index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. <template>
  2. <div class="workflow-container">
  3. <div class="workflow-mask" v-if="isShow"></div>
  4. <div class="layout-view-bg-white flex" :style="{ height: `calc(100vh - ${setViewHeight}` }">
  5. <div class="workflow">
  6. <!-- 顶部工具栏 -->
  7. <Tool @tool="onToolClick" />
  8. <!-- 左侧导航区 -->
  9. <div class="workflow-content">
  10. <div id="workflow-left">
  11. <el-scrollbar>
  12. <div
  13. :id="`left${key}`"
  14. v-for="(val, key) in leftNavList"
  15. :key="val.id"
  16. :style="{ height: val.isOpen ? 'auto' : '50px', overflow: 'hidden' }"
  17. class="workflow-left-id"
  18. >
  19. <div class="workflow-left-title" @click="onTitleClick(val)">
  20. <span>{{ val.title }}</span>
  21. <SvgIcon :name="val.isOpen ? 'ele-ArrowDown' : 'ele-ArrowRight'" />
  22. </div>
  23. <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">
  24. <div class="workflow-left-item-icon">
  25. <SvgIcon :name="v.icon" class="workflow-icon-drag" />
  26. <div class="font10 pl5 name">{{ v.name }}</div>
  27. </div>
  28. </div>
  29. </div>
  30. </el-scrollbar>
  31. </div>
  32. <!-- 右侧绘画区 -->
  33. <div id="workflow-right">
  34. <div
  35. v-for="(v, k) in jsplumbData.nodeList"
  36. :key="v.nodeId"
  37. :id="v.nodeId"
  38. :class="v.class"
  39. :style="{ left: v.left, top: v.top }"
  40. @click="onItemCloneClick(k)"
  41. @contextmenu.prevent="onContextmenu(v, k, $event)"
  42. >
  43. <div class="workflow-right-box" :class="{ 'workflow-right-active': jsPlumbNodeIndex === k }">
  44. <div class="workflow-left-item-icon">
  45. <SvgIcon :name="v.icon" class="workflow-icon-drag" />
  46. <div class="font10 pl5 name">{{ v.name }}</div>
  47. </div>
  48. </div>
  49. </div>
  50. </div>
  51. </div>
  52. </div>
  53. </div>
  54. <!-- 节点右键菜单 -->
  55. <Contextmenu :dropdown="dropdownNode" ref="contextmenuNodeRef" @current="onCurrentNodeClick" />
  56. <!-- 线右键菜单 -->
  57. <Contextmenu :dropdown="dropdownLine" ref="contextmenuLineRef" @current="onCurrentLineClick" />
  58. <!-- 抽屉表单、线 -->
  59. <Drawer ref="drawerRef" @label="setLineLabel" @node="setNodeContent" />
  60. <!-- 顶部工具栏-帮助弹窗 -->
  61. <Help ref="helpRef" />
  62. </div>
  63. </template>
  64. <script lang="ts">
  65. import { defineComponent, toRefs, reactive, computed, onMounted, onUnmounted, nextTick, ref } from 'vue';
  66. import { ElMessage, ElMessageBox } from 'element-plus';
  67. import { jsPlumb } from 'jsplumb';
  68. import Sortable from 'sortablejs';
  69. import { useStore } from '/@/store/index';
  70. import Tool from './component/tool/index.vue';
  71. import Help from './component/tool/help.vue';
  72. import Contextmenu from './component/contextmenu/index.vue';
  73. import Drawer from './component/drawer/index.vue';
  74. import commonFunction from '/@/utils/commonFunction';
  75. import { leftNavList } from './js/mock';
  76. import { jsplumbDefaults, jsplumbMakeSource, jsplumbMakeTarget, jsplumbConnect } from './js/config';
  77. // 定义接口来定义对象的类型
  78. interface NodeListState {
  79. id: string | number;
  80. nodeId: string | undefined;
  81. class: HTMLElement | string;
  82. left: number | string;
  83. top: number | string;
  84. icon: string;
  85. name: string;
  86. }
  87. interface LineListState {
  88. sourceId: string;
  89. targetId: string;
  90. label: string;
  91. }
  92. interface XyState {
  93. x: string | number;
  94. y: string | number;
  95. }
  96. interface WorkflowState {
  97. leftNavList: any[];
  98. dropdownNode: XyState;
  99. dropdownLine: XyState;
  100. isShow: boolean;
  101. jsPlumb: any;
  102. jsPlumbNodeIndex: null | number;
  103. jsplumbDefaults: any;
  104. jsplumbMakeSource: any;
  105. jsplumbMakeTarget: any;
  106. jsplumbConnect: any;
  107. jsplumbData: {
  108. nodeList: Array<NodeListState>;
  109. lineList: Array<LineListState>;
  110. };
  111. }
  112. export default defineComponent({
  113. name: 'pagesWorkflow',
  114. components: { Tool, Contextmenu, Drawer, Help },
  115. setup() {
  116. const contextmenuNodeRef = ref();
  117. const contextmenuLineRef = ref();
  118. const drawerRef = ref();
  119. const helpRef = ref();
  120. const store = useStore();
  121. const { copyText } = commonFunction();
  122. const state = reactive<WorkflowState>({
  123. leftNavList: [],
  124. dropdownNode: { x: '', y: '' },
  125. dropdownLine: { x: '', y: '' },
  126. isShow: false,
  127. jsPlumb: null,
  128. jsPlumbNodeIndex: null,
  129. jsplumbDefaults,
  130. jsplumbMakeSource,
  131. jsplumbMakeTarget,
  132. jsplumbConnect,
  133. jsplumbData: {
  134. nodeList: [],
  135. lineList: [],
  136. },
  137. });
  138. // 设置 view 的高度
  139. const setViewHeight = computed(() => {
  140. let { isTagsview } = store.state.themeConfig.themeConfig;
  141. let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
  142. if (isTagsViewCurrenFull) {
  143. return `30px`;
  144. } else {
  145. if (isTagsview) return `114px`;
  146. else return `80px`;
  147. }
  148. });
  149. // 设置 宽度小于 768,不支持操
  150. const setClientWidth = () => {
  151. const clientWidth = document.body.clientWidth;
  152. clientWidth < 768 ? (state.isShow = true) : (state.isShow = false);
  153. };
  154. // 左侧导航-数据初始化
  155. const initLeftNavList = () => {
  156. state.leftNavList = leftNavList;
  157. state.jsplumbData = {
  158. nodeList: [
  159. { nodeId: 'huej738hbji', left: '148px', top: '93px', class: 'workflow-right-clone', icon: 'iconfont icon-gongju', name: '引擎', id: '11' },
  160. {
  161. nodeId: '52kcszzyxrd',
  162. left: '458px',
  163. top: '203px',
  164. class: 'workflow-right-clone',
  165. icon: 'iconfont icon-shouye_dongtaihui',
  166. name: '模版',
  167. id: '12',
  168. },
  169. {
  170. nodeId: 'nltskl6k4me',
  171. left: '164px',
  172. top: '350px',
  173. class: 'workflow-right-clone',
  174. icon: 'iconfont icon-zhongduancanshuchaxun',
  175. name: '名称',
  176. id: '13',
  177. },
  178. ],
  179. lineList: [
  180. { sourceId: 'huej738hbji', targetId: '52kcszzyxrd', label: '传送' },
  181. { sourceId: 'huej738hbji', targetId: 'nltskl6k4me', label: '' },
  182. ],
  183. };
  184. };
  185. // 左侧导航-初始化拖动
  186. const initSortable = () => {
  187. state.leftNavList.forEach((v, k) => {
  188. Sortable.create(document.getElementById(`left${k}`) as HTMLElement, {
  189. group: {
  190. name: 'vue-next-admin-1',
  191. pull: 'clone',
  192. put: false,
  193. },
  194. animation: 0,
  195. sort: false,
  196. draggable: '.workflow-left-item',
  197. forceFallback: true,
  198. onEnd: function (evt: any) {
  199. const { name, icon, id } = evt.clone.dataset;
  200. const { layerX, layerY, clientX, clientY } = evt.originalEvent;
  201. const el = document.querySelector('#workflow-right') as HTMLElement;
  202. const { x, y, width, height } = el.getBoundingClientRect();
  203. if (clientX < x || clientX > width + x || clientY < y || y > y + height) {
  204. ElMessage.warning('请把节点拖入到画布中');
  205. } else {
  206. // 节点id(唯一)
  207. const nodeId = Math.random().toString(36).substr(2, 12);
  208. // 处理节点数据
  209. const node = {
  210. nodeId,
  211. left: `${layerX - 40}px`,
  212. top: `${layerY - 15}px`,
  213. class: 'workflow-right-clone',
  214. name,
  215. icon,
  216. id,
  217. };
  218. // 右侧视图内容数组
  219. state.jsplumbData.nodeList.push(node);
  220. // 元素加载完毕时
  221. nextTick(() => {
  222. // 整个节点作为source或者target
  223. state.jsPlumb.makeSource(nodeId, state.jsplumbMakeSource);
  224. // // 整个节点作为source或者target
  225. state.jsPlumb.makeTarget(nodeId, state.jsplumbMakeTarget, jsplumbConnect);
  226. // 设置节点可以拖拽(此处为id值,非class)
  227. state.jsPlumb.draggable(nodeId, {
  228. containment: 'parent',
  229. stop: (el: any) => {
  230. state.jsplumbData.nodeList.forEach((v) => {
  231. if (v.nodeId === el.el.id) {
  232. // 节点x, y重新赋值,防止再次从左侧导航中拖拽节点时,x, y恢复默认
  233. v.left = `${el.pos[0]}px`;
  234. v.top = `${el.pos[1]}px`;
  235. }
  236. });
  237. },
  238. });
  239. });
  240. }
  241. },
  242. });
  243. });
  244. };
  245. // 初始化 jsPlumb
  246. const initJsPlumb = () => {
  247. (<any>jsPlumb).ready(() => {
  248. state.jsPlumb = (<any>jsPlumb).getInstance({
  249. detachable: false,
  250. Container: 'workflow-right',
  251. });
  252. state.jsPlumb.fire('jsPlumbDemoLoaded', state.jsPlumb);
  253. // 导入默认配置
  254. state.jsPlumb.importDefaults(state.jsplumbDefaults);
  255. // 会使整个jsPlumb立即重绘。
  256. state.jsPlumb.setSuspendDrawing(false, true);
  257. // 初始化节点、线的链接
  258. initJsPlumbConnection();
  259. // 点击线弹出右键菜单
  260. state.jsPlumb.bind('contextmenu', (conn: any, originalEvent: MouseEvent) => {
  261. originalEvent.preventDefault();
  262. const { sourceId, targetId } = conn;
  263. const { clientX, clientY } = originalEvent;
  264. state.dropdownLine.x = clientX;
  265. state.dropdownLine.y = clientY;
  266. const v: any = state.jsplumbData.nodeList.find((v) => v.nodeId === targetId);
  267. const line: any = state.jsplumbData.lineList.find((v) => v.sourceId === sourceId && v.targetId === targetId);
  268. v.type = 'line';
  269. v.label = line.label;
  270. contextmenuLineRef.value.openContextmenu(v, conn);
  271. });
  272. // 连线之前
  273. state.jsPlumb.bind('beforeDrop', (conn: any) => {
  274. const { sourceId, targetId } = conn;
  275. const item = state.jsplumbData.lineList.find((v) => v.sourceId === sourceId && v.targetId === targetId);
  276. if (item) {
  277. ElMessage.warning('关系已存在,不可重复连接');
  278. return false;
  279. } else {
  280. return true;
  281. }
  282. });
  283. // 连线时
  284. state.jsPlumb.bind('connection', (conn: any) => {
  285. const { sourceId, targetId } = conn;
  286. state.jsplumbData.lineList.push({
  287. sourceId,
  288. targetId,
  289. label: '',
  290. });
  291. });
  292. // 删除连线时回调函数
  293. state.jsPlumb.bind('connectionDetached', (conn: any) => {
  294. const { sourceId, targetId } = conn;
  295. state.jsplumbData.lineList = state.jsplumbData.lineList.filter((line) => {
  296. if (line.sourceId == sourceId && line.targetId == targetId) {
  297. return false;
  298. }
  299. return true;
  300. });
  301. });
  302. });
  303. };
  304. // 初始化节点、线的链接
  305. const initJsPlumbConnection = () => {
  306. // 节点
  307. state.jsplumbData.nodeList.forEach((v) => {
  308. // 整个节点作为source或者target
  309. state.jsPlumb.makeSource(v.nodeId, state.jsplumbMakeSource);
  310. // 整个节点作为source或者target
  311. state.jsPlumb.makeTarget(v.nodeId, state.jsplumbMakeTarget, jsplumbConnect);
  312. // 设置节点可以拖拽(此处为id值,非class)
  313. state.jsPlumb.draggable(v.nodeId, {
  314. containment: 'parent',
  315. stop: (el: any) => {
  316. state.jsplumbData.nodeList.forEach((v) => {
  317. if (v.nodeId === el.el.id) {
  318. // 节点x, y重新赋值,防止再次从左侧导航中拖拽节点时,x, y恢复默认
  319. v.left = `${el.pos[0]}px`;
  320. v.top = `${el.pos[1]}px`;
  321. }
  322. });
  323. },
  324. });
  325. });
  326. // 线
  327. state.jsplumbData.lineList.forEach((v) => {
  328. state.jsPlumb.connect(
  329. {
  330. source: v.sourceId,
  331. target: v.targetId,
  332. label: v.label,
  333. },
  334. state.jsplumbConnect
  335. );
  336. });
  337. };
  338. // 左侧导航-菜单标题点击
  339. const onTitleClick = (val: any) => {
  340. val.isOpen = !val.isOpen;
  341. };
  342. // 右侧内容区-当前项点击
  343. const onItemCloneClick = (k: number) => {
  344. state.jsPlumbNodeIndex = k;
  345. };
  346. // 右侧内容区-当前项右键菜单点击
  347. const onContextmenu = (v: any, k: number, e: MouseEvent) => {
  348. state.jsPlumbNodeIndex = k;
  349. const { clientX, clientY } = e;
  350. state.dropdownNode.x = clientX;
  351. state.dropdownNode.y = clientY;
  352. v.type = 'node';
  353. v.label = '';
  354. let item: any = {};
  355. state.leftNavList.forEach((l) => {
  356. if (l.children) if (l.children.find((c: any) => c.id === v.id)) item = l.children.find((c: any) => c.id === v.id);
  357. });
  358. v.from = item.form;
  359. contextmenuNodeRef.value.openContextmenu(v);
  360. };
  361. // 右侧内容区-当前项右键菜单点击回调(节点)
  362. const onCurrentNodeClick = (item: any) => {
  363. const { contextMenuClickId, nodeId } = item;
  364. if (contextMenuClickId === 0) {
  365. const nodeIndex = state.jsplumbData.nodeList.findIndex((item) => item.nodeId === nodeId);
  366. state.jsplumbData.nodeList.splice(nodeIndex, 1);
  367. state.jsPlumb.removeAllEndpoints(nodeId);
  368. state.jsPlumbNodeIndex = null;
  369. } else if (contextMenuClickId === 1) {
  370. drawerRef.value.open(item);
  371. }
  372. };
  373. // 右侧内容区-当前项右键菜单点击回调(线)
  374. const onCurrentLineClick = (item: any, conn: any) => {
  375. const { contextMenuClickId } = item;
  376. const { endpoints } = conn;
  377. const intercourse: any = [];
  378. endpoints.forEach((v: any) => {
  379. intercourse.push({
  380. id: v.element.id,
  381. innerText: v.element.innerText,
  382. });
  383. });
  384. item.contact = `${intercourse[0].innerText}(${intercourse[0].id}) => ${intercourse[1].innerText}(${intercourse[1].id})`;
  385. if (contextMenuClickId === 0) state.jsPlumb.deleteConnection(conn);
  386. else if (contextMenuClickId === 1) drawerRef.value.open(item, conn);
  387. };
  388. // 设置线的 label
  389. const setLineLabel = (obj: any) => {
  390. const { sourceId, targetId, label } = obj;
  391. const conn = state.jsPlumb.getConnections({
  392. source: sourceId,
  393. target: targetId,
  394. })[0];
  395. conn.setLabel(label);
  396. if (!label || label === '') {
  397. conn.addClass('workflow-right-empty-label');
  398. } else {
  399. conn.removeClass('workflow-right-empty-label');
  400. conn.addClass('workflow-right-label');
  401. }
  402. state.jsplumbData.lineList.forEach((v) => {
  403. if (v.sourceId === sourceId && v.targetId === targetId) v.label = label;
  404. });
  405. };
  406. // 设置节点内容
  407. const setNodeContent = (obj: any) => {
  408. const { nodeId, name, icon } = obj;
  409. // 设置节点 name 与 icon
  410. state.jsplumbData.nodeList.forEach((v) => {
  411. if (v.nodeId === nodeId) {
  412. v.name = name;
  413. v.icon = icon;
  414. }
  415. });
  416. // 重绘
  417. nextTick(() => {
  418. state.jsPlumb.setSuspendDrawing(false, true);
  419. });
  420. };
  421. // 顶部工具栏-当前项点击
  422. const onToolClick = (fnName: String) => {
  423. switch (fnName) {
  424. case 'help':
  425. onToolHelp();
  426. break;
  427. case 'download':
  428. onToolDownload();
  429. break;
  430. case 'submit':
  431. onToolSubmit();
  432. break;
  433. case 'copy':
  434. onToolCopy();
  435. break;
  436. case 'del':
  437. onToolDel();
  438. break;
  439. case 'fullscreen':
  440. onToolFullscreen();
  441. break;
  442. }
  443. };
  444. // 顶部工具栏-帮助
  445. const onToolHelp = () => {
  446. nextTick(() => {
  447. helpRef.value.open();
  448. });
  449. };
  450. // 顶部工具栏-下载
  451. const onToolDownload = () => {
  452. const { globalTitle } = store.state.themeConfig.themeConfig;
  453. const href = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(state.jsplumbData, null, '\t'));
  454. const aLink = document.createElement('a');
  455. aLink.setAttribute('href', href);
  456. aLink.setAttribute('download', `${globalTitle}工作流.json`);
  457. aLink.click();
  458. aLink.remove();
  459. ElMessage.success('下载成功');
  460. };
  461. // 顶部工具栏-提交
  462. const onToolSubmit = () => {
  463. // console.log(state.jsplumbData);
  464. ElMessage.success('数据提交成功');
  465. };
  466. // 顶部工具栏-复制
  467. const onToolCopy = () => {
  468. copyText(JSON.stringify(state.jsplumbData));
  469. };
  470. // 顶部工具栏-删除
  471. const onToolDel = () => {
  472. ElMessageBox.confirm('此操作将清空画布,是否继续?', '提示', {
  473. confirmButtonText: '清空',
  474. cancelButtonText: '取消',
  475. })
  476. .then(() => {
  477. state.jsplumbData.nodeList.forEach((v) => {
  478. state.jsPlumb.removeAllEndpoints(v.nodeId);
  479. });
  480. nextTick(() => {
  481. state.jsplumbData = {
  482. nodeList: [],
  483. lineList: [],
  484. };
  485. ElMessage.success('清空画布成功');
  486. });
  487. })
  488. .catch(() => {});
  489. };
  490. // 顶部工具栏-全屏
  491. const onToolFullscreen = () => {
  492. store.dispatch('tagsViewRoutes/setCurrenFullscreen', true);
  493. };
  494. // 页面加载时
  495. onMounted(async () => {
  496. await initLeftNavList();
  497. initSortable();
  498. initJsPlumb();
  499. setClientWidth();
  500. window.addEventListener('resize', setClientWidth);
  501. });
  502. // 页面卸载时
  503. onUnmounted(() => {
  504. window.removeEventListener('resize', setClientWidth);
  505. });
  506. return {
  507. setViewHeight,
  508. setClientWidth,
  509. setLineLabel,
  510. setNodeContent,
  511. onTitleClick,
  512. onItemCloneClick,
  513. onContextmenu,
  514. onCurrentNodeClick,
  515. onCurrentLineClick,
  516. contextmenuNodeRef,
  517. contextmenuLineRef,
  518. drawerRef,
  519. helpRef,
  520. onToolClick,
  521. ...toRefs(state),
  522. };
  523. },
  524. });
  525. </script>
  526. <style scoped lang="scss">
  527. .workflow-container {
  528. position: relative;
  529. .workflow {
  530. display: flex;
  531. height: 100%;
  532. width: 100%;
  533. flex-direction: column;
  534. .workflow-content {
  535. display: flex;
  536. height: calc(100% - 35px);
  537. #workflow-left {
  538. width: 220px;
  539. height: 100%;
  540. border-right: 1px solid var(--el-border-color-light, #ebeef5);
  541. ::v-deep(.el-collapse-item__content) {
  542. padding-bottom: 0;
  543. }
  544. .workflow-left-title {
  545. height: 50px;
  546. display: flex;
  547. align-items: center;
  548. padding: 0 10px;
  549. border-top: 1px solid var(--el-border-color-light, #ebeef5);
  550. color: var(--el-text-color-primary);
  551. cursor: default;
  552. span {
  553. flex: 1;
  554. }
  555. }
  556. .workflow-left-item {
  557. display: inline-block;
  558. width: calc(50% - 15px);
  559. position: relative;
  560. cursor: move;
  561. margin: 0 0 10px 10px;
  562. .workflow-left-item-icon {
  563. height: 35px;
  564. display: flex;
  565. align-items: center;
  566. transition: all 0.3s ease;
  567. padding: 5px 10px;
  568. border: 1px dashed transparent;
  569. background: var(--next-bg-color);
  570. border-radius: 3px;
  571. i,
  572. .name {
  573. color: var(--el-text-color-secondary);
  574. transition: all 0.3s ease;
  575. white-space: nowrap;
  576. text-overflow: ellipsis;
  577. overflow: hidden;
  578. }
  579. &:hover {
  580. transition: all 0.3s ease;
  581. border: 1px dashed var(--el-color-primary);
  582. background: var(--el-color-primary-light-9);
  583. border-radius: 5px;
  584. i,
  585. .name {
  586. transition: all 0.3s ease;
  587. color: var(--el-color-primary);
  588. }
  589. }
  590. }
  591. }
  592. & .workflow-left-id:first-of-type {
  593. .workflow-left-title {
  594. border-top: none;
  595. }
  596. }
  597. }
  598. #workflow-right {
  599. flex: 1;
  600. position: relative;
  601. overflow: hidden;
  602. height: 100%;
  603. background-image: linear-gradient(90deg, rgb(156 214 255 / 15%) 10%, rgba(0, 0, 0, 0) 10%),
  604. linear-gradient(rgb(156 214 255 / 15%) 10%, rgba(0, 0, 0, 0) 10%);
  605. background-size: 10px 10px;
  606. .workflow-right-clone {
  607. position: absolute;
  608. .workflow-right-box {
  609. height: 35px;
  610. align-items: center;
  611. color: var(--el-text-color-secondary);
  612. padding: 0 10px;
  613. border-radius: 3px;
  614. cursor: move;
  615. transition: all 0.3s ease;
  616. min-width: 94.5px;
  617. background: var(--el-color-white);
  618. border: 1px solid var(--el-border-color-light, #ebeef5);
  619. .workflow-left-item-icon {
  620. display: flex;
  621. align-items: center;
  622. height: 35px;
  623. }
  624. &:hover {
  625. border: 1px dashed var(--el-color-primary);
  626. background: var(--el-color-primary-light-9);
  627. transition: all 0.3s ease;
  628. color: var(--el-color-primary);
  629. i {
  630. cursor: Crosshair;
  631. }
  632. }
  633. }
  634. .workflow-right-active {
  635. border: 1px dashed var(--el-color-primary);
  636. background: var(--el-color-primary-light-9);
  637. color: var(--el-color-primary);
  638. }
  639. }
  640. ::v-deep(.jtk-overlay):not(.aLabel) {
  641. padding: 4px 10px;
  642. border: 1px solid var(--el-border-color-light, #ebeef5) !important;
  643. color: var(--el-text-color-secondary) !important;
  644. background: var(--el-color-white) !important;
  645. border-radius: 3px;
  646. font-size: 10px;
  647. }
  648. ::v-deep(.jtk-overlay.workflow-right-empty-label) {
  649. display: none;
  650. }
  651. }
  652. }
  653. }
  654. .workflow-mask {
  655. position: absolute;
  656. top: 0;
  657. right: 0;
  658. bottom: 0;
  659. left: 0;
  660. &::after {
  661. content: '手机版不支持 jsPlumb 操作';
  662. position: absolute;
  663. top: 0;
  664. right: 0;
  665. bottom: 0;
  666. left: 0;
  667. z-index: 1;
  668. background: rgba(255, 255, 255, 0.9);
  669. color: #666666;
  670. display: flex;
  671. align-items: center;
  672. justify-content: center;
  673. }
  674. }
  675. }
  676. </style>