EditableElement.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <template>
  2. <div
  3. class="editable-element"
  4. ref="elementRef"
  5. :id="`editable-element-${elementInfo.id}`"
  6. :style="{
  7. zIndex: elementIndex,
  8. }"
  9. >
  10. <component
  11. :is="currentElementComponent"
  12. :elementInfo="elementInfo"
  13. :selectElement="selectElement"
  14. :contextmenus="contextmenus"
  15. ></component>
  16. </div>
  17. </template>
  18. <script lang="ts" setup>
  19. import { computed } from 'vue'
  20. import { storeToRefs } from 'pinia'
  21. import { ElementTypes, type PPTElement } from '@/types/slides'
  22. import type { ContextmenuItem } from '@/components/Contextmenu/types'
  23. import useLockElement from '@/hooks/useLockElement'
  24. import useDeleteElement from '@/hooks/useDeleteElement'
  25. import useCombineElement from '@/hooks/useCombineElement'
  26. import useOrderElement from '@/hooks/useOrderElement'
  27. import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
  28. import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
  29. import useSelectElement from '@/hooks/useSelectElement'
  30. import useHistorySnapshot from '@/hooks/useHistorySnapshot'
  31. import { useSlidesStore } from '@/store'
  32. import message from '@/utils/message'
  33. import { ElementOrderCommands, ElementAlignCommands } from '@/types/edit'
  34. import ImageElement from '@/views/components/element/ImageElement/index.vue'
  35. import TextElement from '@/views/components/element/TextElement/index.vue'
  36. import ShapeElement from '@/views/components/element/ShapeElement/index.vue'
  37. import LineElement from '@/views/components/element/LineElement/index.vue'
  38. import ChartElement from '@/views/components/element/ChartElement/index.vue'
  39. import TableElement from '@/views/components/element/TableElement/index.vue'
  40. import LatexElement from '@/views/components/element/LatexElement/index.vue'
  41. import VideoElement from '@/views/components/element/VideoElement/index.vue'
  42. import AudioElement from '@/views/components/element/AudioElement/index.vue'
  43. import FrameElement from '@/views/components/element/FrameElement/index.vue'
  44. const props = defineProps<{
  45. elementInfo: PPTElement
  46. elementIndex: number
  47. isMultiSelect: boolean
  48. selectElement: (e: MouseEvent | TouchEvent, element: PPTElement, canMove?: boolean) => void
  49. openLinkDialog: () => void
  50. openWebpageLinkEditDialog: (elementId: string, currentUrl: string) => void
  51. }>()
  52. const currentElementComponent = computed<unknown>(() => {
  53. const elementTypeMap = {
  54. [ElementTypes.IMAGE]: ImageElement,
  55. [ElementTypes.TEXT]: TextElement,
  56. [ElementTypes.SHAPE]: ShapeElement,
  57. [ElementTypes.LINE]: LineElement,
  58. [ElementTypes.CHART]: ChartElement,
  59. [ElementTypes.TABLE]: TableElement,
  60. [ElementTypes.LATEX]: LatexElement,
  61. [ElementTypes.VIDEO]: VideoElement,
  62. [ElementTypes.AUDIO]: AudioElement,
  63. [ElementTypes.FRAME]: FrameElement,
  64. }
  65. return elementTypeMap[props.elementInfo.type] || null
  66. })
  67. const { orderElement } = useOrderElement()
  68. const { alignElementToCanvas } = useAlignElementToCanvas()
  69. const { combineElements, uncombineElements } = useCombineElement()
  70. const { deleteElement } = useDeleteElement()
  71. const { lockElement, unlockElement } = useLockElement()
  72. const { copyElement, pasteElement, cutElement } = useCopyAndPasteElement()
  73. const { selectAllElements } = useSelectElement()
  74. const contextmenus = (): ContextmenuItem[] => {
  75. if (props.elementInfo.lock) {
  76. return [{
  77. text: '解锁',
  78. handler: () => unlockElement(props.elementInfo),
  79. }]
  80. }
  81. const baseMenu = [
  82. {
  83. text: '剪切',
  84. subText: 'Ctrl + X',
  85. handler: cutElement,
  86. },
  87. {
  88. text: '复制',
  89. subText: 'Ctrl + C',
  90. handler: copyElement,
  91. },
  92. {
  93. text: '粘贴',
  94. subText: 'Ctrl + V',
  95. handler: pasteElement,
  96. },
  97. { divider: true },
  98. {
  99. text: '水平居中',
  100. handler: () => alignElementToCanvas(ElementAlignCommands.HORIZONTAL),
  101. children: [
  102. { text: '水平垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.CENTER), },
  103. { text: '水平居中', handler: () => alignElementToCanvas(ElementAlignCommands.HORIZONTAL) },
  104. { text: '左对齐', handler: () => alignElementToCanvas(ElementAlignCommands.LEFT) },
  105. { text: '右对齐', handler: () => alignElementToCanvas(ElementAlignCommands.RIGHT) },
  106. ],
  107. },
  108. {
  109. text: '垂直居中',
  110. handler: () => alignElementToCanvas(ElementAlignCommands.VERTICAL),
  111. children: [
  112. { text: '水平垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.CENTER) },
  113. { text: '垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.VERTICAL) },
  114. { text: '顶部对齐', handler: () => alignElementToCanvas(ElementAlignCommands.TOP) },
  115. { text: '底部对齐', handler: () => alignElementToCanvas(ElementAlignCommands.BOTTOM) },
  116. ],
  117. },
  118. { divider: true },
  119. {
  120. text: '置于顶层',
  121. disable: props.isMultiSelect && !props.elementInfo.groupId,
  122. handler: () => orderElement(props.elementInfo, ElementOrderCommands.TOP),
  123. children: [
  124. { text: '置于顶层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.TOP) },
  125. { text: '上移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.UP) },
  126. ],
  127. },
  128. {
  129. text: '置于底层',
  130. disable: props.isMultiSelect && !props.elementInfo.groupId,
  131. handler: () => orderElement(props.elementInfo, ElementOrderCommands.BOTTOM),
  132. children: [
  133. { text: '置于底层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.BOTTOM) },
  134. { text: '下移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.DOWN) },
  135. ],
  136. },
  137. { divider: true },
  138. {
  139. text: '设置链接',
  140. handler: props.openLinkDialog,
  141. },
  142. {
  143. text: props.elementInfo.groupId ? '取消组合' : '组合',
  144. subText: 'Ctrl + G',
  145. handler: props.elementInfo.groupId ? uncombineElements : combineElements,
  146. hide: !props.isMultiSelect,
  147. },
  148. {
  149. text: '全选',
  150. subText: 'Ctrl + A',
  151. handler: selectAllElements,
  152. },
  153. {
  154. text: '锁定',
  155. subText: 'Ctrl + L',
  156. handler: lockElement,
  157. },
  158. {
  159. text: '删除',
  160. subText: 'Delete',
  161. handler: deleteElement,
  162. },
  163. ]
  164. // 为网页元素添加特殊菜单项
  165. if (props.elementInfo.type === ElementTypes.FRAME) {
  166. const frameMenu = [
  167. // {
  168. // text: '修改链接',
  169. // handler: () => {
  170. // const frameElement = props.elementInfo as any
  171. // if (frameElement.url) {
  172. // props.openWebpageLinkEditDialog(frameElement.id, frameElement.url)
  173. // }
  174. // },
  175. // },
  176. {
  177. text: '在新窗口打开',
  178. handler: () => {
  179. const frameElement = props.elementInfo as any
  180. if (frameElement.url) {
  181. window.open(frameElement.url, '_blank')
  182. }
  183. },
  184. },
  185. {
  186. text: '复制链接',
  187. handler: () => {
  188. const frameElement = props.elementInfo as any
  189. if (frameElement.url) {
  190. navigator.clipboard.writeText(frameElement.url)
  191. }
  192. },
  193. },
  194. { divider: true },
  195. ]
  196. // 为网页元素过滤掉"设置链接"功能
  197. const filteredBaseMenu = baseMenu.filter(item => item.text !== '设置链接')
  198. return [...frameMenu, ...filteredBaseMenu]
  199. }
  200. return baseMenu
  201. }
  202. </script>