@veltra/desktop 1.1.4 → 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 (145) 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 +4 -13
  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/action.vue.d.ts +1 -1
  9. package/dist/components/action/di.js.map +1 -1
  10. package/dist/components/action/style2.css +17 -14
  11. package/dist/components/auto-complete/auto-complete.js +1 -1
  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 +2 -2
  24. package/dist/components/checkbox/checkbox-button.js +1 -1
  25. package/dist/components/checkbox/checkbox-button.vue.d.ts +1 -1
  26. package/dist/components/checkbox/checkbox.js +1 -1
  27. package/dist/components/checkbox-group/checkbox-group.js +1 -1
  28. package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
  29. package/dist/components/code-editor/code-editor.js +1 -1
  30. package/dist/components/collapse/collapse.js +1 -1
  31. package/dist/components/condition-editor/components/condition-group.js +152 -0
  32. package/dist/components/condition-editor/components/condition-group.js.map +1 -0
  33. package/dist/components/condition-editor/components/condition-row.js +217 -0
  34. package/dist/components/condition-editor/components/condition-row.js.map +1 -0
  35. package/dist/components/condition-editor/condition-editor.js +134 -5
  36. package/dist/components/condition-editor/condition-editor.js.map +1 -1
  37. package/dist/components/condition-editor/condition-editor.vue.d.ts +10 -2
  38. package/dist/components/condition-editor/core/evaluator.js +75 -0
  39. package/dist/components/condition-editor/core/evaluator.js.map +1 -0
  40. package/dist/components/condition-editor/core/operators.js +123 -0
  41. package/dist/components/condition-editor/core/operators.js.map +1 -0
  42. package/dist/components/condition-editor/style.js +7 -0
  43. package/dist/components/condition-editor/style2.css +186 -0
  44. package/dist/components/context-menu/context-menu.js +1 -1
  45. package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
  46. package/dist/components/date-picker/date-picker.js +2 -2
  47. package/dist/components/date-picker/date-picker.vue.d.ts +1 -1
  48. package/dist/components/date-range-picker/date-range-picker.js +2 -2
  49. package/dist/components/date-range-picker/date-range-picker.vue.d.ts +1 -1
  50. package/dist/components/dialog/dialog.js +7 -7
  51. package/dist/components/dialog/style2.css +8 -0
  52. package/dist/components/dialog/use-maximum.js +1 -1
  53. package/dist/components/drawer/style2.css +8 -0
  54. package/dist/components/dropdown/dropdown.js +1 -1
  55. package/dist/components/expression-editor/components/variable-picker.js +8 -8
  56. package/dist/components/expression-editor/expression-editor.js +1 -1
  57. package/dist/components/file-viewer/file-viewer.js +13 -13
  58. package/dist/components/form-item/form-item.js.map +1 -1
  59. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +6 -6
  60. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
  61. package/dist/components/grid-input/grid-input.js +1 -1
  62. package/dist/components/group-input/group-input.js +1 -1
  63. package/dist/components/index.js +154 -154
  64. package/dist/components/input/input.vue_vue_type_script_setup_true_lang.js +1 -1
  65. package/dist/components/layout/layout-resizer.js +1 -1
  66. package/dist/components/layout/layout.js +1 -1
  67. package/dist/components/list/list.js +1 -1
  68. package/dist/components/loading/loading.js +1 -1
  69. package/dist/components/menu/menu-sub-collapsed.js +4 -4
  70. package/dist/components/menu/menu.js +1 -1
  71. package/dist/components/message/message.js +1 -1
  72. package/dist/components/message-confirm/message-confirm.js +1 -1
  73. package/dist/components/multi-select/multi-select.js +15 -15
  74. package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
  75. package/dist/components/multi-tree-select/multi-tree-select.js +8 -8
  76. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +3 -3
  77. package/dist/components/notification/notification.js +1 -1
  78. package/dist/components/number/number.vue.d.ts +1 -1
  79. package/dist/components/number-input/number-input.js +2 -2
  80. package/dist/components/number-range-input/number-range-input.js +1 -1
  81. package/dist/components/paginator/paginator.js +6 -6
  82. package/dist/components/paginator/style.js +1 -1
  83. package/dist/components/palette/palette.js +1 -1
  84. package/dist/components/password-input/password-input.js +2 -2
  85. package/dist/components/pop-confirm/pop-confirm.js +1 -1
  86. package/dist/components/progress-nodes/progress-nodes.js +1 -1
  87. package/dist/components/radio/radio.js +1 -1
  88. package/dist/components/radio-group/radio-group.js +1 -1
  89. package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
  90. package/dist/components/rich-text-editor/rich-text-editor.js +1 -1
  91. package/dist/components/scroll/scroll-bar.js +1 -1
  92. package/dist/components/scroll/scroll.js +1 -1
  93. package/dist/components/select/select.js +10 -10
  94. package/dist/components/select/select.vue.d.ts +2 -2
  95. package/dist/components/slider/slider.js +1 -1
  96. package/dist/components/steps/steps.js +2 -2
  97. package/dist/components/switch/switch.js +1 -1
  98. package/dist/components/table/style2.css +4 -1
  99. package/dist/components/table/table.js +2 -2
  100. package/dist/components/table/table.js.map +1 -1
  101. package/dist/components/table/use-columns.js +1 -1
  102. package/dist/components/table/use-fixed-columns.js +8 -31
  103. package/dist/components/table/use-fixed-columns.js.map +1 -1
  104. package/dist/components/table-editor/table-editor.js +1 -1
  105. package/dist/components/tabs/tabs-horizontal.js +1 -1
  106. package/dist/components/tabs/tabs-vertical.js +1 -1
  107. package/dist/components/tabs/tabs.js +1 -1
  108. package/dist/components/tag/tag.js +1 -1
  109. package/dist/components/textarea/textarea.js +1 -1
  110. package/dist/components/theme/style.js +1 -1
  111. package/dist/components/theme/theme.js +11 -11
  112. package/dist/components/tip/tip.js +1 -1
  113. package/dist/components/tree/tree-node.js +1 -1
  114. package/dist/components/tree/tree.js +5 -5
  115. package/dist/components/tree-select/tree-select.js +6 -6
  116. package/dist/components/tree-select/tree-select.vue.d.ts +3 -3
  117. package/dist/index.d.ts +5 -5
  118. package/dist/index.js +83 -83
  119. package/dist/style.js +2 -2
  120. package/dist/types/action.d.ts +5 -11
  121. package/dist/types/condition-editor.d.ts +41 -8
  122. package/dist/types/index.d.ts +4 -4
  123. package/package.json +11 -11
  124. package/src/components/action/action-group.vue +68 -82
  125. package/src/components/action/action.vue +9 -1
  126. package/src/components/action/di.ts +2 -1
  127. package/src/components/action/style.scss +22 -27
  128. package/src/components/batch-edit/batch-edit-form.vue +65 -70
  129. package/src/components/batch-edit/batch-edit-list.vue +35 -41
  130. package/src/components/batch-edit/style.scss +19 -118
  131. package/src/components/condition-editor/components/condition-group.vue +131 -0
  132. package/src/components/condition-editor/components/condition-row.vue +208 -0
  133. package/src/components/condition-editor/condition-editor.vue +145 -6
  134. package/src/components/condition-editor/core/evaluator.ts +93 -0
  135. package/src/components/condition-editor/core/operators.ts +57 -0
  136. package/src/components/condition-editor/style.scss +239 -1
  137. package/src/components/condition-editor/style.ts +7 -0
  138. package/src/components/dialog/style.scss +13 -0
  139. package/src/components/drawer/style.scss +13 -0
  140. package/src/components/form-item/form-item.vue +1 -1
  141. package/src/components/table/style.scss +9 -1
  142. package/src/components/table/table.vue +1 -3
  143. package/src/components/table/use-fixed-columns.ts +5 -49
  144. package/src/types/action.ts +6 -11
  145. package/src/types/condition-editor.ts +39 -7
