根据组件类型与条件动态添加下拉框及选项

前言

本次在做低代码平台时,遇见了一个稍微有些复杂的业务场景,需求描述:

  1. 根据用户拖拽表单生成的JSON数据(本次采用了FormGenerator去生成JSON数据),生成对应的所有组件的下拉列表,并在流程图的边(节点连接线)上添加条件判断。
  2. 当用户点击流程图的边时,生成该边上对应的所有组件的下拉列表,在点击某个组件的选项后,生成对应组件的下拉列表组件,在用户取消选择后,对应组件的下拉列表组件随之消失。
  3. 针对不同组件对应的下拉列表,要求有不同的条件判断,例如:数字类型的需要有范围选择、大小判断、为空不为空,等条件;文本组件有包含不包含,等条件。
  4. 当用户选择后,自动保存到当前边对应的JSON数据,若用户的选择无效,则不保存。
  5. 要求能够回显用户选择的数据,即点击不同边时,回显该边上JSON对应的所有下拉列表组件。

经过梳理后,其实逻辑看起来也不是特别复杂,不过当时确实是有点被难倒了,主要原因就是一些细节性的问题没有考虑到,然后流程图也需要去学习如何操作,时间上有些紧,同时也在思考如何写能够更便于维护。其次就是没有一个清晰的该处业务的流程思考,不过后来捋了捋也就差不多啦,所以说三思而后行啊。

三思而后行:多思考几遍,自己又觉得自己行了哈哈

Logicflow和FromGenerator就不多说了,虽然也有点坑,但都是轮子,主要记录下这块业务场景如何实现的。

组件展示

  1. 包含所有组件的下拉列表
    在这里插入图片描述
  2. 对应组件的下拉列表组件
    在这里插入图片描述
  3. 根据条件改变输入形式
    在这里插入图片描述
  4. 在这里插入图片描述

具体实现

JSON数据处理

