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 · 现状(精修参考)

现有标记 · 含商标 ™
+
+
+
岩美
岩美科技 · YANMEI TECH
+
+
岩美
岩美科技 · YANMEI TECH
+
+ +

产品 Logo · 岩美酒库

现有锁版精修
+
+
+
岩美酒库
酒水进销存管理系统
+
+ +

门店标识 · 自动字号

取门店名首字 · App 内左上角
+
+
鼎晟酒行DINGSHENG
+
茅韵名酒MAOYUN
+
老李酒水批发LAOLI
+
+ +

标记变体 / App 图标

主体 / 反白 / 单色 · 圆角图标
+
+
+
主体
+
反白
+
单色
+
+
+
App 图标 · 1024
+
64 · 省略酒滴
+
28 · 仅山形
+
+
+ +

安全区 / 最小尺寸

留白 = 标记高度的 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

圆角 / 间距网格 / 阴影系统,全主题统一。

+

圆角

--r-sm … --r-xl
+

间距网格

--sp-1 … --sp-8
+

阴影

--sh-1 / --sh-2 / --sh-3
+
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
+
+
5 条结果
+
+
+

日期字段(滚轮选择)

.datefield + .wheel-pop(年月日 · 单据/生产共用)· datewheel.js
+
2026-06-22
+
选择日期
+
+
2024
2025
2026
2027
2028
+
04
05
06
07
08
+
20
21
22
23
24
+
今天确定
+
+

筛选 Chip

.chip · 内嵌列头筛选入口
+
全部品类
+
分类 浓香
+
时间范围
+
+

状态徽章 · 全部

.badge .b-* · 对齐 schema 真实枚举
+
+
库存 · 按 min_stock 派生在售预警缺货
+
单据 · 入库/出库草稿待审核已通过已驳回
+
往来单位 · 启用停用启用停用供应商客户两者
+
用户角色管理员普通只读
+
设备在线状态在线离线
+
库存盘点进行中已完成
+
授权兑换券未使用已兑换作废
+
账单结清 · 收付未结清已结清收款付款
+
进价待定 · 调货暂估待定价待定价(入库进价留空/0,确认进价后回填)
+
+
+

KPI 卡

.kpi
+
SKU 总数
1,284
▲ 2.3%
+
缺货预警
17
需补货 6 项
+
+

表格行

.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 (列表/壳层原子)
+
每页 8 条 · 共 24
+
茅台 飞天 53°MT-FT-500在售
+
DSJH-001当前 18:00:00
已连接
+

通知

全部已读
入库单 待审核
茅台华东总代 · 3 项
10 分钟前
查看全部
+

通知中心列表行 .nt-item / 类型图标 .nt-ic(warn / danger / info / ok)—— 通知列表页与详情页共用,桌面/移动一致:

+ +

遮罩 .drawer-mask:抽屉/弹层背后的半透明背板,点击关闭。

+
+
+ + +
+

移动专属组件 .m-*

来自 mobile-atoms.css 的移动专属原子,由 mobile-shell.js 装配外壳。复用上方公用原子(按钮/徽章/分段/输入),此处只展示移动新增的 .m-*

+
打开移动 UI Kit · 全部 .m-* 实机展示
+

移动壳 + 列表(实机预览)

.m-top · .m-tabbar · .m-page · .m-kpi · .m-search · .m-filters/.m-pill · .m-card/.m-row · .m-fab
+
+
+
库存
3
+
+
+
SKU 总数
1,284
▲ 2.3%
+
库存货值
¥264万
▲ 1.5%
+
在库数量
18,920
+
缺货预警
17
需补货 6
+
+ +
全部
酱香
浓香
清香
+
商品 · 共 10
+
茅台 飞天 53°
MT-FT-500 · 500ml×6
在售128 件
+
剑南春 水晶剑
JNC-SJ-500 · 500ml×6
缺货8 件
+
+ +
+
+ +
+
.m-form · .m-field · .m-input · .m-select
+
+
+
酱香
+
+
+ +
.m-sheet-h · .m-opt(底部 sheet 内容)
+
+

筛选 · 香型

+
+
全部
+
酱香
+
浓香
+
+
+ +
.m-hub(我的 · 功能入口)
+
+ +
.m-actionbar(全屏表单底部操作条)
+
+
+
合计¥10,500
+
存草稿
提交审核
+
+
+
+
+
+
+ +
+

图标 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
+
+
+
+ + +
+

抽屉示例

编码MT-FT-500
库存128 件
状态在售
+
+ + + + + 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 @@ + + + + + +关于我们 · 岩美酒库 + + + + + +
+
+
+
+
+
+ + +
+
+ +

岩美酒库

酒水进销存 · 财务对账 · 防伪溯源一体化 · 岩美科技出品
+
+ +
+
意见反馈
遇到 Bug 或有功能建议,欢迎告诉我们
+
+
反馈 Bug
+
功能建议
+
+
+
+ + +
+

