@arronqzy/react-view 1.0.5 → 1.0.7

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 (36) hide show
  1. package/dist/styles.css +519 -9
  2. package/package.json +12 -4
  3. package/src/core/template.tsx +12 -2
  4. package/src/panel/ReactViewOnlinePreview.tsx +18 -8
  5. package/src/panel/ReactViewPanel.tsx +386 -170
  6. package/src/panel/ai/AssistantChatPanel.tsx +895 -0
  7. package/src/panel/ai/AssistantMarkdown.tsx +256 -0
  8. package/src/panel/ai/applyAssistantAction.ts +652 -0
  9. package/src/panel/ai/revealConfigField.ts +137 -0
  10. package/src/panel/components/ConfigHintIcon.tsx +6 -2
  11. package/src/panel/components/ElementsLayer.tsx +37 -18
  12. package/src/panel/components/MaterialSidebar.tsx +157 -109
  13. package/src/panel/components/MoveableLayer.tsx +7 -6
  14. package/src/panel/components/PanelConfigSidebar.tsx +434 -389
  15. package/src/panel/components/ViewElementScopePanel.tsx +9 -5
  16. package/src/panel/components/WorkspaceConfigSidebar.tsx +5 -2
  17. package/src/panel/components/WorkspaceProjectNav.tsx +25 -14
  18. package/src/panel/components/WorkspaceStageSplit.tsx +3 -1
  19. package/src/panel/components/scope-config/ScopeTemplatePreviewHost.tsx +11 -7
  20. package/src/panel/components/scope-config/ScopeTemplateUsageHint.tsx +6 -18
  21. package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.tsx +5 -2
  22. package/src/panel/components/table/ConditionEditor.tsx +205 -0
  23. package/src/panel/components/table/JsonCodeEditor.tsx +115 -0
  24. package/src/panel/components/table/PanelConfigTableSection.tsx +2192 -0
  25. package/src/panel/components/table/TableNodeContent.tsx +544 -0
  26. package/src/panel/constants/messages.ts +76 -34
  27. package/src/panel/hooks/usePanelElements.ts +64 -52
  28. package/src/panel/hooks/useWorkspaceProjects.ts +11 -8
  29. package/src/panel/types.ts +4 -0
  30. package/src/panel/utils/panelElementDefaults.ts +69 -39
  31. package/src/panel/utils/scope-field-labels.ts +51 -38
  32. package/src/panel/utils/scope-template-preview.ts +10 -6
  33. package/src/panel/utils/scope-template-spread.ts +44 -21
  34. package/src/panel/utils/scope-template-warnings.ts +28 -16
  35. package/src/panel/utils/scope-template.ts +82 -2
  36. package/src/tailwind.css +17 -9
