@veltra/desktop 1.1.23 → 1.1.25

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 (53) hide show
  1. package/dist/components/action/action-group.vue.d.ts +2 -2
  2. package/dist/components/action/action.vue.d.ts +1 -1
  3. package/dist/components/auto-complete/auto-complete.vue.d.ts +1 -1
  4. package/dist/components/batch-edit/batch-edit.vue.d.ts +2 -2
  5. package/dist/components/cascade/cascade.vue.d.ts +4 -4
  6. package/dist/components/checkbox/checkbox-button.vue.d.ts +1 -1
  7. package/dist/components/checkbox-group/checkbox-group.vue.d.ts +1 -1
  8. package/dist/components/date-picker/date-picker.vue.d.ts +2 -2
  9. package/dist/components/date-range-picker/date-range-picker.vue.d.ts +2 -2
  10. package/dist/components/drawer/drawer.vue.d.ts +2 -2
  11. package/dist/components/empty/empty.vue.d.ts +1 -1
  12. package/dist/components/file-picker/file-picker.vue.d.ts +1 -1
  13. package/dist/components/file-viewer/file-viewer.vue.d.ts +1 -1
  14. package/dist/components/form/dynamic-form-model.d.ts +7 -5
  15. package/dist/components/form/dynamic-form-model.js +31 -12
  16. package/dist/components/form/dynamic-form-model.js.map +1 -1
  17. package/dist/components/form/form-model.d.ts +8 -8
  18. package/dist/components/form/form-model.js +17 -3
  19. package/dist/components/form/form-model.js.map +1 -1
  20. package/dist/components/form/helper.d.ts +6 -1
  21. package/dist/components/form/helper.js +7 -1
  22. package/dist/components/form/helper.js.map +1 -1
  23. package/dist/components/form/index.d.ts +2 -2
  24. package/dist/components/index.d.ts +1 -1
  25. package/dist/components/index.js +2 -1
  26. package/dist/components/input/input.vue.d.ts +3 -3
  27. package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
  28. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +5 -5
  29. package/dist/components/number/number.vue.d.ts +1 -1
  30. package/dist/components/number-input/number-input.vue.d.ts +3 -3
  31. package/dist/components/number-range-input/number-range-input.vue.d.ts +1 -1
  32. package/dist/components/password-input/password-input.vue.d.ts +1 -1
  33. package/dist/components/progress-nodes/progress-nodes.vue.d.ts +2 -2
  34. package/dist/components/radio-group/radio-group.vue.d.ts +1 -1
  35. package/dist/components/rich-text-editor/rich-text-editor.vue.d.ts +1 -1
  36. package/dist/components/select/select.vue.d.ts +2 -2
  37. package/dist/components/table/table.vue.d.ts +2 -2
  38. package/dist/components/tabs/tabs-horizontal.vue.d.ts +4 -4
  39. package/dist/components/tabs/tabs-vertical.vue.d.ts +4 -4
  40. package/dist/components/tabs/tabs.vue.d.ts +4 -4
  41. package/dist/components/textarea/textarea.vue.d.ts +3 -3
  42. package/dist/components/tree-select/tree-select.vue.d.ts +5 -5
  43. package/dist/index.d.ts +4 -4
  44. package/dist/index.js +2 -2
  45. package/dist/types/form.d.ts +12 -9
  46. package/dist/types/index.d.ts +2 -2
  47. package/package.json +6 -6
  48. package/src/components/form/__test__/form-model.test.ts +190 -0
  49. package/src/components/form/dynamic-form-model.ts +47 -19
  50. package/src/components/form/form-model.ts +40 -28
  51. package/src/components/form/helper.ts +9 -0
  52. package/src/components/form/index.ts +2 -1
  53. package/src/types/form.ts +29 -11
