feat:收银页面副屏改大

This commit is contained in:
dzc committed 2026-08-14 15:42:15 +08:00
1 parent 72486eb7ac
commit aff30bf45d
4 files changed
+210 -95

No files matched your search

@@ -74,6 +74,7 @@ class CustomerDisplayProduct {
required this.quantityLabel,
required this.unitPrice,
required this.totalPrice,
this.imageSource,
this.isCombo = false,
this.comboItems = const [],
});
@@ -90,6 +91,9 @@ class CustomerDisplayProduct {
/// 当前购物车条目的结算小计,单位为分。
final int totalPrice;
/// 商品图片来源,兼容完整网络地址和服务端返回的 OSS 相对地址;为空时展示名称首字占位。
final String? imageSource;
/// 是否为食堂套餐;旧版副屏消息缺失该字段时为 false。
final bool isCombo;
@@ -104,6 +108,7 @@ class CustomerDisplayProduct {
other.quantityLabel == quantityLabel &&
other.unitPrice == unitPrice &&
other.totalPrice == totalPrice &&
other.imageSource == imageSource &&
other.isCombo == isCombo &&
listEquals(other.comboItems, comboItems);
}
@@ -115,6 +120,7 @@ class CustomerDisplayProduct {
quantityLabel,
unitPrice,
totalPrice,
imageSource,
isCombo,
Object.hashAll(comboItems),
);
@@ -168,13 +168,16 @@ class CustomerDisplayCoordinator extends ChangeNotifier {
final unitPrice = _nonNegativeInt(data['productPrice']);
final totalPrice = _nonNegativeInt(data['totalPrice']);
final nature = data['nature'];
final rawImageSource = data['productPic'];
if (name is! String ||
name.trim().isEmpty ||
unitPrice == null ||
totalPrice == null ||
nature is! String) {
nature is! String ||
(rawImageSource != null && rawImageSource is! String)) {
return null;
}
final trimmedImageSource = (rawImageSource as String?)?.trim();
String quantityLabel;
if (nature == 'WEIGHT') {
@@ -207,6 +210,8 @@ class CustomerDisplayCoordinator extends ChangeNotifier {
quantityLabel: quantityLabel,
unitPrice: unitPrice,
totalPrice: totalPrice,
imageSource:
trimmedImageSource?.isEmpty == true ? null : trimmedImageSource,
// 套餐组成非空时兼容主屏仅透传明细但缺失标识的过渡消息。
isCombo: rawIsCombo == true || comboItems.isNotEmpty,
comboItems: List.unmodifiable(comboItems),
+192 -94
View File
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:pj_supermarket_cloud/common/style/xfj_design_tokens.dart';
import 'package:pj_supermarket_cloud/common/values/constants.dart';
import 'package:pj_supermarket_cloud/sub_screen/common/models/customer_display_models.dart';
import 'package:pj_supermarket_cloud/sub_screen/common/utils/customer_display_coordinator.dart';
import 'package:pj_supermarket_cloud/sub_screen/page/customer_welcome.dart';
@@ -143,8 +144,8 @@ class _Header extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 76 * scale,
padding: EdgeInsets.symmetric(horizontal: 22 * scale),
height: 82 * scale,
padding: EdgeInsets.symmetric(horizontal: 18 * scale),
decoration: _surfaceDecoration(radius: 16 * scale),
child: Row(
children: [
@@ -160,33 +161,21 @@ class _Header extends StatelessWidget {
'Z',
style: TextStyle(
color: Colors.white,
fontSize: 24 * scale,
fontSize: _fontSize(28, scale),
height: 1,
fontWeight: FontWeight.w900,
),
),
),
SizedBox(width: 14 * scale),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
state.businessTitle,
style: TextStyle(
color: XfjDesignTokens.ink,
fontSize: 22 * scale,
fontWeight: FontWeight.w800,
),
),
Text(
'顾客显示屏',
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: 14 * scale,
fontWeight: FontWeight.w500,
),
),
],
SizedBox(width: 10 * scale),
Text(
state.businessTitle,
style: TextStyle(
color: XfjDesignTokens.ink,
fontSize: _fontSize(28, scale),
height: 1,
fontWeight: FontWeight.w800,
),
),
const Spacer(),
if (state.maskedCustomerName != null) ...[
@@ -230,8 +219,8 @@ class _HeaderPill extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints(minHeight: 44 * scale),
padding: EdgeInsets.symmetric(horizontal: 14 * scale),
constraints: BoxConstraints(minHeight: 42 * scale),
padding: EdgeInsets.symmetric(horizontal: 10 * scale),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.08),
border: Border.all(color: color.withValues(alpha: 0.22)),
@@ -239,13 +228,14 @@ class _HeaderPill extends StatelessWidget {
),
child: Row(
children: [
Icon(icon, color: color, size: 20 * scale),
SizedBox(width: 8 * scale),
Icon(icon, color: color, size: 22 * scale),
SizedBox(width: 5 * scale),
Text(
label,
style: TextStyle(
color: color,
fontSize: 15 * scale,
fontSize: _fontSize(18, scale),
height: 1,
fontWeight: FontWeight.w700,
),
),
@@ -255,12 +245,18 @@ class _HeaderPill extends StatelessWidget {
}
}
/// 副屏商品核对面板,集中展示商品明细、同步状态和顾客提示。
class _ProductPanel extends StatelessWidget {
/// 创建读取当前顾客屏状态的商品核对面板。
const _ProductPanel({required this.state, required this.scale});
/// 副屏协调器产出的当前结算展示状态。
final CustomerDisplayState state;
/// 依据副屏分辨率计算的统一缩放比例。
final double scale;
/// 构建适合远距离核对的商品区域,并在商品较多时保留纵向滚动能力。
@override
Widget build(BuildContext context) {
return Container(
@@ -269,10 +265,10 @@ class _ProductPanel extends StatelessWidget {
children: [
Padding(
padding: EdgeInsets.fromLTRB(
22 * scale,
18 * scale,
22 * scale,
14 * scale,
12 * scale,
14 * scale,
10 * scale,
),
child: Row(
children: [
@@ -284,18 +280,18 @@ class _ProductPanel extends StatelessWidget {
state.products.isEmpty ? '欢迎光临' : '请核对商品信息',
style: TextStyle(
color: XfjDesignTokens.ink,
fontSize: 24 * scale,
fontSize: _fontSize(34, scale),
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 4 * scale),
SizedBox(height: 2 * scale),
Text(
state.products.isEmpty
? '商品加入购物车后将在这里实时展示'
: '本次消费共 ${state.products.length} 项商品',
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: 15 * scale,
fontSize: _fontSize(22, scale),
),
),
],
@@ -326,8 +322,8 @@ class _ProductPanel extends StatelessWidget {
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
horizontal: 22 * scale,
vertical: 12 * scale,
horizontal: 14 * scale,
vertical: 8 * scale,
),
decoration: const BoxDecoration(
color: Color(0xFFFAFAFB),
@@ -337,7 +333,7 @@ class _ProductPanel extends StatelessWidget {
'商品和金额由收银员实时更新,如有疑问请及时告知收银员',
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: 14 * scale,
fontSize: _fontSize(18, scale),
),
),
),
@@ -377,7 +373,7 @@ class _EmptyProducts extends StatelessWidget {
'等待添加商品',
style: TextStyle(
color: XfjDesignTokens.ink,
fontSize: 24 * scale,
fontSize: _fontSize(30, scale),
fontWeight: FontWeight.w800,
),
),
@@ -386,7 +382,7 @@ class _EmptyProducts extends StatelessWidget {
'请将商品交给收银员进行结算',
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: 16 * scale,
fontSize: _fontSize(20, scale),
),
),
],
@@ -395,16 +391,20 @@ class _EmptyProducts extends StatelessWidget {
}
}
/// 副屏商品列表表头,与商品行共享相同的列宽比例。
class _ProductHeader extends StatelessWidget {
/// 创建当前分辨率下的商品列表表头。
const _ProductHeader({required this.scale});
/// 依据副屏分辨率计算的统一缩放比例。
final double scale;
/// 构建商品、数量或重量、单价和小计四列表头。
@override
Widget build(BuildContext context) {
return Container(
height: 48 * scale,
padding: EdgeInsets.symmetric(horizontal: 22 * scale),
height: 42 * scale,
padding: EdgeInsets.symmetric(horizontal: 10 * scale),
color: const Color(0xFFFAFAFB),
child: Row(
children: [
@@ -418,6 +418,7 @@ class _ProductHeader extends StatelessWidget {
}
}
/// 副屏商品列表中的单条商品记录,可选展示套餐组成。
class _ProductRow extends StatelessWidget {
/// 创建一条可同时展示主商品和套餐组成的副屏商品行。
const _ProductRow({required this.product, required this.scale});
@@ -432,11 +433,13 @@ class _ProductRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(minHeight: 74 * scale),
// 单品行按当前副屏可视区容纳 10 条设置高度,字号放大后通过收紧内边距避免溢出。
constraints: BoxConstraints(minHeight: 72 * scale),
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 22 * scale, vertical: 10 * scale),
EdgeInsets.symmetric(horizontal: 10 * scale, vertical: 3 * scale),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
@@ -445,19 +448,21 @@ class _ProductRow extends StatelessWidget {
flex: 5,
child: Row(
children: [
_ProductImage(product: product, scale: scale),
SizedBox(width: 6 * scale),
_ProductTypeBadge(
isCombo: product.isCombo,
scale: scale,
),
SizedBox(width: 9 * scale),
SizedBox(width: 5 * scale),
Expanded(
child: Text(
product.name,
maxLines: 2,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: XfjDesignTokens.ink,
fontSize: 18 * scale,
fontSize: _fontSize(28, scale),
fontWeight: FontWeight.w700,
),
),
@@ -476,7 +481,7 @@ class _ProductRow extends StatelessWidget {
],
),
if (product.isCombo && product.comboItems.isNotEmpty) ...[
SizedBox(height: 10 * scale),
SizedBox(height: 6 * scale),
_ComboContents(product: product, scale: scale),
],
],
@@ -486,6 +491,66 @@ class _ProductRow extends StatelessWidget {
}
}
/// 商品行左侧的固定尺寸图片,网络加载与失败时均保持行高稳定。
class _ProductImage extends StatelessWidget {
/// 创建使用当前商品图片来源的顾客屏缩略图。
const _ProductImage({required this.product, required this.scale});
/// 当前商品,用于读取图片来源、占位首字和无障碍说明。
final CustomerDisplayProduct product;
/// 依据副屏分辨率计算的几何缩放比例。
final double scale;
/// 构建真实商品图片;副屏 Engine 不依赖磁盘缓存插件,使用 Flutter 原生网络图片缓存。
@override
Widget build(BuildContext context) {
final imageSource = product.imageSource;
final imageSize = 60 * scale;
final placeholder = _buildPlaceholder();
return Semantics(
image: true,
label: '${product.name}商品图片',
child: ClipRRect(
borderRadius: BorderRadius.circular(7 * scale),
child: SizedBox(
key: ValueKey('customer-product-image-${product.name}'),
width: imageSize,
height: imageSize,
child: imageSource == null
? placeholder
: Image.network(
_productImageUrl(imageSource),
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) =>
loadingProgress == null ? child : placeholder,
errorBuilder: (context, error, stackTrace) => placeholder,
),
),
),
);
}
/// 为无图片、加载中和加载失败场景生成一致的名称首字占位。
Widget _buildPlaceholder() {
final trimmedName = product.name.trim();
final shortLabel = trimmedName.isEmpty ? '商' : trimmedName.substring(0, 1);
return ColoredBox(
color: XfjDesignTokens.control,
child: Center(
child: Text(
shortLabel,
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: _fontSize(22, scale),
fontWeight: FontWeight.w800,
),
),
),
);
}
}
/// 副屏主商品类型标识,帮助顾客快速区分单品和套餐。
class _ProductTypeBadge extends StatelessWidget {
/// 创建商品类型标识。
@@ -511,14 +576,14 @@ class _ProductTypeBadge extends StatelessWidget {
),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 7 * scale,
vertical: 3 * scale,
horizontal: 5 * scale,
vertical: 2 * scale,
),
child: Text(
isCombo ? '套餐' : '单品',
style: TextStyle(
color: foreground,
fontSize: 13 * scale,
fontSize: _fontSize(18, scale),
fontWeight: FontWeight.w700,
),
),
@@ -548,10 +613,10 @@ class _ComboContents extends StatelessWidget {
return Container(
key: ValueKey('combo-details-${product.name}'),
padding: EdgeInsets.fromLTRB(
14 * scale,
10 * scale,
14 * scale,
11 * scale,
6 * scale,
10 * scale,
7 * scale,
),
decoration: const BoxDecoration(
color: XfjDesignTokens.primarySoft,
@@ -566,10 +631,10 @@ class _ComboContents extends StatelessWidget {
children: [
Icon(
Icons.layers_outlined,
size: 17 * scale,
size: 18 * scale,
color: XfjDesignTokens.primary,
),
SizedBox(width: 7 * scale),
SizedBox(width: 5 * scale),
Expanded(
child: Text(
'套餐内含 · 每套 ${product.comboItems.length} 样 / $totalQuantity 份',
@@ -577,17 +642,17 @@ class _ComboContents extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: XfjDesignTokens.primary,
fontSize: 14 * scale,
fontSize: _fontSize(20, scale),
fontWeight: FontWeight.w800,
),
),
),
SizedBox(width: 12 * scale),
SizedBox(width: 8 * scale),
Text(
'明细不单独计价',
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: 12 * scale,
fontSize: _fontSize(18, scale),
fontWeight: FontWeight.w600,
),
),
@@ -595,15 +660,15 @@ class _ComboContents extends StatelessWidget {
),
for (final item in product.comboItems)
Padding(
padding: EdgeInsets.only(top: 8 * scale),
padding: EdgeInsets.only(top: 5 * scale),
child: Row(
children: [
Icon(
Icons.subdirectory_arrow_right_rounded,
size: 16 * scale,
size: 17 * scale,
color: XfjDesignTokens.muted,
),
SizedBox(width: 8 * scale),
SizedBox(width: 5 * scale),
Expanded(
child: Text(
item.name,
@@ -611,17 +676,17 @@ class _ComboContents extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: XfjDesignTokens.ink,
fontSize: 15 * scale,
fontSize: _fontSize(21, scale),
fontWeight: FontWeight.w600,
),
),
),
SizedBox(width: 12 * scale),
SizedBox(width: 8 * scale),
Text(
'×${item.quantity}',
style: TextStyle(
color: XfjDesignTokens.ink,
fontSize: 15 * scale,
fontSize: _fontSize(21, scale),
fontWeight: FontWeight.w800,
fontFeatures: const [FontFeature.tabularFigures()],
),
@@ -635,6 +700,7 @@ class _ComboContents extends StatelessWidget {
}
}
/// 按商品表格的列宽比例渲染表头文本。
Widget _tableText(
String value,
int flex,
@@ -648,13 +714,14 @@ Widget _tableText(
textAlign: align,
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: 14 * scale,
fontSize: _fontSize(20, scale),
fontWeight: FontWeight.w700,
),
),
);
}
/// 按商品表格的列宽比例渲染数量或金额,并保持数字宽度稳定。
Widget _valueCell(
String value,
int flex,
@@ -668,7 +735,7 @@ Widget _valueCell(
textAlign: TextAlign.center,
style: TextStyle(
color: emphasized ? XfjDesignTokens.ink : XfjDesignTokens.muted,
fontSize: 17 * scale,
fontSize: _fontSize(26, scale),
fontWeight: emphasized ? FontWeight.w800 : FontWeight.w600,
fontFeatures: const [FontFeature.tabularFigures()],
),
@@ -685,7 +752,7 @@ class _SettlementPanel extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20 * scale),
padding: EdgeInsets.all(16 * scale),
decoration: _surfaceDecoration(radius: 16 * scale),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -694,7 +761,7 @@ class _SettlementPanel extends StatelessWidget {
'实际扣款',
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: 15 * scale,
fontSize: _fontSize(20, scale),
fontWeight: FontWeight.w700,
),
),
@@ -704,7 +771,7 @@ class _SettlementPanel extends StatelessWidget {
key: const ValueKey('customer-payable-price'),
style: TextStyle(
color: XfjDesignTokens.primary,
fontSize: 54 * scale,
fontSize: _fontSize(64, scale),
height: 1.05,
fontWeight: FontWeight.w900,
fontFeatures: const [FontFeature.tabularFigures()],
@@ -751,14 +818,15 @@ class _PriceLine extends StatelessWidget {
label,
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: 16 * scale,
fontSize: _fontSize(22, scale),
fontWeight: FontWeight.w600,
),
),
Text(
_money(price),
style: TextStyle(
color: XfjDesignTokens.ink,
fontSize: 18 * scale,
fontSize: _fontSize(26, scale),
fontWeight: FontWeight.w800,
fontFeatures: const [FontFeature.tabularFigures()],
),
@@ -784,6 +852,8 @@ class _PaymentStatePanel extends StatelessWidget {
constraints.maxWidth * 0.54,
constraints.maxHeight * 0.6,
);
// 主屏调试预览可能只有 800×600;仅在极矮区域收拢辅助说明,金额、倒计时和主提示仍保持大字号。
final compactPaymentHint = constraints.maxHeight < 180;
return Row(
children: [
Container(
@@ -808,30 +878,36 @@ class _PaymentStatePanel extends StatelessWidget {
children: [
Text(
'请扫码支付',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: XfjDesignTokens.ink,
fontSize: 26 * scale,
fontSize: _fontSize(34, scale),
height: 1,
fontWeight: FontWeight.w900,
),
),
SizedBox(height: 10 * scale),
SizedBox(height: 6 * scale),
Text(
_countdown(state.countdownSeconds),
key: const ValueKey('customer-payment-countdown'),
style: TextStyle(
color: XfjDesignTokens.danger,
fontSize: 34 * scale,
fontSize: _fontSize(44, scale),
height: 1,
fontWeight: FontWeight.w900,
fontFeatures: const [FontFeature.tabularFigures()],
),
),
SizedBox(height: 8 * scale),
SizedBox(height: 5 * scale),
Text(
'支付完成后请勿重复扫码',
maxLines: compactPaymentHint ? 1 : 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: 14 * scale,
height: 1.4,
fontSize: _fontSize(20, scale),
height: 1.2,
),
),
],
@@ -855,34 +931,37 @@ class _PaymentStatePanel extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 76 * scale,
height: 76 * scale,
width: 68 * scale,
height: 68 * scale,
alignment: Alignment.center,
decoration: BoxDecoration(
color: visual.color,
shape: BoxShape.circle,
),
child: Icon(visual.icon, color: Colors.white, size: 40 * scale),
child: Icon(visual.icon, color: Colors.white, size: 36 * scale),
),
SizedBox(height: 16 * scale),
SizedBox(height: 10 * scale),
Text(
visual.title,
style: TextStyle(
color: XfjDesignTokens.ink,
fontSize: 28 * scale,
fontSize: _fontSize(36, scale),
height: 1,
fontWeight: FontWeight.w900,
),
),
SizedBox(height: 7 * scale),
SizedBox(height: 5 * scale),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20 * scale),
child: Text(
visual.description,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: XfjDesignTokens.muted,
fontSize: 15 * scale,
height: 1.45,
fontSize: _fontSize(22, scale),
height: 1.2,
),
),
),
@@ -946,7 +1025,7 @@ class _Step extends StatelessWidget {
'$index',
style: TextStyle(
color: active ? Colors.white : XfjDesignTokens.muted,
fontSize: 14 * scale,
fontSize: _fontSize(18, scale),
fontWeight: FontWeight.w800,
),
),
@@ -956,7 +1035,7 @@ class _Step extends StatelessWidget {
label,
style: TextStyle(
color: active ? XfjDesignTokens.ink : XfjDesignTokens.muted,
fontSize: 13 * scale,
fontSize: _fontSize(18, scale),
fontWeight: FontWeight.w600,
),
),
@@ -966,31 +1045,36 @@ class _Step extends StatelessWidget {
}
}
/// 商品面板顶部的结算阶段标签,用图标和文字同时表达当前状态。
class _StatusBadge extends StatelessWidget {
/// 创建与当前顾客屏场景对应的状态标签。
const _StatusBadge({required this.scenario, required this.scale});
/// 当前结算场景,决定标签的文案、图标和语义颜色。
final CustomerDisplayScenario scenario;
/// 依据副屏分辨率计算的统一缩放比例。
final double scale;
/// 构建紧凑但可远距离识别的商品核对状态标签。
@override
Widget build(BuildContext context) {
final visual = _scenarioVisual(scenario);
return Container(
padding:
EdgeInsets.symmetric(horizontal: 12 * scale, vertical: 7 * scale),
padding: EdgeInsets.symmetric(horizontal: 8 * scale, vertical: 4 * scale),
decoration: BoxDecoration(
color: visual.color.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(18 * scale),
),
child: Row(
children: [
Icon(visual.icon, color: visual.color, size: 17 * scale),
SizedBox(width: 6 * scale),
Icon(visual.icon, color: visual.color, size: 20 * scale),
SizedBox(width: 4 * scale),
Text(
scenario.title,
style: TextStyle(
color: visual.color,
fontSize: 14 * scale,
fontSize: _fontSize(20, scale),
fontWeight: FontWeight.w700,
),
),
@@ -1066,6 +1150,20 @@ BoxDecoration _surfaceDecoration({required double radius}) {
String _money(int cents) => '¥ ${(cents / 100).toStringAsFixed(2)}';
/// 将文字缩放与布局缩放分离,小尺寸副屏仍保留远距离可读的最低字号。
double _fontSize(double designSize, double scale) {
return designSize * math.max(scale, 0.9);
}
/// 将主屏传来的完整地址或 OSS 相对路径转换为副屏可加载的网络地址。
String _productImageUrl(String source) {
if (RegExp(r'^https?://', caseSensitive: false).hasMatch(source)) {
return source;
}
final separator = source.startsWith('/') || source.startsWith('?') ? '' : '/';
return '${Constants.ossUrl}$separator$source';
}
String _countdown(int seconds) {
final minutes = seconds ~/ 60;
final remainingSeconds = seconds % 60;
@@ -11,6 +11,11 @@ void main() {
expect(coordinator.state.scenario, CustomerDisplayScenario.review);
expect(coordinator.state.products, hasLength(2));
expect(coordinator.state.products.last.quantityLabel, '0.36 kg');
expect(
coordinator.state.products.first.imageSource,
'/admin/sys-file/oss/file?fileName=milk.png',
);
expect(coordinator.state.products.last.imageSource, isNull);
expect(coordinator.state.products.first.isCombo, isFalse);
expect(coordinator.state.products.first.comboItems, isEmpty);
expect(coordinator.state.totalPrice, 4670);
@@ -272,6 +277,7 @@ Map<String, dynamic> _productEvent() {
'shopList': [
{
'productName': '纯牛奶 250ml',
'productPic': '/admin/sys-file/oss/file?fileName=milk.png',
'productPrice': 480,
'nature': 'DEFAULT',
'productNumber': 1,