chore(web): 删除 scan.html,清理 /scan/ nginx 路由和文档
scan.html 从未被实际触发——二维码 URL 格式为 /product/<uuid>, 由 Flutter Web PublicProductScreen 处理,scan.html 是孤立死页。 - 删除 web/scan.html - 移除 nginx-jiu.conf 中 /scan/ location 块 - 更新文档路由表(/scan/<id> → /product/<uuid>) - 标记 todo #4 完成 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,12 +53,6 @@ server {
|
||||
try_files $uri /app/index.html;
|
||||
}
|
||||
|
||||
# 扫码页(/scan/<id> → scan.html,JS 从 URL 读取 id)
|
||||
location /scan/ {
|
||||
root /opt/jiu/marketing;
|
||||
try_files /scan.html =404;
|
||||
}
|
||||
|
||||
# 桌面客户端安装包下载(Windows .exe / macOS .zip)
|
||||
location /downloads/ {
|
||||
alias /opt/jiu/downloads/;
|
||||
|
||||
@@ -155,8 +155,7 @@ git push nas main && git push nas v1.1.0
|
||||
│ ├── assets/
|
||||
│ ├── index.html
|
||||
│ ├── docs.html
|
||||
│ ├── download.html
|
||||
│ └── scan.html
|
||||
│ └── download.html
|
||||
├── images/ # 商品图片上传目录
|
||||
└── web-old/ # 上一版本 Flutter Web(回滚备用)
|
||||
```
|
||||
@@ -175,5 +174,5 @@ Systemd 服务名:`jiu`
|
||||
| `/api/*`, `/health`, `/version` | Go 后端 API |
|
||||
| `/images/*` | 商品图片静态文件 |
|
||||
| `/app/` | Flutter 管理端 SPA |
|
||||
| `/product/<uuid>` | 商品扫码公开页(Flutter Web 路由) |
|
||||
| `/` | 营销首页 |
|
||||
| `/scan/<id>` | 商品扫码页 |
|
||||
|
||||
+23
-23
@@ -102,8 +102,8 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
|
||||
<div class="header-meta">生成于 2026-06-08 · 真相源 todo/todo.json</div>
|
||||
<div class="stats">
|
||||
<div class="stat-pill"><strong>6</strong>全部</div>
|
||||
<div class="stat-pill"><strong>5</strong>未完成</div>
|
||||
<div class="stat-pill"><strong>1</strong>已完成</div>
|
||||
<div class="stat-pill"><strong>4</strong>未完成</div>
|
||||
<div class="stat-pill"><strong>2</strong>已完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -132,7 +132,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<div class="section-title">📋 未完成(5)</div>
|
||||
<div class="section-title">📋 未完成(4)</div>
|
||||
<ul class="todo-list" id="pendingList">
|
||||
|
||||
<li class="todo-card"
|
||||
@@ -173,25 +173,6 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="todo-card"
|
||||
data-id="4"
|
||||
data-level="low"
|
||||
data-tags="前端,Web"
|
||||
data-done="0">
|
||||
<div class="card-header">
|
||||
<span class="item-title">scan.html / features 走 passthrough 不享模板</span>
|
||||
<span class="tag t-low">一般 / 优化</span>
|
||||
</div>
|
||||
<div class="item-desc">features/scan.html 直接复制,不经 njk 模板,页脚/备案/CSS 都得手动同步。建议长期改造为模板化。</div>
|
||||
<div class="card-footer">
|
||||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span></div>
|
||||
<div class="item-meta">
|
||||
<span class="meta-date">🕐 2026-06-08</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="todo-card"
|
||||
data-id="5"
|
||||
data-level="low"
|
||||
@@ -232,10 +213,29 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
|
||||
</ul>
|
||||
|
||||
<div class="done-section">
|
||||
<div class="section-title done-title">✅ 已完成(1)</div>
|
||||
<div class="section-title done-title">✅ 已完成(2)</div>
|
||||
<button class="done-toggle" id="doneToggle">▾ 展开已完成列表</button>
|
||||
<ul class="todo-list hidden" id="doneList">
|
||||
|
||||
<li class="todo-card done"
|
||||
data-id="4"
|
||||
data-level="low"
|
||||
data-tags="前端,Web"
|
||||
data-done="1">
|
||||
<div class="card-header">
|
||||
<span class="item-title">scan.html / features 走 passthrough 不享模板</span>
|
||||
<span class="tag t-low">一般 / 优化</span>
|
||||
</div>
|
||||
<div class="item-desc">features/scan.html 直接复制,不经 njk 模板,页脚/备案/CSS 都得手动同步。建议长期改造为模板化。</div>
|
||||
<div class="card-footer">
|
||||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span></div>
|
||||
<div class="item-meta">
|
||||
<span class="meta-date">🕐 2026-06-08</span>
|
||||
<span class="meta-date">✅ 完成 2026-06-08 · <span class="ver-badge">v1.0.23</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="todo-card done"
|
||||
data-id="1"
|
||||
data-level="mid"
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "酒库管理系统 — 项目 TODO",
|
||||
"updated_at": "2026-06-07T17:48:38.554Z"
|
||||
"updated_at": "2026-06-07T18:07:13.211Z"
|
||||
},
|
||||
"seq": 7,
|
||||
"items": [
|
||||
@@ -56,9 +56,9 @@
|
||||
"Web"
|
||||
],
|
||||
"created_at": "2026-06-07T17:30:32.287Z",
|
||||
"done": false,
|
||||
"completed_at": null,
|
||||
"version": null
|
||||
"done": true,
|
||||
"completed_at": "2026-06-07T18:07:13.211Z",
|
||||
"version": "v1.0.23"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
|
||||
-379
@@ -1,379 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>商品验真 — 岩美</title>
|
||||
<meta name="description" content="岩美防伪验证 — 扫码查看商品详情与防伪信息" />
|
||||
<link rel="stylesheet" href="/assets/color.css" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; font-family: var(--font-sans); color: #1A1612; background: #F7F3EE; -webkit-font-smoothing: antialiased; min-height: 100vh; }
|
||||
img { display: block; max-width: 100%; }
|
||||
|
||||
/* STATE SCREENS */
|
||||
.state-screen { display: flex; align-items: center; justify-content: center; min-height: 100vh; flex-direction: column; gap: 16px; padding: 32px; text-align: center; }
|
||||
.spinner { width: 36px; height: 36px; border: 3px solid #E8E2D9; border-top-color: var(--brand-500); border-radius: 50%; animation: spin 0.8s linear infinite; }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.state-text { font-size: 14px; color: #7A7068; }
|
||||
.error-icon { width: 48px; height: 48px; background: var(--danger-50); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 24px; }
|
||||
.error-title { font-size: 16px; font-weight: 600; color: #1A1612; }
|
||||
.error-sub { font-size: 13px; color: #7A7068; max-width: 260px; }
|
||||
.retry-btn { margin-top: 4px; padding: 10px 24px; background: var(--brand-500); color: #fff; border: none; border-radius: 6px; font-size: 14px; font-family: inherit; cursor: pointer; }
|
||||
.retry-btn:active { background: var(--brand-700); }
|
||||
|
||||
/* MAIN CONTENT */
|
||||
#content { display: none; }
|
||||
|
||||
/* IMAGE GALLERY */
|
||||
.gallery { position: relative; background: #EDE8E2; }
|
||||
.gallery-main { position: relative; overflow: hidden; }
|
||||
.gallery-main img { width: 100%; aspect-ratio: 1; object-fit: cover; display: none; }
|
||||
.gallery-main img.active { display: block; }
|
||||
.gallery-placeholder { width: 100%; aspect-ratio: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }
|
||||
.placeholder-mark { width: 56px; height: 56px; background: #0F3057; border-radius: 10px; display: flex; align-items: center; justify-content: center; }
|
||||
.placeholder-mark::after { content: '岩'; font-size: 24px; font-weight: 700; color: rgba(255,255,255,0.9); }
|
||||
.placeholder-text { font-size: 13px; color: #9E9580; }
|
||||
.gallery-counter { position: absolute; right: 12px; bottom: 12px; background: rgba(0,0,0,0.5); color: #fff; font-size: 12px; padding: 3px 10px; border-radius: 12px; }
|
||||
.thumbnails { display: flex; gap: 8px; padding: 10px 14px; overflow-x: auto; background: #EDE8E2; }
|
||||
.thumbnails::-webkit-scrollbar { display: none; }
|
||||
.thumb { width: 52px; height: 52px; border-radius: 4px; object-fit: cover; border: 2px solid transparent; flex-shrink: 0; cursor: pointer; }
|
||||
.thumb.active { border-color: var(--brand-500); }
|
||||
|
||||
/* VERIFIED RIBBON */
|
||||
.ribbon { background: #EAF4ED; padding: 10px 16px; display: flex; align-items: center; gap: 10px; }
|
||||
.ribbon-check { width: 22px; height: 22px; background: #2E8B57; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.ribbon-check::after { content: '✓'; color: #fff; font-size: 13px; font-weight: 700; }
|
||||
.ribbon-text { flex: 1; }
|
||||
.ribbon-title { font-size: 13px; font-weight: 600; color: #1F6B41; }
|
||||
.ribbon-sub { font-size: 11px; color: #4A7A5C; margin-top: 1px; }
|
||||
.ribbon-icon { font-size: 18px; color: #2E8B57; }
|
||||
|
||||
/* TITLE */
|
||||
.title-section { background: #fff; padding: 20px 16px 18px; }
|
||||
.product-name { font-size: 22px; font-weight: 700; color: #1A1612; line-height: 1.3; margin: 0 0 6px; }
|
||||
.product-subtitle { font-size: 14px; color: #7A7068; margin: 0 0 14px; line-height: 1.4; }
|
||||
.tags { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.tag { display: inline-block; padding: 3px 9px; border-radius: 4px; font-size: 12px; font-weight: 500; }
|
||||
.tag-brand { background: #EEF4FB; color: #154072; }
|
||||
.tag-unit { background: #F0ECE6; color: #6B6259; }
|
||||
|
||||
/* QUICK SPECS */
|
||||
.quick-specs { background: #fff; display: grid; grid-template-columns: repeat(4, 1fr); border-bottom: 1px solid #F0ECE6; }
|
||||
.quick-spec-item { padding: 16px 8px; text-align: center; border-left: 1px solid #F5F1EB; }
|
||||
.quick-spec-item:first-child { border-left: none; }
|
||||
.quick-spec-val { font-size: 20px; font-weight: 700; color: #1A1612; line-height: 1; font-variant-numeric: tabular-nums; }
|
||||
.quick-spec-unit { font-size: 9px; color: #9E9580; font-weight: 500; margin-left: 1px; }
|
||||
.quick-spec-label { font-size: 11px; color: #9E9580; margin-top: 4px; letter-spacing: 0.04em; }
|
||||
|
||||
/* PARAMS */
|
||||
.params-card { background: #fff; margin-top: 10px; }
|
||||
.card-header { padding: 14px 16px 12px; border-bottom: 1px solid #F0ECE6; font-size: 14px; font-weight: 600; color: #1A1612; display: flex; align-items: center; gap: 8px; }
|
||||
.card-header::before { content: ''; display: block; width: 3px; height: 14px; background: #8B2331; border-radius: 2px; }
|
||||
.param-row { display: flex; align-items: flex-start; padding: 10px 16px; border-bottom: 1px solid #F5F1EB; }
|
||||
.param-row:last-child { border-bottom: none; }
|
||||
.param-label { font-size: 13px; color: #9E9580; min-width: 72px; flex-shrink: 0; padding-top: 1px; }
|
||||
.param-val { font-size: 13px; color: #1A1612; flex: 1; line-height: 1.5; }
|
||||
|
||||
/* AUTHENTICITY */
|
||||
.auth-card { background: #FAF8F5; margin-top: 10px; padding: 20px 16px; }
|
||||
.auth-inner { background: #fff; border: 1px solid #E8E4DC; border-radius: 8px; padding: 16px; display: flex; gap: 14px; align-items: center; }
|
||||
.auth-rows { flex: 1; display: grid; grid-template-columns: 64px 1fr; gap: 8px; font-size: 12px; line-height: 1.7; }
|
||||
.auth-lbl { color: #9E9580; }
|
||||
.auth-val { color: #1A1612; font-weight: 500; }
|
||||
.auth-mono { font-family: 'JetBrains Mono', monospace; color: var(--brand-700); font-weight: 600; letter-spacing: 0.04em; }
|
||||
.auth-stamp { width: 72px; height: 72px; border-radius: 50%; border: 2px solid #8B2331; background: rgba(139,35,49,0.04); display: grid; place-items: center; flex-shrink: 0; position: relative; transform: rotate(-8deg); overflow: hidden; }
|
||||
.auth-stamp-text { font-size: 8px; color: #8B2331; font-weight: 700; text-align: center; letter-spacing: 0.02em; line-height: 1.4; }
|
||||
.auth-note { font-size: 11px; color: #A89E92; margin-top: 12px; text-align: center; line-height: 1.6; }
|
||||
|
||||
/* SHOP CARD */
|
||||
.shop-card { background: #fff; margin-top: 10px; }
|
||||
.shop-name-row { display: flex; align-items: center; gap: 8px; padding: 14px 16px 12px; border-bottom: 1px solid #F0ECE6; }
|
||||
.shop-icon { font-size: 16px; }
|
||||
.shop-name-text { font-size: 14px; font-weight: 600; color: #1A1612; }
|
||||
|
||||
/* FOOTER */
|
||||
.footer { padding: 20px 16px 32px; display: flex; align-items: center; justify-content: center; gap: 8px; }
|
||||
.footer-mark { width: 18px; height: 18px; background: #0F3057; border-radius: 3px; display: flex; align-items: center; justify-content: center; }
|
||||
.footer-mark::after { content: '岩'; color: rgba(255,255,255,0.9); font-size: 10px; font-weight: 700; }
|
||||
.footer-text { font-size: 11px; color: #A89E92; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="state-loading" class="state-screen">
|
||||
<div class="spinner"></div>
|
||||
<div class="state-text">正在查询商品信息…</div>
|
||||
</div>
|
||||
|
||||
<div id="state-error" class="state-screen" style="display:none">
|
||||
<div class="error-icon">✗</div>
|
||||
<div class="error-title">商品不存在或已下架</div>
|
||||
<div class="error-sub">请检查二维码是否完整,或联系出售方确认。</div>
|
||||
<button class="retry-btn" onclick="load()">重试</button>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<div class="gallery" id="gallery">
|
||||
<div class="gallery-main" id="gallery-main">
|
||||
<div class="gallery-placeholder" id="gallery-placeholder">
|
||||
<div class="placeholder-mark"></div>
|
||||
<div class="placeholder-text">暂无图片</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="thumbnails" id="thumbnails" style="display:none"></div>
|
||||
</div>
|
||||
|
||||
<div class="ribbon">
|
||||
<div class="ribbon-check"></div>
|
||||
<div class="ribbon-text">
|
||||
<div class="ribbon-title">已通过岩美防伪验证</div>
|
||||
<div class="ribbon-sub" id="ribbon-shop"></div>
|
||||
</div>
|
||||
<div class="ribbon-icon">✓</div>
|
||||
</div>
|
||||
|
||||
<div class="title-section">
|
||||
<h1 class="product-name" id="product-name">—</h1>
|
||||
<p class="product-subtitle" id="product-subtitle"></p>
|
||||
<div class="tags" id="product-tags"></div>
|
||||
</div>
|
||||
|
||||
<div class="quick-specs" id="quick-specs" style="display:none">
|
||||
<!-- filled by JS -->
|
||||
</div>
|
||||
|
||||
<div class="params-card" id="params-card">
|
||||
<div class="card-header">商品参数</div>
|
||||
<div id="params-rows"></div>
|
||||
</div>
|
||||
|
||||
<div class="auth-card" id="auth-card" style="display:none">
|
||||
<div class="card-header">批次与防伪</div>
|
||||
<div class="auth-inner">
|
||||
<div class="auth-rows" id="auth-rows"></div>
|
||||
<div class="auth-stamp">
|
||||
<div class="auth-stamp-text">岩美<br/>防伪<br/>✓</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="auth-note">如发现可疑商品请联系出售方确认</div>
|
||||
</div>
|
||||
|
||||
<div class="shop-card" id="shop-card" style="display:none">
|
||||
<div class="shop-name-row">
|
||||
<span class="shop-icon">🏪</span>
|
||||
<span class="shop-name-text" id="shop-display-name">门店信息</span>
|
||||
</div>
|
||||
<div id="shop-rows"></div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="footer-mark"></div>
|
||||
<div class="footer-text">由岩美酒库管理系统提供防伪验证</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = location.hostname === 'localhost' ? 'http://localhost:8080/api/v1' : '/api/v1';
|
||||
|
||||
function getPublicId() {
|
||||
// URL pattern: /scan/<public_id>
|
||||
const parts = location.pathname.split('/').filter(Boolean);
|
||||
const idx = parts.indexOf('scan');
|
||||
return idx >= 0 ? parts[idx + 1] : null;
|
||||
}
|
||||
|
||||
let _imgs = [];
|
||||
let _current = 0;
|
||||
|
||||
function showImg(i) {
|
||||
_current = i;
|
||||
document.querySelectorAll('#gallery-main img').forEach((el, j) => {
|
||||
el.classList.toggle('active', j === i);
|
||||
});
|
||||
document.querySelectorAll('.thumb').forEach((el, j) => {
|
||||
el.classList.toggle('active', j === i);
|
||||
});
|
||||
const counter = document.getElementById('gallery-counter');
|
||||
if (counter) counter.textContent = `${i + 1} / ${_imgs.length}`;
|
||||
}
|
||||
|
||||
function renderGallery(imageUrls) {
|
||||
const main = document.getElementById('gallery-main');
|
||||
const thumbsEl = document.getElementById('thumbnails');
|
||||
const placeholder = document.getElementById('gallery-placeholder');
|
||||
|
||||
if (!imageUrls || imageUrls.length === 0) return;
|
||||
|
||||
_imgs = imageUrls;
|
||||
placeholder.style.display = 'none';
|
||||
|
||||
imageUrls.forEach((url, i) => {
|
||||
const img = document.createElement('img');
|
||||
img.src = url;
|
||||
img.alt = '';
|
||||
if (i === 0) img.classList.add('active');
|
||||
img.onclick = () => openFullscreen(i);
|
||||
main.appendChild(img);
|
||||
});
|
||||
|
||||
if (imageUrls.length > 1) {
|
||||
const counter = document.createElement('div');
|
||||
counter.className = 'gallery-counter';
|
||||
counter.id = 'gallery-counter';
|
||||
counter.textContent = `1 / ${imageUrls.length}`;
|
||||
main.appendChild(counter);
|
||||
|
||||
thumbsEl.style.display = 'flex';
|
||||
imageUrls.forEach((url, i) => {
|
||||
const t = document.createElement('img');
|
||||
t.src = url;
|
||||
t.className = 'thumb' + (i === 0 ? ' active' : '');
|
||||
t.onclick = () => showImg(i);
|
||||
thumbsEl.appendChild(t);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function openFullscreen(i) {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.9);z-index:999;display:flex;align-items:center;justify-content:center;';
|
||||
const img = document.createElement('img');
|
||||
img.src = _imgs[i];
|
||||
img.style.cssText = 'max-width:100%;max-height:100%;object-fit:contain;';
|
||||
overlay.appendChild(img);
|
||||
overlay.onclick = () => document.body.removeChild(overlay);
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
function parseQuickSpecs(spec, productionDate) {
|
||||
if (!spec) return [];
|
||||
const items = [];
|
||||
const degM = spec.match(/(\d+(?:\.\d+)?)\s*(?:度|°|%vol)/i);
|
||||
if (degM) items.push({ v: degM[1], u: '°', l: '酒精度' });
|
||||
const mlM = spec.match(/(\d+(?:\.\d+)?)\s*ml/i);
|
||||
if (mlM) items.push({ v: mlM[1], u: 'ml', l: '净含量' });
|
||||
const xiangM = spec.match(/([清浓酱兼馥凤]香)/);
|
||||
if (xiangM) items.push({ v: xiangM[1], u: '', l: '香型' });
|
||||
if (productionDate) {
|
||||
const yrM = productionDate.match(/^(\d{4})/);
|
||||
if (yrM) items.push({ v: yrM[1], u: '', l: '年份' });
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function paramRow(label, value) {
|
||||
return `<div class="param-row"><div class="param-label">${label}</div><div class="param-val">${value}</div></div>`;
|
||||
}
|
||||
|
||||
function render(d) {
|
||||
const name = d.name || '';
|
||||
const series = d.series || '';
|
||||
const spec = d.spec || '';
|
||||
const brand = d.brand || '';
|
||||
const unit = d.unit || '';
|
||||
const description = d.description || '';
|
||||
const shop = d.shop;
|
||||
const batch = d.batch;
|
||||
const baseUrl = location.origin;
|
||||
|
||||
const images = (d.images || []).map(img => baseUrl + img.url);
|
||||
renderGallery(images);
|
||||
|
||||
document.getElementById('product-name').textContent = name;
|
||||
const subtitleParts = [series, spec].filter(Boolean);
|
||||
const subtitleEl = document.getElementById('product-subtitle');
|
||||
subtitleEl.textContent = subtitleParts.join(' · ');
|
||||
if (!subtitleParts.length) subtitleEl.style.display = 'none';
|
||||
|
||||
const tagsEl = document.getElementById('product-tags');
|
||||
if (brand) tagsEl.innerHTML += `<span class="tag tag-brand">${brand}</span>`;
|
||||
if (unit) tagsEl.innerHTML += `<span class="tag tag-unit">${unit}</span>`;
|
||||
|
||||
// Ribbon shop info
|
||||
if (shop && shop.name) {
|
||||
document.getElementById('ribbon-shop').textContent = `出售方:${shop.name}`;
|
||||
}
|
||||
|
||||
// Quick Specs — parse from spec string
|
||||
const quickSpecsEl = document.getElementById('quick-specs');
|
||||
const qs = parseQuickSpecs(spec, batch ? batch.production_date : null);
|
||||
if (qs.length > 0) {
|
||||
quickSpecsEl.style.display = 'grid';
|
||||
quickSpecsEl.innerHTML = qs.map(it => `
|
||||
<div class="quick-spec-item">
|
||||
<div class="quick-spec-val">${it.v}<span class="quick-spec-unit">${it.u||''}</span></div>
|
||||
<div class="quick-spec-label">${it.l}</div>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
// Params
|
||||
let paramsHtml = '';
|
||||
if (spec) paramsHtml += paramRow('规格', spec);
|
||||
if (brand) paramsHtml += paramRow('品牌', brand);
|
||||
if (unit) paramsHtml += paramRow('单位', unit);
|
||||
if (description) paramsHtml += paramRow('描述', description);
|
||||
if (paramsHtml) {
|
||||
document.getElementById('params-rows').innerHTML = paramsHtml;
|
||||
} else {
|
||||
document.getElementById('params-card').style.display = 'none';
|
||||
}
|
||||
|
||||
// Authenticity (batch info)
|
||||
if (batch && (batch.batch_no || batch.production_date || batch.in_stock_date)) {
|
||||
const authCard = document.getElementById('auth-card');
|
||||
let authHtml = '';
|
||||
if (batch.batch_no) authHtml += `<div class="auth-lbl">批次号</div><div class="auth-val auth-mono">${batch.batch_no}</div>`;
|
||||
if (batch.production_date) authHtml += `<div class="auth-lbl">生产日期</div><div class="auth-val">${batch.production_date}</div>`;
|
||||
if (batch.in_stock_date) authHtml += `<div class="auth-lbl">入库日期</div><div class="auth-val">${batch.in_stock_date}</div>`;
|
||||
document.getElementById('auth-rows').innerHTML = authHtml;
|
||||
authCard.style.display = 'block';
|
||||
}
|
||||
|
||||
// Shop card
|
||||
if (shop) {
|
||||
const shopCard = document.getElementById('shop-card');
|
||||
const shopName = shop.name || '';
|
||||
document.getElementById('shop-display-name').textContent = shopName || '门店信息';
|
||||
let shopHtml = '';
|
||||
if (shop.code) shopHtml += paramRow('门店编号', shop.code);
|
||||
if (shop.address) shopHtml += paramRow('地址', shop.address);
|
||||
if (shop.phone) shopHtml += paramRow('电话', shop.phone);
|
||||
if (shopHtml) {
|
||||
document.getElementById('shop-rows').innerHTML = shopHtml;
|
||||
shopCard.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('state-loading').style.display = 'none';
|
||||
document.getElementById('content').style.display = 'block';
|
||||
}
|
||||
|
||||
function load() {
|
||||
const pid = getPublicId();
|
||||
if (!pid) {
|
||||
document.getElementById('state-loading').style.display = 'none';
|
||||
document.getElementById('state-error').style.display = 'flex';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('state-loading').style.display = 'flex';
|
||||
document.getElementById('state-error').style.display = 'none';
|
||||
document.getElementById('content').style.display = 'none';
|
||||
|
||||
fetch(`${API_BASE}/public/products/${pid}`)
|
||||
.then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
|
||||
.then(data => render(data.data))
|
||||
.catch(() => {
|
||||
document.getElementById('state-loading').style.display = 'none';
|
||||
document.getElementById('state-error').style.display = 'flex';
|
||||
});
|
||||
}
|
||||
|
||||
load();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user