Files
jiu/client/lib/providers/inventory_provider.dart
wangjia 904bfbe67d feat(client): Phase 2 库存屏照原型重建(去tab/KPI货值/库存·成本价·单价列)
照原型 inventory.html 把库存屏从「3 tab」重排为单视图:
- 去 3 tab(库存查询/预警/流水)→ 单视图;预警并入 KPI 缺货卡点击筛选;流水移商品详情
- 头部:库存管理 + 共 N 个 SKU·数据实时 + 列设置/导出(盘点/新增入库去掉,走专门菜单)
- KPI 4 卡走全店汇总接口:SKU总数/库存货值(Σqty×进价)/在库数量/缺货预警(可点筛选)
- 表格 12 列:加 库存(低库存染色)/成本价(进价)/单价(售价);操作列只「查看详情」;状态圆点
- 仓库/备注默认隐藏;工具栏 搜索+状态筛选+重置
- 后端 GET /inventory/summary 全店汇总(守 shop_id);前端 model/repository/provider 配套
- golden_harness:monospace 指向 Noto(消等宽数字黑块,全 golden 受益);库存屏存量 grey 清零

后端编译/库存测试通过;前端 analyze 0 error,flutter test 242 通过;代码端颜色闸 0 违规。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSKEiHsvauyxYUW2itzUXX
2026-06-25 20:40:54 +08:00

167 lines
4.1 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api/api_client.dart';
import '../core/auth/auth_state.dart';
import '../core/config/app_constants.dart';
import '../core/models/page_result.dart';
import '../models/inventory.dart';
import '../repositories/inventory_repository.dart';
import 'connectivity_provider.dart';
final inventoryRepositoryProvider = Provider<InventoryRepository>((ref) {
return InventoryRepository(ref.watch(apiClientProvider));
});
final inventoryListProvider =
AsyncNotifierProvider<InventoryListNotifier, PageResult<Inventory>>(
InventoryListNotifier.new,
);
/// 全店库存 KPI 汇总(不随分页;换店/网络恢复自动刷新)。
final inventorySummaryProvider =
FutureProvider.autoDispose<InventorySummary>((ref) {
ref.watch(authStateProvider.select((s) => s.user?.shopId));
ref.watch(networkRecoveryCountProvider);
return ref.read(inventoryRepositoryProvider).getSummary();
});
class InventoryListNotifier extends AsyncNotifier<PageResult<Inventory>> {
int _page = 1;
int _pageSize = AppConstants.defaultPageSize;
int? _warehouseId;
String _keyword = '';
List<String> _series = [];
List<String> _spec = [];
PageResult<Inventory>? _cache;
@override
Future<PageResult<Inventory>> build() async {
ref.watch(authStateProvider.select((s) => s.user?.shopId));
ref.watch(networkRecoveryCountProvider); // 网络恢复时自动刷新
try {
final result = await _fetch();
_cache = result;
return result;
} catch (_) {
if (_cache != null) return _cache!;
rethrow;
}
}
Future<PageResult<Inventory>> _fetch() {
return ref.read(inventoryRepositoryProvider).listInventory(
warehouseId: _warehouseId,
keyword: _keyword.isEmpty ? null : _keyword,
series: _series.isEmpty ? null : _series,
spec: _spec.isEmpty ? null : _spec,
page: _page,
pageSize: _pageSize,
);
}
void setPage(int page) {
_page = page;
reload();
}
void setPageSize(int pageSize) {
_pageSize = pageSize;
_page = 1;
reload();
}
void setWarehouseId(int? id) {
_warehouseId = id;
_page = 1;
reload();
}
void setKeyword(String keyword) {
_keyword = keyword;
_page = 1;
reload();
}
void setSeries(List<String> series) {
_series = series;
_page = 1;
reload();
}
void setSpec(List<String> spec) {
_spec = spec;
_page = 1;
reload();
}
void reload() {
state = const AsyncValue.loading();
_fetch().then((result) {
_cache = result;
state = AsyncValue.data(result);
}, onError: (e, st) {
if (_cache != null) {
state = AsyncValue.data(_cache!);
} else {
state = AsyncValue.error(e, st);
}
});
}
}
final inventoryLogProvider =
AsyncNotifierProvider<InventoryLogNotifier, PageResult<InventoryLog>>(
InventoryLogNotifier.new,
);
class InventoryLogNotifier extends AsyncNotifier<PageResult<InventoryLog>> {
int _page = 1;
int _pageSize = AppConstants.defaultPageSize;
PageResult<InventoryLog>? _cache;
@override
Future<PageResult<InventoryLog>> build() async {
ref.watch(authStateProvider.select((s) => s.user?.shopId));
ref.watch(networkRecoveryCountProvider);
try {
final result = await _fetch();
_cache = result;
return result;
} catch (_) {
if (_cache != null) return _cache!;
rethrow;
}
}
Future<PageResult<InventoryLog>> _fetch() {
return ref.read(inventoryRepositoryProvider).listLogs(
page: _page,
pageSize: _pageSize,
);
}
void setPage(int page) {
_page = page;
reload();
}
void setPageSize(int pageSize) {
_pageSize = pageSize;
_page = 1;
reload();
}
void reload() {
state = const AsyncValue.loading();
_fetch().then((result) {
_cache = result;
state = AsyncValue.data(result);
}, onError: (e, st) {
if (_cache != null) {
state = AsyncValue.data(_cache!);
} else {
state = AsyncValue.error(e, st);
}
});
}
}