@veltra/desktop 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/dist/components/action/action-group.js +3 -110
  2. package/dist/components/action/action-group.js.map +1 -1
  3. package/dist/components/action/action-group.vue.d.ts +5 -14
  4. package/dist/components/action/action-group.vue_vue_type_script_setup_true_lang.js +105 -0
  5. package/dist/components/action/action-group.vue_vue_type_script_setup_true_lang.js.map +1 -0
  6. package/dist/components/action/action.js +5 -1
  7. package/dist/components/action/action.js.map +1 -1
  8. package/dist/components/action/di.js.map +1 -1
  9. package/dist/components/action/style2.css +17 -14
  10. package/dist/components/auto-complete/auto-complete.js +1 -1
  11. package/dist/components/auto-complete/auto-complete.vue.d.ts +2 -2
  12. package/dist/components/badge/badge.js +1 -1
  13. package/dist/components/batch-edit/batch-edit-form.js +94 -101
  14. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  15. package/dist/components/batch-edit/batch-edit-list.js +52 -68
  16. package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
  17. package/dist/components/batch-edit/style2.css +13 -89
  18. package/dist/components/breadcrumb/breadcrumb.js +1 -1
  19. package/dist/components/button/button.js +1 -1
  20. package/dist/components/card/card.js +1 -1
  21. package/dist/components/cascade/cascade-panel-item.js +4 -4
  22. package/dist/components/cascade/cascade.js +2 -2
  23. package/dist/components/cascade/cascade.vue.d.ts +5 -5
  24. package/dist/components/checkbox/checkbox-button.js +1 -1
  25. package/dist/components/checkbox/checkbox.js +1 -1
  26. package/dist/components/checkbox-group/checkbox-group.js +1 -1
  27. package/dist/components/code-editor/code-editor.js +1 -1
  28. package/dist/components/collapse/collapse.js +1 -1
  29. package/dist/components/condition-editor/components/condition-group.js +152 -0
  30. package/dist/components/condition-editor/components/condition-group.js.map +1 -0
  31. package/dist/components/condition-editor/components/condition-row.js +217 -0
  32. package/dist/components/condition-editor/components/condition-row.js.map +1 -0
  33. package/dist/components/condition-editor/condition-editor.js +134 -5
  34. package/dist/components/condition-editor/condition-editor.js.map +1 -1
  35. package/dist/components/condition-editor/condition-editor.vue.d.ts +10 -2
  36. package/dist/components/condition-editor/core/evaluator.js +75 -0
  37. package/dist/components/condition-editor/core/evaluator.js.map +1 -0
  38. package/dist/components/condition-editor/core/operators.js +123 -0
  39. package/dist/components/condition-editor/core/operators.js.map +1 -0
  40. package/dist/components/condition-editor/style.js +7 -0
  41. package/dist/components/condition-editor/style2.css +186 -0
  42. package/dist/components/context-menu/context-menu.js +1 -1
  43. package/dist/components/date-picker/date-picker.js +2 -2
  44. package/dist/components/date-picker/date-picker.vue.d.ts +2 -2
  45. package/dist/components/date-range-picker/date-range-picker.js +2 -2
  46. package/dist/components/date-range-picker/date-range-picker.vue.d.ts +2 -2
  47. package/dist/components/dialog/dialog.js +7 -7
  48. package/dist/components/dialog/style2.css +8 -0
  49. package/dist/components/dialog/use-maximum.js +1 -1
  50. package/dist/components/drawer/style2.css +8 -0
  51. package/dist/components/dropdown/dropdown.js +1 -1
  52. package/dist/components/expression-editor/components/variable-picker.js +8 -8
  53. package/dist/components/expression-editor/expression-editor.js +1 -1
  54. package/dist/components/expression-editor/style2.css +1 -0
  55. package/dist/components/file-viewer/file-viewer.js +13 -13
  56. package/dist/components/form-item/form-item.js.map +1 -1
  57. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +6 -6
  58. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
  59. package/dist/components/grid-input/grid-input.js +1 -1
  60. package/dist/components/group-input/group-input.js +2 -1
  61. package/dist/components/group-input/group-input.js.map +1 -1
  62. package/dist/components/group-input/style.js +1 -0
  63. package/dist/components/index.js +154 -154
  64. package/dist/components/input/input.vue.d.ts +4 -4
  65. package/dist/components/input/input.vue_vue_type_script_setup_true_lang.js +1 -1
  66. package/dist/components/layout/layout-resizer.js +1 -1
  67. package/dist/components/layout/layout.js +1 -1
  68. package/dist/components/list/list.js +1 -1
  69. package/dist/components/loading/loading.js +1 -1
  70. package/dist/components/menu/menu-sub-collapsed.js +4 -4
  71. package/dist/components/menu/menu.js +1 -1
  72. package/dist/components/message/message.js +1 -1
  73. package/dist/components/message-confirm/message-confirm.js +1 -1
  74. package/dist/components/multi-select/multi-select-option.js +19 -14
  75. package/dist/components/multi-select/multi-select-option.js.map +1 -1
  76. package/dist/components/multi-select/multi-select.js +15 -15
  77. package/dist/components/multi-select/multi-select.vue.d.ts +4 -4
  78. package/dist/components/multi-tree-select/multi-tree-select.js +8 -8
  79. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +6 -6
  80. package/dist/components/notification/notification.js +1 -1
  81. package/dist/components/notification/notification.vue.d.ts +1 -1
  82. package/dist/components/number/number.vue.d.ts +1 -1
  83. package/dist/components/number-input/number-input.js +2 -2
  84. package/dist/components/number-input/number-input.vue.d.ts +4 -4
  85. package/dist/components/number-range-input/number-range-input.js +1 -1
  86. package/dist/components/number-range-input/number-range-input.vue.d.ts +1 -1
  87. package/dist/components/number-range-input/style2.css +1 -0
  88. package/dist/components/paginator/paginator.js +6 -6
  89. package/dist/components/paginator/style.js +1 -1
  90. package/dist/components/palette/palette.js +1 -1
  91. package/dist/components/password-input/password-input.js +2 -2
  92. package/dist/components/password-input/password-input.vue.d.ts +1 -1
  93. package/dist/components/pop-confirm/pop-confirm.js +1 -1
  94. package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
  95. package/dist/components/progress-nodes/progress-nodes.js +1 -1
  96. package/dist/components/radio/radio.js +1 -1
  97. package/dist/components/radio-group/radio-group.js +1 -1
  98. package/dist/components/rich-text-editor/rich-text-editor.js +1 -1
  99. package/dist/components/rich-text-editor/rich-text-editor.vue.d.ts +1 -1
  100. package/dist/components/scroll/scroll-bar.js +1 -1
  101. package/dist/components/scroll/scroll.js +1 -1
  102. package/dist/components/select/select.js +10 -10
  103. package/dist/components/select/select.vue.d.ts +2 -2
  104. package/dist/components/slider/slider.js +1 -1
  105. package/dist/components/steps/steps.js +2 -2
  106. package/dist/components/switch/switch.js +1 -1
  107. package/dist/components/table/style2.css +4 -1
  108. package/dist/components/table/table.js +2 -2
  109. package/dist/components/table/table.js.map +1 -1
  110. package/dist/components/table/use-columns.js +1 -1
  111. package/dist/components/table/use-fixed-columns.js +8 -31
  112. package/dist/components/table/use-fixed-columns.js.map +1 -1
  113. package/dist/components/table-editor/table-editor.js +1 -1
  114. package/dist/components/tabs/tabs-horizontal.js +1 -1
  115. package/dist/components/tabs/tabs-horizontal.vue.d.ts +4 -4
  116. package/dist/components/tabs/tabs-vertical.js +1 -1
  117. package/dist/components/tabs/tabs-vertical.vue.d.ts +3 -3
  118. package/dist/components/tabs/tabs.js +1 -1
  119. package/dist/components/tabs/tabs.vue.d.ts +4 -4
  120. package/dist/components/tag/tag.js +1 -1
  121. package/dist/components/textarea/textarea.js +1 -1
  122. package/dist/components/textarea/textarea.vue.d.ts +4 -4
  123. package/dist/components/theme/style.js +1 -1
  124. package/dist/components/theme/theme.js +11 -11
  125. package/dist/components/tip/tip.js +1 -1
  126. package/dist/components/tree/tree-node.js +1 -1
  127. package/dist/components/tree/tree.js +5 -5
  128. package/dist/components/tree-select/tree-select.js +6 -6
  129. package/dist/components/tree-select/tree-select.vue.d.ts +5 -5
  130. package/dist/index.d.ts +5 -5
  131. package/dist/index.js +83 -83
  132. package/dist/style.js +2 -2
  133. package/dist/types/action.d.ts +5 -11
  134. package/dist/types/condition-editor.d.ts +41 -8
  135. package/dist/types/index.d.ts +4 -4
  136. package/package.json +11 -11
  137. package/src/components/action/action-group.vue +68 -82
  138. package/src/components/action/action.vue +9 -1
  139. package/src/components/action/di.ts +2 -1
  140. package/src/components/action/style.scss +22 -27
  141. package/src/components/batch-edit/batch-edit-form.vue +65 -70
  142. package/src/components/batch-edit/batch-edit-list.vue +35 -41
  143. package/src/components/batch-edit/style.scss +19 -118
  144. package/src/components/condition-editor/components/condition-group.vue +131 -0
  145. package/src/components/condition-editor/components/condition-row.vue +208 -0
  146. package/src/components/condition-editor/condition-editor.vue +145 -6
  147. package/src/components/condition-editor/core/evaluator.ts +93 -0
  148. package/src/components/condition-editor/core/operators.ts +57 -0
  149. package/src/components/condition-editor/style.scss +239 -1
  150. package/src/components/condition-editor/style.ts +7 -0
  151. package/src/components/dialog/style.scss +13 -0
  152. package/src/components/drawer/style.scss +13 -0
  153. package/src/components/expression-editor/style.scss +1 -0
  154. package/src/components/form-item/form-item.vue +1 -1
  155. package/src/components/group-input/group-input.vue +8 -1
  156. package/src/components/group-input/style.ts +1 -0
  157. package/src/components/multi-select/multi-select-option.vue +19 -13
  158. package/src/components/number-range-input/style.scss +1 -0
  159. package/src/components/table/style.scss +9 -1
  160. package/src/components/table/table.vue +1 -3
  161. package/src/components/table/use-fixed-columns.ts +5 -49
  162. package/src/types/action.ts +6 -11
  163. package/src/types/condition-editor.ts +39 -7
