Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Hooke
/
vue-flow-editor
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
hookehuyr
2023-12-06 12:54:24 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
db3b44a36d8aa70936f6bd0b66bea64d9a23a1a3
db3b44a3
1 parent
c751b0b0
新增批量保存节点属性功能
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
265 additions
and
143 deletions
doc/App.vue
doc/api/index.js
doc/selectUserView.vue
src/shape/control.ts
src/utils/utils.ts
doc/App.vue
View file @
db3b44a
...
...
@@ -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,68 +1438,194 @@ export default {
state.main_attr_set = true;
}
/**
* 保存表单信息
*
*/
async function saveForm() {
if (state.node_name === '') {
ElMessage({
type: 'error',
message: '节点名称不能为空',
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;
}
});
return;
let avail_editable_count = element.field.filter((ele) => {
if (ele.editable.checked && !ele.editable.disabled) {
return ele;
}
});
if (
(element.name === '') ||
(key !=='start-node' && !element.user.length) || // 开始节点不需要检查负责人
(avail_visible_count.length === 0 && avail_editable_count.length === 0)
) { // 节点名称为空, 节点负责人不能为空, 请至少选择一个字段权限
models.push(element.model);
}
}
if (state.detailModel.id !== 'start-node' && state.userTags.length === 0) {
if (models.length) {
ElMessage({
type: 'error',
message: '
节点负责人不能为空
',
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;
}
// 批量新增节点提示
models.forEach(ele => {
ele.desc = 'https://cdn.ipadbiz.cn/oa/flow/icons-error1.png';
editor.updateModel(ele); // 更新流程图信息
});
if (avail_visible_count.length === 0 && avail_editable_count.length === 0) {
ElMessage({
type: 'error',
message: '请至少选择一个字段权限',
return models;
}
const batchSaveForm = async () => { // 批量保存节点信息
for (const key in state.node_tree) {
const element = state.node_tree[key];
// 调整数据结构
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;
ele.field_extend.readonly = auth.editable.disabled;
}
})
});
return;
// 没有错误,修改节点名称
element.field = element.field_extend; // 字段权限保存需要的数据结构
element.model.text = element.name; // 修改节点名称
element.model.desc = ''; // 清空节点错误提示
editor.updateModel(element.model); // 更新流程图信息
}
// 调整数据结构
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
// TAG: 保存表单信息
const { code, data } = await save
FlowNodePropertyAPI({ 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 save
AllFlowNodePropertyAPI({ flow_id: +flow_id, data: JSON.stringify(state.node_tree
) })
if (code) {
ElMessage({
type:'success',
message: '保存成功',
});
state.detailModel.text = state.node_name; // 更新节点名称显示
editor.updateModel(state.detailModel); // 更新流程图信息
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: '保存流程图成功',
});
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();
// }
// }
/**
* 删除前校验
*
...
...
@@ -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,76 +1859,27 @@ export default {
}
)
.then(async () => {
saveF
orm
();
saveF
lowData
();
})
.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,
}));
ElMessageBox.confirm(
'是否确定保存流程图?',
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.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('缺少一条从开始节点到结束节点的完整流程!');
} else {
ElMessageBox.confirm(
'是否确定保存流程?',
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
})
.catch(() => {
});
)
.then(async () => {
batchSaveForm();
saveFlowData();
})
.catch(() => {
});
}
}
const startFlow = () => { // 启用流程图
...
...
@@ -1856,7 +1950,7 @@ export default {
cancel,
setMoreAttr,
onConfirmMoreAttr,
saveForm,
//
saveForm,
handleBeforeDelete,
handleAfterDelete,
handleBeforeAdd,
...
...
doc/api/index.js
View file @
db3b44a
/*
* @Date: 2023-11-30 10:34:01
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-12-0
2 15:08:01
* @LastEditTime: 2023-12-0
6 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
));
...
...
doc/selectUserView.vue
View file @
db3b44a
<!--
* @Date: 2023-11-01 10:18:53
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-1
1-24 16:23:41
* @LastEditTime: 2023-1
2-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);
});
}
}
}
}
);
...
...
src/shape/control.ts
View file @
db3b44a
/*
* @Date: 2023-10-27 09:29:59
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-1
1-14 15:31:33
* @LastEditTime: 2023-1
2-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})
},
...
...
src/utils/utils.ts
View file @
db3b44a
...
...
@@ -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
}
...
...
Please
register
or
login
to post a comment