hookehuyr

新增批量保存节点属性功能

......@@ -455,7 +455,7 @@ import type { FormInstance, FormRules } from 'element-plus'
import qs from 'qs'
import { after } from 'lodash-es';
// import { VueSpinner } from 'vue3-spinners';
import { flowVersionAPI, saveFlowAPI, flowNodesAPI, enableFlowVersionAPI, flowNodePropertyAPI, saveFlowNodePropertyAPI } from "./api";
import { flowVersionAPI, saveFlowAPI, flowNodesAPI, enableFlowVersionAPI, flowNodePropertyAPI, saveFlowNodePropertyAPI, saveAllFlowNodePropertyAPI } from "./api";
const G6 = (window as any).G6.default as any
......@@ -519,12 +519,14 @@ export default {
desc: '拖拽新增流程',
color: '#ed8383',
img: 'https://cdn.ipadbiz.cn/oa/flow/icons-flow.png',
error: '',
},
cc: {
text: '抄送节点',
desc: '拖拽新增抄送',
color: '#ed8383',
img: 'https://cdn.ipadbiz.cn/oa/flow/icon-cc.png',
error: '',
},
},
controlConfig: {
......@@ -540,12 +542,14 @@ export default {
desc: '流程节点',
color: '#ed8383',
img: 'https://cdn.ipadbiz.cn/oa/flow/icons-flow.png',
error: '',
},
cc: {
text: '抄送节点',
desc: '抄送节点',
color: '#ed8383',
img: 'https://cdn.ipadbiz.cn/oa/flow/icon-cc.png',
error: '',
},
end: {
id: 'end-node',
......@@ -751,7 +755,7 @@ export default {
}
}
},
{ immediate: true }
// { immediate: true }
);
watch(
() => state.more_attr,
......@@ -779,6 +783,7 @@ export default {
* @param note
*/
const onSelectFlowVersion = (id: number, code: number, note: string) => {
state.node_tree = {}; // 清空当前版本的节点树状态缓存
state.reloadLoading = true; // 打开loading
state.select_flow_version = code;
updateFlowId(id); // 更新缓存flow_id
......@@ -1115,8 +1120,7 @@ export default {
/******************* END *******************/
const handleNodeNameChange = (val) => {
state.detailModel.text = val; // 更新节点名称显示
editor.updateModel(state.detailModel); // 更新流程图信息
// state.detailModel.text = val; // 更新节点名称显示
}
/***************** 用户选择控件弹框 ****************/
......@@ -1126,6 +1130,7 @@ export default {
*/
const openUserForm = () => {
state.dialogUserFormVisible = true;
state.dialogUserTags = state.node_tree[state.detailModel.id].user;
}
const onCloseUserView = (status: boolean) => {
......@@ -1322,16 +1327,25 @@ export default {
editor.openModel();
// 储存原始节点属性
state.node_attr = {
name: _.cloneDeep(state.node_name),
user: _.cloneDeep(state.userTags),
field: _.cloneDeep(state.field_extend),
property: _.cloneDeep(state.more_attr)
}
// // 储存原始节点属性
// state.node_attr = {
// name: _.cloneDeep(state.node_name),
// user: _.cloneDeep(state.userTags),
// field: _.cloneDeep(state.field_extend),
// property: _.cloneDeep(state.more_attr)
// }
// 临时保存树信息
setNodeTree(model.id, { name: _.cloneDeep(state.node_name), user: _.cloneDeep(state.userTags), field: _.cloneDeep(state.field_auths), property: _.cloneDeep(state.more_attr) });
setNodeTree(model.id,
{
name: _.cloneDeep(state.node_name),
user: model_id === 'start-node' ? '' : _.cloneDeep(state.userTags), // 开始节点没有负责人
field: _.cloneDeep(state.field_auths),
field_extend: _.cloneDeep(state.field_extend),
property: _.cloneDeep(state.more_attr),
model
}
);
} else {
state.statusLoading = false;
......@@ -1424,45 +1438,52 @@ export default {
state.main_attr_set = true;
}
/**
* 保存表单信息
*
*/
async function saveForm() {
if (state.node_name === '') {
ElMessage({
type: 'error',
message: '节点名称不能为空',
});
return;
}
if (state.detailModel.id !== 'start-node' && state.userTags.length === 0) {
ElMessage({
type: 'error',
message: '节点负责人不能为空',
});
return;
}
let avail_visible_count = state.field_auths.filter((ele) => {
const checkSaveNodeTree = () => { // 检查需要保存的节点树属性
let models = [];
for (const key in state.node_tree) {
const element = state.node_tree[key];
let avail_visible_count = element.field.filter((ele) => {
if (ele.visible.checked && !ele.visible.disabled) {
return ele;
}
});
let avail_editable_count = state.field_auths.filter((ele) => {
let avail_editable_count = element.field.filter((ele) => {
if (ele.editable.checked && !ele.editable.disabled) {
return ele;
}
});
if (avail_visible_count.length === 0 && avail_editable_count.length === 0) {
if (
(element.name === '') ||
(key !=='start-node' && !element.user.length) || // 开始节点不需要检查负责人
(avail_visible_count.length === 0 && avail_editable_count.length === 0)
) { // 节点名称为空, 节点负责人不能为空, 请至少选择一个字段权限
models.push(element.model);
}
}
if (models.length) {
ElMessage({
type: 'error',
message: '请至少选择一个字段权限',
message: '流程配置不完善,请点击节点红点完善。',
});
return;
}
// 批量新增节点提示
models.forEach(ele => {
ele.desc = 'https://cdn.ipadbiz.cn/oa/flow/icons-error1.png';
editor.updateModel(ele); // 更新流程图信息
});
return models;
}
const batchSaveForm = async () => { // 批量保存节点信息
for (const key in state.node_tree) {
const element = state.node_tree[key];
// 调整数据结构
state.field_extend.forEach(ele => {
state.field_auths.forEach(auth => {
element.field_extend.forEach(ele => {
element.field.forEach(auth => {
if (ele.field_id === auth.field_id) {
ele.field_extend.visibled = auth.visible.checked;
ele.field_extend.editabled = auth.editable.checked;
......@@ -1470,23 +1491,142 @@ export default {
}
})
});
// 没有错误,修改节点名称
element.field = element.field_extend; // 字段权限保存需要的数据结构
element.model.text = element.name; // 修改节点名称
element.model.desc = ''; // 清空节点错误提示
editor.updateModel(element.model); // 更新流程图信息
}
let flow_id = getFlowId(); // 流程id
// TAG: 保存表单信息
const { code, data } = await saveFlowNodePropertyAPI({ flow_id: +flow_id, node_code: state.detailModel.id, data: JSON.stringify({ name: state.node_name, user: state.userTags, field: state.field_extend, property: state.more_attr }) })
const { code, data } = await saveAllFlowNodePropertyAPI({ flow_id: +flow_id, data: JSON.stringify(state.node_tree) })
if (code) {
state.node_tree = {}; // 清空节点树缓存
editor.closeModel();
}
}
const saveFlowData = async () => { // 保存流程图结构信息
let { nodes, edges } = editor.editorState.graph.save();
// 使用时需要把自定义节点的类型带过去 activity/control
nodes.forEach((node: { [x: string]: string; shape: string }) => {
if (node.shape === 'control') {
node['control'] = node['control']
}
});
nodes = nodes.map(
({ data, id, label, shape, x, y, text, desc, img, control }) => ({
data,
id,
label,
shape,
x,
y,
text,
desc,
img,
control,
}),
);
edges = edges.map(({ shape, source, sourceAnchor, target, targetAnchor }) => ({
shape,
source,
sourceAnchor,
target,
targetAnchor,
}));
// 检查路径有效性
const paths = [];
findPathsToEndNode(edges, 'start-node', [], paths);
let flow_id = getFlowId(); // 流程id
if (paths.length) {
const { code, data } = await saveFlowAPI({ form_id: +form_id, flow_id: +flow_id, data: JSON.stringify({ nodes, edges }) });
if (code) {
ElMessage({
type:'success',
message: '保存成功',
type: 'success',
message: '保存流程图成功',
});
state.detailModel.text = state.node_name; // 更新节点名称显示
editor.updateModel(state.detailModel); // 更新流程图信息
editor.closeModel();
updateFlowId(data); // 更新缓存flow_id
console.log(paths); // 输出满足条件的路径结果数组
}
} else {
ElNotification.error('缺少一条从开始节点到结束节点的完整流程!');
}
}
/**
* 保存表单信息
*
*/
// async function saveForm() {
// if (state.node_name === '') {
// ElMessage({
// type: 'error',
// message: '节点名称不能为空',
// });
// return;
// }
// if (state.detailModel.id !== 'start-node' && state.userTags.length === 0) {
// ElMessage({
// type: 'error',
// message: '节点负责人不能为空',
// });
// return;
// }
// let avail_visible_count = state.field_auths.filter((ele) => {
// if (ele.visible.checked && !ele.visible.disabled) {
// return ele;
// }
// });
// let avail_editable_count = state.field_auths.filter((ele) => {
// if (ele.editable.checked && !ele.editable.disabled) {
// return ele;
// }
// });
// if (avail_visible_count.length === 0 && avail_editable_count.length === 0) {
// ElMessage({
// type: 'error',
// message: '请至少选择一个字段权限',
// });
// return;
// }
// // 调整数据结构
// state.field_extend.forEach(ele => {
// state.field_auths.forEach(auth => {
// if (ele.field_id === auth.field_id) {
// ele.field_extend.visibled = auth.visible.checked;
// ele.field_extend.editabled = auth.editable.checked;
// ele.field_extend.readonly = auth.editable.disabled;
// }
// })
// });
// let flow_id = getFlowId(); // 流程id
// // TODO: 需要一个可以保存多个表单的接口,field字段的结构要重新遍历state.field_extend
// // TODO:只要失焦的话就需要判断表单是否为空,为空需要用户继续填写不能走。
// console.warn(state.node_tree);
// // TAG: 保存表单信息
// const { code, data } = await saveFlowNodePropertyAPI({ flow_id: +flow_id, node_code: state.detailModel.id, data: JSON.stringify({ name: state.node_name, user: state.userTags, field: state.field_extend, property: state.more_attr }) })
// if (code) {
// ElMessage({
// type:'success',
// message: '保存成功',
// });
// state.detailModel.text = state.node_name; // 更新节点名称显示
// editor.updateModel(state.detailModel); // 更新流程图信息
// editor.closeModel();
// }
// }
/**
* 删除前校验
*
* @param {Object} model - The model object.
......@@ -1660,19 +1800,19 @@ export default {
editor.closeModel()
}
const checkFormEdited = () => { // 检查表单是否修改过内容
state.field_extend.forEach(ele => {
state.field_auths.forEach(auth => {
if (ele.field_id === auth.field_id) {
ele.field_extend.visibled = auth.visible.checked;
ele.field_extend.editabled = auth.editable.checked;
ele.field_extend.readonly = auth.editable.disabled;
}
})
});
// const checkFormEdited = () => { // 检查表单是否修改过内容
// state.field_extend.forEach(ele => {
// state.field_auths.forEach(auth => {
// if (ele.field_id === auth.field_id) {
// ele.field_extend.visibled = auth.visible.checked;
// ele.field_extend.editabled = auth.editable.checked;
// ele.field_extend.readonly = auth.editable.disabled;
// }
// })
// });
return _.isEqual(state.node_attr, {name: state.node_name, user: state.userTags, field: state.field_extend, property: state.more_attr})
}
// return _.isEqual(state.node_attr, {name: state.node_name, user: state.userTags, field: state.field_extend, property: state.more_attr})
// }
/**
* 拖动节点结束回调
......@@ -1703,11 +1843,14 @@ export default {
*
* @return {void} No return value.
*/
function saveData(): void {
// 如果表单打开状态,走表单保存逻辑
if (state.detailModel) {
const saveData = async () => {
if (checkSaveNodeTree().length) {
return;
}
if (_.isEmpty(state.node_tree)) {
ElMessageBox.confirm(
'是否确定保存流程信息?',
'是否确定保存流程?',
'温馨提示',
{
confirmButtonText: '确认',
......@@ -1716,46 +1859,13 @@ export default {
}
)
.then(async () => {
saveForm();
saveFlowData();
})
.catch(() => {
});
return;
}
let { nodes, edges } = editor.editorState.graph.save();
// 使用时需要把自定义节点的类型带过去 activity/control
nodes.forEach((node: { [x: string]: string; shape: string }) => {
if (node.shape === 'control') {
node['control'] = node['control']
}
});
nodes = nodes.map(
({ data, id, label, shape, x, y, text, desc, img, control }) => ({
data,
id,
label,
shape,
x,
y,
text,
desc,
img,
control,
}),
);
edges = edges.map(({ shape, source, sourceAnchor, target, targetAnchor }) => ({
shape,
source,
sourceAnchor,
target,
targetAnchor,
}));
} else {
ElMessageBox.confirm(
'是否确定保存流程图?',
'是否确定保存流程?',
'温馨提示',
{
confirmButtonText: '确认',
......@@ -1764,29 +1874,13 @@ export default {
}
)
.then(async () => {
// 检查路径有效性
const paths = [];
findPathsToEndNode(edges, 'start-node', [], paths);
let flow_id = getFlowId(); // 流程id
if (paths.length) {
const { code, data } = await saveFlowAPI({ form_id: +form_id, flow_id: +flow_id, data: JSON.stringify({ nodes, edges }) });
if (code) {
ElMessage({
type: 'success',
message: '保存流程图成功',
});
updateFlowId(data); // 更新缓存flow_id
console.log(paths); // 输出满足条件的路径结果数组
}
} else {
ElNotification.error('缺少一条从开始节点到结束节点的完整流程!');
}
batchSaveForm();
saveFlowData();
})
.catch(() => {
});
}
}
const startFlow = () => { // 启用流程图
......@@ -1856,7 +1950,7 @@ export default {
cancel,
setMoreAttr,
onConfirmMoreAttr,
saveForm,
// saveForm,
handleBeforeDelete,
handleAfterDelete,
handleBeforeAdd,
......
/*
* @Date: 2023-11-30 10:34:01
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-12-02 15:08:01
* @LastEditTime: 2023-12-06 11:36:04
* @FilePath: /vue-flow-editor/doc/api/index.js
* @Description: 文件描述
*/
......@@ -14,6 +14,7 @@ const Api = {
ENABLE_FLOW_VERSION: '/admin/?a=enable_flow_version',
FLOW_NODE_PROPERTY: '/admin/?a=flow_node_property',
SAVE_FLOW_NODE_PROPERTY: '/admin/?a=save_node_property',
SAVE_ALL_FLOW_NODE_PROPERTY: '/admin/?a=save_all_node_property',
}
/**
......@@ -64,3 +65,11 @@ export const flowNodePropertyAPI = (params) => fn(fetch.get(Api.FLOW_NODE_PROPER
* @returns
*/
export const saveFlowNodePropertyAPI = (params) => fn(fetch.stringifyPost(Api.SAVE_FLOW_NODE_PROPERTY, params));
/**
* @description: 保存所有节点属性
* @param {*} flow_id 流程 ID
* @param {*} data 节点属性的数据,json格式字符串
* @returns
*/
export const saveAllFlowNodePropertyAPI = (params) => fn(fetch.stringifyPost(Api.SAVE_ALL_FLOW_NODE_PROPERTY, params));
......
<!--
* @Date: 2023-11-01 10:18:53
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-11-24 16:23:41
* @LastEditTime: 2023-12-06 12:52:57
* @FilePath: /vue-flow-editor/doc/selectUserView.vue
* @Description: 成员列表选择控件
-->
......@@ -411,6 +411,18 @@ watch(
nextTick(() => {
tabTextWidth.value = $("#" + activeTabId.value).width() + "px";
});
userTags.value = _.cloneDeep(props.list);
// 第一项目是组织结构树,默认展开第一个节点
if (userTabs.value[activeTabIdx.value]['type'] === 'corp-tree') {
if (userTabs.value[activeTabIdx.value]['data'].length) {
// 默认展开第一个节点
defaultExpandedKeys.value = [userTabs.value[activeTabIdx.value]['data'][0]['id']];
// 把用户选中的节点注入树结构显示选中状态
nextTick(() => {
currentCheckedNodeKey.value = userTags.value.map(ele => ele.id);
});
}
}
}
}
);
......
/*
* @Date: 2023-10-27 09:29:59
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-11-14 15:31:33
* @LastEditTime: 2023-12-06 09:51:19
* @FilePath: /vue-flow-editor/src/shape/control.ts
* @Description: 自定义控制节点
*/
......@@ -53,7 +53,8 @@ export function registerControl(G6) {
},
drawShape(cfg, group) { // 继承了基类,可以使用drawShape,如果没有继承,必须要有draw
let {text, desc, img, color} = cfg
let {text, desc, img, error, color} = cfg
color = color || BASE_COLOR
desc = desc || '无描述'
......@@ -77,6 +78,10 @@ export function registerControl(G6) {
type: 'image',
attrs: {x: height / 4 - width / 2, y: height / 4 - height / 2, width: height / 2, height: height / 2, img},
},
error: {
type: 'image',
attrs: {x: height / 4 + width / 4 + 5, y: height / 4 - height / 2, width: height / 2.1, height: height / 2.1, img: error},
},
label: {
type: 'text',
attrs: {text, x: height - width / 2, y: height * (4 / 8) - height / 2, fontSize: 14, textAlign: 'left', textBaseline: 'middle', fill: 'black'},
......@@ -111,6 +116,7 @@ export function registerControl(G6) {
group = group.getContainer()
// group.shapes.sideRect.attr({fill: cfg.color})
group.shapes.img.attr({img: cfg.img})
group.shapes.error.attr({img: cfg.desc})
group.shapes.label.attr({text: cfg.text})
// group.shapes.desc.attr({text: cfg.desc})
},
......
......@@ -157,6 +157,7 @@ export function formatNodeModel_control(model: any, controlConfig: object) {
// 把 control 的配置挂到model上
model.shape = 'control'
model.img = controlConfig[control].img
model.error = controlConfig[control].error
model.color = controlConfig[control].color
}
......