@@ -5,20 +5,37 @@
5
5
  :class="[cls.e('table'), bem.is('editing', state.visible)]"
6
6
  :columns="columns"
7
7
  highlight-current
8
- :current-row="state.row"
8
+ :current="state.row"
9
9
  @update:current="handleUpdateCurrentRow"
10
10
  @update:checked="emit('update:checked', $event)"
11
11
  @update:selected="emit('update:selected', $event)"
12
12
  ref="tableRef"
13
13
  >
14
14
  <template #column:__action__="{ row }">
15
- <u-action-group :class="cls.e('row-actions')" hover circle :max="3" @click.stop>
15
+ <u-action-group :class="cls.e('row-actions')" :max="5" @click.stop circle>
16
16
  <u-action
17
- v-if="canEdit(row)"
18
- :icon="props.readonly ? View : EditPen"
19
- :title="props.readonly ? '查看' : '编辑'"
20
- @run="handleEdit(row)"
21
- v-bind="props.actionsProps?.update"
17
+ v-if="allowed('create', row)"
18
+ :icon="InsertToPrev"
19
+ title="在上方插入"
20
+ @run="handleInsertToPrev(row)"
21
+ />
22
+ <u-action
23
+ v-if="allowed('create', row)"
24
+ :icon="InsertToNext"
25
+ title="在下方插入"
26
+ @run="handleInsertToNext(row)"
27
+ />
28
+ <u-action
29
+ v-if="!!props.tree && allowed('createChild', row)"
30
+ :icon="AddChild"
31
+ title="添加子级"
32
+ @run="handleInsertChild(row)"
33
+ />
34
+ <u-action
35
+ v-if="allowed('copy', row)"
36
+ :icon="Copy"
37
+ title="复制此行"
38
+ @run="handleCopy(row)"
22
39
  />
