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
@@ -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(),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
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),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||