update at 2026-01-28 16:45:27

This commit is contained in:
douboer@gmail.com
2026-01-28 16:45:27 +08:00
parent 9fcc29782e
commit b54cc3f623
24 changed files with 39255 additions and 1490 deletions

View File

@@ -39,14 +39,38 @@
"startAngle": 0,
"colorRef": "水",
"sectors": [
{ "content": "乾", "innerFill": 1 },
{ "content": "兑", "innerFill": 0 },
{ "content": "离", "innerFill": 1 },
{ "content": "震", "innerFill": 0 },
{ "content": "巽", "innerFill": 1 },
{ "content": "坎", "innerFill": 0 },
{ "content": "艮", "innerFill": 1 },
{ "content": "坤", "innerFill": 0 }
{
"content": "乾",
"innerFill": 1
},
{
"content": "兑",
"innerFill": 0
},
{
"content": "离",
"innerFill": 1
},
{
"content": "震",
"innerFill": 0
},
{
"content": "巽",
"innerFill": 1
},
{
"content": "坎",
"innerFill": 0
},
{
"content": "艮",
"innerFill": 1
},
{
"content": "坤",
"innerFill": 0
}
]
},
{
@@ -58,18 +82,58 @@
"num": 3,
"interval": 1,
"sectors": [
{ "content": "子", "colorRef": "水", "innerFill": 1 },
{ "content": "丑" },
{ "content": "寅", "colorRef": "木", "innerFill": 0 },
{ "content": "卯", "colorRef": "木", "innerFill": 1 },
{ "content": "辰" },
{ "content": "巳", "colorRef": "火", "innerFill": 1 },
{ "content": "午", "colorRef": "火", "innerFill": 0 },
{ "content": "未", "innerFill": 1 },
{ "content": "申", "colorRef": "金", "innerFill": 0 },
{ "content": "酉", "colorRef": "金", "innerFill": 1 },
{ "content": "戌" },
{ "content": "亥", "innerFill": 0 }
{
"content": "子",
"colorRef": "水",
"innerFill": 1
},
{
"content": "丑"
},
{
"content": "寅",
"colorRef": "木",
"innerFill": 0
},
{
"content": "卯",
"colorRef": "木",
"innerFill": 1
},
{
"content": "辰"
},
{
"content": "巳",
"colorRef": "火",
"innerFill": 1
},
{
"content": "午",
"colorRef": "火",
"innerFill": 0
},
{
"content": "未",
"innerFill": 1
},
{
"content": "申",
"colorRef": "金",
"innerFill": 0
},
{
"content": "酉",
"colorRef": "金",
"innerFill": 1
},
{
"content": "戌"
},
{
"content": "亥",
"innerFill": 0
}
]
},
{
@@ -78,30 +142,88 @@
"rOuter": 240,
"startAngle": 0,
"sectors": [
{ "content": "甲乙|子|丙丁", "colorRef": "木", "innerFill": 1 },
{ "content": "丑" },
{ "content": "戊己|寅|庚辛", "colorRef": "", "innerFill": 0 },
{ "content": "卯" },
{ "content": "壬癸|辰|甲乙", "innerFill": 1 },
{ "content": "巳" },
{ "content": "丙丁|午|戊己", "colorRef": "火", "innerFill": 0 },
{ "content": "未" },
{ "content": "庚辛|申|壬癸", "colorRef": "金", "innerFill": 1 },
{ "content": "酉" },
{ "content": "甲乙|戌|丙丁", "innerFill": 0 },
{ "content": "亥" },
{ "content": "戊己|子|庚辛" },
{ "content": "丑" },
{ "content": "壬癸|寅|甲乙" },
{ "content": "卯" },
{ "content": "丙丁|辰|戊己" },
{ "content": "巳" },
{ "content": "庚辛|午|壬癸" },
{ "content": "未" },
{ "content": "甲乙|申|丙丁" },
{ "content": "" },
{ "content": "戊己|戌|庚辛" },
{ "content": "亥" }
{
"content": "甲乙|子|丙丁",
"colorRef": "",
"innerFill": 1
},
{
"content": "丑"
},
{
"content": "戊己|寅|庚辛",
"colorRef": "土",
"innerFill": 0
},
{
"content": "卯"
},
{
"content": "壬癸|辰|甲乙",
"innerFill": 1
},
{
"content": ""
},
{
"content": "丙丁|午|戊己",
"colorRef": "火",
"innerFill": 0
},
{
"content": "未"
},
{
"content": "庚辛|申|壬癸",
"colorRef": "金",
"innerFill": 1
},
{
"content": "酉"
},
{
"content": "甲乙|戌|丙丁",
"innerFill": 0
},
{
"content": "亥"
},
{
"content": "戊己|子|庚辛"
},
{
"content": "丑"
},
{
"content": "壬癸|寅|甲乙"
},
{
"content": "卯"
},
{
"content": "丙丁|辰|戊己"
},
{
"content": "巳"
},
{
"content": "庚辛|午|壬癸"
},
{
"content": "未"
},
{
"content": "甲乙|申|丙丁"
},
{
"content": "酉"
},
{
"content": "戊己|戌|庚辛"
},
{
"content": "亥"
}
]
},
{
@@ -110,22 +232,81 @@
"rOuter": 270,
"startAngle": 0,
"sectors": [
{ "content": "甲乙|丙丁|戊己|庚辛", "colorRef": "木", "innerFill": 1 },
{ "content": "壬癸|甲乙|丙丁|戊己", "colorRef": "土", "innerFill": 0 },
{ "content": "庚辛|壬癸|甲乙|丙丁", "innerFill": 1 },
{ "content": "戊己|庚辛|壬癸|甲乙", "colorRef": "火", "innerFill": 0 },
{ "content": "丙丁|戊己|庚辛|壬癸", "colorRef": "金", "innerFill": 1 },
{ "content": "子丑|寅卯|辰巳|午未", "innerFill": 0 },
{ "content": "申酉|戌亥|子丑|寅卯", "colorRef": "木", "innerFill": 1 },
{ "content": "辰巳|午未|申酉|戌亥", "colorRef": "水", "innerFill": 0 },
{ "content": "甲乙|丙丁|戊己|庚辛", "innerFill": 1 },
{ "content": "壬癸|甲乙|丙丁|戊己", "colorRef": "土", "innerFill": 0 },
{ "content": "庚辛|壬癸|甲乙|丙丁", "colorRef": "木", "innerFill": 1 },
{ "content": "戊己|庚辛|壬癸|甲乙", "innerFill": 0 },
{ "content": "丙丁|戊己|庚辛|壬癸", "colorRef": "火", "innerFill": 1 },
{ "content": "子丑|寅卯|辰巳|午未", "colorRef": "金", "innerFill": 0 },
{ "content": "申酉|戌亥|子丑|寅卯", "innerFill": 1 },
{ "content": "辰巳|午未|申酉|戌亥", "colorRef": "水", "innerFill": 0 }
{
"content": "甲乙|丙丁|戊己|庚辛",
"colorRef": "木",
"innerFill": 1
},
{
"content": "壬癸|甲乙|丙丁|戊己",
"colorRef": "土",
"innerFill": 0
},
{
"content": "庚辛|壬癸|甲乙|丙丁",
"innerFill": 1
},
{
"content": "戊己|庚辛|壬癸|甲乙",
"colorRef": "火",
"innerFill": 0
},
{
"content": "丙丁|戊己|庚辛|壬癸",
"colorRef": "金",
"innerFill": 1
},
{
"content": "子丑|寅卯|辰巳|午未",
"innerFill": 0
},
{
"content": "申酉|戌亥|子丑|寅卯",
"colorRef": "木",
"innerFill": 1
},
{
"content": "辰巳|午未|申酉|戌亥",
"colorRef": "水",
"innerFill": 0
},
{
"content": "甲乙|丙丁|戊己|庚辛",
"innerFill": 1
},
{
"content": "壬癸|甲乙|丙丁|戊己",
"colorRef": "土",
"innerFill": 0
},
{
"content": "庚辛|壬癸|甲乙|丙丁",
"colorRef": "木",
"innerFill": 1
},
{
"content": "戊己|庚辛|壬癸|甲乙",
"innerFill": 0
},
{
"content": "丙丁|戊己|庚辛|壬癸",
"colorRef": "火",
"innerFill": 1
},
{
"content": "子丑|寅卯|辰巳|午未",
"colorRef": "金",
"innerFill": 0
},
{
"content": "申酉|戌亥|子丑|寅卯",
"innerFill": 1
},
{
"content": "辰巳|午未|申酉|戌亥",
"colorRef": "水",
"innerFill": 0
}
]
},
{
@@ -141,20 +322,18 @@
},
{
"type": "degreeRing",
"degreeRing": {
"rInner": 350,
"rOuter": 380,
"showDegree": 1,
"mode": "both",
"opacity": 1,
"tickLength": 6,
"tickLengthStep": 2,
"majorTick": 10,
"minorTick": 5,
"microTick": 1,
"tickColor": "#000000",
"ringColor": "#000000"
}
"rInner": 350,
"rOuter": 380,
"showDegree": 1,
"mode": "both",
"opacity": 1,
"tickLength": 6,
"tickLengthStep": 2,
"majorTick": 10,
"minorTick": 5,
"microTick": 1,
"tickColor": "#000000",
"ringColor": "#000000"
}
]
}

View File

