@arronqzy/vue-view 0.1.0 → 0.1.2

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.
Files changed (48) hide show
  1. package/package.json +9 -6
  2. package/src/panel/VueViewOnlinePreview.vue +15 -7
  3. package/src/panel/VueViewPanel.vue +81 -55
  4. package/src/panel/components/ConfigHintIcon.vue +4 -2
  5. package/src/panel/components/ElementsLayer.vue +15 -0
  6. package/src/panel/components/MaterialPreview.vue +19 -1
  7. package/src/panel/components/MaterialSidebar.vue +35 -31
  8. package/src/panel/components/MaterialSidebarTreeNode.vue +27 -26
  9. package/src/panel/components/MoveableLayer.vue +7 -4
  10. package/src/panel/components/PanelConfigSidebar.vue +57 -39
  11. package/src/panel/components/ViewElementScopePanel.vue +6 -4
  12. package/src/panel/components/WorkspaceConfigSidebar.vue +7 -1
  13. package/src/panel/components/WorkspaceProjectNav.vue +23 -16
  14. package/src/panel/components/WorkspaceStageSplit.vue +10 -7
  15. package/src/panel/components/config/ConfigColorField.vue +3 -1
  16. package/src/panel/components/config/PanelConfigAudioSection.vue +44 -43
  17. package/src/panel/components/config/PanelConfigChartSection.vue +79 -76
  18. package/src/panel/components/config/PanelConfigGeometrySection.vue +21 -19
  19. package/src/panel/components/config/PanelConfigGridChildSpan.vue +7 -5
  20. package/src/panel/components/config/PanelConfigGridSection.vue +10 -8
  21. package/src/panel/components/config/PanelConfigImageSection.vue +22 -21
  22. package/src/panel/components/config/PanelConfigMultiSelect.vue +42 -40
  23. package/src/panel/components/config/PanelConfigNodeInfo.vue +19 -16
  24. package/src/panel/components/config/PanelConfigReferenceSection.vue +11 -9
  25. package/src/panel/components/config/PanelConfigStyleSections.vue +27 -26
  26. package/src/panel/components/config/PanelConfigTextSection.vue +20 -17
  27. package/src/panel/components/config/PanelConfigVideoSection.vue +15 -14
  28. package/src/panel/components/config/shared.ts +0 -3
  29. package/src/panel/components/elementsLayerNodes.ts +37 -18
  30. package/src/panel/components/materialSidebarData.ts +73 -54
  31. package/src/panel/components/scope-config/ScopeTemplatePreviewHost.vue +7 -2
  32. package/src/panel/components/scope-config/ScopeTemplatePreviewPanel.vue +6 -3
  33. package/src/panel/components/scope-config/ScopeTemplateUsageHint.vue +7 -16
  34. package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.vue +4 -2
  35. package/src/panel/components/table/ConditionEditor.vue +181 -0
  36. package/src/panel/components/table/JsonCodeEditor.vue +148 -0
  37. package/src/panel/components/table/PanelConfigTableSection.vue +2033 -0
  38. package/src/panel/components/table/TableNodeContent.vue +557 -0
  39. package/src/panel/constants/messages.ts +76 -34
  40. package/src/panel/hooks/usePanelElements.ts +54 -41
  41. package/src/panel/hooks/useWorkspaceProjects.ts +8 -5
  42. package/src/panel/types.ts +4 -0
  43. package/src/panel/utils/panelElementDefaults.ts +73 -39
  44. package/src/panel/utils/scope-field-labels.ts +53 -39
  45. package/src/panel/utils/scope-template-spread.ts +57 -24
  46. package/src/panel/utils/scope-template-warnings.ts +23 -22
  47. package/src/panel/utils/scope-template.ts +80 -0
  48. package/src/tailwind.css +18 -10
@@ -1,22 +1,41 @@
1
+ import type { TranslateFn } from "@arronqzy/i18n";
2
+ import { tForLocale } from "@arronqzy/i18n";
3
+
1
4
  /** 数组展开模版:[...{scope?.a?.b}] 从 scope 中定位数组并提取每项字段,输出逗号分隔值 */
2
5
 
3
6
  export const SCOPE_SPREAD_TEMPLATE_RE = /\[\.\.\.\{([^}]+)\}\]/g;
4
7
 
