feat(client): 库存卡片显示单价(进价)+ 成本按角色隐藏
- 手机库存卡副行追加进价(编号 · 规格 · ¥xxx),仅管理员可见 - 宽屏成本价/总价列、货值 KPI、商品抽屉成本行对非管理员隐藏(isAdminProvider) - m-inventory 原型同步 + 移动 golden ×6 重录(管理员形态基准) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
@@ -57,7 +57,7 @@ function render(){
|
||||
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((it,i)=>`<a class="m-card" onclick="openItem(${ITEMS.indexOf(it)})">
|
||||
<div class="m-row">
|
||||
<div class="mc-main"><div class="mc-nm">${it.name}</div><div class="mc-sub">${it.code} · ${it.spec}</div></div>
|
||||
<div class="mc-main"><div class="mc-nm">${it.name}</div><div class="mc-sub">${it.code} · ${it.spec} · ${it.cost}</div></div><!-- 单价=成本进价,仅管理员可见(2026-07-06) -->
|
||||
<div class="mc-right"><span class="badge b-${it.status}">${it.status}</span><span class="mc-amt"><span class="${it.qty<=10?'qty-low':''}">${it.qty}</span> ${it.unit}</span></div>
|
||||
<div class="mc-chev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import '../../core/responsive/responsive.dart';
|
||||
import '../../core/theme/context_tokens.dart';
|
||||
import '../../core/theme/app_dims.g.dart';
|
||||
import '../../core/auth/auth_state.dart';
|
||||
import '../../core/storage/column_prefs.dart';
|
||||
import '../../core/utils/export_util.dart';
|
||||
import '../../models/inventory.dart';
|
||||
@@ -114,12 +115,17 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// 成本仅管理员可见(与出库同口径):非管理员隐藏成本价/总价列、卡片单价、货值 KPI。
|
||||
bool get _isAdmin => ref.watch(isAdminProvider);
|
||||
bool _colAllowed(ColDef c) =>
|
||||
_isAdmin || (c.key != 'cost' && c.key != 'price');
|
||||
|
||||
// ── 列设置(原型 colBtn → openColMenu:多选保持打开) ──
|
||||
void _openColMenu(BuildContext anchorContext) {
|
||||
showDsMultiMenu<String>(
|
||||
anchorContext,
|
||||
itemsBuilder: () => [
|
||||
for (final c in _colDefs.where((c) => !c.required))
|
||||
for (final c in _colDefs.where((c) => !c.required && _colAllowed(c)))
|
||||
DsMenuItem(
|
||||
value: c.key,
|
||||
label: c.label,
|
||||
@@ -528,6 +534,8 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
final sub = [
|
||||
if (item.productCode.isNotEmpty) item.productCode,
|
||||
if (item.spec.isNotEmpty) item.spec,
|
||||
// 单价(成本进价)仅管理员可见(2026-07-06 用户拍板)
|
||||
if (_isAdmin && item.unitPrice != null) _fmtCost(item.unitPrice!),
|
||||
].join(' · ');
|
||||
return MCard(
|
||||
onTap: item.productId != null ? () => _openEditor(item) : null,
|
||||
@@ -626,8 +634,9 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
.toList();
|
||||
|
||||
final hidden = _hiddenCols ?? <String>{};
|
||||
final visibleCols =
|
||||
_colDefs.where((c) => !hidden.contains(c.key)).toList();
|
||||
final visibleCols = _colDefs
|
||||
.where((c) => !hidden.contains(c.key) && _colAllowed(c))
|
||||
.toList();
|
||||
|
||||
final mobile = context.isMobile;
|
||||
// 原型 .main{padding:22px 26px}:桌面整页留白统一在此;窄屏保持紧凑。
|
||||
@@ -715,7 +724,8 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
),
|
||||
MKpiItem(
|
||||
label: '库存货值',
|
||||
value: summary != null
|
||||
// 货值=成本口径,非管理员后端抹零,前端显示占位
|
||||
value: _isAdmin && summary != null
|
||||
? yuanWan(summary.stockValue)
|
||||
: '—',
|
||||
delta: _mDeltaText(valDelta, valTone),
|
||||
@@ -752,8 +762,9 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
),
|
||||
DsKpi(
|
||||
title: '库存货值',
|
||||
value:
|
||||
summary != null ? yuanWan(summary.stockValue) : '—',
|
||||
value: _isAdmin && summary != null
|
||||
? yuanWan(summary.stockValue)
|
||||
: '—',
|
||||
icon: LucideIcons.database,
|
||||
tone: DsKpiTone.ok,
|
||||
delta: valDelta,
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
|
||||
import '../core/auth/auth_state.dart';
|
||||
import '../core/config/app_config.dart';
|
||||
import '../core/utils/dialog_util.dart';
|
||||
import '../core/responsive/responsive.dart';
|
||||
@@ -356,10 +357,14 @@ class _ProductEditorDrawerState extends ConsumerState<_ProductEditorDrawer> {
|
||||
b('${_fmtQty(widget.qty!)} ${widget.unit ?? p.unit}'.trim())
|
||||
));
|
||||
}
|
||||
// 成本仅管理员可见(与库存列表/出库同口径);p.purchasePrice 兜底也要一并门控
|
||||
final isAdmin = ref.watch(isAdminProvider);
|
||||
final cost = widget.cost ?? p.purchasePrice;
|
||||
if (cost != null && cost > 0) rows.add(('成本价(单瓶)', b(_fmtMoney(cost))));
|
||||
if (widget.qty != null && cost != null && cost > 0) {
|
||||
rows.add(('总成本价', b(_fmtMoney(widget.qty! * cost))));
|
||||
if (isAdmin && cost != null && cost > 0) {
|
||||
rows.add(('成本价(单瓶)', b(_fmtMoney(cost))));
|
||||
if (widget.qty != null) {
|
||||
rows.add(('总成本价', b(_fmtMoney(widget.qty! * cost))));
|
||||
}
|
||||
}
|
||||
if ((widget.status ?? '').isNotEmpty) {
|
||||
rows.add((
|
||||
|
||||
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 145 KiB |
@@ -242,6 +242,8 @@ List<Override> _overrides() => [
|
||||
productSeriesListProvider.overrideWith(() => _FakeSeriesNotifier()),
|
||||
productSpecListProvider.overrideWith(() => _FakeSpecNotifier()),
|
||||
isReadonlyProvider.overrideWithValue(false),
|
||||
// 成本列/货值/单价仅管理员可见——golden 基准取管理员形态
|
||||
isAdminProvider.overrideWithValue(true),
|
||||
inventorySummaryProvider.overrideWith((ref) => const InventorySummary(
|
||||
skuCount: 1284,
|
||||
stockValue: 2640000,
|
||||
|
||||
@@ -165,6 +165,8 @@ List<Override> _overrides() => [
|
||||
productSeriesListProvider.overrideWith(() => _FakeSeriesNotifier()),
|
||||
productSpecListProvider.overrideWith(() => _FakeSpecNotifier()),
|
||||
isReadonlyProvider.overrideWithValue(false),
|
||||
// 成本列/货值/单价仅管理员可见——golden 基准取管理员形态(卡片含单价)
|
||||
isAdminProvider.overrideWithValue(true),
|
||||
inventorySummaryProvider.overrideWith((ref) => const InventorySummary(
|
||||
skuCount: 1284,
|
||||
stockValue: 2640000,
|
||||
|
||||