@@ -1,12 +1,12 @@
{
"name": "demo2",
"description": "luopan demo2 config with named color palettes",
"background": "#000000",
"background": "#000000",
"strokeWidth": 1,
"strokeOpacity": 1,
"strokeColor": "白",
"insetDistance": 1,
"outerRadius": 500,
"outerRadius": 500,
"themeRef": "五行弱",
"layers": [
{
@@ -18,21 +18,20 @@
}
},
{
"divisions": 2,
"rInner": 60,
"rOuter": 90,
"startAngle": 0,
"divisions": 2,
"rInner": 60,
"rOuter": 90,
"startAngle": 0,
"sectors": [
{
"content": "阴",
"colorRef": "黑",
"innerFill": 1
{
"content": "阴",
"colorRef": "黑",
"innerFill": 1
},
{
"content": "阳",
"colorRef": "白",
"innerFill": 0
{
"content": "阳",
"colorRef": "白",
"innerFill": 0
}
]
},
@@ -42,14 +41,38 @@
"rOuter": 160,
"startAngle": 0,
"sectors": [
{ "content": "乾", "innerFill": 1 },
{ "content": "兑", "innerFill": 0 },
{ "content": "离", "innerFill": 1 },
{ "content": "震", "innerFill": 0 },
{ "content": "巽", "innerFill": 1 },
{ "content": "坎", "innerFill": 0 },
{ "content": "艮", "innerFill": 1 },
{ "content": "坤", "innerFill": 0 }
{
"content": "乾",
"innerFill": 1
},
{
"content": "兑",
"innerFill": 0
},
{
"content": "离",
"innerFill": 1
},
{
"content": "震",
"innerFill": 0
},
{
"content": "巽",
"innerFill": 1
},
{
"content": "坎",
"innerFill": 0
},
{
"content": "艮",
"innerFill": 1
},
{
"content": "坤",
"innerFill": 0
}
]
},
{
@@ -62,18 +85,58 @@
"num": 3,
"interval": 1,
"sectors": [
{ "content": "子", "colorRef": "水", "innerFill": 1 },
{ "content": "" },
{ "content": "寅", "colorRef": "木", "innerFill": 0 },
{ "content": "卯", "colorRef": "木", "innerFill": 1 },
{ "content": "辰" },
{ "content": "巳", "colorRef": "火", "innerFill": 1 },
{ "content": "午", "colorRef": "火", "innerFill": 0 },
{ "content": "未", "innerFill": 1 },
{ "content": "申", "colorRef": "金", "innerFill": 0 },
{ "content": "酉", "colorRef": "金", "innerFill": 1 },
{ "content": "" },
{ "content": "亥", "innerFill": 0 }
{
"content": "",
"colorRef": "水",
"innerFill": 1
},
{
"content": "丑"
},
{
"content": "寅",
"colorRef": "",
"innerFill": 0
},
{
"content": "卯",
"colorRef": "木",
"innerFill": 1
},
{
"content": "辰"
},
{
"content": "巳",
"colorRef": "火",
"innerFill": 1
},
{
"content": "午",
"colorRef": "火",
"innerFill": 0
},
{
"content": "未",
"innerFill": 1
},
{
"content": "申",
"colorRef": "金",
"innerFill": 0
},
{
"content": "酉",
"colorRef": "金",
"innerFill": 1
},
{
"content": "戌"
},
{
"content": "亥",
"innerFill": 0
}
]
},
{
@@ -82,30 +145,88 @@
"rOuter": 240,
"startAngle": 0,
"sectors": [
{ "content": "甲乙|子|丙丁", "colorRef": "木", "innerFill": 1 },
{ "content": "丑" },
{ "content": "戊己|寅|庚辛", "colorRef": "", "innerFill": 0 },
{ "content": "卯" },
{ "content": "壬癸|辰|甲乙", "innerFill": 1 },
{ "content": "巳" },
{ "content": "丙丁|午|戊己", "colorRef": "火", "innerFill": 0 },
{ "content": "未" },
{ "content": "庚辛|申|壬癸", "colorRef": "金", "innerFill": 1 },
{ "content": "酉" },
{ "content": "甲乙|戌|丙丁", "innerFill": 0 },
{ "content": "亥" },
{ "content": "戊己|子|庚辛" },
{ "content": "丑" },
{ "content": "壬癸|寅|甲乙" },
{ "content": "卯" },
{ "content": "丙丁|辰|戊己" },
{ "content": "巳" },
{ "content": "庚辛|午|壬癸" },
{ "content": "未" },
{ "content": "甲乙|申|丙丁" },
{ "content": "" },
{ "content": "戊己|戌|庚辛" },
{ "content": "亥" }
{
"content": "甲乙|子|丙丁",
"colorRef": "",
"innerFill": 1
},
{
"content": "丑"
},
{
"content": "戊己|寅|庚辛",
"colorRef": "土",
"innerFill": 0
},
{
"content": "卯"
},
{
"content": "壬癸|辰|甲乙",
"innerFill": 1
},
{
"content": ""
},
{
"content": "丙丁|午|戊己",
"colorRef": "火",
"innerFill": 0
},
{
"content": "未"
},
{
"content": "庚辛|申|壬癸",
"colorRef": "金",
"innerFill": 1
},
{
"content": "酉"
},
{
"content": "甲乙|戌|丙丁",
"innerFill": 0
},
{
"content": "亥"
},
{
"content": "戊己|子|庚辛"
},
{
"content": "丑"
},
{
"content": "壬癸|寅|甲乙"
},
{
"content": "卯"
},
{
"content": "丙丁|辰|戊己"
},
{
"content": "巳"
},
{
"content": "庚辛|午|壬癸"
},
{
"content": "未"
},
{
"content": "甲乙|申|丙丁"
},
{
"content": "酉"
},
{
"content": "戊己|戌|庚辛"
},
{
"content": "亥"
}
]
},
{
@@ -114,22 +235,81 @@
"rOuter": 270,
"startAngle": 0,
"sectors": [
{ "content": "甲乙|丙丁|戊己|庚辛", "colorRef": "木", "innerFill": 1 },
{ "content": "壬癸|甲乙|丙丁|戊己", "colorRef": "土", "innerFill": 0 },
{ "content": "庚辛|壬癸|甲乙|丙丁", "innerFill": 1 },
{ "content": "戊己|庚辛|壬癸|甲乙", "colorRef": "火", "innerFill": 0 },
{ "content": "丙丁|戊己|庚辛|壬癸", "colorRef": "金", "innerFill": 1 },
{ "content": "子丑|寅卯|辰巳|午未", "innerFill": 0 },
{ "content": "申酉|戌亥|子丑|寅卯", "colorRef": "木", "innerFill": 1 },
{ "content": "辰巳|午未|申酉|戌亥", "colorRef": "水", "innerFill": 0 },
{ "content": "甲乙|丙丁|戊己|庚辛", "innerFill": 1 },
{ "content": "壬癸|甲乙|丙丁|戊己", "colorRef": "土", "innerFill": 0 },
{ "content": "庚辛|壬癸|甲乙|丙丁", "colorRef": "木", "innerFill": 1 },
{ "content": "戊己|庚辛|壬癸|甲乙", "innerFill": 0 },
{ "content": "丙丁|戊己|庚辛|壬癸", "colorRef": "火", "innerFill": 1 },
{ "content": "子丑|寅卯|辰巳|午未", "colorRef": "金", "innerFill": 0 },
{ "content": "申酉|戌亥|子丑|寅卯", "innerFill": 1 },
{ "content": "辰巳|午未|申酉|戌亥", "colorRef": "水", "innerFill": 0 }
{
"content": "甲乙|丙丁|戊己|庚辛",
"colorRef": "木",
"innerFill": 1
},
{
"content": "壬癸|甲乙|丙丁|戊己",
"colorRef": "土",
"innerFill": 0
},
{
"content": "庚辛|壬癸|甲乙|丙丁",
"innerFill": 1
},
{
"content": "戊己|庚辛|壬癸|甲乙",
"colorRef": "火",
"innerFill": 0
},
{
"content": "丙丁|戊己|庚辛|壬癸",
"colorRef": "金",
"innerFill": 1
},
{
"content": "子丑|寅卯|辰巳|午未",
"innerFill": 0
},
{
"content": "申酉|戌亥|子丑|寅卯",
"colorRef": "木",
"innerFill": 1
},
{
"content": "辰巳|午未|申酉|戌亥",
"colorRef": "水",
"innerFill": 0
},
{
"content": "甲乙|丙丁|戊己|庚辛",
"innerFill": 1
},
{
"content": "壬癸|甲乙|丙丁|戊己",
"colorRef": "土",
"innerFill": 0
},
{
"content": "庚辛|壬癸|甲乙|丙丁",
"colorRef": "木",
"innerFill": 1
},
{
"content": "戊己|庚辛|壬癸|甲乙",
"innerFill": 0
},
{
"content": "丙丁|戊己|庚辛|壬癸",
"colorRef": "火",
"innerFill": 1
},
{
"content": "子丑|寅卯|辰巳|午未",
"colorRef": "金",
"innerFill": 0
},
{
"content": "申酉|戌亥|子丑|寅卯",
"innerFill": 1
},
{
"content": "辰巳|午未|申酉|戌亥",
"colorRef": "水",
"innerFill": 0
}
]
},
{
@@ -145,19 +325,17 @@
},
{
"type": "degreeRing",
"degreeRing": {
"rInner": 450,
"rOuter": 500,
"showDegree": 1,
"mode": "both",
"opacity": 0.3,
"tickLength": 6,
"majorTick": 10,
"minorTick": 5,
"microTick": 1,
"tickColor": "#ffffff",
"ringColor": "#ffffff"
}
"rInner": 450,
"rOuter": 500,
"showDegree": 1,
"mode": "both",
"opacity": 0.3,
"tickLength": 6,
"majorTick": 10,
"minorTick": 5,
"microTick": 1,
"tickColor": "#ffffff",
"ringColor": "#ffffff"
}
]
}

