@chl1860/dynamic-form-vue3
Version:
Vue3 + TypeScript + Ant Design Vue 动态表单组件
850 lines (713 loc) • 24.3 kB
Markdown
# 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 !**