@ticatec/uniface-flexi-module 0.0.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 (136) hide show
  1. package/FLEXICRITERIASET_GUIDE.md +1559 -0
  2. package/FLEXICRITERIASET_GUIDE_CN.md +1133 -0
  3. package/FLEXIDATATABLE_GUIDE.md +1650 -0
  4. package/FLEXIDATATABLE_GUIDE_CN.md +1650 -0
  5. package/FLEXIFORM_GUIDE.md +1068 -0
  6. package/FLEXIFORM_GUIDE_CN.md +1068 -0
  7. package/FLEXI_CONTEXT_GUIDE_CN.md +172 -0
  8. package/MODULE_LOADER_CN.md +228 -0
  9. package/README.md +307 -0
  10. package/README_CN.md +51 -0
  11. package/SANDBOX_CN.md +201 -0
  12. package/dist/FlexiContext.d.ts +28 -0
  13. package/dist/FlexiContext.js +45 -0
  14. package/dist/ModuleLoader.d.ts +41 -0
  15. package/dist/ModuleLoader.js +55 -0
  16. package/dist/Sandbox.d.ts +33 -0
  17. package/dist/Sandbox.js +101 -0
  18. package/dist/criteria-panel/CriteriaFieldsPanel.svelte +26 -0
  19. package/dist/criteria-panel/CriteriaFieldsPanel.svelte.d.ts +22 -0
  20. package/dist/criteria-panel/components/CascadeSelectSearchField.svelte +10 -0
  21. package/dist/criteria-panel/components/CascadeSelectSearchField.svelte.d.ts +25 -0
  22. package/dist/criteria-panel/components/DateRangeField.svelte +11 -0
  23. package/dist/criteria-panel/components/DateRangeField.svelte.d.ts +25 -0
  24. package/dist/criteria-panel/components/DateSearchField.svelte +10 -0
  25. package/dist/criteria-panel/components/DateSearchField.svelte.d.ts +24 -0
  26. package/dist/criteria-panel/components/DateTimeSearchField.svelte +10 -0
  27. package/dist/criteria-panel/components/DateTimeSearchField.svelte.d.ts +24 -0
  28. package/dist/criteria-panel/components/InputOptionSelectSearchField.svelte +9 -0
  29. package/dist/criteria-panel/components/InputOptionSelectSearchField.svelte.d.ts +24 -0
  30. package/dist/criteria-panel/components/NumberRangeField.svelte +11 -0
  31. package/dist/criteria-panel/components/NumberRangeField.svelte.d.ts +25 -0
  32. package/dist/criteria-panel/components/NumberSearchField.svelte +9 -0
  33. package/dist/criteria-panel/components/NumberSearchField.svelte.d.ts +24 -0
  34. package/dist/criteria-panel/components/OptionMultiSelectSearchField.svelte +9 -0
  35. package/dist/criteria-panel/components/OptionMultiSelectSearchField.svelte.d.ts +24 -0
  36. package/dist/criteria-panel/components/OptionSelectSearchField.svelte +9 -0
  37. package/dist/criteria-panel/components/OptionSelectSearchField.svelte.d.ts +24 -0
  38. package/dist/criteria-panel/components/SearchField.svelte +14 -0
  39. package/dist/criteria-panel/components/SearchField.svelte.d.ts +33 -0
  40. package/dist/criteria-panel/components/TextSearchField.svelte +9 -0
  41. package/dist/criteria-panel/components/TextSearchField.svelte.d.ts +24 -0
  42. package/dist/criteria-panel/components/UnknownCriteriaField.svelte +9 -0
  43. package/dist/criteria-panel/components/UnknownCriteriaField.svelte.d.ts +24 -0
  44. package/dist/criteria-panel/index.d.ts +6 -0
  45. package/dist/criteria-panel/index.js +6 -0
  46. package/dist/criteria-panel/lib/CriteriaComponentBuilder.d.ts +19 -0
  47. package/dist/criteria-panel/lib/CriteriaComponentBuilder.js +31 -0
  48. package/dist/criteria-panel/lib/CriteriaFieldBuilder.d.ts +1 -0
  49. package/dist/criteria-panel/lib/CriteriaFieldBuilder.js +127 -0
  50. package/dist/criteria-panel/lib/FlexiCriteriaField.d.ts +38 -0
  51. package/dist/criteria-panel/lib/FlexiCriteriaField.js +31 -0
  52. package/dist/criteria-panel/lib/FlexiCriteriaSet.d.ts +24 -0
  53. package/dist/criteria-panel/lib/FlexiCriteriaSet.js +48 -0
  54. package/dist/flexi-datatable/FlexiDataTable.d.ts +111 -0
  55. package/dist/flexi-datatable/FlexiDataTable.js +90 -0
  56. package/dist/flexi-datatable/index.d.ts +2 -0
  57. package/dist/flexi-datatable/index.js +2 -0
  58. package/dist/flexi-form/FlexiCompound.d.ts +34 -0
  59. package/dist/flexi-form/FlexiCompound.js +84 -0
  60. package/dist/flexi-form/FlexiFormDialog.svelte +24 -0
  61. package/dist/flexi-form/FlexiFormDialog.svelte.d.ts +21 -0
  62. package/dist/flexi-form/FlexiFormPage.svelte +26 -0
  63. package/dist/flexi-form/FlexiFormPage.svelte.d.ts +25 -0
  64. package/dist/flexi-form/Schema.d.ts +6 -0
  65. package/dist/flexi-form/Schema.js +1 -0
  66. package/dist/flexi-form/components/BreakLine.svelte +1 -0
  67. package/dist/flexi-form/components/BreakLine.svelte.d.ts +26 -0
  68. package/dist/flexi-form/components/CardTitleBar.svelte +18 -0
  69. package/dist/flexi-form/components/CardTitleBar.svelte.d.ts +22 -0
  70. package/dist/flexi-form/components/CascadeOptionSelectField.svelte +13 -0
  71. package/dist/flexi-form/components/CascadeOptionSelectField.svelte.d.ts +24 -0
  72. package/dist/flexi-form/components/CellFieldBuilder.d.ts +1 -0
  73. package/dist/flexi-form/components/CellFieldBuilder.js +178 -0
  74. package/dist/flexi-form/components/DateField.svelte +12 -0
  75. package/dist/flexi-form/components/DateField.svelte.d.ts +24 -0
  76. package/dist/flexi-form/components/DateTimeField.svelte +13 -0
  77. package/dist/flexi-form/components/DateTimeField.svelte.d.ts +24 -0
  78. package/dist/flexi-form/components/InputOptionSelectField.svelte +13 -0
  79. package/dist/flexi-form/components/InputOptionSelectField.svelte.d.ts +24 -0
  80. package/dist/flexi-form/components/MemoField.svelte +12 -0
  81. package/dist/flexi-form/components/MemoField.svelte.d.ts +24 -0
  82. package/dist/flexi-form/components/NumberField.svelte +12 -0
  83. package/dist/flexi-form/components/NumberField.svelte.d.ts +24 -0
  84. package/dist/flexi-form/components/OptionsMultiSelectField.svelte +13 -0
  85. package/dist/flexi-form/components/OptionsMultiSelectField.svelte.d.ts +24 -0
  86. package/dist/flexi-form/components/OptionsSelectField.svelte +13 -0
  87. package/dist/flexi-form/components/OptionsSelectField.svelte.d.ts +24 -0
  88. package/dist/flexi-form/components/TextField.svelte +12 -0
  89. package/dist/flexi-form/components/TextField.svelte.d.ts +24 -0
  90. package/dist/flexi-form/components/UnitNumberField.svelte +12 -0
  91. package/dist/flexi-form/components/UnitNumberField.svelte.d.ts +24 -0
  92. package/dist/flexi-form/components/UnknownTypeField.svelte +5 -0
  93. package/dist/flexi-form/components/UnknownTypeField.svelte.d.ts +18 -0
  94. package/dist/flexi-form/containers/FlexiPanel.svelte +13 -0
  95. package/dist/flexi-form/containers/FlexiPanel.svelte.d.ts +33 -0
  96. package/dist/flexi-form/flexi_card/FlexiCard.d.ts +64 -0
  97. package/dist/flexi-form/flexi_card/FlexiCard.js +66 -0
  98. package/dist/flexi-form/flexi_card/FlexiCardPanel.svelte +57 -0
  99. package/dist/flexi-form/flexi_card/FlexiCardPanel.svelte.d.ts +22 -0
  100. package/dist/flexi-form/flexi_composite/FlexiComposite.d.ts +50 -0
  101. package/dist/flexi-form/flexi_composite/FlexiComposite.js +26 -0
  102. package/dist/flexi-form/flexi_composite/FlexiCompositePanel.svelte +42 -0
  103. package/dist/flexi-form/flexi_composite/FlexiCompositePanel.svelte.d.ts +25 -0
  104. package/dist/flexi-form/flexi_composite/README.md +50 -0
  105. package/dist/flexi-form/flexi_datasheet/FlexiDataSheet.d.ts +4 -0
  106. package/dist/flexi-form/flexi_datasheet/FlexiDataSheet.js +2 -0
  107. package/dist/flexi-form/flexi_field/FlexiField.d.ts +76 -0
  108. package/dist/flexi-form/flexi_field/FlexiField.js +128 -0
  109. package/dist/flexi-form/flexi_field/FlexiFieldCell.svelte +35 -0
  110. package/dist/flexi-form/flexi_field/FlexiFieldCell.svelte.d.ts +25 -0
  111. package/dist/flexi-form/flexi_field/UnknownField.d.ts +3 -0
  112. package/dist/flexi-form/flexi_field/UnknownField.js +3 -0
  113. package/dist/flexi-form/flexi_form/FlexiForm.d.ts +127 -0
  114. package/dist/flexi-form/flexi_form/FlexiForm.js +160 -0
  115. package/dist/flexi-form/flexi_form/FlexiFormPanel.svelte +57 -0
  116. package/dist/flexi-form/flexi_form/FlexiFormPanel.svelte.d.ts +25 -0
  117. package/dist/flexi-form/index.d.ts +11 -0
  118. package/dist/flexi-form/index.js +11 -0
  119. package/dist/flexi-form/lib/ComponentBuilder.d.ts +15 -0
  120. package/dist/flexi-form/lib/ComponentBuilder.js +31 -0
  121. package/dist/flexi-form/lib/index.d.ts +5 -0
  122. package/dist/flexi-form/lib/index.js +2 -0
  123. package/dist/flexi-form/lib/types.d.ts +7 -0
  124. package/dist/flexi-form/lib/types.js +6 -0
  125. package/dist/flexi-form/lib/utils.d.ts +10 -0
  126. package/dist/flexi-form/lib/utils.js +48 -0
  127. package/dist/i18n-res/i18nRes.d.ts +2 -0
  128. package/dist/i18n-res/i18nRes.js +8 -0
  129. package/dist/i18n-res/index.d.ts +2 -0
  130. package/dist/i18n-res/index.js +2 -0
  131. package/dist/index.d.ts +5 -0
  132. package/dist/index.js +5 -0
  133. package/dist/uniface-flexi-module.css +46 -0
  134. package/dist/utils.d.ts +4 -0
  135. package/dist/utils.js +8 -0
  136. package/package.json +135 -0