View File

@@ -39,14 +39,38 @@
"startAngle": 0,
"colorRef": "水",
"sectors": [
{ "content": "乾", "innerFill": 1 },
{ "content": "兑", "innerFill": 0 },
{ "content": "离", "innerFill": 1 },
{ "content": "震", "innerFill": 0 },
{ "content": "巽", "innerFill": 1 },
{ "content": "坎", "innerFill": 0 },
{ "content": "艮", "innerFill": 1 },
{ "content": "坤", "innerFill": 0 }
{
"content": "乾",
"innerFill": 1
},
{
"content": "兑",
"innerFill": 0
},
{
"content": "离",
"innerFill": 1
},
{
"content": "震",
"innerFill": 0
},
{
"content": "巽",
"innerFill": 1
},
{
"content": "坎",
"innerFill": 0
},
{
"content": "艮",
"innerFill": 1
},
{
"content": "坤",
"innerFill": 0
}
]
},
{
@@ -58,18 +82,58 @@
"num": 3,
"interval": 1,
"sectors": [
{ "content": "子", "colorRef": "水", "innerFill": 1 },
{ "content": "丑" },
{ "content": "寅", "colorRef": "木", "innerFill": 0 },
{ "content": "卯", "colorRef": "木", "innerFill": 1 },
{ "content": "辰" },
{ "content": "巳", "colorRef": "火", "innerFill": 1 },
{ "content": "午", "colorRef": "火", "innerFill": 0 },
{ "content": "未", "innerFill": 1 },
{ "content": "申", "colorRef": "金", "innerFill": 0 },
{ "content": "酉", "colorRef": "金", "innerFill": 1 },
{ "content": "戌" },
{ "content": "亥", "innerFill": 0 }
{
"content": "子",
"colorRef": "水",
"innerFill": 1
},
{
"content": "丑"
},
{
"content": "寅",
"colorRef": "木",
"innerFill": 0
},
{
"content": "卯",
"colorRef": "木",
"innerFill": 1
},
{
"content": "辰"
},
{
"content": "巳",
"colorRef": "火",
"innerFill": 1
},
{
"content": "午",
"colorRef": "火",
"innerFill": 0
},
{
"content": "未",
"innerFill": 1
},
{
"content": "申",
"colorRef": "金",
"innerFill": 0
},
{
"content": "酉",
"colorRef": "金",
"innerFill": 1
},
{
"content": "戌"
},
{
"content": "亥",
"innerFill": 0
}
]
},
{
@@ -78,30 +142,88 @@
"rOuter": 240,
"startAngle": 0,
"sectors": [
{ "content": "甲乙|子|丙丁", "colorRef": "木", "innerFill": 1 },
{ "content": "丑" },
{ "content": "戊己|寅|庚辛", "colorRef": "", "innerFill": 0 },
{ "content": "卯" },
{ "content": "壬癸|辰|甲乙", "innerFill": 1 },
{ "content": "巳" },
{ "content": "丙丁|午|戊己", "colorRef": "火", "innerFill": 0 },
{ "content": "未" },
{ "content": "庚辛|申|壬癸", "colorRef": "金", "innerFill": 1 },
{ "content": "酉" },
{ "content": "甲乙|戌|丙丁", "innerFill": 0 },
{ "content": "亥" },
{ "content": "戊己|子|庚辛" },
{ "content": "丑" },
{ "content": "壬癸|寅|甲乙" },
{ "content": "卯" },
{ "content": "丙丁|辰|戊己" },
{ "content": "巳" },
{ "content": "庚辛|午|壬癸" },
{ "content": "未" },
{ "content": "甲乙|申|丙丁" },
{ "content": "" },
{ "content": "戊己|戌|庚辛" },
{ "content": "亥" }
{
"content": "甲乙|子|丙丁",
"colorRef": "",
"innerFill": 1
},
{
"content": "丑"
},
{
"content": "戊己|寅|庚辛",
"colorRef": "土",
"innerFill": 0
},
{
"content": "卯"
},
{
"content": "壬癸|辰|甲乙",
"innerFill": 1
},
{
"content": ""
},
{
"content": "丙丁|午|戊己",
"colorRef": "火",
"innerFill": 0
},
{
"content": "未"
},
{
"content": "庚辛|申|壬癸",
"colorRef": "金",
"innerFill": 1
},
{
"content": "酉"
},
{
"content": "甲乙|戌|丙丁",
"innerFill": 0
},
{
"content": "亥"
},
{
"content": "戊己|子|庚辛"
},
{
"content": "丑"
},
{
"content": "壬癸|寅|甲乙"
},
{
"content": "卯"
},
{
"content": "丙丁|辰|戊己"
},
{
"content": "巳"
},
{
"content": "庚辛|午|壬癸"
},
{
"content": "未"
},
{
"content": "甲乙|申|丙丁"
},
{
"content": "酉"
},
{
"content": "戊己|戌|庚辛"
},
{
"content": "亥"
}
]
},
{
@@ -110,22 +232,81 @@
"rOuter": 270,
"startAngle": 0,
"sectors": [
{ "content": "甲乙|丙丁|戊己|庚辛", "colorRef": "木", "innerFill": 1 },
{ "content": "壬癸|甲乙|丙丁|戊己", "colorRef": "土", "innerFill": 0 },
{ "content": "庚辛|壬癸|甲乙|丙丁", "innerFill": 1 },
{ "content": "戊己|庚辛|壬癸|甲乙", "colorRef": "火", "innerFill": 0 },
{ "content": "丙丁|戊己|庚辛|壬癸", "colorRef": "金", "innerFill": 1 },
{ "content": "子丑|寅卯|辰巳|午未", "innerFill": 0 },
{ "content": "申酉|戌亥|子丑|寅卯", "colorRef": "木", "innerFill": 1 },
{ "content": "辰巳|午未|申酉|戌亥", "colorRef": "水", "innerFill": 0 },
{ "content": "甲乙|丙丁|戊己|庚辛", "innerFill": 1 },
{ "content": "壬癸|甲乙|丙丁|戊己", "colorRef": "土", "innerFill": 0 },
{ "content": "庚辛|壬癸|甲乙|丙丁", "colorRef": "木", "innerFill": 1 },
{ "content": "戊己|庚辛|壬癸|甲乙", "innerFill": 0 },
{ "content": "丙丁|戊己|庚辛|壬癸", "colorRef": "火", "innerFill": 1 },
{ "content": "子丑|寅卯|辰巳|午未", "colorRef": "金", "innerFill": 0 },
{ "content": "申酉|戌亥|子丑|寅卯", "innerFill": 1 },
{ "content": "辰巳|午未|申酉|戌亥", "colorRef": "水", "innerFill": 0 }
{
"content": "甲乙|丙丁|戊己|庚辛",
"colorRef": "木",
"innerFill": 1
},
{
"content": "壬癸|甲乙|丙丁|戊己",
"colorRef": "土",
"innerFill": 0
},
{
"content": "庚辛|壬癸|甲乙|丙丁",
"innerFill": 1
},
{
"content": "戊己|庚辛|壬癸|甲乙",
"colorRef": "火",
"innerFill": 0
},
{
"content": "丙丁|戊己|庚辛|壬癸",
"colorRef": "金",
"innerFill": 1
},
{
"content": "子丑|寅卯|辰巳|午未",
"innerFill": 0
},
{
"content": "申酉|戌亥|子丑|寅卯",
"colorRef": "木",
"innerFill": 1
},
{
"content": "辰巳|午未|申酉|戌亥",
"colorRef": "水",
"innerFill": 0
},
{
"content": "甲乙|丙丁|戊己|庚辛",
"innerFill": 1
},
{
"content": "壬癸|甲乙|丙丁|戊己",
"colorRef": "土",
"innerFill": 0
},
{
"content": "庚辛|壬癸|甲乙|丙丁",
"colorRef": "木",
"innerFill": 1
},
{
"content": "戊己|庚辛|壬癸|甲乙",
"innerFill": 0
},
{
"content": "丙丁|戊己|庚辛|壬癸",
"colorRef": "火",
"innerFill": 1
},
{
"content": "子丑|寅卯|辰巳|午未",
"colorRef": "金",
"innerFill": 0
},
{
"content": "申酉|戌亥|子丑|寅卯",
"innerFill": 1
},
{
"content": "辰巳|午未|申酉|戌亥",
"colorRef": "水",
"innerFill": 0
}
]
},
{
@@ -141,20 +322,18 @@
},
{
"type": "degreeRing",
"degreeRing": {
"rInner": 350,
"rOuter": 380,
"showDegree": 1,
"mode": "both",
"opacity": 1,
"tickLength": 6,
"tickLengthStep": 2,
"majorTick": 10,
"minorTick": 5,
"microTick": 1,
"tickColor": "#000000",
"ringColor": "#000000"
}
"rInner": 350,
"rOuter": 380,
"showDegree": 1,
"mode": "both",
"opacity": 1,
"tickLength": 6,
"tickLengthStep": 2,
"majorTick": 10,
"minorTick": 5,
"microTick": 1,
"tickColor": "#000000",
"ringColor": "#000000"
}
]
}

File diff suppressed because one or more lines are too long

3766
public/fengshui10.json Normal file

