feat(client): 库存三态 UI——抽屉挂售 switch + 三态徽章/筛选/已售 KPI 卡
- 新组件 DsSwitch(原型 atoms .switch 对照,L1 已登记:38×22 pill,on=primary) - 商品抽屉状态行:三态徽章后跟挂售 switch(在售⇄库存 即时生效,已售只读, readonly 禁用),桌面 drawer / 移动 sheet 同组件 - 列表状态列/卡片徽章三态化(在售绿/库存蓝/已售灰,icons BADGE_ICON 补两词); 状态筛选改「全部/在售/库存/已售」走服务端;预警缺货退出状态维度由数量染色承担 - KPI 第四卡「缺货预警」→「已售」(sold_count,点击筛选已售) - 原型同步:atoms/index 登记 switch 与 b-库存/b-已售、inventory 两屏三态数据、 抽屉状态行 switch;12 道 ds 闸 + L1 同源闸全过 - golden 重录 ×9(三态徽章 + 已售卡形态) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
@@ -32,6 +32,13 @@ svg{fill:none; stroke:currentColor;}
|
||||
.searchbox svg{width:14px; height:14px; stroke-width:2; color:var(--faint); flex:none;}
|
||||
.searchbox input{border:none; background:transparent; outline:none; font-size:var(--fs-body); color:var(--text); width:100%; font-family:var(--font);}
|
||||
.searchbox input::placeholder{color:var(--faint);}
|
||||
/* ---- Switch 开关(2026-07-07 新增:库存⇄在售 挂售切换等布尔即时生效场景) ----
|
||||
38×22 pill 轨道 + 18px 圆点滑块;on=primary,off=border;disabled 半透明。 */
|
||||
.switch{position:relative; width:38px; height:22px; border-radius:var(--r-pill); background:var(--border); border:none; padding:0; cursor:pointer; transition:background-color .18s; flex:none;}
|
||||
.switch::after{content:""; position:absolute; top:2px; left:2px; width:18px; height:18px; border-radius:50%; background:var(--surface); box-shadow:var(--sh-1); transition:transform .18s;}
|
||||
.switch.on{background:var(--primary);}
|
||||
.switch.on::after{transform:translateX(16px);}
|
||||
.switch:disabled{opacity:.45; cursor:default;}
|
||||
|
||||
/* ---- 日期字段(触发器)+ 滚轮日期选择器(年月日,单据/生产/明细共用) ---- */
|
||||
.datefield{height:38px; width:100%; box-sizing:border-box; background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md); padding:0 11px; display:flex; align-items:center; gap:8px; cursor:pointer; outline:none; transition:border-color .15s,box-shadow .15s;}
|
||||
@@ -79,6 +86,9 @@ svg{fill:none; stroke:currentColor;}
|
||||
.b-在售{background:var(--success-bg); color:var(--success);}
|
||||
.b-缺货{background:var(--danger-bg); color:var(--danger);}
|
||||
.b-预警{background:var(--warn-bg); color:var(--warn);}
|
||||
/* 库存三态(2026-07-07 落库):库存=在库不公开 / 已售=卖光留痕 */
|
||||
.b-库存{background:var(--info-soft); color:var(--primary);}
|
||||
.b-已售{background:var(--border-subtle); color:var(--muted);}
|
||||
/* 单据状态 */
|
||||
.b-草稿{background:var(--info-soft); color:var(--muted);}
|
||||
.b-待审核{background:var(--warn-bg); color:var(--warn);}
|
||||
|
||||
@@ -324,6 +324,12 @@
|
||||
<div class="searchbox" style="width:240px"><svg viewBox="0 0 24 24"><use href="#i-search"/></svg><input placeholder="搜索"></div>
|
||||
<div class="ctrl" style="max-width:200px"><span class="ph">选择往来单位…</span><svg width="14" height="14" viewBox="0 0 24 24" stroke-width="2"><use href="#i-chevron-down"/></svg></div>
|
||||
</div></div>
|
||||
<div class="pcard"><div class="pcard-h"><h3>Switch 开关</h3><span class="tk">.switch .on · 布尔即时生效(库存⇄在售 挂售切换)· 2026-07-07</span></div><div class="pcard-b">
|
||||
<button class="switch on" onclick="this.classList.toggle('on')" aria-label="switch demo"></button>
|
||||
<button class="switch" onclick="this.classList.toggle('on')" aria-label="switch demo"></button>
|
||||
<button class="switch" disabled aria-label="switch disabled"></button>
|
||||
<span style="font-size:var(--fs-xs);color:var(--faint)">on / off / disabled(已售行无 switch)</span>
|
||||
</div></div>
|
||||
<div class="pcard"><div class="pcard-h"><h3>分段切换 / 可搜索选择器</h3><span class="tk">.seg · .combo(录单网格用)</span></div><div class="pcard-b">
|
||||
<div class="seg"><button class="on">入库</button><button>出库</button></div>
|
||||
<div class="combo has" style="width:240px; flex:none"><input value="茅台 飞天 53°" readonly><span class="caf"><svg viewBox="0 0 24 24" stroke-width="2"><use href="#i-close"/></svg></span></div>
|
||||
@@ -369,7 +375,8 @@
|
||||
</div></div>
|
||||
<div class="pcard"><div class="pcard-h"><h3>状态徽章 · 全部</h3><span class="tk">.badge .b-* · 对齐 schema 真实枚举 · 圆点已全局图标化(icons.js BADGE_ICON 单源映射自动注入,屏内无需手写 svg)</span></div><div class="pcard-b" style="display:block">
|
||||
<div style="display:flex;flex-direction:column;gap:13px">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap"><span style="width:160px;flex:none;font-size:var(--fs-xs);color:var(--muted);font-weight:600">库存 · 按 min_stock 派生</span><span class="badge b-在售">在售</span><span class="badge b-预警">预警</span><span class="badge b-缺货">缺货</span></div>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap"><span style="width:160px;flex:none;font-size:var(--fs-xs);color:var(--muted);font-weight:600">库存三态 · status 落库</span><span class="badge b-在售">在售</span><span class="badge b-库存">库存</span><span class="badge b-已售">已售</span><span style="font-size:var(--fs-xs);color:var(--faint)">(在售=公开酒单展示;已售=卖光留痕;预警/缺货退出状态列,由数量染色承担)</span></div>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap"><span style="width:160px;flex:none;font-size:var(--fs-xs);color:var(--muted);font-weight:600">库存 · 数量派生提示</span><span class="badge b-预警">预警</span><span class="badge b-缺货">缺货</span></div>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap"><span style="width:160px;flex:none;font-size:var(--fs-xs);color:var(--muted);font-weight:600">单据 · 入库/出库</span><span class="badge b-草稿">草稿</span><span class="badge b-待审核">待审核</span><span class="badge b-已通过">已通过</span><span class="badge b-已驳回">已驳回</span></div>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap"><span style="width:160px;flex:none;font-size:var(--fs-xs);color:var(--muted);font-weight:600">往来单位 · 启用停用</span><span class="badge b-启用">启用</span><span class="badge b-停用">停用</span><span class="badge b-供应商">供应商</span><span class="badge b-客户">客户</span><span class="badge b-两者">两者</span></div>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap"><span style="width:160px;flex:none;font-size:var(--fs-xs);color:var(--muted);font-weight:600">用户角色</span><span class="badge b-管理员">管理员</span><span class="badge b-普通">普通</span><span class="badge b-只读">只读</span></div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// 单一真相源映射:任何屏渲染出的 .badge.b-*(无 .bi/.ico 者)自动注入图标并隐藏圆点。
|
||||
// 屏内无需手写 svg;新增状态在此登记即可全局生效。
|
||||
var BADGE_ICON = {
|
||||
'在售':'i-cart','预警':'i-alert','缺货':'i-close',
|
||||
'在售':'i-cart','预警':'i-alert','缺货':'i-close','库存':'i-box','已售':'i-check',
|
||||
'草稿':'i-ic06','待审核':'i-ic04','已审核':'i-check','已拒绝':'i-close','已通过':'i-check','已驳回':'i-close',
|
||||
'部分退单':'i-undo','已退单':'i-undo','待定价':'i-yen',
|
||||
'启用':'i-check','停用':'i-close','供应商':'i-box','客户':'i-ic37','两者':'i-refresh',
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<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>
|
||||
<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-ic18"/></svg></div><div class="t">缺货预警 · 点击筛选</div><div class="v">17</div><div class="d down">需补货 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>
|
||||
</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>
|
||||
@@ -101,18 +101,18 @@
|
||||
const ITEMS=[
|
||||
{name:'茅台 飞天 53°',code:'MT-FT-500',spec:'500ml×6',series:'飞天',batch:'PZ240711',cat:'酱香',qty:128,unit:'件',cost:'¥2,680',price:'¥343,040',prodDate:'2024-07-11',inDate:'2026-06-14',supplier:'贵州茅台经销',value:'¥34.3万',status:'在售'},
|
||||
{name:'五粮液 普五 52°',code:'WLY-PW-500',spec:'500ml×6',series:'普五',batch:'PZ240428',cat:'浓香',qty:64,unit:'件',cost:'¥1,050',price:'¥67,200',prodDate:'2024-04-28',inDate:'2026-06-14',supplier:'宜宾五粮液',value:'¥6.7万',status:'在售'},
|
||||
{name:'剑南春 水晶剑',code:'JNC-SJ-500',spec:'500ml×6',series:'水晶剑',batch:'PZ231115',cat:'浓香',qty:8,unit:'件',cost:'¥438',price:'¥3,504',prodDate:'2023-11-15',inDate:'2026-06-12',supplier:'绵竹剑南春',value:'¥3,500',status:'缺货'},
|
||||
{name:'剑南春 水晶剑',code:'JNC-SJ-500',spec:'500ml×6',series:'水晶剑',batch:'PZ231115',cat:'浓香',qty:8,unit:'件',cost:'¥438',price:'¥3,504',prodDate:'2023-11-15',inDate:'2026-06-12',supplier:'绵竹剑南春',value:'¥3,500',status:'已售'},
|
||||
{name:'国窖 1573',code:'GJ-1573-500',spec:'500ml×6',series:'1573',batch:'PZ240208',cat:'浓香',qty:42,unit:'件',cost:'¥980',price:'¥41,160',prodDate:'2024-02-08',inDate:'2026-06-10',supplier:'泸州老窖',value:'¥4.1万',status:'在售'},
|
||||
{name:'洋河 梦之蓝 M6',code:'YH-M6-500',spec:'500ml×6',series:'梦之蓝M6',batch:'PZ240520',cat:'绵柔',qty:23,unit:'件',cost:'¥620',price:'¥14,260',prodDate:'2024-05-20',inDate:'2026-06-09',supplier:'洋河股份',value:'¥1.4万',status:'预警'},
|
||||
{name:'洋河 梦之蓝 M6',code:'YH-M6-500',spec:'500ml×6',series:'梦之蓝M6',batch:'PZ240520',cat:'绵柔',qty:23,unit:'件',cost:'¥620',price:'¥14,260',prodDate:'2024-05-20',inDate:'2026-06-09',supplier:'洋河股份',value:'¥1.4万',status:'库存'},
|
||||
{name:'泸州老窖 特曲',code:'LZLJ-TQ-500',spec:'500ml×6',series:'特曲',batch:'PZ240630',cat:'浓香',qty:156,unit:'件',cost:'¥320',price:'¥49,920',prodDate:'2024-06-30',inDate:'2026-06-08',supplier:'泸州老窖',value:'¥5万',status:'在售'},
|
||||
{name:'习酒 窖藏 1988',code:'XJ-JC1988',spec:'500ml×6',series:'窖藏1988',batch:'PZ230918',cat:'酱香',qty:31,unit:'件',cost:'¥760',price:'¥23,560',prodDate:'2023-09-18',inDate:'2026-06-05',supplier:'贵州习酒',value:'¥2.4万',status:'在售'},
|
||||
{name:'汾酒 青花 20',code:'FJ-QH20-500',spec:'500ml×6',series:'青花20',batch:'PZ240112',cat:'清香',qty:0,unit:'件',cost:'¥420',price:'¥0',prodDate:'2024-01-12',inDate:'2026-06-03',supplier:'山西杏花村',value:'¥0',status:'缺货'},
|
||||
{name:'汾酒 青花 20',code:'FJ-QH20-500',spec:'500ml×6',series:'青花20',batch:'PZ240112',cat:'清香',qty:0,unit:'件',cost:'¥420',price:'¥0',prodDate:'2024-01-12',inDate:'2026-06-03',supplier:'山西杏花村',value:'¥0',status:'已售'},
|
||||
{name:'郎酒 红花郎 15',code:'LJ-HHL15',spec:'500ml×6',series:'红花郎15',batch:'PZ240325',cat:'酱香',qty:54,unit:'件',cost:'¥560',price:'¥30,240',prodDate:'2024-03-25',inDate:'2026-06-01',supplier:'四川郎酒',value:'¥3万',status:'在售'},
|
||||
{name:'古井贡 年份原浆',code:'GJG-NF-500',spec:'500ml×6',series:'年份原浆',batch:'PZ240802',cat:'浓香',qty:12,unit:'件',cost:'¥390',price:'¥4,680',prodDate:'2024-08-02',inDate:'2026-05-28',supplier:'古井贡酒',value:'¥4,700',status:'预警'},
|
||||
{name:'古井贡 年份原浆',code:'GJG-NF-500',spec:'500ml×6',series:'年份原浆',batch:'PZ240802',cat:'浓香',qty:12,unit:'件',cost:'¥390',price:'¥4,680',prodDate:'2024-08-02',inDate:'2026-05-28',supplier:'古井贡酒',value:'¥4,700',status:'库存'},
|
||||
{name:'西凤酒 旗舰版',code:'XF-QJ-500',spec:'500ml×6',series:'旗舰版',batch:'PZ240614',cat:'凤香',qty:88,unit:'件',cost:'¥280',price:'¥24,640',prodDate:'2024-06-14',inDate:'2026-05-25',supplier:'陕西西凤',value:'¥2.5万',status:'在售'},
|
||||
{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:'缺货'},
|
||||
{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=['全部','在售','预警','缺货'];
|
||||
const STATUS=['全部','在售','库存','已售'];
|
||||
const COLS=[{key:'product',label:'商品',fixed:true},{key:'spec',label:'规格'},{key:'series',label:'系列'},{key:'batch',label:'批次号'},{key:'qty',label:'库存',num:true},{key:'cost',label:'成本价',num:true},{key:'price',label:'总价',num:true},{key:'prodDate',label:'生产日期'},{key:'inDate',label:'入库时间'},{key:'supplier',label:'供应商'},{key:'status',label:'状态',filter:'status'},{key:'act',label:'操作',fixed:true}];
|
||||
const state={q:'',code:'',status:'全部',page:1,perPage:10,hidden:new Set()};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<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"><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>
|
||||
<div class="k"><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>
|
||||
@@ -38,12 +38,12 @@
|
||||
const ITEMS=[
|
||||
{name:'茅台 飞天 53°',code:'MT-FT-500',spec:'500ml×6',series:'飞天',batch:'PZ240711',qty:128,unit:'件',cost:'¥2,680',supplier:'贵州茅台经销',status:'在售'},
|
||||
{name:'五粮液 普五 52°',code:'WLY-PW-500',spec:'500ml×6',series:'普五',batch:'PZ240428',qty:64,unit:'件',cost:'¥1,050',supplier:'宜宾五粮液',status:'在售'},
|
||||
{name:'剑南春 水晶剑',code:'JNC-SJ-500',spec:'500ml×6',series:'水晶剑',batch:'PZ231115',qty:8,unit:'件',cost:'¥438',supplier:'绵竹剑南春',status:'缺货'},
|
||||
{name:'剑南春 水晶剑',code:'JNC-SJ-500',spec:'500ml×6',series:'水晶剑',batch:'PZ231115',qty:8,unit:'件',cost:'¥438',supplier:'绵竹剑南春',status:'已售'},
|
||||
{name:'国窖 1573',code:'GJ-1573-500',spec:'500ml×6',series:'1573',batch:'PZ240208',qty:42,unit:'件',cost:'¥980',supplier:'泸州老窖',status:'在售'},
|
||||
{name:'洋河 梦之蓝 M6',code:'YH-M6-500',spec:'550ml×6',series:'梦之蓝M6',batch:'PZ240520',qty:23,unit:'件',cost:'¥620',supplier:'洋河股份',status:'预警'},
|
||||
{name:'洋河 梦之蓝 M6',code:'YH-M6-500',spec:'550ml×6',series:'梦之蓝M6',batch:'PZ240520',qty:23,unit:'件',cost:'¥620',supplier:'洋河股份',status:'库存'},
|
||||
{name:'泸州老窖 特曲',code:'LZLJ-TQ-500',spec:'500ml×6',series:'特曲',batch:'PZ240630',qty:156,unit:'件',cost:'¥320',supplier:'泸州老窖',status:'在售'},
|
||||
{name:'习酒 窖藏 1988',code:'XJ-JC1988',spec:'500ml×6',series:'窖藏1988',batch:'PZ230918',qty:31,unit:'件',cost:'¥760',supplier:'贵州习酒',status:'在售'},
|
||||
{name:'汾酒 青花 20',code:'FJ-QH20-500',spec:'500ml×6',series:'青花20',batch:'PZ240112',qty:0,unit:'件',cost:'¥420',supplier:'山西杏花村',status:'缺货'},
|
||||
{name:'汾酒 青花 20',code:'FJ-QH20-500',spec:'500ml×6',series:'青花20',batch:'PZ240112',qty:0,unit:'件',cost:'¥420',supplier:'山西杏花村',status:'已售'},
|
||||
{name:'郎酒 红花郎 15',code:'LJ-HHL15',spec:'500ml×6',series:'红花郎15',batch:'PZ240325',qty:54,unit:'件',cost:'¥560',supplier:'四川郎酒',status:'在售'},
|
||||
{name:'西凤酒 旗舰版',code:'XF-QJ-500',spec:'500ml×6',series:'旗舰版',batch:'PZ240614',qty:88,unit:'件',cost:'¥280',supplier:'陕西西凤',status:'在售'},
|
||||
];
|
||||
@@ -66,7 +66,7 @@ function render(){
|
||||
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>${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><span class="badge b-${it.status}">${it.status}</span></b></div>
|
||||
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>${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>
|
||||
<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>
|
||||
|
||||
@@ -64,7 +64,8 @@
|
||||
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>`]);
|
||||
if (it.status) r.push(['状态', `<b><span class="badge b-${it.status}">${it.status}</span></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('');
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ class Inventory {
|
||||
final int? productId;
|
||||
final int? stockInItemId;
|
||||
final double quantity;
|
||||
|
||||
/// 库存三态(2026-07-07):stock=库存 / on_sale=在售(公开酒单) / sold=已售(卖光留痕)
|
||||
final String status;
|
||||
final String productCode;
|
||||
final String productName;
|
||||
final String series;
|
||||
@@ -31,6 +34,7 @@ class Inventory {
|
||||
this.productId,
|
||||
this.stockInItemId,
|
||||
required this.quantity,
|
||||
this.status = 'stock',
|
||||
this.productCode = '',
|
||||
this.productName = '',
|
||||
this.series = '',
|
||||
@@ -54,6 +58,7 @@ class Inventory {
|
||||
productId: (json['product_id'] as num?)?.toInt(),
|
||||
stockInItemId: (json['stock_in_item_id'] as num?)?.toInt(),
|
||||
quantity: (json['quantity'] as num).toDouble(),
|
||||
status: json['status'] as String? ?? 'stock',
|
||||
productCode: json['product_code'] as String? ?? '',
|
||||
productName: json['product_name'] as String? ?? '',
|
||||
series: json['series'] as String? ?? '',
|
||||
@@ -77,6 +82,7 @@ class InventorySummary {
|
||||
final int skuCount;
|
||||
final double stockValue;
|
||||
final double inStockQty;
|
||||
final int soldCount; // 已售留痕行数(status=sold)
|
||||
final int shortageCount;
|
||||
final int warningCount;
|
||||
// 月初快照(KPI 环比):由 inventory_logs 反推
|
||||
@@ -87,6 +93,7 @@ class InventorySummary {
|
||||
this.skuCount = 0,
|
||||
this.stockValue = 0,
|
||||
this.inStockQty = 0,
|
||||
this.soldCount = 0,
|
||||
this.shortageCount = 0,
|
||||
this.warningCount = 0,
|
||||
this.lastMonthSku = 0,
|
||||
@@ -97,6 +104,7 @@ class InventorySummary {
|
||||
skuCount: (j['sku_count'] as num?)?.toInt() ?? 0,
|
||||
stockValue: (j['stock_value'] as num?)?.toDouble() ?? 0,
|
||||
inStockQty: (j['in_stock_qty'] as num?)?.toDouble() ?? 0,
|
||||
soldCount: (j['sold_count'] as num?)?.toInt() ?? 0,
|
||||
shortageCount: (j['shortage_count'] as num?)?.toInt() ?? 0,
|
||||
warningCount: (j['warning_count'] as num?)?.toInt() ?? 0,
|
||||
lastMonthSku: (j['last_month_sku'] as num?)?.toInt() ?? 0,
|
||||
|
||||
@@ -30,6 +30,7 @@ class InventoryListNotifier extends AsyncNotifier<PageResult<Inventory>> {
|
||||
int? _warehouseId;
|
||||
String _keyword = '';
|
||||
String _code = ''; // 商品编码独立筛选(原型 codeInput)
|
||||
String _status = ''; // 三态筛选(空=后端默认排除已售)
|
||||
List<String> _series = [];
|
||||
List<String> _spec = [];
|
||||
PageResult<Inventory>? _cache;
|
||||
@@ -53,6 +54,7 @@ class InventoryListNotifier extends AsyncNotifier<PageResult<Inventory>> {
|
||||
warehouseId: _warehouseId,
|
||||
keyword: _keyword.isEmpty ? null : _keyword,
|
||||
code: _code.isEmpty ? null : _code,
|
||||
status: _status.isEmpty ? null : _status,
|
||||
series: _series.isEmpty ? null : _series,
|
||||
spec: _spec.isEmpty ? null : _spec,
|
||||
page: _page,
|
||||
@@ -89,6 +91,12 @@ class InventoryListNotifier extends AsyncNotifier<PageResult<Inventory>> {
|
||||
reload();
|
||||
}
|
||||
|
||||
void setStatus(String status) {
|
||||
_status = status;
|
||||
_page = 1;
|
||||
reload();
|
||||
}
|
||||
|
||||
void setSeries(List<String> series) {
|
||||
_series = series;
|
||||
_page = 1;
|
||||
|
||||
@@ -16,6 +16,7 @@ class InventoryRepository {
|
||||
String? code,
|
||||
List<String>? series,
|
||||
List<String>? spec,
|
||||
String? status, // 三态筛选:stock/on_sale/sold(逗号可多值);空=后端默认(排除已售)
|
||||
int page = 1,
|
||||
int pageSize = 50,
|
||||
}) async {
|
||||
@@ -29,6 +30,7 @@ class InventoryRepository {
|
||||
if (code != null && code.isNotEmpty) 'code': code,
|
||||
if (series != null && series.isNotEmpty) 'series': series.join(','),
|
||||
if (spec != null && spec.isNotEmpty) 'spec': spec.join(','),
|
||||
if (status != null && status.isNotEmpty) 'status': status,
|
||||
};
|
||||
final resp = await _client.get('/inventory', params: params);
|
||||
return PageResult.fromJson(
|
||||
@@ -67,6 +69,18 @@ class InventoryRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// 库存⇄在售 挂售切换(status 仅收 stock/on_sale;sold 由出库流程专属)
|
||||
Future<void> updateStatus(int id, String status) async {
|
||||
try {
|
||||
await _client.put('/inventory/$id/status', data: {'status': status});
|
||||
} on DioException catch (e) {
|
||||
throw AppException(
|
||||
e.response?.data?['error'] as String? ?? '状态更新失败',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateRemark(int id, String remark) async {
|
||||
try {
|
||||
await _client.put('/inventory/$id/remark', data: {'remark': remark});
|
||||
|
||||
@@ -38,12 +38,19 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
Set<String> _filterWarehouse = {};
|
||||
Set<String> _filterSpec = {};
|
||||
Set<String> _filterSeries = {};
|
||||
// 库存状态前端筛选(派生自 qty vs 安全库存):全部/在售/预警/缺货
|
||||
// 库存三态筛选(2026-07-07 status 落库,服务端过滤):全部/在售/库存/已售;
|
||||
// 默认「全部」= 后端排除已售(已售仅点已售卡/筛选时显示)。预警缺货退出状态维度。
|
||||
String _statusFilter = '全部';
|
||||
static const _statusParam = {
|
||||
'全部': '',
|
||||
'在售': 'on_sale',
|
||||
'库存': 'stock',
|
||||
'已售': 'sold',
|
||||
};
|
||||
Set<String>? _hiddenCols; // null = 尚未载入本地存档
|
||||
|
||||
static const _screenId = 'inventory_list';
|
||||
static const _statusOptions = ['全部', '在售', '预警', '缺货'];
|
||||
static const _statusOptions = ['全部', '在售', '库存', '已售'];
|
||||
|
||||
// 列序照原型 inventory.html COLS:商品/规格/系列/批次/库存/成本价/单价/生产/入库/供应商/状态/操作。
|
||||
// 成本价=入库进价(unitPrice),总价=库存×成本价(2026-07-03 用户拍板:原「单价/参考售价」列换掉)。
|
||||
@@ -95,13 +102,21 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
n.setKeyword('');
|
||||
n.setSpec([]);
|
||||
n.setSeries([]);
|
||||
n.setStatus('');
|
||||
}
|
||||
|
||||
String _statusOf(Inventory i) => i.quantity == 0
|
||||
? '缺货'
|
||||
: (i.minStock != null && i.quantity < i.minStock!)
|
||||
? '预警'
|
||||
: '在售';
|
||||
// 三态落库后的显示标签(status 列真相源)
|
||||
String _statusOf(Inventory i) => switch (i.status) {
|
||||
'on_sale' => '在售',
|
||||
'sold' => '已售',
|
||||
_ => '库存',
|
||||
};
|
||||
|
||||
// 低库存提示(预警/缺货退出状态列后由数量染色承担)
|
||||
bool _isLowStock(Inventory i) =>
|
||||
i.status != 'sold' &&
|
||||
(i.quantity == 0 ||
|
||||
(i.minStock != null && i.quantity < i.minStock!));
|
||||
|
||||
void _openEditor(Inventory item) {
|
||||
if (item.productId == null) return;
|
||||
@@ -112,9 +127,20 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
unit: item.unit,
|
||||
cost: item.unitPrice,
|
||||
status: _statusOf(item),
|
||||
inventoryId: item.id,
|
||||
inventoryStatus: item.status,
|
||||
onStatusChanged: () =>
|
||||
ref.read(inventoryListProvider.notifier).reload(),
|
||||
);
|
||||
}
|
||||
|
||||
void _applyStatusFilter(String label) {
|
||||
setState(() => _statusFilter = label);
|
||||
ref
|
||||
.read(inventoryListProvider.notifier)
|
||||
.setStatus(_statusParam[label] ?? '');
|
||||
}
|
||||
|
||||
// 成本仅管理员可见(与出库同口径):非管理员隐藏成本价/总价列、卡片单价、货值 KPI。
|
||||
bool get _isAdmin => ref.watch(isAdminProvider);
|
||||
bool _colAllowed(ColDef c) =>
|
||||
@@ -149,7 +175,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
DsMenuItem(value: s, label: s, selected: s == _statusFilter),
|
||||
],
|
||||
);
|
||||
if (v != null && mounted) setState(() => _statusFilter = v);
|
||||
if (v != null && mounted) _applyStatusFilter(v);
|
||||
}
|
||||
|
||||
// ── 多选漏斗(规格/系列/仓库) ──
|
||||
@@ -223,7 +249,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
selected: _statusFilter == s,
|
||||
caret: false,
|
||||
onTap: () {
|
||||
setState(() => _statusFilter = s);
|
||||
_applyStatusFilter(s);
|
||||
setSheet(() {});
|
||||
}),
|
||||
]),
|
||||
@@ -512,12 +538,12 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
return '¥$buf${parts.length > 1 ? '.${parts[1]}' : ''}';
|
||||
}
|
||||
|
||||
/// 状态徽章(DsBadge):派生 qty vs 安全库存 → 在售/预警/缺货。
|
||||
/// 状态徽章(DsBadge):三态落库 status 真相源。
|
||||
Widget _dsStatusBadge(Inventory item) {
|
||||
return switch (_statusOf(item)) {
|
||||
'缺货' => const DsBadge('缺货', tone: DsBadgeTone.danger),
|
||||
'预警' => const DsBadge('预警', tone: DsBadgeTone.warn),
|
||||
_ => const DsBadge('在售', tone: DsBadgeTone.ok),
|
||||
return switch (item.status) {
|
||||
'on_sale' => const DsBadge('在售', tone: DsBadgeTone.ok),
|
||||
'sold' => const DsBadge('已售', tone: DsBadgeTone.muted),
|
||||
_ => const DsBadge('库存', tone: DsBadgeTone.info),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -526,11 +552,12 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
Widget _inventoryCard(Inventory item) {
|
||||
final t = context.tokens;
|
||||
final status = _statusOf(item);
|
||||
final qtyColor = switch (status) {
|
||||
'缺货' => t.danger,
|
||||
'预警' => t.warn,
|
||||
_ => t.heading,
|
||||
};
|
||||
// 低库存数量染色(预警/缺货退出状态维度后由数量承担)
|
||||
final qtyColor = !_isLowStock(item)
|
||||
? t.heading
|
||||
: item.quantity == 0
|
||||
? t.danger
|
||||
: t.warn;
|
||||
final sub = [
|
||||
if (item.productCode.isNotEmpty) item.productCode,
|
||||
if (item.spec.isNotEmpty) item.spec,
|
||||
@@ -626,11 +653,11 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
?.map((s) => s.name)
|
||||
.toList() ??
|
||||
[];
|
||||
// 状态筛选已走服务端(setStatus);仓库仍为当前页客户端筛选
|
||||
final filteredItems = items
|
||||
.where((i) =>
|
||||
(_filterWarehouse.isEmpty ||
|
||||
_filterWarehouse.contains(i.warehouseName)) &&
|
||||
(_statusFilter == '全部' || _statusOf(i) == _statusFilter))
|
||||
_filterWarehouse.isEmpty ||
|
||||
_filterWarehouse.contains(i.warehouseName))
|
||||
.toList();
|
||||
|
||||
final hidden = _hiddenCols ?? <String>{};
|
||||
@@ -739,12 +766,12 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
deltaTone: _mTone(qtyTone),
|
||||
),
|
||||
MKpiItem(
|
||||
label: '缺货预警',
|
||||
value: '${summary?.shortageCount ?? 0}',
|
||||
label: '已售',
|
||||
value: '${summary?.soldCount ?? 0}',
|
||||
delta: '需补货 ${summary?.warningCount ?? 0} 项',
|
||||
deltaTone: MKpiDeltaTone.warn,
|
||||
selected: _statusFilter == '缺货',
|
||||
onTap: () => setState(() => _statusFilter = '缺货'),
|
||||
selected: _statusFilter == '已售',
|
||||
onTap: () => _applyStatusFilter('已售'),
|
||||
),
|
||||
]),
|
||||
);
|
||||
@@ -780,15 +807,15 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
deltaTone: qtyTone,
|
||||
),
|
||||
DsKpi(
|
||||
title: '缺货预警 · 点击筛选',
|
||||
value: '${summary?.shortageCount ?? 0}',
|
||||
icon: LucideIcons.triangleAlert,
|
||||
title: '已售 · 点击筛选',
|
||||
value: '${summary?.soldCount ?? 0}',
|
||||
icon: LucideIcons.check,
|
||||
tone: DsKpiTone.alert,
|
||||
delta: '需补货 ${summary?.warningCount ?? 0} 项',
|
||||
deltaTone: (summary?.warningCount ?? 0) > 0
|
||||
? DsKpiDelta.down
|
||||
: DsKpiDelta.neutral,
|
||||
onTap: () => setState(() => _statusFilter = '缺货'),
|
||||
onTap: () => _applyStatusFilter('已售'),
|
||||
),
|
||||
];
|
||||
final row = <Widget>[];
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// widgets/ds/ds_switch.dart — 原型 atoms.css .switch 的 Flutter 对照(2026-07-07 登记)。
|
||||
// 38×22 pill 轨道 + 18px 圆点滑块:on=primary / off=border / disabled 45% 透明。
|
||||
// 用于布尔即时生效场景(库存⇄在售挂售切换);不同于 Material Switch 的尺寸与配色。
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/theme/context_tokens.dart';
|
||||
|
||||
class DsSwitch extends StatelessWidget {
|
||||
final bool value;
|
||||
final ValueChanged<bool>? onChanged; // null = disabled
|
||||
const DsSwitch({super.key, required this.value, this.onChanged});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.tokens;
|
||||
final enabled = onChanged != null;
|
||||
return Semantics(
|
||||
toggled: value,
|
||||
button: true,
|
||||
child: GestureDetector(
|
||||
onTap: enabled ? () => onChanged!(!value) : null,
|
||||
child: Opacity(
|
||||
opacity: enabled ? 1 : .45,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 180),
|
||||
width: 38,
|
||||
height: 22,
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
color: value ? t.primary : t.border,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: AnimatedAlign(
|
||||
duration: const Duration(milliseconds: 180),
|
||||
curve: Curves.easeOut,
|
||||
alignment: value ? Alignment.centerRight : Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 18,
|
||||
height: 18,
|
||||
decoration: BoxDecoration(
|
||||
color: t.surface,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: t.shadow, blurRadius: 3, offset: const Offset(0, 1)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,10 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
|
||||
const Map<String, IconData> kStatusIcons = {
|
||||
// 库存状态
|
||||
// 库存状态(三态落库 2026-07-07:在售/库存/已售;预警/缺货为数量派生提示)
|
||||
'在售': LucideIcons.shoppingCart, // i-cart
|
||||
'库存': LucideIcons.box, // i-box
|
||||
'已售': LucideIcons.check, // i-check
|
||||
'预警': LucideIcons.triangleAlert, // i-alert
|
||||
'缺货': LucideIcons.x, // i-close
|
||||
// 单据状态
|
||||
|
||||
@@ -20,10 +20,12 @@ import '../core/theme/app_dims.g.dart';
|
||||
import '../core/theme/context_tokens.dart';
|
||||
import '../models/product.dart';
|
||||
import '../models/product_image.dart';
|
||||
import '../providers/inventory_provider.dart' show inventoryRepositoryProvider;
|
||||
import '../providers/product_option_provider.dart';
|
||||
import '../providers/product_provider.dart';
|
||||
import '../providers/shop_provider.dart' show shopInfoProvider;
|
||||
import 'ds/ds_atoms.dart';
|
||||
import 'ds/ds_switch.dart';
|
||||
import 'ds/grid_combo_cell.dart';
|
||||
import 'ds/m_sheet.dart';
|
||||
import 'searchable_option_field.dart';
|
||||
@@ -41,7 +43,10 @@ Future<void> showProductEditorDrawer(
|
||||
double? qty,
|
||||
String? unit,
|
||||
double? cost,
|
||||
String? status, // 在售 / 预警 / 缺货
|
||||
String? status, // 在售 / 库存 / 已售(三态显示标签)
|
||||
int? inventoryId, // 库存行 id:非空时状态行显示挂售 switch
|
||||
String? inventoryStatus, // stock / on_sale / sold(switch 初始态)
|
||||
VoidCallback? onStatusChanged, // switch 切换成功后回调(列表刷新)
|
||||
}) {
|
||||
// 窄屏:底部 sheet 形态(原型 m-inventory openItem 内嵌公开页编辑)
|
||||
if (context.isMobile) {
|
||||
@@ -56,7 +61,10 @@ Future<void> showProductEditorDrawer(
|
||||
qty: qty,
|
||||
unit: unit,
|
||||
cost: cost,
|
||||
status: status),
|
||||
status: status,
|
||||
inventoryId: inventoryId,
|
||||
inventoryStatus: inventoryStatus,
|
||||
onStatusChanged: onStatusChanged),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -83,7 +91,10 @@ Future<void> showProductEditorDrawer(
|
||||
qty: qty,
|
||||
unit: unit,
|
||||
cost: cost,
|
||||
status: status),
|
||||
status: status,
|
||||
inventoryId: inventoryId,
|
||||
inventoryStatus: inventoryStatus,
|
||||
onStatusChanged: onStatusChanged),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -106,12 +117,18 @@ class _ProductEditorDrawer extends ConsumerStatefulWidget {
|
||||
final String? unit;
|
||||
final double? cost;
|
||||
final String? status;
|
||||
final int? inventoryId;
|
||||
final String? inventoryStatus;
|
||||
final VoidCallback? onStatusChanged;
|
||||
const _ProductEditorDrawer({
|
||||
required this.productId,
|
||||
this.qty,
|
||||
this.unit,
|
||||
this.cost,
|
||||
this.status,
|
||||
this.inventoryId,
|
||||
this.inventoryStatus,
|
||||
this.onStatusChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -125,6 +142,32 @@ class _ProductEditorDrawerState extends ConsumerState<_ProductEditorDrawer> {
|
||||
bool _uploading = false;
|
||||
bool _saving = false;
|
||||
|
||||
// 库存三态挂售 switch(stock⇄on_sale;sold 只读无 switch)
|
||||
late String? _invStatus = widget.inventoryStatus;
|
||||
bool _statusSaving = false;
|
||||
|
||||
Future<void> _toggleOnSale(bool on) async {
|
||||
final id = widget.inventoryId;
|
||||
if (id == null || _statusSaving) return;
|
||||
final next = on ? 'on_sale' : 'stock';
|
||||
final prev = _invStatus;
|
||||
setState(() {
|
||||
_invStatus = next;
|
||||
_statusSaving = true;
|
||||
});
|
||||
try {
|
||||
await ref.read(inventoryRepositoryProvider).updateStatus(id, next);
|
||||
widget.onStatusChanged?.call();
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() => _invStatus = prev);
|
||||
showDsToast(context, '状态更新失败:$e', bg: context.tokens.danger);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _statusSaving = false);
|
||||
}
|
||||
}
|
||||
|
||||
// 介绍选择态(.pe-pick / .pe-text)
|
||||
String? _introTitle; // 已选介绍库标题;null → 占位「从介绍库选择…」
|
||||
int? _introDocId;
|
||||
@@ -366,15 +409,40 @@ class _ProductEditorDrawerState extends ConsumerState<_ProductEditorDrawer> {
|
||||
rows.add(('总成本价', b(_fmtMoney(widget.qty! * cost))));
|
||||
}
|
||||
}
|
||||
if ((widget.status ?? '').isNotEmpty) {
|
||||
// 状态行:三态徽章 + 挂售 switch(在售⇄库存 即时生效;已售只读;readonly 禁用)
|
||||
final invStatus = _invStatus;
|
||||
final statusLabel = switch (invStatus) {
|
||||
'on_sale' => '在售',
|
||||
'sold' => '已售',
|
||||
'stock' => '库存',
|
||||
_ => widget.status ?? '',
|
||||
};
|
||||
if (statusLabel.isNotEmpty) {
|
||||
final badge = DsBadge(statusLabel,
|
||||
tone: switch (statusLabel) {
|
||||
'在售' => DsBadgeTone.ok,
|
||||
'已售' => DsBadgeTone.muted,
|
||||
'库存' => DsBadgeTone.info,
|
||||
'缺货' => DsBadgeTone.danger,
|
||||
'预警' => DsBadgeTone.warn,
|
||||
_ => DsBadgeTone.ok,
|
||||
});
|
||||
final canToggle = widget.inventoryId != null &&
|
||||
invStatus != null &&
|
||||
invStatus != 'sold' &&
|
||||
!ref.watch(isReadonlyProvider);
|
||||
rows.add((
|
||||
'状态',
|
||||
DsBadge(widget.status!,
|
||||
tone: switch (widget.status) {
|
||||
'缺货' => DsBadgeTone.danger,
|
||||
'预警' => DsBadgeTone.warn,
|
||||
_ => DsBadgeTone.ok,
|
||||
})
|
||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
badge,
|
||||
if (widget.inventoryId != null && invStatus != null && invStatus != 'sold') ...[
|
||||
const SizedBox(width: 10),
|
||||
DsSwitch(
|
||||
value: invStatus == 'on_sale',
|
||||
onChanged: canToggle && !_statusSaving ? _toggleOnSale : null,
|
||||
),
|
||||
],
|
||||
])
|
||||
));
|
||||
}
|
||||
return Column(
|
||||
|
||||
|
Before Width: | Height: | Size: 420 KiB After Width: | Height: | Size: 416 KiB |
|
Before Width: | Height: | Size: 414 KiB After Width: | Height: | Size: 410 KiB |
|
Before Width: | Height: | Size: 422 KiB After Width: | Height: | Size: 417 KiB |
|
Before Width: | Height: | Size: 213 KiB After Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 142 KiB |
@@ -27,6 +27,7 @@ const _items = [
|
||||
id: 1,
|
||||
productId: 1,
|
||||
quantity: 128,
|
||||
status: 'on_sale',
|
||||
productCode: 'MT-FT-500',
|
||||
productName: '茅台 飞天 53°',
|
||||
series: '飞天',
|
||||
@@ -153,6 +154,7 @@ const _items = [
|
||||
id: 8,
|
||||
productId: 8,
|
||||
quantity: 0,
|
||||
status: 'sold',
|
||||
productCode: 'FJ-QH20-500',
|
||||
productName: '汾酒 青花 20',
|
||||
series: '青花20',
|
||||
@@ -249,6 +251,7 @@ List<Override> _overrides() => [
|
||||
stockValue: 2640000,
|
||||
inStockQty: 18920,
|
||||
shortageCount: 17,
|
||||
soldCount: 17,
|
||||
warningCount: 6,
|
||||
// 月初快照 → 环比 ▲2.3% / ▲1.1% / ▼0.6%(对齐原型示例文案)
|
||||
lastMonthSku: 1255,
|
||||
|
||||
@@ -22,6 +22,7 @@ const _items = [
|
||||
id: 1,
|
||||
productId: 1,
|
||||
quantity: 128,
|
||||
status: 'on_sale',
|
||||
productCode: 'MT-FT-500',
|
||||
productName: '茅台 飞天 53°',
|
||||
series: '飞天',
|
||||
@@ -76,6 +77,7 @@ const _items = [
|
||||
id: 8,
|
||||
productId: 8,
|
||||
quantity: 0,
|
||||
status: 'sold',
|
||||
productCode: 'FJ-QH20-500',
|
||||
productName: '汾酒 青花 20',
|
||||
series: '青花20',
|
||||
@@ -172,6 +174,7 @@ List<Override> _overrides() => [
|
||||
stockValue: 2640000,
|
||||
inStockQty: 18920,
|
||||
shortageCount: 17,
|
||||
soldCount: 17,
|
||||
warningCount: 6,
|
||||
lastMonthSku: 1255,
|
||||
lastMonthValue: 2611276,
|
||||
|
||||
@@ -51,6 +51,7 @@ class _FakeInvRepo implements InventoryRepository {
|
||||
String? code,
|
||||
List<String>? series,
|
||||
List<String>? spec,
|
||||
String? status,
|
||||
int page = 1,
|
||||
int pageSize = 50,
|
||||
}) async =>
|
||||
|
||||
@@ -99,6 +99,7 @@ class _FakeInventoryRepository extends InventoryRepository {
|
||||
String? code,
|
||||
List<String>? series,
|
||||
List<String>? spec,
|
||||
String? status,
|
||||
int page = 1,
|
||||
int pageSize = 50,
|
||||
}) async {
|
||||
|
||||