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>