slides.ts 7.7 KB

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