产品信息

+
+
当前版本
v1.0.72
检查更新
+
出品方
岩美科技 · YANMEI TECH
+
多端覆盖
Web · macOS · Windows · iOS · 安卓
+
产品定位
酒水进销存 / 财务 / 防伪溯源
+
+
+ + +
+

授权信息

+
+
授权状态
已授权
+
到期日
2027-03-15
+
剩余天数
267 天
+
+
+ + +
+

更新日志

+
+
更多历史更新
+
+ +
© 2026 岩美科技 · 保留所有权利
+
+
+
+
+ + +
+ +
+ + + + + + + + + + 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 @@ + + + + + +财务管理 · 岩美酒库 + + + + + +
+
+
+
+
+
+

财务管理

应收/应付概览 · 本月 +
+
导出报表
+
登记收支
+
+
+ + +
+ 时间范围 +
本月
+
本季
+
自定义
+
+ + +
+
本月销售额
¥186万
▲ 8.4% 较上月
+
本月采购额
¥132万
▼ 3.1% 较上月
+
应收账款 · 偏高
¥48.6万
逾期 ¥9.2万
+
应付账款
¥31.8万
本周到期 ¥4.8万
+
+ + +
+

收支趋势

近 6 个月 · 单位 万元
+
+
+ 收入 + 支出 +
+
+
+
+ + +
+

应收 / 应付 汇总

按往来单位 · 点击行查看流水
+
+ + +
往来单位应收应付净额操作
+
+ + +
+

收支流水

+
+ 类型 +
全部
+
收款
+
付款
+
+ +
+
+ + +
日期类型往来单位金额关联单号备注
+
+
+
+
+ +

往来流水

+
+ + + + + + + + + + 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 @@ + + + + + +库存盘点 · 岩美酒库 + + + + + +
+
+
+
+
+ 库存管理 +
+

库存盘点

盘点中 +
+ 取消 +
暂存
+
提交盘点
+
+
+ +
+

盘点单信息

+
+
+
主仓
+
+
+
+
+ +
+

盘点明细

输入实盘数量自动计算差异 · 盈绿亏红
+
+
+ +
+
+
+
商品(名/编码)账面库存实盘数量差异备注
+
+
+
+ +
+
差异项 0
+
盘盈 +0
+
盘亏 -0
+
+
暂存
+
提交盘点
+
+
+
+
+
+ + + + + + + + + + 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 · 数据实时 +
+
列设置
+
导出
+
新增入库
+
+
+
+
SKU 总数
1,284
▲ 2.3% 较上月
+
库存货值
¥264万
▲ 1.1% 较上月
+
在库数量
18,920
▼ 0.6% 较上月
+
缺货预警 · 点击筛选
17
需补货 6 项
+
+
+ + +
状态
+
+
重置
+
+
+
每页
+
+
+

+
此页布局将在对应 Phase 细化 · 当前先验证导航与主题骨架
+
返回库存管理
+
+
+
+
+ +
+ +
+ + + + + + + + + + + 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 @@ + + + + + +登录 · 岩美酒库 + + + + + +
+ + + +
+ +
+
+ + + +
岩美酒库
+
酒水进销存 · 审核驱动业务流
+
+
入库出库 · 审核后实时改库存
+
往来对账 · 财务一目了然
+
多端协同 · 多租户安全隔离
+
+
+ +
+

登录

+

欢迎回来,请输入账号信息

