|
import api from '@/services' |
|
import { debounce } from 'lodash' |
|
|
|
import { toPng, toJpeg, toSvg } from 'html-to-image' |
|
|
|
class DataSyncService { |
|
private currentPPTId: string | null = null |
|
private saveTimeout: number | null = null |
|
private isOnline = true |
|
private autoSaveDelay = 300000 |
|
private isInitialized = false |
|
private debouncedSave: any = null |
|
|
|
constructor() { |
|
this.setupNetworkMonitoring() |
|
} |
|
|
|
|
|
async initialize() { |
|
if (this.isInitialized) return |
|
await this.setupAutoSave() |
|
this.isInitialized = true |
|
} |
|
|
|
|
|
setAutoSaveDelay(delay: number) { |
|
this.autoSaveDelay = Math.max(500, delay) |
|
if (this.isInitialized) { |
|
this.setupAutoSave() |
|
} |
|
} |
|
|
|
|
|
getAutoSaveDelay(): number { |
|
return this.autoSaveDelay |
|
} |
|
|
|
|
|
setCurrentPPTId(pptId: string) { |
|
this.currentPPTId = pptId |
|
} |
|
|
|
|
|
private async setupAutoSave() { |
|
if (this.debouncedSave) { |
|
this.debouncedSave.cancel() |
|
} |
|
|
|
this.debouncedSave = debounce(async () => { |
|
await this.savePPT() |
|
}, this.autoSaveDelay) |
|
|
|
|
|
try { |
|
const { useSlidesStore } = await import('@/store') |
|
const slidesStore = useSlidesStore() |
|
slidesStore.$subscribe(() => { |
|
if (this.isOnline && this.currentPPTId) { |
|
this.debouncedSave() |
|
} |
|
}) |
|
} |
|
catch (error) { |
|
|
|
} |
|
} |
|
|
|
|
|
private setupNetworkMonitoring() { |
|
window.addEventListener('online', () => { |
|
this.isOnline = true |
|
|
|
}) |
|
|
|
window.addEventListener('offline', () => { |
|
this.isOnline = false |
|
|
|
}) |
|
} |
|
|
|
|
|
async savePPT(force = false): Promise<boolean> { |
|
|
|
const { useAuthStore, useSlidesStore } = await import('@/store') |
|
|
|
try { |
|
const authStore = useAuthStore() |
|
const slidesStore = useSlidesStore() |
|
|
|
if (!authStore.isLoggedIn) { |
|
|
|
return false |
|
} |
|
|
|
|
|
if (!this.currentPPTId && force) { |
|
try { |
|
const response = await api.createPPT(slidesStore.title || '未命名演示文稿') |
|
this.currentPPTId = response.pptId |
|
|
|
|
|
if (response.ppt) { |
|
slidesStore.setSlides(response.ppt.slides) |
|
slidesStore.setTitle(response.ppt.title) |
|
slidesStore.setTheme(response.ppt.theme) |
|
} |
|
|
|
|
|
return true |
|
} |
|
catch (createError) { |
|
|
|
return false |
|
} |
|
} |
|
|
|
if (!this.currentPPTId && !force) { |
|
|
|
return false |
|
} |
|
|
|
const pptData = { |
|
pptId: this.currentPPTId, |
|
title: slidesStore.title, |
|
slides: slidesStore.slides, |
|
theme: slidesStore.theme, |
|
|
|
viewportSize: slidesStore.viewportSize, |
|
viewportRatio: slidesStore.viewportRatio |
|
} |
|
|
|
await api.savePPT(pptData) |
|
|
|
return true |
|
} |
|
catch (error) { |
|
|
|
return false |
|
} |
|
} |
|
|
|
|
|
async createNewPPT(title: string): Promise<string | null> { |
|
const { useAuthStore } = await import('@/store') |
|
const authStore = useAuthStore() |
|
|
|
if (!authStore.isLoggedIn) { |
|
throw new Error('用户未登录') |
|
} |
|
|
|
try { |
|
const response = await api.createPPT(title) |
|
this.setCurrentPPTId(response.pptId) |
|
return response.pptId |
|
} |
|
catch (error) { |
|
|
|
throw error |
|
} |
|
} |
|
|
|
|
|
async loadPPT(pptId: string): Promise<boolean> { |
|
const { useAuthStore, useSlidesStore } = await import('@/store') |
|
const authStore = useAuthStore() |
|
const slidesStore = useSlidesStore() |
|
|
|
if (!authStore.isLoggedIn) { |
|
throw new Error('用户未登录') |
|
} |
|
|
|
try { |
|
const pptData = await api.getPPT(pptId) |
|
|
|
slidesStore.setSlides(pptData.slides) |
|
slidesStore.setTitle(pptData.title) |
|
if (pptData.theme) { |
|
slidesStore.setTheme(pptData.theme) |
|
} |
|
|
|
this.setCurrentPPTId(pptId) |
|
|
|
return true |
|
} |
|
catch (error) { |
|
|
|
throw error |
|
} |
|
} |
|
|
|
|
|
async getPPTList() { |
|
const { useAuthStore } = await import('@/store') |
|
const authStore = useAuthStore() |
|
|
|
if (!authStore.isLoggedIn) { |
|
throw new Error('用户未登录') |
|
} |
|
|
|
return await api.getPPTList() |
|
} |
|
|
|
|
|
async deletePPT(pptId: string): Promise<boolean> { |
|
const { useAuthStore } = await import('@/store') |
|
const authStore = useAuthStore() |
|
|
|
if (!authStore.isLoggedIn) { |
|
throw new Error('用户未登录') |
|
} |
|
|
|
try { |
|
await api.deletePPT(pptId) |
|
|
|
|
|
if (this.currentPPTId === pptId) { |
|
this.currentPPTId = null |
|
} |
|
|
|
|
|
return true |
|
} |
|
catch (error) { |
|
|
|
throw error |
|
} |
|
} |
|
|
|
|
|
async generateShareLink(slideIndex = 0) { |
|
const { useAuthStore } = await import('@/store') |
|
const authStore = useAuthStore() |
|
|
|
if (!authStore.isLoggedIn || !this.currentPPTId) { |
|
throw new Error('用户未登录或没有当前PPT') |
|
} |
|
|
|
try { |
|
const response = await api.generateShareLink( |
|
authStore.currentUser!.id, |
|
this.currentPPTId, |
|
slideIndex |
|
) |
|
return response |
|
} |
|
catch (error) { |
|
|
|
throw error |
|
} |
|
} |
|
|
|
|
|
async manualSave(): Promise<boolean> { |
|
return await this.savePPT(true) |
|
} |
|
|
|
|
|
async generateFrontendScreenshot(elementId = 'slideContainer', options = {}) { |
|
try { |
|
const element = document.getElementById(elementId) |
|
if (!element) { |
|
throw new Error(`Element with id '${elementId}' not found`) |
|
} |
|
|
|
const defaultOptions = { |
|
quality: 0.95, |
|
pixelRatio: 1, |
|
backgroundColor: '#ffffff', |
|
...options |
|
} |
|
|
|
console.log('🖼️ 开始前端截图生成...') |
|
|
|
|
|
const imageDataUrl = await toJpeg(element, defaultOptions) |
|
|
|
console.log('✅ 前端截图生成成功') |
|
return imageDataUrl |
|
} catch (error) { |
|
console.error('❌ 前端截图生成失败:', error) |
|
throw error |
|
} |
|
} |
|
|
|
|
|
async generateScreenshotUrl(slideIndex = 0, useFrontend = false) { |
|
const { useAuthStore } = await import('@/store') |
|
const authStore = useAuthStore() |
|
|
|
if (!authStore.isLoggedIn || !this.currentPPTId) { |
|
throw new Error('用户未登录或没有当前PPT') |
|
} |
|
|
|
const baseUrl = window.location.origin |
|
const backendScreenshotUrl = `${baseUrl}/api/public/screenshot/${authStore.currentUser!.id}/${this.currentPPTId}/${slideIndex}` |
|
|
|
if (useFrontend) { |
|
try { |
|
|
|
const frontendScreenshot = await this.generateFrontendScreenshot() |
|
return { |
|
type: 'frontend', |
|
url: frontendScreenshot, |
|
isDataUrl: true |
|
} |
|
} catch (frontendError) { |
|
console.warn('前端截图失败,回退到后端URL:', frontendError.message) |
|
return { |
|
type: 'backend', |
|
url: backendScreenshotUrl, |
|
isDataUrl: false |
|
} |
|
} |
|
} |
|
|
|
return { |
|
type: 'backend', |
|
url: backendScreenshotUrl, |
|
isDataUrl: false |
|
} |
|
} |
|
} |
|
|
|
|
|
export const dataSyncService = new DataSyncService() |
|
export default dataSyncService |