feat:同时核销几笔订单

feat:人脸质量检测
This commit is contained in:
dzc committed 2026-08-31 17:59:14 +08:00
1 parent 9cc99deed7
commit 4d1b3aa123
2 files changed
+1052

No files matched your search

@@ -0,0 +1,428 @@
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:forui/forui.dart';
import '../../style/xfj_design_tokens.dart';
import '../../utils/app/app_price_tool.dart';
import '../app/app_price.dart';
import '../app/terminal_toolbar.dart';
enum OrderRecordDetailLoadStatus { loading, success, failure }
class OrderRecordDetailItem {
const OrderRecordDetailItem({
required this.name,
required this.quantity,
required this.unitPrice,
required this.totalPrice,
});
final String name;
final String quantity;
final int? unitPrice;
final int? totalPrice;
}
/// 交易记录和核销记录共用的展开控制,统一触控尺寸、动效和无障碍状态。
class OrderRecordExpandButton extends StatelessWidget {
const OrderRecordExpandButton({
super.key,
required this.expanded,
required this.semanticLabel,
required this.onTap,
});
final bool expanded;
final String semanticLabel;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final duration = MediaQuery.disableAnimationsOf(context)
? Duration.zero
: const Duration(milliseconds: 180);
return Semantics(
button: true,
expanded: expanded,
label: semanticLabel,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Container(
constraints: BoxConstraints(minWidth: 48.w, minHeight: 48.h),
padding: EdgeInsets.symmetric(horizontal: 6.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedRotation(
turns: expanded ? 0.25 : 0,
duration: duration,
curve: Curves.easeOutCubic,
child: FIcon(
FAssets.icons.chevronRight,
size: 19.sp,
color: XfjDesignTokens.primary,
),
),
SizedBox(width: 4.w),
Text(
expanded ? '收起' : '展开',
style: FTheme.of(context).typography.sm.copyWith(
color: XfjDesignTokens.primary,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
);
}
}
/// 交易记录和核销记录共用的行内订单详情,保持两类列表的展开层级和异常反馈一致。
class OrderRecordCollapsePanel extends StatelessWidget {
const OrderRecordCollapsePanel({
super.key,
required this.status,
required this.items,
required this.totalPrice,
required this.extraPrice,
required this.payPrice,
this.errorMessage = '',
this.onRetry,
this.cartEmptyMessage = '当前订单没有可展示的购物车明细',
});
final OrderRecordDetailLoadStatus status;
final List<OrderRecordDetailItem> items;
final int totalPrice;
final int extraPrice;
final int payPrice;
final String errorMessage;
final VoidCallback? onRetry;
final String cartEmptyMessage;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
decoration: BoxDecoration(
color: XfjDesignTokens.primarySoft.withValues(alpha: 0.34),
border: const Border(
top: BorderSide(color: Color(0x2E0094FF)),
),
),
child: switch (status) {
OrderRecordDetailLoadStatus.loading => _loading(context),
OrderRecordDetailLoadStatus.failure => _failure(context),
OrderRecordDetailLoadStatus.success => _content(context),
},
);
}
Widget _loading(BuildContext context) {
return SizedBox(
height: 136.h,
child: <Widget>[
SizedBox(
width: 28.w,
height: 28.w,
child: const CircularProgressIndicator(
color: XfjDesignTokens.primary,
strokeWidth: 3,
),
),
SizedBox(width: 12.w),
Text(
'正在查询购物车明细...',
style: FTheme.of(context).typography.base.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w600,
),
),
].toRow(mainAxisAlignment: MainAxisAlignment.center),
);
}
Widget _failure(BuildContext context) {
return SizedBox(
height: 136.h,
child: <Widget>[
FIcon(
FAssets.icons.circleAlert,
size: 28.sp,
color: XfjDesignTokens.danger,
),
SizedBox(width: 12.w),
<Widget>[
Text(
'购物车明细加载失败',
style: FTheme.of(context).typography.base.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 4.h),
Text(
errorMessage.isEmpty ? '请检查数据或网络连接后重试' : errorMessage,
style: FTheme.of(context).typography.sm.copyWith(
color: XfjDesignTokens.muted,
),
),
].toColumn(crossAxisAlignment: CrossAxisAlignment.start).expanded(),
if (onRetry != null)
TerminalToolbarButton(
label: '重新加载',
icon: FAssets.icons.refreshCw,
style: TerminalToolbarButtonStyle.secondary,
onTap: onRetry!,
),
].toRow(crossAxisAlignment: CrossAxisAlignment.center),
);
}
/// 展开区仅展示购物车,订单完整字段统一由完整详情承载。
Widget _content(BuildContext context) {
return _cartDetails(context);
}
/// 构建购物车商品表格及金额汇总。
Widget _cartDetails(BuildContext context) {
return <Widget>[
_sectionTitle(
context,
icon: FAssets.icons.shoppingCart,
title: '购物车明细',
suffix: '${items.length} 种商品',
),
SizedBox(height: 10.h),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: XfjDesignTokens.line),
borderRadius: BorderRadius.circular(10),
),
clipBehavior: Clip.hardEdge,
child: Column(
children: [
_cartRow(
context,
const ['商品', '数量/重量', '单价', '小计'],
header: true,
),
if (items.isEmpty)
SizedBox(
height: 72.h,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FIcon(
FAssets.icons.packageOpen,
size: 22.sp,
color: XfjDesignTokens.muted,
),
SizedBox(width: 8.w),
Flexible(
child: Text(
cartEmptyMessage,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: FTheme.of(context).typography.sm.copyWith(
color: XfjDesignTokens.muted,
),
),
),
],
),
)
else
...items.map(
(item) => _cartRow(
context,
[
item.name,
item.quantity,
item.unitPrice == null
? '-'
: AppPriceTool.fenToYuan(item.unitPrice!),
item.totalPrice == null
? '-'
: AppPriceTool.fenToYuan(item.totalPrice!),
],
),
),
_priceSummary(context),
],
),
),
].toColumn(crossAxisAlignment: CrossAxisAlignment.stretch);
}
/// 根据购物车区域宽度调整金额汇总行数,避免金额与标签互相覆盖。
Widget _priceSummary(BuildContext context) {
final theme = FTheme.of(context);
return Container(
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 10.h),
decoration: const BoxDecoration(
color: Color(0xFFFAFBFC),
border: Border(top: BorderSide(color: XfjDesignTokens.line)),
),
child: LayoutBuilder(
builder: (context, constraints) {
final compact = constraints.maxWidth < 640.w;
final total = _summaryMetric(
context,
label: '商品总价',
price: totalPrice,
textStyle: theme.typography.sm,
);
final extra = _summaryMetric(
context,
label: '额外支付',
price: extraPrice,
textStyle: theme.typography.sm,
);
final actual = _summaryMetric(
context,
label: '实际扣款',
price: payPrice,
textStyle: theme.typography.xl2.copyWith(
fontWeight: FontWeight.w800,
),
emphasized: true,
);
if (compact) {
return <Widget>[
<Widget>[total.expanded(), extra.expanded()].toRow(),
SizedBox(height: 8.h),
actual.alignRight(),
].toColumn(crossAxisAlignment: CrossAxisAlignment.stretch);
}
return <Widget>[
total.expanded(),
extra.expanded(),
actual.expanded(),
].toRow();
},
),
);
}
/// 构建单项金额指标,超长金额会在自身区域内缩放而不撑破布局。
Widget _summaryMetric(
BuildContext context, {
required String label,
required int price,
required TextStyle textStyle,
bool emphasized = false,
}) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
emphasized ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Text(
label,
style: textStyle.copyWith(
color: emphasized ? XfjDesignTokens.ink : XfjDesignTokens.muted,
fontWeight: emphasized ? FontWeight.w700 : FontWeight.w500,
),
),
SizedBox(width: 6.w),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: appPrice(context, price, textStyle: textStyle),
),
),
],
);
}
Widget _sectionTitle(
BuildContext context, {
required SvgAsset icon,
required String title,
String? suffix,
}) {
return <Widget>[
FIcon(icon, size: 20.sp, color: XfjDesignTokens.primary),
SizedBox(width: 8.w),
Text(
title,
style: FTheme.of(context).typography.base.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w800,
),
),
if (suffix != null) ...[
SizedBox(width: 8.w),
Text(
suffix,
style: FTheme.of(context).typography.sm.copyWith(
color: XfjDesignTokens.muted,
),
),
],
].toRow(mainAxisSize: MainAxisSize.min);
}
Widget _cartRow(
BuildContext context,
List<String> values, {
bool header = false,
}) {
final style = FTheme.of(context).typography.sm.copyWith(
color: header ? XfjDesignTokens.muted : XfjDesignTokens.ink,
fontWeight: header ? FontWeight.w700 : FontWeight.w500,
);
return Container(
constraints: BoxConstraints(minHeight: header ? 40.h : 48.h),
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 6.h),
decoration: BoxDecoration(
color: header ? const Color(0xFFF7F8FA) : Colors.white,
border: header
? null
: const Border(top: BorderSide(color: XfjDesignTokens.line)),
),
child: Row(
children: [
Tooltip(
message: values[0],
child: Text(
values[0],
maxLines: header ? 1 : 2,
overflow: TextOverflow.ellipsis,
style: style.copyWith(
fontWeight: header ? FontWeight.w700 : FontWeight.w600,
),
),
).expanded(flex: 5),
Text(
values[1],
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: style,
).expanded(flex: 2),
Text(
values[2],
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
style: style,
).expanded(flex: 2),
Text(
values[3],
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
style: style.copyWith(fontWeight: FontWeight.w700),
).expanded(flex: 2),
],
),
);
}
}
@@ -0,0 +1,624 @@
import 'package:common_utils/common_utils.dart';
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:forui/forui.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
/// 核销订单完整详情抽屉,承载展开区不展示的订单基础字段和远程购物车快照。
class VerificationOrderDetailDrawer extends StatelessWidget {
/// 创建由页面状态驱动的核销订单完整详情抽屉。
const VerificationOrderDetailDrawer({
super.key,
required this.record,
required this.status,
required this.order,
required this.items,
required this.errorMessage,
required this.onRetry,
required this.onClose,
});
/// 列表中的核销记录,作为远程详情缺省字段和抽屉标题数据源。
final ServiceVerificationRecord record;
/// 订单和购物车远程查询状态。
final OrderRecordDetailLoadStatus status;
/// 服务端返回的完整订单信息,加载失败或加载中时为空。
final ServiceHardOrder? order;
/// 服务端返回的订单购物车快照。
final List<ServiceCar> items;
/// 查询失败时展示给操作员的恢复提示。
final String errorMessage;
/// 重新请求订单和购物车数据。
final VoidCallback onRetry;
/// 关闭右侧详情抽屉。
final VoidCallback onClose;
/// 构建带遮罩的右侧宽抽屉,并适配收银终端安全区域。
@override
Widget build(BuildContext context) {
final theme = FTheme.of(context);
return Positioned.fill(
child: ColoredBox(
color: Colors.black.withValues(alpha: 0.35),
child: Align(
alignment: Alignment.centerRight,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 1240.w),
child: SizedBox(
width: double.infinity,
child: SafeArea(
child: ColoredBox(
color: XfjDesignTokens.canvas,
child: Column(
children: [
_header(context, theme),
_body(context, theme).expanded(),
],
),
),
),
),
),
),
),
);
}
/// 构建紧凑标题区域,保留长订单号提示和标准关闭触控范围。
Widget _header(BuildContext context, FThemeData theme) {
final orderCode = _text(order?.orderNo, fallback: record.orderNo);
return Container(
constraints: BoxConstraints(minHeight: 64.h),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.h),
decoration: const BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(color: XfjDesignTokens.line),
),
),
child: Row(
children: [
Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: XfjDesignTokens.primarySoft,
borderRadius: BorderRadius.circular(12),
),
child: FIcon(
FAssets.icons.fileText,
color: XfjDesignTokens.primary,
).center(),
),
SizedBox(width: 12.w),
Expanded(
child: Tooltip(
message: '订单号:$orderCode',
child: <Widget>[
Text(
'订单详情',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.typography.xl.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w800,
),
),
Text(
'订单号:$orderCode',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.typography.sm.copyWith(
color: XfjDesignTokens.muted,
),
),
].toColumn(crossAxisAlignment: CrossAxisAlignment.start),
),
),
SizedBox(width: 8.w),
Semantics(
button: true,
label: '关闭订单详情',
child: InkWell(
onTap: onClose,
borderRadius: BorderRadius.circular(10),
child: FIcon(
FAssets.icons.x,
color: XfjDesignTokens.muted,
size: 24.w,
).center().constrained(
minWidth: 48.w,
minHeight: 48.h,
),
),
),
],
),
);
}
/// 根据远程请求状态切换加载、失败和完整详情内容。
Widget _body(BuildContext context, FThemeData theme) {
return switch (status) {
OrderRecordDetailLoadStatus.loading => _loading(context),
OrderRecordDetailLoadStatus.failure => _failure(context),
OrderRecordDetailLoadStatus.success when order == null => _failure(
context,
detail: '服务端未返回订单详情,请重试',
),
OrderRecordDetailLoadStatus.success => _content(context, theme, order!),
};
}
/// 构建远程订单查询中的反馈状态。
Widget _loading(BuildContext context) {
return <Widget>[
SizedBox(
width: 32.w,
height: 32.w,
child: const CircularProgressIndicator(
color: XfjDesignTokens.primary,
strokeWidth: 3,
),
),
SizedBox(height: 14.h),
Text(
'正在查询订单完整详情...',
style: FTheme.of(context).typography.base.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w600,
),
),
].toColumn(mainAxisAlignment: MainAxisAlignment.center).center();
}
/// 构建可重试的远程详情加载失败状态。
Widget _failure(BuildContext context, {String? detail}) {
return <Widget>[
FIcon(
FAssets.icons.circleAlert,
size: 42.sp,
color: XfjDesignTokens.danger,
),
SizedBox(height: 14.h),
Text(
'订单详情加载失败',
style: FTheme.of(context).typography.lg.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 6.h),
Text(
detail ?? (errorMessage.isEmpty ? '请检查网络连接后重试' : errorMessage),
textAlign: TextAlign.center,
style: FTheme.of(context).typography.sm.copyWith(
color: XfjDesignTokens.muted,
),
),
SizedBox(height: 16.h),
TerminalToolbarButton(
label: '重新加载',
icon: FAssets.icons.refreshCw,
style: TerminalToolbarButtonStyle.secondary,
onTap: onRetry,
),
].toColumn(mainAxisAlignment: MainAxisAlignment.center).center();
}
/// 按可用宽度横向或纵向排列订单信息与商品明细。
Widget _content(
BuildContext context,
FThemeData theme,
ServiceHardOrder detail,
) {
final orderInfo = _orderInfo(detail);
final productInfo = _productInfo(context, theme, detail);
return Padding(
padding: EdgeInsets.all(16.w),
child: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 720.w) {
return <Widget>[
orderInfo.expanded(flex: 5),
SizedBox(height: 12.h),
productInfo.expanded(flex: 7),
].toColumn(crossAxisAlignment: CrossAxisAlignment.stretch);
}
return <Widget>[
orderInfo.expanded(flex: 4),
SizedBox(width: 12.w),
productInfo.expanded(flex: 6),
].toRow(crossAxisAlignment: CrossAxisAlignment.stretch);
},
),
);
}
/// 构建可独立滚动的订单与核销基础信息区域。
Widget _orderInfo(ServiceHardOrder detail) {
final mealType = ProductHourType.values.fromCode(
detail.hourType ?? detail.apptHourType ?? '',
);
return TerminalSurface(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
child: ListView(
padding: EdgeInsets.zero,
children: [
_VerificationDetailLine(
label: '服务端订单 ID',
value: _text(detail.id, fallback: '${record.orderId}'),
),
_VerificationDetailLine(
label: '业务订单号',
value: _text(detail.orderNo, fallback: record.orderNo),
),
_VerificationDetailLine(
label: '用户唯一标识',
value: _text(detail.userId),
),
_VerificationDetailLine(
label: '用户名',
value: _text(detail.username, fallback: record.username),
),
_VerificationDetailLine(
label: '用户部门',
value: _text(
detail.userDeptName,
fallback: record.userDeptName ?? '未提供',
),
),
_VerificationDetailLine(
label: '订单类型',
value: _enumText(
JsonTool.stringToOrderType(detail.orderType),
detail.orderType,
),
),
_VerificationDetailLine(
label: '订单状态',
value: _enumText(
OrderStatus.values.fromCode(detail.orderStatus),
detail.orderStatus,
),
),
_VerificationDetailLine(
label: '支付方式',
value: _enumText(
JsonTool.stringToOrderPayType(detail.payType),
detail.payType,
),
),
_VerificationDetailLine(
label: '用餐时段',
value: mealType?.title ??
_text(detail.hourType ?? detail.apptHourType),
),
_VerificationDetailLine(
label: '核销类型',
value: record.verificationTypeName,
),
_VerificationDetailLine(
label: '核销方式',
value: record.payTypeName,
),
_VerificationDetailLine(
label: '核销时间',
value: DateUtil.formatDate(record.verificationTime),
),
_VerificationDetailLine(
label: '商品摘要',
value: _text(detail.productDesc, fallback: record.productDesc),
),
_VerificationDetailLine(
label: '终端订单号',
value: _text(detail.androidOrderCode),
),
_VerificationDetailLine(
label: '下单时间',
value: detail.createTime == null
? '未提供'
: DateUtil.formatDate(detail.createTime),
),
],
),
);
}
/// 构建商品列表和固定在底部的订单金额汇总。
Widget _productInfo(
BuildContext context,
FThemeData theme,
ServiceHardOrder detail,
) {
return TerminalSurface(
padding: EdgeInsets.fromLTRB(16.w, 14.h, 16.w, 12.h),
child: Column(
children: [
Text(
'商品明细',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.typography.xl.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w800,
),
).alignLeft(),
SizedBox(height: 10.h),
_productRow(
context,
theme,
name: '商品',
quantity: '数量/重量',
unitPrice: '单价',
totalPrice: '小计',
header: true,
),
SizedBox(height: 4.h),
if (items.isEmpty)
<Widget>[
FIcon(
FAssets.icons.packageOpen,
size: 24.sp,
color: XfjDesignTokens.muted,
),
SizedBox(width: 8.w),
Flexible(
child: Text(
'当前订单没有可展示的购物车明细',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: theme.typography.sm.copyWith(
color: XfjDesignTokens.muted,
),
),
),
]
.toRow(mainAxisAlignment: MainAxisAlignment.center)
.center()
.expanded()
else
ListView.separated(
padding: EdgeInsets.zero,
itemCount: items.length,
separatorBuilder: (_, __) => const Divider(
color: XfjDesignTokens.line,
height: 1,
),
itemBuilder: (context, index) {
final item = items[index];
return _productRow(
context,
theme,
name: _text(item.productName, fallback: '未命名商品'),
quantity: _quantityText(item),
unitPrice: item.productPrice,
totalPrice: item.totalPrice,
);
},
).expanded(),
_priceSummary(context, theme, detail),
],
),
);
}
/// 构建商品表头或商品数据行,并稳定数量及金额列宽。
Widget _productRow(
BuildContext context,
FThemeData theme, {
required String name,
required String quantity,
required dynamic unitPrice,
required dynamic totalPrice,
bool header = false,
}) {
final style = theme.typography.sm.copyWith(
color: header ? XfjDesignTokens.muted : XfjDesignTokens.ink,
fontWeight: header ? FontWeight.w700 : FontWeight.w500,
fontFeatures: const [FontFeature.tabularFigures()],
);
return SizedBox(
height: header ? 34.h : 58.h,
child: Row(
children: [
Expanded(
child: Tooltip(
message: name,
child: Text(
name,
maxLines: header ? 1 : 2,
overflow: TextOverflow.ellipsis,
style: style,
),
),
),
SizedBox(width: 8.w),
SizedBox(
width: 82.w,
child: Text(
quantity,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: style,
),
),
SizedBox(width: 8.w),
SizedBox(
width: 92.w,
child: header
? Text(
unitPrice as String,
maxLines: 1,
textAlign: TextAlign.right,
style: style,
)
: _priceValue(context, unitPrice, style),
),
SizedBox(width: 8.w),
SizedBox(
width: 92.w,
child: header
? Text(
totalPrice as String,
maxLines: 1,
textAlign: TextAlign.right,
style: style,
)
: _priceValue(context, totalPrice, style),
),
],
),
);
}
/// 将金额限制在固定列宽内,避免大额订单撑破表格。
Widget _priceValue(BuildContext context, dynamic price, TextStyle style) {
return Align(
alignment: Alignment.centerRight,
child: FittedBox(
fit: BoxFit.scaleDown,
child: appPrice(context, price ?? 0, textStyle: style),
),
);
}
/// 构建商品总价、额外支付和实际扣款汇总。
Widget _priceSummary(
BuildContext context,
FThemeData theme,
ServiceHardOrder detail,
) {
return Container(
margin: EdgeInsets.only(top: 10.h),
padding: EdgeInsets.only(top: 10.h),
decoration: const BoxDecoration(
border: Border(top: BorderSide(color: XfjDesignTokens.primary)),
),
child: <Widget>[
_summaryLine(
context, theme, '商品总价', detail.totalPrice ?? record.totalPrice),
_summaryLine(context, theme, '额外支付', detail.extraPrice ?? 0),
_summaryLine(
context,
theme,
'实际扣款',
detail.payPrice ?? record.totalPrice,
emphasized: true,
),
].toColumn(),
);
}
/// 构建一行金额汇总,并可突出实际扣款。
Widget _summaryLine(
BuildContext context,
FThemeData theme,
String label,
int price, {
bool emphasized = false,
}) {
final style =
(emphasized ? theme.typography.lg : theme.typography.base).copyWith(
color: emphasized ? XfjDesignTokens.ink : XfjDesignTokens.muted,
fontWeight: emphasized ? FontWeight.w800 : FontWeight.w600,
);
return Container(
constraints: BoxConstraints(minHeight: 38.h),
padding: EdgeInsets.symmetric(vertical: 4.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: style.copyWith(height: 1.15),
),
SizedBox(width: 8.w),
Flexible(
child: Align(
alignment: Alignment.centerRight,
child: FittedBox(
fit: BoxFit.scaleDown,
child: appPrice(context, price, textStyle: style),
),
),
),
],
),
);
}
/// 按商品性质生成件数或重量展示文本。
String _quantityText(ServiceCar item) {
if (item.productNature == ProductNature.WEIGHT.code ||
item.productNature == ProductNature.WEIGHT.name) {
final weight = item.productWeight;
return weight == null ? '称重商品' : '${weight.toStringAsFixed(2)} kg';
}
return item.productCount?.toString() ?? '-';
}
/// 优先展示已识别枚举标题,未知编码回退为接口原值。
String _enumText<T extends BaseEnum>(T? value, String? raw) {
return value?.title ?? _text(raw);
}
/// 清理接口字符串,并为缺失字段提供统一占位文本。
String _text(String? value, {String fallback = '未提供'}) {
final result = value?.trim();
return result == null || result.isEmpty ? fallback : result;
}
}
/// 详情抽屉中的只读字段,长标识支持省略并可通过悬浮提示查看完整值。
class _VerificationDetailLine extends StatelessWidget {
/// 创建一项订单详情只读字段。
const _VerificationDetailLine({required this.label, required this.value});
/// 字段名称。
final String label;
/// 字段展示值。
final String value;
/// 构建可省略长值并通过悬浮提示查看全文的字段。
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 12.h),
child: <Widget>[
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: FTheme.of(context).typography.sm.copyWith(
color: XfjDesignTokens.muted,
),
),
SizedBox(height: 6.h),
Tooltip(
message: value,
child: Text(
value,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: FTheme.of(context).typography.base.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w600,
),
),
),
].toColumn(crossAxisAlignment: CrossAxisAlignment.start),
);
}
}