@@ -1,44 +1,57 @@
1
- const LABELS: Record<string, string> = {
2
- name: "节点名称",
3
- textHtml: "文本内容",
4
- textFontFamily: "文本字体",
5
- textFontWeight: "文本字重",
6
- textColor: "文本颜色",
7
- "chart.title": "图表标题",
8
- "chart.color": "图表颜色",
9
- "chart.gradientFrom": "渐变起始色",
10
- "chart.gradientTo": "渐变结束色",
11
- "chart.xAxisName": "X 轴名称",
12
- "chart.yAxisName": "Y 轴名称",
13
- "chart.xAxisTickColor": "X 轴刻度颜色",
14
- "chart.yAxisTickColor": "Y 轴刻度颜色",
15
- "chart.xAxisSplitLineColor": "X 轴分割线颜色",
16
- "chart.yAxisSplitLineColor": "Y 轴分割线颜色",
17
- "chart.xAxisLabelColor": "X 轴标签颜色",
18
- "chart.yAxisLabelColor": "Y 轴标签颜色",
19
- "chart.tooltipBackgroundColor": "Tooltip 背景色",
20
- "chart.tooltipTextColor": "Tooltip 文字色",
21
- "chart.tooltipFormatter": "Tooltip 格式化",
22
- "chart.labelsText": "图表类目",
23
- "chart.valuesText": "图表数值",
24
- "style.backgroundColor": "背景色",
25
- "style.backgroundImage": "背景图",
26
- "style.backgroundImageRemoteUrl": "背景图远程地址",
27
- "style.backgroundSize": "背景尺寸",
28
- "style.backgroundPosition": "背景位置",
29
- "style.borderColor": "边框颜色",
30
- audioSrc: "音频地址",
31
- audioRemoteUrl: "音频远程地址",
32
- videoSrc: "视频地址",
33
- videoRemoteUrl: "视频远程地址",
34
- geometryColor: "几何颜色",
35
- geometryScript: "几何脚本",
1
+ import type { TranslateFn } from "@arronqzy/i18n";
2
+
3
+ const FIELD_KEY_MAP: Record<string, string> = {
4
+ name: "panel.scope.fieldName",
5
+ textHtml: "panel.scope.fieldTextHtml",
6
+ textFontFamily: "panel.scope.fieldTextFontFamily",
7
+ textFontWeight: "panel.scope.fieldTextFontWeight",
8
+ textColor: "panel.scope.fieldTextColor",
9
+ "chart.title": "panel.scope.fieldChartTitle",
10
+ "chart.color": "panel.scope.fieldChartColor",
11
+ "chart.gradientFrom": "panel.scope.fieldChartGradientFrom",
12
+ "chart.gradientTo": "panel.scope.fieldChartGradientTo",
13
+ "chart.xAxisName": "panel.scope.fieldChartXAxisName",
14
+ "chart.yAxisName": "panel.scope.fieldChartYAxisName",
15
+ "chart.xAxisTickColor": "panel.scope.fieldChartXAxisTickColor",
16
+ "chart.yAxisTickColor": "panel.scope.fieldChartYAxisTickColor",
17
+ "chart.xAxisSplitLineColor": "panel.scope.fieldChartXAxisSplitLineColor",
18
+ "chart.yAxisSplitLineColor": "panel.scope.fieldChartYAxisSplitLineColor",
19
+ "chart.xAxisLabelColor": "panel.scope.fieldChartXAxisLabelColor",
20
+ "chart.yAxisLabelColor": "panel.scope.fieldChartYAxisLabelColor",
21
+ "chart.tooltipBackgroundColor": "panel.scope.fieldChartTooltipBackgroundColor",
22
+ "chart.tooltipTextColor": "panel.scope.fieldChartTooltipTextColor",
23
+ "chart.tooltipFormatter": "panel.scope.fieldChartTooltipFormatter",
24
+ "chart.labelsText": "panel.scope.fieldChartLabelsText",
25
+ "chart.valuesText": "panel.scope.fieldChartValuesText",
26
+ "style.backgroundColor": "panel.scope.fieldStyleBackgroundColor",
27
+ "style.backgroundImage": "panel.scope.fieldStyleBackgroundImage",
28
+ "style.backgroundImageRemoteUrl": "panel.scope.fieldStyleBackgroundImageRemoteUrl",
29
+ "style.backgroundSize": "panel.scope.fieldStyleBackgroundSize",
30
+ "style.backgroundPosition": "panel.scope.fieldStyleBackgroundPosition",
31
+ "style.borderColor": "panel.scope.fieldStyleBorderColor",
32
+ audioSrc: "panel.scope.fieldAudioSrc",
33
+ audioRemoteUrl: "panel.scope.fieldAudioRemoteUrl",
34
+ videoSrc: "panel.scope.fieldVideoSrc",
35
+ videoRemoteUrl: "panel.scope.fieldVideoRemoteUrl",
36
+ geometryColor: "panel.scope.fieldGeometryColor",
37
+ geometryScript: "panel.scope.fieldGeometryScript",
36
38
  };
37
39
 
38
- export function resolveScopeFieldLabel(fieldId: string): string {
39
- if (LABELS[fieldId]) return LABELS[fieldId];
40
+ export function getScopeFieldLabels(t: TranslateFn): Record<string, string> {
41
+ const labels: Record<string, string> = {};
42
+ for (const [fieldId, key] of Object.entries(FIELD_KEY_MAP)) {
43
+ labels[fieldId] = t(key);
44
+ }
45
+ return labels;
46
+ }
47
+
48
+ export function resolveScopeFieldLabel(fieldId: string, t?: TranslateFn): string {
49
+ if (t) {
50
+ const key = FIELD_KEY_MAP[fieldId];
51
+ if (key) return t(key);
52
+ }
40
53
  const leaf = fieldId.split(".").pop() ?? fieldId;
41
- return LABELS[fieldId] ?? leaf;
54
+ return leaf;
42
55
  }
43
56
 
44
57
  export function scopeFieldDomId(fieldId: string): string {
@@ -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 {
3
5
  materializeChartLabelsText,
@@ -22,7 +24,8 @@ function shouldSkipField(fieldId: string): boolean {
22
24
  function walkElementTemplateFields(
23
25
  value: unknown,
24
26
  fieldId: string,
25
- results: ScopeTemplateField[]
27
+ results: ScopeTemplateField[],
28
+ t: TranslateFn
26
29
  ) {
27
30
  if (shouldSkipField(fieldId)) return;
28
31
 
@@ -30,7 +33,7 @@ function walkElementTemplateFields(
30
33
  if (!hasScopeTemplate(value)) return;
31
34
  results.push({
32
35
  fieldId,
33
- fieldLabel: resolveScopeFieldLabel(fieldId),
36
+ fieldLabel: resolveScopeFieldLabel(fieldId, t),
34
37
  template: value,
35
38
  });
36
39
  return;
@@ -38,7 +41,7 @@ function walkElementTemplateFields(
38
41
 
39
42
  if (Array.isArray(value)) {
40
43
  value.forEach((item, index) => {
41
- walkElementTemplateFields(item, `${fieldId}.${index}`, results);
44
+ walkElementTemplateFields(item, `${fieldId}.${index}`, results, t);
42
45
  });
43
46
  return;
44
47
  }
@@ -46,16 +49,17 @@ function walkElementTemplateFields(
46
49
  if (value && typeof value === "object") {
47
50
  for (const [key, nested] of Object.entries(value)) {
48
51
  const nextId = fieldId ? `${fieldId}.${key}` : key;
49
- walkElementTemplateFields(nested, nextId, results);
52
+ walkElementTemplateFields(nested, nextId, results, t);
50
53
  }
51
54
  }
52
55
  }
53
56
 
54
57
  export function collectElementScopeTemplateFields(
55
- element: PanelElement
58
+ element: PanelElement,
59
+ t: TranslateFn = tForLocale("zh-CN")
56
60
  ): ScopeTemplateField[] {
57
61
  const results: ScopeTemplateField[] = [];
58
- walkElementTemplateFields(element, "", results);
62
+ walkElementTemplateFields(element, "", results, t);
59
63
  return results;
60
64
  }
61
65
 
@@ -1,5 +1,8 @@
1
1
  /** 数组展开模版:[...{scope?.a?.b}] 从 scope 中定位数组并提取每项字段,输出逗号分隔值 */
2
2
 
3
+ import type { TranslateFn } from "@arronqzy/i18n";
4
+ import { tForLocale } from "@arronqzy/i18n";
5
+
3
6
  export const SCOPE_SPREAD_TEMPLATE_RE = /\[\.\.\.\{([^}]+)\}\]/g;
4
7
 
5
8
  export const SCOPE_SPREAD_TEMPLATE_DEMO = {
@@ -8,15 +11,22 @@ export const SCOPE_SPREAD_TEMPLATE_DEMO = {
8
11
  output: "1,2",
9
12
  } as const;
10
13
 
11
- export const SCOPE_SPREAD_DEEP_TEMPLATE_DEMO = {
12
- scope: {
13
- data: {
14
- data: [{ statusName: "进行中" }, { statusName: "已完成" }],
14
+ export function getScopeSpreadDeepTemplateDemo(t: TranslateFn = tForLocale("zh-CN")) {
15
+ const inProgress = t("panel.scope.demoInProgress");
16
+ const done = t("panel.scope.demoDone");
17
+ return {
18
+ scope: {
19
+ data: {
20
+ data: [{ statusName: inProgress }, { statusName: done }],
21
+ },
15
22
  },
16
- },
17
- template: "[...{scope?.data?.data?.statusName}]",
18
- output: "进行中,已完成",
19
- } as const;
23
+ template: "[...{scope?.data?.data?.statusName}]",
24
+ output: t("panel.scope.demoJoined"),
25
+ } as const;
26
+ }
27
+
28
+ /** @deprecated Prefer getScopeSpreadDeepTemplateDemo(t) */
29
+ export const SCOPE_SPREAD_DEEP_TEMPLATE_DEMO = getScopeSpreadDeepTemplateDemo();
20
30
 
21
31
  export type ScopeSpreadEvalIssue =
22
32
  | "invalid-expression"
@@ -205,25 +215,38 @@ export function buildSpreadNotArrayWarningMessage(
205
215
  fieldLabel: string,
206
216
  template: string,
207
217
  expression: string,
208
- arrayPath: string
218
+ arrayPath: string,
219
+ t: TranslateFn = tForLocale("zh-CN")
209
220
  ): string {
221
+ void expression;
210
222
  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
- );
223
+ const deepDemo = getScopeSpreadDeepTemplateDemo(t);
224
+ return t("panel.scope.warnSpreadNotArray", {
225
+ fieldLabel,
226
+ template,
227
+ arrayPath,
228
+ demoScope,
229
+ demoTemplate: SCOPE_SPREAD_TEMPLATE_DEMO.template,
230
+ demoOutput: SCOPE_SPREAD_TEMPLATE_DEMO.output,
231
+ deepTemplate: deepDemo.template,
232
+ deepScope: JSON.stringify(deepDemo.scope),
233
+ deepOutput: deepDemo.output,
234
+ });
217
235
  }
218
236
 
219
237
  export function buildSpreadInvalidExpressionWarningMessage(
220
238
  fieldLabel: string,
221
- template: string
239
+ template: string,
240
+ t: TranslateFn = tForLocale("zh-CN")
222
241
  ): string {
223
242
  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
- );
243
+ const deepDemo = getScopeSpreadDeepTemplateDemo(t);
244
+ return t("panel.scope.warnSpreadInvalid", {
245
+ fieldLabel,
246
+ template,
247
+ demoTemplate: SCOPE_SPREAD_TEMPLATE_DEMO.template,
248
+ deepTemplate: deepDemo.template,
249
+ demoScope,
250
+ demoOutput: SCOPE_SPREAD_TEMPLATE_DEMO.output,
251
+ });
229
252
  }
@@ -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
 
@@ -81,7 +84,8 @@ function analyzeSpreadTemplateValue(
81
84
  kind: "spread-invalid-expression",
82
85
  message: buildSpreadInvalidExpressionWarningMessage(
83
86
  fieldLabel,
84
- spreadTemplate
87
+ spreadTemplate,
88
+ t
85
89
  ),
86
90
  });
87
91
  continue;
@@ -100,7 +104,8 @@ function analyzeSpreadTemplateValue(
100
104
  kind: "spread-invalid-expression",
101
105
  message: buildSpreadInvalidExpressionWarningMessage(
102
106
  fieldLabel,
103
- spreadTemplate
107
+ spreadTemplate,
108
+ t
104
109
  ),
105
110
  });
106
111
  continue;
@@ -118,7 +123,10 @@ function analyzeSpreadTemplateValue(
118
123
  expression,
119
124
  missingPath: arrayPath,
120
125
  kind: "missing-path",
121
- message: `「${fieldLabel}」引用了 scope.${arrayPath},但当前 scope 数据中并没有「${arrayPath}」字段,建议检查一下。`,
126
+ message: t("panel.scope.warnMissingPath", {
127
+ fieldLabel,
128
+ path: arrayPath,
129
+ }),
122
130
  });
123
131
  }
124
132
  continue;
@@ -136,7 +144,8 @@ function analyzeSpreadTemplateValue(
136
144
  fieldLabel,
137
145
  spreadTemplate,
138
146
  expression,
139
- arrayPath
147
+ arrayPath,
148
+ t
140
149
  ),
141
150
  });
142
151
  }
