Compare commits

...

1 Commits

Author SHA1 Message Date
wangjia 71ed15b40b feat(client): 入库/出库加搜索框,入库/出库/库存加刷新按钮
Deploy Client / build-client-web (push) Successful in 42s
Deploy Client / build-windows (push) Successful in 1m53s
Deploy Client / build-macos (push) Successful in 2m10s
Deploy Client / build-android (push) Successful in 1m15s
Deploy Client / build-ios (push) Successful in 2m28s
Deploy Client / release-deploy-client (push) Successful in 1m21s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZ4DskSRKsSiheQonFtQvx
2026-06-20 16:03:01 +08:00
8 changed files with 167 additions and 42 deletions
+6
View File
@@ -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/), 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). 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 ## [1.0.63] - 2026-06-20
### 修复 ### 修复
@@ -23,6 +23,7 @@ class StockInListNotifier extends AsyncNotifier<PageResult<StockInOrder>> {
String _status = ''; String _status = '';
String? _startDate; String? _startDate;
String? _endDate; String? _endDate;
String _keyword = '';
PageResult<StockInOrder>? _cache; PageResult<StockInOrder>? _cache;
@override @override
@@ -44,6 +45,7 @@ class StockInListNotifier extends AsyncNotifier<PageResult<StockInOrder>> {
status: _status.isEmpty ? null : _status, status: _status.isEmpty ? null : _status,
startDate: _startDate, startDate: _startDate,
endDate: _endDate, endDate: _endDate,
keyword: _keyword.isEmpty ? null : _keyword,
page: _page, page: _page,
pageSize: _pageSize, pageSize: _pageSize,
); );
@@ -76,6 +78,12 @@ class StockInListNotifier extends AsyncNotifier<PageResult<StockInOrder>> {
reload(); reload();
} }
void setKeyword(String keyword) {
_keyword = keyword;
_page = 1;
reload();
}
void reload() { void reload() {
state = const AsyncValue.loading(); state = const AsyncValue.loading();
_fetch().then((result) { _fetch().then((result) {
@@ -23,6 +23,7 @@ class StockOutListNotifier extends AsyncNotifier<PageResult<StockOutOrder>> {
String _status = ''; String _status = '';
String? _startDate; String? _startDate;
String? _endDate; String? _endDate;
String _keyword = '';
PageResult<StockOutOrder>? _cache; PageResult<StockOutOrder>? _cache;
@override @override
@@ -44,6 +45,7 @@ class StockOutListNotifier extends AsyncNotifier<PageResult<StockOutOrder>> {
status: _status.isEmpty ? null : _status, status: _status.isEmpty ? null : _status,
startDate: _startDate, startDate: _startDate,
endDate: _endDate, endDate: _endDate,
keyword: _keyword.isEmpty ? null : _keyword,
page: _page, page: _page,
pageSize: _pageSize, pageSize: _pageSize,
); );
@@ -76,6 +78,12 @@ class StockOutListNotifier extends AsyncNotifier<PageResult<StockOutOrder>> {
reload(); reload();
} }
void setKeyword(String keyword) {
_keyword = keyword;
_page = 1;
reload();
}
void reload() { void reload() {
state = const AsyncValue.loading(); state = const AsyncValue.loading();
_fetch().then((result) { _fetch().then((result) {
@@ -13,6 +13,7 @@ class StockInRepository {
String? status, String? status,
String? startDate, String? startDate,
String? endDate, String? endDate,
String? keyword,
int page = 1, int page = 1,
int pageSize = 20, int pageSize = 20,
}) async { }) async {
@@ -23,6 +24,7 @@ class StockInRepository {
if (status != null && status.isNotEmpty) 'status': status, if (status != null && status.isNotEmpty) 'status': status,
if (startDate != null) 'start_date': startDate, if (startDate != null) 'start_date': startDate,
if (endDate != null) 'end_date': endDate, if (endDate != null) 'end_date': endDate,
if (keyword != null && keyword.isNotEmpty) 'keyword': keyword,
}; };
final resp = await _client.get('/stock-in/orders', params: params); final resp = await _client.get('/stock-in/orders', params: params);
return PageResult.fromJson( return PageResult.fromJson(
@@ -13,6 +13,7 @@ class StockOutRepository {
String? status, String? status,
String? startDate, String? startDate,
String? endDate, String? endDate,
String? keyword,
int page = 1, int page = 1,
int pageSize = 20, int pageSize = 20,
}) async { }) async {
@@ -23,6 +24,7 @@ class StockOutRepository {
if (status != null && status.isNotEmpty) 'status': status, if (status != null && status.isNotEmpty) 'status': status,
if (startDate != null) 'start_date': startDate, if (startDate != null) 'start_date': startDate,
if (endDate != null) 'end_date': endDate, if (endDate != null) 'end_date': endDate,
if (keyword != null && keyword.isNotEmpty) 'keyword': keyword,
}; };
final resp = await _client.get('/stock-out/orders', params: params); final resp = await _client.get('/stock-out/orders', params: params);
return PageResult.fromJson( return PageResult.fromJson(
@@ -677,6 +677,13 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
ColumnPrefs.save(_screenId, v); 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<InventoryListScreen> {
ColumnPrefs.save(_screenId, v); 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(), const Spacer(),
], ],
); );
@@ -39,6 +39,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
Set<String> _filterWarehouse = {}; Set<String> _filterWarehouse = {};
Set<String> _filterSupplier = {}; Set<String> _filterSupplier = {};
Set<String>? _hiddenCols; // null = 尚未载入本地存档(回退到 minWidth 首次默认) Set<String>? _hiddenCols; // null = 尚未载入本地存档(回退到 minWidth 首次默认)
final _searchCtrl = TextEditingController();
static const _screenId = 'stock_in_list'; static const _screenId = 'stock_in_list';
@@ -71,6 +72,15 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
}); });
} }
@override
void dispose() {
_searchCtrl.dispose();
super.dispose();
}
void _triggerSearch() =>
ref.read(stockInListProvider.notifier).setKeyword(_searchCtrl.text.trim());
String? get _startDate => _dateRange != null String? get _startDate => _dateRange != null
? '${_dateRange!.start.year}-${_dateRange!.start.month.toString().padLeft(2, '0')}-${_dateRange!.start.day.toString().padLeft(2, '0')}' ? '${_dateRange!.start.year}-${_dateRange!.start.month.toString().padLeft(2, '0')}-${_dateRange!.start.day.toString().padLeft(2, '0')}'
: null; : null;
@@ -352,6 +362,27 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
) )
: null; : 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( final dateBtn = OutlinedButton.icon(
onPressed: _pickDateRange, onPressed: _pickDateRange,
icon: const Icon(Icons.date_range, size: 16), icon: const Icon(Icons.date_range, size: 16),
@@ -374,28 +405,36 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
: null; : null;
if (isMobile) { if (isMobile) {
return Wrap( return Column(
spacing: 8, mainAxisSize: MainAxisSize.min,
runSpacing: 4,
crossAxisAlignment: WrapCrossAlignment.center,
children: [ children: [
if (newBtn != null) newBtn, searchField,
if (statusFilter != null) statusFilter, const SizedBox(height: 8),
dateBtn, Wrap(
if (clearDate != null) clearDate, spacing: 8,
IconButton( runSpacing: 4,
tooltip: '导出', crossAxisAlignment: WrapCrossAlignment.center,
icon: const Icon(Icons.download, size: 20), children: [
onPressed: doExport, if (newBtn != null) newBtn,
), if (statusFilter != null) statusFilter,
ColumnToggleButton( dateBtn,
columns: _colDefs, if (clearDate != null) clearDate,
hidden: hidden, IconButton(
compact: true, tooltip: '导出',
onChanged: (v) { icon: const Icon(Icons.download, size: 20),
setState(() => _hiddenCols = v); onPressed: doExport,
ColumnPrefs.save(_screenId, v); ),
}, 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<StockInListScreen> {
return Row( return Row(
children: [ children: [
SizedBox(width: 220, child: searchField),
const SizedBox(width: 12),
if (newBtn != null) newBtn, if (newBtn != null) newBtn,
const Spacer(), const Spacer(),
if (statusFilter != null) ...[ if (statusFilter != null) ...[
@@ -429,6 +470,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
ColumnPrefs.save(_screenId, v); ColumnPrefs.save(_screenId, v);
}, },
), ),
refreshBtn,
], ],
); );
}), }),
@@ -38,6 +38,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
Set<String> _filterWarehouse = {}; Set<String> _filterWarehouse = {};
Set<String> _filterCustomer = {}; Set<String> _filterCustomer = {};
Set<String>? _hiddenCols; // null = 尚未载入本地存档(回退到 minWidth 首次默认) Set<String>? _hiddenCols; // null = 尚未载入本地存档(回退到 minWidth 首次默认)
final _searchCtrl = TextEditingController();
static const _screenId = 'stock_out_list'; static const _screenId = 'stock_out_list';
@@ -71,6 +72,16 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
ColDef('actions', '操作', required: true), 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 String? get _startDate => _dateRange != null
? '${_dateRange!.start.year}-${_dateRange!.start.month.toString().padLeft(2, '0')}-${_dateRange!.start.day.toString().padLeft(2, '0')}' ? '${_dateRange!.start.year}-${_dateRange!.start.month.toString().padLeft(2, '0')}-${_dateRange!.start.day.toString().padLeft(2, '0')}'
: null; : null;
@@ -358,6 +369,27 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
) )
: null; : 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( final dateBtn = OutlinedButton.icon(
onPressed: _pickDateRange, onPressed: _pickDateRange,
icon: const Icon(Icons.date_range, size: 16), icon: const Icon(Icons.date_range, size: 16),
@@ -380,28 +412,36 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
: null; : null;
if (isMobile) { if (isMobile) {
return Wrap( return Column(
spacing: 8, mainAxisSize: MainAxisSize.min,
runSpacing: 4,
crossAxisAlignment: WrapCrossAlignment.center,
children: [ children: [
if (newBtn != null) newBtn, searchField,
if (statusFilter != null) statusFilter, const SizedBox(height: 8),
dateBtn, Wrap(
if (clearDate != null) clearDate, spacing: 8,
IconButton( runSpacing: 4,
tooltip: '导出', crossAxisAlignment: WrapCrossAlignment.center,
icon: const Icon(Icons.download, size: 20), children: [
onPressed: doExport, if (newBtn != null) newBtn,
), if (statusFilter != null) statusFilter,
ColumnToggleButton( dateBtn,
columns: _colDefs, if (clearDate != null) clearDate,
hidden: hidden, IconButton(
compact: true, tooltip: '导出',
onChanged: (v) { icon: const Icon(Icons.download, size: 20),
setState(() => _hiddenCols = v); onPressed: doExport,
ColumnPrefs.save(_screenId, v); ),
}, 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<StockOutListScreen> {
return Row( return Row(
children: [ children: [
SizedBox(width: 220, child: searchField),
const SizedBox(width: 12),
if (newBtn != null) newBtn, if (newBtn != null) newBtn,
const Spacer(), const Spacer(),
if (statusFilter != null) ...[ if (statusFilter != null) ...[
@@ -435,6 +477,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
ColumnPrefs.save(_screenId, v); ColumnPrefs.save(_screenId, v);
}, },
), ),
refreshBtn,
], ],
); );
}), }),