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">
|
<div style="margin-top:16px;padding:12px;background:#e8f5e9;border-radius:10px;font-size:12px;color:#2e7d32;text-align:center">
|
||||||
💡 稀释比例 = 1滴精油 : N滴椰子油<br>比例越大越温和,新手建议从高稀释比例开始
|
💡 稀释比例 = 1滴精油 : N滴椰子油<br>比例越大越温和,新手建议从高稀释比例开始
|
||||||
</div>
|
</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>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Safety Cautions Modal -->
|
<!-- Safety Cautions Modal -->
|
||||||
@@ -75,9 +77,11 @@
|
|||||||
<span style="font-size:20px;flex-shrink:0">💧</span>
|
<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="font-weight:600;color:var(--text-dark)">少量多次,多喝水</div><div style="font-size:12px;color:var(--text-light);margin-top:2px">使用精油后多补充水分,帮助身体代谢</div></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>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Search + View Toggle + Add + PDF -->
|
<!-- Search + View Toggle + Add + PDF -->
|
||||||
@@ -203,9 +207,11 @@
|
|||||||
<h4 class="oil-card-caution-title">⚠️ 注意事项</h4>
|
<h4 class="oil-card-caution-title">⚠️ 注意事项</h4>
|
||||||
<p>{{ activeCard.caution }}</p>
|
<p>{{ activeCard.caution }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="text-align:center;padding-top:12px">
|
||||||
|
<button class="btn btn-outline btn-sm" @click="saveCardImage(activeCardName)">💾 保存图片</button>
|
||||||
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Simple Oil Detail Panel (for oils without a knowledge card) -->
|
<!-- Simple Oil Detail Panel (for oils without a knowledge card) -->
|
||||||
@@ -546,12 +552,18 @@ function parseMethodBadges(methodStr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
function openOilDetail(name) {
|
async function openOilDetail(name) {
|
||||||
const card = getOilCard(name)
|
const card = getOilCard(name)
|
||||||
if (card) {
|
if (card) {
|
||||||
activeCardName.value = name
|
activeCardName.value = name
|
||||||
activeCard.value = card
|
activeCard.value = card
|
||||||
selectedOilName.value = null
|
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 {
|
} else {
|
||||||
activeCard.value = null
|
activeCard.value = null
|
||||||
activeCardName.value = null
|
activeCardName.value = null
|
||||||
@@ -748,33 +760,59 @@ async function generateImageFromRef(elRef, imageUrlRef) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveGeneratedImage(imageUrlRef, elRef, name) {
|
// When modal opens, pre-generate the image (so save button has instant dataUrl)
|
||||||
// Generate if not already done
|
watch(showDilution, async (v) => {
|
||||||
if (!imageUrlRef.value) {
|
if (v) {
|
||||||
await generateImageFromRef(elRef, imageUrlRef)
|
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
|
return
|
||||||
}
|
}
|
||||||
// Same as RecipeDetailOverlay.saveImage
|
|
||||||
const { saveImageFromUrl } = await import('../composables/useSaveImage')
|
const { saveImageFromUrl } = await import('../composables/useSaveImage')
|
||||||
await saveImageFromUrl(imageUrlRef.value, name)
|
await saveImageFromUrl(dilutionImageUrl.value, '精油稀释比例指南')
|
||||||
ui.showToast('已保存图片')
|
ui.showToast('已保存图片')
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveDilutionImage() {
|
async function saveContraImage() {
|
||||||
saveGeneratedImage(dilutionImageUrl, dilutionCardRef, '精油稀释比例指南')
|
if (!contraImageUrl.value) {
|
||||||
|
ui.showToast('图片生成中,请稍后再试')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { saveImageFromUrl } = await import('../composables/useSaveImage')
|
||||||
|
await saveImageFromUrl(contraImageUrl.value, '精油使用禁忌')
|
||||||
|
ui.showToast('已保存图片')
|
||||||
}
|
}
|
||||||
function saveContraImage() {
|
|
||||||
saveGeneratedImage(contraImageUrl, contraCardRef, '精油使用禁忌')
|
async function saveCardImage(name) {
|
||||||
}
|
// Oil card: generate on demand since we don't know which card opens
|
||||||
function saveCardImage(name) {
|
|
||||||
const el = document.querySelector('.oil-card-modal')
|
const el = document.querySelector('.oil-card-modal')
|
||||||
if (!el) { ui.showToast('找不到卡片'); return }
|
if (!el) { ui.showToast('找不到卡片'); return }
|
||||||
// Use a temporary ref-like object
|
if (!oilCardImageUrl.value) {
|
||||||
const tmpRef = { value: el }
|
await generateImageFromRef({ value: el }, oilCardImageUrl)
|
||||||
saveGeneratedImage(oilCardImageUrl, tmpRef, name + '_精油知识卡')
|
}
|
||||||
|
if (!oilCardImageUrl.value) {
|
||||||
|
ui.showToast('图片生成失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { saveImageFromUrl } = await import('../composables/useSaveImage')
|
||||||
|
await saveImageFromUrl(oilCardImageUrl.value, name + '_精油知识卡')
|
||||||
|
ui.showToast('已保存图片')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user