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/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import '../../core/config/app_config.dart'; import '../../core/config/app_config.dart';
import '../../core/config/app_constants.dart'; import '../../core/config/app_constants.dart';
import '../../core/responsive/responsive.dart'; import '../../core/responsive/responsive.dart';
@@ -528,6 +529,18 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
return Column( return Column(
children: [ 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 等分,窄屏横向滚动 // KPI 卡(还原原型 .kpi):宽屏 4 等分,窄屏横向滚动
Builder(builder: (context) { Builder(builder: (context) {
final inStockQty = final inStockQty =
@@ -659,7 +672,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
searchField, searchField,
const SizedBox(height: 8), const SizedBox(height: AppDims.sp2),
Row( Row(
children: [ children: [
if (canCheck) if (canCheck)
@@ -716,11 +729,11 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
); );
} }
// 桌面:搜索置于最前,按钮成组靠左 // 桌面:搜索置于最前(原型 searchbox 240,按钮成组靠左
return Row( return Row(
children: [ children: [
SizedBox(width: 220, child: searchField), SizedBox(width: 240, child: searchField),
const SizedBox(width: 12), const SizedBox(width: AppDims.sp3),
if (canCheck) ...[ if (canCheck) ...[
WriteGuard( WriteGuard(
child: OutlinedButton.icon( child: OutlinedButton.icon(
@@ -729,14 +742,14 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
label: const Text('发起盘点'), label: const Text('发起盘点'),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: AppDims.sp2),
], ],
OutlinedButton.icon( OutlinedButton.icon(
onPressed: doExport, onPressed: doExport,
icon: const Icon(Icons.download, size: 16), icon: const Icon(Icons.download, size: 16),
label: const Text('导出'), label: const Text('导出'),
), ),
const SizedBox(width: 8), const SizedBox(width: AppDims.sp2),
ColumnToggleButton( ColumnToggleButton(
columns: _colDefs, columns: _colDefs,
hidden: hidden, hidden: hidden,
@@ -745,7 +758,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
ColumnPrefs.save(_screenId, v); ColumnPrefs.save(_screenId, v);
}, },
), ),
const SizedBox(width: 8), const SizedBox(width: AppDims.sp2),
OutlinedButton.icon( OutlinedButton.icon(
onPressed: () => onPressed: () =>
ref.read(inventoryListProvider.notifier).reload(), ref.read(inventoryListProvider.notifier).reload(),
+6 -2
View File
@@ -83,6 +83,8 @@ class _DataTableCardState extends State<DataTableCard> {
pageSize: widget.pageSize, pageSize: widget.pageSize,
onPageChanged: widget.onPageChanged, onPageChanged: widget.onPageChanged,
onPageSizeChanged: widget.onPageSizeChanged, onPageSizeChanged: widget.onPageSizeChanged,
// 窄屏隐藏「每页 N 条」选择器,给翻页让位,避免横向溢出
compact: context.isMobile,
), ),
], ],
), ),
@@ -207,6 +209,7 @@ class _PaginationBar extends StatelessWidget {
final int pageSize; final int pageSize;
final ValueChanged<int>? onPageChanged; final ValueChanged<int>? onPageChanged;
final ValueChanged<int>? onPageSizeChanged; final ValueChanged<int>? onPageSizeChanged;
final bool compact;
static const _pageSizeOptions = [10, 20, 50, 100]; static const _pageSizeOptions = [10, 20, 50, 100];
@@ -216,6 +219,7 @@ class _PaginationBar extends StatelessWidget {
required this.pageSize, required this.pageSize,
this.onPageChanged, this.onPageChanged,
this.onPageSizeChanged, this.onPageSizeChanged,
this.compact = false,
}); });
@override @override
@@ -225,8 +229,8 @@ class _PaginationBar extends StatelessWidget {
final effectiveSize = _pageSizeOptions.contains(pageSize) ? pageSize : _pageSizeOptions.first; final effectiveSize = _pageSizeOptions.contains(pageSize) ? pageSize : _pageSizeOptions.first;
return Row( return Row(
children: [ children: [
// 每页条数选择器 // 每页条数选择器(窄屏隐藏,避免溢出)
if (onPageSizeChanged != null) ...[ if (onPageSizeChanged != null && !compact) ...[
Text('每页', style: TextStyle(fontSize: 13, color: context.tokens.muted)), Text('每页', style: TextStyle(fontSize: 13, color: context.tokens.muted)),
const SizedBox(width: 4), const SizedBox(width: 4),
DropdownButtonHideUnderline( 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/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:jiu_client/core/models/page_result.dart'; import 'package:jiu_client/core/models/page_result.dart';
@@ -70,20 +71,32 @@ class _FakeSpecNotifier extends ProductSpecListNotifier {
Future<List<ProductSpecOption>> build() async => const []; Future<List<ProductSpecOption>> build() async => const [];
} }
void main() { List<Override> _overrides() => [
TestWidgetsFlutterBinding.ensureInitialized();
SharedPreferences.setMockInitialValues({});
goldenAcrossThemes(
'inventory list 整屏',
goldenPrefix: 'inventory_list',
child: () => const Scaffold(body: InventoryListScreen()),
overrides: () => [
inventoryListProvider.overrideWith(() => _FakeInvNotifier()), inventoryListProvider.overrideWith(() => _FakeInvNotifier()),
productSeriesListProvider.overrideWith(() => _FakeSeriesNotifier()), productSeriesListProvider.overrideWith(() => _FakeSeriesNotifier()),
productSpecListProvider.overrideWith(() => _FakeSpecNotifier()), productSpecListProvider.overrideWith(() => _FakeSpecNotifier()),
isReadonlyProvider.overrideWithValue(false), 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), 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),
);
} }