@@ -0,0 +1,131 @@
1
+ <template>
2
+ <div :class="cls.e('group')">
3
+ <!-- AND/OR 标签 + 汇总结果 -->
4
+ <div :class="cls.e('group-header')">
5
+ <span :class="[cls.e('logic-tag'), cls.em('logic-tag', group.logic)]" @click="toggleLogic">
6
+ {{ group.logic.toUpperCase() }} ▾
7
+ </span>
8
+ <span
9
+ v-if="hasResult"
10
+ :class="[cls.e('group-result'), cls.em('group-result', group._result ? 'pass' : 'fail')]"
11
+ >
12
+ {{ group._result ? '✓ 通过' : '✗ 不通过' }}
13
+ </span>
14
+ </div>
15
+
16
+ <!-- 嵌套组容器 -->
17
+ <div :class="cls.e('group-body')">
18
+ <!-- 子条件 -->
19
+ <ConditionRow
20
+ v-for="(cond, idx) in group.conditions"
21
+ :key="idx"
22
+ :item="cond"
23
+ :fields="fields"
24
+ :size="size"
25
+ :disabled="disabled"
26
+ :readonly="readonly"
27
+ @update:item="(item) => updateCondition(idx, item)"
28
+ @delete="removeCondition(idx)"
29
+ @mention="(p) => emit('mention', p)"
30
+ />
31
+
32
+ <!-- 子分组(递归) -->
33
+ <ConditionGroup
34
+ v-for="(sub, idx) in group.groups"
35
+ :key="idx"
36
+ :group="sub"
37
+ :fields="fields"
38
+ :size="size"
39
+ :disabled="disabled"
40
+ :readonly="readonly"
41
+ @update:group="(g) => updateSubGroup(idx, g)"
42
+ @remove="() => removeSubGroup(idx)"
43
+ @mention="(p) => emit('mention', p)"
44
+ />
45
+
46
+ <!-- 空状态 + 添加按钮 -->
47
+ <div :class="cls.e('group-actions')">
48
+ <span v-if="!readonly" :class="cls.e('add-btn')" @click="addCondition">+ 添加条件</span>
49
+ <span v-if="!readonly" :class="cls.e('add-btn')" @click="addGroup">+ 添加条件组</span>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </template>
54
+
55
+ <script lang="ts" setup>
56
+ import { bem } from '@veltra/utils'
57
+ import { computed } from 'vue'
58
+
59
+ import type { ConditionField, ConditionGroup, ConditionItem } from '../../../types'
60
+ import { createEmptyGroup, createEmptyItem } from '../core/evaluator'
61
+ import ConditionRow, { type MentionPayload } from './condition-row.vue'
62
+
63
+ defineOptions({ name: 'ConditionGroup' })
64
+
65
+ const props = withDefaults(
66
+ defineProps<{
67
+ group: ConditionGroup
68
+ fields?: ConditionField[]
69
+ size?: 'small' | 'default' | 'large'
70
+ disabled?: boolean
71
+ readonly?: boolean
72
+ }>(),
73
+ { size: 'default' }
74
+ )
75
+
76
+ const emit = defineEmits<{
77
+ (e: 'update:group', group: ConditionGroup): void
78
+ (e: 'remove'): void
79
+ (e: 'mention', payload: MentionPayload): void
80
+ }>()
81
+
82
+ const cls = bem('condition-editor')
83
+
84
+ const hasResult = computed(() => props.group._result !== undefined)
85
+
86
+ function toggleLogic() {
87
+ if (props.disabled || props.readonly) return
88
+ emit('update:group', {
89
+ ...props.group,
90
+ logic: props.group.logic === 'and' ? 'or' : 'and'
91
+ })
92
+ }
93
+
94
+ function addCondition() {
95
+ emit('update:group', {
96
+ ...props.group,
97
+ conditions: [...props.group.conditions, createEmptyItem()]
98
+ })
99
+ }
100
+
101
+ function removeCondition(idx: number) {
102
+ const conditions = [...props.group.conditions]
103
+ conditions.splice(idx, 1)
104
+ emit('update:group', { ...props.group, conditions })
105
+ }
106
+
107
+ function updateCondition(idx: number, item: ConditionItem) {
108
+ const conditions = [...props.group.conditions]
109
+ conditions[idx] = item
110
+ emit('update:group', { ...props.group, conditions })
111
+ }
112
+
113
+ function addGroup() {
114
+ emit('update:group', {
115
+ ...props.group,
116
+ groups: [...props.group.groups, createEmptyGroup()]
117
+ })
118
+ }
119
+
120
+ function removeSubGroup(idx: number) {
121
+ const groups = [...props.group.groups]
122
+ groups.splice(idx, 1)
123
+ emit('update:group', { ...props.group, groups })
124
+ }
125
+
126
+ function updateSubGroup(idx: number, g: ConditionGroup) {
127
+ const groups = [...props.group.groups]
128
+ groups[idx] = g
129
+ emit('update:group', { ...props.group, groups })
130
+ }
131
+ </script>
@@ -0,0 +1,208 @@
1
+ <template>
2
+ <div :class="[cls.e('row'), bem.is('focused', focused)]">
3
+ <!-- 字段选择 -->
4
+ <u-select
5
+ :class="cls.e('field')"
6
+ :model-value="item.field"
7
+ :options="fieldOptions"
8
+ value-key="value"
9
+ label-key="label"
10
+ placeholder="字段"
11
+ :size="size"
12
+ :disabled="disabled || readonly"
13
+ :clearable="false"
14
+ @update:model-value="onFieldChange"
15
+ />
16
+
17
+ <!-- 运算符选择 -->
18
+ <u-select
19
+ :class="cls.e('operator')"
20
+ :model-value="item.operator"
21
+ :options="operatorOptions"
22
+ value-key="value"
23
+ label-key="label"
24
+ placeholder="运算符"
25
+ :size="size"
26
+ :disabled="disabled || readonly"
27
+ :clearable="false"
28
+ @update:model-value="onOperatorChange"
29
+ />
30
+
31
+ <!-- 值输入 -->
32
+ <div :class="cls.e('value')">
33
+ <template v-if="needValue">
34
+ <input
35
+ v-if="item.value.kind === 'constant'"
36
+ ref="valueInputRef"
37
+ :class="cls.e('value-input')"
38
+ :value="item.value.value"
39
+ :placeholder="placeholder"
40
+ :disabled="disabled"
41
+ :readonly="readonly"
42
+ @input="onValueInput"
43
+ @keydown="onValueKeydown"
44
+ @focus="focused = true"
45
+ @blur="focused = false"
46
+ />
47
+ <span
48
+ v-else
49
+ :class="cls.e('value-chip')"
50
+ tabindex="0"
51
+ @click="onChipClick"
52
+ @keydown.delete="onChipDelete"
53
+ >
54
+ <span :class="cls.e('value-chip-label')">{{ variableLabel }}</span>
55
+ <span :class="cls.e('value-chip-close')" @click.stop="onChipDelete">×</span>
56
+ </span>
57
+ </template>
58
+ <span v-else :class="cls.e('value-empty')">—</span>
59
+ </div>
60
+
61
+ <!-- 删除 -->
62
+ <span v-if="!readonly" :class="cls.e('row-delete')" @click="emit('delete')">×</span>
63
+
64
+ <!-- 结果指示 -->
65
+ <span
66
+ v-if="hasResult"
67
+ :class="[cls.e('result'), cls.em('result', item._result ? 'pass' : 'fail')]"
68
+ >
69
+ {{ item._result ? '✓' : '✗' }}
70
+ </span>
71
+ <span v-else :class="[cls.e('result'), cls.em('result', 'none')]">—</span>
72
+ </div>
73
+ </template>
74
+
75
+ <script lang="ts" setup>
76
+ import { useFormFallbackProps } from '@veltra/compositions'
77
+ import { bem, injectFormContext } from '@veltra/utils'
78
+ import { computed, ref, useTemplateRef } from 'vue'
79
+
80
+ import type { ConditionField, ConditionItem, ConditionValue } from '../../../types'
81
+ import { USelect } from '../../select'
82
+ import { getOperatorsByFieldType, type OperatorDef } from '../core/operators'
83
+
84
+ defineOptions({ name: 'ConditionRow' })
85
+
86
+ export interface MentionPayload {
87
+ triggerDom: HTMLElement
88
+ setValue: (val: ConditionValue) => void
89
+ }
90
+
91
+ const props = withDefaults(
92
+ defineProps<{
93
+ item: ConditionItem
94
+ fields?: ConditionField[]
95
+ size?: string
96
+ disabled?: boolean
97
+ readonly?: boolean
98
+ }>(),
99
+ {}
100
+ )
101
+
102
+ const { formProps } = injectFormContext()
103
+
104
+ const { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props], {
105
+ size: 'default',
106
+ disabled: false,
107
+ readonly: false
108
+ })
109
+
110
+ const emit = defineEmits<{
111
+ (e: 'update:item', item: ConditionItem): void
112
+ (e: 'delete'): void
113
+ (e: 'mention', payload: MentionPayload): void
114
+ }>()
115
+
116
+ const cls = bem('condition-editor')
117
+
118
+ const valueInputRef = useTemplateRef<HTMLInputElement>('valueInputRef')
119
+ const focused = ref(false)
120
+
121
+ const fieldOptions = computed(() =>
122
+ (props.fields ?? []).map((f) => ({ label: f.label, value: f.value }))
123
+ )
124
+
125
+ const currentField = computed(() => props.fields?.find((f) => f.value === props.item.field))
126
+
127
+ const operatorOptions = computed(() => {
128
+ if (!currentField.value) {
129
+ return props.item.operator ? [{ label: props.item.operator, value: props.item.operator }] : []
130
+ }
131
+ return getOperatorsByFieldType(currentField.value.type)
132
+ })
133
+
134
+ const needValue = computed(() => {
135
+ const ops = operatorOptions.value as OperatorDef[]
136
+ const op = ops.find((o) => o.value === props.item.operator)
137
+ return op?.needValue ?? true
138
+ })
139
+
140
+ const hasResult = computed(() => props.item._result !== undefined)
141
+
142
+ const placeholder = computed(() => (props.fields?.length ? '输入值或 @ 引用变量' : ''))
143
+
144
+ const variableLabel = computed(() => {
145
+ if (props.item.value.kind !== 'variable') return ''
146
+ return props.item.value.name
147
+ })
148
+
149
+ function onFieldChange(val: string) {
150
+ const field = props.fields?.find((f) => f.value === val)
151
+ const newItem: ConditionItem = {
152
+ ...props.item,
153
+ field: val,
154
+ operator: field ? getOperatorsByFieldType(field.type)[0]!.value : 'eq',
155
+ value: { kind: 'constant', value: '' }
156
+ }
157
+ emit('update:item', newItem)
158
+ }
159
+
160
+ function onOperatorChange(val: string) {
161
+ const newItem: ConditionItem = {
162
+ ...props.item,
163
+ operator: val,
164
+ value: { kind: 'constant', value: '' }
165
+ }
166
+ emit('update:item', newItem)
167
+ }
168
+
169
+ function onValueInput(e: Event) {
170
+ const val = (e.target as HTMLInputElement).value
171
+ emit('update:item', {
172
+ ...props.item,
173
+ value: { kind: 'constant', value: val }
174
+ })
175
+ }
176
+
177
+ function onValueKeydown(e: KeyboardEvent) {
178
+ if (disabled.value || readonly.value) return
179
+ if (e.key === '@' && valueInputRef.value) {
180
+ e.preventDefault()
181
+ emit('mention', {
182
+ triggerDom: valueInputRef.value,
183
+ setValue: (val: ConditionValue) => {
184
+ emit('update:item', { ...props.item, value: val })
185
+ }
186
+ })
187
+ }
188
+ }
189
+
190
+ function onChipClick() {
191
+ if (disabled.value || readonly.value) return
192
+ if (valueInputRef.value) {
193
+ emit('mention', {
194
+ triggerDom: valueInputRef.value,
195
+ setValue: (val: ConditionValue) => {
196
+ emit('update:item', { ...props.item, value: val })
197
+ }
198
+ })
199
+ }
200
+ }
201
+
202
+ function onChipDelete() {
203
+ emit('update:item', {
204
+ ...props.item,
205
+ value: { kind: 'constant', value: '' }
206
+ })
207
+ }
208
+ </script>
@@ -1,17 +1,156 @@
1
1
  <template>
