index.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. <template>
  2. <div
  3. class="operate"
  4. :class="{ 'multi-select': isMultiSelect && !isActive }"
  5. :style="{
  6. top: elementInfo.top * canvasScale + 'px',
  7. left: elementInfo.left * canvasScale + 'px',
  8. transform: `rotate(${rotate}deg)`,
  9. transformOrigin: `${elementInfo.width * canvasScale / 2}px ${height * canvasScale / 2}px`,
  10. }"
  11. >
  12. <component
  13. v-if="isSelected && !isLockedInteractiveWidget"
  14. :is="currentOperateComponent"
  15. :elementInfo="elementInfo"
  16. :handlerVisible="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)"
  17. :rotateElement="rotateElement"
  18. :scaleElement="scaleElement"
  19. :dragLineElement="dragLineElement"
  20. :moveShapeKeypoint="moveShapeKeypoint"
  21. ></component>
  22. <div
  23. class="animation-index"
  24. v-if="toolbarState === 'elAnimation' && elementIndexListInAnimation.length"
  25. >
  26. <div class="index-item" v-for="index in elementIndexListInAnimation" :key="index">{{index + 1}}</div>
  27. </div>
  28. <LinkHandler
  29. :elementInfo="elementInfo"
  30. :link="elementInfo.link"
  31. :openLinkDialog="openLinkDialog"
  32. v-if="isActive && elementInfo.link"
  33. @mousedown.stop=""
  34. />
  35. </div>
  36. </template>
  37. <script lang="ts" setup>
  38. import { computed } from 'vue'
  39. import { storeToRefs } from 'pinia'
  40. import { useMainStore, useSlidesStore } from '@/store'
  41. import {
  42. ElementTypes,
  43. type PPTElement,
  44. type PPTLineElement,
  45. type PPTVideoElement,
  46. type PPTAudioElement,
  47. type PPTShapeElement,
  48. type PPTChartElement,
  49. } from '@/types/slides'
  50. import type { OperateLineHandlers, OperateResizeHandlers } from '@/types/edit'
  51. import { isArticleReadingTool, isTopicDiscussionTool } from '@/configs/englishSpeakingTools'
  52. import ImageElementOperate from './ImageElementOperate.vue'
  53. import TextElementOperate from './TextElementOperate.vue'
  54. import ShapeElementOperate from './ShapeElementOperate.vue'
  55. import LineElementOperate from './LineElementOperate.vue'
  56. import TableElementOperate from './TableElementOperate.vue'
  57. import CommonElementOperate from './CommonElementOperate.vue'
  58. import LinkHandler from './LinkHandler.vue'
  59. const props = defineProps<{
  60. elementInfo: PPTElement
  61. isSelected: boolean
  62. isActive: boolean
  63. isActiveGroupElement: boolean
  64. isMultiSelect: boolean
  65. rotateElement: (e: MouseEvent, element: Exclude<PPTElement, PPTChartElement | PPTLineElement | PPTVideoElement | PPTAudioElement>) => void
  66. scaleElement: (e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandlers) => void
  67. dragLineElement: (e: MouseEvent, element: PPTLineElement, command: OperateLineHandlers) => void
  68. moveShapeKeypoint: (e: MouseEvent, element: PPTShapeElement, index: number) => void
  69. openLinkDialog: () => void
  70. }>()
  71. const { canvasScale, toolbarState } = storeToRefs(useMainStore())
  72. const { formatedAnimations } = storeToRefs(useSlidesStore())
  73. const currentOperateComponent = computed<unknown>(() => {
  74. const elementTypeMap = {
  75. [ElementTypes.IMAGE]: ImageElementOperate,
  76. [ElementTypes.TEXT]: TextElementOperate,
  77. [ElementTypes.SHAPE]: ShapeElementOperate,
  78. [ElementTypes.LINE]: LineElementOperate,
  79. [ElementTypes.TABLE]: TableElementOperate,
  80. [ElementTypes.CHART]: CommonElementOperate,
  81. [ElementTypes.LATEX]: CommonElementOperate,
  82. [ElementTypes.VIDEO]: CommonElementOperate,
  83. [ElementTypes.AUDIO]: CommonElementOperate,
  84. [ElementTypes.FRAME]: CommonElementOperate,
  85. }
  86. return elementTypeMap[props.elementInfo.type] || null
  87. })
  88. const elementIndexListInAnimation = computed(() => {
  89. const indexList = []
  90. for (let i = 0; i < formatedAnimations.value.length; i++) {
  91. const elIds = formatedAnimations.value[i].animations.map(item => item.elId)
  92. if (elIds.includes(props.elementInfo.id)) indexList.push(i)
  93. }
  94. return indexList
  95. })
  96. const rotate = computed(() => 'rotate' in props.elementInfo ? props.elementInfo.rotate : 0)
  97. const height = computed(() => 'height' in props.elementInfo ? props.elementInfo.height : 0)
  98. // 英语口语预览(FRAME + toolType 77 / 772)产品设计上占满整页不可变形,
  99. // 强制隐藏 resize / rotate 把手,用户仍可选中+删除+开配置面板,但看不到任何"拖动感"。
  100. const isLockedInteractiveWidget = computed(() => {
  101. if (props.elementInfo.type !== ElementTypes.FRAME) return false
  102. const toolType = (props.elementInfo as any).toolType
  103. return isTopicDiscussionTool(toolType) || isArticleReadingTool(toolType)
  104. })
  105. </script>
  106. <style lang="scss" scoped>
  107. .operate {
  108. position: absolute;
  109. z-index: 100;
  110. user-select: none;
  111. &.multi-select {
  112. opacity: 0.2;
  113. }
  114. }
  115. .animation-index {
  116. position: absolute;
  117. top: 0;
  118. left: -24px;
  119. font-size: 12px;
  120. .index-item {
  121. width: 18px;
  122. height: 18px;
  123. background-color: #fff;
  124. color: $themeColor;
  125. border: 1px solid $themeColor;
  126. display: flex;
  127. justify-content: center;
  128. align-items: center;
  129. & + .index-item {
  130. margin-top: 5px;
  131. }
  132. }
  133. }
  134. </style>