8
+ export function getScopeSpreadTemplateDemo(t: TranslateFn = tForLocale("zh-CN")) {
9
+ return {
10
+ scope: { a: [{ b: 1 }, { b: 2 }] },
11
+ template: "[...{scope?.a?.b}]",
12
+ output: "1,2",
13
+ } as const;
14
+ }
15
+
16
+ export function getScopeSpreadDeepTemplateDemo(t: TranslateFn = tForLocale("zh-CN")) {
17
+ const inProgress = t("panel.scope.demoInProgress");
18
+ const done = t("panel.scope.demoDone");
19
+ return {
20
+ scope: {
21
+ data: {
22
+ data: [{ statusName: inProgress }, { statusName: done }],
23
+ },
24
+ },
25
+ template: "[...{scope?.data?.data?.statusName}]",
26
+ output: t("panel.scope.demoJoined"),
27
+ } as const;
28
+ }
29
+
30
+ /** @deprecated Prefer getScopeSpreadTemplateDemo(t) */
5
31
  export const SCOPE_SPREAD_TEMPLATE_DEMO = {
6
32
  scope: { a: [{ b: 1 }, { b: 2 }] },
7
33
  template: "[...{scope?.a?.b}]",
8
34
  output: "1,2",
9
35
  } as const;
10
36
 
11
- export const SCOPE_SPREAD_DEEP_TEMPLATE_DEMO = {
12
- scope: {
13
- data: {
14
- data: [{ statusName: "进行中" }, { statusName: "已完成" }],
15
- },
16
- },
17
- template: "[...{scope?.data?.data?.statusName}]",
18
- output: "进行中,已完成",
19
- } as const;
37
+ /** @deprecated Prefer getScopeSpreadDeepTemplateDemo(t) */
38
+ export const SCOPE_SPREAD_DEEP_TEMPLATE_DEMO = getScopeSpreadDeepTemplateDemo();
20
39
 
21
40
  export type ScopeSpreadEvalIssue =
22
41
  | "invalid-expression"
@@ -205,25 +224,39 @@ export function buildSpreadNotArrayWarningMessage(
205
224
  fieldLabel: string,
206
225
  template: string,
207
226
  expression: string,
208
- arrayPath: string
227
+ arrayPath: string,
228
+ t: TranslateFn = tForLocale("zh-CN")
209
229
  ): string {
210
- const demoScope = JSON.stringify(SCOPE_SPREAD_TEMPLATE_DEMO.scope);
211
- const deepDemo = SCOPE_SPREAD_DEEP_TEMPLATE_DEMO;
212
- return (
213
- `「${fieldLabel}」中的 ${template} 模版仅可做数组值的映射,当前 scope.${arrayPath} 不是数组,数据不符合规则。` +
214
- `参考示例:scope 为 ${demoScope} 时使用 ${SCOPE_SPREAD_TEMPLATE_DEMO.template} 输出 ${SCOPE_SPREAD_TEMPLATE_DEMO.output};` +
215
- `深层路径可用 ${deepDemo.template}(scope 为 ${JSON.stringify(deepDemo.scope)} 时输出 ${deepDemo.output})。`
216
- );
230
+ const demo = getScopeSpreadTemplateDemo(t);
231
+ const deepDemo = getScopeSpreadDeepTemplateDemo(t);
232
+ const demoScope = JSON.stringify(demo.scope);
233
+ return t("panel.scope.warnSpreadNotArray", {
234
+ fieldLabel,
235
+ template,
236
+ arrayPath,
237
+ demoScope,
238
+ demoTemplate: demo.template,
239
+ demoOutput: demo.output,
240
+ deepTemplate: deepDemo.template,
241
+ deepScope: JSON.stringify(deepDemo.scope),
242
+ deepOutput: deepDemo.output,
243
+ });
217
244
  }
218
245
 
219
246
  export function buildSpreadInvalidExpressionWarningMessage(
220
247
  fieldLabel: string,
221
- template: string
248
+ template: string,
249
+ t: TranslateFn = tForLocale("zh-CN")
222
250
  ): string {
223
- const demoScope = JSON.stringify(SCOPE_SPREAD_TEMPLATE_DEMO.scope);
224
- const deepDemo = SCOPE_SPREAD_DEEP_TEMPLATE_DEMO;
225
- return (
226
- `「${fieldLabel}」中的 ${template} 写法无效,请使用 ${SCOPE_SPREAD_TEMPLATE_DEMO.template} 或 ${deepDemo.template} 形式。` +
227
- `参考示例:scope 为 ${demoScope} 时输出 ${SCOPE_SPREAD_TEMPLATE_DEMO.output}。`
228
- );
251
+ const demo = getScopeSpreadTemplateDemo(t);
252
+ const deepDemo = getScopeSpreadDeepTemplateDemo(t);
253
+ const demoScope = JSON.stringify(demo.scope);
254
+ return t("panel.scope.warnSpreadInvalid", {
255
+ fieldLabel,
256
+ template,
257
+ demoTemplate: demo.template,
258
+ deepTemplate: deepDemo.template,
259
+ demoScope,
260
+ demoOutput: demo.output,
261
+ });
229
262
  }
