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

Merged
fam merged 39 commits from fix/ui-polish-round2 into main 2026-04-09 18:37:12 +00:00
2 changed files with 120 additions and 25 deletions
Showing only changes of commit 9f25da6232 - Show all commits

View File

@@ -51,29 +51,25 @@
<div v-show="!cardImageUrl" ref="cardRef" class="export-card">
<!-- 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">
<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:3">
<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>
<!-- Card content (z-index above overlays) -->
<!-- Card content -->
<div style="position:relative;z-index:2">
<div style="font-size:11px;letter-spacing:3px;color:var(--sage);margin-bottom:8px">
<div class="ec-subtitle">
{{ cardLang === 'en' ? 'doTERRA · Gifts of the Earth' : 'doTERRA · 来自大地的礼物' }}
</div>
<div style="font-size:26px;font-weight:700;color:var(--text-dark);margin-bottom:6px;line-height:1.3">
{{ getCardRecipeName() }}
</div>
<div class="ec-title">{{ getCardRecipeName() }}</div>
<div style="width:80px;height:2px;background:linear-gradient(90deg,var(--sage),var(--gold));border-radius:2px;margin:14px 0"></div>
<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 v-for="(ing, i) in cardIngredients" :key="i" class="ec-ing">
<span class="ec-oil-name">{{ getCardOilName(ing.oil) }}</span>
<span class="ec-drops">{{ ing.drops }} {{ cardLang === 'en' ? 'drops' : '滴' }}</span>
<span class="ec-cost">{{ oilsStore.fmtPrice(oilsStore.pricePerDrop(ing.oil) * ing.drops) }}</span>
<span v-if="hasRetailForOil(ing.oil) && retailPerDrop(ing.oil) > oilsStore.pricePerDrop(ing.oil)" class="ec-retail">{{ oilsStore.fmtPrice(retailPerDrop(ing.oil) * ing.drops) }}</span>
</li>
</ul>
@@ -81,13 +77,16 @@
<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">
<div class="ec-total-bar">
<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>
<div style="margin-top:16px;text-align:center;font-size:11px;color:var(--text-light);letter-spacing:1px">
{{ cardLang === 'en' ? 'Date: ' : '制作日期:' }}{{ todayStr }}
<!-- Logo left + Date right -->
<div class="ec-bottom">
<img v-if="brand.brand_logo" :src="brand.brand_logo" crossorigin="anonymous" class="ec-logo" />
<span v-else></span>
<span class="ec-date">{{ cardLang === 'en' ? 'Date: ' : '制作日期:' }}{{ todayStr }}</span>
</div>
</div>
</div>
@@ -1088,10 +1087,105 @@ async function saveRecipe() {
border-radius: 50%;
}
.card-content {
position: relative;
z-index: 2;
padding-right: 80px; /* leave room for QR (54px) + gap */
/* ===== Export Card Content (responsive) ===== */
.ec-subtitle {
font-size: 11px;
letter-spacing: 3px;
color: var(--sage);
margin-bottom: 8px;
white-space: nowrap;
}
.ec-title {
font-size: 26px;
font-weight: 700;
color: var(--text-dark);
margin-bottom: 6px;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc(100% - 80px); /* leave room for QR */
}
.ec-ing {
display: flex;
align-items: center;
padding: 9px 0;
border-bottom: 1px solid rgba(180,150,100,0.15);
font-size: 14px;
}
.ec-oil-name {
flex: 1;
color: var(--text-dark);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
.ec-drops {
width: 50px;
text-align: right;
color: var(--sage-dark);
font-size: 13px;
white-space: nowrap;
flex-shrink: 0;
}
.ec-cost {
width: 60px;
text-align: right;
color: var(--text-light);
font-size: 12px;
white-space: nowrap;
flex-shrink: 0;
}
.ec-retail {
width: 55px;
text-align: right;
color: var(--text-light);
font-size: 10px;
text-decoration: line-through;
white-space: nowrap;
flex-shrink: 0;
}
.ec-total-bar {
background: linear-gradient(135deg, var(--sage), #5a7d5e);
border-radius: 12px;
padding: 14px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.ec-bottom {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: 16px;
}
.ec-logo {
height: 36px;
object-fit: contain;
opacity: 0.5;
}
.ec-date {
font-size: 11px;
color: var(--text-light);
letter-spacing: 1px;
}
/* Mobile: smaller card text */
@media (max-width: 420px) {
.export-card { padding: 24px; }
.ec-subtitle { font-size: 9px; letter-spacing: 2px; }
.ec-title { font-size: 20px; max-width: calc(100% - 65px); }
.ec-ing { font-size: 12px; padding: 7px 0; }
.ec-drops { width: 42px; font-size: 11px; }
.ec-cost { width: 50px; font-size: 10px; }
.ec-retail { width: 45px; font-size: 9px; }
.ec-total-bar { padding: 10px 14px; }
.ec-total-bar span:first-child { font-size: 11px; }
.ec-total-bar span:last-child { font-size: 16px; }
.ec-date { font-size: 9px; }
.ec-logo { height: 28px; }
}
/* Brand overlays */

View File

@@ -171,8 +171,7 @@
<div class="card-preview-mini">
<!-- 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" />
<!-- Logo: shown in bottom row, not as watermark -->
<!-- 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)" />
@@ -189,9 +188,11 @@
<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') }}
<!-- Logo left + Date right -->
<div style="display:flex;justify-content:space-between;align-items:flex-end;margin-top:8px">
<img v-if="brandLogo" :src="brandLogo" style="height:18px;object-fit:contain;opacity:0.5" />
<span v-else></span>
<span style="font-size:7px;color:var(--text-light);letter-spacing:0.5px">制作日期{{ new Date().toLocaleDateString('zh-CN') }}</span>
</div>
</div>
</div>