File diff suppressed because it is too large Load Diff

3768
public/fengshui2.json Normal file

File diff suppressed because it is too large Load Diff

3768
public/fengshui3.json Normal file

File diff suppressed because it is too large Load Diff

3768
public/fengshui4.json Normal file

File diff suppressed because it is too large Load Diff

3768
public/fengshui5.json Normal file

File diff suppressed because it is too large Load Diff

3768
public/fengshui6.json Normal file

File diff suppressed because it is too large Load Diff

3768
public/fengshui7.json Normal file

File diff suppressed because it is too large Load Diff

3768
public/fengshui8.json Normal file

File diff suppressed because it is too large Load Diff

3768
public/fengshui9.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,42 @@
{
"name": "fengshui",
"path": "/fengshui.json"
},
{
"name": "fengshui2",
"path": "/fengshui2.json"
},
{
"name": "fengshui3",
"path": "/fengshui3.json"
},
{
"name": "fengshui4",
"path": "/fengshui4.json"
},
{
"name": "fengshui5",
"path": "/fengshui5.json"
},
{
"name": "fengshui6",
"path": "/fengshui6.json"
},
{
"name": "fengshui7",
"path": "/fengshui7.json"
},
{
"name": "fengshui8",
"path": "/fengshui8.json"
},
{
"name": "fengshui9",
"path": "/fengshui9.json"
},
{
"name": "fengshui10",
"path": "/fengshui10.json"
}
]
}

View File

@@ -38,14 +38,46 @@
"rOuter": 160,
"startAngle": 0,
"sectors": [
{ "content": "乾", "colorRef": "火", "innerFill": 1 },
{ "content": "兑", "colorRef": "冷", "innerFill": 0 },
{ "content": "离", "colorRef": "火", "innerFill": 1 },
{ "content": "震", "colorRef": "木", "innerFill": 0 },
{ "content": "巽", "colorRef": "木", "innerFill": 1 },
{ "content": "坎", "colorRef": "冷", "innerFill": 0 },
{ "content": "艮", "colorRef": "强", "innerFill": 1 },
{ "content": "坤", "colorRef": "", "innerFill": 0 }
{
"content": "乾",
"colorRef": "火",
"innerFill": 1
},
{
"content": "兑",
"colorRef": "",
"innerFill": 0
},
{
"content": "离",
"colorRef": "火",
"innerFill": 1
},
{
"content": "震",
"colorRef": "木",
"innerFill": 0
},
{
"content": "巽",
"colorRef": "木",
"innerFill": 1
},
{
"content": "坎",
"colorRef": "冷",
"innerFill": 0
},
{
"content": "艮",
"colorRef": "强",
"innerFill": 1
},
{
"content": "坤",
"colorRef": "土",
"innerFill": 0
}
]
},
{
@@ -57,18 +89,58 @@
"num": 3,
"interval": 1,
"sectors": [
{ "content": "子", "colorRef": "水", "innerFill": 1 },
{ "content": "丑" },
{ "content": "寅", "colorRef": "木", "innerFill": 0 },
{ "content": "卯", "colorRef": "木", "innerFill": 1 },
{ "content": "辰" },
{ "content": "巳", "colorRef": "火", "innerFill": 1 },
{ "content": "午", "colorRef": "火", "innerFill": 0 },
{ "content": "未", "innerFill": 1 },
{ "content": "申", "colorRef": "金", "innerFill": 0 },
{ "content": "酉", "colorRef": "金", "innerFill": 1 },
{ "content": "戌" },
{ "content": "亥", "innerFill": 0 }
{
"content": "子",
"colorRef": "水",
"innerFill": 1
},
{
"content": "丑"
},
{
"content": "寅",
"colorRef": "木",
"innerFill": 0
},
{
"content": "卯",
"colorRef": "木",
"innerFill": 1
},
{
"content": "辰"
},
{
"content": "巳",
"colorRef": "火",
"innerFill": 1
},
{
"content": "午",
"colorRef": "火",
"innerFill": 0
},
{
"content": "未",
"innerFill": 1
},
{
"content": "申",
"colorRef": "金",
"innerFill": 0
},
{
"content": "酉",
"colorRef": "金",
"innerFill": 1
},
{
"content": "戌"
},
{
"content": "亥",
"innerFill": 0
}
]
},
{
@@ -80,30 +152,88 @@
"num": 2,
"interval": 1,
"sectors": [
{ "content": "甲乙|子|丙丁", "colorRef": "木", "innerFill": 1 },
{ "content": "丑" },
{ "content": "戊己|寅|庚辛", "colorRef": "", "innerFill": 0 },
{ "content": "卯" },
{ "content": "壬癸|辰|甲乙", "innerFill": 1 },
{ "content": "巳" },
{ "content": "丙丁|午|戊己", "colorRef": "火", "innerFill": 0 },
{ "content": "未" },
{ "content": "庚辛|申|壬癸", "colorRef": "金", "innerFill": 1 },
{ "content": "酉" },
{ "content": "甲乙|戌|丙丁", "innerFill": 0 },
{ "content": "亥" },
{ "content": "戊己|子|庚辛" },
{ "content": "丑" },
{ "content": "壬癸|寅|甲乙" },
{ "content": "卯" },
{ "content": "丙丁|辰|戊己" },
{ "content": "巳" },
{ "content": "庚辛|午|壬癸" },
{ "content": "未" },
{ "content": "甲乙|申|丙丁" },
{ "content": "" },
{ "content": "戊己|戌|庚辛" },
{ "content": "亥" }
{
"content": "甲乙|子|丙丁",
"colorRef": "",
"innerFill": 1
},
{
"content": "丑"
},
{
"content": "戊己|寅|庚辛",
"colorRef": "土",
"innerFill": 0
},
{
"content": "卯"
},
{
"content": "壬癸|辰|甲乙",
"innerFill": 1
},
{
"content": ""
},
{
"content": "丙丁|午|戊己",
"colorRef": "火",
"innerFill": 0
},
{
"content": "未"
},
{
"content": "庚辛|申|壬癸",
"colorRef": "金",
"innerFill": 1
},
{
"content": "酉"
},
{
"content": "甲乙|戌|丙丁",
"innerFill": 0
},
{
"content": "亥"
},
{
"content": "戊己|子|庚辛"
},
{
"content": "丑"
},
{
"content": "壬癸|寅|甲乙"
},
{
"content": "卯"
},
{
"content": "丙丁|辰|戊己"
},
{
"content": "巳"
},
{
"content": "庚辛|午|壬癸"
},
{
"content": "未"
},
{
"content": "甲乙|申|丙丁"
},
{
"content": "酉"
},
{
"content": "戊己|戌|庚辛"
},
{
"content": "亥"
}
]
},
{
@@ -112,22 +242,81 @@
"rOuter": 270,
"startAngle": 0,
"sectors": [
{ "content": "甲乙|丙丁|戊己|庚辛", "colorRef": "木", "innerFill": 1 },
{ "content": "壬癸|甲乙|丙丁|戊己", "colorRef": "土", "innerFill": 0 },
{ "content": "庚辛|壬癸|甲乙|丙丁", "innerFill": 1 },
{ "content": "戊己|庚辛|壬癸|甲乙", "colorRef": "火", "innerFill": 0 },
{ "content": "丙丁|戊己|庚辛|壬癸", "colorRef": "金", "innerFill": 1 },
{ "content": "子丑|寅卯|辰巳|午未", "innerFill": 0 },
{ "content": "申酉|戌亥|子丑|寅卯", "colorRef": "木", "innerFill": 1 },
{ "content": "辰巳|午未|申酉|戌亥", "colorRef": "水", "innerFill": 0 },
{ "content": "甲乙|丙丁|戊己|庚辛", "innerFill": 1 },
{ "content": "壬癸|甲乙|丙丁|戊己", "colorRef": "土", "innerFill": 0 },
{ "content": "庚辛|壬癸|甲乙|丙丁", "colorRef": "木", "innerFill": 1 },
{ "content": "戊己|庚辛|壬癸|甲乙", "innerFill": 0 },
{ "content": "丙丁|戊己|庚辛|壬癸", "colorRef": "火", "innerFill": 1 },
{ "content": "子丑|寅卯|辰巳|午未", "colorRef": "金", "innerFill": 0 },
{ "content": "申酉|戌亥|子丑|寅卯", "innerFill": 1 },
{ "content": "辰巳|午未|申酉|戌亥", "colorRef": "水", "innerFill": 0 }
{
"content": "甲乙|丙丁|戊己|庚辛",
"colorRef": "木",
"innerFill": 1
},
{
"content": "壬癸|甲乙|丙丁|戊己",
"colorRef": "土",
"innerFill": 0
},
{
"content": "庚辛|壬癸|甲乙|丙丁",
"innerFill": 1
},
{
"content": "戊己|庚辛|壬癸|甲乙",
"colorRef": "火",
"innerFill": 0
},
{
"content": "丙丁|戊己|庚辛|壬癸",
"colorRef": "金",
"innerFill": 1
},
{
"content": "子丑|寅卯|辰巳|午未",
"innerFill": 0
},
{
"content": "申酉|戌亥|子丑|寅卯",
"colorRef": "木",
"innerFill": 1
},
{
"content": "辰巳|午未|申酉|戌亥",
"colorRef": "水",
"innerFill": 0
},
{
"content": "甲乙|丙丁|戊己|庚辛",
"innerFill": 1
},
{
"content": "壬癸|甲乙|丙丁|戊己",
"colorRef": "土",
"innerFill": 0
},
{
"content": "庚辛|壬癸|甲乙|丙丁",
"colorRef": "木",
"innerFill": 1
},
{
"content": "戊己|庚辛|壬癸|甲乙",
"innerFill": 0
},
{
"content": "丙丁|戊己|庚辛|壬癸",
"colorRef": "火",
"innerFill": 1
},
{
"content": "子丑|寅卯|辰巳|午未",
"colorRef": "金",
"innerFill": 0
},
{
"content": "申酉|戌亥|子丑|寅卯",
"innerFill": 1
},
{
"content": "辰巳|午未|申酉|戌亥",
"colorRef": "水",
"innerFill": 0
}
]
},
{
@@ -143,20 +332,18 @@
},
{
"type": "degreeRing",
"degreeRing": {
"rInner": 350,
"rOuter": 380,
"showDegree": 1,
"mode": "both",
"opacity": 1,
"tickLength": 6,
"tickLengthStep": 2,
"majorTick": 10,
"minorTick": 5,
"microTick": 1,
"tickColor": "#000000",
"ringColor": "#000000"
}
"rInner": 350,
"rOuter": 380,
"showDegree": 1,
"mode": "both",
"opacity": 1,
"tickLength": 6,
"tickLengthStep": 2,
"majorTick": 10,
"minorTick": 5,
"microTick": 1,
"tickColor": "#000000",
"ringColor": "#000000"
}
]
}