处理JSON,不得不提一嘴ES6结构赋值,属实是属性提取的利器。
原JSON大概长这个样子,为了区别不同类型的组件,和表单那边的小伙伴约定了两个字段,tag用于区分组件输入框的类型,typeNumber用于区分下拉列表内容有哪些

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
{
"fields": [
{
"__config__": {
"label": "单行文本",
"labelWidth": null,
"showLabel": true,
"changeTag": true,
"tag": "el-input",
"tagIcon": "input",
"required": true,
"layout": "colFormItem",
"span": 24,
"document": "https://element.eleme.cn/#/zh-CN/component/input",
"regList": [],
"formId": 101,
"renderKey": 1649927471934,
"typeNumber": 0
},
"__slot__": {
"prepend": "",
"append": ""
},
"placeholder": "请输入单行文本",
"style": {
"width": "100%"
},
"clearable": true,
"prefix-icon": "",
"suffix-icon": "",
"maxlength": null,
"show-word-limit": false,
"readonly": false,
"disabled": false,
"__vModel__": "field101"
},
{
"__config__": {
"label": "多行文本",
"labelWidth": null,
"showLabel": true,
"tag": "el-input",
"tagIcon": "textarea",
"required": true,
"layout": "colFormItem",
"span": 24,
"regList": [],
"changeTag": true,
"document": "https://element.eleme.cn/#/zh-CN/component/input",
"formId": 102,
"typeNumber": 1,
"renderKey": 1649927472786
},
"type": "textarea",
"placeholder": "请输入多行文本",
"autosize": {
"minRows": 4,
"maxRows": 4
},
"style": {
"width": "100%"
},
"maxlength": null,
"show-word-limit": false,
"readonly": false,
"disabled": false,
"__vModel__": "field102"
},
{
"__config__": {
"label": "级联选择",
"showLabel": true,
"labelWidth": null,
"tag": "el-cascader",
"tagIcon": "cascader",
"layout": "colFormItem",
"defaultValue": [
1,
2
],
"dataType": "dynamic",
"span": 24,
"required": true,
"regList": [],
"changeTag": true,
"document": "https://element.eleme.cn/#/zh-CN/component/cascader",
"formId": 111,
"typeNumber": 9,
"renderKey": 1649942215947
},
"options": [
{
"id": 1,
"value": 1,
"label": "选项1",
"children": [
{
"id": 2,
"value": 2,
"label": "选项1-1"
}
]
}
],
"placeholder": "请选择级联选择",
"style": {
"width": "100%"
},
"props": {
"props": {
"multiple": false,
"label": "label",
"value": "value",
"children": "children"
}
},
"show-all-levels": true,
"disabled": false,
"clearable": true,
"filterable": false,
"separator": "/",
"__vModel__": "field111"
},
{
"__config__": {
"label": "计数器",
"showLabel": true,
"changeTag": true,
"labelWidth": null,
"tag": "el-input-number",
"tagIcon": "number",
"span": 24,
"layout": "colFormItem",
"required": true,
"regList": [],
"document": "https://element.eleme.cn/#/zh-CN/component/input-number",
"formId": 105,
"typeNumber": 2,
"renderKey": 1649927477883
},
"placeholder": "计数器",
"step": 1,
"step-strictly": false,
"controls-position": "",
"disabled": false,
"__vModel__": "field105"
},
{
"__config__": {
"label": "下拉选择",
"showLabel": true,
"labelWidth": null,
"tag": "el-select",
"tagIcon": "select",
"layout": "colFormItem",
"span": 24,
"required": true,
"regList": [],
"changeTag": true,
"document": "https://element.eleme.cn/#/zh-CN/component/select",
"formId": 106,
"typeNumber": 6,
"renderKey": 1649927484505
},
"__slot__": {
"options": [
{
"label": "选项一",
"value": 1
},
{
"label": "选项二",
"value": 2
}
]
},
"placeholder": "请选择下拉选择",
"style": {
"width": "100%"
},
"clearable": true,
"disabled": false,
"filterable": false,
"multiple": false,
"__vModel__": "field106"
},
{
"__config__": {
"label": "单选框组",
"labelWidth": null,
"showLabel": true,
"tag": "el-radio-group",
"tagIcon": "radio",
"changeTag": true,
"layout": "colFormItem",
"span": 24,
"optionType": "default",
"regList": [],
"required": true,
"border": false,
"document": "https://element.eleme.cn/#/zh-CN/component/radio",
"formId": 107,
"typeNumber": 4,
"renderKey": 1649927491036
},
"__slot__": {
"options": [
{
"label": "选项一",
"value": 1
},
{
"label": "选项二",
"value": 2
}
]
},
"style": {},
"size": "medium",
"disabled": false,
"__vModel__": "field107"
},
{
"__config__": {
"label": "多选框组",
"tag": "el-checkbox-group",
"tagIcon": "checkbox",
"defaultValue": [],
"span": 24,
"showLabel": true,
"labelWidth": null,
"layout": "colFormItem",
"optionType": "default",
"required": true,
"regList": [],
"changeTag": true,
"border": false,
"document": "https://element.eleme.cn/#/zh-CN/component/checkbox",
"formId": 108,
"typeNumber": 5,
"renderKey": 1649927491380
},
"__slot__": {
"options": [
{
"label": "选项一",
"value": 1
},
{
"label": "选项二",
"value": 2
}
]
},
"style": {},
"size": "medium",
"disabled": false,
"__vModel__": "field108"
},
{
"__config__": {
"label": "日期选择",
"tag": "el-date-picker",
"tagIcon": "date",
"defaultValue": null,
"showLabel": true,
"labelWidth": null,
"span": 24,
"layout": "colFormItem",
"required": true,
"regList": [],
"changeTag": true,
"document": "https://element.eleme.cn/#/zh-CN/component/date-picker",
"formId": 110,
"typeNumber": 3,
"renderKey": 1649927506700
},
"placeholder": "请选择日期选择",
"type": "date",
"style": {
"width": "100%"
},
"disabled": false,
"clearable": true,
"format": "yyyy-MM-dd",
"value-format": "yyyy-MM-dd",
"readonly": false,
"__vModel__": "field110"
}
],
"formRef": "elForm",
"formModel": "formData",
"size": "medium",
"labelPosition": "right",
"labelWidth": 100,
"formRules": "rules",
"gutter": 15,
"disabled": false,
"span": 24,
"formBtns": true,
"unFocusedComponentBorder": false
}

解构工具类

解构赋值 过滤数据:

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
193
194
195
196
197
198
199
200
201
202
203
204
// utils/adpterForForm.js

// 依照tag判断组件输入框的类型(FormGenerator采用的elementui,所以会有el-xxx的tag)
const contentTypeMap = {
'el-input': 'input',
'el-textarea': 'input',
'el-input-number': 'input',
'el-date-picker': 'date',
'el-radio-group': 'select',
'el-checkbox-group': 'select',
'el-select': 'select',
'el-cascader': 'tree',
'el-upload': 'none'
};