@@ -1,3 +1,5 @@
1
+ import type { TranslateFn } from "@arronqzy/i18n";
2
+ import { tForLocale } from "@arronqzy/i18n";
1
3
  import type { PanelElement } from "../types";
2
4
  import { hasScopeTemplate } from "./scope-template";
3
5
  import { resolveScopeFieldLabel } from "./scope-field-labels";
@@ -59,10 +61,11 @@ function pathExistsInScope(scope: unknown, path: string): boolean {
59
61
  function analyzeSpreadTemplateValue(
60
62
  fieldId: string,
61
63
  value: string,
62
- scope: unknown
64
+ scope: unknown,
65
+ t: TranslateFn
63
66
  ): ScopeTemplateWarning[] {
64
67
  const warnings: ScopeTemplateWarning[] = [];
65
- const fieldLabel = resolveScopeFieldLabel(fieldId);
68
+ const fieldLabel = resolveScopeFieldLabel(fieldId, t);
66
69
  const spreadRe = new RegExp(SCOPE_SPREAD_TEMPLATE_RE.source, "g");
67
70
  let match: RegExpExecArray | null;
68
71
 
@@ -79,10 +82,7 @@ function analyzeSpreadTemplateValue(
79
82
  expression,
80
83
  missingPath: "",
81
84
  kind: "spread-invalid-expression",
82
- message: buildSpreadInvalidExpressionWarningMessage(
83
- fieldLabel,
84
- spreadTemplate
85
- ),
85
+ message: buildSpreadInvalidExpressionWarningMessage(fieldLabel, spreadTemplate, t),
86
86
  });
87
87
  continue;
88
88
  }
@@ -98,10 +98,7 @@ function analyzeSpreadTemplateValue(
98
98
  expression,
99
99
  missingPath: "",
100
100
  kind: "spread-invalid-expression",
101
- message: buildSpreadInvalidExpressionWarningMessage(
102
- fieldLabel,
103
- spreadTemplate
104
- ),
101
+ message: buildSpreadInvalidExpressionWarningMessage(fieldLabel, spreadTemplate, t),
105
102
  });
106
103
  continue;
107
104
  }
@@ -118,7 +115,7 @@ function analyzeSpreadTemplateValue(
118
115
  expression,
119
116
  missingPath: arrayPath,
120
117
  kind: "missing-path",
121
- message: `「${fieldLabel}」引用了 scope.${arrayPath},但当前 scope 数据中并没有「${arrayPath}」字段,建议检查一下。`,
118
+ message: t("panel.scope.warnMissingPath", { fieldLabel, path: arrayPath }),
122
119
  });
123
120
  }
124
121
  continue;
@@ -136,7 +133,8 @@ function analyzeSpreadTemplateValue(
136
133
  fieldLabel,
137
134
  spreadTemplate,
138
135
  expression,
139
- arrayPath
136
+ arrayPath,
137
+ t
140
138
  ),
141
139
  });
142
140
  }
@@ -148,14 +146,15 @@ function analyzeSpreadTemplateValue(
148
146
  function analyzeTemplateValue(
149
147
  fieldId: string,
150
148
  value: string,
151
- scope: unknown
149
+ scope: unknown,
150
+ t: TranslateFn
152
151
  ): ScopeTemplateWarning[] {
153
152
  if (!hasScopeTemplate(value)) return [];
154
153
 
155
154
  const warnings: ScopeTemplateWarning[] = [
156
- ...analyzeSpreadTemplateValue(fieldId, value, scope),
155
+ ...analyzeSpreadTemplateValue(fieldId, value, scope, t),
157
156
  ];
158
- const fieldLabel = resolveScopeFieldLabel(fieldId);
157
+ const fieldLabel = resolveScopeFieldLabel(fieldId, t);
159
158
  const expressionRe = /\{([^}]+)\}/g;
160
159
  let match: RegExpExecArray | null;
161
160
 
@@ -172,7 +171,7 @@ function analyzeTemplateValue(
172
171
  expression,
173
172
  missingPath: path,
174
173
  kind: "missing-path",
175
- message: `「${fieldLabel}」引用了 scope.${path},但当前 scope 数据中并没有「${path}」字段,建议检查一下。`,
174
+ message: t("panel.scope.warnMissingPath", { fieldLabel, path }),
176
175
  });
177
176
  }
178
177
  }
