@gopowerteam/form-render 0.0.94 → 1.0.1

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 (47) hide show
  1. package/README.md +1595 -0
  2. package/dist/cjs/config/provide.config.d.ts +0 -1
  3. package/dist/cjs/defines/define-form.d.ts +0 -1
  4. package/dist/cjs/form-items/cascader/index.d.ts +0 -1
  5. package/dist/cjs/form-items/currency/index.d.ts +0 -1
  6. package/dist/cjs/form-items/date/index.d.ts +0 -1
  7. package/dist/cjs/form-items/date-range/index.d.ts +0 -1
  8. package/dist/cjs/form-items/input/index.d.ts +0 -1
  9. package/dist/cjs/form-items/radio/index.d.ts +0 -1
  10. package/dist/cjs/form-items/render/index.d.ts +0 -1
  11. package/dist/cjs/form-items/select/index.d.ts +0 -1
  12. package/dist/cjs/form-items/switch/index.d.ts +0 -1
  13. package/dist/cjs/form-items/textarea/index.d.ts +0 -1
  14. package/dist/cjs/form-items/tree-select/index.d.ts +1 -2
  15. package/dist/cjs/form-render/form-collapsed-dialog.d.ts +6 -7
  16. package/dist/cjs/form-render/form-item-render.d.ts +3 -3
  17. package/dist/cjs/form-render/index.d.ts +126 -17
  18. package/dist/cjs/hooks/use-form.d.ts +2 -3
  19. package/dist/cjs/index.cjs +5 -1
  20. package/dist/cjs/index.d.ts +2 -3
  21. package/dist/cjs/install.d.ts +0 -1
  22. package/dist/cjs/interfaces/form-item-options.d.ts +0 -1
  23. package/dist/cjs/utils/create-form-source.d.ts +0 -1
  24. package/dist/es/config/provide.config.d.ts +0 -1
  25. package/dist/es/defines/define-form.d.ts +0 -1
  26. package/dist/es/form-items/cascader/index.d.ts +0 -1
  27. package/dist/es/form-items/currency/index.d.ts +0 -1
  28. package/dist/es/form-items/date/index.d.ts +0 -1
  29. package/dist/es/form-items/date-range/index.d.ts +0 -1
  30. package/dist/es/form-items/input/index.d.ts +0 -1
  31. package/dist/es/form-items/radio/index.d.ts +0 -1
  32. package/dist/es/form-items/render/index.d.ts +0 -1
  33. package/dist/es/form-items/select/index.d.ts +0 -1
  34. package/dist/es/form-items/switch/index.d.ts +0 -1
  35. package/dist/es/form-items/textarea/index.d.ts +0 -1
  36. package/dist/es/form-items/tree-select/index.d.ts +1 -2
  37. package/dist/es/form-render/form-collapsed-dialog.d.ts +6 -7
  38. package/dist/es/form-render/form-item-render.d.ts +3 -3
  39. package/dist/es/form-render/index.d.ts +126 -17
  40. package/dist/es/hooks/use-form.d.ts +2 -3
  41. package/dist/es/index.d.ts +2 -3
  42. package/dist/es/index.mjs +1603 -1600
  43. package/dist/es/install.d.ts +0 -1
  44. package/dist/es/interfaces/form-item-options.d.ts +0 -1
  45. package/dist/es/utils/create-form-source.d.ts +0 -1
  46. package/dist/style.css +1 -1
  47. package/package.json +31 -29
