import { defineStore } from 'pinia' import { omit } from 'lodash' import type { Slide, SlideTheme, PPTElement, PPTAnimation, SlideTemplate } from '@/types/slides' interface RemovePropData { id: string propName: string | string[] } interface UpdateElementData { id: string | string[] props: Partial slideId?: string } interface FormatedAnimation { animations: PPTAnimation[] autoNext: boolean } export interface SlidesState { title: string theme: SlideTheme slides: Slide[] slideIndex: number viewportSize: number viewportRatio: number templates: SlideTemplate[] } export const useSlidesStore = defineStore('slides', { state: (): SlidesState => ({ title: '未命名演示文稿', // 幻灯片标题 theme: { themeColors: ['#5b9bd5', '#ed7d31', '#a5a5a5', '#ffc000', '#4472c4', '#70ad47'], fontColor: '#333', fontName: '', backgroundColor: '#fff', shadow: { h: 3, v: 3, blur: 2, color: '#808080', }, outline: { width: 2, color: '#525252', style: 'solid', }, }, // 主题样式 slides: [], // 幻灯片页面数据 slideIndex: 0, // 当前页面索引 viewportSize: 1000, // 可视区域宽度基数 viewportRatio: 0.5625, // 可视区域比例,默认16:9 templates: [ { name: '红色通用', id: 'template_1', cover: 'https://asset.pptist.cn/img/template_1.jpg' }, { name: '蓝色通用', id: 'template_2', cover: 'https://asset.pptist.cn/img/template_2.jpg' }, { name: '紫色通用', id: 'template_3', cover: 'https://asset.pptist.cn/img/template_3.jpg' }, { name: '莫兰迪配色', id: 'template_4', cover: 'https://asset.pptist.cn/img/template_4.jpg' }, ], // 模板 }), getters: { currentSlide(state) { return state.slides[state.slideIndex] }, currentSlideAnimations(state) { const currentSlide = state.slides[state.slideIndex] if (!currentSlide?.animations) return [] const els = currentSlide.elements const elIds = els.map(el => el.id) return currentSlide.animations.filter(animation => elIds.includes(animation.elId)) }, // 格式化的当前页动画 // 将触发条件为“与上一动画同时”的项目向上合并到序列中的同一位置 // 为触发条件为“上一动画之后”项目的上一项添加自动向下执行标记 formatedAnimations(state) { const currentSlide = state.slides[state.slideIndex] if (!currentSlide?.animations) return [] const els = currentSlide.elements const elIds = els.map(el => el.id) const animations = currentSlide.animations.filter(animation => elIds.includes(animation.elId)) const formatedAnimations: FormatedAnimation[] = [] for (const animation of animations) { if (animation.trigger === 'click' || !formatedAnimations.length) { formatedAnimations.push({ animations: [animation], autoNext: false }) } else if (animation.trigger === 'meantime') { const last = formatedAnimations[formatedAnimations.length - 1] last.animations = last.animations.filter(item => item.elId !== animation.elId) last.animations.push(animation) formatedAnimations[formatedAnimations.length - 1] = last } else if (animation.trigger === 'auto') { const last = formatedAnimations[formatedAnimations.length - 1] last.autoNext = true formatedAnimations[formatedAnimations.length - 1] = last formatedAnimations.push({ animations: [animation], autoNext: false }) } } return formatedAnimations }, }, actions: { setTitle(title: string) { if (!title) this.title = '未命名演示文稿' else this.title = title }, setTheme(themeProps: Partial) { this.theme = { ...this.theme, ...themeProps } }, setViewportSize(size: number) { // 🔧 修复:确保视口尺寸的合理性和精度 if (typeof size !== 'number' || size <= 0 || size > 2000) { console.warn(`⚠️ Invalid viewportSize: ${size}, using default 1000`) this.viewportSize = 1000 } else { // 保持整数精度,避免浮点误差 this.viewportSize = Math.round(size) } }, setViewportRatio(viewportRatio: number) { // 🔧 修复:确保视口比例的合理性和高精度 if (typeof viewportRatio !== 'number' || viewportRatio <= 0 || viewportRatio > 2) { console.warn(`⚠️ Invalid viewportRatio: ${viewportRatio}, using default 0.5625`) this.viewportRatio = 0.5625 } else { // 🔧 关键修复:保持极高精度,防止累积误差 // 使用8位小数精度确保视口比例的准确性 this.viewportRatio = Math.round(viewportRatio * 100000000) / 100000000 } }, setSlides(slides: Slide[]) { this.slides = slides }, setTemplates(templates: SlideTemplate[]) { this.templates = templates }, addSlide(slide: Slide | Slide[]) { const slides = Array.isArray(slide) ? slide : [slide] for (const slide of slides) { if (slide.sectionTag) delete slide.sectionTag } const addIndex = this.slideIndex + 1 this.slides.splice(addIndex, 0, ...slides) this.slideIndex = addIndex }, updateSlide(props: Partial, slideId?: string) { const slideIndex = slideId ? this.slides.findIndex(item => item.id === slideId) : this.slideIndex this.slides[slideIndex] = { ...this.slides[slideIndex], ...props } }, removeSlideProps(data: RemovePropData) { const { id, propName } = data const slides = this.slides.map(slide => { return slide.id === id ? omit(slide, propName) : slide }) as Slide[] this.slides = slides }, deleteSlide(slideId: string | string[]) { const slidesId = Array.isArray(slideId) ? slideId : [slideId] const slides: Slide[] = JSON.parse(JSON.stringify(this.slides)) const deleteSlidesIndex = [] for (const deletedId of slidesId) { const index = slides.findIndex(item => item.id === deletedId) deleteSlidesIndex.push(index) const deletedSlideSection = slides[index].sectionTag if (deletedSlideSection) { const handleSlideNext = slides[index + 1] if (handleSlideNext && !handleSlideNext.sectionTag) { delete slides[index].sectionTag slides[index + 1].sectionTag = deletedSlideSection } } slides.splice(index, 1) } let newIndex = Math.min(...deleteSlidesIndex) const maxIndex = slides.length - 1 if (newIndex > maxIndex) newIndex = maxIndex this.slideIndex = newIndex this.slides = slides }, updateSlideIndex(index: number) { this.slideIndex = index }, addElement(element: PPTElement | PPTElement[]) { const elements = Array.isArray(element) ? element : [element] const currentSlideEls = this.slides[this.slideIndex].elements const newEls = [...currentSlideEls, ...elements] this.slides[this.slideIndex].elements = newEls }, deleteElement(elementId: string | string[]) { const elementIdList = Array.isArray(elementId) ? elementId : [elementId] const currentSlideEls = this.slides[this.slideIndex].elements const newEls = currentSlideEls.filter(item => !elementIdList.includes(item.id)) this.slides[this.slideIndex].elements = newEls }, updateElement(data: UpdateElementData) { const { id, props, slideId } = data const elIdList = typeof id === 'string' ? [id] : id const slideIndex = slideId ? this.slides.findIndex(item => item.id === slideId) : this.slideIndex const slide = this.slides[slideIndex] const elements = slide.elements.map(el => { return elIdList.includes(el.id) ? { ...el, ...props } : el }) this.slides[slideIndex].elements = (elements as PPTElement[]) }, removeElementProps(data: RemovePropData) { const { id, propName } = data const propsNames = typeof propName === 'string' ? [propName] : propName const slideIndex = this.slideIndex const slide = this.slides[slideIndex] const elements = slide.elements.map(el => { return el.id === id ? omit(el, propsNames) : el }) this.slides[slideIndex].elements = (elements as PPTElement[]) }, }, })