index.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <template>
  2. <div
  3. class="editable-element text"
  4. :class="{ 'lock': elementInfo.isLock }"
  5. :style="{
  6. top: elementInfo.top + 'px',
  7. left: elementInfo.left + 'px',
  8. width: elementInfo.width + 'px',
  9. transform: `rotate(${elementInfo.rotate}deg)`,
  10. }"
  11. @mousedown="handleSelectElement($event, false)"
  12. >
  13. <div class="element-content"
  14. :style="{
  15. backgroundColor: elementInfo.fill,
  16. opacity: elementInfo.opacity,
  17. textShadow: elementInfo.shadow,
  18. lineHeight: elementInfo.lineHeight,
  19. letterSpacing: (elementInfo.letterSpacing || 0) + 'px',
  20. }"
  21. v-contextmenu="contextmenus"
  22. >
  23. <ElementBorder
  24. :width="elementInfo.width"
  25. :height="elementInfo.height"
  26. :borderColor="elementInfo.borderColor"
  27. :borderWidth="elementInfo.borderWidth"
  28. :borderStyle="elementInfo.borderStyle"
  29. />
  30. <div class="text-content"
  31. v-html="elementInfo.content"
  32. :contenteditable="isActive && !elementInfo.isLock"
  33. ></div>
  34. </div>
  35. <div
  36. class="operate"
  37. :class="{
  38. 'active': isActive,
  39. 'multi-select': isMultiSelect && isActive,
  40. 'selected': isHandleEl
  41. }"
  42. :style="{ transform: `scale(${1 / canvasScale})` }"
  43. v-contextmenu="contextmenus"
  44. >
  45. <BorderLine
  46. class="el-border-line"
  47. v-for="line in borderLines"
  48. :key="line.type"
  49. :type="line.type"
  50. :style="line.style"
  51. :isWide="true"
  52. @mousedown="handleSelectElement($event)"
  53. />
  54. <template v-if="!elementInfo.isLock && (isActiveGroupElement || !isMultiSelect)">
  55. <ResizablePoint class="el-resizable-point"
  56. v-for="point in resizablePoints"
  57. :key="point.type"
  58. :type="point.type"
  59. :style="point.style"
  60. @mousedown.stop="scaleElement($event, elementInfo, point.direction)"
  61. />
  62. <RotateHandler
  63. class="el-rotate-handle"
  64. :style="{ left: scaleWidth / 2 + 'px' }"
  65. @mousedown.stop="rotateElement(elementInfo)"
  66. />
  67. </template>
  68. <AnimationIndex v-if="animationIndex !== -1" :animationIndex="animationIndex" />
  69. </div>
  70. </div>
  71. </template>
  72. <script lang="ts">
  73. import { computed, defineComponent, PropType } from 'vue'
  74. import { PPTTextElement } from '@/types/slides'
  75. import { OPERATE_KEYS, ElementScaleHandler, OperateResizablePointTypes, OperateBorderLineTypes } from '@/types/edit'
  76. import ElementBorder from '@/views/_common/_element/ElementBorder.vue'
  77. import RotateHandler from '@/views/_common/_operate/RotateHandler.vue'
  78. import ResizablePoint from '@/views/_common/_operate/ResizablePoint.vue'
  79. import BorderLine from '@/views/_common/_operate/BorderLine.vue'
  80. import AnimationIndex from '@/views/_common/_operate/AnimationIndex.vue'
  81. export default defineComponent({
  82. name: 'slide-element-text',
  83. components: {
  84. ElementBorder,
  85. RotateHandler,
  86. ResizablePoint,
  87. BorderLine,
  88. AnimationIndex,
  89. },
  90. props: {
  91. elementInfo: {
  92. type: Object as PropType<PPTTextElement>,
  93. required: true,
  94. },
  95. canvasScale: {
  96. type: Number,
  97. required: true,
  98. },
  99. isActive: {
  100. type: Boolean,
  101. required: true,
  102. },
  103. isHandleEl: {
  104. type: Boolean,
  105. required: true,
  106. },
  107. isActiveGroupElement: {
  108. type: Boolean,
  109. required: true,
  110. },
  111. isMultiSelect: {
  112. type: Boolean,
  113. required: true,
  114. },
  115. animationIndex: {
  116. type: Number,
  117. required: true,
  118. },
  119. selectElement: {
  120. type: Function as PropType<(e: MouseEvent, element: PPTTextElement, canMove?: boolean) => void>,
  121. required: true,
  122. },
  123. rotateElement: {
  124. type: Function as PropType<(element: PPTTextElement) => void>,
  125. required: true,
  126. },
  127. scaleElement: {
  128. type: Function as PropType<(e: MouseEvent, element: PPTTextElement, command: ElementScaleHandler) => void>,
  129. required: true,
  130. },
  131. contextmenus: {
  132. type: Function,
  133. },
  134. },
  135. setup(props) {
  136. const scaleWidth = computed(() => props.elementInfo ? props.elementInfo.width * props.canvasScale : 0)
  137. const scaleHeight = computed(() => props.elementInfo ? props.elementInfo.height * props.canvasScale : 0)
  138. const resizablePoints = computed(() => {
  139. return [
  140. { type: OperateResizablePointTypes.TL, direction: OPERATE_KEYS.LEFT_TOP, style: {} },
  141. { type: OperateResizablePointTypes.TC, direction: OPERATE_KEYS.TOP, style: {left: scaleWidth.value / 2 + 'px'} },
  142. { type: OperateResizablePointTypes.TR, direction: OPERATE_KEYS.RIGHT_TOP, style: {left: scaleWidth.value + 'px'} },
  143. { type: OperateResizablePointTypes.ML, direction: OPERATE_KEYS.LEFT, style: {top: scaleHeight.value / 2 + 'px'} },
  144. { type: OperateResizablePointTypes.MR, direction: OPERATE_KEYS.RIGHT, style: {left: scaleWidth.value + 'px', top: scaleHeight.value / 2 + 'px'} },
  145. { type: OperateResizablePointTypes.BL, direction: OPERATE_KEYS.LEFT_BOTTOM, style: {top: scaleHeight.value + 'px'} },
  146. { type: OperateResizablePointTypes.BC, direction: OPERATE_KEYS.BOTTOM, style: {left: scaleWidth.value / 2 + 'px', top: scaleHeight.value + 'px'} },
  147. { type: OperateResizablePointTypes.BR, direction: OPERATE_KEYS.RIGHT_BOTTOM, style: {left: scaleWidth.value + 'px', top: scaleHeight.value + 'px'} },
  148. ]
  149. })
  150. const borderLines = computed(() => {
  151. return [
  152. { type: OperateBorderLineTypes.T, style: {width: scaleWidth.value + 'px'} },
  153. { type: OperateBorderLineTypes.B, style: {top: scaleHeight.value + 'px', width: scaleWidth.value + 'px'} },
  154. { type: OperateBorderLineTypes.L, style: {height: scaleHeight.value + 'px'} },
  155. { type: OperateBorderLineTypes.R, style: {left: scaleWidth.value + 'px', height: scaleHeight.value + 'px'} },
  156. ]
  157. })
  158. const handleSelectElement = (e: MouseEvent, canMove = true) => {
  159. if(props.elementInfo.isLock) return
  160. e.stopPropagation()
  161. props.selectElement(e, props.elementInfo, canMove)
  162. }
  163. return {
  164. scaleWidth,
  165. resizablePoints,
  166. borderLines,
  167. handleSelectElement,
  168. }
  169. },
  170. })
  171. </script>
  172. <style lang="scss" scoped>
  173. .editable-element {
  174. position: absolute;
  175. &.lock .el-border-line {
  176. border-color: #888;
  177. }
  178. &:hover .el-border-line {
  179. display: block;
  180. }
  181. &.lock .element-content {
  182. cursor: default;
  183. }
  184. }
  185. .element-content {
  186. position: relative;
  187. padding: 10px;
  188. .text-content {
  189. position: relative;
  190. cursor: text;
  191. }
  192. }
  193. ::v-deep(.text-content) {
  194. word-break: break-word;
  195. font-family: '微软雅黑';
  196. outline: 0;
  197. ::selection {
  198. background-color: rgba(27, 110, 232, 0.3);
  199. color: inherit;
  200. }
  201. ul {
  202. list-style-type: disc;
  203. padding-inline-start: 30px;
  204. li {
  205. list-style-type: disc;
  206. }
  207. }
  208. ol {
  209. list-style-type: decimal;
  210. padding-inline-start: 30px;
  211. li {
  212. list-style-type: decimal;
  213. }
  214. }
  215. }
  216. .operate {
  217. position: absolute;
  218. top: 0;
  219. left: 0;
  220. z-index: 100;
  221. user-select: none;
  222. &.active {
  223. .el-border-line,
  224. .el-resizable-point,
  225. .el-rotate-handle {
  226. display: block;
  227. }
  228. }
  229. &.multi-select:not(.selected) .el-border-line {
  230. border-color: rgba($color: $themeColor, $alpha: .5);
  231. }
  232. .el-border-line,
  233. .el-resizable-point,
  234. .el-rotate-handle {
  235. display: none;
  236. }
  237. }
  238. </style>