@@ -0,0 +1,190 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { DynamicFormModel } from '../dynamic-form-model'
4
+ import { FormModel } from '../form-model'
5
+ import { formField, nestField } from '../helper'
6
+
7
+ describe('FormModel with Nested Fields', () => {
8
+ it('should flatten fields and initialize data correctly', () => {
9
+ const model = new FormModel({
10
+ name: formField({ value: 'Alice' }),
11
+ contact: nestField({
12
+ email: formField({ value: 'alice@example.com' }),
13
+ phone: formField({ value: '12345678901' })
14
+ })
15
+ })
16
+
17
+ // Verify allKeys
18
+ expect(model.allKeys).toEqual(['name', 'contact.email', 'contact.phone'])
19
+
20
+ // Verify initialData and data
21
+ expect(model.data).toEqual({
22
+ name: 'Alice',
23
+ contact: { email: 'alice@example.com', phone: '12345678901' }
24
+ })
25
+ expect(model.initialData).toEqual({
26
+ name: 'Alice',
27
+ contact: { email: 'alice@example.com', phone: '12345678901' }
28
+ })
29
+ })
30
+
31
+ it('should validate flat and nested fields correctly', async () => {
32
+ const model = new FormModel({
33
+ name: formField({ value: '', required: 'Name is required' }),
34
+ contact: nestField({
35
+ email: formField({ value: 'invalid-email', preset: 'email' }),
36
+ phone: formField({ value: '12345678901', required: true })
37
+ })
38
+ })
39
+
40
+ // Expect initial validation to fail on name and contact.email
41
+ await expect(model.validate()).rejects.toBe(false)
42
+ expect(model.errors.get('name')).toEqual(['Name is required'])
43
+ expect(model.errors.get('contact.email')).toEqual(['邮箱格式不正确'])
44
+ expect(model.errors.get('contact.phone')).toBeUndefined()
45
+
46
+ // Fix name and contact.email, and trigger validation
47
+ model.data.name = 'Bob'
48
+ model.data.contact.email = 'bob@example.com'
49
+
50
+ // We need to wait for watch effects/nextTick if reactive triggers validation on change
51
+ // Since validate() is an async function, let's call it directly
52
+ const valid = await model.validate()
53
+ expect(valid).toBe(true)
54
+ expect(model.errors.size).toBe(0)
55
+ })
56
+
57
+ it('should set data and reset data correctly', () => {
58
+ const model = new FormModel({
59
+ name: formField({ value: 'Alice' }),
60
+ contact: nestField({
61
+ email: formField({ value: 'alice@example.com' }),
62
+ phone: formField({ value: '12345678901' })
63
+ })
64
+ })
65
+
66
+ // Update data via setData
67
+ model.setData({ name: 'Bob', contact: { email: 'bob@example.com', phone: '98765432109' } })
68
+
69
+ expect(model.data).toEqual({
70
+ name: 'Bob',
71
+ contact: { email: 'bob@example.com', phone: '98765432109' }
72
+ })
73
+
74
+ // Reset data
75
+ model.resetData()
76
+ expect(model.data).toEqual({
77
+ name: 'Alice',
78
+ contact: { email: 'alice@example.com', phone: '12345678901' }
79
+ })
80
+ })
81
+ })
82
+
83
+ describe('DynamicFormModel with Nested Fields', () => {
84
+ it('should flatten fields and initialize data correctly', () => {
85
+ const model = new DynamicFormModel({
86
+ name: formField({ value: 'Alice' }),
87
+ contact: nestField({
88
+ email: formField({ value: 'alice@example.com' }),
89
+ phone: formField({ value: '12345678901' })
90
+ })
91
+ })
92
+
93
+ // Verify allKeys
94
+ expect(model.allKeys).toEqual(['name', 'contact.email', 'contact.phone'])
95
+
96
+ // Verify initialData and data
97
+ expect(model.data).toEqual({
98
+ name: 'Alice',
99
+ contact: { email: 'alice@example.com', phone: '12345678901' }
100
+ })
101
+ expect(model.initialData).toEqual({
102
+ name: 'Alice',
103
+ contact: { email: 'alice@example.com', phone: '12345678901' }
104
+ })
105
+ })
106
+
107
+ it('should support dynamic add and delete on nested fields', () => {
108
+ const model = new DynamicFormModel({ name: formField({ value: 'Alice' }) })
109
+
110
+ expect(model.allKeys).toEqual(['name'])
111
+
112
+ // Dynamically add nested fields
113
+ model.add(
114
+ 'contact',
115
+ nestField({
116
+ email: formField({ value: 'alice@example.com' }),
117
+ phone: formField({ value: '12345678901' })
118
+ })
119
+ )
120
+
121
+ expect(model.allKeys).toEqual(['name', 'contact.email', 'contact.phone'])
122
+ expect(model.data).toEqual({
123
+ name: 'Alice',
124
+ contact: { email: 'alice@example.com', phone: '12345678901' }
125
+ })
126
+ expect(model.initialData).toEqual({
127
+ name: 'Alice',
128
+ contact: { email: 'alice@example.com', phone: '12345678901' }
129
+ })
130
+
131
+ // Dynamically delete nested fields
132
+ model.delete('contact')
133
+
134
+ expect(model.allKeys).toEqual(['name'])
135
+ expect(model.data.name).toBe('Alice')
136
+ expect(model.fields.contact).toBeUndefined()
137
+ })
138
+
139
+ it('should validate flat and nested fields correctly', async () => {
140
+ const model = new DynamicFormModel()
141
+
142
+ model.add('name', formField({ value: '', required: 'Name is required' }))
143
+ model.add(
144
+ 'contact',
145
+ nestField({
146
+ email: formField({ value: 'invalid-email', preset: 'email' }),
147
+ phone: formField({ value: '12345678901', required: true })
148
+ })
149
+ )
150
+
151
+ // Expect initial validation to fail on name and contact.email
152
+ await expect(model.validate()).resolves.toBe(false)
153
+ expect(model.errors.get('name')).toEqual(['Name is required'])
154
+ expect(model.errors.get('contact.email')).toEqual(['邮箱格式不正确'])
155
+ expect(model.errors.get('contact.phone')).toBeUndefined()
156
+
157
+ // Fix name and contact.email, and trigger validation
158
+ model.data.name = 'Bob'
159
+ model.data.contact.email = 'bob@example.com'
160
+
161
+ const valid = await model.validate()
162
+ expect(valid).toBe(true)
163
+ expect(model.errors.size).toBe(0)
164
+ })
165
+
166
+ it('should set data and reset data correctly for nested fields', () => {
167
+ const model = new DynamicFormModel({
168
+ name: formField({ value: 'Alice' }),
169
+ contact: nestField({
170
+ email: formField({ value: 'alice@example.com' }),
171
+ phone: formField({ value: '12345678901' })
172
+ })
173
+ })
174
+
175
+ // Update data via setData
176
+ model.setData({ name: 'Bob', contact: { email: 'bob@example.com', phone: '98765432109' } })
177
+
178
+ expect(model.data).toEqual({
179
+ name: 'Bob',
180
+ contact: { email: 'bob@example.com', phone: '98765432109' }
181
+ })
182
+
183
+ // Reset data
184
+ model.resetData()
185
+ expect(model.data).toEqual({
186
+ name: 'Alice',
187
+ contact: { email: 'alice@example.com', phone: '12345678901' }
188
+ })
189
+ })
190
+ })
@@ -2,7 +2,21 @@ import { o } from '@cat-kit/core'
2
2
  import { Validator } from '@veltra/utils'
