feat: 配方卡片加入上传个人二维码功能
- RecipeDetailOverlay: 未上传二维码/背景图时,卡片上方显示提示横幅,下方出现「上传我的二维码」按钮,点击跳转到 MyDiary 品牌设置页并记录来源配方 - MyDiary: 新增二维码图片上传区域(直接上传图片文件,存为 base64 → PUT /api/brand qr_code 字段);上传成功后若有待返回配方则自动跳回配方卡片;修复 loadBrandSettings 字段名与后端不一致的问题 - RecipeSearch: 支持 ?openRecipe= 查询参数,页面挂载时自动打开指定配方卡片,实现从 MyDiary 上传后无缝返回 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,12 @@
|
||||
>English</button>
|
||||
</div>
|
||||
|
||||
<!-- QR / brand upload hint -->
|
||||
<div v-if="showBrandHint" class="brand-upload-hint">
|
||||
<span class="hint-icon">✨</span>
|
||||
<span>上传你的专属二维码,生成属于自己的配方卡片</span>
|
||||
</div>
|
||||
|
||||
<!-- Card image (rendered by html2canvas) -->
|
||||
<div v-show="!cardImageUrl" ref="cardRef" class="export-card">
|
||||
<!-- Brand overlay layers -->
|
||||
@@ -126,6 +132,11 @@
|
||||
class="action-btn"
|
||||
@click="showTranslationEditor = true"
|
||||
>✏️ 修改翻译</button>
|
||||
<button
|
||||
v-if="showBrandHint"
|
||||
class="action-btn action-btn-qr"
|
||||
@click="goUploadQr"
|
||||
>📲 上传我的二维码</button>
|
||||
</div>
|
||||
|
||||
<!-- Translation editor (inline) -->
|
||||
@@ -342,6 +353,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { useOilsStore, DROPS_PER_ML, VOLUME_DROPS } from '../stores/oils'
|
||||
import { useRecipesStore } from '../stores/recipes'
|
||||
@@ -363,6 +375,7 @@ const recipesStore = useRecipesStore()
|
||||
const authStore = useAuthStore()
|
||||
const ui = useUiStore()
|
||||
const diaryStore = useDiaryStore()
|
||||
const router = useRouter()
|
||||
|
||||
// ---- View state ----
|
||||
const viewMode = ref('card')
|
||||
@@ -453,6 +466,18 @@ async function loadBrand() {
|
||||
}
|
||||
}
|
||||
|
||||
// Whether to show the brand/QR upload hint
|
||||
const showBrandHint = computed(() =>
|
||||
authStore.isLoggedIn && !!brand.value && !brand.value.qr_code && !brand.value.brand_bg
|
||||
)
|
||||
|
||||
function goUploadQr() {
|
||||
if (recipe.value._id) {
|
||||
localStorage.setItem('oil_return_recipe_id', recipe.value._id)
|
||||
}
|
||||
router.push('/mydiary')
|
||||
}
|
||||
|
||||
// ---- Card image generation ----
|
||||
async function generateCardImage() {
|
||||
if (!cardRef.value || generatingImage.value) return
|
||||
@@ -801,6 +826,7 @@ async function saveRecipe() {
|
||||
ingredients: ingredients.map(i => ({ oil_name: i.oil, drops: i.drops })),
|
||||
}
|
||||
await recipesStore.saveRecipe(payload)
|
||||
// Reload recipes so the data is fresh when re-opened
|
||||
await recipesStore.loadRecipes()
|
||||
ui.showToast('保存成功')
|
||||
emit('close')
|
||||
@@ -1146,6 +1172,42 @@ async function saveRecipe() {
|
||||
background: var(--sage-mist, #eef4ee);
|
||||
}
|
||||
|
||||
/* Brand upload hint banner */
|
||||
.brand-upload-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: linear-gradient(135deg, #eef4ee, #f5f0e8);
|
||||
border: 1.5px dashed var(--sage-light, #c8ddc9);
|
||||
border-radius: 10px;
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 14px;
|
||||
font-size: 13px;
|
||||
color: var(--sage-dark, #5a7d5e);
|
||||
font-weight: 500;
|
||||
animation: hint-pop 0.3s ease;
|
||||
}
|
||||
|
||||
.hint-icon {
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@keyframes hint-pop {
|
||||
from { opacity: 0; transform: translateY(-6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.action-btn-qr {
|
||||
background: linear-gradient(135deg, #c8ddc9, var(--sage, #7a9e7e));
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.action-btn-qr:hover {
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
/* Card bottom actions */
|
||||
.card-bottom-actions {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user