@arronqzy/vue-view 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/package.json +49 -0
- package/src/env.d.ts +62 -0
- package/src/index.ts +4 -0
- package/src/panel/VueViewOnlinePreview.vue +276 -0
- package/src/panel/VueViewPanel.vue +871 -0
- package/src/panel/components/ConfigHintIcon.vue +34 -0
- package/src/panel/components/ElementsLayer.vue +165 -0
- package/src/panel/components/MaterialPreview.vue +135 -0
- package/src/panel/components/MaterialSidebar.vue +526 -0
- package/src/panel/components/MaterialSidebarTreeNode.vue +305 -0
- package/src/panel/components/MoveableLayer.vue +859 -0
- package/src/panel/components/PanelCanvas.vue +630 -0
- package/src/panel/components/PanelConfigSidebar.vue +397 -0
- package/src/panel/components/PanelRulers.vue +177 -0
- package/src/panel/components/SelectLayer.vue +115 -0
- package/src/panel/components/ViewElementScopePanel.vue +76 -0
- package/src/panel/components/WorkspaceConfigSidebar.vue +147 -0
- package/src/panel/components/WorkspaceProjectNav.vue +192 -0
- package/src/panel/components/WorkspaceStageSplit.vue +258 -0
- package/src/panel/components/config/ConfigColorField.vue +52 -0
- package/src/panel/components/config/ConfigFieldGroup.vue +20 -0
- package/src/panel/components/config/ConfigSection.vue +50 -0
- package/src/panel/components/config/PanelConfigAudioSection.vue +256 -0
- package/src/panel/components/config/PanelConfigChartSection.vue +650 -0
- package/src/panel/components/config/PanelConfigGeometrySection.vue +209 -0
- package/src/panel/components/config/PanelConfigGridChildSpan.vue +68 -0
- package/src/panel/components/config/PanelConfigGridSection.vue +103 -0
- package/src/panel/components/config/PanelConfigImageSection.vue +136 -0
- package/src/panel/components/config/PanelConfigMultiSelect.vue +434 -0
- package/src/panel/components/config/PanelConfigNodeInfo.vue +165 -0
- package/src/panel/components/config/PanelConfigReferenceSection.vue +77 -0
- package/src/panel/components/config/PanelConfigStyleSections.vue +208 -0
- package/src/panel/components/config/PanelConfigTextSection.vue +195 -0
- package/src/panel/components/config/PanelConfigVideoSection.vue +107 -0
- package/src/panel/components/config/shared.ts +74 -0
- package/src/panel/components/elementsLayerNodes.ts +830 -0
- package/src/panel/components/materialSidebarData.ts +85 -0
- package/src/panel/components/scope-config/ScopeConfigProvider.vue +153 -0
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.vue +234 -0
- package/src/panel/components/scope-config/ScopeTemplatePreviewHost.vue +192 -0
- package/src/panel/components/scope-config/ScopeTemplatePreviewPanel.vue +42 -0
- package/src/panel/components/scope-config/ScopeTemplateUsageHint.vue +20 -0
- package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.vue +63 -0
- package/src/panel/components/scope-config/scopeConfigContext.ts +17 -0
- package/src/panel/components/scope-config/useScopeConfig.ts +11 -0
- package/src/panel/constants/messages.ts +34 -0
- package/src/panel/constants/zIndex.ts +6 -0
- package/src/panel/hooks/usePanelElements.ts +1075 -0
- package/src/panel/hooks/useRafThrottledScroll.ts +25 -0
- package/src/panel/hooks/useWorkspaceProjects.ts +240 -0
- package/src/panel/lib/panel-ruler-canvas.ts +139 -0
- package/src/panel/library/workspace-project-cache.ts +23 -0
- package/src/panel/library/workspace-project-db.ts +111 -0
- package/src/panel/library/workspace-project-sync.ts +41 -0
- package/src/panel/library/workspace-snapshot.ts +30 -0
- package/src/panel/parseOnlinePreviewSearchParams.ts +13 -0
- package/src/panel/scope/view-scope-store.ts +82 -0
- package/src/panel/types.ts +127 -0
- package/src/panel/utils/chartOptionBuilder.ts +327 -0
- package/src/panel/utils/gridPlacement.ts +189 -0
- package/src/panel/utils/mappingLayerOps.ts +142 -0
- package/src/panel/utils/panelElementDefaults.ts +161 -0
- package/src/panel/utils/panelElementNodes.ts +35 -0
- package/src/panel/utils/panelStateIO.ts +124 -0
- package/src/panel/utils/scope-autocomplete.ts +114 -0
- package/src/panel/utils/scope-field-labels.ts +46 -0
- package/src/panel/utils/scope-template-chart.ts +92 -0
- package/src/panel/utils/scope-template-preview.ts +124 -0
- package/src/panel/utils/scope-template-spread.ts +229 -0
- package/src/panel/utils/scope-template-warnings.ts +243 -0
- package/src/panel/utils/scope-template.ts +97 -0
- package/src/panel/utils/updateElementDraft.ts +221 -0
- package/src/panel/viewportZoom.ts +26 -0
- package/src/tailwind.css +43 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { Checkbox, Collapse, Input, InputNumber, Select, Textarea } from "ant-design-vue";
|
|
4
|
+
import type { PanelChartConfig, PanelElement } from "../../types";
|
|
5
|
+
import {
|
|
6
|
+
buildChartOption,
|
|
7
|
+
CHART_TYPES,
|
|
8
|
+
getChartLabelsDisplayText,
|
|
9
|
+
getChartValuesDisplayText,
|
|
10
|
+
} from "../../utils/chartOptionBuilder";
|
|
11
|
+
import { mergeOptionPatch } from "./shared";
|
|
12
|
+
import ConfigColorField from "./ConfigColorField.vue";
|
|
13
|
+
import ConfigFieldGroup from "./ConfigFieldGroup.vue";
|
|
14
|
+
import ConfigHintIcon from "../ConfigHintIcon.vue";
|
|
15
|
+
import ConfigSection from "./ConfigSection.vue";
|
|
16
|
+
|
|
17
|
+
type ChartType =
|
|
18
|
+
| "bar"
|
|
19
|
+
| "line"
|
|
20
|
+
| "pie"
|
|
21
|
+
| "area"
|
|
22
|
+
| "scatter"
|
|
23
|
+
| "radar"
|
|
24
|
+
| "gauge"
|
|
25
|
+
| "funnel"
|
|
26
|
+
| "";
|
|
27
|
+
|
|
28
|
+
const props = defineProps<{
|
|
29
|
+
element: PanelElement;
|
|
30
|
+
isEditable: boolean;
|
|
31
|
+
basicOpen: boolean;
|
|
32
|
+
advancedOpen: boolean;
|
|
33
|
+
forceOpen?: boolean;
|
|
34
|
+
updateElement: (
|
|
35
|
+
id: string,
|
|
36
|
+
patch: Partial<PanelElement>,
|
|
37
|
+
options?: { batchId?: string; meta?: Record<string, unknown> }
|
|
38
|
+
) => void;
|
|
39
|
+
}>();
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits<{
|
|
42
|
+
"update:basicOpen": [value: boolean];
|
|
43
|
+
"update:advancedOpen": [value: boolean];
|
|
44
|
+
}>();
|
|
45
|
+
|
|
46
|
+
const isAdvancedOptionMode = ref(false);
|
|
47
|
+
const optionJsonText = ref("{}");
|
|
48
|
+
const optionJsonError = ref<string | null>(null);
|
|
49
|
+
|
|
50
|
+
const selectedChartType = computed(
|
|
51
|
+
() => (props.element.materialType ?? "") as ChartType
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const chartOption = computed(
|
|
55
|
+
() => (props.element.chart?.option ?? {}) as Record<string, any>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
watch(
|
|
59
|
+
() => props.element.id,
|
|
60
|
+
() => {
|
|
61
|
+
if (CHART_TYPES.has(props.element.materialType ?? "")) {
|
|
62
|
+
optionJsonText.value = JSON.stringify(buildChartOption(props.element), null, 2);
|
|
63
|
+
} else {
|
|
64
|
+
optionJsonText.value = "{}";
|
|
65
|
+
}
|
|
66
|
+
optionJsonError.value = null;
|
|
67
|
+
},
|
|
68
|
+
{ immediate: true }
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
function updateChart(patch: Partial<PanelChartConfig>) {
|
|
72
|
+
props.updateElement(props.element.id, {
|
|
73
|
+
chart: { ...(props.element.chart ?? {}), ...patch },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function updateOptionForm(patch: Record<string, unknown>) {
|
|
78
|
+
updateChart({
|
|
79
|
+
option: mergeOptionPatch(chartOption.value, patch),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function gradientCss(direction: string | undefined, from: string, to: string) {
|
|
84
|
+
const map: Record<string, string> = {
|
|
85
|
+
"to-bottom": "to bottom",
|
|
86
|
+
"to-bottom-right": "to bottom right",
|
|
87
|
+
"to-top-right": "to top right",
|
|
88
|
+
"to-right": "to right",
|
|
89
|
+
};
|
|
90
|
+
return `linear-gradient(${map[direction ?? "to-right"] ?? "to right"}, ${from}, ${to})`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function hasDataZoomType(type: string) {
|
|
94
|
+
const zoom = chartOption.value.dataZoom;
|
|
95
|
+
return Array.isArray(zoom) && zoom.some((z) => z?.type === type);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function toggleDataZoom(type: "inside" | "slider", checked: boolean) {
|
|
99
|
+
const prev = Array.isArray(chartOption.value.dataZoom)
|
|
100
|
+
? [...chartOption.value.dataZoom]
|
|
101
|
+
: [];
|
|
102
|
+
const next = checked
|
|
103
|
+
? [...prev.filter((z) => z?.type !== type), { type }]
|
|
104
|
+
: prev.filter((z) => z?.type !== type);
|
|
105
|
+
updateOptionForm({ dataZoom: next });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function onOptionJsonChange(v: string) {
|
|
109
|
+
optionJsonText.value = v;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(v) as Record<string, unknown>;
|
|
112
|
+
updateChart({ option: parsed });
|
|
113
|
+
optionJsonError.value = null;
|
|
114
|
+
} catch {
|
|
115
|
+
optionJsonError.value = "JSON 格式错误,修正后会自动应用";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<template>
|
|
121
|
+
<ConfigSection
|
|
122
|
+
title="图表配置 / 基础"
|
|
123
|
+
:open="basicOpen"
|
|
124
|
+
:force-open="forceOpen"
|
|
125
|
+
@update:open="emit('update:basicOpen', $event)"
|
|
126
|
+
>
|
|
127
|
+
<ConfigFieldGroup title="基础显示">
|
|
128
|
+
<label class="block space-y-1.5">
|
|
129
|
+
<div>标题</div>
|
|
130
|
+
<Input
|
|
131
|
+
size="small"
|
|
132
|
+
:value="element.chart?.title ?? ''"
|
|
133
|
+
:disabled="!isEditable"
|
|
134
|
+
@update:value="(v: string) => updateChart({ title: v })"
|
|
135
|
+
/>
|
|
136
|
+
</label>
|
|
137
|
+
<ConfigColorField
|
|
138
|
+
label="主色"
|
|
139
|
+
:value="element.chart?.color ?? '#3b82f6'"
|
|
140
|
+
:disabled="!isEditable"
|
|
141
|
+
@update:value="(v) => updateChart({ color: v || '#3b82f6' })"
|
|
142
|
+
/>
|
|
143
|
+
<label class="flex items-center gap-2">
|
|
144
|
+
<Checkbox
|
|
145
|
+
:checked="element.chart?.colorMode === 'gradient'"
|
|
146
|
+
:disabled="!isEditable"
|
|
147
|
+
@update:checked="(v) => updateChart({ colorMode: v ? 'gradient' : 'solid' })"
|
|
148
|
+
/>
|
|
149
|
+
<span>主色使用渐变</span>
|
|
150
|
+
</label>
|
|
151
|
+
<div v-if="element.chart?.colorMode === 'gradient'" class="grid grid-cols-2 gap-2">
|
|
152
|
+
<ConfigColorField
|
|
153
|
+
label="渐变起始色"
|
|
154
|
+
:value="element.chart?.gradientFrom ?? element.chart?.color ?? '#3b82f6'"
|
|
155
|
+
:disabled="!isEditable"
|
|
156
|
+
@update:value="(v) => updateChart({ gradientFrom: v || '#3b82f6' })"
|
|
157
|
+
/>
|
|
158
|
+
<ConfigColorField
|
|
159
|
+
label="渐变结束色"
|
|
160
|
+
:value="element.chart?.gradientTo ?? '#22d3ee'"
|
|
161
|
+
:disabled="!isEditable"
|
|
162
|
+
@update:value="(v) => updateChart({ gradientTo: v || '#22d3ee' })"
|
|
163
|
+
/>
|
|
164
|
+
<label class="col-span-2 block space-y-1">
|
|
165
|
+
<div>渐变方向</div>
|
|
166
|
+
<Select
|
|
167
|
+
size="small"
|
|
168
|
+
class="w-full"
|
|
169
|
+
:value="element.chart?.gradientDirection ?? 'to-right'"
|
|
170
|
+
:disabled="!isEditable"
|
|
171
|
+
@update:value="(v) => updateChart({ gradientDirection: v as PanelChartConfig['gradientDirection'] })"
|
|
172
|
+
>
|
|
173
|
+
<Select.Option value="to-right">左 → 右</Select.Option>
|
|
174
|
+
<Select.Option value="to-bottom">上 → 下</Select.Option>
|
|
175
|
+
<Select.Option value="to-bottom-right">左上 → 右下</Select.Option>
|
|
176
|
+
<Select.Option value="to-top-right">左下 → 右上</Select.Option>
|
|
177
|
+
</Select>
|
|
178
|
+
</label>
|
|
179
|
+
<div class="col-span-2 space-y-1">
|
|
180
|
+
<div class="text-[11px] text-gray-500">渐变预览</div>
|
|
181
|
+
<div
|
|
182
|
+
class="h-6 rounded border border-gray-200/60"
|
|
183
|
+
:style="{
|
|
184
|
+
backgroundImage: gradientCss(
|
|
185
|
+
element.chart?.gradientDirection,
|
|
186
|
+
element.chart?.gradientFrom ?? element.chart?.color ?? '#3b82f6',
|
|
187
|
+
element.chart?.gradientTo ?? '#22d3ee'
|
|
188
|
+
),
|
|
189
|
+
}"
|
|
190
|
+
/>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
<label class="block space-y-1.5">
|
|
194
|
+
<div>显示模式</div>
|
|
195
|
+
<Select
|
|
196
|
+
size="small"
|
|
197
|
+
class="w-full"
|
|
198
|
+
:value="element.chart?.renderMode ?? 'canvas'"
|
|
199
|
+
:disabled="!isEditable"
|
|
200
|
+
@update:value="(v) => updateChart({ renderMode: v as 'canvas' | 'svg' })"
|
|
201
|
+
>
|
|
202
|
+
<Select.Option value="canvas">Canvas</Select.Option>
|
|
203
|
+
<Select.Option value="svg">SVG</Select.Option>
|
|
204
|
+
</Select>
|
|
205
|
+
</label>
|
|
206
|
+
</ConfigFieldGroup>
|
|
207
|
+
|
|
208
|
+
<ConfigFieldGroup title="提示框 Tooltip">
|
|
209
|
+
<div class="grid grid-cols-2 gap-2">
|
|
210
|
+
<label class="flex items-center gap-2">
|
|
211
|
+
<Checkbox
|
|
212
|
+
:checked="element.chart?.tooltipShow ?? true"
|
|
213
|
+
:disabled="!isEditable"
|
|
214
|
+
@update:checked="(v) => updateChart({ tooltipShow: v === true })"
|
|
215
|
+
/>
|
|
216
|
+
<span>显示 Tooltip</span>
|
|
217
|
+
</label>
|
|
218
|
+
<label class="block space-y-1">
|
|
219
|
+
<div>Tooltip 触发方式</div>
|
|
220
|
+
<Select
|
|
221
|
+
size="small"
|
|
222
|
+
class="w-full"
|
|
223
|
+
:value="element.chart?.tooltipTrigger ?? 'axis'"
|
|
224
|
+
:disabled="!isEditable"
|
|
225
|
+
@update:value="(v) => updateChart({ tooltipTrigger: v as 'axis' | 'item' })"
|
|
226
|
+
>
|
|
227
|
+
<Select.Option value="axis">axis</Select.Option>
|
|
228
|
+
<Select.Option value="item">item</Select.Option>
|
|
229
|
+
</Select>
|
|
230
|
+
</label>
|
|
231
|
+
</div>
|
|
232
|
+
<div class="grid grid-cols-2 gap-2">
|
|
233
|
+
<ConfigColorField
|
|
234
|
+
label="Tooltip 背景色"
|
|
235
|
+
:value="element.chart?.tooltipBackgroundColor ?? '#0f172a'"
|
|
236
|
+
:disabled="!isEditable"
|
|
237
|
+
@update:value="(v) => updateChart({ tooltipBackgroundColor: v || '#0f172a' })"
|
|
238
|
+
/>
|
|
239
|
+
<ConfigColorField
|
|
240
|
+
label="Tooltip 文字色"
|
|
241
|
+
:value="element.chart?.tooltipTextColor ?? '#f8fafc'"
|
|
242
|
+
:disabled="!isEditable"
|
|
243
|
+
@update:value="(v) => updateChart({ tooltipTextColor: v || '#f8fafc' })"
|
|
244
|
+
/>
|
|
245
|
+
</div>
|
|
246
|
+
<label class="block space-y-1">
|
|
247
|
+
<div class="flex items-center gap-1">
|
|
248
|
+
<span>Tooltip Formatter(可选)</span>
|
|
249
|
+
<ConfigHintIcon label="Tooltip Formatter" content-class="max-w-[360px]">
|
|
250
|
+
<div class="font-medium">可用占位符</div>
|
|
251
|
+
<div>{"{a}=系列名, {b}=类目名, {c}=数值, {d}=百分比(饼图)"}</div>
|
|
252
|
+
</ConfigHintIcon>
|
|
253
|
+
</div>
|
|
254
|
+
<Input
|
|
255
|
+
size="small"
|
|
256
|
+
:value="element.chart?.tooltipFormatter ?? ''"
|
|
257
|
+
:disabled="!isEditable"
|
|
258
|
+
placeholder="例如:{b}: {c}"
|
|
259
|
+
@update:value="(v: string) => updateChart({ tooltipFormatter: v || undefined })"
|
|
260
|
+
/>
|
|
261
|
+
</label>
|
|
262
|
+
</ConfigFieldGroup>
|
|
263
|
+
|
|
264
|
+
<ConfigFieldGroup title="数据">
|
|
265
|
+
<label class="block space-y-1">
|
|
266
|
+
<div>类目(逗号分隔)</div>
|
|
267
|
+
<Input
|
|
268
|
+
size="small"
|
|
269
|
+
:value="getChartLabelsDisplayText(element.chart)"
|
|
270
|
+
:disabled="!isEditable"
|
|
271
|
+
@update:value="(v: string) => updateChart({ labelsText: v })"
|
|
272
|
+
/>
|
|
273
|
+
</label>
|
|
274
|
+
<label class="block space-y-1">
|
|
275
|
+
<div>数值(逗号分隔)</div>
|
|
276
|
+
<Input
|
|
277
|
+
size="small"
|
|
278
|
+
:value="getChartValuesDisplayText(element.chart)"
|
|
279
|
+
:disabled="!isEditable"
|
|
280
|
+
@update:value="(v: string) => updateChart({ valuesText: v })"
|
|
281
|
+
/>
|
|
282
|
+
</label>
|
|
283
|
+
</ConfigFieldGroup>
|
|
284
|
+
|
|
285
|
+
<ConfigFieldGroup
|
|
286
|
+
v-if="['bar', 'line', 'area', 'scatter'].includes(selectedChartType)"
|
|
287
|
+
title="坐标轴"
|
|
288
|
+
>
|
|
289
|
+
<div class="grid grid-cols-2 gap-2">
|
|
290
|
+
<label class="block">
|
|
291
|
+
<div class="mb-1">X 轴名称</div>
|
|
292
|
+
<Input
|
|
293
|
+
size="small"
|
|
294
|
+
:value="element.chart?.xAxisName ?? ''"
|
|
295
|
+
:disabled="!isEditable"
|
|
296
|
+
@update:value="(v: string) => updateChart({ xAxisName: v })"
|
|
297
|
+
/>
|
|
298
|
+
</label>
|
|
299
|
+
<label class="block">
|
|
300
|
+
<div class="mb-1">Y 轴名称</div>
|
|
301
|
+
<Input
|
|
302
|
+
size="small"
|
|
303
|
+
:value="element.chart?.yAxisName ?? ''"
|
|
304
|
+
:disabled="!isEditable"
|
|
305
|
+
@update:value="(v: string) => updateChart({ yAxisName: v })"
|
|
306
|
+
/>
|
|
307
|
+
</label>
|
|
308
|
+
</div>
|
|
309
|
+
<div class="grid grid-cols-2 gap-2">
|
|
310
|
+
<label class="flex items-center gap-2">
|
|
311
|
+
<Checkbox
|
|
312
|
+
:checked="element.chart?.xAxisTickShow ?? true"
|
|
313
|
+
:disabled="!isEditable"
|
|
314
|
+
@update:checked="(v) => updateChart({ xAxisTickShow: v === true })"
|
|
315
|
+
/>
|
|
316
|
+
<span>X 轴刻度线</span>
|
|
317
|
+
</label>
|
|
318
|
+
<label class="flex items-center gap-2">
|
|
319
|
+
<Checkbox
|
|
320
|
+
:checked="element.chart?.yAxisTickShow ?? true"
|
|
321
|
+
:disabled="!isEditable"
|
|
322
|
+
@update:checked="(v) => updateChart({ yAxisTickShow: v === true })"
|
|
323
|
+
/>
|
|
324
|
+
<span>Y 轴刻度线</span>
|
|
325
|
+
</label>
|
|
326
|
+
</div>
|
|
327
|
+
<div class="grid grid-cols-2 gap-2">
|
|
328
|
+
<ConfigColorField
|
|
329
|
+
label="X 轴刻度线颜色"
|
|
330
|
+
:value="element.chart?.xAxisTickColor ?? '#94a3b8'"
|
|
331
|
+
:disabled="!isEditable"
|
|
332
|
+
@update:value="(v) => updateChart({ xAxisTickColor: v || '#94a3b8' })"
|
|
333
|
+
/>
|
|
334
|
+
<ConfigColorField
|
|
335
|
+
label="Y 轴刻度线颜色"
|
|
336
|
+
:value="element.chart?.yAxisTickColor ?? '#94a3b8'"
|
|
337
|
+
:disabled="!isEditable"
|
|
338
|
+
@update:value="(v) => updateChart({ yAxisTickColor: v || '#94a3b8' })"
|
|
339
|
+
/>
|
|
340
|
+
</div>
|
|
341
|
+
<div class="grid grid-cols-2 gap-2">
|
|
342
|
+
<label class="flex items-center gap-2">
|
|
343
|
+
<Checkbox
|
|
344
|
+
:checked="element.chart?.xAxisSplitLineShow ?? false"
|
|
345
|
+
:disabled="!isEditable"
|
|
346
|
+
@update:checked="(v) => updateChart({ xAxisSplitLineShow: v === true })"
|
|
347
|
+
/>
|
|
348
|
+
<span>X 轴分割线</span>
|
|
349
|
+
</label>
|
|
350
|
+
<label class="flex items-center gap-2">
|
|
351
|
+
<Checkbox
|
|
352
|
+
:checked="element.chart?.yAxisSplitLineShow ?? true"
|
|
353
|
+
:disabled="!isEditable"
|
|
354
|
+
@update:checked="(v) => updateChart({ yAxisSplitLineShow: v === true })"
|
|
355
|
+
/>
|
|
356
|
+
<span>Y 轴分割线</span>
|
|
357
|
+
</label>
|
|
358
|
+
</div>
|
|
359
|
+
<div class="grid grid-cols-2 gap-2">
|
|
360
|
+
<ConfigColorField
|
|
361
|
+
label="X 轴标签颜色"
|
|
362
|
+
:value="element.chart?.xAxisLabelColor ?? '#64748b'"
|
|
363
|
+
:disabled="!isEditable"
|
|
364
|
+
@update:value="(v) => updateChart({ xAxisLabelColor: v || '#64748b' })"
|
|
365
|
+
/>
|
|
366
|
+
<ConfigColorField
|
|
367
|
+
label="Y 轴标签颜色"
|
|
368
|
+
:value="element.chart?.yAxisLabelColor ?? '#64748b'"
|
|
369
|
+
:disabled="!isEditable"
|
|
370
|
+
@update:value="(v) => updateChart({ yAxisLabelColor: v || '#64748b' })"
|
|
371
|
+
/>
|
|
372
|
+
</div>
|
|
373
|
+
<div class="grid grid-cols-2 gap-2">
|
|
374
|
+
<label class="block">
|
|
375
|
+
<div class="mb-1">X 轴标签字号</div>
|
|
376
|
+
<InputNumber
|
|
377
|
+
size="small"
|
|
378
|
+
class="w-full"
|
|
379
|
+
:min="8"
|
|
380
|
+
:max="48"
|
|
381
|
+
:value="element.chart?.xAxisLabelFontSize ?? 10"
|
|
382
|
+
:disabled="!isEditable"
|
|
383
|
+
@update:value="(v) => {
|
|
384
|
+
const n = Number(v);
|
|
385
|
+
if (!Number.isNaN(n)) updateChart({ xAxisLabelFontSize: Math.max(8, Math.min(48, n)) });
|
|
386
|
+
}"
|
|
387
|
+
/>
|
|
388
|
+
</label>
|
|
389
|
+
<label class="block">
|
|
390
|
+
<div class="mb-1">Y 轴标签字号</div>
|
|
391
|
+
<InputNumber
|
|
392
|
+
size="small"
|
|
393
|
+
class="w-full"
|
|
394
|
+
:min="8"
|
|
395
|
+
:max="48"
|
|
396
|
+
:value="element.chart?.yAxisLabelFontSize ?? 10"
|
|
397
|
+
:disabled="!isEditable"
|
|
398
|
+
@update:value="(v) => {
|
|
399
|
+
const n = Number(v);
|
|
400
|
+
if (!Number.isNaN(n)) updateChart({ yAxisLabelFontSize: Math.max(8, Math.min(48, n)) });
|
|
401
|
+
}"
|
|
402
|
+
/>
|
|
403
|
+
</label>
|
|
404
|
+
</div>
|
|
405
|
+
</ConfigFieldGroup>
|
|
406
|
+
|
|
407
|
+
<ConfigFieldGroup
|
|
408
|
+
v-if="['bar', 'line', 'area', 'pie'].includes(selectedChartType)"
|
|
409
|
+
title="系列"
|
|
410
|
+
>
|
|
411
|
+
<label v-if="selectedChartType === 'bar'" class="block space-y-1">
|
|
412
|
+
<div>柱宽(px)</div>
|
|
413
|
+
<InputNumber
|
|
414
|
+
size="small"
|
|
415
|
+
class="w-full"
|
|
416
|
+
:min="1"
|
|
417
|
+
:value="element.chart?.barWidth ?? 24"
|
|
418
|
+
:disabled="!isEditable"
|
|
419
|
+
@update:value="(v) => {
|
|
420
|
+
const n = Number(v);
|
|
421
|
+
if (!Number.isNaN(n)) updateChart({ barWidth: Math.max(1, n) });
|
|
422
|
+
}"
|
|
423
|
+
/>
|
|
424
|
+
</label>
|
|
425
|
+
<label
|
|
426
|
+
v-if="selectedChartType === 'line' || selectedChartType === 'area'"
|
|
427
|
+
class="flex items-center gap-2"
|
|
428
|
+
>
|
|
429
|
+
<Checkbox
|
|
430
|
+
:checked="element.chart?.smooth ?? true"
|
|
431
|
+
:disabled="!isEditable"
|
|
432
|
+
@update:checked="(v) => updateChart({ smooth: v === true })"
|
|
433
|
+
/>
|
|
434
|
+
<span>平滑曲线</span>
|
|
435
|
+
</label>
|
|
436
|
+
<div v-if="selectedChartType === 'pie'" class="grid grid-cols-2 gap-2">
|
|
437
|
+
<label class="block space-y-1">
|
|
438
|
+
<div>内半径(%)</div>
|
|
439
|
+
<InputNumber
|
|
440
|
+
size="small"
|
|
441
|
+
class="w-full"
|
|
442
|
+
:min="0"
|
|
443
|
+
:max="99"
|
|
444
|
+
:value="element.chart?.pieInnerRadius ?? 30"
|
|
445
|
+
:disabled="!isEditable"
|
|
446
|
+
@update:value="(v) => {
|
|
447
|
+
const n = Number(v);
|
|
448
|
+
if (!Number.isNaN(n)) updateChart({ pieInnerRadius: Math.max(0, Math.min(99, n)) });
|
|
449
|
+
}"
|
|
450
|
+
/>
|
|
451
|
+
</label>
|
|
452
|
+
<label class="block space-y-1">
|
|
453
|
+
<div>外半径(%)</div>
|
|
454
|
+
<InputNumber
|
|
455
|
+
size="small"
|
|
456
|
+
class="w-full"
|
|
457
|
+
:min="1"
|
|
458
|
+
:max="100"
|
|
459
|
+
:value="element.chart?.pieOuterRadius ?? 65"
|
|
460
|
+
:disabled="!isEditable"
|
|
461
|
+
@update:value="(v) => {
|
|
462
|
+
const n = Number(v);
|
|
463
|
+
if (!Number.isNaN(n)) updateChart({ pieOuterRadius: Math.max(1, Math.min(100, n)) });
|
|
464
|
+
}"
|
|
465
|
+
/>
|
|
466
|
+
</label>
|
|
467
|
+
</div>
|
|
468
|
+
</ConfigFieldGroup>
|
|
469
|
+
</ConfigSection>
|
|
470
|
+
|
|
471
|
+
<ConfigSection
|
|
472
|
+
title="图表配置 / 高级"
|
|
473
|
+
:open="advancedOpen"
|
|
474
|
+
:force-open="forceOpen"
|
|
475
|
+
@update:open="emit('update:advancedOpen', $event)"
|
|
476
|
+
>
|
|
477
|
+
<ConfigFieldGroup title="常用项(表单)">
|
|
478
|
+
<Collapse ghost size="small">
|
|
479
|
+
<Collapse.Panel key="layout" header="布局与坐标">
|
|
480
|
+
<div class="grid grid-cols-2 gap-2">
|
|
481
|
+
<label class="block space-y-1">
|
|
482
|
+
<div class="text-[11px]">网格左距(grid.left)</div>
|
|
483
|
+
<InputNumber
|
|
484
|
+
size="small"
|
|
485
|
+
class="w-full"
|
|
486
|
+
:value="Number(chartOption.grid?.left ?? 28)"
|
|
487
|
+
:disabled="!isEditable"
|
|
488
|
+
@update:value="(v) => updateOptionForm({ grid: { left: Number(v) || 0 } })"
|
|
489
|
+
/>
|
|
490
|
+
</label>
|
|
491
|
+
<label class="block space-y-1">
|
|
492
|
+
<div class="text-[11px]">网格右距(grid.right)</div>
|
|
493
|
+
<InputNumber
|
|
494
|
+
size="small"
|
|
495
|
+
class="w-full"
|
|
496
|
+
:value="Number(chartOption.grid?.right ?? 10)"
|
|
497
|
+
:disabled="!isEditable"
|
|
498
|
+
@update:value="(v) => updateOptionForm({ grid: { right: Number(v) || 0 } })"
|
|
499
|
+
/>
|
|
500
|
+
</label>
|
|
501
|
+
<label class="block space-y-1">
|
|
502
|
+
<div class="text-[11px]">图例位置(legend.top)</div>
|
|
503
|
+
<Select
|
|
504
|
+
size="small"
|
|
505
|
+
class="w-full"
|
|
506
|
+
:value="String(chartOption.legend?.top ?? 'top')"
|
|
507
|
+
:disabled="!isEditable"
|
|
508
|
+
@update:value="(v) => updateOptionForm({ legend: { top: v } })"
|
|
509
|
+
>
|
|
510
|
+
<Select.Option value="top">顶部</Select.Option>
|
|
511
|
+
<Select.Option value="bottom">底部</Select.Option>
|
|
512
|
+
<Select.Option value="left">左侧</Select.Option>
|
|
513
|
+
<Select.Option value="right">右侧</Select.Option>
|
|
514
|
+
</Select>
|
|
515
|
+
</label>
|
|
516
|
+
<label class="block space-y-1">
|
|
517
|
+
<div class="text-[11px]">图例排列(legend.orient)</div>
|
|
518
|
+
<Select
|
|
519
|
+
size="small"
|
|
520
|
+
class="w-full"
|
|
521
|
+
:value="String(chartOption.legend?.orient ?? 'horizontal')"
|
|
522
|
+
:disabled="!isEditable"
|
|
523
|
+
@update:value="(v) => updateOptionForm({ legend: { orient: v } })"
|
|
524
|
+
>
|
|
525
|
+
<Select.Option value="horizontal">横向</Select.Option>
|
|
526
|
+
<Select.Option value="vertical">纵向</Select.Option>
|
|
527
|
+
</Select>
|
|
528
|
+
</label>
|
|
529
|
+
</div>
|
|
530
|
+
</Collapse.Panel>
|
|
531
|
+
<Collapse.Panel key="highfreq" header="高频项">
|
|
532
|
+
<div class="space-y-2">
|
|
533
|
+
<label class="flex items-center gap-2">
|
|
534
|
+
<Checkbox
|
|
535
|
+
:checked="Boolean(chartOption.legend?.show ?? true)"
|
|
536
|
+
:disabled="!isEditable"
|
|
537
|
+
@update:checked="(v) => updateOptionForm({ legend: { show: v === true } })"
|
|
538
|
+
/>
|
|
539
|
+
<span>显示图例</span>
|
|
540
|
+
</label>
|
|
541
|
+
<label class="flex items-center gap-2">
|
|
542
|
+
<Checkbox
|
|
543
|
+
:checked="Boolean(chartOption.grid?.containLabel ?? false)"
|
|
544
|
+
:disabled="!isEditable"
|
|
545
|
+
@update:checked="(v) => updateOptionForm({ grid: { containLabel: v === true } })"
|
|
546
|
+
/>
|
|
547
|
+
<span>网格包含标签</span>
|
|
548
|
+
</label>
|
|
549
|
+
<label class="flex items-center gap-2">
|
|
550
|
+
<Checkbox
|
|
551
|
+
:checked="hasDataZoomType('inside')"
|
|
552
|
+
:disabled="!isEditable"
|
|
553
|
+
@update:checked="(v) => toggleDataZoom('inside', v === true)"
|
|
554
|
+
/>
|
|
555
|
+
<span>内置缩放</span>
|
|
556
|
+
</label>
|
|
557
|
+
<label class="flex items-center gap-2">
|
|
558
|
+
<Checkbox
|
|
559
|
+
:checked="hasDataZoomType('slider')"
|
|
560
|
+
:disabled="!isEditable"
|
|
561
|
+
@update:checked="(v) => toggleDataZoom('slider', v === true)"
|
|
562
|
+
/>
|
|
563
|
+
<span>滑块缩放</span>
|
|
564
|
+
</label>
|
|
565
|
+
<label class="flex items-center gap-2">
|
|
566
|
+
<Checkbox
|
|
567
|
+
:checked="Boolean(chartOption.animation ?? false)"
|
|
568
|
+
:disabled="!isEditable"
|
|
569
|
+
@update:checked="(v) => updateOptionForm({ animation: v === true })"
|
|
570
|
+
/>
|
|
571
|
+
<span>开启动画</span>
|
|
572
|
+
</label>
|
|
573
|
+
<label class="block space-y-1">
|
|
574
|
+
<div class="text-[11px]">动画时长(ms)</div>
|
|
575
|
+
<InputNumber
|
|
576
|
+
size="small"
|
|
577
|
+
class="w-full"
|
|
578
|
+
:min="0"
|
|
579
|
+
:value="Number(chartOption.animationDuration ?? 300)"
|
|
580
|
+
:disabled="!isEditable"
|
|
581
|
+
@update:value="(v) => updateOptionForm({ animationDuration: Math.max(0, Number(v) || 0) })"
|
|
582
|
+
/>
|
|
583
|
+
</label>
|
|
584
|
+
</div>
|
|
585
|
+
</Collapse.Panel>
|
|
586
|
+
<Collapse.Panel key="axisPointer" header="轴指示器与对齐">
|
|
587
|
+
<div class="space-y-2">
|
|
588
|
+
<label class="flex items-center gap-2">
|
|
589
|
+
<Checkbox
|
|
590
|
+
:checked="Boolean(chartOption.axisPointer?.show ?? false)"
|
|
591
|
+
:disabled="!isEditable"
|
|
592
|
+
@update:checked="(v) => updateOptionForm({ axisPointer: { show: v === true } })"
|
|
593
|
+
/>
|
|
594
|
+
<span>显示轴指示器</span>
|
|
595
|
+
</label>
|
|
596
|
+
<label class="block space-y-1">
|
|
597
|
+
<div class="text-[11px]">轴指示器类型</div>
|
|
598
|
+
<Select
|
|
599
|
+
size="small"
|
|
600
|
+
class="w-full"
|
|
601
|
+
:value="String(chartOption.axisPointer?.type ?? 'line')"
|
|
602
|
+
:disabled="!isEditable"
|
|
603
|
+
@update:value="(v) => updateOptionForm({ axisPointer: { type: v } })"
|
|
604
|
+
>
|
|
605
|
+
<Select.Option value="line">线</Select.Option>
|
|
606
|
+
<Select.Option value="shadow">阴影</Select.Option>
|
|
607
|
+
<Select.Option value="cross">十字</Select.Option>
|
|
608
|
+
</Select>
|
|
609
|
+
</label>
|
|
610
|
+
</div>
|
|
611
|
+
</Collapse.Panel>
|
|
612
|
+
</Collapse>
|
|
613
|
+
</ConfigFieldGroup>
|
|
614
|
+
|
|
615
|
+
<ConfigFieldGroup title="JSON 高级模式">
|
|
616
|
+
<label class="flex items-center gap-2">
|
|
617
|
+
<Checkbox
|
|
618
|
+
v-model:checked="isAdvancedOptionMode"
|
|
619
|
+
:disabled="!isEditable"
|
|
620
|
+
/>
|
|
621
|
+
<span>开启高级模式(直接编辑图表 JSON 配置)</span>
|
|
622
|
+
<ConfigHintIcon label="图表 JSON 高级模式">
|
|
623
|
+
基础配置会先生成图表配置,高级模式会在此基础上覆盖(深度合并)。
|
|
624
|
+
</ConfigHintIcon>
|
|
625
|
+
</label>
|
|
626
|
+
<template v-if="isAdvancedOptionMode">
|
|
627
|
+
<Textarea
|
|
628
|
+
:value="optionJsonText"
|
|
629
|
+
:disabled="!isEditable"
|
|
630
|
+
:rows="8"
|
|
631
|
+
spellcheck="false"
|
|
632
|
+
class="font-mono text-[11px]"
|
|
633
|
+
@update:value="onOptionJsonChange"
|
|
634
|
+
/>
|
|
635
|
+
<div
|
|
636
|
+
v-if="optionJsonError"
|
|
637
|
+
class="rounded border border-red-400/40 bg-red-50 px-2 py-1.5 text-[11px] text-red-700"
|
|
638
|
+
>
|
|
639
|
+
{{ optionJsonError }}
|
|
640
|
+
</div>
|
|
641
|
+
<div
|
|
642
|
+
v-else
|
|
643
|
+
class="rounded border border-emerald-500/30 bg-emerald-500/10 px-2 py-1.5 text-[11px] text-emerald-700"
|
|
644
|
+
>
|
|
645
|
+
JSON 有效,已实时应用到当前图表。
|
|
646
|
+
</div>
|
|
647
|
+
</template>
|
|
648
|
+
</ConfigFieldGroup>
|
|
649
|
+
</ConfigSection>
|
|
650
|
+
</template>
|