@@ -188,18 +187,19 @@ function walkElementStrings(
188
187
  value: unknown,
189
188
  fieldId: string,
190
189
  scope: unknown,
191
- warnings: ScopeTemplateWarning[]
190
+ warnings: ScopeTemplateWarning[],
191
+ t: TranslateFn
192
192
  ) {
193
193
  if (shouldSkipField(fieldId)) return;
194
194
 
195
195
  if (typeof value === "string") {
196
- warnings.push(...analyzeTemplateValue(fieldId, value, scope));
196
+ warnings.push(...analyzeTemplateValue(fieldId, value, scope, t));
197
197
  return;
198
198
  }
199
199
 
200
200
  if (Array.isArray(value)) {
201
201
  value.forEach((item, index) => {
202
- walkElementStrings(item, `${fieldId}.${index}`, scope, warnings);
202
+ walkElementStrings(item, `${fieldId}.${index}`, scope, warnings, t);
203
203
  });
204
204
  return;
205
205
  }
@@ -207,19 +207,20 @@ function walkElementStrings(
207
207
  if (value && typeof value === "object") {
208
208
  for (const [key, nested] of Object.entries(value)) {
209
209
  const nextId = fieldId ? `${fieldId}.${key}` : key;
210
- walkElementStrings(nested, nextId, scope, warnings);
210
+ walkElementStrings(nested, nextId, scope, warnings, t);
211
211
  }
212
212
  }
213
213
  }
214
214
 
215
215
  export function collectElementScopeWarnings(
216
216
  element: PanelElement,
217
- scope: unknown
217
+ scope: unknown,
218
+ t: TranslateFn = tForLocale("zh-CN")
218
219
  ): ScopeTemplateWarning[] {
219
220
  if (scope === undefined) return [];
220
221
 
221
222
  const warnings: ScopeTemplateWarning[] = [];
222
- walkElementStrings(element, "", scope, warnings);
223
+ walkElementStrings(element, "", scope, warnings, t);
223
224
 
224
225
  const seen = new Set<string>();
225
226
  return warnings.filter((warning) => {
@@ -1,8 +1,14 @@
1
1
  import type { PanelElement } from "../types";
2
+ import type { PanelTableConfig } from "@arronqzy/view-table";
3
+ import { isUsableTableRawData } from "@arronqzy/view-table";
2
4
  import { materializeChartLabelsFromScope } from "./scope-template-chart";
3
5
  import {
4
6
  SCOPE_SPREAD_TEMPLATE_RE,
5
7
  evaluateSpreadScopeExpression,
8
+ extractSpreadScopeExpression,
9
+ getValueAtScopePath,
10
+ isSpreadOnlyScopeTemplate,
11
+ resolveSpreadScopePath,
6
12
  } from "./scope-template-spread";
7
13
 
8
14
  const SCOPE_TEMPLATE_RE = /\{[^}]+\}|\[\.\.\.\{[^}]+\}\]/;
@@ -54,6 +60,66 @@ export function evaluateScopeTemplate(
54
60
  });
55
61
  }
56
62
 
