diff --git a/frontend/src/composables/useSaveImage.js b/frontend/src/composables/useSaveImage.js index a438833..061ae69 100644 --- a/frontend/src/composables/useSaveImage.js +++ b/frontend/src/composables/useSaveImage.js @@ -1,17 +1,18 @@ /** - * Save image utility — handles mobile (share/long-press) and desktop (download). + * Save image — on mobile use navigator.share (same as recipe card), + * on desktop trigger download. */ const isMobile = () => /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) /** * Save from a data URL. - * Mobile: try navigator.share, fallback to showing image for long-press save. + * Mobile: navigator.share({files}) → system share sheet (save to photos / AirDrop etc) * Desktop: download link. */ export async function saveImageFromUrl(dataUrl, filename) { - // Mobile: try native share first - if (isMobile() && navigator.share && navigator.canShare) { + // Try navigator.share with files (works on iOS Safari, Chrome mobile) + if (navigator.share && navigator.canShare) { try { const res = await fetch(dataUrl) const blob = await res.blob() @@ -20,60 +21,18 @@ export async function saveImageFromUrl(dataUrl, filename) { await navigator.share({ files: [file] }) return 'shared' } - } catch {} + } catch (e) { + // User cancelled share or share failed, fall through to download + if (e.name === 'AbortError') return 'cancelled' + } } - // Mobile fallback: show image popup for long-press save - if (isMobile()) { - showImagePopup(dataUrl, filename) - return 'popup' - } - - // Desktop: direct download + // Fallback: direct download const a = document.createElement('a') a.href = dataUrl a.download = filename + '.png' + document.body.appendChild(a) a.click() + setTimeout(() => a.remove(), 100) return 'downloaded' } - -/** - * Capture a DOM element as image and save it. - */ -export async function captureAndSave(element, filename) { - const { default: html2canvas } = await import('html2canvas') - // Hide buttons during capture - const buttons = element.querySelectorAll('button') - buttons.forEach(b => { b._prevDisplay = b.style.display; b.style.display = 'none' }) - try { - const canvas = await html2canvas(element, { - scale: 2, - backgroundColor: '#ffffff', - useCORS: true, - }) - buttons.forEach(b => b.style.display = b._prevDisplay || '') - const dataUrl = canvas.toDataURL('image/png') - return saveImageFromUrl(dataUrl, filename) - } catch (e) { - buttons.forEach(b => b.style.display = b._prevDisplay || '') - console.error('captureAndSave failed:', e) - return false - } -} - -/** - * Show a fullscreen image popup for long-press saving (mobile fallback). - */ -function showImagePopup(dataUrl, filename) { - const overlay = document.createElement('div') - overlay.style.cssText = 'position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.8);display:flex;flex-direction:column;align-items:center;padding:16px;overflow-y:auto' - overlay.onclick = () => overlay.remove() - overlay.innerHTML = ` -
- 📱 长按下方图片 → 保存到相册 -
- - - ` - document.body.appendChild(overlay) -} diff --git a/frontend/src/views/OilReference.vue b/frontend/src/views/OilReference.vue index a50e4c8..241a87c 100644 --- a/frontend/src/views/OilReference.vue +++ b/frontend/src/views/OilReference.vue @@ -727,24 +727,33 @@ function exportPDF() { setTimeout(() => w.print(), 500) } -// Save element as image — same flow as recipe card: -// 1. html2canvas → dataUrl 2. saveImageFromUrl (share/popup/download) +// Save element as image — identical to recipe card flow async function saveElementAsImage(el, name) { if (!el) { ui.showToast('找不到卡片'); return } + const html2canvas = (await import('html2canvas')).default + const { saveImageFromUrl } = await import('../composables/useSaveImage') + + // Hide buttons during capture + const btns = el.querySelectorAll('button') + btns.forEach(b => { b._d = b.style.display; b.style.display = 'none' }) + try { - const html2canvas = (await import('html2canvas')).default - const { saveImageFromUrl } = await import('../composables/useSaveImage') - // Hide buttons during capture - const btns = el.querySelectorAll('button') - btns.forEach(b => { b._d = b.style.display; b.style.display = 'none' }) - const canvas = await html2canvas(el, { scale: 2, backgroundColor: '#fff', useCORS: true }) + // Same params as recipe card's generateCardImage + const canvas = await html2canvas(el, { + backgroundColor: null, + scale: 3, + useCORS: true, + allowTaint: false, + }) btns.forEach(b => b.style.display = b._d || '') + const dataUrl = canvas.toDataURL('image/png') - const result = await saveImageFromUrl(dataUrl, name) - if (result === 'shared' || result === 'downloaded') ui.showToast('图片已保存') + await saveImageFromUrl(dataUrl, name) + ui.showToast('图片已保存') } catch (e) { - console.error('saveElementAsImage:', e) - ui.showToast('保存失败') + btns.forEach(b => b.style.display = b._d || '') + console.error('saveElementAsImage failed:', e) + ui.showToast('截图失败: ' + (e.message || '未知错误')) } }