本次在做低代码平台时,遇见了一个稍微有些复杂的业务场景,需求描述:
FormGenerator
去生成JSON数据),生成对应的所有组件的下拉列表,并在流程图的边(节点连接线)上添加条件判断。经过梳理后,其实逻辑看起来也不是特别复杂,不过当时确实是有点被难倒了,主要原因就是一些细节性的问题没有考虑到,然后流程图也需要去学习如何操作,时间上有些紧,同时也在思考如何写能够更便于维护。其次就是没有一个清晰的该处业务的流程思考,不过后来捋了捋也就差不多啦,所以说三思而后行啊。
三思而后行
:多思考几遍,自己又觉得自己行了哈哈
Logicflow和FromGenerator就不多说了,虽然也有点坑,但都是轮子,主要记录下这块业务场景如何实现的。
处理JSON,不得不提一嘴ES6结构赋值,属实是属性提取的利器。
原JSON大概长这个样子,为了区别不同类型的组件,和表单那边的小伙伴约定了两个字段,tag
用于区分组件输入框的类型,typeNumber
用于区分下拉列表内容有哪些
{
"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
}
解构赋值 过滤数据:
// 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;
}
[{
"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中每个组件的renderKey
和label
作为选项的value
和label
即可生成所有组件的下拉列表。当然,不要忘了用JSON.parse
解析JSON对象后再提取
来让我们看看第2个流程:
当用户点击流程图的边时,生成该边上对应的所有组件的下拉列表,
在点击某个组件的选项后,生成对应组件的下拉列表组件,在用户取消选择后,对应组件的下拉列表组件随之消失
。
我们借鉴Vue的思想,给这个增增删删的组件一个生命周期,那么他大概分为如下几个生命周期:
initEdgeData
)initSelectedItems
)initSelectedComponents
)renderKey
和label
的item添加进已选项中//所有组件下拉列表的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数据中
// 只展示了部分关键代码
....
<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
阶段的各种方法,从而做到数据回显。
做这种组件还是需要理清楚逻辑,多多关注细节问题。同时尽量让逻辑简单起来。当然肯定存在不少更优的写法,不过目前还考虑不到,欢迎各位大佬评论指正。