index.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. <template>
  2. <div
  3. class="canvas"
  4. ref="canvasRef"
  5. @mousedown="$event => handleClickBlankArea($event)"
  6. v-contextmenu="contextmenus"
  7. v-click-outside="removeEditorAreaFocus"
  8. >
  9. <div
  10. class="viewport"
  11. ref="viewportRef"
  12. :style="{
  13. width: viewportStyles.width + 'px',
  14. height: viewportStyles.height + 'px',
  15. left: viewportStyles.left + 'px',
  16. top: viewportStyles.top + 'px',
  17. transform: `scale(${canvasScale})`,
  18. }"
  19. >
  20. <MouseSelection
  21. v-if="mouseSelectionState.isShow"
  22. :top="mouseSelectionState.top"
  23. :left="mouseSelectionState.left"
  24. :width="mouseSelectionState.width"
  25. :height="mouseSelectionState.height"
  26. :quadrant="mouseSelectionState.quadrant"
  27. />
  28. <SlideBackground
  29. :background="currentSlide?.background"
  30. :isShowGridLines="isShowGridLines"
  31. />
  32. <AlignmentLine
  33. v-for="(line, index) in alignmentLines" :key="index"
  34. :type="line.type" :axis="line.axis" :length="line.length"
  35. />
  36. <EditableElement
  37. v-for="(element, index) in elementList"
  38. :key="element.elId"
  39. :elementInfo="element"
  40. :elementIndex="index + 1"
  41. :isActive="activeElementIdList.includes(element.elId)"
  42. :isHandleEl="element.elId === handleElementId"
  43. :isActiveGroupElement="activeGroupElementId === element.elId"
  44. :isMultiSelect="activeElementIdList.length > 1"
  45. :canvasScale="canvasScale"
  46. :selectElement="selectElement"
  47. :rotateElement="rotateElement"
  48. :scaleElement="scaleElement"
  49. :orderElement="orderElement"
  50. :combineElements="combineElements"
  51. :uncombineElements="uncombineElements"
  52. :alignElement="alignElement"
  53. :deleteElement="deleteElement"
  54. :lockElement="lockElement"
  55. :copyElement="copyElement"
  56. :cutElement="cutElement"
  57. />
  58. </div>
  59. </div>
  60. </template>
  61. <script lang="ts">
  62. import { computed, defineComponent, reactive, ref, watch } from 'vue'
  63. import { useStore } from 'vuex'
  64. import { State } from '@/store/state'
  65. import { MutationTypes } from '@/store/constants'
  66. import { ContextmenuItem } from '@/components/Contextmenu/types'
  67. import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
  68. import { getImageDataURL } from '@/utils/image'
  69. import useDropImage from '@/hooks/useDropImage'
  70. import useSetViewportSize from './hooks/useSetViewportSize'
  71. import EditableElement from '@/views/_common/_element/EditableElement.vue'
  72. import MouseSelection from './MouseSelection.vue'
  73. import SlideBackground from './SlideBackground.vue'
  74. import AlignmentLine, { AlignmentLineProps } from './AlignmentLine.vue'
  75. export default defineComponent({
  76. name: 'v-canvas',
  77. components: {
  78. EditableElement,
  79. MouseSelection,
  80. SlideBackground,
  81. AlignmentLine,
  82. },
  83. setup() {
  84. const store = useStore<State>()
  85. const elementList = computed(() => {
  86. const currentSlide = store.getters.currentSlide
  87. return currentSlide ? JSON.parse(JSON.stringify(currentSlide.elements)) : []
  88. })
  89. const activeElementIdList = computed(() => store.state.activeElementIdList)
  90. const handleElementId = computed(() => store.state.handleElementId)
  91. const activeGroupElementId = ref('')
  92. const viewportRef = ref<HTMLElement | null>(null)
  93. const isShowGridLines = ref(false)
  94. const alignmentLines = ref<AlignmentLineProps[]>([])
  95. const currentSlide = computed(() => store.getters.currentSlide)
  96. const dropImageFile = useDropImage(viewportRef)
  97. watch(dropImageFile, () => {
  98. if(dropImageFile.value) {
  99. getImageDataURL(dropImageFile.value).then(dataURL => {
  100. console.log(dataURL)
  101. })
  102. }
  103. })
  104. const canvasRef = ref<HTMLElement | null>(null)
  105. const { canvasScale, viewportLeft, viewportTop } = useSetViewportSize(canvasRef)
  106. const viewportStyles = computed(() => ({
  107. width: VIEWPORT_SIZE,
  108. height: VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO,
  109. left: viewportLeft.value,
  110. top: viewportTop.value,
  111. }))
  112. const mouseSelectionState = reactive({
  113. isShow: false,
  114. top: 0,
  115. left: 0,
  116. width: 0,
  117. height: 0,
  118. quadrant: 1,
  119. })
  120. const updateMouseSelection = (e: MouseEvent) => {
  121. if(!viewportRef.value) return
  122. let isMouseDown = true
  123. const viewportRect = viewportRef.value.getBoundingClientRect()
  124. const minSelectionRange = 5
  125. const startPageX = e.pageX
  126. const startPageY = e.pageY
  127. const left = (startPageX - viewportRect.x) / canvasScale.value
  128. const top = (startPageY - viewportRect.y) / canvasScale.value
  129. mouseSelectionState.isShow = false
  130. mouseSelectionState.quadrant = 4
  131. mouseSelectionState.top = top
  132. mouseSelectionState.left = left
  133. mouseSelectionState.width = 0
  134. mouseSelectionState.height = 0
  135. document.onmousemove = e => {
  136. if(!isMouseDown) return
  137. const currentPageX = e.pageX
  138. const currentPageY = e.pageY
  139. const offsetWidth = (currentPageX - startPageX) / canvasScale.value
  140. const offsetHeight = (currentPageY - startPageY) / canvasScale.value
  141. const width = Math.abs(offsetWidth)
  142. const height = Math.abs(offsetHeight)
  143. if( width < minSelectionRange || height < minSelectionRange ) return
  144. let quadrant = 0
  145. if( offsetWidth > 0 && offsetHeight > 0 ) quadrant = 4
  146. else if( offsetWidth < 0 && offsetHeight < 0 ) quadrant = 1
  147. else if( offsetWidth > 0 && offsetHeight < 0 ) quadrant = 2
  148. else if( offsetWidth < 0 && offsetHeight > 0 ) quadrant = 3
  149. mouseSelectionState.isShow = true
  150. mouseSelectionState.quadrant = quadrant
  151. mouseSelectionState.width = width
  152. mouseSelectionState.height = height
  153. }
  154. document.onmouseup = () => {
  155. document.onmousemove = null
  156. document.onmouseup = null
  157. isMouseDown = false
  158. mouseSelectionState.isShow = false
  159. }
  160. }
  161. const editorAreaFocus = computed(() => store.state.editorAreaFocus)
  162. const handleClickBlankArea = (e: MouseEvent) => {
  163. updateMouseSelection(e)
  164. if(!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
  165. }
  166. const removeEditorAreaFocus = () => {
  167. if(editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, false)
  168. }
  169. const selectElement = () => {
  170. console.log('selectElement')
  171. }
  172. const rotateElement = () => {
  173. console.log('rotateElement')
  174. }
  175. const scaleElement = () => {
  176. console.log('scaleElement')
  177. }
  178. const orderElement = () => {
  179. console.log('orderElement')
  180. }
  181. const combineElements = () => {
  182. console.log('combineElements')
  183. }
  184. const uncombineElements = () => {
  185. console.log('uncombineElements')
  186. }
  187. const alignElement = () => {
  188. console.log('alignElement')
  189. }
  190. const deleteElement = () => {
  191. console.log('deleteElement')
  192. }
  193. const lockElement = () => {
  194. console.log('lockElement')
  195. }
  196. const copyElement = () => {
  197. console.log('copyElement')
  198. }
  199. const cutElement = () => {
  200. console.log('cutElement')
  201. }
  202. const contextmenus = (): ContextmenuItem[] => {
  203. return [
  204. {
  205. text: '全选',
  206. subText: 'Ctrl + A',
  207. },
  208. {
  209. text: '粘贴',
  210. subText: 'Ctrl + V',
  211. },
  212. {
  213. text: '清空页面',
  214. },
  215. ]
  216. }
  217. return {
  218. elementList,
  219. activeElementIdList,
  220. handleElementId,
  221. activeGroupElementId,
  222. canvasRef,
  223. viewportRef,
  224. viewportStyles,
  225. canvasScale,
  226. mouseSelectionState,
  227. handleClickBlankArea,
  228. removeEditorAreaFocus,
  229. currentSlide,
  230. isShowGridLines,
  231. alignmentLines,
  232. selectElement,
  233. rotateElement,
  234. scaleElement,
  235. orderElement,
  236. combineElements,
  237. uncombineElements,
  238. alignElement,
  239. deleteElement,
  240. lockElement,
  241. copyElement,
  242. cutElement,
  243. contextmenus,
  244. }
  245. },
  246. })
  247. </script>
  248. <style lang="scss" scoped>
  249. .canvas {
  250. height: 100%;
  251. user-select: none;
  252. overflow: hidden;
  253. background-color: #f9f9f9;
  254. position: relative;
  255. }
  256. .viewport {
  257. position: absolute;
  258. transform-origin: 0 0;
  259. background-color: #fff;
  260. box-shadow: 0 0 20px 0 rgba(0, 0, 0, .1);
  261. }
  262. </style>