index.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. <template>
  2. <splitpanes class="default-theme">
  3. <pane min-size="50" size="100" max-size="100">
  4. <VueFlow
  5. ref="vueFlowRef"
  6. v-model:nodes="nodes"
  7. v-model:edges="edges"
  8. :class="{ dark }"
  9. class="basic-flow"
  10. :default-viewport="{ zoom: 1.5 }"
  11. :min-zoom="0.2"
  12. :max-zoom="2.5"
  13. @drop="customOnDrop"
  14. @node-contextmenu="onNodeContextMenu"
  15. @dragover="onDragOver"
  16. @dragleave="onDragLeave"
  17. @edge-click="onEdgeClick"
  18. @node-double-click="onNodeDoubleClick"
  19. @node-click="onNodeClick"
  20. @edge-double-click="onEdgeDoubleClick"
  21. @node-drag="onNodeDrag"
  22. >
  23. <!-- 自定义节点类型为default的节点 -->
  24. <template #node-default="props">
  25. <defaultnode :node="props" :key="props.data.idCodeser || props.id" />
  26. </template>
  27. <template #node-point-only="props">
  28. <PointOnlyNode
  29. :node="props"
  30. :key="props.data.idCodeser || props.id"
  31. />
  32. </template>
  33. <Background pattern-color="#aaa" :gap="16" />
  34. <!-- <Controls position="top-left">
  35. <ControlButton title="重置" @click="resetTransform">
  36. <changebak name="reset" />
  37. </ControlButton>
  38. <ControlButton title="背景切换" @click="toggleDarkMode">
  39. <changebak v-if="dark" name="sun" />
  40. <changebak v-else name="moon" />
  41. </ControlButton>
  42. <ControlButton title="保存" @click="saveproject">
  43. <el-icon :color="iconcolor"><UploadFilled /></el-icon>
  44. </ControlButton>
  45. <ControlButton title="删除节点" @click="removeNode()">
  46. <el-icon :color="iconcolor"><DocumentDelete /></el-icon>
  47. </ControlButton>
  48. <ControlButton title="删除线" @click="removeEdge()">
  49. <el-icon :color="iconcolor"><Crop /></el-icon>
  50. </ControlButton>
  51. <ControlButton title="清空全部" @click="confirmDelete()">
  52. <el-icon :color="iconcolor"><DeleteFilled /></el-icon>
  53. </ControlButton>
  54. </Controls> -->
  55. </VueFlow>
  56. </pane>
  57. <pane
  58. v-if="showPanel"
  59. min-size="20"
  60. :size="30"
  61. max-size="50"
  62. class="flow-panel"
  63. >
  64. <asideData ref="asideDataref" @close="closePanel" />
  65. </pane>
  66. </splitpanes>
  67. </template>
  68. <script setup>
  69. import { VueFlow, Panel, useVueFlow, MarkerType } from "@vue-flow/core"
  70. import {
  71. ElMessage,
  72. ElButton,
  73. ElDialog,
  74. ElSelect,
  75. ElMessageBox
  76. } from "element-plus"
  77. import {
  78. DocumentDelete,
  79. Delete,
  80. UploadFilled,
  81. Histogram,
  82. DeleteFilled,
  83. Crop,
  84. Close
  85. } from "@element-plus/icons-vue"
  86. import { request, uploadFile } from "@/utils/request"
  87. import { Background } from "@vue-flow/background"
  88. import { ControlButton, Controls } from "@vue-flow/controls"
  89. import "./main.css" //重置样式
  90. import defaultnode from "./defaultnode.vue"
  91. import PointOnlyNode from "./pointonlynode.vue"
  92. import useDragAndDrop from "./useDnD"
  93. import changebak from "./changebak.vue"
  94. import { useProjectStore } from "@/store/project"
  95. import emitter from "@/utils/emitter"
  96. import { onMounted } from "vue"
  97. import { Splitpanes, Pane } from "splitpanes"
  98. import "splitpanes/dist/splitpanes.css"
  99. import asideData from "./aside/asideData.vue"
  100. import node from "@/assets/img/node.png"
  101. // 获取路由实例
  102. const router = useRouter()
  103. const {
  104. onInit,
  105. onNodeDragStop,
  106. onConnect,
  107. addEdges,
  108. setViewport,
  109. toObject,
  110. addNodes,
  111. updateEdgeData,
  112. onConnectStart,
  113. updateNode,
  114. updateNodeInternals,
  115. onNodeDrag
  116. } = useVueFlow()
  117. const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop()
  118. const dark = ref(false)
  119. let vueFlowRef = ref()
  120. let iconcolor = ref("#000")
  121. const GRID_SIZE = 2;
  122. const edges = ref([])
  123. const nodes = ref([])
  124. let mergedObj = ref("")
  125. // 选中节点
  126. let noid = ref([])
  127. let Edgeid = ref() //选中线段id
  128. let seledge = ref(null)
  129. let previousEdge = null // 用于保存上一个选中的边缘
  130. // 连线颜色
  131. let linecolor = ref("#2267B1")
  132. // 线宽
  133. let linewidth = ref(1)
  134. let midNodeCounter = 0 //储存中间节点的序号
  135. const projectStore = useProjectStore()
  136. let pid = computed(() => projectStore.pid || "")
  137. const showPanel = ref(false)
  138. const asideDataref = ref()
  139. const props = defineProps({
  140. jobId: {
  141. type: String,
  142. default: ""
  143. }
  144. })
  145. // 处理节点拖动时的网格吸附
  146. onNodeDrag(({ node }) => {
  147. if (!node) {
  148. console.error('onNodeDrag 未收到节点数据');
  149. return;
  150. }
  151. console.log('拖动节点:', node.id, node.type, node.position);
  152. const step = node.type === 'point-only' ? 1 : GRID_SIZE;
  153. const newPosition = {
  154. x: Math.round(node.position.x / step) * step,
  155. y: Math.round(node.position.y / step) * step,
  156. };
  157. updateNode(node.id, { position: newPosition });
  158. });
  159. // 旧数据存储
  160. let prevNodes = [...nodes.value]
  161. let prevEdges = [...edges.value]
  162. // 监听节点变化
  163. watch(
  164. nodes,
  165. (newNodes) => {
  166. const deletedNodes = prevNodes.filter(
  167. (node) => !newNodes.some((n) => n.id === node.id)
  168. )
  169. const addedNodes = newNodes.filter(
  170. (node) => !prevNodes.some((n) => n.id === node.id)
  171. )
  172. if (deletedNodes.length > 0) {
  173. console.log("Deleted Nodes:", deletedNodes)
  174. deletedNodes.forEach((node) => {
  175. if (node.data?.pcId) {
  176. comdelete(node.data.pcId)
  177. }
  178. })
  179. }
  180. if (addedNodes.length > 0) {
  181. // console.log('Added Nodes:', addedNodes);
  182. }
  183. // 更新快照
  184. prevNodes = [...newNodes]
  185. },
  186. { deep: true }
  187. )
  188. // 监听连线变化
  189. watch(
  190. edges,
  191. (newEdges) => {
  192. const deletedEdges = prevEdges.filter(
  193. (edge) => !newEdges.some((e) => e.id === edge.id)
  194. )
  195. const addedEdges = newEdges.filter(
  196. (edge) => !prevEdges.some((e) => e.id === edge.id)
  197. )
  198. if (deletedEdges.length > 0) {
  199. console.log("Deleted Edges:", deletedEdges)
  200. deletedEdges.forEach((edge) => {
  201. const sourceNode = prevNodes.find((n) => n.id === edge.source)
  202. const targetNode = prevNodes.find((n) => n.id === edge.target)
  203. if (!sourceNode || !targetNode) return
  204. let npcId = ""
  205. let pcId = ""
  206. // 判断 source 或 target 是否为中间点 comId === '3'
  207. if (sourceNode.data?.comId === "3") {
  208. npcId = sourceNode.data.pcId
  209. pcId = targetNode.data?.pcId
  210. } else if (targetNode.data?.comId === "3") {
  211. npcId = targetNode.data.pcId
  212. pcId = sourceNode.data?.pcId
  213. }
  214. if (npcId && pcId) {
  215. comlinedelete(npcId, pcId)
  216. }
  217. })
  218. }
  219. if (addedEdges.length > 0) {
  220. }
  221. // 更新快照
  222. prevEdges = [...newEdges]
  223. },
  224. { deep: true }
  225. )
  226. const resetTransform = () => {
  227. setViewport({ x: 0, y: 0, zoom: 1 })
  228. }
  229. const toggleDarkMode = () => {
  230. dark.value = !dark.value
  231. if (dark.value) {
  232. iconcolor.value = "#fff"
  233. } else {
  234. iconcolor.value = "#000"
  235. }
  236. }
  237. const saveproject = async () => {
  238. try {
  239. let obj = {
  240. nodes: toObject().nodes,
  241. edges: toObject().edges
  242. }
  243. mergedObj.value = JSON.stringify(obj)
  244. const params = {
  245. transCode: "ES0002",
  246. pid: pid.value,
  247. flow: mergedObj.value,
  248. name: projectStore.projectInfo.name || "",
  249. remark: projectStore.projectInfo.remark || "",
  250. keywords: projectStore.projectInfo.keywords || ""
  251. }
  252. await request(params)
  253. projectStore.setProjectInfo({
  254. ...projectStore.projectInfo,
  255. flow: mergedObj.value || ""
  256. })
  257. console.log('保存成功')
  258. } catch (error) {
  259. console.error("保存失败:", error)
  260. ElMessage.error("保存失败,请稍后重试")
  261. }
  262. }
  263. const removeNode = () => {
  264. if (!noid.value) {
  265. console.warn("未选中任何节点")
  266. return
  267. }
  268. const nodeIdToRemove = noid.value.id
  269. vueFlowRef.value.removeNodes(noid.value.id)
  270. // 清空当前选中
  271. noid.value = null
  272. saveproject()
  273. }
  274. const removeEdge = () => {
  275. if (!seledge.value) {
  276. ElMessage.warning("请先选择要删除的连线")
  277. return
  278. }
  279. vueFlowRef.value.removeEdges(seledge.value) // 移除选中的边
  280. seledge.value = null // 清空选中状态
  281. saveproject()
  282. }
  283. const confirmDelete = () => {
  284. ElMessageBox.confirm("确定要删除所有节点和连线吗?此操作不可恢复!", "警告", {
  285. confirmButtonText: "确认",
  286. cancelButtonText: "取消",
  287. type: "warning"
  288. })
  289. .then(() => {
  290. vueFlowRef.value.removeNodes(nodes.value)
  291. vueFlowRef.value.removeEdges(edges.value)
  292. ElMessage.success("所有节点和连线已删除")
  293. saveproject()
  294. })
  295. .catch(() => {
  296. ElMessage.info("已取消删除")
  297. })
  298. }
  299. const comdelete = (pcId) => {
  300. const params = {
  301. transCode: "ES0005",
  302. pcId: pcId
  303. }
  304. request(params)
  305. .then((res) => {
  306. console.log("组件删除成功")
  307. })
  308. .catch((err) => {
  309. ElMessage.error(err.returnMsg)
  310. })
  311. }
  312. const comlinedelete = (npcId, pcId) => {
  313. const params = {
  314. transCode: "ES0007",
  315. npcId: npcId,
  316. pcId: pcId,
  317. type: 0
  318. }
  319. request(params)
  320. .then((res) => {
  321. console.log("组件连线删除成功")
  322. })
  323. .catch((err) => {
  324. ElMessage.error(err.returnMsg)
  325. })
  326. }
  327. const customOnDrop = async (event) => {
  328. console.log("自定义拖放事件", event)
  329. await onDrop(event) // 等待 onDrop 执行完成
  330. saveproject() // onDrop 执行完之后再保存项目
  331. }
  332. const onNodeContextMenu = (e) => {
  333. console.log("右键点击", e)
  334. }
  335. const onEdgeClick = (e) => {
  336. console.log("Edge Click", e.edge)
  337. // console.log('所有线段:', edges.value);
  338. // 如果已经有选中的边缘
  339. if (seledge.value) {
  340. // 恢复上一个选中边缘的样式
  341. if (previousEdge) {
  342. previousEdge.style = {
  343. ...previousEdge.style,
  344. stroke: previousEdge.originalColor, // 恢复原始颜色
  345. strokeWidth: previousEdge.originalWidth // 恢复原始宽度
  346. }
  347. }
  348. }
  349. // 保存当前点击的边缘为选中边缘
  350. Edgeid.value = e.edge.id
  351. seledge.value = e.edge
  352. // 暂时更改当前选中边缘的样式
  353. seledge.value.originalColor = seledge.value.style.stroke // 保存当前边缘的原始颜色
  354. seledge.value.originalWidth = seledge.value.style.strokeWidth // 保存当前边缘的原始宽度
  355. const isdefault = e.edge.data.type === "default" // 判断是否为默认线
  356. seledge.value.style = {
  357. ...seledge.value.style,
  358. stroke: isdefault ? "#2267B1" : "rgba(255, 255, 0, 0.3)", // 设置选中边缘的颜色
  359. strokeWidth: isdefault ? 2 : 6 // 设置选中边缘的宽度
  360. }
  361. // 保存当前选中的边缘作为上一个选中边缘
  362. previousEdge = seledge.value
  363. }
  364. const onNodeClick = (event) => {
  365. noid.value = event.node
  366. }
  367. const onNodeDoubleClick = (event) => {
  368. console.log("节点被双击:", event)
  369. showPanel.value = true
  370. const pcId = event.node.data.pcId
  371. nextTick(() => {
  372. console.log("diaoy", pcId)
  373. if (asideDataref.value) {
  374. asideDataref.value.getcomdata(pcId)
  375. if (props.jobId){
  376. asideDataref.value.getresultData(props.jobId)
  377. }
  378. }
  379. })
  380. }
  381. const onEdgeDoubleClick = (event) => {
  382. console.log("连线双击:", event)
  383. noid.value = event.node
  384. }
  385. // 监听连接开始,提前取消选中的线段
  386. onConnectStart(() => {
  387. cleanEdgeselect()
  388. })
  389. // 线的类型 default
  390. let lineType = ref("default")
  391. onConnect(async (connection) => {
  392. console.log("线连接", connection)
  393. if (connection.source === connection.target) {
  394. console.warn("禁止节点自连")
  395. return
  396. }
  397. const sourceNode = vueFlowRef.value.getNode(connection.source)
  398. const targetNode = vueFlowRef.value.getNode(connection.target)
  399. if (sourceNode.data.comId === "3" && targetNode.data.comId === "3") {
  400. console.warn("禁止连接两个中间节点")
  401. return
  402. }
  403. const hasDuplicateEdge = edges.value.some(
  404. (edge) =>
  405. (edge.source === connection.source &&
  406. edge.target === connection.target) ||
  407. (edge.source === connection.target && edge.target === connection.source)
  408. )
  409. if (hasDuplicateEdge) {
  410. console.warn("禁止重复连接:起点和终点已连接")
  411. return
  412. }
  413. const isDuplicate = edges.value.some((edge1) => {
  414. // edge1: source -> 中间节点
  415. if (
  416. edge1.source === connection.source ||
  417. edge1.target === connection.source
  418. ) {
  419. const middleNodeId =
  420. edge1.source === connection.source ? edge1.target : edge1.source
  421. // 确保 middleNode 是中间组件(comId === '3')
  422. const middleNode = vueFlowRef.value.getNode(middleNodeId)
  423. if (!middleNode || middleNode.data.comId !== "3") return false
  424. // 查找是否还有另一条边连接 middleNode <-> target
  425. return edges.value.some(
  426. (edge2) =>
  427. (edge2.source === middleNodeId &&
  428. edge2.target === connection.target) ||
  429. (edge2.target === middleNodeId && edge2.source === connection.target)
  430. )
  431. }
  432. return false
  433. })
  434. if (isDuplicate) {
  435. console.warn("禁止重复连接:这两个节点已经通过中间组件连接过了")
  436. return
  437. }
  438. // 是否需要中间节点
  439. const isCom3 = sourceNode.data.comId === "3" || targetNode.data.comId === "3"
  440. if (isCom3) {
  441. //(comID == 3)
  442. const edgeId = `${lineType.value}-${connection.source}-${connection.sourceHandle}-${connection.target}-${connection.targetHandle}`
  443. connection.id = edgeId
  444. connection.type = "smoothstep"
  445. connection.color = linecolor.value
  446. connection.style = { strokeWidth: linewidth.value, stroke: linecolor.value }
  447. // connection.markerEnd = lineType.value === 'data'
  448. // ? { type: MarkerType.ArrowClosed, width: 6, height: 6, color: linecolor.value }
  449. // : MarkerType.ArrowClosed;
  450. const sameEdge = edges.value.find((edge) => edge.id === edgeId)
  451. if (sameEdge) {
  452. deleteflow(sameEdge.data?.wid)
  453. }
  454. addEdges(connection)
  455. seledge.value = null
  456. const pcId1 =
  457. sourceNode.data.comId === "3"
  458. ? targetNode.data.pcId
  459. : sourceNode.data.pcId
  460. const npcId =
  461. sourceNode.data.comId === "3"
  462. ? sourceNode.data.pcId
  463. : targetNode.data.pcId
  464. try {
  465. const { pccId1 } = await createEdge(pid.value, 0, npcId, pcId1)
  466. updateEdgeData(connection.id, {
  467. pcid: pccId1,
  468. type: lineType.value,
  469. frompcId: sourceNode.data.pcId,
  470. topcId: targetNode.data.pcId
  471. })
  472. } catch (error) {
  473. console.error("保存流程失败:", error)
  474. }
  475. } else {
  476. // 添加 point-only 中间节点
  477. const pointNodeId = `point-${connection.source}-${connection.target}}`
  478. const sourceCenterX = sourceNode.position.x + 60 / 2
  479. const sourceCenterY = sourceNode.position.y + 58 / 2
  480. const targetCenterX = targetNode.position.x + 60 / 2
  481. const targetCenterY = targetNode.position.y + 58 / 2
  482. const midX = (sourceCenterX + targetCenterX) / 2
  483. const midY = (sourceCenterY + targetCenterY) / 2
  484. // 计算 sourceNode 和 targetNode 相对于 pointNode 的位置
  485. const dxSource = sourceNode.position.x - midX
  486. const dySource = sourceNode.position.y - midY
  487. const dxTarget = targetNode.position.x - midX
  488. const dyTarget = targetNode.position.y - midY
  489. // 判断 sourceNode 的主要方向(水平 or 垂直)
  490. const isSourceHorizontal = Math.abs(dxSource) > Math.abs(dySource)
  491. const sourceDirection = isSourceHorizontal
  492. ? dxSource < 0
  493. ? "left"
  494. : "right"
  495. : dySource < 0
  496. ? "top"
  497. : "bottom"
  498. // 判断 targetNode 的主要方向
  499. const isTargetHorizontal = Math.abs(dxTarget) > Math.abs(dyTarget)
  500. const targetDirection = isTargetHorizontal
  501. ? dxTarget < 0
  502. ? "left"
  503. : "right"
  504. : dyTarget < 0
  505. ? "top"
  506. : "bottom"
  507. nodes.value.push({
  508. id: pointNodeId,
  509. type: "point-only",
  510. position: { x: midX, y: midY },
  511. data: {
  512. comId: "3",
  513. label: "节点",
  514. image: node
  515. }
  516. })
  517. try {
  518. // 创建 comID 为 3 的中间组件
  519. const { pcId: npcId, ser, idCode } = await createCom3(pid.value)
  520. const pointNode = nodes.value.find((n) => n.id === pointNodeId)
  521. updateNode(pointNodeId, (node) => ({
  522. ...node,
  523. data: {
  524. ...node.data,
  525. pcId: npcId,
  526. idCodeser: `${idCode}${ser}`
  527. }
  528. }))
  529. updateNodeInternals(pointNodeId)
  530. // edge1: source -> pointNode
  531. const edgeId1 = `${lineType.value}-${connection.source}-${connection.sourceHandle}-${pointNodeId}`
  532. const edge1 = {
  533. id: edgeId1,
  534. source: connection.source,
  535. sourceHandle: connection.sourceHandle,
  536. target: pointNodeId,
  537. targetHandle: `source-${sourceDirection}`,
  538. type: "smoothstep",
  539. color: linecolor.value,
  540. style: { strokeWidth: linewidth.value, stroke: linecolor.value }
  541. }
  542. // edge2: pointNode -> target
  543. const edgeId2 = `${lineType.value}-${pointNodeId}-${connection.targetHandle}-${connection.target}`
  544. const edge2 = {
  545. id: edgeId2,
  546. source: pointNodeId,
  547. sourceHandle: `source-${targetDirection}`, // 使用计算的方向
  548. target: connection.target,
  549. targetHandle: connection.targetHandle,
  550. type: "smoothstep",
  551. color: linecolor.value,
  552. style: { strokeWidth: linewidth.value, stroke: linecolor.value }
  553. }
  554. addEdges([edge1, edge2])
  555. // 保存后端 edge 数据(分别对应两条边)
  556. const { pccId1, pccId2 } = await createEdge(
  557. pid.value,
  558. 0,
  559. npcId,
  560. sourceNode.data.pcId,
  561. targetNode.data.pcId
  562. )
  563. updateEdgeData(edgeId1, {
  564. pcid: pccId1,
  565. type: lineType.value,
  566. frompcId: sourceNode.data.pcId,
  567. topcId: npcId
  568. })
  569. updateEdgeData(edgeId2, {
  570. pcid: pccId2,
  571. type: lineType.value,
  572. frompcId: npcId,
  573. topcId: targetNode.data.pcId
  574. })
  575. } catch (err) {
  576. console.error("中间点处理失败:", err)
  577. }
  578. }
  579. saveproject()
  580. })
  581. const createEdge = async (pid, type, npcId, pcId1, pcId2) => {
  582. const params = {
  583. transCode: "ES0006",
  584. pid: pid || "",
  585. type: type || 0,
  586. npcId: npcId || "",
  587. pcId1: pcId1 || "",
  588. pcId2: pcId2 || ""
  589. }
  590. try {
  591. const res = await request(params)
  592. return {
  593. pccId1: res.pccId1,
  594. pccId2: res.pccId2
  595. } // 返回包含 pccId1 和 pccId2 的对象
  596. } catch (err) {
  597. // 处理错误
  598. ElMessage.error(err.returnMsg || "保存流程失败")
  599. throw err // 可以选择重新抛出错误以便调用者处理
  600. }
  601. }
  602. // 创建特殊节点
  603. const createCom3 = async (pid) => {
  604. const params = {
  605. transCode: "ES0004",
  606. pid: pid || "",
  607. comId: "3" // 节点ID
  608. }
  609. try {
  610. const res = await request(params)
  611. return {
  612. pcId: res.pcId,
  613. ser: res.ser,
  614. idCode: res.idCode
  615. }
  616. } catch (err) {
  617. ElMessage.error(err.returnMsg)
  618. }
  619. }
  620. const flowInit = () => {
  621. let nodesflow = JSON.parse(
  622. projectStore.projectInfo.flow || '{"nodes":[],"edges":[]}'
  623. )
  624. nodes.value = nodesflow.nodes
  625. edges.value = nodesflow.edges
  626. }
  627. const cleanEdgeselect = () => {
  628. if (seledge.value) {
  629. // 恢复选中边缘的原始样式
  630. seledge.value.style = {
  631. ...seledge.value.style,
  632. stroke: seledge.value.originalColor,
  633. strokeWidth: previousEdge?.originalWidth || 1 // 恢复原始宽度
  634. }
  635. // 清空选中的边缘
  636. seledge.value = null
  637. Edgeid.value = null
  638. previousEdge = null
  639. }
  640. }
  641. onMounted(() => {
  642. flowInit()
  643. // 点击其他区域取消线段选中
  644. if (vueFlowRef.value) {
  645. vueFlowRef.value.$el.addEventListener("click", (event) => {
  646. // 确保点击的不是边缘
  647. if (seledge.value && !event.target.closest(".vue-flow__edge")) {
  648. cleanEdgeselect()
  649. }
  650. })
  651. }
  652. })
  653. const closePanel = () => {
  654. setTimeout(() => {
  655. showPanel.value = false
  656. }, 100) // 延迟 100ms 销毁组件,避免 Splitpanes 销毁错误
  657. }
  658. // 路由切换前保存
  659. router.beforeEach(async (to, from, next) => {
  660. if (to.path !== from.path) {
  661. try {
  662. await saveproject() // 直接调用 saveproject
  663. next()
  664. } catch (error) {
  665. console.error("路由切换保存失败:", error)
  666. next() // 即使保存失败也允许切换路由
  667. }
  668. } else {
  669. next()
  670. }
  671. })
  672. // 组件卸载前保存
  673. onBeforeUnmount(async () => {
  674. await saveproject() // 直接调用 saveproject
  675. })
  676. defineExpose({
  677. resetTransform,
  678. toggleDarkMode,
  679. saveproject,
  680. removeNode,
  681. removeEdge,
  682. confirmDelete,
  683. asideDataref
  684. })
  685. </script>
  686. <style scoped>
  687. /* .vue-flow__edge.selected .vue-flow__edge-path, .vue-flow__edge:focus .vue-flow__edge-path, .vue-flow__edge:focus-visible .vue-flow__edge-path {
  688. stroke: #555 !important;
  689. } */
  690. .vue-flow__edge:focus .vue-flow__edge-path,
  691. .vue-flow__edge:focus-visible .vue-flow__edge-path {
  692. stroke: #555 !important;
  693. }
  694. .vue-flow__edge {
  695. text-align: left;
  696. /* 设置edges的左对齐 */
  697. }
  698. .vue-flow__edge-text {
  699. transform: translateY(-10px); /* 将 label 向上偏移 */
  700. background: transparent !important;
  701. font-size: 8px;
  702. font-family: "Microsoft YaHei";
  703. color: #333333;
  704. }
  705. .vue-flow__edge-textbg {
  706. fill: transparent !important; /* 将背景设置为透明 */
  707. }
  708. .vue-flow__node-default.selectable:hover,
  709. .vue-flow__node-input.selectable:hover,
  710. .vue-flow__node-output.selectable:hover {
  711. box-shadow: none;
  712. }
  713. .remove {
  714. background: #fff;
  715. color: #666;
  716. margin: 0 10px;
  717. font-size: 12px;
  718. }
  719. .vue-flow__node-default,
  720. .vue-flow__node-input,
  721. .vue-flow__node-output {
  722. /* width: auto !important; */
  723. border: none;
  724. background-color: rgba(0, 0, 0, 0);
  725. }
  726. .node-content {
  727. cursor: move; /* 更改鼠标光标表示可拖动 */
  728. }
  729. .vue-flow__node {
  730. cursor: move;
  731. }
  732. /* 禁用文本选中效果 */
  733. .left_main * {
  734. -webkit-user-select: none; /* Safari */
  735. -moz-user-select: none; /* Firefox */
  736. -ms-user-select: none; /* IE10+/Edge */
  737. user-select: none; /* Standard syntax */
  738. }
  739. .lableaniu {
  740. font-size: 12px;
  741. background-color: #ddd;
  742. padding: 4px 16px;
  743. /* margin-top: -17px; */
  744. margin-left: 5px;
  745. margin-top: 0px;
  746. border-radius: 1px;
  747. }
  748. .vue-flow__controls-button svg {
  749. max-width: 16px;
  750. max-height: 16px;
  751. }
  752. .field {
  753. display: flex;
  754. }
  755. </style>
  756. <!-- 用于侧栏pane -->
  757. <style scoped>
  758. .flow-panel {
  759. font-size: 14px;
  760. }
  761. </style>