diff --git a/.gitea/workflows/ds-gate.yml b/.gitea/workflows/ds-gate.yml
new file mode 100644
index 0000000..f82d190
--- /dev/null
+++ b/.gitea/workflows/ds-gate.yml
@@ -0,0 +1,22 @@
+name: Design System Gate
+
+# 原型设计系统守门闸(单一真源,12 道检查)。
+# 改了原型(tokens/atoms/screens/index)就跑,确保颜色/排版/层级/圆角/字体/图标
+# 全走 tokens.css/atoms.css,组件已在 index.html 登记且未 fork。
+on:
+ push:
+ branches: ['**']
+ paths:
+ - '.superpowers/prototype/**'
+ pull_request:
+ paths:
+ - '.superpowers/prototype/**'
+
+jobs:
+ check-ds:
+ runs-on: mac
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: 设计系统守门(check-ds.mjs · 12 道闸)
+ run: node .superpowers/prototype/tools/check-ds.mjs
diff --git a/.gitignore b/.gitignore
index 05acde2..ddbae71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,8 +67,10 @@ backend/gencode
# iOS 证书私钥(绝不入库)
.ios-certs/
-# brainstorming visual companion
-.superpowers/
+# superpowers 工作目录:brainstorm/sdd 是草稿不入库;
+# prototype 是设计像素真源 + 设计系统守门闸(check-ds.mjs),入库供 CI 跑。
+.superpowers/*
+!.superpowers/prototype/
# design-distill 保真目检对比图(可重生,见 tools/ds-compare.mjs)
design/_compare/
diff --git a/.superpowers/prototype/atoms.css b/.superpowers/prototype/atoms.css
new file mode 100644
index 0000000..e42dc27
--- /dev/null
+++ b/.superpowers/prototype/atoms.css
@@ -0,0 +1,472 @@
+/* ============================================================
+ 岩美酒库 · 组件层(原子)
+ 仅引用 tokens.css 的 var(),被设计系统文档与所有屏幕复用。
+ ============================================================ */
+*{box-sizing:border-box;}
+svg{fill:none; stroke:currentColor;}
+
+/* ---- 按钮 ---- */
+.btn{height:38px; padding:0 16px; border-radius:var(--r-md); font-size:var(--fs-body); font-weight:600; display:inline-flex; align-items:center; gap:7px; cursor:pointer; border:1px solid transparent; transition:background-color .15s,border-color .15s,color .15s; font-family:var(--font);}
+.btn svg{width:16px; height:16px; stroke-width:1.8;}
+.btn.ghost{background:var(--surface); border-color:var(--border); color:var(--text);}
+.btn.ghost:hover{background:var(--bg);}
+.btn.primary{background:var(--primary); color:var(--on-primary);}
+.btn.primary:hover{background:var(--primary-dark);}
+.btn.danger{background:var(--danger); color:#fff;}
+.btn.sm{height:32px; padding:0 12px; font-size:var(--fs-sm);}
+.btn.lg{height:44px; padding:0 22px; font-size:14px;}
+
+/* ---- 输入 / 控件 ---- */
+.input{height:38px; width:100%; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); padding:0 11px; font-size:var(--fs-body); color:var(--text); outline:none; font-family:var(--font);}
+.input:focus{border-color:var(--primary); box-shadow:0 0 0 3px var(--brand50);}
+.input::placeholder{color:var(--faint);}
+.input[readonly]{background:var(--bg); color:var(--muted); cursor:default;}
+.input[readonly]:focus{border-color:var(--border); box-shadow:none;}
+.ctrl{height:38px; width:100%; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); padding:0 11px; font-size:var(--fs-body); color:var(--text); display:flex; align-items:center; justify-content:space-between; cursor:pointer; font-family:var(--font);}
+.ctrl.input-like{cursor:text;} .ctrl input{border:none; outline:none; background:transparent; width:100%; font-size:var(--fs-body); color:var(--text); font-family:var(--font);}
+.ctrl .ph{color:var(--faint);}
+.searchbox{height:34px; background:var(--bg); border:1px solid var(--border); border-radius:var(--r-md); display:flex; align-items:center; gap:8px; padding:0 11px;}
+.searchbox svg{width:14px; height:14px; stroke-width:2; color:var(--faint); flex:none;}
+.searchbox input{border:none; background:transparent; outline:none; font-size:var(--fs-body); color:var(--text); width:100%; font-family:var(--font);}
+.searchbox input::placeholder{color:var(--faint);}
+
+/* ---- 日期字段(触发器)+ 滚轮日期选择器(年月日,单据/生产/明细共用) ---- */
+.datefield{height:38px; width:100%; box-sizing:border-box; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); padding:0 11px; display:flex; align-items:center; gap:8px; cursor:pointer; outline:none; transition:border-color .15s,box-shadow .15s;}
+.datefield:hover{border-color:var(--brand400);}
+.datefield:focus{border-color:var(--primary); box-shadow:0 0 0 3px var(--brand50);}
+.datefield.error{border-color:var(--danger);}
+.datefield.disabled{background:var(--bg); color:var(--muted); pointer-events:none; opacity:.7;}
+.datefield .dval{flex:1; min-width:0; font-size:var(--fs-body); color:var(--text); font-family:var(--font-mono); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
+.datefield .dval.ph{color:var(--faint); font-family:var(--font);}
+.datefield svg{width:15px; height:15px; stroke-width:1.8; color:var(--faint); flex:none;}
+/* 网格单元格变体:默认无边、矮,hover/focus 才显形(对齐 .combo.cell) */
+.datefield.gci-date{height:32px; border-color:transparent; background:transparent; border-radius:5px; padding:0 8px;}
+.datefield.gci-date:hover{background:var(--bg);}
+.datefield.gci-date:focus{background:var(--surface); border-color:var(--primary); box-shadow:0 0 0 2px var(--brand50);}
+.datefield.gci-date.error{border-color:var(--danger); background:var(--danger-bg); box-shadow:none;}
+.wheel-pop{position:fixed; z-index:var(--z-pop); background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); box-shadow:var(--sh-3); padding:8px; width:248px; display:none;}
+.wheel-pop.show{display:block;}
+.wheel-cols{position:relative; display:flex; height:180px; overflow:hidden;}
+.wheel-center{position:absolute; left:4px; right:4px; top:72px; height:36px; border-radius:var(--r-md); background:var(--bg); border-top:1px solid var(--border); border-bottom:1px solid var(--border); pointer-events:none; z-index:0;}
+.wheel-col{position:relative; z-index:1; flex:1; height:100%; overflow-y:scroll; scroll-snap-type:y mandatory; padding:72px 0; text-align:center; scrollbar-width:none; -ms-overflow-style:none;}
+.wheel-col::-webkit-scrollbar{display:none;}
+.wheel-item{height:36px; line-height:36px; scroll-snap-align:center; font-size:var(--fs-body); color:var(--muted); font-family:var(--font-mono); transition:color .12s,font-size .12s;}
+.wheel-item.on{color:var(--primary); font-weight:700; font-size:16px;}
+.wheel-foot{display:flex; justify-content:space-between; align-items:center; padding:8px 4px 2px; margin-top:6px; border-top:1px solid var(--border-subtle);}
+.wheel-foot .wheel-today{font-size:var(--fs-sm); color:var(--muted); cursor:pointer; padding:4px 10px; border-radius:var(--r-sm);}
+.wheel-foot .wheel-today:hover{background:var(--bg); color:var(--text);}
+.wheel-foot .wheel-ok{font-size:var(--fs-sm); color:var(--on-primary); background:var(--primary); font-weight:600; cursor:pointer; padding:5px 16px; border-radius:var(--r-sm);}
+.wheel-foot .wheel-ok:hover{background:var(--primary-dark);}
+
+/* ---- Chip(筛选) ---- */
+.chip{height:34px; padding:0 12px; border:1px solid var(--border); border-radius:var(--r-md); background:var(--surface); font-size:var(--fs-sm); color:var(--text); display:inline-flex; align-items:center; gap:7px; cursor:pointer; transition:border-color .15s;}
+.chip:hover{border-color:var(--brand400);}
+.chip.on{border-color:var(--primary);}
+.chip .cv{color:var(--primary); font-weight:600;}
+.chip svg{width:14px; height:14px; stroke-width:1.8; color:var(--faint);}
+
+/* ---- 状态徽章 ---- */
+.badge{display:inline-flex; align-items:center; gap:5px; height:22px; padding:0 9px; border-radius:var(--r-pill); font-size:var(--fs-sm); font-weight:600;}
+.badge::before{content:""; width:6px; height:6px; border-radius:50%; background:currentColor;}
+.b-在售{background:var(--success-bg); color:var(--success);}
+.b-缺货{background:var(--danger-bg); color:var(--danger);}
+.b-预警{background:var(--warn-bg); color:var(--warn);}
+/* 单据状态 */
+.b-草稿{background:var(--info-soft); color:var(--muted);}
+.b-待审核{background:var(--warn-bg); color:var(--warn);}
+.b-已通过{background:var(--success-bg); color:var(--success);}
+.b-已驳回{background:var(--danger-bg); color:var(--danger);}
+/* 财务 / 往来 / 用户角色 */
+.b-收款,.b-启用{background:var(--success-bg); color:var(--success);}
+.b-付款{background:var(--danger-bg); color:var(--danger);}
+.b-供应商,.b-管理员{background:var(--info-soft); color:var(--primary);}
+.b-超级管理员{background:var(--danger-bg); color:var(--danger);}
+.b-操作员{background:var(--ok-soft); color:var(--success);}
+.b-客户{background:var(--accent-soft); color:var(--accent);}
+.b-两者,.b-只读{background:var(--warn-bg); color:var(--warn);}
+.b-停用,.b-普通,.b-离线{background:var(--border-subtle); color:var(--muted);}
+/* 流水 / 设备 */
+.b-入库,.b-在线{background:var(--success-bg); color:var(--success);}
+.b-出库{background:var(--danger-bg); color:var(--danger);}
+/* 待定价:入库进价留空/0(暂估),确认进价后回填真实成本 */
+.pending-price{color:var(--warn); font-weight:600;}
+.b-待定价{background:var(--warn-bg); color:var(--warn);}
+
+/* ---- KPI 卡 ---- */
+.kpi{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); padding:15px 16px; transition:background-color .28s,border-color .15s,box-shadow .15s;}
+.kpi.click{cursor:pointer;} .kpi.click:hover{border-color:var(--brand400); box-shadow:var(--sh-1);}
+.kpi .t{font-size:var(--fs-sm); color:var(--muted);}
+.kpi .v{font-size:24px; font-weight:700; color:var(--heading); margin-top:7px; letter-spacing:.3px; font-family:var(--font-mono);}
+.kpi.alert .v{color:var(--accent);}
+.kpi .d{font-size:var(--fs-xs); margin-top:5px; display:inline-flex; align-items:center; gap:4px;}
+.up{color:var(--success);} .down{color:var(--danger);}
+.ic{width:30px; height:30px; border-radius:var(--r-md); display:flex; align-items:center; justify-content:center; float:right; margin-top:-2px;}
+.ic svg{width:17px; height:17px; stroke-width:1.8;}
+.ic-info{background:var(--info-soft); color:var(--primary);} .ic-ok{background:var(--ok-soft); color:var(--success);}
+.ic-blue{background:var(--info-soft); color:var(--brand400);} .ic-alert{background:var(--accent-soft); color:var(--accent);}
+
+/* ---- 表格 ---- */
+.table{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); overflow:hidden; transition:background-color .28s,border-color .28s;}
+.table.flush-top{border-radius:0 0 var(--r-lg) var(--r-lg); border-top:none;}
+table{width:100%; border-collapse:collapse;}
+thead th{background:var(--th-bg); text-align:left; font-size:var(--fs-sm); font-weight:600; color:var(--muted); padding:11px 14px; border-bottom:1px solid var(--border); white-space:nowrap;}
+th.num,td.num{text-align:right;}
+th.act,td.act{width:78px;}
+thead th .fh{display:inline-flex; align-items:center; gap:5px; cursor:pointer;}
+thead th .fh.filtered{color:var(--primary);}
+.funnel{width:12px; height:12px; stroke-width:1.6;}
+tbody td{padding:12px 14px; font-size:var(--fs-body); border-bottom:1px solid var(--border-subtle); color:var(--text); white-space:nowrap;}
+tbody tr:last-child td{border-bottom:none;}
+tbody tr.click{cursor:pointer;} tbody tr:hover{background:var(--row-hover);}
+.pname{font-weight:600; color:var(--heading);}
+.pcode{font-size:var(--fs-xs); color:var(--faint); margin-top:2px; font-family:var(--font-mono);}
+.qty{font-weight:600; font-family:var(--font-mono);} .qty.low{color:var(--accent);}
+td.num{font-family:var(--font-mono);}
+.row-act{display:flex; gap:8px; color:var(--muted);}
+.row-act svg{width:16px; height:16px; stroke-width:1.7; cursor:pointer;}
+.row-act svg:hover{color:var(--primary);}
+.empty{padding:48px; text-align:center; color:var(--muted); font-size:var(--fs-body);}
+
+/* ---- 分页 ---- */
+.pager{display:flex; align-items:center; gap:12px; padding:13px 4px 2px; font-size:var(--fs-sm); color:var(--muted);}
+.pager .pg{margin-left:auto; display:flex; gap:4px;}
+.pg button{min-width:30px; height:30px; padding:0 6px; border:1px solid var(--border); background:var(--surface); border-radius:var(--r-sm); color:var(--text); font-size:var(--fs-sm); cursor:pointer; font-family:var(--font-mono);}
+.pg button.cur{background:var(--primary); color:var(--on-primary); border-color:var(--primary);}
+.pg button:disabled{color:var(--faint); cursor:default;}
+.pager .pg-size{display:inline-flex; align-items:center; gap:6px;}
+.pgsize-btn{display:inline-flex; align-items:center; gap:6px; height:30px; padding:0 8px 0 10px; border:1px solid var(--border); border-radius:var(--r-sm); background:var(--surface); color:var(--text); font-size:var(--fs-sm); font-family:var(--font-mono); cursor:pointer;}
+.pgsize-btn:hover{border-color:var(--primary);}
+.pgsize-caret{width:12px; height:12px; color:var(--muted); flex:none;}
+
+/* ---- 下拉菜单(JS 定位) ---- */
+.menu{position:fixed; z-index:var(--z-menu); background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); box-shadow:var(--sh-2); padding:6px; min-width:160px;}
+.menu-item{display:flex; align-items:center; gap:9px; padding:8px 10px; border-radius:var(--r-sm); font-size:var(--fs-body); color:var(--text); cursor:pointer;}
+.menu-item:hover{background:var(--bg);}
+.menu-item .ck{width:16px; height:16px; color:var(--primary);}
+.menu-item .mi-ic{width:16px; height:16px; flex:none; stroke-width:1.8;}
+.menu-item.sel{color:var(--primary); font-weight:600;}
+/* 用户菜单(侧栏底部弹出):比激活导航略浅的同色系 + 浅色字 */
+.menu.user-menu{background:var(--menu-user-bg); border-color:var(--menu-user-bg);}
+.menu.user-menu .menu-item{color:var(--menu-user-fg);}
+.menu.user-menu .menu-item .mi-ic{color:var(--menu-user-fg);}
+.menu.user-menu .menu-item:hover{background:var(--menu-user-hover);}
+
+/* ---- 弹窗 ---- */
+.overlay{position:fixed; inset:0; background:var(--scrim); display:none; align-items:center; justify-content:center; z-index:var(--z-overlay);}
+.overlay.show{display:flex;}
+.modal{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-xl); width:680px; max-width:92vw; max-height:88vh; display:flex; flex-direction:column; box-shadow:0 24px 70px var(--shadow); overflow:hidden;}
+.modal.narrow{width:520px;}
+.modal-head{display:flex; align-items:center; padding:16px 20px; border-bottom:1px solid var(--border);}
+.modal-head h3{margin:0; font-size:var(--fs-h2); color:var(--heading);}
+.modal-head .x, .drawer-head .x{margin-left:auto; cursor:pointer; color:var(--muted); width:22px; height:22px;}
+.modal-body{padding:18px 20px; overflow:auto;}
+.modal-foot{display:flex; justify-content:flex-end; gap:10px; padding:14px 20px; border-top:1px solid var(--border);}
+.frow{display:flex; gap:14px; margin-bottom:14px;}
+.field{flex:1;} .field label{display:block; font-size:var(--fs-sm); color:var(--muted); margin-bottom:6px;}
+.lines{border:1px solid var(--border); border-radius:var(--r-md); overflow:hidden; margin-bottom:6px;}
+.lines .lh, .lines .lr{display:grid; grid-template-columns:1fr 80px 90px 90px 30px; gap:8px; align-items:center; padding:9px 12px; font-size:var(--fs-sm);}
+.lines .lh{background:var(--th-bg); color:var(--muted); font-weight:600;}
+.lines .lr{border-top:1px solid var(--border-subtle);}
+.lines .lr input{height:30px; border:1px solid var(--border); border-radius:5px; padding:0 8px; font-size:var(--fs-sm); background:var(--bg); color:var(--text); width:100%; outline:none; font-family:var(--font);}
+.lines .lr .amt{font-weight:600; color:var(--heading); text-align:right; font-family:var(--font-mono);}
+.lines .lr .del{color:var(--faint); cursor:pointer; text-align:center;}
+.lines .lr .del:hover{color:var(--danger);}
+.ltotal{display:flex; justify-content:flex-end; gap:10px; align-items:center; font-size:var(--fs-body); color:var(--muted); padding:8px 4px 0;}
+.ltotal b{font-size:18px; color:var(--accent); font-family:var(--font-mono);}
+
+/* ---- 抽屉 ---- */
+.drawer{position:fixed; top:0; right:0; height:100vh; width:480px; max-width:94vw; background:var(--surface); border-left:1px solid var(--border); box-shadow:-16px 0 50px var(--shadow); transform:translateX(100%); transition:transform .25s; z-index:var(--z-drawer); display:flex; flex-direction:column;}
+.drawer.show{transform:translateX(0);}
+.drawer.wide{width:600px;}
+/* 抽屉遮罩:点击空白处关闭抽屉(z-index 在抽屉之下) */
+.drawer-mask{position:fixed; inset:0; background:var(--scrim); opacity:0; pointer-events:none; transition:opacity .25s; z-index:var(--z-drawer-scrim);}
+.drawer-mask.show{opacity:1; pointer-events:auto;}
+.drawer-head{padding:18px 20px; border-bottom:1px solid var(--border); display:flex; align-items:center;}
+.drawer-head h3{margin:0; font-size:var(--fs-h2); color:var(--heading);}
+.drawer-body{padding:18px 20px; overflow:auto;}
+.drow{display:flex; justify-content:space-between; padding:11px 0; border-bottom:1px solid var(--border-subtle); font-size:var(--fs-body);}
+.drow span{color:var(--muted);} .drow b{color:var(--text); font-weight:600;}
+/* ---- 商品编辑抽屉 .pe-*(product-editor.js 注入,库存/基础数据共用)---- */
+.pe-card{border:1px solid var(--border); border-radius:var(--r-lg); margin-bottom:14px; overflow:hidden; background:var(--surface);}
+.pe-h{display:flex; align-items:center; gap:8px; padding:11px 13px; border-bottom:1px solid var(--border-subtle); font-size:var(--fs-body); font-weight:600; color:var(--heading);}
+.pe-h .pe-ic{width:22px; height:22px; border-radius:var(--r-sm); background:var(--brand50); color:var(--primary); display:flex; align-items:center; justify-content:center; flex:none;}
+.pe-h .pe-ic svg{width:14px; height:14px; stroke-width:1.9;}
+.pe-h .pe-manage{margin-left:auto; font-size:var(--fs-sm); color:var(--primary); cursor:pointer;}
+.pe-b{padding:13px;}
+.pe-note{font-size:var(--fs-xs); color:var(--faint); margin-top:9px; line-height:1.6;}
+.pe-gallery{display:flex; gap:9px; flex-wrap:wrap;}
+.pe-tile{width:86px; height:106px; border:1.5px dashed var(--border); border-radius:var(--r-md); background:var(--bg); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:5px; cursor:pointer; color:var(--faint); font-size:var(--fs-xs); position:relative; overflow:hidden;}
+.pe-tile:hover{border-color:var(--primary); color:var(--primary);}
+.pe-tile svg{width:20px; height:20px; stroke-width:1.6;}
+.pe-tile img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
+.pe-tile.has{border-style:solid; border-color:var(--border);}
+.pe-tile .rm{position:absolute; top:3px; right:3px; width:18px; height:18px; border-radius:50%; background:var(--danger); color:#fff; display:none; align-items:center; justify-content:center;}
+.pe-tile.has .rm{display:flex;}
+.pe-tile .cap{position:absolute; bottom:0; left:0; right:0; background:var(--scrim); color:#fff; font-size:var(--fs-xs); text-align:center; padding:1px 0;}
+.pe-pick{display:flex; align-items:center; gap:8px; height:38px; padding:0 11px; border:1px solid var(--border); border-radius:var(--r-md); cursor:pointer; color:var(--text); font-size:var(--fs-body); background:var(--surface);}
+.pe-pick:hover{border-color:var(--primary);}
+.pe-pick .ph{color:var(--faint);}
+.pe-pick svg{margin-left:auto; width:14px; height:14px; color:var(--muted);}
+.pe-text{width:100%; box-sizing:border-box; min-height:84px; resize:vertical; border:1px solid var(--border); border-radius:var(--r-md); padding:10px 11px; font-size:var(--fs-body); color:var(--text); background:var(--surface); outline:none; font-family:var(--font); line-height:1.7; margin-top:10px;}
+.pe-text:focus{border-color:var(--primary);}
+.pe-foot{display:flex; gap:10px; justify-content:flex-end; margin-top:6px;}
+/* 明细行三行式:商品名 / 编码 / 系列·规格 */
+.lcell{min-width:0;}
+.lcell .ln{display:block; color:var(--heading); font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
+.lcell .lcode{display:block; font-family:var(--font-mono); color:var(--muted); font-size:var(--fs-xs); margin-top:2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
+.lcell .lspec{display:block; color:var(--muted); font-size:var(--fs-xs); margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
+.lcell .lser{display:block; color:var(--text); font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
+/* 抽屉底部操作分组:审核类 / 打印类 */
+.dactions{margin-top:18px; display:flex; flex-direction:column; gap:14px;}
+.dact-group{display:flex; flex-direction:column; gap:8px;}
+.dact-label{font-size:var(--fs-xs); color:var(--muted); font-weight:600; letter-spacing:.4px;}
+.dact-row{display:flex; flex-wrap:wrap; gap:10px;}
+
+/* ---- Toast ---- */
+.toast{position:fixed; bottom:26px; left:50%; transform:translateX(-50%) translateY(20px); background:var(--toast-bg); color:#fff; padding:11px 18px; border-radius:var(--r-md); font-size:var(--fs-body); opacity:0; pointer-events:none; transition:.25s; z-index:var(--z-toast);}
+.toast.show{opacity:1; transform:translateX(-50%) translateY(0);}
+
+/* ---- 底部状态栏(app-shell 底,statusbar.js 注入) ---- */
+.statusbar{flex:none; height:34px; display:flex; align-items:center; padding:0 6px; background:var(--statusbar-bg); color:var(--statusbar-fg); font-size:var(--fs-xs); border-top:1px solid var(--statusbar-div); overflow:hidden;}
+.statusbar .sb-group{display:flex; align-items:center; min-width:0;}
+.statusbar .sb-group.right{margin-left:auto;}
+.statusbar .sb-i{display:inline-flex; align-items:center; gap:6px; padding:0 13px; white-space:nowrap;}
+.statusbar .sb-group .sb-i + .sb-i{border-left:1px solid var(--statusbar-div);}
+.statusbar .sb-i svg{width:13px; height:13px; stroke-width:1.8; color:var(--statusbar-muted); flex:none;}
+.statusbar .sb-i b{color:var(--statusbar-strong); font-weight:600; font-family:var(--font-mono);}
+.statusbar .sb-i.ok, .statusbar .sb-i.ok svg, .statusbar .sb-i.ok b{color:var(--statusbar-ok);}
+@media(max-width:760px){ .statusbar .sb-hideable{display:none;} }
+
+/* ---- 顶栏通知(小喇叭 + 下拉面板) ---- */
+.nbadge{position:absolute; top:2px; right:2px; min-width:15px; height:15px; padding:0 4px; border-radius:var(--r-pill); background:var(--danger); color:#fff; font-size:var(--fs-xs); font-weight:700; line-height:15px; text-align:center; box-shadow:0 0 0 2px var(--top-bg); pointer-events:none;}
+.notify-pop{position:fixed; z-index:var(--z-pop); width:330px; max-width:92vw; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); box-shadow:var(--sh-3); display:none; flex-direction:column; overflow:hidden;}
+.notify-pop.show{display:flex;}
+.notify-pop .np-head{display:flex; align-items:center; justify-content:space-between; padding:12px 14px; border-bottom:1px solid var(--border-subtle);}
+.notify-pop .np-head h4{margin:0; font-size:var(--fs-title); color:var(--heading);}
+.notify-pop .np-head .np-clear{font-size:var(--fs-sm); color:var(--primary); cursor:pointer;}
+.notify-pop .np-list{max-height:340px; overflow:auto;}
+.notify-pop .np-item{display:flex; gap:11px; padding:11px 14px; border-bottom:1px solid var(--border-subtle); cursor:pointer; transition:background-color .12s;}
+.notify-pop .np-item:hover{background:var(--bg);}
+.notify-pop .np-item.unread{background:var(--brand50);}
+.notify-pop .np-item.unread:hover{background:var(--brand50);}
+.notify-pop .np-ic{width:30px; height:30px; border-radius:var(--r-md); flex:none; display:flex; align-items:center; justify-content:center;}
+.notify-pop .np-ic svg{width:16px; height:16px; stroke-width:1.8;}
+.notify-pop .np-ic.warn{background:var(--warn-bg); color:var(--warn);}
+.notify-pop .np-ic.info{background:var(--info-soft); color:var(--primary);}
+.notify-pop .np-ic.ok{background:var(--success-bg); color:var(--success);}
+.notify-pop .np-tx{flex:1; min-width:0;}
+.notify-pop .np-tx .np-t{font-size:var(--fs-body); color:var(--heading); font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
+.notify-pop .np-tx .np-d{font-size:var(--fs-xs); color:var(--muted); margin-top:2px;}
+.notify-pop .np-time{font-size:var(--fs-xs); color:var(--faint); flex:none;}
+.notify-pop .np-empty{padding:30px; text-align:center; color:var(--muted); font-size:var(--fs-sm);}
+.notify-pop .np-foot{padding:9px 14px; text-align:center; border-top:1px solid var(--border-subtle); font-size:var(--fs-sm); color:var(--primary); cursor:pointer; background:var(--bg);}
+
+/* ---- 通知中心 .nt-*(列表行 + 类型图标,桌面/移动共用,登记于 index.html)---- */
+.nt-item{display:flex; gap:12px; align-items:flex-start; padding:13px 15px; border:1px solid var(--border); border-radius:var(--r-lg); background:var(--surface); cursor:pointer; text-decoration:none; color:var(--text); transition:border-color .15s,background-color .15s;}
+.nt-item+.nt-item{margin-top:10px;}
+.nt-item:hover{border-color:var(--brand400);}
+.nt-item.unread{background:var(--brand50);}
+.nt-ic{width:38px; height:38px; border-radius:var(--r-md); flex:none; display:flex; align-items:center; justify-content:center;}
+.nt-ic svg{width:19px; height:19px; stroke-width:1.8;}
+.nt-ic.warn{background:var(--warn-bg); color:var(--warn);}
+.nt-ic.info{background:var(--info-soft); color:var(--primary);}
+.nt-ic.ok{background:var(--success-bg); color:var(--success);}
+.nt-ic.danger{background:var(--danger-bg); color:var(--danger);}
+.nt-ic.lg{width:50px; height:50px; border-radius:var(--r-lg);}
+.nt-ic.lg svg{width:25px; height:25px;}
+.nt-bd{flex:1; min-width:0;}
+.nt-t{display:flex; align-items:center; gap:7px;}
+.nt-t .nt-dot{width:7px; height:7px; border-radius:50%; background:var(--primary); flex:none;}
+.nt-t .nt-tt{flex:1; min-width:0; font-size:var(--fs-body); font-weight:600; color:var(--heading); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
+.nt-t .nt-time{font-size:var(--fs-xs); color:var(--faint); flex:none; white-space:nowrap;}
+.nt-d{font-size:var(--fs-sm); color:var(--muted); margin-top:3px; line-height:1.55;}
+
+/* ============================================================
+ 录单表单新增原子(入库 / 出库录单屏复用)
+ 仍只引用 tokens.css 的 var(),禁硬编码 hex。
+ ============================================================ */
+
+/* ---- 分段切换 .seg(入库 / 出库 等二三选一)---- */
+.seg{display:inline-flex; background:var(--bg); border:1px solid var(--border); border-radius:var(--r-md); padding:3px; gap:3px;}
+.seg button{height:30px; padding:0 16px; border:none; background:transparent; border-radius:var(--r-sm); font-size:var(--fs-sm); font-weight:600; color:var(--muted); cursor:pointer; font-family:var(--font); transition:color .15s,background-color .15s;}
+.seg button.on{background:var(--surface); color:var(--primary); box-shadow:var(--sh-1);}
+
+/* ---- 可搜索选择器 .combo(单据头全尺寸 + 网格单元格变体 .cell)---- */
+.combo{position:relative; height:38px; width:100%; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); display:flex; align-items:center; cursor:text; transition:border-color .15s,box-shadow .15s;}
+.combo input{border:none; outline:none; background:transparent; width:100%; height:100%; padding:0 30px 0 11px; font-size:var(--fs-body); color:var(--text); font-family:var(--font); cursor:text; border-radius:inherit;}
+.combo input::placeholder{color:var(--faint);}
+.combo .caf{position:absolute; right:8px; top:50%; transform:translateY(-50%); width:16px; height:16px; color:var(--faint); display:flex; align-items:center; justify-content:center;}
+.combo .caf svg{width:14px; height:14px; stroke-width:2;}
+.combo.has .caf{cursor:pointer;}
+.combo.has .caf:hover{color:var(--danger);}
+.combo:focus-within{border-color:var(--primary); box-shadow:0 0 0 3px var(--brand50);}
+.combo.error{border-color:var(--danger); box-shadow:0 0 0 3px var(--danger-bg);}
+/* 网格单元格变体:默认无边、矮,hover/focus 才显形 */
+.combo.cell{height:32px; border-color:transparent; background:transparent; border-radius:5px;}
+.combo.cell input{padding:0 24px 0 8px; font-size:var(--fs-body);}
+.combo.cell:hover{background:var(--bg);}
+.combo.cell:focus-within{background:var(--surface); border-color:var(--primary); box-shadow:0 0 0 2px var(--brand50);}
+.combo.cell.error{border-color:var(--danger); background:var(--danger-bg); box-shadow:none;}
+.combo.cell .caf{right:6px;}
+
+/* combo 浮层(JS fixed 定位,视觉承袭 .menu)---- */
+.combo-pop{position:fixed; z-index:var(--z-pop); background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); box-shadow:var(--sh-2); display:flex; flex-direction:column; overflow:hidden; min-width:200px;}
+.combo-pop .cp-list{overflow:auto; padding:5px; max-height:264px;}
+.combo-pop .cp-grp{font-size:var(--fs-xs); color:var(--faint); font-weight:600; letter-spacing:.5px; padding:8px 9px 4px;}
+.combo-pop .cp-item{display:flex; align-items:center; gap:9px; padding:8px 10px; border-radius:var(--r-sm); font-size:var(--fs-body); color:var(--text); cursor:pointer; white-space:nowrap;}
+.combo-pop .cp-item .nm{overflow:hidden; text-overflow:ellipsis;}
+.combo-pop .cp-item small{color:var(--faint); font-family:var(--font-mono); font-size:var(--fs-xs); margin-left:auto; padding-left:12px;}
+.combo-pop .cp-item.cur{background:var(--row-hover);}
+.combo-pop .cp-item.sel{color:var(--primary); font-weight:600;}
+.combo-pop .cp-item .ck{width:15px; height:15px; color:var(--primary); flex:none;}
+.combo-pop .cp-item .hl{color:var(--primary); font-weight:700;}
+.combo-pop .cp-new{display:flex; align-items:center; gap:8px; padding:10px 12px; border-top:1px solid var(--border-subtle); color:var(--primary); font-size:var(--fs-sm); font-weight:600; cursor:pointer;}
+.combo-pop .cp-new.cur{background:var(--row-hover);}
+.combo-pop .cp-new svg{width:14px; height:14px; stroke-width:2; flex:none;}
+.combo-pop .cp-empty{padding:20px; text-align:center; color:var(--muted); font-size:var(--fs-sm);}
+/* 弹层置顶搜索框(单据头供应商/仓库等显式搜索下拉用) */
+.combo-pop .cp-search{display:flex; align-items:center; gap:8px; padding:9px 11px; border-bottom:1px solid var(--border-subtle); flex:none;}
+.combo-pop .cp-search svg{width:14px; height:14px; stroke-width:2; color:var(--faint); flex:none;}
+.combo-pop .cp-search input{border:none; outline:none; background:transparent; width:100%; font-size:var(--fs-body); color:var(--text); font-family:var(--font);}
+.combo-pop .cp-search input::placeholder{color:var(--faint);}
+.combo-pop .cp-body{display:flex; flex-direction:column; overflow:hidden;}
+/* 弹层底部结果统计(内容多时启用:显示总返回条数) */
+.combo-pop .cp-foot{flex:none; padding:7px 12px; border-top:1px solid var(--border-subtle); font-size:var(--fs-xs); color:var(--muted); background:var(--bg);}
+.combo-pop .cp-foot b{color:var(--text); font-weight:600; font-family:var(--font-mono);}
+
+/* ---- 可编辑明细网格 .grid ---- */
+.grid{border:1px solid var(--border); border-radius:var(--r-lg); background:var(--surface); display:flex; flex-direction:column; min-height:0; overflow:hidden; transition:background-color .28s,border-color .28s;}
+.grid-scroll{overflow:auto; flex:1; min-height:0;}
+.grid table{width:100%; border-collapse:separate; border-spacing:0; min-width:920px;}
+.grid thead th{position:sticky; top:0; z-index:3; background:var(--th-bg); color:var(--muted); font-size:var(--fs-sm); font-weight:600; text-align:left; padding:10px 10px; border-bottom:1px solid var(--border); white-space:nowrap;}
+.grid th.req::after{content:"*"; color:var(--danger); margin-left:3px;}
+.grid th.num, .grid td.num{text-align:right;}
+.grid tbody td{border-bottom:1px solid var(--border-subtle); background:var(--surface); vertical-align:middle; transition:background-color .15s;}
+.grid tbody tr.gr:hover td{background:var(--row-hover);}
+.grid td.cpad{padding:4px 6px;}
+.grid td.gidx{width:38px; text-align:center; color:var(--faint); font-family:var(--font-mono); font-size:var(--fs-sm);}
+.grid tr.gr.error td{box-shadow:inset 3px 0 0 var(--danger);}
+.grid .gci{height:32px; width:100%; border:1px solid transparent; background:transparent; border-radius:5px; padding:0 8px; font-size:var(--fs-body); color:var(--text); outline:none; font-family:var(--font);}
+.grid td.num .gci{text-align:right; font-family:var(--font-mono);}
+.grid .gci:hover{background:var(--bg);}
+.grid .gci:focus{background:var(--surface); border-color:var(--primary); box-shadow:0 0 0 2px var(--brand50);}
+.grid .gci.error{border-color:var(--danger); background:var(--danger-bg);}
+.grid td.ro{color:var(--muted); padding:0 10px; font-size:var(--fs-body); white-space:nowrap;}
+.grid td.ro.num{font-family:var(--font-mono);}
+.grid td.amt{font-family:var(--font-mono); font-weight:600; color:var(--heading); padding:0 12px; white-space:nowrap;}
+.grid td.gact, .grid th.gact{width:92px;}
+.grid .gact-in{display:flex; gap:8px; justify-content:center; color:var(--muted);}
+.grid .gact-in svg{width:16px; height:16px; stroke-width:1.7; cursor:pointer;}
+.grid .gact-in svg:hover{color:var(--primary);} .grid .gact-in .gdel:hover{color:var(--danger);}
+.grid-add{display:flex; align-items:center; gap:8px; padding:11px 14px; color:var(--primary); font-size:var(--fs-body); font-weight:600; cursor:pointer; border-top:1px solid var(--border-subtle); flex:none;}
+.grid-add:hover{background:var(--row-hover);}
+.grid-add svg{width:16px; height:16px; stroke-width:2;}
+.grid-add kbd{margin-left:6px; font-family:var(--font-mono); font-size:var(--fs-xs); color:var(--faint); font-weight:500;}
+
+/* ---- 状态徽章(单据状态,承袭 .badge 机制)---- */
+.b-草稿{background:var(--bg); color:var(--muted);}
+.b-待审核{background:var(--info-soft); color:var(--primary);}
+.b-已通过{background:var(--success-bg); color:var(--success);}
+.b-已驳回{background:var(--danger-bg); color:var(--danger);}
+
+/* ---- 提示条 .notice(草稿恢复 / 驳回理由)---- */
+.notice{display:flex; align-items:center; gap:10px; padding:11px 14px; border-radius:var(--r-md); font-size:var(--fs-body); margin-bottom:14px; border:1px solid var(--border-subtle);}
+.notice svg{width:17px; height:17px; stroke-width:1.8; flex:none;}
+.notice .nb{margin-left:auto; display:flex; gap:8px; flex:none;}
+.notice.info{background:var(--info-soft); color:var(--primary);}
+.notice.danger{background:var(--danger-bg); color:var(--danger);}
+.notice .lk{cursor:pointer; font-weight:600; text-decoration:underline; text-underline-offset:2px;}
+
+/* ---- 移动端明细卡 .mcard ---- */
+.mcard{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); overflow:hidden; transition:border-color .15s;}
+.mcard.error{border-color:var(--danger);}
+.mcard-head{display:flex; align-items:center; padding:11px 14px; background:var(--th-bg); border-bottom:1px solid var(--border-subtle);}
+.mcard-head .mt{font-weight:600; color:var(--heading); font-size:var(--fs-body);}
+.mcard-head .msub{margin-left:9px; color:var(--faint); font-size:var(--fs-xs); font-family:var(--font-mono);}
+.mcard-head .mact{margin-left:auto; display:flex; gap:14px; color:var(--muted);}
+.mcard-head .mact svg{width:18px; height:18px; stroke-width:1.7; cursor:pointer;}
+.mcard-head .mact .gdel:hover{color:var(--danger);}
+.mcard-body{padding:4px 14px 12px;}
+.mrow{display:flex; align-items:center; gap:12px; padding:9px 0; border-bottom:1px solid var(--border-subtle); min-height:46px;}
+.mrow:last-child{border-bottom:none;}
+.mrow > label{width:64px; flex:none; font-size:var(--fs-sm); color:var(--muted);}
+.mrow > label.req::after{content:"*"; color:var(--danger); margin-left:2px;}
+.mrow .mv{flex:1; min-width:0;}
+.mrow .mv.amt{text-align:right; font-family:var(--font-mono); font-weight:700; color:var(--accent); font-size:var(--fs-h2);}
+.mrow .mv input{height:36px; width:100%; border:1px solid var(--border); background:var(--surface); border-radius:var(--r-md); padding:0 10px; font-size:var(--fs-body); color:var(--text); outline:none; font-family:var(--font);}
+.mrow .mv input:focus{border-color:var(--primary); box-shadow:0 0 0 3px var(--brand50);}
+.mrow .mv input.error{border-color:var(--danger);}
+
+/* ---- 行内选填折叠区(入库:产地/保质期/储存/介绍文档)---- */
+.grid .gexp{transition:transform .2s;} .grid .gexp.on{transform:rotate(180deg); color:var(--primary);}
+.grid tr.gsub td{background:var(--info-soft); box-shadow:inset 3px 0 0 var(--primary);}
+.grid .gsub-pad{padding:12px 16px 14px;}
+.opt-head{font-size:var(--fs-sm); color:var(--muted); margin-bottom:10px;}
+.opt-grid{display:flex; flex-wrap:wrap; gap:14px;}
+.opt-field{width:210px;} .opt-field label{display:block; font-size:var(--fs-xs); color:var(--muted); margin-bottom:5px;}
+
+/* ---- 从库存批量挑货(出库)---- */
+.pick-head, .pick-row{display:grid; grid-template-columns:28px minmax(0,1fr) 86px 84px 92px; gap:10px; align-items:center;}
+.pick-head{padding:9px 12px; background:var(--th-bg); font-size:var(--fs-sm); color:var(--muted); font-weight:600; border:1px solid var(--border); border-bottom:none; border-radius:var(--r-md) var(--r-md) 0 0;}
+.pick-list{border:1px solid var(--border); border-radius:0 0 var(--r-md) var(--r-md); max-height:46vh; overflow-y:auto;}
+.pick-row{padding:9px 12px; border-bottom:1px solid var(--border-subtle); cursor:pointer; font-size:var(--fs-body);}
+.pick-row:last-child{border-bottom:none;}
+.pick-row:hover{background:var(--row-hover);}
+.pick-row.on{background:var(--info-soft);}
+.pick-row .ck{width:18px; height:18px; border:1.5px solid var(--border); border-radius:5px; display:flex; align-items:center; justify-content:center; color:#fff;}
+.pick-row.on .ck{background:var(--primary); border-color:var(--primary);}
+.pick-row .pn{font-weight:600; color:var(--heading);}
+.pick-row .ps{color:var(--muted); font-size:var(--fs-sm);}
+.pick-row .pm{text-align:right; font-family:var(--font-mono); color:var(--text);}
+.pick-row .pq{text-align:right; font-family:var(--font-mono); font-weight:600;}
+
+/* ---- 状态徽章补全(兑换券 / 盘点 / 账单结清,对齐 schema 真实枚举)---- */
+.b-未使用{background:var(--info-soft); color:var(--primary);}
+.b-已兑换{background:var(--success-bg); color:var(--success);}
+.b-作废{background:var(--bg); color:var(--muted);}
+.b-进行中{background:var(--warn-bg); color:var(--warn);}
+.b-已完成{background:var(--success-bg); color:var(--success);}
+.b-未结清{background:var(--warn-bg); color:var(--warn);}
+.b-已结清{background:var(--success-bg); color:var(--success);}
+
+/* ============================================================
+ app-shell(顶栏 + 侧栏)—— 单一真源,shell.js 注入;所有管理屏复用
+ ============================================================ */
+.top{height:56px; background:var(--top-bg); border-bottom:1px solid var(--top-border); display:flex; align-items:center; gap:16px; padding:0 18px; flex:none; z-index:var(--z-header); transition:background-color .28s,border-color .28s;}
+.brand-prod{display:flex; align-items:center; gap:7px;}
+.brand-prod .prod-mk{width:30px; height:30px; display:flex; align-items:center; justify-content:center; flex:none;}
+.brand-prod .prod-mk svg{width:30px; height:30px;}
+.brand-prod .prod-nm{font-weight:700; font-size:var(--fs-h2); color:var(--top-fg); letter-spacing:.3px; white-space:nowrap;}
+.brand-sep{width:1px; height:20px; background:var(--top-border); flex:none; margin:0 8px;}
+.logo{display:flex; align-items:center; gap:9px; font-weight:700; font-size:var(--fs-body); color:var(--top-fg);}
+.logo .mk{width:26px; height:26px; border-radius:8px; background:var(--top-mark-bg); color:var(--top-mark-fg); display:flex; align-items:center; justify-content:center; font-weight:800; font-size:var(--fs-body);}
+.logo .shop-nm{font-weight:600;}
+.gsearch{flex:1; max-width:400px; height:36px; background:var(--top-control); border:1px solid var(--top-border); border-radius:var(--r-md); display:flex; align-items:center; gap:8px; padding:0 12px; color:var(--top-faint); font-size:var(--fs-body); cursor:text;}
+.gsearch svg{stroke-width:2; color:var(--top-faint); flex:none;}
+.top-right{margin-left:auto; display:flex; align-items:center; gap:14px; color:var(--top-fg);}
+.theme-pill{display:flex; align-items:center; gap:7px; height:30px; padding:0 11px; border:1px solid var(--top-border); border-radius:var(--r-pill); font-size:var(--fs-sm); color:var(--top-fg); background:var(--top-control); cursor:pointer;}
+.theme-pill .dot{width:11px; height:11px; border-radius:50%; background:var(--top-fg);}
+.theme-pill .tp-shirt{color:var(--side-fg); flex:none;}
+.icon-btn{width:34px; height:34px; border-radius:8px; display:flex; align-items:center; justify-content:center; color:var(--top-fg); cursor:pointer;}
+.icon-btn:hover{background:var(--top-control);}
+
+.body{flex:1; display:flex; min-height:0;}
+.side{width:218px; background:var(--side-bg); border-right:1px solid var(--side-border); padding:12px 12px 0; display:flex; flex-direction:column; flex:none; transition:background-color .28s,border-color .28s;}
+.nav-label{font-size:var(--fs-xs); color:var(--side-muted); font-weight:600; letter-spacing:.6px; padding:10px 10px 6px;}
+.nav{display:flex; align-items:center; gap:11px; height:40px; padding:0 11px; border-radius:var(--r-md); color:var(--side-fg); font-size:var(--fs-body); font-weight:500; margin-bottom:2px; cursor:pointer; text-decoration:none; transition:background-color .15s,color .15s;}
+.nav svg{width:19px; height:19px; flex:none; stroke-width:1.7;}
+.nav:hover{background:var(--side-hover); color:var(--side-active-fg);}
+.nav.active{background:var(--side-active-bg); color:var(--side-active-fg); font-weight:600;}
+.side-bottom{margin-top:auto; padding-bottom:8px;}
+.side-div{height:1px; background:var(--side-border); margin:8px 4px;}
+.side-user{display:flex; align-items:center; gap:10px; padding:9px 10px; border-radius:var(--r-md); cursor:pointer; transition:background-color .15s;}
+.side-user:hover{background:var(--side-hover);}
+.su-pic{width:34px; height:34px; border-radius:50%; background:var(--side-active-bg); color:var(--side-active-fg); display:flex; align-items:center; justify-content:center; font-weight:700; font-size:14px; flex:none;}
+.su-who{flex:1; min-width:0; line-height:1.3;}
+.su-who b{display:block; color:var(--side-active-fg); font-size:var(--fs-body); font-weight:600;}
+.su-who span{color:var(--side-muted); font-size:var(--fs-xs);}
+.su-caret{width:14px; height:14px; color:var(--side-muted); flex:none;}
+.main{flex:1; min-width:0; overflow:auto; padding:22px 26px;}
+/* 侧栏右侧内容列:主区 + 底部状态栏(状态栏不贯穿侧栏) */
+.content-col{flex:1; min-width:0; display:flex; flex-direction:column;}
+.content-col > .main, .content-col > .work{flex:1; min-height:0;}
diff --git a/.superpowers/prototype/index.html b/.superpowers/prototype/index.html
new file mode 100644
index 0000000..77fbdcc
--- /dev/null
+++ b/.superpowers/prototype/index.html
@@ -0,0 +1,544 @@
+
+
+
+
+
+岩美酒库 · 设计系统
+
+
+
+
+
+
+
+
岩岩美酒库
+
设计系统 · Phase 0
+
+
+
+
+
+
+
+
+
+
+
+
+ UI Kit · 桌面屏
由原子组合而成的完整屏,点击进入可交互页面(随主题换色)。桌面与移动共用同一套 tokens + 公用原子,仅外壳/布局不同。
+
+
+
+
+
+ UI Kit · 移动屏
移动端外壳:底部 tab 栏 / 顶栏 / 底部 sheet。窄窗或设备模式(⌘⌥M · 390px)查看最佳;宽屏会自动居中成手机列。
+
+
+
+
+ 岩美酒库 · 设计系统
+ 三层方法:① 令牌层(tokens.css,单源,按主题换色)→ ② 组件层(atoms.css,所有屏复用)→ ③ 页面层(screens/,只引用前两层)。先定底层原子,再组屏。右上角切 A/B/C 三主题,全站实时换色——这就是「改一处、全局生效」。
+
+
+
⛔ 设计系统铁律(单一真源)
single source of truth
+
+
1. 唯一来源。所有页面用到的元素、组件、CSS 样式、颜色,必须出自本设计系统——颜色/间距/圆角/字号取自 tokens.css 的 CSS 变量,组件类取自 atoms.css,且都能在本页查到实样。
+
2. 先登记、后使用。需要新元素/组件/样式/颜色时,先加到本设计系统(变量进 tokens.css、组件类进 atoms.css)并在本页登记展示,之后才能在 screens/ 里引用。
+
3. 禁止系统外内容。页面里不允许出现本系统不存在的类、硬编码颜色(#hex/rgb(),品牌 logo SVG 除外)、或一次性写死的内联尺寸/配色。screens/* 只 import tokens.css + atoms.css,只用已登记的变量与类。
+
违反任意一条即视为未完成——颜色不走 token、用了本页没有的组件、页面自带样式,都要先回到这里补登记。
+
+
+
+ 结构
tokens.css · atoms.css · screens/*
+
+
tokens.css — 三主题色阶 + 间距/圆角/阴影/字号(令牌层)
+
atoms.css — 按钮·输入·Chip·徽章·KPI·表格·弹窗·抽屉·菜单(组件层)
+
screens/inventory.html — 库存屏(页面层,import 上面两层)
+
+
+
+
+
+ 品牌 Brand
三级品牌架构:公司 岩美科技 → 产品 岩美酒库 → 门店租户。下方为基于现有 logo 精修的标识系统。品牌色固定,不随界面 A/B/C 主题变化(仅提供反白变体)。
+
+
+ 品牌架构
公司 / 产品 / 门店 三级
+
+
公司 COMPANY
岩美科技
软件厂商 · 官网/宣传/about · 企业 logo + 商标
+
产品 PRODUCT
岩美酒库
SaaS 软件 · 窗口标题/登录/App 图标
+
门店 TENANT
鼎晟酒行 …
购买软件的 toB 商店 · App 内左上角,每户自有名+标记
+
+
+ 企业 Logo · 现状(精修参考)
现有标记 · 含商标 ™
+
+
+
+
+ 门店标识 · 自动字号
取门店名首字 · App 内左上角
+
+
+ 标记变体 / App 图标
主体 / 反白 / 单色 · 圆角图标
+
+
+ 安全区 / 最小尺寸
留白 = 标记高度的 1/3 · 最小 20px
+
+
+
+
+ 语义状态色
在售 / 预警 / 缺货 等状态。前景色与浅底成对。
+ 中性 / 文本 / 描边
背景层级、文本层级与分隔线。
+ 导航 Chrome
顶栏与侧栏专属角色(与内容 surface 分离,故每主题可独立染色)。
+
+
+ 字体 Type
正文/中文系统字体;数据用等宽(库存数、金额)。下面是字号梯度。
+ 字号梯度
--fs-display … --fs-xs
+
+
--fs-display 28岩美酒库 Display
+
--fs-h1 21页面标题 库存管理
+
--fs-h2 17区块标题
+
--fs-title 15卡片标题
+
--fs-body 13正文与表格内容 · 茅台飞天 53°
+
--fs-sm 12次要说明 / 标签
+
mono¥2,640,000 · 1,284 SKU
+
+
+
+
+ 间距 Spacing
圆角 / 间距网格 / 阴影系统,全主题统一。
+
+
+ 阴影
--sh-1 / --sh-2 / --sh-3
+
+
+
+ 公用组件 Components
来自 atoms.css 的原子,桌面与移动共用(按钮 / 输入 / 徽章 / 分段 / 菜单 / 弹窗等)。随主题换色。移动专属原子见下一节。
+ 按钮
.btn .primary/.ghost/.danger/.sm
+ 输入 / 搜索
.input .searchbox .ctrl
+ 分段切换 / 可搜索选择器
.seg · .combo(录单网格用)
+ 搜索下拉框(弹层)
.combo + .combo-pop > .cp-search / .cp-body(单据头供应商/客户/仓库)
+
+
+
+
+
茅台华东总代GYS002
+
名烟名酒城GYS004
+
华润万家配送GYS005
+
新增「mt」到基础数据
+
+
+ 搜索下拉框 · 带结果统计
data-summary="1" → .combo-pop > … + .cp-foot(内容多时启用,底部显示总返回条数)
+
+
+
+
+
鼎晟供应链GYS001
+
茅台华东总代GYS002
+
川酒集团批发GYS003
+
名烟名酒城GYS004
+
华润万家配送GYS005
+
+
+
+
+ 日期字段(滚轮选择)
.datefield + .wheel-pop(年月日 · 单据/生产共用)· datewheel.js
+
+ 状态徽章 · 全部
.badge .b-* · 对齐 schema 真实枚举
+
+
库存 · 按 min_stock 派生在售预警缺货
+
单据 · 入库/出库草稿待审核已通过已驳回
+
往来单位 · 启用停用启用停用供应商客户两者
+
用户角色管理员普通只读
+
设备在线状态在线离线
+
库存盘点进行中已完成
+
授权兑换券未使用已兑换作废
+
账单结清 · 收付未结清已结清收款付款
+
进价待定 · 调货暂估待定价待定价(入库进价留空/0,确认进价后回填)
+
+
+
+ 表格行
.table thead/tbody
+
| 商品 | 分类 | 库存 | 状态 |
+ 茅台 飞天 53° MT-FT-500 | 酱香 | 128 | 在售 |
+ 剑南春 水晶剑 JNC-SJ-500 | 浓香 | 8 | 缺货 |
+
+
+ 提示条
.notice info/danger(草稿恢复 / 驳回)
+
检测到未提交的入库单草稿(06-21 11:20),是否恢复?
恢复放弃
+
+
+ 单据明细行(商品 / 系列规格分列)
.lines .lr · .lcell .ln/.lcode/.lser/.lspec
+
商品 · 编码系列 / 规格数量单价金额
+
泸州老窖 特曲LZLJ-TQ-52-500特曲52° 500ml×648¥320¥15,360
+
+
+ 抽屉操作分组
.dactions · .dact-group/.dact-label/.dact-row(审核类 / 打印类)
+ 覆盖层(交互)
.menu · .overlay/.modal · .drawer · .toast
+
下拉菜单
+
弹窗
+
抽屉
+
Toast
+
+ 分页 / 移动卡 / 状态栏 / 通知 / 遮罩
.pager · .mcard · .statusbar · .notify-pop · .drawer-mask (列表/壳层原子)
+
+
+
+
+
通知中心列表行 .nt-item / 类型图标 .nt-ic(warn / danger / info / ok)—— 通知列表页与详情页共用,桌面/移动一致:
+
+
遮罩 .drawer-mask:抽屉/弹层背后的半透明背板,点击关闭。
+
+
+
+
+
+ 移动专属组件 .m-*
来自 mobile-atoms.css 的移动专属原子,由 mobile-shell.js 装配外壳。复用上方公用原子(按钮/徽章/分段/输入),此处只展示移动新增的 .m-*。
+
+ 移动壳 + 列表(实机预览)
.m-top · .m-tabbar · .m-page · .m-kpi · .m-search · .m-filters/.m-pill · .m-card/.m-row · .m-fab
+
+
+
+
+
.m-form · .m-field · .m-input · .m-select
+
+
+
.m-sheet-h · .m-opt(底部 sheet 内容)
+
+
+
+
+
.m-actionbar(全屏表单底部操作条)
+
+
+
+
+
+
+
+ 图标 Icons
全部 92 个图标登记在 screens/icons.js 的 SVG sprite(单一真源)。各屏用 <svg><use href="#i-xxx"/></svg> 引用——改一处、全局生效。新增图标先在此登记 symbol,再用 use 引用;门禁 ❽ 禁止内联图标。
+ 图标库 · 92 个
icons.js <symbol> · <use href="#i-*">i-arrow-left
i-bottle
i-box
i-box-2
i-check
i-chevron-down
i-chevron-left
i-chevron-right
i-close
i-download
i-edit
i-eye
i-gear
i-megaphone
i-menu
i-plus
i-refresh
i-search
i-shield
i-shield-2
i-upload
i-bug
i-lightbulb
i-ic01
i-ic02
i-ic03
i-ic04
i-ic05
i-ic06
i-ic07
i-ic08
i-ic09
i-ic10
i-ic11
i-ic12
i-ic13
i-ic14
i-ic15
i-ic16
i-ic17
i-ic18
i-ic19
i-ic20
i-ic21
i-ic22
i-ic23
i-ic24
i-ic25
i-ic26
i-ic27
i-ic28
i-ic29
i-ic30
i-ic31
i-ic32
i-ic33
i-ic34
i-ic35
i-ic36
i-ic37
i-ic38
i-ic39
i-ic40
i-ic41
i-ic42
i-ic43
i-ic44
i-ic45
i-ic46
i-ic47
i-ic48
i-ic49
i-ic50
i-ic51
i-ic52
i-ic53
i-ic54
i-ic55
i-ic56
i-ic57
i-ic58
i-ic59
i-ic60
i-ic61
i-ic62
i-ic63
i-ic64
i-ic65
i-ic66
i-ic67
i-ic68
i-ic69
+
+
+
+
+
+ 弹窗示例
这是 .modal 原子。库存的「新增入库」即基于它。
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/mobile-atoms.css b/.superpowers/prototype/mobile-atoms.css
new file mode 100644
index 0000000..a11e262
--- /dev/null
+++ b/.superpowers/prototype/mobile-atoms.css
@@ -0,0 +1,127 @@
+/* ============================================================
+ 移动端原子层 .m-* —— 复用 tokens.css(颜色/3主题/间距/圆角/字号),
+ 仅新增移动专属组件(顶栏 / 底 tab / 页面骨架 / KPI / 列表卡 / sheet / 表单 / 搜索 / FAB / 筛选 pill)。
+ 单一真源:颜色一律走 var(--*),禁硬编码。kit 展示见 mobile-kit.html。
+ ============================================================ */
+
+/* ---- 移动壳:顶部 app 栏 ---- */
+.m-top{position:fixed; top:0; left:0; right:0; height:52px; display:flex; align-items:center; gap:10px; padding:0 14px; background:var(--top-bg); color:var(--top-fg); z-index:var(--z-header);}
+.m-top .m-back{width:30px; height:30px; margin-left:-6px; display:flex; align-items:center; justify-content:center; color:var(--top-fg); cursor:pointer;}
+.m-top .m-back svg{width:22px; height:22px; stroke-width:2;}
+.m-top .m-title{font-size:var(--fs-title); font-weight:700; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
+.m-top .m-actions{margin-left:auto; display:flex; align-items:center; gap:13px;}
+.m-top .m-ic{width:24px; height:24px; display:flex; align-items:center; justify-content:center; color:var(--top-fg); position:relative; cursor:pointer;}
+.m-top .m-ic svg{width:20px; height:20px; stroke-width:1.9;}
+.m-top .m-dot{position:absolute; top:-2px; right:-2px; min-width:15px; height:15px; padding:0 3px; box-sizing:border-box; border-radius:var(--r-pill); background:var(--danger); color:#fff; font-size:var(--fs-xs); display:flex; align-items:center; justify-content:center;}
+.m-top .m-av{width:28px; height:28px; border-radius:50%; background:var(--brand-mid); color:#fff; display:flex; align-items:center; justify-content:center; font-weight:700; font-size:var(--fs-sm); cursor:pointer;}
+
+/* ---- 移动壳:底部 tab 栏 ---- */
+.m-tabbar{position:fixed; bottom:0; left:0; right:0; min-height:56px; display:flex; background:var(--surface); border-top:1px solid var(--border); z-index:var(--z-header); padding-bottom:env(safe-area-inset-bottom);}
+.m-tab{flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px; padding:8px 0; color:var(--muted); font-size:var(--fs-xs); text-decoration:none; cursor:pointer;}
+.m-tab svg{width:22px; height:22px; stroke-width:1.8;}
+.m-tab.on{color:var(--primary);}
+.m-tab.on svg{stroke-width:2;}
+
+/* ---- 页面骨架(固定顶 + 滚动体 + 固定底)---- */
+.m-page{min-height:100vh; background:var(--page); padding-top:52px; padding-bottom:64px; box-sizing:border-box;}
+.m-page.has-tabs{padding-bottom:calc(56px + env(safe-area-inset-bottom) + 8px);}
+.m-scroll{padding:14px;}
+
+/* ---- KPI 卡(2×2)---- */
+.m-kpi{display:grid; grid-template-columns:1fr 1fr; gap:10px;}
+.m-kpi .k{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); padding:12px 14px;}
+.m-kpi .k .kl{font-size:var(--fs-xs); color:var(--muted); display:flex; align-items:center; gap:5px;}
+.m-kpi .k .kl svg{width:13px; height:13px; stroke-width:1.9; color:var(--brand400);}
+.m-kpi .k .kv{font-size:var(--fs-h2); font-weight:800; color:var(--heading); margin-top:5px; letter-spacing:.2px;}
+.m-kpi .k .kd{font-size:var(--fs-xs); margin-top:3px; color:var(--muted);}
+.m-kpi .k .kd.up{color:var(--success);} .m-kpi .k .kd.down{color:var(--danger);} .m-kpi .k .kd.warn{color:var(--warn);}
+
+/* ---- 列表项卡片 ---- */
+.m-card{display:block; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); padding:13px 14px; margin-bottom:10px; text-decoration:none; color:inherit;}
+.m-card:active{border-color:var(--primary);}
+.m-row{display:flex; align-items:center; gap:12px;}
+.m-row .mc-main{min-width:0; flex:1;}
+.m-row .mc-nm{font-weight:600; color:var(--heading); font-size:var(--fs-body); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
+.m-row .mc-sub{font-size:var(--fs-xs); color:var(--muted); margin-top:3px; font-family:var(--font-mono); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
+.m-row .mc-right{display:flex; flex-direction:column; align-items:flex-end; gap:5px; flex:none;}
+.m-row .mc-amt{font-weight:700; color:var(--heading); font-size:var(--fs-body); font-family:var(--font-mono);}
+.m-row .mc-chev{color:var(--faint); flex:none;}
+.m-row .mc-chev svg{width:18px; height:18px; stroke-width:2;}
+.m-card .mc-foot{display:flex; align-items:center; gap:10px; margin-top:10px; padding-top:10px; border-top:1px solid var(--border-subtle); font-size:var(--fs-xs); color:var(--muted);}
+
+/* ---- 底部 sheet(筛选 / 选择 / 动作)---- */
+.m-sheet-mask{position:fixed; inset:0; background:var(--scrim); opacity:0; pointer-events:none; transition:opacity .25s; z-index:var(--z-drawer-scrim);}
+.m-sheet-mask.show{opacity:1; pointer-events:auto;}
+.m-sheet{position:fixed; left:0; right:0; bottom:0; background:var(--surface); border-radius:var(--r-xl) var(--r-xl) 0 0; transform:translateY(100%); transition:transform .26s; z-index:var(--z-drawer); max-height:86vh; display:flex; flex-direction:column; padding-bottom:env(safe-area-inset-bottom);}
+.m-sheet.show{transform:translateY(0);}
+.m-sheet .grip{width:36px; height:4px; border-radius:var(--r-pill); background:var(--border); margin:8px auto 2px;}
+.m-sheet-h{display:flex; align-items:center; padding:10px 16px 12px; border-bottom:1px solid var(--border-subtle);}
+.m-sheet-h h3{margin:0; font-size:var(--fs-title); color:var(--heading);}
+.m-sheet-h .x{margin-left:auto; width:22px; height:22px; color:var(--muted); cursor:pointer;}
+.m-sheet-b{padding:14px 16px; overflow:auto;}
+.m-opt{display:flex; align-items:center; gap:10px; padding:13px 4px; border-bottom:1px solid var(--border-subtle); font-size:var(--fs-body); color:var(--text); cursor:pointer;}
+.m-opt:last-child{border-bottom:none;}
+.m-opt .ck{margin-left:auto; color:var(--primary); width:18px; height:18px; opacity:0;}
+.m-opt.sel .ck{opacity:1;} .m-opt.sel{color:var(--primary); font-weight:600;}
+.m-opt .oic{width:20px; height:20px; color:var(--muted); flex:none;}
+
+/* ---- 移动表单(全宽堆叠,触控高度 44)---- */
+.m-form{display:block;}
+.m-field{margin-bottom:14px;}
+.m-field > label{display:block; font-size:var(--fs-sm); color:var(--muted); margin-bottom:6px; font-weight:600;}
+.m-input{width:100%; box-sizing:border-box; height:44px; border:1px solid var(--border); border-radius:var(--r-md); padding:0 12px; font-size:var(--fs-body); color:var(--text); background:var(--surface); outline:none; font-family:var(--font);}
+.m-input:focus{border-color:var(--primary);}
+textarea.m-input{height:auto; min-height:88px; padding:10px 12px; resize:vertical; line-height:1.7;}
+.m-select{display:flex; align-items:center; height:44px; padding:0 12px; border:1px solid var(--border); border-radius:var(--r-md); background:var(--surface); color:var(--text); font-size:var(--fs-body); cursor:pointer;}
+.m-select .ph{color:var(--faint);}
+.m-select svg{margin-left:auto; width:16px; height:16px; color:var(--muted);}
+
+/* ---- 移动搜索条 ---- */
+.m-search{display:flex; align-items:center; gap:8px; height:42px; padding:0 12px; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md);}
+.m-search svg{width:17px; height:17px; stroke-width:2; color:var(--muted); flex:none;}
+.m-search input{border:none; outline:none; background:transparent; flex:1; min-width:0; font-size:var(--fs-body); color:var(--text); font-family:var(--font);}
+
+/* ---- 横向筛选 pill 行 ---- */
+.m-filters{display:flex; gap:8px; overflow-x:auto; scrollbar-width:none; margin-top:10px;}
+.m-filters::-webkit-scrollbar{display:none;}
+.m-pill{flex:none; height:32px; padding:0 13px; border:1px solid var(--border); border-radius:var(--r-pill); background:var(--surface); color:var(--text); font-size:var(--fs-sm); display:inline-flex; align-items:center; gap:5px; cursor:pointer;}
+.m-pill svg{width:13px; height:13px; stroke-width:2;}
+.m-pill.on{background:var(--brand50); border-color:var(--primary); color:var(--primary); font-weight:600;}
+
+/* ---- 悬浮新增 FAB ---- */
+.m-fab{position:fixed; right:16px; bottom:74px; width:52px; height:52px; border-radius:50%; background:var(--primary); color:var(--on-primary); display:flex; align-items:center; justify-content:center; box-shadow:var(--sh-3); z-index:var(--z-fab); cursor:pointer;}
+.m-fab svg{width:24px; height:24px; stroke-width:2.2;}
+
+/* ---- 返回设计系统 悬浮球(左下,低调)---- */
+/* 调试用「返回设计系统」球:默认右下,宽屏时移到手机框外的视口右下角(真实 app 不含此球) */
+.m-ds-fab{position:fixed; left:auto; right:16px; bottom:134px; width:44px; height:44px; border-radius:50%; background:var(--surface); color:var(--muted); border:1px solid var(--border); display:flex; align-items:center; justify-content:center; box-shadow:var(--sh-2); z-index:var(--z-fab); opacity:.9; text-decoration:none;}
+.m-ds-fab:active{color:var(--primary); border-color:var(--primary);}
+.m-ds-fab svg{width:20px; height:20px; stroke-width:2;}
+
+/* ---- 分组标题 / 空态 ---- */
+.m-section{font-size:var(--fs-sm); font-weight:700; color:var(--muted); margin:16px 2px 8px; letter-spacing:.4px;}
+.m-empty{text-align:center; color:var(--muted); padding:48px 16px; font-size:var(--fs-sm);}
+.m-empty svg{width:38px; height:38px; stroke-width:1.4; color:var(--faint); display:block; margin:0 auto 10px;}
+
+/* ---- 底部固定操作条(表单提交)---- */
+.m-actionbar{position:fixed; left:0; right:0; bottom:0; display:flex; gap:10px; padding:12px 14px; background:var(--surface); border-top:1px solid var(--border); z-index:var(--z-header); padding-bottom:calc(12px + env(safe-area-inset-bottom));}
+.m-actionbar .btn{flex:1; justify-content:center;}
+
+/* ---- 我的:hub 列表行 ---- */
+.m-hub{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); overflow:hidden;}
+.m-hub a{display:flex; align-items:center; gap:12px; padding:14px; border-bottom:1px solid var(--border-subtle); text-decoration:none; color:var(--text); font-size:var(--fs-body);}
+.m-hub a:last-child{border-bottom:none;}
+.m-hub a .hic{width:34px; height:34px; border-radius:var(--r-md); background:var(--brand50); color:var(--primary); display:flex; align-items:center; justify-content:center; flex:none;}
+.m-hub a .hic svg{width:18px; height:18px; stroke-width:1.8;}
+.m-hub a .hchev{margin-left:auto; color:var(--faint);}
+.m-hub a .hchev svg{width:18px; height:18px; stroke-width:2;}
+
+/* ---- 桌面预览:窄屏(<600=真机)全宽;宽屏(≥600)整套居中成 430px 手机列,不拉伸 ---- */
+@media (min-width:600px){
+ .m-top, .m-tabbar, .m-actionbar{ left:50%; right:auto; transform:translateX(-50%); width:100%; max-width:430px; }
+ .m-page{ max-width:430px; margin:0 auto; min-height:100vh; box-shadow:var(--sh-3); }
+ .m-fab{ right:auto; left:50%; transform:translateX(147px); }
+ .m-ds-fab{ left:auto; right:24px; bottom:24px; transform:none; }
+ .m-sheet{ left:50%; right:auto; max-width:430px; width:100%; transform:translateX(-50%) translateY(100%); }
+ .m-sheet.show{ transform:translateX(-50%) translateY(0); }
+}
diff --git a/.superpowers/prototype/screens/about-preview.html b/.superpowers/prototype/screens/about-preview.html
new file mode 100644
index 0000000..d597d1b
--- /dev/null
+++ b/.superpowers/prototype/screens/about-preview.html
@@ -0,0 +1,236 @@
+
+
+
+
+
+关于我们 · 5 套设计预览 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+ 关于我们 · 5 套设计预览:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/about.html b/.superpowers/prototype/screens/about.html
new file mode 100644
index 0000000..844ce11
--- /dev/null
+++ b/.superpowers/prototype/screens/about.html
@@ -0,0 +1,227 @@
+
+
+
+
+
+关于我们 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
岩美酒库
酒水进销存 · 财务对账 · 防伪溯源一体化 · 岩美科技出品
+
+
+
+
官网›
+
微信客服›
+
隐私政策›
+
服务条款›
+
+
+
+
+
+
+
+
产品信息
+
+
+
+
多端覆盖
Web · macOS · Windows · iOS · 安卓
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
问题反馈
+
+
+
功能建议
+
使用问题
+
Bug 报告
+
其他
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/checkout.html b/.superpowers/prototype/screens/checkout.html
new file mode 100644
index 0000000..a5c2054
--- /dev/null
+++ b/.superpowers/prototype/screens/checkout.html
@@ -0,0 +1,198 @@
+
+
+
+
+
+确认订单 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
确认订单
+
+
+
+
+
订单内容
+
+
+
标准版授权365 天授权 · 时长兑换券
+
¥888
+
+
+
+
+
+
+
付款方式
+
+
+
+
对公转账下单后顾问提供收款账户与合同
+
+
+
+
+
微信 / 支付宝即将上线在线即时到账
+
+
+
+
+
+
+
+
+
费用汇总
+
标准版授权¥888
+
优惠- ¥0
+
+
应付总额¥888
+
提交订单
+
提交即生成待付款订单,支付完成后自动下发时长兑换券短码。当前为原型,未接入真实支付。
+
+
+
+
+
+
+
+
订单已提交
+
顾问将在 1 个工作日内与你对接收款与开票。
收款确认后,时长兑换券短码会下发到你的账号,在「系统设置 · 授权兑换券」激活即可续期。
+
订单号 —
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/datewheel.js b/.superpowers/prototype/screens/datewheel.js
new file mode 100644
index 0000000..1ec91a8
--- /dev/null
+++ b/.superpowers/prototype/screens/datewheel.js
@@ -0,0 +1,47 @@
+// 滚轮日期选择器 —— 年月日,单据日期/生产日期/明细共用(单一真源)
+// 用法:在任意 .datefield 元素上点击(或聚焦后按 Enter/空格)打开滚轮;
+// 元素持有 data-val="YYYY-MM-DD";选定后写回 data-val + .dval 文本,并派发 'datechange' 事件(detail.value)。
+// 三列滚轮(年/月/日)滚动吸附,年/月变化自动重算当月天数。
+(function(){
+ const ITEM=36;
+ const now=new Date();
+ const YEARS=[]; for(let y=now.getFullYear()-15;y<=now.getFullYear()+2;y++) YEARS.push(y);
+ let pop, yCol, mCol, dCol, target=null, building=false;
+
+ const pad=n=>String(n).padStart(2,'0');
+ const dim=(y,m)=>new Date(y,m,0).getDate(); // m: 1-12
+
+ function ensure(){
+ if(pop) return;
+ pop=document.createElement('div'); pop.className='wheel-pop';
+ pop.innerHTML=''
+ +'';
+ document.body.appendChild(pop);
+ yCol=pop.querySelector('[data-k="y"]'); mCol=pop.querySelector('[data-k="m"]'); dCol=pop.querySelector('[data-k="d"]');
+ [yCol,mCol,dCol].forEach(col=>{
+ let t; col.addEventListener('scroll',()=>{ if(building) return; markOn(col); clearTimeout(t); t=setTimeout(()=>{ if(col.dataset.k!=='d') rebuildDays(); },110); });
+ });
+ pop.querySelector('.wheel-today').onclick=()=>setSel(now.getFullYear(),now.getMonth()+1,now.getDate());
+ pop.querySelector('.wheel-ok').onclick=commit;
+ document.addEventListener('mousedown',e=>{ if(pop.classList.contains('show') && !pop.contains(e.target) && e.target!==target && !(target&&target.contains(e.target))) close(); });
+ window.addEventListener('resize',close);
+ }
+ const items=col=>col.querySelectorAll('.wheel-item');
+ const idxOf=col=>{ const n=items(col).length; return Math.max(0,Math.min(n-1,Math.round(col.scrollTop/ITEM))); };
+ function markOn(col){ const idx=idxOf(col); items(col).forEach((el,i)=>el.classList.toggle('on',i===idx)); }
+ function fill(col,arr,selIdx){ building=true; col.innerHTML=arr.map(t=>`${t}
`).join(''); col.scrollTop=selIdx*ITEM; markOn(col); requestAnimationFrame(()=>{ building=false; }); }
+ function rebuildDays(){ const y=YEARS[idxOf(yCol)], m=idxOf(mCol)+1, n=dim(y,m); const cur=Math.min(idxOf(dCol),n-1); fill(dCol,Array.from({length:n},(_,i)=>pad(i+1)),cur); }
+ function setSel(y,m,d){ const yi=Math.max(0,YEARS.indexOf(y)); fill(yCol,YEARS.map(String),yi); fill(mCol,Array.from({length:12},(_,i)=>pad(i+1)),m-1); const n=dim(y,m); fill(dCol,Array.from({length:n},(_,i)=>pad(i+1)),Math.min(d-1,n-1)); }
+
+ function position(el){ const r=el.getBoundingClientRect(); const w=248; pop.style.width=w+'px'; pop.style.left=Math.max(8,Math.min(r.left,window.innerWidth-w-8))+'px'; pop.style.top=(r.bottom+4)+'px'; const ph=pop.getBoundingClientRect(); if(r.bottom+ph.height+8>window.innerHeight && r.top-ph.height-4>0) pop.style.top=(r.top-ph.height-4)+'px'; }
+ function open(el){ ensure(); target=el; let y,m,d; const v=el.dataset.val; if(v && /^\d{4}-\d{2}-\d{2}$/.test(v)){ [y,m,d]=v.split('-').map(Number); } else { y=now.getFullYear(); m=now.getMonth()+1; d=now.getDate(); }
+ pop.classList.add('show'); position(el); // 先显示获得布局,scrollTop 才能生效
+ setSel(y,m,d); }
+ function close(){ if(pop) pop.classList.remove('show'); target=null; }
+ function commit(){ if(!target) return; const y=YEARS[idxOf(yCol)], m=idxOf(mCol)+1, d=idxOf(dCol)+1; const val=y+'-'+pad(m)+'-'+pad(d); target.dataset.val=val; const dv=target.querySelector('.dval'); if(dv){ dv.textContent=val; dv.classList.remove('ph'); } target.classList.remove('error'); target.dispatchEvent(new CustomEvent('datechange',{bubbles:true,detail:{value:val}})); close(); }
+
+ document.addEventListener('click',e=>{ const f=e.target.closest('.datefield'); if(f && !f.classList.contains('disabled')){ if(pop && pop.classList.contains('show') && target===f){ close(); return; } open(f); } });
+ document.addEventListener('keydown',e=>{ const f=e.target.closest && e.target.closest('.datefield'); if(f && (e.key==='Enter'||e.key===' ') && !f.classList.contains('disabled')){ e.preventDefault(); open(f); } });
+ window.DateWheel={ open, close };
+})();
diff --git a/.superpowers/prototype/screens/design-system.html b/.superpowers/prototype/screens/design-system.html
new file mode 100644
index 0000000..b2e37b0
--- /dev/null
+++ b/.superpowers/prototype/screens/design-system.html
@@ -0,0 +1,10 @@
+
+
+
+
+组件总览已并入设计系统首页
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/devices.html b/.superpowers/prototype/screens/devices.html
new file mode 100644
index 0000000..bd4b87f
--- /dev/null
+++ b/.superpowers/prototype/screens/devices.html
@@ -0,0 +1,222 @@
+
+
+
+
+
+设备管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
登录设备管理 管理员可强制下线其他登录设备
+
+ | 用户 | 平台 | 设备 | IP | 登录时间 | 最近活跃 | 状态 | 操作 |
+
+
+
+
外设设备
+
+
+
打印模板 标签与小票排版
+
+
+
+
+
标签模板 · 商品价签
+
40×30mm · 品名 / 规格 / 条码 / 零售价
+
+
+
+
+
+
+
小票模板 · 出库单据
+
58mm 热敏 · 抬头 / 明细 / 合计 / 经手人
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/fab.js b/.superpowers/prototype/screens/fab.js
new file mode 100644
index 0000000..9bab6ba
--- /dev/null
+++ b/.superpowers/prototype/screens/fab.js
@@ -0,0 +1,60 @@
+// 设计期返回悬浮球 —— 可拖动 + 抓取光标(单一真源,所有屏共用)
+// 行为:hover 显示 grab 光标;按下拖动显示 grabbing;松手记忆位置(localStorage)。
+// 仅当发生真实拖动时拦截那次点击,避免拖完误触跳转;纯点击仍正常返回设计系统。
+(function(){
+ function initFab(){
+ var fab=document.querySelector('.ds-fab');
+ if(!fab||fab.dataset.drag)return;
+ fab.dataset.drag='1';
+ fab.style.cursor='grab';
+ fab.style.touchAction='none';
+ var dragging=false, moved=false, startX=0, startY=0, ox=0, oy=0;
+
+ function clamp(x,y){
+ var w=fab.offsetWidth, h=fab.offsetHeight;
+ x=Math.max(6, Math.min(window.innerWidth-w-6, x));
+ y=Math.max(6, Math.min(window.innerHeight-h-6, y));
+ return [x,y];
+ }
+ function place(x,y){
+ var c=clamp(x,y);
+ fab.style.left=c[0]+'px'; fab.style.top=c[1]+'px';
+ fab.style.right='auto'; fab.style.bottom='auto';
+ }
+ // 恢复上次拖到的位置
+ try{ var sv=JSON.parse(localStorage.getItem('jiu-fab-pos')||'null'); if(sv&&typeof sv.x==='number') place(sv.x,sv.y); }catch(e){}
+
+ fab.addEventListener('pointerdown',function(e){
+ dragging=true; moved=false;
+ startX=e.clientX; startY=e.clientY;
+ var r=fab.getBoundingClientRect();
+ ox=e.clientX-r.left; oy=e.clientY-r.top;
+ try{ fab.setPointerCapture(e.pointerId); }catch(_){}
+ fab.style.cursor='grabbing';
+ fab.style.transition='none';
+ e.preventDefault();
+ });
+ fab.addEventListener('pointermove',function(e){
+ if(!dragging)return;
+ if(!moved && Math.hypot(e.clientX-startX, e.clientY-startY)>4) moved=true;
+ if(moved) place(e.clientX-ox, e.clientY-oy);
+ });
+ function end(e){
+ if(!dragging)return;
+ dragging=false;
+ fab.style.cursor='grab';
+ fab.style.transition='';
+ try{ fab.releasePointerCapture(e.pointerId); }catch(_){}
+ if(moved){
+ var r=fab.getBoundingClientRect();
+ try{ localStorage.setItem('jiu-fab-pos',JSON.stringify({x:r.left,y:r.top})); }catch(_){}
+ // 拖动结束后拦截紧随的 click,避免误跳转
+ fab.addEventListener('click',function ph(ev){ ev.preventDefault(); ev.stopPropagation(); fab.removeEventListener('click',ph,true); },true);
+ }
+ }
+ fab.addEventListener('pointerup',end);
+ fab.addEventListener('pointercancel',end);
+ }
+ if(document.readyState!=='loading') initFab();
+ else document.addEventListener('DOMContentLoaded',initFab);
+})();
diff --git a/.superpowers/prototype/screens/finance.html b/.superpowers/prototype/screens/finance.html
new file mode 100644
index 0000000..516b653
--- /dev/null
+++ b/.superpowers/prototype/screens/finance.html
@@ -0,0 +1,254 @@
+
+
+
+
+
+财务管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
应收 / 应付 汇总
按往来单位 · 点击行查看流水
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/icons.js b/.superpowers/prototype/screens/icons.js
new file mode 100644
index 0000000..7d784f0
--- /dev/null
+++ b/.superpowers/prototype/screens/icons.js
@@ -0,0 +1,8 @@
+// 图标 sprite —— 单一真源(Lucide 官方图标,ISC 许可)
+// 所有屏用 引用;改此处一个 symbol,全局生效。
+// 共 97 个,源自 lucide-static。新增图标先在此登记 symbol,再用 use 引用。
+(function(){
+ var SPRITE='';
+ function inject(){ if(document.getElementById('ym-icon-sprite'))return; var d=document.createElement('div'); d.id='ym-icon-sprite'; d.style.cssText='position:absolute;width:0;height:0'; d.innerHTML=SPRITE; document.body.insertBefore(d,document.body.firstChild); }
+ if(document.readyState!=='loading')inject(); else document.addEventListener('DOMContentLoaded',inject);
+})();
diff --git a/.superpowers/prototype/screens/inventory-check.html b/.superpowers/prototype/screens/inventory-check.html
new file mode 100644
index 0000000..cf9281e
--- /dev/null
+++ b/.superpowers/prototype/screens/inventory-check.html
@@ -0,0 +1,204 @@
+
+
+
+
+
+库存盘点 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
库存管理
+
+
+
+
+
+
盘点明细
输入实盘数量自动计算差异 · 盈绿亏红
+
+
+
商品(名/编码)账面库存实盘数量差异备注
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/inventory.html b/.superpowers/prototype/screens/inventory.html
new file mode 100644
index 0000000..b0d43a9
--- /dev/null
+++ b/.superpowers/prototype/screens/inventory.html
@@ -0,0 +1,174 @@
+
+
+
+
+
+库存管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
库存管理
共 1,284 个 SKU · 数据实时
+
+
+
+
+
+
+
+
+
+
此页布局将在对应 Phase 细化 · 当前先验证导航与主题骨架
+
返回库存管理
+
+
+
+
+
+
+
新增入库单
+
+
+
+
+
+
添加商品
+
合计金额 ¥0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/login.html b/.superpowers/prototype/screens/login.html
new file mode 100644
index 0000000..2e0567b
--- /dev/null
+++ b/.superpowers/prototype/screens/login.html
@@ -0,0 +1,114 @@
+
+
+
+
+
+登录 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
岩美酒库
+
酒水进销存 · 审核驱动业务流
+
+
入库出库 · 审核后实时改库存
+
往来对账 · 财务一目了然
+
多端协同 · 多租户安全隔离
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-about.html b/.superpowers/prototype/screens/m-about.html
new file mode 100644
index 0000000..90e0778
--- /dev/null
+++ b/.superpowers/prototype/screens/m-about.html
@@ -0,0 +1,90 @@
+
+
+
+
+
+关于我们 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-devices.html b/.superpowers/prototype/screens/m-devices.html
new file mode 100644
index 0000000..50bed2b
--- /dev/null
+++ b/.superpowers/prototype/screens/m-devices.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+设备管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-finance.html b/.superpowers/prototype/screens/m-finance.html
new file mode 100644
index 0000000..b5cb254
--- /dev/null
+++ b/.superpowers/prototype/screens/m-finance.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+财务管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-inventory-check.html b/.superpowers/prototype/screens/m-inventory-check.html
new file mode 100644
index 0000000..970af97
--- /dev/null
+++ b/.superpowers/prototype/screens/m-inventory-check.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+库存盘点 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-inventory.html b/.superpowers/prototype/screens/m-inventory.html
new file mode 100644
index 0000000..c73d297
--- /dev/null
+++ b/.superpowers/prototype/screens/m-inventory.html
@@ -0,0 +1,105 @@
+
+
+
+
+
+库存 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-login.html b/.superpowers/prototype/screens/m-login.html
new file mode 100644
index 0000000..b8fc221
--- /dev/null
+++ b/.superpowers/prototype/screens/m-login.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+登录 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-me.html b/.superpowers/prototype/screens/m-me.html
new file mode 100644
index 0000000..db9edc5
--- /dev/null
+++ b/.superpowers/prototype/screens/m-me.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+我的 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-notification.html b/.superpowers/prototype/screens/m-notification.html
new file mode 100644
index 0000000..6942bad
--- /dev/null
+++ b/.superpowers/prototype/screens/m-notification.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+通知详情 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-notifications.html b/.superpowers/prototype/screens/m-notifications.html
new file mode 100644
index 0000000..b236ade
--- /dev/null
+++ b/.superpowers/prototype/screens/m-notifications.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+通知 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-partners.html b/.superpowers/prototype/screens/m-partners.html
new file mode 100644
index 0000000..a2c182d
--- /dev/null
+++ b/.superpowers/prototype/screens/m-partners.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+往来单位 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-products.html b/.superpowers/prototype/screens/m-products.html
new file mode 100644
index 0000000..d6f4cab
--- /dev/null
+++ b/.superpowers/prototype/screens/m-products.html
@@ -0,0 +1,119 @@
+
+
+
+
+
+基础数据 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-register.html b/.superpowers/prototype/screens/m-register.html
new file mode 100644
index 0000000..53bff2f
--- /dev/null
+++ b/.superpowers/prototype/screens/m-register.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+注册新门店 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-settings.html b/.superpowers/prototype/screens/m-settings.html
new file mode 100644
index 0000000..4d719a2
--- /dev/null
+++ b/.superpowers/prototype/screens/m-settings.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+系统设置 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-stock-in-list.html b/.superpowers/prototype/screens/m-stock-in-list.html
new file mode 100644
index 0000000..06e88cd
--- /dev/null
+++ b/.superpowers/prototype/screens/m-stock-in-list.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+入库管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-stock-in.html b/.superpowers/prototype/screens/m-stock-in.html
new file mode 100644
index 0000000..0183a3f
--- /dev/null
+++ b/.superpowers/prototype/screens/m-stock-in.html
@@ -0,0 +1,90 @@
+
+
+
+
+
+新增入库单 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-stock-out-list.html b/.superpowers/prototype/screens/m-stock-out-list.html
new file mode 100644
index 0000000..6c0142a
--- /dev/null
+++ b/.superpowers/prototype/screens/m-stock-out-list.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+出库管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-stock-out.html b/.superpowers/prototype/screens/m-stock-out.html
new file mode 100644
index 0000000..b6e6983
--- /dev/null
+++ b/.superpowers/prototype/screens/m-stock-out.html
@@ -0,0 +1,91 @@
+
+
+
+
+
+新增出库单 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/m-users.html b/.superpowers/prototype/screens/m-users.html
new file mode 100644
index 0000000..22cfc72
--- /dev/null
+++ b/.superpowers/prototype/screens/m-users.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+用户管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/mobile-kit.html b/.superpowers/prototype/screens/mobile-kit.html
new file mode 100644
index 0000000..b22c263
--- /dev/null
+++ b/.superpowers/prototype/screens/mobile-kit.html
@@ -0,0 +1,96 @@
+
+
+
+
+
+移动 UI Kit · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/mobile-shell.js b/.superpowers/prototype/screens/mobile-shell.js
new file mode 100644
index 0000000..4c103a8
--- /dev/null
+++ b/.superpowers/prototype/screens/mobile-shell.js
@@ -0,0 +1,92 @@
+// ============================================================
+// 移动壳 单一真源 —— 顶部 app 栏 + 底部 tab 栏 + 主题 + 底部 sheet + toast
+// 用法:
+// …内容…
+//
+// 末尾
+// 改顶栏/底 tab/主题 = 只改此文件。
+// ============================================================
+(function () {
+ const TABS = [
+ { k: 'inventory', label: '库存', href: 'm-inventory.html', icon: 'i-box-2' },
+ { k: 'stock-in', label: '入库', href: 'm-stock-in-list.html', icon: 'i-download' },
+ { k: 'stock-out', label: '出库', href: 'm-stock-out-list.html', icon: 'i-upload' },
+ { k: 'finance', label: '财务', href: 'm-finance.html', icon: 'i-ic08' },
+ { k: 'me', label: '我的', href: 'm-me.html', icon: 'i-ic37' },
+ ];
+ const THEMES = { a: '经典蓝', b: '琥珀', c: '酒窖' };
+
+ function icon(id, cls) { return ``; }
+
+ function setTheme(t) { document.documentElement.dataset.theme = t; localStorage.setItem('jiu-theme', t); }
+
+ // ---- 底部 sheet ----
+ let sheetEl = null, maskEl = null;
+ function closeSheet() { if (sheetEl) sheetEl.classList.remove('show'); if (maskEl) maskEl.classList.remove('show'); }
+ function openSheet(title, bodyHTML) {
+ if (!maskEl) {
+ maskEl = document.createElement('div'); maskEl.className = 'm-sheet-mask'; maskEl.onclick = closeSheet; document.body.appendChild(maskEl);
+ sheetEl = document.createElement('div'); sheetEl.className = 'm-sheet'; document.body.appendChild(sheetEl);
+ }
+ sheetEl.innerHTML = `${title}
${bodyHTML}
`;
+ requestAnimationFrame(() => { maskEl.classList.add('show'); sheetEl.classList.add('show'); });
+ }
+
+ // ---- toast ----
+ let toastT;
+ function mToast(m) {
+ let t = document.getElementById('toast');
+ if (!t) { t = document.createElement('div'); t.id = 'toast'; t.className = 'toast'; document.body.appendChild(t); }
+ t.textContent = m; t.classList.add('show'); clearTimeout(toastT); toastT = setTimeout(() => t.classList.remove('show'), 2200);
+ }
+
+ function openThemeSheet() {
+ const cur = document.documentElement.dataset.theme || 'a';
+ openSheet('切换主题', Object.entries(THEMES).map(([k, v]) =>
+ `${k.toUpperCase()} · ${v}
`).join(''));
+ }
+
+ function mountTop() {
+ const top = document.querySelector('[data-m-top]');
+ if (!top) return;
+ const back = top.dataset.back !== undefined;
+ const title = top.dataset.title || '';
+ top.innerHTML =
+ (back ? `${icon('i-chevron-left')}
` : '') +
+ `${title}
` +
+ `` +
+ `
${icon('i-shirt')}
` +
+ `
${icon('i-bell')}3
` +
+ `
王
` +
+ `
`;
+ }
+
+ function mountTabs() {
+ const bar = document.querySelector('[data-m-tabs]');
+ if (!bar) return;
+ const active = bar.dataset.active || '';
+ bar.innerHTML = TABS.map(t => `${icon(t.icon)}${t.label}`).join('');
+ }
+
+ function mountDsFab() {
+ if (document.getElementById('mDsFab')) return;
+ const a = document.createElement('a');
+ a.id = 'mDsFab'; a.className = 'm-ds-fab'; a.href = '../index.html'; a.title = '返回设计系统'; a.setAttribute('aria-label', '返回设计系统');
+ a.innerHTML = icon('i-arrow-left');
+ document.body.appendChild(a);
+ }
+
+ function mount() { setTheme(localStorage.getItem('jiu-theme') || 'a'); mountTop(); mountTabs(); mountDsFab(); }
+
+ document.addEventListener('keydown', e => { if (e.key === 'Escape') closeSheet(); });
+
+ window.setTheme = setTheme;
+ window.openSheet = openSheet;
+ window.closeSheet = closeSheet;
+ window.openThemeSheet = openThemeSheet;
+ window.mToast = mToast;
+ window.toast = window.toast || mToast;
+
+ if (document.readyState !== 'loading') mount();
+ else document.addEventListener('DOMContentLoaded', mount);
+})();
diff --git a/.superpowers/prototype/screens/notification.html b/.superpowers/prototype/screens/notification.html
new file mode 100644
index 0000000..5999efc
--- /dev/null
+++ b/.superpowers/prototype/screens/notification.html
@@ -0,0 +1,84 @@
+
+
+
+
+
+通知详情 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/notifications.html b/.superpowers/prototype/screens/notifications.html
new file mode 100644
index 0000000..ccb07cb
--- /dev/null
+++ b/.superpowers/prototype/screens/notifications.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+通知中心 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/notify-data.js b/.superpowers/prototype/screens/notify-data.js
new file mode 100644
index 0000000..e6ee860
--- /dev/null
+++ b/.superpowers/prototype/screens/notify-data.js
@@ -0,0 +1,46 @@
+// 通知中心数据(单一来源)—— 通知列表页 / 详情页(桌面 + 移动)共用
+// type 决定 .nt-ic 配色:warn(待办) / danger(库存) / info(单据·财务) / ok(系统)
+// 引入: 后即可读全局 NOTES。
+const NOTES = [
+ { id:1, type:'warn', cat:'审核待办', icon:'i-ic42', day:'今天', time:'10 分钟前', at:'2026-06-22 14:20', unread:true,
+ title:'入库单 RK-20260620-001 待审核', desc:'茅台华东总代 · 3 项 · ¥48,360',
+ body:'王经理 于 14:20 提交了入库单 RK-20260620-001,含 3 个商品、合计 ¥48,360,正在等待审核。请尽快进入入库管理完成审核,避免影响后续库存与对账。',
+ ref:{ label:'查看入库单', href:'stock-in-list.html', icon:'i-download' },
+ meta:[['单号','RK-20260620-001'],['供应商','茅台华东总代'],['商品数','3 项'],['合计金额','¥48,360'],['提交人','王经理']] },
+ { id:2, type:'danger', cat:'库存', icon:'i-ic18', day:'今天', time:'1 小时前', at:'2026-06-22 13:05', unread:true,
+ title:'五粮液 普五 库存预警', desc:'当前 6 件,低于安全库存 6',
+ body:'商品「五粮液 普五 52°」当前库存 6 件,已达到安全库存阈值(6 件)。建议尽快补货,以免缺货影响出库发货。',
+ ref:{ label:'去补货 · 新增入库', href:'stock-in.html', icon:'i-plus' },
+ meta:[['商品','五粮液 普五 52°'],['编码','WLY-PW-500'],['当前库存','6 件'],['安全库存','6 件'],['建议补货','≥ 24 件']] },
+ { id:3, type:'info', cat:'单据', icon:'i-ic06', day:'今天', time:'今天 09:20', at:'2026-06-22 09:20', unread:true,
+ title:'出库单 CK-20260619-003 已通过', desc:'张主管 审核通过',
+ body:'出库单 CK-20260619-003 已由 张主管 审核通过,库存已实时扣减,可进行后续发货与对账。',
+ ref:{ label:'查看出库单', href:'stock-out-list.html', icon:'i-upload' },
+ meta:[['单号','CK-20260619-003'],['客户','城东烟酒行'],['商品数','2 项'],['合计金额','¥18,600'],['审核人','张主管']] },
+ { id:4, type:'danger', cat:'库存', icon:'i-ic18', day:'今天', time:'今天 08:10', at:'2026-06-22 08:10', unread:true,
+ title:'汾酒 青花 20 已缺货', desc:'当前 0 件,请及时补货',
+ body:'商品「汾酒 青花 20」当前库存为 0,已处于缺货状态,相关出库将被拦截。请尽快补货以恢复销售。',
+ ref:{ label:'去补货 · 新增入库', href:'stock-in.html', icon:'i-plus' },
+ meta:[['商品','汾酒 青花 20'],['编码','FJ-QH20-500'],['当前库存','0 件'],['安全库存','8 件']] },
+ { id:5, type:'info', cat:'财务', icon:'i-ic47', day:'昨天', time:'昨天 17:40', at:'2026-06-21 17:40', unread:false,
+ title:'城东烟酒行 账单已结清', desc:'¥18,600 已收讫',
+ body:'往来单位「城东烟酒行」本期应收 ¥18,600 已全部结清,账单状态更新为已结清。',
+ ref:{ label:'查看对账单', href:'partners.html', icon:'i-ic56' },
+ meta:[['往来单位','城东烟酒行'],['结清金额','¥18,600'],['账单状态','已结清'],['经办人','李销售']] },
+ { id:6, type:'warn', cat:'审核待办', icon:'i-ic42', day:'昨天', time:'昨天 11:05', at:'2026-06-21 11:05', unread:false,
+ title:'盘点单 PD-20260618-002 存在差异', desc:'盘盈 2 · 盘亏 5,待复核',
+ body:'盘点单 PD-20260618-002 已完成实盘,存在盘盈 2 件、盘亏 5 件的差异,请复核并确认库存调整。',
+ ref:{ label:'查看盘点单', href:'inventory-check.html', icon:'i-ic61' },
+ meta:[['盘点单','PD-20260618-002'],['盘盈','2 件'],['盘亏','5 件'],['仓库','主仓']] },
+ { id:7, type:'ok', cat:'系统', icon:'i-refresh', day:'更早', time:'2 天前', at:'2026-06-20 10:00', unread:false,
+ title:'系统已更新至 v1.0.72', desc:'退单按钮等体验优化',
+ body:'岩美酒库已更新至 v1.0.72,本次带来用户四级角色、商品公开页编辑器、统一壳层等改进。查看更新日志了解全部内容。',
+ ref:{ label:'查看更新日志', href:'about.html', icon:'i-ic24' },
+ meta:[['当前版本','v1.0.72'],['发布日期','2026-06-22']] },
+ { id:8, type:'ok', cat:'系统', icon:'i-shield', day:'更早', time:'3 天前', at:'2026-06-19 09:00', unread:false,
+ title:'授权兑换券剩余 30 天', desc:'到期前可在系统设置续期',
+ body:'当前门店授权将在 30 天后到期。为避免影响使用,请在到期前于「系统设置 · 授权」中完成续期。',
+ ref:{ label:'去续期', href:'settings.html', icon:'i-shield' },
+ meta:[['授权状态','已授权'],['到期日','2027-03-15'],['剩余天数','30 天']] },
+];
+function noteById(id){ return NOTES.find(n=>n.id===+id) || NOTES[0]; }
diff --git a/.superpowers/prototype/screens/notify.js b/.superpowers/prototype/screens/notify.js
new file mode 100644
index 0000000..742ad75
--- /dev/null
+++ b/.superpowers/prototype/screens/notify.js
@@ -0,0 +1,58 @@
+// 顶栏通知(小喇叭)—— 共用通知机制(单一真源,所有 app-shell 屏复用)
+// 用法:顶栏放
+// 本脚本接管点击 → 弹出 .notify-pop 通知面板;未读数写进 .nbadge;「全部已读」清零。
+(function(){
+ const NOTES = [
+ { ic:'warn', t:'入库单 RK-20260620-001 待审核', d:'茅台华东总代 · 3 项 · ¥48,360', time:'10 分钟前', unread:true },
+ { ic:'warn', t:'五粮液 普五 库存预警', d:'当前 6 件,低于安全库存 6', time:'1 小时前', unread:true },
+ { ic:'info', t:'出库单 CK-20260619-003 已通过', d:'李销售 审核通过', time:'今天 09:20', unread:true },
+ { ic:'ok', t:'授权兑换券剩余 30 天', d:'到期前可在「系统设置」续期', time:'昨天', unread:false },
+ ];
+ const ICONS = {
+ warn:'',
+ info:'',
+ ok:'',
+ };
+ let pop, trigger;
+
+ function unread(){ return NOTES.filter(n => n.unread).length; }
+ function paintBadge(){
+ if(!trigger) return;
+ let b = trigger.querySelector('.nbadge');
+ const n = unread();
+ if(n > 0){ if(!b){ b = document.createElement('span'); b.className='nbadge'; trigger.appendChild(b); } b.textContent = n; }
+ else if(b){ b.remove(); }
+ }
+ function render(){
+ const list = NOTES.length ? NOTES.map((n,i) =>
+ ``
+ ).join('') : '暂无通知
';
+ pop.innerHTML = `通知
全部已读${list}
`;
+ pop.querySelector('.np-clear').onclick = (e) => { e.stopPropagation(); NOTES.forEach(n => n.unread = false); paintBadge(); render(); };
+ pop.querySelector('.np-foot').onclick = () => { close(); location.href = 'notifications.html'; };
+ pop.querySelectorAll('.np-item').forEach(el => el.onclick = () => { const n = NOTES[+el.dataset.i]; if(n.unread){ n.unread = false; paintBadge(); } el.classList.remove('unread'); });
+ }
+ function position(){
+ const r = trigger.getBoundingClientRect();
+ const w = pop.offsetWidth || 330;
+ pop.style.left = Math.max(8, Math.min(r.right - w, window.innerWidth - w - 8)) + 'px';
+ pop.style.top = (r.bottom + 8) + 'px';
+ }
+ function open(){ render(); pop.classList.add('show'); position(); }
+ function close(){ if(pop) pop.classList.remove('show'); }
+ function toggle(e){ e.stopPropagation(); if(pop.classList.contains('show')) close(); else open(); }
+
+ function init(){
+ trigger = document.querySelector('[data-notify]');
+ if(!trigger) return;
+ trigger.style.position = 'relative';
+ pop = document.createElement('div'); pop.className = 'notify-pop'; document.body.appendChild(pop);
+ paintBadge();
+ trigger.addEventListener('click', toggle);
+ document.addEventListener('mousedown', e => { if(pop.classList.contains('show') && !pop.contains(e.target) && !trigger.contains(e.target)) close(); });
+ document.addEventListener('keydown', e => { if(e.key === 'Escape') close(); });
+ window.addEventListener('resize', close);
+ }
+ if(document.readyState !== 'loading') init();
+ else document.addEventListener('DOMContentLoaded', init);
+})();
diff --git a/.superpowers/prototype/screens/partners.html b/.superpowers/prototype/screens/partners.html
new file mode 100644
index 0000000..0d150e1
--- /dev/null
+++ b/.superpowers/prototype/screens/partners.html
@@ -0,0 +1,169 @@
+
+
+
+
+
+往来单位 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/pricing.html b/.superpowers/prototype/screens/pricing.html
new file mode 100644
index 0000000..9503be7
--- /dev/null
+++ b/.superpowers/prototype/screens/pricing.html
@@ -0,0 +1,153 @@
+
+
+
+
+
+购买授权 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
购买授权
按时长购买兑换券,续期门店授权 · 无订阅、不自动扣费
+
+
+
+
+
+
+
+
+
+
+
+
+ 购买后系统生成「时长兑换券」短码,在「系统设置 · 授权兑换券」输入即可叠加续期,到期日顺延。支付通道暂未接入,下单后由顾问对接开票与收款。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/product-detail.html b/.superpowers/prototype/screens/product-detail.html
new file mode 100644
index 0000000..30f391a
--- /dev/null
+++ b/.superpowers/prototype/screens/product-detail.html
@@ -0,0 +1,162 @@
+
+
+
+
+
+商品详情 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
基础数据
+
+
茅台 飞天 53°
+
MT-FT-500
+
在售
+
+
+
+
+
+
+
+
基本信息
+
+
系列飞天
+
规格53° 500ml×6
+
分类酱香
+
单位件 (6 瓶/件)
+
默认成本价¥2,680 / 件
+
条码6901234567890
+
+
+
+
+
+
+
+
各仓库库存分布
合计 128 件 · ¥34.3万
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/product-editor.js b/.superpowers/prototype/screens/product-editor.js
new file mode 100644
index 0000000..56dcfbd
--- /dev/null
+++ b/.superpowers/prototype/screens/product-editor.js
@@ -0,0 +1,104 @@
+// ============================================================
+// 商品编辑抽屉 单一真源 —— 商品信息 + 商品图片 + 商品介绍 + 预览
+// 用法:任意屏引入 (需先引 shell.js 取 openMenu/toast),
+// 行点击调用 openProductEditor(item)。item 字段按需:{name,code,series,spec,cat,qty,unit,cost,value,status}
+// 改这个抽屉(库存 / 基础数据共用)= 只改此文件 + atoms.css 的 .pe-* 样式。
+// ============================================================
+(function () {
+ const INTRO_LIB = [
+ { t: '酱香经典 · 宴请送礼', body: '酱香突出、醇厚协调,空杯留香持久,是商务宴请与节庆馈赠之选。' },
+ { t: '浓香绵柔 · 口粮优选', body: '入口绵甜、诸味协调、尾净味长,日常聚饮与口粮自饮皆宜。' },
+ { t: '高端收藏 · 限量原浆', body: '精选原浆、限量出品,酒体丰满、陈香雅致,兼具品饮与收藏价值。' },
+ { t: '清香纯净 · 入门友好', body: '清香纯正、入口净爽、回味甘冽,适合清淡口味与入门饮用。' },
+ ];
+ const PE_TILE = ['主图', '细节图', '场景图'];
+ const PE_IMG = (bg, c) => `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 130'%3E%3Crect width='100' height='130' fill='%23${bg}'/%3E%3Crect x='40' y='18' width='20' height='97' rx='5' fill='%23${c}'/%3E%3Crect x='37' y='52' width='26' height='34' rx='2' fill='%23F5ECD9'/%3E%3C/svg%3E`;
+ const PE_SAMPLE = [PE_IMG('E9DCC0', '8B2331'), PE_IMG('D9E2EC', '2D4A66'), PE_IMG('EAD9C2', '7A4A1E')];
+ const peState = { imgs: ['', '', ''] };
+
+ const DRAWER_HTML = `
+
+
+
+
+
+
+
商品图片
+
建议主图为白底产品图,可再加细节图(瓶身/防伪)与场景图。
+
+
+
+
+
`;
+
+ function ensure() {
+ if (document.getElementById('pubDrawer')) return;
+ const wrap = document.createElement('div');
+ wrap.innerHTML = DRAWER_HTML.trim();
+ while (wrap.firstChild) document.body.appendChild(wrap.firstChild);
+ }
+
+ function infoRows(it) {
+ const r = [];
+ if (it.code) r.push(['编码', `${it.code}`]);
+ if (it.series) r.push(['系列', `${it.series}`]);
+ if (it.spec) r.push(['规格', `${it.spec}`]);
+ if (it.cat) r.push(['分类', `${it.cat}`]);
+ if (it.qty != null) r.push(['当前库存', `${it.qty} ${it.unit || ''}`]);
+ if (it.cost) r.push(['成本价', `${it.cost}`]);
+ if (it.value) r.push(['库存货值', `${it.value}`]);
+ if (it.status) r.push(['状态', `${it.status}`]);
+ return r.map((x, i) => `${x[0]}${x[1]}
`).join('');
+ }
+
+ function renderPeGallery() {
+ document.getElementById('peGallery').innerHTML = peState.imgs.map((u, i) =>
+ `${u ? `

` : `
${PE_TILE[i]}`}
${u ? `
${PE_TILE[i]}` : ''}
`).join('');
+ }
+
+ function openProductEditor(it) {
+ ensure();
+ document.getElementById('peTitle').textContent = it.name || '商品';
+ document.getElementById('peInfo').innerHTML = infoRows(it);
+ peState.imgs = [PE_SAMPLE[0], '', ''];
+ const nm = document.getElementById('peIntroName');
+ nm.textContent = '从介绍库选择…'; nm.classList.add('ph');
+ document.getElementById('peIntroText').value = '';
+ renderPeGallery();
+ document.getElementById('pubDrawer').classList.add('show');
+ document.getElementById('pubMask').classList.add('show');
+ }
+
+ // 全局暴露(注入 HTML 的 onclick 直接调用)
+ window.openProductEditor = openProductEditor;
+ window.closePubDrawer = () => { const d = document.getElementById('pubDrawer'); if (d) d.classList.remove('show'); const m = document.getElementById('pubMask'); if (m) m.classList.remove('show'); };
+ window.peTile = i => { if (!peState.imgs[i]) { peState.imgs[i] = PE_SAMPLE[i]; renderPeGallery(); } };
+ window.peRemove = i => { peState.imgs[i] = ''; renderPeGallery(); };
+ window.pickIntro = e => {
+ const nm = document.getElementById('peIntroName');
+ openMenu(e.currentTarget, INTRO_LIB.map((x, i) => ({ v: String(i), label: x.t, sel: nm.textContent === x.t })), v => {
+ const x = INTRO_LIB[+v]; nm.textContent = x.t; nm.classList.remove('ph');
+ document.getElementById('peIntroText').value = x.body; closeMenus();
+ });
+ };
+
+ if (document.readyState !== 'loading') ensure();
+ else document.addEventListener('DOMContentLoaded', ensure);
+})();
diff --git a/.superpowers/prototype/screens/products.html b/.superpowers/prototype/screens/products.html
new file mode 100644
index 0000000..f712eeb
--- /dev/null
+++ b/.superpowers/prototype/screens/products.html
@@ -0,0 +1,287 @@
+
+
+
+
+
+基础数据 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 商品 | 系列 | 规格 | 分类 | 单位 | 默认成本价 | 状态 | 操作 |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/public-edit.html b/.superpowers/prototype/screens/public-edit.html
new file mode 100644
index 0000000..07a3893
--- /dev/null
+++ b/.superpowers/prototype/screens/public-edit.html
@@ -0,0 +1,249 @@
+
+
+
+
+
+编辑公开页 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
库存管理
+
茅台 飞天 53°
+
MT-FT-500
+
在售
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/public-product.html b/.superpowers/prototype/screens/public-product.html
new file mode 100644
index 0000000..0fe2b1a
--- /dev/null
+++ b/.superpowers/prototype/screens/public-product.html
@@ -0,0 +1,283 @@
+
+
+
+
+
+茅台 飞天 53° · 鼎晟酒行
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
鼎
+
+
鼎晟酒行
+
DINGSHENG · 授权正品门店
+
+
防伪溯源
+
+
+
+
+
+
+ 商品图
+
+
+
茅台 飞天 53°
+
飞天系列 · 酱香型白酒 · 500ml × 6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
规格参数
+
+
+
+
+
+
+
+
+
+
防伪验证
+
+
+
+
已通过岩美防伪验证
+
门店授权正品 · 批次 MT-FT-2024-08-A0931 · 扫码已上链
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/public-shop.html b/.superpowers/prototype/screens/public-shop.html
new file mode 100644
index 0000000..d891918
--- /dev/null
+++ b/.superpowers/prototype/screens/public-shop.html
@@ -0,0 +1,165 @@
+
+
+
+
+
+鼎晟酒行 · 在售商品
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
在售商品共 8 款白酒
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/register.html b/.superpowers/prototype/screens/register.html
new file mode 100644
index 0000000..1867b2f
--- /dev/null
+++ b/.superpowers/prototype/screens/register.html
@@ -0,0 +1,142 @@
+
+
+
+
+
+注册门店 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
开通门店
+
几分钟创建你的酒水进销存门店,立即开始入库出库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/settings.html b/.superpowers/prototype/screens/settings.html
new file mode 100644
index 0000000..4bfb286
--- /dev/null
+++ b/.superpowers/prototype/screens/settings.html
@@ -0,0 +1,218 @@
+
+
+
+
+
+系统设置 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
系统设置
门店配置 · 用户与授权 · 个性化偏好
+
+
+
+
门店信息
+
用户管理
+
授权兑换券
+
偏好设置
+
+
+
+
+
+
+
门店信息
+
用于单据抬头、对账与多端展示
+
+
+
+
+
+
+
+
+
+
+
+ | 用户 | 角色 | 状态 | 操作 |
+
+ 王经理 wang | 管理员 | 启用 | |
+ 李采购 li | 普通 | 启用 | |
+ 赵会计 zhao | 只读 | 启用 | |
+ 钱仓管 qian | 普通 | 停用 | |
+
+
+
+
+
+
+
+
+
+
授权兑换券
+
本系统以「时长兑换券」授权,输入兑换券短码即可续期,无需订阅
+
+
+
没有兑换券?购买授权时长 →
+
+
+
+
+
+
+
偏好设置
+
主题与默认值,仅影响当前账号在本端的体验
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/shell.js b/.superpowers/prototype/screens/shell.js
new file mode 100644
index 0000000..a4c49a0
--- /dev/null
+++ b/.superpowers/prototype/screens/shell.js
@@ -0,0 +1,113 @@
+// ============================================================
+// app-shell 单一真源 —— 顶栏 + 侧栏 + 主题/用户菜单 + 共享 openMenu/toast
+// 用法:
+//
+// 末尾引入 (其余页面脚本可直接用全局 openMenu/closeMenus/toast)
+// 改顶栏/侧栏/主题/菜单 = 只改此文件。
+// ============================================================
+(function () {
+ const NAVS = [
+ { g: '单据' },
+ { k: 'stock-in', label: '入库管理', href: 'stock-in-list.html', d: 'M12 3v12 M8 11l4 4 4-4 M4 21h16' },
+ { k: 'stock-out', label: '出库管理', href: 'stock-out-list.html', d: 'M12 15V3 M8 7l4-4 4 4 M4 21h16' },
+ { g: '经营' },
+ { k: 'inventory', label: '库存管理', href: 'inventory.html', d: 'M3 7l9-4 9 4-9 4-9-4z M3 7v10l9 4 9-4V7 M12 11v10' },
+ { k: 'finance', label: '财务管理', href: 'finance.html', d: 'M3 5h18v14H3z M3 10h18' },
+ { k: 'partners', label: '往来单位', href: 'partners.html', d: 'M9 8a3 3 0 100-0.01 M3 20c0-3 3-5 6-5s6 2 6 5' },
+ { k: 'products', label: '基础数据', href: 'products.html', d: 'M3 3h7v7H3z M14 3h7v7h-7z M3 14h7v7H3z M14 14h7v7h-7z' },
+ { g: '系统' },
+ { k: 'devices', label: '设备管理', href: 'devices.html', d: 'M4 4h16v12H4z M8 20h8 M12 16v4' },
+ { k: 'settings', label: '系统设置', href: 'settings.html', d: 'M12 9a3 3 0 100 6 3 3 0 000-6z M4 12h2 M18 12h2 M12 4v2 M12 18v2' },
+ { k: 'about', label: '关于我们', href: 'about.html', d: 'M12 16v-4 M12 8h.01 M12 3a9 9 0 100 18 9 9 0 000-18z' },
+ ];
+ const THEMES = { a: { name: '经典蓝' }, b: { name: '琥珀' }, c: { name: '酒窖' } };
+ // 侧栏导航图标(引用 icons.js sprite 的 Lucide 图标)
+ const NAV_ICON = { 'stock-in': 'i-download', 'stock-out': 'i-upload', inventory: 'i-box-2', finance: 'i-ic08', partners: 'i-ic56', products: 'i-ic57', devices: 'i-ic63', settings: 'i-gear', about: 'i-ic24' };
+
+ const BRAND = '鼎鼎晟酒行
';
+ const TOP_RIGHT = '';
+
+ function navSvg(d) { return ``; }
+
+ // ---- 共享菜单 ----
+ let menuEl = null;
+ function closeMenus() { if (menuEl) { menuEl.remove(); menuEl = null; } }
+ function openMenu(t, items, onPick, cls, minW) {
+ closeMenus();
+ const r = t.getBoundingClientRect();
+ const w = Math.max(r.width, minW || 168);
+ menuEl = document.createElement('div'); menuEl.className = 'menu' + (cls ? ' ' + cls : '');
+ menuEl.style.left = Math.max(8, Math.min(r.left, window.innerWidth - w - 8)) + 'px';
+ menuEl.style.minWidth = w + 'px';
+ menuEl.innerHTML = items.map(it => {
+ const lead = it.sel ? ''
+ : it.icon ? ``
+ : '';
+ return ``;
+ }).join('');
+ menuEl.querySelectorAll('.menu-item').forEach(el => el.onclick = e => { e.stopPropagation(); onPick(el.dataset.v); });
+ document.body.appendChild(menuEl);
+ // 空间不够则向上翻(如侧栏底部的用户菜单)
+ const h = menuEl.offsetHeight;
+ const below = r.bottom + 6, above = r.top - h - 6;
+ menuEl.style.top = (below + h > window.innerHeight && above > 6 ? above : below) + 'px';
+ }
+
+ // ---- toast(若页面无 #toast 自动注入) ----
+ let toastT;
+ function toast(m) {
+ let t = document.getElementById('toast');
+ if (!t) { t = document.createElement('div'); t.id = 'toast'; t.className = 'toast'; document.body.appendChild(t); }
+ t.textContent = m; t.classList.add('show'); clearTimeout(toastT); toastT = setTimeout(() => t.classList.remove('show'), 2200);
+ }
+
+ // ---- 主题 ----
+ function setTheme(t) {
+ document.documentElement.dataset.theme = t;
+ localStorage.setItem('jiu-theme', t);
+ const nm = document.getElementById('themeName'); if (nm) nm.textContent = THEMES[t].name;
+ }
+
+ function buildSide(active) {
+ let h = '';
+ for (const n of NAVS) {
+ if (n.g) { h += `${n.g}
`; continue; }
+ h += `${n.label}`;
+ }
+ h += '';
+ return h;
+ }
+ function openUserMenu(e) {
+ openMenu(e.currentTarget, [{ v: 'profile', label: '个人设置', icon: 'i-ic37' }, { v: 'logout', label: '退出登录', icon: 'i-log-out' }], v => { closeMenus(); if (v === 'logout') location.href = 'login.html'; else toast('个人设置(待 Phase 4)'); }, 'user-menu');
+ }
+
+ function mountShell(opts) {
+ opts = opts || {};
+ const top = document.querySelector('[data-shell-top]');
+ const active = opts.active || (top && top.dataset.active) || '';
+ if (top) {
+ top.innerHTML = BRAND + TOP_RIGHT;
+ const pill = top.querySelector('#themePill');
+ if (pill) pill.onclick = e => openMenu(e.currentTarget, Object.entries(THEMES).map(([k, v]) => ({ v: k, label: k.toUpperCase() + ' · ' + v.name, sel: k === document.documentElement.dataset.theme })), v => { setTheme(v); closeMenus(); });
+ }
+ const side = document.getElementById('side');
+ if (side) side.innerHTML = buildSide(active);
+ setTheme(localStorage.getItem('jiu-theme') || 'a');
+ }
+
+ // 关菜单:mousedown(避免点触发器时瞬开瞬关)
+ document.addEventListener('mousedown', e => { if (menuEl && !menuEl.contains(e.target) && !e.target.closest('#themePill,.side-user')) closeMenus(); });
+ document.addEventListener('keydown', e => { if (e.key === 'Escape') closeMenus(); });
+
+ // 暴露给页面脚本复用
+ window.openMenu = openMenu;
+ window.closeMenus = closeMenus;
+ window.toast = window.toast || toast;
+ window.setTheme = setTheme;
+ window.openUserMenu = openUserMenu;
+ window.navSvg = navSvg;
+ window.mountShell = mountShell;
+
+ if (document.readyState !== 'loading') mountShell();
+ else document.addEventListener('DOMContentLoaded', () => mountShell());
+})();
diff --git a/.superpowers/prototype/screens/statusbar.js b/.superpowers/prototype/screens/statusbar.js
new file mode 100644
index 0000000..8a6b12e
--- /dev/null
+++ b/.superpowers/prototype/screens/statusbar.js
@@ -0,0 +1,50 @@
+// 底部状态栏 —— app-shell 共用(单一真源,注入 .app 底部;当前时间每秒走字)
+// 左:门店编号 / 操作员 / 登录时间 / 当前时间 右:连接状态 / 版本 / 授权
+(function(){
+ const ICON = {
+ shop:'',
+ user:'',
+ login:'',
+ clock:'',
+ cloud:'',
+ tag:'',
+ shield:'',
+ };
+ const pad = n => String(n).padStart(2,'0');
+ const hms = d => pad(d.getHours())+':'+pad(d.getMinutes())+':'+pad(d.getSeconds());
+
+ function init(){
+ const app = document.querySelector('.app');
+ if(!app || document.querySelector('.statusbar')) return;
+ const login = hms(new Date());
+ const bar = document.createElement('div');
+ bar.className = 'statusbar';
+ bar.innerHTML =
+ ``
+ + `${ICON.shop}DSJH-001`
+ + `${ICON.user}王经理`
+ + `${ICON.login}登录 ${login}`
+ + `${ICON.clock}当前 ${login}`
+ + `
`
+ + ``
+ + `${ICON.cloud}已连接`
+ + `${ICON.tag}v1.0.72`
+ + `${ICON.shield}授权正常 · 到期 2026-07-13`
+ + `
`;
+ // 不贯穿侧栏:把主内容列(.main/.work)与状态栏包进右侧内容列,侧栏占满整列高
+ const body = app.querySelector('.body');
+ const main = body && body.querySelector(':scope > .main, :scope > .work');
+ if (body && main) {
+ const col = document.createElement('div'); col.className = 'content-col';
+ main.parentNode.insertBefore(col, main);
+ col.appendChild(main);
+ col.appendChild(bar);
+ } else {
+ app.appendChild(bar);
+ }
+ const now = bar.querySelector('#sbNow');
+ setInterval(() => { now.textContent = hms(new Date()); }, 1000);
+ }
+ if(document.readyState !== 'loading') init();
+ else document.addEventListener('DOMContentLoaded', init);
+})();
diff --git a/.superpowers/prototype/screens/stock-in-list.html b/.superpowers/prototype/screens/stock-in-list.html
new file mode 100644
index 0000000..da8f9de
--- /dev/null
+++ b/.superpowers/prototype/screens/stock-in-list.html
@@ -0,0 +1,222 @@
+
+
+
+
+
+入库管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
确认进价
+
+
调货「先卖后定价」:填写真实进价后,系统前向补齐库存成本、已出库成本快照,并按差额补一条应付流水——不反审核、不影响售价 / 应收。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/stock-in.html b/.superpowers/prototype/screens/stock-in.html
new file mode 100644
index 0000000..65cab07
--- /dev/null
+++ b/.superpowers/prototype/screens/stock-in.html
@@ -0,0 +1,195 @@
+
+
+
+
+
+录单 · 入库 / 出库 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
新建入库单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/stock-in.js b/.superpowers/prototype/screens/stock-in.js
new file mode 100644
index 0000000..8ea5711
--- /dev/null
+++ b/.superpowers/prototype/screens/stock-in.js
@@ -0,0 +1,515 @@
+/* ============================================================
+ 岩美酒库 · 录单表单(入库为主 / 出库镜像)
+ 页面级逻辑:复用 atoms.css 的 .combo / .grid / 状态徽章 / .mcard。
+ ============================================================ */
+
+/* ---------------- 字典数据(原型内置;真实环境走基础数据接口)---------------- */
+let SEQ = 1000;
+const SERIES = [
+ {id:'s53',name:'53度',py:'53du',init:'53'},{id:'s52',name:'52度',py:'52du',init:'52'},
+ {id:'s45',name:'45度',py:'45du',init:'45'},{id:'s42',name:'42度',py:'42du',init:'42'},
+ {id:'s38',name:'38度',py:'38du',init:'38'},
+];
+const SPECS = [
+ {id:'p5006',name:'500ml×6',py:'500ml',init:'500'},{id:'p500',name:'500ml',py:'500ml',init:'500'},
+ {id:'p250',name:'250ml',py:'250ml',init:'250'},{id:'p1000',name:'1000ml',py:'1000ml',init:'1000'},
+ {id:'p100',name:'100ml',py:'100ml',init:'100'},
+];
+const NAMES = [
+ ['n1','茅台飞天','maotaifeitian','mtft','s53','p5006',2680,2980,128],
+ ['n2','茅台五星','maotaiwuxing','mtwx','s53','p5006',2380,2680,40],
+ ['n3','五粮液普五','wuliangyepuwu','wlypw','s52','p5006',1050,1280,64],
+ ['n4','五粮液1618','wuliangye1618','wly1618','s52','p5006',1180,1420,22],
+ ['n5','剑南春水晶剑','jiannanchunshuijingjian','jncsjj','s52','p5006',438,560,8],
+ ['n6','国窖1573','guojiao1573','gj1573','s52','p5006',980,1180,42],
+ ['n7','洋河梦之蓝M6','yanghemengzhilanm6','yhmzlm6','s45','p5006',620,780,23],
+ ['n8','泸州老窖特曲','luzhoulaojiaotequ','lzljtq','s52','p5006',320,420,156],
+ ['n9','习酒窖藏1988','xijiujiaocang1988','xjjc1988','s53','p5006',760,920,31],
+ ['n10','汾酒青花20','fenjiuqinghua20','fjqh20','s53','p5006',420,520,0],
+ ['n11','郎酒红花郎15','langjiuhonghualang15','ljhhl15','s53','p5006',560,720,54],
+ ['n12','古井贡年份原浆','gujinggongnianfenyuanjiang','gjgnfyj','s42','p5006',390,490,12],
+ ['n13','西凤酒旗舰版','xifengjiuqijianban','xfjqjb','s45','p5006',280,360,88],
+ ['n14','水井坊井台','shuijingfangjingtai','sjfjt','s52','p5006',680,860,6],
+].map(a=>({id:a[0],name:a[1],py:a[2],init:a[3],dSeries:a[4],dSpec:a[5],cost:a[6],sale:a[7],stock:a[8]}));
+const SUPPLIERS = [
+ ['g1','鼎晟供应链','dingshenggongyinglian','dsgyl','GYS001'],['g2','茅台华东总代','maotaihuadongzongdai','mthdzd','GYS002'],
+ ['g3','川酒集团批发','chuanjiujituanpifa','cjjtpf','GYS003'],['g4','名烟名酒城','mingyanmingjiucheng','mymjc','GYS004'],
+ ['g5','华润万家配送','huarunwanjiapeisong','hrwjps','GYS005'],
+].map(a=>({id:a[0],name:a[1],py:a[2],init:a[3],code:a[4]}));
+const CUSTOMERS = [
+ ['c1','老张烟酒行','laozhangyanjiuhang','lzyjh','KH001'],['c2','城东餐饮集团','chengdongcanyinjituan','cdcyjt','KH002'],
+ ['c3','喜来登酒店','xilaidengjiudian','xldjd','KH003'],['c4','婚庆批发王','hunqingpifawang','hqpfw','KH004'],
+ ['c5','社区团购站','shequtuangouzhan','sqtgz','KH005'],
+].map(a=>({id:a[0],name:a[1],py:a[2],init:a[3],code:a[4]}));
+const WAREHOUSES = [{id:'w1',name:'主仓'},{id:'w2',name:'二号仓'},{id:'w3',name:'冷藏库'}];
+const ORIGINS=[['o1','贵州茅台镇'],['o2','四川宜宾'],['o3','四川泸州'],['o4','江苏宿迁'],['o5','山西汾阳'],['o6','安徽亳州']].map(a=>({id:a[0],name:a[1],py:'',init:''}));
+const SHELF=[['h1','长期'],['h2','36个月'],['h3','24个月'],['h4','18个月']].map(a=>({id:a[0],name:a[1],py:'',init:''}));
+const STORAGE=[['t1','常温避光'],['t2','阴凉干燥'],['t3','防潮防晒']].map(a=>({id:a[0],name:a[1],py:'',init:''}));
+const DOCS=[['d1','酱香型产品说明'],['d2','浓香型产品说明'],['d3','清香型产品说明']].map(a=>({id:a[0],name:a[1],py:'',init:''}));
+const DICT = {series:SERIES,specs:SPECS,names:NAMES,suppliers:SUPPLIERS,customers:CUSTOMERS,warehouses:WAREHOUSES,origins:ORIGINS,shelf:SHELF,storage:STORAGE,docs:DOCS};
+const OPTFIELDS=[{k:'origin',label:'产地',dict:'origins'},{k:'shelfLife',label:'保质期',dict:'shelf'},{k:'storage',label:'储存方式',dict:'storage'},{k:'doc',label:'介绍文档',dict:'docs'}];
+
+/* ---------------- 模式配置(入库 / 出库镜像差异)---------------- */
+const MODES = {
+ in:{ title:'入库单', partnerLabel:'供应商', partnerDict:'suppliers', handler:'入库员',
+ cols:['idx','name','series','spec','prodDate','batch','qty','price','sale','amount','act'] },
+ out:{ title:'出库单', partnerLabel:'客户', partnerDict:'customers', handler:'出库员',
+ cols:['idx','name','series','spec','avail','qty','price','sale','amount','act'] },
+};
+const COLDEF = {
+ idx:{type:'idx',w:38},
+ name:{type:'combo',label:'商品名称',dict:'names',req:true,create:true,grow:true},
+ series:{type:'combo',label:'系列',dict:'series',req:true,create:true,w:104,common:true},
+ spec:{type:'combo',label:'规格',dict:'specs',req:true,create:true,w:116,common:true},
+ prodDate:{type:'date',label:'生产日期',req:true,w:150},
+ batch:{type:'text',label:'批次号',req:true,w:108,ph:'必填'},
+ avail:{type:'avail',label:'可用库存',w:92,num:true},
+ qty:{type:'num',label:'数量',req:true,w:78,num:true},
+ price:{type:'num',label:'单价',req:true,w:96,num:true,money:true},
+ sale:{type:'num',label:'出售价格',req:true,w:104,num:true,money:true},
+ amount:{type:'amount',label:'金额',w:104,num:true},
+ act:{type:'act',w:62},
+};
+// 录单可聚焦字段顺序(出库的 series/spec/avail 只读,不进流)
+function editFields(mode){ return MODES[mode].cols.filter(k=>['combo','date','text','num'].includes(COLDEF[k].type) && !(mode==='out'&&(k==='series'||k==='spec'))); }
+function reqFields(mode){ return editFields(mode).filter(k=>COLDEF[k].req); }
+
+/* ---------------- 状态 ---------------- */
+let mode='in', status='new';
+const state = { doc:{partner:null,warehouse:null,date:today(),remark:''}, rows:[], docErr:new Set() };
+function today(){ const d=new Date(); return d.toISOString().slice(0,10); }
+function newRow(){ return {id:'r'+(++SEQ), name:null,series:null,spec:null,prodDate:'',batch:'',qty:'1',price:'',sale:'',avail:null,expanded:false,origin:null,shelfLife:null,storage:null,doc:null,_err:new Set()}; }
+
+/* ---------------- 工具 ---------------- */
+function idName(dictKey,id){ if(id==null) return ''; const o=(DICT[dictKey]||[]).find(x=>x.id===id); return o?o.name:''; }
+function fmt(n){ return '¥'+(Number(n)||0).toLocaleString('zh-CN',{minimumFractionDigits:2,maximumFractionDigits:2}); }
+function rowAmount(r){ return (parseFloat(r.qty)||0)*(parseFloat(r.price)||0); }
+function grandTotal(){ return state.rows.reduce((s,r)=>s+rowAmount(r),0); }
+// 入库进价留空/0 = 待定价(暂估),金额显示「待定价」橙色;确认进价后回填
+function isPending(r){ return mode==='in' && (!r.price || (parseFloat(r.price)||0)<=0); }
+function amtHTML(r){ return isPending(r) ? '待定价' : fmt(rowAmount(r)); }
+function esc(s){ return (s||'').replace(/&/g,'&').replace(/'+esc(name.slice(i,i+kw.length))+''+esc(name.slice(i+kw.length)); }
+
+/* ---------------- 选项来源(按模式过滤)---------------- */
+function optionsFor(combo){
+ const dict=combo.dataset.dict, field=combo.dataset.field;
+ if(field==='partner') return mode==='in'?SUPPLIERS:CUSTOMERS;
+ if(dict==='names') return mode==='out'?NAMES.filter(n=>n.stock>0):NAMES;
+ return DICT[dict]||[];
+}
+function commonIdsFor(combo){
+ if(combo.dataset.scope!=='row') return null;
+ const i=+combo.dataset.row, r=state.rows[i]; if(!r||!r.name) return null;
+ const nm=NAMES.find(n=>n.id===r.name); if(!nm) return null;
+ if(combo.dataset.field==='series') return [nm.dSeries];
+ if(combo.dataset.field==='spec') return [nm.dSpec];
+ return null;
+}
+function curValueOf(combo){
+ const f=combo.dataset.field;
+ if(combo.dataset.scope==='doc') return state.doc[f];
+ return state.rows[+combo.dataset.row][f];
+}
+function displayOf(combo){ return idName(combo.dataset.dict, curValueOf(combo)); }
+
+/* ---------------- combo 控制器 ---------------- */
+let activeCombo=null, comboState=null;
+const pop=document.createElement('div'); pop.className='combo-pop'; pop.style.display='none'; document.body.appendChild(pop);
+
+function openCombo(combo){
+ if(activeCombo===combo) return;
+ closeCombo();
+ activeCombo=combo;
+ const input=combo.querySelector('input');
+ if(!input.dataset.ph0) input.dataset.ph0=input.getAttribute('placeholder')||'搜索…';
+ const sel=displayOf(combo);
+ input.value='';
+ input.placeholder=sel||input.dataset.ph0;
+ combo.classList.add('open');
+ renderPop(combo);
+}
+function renderPop(combo){
+ const input=combo.querySelector('input'); const kw=input.value;
+ const opts=optionsFor(combo); const common=commonIdsFor(combo);
+ const cur=curValueOf(combo);
+ const matched=opts.filter(o=>match(o,kw));
+ const create=combo.dataset.create==='1' && kw.trim() && !opts.some(o=>o.name===kw.trim());
+ let html=''; const list=[];
+ const itemHTML=(o)=>{ const sel=o.id===cur; list.push({type:'opt',id:o.id}); const code=o.code?`${esc(o.code)}`:''; const ck=sel?'':''; return `${highlight(o.name,kw)}${code}${ck}
`; };
+ if(common && !kw.trim()){
+ const com=matched.filter(o=>common.includes(o.id)); const rest=matched.filter(o=>!common.includes(o.id));
+ if(com.length){ html+='常用
'+com.map(itemHTML).join(''); }
+ if(rest.length){ html+='全部
'+rest.map(itemHTML).join(''); }
+ } else {
+ html += matched.length? matched.map(itemHTML).join('') : (create?'':'无匹配结果
');
+ }
+ let listHtml=''+html+'
';
+ if(create){ list.push({type:'new',kw:kw.trim()}); listHtml+=`新增「${esc(kw.trim())}」到基础数据
`; }
+ // 单据头 combo(供应商/仓库):弹层置顶显式搜索框(持久,避免重渲染丢焦点);网格 combo 仍用「输入即搜+键盘流」
+ if(combo.dataset.scope==='doc'){
+ const withSummary=combo.dataset.summary==='1';
+ let body=pop.querySelector('.cp-body');
+ if(!body){
+ const ph=combo.dataset.field==='partner'?('搜索'+(mode==='in'?'供应商':'客户')+'…'):'搜索仓库…';
+ pop.innerHTML=`${withSummary?'':''}`;
+ const cps=pop.querySelector('.cps-input'); const fieldInput=combo.querySelector('input');
+ cps.value=fieldInput.value;
+ cps.addEventListener('input',()=>{ fieldInput.value=cps.value; renderPop(combo); });
+ setTimeout(()=>{ cps.focus(); cps.setSelectionRange(cps.value.length,cps.value.length); },0);
+ body=pop.querySelector('.cp-body');
+ }
+ body.innerHTML=listHtml;
+ if(withSummary){ const foot=pop.querySelector('.cp-foot'); if(foot) foot.innerHTML = kw.trim()? `匹配 ${matched.length} 条 · 共 ${opts.length} 条` : `共 ${opts.length} 条结果`; }
+ } else {
+ pop.innerHTML=listHtml;
+ }
+ // 位置
+ const r=combo.getBoundingClientRect();
+ const w=Math.max(r.width, 220);
+ pop.style.display='flex'; pop.style.width=w+'px'; pop.style.left=Math.min(r.left, window.innerWidth-w-8)+'px';
+ pop.style.top=(r.bottom+4)+'px';
+ const ph=pop.getBoundingClientRect();
+ if(r.bottom+ph.height+8>window.innerHeight && r.top-ph.height-4>0){ pop.style.top=(r.top-ph.height-4)+'px'; }
+ // 当前高亮:选中项优先,否则首项
+ let curIdx=list.findIndex(x=>x.type==='opt'&&x.id===cur); if(curIdx<0) curIdx=list.length?0:-1;
+ comboState={combo,list,cur:curIdx};
+ paintCur();
+}
+function paintCur(){ pop.querySelectorAll('.cp-item,.cp-new').forEach(el=>el.classList.toggle('cur', +el.dataset.i===comboState.cur)); const el=pop.querySelector('.cur'); if(el) el.scrollIntoViewIfNeeded?.(); }
+function moveCur(d){ if(!comboState||!comboState.list.length) return; comboState.cur=(comboState.cur+d+comboState.list.length)%comboState.list.length; paintCur(); }
+pop.addEventListener('mousedown',e=>{ const it=e.target.closest('[data-i]'); if(!it) return; e.preventDefault(); comboState.cur=+it.dataset.i; pickCur(); });
+pop.addEventListener('mousemove',e=>{ const it=e.target.closest('[data-i]'); if(it && comboState){ comboState.cur=+it.dataset.i; paintCur(); } });
+
+function pickCur(){ if(!comboState) return; const item=comboState.list[comboState.cur]; if(!item) return; pickCombo(activeCombo,item); }
+function pickCombo(combo,item){
+ const field=combo.dataset.field, scope=combo.dataset.scope;
+ let id=item.id;
+ if(item.type==='new'){ id=addDict(combo.dataset.dict,item.kw); }
+ if(scope==='doc'){
+ state.doc[field]=id; state.docErr.delete(field);
+ closePop(); const input=combo.querySelector('input'); input.value=idName(combo.dataset.dict,id); combo.classList.add('has'); combo.classList.remove('error'); paintCaf(combo);
+ refreshSummary(); draftSave();
+ return;
+ }
+ // row
+ const i=+combo.dataset.row, r=state.rows[i];
+ r[field]=id; r._err.delete(field);
+ if(field==='name'){ const nm=NAMES.find(n=>n.id===id);
+ if(mode==='in'){ if(!r.series)r.series=nm.dSeries; if(!r.spec)r.spec=nm.dSpec; if(!r.price)r.price=String(nm.cost); if(!r.sale)r.sale=String(nm.sale); }
+ else { r.series=nm.dSeries; r.spec=nm.dSpec; r.price=String(nm.sale); r.sale=String(nm.sale); r.avail=nm.stock; }
+ }
+ closePop();
+ renderDetail(); updateTotals(); draftSave();
+ if(editFields(mode).includes(field)) advance(i, field);
+}
+function addDict(key,name){ const id='x'+(++SEQ); const o={id,name,py:name.toLowerCase(),init:name.toLowerCase()}; if(key==='names'){Object.assign(o,{dSeries:'s53',dSpec:'p500',cost:0,sale:0,stock:0});} (DICT[key]||DICT.names).push(o); toast('已新增「'+name+'」到基础数据'); return id; }
+function comboCaf(span){ const combo=span.closest('.combo'); if(combo.classList.contains('has')){ // 清除
+ const f=combo.dataset.field;
+ if(combo.dataset.scope==='doc'){ state.doc[f]=null; combo.classList.remove('has'); combo.querySelector('input').value=''; paintCaf(combo); refreshSummary(); draftSave(); }
+ else { const i=+combo.dataset.row; state.rows[i][f]=null; renderDetail(); }
+ } else { combo.querySelector('input').focus(); }
+}
+function paintCaf(combo){ const has=combo.classList.contains('has'); const svg=combo.querySelector('.caf svg'); if(svg) svg.innerHTML = has? '' : ''; }
+function closePop(){ pop.style.display='none'; pop.innerHTML=''; if(activeCombo) activeCombo.classList.remove('open'); activeCombo=null; comboState=null; }
+function closeCombo(){ if(!activeCombo) return; const combo=activeCombo, input=combo.querySelector('input'); input.value=displayOf(combo); closePop(); }
+
+/* ---------------- 键盘流 ---------------- */
+function focusCell(rowIdx,field){
+ const host=document.querySelector(`[data-row="${rowIdx}"][data-field="${field}"]`);
+ if(!host) return false;
+ if(host.classList&&host.classList.contains('datefield')){ host.focus(); return true; }
+ const input = host.matches('input')? host : host.querySelector('input');
+ if(input){ input.focus(); input.select&&input.select(); return true; }
+ return false;
+}
+function advance(rowIdx,fromField){
+ const flds=editFields(mode), reqs=reqFields(mode);
+ let start=flds.indexOf(fromField)+1;
+ for(let k=start;k=0) return focusCell(rowIdx,flds[k]);
+ if(rowIdx>0) return focusCell(rowIdx-1, flds[flds.length-1]);
+}
+
+/* ---------------- 行操作 ---------------- */
+function addRow(focusIt){ state.rows.push(newRow()); renderDetail(); updateTotals(); draftSave(); if(focusIt){ const i=state.rows.length-1; focusCell(i, editFields(mode)[0]); } }
+function delRow(i){ if(state.rows.length<=1){ state.rows=[newRow()]; } else state.rows.splice(i,1); renderDetail(); updateTotals(); draftSave(); }
+function copyRow(i){ const s=state.rows[i]; const r=newRow(); r.name=s.name; r.series=s.series; r.spec=s.spec; r.price=s.price; r.sale=s.sale; r.avail=s.avail; state.rows.splice(i+1,0,r); renderDetail(); updateTotals(); draftSave(); focusCell(i+1, mode==='in'?'prodDate':'qty'); }
+function copyLast(){ if(state.rows.length) copyRow(state.rows.length-1); }
+function copyRowFromFocus(){ const el=document.activeElement; const host=el&&el.closest&&el.closest('[data-row]'); if(host) copyRow(+host.dataset.row); else if(state.rows.length) copyRow(state.rows.length-1); }
+
+/* ---------------- 渲染:明细(网格 / 卡片)---------------- */
+const detail=document.getElementById('detail');
+function readonlyMode(){ return status==='pending'||status==='approved'; }
+function comboHTML(i,field,dict,create,cell,ph){ const r=state.rows[i]; const val=idName(dict,r[field]); const has=!!r[field]; const err=(r._err&&r._err.has(field))?'error':''; const xsvg=has?'':'';
+ return `
`; }
+function comboCellHTML(i,field,cell){ const def=COLDEF[field]; const ph=field==='name'?'搜索名称/拼音…':'选择'+def.label; return comboHTML(i,field,def.dict,!!def.create,cell,ph); }
+function optRowHTML(i,nCols){ const fields=OPTFIELDS.map(f=>`${comboHTML(i,f.k,f.dict,false,false,'选择'+f.label+'(可选)')}
`).join(''); return `选填信息 · 产地 / 保质期 / 储存方式 / 介绍文档 ${fields} |
`; }
+
+let curLayout=null;
+function viewportW(){ return document.documentElement.clientWidth || window.innerWidth || 1024; }
+function renderDetail(){
+ closePop(); renderDetailHead();
+ const layout = viewportW()<600?'cards':'grid';
+ curLayout=layout;
+ detail.innerHTML = layout==='grid'? gridHTML() : cardsHTML();
+}
+function relayout(){ const layout=viewportW()<600?'cards':'grid'; if(layout!==curLayout) renderDetail(); }
+function renderDetailHead(){ const el=document.getElementById('dhAct'); if(!el) return; let h=''; if(readonlyMode()){ el.innerHTML=''; return; } if(mode==='out') h+=``; h+=``; el.innerHTML=h; }
+function toggleExpand(i){ state.rows[i].expanded=!state.rows[i].expanded; renderDetail(); }
+function gridHTML(){
+ const cols=MODES[mode].cols; const ro=readonlyMode();
+ let head=''+cols.map(k=>{ const d=COLDEF[k]; if(k==='idx')return '| # | '; if(k==='act')return ' | '; const req=d.req&&!(mode==='in'&&k==='price'); const cls=(d.num?'num ':'')+(req?'req':''); const style=d.w?`style="width:${d.w}px"`:''; const lbl=(mode==='in'&&k==='price')?'单价(可待定)':(d.label||''); return `${lbl} | `; }).join('')+'
';
+ let body=state.rows.map((r,i)=>{
+ const rerr=r._err.size?'error':'';
+ const tds=cols.map(k=>{ const d=COLDEF[k];
+ if(k==='idx') return `${i+1} | `;
+ if(k==='name') return `${comboCellHTML(i,'name',true)} | `;
+ if(k==='series'||k==='spec'){ if(mode==='out') return `${esc(idName(d.dict,r[k]))||'—'} | `; return `${comboCellHTML(i,k,true)} | `; }
+ if(k==='avail'){ const v=r.avail; const c=v==null?'color:var(--faint)':(v<=0?'color:var(--danger)':''); return `${v==null?'—':v} | `; }
+ if(k==='prodDate'){ const ph=r.prodDate?'':' ph'; return `${r.prodDate?esc(r.prodDate):'选择日期'} | `; }
+ if(k==='batch') return ` | `;
+ if(k==='qty') return ` | `;
+ if(k==='price') return ` | `;
+ if(k==='sale') return ` | `;
+ if(k==='amount') return `${amtHTML(r)} | `;
+ if(k==='act'){ const exp = mode==='in'?``:''; return ` | `; }
+ return ' | ';
+ }).join('');
+ return `${tds}
` + (mode==='in'&&r.expanded?optRowHTML(i,cols.length):'');
+ }).join('');
+ const add = ro?'':``;
+ return ``;
+}
+function cardsHTML(){
+ const ro=readonlyMode();
+ const cards=state.rows.map((r,i)=>{
+ const rerr=r._err.size?'error':'';
+ const sub=[idName('series',r.series),idName('specs',r.spec)].filter(Boolean).join(' · ');
+ let rows='';
+ rows+=mrow('名称',true,comboCellHTML(i,'name',false));
+ if(mode==='in'){ rows+=mrow('系列',true,comboCellHTML(i,'series',false)); rows+=mrow('规格',true,comboCellHTML(i,'spec',false));
+ rows+=mrow('生产日期',true,`${r.prodDate?esc(r.prodDate):'选择日期'} `);
+ rows+=mrow('批次号',true,``);
+ rows+=`${r.expanded?'⌃ 收起选填项':'⌄ 展开选填项(产地/保质期/储存…)'}
`;
+ if(r.expanded){ OPTFIELDS.forEach(f=>{ rows+=mrow(f.label,false,comboHTML(i,f.k,f.dict,false,false,'选择'+f.label)); }); }
+ } else {
+ rows+=mrow('系列',false,`${esc(idName('series',r.series))||'—'}`);
+ rows+=mrow('规格',false,`${esc(idName('specs',r.spec))||'—'}`);
+ rows+=mrow('可用库存',false,`${r.avail==null?'—':r.avail}`);
+ }
+ rows+=mrow('数量',true,``);
+ rows+=mrow(mode==='in'?'单价(可待定)':'单价',mode!=='in',``);
+ rows+=mrow('出售价格',true,``);
+ rows+=``;
+ const act=ro?'':``;
+ return `商品 ${i+1}${sub?`${esc(sub)}`:''}${act}
${rows}
`;
+ }).join('');
+ const add=ro?'':``;
+ return `${cards}${add}
`;
+}
+function mrow(label,req,inner){ return ``; }
+
+function updateTotals(){
+ state.rows.forEach((r,i)=>{ const el=document.querySelector(`[data-amt="${i}"]`); if(el) el.innerHTML=amtHTML(r); });
+ document.getElementById('grandTotal').textContent=fmt(grandTotal());
+ document.getElementById('rowsN').textContent='明细 '+state.rows.length+' 行';
+}
+
+/* ---------------- 单据头 ---------------- */
+function refreshSummary(){
+ const p=idName(MODES[mode].partnerDict,state.doc.partner)||'—';
+ const w=idName('warehouses',state.doc.warehouse)||'—';
+ document.getElementById('docSummary').innerHTML=`${esc(w)} · ${esc(p)} · ${esc(state.doc.date)}`;
+}
+function syncDocCombos(){
+ document.querySelectorAll('.combo[data-scope="doc"]').forEach(c=>{ const v=curValueOf(c); const t=idName(c.dataset.dict,v); c.querySelector('input').value=t; c.classList.toggle('has',!!v); c.classList.toggle('error',state.docErr.has(c.dataset.field)); paintCaf(c); });
+}
+
+/* ---------------- 校验 ---------------- */
+function validate(asDraft){
+ state.docErr.clear(); state.rows.forEach(r=>r._err.clear());
+ let first=null; const flag=(msg,focusFn)=>{ if(!first) first={msg,focusFn}; };
+ // 仓库:草稿也校验
+ if(!state.doc.warehouse){ state.docErr.add('warehouse'); flag(mode==='in'?'请选择入库仓库':'请选择出库仓库',()=>focusDoc('warehouse')); }
+ if(!state.rows.length){ flag('请至少添加一条明细',()=>{}); }
+ // 数量>0:草稿也校验
+ state.rows.forEach((r,i)=>{ if((parseFloat(r.qty)||0)<=0){ r._err.add('qty'); flag(`第 ${i+1} 行数量必须大于 0`,()=>focusCell(i,'qty')); } });
+ if(!asDraft){
+ if(!state.doc.partner){ state.docErr.add('partner'); flag(`请选择${MODES[mode].partnerLabel}`,()=>focusDoc('partner')); }
+ state.rows.forEach((r,i)=>{
+ if(!r.name){ r._err.add('name'); flag(`第 ${i+1} 行请选择商品名称`,()=>focusCell(i,'name')); }
+ if(mode==='in'){
+ if(!r.series){ r._err.add('series'); flag(`第 ${i+1} 行请选择系列`,()=>focusCell(i,'series')); }
+ if(!r.spec){ r._err.add('spec'); flag(`第 ${i+1} 行请选择规格`,()=>focusCell(i,'spec')); }
+ if(!r.prodDate){ r._err.add('prodDate'); flag(`第 ${i+1} 行请填写生产日期`,()=>focusCell(i,'prodDate')); }
+ if(!r.batch || !r.batch.trim()){ r._err.add('batch'); flag(`第 ${i+1} 行请填写批次号`,()=>focusCell(i,'batch')); }
+ } else {
+ if(r.avail!=null && (parseFloat(r.qty)||0)>r.avail){ r._err.add('qty'); flag(`第 ${i+1} 行数量超出可用库存(${r.avail})`,()=>focusCell(i,'qty')); }
+ }
+ if(mode!=='in' && (!r.price || (parseFloat(r.price)||0)<=0)){ r._err.add('price'); flag(`第 ${i+1} 行请填写单价`,()=>focusCell(i,'price')); }
+ if(!r.sale || (parseFloat(r.sale)||0)<=0){ r._err.add('sale'); flag(`第 ${i+1} 行请填写出售价格`,()=>focusCell(i,'sale')); }
+ });
+ }
+ renderDetail(); syncDocCombos();
+ if(first){ toast(first.msg); setTimeout(()=>first.focusFn&&first.focusFn(),30); return false; }
+ return true;
+}
+function focusDoc(field){ const c=document.querySelector(`.combo[data-scope="doc"][data-field="${field}"]`); if(c) c.querySelector('input').focus(); }
+
+/* ---------------- 状态机 + 操作栏 ---------------- */
+function setStatus(s){ status=s; renderActions(); renderDetail(); syncDocLock(); }
+function syncDocLock(){ const ro=readonlyMode(); document.getElementById('remark').disabled=ro; document.getElementById('orderDate').classList.toggle('disabled',ro); document.querySelectorAll('.combo[data-scope="doc"] input').forEach(i=>i.readOnly=ro); }
+const STBADGE={draft:'草稿',pending:'待审核',approved:'已通过',rejected:'已驳回'};
+function renderActions(){
+ const b=document.getElementById('statusBadge');
+ if(STBADGE[status]){ b.style.display='inline-flex'; b.className='badge b-'+STBADGE[status]; b.textContent=STBADGE[status]; } else b.style.display='none';
+ document.getElementById('pageTitle').textContent=(status==='new'?'新建':'')+MODES[mode].title;
+ let desk='',mob='';
+ if(status==='pending'){
+ desk=`撤回
驳回
审核通过
`;
+ mob=`通过
⋯
`;
+ } else if(status==='approved'){
+ desk=`复制为新单
`;
+ mob=`打印
`;
+ } else {
+ const submitLabel=status==='rejected'?'重新提交':'提交审核';
+ desk=`暂存草稿
取消
`;
+ mob=`${submitLabel}
⋯
`;
+ }
+ document.getElementById('pageActions').innerHTML=`${desk}
${mob}
`;
+ document.getElementById('footActions').innerHTML=`${desk}
`;
+}
+function overflowMenu(btn){ const items= status==='pending'?[['撤回','withdraw'],['驳回','reject']]:[['暂存草稿','draft'],['取消','cancel']]; openSmallMenu(btn,items); }
+function doSubmit(asDraft){
+ if(asDraft){ // 暂存草稿:不做任何校验,直接存
+ state.docErr.clear(); state.rows.forEach(r=>r._err.clear());
+ setStatus('draft'); syncDocCombos(); toast('已保存为草稿'); draftClear(); draftSave(); return;
+ }
+ if(!validate(false)) return;
+ setStatus('pending'); toast(MODES[mode].title+'已提交审核'); draftClear();
+}
+function approve(){ setStatus('approved'); toast('审核通过 · 库存已更新、流水已记账(后端事务)'); renderNotice(); }
+function withdraw(){ setStatus('draft'); toast('已撤回到草稿'); }
+function reject(){ openOv('驳回单据', ``,
+ [['取消','ghost','closeOv()'],['确认驳回','danger','confirmReject()']]); }
+let rejectReason='';
+function confirmReject(){ rejectReason=document.getElementById('rejReason').value.trim()||'(未填写理由)'; closeOv(); setStatus('rejected'); renderNotice(); toast('已驳回'); }
+
+/* ---------------- 提示条(草稿恢复 / 驳回理由)---------------- */
+function renderNotice(){
+ const slot=document.getElementById('noticeSlot'); slot.innerHTML='';
+ if(status==='rejected'){ slot.innerHTML=`单据被驳回:${esc(rejectReason)}
`; return; }
+ if(status==='approved'){ slot.innerHTML=`已通过审核,库存与流水已由后端在同一事务更新。本单据现为只读。
`; return; }
+}
+
+/* ---------------- 草稿持久化 ---------------- */
+let draftT=null;
+function draftKey(){ return 'jiu-recdoc-'+mode; }
+function isDirty(){ if(state.doc.partner||state.doc.warehouse||state.doc.remark.trim()) return true; return state.rows.some(r=>r.name||r.batch||r.prodDate||(r.price&&+r.price>0)); }
+function draftSave(){ if(readonlyMode()) return; clearTimeout(draftT); draftT=setTimeout(()=>{ if(!isDirty()){ localStorage.removeItem(draftKey()); return; } localStorage.setItem(draftKey(), JSON.stringify({doc:state.doc,rows:state.rows.map(r=>({...r,_err:undefined})),ts:Date.now()})); },400); }
+function draftClear(){ localStorage.removeItem(draftKey()); }
+function maybeRestore(){
+ const slot=document.getElementById('noticeSlot'); slot.innerHTML='';
+ const raw=localStorage.getItem(draftKey()); if(!raw) return;
+ let d; try{ d=JSON.parse(raw); }catch(e){ return; }
+ if(!d.rows||!d.rows.some(r=>r.name||r.price)) return;
+ const t=new Date(d.ts).toLocaleString('zh-CN',{month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'});
+ slot.innerHTML=`检测到未提交的${MODES[mode].title}草稿(${t}),是否恢复?
恢复放弃
`;
+}
+function restoreDraft(){ const d=JSON.parse(localStorage.getItem(draftKey())); state.doc=Object.assign({remark:''},d.doc); state.rows=d.rows.map(r=>Object.assign(newRow(),r,{_err:new Set()})); document.getElementById('noticeSlot').innerHTML=''; setDateField(document.getElementById('orderDate'),state.doc.date); document.getElementById('remark').value=state.doc.remark||''; syncDocCombos(); refreshSummary(); renderDetail(); updateTotals(); toast('已恢复草稿'); }
+function discardDraft(){ draftClear(); document.getElementById('noticeSlot').innerHTML=''; }
+
+/* ---------------- 退出守卫 ---------------- */
+function exitGuard(){ if(!isDirty()){ resetForm(); toast('已取消'); return; } openOv('未保存的内容', `当前${MODES[mode].title}尚未提交。是否保存为草稿,以免内容丢失?
`, [['继续编辑','ghost','closeOv()'],['放弃','danger','discardAndReset()'],['保存草稿','primary','closeOv();doSubmit(true)']]); }
+function discardAndReset(){ closeOv(); draftClear(); resetForm(); toast('已放弃录入'); }
+function resetForm(){ status='new'; state.doc={partner:null,warehouse:null,date:today(),remark:''}; state.docErr.clear(); state.rows=[newRow()]; setDateField(document.getElementById('orderDate'),state.doc.date); document.getElementById('remark').value=''; document.getElementById('noticeSlot').innerHTML=''; renderActions(); syncDocCombos(); syncDocLock(); refreshSummary(); renderDetail(); updateTotals(); }
+
+/* ---------------- 模式切换(入库 / 出库镜像)---------------- */
+function switchMode(m){ if(m===mode) return; mode=m; status='new';
+ document.querySelectorAll('#modeSeg button').forEach(b=>b.classList.toggle('on',b.dataset.mode===m));
+ document.getElementById('partnerLabel').innerHTML=MODES[m].partnerLabel+' *';
+ const pc=document.querySelector('.combo[data-scope="doc"][data-field="partner"]'); pc.dataset.dict=MODES[m].partnerDict; pc.querySelector('input').setAttribute('placeholder', m==='in'?'选择或搜索供应商…':'选择或搜索客户…'); pc.querySelector('input').dataset.ph0='';
+ document.getElementById('handlerLabel').textContent=MODES[m].handler;
+ document.querySelector('.dochead-grid .field:nth-child(2) label').innerHTML=(m==='in'?'入库仓库':'出库仓库')+' *';
+ document.getElementById('kbdHint').style.display = m==='in'?'':'';
+ state.doc={partner:null,warehouse:null,date:today(),remark:''}; state.docErr.clear(); state.rows=[newRow()];
+ setDateField(document.getElementById('orderDate'),state.doc.date); document.getElementById('remark').value='';
+ renderActions(); syncDocCombos(); syncDocLock(); refreshSummary(); renderDetail(); updateTotals(); maybeRestore();
+}
+
+/* ---------------- 侧栏 / 主题 / 通用(承袭 inventory.html)---------------- */
+const NAVS=[{g:'单据'},{k:'stock-in',label:'入库管理',d:'M12 3v12 M8 11l4 4 4-4 M4 21h16'},{k:'stock-out',label:'出库管理',d:'M12 15V3 M8 7l4-4 4 4 M4 21h16'},{g:'经营'},{k:'inventory',label:'库存管理',d:'M3 7l9-4 9 4-9 4-9-4z M3 7v10l9 4 9-4V7 M12 11v10'},{k:'finance',label:'财务管理',d:'M3 5h18v14H3z M3 10h18'},{k:'partners',label:'往来单位',d:'M9 8a3 3 0 100-0.01 M3 20c0-3 3-5 6-5s6 2 6 5'},{k:'products',label:'基础数据',d:'M3 3h7v7H3z M14 3h7v7h-7z M3 14h7v7H3z M14 14h7v7h-7z'},{g:'系统'},{k:'devices',label:'设备管理',d:'M4 4h16v12H4z M8 20h8 M12 16v4'},{k:'settings',label:'系统设置',d:'M12 9a3 3 0 100 6 3 3 0 000-6z M4 12h2 M18 12h2 M12 4v2 M12 18v2'},{k:'about',label:'关于我们',d:'M12 16v-4 M12 8h.01 M12 3a9 9 0 100 18 9 9 0 000-18z'}];
+function navSvg(d){ return ``; }
+function buildNav(){ let h=''; for(const n of NAVS){ if(n.g){h+=`${n.g}
`;continue;} const active=(n.k==='stock-in'&&mode==='in')||(n.k==='stock-out'&&mode==='out'); const HREF={inventory:'inventory.html',finance:'finance.html',partners:'partners.html',products:'products.html',settings:'settings.html',devices:'devices.html',about:'about.html'}; h+= HREF[n.k] ? `${navSvg(n.d)}${n.label}` : `${navSvg(n.d)}${n.label}
`; } h+=``; document.getElementById('side').innerHTML=h; }
+function navClick(k){ closeSide(); if(k==='stock-in'){ switchMode('in'); buildNav(); } else if(k==='stock-out'){ switchMode('out'); buildNav(); } }
+function closeSide(){ document.getElementById('side').classList.remove('open'); document.getElementById('scrim').classList.remove('show'); }
+document.getElementById('ham').onclick=()=>{ document.getElementById('side').classList.toggle('open'); document.getElementById('scrim').classList.toggle('show'); };
+document.getElementById('scrim').onclick=closeSide;
+
+const THEMES={a:{name:'经典蓝'},b:{name:'琥珀'},c:{name:'酒窖'}};
+document.getElementById('themePill').onclick=e=>{ const cur=document.documentElement.dataset.theme; openSmallMenu(e.currentTarget,Object.entries(THEMES).map(([k,v])=>[k.toUpperCase()+' · '+v.name,'theme:'+k]),true); };
+function setTheme(t){ document.documentElement.dataset.theme=t; document.getElementById('themeName').textContent=THEMES[t].name; localStorage.setItem('jiu-theme',t); }
+
+/* 小菜单(主题 / 溢出)*/
+let menuEl=null;
+function closeMenus(){ if(menuEl){menuEl.remove();menuEl=null;} }
+function openSmallMenu(t,items,isTheme){ closeMenus(); const r=t.getBoundingClientRect(); menuEl=document.createElement('div'); menuEl.className='menu'; menuEl.style.left=Math.min(r.left,window.innerWidth-180)+'px'; menuEl.style.top=(r.bottom+6)+'px'; menuEl.innerHTML=items.map(([label,act])=>``).join(''); menuEl.querySelectorAll('.menu-item').forEach(el=>el.onclick=()=>{ const a=el.dataset.act; closeMenus(); if(a.startsWith('theme:'))setTheme(a.slice(6)); else if(a==='draft')doSubmit(true); else if(a==='cancel')exitGuard(); else if(a==='withdraw')withdraw(); else if(a==='reject')reject(); else if(a==='profile')toast('个人设置(待 Phase 4)'); else if(a==='logout')toast('退出登录(原型)'); }); document.body.appendChild(menuEl); }
+function openUserMenu(e){ openSmallMenu(e.currentTarget, [['个人设置','profile'],['退出登录','logout']]); }
+
+/* 弹窗 */
+function openOv(title,body,acts,opts){ const m=document.querySelector('#ov .modal'); if(m) m.style.width=(opts&&opts.width)||'440px'; document.getElementById('ovTitle').textContent=title; document.getElementById('ovBody').innerHTML=body; document.getElementById('ovFoot').innerHTML=acts.map(([l,c,fn])=>`${l}
`).join(''); document.getElementById('ov').classList.add('show'); }
+function closeOv(){ document.getElementById('ov').classList.remove('show'); }
+
+/* Toast */
+let toastT; function toast(m){ const t=document.getElementById('toast'); t.textContent=m; t.classList.add('show'); clearTimeout(toastT); toastT=setTimeout(()=>t.classList.remove('show'),2200); }
+
+/* ---------------- 出库:从库存批量挑货 ---------------- */
+let pickSel=new Set(), pickKw='';
+function openStockPicker(){ pickSel=new Set(); pickKw=''; openOv('从库存选择商品(出库)', pickerBody(), [['取消','ghost','closeOv()'],['加入明细','primary','pickerConfirm()']], {width:'640px'}); pickerRender(); setTimeout(()=>{const s=document.getElementById('pickSearch'); if(s)s.focus();},60); }
+function pickerBody(){ return `商品系列售价可用库存
`; }
+function pickerRender(){ const el=document.getElementById('pickList'); if(!el) return; const list=NAMES.filter(n=>n.stock>0&&match(n,pickKw)); el.innerHTML = list.length? list.map(n=>{ const on=pickSel.has(n.id); const ck=on?'':''; return `${ck}${highlight(n.name,pickKw)}
${idName('specs',n.dSpec)}
${idName('series',n.dSeries)}${fmt(n.sale)}${n.stock} `; }).join('') : '无匹配的在库商品
'; document.getElementById('pickCount').textContent='已选 '+pickSel.size+' 项'+(pickSel.size?' · 将加入明细':''); }
+function pickerToggle(id){ pickSel.has(id)?pickSel.delete(id):pickSel.add(id); pickerRender(); }
+function pickerConfirm(){ if(!pickSel.size){ toast('请先勾选商品'); return; } const ids=[...pickSel]; ids.forEach(id=>{ const nm=NAMES.find(n=>n.id===id); let r=state.rows.find(x=>!x.name); if(!r){ r=newRow(); state.rows.push(r); } r.name=nm.id; r.series=nm.dSeries; r.spec=nm.dSpec; r.price=String(nm.sale); r.sale=String(nm.sale); r.avail=nm.stock; if(!r.qty||+r.qty<=0) r.qty='1'; }); closeOv(); renderDetail(); updateTotals(); draftSave(); toast('已加入 '+ids.length+' 项商品'); }
+
+/* ---------------- 全局事件 ---------------- */
+document.addEventListener('focusin',e=>{ const c=e.target.closest&&e.target.closest('.combo'); if(c&&e.target.tagName==='INPUT'&&!e.target.readOnly){ openCombo(c); } });
+document.addEventListener('input',e=>{ const t=e.target; if(t.closest&&t.closest('.combo')&&t.tagName==='INPUT'){ if(activeCombo)renderPop(activeCombo); return; }
+ if(t.dataset&&t.dataset.field!==undefined&&t.closest('#detail')){ const i=+t.dataset.row,f=t.dataset.field; state.rows[i][f]=t.value; state.rows[i]._err.delete(f); t.classList.remove('error'); if(f==='qty'||f==='price')updateTotals(); draftSave(); } });
+function setDateField(el,val){ if(!el) return; el.dataset.val=val||''; const dv=el.querySelector('.dval'); if(!dv) return; if(val){ dv.textContent=val; dv.classList.remove('ph'); } else { dv.textContent='选择日期'; dv.classList.add('ph'); } }
+document.addEventListener('datechange',e=>{ const el=e.target;
+ if(el.id==='orderDate'){ state.doc.date=e.detail.value; refreshSummary(); draftSave(); return; }
+ if(el.dataset&&el.dataset.field==='prodDate'&&el.closest&&el.closest('#detail')){ const i=+el.dataset.row; state.rows[i].prodDate=e.detail.value; state.rows[i]._err.delete('prodDate'); el.classList.remove('error'); draftSave(); advance(i,'prodDate'); }
+});
+document.getElementById('remark').addEventListener('input',e=>{ state.doc.remark=e.target.value; draftSave(); });
+document.getElementById('docCollapse').onclick=()=>{ const dh=document.getElementById('dochead'); dh.classList.toggle('folded'); document.getElementById('collapseTxt').textContent=dh.classList.contains('folded')?'展开':'收起'; document.querySelector('#docCollapse svg').style.transform=dh.classList.contains('folded')?'rotate(180deg)':''; };
+document.querySelectorAll('#modeSeg button').forEach(b=>b.onclick=()=>{ switchMode(b.dataset.mode); buildNav(); });
+
+document.addEventListener('keydown',e=>{
+ if((e.metaKey||e.ctrlKey)&&e.key==='Enter'){ e.preventDefault(); if(!readonlyMode())doSubmit(false); return; }
+ if((e.metaKey||e.ctrlKey)&&(e.key==='d'||e.key==='D')){ e.preventDefault(); if(!readonlyMode())copyRowFromFocus(); return; }
+ if(e.key==='Escape'){ if(activeCombo){closeCombo();return;} closeMenus(); closeOv(); closeSide(); return; }
+ if(activeCombo){ if(e.key==='ArrowDown'){e.preventDefault();moveCur(1);} else if(e.key==='ArrowUp'){e.preventDefault();moveCur(-1);} else if(e.key==='Enter'){e.preventDefault();pickCur();} else if(e.key==='Tab'){e.preventDefault(); const combo=activeCombo,i=+combo.dataset.row,f=combo.dataset.field,scope=combo.dataset.scope; pickCur(); if(scope==='row'&&e.shiftKey)retreat(i,f);} return; }
+ const t=e.target; if(t.dataset&&t.dataset.field!==undefined&&t.closest&&t.closest('#detail')&&(t.tagName==='INPUT')){ const i=+t.dataset.row,f=t.dataset.field; if(e.key==='Enter'){e.preventDefault();advance(i,f);} else if(e.key==='Tab'){e.preventDefault(); e.shiftKey?retreat(i,f):advance(i,f);} }
+});
+document.addEventListener('mousedown',e=>{ if(activeCombo && !e.target.closest('.combo') && !e.target.closest('.combo-pop')) closeCombo(); if(menuEl && !e.target.closest('.menu') && !menuEl.contains(e.target)) closeMenus(); });
+let rzT; window.addEventListener('resize',()=>{ clearTimeout(rzT); rzT=setTimeout(relayout,150); });
+
+/* ---------------- 启动 ---------------- */
+function init(){ setTheme(localStorage.getItem('jiu-theme')||'a'); buildNav(); state.rows=[newRow()]; setDateField(document.getElementById('orderDate'),state.doc.date); renderActions(); syncDocCombos(); syncDocLock(); refreshSummary(); renderDetail(); updateTotals(); maybeRestore(); }
+init();
+const __qp=new URLSearchParams(location.search); if(__qp.get('mode')==='out'){ switchMode('out'); buildNav(); }
+// 初次加载 iframe 宽度可能尚未稳定:用 ResizeObserver 在 body 有真实宽度的那一刻纠正布局,避免卡片闪烁
+if(window.ResizeObserver){ new ResizeObserver(()=>relayout()).observe(document.body); }
+window.addEventListener('load',relayout);
+if(window.matchMedia) matchMedia('(max-width:600px)').addEventListener('change',relayout);
+requestAnimationFrame(relayout);
diff --git a/.superpowers/prototype/screens/stock-out-list.html b/.superpowers/prototype/screens/stock-out-list.html
new file mode 100644
index 0000000..617c2c5
--- /dev/null
+++ b/.superpowers/prototype/screens/stock-out-list.html
@@ -0,0 +1,200 @@
+
+
+
+
+
+出库管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/stock-out.html b/.superpowers/prototype/screens/stock-out.html
new file mode 100644
index 0000000..513785e
--- /dev/null
+++ b/.superpowers/prototype/screens/stock-out.html
@@ -0,0 +1,10 @@
+
+
+
+
+新建出库单 · 岩美酒库
+
+
+
+
+
diff --git a/.superpowers/prototype/screens/users.html b/.superpowers/prototype/screens/users.html
new file mode 100644
index 0000000..f68a66f
--- /dev/null
+++ b/.superpowers/prototype/screens/users.html
@@ -0,0 +1,228 @@
+
+
+
+
+
+用户管理 · 岩美酒库
+
+
+
+
+
+
+
+
+
+
+
+
用户管理
门店成员与权限角色
+
+
新增用户
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
超级管理员最高权限 · 含系统参数与数据管理
+
管理员全部业务权限 + 用户与授权管理
+
操作员日常入库出库与查询
+
只读仅查看,禁所有写操作
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.superpowers/prototype/serve.mjs b/.superpowers/prototype/serve.mjs
new file mode 100644
index 0000000..de7d6c2
--- /dev/null
+++ b/.superpowers/prototype/serve.mjs
@@ -0,0 +1,96 @@
+// 零依赖本地热重载静态服务器
+// 用法: node serve.mjs [port] 默认 5180
+import http from 'node:http';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const ROOT = path.dirname(fileURLToPath(import.meta.url));
+const PORT = Number(process.argv[2]) || 5180;
+
+const MIME = {
+ '.html': 'text/html; charset=utf-8',
+ '.css': 'text/css; charset=utf-8',
+ '.js': 'text/javascript; charset=utf-8',
+ '.mjs': 'text/javascript; charset=utf-8',
+ '.json': 'application/json; charset=utf-8',
+ '.svg': 'image/svg+xml',
+ '.png': 'image/png',
+ '.jpg': 'image/jpeg',
+ '.woff2': 'font/woff2',
+};
+
+const clients = new Set();
+const RELOAD_SNIPPET = `\n\n`;
+
+const server = http.createServer((req, res) => {
+ if (req.url === '/__reload') {
+ res.writeHead(200, {
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ Connection: 'keep-alive',
+ });
+ res.write('retry: 500\n\n');
+ clients.add(res);
+ req.on('close', () => clients.delete(res));
+ return;
+ }
+
+ let urlPath = decodeURIComponent(req.url.split('?')[0]);
+ if (urlPath === '/') urlPath = '/index.html';
+ const filePath = path.join(ROOT, urlPath);
+ if (!filePath.startsWith(ROOT)) { res.writeHead(403); res.end('forbidden'); return; }
+
+ fs.readFile(filePath, (err, data) => {
+ if (err) { res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' }); res.end('404
'); return; }
+ const ext = path.extname(filePath).toLowerCase();
+ const mime = MIME[ext] || 'application/octet-stream';
+ const noCache = { 'Cache-Control': 'no-store, no-cache, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' };
+ if (ext === '.html') {
+ const html = data.toString('utf8').replace('