From 26a47aaf23cf89c16d0699b0ba2bec64d20511c4 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Thu, 9 Apr 2026 09:08:22 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=89=8B=E6=9C=BA=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=8A=A0=E9=95=BF=E6=8C=89=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit navigator.share 在部分手机浏览器不可用时,改为弹出全屏图片 让用户长按保存到相册。三层 fallback: 1. navigator.share({files}) → 系统分享面板 2. 图片弹窗 → 长按保存(手机通用) 3. 下载链接(桌面) Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/composables/useSaveImage.js | 69 ++++++++++++------------ 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/frontend/src/composables/useSaveImage.js b/frontend/src/composables/useSaveImage.js index 911d0bb..a438833 100644 --- a/frontend/src/composables/useSaveImage.js +++ b/frontend/src/composables/useSaveImage.js @@ -1,33 +1,40 @@ /** - * Save image — on mobile use native share (save to photos), - * on desktop trigger download. + * Save image utility — handles mobile (share/long-press) and desktop (download). */ const isMobile = () => /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) /** - * Save a data URL or blob as image. - * Mobile: navigator.share → save to photos - * Desktop: download link + * Save from a data URL. + * Mobile: try navigator.share, fallback to showing image for long-press save. + * Desktop: download link. */ export async function saveImageFromUrl(dataUrl, filename) { - if (isMobile() && navigator.canShare) { + // Mobile: try native share first + if (isMobile() && navigator.share && navigator.canShare) { try { const res = await fetch(dataUrl) const blob = await res.blob() const file = new File([blob], filename + '.png', { type: 'image/png' }) if (navigator.canShare({ files: [file] })) { await navigator.share({ files: [file] }) - return true + return 'shared' } } catch {} } - // Desktop fallback + + // Mobile fallback: show image popup for long-press save + if (isMobile()) { + showImagePopup(dataUrl, filename) + return 'popup' + } + + // Desktop: direct download const a = document.createElement('a') a.href = dataUrl a.download = filename + '.png' a.click() - return true + return 'downloaded' } /** @@ -37,40 +44,36 @@ 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.style.display = 'none') + 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 = '') - return saveCanvasImage(canvas, filename) - } catch { - buttons.forEach(b => b.style.display = '') + 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 } } /** - * Save a canvas element as image. + * Show a fullscreen image popup for long-press saving (mobile fallback). */ -export async function saveCanvasImage(canvas, filename) { - if (isMobile() && navigator.canShare) { - try { - const blob = await new Promise(r => canvas.toBlob(r, 'image/png')) - const file = new File([blob], filename + '.png', { type: 'image/png' }) - if (navigator.canShare({ files: [file] })) { - await navigator.share({ files: [file] }) - return true - } - } catch {} - } - // Desktop fallback - const url = canvas.toDataURL('image/png') - const a = document.createElement('a') - a.href = url - a.download = filename + '.png' - a.click() - return true +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) }