@arronqzy/vue-view 0.1.4 → 0.1.6
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/package.json +5 -5
- package/src/panel/VueViewOnlinePreview.vue +27 -17
- package/src/panel/VueViewPanel.vue +3 -0
- package/src/panel/components/PanelConfigSidebar.vue +21 -20
- package/src/panel/components/WorkspaceConfigSidebar.vue +10 -0
- package/src/panel/components/config/ConfigFieldGroup.vue +35 -6
- package/src/panel/components/config/PanelConfigAudioSection.vue +5 -1
- package/src/panel/components/config/PanelConfigChartSection.vue +101 -84
- package/src/panel/components/config/PanelConfigGeometrySection.vue +10 -2
- package/src/panel/components/config/PanelConfigImageSection.vue +5 -1
- package/src/panel/components/config/PanelConfigNodeInfo.vue +21 -12
- package/src/panel/components/config/PanelConfigReferenceSection.vue +5 -1
- package/src/panel/components/config/PanelConfigStyleSections.vue +6 -2
- package/src/panel/components/config/PanelConfigTextSection.vue +5 -1
- package/src/panel/utils/chartOptionBuilder.ts +7 -1
- package/src/panel/utils/outputScale.ts +56 -0
- package/src/panel/utils/panelElementDefaults.ts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/vue-view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Vue 3 visual view editor for Abuilder (Ant Design Vue)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
"selecto": "^1.26.3",
|
|
24
24
|
"uuid": "^13.0.0",
|
|
25
25
|
"vue": "^3.5.13",
|
|
26
|
-
"@arronqzy/blueprint-dsl": "1.0.
|
|
27
|
-
"@arronqzy/
|
|
26
|
+
"@arronqzy/blueprint-dsl": "1.0.6",
|
|
27
|
+
"@arronqzy/rx-store": "1.0.4",
|
|
28
|
+
"@arronqzy/vue-blueprint": "0.1.4",
|
|
28
29
|
"@arronqzy/view-table": "0.1.3",
|
|
29
|
-
"@arronqzy/i18n": "0.1.3",
|
|
30
30
|
"@arronqzy/vue-rx-store": "0.1.2",
|
|
31
|
-
"@arronqzy/
|
|
31
|
+
"@arronqzy/i18n": "0.1.5"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"vue": "^3.4.0"
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
parsePanelLayers,
|
|
32
32
|
resolvePreviewLayerElements,
|
|
33
33
|
} from "./utils/panelStateIO";
|
|
34
|
+
import { applyPreviewSceneFill, readOutputScale } from "./utils/outputScale";
|
|
34
35
|
|
|
35
36
|
const { t, locale } = useI18n();
|
|
36
37
|
|
|
@@ -49,6 +50,8 @@ const blueprintLibraryItems = ref<BlueprintLibraryListItem[]>([]);
|
|
|
49
50
|
const layoutRevision = ref(0);
|
|
50
51
|
const layoutReady = ref(false);
|
|
51
52
|
const sceneRef = ref<HTMLDivElement | null>(null);
|
|
53
|
+
const outputScale = ref(readOutputScale());
|
|
54
|
+
const fillScale = ref({ scaleX: 1, scaleY: 1 });
|
|
52
55
|
|
|
53
56
|
const layers = computed(() => (panelState.value ? parsePanelLayers(panelState.value) : []));
|
|
54
57
|
const activeLayerId = computed(() =>
|
|
@@ -146,24 +149,30 @@ const scopedLayerElements = computed(() => {
|
|
|
146
149
|
const sceneBounds = computed(() => computePanelSceneBounds(scopedLayerElements.value));
|
|
147
150
|
|
|
148
151
|
const displayElements = computed(() =>
|
|
149
|
-
scopedLayerElements.value.map((el) =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
scopedLayerElements.value.map((el) => {
|
|
153
|
+
const x = el.x - sceneBounds.value.minX;
|
|
154
|
+
const y = el.y - sceneBounds.value.minY;
|
|
155
|
+
if (outputScale.value) return { ...el, x, y };
|
|
156
|
+
return {
|
|
157
|
+
...el,
|
|
158
|
+
x: x * fillScale.value.scaleX,
|
|
159
|
+
y: y * fillScale.value.scaleY,
|
|
160
|
+
width: el.width * fillScale.value.scaleX,
|
|
161
|
+
height: el.height * fillScale.value.scaleY,
|
|
162
|
+
};
|
|
163
|
+
})
|
|
154
164
|
);
|
|
155
165
|
|
|
156
166
|
function applySceneFit() {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
scene.style.transform = `scale(${scaleX}, ${scaleY})`;
|
|
167
|
+
const enabled = readOutputScale();
|
|
168
|
+
outputScale.value = enabled;
|
|
169
|
+
const fill = applyPreviewSceneFill(
|
|
170
|
+
sceneRef.value,
|
|
171
|
+
sceneBounds.value.width,
|
|
172
|
+
sceneBounds.value.height,
|
|
173
|
+
enabled
|
|
174
|
+
);
|
|
175
|
+
fillScale.value = { scaleX: fill.scaleX, scaleY: fill.scaleY };
|
|
167
176
|
layoutRevision.value += 1;
|
|
168
177
|
notifyPreviewLayoutChanged();
|
|
169
178
|
}
|
|
@@ -258,9 +267,10 @@ function noopSelect() {}
|
|
|
258
267
|
id="preview-scene"
|
|
259
268
|
class="relative shrink-0 origin-top-left"
|
|
260
269
|
:style="{
|
|
261
|
-
width: `${sceneBounds.width}px`,
|
|
262
|
-
height: `${sceneBounds.height}px`,
|
|
270
|
+
width: `${outputScale ? sceneBounds.width : sceneBounds.width * fillScale.scaleX}px`,
|
|
271
|
+
height: `${outputScale ? sceneBounds.height : sceneBounds.height * fillScale.scaleY}px`,
|
|
263
272
|
transformOrigin: 'left top',
|
|
273
|
+
transform: outputScale ? `scale(${fillScale.scaleX}, ${fillScale.scaleY})` : 'none',
|
|
264
274
|
}"
|
|
265
275
|
>
|
|
266
276
|
<ElementsLayer
|
|
@@ -863,6 +863,9 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
863
863
|
:on-exclude-selected-node="excludeSelectedNode"
|
|
864
864
|
:on-adjust-node-z-order="adjustNodeZOrder"
|
|
865
865
|
:selected-blueprint-node="selectedBlueprintNode"
|
|
866
|
+
:blueprint-graph-nodes="blueprintGraph.document.nodes"
|
|
867
|
+
:blueprint-graph-edges="blueprintGraph.document.edges"
|
|
868
|
+
:blueprint-trace-entries="blueprintDebugSession.entries.value"
|
|
866
869
|
:allow-false-signal-propagation="blueprintGraph.document.allowFalseSignalPropagation"
|
|
867
870
|
:blueprint-library-options="blueprintLibraryOptions"
|
|
868
871
|
:blueprint-node-options="blueprintNodeOptions"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useI18n } from "@arronqzy/i18n/vue";
|
|
3
|
-
import { computed, onMounted, ref, watch } from "vue";
|
|
3
|
+
import { computed, onMounted, provide, ref, watch } from "vue";
|
|
4
4
|
import { Empty, Input } from "ant-design-vue";
|
|
5
5
|
import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../types";
|
|
6
6
|
import { CHART_TYPES } from "../utils/chartOptionBuilder";
|
|
@@ -61,8 +61,8 @@ const sidebarScrollRef = ref<HTMLElement | null>(null);
|
|
|
61
61
|
|
|
62
62
|
const expandedSections = ref<Record<string, boolean>>({
|
|
63
63
|
nodeInfo: true,
|
|
64
|
-
styleBackground:
|
|
65
|
-
styleBorder:
|
|
64
|
+
styleBackground: false,
|
|
65
|
+
styleBorder: false,
|
|
66
66
|
chartBasic: true,
|
|
67
67
|
chartAdvanced: false,
|
|
68
68
|
textConfig: true,
|
|
@@ -123,6 +123,7 @@ const scopeWarnings = computed(() => {
|
|
|
123
123
|
const materialType = computed(() => props.selectedElement?.materialType ?? "");
|
|
124
124
|
|
|
125
125
|
const forceOpenSections = computed(() => hasSearch.value);
|
|
126
|
+
provide("configHasSearch", hasSearch);
|
|
126
127
|
|
|
127
128
|
function isSectionExpanded(key: string, defaultValue = true) {
|
|
128
129
|
return expandedSections.value[key] ?? defaultValue;
|
|
@@ -259,23 +260,6 @@ watch(
|
|
|
259
260
|
@update:open="(v) => setSectionExpanded('nodeInfo', v)"
|
|
260
261
|
/>
|
|
261
262
|
|
|
262
|
-
<PanelConfigStyleSections
|
|
263
|
-
v-if="
|
|
264
|
-
shouldShowSection('styleBackground', t('panel.config.sectionStyleBackground'), [t('panel.config.backgroundColor'), 'background']) ||
|
|
265
|
-
shouldShowSection('styleBorder', t('panel.config.sectionStyleBorder'), [t('panel.config.searchKwBorder'), 'border'])
|
|
266
|
-
"
|
|
267
|
-
:element="selectedElement"
|
|
268
|
-
:is-editable="isNodeEditable"
|
|
269
|
-
:show-background="shouldShowSection('styleBackground', t('panel.config.sectionStyleBackground'), [t('panel.config.backgroundColor'), 'background'])"
|
|
270
|
-
:show-border="shouldShowSection('styleBorder', t('panel.config.sectionStyleBorder'), [t('panel.config.searchKwBorder'), 'border'])"
|
|
271
|
-
:background-open="isSectionExpanded('styleBackground')"
|
|
272
|
-
:border-open="isSectionExpanded('styleBorder')"
|
|
273
|
-
:force-open="forceOpenSections"
|
|
274
|
-
:update-element="updateElement"
|
|
275
|
-
@update:background-open="(v) => setSectionExpanded('styleBackground', v)"
|
|
276
|
-
@update:border-open="(v) => setSectionExpanded('styleBorder', v)"
|
|
277
|
-
/>
|
|
278
|
-
|
|
279
263
|
<PanelConfigChartSection
|
|
280
264
|
v-if="isChartElement && shouldShowSection('chartBasic', t('panel.config.sectionChartBasic'), [t('panel.material.charts'), 'tooltip'])"
|
|
281
265
|
:element="selectedElement"
|
|
@@ -381,6 +365,23 @@ watch(
|
|
|
381
365
|
@update:open="(v) => setSectionExpanded('reference', v)"
|
|
382
366
|
/>
|
|
383
367
|
|
|
368
|
+
<PanelConfigStyleSections
|
|
369
|
+
v-if="
|
|
370
|
+
shouldShowSection('styleBackground', t('panel.config.sectionStyleBackground'), [t('panel.config.backgroundColor'), 'background']) ||
|
|
371
|
+
shouldShowSection('styleBorder', t('panel.config.sectionStyleBorder'), [t('panel.config.searchKwBorder'), 'border'])
|
|
372
|
+
"
|
|
373
|
+
:element="selectedElement"
|
|
374
|
+
:is-editable="isNodeEditable"
|
|
375
|
+
:show-background="shouldShowSection('styleBackground', t('panel.config.sectionStyleBackground'), [t('panel.config.backgroundColor'), 'background'])"
|
|
376
|
+
:show-border="shouldShowSection('styleBorder', t('panel.config.sectionStyleBorder'), [t('panel.config.searchKwBorder'), 'border'])"
|
|
377
|
+
:background-open="isSectionExpanded('styleBackground')"
|
|
378
|
+
:border-open="isSectionExpanded('styleBorder')"
|
|
379
|
+
:force-open="forceOpenSections"
|
|
380
|
+
:update-element="updateElement"
|
|
381
|
+
@update:background-open="(v) => setSectionExpanded('styleBackground', v)"
|
|
382
|
+
@update:border-open="(v) => setSectionExpanded('styleBorder', v)"
|
|
383
|
+
/>
|
|
384
|
+
|
|
384
385
|
<div
|
|
385
386
|
v-if="!isChartElement && materialType && !['text', 'table', 'audio', 'video', 'image', 'geometry', 'grid', 'reference'].includes(materialType)"
|
|
386
387
|
class="text-xs leading-6 text-gray-500"
|
|
@@ -5,6 +5,7 @@ import { Empty } from "ant-design-vue";
|
|
|
5
5
|
import {
|
|
6
6
|
BlueprintExecutionLogPanel,
|
|
7
7
|
BlueprintNodeConfigSidebar,
|
|
8
|
+
type BlueprintGraphEdge,
|
|
8
9
|
type BlueprintGraphNode,
|
|
9
10
|
type ExecutionLogSettings,
|
|
10
11
|
type ExecutionTraceEntry,
|
|
@@ -56,6 +57,9 @@ export type WorkspaceConfigSidebarProps = Omit<
|
|
|
56
57
|
configFocus: WorkspaceConfigFocus;
|
|
57
58
|
executionLog?: BlueprintExecutionLogViewProps;
|
|
58
59
|
selectedBlueprintNode: BlueprintGraphNode | null;
|
|
60
|
+
blueprintGraphNodes?: BlueprintGraphNode[];
|
|
61
|
+
blueprintGraphEdges?: BlueprintGraphEdge[];
|
|
62
|
+
blueprintTraceEntries?: ExecutionTraceEntry[];
|
|
59
63
|
allowFalseSignalPropagation?: boolean;
|
|
60
64
|
onUpdateAllowFalseSignalPropagation?: (value: boolean) => void;
|
|
61
65
|
onUpdateBlueprintNode: (
|
|
@@ -90,6 +94,9 @@ const props = withDefaults(
|
|
|
90
94
|
{
|
|
91
95
|
allowFalseSignalPropagation: false,
|
|
92
96
|
blueprintLibraryOptions: () => [],
|
|
97
|
+
blueprintGraphNodes: () => [],
|
|
98
|
+
blueprintGraphEdges: () => [],
|
|
99
|
+
blueprintTraceEntries: () => [],
|
|
93
100
|
blueprintNodeOptions: () => [],
|
|
94
101
|
selectedElements: () => [],
|
|
95
102
|
}
|
|
@@ -143,6 +150,9 @@ const viewElementOptions = computed(() =>
|
|
|
143
150
|
<BlueprintNodeConfigSidebar
|
|
144
151
|
v-else
|
|
145
152
|
:node="selectedBlueprintNode"
|
|
153
|
+
:graph-nodes="blueprintGraphNodes"
|
|
154
|
+
:graph-edges="blueprintGraphEdges"
|
|
155
|
+
:trace-entries="blueprintTraceEntries.length ? blueprintTraceEntries : executionLog?.entries ?? []"
|
|
146
156
|
:allow-false-signal-propagation="allowFalseSignalPropagation"
|
|
147
157
|
:on-update-allow-false-signal-propagation="onUpdateAllowFalseSignalPropagation"
|
|
148
158
|
:view-element-options="viewElementOptions"
|
|
@@ -1,20 +1,49 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed, inject, ref, type ComputedRef } from "vue";
|
|
2
3
|
import ConfigHintIcon from "../ConfigHintIcon.vue";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const props = withDefaults(
|
|
6
|
+
defineProps<{
|
|
7
|
+
title: string;
|
|
8
|
+
hint?: boolean;
|
|
9
|
+
collapsible?: boolean;
|
|
10
|
+
defaultOpen?: boolean;
|
|
11
|
+
}>(),
|
|
12
|
+
{ defaultOpen: true, collapsible: false }
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const hasSearch = inject<ComputedRef<boolean>>(
|
|
16
|
+
"configHasSearch",
|
|
17
|
+
computed(() => false)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const open = ref(props.defaultOpen);
|
|
21
|
+
const isOpen = computed(() => !props.collapsible || hasSearch.value || open.value);
|
|
22
|
+
|
|
23
|
+
function toggle() {
|
|
24
|
+
if (!props.collapsible || hasSearch.value) return;
|
|
25
|
+
open.value = !open.value;
|
|
26
|
+
}
|
|
8
27
|
</script>
|
|
9
28
|
|
|
10
29
|
<template>
|
|
11
30
|
<div class="space-y-2.5 rounded-lg border border-gray-200/80 bg-white/80 p-2.5">
|
|
12
|
-
<div class="flex items-center gap-1">
|
|
31
|
+
<div class="flex items-center gap-1.5">
|
|
32
|
+
<button
|
|
33
|
+
v-if="collapsible"
|
|
34
|
+
type="button"
|
|
35
|
+
class="flex h-5 w-5 items-center justify-center rounded text-[11px] hover:bg-gray-100"
|
|
36
|
+
@click="toggle"
|
|
37
|
+
>
|
|
38
|
+
{{ isOpen ? "▾" : "▸" }}
|
|
39
|
+
</button>
|
|
13
40
|
<div class="text-[11px] font-semibold text-gray-500">{{ title }}</div>
|
|
14
41
|
<ConfigHintIcon v-if="$slots.hint" :label="title">
|
|
15
42
|
<slot name="hint" />
|
|
16
43
|
</ConfigHintIcon>
|
|
17
44
|
</div>
|
|
18
|
-
<
|
|
45
|
+
<div v-show="isOpen" class="space-y-2.5">
|
|
46
|
+
<slot />
|
|
47
|
+
</div>
|
|
19
48
|
</div>
|
|
20
49
|
</template>
|
|
@@ -168,7 +168,11 @@ function onPosterFileChange(e: Event) {
|
|
|
168
168
|
:src="element.audioSrc || element.audioRemoteUrl || ''"
|
|
169
169
|
/>
|
|
170
170
|
</ConfigFieldGroup>
|
|
171
|
-
<ConfigFieldGroup
|
|
171
|
+
<ConfigFieldGroup
|
|
172
|
+
:title="t('panel.config.groupDisplayStyle')"
|
|
173
|
+
collapsible
|
|
174
|
+
:default-open="false"
|
|
175
|
+
>
|
|
172
176
|
<label class="flex items-center gap-2">
|
|
173
177
|
<Checkbox
|
|
174
178
|
:checked="element.mediaAutoPauseOnEdit !== false"
|
|
@@ -143,6 +143,100 @@ function onOptionJsonChange(v: string) {
|
|
|
143
143
|
:disabled="!isEditable"
|
|
144
144
|
@update:value="(v) => updateChart({ color: v || '#3b82f6' })"
|
|
145
145
|
/>
|
|
146
|
+
</ConfigFieldGroup>
|
|
147
|
+
<ConfigFieldGroup :title="t('panel.config.groupData')">
|
|
148
|
+
<label class="block space-y-1">
|
|
149
|
+
<div>{{ t("panel.config.labelsCsv") }}</div>
|
|
150
|
+
<Input
|
|
151
|
+
size="small"
|
|
152
|
+
:value="getChartLabelsDisplayText(element.chart)"
|
|
153
|
+
:disabled="!isEditable"
|
|
154
|
+
@update:value="(v: string) => updateChart({ labelsText: v })"
|
|
155
|
+
/>
|
|
156
|
+
</label>
|
|
157
|
+
<label class="block space-y-1">
|
|
158
|
+
<div>{{ t("panel.config.valuesCsv") }}</div>
|
|
159
|
+
<Input
|
|
160
|
+
size="small"
|
|
161
|
+
:value="getChartValuesDisplayText(element.chart)"
|
|
162
|
+
:disabled="!isEditable"
|
|
163
|
+
@update:value="(v: string) => updateChart({ valuesText: v })"
|
|
164
|
+
/>
|
|
165
|
+
</label>
|
|
166
|
+
</ConfigFieldGroup>
|
|
167
|
+
|
|
168
|
+
<ConfigFieldGroup
|
|
169
|
+
v-if="['bar', 'line', 'area', 'pie'].includes(selectedChartType)"
|
|
170
|
+
:title="t('panel.config.groupSeries')"
|
|
171
|
+
>
|
|
172
|
+
<label v-if="selectedChartType === 'bar'" class="block space-y-1">
|
|
173
|
+
<div>{{ t("panel.config.barWidthPx") }}</div>
|
|
174
|
+
<InputNumber
|
|
175
|
+
size="small"
|
|
176
|
+
class="w-full"
|
|
177
|
+
:min="1"
|
|
178
|
+
:placeholder="t('panel.config.barWidthAuto')"
|
|
179
|
+
:value="element.chart?.barWidth ?? undefined"
|
|
180
|
+
:disabled="!isEditable"
|
|
181
|
+
@update:value="(v) => {
|
|
182
|
+
if (v == null || v === '') {
|
|
183
|
+
updateChart({ barWidth: undefined });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const n = Number(v);
|
|
187
|
+
if (!Number.isNaN(n) && n > 0) updateChart({ barWidth: Math.max(1, n) });
|
|
188
|
+
}"
|
|
189
|
+
/>
|
|
190
|
+
</label>
|
|
191
|
+
<label
|
|
192
|
+
v-if="selectedChartType === 'line' || selectedChartType === 'area'"
|
|
193
|
+
class="flex items-center gap-2"
|
|
194
|
+
>
|
|
195
|
+
<Checkbox
|
|
196
|
+
:checked="element.chart?.smooth ?? true"
|
|
197
|
+
:disabled="!isEditable"
|
|
198
|
+
@update:checked="(v) => updateChart({ smooth: v === true })"
|
|
199
|
+
/>
|
|
200
|
+
<span>{{ t("panel.config.smooth") }}</span>
|
|
201
|
+
</label>
|
|
202
|
+
<div v-if="selectedChartType === 'pie'" class="grid grid-cols-2 gap-2">
|
|
203
|
+
<label class="block space-y-1">
|
|
204
|
+
<div>{{ t("panel.config.pieInnerRadiusPct") }}</div>
|
|
205
|
+
<InputNumber
|
|
206
|
+
size="small"
|
|
207
|
+
class="w-full"
|
|
208
|
+
:min="0"
|
|
209
|
+
:max="99"
|
|
210
|
+
:value="element.chart?.pieInnerRadius ?? 30"
|
|
211
|
+
:disabled="!isEditable"
|
|
212
|
+
@update:value="(v) => {
|
|
213
|
+
const n = Number(v);
|
|
214
|
+
if (!Number.isNaN(n)) updateChart({ pieInnerRadius: Math.max(0, Math.min(99, n)) });
|
|
215
|
+
}"
|
|
216
|
+
/>
|
|
217
|
+
</label>
|
|
218
|
+
<label class="block space-y-1">
|
|
219
|
+
<div>{{ t("panel.config.pieOuterRadiusPct") }}</div>
|
|
220
|
+
<InputNumber
|
|
221
|
+
size="small"
|
|
222
|
+
class="w-full"
|
|
223
|
+
:min="1"
|
|
224
|
+
:max="100"
|
|
225
|
+
:value="element.chart?.pieOuterRadius ?? 65"
|
|
226
|
+
:disabled="!isEditable"
|
|
227
|
+
@update:value="(v) => {
|
|
228
|
+
const n = Number(v);
|
|
229
|
+
if (!Number.isNaN(n)) updateChart({ pieOuterRadius: Math.max(1, Math.min(100, n)) });
|
|
230
|
+
}"
|
|
231
|
+
/>
|
|
232
|
+
</label>
|
|
233
|
+
</div>
|
|
234
|
+
</ConfigFieldGroup>
|
|
235
|
+
<ConfigFieldGroup
|
|
236
|
+
:title="t('panel.config.groupChartDisplayMore')"
|
|
237
|
+
collapsible
|
|
238
|
+
:default-open="false"
|
|
239
|
+
>
|
|
146
240
|
<label class="flex items-center gap-2">
|
|
147
241
|
<Checkbox
|
|
148
242
|
:checked="element.chart?.colorMode === 'gradient'"
|
|
@@ -208,7 +302,11 @@ function onOptionJsonChange(v: string) {
|
|
|
208
302
|
</label>
|
|
209
303
|
</ConfigFieldGroup>
|
|
210
304
|
|
|
211
|
-
<ConfigFieldGroup
|
|
305
|
+
<ConfigFieldGroup
|
|
306
|
+
:title="t('panel.config.groupTooltip')"
|
|
307
|
+
collapsible
|
|
308
|
+
:default-open="false"
|
|
309
|
+
>
|
|
212
310
|
<div class="grid grid-cols-2 gap-2">
|
|
213
311
|
<label class="flex items-center gap-2">
|
|
214
312
|
<Checkbox
|
|
@@ -264,30 +362,11 @@ function onOptionJsonChange(v: string) {
|
|
|
264
362
|
</label>
|
|
265
363
|
</ConfigFieldGroup>
|
|
266
364
|
|
|
267
|
-
<ConfigFieldGroup :title="t('panel.config.groupData')">
|
|
268
|
-
<label class="block space-y-1">
|
|
269
|
-
<div>{{ t("panel.config.labelsCsv") }}</div>
|
|
270
|
-
<Input
|
|
271
|
-
size="small"
|
|
272
|
-
:value="getChartLabelsDisplayText(element.chart)"
|
|
273
|
-
:disabled="!isEditable"
|
|
274
|
-
@update:value="(v: string) => updateChart({ labelsText: v })"
|
|
275
|
-
/>
|
|
276
|
-
</label>
|
|
277
|
-
<label class="block space-y-1">
|
|
278
|
-
<div>{{ t("panel.config.valuesCsv") }}</div>
|
|
279
|
-
<Input
|
|
280
|
-
size="small"
|
|
281
|
-
:value="getChartValuesDisplayText(element.chart)"
|
|
282
|
-
:disabled="!isEditable"
|
|
283
|
-
@update:value="(v: string) => updateChart({ valuesText: v })"
|
|
284
|
-
/>
|
|
285
|
-
</label>
|
|
286
|
-
</ConfigFieldGroup>
|
|
287
|
-
|
|
288
365
|
<ConfigFieldGroup
|
|
289
366
|
v-if="['bar', 'line', 'area', 'scatter'].includes(selectedChartType)"
|
|
290
367
|
:title="t('panel.config.groupAxes')"
|
|
368
|
+
collapsible
|
|
369
|
+
:default-open="false"
|
|
291
370
|
>
|
|
292
371
|
<div class="grid grid-cols-2 gap-2">
|
|
293
372
|
<label class="block">
|
|
@@ -407,68 +486,6 @@ function onOptionJsonChange(v: string) {
|
|
|
407
486
|
</div>
|
|
408
487
|
</ConfigFieldGroup>
|
|
409
488
|
|
|
410
|
-
<ConfigFieldGroup
|
|
411
|
-
v-if="['bar', 'line', 'area', 'pie'].includes(selectedChartType)"
|
|
412
|
-
:title="t('panel.config.groupSeries')"
|
|
413
|
-
>
|
|
414
|
-
<label v-if="selectedChartType === 'bar'" class="block space-y-1">
|
|
415
|
-
<div>{{ t("panel.config.barWidthPx") }}</div>
|
|
416
|
-
<InputNumber
|
|
417
|
-
size="small"
|
|
418
|
-
class="w-full"
|
|
419
|
-
:min="1"
|
|
420
|
-
:value="element.chart?.barWidth ?? 24"
|
|
421
|
-
:disabled="!isEditable"
|
|
422
|
-
@update:value="(v) => {
|
|
423
|
-
const n = Number(v);
|
|
424
|
-
if (!Number.isNaN(n)) updateChart({ barWidth: Math.max(1, n) });
|
|
425
|
-
}"
|
|
426
|
-
/>
|
|
427
|
-
</label>
|
|
428
|
-
<label
|
|
429
|
-
v-if="selectedChartType === 'line' || selectedChartType === 'area'"
|
|
430
|
-
class="flex items-center gap-2"
|
|
431
|
-
>
|
|
432
|
-
<Checkbox
|
|
433
|
-
:checked="element.chart?.smooth ?? true"
|
|
434
|
-
:disabled="!isEditable"
|
|
435
|
-
@update:checked="(v) => updateChart({ smooth: v === true })"
|
|
436
|
-
/>
|
|
437
|
-
<span>{{ t("panel.config.smooth") }}</span>
|
|
438
|
-
</label>
|
|
439
|
-
<div v-if="selectedChartType === 'pie'" class="grid grid-cols-2 gap-2">
|
|
440
|
-
<label class="block space-y-1">
|
|
441
|
-
<div>{{ t("panel.config.pieInnerRadiusPct") }}</div>
|
|
442
|
-
<InputNumber
|
|
443
|
-
size="small"
|
|
444
|
-
class="w-full"
|
|
445
|
-
:min="0"
|
|
446
|
-
:max="99"
|
|
447
|
-
:value="element.chart?.pieInnerRadius ?? 30"
|
|
448
|
-
:disabled="!isEditable"
|
|
449
|
-
@update:value="(v) => {
|
|
450
|
-
const n = Number(v);
|
|
451
|
-
if (!Number.isNaN(n)) updateChart({ pieInnerRadius: Math.max(0, Math.min(99, n)) });
|
|
452
|
-
}"
|
|
453
|
-
/>
|
|
454
|
-
</label>
|
|
455
|
-
<label class="block space-y-1">
|
|
456
|
-
<div>{{ t("panel.config.pieOuterRadiusPct") }}</div>
|
|
457
|
-
<InputNumber
|
|
458
|
-
size="small"
|
|
459
|
-
class="w-full"
|
|
460
|
-
:min="1"
|
|
461
|
-
:max="100"
|
|
462
|
-
:value="element.chart?.pieOuterRadius ?? 65"
|
|
463
|
-
:disabled="!isEditable"
|
|
464
|
-
@update:value="(v) => {
|
|
465
|
-
const n = Number(v);
|
|
466
|
-
if (!Number.isNaN(n)) updateChart({ pieOuterRadius: Math.max(1, Math.min(100, n)) });
|
|
467
|
-
}"
|
|
468
|
-
/>
|
|
469
|
-
</label>
|
|
470
|
-
</div>
|
|
471
|
-
</ConfigFieldGroup>
|
|
472
489
|
</ConfigSection>
|
|
473
490
|
|
|
474
491
|
<ConfigSection
|
|
@@ -138,7 +138,11 @@ function clearSketch() {
|
|
|
138
138
|
@update:value="(v) => patch({ geometryColor: v || '#3b82f6' })"
|
|
139
139
|
/>
|
|
140
140
|
</ConfigFieldGroup>
|
|
141
|
-
<ConfigFieldGroup
|
|
141
|
+
<ConfigFieldGroup
|
|
142
|
+
:title="t('panel.config.groupCanvasScript')"
|
|
143
|
+
collapsible
|
|
144
|
+
:default-open="false"
|
|
145
|
+
>
|
|
142
146
|
<template #hint>
|
|
143
147
|
{{ t("panel.config.canvasScriptHint") }}
|
|
144
148
|
</template>
|
|
@@ -152,7 +156,11 @@ function clearSketch() {
|
|
|
152
156
|
@update:value="(v: string) => patch({ geometryScript: v || undefined })"
|
|
153
157
|
/>
|
|
154
158
|
</ConfigFieldGroup>
|
|
155
|
-
<ConfigFieldGroup
|
|
159
|
+
<ConfigFieldGroup
|
|
160
|
+
:title="t('panel.config.groupSketchOverlay')"
|
|
161
|
+
collapsible
|
|
162
|
+
:default-open="false"
|
|
163
|
+
>
|
|
156
164
|
<div class="flex items-center gap-2">
|
|
157
165
|
<label class="block space-y-1">
|
|
158
166
|
<div class="text-[11px]">{{ t("panel.config.penColor") }}</div>
|
|
@@ -100,7 +100,11 @@ function onFileChange(e: Event) {
|
|
|
100
100
|
<span v-if="uploadStatus" class="text-[11px] text-gray-500">{{ uploadStatus }}</span>
|
|
101
101
|
</div>
|
|
102
102
|
</ConfigFieldGroup>
|
|
103
|
-
<ConfigFieldGroup
|
|
103
|
+
<ConfigFieldGroup
|
|
104
|
+
:title="t('panel.config.groupDisplayMode')"
|
|
105
|
+
collapsible
|
|
106
|
+
:default-open="false"
|
|
107
|
+
>
|
|
104
108
|
<label class="block space-y-1">
|
|
105
109
|
<div>{{ t("panel.config.objectFitFull") }}</div>
|
|
106
110
|
<Select
|
|
@@ -4,6 +4,7 @@ import { Checkbox, Input, InputNumber, Select } from "ant-design-vue";
|
|
|
4
4
|
import type { PanelElement, PanelLayer } from "../../types";
|
|
5
5
|
import { getPanelMessages } from "../../constants/messages";
|
|
6
6
|
import ConfigSection from "./ConfigSection.vue";
|
|
7
|
+
import ConfigFieldGroup from "./ConfigFieldGroup.vue";
|
|
7
8
|
|
|
8
9
|
const { t, locale } = useI18n();
|
|
9
10
|
const msgs = () => getPanelMessages(t);
|
|
@@ -52,7 +53,7 @@ function patch(patch: Partial<PanelElement>) {
|
|
|
52
53
|
@update:value="(v: string) => patch({ name: v || undefined })"
|
|
53
54
|
/>
|
|
54
55
|
</label>
|
|
55
|
-
<div class="grid grid-cols-
|
|
56
|
+
<div class="grid grid-cols-2 gap-2">
|
|
56
57
|
<label class="block space-y-1">
|
|
57
58
|
<div>X</div>
|
|
58
59
|
<InputNumber
|
|
@@ -74,17 +75,7 @@ function patch(patch: Partial<PanelElement>) {
|
|
|
74
75
|
/>
|
|
75
76
|
</label>
|
|
76
77
|
</div>
|
|
77
|
-
<div class="grid grid-cols-
|
|
78
|
-
<label class="block space-y-1">
|
|
79
|
-
<div>{{ t("panel.config.rotate") }}</div>
|
|
80
|
-
<InputNumber
|
|
81
|
-
size="small"
|
|
82
|
-
class="w-full"
|
|
83
|
-
:value="element.rotate ?? 0"
|
|
84
|
-
:disabled="!isEditable"
|
|
85
|
-
@update:value="(v) => { const n = Number(v); if (!Number.isNaN(n)) patch({ rotate: n }); }"
|
|
86
|
-
/>
|
|
87
|
-
</label>
|
|
78
|
+
<div class="grid grid-cols-2 gap-2">
|
|
88
79
|
<label class="block space-y-1">
|
|
89
80
|
<div>{{ t("panel.config.width") }}</div>
|
|
90
81
|
<InputNumber
|
|
@@ -108,6 +99,23 @@ function patch(patch: Partial<PanelElement>) {
|
|
|
108
99
|
/>
|
|
109
100
|
</label>
|
|
110
101
|
</div>
|
|
102
|
+
<ConfigFieldGroup
|
|
103
|
+
:title="t('panel.config.groupNodeMore')"
|
|
104
|
+
collapsible
|
|
105
|
+
:default-open="false"
|
|
106
|
+
>
|
|
107
|
+
<div class="grid grid-cols-2 gap-2">
|
|
108
|
+
<label class="block space-y-1">
|
|
109
|
+
<div>{{ t("panel.config.rotate") }}</div>
|
|
110
|
+
<InputNumber
|
|
111
|
+
size="small"
|
|
112
|
+
class="w-full"
|
|
113
|
+
:value="element.rotate ?? 0"
|
|
114
|
+
:disabled="!isEditable"
|
|
115
|
+
@update:value="(v) => { const n = Number(v); if (!Number.isNaN(n)) patch({ rotate: n }); }"
|
|
116
|
+
/>
|
|
117
|
+
</label>
|
|
118
|
+
</div>
|
|
111
119
|
<div class="space-y-1.5">
|
|
112
120
|
<div class="text-[11px] text-gray-500">{{ t("panel.config.nodeZOrder") }}</div>
|
|
113
121
|
<div class="text-[11px] text-gray-400">{{ t("panel.config.currentZIndex", { value: nodeZOrderLabel ?? "-" }) }}</div>
|
|
@@ -148,6 +156,7 @@ function patch(patch: Partial<PanelElement>) {
|
|
|
148
156
|
</div>
|
|
149
157
|
<div class="truncate text-gray-400">ID: {{ element.id }}</div>
|
|
150
158
|
<div class="text-gray-400">{{ t("panel.config.type") }}: {{ element.materialType ?? element.id }}</div>
|
|
159
|
+
</ConfigFieldGroup>
|
|
151
160
|
<div class="rounded-lg border border-gray-200/60 bg-white/80 px-2.5 py-2">
|
|
152
161
|
<label class="flex items-center gap-2">
|
|
153
162
|
<Checkbox
|
|
@@ -57,7 +57,11 @@ function patch(patch: Partial<PanelElement>) {
|
|
|
57
57
|
</Select>
|
|
58
58
|
</label>
|
|
59
59
|
</ConfigFieldGroup>
|
|
60
|
-
<ConfigFieldGroup
|
|
60
|
+
<ConfigFieldGroup
|
|
61
|
+
:title="t('panel.config.groupCopyStrategy')"
|
|
62
|
+
collapsible
|
|
63
|
+
:default-open="false"
|
|
64
|
+
>
|
|
61
65
|
<template #hint>
|
|
62
66
|
{{ t("panel.config.copyStrategyHint") }}
|
|
63
67
|
</template>
|
|
@@ -76,6 +76,12 @@ function onFileChange(e: Event) {
|
|
|
76
76
|
:disabled="!isEditable"
|
|
77
77
|
@update:value="(v) => patchStyle({ backgroundColor: v || undefined })"
|
|
78
78
|
/>
|
|
79
|
+
</ConfigFieldGroup>
|
|
80
|
+
<ConfigFieldGroup
|
|
81
|
+
:title="t('panel.config.groupBgLayout')"
|
|
82
|
+
collapsible
|
|
83
|
+
:default-open="false"
|
|
84
|
+
>
|
|
79
85
|
<label class="block space-y-1">
|
|
80
86
|
<div>{{ t("panel.scope.fieldStyleBackgroundImage") }}</div>
|
|
81
87
|
<Input
|
|
@@ -102,8 +108,6 @@ function onFileChange(e: Event) {
|
|
|
102
108
|
</label>
|
|
103
109
|
<span v-if="uploadStatus" class="text-[11px] text-gray-500">{{ uploadStatus }}</span>
|
|
104
110
|
</div>
|
|
105
|
-
</ConfigFieldGroup>
|
|
106
|
-
<ConfigFieldGroup :title="t('panel.config.groupBgLayout')">
|
|
107
111
|
<div class="grid grid-cols-2 gap-2">
|
|
108
112
|
<label class="block space-y-1">
|
|
109
113
|
<div>{{ t("panel.scope.fieldStyleBackgroundSize") }}</div>
|
|
@@ -184,7 +184,11 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
|
|
|
184
184
|
@update:value="(v) => patch({ textColor: v || undefined })"
|
|
185
185
|
/>
|
|
186
186
|
</ConfigFieldGroup>
|
|
187
|
-
<ConfigFieldGroup
|
|
187
|
+
<ConfigFieldGroup
|
|
188
|
+
:title="t('panel.config.groupInputAbility')"
|
|
189
|
+
collapsible
|
|
190
|
+
:default-open="false"
|
|
191
|
+
>
|
|
188
192
|
<label class="flex items-center gap-2">
|
|
189
193
|
<Checkbox
|
|
190
194
|
:checked="element.textAllowInput ?? true"
|
|
@@ -84,6 +84,11 @@ function deepMerge<T extends Record<string, any>>(base: T, patch?: Record<string
|
|
|
84
84
|
return next as T;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function resolveBarWidth(chart?: PanelChartConfig): number | undefined {
|
|
88
|
+
const width = chart?.barWidth;
|
|
89
|
+
return typeof width === "number" && Number.isFinite(width) && width > 0 ? width : undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
export function buildChartOption(element: PanelElement): EChartsOption {
|
|
88
93
|
const chartType = (element.materialType ?? "") as ChartType;
|
|
89
94
|
const labels = resolveChartLabels(element);
|
|
@@ -315,7 +320,8 @@ export function buildChartOption(element: PanelElement): EChartsOption {
|
|
|
315
320
|
chartType === "line" || chartType === "area"
|
|
316
321
|
? (element.chart?.smooth ?? true)
|
|
317
322
|
: undefined,
|
|
318
|
-
barWidth:
|
|
323
|
+
barWidth:
|
|
324
|
+
chartType === "bar" ? resolveBarWidth(element.chart) : undefined,
|
|
319
325
|
areaStyle: chartType === "area" ? { opacity: 0.25 } : undefined,
|
|
320
326
|
itemStyle: { color: chartColor as any },
|
|
321
327
|
lineStyle: { color: chartColor as any },
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** 产物预览是否缩放内部内容(字体等)。默认 false:块仍按视口撑满,内容保持原尺寸以免失真。 */
|
|
2
|
+
export const OUTPUT_SCALE_STORAGE_KEY = "panel:outputScale";
|
|
3
|
+
|
|
4
|
+
export function readOutputScale(): boolean {
|
|
5
|
+
try {
|
|
6
|
+
return localStorage.getItem(OUTPUT_SCALE_STORAGE_KEY) === "1";
|
|
7
|
+
} catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type PreviewFillScale = {
|
|
13
|
+
vw: number;
|
|
14
|
+
vh: number;
|
|
15
|
+
scaleX: number;
|
|
16
|
+
scaleY: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function getPreviewFillScale(
|
|
20
|
+
sceneWidth: number,
|
|
21
|
+
sceneHeight: number
|
|
22
|
+
): PreviewFillScale {
|
|
23
|
+
const vw = window.innerWidth || 1;
|
|
24
|
+
const vh = window.innerHeight || 1;
|
|
25
|
+
const sw = Math.max(1, sceneWidth);
|
|
26
|
+
const sh = Math.max(1, sceneHeight);
|
|
27
|
+
return {
|
|
28
|
+
vw,
|
|
29
|
+
vh,
|
|
30
|
+
scaleX: vw / sw,
|
|
31
|
+
scaleY: vh / sh,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function applyPreviewSceneFill(
|
|
36
|
+
scene: HTMLElement | null,
|
|
37
|
+
sceneWidth: number,
|
|
38
|
+
sceneHeight: number,
|
|
39
|
+
outputScale: boolean
|
|
40
|
+
): PreviewFillScale {
|
|
41
|
+
const fill = getPreviewFillScale(sceneWidth, sceneHeight);
|
|
42
|
+
if (!Number.isFinite(fill.scaleX) || !Number.isFinite(fill.scaleY)) return fill;
|
|
43
|
+
if (!scene) return fill;
|
|
44
|
+
const root = scene.parentElement;
|
|
45
|
+
if (root) root.style.overflow = "hidden";
|
|
46
|
+
if (outputScale) {
|
|
47
|
+
scene.style.width = `${Math.max(1, sceneWidth)}px`;
|
|
48
|
+
scene.style.height = `${Math.max(1, sceneHeight)}px`;
|
|
49
|
+
scene.style.transform = `translate(0px,0px) scale(${fill.scaleX},${fill.scaleY})`;
|
|
50
|
+
} else {
|
|
51
|
+
scene.style.width = `${fill.vw}px`;
|
|
52
|
+
scene.style.height = `${fill.vh}px`;
|
|
53
|
+
scene.style.transform = "none";
|
|
54
|
+
}
|
|
55
|
+
return fill;
|
|
56
|
+
}
|