rewrite: 配方卡片和预览图完全重写,匹配initial commit样式
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 4s
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 1m43s

完全删除之前的CSS class方案,改用inline style匹配原版HTML:

配方卡片 (RecipeDetailOverlay):
- 背景图: absolute全覆盖, opacity:0.12
- Logo: absolute底部居中水印, bottom:60px, opacity:0.2
- QR: absolute右上角 top:36px right:24px, 品牌名在下方
- 内容区: position:relative z-index:2 (在overlay之上)
- 制作日期: text-align:center (居中,匹配原版)
- 所有样式用inline style,跟原版_buildBrandHtml()一致

预览图 (MyDiary):
- 等比缩小版配方卡片,同样布局
- QR右上,Logo底部居中,日期居中

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 12:53:13 +00:00
parent e459a52b5b
commit d1723797e0
2 changed files with 52 additions and 152 deletions

View File

@@ -49,76 +49,45 @@
<!-- Card image (rendered by html2canvas) -->
<div v-show="!cardImageUrl" ref="cardRef" class="export-card">
<!-- Brand overlay layers -->
<div
v-if="brand.brand_bg"
class="card-brand-bg"
:style="{ backgroundImage: `url('${brand.brand_bg}')` }"
/>
<!-- QR: absolute positioned in export-card, top-right corner of content area -->
<div v-if="brand.qr_code" class="card-qr-wrapper">
<img :src="brand.qr_code" class="card-qr" crossorigin="anonymous" />
<div v-if="brand.brand_name" class="card-qr-name">{{ brand.brand_name }}</div>
<!-- Background image overlay -->
<div v-if="brand.brand_bg" style="position:absolute;inset:0;width:100%;height:100%;background-size:cover;background-position:center;opacity:0.12;pointer-events:none;z-index:0" :style="{ backgroundImage: `url('${brand.brand_bg}')` }"></div>
<!-- Logo watermark: bottom center -->
<img v-if="brand.brand_logo" :src="brand.brand_logo" crossorigin="anonymous" style="position:absolute;bottom:60px;left:50%;transform:translateX(-50%);height:60px;opacity:0.2;pointer-events:none;z-index:1" />
<!-- QR: top-right -->
<div v-if="brand.qr_code" style="position:absolute;top:36px;right:24px;display:flex;flex-direction:column;align-items:center;gap:3px;z-index:2">
<img :src="brand.qr_code" crossorigin="anonymous" style="width:54px;height:54px;object-fit:cover;border-radius:6px;box-shadow:0 2px 6px rgba(0,0,0,0.1)" />
<div v-if="brand.brand_name" :style="{ textAlign: brand.brand_align || 'center' }" style="font-size:7px;color:var(--text-light);line-height:1.3;max-width:68px;white-space:pre-line">{{ brand.brand_name }}</div>
</div>
<div class="card-content">
<div class="card-brand-text">
<!-- Card content (z-index above overlays) -->
<div style="position:relative;z-index:2">
<div style="font-size:11px;letter-spacing:3px;color:var(--sage);margin-bottom:8px">
{{ cardLang === 'en' ? 'doTERRA · Gifts of the Earth' : 'doTERRA · 来自大地的礼物' }}
</div>
<div class="card-title">
<div style="font-size:26px;font-weight:700;color:var(--text-dark);margin-bottom:6px;line-height:1.3">
{{ getCardRecipeName() }}
</div>
<div class="card-divider"></div>
<div style="width:80px;height:2px;background:linear-gradient(90deg,var(--sage),var(--gold));border-radius:2px;margin:14px 0"></div>
<!-- Ingredients (excluding coconut oil) -->
<ul class="card-ingredients">
<li v-for="(ing, i) in cardIngredients" :key="i">
<span class="card-oil-name">
{{ getCardOilName(ing.oil) }}
</span>
<span class="card-oil-drops">
{{ ing.drops }} {{ cardLang === 'en' ? 'drops' : '滴' }}
</span>
<span class="card-oil-cost">
{{ oilsStore.fmtPrice(oilsStore.pricePerDrop(ing.oil) * ing.drops) }}
</span>
<span
v-if="hasRetailForOil(ing.oil) && retailPerDrop(ing.oil) > oilsStore.pricePerDrop(ing.oil)"
class="card-retail-strike"
>{{ oilsStore.fmtPrice(retailPerDrop(ing.oil) * ing.drops) }}</span>
<ul style="list-style:none;margin-bottom:20px;padding:0">
<li v-for="(ing, i) in cardIngredients" :key="i" style="display:flex;align-items:center;padding:9px 0;border-bottom:1px solid rgba(180,150,100,0.15);font-size:14px">
<span style="flex:1;color:var(--text-dark);font-weight:500">{{ getCardOilName(ing.oil) }}</span>
<span style="width:50px;text-align:right;color:var(--sage-dark);font-size:13px">{{ ing.drops }} {{ cardLang === 'en' ? 'drops' : '滴' }}</span>
<span style="width:60px;text-align:right;color:var(--text-light);font-size:12px">{{ oilsStore.fmtPrice(oilsStore.pricePerDrop(ing.oil) * ing.drops) }}</span>
<span v-if="hasRetailForOil(ing.oil) && retailPerDrop(ing.oil) > oilsStore.pricePerDrop(ing.oil)" style="width:55px;text-align:right;color:var(--text-light);font-size:10px;text-decoration:line-through">{{ oilsStore.fmtPrice(retailPerDrop(ing.oil) * ing.drops) }}</span>
</li>
</ul>
<!-- Dilution description -->
<div v-if="dilutionDesc" class="card-dilution">{{ dilutionDesc }}</div>
<div v-if="dilutionDesc" style="padding:10px 14px;background:rgba(180,150,100,0.08);border-radius:10px;font-size:12px;color:var(--text-mid);margin-bottom:12px">{{ dilutionDesc }}</div>
<!-- Note -->
<div v-if="displayRecipe.note" class="card-note">
{{ '📝 ' + displayRecipe.note }}
<div v-if="displayRecipe.note" style="font-size:12px;color:var(--brown-light);margin-bottom:12px;font-style:italic">📝 {{ displayRecipe.note }}</div>
<div style="background:linear-gradient(135deg,var(--sage),#5a7d5e);border-radius:12px;padding:14px 20px;display:flex;justify-content:space-between;align-items:center">
<span style="color:rgba(255,255,255,0.85);font-size:13px;letter-spacing:1px">{{ cardLang === 'en' ? 'Total Cost' : '配方总成本' }}</span>
<span style="color:white;font-size:20px;font-weight:700">{{ priceInfo.cost }}<span v-if="priceInfo.hasRetail" style="text-decoration:line-through;opacity:0.6;font-size:13px;margin-left:6px">{{ priceInfo.retail }}</span></span>
</div>
<!-- Total cost bar -->
<div class="card-total">
<div class="card-total-label">
{{ cardLang === 'en' ? 'Total Cost' : '配方总成本' }}
</div>
<div class="card-total-price">
{{ priceInfo.cost }}
<span v-if="priceInfo.hasRetail" class="card-total-retail">{{ priceInfo.retail }}</span>
</div>
</div>
<!-- Logo + Date row -->
<div class="card-bottom-row">
<img
v-if="brand.brand_logo"
:src="brand.brand_logo"
class="card-logo"
crossorigin="anonymous"
/>
<div v-else class="card-logo-placeholder"></div>
<div class="card-footer">
{{ cardLang === 'en' ? 'Date: ' : '制作日期:' }}{{ todayStr }}
</div>
<div style="margin-top:16px;text-align:center;font-size:11px;color:var(--text-light);letter-spacing:1px">
{{ cardLang === 'en' ? 'Date: ' : '制作日期:' }}{{ todayStr }}
</div>
</div>
</div>