package/README.md ADDED
@@ -0,0 +1,1595 @@
1
+ # @gopowerteam/form-render
2
+
3
+ 基于 Vue 3 + Arco Design 的声明式表单渲染库,通过配置化的方式快速构建表单。
4
+
5
+ ## 目录
6
+
7
+ - [速查表](#速查表)
8
+ - [导出列表](#导出列表)
9
+ - [FormRender Props 速查](#formrender-props-速查)
10
+ - [FormRender Methods 速查](#formrender-methods-速查)
11
+ - [FormRender Events 速查](#formrender-events-速查)
12
+ - [字段类型速查](#字段类型速查)
13
+ - [FormItemOptions 速查](#formitemoptions-速查)
14
+ - [快速开始](#快速开始)
15
+ - [表单布局](#表单布局)
16
+ - [表单字段类型](#表单字段类型)
17
+ - [Input 输入框](#input-输入框)
18
+ - [Textarea 文本域](#textarea-文本域)
19
+ - [Select 选择器](#select-选择器)
20
+ - [TreeSelect 树选择](#treeselect-树选择)
21
+ - [Cascader 级联选择](#cascader-级联选择)
22
+ - [Radio 单选框](#radio-单选框)
23
+ - [Switch 开关](#switch-开关)
24
+ - [Date 日期选择](#date-日期选择)
25
+ - [DateRange 日期范围](#daterange-日期范围)
26
+ - [Currency 金额输入](#currency-金额输入)
27
+ - [Render 自定义渲染](#render-自定义渲染)
28
+ - [表单项配置](#表单项配置)
29
+ - [FormRender 组件](#formrender-组件)
30
+ - [useForm Hook](#useform-hook)
31
+ - [高级功能](#高级功能)
32
+ - [与 table-render 集成](#与-table-render-集成)
33
+ - [工具函数](#工具函数)
34
+ - [TypeScript 类型](#typescript-类型)
35
+ - [最佳实践](#最佳实践)
36
+
37
+ ## 速查表
38
+
39
+ ### 导出列表
40
+
41
+ | 导出名称 | 类型 | 说明 |
42
+ |----------|------|------|
43
+ | `defineForm` | 函数 | 定义表单配置,支持泛型 |
44
+ | `FormRender` | 组件 | 表单渲染组件 |
45
+ | `FormRenderInstance` | 类型 | 组件实例类型 |
46
+ | `useForm` | Hook | 通过 ref 获取表单实例 |
47
+ | `createFormSource` | 工具函数 | 手动创建表单数据源 |
48
+ | `FormRenderResolver` | 工具函数 | unplugin-vue-components 解析器 |
49
+ | `FormItemsOptions` | 类型 | 表单配置数组类型 |
50
+ | `FormItemOptions` | 类型 | 表单项配置类型 |
51
+ | `DataRecord` | 类型 | 通用数据记录类型 |
52
+
53
+ ### FormRender Props 速查
54
+
55
+ | 属性 | 类型 | 默认值 | 说明 |
56
+ |------|------|--------|------|
57
+ | `form` | `FormItemsOptions<T>` | - | **必填**,表单配置数组 |
58
+ | `value` | `Record<string, any>` | - | 初始表单数据(非响应式) |
59
+ | `modelValue` | `Record<string, any>` | - | 初始表单数据(支持 v-model) |
60
+ | `layout` | `'horizontal' \| 'vertical'` | `'horizontal'` | 表单布局模式 |
61
+ | `columns` | `number` | 自动计算 | 固定列数 |
62
+ | `minWidth` | `number` | `400` | 每列最小宽度(px) |
63
+ | `name` | `string` | - | 表单 name 属性 |
64
+ | `id` | `string` | - | 表单 id 属性 |
65
+ | `collapsedMode` | `'append' \| 'dialog'` | `'append'` | 折叠模式 |
66
+ | `collapsedDialogColumns` | `number` | `2` | 弹窗模式列数 |
67
+ | `showFormResult` | `boolean` | dialog 模式为 true | 显示已选条件标签 |
68
+ | `submitable` | `boolean` | `false` | 显示提交/取消按钮 |
69
+ | `searchable` | `boolean` | `false` | 显示搜索/重置按钮 |
70
+ | `resetable` | `boolean` | `true` | 显示重置按钮 |
71
+ | `footer` | `boolean` | `false` | 操作按钮底部样式 |
72
+ | `rowGap` | `number` | `6` | 行间距(px) |
73
+ | `colGap` | `number` | `12` | 列间距(px) |
74
+
75
+ ### FormRender Methods 速查
76
+
77
+ | 方法 | 签名 | 说明 |
78
+ |------|------|------|
79
+ | `formSource` | `Ref<DataRecord>` | 响应式表单数据 |
80
+ | `validate` | `() => Promise<void>` | 验证表单 |
81
+ | `reset` | `() => void` | 重置表单 |
82
+ | `updateFormField` | `(key: string, value: any) => void` | 更新单个字段 |
83
+ | `updateFormSource` | `(value: DataRecord) => void` | 更新整个表单 |
84
+
85
+ ### FormRender Events 速查
86
+
87
+ | 事件 | 参数 | 说明 |
88
+ |------|------|------|
89
+ | `submit` | `formSource: Record<string, any>` | 表单提交时触发 |
90
+ | `cancel` | - | 点击取消按钮时触发 |
91
+ | `update:model-value` | `formSource: Record<string, any>` | 表单数据更新时触发 |
92
+
93
+ ### 字段类型速查
94
+
95
+ | 类型 | 渲染方法 | 关键配置 | 常用场景 |
96
+ |------|----------|----------|----------|
97
+ | 输入框 | `r.input()` | `type`, `clearable`, `autoSubmit` | 文本/数字输入 |
98
+ | 文本域 | `r.textarea()` | `autosize`, `maxLength` | 多行文本 |
99
+ | 选择器 | `r.select()` | `options`, `multiple`, `cache` | 单选/多选下拉 |
100
+ | 树选择 | `r.treeSelect()` | `options`, `treeProps`, `fieldNames` | 树形数据选择 |
101
+ | 级联选择 | `r.cascader()` | `options`, `pathMode`, `multiple` | 级联数据选择 |
102
+ | 单选框 | `r.radio()` | `options`, `type: 'button'` | 单选按钮组 |
103
+ | 开关 | `r.switch()` | `openValue`, `closeValue`, `openLabel` | 布尔切换 |
104
+ | 日期 | `r.date()` | `type`, `showTime`, `shortcuts` | 日期/时间选择 |
105
+ | 日期范围 | `r.dateRange()` | `valueFormat`, `shortcuts` | 日期区间选择 |
106
+ | 金额 | `r.currency()` | `inputUnit`, `outputUnit`, `thousands` | 金额输入(支持单位转换) |
107
+ | 自定义 | `r.render()` | 自定义渲染函数 | 完全自定义字段 |
108
+
109
+ ### FormItemOptions 速查
110
+
111
+ | 属性 | 类型 | 默认值 | 说明 |
112
+ |------|------|--------|------|
113
+ | `key` | `string` | - | **必填**,字段键名 |
114
+ | `title` | `string` | - | **必填**,字段标签 |
115
+ | `default` | `any \| (() => any)` | - | 默认值,支持函数 |
116
+ | `render` | `FormItemRender<T>` | - | **必填**,渲染函数 |
117
+ | `rule` | `FieldRule \| FieldRule[]` | - | 验证规则 |
118
+ | `visiable` | `boolean \| ((data: T) => boolean)` | `true` | 条件显隐 |
119
+ | `collapsed` | `boolean` | `false` | 折叠状态下隐藏 |
120
+ | `span` | `number` | `1` | 网格跨列数 |
121
+ | `hideLabel` | `boolean` | `false` | 隐藏字段标签 |
122
+ | `group` | `string \| string[]` | - | 分组(dialog 模式) |
123
+ | `labelStyle` | `CSSProperties` | - | 标签样式 |
124
+ | `contentStyle` | `CSSProperties` | - | 内容样式 |
125
+ | `mode` | `'component' \| 'text'` | `'component'` | 显示模式 |
126
+
127
+ ---
128
+
129
+ ## 快速开始
130
+
131
+ ### 基础用法
132
+
133
+ ```vue
134
+ <script setup lang="ts">
135
+ import { defineForm, FormRender } from '@gopowerteam/form-render'
136
+
137
+ interface UserForm {
138
+ name: string
139
+ email: string
140
+ status: string
141
+ }
142
+
143
+ const form = defineForm<UserForm>([
144
+ {
145
+ key: 'name',
146
+ title: '姓名',
147
+ render: r => r.input({ placeholder: '请输入姓名', clearable: true }),
148
+ },
149
+ {
150
+ key: 'email',
151
+ title: '邮箱',
152
+ render: r => r.input({ placeholder: '请输入邮箱' }),
153
+ },
154
+ {
155
+ key: 'status',
156
+ title: '状态',
157
+ render: r => r.select({
158
+ options: new Map([['1', '启用'], ['0', '禁用']]),
159
+ clearable: true,
160
+ }),
161
+ },
162
+ ])
163
+
164
+ const handleSubmit = (data: UserForm) => {
165
+ console.log('表单数据:', data)
166
+ }
167
+ </script>
168
+
169
+ <template>
170
+ <FormRender
171
+ :form="form"
172
+ searchable
173
+ @submit="handleSubmit"
174
+ />
175
+ </template>
176
+ ```
177
+
178
+ ### defineForm 配置函数
179
+
180
+ `defineForm` 是一个类型安全的表单配置函数,支持泛型参数:
181
+
182
+ ```typescript
183
+ interface User {
184
+ name: string
185
+ age: number
186
+ }
187
+
188
+ // 使用泛型获得类型提示
189
+ const form = defineForm<User>([
190
+ {
191
+ key: 'name', // 自动推导为 'name' | 'age'
192
+ title: '姓名',
193
+ render: r => r.input(),
194
+ },
195
+ {
196
+ key: 'age',
197
+ title: '年龄',
198
+ render: r => r.input({ type: 'number' }),
199
+ },
200
+ ])
201
+ ```
202
+
203
+ ## 表单布局
204
+
205
+ ### 布局模式
206
+
207
+ 支持 `horizontal`(水平)和 `vertical`(垂直)两种布局:
208
+
209
+ ```vue
210
+ <!-- 水平布局(默认) -->
211
+ <FormRender :form="form" layout="horizontal" />
212
+
213
+ <!-- 垂直布局 -->
214
+ <FormRender :form="form" layout="vertical" />
215
+ ```
216
+
217
+ ### 响应式列布局
218
+
219
+ 表单会根据容器宽度自动计算列数:
220
+
221
+ ```vue
222
+ <!-- 自动计算列数,每列最小宽度 400px -->
223
+ <FormRender :form="form" :min-width="400" />
224
+
225
+ <!-- 固定 2 列 -->
226
+ <FormRender :form="form" :columns="2" />
227
+ ```
228
+
229
+ ### 折叠模式
230
+
231
+ 当表单字段较多时,可以使用折叠模式隐藏部分字段:
232
+
233
+ #### append 模式(默认)
234
+
235
+ 点击"展开/收起"按钮,在原位置展开更多字段:
236
+
237
+ ```vue
238
+ <FormRender
239
+ :form="form"
240
+ collapsed-mode="append"
241
+ searchable
242
+ />
243
+ ```
244
+
245
+ #### dialog 模式
246
+
247
+ 点击"高级搜索"按钮,在弹窗中显示更多字段:
248
+
249
+ ```vue
250
+ <FormRender
251
+ :form="form"
252
+ collapsed-mode="dialog"
253
+ :collapsed-dialog-columns="2"
254
+ searchable
255
+ />
256
+ ```
257
+
258
+ 配置字段为折叠状态:
259
+
260
+ ```typescript
261
+ const form = defineForm([
262
+ {
263
+ key: 'name',
264
+ title: '姓名',
265
+ // 此字段在折叠状态下隐藏
266
+ collapsed: true,
267
+ render: r => r.input(),
268
+ },
269
+ ])
270
+ ```
271
+
272
+ ### 表单间距
273
+
274
+ ```vue
275
+ <FormRender
276
+ :form="form"
277
+ :row-gap="6"
278
+ :col-gap="12"
279
+ />
280
+ ```
281
+
282
+ ## 表单字段类型
283
+
284
+ ### Input 输入框
285
+
286
+ ```typescript
287
+ interface RenderInputItemOptions {
288
+ placeholder?: string
289
+ clearable?: boolean
290
+ readonly?: boolean
291
+ autoSubmit?: boolean
292
+ type?: 'string' | 'number'
293
+ }
294
+ ```
295
+
296
+ **基础用法:**
297
+
298
+ ```typescript
299
+ const form = defineForm([
300
+ {
301
+ key: 'name',
302
+ title: '姓名',
303
+ render: r => r.input({ placeholder: '请输入姓名' }),
304
+ },
305
+ ])
306
+ ```
307
+
308
+ **数字输入:**
309
+
310
+ ```typescript
311
+ {
312
+ key: 'age',
313
+ title: '年龄',
314
+ render: r => r.input({ type: 'number', placeholder: '请输入年龄' }),
315
+ }
316
+ ```
317
+
318
+ **自动提交:**
319
+
320
+ ```typescript
321
+ {
322
+ key: 'keyword',
323
+ title: '关键词',
324
+ render: r => r.input({ autoSubmit: true, clearable: true }),
325
+ }
326
+ ```
327
+
328
+ ### Textarea 文本域
329
+
330
+ ```typescript
331
+ interface RenderTextareaItemOptions {
332
+ placeholder?: string
333
+ rows?: number
334
+ resize?: 'none' | 'both' | 'horizontal' | 'vertical'
335
+ autosize?: { minRows: number, maxRows: number }
336
+ maxLength?: number
337
+ }
338
+ ```
339
+
340
+ **基础用法:**
341
+
342
+ ```typescript
343
+ const form = defineForm([
344
+ {
345
+ key: 'description',
346
+ title: '描述',
347
+ render: r => r.textarea({ placeholder: '请输入描述' }),
348
+ },
349
+ ])
350
+ ```
351
+
352
+ **自适应高度:**
353
+
354
+ ```typescript
355
+ {
356
+ key: 'content',
357
+ title: '内容',
358
+ render: r => r.textarea({
359
+ autosize: { minRows: 3, maxRows: 6 },
360
+ maxLength: 500,
361
+ }),
362
+ }
363
+ ```
364
+
365
+ ### Select 选择器
366
+
367
+ ```typescript
368
+ interface RenderSelectItemOptions {
369
+ placeholder?: string
370
+ clearable?: boolean
371
+ searchable?: boolean
372
+ createable?: boolean
373
+ options: SelectOptions | (() => SelectOptions) | (() => Promise<SelectOptions>) | Ref<SelectOptions>
374
+ multiple?: boolean
375
+ maxTagCount?: number
376
+ default?: string | number | boolean
377
+ autoSubmit?: boolean
378
+ cache?: boolean
379
+ onChange?: (value) => void
380
+ }
381
+
382
+ type SelectOptions = Map<string | number | boolean, string>
383
+ ```
384
+
385
+ **静态选项:**
386
+
387
+ ```typescript
388
+ const form = defineForm([
389
+ {
390
+ key: 'status',
391
+ title: '状态',
392
+ render: r => r.select({
393
+ options: new Map([['1', '启用'], ['0', '禁用']]),
394
+ clearable: true,
395
+ }),
396
+ },
397
+ ])
398
+ ```
399
+
400
+ **多选:**
401
+
402
+ ```typescript
403
+ {
404
+ key: 'tags',
405
+ title: '标签',
406
+ render: r => r.select({
407
+ multiple: true,
408
+ options: new Map([['vue', 'Vue'], ['react', 'React'], ['angular', 'Angular']]),
409
+ }),
410
+ }
411
+ ```
412
+
413
+ **异步选项:**
414
+
415
+ ```typescript
416
+ const loadOptions = () => {
417
+ return Promise.resolve(new Map([['a', '选项A'], ['b', '选项B']]))
418
+ }
419
+
420
+ {
421
+ key: 'category',
422
+ title: '分类',
423
+ render: r => r.select({
424
+ options: loadOptions,
425
+ cache: true, // 开启缓存,避免重复请求
426
+ }),
427
+ }
428
+ ```
429
+
430
+ **响应式选项:**
431
+
432
+ ```typescript
433
+ import { ref } from 'vue'
434
+
435
+ const options = ref(new Map([['a', '选项A']]))
436
+
437
+ // 动态更新选项
438
+ setTimeout(() => {
439
+ options.value = new Map([['b', '选项B'], ['c', '选项C']])
440
+ }, 3000)
441
+
442
+ {
443
+ key: 'type',
444
+ title: '类型',
445
+ render: r => r.select({ options }),
446
+ }
447
+ ```
448
+
449
+ ### TreeSelect 树选择
450
+
451
+ ```typescript
452
+ interface RenderTreeSelectItemOptions {
453
+ placeholder?: string
454
+ clearable?: boolean
455
+ searchable?: boolean
456
+ fieldNames?: TreeFieldNames
457
+ options: TreeNodeData[] | (() => ...) | Ref<...>
458
+ multiple?: boolean
459
+ maxTagCount?: number
460
+ default?: string | number | boolean
461
+ autoSubmit?: boolean
462
+ cache?: boolean
463
+ treeProps?: Partial<TreeProps>
464
+ treeCheckStrictly?: boolean
465
+ treeCheckedStrategy?: 'all' | 'parent' | 'child'
466
+ slots?: Record<string, (data?: TreeNodeData) => JSX.Element>
467
+ }
468
+ ```
469
+
470
+ **基础用法:**
471
+
472
+ ```typescript
473
+ const form = defineForm([
474
+ {
475
+ key: 'department',
476
+ title: '部门',
477
+ render: r => r.treeSelect({
478
+ options: [
479
+ {
480
+ key: '1',
481
+ title: '技术部',
482
+ children: [
483
+ { key: '1-1', title: '前端组' },
484
+ { key: '1-2', title: '后端组' },
485
+ ],
486
+ },
487
+ {
488
+ key: '2',
489
+ title: '产品部',
490
+ },
491
+ ],
492
+ }),
493
+ },
494
+ ])
495
+ ```
496
+
497
+ **自定义字段映射:**
498
+
499
+ ```typescript
500
+ {
501
+ key: 'org',
502
+ title: '组织',
503
+ render: r => r.treeSelect({
504
+ fieldNames: { key: 'id', title: 'name', children: 'children' },
505
+ options: [{ id: '1', name: '组织A', children: [] }],
506
+ }),
507
+ }
508
+ ```
509
+
510
+ **使用插槽:**
511
+
512
+ ```typescript
513
+ {
514
+ key: 'category',
515
+ title: '分类',
516
+ render: r => r.treeSelect({
517
+ options: treeOptions,
518
+ slots: {
519
+ 'tree-slot-title': (data) => <div>{data.title}</div>,
520
+ 'footer': () => <div>自定义底部</div>,
521
+ },
522
+ }),
523
+ }
524
+ ```
525
+
526
+ ### Cascader 级联选择
527
+
528
+ ```typescript
529
+ interface RenderCascaderItemOptions {
530
+ placeholder?: string
531
+ clearable?: boolean
532
+ searchable?: boolean
533
+ pathMode?: boolean
534
+ options: CascaderOption[] | (() => ...) | Ref<...>
535
+ multiple?: boolean
536
+ maxTagCount?: number
537
+ default?: string | number | boolean
538
+ autoSubmit?: boolean
539
+ checkStrictly?: boolean
540
+ triggerProps?: TriggerProps
541
+ cache?: boolean
542
+ }
543
+ ```
544
+
545
+ **基础用法:**
546
+
547
+ ```typescript
548
+ const form = defineForm([
549
+ {
550
+ key: 'region',
551
+ title: '地区',
552
+ render: r => r.cascader({
553
+ options: [
554
+ {
555
+ value: 'beijing',
556
+ label: '北京',
557
+ children: [
558
+ { value: 'chaoyang', label: '朝阳区' },
559
+ { value: 'haidian', label: '海淀区' },
560
+ ],
561
+ },
562
+ {
563
+ value: 'shanghai',
564
+ label: '上海',
565
+ children: [
566
+ { value: 'pudong', label: '浦东新区' },
567
+ ],
568
+ },
569
+ ],
570
+ }),
571
+ },
572
+ ])
573
+ ```
574
+
575
+ **路径模式:**
576
+
577
+ ```typescript
578
+ {
579
+ key: 'address',
580
+ title: '地址',
581
+ render: r => r.cascader({
582
+ pathMode: true, // 返回路径数组 ['beijing', 'chaoyang']
583
+ multiple: true,
584
+ options: addressOptions,
585
+ }),
586
+ }
587
+ ```
588
+
589
+ **异步加载:**
590
+
591
+ ```typescript
592
+ const loadOptions = () => {
593
+ return Promise.resolve([
594
+ { value: '1', label: '选项1', children: [] },
595
+ ])
596
+ }
597
+
598
+ {
599
+ key: 'category',
600
+ title: '分类',
601
+ render: r => r.cascader({
602
+ options: loadOptions,
603
+ cache: true,
604
+ }),
605
+ }
606
+ ```
607
+
608
+ ### Radio 单选框
609
+
610
+ ```typescript
611
+ interface RenderRadioItemOptions {
612
+ options: RadioOptions | (() => RadioOptions) | Ref<RadioOptions>
613
+ type?: 'radio' | 'button'
614
+ size?: 'mini' | 'small' | 'medium' | 'large'
615
+ default?: string | number
616
+ autoSubmit?: boolean
617
+ cache?: boolean
618
+ onChange?: (value: string | number) => void
619
+ }
620
+
621
+ type RadioOptions = Map<string | number, string>
622
+ ```
623
+
624
+ **基础用法:**
625
+
626
+ ```typescript
627
+ const form = defineForm([
628
+ {
629
+ key: 'gender',
630
+ title: '性别',
631
+ render: r => r.radio({
632
+ options: new Map([['male', '男'], ['female', '女']]),
633
+ }),
634
+ },
635
+ ])
636
+ ```
637
+
638
+ **按钮样式:**
639
+
640
+ ```typescript
641
+ {
642
+ key: 'status',
643
+ title: '状态',
644
+ render: r => r.radio({
645
+ type: 'button',
646
+ size: 'large',
647
+ options: new Map([['1', '启用'], ['0', '禁用']]),
648
+ autoSubmit: true,
649
+ }),
650
+ }
651
+ ```
652
+
653
+ ### Switch 开关
654
+
655
+ ```typescript
656
+ interface RenderSwitchItemOptions {
657
+ size?: 'small' | 'medium'
658
+ openLabel?: string // 默认: '是'
659
+ closeLabel?: string // 默认: '否'
660
+ openValue?: string | number | boolean // 默认: true
661
+ closeValue?: string | number | boolean // 默认: false
662
+ default?: string | number | boolean
663
+ autoSubmit?: boolean
664
+ }
665
+ ```
666
+
667
+ **基础用法:**
668
+
669
+ ```typescript
670
+ const form = defineForm([
671
+ {
672
+ key: 'enabled',
673
+ title: '启用',
674
+ default: false,
675
+ render: r => r.switch(),
676
+ },
677
+ ])
678
+ ```
679
+
680
+ **自定义值和标签:**
681
+
682
+ ```typescript
683
+ {
684
+ key: 'status',
685
+ title: '状态',
686
+ render: r => r.switch({
687
+ openLabel: '开启',
688
+ closeLabel: '关闭',
689
+ openValue: 1,
690
+ closeValue: 0,
691
+ }),
692
+ }
693
+ ```
694
+
695
+ ### Date 日期选择
696
+
697
+ ```typescript
698
+ interface RenderDateItemOptions {
699
+ placeholder?: string
700
+ clearable?: boolean
701
+ disabledDate?: (value: string, date: Date) => boolean
702
+ type?: 'date' | 'year' | 'quarter' | 'month' | 'week'
703
+ valueFormat?: string
704
+ labelFormat?: string
705
+ shortcuts?: ShortcutType[]
706
+ showTime?: boolean
707
+ onChange?: (value: string | number | Date | undefined) => void
708
+ autoSubmit?: boolean
709
+ }
710
+ ```
711
+
712
+ **日期选择:**
713
+
714
+ ```typescript
715
+ const form = defineForm([
716
+ {
717
+ key: 'birthday',
718
+ title: '出生日期',
719
+ render: r => r.date({ type: 'date' }),
720
+ },
721
+ ])
722
+ ```
723
+
724
+ **日期时间选择:**
725
+
726
+ ```typescript
727
+ {
728
+ key: 'createdAt',
729
+ title: '创建时间',
730
+ render: r => r.date({ showTime: true }),
731
+ }
732
+ ```
733
+
734
+ **年/月/季度/周选择:**
735
+
736
+ ```typescript
737
+ {
738
+ key: 'year',
739
+ title: '年份',
740
+ render: r => r.date({ type: 'year' }),
741
+ }
742
+ {
743
+ key: 'month',
744
+ title: '月份',
745
+ render: r => r.date({ type: 'month' }),
746
+ }
747
+ {
748
+ key: 'quarter',
749
+ title: '季度',
750
+ render: r => r.date({ type: 'quarter' }),
751
+ }
752
+ {
753
+ key: 'week',
754
+ title: '周',
755
+ render: r => r.date({ type: 'week' }),
756
+ }
757
+ ```
758
+
759
+ **禁用日期:**
760
+
761
+ ```typescript
762
+ {
763
+ key: 'date',
764
+ title: '日期',
765
+ render: r => r.date({
766
+ disabledDate: (value, date) => {
767
+ return date > new Date() // 禁用未来日期
768
+ },
769
+ }),
770
+ }
771
+ ```
772
+
773
+ ### DateRange 日期范围
774
+
775
+ ```typescript
776
+ interface RenderDateRangeItemOptions {
777
+ placeholder?: string[]
778
+ clearable?: boolean
779
+ multiple?: boolean
780
+ type?: 'date' | 'year' | 'quarter' | 'month' | 'week'
781
+ valueFormat?: string // 默认: 'YYYY-MM-DD HH:mm:ss'
782
+ labelFormat?: string
783
+ disabledDate?: (value: string[], date: Date) => boolean
784
+ shortcuts?: ShortcutType[]
785
+ onChange?: (value: string[] | undefined) => void
786
+ autoSubmit?: boolean
787
+ }
788
+ ```
789
+
790
+ **基础用法:**
791
+
792
+ ```typescript
793
+ const form = defineForm([
794
+ {
795
+ key: 'dateRange',
796
+ title: '日期范围',
797
+ render: r => r.dateRange(),
798
+ },
799
+ ])
800
+ ```
801
+
802
+ **自定义格式:**
803
+
804
+ ```typescript
805
+ {
806
+ key: 'dateRange',
807
+ title: '日期范围',
808
+ render: r => r.dateRange({
809
+ valueFormat: 'YYYY-MM-DD',
810
+ labelFormat: 'YYYY年MM月DD日',
811
+ autoSubmit: true,
812
+ }),
813
+ }
814
+ ```
815
+
816
+ **自定义快捷选项:**
817
+
818
+ ```typescript
819
+ {
820
+ key: 'dateRange',
821
+ title: '日期范围',
822
+ render: r => r.dateRange({
823
+ shortcuts: [
824
+ {
825
+ label: '最近7天',
826
+ value: () => {
827
+ const end = new Date()
828
+ const start = new Date()
829
+ start.setTime(start.getTime() - 7 * 24 * 3600 * 1000)
830
+ return [start, end]
831
+ },
832
+ },
833
+ ],
834
+ }),
835
+ }
836
+ ```
837
+
838
+ ### Currency 金额输入
839
+
840
+ ```typescript
841
+ interface RenderCurrencyOptions {
842
+ placeholder?: string
843
+ clearable?: boolean
844
+ readonly?: boolean
845
+ prefix?: string | (() => JSX.Element)
846
+ suffix?: string | (() => JSX.Element)
847
+ thousands?: boolean // 默认: true
848
+ precision?: number
849
+ inputUnit?: '分' | '元' | '万' // 默认: '元'
850
+ outputUnit?: '分' | '元' | '万' // 默认: '元'
851
+ }
852
+ ```
853
+
854
+ **基础用法:**
855
+
856
+ ```typescript
857
+ const form = defineForm([
858
+ {
859
+ key: 'price',
860
+ title: '价格',
861
+ render: r => r.currency(),
862
+ },
863
+ ])
864
+ ```
865
+
866
+ **单位转换(分转元):**
867
+
868
+ ```typescript
869
+ {
870
+ key: 'amount',
871
+ title: '金额',
872
+ render: r => r.currency({
873
+ inputUnit: '元', // 输入时显示单位
874
+ outputUnit: '分', // 输出时转换为单位
875
+ precision: 2,
876
+ }),
877
+ }
878
+ ```
879
+
880
+ **千分位显示:**
881
+
882
+ ```typescript
883
+ {
884
+ key: 'total',
885
+ title: '总额',
886
+ render: r => r.currency({
887
+ thousands: true,
888
+ suffix: '元',
889
+ }),
890
+ }
891
+ ```
892
+
893
+ ### Render 自定义渲染
894
+
895
+ ```typescript
896
+ interface RenderInputItemOptions<T> {
897
+ (record: T, form?: FormItemOptions<T>): JSX.Element
898
+ }
899
+ ```
900
+
901
+ **自定义渲染:**
902
+
903
+ ```typescript
904
+ const form = defineForm([
905
+ {
906
+ key: 'custom',
907
+ title: '自定义字段',
908
+ render: r => r.render((data, form) => {
909
+ return (
910
+ <div class="flex gap-2">
911
+ <AInput v-model={data.custom} />
912
+ <AButton>按钮</AButton>
913
+ </div>
914
+ )
915
+ }),
916
+ },
917
+ ])
918
+ ```
919
+
920
+ ## 表单项配置
921
+
922
+ ### FormItemOptions 接口
923
+
924
+ ```typescript
925
+ interface FormItemOptions<T = Record<string, any>> {
926
+ key: keyof T | string // 字段键名
927
+ title: string // 字段标签
928
+ default?: any | (() => any) | (() => Promise<any>) // 默认值
929
+ collapsed?: boolean // 是否在折叠状态下隐藏
930
+ group?: string | string[] // 分组(用于 dialog 模式的 tab 分组)
931
+ visiable?: boolean | ((record: T) => boolean) // 条件显隐
932
+ span?: number // 网格跨列数
933
+ hideLabel?: boolean // 隐藏标签
934
+ labelStyle?: CSSProperties // 标签样式
935
+ contentStyle?: CSSProperties // 内容样式
936
+ rule?: FieldRule | FieldRule[] // 验证规则
937
+ mode?: 'component' | 'text' // 显示模式
938
+ render?: FormItemRender<T> // 渲染函数
939
+ }
940
+ ```
941
+
942
+ ### 配置项说明
943
+
944
+ | 属性 | 类型 | 默认值 | 说明 |
945
+ |------|------|--------|------|
946
+ | `key` | `string` | - | 字段键名,对应表单数据中的属性名 |
947
+ | `title` | `string` | - | 字段标签文本 |
948
+ | `default` | `any \| (() => any)` | - | 默认值,支持函数形式 |
949
+ | `collapsed` | `boolean` | `false` | 在折叠模式下隐藏此字段 |
950
+ | `group` | `string \| string[]` | - | 字段分组,用于弹窗模式的 tab 切换 |
951
+ | `visiable` | `boolean \| ((record) => boolean)` | `true` | 条件显隐,支持响应式函数 |
952
+ | `span` | `number` | `1` | 网格跨列数 |
953
+ | `hideLabel` | `boolean` | `false` | 隐藏字段标签 |
954
+ | `labelStyle` | `CSSProperties` | - | 标签自定义样式 |
955
+ | `contentStyle` | `CSSProperties` | - | 内容区域自定义样式 |
956
+ | `rule` | `FieldRule \| FieldRule[]` | - | 表单验证规则 |
957
+ | `mode` | `'component' \| 'text'` | `'component'` | 显示模式,text 模式用于只读展示 |
958
+ | `render` | `FormItemRender<T>` | - | 字段渲染函数 |
959
+
960
+ ### 条件显隐
961
+
962
+ ```typescript
963
+ const form = defineForm([
964
+ {
965
+ key: 'type',
966
+ title: '类型',
967
+ render: r => r.select({
968
+ options: new Map([['1', '类型A'], ['2', '类型B']]),
969
+ }),
970
+ },
971
+ {
972
+ key: 'extra',
973
+ title: '额外信息',
974
+ // 当 type 为 '1' 时才显示
975
+ visiable: (data) => data.type === '1',
976
+ render: r => r.input(),
977
+ },
978
+ ])
979
+ ```
980
+
981
+ ### 字段跨度
982
+
983
+ ```typescript
984
+ const form = defineForm([
985
+ {
986
+ key: 'title',
987
+ title: '标题',
988
+ span: 2, // 跨 2 列
989
+ render: r => r.input(),
990
+ },
991
+ {
992
+ key: 'content',
993
+ title: '内容',
994
+ span: 2, // 跨 2 列
995
+ render: r => r.textarea(),
996
+ },
997
+ ])
998
+ ```
999
+
1000
+ ### 字段分组
1001
+
1002
+ 用于 `dialog` 折叠模式下的 tab 分组:
1003
+
1004
+ ```typescript
1005
+ const form = defineForm([
1006
+ {
1007
+ key: 'name',
1008
+ title: '姓名',
1009
+ group: ['基本信息'], // 属于"基本信息"分组
1010
+ render: r => r.input(),
1011
+ },
1012
+ {
1013
+ key: 'address',
1014
+ title: '地址',
1015
+ group: ['联系方式'], // 属于"联系方式"分组
1016
+ render: r => r.input(),
1017
+ },
1018
+ ])
1019
+ ```
1020
+
1021
+ ## FormRender 组件
1022
+
1023
+ ### Props
1024
+
1025
+ | 属性 | 类型 | 默认值 | 说明 |
1026
+ |------|------|--------|------|
1027
+ | `form` | `FormItemsOptions<T>` | - | **必填**,表单配置数组 |
1028
+ | `value` | `Record<string, any>` | - | 初始表单数据(非响应式) |
1029
+ | `modelValue` | `Record<string, any>` | - | 初始表单数据(支持 v-model) |
1030
+ | `layout` | `'horizontal' \| 'vertical'` | `'horizontal'` | 表单布局 |
1031
+ | `columns` | `number` | 自动计算 | 固定列数 |
1032
+ | `minWidth` | `number` | `400` | 每列最小宽度(用于自动计算列数) |
1033
+ | `name` | `string` | - | 表单 name 属性 |
1034
+ | `id` | `string` | - | 表单 id 属性 |
1035
+ | `collapsedMode` | `'append' \| 'dialog'` | `'append'` | 折叠模式 |
1036
+ | `collapsedDialogColumns` | `number` | `2` | 弹窗模式的列数 |
1037
+ | `showFormResult` | `boolean` | dialog 模式为 `true` | 显示已选条件标签 |
1038
+ | `submitable` | `boolean` | `false` | 显示提交/取消按钮 |
1039
+ | `searchable` | `boolean` | `false` | 显示搜索/重置按钮 |
1040
+ | `resetable` | `boolean` | `true` | 显示重置按钮 |
1041
+ | `footer` | `boolean` | `false` | 操作按钮使用底部样式 |
1042
+ | `rowGap` | `number` | `6` | 行间距 |
1043
+ | `colGap` | `number` | `12` | 列间距 |
1044
+
1045
+ ### Events
1046
+
1047
+ | 事件 | 参数 | 说明 |
1048
+ |------|------|------|
1049
+ | `submit` | `formSource: Record<string, any>` | 表单提交时触发 |
1050
+ | `cancel` | - | 点击取消按钮时触发 |
1051
+ | `update:model-value` | `formSource: Record<string, any>` | 表单数据更新时触发 |
1052
+
1053
+ ### Expose Methods
1054
+
1055
+ | 方法 | 参数 | 返回值 | 说明 |
1056
+ |------|------|--------|------|
1057
+ | `formSource` | - | `Ref<DataRecord>` | 响应式表单数据 |
1058
+ | `updateFormField` | `(key: string, value: any)` | `void` | 更新单个字段 |
1059
+ | `updateFormSource` | `(value: DataRecord)` | `void` | 更新整个表单 |
1060
+ | `reset` | - | `void` | 重置表单 |
1061
+ | `validate` | - | `Promise` | 验证表单 |
1062
+
1063
+ ### Slots
1064
+
1065
+ | 插槽 | 说明 |
1066
+ |------|------|
1067
+ | `actions` | 自定义操作按钮 |
1068
+
1069
+ ### 使用示例
1070
+
1071
+ ```vue
1072
+ <script setup lang="ts">
1073
+ import { ref } from 'vue'
1074
+ import { defineForm, FormRender } from '@gopowerteam/form-render'
1075
+
1076
+ const formRef = ref()
1077
+ const formData = ref({ name: '初始值' })
1078
+
1079
+ const form = defineForm([
1080
+ { key: 'name', title: '姓名', render: r => r.input() },
1081
+ ])
1082
+
1083
+ const handleSubmit = (data) => {
1084
+ console.log('提交:', data)
1085
+ }
1086
+
1087
+ const handleValidate = async () => {
1088
+ try {
1089
+ await formRef.value?.validate()
1090
+ console.log('验证通过')
1091
+ } catch (errors) {
1092
+ console.log('验证失败:', errors)
1093
+ }
1094
+ }
1095
+
1096
+ const handleReset = () => {
1097
+ formRef.value?.reset()
1098
+ }
1099
+ </script>
1100
+
1101
+ <template>
1102
+ <FormRender
1103
+ ref="formRef"
1104
+ :form="form"
1105
+ v-model="formData"
1106
+ searchable
1107
+ @submit="handleSubmit"
1108
+ >
1109
+ <template #actions>
1110
+ <AButton @click="handleValidate">验证</AButton>
1111
+ <AButton @click="handleReset">重置</AButton>
1112
+ </template>
1113
+ </FormRender>
1114
+ </template>
1115
+ ```
1116
+
1117
+ ## useForm Hook
1118
+
1119
+ 通过 `useForm` 获取表单实例的引用:
1120
+
1121
+ ```typescript
1122
+ import { useForm } from '@gopowerteam/form-render'
1123
+
1124
+ const form = useForm('formRef')
1125
+
1126
+ // 访问表单数据
1127
+ console.log(form.value.formSource)
1128
+
1129
+ // 更新字段
1130
+ form.value.updateFormField('name', '新值')
1131
+
1132
+ // 重置表单
1133
+ form.value.reset()
1134
+
1135
+ // 验证表单
1136
+ await form.value.validate()
1137
+ ```
1138
+
1139
+ ### 完整示例
1140
+
1141
+ ```vue
1142
+ <script setup lang="ts">
1143
+ import { defineForm, FormRender, useForm } from '@gopowerteam/form-render'
1144
+
1145
+ const form = defineForm([
1146
+ { key: 'name', title: '姓名', render: r => r.input() },
1147
+ { key: 'status', title: '状态', render: r => r.select({ options: new Map() }) },
1148
+ ])
1149
+
1150
+ const formRef = useForm('myForm')
1151
+
1152
+ const updateName = () => {
1153
+ formRef.value.updateFormField('name', '新名字')
1154
+ }
1155
+
1156
+ const getFormData = () => {
1157
+ console.log(formRef.value.formSource)
1158
+ }
1159
+ </script>
1160
+
1161
+ <template>
1162
+ <FormRender ref="myForm" :form="form" searchable />
1163
+ <AButton @click="updateName">更新姓名</AButton>
1164
+ <AButton @click="getFormData">获取数据</AButton>
1165
+ </template>
1166
+ ```
1167
+
1168
+ ## 高级功能
1169
+
1170
+ ### 表单验证
1171
+
1172
+ 使用 Arco Design 的 `FieldRule` 进行验证:
1173
+
1174
+ ```typescript
1175
+ import type { FieldRule } from '@arco-design/web-vue'
1176
+
1177
+ const form = defineForm([
1178
+ {
1179
+ key: 'name',
1180
+ title: '姓名',
1181
+ rule: [{ required: true, message: '请输入姓名' }],
1182
+ render: r => r.input(),
1183
+ },
1184
+ {
1185
+ key: 'email',
1186
+ title: '邮箱',
1187
+ rule: [
1188
+ { required: true, message: '请输入邮箱' },
1189
+ { type: 'email', message: '邮箱格式不正确' },
1190
+ ],
1191
+ render: r => r.input(),
1192
+ },
1193
+ {
1194
+ key: 'password',
1195
+ title: '密码',
1196
+ rule: [
1197
+ { required: true, message: '请输入密码' },
1198
+ { minLength: 6, message: '密码至少6位' },
1199
+ ],
1200
+ render: r => r.input(),
1201
+ },
1202
+ ])
1203
+ ```
1204
+
1205
+ **自定义验证器:**
1206
+
1207
+ ```typescript
1208
+ {
1209
+ key: 'confirmPassword',
1210
+ title: '确认密码',
1211
+ rule: [
1212
+ {
1213
+ validator: (value, callback) => {
1214
+ if (value !== formData.password) {
1215
+ callback('两次密码不一致')
1216
+ } else {
1217
+ callback()
1218
+ }
1219
+ },
1220
+ },
1221
+ ],
1222
+ render: r => r.input(),
1223
+ }
1224
+ ```
1225
+
1226
+ ### 动态显隐
1227
+
1228
+ 基于表单数据动态控制字段显隐:
1229
+
1230
+ ```typescript
1231
+ const form = defineForm([
1232
+ {
1233
+ key: 'type',
1234
+ title: '类型',
1235
+ render: r => r.radio({
1236
+ type: 'button',
1237
+ options: new Map([['personal', '个人'], ['company', '企业']]),
1238
+ }),
1239
+ },
1240
+ {
1241
+ key: 'companyName',
1242
+ title: '公司名称',
1243
+ visiable: (data) => data.type === 'company',
1244
+ rule: [{ required: true, message: '请输入公司名称' }],
1245
+ render: r => r.input(),
1246
+ },
1247
+ {
1248
+ key: 'idCard',
1249
+ title: '身份证号',
1250
+ visiable: (data) => data.type === 'personal',
1251
+ render: r => r.input(),
1252
+ },
1253
+ ])
1254
+ ```
1255
+
1256
+ ### 异步选项加载
1257
+
1258
+ 支持 Promise 形式的异步选项:
1259
+
1260
+ ```typescript
1261
+ // 模拟 API 请求
1262
+ const fetchOptions = () => {
1263
+ return new Promise<Map<string, string>>((resolve) => {
1264
+ setTimeout(() => {
1265
+ resolve(new Map([
1266
+ ['1', '选项1'],
1267
+ ['2', '选项2'],
1268
+ ['3', '选项3'],
1269
+ ]))
1270
+ }, 1000)
1271
+ })
1272
+ }
1273
+
1274
+ const form = defineForm([
1275
+ {
1276
+ key: 'category',
1277
+ title: '分类',
1278
+ render: r => r.select({
1279
+ options: fetchOptions,
1280
+ cache: true, // 开启缓存
1281
+ }),
1282
+ },
1283
+ ])
1284
+ ```
1285
+
1286
+ ### 自动提交
1287
+
1288
+ 字段值变化后自动触发表单提交:
1289
+
1290
+ ```typescript
1291
+ const form = defineForm([
1292
+ {
1293
+ key: 'keyword',
1294
+ title: '关键词',
1295
+ render: r => r.input({ autoSubmit: true }),
1296
+ },
1297
+ {
1298
+ key: 'status',
1299
+ title: '状态',
1300
+ render: r => r.select({
1301
+ options: statusOptions,
1302
+ autoSubmit: true, // 选择后立即提交
1303
+ }),
1304
+ },
1305
+ ])
1306
+ ```
1307
+
1308
+ ### 搜索结果展示
1309
+
1310
+ 在 `dialog` 模式下,可以显示已选择的搜索条件:
1311
+
1312
+ ```vue
1313
+ <FormRender
1314
+ :form="form"
1315
+ collapsed-mode="dialog"
1316
+ :show-form-result="true"
1317
+ searchable
1318
+ @submit="handleSearch"
1319
+ />
1320
+ ```
1321
+
1322
+ ## 与 table-render 集成
1323
+
1324
+ `form-render` 可以与 `@gopowerteam/table-render` 配合使用,作为表格的搜索表单:
1325
+
1326
+ ```vue
1327
+ <script setup lang="ts">
1328
+ import { defineForm } from '@gopowerteam/form-render'
1329
+ import { defineColumns, defineTableLoad, useTable, TableRender } from '@gopowerteam/table-render'
1330
+
1331
+ // 定义搜索表单
1332
+ const form = defineForm([
1333
+ {
1334
+ key: 'keyword',
1335
+ title: '关键词',
1336
+ hideLabel: true,
1337
+ render: r => r.input({ placeholder: '请输入关键词', autoSubmit: true }),
1338
+ },
1339
+ {
1340
+ key: 'status',
1341
+ title: '状态',
1342
+ hideLabel: true,
1343
+ render: r => r.select({
1344
+ options: new Map([['1', '启用'], ['0', '禁用']]),
1345
+ clearable: true,
1346
+ autoSubmit: true,
1347
+ }),
1348
+ },
1349
+ {
1350
+ key: 'dateRange',
1351
+ title: '日期',
1352
+ hideLabel: true,
1353
+ collapsed: true,
1354
+ render: r => r.dateRange(),
1355
+ },
1356
+ ])
1357
+
1358
+ // 定义表格列
1359
+ const columns = defineColumns([
1360
+ { key: 'name', title: '名称' },
1361
+ { key: 'status', title: '状态' },
1362
+ { key: 'createdAt', title: '创建时间' },
1363
+ ])
1364
+
1365
+ // 定义数据加载
1366
+ const table = useTable('tableRef')
1367
+ const onTableLoad = defineTableLoad(async ({ form, update, page }) => {
1368
+ const res = await fetch('/api/list', {
1369
+ method: 'POST',
1370
+ body: JSON.stringify({ ...form, page }),
1371
+ })
1372
+ const data = await res.json()
1373
+ update(data.list)
1374
+ page.total = data.total
1375
+ })
1376
+ </script>
1377
+
1378
+ <template>
1379
+ <TableRender
1380
+ ref="tableRef"
1381
+ :form="form"
1382
+ :form-options="{ minWidth: 300 }"
1383
+ :columns="columns"
1384
+ :data-load="onTableLoad"
1385
+ collapsable
1386
+ pageable
1387
+ searchable
1388
+ />
1389
+ </template>
1390
+ ```
1391
+
1392
+ ### 通过 table 实例访问表单
1393
+
1394
+ ```typescript
1395
+ const table = useTable('tableRef')
1396
+
1397
+ // 获取表单数据
1398
+ console.log(table.value.formSource)
1399
+
1400
+ // 更新表单字段
1401
+ table.value.formInstance?.updateFormField('status', '1')
1402
+
1403
+ // 重置表单
1404
+ table.value.formInstance?.reset()
1405
+ ```
1406
+
1407
+ ## 工具函数
1408
+
1409
+ ### createFormSource
1410
+
1411
+ 手动创建表单数据源:
1412
+
1413
+ ```typescript
1414
+ import { createFormSource } from '@gopowerteam/form-render'
1415
+
1416
+ const form = defineForm([
1417
+ { key: 'name', title: '姓名', default: '默认名' },
1418
+ { key: 'age', title: '年龄', default: 18 },
1419
+ ])
1420
+
1421
+ // 创建响应式数据源
1422
+ const [formSource, updateFormSource] = createFormSource(form, { name: '初始名' })
1423
+
1424
+ console.log(formSource.value) // { name: '初始名', age: 18 }
1425
+
1426
+ // 更新数据
1427
+ updateFormSource({ name: '新名字', age: 20 })
1428
+ ```
1429
+
1430
+ ### FormRenderResolver
1431
+
1432
+ 用于 `unplugin-vue-components` 自动导入:
1433
+
1434
+ ```typescript
1435
+ // vite.config.ts
1436
+ import Components from 'unplugin-vue-components/vite'
1437
+ import { FormRenderResolver } from '@gopowerteam/form-render'
1438
+
1439
+ export default {
1440
+ plugins: [
1441
+ Components({
1442
+ resolvers: [FormRenderResolver()],
1443
+ }),
1444
+ ],
1445
+ }
1446
+ ```
1447
+
1448
+ ## TypeScript 类型
1449
+
1450
+ ### 导出的类型
1451
+
1452
+ ```typescript
1453
+ // 核心类型
1454
+ export type { DataRecord, DataProp } from './interfaces'
1455
+ export type { FormItemsOptions, FormItemOptions, FormItemRender, FormItemRenderFun, FormItemRenderReturn } from './interfaces'
1456
+
1457
+ // 组件实例
1458
+ export type { FormRenderInstance } from './form-render'
1459
+
1460
+ // 字段选项类型
1461
+ export type { RenderInputItemOptions } from './form-items/input'
1462
+ export type { RenderTextareaItemOptions } from './form-items/textarea'
1463
+ export type { RenderSelectItemOptions, SelectOptions } from './form-items/select'
1464
+ export type { RenderTreeSelectItemOptions } from './form-items/tree-select'
1465
+ export type { RenderCascaderItemOptions } from './form-items/cascader'
1466
+ export type { RenderRadioItemOptions, RadioOptions } from './form-items/radio'
1467
+ export type { RenderSwitchItemOptions } from './form-items/switch'
1468
+ export type { RenderDateItemOptions } from './form-items/date'
1469
+ export type { RenderDateRangeItemOptions } from './form-items/date-range'
1470
+ export type { RenderCurrencyOptions } from './form-items/currency'
1471
+ ```
1472
+
1473
+ ### 泛型使用
1474
+
1475
+ ```typescript
1476
+ interface UserFormData {
1477
+ name: string
1478
+ email: string
1479
+ age: number
1480
+ }
1481
+
1482
+ // 使用泛型获得完整的类型提示
1483
+ const form = defineForm<UserFormData>([
1484
+ {
1485
+ key: 'name', // 类型为 'name' | 'email' | 'age'
1486
+ title: '姓名',
1487
+ render: r => r.input(),
1488
+ },
1489
+ ])
1490
+
1491
+ // 组件 Props 类型
1492
+ const props = defineProps<{
1493
+ form: FormItemsOptions<UserFormData>
1494
+ }>()
1495
+
1496
+ // 实例类型
1497
+ const formRef = ref<FormRenderInstance>()
1498
+ ```
1499
+
1500
+ ## 最佳实践
1501
+
1502
+ ### 1. 使用 TypeScript 泛型
1503
+
1504
+ 始终使用泛型参数获得类型提示:
1505
+
1506
+ ```typescript
1507
+ // ✅ 推荐
1508
+ interface FormData { name: string }
1509
+ const form = defineForm<FormData>([...])
1510
+
1511
+ // ❌ 不推荐
1512
+ const form = defineForm([...])
1513
+ ```
1514
+
1515
+ ### 2. 提取选项配置
1516
+
1517
+ 将复杂的选项配置提取为常量:
1518
+
1519
+ ```typescript
1520
+ const STATUS_OPTIONS = new Map([
1521
+ ['1', '启用'],
1522
+ ['0', '禁用'],
1523
+ ])
1524
+
1525
+ const form = defineForm([
1526
+ {
1527
+ key: 'status',
1528
+ title: '状态',
1529
+ render: r => r.select({ options: STATUS_OPTIONS }),
1530
+ },
1531
+ ])
1532
+ ```
1533
+
1534
+ ### 3. 合理使用 autoSubmit
1535
+
1536
+ 在搜索表单中,对常用筛选条件开启自动提交:
1537
+
1538
+ ```typescript
1539
+ {
1540
+ key: 'status',
1541
+ title: '状态',
1542
+ render: r => r.select({
1543
+ options: STATUS_OPTIONS,
1544
+ autoSubmit: true, // 选择后立即搜索
1545
+ }),
1546
+ }
1547
+ ```
1548
+
1549
+ ### 4. 使用 hideLabel 优化搜索表单
1550
+
1551
+ 搜索表单中可以隐藏标签以节省空间:
1552
+
1553
+ ```typescript
1554
+ const form = defineForm([
1555
+ {
1556
+ key: 'keyword',
1557
+ title: '关键词',
1558
+ hideLabel: true,
1559
+ render: r => r.input({ placeholder: '请输入关键词' }),
1560
+ },
1561
+ ])
1562
+ ```
1563
+
1564
+ ### 5. 异步选项使用缓存
1565
+
1566
+ 避免重复请求相同的选项数据:
1567
+
1568
+ ```typescript
1569
+ {
1570
+ key: 'category',
1571
+ title: '分类',
1572
+ render: r => r.select({
1573
+ options: fetchCategories,
1574
+ cache: true, // 开启缓存
1575
+ }),
1576
+ }
1577
+ ```
1578
+
1579
+ ### 6. 合理使用折叠模式
1580
+
1581
+ 对于字段较多的表单,使用折叠模式提升用户体验:
1582
+
1583
+ ```typescript
1584
+ const form = defineForm([
1585
+ { key: 'keyword', title: '关键词', render: r => r.input() },
1586
+ { key: 'status', title: '状态', render: r => r.select({ options: STATUS_OPTIONS }) },
1587
+ // 以下字段默认折叠
1588
+ { key: 'dateRange', title: '日期范围', collapsed: true, render: r => r.dateRange() },
1589
+ { key: 'category', title: '分类', collapsed: true, render: r => r.select({ options: [] }) },
1590
+ ])
1591
+ ```
1592
+
1593
+ ## License
1594
+
1595
+ MIT