Are you an LLM? You can read better optimized documentation at /front/component/ma-form/examples/advanced-scenarios.md for this page in Markdown format
Advanced Application Scenarios β
Demonstrates MaForm's complex applications in real business scenarios, including multi-step forms, data dictionary integration, permission control, internationalization, and other advanced features.
Features β
- Multi-step Forms: Complex form workflows divided into steps
- Data Dictionary Integration: Integration with backend dictionary systems
- Permission Control: Form field control based on user permissions
- Internationalization Support: Multi-language form configuration
- Business Rule Engine: Complex business logic processing
- Data Linkage: Multi-level data relationships and dependencies
Multi-step Forms β
1. Step Form Structure β
typescript
interface StepFormData {
currentStep: number
stepData: {
basic: BasicInfo
contact: ContactInfo
additional: AdditionalInfo
}
}
const stepFormConfig = {
steps: [
{
title: 'Basic Information',
key: 'basic',
description: 'Fill in basic personal information',
icon: 'User'
},
{
title: 'Contact Details',
key: 'contact',
description: 'Fill in contact information',
icon: 'Phone'
},
{
title: 'Additional Information',
key: 'additional',
description: 'Supplement other information',
icon: 'Document'
}
]
}
// Step form item configuration
const getStepFormItems = (currentStep: number): MaFormItem[] => {
const stepConfigs = {
0: [ // Basic information step
{
label: 'Name',
prop: 'basic.name',
render: 'input',
itemProps: {
rules: [{ required: true, message: 'Please enter name', trigger: 'blur' }]
},
cols: { xs: 24, sm: 12 }
},
{
label: 'Gender',
prop: 'basic.gender',
render: 'radioGroup',
renderSlots: {
default: () => [
h('el-radio', { label: 'male' }, 'Male'),
h('el-radio', { label: 'female' }, 'Female')
]
},
cols: { xs: 24, sm: 12 }
},
{
label: 'Date of Birth',
prop: 'basic.birthDate',
render: 'datePicker',
renderProps: {
type: 'date',
placeholder: 'Select birth date',
disabledDate: (time: Date) => time.getTime() > Date.now()
},
cols: { xs: 24, sm: 12 }
}
],
1: [ // Contact details step
{
label: 'Phone Number',
prop: 'contact.phone',
render: 'input',
renderProps: {
placeholder: 'Enter phone number'
},
customValidator: (rule, value, callback) => {
if (!value) {
callback(new Error('Please enter phone number'))
} else if (!/^1[3-9]\d{9}$/.test(value)) {
callback(new Error('Please enter a valid phone number'))
} else {
callback()
}
},
cols: { xs: 24, sm: 12 }
},
{
label: 'Email Address',
prop: 'contact.email',
render: 'input',
renderProps: {
type: 'email',
placeholder: 'Enter email address'
},
asyncValidator: async (rule, value) => {
if (value) {
const exists = await checkEmailExists(value)
if (exists) {
throw new Error('This email is already in use')
}
}
},
cols: { xs: 24, sm: 12 }
},
{
label: 'Address',
prop: 'contact.address',
render: 'cascader',
renderProps: {
options: await loadAddressOptions(),
props: { expandTrigger: 'hover' },
placeholder: 'Select address'
},
cols: { xs: 24, sm: 24 }
}
],
2: [ // Additional information step
{
label: 'Bio',
prop: 'additional.bio',
render: 'textarea',
renderProps: {
rows: 4,
placeholder: 'Enter bio',
maxlength: 500,
showWordLimit: true
},
cols: { xs: 24, sm: 24 }
},
{
label: 'Hobbies',
prop: 'additional.hobbies',
render: 'checkboxGroup',
renderSlots: {
default: () => hobbiesList.map(hobby =>
h('el-checkbox', { label: hobby.value }, hobby.label)
)
},
cols: { xs: 24, sm: 24 }
}
]
}
return stepConfigs[currentStep] || []
}
// Step control logic
const stepFormController = {
currentStep: ref(0),
stepData: ref<StepFormData['stepData']>({
basic: {},
contact: {},
additional: {}
}),
// Next step
nextStep: async () => {
const isValid = await formRef.value.validate()
if (isValid) {
if (stepFormController.currentStep.value < stepFormConfig.steps.length - 1) {
stepFormController.currentStep.value++
updateFormItems()
}
}
},
// Previous step
prevStep: () => {
if (stepFormController.currentStep.value > 0) {
stepFormController.currentStep.value--
updateFormItems()
}
},
// Jump to specific step
goToStep: async (targetStep: number) => {
// Validate current step
const isValid = await formRef.value.validate()
if (isValid || targetStep < stepFormController.currentStep.value) {
stepFormController.currentStep.value = targetStep
updateFormItems()
}
}
}
// Update form items
const updateFormItems = () => {
const items = getStepFormItems(stepFormController.currentStep.value)
formRef.value.setItems(items)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
2. Step Validation Strategy β
typescript
const stepValidationStrategy = {
// Step validation
validateStep: async (stepIndex: number): Promise<boolean> => {
const stepKey = stepFormConfig.steps[stepIndex].key
const stepProps = Object.keys(stepFormController.stepData.value[stepKey])
try {
// Validate all fields in current step
const validationPromises = stepProps.map(prop =>
formRef.value.validateField(`${stepKey}.${prop}`)
)
await Promise.all(validationPromises)
return true
} catch (error) {
return false
}
},
// Validate all steps
validateAllSteps: async (): Promise<{ isValid: boolean; invalidSteps: number[] }> => {
const invalidSteps: number[] = []
for (let i = 0; i < stepFormConfig.steps.length; i++) {
const isStepValid = await stepValidationStrategy.validateStep(i)
if (!isStepValid) {
invalidSteps.push(i)
}
}
return {
isValid: invalidSteps.length === 0,
invalidSteps
}
},
// Get step status
getStepStatus: (stepIndex: number): 'wait' | 'process' | 'finish' | 'error' => {
if (stepIndex < stepFormController.currentStep.value) {
return 'finish'
} else if (stepIndex === stepFormController.currentStep.value) {
return 'process'
} else {
return 'wait'
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Data Dictionary Integration β
1. Dictionary Management β
typescript
interface DictionaryItem {
label: string
value: string | number
disabled?: boolean
children?: DictionaryItem[]
extra?: Record<string, any>
}
interface DictionaryConfig {
code: string // Dictionary code
label: string // Dictionary name
cache: boolean // Whether to cache
cascade?: boolean // Whether cascaded
parentField?: string // Parent field (for cascading)
}
const dictionaryService = {
// Dictionary data cache
cache: new Map<string, DictionaryItem[]>(),
// Get dictionary data
async getDictionary(config: DictionaryConfig): Promise<DictionaryItem[]> {
// Check cache
if (config.cache && this.cache.has(config.code)) {
return this.cache.get(config.code)!
}
try {
const response = await fetch(`/api/dictionary/${config.code}`)
const data = await response.json()
// Cache data
if (config.cache) {
this.cache.set(config.code, data)
}
return data
} catch (error) {
console.error(`Failed to get dictionary ${config.code}:`, error)
return []
}
},
// Get cascaded dictionary data
async getCascadeDictionary(
config: DictionaryConfig,
parentValue: string | number
): Promise<DictionaryItem[]> {
const cacheKey = `${config.code}_${parentValue}`
if (config.cache && this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!
}
try {
const response = await fetch(`/api/dictionary/${config.code}?parent=${parentValue}`)
const data = await response.json()
if (config.cache) {
this.cache.set(cacheKey, data)
}
return data
} catch (error) {
console.error(`Failed to get cascaded dictionary ${config.code}:`, error)
return []
}
},
// Clear dictionary cache
clearCache: (code?: string) => {
if (code) {
// Clear cache for specific dictionary
const keysToDelete = Array.from(this.cache.keys()).filter(key => key.startsWith(code))
keysToDelete.forEach(key => this.cache.delete(key))
} else {
// Clear all cache
this.cache.clear()
}
}
}
// Dictionary form item factory
const createDictionaryField = (config: {
label: string
prop: string
dictConfig: DictionaryConfig
renderType?: 'select' | 'radioGroup' | 'checkboxGroup' | 'cascader'
multiple?: boolean
placeholder?: string
}): MaFormItem => {
const { label, prop, dictConfig, renderType = 'select', multiple = false, placeholder } = config
return {
label,
prop,
render: renderType,
renderProps: {
placeholder: placeholder || `Select ${label}`,
multiple: renderType === 'select' ? multiple : undefined,
loading: true // Initial loading state
},
renderSlots: {
default: () => [] // Initially empty, updated dynamically
},
// Load dictionary data when field mounts
async onMounted() {
try {
const dictData = await dictionaryService.getDictionary(dictConfig)
// Update field options
const slots = createDictSlots(dictData, renderType)
formRef.value?.updateItem(prop, {
renderProps: { loading: false },
renderSlots: { default: slots }
})
} catch (error) {
console.error(`Failed to load dictionary data: ${dictConfig.code}`, error)
formRef.value?.updateItem(prop, {
renderProps: { loading: false }
})
}
}
}
}
// Create dictionary option slots
const createDictSlots = (data: DictionaryItem[], renderType: string) => {
return () => {
switch (renderType) {
case 'select':
return data.map(item =>
h('el-option', {
key: item.value,
label: item.label,
value: item.value,
disabled: item.disabled
})
)
case 'radioGroup':
return data.map(item =>
h('el-radio', {
key: item.value,
label: item.value,
disabled: item.disabled
}, item.label)
)
case 'checkboxGroup':
return data.map(item =>
h('el-checkbox', {
key: item.value,
label: item.value,
disabled: item.disabled
}, item.label)
)
default:
return []
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
2. Cascaded Dictionary Implementation β
typescript
const cascadeDictionaryFields = [
{
label: 'Province',
prop: 'province',
render: 'select',
renderProps: {
placeholder: 'Select province',
clearable: true
},
renderSlots: {
default: async () => {
const provinces = await dictionaryService.getDictionary({
code: 'province',
label: 'Province',
cache: true
})
return provinces.map(item =>
h('el-option', { label: item.label, value: item.value })
)
}
},
// Update city options when province changes
onChange: async (value: string) => {
// Clear city and district
formRef.value.setFormData({
...formRef.value.getFormData(),
city: '',
district: ''
})
if (value) {
// Load city data
formRef.value.updateItem('city', {
renderProps: { loading: true }
})
const cities = await dictionaryService.getCascadeDictionary({
code: 'city',
label: 'City',
cache: true,
cascade: true,
parentField: 'province'
}, value)
const citySlots = () => cities.map(item =>
h('el-option', { label: item.label, value: item.value })
)
formRef.value.updateItem('city', {
renderProps: { loading: false },
renderSlots: { default: citySlots }
})
}
}
},
{
label: 'City',
prop: 'city',
render: 'select',
renderProps: {
placeholder: 'Select province first',
clearable: true,
disabled: true // Initially disabled
},
show: (model) => !!model.province,
dependencies: ['province'],
onChange: async (value: string) => {
// Similar district linkage logic
const formData = formRef.value.getFormData()
if (value && formData.province) {
// Load district data...
}
}
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
Permission Control System β
1. Field Permission Configuration β
typescript
interface FieldPermission {
field: string
permissions: {
view: boolean // Whether visible
edit: boolean // Whether editable
required: boolean // Whether required
}
conditions?: {
roles?: string[] // Role conditions
departments?: string[] // Department conditions
customCheck?: () => boolean // Custom conditions
}
}
interface UserContext {
id: string
roles: string[]
department: string
permissions: string[]
}
const permissionService = {
userContext: ref<UserContext | null>(null),
// Check field permission
checkFieldPermission(fieldPermission: FieldPermission): {
visible: boolean
editable: boolean
required: boolean
} {
const user = this.userContext.value
if (!user) {
return { visible: false, editable: false, required: false }
}
// Basic permission check
let { view, edit, required } = fieldPermission.permissions
// Conditional permission check
if (fieldPermission.conditions) {
const { roles, departments, customCheck } = fieldPermission.conditions
// Role check
if (roles && !roles.some(role => user.roles.includes(role))) {
view = false
edit = false
}
// Department check
if (departments && !departments.includes(user.department)) {
view = false
edit = false
}
// Custom check
if (customCheck && !customCheck()) {
view = false
edit = false
}
}
return {
visible: view,
editable: edit && view,
required: required && view && edit
}
},
// Apply permissions to form items
applyPermissionsToItems(items: MaFormItem[], permissions: FieldPermission[]): MaFormItem[] {
return items.map(item => {
const permission = permissions.find(p => p.field === item.prop)
if (!permission) return item
const { visible, editable, required } = this.checkFieldPermission(permission)
return {
...item,
show: visible,
renderProps: {
...item.renderProps,
disabled: !editable
},
itemProps: {
...item.itemProps,
rules: required ? [
...(item.itemProps?.rules || []),
{ required: true, message: `${item.label} is required`, trigger: 'blur' }
] : item.itemProps?.rules
}
}
})
}
}
// Permission-controlled form configuration
const createPermissionControlledForm = (
baseItems: MaFormItem[],
permissions: FieldPermission[]
): MaFormItem[] => {
return permissionService.applyPermissionsToItems(baseItems, permissions)
}
// Usage example
const userFormPermissions: FieldPermission[] = [
{
field: 'username',
permissions: { view: true, edit: true, required: true }
},
{
field: 'salary',
permissions: { view: true, edit: true, required: false },
conditions: {
roles: ['admin', 'hr'], // Only admin and HR can edit salary
}
},
{
field: 'confidentialInfo',
permissions: { view: false, edit: false, required: false },
conditions: {
customCheck: () => permissionService.userContext.value?.permissions.includes('view_confidential')
}
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
2. Dynamic Permission Updates β
typescript
const dynamicPermissionManager = {
// Watch user permission changes
watchUserPermissions: () => {
watch(
() => permissionService.userContext.value,
(newUser, oldUser) => {
if (newUser?.id !== oldUser?.id) {
// Reapply permissions when user changes
dynamicPermissionManager.refreshFormPermissions()
}
},
{ deep: true }
)
},
// Refresh form
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16