@arronqzy/vue-view 0.1.0 → 0.1.3
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 +10 -7
- package/src/panel/VueViewOnlinePreview.vue +15 -7
- package/src/panel/VueViewPanel.vue +81 -55
- package/src/panel/components/ConfigHintIcon.vue +4 -2
- package/src/panel/components/ElementsLayer.vue +15 -0
- package/src/panel/components/MaterialPreview.vue +19 -1
- package/src/panel/components/MaterialSidebar.vue +35 -31
- package/src/panel/components/MaterialSidebarTreeNode.vue +27 -26
- package/src/panel/components/MoveableLayer.vue +7 -4
- package/src/panel/components/PanelConfigSidebar.vue +57 -39
- package/src/panel/components/ViewElementScopePanel.vue +6 -4
- package/src/panel/components/WorkspaceConfigSidebar.vue +7 -1
- package/src/panel/components/WorkspaceProjectNav.vue +23 -16
- package/src/panel/components/WorkspaceStageSplit.vue +10 -7
- package/src/panel/components/config/ConfigColorField.vue +3 -1
- package/src/panel/components/config/PanelConfigAudioSection.vue +44 -43
- package/src/panel/components/config/PanelConfigChartSection.vue +79 -76
- package/src/panel/components/config/PanelConfigGeometrySection.vue +21 -19
- package/src/panel/components/config/PanelConfigGridChildSpan.vue +7 -5
- package/src/panel/components/config/PanelConfigGridSection.vue +10 -8
- package/src/panel/components/config/PanelConfigImageSection.vue +22 -21
- package/src/panel/components/config/PanelConfigMultiSelect.vue +42 -40
- package/src/panel/components/config/PanelConfigNodeInfo.vue +19 -16
- package/src/panel/components/config/PanelConfigReferenceSection.vue +11 -9
- package/src/panel/components/config/PanelConfigStyleSections.vue +27 -26
- package/src/panel/components/config/PanelConfigTextSection.vue +20 -17
- package/src/panel/components/config/PanelConfigVideoSection.vue +15 -14
- package/src/panel/components/config/shared.ts +0 -3
- package/src/panel/components/elementsLayerNodes.ts +37 -18
- package/src/panel/components/materialSidebarData.ts +73 -54
- package/src/panel/components/scope-config/ScopeTemplatePreviewHost.vue +7 -2
- package/src/panel/components/scope-config/ScopeTemplatePreviewPanel.vue +6 -3
- package/src/panel/components/scope-config/ScopeTemplateUsageHint.vue +7 -16
- package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.vue +4 -2
- package/src/panel/components/table/ConditionEditor.vue +181 -0
- package/src/panel/components/table/JsonCodeEditor.vue +148 -0
- package/src/panel/components/table/PanelConfigTableSection.vue +2033 -0
- package/src/panel/components/table/TableNodeContent.vue +557 -0
- package/src/panel/constants/messages.ts +76 -34
- package/src/panel/hooks/usePanelElements.ts +54 -41
- package/src/panel/hooks/useWorkspaceProjects.ts +8 -5
- package/src/panel/types.ts +4 -0
- package/src/panel/utils/panelElementDefaults.ts +73 -39
- package/src/panel/utils/scope-field-labels.ts +53 -39
- package/src/panel/utils/scope-template-spread.ts +57 -24
- package/src/panel/utils/scope-template-warnings.ts +23 -22
- package/src/panel/utils/scope-template.ts +80 -0
- package/src/tailwind.css +18 -10
|
@@ -10,10 +10,12 @@ import {
|
|
|
10
10
|
type VNode,
|
|
11
11
|
type Component,
|
|
12
12
|
} from "vue";
|
|
13
|
+
import { useI18n } from "@arronqzy/i18n/vue";
|
|
13
14
|
import * as echarts from "echarts";
|
|
14
15
|
import type { PanelElement } from "../types";
|
|
15
16
|
import { buildChartOption, CHART_TYPES } from "../utils/chartOptionBuilder";
|
|
16
17
|
import { PREVIEW_LAYOUT_EVENT } from "../utils/panelStateIO";
|
|
18
|
+
import TableNodeContent from "./table/TableNodeContent.vue";
|
|
17
19
|
|
|
18
20
|
export { CHART_TYPES };
|
|
19
21
|
|
|
@@ -62,8 +64,11 @@ export const TextNodeContent = defineComponent({
|
|
|
62
64
|
},
|
|
63
65
|
emits: ["change"],
|
|
64
66
|
setup(p, { emit }) {
|
|
67
|
+
const { t } = useI18n();
|
|
65
68
|
const nodeRef = ref<HTMLDivElement | null>(null);
|
|
66
|
-
const html = computed(
|
|
69
|
+
const html = computed(
|
|
70
|
+
() => p.element.textHtml ?? `<p>${t("panel.defaults.doubleClickTextHtml")}</p>`
|
|
71
|
+
);
|
|
67
72
|
const textStyle = computed(() => ({
|
|
68
73
|
fontFamily: p.element.textFontFamily || undefined,
|
|
69
74
|
fontSize: p.element.textFontSize ? `${p.element.textFontSize}px` : undefined,
|
|
@@ -101,6 +106,7 @@ export const GridNodeContent = defineComponent({
|
|
|
101
106
|
previewMode: { type: Boolean, default: false },
|
|
102
107
|
},
|
|
103
108
|
setup(p) {
|
|
109
|
+
const { t } = useI18n();
|
|
104
110
|
const rows = computed(() => Math.max(1, Math.floor(p.element.gridRows ?? 2)));
|
|
105
111
|
const cols = computed(() => Math.max(1, Math.floor(p.element.gridCols ?? 3)));
|
|
106
112
|
const gap = computed(() => Math.max(0, p.element.gridGap ?? 8));
|
|
@@ -167,7 +173,9 @@ export const GridNodeContent = defineComponent({
|
|
|
167
173
|
? "border-primary/70 bg-primary/10"
|
|
168
174
|
: "border-border/60 bg-muted/20",
|
|
169
175
|
].join(" "),
|
|
170
|
-
title:
|
|
176
|
+
title: occupied.value.has(idx)
|
|
177
|
+
? t("panel.config.slotOccupied", { n: idx + 1 })
|
|
178
|
+
: t("panel.config.slotEmpty", { n: idx + 1 }),
|
|
171
179
|
})
|
|
172
180
|
)
|
|
173
181
|
),
|
|
@@ -266,6 +274,7 @@ export const AudioNodeContent = defineComponent({
|
|
|
266
274
|
selected: { type: Boolean, default: false },
|
|
267
275
|
},
|
|
268
276
|
setup(p) {
|
|
277
|
+
const { t } = useI18n();
|
|
269
278
|
const audioRef = ref<HTMLAudioElement | null>(null);
|
|
270
279
|
const playing = ref(false);
|
|
271
280
|
const src = computed(() => p.element.audioSrc || p.element.audioRemoteUrl || "");
|
|
@@ -369,12 +378,12 @@ export const AudioNodeContent = defineComponent({
|
|
|
369
378
|
onMousedown: (e: Event) => e.stopPropagation(),
|
|
370
379
|
onPointerdown: (e: Event) => e.stopPropagation(),
|
|
371
380
|
onClick: togglePlay,
|
|
372
|
-
title: !src.value ? "
|
|
381
|
+
title: !src.value ? t("panel.config.configureAudioFirst") : playing.value ? t("panel.config.clickPause") : t("panel.config.clickPlay"),
|
|
373
382
|
},
|
|
374
383
|
[
|
|
375
384
|
h("img", {
|
|
376
385
|
src: poster.value,
|
|
377
|
-
alt: "
|
|
386
|
+
alt: t("panel.config.audioPoster"),
|
|
378
387
|
class: "h-full w-full object-cover",
|
|
379
388
|
draggable: false,
|
|
380
389
|
onDragstart: (e: Event) => e.preventDefault(),
|
|
@@ -389,7 +398,7 @@ export const AudioNodeContent = defineComponent({
|
|
|
389
398
|
onMousedown: (e: Event) => e.stopPropagation(),
|
|
390
399
|
onPointerdown: (e: Event) => e.stopPropagation(),
|
|
391
400
|
onClick: togglePlay,
|
|
392
|
-
title: !src.value ? "
|
|
401
|
+
title: !src.value ? t("panel.config.configureAudioFirst") : playing.value ? t("panel.config.clickPause") : t("panel.config.clickPlay"),
|
|
393
402
|
},
|
|
394
403
|
[renderIcon()]
|
|
395
404
|
),
|
|
@@ -416,7 +425,7 @@ export const AudioNodeContent = defineComponent({
|
|
|
416
425
|
class:
|
|
417
426
|
"pointer-events-none absolute right-2 top-2 rounded bg-black/55 px-1.5 py-0.5 text-[10px] text-white",
|
|
418
427
|
},
|
|
419
|
-
!src.value ? "
|
|
428
|
+
!src.value ? t("panel.config.audioNotConfigured") : playing.value ? t("panel.config.pause") : t("panel.config.play")
|
|
420
429
|
),
|
|
421
430
|
h("audio", { ref: audioRef, src: src.value, preload: "metadata", class: "hidden" }),
|
|
422
431
|
]
|
|
@@ -429,11 +438,11 @@ export const AudioNodeContent = defineComponent({
|
|
|
429
438
|
class:
|
|
430
439
|
"flex h-full w-full items-center justify-center rounded border border-dashed border-border/70 bg-muted/15 px-2 text-[11px] text-muted-foreground",
|
|
431
440
|
},
|
|
432
|
-
"
|
|
441
|
+
t("panel.config.audioPlaceholder")
|
|
433
442
|
);
|
|
434
443
|
}
|
|
435
444
|
return h("div", { class: "relative flex h-full w-full items-center justify-center rounded border border-border/60 bg-muted/10 px-2" }, [
|
|
436
|
-
h("div", { class: "pointer-events-none text-[11px] text-muted-foreground" }, playing.value ? "
|
|
445
|
+
h("div", { class: "pointer-events-none text-[11px] text-muted-foreground" }, playing.value ? t("panel.config.audioPlaying") : t("panel.config.audioReady")),
|
|
437
446
|
h(
|
|
438
447
|
"button",
|
|
439
448
|
{
|
|
@@ -442,9 +451,9 @@ export const AudioNodeContent = defineComponent({
|
|
|
442
451
|
onMousedown: (e: Event) => e.stopPropagation(),
|
|
443
452
|
onPointerdown: (e: Event) => e.stopPropagation(),
|
|
444
453
|
onClick: togglePlay,
|
|
445
|
-
title: playing.value ? "
|
|
454
|
+
title: playing.value ? t("panel.config.clickPause") : t("panel.config.clickPlay"),
|
|
446
455
|
},
|
|
447
|
-
playing.value ? "
|
|
456
|
+
playing.value ? t("panel.config.pause") : t("panel.config.play")
|
|
448
457
|
),
|
|
449
458
|
h("audio", { ref: audioRef, src: src.value, preload: "metadata", class: "hidden" }),
|
|
450
459
|
]);
|
|
@@ -459,6 +468,7 @@ export const VideoNodeContent = defineComponent({
|
|
|
459
468
|
selected: { type: Boolean, default: false },
|
|
460
469
|
},
|
|
461
470
|
setup(p) {
|
|
471
|
+
const { t } = useI18n();
|
|
462
472
|
const videoRef = ref<HTMLVideoElement | null>(null);
|
|
463
473
|
const playing = ref(false);
|
|
464
474
|
const src = computed(() => p.element.videoSrc || p.element.videoRemoteUrl || "");
|
|
@@ -504,7 +514,7 @@ export const VideoNodeContent = defineComponent({
|
|
|
504
514
|
class:
|
|
505
515
|
"flex h-full w-full items-center justify-center rounded border border-dashed border-border/70 bg-muted/15 px-2 text-[11px] text-muted-foreground",
|
|
506
516
|
},
|
|
507
|
-
"
|
|
517
|
+
t("panel.config.videoPlaceholder")
|
|
508
518
|
);
|
|
509
519
|
}
|
|
510
520
|
return h("div", { class: "relative h-full w-full p-1" }, [
|
|
@@ -521,9 +531,9 @@ export const VideoNodeContent = defineComponent({
|
|
|
521
531
|
onMousedown: (e: Event) => e.stopPropagation(),
|
|
522
532
|
onPointerdown: (e: Event) => e.stopPropagation(),
|
|
523
533
|
onClick: togglePlay,
|
|
524
|
-
title: playing.value ? "
|
|
534
|
+
title: playing.value ? t("panel.config.clickPause") : t("panel.config.clickPlay"),
|
|
525
535
|
},
|
|
526
|
-
playing.value ? "
|
|
536
|
+
playing.value ? t("panel.config.pause") : t("panel.config.play")
|
|
527
537
|
),
|
|
528
538
|
]);
|
|
529
539
|
};
|
|
@@ -681,6 +691,7 @@ export const ReferenceNodeContent = defineComponent({
|
|
|
681
691
|
previewMode: { type: Boolean, default: false },
|
|
682
692
|
},
|
|
683
693
|
setup(p) {
|
|
694
|
+
const { t } = useI18n();
|
|
684
695
|
const sourceNodes = computed(() => {
|
|
685
696
|
const fromDeep =
|
|
686
697
|
p.element.refCopyMode === "deep"
|
|
@@ -715,7 +726,7 @@ export const ReferenceNodeContent = defineComponent({
|
|
|
715
726
|
|
|
716
727
|
return () => {
|
|
717
728
|
if (!layout.value || sourceNodes.value.length === 0) {
|
|
718
|
-
const hintText = p.element.refLayerId ? "
|
|
729
|
+
const hintText = p.element.refLayerId ? t("panel.config.refLayerEmpty") : t("panel.config.selectRefLayer");
|
|
719
730
|
return h("div", { class: "flex h-full w-full items-center justify-center" }, [
|
|
720
731
|
h(
|
|
721
732
|
"div",
|
|
@@ -777,7 +788,9 @@ export const ReferenceNodeContent = defineComponent({
|
|
|
777
788
|
})
|
|
778
789
|
: node.materialType === "geometry"
|
|
779
790
|
? h(GeometryNodeContent, { element: node })
|
|
780
|
-
:
|
|
791
|
+
: node.materialType === "table"
|
|
792
|
+
? h(TableNodeContent, { element: node })
|
|
793
|
+
: h("div", { class: "h-full w-full" }),
|
|
781
794
|
]
|
|
782
795
|
),
|
|
783
796
|
]
|
|
@@ -794,6 +807,7 @@ export const ImageNodeContent = defineComponent({
|
|
|
794
807
|
element: { type: Object as PropType<PanelElement>, required: true },
|
|
795
808
|
},
|
|
796
809
|
setup(p) {
|
|
810
|
+
const { t } = useI18n();
|
|
797
811
|
return () => {
|
|
798
812
|
if (hasBackgroundImage(p.element)) return null;
|
|
799
813
|
return h(
|
|
@@ -802,7 +816,7 @@ export const ImageNodeContent = defineComponent({
|
|
|
802
816
|
class:
|
|
803
817
|
"flex h-full w-full items-center justify-center rounded border border-dashed border-border/70 bg-muted/15 px-2 text-[11px] text-muted-foreground",
|
|
804
818
|
},
|
|
805
|
-
"
|
|
819
|
+
t("panel.config.imagePlaceholder")
|
|
806
820
|
);
|
|
807
821
|
};
|
|
808
822
|
},
|
|
@@ -814,9 +828,14 @@ export const EmptyNodePlaceholder = defineComponent({
|
|
|
814
828
|
element: { type: Object as PropType<PanelElement>, required: true },
|
|
815
829
|
},
|
|
816
830
|
setup(p) {
|
|
831
|
+
const { t } = useI18n();
|
|
817
832
|
return () => {
|
|
818
|
-
const labelMap: Record<string, string> = { video: "
|
|
819
|
-
const label =
|
|
833
|
+
const labelMap: Record<string, string> = { video: t("panel.config.videoPlaceholder"), audio: t("panel.config.audioPlaceholder") };
|
|
834
|
+
const label =
|
|
835
|
+
labelMap[p.element.materialType ?? ""] ??
|
|
836
|
+
t("panel.config.materialPlaceholder", {
|
|
837
|
+
type: p.element.materialType ?? t("common.node"),
|
|
838
|
+
});
|
|
820
839
|
return h(
|
|
821
840
|
"div",
|
|
822
841
|
{
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TranslateFn } from "@arronqzy/i18n";
|
|
1
2
|
import type { PanelElement } from "../types";
|
|
2
3
|
|
|
3
4
|
export type MaterialCategoryId = "charts" | "basic" | "media";
|
|
@@ -13,60 +14,72 @@ export type MaterialCategory = {
|
|
|
13
14
|
items: MaterialItem[];
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
bar: "
|
|
18
|
-
line: "
|
|
19
|
-
pie: "
|
|
20
|
-
area: "
|
|
21
|
-
scatter: "
|
|
22
|
-
radar: "
|
|
23
|
-
gauge: "
|
|
24
|
-
funnel: "
|
|
25
|
-
text: "
|
|
26
|
-
rect: "
|
|
27
|
-
grid: "
|
|
28
|
-
image: "
|
|
29
|
-
video: "
|
|
30
|
-
audio: "
|
|
31
|
-
reference: "
|
|
32
|
-
geometry: "
|
|
17
|
+
const MATERIAL_LABEL_KEYS: Record<string, string> = {
|
|
18
|
+
bar: "panel.material.bar",
|
|
19
|
+
line: "panel.material.line",
|
|
20
|
+
pie: "panel.material.pie",
|
|
21
|
+
area: "panel.material.area",
|
|
22
|
+
scatter: "panel.material.scatter",
|
|
23
|
+
radar: "panel.material.radar",
|
|
24
|
+
gauge: "panel.material.gauge",
|
|
25
|
+
funnel: "panel.material.funnel",
|
|
26
|
+
text: "panel.material.text",
|
|
27
|
+
rect: "panel.material.rect",
|
|
28
|
+
grid: "panel.material.grid",
|
|
29
|
+
image: "panel.material.image",
|
|
30
|
+
video: "panel.material.video",
|
|
31
|
+
audio: "panel.material.audio",
|
|
32
|
+
reference: "panel.material.reference",
|
|
33
|
+
geometry: "panel.material.geometry",
|
|
34
|
+
table: "panel.material.table",
|
|
33
35
|
};
|
|
34
36
|
|
|
35
|
-
export
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
]
|
|
37
|
+
export function getMaterialLabelMap(t: TranslateFn): Record<string, string> {
|
|
38
|
+
const map: Record<string, string> = {};
|
|
39
|
+
for (const [id, key] of Object.entries(MATERIAL_LABEL_KEYS)) {
|
|
40
|
+
map[id] = t(key);
|
|
41
|
+
}
|
|
42
|
+
return map;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getDefaultCategories(t: TranslateFn): MaterialCategory[] {
|
|
46
|
+
return [
|
|
47
|
+
{
|
|
48
|
+
id: "charts",
|
|
49
|
+
title: t("panel.material.charts"),
|
|
50
|
+
items: [
|
|
51
|
+
{ id: "bar", title: t("panel.material.bar") },
|
|
52
|
+
{ id: "line", title: t("panel.material.line") },
|
|
53
|
+
{ id: "pie", title: t("panel.material.pie") },
|
|
54
|
+
{ id: "area", title: t("panel.material.area") },
|
|
55
|
+
{ id: "scatter", title: t("panel.material.scatter") },
|
|
56
|
+
{ id: "radar", title: t("panel.material.radar") },
|
|
57
|
+
{ id: "gauge", title: t("panel.material.gauge") },
|
|
58
|
+
{ id: "funnel", title: t("panel.material.funnel") },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "basic",
|
|
63
|
+
title: t("panel.material.basic"),
|
|
64
|
+
items: [
|
|
65
|
+
{ id: "text", title: t("panel.material.text") },
|
|
66
|
+
{ id: "table", title: t("panel.material.table") },
|
|
67
|
+
{ id: "geometry", title: t("panel.material.geometry") },
|
|
68
|
+
{ id: "grid", title: t("panel.material.grid") },
|
|
69
|
+
{ id: "image", title: t("panel.material.image") },
|
|
70
|
+
{ id: "reference", title: t("panel.material.reference") },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "media",
|
|
75
|
+
title: t("panel.material.media"),
|
|
76
|
+
items: [
|
|
77
|
+
{ id: "video", title: t("panel.material.video") },
|
|
78
|
+
{ id: "audio", title: t("panel.material.audio") },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
}
|
|
70
83
|
|
|
71
84
|
export const themedScrollbarClass =
|
|
72
85
|
"scrollbar-thin [&::-webkit-scrollbar]:h-2 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-muted/40 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/80 [&::-webkit-scrollbar-thumb]:hover:bg-border";
|
|
@@ -79,7 +92,13 @@ export function compareGridTreeChildOrder(a: PanelElement, b: PanelElement): num
|
|
|
79
92
|
return a.id.localeCompare(b.id);
|
|
80
93
|
}
|
|
81
94
|
|
|
82
|
-
export function getNodeDisplayName(node: PanelElement) {
|
|
95
|
+
export function getNodeDisplayName(node: PanelElement, t?: TranslateFn) {
|
|
83
96
|
const customName = node.name?.trim();
|
|
84
|
-
|
|
97
|
+
if (customName) return customName;
|
|
98
|
+
if (node.chart?.title) return node.chart.title;
|
|
99
|
+
if (t) {
|
|
100
|
+
const key = MATERIAL_LABEL_KEYS[node.materialType ?? ""];
|
|
101
|
+
if (key) return t(key);
|
|
102
|
+
}
|
|
103
|
+
return node.materialType || node.id;
|
|
85
104
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/vue";
|
|
2
3
|
import { computed, onWatcherCleanup, shallowRef, toValue, watch, type MaybeRef } from "vue";
|
|
3
4
|
import type { PanelElement } from "../../types";
|
|
4
5
|
import { hasScopeTemplate } from "../../utils/scope-template";
|
|
@@ -8,6 +9,8 @@ import {
|
|
|
8
9
|
} from "../../utils/scope-template-preview";
|
|
9
10
|
import ScopeTemplatePreviewPanel from "./ScopeTemplatePreviewPanel.vue";
|
|
10
11
|
|
|
12
|
+
const { t, locale } = useI18n();
|
|
13
|
+
|
|
11
14
|
const PREVIEW_SLOT_ATTR = "data-scope-preview-slot";
|
|
12
15
|
const PREVIEW_KEY_ATTR = "data-scope-preview-key";
|
|
13
16
|
|
|
@@ -95,8 +98,10 @@ function inferFieldIdFromDom(
|
|
|
95
98
|
): string | undefined {
|
|
96
99
|
const label = input.closest("label");
|
|
97
100
|
const labelText = label?.textContent ?? "";
|
|
98
|
-
if (labelText.includes("
|
|
99
|
-
|
|
101
|
+
if (labelText.includes(t("panel.scope.fieldChartLabelsText")) || labelText.includes("类目") || /labels?/i.test(labelText))
|
|
102
|
+
return "chart.labelsText";
|
|
103
|
+
if (labelText.includes(t("panel.scope.fieldChartValuesText")) || labelText.includes("数值") || /values?/i.test(labelText))
|
|
104
|
+
return "chart.valuesText";
|
|
100
105
|
return undefined;
|
|
101
106
|
}
|
|
102
107
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/vue";
|
|
2
3
|
import { ref } from "vue";
|
|
3
4
|
import {
|
|
4
5
|
formatScopeTemplatePreview,
|
|
5
6
|
resolveScopeTemplatePreview,
|
|
6
7
|
} from "../../utils/scope-template-preview";
|
|
7
8
|
|
|
9
|
+
const { t, locale } = useI18n();
|
|
10
|
+
|
|
8
11
|
const props = defineProps<{
|
|
9
12
|
template: string;
|
|
10
13
|
scope: unknown;
|
|
@@ -18,7 +21,7 @@ const previewValue = () =>
|
|
|
18
21
|
const previewText = () => formatScopeTemplatePreview(previewValue());
|
|
19
22
|
const previewType = () => {
|
|
20
23
|
const value = previewValue();
|
|
21
|
-
return Array.isArray(value) ?
|
|
24
|
+
return Array.isArray(value) ? t("panel.scope.previewArray", { count: value.length }) : typeof value;
|
|
22
25
|
};
|
|
23
26
|
</script>
|
|
24
27
|
|
|
@@ -29,9 +32,9 @@ const previewType = () => {
|
|
|
29
32
|
class="flex w-full items-center justify-between gap-2 px-2 py-1 text-left text-[10px] text-muted-foreground hover:bg-accent/40"
|
|
30
33
|
@click="isExpanded = !isExpanded"
|
|
31
34
|
>
|
|
32
|
-
<span class="font-medium text-foreground/80"
|
|
35
|
+
<span class="font-medium text-foreground/80">{{ t("panel.scope.previewTitle") }}</span>
|
|
33
36
|
<span class="shrink-0 text-[10px]">
|
|
34
|
-
{{ previewType() }} · {{ isExpanded ? "
|
|
37
|
+
{{ previewType() }} · {{ isExpanded ? t("panel.scope.collapsePreview") : t("panel.scope.expandPreview") }}
|
|
35
38
|
</span>
|
|
36
39
|
</button>
|
|
37
40
|
<pre
|
|
@@ -1,20 +1,11 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/vue";
|
|
3
|
+
const { t } = useI18n();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
1
6
|
<template>
|
|
2
7
|
<div class="space-y-2">
|
|
3
|
-
<p>
|
|
4
|
-
|
|
5
|
-
<code class="rounded bg-muted px-1">{`{scope?.a||0}`}</code>
|
|
6
|
-
、
|
|
7
|
-
<code class="rounded bg-muted px-1">{`{scope?.name||''}`}</code>
|
|
8
|
-
。支持 <code class="rounded bg-muted px-1">?.</code> 与
|
|
9
|
-
<code class="rounded bg-muted px-1">||</code> 占位。
|
|
10
|
-
</p>
|
|
11
|
-
<p>
|
|
12
|
-
数组展开模版
|
|
13
|
-
<code class="rounded bg-muted px-1">{`[...{scope?.a?.b}]`}</code>
|
|
14
|
-
、
|
|
15
|
-
<code class="rounded bg-muted px-1">{`[...{scope?.data?.data?.statusName}]`}</code>
|
|
16
|
-
:在文本字段输出逗号分隔字符串;在图表「类目」等数组字段会展开为 n
|
|
17
|
-
个独立项(如 3 条数据对应 3 个类目)。
|
|
18
|
-
</p>
|
|
8
|
+
<p>{{ t("panel.scope.usageHint1") }}</p>
|
|
9
|
+
<p>{{ t("panel.scope.usageHint2") }}</p>
|
|
19
10
|
</div>
|
|
20
11
|
</template>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/vue";
|
|
2
3
|
import { onMounted, ref, watch } from "vue";
|
|
3
4
|
import { useScopeConfig } from "./useScopeConfig";
|
|
4
5
|
|
|
6
|
+
const { t, locale } = useI18n();
|
|
5
7
|
const WARNINGS_COLLAPSE_STORAGE_KEY = "panel:config-scope-warnings-collapsed";
|
|
6
8
|
|
|
7
9
|
const ctx = useScopeConfig();
|
|
@@ -27,7 +29,7 @@ watch(isCollapsed, (next) => {
|
|
|
27
29
|
>
|
|
28
30
|
<div class="flex items-center justify-between gap-2">
|
|
29
31
|
<div class="text-[11px] font-medium text-amber-700 dark:text-amber-300">
|
|
30
|
-
|
|
32
|
+
{{ t("panel.scope.warningsTitle") }}
|
|
31
33
|
<span
|
|
32
34
|
class="ml-1.5 rounded bg-amber-500/15 px-1.5 py-0.5 text-[10px] font-semibold"
|
|
33
35
|
>
|
|
@@ -39,7 +41,7 @@ watch(isCollapsed, (next) => {
|
|
|
39
41
|
class="rounded border border-border px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-accent"
|
|
40
42
|
@click="isCollapsed = !isCollapsed"
|
|
41
43
|
>
|
|
42
|
-
{{ isCollapsed ? "
|
|
44
|
+
{{ isCollapsed ? t("panel.scope.expandWarnings") : t("panel.scope.collapseWarnings") }}
|
|
43
45
|
</button>
|
|
44
46
|
</div>
|
|
45
47
|
<ul
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Condition, ConditionLeaf, ConditionOp } from "@arronqzy/view-table";
|
|
3
|
+
import {
|
|
4
|
+
addConditionItem,
|
|
5
|
+
getConditionLogic,
|
|
6
|
+
isConditionLeaf,
|
|
7
|
+
listConditionItems,
|
|
8
|
+
removeConditionItem,
|
|
9
|
+
setConditionLogic,
|
|
10
|
+
updateConditionLeaf,
|
|
11
|
+
type ConditionLogic,
|
|
12
|
+
} from "@arronqzy/view-table";
|
|
13
|
+
import { useI18n } from "@arronqzy/i18n/vue";
|
|
14
|
+
import { Button, Input, Select } from "ant-design-vue";
|
|
15
|
+
import { computed } from "vue";
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(
|
|
18
|
+
defineProps<{
|
|
19
|
+
modelValue?: Condition;
|
|
20
|
+
showField?: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}>(),
|
|
23
|
+
{ showField: false, disabled: false }
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const emit = defineEmits<{
|
|
27
|
+
"update:modelValue": [value: Condition];
|
|
28
|
+
}>();
|
|
29
|
+
|
|
30
|
+
const { t } = useI18n();
|
|
31
|
+
|
|
32
|
+
const CONDITION_OPS: ConditionOp[] = [
|
|
33
|
+
"eq",
|
|
34
|
+
"neq",
|
|
35
|
+
"gt",
|
|
36
|
+
"gte",
|
|
37
|
+
"lt",
|
|
38
|
+
"lte",
|
|
39
|
+
"contains",
|
|
40
|
+
"startsWith",
|
|
41
|
+
"endsWith",
|
|
42
|
+
"in",
|
|
43
|
+
"empty",
|
|
44
|
+
"notEmpty",
|
|
45
|
+
"regex",
|
|
46
|
+
"truthy",
|
|
47
|
+
"falsy",
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const logic = computed(() => getConditionLogic(props.modelValue));
|
|
51
|
+
const items = computed(() => listConditionItems(props.modelValue));
|
|
52
|
+
|
|
53
|
+
function opLabel(op: ConditionOp) {
|
|
54
|
+
return t(`panel.config.tableOp.${op}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function leafOf(item: Condition): ConditionLeaf {
|
|
58
|
+
return isConditionLeaf(item) ? item : { op: "eq", value: "" };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function leafValueText(value: unknown): string {
|
|
62
|
+
if (value == null) return "";
|
|
63
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
64
|
+
return String(value);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
return JSON.stringify(value);
|
|
68
|
+
} catch {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseLeafValue(text: string): unknown {
|
|
74
|
+
const trimmed = text.trim();
|
|
75
|
+
if (trimmed === "") return "";
|
|
76
|
+
if (trimmed === "true") return true;
|
|
77
|
+
if (trimmed === "false") return false;
|
|
78
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
|
79
|
+
return text;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function setLogic(v: string) {
|
|
83
|
+
emit("update:modelValue", setConditionLogic(props.modelValue, v as ConditionLogic));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function patchLeaf(index: number, patch: Partial<ConditionLeaf>) {
|
|
87
|
+
emit("update:modelValue", updateConditionLeaf(props.modelValue, index, patch));
|
|
88
|
+
}
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<template>
|
|
92
|
+
<div class="space-y-2">
|
|
93
|
+
<div class="flex items-center justify-between gap-2">
|
|
94
|
+
<label class="min-w-0 flex-1 space-y-1">
|
|
95
|
+
<div class="text-[11px] text-gray-500">{{ t("panel.config.tableConditionLogic") }}</div>
|
|
96
|
+
<Select
|
|
97
|
+
size="small"
|
|
98
|
+
class="w-full"
|
|
99
|
+
:value="logic"
|
|
100
|
+
:disabled="disabled"
|
|
101
|
+
@update:value="(v) => setLogic(String(v))"
|
|
102
|
+
>
|
|
103
|
+
<Select.Option value="and">{{ t("panel.config.tableConditionLogicAnd") }}</Select.Option>
|
|
104
|
+
<Select.Option value="or">{{ t("panel.config.tableConditionLogicOr") }}</Select.Option>
|
|
105
|
+
<Select.Option value="not">{{ t("panel.config.tableConditionLogicNot") }}</Select.Option>
|
|
106
|
+
</Select>
|
|
107
|
+
</label>
|
|
108
|
+
<Button
|
|
109
|
+
size="small"
|
|
110
|
+
class="mt-4"
|
|
111
|
+
:disabled="disabled"
|
|
112
|
+
@click="emit('update:modelValue', addConditionItem(modelValue))"
|
|
113
|
+
>
|
|
114
|
+
{{ t("panel.config.tableConditionAddItem") }}
|
|
115
|
+
</Button>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="text-[10px] text-gray-500">{{ t("panel.config.tableConditionLogicHint") }}</div>
|
|
118
|
+
<div
|
|
119
|
+
v-for="(item, index) in items"
|
|
120
|
+
:key="index"
|
|
121
|
+
class="space-y-1.5 rounded-md border border-gray-200 bg-white/70 p-2"
|
|
122
|
+
>
|
|
123
|
+
<div class="flex items-center justify-between gap-2">
|
|
124
|
+
<div class="text-[10px] font-medium text-gray-500">
|
|
125
|
+
{{ t("panel.config.tableConditionItem", { n: index + 1 }) }}
|
|
126
|
+
</div>
|
|
127
|
+
<Button
|
|
128
|
+
size="small"
|
|
129
|
+
danger
|
|
130
|
+
type="text"
|
|
131
|
+
:disabled="disabled || items.length <= 1"
|
|
132
|
+
@click="emit('update:modelValue', removeConditionItem(modelValue, index))"
|
|
133
|
+
>
|
|
134
|
+
×
|
|
135
|
+
</Button>
|
|
136
|
+
</div>
|
|
137
|
+
<div
|
|
138
|
+
class="grid grid-cols-1 gap-1.5"
|
|
139
|
+
:class="showField ? 'sm:grid-cols-3' : 'sm:grid-cols-2'"
|
|
140
|
+
>
|
|
141
|
+
<label class="block space-y-1">
|
|
142
|
+
<div class="text-[11px] text-gray-500">{{ t("panel.config.tableConditionOp") }}</div>
|
|
143
|
+
<Select
|
|
144
|
+
size="small"
|
|
145
|
+
class="w-full"
|
|
146
|
+
:value="leafOf(item).op"
|
|
147
|
+
:disabled="disabled"
|
|
148
|
+
@update:value="(v) => patchLeaf(index, { op: String(v) as ConditionOp })"
|
|
149
|
+
>
|
|
150
|
+
<Select.Option v-for="op in CONDITION_OPS" :key="op" :value="op">
|
|
151
|
+
{{ opLabel(op) }}
|
|
152
|
+
</Select.Option>
|
|
153
|
+
</Select>
|
|
154
|
+
</label>
|
|
155
|
+
<label v-if="showField" class="block space-y-1">
|
|
156
|
+
<div class="text-[11px] text-gray-500">{{ t("panel.config.tableConditionField") }}</div>
|
|
157
|
+
<Input
|
|
158
|
+
size="small"
|
|
159
|
+
:value="leafOf(item).field ?? ''"
|
|
160
|
+
:disabled="disabled"
|
|
161
|
+
:placeholder="t('panel.config.tableConditionFieldPlaceholder')"
|
|
162
|
+
@update:value="(v: string) => patchLeaf(index, { field: v || undefined })"
|
|
163
|
+
/>
|
|
164
|
+
</label>
|
|
165
|
+
<label
|
|
166
|
+
v-if="!['empty', 'notEmpty', 'truthy', 'falsy'].includes(leafOf(item).op)"
|
|
167
|
+
class="block space-y-1"
|
|
168
|
+
>
|
|
169
|
+
<div class="text-[11px] text-gray-500">{{ t("panel.config.tableConditionValue") }}</div>
|
|
170
|
+
<Input
|
|
171
|
+
size="small"
|
|
172
|
+
:value="leafValueText(leafOf(item).value)"
|
|
173
|
+
:disabled="disabled"
|
|
174
|
+
:placeholder="t('panel.config.tableConditionValuePlaceholder')"
|
|
175
|
+
@update:value="(v: string) => patchLeaf(index, { value: parseLeafValue(v) })"
|
|
176
|
+
/>
|
|
177
|
+
</label>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</template>
|