View File

@@ -1,5 +1,20 @@
# 罗盘项目重构方案
## 0. 重构方案提要(先看这一段)
**目标:** 从“硬编码示例”升级到“配置驱动”的罗盘渲染系统,并为大罗盘性能优化留出清晰路径。
**策略:** 先重构数据解析与渲染结构,再完善主题/多文本/刻度/中心图标,最后补齐测试与性能。
**落地路径:**
1) 解析层与类型统一(配置校验、主题、层级半径推导)。
2) 扇区构建器与颜色引擎含规律填色、groupSplit
3) UI 适配Luopan.vue、useLuopan 重构)。
4) 测试与性能优化(含大罗盘拖拽优化计划 10.4)。
**预期产出:**
- 新增/替换若干模块configParser / colorResolver / sectorBuilder / degreeRing / centerIcon
- `public/*.json` 配置即可新增罗盘,无需改代码。
- 统一主题与颜色规则,支持复杂布局与 SVG 图标。
---
## 一、项目现状分析
@@ -103,6 +118,22 @@
- 新增 `public/themes.json`
- 更新 `demo.json.conf` 说明
### 1.5 layerHeight 参数规则(新增)
**目标:** 支持在 layer 中以 `layerHeight` 定义厚度,并可自动推导 `rInner` / `rOuter`。
**规则:**
1. 若 layer 同时指定 `rInner` 和 `rOuter`,则忽略 `layerHeight`。
2. 若 layer 仅指定 `layerHeight`,且没有 `rInner` / `rOuter`
- 使用上一层的 `rOuter` 作为当前 `rInner`
- 当前 `rOuter = 上一层 rOuter + layerHeight`
3. 若 layer 未提供任何半径信息(`rInner` / `rOuter` / `layerHeight`),则视为配置错误(或按实现约定抛错/警告)。
**实现改动点:**
1. `types.ts`:为 sector layer 增加可选字段 `layerHeight?: number`
2. `configParser.ts`:新增“层半径归一化”步骤,按层顺序推导半径
3. 文档/示例:补充 `layerHeight` 的说明与示例用法
---
## 二、整体架构设计

View File

