2021-09-10 23:24:58 +08:00
|
|
|
|
<template>
|
2022-02-21 23:52:59 +08:00
|
|
|
|
<div class="workflow-container">
|
|
|
|
|
<div class="workflow-mask" v-if="isShow"></div>
|
2021-09-10 23:24:58 +08:00
|
|
|
|
<div class="layout-view-bg-white flex" :style="{ height: `calc(100vh - ${setViewHeight}` }">
|
|
|
|
|
<div class="workflow">
|
2021-09-25 16:47:30 +08:00
|
|
|
|
<!-- 顶部工具栏 -->
|
2022-02-21 23:52:59 +08:00
|
|
|
|
<Tool @tool="onToolClick" />
|
2021-09-25 16:47:30 +08:00
|
|
|
|
|
|
|
|
|
<!-- 左侧导航区 -->
|
|
|
|
|
<div class="workflow-content">
|
2022-05-30 10:01:00 +08:00
|
|
|
|
<div class="workflow-left">
|
2021-09-25 16:47:30 +08:00
|
|
|
|
<el-scrollbar>
|
|
|
|
|
<div
|
2022-05-30 10:01:00 +08:00
|
|
|
|
ref="leftNavRefs"
|
2021-09-25 16:47:30 +08:00
|
|
|
|
v-for="(val, key) in leftNavList"
|
2022-02-21 23:52:59 +08:00
|
|
|
|
:key="val.id"
|
2021-09-25 16:47:30 +08:00
|
|
|
|
:style="{ height: val.isOpen ? 'auto' : '50px', overflow: 'hidden' }"
|
|
|
|
|
class="workflow-left-id"
|
|
|
|
|
>
|
|
|
|
|
<div class="workflow-left-title" @click="onTitleClick(val)">
|
|
|
|
|
<span>{{ val.title }}</span>
|
2022-02-21 23:52:59 +08:00
|
|
|
|
<SvgIcon :name="val.isOpen ? 'ele-ArrowDown' : 'ele-ArrowRight'" />
|
2021-09-25 16:47:30 +08:00
|
|
|
|
</div>
|
2022-02-21 23:52:59 +08:00
|
|
|
|
<div class="workflow-left-item" v-for="(v, k) in val.children" :key="k" :data-name="v.name" :data-icon="v.icon" :data-id="v.id">
|
2021-09-25 16:47:30 +08:00
|
|
|
|
<div class="workflow-left-item-icon">
|
2022-02-21 23:52:59 +08:00
|
|
|
|
<SvgIcon :name="v.icon" class="workflow-icon-drag" />
|
2021-09-25 16:47:30 +08:00
|
|
|
|
<div class="font10 pl5 name">{{ v.name }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2021-09-10 23:24:58 +08:00
|
|
|
|
</div>
|
2021-09-25 16:47:30 +08:00
|
|
|
|
</el-scrollbar>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 右侧绘画区 -->
|
2022-05-30 10:01:00 +08:00
|
|
|
|
<div class="workflow-right" ref="workflowRightRef">
|
2021-09-25 16:47:30 +08:00
|
|
|
|
<div
|
2022-02-21 23:52:59 +08:00
|
|
|
|
v-for="(v, k) in jsplumbData.nodeList"
|
|
|
|
|
:key="v.nodeId"
|
2021-09-25 16:47:30 +08:00
|
|
|
|
:id="v.nodeId"
|
2022-05-30 10:01:00 +08:00
|
|
|
|
:data-node-id="v.nodeId"
|
2021-09-25 16:47:30 +08:00
|
|
|
|
:class="v.class"
|
|
|
|
|
:style="{ left: v.left, top: v.top }"
|
|
|
|
|
@click="onItemCloneClick(k)"
|
|
|
|
|
@contextmenu.prevent="onContextmenu(v, k, $event)"
|
|
|
|
|
>
|
2022-02-21 23:52:59 +08:00
|
|
|
|
<div class="workflow-right-box" :class="{ 'workflow-right-active': jsPlumbNodeIndex === k }">
|
2021-09-25 16:47:30 +08:00
|
|
|
|
<div class="workflow-left-item-icon">
|
2022-02-21 23:52:59 +08:00
|
|
|
|
<SvgIcon :name="v.icon" class="workflow-icon-drag" />
|
2021-09-25 16:47:30 +08:00
|
|
|
|
<div class="font10 pl5 name">{{ v.name }}</div>
|
|
|
|
|
</div>
|
2021-09-10 23:24:58 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2021-09-25 16:47:30 +08:00
|
|
|
|
</div>
|
2021-09-10 23:24:58 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2022-02-21 23:52:59 +08:00
|
|
|
|
|
2021-09-25 16:47:30 +08:00
|
|
|
|
<!-- 节点右键菜单 -->
|
|
|
|
|
<Contextmenu :dropdown="dropdownNode" ref="contextmenuNodeRef" @current="onCurrentNodeClick" />
|
|
|
|
|
<!-- 线右键菜单 -->
|
|
|
|
|
<Contextmenu :dropdown="dropdownLine" ref="contextmenuLineRef" @current="onCurrentLineClick" />
|
2022-02-21 23:52:59 +08:00
|
|
|
|
<!-- 抽屉表单、线 -->
|
|
|
|
|
<Drawer ref="drawerRef" @label="setLineLabel" @node="setNodeContent" />
|
|
|
|
|
|
|
|
|
|
<!-- 顶部工具栏-帮助弹窗 -->
|
|
|
|
|
<Help ref="helpRef" />
|
2021-09-10 23:24:58 +08:00
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script lang="ts">
|
2022-02-21 23:52:59 +08:00
|
|
|
|
import { defineComponent, toRefs, reactive, computed, onMounted, onUnmounted, nextTick, ref } from 'vue';
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
2021-09-25 16:47:30 +08:00
|
|
|
|
import { jsPlumb } from 'jsplumb';
|
2021-09-10 23:24:58 +08:00
|
|
|
|
import Sortable from 'sortablejs';
|
2022-04-18 19:14:38 +08:00
|
|
|
|
import { storeToRefs } from 'pinia';
|
|
|
|
|
import { useThemeConfig } from '/@/stores/themeConfig';
|
|
|
|
|
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
2022-02-21 23:52:59 +08:00
|
|
|
|
import Tool from './component/tool/index.vue';
|
|
|
|
|
import Help from './component/tool/help.vue';
|
2021-09-25 16:47:30 +08:00
|
|
|
|
import Contextmenu from './component/contextmenu/index.vue';
|
|
|
|
|
import Drawer from './component/drawer/index.vue';
|
2022-02-21 23:52:59 +08:00
|
|
|
|
import commonFunction from '/@/utils/commonFunction';
|
|
|
|
|
import { leftNavList } from './js/mock';
|
|
|
|
|
import { jsplumbDefaults, jsplumbMakeSource, jsplumbMakeTarget, jsplumbConnect } from './js/config';
|
|
|
|
|
|
|
|
|
|
// 定义接口来定义对象的类型
|
|
|
|
|
interface NodeListState {
|
|
|
|
|
id: string | number;
|
|
|
|
|
nodeId: string | undefined;
|
|
|
|
|
class: HTMLElement | string;
|
|
|
|
|
left: number | string;
|
|
|
|
|
top: number | string;
|
|
|
|
|
icon: string;
|
|
|
|
|
name: string;
|
|
|
|
|
}
|
|
|
|
|
interface LineListState {
|
|
|
|
|
sourceId: string;
|
|
|
|
|
targetId: string;
|
|
|
|
|
label: string;
|
|
|
|
|
}
|
|
|
|
|
interface XyState {
|
|
|
|
|
x: string | number;
|
|
|
|
|
y: string | number;
|
|
|
|
|
}
|
|
|
|
|
interface WorkflowState {
|
2022-05-30 10:01:00 +08:00
|
|
|
|
workflowRightRef: HTMLDivElement | null;
|
|
|
|
|
leftNavRefs: any[];
|
2022-02-21 23:52:59 +08:00
|
|
|
|
leftNavList: any[];
|
|
|
|
|
dropdownNode: XyState;
|
|
|
|
|
dropdownLine: XyState;
|
|
|
|
|
isShow: boolean;
|
|
|
|
|
jsPlumb: any;
|
|
|
|
|
jsPlumbNodeIndex: null | number;
|
|
|
|
|
jsplumbDefaults: any;
|
|
|
|
|
jsplumbMakeSource: any;
|
|
|
|
|
jsplumbMakeTarget: any;
|
|
|
|
|
jsplumbConnect: any;
|
|
|
|
|
jsplumbData: {
|
|
|
|
|
nodeList: Array<NodeListState>;
|
|
|
|
|
lineList: Array<LineListState>;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-10 23:24:58 +08:00
|
|
|
|
export default defineComponent({
|
|
|
|
|
name: 'pagesWorkflow',
|
2022-02-21 23:52:59 +08:00
|
|
|
|
components: { Tool, Contextmenu, Drawer, Help },
|
2021-09-10 23:24:58 +08:00
|
|
|
|
setup() {
|
2021-09-25 16:47:30 +08:00
|
|
|
|
const contextmenuNodeRef = ref();
|
|
|
|
|
const contextmenuLineRef = ref();
|
|
|
|
|
const drawerRef = ref();
|
2022-02-21 23:52:59 +08:00
|
|
|
|
const helpRef = ref();
|
2022-04-18 19:14:38 +08:00
|
|
|
|
const stores = useTagsViewRoutes();
|
|
|
|
|
const storesThemeConfig = useThemeConfig();
|
|
|
|
|
const { themeConfig } = storeToRefs(storesThemeConfig);
|
|
|
|
|
const { isTagsViewCurrenFull } = storeToRefs(stores);
|
2022-02-21 23:52:59 +08:00
|
|
|
|
const { copyText } = commonFunction();
|
|
|
|
|
const state = reactive<WorkflowState>({
|
2022-05-30 10:01:00 +08:00
|
|
|
|
workflowRightRef: null as HTMLDivElement | null,
|
|
|
|
|
leftNavRefs: [],
|
2021-09-25 16:47:30 +08:00
|
|
|
|
leftNavList: [],
|
|
|
|
|
dropdownNode: { x: '', y: '' },
|
|
|
|
|
dropdownLine: { x: '', y: '' },
|
2022-02-21 23:52:59 +08:00
|
|
|
|
isShow: false,
|
|
|
|
|
jsPlumb: null,
|
|
|
|
|
jsPlumbNodeIndex: null,
|
2021-09-25 16:47:30 +08:00
|
|
|
|
jsplumbDefaults,
|
|
|
|
|
jsplumbMakeSource,
|
2022-02-21 23:52:59 +08:00
|
|
|
|
jsplumbMakeTarget,
|
|
|
|
|
jsplumbConnect,
|
|
|
|
|
jsplumbData: {
|
|
|
|
|
nodeList: [],
|
|
|
|
|
lineList: [],
|
|
|
|
|
},
|
2021-09-10 23:24:58 +08:00
|
|
|
|
});
|
|
|
|
|
// 设置 view 的高度
|
|
|
|
|
const setViewHeight = computed(() => {
|
2022-04-18 19:14:38 +08:00
|
|
|
|
let { isTagsview } = themeConfig.value;
|
|
|
|
|
if (isTagsViewCurrenFull.value) {
|
2021-09-10 23:24:58 +08:00
|
|
|
|
return `30px`;
|
|
|
|
|
} else {
|
|
|
|
|
if (isTagsview) return `114px`;
|
|
|
|
|
else return `80px`;
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-02-21 23:52:59 +08:00
|
|
|
|
// 设置 宽度小于 768,不支持操
|
|
|
|
|
const setClientWidth = () => {
|
|
|
|
|
const clientWidth = document.body.clientWidth;
|
|
|
|
|
clientWidth < 768 ? (state.isShow = true) : (state.isShow = false);
|
|
|
|
|
};
|
2021-09-25 16:47:30 +08:00
|
|
|
|
// 左侧导航-数据初始化
|
|
|
|
|
const initLeftNavList = () => {
|
|
|
|
|
state.leftNavList = leftNavList;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
state.jsplumbData = {
|
|
|
|
|
nodeList: [
|
|
|
|
|
{ nodeId: 'huej738hbji', left: '148px', top: '93px', class: 'workflow-right-clone', icon: 'iconfont icon-gongju', name: '引擎', id: '11' },
|
|
|
|
|
{
|
|
|
|
|
nodeId: '52kcszzyxrd',
|
|
|
|
|
left: '458px',
|
|
|
|
|
top: '203px',
|
|
|
|
|
class: 'workflow-right-clone',
|
|
|
|
|
icon: 'iconfont icon-shouye_dongtaihui',
|
|
|
|
|
name: '模版',
|
|
|
|
|
id: '12',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
nodeId: 'nltskl6k4me',
|
|
|
|
|
left: '164px',
|
|
|
|
|
top: '350px',
|
|
|
|
|
class: 'workflow-right-clone',
|
|
|
|
|
icon: 'iconfont icon-zhongduancanshuchaxun',
|
|
|
|
|
name: '名称',
|
|
|
|
|
id: '13',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
lineList: [
|
|
|
|
|
{ sourceId: 'huej738hbji', targetId: '52kcszzyxrd', label: '传送' },
|
|
|
|
|
{ sourceId: 'huej738hbji', targetId: 'nltskl6k4me', label: '' },
|
|
|
|
|
],
|
|
|
|
|
};
|
2021-09-25 16:47:30 +08:00
|
|
|
|
};
|
|
|
|
|
// 左侧导航-初始化拖动
|
2021-09-10 23:24:58 +08:00
|
|
|
|
const initSortable = () => {
|
2022-05-30 10:01:00 +08:00
|
|
|
|
state.leftNavRefs.forEach(v => {
|
|
|
|
|
Sortable.create(v as HTMLDivElement, {
|
2022-02-21 23:52:59 +08:00
|
|
|
|
group: {
|
|
|
|
|
name: 'vue-next-admin-1',
|
|
|
|
|
pull: 'clone',
|
|
|
|
|
put: false,
|
|
|
|
|
},
|
2021-09-25 16:47:30 +08:00
|
|
|
|
animation: 0,
|
2021-09-10 23:24:58 +08:00
|
|
|
|
sort: false,
|
|
|
|
|
draggable: '.workflow-left-item',
|
|
|
|
|
forceFallback: true,
|
2022-02-21 23:52:59 +08:00
|
|
|
|
onEnd: function (evt: any) {
|
|
|
|
|
const { name, icon, id } = evt.clone.dataset;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
const { layerX, layerY, clientX, clientY } = evt.originalEvent;
|
2022-05-30 10:01:00 +08:00
|
|
|
|
const el = state.workflowRightRef!;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
const { x, y, width, height } = el.getBoundingClientRect();
|
|
|
|
|
if (clientX < x || clientX > width + x || clientY < y || y > y + height) {
|
2022-02-21 23:52:59 +08:00
|
|
|
|
ElMessage.warning('请把节点拖入到画布中');
|
2021-09-25 16:47:30 +08:00
|
|
|
|
} else {
|
|
|
|
|
// 节点id(唯一)
|
|
|
|
|
const nodeId = Math.random().toString(36).substr(2, 12);
|
|
|
|
|
// 处理节点数据
|
|
|
|
|
const node = {
|
|
|
|
|
nodeId,
|
|
|
|
|
left: `${layerX - 40}px`,
|
|
|
|
|
top: `${layerY - 15}px`,
|
|
|
|
|
class: 'workflow-right-clone',
|
|
|
|
|
name,
|
|
|
|
|
icon,
|
2022-02-21 23:52:59 +08:00
|
|
|
|
id,
|
2021-09-25 16:47:30 +08:00
|
|
|
|
};
|
|
|
|
|
// 右侧视图内容数组
|
2022-02-21 23:52:59 +08:00
|
|
|
|
state.jsplumbData.nodeList.push(node);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
// 元素加载完毕时
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
// 整个节点作为source或者target
|
|
|
|
|
state.jsPlumb.makeSource(nodeId, state.jsplumbMakeSource);
|
2022-02-21 23:52:59 +08:00
|
|
|
|
// // 整个节点作为source或者target
|
|
|
|
|
state.jsPlumb.makeTarget(nodeId, state.jsplumbMakeTarget, jsplumbConnect);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
// 设置节点可以拖拽(此处为id值,非class)
|
|
|
|
|
state.jsPlumb.draggable(nodeId, {
|
|
|
|
|
containment: 'parent',
|
2022-02-21 23:52:59 +08:00
|
|
|
|
stop: (el: any) => {
|
|
|
|
|
state.jsplumbData.nodeList.forEach((v) => {
|
2021-09-25 16:47:30 +08:00
|
|
|
|
if (v.nodeId === el.el.id) {
|
|
|
|
|
// 节点x, y重新赋值,防止再次从左侧导航中拖拽节点时,x, y恢复默认
|
|
|
|
|
v.left = `${el.pos[0]}px`;
|
|
|
|
|
v.top = `${el.pos[1]}px`;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-09-10 23:24:58 +08:00
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
2021-09-25 16:47:30 +08:00
|
|
|
|
};
|
2022-02-21 23:52:59 +08:00
|
|
|
|
// 初始化 jsPlumb
|
|
|
|
|
const initJsPlumb = () => {
|
|
|
|
|
(<any>jsPlumb).ready(() => {
|
|
|
|
|
state.jsPlumb = (<any>jsPlumb).getInstance({
|
|
|
|
|
detachable: false,
|
|
|
|
|
Container: 'workflow-right',
|
|
|
|
|
});
|
|
|
|
|
state.jsPlumb.fire('jsPlumbDemoLoaded', state.jsPlumb);
|
|
|
|
|
// 导入默认配置
|
|
|
|
|
state.jsPlumb.importDefaults(state.jsplumbDefaults);
|
|
|
|
|
// 会使整个jsPlumb立即重绘。
|
|
|
|
|
state.jsPlumb.setSuspendDrawing(false, true);
|
|
|
|
|
// 初始化节点、线的链接
|
|
|
|
|
initJsPlumbConnection();
|
|
|
|
|
// 点击线弹出右键菜单
|
|
|
|
|
state.jsPlumb.bind('contextmenu', (conn: any, originalEvent: MouseEvent) => {
|
|
|
|
|
originalEvent.preventDefault();
|
|
|
|
|
const { sourceId, targetId } = conn;
|
|
|
|
|
const { clientX, clientY } = originalEvent;
|
|
|
|
|
state.dropdownLine.x = clientX;
|
|
|
|
|
state.dropdownLine.y = clientY;
|
|
|
|
|
const v: any = state.jsplumbData.nodeList.find((v) => v.nodeId === targetId);
|
|
|
|
|
const line: any = state.jsplumbData.lineList.find((v) => v.sourceId === sourceId && v.targetId === targetId);
|
|
|
|
|
v.type = 'line';
|
|
|
|
|
v.label = line.label;
|
|
|
|
|
contextmenuLineRef.value.openContextmenu(v, conn);
|
|
|
|
|
});
|
|
|
|
|
// 连线之前
|
|
|
|
|
state.jsPlumb.bind('beforeDrop', (conn: any) => {
|
|
|
|
|
const { sourceId, targetId } = conn;
|
|
|
|
|
const item = state.jsplumbData.lineList.find((v) => v.sourceId === sourceId && v.targetId === targetId);
|
|
|
|
|
if (item) {
|
|
|
|
|
ElMessage.warning('关系已存在,不可重复连接');
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// 连线时
|
|
|
|
|
state.jsPlumb.bind('connection', (conn: any) => {
|
|
|
|
|
const { sourceId, targetId } = conn;
|
|
|
|
|
state.jsplumbData.lineList.push({
|
|
|
|
|
sourceId,
|
|
|
|
|
targetId,
|
|
|
|
|
label: '',
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
// 删除连线时回调函数
|
|
|
|
|
state.jsPlumb.bind('connectionDetached', (conn: any) => {
|
|
|
|
|
const { sourceId, targetId } = conn;
|
|
|
|
|
state.jsplumbData.lineList = state.jsplumbData.lineList.filter((line) => {
|
|
|
|
|
if (line.sourceId == sourceId && line.targetId == targetId) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// 初始化节点、线的链接
|
|
|
|
|
const initJsPlumbConnection = () => {
|
|
|
|
|
// 节点
|
|
|
|
|
state.jsplumbData.nodeList.forEach((v) => {
|
|
|
|
|
// 整个节点作为source或者target
|
|
|
|
|
state.jsPlumb.makeSource(v.nodeId, state.jsplumbMakeSource);
|
|
|
|
|
// 整个节点作为source或者target
|
|
|
|
|
state.jsPlumb.makeTarget(v.nodeId, state.jsplumbMakeTarget, jsplumbConnect);
|
|
|
|
|
// 设置节点可以拖拽(此处为id值,非class)
|
|
|
|
|
state.jsPlumb.draggable(v.nodeId, {
|
|
|
|
|
containment: 'parent',
|
|
|
|
|
stop: (el: any) => {
|
|
|
|
|
state.jsplumbData.nodeList.forEach((v) => {
|
|
|
|
|
if (v.nodeId === el.el.id) {
|
|
|
|
|
// 节点x, y重新赋值,防止再次从左侧导航中拖拽节点时,x, y恢复默认
|
|
|
|
|
v.left = `${el.pos[0]}px`;
|
|
|
|
|
v.top = `${el.pos[1]}px`;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
// 线
|
|
|
|
|
state.jsplumbData.lineList.forEach((v) => {
|
|
|
|
|
state.jsPlumb.connect(
|
|
|
|
|
{
|
|
|
|
|
source: v.sourceId,
|
|
|
|
|
target: v.targetId,
|
|
|
|
|
label: v.label,
|
|
|
|
|
},
|
|
|
|
|
state.jsplumbConnect
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
};
|
2021-09-25 16:47:30 +08:00
|
|
|
|
// 左侧导航-菜单标题点击
|
2022-02-21 23:52:59 +08:00
|
|
|
|
const onTitleClick = (val: any) => {
|
2021-09-25 16:47:30 +08:00
|
|
|
|
val.isOpen = !val.isOpen;
|
|
|
|
|
};
|
|
|
|
|
// 右侧内容区-当前项点击
|
2022-02-21 23:52:59 +08:00
|
|
|
|
const onItemCloneClick = (k: number) => {
|
|
|
|
|
state.jsPlumbNodeIndex = k;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
};
|
|
|
|
|
// 右侧内容区-当前项右键菜单点击
|
2022-02-21 23:52:59 +08:00
|
|
|
|
const onContextmenu = (v: any, k: number, e: MouseEvent) => {
|
|
|
|
|
state.jsPlumbNodeIndex = k;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
const { clientX, clientY } = e;
|
|
|
|
|
state.dropdownNode.x = clientX;
|
|
|
|
|
state.dropdownNode.y = clientY;
|
|
|
|
|
v.type = 'node';
|
2022-02-21 23:52:59 +08:00
|
|
|
|
v.label = '';
|
|
|
|
|
let item: any = {};
|
|
|
|
|
state.leftNavList.forEach((l) => {
|
|
|
|
|
if (l.children) if (l.children.find((c: any) => c.id === v.id)) item = l.children.find((c: any) => c.id === v.id);
|
|
|
|
|
});
|
|
|
|
|
v.from = item.form;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
contextmenuNodeRef.value.openContextmenu(v);
|
|
|
|
|
};
|
|
|
|
|
// 右侧内容区-当前项右键菜单点击回调(节点)
|
2022-02-21 23:52:59 +08:00
|
|
|
|
const onCurrentNodeClick = (item: any) => {
|
|
|
|
|
const { contextMenuClickId, nodeId } = item;
|
|
|
|
|
if (contextMenuClickId === 0) {
|
|
|
|
|
const nodeIndex = state.jsplumbData.nodeList.findIndex((item) => item.nodeId === nodeId);
|
|
|
|
|
state.jsplumbData.nodeList.splice(nodeIndex, 1);
|
|
|
|
|
state.jsPlumb.removeAllEndpoints(nodeId);
|
|
|
|
|
state.jsPlumbNodeIndex = null;
|
|
|
|
|
} else if (contextMenuClickId === 1) {
|
|
|
|
|
drawerRef.value.open(item);
|
|
|
|
|
}
|
2021-09-25 16:47:30 +08:00
|
|
|
|
};
|
|
|
|
|
// 右侧内容区-当前项右键菜单点击回调(线)
|
2022-02-21 23:52:59 +08:00
|
|
|
|
const onCurrentLineClick = (item: any, conn: any) => {
|
2021-09-25 16:47:30 +08:00
|
|
|
|
const { contextMenuClickId } = item;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
const { endpoints } = conn;
|
|
|
|
|
const intercourse: any = [];
|
|
|
|
|
endpoints.forEach((v: any) => {
|
|
|
|
|
intercourse.push({
|
|
|
|
|
id: v.element.id,
|
|
|
|
|
innerText: v.element.innerText,
|
2021-09-25 16:47:30 +08:00
|
|
|
|
});
|
|
|
|
|
});
|
2022-02-21 23:52:59 +08:00
|
|
|
|
item.contact = `${intercourse[0].innerText}(${intercourse[0].id}) => ${intercourse[1].innerText}(${intercourse[1].id})`;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
if (contextMenuClickId === 0) state.jsPlumb.deleteConnection(conn);
|
2022-02-21 23:52:59 +08:00
|
|
|
|
else if (contextMenuClickId === 1) drawerRef.value.open(item, conn);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
};
|
2022-02-21 23:52:59 +08:00
|
|
|
|
// 设置线的 label
|
|
|
|
|
const setLineLabel = (obj: any) => {
|
|
|
|
|
const { sourceId, targetId, label } = obj;
|
|
|
|
|
const conn = state.jsPlumb.getConnections({
|
|
|
|
|
source: sourceId,
|
|
|
|
|
target: targetId,
|
|
|
|
|
})[0];
|
|
|
|
|
conn.setLabel(label);
|
|
|
|
|
if (!label || label === '') {
|
|
|
|
|
conn.addClass('workflow-right-empty-label');
|
|
|
|
|
} else {
|
|
|
|
|
conn.removeClass('workflow-right-empty-label');
|
|
|
|
|
conn.addClass('workflow-right-label');
|
|
|
|
|
}
|
|
|
|
|
state.jsplumbData.lineList.forEach((v) => {
|
|
|
|
|
if (v.sourceId === sourceId && v.targetId === targetId) v.label = label;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// 设置节点内容
|
|
|
|
|
const setNodeContent = (obj: any) => {
|
|
|
|
|
const { nodeId, name, icon } = obj;
|
|
|
|
|
// 设置节点 name 与 icon
|
|
|
|
|
state.jsplumbData.nodeList.forEach((v) => {
|
|
|
|
|
if (v.nodeId === nodeId) {
|
|
|
|
|
v.name = name;
|
|
|
|
|
v.icon = icon;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// 重绘
|
|
|
|
|
nextTick(() => {
|
2021-09-25 16:47:30 +08:00
|
|
|
|
state.jsPlumb.setSuspendDrawing(false, true);
|
2021-09-10 23:24:58 +08:00
|
|
|
|
});
|
|
|
|
|
};
|
2022-02-21 23:52:59 +08:00
|
|
|
|
// 顶部工具栏-当前项点击
|
|
|
|
|
const onToolClick = (fnName: String) => {
|
|
|
|
|
switch (fnName) {
|
|
|
|
|
case 'help':
|
|
|
|
|
onToolHelp();
|
|
|
|
|
break;
|
|
|
|
|
case 'download':
|
|
|
|
|
onToolDownload();
|
|
|
|
|
break;
|
|
|
|
|
case 'submit':
|
|
|
|
|
onToolSubmit();
|
|
|
|
|
break;
|
|
|
|
|
case 'copy':
|
|
|
|
|
onToolCopy();
|
|
|
|
|
break;
|
|
|
|
|
case 'del':
|
|
|
|
|
onToolDel();
|
|
|
|
|
break;
|
|
|
|
|
case 'fullscreen':
|
|
|
|
|
onToolFullscreen();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// 顶部工具栏-帮助
|
|
|
|
|
const onToolHelp = () => {
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
helpRef.value.open();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// 顶部工具栏-下载
|
|
|
|
|
const onToolDownload = () => {
|
2022-04-18 19:14:38 +08:00
|
|
|
|
const { globalTitle } = themeConfig.value;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
const href = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(state.jsplumbData, null, '\t'));
|
|
|
|
|
const aLink = document.createElement('a');
|
|
|
|
|
aLink.setAttribute('href', href);
|
|
|
|
|
aLink.setAttribute('download', `${globalTitle}工作流.json`);
|
|
|
|
|
aLink.click();
|
|
|
|
|
aLink.remove();
|
|
|
|
|
ElMessage.success('下载成功');
|
|
|
|
|
};
|
|
|
|
|
// 顶部工具栏-提交
|
|
|
|
|
const onToolSubmit = () => {
|
|
|
|
|
// console.log(state.jsplumbData);
|
|
|
|
|
ElMessage.success('数据提交成功');
|
|
|
|
|
};
|
|
|
|
|
// 顶部工具栏-复制
|
|
|
|
|
const onToolCopy = () => {
|
|
|
|
|
copyText(JSON.stringify(state.jsplumbData));
|
|
|
|
|
};
|
|
|
|
|
// 顶部工具栏-删除
|
|
|
|
|
const onToolDel = () => {
|
|
|
|
|
ElMessageBox.confirm('此操作将清空画布,是否继续?', '提示', {
|
|
|
|
|
confirmButtonText: '清空',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
})
|
|
|
|
|
.then(() => {
|
|
|
|
|
state.jsplumbData.nodeList.forEach((v) => {
|
|
|
|
|
state.jsPlumb.removeAllEndpoints(v.nodeId);
|
|
|
|
|
});
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
state.jsplumbData = {
|
|
|
|
|
nodeList: [],
|
|
|
|
|
lineList: [],
|
|
|
|
|
};
|
|
|
|
|
ElMessage.success('清空画布成功');
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {});
|
|
|
|
|
};
|
|
|
|
|
// 顶部工具栏-全屏
|
|
|
|
|
const onToolFullscreen = () => {
|
2022-04-18 19:14:38 +08:00
|
|
|
|
stores.setCurrenFullscreen(true);
|
2022-02-21 23:52:59 +08:00
|
|
|
|
};
|
2021-09-10 23:24:58 +08:00
|
|
|
|
// 页面加载时
|
2021-09-25 16:47:30 +08:00
|
|
|
|
onMounted(async () => {
|
|
|
|
|
await initLeftNavList();
|
2021-09-10 23:24:58 +08:00
|
|
|
|
initSortable();
|
2021-09-25 16:47:30 +08:00
|
|
|
|
initJsPlumb();
|
2022-02-21 23:52:59 +08:00
|
|
|
|
setClientWidth();
|
|
|
|
|
window.addEventListener('resize', setClientWidth);
|
|
|
|
|
});
|
|
|
|
|
// 页面卸载时
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
window.removeEventListener('resize', setClientWidth);
|
2021-09-10 23:24:58 +08:00
|
|
|
|
});
|
|
|
|
|
return {
|
|
|
|
|
setViewHeight,
|
2022-02-21 23:52:59 +08:00
|
|
|
|
setClientWidth,
|
|
|
|
|
setLineLabel,
|
|
|
|
|
setNodeContent,
|
2021-09-25 16:47:30 +08:00
|
|
|
|
onTitleClick,
|
|
|
|
|
onItemCloneClick,
|
|
|
|
|
onContextmenu,
|
|
|
|
|
onCurrentNodeClick,
|
|
|
|
|
onCurrentLineClick,
|
|
|
|
|
contextmenuNodeRef,
|
|
|
|
|
contextmenuLineRef,
|
|
|
|
|
drawerRef,
|
2022-02-21 23:52:59 +08:00
|
|
|
|
helpRef,
|
|
|
|
|
onToolClick,
|
2021-09-10 23:24:58 +08:00
|
|
|
|
...toRefs(state),
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
2022-02-21 23:52:59 +08:00
|
|
|
|
.workflow-container {
|
|
|
|
|
position: relative;
|
2021-09-10 23:24:58 +08:00
|
|
|
|
.workflow {
|
|
|
|
|
display: flex;
|
|
|
|
|
height: 100%;
|
|
|
|
|
width: 100%;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
flex-direction: column;
|
|
|
|
|
.workflow-content {
|
|
|
|
|
display: flex;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
height: calc(100% - 35px);
|
2022-05-30 10:01:00 +08:00
|
|
|
|
.workflow-left {
|
2021-09-25 16:47:30 +08:00
|
|
|
|
width: 220px;
|
2021-09-10 23:24:58 +08:00
|
|
|
|
height: 100%;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
border-right: 1px solid var(--el-border-color-light, #ebeef5);
|
|
|
|
|
::v-deep(.el-collapse-item__content) {
|
|
|
|
|
padding-bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
.workflow-left-title {
|
|
|
|
|
height: 50px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
padding: 0 10px;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
border-top: 1px solid var(--el-border-color-light, #ebeef5);
|
2022-02-21 23:52:59 +08:00
|
|
|
|
color: var(--el-text-color-primary);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
cursor: default;
|
|
|
|
|
span {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.workflow-left-item {
|
|
|
|
|
display: inline-block;
|
|
|
|
|
width: calc(50% - 15px);
|
|
|
|
|
position: relative;
|
|
|
|
|
cursor: move;
|
|
|
|
|
margin: 0 0 10px 10px;
|
|
|
|
|
.workflow-left-item-icon {
|
|
|
|
|
height: 35px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
border: 1px dashed transparent;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
background: var(--next-bg-color);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
border-radius: 3px;
|
|
|
|
|
i,
|
|
|
|
|
.name {
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
transition: all 0.3s ease;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
white-space: nowrap;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
overflow: hidden;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
}
|
|
|
|
|
&:hover {
|
|
|
|
|
transition: all 0.3s ease;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
border: 1px dashed var(--el-color-primary);
|
|
|
|
|
background: var(--el-color-primary-light-9);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
border-radius: 5px;
|
|
|
|
|
i,
|
|
|
|
|
.name {
|
|
|
|
|
transition: all 0.3s ease;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
color: var(--el-color-primary);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
& .workflow-left-id:first-of-type {
|
|
|
|
|
.workflow-left-title {
|
|
|
|
|
border-top: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-30 10:01:00 +08:00
|
|
|
|
.workflow-right {
|
2021-09-25 16:47:30 +08:00
|
|
|
|
flex: 1;
|
|
|
|
|
position: relative;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
height: 100%;
|
|
|
|
|
background-image: linear-gradient(90deg, rgb(156 214 255 / 15%) 10%, rgba(0, 0, 0, 0) 10%),
|
|
|
|
|
linear-gradient(rgb(156 214 255 / 15%) 10%, rgba(0, 0, 0, 0) 10%);
|
|
|
|
|
background-size: 10px 10px;
|
|
|
|
|
.workflow-right-clone {
|
|
|
|
|
position: absolute;
|
|
|
|
|
.workflow-right-box {
|
|
|
|
|
height: 35px;
|
|
|
|
|
align-items: center;
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
cursor: move;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
min-width: 94.5px;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
background: var(--el-color-white);
|
|
|
|
|
border: 1px solid var(--el-border-color-light, #ebeef5);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
.workflow-left-item-icon {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
height: 35px;
|
|
|
|
|
}
|
|
|
|
|
&:hover {
|
2022-02-21 23:52:59 +08:00
|
|
|
|
border: 1px dashed var(--el-color-primary);
|
|
|
|
|
background: var(--el-color-primary-light-9);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
transition: all 0.3s ease;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
color: var(--el-color-primary);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
i {
|
|
|
|
|
cursor: Crosshair;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.workflow-right-active {
|
2022-02-21 23:52:59 +08:00
|
|
|
|
border: 1px dashed var(--el-color-primary);
|
|
|
|
|
background: var(--el-color-primary-light-9);
|
|
|
|
|
color: var(--el-color-primary);
|
2021-09-25 16:47:30 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
::v-deep(.jtk-overlay):not(.aLabel) {
|
|
|
|
|
padding: 4px 10px;
|
2022-02-21 23:52:59 +08:00
|
|
|
|
border: 1px solid var(--el-border-color-light, #ebeef5) !important;
|
|
|
|
|
color: var(--el-text-color-secondary) !important;
|
|
|
|
|
background: var(--el-color-white) !important;
|
2021-09-25 16:47:30 +08:00
|
|
|
|
border-radius: 3px;
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
}
|
2022-02-21 23:52:59 +08:00
|
|
|
|
::v-deep(.jtk-overlay.workflow-right-empty-label) {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
2021-09-10 23:24:58 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-21 23:52:59 +08:00
|
|
|
|
.workflow-mask {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
&::after {
|
|
|
|
|
content: '手机版不支持 jsPlumb 操作';
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
color: #666666;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-10 23:24:58 +08:00
|
|
|
|
}
|
|
|
|
|
</style>
|