|
1 | 1 | <template>
|
2 | 2 | <div class="simple-process-model-container position-relative">
|
3 |
| - <div class="position-absolute top-0px right-0px bg-#fff"> |
| 3 | + <div class="position-absolute top-0px right-0px bg-#fff z-index-button-group"> |
4 | 4 | <el-row type="flex" justify="end">
|
5 | 5 | <el-button-group key="scale-control" size="default">
|
6 | 6 | <el-button v-if="!readonly" size="default" @click="exportJson">
|
|
23 | 23 | <el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
|
24 | 24 | <el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
|
25 | 25 | <el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
|
| 26 | + <el-button size="default" @click="resetPosition">重置</el-button> |
26 | 27 | </el-button-group>
|
27 | 28 | </el-row>
|
28 | 29 | </div>
|
29 |
| - <div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`"> |
| 30 | + <div |
| 31 | + class="simple-process-model" |
| 32 | + :style="`transform: translate(${currentX}px, ${currentY}px) scale(${scaleValue / 100});`" |
| 33 | + @mousedown="startDrag" |
| 34 | + @mousemove="onDrag" |
| 35 | + @mouseup="stopDrag" |
| 36 | + @mouseleave="stopDrag" |
| 37 | + @mouseenter="setGrabCursor" |
| 38 | + > |
30 | 39 | <ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
|
31 | 40 | </div>
|
32 | 41 | </div>
|
@@ -80,24 +89,43 @@ let scaleValue = ref(100)
|
80 | 89 | const MAX_SCALE_VALUE = 200
|
81 | 90 | const MIN_SCALE_VALUE = 50
|
82 | 91 |
|
83 |
| -// 放大 |
84 |
| -const zoomIn = () => { |
85 |
| - if (scaleValue.value == MAX_SCALE_VALUE) { |
86 |
| - return |
87 |
| - } |
88 |
| - scaleValue.value += 10 |
| 92 | +const isDragging = ref(false) |
| 93 | +const startX = ref(0) |
| 94 | +const startY = ref(0) |
| 95 | +const currentX = ref(0) |
| 96 | +const currentY = ref(0) |
| 97 | +const initialX = ref(0) |
| 98 | +const initialY = ref(0) |
| 99 | +
|
| 100 | +const setGrabCursor = () => { |
| 101 | + document.body.style.cursor = 'grab'; |
89 | 102 | }
|
90 | 103 |
|
91 |
| -// 缩小 |
92 |
| -const zoomOut = () => { |
93 |
| - if (scaleValue.value == MIN_SCALE_VALUE) { |
94 |
| - return |
95 |
| - } |
96 |
| - scaleValue.value -= 10 |
| 104 | +const resetCursor = () => { |
| 105 | + document.body.style.cursor = 'default'; |
97 | 106 | }
|
98 | 107 |
|
99 |
| -const processReZoom = () => { |
100 |
| - scaleValue.value = 100 |
| 108 | +const startDrag = (e: MouseEvent) => { |
| 109 | + isDragging.value = true; |
| 110 | + startX.value = e.clientX - currentX.value; |
| 111 | + startY.value = e.clientY - currentY.value; |
| 112 | + setGrabCursor(); // 设置小手光标 |
| 113 | +} |
| 114 | +
|
| 115 | +const onDrag = (e: MouseEvent) => { |
| 116 | + if (!isDragging.value) return; |
| 117 | + e.preventDefault(); // 禁用文本选择 |
| 118 | + |
| 119 | + // 使用 requestAnimationFrame 优化性能 |
| 120 | + requestAnimationFrame(() => { |
| 121 | + currentX.value = e.clientX - startX.value; |
| 122 | + currentY.value = e.clientY - startY.value; |
| 123 | + }); |
| 124 | +} |
| 125 | +
|
| 126 | +const stopDrag = () => { |
| 127 | + isDragging.value = false; |
| 128 | + resetCursor(); // 重置光标 |
101 | 129 | }
|
102 | 130 |
|
103 | 131 | const errorDialogVisible = ref(false)
|
@@ -193,6 +221,56 @@ const importLocalFile = () => {
|
193 | 221 | }
|
194 | 222 | }
|
195 | 223 | }
|
| 224 | +
|
| 225 | +// 放大 |
| 226 | +const zoomIn = () => { |
| 227 | + if (scaleValue.value == MAX_SCALE_VALUE) { |
| 228 | + return |
| 229 | + } |
| 230 | + scaleValue.value += 10 |
| 231 | +} |
| 232 | +
|
| 233 | +// 缩小 |
| 234 | +const zoomOut = () => { |
| 235 | + if (scaleValue.value == MIN_SCALE_VALUE) { |
| 236 | + return |
| 237 | + } |
| 238 | + scaleValue.value -= 10 |
| 239 | +} |
| 240 | +
|
| 241 | +const processReZoom = () => { |
| 242 | + scaleValue.value = 100 |
| 243 | +} |
| 244 | +
|
| 245 | +// 在组件初始化时记录初始位置 |
| 246 | +onMounted(() => { |
| 247 | + initialX.value = currentX.value |
| 248 | + initialY.value = currentY.value |
| 249 | +}) |
| 250 | +
|
| 251 | +// 重置位置的函数 |
| 252 | +const resetPosition = () => { |
| 253 | + currentX.value = initialX.value |
| 254 | + currentY.value = initialY.value |
| 255 | +} |
196 | 256 | </script>
|
197 | 257 |
|
198 |
| -<style lang="scss" scoped></style> |
| 258 | +<style lang="scss" scoped> |
| 259 | +.simple-process-model-container { |
| 260 | + width: 100%; |
| 261 | + height: 100%; |
| 262 | + position: relative; |
| 263 | + overflow: hidden; |
| 264 | + user-select: none; // 禁用文本选择 |
| 265 | +} |
| 266 | +
|
| 267 | +.simple-process-model { |
| 268 | + position: relative; // 确保相对定位 |
| 269 | + min-width: 100%; // 确保宽度为100% |
| 270 | + min-height: 100%; // 确保高度为100% |
| 271 | +} |
| 272 | +
|
| 273 | +.z-index-button-group { |
| 274 | + z-index: 10; |
| 275 | +} |
| 276 | +</style> |
0 commit comments