feat(design): 原型三域回填对齐真实端 + 授权管理提升独立一级屏(三 tab,桌面+移动)

三域回填(原型=真实 100% 一致铁律首轮集中对齐):
- 库存域:多选筛选 chip(N 项摘要+×单清)/抽屉打印标签/成本按角色标注/移动三态筛选入口/共享抽屉生产日期行
- 设置授权关于域:授权信息三格横排/购买卡三阶段同构/iOS 合规双态/隐私同意弹窗/注销账号入口/客服邮箱统一/关于页按钮规格
- 日期组件域:扁平滚轮面板(输入框+滚轮同宽居中)/范围起止双窗格并排锚定下拉/移动 sheet 纵排,.wheel-* 提升 atoms 并登记

授权管理提升(design-first,评审通过):
- 新增 license.html 独立一级屏(侧边栏「系统」组):授权信息/在线购买续费/订单管理三 tab
- m-license.html 同构三 tab;订单管理 tab=license_purchases 流水(KPI/筛选/表格·卡流/详情)
- settings 子导航摘除授权项;iOS 合规态隐藏购买 tab;index 登记更新

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-10 20:19:03 +08:00
parent adfa0ca34c
commit 13b7274dbd
19 changed files with 969 additions and 189 deletions
+17 -3
View File
@@ -54,15 +54,29 @@ svg{fill:none; stroke:currentColor;}
.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;}
/* 面板(扁平,2026-07-04 拍板;镜像 client wheel_date_picker.dart):卡壳 surface+1px 边框+r-lg 无阴影;
窗格 .wheel-pane = 可输入框 + 三列滚轮(同宽 248 居中);范围 .wheel-range 双窗格并排 + 共用底栏;
移动 sheet 纵排复用挂 .wheel-sheet(窗格纵排居中,底栏与窗格同宽) */
.wheel-pop{position:fixed; z-index:var(--z-pop); background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); padding:12px; display:none;}
.wheel-pop.show{display:block;}
.wheel-pane{width:248px;}
.wheel-pane-title{font-size:var(--fs-sm); font-weight:600; color:var(--muted); padding-left:2px; margin-bottom:6px;}
.wheel-input{display:block; width:100%; box-sizing:border-box; height:34px; margin-bottom:6px; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); outline:none; text-align:center; font-size:var(--fs-body); color:var(--text); font-family:var(--font-mono);}
.wheel-input:focus{border-color:var(--primary);}
.wheel-input::placeholder{color:var(--faint); font-family:var(--font);}
.wheel-box{border:1px solid var(--border); border-radius:var(--r-md); padding:4px;}
.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-center{position:absolute; left:4px; right:4px; top:72px; height:36px; border-radius:var(--r-md); background:var(--bg); 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-range{display:flex; align-items:flex-start; gap:12px;}
.wheel-sheet{display:flex; flex-direction:column; align-items:center;}
.wheel-sheet .wheel-range{flex-direction:column; align-items:center; gap:14px;}
.wheel-sheet .wheel-foot{width:248px; box-sizing:border-box;}
.wheel-foot{display:flex; justify-content:space-between; align-items:center; margin-top:10px; padding-top:10px; border-top:1px solid var(--border-subtle);}
.wheel-foot .wheel-preview{flex:1; font-size:var(--fs-sm); color:var(--muted); font-family:var(--font-mono);}
.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);}
+28 -5
View File
@@ -156,6 +156,7 @@
<a class="kit" href="screens/pricing.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic07"/></svg></div><div class="kt"><b>购买授权</b><span>时长套餐 · 版本档位</span></div></a>
<a class="kit" href="screens/checkout.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic67"/></svg></div><div class="kt"><b>确认订单</b><span>下单 · 开票 · 无支付占位</span></div></a>
<a class="kit" href="screens/pay.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic08"/></svg></div><div class="kt"><b>收银台</b><span>订单信息 · 支付宝扫码 · 三态</span></div></a>
<a class="kit" href="screens/license.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-shield"/></svg></div><div class="kt"><b>授权管理</b><span>独立一级屏 · 授权 / 购买续费 / 订单三 tab</span></div></a>
</div>
</section>
@@ -180,6 +181,7 @@
<a class="kit" href="screens/m-notification.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-bell"/></svg></div><div class="kt"><b>通知详情 · 移动</b><span>正文 / 关联单据 / 操作</span></div></a>
<a class="kit" href="screens/m-about.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic24"/></svg></div><div class="kt"><b>关于 · 移动</b><span>版本 / 更新日志 / 授权</span></div></a>
<a class="kit" href="screens/m-login.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic59"/></svg></div><div class="kt"><b>登录 · 移动</b><span>品牌 + 表单</span></div></a>
<a class="kit" href="screens/m-license.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-shield"/></svg></div><div class="kt"><b>授权管理 · 移动</b><span>授权/购买/订单三 tab · 购买卡 / 订单卡流</span></div></a>
<a class="kit" href="screens/m-register.html"><div class="thumb"><svg viewBox="0 0 24 24"><use href="#i-ic66"/></svg></div><div class="kt"><b>注册新门店 · 移动</b><span>开通向导</span></div></a>
</div>
</section>
@@ -359,14 +361,35 @@
<div class="cp-foot"><b>5</b> 条结果</div>
</div>
</div></div>
<div class="pcard"><div class="pcard-h"><h3>日期字段(滚轮选择</h3><span class="tk">.datefield + .wheel-pop(年月日 · 单据/生产共用)· datewheel.js</span></div><div class="pcard-b" style="align-items:flex-start">
<div class="pcard"><div class="pcard-h"><h3>日期字段(扁平滚轮面板</h3><span class="tk">.datefield 触发器 + .wheel-pop 面板(窗格 .wheel-pane = .wheel-input 可输入框 + .wheel-box 三列滚轮,同宽 248 居中;范围 .wheel-range 双窗格并排 + .wheel-preview 共用底栏;移动 sheet 纵排挂 .wheel-sheet)· datewheel.js</span></div><div class="pcard-b" style="align-items:flex-start">
<div class="datefield" style="max-width:200px" data-val="2026-06-22"><span class="dval">2026-06-22</span><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg></div>
<div class="datefield" style="max-width:200px"><span class="dval ph">选择日期</span><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg></div>
<div class="wheel-pop show" style="position:static; box-shadow:none; width:248px"><div class="wheel-cols"><div class="wheel-center"></div>
<div class="wheel-col" style="overflow:hidden"><div class="wheel-item">2024</div><div class="wheel-item">2025</div><div class="wheel-item on">2026</div><div class="wheel-item">2027</div><div class="wheel-item">2028</div></div>
<div class="wheel-col" style="overflow:hidden"><div class="wheel-item">04</div><div class="wheel-item">05</div><div class="wheel-item on">06</div><div class="wheel-item">07</div><div class="wheel-item">08</div></div>
<div class="wheel-col" style="overflow:hidden"><div class="wheel-item">20</div><div class="wheel-item">21</div><div class="wheel-item on">22</div><div class="wheel-item">23</div><div class="wheel-item">24</div></div>
<div class="wheel-pop show" style="position:static"><div class="wheel-pane">
<input class="wheel-input" value="2026-06-22" readonly>
<div class="wheel-box"><div class="wheel-cols"><div class="wheel-center"></div>
<div class="wheel-col" style="overflow:hidden; padding:0"><div class="wheel-item">2024</div><div class="wheel-item">2025</div><div class="wheel-item on">2026</div><div class="wheel-item">2027</div><div class="wheel-item">2028</div></div>
<div class="wheel-col" style="overflow:hidden; padding:0"><div class="wheel-item">04</div><div class="wheel-item">05</div><div class="wheel-item on">06</div><div class="wheel-item">07</div><div class="wheel-item">08</div></div>
<div class="wheel-col" style="overflow:hidden; padding:0"><div class="wheel-item">20</div><div class="wheel-item">21</div><div class="wheel-item on">22</div><div class="wheel-item">23</div><div class="wheel-item">24</div></div>
</div></div>
</div><div class="wheel-foot"><span class="wheel-today">今天</span><span class="wheel-ok">确定</span></div></div>
<div class="wheel-pop show" style="position:static"><div class="wheel-range">
<div class="wheel-pane"><div class="wheel-pane-title">起始日期</div>
<input class="wheel-input" value="2026-06-01" readonly>
<div class="wheel-box"><div class="wheel-cols"><div class="wheel-center"></div>
<div class="wheel-col" style="overflow:hidden; padding:0"><div class="wheel-item">2024</div><div class="wheel-item">2025</div><div class="wheel-item on">2026</div><div class="wheel-item">2027</div><div class="wheel-item">2028</div></div>
<div class="wheel-col" style="overflow:hidden; padding:0"><div class="wheel-item">04</div><div class="wheel-item">05</div><div class="wheel-item on">06</div><div class="wheel-item">07</div><div class="wheel-item">08</div></div>
<div class="wheel-col" style="overflow:hidden; padding:0"><div class="wheel-item">30</div><div class="wheel-item">31</div><div class="wheel-item on">01</div><div class="wheel-item">02</div><div class="wheel-item">03</div></div>
</div></div>
</div>
<div class="wheel-pane"><div class="wheel-pane-title">结束日期</div>
<input class="wheel-input" value="2026-06-22" readonly>
<div class="wheel-box"><div class="wheel-cols"><div class="wheel-center"></div>
<div class="wheel-col" style="overflow:hidden; padding:0"><div class="wheel-item">2024</div><div class="wheel-item">2025</div><div class="wheel-item on">2026</div><div class="wheel-item">2027</div><div class="wheel-item">2028</div></div>
<div class="wheel-col" style="overflow:hidden; padding:0"><div class="wheel-item">04</div><div class="wheel-item">05</div><div class="wheel-item on">06</div><div class="wheel-item">07</div><div class="wheel-item">08</div></div>
<div class="wheel-col" style="overflow:hidden; padding:0"><div class="wheel-item">20</div><div class="wheel-item">21</div><div class="wheel-item on">22</div><div class="wheel-item">23</div><div class="wheel-item">24</div></div>
</div></div>
</div>
</div><div class="wheel-foot"><span class="wheel-preview">2026-06-01 ~ 2026-06-22</span><span class="wheel-ok">确定</span></div></div>
</div></div>
<div class="pcard"><div class="pcard-h"><h3>筛选 Chip</h3><span class="tk">.chip · 内嵌列头筛选入口</span></div><div class="pcard-b">
<div class="chip">全部品类</div>
+3 -18
View File
@@ -61,13 +61,6 @@
.tl-morebar .btn svg{width:14px; height:14px; transition:transform .2s;}
.timeline.expanded ~ .tl-morebar .btn svg{transform:rotate(180deg);}
/* ---- 授权信息(原样式) ---- */
.lic-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:14px;}
.lic-cell{background:var(--bg); border:1px solid var(--border-subtle); border-radius:var(--r-md); padding:14px;}
.lic-cell .lk{font-size:var(--fs-xs); color:var(--muted);}
.lic-cell .lv{font-size:var(--fs-h2); font-weight:700; color:var(--heading); margin-top:6px; font-family:var(--font-mono);}
.lic-cell .lv.ok{color:var(--success);}
/* ---- 公司 / 支持(原样式) ---- */
.links{display:grid; grid-template-columns:repeat(2,1fr); gap:10px;}
.link-row{display:flex; align-items:center; gap:11px; padding:11px 13px; border:1px solid var(--border); border-radius:var(--r-md); background:var(--bg); color:var(--text); font-size:var(--fs-body); cursor:pointer; transition:border-color .15s;}
@@ -105,8 +98,8 @@
</div>
<div class="hero-links">
<div class="links">
<div class="link-row" onclick="toast('打开官网 yanmei.tech(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic01"/></svg>官网<span class="arr"></span></div>
<div class="link-row" onclick="toast('微信客服:yanmei-kf(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic02"/></svg>微信客服<span class="arr"></span></div>
<div class="link-row" onclick="toast('打开官网 jiu.51yanmei.com(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic01"/></svg>官网<span class="arr"></span></div>
<div class="link-row" onclick="toast('微信客服:请见官网(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic02"/></svg>微信客服<span class="arr"></span></div>
<div class="link-row" onclick="toast('查看隐私政策(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic03"/></svg>隐私政策<span class="arr"></span></div>
<div class="link-row" onclick="toast('查看服务条款(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic06"/></svg>服务条款<span class="arr"></span></div>
</div>
@@ -131,15 +124,7 @@
</div>
</div>
<!-- 授权信息(原样式 -->
<div class="card">
<h3 class="card-h"><svg viewBox="0 0 24 24"><use href="#i-shield"/></svg>授权信息</h3>
<div class="lic-grid">
<div class="lic-cell"><div class="lk">授权状态</div><div class="lv ok">已授权</div></div>
<div class="lic-cell"><div class="lk">到期日</div><div class="lv">2027-03-15</div></div>
<div class="lic-cell"><div class="lk">剩余天数</div><div class="lv">267 天</div></div>
</div>
</div>
<!-- 授权信息卡已迁至 设置 → 授权管理(对齐 Flutter 2026-07-04,见 settings.html -->
<!-- 更新日志(最近 2 条 + 更多) -->
<div class="card">
+114 -34
View File
@@ -1,47 +1,127 @@
// 滚轮日期选择器 —— 年月日,单据日期/生产日期/明细共用(单一真源
// 用法:在任意 .datefield 元素点击(或聚焦后按 Enter/空格)打开滚轮
// 元素持有 data-val="YYYY-MM-DD"选定后写回 data-val + .dval 文本,并派发 'datechange' 事件(detail.value)。
// 三列滚轮(年/月/日)滚动吸附,年/月变化自动重算当月天数。
// 滚轮日期选择器 —— 扁平统一日期组件(单一真源;镜像 client/lib/widgets/wheel_date_picker.dart2026-07-04 拍板
// 单日期:任意 .datefield 元素点击(或聚焦后按 Enter/空格)打开锚定面板 = 可输入框 + 三列滚轮 + 底栏(今天/确定)
// 元素持有 data-val="YYYY-MM-DD"确定写回 data-val + .dval 文本,并派发 'datechange' 事件(detail.value)。
// 范围:DateWheel.openRange(anchor,{from,to,onCommit}) 起始|结束双窗格并排锚定下拉 + 共用底栏(范围预览/确定),
// 起止倒置自动交换;DateWheel.mountRange(host,{from,to,onCommit}) 把同一套窗格纵排挂进移动底部 sheet。
// 窗格(镜像 _DatePane):输入框与滚轮同宽(248)、文字水平垂直居中(mono);键入 2024 / 2024-5-12 / 20240512
// 回车/失焦归一 yyyy-MM-dd 并吸附滚轮(词法对齐 date_util.dart normalizeYmd),滚轮滚动实时回写输入框。
(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
const fmt=(y,m,d)=>y+'-'+pad(m)+'-'+pad(d);
const today=()=>fmt(now.getFullYear(),now.getMonth()+1,now.getDate());
function ensure(){
if(pop) return;
pop=document.createElement('div'); pop.className='wheel-pop';
pop.innerHTML='<div class="wheel-cols"><div class="wheel-center"></div>'
+'<div class="wheel-col" data-k="y"></div><div class="wheel-col" data-k="m"></div><div class="wheel-col" data-k="d"></div></div>'
+'<div class="wheel-foot"><span class="wheel-today">今天</span><span class="wheel-ok">确定</span></div>';
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);
// 键入词法归一(对齐 normalizeYmd):纯数字 yyyymmdd/yyyymm/yyyy 或任意分隔串;缺月/日补 01,超界日 clamp。
function normalize(raw){
const s=String(raw||'').trim(); if(!s) return null;
let y,m,d;
if(/^[0-9]+$/.test(s)){
if(s.length>=8){ y=+s.slice(0,4); m=+s.slice(4,6); d=+s.slice(6,8); }
else if(s.length===6){ y=+s.slice(0,4); m=+s.slice(4,6); }
else y=+s;
} else {
const p=s.split(/[^0-9]+/).filter(Boolean);
if(p.length)y=+p[0]; if(p.length>=2)m=+p[1]; if(p.length>=3)d=+p[2];
}
if(!y||y<1900||y>2200) return null;
m=m||1; d=d||1;
if(m<1||m>12) return null;
const n=dim(y,m); if(d<1)d=1; if(d>n)d=n;
return fmt(y,m,d);
}
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=>`<div class="wheel-item">${t}</div>`).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(); }
// ---- 窗格(可输入框 + 三列滚轮,输入 ↔ 滚轮双向联动) ----
function makePane(title){
const root=document.createElement('div'); root.className='wheel-pane';
root.innerHTML=(title?`<div class="wheel-pane-title">${title}</div>`:'')
+'<input class="wheel-input" type="text" placeholder="年-月-日" spellcheck="false">'
+'<div class="wheel-box"><div class="wheel-cols"><div class="wheel-center"></div>'
+'<div class="wheel-col" data-k="y"></div><div class="wheel-col" data-k="m"></div><div class="wheel-col" data-k="d"></div></div></div>';
const input=root.querySelector('.wheel-input');
const yCol=root.querySelector('[data-k="y"]'), mCol=root.querySelector('[data-k="m"]'), dCol=root.querySelector('[data-k="d"]');
let building=false;
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))); };
const markOn=col=>{ const idx=idxOf(col); items(col).forEach((el,i)=>el.classList.toggle('on',i===idx)); };
const fill=(col,arr,selIdx)=>{ building=true; col.innerHTML=arr.map(t=>`<div class="wheel-item">${t}</div>`).join(''); col.scrollTop=selIdx*ITEM; markOn(col); requestAnimationFrame(()=>{ building=false; }); };
const get=()=>fmt(YEARS[idxOf(yCol)],idxOf(mCol)+1,idxOf(dCol)+1);
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); }
// 吸附滚轮到 val(不动输入框,供键入联动);set 额外回写输入框
function snap(val){ let [y,m,d]=(normalize(val)||today()).split('-').map(Number);
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(YEARS[yi],m); fill(dCol,Array.from({length:n},(_,i)=>pad(i+1)),Math.min(d-1,n-1)); }
function set(val){ snap(val); input.value=get(); }
[yCol,mCol,dCol].forEach(col=>{ let t; col.addEventListener('scroll',()=>{ if(building) return; markOn(col);
input.value=get(); if(api.onChange)api.onChange(get());
clearTimeout(t); t=setTimeout(()=>{ if(col.dataset.k!=='d'){ rebuildDays(); input.value=get(); } },110); }); });
// 键入完整日期(≥8 位)即联动滚轮;回车/失焦归一显示(2024 → 2024-01-01),无效则回显当前值
input.addEventListener('input',()=>{ const n=normalize(input.value); if(n && input.value.trim().length>=8){ snap(n); if(api.onChange)api.onChange(n); } });
input.addEventListener('keydown',e=>{ if(e.key==='Enter'){ e.preventDefault(); const n=normalize(input.value); if(n){ set(n); if(api.onChange)api.onChange(n); } } });
input.addEventListener('blur',()=>{ const n=normalize(input.value); if(n){ set(n); if(api.onChange)api.onChange(n); } else input.value=get(); });
const api={el:root,get,set,onChange:null};
return api;
}
// ---- 锚定下拉面板(透明遮罩点外关闭,不冻结窗口) ----
let pop=null, mode='', target=null, panes=[], onRange=null;
function build(m){
if(pop && mode===m) return;
if(pop) pop.remove();
mode=m; panes=[];
pop=document.createElement('div'); pop.className='wheel-pop';
if(m==='single'){
const p=makePane(); panes=[p]; pop.appendChild(p.el);
pop.insertAdjacentHTML('beforeend','<div class="wheel-foot"><span class="wheel-today">今天</span><span class="wheel-ok">确定</span></div>');
pop.querySelector('.wheel-today').onclick=()=>p.set(today());
pop.querySelector('.wheel-ok').onclick=commitSingle;
} else {
const a=makePane('起始日期'), b=makePane('结束日期'); panes=[a,b];
const row=document.createElement('div'); row.className='wheel-range'; row.appendChild(a.el); row.appendChild(b.el); pop.appendChild(row);
pop.insertAdjacentHTML('beforeend','<div class="wheel-foot"><span class="wheel-preview"></span><span class="wheel-ok">确定</span></div>');
const prev=pop.querySelector('.wheel-preview');
a.onChange=b.onChange=()=>{ prev.textContent=a.get()+' ~ '+b.get(); };
pop.querySelector('.wheel-ok').onclick=commitRange;
}
document.body.appendChild(pop);
}
function position(el){ const r=el.getBoundingClientRect(); const w=pop.offsetWidth;
pop.style.left=Math.max(8,Math.min(r.left,window.innerWidth-w-8))+'px'; pop.style.top=(r.bottom+6)+'px';
const ph=pop.offsetHeight; if(r.bottom+6+ph+8>window.innerHeight && r.top-ph-6>8) pop.style.top=(r.top-ph-6)+'px'; }
function open(el){ build('single'); target=el;
pop.classList.add('show'); // 先显示获得布局,scrollTop 才能生效
panes[0].set(el.dataset.val||''); position(el); }
function openRange(el,opt){ opt=opt||{}; build('range'); target=el; onRange=opt.onCommit||null;
pop.classList.add('show');
panes[0].set(opt.from||''); panes[1].set(opt.to||'');
pop.querySelector('.wheel-preview').textContent=panes[0].get()+' ~ '+panes[1].get();
position(el); }
function close(){ if(pop) pop.classList.remove('show'); target=null; onRange=null; }
function commitSingle(){ if(!target) return; const val=panes[0].get(); 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(); }
function commitRange(){ let f=panes[0].get(), t=panes[1].get(); if(t<f){ const x=f; f=t; t=x; } // 起止倒置自动交换
const cb=onRange; close(); if(cb)cb(f,t); }
// ---- 移动底部 sheet 复用:同一套窗格纵排 + 共用底栏(镜像 _RangeBody(vertical) ----
function mountRange(host,opt){ opt=opt||{}; host.classList.add('wheel-sheet'); host.innerHTML='';
const a=makePane('起始日期'), b=makePane('结束日期');
const row=document.createElement('div'); row.className='wheel-range'; row.appendChild(a.el); row.appendChild(b.el); host.appendChild(row);
host.insertAdjacentHTML('beforeend','<div class="wheel-foot"><span class="wheel-preview"></span><span class="wheel-ok">确定</span></div>');
const prev=host.querySelector('.wheel-preview');
const upd=()=>{ prev.textContent=a.get()+' ~ '+b.get(); };
a.onChange=b.onChange=upd;
a.set(opt.from||''); b.set(opt.to||''); upd();
host.querySelector('.wheel-ok').onclick=()=>{ let f=a.get(), t=b.get(); if(t<f){ const x=f; f=t; t=x; } if(opt.onCommit)opt.onCommit(f,t); };
}
document.addEventListener('mousedown',e=>{ if(pop && pop.classList.contains('show') && !pop.contains(e.target) && e.target!==target && !(target&&target.contains&&target.contains(e.target))) close(); });
window.addEventListener('resize',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 };
window.DateWheel={ open, openRange, mountRange, close };
})();
+24 -12
View File
@@ -23,9 +23,14 @@
.head .actions{margin-left:auto; display:flex; gap:10px;}
.kpis{display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:20px;}
.toolbar{display:flex; align-items:center; gap:10px; background:var(--surface); border:1px solid var(--border); border-bottom:none; border-radius:var(--r-lg) var(--r-lg) 0 0; padding:12px 14px; transition:background-color .28s,border-color .28s;}
.toolbar .searchbox{width:240px;}
.toolbar .searchbox{width:260px;}
.toolbar .searchbox.code{width:180px;}
.toolbar .sp{margin-left:auto;}
/* chip 尾部图标切换:未筛选=下拉箭头,筛选中=× 单清(镜像 DsChip onClear,待提升至 atoms.css */
.chip .cx{display:none;}
.chip.on .cx{display:block; cursor:pointer; color:var(--muted);}
.chip.on .cx:hover{color:var(--text);}
.chip.on .caret{display:none;}
.placeholder{display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; height:60vh; color:var(--muted);}
.placeholder .pi{width:54px; height:54px; border-radius:var(--r-xl); background:var(--surface); border:1px solid var(--border); display:flex; align-items:center; justify-content:center; color:var(--primary);}
.placeholder .pi svg{width:26px; height:26px; stroke-width:1.6;} .placeholder h2{margin:6px 0 0; font-size:var(--fs-h2); color:var(--heading);}
@@ -52,6 +57,7 @@
</div>
<div class="kpis">
<div class="kpi click" onclick="clearFilters()"><div class="ic ic-info"><svg viewBox="0 0 24 24"><use href="#i-box"/></svg></div><div class="t">SKU 总数</div><div class="v">1,284</div><div class="d up">▲ 2.3% 较上月</div></div>
<!-- 库存货值=成本口径,仅管理员可见(非管理员服务端抹零、卡显示「—」,2026-07-06);原型=管理员形态 -->
<div class="kpi"><div class="ic ic-ok"><svg viewBox="0 0 24 24"><use href="#i-ic17"/></svg></div><div class="t">库存货值</div><div class="v">¥264万</div><div class="d up">▲ 1.1% 较上月</div></div>
<div class="kpi"><div class="ic ic-blue"><svg viewBox="0 0 24 24"><use href="#i-box"/></svg></div><div class="t">在库数量</div><div class="v">18,920</div><div class="d down">▼ 0.6% 较上月</div></div>
<div class="kpi alert click" onclick="filterStatus('已售')"><div class="ic ic-alert"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg></div><div class="t">已售 · 点击筛选</div><div class="v">17</div><div class="d down">需补货 6 项</div></div>
@@ -59,11 +65,12 @@
<div class="toolbar">
<!-- 搜索合一(2026-07-07 用户口径):名称/拼音/编码同框,与 Flutter 实现一致 -->
<div class="searchbox"><svg viewBox="0 0 24 24"><use href="#i-search"/></svg><input id="searchInput" placeholder="商品名 / 拼音 / 编码" oninput="state.q=this.value; state.page=1; render()"></div>
<!-- 2026-07-07 用户口径:筛选全部上移工具栏(参照出库管理),列头漏斗退役、只留排序 -->
<div class="chip" id="chipSpec" onclick="openDimFilter('spec',this)">规格 <span class="cv" id="specVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipSeries" onclick="openDimFilter('series',this)">系列 <span class="cv" id="seriesVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipWh" onclick="openDimFilter('wh',this)">仓库 <span class="cv" id="whVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipStatus">状态 <span class="cv" id="statusVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<!-- 2026-07-07 用户口径:筛选全部上移工具栏(参照出库管理),列头漏斗退役、只留排序
规格/系列/仓库=多选(摘要 值/N 项),状态=单选;选中态尾部 × 单清(镜像 DsChip onClear -->
<div class="chip" id="chipSpec" onclick="openDimFilter('spec',this)">规格 <span class="cv" id="specVal"></span><svg class="cx" viewBox="0 0 24 24" onclick="clearDim('spec',event)"><use href="#i-close"/></svg><svg class="caret" viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipSeries" onclick="openDimFilter('series',this)">系列 <span class="cv" id="seriesVal"></span><svg class="cx" viewBox="0 0 24 24" onclick="clearDim('series',event)"><use href="#i-close"/></svg><svg class="caret" viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipWh" onclick="openDimFilter('wh',this)">仓库 <span class="cv" id="whVal"></span><svg class="cx" viewBox="0 0 24 24" onclick="clearDim('wh',event)"><use href="#i-close"/></svg><svg class="caret" viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipStatus">状态 <span class="cv" id="statusVal"></span><svg class="cx" viewBox="0 0 24 24" onclick="clearStatus(event)"><use href="#i-close"/></svg><svg class="caret" viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="sp"></div>
<div class="btn ghost sm" onclick="clearFilters()">重置</div>
</div>
@@ -117,17 +124,19 @@ const ITEMS=[
{name:'水井坊 井台',code:'SJF-JT-500',spec:'500ml×6',series:'井台',batch:'PZ231209',cat:'浓香',qty:6,unit:'件',cost:'¥680',price:'¥4,080',prodDate:'2023-12-09',inDate:'2026-05-20',supplier:'四川水井坊',value:'¥4,100',status:'已售'},
];
const STATUS=['全部','在售','库存','已售'];
// 成本价/总价列 = 成本口径,仅管理员可见(非管理员两列隐藏、列设置菜单不出现,2026-07-06);原型=管理员形态
const COLS=[{key:'product',label:'商品',fixed:true},{key:'spec',label:'规格'},{key:'series',label:'系列'},{key:'batch',label:'批次号'},{key:'qty',label:'库存',num:true,sort:true},{key:'cost',label:'成本价',num:true,sort:true},{key:'price',label:'总价',num:true,sort:true},{key:'prodDate',label:'生产日期',sort:true},{key:'inDate',label:'入库时间',sort:true},{key:'supplier',label:'供应商'},{key:'status',label:'状态'},{key:'act',label:'操作',fixed:true}];
const state={q:'',code:'',status:'全部',page:1,perPage:10,hidden:new Set(),sortBy:null,sortAsc:true};
const state={q:'',code:'',status:'全部',page:1,perPage:10,hidden:new Set(),sortBy:null,sortAsc:true,spec:new Set(),series:new Set(),wh:new Set()};
function setActive(k){ document.querySelectorAll('.nav[data-k]').forEach(e=>e.classList.toggle('active',e.dataset.k===k)); }
function navTo(k){ setActive(k); const inv=document.querySelector('[data-view="inventory"]'), ph=document.querySelector('[data-view="placeholder"]'); if(k==='inventory'){inv.classList.add('active');ph.classList.remove('active');return;} inv.classList.remove('active');ph.classList.add('active'); const nav=NAVS.find(n=>n.k===k); document.getElementById('phTitle').textContent=nav.label; document.getElementById('phIcon').innerHTML=navSvg(nav.d); }
function visibleCols(){ return COLS.filter(c=>!state.hidden.has(c.key)); }
function filtered(){ const rows=ITEMS.filter(it=>{ if(state.status!=='全部'&&it.status!==state.status)return false; if(state.spec&&it.spec!==state.spec)return false; if(state.series&&it.series!==state.series)return false; if(state.wh&&it.supplier!==state.wh)return false; if(state.q){const q=state.q.toLowerCase(); if(!it.name.toLowerCase().includes(q)&&!it.code.toLowerCase().includes(q))return false;} return true; }); if(state.sortBy&&SORT_VAL[state.sortBy]){const v=SORT_VAL[state.sortBy];rows.sort((a,b)=>{const x=v(a),y=v(b);return (x<y?-1:x>y?1:0)*(state.sortAsc?1:-1);});} return rows; }
function filtered(){ const rows=ITEMS.filter(it=>{ if(state.status!=='全部'&&it.status!==state.status)return false; if(state.spec.size&&!state.spec.has(it.spec))return false; if(state.series.size&&!state.series.has(it.series))return false; if(state.wh.size&&!state.wh.has(it.supplier))return false; if(state.q){const q=state.q.toLowerCase(); if(!it.name.toLowerCase().includes(q)&&!it.code.toLowerCase().includes(q))return false;} return true; }); if(state.sortBy&&SORT_VAL[state.sortBy]){const v=SORT_VAL[state.sortBy];rows.sort((a,b)=>{const x=v(a),y=v(b);return (x<y?-1:x>y?1:0)*(state.sortAsc?1:-1);});} return rows; }
function render(){
document.getElementById('statusVal').textContent=state.status==='全部'?'':state.status;
[['spec','chipSpec','specVal'],['series','chipSeries','seriesVal'],['wh','chipWh','whVal']].forEach(([k,cid,vid])=>{ document.getElementById(vid).textContent=state[k]||''; document.getElementById(cid).classList.toggle('on',!!state[k]); });
// 多选摘要(镜像 DsChip value):1 项=值本身,多项=「N 项」
[['spec','chipSpec','specVal'],['series','chipSeries','seriesVal'],['wh','chipWh','whVal']].forEach(([k,cid,vid])=>{ const s=state[k]; document.getElementById(vid).textContent=!s.size?'':(s.size===1?[...s][0]:`${s.size}`); document.getElementById(cid).classList.toggle('on',!!s.size); });
document.getElementById('chipStatus').classList.toggle('on',state.status!=='全部');
const cols=visibleCols();
document.getElementById('thead').innerHTML='<tr>'+cols.map(c=>{ const cls=(c.num?'num ':'')+(c.key==='act'?'act ':''); if(c.filter){const on=state.status!=='全部'; return `<th class="${cls}"><span class="fh ${on?'filtered':''}" onclick="openFilter('${c.filter}',this)">${c.label} <svg class="funnel" viewBox="0 0 24 24"><use href="#i-ic22"/></svg></span></th>`;} if(c.sort){const on=state.sortBy===c.key; const ic=!on?'i-chevron-down':(state.sortAsc?'i-ic51':'i-chevron-down'); return `<th class="${cls}"><span class="sh ${on?'on':''}" onclick="toggleSort('${c.key}')">${c.label} <svg class="sort-ic" viewBox="0 0 24 24"><use href="#${ic}"/></svg></span></th>`;} return `<th class="${cls}">${c.label}</th>`; }).join('')+'</tr>';
@@ -146,12 +155,15 @@ function filterStatus(s){ state.status=s; state.page=1; navTo('inventory'); rend
// 列头排序:无→升→降→无 循环(真实实现为服务端全局排序)
function toggleSort(k){ if(state.sortBy!==k){state.sortBy=k;state.sortAsc=true;} else if(state.sortAsc){state.sortAsc=false;} else {state.sortBy=null;state.sortAsc=true;} state.page=1; render(); }
const SORT_VAL={qty:it=>it.qty,cost:it=>parseFloat(String(it.cost).replace(/[¥,]/g,''))||0,price:it=>parseFloat(String(it.price).replace(/[¥,]/g,''))||0,prodDate:it=>it.prodDate||'',inDate:it=>it.inDate||''};
function clearFilters(){ state.q='';state.status='全部';state.spec=null;state.series=null;state.wh=null;state.page=1; document.getElementById('searchInput').value=''; render(); }
function clearFilters(){ state.q='';state.status='全部';state.spec.clear();state.series.clear();state.wh.clear();state.page=1; document.getElementById('searchInput').value=''; render(); }
function openFilter(kind,t){ openMenu(t,STATUS.map(v=>({v,label:v,sel:v===state.status})),v=>{ state.status=v; state.page=1; closeMenus(); render(); }); }
// 维度筛选 chip(规格/系列/仓库,2026-07-07 从列头漏斗上移;真实实现为多选
// 维度筛选 chip(规格/系列/仓库,2026-07-07 从列头漏斗上移):多选,点选即生效、菜单保持打开(镜像 showDsMultiMenu
const DIM_KEY={spec:'spec',series:'series',wh:'supplier'};
function openDimFilter(dim,t){ const key=DIM_KEY[dim]; const opts=['全部',...new Set(ITEMS.map(it=>it[key]).filter(Boolean))]; openMenu(t,opts.map(v=>({v,label:v,sel:(state[dim]||'全部')===v})),v=>{ state[dim]=v==='全部'?null:v; state.page=1; closeMenus(); render(); }); }
function openDimFilter(dim,t){ const key=DIM_KEY[dim]; const opts=[...new Set(ITEMS.map(it=>it[key]).filter(Boolean))]; openMenu(t,opts.map(v=>({v,label:v,sel:state[dim].has(v)})),v=>{ state[dim].has(v)?state[dim].delete(v):state[dim].add(v); state.page=1; render(); openDimFilter(dim,t); }); }
// chip 尾部 × 单清(镜像 DsChip onClear:只清本维度,不展开菜单)
function clearDim(dim,e){ e.stopPropagation(); state[dim].clear(); state.page=1; render(); }
function clearStatus(e){ e.stopPropagation(); state.status='全部'; state.page=1; render(); }
document.getElementById('chipStatus').onclick=e=>openFilter('status',e.currentTarget);
const COL_KEY='jiu-inv-hidden';
function saveHidden(){ try{ localStorage.setItem(COL_KEY, JSON.stringify([...state.hidden])); }catch(e){} }
+363
View File
@@ -0,0 +1,363 @@
<!DOCTYPE html>
<html lang="zh-CN" data-theme="a">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>授权管理 · 岩美酒库</title>
<link rel="stylesheet" href="../tokens.css">
<link rel="stylesheet" href="../atoms.css">
<style>
/* ====== App-shell(沿用 inventory.html ====== */
body{margin:0; height:100vh; overflow:hidden; font-family:var(--font); color:var(--text); background:var(--bg); transition:background-color .28s;}
.app{height:100vh; display:flex; flex-direction:column;}
.head{display:flex; align-items:flex-end; gap:14px; margin-bottom:16px;}
.head h1{margin:0; font-size:var(--fs-h1); font-weight:700; color:var(--title); letter-spacing:.3px;}
.head .sub{font-size:var(--fs-sm); color:var(--muted); margin-bottom:2px;}
.head .actions{margin-left:auto; display:flex; gap:10px;}
/* 主 tab(授权信息 / 订单管理):.seg 原子 */
.lic-tabs{margin-bottom:18px;}
.ltab{display:none;} .ltab.on{display:block;}
/* ====== Tab1 授权信息(与原 settings 授权面板同构,license_panel.dart 四卡) ====== */
.card{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); padding:22px 24px; margin-bottom:18px; transition:background-color .28s,border-color .28s;}
.card h2{margin:0 0 4px; font-size:var(--fs-h2); color:var(--heading);}
.card .desc{margin:0 0 18px; font-size:var(--fs-sm); color:var(--muted);}
.lic-wrap{max-width:720px;}
/* 授权信息 3 cell 横排(02cffcb)—— 待提升至 atoms.css */
.lic-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:14px;}
.lic-cell{background:var(--bg); border:1px solid var(--border-subtle); border-radius:var(--r-md); padding:14px 16px;}
.lic-cell .lk{font-size:var(--fs-xs); color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
.lic-cell .lv{font-size:var(--fs-h2); font-weight:700; color:var(--heading); margin-top:6px; font-family:var(--font-mono);}
.lic-cell .lv.ok{color:var(--success);}
.redeem{display:flex; gap:10px; max-width:520px; align-items:flex-end;}
.redeem .field{flex:1;}
/* 在线购买卡(对齐 purchase_card.dart 三阶段 pick/waiting/success)—— 待提升至 atoms.css */
.buy{max-width:560px;}
.buy .segs{display:flex; gap:10px; flex-wrap:wrap; margin-bottom:14px;}
.buy .promo-note{padding:12px; border:1px solid var(--warn); background:var(--warn-bg); border-radius:var(--r-md); margin-bottom:10px;}
.buy .promo-note .ph{font-size:var(--fs-body); font-weight:700; color:var(--warn);}
.buy .promo-note .pd{font-size:var(--fs-sm); color:var(--text); margin-top:3px;}
.buy .promo-note.grayed{border-color:var(--border); background:var(--bg);}
.buy .promo-note.grayed .ph, .buy .promo-note.grayed .pd{color:var(--faint);}
.buy .feats{padding:14px; background:var(--bg); border:1px solid var(--border); border-radius:var(--r-md);}
.buy .feats .fr{display:flex; align-items:center; gap:8px; padding:3px 0; font-size:var(--fs-sm); color:var(--text);}
.buy .feats .fr svg{width:14px; height:14px; stroke-width:2; color:var(--success); flex:none;}
.buy .feats.grayed .fr{color:var(--faint);} .buy .feats.grayed .fr svg{color:var(--faint);}
.buy .price-row{display:flex; align-items:flex-end; gap:8px; margin-top:14px;}
.buy .price-row .pl{flex:1; font-size:var(--fs-sm); color:var(--muted);}
.buy .price-row .strike{font-size:var(--fs-sm); color:var(--faint); text-decoration:line-through; font-family:var(--font-mono);}
.buy .price-row .pp{font-size:24px; font-weight:700; color:var(--heading); font-family:var(--font-mono); line-height:1;}
.buy .price-row .pp.promo{color:var(--warn);} .buy .price-row .pp.grayed{color:var(--faint);}
.buy .save{font-size:var(--fs-xs); color:var(--success); margin-top:6px;}
.buy .fine{font-size:var(--fs-xs); color:var(--faint); margin-top:6px;}
.buy .acts{display:flex; justify-content:flex-end; gap:10px; margin-top:12px;}
.buy .obox{padding:12px; background:var(--bg); border:1px solid var(--border-subtle); border-radius:var(--r-md); margin-top:14px;}
.buy .spin{width:18px; height:18px; border-radius:50%; border:2px solid var(--border); border-top-color:var(--primary); animation:buy-spin .8s linear infinite; flex:none;}
@keyframes buy-spin{to{transform:rotate(360deg)}}
.buy .okic{width:40px; height:40px; border-radius:var(--r-md); background:var(--ok-soft); color:var(--success); display:flex; align-items:center; justify-content:center; flex:none;}
.buy .okic svg{width:20px; height:20px; stroke-width:2;}
/* ====== Tab2 订单管理(原 license-orders.html ====== */
.kpis{display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:20px;}
.toolbar{display:flex; align-items:center; gap:10px; background:var(--surface); border:1px solid var(--border); border-bottom:none; border-radius:var(--r-lg) var(--r-lg) 0 0; padding:12px 14px; flex-wrap:wrap; transition:background-color .28s,border-color .28s;}
.toolbar .searchbox{width:260px;}
.toolbar .sp{margin-left:auto;}
/* 订单状态徽章(新增变体,待提升至 atoms.css) */
.badge.b-待支付{background:var(--warn-bg); color:var(--warn);}
.badge.b-已支付{background:var(--success-bg); color:var(--success);}
.badge.b-已关闭{background:var(--info-soft); color:var(--muted);}
/* 设计期控件:授权面板 标准 / iOS 合规 双态切换(仅授权信息 tab 显示,仿 pay.html 三态做法) */
.ds-states{position:fixed; left:22px; bottom:22px; display:flex; gap:6px; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-pill); padding:5px; box-shadow:var(--sh-2); z-index:var(--z-fab);}
.ds-states button{border:none; background:none; font:inherit; font-size:var(--fs-xs); color:var(--muted); padding:5px 12px; border-radius:var(--r-pill); cursor:pointer;}
.ds-states button.on{background:var(--brand50); color:var(--primary); font-weight:600;}
/* 设计期返回悬浮球(右下角,非侵入) */
.ds-fab{position:fixed; right:22px; bottom:22px; 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:.6; transition:opacity .18s,color .18s,border-color .18s,transform .18s,box-shadow .18s; text-decoration:none;}
.ds-fab:hover{opacity:1; color:var(--primary); border-color:var(--primary); box-shadow:var(--sh-3); transform:translateY(-2px);}
.ds-fab svg{width:20px; height:20px; stroke-width:2; fill:none; stroke:currentColor;}
</style>
</head>
<body>
<div class="app">
<div class="top" data-shell-top data-active="license"></div>
<div class="body">
<div class="side" id="side"></div>
<div class="main">
<div class="head">
<h1>授权管理</h1><span class="sub" id="subInfo">授权状态 · 购买续费 · 订单流水</span>
<div class="actions" id="ordersActions" hidden>
<div class="btn ghost" onclick="render()"><svg viewBox="0 0 24 24"><use href="#i-refresh"/></svg>刷新</div>
</div>
</div>
<!-- 主 tab:授权信息 / 订单管理 -->
<div class="lic-tabs"><div class="seg">
<button class="on" id="tabBtnInfo" onclick="showTab('info')">授权信息</button>
<button id="tabBtnBuy" onclick="showTab('buy')">在线购买 / 续费</button>
<button id="tabBtnOrders" onclick="showTab('orders')">订单管理</button>
</div></div>
<!-- ========== Tab1 授权信息(license_panel.dart 四卡 + iOS 合规客服卡) ========== -->
<div class="ltab on lic-wrap" id="tab-info">
<div class="card">
<h2>授权信息</h2>
<p class="desc">当前门店的授权状态与有效期,续期时长在现有到期时间上叠加</p>
<div class="lic-grid">
<div class="lic-cell"><div class="lk">授权状态</div><div class="lv ok">已授权</div></div>
<div class="lic-cell"><div class="lk">到期日</div><div class="lv">2027-03-15</div></div>
<div class="lic-cell"><div class="lk">剩余天数</div><div class="lv">267 天</div></div>
</div>
</div>
<div class="card">
<h2>兑换券</h2>
<p class="desc">输入兑换券短码续期</p>
<div class="redeem">
<div class="field"><label>兑换券短码</label><input class="input" id="voucher" placeholder="输入兑换券短码"></div>
<div class="btn primary" onclick="redeem()">兑换续期</div>
</div>
</div>
<div class="card">
<h2>到期后的降级规则</h2>
<p class="desc">授权到期后系统各功能的可用性说明</p>
<div style="font-size:var(--fs-sm);color:var(--muted)">
<div style="padding-top:3px">· 过期 7 天内(宽限期):仍可正常使用,请尽快续费</div>
<div style="padding-top:3px">· 过期 7–15 天(只读期):仅能查看数据,无法新增或修改</div>
<div style="padding-top:3px">· 过期满 15 天:系统锁定,无法登录,需续费或激活新授权码后恢复</div>
</div>
</div>
<!-- 联系客服(仅 iOS 合规形态,dddf402;措辞中性无购买引导) -->
<div class="card" id="svcCard" hidden>
<h2>联系客服</h2>
<p class="desc">授权与使用问题可通过邮件联系我们</p>
<div style="display:flex;align-items:center">
<span style="font-size:var(--fs-sm);color:var(--muted)">客服邮箱 · </span>
<span style="font-size:var(--fs-sm);font-weight:600;color:var(--primary);font-family:var(--font-mono);cursor:pointer" onclick="toast('打开 mailto:support@51yanmei.com(原型)')">support@51yanmei.com</span>
</div>
</div>
</div>
<!-- ========== Tab2 在线购买 / 续费(purchase_card.dartadmin 形态;iOS 合规态整 tab 隐藏) ========== -->
<div class="ltab lic-wrap" id="tab-buy">
<div class="card">
<h2>在线购买 / 续费</h2>
<p class="desc">选择套餐支付宝支付,到账后授权自动续期</p>
<div class="buy" id="buyCard"></div>
</div>
</div>
<!-- ========== Tab3 订单管理(授权购买/续费订单流水,license_purchases ========== -->
<div class="ltab" id="tab-orders">
<div class="kpis">
<div class="kpi"><div class="ic ic-ok"><svg viewBox="0 0 24 24"><use href="#i-ic24"/></svg></div><div class="t">当前授权 · 高级版</div><div class="v">2027-06-04</div><div class="d up">剩余 329 天 · 续期自动叠加</div></div>
<div class="kpi"><div class="ic ic-info"><svg viewBox="0 0 24 24"><use href="#i-ic47"/></svg></div><div class="t">累计购买金额</div><div class="v">¥4,197</div><div class="d">自 2026-02 开通</div></div>
<div class="kpi"><div class="ic ic-info"><svg viewBox="0 0 24 24"><use href="#i-ic53"/></svg></div><div class="t">订单总数</div><div class="v">7</div><div class="d">已支付 5 笔</div></div>
<div class="kpi warn click" onclick="filterStatus('待支付')"><div class="ic ic-warn"><svg viewBox="0 0 24 24"><use href="#i-ic04"/></svg></div><div class="t">待支付 · 点击筛选</div><div class="v">1</div><div class="d">未支付订单 2 小时后自动关闭</div></div>
</div>
<div class="toolbar">
<div class="searchbox"><svg viewBox="0 0 24 24"><use href="#i-search"/></svg><input id="searchInput" placeholder="搜索订单号 / 套餐" oninput="state.q=this.value; state.page=1; render()"></div>
<div class="chip" id="chipPlan" onclick="openPlanFilter(this)">套餐 <span class="cv" id="planVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipStatus" onclick="openStatusFilter(this)">状态 <span class="cv" id="statusVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipDate" onclick="openDateFilter(this)"><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg>下单时间 <span class="cv" id="dateVal"></span></div>
<div class="sp"></div>
<div class="btn ghost sm" id="resetBtn" onclick="clearFilters()"><svg viewBox="0 0 24 24"><use href="#i-refresh"/></svg>重置</div>
</div>
<div class="table flush-top"><table><thead id="thead"></thead><tbody id="tbody"></tbody></table></div>
<div class="pager"><div class="pg-size">每页 <button class="pgsize-btn" id="pageSizeBtn" onclick="openPageSize(event)"><span id="pageSizeVal">10</span><svg class="pgsize-caret" viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></button></div><span id="pagerInfo"></span><div class="pg" id="pager"></div></div>
</div>
</div>
</div>
</div>
<div class="drawer-mask" id="drawerMask" onclick="closeDrawer()"></div>
<div class="drawer" id="drawer"><div class="drawer-head"><h3 id="dTitle">订单详情</h3><svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closeDrawer()"><use href="#i-close"/></svg></div><div class="drawer-body" id="drawerBody"></div></div>
<div class="toast" id="toast"></div>
<script src="shell.js"></script>
<script>
/* ==================== 主 tab 切换 ==================== */
const TAB_BTN={info:'tabBtnInfo',buy:'tabBtnBuy',orders:'tabBtnOrders'};
function showTab(t){
for(const k of ['info','buy','orders']){
const on = k===t;
document.getElementById('tab-'+k).classList.toggle('on',on);
document.getElementById(TAB_BTN[k]).classList.toggle('on',on);
}
const orders = t==='orders';
document.getElementById('licStates').hidden = orders; // iOS 双态开关在授权/购买两 tab 生效
document.getElementById('ordersActions').hidden = !orders;
document.getElementById('subInfo').textContent = orders ? subOrders()
: (t==='buy' ? '选择套餐支付宝支付 · 到账自动续期' : '授权状态 · 兑换券 · 降级规则');
if(orders) render();
}
/* ==================== Tab1 在线购买卡(对齐 purchase_card.dart / license_plans.dart 三阶段) ==================== */
const PLANS={
promo:{price:1,days:30,orig:299,feats:['标准版全部功能,一分不少','单门店 · 单仓库 · 2 台客户端','1,000 张商品图片分享','每个门店限购一次']},
standard:{name:'标准版',monthly:299,annual:2999,feats:['单门店 · 单仓库','2 台客户端同时使用','1,000 张商品图片分享']},
pro:{name:'高级版',monthly:599,annual:5999,feats:['单门店 · 多仓库','5 台客户端同时使用','10,000 张商品图片分享','免费 AI 周度 / 月度商业数据分析']},
};
const buy={tab:'promo',cycle:'annual',phase:'pick',promoUsed:false};
function fmt(n){ return n.toLocaleString(); }
function segHtml(items,cur,fn){ return `<div class="seg">${items.map(([k,label])=>`<button class="${cur===k?'on':''}" onclick="${fn}('${k}')">${label}</button>`).join('')}</div>`; }
function buyTab(k){ if(k==='promo'&&buy.promoUsed){ toast('本店已享受过首月特惠'); } buy.tab=k; drawBuy(); }
function buyCycle(k){ buy.cycle=k; drawBuy(); }
function buyPay(){ if(buy.tab==='promo'&&buy.promoUsed) return; buy.phase='waiting'; drawBuy(); }
/* 支付成功(86611dc):本单为特惠则置灰「已享受」,选择页默认落 高级版·年付 */
function buyDone(){ if(buy.tab==='promo') buy.promoUsed=true; buy.tab='pro'; buy.cycle='annual'; buy.phase='success'; drawBuy(); }
function buyBack(){ buy.phase='pick'; drawBuy(); }
function drawBuy(){
const el=document.getElementById('buyCard');
if(buy.phase==='waiting'){
const p=buy.tab==='promo'?PLANS.promo.price:(buy.cycle==='annual'?PLANS[buy.tab].annual:PLANS[buy.tab].monthly);
el.innerHTML=`<div style="display:flex;align-items:center;gap:12px"><div class="spin"></div><div style="flex:1;font-size:var(--fs-sm);color:var(--text)">已在浏览器打开支付宝收银台,完成支付后本卡片会自动确认到账。</div></div>
<div class="obox"><div style="font-size:var(--fs-xs);color:var(--muted)">订单号</div><div style="font-size:var(--fs-sm);font-family:var(--font-mono);margin-top:4px">JIU20260710160001</div><div style="font-size:var(--fs-sm);color:var(--text);margin-top:8px">金额 ¥${fmt(p)}</div></div>
<div class="fine" style="margin-top:10px">离开本页不影响到账:支付成功后授权自动续期,可稍后回本页刷新查看。</div>
<div class="acts"><div class="btn ghost" onclick="buyBack()">重新选择</div><div class="btn primary" onclick="buyDone()">我已完成支付</div></div>`;
return;
}
if(buy.phase==='success'){
el.innerHTML=`<div style="display:flex;align-items:center;gap:12px"><div class="okic"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg></div><div style="flex:1;font-size:var(--fs-title);font-weight:600;color:var(--heading)">门店授权已续期至 2028-03-15。</div></div>
<div style="font-size:var(--fs-xs);color:var(--muted);margin-top:10px">订单号 JIU20260710160001</div>
<div class="acts"><div class="btn ghost" onclick="buyBack()">完成</div></div>`;
return;
}
// pick:套餐 seg + (非特惠)周期 seg + 特惠说明框 + 权益清单 + 价格行 + 细则 + 提交
const isPromo=buy.tab==='promo', grayed=isPromo&&buy.promoUsed, g=PLANS[buy.tab];
let h=`<div class="segs">${segHtml([['promo',buy.promoUsed?'首月特惠(已享受)':'🔥 首月特惠 ¥1'],['standard','标准版'],['pro','高级版']],buy.tab,'buyTab')}`;
if(!isPromo) h+=segHtml([['annual','年付 · 省 2 个月'],['monthly','月付']],buy.cycle,'buyCycle');
h+=`</div>`;
if(isPromo){
h+=`<div class="promo-note ${grayed?'grayed':''}"><div class="ph">${grayed?'本店已享受过首月特惠':'新店专享 · 仅 ¥1 体验 30 天'}</div><div class="pd">${grayed?'首月特惠每个门店限购一次,可选择标准版或高级版继续续费。':'解锁标准版全部功能,原价 ¥299,每个门店限购一次——试过才知道多省心。'}</div></div>`;
}
const feats=isPromo?PLANS.promo.feats:g.feats;
h+=`<div class="feats ${grayed?'grayed':''}">${feats.map(f=>`<div class="fr"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg>${f}</div>`).join('')}</div>`;
const annual=buy.cycle==='annual', price=isPromo?PLANS.promo.price:(annual?g.annual:g.monthly);
const label=isPromo?`首月特惠 · ${PLANS.promo.days} 天授权`:`${g.name} · ${annual?'年付':'月付'} · ${annual?365:30} 天授权`;
h+=`<div class="price-row"><span class="pl">${label}</span>`;
if(isPromo) h+=`<span class="strike">¥${fmt(PLANS.promo.orig)}</span>`;
if(!isPromo&&annual) h+=`<span class="strike">¥${fmt(g.monthly*12)}</span>`;
h+=`<span class="pp ${grayed?'grayed':isPromo?'promo':''}">¥${fmt(price)}</span></div>`;
if(!isPromo&&annual) h+=`<div class="save">月付 ¥${fmt(g.monthly)} × 12 个月 = ¥${fmt(g.monthly*12)},年付立省 ¥${fmt(g.monthly*12-g.annual)},相当于免费用 2 个月</div>`;
h+=`<div class="fine">到期时间自动叠加,未到期续费不吃亏 · 支付宝支付</div>
<div class="acts"><div class="btn primary" style="${grayed?'opacity:.45;cursor:default':''}" onclick="buyPay()">${grayed?'本店已享受过':`提交订单 · ¥${fmt(price)}`}</div></div>`;
el.innerHTML=h;
}
drawBuy();
/* 兑换券 */
function redeem(){ const v=document.getElementById('voucher').value.trim(); if(!v){ toast('请输入兑换券短码'); return;} document.getElementById('voucher').value=''; toast('兑换成功,已续期 ✓'); }
/* iOS App Store 合规双态(dddf402):隐藏「在线购买/续费」整个 tab,授权信息 tab 补「联系客服」中性卡 */
function setLicVariant(ios){
document.getElementById('tabBtnBuy').style.display = ios?'none':'';
document.getElementById('svcCard').hidden=!ios;
document.querySelectorAll('#licStates button').forEach((b,i)=>b.classList.toggle('on',(i===1)===ios));
if(ios && document.getElementById('tab-buy').classList.contains('on')) showTab('info');
}
/* ==================== Tab2 订单管理(license_purchases 流水) ====================
状态三态与后端对齐:待支付 pending / 已支付 paid / 已关闭 failed(取消·超时·失败归并)。 */
const ORDERS=[
{no:'pay-20260710101433-9c41f7ae',plan:'高级版 · 年付',days:365,amount:5999,status:'待支付',by:'王老板',created:'2026-07-10 10:14',paidAt:'',channel:'',renewTo:''},
{no:'pay-20260702174902-b2e85c10',plan:'高级版 · 年付',days:365,amount:5999,status:'已关闭',by:'王老板',created:'2026-07-02 17:49',paidAt:'',channel:'',renewTo:'',closeReason:'超时未支付,订单自动关闭'},
{no:'pay-20260610093015-4d7a20cf',plan:'高级版 · 月付',days:30,amount:599,status:'已支付',by:'王老板',created:'2026-06-10 09:30',paidAt:'2026-06-10 09:31',channel:'支付宝',renewTo:'2027-06-04'},
{no:'pay-20260503153847-71c9d3e2',plan:'标准版 · 年付',days:365,amount:2999,status:'已支付',by:'王老板',created:'2026-05-03 15:38',paidAt:'2026-05-03 15:40',channel:'支付宝',renewTo:'2027-05-05'},
{no:'pay-20260405112230-e8f04a6b',plan:'标准版 · 月付',days:30,amount:299,status:'已支付',by:'张店长',created:'2026-04-05 11:22',paidAt:'2026-04-05 11:22',channel:'支付宝',renewTo:'2026-05-05'},
{no:'pay-20260306084514-2a6bd591',plan:'标准版 · 月付',days:30,amount:299,status:'已支付',by:'王老板',created:'2026-03-06 08:45',paidAt:'2026-03-06 08:46',channel:'支付宝',renewTo:'2026-04-05'},
{no:'pay-20260204193302-c5d8e947',plan:'首月特惠 · 30 天',days:30,amount:1,status:'已支付',by:'王老板',created:'2026-02-04 19:33',paidAt:'2026-02-04 19:33',channel:'支付宝',renewTo:'2026-03-06'},
];
const STATUS=['全部','待支付','已支付','已关闭'];
const PLAN_OPTS=['全部','首月特惠 · 30 天','标准版 · 月付','标准版 · 年付','高级版 · 月付','高级版 · 年付'];
const COLS=[{key:'no',label:'订单号'},{key:'plan',label:'套餐'},{key:'days',label:'时长',num:true},{key:'amount',label:'金额',num:true},{key:'status',label:'状态'},{key:'by',label:'下单人'},{key:'created',label:'下单时间'},{key:'paidAt',label:'支付时间'},{key:'act',label:'操作',fixed:true}];
const state={q:'',plan:'全部',status:'全部',dateFrom:'',dateTo:'',page:1,perPage:10};
function money(n){ return '¥'+n.toLocaleString(); }
/* 状态代表图标(同出库单:徽章图标化 .bi) */
const STICON={'待支付':'i-ic04','已支付':'i-check','已关闭':'i-close'};
function stBadge(v){ return `<span class="badge bi b-${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg>${v}</span>`; }
function subOrders(){ const paidSum=ORDERS.filter(o=>o.status==='已支付').reduce((s,o)=>s+o.amount,0); return `${filtered().length} 笔 · 累计 ${money(paidSum)}`; }
function filtered(){ return ORDERS.filter(o=>{
if(state.status!=='全部'&&o.status!==state.status)return false;
if(state.plan!=='全部'&&o.plan!==state.plan)return false;
const d=o.created.slice(0,10);
if(state.dateFrom&&d<state.dateFrom)return false;
if(state.dateTo&&d>state.dateTo)return false;
if(state.q){const q=state.q.toLowerCase(); if(!(o.no.toLowerCase().includes(q)||o.plan.toLowerCase().includes(q)))return false;}
return true; }); }
function syncChips(){
const set=(id,chipId,val)=>{ document.getElementById(id).textContent=val||''; document.getElementById(chipId).classList.toggle('on',!!val); };
set('planVal','chipPlan',state.plan==='全部'?'':state.plan);
set('statusVal','chipStatus',state.status==='全部'?'':state.status);
set('dateVal','chipDate',state.dateLabel||'');
const any=state.q||state.plan!=='全部'||state.status!=='全部'||state.dateFrom||state.dateTo;
document.getElementById('resetBtn').classList.toggle('primary',!!any);
}
function render(){
syncChips();
document.getElementById('thead').innerHTML='<tr>'+COLS.map(c=>{ const cls=(c.num?'num ':'')+(c.key==='act'?'act ':''); return `<th class="${cls}">${c.label}</th>`; }).join('')+'</tr>';
const rows=filtered(); const start=(state.page-1)*state.perPage; const pageRows=rows.slice(start,start+state.perPage); const tb=document.getElementById('tbody');
if(rows.length===0){ tb.innerHTML=`<tr><td colspan="${COLS.length}"><div class="empty">没有匹配的授权订单 · 试试调整筛选或搜索</div></td></tr>`; }
else { tb.innerHTML=pageRows.map(o=>{ const idx=ORDERS.indexOf(o); return '<tr class="click" onclick="openDetail('+idx+')">'+COLS.map(c=>{
if(c.key==='no')return `<td><span class="pcode" style="font-size:var(--fs-sm)">${o.no}</span></td>`;
if(c.key==='plan')return `<td class="pname">${o.plan}</td>`;
if(c.key==='days')return `<td class="num">${o.days} 天</td>`;
if(c.key==='amount')return `<td class="num">${money(o.amount)}</td>`;
if(c.key==='status')return `<td>${stBadge(o.status)}</td>`;
if(c.key==='by')return `<td>${o.by}</td>`;
if(c.key==='created')return `<td style="font-family:var(--font-mono);font-size:var(--fs-sm)">${o.created}</td>`;
if(c.key==='paidAt')return `<td style="font-family:var(--font-mono);font-size:var(--fs-sm)">${o.paidAt||'<span style="color:var(--faint)">—</span>'}</td>`;
if(c.key==='act')return `<td class="act"><div class="row-act" onclick="event.stopPropagation()"><svg viewBox="0 0 24 24" onclick="openDetail(${idx})"><use href="#i-eye"/></svg></div></td>`;
return `<td>${o[c.key]}</td>`; }).join('')+'</tr>'; }).join(''); }
document.getElementById('pagerInfo').textContent=`显示 ${rows.length?start+1:0}${Math.min(start+state.perPage,rows.length)},共 ${rows.length}`;
const pages=Math.max(1,Math.ceil(rows.length/state.perPage)); let pg=`<button ${state.page<=1?'disabled':''} onclick="go(${state.page-1})"></button>`; for(let p=1;p<=pages;p++)pg+=`<button class="${p===state.page?'cur':''}" onclick="go(${p})">${p}</button>`; pg+=`<button ${state.page>=pages?'disabled':''} onclick="go(${state.page+1})"></button>`; document.getElementById('pager').innerHTML=pg;
document.getElementById('subInfo').textContent=subOrders();
}
function go(p){ const pages=Math.max(1,Math.ceil(filtered().length/state.perPage)); state.page=Math.min(Math.max(1,p),pages); render(); }
const PAGE_SIZES=[10,20,50,100];
function openPageSize(e){ openMenu(e.currentTarget, PAGE_SIZES.map(n=>({v:String(n),label:String(n),sel:n===state.perPage})), v=>{ closeMenus(); setPerPage(v); }, 'pgsize-menu', 88); }
function setPerPage(v){ state.perPage=+v; state.page=1; const el=document.getElementById('pageSizeVal'); if(el)el.textContent=v; render(); }
function filterStatus(s){ state.status=s; state.page=1; render(); }
function clearFilters(){ state.q='';state.plan='全部';state.status='全部';state.dateFrom='';state.dateTo='';state.dateLabel='';state.datePreset='all';state.page=1; document.getElementById('searchInput').value=''; render(); }
function openStatusFilter(t){ openMenu(t,STATUS.map(v=>({v,label:v,sel:v===state.status})),v=>{ state.status=v; state.page=1; closeMenus(); render(); }); }
function openPlanFilter(t){ openMenu(t,PLAN_OPTS.map(v=>({v,label:v,sel:v===state.plan})),v=>{ state.plan=v; state.page=1; closeMenus(); render(); }); }
const TODAY='2026-07-10';
function dateMinus(days){ const d=new Date(TODAY); d.setDate(d.getDate()-days); return d.toISOString().slice(0,10); }
const DATE_PRESETS=[{v:'all',label:'全部时间'},{v:'30',label:'近 30 天'},{v:'90',label:'近 90 天'},{v:'year',label:'本年'}];
function openDateFilter(t){ openMenu(t,DATE_PRESETS.map(p=>({v:p.v,label:p.label,sel:p.v===(state.datePreset||'all')})),v=>{ applyDatePreset(v); state.page=1; closeMenus(); render(); }); }
function applyDatePreset(v){ state.datePreset=v; if(v==='all'){state.dateFrom='';state.dateTo='';state.dateLabel='';}
else if(v==='year'){state.dateFrom=TODAY.slice(0,4)+'-01-01';state.dateTo=TODAY;state.dateLabel='本年';}
else {state.dateFrom=dateMinus(Number(v)-1);state.dateTo=TODAY;state.dateLabel='近'+v+'天';} }
function openDetail(idx){ const o=ORDERS[idx]; document.getElementById('dTitle').textContent='订单详情';
const rows=[
['订单号',`<span class="pcode" style="font-size:var(--fs-sm)">${o.no}</span>`],
['套餐',o.plan],['授权时长',`${o.days}`],['金额',`<span style="font-family:var(--font-mono)">${money(o.amount)}</span>`],
['状态',stBadge(o.status)],['支付渠道',o.channel||'<span style="color:var(--faint)">—</span>'],
['下单人',o.by],['下单时间',`<span style="font-family:var(--font-mono);font-size:var(--fs-sm)">${o.created}</span>`],
['支付时间',o.paidAt?`<span style="font-family:var(--font-mono);font-size:var(--fs-sm)">${o.paidAt}</span>`:'<span style="color:var(--faint)">—</span>'],
];
if(o.status==='已支付')rows.push(['授权续期至',`<span style="font-family:var(--font-mono);color:var(--success)">${o.renewTo}</span>`]);
let html=rows.map(([k,v])=>`<div class="drow"><span>${k}</span><b>${v}</b></div>`).join('');
if(o.status==='待支付') html+=`<div class="notice info" style="margin-top:16px"><svg viewBox="0 0 24 24"><use href="#i-info"/></svg><div>订单未支付将在下单 2 小时后自动关闭;支付成功后授权时长自动叠加到当前到期日,不浪费剩余天数。</div></div>`;
if(o.status==='已关闭') html+=`<div class="notice info" style="margin-top:16px"><svg viewBox="0 0 24 24"><use href="#i-info"/></svg><div>${o.closeReason||'订单已关闭'}。如需购买请重新下单,价格以下单时套餐表为准。</div></div>`;
let acts='';
if(o.status==='待支付') acts=`<div class="dact-group"><span class="dact-label">订单操作</span><div class="dact-row"><div class="btn primary sm" onclick="toast('已打开支付宝收银台(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic08"/></svg>继续支付</div><div class="btn danger sm" onclick="toast('订单已取消');closeDrawer()">取消订单</div></div></div>`;
if(o.status==='已关闭') acts=`<div class="dact-group"><span class="dact-label">订单操作</span><div class="dact-row"><div class="btn primary sm" onclick="closeDrawer();showTab('buy')"><svg viewBox="0 0 24 24"><use href="#i-plus"/></svg>重新购买</div></div></div>`;
if(o.status==='已支付') acts=`<div class="dact-group"><span class="dact-label">相关</span><div class="dact-row"><div class="btn ghost sm" onclick="closeDrawer();showTab('info')"><svg viewBox="0 0 24 24"><use href="#i-ic24"/></svg>查看授权</div></div></div>`;
document.getElementById('drawerBody').innerHTML=html+`<div class="dactions">${acts}</div>`;
document.getElementById('drawer').classList.add('show'); document.getElementById('drawerMask').classList.add('show'); }
function closeDrawer(){ document.getElementById('drawer').classList.remove('show'); document.getElementById('drawerMask').classList.remove('show'); }
document.addEventListener('keydown',e=>{ if(e.key==='Escape'){closeMenus();closeDrawer();} });
</script>
<!-- 设计期控件:授权面板形态切换(仅授权信息 tab 显示) -->
<div class="ds-states" id="licStates">
<button class="on" onclick="setLicVariant(false)">标准</button>
<button onclick="setLicVariant(true)">iOS 合规</button>
</div>
<a class="ds-fab" href="../index.html" title="返回设计系统" aria-label="返回设计系统"><svg viewBox="0 0 24 24"><use href="#i-arrow-left"/></svg></a>
<script src="fab.js"></script>
<script src="notify.js"></script>
<script src="statusbar.js"></script>
<script src="icons.js"></script>
</body>
</html>
+4 -3
View File
@@ -14,7 +14,7 @@
.ab-ico svg{width:40px; height:40px;}
.ab-nm{font-size:var(--fs-h1); font-weight:800; color:var(--heading); margin-top:14px;}
.ab-ver{font-size:var(--fs-sm); color:var(--muted); margin-top:4px; font-family:var(--font-mono);}
.ab-btns{display:flex; gap:10px; margin-top:18px; width:100%;}
.ab-btns{display:flex; gap:10px; margin-top:10px; width:100%;}
.tl{position:relative; padding-left:18px;}
.tl-item{position:relative; padding:0 0 16px 14px; border-left:2px solid var(--border-subtle);}
.tl-item:last-child{border-left-color:transparent;}
@@ -38,13 +38,14 @@
<div class="ab-ico"><svg viewBox="0 0 64 64"><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M14 33L23 16L32 25L41 16L50 33"/><path d="M15 41L49 41"/></g><circle cx="32" cy="48" r="3.4" fill="#C97B86"/></svg></div>
<div class="ab-nm">岩美酒库</div>
<div class="ab-ver">v1.1.3 · 酒水进销存管理系统</div>
<div style="margin-top:10px"><div class="btn ghost sm" onclick="mToast('已是最新版本 ✓')"><svg viewBox="0 0 24 24"><use href="#i-refresh"/></svg>检查更新</div></div>
<!-- 检查更新与反馈按钮同规格(f9d5bc7:38h 通栏,左右边缘对齐) -->
<div class="ab-btns" style="margin-top:12px"><div class="btn ghost" style="flex:1; justify-content:center" onclick="mToast('已是最新版本 ✓')"><svg viewBox="0 0 24 24"><use href="#i-refresh"/></svg>检查更新</div></div>
<div class="ab-btns"><div class="btn primary" style="flex:1; justify-content:center" onclick="openFb('Bug')"><svg viewBox="0 0 24 24"><use href="#i-bug"/></svg>反馈 Bug</div><div class="btn ghost" style="flex:1; justify-content:center" onclick="openFb('功能建议')"><svg viewBox="0 0 24 24"><use href="#i-lightbulb"/></svg>功能建议</div></div>
</div>
<div class="m-hub" style="margin-top:10px">
<a onclick="mToast('打开官网 jiu.51yanmei.com(原型)')"><span class="hic"><svg viewBox="0 0 24 24"><use href="#i-ic01"/></svg></span>官方网站<span class="hchev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></span></a>
<a onclick="mToast('微信客服 yammy2023(原型)')"><span class="hic"><svg viewBox="0 0 24 24"><use href="#i-ic02"/></svg></span>微信客服<span class="hchev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></span></a>
<a onclick="mToast('微信客服:请见官网(原型)')"><span class="hic"><svg viewBox="0 0 24 24"><use href="#i-ic02"/></svg></span>微信客服<span class="hchev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></span></a>
<a onclick="mToast('隐私政策(原型)')"><span class="hic"><svg viewBox="0 0 24 24"><use href="#i-shield-2"/></svg></span>隐私政策<span class="hchev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></span></a>
<a onclick="mToast('服务条款(原型)')"><span class="hic"><svg viewBox="0 0 24 24"><use href="#i-ic06"/></svg></span>服务条款<span class="hchev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></span></a>
</div>
+35 -6
View File
@@ -10,6 +10,8 @@
<style>
body{margin:0; font-family:var(--font); color:var(--text); background:var(--page); -webkit-font-smoothing:antialiased;}
.qty-low{color:var(--danger); font-weight:700;}
/* KPI 即筛选选中态(镜像 MKpiItem selected 描边 primary,待提升至 mobile-atoms.css */
.m-kpi .k.sel{border-color:var(--primary);}
</style>
</head>
<body>
@@ -17,13 +19,19 @@
<div class="m-page has-tabs">
<div class="m-scroll">
<div class="m-kpi">
<div class="k"><div class="kl">SKU 总数</div><div class="kv">1,284</div><div class="kd up">▲ 2.3% 较上月</div></div>
<div class="k" onclick="clearAll()"><div class="kl">SKU 总数</div><div class="kv">1,284</div><div class="kd up">▲ 2.3% 较上月</div></div>
<!-- 库存货值=成本口径,仅管理员可见(非管理员显示「—」,2026-07-06);原型=管理员形态 -->
<div class="k"><div class="kl">库存货值</div><div class="kv">¥264万</div><div class="kd up">▲ 1.5% 较上月</div></div>
<div class="k"><div class="kl">在库数量</div><div class="kv">18,920</div><div class="kd">件 · 实时</div></div>
<div class="k"><div class="kl">已售</div><div class="kv">17</div><div class="kd warn">需补货 6 项</div></div>
<!-- 已售卡 = 点击筛选三态「已售」(sold_count2026-07-07),选中描边 primary -->
<div class="k" id="kSold" onclick="setSt('已售')"><div class="kl">已售</div><div class="kv">17</div><div class="kd warn">需补货 6 项</div></div>
</div>
<div style="margin-top:14px" class="m-search"><svg viewBox="0 0 24 24"><use href="#i-search"/></svg><input id="q" placeholder="商品名 / 拼音 / 编码" oninput="state.q=this.value;render()"></div>
<!-- 搜索行(镜像 MSearchRow):搜索框 + 详搜钮(开筛选 sheet;有生效条件转 primary -->
<div style="display:flex;gap:8px;align-items:stretch;margin-top:14px">
<div class="m-search" style="flex:1;min-width:0"><svg viewBox="0 0 24 24"><use href="#i-search"/></svg><input id="q" placeholder="商品名 / 拼音 / 编码" oninput="state.q=this.value;render()"></div>
<div class="btn ghost" id="advBtn" style="height:auto;flex:none;padding:0 12px" onclick="openFilterSheet()" title="详细搜索"><svg viewBox="0 0 24 24"><use href="#i-ic21"/></svg></div>
</div>
<div class="m-section" id="cnt">商品</div>
<div id="list"></div>
@@ -47,11 +55,15 @@ const ITEMS=[
{name:'郎酒 红花郎 15',code:'LJ-HHL15',spec:'500ml×6',series:'红花郎15',batch:'PZ240325',prod:'2024-03-25',qty:54,unit:'件',cost:'¥560',supplier:'四川郎酒',status:'在售'},
{name:'西凤酒 旗舰版',code:'XF-QJ-500',spec:'500ml×6',series:'旗舰版',batch:'PZ240614',prod:'2024-06-14',qty:88,unit:'件',cost:'¥280',supplier:'陕西西凤',status:'在售'},
];
const state={q:''};
const state={q:'',status:'全部',spec:new Set(),series:new Set()};
const MST=['全部','在售','库存','已售'];
function filtered(){ return ITEMS.filter(it=>{ if(state.q){const q=state.q.toLowerCase(); if(!it.name.toLowerCase().includes(q)&&!it.code.toLowerCase().includes(q))return false;} return true; }); }
function filtered(){ return ITEMS.filter(it=>{ if(state.status!=='全部'&&it.status!==state.status)return false; if(state.spec.size&&!state.spec.has(it.spec))return false; if(state.series.size&&!state.series.has(it.series))return false; if(state.q){const q=state.q.toLowerCase(); if(!it.name.toLowerCase().includes(q)&&!it.code.toLowerCase().includes(q))return false;} return true; }); }
function filterActive(){ return state.status!=='全部'||state.spec.size>0||state.series.size>0; }
function render(){
const rows=filtered();
const ab=document.getElementById('advBtn'); ab.classList.toggle('primary',filterActive()); ab.classList.toggle('ghost',!filterActive());
document.getElementById('kSold').classList.toggle('sel',state.status==='已售');
document.getElementById('cnt').textContent=`商品 · 共 ${rows.length}`;
const L=document.getElementById('list');
if(!rows.length){ L.innerHTML=`<div class="m-empty"><svg viewBox="0 0 24 24"><use href="#i-box"/></svg>没有匹配的商品 · 调整筛选或搜索</div>`; return; }
@@ -63,10 +75,27 @@ function render(){
</div>
</a>`).join('');
}
function setSt(s){ state.status=s; render(); }
function clearAll(){ state.q='';state.status='全部';state.spec.clear();state.series.clear(); document.getElementById('q').value=''; render(); }
// ---- 详搜筛选 sheet(镜像 Flutter _openMobileFilterSheet:状态单选 + 规格/系列多选 chip 组 + 重置/完成)----
function mfGroup(label,html){ return `<div class="m-field"><label>${label}</label><div style="display:flex;flex-wrap:wrap;gap:8px">${html}</div></div>`; }
function drawFilterSheet(){
const specOpts=[...new Set(ITEMS.map(it=>it.spec))], seriesOpts=[...new Set(ITEMS.map(it=>it.series))];
openSheet('筛选',
mfGroup('状态',MST.map(s=>`<span class="chip ${state.status===s?'on':''}" onclick="state.status='${s}';drawFilterSheet();render()">${s}</span>`).join(''))
+mfGroup('规格',specOpts.map(v=>`<span class="chip ${state.spec.has(v)?'on':''}" onclick="mfTgl('spec','${v}')">${v}</span>`).join(''))
+mfGroup('系列',seriesOpts.map(v=>`<span class="chip ${state.series.has(v)?'on':''}" onclick="mfTgl('series','${v}')">${v}</span>`).join(''))
+`<div style="display:flex;gap:10px;margin-top:16px"><div class="btn ghost" style="flex:1;justify-content:center" onclick="state.status='全部';state.spec.clear();state.series.clear();drawFilterSheet();render()">重置</div><div class="btn primary" style="flex:1;justify-content:center" onclick="closeSheet()">完成</div></div>`);
}
function mfTgl(k,v){ state[k].has(v)?state[k].delete(v):state[k].add(v); drawFilterSheet(); render(); }
function openFilterSheet(){ drawFilterSheet(); }
// 详情 sheet 行序对齐真实商品抽屉(编码/系列/规格/生产日期/当前库存/成本×2/状态);
// 成本两行仅管理员可见(2026-07-06),原型=管理员形态;生产日期行 2026-07-10。
function openItem(idx){ const it=ITEMS[idx];
if(!peEdit||peEdit.idx!==idx) peEdit={idx,imgs:[PE_SAMPLE[0],'',''],introName:'',introBody:''};
const hasIntro=!!peEdit.introName;
openSheet(it.name, `<div class="drow"><span>编码</span><b style="font-family:var(--font-mono)">${it.code}</b></div><div class="drow"><span>规格</span><b>${it.spec}</b></div><div class="drow"><span>系列</span><b>${it.series}</b></div><div class="drow"><span>批次号</span><b style="font-family:var(--font-mono)">${it.batch}</b></div><div class="drow"><span>生产日期</span><b style="font-family:var(--font-mono)">${it.prod}</b></div><div class="drow"><span>当前库存</span><b>${it.qty} ${it.unit}</b></div><div class="drow"><span>成本价</span><b>${it.cost}</b></div><div class="drow"><span>供应商</span><b>${it.supplier}</b></div><div class="drow" style="border-bottom:none"><span>状态</span><b style="display:flex;align-items:center;gap:10px"><span class="badge b-${it.status}">${it.status}</span>${it.status==='已售'?'':`<button class="switch ${it.status==='在售'?'on':''}" aria-label="挂售切换" onclick="this.classList.toggle('on');const b=this.previousElementSibling;const on=this.classList.contains('on');b.textContent=on?'在售':'库存';b.className='badge '+(on?'b-在售':'b-库存');"></button>`}</b></div>
const totalCost='¥'+Math.round(parseFloat(it.cost.replace(/[¥,]/g,''))*it.qty).toLocaleString();
openSheet(it.name, `<div class="drow"><span>编码</span><b style="font-family:var(--font-mono)">${it.code}</b></div><div class="drow"><span>系列</span><b>${it.series}</b></div><div class="drow"><span>规格</span><b>${it.spec}</b></div><div class="drow"><span>生产日期</span><b style="font-family:var(--font-mono)">${it.prod}</b></div><div class="drow"><span>当前库存</span><b>${it.qty} ${it.unit}</b></div><div class="drow"><span>成本价(单瓶)</span><b>${it.cost}</b></div><div class="drow"><span>总成本价</span><b>${totalCost}</b></div><div class="drow" style="border-bottom:none"><span>状态</span><b style="display:flex;align-items:center;gap:10px"><span class="badge b-${it.status}">${it.status}</span>${it.status==='已售'?'':`<button class="switch ${it.status==='在售'?'on':''}" aria-label="挂售切换" onclick="this.classList.toggle('on');const b=this.previousElementSibling;const on=this.classList.contains('on');b.textContent=on?'在售':'库存';b.className='badge '+(on?'b-在售':'b-库存');"></button>`}</b></div>
<div class="pe-card" style="margin-top:14px"><div class="pe-h"><span class="pe-ic"><svg viewBox="0 0 24 24"><use href="#i-ic27"/></svg></span>商品图片</div>
<div class="pe-b"><div class="pe-gallery" id="mGallery"></div><div class="pe-note">建议主图为白底产品图,可再加细节图(瓶身/防伪)与场景图。</div></div></div>
<div class="pe-card"><div class="pe-h"><span class="pe-ic"><svg viewBox="0 0 24 24"><use href="#i-ic28"/></svg></span>商品介绍 / 卖点</div>
+263 -51
View File
@@ -7,87 +7,299 @@
<link rel="stylesheet" href="../tokens.css">
<link rel="stylesheet" href="../atoms.css">
<link rel="stylesheet" href="../mobile-atoms.css">
<style>body{margin:0; font-family:var(--font); color:var(--text); background:var(--page); -webkit-font-smoothing:antialiased;}</style>
<style>
body{margin:0; font-family:var(--font); color:var(--text); background:var(--page); -webkit-font-smoothing:antialiased;}
/* 主 tab(授权信息 / 订单管理):.seg 原子,通栏均分 */
.lic-tabs{margin:2px 0 12px;}
.lic-tabs .seg{display:flex;} .lic-tabs .seg button{flex:1;}
.ltab{display:none;} .ltab.on{display:block;}
/* 移动端复用公共 LicensePanellicense_screen.dart → license_panel.dart),
卡片即 SettingsCard 镜像(surface / border / r-lg / pad 22 24)—— 待提升至 atoms.css */
.sc{background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); padding:22px 24px; margin-bottom:18px;}
.sc h2{margin:0 0 3px; font-size:var(--fs-h2); color:var(--heading);}
.sc .desc{margin:0 0 16px; font-size:var(--fs-sm); color:var(--muted);}
/* 授权信息 3 cell:窄屏同样横排、间距/内距收窄(02cffcb)—— 待提升至 atoms.css */
.lic-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:8px;}
.lic-cell{background:var(--bg); border:1px solid var(--border-subtle); border-radius:var(--r-md); padding:12px 10px; min-width:0;}
.lic-cell .lk{font-size:var(--fs-xs); color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
.lic-cell .lv{font-size:var(--fs-h2); font-weight:700; color:var(--heading); margin-top:6px; font-family:var(--font-mono); white-space:nowrap;}/* 真实端 FittedBox 按格宽缩放防溢出 */
.lic-cell .lv.ok{color:var(--success);}
/* 在线购买卡(对齐 purchase_card.dart 三阶段)—— 与桌面 license.html 同款,待提升至 atoms.css */
.buy .segs{display:flex; gap:10px; flex-wrap:wrap; margin-bottom:14px;}
.buy .promo-note{padding:12px; border:1px solid var(--warn); background:var(--warn-bg); border-radius:var(--r-md); margin-bottom:10px;}
.buy .promo-note .ph{font-size:var(--fs-body); font-weight:700; color:var(--warn);}
.buy .promo-note .pd{font-size:var(--fs-sm); color:var(--text); margin-top:3px;}
.buy .promo-note.grayed{border-color:var(--border); background:var(--bg);}
.buy .promo-note.grayed .ph, .buy .promo-note.grayed .pd{color:var(--faint);}
.buy .feats{padding:14px; background:var(--bg); border:1px solid var(--border); border-radius:var(--r-md);}
.buy .feats .fr{display:flex; align-items:center; gap:8px; padding:3px 0; font-size:var(--fs-sm); color:var(--text);}
.buy .feats .fr svg{width:14px; height:14px; stroke-width:2; color:var(--success); flex:none;}
.buy .feats.grayed .fr{color:var(--faint);} .buy .feats.grayed .fr svg{color:var(--faint);}
.buy .price-row{display:flex; align-items:flex-end; gap:8px; margin-top:14px;}
.buy .price-row .pl{flex:1; font-size:var(--fs-sm); color:var(--muted);}
.buy .price-row .strike{font-size:var(--fs-sm); color:var(--faint); text-decoration:line-through; font-family:var(--font-mono);}
.buy .price-row .pp{font-size:24px; font-weight:700; color:var(--heading); font-family:var(--font-mono); line-height:1;}
.buy .price-row .pp.promo{color:var(--warn);} .buy .price-row .pp.grayed{color:var(--faint);}
.buy .save{font-size:var(--fs-xs); color:var(--success); margin-top:6px;}
.buy .fine{font-size:var(--fs-xs); color:var(--faint); margin-top:6px;}
.buy .acts{display:flex; justify-content:flex-end; gap:10px; margin-top:12px;}
.buy .obox{padding:12px; background:var(--bg); border:1px solid var(--border-subtle); border-radius:var(--r-md); margin-top:14px;}
.buy .spin{width:18px; height:18px; border-radius:50%; border:2px solid var(--border); border-top-color:var(--primary); animation:buy-spin .8s linear infinite; flex:none;}
@keyframes buy-spin{to{transform:rotate(360deg)}}
.buy .okic{width:40px; height:40px; border-radius:var(--r-md); background:var(--ok-soft); color:var(--success); display:flex; align-items:center; justify-content:center; flex:none;}
.buy .okic svg{width:20px; height:20px; stroke-width:2;}
/* 兑换券行(label+input 占满 + 按钮尾随,同桌面 .redeem) */
.redeem{display:flex; gap:10px; align-items:flex-end;}
.redeem .rfield{flex:1; min-width:0;}
.redeem .rfield label{display:block; font-size:var(--fs-sm); color:var(--muted); margin-bottom:6px;}
/* 订单状态徽章(新词,与桌面 license.html 一致,待提升至 atoms.css */
.badge.b-待支付{background:var(--warn-bg); color:var(--warn);}
.badge.b-已支付{background:var(--success-bg); color:var(--success);}
.badge.b-已关闭{background:var(--info-soft); color:var(--muted);}
/* 设计期控件:标准 / iOS 合规 双态(仿 pay.html 三态做法,仅评审;仅授权信息 tab 显示) */
.ds-states{position:fixed; left:14px; bottom:22px; display:flex; gap:6px; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-pill); padding:5px; box-shadow:var(--sh-2); z-index:var(--z-fab);}
.ds-states button{border:none; background:none; font:inherit; font-size:var(--fs-xs); color:var(--muted); padding:5px 12px; border-radius:var(--r-pill); cursor:pointer;}
.ds-states button.on{background:var(--brand50); color:var(--primary); font-weight:600;}
</style>
</head>
<body>
<div class="m-top" data-m-top data-back data-title="授权管理"></div>
<div class="m-page">
<div class="m-scroll">
<!-- 对齐桌面 settings 授权管理四卡:授权信息 / 在线购买续费 / 兑换券 / 降级规则 -->
<div class="m-section">授权信息</div>
<div class="pe-card"><div class="pe-b" style="padding-top:2px;padding-bottom:2px">
<div class="drow"><span>授权状态</span><b><span class="badge b-已通过">已授权</span></b></div>
<div class="drow"><span>到期日</span><b style="font-family:var(--font-mono)">2027-03-15</b></div>
<div class="drow" style="border-bottom:none"><span>剩余天数</span><b>267 天</b></div>
<!-- 主 tab:授权信息 / 订单管理(与桌面 license.html 同构) -->
<div class="lic-tabs"><div class="seg">
<button class="on" id="tabBtnInfo" onclick="showTab('info')">授权信息</button>
<button id="tabBtnBuy" onclick="showTab('buy')">购买 / 续费</button>
<button id="tabBtnOrders" onclick="showTab('orders')">订单管理</button>
</div></div>
<div style="font-size:var(--fs-xs);color:var(--faint);margin:6px 2px 0">续期时长在现有到期时间上叠加,不会浪费剩余天数</div>
<div class="m-section">在线购买 / 续费</div>
<div class="pe-card" style="padding:14px" id="buyCard"></div>
<div class="m-section">兑换券</div>
<div class="pe-card" style="padding:14px">
<div class="m-field" style="margin-bottom:10px"><label>兑换券短码</label><input class="m-input" placeholder="如 8K3F-Q9ZT-27MB" style="font-family:var(--font-mono)"></div>
<div class="btn primary" style="justify-content:center" onclick="mToast('兑换成功,授权已续期 ✓')">兑换续期</div>
<!-- ========== Tab1 授权信息(license_panel.dart 四卡 + iOS 合规客服卡) ========== -->
<div class="ltab on" id="tab-info">
<!-- 卡1:授权信息(3 cell 横排,窄屏间距收窄,02cffcb) -->
<div class="sc">
<h2>授权信息</h2>
<p class="desc">当前门店的授权状态与有效期,续期时长在现有到期时间上叠加</p>
<div class="lic-grid">
<div class="lic-cell"><div class="lk">授权状态</div><div class="lv ok">已授权</div></div>
<div class="lic-cell"><div class="lk">到期日</div><div class="lv">2027-03-15</div></div>
<div class="lic-cell"><div class="lk">剩余天数</div><div class="lv">267 天</div></div>
</div>
</div>
<!-- 卡2:兑换券 -->
<div class="sc">
<h2>兑换券</h2>
<p class="desc">输入兑换券短码续期</p>
<div class="redeem">
<div class="rfield"><label>兑换券短码</label><input class="input" id="voucher" placeholder="输入兑换券短码"></div>
<div class="btn primary" onclick="redeem()">兑换续期</div>
</div>
</div>
<!-- 卡4:到期后的降级规则(license_copy.degradationNotes -->
<div class="sc">
<h2>到期后的降级规则</h2>
<p class="desc">授权到期后系统各功能的可用性说明</p>
<div style="font-size:var(--fs-sm);color:var(--muted)">
<div style="padding-top:3px">· 过期 7 天内(宽限期):仍可正常使用,请尽快续费</div>
<div style="padding-top:3px">· 过期 7–15 天(只读期):仅能查看数据,无法新增或修改</div>
<div style="padding-top:3px">· 过期满 15 天:系统锁定,无法登录,需续费或激活新授权码后恢复</div>
</div>
</div>
<!-- 卡5:联系客服(仅 iOS 合规形态,dddf402;措辞中性无购买引导) -->
<div class="sc" id="svcCard" hidden>
<h2>联系客服</h2>
<p class="desc">授权与使用问题可通过邮件联系我们</p>
<div style="display:flex;align-items:center">
<span style="font-size:var(--fs-sm);color:var(--muted)">客服邮箱 · </span>
<span style="font-size:var(--fs-sm);font-weight:600;color:var(--primary);font-family:var(--font-mono)" onclick="mToast('打开 mailto:support@51yanmei.com(原型)')">support@51yanmei.com</span>
</div>
</div>
</div>
<div class="m-section">到期后的降级规则</div>
<div class="pe-card"><div class="pe-b" style="padding-top:2px;padding-bottom:2px">
<div class="drow"><span>查看数据</span><b style="color:var(--success)">保留,可继续查询导出</b></div>
<div class="drow"><span>入库 / 出库 / 审核</span><b style="color:var(--warn)">停用,续期后恢复</b></div>
<div class="drow"><span>新增用户 / 设备</span><b style="color:var(--warn)">停用</b></div>
<div class="drow" style="border-bottom:none"><span>数据安全</span><b style="color:var(--success)">云端保留 180 天</b></div>
</div></div>
<!-- ========== Tab2 在线购买 / 续费(purchase_card.dartadmin 形态;iOS 合规态整 tab 隐藏) ========== -->
<div class="ltab" id="tab-buy">
<div class="sc">
<h2>在线购买 / 续费</h2>
<p class="desc">选择套餐支付宝支付,到账后授权自动续期</p>
<div class="buy" id="buyCard"></div>
</div>
</div>
<!-- ========== Tab3 订单管理(license_purchases 流水,与桌面 license.html 同构) ========== -->
<div class="ltab" id="tab-orders">
<div class="m-kpi" style="margin-bottom:10px">
<div class="k"><div class="kl"><svg viewBox="0 0 24 24"><use href="#i-ic24"/></svg>当前授权 · 高级版</div><div class="kv" style="font-size:var(--fs-title)">2027-06-04</div><div class="kd up">剩余 329 天</div></div>
<div class="k"><div class="kl"><svg viewBox="0 0 24 24"><use href="#i-ic47"/></svg>累计购买金额</div><div class="kv">¥4,197</div><div class="kd">已支付 5 笔</div></div>
<div class="k" onclick="setSt('待支付')"><div class="kl"><svg viewBox="0 0 24 24"><use href="#i-ic04"/></svg>待支付 · 点击筛选</div><div class="kv">1</div><div class="kd warn">2 小时后自动关闭</div></div>
<div class="k" onclick="setSt('全部')"><div class="kl"><svg viewBox="0 0 24 24"><use href="#i-ic53"/></svg>订单总数</div><div class="kv">7</div><div class="kd">自 2026-02 开通</div></div>
</div>
<div style="display:flex;gap:8px;align-items:stretch;margin:10px 0">
<div class="m-search" style="flex:1;min-width:0"><svg viewBox="0 0 24 24"><use href="#i-search"/></svg><input id="q" placeholder="搜索订单号 / 套餐" oninput="state.q=this.value;render()"></div>
<div class="btn ghost" id="stSel" style="height:auto;flex:none" onclick="openStatusSheet()"><span id="stVal">全部</span></div>
<div class="btn ghost" id="planSel" style="height:auto;flex:none" onclick="openPlanSheet()"><span id="planVal">套餐</span></div>
</div>
<div class="m-section" id="cnt">授权订单</div>
<div id="list"></div>
</div>
</div>
</div>
<!-- 设计期控件:iOS App Store 合规形态切换(store_compliance.hideExternalPurchaseUi -->
<div class="ds-states" id="licStates">
<button class="on" onclick="setLicVariant(false)">标准</button>
<button onclick="setLicVariant(true)">iOS 合规</button>
</div>
<div class="toast" id="toast"></div>
<script src="icons.js"></script>
<script src="mobile-shell.js"></script>
<script>
/* ==================== 在线购买卡(对齐 purchase_card.dart 三阶段) ==================== */
/* ==================== 主 tab 切换 ==================== */
const TAB_BTN={info:'tabBtnInfo',buy:'tabBtnBuy',orders:'tabBtnOrders'};
function showTab(t){
for(const k of ['info','buy','orders']){
const on = k===t;
document.getElementById('tab-'+k).classList.toggle('on',on);
document.getElementById(TAB_BTN[k]).classList.toggle('on',on);
}
document.getElementById('licStates').hidden = t==='orders'; // iOS 双态开关在授权/购买两 tab 生效
if(t==='orders') render();
}
/* ==================== Tab1 在线购买卡(对齐 purchase_card.dart / license_plans.dart 三阶段) ==================== */
const PLANS={
promo:{name:'首月特惠',price:1,days:'30 天标准版全部功能',note:'新店专享 · 每店限购一次'},
standard:{name:'标准版',monthly:299,annual:2999},
pro:{name:'高级版',monthly:599,annual:5999},
promo:{price:1,days:30,orig:299,feats:['标准版全部功能,一分不少','单门店 · 单仓库 · 2 台客户端','1,000 张商品图片分享','每个门店限购一次']},
standard:{name:'标准版',monthly:299,annual:2999,feats:['单门店 · 单仓库','2 台客户端同时使用','1,000 张商品图片分享']},
pro:{name:'高级版',monthly:599,annual:5999,feats:['单门店 · 多仓库','5 台客户端同时使用','10,000 张商品图片分享','免费 AI 周度 / 月度商业数据分析']},
};
const buy={tab:'promo',cycle:'annual',phase:'pick',promoUsed:false};
function fmt(n){ return n.toLocaleString(); }
function segHtml(items,cur,fn){ return `<div class="seg">${items.map(([k,label])=>`<button class="${cur===k?'on':''}" onclick="${fn}('${k}')">${label}</button>`).join('')}</div>`; }
function buyTab(k){ if(k==='promo'&&buy.promoUsed){ mToast('本店已享受过首月特惠'); } buy.tab=k; drawBuy(); }
function buyCycle(k){ buy.cycle=k; drawBuy(); }
function buyPay(){ if(buy.tab==='promo'&&buy.promoUsed) return; buy.phase='waiting'; drawBuy(); }
/* 支付成功(86611dc):本单为特惠则置灰「已享受」,选择页默认落 高级版·年付 */
function buyDone(){ if(buy.tab==='promo') buy.promoUsed=true; buy.tab='pro'; buy.cycle='annual'; buy.phase='success'; drawBuy(); }
function buyBack(){ buy.phase='pick'; drawBuy(); }
function drawBuy(){
const el=document.getElementById('buyCard');
if(buy.phase==='waiting'){
el.innerHTML=`<div class="notice info" style="margin-bottom:14px"><svg viewBox="0 0 24 24"><use href="#i-info"/></svg><div>已拉起支付宝收银台,请在支付宝内完成付款。支付成功后自动到账,本页每 3 秒自动查询。</div></div>
<div style="text-align:center;color:var(--muted);font-size:var(--fs-sm);padding:8px 0 14px">等待支付结果…</div>
<div style="display:flex;gap:10px"><div class="btn ghost" style="flex:1;justify-content:center" onclick="buy.phase='pick';drawBuy()">返回</div><div class="btn primary" style="flex:2;justify-content:center" onclick="buy.phase='success';drawBuy()">我已完成支付</div></div>`;
const p=buy.tab==='promo'?PLANS.promo.price:(buy.cycle==='annual'?PLANS[buy.tab].annual:PLANS[buy.tab].monthly);
el.innerHTML=`<div style="display:flex;align-items:center;gap:12px"><div class="spin"></div><div style="flex:1;font-size:var(--fs-sm);color:var(--text)">已在浏览器打开支付宝收银台,完成支付后本卡片会自动确认到账。</div></div>
<div class="obox"><div style="font-size:var(--fs-xs);color:var(--muted)">订单号</div><div style="font-size:var(--fs-sm);font-family:var(--font-mono);margin-top:4px">JIU20260710160001</div><div style="font-size:var(--fs-sm);color:var(--text);margin-top:8px">金额 ¥${fmt(p)}</div></div>
<div class="fine" style="margin-top:10px">离开本页不影响到账:支付成功后授权自动续期,可稍后回本页刷新查看。</div>
<div class="acts"><div class="btn ghost" onclick="buyBack()">重新选择</div><div class="btn primary" onclick="buyDone()">我已完成支付</div></div>`;
return;
}
if(buy.phase==='success'){
el.innerHTML=`<div style="text-align:center;padding:8px 0 4px"><span class="badge bi b-已通过" style="height:28px;padding:0 14px"><svg class="ic"><use href="#i-check"/></svg>支付成功,授权已续期</span></div>
<div class="drow"><span>新到期日</span><b style="font-family:var(--font-mono)">2028-03-15</b></div>
<div class="drow" style="border-bottom:none"><span>剩余天数</span><b>632 天</b></div>
<div style="display:flex;gap:10px;margin-top:12px"><div class="btn ghost" style="flex:1;justify-content:center" onclick="buy.phase='pick';drawBuy()">继续购买</div></div>`;
el.innerHTML=`<div style="display:flex;align-items:center;gap:12px"><div class="okic"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg></div><div style="flex:1;font-size:var(--fs-title);font-weight:600;color:var(--heading)">门店授权已续期至 2028-03-15。</div></div>
<div style="font-size:var(--fs-xs);color:var(--muted);margin-top:10px">订单号 JIU20260710160001</div>
<div class="acts"><div class="btn ghost" onclick="buyBack()">完成</div></div>`;
return;
}
const tabs=[['promo',buy.promoUsed?'首月特惠(已享受)':'🔥 首月特惠 ¥1'],['standard','标准版'],['pro','高级版']];
let html=`<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px">${tabs.map(([k,label])=>`<div class="m-pill ${buy.tab===k?'on':''}" ${k==='promo'&&buy.promoUsed?'style="opacity:.45"':''} onclick="${k==='promo'&&buy.promoUsed?"mToast('本店已享受过首月特惠')":`buy.tab='${k}';drawBuy()`}">${label}</div>`).join('')}</div>`;
if(buy.tab==='promo'){
html+=`<div style="display:flex;align-items:baseline;gap:8px"><span style="font-size:var(--fs-display);font-weight:800;color:var(--heading);font-family:var(--font-mono)">¥1</span><span style="color:var(--muted);font-size:var(--fs-sm)">/ 首月</span></div>
<div style="font-size:var(--fs-sm);color:var(--muted);margin-top:6px">${PLANS.promo.days} · ${PLANS.promo.note}</div>`;
} else {
const p=PLANS[buy.tab];
html+=`<div style="display:flex;gap:8px;margin-bottom:10px"><div class="m-pill ${buy.cycle==='annual'?'on':''}" onclick="buy.cycle='annual';drawBuy()">年付 · 省 2 个月</div><div class="m-pill ${buy.cycle==='monthly'?'on':''}" onclick="buy.cycle='monthly';drawBuy()">月付</div></div>`;
if(buy.cycle==='annual'){
const save=p.monthly*12-p.annual;
html+=`<div style="display:flex;align-items:baseline;gap:8px"><span style="font-size:var(--fs-display);font-weight:800;color:var(--heading);font-family:var(--font-mono)">¥${fmt(p.annual)}</span><span style="color:var(--muted);font-size:var(--fs-sm)">/ 年</span><span style="color:var(--faint);font-size:var(--fs-sm);text-decoration:line-through;font-family:var(--font-mono)">¥${fmt(p.monthly*12)}</span></div>
<div style="font-size:var(--fs-sm);color:var(--success);margin-top:6px">月付 ¥${fmt(p.monthly)} × 12 = ¥${fmt(p.monthly*12)},年付立省 ¥${fmt(save)},相当于免费用 2 个月</div>`;
} else {
html+=`<div style="display:flex;align-items:baseline;gap:8px"><span style="font-size:var(--fs-display);font-weight:800;color:var(--heading);font-family:var(--font-mono)">¥${fmt(p.monthly)}</span><span style="color:var(--muted);font-size:var(--fs-sm)">/ 月</span></div>`;
}
// pick:套餐 seg + (非特惠)周期 seg + 特惠说明框 + 权益清单 + 价格行 + 细则 + 提交
const isPromo=buy.tab==='promo', grayed=isPromo&&buy.promoUsed, g=PLANS[buy.tab];
let h=`<div class="segs">${segHtml([['promo',buy.promoUsed?'首月特惠(已享受)':'🔥 首月特惠 ¥1'],['standard','标准版'],['pro','高级版']],buy.tab,'buyTab')}`;
if(!isPromo) h+=segHtml([['annual','年付 · 省 2 个月'],['monthly','月付']],buy.cycle,'buyCycle');
h+=`</div>`;
if(isPromo){
h+=`<div class="promo-note ${grayed?'grayed':''}"><div class="ph">${grayed?'本店已享受过首月特惠':'新店专享 · 仅 ¥1 体验 30 天'}</div><div class="pd">${grayed?'首月特惠每个门店限购一次,可选择标准版或高级版继续续费。':'解锁标准版全部功能,原价 ¥299,每个门店限购一次——试过才知道多省心。'}</div></div>`;
}
html+=`<div class="btn primary" style="justify-content:center;margin-top:14px" onclick="buy.phase='waiting';drawBuy()"><svg viewBox="0 0 24 24"><use href="#i-ic08"/></svg>支付宝支付</div>
<div style="font-size:var(--fs-xs);color:var(--faint);margin-top:8px;text-align:center">手机端直接拉起支付宝 App · 到账即时续期</div>`;
el.innerHTML=html;
const feats=isPromo?PLANS.promo.feats:g.feats;
h+=`<div class="feats ${grayed?'grayed':''}">${feats.map(f=>`<div class="fr"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg>${f}</div>`).join('')}</div>`;
const annual=buy.cycle==='annual', price=isPromo?PLANS.promo.price:(annual?g.annual:g.monthly);
const label=isPromo?`首月特惠 · ${PLANS.promo.days} 天授权`:`${g.name} · ${annual?'年付':'月付'} · ${annual?365:30} 天授权`;
h+=`<div class="price-row"><span class="pl">${label}</span>`;
if(isPromo) h+=`<span class="strike">¥${fmt(PLANS.promo.orig)}</span>`;
if(!isPromo&&annual) h+=`<span class="strike">¥${fmt(g.monthly*12)}</span>`;
h+=`<span class="pp ${grayed?'grayed':isPromo?'promo':''}">¥${fmt(price)}</span></div>`;
if(!isPromo&&annual) h+=`<div class="save">月付 ¥${fmt(g.monthly)} × 12 个月 = ¥${fmt(g.monthly*12)},年付立省 ¥${fmt(g.monthly*12-g.annual)},相当于免费用 2 个月</div>`;
h+=`<div class="fine">到期时间自动叠加,未到期续费不吃亏 · 支付宝支付</div>
<div class="acts"><div class="btn primary" style="${grayed?'opacity:.45;cursor:default':''}" onclick="buyPay()">${grayed?'本店已享受过':`提交订单 · ¥${fmt(price)}`}</div></div>`;
el.innerHTML=h;
}
drawBuy();
/* 兑换券 */
function redeem(){ const v=document.getElementById('voucher').value.trim(); if(!v){ mToast('请输入兑换券短码'); return;} document.getElementById('voucher').value=''; mToast('兑换成功,已续期 ✓'); }
/* iOS App Store 合规双态(dddf402):隐藏「购买/续费」整个 tab,授权信息 tab 补「联系客服」中性卡 */
function setLicVariant(ios){
document.getElementById('tabBtnBuy').style.display = ios?'none':'';
document.getElementById('svcCard').hidden=!ios;
document.querySelectorAll('#licStates button').forEach((b,i)=>b.classList.toggle('on',(i===1)===ios));
if(ios && document.getElementById('tab-buy').classList.contains('on')) showTab('info');
}
/* ==================== Tab2 订单管理(license_purchases 流水) ====================
状态三态与后端对齐:待支付 pending / 已支付 paid / 已关闭 failed(取消·超时·失败归并)。 */
const ORDERS=[
{no:'pay-20260710101433-9c41f7ae',plan:'高级版 · 年付',days:365,amount:'¥5,999',status:'待支付',by:'王老板',created:'2026-07-10 10:14',paidAt:'',channel:'',renewTo:''},
{no:'pay-20260702174902-b2e85c10',plan:'高级版 · 年付',days:365,amount:'¥5,999',status:'已关闭',by:'王老板',created:'2026-07-02 17:49',paidAt:'',channel:'',renewTo:'',closeReason:'超时未支付,订单自动关闭'},
{no:'pay-20260610093015-4d7a20cf',plan:'高级版 · 月付',days:30,amount:'¥599',status:'已支付',by:'王老板',created:'2026-06-10 09:30',paidAt:'2026-06-10 09:31',channel:'支付宝',renewTo:'2027-06-04'},
{no:'pay-20260503153847-71c9d3e2',plan:'标准版 · 年付',days:365,amount:'¥2,999',status:'已支付',by:'王老板',created:'2026-05-03 15:38',paidAt:'2026-05-03 15:40',channel:'支付宝',renewTo:'2027-05-05'},
{no:'pay-20260405112230-e8f04a6b',plan:'标准版 · 月付',days:30,amount:'¥299',status:'已支付',by:'张店长',created:'2026-04-05 11:22',paidAt:'2026-04-05 11:22',channel:'支付宝',renewTo:'2026-05-05'},
{no:'pay-20260306084514-2a6bd591',plan:'标准版 · 月付',days:30,amount:'¥299',status:'已支付',by:'王老板',created:'2026-03-06 08:45',paidAt:'2026-03-06 08:46',channel:'支付宝',renewTo:'2026-04-05'},
{no:'pay-20260204193302-c5d8e947',plan:'首月特惠 · 30 天',days:30,amount:'¥1',status:'已支付',by:'王老板',created:'2026-02-04 19:33',paidAt:'2026-02-04 19:33',channel:'支付宝',renewTo:'2026-03-06'},
];
const ST=['全部','待支付','已支付','已关闭'];
const PLAN_OPTS=['全部','首月特惠 · 30 天','标准版 · 月付','标准版 · 年付','高级版 · 月付','高级版 · 年付'];
const state={q:'',status:'全部',plan:'全部'};
/* 状态代表图标(移动纯图标 .ico / 图标+文字 .bi,同出库列表口径) */
const STICON={'待支付':'i-ic04','已支付':'i-check','已关闭':'i-close'};
function icoB(v){ return `<span class="badge ico b-${v}" title="${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg></span>`; }
function biB(v){ return `<span class="badge bi b-${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg>${v}</span>`; }
function filtered(){ return ORDERS.filter(o=>{
if(state.status!=='全部'&&o.status!==state.status)return false;
if(state.plan!=='全部'&&o.plan!==state.plan)return false;
if(state.q){const q=state.q.toLowerCase(); if(!o.no.toLowerCase().includes(q)&&!o.plan.toLowerCase().includes(q))return false;}
return true; }); }
function render(){
document.getElementById('stVal').textContent=state.status;
const ss=document.getElementById('stSel');
ss.classList.toggle('primary',state.status!=='全部'); ss.classList.toggle('ghost',state.status==='全部');
document.getElementById('planVal').textContent=state.plan==='全部'?'套餐':state.plan;
const ps=document.getElementById('planSel');
ps.classList.toggle('primary',state.plan!=='全部'); ps.classList.toggle('ghost',state.plan==='全部');
const rows=filtered();
document.getElementById('cnt').textContent=`授权订单 · 共 ${rows.length}`;
const L=document.getElementById('list');
if(!rows.length){ L.innerHTML=`<div class="m-empty"><svg viewBox="0 0 24 24"><use href="#i-box"/></svg>没有匹配的授权订单</div>`; return; }
L.innerHTML=rows.map(o=>`<a class="m-card" onclick="openOrder('${o.no}')">
<div class="m-row">
<div class="mc-main"><div class="mc-nm">${o.plan}</div><div class="mc-sub" style="font-family:var(--font-mono)">${o.no}</div></div>
<div class="mc-right"><div style="display:flex;gap:5px">${icoB(o.status)}</div><span class="mc-amt">${o.amount}</span></div>
<div class="mc-chev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></div>
</div>
<div class="mc-foot"><svg width="13" height="13" viewBox="0 0 24 24" stroke-width="1.9" style="color:var(--muted)"><use href="#i-info"/></svg>${o.created} · ${o.by}${o.channel?' · '+o.channel:''}</div>
</a>`).join('');
}
function setSt(s){ state.status=s; render(); }
function openStatusSheet(){
openSheet('状态筛选', ST.map(s=>`<div class="m-opt ${s===state.status?'sel':''}" onclick="setSt('${s}');closeSheet()">${s!=='全部'?`<span class="badge ico b-${s}"><svg class="ic"><use href="#${STICON[s]}"/></svg></span>`:''}${s}<svg class="ck" viewBox="0 0 24 24"><use href="#i-check"/></svg></div>`).join(''));
}
function openPlanSheet(){
openSheet('套餐筛选', PLAN_OPTS.map(p=>`<div class="m-opt ${p===state.plan?'sel':''}" onclick="state.plan='${p}';render();closeSheet()">${p}<svg class="ck" viewBox="0 0 24 24"><use href="#i-check"/></svg></div>`).join(''));
}
/* 订单详情 sheet */
function actions(o){
if(o.status==='待支付') return `<div class="btn danger" style="flex:1;justify-content:center" onclick="mToast('订单已取消');closeSheet()">取消订单</div><div class="btn primary" style="flex:2;justify-content:center" onclick="mToast('已打开支付宝收银台(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic08"/></svg>继续支付</div>`;
if(o.status==='已关闭') return `<div class="btn ghost" style="flex:1;justify-content:center" onclick="closeSheet()">关闭</div><div class="btn primary" style="flex:2;justify-content:center" onclick="closeSheet();showTab('buy')"><svg viewBox="0 0 24 24"><use href="#i-plus"/></svg>重新购买</div>`;
return `<div class="btn ghost" style="flex:1;justify-content:center" onclick="closeSheet();showTab('info')">查看授权</div><div class="btn ghost" style="flex:1;justify-content:center" onclick="closeSheet()">关闭</div>`;
}
function openOrder(no){ const o=ORDERS.find(x=>x.no===no);
const renewRow=o.status==='已支付'?`<div class="drow"><span>授权续期至</span><b style="font-family:var(--font-mono);color:var(--success)">${o.renewTo}</b></div>`:'';
openSheet(o.plan, `<div class="drow"><span>订单号</span><b style="font-family:var(--font-mono);font-size:var(--fs-xs)">${o.no}</b></div>
<div class="drow"><span>授权时长</span><b>${o.days} 天</b></div>
<div class="drow"><span>金额</span><b style="font-family:var(--font-mono)">${o.amount}</b></div>
<div class="drow"><span>支付渠道</span><b>${o.channel||'—'}</b></div>
<div class="drow"><span>下单人</span><b>${o.by}</b></div>
<div class="drow"><span>下单时间</span><b style="font-family:var(--font-mono);font-size:var(--fs-sm)">${o.created}</b></div>
<div class="drow"><span>支付时间</span><b style="font-family:var(--font-mono);font-size:var(--fs-sm)">${o.paidAt||'—'}</b></div>
${renewRow}
<div class="drow" style="border-bottom:none"><span>状态</span><b>${biB(o.status)}</b></div>
${o.status==='待支付'?'<div style="margin-top:12px;font-size:var(--fs-sm);line-height:1.6;color:var(--warn)">订单未支付将在下单 2 小时后自动关闭;支付成功后授权时长自动叠加到当前到期日。</div>':''}
${o.status==='已关闭'?`<div style="margin-top:12px;font-size:var(--fs-sm);line-height:1.6;color:var(--muted)">${o.closeReason||'订单已关闭'}。如需购买请重新下单。</div>`:''}
<div style="display:flex;gap:10px;margin-top:16px">${actions(o)}</div>`);
}
</script>
</body>
</html>
+43
View File
@@ -17,6 +17,18 @@
.lg-sub{font-size:var(--fs-sm); color:var(--muted); margin-top:6px;}
.lg-foot{margin-top:auto; text-align:center; font-size:var(--fs-sm); color:var(--muted); padding-top:24px;}
.lg-foot a{color:var(--primary); text-decoration:none; font-weight:600;}
/* 首启隐私同意卡(镜像 privacy_consent_gate.dart320 宽 surface 卡,遮罩用 .overlay/--scrim
真实端为 Colors.black54)—— 待提升至 atoms.css */
.pc-card{width:320px; margin:0 24px; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); padding:22px 22px 18px; box-sizing:border-box;}
.pc-h{text-align:center; font-size:var(--fs-h2); font-weight:700; color:var(--heading);}
.pc-b{font-size:var(--fs-sm); color:var(--text); line-height:1.7; margin-top:12px;}
.pc-b a{color:var(--primary); font-weight:600; cursor:pointer;}
.pc-hint{font-size:var(--fs-xs); color:var(--warn); margin-top:10px; text-align:center;}
.pc-acts{display:flex; gap:10px; margin-top:16px;}
/* 设计期控件:常规 / 首启同意 双态(仿 pay.html 三态做法,仅评审) */
.ds-states{position:fixed; left:14px; bottom:22px; display:flex; gap:6px; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-pill); padding:5px; box-shadow:var(--sh-2); z-index:var(--z-fab);}
.ds-states button{border:none; background:none; font:inherit; font-size:var(--fs-xs); color:var(--muted); padding:5px 12px; border-radius:var(--r-pill); cursor:pointer;}
.ds-states button.on{background:var(--brand50); color:var(--primary); font-weight:600;}
</style>
</head>
<body>
@@ -33,8 +45,39 @@
<div class="btn primary" style="width:100%; justify-content:center; height:46px; margin-top:6px" onclick="mToast('登录中…');setTimeout(()=>location.href='m-inventory.html',700)">登录</div>
<div class="lg-foot">还没有门店账号?<a href="m-register.html">注册新门店</a></div>
</div>
<!-- 首启隐私同意闸(privacy_consent_gate.dartb740cc6):移动端(iOS/Android)首次启动
全屏遮罩弹出,覆盖全部路由;同意持久化后不再出现;不同意 Android 退出应用、
iOS 留在弹层并提示(原型演示 iOS 行为)。桌面与 Web 不弹 -->
<div class="overlay" id="ovConsent">
<div class="pc-card">
<div class="pc-h">个人信息保护提示</div>
<div class="pc-b">感谢使用岩美酒库。为向您提供门店库存管理服务,我们仅收集必要的账号信息、设备信息与操作日志,用于多端数据同步与账号安全保障。详情请阅读<a onclick="mToast('打开 隐私政策 /privacy/(原型)')">《隐私政策》</a><a onclick="mToast('打开 用户协议 /terms/(原型)')">《用户协议》</a>。点击「同意并继续」即表示您已阅读并同意上述条款。</div>
<div class="pc-hint" id="pcHint" hidden>需同意后方可使用岩美酒库</div>
<div class="pc-acts">
<div class="btn ghost" style="flex:1;justify-content:center" onclick="pcDecline()">不同意</div>
<div class="btn primary" style="flex:1;justify-content:center" onclick="pcAgree()">同意并继续</div>
</div>
</div>
</div>
<!-- 设计期控件:首启同意弹层演示开关 -->
<div class="ds-states">
<button class="on" onclick="setConsent(false)">常规</button>
<button onclick="setConsent(true)">首启同意</button>
</div>
<div class="toast" id="toast"></div>
<script src="icons.js"></script>
<script src="mobile-shell.js"></script>
<script>
function setConsent(on){
document.getElementById('ovConsent').classList.toggle('show',on);
document.getElementById('pcHint').hidden=true;
document.querySelectorAll('.ds-states button').forEach((b,i)=>b.classList.toggle('on',(i===1)===on));
}
function pcAgree(){ mToast('已同意,持久化后不再弹出'); setConsent(false); }
/* iOS 形态:不可程序性退出,留提示;Android 真实端为退出应用 */
function pcDecline(){ document.getElementById('pcHint').hidden=false; }
</script>
</body>
</html>
+30
View File
@@ -14,6 +14,10 @@
.me-hd .nm{font-size:var(--fs-h2); font-weight:700; color:var(--heading);}
.me-hd .sub{font-size:var(--fs-sm); color:var(--muted); margin-top:3px;}
.me-hd .role{margin-left:auto;}
/* 设计期控件:标准 / iOS 合规 双态(仿 pay.html 三态做法,仅评审;避开底部 tabbar) */
.ds-states{position:fixed; left:14px; bottom:74px; display:flex; gap:6px; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-pill); padding:5px; box-shadow:var(--sh-2); z-index:var(--z-fab);}
.ds-states button{border:none; background:none; font:inherit; font-size:var(--fs-xs); color:var(--muted); padding:5px 12px; border-radius:var(--r-pill); cursor:pointer;}
.ds-states button.on{background:var(--brand50); color:var(--primary); font-weight:600;}
</style>
</head>
<body>
@@ -41,15 +45,41 @@
<a href="m-settings.html"><span class="hic"><svg viewBox="0 0 24 24"><use href="#i-gear"/></svg></span>系统设置<span class="hchev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></span></a>
<a onclick="openThemeSheet()"><span class="hic"><svg viewBox="0 0 24 24"><use href="#i-shirt"/></svg></span>主题外观<span class="hchev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></span></a>
<a href="m-about.html"><span class="hic"><svg viewBox="0 0 24 24"><use href="#i-ic24"/></svg></span>关于我们<span class="hchev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></span></a>
<!-- 注销账号(b740cc6,仅 iOS App Store 形态;真实端图标 user-xi-user-x 待登记 icons.js 后替换) -->
<a id="delAccount" hidden onclick="openDelSheet()"><span class="hic"><svg viewBox="0 0 24 24"><use href="#i-ic37"/></svg></span>注销账号<span class="hchev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></span></a>
</div>
<div style="margin-top:18px"><div class="btn ghost" style="width:100%; justify-content:center; color:var(--danger)" onclick="location.href='m-login.html'">退出登录</div></div>
</div>
</div>
<nav class="m-tabbar" data-m-tabs data-active="me"></nav>
<!-- 设计期控件:iOS App Store 合规形态切换(store_compliance.isIosAppStoreBuild -->
<div class="ds-states">
<button class="on" onclick="setMeVariant(false)">标准</button>
<button onclick="setMeVariant(true)">iOS 合规</button>
</div>
<div class="toast" id="toast"></div>
<script src="icons.js"></script>
<script src="mobile-shell.js"></script>
<script>
/* iOS 合规双态:显示「注销账号」入口(审核 5.1.1(v) 应用内可发起账号删除) */
function setMeVariant(ios){
document.getElementById('delAccount').hidden=!ios;
document.querySelectorAll('.ds-states button').forEach((b,i)=>b.classList.toggle('on',(i===1)===ios));
}
/* 注销账号 sheetme_screen._showDeleteAccountSheet:说明 + 预填 mailto 客服邮箱) */
function openDelSheet(){
openSheet('注销账号',`
<div style="font-size:var(--fs-body);font-weight:600;color:var(--heading)">注销将删除您的登录账号,操作不可恢复。</div>
<div style="font-size:var(--fs-sm);color:var(--muted);line-height:1.6">
<div style="margin-top:4px">· 账号与登录凭据将被删除,无法找回</div>
<div style="margin-top:4px">· 门店经营数据归属门店:门店内仍有其他用户时数据保留,需整店删除请在申请中说明</div>
<div style="margin-top:4px">· 为防止误操作与冒用,注销需人工核验身份,我们将在收到申请后 15 个工作日内完成处理</div>
</div>
<div class="btn primary" style="width:100%;box-sizing:border-box;justify-content:center;margin-top:16px" onclick="mToast('打开 mailto:support@51yanmei.com(预填注销申请,原型)');closeSheet()">发送注销申请邮件</div>
<div style="text-align:center;font-size:var(--fs-xs);color:var(--faint);margin-top:8px">客服邮箱 support@51yanmei.com</div>`);
}
</script>
</body>
</html>
+1 -1
View File
@@ -44,7 +44,7 @@ function openShopSheet(){
openSheet('门店信息', `<div class="m-field"><label>门店名称</label><input class="m-input" value="山城酒业连锁 · 解放碑店"></div>
<div class="m-field"><label>门店编号(不可改)</label><input class="m-input" value="S001" disabled style="color:var(--faint)"></div>
<div class="m-field"><label>联系电话</label><input class="m-input" value="023-6337-8899"></div>
<div class="m-field"><label>微信</label><input class="m-input" value="yammy2023"></div>
<div class="m-field"><label>微信</label><input class="m-input" value="dingsheng-wine"></div>
<div class="m-field"><label>门店地址</label><input class="m-input" value="重庆市渝中区民族路 188 号"></div>
<div style="display:flex;gap:10px;margin-top:16px"><div class="btn ghost" style="flex:1;justify-content:center" onclick="closeSheet()">取消</div><div class="btn primary" style="flex:2;justify-content:center" onclick="mToast('门店信息已保存 ✓');closeSheet()">保存</div></div>`);
}
@@ -40,6 +40,7 @@
<script src="icons.js"></script>
<script src="mobile-shell.js"></script>
<script src="datewheel.js"></script>
<script>
const ORDERS=[
{no:'RK-20260620-009',party:'鼎晟调货',wh:'主仓',items:2,amount:'¥5,360',status:'已审核',ret:'部分退单',date:'2026-06-20',by:'王经理',auditor:'张主管',goods:'茅台 飞天 53°、五粮液 普五 52°',pending:1,lines:[{n:'茅台 飞天 53°',s:'500ml×6',q:1,a:'待定价',pend:true},{n:'五粮液 普五 52°',s:'500ml×6',q:1,a:'¥2,680'}]},
@@ -54,7 +55,6 @@ const ORDERS=[
const ST=['全部','草稿','待审核','已审核','已拒绝','部分退单','已退单'];
const OPS=['全部','王经理','李采购'];
const REVIEWERS=['全部','张主管','王经理'];
const DATES=['全部时间','近 7 天','近 30 天','本月'];
const state={q:'',status:'全部'};
/* ---- 状态代表图标(移动纯图标 .ico / 图标+文字 .bi;状态筛选钮为纯文字——用户拍板) ---- */
const STICON={'草稿':'i-ic06','待审核':'i-ic04','已审核':'i-check','已拒绝':'i-close','部分退单':'i-undo','已退单':'i-undo','待定价':'i-yen'};
@@ -112,6 +112,9 @@ function advSelect(key,label,opts){
}
function advToggle(k){ advOpen=advOpen===k?'':k; drawAdv(); }
function advPick(k,v){ adv[k]=v; advOpen=''; drawAdv(); }
/* 单据日期:范围双滚轮底部 sheetdatewheel.js mountRange 纵排,对齐真实端窄屏 showDateRangeDropdown → m-sheet */
function advDateField(){ const v=adv.date!=='全部时间'?adv.date:''; return `<div class="m-field"><label>单据日期</label><div class="m-select" onclick="openAdvDateSheet()"><span class="${v?'':'ph'}">${v||'全部时间'}</span><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg></div></div>`; }
function openAdvDateSheet(){ openSheet('日期区间','<div id="advRangeMount"></div>'); const p=adv.date!=='全部时间'?adv.date.split(' ~ '):['','']; DateWheel.mountRange(document.getElementById('advRangeMount'),{from:p[0],to:p[1],onCommit:(f,t)=>{ adv.date=f+' ~ '+t; drawAdv(); }}); }
function drawAdv(){
openSheet('详细搜索',
advInput('no','单据编号','输入单据编号')
@@ -123,7 +126,7 @@ function drawAdv(){
+advSelect('status','状态',ST)
+advSelect('op','入库员',OPS)
+advSelect('reviewer','审核人',REVIEWERS)
+advSelect('date','单据日期',DATES)
+advDateField()
+`<div style="display:flex;gap:10px;margin-top:16px"><div class="btn ghost" style="flex:1;justify-content:center" onclick="resetAdv()">重置</div><div class="btn primary" style="flex:2;justify-content:center" onclick="applyAdv()">确认</div></div>`);
}
function openAdv(){ advOpen=''; drawAdv(); }
@@ -40,6 +40,7 @@
<script src="icons.js"></script>
<script src="mobile-shell.js"></script>
<script src="datewheel.js"></script>
<script>
const ORDERS=[
{no:'CK-20260620-008',party:'城东烟酒行',wh:'主仓',items:2,amount:'¥18,600',profit:'¥3,400',status:'待审核',date:'2026-06-20',by:'王经理',auditor:'',goods:'茅台 飞天 53°、五粮液 普五 52°',lines:[{n:'茅台 飞天 53°',s:'500ml×6',q:4,a:'¥12,600'},{n:'五粮液 普五 52°',s:'500ml×6',q:3,a:'¥6,000'}]},
@@ -53,7 +54,6 @@ const ORDERS=[
const ST=['全部','草稿','待审核','已审核','已拒绝','部分退单','已退单'];
const OPS=['全部','王经理','李销售'];
const REVIEWERS=['全部','张主管','王经理'];
const DATES=['全部时间','近 7 天','近 30 天','本月'];
const state={q:'',status:'全部'};
/* ---- 状态代表图标(移动纯图标 .ico / 图标+文字 .bi;状态筛选钮为纯文字——用户拍板) ---- */
const STICON={'草稿':'i-ic06','待审核':'i-ic04','已审核':'i-check','已拒绝':'i-close','部分退单':'i-undo','已退单':'i-undo','待定价':'i-yen'};
@@ -111,6 +111,9 @@ function advSelect(key,label,opts){
}
function advToggle(k){ advOpen=advOpen===k?'':k; drawAdv(); }
function advPick(k,v){ adv[k]=v; advOpen=''; drawAdv(); }
/* 单据日期:范围双滚轮底部 sheetdatewheel.js mountRange 纵排,对齐真实端窄屏 showDateRangeDropdown → m-sheet */
function advDateField(){ const v=adv.date!=='全部时间'?adv.date:''; return `<div class="m-field"><label>单据日期</label><div class="m-select" onclick="openAdvDateSheet()"><span class="${v?'':'ph'}">${v||'全部时间'}</span><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg></div></div>`; }
function openAdvDateSheet(){ openSheet('日期区间','<div id="advRangeMount"></div>'); const p=adv.date!=='全部时间'?adv.date.split(' ~ '):['','']; DateWheel.mountRange(document.getElementById('advRangeMount'),{from:p[0],to:p[1],onCommit:(f,t)=>{ adv.date=f+' ~ '+t; drawAdv(); }}); }
function drawAdv(){
openSheet('详细搜索',
advInput('no','单据编号','输入单据编号')
@@ -122,7 +125,7 @@ function drawAdv(){
+advSelect('status','状态',ST)
+advSelect('op','出库员',OPS)
+advSelect('reviewer','审核人',REVIEWERS)
+advSelect('date','单据日期',DATES)
+advDateField()
+`<div style="display:flex;gap:10px;margin-top:16px"><div class="btn ghost" style="flex:1;justify-content:center" onclick="resetAdv()">重置</div><div class="btn primary" style="flex:2;justify-content:center" onclick="applyAdv()">确认</div></div>`);
}
function openAdv(){ advOpen=''; drawAdv(); }
+8 -3
View File
@@ -1,7 +1,7 @@
// ============================================================
// 商品编辑抽屉 单一真源 —— 商品信息 + 商品图片 + 商品介绍 + 预览
// 用法:任意屏引入 <script src="product-editor.js"></script>(需先引 shell.js 取 openMenu/toast),
// 行点击调用 openProductEditor(item)。item 字段按需:{name,code,series,spec,cat,qty,unit,cost,value,status}
// 行点击调用 openProductEditor(item)。item 字段按需:{name,code,series,spec,cat,prodDate,qty,unit,cost,value,status}
// 改这个抽屉(库存 / 基础数据共用)= 只改此文件 + atoms.css 的 .pe-* 样式。
// ============================================================
(function () {
@@ -22,6 +22,8 @@
<div class="drawer-head"><h3 id="peTitle">商品</h3>
<div style="margin-left:auto; display:flex; gap:10px; align-items:center">
<div class="btn ghost sm" onclick="window.open('public-product.html','_blank')"><svg viewBox="0 0 24 24"><use href="#i-eye"/></svg></div>
<!-- 打印标签同入库/出库打印标签QR+品牌型号版本+门店信息移动端无打印不渲染2026-07-04 -->
<div class="btn ghost sm" onclick="toast('打印标签(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic49"/></svg></div>
<svg class="x" viewBox="0 0 24 24" stroke-width="2" onclick="closePubDrawer()"><use href="#i-close"/></svg>
</div>
</div>
@@ -61,9 +63,12 @@
if (it.series) r.push(['系列', `<b>${it.series}</b>`]);
if (it.spec) r.push(['规格', `<b>${it.spec}</b>`]);
if (it.cat) r.push(['分类', `<b>${it.cat}</b>`]);
// 生产日期行(分类后、当前库存前,mono,2026-07-10
if (it.prodDate) r.push(['生产日期', `<b style="font-family:var(--font-mono)">${it.prodDate}</b>`]);
if (it.qty != null) r.push(['当前库存', `<b>${it.qty} ${it.unit || ''}</b>`]);
if (it.cost) r.push(['成本价', `<b>${it.cost}</b>`]);
if (it.value) r.push(['库存货值', `<b>${it.value}</b>`]);
// 成本两行仅管理员可见(非管理员隐藏,与库存列表/出库同口径,2026-07-06);原型=管理员形态
if (it.cost) r.push(['成本价(单瓶)', `<b>${it.cost}</b>`]);
if (it.value) r.push(['总成本价', `<b>${it.value}</b>`]);
// 状态行:三态徽章 + 挂售 switch(在售⇄库存 即时生效;已售只读无 switch)
if (it.status) r.push(['状态', `<b style="display:flex;align-items:center;gap:10px"><span class="badge b-${it.status}">${it.status}</span>${it.status === '已售' ? '' : `<button class="switch ${it.status === '在售' ? 'on' : ''}" aria-label="挂售切换" onclick="this.classList.toggle('on');const b=this.previousElementSibling;const on=this.classList.contains('on');b.textContent=on?'在售':'库存';b.className='badge '+(on?'b-在售':'b-库存');"></button>`}</b>`]);
return r.map((x, i) => `<div class="drow"${i === r.length - 1 ? ' style="border-bottom:none"' : ''}><span>${x[0]}</span>${x[1]}</div>`).join('');
+2 -34
View File
@@ -42,17 +42,6 @@
.b-启用{background:var(--success-bg); color:var(--success);}
.b-停用{background:var(--danger-bg); color:var(--danger);}
/* 授权兑换券 */
.lic{display:flex; gap:24px; flex-wrap:wrap; align-items:center; padding:18px 20px; border:1px solid var(--border); border-radius:var(--r-md); background:var(--bg); margin-bottom:20px;}
.lic .stat{flex:none;}
.lic .stat .t{font-size:var(--fs-sm); color:var(--muted); margin-bottom:6px;}
.lic .stat .v{font-size:24px; font-weight:700; color:var(--heading); font-family:var(--font-mono); letter-spacing:.3px;}
.lic .stat .v.warn{color:var(--warn);}
.lic .ico{width:46px; height:46px; border-radius:var(--r-md); background:var(--ok-soft); color:var(--success); display:flex; align-items:center; justify-content:center; margin-left:auto; flex:none;}
.lic .ico svg{width:24px; height:24px; stroke-width:1.7;}
.redeem{display:flex; gap:10px; max-width:520px; align-items:flex-end;}
.redeem .field{flex:1;}
/* 主题卡 */
.theme-cards{display:grid; grid-template-columns:repeat(3,1fr); gap:14px; max-width:620px; margin-bottom:22px;}
.tcard{border:1.5px solid var(--border); border-radius:var(--r-lg); padding:14px; cursor:pointer; transition:.15s; background:var(--surface);}
@@ -87,13 +76,13 @@
<div class="body">
<div class="side" id="side"></div>
<div class="main">
<div class="head"><h1>系统设置</h1><span class="sub">门店配置 · 用户与授权 · 个性化偏好</span></div>
<div class="head"><h1>系统设置</h1><span class="sub">门店配置 · 用户 · 个性化偏好</span></div>
<div class="settings">
<div class="subnav">
<!-- 授权管理已提升为独立一级屏 license.html2026-07-10),settings 子导航移除该项 -->
<div class="sn on" data-p="store" onclick="showPanel('store')"><svg viewBox="0 0 24 24"><use href="#i-ic44"/></svg>门店信息</div>
<div class="sn" data-p="users" onclick="showPanel('users')"><svg viewBox="0 0 24 24"><use href="#i-ic45"/></svg>用户管理</div>
<div class="sn" data-p="license" onclick="showPanel('license')"><svg viewBox="0 0 24 24"><use href="#i-ic07"/></svg>授权兑换券</div>
<div class="sn" data-p="pref" onclick="showPanel('pref')"><svg viewBox="0 0 24 24"><use href="#i-ic46"/></svg>偏好设置</div>
</div>
@@ -138,24 +127,6 @@
</div>
</div>
<!-- 授权兑换券 -->
<div class="panel" data-p="license">
<div class="card">
<h2>授权兑换券</h2>
<p class="desc">本系统以「时长兑换券」授权,输入兑换券短码即可续期,无需订阅</p>
<div class="lic">
<div class="stat"><div class="t">授权到期日</div><div class="v">2026-09-16</div></div>
<div class="stat"><div class="t">剩余天数</div><div class="v warn">剩余 87 天</div></div>
<div class="ico"><svg viewBox="0 0 24 24"><use href="#i-ic23"/></svg></div>
</div>
<div class="redeem">
<div class="field"><label>兑换券短码</label><input class="input" id="voucher" placeholder="输入兑换券短码"></div>
<div class="btn primary" onclick="redeem()">兑换续期</div>
</div>
<p class="desc" style="margin:16px 0 0">没有兑换券?<a href="pricing.html" style="color:var(--primary);text-decoration:none;font-weight:600">购买授权时长 →</a></p>
</div>
</div>
<!-- 偏好设置 -->
<div class="panel" data-p="pref">
<div class="card">
@@ -203,9 +174,6 @@
/* 偏好设置主题卡 → 切换整站 */
function pickTheme(t){ setTheme(t); toast('已切换为 '+THEMES[t].name+' 主题'); }
/* 兑换券 */
function redeem(){ const v=document.getElementById('voucher').value.trim(); if(!v){ toast('请输入兑换券短码'); return;} document.getElementById('voucher').value=''; toast('兑换成功,已续期'); }
document.addEventListener('keydown',e=>{ if(e.key==='Escape')closeMenus(); });
</script>
+2 -1
View File
@@ -17,12 +17,13 @@
{ 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: 'license', label: '授权管理', href: 'license.html', d: 'M12 3l7 4v5c0 5-3 8-7 9-4-1-7-4-7-9V7l7-4z' },
{ 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 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', license: 'i-shield', settings: 'i-gear', about: 'i-ic24' };
const BRAND = '<div class="brand-prod"><span class="prod-mk"><svg viewBox="0 0 64 64"><g stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"><path d="M14 33L23 16L32 25L41 16L50 33"/><path d="M15 41L49 41"/></g><circle cx="32" cy="48" r="3.4" fill="#C97B86"/></svg></span><span class="prod-nm">岩美酒库</span></div><span class="brand-sep"></span><div class="logo" title="当前门店"><span class="mk">鼎</span><span class="shop-nm">鼎晟酒行</span></div>';
const TOP_RIGHT = '<div class="top-right"><div class="theme-pill" id="themePill"><svg class="tp-shirt" width="15" height="15" viewBox="0 0 24 24" stroke-width="1.9"><use href="#i-shirt"/></svg><span id="themeName">经典蓝</span><svg width="12" height="12" viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div><div class="icon-btn" data-notify title="通知"><svg width="19" height="19" viewBox="0 0 24 24"><use href="#i-bell"/></svg></div></div>';
+11 -7
View File
@@ -224,7 +224,9 @@ const DATE_PRESETS=[
{v:'30',label:'近 30 天'},
{v:'month',label:'本月'},
];
function openDateFilter(t){ openMenu(t,DATE_PRESETS.map(p=>({v:p.v,label:p.label,sel:p.v===(state.datePreset||'all')})),v=>{ applyDatePreset(v); state.page=1; closeMenus(); render(); }); }
function openDateFilter(t){ openMenu(t,[...DATE_PRESETS,{v:'custom',label:'自定义…'}].map(p=>({v:p.v,label:p.label,sel:p.v===(state.datePreset||'all')})),v=>{ closeMenus(); if(v==='custom'){ openCustomRange(t); return; } applyDatePreset(v); state.page=1; render(); }); }
// 自定义范围:起止并排双滚轮锚定 chip 下方(datewheel.js openRange,对齐真实端 showDateRangeDropdown
function openCustomRange(t){ DateWheel.openRange(t,{from:state.dateFrom,to:state.dateTo,onCommit:(f,to)=>{ state.datePreset='custom'; state.dateFrom=f; state.dateTo=to; state.dateLabel=f+' ~ '+to; state.page=1; render(); }}); }
function applyDatePreset(v){ state.datePreset=v; if(v==='all'){state.dateFrom='';state.dateTo='';state.dateLabel='';}
else if(v==='month'){state.dateFrom=TODAY.slice(0,7)+'-01';state.dateTo=TODAY;state.dateLabel='本月';}
else {state.dateFrom=dateMinus(Number(v)-1);state.dateTo=TODAY;state.dateLabel='近'+v+'天';} }
@@ -262,15 +264,14 @@ const OP_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.op)))];
const REVIEWER_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.auditor).filter(Boolean)))];
let adv={};
function advSet(id,val){ const el=document.getElementById(id); if(el)el.value=(!val||val==='全部')?'':val; }
function dateLabelOf(v){ return v==='month'?'本月':(!v||v==='all'?'':'近'+v+'天'); }
function openAdvSearch(){
adv={party:state.party,series:state.series,spec:state.spec,category:state.category,op:state.op,reviewer:state.reviewer,status:state.status,datePreset:state.datePreset||'all'};
adv={party:state.party,series:state.series,spec:state.spec,category:state.category,op:state.op,reviewer:state.reviewer,status:state.status,dateFrom:state.dateFrom||'',dateTo:state.dateTo||''};
advSet('advParty',adv.party); advSet('advSeries',adv.series); advSet('advSpec',adv.spec); advSet('advCategory',adv.category);
advSet('advOp',adv.op); advSet('advReviewer',adv.reviewer); advSet('advStatus',adv.status);
document.getElementById('advNo').value=state.orderNo||'';
document.getElementById('advProduct').value=state.productInfo||'';
document.getElementById('advBatch').value=state.batch||'';
document.getElementById('advDate').value=dateLabelOf(adv.datePreset);
document.getElementById('advDate').value=adv.dateFrom?adv.dateFrom+' ~ '+adv.dateTo:'';
document.getElementById('ovAdv').classList.add('show');
}
function advPick(el,id,opts,key,ph){ openSearchMenu(el,opts.map(v=>({v,label:v,sel:v===adv[key]})),v=>{ adv[key]=v; advSet(id,v); closeMenus(); },{placeholder:ph}); }
@@ -282,18 +283,20 @@ function openAdvCategory(el){ advPick(el,'advCategory',CAT_OPTS,'category','搜
function openAdvOp(el){ advMenu(el,'advOp',OP_OPTS,'op'); }
function openAdvReviewer(el){ advMenu(el,'advReviewer',REVIEWER_OPTS,'reviewer'); }
function openAdvStatus(el){ advMenu(el,'advStatus',STATUS,'status'); }
function openAdvDate(el){ openMenu(el,DATE_PRESETS.map(p=>({v:p.v,label:p.label,sel:p.v===adv.datePreset})),v=>{ adv.datePreset=v; document.getElementById('advDate').value=dateLabelOf(v); closeMenus(); }); }
// 详搜「单据日期」:直接打开范围双滚轮,锚定字段下方(对齐真实端 _AdvSearchDialog._pickDate
function openAdvDate(el){ DateWheel.openRange(el,{from:adv.dateFrom,to:adv.dateTo,onCommit:(f,t)=>{ adv.dateFrom=f; adv.dateTo=t; document.getElementById('advDate').value=f+' ~ '+t; }}); }
function applyAdv(){
state.orderNo=document.getElementById('advNo').value.trim();
state.productInfo=document.getElementById('advProduct').value.trim();
state.batch=document.getElementById('advBatch').value.trim();
state.party=adv.party; state.series=adv.series; state.spec=adv.spec; state.category=adv.category;
state.op=adv.op; state.reviewer=adv.reviewer; state.status=adv.status;
applyDatePreset(adv.datePreset);
state.dateFrom=adv.dateFrom; state.dateTo=adv.dateTo;
state.dateLabel=adv.dateFrom?adv.dateFrom+' ~ '+adv.dateTo:''; state.datePreset=adv.dateFrom?'custom':'all';
state.page=1; closeOverlay('ovAdv'); render();
}
function resetAdv(){
adv={party:'全部',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部',status:'全部',datePreset:'all'};
adv={party:'全部',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部',status:'全部',dateFrom:'',dateTo:''};
['advNo','advProduct','advBatch','advParty','advSeries','advSpec','advCategory','advOp','advReviewer','advStatus','advDate'].forEach(id=>{const el=document.getElementById(id); if(el)el.value='';});
state.party='全部';state.status='全部';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';
applyDatePreset('all'); state.page=1; render();
@@ -341,6 +344,7 @@ document.addEventListener('keydown',e=>{ if(e.key==='Escape'){closeMenus();docum
render();
</script>
<a class="ds-fab" href="../index.html" title="返回设计系统" aria-label="返回设计系统"><svg viewBox="0 0 24 24"><use href="#i-arrow-left"/></svg></a>
<script src="datewheel.js"></script>
<script src="fab.js"></script>
<script src="notify.js"></script>
<script src="statusbar.js"></script>
+11 -7
View File
@@ -218,7 +218,9 @@ function openWhFilter(t){ openMenu(t,WAREHOUSES.map(v=>({v,label:v,sel:v===state
const TODAY='2026-06-22';
function dateMinus(days){ const d=new Date(TODAY); d.setDate(d.getDate()-days); return d.toISOString().slice(0,10); }
const DATE_PRESETS=[{v:'all',label:'全部时间'},{v:'7',label:'近 7 天'},{v:'30',label:'近 30 天'},{v:'month',label:'本月'}];
function openDateFilter(t){ openMenu(t,DATE_PRESETS.map(p=>({v:p.v,label:p.label,sel:p.v===(state.datePreset||'all')})),v=>{ applyDatePreset(v); state.page=1; closeMenus(); render(); }); }
function openDateFilter(t){ openMenu(t,[...DATE_PRESETS,{v:'custom',label:'自定义…'}].map(p=>({v:p.v,label:p.label,sel:p.v===(state.datePreset||'all')})),v=>{ closeMenus(); if(v==='custom'){ openCustomRange(t); return; } applyDatePreset(v); state.page=1; render(); }); }
// 自定义范围:起止并排双滚轮锚定 chip 下方(datewheel.js openRange,对齐真实端 showDateRangeDropdown
function openCustomRange(t){ DateWheel.openRange(t,{from:state.dateFrom,to:state.dateTo,onCommit:(f,to)=>{ state.datePreset='custom'; state.dateFrom=f; state.dateTo=to; state.dateLabel=f+' ~ '+to; state.page=1; render(); }}); }
function applyDatePreset(v){ state.datePreset=v; if(v==='all'){state.dateFrom='';state.dateTo='';state.dateLabel='';}
else if(v==='month'){state.dateFrom=TODAY.slice(0,7)+'-01';state.dateTo=TODAY;state.dateLabel='本月';}
else {state.dateFrom=dateMinus(Number(v)-1);state.dateTo=TODAY;state.dateLabel='近'+v+'天';} }
@@ -257,15 +259,14 @@ const OP_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.op)))];
const REVIEWER_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.auditor).filter(Boolean)))];
let adv={};
function advSet(id,val){ const el=document.getElementById(id); if(el)el.value=(!val||val==='全部')?'':val; }
function dateLabelOf(v){ return v==='month'?'本月':(!v||v==='all'?'':'近'+v+'天'); }
function openAdvSearch(){
adv={party:state.party,series:state.series,spec:state.spec,category:state.category,op:state.op,reviewer:state.reviewer,status:state.status,datePreset:state.datePreset||'all'};
adv={party:state.party,series:state.series,spec:state.spec,category:state.category,op:state.op,reviewer:state.reviewer,status:state.status,dateFrom:state.dateFrom||'',dateTo:state.dateTo||''};
advSet('advParty',adv.party); advSet('advSeries',adv.series); advSet('advSpec',adv.spec); advSet('advCategory',adv.category);
advSet('advOp',adv.op); advSet('advReviewer',adv.reviewer); advSet('advStatus',adv.status);
document.getElementById('advNo').value=state.orderNo||'';
document.getElementById('advProduct').value=state.productInfo||'';
document.getElementById('advBatch').value=state.batch||'';
document.getElementById('advDate').value=dateLabelOf(adv.datePreset);
document.getElementById('advDate').value=adv.dateFrom?adv.dateFrom+' ~ '+adv.dateTo:'';
document.getElementById('ovAdv').classList.add('show');
}
function advPick(el,id,opts,key,ph){ openSearchMenu(el,opts.map(v=>({v,label:v,sel:v===adv[key]})),v=>{ adv[key]=v; advSet(id,v); closeMenus(); },{placeholder:ph}); }
@@ -277,18 +278,20 @@ function openAdvCategory(el){ advPick(el,'advCategory',CAT_OPTS,'category','搜
function openAdvOp(el){ advMenu(el,'advOp',OP_OPTS,'op'); }
function openAdvReviewer(el){ advMenu(el,'advReviewer',REVIEWER_OPTS,'reviewer'); }
function openAdvStatus(el){ advMenu(el,'advStatus',STATUS,'status'); }
function openAdvDate(el){ openMenu(el,DATE_PRESETS.map(p=>({v:p.v,label:p.label,sel:p.v===adv.datePreset})),v=>{ adv.datePreset=v; document.getElementById('advDate').value=dateLabelOf(v); closeMenus(); }); }
// 详搜「单据日期」:直接打开范围双滚轮,锚定字段下方(对齐真实端 _AdvSearchDialog._pickDate
function openAdvDate(el){ DateWheel.openRange(el,{from:adv.dateFrom,to:adv.dateTo,onCommit:(f,t)=>{ adv.dateFrom=f; adv.dateTo=t; document.getElementById('advDate').value=f+' ~ '+t; }}); }
function applyAdv(){
state.orderNo=document.getElementById('advNo').value.trim();
state.productInfo=document.getElementById('advProduct').value.trim();
state.batch=document.getElementById('advBatch').value.trim();
state.party=adv.party; state.series=adv.series; state.spec=adv.spec; state.category=adv.category;
state.op=adv.op; state.reviewer=adv.reviewer; state.status=adv.status;
applyDatePreset(adv.datePreset);
state.dateFrom=adv.dateFrom; state.dateTo=adv.dateTo;
state.dateLabel=adv.dateFrom?adv.dateFrom+' ~ '+adv.dateTo:''; state.datePreset=adv.dateFrom?'custom':'all';
state.page=1; closeOverlay('ovAdv'); render();
}
function resetAdv(){
adv={party:'全部',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部',status:'全部',datePreset:'all'};
adv={party:'全部',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部',status:'全部',dateFrom:'',dateTo:''};
['advNo','advProduct','advBatch','advParty','advSeries','advSpec','advCategory','advOp','advReviewer','advStatus','advDate'].forEach(id=>{const el=document.getElementById(id); if(el)el.value='';});
state.party='全部';state.status='全部';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';
applyDatePreset('all'); state.page=1; render();
@@ -336,6 +339,7 @@ document.addEventListener('keydown',e=>{ if(e.key==='Escape'){closeMenus();docum
render();
</script>
<a class="ds-fab" href="../index.html" title="返回设计系统" aria-label="返回设计系统"><svg viewBox="0 0 24 24"><use href="#i-arrow-left"/></svg></a>
<script src="datewheel.js"></script>
<script src="fab.js"></script>
<script src="notify.js"></script>
<script src="statusbar.js"></script>