diff --git a/client/lib/screens/finance/finance_screen.dart b/client/lib/screens/finance/finance_screen.dart index c578c2f..a088057 100644 --- a/client/lib/screens/finance/finance_screen.dart +++ b/client/lib/screens/finance/finance_screen.dart @@ -560,6 +560,11 @@ class _FinanceScreenState extends ConsumerState { shrinkWrap: true, emptyText: '没有匹配的流水记录', toolbar: toolbar, + // 下拉刷新 = 桌面「刷新」(_reload 连带 KPI/汇总/趋势) + onRefresh: () async { + _reload(); + await Future.delayed(const Duration(milliseconds: 600)); + }, mobileCards: flows.map((r) => _flowCard(t, r)).toList(), columns: const [ DsColumn('date', '日期'), diff --git a/client/lib/screens/inventory/inventory_list_screen.dart b/client/lib/screens/inventory/inventory_list_screen.dart index f5ff6cc..37c1b72 100644 --- a/client/lib/screens/inventory/inventory_list_screen.dart +++ b/client/lib/screens/inventory/inventory_list_screen.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../core/responsive/responsive.dart'; @@ -41,7 +40,6 @@ class _InventoryListScreenState extends ConsumerState { // 库存状态前端筛选(派生自 qty vs 安全库存):全部/在售/预警/缺货 String _statusFilter = '全部'; Set? _hiddenCols; // null = 尚未载入本地存档 - bool _mobileTableView = false; // 移动端:false=卡片列表,true=表格(横向滚动) static const _screenId = 'inventory_list'; static const _statusOptions = ['全部', '在售', '预警', '缺货']; @@ -812,7 +810,6 @@ class _InventoryListScreenState extends ConsumerState { ref.read(inventoryListProvider.notifier).setPageSize(s), toolbar: Builder(builder: (ctx) { final isMobile = ctx.isMobile; - void doExport() => _exportInventory(filteredItems); // 搜索合一(用户口径):名称/拼音/编码同框,回车触发 // (后端 keyword 本就 LIKE name/code/拼音/首字母)。 @@ -824,11 +821,10 @@ class _InventoryListScreenState extends ConsumerState { .setKeyword(v.trim()), ); - final canCheck = !WriteGuard.isReadonly(ref); - if (isMobile) { // 移动端:MSearchRow(原型 .m-search + 详搜钮)独占一行 - // + 紧凑图标操作行(含 表格/列表 切换) + //(盘点/导出/列设置/刷新工具行 2026-07-04 拍板移除, + // 刷新改下拉手势,盘点入口在「我的」) return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -842,62 +838,6 @@ class _InventoryListScreenState extends ConsumerState { onFilterTap: () => _openMobileFilterSheet( specOptions, seriesOptions), ), - const SizedBox(height: AppDims.sp2), - Row( - children: [ - if (canCheck) - WriteGuard( - child: OutlinedButton.icon( - onPressed: () => - context.go('/inventory/check'), - icon: const Icon( - LucideIcons.clipboardCheck, - size: 16), - label: const Text('盘点'), - style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 10, vertical: 6), - minimumSize: Size.zero, - tapTargetSize: - MaterialTapTargetSize.shrinkWrap, - ), - ), - ), - const Spacer(), - IconButton( - tooltip: _mobileTableView ? '卡片视图' : '表格视图', - icon: Icon( - _mobileTableView - ? LucideIcons.layoutList - : LucideIcons.rows3, - size: 20), - onPressed: () => setState(() => - _mobileTableView = !_mobileTableView), - ), - IconButton( - tooltip: '导出', - icon: const Icon(LucideIcons.download, - size: 20), - onPressed: doExport, - ), - Builder( - builder: (bctx) => IconButton( - tooltip: '列设置', - icon: const Icon(LucideIcons.alignLeft, - size: 20), - onPressed: () => _openColMenu(bctx), - ), - ), - IconButton( - tooltip: '刷新', - icon: const Icon(LucideIcons.refreshCw, - size: 20), - onPressed: () => ref - .read(inventoryListProvider.notifier) - .reload(), - ), - ], - ), ], ); } @@ -912,9 +852,13 @@ class _InventoryListScreenState extends ConsumerState { ], ); }), - mobileCards: _mobileTableView - ? null - : filteredItems.map(_inventoryCard).toList(), + mobileCards: filteredItems.map(_inventoryCard).toList(), + // 下拉刷新 = 桌面「刷新」(reload 保留旧内容叠进度) + onRefresh: () async { + ref.read(inventoryListProvider.notifier).reload(); + await Future.delayed( + const Duration(milliseconds: 600)); + }, emptyText: '没有匹配的商品 · 试试调整筛选或搜索', columns: visibleCols.map((c) { final Widget? header = switch (c.key) { diff --git a/client/lib/screens/partners/partners_screen.dart b/client/lib/screens/partners/partners_screen.dart index d2d2787..4138d85 100644 --- a/client/lib/screens/partners/partners_screen.dart +++ b/client/lib/screens/partners/partners_screen.dart @@ -147,6 +147,12 @@ class _PartnersScreenState extends ConsumerState { pagerInfoText: '共 ${result.total} 个往来单位', emptyText: '没有匹配的往来单位 · 试试调整筛选或搜索', toolbar: _toolbar(mobile, partners), + // 下拉刷新 = 桌面「刷新」 + onRefresh: () async { + ref.read(partnersScreenProvider.notifier).reload(); + ref.invalidate(partnerBalancesProvider); + await Future.delayed(const Duration(milliseconds: 600)); + }, mobileCards: partners.map((p) => _card(p, balances)).toList(), columns: const [ DsColumn('name', '名称'), diff --git a/client/lib/screens/products/products_screen.dart b/client/lib/screens/products/products_screen.dart index 3547ace..d38377b 100644 --- a/client/lib/screens/products/products_screen.dart +++ b/client/lib/screens/products/products_screen.dart @@ -114,6 +114,16 @@ class _ProductsScreenState extends ConsumerState { _ => '', }; + /// 下拉刷新(全 tab 通用):档案/介绍/系列/规格/仓库 一并重拉。 + Future _pullRefresh() async { + ref.read(productListProvider.notifier).reload(); + ref.invalidate(productDescriptionDocListProvider); + ref.read(productSeriesListProvider.notifier).reload(); + ref.read(productSpecListProvider.notifier).reload(); + ref.read(warehouseListProvider.notifier).reload(); + await Future.delayed(const Duration(milliseconds: 600)); + } + @override Widget build(BuildContext context) { final t = context.tokens; @@ -370,6 +380,7 @@ class _ProductsScreenState extends ConsumerState { .toList(), ), ), + onRefresh: _pullRefresh, mobileCards: items .map((p) => MobileListCard( onTap: () => showProductEditorDrawer(context, @@ -470,6 +481,7 @@ class _ProductsScreenState extends ConsumerState { .toList(), ), ), + onRefresh: _pullRefresh, mobileCards: filtered .map((it) => MobileListCard( title: Text(it.title), @@ -695,6 +707,7 @@ class _ProductsScreenState extends ConsumerState { onExport: onExport, mobileAdd: true, ), + onRefresh: _pullRefresh, mobileCards: filtered .map((r) => MobileListCard( title: Text(r.name), @@ -775,6 +788,7 @@ class _ProductsScreenState extends ConsumerState { onChanged: () => setState(() {}), mobileAdd: true, ), + onRefresh: _pullRefresh, mobileCards: filtered .map((w) => MobileListCard( title: Text(w.name), diff --git a/client/lib/screens/settings/users_screen.dart b/client/lib/screens/settings/users_screen.dart index 3c7f318..c4350c5 100644 --- a/client/lib/screens/settings/users_screen.dart +++ b/client/lib/screens/settings/users_screen.dart @@ -174,6 +174,8 @@ class _UsersScreenState extends ConsumerState { Expanded( child: DsTable( pagerInfoText: '共 ${filtered.length} 名成员', + // 下拉刷新 = 桌面「刷新」 + onRefresh: () => ref.read(userListProvider.notifier).reload(), emptyText: '没有匹配的成员', toolbar: _toolbar(mobile), columns: const [ diff --git a/client/lib/screens/stock_in/stock_in_list_screen.dart b/client/lib/screens/stock_in/stock_in_list_screen.dart index 2fed780..90beac1 100644 --- a/client/lib/screens/stock_in/stock_in_list_screen.dart +++ b/client/lib/screens/stock_in/stock_in_list_screen.dart @@ -411,6 +411,11 @@ class _StockInListScreenState extends ConsumerState { supplierOptions: supplierOptions, selectedSupplierId: selectedSupplierId, ), + // 下拉刷新 = 桌面「刷新」 + onRefresh: () async { + ref.read(stockInListProvider.notifier).reload(); + await Future.delayed(const Duration(milliseconds: 600)); + }, mobileCards: orders.map((o) => _orderCard(context, o)).toList(), columns: _colDefs diff --git a/client/lib/screens/stock_out/stock_out_list_screen.dart b/client/lib/screens/stock_out/stock_out_list_screen.dart index 6555c2a..c010fc5 100644 --- a/client/lib/screens/stock_out/stock_out_list_screen.dart +++ b/client/lib/screens/stock_out/stock_out_list_screen.dart @@ -406,6 +406,11 @@ class _StockOutListScreenState extends ConsumerState { ], )) .toList(), + // 下拉刷新 = 桌面「刷新」 + onRefresh: () async { + ref.read(stockOutListProvider.notifier).reload(); + await Future.delayed(const Duration(milliseconds: 600)); + }, mobileCards: orders.map((o) => _orderCard(context, o)).toList(), ), ), diff --git a/client/lib/widgets/ds/ds_table.dart b/client/lib/widgets/ds/ds_table.dart index cd9daa1..24ce13b 100644 --- a/client/lib/widgets/ds/ds_table.dart +++ b/client/lib/widgets/ds/ds_table.dart @@ -45,6 +45,9 @@ class DsTable extends StatefulWidget { // 仅文案分页(原型 partners/products 的 .pager 只有一行 span 文案,无翻页控件); // 与 total 互斥,total 为空且此值非空时生效。 final String? pagerInfoText; + + /// 窄屏卡片流下拉刷新(等价桌面工具栏「刷新」,2026-07-04 拍板);null 不启用。 + final Future Function()? onRefresh; // 内嵌收缩模式(多区块滚动页里的表格卡,如 devices/settings): // 不用 Expanded 撑满、表格不带内部滚动,高度随内容。 final bool shrinkWrap; @@ -62,6 +65,7 @@ class DsTable extends StatefulWidget { this.onPageSizeChanged, this.mobileCards, this.pagerInfoText, + this.onRefresh, this.shrinkWrap = false, }); @@ -146,13 +150,27 @@ class _DsTableState extends State { Widget _buildMobile(dynamic t) { final cards = widget.mobileCards!; - if (cards.isEmpty) return _empty(t); - return ListView.separated( - padding: const EdgeInsets.all(12), - itemCount: cards.length, - separatorBuilder: (_, __) => const SizedBox(height: 10), - itemBuilder: (_, i) => cards[i], - ); + final Widget list = cards.isEmpty + ? (widget.onRefresh == null + ? _empty(t) + // 空态也要可下拉:套一个总是可滚的 ListView + : ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: [ + Padding( + padding: const EdgeInsets.only(top: 60), + child: _empty(t)), + ], + )) + : ListView.separated( + padding: const EdgeInsets.all(12), + physics: const AlwaysScrollableScrollPhysics(), + itemCount: cards.length, + separatorBuilder: (_, __) => const SizedBox(height: 10), + itemBuilder: (_, i) => cards[i], + ); + if (widget.onRefresh == null) return list; + return RefreshIndicator(onRefresh: widget.onRefresh!, child: list); } /// 收缩版卡片流(外层页面自带滚动)。 diff --git a/client/test/golden/goldens/inventory_list_mobile_a.png b/client/test/golden/goldens/inventory_list_mobile_a.png index 81a26cd..9605953 100644 Binary files a/client/test/golden/goldens/inventory_list_mobile_a.png and b/client/test/golden/goldens/inventory_list_mobile_a.png differ diff --git a/client/test/golden/goldens/inventory_list_mobile_b.png b/client/test/golden/goldens/inventory_list_mobile_b.png index 7152ee8..8b049e1 100644 Binary files a/client/test/golden/goldens/inventory_list_mobile_b.png and b/client/test/golden/goldens/inventory_list_mobile_b.png differ diff --git a/client/test/golden/goldens/inventory_list_mobile_c.png b/client/test/golden/goldens/inventory_list_mobile_c.png index b9ec78f..5cb9c8d 100644 Binary files a/client/test/golden/goldens/inventory_list_mobile_c.png and b/client/test/golden/goldens/inventory_list_mobile_c.png differ diff --git a/client/test/golden/goldens/m_inventory_a.png b/client/test/golden/goldens/m_inventory_a.png index 79fb85c..2a4a3b8 100644 Binary files a/client/test/golden/goldens/m_inventory_a.png and b/client/test/golden/goldens/m_inventory_a.png differ diff --git a/client/test/golden/goldens/m_inventory_b.png b/client/test/golden/goldens/m_inventory_b.png index 26aa41d..c20fca9 100644 Binary files a/client/test/golden/goldens/m_inventory_b.png and b/client/test/golden/goldens/m_inventory_b.png differ diff --git a/client/test/golden/goldens/m_inventory_c.png b/client/test/golden/goldens/m_inventory_c.png index c45b919..202b7c1 100644 Binary files a/client/test/golden/goldens/m_inventory_c.png and b/client/test/golden/goldens/m_inventory_c.png differ