|
|
@@ -57,10 +57,13 @@
|
|
|
<div
|
|
|
class="canvas-container"
|
|
|
ref="canvasContainer"
|
|
|
+ tabindex="0"
|
|
|
@drop="onDrop"
|
|
|
@dragover.prevent
|
|
|
@click="onCanvasClick"
|
|
|
@mousedown="onCanvasMouseDown"
|
|
|
+ @keydown.delete="handleDelete"
|
|
|
+ @keydown.backspace="handleDelete"
|
|
|
>
|
|
|
<svg class="canvas-svg" ref="canvasSvg" :width="canvasSize.width" :height="canvasSize.height"
|
|
|
:class="{ 'dragging-canvas': isDraggingCanvas }">
|
|
|
@@ -166,7 +169,7 @@
|
|
|
|
|
|
<!-- 节点属性 -->
|
|
|
<el-form v-if="selectedNode" label-width="80px" size="small">
|
|
|
- <el-form-item label="节点名称">
|
|
|
+ <el-form-item label="节点内容">
|
|
|
<el-input v-model="selectedNode.nodeName" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="节点类型">
|
|
|
@@ -270,14 +273,113 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
+ // 确保容器可获取焦点
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.$refs.canvasContainer.focus() // 这里应该是 canvasContainer 不是 container
|
|
|
+ })
|
|
|
+
|
|
|
+ // 添加全局键盘事件监听
|
|
|
+ // window.addEventListener('keydown', this.handleGlobalKeydown)
|
|
|
document.addEventListener('mousemove', this.onMouseMove)
|
|
|
document.addEventListener('mouseup', this.onMouseUp)
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
+ // window.removeEventListener('keydown', this.handleGlobalKeydown)
|
|
|
document.removeEventListener('mousemove', this.onMouseMove)
|
|
|
document.removeEventListener('mouseup', this.onMouseUp)
|
|
|
},
|
|
|
methods: {
|
|
|
+ // 聚焦画布容器
|
|
|
+ focusCanvasContainer() {
|
|
|
+ this.$refs.canvasContainer.focus()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 点击画布时聚焦
|
|
|
+ onCanvasClick() {
|
|
|
+ this.clearSelection()
|
|
|
+ this.focusCanvasContainer()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 点击节点时聚焦
|
|
|
+ selectNode(node) {
|
|
|
+ this.selectedNode = node
|
|
|
+ this.selectedEdge = null
|
|
|
+ this.focusCanvasContainer()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 点击连线时聚焦
|
|
|
+ selectEdge(edge) {
|
|
|
+ this.selectedEdge = edge
|
|
|
+ this.selectedNode = null
|
|
|
+ this.focusCanvasContainer()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 局部键盘事件
|
|
|
+ handleDelete(event) {
|
|
|
+ event.preventDefault() // 阻止默认行为(如浏览器后退)
|
|
|
+ this.deleteSelected()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 全局键盘事件
|
|
|
+ // handleGlobalKeydown(event) {
|
|
|
+ // // 检查是否按下了退格键或删除键
|
|
|
+ // if ((event.key === 'Backspace' || event.key === 'Delete') && !this.isInputFocused()) {
|
|
|
+ // event.preventDefault()
|
|
|
+ // this.deleteSelected()
|
|
|
+ // }
|
|
|
+ // },
|
|
|
+
|
|
|
+ // 删除选中项的逻辑
|
|
|
+ deleteSelected() {
|
|
|
+ if (this.selectedNode) {
|
|
|
+ this.deleteSelectedNode()
|
|
|
+ } else if (this.selectedEdge) {
|
|
|
+ this.deleteSelectedEdge()
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除选中节点
|
|
|
+ deleteSelectedNode() {
|
|
|
+ if (!this.selectedNode) return
|
|
|
+ // 确认对话框
|
|
|
+ this.$confirm('是否确认删除该节点?', '警告', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ const key = this.selectedNode.nodeKey
|
|
|
+ this.nodes = this.nodes.filter(n => n.nodeKey !== key)
|
|
|
+ // 同时删除与该节点相关的连线
|
|
|
+ this.edges = this.edges.filter(e =>
|
|
|
+ e.sourceNodeKey !== key && e.targetNodeKey !== key)
|
|
|
+ this.selectedNode = null
|
|
|
+ }).catch(() => {})
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除选中连线
|
|
|
+ deleteSelectedEdge() {
|
|
|
+ if (!this.selectedEdge) return
|
|
|
+
|
|
|
+ this.$confirm('是否确认删除该连线?', '警告', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ this.edges = this.edges.filter(e => e.edgeKey !== this.selectedEdge.edgeKey)
|
|
|
+ this.selectedEdge = null
|
|
|
+ }).catch(() => {})
|
|
|
+ },
|
|
|
+
|
|
|
+ // 检查是否有输入框获得焦点
|
|
|
+ isInputFocused() {
|
|
|
+ const activeElement = document.activeElement
|
|
|
+ // 注意:节点名称输入框也需要排除
|
|
|
+ const isEditable = ['INPUT', 'TEXTAREA', 'SELECT'].includes(activeElement.tagName)
|
|
|
+ // 额外检查是否是节点名称输入框
|
|
|
+ const isNodeInput = activeElement.classList && activeElement.classList.contains('node-name-input')
|
|
|
+ return isEditable || isNodeInput
|
|
|
+ },
|
|
|
+
|
|
|
/** 加载节点类型 */
|
|
|
loadNodeTypes() {
|
|
|
getNodeTypes().then(res => {
|
|
|
@@ -342,6 +444,7 @@ export default {
|
|
|
fitView() {
|
|
|
this.scale = 1
|
|
|
this.canvasOffset = { x: 0, y: 0 }
|
|
|
+ this.focusCanvasContainer()
|
|
|
},
|
|
|
/** 生成唯一Key */
|
|
|
generateKey() {
|
|
|
@@ -373,13 +476,11 @@ export default {
|
|
|
}
|
|
|
this.nodes.push(newNode)
|
|
|
this.selectNode(newNode)
|
|
|
+ this.focusCanvasContainer()
|
|
|
// 检测并扩展画布
|
|
|
this.checkAndExpandCanvas(newNode)
|
|
|
},
|
|
|
- /** 点击画布 */
|
|
|
- onCanvasClick() {
|
|
|
- this.clearSelection()
|
|
|
- },
|
|
|
+
|
|
|
/** 画布鼠标按下 */
|
|
|
onCanvasMouseDown(e) {
|
|
|
// 只响应左键且点击在空白区域
|
|
|
@@ -395,16 +496,7 @@ export default {
|
|
|
e.preventDefault()
|
|
|
}
|
|
|
},
|
|
|
- /** 选中节点 */
|
|
|
- selectNode(node) {
|
|
|
- this.selectedNode = node
|
|
|
- this.selectedEdge = null
|
|
|
- },
|
|
|
- /** 选中连线 */
|
|
|
- selectEdge(edge) {
|
|
|
- this.selectedEdge = edge
|
|
|
- this.selectedNode = null
|
|
|
- },
|
|
|
+
|
|
|
/** 清除选中 */
|
|
|
clearSelection() {
|
|
|
this.selectedNode = null
|
|
|
@@ -421,6 +513,7 @@ export default {
|
|
|
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) {
|
|
|
@@ -480,6 +573,7 @@ export default {
|
|
|
// 停止拖动画布
|
|
|
if (this.isDraggingCanvas) {
|
|
|
this.isDraggingCanvas = false
|
|
|
+ this.focusCanvasContainer()
|
|
|
return
|
|
|
}
|
|
|
// 停止连线
|
|
|
@@ -505,6 +599,7 @@ export default {
|
|
|
x: anchorPos.x,
|
|
|
y: anchorPos.y
|
|
|
}
|
|
|
+ this.selectNode(node)
|
|
|
},
|
|
|
/** 获取锚点位置 */
|
|
|
getAnchorPos(node, anchor) {
|
|
|
@@ -601,32 +696,13 @@ export default {
|
|
|
const t = this.nodeTypes.find(n => n.typeCode === typeCode)
|
|
|
return t ? t.typeName : typeCode
|
|
|
},
|
|
|
- /** 删除节点 */
|
|
|
+ /** 删除节点(按钮触发) */
|
|
|
deleteNode() {
|
|
|
- if (!this.selectedNode) return
|
|
|
- this.$confirm('是否确认删除该节点?', '警告', {
|
|
|
- confirmButtonText: '确定',
|
|
|
- cancelButtonText: '取消',
|
|
|
- type: 'warning'
|
|
|
- }).then(() => {
|
|
|
- const key = this.selectedNode.nodeKey
|
|
|
- this.nodes = this.nodes.filter(n => n.nodeKey !== key)
|
|
|
- this.edges = this.edges.filter(e =>
|
|
|
- e.sourceNodeKey !== key && e.targetNodeKey !== key)
|
|
|
- this.selectedNode = null
|
|
|
- }).catch(() => {})
|
|
|
+ this.deleteSelectedNode()
|
|
|
},
|
|
|
- /** 删除连线 */
|
|
|
+ /** 删除连线(按钮触发) */
|
|
|
deleteEdge() {
|
|
|
- if (!this.selectedEdge) return
|
|
|
- this.$confirm('是否确认删除该连线?', '警告', {
|
|
|
- confirmButtonText: '确定',
|
|
|
- cancelButtonText: '取消',
|
|
|
- type: 'warning'
|
|
|
- }).then(() => {
|
|
|
- this.edges = this.edges.filter(e => e.edgeKey !== this.selectedEdge.edgeKey)
|
|
|
- this.selectedEdge = null
|
|
|
- }).catch(() => {})
|
|
|
+ this.deleteSelectedEdge()
|
|
|
},
|
|
|
/** 保存工作流 */
|
|
|
handleSave() {
|
|
|
@@ -662,4 +738,4 @@ export default {
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
@import './design.scss';
|
|
|
-</style>
|
|
|
+</style>
|