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