|
|
@@ -27,13 +27,20 @@
|
|
|
<el-button icon="el-icon-zoom-out" size="small" @click="zoomOut">缩小</el-button>
|
|
|
<el-button icon="el-icon-rank" size="small" @click="fitView">适应</el-button>
|
|
|
<el-divider direction="vertical" />
|
|
|
- <el-button type="primary" icon="el-icon-check" size="small" @click="handleSave">保存</el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="!readonlyMode"
|
|
|
+ type="primary"
|
|
|
+ icon="el-icon-check"
|
|
|
+ size="small"
|
|
|
+ @click="handleSave"
|
|
|
+ >保存</el-button>
|
|
|
+ <el-tag v-if="readonlyMode" type="info">历史版本预览</el-tag>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="main-content">
|
|
|
<!-- 左侧节点面板 -->
|
|
|
- <div class="node-panel">
|
|
|
+ <div class="node-panel" v-if="!readonlyMode">
|
|
|
<div class="panel-title">节点类型</div>
|
|
|
<div class="node-category" v-for="category in nodeCategories" :key="category.key">
|
|
|
<div class="category-title">{{ category.name }}</div>
|
|
|
@@ -242,7 +249,7 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 右侧属性面板 -->
|
|
|
- <div class="property-panel" v-if="selectedNode || selectedEdge">
|
|
|
+ <div class="property-panel" v-if="!readonlyMode && (selectedNode || selectedEdge)">
|
|
|
<div class="panel-title">
|
|
|
<span>
|
|
|
<i :class="selectedNode ? 'el-icon-setting' : 'el-icon-share'"></i>
|
|
|
@@ -637,7 +644,7 @@ import {
|
|
|
getSmsTempList,
|
|
|
getCIDGroupList
|
|
|
} from "@/api/company/companyVoiceRobotic";
|
|
|
-import { getWorkflow, addWorkflow, updateWorkflow, getNodeTypes } from '@/api/company/companyWorkflow'
|
|
|
+import { getWorkflow, addWorkflow, updateWorkflow, getNodeTypes, getWorkflowVersionDetail } from '@/api/company/companyWorkflow'
|
|
|
import {getDicts} from "@/api/system/dict/data";
|
|
|
import { getGatewayList, getLlmAccountList, getVoiceCodeList, getBusiGroupList } from '@/api/company/easyCall'
|
|
|
// import { listAll } from '@/api/company/wxDialog';
|
|
|
@@ -657,6 +664,10 @@ export default {
|
|
|
qwWxAddWayOptions: [],
|
|
|
// 工作流ID
|
|
|
workflowId: null,
|
|
|
+ // 历史版本ID
|
|
|
+ versionId: null,
|
|
|
+ // 是否只读预览
|
|
|
+ readonlyMode: false,
|
|
|
// 表单数据
|
|
|
form: {
|
|
|
workflowName: '新建工作流',
|
|
|
@@ -737,8 +748,17 @@ export default {
|
|
|
},
|
|
|
created() {
|
|
|
this.workflowId = this.$route.params.id
|
|
|
+ this.versionId = this.$route.params.versionId
|
|
|
+
|
|
|
+ // 如果有 versionId,说明是历史版本预览模式
|
|
|
+ if (this.versionId) {
|
|
|
+ this.readonlyMode = true
|
|
|
+ }
|
|
|
+
|
|
|
this.loadNodeTypes()
|
|
|
- if (this.workflowId) {
|
|
|
+ if (this.versionId) {
|
|
|
+ this.loadVersionWorkflow()
|
|
|
+ } else if (this.workflowId) {
|
|
|
this.loadWorkflow()
|
|
|
}
|
|
|
// listAll().then(e => {
|
|
|
@@ -894,8 +914,9 @@ export default {
|
|
|
|
|
|
// 局部键盘事件
|
|
|
handleDelete(event) {
|
|
|
- event.preventDefault() // 阻止默认行为(如浏览器后退)
|
|
|
- this.deleteSelected()
|
|
|
+ if (this.readonlyMode) return
|
|
|
+ event.preventDefault()
|
|
|
+ this.deleteSelected()
|
|
|
},
|
|
|
|
|
|
// 全局键盘事件
|
|
|
@@ -1043,9 +1064,86 @@ export default {
|
|
|
this.edges = data.edges || []
|
|
|
})
|
|
|
},
|
|
|
+ /** 加载历史版本详情(只读预览) */
|
|
|
+ loadVersionWorkflow() {
|
|
|
+ getWorkflowVersionDetail(this.versionId).then(res => {
|
|
|
+ const data = res.data || {}
|
|
|
+ const version = data.workflowVersion || {}
|
|
|
+
|
|
|
+ this.form = {
|
|
|
+ workflowName: version.workflowName || '',
|
|
|
+ workflowDesc: version.workflowDesc || '',
|
|
|
+ workflowType: String(version.workflowType || '1'),
|
|
|
+ canvasData: version.canvasData || ''
|
|
|
+ }
|
|
|
+
|
|
|
+ let nodes = data.nodes || []
|
|
|
+ let edges = data.edges || []
|
|
|
+
|
|
|
+ // 处理连线条件
|
|
|
+ if (edges && edges.length > 0) {
|
|
|
+ edges.forEach(edge => {
|
|
|
+ const con = edge.conditionExpr
|
|
|
+ if (con == null || con === '') {
|
|
|
+ edge.conditionExprObj = []
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ edge.conditionExprObj = JSON.parse(con)
|
|
|
+ } catch (e) {
|
|
|
+ edge.conditionExprObj = []
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理节点配置
|
|
|
+ if (nodes && nodes.length > 0) {
|
|
|
+ nodes.forEach(node => {
|
|
|
+ if (typeof node.nodeConfig === 'string') {
|
|
|
+ try {
|
|
|
+ const parsedConfig = JSON.parse(node.nodeConfig)
|
|
|
+ node.nodeConfig = Object.assign({}, parsedConfig)
|
|
|
+ } catch (e) {
|
|
|
+ node.nodeConfig = {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!node.nodeConfig || typeof node.nodeConfig !== 'object') {
|
|
|
+ node.nodeConfig = {}
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ this.nodes = nodes
|
|
|
+ this.edges = edges
|
|
|
+
|
|
|
+ // 恢复画布状态
|
|
|
+ if (this.form.canvasData) {
|
|
|
+ try {
|
|
|
+ const canvasData = JSON.parse(this.form.canvasData)
|
|
|
+ this.scale = canvasData.scale || 1
|
|
|
+ this.canvasOffset = canvasData.offset || { x: 0, y: 0 }
|
|
|
+ } catch (e) {
|
|
|
+ this.scale = 1
|
|
|
+ this.canvasOffset = { x: 0, y: 0 }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
/** 返回列表 */
|
|
|
goBack() {
|
|
|
- this.$router.push('/companyWx/companyWorkflow')
|
|
|
+ // 历史版本预览模式下,返回到列表页并自动打开对应工作流的历史版本弹窗
|
|
|
+ if (this.readonlyMode && this.$route.query.workflowId) {
|
|
|
+ this.$router.push({
|
|
|
+ path: '/companyWx/companyWorkflow',
|
|
|
+ query: {
|
|
|
+ openVersionWorkflowId: this.$route.query.workflowId
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 普通编辑模式保持原逻辑
|
|
|
+ this.$router.push('/companyWx/companyWorkflow')
|
|
|
},
|
|
|
/** 放大 */
|
|
|
zoomIn() {
|
|
|
@@ -1067,51 +1165,48 @@ export default {
|
|
|
},
|
|
|
/** 复制节点 */
|
|
|
copyNode(e) {
|
|
|
- if (!this.selectedNode) {
|
|
|
- return
|
|
|
- }
|
|
|
- e.preventDefault()
|
|
|
- // 深拷贝选中的节点,包括所有属性
|
|
|
- this.copiedNode = JSON.parse(JSON.stringify(this.selectedNode))
|
|
|
- this.$message.success('节点已复制')
|
|
|
+ if (this.readonlyMode) return
|
|
|
+ if (!this.selectedNode) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ e.preventDefault()
|
|
|
+ this.copiedNode = JSON.parse(JSON.stringify(this.selectedNode))
|
|
|
+ this.$message.success('节点已复制')
|
|
|
},
|
|
|
/** 粘贴节点 */
|
|
|
pasteNode(e) {
|
|
|
- if (!this.copiedNode) {
|
|
|
- this.$message.warning('没有可粘贴的节点')
|
|
|
- return
|
|
|
- }
|
|
|
- e.preventDefault()
|
|
|
+ if (this.readonlyMode) return
|
|
|
+ if (!this.copiedNode) {
|
|
|
+ this.$message.warning('没有可粘贴的节点')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ e.preventDefault()
|
|
|
|
|
|
- // 创建新节点,复制所有属性
|
|
|
- const newNode = {
|
|
|
- ...this.copiedNode,
|
|
|
- nodeKey: this.generateKey(),
|
|
|
- // 在原位置偏移一定距离
|
|
|
- posX: this.copiedNode.posX + 30,
|
|
|
- posY: this.copiedNode.posY + 30,
|
|
|
- // 深拷贝 nodeConfig
|
|
|
- nodeConfig: this.copiedNode.nodeConfig ? JSON.parse(JSON.stringify(this.copiedNode.nodeConfig)) : {}
|
|
|
- }
|
|
|
+ const newNode = {
|
|
|
+ ...this.copiedNode,
|
|
|
+ nodeKey: this.generateKey(),
|
|
|
+ posX: this.copiedNode.posX + 30,
|
|
|
+ posY: this.copiedNode.posY + 30,
|
|
|
+ nodeConfig: this.copiedNode.nodeConfig ? JSON.parse(JSON.stringify(this.copiedNode.nodeConfig)) : {}
|
|
|
+ }
|
|
|
|
|
|
- this.nodes.push(newNode)
|
|
|
- // 选中新节点
|
|
|
- this.selectedNode = newNode
|
|
|
- this.selectedEdge = null
|
|
|
+ this.nodes.push(newNode)
|
|
|
+ this.selectedNode = newNode
|
|
|
+ this.selectedEdge = null
|
|
|
|
|
|
- // 检查是否需要扩展画布
|
|
|
- this.checkAndExpandCanvas(newNode)
|
|
|
+ this.checkAndExpandCanvas(newNode)
|
|
|
|
|
|
- this.$message.success('节点已粘贴')
|
|
|
+ this.$message.success('节点已粘贴')
|
|
|
},
|
|
|
/** 显示右键菜单 */
|
|
|
showContextMenu(e) {
|
|
|
- // 获取鼠标相对于画布容器的位置
|
|
|
- const rect = this.$refs.canvasContainer.getBoundingClientRect()
|
|
|
- this.contextMenu.x = e.clientX - rect.left
|
|
|
- this.contextMenu.y = e.clientY - rect.top
|
|
|
- this.contextMenu.node = this.selectedNode
|
|
|
- this.contextMenu.visible = true
|
|
|
+ if (this.readonlyMode) return
|
|
|
+
|
|
|
+ const rect = this.$refs.canvasContainer.getBoundingClientRect()
|
|
|
+ this.contextMenu.x = e.clientX - rect.left
|
|
|
+ this.contextMenu.y = e.clientY - rect.top
|
|
|
+ this.contextMenu.node = this.selectedNode
|
|
|
+ this.contextMenu.visible = true
|
|
|
},
|
|
|
/** 隐藏右键菜单 */
|
|
|
hideContextMenu() {
|
|
|
@@ -1169,62 +1264,63 @@ export default {
|
|
|
},
|
|
|
/** 放置节点 */
|
|
|
onDrop(e) {
|
|
|
- const nodeTypeStr = e.dataTransfer.getData('nodeType')
|
|
|
- if (!nodeTypeStr) return
|
|
|
- const nodeType = JSON.parse(nodeTypeStr)
|
|
|
-
|
|
|
- // 检查开始/结束节点是否已存在(限制只能有一个)
|
|
|
- if (this.isStartNodeType(nodeType.typeCode)) {
|
|
|
- const existingStart = this.nodes.find(n => this.isStartNodeType(n.nodeType))
|
|
|
- if (existingStart) {
|
|
|
- this.$message.warning('已存在开始节点,工作流只能有一个开始节点')
|
|
|
- return
|
|
|
+ if (this.readonlyMode) return
|
|
|
+
|
|
|
+ const nodeTypeStr = e.dataTransfer.getData('nodeType')
|
|
|
+ if (!nodeTypeStr) return
|
|
|
+ const nodeType = JSON.parse(nodeTypeStr)
|
|
|
+
|
|
|
+ if (this.isStartNodeType(nodeType.typeCode)) {
|
|
|
+ const existingStart = this.nodes.find(n => this.isStartNodeType(n.nodeType))
|
|
|
+ if (existingStart) {
|
|
|
+ this.$message.warning('已存在开始节点,工作流只能有一个开始节点')
|
|
|
+ return
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- if (this.isEndNodeType(nodeType.typeCode)) {
|
|
|
- const existingEnd = this.nodes.find(n => this.isEndNodeType(n.nodeType))
|
|
|
- if (existingEnd) {
|
|
|
- this.$message.warning('已存在结束节点,工作流只能有一个结束节点')
|
|
|
- return
|
|
|
+ if (this.isEndNodeType(nodeType.typeCode)) {
|
|
|
+ const existingEnd = this.nodes.find(n => this.isEndNodeType(n.nodeType))
|
|
|
+ if (existingEnd) {
|
|
|
+ this.$message.warning('已存在结束节点,工作流只能有一个结束节点')
|
|
|
+ return
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- const rect = this.$refs.canvasContainer.getBoundingClientRect()
|
|
|
- const x = (e.clientX - rect.left - this.canvasOffset.x) / this.scale
|
|
|
- const y = (e.clientY - rect.top - this.canvasOffset.y) / this.scale
|
|
|
-
|
|
|
- const newNode = {
|
|
|
- nodeKey: this.generateKey(),
|
|
|
- nodeName: nodeType.typeName,
|
|
|
- nodeType: nodeType.typeCode,
|
|
|
- nodeIcon: nodeType.typeIcon,
|
|
|
- nodeColor: nodeType.typeColor,
|
|
|
- posX: Math.round(x - 50),
|
|
|
- posY: Math.round(y - 20),
|
|
|
- height: 40,
|
|
|
- nodeConfig: {} // 初始化为空对象
|
|
|
- }
|
|
|
- this.nodes.push(newNode)
|
|
|
- this.selectNode(newNode)
|
|
|
- this.focusCanvasContainer()
|
|
|
- // 检测并扩展画布
|
|
|
- this.checkAndExpandCanvas(newNode)
|
|
|
+ const rect = this.$refs.canvasContainer.getBoundingClientRect()
|
|
|
+ const x = (e.clientX - rect.left - this.canvasOffset.x) / this.scale
|
|
|
+ const y = (e.clientY - rect.top - this.canvasOffset.y) / this.scale
|
|
|
+
|
|
|
+ const newNode = {
|
|
|
+ nodeKey: this.generateKey(),
|
|
|
+ nodeName: nodeType.typeName,
|
|
|
+ nodeType: nodeType.typeCode,
|
|
|
+ nodeIcon: nodeType.typeIcon,
|
|
|
+ nodeColor: nodeType.typeColor,
|
|
|
+ posX: Math.round(x - 50),
|
|
|
+ posY: Math.round(y - 20),
|
|
|
+ height: 40,
|
|
|
+ nodeConfig: {}
|
|
|
+ }
|
|
|
+ this.nodes.push(newNode)
|
|
|
+ this.selectNode(newNode)
|
|
|
+ this.focusCanvasContainer()
|
|
|
+ this.checkAndExpandCanvas(newNode)
|
|
|
},
|
|
|
|
|
|
/** 画布鼠标按下 */
|
|
|
onCanvasMouseDown(e) {
|
|
|
- // 只响应左键且点击在空白区域
|
|
|
- if (e.button !== 0) return
|
|
|
- if (e.target.tagName === 'rect' && e.target.getAttribute('fill') === 'url(#grid)') {
|
|
|
- this.isDraggingCanvas = true
|
|
|
- this.canvasDragStart = {
|
|
|
- x: e.clientX,
|
|
|
- y: e.clientY,
|
|
|
- scrollLeft: this.$refs.canvasContainer.scrollLeft,
|
|
|
- scrollTop: this.$refs.canvasContainer.scrollTop
|
|
|
+ if (this.readonlyMode) return
|
|
|
+
|
|
|
+ if (e.button !== 0) return
|
|
|
+ if (e.target.tagName === 'rect' && e.target.getAttribute('fill') === 'url(#grid)') {
|
|
|
+ this.isDraggingCanvas = true
|
|
|
+ this.canvasDragStart = {
|
|
|
+ x: e.clientX,
|
|
|
+ y: e.clientY,
|
|
|
+ scrollLeft: this.$refs.canvasContainer.scrollLeft,
|
|
|
+ scrollTop: this.$refs.canvasContainer.scrollTop
|
|
|
+ }
|
|
|
+ e.preventDefault()
|
|
|
}
|
|
|
- e.preventDefault()
|
|
|
- }
|
|
|
},
|
|
|
|
|
|
/** 清除选中 */
|
|
|
@@ -1234,16 +1330,21 @@ export default {
|
|
|
},
|
|
|
/** 节点鼠标按下 */
|
|
|
onNodeMouseDown(e, node) {
|
|
|
- if (e.target.classList.contains('anchor')) return
|
|
|
- this.draggingNode = node
|
|
|
- const rect = this.$refs.canvasContainer.getBoundingClientRect()
|
|
|
- const scrollLeft = this.$refs.canvasContainer.scrollLeft
|
|
|
- const scrollTop = this.$refs.canvasContainer.scrollTop
|
|
|
- this.dragOffset = {
|
|
|
- x: (e.clientX - rect.left + scrollLeft - this.canvasOffset.x) / this.scale - node.posX,
|
|
|
- y: (e.clientY - rect.top + scrollTop - this.canvasOffset.y) / this.scale - node.posY
|
|
|
- }
|
|
|
- this.selectNode(node)
|
|
|
+ if (this.readonlyMode) {
|
|
|
+ this.selectNode(node)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (e.target.classList.contains('anchor')) return
|
|
|
+ this.draggingNode = node
|
|
|
+ const rect = this.$refs.canvasContainer.getBoundingClientRect()
|
|
|
+ const scrollLeft = this.$refs.canvasContainer.scrollLeft
|
|
|
+ const scrollTop = this.$refs.canvasContainer.scrollTop
|
|
|
+ this.dragOffset = {
|
|
|
+ x: (e.clientX - rect.left + scrollLeft - this.canvasOffset.x) / this.scale - node.posX,
|
|
|
+ y: (e.clientY - rect.top + scrollTop - this.canvasOffset.y) / this.scale - node.posY
|
|
|
+ }
|
|
|
+ this.selectNode(node)
|
|
|
},
|
|
|
/** 鼠标移动 */
|
|
|
onMouseMove(e) {
|
|
|
@@ -1320,15 +1421,17 @@ export default {
|
|
|
},
|
|
|
/** 开始连线 */
|
|
|
startConnect(e, node, anchor) {
|
|
|
- this.connecting = true
|
|
|
- const anchorPos = this.getAnchorPos(node, anchor)
|
|
|
- this.connectStart = {
|
|
|
- nodeKey: node.nodeKey,
|
|
|
- anchor: anchor,
|
|
|
- x: anchorPos.x,
|
|
|
- y: anchorPos.y
|
|
|
- }
|
|
|
- this.selectNode(node)
|
|
|
+ if (this.readonlyMode) return
|
|
|
+
|
|
|
+ this.connecting = true
|
|
|
+ const anchorPos = this.getAnchorPos(node, anchor)
|
|
|
+ this.connectStart = {
|
|
|
+ nodeKey: node.nodeKey,
|
|
|
+ anchor: anchor,
|
|
|
+ x: anchorPos.x,
|
|
|
+ y: anchorPos.y
|
|
|
+ }
|
|
|
+ this.selectNode(node)
|
|
|
},
|
|
|
/** 获取锚点位置 */
|
|
|
getAnchorPos(node, anchor) {
|
|
|
@@ -1508,6 +1611,14 @@ export default {
|
|
|
},
|
|
|
/** 保存工作流 */
|
|
|
handleSave() {
|
|
|
+ if (this.readonlyMode) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.form.workflowName) {
|
|
|
+ this.$message.warning('请输入工作流名称')
|
|
|
+ return
|
|
|
+ }
|
|
|
if (!this.form.workflowName) {
|
|
|
this.$message.warning('请输入工作流名称')
|
|
|
return
|