View File

@@ -169,28 +169,30 @@
<div style="margin-bottom:16px">
<label class="form-label">📋 配方卡片预览</label>
<div class="card-preview-mini">
<div v-if="brandBg" class="card-preview-bg" :style="{ backgroundImage: 'url(' + brandBg + ')' }"></div>
<!-- QR + brand name: top-right -->
<div v-if="brandQrImage" class="card-preview-qr-area">
<img :src="brandQrImage" class="card-preview-qr" />
<div v-if="brandName" class="card-preview-qr-text" :style="{ textAlign: brandAlign }">{{ brandName }}</div>
<!-- Background overlay -->
<div v-if="brandBg" style="position:absolute;inset:0;background-size:cover;background-position:center;opacity:0.12;pointer-events:none" :style="{ backgroundImage: 'url(' + brandBg + ')' }"></div>
<!-- Logo watermark: bottom center -->
<img v-if="brandLogo" :src="brandLogo" style="position:absolute;bottom:34px;left:50%;transform:translateX(-50%);height:30px;opacity:0.2;pointer-events:none;z-index:1" />
<!-- QR: top-right -->
<div v-if="brandQrImage" style="position:absolute;top:16px;right:12px;display:flex;flex-direction:column;align-items:center;gap:2px;z-index:2">
<img :src="brandQrImage" style="width:36px;height:36px;object-fit:cover;border-radius:4px;box-shadow:0 1px 4px rgba(0,0,0,0.1)" />
<div v-if="brandName" :style="{ textAlign: brandAlign }" style="font-size:5px;color:var(--text-light);line-height:1.2;max-width:42px;white-space:pre-line">{{ brandName }}</div>
</div>
<!-- Content: left side -->
<div class="card-preview-content">
<div style="font-size:8px;letter-spacing:2px;color:var(--sage);margin-bottom:4px">doTERRA · 来自大地的礼物</div>
<div style="font-size:14px;font-weight:700;color:var(--text-dark)">配方名称</div>
<div style="font-size:10px;color:var(--text-light);margin-top:2px">薰衣草 · 乳香 · 茶树</div>
</div>
<!-- Total cost bar -->
<div class="card-preview-cost">
<span>配方总成本</span>
<span>¥12.50</span>
</div>
<!-- Logo left + date right -->
<div class="card-preview-bottom">
<img v-if="brandLogo" :src="brandLogo" class="card-preview-logo" />
<span v-else></span>
<span class="card-preview-date">制作日期{{ new Date().toLocaleDateString('zh-CN') }}</span>
<!-- Content -->
<div style="position:relative;z-index:1">
<div style="font-size:7px;letter-spacing:1.5px;color:var(--sage);margin-bottom:3px">doTERRA · 来自大地的礼物</div>
<div style="font-size:13px;font-weight:700;color:var(--text-dark);margin-bottom:3px;line-height:1.3">配方名称</div>
<div style="width:30px;height:1px;background:linear-gradient(90deg,var(--sage),var(--gold));margin:6px 0"></div>
<div style="font-size:9px;color:var(--text-light);margin-bottom:6px">薰衣草 · 乳香 · 茶树</div>
<!-- Total cost bar -->
<div style="background:linear-gradient(135deg,var(--sage),#5a7d5e);border-radius:6px;padding:6px 10px;display:flex;justify-content:space-between;align-items:center">
<span style="color:rgba(255,255,255,0.85);font-size:8px;letter-spacing:0.5px">配方总成本</span>
<span style="color:white;font-size:12px;font-weight:700">¥12.50</span>
</div>
<!-- Date: centered -->
<div style="margin-top:8px;text-align:center;font-size:7px;color:var(--text-light);letter-spacing:0.5px">
制作日期{{ new Date().toLocaleDateString('zh-CN') }}
</div>
</div>
</div>
</div>
@@ -1047,7 +1049,7 @@ async function applyBusiness() {
color: var(--sage-dark);
}
/* Card preview mini — matches actual card layout */
/* Card preview mini */
.card-preview-mini {
position: relative;
width: 280px;
@@ -1056,79 +1058,8 @@ async function applyBusiness() {
border: 1px solid #e0ccaa;
overflow: hidden;
font-family: 'Noto Serif SC', serif;
padding: 16px;
padding: 18px;
}
.card-preview-bg {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
opacity: 0.15;
}
.card-preview-content {
position: relative;
z-index: 1;
padding-right: 60px; /* leave room for QR area */
}
.card-preview-qr-area {
position: absolute;
top: 16px;
right: 16px;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
z-index: 2;
max-width: 50px;
}
.card-preview-qr {
width: 40px;
height: 40px;
object-fit: contain;
border-radius: 4px;
}
.card-preview-qr-text {
font-size: 6px;
color: var(--text-light);
line-height: 1.2;
white-space: pre-line;
max-width: 50px;
text-align: center;
}
.card-preview-cost {
position: relative;
z-index: 1;
margin-top: 12px;
height: 22px;
background: linear-gradient(135deg, var(--sage), #5a7d5e);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
color: white;
font-size: 9px;
}
.card-preview-bottom {
position: relative;
z-index: 1;
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: 8px;
}
.card-preview-logo {
height: 16px;
width: auto;
object-fit: contain;
opacity: 0.6;
}
.card-preview-date {
font-size: 8px;
color: var(--text-light);
letter-spacing: 0.5px;
}
/* brand name now inside .card-preview-qr-text */
.hint-text {
font-size: 13px;