@@ -148,14 +157,15 @@ function analyzeSpreadTemplateValue(
148
157
  function analyzeTemplateValue(
149
158
  fieldId: string,
150
159
  value: string,
151
- scope: unknown
160
+ scope: unknown,
161
+ t: TranslateFn
152
162
  ): ScopeTemplateWarning[] {
153
163
  if (!hasScopeTemplate(value)) return [];
154
164
 
155
165
  const warnings: ScopeTemplateWarning[] = [
156
- ...analyzeSpreadTemplateValue(fieldId, value, scope),
166
+ ...analyzeSpreadTemplateValue(fieldId, value, scope, t),
157
167
  ];
158
- const fieldLabel = resolveScopeFieldLabel(fieldId);
168
+ const fieldLabel = resolveScopeFieldLabel(fieldId, t);
159
169
  const expressionRe = /\{([^}]+)\}/g;
160
170
  let match: RegExpExecArray | null;
161
171
 
@@ -172,7 +182,7 @@ function analyzeTemplateValue(
172
182
  expression,
173
183
  missingPath: path,
174
184
  kind: "missing-path",
175
- message: `「${fieldLabel}」引用了 scope.${path},但当前 scope 数据中并没有「${path}」字段,建议检查一下。`,
185
+ message: t("panel.scope.warnMissingPath", { fieldLabel, path }),
176
186
  });
