UNPKG

@chl1860/dynamic-form-vue3

Version:

Vue3 + TypeScript + Ant Design Vue 动态表单组件

850 lines (713 loc) 24.3 kB
# Dynamic Form - Vue3 动态表单组件 v2.0 -- 基于 Vue 3 + TypeScript + Ant Design Vue 构建的**简化架构**动态表单组件,彻底解决了联动和验证问题,显著提升开发效率。 ## ✨ v2.0 核心优势 - 🎯 **彻底解决联动问题**: 告别"No data"错误,100%可靠的字段联动 - 🏗 **简化架构设计**: 单一数据源 + 响应式计算,易于理解和维护 - 🚀 **性能大幅提升**: 减少40%代码复杂度,提升50%开发效率 - 🔧 **路径导向处理**: 优雅的嵌套字段支持,支持深度路径访问 - 📱 **标准组件接口**: 统一的 `value`/`@update:value` 模式 - 🎨 **自定义组件支持**: 完整的自定义组件注册和使用系统 - 💡 **提示功能完善**: 支持简单文本和高级配置的字段提示 - ⚡ **异步功能完备**: 异步初始化、联动和验证的全面支持 - 🛠 **开发体验优化**: 更少的概念,更直观的API设计 ## 📚 文档中心 详细的文档和指南请访问:[📚 文档中心](./docs/) - **[🚀 功能特性概览](./docs/FEATURES_OVERVIEW.md)** - 功能列表和使用场景 - **[📖 详细使用指南](./docs/USAGE_GUIDE.md)** - 完整的使用说明和示例(推荐先阅读) - **[📚 API 参考](./docs/API_REFERENCE.md)** - 完整的类型定义和接口说明 ## 📦 安装 ```bash # 使用 npm 安装 npm install @chl1860/dynamic-form-vue3 # 使用 yarn 安装 yarn add @chl1860/dynamic-form-vue3 # 使用 pnpm 安装 pnpm add @chl1860/dynamic-form-vue3 ``` ### 对等依赖 请确保您的项目中已安装以下对等依赖: ```bash npm install vue@^3.4.0 ant-design-vue@^4.0.0 ``` ### 全局安装(推荐) 在您的 Vue 应用中全局注册组件: ```typescript // main.ts import { createApp } from 'vue' import Antd from 'ant-design-vue' import DynamicForm from '@chl1860/dynamic-form-vue3' import 'ant-design-vue/dist/reset.css' import '@chl1860/dynamic-form-vue3/dist/style.css' // 如果有样式文件 const app = createApp(App) app.use(Antd) app.use(DynamicForm, { componentPrefix: 'Simple', // 可选,默认为 'Simple' debug: false // 可选,是否开启调试模式 }) app.mount('#app') ``` ### 按需导入 ```typescript import { SimpleForm, SimpleFormItem, type SimpleFormSchema } from '@chl1860/dynamic-form-vue3' ``` ## 🚀 v2.0 快速开始 ### 基础用法 ```vue <template> <SimpleForm v-model="formData" :schema="schema" @submit="handleSubmit" @change="handleChange" /> </template> <script setup lang="ts"> import { ref } from 'vue' import { SimpleForm, type SimpleFormSchema } from '@chl1860/dynamic-form-vue3' const formData = ref({}) const schema: SimpleFormSchema = { fields: [ { name: 'username', type: 'input', label: '用户名', placeholder: '请输入用户名', rules: [{ required: true, message: '用户名为必填项' }] }, { name: 'email', type: 'input', label: '邮箱', placeholder: '请输入邮箱地址', rules: [{ required: true, message: '邮箱为必填项' }] } ] } const handleSubmit = (data: any) => { console.log('表单提交:', data) } const handleChange = (data: any) => { console.log('表单变化:', data) } </script> ``` ### 字段联动(完美解决方案) ```typescript const schema: SimpleFormSchema = { fields: [ { name: 'region', type: 'select', label: '地区', options: [ { label: '北京', value: 'beijing' }, { label: '上海', value: 'shanghai' } ] }, { name: 'area', type: 'select', label: '区域', placeholder: '请选择区域', linkage: { dependsOn: 'region', optionsMap: { beijing: [ { label: '朝阳区', value: 'chaoyang' }, { label: '海淀区', value: 'haidian' } ], shanghai: [ { label: '浦东新区', value: 'pudong' }, { label: '黄浦区', value: 'huangpu' } ] } } } ] } ``` ### 条件显示 ```typescript const schema: SimpleFormSchema = { fields: [ { name: 'userType', type: 'radio', label: '用户类型', options: [ { label: '个人用户', value: 'personal' }, { label: '企业用户', value: 'enterprise' } ] }, { name: 'companyName', type: 'input', label: '公司名称', placeholder: '请输入公司名称', linkage: { dependsOn: 'userType', visibleWhen: (value, formData) => formData.userType === 'enterprise' }, rules: [{ required: true, message: '请输入公司名称' }] } ] } ``` ### 嵌套分组(完美支持) ```typescript const schema: SimpleFormSchema = { fields: [ { name: 'basicInfo', type: 'group', label: '基本信息', bordered: true, children: [ { name: 'projectName', // 完整路径: basicInfo.projectName type: 'input', label: '项目名称', rules: [{ required: true, message: '项目名称为必填项' }] }, { name: 'projectType', // 完整路径: basicInfo.projectType type: 'select', label: '项目类型', options: [ { label: 'Web应用', value: 'web' }, { label: '移动应用', value: 'mobile' } ] } ] } ] } ``` ### 提示功能使用 ```typescript // 1. 简单文本提示 const schema: SimpleFormSchema = { fields: [ { name: 'username', type: 'input', label: '用户名', tooltip: '用户名用于登录系统,建议使用字母、数字和下划线的组合', rules: [{ required: true, message: '用户名为必填项' }] } ] } // 2. 高级配置提示 const schema: SimpleFormSchema = { fields: [ { name: 'projectName', type: 'input', label: '项目名称', tooltip: { title: '项目名称将用于系统标识和显示,建议使用简洁明了的名称', placement: 'right', color: '#52c41a', overlayClassName: 'custom-tooltip-success' }, rules: [{ required: true, message: '项目名称为必填项' }] } ] } ``` ### 自定义组件使用 ```typescript // 1. 全局注册自定义组件 import { globalComponentRegistry } from '@chl1860/dynamic-form-vue3' import MyCustomComponent from './MyCustomComponent.vue' globalComponentRegistry.register('my-custom', MyCustomComponent) // 2. 在 schema 中使用 const schema: SimpleFormSchema = { fields: [ { name: 'rating', type: 'my-custom', // 使用注册的组件类型 label: '评分', componentProps: { maxStars: 5, showText: true, textLabels: ['很差', '较差', '一般', '较好', '很好'] } } ] } // 3. 直接使用组件对象 import CustomRatingField from '@chl1860/dynamic-form-vue3' const schema: SimpleFormSchema = { fields: [ { name: 'rating', type: 'custom', label: '评分', component: CustomRatingField, // 直接指定组件 componentProps: { maxStars: 5, showText: true } } ] } ``` ### 异步功能(完整支持) ```typescript const schema: SimpleFormSchema = { // 异步初始化数据 asyncInitializer: async () => { const response = await fetch('/api/user/profile') return await response.json() }, fields: [ { name: 'country', type: 'select', label: '国家', options: [ { label: '中国', value: 'china' }, { label: '美国', value: 'usa' } ] }, { name: 'province', type: 'select', label: '省份', placeholder: '请选择省份', linkage: { dependsOn: 'country', // 异步加载选项 asyncOptionsLoader: async (country, formData) => { const response = await fetch(`/api/provinces?country=${country}`) const provinces = await response.json() return provinces.map(p => ({ label: p.name, value: p.code })) } } }, { name: 'username', type: 'input', label: '用户名', placeholder: '请输入用户名', rules: [ { required: true, message: '用户名为必填项' }, { // 异步验证 asyncValidator: async (value, formData) => { if (!value) return true const response = await fetch('/api/validate-username', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: value }) }) const result = await response.json() return result.valid || '用户名已存在' } } ] } ] } ``` ## 📋 支持的字段类型 ### 内置字段类型 | 类型 | 说明 | 基础组件 | 联动支持 | 异步支持 | |------|------|----------|----------|----------| | `input` | 文本输入框 | `a-input` | ✅ | ✅ | | `select` | 下拉选择器 | `a-select` | ✅ | ✅ | | `radio` | 单选框组 | `a-radio-group` | ✅ | ✅ | | `number` | 数字输入框 | `a-input-number` | ✅ | ✅ | | `textarea` | 多行文本框 | `a-textarea` | ✅ | ✅ | | `checkbox` | 复选框 | `a-checkbox` | ✅ | ✅ | | `date` | 日期选择器 | `a-date-picker` | ✅ | ✅ | | `group` | 字段分组 | 自定义容器 | ✅ | ✅ | ### 自定义字段类型 | 组件名 | 说明 | 特性 | |--------|------|------| | `CustomRatingField` | 星级评分组件 | 自定义交互、文本标签 | | `CustomColorPicker` | 颜色选择器 | 颜色预览、十六进制输入 | | 自定义组件 | 支持任意Vue组件 | 完整联动和验证支持 | ## 🔧 核心 API ### 主要组件 | 组件名 | 说明 | 导入方式 | |--------|------|----------| | `SimpleForm` | 主表单组件 | `import { SimpleForm } from '@chl1860/dynamic-form-vue3'` | | `SimpleFormItem` | 表单项组件 | `import { SimpleFormItem } from '@chl1860/dynamic-form-vue3'` | ### 字段组件 | 组件名 | 说明 | 导入方式 | |--------|------|----------| | `SimpleInput` | 输入框组件 | `import { SimpleInput } from '@chl1860/dynamic-form-vue3'` | | `SimpleSelect` | 选择器组件 | `import { SimpleSelect } from '@chl1860/dynamic-form-vue3'` | | `SimpleRadio` | 单选框组件 | `import { SimpleRadio } from '@chl1860/dynamic-form-vue3'` | | `SimpleGroup` | 分组组件 | `import { SimpleGroup } from '@chl1860/dynamic-form-vue3'` | ### Composables & 工具 | 功能 | 说明 | 导入方式 | |------|------|----------| | `useSimpleForm` | 表单状态管理 Hook | `import { useSimpleForm } from '@chl1860/dynamic-form-vue3'` | | `globalComponentRegistry` | 全局组件注册器 | `import { globalComponentRegistry } from '@chl1860/dynamic-form-vue3'` | | `getByPath` / `setByPath` | 路径工具函数 | `import { getByPath, setByPath } from '@chl1860/dynamic-form-vue3'` | ### SimpleFormSchema | 属性 | 类型 | 说明 | |------|------|------| | fields | `SimpleFieldConfig[]` | 字段配置数组 | | asyncInitializer | `() => Promise<any>` | 异步数据初始化器 | | layout | `FormLayoutConfig` | 表单布局配置 | | submitButton | `ButtonConfig` | 提交按钮配置 | | extraButtons | `ButtonConfig[]` | 额外按钮配置 | ### SimpleFieldConfig | 属性 | 类型 | 说明 | |------|------|------| | name | `string` | 字段名称 | | type | `string` | 字段类型 | | label | `string` | 字段标签 | | placeholder | `string` | 占位符 | | required | `boolean` | 是否必填 | | options | `SelectOption[]` | 选项(select/radio) | | rules | `ValidationRule[]` | 验证规则 | | linkage | `FieldLinkage` | 联动配置 | | tooltip | `string \| TooltipConfig` | 提示信息配置 | | component | `Component` | 自定义组件 | | componentProps | `Record<string, any>` | 自定义组件属性 | | layout | `FieldLayoutConfig` | 字段布局配置 | ### FieldLinkage | 属性 | 类型 | 说明 | |------|------|------| | dependsOn | `string` | 依赖字段路径 | | optionsMap | `Record<string, SelectOption[]>` | 选项映射 | | asyncOptionsLoader | `(value, formData) => Promise<SelectOption[]>` | 异步选项加载器 | | visibleWhen | `(value, formData) => boolean` | 显示条件 | | disabledWhen | `(value, formData) => boolean` | 禁用条件 | | resetOnChange | `boolean` | 依赖变化时是否重置(默认true) | ### TooltipConfig | 属性 | 类型 | 说明 | |------|------|------| | title | `string` | 提示内容 | | placement | `string` | 提示位置(top/left/right/bottom等) | | color | `string` | 提示框颜色 | | overlayClassName | `string` | 自定义样式类名 | | overlayStyle | `Record<string, any>` | 自定义样式对象 | ### 事件 | 事件名 | 参数 | 说明 | |--------|------|------| | submit | `(data: any) => void` | 表单提交 | | change | `(data: any) => void` | 表单数据变化 | | validate | `(isValid: boolean, errors: Record<string, string>) => void` | 表单验证 | | reset | `() => void` | 表单重置 | ## 📁 项目结构 ```text dynamic-form/ ├── src/ # 源代码目录 │ ├── components/ # 组件目录 │ │ ├── SimpleForm.vue # 主表单组件 │ │ ├── SimpleFormItem.vue # 表单项组件 │ │ └── fields/ # 字段组件 │ │ ├── SimpleInput.vue # 输入框组件 │ │ ├── SimpleSelect.vue # 选择器组件 │ │ ├── SimpleRadio.vue # 单选框组件 │ │ ├── SimpleGroup.vue # 分组组件 │ │ ├── CustomRatingField.vue # 自定义评分组件 │ │ └── CustomColorPicker.vue # 自定义颜色选择器 │ ├── composables/ # Composition API │ │ └── useSimpleForm.ts # 表单状态管理 │ ├── types/ # 类型定义 │ │ └── form.ts # 表单相关类型 │ ├── utils/ # 工具函数 │ │ ├── pathHelper.ts # 路径处理工具 │ │ └── componentRegistry.ts # 组件注册工具 │ ├── examples/ # 示例代码 │ │ ├── SimpleLinkageExample.vue # 联动示例 │ │ ├── SimpleComplexExample.vue # 复杂表单示例 │ │ ├── CustomComponentExample.vue # 自定义组件示例 │ │ └── AsyncExample.vue # 异步功能示例 │ ├── App.vue # 主应用组件 │ ├── main.ts # 应用入口 │ ├── index.ts # 组件导出 │ └── shims-vue.d.ts # Vue 类型声明 ├── docs/ # 文档目录 │ ├── USAGE_GUIDE.md # 详细使用指南 │ ├── FEATURES_OVERVIEW.md # 功能特性概览 │ └── API_REFERENCE.md # API 参考文档 ├── public/ # 静态资源 ├── index.html # HTML 模板 ├── package.json # 项目配置 ├── tsconfig.json # TypeScript 配置 ├── vite.config.ts # Vite 配置 └── README.md # 项目说明 ```typescript ## 🏗️ 架构设计 ### 轻量级状态管理 Dynamic Form 采用**轻量级本地状态管理**架构,无需全局状态管理库: - **🎯 单一数据源**: 每个表单实例维护独立的响应式数据 - **🔄 精确联动**: 基于字段级监听器,避免不必要的重渲染 - **⚡ 高性能**: 无全局状态污染,组件间完全隔离 - **🧩 组合式 API**: 利用 Vue 3 原生能力,无额外依赖 ```typescript // 核心状态管理 - src/composables/useSimpleForm.ts const formData = reactive({ ...initialData }) // 表单数据 const errors = ref<Record<string, string>>({}) // 验证错误 const asyncStates = reactive<Record<string, any>>({}) // 异步状态 ``` ### 高级布局配置 Dynamic Form 支持灵活的响应式布局系统: ```typescript const schema: SimpleFormSchema = { // 表单级布局配置 layout: { type: 'grid', // 网格布局 columns: 2, // 每行显示2个字段 gutter: [16, 24], // 水平16px, 垂直24px间距 labelAlign: 'right', // 标签右对齐 // 响应式断点配置 breakpoints: { xs: 1, // 移动设备 (<576px): 1列 sm: 1, // 平板设备 (≥576px): 1列 md: 2, // 中等屏幕 (≥768px): 2列 lg: 3, // 大屏幕 (≥992px): 3列 xl: 4, // 超大屏幕 (≥1200px): 4列 xxl: 4 // 超超大屏幕 (≥1600px): 4列 } }, fields: [ { name: 'username', type: 'input', label: '用户名', // 字段级布局配置 layout: { span: 12, // 占据12个栅格 (24栅格系统中的一半) offset: 0, // 左侧无偏移 // 自定义标签和控件布局 labelCol: { span: 6 }, wrapperCol: { span: 18 } } }, { name: 'description', type: 'textarea', label: '描述', layout: { span: 24, // 占据整行 colSpan: 2 // 在网格布局中跨2列 } } ] } ``` ### 依赖最小化 项目仅依赖必要的核心库: - **Vue 3** - 响应式框架核心 (v3.4.0+) - **Ant Design Vue** - UI 组件库 (v4.0.0+) - **TypeScript** - 类型安全支持 **可选依赖**: - **Zod** - 高级验证库支持 - **@ant-design/colors** - 颜色系统 - **@ant-design/icons-vue** - 图标库 **不使用**:Vuex、Pinia 等全局状态管理库 ## 🚀 在线演示 运行 `npm run dev` 后访问 [http://localhost:3000](http://localhost:3000) 查看在线演示: - **🔗 联动示例** (`SimpleLinkageExample.vue`): 展示完美的字段联动功能 - 条件显示、选项联动、自动重置 - 用户类型、地区选择、车辆信息联动 - 自定义重置行为配置 - **🔧 复杂表单示例** (`SimpleComplexExample.vue`): 展示嵌套分组和验证 - 多层嵌套分组结构 - 复杂验证规则 - 响应式布局配置 - **🎨 自定义组件示例** (`CustomComponentExample.vue`): 展示自定义组件的使用 - 星级评分组件 (`CustomRatingField`) - 颜色选择器组件 (`CustomColorPicker`) - 组件注册系统演示 - **⚡ 异步功能示例** (`AsyncExample.vue`): 展示异步数据加载和验证 - 异步初始化表单数据 - 异步选项加载 - 异步验证器演示 ## 🎯 最佳实践 ### 1. 性能优化建议 ```typescript // ✅ 推荐:使用计算属性缓存复杂选项 const expensiveOptions = computed(() => { return generateComplexOptions() }) const schema: SimpleFormSchema = { fields: [ { name: 'category', type: 'select', label: '分类', options: expensiveOptions.value } ] } // ✅ 推荐:异步选项使用防抖 { name: 'search', type: 'select', label: '搜索', linkage: { dependsOn: 'query', asyncOptionsLoader: debounce(async (query) => { // 搜索API调用 }, 300) } } // ✅ 推荐:大表单使用分页或虚拟滚动 { type: 'group', name: 'page1', label: '基础信息', children: [ // 限制每页字段数量 ] } ``` ### 2. 类型安全最佳实践 ```typescript // ✅ 推荐:定义强类型的表单数据接口 interface UserFormData { username: string email: string profile: { firstName: string lastName: string } } // ✅ 推荐:使用类型化的 useSimpleForm const { formData, validateForm } = useSimpleForm<UserFormData>(schema, initialData) // ✅ 推荐:类型化的验证器 const emailValidator = (value: string, formData: UserFormData): boolean | string => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ return emailRegex.test(value) || '请输入有效的邮箱地址' } ``` ### 3. 错误处理和用户体验 ```typescript const schema: SimpleFormSchema = { fields: [ { name: 'username', type: 'input', label: '用户名', rules: [ { asyncValidator: async (value) => { try { const response = await fetch('/api/validate-username', { method: 'POST', body: JSON.stringify({ username: value }) }) if (!response.ok) { return '验证服务暂时不可用,请稍后重试' } const result = await response.json() return result.valid || result.message || '用户名不可用' } catch (error) { // 网络错误时的友好提示 return '网络连接异常,请检查网络后重试' } } } ] } ] } ``` ## 🔄 迁移指南 ### 从 v1.0 迁移到 v2.0 1. **组件名称变化**: ```vue <!-- v1.0 --> <DynamicForm :schema="schema" v-model="data" /> <!-- v2.0 --> <SimpleForm :schema="schema" v-model="data" /> ``` 2. **Schema 结构简化**: ```typescript // v1.0 - 复杂的 linkage 配置 const schema = { fields: [...], linkage: [ { name: 'rule1', trigger: ['field1'], condition: { field1: { $eq: 'value' } }, effects: [{ type: 'setOptions', targets: ['field2'], options: [...] }] } ] } // v2.0 - 简化的内联配置 const schema = { fields: [ { name: 'field2', linkage: { dependsOn: 'field1', optionsMap: { value: [...] } } } ] } ``` 3. **导入路径变化**: ```typescript // v1.0 import { DynamicForm, useFormState } from '@/index' // v2.0 import { SimpleForm, useSimpleForm } from '@chl1860/dynamic-form-vue3' ``` ## 🧪 开发和测试 ```bash # 开发 npm run dev # 启动开发服务器 (http://localhost:3000) npm run build # 构建生产版本 npm run preview # 预览生产版本 # 质量保障 npm run type-check # TypeScript 类型检查 npm run lint # ESLint 代码检查并修复 npm run test # 运行单元测试 npm run test:ui # 启动测试UI界面 # 发布 npm run prepublishOnly # 发布前自动构建 npm run build:types # 单独生成类型声明文件 ``` ### 构建产物 构建后的产物位于 `dist/` 目录: ```text dist/ ├── dynamic-form.es.js # ES模块版本 ├── dynamic-form.umd.js # UMD版本(用于CDN) ├── style.css # 样式文件(如果有) └── index.d.ts # TypeScript类型声明 ``` ### CDN 使用 ```html <!-- UMD版本 (通过CDN) --> <script src="https://unpkg.com/@chl1860/dynamic-form-vue3@latest/dist/dynamic-form.umd.js"></script> <script> const { SimpleForm } = DynamicForm // 使用组件... </script> ``` ## 🤝 贡献指南 1. Fork 本仓库 2. 创建特性分支 (`git checkout -b feature/AmazingFeature`) 3. 提交你的修改 (`git commit -m 'Add some AmazingFeature'`) 4. 推送到分支 (`git push origin feature/AmazingFeature`) 5. 打开一个 Pull Request ## 📊 版本历史 - **v2.0.0** (2025-01): 🎉 简化架构,彻底解决联动和验证问题 - **v1.0.0** (2024-12): 初始版本,复杂架构(已移除) ## 📄 许可证 此项目基于 MIT 许可证开源 - 查看 [LICENSE](LICENSE) 文件了解详情。 ## 🙏 致谢 - [Vue.js](https://vuejs.org/) - 渐进式 JavaScript 框架 - [Ant Design Vue](https://antdv.com/) - 企业级 UI 组件库 - [TypeScript](https://www.typescriptlang.org/) - JavaScript 的超集 - [Vite](https://vitejs.dev/) - 下一代前端构建工具 --- 🎯 **立即开始体验 v2.0 !**