+
+
+
+
+
+ 记住我 +
+ 忘记密码? +
+
登录
+
还没有门店账号?注册新门店 →
+
岩美酒库 © 2026 · 仅授权门店可登录
+
+
+ +
+ + + + + + + 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 @@ + + + + + +关于我们 · 岩美酒库 + + + + + + +
+
+
+
+
+
岩美酒库
+
v1.0.72 · 酒水进销存管理系统
+
检查更新
+
反馈 Bug
功能建议
+
+ +
产品信息
+
+
出品方岩美科技 · YANMEI TECH
+
多端覆盖Web · macOS · Windows · iOS · 安卓
+
产品定位酒水进销存 / 财务 / 防伪溯源
+
+ +
授权信息
+
+
授权状态已授权
+
到期日2027-03-15
+
剩余天数267 天
+
+ +
更新日志
+
+
+
+
+ + + + + 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 @@ + + + + + +财务管理 · 岩美酒库 + + + + + + +
+
+
+
+
本月收入
¥86.4万
▲ 8.2% 较上月
+
本月支出
¥52.1万
▲ 3.1% 较上月
+
应收合计
¥23.8万
12 笔未结
+
应付合计
¥9.6万
5 笔未结
+
+
+
应收账款
+
+
+
+ +
+ + + + + + 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 @@ + + + + + +库存 · 岩美酒库 + + + + + + +
+
+
+
+
SKU 总数
1,284
▲ 2.3% 较上月
+
库存货值
¥264万
▲ 1.5% 较上月
+
在库数量
18,920
件 · 实时
+
缺货预警
17
需补货 6 项
+
+ + + +
商品
+
+
+
+ +
+
+ + + + + + 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 @@ + + + + + +我的 · 岩美酒库 + + + + + + +
+
+
+
+
+
王经理
鼎晟酒行 · 13800138000
+ 管理员 +
+ +
经营管理
+ + +
系统
+ + +
退出登录
+
+
+ +
+ + + + + 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 @@ + + + + + +新增入库单 · 岩美酒库 + + + + + + +
+
+
+
+
选择或搜索往来单位…
+
主仓
+
+
+
+ +
入库明细
+
+
添加商品
+
+
+
+
合计¥0
+
存草稿
+
提交审核
+
+
+ + + + + + 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 @@ + + + + + +新增出库单 · 岩美酒库 + + + + + + +
+
+
+
+
选择或搜索往来单位…
+
主仓
+
+
+
+
出库明细
+
+
添加商品
+
+
+
+
合计¥0
+
存草稿
+
提交审核
+
+
+ + + + + + 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 @@ + + + + + +用户管理 · 岩美酒库 + + + + + + +
+
+
+
成员 · 共 4
+
+
+
+
+
+ + + + + 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 · 岩美酒库 + + + + + + +
+
+
+ +
.m-kpi —— KPI 卡(2×2)
+
+
SKU 总数
1,284
▲ 2.3% 较上月
+
库存货值
¥264万
▲ 1.5% 较上月
+
在库数量
18,920
▼ 0.6% 较上月
+
缺货预警
17
需补货 6 项
+
+ +
.m-search + .m-filters / .m-pill
+ +
+
全部
酱香
浓香
清香
缺货
+
+ +
.m-card / .m-row —— 列表项卡片
+ +
+
茅台 飞天 53°
MT-FT-500 · 500ml×6
+
在售128 件
+
+
+
+ +
+
RK-20260620-003
鼎晟供应链 · 6 项
+
待审核¥12,800
+
+
+
2026-06-20 · 经手人 王经理
+
+ +
.m-field / .m-input / .m-select —— 表单
+
+
+
酱香
+
+
+ +
.btn —— 按钮(复用桌面原子)
+
+
主操作
+
次操作
+
危险
+
+ +
.seg —— 分段切换(复用)
+
+ +
.m-sheet —— 底部 sheet(点按钮弹出)
+
打开筛选 sheet
+ +
.m-hub —— 我的:功能入口
+ + +
.m-section / .m-empty
+
今日待办
+
暂无数据 · 下拉刷新或调整筛选
+ +
+
+ +
+
+ + + + + + 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) => + `
${ICONS[n.ic]}
${n.t}
${n.d}
${n.time}
` + ).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 @@ + + + + + +购买授权 · 岩美酒库 + + + + + +
+
+
+
+
+

购买授权

按时长购买兑换券,续期门店授权 · 无订阅、不自动扣费
+ +
+
+
当前授权
标准版 · 生效中
+
到期日
2026-09-16
+
剩余天数
剩余 87 天
+
+ 已有兑换券?去激活 → +
+ +
+ + +
+ +
+ +
+ + 购买后系统生成「时长兑换券」短码,在「系统设置 · 授权兑换券」输入即可叠加续期,到期日顺延。支付通道暂未接入,下单后由顾问对接开票与收款。 +
+
+
+
+ +
+ + + + + + + + + + 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 + 在售 +
+
编辑
+
入库
+
出库
+
+
+ +
+
当前库存
128
▲ 12 件 较上周
+
库存货值
¥34.3万
▲ 1.4% 较上月
+
近 30 天出库
86
▼ 4.5% 较上月
+
+ +
+
+

基本信息

+
+
系列飞天
+
规格53° 500ml×6
+
分类酱香
+
单位件 (6 瓶/件)
+
默认成本价¥2,680 / 件
+
条码6901234567890
+
+
+ +
+

价格历史

成本价变更
+
+
+
+ +
+

各仓库库存分布

合计 128 件 · ¥34.3万
+
+ +
仓库数量货值
+
+ +
+

近期流水