3
3
  import { isReactive, reactive, shallowReactive, watch } from 'vue'
4
4
 
5
- import type { FormModelItem, DataSettingConfig } from '../../types'
5
+ import type { FormModelField, FormModelItem, DataSettingConfig } from '../../types'
6
+
7
+ function flattenFields(fields: Record<string, any>, prefix = ''): Record<string, FormModelItem> {
8
+ const result: Record<string, FormModelItem> = {}
9
+ for (const key in fields) {
10
+ const item = fields[key]
11
+ const fullPath = prefix ? `${prefix}.${key}` : key
12
+ if (item && typeof item === 'object' && '__isNested' in item && item.__isNested) {
13
+ Object.assign(result, flattenFields((item as any).fields, fullPath))
14
+ } else {
15
+ result[fullPath] = item
16
+ }
17
+ }
18
+ return result
19
+ }
6
20
 
7
21
  /**
8
22
  * 动态表单模型
@@ -25,10 +39,12 @@ export class DynamicFormModel {
25
39
  }
26
40
 
27
41
  /** 表单规则 */
28
- readonly fields: Record<string, FormModelItem> = shallowReactive(
29
- {} as Record<string, FormModelItem>
42
+ readonly fields: Record<string, FormModelField> = shallowReactive(
43
+ {} as Record<string, FormModelField>
30
44
  )
31
45
 
46
+ private flatFields: Record<string, FormModelItem>
47
+
32
48
  private validator: Validator
33
49
 
34
50
  /**
@@ -52,8 +68,17 @@ export class DynamicFormModel {
52
68
  return this._allKeys
53
69
  }
54
70
 
71
+ private updateFlatFields(): void {
72
+ for (const key in this.flatFields) {
73
+ delete this.flatFields[key]
74
+ }
75
+ const flatted = flattenFields(this.fields)
76
+ Object.assign(this.flatFields, flatted)
77
+ this.getAllKeys()
78
+ }
79
+
55
80
  private getAllKeys(): void {
56
- this._allKeys = Object.keys(this.fields)
81
+ this._allKeys = Object.keys(this.flatFields)
57
82
  }
58
83
 
59
84
  /** 初始数据 */
@@ -64,10 +89,10 @@ export class DynamicFormModel {
64
89
  */
65
90
  private readonly oldData: Record<string, any> = {}
66
91
 
67
- constructor(fields?: Record<string, FormModelItem>) {
92
+ constructor(fields?: Record<string, FormModelField>) {
93
+ this.flatFields = shallowReactive({} as Record<string, FormModelItem>)
94
+ this.validator = new Validator(this.flatFields)
68
95
  this.append(fields ?? {})
69
- this.validator = new Validator(this.fields)
70
- this.getAllKeys()
71
96
  this.watchData()
72
97
  }
73
98
 
@@ -94,9 +119,9 @@ export class DynamicFormModel {
94
119
  })
95
120
  }
96
121
 
97
- append(fields: Record<string, FormModelItem>): void {
122
+ append(fields: Record<string, FormModelField>): void {
98
123
  for (const key in fields) {
99
- this.add(key, fields[key] ?? {})
124
+ this.add(key, fields[key]!)
100
125
  }
101
126
  }
102
127
 
@@ -105,17 +130,20 @@ export class DynamicFormModel {
105
130
  * @param field 字段
106
131
  * @param item 字段配置项
107
132
  */
108
- add(field: string, item: FormModelItem): void {
133
+ add(field: string, item: FormModelField): void {
109
134
  this.fields[field] = item
110
- const { value } = item
111
- const v = typeof value === 'function' ? value() : value
112
- /** 这是有bug的 */
113
- if (v !== undefined) {
114
- this.initialData[field] = JSON.parse(JSON.stringify(v))
135
+ const flatItem = flattenFields({ [field]: item })
136
+ for (const key in flatItem) {
137
+ const fieldItem = flatItem[key]!
138
+ const { value } = fieldItem
139
+ const v = typeof value === 'function' ? value() : value
140
+ if (v !== undefined) {
141
+ o(this.initialData).set(key, JSON.parse(JSON.stringify(v)))
142
+ }
143
+ o(this.data).set(key, v)
144
+ o(this.oldData).set(key, v)
115
145
  }
116
- o(this.data).set(field, v)
117
- o(this.oldData).set(field, v)
118
- this.getAllKeys()
146
+ this.updateFlatFields()
119
147
  }
120
148
 
121
149
  /**
@@ -124,7 +152,7 @@ export class DynamicFormModel {
124
152
  */
125
153
  delete(field: string): void {
126
154
  delete this.fields[field]
127
- this.getAllKeys()
155
+ this.updateFlatFields()
128
156
  }
129
157
 
130
158
  /**
@@ -2,19 +2,30 @@ import { o } from '@cat-kit/core'
2
2
  import { middleProxy, Validator } from '@veltra/utils'
3
3
  import { nextTick, reactive, shallowReactive, watch, type Reactive } from 'vue'
4
4
 
5
- import type {
6
- FormModelItem,
7
- ModelData,
8
- ModelRules,
9
- DataSettingConfig,
10
- IFormModel
11
- } from '../../types'
5
+ import type { ModelData, DataSettingConfig, IFormModel, AllKeys, FormModelItem } from '../../types'
6
+
7
+ /**
8
+ * 递归展平字段逻辑
9
+ */
10
+ function flattenFields(fields: Record<string, any>, prefix = ''): Record<string, FormModelItem> {
11
+ const result: Record<string, FormModelItem> = {}
12
+ for (const key in fields) {
13
+ const item = fields[key]
14
+ const fullPath = prefix ? `${prefix}.${key}` : key
15
+ if (item && typeof item === 'object' && '__isNested' in item && item.__isNested) {
16
+ Object.assign(result, flattenFields((item as any).fields, fullPath))
17
+ } else {
18
+ result[fullPath] = item
19
+ }
20
+ }
21
+ return result
22
+ }
12
23
 
13
24
  /**
14
25
  * 表单模型
15
26
  */
16
27
  export class FormModel<
17
- Fields extends Record<string, FormModelItem> = Record<string, FormModelItem>
28
+ Fields extends Record<string, any> = Record<string, any>
18
29
  > implements IFormModel<Fields> {
19
30
  /** 表单数据 */
20
31
  data!: ModelData<Fields>
@@ -30,14 +41,14 @@ export class FormModel<
30
41
  * @description
31
42
  * 这个值会在表单组件渲染时由表单设置,因为只有真正渲染的组件才应该被校验
32
43
  */
33
- formKeys: Map<number, (keyof Fields)[]> = new Map()
44
+ formKeys: Map<number, AllKeys<Fields>[]> = new Map()
34
45
 
35
46
  /** 初始数据 */
36
47
  readonly initialData: ModelData<Fields>
37
48
 
38
- readonly errors: Map<keyof Fields, string[] | undefined> = shallowReactive(new Map())
49
+ readonly errors: Map<AllKeys<Fields>, string[] | undefined> = shallowReactive(new Map())
39
50
 
40
- private validator: Validator<ModelRules<Fields>>
51
+ private validator: Validator<Record<string, FormModelItem>>
41
52
 
42
53
  /**
43
54
  * 是否在表单值更新时校验
@@ -48,11 +59,12 @@ export class FormModel<
48
59
 
49
60
  constructor(fields: Fields) {
50
61
  this.fields = fields
62
+ const flatFields = flattenFields(fields)
51
63
  const rawData = {} as ModelData<Fields>
52
64
  const allKeys: string[] = []
53
65
 
54
- for (const key in fields) {
55
- const fieldItem = fields[key]!
66
+ for (const key in flatFields) {
67
+ const fieldItem = flatFields[key]!
56
68
  allKeys.push(key)
57
69
  const { value } = fieldItem
58
70
  let v = value
@@ -72,7 +84,7 @@ export class FormModel<
72
84
 
73
85
  this.setProxyData(reactive(rawData))
74
86
 
75
- this.validator = new Validator(this.fields)
87
+ this.validator = new Validator(flatFields)
76
88
 
77
89
  return shallowReactive(this)
78
90
  }
@@ -93,23 +105,23 @@ export class FormModel<
93
105
  this.validateOnFieldChange = true
94
106
  return
95
107
  }
96
- this.validate(fields)
108
+ this.validate(fields as any)
97
109
  }
98
110
  })
99
111
 
100
112
  this.data = data as ModelData<Fields>
101
113
  }
102
114
 
103
- private getValidateFields(fields?: keyof Fields | (keyof Fields)[]) {
115
+ private getValidateFields(fields?: AllKeys<Fields> | AllKeys<Fields>[]) {
104
116
  if (!fields) {
105
117
  if (this.formKeys.size) {
106
- let _fields: (keyof Fields)[] = []
118
+ let _fields: AllKeys<Fields>[] = []
107
119
  this.formKeys.forEach((fields) => {
108
120
  _fields = _fields.concat(fields)
109
121
  })
110
122
  return _fields
111
123
  } else {
112
- return this.allKeys
124
+ return this.allKeys as AllKeys<Fields>[]
113
125
  }
114
126
  }
115
127
 
@@ -124,23 +136,23 @@ export class FormModel<
124
136
  * @param fields 需要校验的字段, 不传则校验所有字段
125
137
  * @returns
126
138
  */
127
- async validate(fields?: keyof Fields | (keyof Fields)[]): Promise<boolean> {
139
+ async validate(fields?: AllKeys<Fields> | AllKeys<Fields>[]): Promise<boolean> {
128
140
  const { errors, validator, data } = this
129
141
 
130
- const results = await validator.validate(data, this.getValidateFields(fields))
142
+ const results = await validator.validate(data, this.getValidateFields(fields) as any)
131
143
 
132
144
  // 全量校验
133
145
  if (!fields) {
134
146
  errors.clear()
135
147
 
136
148
  for (const field in results) {
137
- errors.set(field, results[field])
149
+ errors.set(field as any, results[field])
138
150
  }
139
151
  }
140
152
  // 局部校验
141
153
  else {
142
154
  ~(Array.isArray(fields) ? fields : [fields]).forEach((field) => {
143
- const errs = results[field]
155
+ const errs = results[field as string]
144
156
  if (errs?.length) {
145
157
  errors.set(field, errs)
146
158
  } else {
@@ -165,12 +177,12 @@ export class FormModel<
165
177
  * 重置数据
166
178
  * @param fields 需要重置的字段
167
179
  */
168
- resetData(keys?: keyof Fields | (keyof Fields)[]): void {
180
+ resetData(keys?: AllKeys<Fields> | AllKeys<Fields>[]): void {
169
181
  if (typeof keys === 'string') {
170
182
  keys = [keys]
171
183
  } else if (Array.isArray(keys)) {
172
184
  } else {
173
- keys = this.allKeys
185
+ keys = this.allKeys as AllKeys<Fields>[]
174
186
  }
175
187
 
176
188
  this.clearValidate()
@@ -229,12 +241,12 @@ export class FormModel<
229
241
  * 监听值变更
230
242
  * @param cb 回调
231
243
  */
232
- onChange(cb: (field: keyof Fields, val: any) => void): void {
233
- this.modelChangeCallback.add(cb)
244
+ onChange(cb: (field: AllKeys<Fields>, val: any) => void): void {
245
+ this.modelChangeCallback.add(cb as any)
234
246
  }
235
247
 
236
248
  /** 关闭监听值变更 */
237
- offChange(cb: (field: keyof Fields, val: any) => void): void {
238
- this.modelChangeCallback.delete(cb)
249
+ offChange(cb: (field: AllKeys<Fields>, val: any) => void): void {
250
+ this.modelChangeCallback.delete(cb as any)
239
251
  }
240
252
  }
@@ -12,3 +12,12 @@ export function formField<Val = unknown>(item?: FormModelItem<Val>): FormModelIt
12
12
 
13
13
  return item
14
14
  }
15
+
16
+ export interface NestedFieldMarker<T extends Record<string, any> = Record<string, any>> {
17
+ __isNested: true
18
+ fields: T
19
+ }
20
+
21
+ export function nestField<T extends Record<string, any>>(fields: T): NestedFieldMarker<T> {
22
+ return { __isNested: true, fields }
23
+ }
@@ -1,6 +1,7 @@
1
1
  export { default as UForm } from './form.vue'
2
2
 
3
- export * from './helper'
3
+ export { formField, nestField } from './helper'
4
+ export type { NestedFieldMarker } from './helper'
4
5
 
5
6
  export { FormModel } from './form-model'
6
7
  export { DynamicFormModel } from './dynamic-form-model'
package/src/types/form.ts CHANGED
@@ -1,15 +1,35 @@
1
1
  import type { ComponentProps, DeconstructValue, ValidateRule } from '@veltra/utils'
2
2
  import type { ShallowRef } from 'vue'
3
3
 
4
+ import type { NestedFieldMarker } from '../components/form/helper'
5
+
4
6
  export interface FormModelItem<Val = any> extends ValidateRule {
5
7
  /** 模型值 */
6
8
  value?: Val
7
9
  }
8
10
 
9
- export type ModelData<Fields extends Record<string, FormModelItem>> = {
10
- [key in keyof Fields]: Fields[key]['value'] extends () => infer T ? T : Fields[key]['value']
11
+ export type FormModelField = FormModelItem | NestedFieldMarker
12
+
13
+ // 最终暴露的 ModelData 类型
14
+ export type ModelData<Fields extends Record<string, any>> = {
15
+ [key in keyof Fields]: Fields[key] extends NestedFieldMarker<infer SubFields>
16
+ ? ModelData<SubFields>
17
+ : Fields[key] extends FormModelItem
18
+ ? Fields[key]['value'] extends () => infer T
19
+ ? T
20
+ : Fields[key]['value']
21
+ : never
11
22
  }
12
23
 
24
+ // 获取所有的扁平路径 Union(如 'name' | 'contact.email')
25
+ export type AllKeys<Fields extends Record<string, any>> = {
26
+ [K in keyof Fields]: K extends string
27
+ ? Fields[K] extends NestedFieldMarker<infer SubFields>
28
+ ? `${K}.${AllKeys<SubFields>}`
29
+ : K
30
+ : never
31
+ }[keyof Fields]
32
+
13
33
  export type ModelRules<Fields extends Record<string, FormModelItem>> = {
14
34
  [key in keyof Fields]: Omit<Fields[key], 'value'>
15
35
  }
@@ -22,9 +42,7 @@ export interface DataSettingConfig {
22
42
  validate?: boolean
23
43
  }
24
44
 
25
- export type IFormModel<
26
- Fields extends Record<string, FormModelItem> = Record<string, FormModelItem>
27
- > = {
45
+ export type IFormModel<Fields extends Record<string, any> = Record<string, any>> = {
28
46
  /** 表单数据 */
29
47
  readonly data: ModelData<Fields>
30
48
  /** 字段校验规则 */
@@ -34,16 +52,16 @@ export type IFormModel<
34
52
  */
35
53
  readonly allKeys: string[]
36
54
  /** 需要校验的key */
37
- formKeys: Map<number, (keyof Fields)[]>
55
+ formKeys: Map<number, AllKeys<Fields>[]>
38
56
  /** 错误 */
39
- readonly errors: Map<keyof Fields, string[] | undefined>
57
+ readonly errors: Map<AllKeys<Fields>, string[] | undefined>
40
58
  /**
41
59
  * 字段校验
42
60
  * @param fields 字段, 如果不传入时将会使用keyFields来进行校验
43
61
  */
44
- validate: (fields?: keyof Fields | (keyof Fields)[]) => Promise<boolean>
62
+ validate: (fields?: AllKeys<Fields> | AllKeys<Fields>[]) => Promise<boolean>
45
63
  /** 重置数据 */
46
- resetData(fields?: keyof Fields | (keyof Fields)[]): void
64
+ resetData(fields?: AllKeys<Fields> | AllKeys<Fields>[]): void
47
65
 
48
66
  /**
49
67
  * 设置数据
@@ -54,9 +72,9 @@ export type IFormModel<
54
72
  /** 清除校验 */
55
73
  clearValidate(): void
56
74
  /** 监听值变更 */
57
- onChange(cb: (field: keyof Fields, val: any) => void): void
75
+ onChange(cb: (field: AllKeys<Fields>, val: any) => void): void
58
76
  /** 关闭监听值变更 */
59
- offChange(cb: (field: keyof Fields, val: any) => void): void
77
+ offChange(cb: (field: AllKeys<Fields>, val: any) => void): void
60
78
  }
61
79
 
62
80
  /** 表单组件属性 */