ElementAnimationPanel.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <template>
  2. <div class="element-animation-panel">
  3. <div class="element-animation" v-if="handleElement">
  4. <Popover trigger="click" v-model:visible="animationPoolVisible">
  5. <template #content>
  6. <div class="animation-pool">
  7. <div class="pool-type" v-for="type in animations" :key="type.name">
  8. <div class="type-title">{{type.name}}:</div>
  9. <div class="pool-item-wrapper">
  10. <div
  11. class="pool-item"
  12. v-for="item in type.children" :key="item.name"
  13. @mouseenter="hoverPreviewAnimation = item.value"
  14. @mouseleave="hoverPreviewAnimation = ''"
  15. @click="addAnimation(item.value)"
  16. >
  17. <div
  18. class="animation-box"
  19. :class="[
  20. 'animate__animated',
  21. 'animate__faster',
  22. hoverPreviewAnimation === item.value && `animate__${item.value}`,
  23. ]"
  24. >
  25. {{item.name}}
  26. </div>
  27. </div>
  28. </div>
  29. </div>
  30. </div>
  31. </template>
  32. <Button class="element-animation-btn">
  33. <IconEffects style="margin-right: 5px;" /> {{handleElementAnimation || '点击选择动画'}}
  34. </Button>
  35. </Popover>
  36. </div>
  37. <div class="tip" v-else><IconClick /> 选中画布中的元素添加动画</div>
  38. <Divider />
  39. <Draggable
  40. class="animation-sequence"
  41. :modelValue="animationSequence"
  42. :animation="300"
  43. :scroll="true"
  44. :scrollSensitivity="50"
  45. @end="handleDragEnd"
  46. itemKey="id"
  47. >
  48. <template #item="{ element, index }">
  49. <div class="sequence-item" :class="{ 'active': handleElement?.id === element.elId }">
  50. <div class="index">{{index + 1}}</div>
  51. <div class="text">【{{element.elType}}】{{element.animationType}}</div>
  52. <div class="handler">
  53. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="预览">
  54. <IconPlayOne class="handler-btn" @click="runAnimation(element.elId, element.type)" />
  55. </Tooltip>
  56. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="删除">
  57. <IconCloseSmall class="handler-btn" @click="deleteAnimation(element.elId)" />
  58. </Tooltip>
  59. </div>
  60. </div>
  61. </template>
  62. </Draggable>
  63. </div>
  64. </template>
  65. <script lang="ts">
  66. import { computed, defineComponent, ref } from 'vue'
  67. import { MutationTypes, useStore } from '@/store'
  68. import { PPTAnimation, PPTElement, Slide } from '@/types/slides'
  69. import { ANIMATIONS } from '@/configs/animation'
  70. import { ELEMENT_TYPE_ZH } from '@/configs/element'
  71. import useHistorySnapshot from '@/hooks/useHistorySnapshot'
  72. import Draggable from 'vuedraggable'
  73. const animationTypes: { [key: string]: string } = {}
  74. for(const type of ANIMATIONS) {
  75. for(const animation of type.children) {
  76. animationTypes[animation.value] = animation.name
  77. }
  78. }
  79. export default defineComponent({
  80. name: 'element-animation-panel',
  81. components: {
  82. Draggable,
  83. },
  84. setup() {
  85. const store = useStore()
  86. const handleElement = computed<PPTElement>(() => store.getters.handleElement)
  87. const currentSlideAnimations = computed<PPTAnimation[] | null>(() => store.getters.currentSlideAnimations)
  88. const currentSlide = computed<Slide>(() => store.getters.currentSlide)
  89. const hoverPreviewAnimation = ref('')
  90. const animationPoolVisible = ref(false)
  91. const { addHistorySnapshot } = useHistorySnapshot()
  92. const animations = ANIMATIONS
  93. const animationSequence = computed(() => {
  94. if(!currentSlideAnimations.value) return []
  95. const animationSequence = []
  96. for(const animation of currentSlideAnimations.value) {
  97. const el = currentSlide.value.elements.find(el => el.id === animation.elId)
  98. if(!el) continue
  99. const elType = ELEMENT_TYPE_ZH[el.type]
  100. const animationType = animationTypes[animation.type]
  101. animationSequence.push({
  102. ...animation,
  103. elType,
  104. animationType,
  105. })
  106. }
  107. return animationSequence
  108. })
  109. const handleElementAnimation = computed(() => {
  110. if(!handleElement.value) return null
  111. const animations = currentSlideAnimations.value || []
  112. const animation = animations.find(item => item.elId === handleElement.value.id)
  113. if(!animation) return null
  114. return animationTypes[animation.type]
  115. })
  116. const updateElementAnimation = (type: string) => {
  117. const animations = (currentSlideAnimations.value as PPTAnimation[]).map(item => {
  118. if(item.elId === handleElement.value.id) return { ...item, type }
  119. return item
  120. })
  121. store.commit(MutationTypes.UPDATE_SLIDE, { animations })
  122. animationPoolVisible.value = false
  123. addHistorySnapshot()
  124. }
  125. const addAnimation = (type: string) => {
  126. if(handleElementAnimation.value) {
  127. updateElementAnimation(type)
  128. return
  129. }
  130. const animations: PPTAnimation[] = currentSlideAnimations.value ? JSON.parse(JSON.stringify(currentSlideAnimations.value)) : []
  131. animations.push({
  132. elId: handleElement.value.id,
  133. type,
  134. duration: 1000,
  135. })
  136. store.commit(MutationTypes.UPDATE_SLIDE, { animations })
  137. animationPoolVisible.value = false
  138. addHistorySnapshot()
  139. }
  140. const deleteAnimation = (elId: string) => {
  141. const animations = (currentSlideAnimations.value as PPTAnimation[]).filter(item => item.elId !== elId)
  142. store.commit(MutationTypes.UPDATE_SLIDE, { animations })
  143. addHistorySnapshot()
  144. }
  145. const handleDragEnd = (eventData: { newIndex: number; oldIndex: number }) => {
  146. const { newIndex, oldIndex } = eventData
  147. if(oldIndex === newIndex) return
  148. const animations: PPTAnimation[] = JSON.parse(JSON.stringify(currentSlideAnimations.value))
  149. const animation = animations[oldIndex]
  150. animations.splice(oldIndex, 1)
  151. animations.splice(newIndex, 0, animation)
  152. store.commit(MutationTypes.UPDATE_SLIDE, { animations })
  153. addHistorySnapshot()
  154. }
  155. const runAnimation = (elId: string, animationType: string) => {
  156. const prefix = 'animate__'
  157. const elRef = document.querySelector(`#editable-element-${elId} [class^=editable-element-]`)
  158. if(elRef) {
  159. const animationName = `${prefix}${animationType}`
  160. elRef.classList.add(`${prefix}animated`, animationName)
  161. const handleAnimationEnd = () => {
  162. elRef.classList.remove(`${prefix}animated`, animationName)
  163. }
  164. elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
  165. }
  166. }
  167. return {
  168. handleElement,
  169. animationPoolVisible,
  170. animations,
  171. animationSequence,
  172. hoverPreviewAnimation,
  173. handleElementAnimation,
  174. addAnimation,
  175. deleteAnimation,
  176. handleDragEnd,
  177. runAnimation,
  178. }
  179. },
  180. })
  181. </script>
  182. <style lang="scss" scoped>
  183. .element-animation-btn {
  184. width: 100%;
  185. }
  186. .tip {
  187. text-align: center;
  188. font-style: italic;
  189. padding-top: 12px;
  190. }
  191. .animation-pool {
  192. width: 400px;
  193. height: 500px;
  194. overflow-y: auto;
  195. overflow-x: hidden;
  196. font-size: 12px;
  197. margin-right: -12px;
  198. padding-right: 12px;
  199. }
  200. .type-title {
  201. width: 100%;
  202. font-size: 13px;
  203. margin-bottom: 10px;
  204. border-left: 4px solid #aaa;
  205. background-color: #eee;
  206. padding: 2px 0 2px 10px;
  207. }
  208. .pool-item-wrapper {
  209. @include grid-layout-wrapper();
  210. }
  211. .pool-item {
  212. @include grid-layout-item(4, 24%);
  213. margin-bottom: 10px;
  214. height: 40px;
  215. line-height: 40px;
  216. text-align: center;
  217. background-color: $lightGray;
  218. cursor: pointer;
  219. }
  220. .sequence-item {
  221. height: 36px;
  222. display: flex;
  223. align-items: center;
  224. border: 1px solid $borderColor;
  225. padding: 6px;
  226. border-radius: $borderRadius;
  227. margin-bottom: 8px;
  228. cursor: grab;
  229. &:active {
  230. cursor: grabbing;
  231. }
  232. &.active {
  233. border-color: $themeColor;
  234. }
  235. .index {
  236. flex: 1;
  237. }
  238. .text {
  239. flex: 6;
  240. }
  241. .handler {
  242. flex: 2;
  243. font-size: 15px;
  244. text-align: right;
  245. }
  246. .handler-btn {
  247. margin-left: 8px;
  248. cursor: pointer;
  249. }
  250. }
  251. </style>