2
- <div :class="cls.b"></div>
2
+ <div :class="className" @keydown="onKeydown">
3
+ <ConditionGroup
4
+ :group="rootGroup"
5
+ :fields="props.fields"
6
+ :size="size"
7
+ :disabled="disabled"
8
+ :readonly="readonly"
9
+ @update:group="onGroupUpdate"
10
+ @mention="onMention"
11
+ />
12
+
13
+ <VariablePicker
14
+ ref="pickerRef"
15
+ :visible="pickerVisible"
16
+ :trigger-dom="pickerTriggerDom"
17
+ :variables="props.variables"
18
+ :filter="pickerFilter"
19
+ :selectable-levels="'leaf'"
20
+ @select="onPickerSelect"
21
+ @dismiss="onPickerDismiss"
22
+ @update:visible="onPickerVisibleChange"
23
+ />
24
+ </div>
3
25
  </template>
4
26
 
5
27
  <script lang="ts" setup>
6
- import { bem } from '@veltra/utils'
28
+ import { useFormFallbackProps } from '@veltra/compositions'
29
+ import { bem, injectFormContext } from '@veltra/utils'
30
+ import { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'
31
+
32
+ import type { ConditionEditorProps, ConditionExpression } from '../../types'
33
+ import VariablePicker from '../expression-editor/components/variable-picker.vue'
34
+ import ConditionGroup from './components/condition-group.vue'
35
+ import type { MentionPayload } from './components/condition-row.vue'
36
+ import { createEmptyGroup, evaluate } from './core/evaluator'
7
37
 
8
- import type { ConditionEditorProps } from '../../types'
38
+ defineOptions({ name: 'ConditionEditor' })
9
39
 
10
- defineOptions({
11
- name: 'ConditionEditor'
40
+ const props = withDefaults(defineProps<ConditionEditorProps>(), {
41
+ fields: () => []
12
42
  })
13
43
 
14
- defineProps<ConditionEditorProps>()
44
+ const emit = defineEmits<{
45
+ (e: 'update:modelValue', value: ConditionExpression): void
46
+ (e: 'evaluate', results: ConditionExpression): void
47
+ }>()
15
48
 
16
49
  const cls = bem('condition-editor')
50
+ const { formProps } = injectFormContext()
51
+ const { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])
52
+
53
+ const className = computed(() => [
54
+ cls.b,
55
+ cls.m(size.value),
56
+ bem.is('disabled', disabled.value),
57
+ bem.is('readonly', readonly.value)
58
+ ])
59
+
60
+ const rootGroup = shallowRef<ConditionExpression>(
61
+ props.modelValue ? JSON.parse(JSON.stringify(props.modelValue)) : createEmptyGroup()
62
+ )
63
+
64
+ function serialize(): ConditionExpression {
65
+ function clean(group: ConditionExpression): ConditionExpression {
66
+ return {
67
+ logic: group.logic,
68
+ conditions: group.conditions.map((c) => {
69
+ const { _result, ...rest } = c
70
+ return rest
71
+ }),
72
+ groups: group.groups.map(clean).filter((g) => g.conditions.length > 0 || g.groups.length > 0)
73
+ }
74
+ }
75
+ return clean(JSON.parse(JSON.stringify(rootGroup.value)))
76
+ }
77
+
78
+ function emitUpdate() {
79
+ emit('update:modelValue', serialize())
80
+ }
81
+
82
+ function runEvaluate() {
83
+ if (!props.data) return
84
+ const cloned = JSON.parse(JSON.stringify(rootGroup.value)) as ConditionExpression
85
+ const result = evaluate(cloned, props.data)
86
+ rootGroup.value = result
87
+ emit('evaluate', serialize())
88
+ }
89
+
90
+ // ── VariablePicker ──
91
+
92
+ const pickerRef = useTemplateRef<{ handleKeydown: (e: KeyboardEvent) => boolean }>('pickerRef')
93
+ const pickerVisible = shallowRef(false)
94
+ const pickerTriggerDom = shallowRef<HTMLElement | undefined>(undefined)
95
+ const pickerFilter = shallowRef('')
96
+ const mentionTarget = shallowRef<MentionPayload | null>(null)
97
+
98
+ function onMention(payload: MentionPayload) {
99
+ if (disabled.value || readonly.value) return
100
+ mentionTarget.value = payload
101
+ pickerTriggerDom.value = payload.triggerDom
102
+ pickerFilter.value = ''
103
+ pickerVisible.value = true
104
+ }
105
+
106
+ function onPickerSelect(item: { label: string; value: string }) {
107
+ mentionTarget.value?.setValue({ kind: 'variable', name: item.value })
108
+ pickerVisible.value = false
109
+ mentionTarget.value = null
110
+ }
111
+
112
+ function onPickerDismiss() {
113
+ pickerVisible.value = false
114
+ mentionTarget.value = null
115
+ }
116
+
117
+ function onPickerVisibleChange(v: boolean) {
118
+ if (!v) {
119
+ pickerVisible.value = false
120
+ mentionTarget.value = null
121
+ }
122
+ }
123
+
124
+ function onGroupUpdate(group: ConditionExpression) {
125
+ rootGroup.value = group
126
+ emitUpdate()
127
+ void nextTick(() => runEvaluate())
128
+ }
129
+
130
+ watch(
131
+ () => props.modelValue,
132
+ (v) => {
133
+ if (!v) return
134
+ const current = serialize()
135
+ if (JSON.stringify(v) === JSON.stringify(current)) return
136
+ rootGroup.value = JSON.parse(JSON.stringify(v))
137
+ }
138
+ )
139
+
140
+ watch(
141
+ () => props.data,
142
+ () => runEvaluate(),
143
+ { deep: true }
144
+ )
145
+
146
+ function onKeydown(e: KeyboardEvent) {
147
+ if (disabled.value || readonly.value) return
148
+ if (pickerVisible.value) {
149
+ const handled = pickerRef.value?.handleKeydown(e)
150
+ if (handled) {
151
+ e.preventDefault()
152
+ return
153
+ }
154
+ }
155
+ }
17
156
  </script>
@@ -0,0 +1,93 @@
1
+ import type { ConditionExpression, ConditionGroup, ConditionItem } from '../../../types'
2
+
3
+ function resolveValue(item: ConditionItem, data: Record<string, unknown>): string {
4
+ if (item.value.kind === 'constant') return item.value.value
5
+ return String(getByPath(data, item.value.name) ?? '')
6
+ }
7
+
8
+ function resolveCompareValue(item: ConditionItem, data: Record<string, unknown>): string {
9
+ if (item.value.kind === 'constant') return item.value.value
10
+ return String(getByPath(data, item.value.name) ?? '')
11
+ }
12
+
13
+ function getByPath(obj: Record<string, unknown>, path: string): unknown {
14
+ return path.split('.').reduce((acc: any, key) => (acc != null ? acc[key] : undefined), obj)
15
+ }
16
+
17
+ function evaluateItem(item: ConditionItem, data: Record<string, unknown>): boolean {
18
+ const resolved = resolveValue(item, data)
19
+ const compareValue = resolveCompareValue(item, data)
20
+
21
+ switch (item.operator) {
22
+ case 'empty':
23
+ return resolved === '' || resolved === 'undefined' || resolved === 'null'
24
+ case 'not_empty':
25
+ return resolved !== '' && resolved !== 'undefined' && resolved !== 'null'
26
+ case 'is_true':
27
+ return resolved === 'true' || resolved === '1'
28
+ case 'is_false':
29
+ return resolved === 'false' || resolved === '0' || resolved === ''
30
+ case 'eq':
31
+ return resolved === compareValue
32
+ case 'ne':
33
+ return resolved !== compareValue
34
+ case 'contains':
35
+ return resolved.includes(compareValue)
36
+ case 'not_contains':
37
+ return !resolved.includes(compareValue)
38
+ case 'gt':
39
+ return Number(resolved) > Number(compareValue)
40
+ case 'lt':
41
+ return Number(resolved) < Number(compareValue)
42
+ case 'gte':
43
+ return Number(resolved) >= Number(compareValue)
44
+ case 'lte':
45
+ return Number(resolved) <= Number(compareValue)
46
+ case 'before':
47
+ return new Date(resolved).getTime() < new Date(compareValue).getTime()
48
+ case 'after':
49
+ return new Date(resolved).getTime() > new Date(compareValue).getTime()
50
+ case 'in': {
51
+ const vals = compareValue.split(',').map((s) => s.trim())
52
+ return vals.includes(resolved)
53
+ }
54
+ default:
55
+ return false
56
+ }
57
+ }
58
+
59
+ function evaluateGroup(group: ConditionGroup, data: Record<string, unknown>): boolean {
60
+ const conditionsResult = group.conditions.map((item) => {
61
+ const result = evaluateItem(item, data)
62
+ item._result = result
63
+ return result
64
+ })
65
+
66
+ const groupsResult = group.groups.map((sub) => {
67
+ const result = evaluateGroup(sub, data)
68
+ sub._result = result
69
+ return result
70
+ })
71
+
72
+ const allResults = [...conditionsResult, ...groupsResult]
73
+ if (allResults.length === 0) return false
74
+
75
+ return group.logic === 'and' ? allResults.every(Boolean) : allResults.some(Boolean)
76
+ }
77
+
78
+ export function evaluate(
79
+ expression: ConditionExpression,
80
+ data: Record<string, unknown>
81
+ ): ConditionExpression {
82
+ const cloned = JSON.parse(JSON.stringify(expression)) as ConditionExpression
83
+ evaluateGroup(cloned, data)
84
+ return cloned
85
+ }
86
+
87
+ export function createEmptyGroup(): ConditionGroup {
88
+ return { logic: 'and', conditions: [], groups: [] }
89
+ }
90
+
91
+ export function createEmptyItem(): ConditionItem {
92
+ return { field: '', operator: 'eq', value: { kind: 'constant', value: '' } }
93
+ }
@@ -0,0 +1,57 @@
1
+ export interface OperatorDef {
2
+ label: string
3
+ value: string
4
+ needValue: boolean
5
+ }
6
+
7
+ const STRING_OPERATORS: OperatorDef[] = [
8
+ { label: '等于', value: 'eq', needValue: true },
9
+ { label: '不等于', value: 'ne', needValue: true },
10
+ { label: '包含', value: 'contains', needValue: true },
11
+ { label: '不包含', value: 'not_contains', needValue: true },
12
+ { label: '为空', value: 'empty', needValue: false },
13
+ { label: '不为空', value: 'not_empty', needValue: false }
14
+ ]
15
+
16
+ const NUMBER_OPERATORS: OperatorDef[] = [
17
+ { label: '等于', value: 'eq', needValue: true },
18
+ { label: '不等于', value: 'ne', needValue: true },
19
+ { label: '大于', value: 'gt', needValue: true },
20
+ { label: '小于', value: 'lt', needValue: true },
21
+ { label: '大于等于', value: 'gte', needValue: true },
22
+ { label: '小于等于', value: 'lte', needValue: true }
23
+ ]
24
+
25
+ const BOOLEAN_OPERATORS: OperatorDef[] = [
26
+ { label: '是', value: 'is_true', needValue: false },
27
+ { label: '否', value: 'is_false', needValue: false }
28
+ ]
29
+
30
+ const DATE_OPERATORS: OperatorDef[] = [
31
+ { label: '等于', value: 'eq', needValue: true },
32
+ { label: '不等于', value: 'ne', needValue: true },
33
+ { label: '早于', value: 'before', needValue: true },
34
+ { label: '晚于', value: 'after', needValue: true }
35
+ ]
36
+
37
+ const ENUM_OPERATORS: OperatorDef[] = [
38
+ { label: '等于', value: 'eq', needValue: true },
39
+ { label: '不等于', value: 'ne', needValue: true },
40
+ { label: '包含于', value: 'in', needValue: true }
41
+ ]
42
+
43
+ const OPERATOR_MAP: Record<string, OperatorDef[]> = {
44
+ string: STRING_OPERATORS,
45
+ number: NUMBER_OPERATORS,
46
+ boolean: BOOLEAN_OPERATORS,
47
+ date: DATE_OPERATORS,
48
+ enum: ENUM_OPERATORS
49
+ }
50
+
51
+ export function getOperatorsByFieldType(type: string): OperatorDef[] {
52
+ return OPERATOR_MAP[type] ?? STRING_OPERATORS
53
+ }
54
+
55
+ export function getOperatorDef(type: string, operator: string): OperatorDef | undefined {
56
+ return getOperatorsByFieldType(type).find((op) => op.value === operator)
57
+ }