fix(client): 财务汇总往来搜索改为表头下拉框形态(验收反馈)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,9 +58,13 @@ class _FinanceScreenState extends ConsumerState<FinanceScreen> {
|
||||
// 窄屏三分段(原型 m-finance .seg:应收 / 应付 / 流水)
|
||||
int _mSeg = 0;
|
||||
// 应收/应付汇总卡「往来单位」搜索(本地即时过滤——financePartnerRowsProvider
|
||||
// 全量加载非分页,搜索口径=显示口径;桌面 DsSearchBox / 移动 MSearchRow 共用)
|
||||
// 全量加载非分页,搜索口径=显示口径;移动 应收·应付 分段头 MSearchRow 沿用)
|
||||
final _summaryKwCtrl = TextEditingController();
|
||||
String _summaryKw = '';
|
||||
// 桌面汇总表表头「往来单位」下拉筛选(验收反馈改版:由卡标题行文字搜索
|
||||
// 改为表头内嵌 ComboSearchField,同 _flowTable 往来单位筛选控件;单选精确
|
||||
// 匹配一行,× 清除恢复全量;候选=当前汇总行往来单位,口径=显示口径)。
|
||||
int? _summaryPartnerId;
|
||||
|
||||
static const _flowChips = ['全部', '应收', '应付', '收款', '付款'];
|
||||
static const _chipToType = {
|
||||
@@ -129,13 +133,30 @@ class _FinanceScreenState extends ConsumerState<FinanceScreen> {
|
||||
|
||||
void _setSummaryKw(String kw) => setState(() => _summaryKw = kw);
|
||||
|
||||
/// 汇总行本地过滤(往来单位名 contains,大小写不敏感)。
|
||||
/// 汇总行本地过滤(往来单位名 contains,大小写不敏感;移动端专用)。
|
||||
List<PartnerFinanceRow> _filterSummaryRows(List<PartnerFinanceRow> rows) {
|
||||
final kw = _summaryKw.trim().toLowerCase();
|
||||
if (kw.isEmpty) return rows;
|
||||
return rows.where((r) => r.name.toLowerCase().contains(kw)).toList();
|
||||
}
|
||||
|
||||
void _setSummaryPartner(int? id) => setState(() => _summaryPartnerId = id);
|
||||
|
||||
/// 桌面汇总表头下拉筛选:选中 → 精确匹配单行;未选 → 原样返回(专用)。
|
||||
/// [id] 优先取 partnerId,缺失(自由文本往来单位)时用行下标取负数兜底,
|
||||
/// 与 header 里构建 ComboSearchField options 的合成 id 规则保持一致。
|
||||
List<PartnerFinanceRow> _filterSummaryByPartner(
|
||||
List<PartnerFinanceRow> rows,
|
||||
) {
|
||||
if (_summaryPartnerId == null) return rows;
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
if ((rows[i].partnerId ?? -(i + 1)) == _summaryPartnerId) {
|
||||
return [rows[i]];
|
||||
}
|
||||
}
|
||||
return const [];
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_summaryKwCtrl.dispose();
|
||||
@@ -242,17 +263,7 @@ class _FinanceScreenState extends ConsumerState<FinanceScreen> {
|
||||
_secHead(t, '收支趋势', '近 6 个月 · 单位 万元'),
|
||||
_trendChart(),
|
||||
const SizedBox(height: 22),
|
||||
_secHead(
|
||||
t,
|
||||
'应收 / 应付 汇总',
|
||||
'按往来单位 · 点击行查看流水',
|
||||
trailing: DsSearchBox(
|
||||
controller: _summaryKwCtrl,
|
||||
hint: '搜索往来单位',
|
||||
width: 220,
|
||||
onChanged: _setSummaryKw,
|
||||
),
|
||||
),
|
||||
_secHead(t, '应收 / 应付 汇总', '按往来单位 · 点击行查看流水'),
|
||||
_summaryTable(t, mobile),
|
||||
const SizedBox(height: 22),
|
||||
_secHead(t, '收支流水', _flowSub(flows)),
|
||||
@@ -531,7 +542,7 @@ class _FinanceScreenState extends ConsumerState<FinanceScreen> {
|
||||
}
|
||||
|
||||
/// 原型 .sec-head:h2 17/700 + 副标 fs-xs muted,下距 12。
|
||||
/// [trailing] 靠右附加控件(原型 .sec-head .sum-search,如汇总卡的往来单位搜索)。
|
||||
/// [trailing] 靠右附加控件(当前各段落调用均未传入,保留供后续段落复用)。
|
||||
Widget _secHead(dynamic t, String title, String sub, {Widget? trailing}) =>
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
@@ -628,10 +639,20 @@ class _FinanceScreenState extends ConsumerState<FinanceScreen> {
|
||||
],
|
||||
),
|
||||
data: (allRows) {
|
||||
final rows = _filterSummaryRows(allRows);
|
||||
final rows = _filterSummaryByPartner(allRows);
|
||||
// 下拉候选=当前汇总行往来单位(口径=显示口径,不用全量 partners,避免
|
||||
// 选到无余额单位出空表);id 合成规则同 _filterSummaryByPartner。
|
||||
final partnerOptions = <OptionItem>[
|
||||
for (var i = 0; i < allRows.length; i++)
|
||||
OptionItem(
|
||||
id: allRows[i].partnerId ?? -(i + 1),
|
||||
name: allRows[i].name,
|
||||
),
|
||||
];
|
||||
return DsTable(
|
||||
shrinkWrap: true,
|
||||
emptyText: _summaryKw.trim().isEmpty ? '暂无未结清的应收 / 应付' : '没有匹配的往来单位',
|
||||
emptyText:
|
||||
_summaryPartnerId == null ? '暂无未结清的应收 / 应付' : '没有匹配的往来单位',
|
||||
mobileCards: rows
|
||||
.map(
|
||||
(r) => MobileListCard(
|
||||
@@ -655,12 +676,38 @@ class _FinanceScreenState extends ConsumerState<FinanceScreen> {
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
columns: const [
|
||||
DsColumn('name', '往来单位'),
|
||||
DsColumn('recv', '应收', numeric: true),
|
||||
DsColumn('pay', '应付', numeric: true),
|
||||
DsColumn('net', '净额', numeric: true),
|
||||
DsColumn('actions', '操作', action: true),
|
||||
columns: [
|
||||
DsColumn(
|
||||
'name',
|
||||
'往来单位',
|
||||
// 表头「往来单位」文字右边内嵌下拉(验收反馈:由卡标题行搜索框
|
||||
// 改到此处,与 _flowTable 往来单位筛选同款 ComboSearchField)。
|
||||
header: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'往来单位',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsSm,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: t.muted,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
ComboSearchField(
|
||||
options: partnerOptions,
|
||||
selectedId: _summaryPartnerId,
|
||||
hint: '搜索往来单位',
|
||||
width: 180,
|
||||
onChanged: _setSummaryPartner,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const DsColumn('recv', '应收', numeric: true),
|
||||
const DsColumn('pay', '应付', numeric: true),
|
||||
const DsColumn('net', '净额', numeric: true),
|
||||
const DsColumn('actions', '操作', action: true),
|
||||
],
|
||||
rows: rows.map((r) {
|
||||
return DsRow(
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
// test/finance_summary_search_test.dart — 财务「应收/应付 汇总」往来单位搜索回归。
|
||||
// test/finance_summary_search_test.dart — 财务「应收/应付 汇总」往来单位筛选回归。
|
||||
//
|
||||
// financePartnerRowsProvider(GET /finance/summary)不分页、一次性全量加载,
|
||||
// 搜索口径=显示口径:仅需验证本地 contains 过滤在已加载行上生效——桌面
|
||||
// _summaryTable 工具头 DsSearchBox / 移动 应收·应付 分段头 MSearchRow 均覆盖:
|
||||
// 1. 输入关键字 → 汇总行按往来单位名过滤,未命中不显示;
|
||||
// 搜索口径=显示口径:
|
||||
// 桌面(验收反馈改版):表头「往来单位」内嵌 ComboSearchField(与 _flowTable
|
||||
// 往来单位筛选同款),单选精确匹配 → 汇总只显示该行;× 清除 → 恢复全量。
|
||||
// 移动 应收·应付 分段头仍是 MSearchRow 本地 contains 过滤,未改动:
|
||||
// 1. 输入关键字 → 汇总卡片按往来单位名过滤,未命中不显示;
|
||||
// 2. 关键字不匹配任何行 → 展示「没有匹配的往来单位」空态;
|
||||
// 3. 清空关键字 → 恢复全量展示。
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:jiu_client/core/api/api_client.dart';
|
||||
@@ -24,7 +27,7 @@ import 'package:jiu_client/providers/stock_in_provider.dart';
|
||||
import 'package:jiu_client/providers/stock_out_provider.dart';
|
||||
import 'package:jiu_client/repositories/finance_repository.dart';
|
||||
import 'package:jiu_client/screens/finance/finance_screen.dart';
|
||||
import 'package:jiu_client/widgets/ds/ds_atoms.dart' show DsSearchBox;
|
||||
import 'package:jiu_client/widgets/combo_search_field.dart';
|
||||
import 'package:jiu_client/widgets/ds/m_search_row.dart' show MSearchRow;
|
||||
|
||||
/// 不打真实网络:仅用于满足 FinanceRepository 构造签名,方法全部被覆写。
|
||||
@@ -122,8 +125,8 @@ void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
|
||||
group('桌面汇总卡往来单位搜索', () {
|
||||
testWidgets('输入关键字过滤汇总行,无匹配报空态,清空恢复全量', (tester) async {
|
||||
group('桌面汇总表头往来单位下拉筛选', () {
|
||||
testWidgets('选中往来单位只显示该行,× 清除恢复全量', (tester) async {
|
||||
await _pumpFinance(tester, size: const Size(1280, 900));
|
||||
|
||||
// 初始:3 条汇总行全部展示。
|
||||
@@ -131,28 +134,36 @@ void main() {
|
||||
expect(find.text('金樽商贸'), findsOneWidget);
|
||||
expect(find.text('茅台华南经销'), findsOneWidget);
|
||||
|
||||
final searchField = find.descendant(
|
||||
of: find.byType(DsSearchBox),
|
||||
matching: find.byType(TextField),
|
||||
// 定位表头内嵌的 ComboSearchField(按构造参数 hint 匹配,避免与
|
||||
// _flowTable toolbar 的往来单位 ComboSearchField(hint:'往来单位')
|
||||
// 混淆;选中后触发器文案会变成往来单位名,故不能用显示文字定位)。
|
||||
final combo = find.byWidgetPredicate(
|
||||
(w) => w is ComboSearchField && w.hint == '搜索往来单位',
|
||||
);
|
||||
expect(searchField, findsOneWidget);
|
||||
|
||||
// 输入「金樽」:只剩「金樽商贸」。
|
||||
await tester.enterText(searchField, '金樽');
|
||||
expect(combo, findsOneWidget);
|
||||
await tester.ensureVisible(combo);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('金樽商贸'), findsOneWidget);
|
||||
|
||||
// 打开弹层,选中「金樽商贸」。
|
||||
await tester.tap(combo);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.widgetWithText(InkWell, '金樽商贸'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 只剩「金樽商贸」一行。
|
||||
expect(find.text('华东酒类批发'), findsNothing);
|
||||
expect(find.text('茅台华南经销'), findsNothing);
|
||||
expect(find.text('金樽商贸'), findsWidgets);
|
||||
|
||||
// 输入不存在的关键字:空态提示。
|
||||
await tester.enterText(searchField, '不存在的单位名');
|
||||
// 点触发器上的 × 清除选择 → 恢复全量 3 行。
|
||||
final clearIcon = find.descendant(
|
||||
of: combo,
|
||||
matching: find.byIcon(LucideIcons.x),
|
||||
);
|
||||
expect(clearIcon, findsOneWidget);
|
||||
await tester.tap(clearIcon);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('没有匹配的往来单位'), findsOneWidget);
|
||||
expect(find.text('金樽商贸'), findsNothing);
|
||||
|
||||
// 清空:恢复全量 3 行。
|
||||
await tester.enterText(searchField, '');
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('华东酒类批发'), findsOneWidget);
|
||||
expect(find.text('金樽商贸'), findsOneWidget);
|
||||
expect(find.text('茅台华南经销'), findsOneWidget);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 285 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 282 KiB After Width: | Height: | Size: 282 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 286 KiB After Width: | Height: | Size: 285 KiB |
Reference in New Issue
Block a user