slides.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import { defineStore } from "pinia"
  2. import tinycolor from "tinycolor2"
  3. import { omit } from "lodash"
  4. import type { Slide, SlideTheme, PPTElement, PPTAnimation } from "@/types/slides"
  5. import { slides } from "@/mocks/slides"
  6. import { theme } from "@/mocks/theme"
  7. import { layouts } from "@/mocks/layout"
  8. interface RemovePropData {
  9. id: string
  10. propName: string | string[]
  11. }
  12. interface UpdateElementData {
  13. id: string | string[]
  14. props: Partial<PPTElement>
  15. slideId?: string
  16. }
  17. interface FormatedAnimation {
  18. animations: PPTAnimation[]
  19. autoNext: boolean
  20. }
  21. export interface SlidesState {
  22. title: string
  23. theme: SlideTheme
  24. slides: Slide[]
  25. slideIndex: number
  26. viewportSize: number
  27. viewportRatio: number
  28. }
  29. export const useSlidesStore = defineStore("slides", {
  30. state: (): SlidesState => ({
  31. title: "未命名演示文稿", // 幻灯片标题
  32. theme: theme, // 主题样式
  33. slides: slides, // 幻灯片页面数据
  34. slideIndex: 0, // 当前页面索引
  35. viewportSize: 1920, // 可视区域宽度基数
  36. viewportRatio: 0.5625 // 可视区域比例,默认16:9
  37. }),
  38. getters: {
  39. currentSlide(state) {
  40. return state.slides[state.slideIndex]
  41. },
  42. currentSlideAnimations(state) {
  43. const currentSlide = state.slides[state.slideIndex]
  44. if (!currentSlide?.animations) return []
  45. const els = currentSlide.elements
  46. const elIds = els.map(el => el.id)
  47. return currentSlide.animations.filter(animation => elIds.includes(animation.elId))
  48. },
  49. // 格式化的当前页动画
  50. // 将触发条件为“与上一动画同时”的项目向上合并到序列中的同一位置
  51. // 为触发条件为“上一动画之后”项目的上一项添加自动向下执行标记
  52. formatedAnimations(state) {
  53. const currentSlide = state.slides[state.slideIndex]
  54. if (!currentSlide?.animations) return []
  55. const els = currentSlide.elements
  56. const elIds = els.map(el => el.id)
  57. const animations = currentSlide.animations.filter(animation => elIds.includes(animation.elId))
  58. const formatedAnimations: FormatedAnimation[] = []
  59. for (const animation of animations) {
  60. if (animation.trigger === "click" || !formatedAnimations.length) {
  61. formatedAnimations.push({ animations: [animation], autoNext: false })
  62. } else if (animation.trigger === "meantime") {
  63. const last = formatedAnimations[formatedAnimations.length - 1]
  64. last.animations = last.animations.filter(item => item.elId !== animation.elId)
  65. last.animations.push(animation)
  66. formatedAnimations[formatedAnimations.length - 1] = last
  67. } else if (animation.trigger === "auto") {
  68. const last = formatedAnimations[formatedAnimations.length - 1]
  69. last.autoNext = true
  70. formatedAnimations[formatedAnimations.length - 1] = last
  71. formatedAnimations.push({ animations: [animation], autoNext: false })
  72. }
  73. }
  74. return formatedAnimations
  75. },
  76. layouts(state) {
  77. const { themeColor, fontColor, fontName, backgroundColor } = state.theme
  78. const subColor = tinycolor(fontColor).isDark() ? "rgba(230, 230, 230, 0.5)" : "rgba(180, 180, 180, 0.5)"
  79. const layoutsString = JSON.stringify(layouts)
  80. .replace(/{{themeColor}}/g, themeColor)
  81. .replace(/{{fontColor}}/g, fontColor)
  82. .replace(/{{fontName}}/g, fontName)
  83. .replace(/{{backgroundColor}}/g, backgroundColor)
  84. .replace(/{{subColor}}/g, subColor)
  85. return JSON.parse(layoutsString)
  86. }
  87. },
  88. actions: {
  89. setTitle(title: string) {
  90. if (!title) this.title = "未命名演示文稿"
  91. else this.title = title
  92. },
  93. setTheme(themeProps: Partial<SlideTheme>) {
  94. this.theme = { ...this.theme, ...themeProps }
  95. },
  96. setViewportSize(size: number) {
  97. this.viewportSize = size
  98. },
  99. setViewportRatio(viewportRatio: number) {
  100. this.viewportRatio = viewportRatio
  101. },
  102. setSlides(slides: Slide[]) {
  103. this.slides = slides
  104. },
  105. addSlide(slide: Slide | Slide[]) {
  106. const slides = Array.isArray(slide) ? slide : [slide]
  107. for (const slide of slides) {
  108. if (slide.sectionTag) delete slide.sectionTag
  109. }
  110. const addIndex = this.slideIndex + 1
  111. this.slides.splice(addIndex, 0, ...slides)
  112. this.slideIndex = addIndex
  113. },
  114. updateSlide(props: Partial<Slide>, slideId?: string) {
  115. const slideIndex = slideId ? this.slides.findIndex(item => item.id === slideId) : this.slideIndex
  116. this.slides[slideIndex] = { ...this.slides[slideIndex], ...props }
  117. },
  118. removeSlideProps(data: RemovePropData) {
  119. const { id, propName } = data
  120. const slides = this.slides.map(slide => {
  121. return slide.id === id ? omit(slide, propName) : slide
  122. }) as Slide[]
  123. this.slides = slides
  124. },
  125. deleteSlide(slideId: string | string[]) {
  126. const slidesId = Array.isArray(slideId) ? slideId : [slideId]
  127. const slides: Slide[] = JSON.parse(JSON.stringify(this.slides))
  128. const deleteSlidesIndex = []
  129. for (const deletedId of slidesId) {
  130. const index = slides.findIndex(item => item.id === deletedId)
  131. deleteSlidesIndex.push(index)
  132. const deletedSlideSection = slides[index].sectionTag
  133. if (deletedSlideSection) {
  134. const handleSlideNext = slides[index + 1]
  135. if (handleSlideNext && !handleSlideNext.sectionTag) {
  136. delete slides[index].sectionTag
  137. slides[index + 1].sectionTag = deletedSlideSection
  138. }
  139. }
  140. slides.splice(index, 1)
  141. }
  142. let newIndex = Math.min(...deleteSlidesIndex)
  143. const maxIndex = slides.length - 1
  144. if (newIndex > maxIndex) newIndex = maxIndex
  145. this.slideIndex = newIndex
  146. this.slides = slides
  147. },
  148. updateSlideIndex(index: number) {
  149. this.slideIndex = index
  150. },
  151. addElement(element: PPTElement | PPTElement[]) {
  152. const elements = Array.isArray(element) ? element : [element]
  153. const currentSlideEls = this.slides[this.slideIndex].elements
  154. const newEls = [...currentSlideEls, ...elements]
  155. this.slides[this.slideIndex].elements = newEls
  156. },
  157. deleteElement(elementId: string | string[]) {
  158. const elementIdList = Array.isArray(elementId) ? elementId : [elementId]
  159. const currentSlideEls = this.slides[this.slideIndex].elements
  160. const newEls = currentSlideEls.filter(item => !elementIdList.includes(item.id))
  161. this.slides[this.slideIndex].elements = newEls
  162. },
  163. updateElement(data: UpdateElementData) {
  164. const { id, props, slideId } = data
  165. const elIdList = typeof id === "string" ? [id] : id
  166. const slideIndex = slideId ? this.slides.findIndex(item => item.id === slideId) : this.slideIndex
  167. const slide = this.slides[slideIndex]
  168. const elements = slide.elements.map(el => {
  169. return elIdList.includes(el.id) ? { ...el, ...props } : el
  170. })
  171. this.slides[slideIndex].elements = elements as PPTElement[]
  172. },
  173. removeElementProps(data: RemovePropData) {
  174. const { id, propName } = data
  175. const propsNames = typeof propName === "string" ? [propName] : propName
  176. const slideIndex = this.slideIndex
  177. const slide = this.slides[slideIndex]
  178. const elements = slide.elements.map(el => {
  179. return el.id === id ? omit(el, propsNames) : el
  180. })
  181. this.slides[slideIndex].elements = elements as PPTElement[]
  182. }
  183. }
  184. })