Upload dataSyncService.ts
Browse files
frontend/src/services/dataSyncService.ts
CHANGED
|
@@ -392,6 +392,105 @@ class DataSyncService {
|
|
| 392 |
}
|
| 393 |
}
|
| 394 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
// Get PPT screenshot data (for frontend direct rendering)
|
| 396 |
async getScreenshotData(slideIndex = 0) {
|
| 397 |
const { useAuthStore } = await import('@/store')
|
|
|
|
| 392 |
}
|
| 393 |
}
|
| 394 |
|
| 395 |
+
// Generate image and upload to backend for external link access
|
| 396 |
+
async generateAndUploadImage(slideIndex = 0, options: any = {}) {
|
| 397 |
+
const { format = 'jpeg', quality = 90, elementSelector = '.slide-viewport, .canvas-viewport, .viewport-wrapper' } = options;
|
| 398 |
+
|
| 399 |
+
try {
|
| 400 |
+
const { useAuthStore, useSlidesStore } = await import('@/store')
|
| 401 |
+
const authStore = useAuthStore()
|
| 402 |
+
const slidesStore = useSlidesStore()
|
| 403 |
+
|
| 404 |
+
if (!authStore.isLoggedIn || !this.currentPPTId) {
|
| 405 |
+
throw new Error('User not logged in or no current PPT')
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
// Find target element for screenshot
|
| 409 |
+
let targetElement: HTMLElement | null = null
|
| 410 |
+
const selectors = elementSelector.split(',').map((s: string) => s.trim())
|
| 411 |
+
|
| 412 |
+
for (const selector of selectors) {
|
| 413 |
+
targetElement = document.querySelector(selector) as HTMLElement
|
| 414 |
+
if (targetElement) {
|
| 415 |
+
console.log(`✅ Found screenshot target element: ${selector}`)
|
| 416 |
+
break
|
| 417 |
+
}
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
if (!targetElement) {
|
| 421 |
+
throw new Error(`Screenshot target element not found. Tried selectors: ${selectors.join(', ')}`)
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
// Dynamically load html2canvas
|
| 425 |
+
const html2canvas = await this.loadHtml2Canvas()
|
| 426 |
+
|
| 427 |
+
console.log('🖼️ Starting to generate image for upload...', { format, quality })
|
| 428 |
+
|
| 429 |
+
// Wait for fonts and images to load
|
| 430 |
+
await this.ensureResourcesReady()
|
| 431 |
+
|
| 432 |
+
// Generate screenshot
|
| 433 |
+
const canvas = await html2canvas(targetElement, {
|
| 434 |
+
scale: 2, // High resolution
|
| 435 |
+
useCORS: true,
|
| 436 |
+
allowTaint: true,
|
| 437 |
+
backgroundColor: slidesStore.theme?.backgroundColor || '#ffffff',
|
| 438 |
+
logging: false,
|
| 439 |
+
onclone: function(clonedDoc) {
|
| 440 |
+
// Ensure correct fonts in cloned document
|
| 441 |
+
const clonedElements = clonedDoc.querySelectorAll('*')
|
| 442 |
+
clonedElements.forEach((el: any) => {
|
| 443 |
+
if (el.style) {
|
| 444 |
+
el.style.fontFamily = 'Microsoft YaHei, PingFang SC, Hiragino Sans GB, Source Han Sans SC, Noto Sans SC, WenQuanYi Micro Hei, SimHei, SimSun, Arial, Helvetica, sans-serif'
|
| 445 |
+
}
|
| 446 |
+
})
|
| 447 |
+
}
|
| 448 |
+
})
|
| 449 |
+
|
| 450 |
+
// Convert to specified format
|
| 451 |
+
const imageDataUrl = canvas.toDataURL(`image/${format}`, quality / 100)
|
| 452 |
+
|
| 453 |
+
console.log('✅ Image generated, uploading to backend...')
|
| 454 |
+
|
| 455 |
+
// Upload to backend
|
| 456 |
+
const baseUrl = window.location.origin
|
| 457 |
+
const uploadResponse = await fetch(`${baseUrl}/api/public/save-image/${authStore.currentUser!.id}/${this.currentPPTId}/${slideIndex}`, {
|
| 458 |
+
method: 'POST',
|
| 459 |
+
headers: {
|
| 460 |
+
'Content-Type': 'application/json'
|
| 461 |
+
},
|
| 462 |
+
body: JSON.stringify({
|
| 463 |
+
imageData: imageDataUrl,
|
| 464 |
+
format,
|
| 465 |
+
quality
|
| 466 |
+
})
|
| 467 |
+
})
|
| 468 |
+
|
| 469 |
+
if (!uploadResponse.ok) {
|
| 470 |
+
throw new Error(`Upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`)
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
const uploadResult = await uploadResponse.json()
|
| 474 |
+
|
| 475 |
+
console.log('✅ Image uploaded successfully, external URL:', uploadResult.imageUrl)
|
| 476 |
+
|
| 477 |
+
return {
|
| 478 |
+
success: true,
|
| 479 |
+
imageUrl: uploadResult.imageUrl,
|
| 480 |
+
imageId: uploadResult.imageId,
|
| 481 |
+
expiresAt: uploadResult.expiresAt,
|
| 482 |
+
format,
|
| 483 |
+
quality,
|
| 484 |
+
width: canvas.width,
|
| 485 |
+
height: canvas.height
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
} catch (error: any) {
|
| 489 |
+
console.error('❌ Failed to generate and upload image:', error)
|
| 490 |
+
throw error
|
| 491 |
+
}
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
// Get PPT screenshot data (for frontend direct rendering)
|
| 495 |
async getScreenshotData(slideIndex = 0) {
|
| 496 |
const { useAuthStore } = await import('@/store')
|