File size: 6,231 Bytes
89ce340 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
<template>
<div
class="element-create-selection"
ref="selectionRef"
@mousedown.stop="$event => createSelection($event)"
@contextmenu.stop.prevent
>
<div :class="['selection', creatingElement?.type]" v-if="start && end" :style="position">
<!-- 绘制线条专用 -->
<svg
v-if="creatingElement?.type === 'line' && lineData"
overflow="visible"
:width="lineData.svgWidth"
:height="lineData.svgHeight"
>
<path
:d="lineData.path"
stroke="#d14424"
fill="none"
stroke-width="2"
></path>
</svg>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore, useKeyboardStore } from '@/store'
import type { CreateElementSelectionData } from '@/types/edit'
const emit = defineEmits<{
(event: 'created', payload: CreateElementSelectionData): void
}>()
const mainStore = useMainStore()
const { creatingElement } = storeToRefs(mainStore)
const { ctrlOrShiftKeyActive } = storeToRefs(useKeyboardStore())
const start = ref<[number, number]>()
const end = ref<[number, number]>()
const selectionRef = ref<HTMLElement>()
const offset = ref({
x: 0,
y: 0,
})
onMounted(() => {
if (!selectionRef.value) return
const { x, y } = selectionRef.value.getBoundingClientRect()
offset.value = { x, y }
})
// 鼠标拖动创建元素生成位置大小
// 获取范围的起始位置和终点位置
const createSelection = (e: MouseEvent) => {
let isMouseDown = true
const startPageX = e.pageX
const startPageY = e.pageY
start.value = [startPageX, startPageY]
document.onmousemove = e => {
if (!creatingElement.value || !isMouseDown) return
let currentPageX = e.pageX
let currentPageY = e.pageY
// 按住Ctrl键或者Shift键时:
// 对于非线条元素需要锁定宽高比例,对于线条元素需要锁定水平或垂直方向
if (ctrlOrShiftKeyActive.value) {
const moveX = currentPageX - startPageX
const moveY = currentPageY - startPageY
// 水平和垂直方向的拖动距离,后面以拖动距离较大的方向为基础计算另一方向的数据
const absX = Math.abs(moveX)
const absY = Math.abs(moveY)
if (creatingElement.value.type === 'shape') {
// 判断是否为反向拖动:从左上到右下为正向操作,此外所有情况都是反向操作
const isOpposite = (moveY > 0 && moveX < 0) || (moveY < 0 && moveX > 0)
if (absX > absY) {
currentPageY = isOpposite ? startPageY - moveX : startPageY + moveX
}
else {
currentPageX = isOpposite ? startPageX - moveY : startPageX + moveY
}
}
else if (creatingElement.value.type === 'line') {
if (absX > absY) currentPageY = startPageY
else currentPageX = startPageX
}
}
end.value = [currentPageX, currentPageY]
}
document.onmouseup = e => {
document.onmousemove = null
document.onmouseup = null
if (e.button === 2) {
setTimeout(() => mainStore.setCreatingElement(null), 0)
return
}
isMouseDown = false
const endPageX = e.pageX
const endPageY = e.pageY
const minSize = 30
if (
creatingElement.value?.type === 'line' &&
(Math.abs(endPageX - startPageX) >= minSize || Math.abs(endPageY - startPageY) >= minSize)
) {
emit('created', {
start: start.value!,
end: end.value!,
})
}
else if (
creatingElement.value?.type !== 'line' &&
(Math.abs(endPageX - startPageX) >= minSize && Math.abs(endPageY - startPageY) >= minSize)
) {
emit('created', {
start: start.value!,
end: end.value!,
})
}
else {
const defaultSize = 200
const minX = Math.min(endPageX, startPageX)
const minY = Math.min(endPageY, startPageY)
const maxX = Math.max(endPageX, startPageX)
const maxY = Math.max(endPageY, startPageY)
const offsetX = maxX - minX >= minSize ? maxX - minX : defaultSize
const offsetY = maxY - minY >= minSize ? maxY - minY : defaultSize
emit('created', {
start: [minX, minY],
end: [minX + offsetX, minY + offsetY],
})
}
}
}
// 绘制线条的路径相关数据(仅当绘制元素类型为线条时使用)
const lineData = computed(() => {
if (!start.value || !end.value) return null
if (!creatingElement.value || creatingElement.value.type !== 'line') return null
const [_startX, _startY] = start.value
const [_endX, _endY] = end.value
const minX = Math.min(_startX, _endX)
const maxX = Math.max(_startX, _endX)
const minY = Math.min(_startY, _endY)
const maxY = Math.max(_startY, _endY)
const svgWidth = maxX - minX >= 24 ? maxX - minX : 24
const svgHeight = maxY - minY >= 24 ? maxY - minY : 24
const startX = _startX === minX ? 0 : maxX - minX
const startY = _startY === minY ? 0 : maxY - minY
const endX = _endX === minX ? 0 : maxX - minX
const endY = _endY === minY ? 0 : maxY - minY
const path = `M${startX}, ${startY} L${endX}, ${endY}`
return {
svgWidth,
svgHeight,
startX,
startY,
endX,
endY,
path,
}
})
// 根据生成范围的起始位置和终点位置,计算元素创建时的位置和大小
const position = computed(() => {
if (!start.value || !end.value) return {}
const [startX, startY] = start.value
const [endX, endY] = end.value
const minX = Math.min(startX, endX)
const maxX = Math.max(startX, endX)
const minY = Math.min(startY, endY)
const maxY = Math.max(startY, endY)
const width = maxX - minX
const height = maxY - minY
return {
left: minX - offset.value.x + 'px',
top: minY - offset.value.y + 'px',
width: width + 'px',
height: height + 'px',
}
})
</script>
<style lang="scss" scoped>
.element-create-selection {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
cursor: crosshair;
svg {
overflow: visible;
}
}
.selection {
position: absolute;
opacity: .8;
&:not(.line) {
border: 1px solid $themeColor;
}
}
</style> |