useFieldDependencies.js 5.62 KB
/**
 * 字段关联系统 Composable
 *
 * @description 管理计划书字段之间的关联关系(显示/隐藏、启用/禁用)
 * @module composables/useFieldDependencies
 * @author Claude Code
 * @created 2026-02-14
 */

import { computed, reactive, isRef } from 'vue'
import { PLAN_FIELD_DEFINITIONS } from '@/config/plan-fields'

/**
 * �测循环依赖
 *
 * @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 || {}
  }

  /**
   * 检查字段是否应该显示
   *
   * @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 conditions = definition.show_when
      if (Array.isArray(conditions)) {
        for (const condition of conditions) {
          if (!condition) continue
          const currentValue = formData[condition.field]
          if (currentValue !== condition.equals) {
            return false
          }
        }
      } else if (conditions && typeof conditions === 'object') {
        for (const [depKey, expectedValue] of Object.entries(conditions)) {
          const currentValue = formData[depKey]
          if (currentValue !== expectedValue) {
            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()

  return {
    // 状态
    fieldVisibility,
    fieldEnabled,
    visibleFields,

    // 方法
    isFieldVisible,
    isFieldEnabled,
    updateFieldValue,
    initFieldStates
  }
}