@@ -0,0 +1,1068 @@
1
+ # FlexiForm 完整使用指南
2
+
3
+ 在 Svelte 应用中构建动态、灵活表单的 FlexiForm 综合指南。
4
+
5
+ ## 目录
6
+
7
+ 1. [概述](#概述)
8
+ 2. [基础用法](#基础用法)
9
+ 3. [Schema 结构](#schema-结构)
10
+ 4. [字段类型](#字段类型)
11
+ 5. [布局和排列](#布局和排列)
12
+ 6. [动态表单生成](#动态表单生成)
13
+ 7. [事件和交互](#事件和交互)
14
+ 8. [扩展 FlexiForm](#扩展-flexiform)
15
+ 9. [高级模式](#高级模式)
16
+ 10. [最佳实践](#最佳实践)
17
+
18
+ ## 概述
19
+
20
+ FlexiForm 是一个强大的表单框架,可以从 JSON 配置生成动态表单。它支持多种布局模式、各种字段类型,并为自定义组件提供可扩展的架构。
21
+
22
+ ### 主要特性
23
+
24
+ - **动态基于 Schema**:从 JSON 配置生成表单
25
+ - **灵活布局**:支持 flex 和 grid 布局
26
+ - **可扩展字段类型**:内置字段 + 自定义字段支持
27
+ - **事件系统**:丰富的表单交互事件处理
28
+ - **类型安全**:完整的 TypeScript 支持
29
+ - **模块化架构**:即插即用组件
30
+
31
+ ## 基础用法
32
+
33
+ ### 1. 安装和设置
34
+
35
+ ```typescript
36
+ import { FlexiFormPage, registerFormFieldBuilder } from '@ticatec/uniface-flexi-form/flexi-form';
37
+ import '@ticatec/uniface-flexi-form/uniface-flexi-form.css';
38
+
39
+ // 注册所有字段构建器(必需)
40
+ registerFormFieldBuilder();
41
+ ```
42
+
43
+ ### 2. 简单表单示例
44
+
45
+ ```svelte
46
+ <!-- SimpleForm.svelte -->
47
+ <script lang="ts">
48
+ import { FlexiFormPage } from '@ticatec/uniface-flexi-form/flexi-form';
49
+
50
+ // 表单数据
51
+ let userData = {
52
+ name: '',
53
+ email: '',
54
+ age: null,
55
+ birthDate: null
56
+ };
57
+
58
+ // 表单配置
59
+ const userFormSchema = {
60
+ mode: 'flex',
61
+ arrangement: 'vertical',
62
+ variant: 'outlined',
63
+ elements: {
64
+ 'user-info': {
65
+ title: '用户信息',
66
+ fields: [
67
+ {
68
+ type: 'text-editor',
69
+ keyField: 'name',
70
+ name: 'name',
71
+ label: '全名',
72
+ required: true,
73
+ props: {
74
+ placeholder: '请输入您的全名'
75
+ }
76
+ },
77
+ {
78
+ type: 'text-editor',
79
+ keyField: 'email',
80
+ name: 'email',
81
+ label: '邮箱地址',
82
+ required: true,
83
+ props: {
84
+ type: 'email',
85
+ placeholder: '请输入您的邮箱'
86
+ }
87
+ },
88
+ {
89
+ type: 'number-editor',
90
+ keyField: 'age',
91
+ name: 'age',
92
+ label: '年龄',
93
+ props: {
94
+ min: 0,
95
+ max: 120
96
+ }
97
+ },
98
+ {
99
+ type: 'date-picker',
100
+ keyField: 'birthDate',
101
+ name: 'birthDate',
102
+ label: '出生日期'
103
+ }
104
+ ]
105
+ }
106
+ }
107
+ };
108
+
109
+ function handleFormChange(event) {
110
+ console.log('表单数据改变:', event.detail);
111
+ }
112
+
113
+ function handleSubmit() {
114
+ console.log('提交表单:', userData);
115
+ }
116
+ </script>
117
+
118
+ <FlexiFormPage
119
+ schema={userFormSchema}
120
+ bind:data={userData}
121
+ on:change={handleFormChange}
122
+ />
123
+
124
+ <button on:click={handleSubmit}>提交</button>
125
+ ```
126
+
127
+ ## Schema 结构
128
+
129
+ ### FlexiFormSchema
130
+
131
+ ```typescript
132
+ interface FlexiFormSchema {
133
+ mode?: 'flex' | 'grid'; // 布局模式
134
+ arrangement?: 'vertical' | 'horizontal'; // 布局方向
135
+ props?: any; // 布局特定属性
136
+ elements: FormElements; // 表单卡片/部分
137
+ label$style?: string; // 标签样式
138
+ variant?: 'filled' | 'outlined' | ''; // 字段变体
139
+ actions?: Array<FormActionSchema>; // 表单操作
140
+ style?: string; // 自定义 CSS
141
+ }
142
+
143
+ type FormElements = {
144
+ [cardKey: string]: FlexiCardSchema | FlexiBlockSchema;
145
+ }
146
+ ```
147
+
148
+ ### FlexiCardSchema
149
+
150
+ ```typescript
151
+ interface FlexiCardSchema {
152
+ title?: string; // 卡片标题
153
+ mode?: 'flex' | 'grid'; // 卡片布局模式
154
+ props?: any; // 布局属性
155
+ arrangement?: 'vertical' | 'horizontal'; // 布局方向
156
+ fields: Array<FlexiFieldSchema>; // 此卡片中的字段
157
+ readonly?: boolean; // 使卡片只读
158
+ disabled?: boolean; // 禁用卡片
159
+ variant?: 'filled' | 'outlined' | ''; // 字段变体
160
+ foldable?: boolean; // 允许折叠
161
+ actions?: Array<ActionSchema>; // 卡片操作
162
+ }
163
+ ```
164
+
165
+ ### FlexiCompositeSchema
166
+
167
+ ```typescript
168
+ interface FlexiCompositeSchema {
169
+ type?: 'block'; // 块类型标识符
170
+ title?: string; // 块标题
171
+ fields: Array<FlexiFieldSchema>; // 此组合块中的字段
172
+ readonly?: boolean; // 使块只读
173
+ disabled?: boolean; // 禁用块
174
+ label$style?: string; // 标签样式
175
+ variant?: string; // 字段变体
176
+ mode?: LayoutMode; // 布局模式
177
+ props?: any; // 布局属性
178
+ arrangement?: Arrangement; // 布局方向
179
+ }
180
+ ```
181
+
182
+ ### FlexiCompositeFieldSchema
183
+
184
+ ```typescript
185
+ interface FlexiCompositeFieldSchema {
186
+ type: "composite"; // 类型标识符
187
+ keyField?: string; // 嵌套对象的数据绑定路径
188
+ name: string; // 字段名称
189
+ composite: string; // 引用的组合配置名称
190
+ cell?: any; // 单元格属性
191
+ }
192
+ ```
193
+
194
+ ### FlexiFieldSchema
195
+
196
+ ```typescript
197
+ interface FlexiFieldSchema {
198
+ type: string; // 字段类型标识符
199
+ keyField: string; // 数据绑定路径
200
+ name: string; // 字段名称
201
+ label: string; // 显示标签
202
+ dictName?: string; // 选项字典
203
+ variant?: 'filled' | 'outlined' | ''; // 字段变体
204
+ readonly?: boolean; // 字段只读状态
205
+ disabled?: boolean; // 字段禁用状态
206
+ required?: boolean; // 字段必填状态
207
+ events?: Record<string, string>; // 事件处理器
208
+ cell?: any; // 单元格属性
209
+ props?: any; // 字段特定属性
210
+ }
211
+ ```
212
+
213
+ ## 字段类型
214
+
215
+ ### 内置字段类型
216
+
217
+ | 类型 | 描述 | 常用属性 |
218
+ |------|-------------|--------------|
219
+ | `text-editor` | 单行文本输入 | `placeholder`, `maxLength`, `pattern` |
220
+ | `memo-editor` | 多行文本区域 | `rows`, `placeholder`, `maxLength` |
221
+ | `number-editor` | 数字输入 | `min`, `max`, `step`, `precision` |
222
+ | `unit-number-editor` | 带单位数字输入 | `min`, `max`, `step`, `unit`, `precision` |
223
+ | `date-picker` | 日期选择 | `min`, `max`, `format` |
224
+ | `datetime-picker` | 日期时间选择 | `min`, `max`, `format` |
225
+ | `options-selector` | 单选下拉框 | `dictName`, `placeholder` |
226
+ | `options-multi-selector` | 多选下拉框 | `dictName`, `maxSelections` |
227
+ | `cascade-options-selector` | 级联选择 | `dictName`, `levels` |
228
+ | `input-options-selector` | 可搜索选择 | `getOptions`, `minLength` |
229
+ | `-` | 分隔线/换行符 | 视觉分隔符 |
230
+
231
+ ### 字段示例
232
+
233
+ ```typescript
234
+ // 带验证的文本字段
235
+ {
236
+ type: 'text-editor',
237
+ keyField: 'username',
238
+ name: 'username',
239
+ label: '用户名',
240
+ required: true,
241
+ props: {
242
+ placeholder: '请输入用户名',
243
+ maxLength: 50,
244
+ pattern: '^[a-zA-Z0-9_]+$'
245
+ }
246
+ }
247
+
248
+ // 带范围的数字字段
249
+ {
250
+ type: 'number-editor',
251
+ keyField: 'price',
252
+ name: 'price',
253
+ label: '价格',
254
+ props: {
255
+ min: 0,
256
+ max: 9999.99,
257
+ step: 0.01,
258
+ prefix: '¥'
259
+ }
260
+ }
261
+
262
+ // 带约束的日期字段
263
+ {
264
+ type: 'date-picker',
265
+ keyField: 'startDate',
266
+ name: 'startDate',
267
+ label: '开始日期',
268
+ props: {
269
+ min: '2024-01-01',
270
+ max: '2024-12-31',
271
+ format: 'YYYY-MM-DD'
272
+ }
273
+ }
274
+
275
+ // 带字典的选择字段
276
+ {
277
+ type: 'options-selector',
278
+ keyField: 'category',
279
+ name: 'category',
280
+ label: '分类',
281
+ dictName: 'product-categories',
282
+ required: true,
283
+ props: {
284
+ placeholder: '请选择分类'
285
+ }
286
+ }
287
+ ```
288
+
289
+ ## 布局和排列
290
+
291
+ ### Flex 布局
292
+
293
+ ```typescript
294
+ const flexFormSchema = {
295
+ mode: 'flex',
296
+ arrangement: 'vertical',
297
+ elements: {
298
+ 'main-card': {
299
+ mode: 'flex',
300
+ arrangement: 'horizontal', // 字段并排
301
+ fields: [
302
+ {
303
+ type: 'text-editor',
304
+ keyField: 'firstName',
305
+ name: 'firstName',
306
+ label: '名字',
307
+ cell: { flex: 1 } // 等宽
308
+ },
309
+ {
310
+ type: 'text-editor',
311
+ keyField: 'lastName',
312
+ name: 'lastName',
313
+ label: '姓氏',
314
+ cell: { flex: 1 } // 等宽
315
+ }
316
+ ]
317
+ }
318
+ }
319
+ };
320
+ ```
321
+
322
+ ### Grid 布局
323
+
324
+ ```typescript
325
+ const gridFormSchema = {
326
+ mode: 'grid',
327
+ props: {
328
+ columns: 3, // 3列网格
329
+ gap: '16px'
330
+ },
331
+ elements: {
332
+ 'contact-info': {
333
+ mode: 'grid',
334
+ props: {
335
+ columns: 2 // 此卡片2列网格
336
+ },
337
+ fields: [
338
+ {
339
+ type: 'text-editor',
340
+ keyField: 'email',
341
+ name: 'email',
342
+ label: '邮箱',
343
+ cell: { colspan: 2 } // 跨2列
344
+ },
345
+ {
346
+ type: 'text-editor',
347
+ keyField: 'phone',
348
+ name: 'phone',
349
+ label: '电话'
350
+ },
351
+ {
352
+ type: 'text-editor',
353
+ keyField: 'mobile',
354
+ name: 'mobile',
355
+ label: '手机'
356
+ }
357
+ ]
358
+ }
359
+ }
360
+ };
361
+ ```
362
+
363
+ ## 动态表单生成
364
+
365
+ ### 从 API 加载 Schema
366
+
367
+ ```typescript
368
+ class DynamicFormLoader {
369
+ async loadFormSchema(formId: string): Promise<FlexiFormSchema> {
370
+ const response = await fetch(`/api/forms/${formId}/schema`);
371
+ return await response.json();
372
+ }
373
+
374
+ async loadFormData(formId: string, recordId?: string): Promise<any> {
375
+ const url = recordId
376
+ ? `/api/forms/${formId}/data/${recordId}`
377
+ : `/api/forms/${formId}/data/new`;
378
+ const response = await fetch(url);
379
+ return await response.json();
380
+ }
381
+ }
382
+
383
+ // 在组件中使用
384
+ const loader = new DynamicFormLoader();
385
+ let formSchema = null;
386
+ let formData = {};
387
+
388
+ onMount(async () => {
389
+ formSchema = await loader.loadFormSchema('user-profile');
390
+ formData = await loader.loadFormData('user-profile', userId);
391
+ });
392
+ ```
393
+
394
+ ### Schema 构建器模式
395
+
396
+ ```typescript
397
+ class FormSchemaBuilder {
398
+ private schema: FlexiFormSchema;
399
+
400
+ constructor() {
401
+ this.schema = {
402
+ mode: 'flex',
403
+ arrangement: 'vertical',
404
+ variant: 'outlined',
405
+ elements: {}
406
+ };
407
+ }
408
+
409
+ addCard(key: string, title?: string): CardBuilder {
410
+ const cardBuilder = new CardBuilder(title);
411
+ this.schema.elements[key] = cardBuilder.getSchema();
412
+ return cardBuilder;
413
+ }
414
+
415
+ setLayout(mode: 'flex' | 'grid', arrangement?: 'vertical' | 'horizontal') {
416
+ this.schema.mode = mode;
417
+ this.schema.arrangement = arrangement;
418
+ return this;
419
+ }
420
+
421
+ build(): FlexiFormSchema {
422
+ return this.schema;
423
+ }
424
+ }
425
+
426
+ class CardBuilder {
427
+ private card: FlexiCardSchema;
428
+
429
+ constructor(title?: string) {
430
+ this.card = {
431
+ title,
432
+ fields: []
433
+ };
434
+ }
435
+
436
+ addTextField(keyField: string, label: string, options: any = {}) {
437
+ this.card.fields.push({
438
+ type: 'text-editor',
439
+ keyField,
440
+ name: keyField,
441
+ label,
442
+ ...options
443
+ });
444
+ return this;
445
+ }
446
+
447
+ addSelectField(keyField: string, label: string, dictName: string, options: any = {}) {
448
+ this.card.fields.push({
449
+ type: 'options-selector',
450
+ keyField,
451
+ name: keyField,
452
+ label,
453
+ dictName,
454
+ ...options
455
+ });
456
+ return this;
457
+ }
458
+
459
+ getSchema(): FlexiCardSchema {
460
+ return this.card;
461
+ }
462
+ }
463
+
464
+ // 使用方法
465
+ const schema = new FormSchemaBuilder()
466
+ .setLayout('flex', 'vertical')
467
+ .addCard('personal', '个人信息')
468
+ .addTextField('name', '全名', { required: true })
469
+ .addTextField('email', '邮箱', { required: true })
470
+ .addSelectField('country', '国家', 'countries')
471
+ .build();
472
+ ```
473
+
474
+ ## 事件和交互
475
+
476
+ ### 表单级事件
477
+
478
+ ```svelte
479
+ <script>
480
+ function handleFormChange(event) {
481
+ const { data, field, value } = event.detail;
482
+ console.log(`字段 ${field} 改变为:`, value);
483
+ console.log('完整表单数据:', data);
484
+ }
485
+
486
+ function handleFormSubmit(event) {
487
+ const { data, isValid } = event.detail;
488
+ if (isValid) {
489
+ submitForm(data);
490
+ } else {
491
+ console.error('表单验证失败');
492
+ }
493
+ }
494
+
495
+ function handleFormReset(event) {
496
+ console.log('表单已重置');
497
+ }
498
+ </script>
499
+
500
+ <FlexiFormPage
501
+ {schema}
502
+ bind:data={formData}
503
+ on:change={handleFormChange}
504
+ on:submit={handleFormSubmit}
505
+ on:reset={handleFormReset}
506
+ />
507
+ ```
508
+
509
+ ### 字段级事件
510
+
511
+ ```typescript
512
+ // 在 schema 定义中
513
+ {
514
+ type: 'text-editor',
515
+ keyField: 'email',
516
+ name: 'email',
517
+ label: '邮箱',
518
+ events: {
519
+ change: 'onEmailChange',
520
+ blur: 'onEmailBlur',
521
+ focus: 'onEmailFocus'
522
+ }
523
+ }
524
+
525
+ // 在表单实现类中
526
+ class UserForm extends FlexiForm {
527
+ onEmailChange(value: string) {
528
+ console.log('邮箱改变:', value);
529
+ // 触发邮箱验证
530
+ this.validateEmail(value);
531
+ }
532
+
533
+ onEmailBlur(event: FocusEvent) {
534
+ console.log('邮箱字段失去焦点');
535
+ // 检查邮箱重复
536
+ this.checkEmailUniqueness(event.target.value);
537
+ }
538
+
539
+ onEmailFocus(event: FocusEvent) {
540
+ console.log('邮箱字段获得焦点');
541
+ }
542
+ }
543
+ ```
544
+
545
+ ## 扩展 FlexiForm
546
+
547
+ ### 创建自定义字段类型
548
+
549
+ #### 1. 创建自定义字段组件
550
+
551
+ ```svelte
552
+ <!-- CustomRatingField.svelte -->
553
+ <script lang="ts">
554
+ import type FlexiField from "$lib/flexi-form/flexi_field/FlexiField";
555
+
556
+ export let field: FlexiField;
557
+ export let readonly: boolean;
558
+ export let disabled: boolean;
559
+ export let variant: "" | "outlined" | "filled";
560
+
561
+ $: props = field.props;
562
+ $: data = field.data;
563
+ $: events = field.events;
564
+
565
+ const maxRating = props.maxRating || 5;
566
+ let currentRating = data[field.keyField] || 0;
567
+
568
+ function setRating(rating: number) {
569
+ if (readonly || disabled) return;
570
+ currentRating = rating;
571
+ data[field.keyField] = rating;
572
+ events.change?.(rating);
573
+ }
574
+ </script>
575
+
576
+ <div class="rating-field" class:readonly class:disabled>
577
+ <label>{field.label}</label>
578
+ <div class="stars">
579
+ {#each Array(maxRating) as _, i}
580
+ <button
581
+ type="button"
582
+ class="star"
583
+ class:filled={i < currentRating}
584
+ {disabled}
585
+ on:click={() => setRating(i + 1)}
586
+ >
587
+
588
+ </button>
589
+ {/each}
590
+ </div>
591
+ </div>
592
+
593
+ <style>
594
+ .rating-field {
595
+ display: flex;
596
+ flex-direction: column;
597
+ gap: 8px;
598
+ }
599
+
600
+ .stars {
601
+ display: flex;
602
+ gap: 4px;
603
+ }
604
+
605
+ .star {
606
+ background: none;
607
+ border: none;
608
+ font-size: 24px;
609
+ cursor: pointer;
610
+ opacity: 0.3;
611
+ transition: opacity 0.2s;
612
+ }
613
+
614
+ .star.filled {
615
+ opacity: 1;
616
+ }
617
+
618
+ .star:hover:not(:disabled) {
619
+ opacity: 0.7;
620
+ }
621
+
622
+ .readonly .star, .disabled .star {
623
+ cursor: default;
624
+ }
625
+ </style>
626
+ ```
627
+
628
+ #### 2. 注册自定义字段类型
629
+
630
+ ```typescript
631
+ import { ComponentBuilder } from '@ticatec/uniface-flexi-form/flexi-form/lib/ComponentBuilder';
632
+ import CustomRatingField from './CustomRatingField.svelte';
633
+
634
+ // 创建构建器函数
635
+ const buildRatingField = (schema: FlexiFieldSchema, dictLoader: DictionaryLoader) => {
636
+ return {
637
+ component: CustomRatingField,
638
+ props: schema.props
639
+ };
640
+ };
641
+
642
+ // 注册自定义字段类型
643
+ const componentBuilder = ComponentBuilder.getInstance();
644
+ componentBuilder.register('rating-field', buildRatingField);
645
+
646
+ // 在 schema 中使用
647
+ const schemaWithRating = {
648
+ mode: 'flex',
649
+ elements: {
650
+ 'review-card': {
651
+ title: '产品评价',
652
+ fields: [
653
+ {
654
+ type: 'rating-field',
655
+ keyField: 'rating',
656
+ name: 'rating',
657
+ label: '产品评分',
658
+ props: {
659
+ maxRating: 5
660
+ }
661
+ },
662
+ {
663
+ type: 'memo-editor',
664
+ keyField: 'comment',
665
+ name: 'comment',
666
+ label: '评价意见'
667
+ }
668
+ ]
669
+ }
670
+ }
671
+ };
672
+ ```
673
+
674
+ ### 创建自定义表单类
675
+
676
+ ```typescript
677
+ import FlexiForm, { type FlexiFormSchema } from '@ticatec/uniface-flexi-form/flexi-form';
678
+
679
+ class UserProfileForm extends FlexiForm {
680
+ private userId: string;
681
+
682
+ constructor(userId: string, data: any, schema: FlexiFormSchema) {
683
+ super(data, schema);
684
+ this.userId = userId;
685
+ }
686
+
687
+ protected buildCards(): void {
688
+ // 自定义卡片构建逻辑
689
+ for (const [key, element] of Object.entries(this.elements)) {
690
+ if (element.type === 'block') {
691
+ // 处理块元素
692
+ } else {
693
+ // 使用自定义逻辑创建卡片
694
+ const card = new CustomUserCard(this, element as FlexiCardSchema);
695
+ this.cards.push(card);
696
+ }
697
+ }
698
+ }
699
+
700
+ getTitle(): string {
701
+ return `用户资料 - ${this.data.name || '新用户'}`;
702
+ }
703
+
704
+ // 自定义方法
705
+ async saveProfile(): Promise<void> {
706
+ try {
707
+ await fetch(`/api/users/${this.userId}`, {
708
+ method: 'PUT',
709
+ headers: { 'Content-Type': 'application/json' },
710
+ body: JSON.stringify(this.data)
711
+ });
712
+ console.log('资料保存成功');
713
+ } catch (error) {
714
+ console.error('保存资料失败:', error);
715
+ }
716
+ }
717
+
718
+ validateEmail(email: string): boolean {
719
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
720
+ return emailRegex.test(email);
721
+ }
722
+
723
+ // 事件处理器(从字段事件调用)
724
+ onEmailChange(value: string): void {
725
+ if (!this.validateEmail(value)) {
726
+ console.warn('邮箱格式无效');
727
+ }
728
+ }
729
+
730
+ onPhoneChange(value: string): void {
731
+ // 格式化电话号码
732
+ const formatted = this.formatPhoneNumber(value);
733
+ this.data.phone = formatted;
734
+ this.invalidate(); // 触发 UI 更新
735
+ }
736
+
737
+ private formatPhoneNumber(phone: string): string {
738
+ const cleaned = phone.replace(/\D/g, '');
739
+ if (cleaned.length === 11) {
740
+ return `${cleaned.slice(0, 3)}-${cleaned.slice(3, 7)}-${cleaned.slice(7)}`;
741
+ }
742
+ return phone;
743
+ }
744
+ }
745
+
746
+ // 使用方法
747
+ const userForm = new UserProfileForm(userId, userData, formSchema);
748
+ await userForm.initialize();
749
+ ```
750
+
751
+ ## 高级模式
752
+
753
+ ### 条件字段显示
754
+
755
+ ```typescript
756
+ class ConditionalFormCard extends FlexiCard {
757
+ protected createFromSchema(): void {
758
+ super.createFromSchema();
759
+
760
+ // 添加条件逻辑
761
+ this.setupConditionalFields();
762
+ }
763
+
764
+ private setupConditionalFields(): void {
765
+ const typeField = this.field['userType'];
766
+ const companyField = this.field['company'];
767
+
768
+ if (typeField && companyField) {
769
+ typeField.events.change = (value: string) => {
770
+ companyField.visibility = value === 'business'
771
+ ? Visibility.View
772
+ : Visibility.Hidden;
773
+ this.invalidate();
774
+ };
775
+ }
776
+ }
777
+ }
778
+ ```
779
+
780
+ ### 动态字段依赖
781
+
782
+ ```typescript
783
+ class DependentFieldsHandler {
784
+ private form: FlexiForm;
785
+ private dependencies: Map<string, string[]> = new Map();
786
+
787
+ constructor(form: FlexiForm) {
788
+ this.form = form;
789
+ this.setupDependencies();
790
+ }
791
+
792
+ setupDependencies(): void {
793
+ // 定义字段依赖关系
794
+ this.dependencies.set('country', ['state', 'city']);
795
+ this.dependencies.set('state', ['city']);
796
+
797
+ // 设置变化处理器
798
+ for (const [parentField, dependentFields] of this.dependencies) {
799
+ this.setupFieldHandler(parentField, dependentFields);
800
+ }
801
+ }
802
+
803
+ private setupFieldHandler(parentField: string, dependentFields: string[]): void {
804
+ const field = this.findField(parentField);
805
+ if (field) {
806
+ const originalHandler = field.events.change;
807
+ field.events.change = async (value: any) => {
808
+ // 调用原始处理器
809
+ originalHandler?.(value);
810
+
811
+ // 更新依赖字段
812
+ await this.updateDependentFields(parentField, value, dependentFields);
813
+ };
814
+ }
815
+ }
816
+
817
+ private async updateDependentFields(
818
+ parentField: string,
819
+ value: any,
820
+ dependentFields: string[]
821
+ ): Promise<void> {
822
+ for (const fieldName of dependentFields) {
823
+ const field = this.findField(fieldName);
824
+ if (field) {
825
+ // 清除当前值
826
+ this.form.data[fieldName] = null;
827
+
828
+ // 加载新选项
829
+ const options = await this.loadOptions(fieldName, { [parentField]: value });
830
+ field.props.options = options;
831
+ }
832
+ }
833
+
834
+ this.form.invalidate();
835
+ }
836
+
837
+ private async loadOptions(fieldName: string, context: any): Promise<any[]> {
838
+ const response = await fetch(`/api/options/${fieldName}`, {
839
+ method: 'POST',
840
+ headers: { 'Content-Type': 'application/json' },
841
+ body: JSON.stringify(context)
842
+ });
843
+ return await response.json();
844
+ }
845
+
846
+ private findField(fieldName: string): FlexiField | null {
847
+ for (const card of this.form.cards) {
848
+ if (card.field[fieldName]) {
849
+ return card.field[fieldName];
850
+ }
851
+ }
852
+ return null;
853
+ }
854
+ }
855
+ ```
856
+
857
+ ### 表单向导模式
858
+
859
+ ```typescript
860
+ class WizardFormManager {
861
+ private steps: FlexiFormSchema[] = [];
862
+ private currentStep = 0;
863
+ private stepData: any[] = [];
864
+
865
+ constructor(steps: FlexiFormSchema[]) {
866
+ this.steps = steps;
867
+ this.stepData = new Array(steps.length).fill({});
868
+ }
869
+
870
+ getCurrentSchema(): FlexiFormSchema {
871
+ return this.steps[this.currentStep];
872
+ }
873
+
874
+ getCurrentData(): any {
875
+ return this.stepData[this.currentStep];
876
+ }
877
+
878
+ nextStep(): boolean {
879
+ if (this.currentStep < this.steps.length - 1) {
880
+ this.currentStep++;
881
+ return true;
882
+ }
883
+ return false;
884
+ }
885
+
886
+ previousStep(): boolean {
887
+ if (this.currentStep > 0) {
888
+ this.currentStep--;
889
+ return true;
890
+ }
891
+ return false;
892
+ }
893
+
894
+ getFinalData(): any {
895
+ return this.stepData.reduce((acc, stepData) => ({
896
+ ...acc,
897
+ ...stepData
898
+ }), {});
899
+ }
900
+
901
+ isFirstStep(): boolean {
902
+ return this.currentStep === 0;
903
+ }
904
+
905
+ isLastStep(): boolean {
906
+ return this.currentStep === this.steps.length - 1;
907
+ }
908
+
909
+ getProgress(): number {
910
+ return ((this.currentStep + 1) / this.steps.length) * 100;
911
+ }
912
+ }
913
+
914
+ // 在 Svelte 组件中使用
915
+ let wizardManager = new WizardFormManager([
916
+ personalInfoSchema,
917
+ contactInfoSchema,
918
+ preferencesSchema
919
+ ]);
920
+
921
+ function handleNext() {
922
+ if (wizardManager.nextStep()) {
923
+ currentSchema = wizardManager.getCurrentSchema();
924
+ currentData = wizardManager.getCurrentData();
925
+ } else {
926
+ // 提交最终表单
927
+ submitWizardData(wizardManager.getFinalData());
928
+ }
929
+ }
930
+ ```
931
+
932
+ ## 最佳实践
933
+
934
+ ### 1. Schema 组织
935
+
936
+ ```typescript
937
+ // 好的:有组织的 schema 结构
938
+ const FORM_SCHEMAS = {
939
+ userProfile: {
940
+ mode: 'flex',
941
+ arrangement: 'vertical',
942
+ elements: {
943
+ personal: USER_PERSONAL_CARD,
944
+ contact: USER_CONTACT_CARD,
945
+ preferences: USER_PREFERENCES_CARD
946
+ }
947
+ }
948
+ };
949
+
950
+ const USER_PERSONAL_CARD: FlexiCardSchema = {
951
+ title: '个人信息',
952
+ fields: [
953
+ FIELDS.firstName,
954
+ FIELDS.lastName,
955
+ FIELDS.birthDate
956
+ ]
957
+ };
958
+
959
+ const FIELDS = {
960
+ firstName: {
961
+ type: 'text-editor',
962
+ keyField: 'firstName',
963
+ name: 'firstName',
964
+ label: '名字',
965
+ required: true,
966
+ props: { maxLength: 50 }
967
+ },
968
+ // ... 更多字段定义
969
+ };
970
+ ```
971
+
972
+ ### 2. 类型安全
973
+
974
+ ```typescript
975
+ // 为你的 schema 定义严格类型
976
+ interface UserFormData {
977
+ firstName: string;
978
+ lastName: string;
979
+ email: string;
980
+ birthDate: Date;
981
+ preferences: UserPreferences;
982
+ }
983
+
984
+ interface UserPreferences {
985
+ newsletter: boolean;
986
+ language: string;
987
+ timezone: string;
988
+ }
989
+
990
+ // 使用类型化表单创建
991
+ class TypedUserForm extends FlexiForm {
992
+ protected data: UserFormData;
993
+
994
+ constructor(data: UserFormData, schema: FlexiFormSchema) {
995
+ super(data, schema);
996
+ }
997
+
998
+ getData(): UserFormData {
999
+ return this.data;
1000
+ }
1001
+ }
1002
+ ```
1003
+
1004
+ ### 3. 错误处理
1005
+
1006
+ ```typescript
1007
+ class FormErrorHandler {
1008
+ static handleFormError(error: any, form: FlexiForm): void {
1009
+ if (error.validationErrors) {
1010
+ this.displayValidationErrors(error.validationErrors, form);
1011
+ } else if (error.networkError) {
1012
+ this.displayNetworkError(error.networkError);
1013
+ } else {
1014
+ this.displayGenericError(error);
1015
+ }
1016
+ }
1017
+
1018
+ private static displayValidationErrors(
1019
+ errors: Record<string, string>,
1020
+ form: FlexiForm
1021
+ ): void {
1022
+ for (const [fieldName, errorMessage] of Object.entries(errors)) {
1023
+ const field = this.findFieldInForm(fieldName, form);
1024
+ if (field) {
1025
+ field.error = errorMessage;
1026
+ }
1027
+ }
1028
+ form.invalidate();
1029
+ }
1030
+ }
1031
+ ```
1032
+
1033
+ ### 4. 性能优化
1034
+
1035
+ ```typescript
1036
+ // 延迟加载大型表单
1037
+ class LazyFormLoader {
1038
+ private formCache = new Map<string, FlexiFormSchema>();
1039
+
1040
+ async loadForm(formId: string): Promise<FlexiFormSchema> {
1041
+ if (this.formCache.has(formId)) {
1042
+ return this.formCache.get(formId)!;
1043
+ }
1044
+
1045
+ const schema = await fetch(`/api/forms/${formId}`).then(r => r.json());
1046
+ this.formCache.set(formId, schema);
1047
+ return schema;
1048
+ }
1049
+
1050
+ preloadForms(formIds: string[]): void {
1051
+ formIds.forEach(id => this.loadForm(id));
1052
+ }
1053
+ }
1054
+
1055
+ // 防抖表单变化
1056
+ function createDebouncedFormHandler(delay = 300) {
1057
+ let timeoutId: number;
1058
+
1059
+ return function(handler: Function) {
1060
+ return function(...args: any[]) {
1061
+ clearTimeout(timeoutId);
1062
+ timeoutId = setTimeout(() => handler(...args), delay);
1063
+ };
1064
+ };
1065
+ }
1066
+ ```
1067
+
1068
+ 这个综合指南提供了有效使用和扩展 FlexiForm 所需的一切。示例从基础用法逐步发展到高级模式,展示了如何使用 FlexiForm 的灵活架构构建复杂的表单系统。