@arronqzy/react-view 0.0.3 → 1.0.5
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/dist/styles.css +2654 -0
- package/package.json +9 -9
- package/readme.md +74 -5
- package/src/panel/ReactViewPanel.tsx +17 -7
- package/src/panel/components/ConfigHintIcon.tsx +46 -0
- package/src/panel/components/ElementsLayer.tsx +2 -4
- package/src/panel/components/MoveableLayer.tsx +68 -55
- package/src/panel/components/PanelCanvas.tsx +81 -64
- package/src/panel/components/PanelConfigSidebar.tsx +55 -77
- package/src/panel/components/SelectLayer.tsx +1 -0
- package/src/panel/components/ViewElementScopePanel.tsx +5 -8
- package/src/panel/components/scope-config/ScopeConfigContext.tsx +9 -0
- package/src/panel/components/scope-config/ScopeTemplatePreviewHost.tsx +238 -0
- package/src/panel/components/scope-config/ScopeTemplateUsageHint.tsx +24 -0
- package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.tsx +1 -1
- package/src/panel/hooks/useRafThrottledScroll.ts +30 -0
- package/src/panel/types.ts +2 -0
- package/src/panel/utils/chartOptionBuilder.ts +12 -1
- package/src/panel/utils/scope-field-labels.ts +1 -0
- package/src/panel/utils/scope-template-chart.ts +92 -0
- package/src/panel/utils/scope-template-preview.ts +124 -0
- package/src/panel/utils/scope-template-spread.ts +229 -0
- package/src/panel/utils/scope-template-warnings.ts +110 -2
- package/src/panel/utils/scope-template.ts +25 -3
- package/src/tailwind.css +27 -14
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/** 数组展开模版:[...{scope?.a?.b}] 从 scope 中定位数组并提取每项字段,输出逗号分隔值 */
|
|
2
|
+
|
|
3
|
+
export const SCOPE_SPREAD_TEMPLATE_RE = /\[\.\.\.\{([^}]+)\}\]/g;
|
|
4
|
+
|
|
5
|
+
export const SCOPE_SPREAD_TEMPLATE_DEMO = {
|
|
6
|
+
scope: { a: [{ b: 1 }, { b: 2 }] },
|
|
7
|
+
template: "[...{scope?.a?.b}]",
|
|
8
|
+
output: "1,2",
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
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;
|
|
20
|
+
|
|
21
|
+
export type ScopeSpreadEvalIssue =
|
|
22
|
+
| "invalid-expression"
|
|
23
|
+
| "path-missing"
|
|
24
|
+
| "not-array";
|
|
25
|
+
|
|
26
|
+
export type ScopeSpreadEvalResult =
|
|
27
|
+
| { ok: true; value: string }
|
|
28
|
+
| { ok: false; issue: ScopeSpreadEvalIssue; arrayPath?: string };
|
|
29
|
+
|
|
30
|
+
export type SpreadScopePathResolution =
|
|
31
|
+
| {
|
|
32
|
+
ok: true;
|
|
33
|
+
arrayPathSegments: string[];
|
|
34
|
+
itemPathSegments: string[];
|
|
35
|
+
arrayValue: unknown[];
|
|
36
|
+
}
|
|
37
|
+
| { ok: false; issue: ScopeSpreadEvalIssue; arrayPath?: string };
|
|
38
|
+
|
|
39
|
+
/** 解析 scope?.a?.b 形式的路径段(不含 scope 前缀) */
|
|
40
|
+
export function parseScopePathSegments(expression: string): string[] | null {
|
|
41
|
+
const trimmed = expression.trim();
|
|
42
|
+
if (!trimmed.startsWith("scope")) return null;
|
|
43
|
+
|
|
44
|
+
let body = trimmed.slice("scope".length);
|
|
45
|
+
if (body.startsWith("?.")) body = body.slice(2);
|
|
46
|
+
else if (body.startsWith(".")) body = body.slice(1);
|
|
47
|
+
else if (body.length > 0) return null;
|
|
48
|
+
|
|
49
|
+
if (!body) return [];
|
|
50
|
+
return body.split("?.").filter(Boolean);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function formatScopePathSegments(segments: string[]): string {
|
|
54
|
+
return segments.join(".");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getValueAtScopePath(scope: unknown, path: string[]): unknown {
|
|
58
|
+
let current: unknown = scope;
|
|
59
|
+
for (const part of path) {
|
|
60
|
+
if (current === null || current === undefined) return undefined;
|
|
61
|
+
if (typeof current !== "object") return undefined;
|
|
62
|
+
current = (current as Record<string, unknown>)[part];
|
|
63
|
+
}
|
|
64
|
+
return current;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 从完整路径中定位数组段与项内字段段。
|
|
69
|
+
* 例如 scope?.data?.data?.statusName → 数组在 data.data,项字段为 statusName。
|
|
70
|
+
*/
|
|
71
|
+
export function resolveSpreadScopePath(
|
|
72
|
+
expression: string,
|
|
73
|
+
scope: unknown
|
|
74
|
+
): SpreadScopePathResolution {
|
|
75
|
+
const segments = parseScopePathSegments(expression);
|
|
76
|
+
if (segments === null || segments.length === 0) {
|
|
77
|
+
return { ok: false, issue: "invalid-expression" };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (segments.length === 1) {
|
|
81
|
+
const arrayPathSegments = segments;
|
|
82
|
+
const arrayValue = getValueAtScopePath(scope, arrayPathSegments);
|
|
83
|
+
const arrayPath = formatScopePathSegments(arrayPathSegments);
|
|
84
|
+
if (arrayValue === undefined) {
|
|
85
|
+
return { ok: false, issue: "path-missing", arrayPath };
|
|
86
|
+
}
|
|
87
|
+
if (!Array.isArray(arrayValue)) {
|
|
88
|
+
return { ok: false, issue: "not-array", arrayPath };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
ok: true,
|
|
92
|
+
arrayPathSegments,
|
|
93
|
+
itemPathSegments: [],
|
|
94
|
+
arrayValue,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (let splitAt = segments.length - 1; splitAt >= 1; splitAt--) {
|
|
99
|
+
const arrayPathSegments = segments.slice(0, splitAt);
|
|
100
|
+
const itemPathSegments = segments.slice(splitAt);
|
|
101
|
+
const arrayValue = getValueAtScopePath(scope, arrayPathSegments);
|
|
102
|
+
if (arrayValue === undefined) continue;
|
|
103
|
+
if (Array.isArray(arrayValue)) {
|
|
104
|
+
return {
|
|
105
|
+
ok: true,
|
|
106
|
+
arrayPathSegments,
|
|
107
|
+
itemPathSegments,
|
|
108
|
+
arrayValue,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const rootValue = getValueAtScopePath(scope, [segments[0]!]);
|
|
114
|
+
if (rootValue === undefined) {
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
issue: "path-missing",
|
|
118
|
+
arrayPath: segments[0],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (let splitAt = 1; splitAt < segments.length; splitAt++) {
|
|
123
|
+
const arrayPathSegments = segments.slice(0, splitAt);
|
|
124
|
+
const arrayValue = getValueAtScopePath(scope, arrayPathSegments);
|
|
125
|
+
if (arrayValue === undefined) continue;
|
|
126
|
+
if (!Array.isArray(arrayValue)) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
issue: "not-array",
|
|
130
|
+
arrayPath: formatScopePathSegments(arrayPathSegments),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
issue: "path-missing",
|
|
138
|
+
arrayPath: formatScopePathSegments(segments.slice(0, -1)),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatSpreadItemValue(value: unknown): string {
|
|
143
|
+
if (value === null || value === undefined) return "";
|
|
144
|
+
if (typeof value === "string") return value;
|
|
145
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
146
|
+
return String(value);
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
return JSON.stringify(value);
|
|
150
|
+
} catch {
|
|
151
|
+
return String(value);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function evaluateSpreadScopeExpression(
|
|
156
|
+
expression: string,
|
|
157
|
+
scope: unknown
|
|
158
|
+
): ScopeSpreadEvalResult {
|
|
159
|
+
const valuesResult = evaluateSpreadScopeExpressionValues(expression, scope);
|
|
160
|
+
if (!valuesResult.ok) {
|
|
161
|
+
return valuesResult;
|
|
162
|
+
}
|
|
163
|
+
return { ok: true, value: valuesResult.values.join(",") };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function evaluateSpreadScopeExpressionValues(
|
|
167
|
+
expression: string,
|
|
168
|
+
scope: unknown
|
|
169
|
+
): { ok: true; values: string[] } | { ok: false; issue: ScopeSpreadEvalIssue; arrayPath?: string } {
|
|
170
|
+
const resolved = resolveSpreadScopePath(expression, scope);
|
|
171
|
+
if (!resolved.ok) {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
issue: resolved.issue,
|
|
175
|
+
arrayPath: resolved.arrayPath,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const values = resolved.arrayValue
|
|
180
|
+
.map((item) =>
|
|
181
|
+
resolved.itemPathSegments.length === 0
|
|
182
|
+
? item
|
|
183
|
+
: getValueAtScopePath(item, resolved.itemPathSegments)
|
|
184
|
+
)
|
|
185
|
+
.map(formatSpreadItemValue)
|
|
186
|
+
.filter((value) => value.length > 0);
|
|
187
|
+
|
|
188
|
+
return { ok: true, values };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function isSpreadOnlyScopeTemplate(value: string): boolean {
|
|
192
|
+
return /^\[\.\.\.\{[^}]+\}\]$/.test(value.trim());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function extractSpreadScopeExpression(template: string): string | null {
|
|
196
|
+
const match = /^\[\.\.\.\{([^}]+)\}\]$/.exec(template.trim());
|
|
197
|
+
return match ? match[1]!.trim() : null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function isSpreadTemplateBrace(value: string, braceIndex: number): boolean {
|
|
201
|
+
return value.slice(Math.max(0, braceIndex - 4), braceIndex) === "[...";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function buildSpreadNotArrayWarningMessage(
|
|
205
|
+
fieldLabel: string,
|
|
206
|
+
template: string,
|
|
207
|
+
expression: string,
|
|
208
|
+
arrayPath: string
|
|
209
|
+
): 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
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function buildSpreadInvalidExpressionWarningMessage(
|
|
220
|
+
fieldLabel: string,
|
|
221
|
+
template: string
|
|
222
|
+
): 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
|
+
);
|
|
229
|
+
}
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import type { PanelElement } from "../types";
|
|
2
2
|
import { hasScopeTemplate } from "./scope-template";
|
|
3
3
|
import { resolveScopeFieldLabel } from "./scope-field-labels";
|
|
4
|
+
import {
|
|
5
|
+
SCOPE_SPREAD_TEMPLATE_RE,
|
|
6
|
+
buildSpreadInvalidExpressionWarningMessage,
|
|
7
|
+
buildSpreadNotArrayWarningMessage,
|
|
8
|
+
formatScopePathSegments,
|
|
9
|
+
isSpreadTemplateBrace,
|
|
10
|
+
parseScopePathSegments,
|
|
11
|
+
resolveSpreadScopePath,
|
|
12
|
+
} from "./scope-template-spread";
|
|
13
|
+
|
|
14
|
+
export type ScopeTemplateWarningKind =
|
|
15
|
+
| "missing-path"
|
|
16
|
+
| "spread-not-array"
|
|
17
|
+
| "spread-invalid-expression";
|
|
4
18
|
|
|
5
19
|
export type ScopeTemplateWarning = {
|
|
6
20
|
fieldId: string;
|
|
@@ -9,6 +23,7 @@ export type ScopeTemplateWarning = {
|
|
|
9
23
|
expression: string;
|
|
10
24
|
missingPath: string;
|
|
11
25
|
message: string;
|
|
26
|
+
kind: ScopeTemplateWarningKind;
|
|
12
27
|
};
|
|
13
28
|
|
|
14
29
|
const SCOPE_PATH_RE = /scope(?:\?\.|\.)(\w+(?:\?\.\w+)*)/g;
|
|
@@ -41,6 +56,95 @@ function pathExistsInScope(scope: unknown, path: string): boolean {
|
|
|
41
56
|
return true;
|
|
42
57
|
}
|
|
43
58
|
|
|
59
|
+
function analyzeSpreadTemplateValue(
|
|
60
|
+
fieldId: string,
|
|
61
|
+
value: string,
|
|
62
|
+
scope: unknown
|
|
63
|
+
): ScopeTemplateWarning[] {
|
|
64
|
+
const warnings: ScopeTemplateWarning[] = [];
|
|
65
|
+
const fieldLabel = resolveScopeFieldLabel(fieldId);
|
|
66
|
+
const spreadRe = new RegExp(SCOPE_SPREAD_TEMPLATE_RE.source, "g");
|
|
67
|
+
let match: RegExpExecArray | null;
|
|
68
|
+
|
|
69
|
+
while ((match = spreadRe.exec(value)) !== null) {
|
|
70
|
+
const spreadTemplate = match[0];
|
|
71
|
+
const expression = match[1].trim();
|
|
72
|
+
const segments = parseScopePathSegments(expression);
|
|
73
|
+
|
|
74
|
+
if (segments === null || segments.length === 0) {
|
|
75
|
+
warnings.push({
|
|
76
|
+
fieldId,
|
|
77
|
+
fieldLabel,
|
|
78
|
+
template: value,
|
|
79
|
+
expression,
|
|
80
|
+
missingPath: "",
|
|
81
|
+
kind: "spread-invalid-expression",
|
|
82
|
+
message: buildSpreadInvalidExpressionWarningMessage(
|
|
83
|
+
fieldLabel,
|
|
84
|
+
spreadTemplate
|
|
85
|
+
),
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const resolved = resolveSpreadScopePath(expression, scope);
|
|
91
|
+
if (resolved.ok) continue;
|
|
92
|
+
|
|
93
|
+
if (resolved.issue === "invalid-expression") {
|
|
94
|
+
warnings.push({
|
|
95
|
+
fieldId,
|
|
96
|
+
fieldLabel,
|
|
97
|
+
template: value,
|
|
98
|
+
expression,
|
|
99
|
+
missingPath: "",
|
|
100
|
+
kind: "spread-invalid-expression",
|
|
101
|
+
message: buildSpreadInvalidExpressionWarningMessage(
|
|
102
|
+
fieldLabel,
|
|
103
|
+
spreadTemplate
|
|
104
|
+
),
|
|
105
|
+
});
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const arrayPath =
|
|
110
|
+
resolved.arrayPath ?? formatScopePathSegments(segments.slice(0, -1));
|
|
111
|
+
|
|
112
|
+
if (resolved.issue === "path-missing") {
|
|
113
|
+
if (!pathExistsInScope(scope, arrayPath)) {
|
|
114
|
+
warnings.push({
|
|
115
|
+
fieldId,
|
|
116
|
+
fieldLabel,
|
|
117
|
+
template: value,
|
|
118
|
+
expression,
|
|
119
|
+
missingPath: arrayPath,
|
|
120
|
+
kind: "missing-path",
|
|
121
|
+
message: `「${fieldLabel}」引用了 scope.${arrayPath},但当前 scope 数据中并没有「${arrayPath}」字段,建议检查一下。`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (resolved.issue === "not-array") {
|
|
128
|
+
warnings.push({
|
|
129
|
+
fieldId,
|
|
130
|
+
fieldLabel,
|
|
131
|
+
template: value,
|
|
132
|
+
expression,
|
|
133
|
+
missingPath: arrayPath,
|
|
134
|
+
kind: "spread-not-array",
|
|
135
|
+
message: buildSpreadNotArrayWarningMessage(
|
|
136
|
+
fieldLabel,
|
|
137
|
+
spreadTemplate,
|
|
138
|
+
expression,
|
|
139
|
+
arrayPath
|
|
140
|
+
),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return warnings;
|
|
146
|
+
}
|
|
147
|
+
|
|
44
148
|
function analyzeTemplateValue(
|
|
45
149
|
fieldId: string,
|
|
46
150
|
value: string,
|
|
@@ -48,12 +152,15 @@ function analyzeTemplateValue(
|
|
|
48
152
|
): ScopeTemplateWarning[] {
|
|
49
153
|
if (!hasScopeTemplate(value)) return [];
|
|
50
154
|
|
|
51
|
-
const warnings: ScopeTemplateWarning[] = [
|
|
155
|
+
const warnings: ScopeTemplateWarning[] = [
|
|
156
|
+
...analyzeSpreadTemplateValue(fieldId, value, scope),
|
|
157
|
+
];
|
|
52
158
|
const fieldLabel = resolveScopeFieldLabel(fieldId);
|
|
53
159
|
const expressionRe = /\{([^}]+)\}/g;
|
|
54
160
|
let match: RegExpExecArray | null;
|
|
55
161
|
|
|
56
162
|
while ((match = expressionRe.exec(value)) !== null) {
|
|
163
|
+
if (isSpreadTemplateBrace(value, match.index)) continue;
|
|
57
164
|
const expression = match[1].trim();
|
|
58
165
|
const paths = extractScopePropertyPaths(expression);
|
|
59
166
|
for (const path of paths) {
|
|
@@ -64,6 +171,7 @@ function analyzeTemplateValue(
|
|
|
64
171
|
template: value,
|
|
65
172
|
expression,
|
|
66
173
|
missingPath: path,
|
|
174
|
+
kind: "missing-path",
|
|
67
175
|
message: `「${fieldLabel}」引用了 scope.${path},但当前 scope 数据中并没有「${path}」字段,建议检查一下。`,
|
|
68
176
|
});
|
|
69
177
|
}
|
|
@@ -115,7 +223,7 @@ export function collectElementScopeWarnings(
|
|
|
115
223
|
|
|
116
224
|
const seen = new Set<string>();
|
|
117
225
|
return warnings.filter((warning) => {
|
|
118
|
-
const key = `${warning.fieldId}|${warning.missingPath}|${warning.expression}`;
|
|
226
|
+
const key = `${warning.fieldId}|${warning.kind}|${warning.missingPath}|${warning.expression}`;
|
|
119
227
|
if (seen.has(key)) return false;
|
|
120
228
|
seen.add(key);
|
|
121
229
|
return true;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { PanelElement } from "../types";
|
|
2
|
+
import { materializeChartLabelsFromScope } from "./scope-template-chart";
|
|
3
|
+
import {
|
|
4
|
+
SCOPE_SPREAD_TEMPLATE_RE,
|
|
5
|
+
evaluateSpreadScopeExpression,
|
|
6
|
+
} from "./scope-template-spread";
|
|
2
7
|
|
|
3
|
-
const SCOPE_TEMPLATE_RE = /\{[^}]+\}/;
|
|
8
|
+
const SCOPE_TEMPLATE_RE = /\{[^}]+\}|\[\.\.\.\{[^}]+\}\]/;
|
|
4
9
|
|
|
5
10
|
export function hasScopeTemplate(value: string): boolean {
|
|
6
11
|
return SCOPE_TEMPLATE_RE.test(value);
|
|
@@ -25,7 +30,16 @@ export function evaluateScopeTemplate(
|
|
|
25
30
|
scope: unknown
|
|
26
31
|
): string {
|
|
27
32
|
if (!hasScopeTemplate(template)) return template;
|
|
28
|
-
|
|
33
|
+
|
|
34
|
+
const withSpread = template.replace(
|
|
35
|
+
SCOPE_SPREAD_TEMPLATE_RE,
|
|
36
|
+
(_, rawExpr: string) => {
|
|
37
|
+
const result = evaluateSpreadScopeExpression(rawExpr, scope);
|
|
38
|
+
return result.ok ? result.value : "";
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return withSpread.replace(/\{([^}]+)\}/g, (_, rawExpr: string) => {
|
|
29
43
|
const value = evaluateScopeExpression(rawExpr, scope);
|
|
30
44
|
if (value === null || value === undefined) return "";
|
|
31
45
|
if (typeof value === "string") return value;
|
|
@@ -62,7 +76,15 @@ export function resolvePanelElementScope(
|
|
|
62
76
|
scope: unknown | undefined
|
|
63
77
|
): PanelElement {
|
|
64
78
|
if (scope === undefined) return element;
|
|
65
|
-
|
|
79
|
+
const resolved = resolveScopedValue(structuredClone(element), scope);
|
|
80
|
+
if (resolved.chart) {
|
|
81
|
+
const labels = materializeChartLabelsFromScope(element.chart, scope);
|
|
82
|
+
if (labels !== undefined) {
|
|
83
|
+
resolved.chart.labels = labels;
|
|
84
|
+
delete resolved.chart.labelsText;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return resolved;
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
export function formatViewElementScope(scope: unknown): string {
|
package/src/tailwind.css
CHANGED
|
@@ -29,30 +29,43 @@
|
|
|
29
29
|
/* Moveable:仅角点控制柄固定屏幕像素;勿作用于 moveable-direction 边线 */
|
|
30
30
|
.rv-moveable-layer {
|
|
31
31
|
--moveable-color: #1990ff;
|
|
32
|
+
z-index: 9999 !important;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
.rv-moveable-layer
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
.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
|
+
}
|
|
41
|
+
|
|
42
|
+
.rv-moveable-layer .moveable-line {
|
|
43
|
+
--moveable-color: #1990ff;
|
|
43
44
|
background: var(--moveable-color) !important;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/* 组选/单边控制线:厚度由 moveable zoom 补偿,保持屏幕 1px */
|
|
46
48
|
.rv-moveable-layer .moveable-line.moveable-direction {
|
|
47
|
-
|
|
48
|
-
height: 1px !important;
|
|
49
|
-
margin: 0 !important;
|
|
49
|
+
background: var(--moveable-color) !important;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
.rv-moveable-layer .moveable-
|
|
53
|
-
|
|
52
|
+
.rv-moveable-layer .moveable-group .moveable-control-box,
|
|
53
|
+
.rv-moveable-layer.moveable-group .moveable-control-box {
|
|
54
|
+
z-index: 2 !important;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
.rv-moveable-layer .moveable-around-control {
|
|
57
58
|
background: transparent !important;
|
|
58
59
|
}
|
|
60
|
+
|
|
61
|
+
.rv-moveable-layer .moveable-control {
|
|
62
|
+
width: 14px !important;
|
|
63
|
+
height: 14px !important;
|
|
64
|
+
margin-top: -7px !important;
|
|
65
|
+
margin-left: -7px !important;
|
|
66
|
+
border-radius: 50% !important;
|
|
67
|
+
box-sizing: border-box !important;
|
|
68
|
+
border: 2px solid hsl(var(--background)) !important;
|
|
69
|
+
background: #1990ff !important;
|
|
70
|
+
background: var(--moveable-color) !important;
|
|
71
|
+
}
|