index.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <template>
  2. <div
  3. class="canvas"
  4. ref="canvasRef"
  5. @mousewheel="$event => mousewheelScaleCanvas($event)"
  6. @mousedown="$event => handleClickBlankArea($event)"
  7. v-contextmenu="contextmenus"
  8. v-click-outside="removeEditorAreaFocus"
  9. >
  10. <div
  11. class="viewport-wrapper"
  12. :style="{
  13. width: viewportStyles.width * canvasScale + 'px',
  14. height: viewportStyles.height * canvasScale + 'px',
  15. left: viewportStyles.left + 'px',
  16. top: viewportStyles.top + 'px',
  17. }"
  18. >
  19. <div class="operates">
  20. <AlignmentLine
  21. v-for="(line, index) in alignmentLines"
  22. :key="index"
  23. :type="line.type"
  24. :axis="line.axis"
  25. :length="line.length"
  26. />
  27. <MultiSelectOperate
  28. v-if="activeElementIdList.length > 1"
  29. :elementList="elementList"
  30. :scaleMultiElement="scaleMultiElement"
  31. />
  32. <Operate
  33. v-for="element in elementList"
  34. :key="element.id"
  35. :elementInfo="element"
  36. :isSelected="activeElementIdList.includes(element.id)"
  37. :isActive="handleElementId === element.id"
  38. :isActiveGroupElement="activeGroupElementId === element.id"
  39. :isMultiSelect="activeElementIdList.length > 1"
  40. :rotateElement="rotateElement"
  41. :scaleElement="scaleElement"
  42. :dragLineElement="dragLineElement"
  43. />
  44. <SlideBackground />
  45. </div>
  46. <div
  47. class="viewport"
  48. ref="viewportRef"
  49. :style="{ transform: `scale(${canvasScale})` }"
  50. >
  51. <MouseSelection
  52. v-if="mouseSelectionState.isShow"
  53. :top="mouseSelectionState.top"
  54. :left="mouseSelectionState.left"
  55. :width="mouseSelectionState.width"
  56. :height="mouseSelectionState.height"
  57. :quadrant="mouseSelectionState.quadrant"
  58. />
  59. <EditableElement
  60. v-for="(element, index) in elementList"
  61. :key="element.id"
  62. :elementInfo="element"
  63. :elementIndex="index + 1"
  64. :isMultiSelect="activeElementIdList.length > 1"
  65. :selectElement="selectElement"
  66. />
  67. </div>
  68. </div>
  69. </div>
  70. </template>
  71. <script lang="ts">
  72. import { computed, defineComponent, Ref, ref, watch, watchEffect } from 'vue'
  73. import { useStore } from 'vuex'
  74. import throttle from 'lodash/throttle'
  75. import { State, MutationTypes } from '@/store'
  76. import { ContextmenuItem } from '@/components/Contextmenu/types'
  77. import { PPTElement, Slide } from '@/types/slides'
  78. import { AlignmentLineProps } from '@/types/edit'
  79. import useViewportSize from './hooks/useViewportSize'
  80. import useMouseSelection from './hooks/useMouseSelection'
  81. import useDropImageOrText from './hooks/useDropImageOrText'
  82. import useRotateElement from './hooks/useRotateElement'
  83. import useScaleElement from './hooks/useScaleElement'
  84. import useSelectElement from './hooks/useSelectElement'
  85. import useDragElement from './hooks/useDragElement'
  86. import useDragLineElement from './hooks/useDragLineElement'
  87. import useDeleteElement from '@/hooks/useDeleteElement'
  88. import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
  89. import useSelectAllElement from '@/hooks/useSelectAllElement'
  90. import useScaleCanvas from '@/hooks/useScaleCanvas'
  91. import EditableElement from './EditableElement.vue'
  92. import MouseSelection from './MouseSelection.vue'
  93. import SlideBackground from './SlideBackground.vue'
  94. import AlignmentLine from './AlignmentLine.vue'
  95. import MultiSelectOperate from './Operate/MultiSelectOperate.vue'
  96. import Operate from './Operate/index.vue'
  97. export default defineComponent({
  98. name: 'editor-canvas',
  99. components: {
  100. EditableElement,
  101. MouseSelection,
  102. SlideBackground,
  103. AlignmentLine,
  104. MultiSelectOperate,
  105. Operate,
  106. },
  107. setup() {
  108. const store = useStore<State>()
  109. const activeElementIdList = computed(() => store.state.activeElementIdList)
  110. const handleElementId = computed(() => store.state.handleElementId)
  111. const editorAreaFocus = computed(() => store.state.editorAreaFocus)
  112. const ctrlKeyState = computed(() => store.state.ctrlKeyState)
  113. const ctrlOrShiftKeyActive: Ref<boolean> = computed(() => store.getters.ctrlOrShiftKeyActive)
  114. const viewportRef = ref<HTMLElement | null>(null)
  115. const isShowGridLines = ref(false)
  116. const alignmentLines = ref<AlignmentLineProps[]>([])
  117. const activeGroupElementId = ref('')
  118. watch(handleElementId, () => activeGroupElementId.value = '')
  119. const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
  120. const elementList = ref<PPTElement[]>([])
  121. const setLocalElementList = () => {
  122. elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []
  123. }
  124. watchEffect(setLocalElementList)
  125. useDropImageOrText(viewportRef)
  126. const canvasRef = ref<HTMLElement | null>(null)
  127. const canvasScale = computed(() => store.state.canvasScale)
  128. const { viewportStyles } = useViewportSize(canvasRef)
  129. const { mouseSelectionState, updateMouseSelection } = useMouseSelection(elementList, viewportRef)
  130. const { dragElement } = useDragElement(elementList, activeGroupElementId, alignmentLines)
  131. const { dragLineElement } = useDragLineElement(elementList)
  132. const { selectElement } = useSelectElement(elementList, activeGroupElementId, dragElement)
  133. const { scaleElement, scaleMultiElement } = useScaleElement(elementList, activeGroupElementId, alignmentLines)
  134. const { rotateElement } = useRotateElement(elementList, viewportRef)
  135. const { selectAllElement } = useSelectAllElement()
  136. const { deleteAllElements } = useDeleteElement()
  137. const { pasteElement } = useCopyAndPasteElement()
  138. const handleClickBlankArea = (e: MouseEvent) => {
  139. store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
  140. if(!ctrlOrShiftKeyActive.value) updateMouseSelection(e)
  141. if(!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
  142. }
  143. const removeEditorAreaFocus = () => {
  144. if(editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, false)
  145. }
  146. const { scaleCanvas } = useScaleCanvas()
  147. const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing: false })
  148. const mousewheelScaleCanvas = (e: WheelEvent) => {
  149. if(!ctrlKeyState.value) return
  150. e.preventDefault()
  151. if(e.deltaY > 0) throttleScaleCanvas('-')
  152. else if(e.deltaY < 0) throttleScaleCanvas('+')
  153. }
  154. const showGridLines = computed(() => store.state.showGridLines)
  155. const toggleGridLines = () => {
  156. store.commit(MutationTypes.SET_GRID_LINES_STATE, !showGridLines.value)
  157. }
  158. const contextmenus = (): ContextmenuItem[] => {
  159. return [
  160. {
  161. text: '全选',
  162. subText: 'Ctrl + A',
  163. handler: selectAllElement,
  164. },
  165. {
  166. text: '粘贴',
  167. subText: 'Ctrl + V',
  168. handler: pasteElement,
  169. },
  170. {
  171. text: '网格参考线',
  172. children: [
  173. { text: '网格线', handler: toggleGridLines },
  174. { text: '参考线' },
  175. ],
  176. },
  177. {
  178. text: '清空本页',
  179. handler: deleteAllElements,
  180. },
  181. ]
  182. }
  183. return {
  184. elementList,
  185. activeElementIdList,
  186. handleElementId,
  187. activeGroupElementId,
  188. canvasRef,
  189. viewportRef,
  190. viewportStyles,
  191. canvasScale,
  192. mouseSelectionState,
  193. handleClickBlankArea,
  194. removeEditorAreaFocus,
  195. currentSlide,
  196. isShowGridLines,
  197. alignmentLines,
  198. selectElement,
  199. rotateElement,
  200. scaleElement,
  201. dragLineElement,
  202. scaleMultiElement,
  203. mousewheelScaleCanvas,
  204. contextmenus,
  205. }
  206. },
  207. })
  208. </script>
  209. <style lang="scss" scoped>
  210. .canvas {
  211. height: 100%;
  212. user-select: none;
  213. overflow: hidden;
  214. background-color: #f9f9f9;
  215. position: relative;
  216. }
  217. .viewport-wrapper {
  218. position: absolute;
  219. box-shadow: 0 0 20px 0 rgba(0, 0, 0, .1);
  220. }
  221. .viewport {
  222. position: absolute;
  223. top: 0;
  224. left: 0;
  225. transform-origin: 0 0;
  226. }
  227. </style>