diff --git a/CHANGELOG-client.md b/CHANGELOG-client.md index b958e73..3b6a7a3 100644 --- a/CHANGELOG-client.md +++ b/CHANGELOG-client.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.64] - 2026-06-20 + +### 新功能 +- 入库单 / 出库单列表新增搜索框:输入单号或往来单位名称即可即时筛选,不用再翻页查找 +- 入库 / 出库 / 库存三个列表均新增刷新按钮:多端并发录入后可一键同步最新数据 + ## [1.0.63] - 2026-06-20 ### 修复 diff --git a/client/lib/providers/stock_in_provider.dart b/client/lib/providers/stock_in_provider.dart index 2d08713..fb65533 100644 --- a/client/lib/providers/stock_in_provider.dart +++ b/client/lib/providers/stock_in_provider.dart @@ -23,6 +23,7 @@ class StockInListNotifier extends AsyncNotifier> { String _status = ''; String? _startDate; String? _endDate; + String _keyword = ''; PageResult? _cache; @override @@ -44,6 +45,7 @@ class StockInListNotifier extends AsyncNotifier> { status: _status.isEmpty ? null : _status, startDate: _startDate, endDate: _endDate, + keyword: _keyword.isEmpty ? null : _keyword, page: _page, pageSize: _pageSize, ); @@ -76,6 +78,12 @@ class StockInListNotifier extends AsyncNotifier> { reload(); } + void setKeyword(String keyword) { + _keyword = keyword; + _page = 1; + reload(); + } + void reload() { state = const AsyncValue.loading(); _fetch().then((result) { diff --git a/client/lib/providers/stock_out_provider.dart b/client/lib/providers/stock_out_provider.dart index a062001..3962b56 100644 --- a/client/lib/providers/stock_out_provider.dart +++ b/client/lib/providers/stock_out_provider.dart @@ -23,6 +23,7 @@ class StockOutListNotifier extends AsyncNotifier> { String _status = ''; String? _startDate; String? _endDate; + String _keyword = ''; PageResult? _cache; @override @@ -44,6 +45,7 @@ class StockOutListNotifier extends AsyncNotifier> { status: _status.isEmpty ? null : _status, startDate: _startDate, endDate: _endDate, + keyword: _keyword.isEmpty ? null : _keyword, page: _page, pageSize: _pageSize, ); @@ -76,6 +78,12 @@ class StockOutListNotifier extends AsyncNotifier> { reload(); } + void setKeyword(String keyword) { + _keyword = keyword; + _page = 1; + reload(); + } + void reload() { state = const AsyncValue.loading(); _fetch().then((result) { diff --git a/client/lib/repositories/stock_in_repository.dart b/client/lib/repositories/stock_in_repository.dart index 236b142..5fc5d18 100644 --- a/client/lib/repositories/stock_in_repository.dart +++ b/client/lib/repositories/stock_in_repository.dart @@ -13,6 +13,7 @@ class StockInRepository { String? status, String? startDate, String? endDate, + String? keyword, int page = 1, int pageSize = 20, }) async { @@ -23,6 +24,7 @@ class StockInRepository { if (status != null && status.isNotEmpty) 'status': status, if (startDate != null) 'start_date': startDate, if (endDate != null) 'end_date': endDate, + if (keyword != null && keyword.isNotEmpty) 'keyword': keyword, }; final resp = await _client.get('/stock-in/orders', params: params); return PageResult.fromJson( diff --git a/client/lib/repositories/stock_out_repository.dart b/client/lib/repositories/stock_out_repository.dart index 3958da7..c5b1a56 100644 --- a/client/lib/repositories/stock_out_repository.dart +++ b/client/lib/repositories/stock_out_repository.dart @@ -13,6 +13,7 @@ class StockOutRepository { String? status, String? startDate, String? endDate, + String? keyword, int page = 1, int pageSize = 20, }) async { @@ -23,6 +24,7 @@ class StockOutRepository { if (status != null && status.isNotEmpty) 'status': status, if (startDate != null) 'start_date': startDate, if (endDate != null) 'end_date': endDate, + if (keyword != null && keyword.isNotEmpty) 'keyword': keyword, }; final resp = await _client.get('/stock-out/orders', params: params); return PageResult.fromJson( diff --git a/client/lib/screens/inventory/inventory_list_screen.dart b/client/lib/screens/inventory/inventory_list_screen.dart index 6c23c5f..0b845d4 100644 --- a/client/lib/screens/inventory/inventory_list_screen.dart +++ b/client/lib/screens/inventory/inventory_list_screen.dart @@ -677,6 +677,13 @@ class _InventoryListScreenState extends ConsumerState { ColumnPrefs.save(_screenId, v); }, ), + IconButton( + tooltip: '刷新', + icon: const Icon(Icons.refresh, size: 20), + onPressed: () => ref + .read(inventoryListProvider.notifier) + .reload(), + ), ], ), ], @@ -712,6 +719,13 @@ class _InventoryListScreenState extends ConsumerState { ColumnPrefs.save(_screenId, v); }, ), + const SizedBox(width: 8), + OutlinedButton.icon( + onPressed: () => + ref.read(inventoryListProvider.notifier).reload(), + icon: const Icon(Icons.refresh, size: 16), + label: const Text('刷新'), + ), const Spacer(), ], ); 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 d69cba3..abd9299 100644 --- a/client/lib/screens/stock_in/stock_in_list_screen.dart +++ b/client/lib/screens/stock_in/stock_in_list_screen.dart @@ -39,6 +39,7 @@ class _StockInListScreenState extends ConsumerState { Set _filterWarehouse = {}; Set _filterSupplier = {}; Set? _hiddenCols; // null = 尚未载入本地存档(回退到 minWidth 首次默认) + final _searchCtrl = TextEditingController(); static const _screenId = 'stock_in_list'; @@ -71,6 +72,15 @@ class _StockInListScreenState extends ConsumerState { }); } + @override + void dispose() { + _searchCtrl.dispose(); + super.dispose(); + } + + void _triggerSearch() => + ref.read(stockInListProvider.notifier).setKeyword(_searchCtrl.text.trim()); + String? get _startDate => _dateRange != null ? '${_dateRange!.start.year}-${_dateRange!.start.month.toString().padLeft(2, '0')}-${_dateRange!.start.day.toString().padLeft(2, '0')}' : null; @@ -352,6 +362,27 @@ class _StockInListScreenState extends ConsumerState { ) : null; + final searchField = TextField( + controller: _searchCtrl, + decoration: InputDecoration( + hintText: '单号/往来单位,回车搜索', + prefixIcon: const Icon(Icons.search, size: 16), + hintStyle: const TextStyle(fontSize: 12), + suffixIcon: IconButton( + icon: const Icon(Icons.search, size: 16), + tooltip: '搜索', + onPressed: _triggerSearch, + ), + ), + onSubmitted: (_) => _triggerSearch(), + ); + + final refreshBtn = IconButton( + tooltip: '刷新', + icon: const Icon(Icons.refresh, size: 20), + onPressed: () => ref.read(stockInListProvider.notifier).reload(), + ); + final dateBtn = OutlinedButton.icon( onPressed: _pickDateRange, icon: const Icon(Icons.date_range, size: 16), @@ -374,28 +405,36 @@ class _StockInListScreenState extends ConsumerState { : null; if (isMobile) { - return Wrap( - spacing: 8, - runSpacing: 4, - crossAxisAlignment: WrapCrossAlignment.center, + return Column( + mainAxisSize: MainAxisSize.min, children: [ - if (newBtn != null) newBtn, - if (statusFilter != null) statusFilter, - dateBtn, - if (clearDate != null) clearDate, - IconButton( - tooltip: '导出', - icon: const Icon(Icons.download, size: 20), - onPressed: doExport, - ), - ColumnToggleButton( - columns: _colDefs, - hidden: hidden, - compact: true, - onChanged: (v) { - setState(() => _hiddenCols = v); - ColumnPrefs.save(_screenId, v); - }, + searchField, + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 4, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (newBtn != null) newBtn, + if (statusFilter != null) statusFilter, + dateBtn, + if (clearDate != null) clearDate, + IconButton( + tooltip: '导出', + icon: const Icon(Icons.download, size: 20), + onPressed: doExport, + ), + ColumnToggleButton( + columns: _colDefs, + hidden: hidden, + compact: true, + onChanged: (v) { + setState(() => _hiddenCols = v); + ColumnPrefs.save(_screenId, v); + }, + ), + refreshBtn, + ], ), ], ); @@ -403,6 +442,8 @@ class _StockInListScreenState extends ConsumerState { return Row( children: [ + SizedBox(width: 220, child: searchField), + const SizedBox(width: 12), if (newBtn != null) newBtn, const Spacer(), if (statusFilter != null) ...[ @@ -429,6 +470,7 @@ class _StockInListScreenState extends ConsumerState { ColumnPrefs.save(_screenId, v); }, ), + refreshBtn, ], ); }), 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 5df1f71..56aa160 100644 --- a/client/lib/screens/stock_out/stock_out_list_screen.dart +++ b/client/lib/screens/stock_out/stock_out_list_screen.dart @@ -38,6 +38,7 @@ class _StockOutListScreenState extends ConsumerState { Set _filterWarehouse = {}; Set _filterCustomer = {}; Set? _hiddenCols; // null = 尚未载入本地存档(回退到 minWidth 首次默认) + final _searchCtrl = TextEditingController(); static const _screenId = 'stock_out_list'; @@ -71,6 +72,16 @@ class _StockOutListScreenState extends ConsumerState { ColDef('actions', '操作', required: true), ]; + @override + void dispose() { + _searchCtrl.dispose(); + super.dispose(); + } + + void _triggerSearch() => ref + .read(stockOutListProvider.notifier) + .setKeyword(_searchCtrl.text.trim()); + String? get _startDate => _dateRange != null ? '${_dateRange!.start.year}-${_dateRange!.start.month.toString().padLeft(2, '0')}-${_dateRange!.start.day.toString().padLeft(2, '0')}' : null; @@ -358,6 +369,27 @@ class _StockOutListScreenState extends ConsumerState { ) : null; + final searchField = TextField( + controller: _searchCtrl, + decoration: InputDecoration( + hintText: '单号/往来单位,回车搜索', + prefixIcon: const Icon(Icons.search, size: 16), + hintStyle: const TextStyle(fontSize: 12), + suffixIcon: IconButton( + icon: const Icon(Icons.search, size: 16), + tooltip: '搜索', + onPressed: _triggerSearch, + ), + ), + onSubmitted: (_) => _triggerSearch(), + ); + + final refreshBtn = IconButton( + tooltip: '刷新', + icon: const Icon(Icons.refresh, size: 20), + onPressed: () => ref.read(stockOutListProvider.notifier).reload(), + ); + final dateBtn = OutlinedButton.icon( onPressed: _pickDateRange, icon: const Icon(Icons.date_range, size: 16), @@ -380,28 +412,36 @@ class _StockOutListScreenState extends ConsumerState { : null; if (isMobile) { - return Wrap( - spacing: 8, - runSpacing: 4, - crossAxisAlignment: WrapCrossAlignment.center, + return Column( + mainAxisSize: MainAxisSize.min, children: [ - if (newBtn != null) newBtn, - if (statusFilter != null) statusFilter, - dateBtn, - if (clearDate != null) clearDate, - IconButton( - tooltip: '导出', - icon: const Icon(Icons.download, size: 20), - onPressed: doExport, - ), - ColumnToggleButton( - columns: _colDefs, - hidden: hidden, - compact: true, - onChanged: (v) { - setState(() => _hiddenCols = v); - ColumnPrefs.save(_screenId, v); - }, + searchField, + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 4, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (newBtn != null) newBtn, + if (statusFilter != null) statusFilter, + dateBtn, + if (clearDate != null) clearDate, + IconButton( + tooltip: '导出', + icon: const Icon(Icons.download, size: 20), + onPressed: doExport, + ), + ColumnToggleButton( + columns: _colDefs, + hidden: hidden, + compact: true, + onChanged: (v) { + setState(() => _hiddenCols = v); + ColumnPrefs.save(_screenId, v); + }, + ), + refreshBtn, + ], ), ], ); @@ -409,6 +449,8 @@ class _StockOutListScreenState extends ConsumerState { return Row( children: [ + SizedBox(width: 220, child: searchField), + const SizedBox(width: 12), if (newBtn != null) newBtn, const Spacer(), if (statusFilter != null) ...[ @@ -435,6 +477,7 @@ class _StockOutListScreenState extends ConsumerState { ColumnPrefs.save(_screenId, v); }, ), + refreshBtn, ], ); }),