@@ -42,6 +42,7 @@
<div
v-else
class="svg-container"
:class="{ 'is-dragging': isDragging }"
@wheel.prevent="handleWheel"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@@ -53,252 +54,258 @@
:height="size"
:viewBox="`${viewBoxMin} ${viewBoxMin} ${viewBoxSize} ${viewBoxSize}`"
class="svg"
:style="{
transform: `scale(${scale}) translate(${panX}px, ${panY}px)`,
cursor: isDragging ? 'grabbing' : 'grab'
}"
>
<!-- 背景 -->
<circle
:r="outerMost || viewBoxSize / 2"
:fill="config?.background || '#ffffff'"
/>
<g v-memo="[sectors]">
<!-- 扇区 -->
<g>
<path
v-for="s in sectors"
:key="s.key"
:d="s.path"
:fill="s.fill"
<g ref="viewport" class="viewport">
<!-- 背景 -->
<circle
:r="outerMost || viewBoxSize / 2"
:fill="config?.background || '#ffffff'"
/>
</g>
<!-- 内部填色区域 -->
<g>
<template v-for="s in sectors" :key="s.key + '-inner'">
<path
v-if="s.innerFillPath"
:d="s.innerFillPath"
:fill="s.innerFillColor"
fill-opacity="1"
stroke="none"
/>
</template>
</g>
<g v-memo="[sectors]">
<!-- 扇区 -->
<g>
<path
v-for="s in sectors"
:key="s.key"
:d="s.path"
:fill="s.fill"
/>
</g>
<!-- 定义文字路径 -->
<defs>
<path
v-for="s in sectors"
:key="s.textPathId"
:id="s.textPathId"
:d="s.textPath"
fill="none"
/>
<template v-for="s in sectors" :key="s.key + '-units'">
<path
v-for="unit in s.textUnits || []"
:key="unit.textPathId"
:id="unit.textPathId"
:d="unit.textPath"
<!-- 内部填色区域 -->
<g>
<template v-for="s in sectors" :key="s.key + '-inner'">
<path
v-if="s.innerFillPath"
:d="s.innerFillPath"
:fill="s.innerFillColor"
fill-opacity="1"
stroke="none"
/>
</template>
</g>
<!-- 定义文字路径 -->
<defs>
<path
v-for="s in sectors"
:key="s.textPathId"
:id="s.textPathId"
:d="s.textPath"
fill="none"
/>
<template v-for="s in sectors" :key="s.key + '-units'">
<path
v-for="unit in s.textUnits || []"
:key="unit.textPathId"
:id="unit.textPathId"
:d="unit.textPath"
fill="none"
/>
</template>
</defs>
<!-- 文字标签沿圆弧排列 -->
<g>
<template v-for="s in sectors" :key="s.key + '-text'">
<template v-if="s.textUnits">
<g v-for="unit in s.textUnits || []" :key="unit.textPathId">
<text
v-if="!unit.isSvg"
:font-size="unit.fontSize"
:fill="s.textColor"
:writing-mode="unit.isVertical ? 'tb' : undefined"
:glyph-orientation-vertical="unit.isVertical ? '0' : undefined"
:text-anchor="unit.isVertical ? 'middle' : undefined"
style="user-select: none"
>
<textPath
:href="'#' + unit.textPathId"
startOffset="50%"
text-anchor="middle"
dominant-baseline="central"
>
{{ unit.content }}
</textPath>
</text>
<image
v-else
:href="unit.svgPath"
:x="getUnitSvgBox(s, unit).x"
:y="getUnitSvgBox(s, unit).y"
:width="getUnitSvgBox(s, unit).size"
:height="getUnitSvgBox(s, unit).size"
:opacity="s.textColor ? 1 : 1"
/>
</g>
</template>
<template v-else>
<text
v-if="!s.isSvgContent"
:font-size="s.fontSize"
:fill="s.textColor"
:writing-mode="s.isVertical ? 'tb' : undefined"
:glyph-orientation-vertical="s.isVertical ? '0' : undefined"
:text-anchor="s.isVertical ? 'middle' : undefined"
style="user-select: none"
>
<textPath
:href="'#' + s.textPathId"
startOffset="50%"
text-anchor="middle"
dominant-baseline="central"
>
{{ s.label }}
</textPath>
</text>
<image
v-else
:href="s.svgPath"
:x="getSectorSvgBox(s).x"
:y="getSectorSvgBox(s).y"
:width="getSectorSvgBox(s).size"
:height="getSectorSvgBox(s).size"
/>
</template>
</template>
</g>
</g>
<!-- 扇区边界线 -->
<g
v-memo="[sectors, boundaryRings, strokeColor, strokeOpacity, strokeWidth]"
:stroke="strokeColor"
:stroke-opacity="strokeOpacity"
:stroke-width="strokeWidth"
>
<circle
v-for="r in boundaryRings"
:key="'ring-outline-' + r"
:r="r"
fill="none"
/>
</template>
</defs>
<!-- 文字标签沿圆弧排列 -->
<g>
<template v-for="s in sectors" :key="s.key + '-text'">
<template v-if="s.textUnits">
<g v-for="unit in s.textUnits || []" :key="unit.textPathId">
<text
v-if="!unit.isSvg"
:font-size="unit.fontSize"
:fill="s.textColor"
:writing-mode="unit.isVertical ? 'tb' : undefined"
:glyph-orientation-vertical="unit.isVertical ? '0' : undefined"
:text-anchor="unit.isVertical ? 'middle' : undefined"
style="user-select: none"
>
<textPath
:href="'#' + unit.textPathId"
startOffset="50%"
text-anchor="middle"
dominant-baseline="central"
>
{{ unit.content }}
</textPath>
</text>
<image
v-else
:href="unit.svgPath"
:x="getUnitSvgBox(s, unit).x"
:y="getUnitSvgBox(s, unit).y"
:width="getUnitSvgBox(s, unit).size"
:height="getUnitSvgBox(s, unit).size"
:opacity="s.textColor ? 1 : 1"
/>
</g>
</template>
<template v-else>
<text
v-if="!s.isSvgContent"
:font-size="s.fontSize"
:fill="s.textColor"
:writing-mode="s.isVertical ? 'tb' : undefined"
:glyph-orientation-vertical="s.isVertical ? '0' : undefined"
:text-anchor="s.isVertical ? 'middle' : undefined"
style="user-select: none"
>
<textPath
:href="'#' + s.textPathId"
startOffset="50%"
text-anchor="middle"
dominant-baseline="central"
>
{{ s.label }}
</textPath>
</text>
<image
v-else
:href="s.svgPath"
:x="getSectorSvgBox(s).x"
:y="getSectorSvgBox(s).y"
:width="getSectorSvgBox(s).size"
:height="getSectorSvgBox(s).size"
<template v-for="s in sectors" :key="'split-' + s.key">
<line
v-if="s.groupSplitVisible !== false"
:x1="toXY(s.aStart, s.rInner).x"
:y1="toXY(s.aStart, s.rInner).y"
:x2="toXY(s.aStart, s.rOuter).x"
:y2="toXY(s.aStart, s.rOuter).y"
/>
</template>
</g>
</template>
<!-- 形心点仅辅助线开启时显示 -->
<g v-if="showGuides" v-memo="[sectors]">
<circle
v-for="s in sectors"
:key="s.key + '-center'"
:cx="s.cx"
:cy="s.cy"
r="2.2"
fill="#ef4444"
opacity="0.8"
/>
</g>
<!-- 辅助线圆环分度线 -->
<g v-if="showGuides" v-memo="[rings, anglesDeg, outerMost]" stroke="#111827" stroke-opacity="0.18">
<!-- 圆环 -->
<circle
v-for="r in rings"
:key="'ring-' + r"
:r="r"
fill="none"
:stroke-width="SECTOR_STROKE_WIDTH"
/>
<!-- 径向线 -->
<line
v-for="a in anglesDeg"
:key="'ang-' + a"
:x1="0"
:y1="0"
:x2="toXY(a, outerMost).x"
:y2="toXY(a, outerMost).y"
:stroke-width="SECTOR_STROKE_WIDTH"
/>
</g>
<!-- 刻度环 -->
<g v-if="degreeRing" v-memo="[degreeRing]">
<defs>
<path
v-for="label in degreeRing.labels || []"
:key="label.textPathId"
:id="label.textPathId"
:d="label.textPath"
fill="none"
/>
</defs>
<circle
v-if="degreeRing.background"
:r="(degreeRing.ring.rInner + degreeRing.ring.rOuter) / 2"
fill="none"
:stroke="degreeRing.background.color"
:stroke-opacity="degreeRing.background.opacity"
:stroke-width="degreeRing.ring.rOuter - degreeRing.ring.rInner"
/>
<circle
:r="degreeRing.ring.rOuter"
fill="none"
:stroke="degreeRing.ring.color"
:stroke-opacity="degreeRing.ring.opacity"
:stroke-width="SECTOR_STROKE_WIDTH"
/>
<circle
:r="degreeRing.ring.rInner"
fill="none"
:stroke="degreeRing.ring.color"
:stroke-opacity="degreeRing.ring.opacity"
:stroke-width="SECTOR_STROKE_WIDTH"
/>
<line
v-for="tick in degreeRing.ticks"
:key="'tick-' + tick.angle + '-' + tick.startR + '-' + tick.endR"
:x1="tick.x1"
:y1="tick.y1"
:x2="tick.x2"
:y2="tick.y2"
:stroke="degreeRing.tickColor"
:stroke-width="SECTOR_STROKE_WIDTH"
/>
<text
v-for="label in degreeRing.labels || []"
:key="'degree-' + label.angle"
:fill="degreeRing.tickColor"
:font-size="label.fontSize"
style="user-select: none"
>
<textPath
:href="'#' + label.textPathId"
startOffset="50%"
text-anchor="middle"
dominant-baseline="central"
>
{{ label.text }}
</textPath>
</text>
</g>
<!-- 中心图标 -->
<g v-if="centerIcon" v-memo="[centerIcon]">
<image
:href="centerIcon.svgPath"
:width="centerIcon.rIcon * 2"
:height="centerIcon.rIcon * 2"
:x="-centerIcon.rIcon"
:y="-centerIcon.rIcon"
:opacity="centerIcon.opacity"
:transform="`rotate(${centerIcon.rotation})`"
/>
</g>
</g>
</g>
<!-- 扇区边界线 -->
<g
v-memo="[sectors, boundaryRings, strokeColor, strokeOpacity, strokeWidth]"
:stroke="strokeColor"
:stroke-opacity="strokeOpacity"
:stroke-width="strokeWidth"
>
<circle
v-for="r in boundaryRings"
:key="'ring-outline-' + r"
:r="r"
fill="none"
/>
<template v-for="s in sectors" :key="'split-' + s.key">
<line
v-if="s.groupSplitVisible !== false"
:x1="toXY(s.aStart, s.rInner).x"
:y1="toXY(s.aStart, s.rInner).y"
:x2="toXY(s.aStart, s.rOuter).x"
:y2="toXY(s.aStart, s.rOuter).y"
/>
</template>
</g>
<!-- 形心点仅辅助线开启时显示 -->
<g v-if="showGuides" v-memo="[sectors]">
<circle
v-for="s in sectors"
:key="s.key + '-center'"
:cx="s.cx"
:cy="s.cy"
r="2.2"
fill="#ef4444"
opacity="0.8"
/>
</g>
<!-- 辅助线圆环分度线 -->
<g v-if="showGuides" v-memo="[rings, anglesDeg, outerMost]" stroke="#111827" stroke-opacity="0.18">
<!-- 圆环 -->
<circle
v-for="r in rings"
:key="'ring-' + r"
:r="r"
fill="none"
:stroke-width="SECTOR_STROKE_WIDTH"
/>
<!-- 径向线 -->
<line
v-for="a in anglesDeg"
:key="'ang-' + a"
:x1="0"
:y1="0"
:x2="toXY(a, outerMost).x"
:y2="toXY(a, outerMost).y"
:stroke-width="SECTOR_STROKE_WIDTH"
/>
</g>
<!-- 刻度环 -->
<g v-if="degreeRing" v-memo="[degreeRing]">
<defs>
<path
v-for="label in degreeRing.labels || []"
:key="label.textPathId"
:id="label.textPathId"
:d="label.textPath"
fill="none"
/>
</defs>
<circle
:r="degreeRing.ring.rOuter"
fill="none"
:stroke="degreeRing.ring.color"
:stroke-opacity="degreeRing.ring.opacity"
:stroke-width="SECTOR_STROKE_WIDTH"
/>
<circle
:r="degreeRing.ring.rInner"
fill="none"
:stroke="degreeRing.ring.color"
:stroke-opacity="degreeRing.ring.opacity"
:stroke-width="SECTOR_STROKE_WIDTH"
/>
<line
v-for="tick in degreeRing.ticks"
:key="'tick-' + tick.angle + '-' + tick.startR + '-' + tick.endR"
:x1="tick.x1"
:y1="tick.y1"
:x2="tick.x2"
:y2="tick.y2"
:stroke="degreeRing.tickColor"
:stroke-width="SECTOR_STROKE_WIDTH"
/>
<text
v-for="label in degreeRing.labels || []"
:key="'degree-' + label.angle"
:fill="degreeRing.tickColor"
:font-size="label.fontSize"
style="user-select: none"
>
<textPath
:href="'#' + label.textPathId"
startOffset="50%"
text-anchor="middle"
dominant-baseline="central"
>
{{ label.text }}
</textPath>
</text>
</g>
<!-- 中心图标 -->
<g v-if="centerIcon" v-memo="[centerIcon]">
<image
:href="centerIcon.svgPath"
:width="centerIcon.rIcon * 2"
:height="centerIcon.rIcon * 2"
:x="-centerIcon.rIcon"
:y="-centerIcon.rIcon"
:opacity="centerIcon.opacity"
:transform="`rotate(${centerIcon.rotation})`"
/>
</g>
</svg>
</div>
@@ -341,6 +348,7 @@ const textRadialPosition = ref<TextRadialPosition>(DEFAULT_TEXT_RADIAL_POSITION)
const scale = ref(1);
const panX = ref(0);
const panY = ref(0);
const viewport = ref<SVGGElement | null>(null);
let nextScale = scale.value;
let nextPanX = panX.value;
let nextPanY = panY.value;
@@ -421,6 +429,9 @@ const loadConfigList = async () => {
};
onMounted(loadConfigList);
onMounted(() => {
applyViewportTransform(scale.value, panX.value, panY.value);
});
watch(selectedConfigPath, (value, previous) => {
if (value === previous) return;
@@ -472,6 +483,14 @@ const boundaryRings = computed(() => {
return Array.from(set);
});
const applyViewportTransform = (scaleValue: number, panXValue: number, panYValue: number) => {
if (!viewport.value) return;
viewport.value.setAttribute(
'transform',
`scale(${scaleValue}) translate(${panXValue}, ${panYValue})`
);
};
// 使用 `rAF` 合并缩放/拖拽更新,减少渲染频率
const scheduleTransform = () => {
if (rafId !== null) return;
@@ -484,6 +503,7 @@ const scheduleTransform = () => {
scale.value = nextScale;
panX.value = nextPanX;
panY.value = nextPanY;
applyViewportTransform(nextScale, nextPanX, nextPanY);
rafId = null;
});
};
@@ -638,6 +658,12 @@ button.active {
display: flex;
align-items: center;
justify-content: center;
contain: paint;
cursor: grab;
}
.svg-container.is-dragging {
cursor: grabbing;
}
.status {
@@ -650,10 +676,13 @@ button.active {
.svg {
background: transparent;
transition: transform 0.1s ease-out;
user-select: none;
}
.viewport {
will-change: transform;
}
.note {
color: #6b7280;
font-size: 13px;

View File

@@ -16,8 +16,8 @@ import type {
ThemesConfig,
TextRadialPosition,
} from '../types';
import { polarToXY } from '../utils';
import { parseConfig } from '../configParser';
import { getTextColorForBackground, polarToXY } from '../utils';
import { normalizeLayerRadii, parseConfig } from '../configParser';
import { ColorResolver } from '../colorResolver';
import { SectorBuilder } from '../sectorBuilder';
import { buildDegreeRing } from '../degreeRing';
@@ -39,6 +39,29 @@ const resolveThemeColor = (
return theme.colorPalettes[value] ?? fallback;
};
const resolveDegreeRingColor = (
value: string | undefined,
theme: ThemeConfig,
background: string
) => {
if (typeof value === 'string' && value.trim().length > 0) {
return resolveThemeColor(theme, value, value);
}
const contrast = getTextColorForBackground(background);
return contrast === '#ffffff' ? '#ffffff' : '#000000';
};
const resolveDegreeRingBackground = (
value: string | undefined,
theme: ThemeConfig,
fallback: string
) => {
if (typeof value === 'string' && value.trim().length > 0) {
return resolveThemeColor(theme, value, fallback);
}
return fallback;
};
const findDegreeRingLayer = (layers: LayerConfig[]) =>
layers.find((layer) => layer.type === 'degreeRing');
@@ -158,39 +181,45 @@ export function useLuopan(
error.value = null;
let configObj: LuopanConfigInput;
let normalizedLayers: LuopanConfig['layers'];
const configInput = configSource.value;
if (typeof configInput === 'string') {
const jsonText = await fetch(configInput).then((res) => res.text());
configObj = parseConfig(jsonText);
// parseConfig 已完成层归一化,避免重复处理
normalizedLayers = configObj.layers;
} else {
configObj = configInput;
normalizedLayers = normalizeLayerRadii(configObj.layers);
}
const resolvedTheme = await resolveTheme(configObj);
const normalizedConfig = { ...configObj, layers: normalizedLayers };
const resolvedTheme = await resolveTheme(normalizedConfig);
const resolvedBackground = resolveThemeColor(
resolvedTheme,
configObj.background,
normalizedConfig.background,
'#000000'
);
const resolvedStrokeColor = resolveThemeColor(
resolvedTheme,
configObj.strokeColor,
normalizedConfig.strokeColor,
'#1f2937'
);
const resolvedConfig: LuopanConfig = {
...configObj,
...normalizedConfig,
theme: resolvedTheme,
background: resolvedBackground,
strokeColor: resolvedStrokeColor,
strokeWidth: typeof configObj.strokeWidth === 'number'
? configObj.strokeWidth
strokeWidth: typeof normalizedConfig.strokeWidth === 'number'
? normalizedConfig.strokeWidth
: undefined,
strokeOpacity: typeof configObj.strokeOpacity === 'number'
? configObj.strokeOpacity
strokeOpacity: typeof normalizedConfig.strokeOpacity === 'number'
? normalizedConfig.strokeOpacity
: undefined,
insetDistance: typeof configObj.insetDistance === 'number'
? configObj.insetDistance
insetDistance: typeof normalizedConfig.insetDistance === 'number'
? normalizedConfig.insetDistance
: undefined,
};
@@ -198,15 +227,52 @@ export function useLuopan(
sectors.value = buildSectors(resolvedConfig);
const degreeRingLayer = findDegreeRingLayer(resolvedConfig.layers);
degreeRing.value = degreeRingLayer
? buildDegreeRing(degreeRingLayer.degreeRing)
: null;
if (degreeRingLayer) {
const ringBackground = resolveDegreeRingBackground(
degreeRingLayer.degreeRing.colorRef,
resolvedConfig.theme,
resolvedConfig.background
);
const hasBackgroundRef =
typeof degreeRingLayer.degreeRing.colorRef === 'string' &&
degreeRingLayer.degreeRing.colorRef.trim().length > 0;
const tickColor = resolveDegreeRingColor(
degreeRingLayer.degreeRing.tickColor,
resolvedConfig.theme,
ringBackground
);
const ringColor = resolveDegreeRingColor(
degreeRingLayer.degreeRing.ringColor,
resolvedConfig.theme,
ringBackground
);
const ringData = buildDegreeRing({
...degreeRingLayer.degreeRing,
tickColor,
ringColor,
});
degreeRing.value = {
...ringData,
background: hasBackgroundRef
? {
color: ringBackground,
opacity: degreeRingLayer.degreeRing.opacity,
}
: undefined,
};
} else {
degreeRing.value = null;
}
const centerIconLayer = findCenterIconLayer(resolvedConfig.layers);
centerIcon.value = centerIconLayer
? await loadCenterIcon(centerIconLayer.centerIcon)
: null;
} catch (err) {
console.error('加载罗盘配置失败', {
source: typeof configSource.value === 'string' ? configSource.value : 'inline config',
error: err,
});
error.value = err as Error;
} finally {
loading.value = false;

View File

@@ -1,4 +1,13 @@
import type { LuopanConfigInput, ThemeConfig } from './types';
import type {
DegreeRingLayerConfig,
DegreeRingLayerConfigInput,
LayerConfig,
LayerConfigInput,
LuopanConfigInput,
SectorLayerConfig,
SectorLayerConfigInput,
ThemeConfig,
} from './types';
const isObject = (value: unknown): value is Record<string, unknown> =>
typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -9,6 +18,9 @@ const assertCondition = (condition: boolean, message: string): void => {
}
};
const isFiniteNumber = (value: unknown): value is number =>
typeof value === 'number' && Number.isFinite(value);
export const stripJsonComments = (input: string): string => {
let output = '';
let inString = false;
@@ -53,6 +65,175 @@ const normalizeTheme = (theme: Record<string, unknown>): ThemeConfig => {
};
};
const normalizeSectorLayer = (
layer: SectorLayerConfigInput,
index: number,
lastOuter: number | null
): { layer: SectorLayerConfig; nextOuter: number } => {
const rInnerRaw = layer.rInner;
const rOuterRaw = layer.rOuter;
const layerHeightRaw = layer.layerHeight;
const hasRInner = rInnerRaw !== undefined;
const hasROuter = rOuterRaw !== undefined;
if (hasRInner && !isFiniteNumber(rInnerRaw)) {
throw new Error(`layers[${index}].rInner 必须为数字`);
}
if (hasROuter && !isFiniteNumber(rOuterRaw)) {
throw new Error(`layers[${index}].rOuter 必须为数字`);
}
if (hasRInner || hasROuter) {
assertCondition(
hasRInner && hasROuter,
`layers[${index}] 必须同时提供 rInner 与 rOuter`
);
const rInner = rInnerRaw as number;
const rOuter = rOuterRaw as number;
assertCondition(rOuter > rInner, `layers[${index}] rOuter 必须大于 rInner`);
return {
layer: { ...layer, rInner, rOuter },
nextOuter: rOuter,
};
}
if (layerHeightRaw === undefined) {
throw new Error(`layers[${index}] 缺少 rInner/rOuter 或 layerHeight`);
}
if (!isFiniteNumber(layerHeightRaw)) {
throw new Error(`layers[${index}].layerHeight 必须为数字`);
}
assertCondition(
layerHeightRaw > 0,
`layers[${index}].layerHeight 必须大于 0`
);
assertCondition(
lastOuter !== null,
`layers[${index}] 使用 layerHeight 时必须有上一层 rOuter`
);
const rInner = lastOuter as number;
const rOuter = rInner + layerHeightRaw;
return {
layer: { ...layer, rInner, rOuter },
nextOuter: rOuter,
};
};
const normalizeDegreeRingLayer = (
layer: DegreeRingLayerConfigInput,
index: number,
lastOuter: number | null
): { layer: DegreeRingLayerConfig; nextOuter: number } => {
if (Object.prototype.hasOwnProperty.call(layer, 'degreeRing')) {
const keys = Object.keys(layer).join(', ');
console.error('检测到废弃的 degreeRing 嵌套配置', {
index,
keys,
layer,
});
throw new Error(
`layers[${index}].degreeRing 已废弃请改为扁平写法keys: ${keys}`
);
}
const { type: _type, layerHeight: layerHeightRaw, ...degreeRing } = layer;
const rInnerRaw = degreeRing.rInner;
const rOuterRaw = degreeRing.rOuter;
const hasRInner = rInnerRaw !== undefined;
const hasROuter = rOuterRaw !== undefined;
if (hasRInner && !isFiniteNumber(rInnerRaw)) {
throw new Error(`layers[${index}].rInner 必须为数字`);
}
if (hasROuter && !isFiniteNumber(rOuterRaw)) {
throw new Error(`layers[${index}].rOuter 必须为数字`);
}
if (hasRInner || hasROuter) {
assertCondition(
hasRInner && hasROuter,
`layers[${index}] 必须同时提供 rInner 与 rOuter`
);
const rInner = rInnerRaw as number;
const rOuter = rOuterRaw as number;
assertCondition(
rOuter > rInner,
`layers[${index}].rOuter 必须大于 rInner`
);
return {
layer: {
type: 'degreeRing',
layerHeight: layerHeightRaw,
degreeRing: { ...degreeRing, rInner, rOuter },
},
nextOuter: rOuter,
};
}
if (layerHeightRaw === undefined) {
throw new Error(`layers[${index}] 缺少 rInner/rOuter 或 layerHeight`);
}
if (!isFiniteNumber(layerHeightRaw)) {
throw new Error(`layers[${index}].layerHeight 必须为数字`);
}
assertCondition(
layerHeightRaw > 0,
`layers[${index}].layerHeight 必须大于 0`
);
assertCondition(
lastOuter !== null,
`layers[${index}] 使用 layerHeight 时必须有上一层 rOuter`
);
const rInner = lastOuter as number;
const rOuter = rInner + layerHeightRaw;
return {
layer: {
type: 'degreeRing',
layerHeight: layerHeightRaw,
degreeRing: { ...degreeRing, rInner, rOuter },
},
nextOuter: rOuter,
};
};
export const normalizeLayerRadii = (layers: LayerConfigInput[]): LayerConfig[] => {
const normalized: LayerConfig[] = [];
let lastOuter: number | null = null;
layers.forEach((layer, index) => {
assertCondition(isObject(layer), `layers[${index}] 必须为对象`);
const type = (layer as Record<string, unknown>).type;
if (type === 'centerIcon') {
normalized.push(layer as LayerConfig);
return;
}
if (type === 'degreeRing') {
const { layer: degreeRingLayer, nextOuter } = normalizeDegreeRingLayer(
layer as DegreeRingLayerConfigInput,
index,
lastOuter
);
normalized.push(degreeRingLayer);
lastOuter = nextOuter;
return;
}
const { layer: sectorLayer, nextOuter } = normalizeSectorLayer(
layer as SectorLayerConfigInput,
index,
lastOuter
);
normalized.push(sectorLayer);
lastOuter = nextOuter;
});
return normalized;
};
export const parseConfig = (jsonText: string): LuopanConfigInput => {
const cleanText = stripJsonComments(jsonText);
let parsed: unknown;
@@ -73,6 +254,7 @@ export const parseConfig = (jsonText: string): LuopanConfigInput => {
assertCondition(isObject(config.theme), 'theme 必须为对象');
}
assertCondition(Array.isArray(config.layers), 'layers 为必填数组');
const normalizedLayers = normalizeLayerRadii(config.layers as LayerConfigInput[]);
return {
name: config.name,
@@ -85,6 +267,6 @@ export const parseConfig = (jsonText: string): LuopanConfigInput => {
outerRadius: typeof config.outerRadius === 'number' ? config.outerRadius : undefined,
themeRef: typeof config.themeRef === 'string' ? config.themeRef : undefined,
theme: isObject(config.theme) ? normalizeTheme(config.theme) : undefined,
layers: config.layers as LuopanConfigInput['layers'],
layers: normalizedLayers,
};
};

View File

@@ -15,6 +15,8 @@ export function buildDegreeRing(config: DegreeRingConfig): DegreeRingData {
const ticks: TickMark[] = [];
const { rInner, rOuter, mode } = config;
const labelFontSize = 8;
const tickColor = config.tickColor ?? '#000000';
const ringColor = config.ringColor ?? tickColor;
const majorTick = Math.max(1, config.majorTick);
const minorTick = Math.max(1, config.minorTick);
@@ -125,11 +127,11 @@ export function buildDegreeRing(config: DegreeRingConfig): DegreeRingData {
return {
ticks,
tickColor: config.tickColor,
tickColor,
ring: {
rInner,
rOuter,
color: config.ringColor,
color: ringColor,
opacity: config.opacity,
},
labels: labels.length > 0 ? labels : undefined,

View File

@@ -62,10 +62,6 @@ export class SectorBuilder {
// 颜色优先级:扇区 > 规律填色 > 背景
const fillColor = this.colorResolver.resolveSectorColor(layerColorMap, sectorConfig, i);
const layerColor = layer.colorRef ? this.colorResolver.resolveColor(layer.colorRef) : undefined;
const sectorColor = sectorConfig?.colorRef
? this.colorResolver.resolveColor(sectorConfig.colorRef)
: undefined;
// 扇区的 `innerFill` 优先级高于层级的 `innerFill`。
const innerFill = (sectorConfig?.innerFill ?? layer.innerFill ?? 0) === 1;
const innerFillPath = innerFill
@@ -80,10 +76,10 @@ export class SectorBuilder {
const normalizedInnerFillPath =
innerFillPath && innerFillPath.length > 0 ? innerFillPath : undefined;
const hasInnerFillPath = Boolean(normalizedInnerFillPath);
// `innerFill` 开启时:外圈保持白色,仅填充内缩块。
// `innerFill` 开启时:外圈保持白色,内缩块仍按规律填色
const baseFillColor = hasInnerFillPath ? '#ffffff' : fillColor;
const innerFillColor = hasInnerFillPath ? sectorColor ?? layerColor ?? fillColor : undefined;
const textBaseColor = hasInnerFillPath ? innerFillColor ?? fillColor : fillColor;
const innerFillColor = hasInnerFillPath ? fillColor : undefined;
const textBaseColor = fillColor;
const textColor = getTextColorForBackground(textBaseColor);
const sectorKey = `L${layerIndex}-P${i}`;

View File

@@ -22,7 +22,7 @@ export interface Example {
/**
* JSON 配置根对象
*/
export interface LuopanConfigBase {
export interface LuopanConfigBase<TLayer = LayerConfig> {
name: string;
description?: string;
background: string;
@@ -33,15 +33,13 @@ export interface LuopanConfigBase {
outerRadius?: number;
themeRef?: string;
theme?: ThemeConfig;
layers: LayerConfig[];
layers: TLayer[];
}
export interface LuopanConfig extends LuopanConfigBase {
export interface LuopanConfig extends LuopanConfigBase<LayerConfig> {
theme: ThemeConfig;
}
export type LuopanConfigInput = LuopanConfigBase;
/**
* 主题配置
*/
@@ -84,8 +82,15 @@ export interface DegreeRingConfig {
majorTick: number;
minorTick: number;
microTick: number;
tickColor: string;
ringColor: string;
tickColor?: string;
ringColor?: string;
colorRef?: string;
}
export interface DegreeRingConfigInput
extends Omit<DegreeRingConfig, 'rInner' | 'rOuter'> {
rInner?: number;
rOuter?: number;
}
/**
@@ -99,11 +104,9 @@ export type LayerConfig =
/**
* 普通扇区层配置
*/
export interface SectorLayerConfig {
interface SectorLayerBase {
type?: 'sectors';
divisions: number;
rInner: number;
rOuter: number;
startAngle?: number;
colorRef?: string;
innerFill?: 0 | 1;
@@ -115,6 +118,25 @@ export interface SectorLayerConfig {
sectors?: SectorConfig[];
}
export interface SectorLayerConfig extends SectorLayerBase {
rInner: number;
rOuter: number;
layerHeight?: number;
}
export interface SectorLayerConfigInput extends SectorLayerBase {
rInner?: number;
rOuter?: number;
layerHeight?: number;
}
export type LayerConfigInput =
| SectorLayerConfigInput
| CenterIconLayerConfig
| DegreeRingLayerConfigInput;
export type LuopanConfigInput = LuopanConfigBase<LayerConfigInput>;
/**
* 中心图标层配置
*/
@@ -129,6 +151,13 @@ export interface CenterIconLayerConfig {
export interface DegreeRingLayerConfig {
type: 'degreeRing';
degreeRing: DegreeRingConfig;
layerHeight?: number;
}
// 扁平化输入:刻度环字段直接挂在层上。
export interface DegreeRingLayerConfigInput extends DegreeRingConfigInput {
type: 'degreeRing';
layerHeight?: number;
}
/**
@@ -190,6 +219,7 @@ export interface DegreeRingData {
ticks: TickMark[];
tickColor: string;
ring: { rInner: number; rOuter: number; color: string; opacity: number };
background?: { color: string; opacity: number };
labels?: DegreeLabel[];
}

View File

@@ -5,11 +5,11 @@
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Luopan from '../src/Luopan.vue';
import type { LuopanConfig } from '../src/types';
import type { LuopanConfigInput } from '../src/types';
const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0));
const baseConfig: LuopanConfig = {
const baseConfig: LuopanConfigInput = {
name: '测试配置',
background: '#000000',
theme: {
@@ -26,20 +26,18 @@ const baseConfig: LuopanConfig = {
},
{
type: 'degreeRing',
degreeRing: {
rInner: 90,
rOuter: 100,
showDegree: 1,
mode: 'both',
opacity: 0.3,
tickLength: 6,
tickLengthStep: 1,
majorTick: 10,
minorTick: 5,
microTick: 1,
tickColor: '#ffffff',
ringColor: '#ffffff',
},
rInner: 90,
rOuter: 100,
showDegree: 1,
mode: 'both',
opacity: 0.3,
tickLength: 6,
tickLengthStep: 1,
majorTick: 10,
minorTick: 5,
microTick: 1,
tickColor: '#ffffff',
ringColor: '#ffffff',
},
{
type: 'centerIcon',

View File

@@ -5,9 +5,9 @@
import { describe, it, expect } from 'vitest';
import { ref } from 'vue';
import { useLuopan } from '../src/composables/useLuopan';
import type { LuopanConfig, TextRadialPosition } from '../src/types';
import type { LuopanConfigInput, TextRadialPosition } from '../src/types';
const createMockConfig = (): LuopanConfig => ({
const createMockConfig = (): LuopanConfigInput => ({
name: '测试配置',
background: '#000000',
theme: {