fix: 保存图片一次点击即弹分享面板
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 3s
PR Preview / test (pull_request) Successful in 4s
PR Preview / deploy-preview (pull_request) Successful in 14s
Test / e2e-test (push) Failing after 1m20s
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 3s
PR Preview / test (pull_request) Successful in 4s
PR Preview / deploy-preview (pull_request) Successful in 14s
Test / e2e-test (push) Failing after 1m20s
根因: navigator.share 需要在用户手势同步上下文中调用。 之前点保存→html2canvas截图(异步)→share(手势已过期),失败。 修复: 打开 modal 时预生成图片(watch showDilution/showContra, openOilDetail里预生成),点保存按钮时 dataUrl 已缓存, navigator.share 在用户手势上下文内直接调用,一次即弹分享面板。 保存按钮放回卡片内部。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,9 +36,11 @@
|
||||
<div style="margin-top:16px;padding:12px;background:#e8f5e9;border-radius:10px;font-size:12px;color:#2e7d32;text-align:center">
|
||||
💡 稀释比例 = 1滴精油 : N滴椰子油<br>比例越大越温和,新手建议从高稀释比例开始
|
||||
</div>
|
||||
<div style="text-align:center;margin-top:12px">
|
||||
<button @click="saveDilutionImage" style="padding:8px 16px;border-radius:10px;border:1.5px solid var(--sage);background:white;color:var(--sage-dark);cursor:pointer;font-size:13px;font-family:inherit">💾 保存图片</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="saveDilutionImage" style="margin-top:12px;padding:8px 20px;border-radius:20px;border:none;background:rgba(255,255,255,0.9);color:#2e7d32;cursor:pointer;font-size:13px;font-family:inherit;font-weight:600;z-index:2">💾 保存图片</button>
|
||||
</div>
|
||||
|
||||
<!-- Safety Cautions Modal -->
|
||||
@@ -75,9 +77,11 @@
|
||||
<span style="font-size:20px;flex-shrink:0">💧</span>
|
||||
<div><div style="font-weight:600;color:var(--text-dark)">少量多次,多喝水</div><div style="font-size:12px;color:var(--text-light);margin-top:2px">使用精油后多补充水分,帮助身体代谢</div></div>
|
||||
</div>
|
||||
<div style="text-align:center;margin-top:12px">
|
||||
<button @click="saveContraImage" style="padding:8px 16px;border-radius:10px;border:1.5px solid #e65100;background:white;color:#e65100;cursor:pointer;font-size:13px;font-family:inherit">💾 保存图片</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="saveContraImage" style="margin-top:12px;padding:8px 20px;border-radius:20px;border:none;background:rgba(255,255,255,0.9);color:#e65100;cursor:pointer;font-size:13px;font-family:inherit;font-weight:600;z-index:2">💾 保存图片</button>
|
||||
</div>
|
||||
|
||||
<!-- Search + View Toggle + Add + PDF -->
|
||||
@@ -203,9 +207,11 @@
|
||||
<h4 class="oil-card-caution-title">⚠️ 注意事项</h4>
|
||||
<p>{{ activeCard.caution }}</p>
|
||||
</div>
|
||||
<div style="text-align:center;padding-top:12px">
|
||||
<button class="btn btn-outline btn-sm" @click="saveCardImage(activeCardName)">💾 保存图片</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="saveCardImage(activeCardName)" style="margin-top:12px;padding:8px 20px;border-radius:20px;border:none;background:rgba(255,255,255,0.9);color:var(--sage-dark);cursor:pointer;font-size:13px;font-family:inherit;font-weight:600;z-index:2">💾 保存图片</button>
|
||||
</div>
|
||||
|
||||
<!-- Simple Oil Detail Panel (for oils without a knowledge card) -->
|
||||
@@ -546,12 +552,18 @@ function parseMethodBadges(methodStr) {
|
||||
}
|
||||
|
||||
// Actions
|
||||
function openOilDetail(name) {
|
||||
async function openOilDetail(name) {
|
||||
const card = getOilCard(name)
|
||||
if (card) {
|
||||
activeCardName.value = name
|
||||
activeCard.value = card
|
||||
selectedOilName.value = null
|
||||
// Pre-generate card image for instant save
|
||||
oilCardImageUrl.value = null
|
||||
await nextTick()
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
const el = document.querySelector('.oil-card-modal')
|
||||
if (el) await generateImageFromRef({ value: el }, oilCardImageUrl)
|
||||
} else {
|
||||
activeCard.value = null
|
||||
activeCardName.value = null
|
||||
@@ -748,33 +760,59 @@ async function generateImageFromRef(elRef, imageUrlRef) {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveGeneratedImage(imageUrlRef, elRef, name) {
|
||||
// Generate if not already done
|
||||
if (!imageUrlRef.value) {
|
||||
await generateImageFromRef(elRef, imageUrlRef)
|
||||
// When modal opens, pre-generate the image (so save button has instant dataUrl)
|
||||
watch(showDilution, async (v) => {
|
||||
if (v) {
|
||||
dilutionImageUrl.value = null
|
||||
await nextTick()
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
await generateImageFromRef(dilutionCardRef, dilutionImageUrl)
|
||||
}
|
||||
if (!imageUrlRef.value) {
|
||||
ui.showToast('图片生成失败')
|
||||
})
|
||||
watch(showContra, async (v) => {
|
||||
if (v) {
|
||||
contraImageUrl.value = null
|
||||
await nextTick()
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
await generateImageFromRef(contraCardRef, contraImageUrl)
|
||||
}
|
||||
})
|
||||
|
||||
// Save: dataUrl is already cached, navigator.share runs in fresh user gesture
|
||||
async function saveDilutionImage() {
|
||||
if (!dilutionImageUrl.value) {
|
||||
ui.showToast('图片生成中,请稍后再试')
|
||||
return
|
||||
}
|
||||
// Same as RecipeDetailOverlay.saveImage
|
||||
const { saveImageFromUrl } = await import('../composables/useSaveImage')
|
||||
await saveImageFromUrl(imageUrlRef.value, name)
|
||||
await saveImageFromUrl(dilutionImageUrl.value, '精油稀释比例指南')
|
||||
ui.showToast('已保存图片')
|
||||
}
|
||||
|
||||
function saveDilutionImage() {
|
||||
saveGeneratedImage(dilutionImageUrl, dilutionCardRef, '精油稀释比例指南')
|
||||
async function saveContraImage() {
|
||||
if (!contraImageUrl.value) {
|
||||
ui.showToast('图片生成中,请稍后再试')
|
||||
return
|
||||
}
|
||||
const { saveImageFromUrl } = await import('../composables/useSaveImage')
|
||||
await saveImageFromUrl(contraImageUrl.value, '精油使用禁忌')
|
||||
ui.showToast('已保存图片')
|
||||
}
|
||||
function saveContraImage() {
|
||||
saveGeneratedImage(contraImageUrl, contraCardRef, '精油使用禁忌')
|
||||
}
|
||||
function saveCardImage(name) {
|
||||
|
||||
async function saveCardImage(name) {
|
||||
// Oil card: generate on demand since we don't know which card opens
|
||||
const el = document.querySelector('.oil-card-modal')
|
||||
if (!el) { ui.showToast('找不到卡片'); return }
|
||||
// Use a temporary ref-like object
|
||||
const tmpRef = { value: el }
|
||||
saveGeneratedImage(oilCardImageUrl, tmpRef, name + '_精油知识卡')
|
||||
if (!oilCardImageUrl.value) {
|
||||
await generateImageFromRef({ value: el }, oilCardImageUrl)
|
||||
}
|
||||
if (!oilCardImageUrl.value) {
|
||||
ui.showToast('图片生成失败')
|
||||
return
|
||||
}
|
||||
const { saveImageFromUrl } = await import('../composables/useSaveImage')
|
||||
await saveImageFromUrl(oilCardImageUrl.value, name + '_精油知识卡')
|
||||
ui.showToast('已保存图片')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user