23
40
 
24
41
  <u-action
@@ -29,23 +46,6 @@
29
46
  @run="handleDelete(row)"
30
47
  v-bind="props.actionsProps?.delete"
31
48
  />
32
-
33
- <u-action v-if="allowed('create', row)" in-dropdown @run="handleInsertToPrev(row)">
34
- 在上方插入
35
- </u-action>
36
- <u-action v-if="allowed('create', row)" in-dropdown @run="handleInsertToNext(row)">
37
- 在下方插入
38
- </u-action>
39
- <u-action
40
- v-if="!!props.tree && allowed('createChild', row)"
41
- in-dropdown
42
- @run="handleInsertChild(row)"
43
- >
44
- 添加子级
45
- </u-action>
46
- <u-action v-if="allowed('copy', row)" in-dropdown @run="handleCopy(row)">
47
- 复制此行
48
- </u-action>
49
49
  </u-action-group>
50
50
  </template>
51
51
 
@@ -60,17 +60,16 @@
60
60
  "
61
61
  >
62
62
  <div :class="cls.e('add')">
63
- <button
64
- type="button"
65
- :class="[cls.e('add-btn'), bem.is('loading', state.loading)]"
66
- :disabled="state.loading"
63
+ <u-button
64
+ :class="cls.e('add-btn')"
65
+ :icon="Plus"
66
+ :loading="state.loading"
67
+ type="primary"
68
+ plain
67
69
  @click.stop="handleCreate"
68
70
  >
69
- <u-icon :class="cls.e('add-icon')">
70
- <Plus />
71
- </u-icon>
72
- <span :class="cls.e('add-label')">新增一行</span>
73
- </button>
71
+ 新增一行
72
+ </u-button>
74
73
  </div>
75
74
  </template>
76
75
  </u-table>
@@ -78,13 +77,13 @@
78
77
 
79
78
  <script setup lang="ts">
80
79
  import { o } from '@cat-kit/core'
81
- import { Delete, EditPen, Plus, View } from '@veltra/icons/normal'
80
+ import { AddChild, Copy, Delete, InsertToNext, InsertToPrev, Plus } from '@veltra/icons/normal'
82
81
  import { bem } from '@veltra/utils'
83
82
  import { computed, inject, type Slots } from 'vue'
84
83
 
85
84
  import type { BatchEditFeature, TableRow } from '../../types'
86
85
  import { UAction, UActionGroup } from '../action'
87
- import { UIcon } from '../icon'
86
+ import { UButton } from '../button'
88
87
  import { UTable } from '../table'
89
88
  import { BatchEditDIKey } from './di'
90
89
 
