From 782cf057a3bea0936ad9aea1d38e2e0c6bd6c1bb Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Tue, 14 Jul 2026 10:09:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(client):=20=E5=87=BA=E5=85=A5=E5=BA=93?= =?UTF-8?q?=E6=8A=BD=E5=B1=89=E7=94=9F=E4=BA=A7=E6=97=A5=E6=9C=9F=E6=88=AA?= =?UTF-8?q?=E5=8F=96=E9=98=B2=E7=A9=BA=E4=B8=B2=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- client/lib/core/utils/date_util.dart | 10 ++++++ .../stock_in/stock_in_list_screen.dart | 5 ++- .../stock_out/stock_out_list_screen.dart | 5 ++- client/test/core/utils/date_util_test.dart | 31 +++++++++++++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 client/test/core/utils/date_util_test.dart diff --git a/client/lib/core/utils/date_util.dart b/client/lib/core/utils/date_util.dart index cfeeb49..a80c70e 100644 --- a/client/lib/core/utils/date_util.dart +++ b/client/lib/core/utils/date_util.dart @@ -12,6 +12,16 @@ DateTime? parseYmd(String? s) { return DateTime.tryParse(t); } +/// 安全截取日期字符串前 10 位(`yyyy-MM-dd`)。 +/// +/// null 或空串一律返回 null(视为「无值」,供上层「两值皆空不渲染」的逻辑判断); +/// 长度不足 10 原样返回。用于兜底后端 `Date` 零值序列化为 `""`(而非 null)、 +/// 或历史导入数据缺失生产日期的场景,避免 `''.substring(0, 10)` 抛 RangeError。 +String? shortDate(String? v) { + if (v == null || v.isEmpty) return null; + return v.length >= 10 ? v.substring(0, 10) : v; +} + /// 组合年/月/日为 `yyyy-MM-dd`,自动把超出当月的「日」clamp 回当月最大值 /// (如 2 月选 31 → 28/29)。任一为空返回 null。 String? composeYmd(int? year, int? month, int? day) { diff --git a/client/lib/screens/stock_in/stock_in_list_screen.dart b/client/lib/screens/stock_in/stock_in_list_screen.dart index 31a5f4e..5ebe583 100644 --- a/client/lib/screens/stock_in/stock_in_list_screen.dart +++ b/client/lib/screens/stock_in/stock_in_list_screen.dart @@ -1,3 +1,4 @@ +import '../../core/utils/date_util.dart'; import '../../core/utils/dialog_util.dart'; import '../../core/exceptions.dart'; import '../../core/utils/print_util.dart' show LabelData; @@ -996,9 +997,7 @@ class _StockInListScreenState extends ConsumerState { price: it.costPrice == 0 ? '待定价' : _num.format(it.costPrice), amount: it.costAmount == 0 ? '待定价' : _num.format(it.costAmount), pending: it.costPrice == 0, - productionDate: it.productionDate != null - ? it.productionDate!.substring(0, 10) - : null, + productionDate: shortDate(it.productionDate), batchNo: it.batchNo, )) .toList(), diff --git a/client/lib/screens/stock_out/stock_out_list_screen.dart b/client/lib/screens/stock_out/stock_out_list_screen.dart index 76ab962..c2577bc 100644 --- a/client/lib/screens/stock_out/stock_out_list_screen.dart +++ b/client/lib/screens/stock_out/stock_out_list_screen.dart @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; +import '../../core/utils/date_util.dart'; import '../../core/utils/dialog_util.dart'; import '../../core/exceptions.dart'; import '../../core/responsive/responsive.dart'; @@ -1076,9 +1077,7 @@ class _StockOutListScreenState extends ConsumerState { cost: admin ? _num.format(it.costPrice) : null, profit: admin ? (pending ? '待定价' : _num.format(profit)) : null, pending: pending, - productionDate: it.productionDate != null - ? it.productionDate!.substring(0, 10) - : null, + productionDate: shortDate(it.productionDate), batchNo: it.batchNo, ); }).toList(), diff --git a/client/test/core/utils/date_util_test.dart b/client/test/core/utils/date_util_test.dart new file mode 100644 index 0000000..1616166 --- /dev/null +++ b/client/test/core/utils/date_util_test.dart @@ -0,0 +1,31 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:jiu_client/core/utils/date_util.dart'; + +/// 2026-07-14 Critical 修复回归:出入库详情抽屉构造 OrderLine 时对 +/// `productionDate` 做 `substring(0, 10)`,只防 null 不防空串——后端 +/// `Date.MarshalJSON` 对零值日期序列化为 `""`(非 null),历史导入数据里 +/// 大量商品无生产日期,`''.substring(0, 10)` 会抛 RangeError 导致打开 +/// 详情抽屉直接崩溃。`shortDate` 需把空串也当「无值」归一为 null。 +void main() { + group('shortDate', () { + test('null 返回 null', () { + expect(shortDate(null), isNull); + }); + + test('空串返回 null(回归:曾经 substring(0, 10) 抛 RangeError)', () { + expect(shortDate(''), isNull); + }); + + test('标准 yyyy-MM-dd 原样截取前 10 位', () { + expect(shortDate('2021-01-27'), '2021-01-27'); + }); + + test('带时间戳的日期截取前 10 位', () { + expect(shortDate('2021-01-27T00:00:00Z'), '2021-01-27'); + }); + + test('长度不足 10 原样返回', () { + expect(shortDate('2021'), '2021'); + }); + }); +}