feat(client): 库存列表收尾——副标/toolbar 间距/移动端 golden

- 副标(还原原型 .head .sub):KPI 上方「共 N 个商品 · 数据实时」(千分位)
- toolbar 间距 token 化:搜索框对齐原型 240,间隔走 AppDims.sp2/sp3
- 移动端 golden:窄屏(390)卡片流路径 ×三主题入回归闸
- 顺带修 DataTableCard 分页条窄屏横向溢出(mobile 隐藏每页选择器,
  golden 闸抓出的预存 bug)

analyze 0 error · flutter test +175(库存桌面/移动各 3 golden 全过)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSKEiHsvauyxYUW2itzUXX
This commit is contained in:
wangjia
2026-06-25 12:11:17 +08:00
parent dc120a55df
commit b9c85dad93
9 changed files with 49 additions and 19 deletions
@@ -4,6 +4,7 @@ import 'package:file_picker/file_picker.dart';
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 '../../core/config/app_config.dart';
import '../../core/config/app_constants.dart';
import '../../core/responsive/responsive.dart';
@@ -528,6 +529,18 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
return Column(
children: [
// 副标(还原原型 .head .sub):商品总数 + 实时提示
Container(
width: double.infinity,
color: context.tokens.bg,
padding: const EdgeInsets.fromLTRB(
AppDims.sp3, AppDims.sp3, AppDims.sp3, 0),
child: Text(
'${NumberFormat.decimalPattern().format(result.total)} 个商品 · 数据实时',
style:
TextStyle(fontSize: AppDims.fsSm, color: context.tokens.muted),
),
),
// KPI 卡(还原原型 .kpi):宽屏 4 等分,窄屏横向滚动
Builder(builder: (context) {
final inStockQty =
@@ -659,7 +672,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
mainAxisSize: MainAxisSize.min,
children: [
searchField,
const SizedBox(height: 8),
const SizedBox(height: AppDims.sp2),
Row(
children: [
if (canCheck)
@@ -716,11 +729,11 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
);
}
// 桌面:搜索置于最前,按钮成组靠左
// 桌面:搜索置于最前(原型 searchbox 240,按钮成组靠左
return Row(
children: [
SizedBox(width: 220, child: searchField),
const SizedBox(width: 12),
SizedBox(width: 240, child: searchField),
const SizedBox(width: AppDims.sp3),
if (canCheck) ...[
WriteGuard(
child: OutlinedButton.icon(
@@ -729,14 +742,14 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
label: const Text('发起盘点'),
),
),
const SizedBox(width: 8),
const SizedBox(width: AppDims.sp2),
],
OutlinedButton.icon(
onPressed: doExport,
icon: const Icon(Icons.download, size: 16),
label: const Text('导出'),
),
const SizedBox(width: 8),
const SizedBox(width: AppDims.sp2),
ColumnToggleButton(
columns: _colDefs,
hidden: hidden,
@@ -745,7 +758,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
ColumnPrefs.save(_screenId, v);
},
),
const SizedBox(width: 8),
const SizedBox(width: AppDims.sp2),
OutlinedButton.icon(
onPressed: () =>
ref.read(inventoryListProvider.notifier).reload(),
+6 -2
View File
@@ -83,6 +83,8 @@ class _DataTableCardState extends State<DataTableCard> {
pageSize: widget.pageSize,
onPageChanged: widget.onPageChanged,
onPageSizeChanged: widget.onPageSizeChanged,
// 窄屏隐藏「每页 N 条」选择器,给翻页让位,避免横向溢出
compact: context.isMobile,
),
],
),
@@ -207,6 +209,7 @@ class _PaginationBar extends StatelessWidget {
final int pageSize;
final ValueChanged<int>? onPageChanged;
final ValueChanged<int>? onPageSizeChanged;
final bool compact;
static const _pageSizeOptions = [10, 20, 50, 100];
@@ -216,6 +219,7 @@ class _PaginationBar extends StatelessWidget {
required this.pageSize,
this.onPageChanged,
this.onPageSizeChanged,
this.compact = false,
});
@override
@@ -225,8 +229,8 @@ class _PaginationBar extends StatelessWidget {
final effectiveSize = _pageSizeOptions.contains(pageSize) ? pageSize : _pageSizeOptions.first;
return Row(
children: [
// 每页条数选择器
if (onPageSizeChanged != null) ...[
// 每页条数选择器(窄屏隐藏,避免溢出)
if (onPageSizeChanged != null && !compact) ...[
Text('每页', style: TextStyle(fontSize: 13, color: context.tokens.muted)),
const SizedBox(width: 4),
DropdownButtonHideUnderline(
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:jiu_client/core/models/page_result.dart';
@@ -70,20 +71,32 @@ class _FakeSpecNotifier extends ProductSpecListNotifier {
Future<List<ProductSpecOption>> build() async => const [];
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
SharedPreferences.setMockInitialValues({});
goldenAcrossThemes(
'inventory list 整屏',
goldenPrefix: 'inventory_list',
child: () => const Scaffold(body: InventoryListScreen()),
overrides: () => [
List<Override> _overrides() => [
inventoryListProvider.overrideWith(() => _FakeInvNotifier()),
productSeriesListProvider.overrideWith(() => _FakeSeriesNotifier()),
productSpecListProvider.overrideWith(() => _FakeSpecNotifier()),
isReadonlyProvider.overrideWithValue(false),
],
];
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
SharedPreferences.setMockInitialValues({});
// 桌面:宽屏表格路径
goldenAcrossThemes(
'inventory list 整屏(桌面)',
goldenPrefix: 'inventory_list',
child: () => const Scaffold(body: InventoryListScreen()),
overrides: _overrides,
logical: const Size(1280, 900),
);
// 移动:窄屏(<600)→ context.isMobile → MobileListCard 卡片流路径
goldenAcrossThemes(
'inventory list 整屏(移动)',
goldenPrefix: 'inventory_list_mobile',
child: () => const Scaffold(body: InventoryListScreen()),
overrides: _overrides,
logical: const Size(390, 1400),
);
}