177
187
  }
178
188
  }
@@ -188,18 +198,19 @@ function walkElementStrings(
188
198
  value: unknown,
189
199
  fieldId: string,
190
200
  scope: unknown,
191
- warnings: ScopeTemplateWarning[]
201
+ warnings: ScopeTemplateWarning[],
202
+ t: TranslateFn
192
203
  ) {
193
204
  if (shouldSkipField(fieldId)) return;
194
205
 
195
206
  if (typeof value === "string") {
196
- warnings.push(...analyzeTemplateValue(fieldId, value, scope));
207
+ warnings.push(...analyzeTemplateValue(fieldId, value, scope, t));
197
208
  return;
198
209
  }
199
210
 
200
211
  if (Array.isArray(value)) {
201
212
  value.forEach((item, index) => {
202
- walkElementStrings(item, `${fieldId}.${index}`, scope, warnings);
213
+ walkElementStrings(item, `${fieldId}.${index}`, scope, warnings, t);
203
214
  });
204
215
  return;
205
216
  }
@@ -207,19 +218,20 @@ function walkElementStrings(
207
218
  if (value && typeof value === "object") {
208
219
  for (const [key, nested] of Object.entries(value)) {
209
220
  const nextId = fieldId ? `${fieldId}.${key}` : key;
210
- walkElementStrings(nested, nextId, scope, warnings);
221
+ walkElementStrings(nested, nextId, scope, warnings, t);
211
222
  }
212
223
  }
213
224
  }
214
225
 
215
226
  export function collectElementScopeWarnings(
216
227
  element: PanelElement,
217
- scope: unknown
228
+ scope: unknown,
229
+ t: TranslateFn = tForLocale("zh-CN")
218
230
  ): ScopeTemplateWarning[] {
219
231
  if (scope === undefined) return [];
220
232
 
221
233
  const warnings: ScopeTemplateWarning[] = [];
222
- walkElementStrings(element, "", scope, warnings);
234
+ walkElementStrings(element, "", scope, warnings, t);
223
235
 
224
236
  const seen = new Set<string>();
225
237
  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 = /\{[^}]+\}|\[\.\.\.\{[^}]+\}\]/;
