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; }
|
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)})">
|
L.innerHTML=rows.map((it,i)=>`<a class="m-card" onclick="openItem(${ITEMS.indexOf(it)})">
|
||||||
<div class="m-row">
|
<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-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 class="mc-chev"><svg viewBox="0 0 24 24"><use href="#i-chevron-right"/></svg></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:lucide_icons_flutter/lucide_icons.dart';
|
|||||||
import '../../core/responsive/responsive.dart';
|
import '../../core/responsive/responsive.dart';
|
||||||
import '../../core/theme/context_tokens.dart';
|
import '../../core/theme/context_tokens.dart';
|
||||||
import '../../core/theme/app_dims.g.dart';
|
import '../../core/theme/app_dims.g.dart';
|
||||||
|
import '../../core/auth/auth_state.dart';
|
||||||
import '../../core/storage/column_prefs.dart';
|
import '../../core/storage/column_prefs.dart';
|
||||||
import '../../core/utils/export_util.dart';
|
import '../../core/utils/export_util.dart';
|
||||||
import '../../models/inventory.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:多选保持打开) ──
|
// ── 列设置(原型 colBtn → openColMenu:多选保持打开) ──
|
||||||
void _openColMenu(BuildContext anchorContext) {
|
void _openColMenu(BuildContext anchorContext) {
|
||||||
showDsMultiMenu<String>(
|
showDsMultiMenu<String>(
|
||||||
anchorContext,
|
anchorContext,
|
||||||
itemsBuilder: () => [
|
itemsBuilder: () => [
|
||||||
for (final c in _colDefs.where((c) => !c.required))
|
for (final c in _colDefs.where((c) => !c.required && _colAllowed(c)))
|
||||||
DsMenuItem(
|
DsMenuItem(
|
||||||
value: c.key,
|
value: c.key,
|
||||||
label: c.label,
|
label: c.label,
|
||||||
@@ -528,6 +534,8 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
|||||||
final sub = [
|
final sub = [
|
||||||
if (item.productCode.isNotEmpty) item.productCode,
|
if (item.productCode.isNotEmpty) item.productCode,
|
||||||
if (item.spec.isNotEmpty) item.spec,
|
if (item.spec.isNotEmpty) item.spec,
|
||||||
|
// 单价(成本进价)仅管理员可见(2026-07-06 用户拍板)
|
||||||
|
if (_isAdmin && item.unitPrice != null) _fmtCost(item.unitPrice!),
|
||||||
].join(' · ');
|
].join(' · ');
|
||||||
return MCard(
|
return MCard(
|
||||||
onTap: item.productId != null ? () => _openEditor(item) : null,
|
onTap: item.productId != null ? () => _openEditor(item) : null,
|
||||||
@@ -626,8 +634,9 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final hidden = _hiddenCols ?? <String>{};
|
final hidden = _hiddenCols ?? <String>{};
|
||||||
final visibleCols =
|
final visibleCols = _colDefs
|
||||||
_colDefs.where((c) => !hidden.contains(c.key)).toList();
|
.where((c) => !hidden.contains(c.key) && _colAllowed(c))
|
||||||
|
.toList();
|
||||||
|
|
||||||
final mobile = context.isMobile;
|
final mobile = context.isMobile;
|
||||||
// 原型 .main{padding:22px 26px}:桌面整页留白统一在此;窄屏保持紧凑。
|
// 原型 .main{padding:22px 26px}:桌面整页留白统一在此;窄屏保持紧凑。
|
||||||
@@ -715,7 +724,8 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
|||||||
),
|
),
|
||||||
MKpiItem(
|
MKpiItem(
|
||||||
label: '库存货值',
|
label: '库存货值',
|
||||||
value: summary != null
|
// 货值=成本口径,非管理员后端抹零,前端显示占位
|
||||||
|
value: _isAdmin && summary != null
|
||||||
? yuanWan(summary.stockValue)
|
? yuanWan(summary.stockValue)
|
||||||
: '—',
|
: '—',
|
||||||
delta: _mDeltaText(valDelta, valTone),
|
delta: _mDeltaText(valDelta, valTone),
|
||||||
@@ -752,8 +762,9 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
|||||||
),
|
),
|
||||||
DsKpi(
|
DsKpi(
|
||||||
title: '库存货值',
|
title: '库存货值',
|
||||||
value:
|
value: _isAdmin && summary != null
|
||||||
summary != null ? yuanWan(summary.stockValue) : '—',
|
? yuanWan(summary.stockValue)
|
||||||
|
: '—',
|
||||||
icon: LucideIcons.database,
|
icon: LucideIcons.database,
|
||||||
tone: DsKpiTone.ok,
|
tone: DsKpiTone.ok,
|
||||||
delta: valDelta,
|
delta: valDelta,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||||
|
|
||||||
|
import '../core/auth/auth_state.dart';
|
||||||
import '../core/config/app_config.dart';
|
import '../core/config/app_config.dart';
|
||||||
import '../core/utils/dialog_util.dart';
|
import '../core/utils/dialog_util.dart';
|
||||||
import '../core/responsive/responsive.dart';
|
import '../core/responsive/responsive.dart';
|
||||||
@@ -356,10 +357,14 @@ class _ProductEditorDrawerState extends ConsumerState<_ProductEditorDrawer> {
|
|||||||
b('${_fmtQty(widget.qty!)} ${widget.unit ?? p.unit}'.trim())
|
b('${_fmtQty(widget.qty!)} ${widget.unit ?? p.unit}'.trim())
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
// 成本仅管理员可见(与库存列表/出库同口径);p.purchasePrice 兜底也要一并门控
|
||||||
|
final isAdmin = ref.watch(isAdminProvider);
|
||||||
final cost = widget.cost ?? p.purchasePrice;
|
final cost = widget.cost ?? p.purchasePrice;
|
||||||
if (cost != null && cost > 0) rows.add(('成本价(单瓶)', b(_fmtMoney(cost))));
|
if (isAdmin && cost != null && cost > 0) {
|
||||||
if (widget.qty != null && cost != null && cost > 0) {
|
rows.add(('成本价(单瓶)', b(_fmtMoney(cost))));
|
||||||
rows.add(('总成本价', b(_fmtMoney(widget.qty! * cost))));
|
if (widget.qty != null) {
|
||||||
|
rows.add(('总成本价', b(_fmtMoney(widget.qty! * cost))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ((widget.status ?? '').isNotEmpty) {
|
if ((widget.status ?? '').isNotEmpty) {
|
||||||
rows.add((
|
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()),
|
productSeriesListProvider.overrideWith(() => _FakeSeriesNotifier()),
|
||||||
productSpecListProvider.overrideWith(() => _FakeSpecNotifier()),
|
productSpecListProvider.overrideWith(() => _FakeSpecNotifier()),
|
||||||
isReadonlyProvider.overrideWithValue(false),
|
isReadonlyProvider.overrideWithValue(false),
|
||||||
|
// 成本列/货值/单价仅管理员可见——golden 基准取管理员形态
|
||||||
|
isAdminProvider.overrideWithValue(true),
|
||||||
inventorySummaryProvider.overrideWith((ref) => const InventorySummary(
|
inventorySummaryProvider.overrideWith((ref) => const InventorySummary(
|
||||||
skuCount: 1284,
|
skuCount: 1284,
|
||||||
stockValue: 2640000,
|
stockValue: 2640000,
|
||||||
|
|||||||
@@ -165,6 +165,8 @@ List<Override> _overrides() => [
|
|||||||
productSeriesListProvider.overrideWith(() => _FakeSeriesNotifier()),
|
productSeriesListProvider.overrideWith(() => _FakeSeriesNotifier()),
|
||||||
productSpecListProvider.overrideWith(() => _FakeSpecNotifier()),
|
productSpecListProvider.overrideWith(() => _FakeSpecNotifier()),
|
||||||
isReadonlyProvider.overrideWithValue(false),
|
isReadonlyProvider.overrideWithValue(false),
|
||||||
|
// 成本列/货值/单价仅管理员可见——golden 基准取管理员形态(卡片含单价)
|
||||||
|
isAdminProvider.overrideWithValue(true),
|
||||||
inventorySummaryProvider.overrideWith((ref) => const InventorySummary(
|
inventorySummaryProvider.overrideWith((ref) => const InventorySummary(
|
||||||
skuCount: 1284,
|
skuCount: 1284,
|
||||||
stockValue: 2640000,
|
stockValue: 2640000,
|
||||||
|
|||||||