useFieldDependencies.js
9.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
/**
* 字段关联系统 Composable
*
* @description 管理计划书字段之间的关联关系(显示/隐藏、启用/禁用)
* @module composables/useFieldDependencies
* @author Claude Code
* @created 2026-02-14
* @updated 2026-02-15 - 集成新的条件评估引擎,支持复杂条件
*/
import { computed, reactive, isRef, watch } from 'vue'
import { PLAN_FIELD_DEFINITIONS } from '@/config/plan-fields'
import {
evaluateCondition,
getConditionDependencies,
convertToNewFormat
} from '@/config/plan-conditions'
/**
* �测循环依赖
*
* @private
* @param {string} fieldKey - 字段键名
* @param {Set<string>} visited - 已访问的字段集合(用于递归)
* @returns {boolean} 是否存在循环依赖
*
* @example
* // 场景:A 依赖 B,B 依赖 C,C 依赖 A(循环)
* detectCircularDeps('A') // false
* detectCircularDeps('B') // true
* detectCircularDeps('C') // true
*/
function detectCircularDeps(fieldKey, fieldDefinitions, visited = new Set()) {
// 防止无限递归
if (visited.size > 50) {
console.error('[useFieldDependencies] 依赖层级过深,可能存在循环依赖')
return true
}
// 检查是否已访问
if (visited.has(fieldKey)) {
console.error(`[useFieldDependencies] �测到循环依赖: ${[...visited, fieldKey].join(' -> ')}`)
return true
}
visited.add(fieldKey)
const definition = fieldDefinitions[fieldKey]
if (!definition?.affects) return false
// 递归检查依赖字段
for (const depKey of definition.affects) {
if (detectCircularDeps(depKey, fieldDefinitions, visited)) {
return true
}
}
visited.delete(fieldKey)
return false
}
/**
* 字段关联系统
*
* @description 管理字段的显示/隐藏状态,根据字段关联关系自动更新
* @param {Object} formData - 表单数据
* @returns {Object} 字段关联管理方法和状态
*
* @example
* const { visibleFields, updateFieldValue, isFieldVisible, isFieldEnabled } = useFieldDependencies(formData)
*
* // 检查字段是否可见
* if (isFieldVisible('withdrawal_mode')) {
* // 处理逻辑
* }
*
* // 更新字段值
* updateFieldValue('withdrawal_enabled', true)
*
* // 获取所有可见字段
* const visible = visibleFields.value
*/
export function useFieldDependencies(formData, fieldDefinitions = PLAN_FIELD_DEFINITIONS) {
// 字段显示状态映射
const fieldVisibility = reactive({})
// 字段启用状态映射
const fieldEnabled = reactive({})
const getFieldDefinitions = () => {
const definitions = isRef(fieldDefinitions) ? fieldDefinitions.value : fieldDefinitions
return definitions || {}
}
/**
* 检查字段是否应该显示
*
* @description 使用新的条件评估引擎,支持复杂条件(AND/OR/NOT/嵌套)
* @param {string} fieldKey - 字段键名
* @returns {boolean} 是否显示
*/
function isFieldVisible(fieldKey) {
const definitions = getFieldDefinitions()
const definition = definitions[fieldKey]
if (!definition) return false
// 使用新的条件评估引擎评估 show_when
if (definition.show_when) {
// 转换为新格式(向后兼容)
const normalizedCondition = convertToNewFormat(definition.show_when)
if (!evaluateCondition(normalizedCondition, formData)) {
return false
}
}
// 检查是否被依赖字段影响(旧逻辑,保持兼容)
for (const [key, def] of Object.entries(definitions)) {
if (def.affects?.includes(fieldKey)) {
// 依赖字段必须为 true 才显示
if (formData[key] !== true) {
return false
}
}
}
return true
}
/**
* 检查字段是否启用
*
* @param {string} fieldKey - 字段键名
* @returns {boolean} 是否启用
*/
function isFieldEnabled(fieldKey) {
const definition = getFieldDefinitions()[fieldKey]
if (!definition) return false
// 如果有依赖字段,检查依赖字段是否满足
if (definition.depends_on) {
const depValue = formData[definition.depends_on]
return depValue === true
}
return true
}
/**
* 更新字段值并更新关联状态
*
* @param {string} fieldKey - 字段键名
* @param {*} value - 新值
*/
function updateFieldValue(fieldKey, value) {
formData[fieldKey] = value
// 更新受影响字段的显示状态
const definition = getFieldDefinitions()[fieldKey]
if (definition?.affects) {
for (const affectedKey of definition.affects) {
fieldVisibility[affectedKey] = isFieldVisible(affectedKey)
fieldEnabled[affectedKey] = isFieldEnabled(affectedKey)
}
}
}
/**
* 获取所有可见字段列表
*
* @returns {string[]} 可见字段键名数组
*/
const visibleFields = computed(() => {
return Object.keys(getFieldDefinitions()).filter(key => isFieldVisible(key))
})
/**
* 初始化所有字段的显示状态(包含循环依赖检测)
*/
function initFieldStates() {
const definitions = getFieldDefinitions()
// 开发环境检测循环依赖
if (process.env.NODE_ENV === 'development') {
for (const key of Object.keys(definitions)) {
detectCircularDeps(key, definitions)
}
}
for (const key of Object.keys(definitions)) {
fieldVisibility[key] = isFieldVisible(key)
fieldEnabled[key] = isFieldEnabled(key)
}
}
// 初始化
initFieldStates()
/**
* 清理隐藏字段的值
*
* @description 当字段隐藏时,根据 clear_when_hidden 配置决定是否清空值
* @param {string} fieldKey - 字段键名
* @param {Object} definition - 字段定义
*/
function clearHiddenFieldValue(fieldKey, definition) {
// 默认行为:隐藏时不清空(保持向后兼容)
const clearConfig = definition.clear_when_hidden
// false 或 undefined:不清空
if (clearConfig === false || clearConfig === undefined) {
return
}
// true:清空为 undefined
if (clearConfig === true) {
formData[fieldKey] = undefined
return
}
// null:设置为 null
if (clearConfig === null) {
formData[fieldKey] = null
return
}
// 对象配置
if (typeof clearConfig === 'object') {
// 清空自身
if (clearConfig.clear_self !== false) {
formData[fieldKey] = undefined
}
// 级联清空依赖字段
if (Array.isArray(clearConfig.clear_dependents)) {
for (const depKey of clearConfig.clear_dependents) {
if (formData[depKey] !== undefined) {
formData[depKey] = undefined
}
}
}
}
}
/**
* 更新字段可见性并处理隐藏字段的清理
*
* @param {string} fieldKey - 字段键名
* @param {string} triggerFieldKey - 触发更新的字段键名(可选)
*/
function updateFieldVisibility(fieldKey, triggerFieldKey = null) {
const definitions = getFieldDefinitions()
const definition = definitions[fieldKey]
if (!definition) return
const wasVisible = fieldVisibility[fieldKey]
const isVisible = isFieldVisible(fieldKey)
fieldVisibility[fieldKey] = isVisible
// 如果从可见变为不可见,且配置了清理规则,则清理字段值
if (wasVisible && !isVisible) {
clearHiddenFieldValue(fieldKey, definition)
}
// 更新启用状态
fieldEnabled[fieldKey] = isFieldEnabled(fieldKey)
}
/**
* 批量更新所有字段的可见性
*
* @description 通常在表单值变化后调用,重新计算所有字段的可见性
*/
function refreshAllVisibility() {
const definitions = getFieldDefinitions()
for (const key of Object.keys(definitions)) {
updateFieldVisibility(key)
}
}
return {
// 状态
fieldVisibility,
fieldEnabled,
visibleFields,
// 方法
isFieldVisible,
isFieldEnabled,
updateFieldValue,
initFieldStates,
updateFieldVisibility,
refreshAllVisibility
}
}
/**
* 过滤隐藏字段(用于提交前)
*
* @description 过滤掉当前不可见的字段,避免提交脏数据
* @param {Object} formData - 完整的表单数据
* @param {string[]} visibleFields - 可见字段列表
* @param {Object} options - 配置选项
* @param {string[]} options.alwaysInclude - 始终包含的字段(即使不可见)
* @returns {Object} 过滤后的表单数据
*
* @example
* const filteredData = filterHiddenFields(formData, visibleFields.value)
* await submitAPI(filteredData)
*/
export function filterHiddenFields(formData, visibleFields, options = {}) {
const { alwaysInclude = [] } = options
const filtered = {}
for (const [key, value] of Object.entries(formData)) {
// 可见字段始终包含
if (visibleFields.includes(key)) {
filtered[key] = value
continue
}
// 配置为始终包含的字段
if (alwaysInclude.includes(key)) {
filtered[key] = value
}
// 其他不可见字段被过滤掉
}
return filtered
}
/**
* 获取字段的条件依赖关系(用于调试和可视化)
*
* @description 返回字段的条件依赖图
* @param {Object} fieldDefinitions - 字段定义
* @returns {Map<string, Set<string>>} 字段到依赖字段的映射
*/
export function getFieldDependencyGraph(fieldDefinitions = PLAN_FIELD_DEFINITIONS) {
const graph = new Map()
for (const [key, definition] of Object.entries(fieldDefinitions)) {
const deps = new Set()
// 从 show_when 提取依赖
if (definition.show_when) {
const conditionDeps = getConditionDependencies(definition.show_when)
conditionDeps.forEach(d => deps.add(d))
}
// 从 affects 提取反向依赖
if (definition.affects) {
for (const affectedKey of definition.affects) {
if (!graph.has(affectedKey)) {
graph.set(affectedKey, new Set())
}
graph.get(affectedKey).add(key)
}
}
// 从 depends_on 提取
if (definition.depends_on) {
deps.add(definition.depends_on)
}
if (deps.size > 0) {
graph.set(key, deps)
}
}
return graph
}