index.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <template>
  2. <div
  3. class="editable-element-image"
  4. :class="{
  5. 'lock': elementInfo.lock,
  6. 'cliping': clipingImageElementId === elementInfo.id,
  7. }"
  8. :style="{
  9. top: elementInfo.top + 'px',
  10. left: elementInfo.left + 'px',
  11. width: elementInfo.width + 'px',
  12. height: elementInfo.height + 'px',
  13. transform: `rotate(${elementInfo.rotate}deg)`,
  14. }"
  15. @mousedown="$event => handleSelectElement($event)"
  16. >
  17. <div
  18. class="element-content"
  19. v-contextmenu="contextmenus"
  20. :style="{
  21. filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
  22. transform: flip,
  23. }"
  24. >
  25. <ImageRectOutline
  26. v-if="clipShape.type === 'rect'"
  27. :width="elementInfo.width"
  28. :height="elementInfo.height"
  29. :radius="clipShape.radius"
  30. :outline="elementInfo.outline"
  31. />
  32. <ImageEllipseOutline
  33. v-else-if="clipShape.type === 'ellipse'"
  34. :width="elementInfo.width"
  35. :height="elementInfo.height"
  36. :outline="elementInfo.outline"
  37. />
  38. <ImagePolygonOutline
  39. v-else-if="clipShape.type === 'polygon'"
  40. :width="elementInfo.width"
  41. :height="elementInfo.height"
  42. :outline="elementInfo.outline"
  43. :createPath="clipShape.createPath"
  44. />
  45. <div class="image-content" :style="{clipPath: clipShape.style}">
  46. <img
  47. :src="elementInfo.src"
  48. :draggable="false"
  49. :style="{
  50. top: imgPosition.top,
  51. left: imgPosition.left,
  52. width: imgPosition.width,
  53. height: imgPosition.height,
  54. filter: filter,
  55. }"
  56. alt=""
  57. />
  58. </div>
  59. </div>
  60. </div>
  61. </template>
  62. <script lang="ts">
  63. import { computed, defineComponent, PropType } from 'vue'
  64. import { useStore } from 'vuex'
  65. import { State } from '@/store'
  66. import { PPTImageElement } from '@/types/slides'
  67. import { ContextmenuItem } from '@/components/Contextmenu/types'
  68. import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
  69. import useElementShadow from '@/views/components/element/hooks/useElementShadow'
  70. import ImageRectOutline from './ImageRectOutline.vue'
  71. import ImageEllipseOutline from './ImageEllipseOutline.vue'
  72. import ImagePolygonOutline from './ImagePolygonOutline.vue'
  73. export default defineComponent({
  74. name: 'editable-element-image',
  75. components: {
  76. ImageRectOutline,
  77. ImageEllipseOutline,
  78. ImagePolygonOutline,
  79. },
  80. props: {
  81. elementInfo: {
  82. type: Object as PropType<PPTImageElement>,
  83. required: true,
  84. },
  85. selectElement: {
  86. type: Function as PropType<(e: MouseEvent, element: PPTImageElement, canMove?: boolean) => void>,
  87. required: true,
  88. },
  89. contextmenus: {
  90. type: Function as PropType<() => ContextmenuItem[]>,
  91. },
  92. },
  93. setup(props) {
  94. const store = useStore<State>()
  95. const clipingImageElementId = computed(() => store.state.clipingImageElementId)
  96. const shadow = computed(() => props.elementInfo.shadow)
  97. const { shadowStyle } = useElementShadow(shadow)
  98. const handleSelectElement = (e: MouseEvent) => {
  99. if(props.elementInfo.lock) return
  100. e.stopPropagation()
  101. props.selectElement(e, props.elementInfo)
  102. }
  103. const clipShape = computed(() => {
  104. if(!props.elementInfo || !props.elementInfo.clip) return CLIPPATHS.rect
  105. const shape = props.elementInfo.clip.shape || ClipPathTypes.RECT
  106. return CLIPPATHS[shape]
  107. })
  108. const imgPosition = computed(() => {
  109. if(!props.elementInfo || !props.elementInfo.clip) {
  110. return {
  111. top: '0',
  112. left: '0',
  113. width: '100%',
  114. height: '100%',
  115. }
  116. }
  117. const [start, end] = props.elementInfo.clip.range
  118. const widthScale = (end[0] - start[0]) / 100
  119. const heightScale = (end[1] - start[1]) / 100
  120. const left = start[0] / widthScale
  121. const top = start[1] / heightScale
  122. return {
  123. left: -left + '%',
  124. top: -top + '%',
  125. width: 100 / widthScale + '%',
  126. height: 100 / heightScale + '%',
  127. }
  128. })
  129. const filter = computed(() => {
  130. if(!props.elementInfo.filters) return ''
  131. let filter = ''
  132. for(const key of Object.keys(props.elementInfo.filters)) {
  133. filter += `${key}(${props.elementInfo.filters[key]}) `
  134. }
  135. return filter
  136. })
  137. const flip = computed(() => {
  138. if(!props.elementInfo.flip) return ''
  139. const { x, y } = props.elementInfo.flip
  140. if(x && y) return `rotateX(${x}deg) rotateY(${y}deg)`
  141. else if(x) return `rotateX(${x}deg)`
  142. else if(y) return `rotateY(${y}deg)`
  143. return ''
  144. })
  145. return {
  146. clipingImageElementId,
  147. shadowStyle,
  148. handleSelectElement,
  149. clipShape,
  150. imgPosition,
  151. filter,
  152. flip,
  153. }
  154. },
  155. })
  156. </script>
  157. <style lang="scss" scoped>
  158. .editable-element-image {
  159. position: absolute;
  160. &.lock .element-content {
  161. cursor: default;
  162. }
  163. &.cliping {
  164. visibility: hidden;
  165. }
  166. }
  167. .element-content {
  168. width: 100%;
  169. height: 100%;
  170. position: relative;
  171. cursor: move;
  172. .image-content {
  173. width: 100%;
  174. height: 100%;
  175. overflow: hidden;
  176. position: relative;
  177. }
  178. img {
  179. position: absolute;
  180. }
  181. }
  182. </style>