// { label: '等于', value: 0 },
// { label: '不等于', value: 1 },
// { label: '包含', value: 2 },
// { label: '不包含', value: 3 },
// { label: '为空', value: 4 },
// { label: '不为空', value: 5 }
// { label: '大于', value: 6 },
// { label: '大于等于', value: 7 },
// { label: '小于', value: 8 },
// { label: '小于等于', value: 9 },
// { label: '选择范围', value: 10 },
// { label: '等于任意一个', value: 11 },
// { label: '不等于任意一个', value: 12 },
// { label: '包含任意一个', value: 13 },
// { label: '同时包含', value: 14 },
// { label: '属于', value: 15 },
// { label: '不属于', value: 16 },
// { label: '已验证', value: 17 },
// { label: '未验证', value: 18 },
// 依照type判断组件所对应的下拉列表
const judgeListMap = {
// 单行文本
'0': [
{ label: '等于', value: 0 },
{ label: '不等于', value: 1 },
{ label: '包含', value: 2 },
{ label: '不包含', value: 3 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 多行文本
'1': [
{ label: '包含', value: 2 },
{ label: '不包含', value: 3 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 数字
'2': [
{ label: '等于', value: 0 },
{ label: '不等于', value: 1 },
{ label: '大于', value: 6 },
{ label: '大于等于', value: 7 },
{ label: '小于', value: 8 },
{ label: '小于等于', value: 9 },
{ label: '选择范围', value: 10 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 日期时间
'3': [
{ label: '等于', value: 0 },
{ label: '不等于', value: 1 },
{ label: '大于等于', value: 7 },
{ label: '小于等于', value: 9 },
{ label: '选择范围', value: 10 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 单选按钮组
'4': [
{ label: '等于', value: 0 },
{ label: '不等于', value: 1 },
{ label: '等于任意一个', value: 11 },
{ label: '不等于任意一个', value: 12 },
{ label: '包含', value: 2 },
{ label: '不包含', value: 3 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 复选框组
'5': [
{ label: '等于', value: 0 },
{ label: '包含任意一个', value: 13 },
{ label: '同时包含', value: 14 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 单选列表
'6': [
{ label: '等于', value: 0 },
{ label: '不等于', value: 1 },
{ label: '等于任意一个', value: 11 },
{ label: '不等于任意一个', value: 12 },
{ label: '包含', value: 2 },
{ label: '不包含', value: 3 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 复选列表
'7': [
{ label: '等于', value: 0 },
{ label: '包含任意一个', value: 13 },
{ label: '同时包含', value: 14 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 地址
'9': [
{ label: '属于', value: 15 },
{ label: '不属于', value: 16 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 图片
'10': [
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 附件
'11': [
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 手机
'12': [
{ label: '包含', value: 2 },
{ label: '已验证', value: 17 },
{ label: '未验证', value: 18 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 成员单选
'13': [
{ label: '等于', value: 0 },
{ label: '不等于', value: 1 },
{ label: '等于任意一个', value: 11 },
{ label: '不等于任意一个', value: 12 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 成员多选
'14': [
{ label: '等于', value: 0 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 },
{ label: '包含任意一个', value: 13 },
{ label: '同时包含', value: 14 }
],
// 成员单选
'15': [
{ label: '等于', value: 0 },
{ label: '不等于', value: 1 },
{ label: '等于任意一个', value: 11 },
{ label: '不等于任意一个', value: 12 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 }
],
// 部门多选
'16': [
{ label: '等于', value: 0 },
{ label: '为空', value: 4 },
{ label: '不为空', value: 5 },
{ label: '包含任意一个', value: 13 },
{ label: '同时包含', value: 14 }
]
};

// 获取表单JSON数据中所有组件的必要信息,并存放在map中去,便于查询和管理
export function getComponentMap (data) {
// 声明一个map集合
const map = new Map();
// 遍历解构赋值每一项
for (const item of data) {
const {
'__config__': {
'label': label = '',
'renderKey': renderKey = '',
'tag': tag = '',
'typeNumber': type = ''
},
'__slot__': {
'options': options = []
} = {}
} = item;
// 构造所需要的数据对象
const obj = {
label: label,
renderKey: renderKey, // 唯一标识
tag: tag, // 决定条件组件卡片的内容选择形式,因为文本输入框和数字、日期的展示形式不同
type: type, // 决定条件卡片的条件选项有哪些
options: options // 如果内容选项为下拉列表时,通过options展示
};
obj.judgeList = judgeListMap[obj.type]; // 依照type添加条件列表
obj.componentFormat = contentTypeMap[obj.tag]; // 依照tag添加内容展示形式
map.set(obj.renderKey, obj); // 依据组件的唯一标识去存放进map
}
return map;
}

解构结果

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
[{
"key": "1011655792483713",
"value": {
"label": "单行文本",
"renderKey": "1011655792483713",
"tag": "el-input",
"type": 0,
"options": [],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "不等于",
"value": 1
}, {
"label": "包含",
"value": 2
}, {
"label": "不包含",
"value": 3
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "input"
}
}, {
"key": "1021655792484018",
"value": {
"label": "多行文本",
"renderKey": "1021655792484018",
"tag": "el-input",
"type": 1,
"options": [],
"judgeList": [{
"label": "包含",
"value": 2
}, {
"label": "不包含",
"value": 3
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "input"
}
}, {
"key": "1031655792484335",
"value": {
"label": "数字",
"renderKey": "1031655792484335",
"tag": "el-input",
"type": 2,
"options": [],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "不等于",
"value": 1
}, {
"label": "大于",
"value": 6
}, {
"label": "大于等于",
"value": 7
}, {
"label": "小于",
"value": 8
}, {
"label": "小于等于",
"value": 9
}, {
"label": "选择范围",
"value": 10
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "input"
}
}, {
"key": "1041655792484586",
"value": {
"label": "日期时间",
"renderKey": "1041655792484586",
"tag": "el-date-picker",
"type": 3,
"options": [],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "不等于",
"value": 1
}, {
"label": "大于等于",
"value": 7
}, {
"label": "小于等于",
"value": 9
}, {
"label": "选择范围",
"value": 10
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "date"
}
}, {
"key": "1051655792484934",
"value": {
"label": "单选按钮组",
"renderKey": "1051655792484934",
"tag": "el-radio-group",
"type": 4,
"options": [{
"label": "选项一",
"value": 1
}, {
"label": "选项二",
"value": 2
}],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "不等于",
"value": 1
}, {
"label": "等于任意一个",
"value": 11
}, {
"label": "不等于任意一个",
"value": 12
}, {
"label": "包含",
"value": 2
}, {
"label": "不包含",
"value": 3
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "select"
}
}, {
"key": "1061655792485183",
"value": {
"label": "复选框组",
"renderKey": "1061655792485183",
"tag": "el-checkbox-group",
"type": 5,
"options": [{
"label": "选项一",
"value": 1
}, {
"label": "选项二",
"value": 2
}],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "包含任意一个",
"value": 13
}, {
"label": "同时包含",
"value": 14
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "select"
}
}, {
"key": "1071655792485471",
"value": {
"label": "下拉框",
"renderKey": "1071655792485471",
"tag": "el-select",
"type": 6,
"options": [{
"label": "选项一",
"value": 1
}, {
"label": "选项二",
"value": 2
}],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "不等于",
"value": 1
}, {
"label": "等于任意一个",
"value": 11
}, {
"label": "不等于任意一个",
"value": 12
}, {
"label": "包含",
"value": 2
}, {
"label": "不包含",
"value": 3
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "select"
}
}, {
"key": "1081655792485723",
"value": {
"label": "下拉复选框",
"renderKey": "1081655792485723",
"tag": "el-select",
"type": 7,
"options": [{
"label": "选项一",
"value": 1
}, {
"label": "选项二",
"value": 2
}],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "包含任意一个",
"value": 13
}, {
"label": "同时包含",
"value": 14
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "select"
}
}, {
"key": "1091655792486011",
"value": {
"label": "分割线",
"renderKey": "1091655792486011",
"tag": "el-divider",
"type": 8,
"options": [],
"judgeList": "__vue_devtool_undefined__",
"componentFormat": "__vue_devtool_undefined__"
}
}, {
"key": "1101655792486384",
"value": {
"label": "地址",
"renderKey": "1101655792486384",
"tag": "el-cascader",
"type": 9,
"options": [],
"judgeList": [{
"label": "属于",
"value": 15
}, {
"label": "不属于",
"value": 16
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "tree"
}
}, {
"key": "1111655792487022",
"value": {
"label": "附件",
"renderKey": "1111655792487022",
"tag": "el-upload",
"type": 11,
"options": [],
"judgeList": [{
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "none"
}
}, {
"key": "1121655792487317",
"value": {
"label": "图片",
"renderKey": "1121655792487317",
"tag": "el-upload",
"type": 10,
"options": [],
"judgeList": [{
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "none"
}
}, {
"key": "1131655792487550",
"value": {
"label": "手机",
"renderKey": "1131655792487550",
"tag": "el-input",
"type": 12,
"options": [],
"judgeList": [{
"label": "包含",
"value": 2
}, {
"label": "已验证",
"value": 17
}, {
"label": "未验证",
"value": 18
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "input"
}
}, {
"key": "1141655792487891",
"value": {
"label": "成员单选",
"renderKey": "1141655792487891",
"tag": "el-cascader",
"type": 13,
"options": [],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "不等于",
"value": 1
}, {
"label": "等于任意一个",
"value": 11
}, {
"label": "不等于任意一个",
"value": 12
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "tree"
}
}, {
"key": "1151655792488133",
"value": {
"label": "部门单选",
"renderKey": "1151655792488133",
"tag": "el-cascader",
"type": 15,
"options": [],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "不等于",
"value": 1
}, {
"label": "等于任意一个",
"value": 11
}, {
"label": "不等于任意一个",
"value": 12
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}],
"componentFormat": "tree"
}
}, {
"key": "1161655792488389",
"value": {
"label": "部门多选",
"renderKey": "1161655792488389",
"tag": "el-cascader",
"type": 16,
"options": [],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}, {
"label": "包含任意一个",
"value": 13
}, {
"label": "同时包含",
"value": 14
}],
"componentFormat": "tree"
}
}, {
"key": "1171655792488689",
"value": {
"label": "成员多选",
"renderKey": "1171655792488689",
"tag": "el-cascader",
"type": 14,
"options": [],
"judgeList": [{
"label": "等于",
"value": 0
}, {
"label": "为空",
"value": 4
}, {
"label": "不为空",
"value": 5
}, {
"label": "包含任意一个",
"value": 13
}, {
"label": "同时包含",
"value": 14
}],
"componentFormat": "tree"
}
}]

至此,我们就满足了业务流程中的第 1 点和第 3 点:

根据用户拖拽表单生成的JSON数据(本次采用了FormGenerator去生成JSON数据),生成对应的所有组件的下拉列表,并在流程图的连接线上添加条件判断。

针对不同组件对应的下拉列表,要求有不同的条件判断,例如:数字类型的需要有范围选择、大小判断、为空不为空,等条件;文本组件有包含不包含,等条件

我们只需提取map中每个组件的renderKeylabel作为选项的valuelabel即可生成所有组件的下拉列表。当然,不要忘了用JSON.parse解析JSON对象后再提取

生成/删除单个下拉列表组件

来让我们看看第2个流程:

当用户点击流程图的边时,生成该边上对应的所有组件的下拉列表,在点击某个组件的选项后,生成对应组件的下拉列表组件,在用户取消选择后,对应组件的下拉列表组件随之消失

我们借鉴Vue的思想,给这个增增删删的组件一个生命周期,那么他大概分为如下几个生命周期:

  1. init:点击流程图时,属于init初始化的阶段,判断是否有回显的数据,并组织各类数据去展示。
    • 首先初始化该边的数据,由父组件传递过来,然后判断是否有数据需要回显(initEdgeData
    • 当边的数据初始化结束后,假如没有数据需要回显,则结束init。否则,添加回显的数据item进已选项中(initSelectedItems
    • 当选中项存在时,需要初始化选中项对应的组件(initSelectedComponents
  2. update:当已选组件列表发生变化时,更新下拉列表组件
    • 如果是在全部组件列表里添加了新选项,那么就需要将其由renderKeylabel的item添加进已选项中
    • 如果是取消了某个选项,删除该选项renderKey对应的数据
    • 如果是更新了已选项的数据,判断是否应该被保存
  3. distory:当全部组件列表的某个已选项取消选择后,删除对应的下拉列表组件。触发update。所以这个阶段可以和update合二为一
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
//所有组件下拉列表的vue结构
<!-- 添加流转条件多选列表 -->
<el-select
v-model="selectedComponentIds"
size="small"
:popper-append-to-body="false"
collapse-tags
filterable
multiple
placeholder="请选择"
@change="updateSelectedComponents"
>
<el-option
v-for="item in formComponents"
:key="item.renderKey"
:label="item.label"
:value="item.renderKey"
/>
</el-select>
// 遍历展示下拉组件的vue结构
<div class="conditions-items-box">
<!-- key值不同保证更新子组件的值 -->
<componentsCard
v-for="item in selectedComponentsAry"
:key="JSON.stringify(item.condition)"
:custom-component="item"
:cur-judge="curJudge"
@changeCurJudge="showJudge"
@saveComponent="saveComponent"
/>
</div>
<script>
import componentsCard from '@/components/workflow/PropertySetting/componentsCard';
import { getComponentMap } from '@/utils/adpterForForm';
components: { componentsCard },
data() {
return {
conditionOptions: [{
value: 0,
label: '使用自定义流转条件'
}, {
value: 1,
label: '使用Else条件'
}],
choseType: false,
isAll: false,
selectedCondition: 0,
formComponents: [], // 用于遍历展示所有组件的选项列表
componentsMap: null, // 存放所有组件的基础信息
selectedComponentIds: [], // 存放所有已选组件的renderKey(唯一id)
selectedComponentsAry: null, // 遍历展示每个组件下拉列表组件
curJudge: -1,
form: null, // 拷贝当前边的自定义数据
saveMap: new Map() // 仅保存conditions,这是后端要求的,即renderKey、Judge、content
};
},
// 通过watch监听边的变化并获取数据
watch: {
// 边更新后初始化数据
nodeData: function(val) {
if (val.type === 'customEdge') {
this.initEdgeData(val);
}
}
},
// Vue组件初始化时
created() {
// 构建组件map,key为renderKey,value为组件所有的基本信息
// 把表单数据存在了localStorage里面防止刷新页面后表单数据丢失
this.componentsMap = getComponentMap(JSON.parse(window.localStorage.drawingItems));
// 获取表单数据并展示选择列表。这里就是v-for展示下拉选项用的
this.formComponents = [...this.componentsMap.values()];
// 此处可以优化,只用到了label和renderKey,存在一个数据冗余的问题 可以遍历解构label和value。
},
methods:{
// 初始化边的条件数据
initEdgeData(data) {
// 提取边的自定义属性properties,并拷贝一份到当前实例(不需要深拷贝)
// 便于在保存数据时直接拿来save替换掉老数据
// 保存的数据都存在properties.conditions里面
const { properties } = data;
if (properties) {
this.$data.form = Object.assign({}, this.$data.form, properties);
}
// 更新当前选中项
this.initSelectedItems(data);
// 更新当前选中项对应的自定义组件
this.initSelectedComponents(this.$data.form.conditions);
},

// 初始化选中项
initSelectedItems(data) {
// 初始化选中项数组。
this.selectedComponentIds = [];
// 将边上之前添加过的数据回显出来
data.properties.conditions.forEach(item => {
this.selectedComponentIds.push(item.renderKey);
});
},

// 初始化选中项对应的组件,此时除了初始化组件的基本信息外,还会添加已选组件的用户输入信息,即conditionList
initSelectedComponents(conditionList) {
const ary = [];
// 遍历当前选中项,更新条件map和卡片数组
for (const item of conditionList) {
// 此处需要深拷贝,componentsMap获取的是一份地址,直接操作会污染componentsMap
const componentObj = Object.create(this.componentsMap.get(item.renderKey));
componentObj.condition = item;
ary.push(componentObj);
// 初始化要保存的map,map保存的仅有conditions,这是后端要求的
/* conditions形如
*{
* renderKey:唯一id
* Judge:条件
* content:内容
*}
*/
this.saveMap.set(item.renderKey, item);
}
// 更新当前选中项对应的组件卡片数组
this.selectedComponentsAry = ary;
},

// 更新当前选中项对应的组件
// 此方法在触发下拉列表事@change件时传入已选项的renderKey的list
// 使用renderKey,控制条件组件的展示
updateSelectedComponents(idList) {
const ary = []; // 用于遍历展示用的已选项数组
const newSaveMap = new Map(); // 用于替换saveMap的新map,数据格式和saveMap一样,是update后的saveMap
let saveMapItem = null; // 判断是否已存在该数据,是延用数据还是初始化保存项

// 遍历当前选中项,更新newSaveMap和卡片数组
// item为renderKey(唯一id)
for (const item of idList) {
// 此处需要浅拷贝,直接操作会污染componentsMap(组件的基本信息)
const componentObj = Object.create(this.componentsMap.get(item)); // 目标组件的基本信息
saveMapItem = this.saveMap.get(item);
// 向组件添加要入库的condition信息,即用户输入的信息
// 同时判断该数据是否已存在,是延用数据还是初始化数据
// 这么做的原因是因为无法获取到最新添加/删除的id,只能获得所有的id,然后遍历替换数据,所以已存在的老数据需要保留
componentObj.condition = saveMapItem ? saveMapItem : {
Judge: -1, // 条件默认为 请选择
content: null, // 选择的内容默认为 null
renderKey: item // 唯一id
};
ary.push(componentObj); // 用于遍历展示用的已选项数组
newSaveMap.set(item, this.saveMap.get(item)); // 向准备替换的map内添加数据
}
// 更新要存入节点的map
this.saveMap = newSaveMap;
this.form.conditions = this.filterSaveData();
// 更新当前选中项对应的组件卡片数组
this.selectedComponentsAry = ary;
this.saveData();
},

// 新添加的下拉列表组件值被修改后触发
saveComponent(conditions) {
const { Judge: judge, renderKey } = conditions;
if (judge !== -1) {
this.saveMap.set(renderKey, conditions);
}
this.form.conditions = this.filterSaveData();
this.saveData();
}

// 过滤可保存数据
filterSaveData() {
this.form.conditions = [...this.saveMap.values()].filter(item => {
if (item && item.judge !== -1) {
// 选择了条件
if (item.judge === 4 || item.judge === 5) {
// 条件是 为空 和 不为空 不需要content有值
return item;
} else if (item.content !== '' && item.content !== null && item.content !== undefined) {
// 其他条件content必须有值才通过过滤
return item;
}
}
});
},

// 保存数据到当前选中元素
saveData() {
const { id } = this.$props.nodeData;
// logicFlow的保存节点信息的Api
this.$props.lf.setProperties(id, this.$data.form);
},
}
</script>

至此,我们已经完成了流程2的所有逻辑。

当用户点击流程图的边时,生成该边上对应的所有组件的下拉列表,在点击某个组件的选项后,生成对应组件的下拉列表组件,在用户取消选择后,对应组件的下拉列表组件随之消失。

因为流程3的逻辑在我们编写工具类时就已经搞定了,所以进入流程4

当用户选择后,自动保存到当前边对应的JSON数据,若用户的选择无效,则不保存

子组件的编写+数据保存

子组件采用工厂模式写了一个工厂组件,这里起得名字叫componentsCard,通过组件内部的componentFormat属性决定该组件该如何展示。该属性在解构的时候通过tag属性添加。

当用户在全部组件的下拉列表选择后,该组件的renderKey就会被添加进已选项中,假如用户并没有进行任何操作,则该条数据会被filterSaveData过滤掉,被认为是无效数据。只有满足filterSaveData过滤条件的数据才会被存入当前边的JSON数据中

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// 只展示了部分关键代码

....

<div class="container-title">
<div class="title-name">
{{ customComponent.label }}
</div>
<div class="title-conditions" @click="showJudgeList(customComponent)">
{{ selectedItem.label }}
<i class="icon-wf-caretdown iconfont" />
<div v-if="+curJudge === +customComponent.renderKey" class="list-box">
<div
v-for="(item,index) in customComponent.judgeList "
:key="index"
class="list-item"
@click.capture.stop="selectItem(item)"
>
{{ item.label }}
</div>
</div>
</div>
</div>

....

<!-- 为空或不为空作为条件时,该选项不显示 -->
<div v-show="showContent">
<div
v-if="customComponent.componentFormat === 'input'"
class="content"
>
<el-input
v-show="selectedItem.value !== 4 && selectedItem.value !== 5"
v-model="content"
@change="saveData"
/>
</div>
<div
v-if="customComponent.componentFormat === 'select'"
class="content"
>
<!-- 单选列表 -->
<el-select
v-show="!isMultipleSelect"
v-model="content"
@change="saveData"
>
<el-option
v-for="item in customComponent.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- 多选列表 -->
<el-select
v-show="isMultipleSelect"
v-model="contentAry"
multiple
placeholder="请选择"
@change="saveDataAry"
>
<el-option
v-for="item in customComponent.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div
v-if="customComponent.componentFormat === 'date'"
class="content"
>
<el-date-picker
v-show="!isRange"
v-model="content"
type="date"
placeholder="选择日期"
value-format="timestamp"
@change="saveData"
/>
<el-date-picker
v-show="isRange"
v-model="contentAry"
type="daterange"
range-separator="~"
value-format="timestamp"
@change="saveDataAry"
/>
</div>
<div
v-if="customComponent.componentFormat === 'tree'"
class="content"
>
<el-cascader
v-model="content"
:options="customComponent.options"
@change="saveData"
/>
</div>
<div
v-if="customComponent.componentFormat === 'none'"
class="content"
/>
</div>

<script>
export default {
props: {
curJudge: {
type: Number,
default: -1
},
customComponent: {
type: Object,
default: () => {}
}
},
data() {
return {
selectedItem: { label: '请选择', value: -1 }, // 条件字段选择的item
content: '', // 内容
contentAry: [], // 内容也可能是数组(范围选择时)
multipleSelectVal: [2, 3, 11, 12, 13, 14], // 这些值表示可以进行多选
rangeVal: [10] // 这些值表示是范围选择,
};
},
computed: {
// 要保存的数据
sendData: function() {
return {
content: this.content,
renderKey: this.customComponent.renderKey,
Judge: this.selectedItem.value
};
},
sendDataAry: function() {
return {
content: this.contentAry,
renderKey: this.customComponent.renderKey,
Judge: this.selectedItem.value
};
},
// 是否展示输入框
showContent: function() {
return this.selectedItem.value !== 4 && this.selectedItem.value !== 5;
},
// 是否多选
isMultipleSelect: function() {
return this.findVal(this.multipleSelectVal, this.selectedItem.value);
},
// 是否范围选择
isRange: function() {
return this.findVal(this.rangeVal, this.selectedItem.value);
}
},
mounted() {
// 判断显示的内容是 数组 还是 字符串/数字
Array.isArray(this.customComponent.condition.content) ? this.contentAry = this.customComponent.condition.content : this.content = this.customComponent.condition.content;
// 已选值传入时需要回显,找到对应的选项
const initSelectJudge = this.customComponent.judgeList.find(options => {
return options.value === this.customComponent.condition.Judge;
});
// 初始化当前组件条件选项的值
this.selectedItem = initSelectJudge || this.selectedItem;
},
methods: {
selectItem(item) {
this.selectedItem = item;
// 选择完关闭条件列表
this.$emit('changeCurJudge', -1);
this.content = null;
this.contentAry = null;

// 这里将数据是否保存的判断全部交由父组件去处理了,这里本来是只有选择 为空/不为空 才会发送
// 原因1
// 数据保存的条件是blur,所以当更改数据后就需要确认数据是否能够被保存(这里和流程图的设计有关)
// 原因2
// 但即使父组件接收到了数据,也需要额外的判断流程,不如就将所有的判断逻辑都交由了父组件处理
// 这样子组件只管选择就行了,后期即使需要更改也只需要更改父组件的判断逻辑
// 原因3
// 这样在用户切换条件时,能够重置输入框
this.$emit('saveComponent', this.sendData);
},
showJudgeList(item) {
this.$emit('changeCurJudge', item.renderKey);
},
// 保存数据,通过计算属性包装的data直接发送
saveData(val) {
this.content = val;
this.$emit('saveComponent', this.sendData);
},
// 保存数据,通过计算属性包装的data直接发送
saveDataAry(val) {
this.contentAry = val;
this.$emit('saveComponent', this.sendDataAry);
},
// 工具方法,找数用的
findVal(ary, val) {
return ary.find(item => {
return item === val;
});
}
}
};
</script>

这样子组件就可以在数据更改后向父组件通知,并将所有的判断逻辑交由父组件处理,将子组件的功能尽量简化。避免后期在维护时还需要关注子组件的判断逻辑。

而且通过工厂组件,当需要新加组件时也只需要去维护一个componentCard组件就行了,逻辑完全可以复用。

到这,业务流程4就能跑通了。

当用户选择后,自动保存到当前边对应的JSON数据,若用户的选择无效,则不保存。

至于业务流程5

要求能够回显用户选择的数据,即点击不同边时,回显该边上JSON对应的所有下拉列表组件

因为在父组件使用了watch,当节点发生变化时,会重新执行init阶段的各种方法,从而做到数据回显。

总结

做这种组件还是需要理清楚逻辑,多多关注细节问题。同时尽量让逻辑简单起来。当然肯定存在不少更优的写法,不过目前还考虑不到,欢迎各位大佬评论指正。

作者

徐云飞

发布于

2023-02-05

更新于

2023-02-05

许可协议

评论