feat: Header重排、共享配方到公共库、待审核配方、权限优化 #17

Merged
fam merged 39 commits from fix/ui-polish-round2 into main 2026-04-09 18:37:12 +00:00
Showing only changes of commit 5b5b73bba8 - Show all commits

View File

@@ -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>