最近 8 条出入库记录
+
+ +
日期类型数量单号经手人
+
+
+
+
+
+ + + + + + + + + + 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 · 扫码已上链
+
+ +
+ +
+
岩美酒库 提供技术支持
+
YANMEI TECH · 酒水进销存与防伪溯源
+
+
+ + + + + + + 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 @@ + + + + + +鼎晟酒行 · 在售商品 + + + + + +
+ + + +
+ +
+ +
+
+
+
+
鼎晟酒行
+
+ + 岩美认证门店 +
+
+
+
十年老店,专营名优白酒与进口洋酒,正品保障、明码标价、支持扫码防伪溯源。
+
+
江苏省南京市建邺区江东中路 88 号
+
营业时间 09:00 - 21:30(全年无休)
+
+ + + 联系门店 · 025-8333 1234 + +
+ + +
+ +
+ +
在售商品共 8 款白酒
+ + + + + +
+
+ + 由 岩美酒库 提供技术支持 +
+
YANMEI TECH · 参考价仅供参考,以门店实际报价为准
+
+
+ + + + + + + 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 @@ + + + + + +注册门店 · 岩美酒库 + + + + + +
+ + + +
+ +
+
+ + + +
开通门店
+
几分钟创建你的酒水进销存门店,立即开始入库出库
+
+
1
填写门店信息店名、编号与联系人
+
2
创建管理员账号用于登录与管理成员
+
3
激活授权输入兑换券,或先试用
+
+
+ +
+

注册新门店

+

已有门店账号?直接登录 →

+ +
+
+
用于登录与单据抬头
+
+
+
+ +
+ +
+
+
+
+
+
留空将以试用模式创建,可在「系统设置 · 授权兑换券」随时激活
+
+ +
+ + 我已阅读并同意 服务条款隐私政策 +
+ +
创建门店并进入
+
创建即代表你将成为该门店的管理员
+
+
+ +
+ + + + + + + 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
普通停用
+
+
+
+ + +
+
+

授权兑换券

+

本系统以「时长兑换券」授权,输入兑换券短码即可续期,无需订阅

+
+
授权到期日
2026-09-16
+
剩余天数
剩余 87 天
+
+
+
+
+
兑换续期
+
+

没有兑换券?购买授权时长 →

+
+
+ + +
+
+

偏好设置

+

主题与默认值,仅影响当前账号在本端的体验

+ +
+
A 经典蓝
浅色 · 默认
+
B 琥珀
深色
+
C 酒窖
暖浅色
+
+
+
主仓
+
+
+
保存
+
+
+
+
+
+
+
+
+ +
+ + + + + + + + + + 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 `${d.split(' M').map((p, i) => ``).join('')}`; } + + // ---- 共享菜单 ---- + 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 += ``; 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 @@ + + + + + +入库管理 · 岩美酒库 + + + + + +
+
+
+
+
+
+

入库管理

本月 28 笔入库单 +
+
刷新
+
导出
+ 新增入库 +
+
+
+
本月入库笔数
28
▲ 12% 较上月
+
本月入库金额
¥186万
▲ 8.4% 较上月
+
待审核单数 · 点击筛选
4
需尽快处理
+
+
+ +
供应商
+
仓库
+
状态
+
入库时间
+
+
重置
+
+
+
每页
+
+
+
+ +
+

入库单详情

+ +
+
+ + + + + + + + + + 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 @@ + + + + + +录单 · 入库 / 出库 · 岩美酒库 + + + + + +
+
+
+
岩美酒库
+
+
经典蓝
+
+
+
+
+
+
+
+
+ +

新建入库单

