xkit-editor 2.0.0-alpha.0 → 2.0.0-alpha.1
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/README.md +26 -0
- package/lib/packages/editor/src/components/json-code-editor.vue.d.ts +2 -0
- package/lib/packages/editor/src/components/json-code-editor.vue.d.ts.map +1 -1
- package/lib/packages/editor/src/x-editor.vue.d.ts +8 -0
- package/lib/packages/editor/src/x-editor.vue.d.ts.map +1 -1
- package/lib/packages/ui/src/components/radios/type.d.ts +2 -2
- package/lib/xkit-editor.css +1 -1
- package/lib/xkit-editor.js +64 -52
- package/lib/xkit-editor.umd.cjs +2 -2
- package/package.json +3 -4
- package/src/component-configs/__tests__/config.test.ts +0 -288
- package/src/component-configs/__tests__/group.test.ts +0 -104
- package/src/component-configs/cascader.ts +0 -20
- package/src/component-configs/checkbox.ts +0 -43
- package/src/component-configs/common-props.ts +0 -130
- package/src/component-configs/date-picker.ts +0 -153
- package/src/component-configs/date-range-picker.ts +0 -185
- package/src/component-configs/group.ts +0 -196
- package/src/component-configs/icon-select.ts +0 -56
- package/src/component-configs/input-array.ts +0 -184
- package/src/component-configs/input-number-range.ts +0 -143
- package/src/component-configs/input-number.ts +0 -111
- package/src/component-configs/input-table.ts +0 -191
- package/src/component-configs/input-upload.ts +0 -123
- package/src/component-configs/input.ts +0 -48
- package/src/component-configs/layout-sections.ts +0 -546
- package/src/component-configs/options-config.ts +0 -12
- package/src/component-configs/radio.ts +0 -26
- package/src/component-configs/select.ts +0 -42
- package/src/component-configs/switch.ts +0 -19
- package/src/component-configs/textarea.ts +0 -84
- package/src/component-configs/types.ts +0 -37
- package/src/components/condition-editor.vue +0 -641
- package/src/components/design-renderers/input-array-design.vue +0 -283
- package/src/components/design-renderers/input-table-design.vue +0 -311
- package/src/components/form-item-layout-editor.vue +0 -291
- package/src/components/form-item-span-editor.vue +0 -328
- package/src/components/form-layout-editor.vue +0 -263
- package/src/components/group-title-color-select.vue +0 -75
- package/src/components/group-title-style-picker.vue +0 -125
- package/src/components/input-number-range-binding-editor.vue +0 -226
- package/src/components/input-table-default-rows-editor.vue +0 -147
- package/src/components/input-table-operation-editor.vue +0 -168
- package/src/components/json-code-editor.vue +0 -219
- package/src/components/object-section-editor.vue +0 -342
- package/src/components/options/default-dict-options-panel.vue +0 -197
- package/src/components/options/options-editor.vue +0 -161
- package/src/components/table-columns-editor.vue +0 -353
- package/src/components/validation-rules-editor.vue +0 -321
- package/src/config.ts +0 -51
- package/src/editor-item-wrapper.vue +0 -254
- package/src/group-title-styles.ts +0 -82
- package/src/index.ts +0 -6
- package/src/option-sources.ts +0 -45
- package/src/root-configs.ts +0 -278
- package/src/shims-vue.d.ts +0 -5
- package/src/utils/__tests__/component-replace.test.ts +0 -129
- package/src/utils/__tests__/editor-schema.test.ts +0 -429
- package/src/utils/__tests__/form-item-width.test.ts +0 -53
- package/src/utils/__tests__/group-drag-path.test.ts +0 -84
- package/src/utils/__tests__/record-field.test.ts +0 -34
- package/src/utils/component-replace.ts +0 -67
- package/src/utils/editor-schema.ts +0 -374
- package/src/utils/form-item-width.ts +0 -40
- package/src/utils/local-dict-center.ts +0 -81
- package/src/utils/record-field.ts +0 -30
- package/src/validation-rules.ts +0 -104
- package/src/x-editor.vue +0 -1300
|
@@ -1,641 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="condition-editor">
|
|
3
|
-
<template v-if="inlineVisible">
|
|
4
|
-
<el-select :model-value="currentMode" placeholder="请选择状态" @change="handleModeChange" class="w-full">
|
|
5
|
-
<el-option v-for="option in modeOptions" :key="String(option.value)" :label="option.label" :value="option.value" />
|
|
6
|
-
</el-select>
|
|
7
|
-
|
|
8
|
-
<div v-if="currentMode === CONDITION_MODE" class="condition-actions">
|
|
9
|
-
<el-button type="primary" plain size="small" @click="handleOpen" class="w-full">
|
|
10
|
-
配置条件
|
|
11
|
-
</el-button>
|
|
12
|
-
<div v-if="Array.isArray(modelValue) && modelValue.length > 0" class="condition-preview">
|
|
13
|
-
已配置 {{ builderConditionCount }} 条条件
|
|
14
|
-
</div>
|
|
15
|
-
</div>
|
|
16
|
-
</template>
|
|
17
|
-
|
|
18
|
-
<el-dialog v-model="dialogVisible" title="配置显示条件" width="860px" append-to-body>
|
|
19
|
-
<div class="condition-dialog">
|
|
20
|
-
<x-condition-builder
|
|
21
|
-
v-model="builderValue"
|
|
22
|
-
:items="builderItems"
|
|
23
|
-
:init-button-attrs="{ label: '添加条件' }"
|
|
24
|
-
class="condition-builder"
|
|
25
|
-
/>
|
|
26
|
-
</div>
|
|
27
|
-
<template #footer>
|
|
28
|
-
<el-button @click="dialogVisible = false">取消</el-button>
|
|
29
|
-
<el-button type="primary" @click="handleSave">确定</el-button>
|
|
30
|
-
</template>
|
|
31
|
-
</el-dialog>
|
|
32
|
-
</div>
|
|
33
|
-
</template>
|
|
34
|
-
|
|
35
|
-
<script setup lang="ts">
|
|
36
|
-
import { computed, inject, ref, unref, watch, type ComputedRef } from 'vue'
|
|
37
|
-
import { dayjs, ElButton, ElDialog, ElOption, ElSelect } from 'element-plus'
|
|
38
|
-
import { XConditionBuilder, genId, type OptionItem, type XConditionNode, type XFormItem } from 'xkit-ui'
|
|
39
|
-
import { listEditorLocalDicts } from '../utils/local-dict-center'
|
|
40
|
-
|
|
41
|
-
type StateKind = 'visible' | 'disabled' | 'static'
|
|
42
|
-
type FieldValueKind = 'number' | 'date' | 'boolean' | 'text' | 'option-single' | 'option-multiple' | 'tree-option'
|
|
43
|
-
type EditorOperator =
|
|
44
|
-
| 'eq'
|
|
45
|
-
| 'neq'
|
|
46
|
-
| 'gt'
|
|
47
|
-
| 'gte'
|
|
48
|
-
| 'lt'
|
|
49
|
-
| 'lte'
|
|
50
|
-
| 'contains'
|
|
51
|
-
| 'not_contains'
|
|
52
|
-
| 'in'
|
|
53
|
-
| 'not_in'
|
|
54
|
-
| 'exists'
|
|
55
|
-
| 'empty'
|
|
56
|
-
|
|
57
|
-
type FieldMeta = {
|
|
58
|
-
label: string
|
|
59
|
-
name: string
|
|
60
|
-
item: XFormItem
|
|
61
|
-
type: string
|
|
62
|
-
valueKind: FieldValueKind
|
|
63
|
-
options?: OptionItem[]
|
|
64
|
-
hasOptions: boolean
|
|
65
|
-
isTree: boolean
|
|
66
|
-
multiple: boolean
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const CONDITION_MODE = 'condition'
|
|
70
|
-
const unaryOperators = new Set<EditorOperator>(['exists', 'empty'])
|
|
71
|
-
const multiValueOperators = new Set<EditorOperator>(['contains', 'not_contains', 'in', 'not_in'])
|
|
72
|
-
|
|
73
|
-
const props = withDefaults(defineProps<{
|
|
74
|
-
modelValue?: any
|
|
75
|
-
defaultValue?: boolean
|
|
76
|
-
stateKind?: StateKind
|
|
77
|
-
inlineVisible?: boolean
|
|
78
|
-
autoOpenKey?: string | number
|
|
79
|
-
}>(), {
|
|
80
|
-
inlineVisible: true
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
const emit = defineEmits<{
|
|
84
|
-
(e: 'update:modelValue', value: any): void
|
|
85
|
-
}>()
|
|
86
|
-
|
|
87
|
-
const editorContext = inject<ComputedRef<{ form?: { body?: XFormItem[] }; selectedItem?: any }> | null>('x-editor-context', null)
|
|
88
|
-
|
|
89
|
-
const dialogVisible = ref(false)
|
|
90
|
-
const builderValue = ref<XConditionNode | undefined>(undefined)
|
|
91
|
-
|
|
92
|
-
const stateKind = computed<StateKind>(() => props.stateKind ?? (props.defaultValue === true ? 'visible' : 'disabled'))
|
|
93
|
-
|
|
94
|
-
const modeOptions = computed(() => {
|
|
95
|
-
if (stateKind.value === 'visible') {
|
|
96
|
-
return [
|
|
97
|
-
{ label: '始终显示', value: true },
|
|
98
|
-
{ label: '始终隐藏', value: false },
|
|
99
|
-
{ label: '满足条件时显示', value: CONDITION_MODE }
|
|
100
|
-
]
|
|
101
|
-
}
|
|
102
|
-
if (stateKind.value === 'static') {
|
|
103
|
-
return [
|
|
104
|
-
{ label: '默认编辑', value: false },
|
|
105
|
-
{ label: '始终只读', value: true },
|
|
106
|
-
{ label: '满足条件时只读', value: CONDITION_MODE }
|
|
107
|
-
]
|
|
108
|
-
}
|
|
109
|
-
return [
|
|
110
|
-
{ label: '始终可用', value: false },
|
|
111
|
-
{ label: '始终禁用', value: true },
|
|
112
|
-
{ label: '满足条件时禁用', value: CONDITION_MODE }
|
|
113
|
-
]
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
const currentMode = computed(() => {
|
|
117
|
-
if (typeof props.modelValue === 'boolean') return props.modelValue
|
|
118
|
-
if (Array.isArray(props.modelValue)) return CONDITION_MODE
|
|
119
|
-
return props.defaultValue ?? true
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
const collectFormItems = (items: XFormItem[] = []) => {
|
|
123
|
-
const result: XFormItem[] = []
|
|
124
|
-
items.forEach((item) => {
|
|
125
|
-
if (item?.type === 'group' && Array.isArray(item.body)) {
|
|
126
|
-
result.push(...collectFormItems(item.body))
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
result.push(item)
|
|
130
|
-
})
|
|
131
|
-
return result
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const normalizeType = (type: unknown) => String(type || 'input').replace(/^el-/, '').replace(/^x-/, '')
|
|
135
|
-
|
|
136
|
-
const normalizeDictOptions = (options: unknown) => {
|
|
137
|
-
if (typeof options === 'string') return { type: 'dict', code: options }
|
|
138
|
-
if (!options || typeof options !== 'object') return undefined
|
|
139
|
-
const source = options as Record<string, unknown>
|
|
140
|
-
if (source.type === 'dict' && typeof source.code === 'string') {
|
|
141
|
-
return { type: 'dict', code: source.code }
|
|
142
|
-
}
|
|
143
|
-
return undefined
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const resolveConditionOptions = (item: XFormItem): OptionItem[] | undefined => {
|
|
147
|
-
const options = unref((item as Record<string, any>).options)
|
|
148
|
-
if (Array.isArray(options)) return options
|
|
149
|
-
|
|
150
|
-
const dictOptions = normalizeDictOptions(options)
|
|
151
|
-
if (!dictOptions) return undefined
|
|
152
|
-
|
|
153
|
-
return listEditorLocalDicts().find((dict) => dict.code === dictOptions.code)?.options
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const hasTreeOptions = (options?: OptionItem[]) => {
|
|
157
|
-
return Boolean(options?.some((option) => Array.isArray(option?.children) && option.children.length > 0))
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const createFieldMeta = (item: XFormItem): FieldMeta | undefined => {
|
|
161
|
-
if (!item?.name || item.type === 'group') return undefined
|
|
162
|
-
const type = normalizeType(item.type)
|
|
163
|
-
const options = resolveConditionOptions(item)
|
|
164
|
-
const hasOptions = Array.isArray(options)
|
|
165
|
-
const isTree = ['cascader', 'tree-select'].includes(type) || hasTreeOptions(options)
|
|
166
|
-
const multiple = Boolean((item as any).multiple || ['checkboxes'].includes(type))
|
|
167
|
-
let valueKind: FieldValueKind = 'text'
|
|
168
|
-
|
|
169
|
-
if (isTree) {
|
|
170
|
-
valueKind = 'tree-option'
|
|
171
|
-
} else if (hasOptions || ['select', 'radios', 'radio'].includes(type)) {
|
|
172
|
-
valueKind = multiple ? 'option-multiple' : 'option-single'
|
|
173
|
-
} else if (['input-number', 'input-number-range', 'slider'].includes(type)) {
|
|
174
|
-
valueKind = 'number'
|
|
175
|
-
} else if (['date-picker', 'date-range-picker', 'date-range-picker-v2'].includes(type)) {
|
|
176
|
-
valueKind = 'date'
|
|
177
|
-
} else if (['switch', 'checkbox'].includes(type)) {
|
|
178
|
-
valueKind = 'boolean'
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
label: typeof item.label === 'string' && item.label ? item.label : item.name,
|
|
183
|
-
name: item.name,
|
|
184
|
-
item,
|
|
185
|
-
type,
|
|
186
|
-
valueKind,
|
|
187
|
-
options,
|
|
188
|
-
hasOptions,
|
|
189
|
-
isTree,
|
|
190
|
-
multiple
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const fieldMetas = computed(() => {
|
|
195
|
-
const items = editorContext?.value?.form?.body ?? []
|
|
196
|
-
const currentName = editorContext?.value?.selectedItem?.name
|
|
197
|
-
const metas = collectFormItems(items)
|
|
198
|
-
.map(createFieldMeta)
|
|
199
|
-
.filter(Boolean) as FieldMeta[]
|
|
200
|
-
const filtered = metas.filter((item) => item.name !== currentName)
|
|
201
|
-
return filtered.length > 0 ? filtered : metas
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
const findFieldMeta = (field?: string) => fieldMetas.value.find((item) => item.name === field)
|
|
205
|
-
|
|
206
|
-
const fieldOptions = computed(() => fieldMetas.value.map((item) => ({ label: item.label, value: item.name })))
|
|
207
|
-
|
|
208
|
-
const operatorOptionsByKind: Record<FieldValueKind, Array<{ label: string; value: EditorOperator }>> = {
|
|
209
|
-
number: [
|
|
210
|
-
{ label: '等于', value: 'eq' },
|
|
211
|
-
{ label: '不等于', value: 'neq' },
|
|
212
|
-
{ label: '大于', value: 'gt' },
|
|
213
|
-
{ label: '大于等于', value: 'gte' },
|
|
214
|
-
{ label: '小于', value: 'lt' },
|
|
215
|
-
{ label: '小于等于', value: 'lte' },
|
|
216
|
-
{ label: '有值', value: 'exists' },
|
|
217
|
-
{ label: '为空', value: 'empty' }
|
|
218
|
-
],
|
|
219
|
-
date: [
|
|
220
|
-
{ label: '等于', value: 'eq' },
|
|
221
|
-
{ label: '不等于', value: 'neq' },
|
|
222
|
-
{ label: '晚于', value: 'gt' },
|
|
223
|
-
{ label: '不早于', value: 'gte' },
|
|
224
|
-
{ label: '早于', value: 'lt' },
|
|
225
|
-
{ label: '不晚于', value: 'lte' },
|
|
226
|
-
{ label: '有值', value: 'exists' },
|
|
227
|
-
{ label: '为空', value: 'empty' }
|
|
228
|
-
],
|
|
229
|
-
boolean: [
|
|
230
|
-
{ label: '等于', value: 'eq' },
|
|
231
|
-
{ label: '不等于', value: 'neq' }
|
|
232
|
-
],
|
|
233
|
-
text: [
|
|
234
|
-
{ label: '等于', value: 'eq' },
|
|
235
|
-
{ label: '不等于', value: 'neq' },
|
|
236
|
-
{ label: '包含', value: 'contains' },
|
|
237
|
-
{ label: '不包含', value: 'not_contains' },
|
|
238
|
-
{ label: '有值', value: 'exists' },
|
|
239
|
-
{ label: '为空', value: 'empty' }
|
|
240
|
-
],
|
|
241
|
-
'option-single': [
|
|
242
|
-
{ label: '等于', value: 'eq' },
|
|
243
|
-
{ label: '不等于', value: 'neq' },
|
|
244
|
-
{ label: '属于其中之一', value: 'in' },
|
|
245
|
-
{ label: '不属于其中之一', value: 'not_in' },
|
|
246
|
-
{ label: '有值', value: 'exists' },
|
|
247
|
-
{ label: '为空', value: 'empty' }
|
|
248
|
-
],
|
|
249
|
-
'option-multiple': [
|
|
250
|
-
{ label: '包含', value: 'contains' },
|
|
251
|
-
{ label: '不包含', value: 'not_contains' },
|
|
252
|
-
{ label: '属于其中之一', value: 'in' },
|
|
253
|
-
{ label: '不属于其中之一', value: 'not_in' },
|
|
254
|
-
{ label: '有值', value: 'exists' },
|
|
255
|
-
{ label: '为空', value: 'empty' }
|
|
256
|
-
],
|
|
257
|
-
'tree-option': [
|
|
258
|
-
{ label: '等于', value: 'eq' },
|
|
259
|
-
{ label: '不等于', value: 'neq' },
|
|
260
|
-
{ label: '包含', value: 'contains' },
|
|
261
|
-
{ label: '不包含', value: 'not_contains' },
|
|
262
|
-
{ label: '属于其中之一', value: 'in' },
|
|
263
|
-
{ label: '不属于其中之一', value: 'not_in' },
|
|
264
|
-
{ label: '有值', value: 'exists' },
|
|
265
|
-
{ label: '为空', value: 'empty' }
|
|
266
|
-
]
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const getOperatorOptions = (row: any) => {
|
|
270
|
-
const meta = findFieldMeta(row.field)
|
|
271
|
-
return meta ? operatorOptionsByKind[meta.valueKind] : []
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const isMultiValueOperator = (operator?: EditorOperator) => Boolean(operator && multiValueOperators.has(operator))
|
|
275
|
-
|
|
276
|
-
const getValueOptions = (row: any) => findFieldMeta(row.field)?.options ?? []
|
|
277
|
-
|
|
278
|
-
const isValueEditorVisible = (row: any, valueKind: FieldValueKind) => {
|
|
279
|
-
const meta = findFieldMeta(row.field)
|
|
280
|
-
return Boolean(meta && meta.valueKind === valueKind && row.operator && !unaryOperators.has(row.operator))
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const getDateValueFormat = (meta?: FieldMeta) => {
|
|
284
|
-
const sourceValueFormat = (meta?.item as any)?.valueFormat
|
|
285
|
-
if (typeof sourceValueFormat === 'string' && sourceValueFormat) return sourceValueFormat
|
|
286
|
-
|
|
287
|
-
const pickerType = String((meta?.item as any)?._type || 'date')
|
|
288
|
-
if (['year', 'years', 'yearrange'].includes(pickerType)) return 'YYYY'
|
|
289
|
-
if (['month', 'months', 'monthrange'].includes(pickerType)) return 'YYYY-MM'
|
|
290
|
-
if (['datetime', 'datetimerange'].includes(pickerType)) return 'YYYY-MM-DD HH:mm:ss'
|
|
291
|
-
return 'YYYY-MM-DD'
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const resetConditionRowValue = (row: any) => {
|
|
295
|
-
row.value = undefined
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const builderItems = computed<XFormItem[]>(() => [
|
|
299
|
-
{
|
|
300
|
-
name: 'field',
|
|
301
|
-
label: false,
|
|
302
|
-
type: 'select',
|
|
303
|
-
placeholder: '选择字段',
|
|
304
|
-
options: fieldOptions.value,
|
|
305
|
-
style: { width: '180px' },
|
|
306
|
-
pipeOut(value: string, row: any) {
|
|
307
|
-
if (row.field !== value) {
|
|
308
|
-
row.operator = undefined
|
|
309
|
-
resetConditionRowValue(row)
|
|
310
|
-
}
|
|
311
|
-
return value
|
|
312
|
-
}
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
name: 'operator',
|
|
316
|
-
label: false,
|
|
317
|
-
type: 'select',
|
|
318
|
-
placeholder: '选择判断',
|
|
319
|
-
options: getOperatorOptions,
|
|
320
|
-
visible: (row: any) => Boolean(row.field),
|
|
321
|
-
style: { width: '150px' },
|
|
322
|
-
pipeOut(value: EditorOperator, row: any) {
|
|
323
|
-
if (row.operator !== value) resetConditionRowValue(row)
|
|
324
|
-
return value
|
|
325
|
-
}
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
name: 'value',
|
|
329
|
-
label: false,
|
|
330
|
-
type: 'select',
|
|
331
|
-
placeholder: '选择值',
|
|
332
|
-
options: getValueOptions,
|
|
333
|
-
visible: (row: any) => isValueEditorVisible(row, 'option-single') || isValueEditorVisible(row, 'option-multiple'),
|
|
334
|
-
attrs: (row: any) => ({
|
|
335
|
-
multiple: isMultiValueOperator(row.operator),
|
|
336
|
-
collapseTags: true,
|
|
337
|
-
maxCollapseTags: 10,
|
|
338
|
-
collapseTagsTooltip: true
|
|
339
|
-
}),
|
|
340
|
-
style: { width: '220px' }
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
name: 'value',
|
|
344
|
-
label: false,
|
|
345
|
-
type: 'cascader',
|
|
346
|
-
placeholder: '选择值',
|
|
347
|
-
options: getValueOptions,
|
|
348
|
-
visible: (row: any) => isValueEditorVisible(row, 'tree-option'),
|
|
349
|
-
attrs: (row: any) => ({
|
|
350
|
-
multiple: isMultiValueOperator(row.operator),
|
|
351
|
-
collapseTags: true,
|
|
352
|
-
maxCollapseTags: 10,
|
|
353
|
-
collapseTagsTooltip: true
|
|
354
|
-
}),
|
|
355
|
-
style: { width: '240px' }
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
name: 'value',
|
|
359
|
-
label: false,
|
|
360
|
-
type: 'input-number',
|
|
361
|
-
placeholder: '输入数值',
|
|
362
|
-
visible: (row: any) => isValueEditorVisible(row, 'number'),
|
|
363
|
-
style: { width: '160px' }
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
name: 'value',
|
|
367
|
-
label: false,
|
|
368
|
-
type: 'date-picker',
|
|
369
|
-
placeholder: '选择日期',
|
|
370
|
-
visible: (row: any) => isValueEditorVisible(row, 'date'),
|
|
371
|
-
attrs: (row: any) => {
|
|
372
|
-
const meta = findFieldMeta(row.field)
|
|
373
|
-
return {
|
|
374
|
-
type: (meta?.item as any)?._type,
|
|
375
|
-
valueFormat: getDateValueFormat(meta)
|
|
376
|
-
}
|
|
377
|
-
},
|
|
378
|
-
style: { width: '180px' }
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
name: 'value',
|
|
382
|
-
label: false,
|
|
383
|
-
type: 'select',
|
|
384
|
-
placeholder: '选择值',
|
|
385
|
-
options: [
|
|
386
|
-
{ label: '是', value: true },
|
|
387
|
-
{ label: '否', value: false }
|
|
388
|
-
],
|
|
389
|
-
visible: (row: any) => isValueEditorVisible(row, 'boolean'),
|
|
390
|
-
style: { width: '140px' }
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
name: 'value',
|
|
394
|
-
label: false,
|
|
395
|
-
type: 'input',
|
|
396
|
-
placeholder: '输入内容',
|
|
397
|
-
visible: (row: any) => isValueEditorVisible(row, 'text'),
|
|
398
|
-
style: { width: '220px' }
|
|
399
|
-
}
|
|
400
|
-
])
|
|
401
|
-
|
|
402
|
-
const createEmptyBuilderValue = (): XConditionNode => ({
|
|
403
|
-
id: genId('cond'),
|
|
404
|
-
level: 0,
|
|
405
|
-
type: 'group',
|
|
406
|
-
data: { relationType: 'and' },
|
|
407
|
-
children: []
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
const toValueList = (value: any) => {
|
|
411
|
-
if (Array.isArray(value)) return value
|
|
412
|
-
if (value === undefined || value === null || value === '') return []
|
|
413
|
-
return [value]
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const normalizeDateValue = (value: any, field: string): any => {
|
|
417
|
-
const meta = findFieldMeta(field)
|
|
418
|
-
if (meta?.valueKind !== 'date') return value
|
|
419
|
-
const format = getDateValueFormat(meta)
|
|
420
|
-
if (Array.isArray(value)) return value.map((item) => normalizeDateValue(item, field))
|
|
421
|
-
if (value instanceof Date) return dayjs(value).format(format)
|
|
422
|
-
return value
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const createVarExpression = (field: string) => ['var', `form.${field}`]
|
|
426
|
-
|
|
427
|
-
const buildPositiveExpression = (operator: EditorOperator, left: any[], value: any) => {
|
|
428
|
-
if (operator === 'eq') return ['==', left, value]
|
|
429
|
-
if (operator === 'neq') return ['!=', left, value]
|
|
430
|
-
if (operator === 'gt') return ['>', left, value]
|
|
431
|
-
if (operator === 'gte') return ['>=', left, value]
|
|
432
|
-
if (operator === 'lt') return ['<', left, value]
|
|
433
|
-
if (operator === 'lte') return ['<=', left, value]
|
|
434
|
-
if (operator === 'exists') return ['exists', left]
|
|
435
|
-
if (operator === 'empty') return ['empty', left]
|
|
436
|
-
if (operator === 'in') return ['in', left, toValueList(value)]
|
|
437
|
-
if (operator === 'contains') {
|
|
438
|
-
const values = toValueList(value)
|
|
439
|
-
if (values.length > 1) return ['or', ...values.map((item) => ['includes', left, item])]
|
|
440
|
-
return ['includes', left, values[0]]
|
|
441
|
-
}
|
|
442
|
-
return undefined
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const isMultipleField = (field: string) => {
|
|
446
|
-
const meta = findFieldMeta(field)
|
|
447
|
-
return Boolean(meta?.multiple || meta?.valueKind === 'option-multiple')
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const buildAnyIncludesExpression = (left: any[], values: any[]) => {
|
|
451
|
-
if (values.length > 1) return ['or', ...values.map((item) => ['includes', left, item])]
|
|
452
|
-
return ['includes', left, values[0]]
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const buildExpressionFromCondition = (field: string, operator: EditorOperator, value: any) => {
|
|
456
|
-
const left = createVarExpression(field)
|
|
457
|
-
const normalizedValue = normalizeDateValue(value, field)
|
|
458
|
-
if (operator === 'in' && isMultipleField(field)) return buildAnyIncludesExpression(left, toValueList(normalizedValue))
|
|
459
|
-
if (operator === 'not_in' && isMultipleField(field)) {
|
|
460
|
-
return ['not', buildAnyIncludesExpression(left, toValueList(normalizedValue))]
|
|
461
|
-
}
|
|
462
|
-
if (operator === 'not_in') return ['not', ['in', left, toValueList(normalizedValue)]]
|
|
463
|
-
if (operator === 'not_contains') {
|
|
464
|
-
const values = toValueList(normalizedValue)
|
|
465
|
-
return ['not', buildAnyIncludesExpression(left, values)]
|
|
466
|
-
}
|
|
467
|
-
return buildPositiveExpression(operator, left, normalizedValue)
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const buildExpressionFromNode = (node?: XConditionNode): any[] | undefined => {
|
|
471
|
-
if (!node) return undefined
|
|
472
|
-
if (node.type === 'group') {
|
|
473
|
-
const relation = node.data?.relationType === 'or' ? 'or' : 'and'
|
|
474
|
-
const children = (node.children || []).map((child) => buildExpressionFromNode(child)).filter(Boolean) as any[]
|
|
475
|
-
if (children.length === 0) return undefined
|
|
476
|
-
return [relation, ...children]
|
|
477
|
-
}
|
|
478
|
-
const field = node.data?.field
|
|
479
|
-
const operator = node.data?.operator as EditorOperator | undefined
|
|
480
|
-
if (!field || !operator) return undefined
|
|
481
|
-
if (multiValueOperators.has(operator) && toValueList(node.data?.value).length === 0) return undefined
|
|
482
|
-
if (!unaryOperators.has(operator) && (node.data?.value === undefined || node.data?.value === null || node.data?.value === '')) {
|
|
483
|
-
return undefined
|
|
484
|
-
}
|
|
485
|
-
return buildExpressionFromCondition(field, operator, node.data?.value)
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
const extractVarPath = (value: any) => {
|
|
489
|
-
if (Array.isArray(value) && value[0] === 'var' && typeof value[1] === 'string') return value[1] as string
|
|
490
|
-
return undefined
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const stripScopePrefix = (path: string) => {
|
|
494
|
-
const prefixes = ['form.', 'formData.', 'row.', 'rowData.']
|
|
495
|
-
const matched = prefixes.find((prefix) => path.startsWith(prefix))
|
|
496
|
-
return matched ? path.slice(matched.length) : path
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const createConditionNode = (level: number, fieldPath: string, operator: EditorOperator, value?: any): XConditionNode => ({
|
|
500
|
-
id: genId('cond'),
|
|
501
|
-
level,
|
|
502
|
-
type: 'condition',
|
|
503
|
-
data: {
|
|
504
|
-
field: stripScopePrefix(fieldPath),
|
|
505
|
-
operator,
|
|
506
|
-
value
|
|
507
|
-
}
|
|
508
|
-
})
|
|
509
|
-
|
|
510
|
-
const parseExpressionToNode = (expression: any, level = 0): XConditionNode | undefined => {
|
|
511
|
-
if (!Array.isArray(expression)) return undefined
|
|
512
|
-
const [operator, ...args] = expression
|
|
513
|
-
if (operator === 'and' || operator === 'or') {
|
|
514
|
-
const children = args.map((item) => parseExpressionToNode(item, level + 1)).filter(Boolean) as XConditionNode[]
|
|
515
|
-
return {
|
|
516
|
-
id: genId('cond'),
|
|
517
|
-
level,
|
|
518
|
-
type: 'group',
|
|
519
|
-
data: { relationType: operator },
|
|
520
|
-
children
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
if (operator === 'not') {
|
|
524
|
-
const inner = parseExpressionToNode(args[0], level)
|
|
525
|
-
if (!inner || inner.type !== 'condition') return undefined
|
|
526
|
-
if (inner.data?.operator === 'in') inner.data.operator = 'not_in'
|
|
527
|
-
if (inner.data?.operator === 'contains') inner.data.operator = 'not_contains'
|
|
528
|
-
return inner
|
|
529
|
-
}
|
|
530
|
-
if (typeof operator !== 'string') return undefined
|
|
531
|
-
if (operator === 'exists' || operator === 'empty') {
|
|
532
|
-
const fieldPath = extractVarPath(args[0])
|
|
533
|
-
return fieldPath ? createConditionNode(level, fieldPath, operator, undefined) : undefined
|
|
534
|
-
}
|
|
535
|
-
const fieldPath = extractVarPath(args[0])
|
|
536
|
-
if (!fieldPath) return undefined
|
|
537
|
-
if (operator === '==') return createConditionNode(level, fieldPath, 'eq', args[1])
|
|
538
|
-
if (operator === '!=') return createConditionNode(level, fieldPath, 'neq', args[1])
|
|
539
|
-
if (operator === '>') return createConditionNode(level, fieldPath, 'gt', args[1])
|
|
540
|
-
if (operator === '>=') return createConditionNode(level, fieldPath, 'gte', args[1])
|
|
541
|
-
if (operator === '<') return createConditionNode(level, fieldPath, 'lt', args[1])
|
|
542
|
-
if (operator === '<=') return createConditionNode(level, fieldPath, 'lte', args[1])
|
|
543
|
-
if (operator === 'in') return createConditionNode(level, fieldPath, 'in', args[1])
|
|
544
|
-
if (operator === 'includes') return createConditionNode(level, fieldPath, 'contains', args[1])
|
|
545
|
-
return undefined
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const buildBuilderValue = (expression: any) => {
|
|
549
|
-
const parsed = parseExpressionToNode(expression, 0)
|
|
550
|
-
return parsed?.type === 'group' ? parsed : createEmptyBuilderValue()
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const builderConditionCount = computed(() => {
|
|
554
|
-
const countExpression = (expression: any): number => {
|
|
555
|
-
if (!Array.isArray(expression)) return 0
|
|
556
|
-
const [operator, ...args] = expression
|
|
557
|
-
if (operator === 'and' || operator === 'or') return args.reduce((sum, item) => sum + countExpression(item), 0)
|
|
558
|
-
if (operator === 'not') return countExpression(args[0])
|
|
559
|
-
return 1
|
|
560
|
-
}
|
|
561
|
-
return Array.isArray(props.modelValue) ? countExpression(props.modelValue) : 0
|
|
562
|
-
})
|
|
563
|
-
|
|
564
|
-
const ensureConditionMode = () => {
|
|
565
|
-
if (!Array.isArray(props.modelValue)) emit('update:modelValue', [])
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const handleModeChange = (val: any) => {
|
|
569
|
-
if (val === true || val === false) {
|
|
570
|
-
emit('update:modelValue', val)
|
|
571
|
-
return
|
|
572
|
-
}
|
|
573
|
-
ensureConditionMode()
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const handleOpen = () => {
|
|
577
|
-
ensureConditionMode()
|
|
578
|
-
builderValue.value = buildBuilderValue(props.modelValue)
|
|
579
|
-
dialogVisible.value = true
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
const handleSave = () => {
|
|
583
|
-
const expression = buildExpressionFromNode(builderValue.value)
|
|
584
|
-
emit('update:modelValue', expression ?? [])
|
|
585
|
-
dialogVisible.value = false
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
watch(
|
|
589
|
-
() => props.modelValue,
|
|
590
|
-
(val) => {
|
|
591
|
-
if (dialogVisible.value && Array.isArray(val)) {
|
|
592
|
-
builderValue.value = buildBuilderValue(val)
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
)
|
|
596
|
-
|
|
597
|
-
watch(
|
|
598
|
-
() => props.autoOpenKey,
|
|
599
|
-
(key, oldKey) => {
|
|
600
|
-
if (key === undefined || key === oldKey) return
|
|
601
|
-
handleOpen()
|
|
602
|
-
}
|
|
603
|
-
)
|
|
604
|
-
|
|
605
|
-
defineExpose({
|
|
606
|
-
open: handleOpen
|
|
607
|
-
})
|
|
608
|
-
</script>
|
|
609
|
-
|
|
610
|
-
<style scoped>
|
|
611
|
-
.condition-editor {
|
|
612
|
-
width: 100%;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
.condition-actions {
|
|
616
|
-
margin-top: 8px;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
.condition-preview {
|
|
620
|
-
margin-top: 4px;
|
|
621
|
-
font-size: 12px;
|
|
622
|
-
color: #606266;
|
|
623
|
-
background: #f8f9fa;
|
|
624
|
-
padding: 4px 8px;
|
|
625
|
-
border-radius: 4px;
|
|
626
|
-
word-break: break-all;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
.condition-dialog {
|
|
630
|
-
min-height: 220px;
|
|
631
|
-
padding: 16px;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
.condition-builder {
|
|
635
|
-
min-height: 200px;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
.w-full {
|
|
639
|
-
width: 100%;
|
|
640
|
-
}
|
|
641
|
-
</style>
|