@@ -105,7 +104,6 @@ const {
105
104
  staticFeatures,
106
105
  dynamicFeatures,
107
106
  handleCreate,
108
- handleEdit,
109
107
  handleCopy,
110
108
  handleDelete,
111
109
  handleInsertToNext,
@@ -139,7 +137,7 @@ const columns = computed(() => {
139
137
  name: '操作',
140
138
  key: '__action__',
141
139
  align: 'center',
142
- width: 132,
140
+ width: 180,
143
141
  fixed: 'right',
144
142
  resizable: false
145
143
  })
@@ -152,10 +150,6 @@ function handleUpdateCurrentRow(row?: TableRow) {
152
150
  }
153
151
  }
154
152
 
155
- function canEdit(row: TableRow) {
156
- return !props.readonly ? allowed('update', row) : true
157
- }
158
-
159
153
  function canDelete(row: TableRow) {
160
154
  return !props.readonly && allowed('delete', row)
161
155
  }
@@ -9,26 +9,17 @@ $root-name: batch-edit;
9
9
  overflow: hidden;
10
10
  border-radius: fn.use-var(radius, default);
11
11
 
12
+ &:focus,
13
+ &:focus-visible {
14
+ outline: none;
15
+ }
16
+
12
17
  // ---------- 表格容器 ----------
13
18
  @include m.e(table) {
14
19
  flex-grow: 1;
15
20
  }
16
21
 
17
- // ---------- 行内悬浮动作条(操作列) ----------
18
- // 操作组使用 `u-action-group` 的 hover 模式
19
- // 这里在父级表格行 hover / 当前激活时强制显现整组
20
- .u-table__row:hover .#{vars.$namespace}action-group.is-hover,
21
- .u-table__row.is-current .#{vars.$namespace}action-group.is-hover {
22
- opacity: 1;
23
- background-color: color-mix(in srgb, #{fn.use-var(bg-color, top)} 70%, transparent);
24
- backdrop-filter: fn.use-var(bg-filter);
25
- -webkit-backdrop-filter: fn.use-var(bg-filter);
26
- box-shadow: 0 1px 4px color-mix(in srgb, #{fn.use-var(shadow, color)} 35%, transparent);
27
- }
28
-
29
22
  // ---------- 底部"新增一行"按钮 ----------
30
- // 按钮通过水平方向 sticky 始终贴住滚动视口左侧,
31
- // 避免列很长横向滚动时按钮内容被滚出视口外
32
23
  @include m.e(add) {
33
24
  position: sticky;
34
25
  left: 0;
@@ -39,72 +30,14 @@ $root-name: batch-edit;
39
30
  }
40
31
 
41
32
  @include m.e(add-btn) {
42
- --add-color: #{fn.use-var(color, primary)};
43
- --add-border: #{color-mix(in srgb, fn.use-var(color, primary) 60%, transparent)};
44
- --add-bg: #{color-mix(in srgb, fn.use-var(color, primary) 5%, transparent)};
45
-
46
33
  position: sticky;
47
34
  left: 12px;
48
- height: 36px;
49
- min-width: 220px;
50
- max-width: 320px;
51
- display: inline-flex;
52
- align-items: center;
53
- justify-content: center;
54
- gap: 6px;
55
- padding: 0 20px;
56
- background: var(--add-bg);
57
- border: 1px dashed var(--add-border);
58
- border-radius: fn.use-var(radius, default);
59
- color: var(--add-color);
60
- font-size: fn.use-var(font-size-main, default);
61
- font-weight: 500;
62
- cursor: pointer;
63
- transition:
64
- border-color 0.18s ease,
65
- color 0.18s ease,
66
- background-color 0.18s ease,
67
- transform 0.18s ease;
68
- font-family: inherit;
69
-
70
- &:hover:not(:disabled) {
71
- --add-border: #{fn.use-var(color, primary)};
72
- --add-bg: #{fn.use-var(color, primary, light-9)};
73
- border-style: solid;
74
- }
75
-
76
- &:active:not(:disabled) {
77
- transform: translateY(1px);
78
- }
79
-
80
- &:disabled {
81
- cursor: not-allowed;
82
- opacity: 0.55;
83
- }
84
-
85
- @include m.is(loading) {
86
- pointer-events: none;
87
- }
88
- }
89
-
90
- @include m.e(add-icon) {
91
- font-size: 16px;
92
- display: inline-flex;
93
- transition: transform 0.18s ease;
94
- }
95
-
96
- .#{vars.$namespace}#{$root-name}__add-btn:hover .#{vars.$namespace}#{$root-name}__add-icon {
97
- transform: rotate(90deg);
98
- }
99
-
100
- @include m.e(add-label) {
101
- line-height: 1;
102
35
  }
