@zeng-alt/vue-spel-query-builder 1.0.0

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/index.cjs ADDED
@@ -0,0 +1,3111 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let spel2js = require("spel2js");
3
+ let vue = require("vue");
4
+ let codemirror = require("codemirror");
5
+ let _codemirror_state = require("@codemirror/state");
6
+ let _codemirror_view = require("@codemirror/view");
7
+ let _codemirror_commands = require("@codemirror/commands");
8
+ let _codemirror_language = require("@codemirror/language");
9
+ let _lezer_highlight = require("@lezer/highlight");
10
+ let _codemirror_autocomplete = require("@codemirror/autocomplete");
11
+ let naive_ui = require("naive-ui");
12
+ //#region src/spel-service.ts
13
+ var SpelService = class {
14
+ context = null;
15
+ setContext(authentication, principal) {
16
+ this.context = spel2js.StandardContext.create(authentication, principal);
17
+ }
18
+ getContext() {
19
+ return this.context;
20
+ }
21
+ compile(expression) {
22
+ return spel2js.SpelExpressionEvaluator.compile(expression);
23
+ }
24
+ eval(expression, locals) {
25
+ return spel2js.SpelExpressionEvaluator.eval(expression, this.getContext(), locals);
26
+ }
27
+ };
28
+ var spelService = new SpelService();
29
+ //#endregion
30
+ //#region node_modules/.pnpm/vue-codemirror@6.1.1_codemirror@6.0.2_vue@3.5.34_typescript@6.0.3_/node_modules/vue-codemirror/dist/vue-codemirror.esm.js
31
+ /*!
32
+ * VueCodemirror v6.1.1
33
+ * Copyright (c) Surmon. All rights reserved.
34
+ * Released under the MIT License.
35
+ * Surmon
36
+ */
37
+ var h = Object.freeze({
38
+ autofocus: !1,
39
+ disabled: !1,
40
+ indentWithTab: !0,
41
+ tabSize: 2,
42
+ placeholder: "",
43
+ autoDestroy: !0,
44
+ extensions: [codemirror.basicSetup]
45
+ }), y = Symbol("vue-codemirror-global-config");
46
+ var O, j = function(e) {
47
+ var t = e.onUpdate, n = e.onChange, o = e.onFocus, r = e.onBlur, u = function(e, t) {
48
+ var n = {};
49
+ for (var o in e) Object.prototype.hasOwnProperty.call(e, o) && t.indexOf(o) < 0 && (n[o] = e[o]);
50
+ if (null != e && "function" == typeof Object.getOwnPropertySymbols) {
51
+ var r = 0;
52
+ for (o = Object.getOwnPropertySymbols(e); r < o.length; r++) t.indexOf(o[r]) < 0 && Object.prototype.propertyIsEnumerable.call(e, o[r]) && (n[o[r]] = e[o[r]]);
53
+ }
54
+ return n;
55
+ }(e, [
56
+ "onUpdate",
57
+ "onChange",
58
+ "onFocus",
59
+ "onBlur"
60
+ ]);
61
+ return _codemirror_state.EditorState.create({
62
+ doc: u.doc,
63
+ selection: u.selection,
64
+ extensions: (Array.isArray(u.extensions) ? u.extensions : [u.extensions]).concat([_codemirror_view.EditorView.updateListener.of((function(e) {
65
+ t(e), e.docChanged && n(e.state.doc.toString(), e), e.focusChanged && (e.view.hasFocus ? o(e) : r(e));
66
+ }))])
67
+ });
68
+ }, S = function(e) {
69
+ var t = new _codemirror_state.Compartment();
70
+ return {
71
+ compartment: t,
72
+ run: function(n) {
73
+ t.get(e.state) ? e.dispatch({ effects: t.reconfigure(n) }) : e.dispatch({ effects: _codemirror_state.StateEffect.appendConfig.of(t.of(n)) });
74
+ }
75
+ };
76
+ }, x = function(e, t) {
77
+ var n = S(e), o = n.compartment, r = n.run;
78
+ return function(n) {
79
+ var u = o.get(e.state);
80
+ r((null != n ? n : u !== t) ? t : []);
81
+ };
82
+ }, C = {
83
+ type: Boolean,
84
+ default: void 0
85
+ }, D = {
86
+ autofocus: C,
87
+ disabled: C,
88
+ indentWithTab: C,
89
+ tabSize: Number,
90
+ placeholder: String,
91
+ style: Object,
92
+ autoDestroy: C,
93
+ phrases: Object,
94
+ root: Object,
95
+ extensions: Array,
96
+ selection: Object
97
+ }, U = { modelValue: {
98
+ type: String,
99
+ default: ""
100
+ } }, w = Object.assign(Object.assign({}, D), U);
101
+ (function(e) {
102
+ e.Change = "change", e.Update = "update", e.Focus = "focus", e.Blur = "blur", e.Ready = "ready", e.ModelUpdate = "update:modelValue";
103
+ })(O || (O = {}));
104
+ var z = {};
105
+ z[O.Change] = function(e, t) {
106
+ return !0;
107
+ }, z[O.Update] = function(e) {
108
+ return !0;
109
+ }, z[O.Focus] = function(e) {
110
+ return !0;
111
+ }, z[O.Blur] = function(e) {
112
+ return !0;
113
+ }, z[O.Ready] = function(e) {
114
+ return !0;
115
+ };
116
+ var B = {};
117
+ B[O.ModelUpdate] = z[O.Change];
118
+ var F = Object.assign(Object.assign({}, z), B), T = (0, vue.defineComponent)({
119
+ name: "VueCodemirror",
120
+ props: Object.assign({}, w),
121
+ emits: Object.assign({}, F),
122
+ setup: function(t, s) {
123
+ var f = (0, vue.shallowRef)(), d = (0, vue.shallowRef)(), C = (0, vue.shallowRef)(), D = Object.assign(Object.assign({}, h), (0, vue.inject)(y, {})), U = (0, vue.computed)((function() {
124
+ var e = {};
125
+ return Object.keys((0, vue.toRaw)(t)).forEach((function(n) {
126
+ var o;
127
+ "modelValue" !== n && (e[n] = null !== (o = t[n]) && void 0 !== o ? o : D[n]);
128
+ })), e;
129
+ }));
130
+ return (0, vue.onMounted)((function() {
131
+ var e;
132
+ d.value = j({
133
+ doc: t.modelValue,
134
+ selection: U.value.selection,
135
+ extensions: null !== (e = D.extensions) && void 0 !== e ? e : [],
136
+ onFocus: function(e) {
137
+ return s.emit(O.Focus, e);
138
+ },
139
+ onBlur: function(e) {
140
+ return s.emit(O.Blur, e);
141
+ },
142
+ onUpdate: function(e) {
143
+ return s.emit(O.Update, e);
144
+ },
145
+ onChange: function(e, n) {
146
+ e !== t.modelValue && (s.emit(O.Change, e, n), s.emit(O.ModelUpdate, e, n));
147
+ }
148
+ }), C.value = function(e) {
149
+ return new _codemirror_view.EditorView(Object.assign({}, e));
150
+ }({
151
+ state: d.value,
152
+ parent: f.value,
153
+ root: U.value.root
154
+ });
155
+ var n = function(e) {
156
+ var t = function() {
157
+ return e.state.doc.toString();
158
+ }, n = S(e).run, o = x(e, [_codemirror_view.EditorView.editable.of(!1), _codemirror_state.EditorState.readOnly.of(!0)]), r = x(e, _codemirror_view.keymap.of([_codemirror_commands.indentWithTab])), u = S(e).run, a = S(e).run, i = S(e).run, c = S(e).run;
159
+ return {
160
+ focus: function() {
161
+ return e.focus();
162
+ },
163
+ getDoc: t,
164
+ setDoc: function(n) {
165
+ n !== t() && e.dispatch({ changes: {
166
+ from: 0,
167
+ to: e.state.doc.length,
168
+ insert: n
169
+ } });
170
+ },
171
+ reExtensions: n,
172
+ toggleDisabled: o,
173
+ toggleIndentWithTab: r,
174
+ setTabSize: function(e) {
175
+ u([_codemirror_state.EditorState.tabSize.of(e), _codemirror_language.indentUnit.of(" ".repeat(e))]);
176
+ },
177
+ setPhrases: function(e) {
178
+ a([_codemirror_state.EditorState.phrases.of(e)]);
179
+ },
180
+ setPlaceholder: function(e) {
181
+ i((0, _codemirror_view.placeholder)(e));
182
+ },
183
+ setStyle: function(e) {
184
+ void 0 === e && (e = {}), c(_codemirror_view.EditorView.theme({ "&": Object.assign({}, e) }));
185
+ }
186
+ };
187
+ }(C.value);
188
+ (0, vue.watch)((function() {
189
+ return t.modelValue;
190
+ }), (function(e) {
191
+ e !== n.getDoc() && n.setDoc(e);
192
+ })), (0, vue.watch)((function() {
193
+ return t.extensions;
194
+ }), (function(e) {
195
+ return n.reExtensions(e || []);
196
+ }), { immediate: !0 }), (0, vue.watch)((function() {
197
+ return U.value.disabled;
198
+ }), (function(e) {
199
+ return n.toggleDisabled(e);
200
+ }), { immediate: !0 }), (0, vue.watch)((function() {
201
+ return U.value.indentWithTab;
202
+ }), (function(e) {
203
+ return n.toggleIndentWithTab(e);
204
+ }), { immediate: !0 }), (0, vue.watch)((function() {
205
+ return U.value.tabSize;
206
+ }), (function(e) {
207
+ return n.setTabSize(e);
208
+ }), { immediate: !0 }), (0, vue.watch)((function() {
209
+ return U.value.phrases;
210
+ }), (function(e) {
211
+ return n.setPhrases(e || {});
212
+ }), { immediate: !0 }), (0, vue.watch)((function() {
213
+ return U.value.placeholder;
214
+ }), (function(e) {
215
+ return n.setPlaceholder(e);
216
+ }), { immediate: !0 }), (0, vue.watch)((function() {
217
+ return U.value.style;
218
+ }), (function(e) {
219
+ return n.setStyle(e);
220
+ }), { immediate: !0 }), U.value.autofocus && n.focus(), s.emit(O.Ready, {
221
+ state: d.value,
222
+ view: C.value,
223
+ container: f.value
224
+ });
225
+ })), (0, vue.onBeforeUnmount)((function() {
226
+ U.value.autoDestroy && C.value && function(e) {
227
+ e.destroy();
228
+ }(C.value);
229
+ })), function() {
230
+ return (0, vue.h)("div", {
231
+ class: "v-codemirror",
232
+ style: { display: "contents" },
233
+ ref: f
234
+ });
235
+ };
236
+ }
237
+ });
238
+ //#endregion
239
+ //#region src/utils/index.ts
240
+ function generateId() {
241
+ return `node_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
242
+ }
243
+ function validateSpelExpression(expression) {
244
+ try {
245
+ spelService.compile(expression);
246
+ return { valid: true };
247
+ } catch (error) {
248
+ return {
249
+ valid: false,
250
+ error: error instanceof Error ? error.message : "Invalid SpEL expression"
251
+ };
252
+ }
253
+ }
254
+ function evalSpelExpression(expression, locals) {
255
+ try {
256
+ spelService.setContext();
257
+ return spelService.eval(expression, locals);
258
+ } catch (error) {
259
+ console.error("SpEL evaluation error:", error);
260
+ return null;
261
+ }
262
+ }
263
+ /**
264
+ * 将 RuleNode 树转换为 SpEL 表达式字符串
265
+ */
266
+ function ruleNodeToSpel(node) {
267
+ if (node.type === "condition") {
268
+ if (!node.left || !node.comparator) return "";
269
+ let leftExpr = formatExpression(node.left);
270
+ if (node.listFilter && node.listFilter.comparator) {
271
+ const { comparator, fieldPath, value } = node.listFilter;
272
+ let target = "#this";
273
+ if (fieldPath) target = `${fieldPath}`;
274
+ switch (comparator) {
275
+ case "isEmpty":
276
+ leftExpr = `${leftExpr}.?[${target} == null || ${target}.isEmpty()]`;
277
+ break;
278
+ case "isNotEmpty":
279
+ leftExpr = `${leftExpr}.?[${target} != null && !${target}.isEmpty()]`;
280
+ break;
281
+ case "isNull":
282
+ leftExpr = `${leftExpr}.?[${target} == null]`;
283
+ break;
284
+ case "isNotNull":
285
+ leftExpr = `${leftExpr}.?[${target} != null]`;
286
+ break;
287
+ default: {
288
+ const filterVal = value ? formatExpression(value) : "";
289
+ leftExpr = `${leftExpr}.?[${target} ${comparator} ${filterVal}]`;
290
+ break;
291
+ }
292
+ }
293
+ }
294
+ if (node.comparator.startsWith("count ")) {
295
+ leftExpr = `${leftExpr}.size()`;
296
+ const rightExpr = node.right ? formatExpression(node.right) : "";
297
+ const op = node.comparator.replace("count ", "");
298
+ return `${leftExpr} ${op} ${rightExpr}`;
299
+ }
300
+ const rightExpr = node.right ? formatExpression(node.right) : "";
301
+ switch (node.comparator) {
302
+ case "==": return `${leftExpr} == ${rightExpr}`;
303
+ case "!=": return `${leftExpr} != ${rightExpr}`;
304
+ case ">": return `${leftExpr} > ${rightExpr}`;
305
+ case ">=": return `${leftExpr} >= ${rightExpr}`;
306
+ case "<": return `${leftExpr} < ${rightExpr}`;
307
+ case "<=": return `${leftExpr} <= ${rightExpr}`;
308
+ case "isEmpty": return `${leftExpr} == null || ${leftExpr}.isEmpty()`;
309
+ case "isNotEmpty": return `${leftExpr} != null && !${leftExpr}.isEmpty()`;
310
+ case "isNull": return `${leftExpr} == null`;
311
+ case "isNotNull": return `${leftExpr} != null`;
312
+ case "count ==": return `${leftExpr}.size() == ${rightExpr}`;
313
+ case "count !=": return `${leftExpr}.size() != ${rightExpr}`;
314
+ case "count <": return `${leftExpr}.size() < ${rightExpr}`;
315
+ case "count <=": return `${leftExpr}.size() <= ${rightExpr}`;
316
+ case "count >": return `${leftExpr}.size() > ${rightExpr}`;
317
+ case "count >=": return `${leftExpr}.size() >= ${rightExpr}`;
318
+ default: return `${leftExpr} ${node.comparator} ${rightExpr}`;
319
+ }
320
+ }
321
+ if (node.type === "group" && node.children?.length) {
322
+ const childExps = node.children.map(ruleNodeToSpel).filter((exp) => exp.trim() !== "");
323
+ if (childExps.length === 0) return "";
324
+ if (childExps.length === 1) return node.operator === "not" ? `!(${childExps[0]})` : childExps[0] || "";
325
+ const separator = node.operator === "or" ? " || " : " && ";
326
+ const combined = `(${childExps.join(separator)})`;
327
+ return node.operator === "not" ? `!${combined}` : combined;
328
+ }
329
+ return "";
330
+ }
331
+ /**
332
+ * 格式化 Expression 为 SpEL 字符串
333
+ * - 字面量:'字符串',数字不加引号(这里简单处理,所有字面量加单引号,数字可后续增强)
334
+ * - 字段:直接使用路径
335
+ * - 函数:base.method(arg1, arg2, ...)
336
+ */
337
+ function formatExpression(expr) {
338
+ if (!expr) return "";
339
+ switch (expr.type) {
340
+ case "literal": return formatLiteral(expr.value);
341
+ case "field": return expr.path;
342
+ case "function": {
343
+ const base = expr.call.base ? formatExpression(expr.call.base) : "";
344
+ if (base) return `${base}.${format(expr.call.method, expr.call.args.map(formatExpression))}`;
345
+ return `${format(expr.call.method, expr.call.args.map(formatExpression))}`;
346
+ }
347
+ default: return "";
348
+ }
349
+ }
350
+ function format(template, args) {
351
+ return template.replace(/\{(\d+)\}/g, (_, index) => {
352
+ return args[index] ?? "";
353
+ });
354
+ }
355
+ /**
356
+ * 格式化字面量:字符串用单引号包裹并转义,数字/布尔不加引号
357
+ */
358
+ function formatLiteral(value) {
359
+ if (value === "" || value === void 0 || value === null) return "''";
360
+ if (/^-?\d+(\.\d+)?$/.test(value)) return value;
361
+ if (value === "true" || value === "false") return value;
362
+ if (value.charAt(0) === "'" && value.charAt(value.length - 1) === "'") return value;
363
+ return `'${value}'`;
364
+ }
365
+ /**
366
+ * 创建一个新的空白条件节点
367
+ */
368
+ function createEmptyCondition() {
369
+ return {
370
+ id: generateId(),
371
+ type: "condition",
372
+ left: {
373
+ type: "field",
374
+ path: ""
375
+ },
376
+ comparator: "=="
377
+ };
378
+ }
379
+ /**
380
+ * 创建一个新的空白分组节点
381
+ */
382
+ function createEmptyGroup(operator = "and") {
383
+ return {
384
+ id: generateId(),
385
+ type: "group",
386
+ operator,
387
+ children: []
388
+ };
389
+ }
390
+ //#endregion
391
+ //#region src/composables/useSpelEditor.ts
392
+ function useSpelEditor(props, emit) {
393
+ const internalValue = (0, vue.ref)(props.modelValue);
394
+ const isFocused = (0, vue.ref)(false);
395
+ const validation = (0, vue.ref)({ valid: true });
396
+ const heightStyle = (0, vue.computed)(() => {
397
+ if (typeof props.height === "number") return { height: `${props.height}px` };
398
+ return { height: props.height || "400px" };
399
+ });
400
+ const handleInput = (value) => {
401
+ internalValue.value = value;
402
+ emit("update:modelValue", value);
403
+ emit("change", value);
404
+ };
405
+ const handleValidate = async () => {
406
+ validation.value = validateSpelExpression(internalValue.value);
407
+ emit("validate", validation.value.valid, validation.value.error);
408
+ return validation.value.valid;
409
+ };
410
+ const run = async () => {
411
+ try {
412
+ const expression = internalValue.value;
413
+ spelService.setContext(props.authentication, props.principal);
414
+ const result = spelService.eval(expression, props.locals);
415
+ emit("run", result, void 0);
416
+ return result;
417
+ } catch (error) {
418
+ emit("run", void 0, getErrorMessage(error) || "执行表达式时发生错误");
419
+ return;
420
+ }
421
+ };
422
+ const getErrorMessage = (error) => {
423
+ if (typeof error === "object" && error !== null && "message" in error) return String(error.message);
424
+ return String(error);
425
+ };
426
+ const setValue = (value) => {
427
+ internalValue.value = value;
428
+ emit("update:modelValue", value);
429
+ };
430
+ const getValue = () => internalValue.value;
431
+ (0, vue.watch)(() => props.modelValue, (newValue) => {
432
+ internalValue.value = newValue;
433
+ });
434
+ return {
435
+ internalValue,
436
+ isFocused,
437
+ validation,
438
+ heightStyle,
439
+ handleInput,
440
+ handleValidate,
441
+ setValue,
442
+ getValue,
443
+ run
444
+ };
445
+ }
446
+ //#endregion
447
+ //#region src/composables/useRuleTree.ts
448
+ function useRuleTree(props, emit) {
449
+ const getSpelExpression = () => {
450
+ return ruleNodeToSpel(props.modelValue);
451
+ };
452
+ const setSpelExpression = (_expression) => {
453
+ console.warn("SpEL parsing to RuleTree is not implemented yet");
454
+ };
455
+ const addCondition = (parentId) => {
456
+ const newNode = createEmptyCondition();
457
+ const updateNode = (node) => {
458
+ if (node.id === parentId && node.children) return {
459
+ ...node,
460
+ children: [...node.children, newNode]
461
+ };
462
+ if (node.children) return {
463
+ ...node,
464
+ children: node.children.map(updateNode)
465
+ };
466
+ return node;
467
+ };
468
+ const newValue = updateNode(props.modelValue);
469
+ emit("update:modelValue", newValue);
470
+ emit("change", newValue);
471
+ };
472
+ const addGroup = (parentId, operator = "and") => {
473
+ const newNode = createEmptyGroup(operator);
474
+ const updateNode = (node) => {
475
+ if (node.id === parentId && node.children) return {
476
+ ...node,
477
+ children: [...node.children, newNode]
478
+ };
479
+ if (node.children) return {
480
+ ...node,
481
+ children: node.children.map(updateNode)
482
+ };
483
+ return node;
484
+ };
485
+ const newValue = updateNode(props.modelValue);
486
+ emit("update:modelValue", newValue);
487
+ emit("change", newValue);
488
+ };
489
+ const removeNode = (nodeId) => {
490
+ const updateNode = (node) => {
491
+ if (node.id === nodeId) return null;
492
+ if (node.children) {
493
+ const filteredChildren = node.children.map(updateNode).filter((n) => n !== null);
494
+ return {
495
+ ...node,
496
+ children: filteredChildren
497
+ };
498
+ }
499
+ return node;
500
+ };
501
+ const newValue = updateNode(props.modelValue);
502
+ if (newValue) {
503
+ emit("update:modelValue", newValue);
504
+ emit("change", newValue);
505
+ }
506
+ };
507
+ const updateNode = (nodeId, updates) => {
508
+ const doUpdate = (node) => {
509
+ if (node.id === nodeId) return {
510
+ ...node,
511
+ ...updates
512
+ };
513
+ if (node.children) return {
514
+ ...node,
515
+ children: node.children.map(doUpdate)
516
+ };
517
+ return node;
518
+ };
519
+ const newValue = doUpdate(props.modelValue);
520
+ emit("update:modelValue", newValue);
521
+ emit("change", newValue);
522
+ };
523
+ const validate = () => {
524
+ try {
525
+ if (!getSpelExpression().trim()) return false;
526
+ return true;
527
+ } catch {
528
+ return false;
529
+ }
530
+ };
531
+ const run = (props, sepl) => {
532
+ spelService.setContext(props.authentication, props.principal);
533
+ return spelService.eval(sepl, props.locals);
534
+ };
535
+ return {
536
+ getSpelExpression,
537
+ setSpelExpression,
538
+ addCondition,
539
+ addGroup,
540
+ removeNode,
541
+ updateNode,
542
+ validate,
543
+ run
544
+ };
545
+ }
546
+ //#endregion
547
+ //#region src/components/SpelEditor/spel-theme.ts
548
+ /**
549
+ * 编辑器主题色板
550
+ */
551
+ var DARK = {
552
+ editorBg: "#1a1a2e",
553
+ gutterBg: "#16213e",
554
+ gutterBorder: "#3e4451",
555
+ gutterFg: "#5c6370",
556
+ contentFg: "#e5e5e5",
557
+ activeLine: "rgba(97,175,239,.08)",
558
+ activeGutter: "rgba(97,175,239,.12)",
559
+ selectionBg: "rgba(97,175,239,.3)",
560
+ cursor: "#61afef",
561
+ matchBracket: "#e5c07b",
562
+ matchBracketBg: "rgba(229,192,107,.15)",
563
+ keyword: "#c678dd",
564
+ operator: "#e06c75",
565
+ string: "#98c379",
566
+ number: "#d19a66",
567
+ atom: "#d19a66",
568
+ variable: "#61afef",
569
+ definition: "#e6c07b",
570
+ property: "#98c379",
571
+ comment: "#5c6370",
572
+ acBg: "#21252b",
573
+ acBorder: "#3e4451",
574
+ acScrollThumb: "#4b5263",
575
+ acItemFg: "#abb2bf",
576
+ acItemHoverBg: "rgba(97,175,239,.2)",
577
+ acIconVar: "#61afef",
578
+ acIconVarBg: "#1d3d55",
579
+ acIconProp: "#98c379",
580
+ acIconPropBg: "#1a3322",
581
+ acIconKey: "#c678dd",
582
+ acIconKeyBg: "#2d1a3d",
583
+ acIconFn: "#e5c07b",
584
+ acIconFnBg: "#3a2c0a",
585
+ acLabel: "#e5c07b",
586
+ acLabelHover: "#f5d88a",
587
+ acDetail: "#5c6370",
588
+ acDetailHover: "#9da5b4",
589
+ acDetailBorder: "#3e4451",
590
+ ttBg: "#1c2028",
591
+ ttBorder: "#4b5263",
592
+ ttFg: "#abb2bf",
593
+ ttLabelFg: "#61afef",
594
+ ttDivider: "#3e4451",
595
+ ttCodeBg: "#0d1117",
596
+ ttCodeFg: "#98c379",
597
+ ttCodeBorder: "#3e4451",
598
+ badgeVarBg: "#1a3a52",
599
+ badgeVarFg: "#61afef",
600
+ badgePropBg: "#1a3b24",
601
+ badgePropFg: "#98c379",
602
+ badgeKeyBg: "#2d1a3d",
603
+ badgeKeyFg: "#c678dd",
604
+ badgeFnBg: "#3b2e0a",
605
+ badgeFnFg: "#e5c07b",
606
+ headerFrom: "#4338ca",
607
+ headerTo: "#7c3aed",
608
+ errBg: "rgba(127,29,29,.7)",
609
+ errBorder: "#991b1b",
610
+ errFg: "#fca5a5"
611
+ };
612
+ var LIGHT = {
613
+ editorBg: "#fafafa",
614
+ gutterBg: "#f0f0f0",
615
+ gutterBorder: "#d1d5db",
616
+ gutterFg: "#9ca3af",
617
+ contentFg: "#1f2937",
618
+ activeLine: "rgba(59,130,246,.06)",
619
+ activeGutter: "rgba(59,130,246,.1)",
620
+ selectionBg: "rgba(59,130,246,.2)",
621
+ cursor: "#2563eb",
622
+ matchBracket: "#d97706",
623
+ matchBracketBg: "rgba(217,119,6,.12)",
624
+ keyword: "#7c3aed",
625
+ operator: "#dc2626",
626
+ string: "#16a34a",
627
+ number: "#d97706",
628
+ atom: "#d97706",
629
+ variable: "#2563eb",
630
+ definition: "#b45309",
631
+ property: "#0f766e",
632
+ comment: "#6b7280",
633
+ acBg: "#ffffff",
634
+ acBorder: "#e5e7eb",
635
+ acScrollThumb: "#d1d5db",
636
+ acItemFg: "#374151",
637
+ acItemHoverBg: "rgba(59,130,246,.1)",
638
+ acIconVar: "#2563eb",
639
+ acIconVarBg: "#dbeafe",
640
+ acIconProp: "#0f766e",
641
+ acIconPropBg: "#ccfbf1",
642
+ acIconKey: "#7c3aed",
643
+ acIconKeyBg: "#ede9fe",
644
+ acIconFn: "#b45309",
645
+ acIconFnBg: "#fef3c7",
646
+ acLabel: "#92400e",
647
+ acLabelHover: "#78350f",
648
+ acDetail: "#9ca3af",
649
+ acDetailHover: "#6b7280",
650
+ acDetailBorder: "#e5e7eb",
651
+ ttBg: "#ffffff",
652
+ ttBorder: "#e5e7eb",
653
+ ttFg: "#4b5563",
654
+ ttLabelFg: "#2563eb",
655
+ ttDivider: "#e5e7eb",
656
+ ttCodeBg: "#f3f4f6",
657
+ ttCodeFg: "#16a34a",
658
+ ttCodeBorder: "#d1d5db",
659
+ badgeVarBg: "#dbeafe",
660
+ badgeVarFg: "#1d4ed8",
661
+ badgePropBg: "#d1fae5",
662
+ badgePropFg: "#065f46",
663
+ badgeKeyBg: "#ede9fe",
664
+ badgeKeyFg: "#6d28d9",
665
+ badgeFnBg: "#fef3c7",
666
+ badgeFnFg: "#92400e",
667
+ headerFrom: "#6366f1",
668
+ headerTo: "#a855f7",
669
+ errBg: "rgba(254,242,242,.95)",
670
+ errBorder: "#fca5a5",
671
+ errFg: "#dc2626"
672
+ };
673
+ /**
674
+ * 创建语法高亮样式(随主题响应式重建)
675
+ */
676
+ function createSpelHighlightStyle(T) {
677
+ return _codemirror_language.HighlightStyle.define([
678
+ {
679
+ tag: _lezer_highlight.tags.keyword,
680
+ color: T.keyword
681
+ },
682
+ {
683
+ tag: _lezer_highlight.tags.operator,
684
+ color: T.operator
685
+ },
686
+ {
687
+ tag: _lezer_highlight.tags.string,
688
+ color: T.string
689
+ },
690
+ {
691
+ tag: _lezer_highlight.tags.number,
692
+ color: T.number
693
+ },
694
+ {
695
+ tag: _lezer_highlight.tags.bool,
696
+ color: T.atom
697
+ },
698
+ {
699
+ tag: _lezer_highlight.tags.null,
700
+ color: T.atom
701
+ },
702
+ {
703
+ tag: _lezer_highlight.tags.atom,
704
+ color: T.atom
705
+ },
706
+ {
707
+ tag: _lezer_highlight.tags.variableName,
708
+ color: T.variable
709
+ },
710
+ {
711
+ tag: _lezer_highlight.tags.special(_lezer_highlight.tags.variableName),
712
+ color: T.variable
713
+ },
714
+ {
715
+ tag: _lezer_highlight.tags.definition(_lezer_highlight.tags.variableName),
716
+ color: T.definition
717
+ },
718
+ {
719
+ tag: _lezer_highlight.tags.propertyName,
720
+ color: T.property
721
+ },
722
+ {
723
+ tag: _lezer_highlight.tags.comment,
724
+ color: T.comment,
725
+ fontStyle: "italic"
726
+ }
727
+ ]);
728
+ }
729
+ /**
730
+ * 创建编辑器 UI 主题(随主题响应式重建)
731
+ */
732
+ function createSpelTheme(T, isDark, fontSize) {
733
+ return _codemirror_view.EditorView.theme({
734
+ "&": {
735
+ height: "100%",
736
+ backgroundColor: T.editorBg
737
+ },
738
+ ".cm-scroller": {
739
+ overflow: "auto",
740
+ backgroundColor: T.editorBg,
741
+ fontFamily: "'Fira Code','JetBrains Mono','Monaco','Consolas',monospace",
742
+ fontSize: `${fontSize}px`
743
+ },
744
+ ".cm-content": {
745
+ caretColor: T.cursor,
746
+ color: T.contentFg,
747
+ padding: "12px"
748
+ },
749
+ ".cm-line": {
750
+ color: T.contentFg,
751
+ padding: "2px 0"
752
+ },
753
+ ".cm-cursor": {
754
+ borderLeftColor: T.cursor,
755
+ borderLeftWidth: "2px"
756
+ },
757
+ ".cm-selectionBackground": { backgroundColor: `${T.selectionBg} !important` },
758
+ "&.cm-focused .cm-selectionBackground": { backgroundColor: `${T.selectionBg} !important` },
759
+ ".cm-activeLine": { backgroundColor: T.activeLine },
760
+ ".cm-gutters": {
761
+ backgroundColor: T.gutterBg,
762
+ borderRight: `1px solid ${T.gutterBorder}`,
763
+ minWidth: "48px"
764
+ },
765
+ ".cm-lineNumbers .cm-gutterElement": {
766
+ color: T.gutterFg,
767
+ padding: "0 12px",
768
+ fontSize: "12px"
769
+ },
770
+ ".cm-activeLineGutter": { backgroundColor: T.activeGutter },
771
+ ".cm-matchingBracket": {
772
+ color: `${T.matchBracket} !important`,
773
+ backgroundColor: T.matchBracketBg,
774
+ fontWeight: "bold"
775
+ }
776
+ }, { dark: isDark });
777
+ }
778
+ //#endregion
779
+ //#region src/components/SpelEditor/spel-completions.ts
780
+ function buildEntries(authentication, principal, locals) {
781
+ const list = [];
782
+ if (authentication) {
783
+ list.push({
784
+ label: "authentication",
785
+ type: "variable",
786
+ detail: "用户认证信息",
787
+ desc: "存储当前登录用户的认证信息。",
788
+ extra: "authentication.name / authentication.roles"
789
+ });
790
+ for (const key of Object.keys(authentication)) {
791
+ list.push({
792
+ label: `authentication.${key}`,
793
+ type: "property",
794
+ detail: "authentication 属性",
795
+ desc: `authentication 对象的 ${key} 属性`
796
+ });
797
+ const val = authentication[key];
798
+ if (val && typeof val === "object" && !Array.isArray(val)) for (const sub of Object.keys(val)) list.push({
799
+ label: `authentication.${key}.${sub}`,
800
+ type: "property",
801
+ detail: "嵌套属性",
802
+ desc: `authentication.${key} 的 ${sub} 属性`
803
+ });
804
+ }
805
+ }
806
+ if (principal) {
807
+ list.push({
808
+ label: "principal",
809
+ type: "variable",
810
+ detail: "基础变量信息",
811
+ desc: "业务主体对象。",
812
+ extra: "principal.id / principal.status"
813
+ });
814
+ for (const key of Object.keys(principal)) {
815
+ list.push({
816
+ label: `principal.${key}`,
817
+ type: "property",
818
+ detail: "principal 属性",
819
+ desc: `principal 对象的 ${key} 属性`
820
+ });
821
+ const val = principal[key];
822
+ if (val && typeof val === "object" && !Array.isArray(val)) for (const sub of Object.keys(val)) list.push({
823
+ label: `principal.${key}.${sub}`,
824
+ type: "property",
825
+ detail: "嵌套属性",
826
+ desc: `principal.${key} 的 ${sub} 属性`
827
+ });
828
+ }
829
+ }
830
+ if (locals) for (const key of Object.keys(locals)) {
831
+ list.push({
832
+ label: `#${key}`,
833
+ type: "variable",
834
+ detail: "locals 变量",
835
+ desc: "用户自定义本地变量。",
836
+ extra: `#${key}`
837
+ });
838
+ const val = locals[key];
839
+ if (val && typeof val === "object" && !Array.isArray(val)) for (const sub of Object.keys(val)) list.push({
840
+ label: `#${key}.${sub}`,
841
+ type: "property",
842
+ detail: `#${key} 属性`,
843
+ desc: `#${key} 的 ${sub} 属性`
844
+ });
845
+ }
846
+ [
847
+ {
848
+ label: "true",
849
+ detail: "布尔值",
850
+ desc: "布尔真值。",
851
+ extra: "principal.active == true"
852
+ },
853
+ {
854
+ label: "false",
855
+ detail: "布尔值",
856
+ desc: "布尔假值。",
857
+ extra: "principal.deleted == false"
858
+ },
859
+ {
860
+ label: "null",
861
+ detail: "空值",
862
+ desc: "表示空引用。",
863
+ extra: "principal.owner != null"
864
+ },
865
+ {
866
+ label: "and",
867
+ detail: "逻辑与",
868
+ desc: "逻辑与 &&。",
869
+ extra: "a > 0 and b > 0"
870
+ },
871
+ {
872
+ label: "or",
873
+ detail: "逻辑或",
874
+ desc: "逻辑或 ||。",
875
+ extra: "a == 1 or b == 1"
876
+ },
877
+ {
878
+ label: "not",
879
+ detail: "逻辑非",
880
+ desc: "逻辑非 !。",
881
+ extra: "not principal.deleted"
882
+ }
883
+ ].forEach((k) => list.push({
884
+ ...k,
885
+ type: "keyword"
886
+ }));
887
+ [
888
+ {
889
+ label: "contains",
890
+ detail: "包含判断",
891
+ desc: "字符串或集合包含判断。",
892
+ extra: "principal.name.contains('admin')"
893
+ },
894
+ {
895
+ label: "startsWith",
896
+ detail: "前缀匹配",
897
+ desc: "字符串前缀匹配。",
898
+ extra: "principal.code.startsWith('ORD')"
899
+ },
900
+ {
901
+ label: "endsWith",
902
+ detail: "后缀匹配",
903
+ desc: "字符串后缀匹配。",
904
+ extra: "principal.file.endsWith('.pdf')"
905
+ },
906
+ {
907
+ label: "matches",
908
+ detail: "正则匹配",
909
+ desc: "正则表达式匹配。",
910
+ extra: "principal.phone.matches('1[3-9]\\\\\\\\d{9}')"
911
+ },
912
+ {
913
+ label: "isEmpty",
914
+ detail: "判断为空",
915
+ desc: "字符串或集合长度是否为 0。",
916
+ extra: "principal.tags.isEmpty()"
917
+ },
918
+ {
919
+ label: "size()",
920
+ detail: "集合长度",
921
+ desc: "返回集合的元素数量。",
922
+ extra: "principal.items.size() > 0"
923
+ },
924
+ {
925
+ label: "length()",
926
+ detail: "字符串长度",
927
+ desc: "返回字符串的字符数量。",
928
+ extra: "principal.name.length() > 2"
929
+ }
930
+ ].forEach((f) => list.push({
931
+ ...f,
932
+ type: "function"
933
+ }));
934
+ return list;
935
+ }
936
+ function buildTypeMap(authentication, principal, locals) {
937
+ const map = {};
938
+ function walk(obj, prefix) {
939
+ if (!obj || typeof obj !== "object") return;
940
+ for (const key of Object.keys(obj)) {
941
+ const fp = prefix ? `${prefix}.${key}` : key;
942
+ const val = obj[key];
943
+ if (val === null || val === void 0) map[fp] = "null";
944
+ else if (Array.isArray(val)) map[fp] = "array";
945
+ else if (typeof val === "object") {
946
+ map[fp] = "object";
947
+ walk(val, fp);
948
+ } else map[fp] = typeof val;
949
+ }
950
+ }
951
+ if (authentication) walk(authentication, "authentication");
952
+ if (principal) walk(principal, "principal");
953
+ if (locals) for (const key of Object.keys(locals)) {
954
+ const fp = `#${key}`;
955
+ const val = locals[key];
956
+ if (val === null || val === void 0) map[fp] = "null";
957
+ else if (Array.isArray(val)) map[fp] = "array";
958
+ else if (typeof val === "object") {
959
+ map[fp] = "object";
960
+ walk(val, fp);
961
+ } else map[fp] = typeof val;
962
+ }
963
+ return map;
964
+ }
965
+ function buildElementFields(obj) {
966
+ if (!obj || typeof obj !== "object") return [];
967
+ return Object.keys(obj).map((key) => {
968
+ const val = obj[key];
969
+ const f = {
970
+ label: key,
971
+ value: key,
972
+ type: "null"
973
+ };
974
+ if (val === null || val === void 0) f.type = "null";
975
+ else if (Array.isArray(val)) {
976
+ f.type = "array";
977
+ if (val.length > 0) {
978
+ f.elementType = typeof val[0];
979
+ if (typeof val[0] === "object" && val[0] !== null) f.elementFields = buildElementFields(val[0]);
980
+ } else f.elementType = "string";
981
+ } else if (typeof val === "object") {
982
+ f.type = "object";
983
+ f.children = buildElementFields(val);
984
+ } else f.type = typeof val;
985
+ return f;
986
+ });
987
+ }
988
+ function buildArrayMeta(authentication, principal, locals) {
989
+ const meta = {};
990
+ function walkArr(obj, prefix) {
991
+ if (!obj || typeof obj !== "object") return;
992
+ for (const key of Object.keys(obj)) {
993
+ const fp = prefix ? `${prefix}.${key}` : key;
994
+ const val = obj[key];
995
+ if (Array.isArray(val)) if (val.length > 0 && typeof val[0] === "object" && val[0] !== null) meta[fp] = {
996
+ elementType: "object",
997
+ elementFields: buildElementFields(val[0])
998
+ };
999
+ else if (val.length > 0) meta[fp] = { elementType: typeof val[0] };
1000
+ else meta[fp] = { elementType: "string" };
1001
+ else if (typeof val === "object" && val !== null) walkArr(val, fp);
1002
+ }
1003
+ }
1004
+ if (authentication) walkArr(authentication, "authentication");
1005
+ if (principal) walkArr(principal, "principal");
1006
+ if (locals) for (const key of Object.keys(locals)) {
1007
+ const fp = `#${key}`;
1008
+ const val = locals[key];
1009
+ if (Array.isArray(val)) if (val.length > 0 && typeof val[0] === "object" && val[0] !== null) meta[fp] = {
1010
+ elementType: "object",
1011
+ elementFields: buildElementFields(val[0])
1012
+ };
1013
+ else if (val.length > 0) meta[fp] = { elementType: typeof val[0] };
1014
+ else meta[fp] = { elementType: "string" };
1015
+ else if (typeof val === "object" && val !== null) walkArr(val, fp);
1016
+ }
1017
+ return meta;
1018
+ }
1019
+ function buildStringMethodEntries() {
1020
+ return [
1021
+ {
1022
+ label: "length",
1023
+ type: "property",
1024
+ detail: "字符串长度",
1025
+ desc: "返回字符串的字符数量。",
1026
+ extra: "principal.name.length"
1027
+ },
1028
+ {
1029
+ label: "isEmpty()",
1030
+ type: "function",
1031
+ detail: "判断为空",
1032
+ desc: "判断长度是否为 0。",
1033
+ extra: "principal.name.isEmpty()"
1034
+ },
1035
+ {
1036
+ label: "toUpperCase()",
1037
+ type: "function",
1038
+ detail: "转大写",
1039
+ desc: "将字符串转换为大写。",
1040
+ extra: "principal.name.toUpperCase()"
1041
+ },
1042
+ {
1043
+ label: "toLowerCase()",
1044
+ type: "function",
1045
+ detail: "转小写",
1046
+ desc: "将字符串转换为小写。",
1047
+ extra: "principal.name.toLowerCase()"
1048
+ },
1049
+ {
1050
+ label: "trim()",
1051
+ type: "function",
1052
+ detail: "去除空格",
1053
+ desc: "去除字符串首尾空白字符。",
1054
+ extra: "principal.name.trim()"
1055
+ },
1056
+ {
1057
+ label: "substring(x)",
1058
+ type: "function",
1059
+ detail: "子串截取(1参)",
1060
+ desc: "从指定位置截取。",
1061
+ extra: "principal.name.substring(3)"
1062
+ },
1063
+ {
1064
+ label: "substring(x,y)",
1065
+ type: "function",
1066
+ detail: "子串截取(2参)",
1067
+ desc: "从起始截取到结束。",
1068
+ extra: "principal.name.substring(0, 3)"
1069
+ },
1070
+ {
1071
+ label: "replace(x,y)",
1072
+ type: "function",
1073
+ detail: "字符串替换",
1074
+ desc: "替换字符。",
1075
+ extra: "principal.name.replace('old', 'new')"
1076
+ },
1077
+ {
1078
+ label: "startsWith(x)",
1079
+ type: "function",
1080
+ detail: "前缀匹配",
1081
+ desc: "是否以指定前缀开头。",
1082
+ extra: "principal.code.startsWith('ORD')"
1083
+ },
1084
+ {
1085
+ label: "endsWith(x)",
1086
+ type: "function",
1087
+ detail: "后缀匹配",
1088
+ desc: "是否以指定后缀结尾。",
1089
+ extra: "principal.file.endsWith('.pdf')"
1090
+ },
1091
+ {
1092
+ label: "contains(x)",
1093
+ type: "function",
1094
+ detail: "包含判断",
1095
+ desc: "是否包含指定子串。",
1096
+ extra: "principal.name.contains('value')"
1097
+ },
1098
+ {
1099
+ label: "indexOf(x)",
1100
+ type: "function",
1101
+ detail: "查找索引",
1102
+ desc: "返回首次位置。",
1103
+ extra: "principal.name.indexOf('a')"
1104
+ },
1105
+ {
1106
+ label: "charAt(x)",
1107
+ type: "function",
1108
+ detail: "获取字符",
1109
+ desc: "返回指定位置字符。",
1110
+ extra: "principal.name.charAt(0)"
1111
+ }
1112
+ ];
1113
+ }
1114
+ function buildArrayMethodEntries(am) {
1115
+ const r = [{
1116
+ label: "size()",
1117
+ type: "function",
1118
+ detail: "集合大小",
1119
+ desc: "返回数组元素数量。",
1120
+ extra: "...roles.size() > 0"
1121
+ }, {
1122
+ label: "contains(x)",
1123
+ type: "function",
1124
+ detail: "包含判断",
1125
+ desc: "数组是否包含指定元素。",
1126
+ extra: "...roles.contains('admin')"
1127
+ }];
1128
+ if (am?.elementType === "object" && am.elementFields) {
1129
+ r.push({
1130
+ label: "?[field == x]",
1131
+ type: "function",
1132
+ detail: "过滤-对象数组",
1133
+ desc: `可用字段:${am.elementFields.map((f) => f.label).join(", ")}`,
1134
+ extra: `...roles.?[${am.elementFields[0]?.label ?? "field"} == value]`
1135
+ });
1136
+ r.push({
1137
+ label: "![field]",
1138
+ type: "function",
1139
+ detail: "投影-提取字段",
1140
+ desc: "从对象数组中提取指定字段。",
1141
+ extra: "...roles.![code]"
1142
+ });
1143
+ for (const f of am.elementFields) r.push({
1144
+ label: `[0].${f.label}`,
1145
+ type: "property",
1146
+ detail: "数组元素字段",
1147
+ desc: `元素 ${f.label},类型 ${f.type}`,
1148
+ extra: `...roles[0].${f.label}`
1149
+ });
1150
+ } else r.push({
1151
+ label: "?[#this == x]",
1152
+ type: "function",
1153
+ detail: "过滤-基本类型数组",
1154
+ desc: `元素类型: ${am?.elementType ?? "string"}`,
1155
+ extra: "...roles.?[#this == 'value']"
1156
+ });
1157
+ return r;
1158
+ }
1159
+ function buildFilterFieldEntries(fields, prefix = "") {
1160
+ return fields.map((f) => ({
1161
+ label: f.label,
1162
+ type: "property",
1163
+ detail: f.type === "array" ? `array<${f.elementType ?? "?"}>` : f.type === "object" ? "object" : f.type,
1164
+ desc: f.type === "object" ? `对象 ${f.label}` : f.type === "array" ? `数组 ${f.label}` : `字段 ${f.label}`,
1165
+ extra: `...?[${prefix ? `${prefix}.` : ""}${f.label} ...]`
1166
+ }));
1167
+ }
1168
+ function resolveFieldPath(fields, path) {
1169
+ const parts = path.split(".");
1170
+ let cur = null;
1171
+ let pool = fields;
1172
+ for (const part of parts) {
1173
+ cur = pool.find((f) => f.label === part) ?? null;
1174
+ if (!cur) return null;
1175
+ if (cur.type === "object" && cur.children) pool = cur.children;
1176
+ else if (cur.type === "array" && cur.elementFields) pool = cur.elementFields;
1177
+ else if (cur.type === "array") return cur;
1178
+ }
1179
+ return cur;
1180
+ }
1181
+ //#endregion
1182
+ //#region src/components/SpelEditor/spel-autocomplete-style.ts
1183
+ var ID = "spel-autocomplete-style";
1184
+ function injectAutocompleteStyle(T) {
1185
+ let el = document.getElementById(ID);
1186
+ if (!el) {
1187
+ el = document.createElement("style");
1188
+ el.id = ID;
1189
+ document.head.appendChild(el);
1190
+ }
1191
+ el.textContent = [
1192
+ `.cm-tooltip-autocomplete { background:${T.acBg}!important; border:1px solid ${T.acBorder}!important; border-radius:8px!important; }`,
1193
+ `.cm-tooltip-autocomplete .cm-completionItem { display:flex!important; gap:8px!important; padding:7px 10px!important; cursor:pointer!important; color:${T.acItemFg}!important; }`,
1194
+ `.cm-tooltip-autocomplete .cm-completionItem:hover { background:${T.acItemHoverBg}!important; }`,
1195
+ `.cm-completionIcon.cm-variable { background:${T.acIconVarBg}; color:${T.acIconVar}; }`,
1196
+ `.cm-completionIcon.cm-property { background:${T.acIconPropBg}; color:${T.acIconProp}; }`,
1197
+ `.cm-completionLabel { font-family:'Fira Code',monospace!important; font-size:13px!important; color:${T.acLabel}!important; }`,
1198
+ `.cm-completionDetail { margin-left:auto!important; font-size:11px!important; color:${T.acDetail}!important; border-left:1px solid ${T.acDetailBorder}!important; }`
1199
+ ].join("\n");
1200
+ }
1201
+ function getTokenAt(doc, pos) {
1202
+ const line = doc.sliceString(Math.max(0, pos - 100), Math.min(doc.length, pos + 100));
1203
+ const off = Math.min(pos, 100);
1204
+ let s = off;
1205
+ while (s > 0) {
1206
+ const c = line[s - 1];
1207
+ if (!c || !/[#\w.]/.test(c)) break;
1208
+ s--;
1209
+ }
1210
+ let e = off;
1211
+ while (e < line.length) {
1212
+ const c = line[e];
1213
+ if (!c || !/[\w.(]/.test(c)) break;
1214
+ e++;
1215
+ }
1216
+ const text = line.slice(s, e).replace(/\(.*$/, "");
1217
+ if (!text) return null;
1218
+ return {
1219
+ from: pos - (off - s),
1220
+ to: pos + (e - off),
1221
+ text
1222
+ };
1223
+ }
1224
+ //#endregion
1225
+ //#region src/components/SpelEditor/spel-parser.ts
1226
+ /**
1227
+ * SpEL StreamLanguage 解析器
1228
+ * 用于语法高亮、括号匹配等
1229
+ */
1230
+ var SPEL_KEYWORDS = new Set([
1231
+ "true",
1232
+ "false",
1233
+ "null",
1234
+ "and",
1235
+ "or",
1236
+ "not",
1237
+ "instanceof",
1238
+ "matches",
1239
+ "new",
1240
+ "div",
1241
+ "mod"
1242
+ ]);
1243
+ var SPEL_BUILTIN_FNS = new Set([
1244
+ "contains",
1245
+ "startsWith",
1246
+ "endsWith",
1247
+ "isEmpty",
1248
+ "size",
1249
+ "length",
1250
+ "substring",
1251
+ "toUpperCase",
1252
+ "toLowerCase",
1253
+ "trim"
1254
+ ]);
1255
+ var spelLanguage = _codemirror_language.StreamLanguage.define({
1256
+ name: "spel",
1257
+ startState: () => ({}),
1258
+ token(stream) {
1259
+ if (stream.eatSpace()) return null;
1260
+ const ch = stream.peek();
1261
+ if (stream.match("//")) {
1262
+ stream.skipToEnd();
1263
+ return "comment";
1264
+ }
1265
+ if (ch === "\"" || ch === "'") {
1266
+ const q = stream.next();
1267
+ if (!q) return null;
1268
+ let esc = false;
1269
+ while (!stream.eol()) {
1270
+ const c = stream.next();
1271
+ if (!c) break;
1272
+ if (esc) {
1273
+ esc = false;
1274
+ continue;
1275
+ }
1276
+ if (c === "\\") {
1277
+ esc = true;
1278
+ continue;
1279
+ }
1280
+ if (c === q) break;
1281
+ }
1282
+ return "string";
1283
+ }
1284
+ if (ch && /\d/.test(ch)) {
1285
+ stream.match(/^\d+(\.\d+)?([eE][+-]?\d+)?/);
1286
+ return "number";
1287
+ }
1288
+ if (ch === "#") {
1289
+ stream.next();
1290
+ stream.match(/^[a-zA-Z_]\w*/);
1291
+ return "variable-2";
1292
+ }
1293
+ if (ch && /[a-zA-Z_$]/.test(ch)) {
1294
+ stream.match(/^[a-zA-Z_$]\w*/);
1295
+ const word = stream.current();
1296
+ if (SPEL_KEYWORDS.has(word)) return "keyword";
1297
+ if (SPEL_BUILTIN_FNS.has(word)) return "def";
1298
+ if (stream.match(/^\s*\(/, false)) return "def";
1299
+ return "variable";
1300
+ }
1301
+ if (ch === ".") {
1302
+ stream.next();
1303
+ if (stream.match(/^[a-zA-Z_]\w*/)) return "property";
1304
+ return "operator";
1305
+ }
1306
+ if (ch && /[=!<>&|+\-*/%^~?:]/.test(ch)) {
1307
+ stream.match(/^(===|!==|==|!=|<=|>=|&&|\|\||[=!<>&|+\-*/%^~?:])/);
1308
+ return "operator";
1309
+ }
1310
+ stream.next();
1311
+ return null;
1312
+ }
1313
+ });
1314
+ //#endregion
1315
+ //#region src/components/SpelEditor/spel-autocomplete-tooltip.ts
1316
+ function buildHoverTooltipDom(entry, T) {
1317
+ const badge = {
1318
+ variable: {
1319
+ bg: T.badgeVarBg,
1320
+ fg: T.badgeVarFg,
1321
+ text: "VAR"
1322
+ },
1323
+ property: {
1324
+ bg: T.badgePropBg,
1325
+ fg: T.badgePropFg,
1326
+ text: "PROP"
1327
+ },
1328
+ keyword: {
1329
+ bg: T.badgeKeyBg,
1330
+ fg: T.badgeKeyFg,
1331
+ text: "KEY"
1332
+ },
1333
+ function: {
1334
+ bg: T.badgeFnBg,
1335
+ fg: T.badgeFnFg,
1336
+ text: "FN"
1337
+ }
1338
+ }[entry.type] ?? {
1339
+ bg: T.acBg,
1340
+ fg: T.acItemFg,
1341
+ text: ""
1342
+ };
1343
+ const root = document.createElement("div");
1344
+ Object.assign(root.style, {
1345
+ padding: "12px 14px",
1346
+ minWidth: "240px",
1347
+ maxWidth: "320px",
1348
+ background: T.ttBg,
1349
+ border: `1px solid ${T.ttBorder}`,
1350
+ borderRadius: "8px",
1351
+ boxShadow: "0 8px 32px rgba(0,0,0,.18)",
1352
+ fontFamily: "'Fira Code','JetBrains Mono','Consolas',monospace",
1353
+ fontSize: "12px",
1354
+ lineHeight: "1.6",
1355
+ color: T.ttFg
1356
+ });
1357
+ const h = document.createElement("div");
1358
+ Object.assign(h.style, {
1359
+ display: "flex",
1360
+ alignItems: "center",
1361
+ justifyContent: "space-between",
1362
+ gap: "8px",
1363
+ marginBottom: "8px"
1364
+ });
1365
+ const lb = document.createElement("span");
1366
+ lb.textContent = entry.label;
1367
+ Object.assign(lb.style, {
1368
+ fontWeight: "600",
1369
+ fontSize: "13px",
1370
+ color: T.ttLabelFg
1371
+ });
1372
+ const be = document.createElement("span");
1373
+ be.textContent = badge.text;
1374
+ Object.assign(be.style, {
1375
+ flexShrink: "0",
1376
+ fontSize: "10px",
1377
+ fontWeight: "700",
1378
+ padding: "2px 6px",
1379
+ borderRadius: "3px",
1380
+ background: badge.bg,
1381
+ color: badge.fg
1382
+ });
1383
+ h.appendChild(lb);
1384
+ h.appendChild(be);
1385
+ const dv = document.createElement("div");
1386
+ Object.assign(dv.style, {
1387
+ height: "1px",
1388
+ background: T.ttDivider,
1389
+ marginBottom: "8px"
1390
+ });
1391
+ const dc = document.createElement("div");
1392
+ dc.textContent = entry.desc;
1393
+ Object.assign(dc.style, {
1394
+ fontSize: "12px",
1395
+ color: T.ttFg,
1396
+ lineHeight: "1.65"
1397
+ });
1398
+ root.appendChild(h);
1399
+ root.appendChild(dv);
1400
+ root.appendChild(dc);
1401
+ if (entry.extra) {
1402
+ const ex = document.createElement("div");
1403
+ ex.textContent = entry.extra;
1404
+ Object.assign(ex.style, {
1405
+ marginTop: "8px",
1406
+ padding: "6px 8px",
1407
+ background: T.ttCodeBg,
1408
+ borderRadius: "4px",
1409
+ fontSize: "11px",
1410
+ color: T.ttCodeFg,
1411
+ borderLeft: `2px solid ${T.ttCodeBorder}`
1412
+ });
1413
+ root.appendChild(ex);
1414
+ }
1415
+ return root;
1416
+ }
1417
+ //#endregion
1418
+ //#region src/components/SpelEditor/spel-autocomplete-ext.ts
1419
+ function buildExtensions(T, isDark, fontSize, source, tooltip) {
1420
+ return [
1421
+ (0, _codemirror_view.lineNumbers)(),
1422
+ (0, _codemirror_view.highlightActiveLineGutter)(),
1423
+ (0, _codemirror_view.highlightSpecialChars)(),
1424
+ (0, _codemirror_commands.history)(),
1425
+ (0, _codemirror_language.foldGutter)(),
1426
+ (0, _codemirror_view.drawSelection)(),
1427
+ (0, _codemirror_view.dropCursor)(),
1428
+ _codemirror_state.EditorState.allowMultipleSelections.of(true),
1429
+ (0, _codemirror_language.indentOnInput)(),
1430
+ (0, _codemirror_language.bracketMatching)(),
1431
+ (0, _codemirror_autocomplete.closeBrackets)(),
1432
+ (0, _codemirror_view.rectangularSelection)(),
1433
+ (0, _codemirror_view.crosshairCursor)(),
1434
+ (0, _codemirror_view.highlightActiveLine)(),
1435
+ _codemirror_view.keymap.of([
1436
+ ..._codemirror_autocomplete.closeBracketsKeymap,
1437
+ ..._codemirror_commands.defaultKeymap,
1438
+ ..._codemirror_commands.historyKeymap,
1439
+ ..._codemirror_autocomplete.completionKeymap
1440
+ ]),
1441
+ spelLanguage,
1442
+ (0, _codemirror_language.syntaxHighlighting)(createSpelHighlightStyle(T)),
1443
+ (0, _codemirror_autocomplete.autocompletion)({
1444
+ override: [source],
1445
+ defaultKeymap: true,
1446
+ closeOnBlur: false,
1447
+ activateOnTyping: true
1448
+ }),
1449
+ tooltip,
1450
+ createSpelTheme(T, isDark, fontSize),
1451
+ _codemirror_view.EditorView.lineWrapping
1452
+ ];
1453
+ }
1454
+ //#endregion
1455
+ //#region src/components/SpelEditor/SpelEditor.vue?vue&type=script&setup=true&lang.ts
1456
+ var SpelEditor_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
1457
+ __name: "SpelEditor",
1458
+ props: {
1459
+ modelValue: {},
1460
+ authentication: {},
1461
+ principal: {},
1462
+ locals: {},
1463
+ disabled: { type: Boolean },
1464
+ readonly: { type: Boolean },
1465
+ height: {},
1466
+ theme: { default: "dark" },
1467
+ size: { default: "small" }
1468
+ },
1469
+ emits: [
1470
+ "update:modelValue",
1471
+ "change",
1472
+ "validate",
1473
+ "run"
1474
+ ],
1475
+ setup(__props, { expose: __expose, emit: __emit }) {
1476
+ const props = __props;
1477
+ const { internalValue, validation, handleInput, handleValidate, setValue, getValue, run } = useSpelEditor(props, __emit);
1478
+ const cmRef = (0, vue.ref)();
1479
+ const focus = () => cmRef.value?.view?.focus();
1480
+ __expose({
1481
+ getValue,
1482
+ setValue,
1483
+ validate: handleValidate,
1484
+ run,
1485
+ focus
1486
+ });
1487
+ const isDark = (0, vue.computed)(() => (props.theme ?? "dark") === "dark");
1488
+ const T$1 = (0, vue.computed)(() => isDark.value ? DARK : LIGHT);
1489
+ (0, vue.watch)(isDark, () => injectAutocompleteStyle(T$1.value), { immediate: true });
1490
+ const sz = (0, vue.computed)(() => props.size ?? "small");
1491
+ const sf = {
1492
+ tiny: 12,
1493
+ small: 13,
1494
+ medium: 14,
1495
+ large: 15
1496
+ };
1497
+ const editorFontSize = (0, vue.computed)(() => sf[sz.value]);
1498
+ const cl = (m) => (0, vue.computed)(() => m[sz.value] ?? m.small ?? "");
1499
+ const hPC = cl({
1500
+ tiny: "px-2 py-1",
1501
+ small: "px-4 py-2",
1502
+ medium: "px-5 py-2.5",
1503
+ large: "px-6 py-3"
1504
+ });
1505
+ const hTC = cl({
1506
+ tiny: "text-xs",
1507
+ small: "text-sm",
1508
+ medium: "text-base",
1509
+ large: "text-lg"
1510
+ });
1511
+ const cStyle = (0, vue.computed)(() => {
1512
+ if (typeof props.height === "number") return { height: `${props.height}px` };
1513
+ if (props.height) return { height: props.height };
1514
+ return { minHeight: "200px" };
1515
+ });
1516
+ const A = () => props.authentication, B = () => props.principal, L = () => props.locals;
1517
+ const toO = (e, p) => {
1518
+ const l = p.toLowerCase();
1519
+ return e.filter((x) => x.label.toLowerCase().startsWith(l)).map((x) => ({
1520
+ label: x.label,
1521
+ type: x.type,
1522
+ detail: x.detail
1523
+ }));
1524
+ };
1525
+ function fieldO(r, pfx, part) {
1526
+ if (r.type === "object" && r.children) return toO(buildFilterFieldEntries(r.children, pfx), part);
1527
+ if (r.type === "string") return toO(buildStringMethodEntries(), part);
1528
+ if (r.type === "array") {
1529
+ if (r.elementFields) return toO(buildFilterFieldEntries(r.elementFields, pfx), part);
1530
+ return toO(buildArrayMethodEntries({ elementType: r.elementType ?? "string" }), part);
1531
+ }
1532
+ return [];
1533
+ }
1534
+ function filterCtx(ap, inside, pos) {
1535
+ const meta = buildArrayMeta(A(), B(), L())[ap];
1536
+ if (!meta) return null;
1537
+ const di = inside.lastIndexOf(".");
1538
+ if (di >= 0) {
1539
+ const pfx = inside.slice(0, di);
1540
+ const part = inside.slice(di + 1);
1541
+ if (pfx === "#this") {
1542
+ if (meta.elementType === "string") return {
1543
+ from: pos - part.length,
1544
+ options: toO(buildStringMethodEntries(), part)
1545
+ };
1546
+ if (meta.elementType === "array") return {
1547
+ from: pos - part.length,
1548
+ options: toO(buildArrayMethodEntries({ elementType: meta.elementType }), part)
1549
+ };
1550
+ }
1551
+ if (meta.elementType === "object" && meta.elementFields) {
1552
+ const r = resolveFieldPath(meta.elementFields, pfx);
1553
+ if (r) return {
1554
+ from: pos - part.length,
1555
+ options: fieldO(r, pfx, part)
1556
+ };
1557
+ }
1558
+ } else {
1559
+ const all = [];
1560
+ if (meta.elementType === "object" && meta.elementFields) all.push(...buildFilterFieldEntries(meta.elementFields));
1561
+ all.push({
1562
+ label: "#this",
1563
+ type: "variable",
1564
+ detail: meta.elementType ?? "any",
1565
+ desc: "当前数组元素",
1566
+ extra: ""
1567
+ });
1568
+ return {
1569
+ from: pos - inside.length,
1570
+ options: toO(all, inside)
1571
+ };
1572
+ }
1573
+ return null;
1574
+ }
1575
+ const completionSource = (ctx) => {
1576
+ const word = ctx.matchBefore(/[#a-zA-Z_][\w#.]*/);
1577
+ const pos = ctx.pos;
1578
+ const before = ctx.state.sliceDoc(Math.max(0, pos - 200), pos);
1579
+ const lm = before.match(/('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")\.([\w]*)$/);
1580
+ if (lm) return {
1581
+ from: pos - (lm[2]?.length ?? 0),
1582
+ options: toO(buildStringMethodEntries(), lm[2] ?? "")
1583
+ };
1584
+ const fm = before.match(/(\w+(?:\.\w+)*)\.([?!])[[]([^[\]]*)$/);
1585
+ if (fm) {
1586
+ const r = filterCtx(fm[1], fm[3], pos);
1587
+ if (r) return r;
1588
+ }
1589
+ if (!word || word.from === word.to && !ctx.explicit) return null;
1590
+ const text = word.text;
1591
+ const di = text.lastIndexOf(".");
1592
+ if (di > 0) {
1593
+ const base = text.slice(0, di);
1594
+ const part = text.slice(di + 1);
1595
+ const type = buildTypeMap(A(), B(), L())[base];
1596
+ if (type === "string") return {
1597
+ from: word.from + di + 1,
1598
+ options: toO(buildStringMethodEntries(), part)
1599
+ };
1600
+ if (type === "array") {
1601
+ const am = buildArrayMeta(A(), B(), L())[base];
1602
+ return {
1603
+ from: word.from + di + 1,
1604
+ options: toO(buildArrayMethodEntries(am), part)
1605
+ };
1606
+ }
1607
+ }
1608
+ const lower = text.toLowerCase();
1609
+ return {
1610
+ from: word.from,
1611
+ options: buildEntries(A(), B(), L()).filter((e) => e.label.toLowerCase().startsWith(lower)).map((e) => ({
1612
+ label: e.label,
1613
+ type: e.type,
1614
+ detail: e.detail,
1615
+ apply: (v) => v.dispatch({ changes: {
1616
+ from: word.from,
1617
+ to: word.to,
1618
+ insert: e.label
1619
+ } })
1620
+ }))
1621
+ };
1622
+ };
1623
+ const spelHoverTooltip = (0, vue.computed)(() => (0, _codemirror_view.hoverTooltip)((view, pos) => {
1624
+ const token = getTokenAt(view.state.doc, pos);
1625
+ if (!token) return null;
1626
+ const entry = buildEntries(A(), B(), L()).find((e) => e.label === token.text);
1627
+ if (!entry) return null;
1628
+ return {
1629
+ pos: token.from,
1630
+ end: token.to,
1631
+ above: true,
1632
+ create: () => ({ dom: buildHoverTooltipDom(entry, T$1.value) })
1633
+ };
1634
+ }, { hoverTime: 300 }));
1635
+ const extensions = (0, vue.computed)(() => buildExtensions(T$1.value, isDark.value, editorFontSize.value, completionSource, spelHoverTooltip.value));
1636
+ (0, vue.watch)(() => props.modelValue, (nv) => {
1637
+ if (!cmRef.value?.view) return;
1638
+ const view = cmRef.value.view;
1639
+ if (view.state.doc.toString() !== nv) view.dispatch({ changes: {
1640
+ from: 0,
1641
+ to: view.state.doc.length,
1642
+ insert: nv
1643
+ } });
1644
+ });
1645
+ return (_ctx, _cache) => {
1646
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
1647
+ class: (0, vue.normalizeClass)(["spel-editor-wrap rounded-lg overflow-hidden shadow-lg", [isDark.value ? "border-gray-700" : "border-gray-200", `size--${sz.value}`]]),
1648
+ style: (0, vue.normalizeStyle)([cStyle.value, {
1649
+ borderWidth: "1px",
1650
+ borderStyle: "solid"
1651
+ }])
1652
+ }, [
1653
+ (0, vue.createElementVNode)("div", {
1654
+ class: (0, vue.normalizeClass)(["flex items-center justify-between select-none", (0, vue.unref)(hPC)]),
1655
+ style: (0, vue.normalizeStyle)({ background: `linear-gradient(to right, ${T$1.value.headerFrom}, ${T$1.value.headerTo})` })
1656
+ }, [(0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["flex items-center gap-2 text-white font-medium", (0, vue.unref)(hTC)]) }, [..._cache[1] || (_cache[1] = [(0, vue.createElementVNode)("svg", {
1657
+ class: "w-4 h-4",
1658
+ fill: "none",
1659
+ stroke: "currentColor",
1660
+ viewBox: "0 0 24 24"
1661
+ }, [(0, vue.createElementVNode)("path", {
1662
+ "stroke-linecap": "round",
1663
+ "stroke-linejoin": "round",
1664
+ "stroke-width": "2",
1665
+ d: "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
1666
+ })], -1), (0, vue.createTextVNode)(" SpEL Editor ", -1)])], 2), _cache[2] || (_cache[2] = (0, vue.createElementVNode)("span", { class: "flex items-center gap-1.5" }, [
1667
+ (0, vue.createElementVNode)("span", { class: "w-2.5 h-2.5 rounded-full bg-red-400/80" }),
1668
+ (0, vue.createElementVNode)("span", { class: "w-2.5 h-2.5 rounded-full bg-yellow-400/80" }),
1669
+ (0, vue.createElementVNode)("span", { class: "w-2.5 h-2.5 rounded-full bg-green-400/80" })
1670
+ ], -1))], 6),
1671
+ (0, vue.createVNode)((0, vue.unref)(T), {
1672
+ ref_key: "cmRef",
1673
+ ref: cmRef,
1674
+ modelValue: (0, vue.unref)(internalValue),
1675
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => (0, vue.isRef)(internalValue) ? internalValue.value = $event : null),
1676
+ extensions: extensions.value,
1677
+ autofocus: false,
1678
+ "indent-with-tab": true,
1679
+ "tab-size": 2,
1680
+ class: "spel-cm-wrap",
1681
+ onChange: (0, vue.unref)(handleInput)
1682
+ }, null, 8, [
1683
+ "modelValue",
1684
+ "extensions",
1685
+ "onChange"
1686
+ ]),
1687
+ (0, vue.createVNode)(vue.Transition, { name: "spel-err" }, {
1688
+ default: (0, vue.withCtx)(() => [!(0, vue.unref)(validation).valid ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
1689
+ key: 0,
1690
+ class: "flex items-center gap-2 px-4 py-2 text-sm border-t",
1691
+ style: (0, vue.normalizeStyle)({
1692
+ background: T$1.value.errBg,
1693
+ borderColor: T$1.value.errBorder,
1694
+ color: T$1.value.errFg
1695
+ })
1696
+ }, [_cache[3] || (_cache[3] = (0, vue.createElementVNode)("svg", {
1697
+ class: "w-4 h-4 shrink-0",
1698
+ fill: "none",
1699
+ stroke: "currentColor",
1700
+ viewBox: "0 0 24 24"
1701
+ }, [(0, vue.createElementVNode)("path", {
1702
+ "stroke-linecap": "round",
1703
+ "stroke-linejoin": "round",
1704
+ "stroke-width": "2",
1705
+ d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
1706
+ })], -1)), (0, vue.createTextVNode)(" " + (0, vue.toDisplayString)((0, vue.unref)(validation).error), 1)], 4)) : (0, vue.createCommentVNode)("", true)]),
1707
+ _: 1
1708
+ })
1709
+ ], 6);
1710
+ };
1711
+ }
1712
+ });
1713
+ //#endregion
1714
+ //#region \0plugin-vue:export-helper
1715
+ var _plugin_vue_export_helper_default = (sfc, props) => {
1716
+ const target = sfc.__vccOpts || sfc;
1717
+ for (const [key, val] of props) target[key] = val;
1718
+ return target;
1719
+ };
1720
+ //#endregion
1721
+ //#region src/components/SpelEditor/SpelEditor.vue
1722
+ var SpelEditor_default = /* @__PURE__ */ _plugin_vue_export_helper_default(SpelEditor_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-cb26e4f6"]]);
1723
+ //#endregion
1724
+ //#region src/components/RuleTree/ExpressionEditor.vue?vue&type=script&setup=true&lang.ts
1725
+ var _hoisted_1$1 = { class: "flex items-center gap-1" };
1726
+ var _hoisted_2$1 = {
1727
+ key: 0,
1728
+ class: "text-gray-400 font-mono text-sm"
1729
+ };
1730
+ var _hoisted_3$1 = {
1731
+ key: 0,
1732
+ class: "text-gray-400 font-mono text-sm"
1733
+ };
1734
+ //#endregion
1735
+ //#region src/components/RuleTree/ExpressionEditor.vue
1736
+ var ExpressionEditor_default = /* @__PURE__ */ (0, vue.defineComponent)({
1737
+ __name: "ExpressionEditor",
1738
+ props: {
1739
+ modelValue: { default: () => ({
1740
+ type: "field",
1741
+ path: ""
1742
+ }) },
1743
+ fieldOptions: {},
1744
+ disabled: { type: Boolean },
1745
+ allowLiteral: {
1746
+ type: Boolean,
1747
+ default: true
1748
+ },
1749
+ allowFunction: {
1750
+ type: Boolean,
1751
+ default: true
1752
+ },
1753
+ baseTypeFilter: {},
1754
+ forceNumberInput: { type: Boolean },
1755
+ size: { default: "small" }
1756
+ },
1757
+ emits: ["update:modelValue"],
1758
+ setup(__props, { emit: __emit }) {
1759
+ const FUNCTIONS = [
1760
+ {
1761
+ label: "length",
1762
+ value: "length",
1763
+ argumentCount: 0,
1764
+ hasBase: true,
1765
+ baseType: "string",
1766
+ returnType: "number"
1767
+ },
1768
+ {
1769
+ label: "size()",
1770
+ value: "size()",
1771
+ argumentCount: 0,
1772
+ hasBase: true,
1773
+ baseType: "collection",
1774
+ returnType: "number"
1775
+ },
1776
+ {
1777
+ label: "toUpperCase()",
1778
+ value: "toUpperCase()",
1779
+ argumentCount: 0,
1780
+ hasBase: true,
1781
+ baseType: "string",
1782
+ returnType: "string"
1783
+ },
1784
+ {
1785
+ label: "toLowerCase()",
1786
+ value: "toLowerCase()",
1787
+ argumentCount: 0,
1788
+ hasBase: true,
1789
+ baseType: "string",
1790
+ returnType: "string"
1791
+ },
1792
+ {
1793
+ label: "trim()",
1794
+ value: "trim()",
1795
+ argumentCount: 0,
1796
+ hasBase: true,
1797
+ baseType: "string",
1798
+ returnType: "string"
1799
+ },
1800
+ {
1801
+ label: "abs()",
1802
+ value: "abs()",
1803
+ argumentCount: 0,
1804
+ hasBase: true,
1805
+ baseType: "number",
1806
+ returnType: "number"
1807
+ },
1808
+ {
1809
+ label: "round()",
1810
+ value: "round()",
1811
+ argumentCount: 0,
1812
+ hasBase: true,
1813
+ baseType: "number",
1814
+ returnType: "number"
1815
+ },
1816
+ {
1817
+ label: "substring(x)",
1818
+ value: "substring({0})",
1819
+ argumentCount: 1,
1820
+ hasBase: true,
1821
+ baseType: "string",
1822
+ returnType: "string"
1823
+ },
1824
+ {
1825
+ label: "substring(x, y)",
1826
+ value: "substring({0}, {1})",
1827
+ argumentCount: 2,
1828
+ hasBase: true,
1829
+ baseType: "string",
1830
+ returnType: "string"
1831
+ },
1832
+ {
1833
+ label: "replace()",
1834
+ value: "replace({0}, {1})",
1835
+ argumentCount: 2,
1836
+ hasBase: true,
1837
+ baseType: "string",
1838
+ returnType: "string"
1839
+ },
1840
+ {
1841
+ label: "startsWith()",
1842
+ value: "startsWith({0})",
1843
+ argumentCount: 1,
1844
+ hasBase: true,
1845
+ baseType: "string",
1846
+ returnType: "boolean"
1847
+ },
1848
+ {
1849
+ label: "endsWith()",
1850
+ value: "endsWith({0})",
1851
+ argumentCount: 1,
1852
+ hasBase: true,
1853
+ baseType: "string",
1854
+ returnType: "boolean"
1855
+ },
1856
+ {
1857
+ label: "contains()",
1858
+ value: "contains({0})",
1859
+ argumentCount: 1,
1860
+ hasBase: true,
1861
+ baseType: "string",
1862
+ returnType: "boolean"
1863
+ },
1864
+ {
1865
+ label: "indexOf()",
1866
+ value: "indexOf({0})",
1867
+ argumentCount: 1,
1868
+ hasBase: true,
1869
+ baseType: "string",
1870
+ returnType: "number"
1871
+ },
1872
+ {
1873
+ label: "charAt()",
1874
+ value: "charAt({0})",
1875
+ argumentCount: 1,
1876
+ hasBase: true,
1877
+ baseType: "string",
1878
+ returnType: "string"
1879
+ },
1880
+ {
1881
+ label: "now()",
1882
+ value: "now()",
1883
+ argumentCount: 0,
1884
+ hasBase: false,
1885
+ returnType: "date"
1886
+ },
1887
+ {
1888
+ label: "random()",
1889
+ value: "random()",
1890
+ argumentCount: 0,
1891
+ hasBase: false,
1892
+ returnType: "number"
1893
+ },
1894
+ {
1895
+ label: "currentUser()",
1896
+ value: "currentUser()",
1897
+ argumentCount: 0,
1898
+ hasBase: false,
1899
+ returnType: "string"
1900
+ }
1901
+ ];
1902
+ const fullFunctionOptions = FUNCTIONS.map((f) => ({
1903
+ label: f.label,
1904
+ value: f.value
1905
+ }));
1906
+ const props = __props;
1907
+ const emit = __emit;
1908
+ const typeOptions = (0, vue.computed)(() => {
1909
+ const ops = [{
1910
+ label: "字段",
1911
+ value: "field"
1912
+ }];
1913
+ if (props.allowLiteral) ops.push({
1914
+ label: "值",
1915
+ value: "literal"
1916
+ });
1917
+ if (props.allowFunction) ops.push({
1918
+ label: "方法",
1919
+ value: "function"
1920
+ });
1921
+ return ops;
1922
+ });
1923
+ const currentType = (0, vue.computed)(() => props.modelValue.type);
1924
+ const currentFunctionDef = (0, vue.computed)(() => {
1925
+ if (props.modelValue.type !== "function") return null;
1926
+ const funcExpr = props.modelValue;
1927
+ return FUNCTIONS.find((f) => f.value === funcExpr.call.method) ?? null;
1928
+ });
1929
+ function filterFieldTree(opts, typeFilter) {
1930
+ if (!typeFilter) return opts;
1931
+ return opts.reduce((acc, opt) => {
1932
+ if (opt.children && opt.children.length > 0) {
1933
+ const filteredChildren = filterFieldTree(opt.children, typeFilter);
1934
+ if (filteredChildren.length > 0) acc.push({
1935
+ ...opt,
1936
+ children: filteredChildren
1937
+ });
1938
+ } else if (opt.type === typeFilter) acc.push(opt);
1939
+ return acc;
1940
+ }, []);
1941
+ }
1942
+ const filteredFieldOptions = (0, vue.computed)(() => {
1943
+ return filterFieldTree(props.fieldOptions ?? [], props.baseTypeFilter);
1944
+ });
1945
+ const filteredFunctionOptions = (0, vue.computed)(() => {
1946
+ if (!props.baseTypeFilter) return fullFunctionOptions;
1947
+ return FUNCTIONS.filter((f) => f.returnType === props.baseTypeFilter).map((f) => ({
1948
+ label: f.label,
1949
+ value: f.value
1950
+ }));
1951
+ });
1952
+ function setType(type) {
1953
+ if (type === "literal") emit("update:modelValue", {
1954
+ type: "literal",
1955
+ value: ""
1956
+ });
1957
+ else if (type === "field") emit("update:modelValue", {
1958
+ type: "field",
1959
+ path: ""
1960
+ });
1961
+ else emit("update:modelValue", {
1962
+ type: "function",
1963
+ call: {
1964
+ method: "",
1965
+ base: void 0,
1966
+ args: []
1967
+ }
1968
+ });
1969
+ }
1970
+ function updateLiteral(value) {
1971
+ emit("update:modelValue", {
1972
+ type: "literal",
1973
+ value
1974
+ });
1975
+ }
1976
+ function updateFieldPath(path) {
1977
+ emit("update:modelValue", {
1978
+ type: "field",
1979
+ path
1980
+ });
1981
+ }
1982
+ function updateCall(partial) {
1983
+ if (props.modelValue.type !== "function") return;
1984
+ emit("update:modelValue", {
1985
+ ...props.modelValue,
1986
+ call: {
1987
+ ...props.modelValue.call,
1988
+ ...partial
1989
+ }
1990
+ });
1991
+ }
1992
+ function onMethodChange(method) {
1993
+ const def = FUNCTIONS.find((f) => f.value === method);
1994
+ if (!def) return;
1995
+ const args = Array.from({ length: def.argumentCount }, () => ({
1996
+ type: "literal",
1997
+ value: ""
1998
+ }));
1999
+ emit("update:modelValue", {
2000
+ type: "function",
2001
+ call: {
2002
+ method,
2003
+ base: def.hasBase ? {
2004
+ type: "field",
2005
+ path: ""
2006
+ } : void 0,
2007
+ args
2008
+ }
2009
+ });
2010
+ }
2011
+ function updateArg(index, expr) {
2012
+ if (props.modelValue.type !== "function") return;
2013
+ const args = [...props.modelValue.call.args];
2014
+ args[index] = expr;
2015
+ emit("update:modelValue", {
2016
+ ...props.modelValue,
2017
+ call: {
2018
+ ...props.modelValue.call,
2019
+ args
2020
+ }
2021
+ });
2022
+ }
2023
+ const literalPlaceholder = (0, vue.computed)(() => {
2024
+ if (props.baseTypeFilter) return `输入${props.baseTypeFilter}值…`;
2025
+ return "输入值…";
2026
+ });
2027
+ const safeLiteralValue = (0, vue.computed)(() => {
2028
+ if (props.modelValue.type === "literal") return props.modelValue.value;
2029
+ return "";
2030
+ });
2031
+ (0, vue.watchEffect)(() => {
2032
+ if (props.forceNumberInput && props.modelValue.type !== "literal") emit("update:modelValue", {
2033
+ type: "literal",
2034
+ value: ""
2035
+ });
2036
+ });
2037
+ return (_ctx, _cache) => {
2038
+ const _component_n_input_number = (0, vue.resolveComponent)("n-input-number");
2039
+ const _component_n_select = (0, vue.resolveComponent)("n-select");
2040
+ const _component_n_input = (0, vue.resolveComponent)("n-input");
2041
+ const _component_n_cascader = (0, vue.resolveComponent)("n-cascader");
2042
+ const _component_ExpressionEditor = (0, vue.resolveComponent)("ExpressionEditor", true);
2043
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$1, [__props.forceNumberInput ? ((0, vue.openBlock)(), (0, vue.createBlock)(_component_n_input_number, {
2044
+ key: 0,
2045
+ value: safeLiteralValue.value !== "" ? Number(safeLiteralValue.value) : null,
2046
+ placeholder: "数字…",
2047
+ size: __props.size,
2048
+ disabled: __props.disabled,
2049
+ class: "!w-[120px]",
2050
+ "onUpdate:value": _cache[0] || (_cache[0] = (v) => updateLiteral(String(v ?? "")))
2051
+ }, null, 8, [
2052
+ "value",
2053
+ "size",
2054
+ "disabled"
2055
+ ])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [(0, vue.createVNode)(_component_n_select, {
2056
+ value: currentType.value,
2057
+ options: typeOptions.value,
2058
+ size: __props.size,
2059
+ class: "!w-[72px]",
2060
+ disabled: __props.disabled,
2061
+ "onUpdate:value": setType
2062
+ }, null, 8, [
2063
+ "value",
2064
+ "options",
2065
+ "size",
2066
+ "disabled"
2067
+ ]), __props.modelValue.type === "literal" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [__props.forceNumberInput ? ((0, vue.openBlock)(), (0, vue.createBlock)(_component_n_input_number, {
2068
+ key: 0,
2069
+ value: Number(__props.modelValue.value),
2070
+ placeholder: "数字…",
2071
+ size: __props.size,
2072
+ disabled: __props.disabled,
2073
+ class: "!w-[110px]",
2074
+ "onUpdate:value": _cache[1] || (_cache[1] = (v) => updateLiteral(String(v ?? "")))
2075
+ }, null, 8, [
2076
+ "value",
2077
+ "size",
2078
+ "disabled"
2079
+ ])) : ((0, vue.openBlock)(), (0, vue.createBlock)(_component_n_input, {
2080
+ key: 1,
2081
+ value: __props.modelValue.value,
2082
+ placeholder: literalPlaceholder.value,
2083
+ size: __props.size,
2084
+ disabled: __props.disabled,
2085
+ class: "!w-[120px]",
2086
+ "onUpdate:value": updateLiteral
2087
+ }, null, 8, [
2088
+ "value",
2089
+ "placeholder",
2090
+ "size",
2091
+ "disabled"
2092
+ ]))], 64)) : __props.modelValue.type === "field" ? ((0, vue.openBlock)(), (0, vue.createBlock)(_component_n_cascader, {
2093
+ key: 1,
2094
+ value: __props.modelValue.path,
2095
+ options: filteredFieldOptions.value,
2096
+ placeholder: "选择字段…",
2097
+ size: __props.size,
2098
+ disabled: __props.disabled,
2099
+ class: "!w-[160px]",
2100
+ clearable: "",
2101
+ "check-strategy": "child",
2102
+ "onUpdate:value": updateFieldPath
2103
+ }, null, 8, [
2104
+ "value",
2105
+ "options",
2106
+ "size",
2107
+ "disabled"
2108
+ ])) : __props.modelValue.type === "function" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, [
2109
+ (0, vue.createVNode)(_component_n_select, {
2110
+ value: __props.modelValue.call.method,
2111
+ options: filteredFunctionOptions.value,
2112
+ placeholder: "方法…",
2113
+ size: __props.size,
2114
+ disabled: __props.disabled,
2115
+ class: "!w-[130px]",
2116
+ "onUpdate:value": onMethodChange
2117
+ }, null, 8, [
2118
+ "value",
2119
+ "options",
2120
+ "size",
2121
+ "disabled"
2122
+ ]),
2123
+ _cache[3] || (_cache[3] = (0, vue.createElementVNode)("span", { class: "text-gray-400 font-mono text-sm" }, "(", -1)),
2124
+ currentFunctionDef.value?.hasBase && __props.modelValue.call.base ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [(0, vue.createVNode)(_component_ExpressionEditor, {
2125
+ "model-value": __props.modelValue.call.base,
2126
+ "field-options": filteredFieldOptions.value,
2127
+ "base-type-filter": currentFunctionDef.value?.baseType,
2128
+ disabled: __props.disabled,
2129
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = (v) => updateCall({ base: v }))
2130
+ }, null, 8, [
2131
+ "model-value",
2132
+ "field-options",
2133
+ "base-type-filter",
2134
+ "disabled"
2135
+ ]), __props.modelValue.call.args.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_2$1, ",")) : (0, vue.createCommentVNode)("", true)], 64)) : (0, vue.createCommentVNode)("", true),
2136
+ ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.modelValue.call.args, (arg, index) => {
2137
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: "arg" + index }, [index > 0 || (currentFunctionDef.value?.hasBase ? true : index > 0) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$1, ",")) : (0, vue.createCommentVNode)("", true), (0, vue.createVNode)(_component_ExpressionEditor, {
2138
+ "model-value": arg,
2139
+ "field-options": filteredFieldOptions.value,
2140
+ disabled: __props.disabled,
2141
+ "onUpdate:modelValue": (v) => updateArg(index, v)
2142
+ }, null, 8, [
2143
+ "model-value",
2144
+ "field-options",
2145
+ "disabled",
2146
+ "onUpdate:modelValue"
2147
+ ])], 64);
2148
+ }), 128)),
2149
+ _cache[4] || (_cache[4] = (0, vue.createElementVNode)("span", { class: "text-gray-400 font-mono text-sm" }, ")", -1))
2150
+ ], 64)) : (0, vue.createCommentVNode)("", true)], 64))]);
2151
+ };
2152
+ }
2153
+ });
2154
+ //#endregion
2155
+ //#region src/components/RuleTree/RuleTreeNode.vue?vue&type=script&setup=true&lang.ts
2156
+ var _hoisted_1 = { class: "expr-chip expr-chip--field font-mono text-[11px] flex-1 min-w-0" };
2157
+ var _hoisted_2 = {
2158
+ key: 0,
2159
+ class: "expr-chip expr-chip--op text-[11px] font-semibold flex-shrink-0"
2160
+ };
2161
+ var _hoisted_3 = {
2162
+ key: 1,
2163
+ class: "expr-chip expr-chip--val font-mono text-[11px] flex-1 min-w-0"
2164
+ };
2165
+ var _hoisted_4 = {
2166
+ key: 2,
2167
+ class: "text-[11px] tracking-widest text-placeholder"
2168
+ };
2169
+ var _hoisted_5 = {
2170
+ key: 1,
2171
+ class: "text-xs italic text-placeholder"
2172
+ };
2173
+ var _hoisted_6 = { class: "flex items-start gap-2 flex-wrap" };
2174
+ var _hoisted_7 = { class: "expr-block" };
2175
+ var _hoisted_8 = { class: "expr-block__body" };
2176
+ var _hoisted_9 = { class: "expr-block" };
2177
+ var _hoisted_10 = { class: "expr-block__body" };
2178
+ var _hoisted_11 = {
2179
+ key: 0,
2180
+ class: "expr-block"
2181
+ };
2182
+ var _hoisted_12 = { class: "expr-block__body" };
2183
+ var _hoisted_13 = {
2184
+ key: 0,
2185
+ class: "flex items-start gap-2 flex-wrap"
2186
+ };
2187
+ var _hoisted_14 = { class: "expr-block" };
2188
+ var _hoisted_15 = { class: "expr-block__body" };
2189
+ var _hoisted_16 = {
2190
+ key: 1,
2191
+ class: "flex items-center gap-1.5 px-2 py-1.5 rounded preview-block"
2192
+ };
2193
+ var _hoisted_17 = { class: "text-[11px] leading-none preview-code" };
2194
+ var _hoisted_18 = { class: "preview-left" };
2195
+ var _hoisted_19 = {
2196
+ key: 0,
2197
+ class: "mx-1 preview-op"
2198
+ };
2199
+ var _hoisted_20 = {
2200
+ key: 1,
2201
+ class: "preview-right"
2202
+ };
2203
+ var _hoisted_21 = {
2204
+ key: 0,
2205
+ class: "group-connector"
2206
+ };
2207
+ var _hoisted_22 = { class: "flex items-center gap-2 mb-2" };
2208
+ var _hoisted_23 = { class: "op-toggle" };
2209
+ var _hoisted_24 = ["disabled", "onClick"];
2210
+ var _hoisted_25 = { class: "flex items-center gap-1" };
2211
+ var _hoisted_26 = { class: "relative pl-5" };
2212
+ var _hoisted_27 = {
2213
+ key: 0,
2214
+ class: "group-vline"
2215
+ };
2216
+ var _hoisted_28 = { class: "flex flex-col gap-1.5" };
2217
+ var _hoisted_29 = {
2218
+ key: 1,
2219
+ class: "flex flex-col items-center justify-center py-6 rounded-md border border-dashed empty-state"
2220
+ };
2221
+ //#endregion
2222
+ //#region src/components/RuleTree/RuleTreeNode.vue
2223
+ var RuleTreeNode_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ (0, vue.defineComponent)({
2224
+ __name: "RuleTreeNode",
2225
+ props: {
2226
+ node: {},
2227
+ authentication: {},
2228
+ principal: {},
2229
+ locals: {},
2230
+ disabled: { type: Boolean },
2231
+ level: {},
2232
+ theme: { default: "light" },
2233
+ size: { default: "small" }
2234
+ },
2235
+ emits: [
2236
+ "add-condition",
2237
+ "add-group",
2238
+ "remove-node",
2239
+ "update-node"
2240
+ ],
2241
+ setup(__props, { emit: __emit }) {
2242
+ const props = __props;
2243
+ const emit = __emit;
2244
+ const level = props.level ?? 0;
2245
+ const isEditing = (0, vue.ref)(false);
2246
+ const handleAddCondition = () => emit("add-condition", props.node.id);
2247
+ const handleAddGroup = () => emit("add-group", props.node.id);
2248
+ const handleRemove = () => emit("remove-node", props.node.id);
2249
+ function handleUpdate(updates) {
2250
+ emit("update-node", props.node.id, updates);
2251
+ }
2252
+ const fieldOptions = (0, vue.computed)(() => {
2253
+ const result = [];
2254
+ if (props.authentication) result.push(...buildFieldOptions(props.authentication, "authentication"));
2255
+ if (props.principal) result.push(...buildFieldOptions(props.principal, "principal"));
2256
+ if (props.locals) result.push(...buildFieldOptions(props.locals, "#"));
2257
+ return result;
2258
+ });
2259
+ function buildFieldOptions(obj, prefix = "") {
2260
+ const options = [];
2261
+ for (const [key, value] of Object.entries(obj)) {
2262
+ const fullPath = prefix ? prefix === "#" ? `#${key}` : `${prefix}.${key}` : key;
2263
+ if (Array.isArray(value)) {
2264
+ const elementType = value.length > 0 ? typeof value[0] : "number";
2265
+ const option = {
2266
+ label: key,
2267
+ value: fullPath,
2268
+ type: "array",
2269
+ elementType
2270
+ };
2271
+ if (elementType === "object" && value.length > 0 && value[0] && !Array.isArray(value[0])) option.elementChildren = buildFieldOptions(value[0], "");
2272
+ options.push(option);
2273
+ } else if (value && typeof value === "object" && !Array.isArray(value)) options.push({
2274
+ label: key,
2275
+ value: fullPath,
2276
+ type: "object",
2277
+ children: buildFieldOptions(value, fullPath)
2278
+ });
2279
+ else options.push({
2280
+ label: key,
2281
+ value: fullPath,
2282
+ type: typeof value
2283
+ });
2284
+ }
2285
+ return options;
2286
+ }
2287
+ const logicalOperatorOptions = [
2288
+ {
2289
+ label: "且",
2290
+ value: "and"
2291
+ },
2292
+ {
2293
+ label: "或",
2294
+ value: "or"
2295
+ },
2296
+ {
2297
+ label: "非",
2298
+ value: "not"
2299
+ }
2300
+ ];
2301
+ const currentOperator = (0, vue.computed)(() => props.node.operator ?? "and");
2302
+ function getDetailedType(val) {
2303
+ if (val === null || val === void 0) return "null";
2304
+ if (Array.isArray(val)) return "array";
2305
+ const t = typeof val;
2306
+ if (t === "object") return "object";
2307
+ return t;
2308
+ }
2309
+ const leftType = (0, vue.ref)(null);
2310
+ (0, vue.watchEffect)(() => {
2311
+ if (props.node.type !== "condition" || !props.node.left) {
2312
+ leftType.value = null;
2313
+ return;
2314
+ }
2315
+ const exprStr = formatExpression(props.node.left);
2316
+ if (!exprStr) {
2317
+ leftType.value = null;
2318
+ return;
2319
+ }
2320
+ try {
2321
+ spelService.setContext(props.authentication, props.principal);
2322
+ leftType.value = getDetailedType(spelService.eval(exprStr, props.locals));
2323
+ } catch {
2324
+ leftType.value = null;
2325
+ }
2326
+ });
2327
+ function getComparatorsForType(type) {
2328
+ const base = [
2329
+ {
2330
+ label: "==",
2331
+ value: "=="
2332
+ },
2333
+ {
2334
+ label: "!=",
2335
+ value: "!="
2336
+ },
2337
+ {
2338
+ label: "isNull",
2339
+ value: "isNull"
2340
+ },
2341
+ {
2342
+ label: "isNotNull",
2343
+ value: "isNotNull"
2344
+ }
2345
+ ];
2346
+ if (!type) return [
2347
+ ...base,
2348
+ {
2349
+ label: ">",
2350
+ value: ">"
2351
+ },
2352
+ {
2353
+ label: ">=",
2354
+ value: ">="
2355
+ },
2356
+ {
2357
+ label: "<",
2358
+ value: "<"
2359
+ },
2360
+ {
2361
+ label: "<=",
2362
+ value: "<="
2363
+ },
2364
+ {
2365
+ label: "isEmpty",
2366
+ value: "isEmpty"
2367
+ },
2368
+ {
2369
+ label: "isNotEmpty",
2370
+ value: "isNotEmpty"
2371
+ }
2372
+ ];
2373
+ switch (type) {
2374
+ case "number": return [
2375
+ ...base,
2376
+ {
2377
+ label: ">",
2378
+ value: ">"
2379
+ },
2380
+ {
2381
+ label: ">=",
2382
+ value: ">="
2383
+ },
2384
+ {
2385
+ label: "<",
2386
+ value: "<"
2387
+ },
2388
+ {
2389
+ label: "<=",
2390
+ value: "<="
2391
+ }
2392
+ ];
2393
+ case "string": return [
2394
+ ...base,
2395
+ {
2396
+ label: "isEmpty",
2397
+ value: "isEmpty"
2398
+ },
2399
+ {
2400
+ label: "isNotEmpty",
2401
+ value: "isNotEmpty"
2402
+ }
2403
+ ];
2404
+ case "boolean": return base;
2405
+ case "array": return [
2406
+ {
2407
+ label: "count ==",
2408
+ value: "count =="
2409
+ },
2410
+ {
2411
+ label: "count !=",
2412
+ value: "count !="
2413
+ },
2414
+ {
2415
+ label: "count <",
2416
+ value: "count <"
2417
+ },
2418
+ {
2419
+ label: "count <=",
2420
+ value: "count <="
2421
+ },
2422
+ {
2423
+ label: "count >",
2424
+ value: "count >"
2425
+ },
2426
+ {
2427
+ label: "count >=",
2428
+ value: "count >="
2429
+ },
2430
+ ...base
2431
+ ];
2432
+ case "object":
2433
+ case "null": return [
2434
+ ...base,
2435
+ {
2436
+ label: "isEmpty",
2437
+ value: "isEmpty"
2438
+ },
2439
+ {
2440
+ label: "isNotEmpty",
2441
+ value: "isNotEmpty"
2442
+ }
2443
+ ];
2444
+ default: return base;
2445
+ }
2446
+ }
2447
+ const availableComparators = (0, vue.computed)(() => getComparatorsForType(leftType.value));
2448
+ (0, vue.watchEffect)(() => {
2449
+ if (props.node.type !== "condition") return;
2450
+ const allowed = availableComparators.value.map((c) => c.value);
2451
+ if (props.node.comparator && !allowed.includes(props.node.comparator)) handleUpdate({ comparator: "" });
2452
+ });
2453
+ function findFieldOption(path, options) {
2454
+ for (const opt of options) {
2455
+ if (opt.value === path) return opt;
2456
+ if (opt.children) {
2457
+ const found = findFieldOption(path, opt.children);
2458
+ if (found) return found;
2459
+ }
2460
+ }
2461
+ return null;
2462
+ }
2463
+ const currentArrayFieldOption = (0, vue.computed)(() => {
2464
+ if (props.node.type !== "condition" || props.node.left?.type !== "field") return null;
2465
+ return findFieldOption(props.node.left.path, fieldOptions.value);
2466
+ });
2467
+ const listElementType = (0, vue.computed)(() => {
2468
+ const opt = currentArrayFieldOption.value;
2469
+ if (opt?.type === "array") return opt.elementType ?? "number";
2470
+ return null;
2471
+ });
2472
+ function findElementFieldType(path, children) {
2473
+ for (const child of children) {
2474
+ if (child.value === path) return child.type;
2475
+ if (child.children) {
2476
+ const found = findElementFieldType(path, child.children);
2477
+ if (found) return found;
2478
+ }
2479
+ }
2480
+ }
2481
+ const selectedElementFieldType = (0, vue.computed)(() => {
2482
+ const opt = currentArrayFieldOption.value;
2483
+ const fieldPath = props.node.listFilter?.fieldPath;
2484
+ if (!opt || !opt.elementChildren || !fieldPath) return void 0;
2485
+ return findElementFieldType(fieldPath, opt.elementChildren);
2486
+ });
2487
+ const listFilterComparators = (0, vue.computed)(() => {
2488
+ if (listElementType.value === "object") {
2489
+ const fieldType = selectedElementFieldType.value;
2490
+ if (!fieldType) return [];
2491
+ return getComparatorsForType(fieldType);
2492
+ }
2493
+ return getComparatorsForType(listElementType.value);
2494
+ });
2495
+ const hasFilterValue = (0, vue.computed)(() => {
2496
+ const comp = props.node.listFilter?.comparator;
2497
+ if (!comp) return false;
2498
+ return ![
2499
+ "isEmpty",
2500
+ "isNotEmpty",
2501
+ "isNull",
2502
+ "isNotNull"
2503
+ ].includes(comp);
2504
+ });
2505
+ (0, vue.watch)(() => props.node.left, () => {
2506
+ if (props.node.listFilter) handleUpdate({ listFilter: void 0 });
2507
+ });
2508
+ function updateListFilter(partial) {
2509
+ handleUpdate({ listFilter: {
2510
+ ...props.node.listFilter || { comparator: "" },
2511
+ ...partial
2512
+ } });
2513
+ }
2514
+ function updateListFilterField(fieldPath) {
2515
+ updateListFilter({
2516
+ fieldPath,
2517
+ comparator: ""
2518
+ });
2519
+ }
2520
+ function updateListFilterComparator(comp) {
2521
+ const needsValue = ![
2522
+ "isEmpty",
2523
+ "isNotEmpty",
2524
+ "isNull",
2525
+ "isNotNull"
2526
+ ].includes(comp);
2527
+ const partial = { comparator: comp };
2528
+ if (!needsValue) partial.value = void 0;
2529
+ else if (!props.node.listFilter?.value) partial.value = {
2530
+ type: "literal",
2531
+ value: "",
2532
+ literalType: listElementType.value === "object" ? selectedElementFieldType.value === "number" ? "number" : "string" : listElementType.value === "number" ? "number" : "string"
2533
+ };
2534
+ updateListFilter(partial);
2535
+ }
2536
+ function updateListFilterValue(val) {
2537
+ const valueType = listElementType.value === "object" ? selectedElementFieldType.value === "number" ? "number" : "string" : listElementType.value === "number" ? "number" : "string";
2538
+ updateListFilter({ value: {
2539
+ type: "literal",
2540
+ value: String(val ?? ""),
2541
+ literalType: valueType
2542
+ } });
2543
+ }
2544
+ const isCountOperator = (0, vue.computed)(() => props.node.comparator?.startsWith("count ") ?? false);
2545
+ const hasValueInput = (0, vue.computed)(() => {
2546
+ return !!props.node.comparator && ![
2547
+ "isEmpty",
2548
+ "isNotEmpty",
2549
+ "isNull",
2550
+ "isNotNull"
2551
+ ].includes(props.node.comparator);
2552
+ });
2553
+ const isConfigured = (0, vue.computed)(() => {
2554
+ if (props.node.type !== "condition") return false;
2555
+ return !!(props.node.left && props.node.comparator);
2556
+ });
2557
+ const summaryExpression = (0, vue.computed)(() => {
2558
+ if (props.node.type !== "condition") return {
2559
+ left: "",
2560
+ op: "",
2561
+ right: ""
2562
+ };
2563
+ let leftStr = formatExpression(props.node.left);
2564
+ if (props.node.listFilter && props.node.listFilter.comparator) {
2565
+ const { comparator, fieldPath, value } = props.node.listFilter;
2566
+ let target = "#this";
2567
+ if (fieldPath) target = `#this.${fieldPath}`;
2568
+ switch (comparator) {
2569
+ case "isEmpty":
2570
+ leftStr = `${leftStr}.?[${target} == null || ${target}.isEmpty()]`;
2571
+ break;
2572
+ case "isNotEmpty":
2573
+ leftStr = `${leftStr}.?[${target} != null && !${target}.isEmpty()]`;
2574
+ break;
2575
+ case "isNull":
2576
+ leftStr = `${leftStr}.?[${target} == null]`;
2577
+ break;
2578
+ case "isNotNull":
2579
+ leftStr = `${leftStr}.?[${target} != null]`;
2580
+ break;
2581
+ default: {
2582
+ const filterVal = value ? formatExpression(value) : "";
2583
+ leftStr = `${leftStr}.?[${target} ${comparator} ${filterVal}]`;
2584
+ break;
2585
+ }
2586
+ }
2587
+ }
2588
+ const op = props.node.comparator ?? "";
2589
+ if (op.startsWith("count ")) leftStr = `${leftStr}.size()`;
2590
+ return {
2591
+ left: leftStr,
2592
+ op,
2593
+ right: props.node.right ? formatExpression(props.node.right) : ""
2594
+ };
2595
+ });
2596
+ function handleOperatorChange(value) {
2597
+ handleUpdate({ operator: value });
2598
+ }
2599
+ function updateLeft(expr) {
2600
+ handleUpdate({ left: expr });
2601
+ }
2602
+ function updateRight(expr) {
2603
+ handleUpdate({ right: expr });
2604
+ }
2605
+ function updateComparator(value) {
2606
+ handleUpdate({ comparator: value });
2607
+ }
2608
+ function toggleEdit() {
2609
+ if (!props.disabled) isEditing.value = !isEditing.value;
2610
+ }
2611
+ function closeEdit() {
2612
+ isEditing.value = false;
2613
+ }
2614
+ const vClickOutside = {
2615
+ mounted(el, binding) {
2616
+ const handler = (event) => {
2617
+ const target = event.target;
2618
+ if (target.closest(".n-cascader-menu") || target.closest(".n-popover-shared") || target.closest(".v-binder-follower-content")) return;
2619
+ if (!el.contains(target) && el !== target) binding.value(event);
2620
+ };
2621
+ el.__clickOutsideHandler = handler;
2622
+ document.addEventListener("click", handler, true);
2623
+ },
2624
+ unmounted(el) {
2625
+ document.removeEventListener("click", el.__clickOutsideHandler, true);
2626
+ delete el.__clickOutsideHandler;
2627
+ }
2628
+ };
2629
+ const listFilterLiteralValue = (0, vue.computed)(() => {
2630
+ const val = props.node.listFilter?.value;
2631
+ if (val && val.type === "literal") return val.value;
2632
+ return "";
2633
+ });
2634
+ return (_ctx, _cache) => {
2635
+ const _component_n_button = (0, vue.resolveComponent)("n-button");
2636
+ const _component_n_select = (0, vue.resolveComponent)("n-select");
2637
+ const _component_n_input_number = (0, vue.resolveComponent)("n-input-number");
2638
+ const _component_n_input = (0, vue.resolveComponent)("n-input");
2639
+ const _component_n_cascader = (0, vue.resolveComponent)("n-cascader");
2640
+ const _component_RuleTreeNode = (0, vue.resolveComponent)("RuleTreeNode", true);
2641
+ return __props.node.type === "condition" ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2642
+ key: 0,
2643
+ class: (0, vue.normalizeClass)(["rounded-md border overflow-hidden transition-all duration-150", [
2644
+ `theme--${props.theme}`,
2645
+ `size--${__props.size}`,
2646
+ isEditing.value ? "is-editing" : "is-idle",
2647
+ __props.disabled ? "opacity-60 pointer-events-none" : ""
2648
+ ]])
2649
+ }, [(0, vue.createElementVNode)("div", {
2650
+ class: "group/row flex items-center gap-1.5 px-2.5 min-h-[34px] cursor-pointer select-none overflow-hidden",
2651
+ onClick: toggleEdit
2652
+ }, [
2653
+ _cache[10] || (_cache[10] = (0, vue.createElementVNode)("span", { class: "i-carbon:rule text-[11px] flex-shrink-0 icon-muted" }, null, -1)),
2654
+ isConfigured.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [
2655
+ (0, vue.createElementVNode)("span", _hoisted_1, (0, vue.toDisplayString)(summaryExpression.value.left || "?"), 1),
2656
+ summaryExpression.value.op ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_2, (0, vue.toDisplayString)(summaryExpression.value.op), 1)) : (0, vue.createCommentVNode)("", true),
2657
+ summaryExpression.value.right ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3, (0, vue.toDisplayString)(summaryExpression.value.right), 1)) : hasValueInput.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_4, "···")) : (0, vue.createCommentVNode)("", true)
2658
+ ], 64)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5, "点击配置条件…")),
2659
+ (0, vue.createVNode)(_component_n_button, {
2660
+ class: "!ml-auto opacity-0 group-hover/row:opacity-100 transition-opacity duration-100 flex-shrink-0",
2661
+ text: "",
2662
+ size: __props.size,
2663
+ type: "error",
2664
+ disabled: __props.disabled,
2665
+ onClick: (0, vue.withModifiers)(handleRemove, ["stop"])
2666
+ }, {
2667
+ icon: (0, vue.withCtx)(() => [..._cache[9] || (_cache[9] = [(0, vue.createElementVNode)("span", { class: "i-carbon:close text-xs" }, null, -1)])]),
2668
+ _: 1
2669
+ }, 8, ["size", "disabled"])
2670
+ ]), (0, vue.createVNode)(vue.Transition, { name: "slide-down" }, {
2671
+ default: (0, vue.withCtx)(() => [isEditing.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2672
+ key: 0,
2673
+ class: "edit-panel border-t px-3 py-3 space-y-2",
2674
+ onClick: _cache[4] || (_cache[4] = (0, vue.withModifiers)(() => {}, ["stop"]))
2675
+ }, [
2676
+ (0, vue.createElementVNode)("div", _hoisted_6, [
2677
+ (0, vue.createElementVNode)("div", _hoisted_7, [_cache[11] || (_cache[11] = (0, vue.createElementVNode)("div", { class: "expr-block__label" }, "左侧", -1)), (0, vue.createElementVNode)("div", _hoisted_8, [(0, vue.createVNode)(ExpressionEditor_default, {
2678
+ "model-value": __props.node.left ?? {
2679
+ type: "field",
2680
+ path: ""
2681
+ },
2682
+ "field-options": fieldOptions.value,
2683
+ disabled: __props.disabled,
2684
+ "allow-literal": false,
2685
+ size: __props.size,
2686
+ "onUpdate:modelValue": updateLeft
2687
+ }, null, 8, [
2688
+ "model-value",
2689
+ "field-options",
2690
+ "disabled",
2691
+ "size"
2692
+ ])])]),
2693
+ (0, vue.createElementVNode)("div", _hoisted_9, [_cache[12] || (_cache[12] = (0, vue.createElementVNode)("div", { class: "expr-block__label" }, "操作符", -1)), (0, vue.createElementVNode)("div", _hoisted_10, [(0, vue.createVNode)(_component_n_select, {
2694
+ value: __props.node.comparator,
2695
+ options: availableComparators.value,
2696
+ placeholder: "…",
2697
+ disabled: __props.disabled,
2698
+ size: __props.size,
2699
+ class: "!w-[108px]",
2700
+ "onUpdate:value": updateComparator
2701
+ }, null, 8, [
2702
+ "value",
2703
+ "options",
2704
+ "disabled",
2705
+ "size"
2706
+ ])])]),
2707
+ hasValueInput.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_11, [_cache[13] || (_cache[13] = (0, vue.createElementVNode)("div", { class: "expr-block__label" }, "右侧", -1)), (0, vue.createElementVNode)("div", _hoisted_12, [isCountOperator.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(ExpressionEditor_default, {
2708
+ key: 0,
2709
+ "model-value": __props.node.right ?? {
2710
+ type: "literal",
2711
+ value: "",
2712
+ literalType: "number"
2713
+ },
2714
+ "force-number-input": true,
2715
+ disabled: __props.disabled,
2716
+ size: __props.size,
2717
+ "onUpdate:modelValue": updateRight
2718
+ }, null, 8, [
2719
+ "model-value",
2720
+ "disabled",
2721
+ "size"
2722
+ ])) : ((0, vue.openBlock)(), (0, vue.createBlock)(ExpressionEditor_default, {
2723
+ key: 1,
2724
+ "model-value": __props.node.right ?? {
2725
+ type: "literal",
2726
+ value: "",
2727
+ literalType: "string"
2728
+ },
2729
+ "field-options": fieldOptions.value,
2730
+ disabled: __props.disabled,
2731
+ "allow-literal": true,
2732
+ size: __props.size,
2733
+ "onUpdate:modelValue": updateRight
2734
+ }, null, 8, [
2735
+ "model-value",
2736
+ "field-options",
2737
+ "disabled",
2738
+ "size"
2739
+ ]))])])) : (0, vue.createCommentVNode)("", true)
2740
+ ]),
2741
+ leftType.value === "array" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_13, [(0, vue.createElementVNode)("div", _hoisted_14, [_cache[16] || (_cache[16] = (0, vue.createElementVNode)("div", { class: "expr-block__label" }, "列表过滤", -1)), (0, vue.createElementVNode)("div", _hoisted_15, [listElementType.value !== "object" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [
2742
+ _cache[14] || (_cache[14] = (0, vue.createElementVNode)("span", { class: "text-xs mr-1 text-secondary" }, "#this", -1)),
2743
+ (0, vue.createVNode)(_component_n_select, {
2744
+ value: __props.node.listFilter?.comparator ?? "",
2745
+ options: listFilterComparators.value,
2746
+ placeholder: "操作符",
2747
+ size: __props.size,
2748
+ class: "!w-[80px]",
2749
+ disabled: __props.disabled,
2750
+ "onUpdate:value": updateListFilterComparator
2751
+ }, null, 8, [
2752
+ "value",
2753
+ "options",
2754
+ "size",
2755
+ "disabled"
2756
+ ]),
2757
+ hasFilterValue.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [listElementType.value === "number" ? ((0, vue.openBlock)(), (0, vue.createBlock)(_component_n_input_number, {
2758
+ key: 0,
2759
+ value: listFilterLiteralValue.value !== "" ? Number(listFilterLiteralValue.value) : null,
2760
+ size: __props.size,
2761
+ class: "!w-[100px]",
2762
+ disabled: __props.disabled,
2763
+ "onUpdate:value": _cache[0] || (_cache[0] = (v) => updateListFilterValue(v ?? 0))
2764
+ }, null, 8, [
2765
+ "value",
2766
+ "size",
2767
+ "disabled"
2768
+ ])) : ((0, vue.openBlock)(), (0, vue.createBlock)(_component_n_input, {
2769
+ key: 1,
2770
+ value: listFilterLiteralValue.value,
2771
+ size: __props.size,
2772
+ class: "!w-[120px]",
2773
+ disabled: __props.disabled,
2774
+ placeholder: "文本…",
2775
+ "onUpdate:value": _cache[1] || (_cache[1] = (v) => updateListFilterValue(v))
2776
+ }, null, 8, [
2777
+ "value",
2778
+ "size",
2779
+ "disabled"
2780
+ ]))], 64)) : (0, vue.createCommentVNode)("", true)
2781
+ ], 64)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [
2782
+ _cache[15] || (_cache[15] = (0, vue.createElementVNode)("span", { class: "text-xs mr-1 text-secondary" }, "#this.", -1)),
2783
+ (0, vue.createVNode)(_component_n_cascader, {
2784
+ value: __props.node.listFilter?.fieldPath,
2785
+ options: currentArrayFieldOption.value?.elementChildren ?? [],
2786
+ placeholder: "选择字段",
2787
+ size: __props.size,
2788
+ class: "!w-[140px]",
2789
+ disabled: __props.disabled,
2790
+ "check-strategy": "child",
2791
+ "onUpdate:value": updateListFilterField
2792
+ }, null, 8, [
2793
+ "value",
2794
+ "options",
2795
+ "size",
2796
+ "disabled"
2797
+ ]),
2798
+ selectedElementFieldType.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(_component_n_select, {
2799
+ key: 0,
2800
+ value: __props.node.listFilter?.comparator ?? "",
2801
+ options: listFilterComparators.value,
2802
+ placeholder: "操作符",
2803
+ size: __props.size,
2804
+ class: "!w-[80px]",
2805
+ disabled: __props.disabled,
2806
+ "onUpdate:value": updateListFilterComparator
2807
+ }, null, 8, [
2808
+ "value",
2809
+ "options",
2810
+ "size",
2811
+ "disabled"
2812
+ ])) : (0, vue.createCommentVNode)("", true),
2813
+ hasFilterValue.value && selectedElementFieldType.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [selectedElementFieldType.value === "number" ? ((0, vue.openBlock)(), (0, vue.createBlock)(_component_n_input_number, {
2814
+ key: 0,
2815
+ value: listFilterLiteralValue.value !== "" ? Number(listFilterLiteralValue.value) : null,
2816
+ size: __props.size,
2817
+ class: "!w-[100px]",
2818
+ disabled: __props.disabled,
2819
+ "onUpdate:value": _cache[2] || (_cache[2] = (v) => updateListFilterValue(v ?? 0))
2820
+ }, null, 8, [
2821
+ "value",
2822
+ "size",
2823
+ "disabled"
2824
+ ])) : ((0, vue.openBlock)(), (0, vue.createBlock)(_component_n_input, {
2825
+ key: 1,
2826
+ value: listFilterLiteralValue.value,
2827
+ size: __props.size,
2828
+ class: "!w-[120px]",
2829
+ disabled: __props.disabled,
2830
+ placeholder: "文本…",
2831
+ "onUpdate:value": _cache[3] || (_cache[3] = (v) => updateListFilterValue(v))
2832
+ }, null, 8, [
2833
+ "value",
2834
+ "size",
2835
+ "disabled"
2836
+ ]))], 64)) : (0, vue.createCommentVNode)("", true)
2837
+ ], 64))])])])) : (0, vue.createCommentVNode)("", true),
2838
+ isConfigured.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_16, [_cache[17] || (_cache[17] = (0, vue.createElementVNode)("span", { class: "i-carbon:code text-xs flex-shrink-0 icon-muted" }, null, -1)), (0, vue.createElementVNode)("code", _hoisted_17, [
2839
+ (0, vue.createElementVNode)("span", _hoisted_18, (0, vue.toDisplayString)(summaryExpression.value.left || "…"), 1),
2840
+ summaryExpression.value.op ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_19, (0, vue.toDisplayString)(summaryExpression.value.op), 1)) : (0, vue.createCommentVNode)("", true),
2841
+ summaryExpression.value.right ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_20, (0, vue.toDisplayString)(summaryExpression.value.right), 1)) : (0, vue.createCommentVNode)("", true)
2842
+ ])])) : (0, vue.createCommentVNode)("", true)
2843
+ ])) : (0, vue.createCommentVNode)("", true)]),
2844
+ _: 1
2845
+ })], 2)), [[vClickOutside, closeEdit]]) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2846
+ key: 1,
2847
+ class: (0, vue.normalizeClass)(["relative group-root", [
2848
+ `theme--${props.theme}`,
2849
+ `size--${__props.size}`,
2850
+ (0, vue.unref)(level) > 0 ? "ml-4" : ""
2851
+ ]])
2852
+ }, [
2853
+ (0, vue.unref)(level) > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_21)) : (0, vue.createCommentVNode)("", true),
2854
+ (0, vue.createElementVNode)("div", _hoisted_22, [
2855
+ (0, vue.createElementVNode)("div", _hoisted_23, [((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(logicalOperatorOptions, (opt) => {
2856
+ return (0, vue.createElementVNode)("button", {
2857
+ key: opt.value,
2858
+ class: (0, vue.normalizeClass)(["op-toggle__btn", {
2859
+ "op-toggle__btn--and": currentOperator.value === "and" && opt.value === "and",
2860
+ "op-toggle__btn--or": currentOperator.value === "or" && opt.value === "or",
2861
+ "op-toggle__btn--not": currentOperator.value === "not" && opt.value === "not",
2862
+ "op-toggle__btn--off": currentOperator.value !== opt.value
2863
+ }]),
2864
+ disabled: __props.disabled,
2865
+ onClick: ($event) => handleOperatorChange(opt.value)
2866
+ }, (0, vue.toDisplayString)(opt.label), 11, _hoisted_24);
2867
+ }), 64))]),
2868
+ _cache[24] || (_cache[24] = (0, vue.createElementVNode)("div", { class: "flex-1" }, null, -1)),
2869
+ (0, vue.createElementVNode)("div", _hoisted_25, [
2870
+ (0, vue.createVNode)(_component_n_button, {
2871
+ class: "action-btn",
2872
+ size: __props.size,
2873
+ disabled: __props.disabled,
2874
+ onClick: handleAddCondition
2875
+ }, {
2876
+ icon: (0, vue.withCtx)(() => [..._cache[18] || (_cache[18] = [(0, vue.createElementVNode)("span", { class: "i-carbon:add text-xs" }, null, -1)])]),
2877
+ default: (0, vue.withCtx)(() => [_cache[19] || (_cache[19] = (0, vue.createTextVNode)("条件", -1))]),
2878
+ _: 1
2879
+ }, 8, ["size", "disabled"]),
2880
+ (0, vue.createVNode)(_component_n_button, {
2881
+ class: "action-btn",
2882
+ size: __props.size,
2883
+ disabled: __props.disabled,
2884
+ onClick: handleAddGroup
2885
+ }, {
2886
+ icon: (0, vue.withCtx)(() => [..._cache[20] || (_cache[20] = [(0, vue.createElementVNode)("span", { class: "i-carbon:folder-add text-xs" }, null, -1)])]),
2887
+ default: (0, vue.withCtx)(() => [_cache[21] || (_cache[21] = (0, vue.createTextVNode)("分组", -1))]),
2888
+ _: 1
2889
+ }, 8, ["size", "disabled"]),
2890
+ (0, vue.unref)(level) > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [_cache[23] || (_cache[23] = (0, vue.createElementVNode)("div", { class: "w-px h-4 mx-0.5 divider" }, null, -1)), (0, vue.createVNode)(_component_n_button, {
2891
+ size: __props.size,
2892
+ quaternary: "",
2893
+ circle: "",
2894
+ type: "error",
2895
+ disabled: __props.disabled,
2896
+ onClick: handleRemove
2897
+ }, {
2898
+ icon: (0, vue.withCtx)(() => [..._cache[22] || (_cache[22] = [(0, vue.createElementVNode)("span", { class: "i-carbon:trash-can text-sm" }, null, -1)])]),
2899
+ _: 1
2900
+ }, 8, ["size", "disabled"])], 64)) : (0, vue.createCommentVNode)("", true)
2901
+ ])
2902
+ ]),
2903
+ (0, vue.createElementVNode)("div", _hoisted_26, [__props.node.children && __props.node.children.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_27)) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("div", _hoisted_28, [__props.node.children && __props.node.children.length > 0 ? ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, (0, vue.renderList)(__props.node.children, (child) => {
2904
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2905
+ key: child.id,
2906
+ class: "relative"
2907
+ }, [_cache[25] || (_cache[25] = (0, vue.createElementVNode)("div", { class: "group-hline" }, null, -1)), (0, vue.createVNode)(_component_RuleTreeNode, {
2908
+ node: child,
2909
+ authentication: __props.authentication,
2910
+ principal: __props.principal,
2911
+ locals: __props.locals,
2912
+ disabled: __props.disabled,
2913
+ level: (0, vue.unref)(level) + 1,
2914
+ theme: props.theme,
2915
+ size: __props.size,
2916
+ onAddCondition: _cache[5] || (_cache[5] = (id) => _ctx.$emit("add-condition", id)),
2917
+ onAddGroup: _cache[6] || (_cache[6] = (id) => _ctx.$emit("add-group", id)),
2918
+ onRemoveNode: _cache[7] || (_cache[7] = (id) => _ctx.$emit("remove-node", id)),
2919
+ onUpdateNode: _cache[8] || (_cache[8] = (id, updates) => _ctx.$emit("update-node", id, updates))
2920
+ }, null, 8, [
2921
+ "node",
2922
+ "authentication",
2923
+ "principal",
2924
+ "locals",
2925
+ "disabled",
2926
+ "level",
2927
+ "theme",
2928
+ "size"
2929
+ ])]);
2930
+ }), 128)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_29, [..._cache[26] || (_cache[26] = [(0, vue.createElementVNode)("span", { class: "i-carbon:add-alt text-xl mb-1 icon-muted" }, null, -1), (0, vue.createElementVNode)("p", { class: "text-[11px] text-placeholder" }, "暂无条件,点击右上方按钮添加", -1)])]))])])
2931
+ ], 2));
2932
+ };
2933
+ }
2934
+ }), [["__scopeId", "data-v-41249d75"]]);
2935
+ //#endregion
2936
+ //#region src/components/RuleTree/RuleTree.vue
2937
+ var RuleTree_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ (0, vue.defineComponent)({
2938
+ __name: "RuleTree",
2939
+ props: {
2940
+ modelValue: {},
2941
+ authentication: {},
2942
+ principal: {},
2943
+ locals: {},
2944
+ disabled: { type: Boolean },
2945
+ theme: { default: "light" },
2946
+ size: { default: "small" }
2947
+ },
2948
+ emits: ["update:modelValue", "change"],
2949
+ setup(__props, { expose: __expose, emit: __emit }) {
2950
+ const props = __props;
2951
+ const { getSpelExpression, setSpelExpression, addCondition, addGroup, removeNode, updateNode, validate } = useRuleTree(props, __emit);
2952
+ __expose({
2953
+ getSpelExpression,
2954
+ setSpelExpression,
2955
+ validate
2956
+ });
2957
+ const currentTheme = (0, vue.computed)(() => props.theme === "dark" ? naive_ui.darkTheme : naive_ui.lightTheme);
2958
+ return (_ctx, _cache) => {
2959
+ return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(naive_ui.NConfigProvider), { theme: currentTheme.value }, {
2960
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", { class: (0, vue.normalizeClass)(["rule-tree-container", [`theme--${props.theme}`, `size--${props.size}`]]) }, [(0, vue.createVNode)(RuleTreeNode_default, {
2961
+ node: __props.modelValue,
2962
+ authentication: __props.authentication,
2963
+ principal: __props.principal,
2964
+ locals: __props.locals,
2965
+ disabled: __props.disabled,
2966
+ level: 0,
2967
+ theme: props.theme,
2968
+ size: props.size,
2969
+ onAddCondition: (0, vue.unref)(addCondition),
2970
+ onAddGroup: (0, vue.unref)(addGroup),
2971
+ onRemoveNode: (0, vue.unref)(removeNode),
2972
+ onUpdateNode: (0, vue.unref)(updateNode)
2973
+ }, null, 8, [
2974
+ "node",
2975
+ "authentication",
2976
+ "principal",
2977
+ "locals",
2978
+ "disabled",
2979
+ "theme",
2980
+ "size",
2981
+ "onAddCondition",
2982
+ "onAddGroup",
2983
+ "onRemoveNode",
2984
+ "onUpdateNode"
2985
+ ])], 2)]),
2986
+ _: 1
2987
+ }, 8, ["theme"]);
2988
+ };
2989
+ }
2990
+ }), [["__scopeId", "data-v-6f5f2d8f"]]);
2991
+ //#endregion
2992
+ //#region src/constants/index.ts
2993
+ var DEFAULT_COMPARATORS = [
2994
+ {
2995
+ value: "==",
2996
+ label: "等于",
2997
+ types: [
2998
+ "string",
2999
+ "number",
3000
+ "boolean",
3001
+ "date"
3002
+ ]
3003
+ },
3004
+ {
3005
+ value: "!=",
3006
+ label: "不等于",
3007
+ types: [
3008
+ "string",
3009
+ "number",
3010
+ "boolean",
3011
+ "date"
3012
+ ]
3013
+ },
3014
+ {
3015
+ value: ">",
3016
+ label: "大于",
3017
+ types: ["number", "date"]
3018
+ },
3019
+ {
3020
+ value: ">=",
3021
+ label: "大于等于",
3022
+ types: ["number", "date"]
3023
+ },
3024
+ {
3025
+ value: "<",
3026
+ label: "小于",
3027
+ types: ["number", "date"]
3028
+ },
3029
+ {
3030
+ value: "<=",
3031
+ label: "小于等于",
3032
+ types: ["number", "date"]
3033
+ },
3034
+ {
3035
+ value: "contains",
3036
+ label: "包含",
3037
+ types: ["string"]
3038
+ },
3039
+ {
3040
+ value: "startsWith",
3041
+ label: "开头",
3042
+ types: ["string"]
3043
+ },
3044
+ {
3045
+ value: "endsWith",
3046
+ label: "结尾",
3047
+ types: ["string"]
3048
+ },
3049
+ {
3050
+ value: "matches",
3051
+ label: "正则匹配",
3052
+ types: ["string"]
3053
+ },
3054
+ {
3055
+ value: "in",
3056
+ label: "在列表中",
3057
+ types: ["string", "number"]
3058
+ },
3059
+ {
3060
+ value: "not in",
3061
+ label: "不在列表中",
3062
+ types: ["string", "number"]
3063
+ },
3064
+ {
3065
+ value: "isEmpty",
3066
+ label: "为空",
3067
+ types: [
3068
+ "string",
3069
+ "array",
3070
+ "object"
3071
+ ]
3072
+ },
3073
+ {
3074
+ value: "isNotEmpty",
3075
+ label: "不为空",
3076
+ types: [
3077
+ "string",
3078
+ "array",
3079
+ "object"
3080
+ ]
3081
+ }
3082
+ ];
3083
+ var GROUP_OPERATORS = [{
3084
+ value: "and",
3085
+ label: "且"
3086
+ }, {
3087
+ value: "or",
3088
+ label: "或"
3089
+ }];
3090
+ var EDITOR_THEME = "one-dark";
3091
+ var EDITOR_DEFAULT_HEIGHT = "400px";
3092
+ var RULE_TREE_DEFAULT_HEIGHT = "500px";
3093
+ //#endregion
3094
+ exports.DEFAULT_COMPARATORS = DEFAULT_COMPARATORS;
3095
+ exports.EDITOR_DEFAULT_HEIGHT = EDITOR_DEFAULT_HEIGHT;
3096
+ exports.EDITOR_THEME = EDITOR_THEME;
3097
+ exports.GROUP_OPERATORS = GROUP_OPERATORS;
3098
+ exports.RULE_TREE_DEFAULT_HEIGHT = RULE_TREE_DEFAULT_HEIGHT;
3099
+ exports.RuleTree = RuleTree_default;
3100
+ exports.SpelEditor = SpelEditor_default;
3101
+ exports.createEmptyCondition = createEmptyCondition;
3102
+ exports.createEmptyGroup = createEmptyGroup;
3103
+ exports.evalSpelExpression = evalSpelExpression;
3104
+ exports.generateId = generateId;
3105
+ exports.ruleNodeToSpel = ruleNodeToSpel;
3106
+ exports.spelService = spelService;
3107
+ exports.useRuleTree = useRuleTree;
3108
+ exports.useSpelEditor = useSpelEditor;
3109
+ exports.validateSpelExpression = validateSpelExpression;
3110
+
3111
+ //# sourceMappingURL=index.cjs.map