63
+ function tryParseJson(text: string): unknown | undefined {
64
+ try {
65
+ return JSON.parse(text);
66
+ } catch {
67
+ return undefined;
68
+ }
69
+ }
70
+
71
+ function materializeTemplateValue(template: string, scope: unknown): unknown {
72
+ const trimmed = template.trim();
73
+ if (!trimmed) return undefined;
74
+
75
+ if (isSpreadOnlyScopeTemplate(trimmed)) {
76
+ const expression = extractSpreadScopeExpression(trimmed);
77
+ if (!expression) return undefined;
78
+ const resolved = resolveSpreadScopePath(expression, scope);
79
+ if (!resolved.ok) return undefined;
80
+ if (resolved.itemPathSegments.length === 0) {
81
+ return resolved.arrayValue;
82
+ }
83
+ return resolved.arrayValue.map((item) =>
84
+ getValueAtScopePath(item, resolved.itemPathSegments)
85
+ );
86
+ }
87
+
88
+ const singleMatch = /^\{([^}]+)\}$/.exec(trimmed);
89
+ if (singleMatch) {
90
+ return evaluateScopeExpression(singleMatch[1]!, scope);
91
+ }
92
+
93
+ if (hasScopeTemplate(trimmed)) {
94
+ const text = evaluateScopeTemplate(trimmed, scope);
95
+ const parsed = tryParseJson(text);
96
+ return parsed !== undefined ? parsed : text;
97
+ }
98
+
99
+ return tryParseJson(trimmed);
100
+ }
101
+
102
+ /** 将 table.source / rowsText 模版解析为原始行数据(供 transform 使用) */
103
+ export function materializeTableSourceFromScope(
104
+ table: PanelTableConfig | undefined,
105
+ scope: unknown
106
+ ): unknown | undefined {
107
+ if (!table) return undefined;
108
+
109
+ if (typeof table.source === "string" && table.source.trim()) {
110
+ return materializeTemplateValue(table.source, scope);
111
+ }
112
+
113
+ if (typeof table.rowsText === "string" && table.rowsText.trim()) {
114
+ const trimmed = table.rowsText.trim();
115
+ if (hasScopeTemplate(trimmed) || isSpreadOnlyScopeTemplate(trimmed)) {
116
+ return materializeTemplateValue(trimmed, scope);
117
+ }
118
+ }
119
+
120
+ return undefined;
121
+ }
122
+
57
123
  function resolveScopedValue<T>(value: T, scope: unknown): T {
58
124
  if (typeof value === "string" && hasScopeTemplate(value)) {
59
125
  return evaluateScopeTemplate(value, scope) as T;
@@ -84,6 +150,20 @@ export function resolvePanelElementScope(
84
150
  delete resolved.chart.labelsText;
85
151
  }
86
152
  }
153
+ if (resolved.table) {
154
+ const hasDynamicSource =
155
+ typeof element.table?.source === "string" && element.table.source.trim().length > 0;
156
+ if (hasDynamicSource) {
157
+ const raw = materializeTableSourceFromScope(element.table, scope);
158
+ if (isUsableTableRawData(raw)) {
159
+ resolved.table = { ...resolved.table, rows: raw };
160
+ } else {
161
+ const next = { ...resolved.table };
162
+ delete next.rows;
163
+ resolved.table = next;
164
+ }
165
+ }
166
+ }
87
167
  return resolved;
88
168
  }
89
169
 
package/src/tailwind.css CHANGED
@@ -1,14 +1,21 @@
1
- /* Moveable:仅角点控制柄固定屏幕像素;勿作用于 moveable-direction 边线 */
1
+ /*
2
+ * Moveable 控制框:
3
+ * - 根节点必须高于画布元素 zIndex(节点默认 ≥1),否则多选组框线会被节点盖住,只剩锚点可见
4
+ * - 勿把 .moveable-group 压到 z-index:1(会覆盖根节点的 9999)
5
+ */
2
6
  .rv-moveable-layer {
3
7
  --moveable-color: #1990ff;
4
8
  z-index: 9999 !important;
5
9
  }
6
10
 
7
11
  .rv-moveable-layer.moveable-group,
8
- .rv-moveable-layer .moveable-group,
9
- .rv-moveable-layer.moveable-control-box,
10
- .rv-moveable-layer .moveable-control-box {
11
- z-index: 1 !important;
12
+ .rv-moveable-layer.moveable-control-box {
13
+ z-index: 9999 !important;
14
+ }
15
+
16
+ /* Moveable 默认 .group { z-index: -1 },必须覆盖,避免整组掉到节点下方 */
17
+ .rv-moveable-layer.moveable-group {
18
+ z-index: 9999 !important;
12
19
  }
13
20
 
14
21
  .rv-moveable-layer .moveable-line {
@@ -16,13 +23,13 @@
16
23
  background: var(--moveable-color) !important;
17
24
  }
18
25
 
19
- /* 组选/单边控制线:厚度由 moveable zoom 补偿,保持屏幕 1px */
20
- .rv-moveable-layer .moveable-line.moveable-direction {
26
+ /* 组选/边线:保持颜色;厚度仍由 moveable zoom 的 scaleY 补偿 */
27
+ .rv-moveable-layer .moveable-line.moveable-direction:not(.moveable-edge) {
21
28
  background: var(--moveable-color) !important;
22
29
  }
23
30
 
24
- .rv-moveable-layer .moveable-group .moveable-control-box,
25
- .rv-moveable-layer.moveable-group .moveable-control-box {
31
+ /* 子 control-box 相对组内层叠,勿压低根节点 */
32
+ .rv-moveable-layer.moveable-group > .moveable-control-box {
26
33
  z-index: 2 !important;
27
34
  }
28
35
 
@@ -37,7 +44,8 @@
37
44
  margin-left: -7px !important;
38
45
  border-radius: 50% !important;
39
46
  box-sizing: border-box !important;
40
- border: 2px solid hsl(var(--background)) !important;
47
+ border: 2px solid #fff !important;
41
48
  background: #1990ff !important;
42
49
  background: var(--moveable-color) !important;
50
+ z-index: 10 !important;
43
51
  }