@robot-admin/naive-ui-components 0.3.1 → 0.3.2

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 (43) hide show
  1. package/dist/C_Form.cjs +1 -0
  2. package/dist/C_Form.js +1 -0
  3. package/dist/C_Form2.js +22 -25
  4. package/dist/C_Form2.js.map +1 -1
  5. package/dist/C_Table.cjs +1 -0
  6. package/dist/C_Table.js +1 -0
  7. package/dist/constants.d.ts +4 -4
  8. package/dist/constants2.d.ts +5 -5
  9. package/dist/constants3.d.ts +2 -2
  10. package/dist/constants4.d.ts +5 -5
  11. package/dist/constants5.d.ts +2 -2
  12. package/dist/data.d.ts +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/index10.vue.d.ts +5 -5
  15. package/dist/index11.vue.d.ts +2 -2
  16. package/dist/index12.vue.d.ts +5 -5
  17. package/dist/index13.vue.d.ts +2 -2
  18. package/dist/index14.vue.d.ts +1 -1
  19. package/dist/index16.vue.d.ts +3 -3
  20. package/dist/index16.vue.d.ts.map +1 -1
  21. package/dist/index2.vue.d.ts +3 -3
  22. package/dist/index3.vue.d.ts +2 -2
  23. package/dist/index4.vue.d.ts +2 -2
  24. package/dist/index5.vue.d.ts +2 -2
  25. package/dist/index6.vue.d.ts +3 -3
  26. package/dist/index8.vue.d.ts +2 -2
  27. package/dist/resolver.js.map +1 -1
  28. package/dist/useCalendarEvents.d.ts +2 -2
  29. package/dist/useCollapsePanel.d.ts +2 -2
  30. package/dist/useCropperCore.d.ts +5 -5
  31. package/dist/useDraggableLayout.d.ts +3 -3
  32. package/dist/useDynamicFormState.d.ts +118 -118
  33. package/dist/useDynamicFormState.d.ts.map +1 -1
  34. package/dist/useEdgeInteraction.d.ts +1 -1
  35. package/dist/useInfiniteScroll.d.ts +1 -1
  36. package/dist/useModalEdit.d.ts +2 -2
  37. package/dist/useQRCode.d.ts +4 -4
  38. package/dist/useQRCode.d.ts.map +1 -1
  39. package/dist/useSignatureHistory.d.ts +1 -1
  40. package/dist/useTimeSelection.d.ts +2 -2
  41. package/dist/useTreeOperations.d.ts +6 -6
  42. package/dist/useWorkflowValidation.d.ts +4 -4
  43. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"C_Form2.js","names":["formItems","formItems","formItems","formItems","formItems","formItems","formItems","$attrs"],"sources":["../src/components/C_Form/composables/useFormConfig.ts","../src/components/C_Form/composables/useFormState.ts","../src/components/C_Form/composables/useFormRenderer.ts","../src/components/C_Form/layouts/Default/index.vue","../src/components/C_Form/layouts/Default/index.vue","../src/components/C_Form/layouts/Default/index.vue","../src/components/C_Form/layouts/Inline/index.vue","../src/components/C_Form/layouts/Inline/index.vue","../src/components/C_Form/layouts/Inline/index.vue","../src/components/C_Form/layouts/Grid/index.vue","../src/components/C_Form/layouts/Grid/index.vue","../src/components/C_Form/layouts/Grid/index.vue","../src/components/C_Form/layouts/Card/index.vue","../src/components/C_Form/layouts/Card/index.vue","../src/components/C_Form/layouts/Card/index.vue","../src/components/C_Form/layouts/Tabs/index.vue","../src/components/C_Form/layouts/Tabs/index.vue","../src/components/C_Form/layouts/Tabs/index.vue","../src/components/C_Form/layouts/Steps/index.vue","../src/components/C_Form/layouts/Steps/index.vue","../src/components/C_Form/layouts/Steps/index.vue","../src/components/C_Form/composables/useDynamicFormState.ts","../src/components/C_Form/layouts/Dynamic/index.vue","../src/components/C_Form/layouts/Dynamic/index.vue","../src/components/C_Form/layouts/Dynamic/index.vue","../src/components/C_Form/layouts/Custom/index.vue","../src/components/C_Form/layouts/Custom/index.vue","../src/components/C_Form/layouts/Custom/index.vue","../src/components/C_Form/index.vue","../src/components/C_Form/index.vue","../src/components/C_Form/index.vue"],"sourcesContent":["/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: C_Form 配置解析 Composable — 统一 FormConfig 接口与默认值\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport type {\r\n LayoutType,\r\n LabelPlacement,\r\n GridLayoutConfig,\r\n InlineLayoutConfig,\r\n CardLayoutConfig,\r\n TabsLayoutConfig,\r\n StepsLayoutConfig,\r\n DynamicLayoutConfig,\r\n CustomLayoutConfig,\r\n DynamicFieldConfig,\r\n RenderMode,\r\n FormOption,\r\n} from \"../types\";\r\n\r\n/* =================== FormConfig 类型定义 =================== */\r\n\r\n/** 布局回调事件 — 替代原先 16 个 emit 中的纯透传事件 */\r\nexport interface LayoutCallbacks {\r\n /* tabs 布局回调 */\r\n onTabChange?: (tabKey: string, tabIndex: number) => void;\r\n onTabValidate?: (tabKey: string) => void;\r\n\r\n /* steps 布局回调 */\r\n onStepChange?: (stepIndex: number, stepKey: string) => void;\r\n onStepBeforeChange?: (currentStep: number, targetStep: number) => void;\r\n onStepValidate?: (stepIndex: number) => void;\r\n\r\n /* dynamic 布局回调 */\r\n onFieldAdd?: (fieldConfig: DynamicFieldConfig) => void;\r\n onFieldRemove?: (fieldId: string) => void;\r\n onFieldToggle?: (fieldId: string, visible: boolean) => void;\r\n onFieldsClear?: () => void;\r\n\r\n /* custom 布局回调 */\r\n onRenderModeChange?: (mode: RenderMode) => void;\r\n onGroupToggle?: (groupKey: string, collapsed: boolean) => void;\r\n onGroupReset?: (groupKey: string) => void;\r\n onFieldsChange?: (fields: FormOption[]) => void;\r\n}\r\n\r\n/**\r\n * C_Form 统一配置接口\r\n * @description 收拢原先 13 个分散 Props 为 1 个 config 对象\r\n * 默认值均在 FORM_DEFAULTS 中集中管理\r\n */\r\nexport interface FormConfig extends LayoutCallbacks {\r\n /** 布局类型,默认 'default' */\r\n layout?: LayoutType;\r\n /** 标签位置,默认 'left' */\r\n labelPlacement?: LabelPlacement;\r\n /** 标签宽度,默认 'auto' */\r\n labelWidth?: string | number;\r\n /** 尺寸,默认 'medium' */\r\n size?: \"small\" | \"medium\" | \"large\";\r\n /** 禁用整个表单,默认 false */\r\n disabled?: boolean;\r\n /** 只读,默认 false */\r\n readonly?: boolean;\r\n /** 显示默认提交/重置按钮,默认 true */\r\n showActions?: boolean;\r\n /** 值变化时自动校验,默认 false */\r\n validateOnChange?: boolean;\r\n\r\n /* ===== 布局级配置 ===== */\r\n grid?: GridLayoutConfig;\r\n inline?: InlineLayoutConfig;\r\n card?: CardLayoutConfig;\r\n tabs?: TabsLayoutConfig;\r\n steps?: StepsLayoutConfig;\r\n dynamic?: DynamicLayoutConfig;\r\n custom?: CustomLayoutConfig;\r\n}\r\n\r\n/** 解析后的配置(所有必填字段均已设置默认值) */\r\nexport interface ResolvedFormConfig extends Required<\r\n Omit<\r\n FormConfig,\r\n | keyof LayoutCallbacks\r\n | \"grid\"\r\n | \"inline\"\r\n | \"card\"\r\n | \"tabs\"\r\n | \"steps\"\r\n | \"dynamic\"\r\n | \"custom\"\r\n >\r\n> {\r\n /* 布局配置保留可选,因为只有对应 layout 时才有值 */\r\n grid?: GridLayoutConfig;\r\n inline?: InlineLayoutConfig;\r\n card?: CardLayoutConfig;\r\n tabs?: TabsLayoutConfig;\r\n steps?: StepsLayoutConfig;\r\n dynamic?: DynamicLayoutConfig;\r\n custom?: CustomLayoutConfig;\r\n /* 回调保留可选 */\r\n onTabChange?: LayoutCallbacks[\"onTabChange\"];\r\n onTabValidate?: LayoutCallbacks[\"onTabValidate\"];\r\n onStepChange?: LayoutCallbacks[\"onStepChange\"];\r\n onStepBeforeChange?: LayoutCallbacks[\"onStepBeforeChange\"];\r\n onStepValidate?: LayoutCallbacks[\"onStepValidate\"];\r\n onFieldAdd?: LayoutCallbacks[\"onFieldAdd\"];\r\n onFieldRemove?: LayoutCallbacks[\"onFieldRemove\"];\r\n onFieldToggle?: LayoutCallbacks[\"onFieldToggle\"];\r\n onFieldsClear?: LayoutCallbacks[\"onFieldsClear\"];\r\n onRenderModeChange?: LayoutCallbacks[\"onRenderModeChange\"];\r\n onGroupToggle?: LayoutCallbacks[\"onGroupToggle\"];\r\n onGroupReset?: LayoutCallbacks[\"onGroupReset\"];\r\n onFieldsChange?: LayoutCallbacks[\"onFieldsChange\"];\r\n}\r\n\r\n/* =================== 默认值常量 =================== */\r\n\r\nexport const FORM_DEFAULTS: ResolvedFormConfig = {\r\n layout: \"default\",\r\n labelPlacement: \"left\",\r\n labelWidth: \"auto\",\r\n size: \"medium\",\r\n disabled: false,\r\n readonly: false,\r\n showActions: true,\r\n validateOnChange: false,\r\n} as const;\r\n\r\n/** 拥有自身控制按钮的布局(不显示默认操作按钮) */\r\nexport const LAYOUTS_WITH_OWN_CONTROLS: readonly LayoutType[] = [\r\n \"steps\",\r\n \"custom\",\r\n] as const;\r\n\r\n/* =================== 核心函数 =================== */\r\n\r\n/**\r\n * 解析 FormConfig,合并默认值\r\n * @param config - 用户传入的配置(可选)\r\n * @returns 具有完整默认值的 ResolvedFormConfig\r\n */\r\nexport function resolveFormConfig(config?: FormConfig): ResolvedFormConfig {\r\n return { ...FORM_DEFAULTS, ...config };\r\n}\r\n\r\n/**\r\n * 计算是否显示默认操作按钮\r\n * @param resolved - 已解析的配置\r\n */\r\nexport function shouldShowActions(resolved: ResolvedFormConfig): boolean {\r\n if (resolved.showActions === false) return false;\r\n if (LAYOUTS_WITH_OWN_CONTROLS.includes(resolved.layout)) return false;\r\n return true;\r\n}\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: C_Form 状态引擎 Composable — 表单数据、校验规则、验证 API\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport {\r\n reactive,\r\n computed,\r\n watch,\r\n nextTick,\r\n onMounted,\r\n type ComputedRef,\r\n type Ref,\r\n} from 'vue'\r\nimport type { FormInst, FormRules } from 'naive-ui/es/form'\r\nimport { mergeRules } from '@robot-admin/form-validate'\r\nimport type {\r\n FormOption,\r\n FormModel,\r\n ComponentType,\r\n SubmitEventPayload,\r\n} from '../types'\r\nimport type { ResolvedFormConfig } from './useFormConfig'\r\n\r\n/* =================== 默认值映射 =================== */\r\n\r\n/** 各控件类型的默认空值 */\r\nconst DEFAULT_VALUES: Record<ComponentType, unknown> = {\r\n input: '',\r\n textarea: '',\r\n editor: '',\r\n select: null,\r\n datePicker: null,\r\n daterange: null,\r\n timePicker: null,\r\n cascader: null,\r\n colorPicker: null,\r\n checkbox: null,\r\n upload: [],\r\n radio: '',\r\n inputNumber: null,\r\n slider: null,\r\n rate: null,\r\n switch: null,\r\n} as const\r\n\r\nconst getDefaultValue = (type: ComponentType): unknown => {\r\n return DEFAULT_VALUES[type] ?? null\r\n}\r\n\r\n/* =================== Composable =================== */\r\n\r\n/**\r\n * C_Form 状态引擎 — 管理表单数据模型、校验规则、验证 API、字段操作\r\n * @param options - 表单配置项(响应式)\r\n * @param config - 解析后的表单配置(响应式)\r\n * @param formRef - NForm 实例引用\r\n * @param emit - 组件事件发射器\r\n */\r\nexport function useFormState(\r\n options: ComputedRef<FormOption[]>,\r\n config: ComputedRef<ResolvedFormConfig>,\r\n formRef: Ref<FormInst | null>,\r\n emit: {\r\n (e: 'update:modelValue', model: FormModel): void\r\n (e: 'validate-success', model: FormModel): void\r\n (e: 'validate-error', errors: unknown): void\r\n (e: 'submit', payload: SubmitEventPayload): void\r\n }\r\n) {\r\n /* ===== 响应式状态 ===== */\r\n const formModel = reactive<FormModel>({})\r\n const formRules = reactive<FormRules>({})\r\n\r\n /* ===== 可见字段 ===== */\r\n const visibleOptions = computed(() =>\r\n options.value.filter(item => item.show !== false)\r\n )\r\n\r\n /* ===== 初始化 ===== */\r\n const initialize = (): void => {\r\n try {\r\n /* 清空现有规则 */\r\n Object.keys(formRules).forEach(key => delete formRules[key])\r\n\r\n /* 初始化表单数据和验证规则 */\r\n options.value.forEach(item => {\r\n /*\r\n * 只为新增字段设置默认值,保留已有字段的用户输入\r\n * 解决 options 依赖 formData 时的循环重置问题\r\n */\r\n if (!(item.prop in formModel)) {\r\n formModel[item.prop] =\r\n item.value !== undefined\r\n ? item.value\r\n : getDefaultValue(item.type as ComponentType)\r\n }\r\n\r\n if (item.rules?.length) {\r\n /*\r\n * mergeRules 内部只调用 rule.validator?.(),\r\n * 原生 naive-ui 声明式规则(如 { required: true })没有 validator 会被跳过。\r\n * 仅当所有规则都有 validator 时才走 mergeRules 串行验证,否则直接交给 naive-ui 处理。\r\n */\r\n const allHaveValidator = item.rules.every(\r\n r => typeof (r as Record<string, unknown>).validator === 'function'\r\n )\r\n formRules[item.prop] = allHaveValidator\r\n ? mergeRules(item.rules)\r\n : item.rules\r\n }\r\n })\r\n } catch (error) {\r\n console.error('[C_Form] 初始化失败:', error)\r\n }\r\n }\r\n\r\n /* ===== 字段变化处理 ===== */\r\n const handleFieldChange = (field: string): void => {\r\n if (config.value.validateOnChange) {\r\n nextTick(() => {\r\n validateField(field).catch(() => {})\r\n })\r\n }\r\n }\r\n\r\n /* ===== 验证 API ===== */\r\n const validate = async (): Promise<void> => {\r\n if (!formRef.value) {\r\n throw new Error('[C_Form] 表单引用不存在')\r\n }\r\n\r\n try {\r\n await formRef.value.validate()\r\n emit('validate-success', getModel())\r\n } catch (errors) {\r\n emit('validate-error', errors)\r\n throw errors\r\n }\r\n }\r\n\r\n const validateField = async (field: string | string[]): Promise<void> => {\r\n if (!formRef.value) {\r\n throw new Error('[C_Form] 表单引用不存在')\r\n }\r\n\r\n const fields = Array.isArray(field) ? field : [field]\r\n\r\n /*\r\n * naive-ui 的 Form.validate() 不支持直接传字段名数组,\r\n * 需要验证整个表单后过滤出目标字段的错误\r\n */\r\n try {\r\n await formRef.value.validate()\r\n } catch (allErrors: unknown) {\r\n /* allErrors 是 ValidateError[][] — 每个错误数组含 field 信息 */\r\n if (!Array.isArray(allErrors)) throw allErrors\r\n\r\n /* 过滤出目标字段的错误 */\r\n const targetErrors = allErrors.filter((errorGroup: unknown[]) =>\r\n errorGroup?.some(err => {\r\n const e = err as Record<string, unknown>\r\n return typeof e.field === 'string' && fields.includes(e.field)\r\n })\r\n )\r\n\r\n if (targetErrors.length > 0) {\r\n throw targetErrors\r\n }\r\n /* 目标字段无错误,其他字段的错误忽略 */\r\n }\r\n }\r\n\r\n const clearValidation = (field?: string | string[]): void => {\r\n if (!formRef.value) return\r\n\r\n if (field) {\r\n const fields = Array.isArray(field) ? field : [field]\r\n fields.forEach(fieldName => {\r\n if (formModel[fieldName] !== undefined) {\r\n const currentValue = formModel[fieldName]\r\n formModel[fieldName] = currentValue\r\n }\r\n })\r\n } else {\r\n formRef.value.restoreValidation()\r\n }\r\n }\r\n\r\n const validateByFilter = async (\r\n filterFn: (option: FormOption) => boolean,\r\n context: string\r\n ): Promise<boolean> => {\r\n try {\r\n const fields = options.value.filter(filterFn).map(option => option.prop)\r\n if (fields.length === 0) return true\r\n await validateField(fields)\r\n return true\r\n } catch (error) {\r\n console.warn(`[C_Form] ${context}验证失败:`, error)\r\n return false\r\n }\r\n }\r\n\r\n const validateStep = async (stepIndex: number): Promise<boolean> => {\r\n const stepKey = config.value.steps?.steps?.[stepIndex]?.key\r\n if (!stepKey) return true\r\n\r\n return validateByFilter(\r\n option => option.layout?.step === stepKey,\r\n `步骤 ${stepIndex} `\r\n )\r\n }\r\n\r\n const validateTab = async (tabKey: string): Promise<boolean> => {\r\n return validateByFilter(\r\n option => option.layout?.tab === tabKey,\r\n `标签页 ${tabKey} `\r\n )\r\n }\r\n\r\n const validateDynamicFields = async (): Promise<boolean> => {\r\n return validateByFilter(\r\n option => Boolean(option.layout?.dynamic),\r\n '动态字段 '\r\n )\r\n }\r\n\r\n const validateCustomGroup = async (groupKey: string): Promise<boolean> => {\r\n return validateByFilter(\r\n option => option.layout?.group === groupKey,\r\n `自定义分组 ${groupKey} `\r\n )\r\n }\r\n\r\n /* ===== 数据 API ===== */\r\n const getModel = (): FormModel => ({ ...formModel })\r\n\r\n const setFields = (fields: FormModel): void => {\r\n Object.assign(formModel, fields)\r\n }\r\n\r\n const resetFields = (): void => {\r\n try {\r\n clearValidation()\r\n\r\n options.value.forEach(item => {\r\n const defaultValue =\r\n item.value !== undefined\r\n ? item.value\r\n : getDefaultValue(item.type as ComponentType)\r\n\r\n formModel[item.prop] = defaultValue\r\n })\r\n } catch (error) {\r\n console.error('[C_Form] 重置表单失败:', error)\r\n }\r\n }\r\n\r\n const setFieldValue = async (\r\n field: string,\r\n value: unknown,\r\n shouldValidate: boolean = false\r\n ): Promise<void> => {\r\n formModel[field] = value\r\n if (shouldValidate) {\r\n await validateField(field)\r\n }\r\n }\r\n\r\n const getFieldValue = (field: string): unknown => formModel[field]\r\n\r\n const setFieldsValue = async (\r\n fields: FormModel,\r\n shouldValidate: boolean = false\r\n ): Promise<void> => {\r\n Object.assign(formModel, fields)\r\n if (shouldValidate) {\r\n await validate()\r\n }\r\n }\r\n\r\n /* ===== 提交 & 重置 ===== */\r\n const handleSubmit = async (): Promise<void> => {\r\n try {\r\n await validate()\r\n emit('submit', { model: getModel(), form: formRef.value! })\r\n } catch (error) {\r\n console.warn('[C_Form] 表单验证失败:', error)\r\n }\r\n }\r\n\r\n /* ===== 生命周期 ===== */\r\n onMounted(() => {\r\n initialize()\r\n\r\n watch(\r\n () => options.value,\r\n () => initialize(),\r\n { deep: true }\r\n )\r\n\r\n watch(\r\n () => formModel,\r\n val => emit('update:modelValue', { ...val }),\r\n { deep: true }\r\n )\r\n })\r\n\r\n return {\r\n formModel,\r\n formRules,\r\n visibleOptions,\r\n initialize,\r\n handleFieldChange,\r\n /* 验证 API */\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n /* 数据 API */\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n /* 操作 */\r\n handleSubmit,\r\n handleReset: resetFields,\r\n }\r\n}\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: C_Form 渲染引擎 Composable — 统一组件注册表 + formItems 生成\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport { computed, h, type VNode, type ComputedRef, type Component } from \"vue\";\r\n\r\nimport type { FormOption, FormModel, OptionItem } from \"../types\";\r\nimport type { ResolvedFormConfig } from \"./useFormConfig\";\r\n\r\n/* =================== 组件映射类型 =================== */\r\n\r\n/** 组件映射表 — 由调用方(C_Form)注入已解析的组件引用 */\r\nexport type ComponentMap = Record<string, Component>;\r\n\r\n/* =================== 渲染器类型 =================== */\r\n\r\n/** 单个控件的渲染函数签名 */\r\nexport type FormRenderer = (\r\n baseProps: Record<string, any>,\r\n item: FormOption,\r\n config: ResolvedFormConfig,\r\n ctx?: { slots?: Record<string, any>; components?: ComponentMap },\r\n) => VNode | null;\r\n\r\n/* =================== 渲染器工厂 =================== */\r\n\r\n/**\r\n * 构建渲染器注册表\r\n * @param C - 组件映射表(由 C_Form 的 <script setup> 解析并注入)\r\n *\r\n * 为什么不在 .ts 文件中直接 resolveComponent?\r\n * unplugin-vue-components 只转换 .vue SFC 中的 resolveComponent 调用,\r\n * .ts 文件中的 resolveComponent 不会被转换,运行时无法找到组件。\r\n */\r\nfunction buildRenderers(C: ComponentMap): Record<string, FormRenderer> {\r\n return {\r\n /* ===== 基础控件 ===== */\r\n input: (props) => h(C.NInput, { ...props }),\r\n textarea: (props) => h(C.NInput, { ...props, type: \"textarea\" }),\r\n inputNumber: (props) => h(C.NInputNumber, { ...props }),\r\n switch: (props) => h(C.NSwitch, { ...props }),\r\n slider: (props) => h(C.NSlider, { ...props }),\r\n rate: (props) => h(C.NRate, { ...props }),\r\n datePicker: (props) => h(C.NDatePicker, { ...props }),\r\n daterange: (props) => h(C.NDatePicker, { ...props, type: \"daterange\" }),\r\n timePicker: (props) => h(C.NTimePicker, { ...props }),\r\n cascader: (props) => h(C.NCascader, { ...props }),\r\n colorPicker: (props) => h(C.NColorPicker, { ...props }),\r\n\r\n /* ===== 复杂控件 — 带子元素/插槽 ===== */\r\n select: (baseProps, item) =>\r\n h(C.NSelect, {\r\n ...baseProps,\r\n options:\r\n item.children?.map((child: OptionItem) => ({\r\n value: child.value,\r\n label: child.label,\r\n disabled: child.disabled,\r\n })) || [],\r\n }),\r\n\r\n checkbox: (baseProps, item) =>\r\n h(\r\n C.NCheckboxGroup,\r\n { ...baseProps },\r\n {\r\n default: () =>\r\n h(\r\n C.NSpace,\r\n {},\r\n {\r\n default: () =>\r\n item.children?.map((child: OptionItem) =>\r\n h(C.NCheckbox, {\r\n value: child.value,\r\n label: child.label,\r\n disabled: child.disabled,\r\n key: String(child.value),\r\n }),\r\n ) || [],\r\n },\r\n ),\r\n },\r\n ),\r\n\r\n radio: (baseProps, item) =>\r\n h(\r\n C.NRadioGroup,\r\n { ...baseProps },\r\n {\r\n default: () =>\r\n h(\r\n C.NSpace,\r\n {},\r\n {\r\n default: () =>\r\n item.children?.map((child: OptionItem) =>\r\n h(\r\n C.NRadio,\r\n {\r\n value: child.value,\r\n disabled: child.disabled,\r\n key: String(child.value),\r\n },\r\n { default: () => child.label },\r\n ),\r\n ) || [],\r\n },\r\n ),\r\n },\r\n ),\r\n\r\n upload: (baseProps, item, _config, ctx) =>\r\n h(\r\n C.NUpload,\r\n {\r\n fileList: baseProps.value || [],\r\n \"onUpdate:fileList\": (fileList: unknown[]) => {\r\n baseProps[\"onUpdate:value\"]?.(fileList);\r\n },\r\n ...item.attrs,\r\n },\r\n {\r\n trigger: () =>\r\n ctx?.slots?.[\"uploadClick\"]?.() ||\r\n h(C.NButton, { type: \"primary\" }, { default: () => \"选择文件\" }),\r\n tip: () => ctx?.slots?.[\"uploadTip\"]?.() || null,\r\n },\r\n ),\r\n\r\n editor: (baseProps, item, config) =>\r\n h(C.C_Editor, {\r\n editorId: `editor-${item.prop}`,\r\n modelValue: baseProps.value || \"\",\r\n placeholder: item.placeholder,\r\n disabled: config.disabled,\r\n readonly: config.readonly,\r\n \"onUpdate:modelValue\": (value: string) => {\r\n baseProps[\"onUpdate:value\"]?.(value);\r\n },\r\n ...item.attrs,\r\n }),\r\n };\r\n}\r\n\r\n/** 自定义渲染器扩展存储 */\r\nconst customRenderers: Record<string, FormRenderer> = {};\r\n\r\n/**\r\n * 运行时注册自定义渲染器 — 开闭原则\r\n * @param type - 控件类型名\r\n * @param renderer - 渲染函数\r\n */\r\nexport function registerRenderer(type: string, renderer: FormRenderer) {\r\n customRenderers[type] = renderer;\r\n}\r\n\r\n/* =================== Composable =================== */\r\n\r\n/**\r\n * 渲染引擎 Composable — 生成 formItems VNode 数组\r\n * @param componentMap - 已解析的组件映射表(由 C_Form 的 <script setup> 提供)\r\n */\r\nexport function useFormRenderer(\r\n formModel: FormModel,\r\n visibleOptions: ComputedRef<FormOption[]>,\r\n config: ComputedRef<ResolvedFormConfig>,\r\n handleFieldChange: (field: string) => void,\r\n componentMap: ComponentMap,\r\n instanceSlots?: Record<string, any>,\r\n) {\r\n /* 构建渲染器注册表(注入已解析的组件引用) */\r\n const renderers = { ...buildRenderers(componentMap), ...customRenderers };\r\n\r\n /**\r\n * 为指定表单项生成基础 props(双向绑定 + 占位符)\r\n */\r\n const getBaseProps = (item: FormOption): Record<string, unknown> => {\r\n const baseProps: Record<string, unknown> = {\r\n value: formModel[item.prop],\r\n \"onUpdate:value\": (value: unknown) => {\r\n formModel[item.prop] = value;\r\n handleFieldChange(item.prop);\r\n },\r\n };\r\n\r\n if (item.type === \"textarea\") {\r\n baseProps.type = \"textarea\";\r\n }\r\n\r\n if (item.placeholder) {\r\n baseProps.placeholder = item.placeholder;\r\n }\r\n\r\n return baseProps;\r\n };\r\n\r\n /**\r\n * 渲染单个表单项控件\r\n */\r\n const renderFormItem = (item: FormOption): VNode | null => {\r\n try {\r\n const renderer = renderers[item.type];\r\n if (!renderer) {\r\n console.warn(`[C_Form] 未支持的组件类型: ${item.type}`);\r\n return null;\r\n }\r\n\r\n const baseProps = getBaseProps(item);\r\n return renderer({ ...baseProps, ...item.attrs }, item, config.value, {\r\n slots: instanceSlots,\r\n components: componentMap,\r\n });\r\n } catch (error) {\r\n console.error(`[C_Form] 渲染表单项失败:`, error, item);\r\n return null;\r\n }\r\n };\r\n\r\n /**\r\n * formItems: 各布局组件接收的 VNode[] ,每个 VNode 是一个 NFormItem\r\n */\r\n const formItems = computed(() =>\r\n visibleOptions.value.map((item) =>\r\n h(\r\n componentMap.NFormItem,\r\n {\r\n label: item.label,\r\n path: item.prop,\r\n key: item.prop,\r\n required: !!item.rules?.length,\r\n },\r\n {\r\n default: () => renderFormItem(item),\r\n },\r\n ),\r\n ),\r\n );\r\n\r\n return { renderFormItem, formItems, getBaseProps };\r\n}\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 卡片布局 - 基础默认布局\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-default\">\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { type VNode } from \"vue\";\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: any;\r\n options?: any[];\r\n}\r\n\r\ndefineProps<Props>();\r\n</script>\r\n\r\n<style scoped>\r\n.c-form-default {\r\n width: 100%;\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 卡片布局 - 基础默认布局\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-default\">\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { type VNode } from \"vue\";\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: any;\r\n options?: any[];\r\n}\r\n\r\ndefineProps<Props>();\r\n</script>\r\n\r\n<style scoped>\r\n.c-form-default {\r\n width: 100%;\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 卡片布局 - 基础默认布局\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-default\">\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { type VNode } from \"vue\";\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: any;\r\n options?: any[];\r\n}\r\n\r\ndefineProps<Props>();\r\n</script>\r\n\r\n<style scoped>\r\n.c-form-default {\r\n width: 100%;\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 内联布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-inline\" :style=\"containerStyle\">\r\n <div\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n class=\"c-form-inline-item\"\r\n :style=\"getItemStyle(index)\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport type { VNode, CSSProperties } from \"vue\";\r\nimport type { LayoutProps, FormOption, ItemLayoutConfig } from \"../../types\";\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<LayoutProps>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\n/**\r\n * 获取统一项目宽度\r\n */\r\nconst itemWidth = computed((): number => 280);\r\n\r\n/**\r\n * 获取项目间距\r\n */\r\nconst gap = computed((): number => {\r\n return props.layoutConfig?.inline?.gap ?? 16;\r\n});\r\n\r\n/**\r\n * 获取行间距\r\n */\r\nconst rowGap = computed((): number => 16);\r\n\r\n/**\r\n * 获取对齐方式 - 既控制垂直对齐也控制水平对齐\r\n */\r\nconst align = computed((): \"start\" | \"center\" | \"end\" => {\r\n const alignValue = props.layoutConfig?.inline?.align;\r\n return alignValue === \"start\" || alignValue === \"end\" ? alignValue : \"center\";\r\n});\r\n\r\n/**\r\n * 获取垂直对齐样式\r\n */\r\nconst alignItems = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n default:\r\n return \"center\";\r\n }\r\n});\r\n\r\n/**\r\n * 获取水平对齐样式\r\n */\r\nconst justifyContent = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n case \"center\":\r\n return \"center\";\r\n default:\r\n return \"flex-start\";\r\n }\r\n});\r\n\r\n/**\r\n * 容器样式计算\r\n */\r\nconst containerStyle = computed((): CSSProperties => {\r\n return {\r\n display: \"flex\",\r\n flexWrap: \"wrap\",\r\n alignItems: alignItems.value,\r\n justifyContent: justifyContent.value,\r\n gap: `${rowGap.value}px ${gap.value}px`,\r\n width: \"100%\",\r\n };\r\n});\r\n\r\n/* ================= 方法 ================= */\r\n\r\n/**\r\n * 获取表单项的唯一key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as any;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `form-item-${index}`;\r\n};\r\n\r\n/**\r\n * 获取表单项样式\r\n */\r\nconst getItemStyle = (index: number): CSSProperties => {\r\n const option: FormOption | undefined = props.options?.[index];\r\n const layoutConfig: ItemLayoutConfig | undefined = option?.layout;\r\n\r\n /* 基础样式,统一宽度 */\r\n const baseStyle: CSSProperties = {\r\n width: `${itemWidth.value}px`,\r\n flexShrink: 0,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n };\r\n\r\n /* 如果单独设置了宽度,则覆盖统一宽度 */\r\n if (layoutConfig?.width !== undefined) {\r\n baseStyle.width =\r\n typeof layoutConfig.width === \"number\"\r\n ? `${layoutConfig.width}px`\r\n : layoutConfig.width;\r\n }\r\n\r\n /* 合并自定义样式 */\r\n if (layoutConfig?.style) {\r\n Object.assign(baseStyle, layoutConfig.style);\r\n }\r\n\r\n return baseStyle;\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 内联布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-inline\" :style=\"containerStyle\">\r\n <div\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n class=\"c-form-inline-item\"\r\n :style=\"getItemStyle(index)\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport type { VNode, CSSProperties } from \"vue\";\r\nimport type { LayoutProps, FormOption, ItemLayoutConfig } from \"../../types\";\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<LayoutProps>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\n/**\r\n * 获取统一项目宽度\r\n */\r\nconst itemWidth = computed((): number => 280);\r\n\r\n/**\r\n * 获取项目间距\r\n */\r\nconst gap = computed((): number => {\r\n return props.layoutConfig?.inline?.gap ?? 16;\r\n});\r\n\r\n/**\r\n * 获取行间距\r\n */\r\nconst rowGap = computed((): number => 16);\r\n\r\n/**\r\n * 获取对齐方式 - 既控制垂直对齐也控制水平对齐\r\n */\r\nconst align = computed((): \"start\" | \"center\" | \"end\" => {\r\n const alignValue = props.layoutConfig?.inline?.align;\r\n return alignValue === \"start\" || alignValue === \"end\" ? alignValue : \"center\";\r\n});\r\n\r\n/**\r\n * 获取垂直对齐样式\r\n */\r\nconst alignItems = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n default:\r\n return \"center\";\r\n }\r\n});\r\n\r\n/**\r\n * 获取水平对齐样式\r\n */\r\nconst justifyContent = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n case \"center\":\r\n return \"center\";\r\n default:\r\n return \"flex-start\";\r\n }\r\n});\r\n\r\n/**\r\n * 容器样式计算\r\n */\r\nconst containerStyle = computed((): CSSProperties => {\r\n return {\r\n display: \"flex\",\r\n flexWrap: \"wrap\",\r\n alignItems: alignItems.value,\r\n justifyContent: justifyContent.value,\r\n gap: `${rowGap.value}px ${gap.value}px`,\r\n width: \"100%\",\r\n };\r\n});\r\n\r\n/* ================= 方法 ================= */\r\n\r\n/**\r\n * 获取表单项的唯一key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as any;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `form-item-${index}`;\r\n};\r\n\r\n/**\r\n * 获取表单项样式\r\n */\r\nconst getItemStyle = (index: number): CSSProperties => {\r\n const option: FormOption | undefined = props.options?.[index];\r\n const layoutConfig: ItemLayoutConfig | undefined = option?.layout;\r\n\r\n /* 基础样式,统一宽度 */\r\n const baseStyle: CSSProperties = {\r\n width: `${itemWidth.value}px`,\r\n flexShrink: 0,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n };\r\n\r\n /* 如果单独设置了宽度,则覆盖统一宽度 */\r\n if (layoutConfig?.width !== undefined) {\r\n baseStyle.width =\r\n typeof layoutConfig.width === \"number\"\r\n ? `${layoutConfig.width}px`\r\n : layoutConfig.width;\r\n }\r\n\r\n /* 合并自定义样式 */\r\n if (layoutConfig?.style) {\r\n Object.assign(baseStyle, layoutConfig.style);\r\n }\r\n\r\n return baseStyle;\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 内联布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-inline\" :style=\"containerStyle\">\r\n <div\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n class=\"c-form-inline-item\"\r\n :style=\"getItemStyle(index)\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport type { VNode, CSSProperties } from \"vue\";\r\nimport type { LayoutProps, FormOption, ItemLayoutConfig } from \"../../types\";\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<LayoutProps>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\n/**\r\n * 获取统一项目宽度\r\n */\r\nconst itemWidth = computed((): number => 280);\r\n\r\n/**\r\n * 获取项目间距\r\n */\r\nconst gap = computed((): number => {\r\n return props.layoutConfig?.inline?.gap ?? 16;\r\n});\r\n\r\n/**\r\n * 获取行间距\r\n */\r\nconst rowGap = computed((): number => 16);\r\n\r\n/**\r\n * 获取对齐方式 - 既控制垂直对齐也控制水平对齐\r\n */\r\nconst align = computed((): \"start\" | \"center\" | \"end\" => {\r\n const alignValue = props.layoutConfig?.inline?.align;\r\n return alignValue === \"start\" || alignValue === \"end\" ? alignValue : \"center\";\r\n});\r\n\r\n/**\r\n * 获取垂直对齐样式\r\n */\r\nconst alignItems = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n default:\r\n return \"center\";\r\n }\r\n});\r\n\r\n/**\r\n * 获取水平对齐样式\r\n */\r\nconst justifyContent = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n case \"center\":\r\n return \"center\";\r\n default:\r\n return \"flex-start\";\r\n }\r\n});\r\n\r\n/**\r\n * 容器样式计算\r\n */\r\nconst containerStyle = computed((): CSSProperties => {\r\n return {\r\n display: \"flex\",\r\n flexWrap: \"wrap\",\r\n alignItems: alignItems.value,\r\n justifyContent: justifyContent.value,\r\n gap: `${rowGap.value}px ${gap.value}px`,\r\n width: \"100%\",\r\n };\r\n});\r\n\r\n/* ================= 方法 ================= */\r\n\r\n/**\r\n * 获取表单项的唯一key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as any;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `form-item-${index}`;\r\n};\r\n\r\n/**\r\n * 获取表单项样式\r\n */\r\nconst getItemStyle = (index: number): CSSProperties => {\r\n const option: FormOption | undefined = props.options?.[index];\r\n const layoutConfig: ItemLayoutConfig | undefined = option?.layout;\r\n\r\n /* 基础样式,统一宽度 */\r\n const baseStyle: CSSProperties = {\r\n width: `${itemWidth.value}px`,\r\n flexShrink: 0,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n };\r\n\r\n /* 如果单独设置了宽度,则覆盖统一宽度 */\r\n if (layoutConfig?.width !== undefined) {\r\n baseStyle.width =\r\n typeof layoutConfig.width === \"number\"\r\n ? `${layoutConfig.width}px`\r\n : layoutConfig.width;\r\n }\r\n\r\n /* 合并自定义样式 */\r\n if (layoutConfig?.style) {\r\n Object.assign(baseStyle, layoutConfig.style);\r\n }\r\n\r\n return baseStyle;\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 网格表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-grid-layout\">\r\n <!-- 配置面板 -->\r\n <NCard\r\n v-if=\"showConfigPanel\"\r\n size=\"small\"\r\n :bordered=\"false\"\r\n class=\"config-panel\"\r\n >\r\n <template #header>\r\n <div class=\"config-header\">\r\n <C_Icon :name=\"'mdi:view-grid'\" :size=\"18\" />\r\n <span>网格配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div\r\n v-for=\"control in configControls\"\r\n :key=\"control.key\"\r\n class=\"config-item\"\r\n >\r\n <span class=\"config-label\">{{ control.label }}:</span>\r\n <component\r\n :is=\"control.component\"\r\n v-model:value=\"control.value\"\r\n v-bind=\"control.props\"\r\n @update:value=\"emitConfigChange\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 网格容器 -->\r\n <NGrid v-bind=\"gridProps\" class=\"grid-container\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n v-bind=\"getItemProps(index)\"\r\n class=\"grid-item\"\r\n >\r\n <div class=\"item-wrapper\">\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n\r\n <!-- 统计信息 -->\r\n <NAlert\r\n v-if=\"showStats && isDev\"\r\n type=\"info\"\r\n :show-icon=\"false\"\r\n size=\"small\"\r\n class=\"grid-stats\"\r\n >\r\n <template #header>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n 网格信息\r\n </template>\r\n {{ statsText }}\r\n </NAlert>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, watchEffect, toRaw } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\n\r\ninterface GridConfig {\r\n cols?: number;\r\n gutter?: number;\r\n responsive?: boolean;\r\n showConfigPanel?: boolean;\r\n showStats?: boolean;\r\n}\r\n\r\ninterface FormOption {\r\n type: string;\r\n prop: string;\r\n label?: string;\r\n layout?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n grid?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n };\r\n };\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: { grid?: GridConfig };\r\n options?: FormOption[];\r\n}\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst isDev = ref(typeof import.meta !== \"undefined\" && !!import.meta.env?.DEV);\r\nconst internalConfig = reactive({\r\n cols: 24,\r\n gutter: 16,\r\n responsive: true,\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst gridConfig = computed(() => props.layoutConfig?.grid || {});\r\nconst showConfigPanel = computed(() => gridConfig.value.showConfigPanel);\r\nconst showStats = computed(() => gridConfig.value.showStats);\r\n\r\n/* 生效的配置(内部配置优先) */\r\nconst effectiveConfig = computed(() => ({\r\n cols: showConfigPanel.value\r\n ? internalConfig.cols\r\n : (gridConfig.value.cols ?? 24),\r\n gutter: showConfigPanel.value\r\n ? internalConfig.gutter\r\n : (gridConfig.value.gutter ?? 16),\r\n responsive: showConfigPanel.value\r\n ? internalConfig.responsive\r\n : (gridConfig.value.responsive ?? true),\r\n}));\r\n\r\n/* 网格属性 */\r\nconst gridProps = computed(() => ({\r\n cols: effectiveConfig.value.cols,\r\n xGap: effectiveConfig.value.gutter,\r\n yGap: effectiveConfig.value.gutter,\r\n responsive: effectiveConfig.value.responsive ? \"screen\" : \"self\",\r\n}));\r\n\r\n/* 配置控件定义 */\r\nconst configControls = computed(() => [\r\n {\r\n key: \"cols\",\r\n label: \"列数\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.cols,\r\n set: (val: number) => (internalConfig.cols = val),\r\n }),\r\n props: { min: 1, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"gutter\",\r\n label: \"间距\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.gutter,\r\n set: (val: number) => (internalConfig.gutter = val),\r\n }),\r\n props: { min: 0, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"responsive\",\r\n label: \"响应式\",\r\n component: \"NSwitch\",\r\n value: computed({\r\n get: () => internalConfig.responsive,\r\n set: (val: boolean) => (internalConfig.responsive = val),\r\n }),\r\n props: { size: \"small\" },\r\n },\r\n]);\r\n\r\n/* 统计文本 */\r\nconst statsText = computed(\r\n () =>\r\n `列数: ${effectiveConfig.value.cols} | 间距: ${effectiveConfig.value.gutter}px | 项目: ${props.formItems.length}个`,\r\n);\r\n\r\n/* ================= 核心方法 ================= */\r\n\r\n/**\r\n * 获取表单项key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string =>\r\n String(item.key) || (item.props as any)?.path || `grid-item-${index}`;\r\n\r\n/**\r\n * 获取布局属性值\r\n */\r\nconst getLayoutValue = (index: number, key: \"span\" | \"offset\" | \"suffix\") => {\r\n const option = props.options?.[index]?.layout;\r\n return option?.[key] ?? option?.grid?.[key];\r\n};\r\n\r\n/**\r\n * 获取默认span值\r\n */\r\nconst getDefaultSpan = (): number => {\r\n const { cols } = effectiveConfig.value;\r\n return cols <= 12 ? cols : cols <= 24 ? 12 : 8;\r\n};\r\n\r\n/**\r\n * 获取网格项属性\r\n */\r\nconst getItemProps = (index: number) => {\r\n const span = getLayoutValue(index, \"span\");\r\n const offset = getLayoutValue(index, \"offset\");\r\n const suffix = getLayoutValue(index, \"suffix\");\r\n\r\n return {\r\n span:\r\n typeof span === \"number\" && span > 0 && span <= effectiveConfig.value.cols\r\n ? span\r\n : getDefaultSpan(),\r\n offset:\r\n typeof offset === \"number\" &&\r\n offset >= 0 &&\r\n offset < effectiveConfig.value.cols\r\n ? offset\r\n : 0,\r\n suffix: Boolean(suffix),\r\n };\r\n};\r\n\r\n/**\r\n * 配置变更事件\r\n */\r\nconst emitConfigChange = () => {\r\n if (isDev.value) {\r\n console.log(\"Grid config updated:\", toRaw(internalConfig));\r\n }\r\n};\r\n\r\n/* ================= 监听器 ================= */\r\n\r\n/* 同步外部配置 */\r\nwatch(\r\n gridConfig,\r\n (config) => {\r\n if (config.cols !== undefined) internalConfig.cols = config.cols;\r\n if (config.gutter !== undefined) internalConfig.gutter = config.gutter;\r\n if (config.responsive !== undefined)\r\n internalConfig.responsive = config.responsive;\r\n },\r\n { immediate: true },\r\n);\r\n\r\n/* 开发环境验证 */\r\nif (isDev.value) {\r\n watchEffect(() => {\r\n const optionsCount = props.options.length;\r\n const itemsCount = props.formItems.length;\r\n\r\n if (optionsCount > 0 && optionsCount !== itemsCount) {\r\n console.warn(\r\n `[GridLayout] 配置数量(${optionsCount})与表单项数量(${itemsCount})不匹配`,\r\n );\r\n }\r\n });\r\n}\r\n\r\n/* ================= 暴露接口 ================= */\r\n\r\ndefineExpose({\r\n updateGridConfig: (config: Partial<GridConfig>) =>\r\n Object.assign(internalConfig, config),\r\n getCurrentConfig: () => ({ ...effectiveConfig.value }),\r\n getGridInfo: () => ({\r\n ...effectiveConfig.value,\r\n itemCount: props.formItems.length,\r\n }),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 网格表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-grid-layout\">\r\n <!-- 配置面板 -->\r\n <NCard\r\n v-if=\"showConfigPanel\"\r\n size=\"small\"\r\n :bordered=\"false\"\r\n class=\"config-panel\"\r\n >\r\n <template #header>\r\n <div class=\"config-header\">\r\n <C_Icon :name=\"'mdi:view-grid'\" :size=\"18\" />\r\n <span>网格配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div\r\n v-for=\"control in configControls\"\r\n :key=\"control.key\"\r\n class=\"config-item\"\r\n >\r\n <span class=\"config-label\">{{ control.label }}:</span>\r\n <component\r\n :is=\"control.component\"\r\n v-model:value=\"control.value\"\r\n v-bind=\"control.props\"\r\n @update:value=\"emitConfigChange\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 网格容器 -->\r\n <NGrid v-bind=\"gridProps\" class=\"grid-container\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n v-bind=\"getItemProps(index)\"\r\n class=\"grid-item\"\r\n >\r\n <div class=\"item-wrapper\">\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n\r\n <!-- 统计信息 -->\r\n <NAlert\r\n v-if=\"showStats && isDev\"\r\n type=\"info\"\r\n :show-icon=\"false\"\r\n size=\"small\"\r\n class=\"grid-stats\"\r\n >\r\n <template #header>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n 网格信息\r\n </template>\r\n {{ statsText }}\r\n </NAlert>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, watchEffect, toRaw } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\n\r\ninterface GridConfig {\r\n cols?: number;\r\n gutter?: number;\r\n responsive?: boolean;\r\n showConfigPanel?: boolean;\r\n showStats?: boolean;\r\n}\r\n\r\ninterface FormOption {\r\n type: string;\r\n prop: string;\r\n label?: string;\r\n layout?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n grid?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n };\r\n };\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: { grid?: GridConfig };\r\n options?: FormOption[];\r\n}\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst isDev = ref(typeof import.meta !== \"undefined\" && !!import.meta.env?.DEV);\r\nconst internalConfig = reactive({\r\n cols: 24,\r\n gutter: 16,\r\n responsive: true,\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst gridConfig = computed(() => props.layoutConfig?.grid || {});\r\nconst showConfigPanel = computed(() => gridConfig.value.showConfigPanel);\r\nconst showStats = computed(() => gridConfig.value.showStats);\r\n\r\n/* 生效的配置(内部配置优先) */\r\nconst effectiveConfig = computed(() => ({\r\n cols: showConfigPanel.value\r\n ? internalConfig.cols\r\n : (gridConfig.value.cols ?? 24),\r\n gutter: showConfigPanel.value\r\n ? internalConfig.gutter\r\n : (gridConfig.value.gutter ?? 16),\r\n responsive: showConfigPanel.value\r\n ? internalConfig.responsive\r\n : (gridConfig.value.responsive ?? true),\r\n}));\r\n\r\n/* 网格属性 */\r\nconst gridProps = computed(() => ({\r\n cols: effectiveConfig.value.cols,\r\n xGap: effectiveConfig.value.gutter,\r\n yGap: effectiveConfig.value.gutter,\r\n responsive: effectiveConfig.value.responsive ? \"screen\" : \"self\",\r\n}));\r\n\r\n/* 配置控件定义 */\r\nconst configControls = computed(() => [\r\n {\r\n key: \"cols\",\r\n label: \"列数\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.cols,\r\n set: (val: number) => (internalConfig.cols = val),\r\n }),\r\n props: { min: 1, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"gutter\",\r\n label: \"间距\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.gutter,\r\n set: (val: number) => (internalConfig.gutter = val),\r\n }),\r\n props: { min: 0, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"responsive\",\r\n label: \"响应式\",\r\n component: \"NSwitch\",\r\n value: computed({\r\n get: () => internalConfig.responsive,\r\n set: (val: boolean) => (internalConfig.responsive = val),\r\n }),\r\n props: { size: \"small\" },\r\n },\r\n]);\r\n\r\n/* 统计文本 */\r\nconst statsText = computed(\r\n () =>\r\n `列数: ${effectiveConfig.value.cols} | 间距: ${effectiveConfig.value.gutter}px | 项目: ${props.formItems.length}个`,\r\n);\r\n\r\n/* ================= 核心方法 ================= */\r\n\r\n/**\r\n * 获取表单项key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string =>\r\n String(item.key) || (item.props as any)?.path || `grid-item-${index}`;\r\n\r\n/**\r\n * 获取布局属性值\r\n */\r\nconst getLayoutValue = (index: number, key: \"span\" | \"offset\" | \"suffix\") => {\r\n const option = props.options?.[index]?.layout;\r\n return option?.[key] ?? option?.grid?.[key];\r\n};\r\n\r\n/**\r\n * 获取默认span值\r\n */\r\nconst getDefaultSpan = (): number => {\r\n const { cols } = effectiveConfig.value;\r\n return cols <= 12 ? cols : cols <= 24 ? 12 : 8;\r\n};\r\n\r\n/**\r\n * 获取网格项属性\r\n */\r\nconst getItemProps = (index: number) => {\r\n const span = getLayoutValue(index, \"span\");\r\n const offset = getLayoutValue(index, \"offset\");\r\n const suffix = getLayoutValue(index, \"suffix\");\r\n\r\n return {\r\n span:\r\n typeof span === \"number\" && span > 0 && span <= effectiveConfig.value.cols\r\n ? span\r\n : getDefaultSpan(),\r\n offset:\r\n typeof offset === \"number\" &&\r\n offset >= 0 &&\r\n offset < effectiveConfig.value.cols\r\n ? offset\r\n : 0,\r\n suffix: Boolean(suffix),\r\n };\r\n};\r\n\r\n/**\r\n * 配置变更事件\r\n */\r\nconst emitConfigChange = () => {\r\n if (isDev.value) {\r\n console.log(\"Grid config updated:\", toRaw(internalConfig));\r\n }\r\n};\r\n\r\n/* ================= 监听器 ================= */\r\n\r\n/* 同步外部配置 */\r\nwatch(\r\n gridConfig,\r\n (config) => {\r\n if (config.cols !== undefined) internalConfig.cols = config.cols;\r\n if (config.gutter !== undefined) internalConfig.gutter = config.gutter;\r\n if (config.responsive !== undefined)\r\n internalConfig.responsive = config.responsive;\r\n },\r\n { immediate: true },\r\n);\r\n\r\n/* 开发环境验证 */\r\nif (isDev.value) {\r\n watchEffect(() => {\r\n const optionsCount = props.options.length;\r\n const itemsCount = props.formItems.length;\r\n\r\n if (optionsCount > 0 && optionsCount !== itemsCount) {\r\n console.warn(\r\n `[GridLayout] 配置数量(${optionsCount})与表单项数量(${itemsCount})不匹配`,\r\n );\r\n }\r\n });\r\n}\r\n\r\n/* ================= 暴露接口 ================= */\r\n\r\ndefineExpose({\r\n updateGridConfig: (config: Partial<GridConfig>) =>\r\n Object.assign(internalConfig, config),\r\n getCurrentConfig: () => ({ ...effectiveConfig.value }),\r\n getGridInfo: () => ({\r\n ...effectiveConfig.value,\r\n itemCount: props.formItems.length,\r\n }),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 网格表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-grid-layout\">\r\n <!-- 配置面板 -->\r\n <NCard\r\n v-if=\"showConfigPanel\"\r\n size=\"small\"\r\n :bordered=\"false\"\r\n class=\"config-panel\"\r\n >\r\n <template #header>\r\n <div class=\"config-header\">\r\n <C_Icon :name=\"'mdi:view-grid'\" :size=\"18\" />\r\n <span>网格配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div\r\n v-for=\"control in configControls\"\r\n :key=\"control.key\"\r\n class=\"config-item\"\r\n >\r\n <span class=\"config-label\">{{ control.label }}:</span>\r\n <component\r\n :is=\"control.component\"\r\n v-model:value=\"control.value\"\r\n v-bind=\"control.props\"\r\n @update:value=\"emitConfigChange\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 网格容器 -->\r\n <NGrid v-bind=\"gridProps\" class=\"grid-container\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n v-bind=\"getItemProps(index)\"\r\n class=\"grid-item\"\r\n >\r\n <div class=\"item-wrapper\">\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n\r\n <!-- 统计信息 -->\r\n <NAlert\r\n v-if=\"showStats && isDev\"\r\n type=\"info\"\r\n :show-icon=\"false\"\r\n size=\"small\"\r\n class=\"grid-stats\"\r\n >\r\n <template #header>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n 网格信息\r\n </template>\r\n {{ statsText }}\r\n </NAlert>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, watchEffect, toRaw } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\n\r\ninterface GridConfig {\r\n cols?: number;\r\n gutter?: number;\r\n responsive?: boolean;\r\n showConfigPanel?: boolean;\r\n showStats?: boolean;\r\n}\r\n\r\ninterface FormOption {\r\n type: string;\r\n prop: string;\r\n label?: string;\r\n layout?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n grid?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n };\r\n };\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: { grid?: GridConfig };\r\n options?: FormOption[];\r\n}\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst isDev = ref(typeof import.meta !== \"undefined\" && !!import.meta.env?.DEV);\r\nconst internalConfig = reactive({\r\n cols: 24,\r\n gutter: 16,\r\n responsive: true,\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst gridConfig = computed(() => props.layoutConfig?.grid || {});\r\nconst showConfigPanel = computed(() => gridConfig.value.showConfigPanel);\r\nconst showStats = computed(() => gridConfig.value.showStats);\r\n\r\n/* 生效的配置(内部配置优先) */\r\nconst effectiveConfig = computed(() => ({\r\n cols: showConfigPanel.value\r\n ? internalConfig.cols\r\n : (gridConfig.value.cols ?? 24),\r\n gutter: showConfigPanel.value\r\n ? internalConfig.gutter\r\n : (gridConfig.value.gutter ?? 16),\r\n responsive: showConfigPanel.value\r\n ? internalConfig.responsive\r\n : (gridConfig.value.responsive ?? true),\r\n}));\r\n\r\n/* 网格属性 */\r\nconst gridProps = computed(() => ({\r\n cols: effectiveConfig.value.cols,\r\n xGap: effectiveConfig.value.gutter,\r\n yGap: effectiveConfig.value.gutter,\r\n responsive: effectiveConfig.value.responsive ? \"screen\" : \"self\",\r\n}));\r\n\r\n/* 配置控件定义 */\r\nconst configControls = computed(() => [\r\n {\r\n key: \"cols\",\r\n label: \"列数\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.cols,\r\n set: (val: number) => (internalConfig.cols = val),\r\n }),\r\n props: { min: 1, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"gutter\",\r\n label: \"间距\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.gutter,\r\n set: (val: number) => (internalConfig.gutter = val),\r\n }),\r\n props: { min: 0, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"responsive\",\r\n label: \"响应式\",\r\n component: \"NSwitch\",\r\n value: computed({\r\n get: () => internalConfig.responsive,\r\n set: (val: boolean) => (internalConfig.responsive = val),\r\n }),\r\n props: { size: \"small\" },\r\n },\r\n]);\r\n\r\n/* 统计文本 */\r\nconst statsText = computed(\r\n () =>\r\n `列数: ${effectiveConfig.value.cols} | 间距: ${effectiveConfig.value.gutter}px | 项目: ${props.formItems.length}个`,\r\n);\r\n\r\n/* ================= 核心方法 ================= */\r\n\r\n/**\r\n * 获取表单项key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string =>\r\n String(item.key) || (item.props as any)?.path || `grid-item-${index}`;\r\n\r\n/**\r\n * 获取布局属性值\r\n */\r\nconst getLayoutValue = (index: number, key: \"span\" | \"offset\" | \"suffix\") => {\r\n const option = props.options?.[index]?.layout;\r\n return option?.[key] ?? option?.grid?.[key];\r\n};\r\n\r\n/**\r\n * 获取默认span值\r\n */\r\nconst getDefaultSpan = (): number => {\r\n const { cols } = effectiveConfig.value;\r\n return cols <= 12 ? cols : cols <= 24 ? 12 : 8;\r\n};\r\n\r\n/**\r\n * 获取网格项属性\r\n */\r\nconst getItemProps = (index: number) => {\r\n const span = getLayoutValue(index, \"span\");\r\n const offset = getLayoutValue(index, \"offset\");\r\n const suffix = getLayoutValue(index, \"suffix\");\r\n\r\n return {\r\n span:\r\n typeof span === \"number\" && span > 0 && span <= effectiveConfig.value.cols\r\n ? span\r\n : getDefaultSpan(),\r\n offset:\r\n typeof offset === \"number\" &&\r\n offset >= 0 &&\r\n offset < effectiveConfig.value.cols\r\n ? offset\r\n : 0,\r\n suffix: Boolean(suffix),\r\n };\r\n};\r\n\r\n/**\r\n * 配置变更事件\r\n */\r\nconst emitConfigChange = () => {\r\n if (isDev.value) {\r\n console.log(\"Grid config updated:\", toRaw(internalConfig));\r\n }\r\n};\r\n\r\n/* ================= 监听器 ================= */\r\n\r\n/* 同步外部配置 */\r\nwatch(\r\n gridConfig,\r\n (config) => {\r\n if (config.cols !== undefined) internalConfig.cols = config.cols;\r\n if (config.gutter !== undefined) internalConfig.gutter = config.gutter;\r\n if (config.responsive !== undefined)\r\n internalConfig.responsive = config.responsive;\r\n },\r\n { immediate: true },\r\n);\r\n\r\n/* 开发环境验证 */\r\nif (isDev.value) {\r\n watchEffect(() => {\r\n const optionsCount = props.options.length;\r\n const itemsCount = props.formItems.length;\r\n\r\n if (optionsCount > 0 && optionsCount !== itemsCount) {\r\n console.warn(\r\n `[GridLayout] 配置数量(${optionsCount})与表单项数量(${itemsCount})不匹配`,\r\n );\r\n }\r\n });\r\n}\r\n\r\n/* ================= 暴露接口 ================= */\r\n\r\ndefineExpose({\r\n updateGridConfig: (config: Partial<GridConfig>) =>\r\n Object.assign(internalConfig, config),\r\n getCurrentConfig: () => ({ ...effectiveConfig.value }),\r\n getGridInfo: () => ({\r\n ...effectiveConfig.value,\r\n itemCount: props.formItems.length,\r\n }),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 卡片组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-card\">\r\n <!-- 布局配置面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showLayoutConfig\"\r\n class=\"layout-config-panel\"\r\n :bordered=\"false\"\r\n >\r\n <template #header>\r\n <div class=\"flex items-center gap-2\">\r\n <C_Icon :name=\"'mdi:cog'\" :size=\"18\" />\r\n <span>布局配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div class=\"config-item\">\r\n <span>卡片间距</span>\r\n <div class=\"flex items-center gap-2\">\r\n <NSlider\r\n v-model:value=\"cardGap\"\r\n :min=\"12\"\r\n :max=\"32\"\r\n :step=\"4\"\r\n class=\"w-24\"\r\n />\r\n <span class=\"text-xs min-w-12\">{{ cardGap }}px</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>显示图标</span>\r\n <NSwitch v-model:value=\"showIcons\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>可折叠</span>\r\n <NSwitch v-model:value=\"collapsible\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>布局方向</span>\r\n <NRadioGroup v-model:value=\"currentDirection\" size=\"small\">\r\n <NRadio value=\"vertical\">垂直</NRadio>\r\n <NRadio value=\"horizontal\">水平</NRadio>\r\n </NRadioGroup>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 表单内容区域 -->\r\n <div\r\n class=\"form-content\"\r\n :class=\"layoutClass\"\r\n :style=\"{ gap: `${cardGap}px` }\"\r\n >\r\n <!-- 无分组时的单卡片模式 -->\r\n <NCard v-if=\"!hasGroups\" class=\"single-card\" hoverable>\r\n <template #header>\r\n <div class=\"flex items-center gap-3\">\r\n <C_Icon\r\n v-if=\"showIcons\"\r\n :name=\"'mdi:form-select'\"\r\n :size=\"18\"\r\n style=\"color: #3b82f6\"\r\n />\r\n <span>表单信息</span>\r\n </div>\r\n </template>\r\n\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </NCard>\r\n\r\n <!-- 有分组时的多卡片模式 -->\r\n <template v-else>\r\n <NCard\r\n v-for=\"group in groupsWithItems\"\r\n :key=\"group.config.key\"\r\n class=\"group-card\"\r\n :class=\"[\r\n `${group.config.key}-card`,\r\n { collapsed: collapsible && collapsedGroups[group.config.key] },\r\n ]\"\r\n hoverable\r\n >\r\n <template #header>\r\n <div class=\"card-header\">\r\n <div class=\"header-info\">\r\n <C_Icon\r\n v-if=\"showIcons && group.config.icon\"\r\n :name=\"group.config.icon\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n <C_Icon\r\n v-else-if=\"showIcons\"\r\n :name=\"getDefaultIcon(group.config.key)\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n\r\n <div class=\"header-text\">\r\n <h3>{{ group.config.title }}</h3>\r\n <p v-if=\"group.config.description\">\r\n {{ group.config.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"header-actions\">\r\n <!-- 统计信息 -->\r\n <div class=\"field-stats\">\r\n <NBadge :value=\"group.items.length\" type=\"info\" show-zero />\r\n <NBadge\r\n :value=\"`${getFilledCount(group)}/${group.items.length}`\"\r\n :type=\"\r\n getFilledCount(group) === group.items.length\r\n ? 'success'\r\n : 'warning'\r\n \"\r\n />\r\n </div>\r\n\r\n <!-- 折叠按钮 -->\r\n <NButton\r\n v-if=\"collapsible\"\r\n quaternary\r\n circle\r\n size=\"small\"\r\n @click=\"toggleGroup(group.config.key)\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n collapsedGroups[group.config.key]\r\n ? 'mdi:chevron-down'\r\n : 'mdi:chevron-up'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n </template>\r\n\r\n <div v-show=\"!collapsedGroups[group.config.key]\" class=\"card-content\">\r\n <!-- 进度指示器 -->\r\n <div v-if=\"showProgress\" class=\"progress-section\">\r\n <NProgress\r\n :percentage=\"getGroupProgress(group)\"\r\n :color=\"getGroupProgress(group) === 100 ? '#52c41a' : '#1890ff'\"\r\n :show-indicator=\"false\"\r\n class=\"mb-4\"\r\n />\r\n </div>\r\n\r\n <!-- 表单项 -->\r\n <template v-for=\"item in group.items\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n </NCard>\r\n </template>\r\n </div>\r\n\r\n <!-- 统一操作面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showActionPanel\"\r\n class=\"action-panel\"\r\n :bordered=\"false\"\r\n >\r\n <div class=\"action-content\">\r\n <div class=\"status-summary\">\r\n <div class=\"status-item\">\r\n <span class=\"label\">完成进度:</span>\r\n <div class=\"progress-display\">\r\n <NProgress\r\n :percentage=\"totalProgress\"\r\n :show-indicator=\"false\"\r\n :color=\"totalProgress === 100 ? '#52c41a' : '#1890ff'\"\r\n class=\"flex-1\"\r\n />\r\n <span class=\"text-sm min-w-12\">{{ totalProgress }}%</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"action-buttons\">\r\n <NButton v-if=\"collapsible\" @click=\"toggleAllGroups\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n allCollapsed\r\n ? 'mdi:unfold-more-horizontal'\r\n : 'mdi:unfold-less-horizontal'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n {{ allCollapsed ? \"展开全部\" : \"折叠全部\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/**\r\n * 分组配置接口\r\n */\r\ninterface GroupConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n}\r\n\r\n/**\r\n * 分组数据接口\r\n */\r\ninterface GroupWithItems {\r\n config: GroupConfig;\r\n items: VNode[];\r\n}\r\n\r\n/**\r\n * 组件属性接口\r\n */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n card?: {\r\n groups?: GroupConfig[];\r\n direction?: \"vertical\" | \"horizontal\";\r\n showLayoutConfig?: boolean;\r\n showActionPanel?: boolean;\r\n showProgress?: boolean;\r\n };\r\n };\r\n options?: Array<{\r\n layout?: {\r\n group?: string;\r\n };\r\n prop?: string;\r\n }>;\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst cardGap = ref(20);\r\nconst showIcons = ref(true);\r\nconst collapsible = ref(true);\r\nconst showProgress = ref(true);\r\nconst currentDirection = ref<\"vertical\" | \"horizontal\">(\"vertical\");\r\nconst collapsedGroups = ref<Record<string, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst groups = computed((): GroupConfig[] => {\r\n return props.layoutConfig?.card?.groups || [];\r\n});\r\n\r\nconst hasGroups = computed((): boolean => {\r\n return groups.value.length > 0;\r\n});\r\n\r\nconst showLayoutConfig = computed((): boolean => {\r\n return props.layoutConfig?.card?.showLayoutConfig ?? true;\r\n});\r\n\r\nconst showActionPanel = computed((): boolean => {\r\n return props.layoutConfig?.card?.showActionPanel ?? true;\r\n});\r\n\r\nconst layoutClass = computed((): string => {\r\n if (!hasGroups.value) return \"layout-single\";\r\n return `layout-${currentDirection.value}`;\r\n});\r\n\r\nconst groupsWithItems = computed((): GroupWithItems[] => {\r\n if (!hasGroups.value) return [];\r\n\r\n const groupMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化分组映射 */\r\n groups.value.forEach((group) => {\r\n groupMap.set(group.key, []);\r\n });\r\n\r\n /* 将表单项分配到对应分组 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const groupKey = option?.layout?.group || groups.value[0]?.key || \"default\";\r\n\r\n if (!groupMap.has(groupKey)) {\r\n groupMap.set(groupKey, []);\r\n }\r\n groupMap.get(groupKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的分组 */\r\n return groups.value\r\n .map((groupConfig) => ({\r\n config: groupConfig,\r\n items: groupMap.get(groupConfig.key) || [],\r\n }))\r\n .filter((group) => group.items.length > 0);\r\n});\r\n\r\nconst allCollapsed = computed(() => {\r\n const groupKeys = Object.keys(collapsedGroups.value);\r\n return (\r\n groupKeys.length > 0 && groupKeys.every((key) => collapsedGroups.value[key])\r\n );\r\n});\r\n\r\nconst totalProgress = computed(() => {\r\n if (!props.options || props.options.length === 0) return 0;\r\n\r\n const filledCount = props.options.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n\r\n return Math.round((filledCount / props.options.length) * 100);\r\n});\r\n\r\n/* ================= 工具函数 ================= */\r\nconst isFieldFilled = (value: any): boolean => {\r\n if (value === null || value === undefined) return false;\r\n if (Array.isArray(value)) return value.length > 0;\r\n if (typeof value === \"string\") return value.trim() !== \"\";\r\n return true;\r\n};\r\n\r\nconst getFilledCount = (group: GroupWithItems): number => {\r\n if (!props.options) return 0;\r\n\r\n const groupFields = props.options.filter(\r\n (option) => option.layout?.group === group.config.key,\r\n );\r\n\r\n return groupFields.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n};\r\n\r\nconst getGroupProgress = (group: GroupWithItems): number => {\r\n if (group.items.length === 0) return 0;\r\n const filledCount = getFilledCount(group);\r\n return Math.round((filledCount / group.items.length) * 100);\r\n};\r\n\r\nconst getDefaultIcon = (groupKey: string): string => {\r\n const iconMap: Record<string, string> = {\r\n basic: \"mdi:account\",\r\n contact: \"mdi:phone\",\r\n preferences: \"mdi:cog\",\r\n settings: \"mdi:cog\",\r\n info: \"mdi:information\",\r\n default: \"mdi:form-select\",\r\n };\r\n return iconMap[groupKey] || iconMap.default;\r\n};\r\n\r\n/* ================= 操作方法 ================= */\r\nconst toggleGroup = (groupKey: string): void => {\r\n collapsedGroups.value[groupKey] = !collapsedGroups.value[groupKey];\r\n};\r\n\r\nconst toggleAllGroups = (): void => {\r\n const shouldCollapse = !allCollapsed.value;\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = shouldCollapse;\r\n });\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n /* 初始化配置 */\r\n const config = props.layoutConfig?.card;\r\n if (config?.direction) {\r\n currentDirection.value = config.direction;\r\n }\r\n\r\n if (config?.showProgress !== undefined) {\r\n showProgress.value = config.showProgress;\r\n }\r\n\r\n /* 初始化折叠状态 */\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = false;\r\n });\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 卡片组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-card\">\r\n <!-- 布局配置面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showLayoutConfig\"\r\n class=\"layout-config-panel\"\r\n :bordered=\"false\"\r\n >\r\n <template #header>\r\n <div class=\"flex items-center gap-2\">\r\n <C_Icon :name=\"'mdi:cog'\" :size=\"18\" />\r\n <span>布局配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div class=\"config-item\">\r\n <span>卡片间距</span>\r\n <div class=\"flex items-center gap-2\">\r\n <NSlider\r\n v-model:value=\"cardGap\"\r\n :min=\"12\"\r\n :max=\"32\"\r\n :step=\"4\"\r\n class=\"w-24\"\r\n />\r\n <span class=\"text-xs min-w-12\">{{ cardGap }}px</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>显示图标</span>\r\n <NSwitch v-model:value=\"showIcons\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>可折叠</span>\r\n <NSwitch v-model:value=\"collapsible\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>布局方向</span>\r\n <NRadioGroup v-model:value=\"currentDirection\" size=\"small\">\r\n <NRadio value=\"vertical\">垂直</NRadio>\r\n <NRadio value=\"horizontal\">水平</NRadio>\r\n </NRadioGroup>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 表单内容区域 -->\r\n <div\r\n class=\"form-content\"\r\n :class=\"layoutClass\"\r\n :style=\"{ gap: `${cardGap}px` }\"\r\n >\r\n <!-- 无分组时的单卡片模式 -->\r\n <NCard v-if=\"!hasGroups\" class=\"single-card\" hoverable>\r\n <template #header>\r\n <div class=\"flex items-center gap-3\">\r\n <C_Icon\r\n v-if=\"showIcons\"\r\n :name=\"'mdi:form-select'\"\r\n :size=\"18\"\r\n style=\"color: #3b82f6\"\r\n />\r\n <span>表单信息</span>\r\n </div>\r\n </template>\r\n\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </NCard>\r\n\r\n <!-- 有分组时的多卡片模式 -->\r\n <template v-else>\r\n <NCard\r\n v-for=\"group in groupsWithItems\"\r\n :key=\"group.config.key\"\r\n class=\"group-card\"\r\n :class=\"[\r\n `${group.config.key}-card`,\r\n { collapsed: collapsible && collapsedGroups[group.config.key] },\r\n ]\"\r\n hoverable\r\n >\r\n <template #header>\r\n <div class=\"card-header\">\r\n <div class=\"header-info\">\r\n <C_Icon\r\n v-if=\"showIcons && group.config.icon\"\r\n :name=\"group.config.icon\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n <C_Icon\r\n v-else-if=\"showIcons\"\r\n :name=\"getDefaultIcon(group.config.key)\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n\r\n <div class=\"header-text\">\r\n <h3>{{ group.config.title }}</h3>\r\n <p v-if=\"group.config.description\">\r\n {{ group.config.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"header-actions\">\r\n <!-- 统计信息 -->\r\n <div class=\"field-stats\">\r\n <NBadge :value=\"group.items.length\" type=\"info\" show-zero />\r\n <NBadge\r\n :value=\"`${getFilledCount(group)}/${group.items.length}`\"\r\n :type=\"\r\n getFilledCount(group) === group.items.length\r\n ? 'success'\r\n : 'warning'\r\n \"\r\n />\r\n </div>\r\n\r\n <!-- 折叠按钮 -->\r\n <NButton\r\n v-if=\"collapsible\"\r\n quaternary\r\n circle\r\n size=\"small\"\r\n @click=\"toggleGroup(group.config.key)\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n collapsedGroups[group.config.key]\r\n ? 'mdi:chevron-down'\r\n : 'mdi:chevron-up'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n </template>\r\n\r\n <div v-show=\"!collapsedGroups[group.config.key]\" class=\"card-content\">\r\n <!-- 进度指示器 -->\r\n <div v-if=\"showProgress\" class=\"progress-section\">\r\n <NProgress\r\n :percentage=\"getGroupProgress(group)\"\r\n :color=\"getGroupProgress(group) === 100 ? '#52c41a' : '#1890ff'\"\r\n :show-indicator=\"false\"\r\n class=\"mb-4\"\r\n />\r\n </div>\r\n\r\n <!-- 表单项 -->\r\n <template v-for=\"item in group.items\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n </NCard>\r\n </template>\r\n </div>\r\n\r\n <!-- 统一操作面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showActionPanel\"\r\n class=\"action-panel\"\r\n :bordered=\"false\"\r\n >\r\n <div class=\"action-content\">\r\n <div class=\"status-summary\">\r\n <div class=\"status-item\">\r\n <span class=\"label\">完成进度:</span>\r\n <div class=\"progress-display\">\r\n <NProgress\r\n :percentage=\"totalProgress\"\r\n :show-indicator=\"false\"\r\n :color=\"totalProgress === 100 ? '#52c41a' : '#1890ff'\"\r\n class=\"flex-1\"\r\n />\r\n <span class=\"text-sm min-w-12\">{{ totalProgress }}%</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"action-buttons\">\r\n <NButton v-if=\"collapsible\" @click=\"toggleAllGroups\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n allCollapsed\r\n ? 'mdi:unfold-more-horizontal'\r\n : 'mdi:unfold-less-horizontal'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n {{ allCollapsed ? \"展开全部\" : \"折叠全部\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/**\r\n * 分组配置接口\r\n */\r\ninterface GroupConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n}\r\n\r\n/**\r\n * 分组数据接口\r\n */\r\ninterface GroupWithItems {\r\n config: GroupConfig;\r\n items: VNode[];\r\n}\r\n\r\n/**\r\n * 组件属性接口\r\n */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n card?: {\r\n groups?: GroupConfig[];\r\n direction?: \"vertical\" | \"horizontal\";\r\n showLayoutConfig?: boolean;\r\n showActionPanel?: boolean;\r\n showProgress?: boolean;\r\n };\r\n };\r\n options?: Array<{\r\n layout?: {\r\n group?: string;\r\n };\r\n prop?: string;\r\n }>;\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst cardGap = ref(20);\r\nconst showIcons = ref(true);\r\nconst collapsible = ref(true);\r\nconst showProgress = ref(true);\r\nconst currentDirection = ref<\"vertical\" | \"horizontal\">(\"vertical\");\r\nconst collapsedGroups = ref<Record<string, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst groups = computed((): GroupConfig[] => {\r\n return props.layoutConfig?.card?.groups || [];\r\n});\r\n\r\nconst hasGroups = computed((): boolean => {\r\n return groups.value.length > 0;\r\n});\r\n\r\nconst showLayoutConfig = computed((): boolean => {\r\n return props.layoutConfig?.card?.showLayoutConfig ?? true;\r\n});\r\n\r\nconst showActionPanel = computed((): boolean => {\r\n return props.layoutConfig?.card?.showActionPanel ?? true;\r\n});\r\n\r\nconst layoutClass = computed((): string => {\r\n if (!hasGroups.value) return \"layout-single\";\r\n return `layout-${currentDirection.value}`;\r\n});\r\n\r\nconst groupsWithItems = computed((): GroupWithItems[] => {\r\n if (!hasGroups.value) return [];\r\n\r\n const groupMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化分组映射 */\r\n groups.value.forEach((group) => {\r\n groupMap.set(group.key, []);\r\n });\r\n\r\n /* 将表单项分配到对应分组 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const groupKey = option?.layout?.group || groups.value[0]?.key || \"default\";\r\n\r\n if (!groupMap.has(groupKey)) {\r\n groupMap.set(groupKey, []);\r\n }\r\n groupMap.get(groupKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的分组 */\r\n return groups.value\r\n .map((groupConfig) => ({\r\n config: groupConfig,\r\n items: groupMap.get(groupConfig.key) || [],\r\n }))\r\n .filter((group) => group.items.length > 0);\r\n});\r\n\r\nconst allCollapsed = computed(() => {\r\n const groupKeys = Object.keys(collapsedGroups.value);\r\n return (\r\n groupKeys.length > 0 && groupKeys.every((key) => collapsedGroups.value[key])\r\n );\r\n});\r\n\r\nconst totalProgress = computed(() => {\r\n if (!props.options || props.options.length === 0) return 0;\r\n\r\n const filledCount = props.options.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n\r\n return Math.round((filledCount / props.options.length) * 100);\r\n});\r\n\r\n/* ================= 工具函数 ================= */\r\nconst isFieldFilled = (value: any): boolean => {\r\n if (value === null || value === undefined) return false;\r\n if (Array.isArray(value)) return value.length > 0;\r\n if (typeof value === \"string\") return value.trim() !== \"\";\r\n return true;\r\n};\r\n\r\nconst getFilledCount = (group: GroupWithItems): number => {\r\n if (!props.options) return 0;\r\n\r\n const groupFields = props.options.filter(\r\n (option) => option.layout?.group === group.config.key,\r\n );\r\n\r\n return groupFields.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n};\r\n\r\nconst getGroupProgress = (group: GroupWithItems): number => {\r\n if (group.items.length === 0) return 0;\r\n const filledCount = getFilledCount(group);\r\n return Math.round((filledCount / group.items.length) * 100);\r\n};\r\n\r\nconst getDefaultIcon = (groupKey: string): string => {\r\n const iconMap: Record<string, string> = {\r\n basic: \"mdi:account\",\r\n contact: \"mdi:phone\",\r\n preferences: \"mdi:cog\",\r\n settings: \"mdi:cog\",\r\n info: \"mdi:information\",\r\n default: \"mdi:form-select\",\r\n };\r\n return iconMap[groupKey] || iconMap.default;\r\n};\r\n\r\n/* ================= 操作方法 ================= */\r\nconst toggleGroup = (groupKey: string): void => {\r\n collapsedGroups.value[groupKey] = !collapsedGroups.value[groupKey];\r\n};\r\n\r\nconst toggleAllGroups = (): void => {\r\n const shouldCollapse = !allCollapsed.value;\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = shouldCollapse;\r\n });\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n /* 初始化配置 */\r\n const config = props.layoutConfig?.card;\r\n if (config?.direction) {\r\n currentDirection.value = config.direction;\r\n }\r\n\r\n if (config?.showProgress !== undefined) {\r\n showProgress.value = config.showProgress;\r\n }\r\n\r\n /* 初始化折叠状态 */\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = false;\r\n });\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 卡片组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-card\">\r\n <!-- 布局配置面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showLayoutConfig\"\r\n class=\"layout-config-panel\"\r\n :bordered=\"false\"\r\n >\r\n <template #header>\r\n <div class=\"flex items-center gap-2\">\r\n <C_Icon :name=\"'mdi:cog'\" :size=\"18\" />\r\n <span>布局配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div class=\"config-item\">\r\n <span>卡片间距</span>\r\n <div class=\"flex items-center gap-2\">\r\n <NSlider\r\n v-model:value=\"cardGap\"\r\n :min=\"12\"\r\n :max=\"32\"\r\n :step=\"4\"\r\n class=\"w-24\"\r\n />\r\n <span class=\"text-xs min-w-12\">{{ cardGap }}px</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>显示图标</span>\r\n <NSwitch v-model:value=\"showIcons\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>可折叠</span>\r\n <NSwitch v-model:value=\"collapsible\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>布局方向</span>\r\n <NRadioGroup v-model:value=\"currentDirection\" size=\"small\">\r\n <NRadio value=\"vertical\">垂直</NRadio>\r\n <NRadio value=\"horizontal\">水平</NRadio>\r\n </NRadioGroup>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 表单内容区域 -->\r\n <div\r\n class=\"form-content\"\r\n :class=\"layoutClass\"\r\n :style=\"{ gap: `${cardGap}px` }\"\r\n >\r\n <!-- 无分组时的单卡片模式 -->\r\n <NCard v-if=\"!hasGroups\" class=\"single-card\" hoverable>\r\n <template #header>\r\n <div class=\"flex items-center gap-3\">\r\n <C_Icon\r\n v-if=\"showIcons\"\r\n :name=\"'mdi:form-select'\"\r\n :size=\"18\"\r\n style=\"color: #3b82f6\"\r\n />\r\n <span>表单信息</span>\r\n </div>\r\n </template>\r\n\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </NCard>\r\n\r\n <!-- 有分组时的多卡片模式 -->\r\n <template v-else>\r\n <NCard\r\n v-for=\"group in groupsWithItems\"\r\n :key=\"group.config.key\"\r\n class=\"group-card\"\r\n :class=\"[\r\n `${group.config.key}-card`,\r\n { collapsed: collapsible && collapsedGroups[group.config.key] },\r\n ]\"\r\n hoverable\r\n >\r\n <template #header>\r\n <div class=\"card-header\">\r\n <div class=\"header-info\">\r\n <C_Icon\r\n v-if=\"showIcons && group.config.icon\"\r\n :name=\"group.config.icon\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n <C_Icon\r\n v-else-if=\"showIcons\"\r\n :name=\"getDefaultIcon(group.config.key)\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n\r\n <div class=\"header-text\">\r\n <h3>{{ group.config.title }}</h3>\r\n <p v-if=\"group.config.description\">\r\n {{ group.config.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"header-actions\">\r\n <!-- 统计信息 -->\r\n <div class=\"field-stats\">\r\n <NBadge :value=\"group.items.length\" type=\"info\" show-zero />\r\n <NBadge\r\n :value=\"`${getFilledCount(group)}/${group.items.length}`\"\r\n :type=\"\r\n getFilledCount(group) === group.items.length\r\n ? 'success'\r\n : 'warning'\r\n \"\r\n />\r\n </div>\r\n\r\n <!-- 折叠按钮 -->\r\n <NButton\r\n v-if=\"collapsible\"\r\n quaternary\r\n circle\r\n size=\"small\"\r\n @click=\"toggleGroup(group.config.key)\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n collapsedGroups[group.config.key]\r\n ? 'mdi:chevron-down'\r\n : 'mdi:chevron-up'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n </template>\r\n\r\n <div v-show=\"!collapsedGroups[group.config.key]\" class=\"card-content\">\r\n <!-- 进度指示器 -->\r\n <div v-if=\"showProgress\" class=\"progress-section\">\r\n <NProgress\r\n :percentage=\"getGroupProgress(group)\"\r\n :color=\"getGroupProgress(group) === 100 ? '#52c41a' : '#1890ff'\"\r\n :show-indicator=\"false\"\r\n class=\"mb-4\"\r\n />\r\n </div>\r\n\r\n <!-- 表单项 -->\r\n <template v-for=\"item in group.items\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n </NCard>\r\n </template>\r\n </div>\r\n\r\n <!-- 统一操作面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showActionPanel\"\r\n class=\"action-panel\"\r\n :bordered=\"false\"\r\n >\r\n <div class=\"action-content\">\r\n <div class=\"status-summary\">\r\n <div class=\"status-item\">\r\n <span class=\"label\">完成进度:</span>\r\n <div class=\"progress-display\">\r\n <NProgress\r\n :percentage=\"totalProgress\"\r\n :show-indicator=\"false\"\r\n :color=\"totalProgress === 100 ? '#52c41a' : '#1890ff'\"\r\n class=\"flex-1\"\r\n />\r\n <span class=\"text-sm min-w-12\">{{ totalProgress }}%</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"action-buttons\">\r\n <NButton v-if=\"collapsible\" @click=\"toggleAllGroups\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n allCollapsed\r\n ? 'mdi:unfold-more-horizontal'\r\n : 'mdi:unfold-less-horizontal'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n {{ allCollapsed ? \"展开全部\" : \"折叠全部\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/**\r\n * 分组配置接口\r\n */\r\ninterface GroupConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n}\r\n\r\n/**\r\n * 分组数据接口\r\n */\r\ninterface GroupWithItems {\r\n config: GroupConfig;\r\n items: VNode[];\r\n}\r\n\r\n/**\r\n * 组件属性接口\r\n */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n card?: {\r\n groups?: GroupConfig[];\r\n direction?: \"vertical\" | \"horizontal\";\r\n showLayoutConfig?: boolean;\r\n showActionPanel?: boolean;\r\n showProgress?: boolean;\r\n };\r\n };\r\n options?: Array<{\r\n layout?: {\r\n group?: string;\r\n };\r\n prop?: string;\r\n }>;\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst cardGap = ref(20);\r\nconst showIcons = ref(true);\r\nconst collapsible = ref(true);\r\nconst showProgress = ref(true);\r\nconst currentDirection = ref<\"vertical\" | \"horizontal\">(\"vertical\");\r\nconst collapsedGroups = ref<Record<string, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst groups = computed((): GroupConfig[] => {\r\n return props.layoutConfig?.card?.groups || [];\r\n});\r\n\r\nconst hasGroups = computed((): boolean => {\r\n return groups.value.length > 0;\r\n});\r\n\r\nconst showLayoutConfig = computed((): boolean => {\r\n return props.layoutConfig?.card?.showLayoutConfig ?? true;\r\n});\r\n\r\nconst showActionPanel = computed((): boolean => {\r\n return props.layoutConfig?.card?.showActionPanel ?? true;\r\n});\r\n\r\nconst layoutClass = computed((): string => {\r\n if (!hasGroups.value) return \"layout-single\";\r\n return `layout-${currentDirection.value}`;\r\n});\r\n\r\nconst groupsWithItems = computed((): GroupWithItems[] => {\r\n if (!hasGroups.value) return [];\r\n\r\n const groupMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化分组映射 */\r\n groups.value.forEach((group) => {\r\n groupMap.set(group.key, []);\r\n });\r\n\r\n /* 将表单项分配到对应分组 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const groupKey = option?.layout?.group || groups.value[0]?.key || \"default\";\r\n\r\n if (!groupMap.has(groupKey)) {\r\n groupMap.set(groupKey, []);\r\n }\r\n groupMap.get(groupKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的分组 */\r\n return groups.value\r\n .map((groupConfig) => ({\r\n config: groupConfig,\r\n items: groupMap.get(groupConfig.key) || [],\r\n }))\r\n .filter((group) => group.items.length > 0);\r\n});\r\n\r\nconst allCollapsed = computed(() => {\r\n const groupKeys = Object.keys(collapsedGroups.value);\r\n return (\r\n groupKeys.length > 0 && groupKeys.every((key) => collapsedGroups.value[key])\r\n );\r\n});\r\n\r\nconst totalProgress = computed(() => {\r\n if (!props.options || props.options.length === 0) return 0;\r\n\r\n const filledCount = props.options.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n\r\n return Math.round((filledCount / props.options.length) * 100);\r\n});\r\n\r\n/* ================= 工具函数 ================= */\r\nconst isFieldFilled = (value: any): boolean => {\r\n if (value === null || value === undefined) return false;\r\n if (Array.isArray(value)) return value.length > 0;\r\n if (typeof value === \"string\") return value.trim() !== \"\";\r\n return true;\r\n};\r\n\r\nconst getFilledCount = (group: GroupWithItems): number => {\r\n if (!props.options) return 0;\r\n\r\n const groupFields = props.options.filter(\r\n (option) => option.layout?.group === group.config.key,\r\n );\r\n\r\n return groupFields.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n};\r\n\r\nconst getGroupProgress = (group: GroupWithItems): number => {\r\n if (group.items.length === 0) return 0;\r\n const filledCount = getFilledCount(group);\r\n return Math.round((filledCount / group.items.length) * 100);\r\n};\r\n\r\nconst getDefaultIcon = (groupKey: string): string => {\r\n const iconMap: Record<string, string> = {\r\n basic: \"mdi:account\",\r\n contact: \"mdi:phone\",\r\n preferences: \"mdi:cog\",\r\n settings: \"mdi:cog\",\r\n info: \"mdi:information\",\r\n default: \"mdi:form-select\",\r\n };\r\n return iconMap[groupKey] || iconMap.default;\r\n};\r\n\r\n/* ================= 操作方法 ================= */\r\nconst toggleGroup = (groupKey: string): void => {\r\n collapsedGroups.value[groupKey] = !collapsedGroups.value[groupKey];\r\n};\r\n\r\nconst toggleAllGroups = (): void => {\r\n const shouldCollapse = !allCollapsed.value;\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = shouldCollapse;\r\n });\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n /* 初始化配置 */\r\n const config = props.layoutConfig?.card;\r\n if (config?.direction) {\r\n currentDirection.value = config.direction;\r\n }\r\n\r\n if (config?.showProgress !== undefined) {\r\n showProgress.value = config.showProgress;\r\n }\r\n\r\n /* 初始化折叠状态 */\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = false;\r\n });\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 标签布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-tabs\">\r\n <!-- 无标签配置时的单一面板模式 -->\r\n <div v-if=\"!hasTabs\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有标签配置时的分标签模式 -->\r\n <div v-else class=\"tabs-container\">\r\n <NTabs\r\n v-model:value=\"currentTab\"\r\n :type=\"tabsConfig.type\"\r\n :size=\"tabsConfig.size\"\r\n :placement=\"tabsConfig.placement\"\r\n :animated=\"tabsConfig.animated\"\r\n :closable=\"tabsConfig.closable\"\r\n :addable=\"tabsConfig.addable\"\r\n class=\"form-tabs\"\r\n @update:value=\"handleTabChange\"\r\n @close=\"handleTabClose\"\r\n @add=\"handleTabAdd\"\r\n >\r\n <NTabPane\r\n v-for=\"tab in tabsWithItems\"\r\n :key=\"tab.config.key\"\r\n :name=\"tab.config.key\"\r\n :tab=\"tab.config.title\"\r\n :disabled=\"tab.config.disabled\"\r\n :closable=\"tab.config.closable\"\r\n >\r\n <template #tab>\r\n <NSpace align=\"center\" :size=\"8\">\r\n <C_Icon\r\n v-if=\"tab.config.icon\"\r\n :name=\"tab.config.icon\"\r\n :size=\"16\"\r\n class=\"tab-icon\"\r\n />\r\n <span>{{ tab.config.title }}</span>\r\n <NBadge\r\n v-if=\"tabsConfig.showCount\"\r\n :value=\"tab.items.length\"\r\n :max=\"99\"\r\n :show=\"tab.items.length > 0\"\r\n type=\"info\"\r\n />\r\n </NSpace>\r\n </template>\r\n\r\n <!-- 标签页头部信息 -->\r\n <div\r\n v-if=\"tabsConfig.showTabHeader && tab.config.description\"\r\n class=\"tab-header\"\r\n >\r\n <p class=\"tab-description\">{{ tab.config.description }}</p>\r\n </div>\r\n\r\n <!-- 标签页内的表单项 -->\r\n <div class=\"tab-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in tab.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 空状态 -->\r\n <NEmpty\r\n v-if=\"tab.items.length === 0\"\r\n description=\"暂无表单项\"\r\n class=\"tab-empty\"\r\n />\r\n </NTabPane>\r\n </NTabs>\r\n\r\n <!-- 标签页操作按钮 -->\r\n <div v-if=\"tabsConfig.showActions\" class=\"tabs-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NSpace>\r\n <NButton\r\n v-if=\"tabsConfig.validateBeforeSwitch\"\r\n type=\"primary\"\r\n size=\"small\"\r\n @click=\"validateCurrentTab\"\r\n >\r\n <C_Icon\r\n :name=\"'carbon:star-check'\"\r\n :size=\"14\"\r\n style=\"margin-right: 4px\"\r\n />\r\n 验证当前标签\r\n </NButton>\r\n </NSpace>\r\n\r\n <NSpace>\r\n <slot\r\n name=\"tab-actions\"\r\n :current-tab=\"currentTab\"\r\n :total-tabs=\"tabsWithItems.length\"\r\n :validate-tab=\"validateCurrentTab\"\r\n :switch-to-tab=\"switchToTab\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n ref,\r\n reactive,\r\n computed,\r\n watch,\r\n nextTick,\r\n onMounted,\r\n readonly,\r\n} from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface TabConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n disabled?: boolean;\r\n closable?: boolean;\r\n}\r\n\r\ninterface TabsLayoutConfig {\r\n tabs: TabConfig[];\r\n type?: \"line\" | \"card\" | \"segment\";\r\n size?: \"small\" | \"medium\" | \"large\";\r\n placement?: \"top\" | \"right\" | \"bottom\" | \"left\";\r\n animated?: boolean;\r\n closable?: boolean;\r\n addable?: boolean;\r\n showTabHeader?: boolean;\r\n showActions?: boolean;\r\n showCount?: boolean;\r\n validateBeforeSwitch?: boolean;\r\n defaultTab?: string;\r\n}\r\n\r\ninterface TabWithItems {\r\n config: TabConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n tabs?: TabsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n tab?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"tab-change\": [tabKey: string, tabIndex: number];\r\n \"tab-before-change\": [currentTab: string, targetTab: string];\r\n \"tab-validate\": [tabKey: string];\r\n \"tab-close\": [tabKey: string];\r\n \"tab-add\": [];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentTab = ref<string>(\"\");\r\nconst tabValidationStatus = reactive<Record<string, boolean>>({});\r\n\r\n/* ================= 默认配置 ================= */\r\nconst getDefaultTabsConfig = (): Required<TabsLayoutConfig> => ({\r\n tabs: [],\r\n type: \"line\",\r\n size: \"medium\",\r\n placement: \"top\",\r\n animated: true,\r\n closable: false,\r\n addable: false,\r\n showTabHeader: true,\r\n showActions: false,\r\n showCount: false,\r\n validateBeforeSwitch: false,\r\n defaultTab: \"\",\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst tabsConfig = computed(() => {\r\n const defaultConfig = getDefaultTabsConfig();\r\n const userConfig = props.layoutConfig?.tabs || {};\r\n\r\n return {\r\n ...defaultConfig,\r\n ...userConfig,\r\n };\r\n});\r\n\r\nconst hasTabs = computed((): boolean => {\r\n return tabsConfig.value.tabs.length > 0;\r\n});\r\n\r\nconst tabsWithItems = computed((): TabWithItems[] => {\r\n if (!hasTabs.value) return [];\r\n\r\n const tabMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化标签映射 */\r\n tabsConfig.value.tabs.forEach((tab) => {\r\n tabMap.set(tab.key, []);\r\n });\r\n\r\n /* 分配表单项到对应标签 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const tabKey =\r\n option?.layout?.tab || tabsConfig.value.tabs[0]?.key || \"default\";\r\n\r\n if (!tabMap.has(tabKey)) {\r\n tabMap.set(tabKey, []);\r\n }\r\n tabMap.get(tabKey)!.push(item);\r\n });\r\n\r\n /* 返回所有标签(包括空标签) */\r\n return tabsConfig.value.tabs.map((tabConfig) => ({\r\n config: tabConfig,\r\n items: tabMap.get(tabConfig.key) || [],\r\n }));\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `tab-item-${index}`;\r\n};\r\n\r\nconst validateCurrentTab = async (): Promise<boolean> => {\r\n if (!currentTab.value) return true;\r\n\r\n try {\r\n emit(\"tab-validate\", currentTab.value);\r\n const valid = true;\r\n tabValidationStatus[currentTab.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签验证失败:\", error);\r\n tabValidationStatus[currentTab.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToTab = async (targetTab: string): Promise<boolean> => {\r\n if (!targetTab || targetTab === currentTab.value) {\r\n return true;\r\n }\r\n\r\n const targetTabExists = tabsWithItems.value.some(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (!targetTabExists) {\r\n return false;\r\n }\r\n\r\n try {\r\n /* 验证当前标签(如果需要) */\r\n if (tabsConfig.value.validateBeforeSwitch && currentTab.value) {\r\n const isValid = await validateCurrentTab();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发标签切换前事件 */\r\n if (currentTab.value) {\r\n emit(\"tab-before-change\", currentTab.value, targetTab);\r\n }\r\n\r\n currentTab.value = targetTab;\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签切换失败:\", error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleTabChange = (tabKey: string): void => {\r\n switchToTab(tabKey);\r\n};\r\n\r\nconst handleTabClose = (tabKey: string): void => {\r\n emit(\"tab-close\", tabKey);\r\n};\r\n\r\nconst handleTabAdd = (): void => {\r\n emit(\"tab-add\");\r\n};\r\n\r\nconst initializeCurrentTab = (): void => {\r\n if (!hasTabs.value || tabsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultTab } = tabsConfig.value;\r\n const targetTab = defaultTab || tabsWithItems.value[0]?.config.key;\r\n\r\n if (targetTab && targetTab !== currentTab.value) {\r\n currentTab.value = targetTab;\r\n nextTick(() => {\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (tabIndex >= 0) {\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n }\r\n });\r\n }\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n initializeCurrentTab();\r\n});\r\n\r\n/* 只监听标签结构变化(key / 数量),不监听表单项内容变化 */\r\nconst tabStructureKey = computed(() =>\r\n tabsConfig.value.tabs.map((t) => t.key).join(\",\"),\r\n);\r\n\r\nwatch(tabStructureKey, () => {\r\n if (\r\n currentTab.value &&\r\n !tabsConfig.value.tabs.some((tab) => tab.key === currentTab.value)\r\n ) {\r\n initializeCurrentTab();\r\n }\r\n});\r\n\r\n/* 监听配置变化 */\r\nwatch(\r\n () => tabsConfig.value.defaultTab,\r\n (newDefaultTab) => {\r\n if (newDefaultTab && newDefaultTab !== currentTab.value) {\r\n switchToTab(newDefaultTab);\r\n }\r\n },\r\n);\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n switchToTab,\r\n validateCurrentTab,\r\n currentTab: readonly(currentTab),\r\n totalTabs: computed(() => tabsWithItems.value.length),\r\n tabsWithItems: readonly(tabsWithItems),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 标签布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-tabs\">\r\n <!-- 无标签配置时的单一面板模式 -->\r\n <div v-if=\"!hasTabs\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有标签配置时的分标签模式 -->\r\n <div v-else class=\"tabs-container\">\r\n <NTabs\r\n v-model:value=\"currentTab\"\r\n :type=\"tabsConfig.type\"\r\n :size=\"tabsConfig.size\"\r\n :placement=\"tabsConfig.placement\"\r\n :animated=\"tabsConfig.animated\"\r\n :closable=\"tabsConfig.closable\"\r\n :addable=\"tabsConfig.addable\"\r\n class=\"form-tabs\"\r\n @update:value=\"handleTabChange\"\r\n @close=\"handleTabClose\"\r\n @add=\"handleTabAdd\"\r\n >\r\n <NTabPane\r\n v-for=\"tab in tabsWithItems\"\r\n :key=\"tab.config.key\"\r\n :name=\"tab.config.key\"\r\n :tab=\"tab.config.title\"\r\n :disabled=\"tab.config.disabled\"\r\n :closable=\"tab.config.closable\"\r\n >\r\n <template #tab>\r\n <NSpace align=\"center\" :size=\"8\">\r\n <C_Icon\r\n v-if=\"tab.config.icon\"\r\n :name=\"tab.config.icon\"\r\n :size=\"16\"\r\n class=\"tab-icon\"\r\n />\r\n <span>{{ tab.config.title }}</span>\r\n <NBadge\r\n v-if=\"tabsConfig.showCount\"\r\n :value=\"tab.items.length\"\r\n :max=\"99\"\r\n :show=\"tab.items.length > 0\"\r\n type=\"info\"\r\n />\r\n </NSpace>\r\n </template>\r\n\r\n <!-- 标签页头部信息 -->\r\n <div\r\n v-if=\"tabsConfig.showTabHeader && tab.config.description\"\r\n class=\"tab-header\"\r\n >\r\n <p class=\"tab-description\">{{ tab.config.description }}</p>\r\n </div>\r\n\r\n <!-- 标签页内的表单项 -->\r\n <div class=\"tab-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in tab.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 空状态 -->\r\n <NEmpty\r\n v-if=\"tab.items.length === 0\"\r\n description=\"暂无表单项\"\r\n class=\"tab-empty\"\r\n />\r\n </NTabPane>\r\n </NTabs>\r\n\r\n <!-- 标签页操作按钮 -->\r\n <div v-if=\"tabsConfig.showActions\" class=\"tabs-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NSpace>\r\n <NButton\r\n v-if=\"tabsConfig.validateBeforeSwitch\"\r\n type=\"primary\"\r\n size=\"small\"\r\n @click=\"validateCurrentTab\"\r\n >\r\n <C_Icon\r\n :name=\"'carbon:star-check'\"\r\n :size=\"14\"\r\n style=\"margin-right: 4px\"\r\n />\r\n 验证当前标签\r\n </NButton>\r\n </NSpace>\r\n\r\n <NSpace>\r\n <slot\r\n name=\"tab-actions\"\r\n :current-tab=\"currentTab\"\r\n :total-tabs=\"tabsWithItems.length\"\r\n :validate-tab=\"validateCurrentTab\"\r\n :switch-to-tab=\"switchToTab\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n ref,\r\n reactive,\r\n computed,\r\n watch,\r\n nextTick,\r\n onMounted,\r\n readonly,\r\n} from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface TabConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n disabled?: boolean;\r\n closable?: boolean;\r\n}\r\n\r\ninterface TabsLayoutConfig {\r\n tabs: TabConfig[];\r\n type?: \"line\" | \"card\" | \"segment\";\r\n size?: \"small\" | \"medium\" | \"large\";\r\n placement?: \"top\" | \"right\" | \"bottom\" | \"left\";\r\n animated?: boolean;\r\n closable?: boolean;\r\n addable?: boolean;\r\n showTabHeader?: boolean;\r\n showActions?: boolean;\r\n showCount?: boolean;\r\n validateBeforeSwitch?: boolean;\r\n defaultTab?: string;\r\n}\r\n\r\ninterface TabWithItems {\r\n config: TabConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n tabs?: TabsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n tab?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"tab-change\": [tabKey: string, tabIndex: number];\r\n \"tab-before-change\": [currentTab: string, targetTab: string];\r\n \"tab-validate\": [tabKey: string];\r\n \"tab-close\": [tabKey: string];\r\n \"tab-add\": [];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentTab = ref<string>(\"\");\r\nconst tabValidationStatus = reactive<Record<string, boolean>>({});\r\n\r\n/* ================= 默认配置 ================= */\r\nconst getDefaultTabsConfig = (): Required<TabsLayoutConfig> => ({\r\n tabs: [],\r\n type: \"line\",\r\n size: \"medium\",\r\n placement: \"top\",\r\n animated: true,\r\n closable: false,\r\n addable: false,\r\n showTabHeader: true,\r\n showActions: false,\r\n showCount: false,\r\n validateBeforeSwitch: false,\r\n defaultTab: \"\",\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst tabsConfig = computed(() => {\r\n const defaultConfig = getDefaultTabsConfig();\r\n const userConfig = props.layoutConfig?.tabs || {};\r\n\r\n return {\r\n ...defaultConfig,\r\n ...userConfig,\r\n };\r\n});\r\n\r\nconst hasTabs = computed((): boolean => {\r\n return tabsConfig.value.tabs.length > 0;\r\n});\r\n\r\nconst tabsWithItems = computed((): TabWithItems[] => {\r\n if (!hasTabs.value) return [];\r\n\r\n const tabMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化标签映射 */\r\n tabsConfig.value.tabs.forEach((tab) => {\r\n tabMap.set(tab.key, []);\r\n });\r\n\r\n /* 分配表单项到对应标签 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const tabKey =\r\n option?.layout?.tab || tabsConfig.value.tabs[0]?.key || \"default\";\r\n\r\n if (!tabMap.has(tabKey)) {\r\n tabMap.set(tabKey, []);\r\n }\r\n tabMap.get(tabKey)!.push(item);\r\n });\r\n\r\n /* 返回所有标签(包括空标签) */\r\n return tabsConfig.value.tabs.map((tabConfig) => ({\r\n config: tabConfig,\r\n items: tabMap.get(tabConfig.key) || [],\r\n }));\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `tab-item-${index}`;\r\n};\r\n\r\nconst validateCurrentTab = async (): Promise<boolean> => {\r\n if (!currentTab.value) return true;\r\n\r\n try {\r\n emit(\"tab-validate\", currentTab.value);\r\n const valid = true;\r\n tabValidationStatus[currentTab.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签验证失败:\", error);\r\n tabValidationStatus[currentTab.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToTab = async (targetTab: string): Promise<boolean> => {\r\n if (!targetTab || targetTab === currentTab.value) {\r\n return true;\r\n }\r\n\r\n const targetTabExists = tabsWithItems.value.some(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (!targetTabExists) {\r\n return false;\r\n }\r\n\r\n try {\r\n /* 验证当前标签(如果需要) */\r\n if (tabsConfig.value.validateBeforeSwitch && currentTab.value) {\r\n const isValid = await validateCurrentTab();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发标签切换前事件 */\r\n if (currentTab.value) {\r\n emit(\"tab-before-change\", currentTab.value, targetTab);\r\n }\r\n\r\n currentTab.value = targetTab;\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签切换失败:\", error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleTabChange = (tabKey: string): void => {\r\n switchToTab(tabKey);\r\n};\r\n\r\nconst handleTabClose = (tabKey: string): void => {\r\n emit(\"tab-close\", tabKey);\r\n};\r\n\r\nconst handleTabAdd = (): void => {\r\n emit(\"tab-add\");\r\n};\r\n\r\nconst initializeCurrentTab = (): void => {\r\n if (!hasTabs.value || tabsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultTab } = tabsConfig.value;\r\n const targetTab = defaultTab || tabsWithItems.value[0]?.config.key;\r\n\r\n if (targetTab && targetTab !== currentTab.value) {\r\n currentTab.value = targetTab;\r\n nextTick(() => {\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (tabIndex >= 0) {\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n }\r\n });\r\n }\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n initializeCurrentTab();\r\n});\r\n\r\n/* 只监听标签结构变化(key / 数量),不监听表单项内容变化 */\r\nconst tabStructureKey = computed(() =>\r\n tabsConfig.value.tabs.map((t) => t.key).join(\",\"),\r\n);\r\n\r\nwatch(tabStructureKey, () => {\r\n if (\r\n currentTab.value &&\r\n !tabsConfig.value.tabs.some((tab) => tab.key === currentTab.value)\r\n ) {\r\n initializeCurrentTab();\r\n }\r\n});\r\n\r\n/* 监听配置变化 */\r\nwatch(\r\n () => tabsConfig.value.defaultTab,\r\n (newDefaultTab) => {\r\n if (newDefaultTab && newDefaultTab !== currentTab.value) {\r\n switchToTab(newDefaultTab);\r\n }\r\n },\r\n);\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n switchToTab,\r\n validateCurrentTab,\r\n currentTab: readonly(currentTab),\r\n totalTabs: computed(() => tabsWithItems.value.length),\r\n tabsWithItems: readonly(tabsWithItems),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 标签布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-tabs\">\r\n <!-- 无标签配置时的单一面板模式 -->\r\n <div v-if=\"!hasTabs\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有标签配置时的分标签模式 -->\r\n <div v-else class=\"tabs-container\">\r\n <NTabs\r\n v-model:value=\"currentTab\"\r\n :type=\"tabsConfig.type\"\r\n :size=\"tabsConfig.size\"\r\n :placement=\"tabsConfig.placement\"\r\n :animated=\"tabsConfig.animated\"\r\n :closable=\"tabsConfig.closable\"\r\n :addable=\"tabsConfig.addable\"\r\n class=\"form-tabs\"\r\n @update:value=\"handleTabChange\"\r\n @close=\"handleTabClose\"\r\n @add=\"handleTabAdd\"\r\n >\r\n <NTabPane\r\n v-for=\"tab in tabsWithItems\"\r\n :key=\"tab.config.key\"\r\n :name=\"tab.config.key\"\r\n :tab=\"tab.config.title\"\r\n :disabled=\"tab.config.disabled\"\r\n :closable=\"tab.config.closable\"\r\n >\r\n <template #tab>\r\n <NSpace align=\"center\" :size=\"8\">\r\n <C_Icon\r\n v-if=\"tab.config.icon\"\r\n :name=\"tab.config.icon\"\r\n :size=\"16\"\r\n class=\"tab-icon\"\r\n />\r\n <span>{{ tab.config.title }}</span>\r\n <NBadge\r\n v-if=\"tabsConfig.showCount\"\r\n :value=\"tab.items.length\"\r\n :max=\"99\"\r\n :show=\"tab.items.length > 0\"\r\n type=\"info\"\r\n />\r\n </NSpace>\r\n </template>\r\n\r\n <!-- 标签页头部信息 -->\r\n <div\r\n v-if=\"tabsConfig.showTabHeader && tab.config.description\"\r\n class=\"tab-header\"\r\n >\r\n <p class=\"tab-description\">{{ tab.config.description }}</p>\r\n </div>\r\n\r\n <!-- 标签页内的表单项 -->\r\n <div class=\"tab-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in tab.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 空状态 -->\r\n <NEmpty\r\n v-if=\"tab.items.length === 0\"\r\n description=\"暂无表单项\"\r\n class=\"tab-empty\"\r\n />\r\n </NTabPane>\r\n </NTabs>\r\n\r\n <!-- 标签页操作按钮 -->\r\n <div v-if=\"tabsConfig.showActions\" class=\"tabs-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NSpace>\r\n <NButton\r\n v-if=\"tabsConfig.validateBeforeSwitch\"\r\n type=\"primary\"\r\n size=\"small\"\r\n @click=\"validateCurrentTab\"\r\n >\r\n <C_Icon\r\n :name=\"'carbon:star-check'\"\r\n :size=\"14\"\r\n style=\"margin-right: 4px\"\r\n />\r\n 验证当前标签\r\n </NButton>\r\n </NSpace>\r\n\r\n <NSpace>\r\n <slot\r\n name=\"tab-actions\"\r\n :current-tab=\"currentTab\"\r\n :total-tabs=\"tabsWithItems.length\"\r\n :validate-tab=\"validateCurrentTab\"\r\n :switch-to-tab=\"switchToTab\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n ref,\r\n reactive,\r\n computed,\r\n watch,\r\n nextTick,\r\n onMounted,\r\n readonly,\r\n} from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface TabConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n disabled?: boolean;\r\n closable?: boolean;\r\n}\r\n\r\ninterface TabsLayoutConfig {\r\n tabs: TabConfig[];\r\n type?: \"line\" | \"card\" | \"segment\";\r\n size?: \"small\" | \"medium\" | \"large\";\r\n placement?: \"top\" | \"right\" | \"bottom\" | \"left\";\r\n animated?: boolean;\r\n closable?: boolean;\r\n addable?: boolean;\r\n showTabHeader?: boolean;\r\n showActions?: boolean;\r\n showCount?: boolean;\r\n validateBeforeSwitch?: boolean;\r\n defaultTab?: string;\r\n}\r\n\r\ninterface TabWithItems {\r\n config: TabConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n tabs?: TabsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n tab?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"tab-change\": [tabKey: string, tabIndex: number];\r\n \"tab-before-change\": [currentTab: string, targetTab: string];\r\n \"tab-validate\": [tabKey: string];\r\n \"tab-close\": [tabKey: string];\r\n \"tab-add\": [];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentTab = ref<string>(\"\");\r\nconst tabValidationStatus = reactive<Record<string, boolean>>({});\r\n\r\n/* ================= 默认配置 ================= */\r\nconst getDefaultTabsConfig = (): Required<TabsLayoutConfig> => ({\r\n tabs: [],\r\n type: \"line\",\r\n size: \"medium\",\r\n placement: \"top\",\r\n animated: true,\r\n closable: false,\r\n addable: false,\r\n showTabHeader: true,\r\n showActions: false,\r\n showCount: false,\r\n validateBeforeSwitch: false,\r\n defaultTab: \"\",\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst tabsConfig = computed(() => {\r\n const defaultConfig = getDefaultTabsConfig();\r\n const userConfig = props.layoutConfig?.tabs || {};\r\n\r\n return {\r\n ...defaultConfig,\r\n ...userConfig,\r\n };\r\n});\r\n\r\nconst hasTabs = computed((): boolean => {\r\n return tabsConfig.value.tabs.length > 0;\r\n});\r\n\r\nconst tabsWithItems = computed((): TabWithItems[] => {\r\n if (!hasTabs.value) return [];\r\n\r\n const tabMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化标签映射 */\r\n tabsConfig.value.tabs.forEach((tab) => {\r\n tabMap.set(tab.key, []);\r\n });\r\n\r\n /* 分配表单项到对应标签 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const tabKey =\r\n option?.layout?.tab || tabsConfig.value.tabs[0]?.key || \"default\";\r\n\r\n if (!tabMap.has(tabKey)) {\r\n tabMap.set(tabKey, []);\r\n }\r\n tabMap.get(tabKey)!.push(item);\r\n });\r\n\r\n /* 返回所有标签(包括空标签) */\r\n return tabsConfig.value.tabs.map((tabConfig) => ({\r\n config: tabConfig,\r\n items: tabMap.get(tabConfig.key) || [],\r\n }));\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `tab-item-${index}`;\r\n};\r\n\r\nconst validateCurrentTab = async (): Promise<boolean> => {\r\n if (!currentTab.value) return true;\r\n\r\n try {\r\n emit(\"tab-validate\", currentTab.value);\r\n const valid = true;\r\n tabValidationStatus[currentTab.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签验证失败:\", error);\r\n tabValidationStatus[currentTab.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToTab = async (targetTab: string): Promise<boolean> => {\r\n if (!targetTab || targetTab === currentTab.value) {\r\n return true;\r\n }\r\n\r\n const targetTabExists = tabsWithItems.value.some(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (!targetTabExists) {\r\n return false;\r\n }\r\n\r\n try {\r\n /* 验证当前标签(如果需要) */\r\n if (tabsConfig.value.validateBeforeSwitch && currentTab.value) {\r\n const isValid = await validateCurrentTab();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发标签切换前事件 */\r\n if (currentTab.value) {\r\n emit(\"tab-before-change\", currentTab.value, targetTab);\r\n }\r\n\r\n currentTab.value = targetTab;\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签切换失败:\", error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleTabChange = (tabKey: string): void => {\r\n switchToTab(tabKey);\r\n};\r\n\r\nconst handleTabClose = (tabKey: string): void => {\r\n emit(\"tab-close\", tabKey);\r\n};\r\n\r\nconst handleTabAdd = (): void => {\r\n emit(\"tab-add\");\r\n};\r\n\r\nconst initializeCurrentTab = (): void => {\r\n if (!hasTabs.value || tabsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultTab } = tabsConfig.value;\r\n const targetTab = defaultTab || tabsWithItems.value[0]?.config.key;\r\n\r\n if (targetTab && targetTab !== currentTab.value) {\r\n currentTab.value = targetTab;\r\n nextTick(() => {\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (tabIndex >= 0) {\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n }\r\n });\r\n }\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n initializeCurrentTab();\r\n});\r\n\r\n/* 只监听标签结构变化(key / 数量),不监听表单项内容变化 */\r\nconst tabStructureKey = computed(() =>\r\n tabsConfig.value.tabs.map((t) => t.key).join(\",\"),\r\n);\r\n\r\nwatch(tabStructureKey, () => {\r\n if (\r\n currentTab.value &&\r\n !tabsConfig.value.tabs.some((tab) => tab.key === currentTab.value)\r\n ) {\r\n initializeCurrentTab();\r\n }\r\n});\r\n\r\n/* 监听配置变化 */\r\nwatch(\r\n () => tabsConfig.value.defaultTab,\r\n (newDefaultTab) => {\r\n if (newDefaultTab && newDefaultTab !== currentTab.value) {\r\n switchToTab(newDefaultTab);\r\n }\r\n },\r\n);\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n switchToTab,\r\n validateCurrentTab,\r\n currentTab: readonly(currentTab),\r\n totalTabs: computed(() => tabsWithItems.value.length),\r\n tabsWithItems: readonly(tabsWithItems),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 步骤表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-steps\">\r\n <!-- 无步骤配置时的单一面板模式 -->\r\n <div v-if=\"!hasSteps\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有步骤配置时的分步骤模式 -->\r\n <div v-else class=\"steps-container\">\r\n <!-- 步骤指示器 -->\r\n <NSteps\r\n :current=\"currentStep + 1\"\r\n :status=\"stepStatus\"\r\n :size=\"stepsConfig.size\"\r\n :vertical=\"stepsConfig.vertical\"\r\n class=\"steps-indicator\"\r\n >\r\n <NStep\r\n v-for=\"step in stepsWithItems\"\r\n :key=\"step.config.key\"\r\n :title=\"step.config.title\"\r\n :description=\"step.config.description\"\r\n :disabled=\"step.config.disabled\"\r\n />\r\n </NSteps>\r\n\r\n <!-- 步骤内容区域 -->\r\n <NCard class=\"steps-content\" :bordered=\"false\">\r\n <div\r\n v-for=\"(step, index) in stepsWithItems\"\r\n v-show=\"currentStep === index\"\r\n :key=\"step.config.key\"\r\n class=\"step-panel\"\r\n >\r\n <!-- 步骤标题和描述 -->\r\n <div v-if=\"stepsConfig.showStepHeader\" class=\"step-header\">\r\n <h3 class=\"step-title\">{{ step.config.title }}</h3>\r\n <p v-if=\"step.config.description\" class=\"step-description\">\r\n {{ step.config.description }}\r\n </p>\r\n </div>\r\n\r\n <!-- 步骤内的表单项 -->\r\n <div class=\"step-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in step.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 步骤操作按钮 -->\r\n <div class=\"steps-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NButton\r\n v-if=\"currentStep > 0\"\r\n :disabled=\"loading\"\r\n @click=\"handlePreviousStep\"\r\n >\r\n <C_Icon\r\n :name=\"'mdi:chevron-left-first'\"\r\n :size=\"16\"\r\n style=\"margin-right: 4px\"\r\n />\r\n {{ stepsConfig.prevButtonText }}\r\n </NButton>\r\n\r\n <div></div>\r\n\r\n <NSpace>\r\n <NButton\r\n v-if=\"currentStep < stepsWithItems.length - 1\"\r\n type=\"primary\"\r\n :loading=\"loading\"\r\n @click=\"handleNextStep\"\r\n >\r\n {{ stepsConfig.nextButtonText }}\r\n <C_Icon\r\n :name=\"'mdi:chevron-right-last'\"\r\n :size=\"16\"\r\n style=\"margin-left: 4px\"\r\n />\r\n </NButton>\r\n\r\n <slot\r\n name=\"step-actions\"\r\n :current-step=\"currentStep\"\r\n :total-steps=\"stepsWithItems.length\"\r\n :is-first-step=\"isFirstStep\"\r\n :is-last-step=\"isLastStep\"\r\n :next-step=\"handleNextStep\"\r\n :previous-step=\"handlePreviousStep\"\r\n :go-to-step=\"goToStep\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, onMounted, readonly } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { StepConfig, StepsLayoutConfig } from \"../../types\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface StepWithItems {\r\n config: StepConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n steps?: StepsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n step?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"step-change\": [stepIndex: number, stepKey: string];\r\n \"step-before-change\": [currentStep: number, targetStep: number];\r\n \"step-validate\": [stepIndex: number];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentStep = ref<number>(0);\r\nconst loading = ref<boolean>(false);\r\nconst stepValidationStatus = reactive<Record<number, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst stepsConfig = computed(() => {\r\n const config = props.layoutConfig?.steps || {};\r\n return {\r\n steps: config.steps || [],\r\n vertical: config.vertical || false,\r\n size: config.size || \"medium\",\r\n showStepHeader: config.showStepHeader !== false,\r\n validateBeforeNext: config.validateBeforeNext || false,\r\n prevButtonText: config.prevButtonText || \"上一步\",\r\n nextButtonText: config.nextButtonText || \"下一步\",\r\n defaultStep: config.defaultStep || 0,\r\n };\r\n});\r\n\r\nconst hasSteps = computed((): boolean => {\r\n return stepsConfig.value.steps.length > 0;\r\n});\r\n\r\nconst stepsWithItems = computed((): StepWithItems[] => {\r\n if (!hasSteps.value) return [];\r\n\r\n const stepMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化步骤映射 */\r\n stepsConfig.value.steps.forEach((step) => {\r\n stepMap.set(step.key, []);\r\n });\r\n\r\n /* 分配表单项到对应步骤 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const stepKey =\r\n option?.layout?.step || stepsConfig.value.steps[0]?.key || \"default\";\r\n\r\n if (!stepMap.has(stepKey)) {\r\n stepMap.set(stepKey, []);\r\n }\r\n stepMap.get(stepKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的步骤 */\r\n return stepsConfig.value.steps\r\n .map((stepConfig) => ({\r\n config: stepConfig,\r\n items: stepMap.get(stepConfig.key) || [],\r\n }))\r\n .filter((step) => step.items.length > 0);\r\n});\r\n\r\nconst stepStatus = computed(() => {\r\n for (let i = 0; i <= currentStep.value; i++) {\r\n if (stepValidationStatus[i] === false) {\r\n return \"error\";\r\n }\r\n }\r\n return \"process\";\r\n});\r\n\r\nconst isFirstStep = computed((): boolean => {\r\n return currentStep.value === 0;\r\n});\r\n\r\nconst isLastStep = computed((): boolean => {\r\n return currentStep.value === stepsWithItems.value.length - 1;\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `step-item-${index}`;\r\n};\r\n\r\nconst validateCurrentStep = async (): Promise<boolean> => {\r\n try {\r\n const result = await Promise.resolve(\r\n emit(\"step-validate\", currentStep.value) as unknown as\r\n | boolean\r\n | Promise<boolean>,\r\n );\r\n const valid = result !== false;\r\n stepValidationStatus[currentStep.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤验证失败:\", error);\r\n stepValidationStatus[currentStep.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToStep = async (\r\n targetStep: number,\r\n needValidation = false,\r\n): Promise<boolean> => {\r\n if (targetStep < 0 || targetStep >= stepsWithItems.value.length) {\r\n return false;\r\n }\r\n\r\n if (targetStep === currentStep.value) {\r\n return true;\r\n }\r\n\r\n try {\r\n loading.value = true;\r\n\r\n /* 验证步骤(如果需要) */\r\n if (needValidation && stepsConfig.value.validateBeforeNext) {\r\n const isValid = await validateCurrentStep();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发步骤切换前事件 */\r\n await emit(\"step-before-change\", currentStep.value, targetStep);\r\n\r\n currentStep.value = targetStep;\r\n emit(\r\n \"step-change\",\r\n currentStep.value,\r\n stepsWithItems.value[currentStep.value].config.key,\r\n );\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤切换失败:\", error);\r\n return false;\r\n } finally {\r\n loading.value = false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleNextStep = async (): Promise<void> => {\r\n await switchToStep(currentStep.value + 1, true);\r\n};\r\n\r\nconst handlePreviousStep = (): void => {\r\n switchToStep(currentStep.value - 1);\r\n};\r\n\r\nconst goToStep = async (stepIndex: number): Promise<void> => {\r\n if (stepsWithItems.value[stepIndex]?.config.disabled) {\r\n return;\r\n }\r\n\r\n const needValidation = stepIndex > currentStep.value;\r\n await switchToStep(stepIndex, needValidation);\r\n};\r\n\r\nconst initializeCurrentStep = (): void => {\r\n if (!hasSteps.value || stepsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultStep } = stepsConfig.value;\r\n const isValidDefaultStep =\r\n defaultStep >= 0 &&\r\n defaultStep < stepsWithItems.value.length &&\r\n !stepsWithItems.value[defaultStep]?.config.disabled;\r\n\r\n currentStep.value = isValidDefaultStep ? defaultStep : 0;\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\n\r\n/* 只监听步骤结构变化(key / 数量),不监听表单项内容变化 */\r\nconst stepStructureKey = computed(() =>\r\n stepsConfig.value.steps.map((s) => s.key).join(\",\"),\r\n);\r\n\r\nwatch(stepStructureKey, () => {\r\n initializeCurrentStep();\r\n});\r\n\r\nonMounted(() => {\r\n initializeCurrentStep();\r\n});\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n nextStep: handleNextStep,\r\n previousStep: handlePreviousStep,\r\n goToStep,\r\n validateCurrentStep,\r\n currentStep: readonly(currentStep),\r\n totalSteps: computed(() => stepsWithItems.value.length),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 步骤表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-steps\">\r\n <!-- 无步骤配置时的单一面板模式 -->\r\n <div v-if=\"!hasSteps\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有步骤配置时的分步骤模式 -->\r\n <div v-else class=\"steps-container\">\r\n <!-- 步骤指示器 -->\r\n <NSteps\r\n :current=\"currentStep + 1\"\r\n :status=\"stepStatus\"\r\n :size=\"stepsConfig.size\"\r\n :vertical=\"stepsConfig.vertical\"\r\n class=\"steps-indicator\"\r\n >\r\n <NStep\r\n v-for=\"step in stepsWithItems\"\r\n :key=\"step.config.key\"\r\n :title=\"step.config.title\"\r\n :description=\"step.config.description\"\r\n :disabled=\"step.config.disabled\"\r\n />\r\n </NSteps>\r\n\r\n <!-- 步骤内容区域 -->\r\n <NCard class=\"steps-content\" :bordered=\"false\">\r\n <div\r\n v-for=\"(step, index) in stepsWithItems\"\r\n v-show=\"currentStep === index\"\r\n :key=\"step.config.key\"\r\n class=\"step-panel\"\r\n >\r\n <!-- 步骤标题和描述 -->\r\n <div v-if=\"stepsConfig.showStepHeader\" class=\"step-header\">\r\n <h3 class=\"step-title\">{{ step.config.title }}</h3>\r\n <p v-if=\"step.config.description\" class=\"step-description\">\r\n {{ step.config.description }}\r\n </p>\r\n </div>\r\n\r\n <!-- 步骤内的表单项 -->\r\n <div class=\"step-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in step.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 步骤操作按钮 -->\r\n <div class=\"steps-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NButton\r\n v-if=\"currentStep > 0\"\r\n :disabled=\"loading\"\r\n @click=\"handlePreviousStep\"\r\n >\r\n <C_Icon\r\n :name=\"'mdi:chevron-left-first'\"\r\n :size=\"16\"\r\n style=\"margin-right: 4px\"\r\n />\r\n {{ stepsConfig.prevButtonText }}\r\n </NButton>\r\n\r\n <div></div>\r\n\r\n <NSpace>\r\n <NButton\r\n v-if=\"currentStep < stepsWithItems.length - 1\"\r\n type=\"primary\"\r\n :loading=\"loading\"\r\n @click=\"handleNextStep\"\r\n >\r\n {{ stepsConfig.nextButtonText }}\r\n <C_Icon\r\n :name=\"'mdi:chevron-right-last'\"\r\n :size=\"16\"\r\n style=\"margin-left: 4px\"\r\n />\r\n </NButton>\r\n\r\n <slot\r\n name=\"step-actions\"\r\n :current-step=\"currentStep\"\r\n :total-steps=\"stepsWithItems.length\"\r\n :is-first-step=\"isFirstStep\"\r\n :is-last-step=\"isLastStep\"\r\n :next-step=\"handleNextStep\"\r\n :previous-step=\"handlePreviousStep\"\r\n :go-to-step=\"goToStep\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, onMounted, readonly } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { StepConfig, StepsLayoutConfig } from \"../../types\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface StepWithItems {\r\n config: StepConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n steps?: StepsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n step?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"step-change\": [stepIndex: number, stepKey: string];\r\n \"step-before-change\": [currentStep: number, targetStep: number];\r\n \"step-validate\": [stepIndex: number];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentStep = ref<number>(0);\r\nconst loading = ref<boolean>(false);\r\nconst stepValidationStatus = reactive<Record<number, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst stepsConfig = computed(() => {\r\n const config = props.layoutConfig?.steps || {};\r\n return {\r\n steps: config.steps || [],\r\n vertical: config.vertical || false,\r\n size: config.size || \"medium\",\r\n showStepHeader: config.showStepHeader !== false,\r\n validateBeforeNext: config.validateBeforeNext || false,\r\n prevButtonText: config.prevButtonText || \"上一步\",\r\n nextButtonText: config.nextButtonText || \"下一步\",\r\n defaultStep: config.defaultStep || 0,\r\n };\r\n});\r\n\r\nconst hasSteps = computed((): boolean => {\r\n return stepsConfig.value.steps.length > 0;\r\n});\r\n\r\nconst stepsWithItems = computed((): StepWithItems[] => {\r\n if (!hasSteps.value) return [];\r\n\r\n const stepMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化步骤映射 */\r\n stepsConfig.value.steps.forEach((step) => {\r\n stepMap.set(step.key, []);\r\n });\r\n\r\n /* 分配表单项到对应步骤 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const stepKey =\r\n option?.layout?.step || stepsConfig.value.steps[0]?.key || \"default\";\r\n\r\n if (!stepMap.has(stepKey)) {\r\n stepMap.set(stepKey, []);\r\n }\r\n stepMap.get(stepKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的步骤 */\r\n return stepsConfig.value.steps\r\n .map((stepConfig) => ({\r\n config: stepConfig,\r\n items: stepMap.get(stepConfig.key) || [],\r\n }))\r\n .filter((step) => step.items.length > 0);\r\n});\r\n\r\nconst stepStatus = computed(() => {\r\n for (let i = 0; i <= currentStep.value; i++) {\r\n if (stepValidationStatus[i] === false) {\r\n return \"error\";\r\n }\r\n }\r\n return \"process\";\r\n});\r\n\r\nconst isFirstStep = computed((): boolean => {\r\n return currentStep.value === 0;\r\n});\r\n\r\nconst isLastStep = computed((): boolean => {\r\n return currentStep.value === stepsWithItems.value.length - 1;\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `step-item-${index}`;\r\n};\r\n\r\nconst validateCurrentStep = async (): Promise<boolean> => {\r\n try {\r\n const result = await Promise.resolve(\r\n emit(\"step-validate\", currentStep.value) as unknown as\r\n | boolean\r\n | Promise<boolean>,\r\n );\r\n const valid = result !== false;\r\n stepValidationStatus[currentStep.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤验证失败:\", error);\r\n stepValidationStatus[currentStep.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToStep = async (\r\n targetStep: number,\r\n needValidation = false,\r\n): Promise<boolean> => {\r\n if (targetStep < 0 || targetStep >= stepsWithItems.value.length) {\r\n return false;\r\n }\r\n\r\n if (targetStep === currentStep.value) {\r\n return true;\r\n }\r\n\r\n try {\r\n loading.value = true;\r\n\r\n /* 验证步骤(如果需要) */\r\n if (needValidation && stepsConfig.value.validateBeforeNext) {\r\n const isValid = await validateCurrentStep();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发步骤切换前事件 */\r\n await emit(\"step-before-change\", currentStep.value, targetStep);\r\n\r\n currentStep.value = targetStep;\r\n emit(\r\n \"step-change\",\r\n currentStep.value,\r\n stepsWithItems.value[currentStep.value].config.key,\r\n );\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤切换失败:\", error);\r\n return false;\r\n } finally {\r\n loading.value = false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleNextStep = async (): Promise<void> => {\r\n await switchToStep(currentStep.value + 1, true);\r\n};\r\n\r\nconst handlePreviousStep = (): void => {\r\n switchToStep(currentStep.value - 1);\r\n};\r\n\r\nconst goToStep = async (stepIndex: number): Promise<void> => {\r\n if (stepsWithItems.value[stepIndex]?.config.disabled) {\r\n return;\r\n }\r\n\r\n const needValidation = stepIndex > currentStep.value;\r\n await switchToStep(stepIndex, needValidation);\r\n};\r\n\r\nconst initializeCurrentStep = (): void => {\r\n if (!hasSteps.value || stepsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultStep } = stepsConfig.value;\r\n const isValidDefaultStep =\r\n defaultStep >= 0 &&\r\n defaultStep < stepsWithItems.value.length &&\r\n !stepsWithItems.value[defaultStep]?.config.disabled;\r\n\r\n currentStep.value = isValidDefaultStep ? defaultStep : 0;\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\n\r\n/* 只监听步骤结构变化(key / 数量),不监听表单项内容变化 */\r\nconst stepStructureKey = computed(() =>\r\n stepsConfig.value.steps.map((s) => s.key).join(\",\"),\r\n);\r\n\r\nwatch(stepStructureKey, () => {\r\n initializeCurrentStep();\r\n});\r\n\r\nonMounted(() => {\r\n initializeCurrentStep();\r\n});\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n nextStep: handleNextStep,\r\n previousStep: handlePreviousStep,\r\n goToStep,\r\n validateCurrentStep,\r\n currentStep: readonly(currentStep),\r\n totalSteps: computed(() => stepsWithItems.value.length),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 步骤表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-steps\">\r\n <!-- 无步骤配置时的单一面板模式 -->\r\n <div v-if=\"!hasSteps\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有步骤配置时的分步骤模式 -->\r\n <div v-else class=\"steps-container\">\r\n <!-- 步骤指示器 -->\r\n <NSteps\r\n :current=\"currentStep + 1\"\r\n :status=\"stepStatus\"\r\n :size=\"stepsConfig.size\"\r\n :vertical=\"stepsConfig.vertical\"\r\n class=\"steps-indicator\"\r\n >\r\n <NStep\r\n v-for=\"step in stepsWithItems\"\r\n :key=\"step.config.key\"\r\n :title=\"step.config.title\"\r\n :description=\"step.config.description\"\r\n :disabled=\"step.config.disabled\"\r\n />\r\n </NSteps>\r\n\r\n <!-- 步骤内容区域 -->\r\n <NCard class=\"steps-content\" :bordered=\"false\">\r\n <div\r\n v-for=\"(step, index) in stepsWithItems\"\r\n v-show=\"currentStep === index\"\r\n :key=\"step.config.key\"\r\n class=\"step-panel\"\r\n >\r\n <!-- 步骤标题和描述 -->\r\n <div v-if=\"stepsConfig.showStepHeader\" class=\"step-header\">\r\n <h3 class=\"step-title\">{{ step.config.title }}</h3>\r\n <p v-if=\"step.config.description\" class=\"step-description\">\r\n {{ step.config.description }}\r\n </p>\r\n </div>\r\n\r\n <!-- 步骤内的表单项 -->\r\n <div class=\"step-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in step.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 步骤操作按钮 -->\r\n <div class=\"steps-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NButton\r\n v-if=\"currentStep > 0\"\r\n :disabled=\"loading\"\r\n @click=\"handlePreviousStep\"\r\n >\r\n <C_Icon\r\n :name=\"'mdi:chevron-left-first'\"\r\n :size=\"16\"\r\n style=\"margin-right: 4px\"\r\n />\r\n {{ stepsConfig.prevButtonText }}\r\n </NButton>\r\n\r\n <div></div>\r\n\r\n <NSpace>\r\n <NButton\r\n v-if=\"currentStep < stepsWithItems.length - 1\"\r\n type=\"primary\"\r\n :loading=\"loading\"\r\n @click=\"handleNextStep\"\r\n >\r\n {{ stepsConfig.nextButtonText }}\r\n <C_Icon\r\n :name=\"'mdi:chevron-right-last'\"\r\n :size=\"16\"\r\n style=\"margin-left: 4px\"\r\n />\r\n </NButton>\r\n\r\n <slot\r\n name=\"step-actions\"\r\n :current-step=\"currentStep\"\r\n :total-steps=\"stepsWithItems.length\"\r\n :is-first-step=\"isFirstStep\"\r\n :is-last-step=\"isLastStep\"\r\n :next-step=\"handleNextStep\"\r\n :previous-step=\"handlePreviousStep\"\r\n :go-to-step=\"goToStep\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, onMounted, readonly } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { StepConfig, StepsLayoutConfig } from \"../../types\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface StepWithItems {\r\n config: StepConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n steps?: StepsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n step?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"step-change\": [stepIndex: number, stepKey: string];\r\n \"step-before-change\": [currentStep: number, targetStep: number];\r\n \"step-validate\": [stepIndex: number];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentStep = ref<number>(0);\r\nconst loading = ref<boolean>(false);\r\nconst stepValidationStatus = reactive<Record<number, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst stepsConfig = computed(() => {\r\n const config = props.layoutConfig?.steps || {};\r\n return {\r\n steps: config.steps || [],\r\n vertical: config.vertical || false,\r\n size: config.size || \"medium\",\r\n showStepHeader: config.showStepHeader !== false,\r\n validateBeforeNext: config.validateBeforeNext || false,\r\n prevButtonText: config.prevButtonText || \"上一步\",\r\n nextButtonText: config.nextButtonText || \"下一步\",\r\n defaultStep: config.defaultStep || 0,\r\n };\r\n});\r\n\r\nconst hasSteps = computed((): boolean => {\r\n return stepsConfig.value.steps.length > 0;\r\n});\r\n\r\nconst stepsWithItems = computed((): StepWithItems[] => {\r\n if (!hasSteps.value) return [];\r\n\r\n const stepMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化步骤映射 */\r\n stepsConfig.value.steps.forEach((step) => {\r\n stepMap.set(step.key, []);\r\n });\r\n\r\n /* 分配表单项到对应步骤 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const stepKey =\r\n option?.layout?.step || stepsConfig.value.steps[0]?.key || \"default\";\r\n\r\n if (!stepMap.has(stepKey)) {\r\n stepMap.set(stepKey, []);\r\n }\r\n stepMap.get(stepKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的步骤 */\r\n return stepsConfig.value.steps\r\n .map((stepConfig) => ({\r\n config: stepConfig,\r\n items: stepMap.get(stepConfig.key) || [],\r\n }))\r\n .filter((step) => step.items.length > 0);\r\n});\r\n\r\nconst stepStatus = computed(() => {\r\n for (let i = 0; i <= currentStep.value; i++) {\r\n if (stepValidationStatus[i] === false) {\r\n return \"error\";\r\n }\r\n }\r\n return \"process\";\r\n});\r\n\r\nconst isFirstStep = computed((): boolean => {\r\n return currentStep.value === 0;\r\n});\r\n\r\nconst isLastStep = computed((): boolean => {\r\n return currentStep.value === stepsWithItems.value.length - 1;\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `step-item-${index}`;\r\n};\r\n\r\nconst validateCurrentStep = async (): Promise<boolean> => {\r\n try {\r\n const result = await Promise.resolve(\r\n emit(\"step-validate\", currentStep.value) as unknown as\r\n | boolean\r\n | Promise<boolean>,\r\n );\r\n const valid = result !== false;\r\n stepValidationStatus[currentStep.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤验证失败:\", error);\r\n stepValidationStatus[currentStep.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToStep = async (\r\n targetStep: number,\r\n needValidation = false,\r\n): Promise<boolean> => {\r\n if (targetStep < 0 || targetStep >= stepsWithItems.value.length) {\r\n return false;\r\n }\r\n\r\n if (targetStep === currentStep.value) {\r\n return true;\r\n }\r\n\r\n try {\r\n loading.value = true;\r\n\r\n /* 验证步骤(如果需要) */\r\n if (needValidation && stepsConfig.value.validateBeforeNext) {\r\n const isValid = await validateCurrentStep();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发步骤切换前事件 */\r\n await emit(\"step-before-change\", currentStep.value, targetStep);\r\n\r\n currentStep.value = targetStep;\r\n emit(\r\n \"step-change\",\r\n currentStep.value,\r\n stepsWithItems.value[currentStep.value].config.key,\r\n );\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤切换失败:\", error);\r\n return false;\r\n } finally {\r\n loading.value = false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleNextStep = async (): Promise<void> => {\r\n await switchToStep(currentStep.value + 1, true);\r\n};\r\n\r\nconst handlePreviousStep = (): void => {\r\n switchToStep(currentStep.value - 1);\r\n};\r\n\r\nconst goToStep = async (stepIndex: number): Promise<void> => {\r\n if (stepsWithItems.value[stepIndex]?.config.disabled) {\r\n return;\r\n }\r\n\r\n const needValidation = stepIndex > currentStep.value;\r\n await switchToStep(stepIndex, needValidation);\r\n};\r\n\r\nconst initializeCurrentStep = (): void => {\r\n if (!hasSteps.value || stepsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultStep } = stepsConfig.value;\r\n const isValidDefaultStep =\r\n defaultStep >= 0 &&\r\n defaultStep < stepsWithItems.value.length &&\r\n !stepsWithItems.value[defaultStep]?.config.disabled;\r\n\r\n currentStep.value = isValidDefaultStep ? defaultStep : 0;\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\n\r\n/* 只监听步骤结构变化(key / 数量),不监听表单项内容变化 */\r\nconst stepStructureKey = computed(() =>\r\n stepsConfig.value.steps.map((s) => s.key).join(\",\"),\r\n);\r\n\r\nwatch(stepStructureKey, () => {\r\n initializeCurrentStep();\r\n});\r\n\r\nonMounted(() => {\r\n initializeCurrentStep();\r\n});\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n nextStep: handleNextStep,\r\n previousStep: handlePreviousStep,\r\n goToStep,\r\n validateCurrentStep,\r\n currentStep: readonly(currentStep),\r\n totalSteps: computed(() => stepsWithItems.value.length),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","/**\r\n * @description 动态表单状态管理组合式函数\r\n * @module useDynamicFormState\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * @returns 包含动态表单状态和操作方法的对象\r\n */\r\n\r\nimport { reactive, computed, readonly } from 'vue'\r\nimport type {\r\n FormOption,\r\n ComponentType,\r\n DynamicFieldConfig,\r\n DynamicFormConfig,\r\n DynamicFormState,\r\n} from '../types'\r\n\r\n/**\r\n * @description 默认表单配置\r\n */\r\nconst DEFAULT_CONFIG: DynamicFormConfig = {\r\n maxFields: 20,\r\n autoSave: false,\r\n enableSort: true,\r\n showControls: true,\r\n showItemControls: true,\r\n}\r\n\r\n/**\r\n * @description 可用的字段类型选项\r\n */\r\nexport const FIELD_TYPE_OPTIONS = [\r\n { label: '文本输入', value: 'input' as ComponentType },\r\n { label: '数字输入', value: 'inputNumber' as ComponentType },\r\n { label: '多行文本', value: 'textarea' as ComponentType },\r\n { label: '下拉选择', value: 'select' as ComponentType },\r\n { label: '开关切换', value: 'switch' as ComponentType },\r\n { label: '评分组件', value: 'rate' as ComponentType },\r\n]\r\n\r\n/**\r\n * @description 创建和管理动态表单状态\r\n * @returns 包含状态和方法的对象\r\n */\r\nexport const useDynamicFormState = () => {\r\n /**\r\n * @description 响应式表单状态\r\n */\r\n const state = reactive<DynamicFormState>({\r\n config: { ...DEFAULT_CONFIG },\r\n baseFields: [],\r\n dynamicFields: [],\r\n hiddenFieldIds: new Set<string>(),\r\n fieldCounter: 0,\r\n isInitialized: false,\r\n })\r\n\r\n /**\r\n * @description 计算所有字段(基础字段+动态字段)\r\n */\r\n const allFields = computed<FormOption[]>(() => [\r\n ...state.baseFields,\r\n ...state.dynamicFields.map(field => ({\r\n ...field,\r\n show: !state.hiddenFieldIds.has(field.prop),\r\n })),\r\n ])\r\n\r\n /**\r\n * @description 计算可见字段\r\n */\r\n const visibleFields = computed<FormOption[]>(() =>\r\n allFields.value.filter(field => field.show !== false)\r\n )\r\n\r\n /**\r\n * @description 计算动态字段数量\r\n */\r\n const dynamicFieldsCount = computed(() => state.dynamicFields.length)\r\n\r\n /**\r\n * @description 计算隐藏字段数量\r\n */\r\n const hiddenFieldsCount = computed(() => state.hiddenFieldIds.size)\r\n\r\n /**\r\n * @description 是否可以添加更多字段\r\n */\r\n const canAddMoreFields = computed(\r\n () => state.dynamicFields.length < state.config.maxFields\r\n )\r\n\r\n /**\r\n * @description 是否所有字段都可见\r\n */\r\n const allVisible = computed(() => state.hiddenFieldIds.size === 0)\r\n\r\n /**\r\n * @description 添加动态字段\r\n * @param config - 字段配置\r\n */\r\n const addField = (config: Partial<DynamicFieldConfig> = {}) => {\r\n if (!canAddMoreFields.value) {\r\n console.warn('[useDynamicFormState] 已达到最大字段数量限制')\r\n return\r\n }\r\n\r\n state.fieldCounter++\r\n\r\n const defaultType =\r\n config.type ||\r\n FIELD_TYPE_OPTIONS[Math.floor(Math.random() * FIELD_TYPE_OPTIONS.length)]\r\n .value\r\n\r\n const newField: DynamicFieldConfig = {\r\n id: `dynamic_field_${state.fieldCounter}`,\r\n type: defaultType,\r\n prop: config.prop || `dynamic_${state.fieldCounter}`,\r\n label: config.label || `动态字段 ${state.fieldCounter}`,\r\n placeholder: config.placeholder || `请输入${config.label || '内容'}`,\r\n visible: true,\r\n removable: true,\r\n created: Date.now(),\r\n layout: config.layout || { span: 12 },\r\n ...config,\r\n }\r\n\r\n state.dynamicFields.push(newField)\r\n\r\n console.log('[useDynamicFormState] 添加字段:', newField)\r\n }\r\n\r\n /**\r\n * @description 移除动态字段\r\n * @param index - 可选,要移除的字段索引,默认移除最后一个\r\n */\r\n const removeField = (index?: number) => {\r\n if (state.dynamicFields.length === 0) {\r\n console.warn('[useDynamicFormState] 没有可移除的动态字段')\r\n return\r\n }\r\n\r\n const targetIndex = index ?? state.dynamicFields.length - 1\r\n\r\n if (targetIndex < 0 || targetIndex >= state.dynamicFields.length) {\r\n console.warn('[useDynamicFormState] 字段索引超出范围')\r\n return\r\n }\r\n\r\n const removed = state.dynamicFields.splice(targetIndex, 1)[0]\r\n if (removed) {\r\n state.hiddenFieldIds.delete(removed.prop)\r\n console.log('[useDynamicFormState] 移除字段:', removed.prop)\r\n }\r\n }\r\n\r\n /**\r\n * @description 清空所有动态字段\r\n */\r\n const clearDynamicFields = () => {\r\n console.log(\r\n '[useDynamicFormState] 清空动态字段:',\r\n state.dynamicFields.length\r\n )\r\n state.dynamicFields.forEach(field =>\r\n state.hiddenFieldIds.delete(field.prop)\r\n )\r\n state.dynamicFields.length = 0\r\n state.fieldCounter = 0\r\n }\r\n\r\n /**\r\n * @description 切换字段可见性\r\n * @param fieldId - 字段ID\r\n */\r\n const toggleFieldVisibility = (fieldId: string) => {\r\n if (state.hiddenFieldIds.has(fieldId)) {\r\n state.hiddenFieldIds.delete(fieldId)\r\n console.log('[useDynamicFormState] 显示字段:', fieldId)\r\n } else {\r\n state.hiddenFieldIds.add(fieldId)\r\n console.log('[useDynamicFormState] 隐藏字段:', fieldId)\r\n }\r\n }\r\n\r\n /**\r\n * @description 切换所有字段可见性\r\n */\r\n const toggleAllVisibility = () => {\r\n if (allVisible.value) {\r\n state.dynamicFields.forEach(field => {\r\n state.hiddenFieldIds.add(field.prop)\r\n })\r\n console.log('[useDynamicFormState] 隐藏所有动态字段')\r\n } else {\r\n state.hiddenFieldIds.clear()\r\n console.log('[useDynamicFormState] 显示所有字段')\r\n }\r\n }\r\n\r\n /**\r\n * @description 更新表单配置\r\n * @param newConfig - 新的配置对象\r\n */\r\n const updateConfig = (newConfig: Partial<DynamicFormConfig>) => {\r\n Object.assign(state.config, newConfig)\r\n console.log('[useDynamicFormState] 更新配置:', newConfig)\r\n }\r\n\r\n /**\r\n * @description 导出当前表单配置\r\n * @returns JSON格式的配置字符串\r\n */\r\n const exportConfig = () => {\r\n const config = {\r\n baseFields: state.baseFields,\r\n dynamicFields: state.dynamicFields,\r\n config: state.config,\r\n hiddenFields: Array.from(state.hiddenFieldIds),\r\n timestamp: Date.now(),\r\n }\r\n return JSON.stringify(config, null, 2)\r\n }\r\n\r\n /**\r\n * @description 初始化表单状态\r\n * @param baseFields - 基础字段配置\r\n * @param config - 可选,表单配置\r\n */\r\n const initialize = (\r\n baseFields: FormOption[],\r\n config: Partial<DynamicFormConfig> = {}\r\n ) => {\r\n console.log('[useDynamicFormState] 初始化状态:', {\r\n baseFieldsCount: baseFields.length,\r\n config,\r\n })\r\n\r\n state.baseFields = [...baseFields]\r\n Object.assign(state.config, config)\r\n state.isInitialized = true\r\n\r\n console.log('[useDynamicFormState] 初始化完成')\r\n }\r\n\r\n return {\r\n state: readonly(state),\r\n allFields,\r\n visibleFields,\r\n dynamicFieldsCount,\r\n hiddenFieldsCount,\r\n canAddMoreFields,\r\n allVisible,\r\n FIELD_TYPE_OPTIONS,\r\n addField,\r\n removeField,\r\n clearDynamicFields,\r\n toggleFieldVisibility,\r\n toggleAllVisibility,\r\n updateConfig,\r\n exportConfig,\r\n initialize,\r\n }\r\n}\r\n\r\n/**\r\n * @description 动态表单状态类型\r\n */\r\nexport type DynamicFormStateType = ReturnType<typeof useDynamicFormState>\r\n\r\n/**\r\n * @description 动态表单状态注入键\r\n */\r\nexport const DYNAMIC_FORM_STATE_KEY = Symbol('dynamicFormState')\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 动态表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-dynamic\">\r\n <!-- 动态控制面板 -->\r\n <div\r\n v-if=\"showControls && isDynamicStateAvailable\"\r\n class=\"dynamic-controls\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制\" :bordered=\"false\">\r\n <template #header-extra>\r\n <NBadge :value=\"dynamicFieldsCount\" type=\"warning\">\r\n <C_Icon :name=\"'mdi:layers'\" :size=\"16\" />\r\n </NBadge>\r\n </template>\r\n\r\n <NSpace justify=\"space-between\" align=\"center\">\r\n <NSpace>\r\n <NButton\r\n size=\"small\"\r\n type=\"primary\"\r\n :disabled=\"!canAddMoreFields\"\r\n @click=\"dynamicState!.addField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:plus'\" :size=\"14\" />\r\n </template>\r\n 添加字段 ({{ dynamicFieldsCount }}/{{ maxFields }})\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"warning\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.removeField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:minus'\" :size=\"14\" />\r\n </template>\r\n 移除字段\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"error\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.clearDynamicFields()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:delete-sweep'\" :size=\"14\" />\r\n </template>\r\n 清空动态\r\n </NButton>\r\n </NSpace>\r\n\r\n <!-- 最大字段数配置 -->\r\n <div class=\"max-fields-config\">\r\n <span>最大字段数:</span>\r\n <NInputNumber\r\n :value=\"maxFields\"\r\n @update:value=\"\r\n (v: any) => v && dynamicState!.updateConfig({ maxFields: v })\r\n \"\r\n :min=\"5\"\r\n :max=\"50\"\r\n size=\"small\"\r\n style=\"width: 100px\"\r\n />\r\n </div>\r\n </NSpace>\r\n\r\n <!-- 状态信息 -->\r\n <div class=\"dynamic-stats\">\r\n <NSpace>\r\n <NTag type=\"info\">总字段: {{ totalFieldsCount }}</NTag>\r\n <NTag type=\"warning\">动态: {{ dynamicFieldsCount }}</NTag>\r\n </NSpace>\r\n </div>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 状态不可用时的提示 -->\r\n <div\r\n v-else-if=\"showControls && !isDynamicStateAvailable\"\r\n class=\"dynamic-controls-fallback\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制 (静态模式)\" :bordered=\"false\">\r\n <NAlert type=\"info\" show-icon>\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n </template>\r\n 当前为静态模式,如需动态功能请在父组件中提供动态表单状态。\r\n </NAlert>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 表单项渲染区域 -->\r\n <div class=\"dynamic-form-items\">\r\n <NGrid :cols=\"gridCols\" :x-gap=\"gridGutter\" :y-gap=\"gridGutter\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :span=\"getItemSpan(index)\"\r\n >\r\n <div\r\n class=\"dynamic-item-wrapper\"\r\n :class=\"{ 'is-dynamic-field': isDynamicField(item) }\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { DynamicFieldConfig, LayoutConfig, FormOption } from \"../../types\";\r\nimport {\r\n DYNAMIC_FORM_STATE_KEY,\r\n type DynamicFormStateType,\r\n} from \"../../composables/useDynamicFormState\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 接口定义 ================= */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: LayoutConfig;\r\n options?: FormOption[];\r\n dynamicFormState?: DynamicFormStateType | null;\r\n}\r\n\r\n/* ================= 组件配置 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n dynamicFormState: null,\r\n});\r\n\r\ndefineEmits<{\r\n \"field-add\": [fieldConfig: DynamicFieldConfig];\r\n \"field-remove\": [fieldId: string];\r\n \"fields-clear\": [];\r\n}>();\r\n\r\n/* ================= 状态管理 ================= */\r\nconst injectedDynamicState = inject<DynamicFormStateType | null>(\r\n DYNAMIC_FORM_STATE_KEY,\r\n null,\r\n);\r\n\r\nconst dynamicState = computed(\r\n () => props.dynamicFormState || injectedDynamicState,\r\n);\r\nconst isDynamicStateAvailable = computed(() => !!dynamicState.value);\r\n\r\n/* ================= 计算属性 ================= */\r\nconst gridCols = computed(() => props.layoutConfig?.dynamic?.grid?.cols ?? 24);\r\nconst gridGutter = computed(\r\n () => props.layoutConfig?.dynamic?.grid?.gutter ?? 16,\r\n);\r\nconst showControls = computed(\r\n () => props.layoutConfig?.dynamic?.controls?.showControls !== false,\r\n);\r\n\r\nconst maxFields = computed(() => {\r\n return (\r\n dynamicState.value?.state.config.maxFields ??\r\n props.layoutConfig?.dynamic?.dynamic?.maxFields ??\r\n 50\r\n );\r\n});\r\n\r\nconst dynamicFieldsCount = computed(\r\n () => dynamicState.value?.dynamicFieldsCount.value ?? 0,\r\n);\r\nconst canAddMoreFields = computed(\r\n () => dynamicState.value?.canAddMoreFields.value ?? false,\r\n);\r\nconst totalFieldsCount = computed(() => props.formItems.length);\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n return (\r\n item.key?.toString() || (item.props as any)?.path || `dynamic-item-${index}`\r\n );\r\n};\r\n\r\nconst getItemSpan = (index: number): number => {\r\n const span = props.options?.[index]?.layout?.span;\r\n return typeof span === \"number\" && span > 0 && span <= gridCols.value\r\n ? span\r\n : Math.min(12, gridCols.value);\r\n};\r\n\r\nconst isDynamicField = (item: VNode): boolean => {\r\n if (!dynamicState.value) return false;\r\n const fieldId = (item.props as any)?.path || item.key?.toString() || \"\";\r\n return dynamicState.value.state.dynamicFields.some(\r\n (field: any) => field.prop === fieldId,\r\n );\r\n};\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n addField: () => dynamicState.value?.addField(),\r\n removeField: () => dynamicState.value?.removeField(),\r\n clearAllDynamic: () => dynamicState.value?.clearDynamicFields(),\r\n isDynamicStateAvailable,\r\n dynamicState,\r\n});\r\n\r\n/* ================= 开发环境验证 ================= */\r\nif (typeof import.meta !== \"undefined\" && import.meta.env?.DEV) {\r\n watchEffect(() => {\r\n const { options, formItems } = props;\r\n if (options && options.length !== formItems.length) {\r\n console.warn(\r\n `[Dynamic Layout] 配置项数量(${options.length})与表单项数量(${formItems.length})不匹配`,\r\n );\r\n }\r\n\r\n console.log(\r\n \"[Dynamic Layout]\",\r\n isDynamicStateAvailable.value ? \"动态模式已启用\" : \"运行在静态模式\",\r\n {\r\n totalFields: totalFieldsCount.value,\r\n dynamicFields: dynamicFieldsCount.value,\r\n stateSource: props.dynamicFormState ? \"props透传\" : \"inject注入\",\r\n },\r\n );\r\n });\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 动态表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-dynamic\">\r\n <!-- 动态控制面板 -->\r\n <div\r\n v-if=\"showControls && isDynamicStateAvailable\"\r\n class=\"dynamic-controls\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制\" :bordered=\"false\">\r\n <template #header-extra>\r\n <NBadge :value=\"dynamicFieldsCount\" type=\"warning\">\r\n <C_Icon :name=\"'mdi:layers'\" :size=\"16\" />\r\n </NBadge>\r\n </template>\r\n\r\n <NSpace justify=\"space-between\" align=\"center\">\r\n <NSpace>\r\n <NButton\r\n size=\"small\"\r\n type=\"primary\"\r\n :disabled=\"!canAddMoreFields\"\r\n @click=\"dynamicState!.addField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:plus'\" :size=\"14\" />\r\n </template>\r\n 添加字段 ({{ dynamicFieldsCount }}/{{ maxFields }})\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"warning\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.removeField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:minus'\" :size=\"14\" />\r\n </template>\r\n 移除字段\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"error\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.clearDynamicFields()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:delete-sweep'\" :size=\"14\" />\r\n </template>\r\n 清空动态\r\n </NButton>\r\n </NSpace>\r\n\r\n <!-- 最大字段数配置 -->\r\n <div class=\"max-fields-config\">\r\n <span>最大字段数:</span>\r\n <NInputNumber\r\n :value=\"maxFields\"\r\n @update:value=\"\r\n (v: any) => v && dynamicState!.updateConfig({ maxFields: v })\r\n \"\r\n :min=\"5\"\r\n :max=\"50\"\r\n size=\"small\"\r\n style=\"width: 100px\"\r\n />\r\n </div>\r\n </NSpace>\r\n\r\n <!-- 状态信息 -->\r\n <div class=\"dynamic-stats\">\r\n <NSpace>\r\n <NTag type=\"info\">总字段: {{ totalFieldsCount }}</NTag>\r\n <NTag type=\"warning\">动态: {{ dynamicFieldsCount }}</NTag>\r\n </NSpace>\r\n </div>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 状态不可用时的提示 -->\r\n <div\r\n v-else-if=\"showControls && !isDynamicStateAvailable\"\r\n class=\"dynamic-controls-fallback\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制 (静态模式)\" :bordered=\"false\">\r\n <NAlert type=\"info\" show-icon>\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n </template>\r\n 当前为静态模式,如需动态功能请在父组件中提供动态表单状态。\r\n </NAlert>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 表单项渲染区域 -->\r\n <div class=\"dynamic-form-items\">\r\n <NGrid :cols=\"gridCols\" :x-gap=\"gridGutter\" :y-gap=\"gridGutter\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :span=\"getItemSpan(index)\"\r\n >\r\n <div\r\n class=\"dynamic-item-wrapper\"\r\n :class=\"{ 'is-dynamic-field': isDynamicField(item) }\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { DynamicFieldConfig, LayoutConfig, FormOption } from \"../../types\";\r\nimport {\r\n DYNAMIC_FORM_STATE_KEY,\r\n type DynamicFormStateType,\r\n} from \"../../composables/useDynamicFormState\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 接口定义 ================= */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: LayoutConfig;\r\n options?: FormOption[];\r\n dynamicFormState?: DynamicFormStateType | null;\r\n}\r\n\r\n/* ================= 组件配置 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n dynamicFormState: null,\r\n});\r\n\r\ndefineEmits<{\r\n \"field-add\": [fieldConfig: DynamicFieldConfig];\r\n \"field-remove\": [fieldId: string];\r\n \"fields-clear\": [];\r\n}>();\r\n\r\n/* ================= 状态管理 ================= */\r\nconst injectedDynamicState = inject<DynamicFormStateType | null>(\r\n DYNAMIC_FORM_STATE_KEY,\r\n null,\r\n);\r\n\r\nconst dynamicState = computed(\r\n () => props.dynamicFormState || injectedDynamicState,\r\n);\r\nconst isDynamicStateAvailable = computed(() => !!dynamicState.value);\r\n\r\n/* ================= 计算属性 ================= */\r\nconst gridCols = computed(() => props.layoutConfig?.dynamic?.grid?.cols ?? 24);\r\nconst gridGutter = computed(\r\n () => props.layoutConfig?.dynamic?.grid?.gutter ?? 16,\r\n);\r\nconst showControls = computed(\r\n () => props.layoutConfig?.dynamic?.controls?.showControls !== false,\r\n);\r\n\r\nconst maxFields = computed(() => {\r\n return (\r\n dynamicState.value?.state.config.maxFields ??\r\n props.layoutConfig?.dynamic?.dynamic?.maxFields ??\r\n 50\r\n );\r\n});\r\n\r\nconst dynamicFieldsCount = computed(\r\n () => dynamicState.value?.dynamicFieldsCount.value ?? 0,\r\n);\r\nconst canAddMoreFields = computed(\r\n () => dynamicState.value?.canAddMoreFields.value ?? false,\r\n);\r\nconst totalFieldsCount = computed(() => props.formItems.length);\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n return (\r\n item.key?.toString() || (item.props as any)?.path || `dynamic-item-${index}`\r\n );\r\n};\r\n\r\nconst getItemSpan = (index: number): number => {\r\n const span = props.options?.[index]?.layout?.span;\r\n return typeof span === \"number\" && span > 0 && span <= gridCols.value\r\n ? span\r\n : Math.min(12, gridCols.value);\r\n};\r\n\r\nconst isDynamicField = (item: VNode): boolean => {\r\n if (!dynamicState.value) return false;\r\n const fieldId = (item.props as any)?.path || item.key?.toString() || \"\";\r\n return dynamicState.value.state.dynamicFields.some(\r\n (field: any) => field.prop === fieldId,\r\n );\r\n};\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n addField: () => dynamicState.value?.addField(),\r\n removeField: () => dynamicState.value?.removeField(),\r\n clearAllDynamic: () => dynamicState.value?.clearDynamicFields(),\r\n isDynamicStateAvailable,\r\n dynamicState,\r\n});\r\n\r\n/* ================= 开发环境验证 ================= */\r\nif (typeof import.meta !== \"undefined\" && import.meta.env?.DEV) {\r\n watchEffect(() => {\r\n const { options, formItems } = props;\r\n if (options && options.length !== formItems.length) {\r\n console.warn(\r\n `[Dynamic Layout] 配置项数量(${options.length})与表单项数量(${formItems.length})不匹配`,\r\n );\r\n }\r\n\r\n console.log(\r\n \"[Dynamic Layout]\",\r\n isDynamicStateAvailable.value ? \"动态模式已启用\" : \"运行在静态模式\",\r\n {\r\n totalFields: totalFieldsCount.value,\r\n dynamicFields: dynamicFieldsCount.value,\r\n stateSource: props.dynamicFormState ? \"props透传\" : \"inject注入\",\r\n },\r\n );\r\n });\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 动态表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-dynamic\">\r\n <!-- 动态控制面板 -->\r\n <div\r\n v-if=\"showControls && isDynamicStateAvailable\"\r\n class=\"dynamic-controls\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制\" :bordered=\"false\">\r\n <template #header-extra>\r\n <NBadge :value=\"dynamicFieldsCount\" type=\"warning\">\r\n <C_Icon :name=\"'mdi:layers'\" :size=\"16\" />\r\n </NBadge>\r\n </template>\r\n\r\n <NSpace justify=\"space-between\" align=\"center\">\r\n <NSpace>\r\n <NButton\r\n size=\"small\"\r\n type=\"primary\"\r\n :disabled=\"!canAddMoreFields\"\r\n @click=\"dynamicState!.addField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:plus'\" :size=\"14\" />\r\n </template>\r\n 添加字段 ({{ dynamicFieldsCount }}/{{ maxFields }})\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"warning\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.removeField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:minus'\" :size=\"14\" />\r\n </template>\r\n 移除字段\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"error\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.clearDynamicFields()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:delete-sweep'\" :size=\"14\" />\r\n </template>\r\n 清空动态\r\n </NButton>\r\n </NSpace>\r\n\r\n <!-- 最大字段数配置 -->\r\n <div class=\"max-fields-config\">\r\n <span>最大字段数:</span>\r\n <NInputNumber\r\n :value=\"maxFields\"\r\n @update:value=\"\r\n (v: any) => v && dynamicState!.updateConfig({ maxFields: v })\r\n \"\r\n :min=\"5\"\r\n :max=\"50\"\r\n size=\"small\"\r\n style=\"width: 100px\"\r\n />\r\n </div>\r\n </NSpace>\r\n\r\n <!-- 状态信息 -->\r\n <div class=\"dynamic-stats\">\r\n <NSpace>\r\n <NTag type=\"info\">总字段: {{ totalFieldsCount }}</NTag>\r\n <NTag type=\"warning\">动态: {{ dynamicFieldsCount }}</NTag>\r\n </NSpace>\r\n </div>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 状态不可用时的提示 -->\r\n <div\r\n v-else-if=\"showControls && !isDynamicStateAvailable\"\r\n class=\"dynamic-controls-fallback\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制 (静态模式)\" :bordered=\"false\">\r\n <NAlert type=\"info\" show-icon>\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n </template>\r\n 当前为静态模式,如需动态功能请在父组件中提供动态表单状态。\r\n </NAlert>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 表单项渲染区域 -->\r\n <div class=\"dynamic-form-items\">\r\n <NGrid :cols=\"gridCols\" :x-gap=\"gridGutter\" :y-gap=\"gridGutter\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :span=\"getItemSpan(index)\"\r\n >\r\n <div\r\n class=\"dynamic-item-wrapper\"\r\n :class=\"{ 'is-dynamic-field': isDynamicField(item) }\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { DynamicFieldConfig, LayoutConfig, FormOption } from \"../../types\";\r\nimport {\r\n DYNAMIC_FORM_STATE_KEY,\r\n type DynamicFormStateType,\r\n} from \"../../composables/useDynamicFormState\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 接口定义 ================= */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: LayoutConfig;\r\n options?: FormOption[];\r\n dynamicFormState?: DynamicFormStateType | null;\r\n}\r\n\r\n/* ================= 组件配置 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n dynamicFormState: null,\r\n});\r\n\r\ndefineEmits<{\r\n \"field-add\": [fieldConfig: DynamicFieldConfig];\r\n \"field-remove\": [fieldId: string];\r\n \"fields-clear\": [];\r\n}>();\r\n\r\n/* ================= 状态管理 ================= */\r\nconst injectedDynamicState = inject<DynamicFormStateType | null>(\r\n DYNAMIC_FORM_STATE_KEY,\r\n null,\r\n);\r\n\r\nconst dynamicState = computed(\r\n () => props.dynamicFormState || injectedDynamicState,\r\n);\r\nconst isDynamicStateAvailable = computed(() => !!dynamicState.value);\r\n\r\n/* ================= 计算属性 ================= */\r\nconst gridCols = computed(() => props.layoutConfig?.dynamic?.grid?.cols ?? 24);\r\nconst gridGutter = computed(\r\n () => props.layoutConfig?.dynamic?.grid?.gutter ?? 16,\r\n);\r\nconst showControls = computed(\r\n () => props.layoutConfig?.dynamic?.controls?.showControls !== false,\r\n);\r\n\r\nconst maxFields = computed(() => {\r\n return (\r\n dynamicState.value?.state.config.maxFields ??\r\n props.layoutConfig?.dynamic?.dynamic?.maxFields ??\r\n 50\r\n );\r\n});\r\n\r\nconst dynamicFieldsCount = computed(\r\n () => dynamicState.value?.dynamicFieldsCount.value ?? 0,\r\n);\r\nconst canAddMoreFields = computed(\r\n () => dynamicState.value?.canAddMoreFields.value ?? false,\r\n);\r\nconst totalFieldsCount = computed(() => props.formItems.length);\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n return (\r\n item.key?.toString() || (item.props as any)?.path || `dynamic-item-${index}`\r\n );\r\n};\r\n\r\nconst getItemSpan = (index: number): number => {\r\n const span = props.options?.[index]?.layout?.span;\r\n return typeof span === \"number\" && span > 0 && span <= gridCols.value\r\n ? span\r\n : Math.min(12, gridCols.value);\r\n};\r\n\r\nconst isDynamicField = (item: VNode): boolean => {\r\n if (!dynamicState.value) return false;\r\n const fieldId = (item.props as any)?.path || item.key?.toString() || \"\";\r\n return dynamicState.value.state.dynamicFields.some(\r\n (field: any) => field.prop === fieldId,\r\n );\r\n};\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n addField: () => dynamicState.value?.addField(),\r\n removeField: () => dynamicState.value?.removeField(),\r\n clearAllDynamic: () => dynamicState.value?.clearDynamicFields(),\r\n isDynamicStateAvailable,\r\n dynamicState,\r\n});\r\n\r\n/* ================= 开发环境验证 ================= */\r\nif (typeof import.meta !== \"undefined\" && import.meta.env?.DEV) {\r\n watchEffect(() => {\r\n const { options, formItems } = props;\r\n if (options && options.length !== formItems.length) {\r\n console.warn(\r\n `[Dynamic Layout] 配置项数量(${options.length})与表单项数量(${formItems.length})不匹配`,\r\n );\r\n }\r\n\r\n console.log(\r\n \"[Dynamic Layout]\",\r\n isDynamicStateAvailable.value ? \"动态模式已启用\" : \"运行在静态模式\",\r\n {\r\n totalFields: totalFieldsCount.value,\r\n dynamicFields: dynamicFieldsCount.value,\r\n stateSource: props.dynamicFormState ? \"props透传\" : \"inject注入\",\r\n },\r\n );\r\n });\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 自定义布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"custom-layout\">\r\n <!-- 顶部工具栏 -->\r\n <NCard :bordered=\"false\" class=\"toolbar-card\">\r\n <div class=\"toolbar-content\">\r\n <!-- 模式切换 -->\r\n <div class=\"mode-section\">\r\n <span class=\"section-label\">自定义模式:</span>\r\n <NButtonGroup>\r\n <NButton\r\n :type=\"isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = true\"\r\n size=\"small\"\r\n >\r\n 🎨 设计模式\r\n </NButton>\r\n <NButton\r\n :type=\"!isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = false\"\r\n size=\"small\"\r\n >\r\n 📝 填写模式\r\n </NButton>\r\n </NButtonGroup>\r\n </div>\r\n\r\n <!-- 统计信息 -->\r\n <div class=\"stats-section\">\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ customAreas.length }}</div>\r\n <div class=\"stat-label\">自定义区域</div>\r\n </div>\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ totalFieldsCount }}</div>\r\n <div class=\"stat-label\">字段总数</div>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作按钮 -->\r\n <div class=\"actions-section\">\r\n <template v-if=\"isDesignMode\">\r\n <NButton secondary @click=\"handleSaveLayout\" size=\"small\">\r\n 💾 保存布局\r\n </NButton>\r\n <NButton secondary @click=\"handleResetLayout\" size=\"small\">\r\n 🔄 重置布局\r\n </NButton>\r\n </template>\r\n <template v-else>\r\n <NButton secondary @click=\"handleExportData\" size=\"small\">\r\n 📊 导出数据\r\n </NButton>\r\n </template>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 设计模式工具面板 -->\r\n <NCard v-if=\"isDesignMode\" class=\"design-panel\" title=\"🎨 设计工具\">\r\n <div class=\"design-tools\">\r\n <div class=\"tool-group\">\r\n <span class=\"group-label\">添加区域:</span>\r\n <NButton @click=\"addArea('horizontal')\" size=\"small\">\r\n ➡️ 水平区域\r\n </NButton>\r\n <NButton @click=\"addArea('vertical')\" size=\"small\">\r\n ⬇️ 垂直区域\r\n </NButton>\r\n <NButton @click=\"addArea('grid')\" size=\"small\"> ⚏ 网格区域 </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 主画布区域 -->\r\n <div class=\"layout-canvas\" :class=\"{ 'design-mode': isDesignMode }\">\r\n <!-- 设计模式 -->\r\n <template v-if=\"isDesignMode\">\r\n <div class=\"canvas-hint\" v-if=\"customAreas.length === 0\">\r\n <div class=\"hint-content\">\r\n <h3>🎨 开始自定义你的布局</h3>\r\n <p>点击上方按钮添加区域</p>\r\n </div>\r\n </div>\r\n\r\n <!-- 区域列表 -->\r\n <div v-else class=\"areas-container\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"custom-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <div class=\"area-header\">\r\n <div class=\"area-info\">\r\n <NInput\r\n v-if=\"editingTitleId === area.id\"\r\n v-model:value=\"area.title\"\r\n size=\"small\"\r\n @blur=\"editingTitleId = ''\"\r\n @keyup.enter=\"editingTitleId = ''\"\r\n class=\"title-input\"\r\n />\r\n <span\r\n v-else\r\n class=\"area-title\"\r\n @click=\"editingTitleId = area.id\"\r\n >\r\n {{ area.title }}\r\n </span>\r\n <NTag size=\"small\">{{ area.fields.length }} 字段</NTag>\r\n </div>\r\n <div class=\"area-controls\">\r\n <NButton\r\n text\r\n @click=\"deleteArea(area.id)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n title=\"删除区域\"\r\n >\r\n 🗑️\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段容器 -->\r\n <div class=\"area-fields\">\r\n <div\r\n v-for=\"field in area.fields\"\r\n :key=\"field.id\"\r\n class=\"field-item\"\r\n >\r\n <div class=\"field-preview\">\r\n <span class=\"field-label\">{{\r\n field.label || field.prop\r\n }}</span>\r\n <span class=\"field-type\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"area-drop-zone\" v-if=\"area.fields.length === 0\">\r\n 拖拽字段到这里\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段池 -->\r\n <NCard class=\"field-pool\" title=\"📦 可用字段\">\r\n <div class=\"pool-fields-grid\">\r\n <div\r\n v-for=\"field in availableFields\"\r\n :key=\"field.id\"\r\n class=\"pool-field\"\r\n >\r\n <span class=\"field-name\">{{ field.label || field.prop }}</span>\r\n <span class=\"field-type-tag\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </NCard>\r\n </template>\r\n\r\n <!-- 填写模式 -->\r\n <template v-else>\r\n <div v-if=\"customAreas.length === 0\" class=\"empty-layout\">\r\n <NEmpty description=\"尚未设计布局\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" type=\"primary\">\r\n 🎨 开始设计\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n\r\n <div v-else class=\"form-container\">\r\n <div class=\"form-areas\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"form-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <NCard v-if=\"area.fields.length > 0\" :title=\"area.title\">\r\n <div class=\"area-form-items\" :class=\"`layout-${area.type}`\">\r\n <component\r\n v-for=\"field in area.fields\"\r\n :key=\"field.prop\"\r\n :is=\"getFormItemForField(field)\"\r\n />\r\n </div>\r\n </NCard>\r\n <NEmpty v-else description=\"此区域暂无字段\" size=\"small\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" size=\"small\" secondary>\r\n 🎨 添加字段\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { FormOption } from \"../../types\";\r\n\r\n/* 接口定义 */\r\ninterface CustomArea {\r\n id: string;\r\n title: string;\r\n type: \"horizontal\" | \"vertical\" | \"grid\";\r\n fields: DraggableFormOption[];\r\n}\r\n\r\ninterface DraggableFormOption extends FormOption {\r\n id: string;\r\n}\r\n\r\ninterface Props {\r\n options?: FormOption[];\r\n formItems?: VNode[];\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n options: () => [],\r\n formItems: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"fields-change\": [fields: FormOption[]];\r\n \"export-data\": [data: any];\r\n}>();\r\n\r\n/* 响应式状态 */\r\nconst isDesignMode = ref(true);\r\nconst customAreas = ref<CustomArea[]>([]);\r\nconst availableFields = ref<DraggableFormOption[]>([]);\r\nconst editingTitleId = ref<string | number>(\"\");\r\n\r\n/* 计算属性 */\r\nconst allFormOptions = computed(() => {\r\n if (props.options?.length > 0) {\r\n return props.options;\r\n }\r\n\r\n return (\r\n props.formItems\r\n ?.map((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return {\r\n prop: itemProps?.path || \"\",\r\n label: itemProps?.label || itemProps?.path || \"\",\r\n type: \"input\",\r\n show: true,\r\n } as FormOption;\r\n })\r\n .filter((option) => option.prop) || []\r\n );\r\n});\r\n\r\nconst totalFieldsCount = computed(() =>\r\n customAreas.value.reduce((total, area) => total + area.fields.length, 0),\r\n);\r\n\r\n/* 工具函数 */\r\nconst generateId = () =>\r\n `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\r\n\r\nconst getFieldTypeName = (type: string) => {\r\n const typeMap: Record<string, string> = {\r\n input: \"输入框\",\r\n select: \"下拉框\",\r\n radio: \"单选框\",\r\n checkbox: \"复选框\",\r\n textarea: \"文本域\",\r\n date: \"日期\",\r\n number: \"数字\",\r\n };\r\n return typeMap[type] || type;\r\n};\r\n\r\nconst getFormItemForField = (field: FormOption) => {\r\n return (\r\n props.formItems?.find((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return itemProps?.path === field.prop;\r\n }) || null\r\n );\r\n};\r\n\r\n/* 布局操作 */\r\nconst addArea = (type: \"horizontal\" | \"vertical\" | \"grid\") => {\r\n const area: CustomArea = {\r\n id: generateId(),\r\n title: `${type === \"horizontal\" ? \"水平\" : type === \"vertical\" ? \"垂直\" : \"网格\"}区域`,\r\n type,\r\n fields: [],\r\n };\r\n customAreas.value.push(area);\r\n};\r\n\r\nconst deleteArea = (areaId: string | number) => {\r\n const index = customAreas.value.findIndex((area) => area.id === areaId);\r\n if (index !== -1) {\r\n customAreas.value.splice(index, 1);\r\n }\r\n};\r\n\r\nconst handleSaveLayout = () => {\r\n const config = JSON.stringify(customAreas.value, null, 2);\r\n const blob = new Blob([config], { type: \"application/json\" });\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = `layout-config-${Date.now()}.json`;\r\n document.body.appendChild(link);\r\n link.click();\r\n document.body.removeChild(link);\r\n URL.revokeObjectURL(url);\r\n};\r\n\r\nconst handleResetLayout = () => {\r\n customAreas.value = [];\r\n};\r\n\r\nconst handleExportData = () => {\r\n const exportData = {\r\n layout: customAreas.value,\r\n formData: props.formData,\r\n timestamp: new Date().toISOString(),\r\n };\r\n emit(\"export-data\", exportData);\r\n};\r\n\r\n/* 监听器 */\r\nwatchEffect(() => {\r\n const usedProps = new Set(\r\n customAreas.value.flatMap((area) => area.fields.map((field) => field.prop)),\r\n );\r\n\r\n availableFields.value = allFormOptions.value\r\n .filter((field) => !usedProps.has(field.prop))\r\n .map((field) => ({\r\n ...field,\r\n id: field.prop,\r\n }));\r\n});\r\n\r\nwatch(\r\n () => customAreas.value,\r\n () => {\r\n const allFields = customAreas.value.flatMap((area) =>\r\n area.fields.map((field) => ({\r\n ...field,\r\n id: undefined,\r\n })),\r\n );\r\n emit(\"fields-change\", allFields);\r\n },\r\n { deep: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 自定义布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"custom-layout\">\r\n <!-- 顶部工具栏 -->\r\n <NCard :bordered=\"false\" class=\"toolbar-card\">\r\n <div class=\"toolbar-content\">\r\n <!-- 模式切换 -->\r\n <div class=\"mode-section\">\r\n <span class=\"section-label\">自定义模式:</span>\r\n <NButtonGroup>\r\n <NButton\r\n :type=\"isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = true\"\r\n size=\"small\"\r\n >\r\n 🎨 设计模式\r\n </NButton>\r\n <NButton\r\n :type=\"!isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = false\"\r\n size=\"small\"\r\n >\r\n 📝 填写模式\r\n </NButton>\r\n </NButtonGroup>\r\n </div>\r\n\r\n <!-- 统计信息 -->\r\n <div class=\"stats-section\">\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ customAreas.length }}</div>\r\n <div class=\"stat-label\">自定义区域</div>\r\n </div>\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ totalFieldsCount }}</div>\r\n <div class=\"stat-label\">字段总数</div>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作按钮 -->\r\n <div class=\"actions-section\">\r\n <template v-if=\"isDesignMode\">\r\n <NButton secondary @click=\"handleSaveLayout\" size=\"small\">\r\n 💾 保存布局\r\n </NButton>\r\n <NButton secondary @click=\"handleResetLayout\" size=\"small\">\r\n 🔄 重置布局\r\n </NButton>\r\n </template>\r\n <template v-else>\r\n <NButton secondary @click=\"handleExportData\" size=\"small\">\r\n 📊 导出数据\r\n </NButton>\r\n </template>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 设计模式工具面板 -->\r\n <NCard v-if=\"isDesignMode\" class=\"design-panel\" title=\"🎨 设计工具\">\r\n <div class=\"design-tools\">\r\n <div class=\"tool-group\">\r\n <span class=\"group-label\">添加区域:</span>\r\n <NButton @click=\"addArea('horizontal')\" size=\"small\">\r\n ➡️ 水平区域\r\n </NButton>\r\n <NButton @click=\"addArea('vertical')\" size=\"small\">\r\n ⬇️ 垂直区域\r\n </NButton>\r\n <NButton @click=\"addArea('grid')\" size=\"small\"> ⚏ 网格区域 </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 主画布区域 -->\r\n <div class=\"layout-canvas\" :class=\"{ 'design-mode': isDesignMode }\">\r\n <!-- 设计模式 -->\r\n <template v-if=\"isDesignMode\">\r\n <div class=\"canvas-hint\" v-if=\"customAreas.length === 0\">\r\n <div class=\"hint-content\">\r\n <h3>🎨 开始自定义你的布局</h3>\r\n <p>点击上方按钮添加区域</p>\r\n </div>\r\n </div>\r\n\r\n <!-- 区域列表 -->\r\n <div v-else class=\"areas-container\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"custom-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <div class=\"area-header\">\r\n <div class=\"area-info\">\r\n <NInput\r\n v-if=\"editingTitleId === area.id\"\r\n v-model:value=\"area.title\"\r\n size=\"small\"\r\n @blur=\"editingTitleId = ''\"\r\n @keyup.enter=\"editingTitleId = ''\"\r\n class=\"title-input\"\r\n />\r\n <span\r\n v-else\r\n class=\"area-title\"\r\n @click=\"editingTitleId = area.id\"\r\n >\r\n {{ area.title }}\r\n </span>\r\n <NTag size=\"small\">{{ area.fields.length }} 字段</NTag>\r\n </div>\r\n <div class=\"area-controls\">\r\n <NButton\r\n text\r\n @click=\"deleteArea(area.id)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n title=\"删除区域\"\r\n >\r\n 🗑️\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段容器 -->\r\n <div class=\"area-fields\">\r\n <div\r\n v-for=\"field in area.fields\"\r\n :key=\"field.id\"\r\n class=\"field-item\"\r\n >\r\n <div class=\"field-preview\">\r\n <span class=\"field-label\">{{\r\n field.label || field.prop\r\n }}</span>\r\n <span class=\"field-type\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"area-drop-zone\" v-if=\"area.fields.length === 0\">\r\n 拖拽字段到这里\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段池 -->\r\n <NCard class=\"field-pool\" title=\"📦 可用字段\">\r\n <div class=\"pool-fields-grid\">\r\n <div\r\n v-for=\"field in availableFields\"\r\n :key=\"field.id\"\r\n class=\"pool-field\"\r\n >\r\n <span class=\"field-name\">{{ field.label || field.prop }}</span>\r\n <span class=\"field-type-tag\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </NCard>\r\n </template>\r\n\r\n <!-- 填写模式 -->\r\n <template v-else>\r\n <div v-if=\"customAreas.length === 0\" class=\"empty-layout\">\r\n <NEmpty description=\"尚未设计布局\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" type=\"primary\">\r\n 🎨 开始设计\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n\r\n <div v-else class=\"form-container\">\r\n <div class=\"form-areas\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"form-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <NCard v-if=\"area.fields.length > 0\" :title=\"area.title\">\r\n <div class=\"area-form-items\" :class=\"`layout-${area.type}`\">\r\n <component\r\n v-for=\"field in area.fields\"\r\n :key=\"field.prop\"\r\n :is=\"getFormItemForField(field)\"\r\n />\r\n </div>\r\n </NCard>\r\n <NEmpty v-else description=\"此区域暂无字段\" size=\"small\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" size=\"small\" secondary>\r\n 🎨 添加字段\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { FormOption } from \"../../types\";\r\n\r\n/* 接口定义 */\r\ninterface CustomArea {\r\n id: string;\r\n title: string;\r\n type: \"horizontal\" | \"vertical\" | \"grid\";\r\n fields: DraggableFormOption[];\r\n}\r\n\r\ninterface DraggableFormOption extends FormOption {\r\n id: string;\r\n}\r\n\r\ninterface Props {\r\n options?: FormOption[];\r\n formItems?: VNode[];\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n options: () => [],\r\n formItems: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"fields-change\": [fields: FormOption[]];\r\n \"export-data\": [data: any];\r\n}>();\r\n\r\n/* 响应式状态 */\r\nconst isDesignMode = ref(true);\r\nconst customAreas = ref<CustomArea[]>([]);\r\nconst availableFields = ref<DraggableFormOption[]>([]);\r\nconst editingTitleId = ref<string | number>(\"\");\r\n\r\n/* 计算属性 */\r\nconst allFormOptions = computed(() => {\r\n if (props.options?.length > 0) {\r\n return props.options;\r\n }\r\n\r\n return (\r\n props.formItems\r\n ?.map((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return {\r\n prop: itemProps?.path || \"\",\r\n label: itemProps?.label || itemProps?.path || \"\",\r\n type: \"input\",\r\n show: true,\r\n } as FormOption;\r\n })\r\n .filter((option) => option.prop) || []\r\n );\r\n});\r\n\r\nconst totalFieldsCount = computed(() =>\r\n customAreas.value.reduce((total, area) => total + area.fields.length, 0),\r\n);\r\n\r\n/* 工具函数 */\r\nconst generateId = () =>\r\n `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\r\n\r\nconst getFieldTypeName = (type: string) => {\r\n const typeMap: Record<string, string> = {\r\n input: \"输入框\",\r\n select: \"下拉框\",\r\n radio: \"单选框\",\r\n checkbox: \"复选框\",\r\n textarea: \"文本域\",\r\n date: \"日期\",\r\n number: \"数字\",\r\n };\r\n return typeMap[type] || type;\r\n};\r\n\r\nconst getFormItemForField = (field: FormOption) => {\r\n return (\r\n props.formItems?.find((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return itemProps?.path === field.prop;\r\n }) || null\r\n );\r\n};\r\n\r\n/* 布局操作 */\r\nconst addArea = (type: \"horizontal\" | \"vertical\" | \"grid\") => {\r\n const area: CustomArea = {\r\n id: generateId(),\r\n title: `${type === \"horizontal\" ? \"水平\" : type === \"vertical\" ? \"垂直\" : \"网格\"}区域`,\r\n type,\r\n fields: [],\r\n };\r\n customAreas.value.push(area);\r\n};\r\n\r\nconst deleteArea = (areaId: string | number) => {\r\n const index = customAreas.value.findIndex((area) => area.id === areaId);\r\n if (index !== -1) {\r\n customAreas.value.splice(index, 1);\r\n }\r\n};\r\n\r\nconst handleSaveLayout = () => {\r\n const config = JSON.stringify(customAreas.value, null, 2);\r\n const blob = new Blob([config], { type: \"application/json\" });\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = `layout-config-${Date.now()}.json`;\r\n document.body.appendChild(link);\r\n link.click();\r\n document.body.removeChild(link);\r\n URL.revokeObjectURL(url);\r\n};\r\n\r\nconst handleResetLayout = () => {\r\n customAreas.value = [];\r\n};\r\n\r\nconst handleExportData = () => {\r\n const exportData = {\r\n layout: customAreas.value,\r\n formData: props.formData,\r\n timestamp: new Date().toISOString(),\r\n };\r\n emit(\"export-data\", exportData);\r\n};\r\n\r\n/* 监听器 */\r\nwatchEffect(() => {\r\n const usedProps = new Set(\r\n customAreas.value.flatMap((area) => area.fields.map((field) => field.prop)),\r\n );\r\n\r\n availableFields.value = allFormOptions.value\r\n .filter((field) => !usedProps.has(field.prop))\r\n .map((field) => ({\r\n ...field,\r\n id: field.prop,\r\n }));\r\n});\r\n\r\nwatch(\r\n () => customAreas.value,\r\n () => {\r\n const allFields = customAreas.value.flatMap((area) =>\r\n area.fields.map((field) => ({\r\n ...field,\r\n id: undefined,\r\n })),\r\n );\r\n emit(\"fields-change\", allFields);\r\n },\r\n { deep: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 自定义布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"custom-layout\">\r\n <!-- 顶部工具栏 -->\r\n <NCard :bordered=\"false\" class=\"toolbar-card\">\r\n <div class=\"toolbar-content\">\r\n <!-- 模式切换 -->\r\n <div class=\"mode-section\">\r\n <span class=\"section-label\">自定义模式:</span>\r\n <NButtonGroup>\r\n <NButton\r\n :type=\"isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = true\"\r\n size=\"small\"\r\n >\r\n 🎨 设计模式\r\n </NButton>\r\n <NButton\r\n :type=\"!isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = false\"\r\n size=\"small\"\r\n >\r\n 📝 填写模式\r\n </NButton>\r\n </NButtonGroup>\r\n </div>\r\n\r\n <!-- 统计信息 -->\r\n <div class=\"stats-section\">\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ customAreas.length }}</div>\r\n <div class=\"stat-label\">自定义区域</div>\r\n </div>\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ totalFieldsCount }}</div>\r\n <div class=\"stat-label\">字段总数</div>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作按钮 -->\r\n <div class=\"actions-section\">\r\n <template v-if=\"isDesignMode\">\r\n <NButton secondary @click=\"handleSaveLayout\" size=\"small\">\r\n 💾 保存布局\r\n </NButton>\r\n <NButton secondary @click=\"handleResetLayout\" size=\"small\">\r\n 🔄 重置布局\r\n </NButton>\r\n </template>\r\n <template v-else>\r\n <NButton secondary @click=\"handleExportData\" size=\"small\">\r\n 📊 导出数据\r\n </NButton>\r\n </template>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 设计模式工具面板 -->\r\n <NCard v-if=\"isDesignMode\" class=\"design-panel\" title=\"🎨 设计工具\">\r\n <div class=\"design-tools\">\r\n <div class=\"tool-group\">\r\n <span class=\"group-label\">添加区域:</span>\r\n <NButton @click=\"addArea('horizontal')\" size=\"small\">\r\n ➡️ 水平区域\r\n </NButton>\r\n <NButton @click=\"addArea('vertical')\" size=\"small\">\r\n ⬇️ 垂直区域\r\n </NButton>\r\n <NButton @click=\"addArea('grid')\" size=\"small\"> ⚏ 网格区域 </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 主画布区域 -->\r\n <div class=\"layout-canvas\" :class=\"{ 'design-mode': isDesignMode }\">\r\n <!-- 设计模式 -->\r\n <template v-if=\"isDesignMode\">\r\n <div class=\"canvas-hint\" v-if=\"customAreas.length === 0\">\r\n <div class=\"hint-content\">\r\n <h3>🎨 开始自定义你的布局</h3>\r\n <p>点击上方按钮添加区域</p>\r\n </div>\r\n </div>\r\n\r\n <!-- 区域列表 -->\r\n <div v-else class=\"areas-container\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"custom-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <div class=\"area-header\">\r\n <div class=\"area-info\">\r\n <NInput\r\n v-if=\"editingTitleId === area.id\"\r\n v-model:value=\"area.title\"\r\n size=\"small\"\r\n @blur=\"editingTitleId = ''\"\r\n @keyup.enter=\"editingTitleId = ''\"\r\n class=\"title-input\"\r\n />\r\n <span\r\n v-else\r\n class=\"area-title\"\r\n @click=\"editingTitleId = area.id\"\r\n >\r\n {{ area.title }}\r\n </span>\r\n <NTag size=\"small\">{{ area.fields.length }} 字段</NTag>\r\n </div>\r\n <div class=\"area-controls\">\r\n <NButton\r\n text\r\n @click=\"deleteArea(area.id)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n title=\"删除区域\"\r\n >\r\n 🗑️\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段容器 -->\r\n <div class=\"area-fields\">\r\n <div\r\n v-for=\"field in area.fields\"\r\n :key=\"field.id\"\r\n class=\"field-item\"\r\n >\r\n <div class=\"field-preview\">\r\n <span class=\"field-label\">{{\r\n field.label || field.prop\r\n }}</span>\r\n <span class=\"field-type\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"area-drop-zone\" v-if=\"area.fields.length === 0\">\r\n 拖拽字段到这里\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段池 -->\r\n <NCard class=\"field-pool\" title=\"📦 可用字段\">\r\n <div class=\"pool-fields-grid\">\r\n <div\r\n v-for=\"field in availableFields\"\r\n :key=\"field.id\"\r\n class=\"pool-field\"\r\n >\r\n <span class=\"field-name\">{{ field.label || field.prop }}</span>\r\n <span class=\"field-type-tag\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </NCard>\r\n </template>\r\n\r\n <!-- 填写模式 -->\r\n <template v-else>\r\n <div v-if=\"customAreas.length === 0\" class=\"empty-layout\">\r\n <NEmpty description=\"尚未设计布局\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" type=\"primary\">\r\n 🎨 开始设计\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n\r\n <div v-else class=\"form-container\">\r\n <div class=\"form-areas\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"form-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <NCard v-if=\"area.fields.length > 0\" :title=\"area.title\">\r\n <div class=\"area-form-items\" :class=\"`layout-${area.type}`\">\r\n <component\r\n v-for=\"field in area.fields\"\r\n :key=\"field.prop\"\r\n :is=\"getFormItemForField(field)\"\r\n />\r\n </div>\r\n </NCard>\r\n <NEmpty v-else description=\"此区域暂无字段\" size=\"small\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" size=\"small\" secondary>\r\n 🎨 添加字段\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { FormOption } from \"../../types\";\r\n\r\n/* 接口定义 */\r\ninterface CustomArea {\r\n id: string;\r\n title: string;\r\n type: \"horizontal\" | \"vertical\" | \"grid\";\r\n fields: DraggableFormOption[];\r\n}\r\n\r\ninterface DraggableFormOption extends FormOption {\r\n id: string;\r\n}\r\n\r\ninterface Props {\r\n options?: FormOption[];\r\n formItems?: VNode[];\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n options: () => [],\r\n formItems: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"fields-change\": [fields: FormOption[]];\r\n \"export-data\": [data: any];\r\n}>();\r\n\r\n/* 响应式状态 */\r\nconst isDesignMode = ref(true);\r\nconst customAreas = ref<CustomArea[]>([]);\r\nconst availableFields = ref<DraggableFormOption[]>([]);\r\nconst editingTitleId = ref<string | number>(\"\");\r\n\r\n/* 计算属性 */\r\nconst allFormOptions = computed(() => {\r\n if (props.options?.length > 0) {\r\n return props.options;\r\n }\r\n\r\n return (\r\n props.formItems\r\n ?.map((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return {\r\n prop: itemProps?.path || \"\",\r\n label: itemProps?.label || itemProps?.path || \"\",\r\n type: \"input\",\r\n show: true,\r\n } as FormOption;\r\n })\r\n .filter((option) => option.prop) || []\r\n );\r\n});\r\n\r\nconst totalFieldsCount = computed(() =>\r\n customAreas.value.reduce((total, area) => total + area.fields.length, 0),\r\n);\r\n\r\n/* 工具函数 */\r\nconst generateId = () =>\r\n `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\r\n\r\nconst getFieldTypeName = (type: string) => {\r\n const typeMap: Record<string, string> = {\r\n input: \"输入框\",\r\n select: \"下拉框\",\r\n radio: \"单选框\",\r\n checkbox: \"复选框\",\r\n textarea: \"文本域\",\r\n date: \"日期\",\r\n number: \"数字\",\r\n };\r\n return typeMap[type] || type;\r\n};\r\n\r\nconst getFormItemForField = (field: FormOption) => {\r\n return (\r\n props.formItems?.find((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return itemProps?.path === field.prop;\r\n }) || null\r\n );\r\n};\r\n\r\n/* 布局操作 */\r\nconst addArea = (type: \"horizontal\" | \"vertical\" | \"grid\") => {\r\n const area: CustomArea = {\r\n id: generateId(),\r\n title: `${type === \"horizontal\" ? \"水平\" : type === \"vertical\" ? \"垂直\" : \"网格\"}区域`,\r\n type,\r\n fields: [],\r\n };\r\n customAreas.value.push(area);\r\n};\r\n\r\nconst deleteArea = (areaId: string | number) => {\r\n const index = customAreas.value.findIndex((area) => area.id === areaId);\r\n if (index !== -1) {\r\n customAreas.value.splice(index, 1);\r\n }\r\n};\r\n\r\nconst handleSaveLayout = () => {\r\n const config = JSON.stringify(customAreas.value, null, 2);\r\n const blob = new Blob([config], { type: \"application/json\" });\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = `layout-config-${Date.now()}.json`;\r\n document.body.appendChild(link);\r\n link.click();\r\n document.body.removeChild(link);\r\n URL.revokeObjectURL(url);\r\n};\r\n\r\nconst handleResetLayout = () => {\r\n customAreas.value = [];\r\n};\r\n\r\nconst handleExportData = () => {\r\n const exportData = {\r\n layout: customAreas.value,\r\n formData: props.formData,\r\n timestamp: new Date().toISOString(),\r\n };\r\n emit(\"export-data\", exportData);\r\n};\r\n\r\n/* 监听器 */\r\nwatchEffect(() => {\r\n const usedProps = new Set(\r\n customAreas.value.flatMap((area) => area.fields.map((field) => field.prop)),\r\n );\r\n\r\n availableFields.value = allFormOptions.value\r\n .filter((field) => !usedProps.has(field.prop))\r\n .map((field) => ({\r\n ...field,\r\n id: field.prop,\r\n }));\r\n});\r\n\r\nwatch(\r\n () => customAreas.value,\r\n () => {\r\n const allFields = customAreas.value.flatMap((area) =>\r\n area.fields.map((field) => ({\r\n ...field,\r\n id: undefined,\r\n })),\r\n );\r\n emit(\"fields-change\", allFields);\r\n },\r\n { deep: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: 表单组件 — 薄 UI 壳 + 厚 Composable 引擎\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <NForm\r\n ref=\"formRef\"\r\n :model=\"formModel\"\r\n :rules=\"formRules\"\r\n :validate-on-rule-change=\"false\"\r\n :label-placement=\"resolved.labelPlacement\"\r\n :label-width=\"resolved.labelWidth\"\r\n :size=\"resolved.size\"\r\n :disabled=\"resolved.disabled\"\r\n :readonly=\"resolved.readonly\"\r\n v-bind=\"$attrs\"\r\n >\r\n <!-- 布局组件渲染 -->\r\n <component\r\n :is=\"layoutComponent\"\r\n :form-items=\"formItems\"\r\n :layout-config=\"mergedLayoutConfig\"\r\n :options=\"visibleOptions\"\r\n :form-data=\"formModel\"\r\n @tab-change=\"handleLayoutEvent('onTabChange', $event)\"\r\n @tab-validate=\"handleLayoutEvent('onTabValidate', $event)\"\r\n @step-change=\"handleStepChange\"\r\n @step-before-change=\"handleStepBeforeChange\"\r\n @step-validate=\"handleStepValidate\"\r\n @field-add=\"handleLayoutEvent('onFieldAdd', $event)\"\r\n @field-remove=\"handleLayoutEvent('onFieldRemove', $event)\"\r\n @field-toggle=\"\r\n (id: string, visible: boolean) => resolved.onFieldToggle?.(id, visible)\r\n \"\r\n @fields-clear=\"handleLayoutEvent('onFieldsClear')\"\r\n @render-mode-change=\"handleLayoutEvent('onRenderModeChange', $event)\"\r\n @group-toggle=\"\r\n (key: string, collapsed: boolean) =>\r\n resolved.onGroupToggle?.(key, collapsed)\r\n \"\r\n @group-reset=\"handleLayoutEvent('onGroupReset', $event)\"\r\n @validate-success=\"(model: FormModel) => emit('validate-success', model)\"\r\n @validate-error=\"(errors: unknown) => emit('validate-error', errors)\"\r\n @fields-change=\"handleFieldsChange\"\r\n />\r\n\r\n <!-- 表单操作按钮区域(只在特定布局中显示) -->\r\n <NFormItem v-if=\"showActions\" style=\"margin-top: 20px\">\r\n <slot\r\n name=\"action\"\r\n :form=\"formRef\"\r\n :model=\"formModel\"\r\n :validate=\"validate\"\r\n :validateField=\"validateField\"\r\n :reset=\"resetFields\"\r\n :setFields=\"setFields\"\r\n :getModel=\"getModel\"\r\n :clearValidation=\"clearValidation\"\r\n >\r\n <NSpace>\r\n <NButton type=\"primary\" @click=\"handleSubmit\">提交</NButton>\r\n <NButton @click=\"handleReset\">重置</NButton>\r\n </NSpace>\r\n </slot>\r\n </NFormItem>\r\n </NForm>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport {\r\n computed,\r\n ref,\r\n watch,\r\n getCurrentInstance,\r\n resolveComponent,\r\n} from \"vue\";\r\nimport type { FormInst } from \"naive-ui/es/form\";\r\nimport { NForm, NFormItem, NButton, NSpace } from \"naive-ui\";\r\nimport type {\r\n FormOption,\r\n LayoutType,\r\n LayoutConfig,\r\n SubmitEventPayload,\r\n FormModel,\r\n} from \"./types\";\r\nimport {\r\n type FormConfig,\r\n resolveFormConfig,\r\n shouldShowActions as calcShowActions,\r\n} from \"./composables/useFormConfig\";\r\nimport { useFormState } from \"./composables/useFormState\";\r\nimport {\r\n useFormRenderer,\r\n type ComponentMap,\r\n} from \"./composables/useFormRenderer\";\r\n\r\n/* ===== 布局组件静态映射(取代 DynamicComponent) ===== */\r\nimport DefaultLayout from \"./layouts/Default/index.vue\";\r\nimport InlineLayout from \"./layouts/Inline/index.vue\";\r\nimport GridLayout from \"./layouts/Grid/index.vue\";\r\nimport CardLayout from \"./layouts/Card/index.vue\";\r\nimport TabsLayout from \"./layouts/Tabs/index.vue\";\r\nimport StepsLayout from \"./layouts/Steps/index.vue\";\r\nimport DynamicLayout from \"./layouts/Dynamic/index.vue\";\r\nimport CustomLayout from \"./layouts/Custom/index.vue\";\r\n\r\ndefineOptions({ name: \"C_Form\" });\r\n\r\nconst LAYOUT_MAP: Record<LayoutType, any> = {\r\n default: DefaultLayout,\r\n inline: InlineLayout,\r\n grid: GridLayout,\r\n card: CardLayout,\r\n tabs: TabsLayout,\r\n steps: StepsLayout,\r\n dynamic: DynamicLayout,\r\n custom: CustomLayout,\r\n} as const;\r\n\r\n/* ===== Naive UI 组件解析映射 ===== */\r\n/*\r\n * 必须在 <script setup> 中调用 resolveComponent,\r\n * unplugin-vue-components 只在 .vue SFC 中转换这些调用\r\n */\r\nconst COMPONENT_MAP: ComponentMap = {\r\n NFormItem: resolveComponent(\"NFormItem\"),\r\n NInput: resolveComponent(\"NInput\"),\r\n NInputNumber: resolveComponent(\"NInputNumber\"),\r\n NSwitch: resolveComponent(\"NSwitch\"),\r\n NSlider: resolveComponent(\"NSlider\"),\r\n NRate: resolveComponent(\"NRate\"),\r\n NDatePicker: resolveComponent(\"NDatePicker\"),\r\n NTimePicker: resolveComponent(\"NTimePicker\"),\r\n NCascader: resolveComponent(\"NCascader\"),\r\n NColorPicker: resolveComponent(\"NColorPicker\"),\r\n NSelect: resolveComponent(\"NSelect\"),\r\n NCheckboxGroup: resolveComponent(\"NCheckboxGroup\"),\r\n NCheckbox: resolveComponent(\"NCheckbox\"),\r\n NRadioGroup: resolveComponent(\"NRadioGroup\"),\r\n NRadio: resolveComponent(\"NRadio\"),\r\n NUpload: resolveComponent(\"NUpload\"),\r\n NButton: resolveComponent(\"NButton\"),\r\n NSpace: resolveComponent(\"NSpace\"),\r\n C_Editor: resolveComponent(\"C_Editor\"),\r\n} as ComponentMap;\r\n\r\n/* ================= 组件属性定义 ================= */\r\n\r\ninterface CFormProps {\r\n /** 字段配置数组 */\r\n options: FormOption[];\r\n /** 双向绑定表单数据 */\r\n modelValue?: FormModel;\r\n /** 统一配置对象(收拢原先 13 个分散 Props) */\r\n config?: FormConfig;\r\n}\r\n\r\nconst props = withDefaults(defineProps<CFormProps>(), {\r\n config: () => ({}),\r\n});\r\n\r\n/* ================= 组件事件定义(从 16 个精简到 4 个) ================= */\r\n\r\nconst emit = defineEmits<{\r\n submit: [payload: SubmitEventPayload];\r\n \"update:modelValue\": [model: FormModel];\r\n \"validate-success\": [model: FormModel];\r\n \"validate-error\": [errors: unknown];\r\n}>();\r\n\r\n/* ================= 配置解析 ================= */\r\n\r\nconst resolved = computed(() => resolveFormConfig(props.config));\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst formRef = ref<FormInst | null>(null);\r\nconst optionsRef = computed(() => props.options);\r\n\r\n/* ===== 状态引擎 ===== */\r\nconst {\r\n formModel,\r\n formRules,\r\n visibleOptions,\r\n initialize,\r\n handleFieldChange,\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n handleSubmit,\r\n handleReset,\r\n} = useFormState(optionsRef, resolved, formRef, emit);\r\n\r\n/* ===== 渲染引擎 ===== */\r\nconst currentInstance = getCurrentInstance();\r\nconst { formItems } = useFormRenderer(\r\n formModel,\r\n visibleOptions,\r\n resolved,\r\n handleFieldChange,\r\n COMPONENT_MAP,\r\n currentInstance?.slots,\r\n);\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst layoutComponent = computed(\r\n () => LAYOUT_MAP[resolved.value.layout] || LAYOUT_MAP.default,\r\n);\r\n\r\nconst mergedLayoutConfig = computed<LayoutConfig>(() => ({\r\n type: resolved.value.layout,\r\n grid: resolved.value.grid,\r\n inline: resolved.value.inline,\r\n card: resolved.value.card,\r\n tabs: resolved.value.tabs,\r\n steps: resolved.value.steps,\r\n dynamic: resolved.value.dynamic,\r\n custom: resolved.value.custom,\r\n}));\r\n\r\nconst showActions = computed(() => calcShowActions(resolved.value));\r\n\r\n/* ================= 布局事件 → config 回调桥接 ================= */\r\n\r\n/** 通用布局事件桥接 */\r\nconst handleLayoutEvent = (callbackName: string, ...args: any[]) => {\r\n const callback = (resolved.value as any)[callbackName];\r\n callback?.(...args);\r\n};\r\n\r\n/** 字段变化事件(保留回调通道) */\r\nconst handleFieldsChange = (fields: FormOption[]): void => {\r\n resolved.value.onFieldsChange?.(fields);\r\n};\r\n\r\n/** Steps 布局事件 — 需要多参数特殊处理 */\r\nconst handleStepChange = (stepIndex: number, stepKey: string): void => {\r\n resolved.value.onStepChange?.(stepIndex, stepKey);\r\n};\r\n\r\nconst handleStepBeforeChange = async (\r\n currentStep: number,\r\n targetStep: number,\r\n): Promise<boolean> => {\r\n resolved.value.onStepBeforeChange?.(currentStep, targetStep);\r\n return true;\r\n};\r\n\r\nconst handleStepValidate = async (stepIndex: number): Promise<boolean> => {\r\n try {\r\n const currentStepKey = resolved.value.steps?.steps?.[stepIndex]?.key;\r\n if (!currentStepKey) return true;\r\n\r\n const stepFields = props.options\r\n .filter((option) => option.layout?.step === currentStepKey)\r\n .map((option) => option.prop);\r\n\r\n if (stepFields.length === 0) return true;\r\n\r\n await validateField(stepFields);\r\n resolved.value.onStepValidate?.(stepIndex);\r\n return true;\r\n } catch (error) {\r\n console.warn(`[C_Form] 步骤 ${stepIndex} 验证失败:`, error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= modelValue 双向同步 ================= */\r\n\r\nwatch(\r\n () => props.modelValue,\r\n (val) => {\r\n if (val) Object.assign(formModel, val);\r\n },\r\n { immediate: true, deep: true },\r\n);\r\n\r\n/* ================= 组件暴露 ================= */\r\n\r\ndefineExpose({\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n formRef,\r\n formModel,\r\n initialize,\r\n layoutType: computed(() => resolved.value.layout),\r\n shouldShowDefaultActions: showActions,\r\n});\r\n</script>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: 表单组件 — 薄 UI 壳 + 厚 Composable 引擎\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <NForm\r\n ref=\"formRef\"\r\n :model=\"formModel\"\r\n :rules=\"formRules\"\r\n :validate-on-rule-change=\"false\"\r\n :label-placement=\"resolved.labelPlacement\"\r\n :label-width=\"resolved.labelWidth\"\r\n :size=\"resolved.size\"\r\n :disabled=\"resolved.disabled\"\r\n :readonly=\"resolved.readonly\"\r\n v-bind=\"$attrs\"\r\n >\r\n <!-- 布局组件渲染 -->\r\n <component\r\n :is=\"layoutComponent\"\r\n :form-items=\"formItems\"\r\n :layout-config=\"mergedLayoutConfig\"\r\n :options=\"visibleOptions\"\r\n :form-data=\"formModel\"\r\n @tab-change=\"handleLayoutEvent('onTabChange', $event)\"\r\n @tab-validate=\"handleLayoutEvent('onTabValidate', $event)\"\r\n @step-change=\"handleStepChange\"\r\n @step-before-change=\"handleStepBeforeChange\"\r\n @step-validate=\"handleStepValidate\"\r\n @field-add=\"handleLayoutEvent('onFieldAdd', $event)\"\r\n @field-remove=\"handleLayoutEvent('onFieldRemove', $event)\"\r\n @field-toggle=\"\r\n (id: string, visible: boolean) => resolved.onFieldToggle?.(id, visible)\r\n \"\r\n @fields-clear=\"handleLayoutEvent('onFieldsClear')\"\r\n @render-mode-change=\"handleLayoutEvent('onRenderModeChange', $event)\"\r\n @group-toggle=\"\r\n (key: string, collapsed: boolean) =>\r\n resolved.onGroupToggle?.(key, collapsed)\r\n \"\r\n @group-reset=\"handleLayoutEvent('onGroupReset', $event)\"\r\n @validate-success=\"(model: FormModel) => emit('validate-success', model)\"\r\n @validate-error=\"(errors: unknown) => emit('validate-error', errors)\"\r\n @fields-change=\"handleFieldsChange\"\r\n />\r\n\r\n <!-- 表单操作按钮区域(只在特定布局中显示) -->\r\n <NFormItem v-if=\"showActions\" style=\"margin-top: 20px\">\r\n <slot\r\n name=\"action\"\r\n :form=\"formRef\"\r\n :model=\"formModel\"\r\n :validate=\"validate\"\r\n :validateField=\"validateField\"\r\n :reset=\"resetFields\"\r\n :setFields=\"setFields\"\r\n :getModel=\"getModel\"\r\n :clearValidation=\"clearValidation\"\r\n >\r\n <NSpace>\r\n <NButton type=\"primary\" @click=\"handleSubmit\">提交</NButton>\r\n <NButton @click=\"handleReset\">重置</NButton>\r\n </NSpace>\r\n </slot>\r\n </NFormItem>\r\n </NForm>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport {\r\n computed,\r\n ref,\r\n watch,\r\n getCurrentInstance,\r\n resolveComponent,\r\n} from \"vue\";\r\nimport type { FormInst } from \"naive-ui/es/form\";\r\nimport { NForm, NFormItem, NButton, NSpace } from \"naive-ui\";\r\nimport type {\r\n FormOption,\r\n LayoutType,\r\n LayoutConfig,\r\n SubmitEventPayload,\r\n FormModel,\r\n} from \"./types\";\r\nimport {\r\n type FormConfig,\r\n resolveFormConfig,\r\n shouldShowActions as calcShowActions,\r\n} from \"./composables/useFormConfig\";\r\nimport { useFormState } from \"./composables/useFormState\";\r\nimport {\r\n useFormRenderer,\r\n type ComponentMap,\r\n} from \"./composables/useFormRenderer\";\r\n\r\n/* ===== 布局组件静态映射(取代 DynamicComponent) ===== */\r\nimport DefaultLayout from \"./layouts/Default/index.vue\";\r\nimport InlineLayout from \"./layouts/Inline/index.vue\";\r\nimport GridLayout from \"./layouts/Grid/index.vue\";\r\nimport CardLayout from \"./layouts/Card/index.vue\";\r\nimport TabsLayout from \"./layouts/Tabs/index.vue\";\r\nimport StepsLayout from \"./layouts/Steps/index.vue\";\r\nimport DynamicLayout from \"./layouts/Dynamic/index.vue\";\r\nimport CustomLayout from \"./layouts/Custom/index.vue\";\r\n\r\ndefineOptions({ name: \"C_Form\" });\r\n\r\nconst LAYOUT_MAP: Record<LayoutType, any> = {\r\n default: DefaultLayout,\r\n inline: InlineLayout,\r\n grid: GridLayout,\r\n card: CardLayout,\r\n tabs: TabsLayout,\r\n steps: StepsLayout,\r\n dynamic: DynamicLayout,\r\n custom: CustomLayout,\r\n} as const;\r\n\r\n/* ===== Naive UI 组件解析映射 ===== */\r\n/*\r\n * 必须在 <script setup> 中调用 resolveComponent,\r\n * unplugin-vue-components 只在 .vue SFC 中转换这些调用\r\n */\r\nconst COMPONENT_MAP: ComponentMap = {\r\n NFormItem: resolveComponent(\"NFormItem\"),\r\n NInput: resolveComponent(\"NInput\"),\r\n NInputNumber: resolveComponent(\"NInputNumber\"),\r\n NSwitch: resolveComponent(\"NSwitch\"),\r\n NSlider: resolveComponent(\"NSlider\"),\r\n NRate: resolveComponent(\"NRate\"),\r\n NDatePicker: resolveComponent(\"NDatePicker\"),\r\n NTimePicker: resolveComponent(\"NTimePicker\"),\r\n NCascader: resolveComponent(\"NCascader\"),\r\n NColorPicker: resolveComponent(\"NColorPicker\"),\r\n NSelect: resolveComponent(\"NSelect\"),\r\n NCheckboxGroup: resolveComponent(\"NCheckboxGroup\"),\r\n NCheckbox: resolveComponent(\"NCheckbox\"),\r\n NRadioGroup: resolveComponent(\"NRadioGroup\"),\r\n NRadio: resolveComponent(\"NRadio\"),\r\n NUpload: resolveComponent(\"NUpload\"),\r\n NButton: resolveComponent(\"NButton\"),\r\n NSpace: resolveComponent(\"NSpace\"),\r\n C_Editor: resolveComponent(\"C_Editor\"),\r\n} as ComponentMap;\r\n\r\n/* ================= 组件属性定义 ================= */\r\n\r\ninterface CFormProps {\r\n /** 字段配置数组 */\r\n options: FormOption[];\r\n /** 双向绑定表单数据 */\r\n modelValue?: FormModel;\r\n /** 统一配置对象(收拢原先 13 个分散 Props) */\r\n config?: FormConfig;\r\n}\r\n\r\nconst props = withDefaults(defineProps<CFormProps>(), {\r\n config: () => ({}),\r\n});\r\n\r\n/* ================= 组件事件定义(从 16 个精简到 4 个) ================= */\r\n\r\nconst emit = defineEmits<{\r\n submit: [payload: SubmitEventPayload];\r\n \"update:modelValue\": [model: FormModel];\r\n \"validate-success\": [model: FormModel];\r\n \"validate-error\": [errors: unknown];\r\n}>();\r\n\r\n/* ================= 配置解析 ================= */\r\n\r\nconst resolved = computed(() => resolveFormConfig(props.config));\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst formRef = ref<FormInst | null>(null);\r\nconst optionsRef = computed(() => props.options);\r\n\r\n/* ===== 状态引擎 ===== */\r\nconst {\r\n formModel,\r\n formRules,\r\n visibleOptions,\r\n initialize,\r\n handleFieldChange,\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n handleSubmit,\r\n handleReset,\r\n} = useFormState(optionsRef, resolved, formRef, emit);\r\n\r\n/* ===== 渲染引擎 ===== */\r\nconst currentInstance = getCurrentInstance();\r\nconst { formItems } = useFormRenderer(\r\n formModel,\r\n visibleOptions,\r\n resolved,\r\n handleFieldChange,\r\n COMPONENT_MAP,\r\n currentInstance?.slots,\r\n);\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst layoutComponent = computed(\r\n () => LAYOUT_MAP[resolved.value.layout] || LAYOUT_MAP.default,\r\n);\r\n\r\nconst mergedLayoutConfig = computed<LayoutConfig>(() => ({\r\n type: resolved.value.layout,\r\n grid: resolved.value.grid,\r\n inline: resolved.value.inline,\r\n card: resolved.value.card,\r\n tabs: resolved.value.tabs,\r\n steps: resolved.value.steps,\r\n dynamic: resolved.value.dynamic,\r\n custom: resolved.value.custom,\r\n}));\r\n\r\nconst showActions = computed(() => calcShowActions(resolved.value));\r\n\r\n/* ================= 布局事件 → config 回调桥接 ================= */\r\n\r\n/** 通用布局事件桥接 */\r\nconst handleLayoutEvent = (callbackName: string, ...args: any[]) => {\r\n const callback = (resolved.value as any)[callbackName];\r\n callback?.(...args);\r\n};\r\n\r\n/** 字段变化事件(保留回调通道) */\r\nconst handleFieldsChange = (fields: FormOption[]): void => {\r\n resolved.value.onFieldsChange?.(fields);\r\n};\r\n\r\n/** Steps 布局事件 — 需要多参数特殊处理 */\r\nconst handleStepChange = (stepIndex: number, stepKey: string): void => {\r\n resolved.value.onStepChange?.(stepIndex, stepKey);\r\n};\r\n\r\nconst handleStepBeforeChange = async (\r\n currentStep: number,\r\n targetStep: number,\r\n): Promise<boolean> => {\r\n resolved.value.onStepBeforeChange?.(currentStep, targetStep);\r\n return true;\r\n};\r\n\r\nconst handleStepValidate = async (stepIndex: number): Promise<boolean> => {\r\n try {\r\n const currentStepKey = resolved.value.steps?.steps?.[stepIndex]?.key;\r\n if (!currentStepKey) return true;\r\n\r\n const stepFields = props.options\r\n .filter((option) => option.layout?.step === currentStepKey)\r\n .map((option) => option.prop);\r\n\r\n if (stepFields.length === 0) return true;\r\n\r\n await validateField(stepFields);\r\n resolved.value.onStepValidate?.(stepIndex);\r\n return true;\r\n } catch (error) {\r\n console.warn(`[C_Form] 步骤 ${stepIndex} 验证失败:`, error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= modelValue 双向同步 ================= */\r\n\r\nwatch(\r\n () => props.modelValue,\r\n (val) => {\r\n if (val) Object.assign(formModel, val);\r\n },\r\n { immediate: true, deep: true },\r\n);\r\n\r\n/* ================= 组件暴露 ================= */\r\n\r\ndefineExpose({\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n formRef,\r\n formModel,\r\n initialize,\r\n layoutType: computed(() => resolved.value.layout),\r\n shouldShowDefaultActions: showActions,\r\n});\r\n</script>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: 表单组件 — 薄 UI 壳 + 厚 Composable 引擎\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <NForm\r\n ref=\"formRef\"\r\n :model=\"formModel\"\r\n :rules=\"formRules\"\r\n :validate-on-rule-change=\"false\"\r\n :label-placement=\"resolved.labelPlacement\"\r\n :label-width=\"resolved.labelWidth\"\r\n :size=\"resolved.size\"\r\n :disabled=\"resolved.disabled\"\r\n :readonly=\"resolved.readonly\"\r\n v-bind=\"$attrs\"\r\n >\r\n <!-- 布局组件渲染 -->\r\n <component\r\n :is=\"layoutComponent\"\r\n :form-items=\"formItems\"\r\n :layout-config=\"mergedLayoutConfig\"\r\n :options=\"visibleOptions\"\r\n :form-data=\"formModel\"\r\n @tab-change=\"handleLayoutEvent('onTabChange', $event)\"\r\n @tab-validate=\"handleLayoutEvent('onTabValidate', $event)\"\r\n @step-change=\"handleStepChange\"\r\n @step-before-change=\"handleStepBeforeChange\"\r\n @step-validate=\"handleStepValidate\"\r\n @field-add=\"handleLayoutEvent('onFieldAdd', $event)\"\r\n @field-remove=\"handleLayoutEvent('onFieldRemove', $event)\"\r\n @field-toggle=\"\r\n (id: string, visible: boolean) => resolved.onFieldToggle?.(id, visible)\r\n \"\r\n @fields-clear=\"handleLayoutEvent('onFieldsClear')\"\r\n @render-mode-change=\"handleLayoutEvent('onRenderModeChange', $event)\"\r\n @group-toggle=\"\r\n (key: string, collapsed: boolean) =>\r\n resolved.onGroupToggle?.(key, collapsed)\r\n \"\r\n @group-reset=\"handleLayoutEvent('onGroupReset', $event)\"\r\n @validate-success=\"(model: FormModel) => emit('validate-success', model)\"\r\n @validate-error=\"(errors: unknown) => emit('validate-error', errors)\"\r\n @fields-change=\"handleFieldsChange\"\r\n />\r\n\r\n <!-- 表单操作按钮区域(只在特定布局中显示) -->\r\n <NFormItem v-if=\"showActions\" style=\"margin-top: 20px\">\r\n <slot\r\n name=\"action\"\r\n :form=\"formRef\"\r\n :model=\"formModel\"\r\n :validate=\"validate\"\r\n :validateField=\"validateField\"\r\n :reset=\"resetFields\"\r\n :setFields=\"setFields\"\r\n :getModel=\"getModel\"\r\n :clearValidation=\"clearValidation\"\r\n >\r\n <NSpace>\r\n <NButton type=\"primary\" @click=\"handleSubmit\">提交</NButton>\r\n <NButton @click=\"handleReset\">重置</NButton>\r\n </NSpace>\r\n </slot>\r\n </NFormItem>\r\n </NForm>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport {\r\n computed,\r\n ref,\r\n watch,\r\n getCurrentInstance,\r\n resolveComponent,\r\n} from \"vue\";\r\nimport type { FormInst } from \"naive-ui/es/form\";\r\nimport { NForm, NFormItem, NButton, NSpace } from \"naive-ui\";\r\nimport type {\r\n FormOption,\r\n LayoutType,\r\n LayoutConfig,\r\n SubmitEventPayload,\r\n FormModel,\r\n} from \"./types\";\r\nimport {\r\n type FormConfig,\r\n resolveFormConfig,\r\n shouldShowActions as calcShowActions,\r\n} from \"./composables/useFormConfig\";\r\nimport { useFormState } from \"./composables/useFormState\";\r\nimport {\r\n useFormRenderer,\r\n type ComponentMap,\r\n} from \"./composables/useFormRenderer\";\r\n\r\n/* ===== 布局组件静态映射(取代 DynamicComponent) ===== */\r\nimport DefaultLayout from \"./layouts/Default/index.vue\";\r\nimport InlineLayout from \"./layouts/Inline/index.vue\";\r\nimport GridLayout from \"./layouts/Grid/index.vue\";\r\nimport CardLayout from \"./layouts/Card/index.vue\";\r\nimport TabsLayout from \"./layouts/Tabs/index.vue\";\r\nimport StepsLayout from \"./layouts/Steps/index.vue\";\r\nimport DynamicLayout from \"./layouts/Dynamic/index.vue\";\r\nimport CustomLayout from \"./layouts/Custom/index.vue\";\r\n\r\ndefineOptions({ name: \"C_Form\" });\r\n\r\nconst LAYOUT_MAP: Record<LayoutType, any> = {\r\n default: DefaultLayout,\r\n inline: InlineLayout,\r\n grid: GridLayout,\r\n card: CardLayout,\r\n tabs: TabsLayout,\r\n steps: StepsLayout,\r\n dynamic: DynamicLayout,\r\n custom: CustomLayout,\r\n} as const;\r\n\r\n/* ===== Naive UI 组件解析映射 ===== */\r\n/*\r\n * 必须在 <script setup> 中调用 resolveComponent,\r\n * unplugin-vue-components 只在 .vue SFC 中转换这些调用\r\n */\r\nconst COMPONENT_MAP: ComponentMap = {\r\n NFormItem: resolveComponent(\"NFormItem\"),\r\n NInput: resolveComponent(\"NInput\"),\r\n NInputNumber: resolveComponent(\"NInputNumber\"),\r\n NSwitch: resolveComponent(\"NSwitch\"),\r\n NSlider: resolveComponent(\"NSlider\"),\r\n NRate: resolveComponent(\"NRate\"),\r\n NDatePicker: resolveComponent(\"NDatePicker\"),\r\n NTimePicker: resolveComponent(\"NTimePicker\"),\r\n NCascader: resolveComponent(\"NCascader\"),\r\n NColorPicker: resolveComponent(\"NColorPicker\"),\r\n NSelect: resolveComponent(\"NSelect\"),\r\n NCheckboxGroup: resolveComponent(\"NCheckboxGroup\"),\r\n NCheckbox: resolveComponent(\"NCheckbox\"),\r\n NRadioGroup: resolveComponent(\"NRadioGroup\"),\r\n NRadio: resolveComponent(\"NRadio\"),\r\n NUpload: resolveComponent(\"NUpload\"),\r\n NButton: resolveComponent(\"NButton\"),\r\n NSpace: resolveComponent(\"NSpace\"),\r\n C_Editor: resolveComponent(\"C_Editor\"),\r\n} as ComponentMap;\r\n\r\n/* ================= 组件属性定义 ================= */\r\n\r\ninterface CFormProps {\r\n /** 字段配置数组 */\r\n options: FormOption[];\r\n /** 双向绑定表单数据 */\r\n modelValue?: FormModel;\r\n /** 统一配置对象(收拢原先 13 个分散 Props) */\r\n config?: FormConfig;\r\n}\r\n\r\nconst props = withDefaults(defineProps<CFormProps>(), {\r\n config: () => ({}),\r\n});\r\n\r\n/* ================= 组件事件定义(从 16 个精简到 4 个) ================= */\r\n\r\nconst emit = defineEmits<{\r\n submit: [payload: SubmitEventPayload];\r\n \"update:modelValue\": [model: FormModel];\r\n \"validate-success\": [model: FormModel];\r\n \"validate-error\": [errors: unknown];\r\n}>();\r\n\r\n/* ================= 配置解析 ================= */\r\n\r\nconst resolved = computed(() => resolveFormConfig(props.config));\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst formRef = ref<FormInst | null>(null);\r\nconst optionsRef = computed(() => props.options);\r\n\r\n/* ===== 状态引擎 ===== */\r\nconst {\r\n formModel,\r\n formRules,\r\n visibleOptions,\r\n initialize,\r\n handleFieldChange,\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n handleSubmit,\r\n handleReset,\r\n} = useFormState(optionsRef, resolved, formRef, emit);\r\n\r\n/* ===== 渲染引擎 ===== */\r\nconst currentInstance = getCurrentInstance();\r\nconst { formItems } = useFormRenderer(\r\n formModel,\r\n visibleOptions,\r\n resolved,\r\n handleFieldChange,\r\n COMPONENT_MAP,\r\n currentInstance?.slots,\r\n);\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst layoutComponent = computed(\r\n () => LAYOUT_MAP[resolved.value.layout] || LAYOUT_MAP.default,\r\n);\r\n\r\nconst mergedLayoutConfig = computed<LayoutConfig>(() => ({\r\n type: resolved.value.layout,\r\n grid: resolved.value.grid,\r\n inline: resolved.value.inline,\r\n card: resolved.value.card,\r\n tabs: resolved.value.tabs,\r\n steps: resolved.value.steps,\r\n dynamic: resolved.value.dynamic,\r\n custom: resolved.value.custom,\r\n}));\r\n\r\nconst showActions = computed(() => calcShowActions(resolved.value));\r\n\r\n/* ================= 布局事件 → config 回调桥接 ================= */\r\n\r\n/** 通用布局事件桥接 */\r\nconst handleLayoutEvent = (callbackName: string, ...args: any[]) => {\r\n const callback = (resolved.value as any)[callbackName];\r\n callback?.(...args);\r\n};\r\n\r\n/** 字段变化事件(保留回调通道) */\r\nconst handleFieldsChange = (fields: FormOption[]): void => {\r\n resolved.value.onFieldsChange?.(fields);\r\n};\r\n\r\n/** Steps 布局事件 — 需要多参数特殊处理 */\r\nconst handleStepChange = (stepIndex: number, stepKey: string): void => {\r\n resolved.value.onStepChange?.(stepIndex, stepKey);\r\n};\r\n\r\nconst handleStepBeforeChange = async (\r\n currentStep: number,\r\n targetStep: number,\r\n): Promise<boolean> => {\r\n resolved.value.onStepBeforeChange?.(currentStep, targetStep);\r\n return true;\r\n};\r\n\r\nconst handleStepValidate = async (stepIndex: number): Promise<boolean> => {\r\n try {\r\n const currentStepKey = resolved.value.steps?.steps?.[stepIndex]?.key;\r\n if (!currentStepKey) return true;\r\n\r\n const stepFields = props.options\r\n .filter((option) => option.layout?.step === currentStepKey)\r\n .map((option) => option.prop);\r\n\r\n if (stepFields.length === 0) return true;\r\n\r\n await validateField(stepFields);\r\n resolved.value.onStepValidate?.(stepIndex);\r\n return true;\r\n } catch (error) {\r\n console.warn(`[C_Form] 步骤 ${stepIndex} 验证失败:`, error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= modelValue 双向同步 ================= */\r\n\r\nwatch(\r\n () => props.modelValue,\r\n (val) => {\r\n if (val) Object.assign(formModel, val);\r\n },\r\n { immediate: true, deep: true },\r\n);\r\n\r\n/* ================= 组件暴露 ================= */\r\n\r\ndefineExpose({\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n formRef,\r\n formModel,\r\n initialize,\r\n layoutType: computed(() => resolved.value.layout),\r\n shouldShowDefaultActions: showActions,\r\n});\r\n</script>\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"C_Form2.js","names":["formItems","formItems","formItems","formItems","formItems","formItems","formItems","$attrs"],"sources":["../src/components/C_Form/composables/useFormConfig.ts","../src/components/C_Form/composables/useFormState.ts","../src/components/C_Form/composables/useFormRenderer.ts","../src/components/C_Form/layouts/Default/index.vue","../src/components/C_Form/layouts/Default/index.vue","../src/components/C_Form/layouts/Default/index.vue","../src/components/C_Form/layouts/Inline/index.vue","../src/components/C_Form/layouts/Inline/index.vue","../src/components/C_Form/layouts/Inline/index.vue","../src/components/C_Form/layouts/Grid/index.vue","../src/components/C_Form/layouts/Grid/index.vue","../src/components/C_Form/layouts/Grid/index.vue","../src/components/C_Form/layouts/Card/index.vue","../src/components/C_Form/layouts/Card/index.vue","../src/components/C_Form/layouts/Card/index.vue","../src/components/C_Form/layouts/Tabs/index.vue","../src/components/C_Form/layouts/Tabs/index.vue","../src/components/C_Form/layouts/Tabs/index.vue","../src/components/C_Form/layouts/Steps/index.vue","../src/components/C_Form/layouts/Steps/index.vue","../src/components/C_Form/layouts/Steps/index.vue","../src/components/C_Form/composables/useDynamicFormState.ts","../src/components/C_Form/layouts/Dynamic/index.vue","../src/components/C_Form/layouts/Dynamic/index.vue","../src/components/C_Form/layouts/Dynamic/index.vue","../src/components/C_Form/layouts/Custom/index.vue","../src/components/C_Form/layouts/Custom/index.vue","../src/components/C_Form/layouts/Custom/index.vue","../src/components/C_Form/index.vue","../src/components/C_Form/index.vue","../src/components/C_Form/index.vue"],"sourcesContent":["/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: C_Form 配置解析 Composable — 统一 FormConfig 接口与默认值\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport type {\r\n LayoutType,\r\n LabelPlacement,\r\n GridLayoutConfig,\r\n InlineLayoutConfig,\r\n CardLayoutConfig,\r\n TabsLayoutConfig,\r\n StepsLayoutConfig,\r\n DynamicLayoutConfig,\r\n CustomLayoutConfig,\r\n DynamicFieldConfig,\r\n RenderMode,\r\n FormOption,\r\n} from \"../types\";\r\n\r\n/* =================== FormConfig 类型定义 =================== */\r\n\r\n/** 布局回调事件 — 替代原先 16 个 emit 中的纯透传事件 */\r\nexport interface LayoutCallbacks {\r\n /* tabs 布局回调 */\r\n onTabChange?: (tabKey: string, tabIndex: number) => void;\r\n onTabValidate?: (tabKey: string) => void;\r\n\r\n /* steps 布局回调 */\r\n onStepChange?: (stepIndex: number, stepKey: string) => void;\r\n onStepBeforeChange?: (currentStep: number, targetStep: number) => void;\r\n onStepValidate?: (stepIndex: number) => void;\r\n\r\n /* dynamic 布局回调 */\r\n onFieldAdd?: (fieldConfig: DynamicFieldConfig) => void;\r\n onFieldRemove?: (fieldId: string) => void;\r\n onFieldToggle?: (fieldId: string, visible: boolean) => void;\r\n onFieldsClear?: () => void;\r\n\r\n /* custom 布局回调 */\r\n onRenderModeChange?: (mode: RenderMode) => void;\r\n onGroupToggle?: (groupKey: string, collapsed: boolean) => void;\r\n onGroupReset?: (groupKey: string) => void;\r\n onFieldsChange?: (fields: FormOption[]) => void;\r\n}\r\n\r\n/**\r\n * C_Form 统一配置接口\r\n * @description 收拢原先 13 个分散 Props 为 1 个 config 对象\r\n * 默认值均在 FORM_DEFAULTS 中集中管理\r\n */\r\nexport interface FormConfig extends LayoutCallbacks {\r\n /** 布局类型,默认 'default' */\r\n layout?: LayoutType;\r\n /** 标签位置,默认 'left' */\r\n labelPlacement?: LabelPlacement;\r\n /** 标签宽度,默认 'auto' */\r\n labelWidth?: string | number;\r\n /** 尺寸,默认 'medium' */\r\n size?: \"small\" | \"medium\" | \"large\";\r\n /** 禁用整个表单,默认 false */\r\n disabled?: boolean;\r\n /** 只读,默认 false */\r\n readonly?: boolean;\r\n /** 显示默认提交/重置按钮,默认 true */\r\n showActions?: boolean;\r\n /** 值变化时自动校验,默认 false */\r\n validateOnChange?: boolean;\r\n\r\n /* ===== 布局级配置 ===== */\r\n grid?: GridLayoutConfig;\r\n inline?: InlineLayoutConfig;\r\n card?: CardLayoutConfig;\r\n tabs?: TabsLayoutConfig;\r\n steps?: StepsLayoutConfig;\r\n dynamic?: DynamicLayoutConfig;\r\n custom?: CustomLayoutConfig;\r\n}\r\n\r\n/** 解析后的配置(所有必填字段均已设置默认值) */\r\nexport interface ResolvedFormConfig extends Required<\r\n Omit<\r\n FormConfig,\r\n | keyof LayoutCallbacks\r\n | \"grid\"\r\n | \"inline\"\r\n | \"card\"\r\n | \"tabs\"\r\n | \"steps\"\r\n | \"dynamic\"\r\n | \"custom\"\r\n >\r\n> {\r\n /* 布局配置保留可选,因为只有对应 layout 时才有值 */\r\n grid?: GridLayoutConfig;\r\n inline?: InlineLayoutConfig;\r\n card?: CardLayoutConfig;\r\n tabs?: TabsLayoutConfig;\r\n steps?: StepsLayoutConfig;\r\n dynamic?: DynamicLayoutConfig;\r\n custom?: CustomLayoutConfig;\r\n /* 回调保留可选 */\r\n onTabChange?: LayoutCallbacks[\"onTabChange\"];\r\n onTabValidate?: LayoutCallbacks[\"onTabValidate\"];\r\n onStepChange?: LayoutCallbacks[\"onStepChange\"];\r\n onStepBeforeChange?: LayoutCallbacks[\"onStepBeforeChange\"];\r\n onStepValidate?: LayoutCallbacks[\"onStepValidate\"];\r\n onFieldAdd?: LayoutCallbacks[\"onFieldAdd\"];\r\n onFieldRemove?: LayoutCallbacks[\"onFieldRemove\"];\r\n onFieldToggle?: LayoutCallbacks[\"onFieldToggle\"];\r\n onFieldsClear?: LayoutCallbacks[\"onFieldsClear\"];\r\n onRenderModeChange?: LayoutCallbacks[\"onRenderModeChange\"];\r\n onGroupToggle?: LayoutCallbacks[\"onGroupToggle\"];\r\n onGroupReset?: LayoutCallbacks[\"onGroupReset\"];\r\n onFieldsChange?: LayoutCallbacks[\"onFieldsChange\"];\r\n}\r\n\r\n/* =================== 默认值常量 =================== */\r\n\r\nexport const FORM_DEFAULTS: ResolvedFormConfig = {\r\n layout: \"default\",\r\n labelPlacement: \"left\",\r\n labelWidth: \"auto\",\r\n size: \"medium\",\r\n disabled: false,\r\n readonly: false,\r\n showActions: true,\r\n validateOnChange: false,\r\n} as const;\r\n\r\n/** 拥有自身控制按钮的布局(不显示默认操作按钮) */\r\nexport const LAYOUTS_WITH_OWN_CONTROLS: readonly LayoutType[] = [\r\n \"steps\",\r\n \"custom\",\r\n] as const;\r\n\r\n/* =================== 核心函数 =================== */\r\n\r\n/**\r\n * 解析 FormConfig,合并默认值\r\n * @param config - 用户传入的配置(可选)\r\n * @returns 具有完整默认值的 ResolvedFormConfig\r\n */\r\nexport function resolveFormConfig(config?: FormConfig): ResolvedFormConfig {\r\n return { ...FORM_DEFAULTS, ...config };\r\n}\r\n\r\n/**\r\n * 计算是否显示默认操作按钮\r\n * @param resolved - 已解析的配置\r\n */\r\nexport function shouldShowActions(resolved: ResolvedFormConfig): boolean {\r\n if (resolved.showActions === false) return false;\r\n if (LAYOUTS_WITH_OWN_CONTROLS.includes(resolved.layout)) return false;\r\n return true;\r\n}\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: C_Form 状态引擎 Composable — 表单数据、校验规则、验证 API\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport {\r\n reactive,\r\n computed,\r\n watch,\r\n nextTick,\r\n onMounted,\r\n type ComputedRef,\r\n type Ref,\r\n} from 'vue'\r\nimport type { FormInst, FormRules } from 'naive-ui/es/form'\r\nimport { mergeRules } from '@robot-admin/form-validate'\r\nimport type {\r\n FormOption,\r\n FormModel,\r\n ComponentType,\r\n SubmitEventPayload,\r\n} from '../types'\r\nimport type { ResolvedFormConfig } from './useFormConfig'\r\n\r\n/* =================== 默认值映射 =================== */\r\n\r\n/** 各控件类型的默认空值 */\r\nconst DEFAULT_VALUES: Record<ComponentType, unknown> = {\r\n input: '',\r\n textarea: '',\r\n editor: '',\r\n select: null,\r\n datePicker: null,\r\n daterange: null,\r\n timePicker: null,\r\n cascader: null,\r\n colorPicker: null,\r\n checkbox: null,\r\n upload: [],\r\n radio: '',\r\n inputNumber: null,\r\n slider: null,\r\n rate: null,\r\n switch: null,\r\n} as const\r\n\r\nconst getDefaultValue = (type: ComponentType): unknown => {\r\n return DEFAULT_VALUES[type] ?? null\r\n}\r\n\r\n/* =================== Composable =================== */\r\n\r\n/**\r\n * C_Form 状态引擎 — 管理表单数据模型、校验规则、验证 API、字段操作\r\n * @param options - 表单配置项(响应式)\r\n * @param config - 解析后的表单配置(响应式)\r\n * @param formRef - NForm 实例引用\r\n * @param emit - 组件事件发射器\r\n */\r\nexport function useFormState(\r\n options: ComputedRef<FormOption[]>,\r\n config: ComputedRef<ResolvedFormConfig>,\r\n formRef: Ref<FormInst | null>,\r\n emit: {\r\n (e: 'update:modelValue', model: FormModel): void\r\n (e: 'validate-success', model: FormModel): void\r\n (e: 'validate-error', errors: unknown): void\r\n (e: 'submit', payload: SubmitEventPayload): void\r\n }\r\n) {\r\n /* ===== 响应式状态 ===== */\r\n const formModel = reactive<FormModel>({})\r\n const formRules = reactive<FormRules>({})\r\n\r\n /* ===== 可见字段 ===== */\r\n const visibleOptions = computed(() =>\r\n options.value.filter(item => item.show !== false)\r\n )\r\n\r\n /* ===== 初始化 ===== */\r\n const initialize = (): void => {\r\n try {\r\n /* 清空现有规则 */\r\n Object.keys(formRules).forEach(key => delete formRules[key])\r\n\r\n /* 初始化表单数据和验证规则 */\r\n options.value.forEach(item => {\r\n /*\r\n * 只为新增字段设置默认值,保留已有字段的用户输入\r\n * 解决 options 依赖 formData 时的循环重置问题\r\n */\r\n if (!(item.prop in formModel)) {\r\n formModel[item.prop] =\r\n item.value !== undefined\r\n ? item.value\r\n : getDefaultValue(item.type as ComponentType)\r\n }\r\n\r\n if (item.rules?.length) {\r\n /*\r\n * mergeRules 内部只调用 rule.validator?.(),\r\n * 原生 naive-ui 声明式规则(如 { required: true })没有 validator 会被跳过。\r\n * 仅当所有规则都有 validator 时才走 mergeRules 串行验证,否则直接交给 naive-ui 处理。\r\n */\r\n const allHaveValidator = item.rules.every(\r\n r => typeof (r as Record<string, unknown>).validator === 'function'\r\n )\r\n formRules[item.prop] = allHaveValidator\r\n ? mergeRules(item.rules)\r\n : item.rules\r\n }\r\n })\r\n } catch (error) {\r\n console.error('[C_Form] 初始化失败:', error)\r\n }\r\n }\r\n\r\n /* ===== 字段变化处理 ===== */\r\n const handleFieldChange = (field: string): void => {\r\n if (config.value.validateOnChange) {\r\n nextTick(() => {\r\n validateField(field).catch(() => {})\r\n })\r\n }\r\n }\r\n\r\n /* ===== 验证 API ===== */\r\n const validate = async (): Promise<void> => {\r\n if (!formRef.value) {\r\n throw new Error('[C_Form] 表单引用不存在')\r\n }\r\n\r\n try {\r\n await formRef.value.validate()\r\n emit('validate-success', getModel())\r\n } catch (errors) {\r\n emit('validate-error', errors)\r\n throw errors\r\n }\r\n }\r\n\r\n const validateField = async (field: string | string[]): Promise<void> => {\r\n if (!formRef.value) {\r\n throw new Error('[C_Form] 表单引用不存在')\r\n }\r\n\r\n const fields = Array.isArray(field) ? field : [field]\r\n\r\n /*\r\n * naive-ui 的 Form.validate() 不支持直接传字段名数组,\r\n * 需要验证整个表单后过滤出目标字段的错误\r\n */\r\n try {\r\n await formRef.value.validate()\r\n } catch (allErrors: unknown) {\r\n /* allErrors 是 ValidateError[][] — 每个错误数组含 field 信息 */\r\n if (!Array.isArray(allErrors)) throw allErrors\r\n\r\n /* 过滤出目标字段的错误 */\r\n const targetErrors = allErrors.filter((errorGroup: unknown[]) =>\r\n errorGroup?.some(err => {\r\n const e = err as Record<string, unknown>\r\n return typeof e.field === 'string' && fields.includes(e.field)\r\n })\r\n )\r\n\r\n if (targetErrors.length > 0) {\r\n throw targetErrors\r\n }\r\n /* 目标字段无错误,其他字段的错误忽略 */\r\n }\r\n }\r\n\r\n const clearValidation = (field?: string | string[]): void => {\r\n if (!formRef.value) return\r\n\r\n if (field) {\r\n const fields = Array.isArray(field) ? field : [field]\r\n fields.forEach(fieldName => {\r\n if (formModel[fieldName] !== undefined) {\r\n const currentValue = formModel[fieldName]\r\n formModel[fieldName] = currentValue\r\n }\r\n })\r\n } else {\r\n formRef.value.restoreValidation()\r\n }\r\n }\r\n\r\n const validateByFilter = async (\r\n filterFn: (option: FormOption) => boolean,\r\n context: string\r\n ): Promise<boolean> => {\r\n try {\r\n const fields = options.value.filter(filterFn).map(option => option.prop)\r\n if (fields.length === 0) return true\r\n await validateField(fields)\r\n return true\r\n } catch (error) {\r\n console.warn(`[C_Form] ${context}验证失败:`, error)\r\n return false\r\n }\r\n }\r\n\r\n const validateStep = async (stepIndex: number): Promise<boolean> => {\r\n const stepKey = config.value.steps?.steps?.[stepIndex]?.key\r\n if (!stepKey) return true\r\n\r\n return validateByFilter(\r\n option => option.layout?.step === stepKey,\r\n `步骤 ${stepIndex} `\r\n )\r\n }\r\n\r\n const validateTab = async (tabKey: string): Promise<boolean> => {\r\n return validateByFilter(\r\n option => option.layout?.tab === tabKey,\r\n `标签页 ${tabKey} `\r\n )\r\n }\r\n\r\n const validateDynamicFields = async (): Promise<boolean> => {\r\n return validateByFilter(\r\n option => Boolean(option.layout?.dynamic),\r\n '动态字段 '\r\n )\r\n }\r\n\r\n const validateCustomGroup = async (groupKey: string): Promise<boolean> => {\r\n return validateByFilter(\r\n option => option.layout?.group === groupKey,\r\n `自定义分组 ${groupKey} `\r\n )\r\n }\r\n\r\n /* ===== 数据 API ===== */\r\n const getModel = (): FormModel => ({ ...formModel })\r\n\r\n const setFields = (fields: FormModel): void => {\r\n Object.assign(formModel, fields)\r\n }\r\n\r\n const resetFields = (): void => {\r\n try {\r\n clearValidation()\r\n\r\n options.value.forEach(item => {\r\n const defaultValue =\r\n item.value !== undefined\r\n ? item.value\r\n : getDefaultValue(item.type as ComponentType)\r\n\r\n formModel[item.prop] = defaultValue\r\n })\r\n } catch (error) {\r\n console.error('[C_Form] 重置表单失败:', error)\r\n }\r\n }\r\n\r\n const setFieldValue = async (\r\n field: string,\r\n value: unknown,\r\n shouldValidate: boolean = false\r\n ): Promise<void> => {\r\n formModel[field] = value\r\n if (shouldValidate) {\r\n await validateField(field)\r\n }\r\n }\r\n\r\n const getFieldValue = (field: string): unknown => formModel[field]\r\n\r\n const setFieldsValue = async (\r\n fields: FormModel,\r\n shouldValidate: boolean = false\r\n ): Promise<void> => {\r\n Object.assign(formModel, fields)\r\n if (shouldValidate) {\r\n await validate()\r\n }\r\n }\r\n\r\n /* ===== 提交 & 重置 ===== */\r\n const handleSubmit = async (): Promise<void> => {\r\n try {\r\n await validate()\r\n emit('submit', { model: getModel(), form: formRef.value! })\r\n } catch (error) {\r\n console.warn('[C_Form] 表单验证失败:', error)\r\n }\r\n }\r\n\r\n /* ===== 生命周期 ===== */\r\n onMounted(() => {\r\n initialize()\r\n\r\n watch(\r\n () => options.value,\r\n () => initialize(),\r\n { deep: true }\r\n )\r\n\r\n watch(\r\n () => formModel,\r\n val => emit('update:modelValue', { ...val }),\r\n { deep: true }\r\n )\r\n })\r\n\r\n return {\r\n formModel,\r\n formRules,\r\n visibleOptions,\r\n initialize,\r\n handleFieldChange,\r\n /* 验证 API */\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n /* 数据 API */\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n /* 操作 */\r\n handleSubmit,\r\n handleReset: resetFields,\r\n }\r\n}\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: C_Form 渲染引擎 Composable — 统一组件注册表 + formItems 生成\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport { computed, h, type VNode, type ComputedRef, type Component } from \"vue\";\r\n\r\nimport type { FormOption, FormModel, OptionItem } from \"../types\";\r\nimport type { ResolvedFormConfig } from \"./useFormConfig\";\r\n\r\n/* =================== 组件映射类型 =================== */\r\n\r\n/** 组件映射表 — 由调用方(C_Form)注入已解析的组件引用 */\r\nexport type ComponentMap = Record<string, Component>;\r\n\r\n/* =================== 渲染器类型 =================== */\r\n\r\n/** 单个控件的渲染函数签名 */\r\nexport type FormRenderer = (\r\n baseProps: Record<string, any>,\r\n item: FormOption,\r\n config: ResolvedFormConfig,\r\n ctx?: { slots?: Record<string, any>; components?: ComponentMap },\r\n) => VNode | null;\r\n\r\n/* =================== 渲染器工厂 =================== */\r\n\r\n/**\r\n * 构建渲染器注册表\r\n * @param C - 组件映射表(由 C_Form 直接 import naive-ui 组件后注入)\r\n */\r\nfunction buildRenderers(C: ComponentMap): Record<string, FormRenderer> {\r\n return {\r\n /* ===== 基础控件 ===== */\r\n input: (props) => h(C.NInput, { ...props }),\r\n textarea: (props) => h(C.NInput, { ...props, type: \"textarea\" }),\r\n inputNumber: (props) => h(C.NInputNumber, { ...props }),\r\n switch: (props) => h(C.NSwitch, { ...props }),\r\n slider: (props) => h(C.NSlider, { ...props }),\r\n rate: (props) => h(C.NRate, { ...props }),\r\n datePicker: (props) => h(C.NDatePicker, { ...props }),\r\n daterange: (props) => h(C.NDatePicker, { ...props, type: \"daterange\" }),\r\n timePicker: (props) => h(C.NTimePicker, { ...props }),\r\n cascader: (props) => h(C.NCascader, { ...props }),\r\n colorPicker: (props) => h(C.NColorPicker, { ...props }),\r\n\r\n /* ===== 复杂控件 — 带子元素/插槽 ===== */\r\n select: (baseProps, item) =>\r\n h(C.NSelect, {\r\n ...baseProps,\r\n options:\r\n item.children?.map((child: OptionItem) => ({\r\n value: child.value,\r\n label: child.label,\r\n disabled: child.disabled,\r\n })) || [],\r\n }),\r\n\r\n checkbox: (baseProps, item) =>\r\n h(\r\n C.NCheckboxGroup,\r\n { ...baseProps },\r\n {\r\n default: () =>\r\n h(\r\n C.NSpace,\r\n {},\r\n {\r\n default: () =>\r\n item.children?.map((child: OptionItem) =>\r\n h(C.NCheckbox, {\r\n value: child.value,\r\n label: child.label,\r\n disabled: child.disabled,\r\n key: String(child.value),\r\n }),\r\n ) || [],\r\n },\r\n ),\r\n },\r\n ),\r\n\r\n radio: (baseProps, item) =>\r\n h(\r\n C.NRadioGroup,\r\n { ...baseProps },\r\n {\r\n default: () =>\r\n h(\r\n C.NSpace,\r\n {},\r\n {\r\n default: () =>\r\n item.children?.map((child: OptionItem) =>\r\n h(\r\n C.NRadio,\r\n {\r\n value: child.value,\r\n disabled: child.disabled,\r\n key: String(child.value),\r\n },\r\n { default: () => child.label },\r\n ),\r\n ) || [],\r\n },\r\n ),\r\n },\r\n ),\r\n\r\n upload: (baseProps, item, _config, ctx) =>\r\n h(\r\n C.NUpload,\r\n {\r\n fileList: baseProps.value || [],\r\n \"onUpdate:fileList\": (fileList: unknown[]) => {\r\n baseProps[\"onUpdate:value\"]?.(fileList);\r\n },\r\n ...item.attrs,\r\n },\r\n {\r\n trigger: () =>\r\n ctx?.slots?.[\"uploadClick\"]?.() ||\r\n h(C.NButton, { type: \"primary\" }, { default: () => \"选择文件\" }),\r\n tip: () => ctx?.slots?.[\"uploadTip\"]?.() || null,\r\n },\r\n ),\r\n\r\n editor: (baseProps, item, config) =>\r\n h(C.C_Editor, {\r\n editorId: `editor-${item.prop}`,\r\n modelValue: baseProps.value || \"\",\r\n placeholder: item.placeholder,\r\n disabled: config.disabled,\r\n readonly: config.readonly,\r\n \"onUpdate:modelValue\": (value: string) => {\r\n baseProps[\"onUpdate:value\"]?.(value);\r\n },\r\n ...item.attrs,\r\n }),\r\n };\r\n}\r\n\r\n/** 自定义渲染器扩展存储 */\r\nconst customRenderers: Record<string, FormRenderer> = {};\r\n\r\n/**\r\n * 运行时注册自定义渲染器 — 开闭原则\r\n * @param type - 控件类型名\r\n * @param renderer - 渲染函数\r\n */\r\nexport function registerRenderer(type: string, renderer: FormRenderer) {\r\n customRenderers[type] = renderer;\r\n}\r\n\r\n/* =================== Composable =================== */\r\n\r\n/**\r\n * 渲染引擎 Composable — 生成 formItems VNode 数组\r\n * @param componentMap - 已解析的组件映射表(由 C_Form 的 <script setup> 提供)\r\n */\r\nexport function useFormRenderer(\r\n formModel: FormModel,\r\n visibleOptions: ComputedRef<FormOption[]>,\r\n config: ComputedRef<ResolvedFormConfig>,\r\n handleFieldChange: (field: string) => void,\r\n componentMap: ComponentMap,\r\n instanceSlots?: Record<string, any>,\r\n) {\r\n /* 构建渲染器注册表(注入已解析的组件引用) */\r\n const renderers = { ...buildRenderers(componentMap), ...customRenderers };\r\n\r\n /**\r\n * 为指定表单项生成基础 props(双向绑定 + 占位符)\r\n */\r\n const getBaseProps = (item: FormOption): Record<string, unknown> => {\r\n const baseProps: Record<string, unknown> = {\r\n value: formModel[item.prop],\r\n \"onUpdate:value\": (value: unknown) => {\r\n formModel[item.prop] = value;\r\n handleFieldChange(item.prop);\r\n },\r\n };\r\n\r\n if (item.type === \"textarea\") {\r\n baseProps.type = \"textarea\";\r\n }\r\n\r\n if (item.placeholder) {\r\n baseProps.placeholder = item.placeholder;\r\n }\r\n\r\n return baseProps;\r\n };\r\n\r\n /**\r\n * 渲染单个表单项控件\r\n */\r\n const renderFormItem = (item: FormOption): VNode | null => {\r\n try {\r\n const renderer = renderers[item.type];\r\n if (!renderer) {\r\n console.warn(`[C_Form] 未支持的组件类型: ${item.type}`);\r\n return null;\r\n }\r\n\r\n const baseProps = getBaseProps(item);\r\n return renderer({ ...baseProps, ...item.attrs }, item, config.value, {\r\n slots: instanceSlots,\r\n components: componentMap,\r\n });\r\n } catch (error) {\r\n console.error(`[C_Form] 渲染表单项失败:`, error, item);\r\n return null;\r\n }\r\n };\r\n\r\n /**\r\n * formItems: 各布局组件接收的 VNode[] ,每个 VNode 是一个 NFormItem\r\n */\r\n const formItems = computed(() =>\r\n visibleOptions.value.map((item) =>\r\n h(\r\n componentMap.NFormItem,\r\n {\r\n label: item.label,\r\n path: item.prop,\r\n key: item.prop,\r\n required: !!item.rules?.length,\r\n },\r\n {\r\n default: () => renderFormItem(item),\r\n },\r\n ),\r\n ),\r\n );\r\n\r\n return { renderFormItem, formItems, getBaseProps };\r\n}\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 卡片布局 - 基础默认布局\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-default\">\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { type VNode } from \"vue\";\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: any;\r\n options?: any[];\r\n}\r\n\r\ndefineProps<Props>();\r\n</script>\r\n\r\n<style scoped>\r\n.c-form-default {\r\n width: 100%;\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 卡片布局 - 基础默认布局\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-default\">\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { type VNode } from \"vue\";\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: any;\r\n options?: any[];\r\n}\r\n\r\ndefineProps<Props>();\r\n</script>\r\n\r\n<style scoped>\r\n.c-form-default {\r\n width: 100%;\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 卡片布局 - 基础默认布局\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-default\">\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { type VNode } from \"vue\";\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: any;\r\n options?: any[];\r\n}\r\n\r\ndefineProps<Props>();\r\n</script>\r\n\r\n<style scoped>\r\n.c-form-default {\r\n width: 100%;\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 内联布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-inline\" :style=\"containerStyle\">\r\n <div\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n class=\"c-form-inline-item\"\r\n :style=\"getItemStyle(index)\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport type { VNode, CSSProperties } from \"vue\";\r\nimport type { LayoutProps, FormOption, ItemLayoutConfig } from \"../../types\";\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<LayoutProps>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\n/**\r\n * 获取统一项目宽度\r\n */\r\nconst itemWidth = computed((): number => 280);\r\n\r\n/**\r\n * 获取项目间距\r\n */\r\nconst gap = computed((): number => {\r\n return props.layoutConfig?.inline?.gap ?? 16;\r\n});\r\n\r\n/**\r\n * 获取行间距\r\n */\r\nconst rowGap = computed((): number => 16);\r\n\r\n/**\r\n * 获取对齐方式 - 既控制垂直对齐也控制水平对齐\r\n */\r\nconst align = computed((): \"start\" | \"center\" | \"end\" => {\r\n const alignValue = props.layoutConfig?.inline?.align;\r\n return alignValue === \"start\" || alignValue === \"end\" ? alignValue : \"center\";\r\n});\r\n\r\n/**\r\n * 获取垂直对齐样式\r\n */\r\nconst alignItems = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n default:\r\n return \"center\";\r\n }\r\n});\r\n\r\n/**\r\n * 获取水平对齐样式\r\n */\r\nconst justifyContent = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n case \"center\":\r\n return \"center\";\r\n default:\r\n return \"flex-start\";\r\n }\r\n});\r\n\r\n/**\r\n * 容器样式计算\r\n */\r\nconst containerStyle = computed((): CSSProperties => {\r\n return {\r\n display: \"flex\",\r\n flexWrap: \"wrap\",\r\n alignItems: alignItems.value,\r\n justifyContent: justifyContent.value,\r\n gap: `${rowGap.value}px ${gap.value}px`,\r\n width: \"100%\",\r\n };\r\n});\r\n\r\n/* ================= 方法 ================= */\r\n\r\n/**\r\n * 获取表单项的唯一key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as any;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `form-item-${index}`;\r\n};\r\n\r\n/**\r\n * 获取表单项样式\r\n */\r\nconst getItemStyle = (index: number): CSSProperties => {\r\n const option: FormOption | undefined = props.options?.[index];\r\n const layoutConfig: ItemLayoutConfig | undefined = option?.layout;\r\n\r\n /* 基础样式,统一宽度 */\r\n const baseStyle: CSSProperties = {\r\n width: `${itemWidth.value}px`,\r\n flexShrink: 0,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n };\r\n\r\n /* 如果单独设置了宽度,则覆盖统一宽度 */\r\n if (layoutConfig?.width !== undefined) {\r\n baseStyle.width =\r\n typeof layoutConfig.width === \"number\"\r\n ? `${layoutConfig.width}px`\r\n : layoutConfig.width;\r\n }\r\n\r\n /* 合并自定义样式 */\r\n if (layoutConfig?.style) {\r\n Object.assign(baseStyle, layoutConfig.style);\r\n }\r\n\r\n return baseStyle;\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 内联布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-inline\" :style=\"containerStyle\">\r\n <div\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n class=\"c-form-inline-item\"\r\n :style=\"getItemStyle(index)\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport type { VNode, CSSProperties } from \"vue\";\r\nimport type { LayoutProps, FormOption, ItemLayoutConfig } from \"../../types\";\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<LayoutProps>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\n/**\r\n * 获取统一项目宽度\r\n */\r\nconst itemWidth = computed((): number => 280);\r\n\r\n/**\r\n * 获取项目间距\r\n */\r\nconst gap = computed((): number => {\r\n return props.layoutConfig?.inline?.gap ?? 16;\r\n});\r\n\r\n/**\r\n * 获取行间距\r\n */\r\nconst rowGap = computed((): number => 16);\r\n\r\n/**\r\n * 获取对齐方式 - 既控制垂直对齐也控制水平对齐\r\n */\r\nconst align = computed((): \"start\" | \"center\" | \"end\" => {\r\n const alignValue = props.layoutConfig?.inline?.align;\r\n return alignValue === \"start\" || alignValue === \"end\" ? alignValue : \"center\";\r\n});\r\n\r\n/**\r\n * 获取垂直对齐样式\r\n */\r\nconst alignItems = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n default:\r\n return \"center\";\r\n }\r\n});\r\n\r\n/**\r\n * 获取水平对齐样式\r\n */\r\nconst justifyContent = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n case \"center\":\r\n return \"center\";\r\n default:\r\n return \"flex-start\";\r\n }\r\n});\r\n\r\n/**\r\n * 容器样式计算\r\n */\r\nconst containerStyle = computed((): CSSProperties => {\r\n return {\r\n display: \"flex\",\r\n flexWrap: \"wrap\",\r\n alignItems: alignItems.value,\r\n justifyContent: justifyContent.value,\r\n gap: `${rowGap.value}px ${gap.value}px`,\r\n width: \"100%\",\r\n };\r\n});\r\n\r\n/* ================= 方法 ================= */\r\n\r\n/**\r\n * 获取表单项的唯一key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as any;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `form-item-${index}`;\r\n};\r\n\r\n/**\r\n * 获取表单项样式\r\n */\r\nconst getItemStyle = (index: number): CSSProperties => {\r\n const option: FormOption | undefined = props.options?.[index];\r\n const layoutConfig: ItemLayoutConfig | undefined = option?.layout;\r\n\r\n /* 基础样式,统一宽度 */\r\n const baseStyle: CSSProperties = {\r\n width: `${itemWidth.value}px`,\r\n flexShrink: 0,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n };\r\n\r\n /* 如果单独设置了宽度,则覆盖统一宽度 */\r\n if (layoutConfig?.width !== undefined) {\r\n baseStyle.width =\r\n typeof layoutConfig.width === \"number\"\r\n ? `${layoutConfig.width}px`\r\n : layoutConfig.width;\r\n }\r\n\r\n /* 合并自定义样式 */\r\n if (layoutConfig?.style) {\r\n Object.assign(baseStyle, layoutConfig.style);\r\n }\r\n\r\n return baseStyle;\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 内联布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-inline\" :style=\"containerStyle\">\r\n <div\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n class=\"c-form-inline-item\"\r\n :style=\"getItemStyle(index)\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport type { VNode, CSSProperties } from \"vue\";\r\nimport type { LayoutProps, FormOption, ItemLayoutConfig } from \"../../types\";\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<LayoutProps>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\n/**\r\n * 获取统一项目宽度\r\n */\r\nconst itemWidth = computed((): number => 280);\r\n\r\n/**\r\n * 获取项目间距\r\n */\r\nconst gap = computed((): number => {\r\n return props.layoutConfig?.inline?.gap ?? 16;\r\n});\r\n\r\n/**\r\n * 获取行间距\r\n */\r\nconst rowGap = computed((): number => 16);\r\n\r\n/**\r\n * 获取对齐方式 - 既控制垂直对齐也控制水平对齐\r\n */\r\nconst align = computed((): \"start\" | \"center\" | \"end\" => {\r\n const alignValue = props.layoutConfig?.inline?.align;\r\n return alignValue === \"start\" || alignValue === \"end\" ? alignValue : \"center\";\r\n});\r\n\r\n/**\r\n * 获取垂直对齐样式\r\n */\r\nconst alignItems = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n default:\r\n return \"center\";\r\n }\r\n});\r\n\r\n/**\r\n * 获取水平对齐样式\r\n */\r\nconst justifyContent = computed((): string => {\r\n switch (align.value) {\r\n case \"start\":\r\n return \"flex-start\";\r\n case \"end\":\r\n return \"flex-end\";\r\n case \"center\":\r\n return \"center\";\r\n default:\r\n return \"flex-start\";\r\n }\r\n});\r\n\r\n/**\r\n * 容器样式计算\r\n */\r\nconst containerStyle = computed((): CSSProperties => {\r\n return {\r\n display: \"flex\",\r\n flexWrap: \"wrap\",\r\n alignItems: alignItems.value,\r\n justifyContent: justifyContent.value,\r\n gap: `${rowGap.value}px ${gap.value}px`,\r\n width: \"100%\",\r\n };\r\n});\r\n\r\n/* ================= 方法 ================= */\r\n\r\n/**\r\n * 获取表单项的唯一key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as any;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `form-item-${index}`;\r\n};\r\n\r\n/**\r\n * 获取表单项样式\r\n */\r\nconst getItemStyle = (index: number): CSSProperties => {\r\n const option: FormOption | undefined = props.options?.[index];\r\n const layoutConfig: ItemLayoutConfig | undefined = option?.layout;\r\n\r\n /* 基础样式,统一宽度 */\r\n const baseStyle: CSSProperties = {\r\n width: `${itemWidth.value}px`,\r\n flexShrink: 0,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n };\r\n\r\n /* 如果单独设置了宽度,则覆盖统一宽度 */\r\n if (layoutConfig?.width !== undefined) {\r\n baseStyle.width =\r\n typeof layoutConfig.width === \"number\"\r\n ? `${layoutConfig.width}px`\r\n : layoutConfig.width;\r\n }\r\n\r\n /* 合并自定义样式 */\r\n if (layoutConfig?.style) {\r\n Object.assign(baseStyle, layoutConfig.style);\r\n }\r\n\r\n return baseStyle;\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 网格表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-grid-layout\">\r\n <!-- 配置面板 -->\r\n <NCard\r\n v-if=\"showConfigPanel\"\r\n size=\"small\"\r\n :bordered=\"false\"\r\n class=\"config-panel\"\r\n >\r\n <template #header>\r\n <div class=\"config-header\">\r\n <C_Icon :name=\"'mdi:view-grid'\" :size=\"18\" />\r\n <span>网格配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div\r\n v-for=\"control in configControls\"\r\n :key=\"control.key\"\r\n class=\"config-item\"\r\n >\r\n <span class=\"config-label\">{{ control.label }}:</span>\r\n <component\r\n :is=\"control.component\"\r\n v-model:value=\"control.value\"\r\n v-bind=\"control.props\"\r\n @update:value=\"emitConfigChange\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 网格容器 -->\r\n <NGrid v-bind=\"gridProps\" class=\"grid-container\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n v-bind=\"getItemProps(index)\"\r\n class=\"grid-item\"\r\n >\r\n <div class=\"item-wrapper\">\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n\r\n <!-- 统计信息 -->\r\n <NAlert\r\n v-if=\"showStats && isDev\"\r\n type=\"info\"\r\n :show-icon=\"false\"\r\n size=\"small\"\r\n class=\"grid-stats\"\r\n >\r\n <template #header>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n 网格信息\r\n </template>\r\n {{ statsText }}\r\n </NAlert>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, watchEffect, toRaw } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\n\r\ninterface GridConfig {\r\n cols?: number;\r\n gutter?: number;\r\n responsive?: boolean;\r\n showConfigPanel?: boolean;\r\n showStats?: boolean;\r\n}\r\n\r\ninterface FormOption {\r\n type: string;\r\n prop: string;\r\n label?: string;\r\n layout?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n grid?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n };\r\n };\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: { grid?: GridConfig };\r\n options?: FormOption[];\r\n}\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst isDev = ref(typeof import.meta !== \"undefined\" && !!import.meta.env?.DEV);\r\nconst internalConfig = reactive({\r\n cols: 24,\r\n gutter: 16,\r\n responsive: true,\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst gridConfig = computed(() => props.layoutConfig?.grid || {});\r\nconst showConfigPanel = computed(() => gridConfig.value.showConfigPanel);\r\nconst showStats = computed(() => gridConfig.value.showStats);\r\n\r\n/* 生效的配置(内部配置优先) */\r\nconst effectiveConfig = computed(() => ({\r\n cols: showConfigPanel.value\r\n ? internalConfig.cols\r\n : (gridConfig.value.cols ?? 24),\r\n gutter: showConfigPanel.value\r\n ? internalConfig.gutter\r\n : (gridConfig.value.gutter ?? 16),\r\n responsive: showConfigPanel.value\r\n ? internalConfig.responsive\r\n : (gridConfig.value.responsive ?? true),\r\n}));\r\n\r\n/* 网格属性 */\r\nconst gridProps = computed(() => ({\r\n cols: effectiveConfig.value.cols,\r\n xGap: effectiveConfig.value.gutter,\r\n yGap: effectiveConfig.value.gutter,\r\n responsive: effectiveConfig.value.responsive ? \"screen\" : \"self\",\r\n}));\r\n\r\n/* 配置控件定义 */\r\nconst configControls = computed(() => [\r\n {\r\n key: \"cols\",\r\n label: \"列数\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.cols,\r\n set: (val: number) => (internalConfig.cols = val),\r\n }),\r\n props: { min: 1, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"gutter\",\r\n label: \"间距\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.gutter,\r\n set: (val: number) => (internalConfig.gutter = val),\r\n }),\r\n props: { min: 0, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"responsive\",\r\n label: \"响应式\",\r\n component: \"NSwitch\",\r\n value: computed({\r\n get: () => internalConfig.responsive,\r\n set: (val: boolean) => (internalConfig.responsive = val),\r\n }),\r\n props: { size: \"small\" },\r\n },\r\n]);\r\n\r\n/* 统计文本 */\r\nconst statsText = computed(\r\n () =>\r\n `列数: ${effectiveConfig.value.cols} | 间距: ${effectiveConfig.value.gutter}px | 项目: ${props.formItems.length}个`,\r\n);\r\n\r\n/* ================= 核心方法 ================= */\r\n\r\n/**\r\n * 获取表单项key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string =>\r\n String(item.key) || (item.props as any)?.path || `grid-item-${index}`;\r\n\r\n/**\r\n * 获取布局属性值\r\n */\r\nconst getLayoutValue = (index: number, key: \"span\" | \"offset\" | \"suffix\") => {\r\n const option = props.options?.[index]?.layout;\r\n return option?.[key] ?? option?.grid?.[key];\r\n};\r\n\r\n/**\r\n * 获取默认span值\r\n */\r\nconst getDefaultSpan = (): number => {\r\n const { cols } = effectiveConfig.value;\r\n return cols <= 12 ? cols : cols <= 24 ? 12 : 8;\r\n};\r\n\r\n/**\r\n * 获取网格项属性\r\n */\r\nconst getItemProps = (index: number) => {\r\n const span = getLayoutValue(index, \"span\");\r\n const offset = getLayoutValue(index, \"offset\");\r\n const suffix = getLayoutValue(index, \"suffix\");\r\n\r\n return {\r\n span:\r\n typeof span === \"number\" && span > 0 && span <= effectiveConfig.value.cols\r\n ? span\r\n : getDefaultSpan(),\r\n offset:\r\n typeof offset === \"number\" &&\r\n offset >= 0 &&\r\n offset < effectiveConfig.value.cols\r\n ? offset\r\n : 0,\r\n suffix: Boolean(suffix),\r\n };\r\n};\r\n\r\n/**\r\n * 配置变更事件\r\n */\r\nconst emitConfigChange = () => {\r\n if (isDev.value) {\r\n console.log(\"Grid config updated:\", toRaw(internalConfig));\r\n }\r\n};\r\n\r\n/* ================= 监听器 ================= */\r\n\r\n/* 同步外部配置 */\r\nwatch(\r\n gridConfig,\r\n (config) => {\r\n if (config.cols !== undefined) internalConfig.cols = config.cols;\r\n if (config.gutter !== undefined) internalConfig.gutter = config.gutter;\r\n if (config.responsive !== undefined)\r\n internalConfig.responsive = config.responsive;\r\n },\r\n { immediate: true },\r\n);\r\n\r\n/* 开发环境验证 */\r\nif (isDev.value) {\r\n watchEffect(() => {\r\n const optionsCount = props.options.length;\r\n const itemsCount = props.formItems.length;\r\n\r\n if (optionsCount > 0 && optionsCount !== itemsCount) {\r\n console.warn(\r\n `[GridLayout] 配置数量(${optionsCount})与表单项数量(${itemsCount})不匹配`,\r\n );\r\n }\r\n });\r\n}\r\n\r\n/* ================= 暴露接口 ================= */\r\n\r\ndefineExpose({\r\n updateGridConfig: (config: Partial<GridConfig>) =>\r\n Object.assign(internalConfig, config),\r\n getCurrentConfig: () => ({ ...effectiveConfig.value }),\r\n getGridInfo: () => ({\r\n ...effectiveConfig.value,\r\n itemCount: props.formItems.length,\r\n }),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 网格表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-grid-layout\">\r\n <!-- 配置面板 -->\r\n <NCard\r\n v-if=\"showConfigPanel\"\r\n size=\"small\"\r\n :bordered=\"false\"\r\n class=\"config-panel\"\r\n >\r\n <template #header>\r\n <div class=\"config-header\">\r\n <C_Icon :name=\"'mdi:view-grid'\" :size=\"18\" />\r\n <span>网格配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div\r\n v-for=\"control in configControls\"\r\n :key=\"control.key\"\r\n class=\"config-item\"\r\n >\r\n <span class=\"config-label\">{{ control.label }}:</span>\r\n <component\r\n :is=\"control.component\"\r\n v-model:value=\"control.value\"\r\n v-bind=\"control.props\"\r\n @update:value=\"emitConfigChange\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 网格容器 -->\r\n <NGrid v-bind=\"gridProps\" class=\"grid-container\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n v-bind=\"getItemProps(index)\"\r\n class=\"grid-item\"\r\n >\r\n <div class=\"item-wrapper\">\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n\r\n <!-- 统计信息 -->\r\n <NAlert\r\n v-if=\"showStats && isDev\"\r\n type=\"info\"\r\n :show-icon=\"false\"\r\n size=\"small\"\r\n class=\"grid-stats\"\r\n >\r\n <template #header>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n 网格信息\r\n </template>\r\n {{ statsText }}\r\n </NAlert>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, watchEffect, toRaw } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\n\r\ninterface GridConfig {\r\n cols?: number;\r\n gutter?: number;\r\n responsive?: boolean;\r\n showConfigPanel?: boolean;\r\n showStats?: boolean;\r\n}\r\n\r\ninterface FormOption {\r\n type: string;\r\n prop: string;\r\n label?: string;\r\n layout?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n grid?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n };\r\n };\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: { grid?: GridConfig };\r\n options?: FormOption[];\r\n}\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst isDev = ref(typeof import.meta !== \"undefined\" && !!import.meta.env?.DEV);\r\nconst internalConfig = reactive({\r\n cols: 24,\r\n gutter: 16,\r\n responsive: true,\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst gridConfig = computed(() => props.layoutConfig?.grid || {});\r\nconst showConfigPanel = computed(() => gridConfig.value.showConfigPanel);\r\nconst showStats = computed(() => gridConfig.value.showStats);\r\n\r\n/* 生效的配置(内部配置优先) */\r\nconst effectiveConfig = computed(() => ({\r\n cols: showConfigPanel.value\r\n ? internalConfig.cols\r\n : (gridConfig.value.cols ?? 24),\r\n gutter: showConfigPanel.value\r\n ? internalConfig.gutter\r\n : (gridConfig.value.gutter ?? 16),\r\n responsive: showConfigPanel.value\r\n ? internalConfig.responsive\r\n : (gridConfig.value.responsive ?? true),\r\n}));\r\n\r\n/* 网格属性 */\r\nconst gridProps = computed(() => ({\r\n cols: effectiveConfig.value.cols,\r\n xGap: effectiveConfig.value.gutter,\r\n yGap: effectiveConfig.value.gutter,\r\n responsive: effectiveConfig.value.responsive ? \"screen\" : \"self\",\r\n}));\r\n\r\n/* 配置控件定义 */\r\nconst configControls = computed(() => [\r\n {\r\n key: \"cols\",\r\n label: \"列数\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.cols,\r\n set: (val: number) => (internalConfig.cols = val),\r\n }),\r\n props: { min: 1, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"gutter\",\r\n label: \"间距\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.gutter,\r\n set: (val: number) => (internalConfig.gutter = val),\r\n }),\r\n props: { min: 0, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"responsive\",\r\n label: \"响应式\",\r\n component: \"NSwitch\",\r\n value: computed({\r\n get: () => internalConfig.responsive,\r\n set: (val: boolean) => (internalConfig.responsive = val),\r\n }),\r\n props: { size: \"small\" },\r\n },\r\n]);\r\n\r\n/* 统计文本 */\r\nconst statsText = computed(\r\n () =>\r\n `列数: ${effectiveConfig.value.cols} | 间距: ${effectiveConfig.value.gutter}px | 项目: ${props.formItems.length}个`,\r\n);\r\n\r\n/* ================= 核心方法 ================= */\r\n\r\n/**\r\n * 获取表单项key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string =>\r\n String(item.key) || (item.props as any)?.path || `grid-item-${index}`;\r\n\r\n/**\r\n * 获取布局属性值\r\n */\r\nconst getLayoutValue = (index: number, key: \"span\" | \"offset\" | \"suffix\") => {\r\n const option = props.options?.[index]?.layout;\r\n return option?.[key] ?? option?.grid?.[key];\r\n};\r\n\r\n/**\r\n * 获取默认span值\r\n */\r\nconst getDefaultSpan = (): number => {\r\n const { cols } = effectiveConfig.value;\r\n return cols <= 12 ? cols : cols <= 24 ? 12 : 8;\r\n};\r\n\r\n/**\r\n * 获取网格项属性\r\n */\r\nconst getItemProps = (index: number) => {\r\n const span = getLayoutValue(index, \"span\");\r\n const offset = getLayoutValue(index, \"offset\");\r\n const suffix = getLayoutValue(index, \"suffix\");\r\n\r\n return {\r\n span:\r\n typeof span === \"number\" && span > 0 && span <= effectiveConfig.value.cols\r\n ? span\r\n : getDefaultSpan(),\r\n offset:\r\n typeof offset === \"number\" &&\r\n offset >= 0 &&\r\n offset < effectiveConfig.value.cols\r\n ? offset\r\n : 0,\r\n suffix: Boolean(suffix),\r\n };\r\n};\r\n\r\n/**\r\n * 配置变更事件\r\n */\r\nconst emitConfigChange = () => {\r\n if (isDev.value) {\r\n console.log(\"Grid config updated:\", toRaw(internalConfig));\r\n }\r\n};\r\n\r\n/* ================= 监听器 ================= */\r\n\r\n/* 同步外部配置 */\r\nwatch(\r\n gridConfig,\r\n (config) => {\r\n if (config.cols !== undefined) internalConfig.cols = config.cols;\r\n if (config.gutter !== undefined) internalConfig.gutter = config.gutter;\r\n if (config.responsive !== undefined)\r\n internalConfig.responsive = config.responsive;\r\n },\r\n { immediate: true },\r\n);\r\n\r\n/* 开发环境验证 */\r\nif (isDev.value) {\r\n watchEffect(() => {\r\n const optionsCount = props.options.length;\r\n const itemsCount = props.formItems.length;\r\n\r\n if (optionsCount > 0 && optionsCount !== itemsCount) {\r\n console.warn(\r\n `[GridLayout] 配置数量(${optionsCount})与表单项数量(${itemsCount})不匹配`,\r\n );\r\n }\r\n });\r\n}\r\n\r\n/* ================= 暴露接口 ================= */\r\n\r\ndefineExpose({\r\n updateGridConfig: (config: Partial<GridConfig>) =>\r\n Object.assign(internalConfig, config),\r\n getCurrentConfig: () => ({ ...effectiveConfig.value }),\r\n getGridInfo: () => ({\r\n ...effectiveConfig.value,\r\n itemCount: props.formItems.length,\r\n }),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 网格表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-grid-layout\">\r\n <!-- 配置面板 -->\r\n <NCard\r\n v-if=\"showConfigPanel\"\r\n size=\"small\"\r\n :bordered=\"false\"\r\n class=\"config-panel\"\r\n >\r\n <template #header>\r\n <div class=\"config-header\">\r\n <C_Icon :name=\"'mdi:view-grid'\" :size=\"18\" />\r\n <span>网格配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div\r\n v-for=\"control in configControls\"\r\n :key=\"control.key\"\r\n class=\"config-item\"\r\n >\r\n <span class=\"config-label\">{{ control.label }}:</span>\r\n <component\r\n :is=\"control.component\"\r\n v-model:value=\"control.value\"\r\n v-bind=\"control.props\"\r\n @update:value=\"emitConfigChange\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 网格容器 -->\r\n <NGrid v-bind=\"gridProps\" class=\"grid-container\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n v-bind=\"getItemProps(index)\"\r\n class=\"grid-item\"\r\n >\r\n <div class=\"item-wrapper\">\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n\r\n <!-- 统计信息 -->\r\n <NAlert\r\n v-if=\"showStats && isDev\"\r\n type=\"info\"\r\n :show-icon=\"false\"\r\n size=\"small\"\r\n class=\"grid-stats\"\r\n >\r\n <template #header>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n 网格信息\r\n </template>\r\n {{ statsText }}\r\n </NAlert>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, watchEffect, toRaw } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\n\r\ninterface GridConfig {\r\n cols?: number;\r\n gutter?: number;\r\n responsive?: boolean;\r\n showConfigPanel?: boolean;\r\n showStats?: boolean;\r\n}\r\n\r\ninterface FormOption {\r\n type: string;\r\n prop: string;\r\n label?: string;\r\n layout?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n grid?: {\r\n span?: number;\r\n offset?: number;\r\n suffix?: boolean;\r\n };\r\n };\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: { grid?: GridConfig };\r\n options?: FormOption[];\r\n}\r\n\r\n/* ================= 组件属性 ================= */\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst isDev = ref(typeof import.meta !== \"undefined\" && !!import.meta.env?.DEV);\r\nconst internalConfig = reactive({\r\n cols: 24,\r\n gutter: 16,\r\n responsive: true,\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst gridConfig = computed(() => props.layoutConfig?.grid || {});\r\nconst showConfigPanel = computed(() => gridConfig.value.showConfigPanel);\r\nconst showStats = computed(() => gridConfig.value.showStats);\r\n\r\n/* 生效的配置(内部配置优先) */\r\nconst effectiveConfig = computed(() => ({\r\n cols: showConfigPanel.value\r\n ? internalConfig.cols\r\n : (gridConfig.value.cols ?? 24),\r\n gutter: showConfigPanel.value\r\n ? internalConfig.gutter\r\n : (gridConfig.value.gutter ?? 16),\r\n responsive: showConfigPanel.value\r\n ? internalConfig.responsive\r\n : (gridConfig.value.responsive ?? true),\r\n}));\r\n\r\n/* 网格属性 */\r\nconst gridProps = computed(() => ({\r\n cols: effectiveConfig.value.cols,\r\n xGap: effectiveConfig.value.gutter,\r\n yGap: effectiveConfig.value.gutter,\r\n responsive: effectiveConfig.value.responsive ? \"screen\" : \"self\",\r\n}));\r\n\r\n/* 配置控件定义 */\r\nconst configControls = computed(() => [\r\n {\r\n key: \"cols\",\r\n label: \"列数\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.cols,\r\n set: (val: number) => (internalConfig.cols = val),\r\n }),\r\n props: { min: 1, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"gutter\",\r\n label: \"间距\",\r\n component: \"NInputNumber\",\r\n value: computed({\r\n get: () => internalConfig.gutter,\r\n set: (val: number) => (internalConfig.gutter = val),\r\n }),\r\n props: { min: 0, max: 48, size: \"small\" },\r\n },\r\n {\r\n key: \"responsive\",\r\n label: \"响应式\",\r\n component: \"NSwitch\",\r\n value: computed({\r\n get: () => internalConfig.responsive,\r\n set: (val: boolean) => (internalConfig.responsive = val),\r\n }),\r\n props: { size: \"small\" },\r\n },\r\n]);\r\n\r\n/* 统计文本 */\r\nconst statsText = computed(\r\n () =>\r\n `列数: ${effectiveConfig.value.cols} | 间距: ${effectiveConfig.value.gutter}px | 项目: ${props.formItems.length}个`,\r\n);\r\n\r\n/* ================= 核心方法 ================= */\r\n\r\n/**\r\n * 获取表单项key\r\n */\r\nconst getItemKey = (item: VNode, index: number): string =>\r\n String(item.key) || (item.props as any)?.path || `grid-item-${index}`;\r\n\r\n/**\r\n * 获取布局属性值\r\n */\r\nconst getLayoutValue = (index: number, key: \"span\" | \"offset\" | \"suffix\") => {\r\n const option = props.options?.[index]?.layout;\r\n return option?.[key] ?? option?.grid?.[key];\r\n};\r\n\r\n/**\r\n * 获取默认span值\r\n */\r\nconst getDefaultSpan = (): number => {\r\n const { cols } = effectiveConfig.value;\r\n return cols <= 12 ? cols : cols <= 24 ? 12 : 8;\r\n};\r\n\r\n/**\r\n * 获取网格项属性\r\n */\r\nconst getItemProps = (index: number) => {\r\n const span = getLayoutValue(index, \"span\");\r\n const offset = getLayoutValue(index, \"offset\");\r\n const suffix = getLayoutValue(index, \"suffix\");\r\n\r\n return {\r\n span:\r\n typeof span === \"number\" && span > 0 && span <= effectiveConfig.value.cols\r\n ? span\r\n : getDefaultSpan(),\r\n offset:\r\n typeof offset === \"number\" &&\r\n offset >= 0 &&\r\n offset < effectiveConfig.value.cols\r\n ? offset\r\n : 0,\r\n suffix: Boolean(suffix),\r\n };\r\n};\r\n\r\n/**\r\n * 配置变更事件\r\n */\r\nconst emitConfigChange = () => {\r\n if (isDev.value) {\r\n console.log(\"Grid config updated:\", toRaw(internalConfig));\r\n }\r\n};\r\n\r\n/* ================= 监听器 ================= */\r\n\r\n/* 同步外部配置 */\r\nwatch(\r\n gridConfig,\r\n (config) => {\r\n if (config.cols !== undefined) internalConfig.cols = config.cols;\r\n if (config.gutter !== undefined) internalConfig.gutter = config.gutter;\r\n if (config.responsive !== undefined)\r\n internalConfig.responsive = config.responsive;\r\n },\r\n { immediate: true },\r\n);\r\n\r\n/* 开发环境验证 */\r\nif (isDev.value) {\r\n watchEffect(() => {\r\n const optionsCount = props.options.length;\r\n const itemsCount = props.formItems.length;\r\n\r\n if (optionsCount > 0 && optionsCount !== itemsCount) {\r\n console.warn(\r\n `[GridLayout] 配置数量(${optionsCount})与表单项数量(${itemsCount})不匹配`,\r\n );\r\n }\r\n });\r\n}\r\n\r\n/* ================= 暴露接口 ================= */\r\n\r\ndefineExpose({\r\n updateGridConfig: (config: Partial<GridConfig>) =>\r\n Object.assign(internalConfig, config),\r\n getCurrentConfig: () => ({ ...effectiveConfig.value }),\r\n getGridInfo: () => ({\r\n ...effectiveConfig.value,\r\n itemCount: props.formItems.length,\r\n }),\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 卡片组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-card\">\r\n <!-- 布局配置面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showLayoutConfig\"\r\n class=\"layout-config-panel\"\r\n :bordered=\"false\"\r\n >\r\n <template #header>\r\n <div class=\"flex items-center gap-2\">\r\n <C_Icon :name=\"'mdi:cog'\" :size=\"18\" />\r\n <span>布局配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div class=\"config-item\">\r\n <span>卡片间距</span>\r\n <div class=\"flex items-center gap-2\">\r\n <NSlider\r\n v-model:value=\"cardGap\"\r\n :min=\"12\"\r\n :max=\"32\"\r\n :step=\"4\"\r\n class=\"w-24\"\r\n />\r\n <span class=\"text-xs min-w-12\">{{ cardGap }}px</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>显示图标</span>\r\n <NSwitch v-model:value=\"showIcons\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>可折叠</span>\r\n <NSwitch v-model:value=\"collapsible\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>布局方向</span>\r\n <NRadioGroup v-model:value=\"currentDirection\" size=\"small\">\r\n <NRadio value=\"vertical\">垂直</NRadio>\r\n <NRadio value=\"horizontal\">水平</NRadio>\r\n </NRadioGroup>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 表单内容区域 -->\r\n <div\r\n class=\"form-content\"\r\n :class=\"layoutClass\"\r\n :style=\"{ gap: `${cardGap}px` }\"\r\n >\r\n <!-- 无分组时的单卡片模式 -->\r\n <NCard v-if=\"!hasGroups\" class=\"single-card\" hoverable>\r\n <template #header>\r\n <div class=\"flex items-center gap-3\">\r\n <C_Icon\r\n v-if=\"showIcons\"\r\n :name=\"'mdi:form-select'\"\r\n :size=\"18\"\r\n style=\"color: #3b82f6\"\r\n />\r\n <span>表单信息</span>\r\n </div>\r\n </template>\r\n\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </NCard>\r\n\r\n <!-- 有分组时的多卡片模式 -->\r\n <template v-else>\r\n <NCard\r\n v-for=\"group in groupsWithItems\"\r\n :key=\"group.config.key\"\r\n class=\"group-card\"\r\n :class=\"[\r\n `${group.config.key}-card`,\r\n { collapsed: collapsible && collapsedGroups[group.config.key] },\r\n ]\"\r\n hoverable\r\n >\r\n <template #header>\r\n <div class=\"card-header\">\r\n <div class=\"header-info\">\r\n <C_Icon\r\n v-if=\"showIcons && group.config.icon\"\r\n :name=\"group.config.icon\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n <C_Icon\r\n v-else-if=\"showIcons\"\r\n :name=\"getDefaultIcon(group.config.key)\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n\r\n <div class=\"header-text\">\r\n <h3>{{ group.config.title }}</h3>\r\n <p v-if=\"group.config.description\">\r\n {{ group.config.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"header-actions\">\r\n <!-- 统计信息 -->\r\n <div class=\"field-stats\">\r\n <NBadge :value=\"group.items.length\" type=\"info\" show-zero />\r\n <NBadge\r\n :value=\"`${getFilledCount(group)}/${group.items.length}`\"\r\n :type=\"\r\n getFilledCount(group) === group.items.length\r\n ? 'success'\r\n : 'warning'\r\n \"\r\n />\r\n </div>\r\n\r\n <!-- 折叠按钮 -->\r\n <NButton\r\n v-if=\"collapsible\"\r\n quaternary\r\n circle\r\n size=\"small\"\r\n @click=\"toggleGroup(group.config.key)\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n collapsedGroups[group.config.key]\r\n ? 'mdi:chevron-down'\r\n : 'mdi:chevron-up'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n </template>\r\n\r\n <div v-show=\"!collapsedGroups[group.config.key]\" class=\"card-content\">\r\n <!-- 进度指示器 -->\r\n <div v-if=\"showProgress\" class=\"progress-section\">\r\n <NProgress\r\n :percentage=\"getGroupProgress(group)\"\r\n :color=\"getGroupProgress(group) === 100 ? '#52c41a' : '#1890ff'\"\r\n :show-indicator=\"false\"\r\n class=\"mb-4\"\r\n />\r\n </div>\r\n\r\n <!-- 表单项 -->\r\n <template v-for=\"item in group.items\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n </NCard>\r\n </template>\r\n </div>\r\n\r\n <!-- 统一操作面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showActionPanel\"\r\n class=\"action-panel\"\r\n :bordered=\"false\"\r\n >\r\n <div class=\"action-content\">\r\n <div class=\"status-summary\">\r\n <div class=\"status-item\">\r\n <span class=\"label\">完成进度:</span>\r\n <div class=\"progress-display\">\r\n <NProgress\r\n :percentage=\"totalProgress\"\r\n :show-indicator=\"false\"\r\n :color=\"totalProgress === 100 ? '#52c41a' : '#1890ff'\"\r\n class=\"flex-1\"\r\n />\r\n <span class=\"text-sm min-w-12\">{{ totalProgress }}%</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"action-buttons\">\r\n <NButton v-if=\"collapsible\" @click=\"toggleAllGroups\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n allCollapsed\r\n ? 'mdi:unfold-more-horizontal'\r\n : 'mdi:unfold-less-horizontal'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n {{ allCollapsed ? \"展开全部\" : \"折叠全部\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/**\r\n * 分组配置接口\r\n */\r\ninterface GroupConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n}\r\n\r\n/**\r\n * 分组数据接口\r\n */\r\ninterface GroupWithItems {\r\n config: GroupConfig;\r\n items: VNode[];\r\n}\r\n\r\n/**\r\n * 组件属性接口\r\n */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n card?: {\r\n groups?: GroupConfig[];\r\n direction?: \"vertical\" | \"horizontal\";\r\n showLayoutConfig?: boolean;\r\n showActionPanel?: boolean;\r\n showProgress?: boolean;\r\n };\r\n };\r\n options?: Array<{\r\n layout?: {\r\n group?: string;\r\n };\r\n prop?: string;\r\n }>;\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst cardGap = ref(20);\r\nconst showIcons = ref(true);\r\nconst collapsible = ref(true);\r\nconst showProgress = ref(true);\r\nconst currentDirection = ref<\"vertical\" | \"horizontal\">(\"vertical\");\r\nconst collapsedGroups = ref<Record<string, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst groups = computed((): GroupConfig[] => {\r\n return props.layoutConfig?.card?.groups || [];\r\n});\r\n\r\nconst hasGroups = computed((): boolean => {\r\n return groups.value.length > 0;\r\n});\r\n\r\nconst showLayoutConfig = computed((): boolean => {\r\n return props.layoutConfig?.card?.showLayoutConfig ?? true;\r\n});\r\n\r\nconst showActionPanel = computed((): boolean => {\r\n return props.layoutConfig?.card?.showActionPanel ?? true;\r\n});\r\n\r\nconst layoutClass = computed((): string => {\r\n if (!hasGroups.value) return \"layout-single\";\r\n return `layout-${currentDirection.value}`;\r\n});\r\n\r\nconst groupsWithItems = computed((): GroupWithItems[] => {\r\n if (!hasGroups.value) return [];\r\n\r\n const groupMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化分组映射 */\r\n groups.value.forEach((group) => {\r\n groupMap.set(group.key, []);\r\n });\r\n\r\n /* 将表单项分配到对应分组 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const groupKey = option?.layout?.group || groups.value[0]?.key || \"default\";\r\n\r\n if (!groupMap.has(groupKey)) {\r\n groupMap.set(groupKey, []);\r\n }\r\n groupMap.get(groupKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的分组 */\r\n return groups.value\r\n .map((groupConfig) => ({\r\n config: groupConfig,\r\n items: groupMap.get(groupConfig.key) || [],\r\n }))\r\n .filter((group) => group.items.length > 0);\r\n});\r\n\r\nconst allCollapsed = computed(() => {\r\n const groupKeys = Object.keys(collapsedGroups.value);\r\n return (\r\n groupKeys.length > 0 && groupKeys.every((key) => collapsedGroups.value[key])\r\n );\r\n});\r\n\r\nconst totalProgress = computed(() => {\r\n if (!props.options || props.options.length === 0) return 0;\r\n\r\n const filledCount = props.options.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n\r\n return Math.round((filledCount / props.options.length) * 100);\r\n});\r\n\r\n/* ================= 工具函数 ================= */\r\nconst isFieldFilled = (value: any): boolean => {\r\n if (value === null || value === undefined) return false;\r\n if (Array.isArray(value)) return value.length > 0;\r\n if (typeof value === \"string\") return value.trim() !== \"\";\r\n return true;\r\n};\r\n\r\nconst getFilledCount = (group: GroupWithItems): number => {\r\n if (!props.options) return 0;\r\n\r\n const groupFields = props.options.filter(\r\n (option) => option.layout?.group === group.config.key,\r\n );\r\n\r\n return groupFields.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n};\r\n\r\nconst getGroupProgress = (group: GroupWithItems): number => {\r\n if (group.items.length === 0) return 0;\r\n const filledCount = getFilledCount(group);\r\n return Math.round((filledCount / group.items.length) * 100);\r\n};\r\n\r\nconst getDefaultIcon = (groupKey: string): string => {\r\n const iconMap: Record<string, string> = {\r\n basic: \"mdi:account\",\r\n contact: \"mdi:phone\",\r\n preferences: \"mdi:cog\",\r\n settings: \"mdi:cog\",\r\n info: \"mdi:information\",\r\n default: \"mdi:form-select\",\r\n };\r\n return iconMap[groupKey] || iconMap.default;\r\n};\r\n\r\n/* ================= 操作方法 ================= */\r\nconst toggleGroup = (groupKey: string): void => {\r\n collapsedGroups.value[groupKey] = !collapsedGroups.value[groupKey];\r\n};\r\n\r\nconst toggleAllGroups = (): void => {\r\n const shouldCollapse = !allCollapsed.value;\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = shouldCollapse;\r\n });\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n /* 初始化配置 */\r\n const config = props.layoutConfig?.card;\r\n if (config?.direction) {\r\n currentDirection.value = config.direction;\r\n }\r\n\r\n if (config?.showProgress !== undefined) {\r\n showProgress.value = config.showProgress;\r\n }\r\n\r\n /* 初始化折叠状态 */\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = false;\r\n });\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 卡片组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-card\">\r\n <!-- 布局配置面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showLayoutConfig\"\r\n class=\"layout-config-panel\"\r\n :bordered=\"false\"\r\n >\r\n <template #header>\r\n <div class=\"flex items-center gap-2\">\r\n <C_Icon :name=\"'mdi:cog'\" :size=\"18\" />\r\n <span>布局配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div class=\"config-item\">\r\n <span>卡片间距</span>\r\n <div class=\"flex items-center gap-2\">\r\n <NSlider\r\n v-model:value=\"cardGap\"\r\n :min=\"12\"\r\n :max=\"32\"\r\n :step=\"4\"\r\n class=\"w-24\"\r\n />\r\n <span class=\"text-xs min-w-12\">{{ cardGap }}px</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>显示图标</span>\r\n <NSwitch v-model:value=\"showIcons\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>可折叠</span>\r\n <NSwitch v-model:value=\"collapsible\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>布局方向</span>\r\n <NRadioGroup v-model:value=\"currentDirection\" size=\"small\">\r\n <NRadio value=\"vertical\">垂直</NRadio>\r\n <NRadio value=\"horizontal\">水平</NRadio>\r\n </NRadioGroup>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 表单内容区域 -->\r\n <div\r\n class=\"form-content\"\r\n :class=\"layoutClass\"\r\n :style=\"{ gap: `${cardGap}px` }\"\r\n >\r\n <!-- 无分组时的单卡片模式 -->\r\n <NCard v-if=\"!hasGroups\" class=\"single-card\" hoverable>\r\n <template #header>\r\n <div class=\"flex items-center gap-3\">\r\n <C_Icon\r\n v-if=\"showIcons\"\r\n :name=\"'mdi:form-select'\"\r\n :size=\"18\"\r\n style=\"color: #3b82f6\"\r\n />\r\n <span>表单信息</span>\r\n </div>\r\n </template>\r\n\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </NCard>\r\n\r\n <!-- 有分组时的多卡片模式 -->\r\n <template v-else>\r\n <NCard\r\n v-for=\"group in groupsWithItems\"\r\n :key=\"group.config.key\"\r\n class=\"group-card\"\r\n :class=\"[\r\n `${group.config.key}-card`,\r\n { collapsed: collapsible && collapsedGroups[group.config.key] },\r\n ]\"\r\n hoverable\r\n >\r\n <template #header>\r\n <div class=\"card-header\">\r\n <div class=\"header-info\">\r\n <C_Icon\r\n v-if=\"showIcons && group.config.icon\"\r\n :name=\"group.config.icon\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n <C_Icon\r\n v-else-if=\"showIcons\"\r\n :name=\"getDefaultIcon(group.config.key)\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n\r\n <div class=\"header-text\">\r\n <h3>{{ group.config.title }}</h3>\r\n <p v-if=\"group.config.description\">\r\n {{ group.config.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"header-actions\">\r\n <!-- 统计信息 -->\r\n <div class=\"field-stats\">\r\n <NBadge :value=\"group.items.length\" type=\"info\" show-zero />\r\n <NBadge\r\n :value=\"`${getFilledCount(group)}/${group.items.length}`\"\r\n :type=\"\r\n getFilledCount(group) === group.items.length\r\n ? 'success'\r\n : 'warning'\r\n \"\r\n />\r\n </div>\r\n\r\n <!-- 折叠按钮 -->\r\n <NButton\r\n v-if=\"collapsible\"\r\n quaternary\r\n circle\r\n size=\"small\"\r\n @click=\"toggleGroup(group.config.key)\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n collapsedGroups[group.config.key]\r\n ? 'mdi:chevron-down'\r\n : 'mdi:chevron-up'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n </template>\r\n\r\n <div v-show=\"!collapsedGroups[group.config.key]\" class=\"card-content\">\r\n <!-- 进度指示器 -->\r\n <div v-if=\"showProgress\" class=\"progress-section\">\r\n <NProgress\r\n :percentage=\"getGroupProgress(group)\"\r\n :color=\"getGroupProgress(group) === 100 ? '#52c41a' : '#1890ff'\"\r\n :show-indicator=\"false\"\r\n class=\"mb-4\"\r\n />\r\n </div>\r\n\r\n <!-- 表单项 -->\r\n <template v-for=\"item in group.items\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n </NCard>\r\n </template>\r\n </div>\r\n\r\n <!-- 统一操作面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showActionPanel\"\r\n class=\"action-panel\"\r\n :bordered=\"false\"\r\n >\r\n <div class=\"action-content\">\r\n <div class=\"status-summary\">\r\n <div class=\"status-item\">\r\n <span class=\"label\">完成进度:</span>\r\n <div class=\"progress-display\">\r\n <NProgress\r\n :percentage=\"totalProgress\"\r\n :show-indicator=\"false\"\r\n :color=\"totalProgress === 100 ? '#52c41a' : '#1890ff'\"\r\n class=\"flex-1\"\r\n />\r\n <span class=\"text-sm min-w-12\">{{ totalProgress }}%</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"action-buttons\">\r\n <NButton v-if=\"collapsible\" @click=\"toggleAllGroups\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n allCollapsed\r\n ? 'mdi:unfold-more-horizontal'\r\n : 'mdi:unfold-less-horizontal'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n {{ allCollapsed ? \"展开全部\" : \"折叠全部\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/**\r\n * 分组配置接口\r\n */\r\ninterface GroupConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n}\r\n\r\n/**\r\n * 分组数据接口\r\n */\r\ninterface GroupWithItems {\r\n config: GroupConfig;\r\n items: VNode[];\r\n}\r\n\r\n/**\r\n * 组件属性接口\r\n */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n card?: {\r\n groups?: GroupConfig[];\r\n direction?: \"vertical\" | \"horizontal\";\r\n showLayoutConfig?: boolean;\r\n showActionPanel?: boolean;\r\n showProgress?: boolean;\r\n };\r\n };\r\n options?: Array<{\r\n layout?: {\r\n group?: string;\r\n };\r\n prop?: string;\r\n }>;\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst cardGap = ref(20);\r\nconst showIcons = ref(true);\r\nconst collapsible = ref(true);\r\nconst showProgress = ref(true);\r\nconst currentDirection = ref<\"vertical\" | \"horizontal\">(\"vertical\");\r\nconst collapsedGroups = ref<Record<string, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst groups = computed((): GroupConfig[] => {\r\n return props.layoutConfig?.card?.groups || [];\r\n});\r\n\r\nconst hasGroups = computed((): boolean => {\r\n return groups.value.length > 0;\r\n});\r\n\r\nconst showLayoutConfig = computed((): boolean => {\r\n return props.layoutConfig?.card?.showLayoutConfig ?? true;\r\n});\r\n\r\nconst showActionPanel = computed((): boolean => {\r\n return props.layoutConfig?.card?.showActionPanel ?? true;\r\n});\r\n\r\nconst layoutClass = computed((): string => {\r\n if (!hasGroups.value) return \"layout-single\";\r\n return `layout-${currentDirection.value}`;\r\n});\r\n\r\nconst groupsWithItems = computed((): GroupWithItems[] => {\r\n if (!hasGroups.value) return [];\r\n\r\n const groupMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化分组映射 */\r\n groups.value.forEach((group) => {\r\n groupMap.set(group.key, []);\r\n });\r\n\r\n /* 将表单项分配到对应分组 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const groupKey = option?.layout?.group || groups.value[0]?.key || \"default\";\r\n\r\n if (!groupMap.has(groupKey)) {\r\n groupMap.set(groupKey, []);\r\n }\r\n groupMap.get(groupKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的分组 */\r\n return groups.value\r\n .map((groupConfig) => ({\r\n config: groupConfig,\r\n items: groupMap.get(groupConfig.key) || [],\r\n }))\r\n .filter((group) => group.items.length > 0);\r\n});\r\n\r\nconst allCollapsed = computed(() => {\r\n const groupKeys = Object.keys(collapsedGroups.value);\r\n return (\r\n groupKeys.length > 0 && groupKeys.every((key) => collapsedGroups.value[key])\r\n );\r\n});\r\n\r\nconst totalProgress = computed(() => {\r\n if (!props.options || props.options.length === 0) return 0;\r\n\r\n const filledCount = props.options.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n\r\n return Math.round((filledCount / props.options.length) * 100);\r\n});\r\n\r\n/* ================= 工具函数 ================= */\r\nconst isFieldFilled = (value: any): boolean => {\r\n if (value === null || value === undefined) return false;\r\n if (Array.isArray(value)) return value.length > 0;\r\n if (typeof value === \"string\") return value.trim() !== \"\";\r\n return true;\r\n};\r\n\r\nconst getFilledCount = (group: GroupWithItems): number => {\r\n if (!props.options) return 0;\r\n\r\n const groupFields = props.options.filter(\r\n (option) => option.layout?.group === group.config.key,\r\n );\r\n\r\n return groupFields.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n};\r\n\r\nconst getGroupProgress = (group: GroupWithItems): number => {\r\n if (group.items.length === 0) return 0;\r\n const filledCount = getFilledCount(group);\r\n return Math.round((filledCount / group.items.length) * 100);\r\n};\r\n\r\nconst getDefaultIcon = (groupKey: string): string => {\r\n const iconMap: Record<string, string> = {\r\n basic: \"mdi:account\",\r\n contact: \"mdi:phone\",\r\n preferences: \"mdi:cog\",\r\n settings: \"mdi:cog\",\r\n info: \"mdi:information\",\r\n default: \"mdi:form-select\",\r\n };\r\n return iconMap[groupKey] || iconMap.default;\r\n};\r\n\r\n/* ================= 操作方法 ================= */\r\nconst toggleGroup = (groupKey: string): void => {\r\n collapsedGroups.value[groupKey] = !collapsedGroups.value[groupKey];\r\n};\r\n\r\nconst toggleAllGroups = (): void => {\r\n const shouldCollapse = !allCollapsed.value;\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = shouldCollapse;\r\n });\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n /* 初始化配置 */\r\n const config = props.layoutConfig?.card;\r\n if (config?.direction) {\r\n currentDirection.value = config.direction;\r\n }\r\n\r\n if (config?.showProgress !== undefined) {\r\n showProgress.value = config.showProgress;\r\n }\r\n\r\n /* 初始化折叠状态 */\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = false;\r\n });\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 卡片组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-card\">\r\n <!-- 布局配置面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showLayoutConfig\"\r\n class=\"layout-config-panel\"\r\n :bordered=\"false\"\r\n >\r\n <template #header>\r\n <div class=\"flex items-center gap-2\">\r\n <C_Icon :name=\"'mdi:cog'\" :size=\"18\" />\r\n <span>布局配置</span>\r\n </div>\r\n </template>\r\n\r\n <div class=\"config-controls\">\r\n <div class=\"config-item\">\r\n <span>卡片间距</span>\r\n <div class=\"flex items-center gap-2\">\r\n <NSlider\r\n v-model:value=\"cardGap\"\r\n :min=\"12\"\r\n :max=\"32\"\r\n :step=\"4\"\r\n class=\"w-24\"\r\n />\r\n <span class=\"text-xs min-w-12\">{{ cardGap }}px</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>显示图标</span>\r\n <NSwitch v-model:value=\"showIcons\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>可折叠</span>\r\n <NSwitch v-model:value=\"collapsible\" size=\"small\" />\r\n </div>\r\n\r\n <div class=\"config-item\">\r\n <span>布局方向</span>\r\n <NRadioGroup v-model:value=\"currentDirection\" size=\"small\">\r\n <NRadio value=\"vertical\">垂直</NRadio>\r\n <NRadio value=\"horizontal\">水平</NRadio>\r\n </NRadioGroup>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 表单内容区域 -->\r\n <div\r\n class=\"form-content\"\r\n :class=\"layoutClass\"\r\n :style=\"{ gap: `${cardGap}px` }\"\r\n >\r\n <!-- 无分组时的单卡片模式 -->\r\n <NCard v-if=\"!hasGroups\" class=\"single-card\" hoverable>\r\n <template #header>\r\n <div class=\"flex items-center gap-3\">\r\n <C_Icon\r\n v-if=\"showIcons\"\r\n :name=\"'mdi:form-select'\"\r\n :size=\"18\"\r\n style=\"color: #3b82f6\"\r\n />\r\n <span>表单信息</span>\r\n </div>\r\n </template>\r\n\r\n <template v-for=\"item in formItems\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </NCard>\r\n\r\n <!-- 有分组时的多卡片模式 -->\r\n <template v-else>\r\n <NCard\r\n v-for=\"group in groupsWithItems\"\r\n :key=\"group.config.key\"\r\n class=\"group-card\"\r\n :class=\"[\r\n `${group.config.key}-card`,\r\n { collapsed: collapsible && collapsedGroups[group.config.key] },\r\n ]\"\r\n hoverable\r\n >\r\n <template #header>\r\n <div class=\"card-header\">\r\n <div class=\"header-info\">\r\n <C_Icon\r\n v-if=\"showIcons && group.config.icon\"\r\n :name=\"group.config.icon\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n <C_Icon\r\n v-else-if=\"showIcons\"\r\n :name=\"getDefaultIcon(group.config.key)\"\r\n :size=\"20\"\r\n class=\"card-icon\"\r\n />\r\n\r\n <div class=\"header-text\">\r\n <h3>{{ group.config.title }}</h3>\r\n <p v-if=\"group.config.description\">\r\n {{ group.config.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"header-actions\">\r\n <!-- 统计信息 -->\r\n <div class=\"field-stats\">\r\n <NBadge :value=\"group.items.length\" type=\"info\" show-zero />\r\n <NBadge\r\n :value=\"`${getFilledCount(group)}/${group.items.length}`\"\r\n :type=\"\r\n getFilledCount(group) === group.items.length\r\n ? 'success'\r\n : 'warning'\r\n \"\r\n />\r\n </div>\r\n\r\n <!-- 折叠按钮 -->\r\n <NButton\r\n v-if=\"collapsible\"\r\n quaternary\r\n circle\r\n size=\"small\"\r\n @click=\"toggleGroup(group.config.key)\"\r\n >\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n collapsedGroups[group.config.key]\r\n ? 'mdi:chevron-down'\r\n : 'mdi:chevron-up'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n </template>\r\n\r\n <div v-show=\"!collapsedGroups[group.config.key]\" class=\"card-content\">\r\n <!-- 进度指示器 -->\r\n <div v-if=\"showProgress\" class=\"progress-section\">\r\n <NProgress\r\n :percentage=\"getGroupProgress(group)\"\r\n :color=\"getGroupProgress(group) === 100 ? '#52c41a' : '#1890ff'\"\r\n :show-indicator=\"false\"\r\n class=\"mb-4\"\r\n />\r\n </div>\r\n\r\n <!-- 表单项 -->\r\n <template v-for=\"item in group.items\" :key=\"item.key\">\r\n <component :is=\"item\" />\r\n </template>\r\n </div>\r\n </NCard>\r\n </template>\r\n </div>\r\n\r\n <!-- 统一操作面板 -->\r\n <NCard\r\n v-if=\"hasGroups && showActionPanel\"\r\n class=\"action-panel\"\r\n :bordered=\"false\"\r\n >\r\n <div class=\"action-content\">\r\n <div class=\"status-summary\">\r\n <div class=\"status-item\">\r\n <span class=\"label\">完成进度:</span>\r\n <div class=\"progress-display\">\r\n <NProgress\r\n :percentage=\"totalProgress\"\r\n :show-indicator=\"false\"\r\n :color=\"totalProgress === 100 ? '#52c41a' : '#1890ff'\"\r\n class=\"flex-1\"\r\n />\r\n <span class=\"text-sm min-w-12\">{{ totalProgress }}%</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"action-buttons\">\r\n <NButton v-if=\"collapsible\" @click=\"toggleAllGroups\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n allCollapsed\r\n ? 'mdi:unfold-more-horizontal'\r\n : 'mdi:unfold-less-horizontal'\r\n \"\r\n :size=\"18\"\r\n />\r\n </template>\r\n {{ allCollapsed ? \"展开全部\" : \"折叠全部\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/**\r\n * 分组配置接口\r\n */\r\ninterface GroupConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n}\r\n\r\n/**\r\n * 分组数据接口\r\n */\r\ninterface GroupWithItems {\r\n config: GroupConfig;\r\n items: VNode[];\r\n}\r\n\r\n/**\r\n * 组件属性接口\r\n */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n card?: {\r\n groups?: GroupConfig[];\r\n direction?: \"vertical\" | \"horizontal\";\r\n showLayoutConfig?: boolean;\r\n showActionPanel?: boolean;\r\n showProgress?: boolean;\r\n };\r\n };\r\n options?: Array<{\r\n layout?: {\r\n group?: string;\r\n };\r\n prop?: string;\r\n }>;\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst cardGap = ref(20);\r\nconst showIcons = ref(true);\r\nconst collapsible = ref(true);\r\nconst showProgress = ref(true);\r\nconst currentDirection = ref<\"vertical\" | \"horizontal\">(\"vertical\");\r\nconst collapsedGroups = ref<Record<string, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst groups = computed((): GroupConfig[] => {\r\n return props.layoutConfig?.card?.groups || [];\r\n});\r\n\r\nconst hasGroups = computed((): boolean => {\r\n return groups.value.length > 0;\r\n});\r\n\r\nconst showLayoutConfig = computed((): boolean => {\r\n return props.layoutConfig?.card?.showLayoutConfig ?? true;\r\n});\r\n\r\nconst showActionPanel = computed((): boolean => {\r\n return props.layoutConfig?.card?.showActionPanel ?? true;\r\n});\r\n\r\nconst layoutClass = computed((): string => {\r\n if (!hasGroups.value) return \"layout-single\";\r\n return `layout-${currentDirection.value}`;\r\n});\r\n\r\nconst groupsWithItems = computed((): GroupWithItems[] => {\r\n if (!hasGroups.value) return [];\r\n\r\n const groupMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化分组映射 */\r\n groups.value.forEach((group) => {\r\n groupMap.set(group.key, []);\r\n });\r\n\r\n /* 将表单项分配到对应分组 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const groupKey = option?.layout?.group || groups.value[0]?.key || \"default\";\r\n\r\n if (!groupMap.has(groupKey)) {\r\n groupMap.set(groupKey, []);\r\n }\r\n groupMap.get(groupKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的分组 */\r\n return groups.value\r\n .map((groupConfig) => ({\r\n config: groupConfig,\r\n items: groupMap.get(groupConfig.key) || [],\r\n }))\r\n .filter((group) => group.items.length > 0);\r\n});\r\n\r\nconst allCollapsed = computed(() => {\r\n const groupKeys = Object.keys(collapsedGroups.value);\r\n return (\r\n groupKeys.length > 0 && groupKeys.every((key) => collapsedGroups.value[key])\r\n );\r\n});\r\n\r\nconst totalProgress = computed(() => {\r\n if (!props.options || props.options.length === 0) return 0;\r\n\r\n const filledCount = props.options.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n\r\n return Math.round((filledCount / props.options.length) * 100);\r\n});\r\n\r\n/* ================= 工具函数 ================= */\r\nconst isFieldFilled = (value: any): boolean => {\r\n if (value === null || value === undefined) return false;\r\n if (Array.isArray(value)) return value.length > 0;\r\n if (typeof value === \"string\") return value.trim() !== \"\";\r\n return true;\r\n};\r\n\r\nconst getFilledCount = (group: GroupWithItems): number => {\r\n if (!props.options) return 0;\r\n\r\n const groupFields = props.options.filter(\r\n (option) => option.layout?.group === group.config.key,\r\n );\r\n\r\n return groupFields.filter((option) => {\r\n const value = props.formData?.[option.prop || \"\"];\r\n return isFieldFilled(value);\r\n }).length;\r\n};\r\n\r\nconst getGroupProgress = (group: GroupWithItems): number => {\r\n if (group.items.length === 0) return 0;\r\n const filledCount = getFilledCount(group);\r\n return Math.round((filledCount / group.items.length) * 100);\r\n};\r\n\r\nconst getDefaultIcon = (groupKey: string): string => {\r\n const iconMap: Record<string, string> = {\r\n basic: \"mdi:account\",\r\n contact: \"mdi:phone\",\r\n preferences: \"mdi:cog\",\r\n settings: \"mdi:cog\",\r\n info: \"mdi:information\",\r\n default: \"mdi:form-select\",\r\n };\r\n return iconMap[groupKey] || iconMap.default;\r\n};\r\n\r\n/* ================= 操作方法 ================= */\r\nconst toggleGroup = (groupKey: string): void => {\r\n collapsedGroups.value[groupKey] = !collapsedGroups.value[groupKey];\r\n};\r\n\r\nconst toggleAllGroups = (): void => {\r\n const shouldCollapse = !allCollapsed.value;\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = shouldCollapse;\r\n });\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n /* 初始化配置 */\r\n const config = props.layoutConfig?.card;\r\n if (config?.direction) {\r\n currentDirection.value = config.direction;\r\n }\r\n\r\n if (config?.showProgress !== undefined) {\r\n showProgress.value = config.showProgress;\r\n }\r\n\r\n /* 初始化折叠状态 */\r\n groups.value.forEach((group) => {\r\n collapsedGroups.value[group.key] = false;\r\n });\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 标签布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-tabs\">\r\n <!-- 无标签配置时的单一面板模式 -->\r\n <div v-if=\"!hasTabs\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有标签配置时的分标签模式 -->\r\n <div v-else class=\"tabs-container\">\r\n <NTabs\r\n v-model:value=\"currentTab\"\r\n :type=\"tabsConfig.type\"\r\n :size=\"tabsConfig.size\"\r\n :placement=\"tabsConfig.placement\"\r\n :animated=\"tabsConfig.animated\"\r\n :closable=\"tabsConfig.closable\"\r\n :addable=\"tabsConfig.addable\"\r\n class=\"form-tabs\"\r\n @update:value=\"handleTabChange\"\r\n @close=\"handleTabClose\"\r\n @add=\"handleTabAdd\"\r\n >\r\n <NTabPane\r\n v-for=\"tab in tabsWithItems\"\r\n :key=\"tab.config.key\"\r\n :name=\"tab.config.key\"\r\n :tab=\"tab.config.title\"\r\n :disabled=\"tab.config.disabled\"\r\n :closable=\"tab.config.closable\"\r\n >\r\n <template #tab>\r\n <NSpace align=\"center\" :size=\"8\">\r\n <C_Icon\r\n v-if=\"tab.config.icon\"\r\n :name=\"tab.config.icon\"\r\n :size=\"16\"\r\n class=\"tab-icon\"\r\n />\r\n <span>{{ tab.config.title }}</span>\r\n <NBadge\r\n v-if=\"tabsConfig.showCount\"\r\n :value=\"tab.items.length\"\r\n :max=\"99\"\r\n :show=\"tab.items.length > 0\"\r\n type=\"info\"\r\n />\r\n </NSpace>\r\n </template>\r\n\r\n <!-- 标签页头部信息 -->\r\n <div\r\n v-if=\"tabsConfig.showTabHeader && tab.config.description\"\r\n class=\"tab-header\"\r\n >\r\n <p class=\"tab-description\">{{ tab.config.description }}</p>\r\n </div>\r\n\r\n <!-- 标签页内的表单项 -->\r\n <div class=\"tab-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in tab.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 空状态 -->\r\n <NEmpty\r\n v-if=\"tab.items.length === 0\"\r\n description=\"暂无表单项\"\r\n class=\"tab-empty\"\r\n />\r\n </NTabPane>\r\n </NTabs>\r\n\r\n <!-- 标签页操作按钮 -->\r\n <div v-if=\"tabsConfig.showActions\" class=\"tabs-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NSpace>\r\n <NButton\r\n v-if=\"tabsConfig.validateBeforeSwitch\"\r\n type=\"primary\"\r\n size=\"small\"\r\n @click=\"validateCurrentTab\"\r\n >\r\n <C_Icon\r\n :name=\"'carbon:star-check'\"\r\n :size=\"14\"\r\n style=\"margin-right: 4px\"\r\n />\r\n 验证当前标签\r\n </NButton>\r\n </NSpace>\r\n\r\n <NSpace>\r\n <slot\r\n name=\"tab-actions\"\r\n :current-tab=\"currentTab\"\r\n :total-tabs=\"tabsWithItems.length\"\r\n :validate-tab=\"validateCurrentTab\"\r\n :switch-to-tab=\"switchToTab\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n ref,\r\n reactive,\r\n computed,\r\n watch,\r\n nextTick,\r\n onMounted,\r\n readonly,\r\n} from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface TabConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n disabled?: boolean;\r\n closable?: boolean;\r\n}\r\n\r\ninterface TabsLayoutConfig {\r\n tabs: TabConfig[];\r\n type?: \"line\" | \"card\" | \"segment\";\r\n size?: \"small\" | \"medium\" | \"large\";\r\n placement?: \"top\" | \"right\" | \"bottom\" | \"left\";\r\n animated?: boolean;\r\n closable?: boolean;\r\n addable?: boolean;\r\n showTabHeader?: boolean;\r\n showActions?: boolean;\r\n showCount?: boolean;\r\n validateBeforeSwitch?: boolean;\r\n defaultTab?: string;\r\n}\r\n\r\ninterface TabWithItems {\r\n config: TabConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n tabs?: TabsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n tab?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"tab-change\": [tabKey: string, tabIndex: number];\r\n \"tab-before-change\": [currentTab: string, targetTab: string];\r\n \"tab-validate\": [tabKey: string];\r\n \"tab-close\": [tabKey: string];\r\n \"tab-add\": [];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentTab = ref<string>(\"\");\r\nconst tabValidationStatus = reactive<Record<string, boolean>>({});\r\n\r\n/* ================= 默认配置 ================= */\r\nconst getDefaultTabsConfig = (): Required<TabsLayoutConfig> => ({\r\n tabs: [],\r\n type: \"line\",\r\n size: \"medium\",\r\n placement: \"top\",\r\n animated: true,\r\n closable: false,\r\n addable: false,\r\n showTabHeader: true,\r\n showActions: false,\r\n showCount: false,\r\n validateBeforeSwitch: false,\r\n defaultTab: \"\",\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst tabsConfig = computed(() => {\r\n const defaultConfig = getDefaultTabsConfig();\r\n const userConfig = props.layoutConfig?.tabs || {};\r\n\r\n return {\r\n ...defaultConfig,\r\n ...userConfig,\r\n };\r\n});\r\n\r\nconst hasTabs = computed((): boolean => {\r\n return tabsConfig.value.tabs.length > 0;\r\n});\r\n\r\nconst tabsWithItems = computed((): TabWithItems[] => {\r\n if (!hasTabs.value) return [];\r\n\r\n const tabMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化标签映射 */\r\n tabsConfig.value.tabs.forEach((tab) => {\r\n tabMap.set(tab.key, []);\r\n });\r\n\r\n /* 分配表单项到对应标签 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const tabKey =\r\n option?.layout?.tab || tabsConfig.value.tabs[0]?.key || \"default\";\r\n\r\n if (!tabMap.has(tabKey)) {\r\n tabMap.set(tabKey, []);\r\n }\r\n tabMap.get(tabKey)!.push(item);\r\n });\r\n\r\n /* 返回所有标签(包括空标签) */\r\n return tabsConfig.value.tabs.map((tabConfig) => ({\r\n config: tabConfig,\r\n items: tabMap.get(tabConfig.key) || [],\r\n }));\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `tab-item-${index}`;\r\n};\r\n\r\nconst validateCurrentTab = async (): Promise<boolean> => {\r\n if (!currentTab.value) return true;\r\n\r\n try {\r\n emit(\"tab-validate\", currentTab.value);\r\n const valid = true;\r\n tabValidationStatus[currentTab.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签验证失败:\", error);\r\n tabValidationStatus[currentTab.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToTab = async (targetTab: string): Promise<boolean> => {\r\n if (!targetTab || targetTab === currentTab.value) {\r\n return true;\r\n }\r\n\r\n const targetTabExists = tabsWithItems.value.some(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (!targetTabExists) {\r\n return false;\r\n }\r\n\r\n try {\r\n /* 验证当前标签(如果需要) */\r\n if (tabsConfig.value.validateBeforeSwitch && currentTab.value) {\r\n const isValid = await validateCurrentTab();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发标签切换前事件 */\r\n if (currentTab.value) {\r\n emit(\"tab-before-change\", currentTab.value, targetTab);\r\n }\r\n\r\n currentTab.value = targetTab;\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签切换失败:\", error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleTabChange = (tabKey: string): void => {\r\n switchToTab(tabKey);\r\n};\r\n\r\nconst handleTabClose = (tabKey: string): void => {\r\n emit(\"tab-close\", tabKey);\r\n};\r\n\r\nconst handleTabAdd = (): void => {\r\n emit(\"tab-add\");\r\n};\r\n\r\nconst initializeCurrentTab = (): void => {\r\n if (!hasTabs.value || tabsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultTab } = tabsConfig.value;\r\n const targetTab = defaultTab || tabsWithItems.value[0]?.config.key;\r\n\r\n if (targetTab && targetTab !== currentTab.value) {\r\n currentTab.value = targetTab;\r\n nextTick(() => {\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (tabIndex >= 0) {\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n }\r\n });\r\n }\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n initializeCurrentTab();\r\n});\r\n\r\n/* 只监听标签结构变化(key / 数量),不监听表单项内容变化 */\r\nconst tabStructureKey = computed(() =>\r\n tabsConfig.value.tabs.map((t) => t.key).join(\",\"),\r\n);\r\n\r\nwatch(tabStructureKey, () => {\r\n if (\r\n currentTab.value &&\r\n !tabsConfig.value.tabs.some((tab) => tab.key === currentTab.value)\r\n ) {\r\n initializeCurrentTab();\r\n }\r\n});\r\n\r\n/* 监听配置变化 */\r\nwatch(\r\n () => tabsConfig.value.defaultTab,\r\n (newDefaultTab) => {\r\n if (newDefaultTab && newDefaultTab !== currentTab.value) {\r\n switchToTab(newDefaultTab);\r\n }\r\n },\r\n);\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n switchToTab,\r\n validateCurrentTab,\r\n currentTab: readonly(currentTab),\r\n totalTabs: computed(() => tabsWithItems.value.length),\r\n tabsWithItems: readonly(tabsWithItems),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 标签布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-tabs\">\r\n <!-- 无标签配置时的单一面板模式 -->\r\n <div v-if=\"!hasTabs\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有标签配置时的分标签模式 -->\r\n <div v-else class=\"tabs-container\">\r\n <NTabs\r\n v-model:value=\"currentTab\"\r\n :type=\"tabsConfig.type\"\r\n :size=\"tabsConfig.size\"\r\n :placement=\"tabsConfig.placement\"\r\n :animated=\"tabsConfig.animated\"\r\n :closable=\"tabsConfig.closable\"\r\n :addable=\"tabsConfig.addable\"\r\n class=\"form-tabs\"\r\n @update:value=\"handleTabChange\"\r\n @close=\"handleTabClose\"\r\n @add=\"handleTabAdd\"\r\n >\r\n <NTabPane\r\n v-for=\"tab in tabsWithItems\"\r\n :key=\"tab.config.key\"\r\n :name=\"tab.config.key\"\r\n :tab=\"tab.config.title\"\r\n :disabled=\"tab.config.disabled\"\r\n :closable=\"tab.config.closable\"\r\n >\r\n <template #tab>\r\n <NSpace align=\"center\" :size=\"8\">\r\n <C_Icon\r\n v-if=\"tab.config.icon\"\r\n :name=\"tab.config.icon\"\r\n :size=\"16\"\r\n class=\"tab-icon\"\r\n />\r\n <span>{{ tab.config.title }}</span>\r\n <NBadge\r\n v-if=\"tabsConfig.showCount\"\r\n :value=\"tab.items.length\"\r\n :max=\"99\"\r\n :show=\"tab.items.length > 0\"\r\n type=\"info\"\r\n />\r\n </NSpace>\r\n </template>\r\n\r\n <!-- 标签页头部信息 -->\r\n <div\r\n v-if=\"tabsConfig.showTabHeader && tab.config.description\"\r\n class=\"tab-header\"\r\n >\r\n <p class=\"tab-description\">{{ tab.config.description }}</p>\r\n </div>\r\n\r\n <!-- 标签页内的表单项 -->\r\n <div class=\"tab-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in tab.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 空状态 -->\r\n <NEmpty\r\n v-if=\"tab.items.length === 0\"\r\n description=\"暂无表单项\"\r\n class=\"tab-empty\"\r\n />\r\n </NTabPane>\r\n </NTabs>\r\n\r\n <!-- 标签页操作按钮 -->\r\n <div v-if=\"tabsConfig.showActions\" class=\"tabs-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NSpace>\r\n <NButton\r\n v-if=\"tabsConfig.validateBeforeSwitch\"\r\n type=\"primary\"\r\n size=\"small\"\r\n @click=\"validateCurrentTab\"\r\n >\r\n <C_Icon\r\n :name=\"'carbon:star-check'\"\r\n :size=\"14\"\r\n style=\"margin-right: 4px\"\r\n />\r\n 验证当前标签\r\n </NButton>\r\n </NSpace>\r\n\r\n <NSpace>\r\n <slot\r\n name=\"tab-actions\"\r\n :current-tab=\"currentTab\"\r\n :total-tabs=\"tabsWithItems.length\"\r\n :validate-tab=\"validateCurrentTab\"\r\n :switch-to-tab=\"switchToTab\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n ref,\r\n reactive,\r\n computed,\r\n watch,\r\n nextTick,\r\n onMounted,\r\n readonly,\r\n} from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface TabConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n disabled?: boolean;\r\n closable?: boolean;\r\n}\r\n\r\ninterface TabsLayoutConfig {\r\n tabs: TabConfig[];\r\n type?: \"line\" | \"card\" | \"segment\";\r\n size?: \"small\" | \"medium\" | \"large\";\r\n placement?: \"top\" | \"right\" | \"bottom\" | \"left\";\r\n animated?: boolean;\r\n closable?: boolean;\r\n addable?: boolean;\r\n showTabHeader?: boolean;\r\n showActions?: boolean;\r\n showCount?: boolean;\r\n validateBeforeSwitch?: boolean;\r\n defaultTab?: string;\r\n}\r\n\r\ninterface TabWithItems {\r\n config: TabConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n tabs?: TabsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n tab?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"tab-change\": [tabKey: string, tabIndex: number];\r\n \"tab-before-change\": [currentTab: string, targetTab: string];\r\n \"tab-validate\": [tabKey: string];\r\n \"tab-close\": [tabKey: string];\r\n \"tab-add\": [];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentTab = ref<string>(\"\");\r\nconst tabValidationStatus = reactive<Record<string, boolean>>({});\r\n\r\n/* ================= 默认配置 ================= */\r\nconst getDefaultTabsConfig = (): Required<TabsLayoutConfig> => ({\r\n tabs: [],\r\n type: \"line\",\r\n size: \"medium\",\r\n placement: \"top\",\r\n animated: true,\r\n closable: false,\r\n addable: false,\r\n showTabHeader: true,\r\n showActions: false,\r\n showCount: false,\r\n validateBeforeSwitch: false,\r\n defaultTab: \"\",\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst tabsConfig = computed(() => {\r\n const defaultConfig = getDefaultTabsConfig();\r\n const userConfig = props.layoutConfig?.tabs || {};\r\n\r\n return {\r\n ...defaultConfig,\r\n ...userConfig,\r\n };\r\n});\r\n\r\nconst hasTabs = computed((): boolean => {\r\n return tabsConfig.value.tabs.length > 0;\r\n});\r\n\r\nconst tabsWithItems = computed((): TabWithItems[] => {\r\n if (!hasTabs.value) return [];\r\n\r\n const tabMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化标签映射 */\r\n tabsConfig.value.tabs.forEach((tab) => {\r\n tabMap.set(tab.key, []);\r\n });\r\n\r\n /* 分配表单项到对应标签 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const tabKey =\r\n option?.layout?.tab || tabsConfig.value.tabs[0]?.key || \"default\";\r\n\r\n if (!tabMap.has(tabKey)) {\r\n tabMap.set(tabKey, []);\r\n }\r\n tabMap.get(tabKey)!.push(item);\r\n });\r\n\r\n /* 返回所有标签(包括空标签) */\r\n return tabsConfig.value.tabs.map((tabConfig) => ({\r\n config: tabConfig,\r\n items: tabMap.get(tabConfig.key) || [],\r\n }));\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `tab-item-${index}`;\r\n};\r\n\r\nconst validateCurrentTab = async (): Promise<boolean> => {\r\n if (!currentTab.value) return true;\r\n\r\n try {\r\n emit(\"tab-validate\", currentTab.value);\r\n const valid = true;\r\n tabValidationStatus[currentTab.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签验证失败:\", error);\r\n tabValidationStatus[currentTab.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToTab = async (targetTab: string): Promise<boolean> => {\r\n if (!targetTab || targetTab === currentTab.value) {\r\n return true;\r\n }\r\n\r\n const targetTabExists = tabsWithItems.value.some(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (!targetTabExists) {\r\n return false;\r\n }\r\n\r\n try {\r\n /* 验证当前标签(如果需要) */\r\n if (tabsConfig.value.validateBeforeSwitch && currentTab.value) {\r\n const isValid = await validateCurrentTab();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发标签切换前事件 */\r\n if (currentTab.value) {\r\n emit(\"tab-before-change\", currentTab.value, targetTab);\r\n }\r\n\r\n currentTab.value = targetTab;\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签切换失败:\", error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleTabChange = (tabKey: string): void => {\r\n switchToTab(tabKey);\r\n};\r\n\r\nconst handleTabClose = (tabKey: string): void => {\r\n emit(\"tab-close\", tabKey);\r\n};\r\n\r\nconst handleTabAdd = (): void => {\r\n emit(\"tab-add\");\r\n};\r\n\r\nconst initializeCurrentTab = (): void => {\r\n if (!hasTabs.value || tabsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultTab } = tabsConfig.value;\r\n const targetTab = defaultTab || tabsWithItems.value[0]?.config.key;\r\n\r\n if (targetTab && targetTab !== currentTab.value) {\r\n currentTab.value = targetTab;\r\n nextTick(() => {\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (tabIndex >= 0) {\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n }\r\n });\r\n }\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n initializeCurrentTab();\r\n});\r\n\r\n/* 只监听标签结构变化(key / 数量),不监听表单项内容变化 */\r\nconst tabStructureKey = computed(() =>\r\n tabsConfig.value.tabs.map((t) => t.key).join(\",\"),\r\n);\r\n\r\nwatch(tabStructureKey, () => {\r\n if (\r\n currentTab.value &&\r\n !tabsConfig.value.tabs.some((tab) => tab.key === currentTab.value)\r\n ) {\r\n initializeCurrentTab();\r\n }\r\n});\r\n\r\n/* 监听配置变化 */\r\nwatch(\r\n () => tabsConfig.value.defaultTab,\r\n (newDefaultTab) => {\r\n if (newDefaultTab && newDefaultTab !== currentTab.value) {\r\n switchToTab(newDefaultTab);\r\n }\r\n },\r\n);\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n switchToTab,\r\n validateCurrentTab,\r\n currentTab: readonly(currentTab),\r\n totalTabs: computed(() => tabsWithItems.value.length),\r\n tabsWithItems: readonly(tabsWithItems),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 标签布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-tabs\">\r\n <!-- 无标签配置时的单一面板模式 -->\r\n <div v-if=\"!hasTabs\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有标签配置时的分标签模式 -->\r\n <div v-else class=\"tabs-container\">\r\n <NTabs\r\n v-model:value=\"currentTab\"\r\n :type=\"tabsConfig.type\"\r\n :size=\"tabsConfig.size\"\r\n :placement=\"tabsConfig.placement\"\r\n :animated=\"tabsConfig.animated\"\r\n :closable=\"tabsConfig.closable\"\r\n :addable=\"tabsConfig.addable\"\r\n class=\"form-tabs\"\r\n @update:value=\"handleTabChange\"\r\n @close=\"handleTabClose\"\r\n @add=\"handleTabAdd\"\r\n >\r\n <NTabPane\r\n v-for=\"tab in tabsWithItems\"\r\n :key=\"tab.config.key\"\r\n :name=\"tab.config.key\"\r\n :tab=\"tab.config.title\"\r\n :disabled=\"tab.config.disabled\"\r\n :closable=\"tab.config.closable\"\r\n >\r\n <template #tab>\r\n <NSpace align=\"center\" :size=\"8\">\r\n <C_Icon\r\n v-if=\"tab.config.icon\"\r\n :name=\"tab.config.icon\"\r\n :size=\"16\"\r\n class=\"tab-icon\"\r\n />\r\n <span>{{ tab.config.title }}</span>\r\n <NBadge\r\n v-if=\"tabsConfig.showCount\"\r\n :value=\"tab.items.length\"\r\n :max=\"99\"\r\n :show=\"tab.items.length > 0\"\r\n type=\"info\"\r\n />\r\n </NSpace>\r\n </template>\r\n\r\n <!-- 标签页头部信息 -->\r\n <div\r\n v-if=\"tabsConfig.showTabHeader && tab.config.description\"\r\n class=\"tab-header\"\r\n >\r\n <p class=\"tab-description\">{{ tab.config.description }}</p>\r\n </div>\r\n\r\n <!-- 标签页内的表单项 -->\r\n <div class=\"tab-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in tab.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 空状态 -->\r\n <NEmpty\r\n v-if=\"tab.items.length === 0\"\r\n description=\"暂无表单项\"\r\n class=\"tab-empty\"\r\n />\r\n </NTabPane>\r\n </NTabs>\r\n\r\n <!-- 标签页操作按钮 -->\r\n <div v-if=\"tabsConfig.showActions\" class=\"tabs-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NSpace>\r\n <NButton\r\n v-if=\"tabsConfig.validateBeforeSwitch\"\r\n type=\"primary\"\r\n size=\"small\"\r\n @click=\"validateCurrentTab\"\r\n >\r\n <C_Icon\r\n :name=\"'carbon:star-check'\"\r\n :size=\"14\"\r\n style=\"margin-right: 4px\"\r\n />\r\n 验证当前标签\r\n </NButton>\r\n </NSpace>\r\n\r\n <NSpace>\r\n <slot\r\n name=\"tab-actions\"\r\n :current-tab=\"currentTab\"\r\n :total-tabs=\"tabsWithItems.length\"\r\n :validate-tab=\"validateCurrentTab\"\r\n :switch-to-tab=\"switchToTab\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n ref,\r\n reactive,\r\n computed,\r\n watch,\r\n nextTick,\r\n onMounted,\r\n readonly,\r\n} from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface TabConfig {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n icon?: string;\r\n disabled?: boolean;\r\n closable?: boolean;\r\n}\r\n\r\ninterface TabsLayoutConfig {\r\n tabs: TabConfig[];\r\n type?: \"line\" | \"card\" | \"segment\";\r\n size?: \"small\" | \"medium\" | \"large\";\r\n placement?: \"top\" | \"right\" | \"bottom\" | \"left\";\r\n animated?: boolean;\r\n closable?: boolean;\r\n addable?: boolean;\r\n showTabHeader?: boolean;\r\n showActions?: boolean;\r\n showCount?: boolean;\r\n validateBeforeSwitch?: boolean;\r\n defaultTab?: string;\r\n}\r\n\r\ninterface TabWithItems {\r\n config: TabConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n tabs?: TabsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n tab?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"tab-change\": [tabKey: string, tabIndex: number];\r\n \"tab-before-change\": [currentTab: string, targetTab: string];\r\n \"tab-validate\": [tabKey: string];\r\n \"tab-close\": [tabKey: string];\r\n \"tab-add\": [];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentTab = ref<string>(\"\");\r\nconst tabValidationStatus = reactive<Record<string, boolean>>({});\r\n\r\n/* ================= 默认配置 ================= */\r\nconst getDefaultTabsConfig = (): Required<TabsLayoutConfig> => ({\r\n tabs: [],\r\n type: \"line\",\r\n size: \"medium\",\r\n placement: \"top\",\r\n animated: true,\r\n closable: false,\r\n addable: false,\r\n showTabHeader: true,\r\n showActions: false,\r\n showCount: false,\r\n validateBeforeSwitch: false,\r\n defaultTab: \"\",\r\n});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst tabsConfig = computed(() => {\r\n const defaultConfig = getDefaultTabsConfig();\r\n const userConfig = props.layoutConfig?.tabs || {};\r\n\r\n return {\r\n ...defaultConfig,\r\n ...userConfig,\r\n };\r\n});\r\n\r\nconst hasTabs = computed((): boolean => {\r\n return tabsConfig.value.tabs.length > 0;\r\n});\r\n\r\nconst tabsWithItems = computed((): TabWithItems[] => {\r\n if (!hasTabs.value) return [];\r\n\r\n const tabMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化标签映射 */\r\n tabsConfig.value.tabs.forEach((tab) => {\r\n tabMap.set(tab.key, []);\r\n });\r\n\r\n /* 分配表单项到对应标签 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const tabKey =\r\n option?.layout?.tab || tabsConfig.value.tabs[0]?.key || \"default\";\r\n\r\n if (!tabMap.has(tabKey)) {\r\n tabMap.set(tabKey, []);\r\n }\r\n tabMap.get(tabKey)!.push(item);\r\n });\r\n\r\n /* 返回所有标签(包括空标签) */\r\n return tabsConfig.value.tabs.map((tabConfig) => ({\r\n config: tabConfig,\r\n items: tabMap.get(tabConfig.key) || [],\r\n }));\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `tab-item-${index}`;\r\n};\r\n\r\nconst validateCurrentTab = async (): Promise<boolean> => {\r\n if (!currentTab.value) return true;\r\n\r\n try {\r\n emit(\"tab-validate\", currentTab.value);\r\n const valid = true;\r\n tabValidationStatus[currentTab.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签验证失败:\", error);\r\n tabValidationStatus[currentTab.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToTab = async (targetTab: string): Promise<boolean> => {\r\n if (!targetTab || targetTab === currentTab.value) {\r\n return true;\r\n }\r\n\r\n const targetTabExists = tabsWithItems.value.some(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (!targetTabExists) {\r\n return false;\r\n }\r\n\r\n try {\r\n /* 验证当前标签(如果需要) */\r\n if (tabsConfig.value.validateBeforeSwitch && currentTab.value) {\r\n const isValid = await validateCurrentTab();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发标签切换前事件 */\r\n if (currentTab.value) {\r\n emit(\"tab-before-change\", currentTab.value, targetTab);\r\n }\r\n\r\n currentTab.value = targetTab;\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Tabs Layout] 标签切换失败:\", error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleTabChange = (tabKey: string): void => {\r\n switchToTab(tabKey);\r\n};\r\n\r\nconst handleTabClose = (tabKey: string): void => {\r\n emit(\"tab-close\", tabKey);\r\n};\r\n\r\nconst handleTabAdd = (): void => {\r\n emit(\"tab-add\");\r\n};\r\n\r\nconst initializeCurrentTab = (): void => {\r\n if (!hasTabs.value || tabsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultTab } = tabsConfig.value;\r\n const targetTab = defaultTab || tabsWithItems.value[0]?.config.key;\r\n\r\n if (targetTab && targetTab !== currentTab.value) {\r\n currentTab.value = targetTab;\r\n nextTick(() => {\r\n const tabIndex = tabsWithItems.value.findIndex(\r\n (tab) => tab.config.key === targetTab,\r\n );\r\n if (tabIndex >= 0) {\r\n emit(\"tab-change\", targetTab, tabIndex);\r\n }\r\n });\r\n }\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\nonMounted(() => {\r\n initializeCurrentTab();\r\n});\r\n\r\n/* 只监听标签结构变化(key / 数量),不监听表单项内容变化 */\r\nconst tabStructureKey = computed(() =>\r\n tabsConfig.value.tabs.map((t) => t.key).join(\",\"),\r\n);\r\n\r\nwatch(tabStructureKey, () => {\r\n if (\r\n currentTab.value &&\r\n !tabsConfig.value.tabs.some((tab) => tab.key === currentTab.value)\r\n ) {\r\n initializeCurrentTab();\r\n }\r\n});\r\n\r\n/* 监听配置变化 */\r\nwatch(\r\n () => tabsConfig.value.defaultTab,\r\n (newDefaultTab) => {\r\n if (newDefaultTab && newDefaultTab !== currentTab.value) {\r\n switchToTab(newDefaultTab);\r\n }\r\n },\r\n);\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n switchToTab,\r\n validateCurrentTab,\r\n currentTab: readonly(currentTab),\r\n totalTabs: computed(() => tabsWithItems.value.length),\r\n tabsWithItems: readonly(tabsWithItems),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 步骤表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-steps\">\r\n <!-- 无步骤配置时的单一面板模式 -->\r\n <div v-if=\"!hasSteps\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有步骤配置时的分步骤模式 -->\r\n <div v-else class=\"steps-container\">\r\n <!-- 步骤指示器 -->\r\n <NSteps\r\n :current=\"currentStep + 1\"\r\n :status=\"stepStatus\"\r\n :size=\"stepsConfig.size\"\r\n :vertical=\"stepsConfig.vertical\"\r\n class=\"steps-indicator\"\r\n >\r\n <NStep\r\n v-for=\"step in stepsWithItems\"\r\n :key=\"step.config.key\"\r\n :title=\"step.config.title\"\r\n :description=\"step.config.description\"\r\n :disabled=\"step.config.disabled\"\r\n />\r\n </NSteps>\r\n\r\n <!-- 步骤内容区域 -->\r\n <NCard class=\"steps-content\" :bordered=\"false\">\r\n <div\r\n v-for=\"(step, index) in stepsWithItems\"\r\n v-show=\"currentStep === index\"\r\n :key=\"step.config.key\"\r\n class=\"step-panel\"\r\n >\r\n <!-- 步骤标题和描述 -->\r\n <div v-if=\"stepsConfig.showStepHeader\" class=\"step-header\">\r\n <h3 class=\"step-title\">{{ step.config.title }}</h3>\r\n <p v-if=\"step.config.description\" class=\"step-description\">\r\n {{ step.config.description }}\r\n </p>\r\n </div>\r\n\r\n <!-- 步骤内的表单项 -->\r\n <div class=\"step-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in step.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 步骤操作按钮 -->\r\n <div class=\"steps-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NButton\r\n v-if=\"currentStep > 0\"\r\n :disabled=\"loading\"\r\n @click=\"handlePreviousStep\"\r\n >\r\n <C_Icon\r\n :name=\"'mdi:chevron-left-first'\"\r\n :size=\"16\"\r\n style=\"margin-right: 4px\"\r\n />\r\n {{ stepsConfig.prevButtonText }}\r\n </NButton>\r\n\r\n <div></div>\r\n\r\n <NSpace>\r\n <NButton\r\n v-if=\"currentStep < stepsWithItems.length - 1\"\r\n type=\"primary\"\r\n :loading=\"loading\"\r\n @click=\"handleNextStep\"\r\n >\r\n {{ stepsConfig.nextButtonText }}\r\n <C_Icon\r\n :name=\"'mdi:chevron-right-last'\"\r\n :size=\"16\"\r\n style=\"margin-left: 4px\"\r\n />\r\n </NButton>\r\n\r\n <slot\r\n name=\"step-actions\"\r\n :current-step=\"currentStep\"\r\n :total-steps=\"stepsWithItems.length\"\r\n :is-first-step=\"isFirstStep\"\r\n :is-last-step=\"isLastStep\"\r\n :next-step=\"handleNextStep\"\r\n :previous-step=\"handlePreviousStep\"\r\n :go-to-step=\"goToStep\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, onMounted, readonly } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { StepConfig, StepsLayoutConfig } from \"../../types\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface StepWithItems {\r\n config: StepConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n steps?: StepsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n step?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"step-change\": [stepIndex: number, stepKey: string];\r\n \"step-before-change\": [currentStep: number, targetStep: number];\r\n \"step-validate\": [stepIndex: number];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentStep = ref<number>(0);\r\nconst loading = ref<boolean>(false);\r\nconst stepValidationStatus = reactive<Record<number, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst stepsConfig = computed(() => {\r\n const config = props.layoutConfig?.steps || {};\r\n return {\r\n steps: config.steps || [],\r\n vertical: config.vertical || false,\r\n size: config.size || \"medium\",\r\n showStepHeader: config.showStepHeader !== false,\r\n validateBeforeNext: config.validateBeforeNext || false,\r\n prevButtonText: config.prevButtonText || \"上一步\",\r\n nextButtonText: config.nextButtonText || \"下一步\",\r\n defaultStep: config.defaultStep || 0,\r\n };\r\n});\r\n\r\nconst hasSteps = computed((): boolean => {\r\n return stepsConfig.value.steps.length > 0;\r\n});\r\n\r\nconst stepsWithItems = computed((): StepWithItems[] => {\r\n if (!hasSteps.value) return [];\r\n\r\n const stepMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化步骤映射 */\r\n stepsConfig.value.steps.forEach((step) => {\r\n stepMap.set(step.key, []);\r\n });\r\n\r\n /* 分配表单项到对应步骤 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const stepKey =\r\n option?.layout?.step || stepsConfig.value.steps[0]?.key || \"default\";\r\n\r\n if (!stepMap.has(stepKey)) {\r\n stepMap.set(stepKey, []);\r\n }\r\n stepMap.get(stepKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的步骤 */\r\n return stepsConfig.value.steps\r\n .map((stepConfig) => ({\r\n config: stepConfig,\r\n items: stepMap.get(stepConfig.key) || [],\r\n }))\r\n .filter((step) => step.items.length > 0);\r\n});\r\n\r\nconst stepStatus = computed(() => {\r\n for (let i = 0; i <= currentStep.value; i++) {\r\n if (stepValidationStatus[i] === false) {\r\n return \"error\";\r\n }\r\n }\r\n return \"process\";\r\n});\r\n\r\nconst isFirstStep = computed((): boolean => {\r\n return currentStep.value === 0;\r\n});\r\n\r\nconst isLastStep = computed((): boolean => {\r\n return currentStep.value === stepsWithItems.value.length - 1;\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `step-item-${index}`;\r\n};\r\n\r\nconst validateCurrentStep = async (): Promise<boolean> => {\r\n try {\r\n const result = await Promise.resolve(\r\n emit(\"step-validate\", currentStep.value) as unknown as\r\n | boolean\r\n | Promise<boolean>,\r\n );\r\n const valid = result !== false;\r\n stepValidationStatus[currentStep.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤验证失败:\", error);\r\n stepValidationStatus[currentStep.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToStep = async (\r\n targetStep: number,\r\n needValidation = false,\r\n): Promise<boolean> => {\r\n if (targetStep < 0 || targetStep >= stepsWithItems.value.length) {\r\n return false;\r\n }\r\n\r\n if (targetStep === currentStep.value) {\r\n return true;\r\n }\r\n\r\n try {\r\n loading.value = true;\r\n\r\n /* 验证步骤(如果需要) */\r\n if (needValidation && stepsConfig.value.validateBeforeNext) {\r\n const isValid = await validateCurrentStep();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发步骤切换前事件 */\r\n await emit(\"step-before-change\", currentStep.value, targetStep);\r\n\r\n currentStep.value = targetStep;\r\n emit(\r\n \"step-change\",\r\n currentStep.value,\r\n stepsWithItems.value[currentStep.value].config.key,\r\n );\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤切换失败:\", error);\r\n return false;\r\n } finally {\r\n loading.value = false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleNextStep = async (): Promise<void> => {\r\n await switchToStep(currentStep.value + 1, true);\r\n};\r\n\r\nconst handlePreviousStep = (): void => {\r\n switchToStep(currentStep.value - 1);\r\n};\r\n\r\nconst goToStep = async (stepIndex: number): Promise<void> => {\r\n if (stepsWithItems.value[stepIndex]?.config.disabled) {\r\n return;\r\n }\r\n\r\n const needValidation = stepIndex > currentStep.value;\r\n await switchToStep(stepIndex, needValidation);\r\n};\r\n\r\nconst initializeCurrentStep = (): void => {\r\n if (!hasSteps.value || stepsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultStep } = stepsConfig.value;\r\n const isValidDefaultStep =\r\n defaultStep >= 0 &&\r\n defaultStep < stepsWithItems.value.length &&\r\n !stepsWithItems.value[defaultStep]?.config.disabled;\r\n\r\n currentStep.value = isValidDefaultStep ? defaultStep : 0;\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\n\r\n/* 只监听步骤结构变化(key / 数量),不监听表单项内容变化 */\r\nconst stepStructureKey = computed(() =>\r\n stepsConfig.value.steps.map((s) => s.key).join(\",\"),\r\n);\r\n\r\nwatch(stepStructureKey, () => {\r\n initializeCurrentStep();\r\n});\r\n\r\nonMounted(() => {\r\n initializeCurrentStep();\r\n});\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n nextStep: handleNextStep,\r\n previousStep: handlePreviousStep,\r\n goToStep,\r\n validateCurrentStep,\r\n currentStep: readonly(currentStep),\r\n totalSteps: computed(() => stepsWithItems.value.length),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 步骤表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-steps\">\r\n <!-- 无步骤配置时的单一面板模式 -->\r\n <div v-if=\"!hasSteps\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有步骤配置时的分步骤模式 -->\r\n <div v-else class=\"steps-container\">\r\n <!-- 步骤指示器 -->\r\n <NSteps\r\n :current=\"currentStep + 1\"\r\n :status=\"stepStatus\"\r\n :size=\"stepsConfig.size\"\r\n :vertical=\"stepsConfig.vertical\"\r\n class=\"steps-indicator\"\r\n >\r\n <NStep\r\n v-for=\"step in stepsWithItems\"\r\n :key=\"step.config.key\"\r\n :title=\"step.config.title\"\r\n :description=\"step.config.description\"\r\n :disabled=\"step.config.disabled\"\r\n />\r\n </NSteps>\r\n\r\n <!-- 步骤内容区域 -->\r\n <NCard class=\"steps-content\" :bordered=\"false\">\r\n <div\r\n v-for=\"(step, index) in stepsWithItems\"\r\n v-show=\"currentStep === index\"\r\n :key=\"step.config.key\"\r\n class=\"step-panel\"\r\n >\r\n <!-- 步骤标题和描述 -->\r\n <div v-if=\"stepsConfig.showStepHeader\" class=\"step-header\">\r\n <h3 class=\"step-title\">{{ step.config.title }}</h3>\r\n <p v-if=\"step.config.description\" class=\"step-description\">\r\n {{ step.config.description }}\r\n </p>\r\n </div>\r\n\r\n <!-- 步骤内的表单项 -->\r\n <div class=\"step-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in step.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 步骤操作按钮 -->\r\n <div class=\"steps-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NButton\r\n v-if=\"currentStep > 0\"\r\n :disabled=\"loading\"\r\n @click=\"handlePreviousStep\"\r\n >\r\n <C_Icon\r\n :name=\"'mdi:chevron-left-first'\"\r\n :size=\"16\"\r\n style=\"margin-right: 4px\"\r\n />\r\n {{ stepsConfig.prevButtonText }}\r\n </NButton>\r\n\r\n <div></div>\r\n\r\n <NSpace>\r\n <NButton\r\n v-if=\"currentStep < stepsWithItems.length - 1\"\r\n type=\"primary\"\r\n :loading=\"loading\"\r\n @click=\"handleNextStep\"\r\n >\r\n {{ stepsConfig.nextButtonText }}\r\n <C_Icon\r\n :name=\"'mdi:chevron-right-last'\"\r\n :size=\"16\"\r\n style=\"margin-left: 4px\"\r\n />\r\n </NButton>\r\n\r\n <slot\r\n name=\"step-actions\"\r\n :current-step=\"currentStep\"\r\n :total-steps=\"stepsWithItems.length\"\r\n :is-first-step=\"isFirstStep\"\r\n :is-last-step=\"isLastStep\"\r\n :next-step=\"handleNextStep\"\r\n :previous-step=\"handlePreviousStep\"\r\n :go-to-step=\"goToStep\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, onMounted, readonly } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { StepConfig, StepsLayoutConfig } from \"../../types\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface StepWithItems {\r\n config: StepConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n steps?: StepsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n step?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"step-change\": [stepIndex: number, stepKey: string];\r\n \"step-before-change\": [currentStep: number, targetStep: number];\r\n \"step-validate\": [stepIndex: number];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentStep = ref<number>(0);\r\nconst loading = ref<boolean>(false);\r\nconst stepValidationStatus = reactive<Record<number, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst stepsConfig = computed(() => {\r\n const config = props.layoutConfig?.steps || {};\r\n return {\r\n steps: config.steps || [],\r\n vertical: config.vertical || false,\r\n size: config.size || \"medium\",\r\n showStepHeader: config.showStepHeader !== false,\r\n validateBeforeNext: config.validateBeforeNext || false,\r\n prevButtonText: config.prevButtonText || \"上一步\",\r\n nextButtonText: config.nextButtonText || \"下一步\",\r\n defaultStep: config.defaultStep || 0,\r\n };\r\n});\r\n\r\nconst hasSteps = computed((): boolean => {\r\n return stepsConfig.value.steps.length > 0;\r\n});\r\n\r\nconst stepsWithItems = computed((): StepWithItems[] => {\r\n if (!hasSteps.value) return [];\r\n\r\n const stepMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化步骤映射 */\r\n stepsConfig.value.steps.forEach((step) => {\r\n stepMap.set(step.key, []);\r\n });\r\n\r\n /* 分配表单项到对应步骤 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const stepKey =\r\n option?.layout?.step || stepsConfig.value.steps[0]?.key || \"default\";\r\n\r\n if (!stepMap.has(stepKey)) {\r\n stepMap.set(stepKey, []);\r\n }\r\n stepMap.get(stepKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的步骤 */\r\n return stepsConfig.value.steps\r\n .map((stepConfig) => ({\r\n config: stepConfig,\r\n items: stepMap.get(stepConfig.key) || [],\r\n }))\r\n .filter((step) => step.items.length > 0);\r\n});\r\n\r\nconst stepStatus = computed(() => {\r\n for (let i = 0; i <= currentStep.value; i++) {\r\n if (stepValidationStatus[i] === false) {\r\n return \"error\";\r\n }\r\n }\r\n return \"process\";\r\n});\r\n\r\nconst isFirstStep = computed((): boolean => {\r\n return currentStep.value === 0;\r\n});\r\n\r\nconst isLastStep = computed((): boolean => {\r\n return currentStep.value === stepsWithItems.value.length - 1;\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `step-item-${index}`;\r\n};\r\n\r\nconst validateCurrentStep = async (): Promise<boolean> => {\r\n try {\r\n const result = await Promise.resolve(\r\n emit(\"step-validate\", currentStep.value) as unknown as\r\n | boolean\r\n | Promise<boolean>,\r\n );\r\n const valid = result !== false;\r\n stepValidationStatus[currentStep.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤验证失败:\", error);\r\n stepValidationStatus[currentStep.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToStep = async (\r\n targetStep: number,\r\n needValidation = false,\r\n): Promise<boolean> => {\r\n if (targetStep < 0 || targetStep >= stepsWithItems.value.length) {\r\n return false;\r\n }\r\n\r\n if (targetStep === currentStep.value) {\r\n return true;\r\n }\r\n\r\n try {\r\n loading.value = true;\r\n\r\n /* 验证步骤(如果需要) */\r\n if (needValidation && stepsConfig.value.validateBeforeNext) {\r\n const isValid = await validateCurrentStep();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发步骤切换前事件 */\r\n await emit(\"step-before-change\", currentStep.value, targetStep);\r\n\r\n currentStep.value = targetStep;\r\n emit(\r\n \"step-change\",\r\n currentStep.value,\r\n stepsWithItems.value[currentStep.value].config.key,\r\n );\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤切换失败:\", error);\r\n return false;\r\n } finally {\r\n loading.value = false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleNextStep = async (): Promise<void> => {\r\n await switchToStep(currentStep.value + 1, true);\r\n};\r\n\r\nconst handlePreviousStep = (): void => {\r\n switchToStep(currentStep.value - 1);\r\n};\r\n\r\nconst goToStep = async (stepIndex: number): Promise<void> => {\r\n if (stepsWithItems.value[stepIndex]?.config.disabled) {\r\n return;\r\n }\r\n\r\n const needValidation = stepIndex > currentStep.value;\r\n await switchToStep(stepIndex, needValidation);\r\n};\r\n\r\nconst initializeCurrentStep = (): void => {\r\n if (!hasSteps.value || stepsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultStep } = stepsConfig.value;\r\n const isValidDefaultStep =\r\n defaultStep >= 0 &&\r\n defaultStep < stepsWithItems.value.length &&\r\n !stepsWithItems.value[defaultStep]?.config.disabled;\r\n\r\n currentStep.value = isValidDefaultStep ? defaultStep : 0;\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\n\r\n/* 只监听步骤结构变化(key / 数量),不监听表单项内容变化 */\r\nconst stepStructureKey = computed(() =>\r\n stepsConfig.value.steps.map((s) => s.key).join(\",\"),\r\n);\r\n\r\nwatch(stepStructureKey, () => {\r\n initializeCurrentStep();\r\n});\r\n\r\nonMounted(() => {\r\n initializeCurrentStep();\r\n});\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n nextStep: handleNextStep,\r\n previousStep: handlePreviousStep,\r\n goToStep,\r\n validateCurrentStep,\r\n currentStep: readonly(currentStep),\r\n totalSteps: computed(() => stepsWithItems.value.length),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 步骤表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-steps\">\r\n <!-- 无步骤配置时的单一面板模式 -->\r\n <div v-if=\"!hasSteps\" class=\"single-panel\">\r\n <component\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n\r\n <!-- 有步骤配置时的分步骤模式 -->\r\n <div v-else class=\"steps-container\">\r\n <!-- 步骤指示器 -->\r\n <NSteps\r\n :current=\"currentStep + 1\"\r\n :status=\"stepStatus\"\r\n :size=\"stepsConfig.size\"\r\n :vertical=\"stepsConfig.vertical\"\r\n class=\"steps-indicator\"\r\n >\r\n <NStep\r\n v-for=\"step in stepsWithItems\"\r\n :key=\"step.config.key\"\r\n :title=\"step.config.title\"\r\n :description=\"step.config.description\"\r\n :disabled=\"step.config.disabled\"\r\n />\r\n </NSteps>\r\n\r\n <!-- 步骤内容区域 -->\r\n <NCard class=\"steps-content\" :bordered=\"false\">\r\n <div\r\n v-for=\"(step, index) in stepsWithItems\"\r\n v-show=\"currentStep === index\"\r\n :key=\"step.config.key\"\r\n class=\"step-panel\"\r\n >\r\n <!-- 步骤标题和描述 -->\r\n <div v-if=\"stepsConfig.showStepHeader\" class=\"step-header\">\r\n <h3 class=\"step-title\">{{ step.config.title }}</h3>\r\n <p v-if=\"step.config.description\" class=\"step-description\">\r\n {{ step.config.description }}\r\n </p>\r\n </div>\r\n\r\n <!-- 步骤内的表单项 -->\r\n <div class=\"step-form-items\">\r\n <component\r\n v-for=\"(item, itemIndex) in step.items\"\r\n :key=\"getItemKey(item, itemIndex)\"\r\n :is=\"item\"\r\n />\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 步骤操作按钮 -->\r\n <div class=\"steps-actions\">\r\n <NSpace justify=\"space-between\">\r\n <NButton\r\n v-if=\"currentStep > 0\"\r\n :disabled=\"loading\"\r\n @click=\"handlePreviousStep\"\r\n >\r\n <C_Icon\r\n :name=\"'mdi:chevron-left-first'\"\r\n :size=\"16\"\r\n style=\"margin-right: 4px\"\r\n />\r\n {{ stepsConfig.prevButtonText }}\r\n </NButton>\r\n\r\n <div></div>\r\n\r\n <NSpace>\r\n <NButton\r\n v-if=\"currentStep < stepsWithItems.length - 1\"\r\n type=\"primary\"\r\n :loading=\"loading\"\r\n @click=\"handleNextStep\"\r\n >\r\n {{ stepsConfig.nextButtonText }}\r\n <C_Icon\r\n :name=\"'mdi:chevron-right-last'\"\r\n :size=\"16\"\r\n style=\"margin-left: 4px\"\r\n />\r\n </NButton>\r\n\r\n <slot\r\n name=\"step-actions\"\r\n :current-step=\"currentStep\"\r\n :total-steps=\"stepsWithItems.length\"\r\n :is-first-step=\"isFirstStep\"\r\n :is-last-step=\"isLastStep\"\r\n :next-step=\"handleNextStep\"\r\n :previous-step=\"handlePreviousStep\"\r\n :go-to-step=\"goToStep\"\r\n />\r\n </NSpace>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, reactive, computed, watch, onMounted, readonly } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { StepConfig, StepsLayoutConfig } from \"../../types\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 类型定义 ================= */\r\ninterface StepWithItems {\r\n config: StepConfig;\r\n items: VNode[];\r\n}\r\n\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: {\r\n steps?: StepsLayoutConfig;\r\n };\r\n options?: Array<{\r\n layout?: {\r\n step?: string;\r\n };\r\n }>;\r\n}\r\n\r\n/* ================= 组件属性和事件 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"step-change\": [stepIndex: number, stepKey: string];\r\n \"step-before-change\": [currentStep: number, targetStep: number];\r\n \"step-validate\": [stepIndex: number];\r\n}>();\r\n\r\n/* ================= 响应式状态 ================= */\r\nconst currentStep = ref<number>(0);\r\nconst loading = ref<boolean>(false);\r\nconst stepValidationStatus = reactive<Record<number, boolean>>({});\r\n\r\n/* ================= 计算属性 ================= */\r\nconst stepsConfig = computed(() => {\r\n const config = props.layoutConfig?.steps || {};\r\n return {\r\n steps: config.steps || [],\r\n vertical: config.vertical || false,\r\n size: config.size || \"medium\",\r\n showStepHeader: config.showStepHeader !== false,\r\n validateBeforeNext: config.validateBeforeNext || false,\r\n prevButtonText: config.prevButtonText || \"上一步\",\r\n nextButtonText: config.nextButtonText || \"下一步\",\r\n defaultStep: config.defaultStep || 0,\r\n };\r\n});\r\n\r\nconst hasSteps = computed((): boolean => {\r\n return stepsConfig.value.steps.length > 0;\r\n});\r\n\r\nconst stepsWithItems = computed((): StepWithItems[] => {\r\n if (!hasSteps.value) return [];\r\n\r\n const stepMap = new Map<string, VNode[]>();\r\n\r\n /* 初始化步骤映射 */\r\n stepsConfig.value.steps.forEach((step) => {\r\n stepMap.set(step.key, []);\r\n });\r\n\r\n /* 分配表单项到对应步骤 */\r\n props.formItems.forEach((item, index) => {\r\n const option = props.options?.[index];\r\n const stepKey =\r\n option?.layout?.step || stepsConfig.value.steps[0]?.key || \"default\";\r\n\r\n if (!stepMap.has(stepKey)) {\r\n stepMap.set(stepKey, []);\r\n }\r\n stepMap.get(stepKey)!.push(item);\r\n });\r\n\r\n /* 只返回有表单项的步骤 */\r\n return stepsConfig.value.steps\r\n .map((stepConfig) => ({\r\n config: stepConfig,\r\n items: stepMap.get(stepConfig.key) || [],\r\n }))\r\n .filter((step) => step.items.length > 0);\r\n});\r\n\r\nconst stepStatus = computed(() => {\r\n for (let i = 0; i <= currentStep.value; i++) {\r\n if (stepValidationStatus[i] === false) {\r\n return \"error\";\r\n }\r\n }\r\n return \"process\";\r\n});\r\n\r\nconst isFirstStep = computed((): boolean => {\r\n return currentStep.value === 0;\r\n});\r\n\r\nconst isLastStep = computed((): boolean => {\r\n return currentStep.value === stepsWithItems.value.length - 1;\r\n});\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n if (item.key != null) {\r\n return String(item.key);\r\n }\r\n\r\n const itemProps = item.props as Record<string, any> | null;\r\n if (itemProps?.path) {\r\n return itemProps.path;\r\n }\r\n\r\n return `step-item-${index}`;\r\n};\r\n\r\nconst validateCurrentStep = async (): Promise<boolean> => {\r\n try {\r\n const result = await Promise.resolve(\r\n emit(\"step-validate\", currentStep.value) as unknown as\r\n | boolean\r\n | Promise<boolean>,\r\n );\r\n const valid = result !== false;\r\n stepValidationStatus[currentStep.value] = valid;\r\n return valid;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤验证失败:\", error);\r\n stepValidationStatus[currentStep.value] = false;\r\n return false;\r\n }\r\n};\r\n\r\nconst switchToStep = async (\r\n targetStep: number,\r\n needValidation = false,\r\n): Promise<boolean> => {\r\n if (targetStep < 0 || targetStep >= stepsWithItems.value.length) {\r\n return false;\r\n }\r\n\r\n if (targetStep === currentStep.value) {\r\n return true;\r\n }\r\n\r\n try {\r\n loading.value = true;\r\n\r\n /* 验证步骤(如果需要) */\r\n if (needValidation && stepsConfig.value.validateBeforeNext) {\r\n const isValid = await validateCurrentStep();\r\n if (!isValid) {\r\n return false;\r\n }\r\n }\r\n\r\n /* 触发步骤切换前事件 */\r\n await emit(\"step-before-change\", currentStep.value, targetStep);\r\n\r\n currentStep.value = targetStep;\r\n emit(\r\n \"step-change\",\r\n currentStep.value,\r\n stepsWithItems.value[currentStep.value].config.key,\r\n );\r\n return true;\r\n } catch (error) {\r\n console.error(\"[Steps Layout] 步骤切换失败:\", error);\r\n return false;\r\n } finally {\r\n loading.value = false;\r\n }\r\n};\r\n\r\n/* ================= 事件处理方法 ================= */\r\nconst handleNextStep = async (): Promise<void> => {\r\n await switchToStep(currentStep.value + 1, true);\r\n};\r\n\r\nconst handlePreviousStep = (): void => {\r\n switchToStep(currentStep.value - 1);\r\n};\r\n\r\nconst goToStep = async (stepIndex: number): Promise<void> => {\r\n if (stepsWithItems.value[stepIndex]?.config.disabled) {\r\n return;\r\n }\r\n\r\n const needValidation = stepIndex > currentStep.value;\r\n await switchToStep(stepIndex, needValidation);\r\n};\r\n\r\nconst initializeCurrentStep = (): void => {\r\n if (!hasSteps.value || stepsWithItems.value.length === 0) {\r\n return;\r\n }\r\n\r\n const { defaultStep } = stepsConfig.value;\r\n const isValidDefaultStep =\r\n defaultStep >= 0 &&\r\n defaultStep < stepsWithItems.value.length &&\r\n !stepsWithItems.value[defaultStep]?.config.disabled;\r\n\r\n currentStep.value = isValidDefaultStep ? defaultStep : 0;\r\n};\r\n\r\n/* ================= 生命周期 ================= */\r\n\r\n/* 只监听步骤结构变化(key / 数量),不监听表单项内容变化 */\r\nconst stepStructureKey = computed(() =>\r\n stepsConfig.value.steps.map((s) => s.key).join(\",\"),\r\n);\r\n\r\nwatch(stepStructureKey, () => {\r\n initializeCurrentStep();\r\n});\r\n\r\nonMounted(() => {\r\n initializeCurrentStep();\r\n});\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n nextStep: handleNextStep,\r\n previousStep: handlePreviousStep,\r\n goToStep,\r\n validateCurrentStep,\r\n currentStep: readonly(currentStep),\r\n totalSteps: computed(() => stepsWithItems.value.length),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","/**\r\n * @description 动态表单状态管理组合式函数\r\n * @module useDynamicFormState\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * @returns 包含动态表单状态和操作方法的对象\r\n */\r\n\r\nimport { reactive, computed, readonly } from 'vue'\r\nimport type {\r\n FormOption,\r\n ComponentType,\r\n DynamicFieldConfig,\r\n DynamicFormConfig,\r\n DynamicFormState,\r\n} from '../types'\r\n\r\n/**\r\n * @description 默认表单配置\r\n */\r\nconst DEFAULT_CONFIG: DynamicFormConfig = {\r\n maxFields: 20,\r\n autoSave: false,\r\n enableSort: true,\r\n showControls: true,\r\n showItemControls: true,\r\n}\r\n\r\n/**\r\n * @description 可用的字段类型选项\r\n */\r\nexport const FIELD_TYPE_OPTIONS = [\r\n { label: '文本输入', value: 'input' as ComponentType },\r\n { label: '数字输入', value: 'inputNumber' as ComponentType },\r\n { label: '多行文本', value: 'textarea' as ComponentType },\r\n { label: '下拉选择', value: 'select' as ComponentType },\r\n { label: '开关切换', value: 'switch' as ComponentType },\r\n { label: '评分组件', value: 'rate' as ComponentType },\r\n]\r\n\r\n/**\r\n * @description 创建和管理动态表单状态\r\n * @returns 包含状态和方法的对象\r\n */\r\nexport const useDynamicFormState = () => {\r\n /**\r\n * @description 响应式表单状态\r\n */\r\n const state = reactive<DynamicFormState>({\r\n config: { ...DEFAULT_CONFIG },\r\n baseFields: [],\r\n dynamicFields: [],\r\n hiddenFieldIds: new Set<string>(),\r\n fieldCounter: 0,\r\n isInitialized: false,\r\n })\r\n\r\n /**\r\n * @description 计算所有字段(基础字段+动态字段)\r\n */\r\n const allFields = computed<FormOption[]>(() => [\r\n ...state.baseFields,\r\n ...state.dynamicFields.map(field => ({\r\n ...field,\r\n show: !state.hiddenFieldIds.has(field.prop),\r\n })),\r\n ])\r\n\r\n /**\r\n * @description 计算可见字段\r\n */\r\n const visibleFields = computed<FormOption[]>(() =>\r\n allFields.value.filter(field => field.show !== false)\r\n )\r\n\r\n /**\r\n * @description 计算动态字段数量\r\n */\r\n const dynamicFieldsCount = computed(() => state.dynamicFields.length)\r\n\r\n /**\r\n * @description 计算隐藏字段数量\r\n */\r\n const hiddenFieldsCount = computed(() => state.hiddenFieldIds.size)\r\n\r\n /**\r\n * @description 是否可以添加更多字段\r\n */\r\n const canAddMoreFields = computed(\r\n () => state.dynamicFields.length < state.config.maxFields\r\n )\r\n\r\n /**\r\n * @description 是否所有字段都可见\r\n */\r\n const allVisible = computed(() => state.hiddenFieldIds.size === 0)\r\n\r\n /**\r\n * @description 添加动态字段\r\n * @param config - 字段配置\r\n */\r\n const addField = (config: Partial<DynamicFieldConfig> = {}) => {\r\n if (!canAddMoreFields.value) {\r\n console.warn('[useDynamicFormState] 已达到最大字段数量限制')\r\n return\r\n }\r\n\r\n state.fieldCounter++\r\n\r\n const defaultType =\r\n config.type ||\r\n FIELD_TYPE_OPTIONS[Math.floor(Math.random() * FIELD_TYPE_OPTIONS.length)]\r\n .value\r\n\r\n const newField: DynamicFieldConfig = {\r\n id: `dynamic_field_${state.fieldCounter}`,\r\n type: defaultType,\r\n prop: config.prop || `dynamic_${state.fieldCounter}`,\r\n label: config.label || `动态字段 ${state.fieldCounter}`,\r\n placeholder: config.placeholder || `请输入${config.label || '内容'}`,\r\n visible: true,\r\n removable: true,\r\n created: Date.now(),\r\n layout: config.layout || { span: 12 },\r\n ...config,\r\n }\r\n\r\n state.dynamicFields.push(newField)\r\n\r\n console.log('[useDynamicFormState] 添加字段:', newField)\r\n }\r\n\r\n /**\r\n * @description 移除动态字段\r\n * @param index - 可选,要移除的字段索引,默认移除最后一个\r\n */\r\n const removeField = (index?: number) => {\r\n if (state.dynamicFields.length === 0) {\r\n console.warn('[useDynamicFormState] 没有可移除的动态字段')\r\n return\r\n }\r\n\r\n const targetIndex = index ?? state.dynamicFields.length - 1\r\n\r\n if (targetIndex < 0 || targetIndex >= state.dynamicFields.length) {\r\n console.warn('[useDynamicFormState] 字段索引超出范围')\r\n return\r\n }\r\n\r\n const removed = state.dynamicFields.splice(targetIndex, 1)[0]\r\n if (removed) {\r\n state.hiddenFieldIds.delete(removed.prop)\r\n console.log('[useDynamicFormState] 移除字段:', removed.prop)\r\n }\r\n }\r\n\r\n /**\r\n * @description 清空所有动态字段\r\n */\r\n const clearDynamicFields = () => {\r\n console.log(\r\n '[useDynamicFormState] 清空动态字段:',\r\n state.dynamicFields.length\r\n )\r\n state.dynamicFields.forEach(field =>\r\n state.hiddenFieldIds.delete(field.prop)\r\n )\r\n state.dynamicFields.length = 0\r\n state.fieldCounter = 0\r\n }\r\n\r\n /**\r\n * @description 切换字段可见性\r\n * @param fieldId - 字段ID\r\n */\r\n const toggleFieldVisibility = (fieldId: string) => {\r\n if (state.hiddenFieldIds.has(fieldId)) {\r\n state.hiddenFieldIds.delete(fieldId)\r\n console.log('[useDynamicFormState] 显示字段:', fieldId)\r\n } else {\r\n state.hiddenFieldIds.add(fieldId)\r\n console.log('[useDynamicFormState] 隐藏字段:', fieldId)\r\n }\r\n }\r\n\r\n /**\r\n * @description 切换所有字段可见性\r\n */\r\n const toggleAllVisibility = () => {\r\n if (allVisible.value) {\r\n state.dynamicFields.forEach(field => {\r\n state.hiddenFieldIds.add(field.prop)\r\n })\r\n console.log('[useDynamicFormState] 隐藏所有动态字段')\r\n } else {\r\n state.hiddenFieldIds.clear()\r\n console.log('[useDynamicFormState] 显示所有字段')\r\n }\r\n }\r\n\r\n /**\r\n * @description 更新表单配置\r\n * @param newConfig - 新的配置对象\r\n */\r\n const updateConfig = (newConfig: Partial<DynamicFormConfig>) => {\r\n Object.assign(state.config, newConfig)\r\n console.log('[useDynamicFormState] 更新配置:', newConfig)\r\n }\r\n\r\n /**\r\n * @description 导出当前表单配置\r\n * @returns JSON格式的配置字符串\r\n */\r\n const exportConfig = () => {\r\n const config = {\r\n baseFields: state.baseFields,\r\n dynamicFields: state.dynamicFields,\r\n config: state.config,\r\n hiddenFields: Array.from(state.hiddenFieldIds),\r\n timestamp: Date.now(),\r\n }\r\n return JSON.stringify(config, null, 2)\r\n }\r\n\r\n /**\r\n * @description 初始化表单状态\r\n * @param baseFields - 基础字段配置\r\n * @param config - 可选,表单配置\r\n */\r\n const initialize = (\r\n baseFields: FormOption[],\r\n config: Partial<DynamicFormConfig> = {}\r\n ) => {\r\n console.log('[useDynamicFormState] 初始化状态:', {\r\n baseFieldsCount: baseFields.length,\r\n config,\r\n })\r\n\r\n state.baseFields = [...baseFields]\r\n Object.assign(state.config, config)\r\n state.isInitialized = true\r\n\r\n console.log('[useDynamicFormState] 初始化完成')\r\n }\r\n\r\n return {\r\n state: readonly(state),\r\n allFields,\r\n visibleFields,\r\n dynamicFieldsCount,\r\n hiddenFieldsCount,\r\n canAddMoreFields,\r\n allVisible,\r\n FIELD_TYPE_OPTIONS,\r\n addField,\r\n removeField,\r\n clearDynamicFields,\r\n toggleFieldVisibility,\r\n toggleAllVisibility,\r\n updateConfig,\r\n exportConfig,\r\n initialize,\r\n }\r\n}\r\n\r\n/**\r\n * @description 动态表单状态类型\r\n */\r\nexport type DynamicFormStateType = ReturnType<typeof useDynamicFormState>\r\n\r\n/**\r\n * @description 动态表单状态注入键\r\n */\r\nexport const DYNAMIC_FORM_STATE_KEY = Symbol('dynamicFormState')\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 动态表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-dynamic\">\r\n <!-- 动态控制面板 -->\r\n <div\r\n v-if=\"showControls && isDynamicStateAvailable\"\r\n class=\"dynamic-controls\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制\" :bordered=\"false\">\r\n <template #header-extra>\r\n <NBadge :value=\"dynamicFieldsCount\" type=\"warning\">\r\n <C_Icon :name=\"'mdi:layers'\" :size=\"16\" />\r\n </NBadge>\r\n </template>\r\n\r\n <NSpace justify=\"space-between\" align=\"center\">\r\n <NSpace>\r\n <NButton\r\n size=\"small\"\r\n type=\"primary\"\r\n :disabled=\"!canAddMoreFields\"\r\n @click=\"dynamicState!.addField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:plus'\" :size=\"14\" />\r\n </template>\r\n 添加字段 ({{ dynamicFieldsCount }}/{{ maxFields }})\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"warning\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.removeField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:minus'\" :size=\"14\" />\r\n </template>\r\n 移除字段\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"error\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.clearDynamicFields()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:delete-sweep'\" :size=\"14\" />\r\n </template>\r\n 清空动态\r\n </NButton>\r\n </NSpace>\r\n\r\n <!-- 最大字段数配置 -->\r\n <div class=\"max-fields-config\">\r\n <span>最大字段数:</span>\r\n <NInputNumber\r\n :value=\"maxFields\"\r\n @update:value=\"\r\n (v: any) => v && dynamicState!.updateConfig({ maxFields: v })\r\n \"\r\n :min=\"5\"\r\n :max=\"50\"\r\n size=\"small\"\r\n style=\"width: 100px\"\r\n />\r\n </div>\r\n </NSpace>\r\n\r\n <!-- 状态信息 -->\r\n <div class=\"dynamic-stats\">\r\n <NSpace>\r\n <NTag type=\"info\">总字段: {{ totalFieldsCount }}</NTag>\r\n <NTag type=\"warning\">动态: {{ dynamicFieldsCount }}</NTag>\r\n </NSpace>\r\n </div>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 状态不可用时的提示 -->\r\n <div\r\n v-else-if=\"showControls && !isDynamicStateAvailable\"\r\n class=\"dynamic-controls-fallback\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制 (静态模式)\" :bordered=\"false\">\r\n <NAlert type=\"info\" show-icon>\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n </template>\r\n 当前为静态模式,如需动态功能请在父组件中提供动态表单状态。\r\n </NAlert>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 表单项渲染区域 -->\r\n <div class=\"dynamic-form-items\">\r\n <NGrid :cols=\"gridCols\" :x-gap=\"gridGutter\" :y-gap=\"gridGutter\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :span=\"getItemSpan(index)\"\r\n >\r\n <div\r\n class=\"dynamic-item-wrapper\"\r\n :class=\"{ 'is-dynamic-field': isDynamicField(item) }\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { DynamicFieldConfig, LayoutConfig, FormOption } from \"../../types\";\r\nimport {\r\n DYNAMIC_FORM_STATE_KEY,\r\n type DynamicFormStateType,\r\n} from \"../../composables/useDynamicFormState\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 接口定义 ================= */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: LayoutConfig;\r\n options?: FormOption[];\r\n dynamicFormState?: DynamicFormStateType | null;\r\n}\r\n\r\n/* ================= 组件配置 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n dynamicFormState: null,\r\n});\r\n\r\ndefineEmits<{\r\n \"field-add\": [fieldConfig: DynamicFieldConfig];\r\n \"field-remove\": [fieldId: string];\r\n \"fields-clear\": [];\r\n}>();\r\n\r\n/* ================= 状态管理 ================= */\r\nconst injectedDynamicState = inject<DynamicFormStateType | null>(\r\n DYNAMIC_FORM_STATE_KEY,\r\n null,\r\n);\r\n\r\nconst dynamicState = computed(\r\n () => props.dynamicFormState || injectedDynamicState,\r\n);\r\nconst isDynamicStateAvailable = computed(() => !!dynamicState.value);\r\n\r\n/* ================= 计算属性 ================= */\r\nconst gridCols = computed(() => props.layoutConfig?.dynamic?.grid?.cols ?? 24);\r\nconst gridGutter = computed(\r\n () => props.layoutConfig?.dynamic?.grid?.gutter ?? 16,\r\n);\r\nconst showControls = computed(\r\n () => props.layoutConfig?.dynamic?.controls?.showControls !== false,\r\n);\r\n\r\nconst maxFields = computed(() => {\r\n return (\r\n dynamicState.value?.state.config.maxFields ??\r\n props.layoutConfig?.dynamic?.dynamic?.maxFields ??\r\n 50\r\n );\r\n});\r\n\r\nconst dynamicFieldsCount = computed(\r\n () => dynamicState.value?.dynamicFieldsCount.value ?? 0,\r\n);\r\nconst canAddMoreFields = computed(\r\n () => dynamicState.value?.canAddMoreFields.value ?? false,\r\n);\r\nconst totalFieldsCount = computed(() => props.formItems.length);\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n return (\r\n item.key?.toString() || (item.props as any)?.path || `dynamic-item-${index}`\r\n );\r\n};\r\n\r\nconst getItemSpan = (index: number): number => {\r\n const span = props.options?.[index]?.layout?.span;\r\n return typeof span === \"number\" && span > 0 && span <= gridCols.value\r\n ? span\r\n : Math.min(12, gridCols.value);\r\n};\r\n\r\nconst isDynamicField = (item: VNode): boolean => {\r\n if (!dynamicState.value) return false;\r\n const fieldId = (item.props as any)?.path || item.key?.toString() || \"\";\r\n return dynamicState.value.state.dynamicFields.some(\r\n (field: any) => field.prop === fieldId,\r\n );\r\n};\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n addField: () => dynamicState.value?.addField(),\r\n removeField: () => dynamicState.value?.removeField(),\r\n clearAllDynamic: () => dynamicState.value?.clearDynamicFields(),\r\n isDynamicStateAvailable,\r\n dynamicState,\r\n});\r\n\r\n/* ================= 开发环境验证 ================= */\r\nif (typeof import.meta !== \"undefined\" && import.meta.env?.DEV) {\r\n watchEffect(() => {\r\n const { options, formItems } = props;\r\n if (options && options.length !== formItems.length) {\r\n console.warn(\r\n `[Dynamic Layout] 配置项数量(${options.length})与表单项数量(${formItems.length})不匹配`,\r\n );\r\n }\r\n\r\n console.log(\r\n \"[Dynamic Layout]\",\r\n isDynamicStateAvailable.value ? \"动态模式已启用\" : \"运行在静态模式\",\r\n {\r\n totalFields: totalFieldsCount.value,\r\n dynamicFields: dynamicFieldsCount.value,\r\n stateSource: props.dynamicFormState ? \"props透传\" : \"inject注入\",\r\n },\r\n );\r\n });\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 动态表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-dynamic\">\r\n <!-- 动态控制面板 -->\r\n <div\r\n v-if=\"showControls && isDynamicStateAvailable\"\r\n class=\"dynamic-controls\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制\" :bordered=\"false\">\r\n <template #header-extra>\r\n <NBadge :value=\"dynamicFieldsCount\" type=\"warning\">\r\n <C_Icon :name=\"'mdi:layers'\" :size=\"16\" />\r\n </NBadge>\r\n </template>\r\n\r\n <NSpace justify=\"space-between\" align=\"center\">\r\n <NSpace>\r\n <NButton\r\n size=\"small\"\r\n type=\"primary\"\r\n :disabled=\"!canAddMoreFields\"\r\n @click=\"dynamicState!.addField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:plus'\" :size=\"14\" />\r\n </template>\r\n 添加字段 ({{ dynamicFieldsCount }}/{{ maxFields }})\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"warning\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.removeField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:minus'\" :size=\"14\" />\r\n </template>\r\n 移除字段\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"error\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.clearDynamicFields()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:delete-sweep'\" :size=\"14\" />\r\n </template>\r\n 清空动态\r\n </NButton>\r\n </NSpace>\r\n\r\n <!-- 最大字段数配置 -->\r\n <div class=\"max-fields-config\">\r\n <span>最大字段数:</span>\r\n <NInputNumber\r\n :value=\"maxFields\"\r\n @update:value=\"\r\n (v: any) => v && dynamicState!.updateConfig({ maxFields: v })\r\n \"\r\n :min=\"5\"\r\n :max=\"50\"\r\n size=\"small\"\r\n style=\"width: 100px\"\r\n />\r\n </div>\r\n </NSpace>\r\n\r\n <!-- 状态信息 -->\r\n <div class=\"dynamic-stats\">\r\n <NSpace>\r\n <NTag type=\"info\">总字段: {{ totalFieldsCount }}</NTag>\r\n <NTag type=\"warning\">动态: {{ dynamicFieldsCount }}</NTag>\r\n </NSpace>\r\n </div>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 状态不可用时的提示 -->\r\n <div\r\n v-else-if=\"showControls && !isDynamicStateAvailable\"\r\n class=\"dynamic-controls-fallback\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制 (静态模式)\" :bordered=\"false\">\r\n <NAlert type=\"info\" show-icon>\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n </template>\r\n 当前为静态模式,如需动态功能请在父组件中提供动态表单状态。\r\n </NAlert>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 表单项渲染区域 -->\r\n <div class=\"dynamic-form-items\">\r\n <NGrid :cols=\"gridCols\" :x-gap=\"gridGutter\" :y-gap=\"gridGutter\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :span=\"getItemSpan(index)\"\r\n >\r\n <div\r\n class=\"dynamic-item-wrapper\"\r\n :class=\"{ 'is-dynamic-field': isDynamicField(item) }\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { DynamicFieldConfig, LayoutConfig, FormOption } from \"../../types\";\r\nimport {\r\n DYNAMIC_FORM_STATE_KEY,\r\n type DynamicFormStateType,\r\n} from \"../../composables/useDynamicFormState\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 接口定义 ================= */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: LayoutConfig;\r\n options?: FormOption[];\r\n dynamicFormState?: DynamicFormStateType | null;\r\n}\r\n\r\n/* ================= 组件配置 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n dynamicFormState: null,\r\n});\r\n\r\ndefineEmits<{\r\n \"field-add\": [fieldConfig: DynamicFieldConfig];\r\n \"field-remove\": [fieldId: string];\r\n \"fields-clear\": [];\r\n}>();\r\n\r\n/* ================= 状态管理 ================= */\r\nconst injectedDynamicState = inject<DynamicFormStateType | null>(\r\n DYNAMIC_FORM_STATE_KEY,\r\n null,\r\n);\r\n\r\nconst dynamicState = computed(\r\n () => props.dynamicFormState || injectedDynamicState,\r\n);\r\nconst isDynamicStateAvailable = computed(() => !!dynamicState.value);\r\n\r\n/* ================= 计算属性 ================= */\r\nconst gridCols = computed(() => props.layoutConfig?.dynamic?.grid?.cols ?? 24);\r\nconst gridGutter = computed(\r\n () => props.layoutConfig?.dynamic?.grid?.gutter ?? 16,\r\n);\r\nconst showControls = computed(\r\n () => props.layoutConfig?.dynamic?.controls?.showControls !== false,\r\n);\r\n\r\nconst maxFields = computed(() => {\r\n return (\r\n dynamicState.value?.state.config.maxFields ??\r\n props.layoutConfig?.dynamic?.dynamic?.maxFields ??\r\n 50\r\n );\r\n});\r\n\r\nconst dynamicFieldsCount = computed(\r\n () => dynamicState.value?.dynamicFieldsCount.value ?? 0,\r\n);\r\nconst canAddMoreFields = computed(\r\n () => dynamicState.value?.canAddMoreFields.value ?? false,\r\n);\r\nconst totalFieldsCount = computed(() => props.formItems.length);\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n return (\r\n item.key?.toString() || (item.props as any)?.path || `dynamic-item-${index}`\r\n );\r\n};\r\n\r\nconst getItemSpan = (index: number): number => {\r\n const span = props.options?.[index]?.layout?.span;\r\n return typeof span === \"number\" && span > 0 && span <= gridCols.value\r\n ? span\r\n : Math.min(12, gridCols.value);\r\n};\r\n\r\nconst isDynamicField = (item: VNode): boolean => {\r\n if (!dynamicState.value) return false;\r\n const fieldId = (item.props as any)?.path || item.key?.toString() || \"\";\r\n return dynamicState.value.state.dynamicFields.some(\r\n (field: any) => field.prop === fieldId,\r\n );\r\n};\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n addField: () => dynamicState.value?.addField(),\r\n removeField: () => dynamicState.value?.removeField(),\r\n clearAllDynamic: () => dynamicState.value?.clearDynamicFields(),\r\n isDynamicStateAvailable,\r\n dynamicState,\r\n});\r\n\r\n/* ================= 开发环境验证 ================= */\r\nif (typeof import.meta !== \"undefined\" && import.meta.env?.DEV) {\r\n watchEffect(() => {\r\n const { options, formItems } = props;\r\n if (options && options.length !== formItems.length) {\r\n console.warn(\r\n `[Dynamic Layout] 配置项数量(${options.length})与表单项数量(${formItems.length})不匹配`,\r\n );\r\n }\r\n\r\n console.log(\r\n \"[Dynamic Layout]\",\r\n isDynamicStateAvailable.value ? \"动态模式已启用\" : \"运行在静态模式\",\r\n {\r\n totalFields: totalFieldsCount.value,\r\n dynamicFields: dynamicFieldsCount.value,\r\n stateSource: props.dynamicFormState ? \"props透传\" : \"inject注入\",\r\n },\r\n );\r\n });\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 动态表单组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-form-dynamic\">\r\n <!-- 动态控制面板 -->\r\n <div\r\n v-if=\"showControls && isDynamicStateAvailable\"\r\n class=\"dynamic-controls\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制\" :bordered=\"false\">\r\n <template #header-extra>\r\n <NBadge :value=\"dynamicFieldsCount\" type=\"warning\">\r\n <C_Icon :name=\"'mdi:layers'\" :size=\"16\" />\r\n </NBadge>\r\n </template>\r\n\r\n <NSpace justify=\"space-between\" align=\"center\">\r\n <NSpace>\r\n <NButton\r\n size=\"small\"\r\n type=\"primary\"\r\n :disabled=\"!canAddMoreFields\"\r\n @click=\"dynamicState!.addField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:plus'\" :size=\"14\" />\r\n </template>\r\n 添加字段 ({{ dynamicFieldsCount }}/{{ maxFields }})\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"warning\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.removeField()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:minus'\" :size=\"14\" />\r\n </template>\r\n 移除字段\r\n </NButton>\r\n\r\n <NButton\r\n size=\"small\"\r\n type=\"error\"\r\n :disabled=\"dynamicFieldsCount === 0\"\r\n @click=\"dynamicState!.clearDynamicFields()\"\r\n >\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:delete-sweep'\" :size=\"14\" />\r\n </template>\r\n 清空动态\r\n </NButton>\r\n </NSpace>\r\n\r\n <!-- 最大字段数配置 -->\r\n <div class=\"max-fields-config\">\r\n <span>最大字段数:</span>\r\n <NInputNumber\r\n :value=\"maxFields\"\r\n @update:value=\"\r\n (v: any) => v && dynamicState!.updateConfig({ maxFields: v })\r\n \"\r\n :min=\"5\"\r\n :max=\"50\"\r\n size=\"small\"\r\n style=\"width: 100px\"\r\n />\r\n </div>\r\n </NSpace>\r\n\r\n <!-- 状态信息 -->\r\n <div class=\"dynamic-stats\">\r\n <NSpace>\r\n <NTag type=\"info\">总字段: {{ totalFieldsCount }}</NTag>\r\n <NTag type=\"warning\">动态: {{ dynamicFieldsCount }}</NTag>\r\n </NSpace>\r\n </div>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 状态不可用时的提示 -->\r\n <div\r\n v-else-if=\"showControls && !isDynamicStateAvailable\"\r\n class=\"dynamic-controls-fallback\"\r\n >\r\n <NCard size=\"small\" title=\"动态表单控制 (静态模式)\" :bordered=\"false\">\r\n <NAlert type=\"info\" show-icon>\r\n <template #icon>\r\n <C_Icon :name=\"'mdi:information-outline'\" :size=\"16\" />\r\n </template>\r\n 当前为静态模式,如需动态功能请在父组件中提供动态表单状态。\r\n </NAlert>\r\n </NCard>\r\n </div>\r\n\r\n <!-- 表单项渲染区域 -->\r\n <div class=\"dynamic-form-items\">\r\n <NGrid :cols=\"gridCols\" :x-gap=\"gridGutter\" :y-gap=\"gridGutter\">\r\n <NGridItem\r\n v-for=\"(item, index) in formItems\"\r\n :key=\"getItemKey(item, index)\"\r\n :span=\"getItemSpan(index)\"\r\n >\r\n <div\r\n class=\"dynamic-item-wrapper\"\r\n :class=\"{ 'is-dynamic-field': isDynamicField(item) }\"\r\n >\r\n <component :is=\"item\" />\r\n </div>\r\n </NGridItem>\r\n </NGrid>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { DynamicFieldConfig, LayoutConfig, FormOption } from \"../../types\";\r\nimport {\r\n DYNAMIC_FORM_STATE_KEY,\r\n type DynamicFormStateType,\r\n} from \"../../composables/useDynamicFormState\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\n/* ================= 接口定义 ================= */\r\ninterface Props {\r\n formItems: VNode[];\r\n layoutConfig?: LayoutConfig;\r\n options?: FormOption[];\r\n dynamicFormState?: DynamicFormStateType | null;\r\n}\r\n\r\n/* ================= 组件配置 ================= */\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layoutConfig: () => ({}),\r\n options: () => [],\r\n dynamicFormState: null,\r\n});\r\n\r\ndefineEmits<{\r\n \"field-add\": [fieldConfig: DynamicFieldConfig];\r\n \"field-remove\": [fieldId: string];\r\n \"fields-clear\": [];\r\n}>();\r\n\r\n/* ================= 状态管理 ================= */\r\nconst injectedDynamicState = inject<DynamicFormStateType | null>(\r\n DYNAMIC_FORM_STATE_KEY,\r\n null,\r\n);\r\n\r\nconst dynamicState = computed(\r\n () => props.dynamicFormState || injectedDynamicState,\r\n);\r\nconst isDynamicStateAvailable = computed(() => !!dynamicState.value);\r\n\r\n/* ================= 计算属性 ================= */\r\nconst gridCols = computed(() => props.layoutConfig?.dynamic?.grid?.cols ?? 24);\r\nconst gridGutter = computed(\r\n () => props.layoutConfig?.dynamic?.grid?.gutter ?? 16,\r\n);\r\nconst showControls = computed(\r\n () => props.layoutConfig?.dynamic?.controls?.showControls !== false,\r\n);\r\n\r\nconst maxFields = computed(() => {\r\n return (\r\n dynamicState.value?.state.config.maxFields ??\r\n props.layoutConfig?.dynamic?.dynamic?.maxFields ??\r\n 50\r\n );\r\n});\r\n\r\nconst dynamicFieldsCount = computed(\r\n () => dynamicState.value?.dynamicFieldsCount.value ?? 0,\r\n);\r\nconst canAddMoreFields = computed(\r\n () => dynamicState.value?.canAddMoreFields.value ?? false,\r\n);\r\nconst totalFieldsCount = computed(() => props.formItems.length);\r\n\r\n/* ================= 工具方法 ================= */\r\nconst getItemKey = (item: VNode, index: number): string => {\r\n return (\r\n item.key?.toString() || (item.props as any)?.path || `dynamic-item-${index}`\r\n );\r\n};\r\n\r\nconst getItemSpan = (index: number): number => {\r\n const span = props.options?.[index]?.layout?.span;\r\n return typeof span === \"number\" && span > 0 && span <= gridCols.value\r\n ? span\r\n : Math.min(12, gridCols.value);\r\n};\r\n\r\nconst isDynamicField = (item: VNode): boolean => {\r\n if (!dynamicState.value) return false;\r\n const fieldId = (item.props as any)?.path || item.key?.toString() || \"\";\r\n return dynamicState.value.state.dynamicFields.some(\r\n (field: any) => field.prop === fieldId,\r\n );\r\n};\r\n\r\n/* ================= 对外暴露 ================= */\r\ndefineExpose({\r\n addField: () => dynamicState.value?.addField(),\r\n removeField: () => dynamicState.value?.removeField(),\r\n clearAllDynamic: () => dynamicState.value?.clearDynamicFields(),\r\n isDynamicStateAvailable,\r\n dynamicState,\r\n});\r\n\r\n/* ================= 开发环境验证 ================= */\r\nif (typeof import.meta !== \"undefined\" && import.meta.env?.DEV) {\r\n watchEffect(() => {\r\n const { options, formItems } = props;\r\n if (options && options.length !== formItems.length) {\r\n console.warn(\r\n `[Dynamic Layout] 配置项数量(${options.length})与表单项数量(${formItems.length})不匹配`,\r\n );\r\n }\r\n\r\n console.log(\r\n \"[Dynamic Layout]\",\r\n isDynamicStateAvailable.value ? \"动态模式已启用\" : \"运行在静态模式\",\r\n {\r\n totalFields: totalFieldsCount.value,\r\n dynamicFields: dynamicFieldsCount.value,\r\n stateSource: props.dynamicFormState ? \"props透传\" : \"inject注入\",\r\n },\r\n );\r\n });\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 自定义布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"custom-layout\">\r\n <!-- 顶部工具栏 -->\r\n <NCard :bordered=\"false\" class=\"toolbar-card\">\r\n <div class=\"toolbar-content\">\r\n <!-- 模式切换 -->\r\n <div class=\"mode-section\">\r\n <span class=\"section-label\">自定义模式:</span>\r\n <NButtonGroup>\r\n <NButton\r\n :type=\"isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = true\"\r\n size=\"small\"\r\n >\r\n 🎨 设计模式\r\n </NButton>\r\n <NButton\r\n :type=\"!isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = false\"\r\n size=\"small\"\r\n >\r\n 📝 填写模式\r\n </NButton>\r\n </NButtonGroup>\r\n </div>\r\n\r\n <!-- 统计信息 -->\r\n <div class=\"stats-section\">\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ customAreas.length }}</div>\r\n <div class=\"stat-label\">自定义区域</div>\r\n </div>\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ totalFieldsCount }}</div>\r\n <div class=\"stat-label\">字段总数</div>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作按钮 -->\r\n <div class=\"actions-section\">\r\n <template v-if=\"isDesignMode\">\r\n <NButton secondary @click=\"handleSaveLayout\" size=\"small\">\r\n 💾 保存布局\r\n </NButton>\r\n <NButton secondary @click=\"handleResetLayout\" size=\"small\">\r\n 🔄 重置布局\r\n </NButton>\r\n </template>\r\n <template v-else>\r\n <NButton secondary @click=\"handleExportData\" size=\"small\">\r\n 📊 导出数据\r\n </NButton>\r\n </template>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 设计模式工具面板 -->\r\n <NCard v-if=\"isDesignMode\" class=\"design-panel\" title=\"🎨 设计工具\">\r\n <div class=\"design-tools\">\r\n <div class=\"tool-group\">\r\n <span class=\"group-label\">添加区域:</span>\r\n <NButton @click=\"addArea('horizontal')\" size=\"small\">\r\n ➡️ 水平区域\r\n </NButton>\r\n <NButton @click=\"addArea('vertical')\" size=\"small\">\r\n ⬇️ 垂直区域\r\n </NButton>\r\n <NButton @click=\"addArea('grid')\" size=\"small\"> ⚏ 网格区域 </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 主画布区域 -->\r\n <div class=\"layout-canvas\" :class=\"{ 'design-mode': isDesignMode }\">\r\n <!-- 设计模式 -->\r\n <template v-if=\"isDesignMode\">\r\n <div class=\"canvas-hint\" v-if=\"customAreas.length === 0\">\r\n <div class=\"hint-content\">\r\n <h3>🎨 开始自定义你的布局</h3>\r\n <p>点击上方按钮添加区域</p>\r\n </div>\r\n </div>\r\n\r\n <!-- 区域列表 -->\r\n <div v-else class=\"areas-container\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"custom-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <div class=\"area-header\">\r\n <div class=\"area-info\">\r\n <NInput\r\n v-if=\"editingTitleId === area.id\"\r\n v-model:value=\"area.title\"\r\n size=\"small\"\r\n @blur=\"editingTitleId = ''\"\r\n @keyup.enter=\"editingTitleId = ''\"\r\n class=\"title-input\"\r\n />\r\n <span\r\n v-else\r\n class=\"area-title\"\r\n @click=\"editingTitleId = area.id\"\r\n >\r\n {{ area.title }}\r\n </span>\r\n <NTag size=\"small\">{{ area.fields.length }} 字段</NTag>\r\n </div>\r\n <div class=\"area-controls\">\r\n <NButton\r\n text\r\n @click=\"deleteArea(area.id)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n title=\"删除区域\"\r\n >\r\n 🗑️\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段容器 -->\r\n <div class=\"area-fields\">\r\n <div\r\n v-for=\"field in area.fields\"\r\n :key=\"field.id\"\r\n class=\"field-item\"\r\n >\r\n <div class=\"field-preview\">\r\n <span class=\"field-label\">{{\r\n field.label || field.prop\r\n }}</span>\r\n <span class=\"field-type\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"area-drop-zone\" v-if=\"area.fields.length === 0\">\r\n 拖拽字段到这里\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段池 -->\r\n <NCard class=\"field-pool\" title=\"📦 可用字段\">\r\n <div class=\"pool-fields-grid\">\r\n <div\r\n v-for=\"field in availableFields\"\r\n :key=\"field.id\"\r\n class=\"pool-field\"\r\n >\r\n <span class=\"field-name\">{{ field.label || field.prop }}</span>\r\n <span class=\"field-type-tag\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </NCard>\r\n </template>\r\n\r\n <!-- 填写模式 -->\r\n <template v-else>\r\n <div v-if=\"customAreas.length === 0\" class=\"empty-layout\">\r\n <NEmpty description=\"尚未设计布局\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" type=\"primary\">\r\n 🎨 开始设计\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n\r\n <div v-else class=\"form-container\">\r\n <div class=\"form-areas\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"form-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <NCard v-if=\"area.fields.length > 0\" :title=\"area.title\">\r\n <div class=\"area-form-items\" :class=\"`layout-${area.type}`\">\r\n <component\r\n v-for=\"field in area.fields\"\r\n :key=\"field.prop\"\r\n :is=\"getFormItemForField(field)\"\r\n />\r\n </div>\r\n </NCard>\r\n <NEmpty v-else description=\"此区域暂无字段\" size=\"small\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" size=\"small\" secondary>\r\n 🎨 添加字段\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { FormOption } from \"../../types\";\r\n\r\n/* 接口定义 */\r\ninterface CustomArea {\r\n id: string;\r\n title: string;\r\n type: \"horizontal\" | \"vertical\" | \"grid\";\r\n fields: DraggableFormOption[];\r\n}\r\n\r\ninterface DraggableFormOption extends FormOption {\r\n id: string;\r\n}\r\n\r\ninterface Props {\r\n options?: FormOption[];\r\n formItems?: VNode[];\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n options: () => [],\r\n formItems: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"fields-change\": [fields: FormOption[]];\r\n \"export-data\": [data: any];\r\n}>();\r\n\r\n/* 响应式状态 */\r\nconst isDesignMode = ref(true);\r\nconst customAreas = ref<CustomArea[]>([]);\r\nconst availableFields = ref<DraggableFormOption[]>([]);\r\nconst editingTitleId = ref<string | number>(\"\");\r\n\r\n/* 计算属性 */\r\nconst allFormOptions = computed(() => {\r\n if (props.options?.length > 0) {\r\n return props.options;\r\n }\r\n\r\n return (\r\n props.formItems\r\n ?.map((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return {\r\n prop: itemProps?.path || \"\",\r\n label: itemProps?.label || itemProps?.path || \"\",\r\n type: \"input\",\r\n show: true,\r\n } as FormOption;\r\n })\r\n .filter((option) => option.prop) || []\r\n );\r\n});\r\n\r\nconst totalFieldsCount = computed(() =>\r\n customAreas.value.reduce((total, area) => total + area.fields.length, 0),\r\n);\r\n\r\n/* 工具函数 */\r\nconst generateId = () =>\r\n `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\r\n\r\nconst getFieldTypeName = (type: string) => {\r\n const typeMap: Record<string, string> = {\r\n input: \"输入框\",\r\n select: \"下拉框\",\r\n radio: \"单选框\",\r\n checkbox: \"复选框\",\r\n textarea: \"文本域\",\r\n date: \"日期\",\r\n number: \"数字\",\r\n };\r\n return typeMap[type] || type;\r\n};\r\n\r\nconst getFormItemForField = (field: FormOption) => {\r\n return (\r\n props.formItems?.find((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return itemProps?.path === field.prop;\r\n }) || null\r\n );\r\n};\r\n\r\n/* 布局操作 */\r\nconst addArea = (type: \"horizontal\" | \"vertical\" | \"grid\") => {\r\n const area: CustomArea = {\r\n id: generateId(),\r\n title: `${type === \"horizontal\" ? \"水平\" : type === \"vertical\" ? \"垂直\" : \"网格\"}区域`,\r\n type,\r\n fields: [],\r\n };\r\n customAreas.value.push(area);\r\n};\r\n\r\nconst deleteArea = (areaId: string | number) => {\r\n const index = customAreas.value.findIndex((area) => area.id === areaId);\r\n if (index !== -1) {\r\n customAreas.value.splice(index, 1);\r\n }\r\n};\r\n\r\nconst handleSaveLayout = () => {\r\n const config = JSON.stringify(customAreas.value, null, 2);\r\n const blob = new Blob([config], { type: \"application/json\" });\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = `layout-config-${Date.now()}.json`;\r\n document.body.appendChild(link);\r\n link.click();\r\n document.body.removeChild(link);\r\n URL.revokeObjectURL(url);\r\n};\r\n\r\nconst handleResetLayout = () => {\r\n customAreas.value = [];\r\n};\r\n\r\nconst handleExportData = () => {\r\n const exportData = {\r\n layout: customAreas.value,\r\n formData: props.formData,\r\n timestamp: new Date().toISOString(),\r\n };\r\n emit(\"export-data\", exportData);\r\n};\r\n\r\n/* 监听器 */\r\nwatchEffect(() => {\r\n const usedProps = new Set(\r\n customAreas.value.flatMap((area) => area.fields.map((field) => field.prop)),\r\n );\r\n\r\n availableFields.value = allFormOptions.value\r\n .filter((field) => !usedProps.has(field.prop))\r\n .map((field) => ({\r\n ...field,\r\n id: field.prop,\r\n }));\r\n});\r\n\r\nwatch(\r\n () => customAreas.value,\r\n () => {\r\n const allFields = customAreas.value.flatMap((area) =>\r\n area.fields.map((field) => ({\r\n ...field,\r\n id: undefined,\r\n })),\r\n );\r\n emit(\"fields-change\", allFields);\r\n },\r\n { deep: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 自定义布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"custom-layout\">\r\n <!-- 顶部工具栏 -->\r\n <NCard :bordered=\"false\" class=\"toolbar-card\">\r\n <div class=\"toolbar-content\">\r\n <!-- 模式切换 -->\r\n <div class=\"mode-section\">\r\n <span class=\"section-label\">自定义模式:</span>\r\n <NButtonGroup>\r\n <NButton\r\n :type=\"isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = true\"\r\n size=\"small\"\r\n >\r\n 🎨 设计模式\r\n </NButton>\r\n <NButton\r\n :type=\"!isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = false\"\r\n size=\"small\"\r\n >\r\n 📝 填写模式\r\n </NButton>\r\n </NButtonGroup>\r\n </div>\r\n\r\n <!-- 统计信息 -->\r\n <div class=\"stats-section\">\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ customAreas.length }}</div>\r\n <div class=\"stat-label\">自定义区域</div>\r\n </div>\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ totalFieldsCount }}</div>\r\n <div class=\"stat-label\">字段总数</div>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作按钮 -->\r\n <div class=\"actions-section\">\r\n <template v-if=\"isDesignMode\">\r\n <NButton secondary @click=\"handleSaveLayout\" size=\"small\">\r\n 💾 保存布局\r\n </NButton>\r\n <NButton secondary @click=\"handleResetLayout\" size=\"small\">\r\n 🔄 重置布局\r\n </NButton>\r\n </template>\r\n <template v-else>\r\n <NButton secondary @click=\"handleExportData\" size=\"small\">\r\n 📊 导出数据\r\n </NButton>\r\n </template>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 设计模式工具面板 -->\r\n <NCard v-if=\"isDesignMode\" class=\"design-panel\" title=\"🎨 设计工具\">\r\n <div class=\"design-tools\">\r\n <div class=\"tool-group\">\r\n <span class=\"group-label\">添加区域:</span>\r\n <NButton @click=\"addArea('horizontal')\" size=\"small\">\r\n ➡️ 水平区域\r\n </NButton>\r\n <NButton @click=\"addArea('vertical')\" size=\"small\">\r\n ⬇️ 垂直区域\r\n </NButton>\r\n <NButton @click=\"addArea('grid')\" size=\"small\"> ⚏ 网格区域 </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 主画布区域 -->\r\n <div class=\"layout-canvas\" :class=\"{ 'design-mode': isDesignMode }\">\r\n <!-- 设计模式 -->\r\n <template v-if=\"isDesignMode\">\r\n <div class=\"canvas-hint\" v-if=\"customAreas.length === 0\">\r\n <div class=\"hint-content\">\r\n <h3>🎨 开始自定义你的布局</h3>\r\n <p>点击上方按钮添加区域</p>\r\n </div>\r\n </div>\r\n\r\n <!-- 区域列表 -->\r\n <div v-else class=\"areas-container\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"custom-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <div class=\"area-header\">\r\n <div class=\"area-info\">\r\n <NInput\r\n v-if=\"editingTitleId === area.id\"\r\n v-model:value=\"area.title\"\r\n size=\"small\"\r\n @blur=\"editingTitleId = ''\"\r\n @keyup.enter=\"editingTitleId = ''\"\r\n class=\"title-input\"\r\n />\r\n <span\r\n v-else\r\n class=\"area-title\"\r\n @click=\"editingTitleId = area.id\"\r\n >\r\n {{ area.title }}\r\n </span>\r\n <NTag size=\"small\">{{ area.fields.length }} 字段</NTag>\r\n </div>\r\n <div class=\"area-controls\">\r\n <NButton\r\n text\r\n @click=\"deleteArea(area.id)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n title=\"删除区域\"\r\n >\r\n 🗑️\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段容器 -->\r\n <div class=\"area-fields\">\r\n <div\r\n v-for=\"field in area.fields\"\r\n :key=\"field.id\"\r\n class=\"field-item\"\r\n >\r\n <div class=\"field-preview\">\r\n <span class=\"field-label\">{{\r\n field.label || field.prop\r\n }}</span>\r\n <span class=\"field-type\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"area-drop-zone\" v-if=\"area.fields.length === 0\">\r\n 拖拽字段到这里\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段池 -->\r\n <NCard class=\"field-pool\" title=\"📦 可用字段\">\r\n <div class=\"pool-fields-grid\">\r\n <div\r\n v-for=\"field in availableFields\"\r\n :key=\"field.id\"\r\n class=\"pool-field\"\r\n >\r\n <span class=\"field-name\">{{ field.label || field.prop }}</span>\r\n <span class=\"field-type-tag\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </NCard>\r\n </template>\r\n\r\n <!-- 填写模式 -->\r\n <template v-else>\r\n <div v-if=\"customAreas.length === 0\" class=\"empty-layout\">\r\n <NEmpty description=\"尚未设计布局\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" type=\"primary\">\r\n 🎨 开始设计\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n\r\n <div v-else class=\"form-container\">\r\n <div class=\"form-areas\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"form-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <NCard v-if=\"area.fields.length > 0\" :title=\"area.title\">\r\n <div class=\"area-form-items\" :class=\"`layout-${area.type}`\">\r\n <component\r\n v-for=\"field in area.fields\"\r\n :key=\"field.prop\"\r\n :is=\"getFormItemForField(field)\"\r\n />\r\n </div>\r\n </NCard>\r\n <NEmpty v-else description=\"此区域暂无字段\" size=\"small\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" size=\"small\" secondary>\r\n 🎨 添加字段\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { FormOption } from \"../../types\";\r\n\r\n/* 接口定义 */\r\ninterface CustomArea {\r\n id: string;\r\n title: string;\r\n type: \"horizontal\" | \"vertical\" | \"grid\";\r\n fields: DraggableFormOption[];\r\n}\r\n\r\ninterface DraggableFormOption extends FormOption {\r\n id: string;\r\n}\r\n\r\ninterface Props {\r\n options?: FormOption[];\r\n formItems?: VNode[];\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n options: () => [],\r\n formItems: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"fields-change\": [fields: FormOption[]];\r\n \"export-data\": [data: any];\r\n}>();\r\n\r\n/* 响应式状态 */\r\nconst isDesignMode = ref(true);\r\nconst customAreas = ref<CustomArea[]>([]);\r\nconst availableFields = ref<DraggableFormOption[]>([]);\r\nconst editingTitleId = ref<string | number>(\"\");\r\n\r\n/* 计算属性 */\r\nconst allFormOptions = computed(() => {\r\n if (props.options?.length > 0) {\r\n return props.options;\r\n }\r\n\r\n return (\r\n props.formItems\r\n ?.map((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return {\r\n prop: itemProps?.path || \"\",\r\n label: itemProps?.label || itemProps?.path || \"\",\r\n type: \"input\",\r\n show: true,\r\n } as FormOption;\r\n })\r\n .filter((option) => option.prop) || []\r\n );\r\n});\r\n\r\nconst totalFieldsCount = computed(() =>\r\n customAreas.value.reduce((total, area) => total + area.fields.length, 0),\r\n);\r\n\r\n/* 工具函数 */\r\nconst generateId = () =>\r\n `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\r\n\r\nconst getFieldTypeName = (type: string) => {\r\n const typeMap: Record<string, string> = {\r\n input: \"输入框\",\r\n select: \"下拉框\",\r\n radio: \"单选框\",\r\n checkbox: \"复选框\",\r\n textarea: \"文本域\",\r\n date: \"日期\",\r\n number: \"数字\",\r\n };\r\n return typeMap[type] || type;\r\n};\r\n\r\nconst getFormItemForField = (field: FormOption) => {\r\n return (\r\n props.formItems?.find((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return itemProps?.path === field.prop;\r\n }) || null\r\n );\r\n};\r\n\r\n/* 布局操作 */\r\nconst addArea = (type: \"horizontal\" | \"vertical\" | \"grid\") => {\r\n const area: CustomArea = {\r\n id: generateId(),\r\n title: `${type === \"horizontal\" ? \"水平\" : type === \"vertical\" ? \"垂直\" : \"网格\"}区域`,\r\n type,\r\n fields: [],\r\n };\r\n customAreas.value.push(area);\r\n};\r\n\r\nconst deleteArea = (areaId: string | number) => {\r\n const index = customAreas.value.findIndex((area) => area.id === areaId);\r\n if (index !== -1) {\r\n customAreas.value.splice(index, 1);\r\n }\r\n};\r\n\r\nconst handleSaveLayout = () => {\r\n const config = JSON.stringify(customAreas.value, null, 2);\r\n const blob = new Blob([config], { type: \"application/json\" });\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = `layout-config-${Date.now()}.json`;\r\n document.body.appendChild(link);\r\n link.click();\r\n document.body.removeChild(link);\r\n URL.revokeObjectURL(url);\r\n};\r\n\r\nconst handleResetLayout = () => {\r\n customAreas.value = [];\r\n};\r\n\r\nconst handleExportData = () => {\r\n const exportData = {\r\n layout: customAreas.value,\r\n formData: props.formData,\r\n timestamp: new Date().toISOString(),\r\n };\r\n emit(\"export-data\", exportData);\r\n};\r\n\r\n/* 监听器 */\r\nwatchEffect(() => {\r\n const usedProps = new Set(\r\n customAreas.value.flatMap((area) => area.fields.map((field) => field.prop)),\r\n );\r\n\r\n availableFields.value = allFormOptions.value\r\n .filter((field) => !usedProps.has(field.prop))\r\n .map((field) => ({\r\n ...field,\r\n id: field.prop,\r\n }));\r\n});\r\n\r\nwatch(\r\n () => customAreas.value,\r\n () => {\r\n const allFields = customAreas.value.flatMap((area) =>\r\n area.fields.map((field) => ({\r\n ...field,\r\n id: undefined,\r\n })),\r\n );\r\n emit(\"fields-change\", allFields);\r\n },\r\n { deep: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 表单组件 - 自定义布局组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"custom-layout\">\r\n <!-- 顶部工具栏 -->\r\n <NCard :bordered=\"false\" class=\"toolbar-card\">\r\n <div class=\"toolbar-content\">\r\n <!-- 模式切换 -->\r\n <div class=\"mode-section\">\r\n <span class=\"section-label\">自定义模式:</span>\r\n <NButtonGroup>\r\n <NButton\r\n :type=\"isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = true\"\r\n size=\"small\"\r\n >\r\n 🎨 设计模式\r\n </NButton>\r\n <NButton\r\n :type=\"!isDesignMode ? 'primary' : 'default'\"\r\n @click=\"isDesignMode = false\"\r\n size=\"small\"\r\n >\r\n 📝 填写模式\r\n </NButton>\r\n </NButtonGroup>\r\n </div>\r\n\r\n <!-- 统计信息 -->\r\n <div class=\"stats-section\">\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ customAreas.length }}</div>\r\n <div class=\"stat-label\">自定义区域</div>\r\n </div>\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ totalFieldsCount }}</div>\r\n <div class=\"stat-label\">字段总数</div>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作按钮 -->\r\n <div class=\"actions-section\">\r\n <template v-if=\"isDesignMode\">\r\n <NButton secondary @click=\"handleSaveLayout\" size=\"small\">\r\n 💾 保存布局\r\n </NButton>\r\n <NButton secondary @click=\"handleResetLayout\" size=\"small\">\r\n 🔄 重置布局\r\n </NButton>\r\n </template>\r\n <template v-else>\r\n <NButton secondary @click=\"handleExportData\" size=\"small\">\r\n 📊 导出数据\r\n </NButton>\r\n </template>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 设计模式工具面板 -->\r\n <NCard v-if=\"isDesignMode\" class=\"design-panel\" title=\"🎨 设计工具\">\r\n <div class=\"design-tools\">\r\n <div class=\"tool-group\">\r\n <span class=\"group-label\">添加区域:</span>\r\n <NButton @click=\"addArea('horizontal')\" size=\"small\">\r\n ➡️ 水平区域\r\n </NButton>\r\n <NButton @click=\"addArea('vertical')\" size=\"small\">\r\n ⬇️ 垂直区域\r\n </NButton>\r\n <NButton @click=\"addArea('grid')\" size=\"small\"> ⚏ 网格区域 </NButton>\r\n </div>\r\n </div>\r\n </NCard>\r\n\r\n <!-- 主画布区域 -->\r\n <div class=\"layout-canvas\" :class=\"{ 'design-mode': isDesignMode }\">\r\n <!-- 设计模式 -->\r\n <template v-if=\"isDesignMode\">\r\n <div class=\"canvas-hint\" v-if=\"customAreas.length === 0\">\r\n <div class=\"hint-content\">\r\n <h3>🎨 开始自定义你的布局</h3>\r\n <p>点击上方按钮添加区域</p>\r\n </div>\r\n </div>\r\n\r\n <!-- 区域列表 -->\r\n <div v-else class=\"areas-container\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"custom-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <div class=\"area-header\">\r\n <div class=\"area-info\">\r\n <NInput\r\n v-if=\"editingTitleId === area.id\"\r\n v-model:value=\"area.title\"\r\n size=\"small\"\r\n @blur=\"editingTitleId = ''\"\r\n @keyup.enter=\"editingTitleId = ''\"\r\n class=\"title-input\"\r\n />\r\n <span\r\n v-else\r\n class=\"area-title\"\r\n @click=\"editingTitleId = area.id\"\r\n >\r\n {{ area.title }}\r\n </span>\r\n <NTag size=\"small\">{{ area.fields.length }} 字段</NTag>\r\n </div>\r\n <div class=\"area-controls\">\r\n <NButton\r\n text\r\n @click=\"deleteArea(area.id)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n title=\"删除区域\"\r\n >\r\n 🗑️\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段容器 -->\r\n <div class=\"area-fields\">\r\n <div\r\n v-for=\"field in area.fields\"\r\n :key=\"field.id\"\r\n class=\"field-item\"\r\n >\r\n <div class=\"field-preview\">\r\n <span class=\"field-label\">{{\r\n field.label || field.prop\r\n }}</span>\r\n <span class=\"field-type\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"area-drop-zone\" v-if=\"area.fields.length === 0\">\r\n 拖拽字段到这里\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 字段池 -->\r\n <NCard class=\"field-pool\" title=\"📦 可用字段\">\r\n <div class=\"pool-fields-grid\">\r\n <div\r\n v-for=\"field in availableFields\"\r\n :key=\"field.id\"\r\n class=\"pool-field\"\r\n >\r\n <span class=\"field-name\">{{ field.label || field.prop }}</span>\r\n <span class=\"field-type-tag\">{{\r\n getFieldTypeName(field.type)\r\n }}</span>\r\n </div>\r\n </div>\r\n </NCard>\r\n </template>\r\n\r\n <!-- 填写模式 -->\r\n <template v-else>\r\n <div v-if=\"customAreas.length === 0\" class=\"empty-layout\">\r\n <NEmpty description=\"尚未设计布局\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" type=\"primary\">\r\n 🎨 开始设计\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n\r\n <div v-else class=\"form-container\">\r\n <div class=\"form-areas\">\r\n <div\r\n v-for=\"area in customAreas\"\r\n :key=\"area.id\"\r\n class=\"form-area\"\r\n :class=\"`area-${area.type}`\"\r\n >\r\n <NCard v-if=\"area.fields.length > 0\" :title=\"area.title\">\r\n <div class=\"area-form-items\" :class=\"`layout-${area.type}`\">\r\n <component\r\n v-for=\"field in area.fields\"\r\n :key=\"field.prop\"\r\n :is=\"getFormItemForField(field)\"\r\n />\r\n </div>\r\n </NCard>\r\n <NEmpty v-else description=\"此区域暂无字段\" size=\"small\">\r\n <template #extra>\r\n <NButton @click=\"isDesignMode = true\" size=\"small\" secondary>\r\n 🎨 添加字段\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, watchEffect } from \"vue\";\r\nimport type { VNode } from \"vue\";\r\nimport type { FormOption } from \"../../types\";\r\n\r\n/* 接口定义 */\r\ninterface CustomArea {\r\n id: string;\r\n title: string;\r\n type: \"horizontal\" | \"vertical\" | \"grid\";\r\n fields: DraggableFormOption[];\r\n}\r\n\r\ninterface DraggableFormOption extends FormOption {\r\n id: string;\r\n}\r\n\r\ninterface Props {\r\n options?: FormOption[];\r\n formItems?: VNode[];\r\n formData?: Record<string, any>;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n options: () => [],\r\n formItems: () => [],\r\n formData: () => ({}),\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"fields-change\": [fields: FormOption[]];\r\n \"export-data\": [data: any];\r\n}>();\r\n\r\n/* 响应式状态 */\r\nconst isDesignMode = ref(true);\r\nconst customAreas = ref<CustomArea[]>([]);\r\nconst availableFields = ref<DraggableFormOption[]>([]);\r\nconst editingTitleId = ref<string | number>(\"\");\r\n\r\n/* 计算属性 */\r\nconst allFormOptions = computed(() => {\r\n if (props.options?.length > 0) {\r\n return props.options;\r\n }\r\n\r\n return (\r\n props.formItems\r\n ?.map((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return {\r\n prop: itemProps?.path || \"\",\r\n label: itemProps?.label || itemProps?.path || \"\",\r\n type: \"input\",\r\n show: true,\r\n } as FormOption;\r\n })\r\n .filter((option) => option.prop) || []\r\n );\r\n});\r\n\r\nconst totalFieldsCount = computed(() =>\r\n customAreas.value.reduce((total, area) => total + area.fields.length, 0),\r\n);\r\n\r\n/* 工具函数 */\r\nconst generateId = () =>\r\n `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\r\n\r\nconst getFieldTypeName = (type: string) => {\r\n const typeMap: Record<string, string> = {\r\n input: \"输入框\",\r\n select: \"下拉框\",\r\n radio: \"单选框\",\r\n checkbox: \"复选框\",\r\n textarea: \"文本域\",\r\n date: \"日期\",\r\n number: \"数字\",\r\n };\r\n return typeMap[type] || type;\r\n};\r\n\r\nconst getFormItemForField = (field: FormOption) => {\r\n return (\r\n props.formItems?.find((item: VNode) => {\r\n const itemProps = item.props as any;\r\n return itemProps?.path === field.prop;\r\n }) || null\r\n );\r\n};\r\n\r\n/* 布局操作 */\r\nconst addArea = (type: \"horizontal\" | \"vertical\" | \"grid\") => {\r\n const area: CustomArea = {\r\n id: generateId(),\r\n title: `${type === \"horizontal\" ? \"水平\" : type === \"vertical\" ? \"垂直\" : \"网格\"}区域`,\r\n type,\r\n fields: [],\r\n };\r\n customAreas.value.push(area);\r\n};\r\n\r\nconst deleteArea = (areaId: string | number) => {\r\n const index = customAreas.value.findIndex((area) => area.id === areaId);\r\n if (index !== -1) {\r\n customAreas.value.splice(index, 1);\r\n }\r\n};\r\n\r\nconst handleSaveLayout = () => {\r\n const config = JSON.stringify(customAreas.value, null, 2);\r\n const blob = new Blob([config], { type: \"application/json\" });\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = `layout-config-${Date.now()}.json`;\r\n document.body.appendChild(link);\r\n link.click();\r\n document.body.removeChild(link);\r\n URL.revokeObjectURL(url);\r\n};\r\n\r\nconst handleResetLayout = () => {\r\n customAreas.value = [];\r\n};\r\n\r\nconst handleExportData = () => {\r\n const exportData = {\r\n layout: customAreas.value,\r\n formData: props.formData,\r\n timestamp: new Date().toISOString(),\r\n };\r\n emit(\"export-data\", exportData);\r\n};\r\n\r\n/* 监听器 */\r\nwatchEffect(() => {\r\n const usedProps = new Set(\r\n customAreas.value.flatMap((area) => area.fields.map((field) => field.prop)),\r\n );\r\n\r\n availableFields.value = allFormOptions.value\r\n .filter((field) => !usedProps.has(field.prop))\r\n .map((field) => ({\r\n ...field,\r\n id: field.prop,\r\n }));\r\n});\r\n\r\nwatch(\r\n () => customAreas.value,\r\n () => {\r\n const allFields = customAreas.value.flatMap((area) =>\r\n area.fields.map((field) => ({\r\n ...field,\r\n id: undefined,\r\n })),\r\n );\r\n emit(\"fields-change\", allFields);\r\n },\r\n { deep: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: 表单组件 — 薄 UI 壳 + 厚 Composable 引擎\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <NForm\r\n ref=\"formRef\"\r\n :model=\"formModel\"\r\n :rules=\"formRules\"\r\n :validate-on-rule-change=\"false\"\r\n :label-placement=\"resolved.labelPlacement\"\r\n :label-width=\"resolved.labelWidth\"\r\n :size=\"resolved.size\"\r\n :disabled=\"resolved.disabled\"\r\n :readonly=\"resolved.readonly\"\r\n v-bind=\"$attrs\"\r\n >\r\n <!-- 布局组件渲染 -->\r\n <component\r\n :is=\"layoutComponent\"\r\n :form-items=\"formItems\"\r\n :layout-config=\"mergedLayoutConfig\"\r\n :options=\"visibleOptions\"\r\n :form-data=\"formModel\"\r\n @tab-change=\"handleLayoutEvent('onTabChange', $event)\"\r\n @tab-validate=\"handleLayoutEvent('onTabValidate', $event)\"\r\n @step-change=\"handleStepChange\"\r\n @step-before-change=\"handleStepBeforeChange\"\r\n @step-validate=\"handleStepValidate\"\r\n @field-add=\"handleLayoutEvent('onFieldAdd', $event)\"\r\n @field-remove=\"handleLayoutEvent('onFieldRemove', $event)\"\r\n @field-toggle=\"\r\n (id: string, visible: boolean) => resolved.onFieldToggle?.(id, visible)\r\n \"\r\n @fields-clear=\"handleLayoutEvent('onFieldsClear')\"\r\n @render-mode-change=\"handleLayoutEvent('onRenderModeChange', $event)\"\r\n @group-toggle=\"\r\n (key: string, collapsed: boolean) =>\r\n resolved.onGroupToggle?.(key, collapsed)\r\n \"\r\n @group-reset=\"handleLayoutEvent('onGroupReset', $event)\"\r\n @validate-success=\"(model: FormModel) => emit('validate-success', model)\"\r\n @validate-error=\"(errors: unknown) => emit('validate-error', errors)\"\r\n @fields-change=\"handleFieldsChange\"\r\n />\r\n\r\n <!-- 表单操作按钮区域(只在特定布局中显示) -->\r\n <NFormItem v-if=\"showActions\" style=\"margin-top: 20px\">\r\n <slot\r\n name=\"action\"\r\n :form=\"formRef\"\r\n :model=\"formModel\"\r\n :validate=\"validate\"\r\n :validateField=\"validateField\"\r\n :reset=\"resetFields\"\r\n :setFields=\"setFields\"\r\n :getModel=\"getModel\"\r\n :clearValidation=\"clearValidation\"\r\n >\r\n <NSpace>\r\n <NButton type=\"primary\" @click=\"handleSubmit\">提交</NButton>\r\n <NButton @click=\"handleReset\">重置</NButton>\r\n </NSpace>\r\n </slot>\r\n </NFormItem>\r\n </NForm>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport {\r\n computed,\r\n ref,\r\n watch,\r\n getCurrentInstance,\r\n} from \"vue\";\r\nimport type { FormInst } from \"naive-ui/es/form\";\r\nimport {\r\n NForm,\r\n NFormItem,\r\n NButton,\r\n NSpace,\r\n NInput,\r\n NInputNumber,\r\n NSwitch,\r\n NSlider,\r\n NRate,\r\n NDatePicker,\r\n NTimePicker,\r\n NCascader,\r\n NColorPicker,\r\n NSelect,\r\n NCheckboxGroup,\r\n NCheckbox,\r\n NRadioGroup,\r\n NRadio,\r\n NUpload,\r\n} from \"naive-ui\";\r\nimport { C_Editor } from \"../C_Editor\";\r\nimport type {\r\n FormOption,\r\n LayoutType,\r\n LayoutConfig,\r\n SubmitEventPayload,\r\n FormModel,\r\n} from \"./types\";\r\nimport {\r\n type FormConfig,\r\n resolveFormConfig,\r\n shouldShowActions as calcShowActions,\r\n} from \"./composables/useFormConfig\";\r\nimport { useFormState } from \"./composables/useFormState\";\r\nimport {\r\n useFormRenderer,\r\n type ComponentMap,\r\n} from \"./composables/useFormRenderer\";\r\n\r\n/* ===== 布局组件静态映射(取代 DynamicComponent) ===== */\r\nimport DefaultLayout from \"./layouts/Default/index.vue\";\r\nimport InlineLayout from \"./layouts/Inline/index.vue\";\r\nimport GridLayout from \"./layouts/Grid/index.vue\";\r\nimport CardLayout from \"./layouts/Card/index.vue\";\r\nimport TabsLayout from \"./layouts/Tabs/index.vue\";\r\nimport StepsLayout from \"./layouts/Steps/index.vue\";\r\nimport DynamicLayout from \"./layouts/Dynamic/index.vue\";\r\nimport CustomLayout from \"./layouts/Custom/index.vue\";\r\n\r\ndefineOptions({ name: \"C_Form\" });\r\n\r\nconst LAYOUT_MAP: Record<LayoutType, any> = {\r\n default: DefaultLayout,\r\n inline: InlineLayout,\r\n grid: GridLayout,\r\n card: CardLayout,\r\n tabs: TabsLayout,\r\n steps: StepsLayout,\r\n dynamic: DynamicLayout,\r\n custom: CustomLayout,\r\n} as const;\r\n\r\n/* ===== Naive UI 组件映射 ===== */\r\n/*\r\n * 库代码是预构建产物,resolveComponent 无法在运行时解析未全局注册的组件。\r\n * 直接 import 即可。naive-ui 作为 peerDependency,与消费项目共享同一份实例。\r\n */\r\nconst COMPONENT_MAP: ComponentMap = {\r\n NFormItem,\r\n NInput,\r\n NInputNumber,\r\n NSwitch,\r\n NSlider,\r\n NRate,\r\n NDatePicker,\r\n NTimePicker,\r\n NCascader,\r\n NColorPicker,\r\n NSelect,\r\n NCheckboxGroup,\r\n NCheckbox,\r\n NRadioGroup,\r\n NRadio,\r\n NUpload,\r\n NButton,\r\n NSpace,\r\n C_Editor,\r\n} as ComponentMap;\r\n\r\n/* ================= 组件属性定义 ================= */\r\n\r\ninterface CFormProps {\r\n /** 字段配置数组 */\r\n options: FormOption[];\r\n /** 双向绑定表单数据 */\r\n modelValue?: FormModel;\r\n /** 统一配置对象(收拢原先 13 个分散 Props) */\r\n config?: FormConfig;\r\n}\r\n\r\nconst props = withDefaults(defineProps<CFormProps>(), {\r\n config: () => ({}),\r\n});\r\n\r\n/* ================= 组件事件定义(从 16 个精简到 4 个) ================= */\r\n\r\nconst emit = defineEmits<{\r\n submit: [payload: SubmitEventPayload];\r\n \"update:modelValue\": [model: FormModel];\r\n \"validate-success\": [model: FormModel];\r\n \"validate-error\": [errors: unknown];\r\n}>();\r\n\r\n/* ================= 配置解析 ================= */\r\n\r\nconst resolved = computed(() => resolveFormConfig(props.config));\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst formRef = ref<FormInst | null>(null);\r\nconst optionsRef = computed(() => props.options);\r\n\r\n/* ===== 状态引擎 ===== */\r\nconst {\r\n formModel,\r\n formRules,\r\n visibleOptions,\r\n initialize,\r\n handleFieldChange,\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n handleSubmit,\r\n handleReset,\r\n} = useFormState(optionsRef, resolved, formRef, emit);\r\n\r\n/* ===== 渲染引擎 ===== */\r\nconst currentInstance = getCurrentInstance();\r\nconst { formItems } = useFormRenderer(\r\n formModel,\r\n visibleOptions,\r\n resolved,\r\n handleFieldChange,\r\n COMPONENT_MAP,\r\n currentInstance?.slots,\r\n);\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst layoutComponent = computed(\r\n () => LAYOUT_MAP[resolved.value.layout] || LAYOUT_MAP.default,\r\n);\r\n\r\nconst mergedLayoutConfig = computed<LayoutConfig>(() => ({\r\n type: resolved.value.layout,\r\n grid: resolved.value.grid,\r\n inline: resolved.value.inline,\r\n card: resolved.value.card,\r\n tabs: resolved.value.tabs,\r\n steps: resolved.value.steps,\r\n dynamic: resolved.value.dynamic,\r\n custom: resolved.value.custom,\r\n}));\r\n\r\nconst showActions = computed(() => calcShowActions(resolved.value));\r\n\r\n/* ================= 布局事件 → config 回调桥接 ================= */\r\n\r\n/** 通用布局事件桥接 */\r\nconst handleLayoutEvent = (callbackName: string, ...args: any[]) => {\r\n const callback = (resolved.value as any)[callbackName];\r\n callback?.(...args);\r\n};\r\n\r\n/** 字段变化事件(保留回调通道) */\r\nconst handleFieldsChange = (fields: FormOption[]): void => {\r\n resolved.value.onFieldsChange?.(fields);\r\n};\r\n\r\n/** Steps 布局事件 — 需要多参数特殊处理 */\r\nconst handleStepChange = (stepIndex: number, stepKey: string): void => {\r\n resolved.value.onStepChange?.(stepIndex, stepKey);\r\n};\r\n\r\nconst handleStepBeforeChange = async (\r\n currentStep: number,\r\n targetStep: number,\r\n): Promise<boolean> => {\r\n resolved.value.onStepBeforeChange?.(currentStep, targetStep);\r\n return true;\r\n};\r\n\r\nconst handleStepValidate = async (stepIndex: number): Promise<boolean> => {\r\n try {\r\n const currentStepKey = resolved.value.steps?.steps?.[stepIndex]?.key;\r\n if (!currentStepKey) return true;\r\n\r\n const stepFields = props.options\r\n .filter((option) => option.layout?.step === currentStepKey)\r\n .map((option) => option.prop);\r\n\r\n if (stepFields.length === 0) return true;\r\n\r\n await validateField(stepFields);\r\n resolved.value.onStepValidate?.(stepIndex);\r\n return true;\r\n } catch (error) {\r\n console.warn(`[C_Form] 步骤 ${stepIndex} 验证失败:`, error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= modelValue 双向同步 ================= */\r\n\r\nwatch(\r\n () => props.modelValue,\r\n (val) => {\r\n if (val) Object.assign(formModel, val);\r\n },\r\n { immediate: true, deep: true },\r\n);\r\n\r\n/* ================= 组件暴露 ================= */\r\n\r\ndefineExpose({\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n formRef,\r\n formModel,\r\n initialize,\r\n layoutType: computed(() => resolved.value.layout),\r\n shouldShowDefaultActions: showActions,\r\n});\r\n</script>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: 表单组件 — 薄 UI 壳 + 厚 Composable 引擎\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <NForm\r\n ref=\"formRef\"\r\n :model=\"formModel\"\r\n :rules=\"formRules\"\r\n :validate-on-rule-change=\"false\"\r\n :label-placement=\"resolved.labelPlacement\"\r\n :label-width=\"resolved.labelWidth\"\r\n :size=\"resolved.size\"\r\n :disabled=\"resolved.disabled\"\r\n :readonly=\"resolved.readonly\"\r\n v-bind=\"$attrs\"\r\n >\r\n <!-- 布局组件渲染 -->\r\n <component\r\n :is=\"layoutComponent\"\r\n :form-items=\"formItems\"\r\n :layout-config=\"mergedLayoutConfig\"\r\n :options=\"visibleOptions\"\r\n :form-data=\"formModel\"\r\n @tab-change=\"handleLayoutEvent('onTabChange', $event)\"\r\n @tab-validate=\"handleLayoutEvent('onTabValidate', $event)\"\r\n @step-change=\"handleStepChange\"\r\n @step-before-change=\"handleStepBeforeChange\"\r\n @step-validate=\"handleStepValidate\"\r\n @field-add=\"handleLayoutEvent('onFieldAdd', $event)\"\r\n @field-remove=\"handleLayoutEvent('onFieldRemove', $event)\"\r\n @field-toggle=\"\r\n (id: string, visible: boolean) => resolved.onFieldToggle?.(id, visible)\r\n \"\r\n @fields-clear=\"handleLayoutEvent('onFieldsClear')\"\r\n @render-mode-change=\"handleLayoutEvent('onRenderModeChange', $event)\"\r\n @group-toggle=\"\r\n (key: string, collapsed: boolean) =>\r\n resolved.onGroupToggle?.(key, collapsed)\r\n \"\r\n @group-reset=\"handleLayoutEvent('onGroupReset', $event)\"\r\n @validate-success=\"(model: FormModel) => emit('validate-success', model)\"\r\n @validate-error=\"(errors: unknown) => emit('validate-error', errors)\"\r\n @fields-change=\"handleFieldsChange\"\r\n />\r\n\r\n <!-- 表单操作按钮区域(只在特定布局中显示) -->\r\n <NFormItem v-if=\"showActions\" style=\"margin-top: 20px\">\r\n <slot\r\n name=\"action\"\r\n :form=\"formRef\"\r\n :model=\"formModel\"\r\n :validate=\"validate\"\r\n :validateField=\"validateField\"\r\n :reset=\"resetFields\"\r\n :setFields=\"setFields\"\r\n :getModel=\"getModel\"\r\n :clearValidation=\"clearValidation\"\r\n >\r\n <NSpace>\r\n <NButton type=\"primary\" @click=\"handleSubmit\">提交</NButton>\r\n <NButton @click=\"handleReset\">重置</NButton>\r\n </NSpace>\r\n </slot>\r\n </NFormItem>\r\n </NForm>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport {\r\n computed,\r\n ref,\r\n watch,\r\n getCurrentInstance,\r\n} from \"vue\";\r\nimport type { FormInst } from \"naive-ui/es/form\";\r\nimport {\r\n NForm,\r\n NFormItem,\r\n NButton,\r\n NSpace,\r\n NInput,\r\n NInputNumber,\r\n NSwitch,\r\n NSlider,\r\n NRate,\r\n NDatePicker,\r\n NTimePicker,\r\n NCascader,\r\n NColorPicker,\r\n NSelect,\r\n NCheckboxGroup,\r\n NCheckbox,\r\n NRadioGroup,\r\n NRadio,\r\n NUpload,\r\n} from \"naive-ui\";\r\nimport { C_Editor } from \"../C_Editor\";\r\nimport type {\r\n FormOption,\r\n LayoutType,\r\n LayoutConfig,\r\n SubmitEventPayload,\r\n FormModel,\r\n} from \"./types\";\r\nimport {\r\n type FormConfig,\r\n resolveFormConfig,\r\n shouldShowActions as calcShowActions,\r\n} from \"./composables/useFormConfig\";\r\nimport { useFormState } from \"./composables/useFormState\";\r\nimport {\r\n useFormRenderer,\r\n type ComponentMap,\r\n} from \"./composables/useFormRenderer\";\r\n\r\n/* ===== 布局组件静态映射(取代 DynamicComponent) ===== */\r\nimport DefaultLayout from \"./layouts/Default/index.vue\";\r\nimport InlineLayout from \"./layouts/Inline/index.vue\";\r\nimport GridLayout from \"./layouts/Grid/index.vue\";\r\nimport CardLayout from \"./layouts/Card/index.vue\";\r\nimport TabsLayout from \"./layouts/Tabs/index.vue\";\r\nimport StepsLayout from \"./layouts/Steps/index.vue\";\r\nimport DynamicLayout from \"./layouts/Dynamic/index.vue\";\r\nimport CustomLayout from \"./layouts/Custom/index.vue\";\r\n\r\ndefineOptions({ name: \"C_Form\" });\r\n\r\nconst LAYOUT_MAP: Record<LayoutType, any> = {\r\n default: DefaultLayout,\r\n inline: InlineLayout,\r\n grid: GridLayout,\r\n card: CardLayout,\r\n tabs: TabsLayout,\r\n steps: StepsLayout,\r\n dynamic: DynamicLayout,\r\n custom: CustomLayout,\r\n} as const;\r\n\r\n/* ===== Naive UI 组件映射 ===== */\r\n/*\r\n * 库代码是预构建产物,resolveComponent 无法在运行时解析未全局注册的组件。\r\n * 直接 import 即可。naive-ui 作为 peerDependency,与消费项目共享同一份实例。\r\n */\r\nconst COMPONENT_MAP: ComponentMap = {\r\n NFormItem,\r\n NInput,\r\n NInputNumber,\r\n NSwitch,\r\n NSlider,\r\n NRate,\r\n NDatePicker,\r\n NTimePicker,\r\n NCascader,\r\n NColorPicker,\r\n NSelect,\r\n NCheckboxGroup,\r\n NCheckbox,\r\n NRadioGroup,\r\n NRadio,\r\n NUpload,\r\n NButton,\r\n NSpace,\r\n C_Editor,\r\n} as ComponentMap;\r\n\r\n/* ================= 组件属性定义 ================= */\r\n\r\ninterface CFormProps {\r\n /** 字段配置数组 */\r\n options: FormOption[];\r\n /** 双向绑定表单数据 */\r\n modelValue?: FormModel;\r\n /** 统一配置对象(收拢原先 13 个分散 Props) */\r\n config?: FormConfig;\r\n}\r\n\r\nconst props = withDefaults(defineProps<CFormProps>(), {\r\n config: () => ({}),\r\n});\r\n\r\n/* ================= 组件事件定义(从 16 个精简到 4 个) ================= */\r\n\r\nconst emit = defineEmits<{\r\n submit: [payload: SubmitEventPayload];\r\n \"update:modelValue\": [model: FormModel];\r\n \"validate-success\": [model: FormModel];\r\n \"validate-error\": [errors: unknown];\r\n}>();\r\n\r\n/* ================= 配置解析 ================= */\r\n\r\nconst resolved = computed(() => resolveFormConfig(props.config));\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst formRef = ref<FormInst | null>(null);\r\nconst optionsRef = computed(() => props.options);\r\n\r\n/* ===== 状态引擎 ===== */\r\nconst {\r\n formModel,\r\n formRules,\r\n visibleOptions,\r\n initialize,\r\n handleFieldChange,\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n handleSubmit,\r\n handleReset,\r\n} = useFormState(optionsRef, resolved, formRef, emit);\r\n\r\n/* ===== 渲染引擎 ===== */\r\nconst currentInstance = getCurrentInstance();\r\nconst { formItems } = useFormRenderer(\r\n formModel,\r\n visibleOptions,\r\n resolved,\r\n handleFieldChange,\r\n COMPONENT_MAP,\r\n currentInstance?.slots,\r\n);\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst layoutComponent = computed(\r\n () => LAYOUT_MAP[resolved.value.layout] || LAYOUT_MAP.default,\r\n);\r\n\r\nconst mergedLayoutConfig = computed<LayoutConfig>(() => ({\r\n type: resolved.value.layout,\r\n grid: resolved.value.grid,\r\n inline: resolved.value.inline,\r\n card: resolved.value.card,\r\n tabs: resolved.value.tabs,\r\n steps: resolved.value.steps,\r\n dynamic: resolved.value.dynamic,\r\n custom: resolved.value.custom,\r\n}));\r\n\r\nconst showActions = computed(() => calcShowActions(resolved.value));\r\n\r\n/* ================= 布局事件 → config 回调桥接 ================= */\r\n\r\n/** 通用布局事件桥接 */\r\nconst handleLayoutEvent = (callbackName: string, ...args: any[]) => {\r\n const callback = (resolved.value as any)[callbackName];\r\n callback?.(...args);\r\n};\r\n\r\n/** 字段变化事件(保留回调通道) */\r\nconst handleFieldsChange = (fields: FormOption[]): void => {\r\n resolved.value.onFieldsChange?.(fields);\r\n};\r\n\r\n/** Steps 布局事件 — 需要多参数特殊处理 */\r\nconst handleStepChange = (stepIndex: number, stepKey: string): void => {\r\n resolved.value.onStepChange?.(stepIndex, stepKey);\r\n};\r\n\r\nconst handleStepBeforeChange = async (\r\n currentStep: number,\r\n targetStep: number,\r\n): Promise<boolean> => {\r\n resolved.value.onStepBeforeChange?.(currentStep, targetStep);\r\n return true;\r\n};\r\n\r\nconst handleStepValidate = async (stepIndex: number): Promise<boolean> => {\r\n try {\r\n const currentStepKey = resolved.value.steps?.steps?.[stepIndex]?.key;\r\n if (!currentStepKey) return true;\r\n\r\n const stepFields = props.options\r\n .filter((option) => option.layout?.step === currentStepKey)\r\n .map((option) => option.prop);\r\n\r\n if (stepFields.length === 0) return true;\r\n\r\n await validateField(stepFields);\r\n resolved.value.onStepValidate?.(stepIndex);\r\n return true;\r\n } catch (error) {\r\n console.warn(`[C_Form] 步骤 ${stepIndex} 验证失败:`, error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= modelValue 双向同步 ================= */\r\n\r\nwatch(\r\n () => props.modelValue,\r\n (val) => {\r\n if (val) Object.assign(formModel, val);\r\n },\r\n { immediate: true, deep: true },\r\n);\r\n\r\n/* ================= 组件暴露 ================= */\r\n\r\ndefineExpose({\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n formRef,\r\n formModel,\r\n initialize,\r\n layoutType: computed(() => resolved.value.layout),\r\n shouldShowDefaultActions: showActions,\r\n});\r\n</script>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Description: 表单组件 — 薄 UI 壳 + 厚 Composable 引擎\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <NForm\r\n ref=\"formRef\"\r\n :model=\"formModel\"\r\n :rules=\"formRules\"\r\n :validate-on-rule-change=\"false\"\r\n :label-placement=\"resolved.labelPlacement\"\r\n :label-width=\"resolved.labelWidth\"\r\n :size=\"resolved.size\"\r\n :disabled=\"resolved.disabled\"\r\n :readonly=\"resolved.readonly\"\r\n v-bind=\"$attrs\"\r\n >\r\n <!-- 布局组件渲染 -->\r\n <component\r\n :is=\"layoutComponent\"\r\n :form-items=\"formItems\"\r\n :layout-config=\"mergedLayoutConfig\"\r\n :options=\"visibleOptions\"\r\n :form-data=\"formModel\"\r\n @tab-change=\"handleLayoutEvent('onTabChange', $event)\"\r\n @tab-validate=\"handleLayoutEvent('onTabValidate', $event)\"\r\n @step-change=\"handleStepChange\"\r\n @step-before-change=\"handleStepBeforeChange\"\r\n @step-validate=\"handleStepValidate\"\r\n @field-add=\"handleLayoutEvent('onFieldAdd', $event)\"\r\n @field-remove=\"handleLayoutEvent('onFieldRemove', $event)\"\r\n @field-toggle=\"\r\n (id: string, visible: boolean) => resolved.onFieldToggle?.(id, visible)\r\n \"\r\n @fields-clear=\"handleLayoutEvent('onFieldsClear')\"\r\n @render-mode-change=\"handleLayoutEvent('onRenderModeChange', $event)\"\r\n @group-toggle=\"\r\n (key: string, collapsed: boolean) =>\r\n resolved.onGroupToggle?.(key, collapsed)\r\n \"\r\n @group-reset=\"handleLayoutEvent('onGroupReset', $event)\"\r\n @validate-success=\"(model: FormModel) => emit('validate-success', model)\"\r\n @validate-error=\"(errors: unknown) => emit('validate-error', errors)\"\r\n @fields-change=\"handleFieldsChange\"\r\n />\r\n\r\n <!-- 表单操作按钮区域(只在特定布局中显示) -->\r\n <NFormItem v-if=\"showActions\" style=\"margin-top: 20px\">\r\n <slot\r\n name=\"action\"\r\n :form=\"formRef\"\r\n :model=\"formModel\"\r\n :validate=\"validate\"\r\n :validateField=\"validateField\"\r\n :reset=\"resetFields\"\r\n :setFields=\"setFields\"\r\n :getModel=\"getModel\"\r\n :clearValidation=\"clearValidation\"\r\n >\r\n <NSpace>\r\n <NButton type=\"primary\" @click=\"handleSubmit\">提交</NButton>\r\n <NButton @click=\"handleReset\">重置</NButton>\r\n </NSpace>\r\n </slot>\r\n </NFormItem>\r\n </NForm>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport {\r\n computed,\r\n ref,\r\n watch,\r\n getCurrentInstance,\r\n} from \"vue\";\r\nimport type { FormInst } from \"naive-ui/es/form\";\r\nimport {\r\n NForm,\r\n NFormItem,\r\n NButton,\r\n NSpace,\r\n NInput,\r\n NInputNumber,\r\n NSwitch,\r\n NSlider,\r\n NRate,\r\n NDatePicker,\r\n NTimePicker,\r\n NCascader,\r\n NColorPicker,\r\n NSelect,\r\n NCheckboxGroup,\r\n NCheckbox,\r\n NRadioGroup,\r\n NRadio,\r\n NUpload,\r\n} from \"naive-ui\";\r\nimport { C_Editor } from \"../C_Editor\";\r\nimport type {\r\n FormOption,\r\n LayoutType,\r\n LayoutConfig,\r\n SubmitEventPayload,\r\n FormModel,\r\n} from \"./types\";\r\nimport {\r\n type FormConfig,\r\n resolveFormConfig,\r\n shouldShowActions as calcShowActions,\r\n} from \"./composables/useFormConfig\";\r\nimport { useFormState } from \"./composables/useFormState\";\r\nimport {\r\n useFormRenderer,\r\n type ComponentMap,\r\n} from \"./composables/useFormRenderer\";\r\n\r\n/* ===== 布局组件静态映射(取代 DynamicComponent) ===== */\r\nimport DefaultLayout from \"./layouts/Default/index.vue\";\r\nimport InlineLayout from \"./layouts/Inline/index.vue\";\r\nimport GridLayout from \"./layouts/Grid/index.vue\";\r\nimport CardLayout from \"./layouts/Card/index.vue\";\r\nimport TabsLayout from \"./layouts/Tabs/index.vue\";\r\nimport StepsLayout from \"./layouts/Steps/index.vue\";\r\nimport DynamicLayout from \"./layouts/Dynamic/index.vue\";\r\nimport CustomLayout from \"./layouts/Custom/index.vue\";\r\n\r\ndefineOptions({ name: \"C_Form\" });\r\n\r\nconst LAYOUT_MAP: Record<LayoutType, any> = {\r\n default: DefaultLayout,\r\n inline: InlineLayout,\r\n grid: GridLayout,\r\n card: CardLayout,\r\n tabs: TabsLayout,\r\n steps: StepsLayout,\r\n dynamic: DynamicLayout,\r\n custom: CustomLayout,\r\n} as const;\r\n\r\n/* ===== Naive UI 组件映射 ===== */\r\n/*\r\n * 库代码是预构建产物,resolveComponent 无法在运行时解析未全局注册的组件。\r\n * 直接 import 即可。naive-ui 作为 peerDependency,与消费项目共享同一份实例。\r\n */\r\nconst COMPONENT_MAP: ComponentMap = {\r\n NFormItem,\r\n NInput,\r\n NInputNumber,\r\n NSwitch,\r\n NSlider,\r\n NRate,\r\n NDatePicker,\r\n NTimePicker,\r\n NCascader,\r\n NColorPicker,\r\n NSelect,\r\n NCheckboxGroup,\r\n NCheckbox,\r\n NRadioGroup,\r\n NRadio,\r\n NUpload,\r\n NButton,\r\n NSpace,\r\n C_Editor,\r\n} as ComponentMap;\r\n\r\n/* ================= 组件属性定义 ================= */\r\n\r\ninterface CFormProps {\r\n /** 字段配置数组 */\r\n options: FormOption[];\r\n /** 双向绑定表单数据 */\r\n modelValue?: FormModel;\r\n /** 统一配置对象(收拢原先 13 个分散 Props) */\r\n config?: FormConfig;\r\n}\r\n\r\nconst props = withDefaults(defineProps<CFormProps>(), {\r\n config: () => ({}),\r\n});\r\n\r\n/* ================= 组件事件定义(从 16 个精简到 4 个) ================= */\r\n\r\nconst emit = defineEmits<{\r\n submit: [payload: SubmitEventPayload];\r\n \"update:modelValue\": [model: FormModel];\r\n \"validate-success\": [model: FormModel];\r\n \"validate-error\": [errors: unknown];\r\n}>();\r\n\r\n/* ================= 配置解析 ================= */\r\n\r\nconst resolved = computed(() => resolveFormConfig(props.config));\r\n\r\n/* ================= 响应式状态 ================= */\r\n\r\nconst formRef = ref<FormInst | null>(null);\r\nconst optionsRef = computed(() => props.options);\r\n\r\n/* ===== 状态引擎 ===== */\r\nconst {\r\n formModel,\r\n formRules,\r\n visibleOptions,\r\n initialize,\r\n handleFieldChange,\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n handleSubmit,\r\n handleReset,\r\n} = useFormState(optionsRef, resolved, formRef, emit);\r\n\r\n/* ===== 渲染引擎 ===== */\r\nconst currentInstance = getCurrentInstance();\r\nconst { formItems } = useFormRenderer(\r\n formModel,\r\n visibleOptions,\r\n resolved,\r\n handleFieldChange,\r\n COMPONENT_MAP,\r\n currentInstance?.slots,\r\n);\r\n\r\n/* ================= 计算属性 ================= */\r\n\r\nconst layoutComponent = computed(\r\n () => LAYOUT_MAP[resolved.value.layout] || LAYOUT_MAP.default,\r\n);\r\n\r\nconst mergedLayoutConfig = computed<LayoutConfig>(() => ({\r\n type: resolved.value.layout,\r\n grid: resolved.value.grid,\r\n inline: resolved.value.inline,\r\n card: resolved.value.card,\r\n tabs: resolved.value.tabs,\r\n steps: resolved.value.steps,\r\n dynamic: resolved.value.dynamic,\r\n custom: resolved.value.custom,\r\n}));\r\n\r\nconst showActions = computed(() => calcShowActions(resolved.value));\r\n\r\n/* ================= 布局事件 → config 回调桥接 ================= */\r\n\r\n/** 通用布局事件桥接 */\r\nconst handleLayoutEvent = (callbackName: string, ...args: any[]) => {\r\n const callback = (resolved.value as any)[callbackName];\r\n callback?.(...args);\r\n};\r\n\r\n/** 字段变化事件(保留回调通道) */\r\nconst handleFieldsChange = (fields: FormOption[]): void => {\r\n resolved.value.onFieldsChange?.(fields);\r\n};\r\n\r\n/** Steps 布局事件 — 需要多参数特殊处理 */\r\nconst handleStepChange = (stepIndex: number, stepKey: string): void => {\r\n resolved.value.onStepChange?.(stepIndex, stepKey);\r\n};\r\n\r\nconst handleStepBeforeChange = async (\r\n currentStep: number,\r\n targetStep: number,\r\n): Promise<boolean> => {\r\n resolved.value.onStepBeforeChange?.(currentStep, targetStep);\r\n return true;\r\n};\r\n\r\nconst handleStepValidate = async (stepIndex: number): Promise<boolean> => {\r\n try {\r\n const currentStepKey = resolved.value.steps?.steps?.[stepIndex]?.key;\r\n if (!currentStepKey) return true;\r\n\r\n const stepFields = props.options\r\n .filter((option) => option.layout?.step === currentStepKey)\r\n .map((option) => option.prop);\r\n\r\n if (stepFields.length === 0) return true;\r\n\r\n await validateField(stepFields);\r\n resolved.value.onStepValidate?.(stepIndex);\r\n return true;\r\n } catch (error) {\r\n console.warn(`[C_Form] 步骤 ${stepIndex} 验证失败:`, error);\r\n return false;\r\n }\r\n};\r\n\r\n/* ================= modelValue 双向同步 ================= */\r\n\r\nwatch(\r\n () => props.modelValue,\r\n (val) => {\r\n if (val) Object.assign(formModel, val);\r\n },\r\n { immediate: true, deep: true },\r\n);\r\n\r\n/* ================= 组件暴露 ================= */\r\n\r\ndefineExpose({\r\n validate,\r\n validateField,\r\n validateStep,\r\n validateTab,\r\n validateDynamicFields,\r\n validateCustomGroup,\r\n clearValidation,\r\n getModel,\r\n setFields,\r\n resetFields,\r\n setFieldValue,\r\n getFieldValue,\r\n setFieldsValue,\r\n formRef,\r\n formModel,\r\n initialize,\r\n layoutType: computed(() => resolved.value.layout),\r\n shouldShowDefaultActions: showActions,\r\n});\r\n</script>\r\n"],"mappings":";;;;;;;;AAyHA,MAAa,gBAAoC;CAC/C,QAAQ;CACR,gBAAgB;CAChB,YAAY;CACZ,MAAM;CACN,UAAU;CACV,UAAU;CACV,aAAa;CACb,kBAAkB;CACnB;;AAGD,MAAa,4BAAmD,CAC9D,SACA,SACD;;;;;;AASD,SAAgB,kBAAkB,QAAyC;AACzE,QAAO;EAAE,GAAG;EAAe,GAAG;EAAQ;;;;;;AAOxC,SAAgB,kBAAkB,UAAuC;AACvE,KAAI,SAAS,gBAAgB,MAAO,QAAO;AAC3C,KAAI,0BAA0B,SAAS,SAAS,OAAO,CAAE,QAAO;AAChE,QAAO;;;;;;AC/HT,MAAM,iBAAiD;CACrD,OAAO;CACP,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,YAAY;CACZ,WAAW;CACX,YAAY;CACZ,UAAU;CACV,aAAa;CACb,UAAU;CACV,QAAQ,EAAE;CACV,OAAO;CACP,aAAa;CACb,QAAQ;CACR,MAAM;CACN,QAAQ;CACT;AAED,MAAM,mBAAmB,SAAiC;AACxD,QAAO,eAAe,SAAS;;;;;;;;;AAYjC,SAAgB,aACd,SACA,QACA,SACA,MAMA;CAEA,MAAM,YAAY,SAAoB,EAAE,CAAC;CACzC,MAAM,YAAY,SAAoB,EAAE,CAAC;CAGzC,MAAM,iBAAiB,eACrB,QAAQ,MAAM,QAAO,SAAQ,KAAK,SAAS,MAAM,CAClD;CAGD,MAAM,mBAAyB;AAC7B,MAAI;AAEF,UAAO,KAAK,UAAU,CAAC,SAAQ,QAAO,OAAO,UAAU,KAAK;AAG5D,WAAQ,MAAM,SAAQ,SAAQ;AAK5B,QAAI,EAAE,KAAK,QAAQ,WACjB,WAAU,KAAK,QACb,KAAK,UAAU,SACX,KAAK,QACL,gBAAgB,KAAK,KAAsB;AAGnD,QAAI,KAAK,OAAO,QAAQ;KAMtB,MAAM,mBAAmB,KAAK,MAAM,OAClC,MAAK,OAAQ,EAA8B,cAAc,WAC1D;AACD,eAAU,KAAK,QAAQ,mBACnB,WAAW,KAAK,MAAM,GACtB,KAAK;;KAEX;WACK,OAAO;AACd,WAAQ,MAAM,mBAAmB,MAAM;;;CAK3C,MAAM,qBAAqB,UAAwB;AACjD,MAAI,OAAO,MAAM,iBACf,gBAAe;AACb,iBAAc,MAAM,CAAC,YAAY,GAAG;IACpC;;CAKN,MAAM,WAAW,YAA2B;AAC1C,MAAI,CAAC,QAAQ,MACX,OAAM,IAAI,MAAM,mBAAmB;AAGrC,MAAI;AACF,SAAM,QAAQ,MAAM,UAAU;AAC9B,QAAK,oBAAoB,UAAU,CAAC;WAC7B,QAAQ;AACf,QAAK,kBAAkB,OAAO;AAC9B,SAAM;;;CAIV,MAAM,gBAAgB,OAAO,UAA4C;AACvE,MAAI,CAAC,QAAQ,MACX,OAAM,IAAI,MAAM,mBAAmB;EAGrC,MAAM,SAAS,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM;AAMrD,MAAI;AACF,SAAM,QAAQ,MAAM,UAAU;WACvB,WAAoB;AAE3B,OAAI,CAAC,MAAM,QAAQ,UAAU,CAAE,OAAM;GAGrC,MAAM,eAAe,UAAU,QAAQ,eACrC,YAAY,MAAK,QAAO;IACtB,MAAM,IAAI;AACV,WAAO,OAAO,EAAE,UAAU,YAAY,OAAO,SAAS,EAAE,MAAM;KAC9D,CACH;AAED,OAAI,aAAa,SAAS,EACxB,OAAM;;;CAMZ,MAAM,mBAAmB,UAAoC;AAC3D,MAAI,CAAC,QAAQ,MAAO;AAEpB,MAAI,MAEF,EADe,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM,EAC9C,SAAQ,cAAa;AAC1B,OAAI,UAAU,eAAe,OAE3B,WAAU,aADW,UAAU;IAGjC;MAEF,SAAQ,MAAM,mBAAmB;;CAIrC,MAAM,mBAAmB,OACvB,UACA,YACqB;AACrB,MAAI;GACF,MAAM,SAAS,QAAQ,MAAM,OAAO,SAAS,CAAC,KAAI,WAAU,OAAO,KAAK;AACxE,OAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAM,cAAc,OAAO;AAC3B,UAAO;WACA,OAAO;AACd,WAAQ,KAAK,YAAY,QAAQ,QAAQ,MAAM;AAC/C,UAAO;;;CAIX,MAAM,eAAe,OAAO,cAAwC;EAClE,MAAM,UAAU,OAAO,MAAM,OAAO,QAAQ,YAAY;AACxD,MAAI,CAAC,QAAS,QAAO;AAErB,SAAO,kBACL,WAAU,OAAO,QAAQ,SAAS,SAClC,MAAM,UAAU,GACjB;;CAGH,MAAM,cAAc,OAAO,WAAqC;AAC9D,SAAO,kBACL,WAAU,OAAO,QAAQ,QAAQ,QACjC,OAAO,OAAO,GACf;;CAGH,MAAM,wBAAwB,YAA8B;AAC1D,SAAO,kBACL,WAAU,QAAQ,OAAO,QAAQ,QAAQ,EACzC,QACD;;CAGH,MAAM,sBAAsB,OAAO,aAAuC;AACxE,SAAO,kBACL,WAAU,OAAO,QAAQ,UAAU,UACnC,SAAS,SAAS,GACnB;;CAIH,MAAM,kBAA6B,EAAE,GAAG,WAAW;CAEnD,MAAM,aAAa,WAA4B;AAC7C,SAAO,OAAO,WAAW,OAAO;;CAGlC,MAAM,oBAA0B;AAC9B,MAAI;AACF,oBAAiB;AAEjB,WAAQ,MAAM,SAAQ,SAAQ;IAC5B,MAAM,eACJ,KAAK,UAAU,SACX,KAAK,QACL,gBAAgB,KAAK,KAAsB;AAEjD,cAAU,KAAK,QAAQ;KACvB;WACK,OAAO;AACd,WAAQ,MAAM,oBAAoB,MAAM;;;CAI5C,MAAM,gBAAgB,OACpB,OACA,OACA,iBAA0B,UACR;AAClB,YAAU,SAAS;AACnB,MAAI,eACF,OAAM,cAAc,MAAM;;CAI9B,MAAM,iBAAiB,UAA2B,UAAU;CAE5D,MAAM,iBAAiB,OACrB,QACA,iBAA0B,UACR;AAClB,SAAO,OAAO,WAAW,OAAO;AAChC,MAAI,eACF,OAAM,UAAU;;CAKpB,MAAM,eAAe,YAA2B;AAC9C,MAAI;AACF,SAAM,UAAU;AAChB,QAAK,UAAU;IAAE,OAAO,UAAU;IAAE,MAAM,QAAQ;IAAQ,CAAC;WACpD,OAAO;AACd,WAAQ,KAAK,oBAAoB,MAAM;;;AAK3C,iBAAgB;AACd,cAAY;AAEZ,cACQ,QAAQ,aACR,YAAY,EAClB,EAAE,MAAM,MAAM,CACf;AAED,cACQ,YACN,QAAO,KAAK,qBAAqB,EAAE,GAAG,KAAK,CAAC,EAC5C,EAAE,MAAM,MAAM,CACf;GACD;AAEF,QAAO;EACL;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA,aAAa;EACd;;;;;;;;;AC9SH,SAAS,eAAe,GAA+C;AACrE,QAAO;EAEL,QAAQ,UAAU,EAAE,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;EAC3C,WAAW,UAAU,EAAE,EAAE,QAAQ;GAAE,GAAG;GAAO,MAAM;GAAY,CAAC;EAChE,cAAc,UAAU,EAAE,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;EACvD,SAAS,UAAU,EAAE,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;EAC7C,SAAS,UAAU,EAAE,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;EAC7C,OAAO,UAAU,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;EACzC,aAAa,UAAU,EAAE,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;EACrD,YAAY,UAAU,EAAE,EAAE,aAAa;GAAE,GAAG;GAAO,MAAM;GAAa,CAAC;EACvE,aAAa,UAAU,EAAE,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;EACrD,WAAW,UAAU,EAAE,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;EACjD,cAAc,UAAU,EAAE,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;EAGvD,SAAS,WAAW,SAClB,EAAE,EAAE,SAAS;GACX,GAAG;GACH,SACE,KAAK,UAAU,KAAK,WAAuB;IACzC,OAAO,MAAM;IACb,OAAO,MAAM;IACb,UAAU,MAAM;IACjB,EAAE,IAAI,EAAE;GACZ,CAAC;EAEJ,WAAW,WAAW,SACpB,EACE,EAAE,gBACF,EAAE,GAAG,WAAW,EAChB,EACE,eACE,EACE,EAAE,QACF,EAAE,EACF,EACE,eACE,KAAK,UAAU,KAAK,UAClB,EAAE,EAAE,WAAW;GACb,OAAO,MAAM;GACb,OAAO,MAAM;GACb,UAAU,MAAM;GAChB,KAAK,OAAO,MAAM,MAAM;GACzB,CAAC,CACH,IAAI,EAAE,EACV,CACF,EACJ,CACF;EAEH,QAAQ,WAAW,SACjB,EACE,EAAE,aACF,EAAE,GAAG,WAAW,EAChB,EACE,eACE,EACE,EAAE,QACF,EAAE,EACF,EACE,eACE,KAAK,UAAU,KAAK,UAClB,EACE,EAAE,QACF;GACE,OAAO,MAAM;GACb,UAAU,MAAM;GAChB,KAAK,OAAO,MAAM,MAAM;GACzB,EACD,EAAE,eAAe,MAAM,OAAO,CAC/B,CACF,IAAI,EAAE,EACV,CACF,EACJ,CACF;EAEH,SAAS,WAAW,MAAM,SAAS,QACjC,EACE,EAAE,SACF;GACE,UAAU,UAAU,SAAS,EAAE;GAC/B,sBAAsB,aAAwB;AAC5C,cAAU,oBAAoB,SAAS;;GAEzC,GAAG,KAAK;GACT,EACD;GACE,eACE,KAAK,QAAQ,kBAAkB,IAC/B,EAAE,EAAE,SAAS,EAAE,MAAM,WAAW,EAAE,EAAE,eAAe,QAAQ,CAAC;GAC9D,WAAW,KAAK,QAAQ,gBAAgB,IAAI;GAC7C,CACF;EAEH,SAAS,WAAW,MAAM,WACxB,EAAE,EAAE,UAAU;GACZ,UAAU,UAAU,KAAK;GACzB,YAAY,UAAU,SAAS;GAC/B,aAAa,KAAK;GAClB,UAAU,OAAO;GACjB,UAAU,OAAO;GACjB,wBAAwB,UAAkB;AACxC,cAAU,oBAAoB,MAAM;;GAEtC,GAAG,KAAK;GACT,CAAC;EACL;;;AAIH,MAAM,kBAAgD,EAAE;;;;;;AAOxD,SAAgB,iBAAiB,MAAc,UAAwB;AACrE,iBAAgB,QAAQ;;;;;;AAS1B,SAAgB,gBACd,WACA,gBACA,QACA,mBACA,cACA,eACA;CAEA,MAAM,YAAY;EAAE,GAAG,eAAe,aAAa;EAAE,GAAG;EAAiB;;;;CAKzE,MAAM,gBAAgB,SAA8C;EAClE,MAAM,YAAqC;GACzC,OAAO,UAAU,KAAK;GACtB,mBAAmB,UAAmB;AACpC,cAAU,KAAK,QAAQ;AACvB,sBAAkB,KAAK,KAAK;;GAE/B;AAED,MAAI,KAAK,SAAS,WAChB,WAAU,OAAO;AAGnB,MAAI,KAAK,YACP,WAAU,cAAc,KAAK;AAG/B,SAAO;;;;;CAMT,MAAM,kBAAkB,SAAmC;AACzD,MAAI;GACF,MAAM,WAAW,UAAU,KAAK;AAChC,OAAI,CAAC,UAAU;AACb,YAAQ,KAAK,sBAAsB,KAAK,OAAO;AAC/C,WAAO;;AAIT,UAAO,SAAS;IAAE,GADA,aAAa,KAAK;IACJ,GAAG,KAAK;IAAO,EAAE,MAAM,OAAO,OAAO;IACnE,OAAO;IACP,YAAY;IACb,CAAC;WACK,OAAO;AACd,WAAQ,MAAM,qBAAqB,OAAO,KAAK;AAC/C,UAAO;;;AAwBX,QAAO;EAAE;EAAgB,WAjBP,eAChB,eAAe,MAAM,KAAK,SACxB,EACE,aAAa,WACb;GACE,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,KAAK,KAAK;GACV,UAAU,CAAC,CAAC,KAAK,OAAO;GACzB,EACD,EACE,eAAe,eAAe,KAAK,EACpC,CACF,CACF,CACF;EAEmC;EAAc;;;;;;;;;;;;;;;uBErOlD,mBAIM,OAJN,cAIM,mBAHJ,mBAEW,UAAA,MAAA,WAFcA,KAAAA,YAAR,SAAI;wBACnB,YAAwB,wBAAR,KAAI,EAAA,OADoB,KAAK;;;;;;;;;;;;;;;;;;;;EGkBnD,MAAM,QAAQ;;;;EAUd,MAAM,YAAY,eAAuB,IAAI;;;;EAK7C,MAAM,MAAM,eAAuB;AACjC,UAAO,MAAM,cAAc,QAAQ,OAAO;IAC1C;;;;EAKF,MAAM,SAAS,eAAuB,GAAG;;;;EAKzC,MAAM,QAAQ,eAA2C;GACvD,MAAM,aAAa,MAAM,cAAc,QAAQ;AAC/C,UAAO,eAAe,WAAW,eAAe,QAAQ,aAAa;IACrE;;;;EAKF,MAAM,aAAa,eAAuB;AACxC,WAAQ,MAAM,OAAd;IACE,KAAK,QACH,QAAO;IACT,KAAK,MACH,QAAO;IACT,QACE,QAAO;;IAEX;;;;EAKF,MAAM,iBAAiB,eAAuB;AAC5C,WAAQ,MAAM,OAAd;IACE,KAAK,QACH,QAAO;IACT,KAAK,MACH,QAAO;IACT,KAAK,SACH,QAAO;IACT,QACE,QAAO;;IAEX;;;;EAKF,MAAM,iBAAiB,eAA8B;AACnD,UAAO;IACL,SAAS;IACT,UAAU;IACV,YAAY,WAAW;IACvB,gBAAgB,eAAe;IAC/B,KAAK,GAAG,OAAO,MAAM,KAAK,IAAI,MAAM;IACpC,OAAO;IACR;IACD;;;;EAOF,MAAM,cAAc,MAAa,UAA0B;AACzD,OAAI,KAAK,OAAO,KACd,QAAO,OAAO,KAAK,IAAI;GAGzB,MAAM,YAAY,KAAK;AACvB,OAAI,WAAW,KACb,QAAO,UAAU;AAGnB,UAAO,aAAa;;;;;EAMtB,MAAM,gBAAgB,UAAiC;GAErD,MAAM,gBADiC,MAAM,UAAU,SACI;GAG3D,MAAM,YAA2B;IAC/B,OAAO,GAAG,UAAU,MAAM;IAC1B,YAAY;IACZ,SAAS;IACT,eAAe;IAChB;AAGD,OAAI,cAAc,UAAU,OAC1B,WAAU,QACR,OAAO,aAAa,UAAU,WAC1B,GAAG,aAAa,MAAM,MACtB,aAAa;AAIrB,OAAI,cAAc,MAChB,QAAO,OAAO,WAAW,aAAa,MAAM;AAG9C,UAAO;;;uBA7IP,mBASM,OAAA;IATD,OAAM;IAAiB,OAAK,eAAE,eAAA,MAAc;yBAC/C,mBAOM,UAAA,MAAA,WANoBC,KAAAA,YAAhB,MAAM,UAAK;wBADrB,mBAOM,OAAA;KALH,KAAK,WAAW,MAAM,MAAK;KAC5B,OAAM;KACL,OAAK,eAAE,aAAa,MAAK,CAAA;sBAE1B,YAAwB,wBAAR,KAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;EG+F1B,MAAM,QAAQ;EAOd,MAAM,QAAQ,IAAI,OAAO,OAAO,SAAS,eAAe,CAAC,CAAC,OAAO,KAAK,KAAK,IAAI;EAC/E,MAAM,iBAAiB,SAAS;GAC9B,MAAM;GACN,QAAQ;GACR,YAAY;GACb,CAAC;EAIF,MAAM,aAAa,eAAe,MAAM,cAAc,QAAQ,EAAE,CAAC;EACjE,MAAM,kBAAkB,eAAe,WAAW,MAAM,gBAAgB;EACxE,MAAM,YAAY,eAAe,WAAW,MAAM,UAAU;EAG5D,MAAM,kBAAkB,gBAAgB;GACtC,MAAM,gBAAgB,QAClB,eAAe,OACd,WAAW,MAAM,QAAQ;GAC9B,QAAQ,gBAAgB,QACpB,eAAe,SACd,WAAW,MAAM,UAAU;GAChC,YAAY,gBAAgB,QACxB,eAAe,aACd,WAAW,MAAM,cAAc;GACrC,EAAE;EAGH,MAAM,YAAY,gBAAgB;GAChC,MAAM,gBAAgB,MAAM;GAC5B,MAAM,gBAAgB,MAAM;GAC5B,MAAM,gBAAgB,MAAM;GAC5B,YAAY,gBAAgB,MAAM,aAAa,WAAW;GAC3D,EAAE;EAGH,MAAM,iBAAiB,eAAe;GACpC;IACE,KAAK;IACL,OAAO;IACP,WAAW;IACX,OAAO,SAAS;KACd,WAAW,eAAe;KAC1B,MAAM,QAAiB,eAAe,OAAO;KAC9C,CAAC;IACF,OAAO;KAAE,KAAK;KAAG,KAAK;KAAI,MAAM;KAAS;IAC1C;GACD;IACE,KAAK;IACL,OAAO;IACP,WAAW;IACX,OAAO,SAAS;KACd,WAAW,eAAe;KAC1B,MAAM,QAAiB,eAAe,SAAS;KAChD,CAAC;IACF,OAAO;KAAE,KAAK;KAAG,KAAK;KAAI,MAAM;KAAS;IAC1C;GACD;IACE,KAAK;IACL,OAAO;IACP,WAAW;IACX,OAAO,SAAS;KACd,WAAW,eAAe;KAC1B,MAAM,QAAkB,eAAe,aAAa;KACrD,CAAC;IACF,OAAO,EAAE,MAAM,SAAS;IACzB;GACF,CAAC;EAGF,MAAM,YAAY,eAEd,OAAO,gBAAgB,MAAM,KAAK,SAAS,gBAAgB,MAAM,OAAO,WAAW,MAAM,UAAU,OAAO,GAC7G;;;;EAOD,MAAM,cAAc,MAAa,UAC/B,OAAO,KAAK,IAAI,IAAK,KAAK,OAAe,QAAQ,aAAa;;;;EAKhE,MAAM,kBAAkB,OAAe,QAAsC;GAC3E,MAAM,SAAS,MAAM,UAAU,QAAQ;AACvC,UAAO,SAAS,QAAQ,QAAQ,OAAO;;;;;EAMzC,MAAM,uBAA+B;GACnC,MAAM,EAAE,SAAS,gBAAgB;AACjC,UAAO,QAAQ,KAAK,OAAO,QAAQ,KAAK,KAAK;;;;;EAM/C,MAAM,gBAAgB,UAAkB;GACtC,MAAM,OAAO,eAAe,OAAO,OAAO;GAC1C,MAAM,SAAS,eAAe,OAAO,SAAS;GAC9C,MAAM,SAAS,eAAe,OAAO,SAAS;AAE9C,UAAO;IACL,MACE,OAAO,SAAS,YAAY,OAAO,KAAK,QAAQ,gBAAgB,MAAM,OAClE,OACA,gBAAgB;IACtB,QACE,OAAO,WAAW,YAClB,UAAU,KACV,SAAS,gBAAgB,MAAM,OAC3B,SACA;IACN,QAAQ,QAAQ,OAAO;IACxB;;;;;EAMH,MAAM,yBAAyB;AAC7B,OAAI,MAAM,MACR,SAAQ,IAAI,wBAAwB,MAAM,eAAe,CAAC;;AAO9D,QACE,aACC,WAAW;AACV,OAAI,OAAO,SAAS,OAAW,gBAAe,OAAO,OAAO;AAC5D,OAAI,OAAO,WAAW,OAAW,gBAAe,SAAS,OAAO;AAChE,OAAI,OAAO,eAAe,OACxB,gBAAe,aAAa,OAAO;KAEvC,EAAE,WAAW,MAAM,CACpB;AAGD,MAAI,MAAM,MACR,mBAAkB;GAChB,MAAM,eAAe,MAAM,QAAQ;GACnC,MAAM,aAAa,MAAM,UAAU;AAEnC,OAAI,eAAe,KAAK,iBAAiB,WACvC,SAAQ,KACN,qBAAqB,aAAa,UAAU,WAAW,MACxD;IAEH;AAKJ,WAAa;GACX,mBAAmB,WACjB,OAAO,OAAO,gBAAgB,OAAO;GACvC,yBAAyB,EAAE,GAAG,gBAAgB,OAAO;GACrD,oBAAoB;IAClB,GAAG,gBAAgB;IACnB,WAAW,MAAM,UAAU;IAC5B;GACF,CAAC;;;;;;uBArRA,mBA4DM,OA5DN,cA4DM;IA3DJ,mBAAA,SAAa;IAEL,gBAAA,sBADR,YA4BQ,kBAAA;;KA1BN,MAAK;KACJ,UAAU;KACX,OAAM;;KAEK,QAAM,cAIT,CAHN,mBAGM,OAHN,cAGM,CAFJ,YAA6C,gBAAA;MAApC,MAAM;MAAkB,MAAM;mCACvC,mBAAiB,QAAA,MAAX,QAAI,GAAA;4BAkBR,CAdN,mBAcM,OAdN,cAcM,mBAbJ,mBAYM,UAAA,MAAA,WAXc,eAAA,QAAX,YAAO;0BADhB,mBAYM,OAAA;OAVH,KAAK,QAAQ;OACd,OAAM;UAEN,mBAAsD,QAAtD,cAAsD,gBAAxB,QAAQ,MAAK,GAAG,KAAC,EAAA,gBAC/C,YAKE,wBAJK,QAAQ,UAAS,EADxB,WAKE;OAHQ,OAAO,QAAQ;qCAAR,QAAQ,QAAK;4BACpB,QAAQ,OAAK,EACpB,kBAAc,kBAAgB,CAAA,EAAA,MAAA,IAAA,CAAA,SAAA,iBAAA,CAAA;;;;IAMvC,mBAAA,SAAa;IACb,YAWQ,kBAXR,WAAe,UAWP,OAXgB,EAAE,OAAM,kBAAgB,CAAA,EAAA;4BAEV,mBADpC,mBASY,UAAA,MAAA,WARcC,KAAAA,YAAhB,MAAM,UAAK;0BADrB,YASY,sBATZ,WASY,EAPT,KAAK,WAAW,MAAM,MAAK,uBACpB,aAAa,MAAK,EAAA,EAC1B,OAAM,aAAW,CAAA,EAAA;8BAIX,CAFN,mBAEM,OAFN,cAEM,eADJ,YAAwB,wBAAR,KAAI,CAAA;;;;;;IAK1B,mBAAA,SAAa;IAEL,UAAA,SAAa,MAAA,sBADrB,YAYS,mBAAA;;KAVP,MAAK;KACJ,aAAW;KACZ,MAAK;KACL,OAAM;;KAEK,QAAM,cACwC,CAAvD,YAAuD,gBAAA;MAA9C,MAAM;MAA4B,MAAM;mDAAM,UAEzD,GAAA;4BACA,iBADW,MACX,gBAAG,UAAA,MAAS,EAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EGqMlB,MAAM,QAAQ;EAOd,MAAM,UAAU,IAAI,GAAG;EACvB,MAAM,YAAY,IAAI,KAAK;EAC3B,MAAM,cAAc,IAAI,KAAK;EAC7B,MAAM,eAAe,IAAI,KAAK;EAC9B,MAAM,mBAAmB,IAA+B,WAAW;EACnE,MAAM,kBAAkB,IAA6B,EAAE,CAAC;EAGxD,MAAM,SAAS,eAA8B;AAC3C,UAAO,MAAM,cAAc,MAAM,UAAU,EAAE;IAC7C;EAEF,MAAM,YAAY,eAAwB;AACxC,UAAO,OAAO,MAAM,SAAS;IAC7B;EAEF,MAAM,mBAAmB,eAAwB;AAC/C,UAAO,MAAM,cAAc,MAAM,oBAAoB;IACrD;EAEF,MAAM,kBAAkB,eAAwB;AAC9C,UAAO,MAAM,cAAc,MAAM,mBAAmB;IACpD;EAEF,MAAM,cAAc,eAAuB;AACzC,OAAI,CAAC,UAAU,MAAO,QAAO;AAC7B,UAAO,UAAU,iBAAiB;IAClC;EAEF,MAAM,kBAAkB,eAAiC;AACvD,OAAI,CAAC,UAAU,MAAO,QAAO,EAAE;GAE/B,MAAM,2BAAW,IAAI,KAAsB;AAG3C,UAAO,MAAM,SAAS,UAAU;AAC9B,aAAS,IAAI,MAAM,KAAK,EAAE,CAAC;KAC3B;AAGF,SAAM,UAAU,SAAS,MAAM,UAAU;IAEvC,MAAM,YADS,MAAM,UAAU,SACN,QAAQ,SAAS,OAAO,MAAM,IAAI,OAAO;AAElE,QAAI,CAAC,SAAS,IAAI,SAAS,CACzB,UAAS,IAAI,UAAU,EAAE,CAAC;AAE5B,aAAS,IAAI,SAAS,CAAE,KAAK,KAAK;KAClC;AAGF,UAAO,OAAO,MACX,KAAK,iBAAiB;IACrB,QAAQ;IACR,OAAO,SAAS,IAAI,YAAY,IAAI,IAAI,EAAE;IAC3C,EAAE,CACF,QAAQ,UAAU,MAAM,MAAM,SAAS,EAAE;IAC5C;EAEF,MAAM,eAAe,eAAe;GAClC,MAAM,YAAY,OAAO,KAAK,gBAAgB,MAAM;AACpD,UACE,UAAU,SAAS,KAAK,UAAU,OAAO,QAAQ,gBAAgB,MAAM,KAAK;IAE9E;EAEF,MAAM,gBAAgB,eAAe;AACnC,OAAI,CAAC,MAAM,WAAW,MAAM,QAAQ,WAAW,EAAG,QAAO;GAEzD,MAAM,cAAc,MAAM,QAAQ,QAAQ,WAAW;IACnD,MAAM,QAAQ,MAAM,WAAW,OAAO,QAAQ;AAC9C,WAAO,cAAc,MAAM;KAC3B,CAAC;AAEH,UAAO,KAAK,MAAO,cAAc,MAAM,QAAQ,SAAU,IAAI;IAC7D;EAGF,MAAM,iBAAiB,UAAwB;AAC7C,OAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,OAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,MAAM,SAAS;AAChD,OAAI,OAAO,UAAU,SAAU,QAAO,MAAM,MAAM,KAAK;AACvD,UAAO;;EAGT,MAAM,kBAAkB,UAAkC;AACxD,OAAI,CAAC,MAAM,QAAS,QAAO;AAM3B,UAJoB,MAAM,QAAQ,QAC/B,WAAW,OAAO,QAAQ,UAAU,MAAM,OAAO,IACnD,CAEkB,QAAQ,WAAW;IACpC,MAAM,QAAQ,MAAM,WAAW,OAAO,QAAQ;AAC9C,WAAO,cAAc,MAAM;KAC3B,CAAC;;EAGL,MAAM,oBAAoB,UAAkC;AAC1D,OAAI,MAAM,MAAM,WAAW,EAAG,QAAO;GACrC,MAAM,cAAc,eAAe,MAAM;AACzC,UAAO,KAAK,MAAO,cAAc,MAAM,MAAM,SAAU,IAAI;;EAG7D,MAAM,kBAAkB,aAA6B;GACnD,MAAM,UAAkC;IACtC,OAAO;IACP,SAAS;IACT,aAAa;IACb,UAAU;IACV,MAAM;IACN,SAAS;IACV;AACD,UAAO,QAAQ,aAAa,QAAQ;;EAItC,MAAM,eAAe,aAA2B;AAC9C,mBAAgB,MAAM,YAAY,CAAC,gBAAgB,MAAM;;EAG3D,MAAM,wBAA8B;GAClC,MAAM,iBAAiB,CAAC,aAAa;AACrC,UAAO,MAAM,SAAS,UAAU;AAC9B,oBAAgB,MAAM,MAAM,OAAO;KACnC;;AAIJ,kBAAgB;GAEd,MAAM,SAAS,MAAM,cAAc;AACnC,OAAI,QAAQ,UACV,kBAAiB,QAAQ,OAAO;AAGlC,OAAI,QAAQ,iBAAiB,OAC3B,cAAa,QAAQ,OAAO;AAI9B,UAAO,MAAM,SAAS,UAAU;AAC9B,oBAAgB,MAAM,MAAM,OAAO;KACnC;IACF;;;;;;;;;;uBAtZA,mBA8MM,OA9MN,cA8MM;IA7MJ,mBAAA,WAAe;IAEP,UAAA,SAAa,iBAAA,sBADrB,YA6CQ,kBAAA;;KA3CN,OAAM;KACL,UAAU;;KAEA,QAAM,cAIT,CAHN,mBAGM,OAHN,cAGM,CAFJ,YAAuC,gBAAA;MAA9B,MAAM;MAAY,MAAM;mCACjC,mBAAiB,QAAA,MAAX,QAAI,GAAA;4BAoCR,CAhCN,mBAgCM,OAhCN,cAgCM;MA/BJ,mBAYM,OAZN,cAYM,2BAXJ,mBAAiB,QAAA,MAAX,QAAI,GAAA,GACV,mBASM,OATN,cASM,CARJ,YAME,oBAAA;OALQ,OAAO,QAAA;+DAAA,QAAO,QAAA;OACrB,KAAK;OACL,KAAK;OACL,MAAM;OACP,OAAM;8BAER,mBAAqD,QAArD,cAAqD,gBAAnB,QAAA,MAAO,GAAG,MAAE,EAAA;MAIlD,mBAGM,OAHN,cAGM,2BAFJ,mBAAiB,QAAA,MAAX,QAAI,GAAA,GACV,YAAkD,oBAAA;OAAjC,OAAO,UAAA;+DAAA,UAAS,QAAA;OAAE,MAAK;;MAG1C,mBAGM,OAHN,cAGM,2BAFJ,mBAAgB,QAAA,MAAV,OAAG,GAAA,GACT,YAAoD,oBAAA;OAAnC,OAAO,YAAA;+DAAA,YAAW,QAAA;OAAE,MAAK;;MAG5C,mBAMM,OANN,cAMM,6BALJ,mBAAiB,QAAA,MAAX,QAAI,GAAA,GACV,YAGc,wBAAA;OAHO,OAAO,iBAAA;+DAAA,iBAAgB,QAAA;OAAE,MAAK;;8BACb,CAApC,YAAoC,mBAAA,EAA5B,OAAM,YAAU,EAAA;+BAAG,OAAA,OAAA,OAAA,KAAA,iBAAF,MAAE,GAAA;;;WAC3B,YAAsC,mBAAA,EAA9B,OAAM,cAAY,EAAA;+BAAG,OAAA,OAAA,OAAA,KAAA,iBAAF,MAAE,GAAA;;;;;;;;;IAMrC,mBAAA,WAAe;IACf,mBAmHM,OAAA;KAlHJ,OAAK,eAAA,CAAC,gBACE,YAAA,MAAW,CAAA;KAClB,OAAK,eAAA,EAAA,KAAA,GAAY,QAAA,MAAO,KAAA,CAAA;QAEzB,mBAAA,eAAmB,GACL,UAAA,sBAAd,YAgBQ,kBAAA;;KAhBiB,OAAM;KAAc,WAAA;;KAChC,QAAM,cAST,CARN,mBAQM,OARN,eAQM,CANI,UAAA,sBADR,YAKE,gBAAA;;MAHC,MAAM;MACN,MAAM;MACP,OAAA,EAAA,SAAA,WAAsB;yEAExB,mBAAiB,QAAA,MAAX,QAAI,GAAA;4BAIqB,mBAAnC,mBAEW,UAAA,MAAA,WAFcC,KAAAA,YAAR,SAAI;0BACnB,YAAwB,wBAAR,KAAI,EAAA,OADoB,KAAK;;;wBAMjD,mBAyFW,UAAA,EAAA,KAAA,GAAA,EAAA,CA1FX,mBAAA,eAAmB,oBAEjB,mBAuFQ,UAAA,MAAA,WAtFU,gBAAA,QAAT,UAAK;yBADd,YAuFQ,kBAAA;MArFL,KAAK,MAAM,OAAO;MACnB,OAAK,eAAA,CAAC,cAAY,IACQ,MAAM,OAAO,IAAG,qBAAmC,YAAA,SAAe,gBAAA,MAAgB,MAAM,OAAO,MAAG;MAI5H,WAAA;;MAEW,QAAM,cA0DT,CAzDN,mBAyDM,OAzDN,eAyDM,CAxDJ,mBAoBM,OApBN,eAoBM,CAlBI,UAAA,SAAa,MAAM,OAAO,qBADlC,YAKE,gBAAA;;OAHC,MAAM,MAAM,OAAO;OACnB,MAAM;OACP,OAAM;+BAGK,UAAA,sBADb,YAKE,gBAAA;;OAHC,MAAM,eAAe,MAAM,OAAO,IAAG;OACrC,MAAM;OACP,OAAM;iEAGR,mBAKM,OALN,eAKM,CAJJ,mBAAiC,MAAA,MAAA,gBAA1B,MAAM,OAAO,MAAK,EAAA,EAAA,EAChB,MAAM,OAAO,4BAAtB,mBAEI,KAAA,eAAA,gBADC,MAAM,OAAO,YAAW,EAAA,EAAA,0CAKjC,mBAiCM,OAjCN,eAiCM;OAhCJ,mBAAA,SAAa;OACb,mBAUM,OAVN,eAUM,CATJ,YAA4D,mBAAA;QAAnD,OAAO,MAAM,MAAM;QAAQ,MAAK;QAAO,aAAA;+BAChD,YAOE,mBAAA;QANC,OAAK,GAAK,eAAe,MAAK,CAAA,GAAK,MAAM,MAAM;QAC/C,MAA8B,eAAe,MAAK,KAAM,MAAM,MAAM;;OAQzE,mBAAA,SAAa;OAEL,YAAA,sBADR,YAiBU,oBAAA;;QAfR,YAAA;QACA,QAAA;QACA,MAAK;QACJ,UAAK,WAAE,YAAY,MAAM,OAAO,IAAG;;QAEzB,MAAI,cAQX,CAPF,YAOE,gBAAA;SANC,MAAgC,gBAAA,MAAgB,MAAM,OAAO;SAK7D,MAAM;;;;;6BAuBb,gBAfN,mBAeM,OAfN,eAeM;OAdJ,mBAAA,UAAc;OACH,aAAA,sBAAX,mBAOM,OAPN,eAOM,CANJ,YAKE,sBAAA;QAJC,YAAY,iBAAiB,MAAK;QAClC,OAAO,iBAAiB,MAAK,KAAA,MAAA,YAAA;QAC7B,kBAAgB;QACjB,OAAM;;OAIV,mBAAA,QAAY;yBACZ,mBAEW,UAAA,MAAA,WAFc,MAAM,QAAd,SAAI;4BACnB,YAAwB,wBAAR,KAAI,EAAA,OADsB,KAAK;;yBAZrC,gBAAA,MAAgB,MAAM,OAAO,KAAG;;;;IAoBpD,mBAAA,WAAe;IAEP,UAAA,SAAa,gBAAA,sBADrB,YAqCQ,kBAAA;;KAnCN,OAAM;KACL,UAAU;;4BAiCL,CA/BN,mBA+BM,OA/BN,eA+BM,CA9BJ,mBAaM,OAbN,eAaM,CAZJ,mBAWM,OAXN,eAWM,6BAVJ,mBAAgC,QAAA,EAA1B,OAAM,SAAO,EAAC,SAAK,GAAA,GACzB,mBAQM,OARN,eAQM,CAPJ,YAKE,sBAAA;MAJC,YAAY,cAAA;MACZ,kBAAgB;MAChB,OAAO,cAAA,UAAa,MAAA,YAAA;MACrB,OAAM;2CAER,mBAA0D,QAA1D,eAA0D,gBAAxB,cAAA,MAAa,GAAG,KAAC,EAAA,QAKzD,mBAcM,OAdN,eAcM,CAbW,YAAA,sBAAf,YAYU,oBAAA;;MAZmB,SAAO;;MACvB,MAAI,cAQX,CAPF,YAOE,gBAAA;OANC,MAA0B,aAAA;OAK1B,MAAM;;6BAGX,iBADW,MACX,gBAAG,aAAA,QAAY,SAAA,OAAA,EAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EGjC3B,MAAM,QAAQ;EAKd,MAAM,OAAO;EASb,MAAM,aAAa,IAAY,GAAG;EAClC,MAAM,sBAAsB,SAAkC,EAAE,CAAC;EAGjE,MAAM,8BAA0D;GAC9D,MAAM,EAAE;GACR,MAAM;GACN,MAAM;GACN,WAAW;GACX,UAAU;GACV,UAAU;GACV,SAAS;GACT,eAAe;GACf,aAAa;GACb,WAAW;GACX,sBAAsB;GACtB,YAAY;GACb;EAGD,MAAM,aAAa,eAAe;GAChC,MAAM,gBAAgB,sBAAsB;GAC5C,MAAM,aAAa,MAAM,cAAc,QAAQ,EAAE;AAEjD,UAAO;IACL,GAAG;IACH,GAAG;IACJ;IACD;EAEF,MAAM,UAAU,eAAwB;AACtC,UAAO,WAAW,MAAM,KAAK,SAAS;IACtC;EAEF,MAAM,gBAAgB,eAA+B;AACnD,OAAI,CAAC,QAAQ,MAAO,QAAO,EAAE;GAE7B,MAAM,yBAAS,IAAI,KAAsB;AAGzC,cAAW,MAAM,KAAK,SAAS,QAAQ;AACrC,WAAO,IAAI,IAAI,KAAK,EAAE,CAAC;KACvB;AAGF,SAAM,UAAU,SAAS,MAAM,UAAU;IAEvC,MAAM,UADS,MAAM,UAAU,SAErB,QAAQ,OAAO,WAAW,MAAM,KAAK,IAAI,OAAO;AAE1D,QAAI,CAAC,OAAO,IAAI,OAAO,CACrB,QAAO,IAAI,QAAQ,EAAE,CAAC;AAExB,WAAO,IAAI,OAAO,CAAE,KAAK,KAAK;KAC9B;AAGF,UAAO,WAAW,MAAM,KAAK,KAAK,eAAe;IAC/C,QAAQ;IACR,OAAO,OAAO,IAAI,UAAU,IAAI,IAAI,EAAE;IACvC,EAAE;IACH;EAGF,MAAM,cAAc,MAAa,UAA0B;AACzD,OAAI,KAAK,OAAO,KACd,QAAO,OAAO,KAAK,IAAI;GAGzB,MAAM,YAAY,KAAK;AACvB,OAAI,WAAW,KACb,QAAO,UAAU;AAGnB,UAAO,YAAY;;EAGrB,MAAM,qBAAqB,YAA8B;AACvD,OAAI,CAAC,WAAW,MAAO,QAAO;AAE9B,OAAI;AACF,SAAK,gBAAgB,WAAW,MAAM;IACtC,MAAM,QAAQ;AACd,wBAAoB,WAAW,SAAS;AACxC,WAAO;YACA,OAAO;AACd,YAAQ,MAAM,yBAAyB,MAAM;AAC7C,wBAAoB,WAAW,SAAS;AACxC,WAAO;;;EAIX,MAAM,cAAc,OAAO,cAAwC;AACjE,OAAI,CAAC,aAAa,cAAc,WAAW,MACzC,QAAO;AAMT,OAAI,CAHoB,cAAc,MAAM,MACzC,QAAQ,IAAI,OAAO,QAAQ,UAC7B,CAEC,QAAO;AAGT,OAAI;AAEF,QAAI,WAAW,MAAM,wBAAwB,WAAW,OAEtD;SAAI,CADY,MAAM,oBAAoB,CAExC,QAAO;;AAKX,QAAI,WAAW,MACb,MAAK,qBAAqB,WAAW,OAAO,UAAU;AAGxD,eAAW,QAAQ;AAInB,SAAK,cAAc,WAHF,cAAc,MAAM,WAClC,QAAQ,IAAI,OAAO,QAAQ,UAC7B,CACsC;AACvC,WAAO;YACA,OAAO;AACd,YAAQ,MAAM,yBAAyB,MAAM;AAC7C,WAAO;;;EAKX,MAAM,mBAAmB,WAAyB;AAChD,eAAY,OAAO;;EAGrB,MAAM,kBAAkB,WAAyB;AAC/C,QAAK,aAAa,OAAO;;EAG3B,MAAM,qBAA2B;AAC/B,QAAK,UAAU;;EAGjB,MAAM,6BAAmC;AACvC,OAAI,CAAC,QAAQ,SAAS,cAAc,MAAM,WAAW,EACnD;GAGF,MAAM,EAAE,eAAe,WAAW;GAClC,MAAM,YAAY,cAAc,cAAc,MAAM,IAAI,OAAO;AAE/D,OAAI,aAAa,cAAc,WAAW,OAAO;AAC/C,eAAW,QAAQ;AACnB,mBAAe;KACb,MAAM,WAAW,cAAc,MAAM,WAClC,QAAQ,IAAI,OAAO,QAAQ,UAC7B;AACD,SAAI,YAAY,EACd,MAAK,cAAc,WAAW,SAAS;MAEzC;;;AAKN,kBAAgB;AACd,yBAAsB;IACtB;AAOF,QAJwB,eACtB,WAAW,MAAM,KAAK,KAAK,MAAM,EAAE,IAAI,CAAC,KAAK,IAAI,CAClD,QAE4B;AAC3B,OACE,WAAW,SACX,CAAC,WAAW,MAAM,KAAK,MAAM,QAAQ,IAAI,QAAQ,WAAW,MAAM,CAElE,uBAAsB;IAExB;AAGF,cACQ,WAAW,MAAM,aACtB,kBAAkB;AACjB,OAAI,iBAAiB,kBAAkB,WAAW,MAChD,aAAY,cAAc;IAG/B;AAGD,WAAa;GACX;GACA;GACA,YAAY,SAAS,WAAW;GAChC,WAAW,eAAe,cAAc,MAAM,OAAO;GACrD,eAAe,SAAS,cAAc;GACvC,CAAC;;;;;;;;uBA9XA,mBA6GM,OA7GN,cA6GM,CA5GJ,mBAAA,kBAAsB,GACV,QAAA,sBAAZ,mBAMM,OANN,cAMM,mBALJ,mBAIE,UAAA,MAAA,WAHwBC,KAAAA,YAAhB,MAAM,UAAK;wBADrB,YAIE,wBADK,KAAI,EAAA,EADR,KAAK,WAAW,MAAM,MAAK;gCAMhC,mBAiGM,UAAA,EAAA,KAAA,GAAA,EAAA,CAlGN,mBAAA,iBAAqB,EACrB,mBAiGM,OAjGN,cAiGM;IAhGJ,YAgEQ,kBAAA;KA/DE,OAAO,WAAA;8DAAA,WAAU,QAAA,SAQV;KAPd,MAAM,WAAA,MAAW;KACjB,MAAM,WAAA,MAAW;KACjB,WAAW,WAAA,MAAW;KACtB,UAAU,WAAA,MAAW;KACrB,UAAU,WAAA,MAAW;KACrB,SAAS,WAAA,MAAW;KACrB,OAAM;KAEL,SAAO;KACP,OAAK;;4BAGwB,mBAD9B,mBAkDW,UAAA,MAAA,WAjDK,cAAA,QAAP,QAAG;0BADZ,YAkDW,qBAAA;OAhDR,KAAK,IAAI,OAAO;OAChB,MAAM,IAAI,OAAO;OACjB,KAAK,IAAI,OAAO;OAChB,UAAU,IAAI,OAAO;OACrB,UAAU,IAAI,OAAO;;OAEX,KAAG,cAgBH,CAfT,YAeS,mBAAA;QAfD,OAAM;QAAU,MAAM;;+BAM1B;SAJM,IAAI,OAAO,qBADnB,YAKE,gBAAA;;UAHC,MAAM,IAAI,OAAO;UACjB,MAAM;UACP,OAAM;;SAER,mBAAmC,QAAA,MAAA,gBAA1B,IAAI,OAAO,MAAK,EAAA,EAAA;SAEjB,WAAA,MAAW,0BADnB,YAME,mBAAA;;UAJC,OAAO,IAAI,MAAM;UACjB,KAAK;UACL,MAAM,IAAI,MAAM,SAAM;UACvB,MAAK;;;;;8BAWL;QAJE,WAAA,MAAW,iBAAiB,IAAI,OAAO,4BAD/C,mBAKM,OALN,cAKM,CADJ,mBAA2D,KAA3D,cAA2D,gBAA7B,IAAI,OAAO,YAAW,EAAA,EAAA;QAItD,mBAMM,OANN,cAMM,mBALJ,mBAIE,UAAA,MAAA,WAH4B,IAAI,QAAxB,MAAM,cAAS;6BADzB,YAIE,wBADK,KAAI,EAAA,EADR,KAAK,WAAW,MAAM,UAAS;;QAO5B,IAAI,MAAM,WAAM,kBADxB,YAIE,mBAAA;;SAFA,aAAY;SACZ,OAAM;;;;;;;;;;;;;;;;;;;;;IAKZ,mBAAA,YAAgB;IACL,WAAA,MAAW,4BAAtB,mBA4BM,OA5BN,cA4BM,CA3BJ,YA0BS,mBAAA,EA1BD,SAAQ,iBAAe,EAAA;4BAepB,CAdT,YAcS,mBAAA,MAAA;6BADG,CAXF,WAAA,MAAW,qCADnB,YAYU,oBAAA;;OAVR,MAAK;OACL,MAAK;OACJ,SAAO;;8BAMN,CAJF,YAIE,gBAAA;QAHC,MAAM;QACN,MAAM;QACP,OAAA,EAAA,gBAAA,OAAyB;qDACzB,YAEJ,GAAA;;;;;SAGF,YAQS,mBAAA,MAAA;6BADL,CANF,WAME,KAAA,QAAA,eAAA;OAJC,YAAa,WAAA;OACb,WAAY,cAAA,MAAc;OAC1B,aAAc;OACC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EG4B9B,MAAM,QAAQ;EAKd,MAAM,OAAO;EAOb,MAAM,cAAc,IAAY,EAAE;EAClC,MAAM,UAAU,IAAa,MAAM;EACnC,MAAM,uBAAuB,SAAkC,EAAE,CAAC;EAGlE,MAAM,cAAc,eAAe;GACjC,MAAM,SAAS,MAAM,cAAc,SAAS,EAAE;AAC9C,UAAO;IACL,OAAO,OAAO,SAAS,EAAE;IACzB,UAAU,OAAO,YAAY;IAC7B,MAAM,OAAO,QAAQ;IACrB,gBAAgB,OAAO,mBAAmB;IAC1C,oBAAoB,OAAO,sBAAsB;IACjD,gBAAgB,OAAO,kBAAkB;IACzC,gBAAgB,OAAO,kBAAkB;IACzC,aAAa,OAAO,eAAe;IACpC;IACD;EAEF,MAAM,WAAW,eAAwB;AACvC,UAAO,YAAY,MAAM,MAAM,SAAS;IACxC;EAEF,MAAM,iBAAiB,eAAgC;AACrD,OAAI,CAAC,SAAS,MAAO,QAAO,EAAE;GAE9B,MAAM,0BAAU,IAAI,KAAsB;AAG1C,eAAY,MAAM,MAAM,SAAS,SAAS;AACxC,YAAQ,IAAI,KAAK,KAAK,EAAE,CAAC;KACzB;AAGF,SAAM,UAAU,SAAS,MAAM,UAAU;IAEvC,MAAM,WADS,MAAM,UAAU,SAErB,QAAQ,QAAQ,YAAY,MAAM,MAAM,IAAI,OAAO;AAE7D,QAAI,CAAC,QAAQ,IAAI,QAAQ,CACvB,SAAQ,IAAI,SAAS,EAAE,CAAC;AAE1B,YAAQ,IAAI,QAAQ,CAAE,KAAK,KAAK;KAChC;AAGF,UAAO,YAAY,MAAM,MACtB,KAAK,gBAAgB;IACpB,QAAQ;IACR,OAAO,QAAQ,IAAI,WAAW,IAAI,IAAI,EAAE;IACzC,EAAE,CACF,QAAQ,SAAS,KAAK,MAAM,SAAS,EAAE;IAC1C;EAEF,MAAM,aAAa,eAAe;AAChC,QAAK,IAAI,IAAI,GAAG,KAAK,YAAY,OAAO,IACtC,KAAI,qBAAqB,OAAO,MAC9B,QAAO;AAGX,UAAO;IACP;EAEF,MAAM,cAAc,eAAwB;AAC1C,UAAO,YAAY,UAAU;IAC7B;EAEF,MAAM,aAAa,eAAwB;AACzC,UAAO,YAAY,UAAU,eAAe,MAAM,SAAS;IAC3D;EAGF,MAAM,cAAc,MAAa,UAA0B;AACzD,OAAI,KAAK,OAAO,KACd,QAAO,OAAO,KAAK,IAAI;GAGzB,MAAM,YAAY,KAAK;AACvB,OAAI,WAAW,KACb,QAAO,UAAU;AAGnB,UAAO,aAAa;;EAGtB,MAAM,sBAAsB,YAA8B;AACxD,OAAI;IAMF,MAAM,QALS,MAAM,QAAQ,QAC3B,KAAK,iBAAiB,YAAY,MAAM,CAGzC,KACwB;AACzB,yBAAqB,YAAY,SAAS;AAC1C,WAAO;YACA,OAAO;AACd,YAAQ,MAAM,0BAA0B,MAAM;AAC9C,yBAAqB,YAAY,SAAS;AAC1C,WAAO;;;EAIX,MAAM,eAAe,OACnB,YACA,iBAAiB,UACI;AACrB,OAAI,aAAa,KAAK,cAAc,eAAe,MAAM,OACvD,QAAO;AAGT,OAAI,eAAe,YAAY,MAC7B,QAAO;AAGT,OAAI;AACF,YAAQ,QAAQ;AAGhB,QAAI,kBAAkB,YAAY,MAAM,oBAEtC;SAAI,CADY,MAAM,qBAAqB,CAEzC,QAAO;;AAKX,UAAM,KAAK,sBAAsB,YAAY,OAAO,WAAW;AAE/D,gBAAY,QAAQ;AACpB,SACE,eACA,YAAY,OACZ,eAAe,MAAM,YAAY,OAAO,OAAO,IAChD;AACD,WAAO;YACA,OAAO;AACd,YAAQ,MAAM,0BAA0B,MAAM;AAC9C,WAAO;aACC;AACR,YAAQ,QAAQ;;;EAKpB,MAAM,iBAAiB,YAA2B;AAChD,SAAM,aAAa,YAAY,QAAQ,GAAG,KAAK;;EAGjD,MAAM,2BAAiC;AACrC,gBAAa,YAAY,QAAQ,EAAE;;EAGrC,MAAM,WAAW,OAAO,cAAqC;AAC3D,OAAI,eAAe,MAAM,YAAY,OAAO,SAC1C;AAIF,SAAM,aAAa,WADI,YAAY,YAAY,MACF;;EAG/C,MAAM,8BAAoC;AACxC,OAAI,CAAC,SAAS,SAAS,eAAe,MAAM,WAAW,EACrD;GAGF,MAAM,EAAE,gBAAgB,YAAY;AAMpC,eAAY,QAJV,eAAe,KACf,cAAc,eAAe,MAAM,UACnC,CAAC,eAAe,MAAM,cAAc,OAAO,WAEJ,cAAc;;AAUzD,QAJyB,eACvB,YAAY,MAAM,MAAM,KAAK,MAAM,EAAE,IAAI,CAAC,KAAK,IAAI,CACpD,QAE6B;AAC5B,0BAAuB;IACvB;AAEF,kBAAgB;AACd,0BAAuB;IACvB;AAGF,WAAa;GACX,UAAU;GACV,cAAc;GACd;GACA;GACA,aAAa,SAAS,YAAY;GAClC,YAAY,eAAe,eAAe,MAAM,OAAO;GACxD,CAAC;;;;;;;uBArVA,mBAuGM,OAvGN,cAuGM,CAtGJ,mBAAA,kBAAsB,GACV,SAAA,sBAAZ,mBAMM,OANN,cAMM,mBALJ,mBAIE,UAAA,MAAA,WAHwBC,KAAAA,YAAhB,MAAM,UAAK;wBADrB,YAIE,wBADK,KAAI,EAAA,EADR,KAAK,WAAW,MAAM,MAAK;gCAMhC,mBA2FM,UAAA,EAAA,KAAA,GAAA,EAAA,CA5FN,mBAAA,iBAAqB,EACrB,mBA2FM,OA3FN,cA2FM;IA1FJ,mBAAA,UAAc;IACd,YAcS,mBAAA;KAbN,SAAS,YAAA,QAAW;KACpB,QAAQ,WAAA;KACR,MAAM,YAAA,MAAY;KAClB,UAAU,YAAA,MAAY;KACvB,OAAM;;4BAG0B,mBADhC,mBAME,UAAA,MAAA,WALe,eAAA,QAAR,SAAI;0BADb,YAME,kBAAA;OAJC,KAAK,KAAK,OAAO;OACjB,OAAO,KAAK,OAAO;OACnB,aAAa,KAAK,OAAO;OACzB,UAAU,KAAK,OAAO;;;;;;;;;;;;;;IAI3B,mBAAA,WAAe;IACf,YAwBQ,kBAAA;KAxBD,OAAM;KAAiB,UAAU;;4BAEG,mBADzC,mBAsBM,UAAA,MAAA,WArBoB,eAAA,QAAhB,MAAM,UAAK;0CADrB,mBAsBM,OAAA;OAnBH,KAAK,KAAK,OAAO;OAClB,OAAM;;OAEN,mBAAA,YAAgB;OACL,YAAA,MAAY,+BAAvB,mBAKM,OALN,cAKM,CAJJ,mBAAmD,MAAnD,cAAmD,gBAAzB,KAAK,OAAO,MAAK,EAAA,EAAA,EAClC,KAAK,OAAO,4BAArB,mBAEI,KAFJ,cAEI,gBADC,KAAK,OAAO,YAAW,EAAA,EAAA;OAI9B,mBAAA,YAAgB;OAChB,mBAMM,OANN,cAMM,mBALJ,mBAIE,UAAA,MAAA,WAH4B,KAAK,QAAzB,MAAM,cAAS;4BADzB,YAIE,wBADK,KAAI,EAAA,EADR,KAAK,WAAW,MAAM,UAAS;;oBAhB5B,YAAA,UAAgB,MAAK;;;;IAuBjC,mBAAA,WAAe;IACf,mBA4CM,OA5CN,cA4CM,CA3CJ,YA0CS,mBAAA,EA1CD,SAAQ,iBAAe,EAAA;4BAYnB;MAVF,YAAA,QAAW,kBADnB,YAWU,oBAAA;;OATP,UAAU,QAAA;OACV,SAAO;;8BAMN,CAJF,YAIE,gBAAA;QAHC,MAAM;QACN,MAAM;QACP,OAAA,EAAA,gBAAA,OAAyB;2BACzB,MACF,gBAAG,YAAA,MAAY,eAAc,EAAA,EAAA;;;gCAG/B,mBAAW,OAAA,MAAA,MAAA,GAAA;MAEX,YAyBS,mBAAA,MAAA;8BAZG,CAXF,YAAA,QAAc,eAAA,MAAe,SAAM,kBAD3C,YAYU,oBAAA;;QAVR,MAAK;QACJ,SAAS,QAAA;QACT,SAAO;;+BAEwB,iCAA7B,YAAA,MAAY,eAAc,GAAG,KAChC,EAAA,EAAA,YAIE,gBAAA;SAHC,MAAM;SACN,MAAM;SACP,OAAA,EAAA,eAAA,OAAwB;;;+DAI5B,WASE,KAAA,QAAA,gBAAA;QAPC,aAAc,YAAA;QACd,YAAa,eAAA,MAAe;QAC5B,aAAe,YAAA;QACf,YAAc,WAAA;QACd,UAAW;QACX,cAAe;QACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;AEvF3B,MAAM,iBAAoC;CACxC,WAAW;CACX,UAAU;CACV,YAAY;CACZ,cAAc;CACd,kBAAkB;CACnB;;;;AAKD,MAAa,qBAAqB;CAChC;EAAE,OAAO;EAAQ,OAAO;EAA0B;CAClD;EAAE,OAAO;EAAQ,OAAO;EAAgC;CACxD;EAAE,OAAO;EAAQ,OAAO;EAA6B;CACrD;EAAE,OAAO;EAAQ,OAAO;EAA2B;CACnD;EAAE,OAAO;EAAQ,OAAO;EAA2B;CACnD;EAAE,OAAO;EAAQ,OAAO;EAAyB;CAClD;;;;;AAMD,MAAa,4BAA4B;;;;CAIvC,MAAM,QAAQ,SAA2B;EACvC,QAAQ,EAAE,GAAG,gBAAgB;EAC7B,YAAY,EAAE;EACd,eAAe,EAAE;EACjB,gCAAgB,IAAI,KAAa;EACjC,cAAc;EACd,eAAe;EAChB,CAAC;;;;CAKF,MAAM,YAAY,eAA6B,CAC7C,GAAG,MAAM,YACT,GAAG,MAAM,cAAc,KAAI,WAAU;EACnC,GAAG;EACH,MAAM,CAAC,MAAM,eAAe,IAAI,MAAM,KAAK;EAC5C,EAAE,CACJ,CAAC;;;;CAKF,MAAM,gBAAgB,eACpB,UAAU,MAAM,QAAO,UAAS,MAAM,SAAS,MAAM,CACtD;;;;CAKD,MAAM,qBAAqB,eAAe,MAAM,cAAc,OAAO;;;;CAKrE,MAAM,oBAAoB,eAAe,MAAM,eAAe,KAAK;;;;CAKnE,MAAM,mBAAmB,eACjB,MAAM,cAAc,SAAS,MAAM,OAAO,UACjD;;;;CAKD,MAAM,aAAa,eAAe,MAAM,eAAe,SAAS,EAAE;;;;;CAMlE,MAAM,YAAY,SAAsC,EAAE,KAAK;AAC7D,MAAI,CAAC,iBAAiB,OAAO;AAC3B,WAAQ,KAAK,oCAAoC;AACjD;;AAGF,QAAM;EAEN,MAAM,cACJ,OAAO,QACP,mBAAmB,KAAK,MAAM,KAAK,QAAQ,GAAG,mBAAmB,OAAO,EACrE;EAEL,MAAM,WAA+B;GACnC,IAAI,iBAAiB,MAAM;GAC3B,MAAM;GACN,MAAM,OAAO,QAAQ,WAAW,MAAM;GACtC,OAAO,OAAO,SAAS,QAAQ,MAAM;GACrC,aAAa,OAAO,eAAe,MAAM,OAAO,SAAS;GACzD,SAAS;GACT,WAAW;GACX,SAAS,KAAK,KAAK;GACnB,QAAQ,OAAO,UAAU,EAAE,MAAM,IAAI;GACrC,GAAG;GACJ;AAED,QAAM,cAAc,KAAK,SAAS;AAElC,UAAQ,IAAI,+BAA+B,SAAS;;;;;;CAOtD,MAAM,eAAe,UAAmB;AACtC,MAAI,MAAM,cAAc,WAAW,GAAG;AACpC,WAAQ,KAAK,mCAAmC;AAChD;;EAGF,MAAM,cAAc,SAAS,MAAM,cAAc,SAAS;AAE1D,MAAI,cAAc,KAAK,eAAe,MAAM,cAAc,QAAQ;AAChE,WAAQ,KAAK,iCAAiC;AAC9C;;EAGF,MAAM,UAAU,MAAM,cAAc,OAAO,aAAa,EAAE,CAAC;AAC3D,MAAI,SAAS;AACX,SAAM,eAAe,OAAO,QAAQ,KAAK;AACzC,WAAQ,IAAI,+BAA+B,QAAQ,KAAK;;;;;;CAO5D,MAAM,2BAA2B;AAC/B,UAAQ,IACN,iCACA,MAAM,cAAc,OACrB;AACD,QAAM,cAAc,SAAQ,UAC1B,MAAM,eAAe,OAAO,MAAM,KAAK,CACxC;AACD,QAAM,cAAc,SAAS;AAC7B,QAAM,eAAe;;;;;;CAOvB,MAAM,yBAAyB,YAAoB;AACjD,MAAI,MAAM,eAAe,IAAI,QAAQ,EAAE;AACrC,SAAM,eAAe,OAAO,QAAQ;AACpC,WAAQ,IAAI,+BAA+B,QAAQ;SAC9C;AACL,SAAM,eAAe,IAAI,QAAQ;AACjC,WAAQ,IAAI,+BAA+B,QAAQ;;;;;;CAOvD,MAAM,4BAA4B;AAChC,MAAI,WAAW,OAAO;AACpB,SAAM,cAAc,SAAQ,UAAS;AACnC,UAAM,eAAe,IAAI,MAAM,KAAK;KACpC;AACF,WAAQ,IAAI,iCAAiC;SACxC;AACL,SAAM,eAAe,OAAO;AAC5B,WAAQ,IAAI,+BAA+B;;;;;;;CAQ/C,MAAM,gBAAgB,cAA0C;AAC9D,SAAO,OAAO,MAAM,QAAQ,UAAU;AACtC,UAAQ,IAAI,+BAA+B,UAAU;;;;;;CAOvD,MAAM,qBAAqB;EACzB,MAAM,SAAS;GACb,YAAY,MAAM;GAClB,eAAe,MAAM;GACrB,QAAQ,MAAM;GACd,cAAc,MAAM,KAAK,MAAM,eAAe;GAC9C,WAAW,KAAK,KAAK;GACtB;AACD,SAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;;;;;;;CAQxC,MAAM,cACJ,YACA,SAAqC,EAAE,KACpC;AACH,UAAQ,IAAI,gCAAgC;GAC1C,iBAAiB,WAAW;GAC5B;GACD,CAAC;AAEF,QAAM,aAAa,CAAC,GAAG,WAAW;AAClC,SAAO,OAAO,MAAM,QAAQ,OAAO;AACnC,QAAM,gBAAgB;AAEtB,UAAQ,IAAI,8BAA8B;;AAG5C,QAAO;EACL,OAAO,SAAS,MAAM;EACtB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;;;;;AAWH,MAAa,yBAAyB,OAAO,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;EEnIhE,MAAM,QAAQ;EAad,MAAM,uBAAuB,OAC3B,wBACA,KACD;EAED,MAAM,eAAe,eACb,MAAM,oBAAoB,qBACjC;EACD,MAAM,0BAA0B,eAAe,CAAC,CAAC,aAAa,MAAM;EAGpE,MAAM,WAAW,eAAe,MAAM,cAAc,SAAS,MAAM,QAAQ,GAAG;EAC9E,MAAM,aAAa,eACX,MAAM,cAAc,SAAS,MAAM,UAAU,GACpD;EACD,MAAM,eAAe,eACb,MAAM,cAAc,SAAS,UAAU,iBAAiB,MAC/D;EAED,MAAM,YAAY,eAAe;AAC/B,UACE,aAAa,OAAO,MAAM,OAAO,aACjC,MAAM,cAAc,SAAS,SAAS,aACtC;IAEF;EAEF,MAAM,qBAAqB,eACnB,aAAa,OAAO,mBAAmB,SAAS,EACvD;EACD,MAAM,mBAAmB,eACjB,aAAa,OAAO,iBAAiB,SAAS,MACrD;EACD,MAAM,mBAAmB,eAAe,MAAM,UAAU,OAAO;EAG/D,MAAM,cAAc,MAAa,UAA0B;AACzD,UACE,KAAK,KAAK,UAAU,IAAK,KAAK,OAAe,QAAQ,gBAAgB;;EAIzE,MAAM,eAAe,UAA0B;GAC7C,MAAM,OAAO,MAAM,UAAU,QAAQ,QAAQ;AAC7C,UAAO,OAAO,SAAS,YAAY,OAAO,KAAK,QAAQ,SAAS,QAC5D,OACA,KAAK,IAAI,IAAI,SAAS,MAAM;;EAGlC,MAAM,kBAAkB,SAAyB;AAC/C,OAAI,CAAC,aAAa,MAAO,QAAO;GAChC,MAAM,UAAW,KAAK,OAAe,QAAQ,KAAK,KAAK,UAAU,IAAI;AACrE,UAAO,aAAa,MAAM,MAAM,cAAc,MAC3C,UAAe,MAAM,SAAS,QAChC;;AAIH,WAAa;GACX,gBAAgB,aAAa,OAAO,UAAU;GAC9C,mBAAmB,aAAa,OAAO,aAAa;GACpD,uBAAuB,aAAa,OAAO,oBAAoB;GAC/D;GACA;GACD,CAAC;AAGF,MAAI,OAAO,OAAO,SAAS,eAAe,OAAO,KAAK,KAAK,IACzD,mBAAkB;GAChB,MAAM,EAAE,SAAS,cAAc;AAC/B,OAAI,WAAW,QAAQ,WAAW,UAAU,OAC1C,SAAQ,KACN,0BAA0B,QAAQ,OAAO,UAAU,UAAU,OAAO,MACrE;AAGH,WAAQ,IACN,oBACA,wBAAwB,QAAQ,YAAY,WAC5C;IACE,aAAa,iBAAiB;IAC9B,eAAe,mBAAmB;IAClC,aAAa,MAAM,mBAAmB,YAAY;IACnD,CACF;IACD;;;;;;;;;;;uBAtOF,mBA8GM,OA9GN,cA8GM;IA7GJ,mBAAA,WAAe;IAEP,aAAA,SAAgB,wBAAA,sBADxB,mBA0EM,OA1EN,cA0EM,CAtEJ,YAqEQ,kBAAA;KArED,MAAK;KAAQ,OAAM;KAAU,UAAU;;KACjC,gBAAY,cAGZ,CAFT,YAES,mBAAA;MAFA,OAAO,mBAAA;MAAoB,MAAK;;6BACG,CAA1C,YAA0C,gBAAA;OAAjC,MAAM;OAAe,MAAM;;;;4BAyD/B,CArDT,YAqDS,mBAAA;MArDD,SAAQ;MAAgB,OAAM;;6BAqC3B;OApCT,YAoCS,mBAAA,MAAA;+BAzBG;SAVV,YAUU,oBAAA;UATR,MAAK;UACL,MAAK;UACJ,UAAQ,CAAG,iBAAA;UACX,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,aAAA,MAAc,UAAQ;;UAEnB,MAAI,cAC2B,CAAxC,YAAwC,gBAAA;WAA/B,MAAM;WAAa,MAAM;;iCAE9B,iBADK,YACL,gBAAG,mBAAA,MAAkB,GAAG,MAAC,gBAAG,UAAA,MAAS,GAAG,MAChD,EAAA;;;SAEA,YAUU,oBAAA;UATR,MAAK;UACL,MAAK;UACJ,UAAU,mBAAA,UAAkB;UAC5B,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,aAAA,MAAc,aAAW;;UAEtB,MAAI,cAC4B,CAAzC,YAAyC,gBAAA;WAAhC,MAAM;WAAc,MAAM;;iCAGvC,2CAFa,UAEb,GAAA;;;;SAEA,YAUU,oBAAA;UATR,MAAK;UACL,MAAK;UACJ,UAAU,mBAAA,UAAkB;UAC5B,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,aAAA,MAAc,oBAAkB;;UAE7B,MAAI,cACmC,CAAhD,YAAgD,gBAAA;WAAvC,MAAM;WAAqB,MAAM;;iCAG9C,2CAFa,UAEb,GAAA;;;;;;;OAGF,mBAAA,YAAgB;OAChB,mBAYM,OAZN,cAYM,2BAXJ,mBAAmB,QAAA,MAAb,UAAM,GAAA,GACZ,YASE,yBAAA;QARC,OAAO,UAAA;QACP,kBAAY,OAAA,OAAA,OAAA,MAAqB,MAAW,KAAK,aAAA,MAAc,aAAY,EAAA,WAAc,GAAC,CAAA;QAG1F,KAAK;QACL,KAAK;QACN,MAAK;QACL,OAAA,EAAA,SAAA,SAAoB;;;;SAM1B,mBAKM,OALN,cAKM,CAJJ,YAGS,mBAAA,MAAA;6BAF6C,CAApD,YAAoD,iBAAA,EAA9C,MAAK,QAAM,EAAA;8BAAM,iBAAL,UAAK,gBAAG,iBAAA,MAAgB,EAAA,EAAA;;UAC1C,YAAwD,iBAAA,EAAlD,MAAK,WAAS,EAAA;8BAAK,iBAAJ,SAAI,gBAAG,mBAAA,MAAkB,EAAA,EAAA;;;;;;YAQzC,aAAA,SAAY,CAAK,wBAAA,sBAD9B,mBAYM,UAAA,EAAA,KAAA,GAAA,EAAA,CAbN,mBAAA,cAAkB,EAClB,mBAYM,OAZN,cAYM,CARJ,YAOQ,kBAAA;KAPD,MAAK;KAAQ,OAAM;KAAiB,UAAU;;4BAM1C,CALT,YAKS,mBAAA;MALD,MAAK;MAAO,aAAA;;MACP,MAAI,cAC0C,CAAvD,YAAuD,gBAAA;OAA9C,MAAM;OAA4B,MAAM;;6BAGrD,2CAFa,mCAEb,GAAA;;;;;;IAIJ,mBAAA,YAAgB;IAChB,mBAeM,OAfN,cAeM,CAdJ,YAaQ,kBAAA;KAbA,MAAM,SAAA;KAAW,SAAO,WAAA;KAAa,SAAO,WAAA;;4BAEd,mBADpC,mBAWY,UAAA,MAAA,WAVcC,KAAAA,YAAhB,MAAM,UAAK;0BADrB,YAWY,sBAAA;OATT,KAAK,WAAW,MAAM,MAAK;OAC3B,MAAM,YAAY,MAAK;;8BAOlB,CALN,mBAKM,OAAA,EAJJ,OAAK,eAAA,CAAC,wBAAsB,EAAA,oBACE,eAAe,KAAI,EAAA,CAAA,CAAA,mBAEjD,YAAwB,wBAAR,KAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EG8HhC,MAAM,QAAQ;EAMd,MAAM,OAAO;EAMb,MAAM,eAAe,IAAI,KAAK;EAC9B,MAAM,cAAc,IAAkB,EAAE,CAAC;EACzC,MAAM,kBAAkB,IAA2B,EAAE,CAAC;EACtD,MAAM,iBAAiB,IAAqB,GAAG;EAG/C,MAAM,iBAAiB,eAAe;AACpC,OAAI,MAAM,SAAS,SAAS,EAC1B,QAAO,MAAM;AAGf,UACE,MAAM,WACF,KAAK,SAAgB;IACrB,MAAM,YAAY,KAAK;AACvB,WAAO;KACL,MAAM,WAAW,QAAQ;KACzB,OAAO,WAAW,SAAS,WAAW,QAAQ;KAC9C,MAAM;KACN,MAAM;KACP;KACD,CACD,QAAQ,WAAW,OAAO,KAAK,IAAI,EAAE;IAE1C;EAEF,MAAM,mBAAmB,eACvB,YAAY,MAAM,QAAQ,OAAO,SAAS,QAAQ,KAAK,OAAO,QAAQ,EAAE,CACzE;EAGD,MAAM,mBACJ,GAAG,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,UAAU,GAAG,GAAG;EAE9D,MAAM,oBAAoB,SAAiB;AAUzC,UATwC;IACtC,OAAO;IACP,QAAQ;IACR,OAAO;IACP,UAAU;IACV,UAAU;IACV,MAAM;IACN,QAAQ;IACT,CACc,SAAS;;EAG1B,MAAM,uBAAuB,UAAsB;AACjD,UACE,MAAM,WAAW,MAAM,SAAgB;AAErC,WADkB,KAAK,OACL,SAAS,MAAM;KACjC,IAAI;;EAKV,MAAM,WAAW,SAA6C;GAC5D,MAAM,OAAmB;IACvB,IAAI,YAAY;IAChB,OAAO,GAAG,SAAS,eAAe,OAAO,SAAS,aAAa,OAAO,KAAK;IAC3E;IACA,QAAQ,EAAE;IACX;AACD,eAAY,MAAM,KAAK,KAAK;;EAG9B,MAAM,cAAc,WAA4B;GAC9C,MAAM,QAAQ,YAAY,MAAM,WAAW,SAAS,KAAK,OAAO,OAAO;AACvE,OAAI,UAAU,GACZ,aAAY,MAAM,OAAO,OAAO,EAAE;;EAItC,MAAM,yBAAyB;GAC7B,MAAM,SAAS,KAAK,UAAU,YAAY,OAAO,MAAM,EAAE;GACzD,MAAM,OAAO,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,oBAAoB,CAAC;GAC7D,MAAM,MAAM,IAAI,gBAAgB,KAAK;GACrC,MAAM,OAAO,SAAS,cAAc,IAAI;AACxC,QAAK,OAAO;AACZ,QAAK,WAAW,iBAAiB,KAAK,KAAK,CAAC;AAC5C,YAAS,KAAK,YAAY,KAAK;AAC/B,QAAK,OAAO;AACZ,YAAS,KAAK,YAAY,KAAK;AAC/B,OAAI,gBAAgB,IAAI;;EAG1B,MAAM,0BAA0B;AAC9B,eAAY,QAAQ,EAAE;;EAGxB,MAAM,yBAAyB;AAM7B,QAAK,eALc;IACjB,QAAQ,YAAY;IACpB,UAAU,MAAM;IAChB,4BAAW,IAAI,MAAM,EAAC,aAAa;IACpC,CAC8B;;AAIjC,oBAAkB;GAChB,MAAM,YAAY,IAAI,IACpB,YAAY,MAAM,SAAS,SAAS,KAAK,OAAO,KAAK,UAAU,MAAM,KAAK,CAAC,CAC5E;AAED,mBAAgB,QAAQ,eAAe,MACpC,QAAQ,UAAU,CAAC,UAAU,IAAI,MAAM,KAAK,CAAC,CAC7C,KAAK,WAAW;IACf,GAAG;IACH,IAAI,MAAM;IACX,EAAE;IACL;AAEF,cACQ,YAAY,aACZ;AAOJ,QAAK,iBANa,YAAY,MAAM,SAAS,SAC3C,KAAK,OAAO,KAAK,WAAW;IAC1B,GAAG;IACH,IAAI;IACL,EAAE,CACJ,CAC+B;KAElC,EAAE,MAAM,MAAM,CACf;;;;;;;;uBAjXC,mBA6MM,OA7MN,YA6MM;IA5MJ,mBAAA,UAAc;IACd,YAoDQ,kBAAA;KApDA,UAAU;KAAO,OAAM;;4BAmDvB,CAlDN,mBAkDM,OAlDN,YAkDM;MAjDJ,mBAAA,SAAa;MACb,mBAkBM,OAlBN,YAkBM,6BAjBJ,mBAAyC,QAAA,EAAnC,OAAM,iBAAe,EAAC,UAAM,GAAA,GAClC,YAee,yBAAA,MAAA;8BARH,CANV,YAMU,oBAAA;QALP,MAAM,aAAA,QAAY,YAAA;QAClB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,aAAA,QAAY;QACpB,MAAK;;+BAGP,OAAA,OAAA,OAAA,KAAA,iBAFC,aAED,GAAA;;;wBACA,YAMU,oBAAA;QALP,MAAI,CAAG,aAAA,QAAY,YAAA;QACnB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,aAAA,QAAY;QACpB,MAAK;;+BAGP,OAAA,QAAA,OAAA,MAAA,iBAFC,aAED,GAAA;;;;;;MAIJ,mBAAA,SAAa;MACb,mBASM,OATN,YASM,CARJ,mBAGM,OAHN,YAGM,CAFJ,mBAAsD,OAAtD,YAAsD,gBAA3B,YAAA,MAAY,OAAM,EAAA,EAAA,8BAC7C,mBAAmC,OAAA,EAA9B,OAAM,cAAY,EAAC,SAAK,GAAA,KAE/B,mBAGM,OAHN,YAGM,CAFJ,mBAAoD,OAApD,YAAoD,gBAAzB,iBAAA,MAAgB,EAAA,EAAA,8BAC3C,mBAAkC,OAAA,EAA7B,OAAM,cAAY,EAAC,QAAI,GAAA;MAIhC,mBAAA,SAAa;MACb,mBAcM,OAdN,YAcM,CAbY,aAAA,sBAAhB,mBAOW,UAAA,EAAA,KAAA,GAAA,EAAA,CANT,YAEU,oBAAA;OAFD,WAAA;OAAW,SAAO;OAAkB,MAAK;;8BAElD,OAAA,QAAA,OAAA,MAAA,iBAF0D,aAE1D,GAAA;;;UACA,YAEU,oBAAA;OAFD,WAAA;OAAW,SAAO;OAAmB,MAAK;;8BAEnD,OAAA,QAAA,OAAA,MAAA,iBAF2D,aAE3D,GAAA;;;gCAGA,YAEU,oBAAA;;OAFD,WAAA;OAAW,SAAO;OAAkB,MAAK;;8BAElD,OAAA,QAAA,OAAA,MAAA,iBAF0D,aAE1D,GAAA;;;;;;;IAMR,mBAAA,aAAiB;IACJ,aAAA,sBAAb,YAaQ,kBAAA;;KAbmB,OAAM;KAAe,OAAM;;4BAY9C,CAXN,mBAWM,OAXN,aAWM,CAVJ,mBASM,OATN,aASM;kCARJ,mBAAsC,QAAA,EAAhC,OAAM,eAAa,EAAC,SAAK,GAAA;MAC/B,YAEU,oBAAA;OAFA,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,QAAO,aAAA;OAAgB,MAAK;;8BAE7C,OAAA,QAAA,OAAA,MAAA,iBAFqD,aAErD,GAAA;;;;MACA,YAEU,oBAAA;OAFA,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,QAAO,WAAA;OAAc,MAAK;;8BAE3C,OAAA,QAAA,OAAA,MAAA,iBAFmD,aAEnD,GAAA;;;;MACA,YAAiE,oBAAA;OAAvD,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,QAAO,OAAA;OAAU,MAAK;;8BAAgB,OAAA,QAAA,OAAA,MAAA,iBAAR,YAAQ,GAAA;;;;;;;IAK7D,mBAAA,UAAc;IACd,mBAmIM,OAAA,EAnID,OAAK,eAAA,CAAC,iBAAe,EAAA,eAA0B,aAAA,OAAY,CAAA,CAAA,KAC9D,mBAAA,SAAa,EACG,aAAA,sBAAhB,mBAuFW,UAAA,EAAA,KAAA,GAAA,EAAA;KAtFsB,YAAA,MAAY,WAAM,kBAAjD,mBAKM,OALN,aAKM,OAAA,QAAA,OAAA,MAAA,CAJJ,mBAGM,OAAA,EAHD,OAAM,gBAAc,EAAA,CACvB,mBAAqB,MAAA,MAAjB,eAAY,EAChB,mBAAiB,KAAA,MAAd,aAAU,2BAKjB,mBA6DM,UAAA,EAAA,KAAA,GAAA,EAAA,CA9DN,mBAAA,SAAa,EACb,mBA6DM,OA7DN,aA6DM,mBA5DJ,mBA2DM,UAAA,MAAA,WA1DW,YAAA,QAAR,SAAI;0BADb,mBA2DM,OAAA;OAzDH,KAAK,KAAK;OACX,OAAK,eAAA,CAAC,eAAa,QACH,KAAK,OAAI,CAAA;;OAEzB,mBA8BM,OA9BN,aA8BM,CA7BJ,mBAiBM,OAjBN,aAiBM,CAfI,eAAA,UAAmB,KAAK,mBADhC,YAOE,mBAAA;;QALQ,OAAO,KAAK;sCAAL,KAAK,QAAK;QACzB,MAAK;QACJ,QAAI,OAAA,OAAA,OAAA,MAAA,WAAE,eAAA,QAAc;QACpB,SAAK,OAAA,OAAA,OAAA,KAAA,UAAA,WAAQ,eAAA,QAAc,IAAA,CAAA,QAAA,CAAA;QAC5B,OAAM;iEAER,mBAMO,QAAA;;QAJL,OAAM;QACL,UAAK,WAAE,eAAA,QAAiB,KAAK;0BAE3B,KAAK,MAAK,EAAA,GAAA,YAAA,GAEf,YAAqD,iBAAA,EAA/C,MAAK,SAAO,EAAA;+BAAyB,iCAArB,KAAK,OAAO,OAAM,GAAG,OAAG,EAAA;;mBAEhD,mBAUM,OAVN,aAUM,CATJ,YAQU,oBAAA;QAPR,MAAA;QACC,UAAK,WAAE,WAAW,KAAK,GAAE;QAC1B,MAAK;QACL,MAAK;QACL,OAAM;;+BAGR,OAAA,QAAA,OAAA,MAAA,iBAFC,SAED,GAAA;;;;OAIJ,mBAAA,SAAa;OACb,mBAeM,OAfN,aAeM,mBAdJ,mBAaM,UAAA,MAAA,WAZY,KAAK,SAAd,UAAK;4BADd,mBAaM,OAAA;SAXH,KAAK,MAAM;SACZ,OAAM;YAEN,mBAOM,OAPN,aAOM,CANJ,mBAES,QAFT,aAES,gBADP,MAAM,SAAS,MAAM,KAAI,EAAA,EAAA,EAE3B,mBAES,QAFT,aAES,gBADP,iBAAiB,MAAM,KAAI,CAAA,EAAA,EAAA;;OAMD,KAAK,OAAO,WAAM,kBAApD,mBAEM,OAFN,aAA4D,YAE5D;;;KAIJ,mBAAA,QAAY;KACZ,YAaQ,kBAAA;MAbD,OAAM;MAAa,OAAM;;6BAYxB,CAXN,mBAWM,OAXN,aAWM,mBAVJ,mBASM,UAAA,MAAA,WARY,gBAAA,QAAT,UAAK;2BADd,mBASM,OAAA;QAPH,KAAK,MAAM;QACZ,OAAM;WAEN,mBAA+D,QAA/D,aAA+D,gBAAnC,MAAM,SAAS,MAAM,KAAI,EAAA,EAAA,EACrD,mBAES,QAFT,aAES,gBADP,iBAAiB,MAAM,KAAI,CAAA,EAAA,EAAA;;;;4BAQrC,mBAsCW,UAAA,EAAA,KAAA,GAAA,EAAA,CAvCX,mBAAA,SAAa,EAEA,YAAA,MAAY,WAAM,kBAA7B,mBAQM,OARN,aAQM,CAPJ,YAMS,mBAAA,EAND,aAAY,UAAQ,EAAA;KACf,OAAK,cAGJ,CAFV,YAEU,oBAAA;MAFA,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,aAAA,QAAY;MAAS,MAAK;;6BAE3C,OAAA,QAAA,OAAA,MAAA,iBAFqD,aAErD,GAAA;;;;;0BAKN,mBA0BM,OA1BN,aA0BM,CAzBJ,mBAwBM,OAxBN,aAwBM,mBAvBJ,mBAsBM,UAAA,MAAA,WArBW,YAAA,QAAR,SAAI;yBADb,mBAsBM,OAAA;MApBH,KAAK,KAAK;MACX,OAAK,eAAA,CAAC,aAAW,QACD,KAAK,OAAI,CAAA;SAEZ,KAAK,OAAO,SAAM,kBAA/B,YAQQ,kBAAA;;MAR8B,OAAO,KAAK;;6BAO1C,CANN,mBAMM,OAAA,EAND,OAAK,eAAA,CAAC,mBAAiB,UAAmB,KAAK,OAAI,CAAA,uBACtD,mBAIE,UAAA,MAAA,WAHgB,KAAK,SAAd,UAAK;2BADd,YAIE,wBADK,oBAAoB,MAAK,CAAA,EAAA,EAD7B,KAAK,MAAM;;;0CAKlB,YAMS,mBAAA;;MANM,aAAY;MAAU,MAAK;;MAC7B,OAAK,cAGJ,CAFV,YAEU,oBAAA;OAFA,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,aAAA,QAAY;OAAS,MAAK;OAAQ,WAAA;;8BAEnD,OAAA,QAAA,OAAA,MAAA,iBAF6D,aAE7D,GAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EG3ElB,MAAM,aAAsC;GAC1C,SAAS;GACT,QAAQ;GACR,MAAM;GACN,MAAM;GACN,MAAM;GACN,OAAO;GACP,SAAS;GACT,QAAQ;GACT;EAOD,MAAM,gBAA8B;GAClC;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EAaD,MAAM,QAAQ;EAMd,MAAM,OAAO;EASb,MAAM,WAAW,eAAe,kBAAkB,MAAM,OAAO,CAAC;EAIhE,MAAM,UAAU,IAAqB,KAAK;EAI1C,MAAM,EACJ,WACA,WACA,gBACA,YACA,mBACA,UACA,eACA,cACA,aACA,uBACA,qBACA,iBACA,UACA,WACA,aACA,eACA,eACA,gBACA,cACA,gBACE,aAxBe,eAAe,MAAM,QAAQ,EAwBnB,UAAU,SAAS,KAAK;EAIrD,MAAM,EAAE,cAAc,gBACpB,WACA,gBACA,UACA,mBACA,eANsB,oBAAoB,EAOzB,MAClB;EAID,MAAM,kBAAkB,eAChB,WAAW,SAAS,MAAM,WAAW,WAAW,QACvD;EAED,MAAM,qBAAqB,gBAA8B;GACvD,MAAM,SAAS,MAAM;GACrB,MAAM,SAAS,MAAM;GACrB,QAAQ,SAAS,MAAM;GACvB,MAAM,SAAS,MAAM;GACrB,MAAM,SAAS,MAAM;GACrB,OAAO,SAAS,MAAM;GACtB,SAAS,SAAS,MAAM;GACxB,QAAQ,SAAS,MAAM;GACxB,EAAE;EAEH,MAAM,cAAc,eAAe,kBAAgB,SAAS,MAAM,CAAC;;EAKnE,MAAM,qBAAqB,cAAsB,GAAG,SAAgB;GAClE,MAAM,WAAY,SAAS,MAAc;AACzC,cAAW,GAAG,KAAK;;;EAIrB,MAAM,sBAAsB,WAA+B;AACzD,YAAS,MAAM,iBAAiB,OAAO;;;EAIzC,MAAM,oBAAoB,WAAmB,YAA0B;AACrE,YAAS,MAAM,eAAe,WAAW,QAAQ;;EAGnD,MAAM,yBAAyB,OAC7B,aACA,eACqB;AACrB,YAAS,MAAM,qBAAqB,aAAa,WAAW;AAC5D,UAAO;;EAGT,MAAM,qBAAqB,OAAO,cAAwC;AACxE,OAAI;IACF,MAAM,iBAAiB,SAAS,MAAM,OAAO,QAAQ,YAAY;AACjE,QAAI,CAAC,eAAgB,QAAO;IAE5B,MAAM,aAAa,MAAM,QACtB,QAAQ,WAAW,OAAO,QAAQ,SAAS,eAAe,CAC1D,KAAK,WAAW,OAAO,KAAK;AAE/B,QAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,UAAM,cAAc,WAAW;AAC/B,aAAS,MAAM,iBAAiB,UAAU;AAC1C,WAAO;YACA,OAAO;AACd,YAAQ,KAAK,eAAe,UAAU,SAAS,MAAM;AACrD,WAAO;;;AAMX,cACQ,MAAM,aACX,QAAQ;AACP,OAAI,IAAK,QAAO,OAAO,WAAW,IAAI;KAExC;GAAE,WAAW;GAAM,MAAM;GAAM,CAChC;AAID,WAAa;GACX;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,YAAY,eAAe,SAAS,MAAM,OAAO;GACjD,0BAA0B;GAC3B,CAAC;;uBArUA,YA4DQ,MAAA,MAAA,EA5DR,WA4DQ;aA3DF;IAAJ,KAAI;IACH,OAAO,MAAA,UAAS;IAChB,OAAO,MAAA,UAAS;IAChB,2BAAyB;IACzB,mBAAiB,SAAA,MAAS;IAC1B,eAAa,SAAA,MAAS;IACtB,MAAM,SAAA,MAAS;IACf,UAAU,SAAA,MAAS;IACnB,UAAU,SAAA,MAAS;MACZC,KAAAA,OAAM,EAAA;2BAEC;KAAf,mBAAA,WAAe;mBACf,YA0BE,wBAzBK,gBAAA,MAAe,EAAA;MACnB,cAAY,MAAA,UAAS;MACrB,iBAAe,mBAAA;MACf,SAAS,MAAA,eAAc;MACvB,aAAW,MAAA,UAAS;MACpB,aAAU,OAAA,OAAA,OAAA,MAAA,WAAE,kBAAiB,eAAgB,OAAM;MACnD,eAAY,OAAA,OAAA,OAAA,MAAA,WAAE,kBAAiB,iBAAkB,OAAM;MACvD,cAAa;MACb,oBAAoB;MACpB,gBAAe;MACf,YAAS,OAAA,OAAA,OAAA,MAAA,WAAE,kBAAiB,cAAe,OAAM;MACjD,eAAY,OAAA,OAAA,OAAA,MAAA,WAAE,kBAAiB,iBAAkB,OAAM;MACvD,eAAY,OAAA,OAAA,OAAA,MAAa,IAAY,YAAqB,SAAA,MAAS,gBAAgB,IAAI,QAAO;MAG9F,eAAY,OAAA,OAAA,OAAA,MAAA,WAAE,kBAAiB,gBAAA;MAC/B,oBAAkB,OAAA,OAAA,OAAA,MAAA,WAAE,kBAAiB,sBAAuB,OAAM;MAClE,eAAY,OAAA,OAAA,OAAA,MAAa,KAAa,cAAkC,SAAA,MAAS,gBAAgB,KAAK,UAAS;MAI/G,cAAW,OAAA,OAAA,OAAA,MAAA,WAAE,kBAAiB,gBAAiB,OAAM;MACrD,mBAAgB,OAAA,OAAA,OAAA,MAAG,UAAqB,KAAI,oBAAqB,MAAK;MACtE,iBAAc,OAAA,QAAA,OAAA,OAAG,WAAoB,KAAI,kBAAmB,OAAM;MAClE,gBAAe;;;;;;;KAGlB,mBAAA,wBAA4B;KACX,YAAA,sBAAjB,YAiBY,MAAA,UAAA,EAAA;;MAjBkB,OAAA,EAAA,cAAA,QAAwB;;6BAgB7C,CAfP,WAeO,KAAA,QAAA,UAAA;OAbJ,MAAM,QAAA;OACN,OAAO,MAAA,UAAS;OAChB,UAAU,MAAA,SAAQ;OAClB,eAAe,MAAA,cAAa;OAC5B,OAAO,MAAA,YAAW;OAClB,WAAW,MAAA,UAAS;OACpB,UAAU,MAAA,SAAQ;OAClB,iBAAiB,MAAA,gBAAe;eAM5B,CAJL,YAGS,MAAA,OAAA,EAAA,MAAA;8BAFmD,CAA1D,YAA0D,MAAA,QAAA,EAAA;QAAjD,MAAK;QAAW,SAAO,MAAA,aAAY;;+BAAI,OAAA,QAAA,OAAA,MAAA,iBAAF,MAAE,GAAA;;;2BAChD,YAA0C,MAAA,QAAA,EAAA,EAAhC,SAAO,MAAA,YAAW,EAAA,EAAA;+BAAI,OAAA,QAAA,OAAA,MAAA,iBAAF,MAAE,GAAA"}