+ +
+ + +
+
+
+ +
+
+ +
+
+ 单据信息 + + 收起 +
+
+
+
+
+
+
+
+
+
选择日期
+
+
+
王经理
+
+
+
+ + +
+
+ +
+ 商品明细 + Enter 下一格 · 末格自动加行 · ⌘D 复制行 · ⌘↵ 提交 +
+
+
+
+ +
+ 明细 0 行 +
合计金额 ¥0.00
+
+
+
+
+
+ +
+
+ + + + + + + + + + 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 `
${xsvg}
`; } +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 `
${exp}
`; } + return ''; + }).join(''); + return `${tds}` + (mode==='in'&&r.expanded?optRowHTML(i,cols.length):''); + }).join(''); + const add = ro?'':`
添加商品末格 Enter 自动加行
`; + return `
${head}${body}
${add}
`; +} +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+=`
${amtHTML(r)}
`; + const act=ro?'':`
`; + return `
商品 ${i+1}${sub?`${esc(sub)}`:''}${act}
${rows}
`; + }).join(''); + const add=ro?'':`
添加商品
`; + return `
${cards}${add}
`; +} +function mrow(label,req,inner){ return `
${inner}
`; } + +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=`
暂存草稿
${submitLabel}
取消
`; + 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 `${d.split(' M').map((p,i)=>``).join('')}`; } +function buildNav(){ let h=''; for(const n of NAVS){ if(n.g){h+=``;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}` : ``; } 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 @@ + + + + + +出库管理 · 岩美酒库 + + + + + +
+
+
+
+
+
+

出库管理

本月 31 笔出库单 +
+
刷新
+
导出
+ 新增出库 +
+
+
+
本月出库笔数
31
▲ 9% 较上月
+
本月出库金额
¥214万
▲ 6.2% 较上月
+
待审核单数 · 点击筛选
3
需尽快处理
+
+
+ +
客户
+
仓库
+
状态
+
出库时间
+
+
重置
+
+
+
每页
+
+
+
+ +
+

出库单详情

+
+ + + + + + + + + + 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 @@ + + + + + +用户管理 · 岩美酒库 + + + + + +
+
+
+
+
+
+

用户管理

门店成员与权限角色 +
+
新增用户
+
+ +
+
成员总数
4
+
启用中
3
+
管理员
1
+
只读账号
1
+
+ +
+ +
+
全部角色
+
+ +
+
+ + + +
用户角色状态最近登录操作
+
+
+
+
+
+ + +
+ +
+ +
+ + + + + + + + + + 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('', RELOAD_SNIPPET + ''); + res.writeHead(200, { 'Content-Type': mime, ...noCache }); + res.end(html); + } else { + res.writeHead(200, { 'Content-Type': mime, ...noCache }); + res.end(data); + } + }); +}); + +// 设计系统守门:启动 + 每次改动自动跑 check-ds.mjs,控制台报 PASS/FAIL +import { spawn } from 'node:child_process'; +function runDsCheck() { + const p = spawn(process.execPath, [path.join(ROOT, 'tools', 'check-ds.mjs')], { cwd: ROOT }); + let out = ''; + p.stdout.on('data', d => out += d); + p.stderr.on('data', d => out += d); + p.on('close', code => { + const t = new Date().toLocaleTimeString(); + if (code === 0) console.log(`\x1b[32m[设计系统 ✓ ${t}] screens 颜色全部走 token、变量均已定义\x1b[0m`); + else { + const m = out.match(/❶[^\n]*共 (\d+)[\s\S]*?❷[^\n]*共 (\d+)/); + const sum = m ? `硬编码颜色 ${m[1]} · 未定义 token ${m[2]}` : '存在违规'; + console.log(`\x1b[31m[设计系统 ✗ ${t}] ${sum} —— 运行 node tools/check-ds.mjs 看详情\x1b[0m`); + } + }); +} + +let debounce; +fs.watch(ROOT, { recursive: true }, (_ev, file) => { + if (file && file.endsWith('serve.mjs')) return; + clearTimeout(debounce); + debounce = setTimeout(() => { + for (const c of clients) c.write('data: reload\n\n'); + if (file && /\.(html|css|js)$/.test(file)) runDsCheck(); + }, 80); +}); + +server.listen(PORT, () => { + console.log(`prototype live at http://localhost:${PORT}/ (watching ${ROOT})`); + runDsCheck(); +}); diff --git a/.superpowers/prototype/tokens.css b/.superpowers/prototype/tokens.css new file mode 100644 index 0000000..8db1721 --- /dev/null +++ b/.superpowers/prototype/tokens.css @@ -0,0 +1,70 @@ +/* ============================================================ + 岩美酒库 · 令牌层(单一真源) + 颜色按主题切换;间距/圆角/阴影/字号 全主题统一。 + 规则:屏幕与组件只引用 var(--token),禁硬编码 hex。 + ============================================================ */ + +:root{ + /* 间距(4px 基) */ + --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px; --sp-6:24px; --sp-8:32px; --sp-10:40px; + /* 圆角 */ + --r-sm:4px; --r-md:6px; --r-lg:10px; --r-xl:14px; --r-pill:999px; + /* 字号梯度 */ + --fs-display:28px; --fs-h1:21px; --fs-h2:17px; --fs-title:15px; --fs-body:13px; --fs-sm:12px; --fs-xs:11px; + /* 字体族 */ + --font:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei","Inter",sans-serif; + --font-mono:"JetBrains Mono","SF Mono",ui-monospace,monospace; + /* 阴影系统(用各主题 --shadow 染色) */ + --sh-1:0 1px 3px var(--shadow); --sh-2:0 4px 14px var(--shadow); --sh-3:0 18px 50px var(--shadow); + /* 遮罩 / 玻璃叠加(主题无关:弹窗·抽屉背板、hero 上的半透明白) */ + --scrim:rgba(15,20,30,.42); --hero-glass:rgba(255,255,255,.16); --hero-glass-strong:rgba(255,255,255,.24); + /* 品牌标识色(logo / 门店字标,固定不随主题) */ + --brand-deep:#0F3057; --brand-mid:#1B4F8E; --brand-wine:#8B2331; + /* 底部状态栏(深石板灰绿,主题无关的 chrome 条) */ + --statusbar-bg:#37423D; --statusbar-fg:rgba(255,255,255,.82); --statusbar-muted:rgba(255,255,255,.46); --statusbar-strong:#FFFFFF; --statusbar-div:rgba(255,255,255,.13); --statusbar-ok:#7BC49A; + /* Toast 深底(主题无关) */ + --toast-bg:#1F2330; + /* z-index 层级阶梯(全局唯一来源,禁止裸写 z-index≥10) */ + --z-sticky:20; --z-shell-scrim:30; --z-shell-panel:35; --z-header:40; --z-fab:45; + --z-drawer-scrim:65; --z-drawer:70; --z-overlay:80; --z-pop:85; --z-menu:88; --z-toast:90; +} + +/* ---------- A 经典蓝(浅 · 基准/默认) ---------- */ +:root[data-theme="a"]{ + --page:#E7EBF1; --bg:#F5F7FA; --surface:#FFFFFF; --border:#DCE2EB; --border-subtle:#ECEFF4; + --text:#232934; --heading:#1B2430; --title:#1B2430; --muted:#6E7888; --faint:#99A3B3; + --primary:#2563AC; --primary-dark:#154072; --on-primary:#FFFFFF; --brand400:#4F86C6; --brand50:#EEF4FB; --accent:#8B2331; + --success:#2E8B57; --danger:#D14343; --warn:#E08E00; + --info-soft:#EEF4FB; --ok-soft:#E8F5EE; --accent-soft:#FAEEF0; + --success-bg:#E8F5EE; --danger-bg:#FDECEC; --warn-bg:#FFF4DB; + --th-bg:#F7F9FC; --row-hover:#EEF4FB; --shadow:rgba(20,40,70,.20); + --top-bg:#2563AC; --top-fg:#FFFFFF; --top-faint:rgba(255,255,255,.72); --top-border:#1E5594; --top-control:rgba(255,255,255,.16); --top-mark-bg:rgba(255,255,255,.22); --top-mark-fg:#FFFFFF; + --side-bg:#163A6C; --side-fg:#C4D6ED; --side-muted:#7C9FCB; --side-active-bg:#2C6BB3; --side-active-fg:#FFFFFF; --side-border:#1E4A86; --side-hover:#1C4680; + --menu-user-bg:#3C79C0; --menu-user-fg:#FFFFFF; --menu-user-hover:#4D88CC; +} +/* ---------- B 琥珀(深) ---------- */ +:root[data-theme="b"]{ + --page:#0B0B10; --bg:#15151C; --surface:#1E1E27; --border:#2B2B37; --border-subtle:#26262F; + --text:#E8E8EE; --heading:#F6F6F9; --title:#F6F6F9; --muted:#9A9AA8; --faint:#6E6E7E; + --primary:#F5A300; --primary-dark:#D98C00; --on-primary:#1A1408; --brand400:#E0A23A; --brand50:#2A2212; --accent:#F87171; + --success:#34D399; --danger:#F87171; --warn:#FBBF24; + --info-soft:#1F2530; --ok-soft:#16312A; --accent-soft:#33181A; + --success-bg:#16312A; --danger-bg:#3A1A1C; --warn-bg:#332811; + --th-bg:#1A1A22; --row-hover:#24242F; --shadow:rgba(0,0,0,.55); + --top-bg:#1A1A22; --top-fg:#E8E8EE; --top-faint:#6E6E7E; --top-border:#2B2B37; --top-control:#15151C; --top-mark-bg:linear-gradient(135deg,#F5A300,#D98C00); --top-mark-fg:#1A1408; + --side-bg:#121219; --side-fg:#9A9AA8; --side-muted:#5E5E6C; --side-active-bg:#2A2212; --side-active-fg:#F5A300; --side-border:#24242E; --side-hover:#1E1E27; + --menu-user-bg:#3A3120; --menu-user-fg:#F0E7D6; --menu-user-hover:#473B27; +} +/* ---------- C 酒窖(暖浅) ---------- */ +:root[data-theme="c"]{ + --page:#E9DECB; --bg:#FAF5EC; --surface:#FFFDF8; --border:#EADDC9; --border-subtle:#F1E8D8; + --text:#33271F; --heading:#241A14; --title:#9E3B30; --muted:#917A64; --faint:#B9A488; + --primary:#7C1F2C; --primary-dark:#5F1621; --on-primary:#F7E9D6; --brand400:#B5862F; --brand50:#F3E7D2; --accent:#A23522; + --success:#2E6B43; --danger:#B23A3A; --warn:#B07A12; + --info-soft:#F1E6D2; --ok-soft:#E7F0E6; --accent-soft:#F3E0D5; + --success-bg:#E7F0E6; --danger-bg:#F6E4DD; --warn-bg:#FBEFCF; + --th-bg:#F6EEDF; --row-hover:#F5ECDB; --shadow:rgba(74,20,32,.22); + --top-bg:#7C1F2C; --top-fg:#F7E9D6; --top-faint:rgba(247,233,214,.72); --top-border:#671E2C; --top-control:rgba(255,255,255,.14); --top-mark-bg:rgba(255,255,255,.20); --top-mark-fg:#F7E9D6; + --side-bg:#4A1420; --side-fg:#E0C9B2; --side-muted:#B58E76; --side-active-bg:#671E2C; --side-active-fg:#F7E9D6; --side-border:#5A2330; --side-hover:#5A1B28; + --menu-user-bg:#7E2B3B; --menu-user-fg:#F7E9D6; --menu-user-hover:#8E3344; +} diff --git a/.superpowers/prototype/tools/check-ds.mjs b/.superpowers/prototype/tools/check-ds.mjs new file mode 100644 index 0000000..083b5d2 --- /dev/null +++ b/.superpowers/prototype/tools/check-ds.mjs @@ -0,0 +1,123 @@ +#!/usr/bin/env node +// 设计系统守门检查器(单一真源强制)—— 扫描 screens/* + atoms.css + 共享 JS,强制走 tokens.css。 +// 用法:node tools/check-ds.mjs 违规则 exit 1 +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const read = f => fs.readFileSync(path.join(ROOT, f), 'utf8'); +const screens = fs.readdirSync(path.join(ROOT, 'screens')).filter(f => f.endsWith('.html')); +// 共享 JS 模块(运行时生成 UI,也纳入颜色/数值扫描;tokens.css 是令牌之源,不扫) +const SHARED_JS = ['shell.js', 'mobile-shell.js', 'statusbar.js', 'notify.js', 'fab.js', 'datewheel.js'].filter(f => fs.existsSync(path.join(ROOT, 'screens', f))); + +const COLOR_ALLOW = new Set(['#fff', '#ffffff', '#1677ff', '#07c160']); +const definedVars = new Set(); +for (const f of ['tokens.css', 'atoms.css']) for (const m of read(f).matchAll(/--([a-z0-9-]+)\s*:/gi)) definedVars.add('--' + m[1]); +const ATOM_CSS = ['atoms.css', 'mobile-atoms.css'].filter(f => fs.existsSync(path.join(ROOT, f))); +const atomClasses = new Set(); +for (const f of ATOM_CSS) for (const m of read(f).matchAll(/\.([a-zA-Z][\w-]*)/g)) atomClasses.add(m[1]); +const iconIds = new Set(); +for (const m of read('screens/icons.js').matchAll(/ { + const depthBefore = svgDepth; + if (!line.includes('ds-allow')) { + for (const m of line.matchAll(COLOR_RE)) { + const c = m[0]; + if (COLOR_ALLOW.has(c.toLowerCase())) continue; + const before = line.slice(0, m.index); + if (depthBefore > 0 || /(?:fill|stroke)\s*=\s*["']?$/.test(before)) continue; + colorViol.push(`${label}:${i + 1} ${c}`); + } + for (const m of line.matchAll(VAR_RE)) if (!definedVars.has(m[1])) varViol.push(`${label}:${i + 1} var(${m[1]})`); + for (const m of line.matchAll(/font-size:\s*(\d+)px/g)) if (FS_NAME[+m[1]]) fsViol.push(`${label}:${i + 1} font-size:${m[1]}px → var(${FS_NAME[+m[1]]})`); + for (const m of line.matchAll(/z-index:\s*(\d+)\b/g)) if (+m[1] >= 10) zViol.push(`${label}:${i + 1} z-index:${m[1]} → var(--z-*)`); + for (const m of line.matchAll(/border-radius:\s*(\d+)px/g)) if (R_NAME[+m[1]]) radiusViol.push(`${label}:${i + 1} border-radius:${m[1]}px → var(${R_NAME[+m[1]]})`); + for (const m of line.matchAll(/font-family:\s*([^;}\n]+)/g)) if (!/var\(--font/.test(m[1])) ffViol.push(`${label}:${i + 1} font-family 未走 var(--font*)`); + } + svgDepth += (line.match(//g) || []).length; + if (svgDepth < 0) svgDepth = 0; + }); +} +for (const f of screens) valueScan('screens/' + f, read('screens/' + f)); +for (const f of ATOM_CSS) valueScan(f, read(f)); +for (const f of SHARED_JS) valueScan('screens/' + f, read('screens/' + f)); + +// ---- HTML 专项检查(screens)---- +function localClassesOf(src) { const set = new Set(); for (const sm of src.matchAll(//gi)) for (const m of sm[0].matchAll(/\.([a-zA-Z][\w-]*)/g)) set.add(m[1]); return set; } +function usedClassTokens(src) { + const found = []; + for (const m of src.matchAll(/class\s*=\s*"([^"]*)"/g)) for (const t of m[1].split(/\s+/)) found.push(t); + for (const m of src.matchAll(/className\s*=\s*['"]([^'"]*)['"]/g)) for (const t of m[1].split(/\s+/)) found.push(t); + for (const m of src.matchAll(/classList\.(?:add|toggle|remove)\(([^)]*)\)/g)) for (const sm of m[1].matchAll(/['"]([\w-]+)['"]/g)) found.push(sm[1]); + return found.filter(t => t && !t.includes('${') && !t.includes('{') && /^[a-zA-Z][\w-]*$/.test(t)); +} +function forkAtoms(src) { + const out = new Set(); + for (const sm of src.matchAll(//gi)) for (const rm of sm[0].matchAll(/([^{}]+)\{/g)) for (let sel of rm[1].split(',')) + { const m = sel.trim().match(/^\.([a-zA-Z][\w-]*)$/); if (m && COMPONENT_ATOMS.has(m[1])) out.add(m[1]); } + return out; +} +for (const file of screens) { + const src = read('screens/' + file); + const isStub = /location\.replace/.test(src) && !/[\s\S]*\S[\s\S]*<\/body>/.test(src.replace(/<\/body>/, '')); + const local = localClassesOf(src); + for (const t of new Set(usedClassTokens(src))) if (!atomClasses.has(t) && !local.has(t) && !CLASS_WHITELIST.has(t)) classViol.push(`screens/${file} .${t}`); + for (const c of forkAtoms(src)) forkViol.push(`screens/${file} .${c}{}`); + if (!isStub && (!src.includes('tokens.css') || !src.includes('atoms.css'))) importViol.push(`screens/${file}`); + for (const m of src.matchAll(/]*)>([\s\S]*?)<\/svg>/g)) { + const inner = m[2]; + if (/rect width="64"/.test(inner) || /viewBox="0 0 64 64"/.test(m[1])) continue; + const u = inner.match(/]*href="#(i-[\w-]+)"/); + if (u) { if (!iconIds.has(u[1])) iconViol.push(`screens/${file} 未登记`); continue; } + if (/<(path|circle|rect|line|polyline|ellipse)\b/.test(inner)) iconViol.push(`screens/${file} 内联图标`); + } + // 响应式断点魔法数(仅 @media max-width) + for (const m of src.matchAll(/@media[^{]*max-width:\s*(\d+)px/g)) if (!BREAKPOINTS.has(+m[1])) bpViol.push(`screens/${file} @media max-width:${m[1]}px(规范断点:${[...BREAKPOINTS].join('/')})`); +} +// 同样扫 atoms.css / mobile-atoms.css 的断点 +for (const f of ATOM_CSS) for (const m of read(f).matchAll(/@media[^{]*max-width:\s*(\d+)px/g)) if (!BREAKPOINTS.has(+m[1])) bpViol.push(`${f} @media max-width:${m[1]}px`); + +// ---- 新原子登记:组件原子必须在 index.html 出现(活文档)---- +for (const c of COMPONENT_ATOMS) { + const re = new RegExp(`class="[^"]*\\b${c}\\b|\\.${c}[\\s{:.,]`); + if (!re.test(indexSrc)) regViol.push(`.${c} ← 未在 index.html 登记展示`); +} + +const banner = s => `\n${'='.repeat(60)}\n${s}\n${'='.repeat(60)}`; +const sec = (n, arr) => { console.log(banner(`${n} 共 ${arr.length}`)); console.log(arr.length ? arr.join('\n') : '(无)✓'); }; +console.log(banner('设计系统守门检查(单一真源)')); +console.log(`token: ${definedVars.size} · 原子类: ${atomClasses.size} · 图标: ${iconIds.size} · 扫描: screens(${screens.length}) + atoms.css + 共享JS(${SHARED_JS.length})`); +sec('❶ 硬编码颜色', colorViol); +sec('❷ 未定义 token', varViol); +sec('❸ 未登记组件类', classViol); +sec('❹ font-size 未走 --fs-*', fsViol); +sec('❺ z-index 未走阶梯', zViol); +sec('❻ fork 了 atoms 组件', forkViol); +sec('❼ 缺 tokens/atoms 引入', importViol); +sec('❽ 图标未走 sprite', iconViol); +sec('❾ border-radius 未走 --r-*', radiusViol); +sec('❿ font-family 未走 --font', ffViol); +sec('⓫ 响应式断点魔法数', bpViol); +sec('⓬ 组件原子未在 index.html 登记', regViol); + +const all = [colorViol, varViol, classViol, fsViol, zViol, forkViol, importViol, iconViol, radiusViol, ffViol, bpViol, regViol]; +const fail = all.reduce((s, a) => s + a.length, 0); +console.log(banner(fail ? `✗ 未通过:${fail} 处违规` : '✓ 通过:颜色/排版/层级/圆角/字体/图标走单一来源,组件已登记未 fork,断点规范')); +process.exit(fail ? 1 : 0);