index.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. <template>
  2. <div v-loading="loading" class="layout-padding w100 h100">
  3. <div class="layout-padding-auto layout-padding-view pd20">
  4. <!-- 外部内容表单 -->
  5. <el-form :model="form" label-width="80px" ref="ruleFormRef">
  6. <el-row :gutter="100">
  7. <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
  8. <el-form-item label="模板名称" prop="name" :rules="[{ required: true, message: '请填写模板名称', trigger: 'blur' }]">
  9. <el-input v-model="form.name" placeholder="请填写模板名称" clearable></el-input>
  10. </el-form-item>
  11. </el-col>
  12. <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
  13. <el-form-item label="模板编码" prop="code" :rules="[{ required: true, message: '请填写模板编码', trigger: 'blur' }]">
  14. <el-input v-model="form.code" placeholder="请填写模板编码" clearable></el-input>
  15. </el-form-item>
  16. </el-col>
  17. <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
  18. <el-form-item label="模板类型" prop="flowType" :rules="[{ required: true, message: '请选择模板类型', trigger: 'change' }]">
  19. <el-select v-model="form.flowType" class="w100" placeholder="请选择模板类型">
  20. <el-option v-for="item in baseDataResult.flowTypeOptions" :key="item.key" :label="item.value" :value="item.key" />
  21. </el-select>
  22. </el-form-item>
  23. </el-col>
  24. <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
  25. <el-form-item label="展示主办" prop="isMainHandlerShow" :rules="[{ required: false, message: '请选择模板类型', trigger: 'change' }]">
  26. <el-switch v-model="form.isMainHandlerShow" inline-prompt active-text="展示" inactive-text="隐藏" />
  27. </el-form-item>
  28. </el-col>
  29. <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
  30. <el-form-item label="模板描述" prop="description" :rules="[{ required: false, message: '请填写模板描述', trigger: 'blur' }]">
  31. <el-input v-model="form.description" placeholder="请填写模板描述" clearable></el-input>
  32. </el-form-item>
  33. </el-col>
  34. </el-row>
  35. </el-form>
  36. <!-- 流程图画布内容 -->
  37. <div class="border w100 h100" style="position: relative; flex: 1">
  38. <div class="w100 h100" ref="lfElRef"></div>
  39. </div>
  40. </div>
  41. <!-- 节点内容弹窗 -->
  42. <PropertySetting
  43. ref="propertySettingRef"
  44. v-model="formData"
  45. @change="handlePropertyChange"
  46. @changeOther="handlePropertyChangeOther"
  47. :baseData="baseDataResult"
  48. :nodes="nodes"
  49. />
  50. </div>
  51. </template>
  52. <script setup lang="ts" name="hotlineFlowDesigner">
  53. import { defineAsyncComponent, nextTick, onMounted, reactive, Ref, ref, unref, watch } from 'vue';
  54. import { useRoute, useRouter } from 'vue-router';
  55. import LogicFlow from '@logicflow/core';
  56. import { Control, DndPanel, Group, InsertNodeInPolyline, Menu, MiniMap, SelectionSelect, Snapshot } from '@logicflow/extension';
  57. // import '@logicflow/core/dist/style/index.css';
  58. import '@logicflow/core/lib/style/index.css';
  59. import '@logicflow/extension/lib/style/index.css';
  60. import { SnakerFlowAdapter, SnakerFlowElement } from './snakerflow/index';
  61. import { NodeTypeEnum } from './enums';
  62. import { baseData, workflowAdd, workflowUpdate } from '@/api/system/workflow';
  63. import mittBus from '@/utils/mitt';
  64. import { throttle } from '@/utils/tools';
  65. import { ElMessage, FormInstance } from 'element-plus';
  66. // 引入组件
  67. const PropertySetting = defineAsyncComponent(() => import('./PropertySetting/index.vue')); // 节点属性设置
  68. const route = useRoute(); // 当前路由信息
  69. const router = useRouter(); // 路由实例
  70. const emits = defineEmits(['update:modelValue', 'on-save']); // 定义组件事件
  71. // 提交表单数据
  72. let formData = reactive({} as any);
  73. // 外层表单
  74. let form = reactive<Record<string, any>>({});
  75. const ruleFormRef = ref<RefType>();
  76. // 定义组件接收的参数
  77. const props = defineProps({
  78. modelValue: {
  79. type: [Object, String],
  80. },
  81. config: {
  82. type: Object,
  83. default() {
  84. return {
  85. grid: {
  86. size: 20,
  87. visible: true,
  88. type: 'dot',
  89. config: {
  90. color: '#ababab',
  91. thickness: 1,
  92. },
  93. },
  94. };
  95. },
  96. },
  97. highLight: {
  98. // 高亮数据
  99. type: Object,
  100. default() {
  101. return {};
  102. },
  103. },
  104. viewer: {
  105. // 预览模式
  106. type: Boolean,
  107. default: false,
  108. },
  109. loading: {
  110. //加载状态
  111. type: Boolean,
  112. default: false,
  113. },
  114. extendAttrConfig: {
  115. // 扩展属性配置
  116. type: Object,
  117. default: () => {},
  118. },
  119. });
  120. // 监听流程变化
  121. watch(
  122. () => props.modelValue,
  123. () => {
  124. reRender(props.modelValue);
  125. },
  126. {
  127. deep: true,
  128. }
  129. );
  130. // 初始化
  131. // 定义挂载元素Ref
  132. const lfElRef: Ref = ref(null);
  133. // 定义LogicFlow实例
  134. const lfInstance = ref(null) as Ref<LogicFlow | null>;
  135. const init = async () => {
  136. // 画布配置
  137. LogicFlow.use(Snapshot);
  138. LogicFlow.use(DndPanel);
  139. LogicFlow.use(SelectionSelect);
  140. LogicFlow.use(Menu);
  141. LogicFlow.use(Control);
  142. LogicFlow.use(Group);
  143. LogicFlow.use(InsertNodeInPolyline);
  144. LogicFlow.use(MiniMap);
  145. LogicFlow.use(SnakerFlowElement);
  146. LogicFlow.use(SnakerFlowAdapter);
  147. const defaultConfig: any = {};
  148. lfInstance.value = new LogicFlow({
  149. container: unref(lfElRef),
  150. stopScrollGraph: true, // 进禁止鼠标滚动移动画布
  151. stopZoomGraph: false, // 禁止缩放画布
  152. autoExpand: false, // 节点拖动靠近画布边缘时是否自动扩充画布,
  153. keyboard: {
  154. // 键盘事件
  155. enabled: true,
  156. },
  157. ...props.config,
  158. ...defaultConfig,
  159. plugins: [MiniMap],
  160. pluginsOptions: {
  161. miniMap: {
  162. ...miniMapOptions,
  163. showEdge: true,
  164. },
  165. },
  166. });
  167. // 初始化操作
  168. initOp();
  169. reRender(props.modelValue as any);
  170. // 初始化事件
  171. initEvent();
  172. };
  173. const miniMapOptions: MiniMap.MiniMapOption = {
  174. isShowHeader: false,
  175. isShowCloseIcon: true,
  176. headerTitle: 'MiniMap',
  177. width: 200,
  178. height: 120,
  179. // leftPosition: 100,
  180. // topPosition: 100,
  181. };
  182. // 初始化操作
  183. const initOp = () => {
  184. const lf: any = unref(lfInstance);
  185. if (!lf) return;
  186. if (props.viewer) {
  187. // 预览模式时
  188. lf.extension.menu.setMenuConfig({
  189. nodeMenu: [],
  190. edgeMenu: [],
  191. });
  192. // 删除上一步
  193. lf.extension.control.removeItem('undo');
  194. // 删除下一步
  195. lf.extension.control.removeItem('redo');
  196. return;
  197. }
  198. lf.extension.menu.setMenuConfig({
  199. nodeMenu: [
  200. {
  201. text: '删除',
  202. callback(node: any) {
  203. lf.deleteNode(node.id);
  204. },
  205. },
  206. ], // 覆盖默认的节点右键菜单
  207. edgeMenu: [
  208. {
  209. text: '删除',
  210. callback(node: any) {
  211. lf.deleteEdge(node.id);
  212. },
  213. },
  214. ], // 删除默认的边右键菜单
  215. graphMenu: [], // 覆盖默认的边右键菜单,与false表现一样
  216. });
  217. // 控制面板-清空画布
  218. lf.extension.control.addItem({
  219. iconClass: 'lf-control-clear',
  220. title: '清空当前流程图',
  221. text: '清空',
  222. onClick: () => {
  223. lf.clearData();
  224. },
  225. });
  226. // 导航图
  227. lf.extension.control.addItem({
  228. iconClass: 'custom-minimap',
  229. title: '打开流程图导航',
  230. text: '导航',
  231. // onMouseEnter: (lf:any, ev:any) => {
  232. // const position = lf.getPointByClient(ev.x, ev.y);
  233. // lf.extension.miniMap.show(
  234. // position.domOverlayPosition.x - 120,
  235. // position.domOverlayPosition.y + 35
  236. // );
  237. // },
  238. onClick: (lf: any, ev: any) => {
  239. const visible = lf.extension.miniMap.isShow;
  240. const miniMap = lfInstance.value.extension.miniMap as MiniMap;
  241. if (visible) {
  242. miniMap.hide();
  243. } else {
  244. miniMap.show();
  245. }
  246. /* const position = lf.getPointByClient(ev.x, ev.y);
  247. lf.extension.miniMap.show(position.domOverlayPosition.x - 120, position.domOverlayPosition.y + 35);*/
  248. },
  249. });
  250. // 控制面板-暂存
  251. lf.extension.control.addItem({
  252. iconClass: 'lf-control-save',
  253. title: '保存',
  254. text: '保存',
  255. onClick: () => {
  256. saveOnly(ruleFormRef.value);
  257. },
  258. });
  259. lf.extension.snapshot.useGlobalRules = false;
  260. lf.extension.snapshot.customCssRules = `
  261. .lf-canvas-overlay {
  262. background: white;
  263. }
  264. `;
  265. // 控制面板-导出
  266. lf.extension.control.addItem({
  267. iconClass: 'lf-control-release',
  268. title: '导出为图片',
  269. text: '导出',
  270. onClick: () => {
  271. const exportName = lf.graphModel.name ? lf.graphModel.name : '流程图';
  272. lf.getSnapshot(exportName + `${new Date().getTime()}`);
  273. },
  274. });
  275. // 控制面板-发布
  276. // lf.extension.control.addItem({
  277. // iconClass: 'lf-control-publish',
  278. // title: '发布流程模板',
  279. // text: '发布',
  280. // onClick: () => {
  281. // publish(ruleFormRef.value);
  282. // },
  283. // });
  284. // 设置默认边
  285. lf.setDefaultEdgeType('hotline:transition');
  286. // 设置拖拽面板
  287. lf.extension.dndPanel.setPatternItems([
  288. {
  289. label: '选区',
  290. icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAOVJREFUOBGtVMENwzAIjKP++2026ETdpv10iy7WFbqFyyW6GBywLCv5gI+Dw2Bluj1znuSjhb99Gkn6QILDY2imo60p8nsnc9bEo3+QJ+AKHfMdZHnl78wyTnyHZD53Zzx73MRSgYvnqgCUHj6gwdck7Zsp1VOrz0Uz8NbKunzAW+Gu4fYW28bUYutYlzSa7B84Fh7d1kjLwhcSdYAYrdkMQVpsBr5XgDGuXwQfQr0y9zwLda+DUYXLaGKdd2ZTtvbolaO87pdo24hP7ov16N0zArH1ur3iwJpXxm+v7oAJNR4JEP8DoAuSFEkYH7cAAAAASUVORK5CYII=',
  291. callback: () => {
  292. lf.extension.selectionSelect.openSelectionSelect();
  293. lf.once('selection:selected', () => {
  294. lf.extension.selectionSelect.closeSelectionSelect();
  295. });
  296. },
  297. },
  298. {
  299. type: 'hotline:start',
  300. text: '开始',
  301. label: '开始节点',
  302. properties: {
  303. stepType: 1, // 节点类型(开始1 结束2)
  304. },
  305. icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAnBJREFUOBGdVL1rU1EcPfdGBddmaZLiEhdx1MHZQXApraCzQ7GKLgoRBxMfcRELuihWKcXFRcEWF8HBf0DdDCKYRZpnl7p0svLe9Zzbd29eQhTbC8nv+9zf130AT63jvooOGS8Vf9Nt5zxba7sXQwODfkWpkbjTQfCGUd9gIp3uuPP8bZ946g56dYQvnBg+b1HB8VIQmMFrazKcKSvFW2dQTxJnJdQ77urmXWOMBCmXM2Rke4S7UAW+/8ywwFoewmBps2tu7mbTdp8VMOkIRAkKfrVawalJTtIliclFbaOBqa0M2xImHeVIfd/nKAfVq/LGnPss5Kh00VEdSzfwnBXPUpmykNss4lUI9C1ga+8PNrBD5YeqRY2Zz8PhjooIbfJXjowvQJBqkmEkVnktWhwu2SM7SMx7Cj0N9IC0oQXRo8xwAGzQms+xrB/nNSUWVveI48ayrFGyC2+E2C+aWrZHXvOuz+CiV6iycWe1Rd1Q6+QUG07nb5SbPrL4426d+9E1axKjY3AoRrlEeSQo2Eu0T6BWAAr6COhTcWjRaYfKG5csnvytvUr/WY4rrPMB53Uo7jZRjXaG6/CFfNMaXEu75nG47X+oepU7PKJvvzGDY1YLSKHJrK7vFUwXKkaxwhCW3u+sDFMVrIju54RYYbFKpALZAo7sB6wcKyyrd+aBMryMT2gPyD6GsQoRFkGHr14TthZni9ck0z+Pnmee460mHXbRAypKNy3nuMdrWgVKj8YVV8E7PSzp1BZ9SJnJAsXdryw/h5ctboUVi4AFiCd+lQaYMw5z3LGTBKjLQOeUF35k89f58Vv/tGh+l+PE/wG0rgfIUbZK5AAAAABJRU5ErkJggg==',
  306. },
  307. {
  308. type: 'hotline:task',
  309. text: '流程节点',
  310. label: '流程节点',
  311. properties: {},
  312. icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0D+g1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQ+BVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeA+YeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQy+JjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTby+r31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0+HAd2GelJsZz1OJegCxqzl+CLVHa/IibuHeJ1HAKzhuDR+ymNaRFM+4jU6UWKXorRmbyqkq/D76FffevwdCp+jN3UAN/C9JRVTDuOxC/oh+EdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg==',
  313. className: 'important-node',
  314. },
  315. {
  316. type: 'hotline:end',
  317. text: '结束',
  318. label: '结束节点',
  319. properties: {
  320. stepType: 2, // 节点类型(开始1 结束2)
  321. },
  322. icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAA1BJREFUOBFtVE1IVUEYPXOf+tq40Y3vPcmFIdSjIorWoRG0ERWUgnb5FwVhYQSl72oUoZAboxKNFtWiwKRN0M+jpfSzqJAQclHo001tKkjl3emc8V69igP3znzfnO/M9zcDcKT67azmjYWTwl9Vn7Vumeqzj1DVb6cleQY4oAVnIOPb+mKAGxQmKI5CWNJ2aLPatxWa3aB9K7/fB+/Z0jUF6TmMlFLQqrkECWQzOZxYGjTlOl8eeKaIY5yHnFn486xBustDjWT6dG7pmjHOJd+33t0iitTPkK6tEvjxq4h2MozQ6WFSX/LkDUGfFwfhEZj1Auz/U4pyAi5Sznd7uKzznXeVHlI/Aywmk6j7fsUsEuCGADrWARXXwjxWQsUbIupDHJI7kF5dRktg0eN81IbiZXiTESic50iwS+t1oJgL83jAiBupLDCQqwziaWSoAFSeIR3P5Xv5az00wyIn35QRYTwdSYbz8pH8fxUUAtxnFvYmEmgI0wYXUXcCCSpeEVpXlsRhBnCEATxWylL9+EKCAYhe1NGstUa6356kS9NVvt3DU2fd+Wtbm/+lSbylJqsqkSm9CRhvoJVlvKPvF1RKY/FcPn5j4UfIMLn8D4UYb54BNsilTDXKnF4CfTobA0FpoW/LSp306wkXM+XaOJhZaFkcNM82ASNAWMrhrUbRfmyeI1FvRBTpN06WKxa9BK0o2E4Pd3zfBBEwPsv9sQBnmLVbLEIZ/Xe9LYwJu/Er17W6HYVBc7vmuk0xUQ+pqxdom5Fnp55SiytXLPYoMXNM4u4SNSCFWnrVIzKG3EGyMXo6n/BQOe+bX3FClY4PwydVhthOZ9NnS+ntiLh0fxtlUJHAuGaFoVmttpVMeum0p3WEXbcll94l1wM/gZ0Ccczop77VvN2I7TlsZCsuXf1WHvWEhjO8DPtyOVg2/mvK9QqboEth+7pD6NUQC1HN/TwvydGBARi9MZSzLE4b8Ru3XhX2PBxf8E1er2A6516o0w4sIA+lwURhAON82Kwe2iDAC1Watq4XHaGQ7skLcFOtI5lDxuM2gZe6WFIotPAhbaeYlU4to5cuarF1QrcZ/lwrLaCJl66JBocYZnrNlvm2+MBCTmUymPrYZVbjdlr/BxlMjmNmNI3SAAAAAElFTkSuQmCC',
  323. },
  324. ]);
  325. };
  326. // 初始化事件
  327. // 定义表单信息
  328. const propertySettingRef = ref(null) as any;
  329. // 当前操作节点/或边id
  330. const currentOpId = ref('');
  331. const nodes = ref<EmptyArrayType>([]);
  332. const initEvent = () => {
  333. if (props.viewer) return;
  334. // 初始化事件
  335. const lf = unref(lfInstance);
  336. if (!lf) return;
  337. const eventCenter: any = lf.graphModel.eventCenter;
  338. // 空白区右键事件-弹出流程属性表单
  339. // eventCenter.on('blank:contextmenu', () => {
  340. // propertySettingRef.value.show({
  341. // name: lf.graphModel.name,
  342. // code: lf.graphModel.code,
  343. // version: lf.graphModel.version,
  344. // description: lf.graphModel.description,
  345. // moduleName: lf.graphModel.moduleName,
  346. // type: 'process'
  347. // })
  348. // })
  349. // 节点点击事件
  350. eventCenter.on('node:click', (args: any, data) => {
  351. if (['hotline:start', 'hotline:task', 'hotline:end'].includes(args.data.type)) {
  352. currentOpId.value = args.data.id;
  353. propertySettingRef.value.show({
  354. ...args.data.properties,
  355. code: args.data.id,
  356. id: args.data.id,
  357. name: args.data.text?.value,
  358. type: args.data.type,
  359. });
  360. }
  361. nodes.value = lf.graphModel.nodes;
  362. });
  363. // 边点击事件
  364. // eventCenter.on('edge:click', (args: any) => {
  365. // console.log(args, 'args')
  366. // currentOpId.value = args.data.id
  367. // propertySettingRef.value.show({
  368. // ...args.data.properties,
  369. // id: args.data.id,
  370. // name: args.data.text?.value,
  371. // type: args.data.type
  372. // })
  373. // })
  374. };
  375. const closePage = () => {
  376. // 更新
  377. ElMessage.success('操作成功');
  378. // 关闭当前 tagsView
  379. mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
  380. mittBus.emit('clearCache', 'systemWorkflow');
  381. router.push({
  382. name: 'systemWorkflow',
  383. state: {
  384. index: '1',
  385. },
  386. });
  387. };
  388. // 暂存(保存为草稿)
  389. const saveOnly = throttle((formEl: FormInstance | undefined) => {
  390. // lfInstance.value?.getSnapshot(); // 下载为图片
  391. const lf = unref(lfInstance);
  392. if (!lf) return;
  393. if (!formEl) return;
  394. // 表单验证
  395. formEl.validate((valid: boolean) => {
  396. if (!valid) return;
  397. // 流程模板属性 最外层
  398. Object.keys(form).forEach((key: string) => {
  399. // 监听属性变化 并保存
  400. lf.graphModel[key] = form[key];
  401. });
  402. const { submitData } = getGraphData();
  403. if (submitData.error) {
  404. //错误提示
  405. ElMessage.warning(submitData.error);
  406. return;
  407. }
  408. if (route.params.id) {
  409. workflowUpdate(submitData).then(() => {
  410. //更新
  411. closePage();
  412. });
  413. } else {
  414. workflowAdd(submitData).then(() => {
  415. //保存
  416. closePage();
  417. });
  418. }
  419. emits('on-save', getGraphData());
  420. });
  421. }, 300);
  422. // 重新渲染
  423. const reRender = (data: any): void => {
  424. const lf = unref(lfInstance);
  425. if (!lf) return;
  426. lf.render(data);
  427. if (route.params.id) {
  428. nextTick(() => {
  429. // 最外层扩展属性赋值
  430. form.name = lf.graphModel.name;
  431. form.code = lf.graphModel.code;
  432. form.flowType = lf.graphModel.flowType;
  433. form.description = lf.graphModel.description;
  434. form.isMainHandlerShow = lf.graphModel.isMainHandlerShow;
  435. form.id = route.params.id;
  436. form.name = lf.graphModel.name;
  437. });
  438. }
  439. };
  440. // 处理当前节点属性值变化事件
  441. const handlePropertyChange = (e: any) => {
  442. const lf = unref(lfInstance);
  443. if (!lf) return;
  444. if (([NodeTypeEnum.task, NodeTypeEnum.start, NodeTypeEnum.end] as NodeTypeEnum[]).includes(e.type)) {
  445. // 节点属性
  446. const nodeId = unref(currentOpId);
  447. // 节点信息
  448. if (e.propertyName === 'id') {
  449. // 更新唯一标识
  450. if (!lf.getNodeModelById(e.propertyValue)) {
  451. lf.changeNodeId(nodeId, e.propertyValue);
  452. currentOpId.value = e.propertyValue;
  453. }
  454. } else if (e.propertyName === 'name') {
  455. // 更新节点文本值
  456. lf.updateText(nodeId, e.propertyValue);
  457. } else {
  458. // 更新基础属性
  459. lf.setProperties(nodeId, {
  460. [e.propertyName]: e.propertyValue,
  461. });
  462. }
  463. emits('update:modelValue', getGraphData());
  464. }
  465. };
  466. /* 处理其他节点的属性变化 */
  467. const handlePropertyChangeOther = (e: any) => {
  468. const lf = unref(lfInstance);
  469. if (!lf) return;
  470. if (([NodeTypeEnum.task, NodeTypeEnum.start, NodeTypeEnum.end] as NodeTypeEnum[]).includes(e.type)) {
  471. // 节点属性
  472. const nodeId = unref(e.id);
  473. // 节点信息
  474. if (e.propertyName === 'id') {
  475. // 更新唯一标识
  476. if (!lf.getNodeModelById(e.propertyValue)) {
  477. lf.changeNodeId(nodeId, e.propertyValue);
  478. currentOpId.value = e.propertyValue;
  479. }
  480. } else if (e.propertyName === 'name') {
  481. // 更新节点文本值
  482. lf.updateText(nodeId, e.propertyValue);
  483. } else {
  484. // 更新基础属性
  485. lf.setProperties(nodeId, {
  486. [e.propertyName]: e.propertyValue,
  487. });
  488. }
  489. emits('update:modelValue', getGraphData());
  490. }
  491. };
  492. /**
  493. * 获取流程数据
  494. */
  495. const getGraphData = () => {
  496. const lf = unref(lfInstance);
  497. if (!lf) return {};
  498. return lf.getGraphData();
  499. };
  500. // 刷新导入下拉数据
  501. const refreshImport = () => {
  502. // return unref(importDataRef)?.refresh()
  503. };
  504. // 导入json
  505. const importJson = (data: any) => {
  506. reRender(data);
  507. };
  508. const baseDataResult = ref<EmptyObjectType>({});
  509. onMounted(async () => {
  510. // 获取页面基础数据
  511. const { result } = await baseData();
  512. baseDataResult.value = result;
  513. await init();
  514. });
  515. // 出提供给外部操作-$refs.xxx
  516. defineExpose({
  517. getGraphData,
  518. refreshImport,
  519. importJson,
  520. });
  521. </script>
  522. <style lang="scss">
  523. .border {
  524. border: var(--el-border);
  525. border-radius: var(--el-border-radius-base);
  526. padding: 10px;
  527. }
  528. .el-step__icon-inner {
  529. font-size: var(--el-font-size-base) !important;
  530. font-weight: 700 !important;
  531. }
  532. .lf-menu {
  533. background-color: var(--el-color-white);
  534. }
  535. .lf-menu-item:hover {
  536. background-color: var(--hotline-color-menu-hover);
  537. }
  538. .lf-text-input {
  539. background-color: var(--el-color-white);
  540. }
  541. .lf-element-text {
  542. color: var(--hotline-color-text-main);
  543. }
  544. .lf-graph {
  545. background-color: var(--el-color-white) !important;
  546. color: var(--hotline-color-text-main);
  547. }
  548. .lf-control {
  549. background-color: var(--el-color-white) !important;
  550. }
  551. .custom-minimap {
  552. background-image: url('data:image/png;base64,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');
  553. }
  554. .lf-control-clear {
  555. background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjQ1Nzg5MTYyODczIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjIwNDYiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PGRlZnM+PHN0eWxlIHR5cGU9InRleHQvY3NzIj48L3N0eWxlPjwvZGVmcz48cGF0aCBkPSJNODk5LjEgODY5LjZsLTUzLTMwNS42SDg2NGMxNC40IDAgMjYtMTEuNiAyNi0yNlYzNDZjMC0xNC40LTExLjYtMjYtMjYtMjZINjE4VjEzOGMwLTE0LjQtMTEuNi0yNi0yNi0yNkg0MzJjLTE0LjQgMC0yNiAxMS42LTI2IDI2djE4MkgxNjBjLTE0LjQgMC0yNiAxMS42LTI2IDI2djE5MmMwIDE0LjQgMTEuNiAyNiAyNiAyNmgxNy45bC01MyAzMDUuNmMtMC4zIDEuNS0wLjQgMy0wLjQgNC40IDAgMTQuNCAxMS42IDI2IDI2IDI2aDcyM2MxLjUgMCAzLTAuMSA0LjQtMC40IDE0LjItMi40IDIzLjctMTUuOSAyMS4yLTMwek0yMDQgMzkwaDI3MlYxODJoNzJ2MjA4aDI3MnYxMDRIMjA0VjM5MHogbTQ2OCA0NDBWNjc0YzAtNC40LTMuNi04LTgtOGgtNDhjLTQuNCAwLTggMy42LTggOHYxNTZINDE2VjY3NGMwLTQuNC0zLjYtOC04LThoLTQ4Yy00LjQgMC04IDMuNi04IDh2MTU2SDIwMi44bDQ1LjEtMjYwSDc3Nmw0NS4xIDI2MEg2NzJ6IiBwLWlkPSIyMDQ3Ij48L3BhdGg+PC9zdmc+');
  556. }
  557. .lf-control-save {
  558. background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAeFBMVEUAAAA0NDQzMzMyMjIzMzM0NDQzMzMzMzMzMzMzMzMyMjIzMzMyMjIyMjIzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMyMjIzMzMzMzMzMzMzMzMzMzM0NDQzMzMyMjIzMzM0NDQzMzOd5GGdAAAAJ3RSTlMAQL+AVSOtd6VgURjHHRH17dnPcPvls2pH4p+ZKYzcNJCHWgpmSzvtPOJXAAADgElEQVR42u3dyU7jQBSF4Ws7njPPI9DQ3ef937AFqHFVIA6xKzWg86+9+VSWypIXRxhjjDHGGGOMse83imyXiunSMpstYb+Hp7EYrBzCWfNKTFXFcNpAzHTAjXkqmcN9JiTwov6SDH7UV/ILvtRPUsGfeklieFQPSQmv6i4Zwq+6SlJoTfaL6MsSKEUdg1IcvTUwJdlAbXiUC2kQ6ZgGkfdMSTIoFSIWIWYlMzRNa6sQs5KldiBWIUYlIyjldiFGJRGUIruQdkmYkK8kYUK+kngDGR+j47gFclXiCaTAa8VlyHWJF5Ap3ptehlyXeABpnk8uQ65LnEO2aNpehlyXuIbkaMpbINcljiEFmorLkOuSh9otJEZT3AK5LnkKFPJJsgsV8kmShgo5l0TBQmTwUyAy8AeSoSm7GRL5A9EuxJAh2idKyBBZ439rCRoiu48LLXCInPDaSYKHSFolVSo/APIWIYQQEhhkAaXUwN+xwhHkGUq5dCqHUu4I8gdqtXSohlrlCFJDK3mObuw5gVbtCCJrGG0triAbGG3jDFKvYLBV7QwiCQyWiDtIvTJ5IA4h8hvG+i0uIZKYe7HcQmQBIy3ENUQGMNBA3ENkMEfP5gPxASIv+Qo9WuUv4gdEZFyu0bF1ORbxBvKGSR+jG3tM3xCeQbpFCCE3RgghhLRHCCGEtEcIIYS0RwghhLRHCCGEtBcgJCqTfdyr7LTZjt1CHhfZBGaKk60zSPq0hMmyyA0kmcJ0BxeQPe7Q0D4kxl1a2YZkuFMTu5AEdyu2CUlwxzJ7kL+4axtrkAJ3bTa2BNniziWWIHucNY17tcNZD5YgD9DKU+lZmkPvjxXII9TmIzHQaA61gxXIQr+/DDWB0tQK5AClSgxVQe3FBmSNpp0YawelkQ3IDE2xGCuG0tEGZIKmkxjrBKW/NiD61WWsBEoRIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQohpyAhKC18gCyiNbh9S2fsC2aNp2WHaZnL0A3KcoGnWZWxo6AdkCKWs07BYUbuH1AXUym6DXNMij9Q0SGQsHaKWF1NopaFOpJ01DHW07rwy0BnB8+JAhx0/VYU1RXuxQ3hjtF82D3OO9nPhDtJqZWFP0n70K8wR7fPiKshZ8/OGpfQp3TRD885azrJNGub0v9ZIGGOMMcYYY4yxb/cPDX+XlnDYr4YAAAAASUVORK5CYII=');
  559. }
  560. .lf-control-release {
  561. background-image: url('data:image/png;base64,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');
  562. }
  563. </style>