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 2469f15656 - Show all commits

View File

@@ -342,7 +342,8 @@
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { ref, computed, watch, nextTick } from 'vue'
import html2canvas from 'html2canvas'
import { useOilsStore, VOLUME_DROPS, DROPS_PER_ML } from '../stores/oils'
import { useAuthStore } from '../stores/auth'
import { useUiStore } from '../stores/ui'
@@ -727,45 +728,63 @@ function exportPDF() {
setTimeout(() => w.print(), 500)
}
// 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')
// ──── Save image logic (identical to RecipeDetailOverlay) ────
// Pre-generated image URLs (same pattern as cardImageUrl in recipe card)
const dilutionImageUrl = ref(null)
const contraImageUrl = ref(null)
const oilCardImageUrl = ref(null)
async function generateImageFromRef(elRef, imageUrlRef) {
const el = elRef.value || elRef
if (!el) return
// Hide buttons during capture
const btns = el.querySelectorAll('button')
btns.forEach(b => { b._d = b.style.display; b.style.display = 'none' })
await nextTick()
await new Promise(r => setTimeout(r, 100))
try {
// 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')
await saveImageFromUrl(dataUrl, name)
ui.showToast('图片已保存')
imageUrlRef.value = canvas.toDataURL('image/png')
} catch (e) {
console.error('generateImage failed:', e)
} finally {
btns.forEach(b => b.style.display = b._d || '')
console.error('saveElementAsImage failed:', e)
ui.showToast('截图失败: ' + (e.message || '未知错误'))
}
}
async function saveGeneratedImage(imageUrlRef, elRef, name) {
// Generate if not already done
if (!imageUrlRef.value) {
await generateImageFromRef(elRef, imageUrlRef)
}
if (!imageUrlRef.value) {
ui.showToast('图片生成失败')
return
}
// Same as RecipeDetailOverlay.saveImage
const { saveImageFromUrl } = await import('../composables/useSaveImage')
await saveImageFromUrl(imageUrlRef.value, name)
ui.showToast('已保存图片')
}
function saveDilutionImage() {
saveElementAsImage(dilutionCardRef.value, '精油稀释比例指南')
saveGeneratedImage(dilutionImageUrl, dilutionCardRef, '精油稀释比例指南')
}
function saveContraImage() {
saveElementAsImage(contraCardRef.value, '精油使用禁忌')
saveGeneratedImage(contraImageUrl, contraCardRef, '精油使用禁忌')
}
function saveCardImage(name) {
const el = document.querySelector('.oil-card-modal')
saveElementAsImage(el, name + '_精油知识卡')
if (!el) { ui.showToast('找不到卡片'); return }
// Use a temporary ref-like object
const tmpRef = { value: el }
saveGeneratedImage(oilCardImageUrl, tmpRef, name + '_精油知识卡')
}
</script>