From 3015fc956f0ffb2279216ec6cfc180793a9ce786 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 Date: Wed, 26 Aug 2026 11:56:24 +0800 Subject: [PATCH] fix: Optimize the execution logic of dynamic forms --- ui/src/api/types/model.ts | 1 - ui/src/components/mk-dynamics-form/Demo.vue | 541 +++++++----------- .../mk-dynamics-form/DemoConstructor.vue | 217 ------- .../constructor/BasicInfoConstructor.vue | 145 +++++ .../mk-dynamics-form/constructor/index.vue | 272 ++------- .../items/JsonInputConstructor.vue | 2 +- .../items/KnowledgeConstructor.vue | 2 +- .../constructor/items/ModelConstructor.vue | 3 +- .../constructor/items/MultiRowConstructor.vue | 6 +- .../items/MultiSelectConstructor.vue | 6 +- .../items/RadioCardConstructor.vue | 6 +- .../constructor/items/RadioRowConstructor.vue | 6 +- .../items/SingleSelectConstructor.vue | 6 +- .../items/TreeSelectConstructor.vue | 12 +- .../items/UploadInputConstructor.vue | 2 +- .../visibility/ConditionRow.vue | 6 +- .../visibility/FieldSelector.vue | 2 +- .../visibility/index.ts} | 2 +- .../visibility/index.vue} | 15 +- ui/src/components/mk-dynamics-form/index.vue | 118 +++- .../mk-dynamics-form/items/JsonInput.vue | 2 +- .../items/common/SelectHeader.vue | 6 +- .../items/complex/ArrayObjectCard.vue | 4 +- .../items/label/SettingLabel.vue | 33 +- .../items/label/TooltipLabel.vue | 2 +- .../mk-dynamics-form/items/model/Model.vue | 3 +- .../items/upload/LocalFileUpload.vue | 7 +- .../items/upload/UploadInput.vue | 2 +- ui/src/components/mk-dynamics-form/type.ts | 4 +- .../mk-dynamics-form/visibility/index.ts | 188 ------ 30 files changed, 536 insertions(+), 1085 deletions(-) delete mode 100644 ui/src/components/mk-dynamics-form/DemoConstructor.vue create mode 100644 ui/src/components/mk-dynamics-form/constructor/BasicInfoConstructor.vue rename ui/src/components/mk-dynamics-form/{ => constructor}/visibility/ConditionRow.vue (97%) rename ui/src/components/mk-dynamics-form/{ => constructor}/visibility/FieldSelector.vue (96%) rename ui/src/components/mk-dynamics-form/{visibility/field-type.ts => constructor/visibility/index.ts} (97%) rename ui/src/components/mk-dynamics-form/{visibility/Constructor.vue => constructor/visibility/index.vue} (93%) delete mode 100644 ui/src/components/mk-dynamics-form/visibility/index.ts diff --git a/ui/src/api/types/model.ts b/ui/src/api/types/model.ts index acbd9a9a3cc..e6c3133d802 100644 --- a/ui/src/api/types/model.ts +++ b/ui/src/api/types/model.ts @@ -47,7 +47,6 @@ export interface DynamicFormField { input_type?: string label: string | DynamicFormLabel option_list?: Record[] - relation_show_field_dict?: Record required?: boolean text_field?: string value_field?: string diff --git a/ui/src/components/mk-dynamics-form/Demo.vue b/ui/src/components/mk-dynamics-form/Demo.vue index ac30f9893d6..76efb77ca18 100644 --- a/ui/src/components/mk-dynamics-form/Demo.vue +++ b/ui/src/components/mk-dynamics-form/Demo.vue @@ -1,357 +1,204 @@ + - + + diff --git a/ui/src/components/mk-dynamics-form/DemoConstructor.vue b/ui/src/components/mk-dynamics-form/DemoConstructor.vue deleted file mode 100644 index abeeb631954..00000000000 --- a/ui/src/components/mk-dynamics-form/DemoConstructor.vue +++ /dev/null @@ -1,217 +0,0 @@ - - - - - diff --git a/ui/src/components/mk-dynamics-form/constructor/BasicInfoConstructor.vue b/ui/src/components/mk-dynamics-form/constructor/BasicInfoConstructor.vue new file mode 100644 index 00000000000..eee504324a7 --- /dev/null +++ b/ui/src/components/mk-dynamics-form/constructor/BasicInfoConstructor.vue @@ -0,0 +1,145 @@ + + + diff --git a/ui/src/components/mk-dynamics-form/constructor/index.vue b/ui/src/components/mk-dynamics-form/constructor/index.vue index 66f1c30cbc2..46e4b63b212 100644 --- a/ui/src/components/mk-dynamics-form/constructor/index.vue +++ b/ui/src/components/mk-dynamics-form/constructor/index.vue @@ -1,193 +1,42 @@ diff --git a/ui/src/components/mk-dynamics-form/index.vue b/ui/src/components/mk-dynamics-form/index.vue index 5226c883ae7..92317757247 100644 --- a/ui/src/components/mk-dynamics-form/index.vue +++ b/ui/src/components/mk-dynamics-form/index.vue @@ -41,7 +41,7 @@ import type { FormInstance } from 'element-plus' import type { ApiResponse } from '@/api/admin/core/types' import _ from 'lodash' import { get, post, put, del } from '@/api/admin/core/request' -import { computeVisibilityMap } from './visibility' +import type { CompareOptions, VisibilityCondition, VisibilityRules } from './type' const request = { get, post, @@ -82,33 +82,107 @@ const ruleFormRef = ref() const formFieldRef = ref>>([]) -const visibilityMap = computed(() => computeVisibilityMap(formFieldList.value, formValue.value)) +// ===== 显隐规则求值 ===== +const containImpl = (source: any, target: any): boolean => { + if (Array.isArray(target)) { + return target.every((t) => containImpl(source, t)) + } + const t = String(target) + if (typeof source === 'string') return source.includes(t) + if (Array.isArray(source)) return source.some((item) => String(item) === t) + return String(source).includes(t) +} + +const numOrStrCmp = ( + left: any, + right: any, + numFn: (a: number, b: number) => boolean, + strFn: (a: string, b: string) => boolean, +): boolean => { + const a = Number(left) + const b = Number(right) + if (!Number.isNaN(a) && !Number.isNaN(b)) return numFn(a, b) + try { + return strFn(String(left), String(right)) + } catch { + return false + } +} + +const compareHandlers: Record boolean> = { + eq: (l, r) => String(l) === String(r), + not_eq: (l, r) => String(l) !== String(r), + contain: (l, r) => containImpl(l, r), + not_contain: (l, r) => !containImpl(l, r), + is_true: (l) => l === true, + is_not_true: (l) => l !== true, + gt: (l, r) => numOrStrCmp(l, r, (a, b) => a > b, (a, b) => a > b), + ge: (l, r) => numOrStrCmp(l, r, (a, b) => a >= b, (a, b) => a >= b), + lt: (l, r) => numOrStrCmp(l, r, (a, b) => a < b, (a, b) => a < b), + le: (l, r) => numOrStrCmp(l, r, (a, b) => a <= b, (a, b) => a <= b), +} + +const compareByOp = (left: any, op: CompareOptions, right: any): boolean => { + const fn = compareHandlers[op] + if (!fn) throw new Error(`Unknown compare op: ${op}`) + return fn(left, right) +} /** - * 当前 field是否展示 - * @param field + * 取条件左值:self 为真时实时从本表单 formValue 取,否则用预填 leftValue。 */ -const show = (field: FormField) => { - if (field.relation_show_field_dict) { - const keys: Array = Object.keys(field.relation_show_field_dict) - for (const index in keys) { - const key: string = keys[index] as string - const v = _.get(formValue.value, key) - if (v && v !== undefined && v !== null) { - const values = field.relation_show_field_dict[key] - if (values && values.length > 0) { - return values.includes(v) - } else { - return true - } - } else { - return false - } +const lookupLeft = (cond: VisibilityCondition, values: Dict): any => { + if (cond.self) { + return values?.[cond.field[1]] + } + return cond.leftValue +} + +/** + * 对单条 visibility_rules 求值,返回该字段是否可见。 + */ +const evaluateVisibility = (rules: VisibilityRules | null | undefined, values: Dict): boolean => { + if (!rules || !rules.conditions || rules.conditions.length === 0) { + return true + } + const results = rules.conditions.map((cond) => { + const left = lookupLeft(cond, values) + if (left == null && cond.compare !== 'is_true' && cond.compare !== 'is_not_true') { + return false + } + return compareByOp(left, cond.compare as CompareOptions, cond.value) + }) + const matched = rules.condition === 'or' ? results.some(Boolean) : results.every(Boolean) + return rules.action === 'show' ? matched : !matched +} + +/** + * 单向扫描当前表单字段列表,计算显隐表。 + * 前面字段被隐藏后其值置空,级联影响后续字段判定。 + */ +const visibilityMap = computed>(() => { + const copy: Dict = { ...formValue.value } + const map: Dict = {} + for (const field of formFieldList.value) { + if (!field.visibility_rules?.conditions?.length) { + map[field.field] = true + continue + } + const visible = evaluateVisibility(field.visibility_rules, copy) + map[field.field] = visible + if (!visible) { + copy[field.field] = null } } + return map +}) - // new - if (field.visibility_rules?.node_id) { +/** + * 当前 field是否展示 + * @param field + */ +const show = (field: FormField) => { + if (field.visibility_rules?.conditions?.length) { return visibilityMap.value[field.field] ?? true } diff --git a/ui/src/components/mk-dynamics-form/items/JsonInput.vue b/ui/src/components/mk-dynamics-form/items/JsonInput.vue index 59502cf5770..8386a0e2c5a 100644 --- a/ui/src/components/mk-dynamics-form/items/JsonInput.vue +++ b/ui/src/components/mk-dynamics-form/items/JsonInput.vue @@ -16,7 +16,7 @@ diff --git a/ui/src/components/mk-dynamics-form/items/common/SelectHeader.vue b/ui/src/components/mk-dynamics-form/items/common/SelectHeader.vue index 019593c3827..b774efc4cea 100644 --- a/ui/src/components/mk-dynamics-form/items/common/SelectHeader.vue +++ b/ui/src/components/mk-dynamics-form/items/common/SelectHeader.vue @@ -9,11 +9,11 @@ - + > diff --git a/ui/src/components/mk-dynamics-form/items/complex/ArrayObjectCard.vue b/ui/src/components/mk-dynamics-form/items/complex/ArrayObjectCard.vue index 696a82c01e2..15ca48f31df 100644 --- a/ui/src/components/mk-dynamics-form/items/complex/ArrayObjectCard.vue +++ b/ui/src/components/mk-dynamics-form/items/complex/ArrayObjectCard.vue @@ -16,13 +16,13 @@ > - +
- + {{ add_msg }}
diff --git a/ui/src/components/mk-dynamics-form/items/label/SettingLabel.vue b/ui/src/components/mk-dynamics-form/items/label/SettingLabel.vue index 2369117ca0c..874dc3433b8 100644 --- a/ui/src/components/mk-dynamics-form/items/label/SettingLabel.vue +++ b/ui/src/components/mk-dynamics-form/items/label/SettingLabel.vue @@ -8,10 +8,10 @@ - + - - + +