|
@@ -0,0 +1,158 @@
|
|
|
+<template>
|
|
|
+ <div
|
|
|
+ class="flow-node"
|
|
|
+ :style="nodeStyle"
|
|
|
+ ref="nodeElement"
|
|
|
+ @mousedown="startDrag"
|
|
|
+ >
|
|
|
+ <div class="node-header" :style="headerStyle">
|
|
|
+ {{ nodeData.name }}
|
|
|
+ </div>
|
|
|
+ <div class="node-content">
|
|
|
+ <slot></slot>
|
|
|
+ </div>
|
|
|
+ <div class="node-footer">
|
|
|
+ <button
|
|
|
+ v-for="type in availableChildTypes"
|
|
|
+ :key="type"
|
|
|
+ @click.stop="addChild(type)"
|
|
|
+ class="add-child-btn"
|
|
|
+ >
|
|
|
+ 添加{{ nodeTypes[type].name }}节点
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed, ref } from 'vue'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ node: {
|
|
|
+ type: Object,
|
|
|
+ required: true
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const emit = defineEmits(['add-child', 'node-drag'])
|
|
|
+
|
|
|
+const nodeTypes = {
|
|
|
+ start: { name: '开始', color: '#4CAF50' },
|
|
|
+ process: { name: '处理', color: '#2196F3' },
|
|
|
+ decision: { name: '判断', color: '#FFC107' },
|
|
|
+ end: { name: '结束', color: '#F44336' }
|
|
|
+}
|
|
|
+
|
|
|
+const nodeElement = ref(null)
|
|
|
+const isDragging = ref(false)
|
|
|
+const dragStartPos = ref({ x: 0, y: 0 })
|
|
|
+const nodeStartPos = ref({ x: 0, y: 0 })
|
|
|
+
|
|
|
+const nodeData = computed(() => ({
|
|
|
+ ...props.node,
|
|
|
+ ...nodeTypes[props.node.type]
|
|
|
+}))
|
|
|
+
|
|
|
+const nodeStyle = computed(() => ({
|
|
|
+ left: `${props.node.x}px`,
|
|
|
+ top: `${props.node.y}px`,
|
|
|
+ borderColor: nodeData.value.color
|
|
|
+}))
|
|
|
+
|
|
|
+const headerStyle = computed(() => ({
|
|
|
+ backgroundColor: nodeData.value.color
|
|
|
+}))
|
|
|
+
|
|
|
+const availableChildTypes = computed(() => {
|
|
|
+ if (props.node.type === 'start') return ['process', 'decision']
|
|
|
+ if (props.node.type === 'process') return ['process', 'decision', 'end']
|
|
|
+ if (props.node.type === 'decision') return ['process', 'end']
|
|
|
+ return []
|
|
|
+})
|
|
|
+
|
|
|
+function addChild(type) {
|
|
|
+ emit('add-child', props.node.id, type)
|
|
|
+}
|
|
|
+
|
|
|
+function startDrag(e) {
|
|
|
+ if (e.target.closest('.add-child-btn')) return
|
|
|
+
|
|
|
+ isDragging.value = true
|
|
|
+ dragStartPos.value = {
|
|
|
+ x: e.clientX,
|
|
|
+ y: e.clientY
|
|
|
+ }
|
|
|
+ nodeStartPos.value = {
|
|
|
+ x: props.node.x,
|
|
|
+ y: props.node.y
|
|
|
+ }
|
|
|
+
|
|
|
+ document.addEventListener('mousemove', handleDrag)
|
|
|
+ document.addEventListener('mouseup', stopDrag)
|
|
|
+ e.preventDefault()
|
|
|
+}
|
|
|
+
|
|
|
+function handleDrag(e) {
|
|
|
+ if (!isDragging.value) return
|
|
|
+
|
|
|
+ const dx = e.clientX - dragStartPos.value.x
|
|
|
+ const dy = e.clientY - dragStartPos.value.y
|
|
|
+
|
|
|
+ const newX = nodeStartPos.value.x + dx
|
|
|
+ const newY = nodeStartPos.value.y + dy
|
|
|
+
|
|
|
+ emit('node-drag', props.node.id, newX, newY)
|
|
|
+}
|
|
|
+
|
|
|
+function stopDrag() {
|
|
|
+ isDragging.value = false
|
|
|
+ document.removeEventListener('mousemove', handleDrag)
|
|
|
+ document.removeEventListener('mouseup', stopDrag)
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style>
|
|
|
+.flow-node {
|
|
|
+ position: absolute;
|
|
|
+ width: 200px;
|
|
|
+ background: white;
|
|
|
+ border: 2px solid;
|
|
|
+ border-radius: 4px;
|
|
|
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
+ z-index: 1;
|
|
|
+ cursor: move;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+
|
|
|
+.node-header {
|
|
|
+ padding: 8px 12px;
|
|
|
+ color: white;
|
|
|
+ font-weight: bold;
|
|
|
+ border-top-left-radius: 2px;
|
|
|
+ border-top-right-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.node-content {
|
|
|
+ padding: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.node-footer {
|
|
|
+ padding: 8px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.add-child-btn {
|
|
|
+ padding: 4px 8px;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ border-radius: 3px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.add-child-btn:hover {
|
|
|
+ background: #e0e0e0;
|
|
|
+}
|
|
|
+</style>
|