103
36
 
104
37
  // ---------- 右侧表单侧栏 ----------
105
38
  @include m.e(form) {
106
39
  --u-batch-edit-form-radius: #{fn.use-var(radius, large)};
107
- --u-batch-edit-form-padding: 16px;
40
+ --u-batch-edit-form-padding: 18px;
108
41
 
109
42
  display: flex;
110
43
  flex-direction: column;
@@ -116,31 +49,19 @@ $root-name: batch-edit;
116
49
  overflow: hidden;
117
50
  isolation: isolate;
118
51
  min-height: 0;
119
-
120
- // 顶部装饰渐变条
121
- &::before {
122
- content: '';
123
- position: absolute;
124
- top: 0;
125
- left: 0;
126
- right: 0;
127
- height: 3px;
128
- background: linear-gradient(
129
- 90deg,
130
- fn.use-var(color, primary) 0%,
131
- color-mix(in srgb, #{fn.use-var(color, primary)} 50%, transparent) 100%
132
- );
133
- opacity: 0.7;
134
- z-index: 1;
135
- }
136
52
  }
137
53
 
138
54
  @include m.e(form-header) {
139
55
  display: flex;
140
56
  align-items: center;
141
- gap: 10px;
142
- padding: 8px 14px;
57
+ gap: 12px;
58
+ padding: 10px 14px;
143
59
  border-bottom: 1px solid fn.use-var(border-color);
60
+ background-color: color-mix(
61
+ in srgb,
62
+ #{fn.use-var(bg-color, top)} 96%,
63
+ #{fn.use-var(bg-color, hover)}
64
+ );
144
65
  flex-shrink: 0;
145
66
  }
146
67
 
@@ -180,8 +101,8 @@ $root-name: batch-edit;
180
101
  flex: 1 1 auto;
181
102
  min-width: 0;
182
103
  display: flex;
183
- flex-direction: column;
184
- gap: 2px;
104
+ align-items: center;
105
+ gap: 8px;
185
106
 
186
107
  &-text {
187
108
  margin: 0;
@@ -194,32 +115,26 @@ $root-name: batch-edit;
194
115
 
195
116
  @include m.e(form-chip) {
196
117
  display: inline-block;
197
- align-self: flex-start;
198
118
  padding: 1px 8px;
199
119
  font-size: 12px;
200
120
  line-height: 18px;
201
- border-radius: 999px;
202
- background-color: fn.use-var(bg-color, hover);
121
+ border-radius: fn.use-var(radius, default);
122
+ background-color: fn.use-var(bg-color, default);
203
123
  color: fn.use-var(text-color, second);
204
124
  white-space: nowrap;
205
125
  overflow: hidden;
206
126
  text-overflow: ellipsis;
207
- max-width: 100%;
127
+ max-width: 60%;
208
128
  }
209
129
 
210
130
  @include m.e(form-close) {
211
131
  flex-shrink: 0;
212
- align-self: flex-start;
213
132
  }
214
133
 
215
134
  @include m.e(form-body) {
216
135
  flex: 1 1 auto;
217
136
  min-height: 0;
218
-
219
- // 仅限定 batch-edit 内的 u-form 表单滚动区内边距
220
- .#{vars.$namespace}form__body {
221
- padding: 10px var(--u-batch-edit-form-padding) 4px;
222
- }
137
+ padding: 6px 8px 0;
223
138
  }
224
139
 
225
140
  @include m.e(form-footer) {
@@ -266,17 +181,3 @@ $root-name: batch-edit;
266
181
  flex-shrink: 0;
267
182
  }
268
183
  }
269
-
270
- // 表单面板进出过渡:从右侧滑入并轻微缩放
271
- .batch-edit-panel-enter-active,
272
- .batch-edit-panel-leave-active {
273
- transition:
274
- transform 0.32s cubic-bezier(0.32, 0.72, 0, 1),
275
- opacity 0.24s ease;
276
- }
277
-
278
- .batch-edit-panel-enter-from,
279
- .batch-edit-panel-leave-to {
280
- transform: translateX(16px) scale(0.985);
281
- opacity: 0;
282
- }
@@ -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>