@@ -33,13 +39,13 @@ export function evaluateScopeTemplate(
33
39
 
34
40
  const withSpread = template.replace(
35
41
  SCOPE_SPREAD_TEMPLATE_RE,
36
- (_, rawExpr: string) => {
42
+ (_: string, rawExpr: string) => {
37
43
  const result = evaluateSpreadScopeExpression(rawExpr, scope);
38
44
  return result.ok ? result.value : "";
39
45
  }
40
46
  );
41
47
 
42
- return withSpread.replace(/\{([^}]+)\}/g, (_, rawExpr: string) => {
48
+ return withSpread.replace(/\{([^}]+)\}/g, (_: string, rawExpr: string) => {
43
49
  const value = evaluateScopeExpression(rawExpr, scope);
44
50
  if (value === null || value === undefined) return "";
45
51
  if (typeof value === "string") return value;
@@ -54,6 +60,65 @@ 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
+ export function materializeTableSourceFromScope(
103
+ table: PanelTableConfig | undefined,
104
+ scope: unknown
105
+ ): unknown | undefined {
106
+ if (!table) return undefined;
107
+
108
+ if (typeof table.source === "string" && table.source.trim()) {
109
+ return materializeTemplateValue(table.source, scope);
110
+ }
111
+
112
+ if (typeof table.rowsText === "string" && table.rowsText.trim()) {
113
+ const trimmed = table.rowsText.trim();
114
+ if (hasScopeTemplate(trimmed) || isSpreadOnlyScopeTemplate(trimmed)) {
115
+ return materializeTemplateValue(trimmed, scope);
116
+ }
117
+ }
118
+
119
+ return undefined;
120
+ }
121
+
57
122
  function resolveScopedValue<T>(value: T, scope: unknown): T {
58
123
  if (typeof value === "string" && hasScopeTemplate(value)) {
59
124
  return evaluateScopeTemplate(value, scope) as T;
@@ -84,6 +149,21 @@ export function resolvePanelElementScope(
84
149
  delete resolved.chart.labelsText;
85
150
  }
86
151
  }
152
+ if (resolved.table) {
153
+ const hasDynamicSource =
154
+ typeof element.table?.source === "string" && element.table.source.trim().length > 0;
155
+ if (hasDynamicSource) {
156
+ const raw = materializeTableSourceFromScope(element.table, scope);
157
+ if (isUsableTableRawData(raw)) {
158
+ resolved.table = { ...resolved.table, rows: raw };
159
+ } else {
160
+ // Dynamic missing/empty → fall back to static rowsText (do not keep empty rows)
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
@@ -26,17 +26,24 @@
26
26
  background: hsl(var(--foreground) / 0.45);
27
27
  }
28
28
 
29
- /* Moveable:仅角点控制柄固定屏幕像素;勿作用于 moveable-direction 边线 */
29
+ /*
30
+ * Moveable 控制框:
31
+ * - 根节点必须高于画布元素 zIndex(节点默认 ≥1),否则多选组框线会被节点盖住,只剩锚点可见
32
+ * - 勿把 .moveable-group 压到 z-index:1(会覆盖根节点的 9999)
33
+ */
30
34
  .rv-moveable-layer {
31
35
  --moveable-color: #1990ff;
32
36
  z-index: 9999 !important;
33
37
  }
34
38
 
35
39
  .rv-moveable-layer.moveable-group,
36
- .rv-moveable-layer .moveable-group,
37
- .rv-moveable-layer.moveable-control-box,
38
- .rv-moveable-layer .moveable-control-box {
39
- z-index: 1 !important;
40
+ .rv-moveable-layer.moveable-control-box {
41
+ z-index: 9999 !important;
42
+ }
43
+
44
+ /* Moveable 默认 .group { z-index: -1 },必须覆盖,避免整组掉到节点下方 */
45
+ .rv-moveable-layer.moveable-group {
46
+ z-index: 9999 !important;
40
47
  }
41
48
 
42
49
  .rv-moveable-layer .moveable-line {
@@ -44,13 +51,13 @@
44
51
  background: var(--moveable-color) !important;
45
52
  }
46
53
 
47
- /* 组选/单边控制线:厚度由 moveable zoom 补偿,保持屏幕 1px */
48
- .rv-moveable-layer .moveable-line.moveable-direction {
54
+ /* 组选/边线:保持颜色;厚度仍由 moveable zoom 的 scaleY 补偿 */
55
+ .rv-moveable-layer .moveable-line.moveable-direction:not(.moveable-edge) {
49
56
  background: var(--moveable-color) !important;
50
57
  }
51
58
 
52
- .rv-moveable-layer .moveable-group .moveable-control-box,
53
- .rv-moveable-layer.moveable-group .moveable-control-box {
59
+ /* 子 control-box 相对组内层叠,勿压低根节点 */
60
+ .rv-moveable-layer.moveable-group > .moveable-control-box {
54
61
  z-index: 2 !important;
55
62
  }
56
63
 
@@ -68,4 +75,5 @@
68
75
  border: 2px solid hsl(var(--background)) !important;
69
76
  background: #1990ff !important;
70
77
  background: var(--moveable-color) !important;
78
+ z-index: 10 !important;
71
79
  }