ElementCreateSelection.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. <template>
  2. <div
  3. class="element-create-selection"
  4. ref="selectionRef"
  5. @mousedown.stop="$event => createSelection($event)"
  6. >
  7. <div :class="['selection', elementType]" v-if="start && end" :style="position">
  8. <!-- 绘制线条专用 -->
  9. <SvgWrapper
  10. v-if="elementType === 'line' && lineData"
  11. overflow="visible"
  12. :width="lineData.svgWidth"
  13. :height="lineData.svgHeight"
  14. >
  15. <path
  16. :d="lineData.path"
  17. stroke="#888"
  18. fill="none"
  19. stroke-width="1"
  20. stroke-linecap
  21. stroke-linejoin
  22. stroke-miterlimit
  23. ></path>
  24. </SvgWrapper>
  25. </div>
  26. </div>
  27. </template>
  28. <script lang="ts">
  29. import { computed, defineComponent, onMounted, reactive, Ref, ref } from 'vue'
  30. import { useStore } from 'vuex'
  31. import { MutationTypes, State } from '@/store'
  32. import SvgWrapper from '@/components/SvgWrapper.vue'
  33. export default defineComponent({
  34. name: 'element-create-selection',
  35. components: {
  36. SvgWrapper,
  37. },
  38. setup(props, { emit }) {
  39. const store = useStore<State>()
  40. const ctrlOrShiftKeyActive: Ref<boolean> = computed(() => store.getters.ctrlOrShiftKeyActive)
  41. const elementType = computed(() => store.state.creatingElementType)
  42. const start = ref<[number, number] | null>(null)
  43. const end = ref<[number, number] | null>(null)
  44. const selectionRef = ref<HTMLElement | null>(null)
  45. const offset = reactive({
  46. x: 0,
  47. y: 0,
  48. })
  49. onMounted(() => {
  50. if(!selectionRef.value) return
  51. const { x, y } = selectionRef.value.getBoundingClientRect()
  52. offset.x = x
  53. offset.y = y
  54. })
  55. const createSelection = (e: MouseEvent) => {
  56. let isMouseDown = true
  57. const startPageX = e.pageX
  58. const startPageY = e.pageY
  59. start.value = [startPageX, startPageY]
  60. document.onmousemove = e => {
  61. if(!isMouseDown) return
  62. let currentPageX = e.pageX
  63. let currentPageY = e.pageY
  64. if(ctrlOrShiftKeyActive.value) {
  65. const moveX = currentPageX - startPageX
  66. const moveY = currentPageY - startPageY
  67. const absX = Math.abs(moveX)
  68. const absY = Math.abs(moveY)
  69. if(elementType.value === 'shape') {
  70. // moveX和moveY一正一负
  71. const isOpposite = (moveY > 0 && moveX < 0) || (moveY < 0 && moveX > 0)
  72. if(absX > absY) {
  73. currentPageY = isOpposite ? startPageY - moveX : startPageY + moveX
  74. }
  75. else {
  76. currentPageX = isOpposite ? startPageX - moveY : startPageX + moveY
  77. }
  78. }
  79. else if(elementType.value === 'line') {
  80. if(absX > absY) currentPageY = startPageY
  81. else currentPageX = startPageX
  82. }
  83. }
  84. end.value = [currentPageX, currentPageY]
  85. }
  86. document.onmouseup = e => {
  87. document.onmousemove = null
  88. document.onmouseup = null
  89. isMouseDown = false
  90. const endPageX = e.pageX
  91. const endPageY = e.pageY
  92. const minSize = 30
  93. if(Math.abs(endPageX - startPageX) >= minSize || Math.abs(endPageY - startPageY) >= minSize) {
  94. emit('created', {
  95. start: start.value,
  96. end: end.value,
  97. })
  98. store.commit(MutationTypes.SET_CREATING_ELEMENT_TYPE, '')
  99. }
  100. }
  101. }
  102. const lineData = computed(() => {
  103. if(!start.value || !end.value || elementType.value !== 'line') return null
  104. const [_startX, _startY] = start.value
  105. const [_endX, _endY] = end.value
  106. const minX = Math.min(_startX, _endX)
  107. const maxX = Math.max(_startX, _endX)
  108. const minY = Math.min(_startY, _endY)
  109. const maxY = Math.max(_startY, _endY)
  110. const svgWidth = maxX - minX >= 24 ? maxX - minX : 24
  111. const svgHeight = maxY - minY >= 24 ? maxY - minY : 24
  112. const startX = _startX === minX ? 0 : maxX - minX
  113. const startY = _startY === minY ? 0 : maxY - minY
  114. const endX = _endX === minX ? 0 : maxX - minX
  115. const endY = _endY === minY ? 0 : maxY - minY
  116. const path = `M${startX}, ${startY} L${endX}, ${endY}`
  117. return {
  118. svgWidth,
  119. svgHeight,
  120. startX,
  121. startY,
  122. endX,
  123. endY,
  124. path,
  125. }
  126. })
  127. const position = computed(() => {
  128. if(!start.value || !end.value) return {}
  129. const [startX, startY] = start.value
  130. const [endX, endY] = end.value
  131. const minX = Math.min(startX, endX)
  132. const maxX = Math.max(startX, endX)
  133. const minY = Math.min(startY, endY)
  134. const maxY = Math.max(startY, endY)
  135. const width = maxX - minX
  136. const height = maxY - minY
  137. return {
  138. left: minX - offset.x + 'px',
  139. top: minY - offset.y + 'px',
  140. width: width + 'px',
  141. height: height + 'px',
  142. }
  143. })
  144. return {
  145. selectionRef,
  146. start,
  147. end,
  148. elementType,
  149. createSelection,
  150. lineData,
  151. position,
  152. }
  153. },
  154. })
  155. </script>
  156. <style lang="scss" scoped>
  157. .element-create-selection {
  158. position: absolute;
  159. top: 0;
  160. left: 0;
  161. width: 100%;
  162. height: 100%;
  163. z-index: 2;
  164. cursor: crosshair;
  165. }
  166. .selection {
  167. position: absolute;
  168. &:not(.line) {
  169. border: 1px solid #888;
  170. }
  171. }
  172. </style>