feat:机器上可以查询全部订单
This commit is contained in:
1 parent
88ff8988fb
commit
a6fc25ddce
13 files changed
+956
-388
No files matched your search
+5
-1
@@ -45,4 +45,8 @@ app.*.map.json
|
||||
/android/app/release
|
||||
|
||||
# FVM Version Cache
|
||||
.fvm/
|
||||
.fvm/
|
||||
# AI assist files
|
||||
.video_agent/
|
||||
.mimosa/
|
||||
.v2c/
|
||||
@@ -189,6 +189,22 @@ abstract class HardApi {
|
||||
return ServiceVerificationRecordPage.fromJson(res.data);
|
||||
}
|
||||
|
||||
/// 分页查询当前设备的交易记录;scope=MERCHANT 时按终端所属商户查询全部设备。
|
||||
static Future<ServiceTerminalOrderRecordPage?> terminalOrderRecordPage({
|
||||
required int current,
|
||||
required int size,
|
||||
required TerminalOrderRecordQueryRequest request,
|
||||
}) async {
|
||||
final res = await HttpManage().dio.post(
|
||||
'/hard/hard-api/ad/order-record/page',
|
||||
queryParameters: {'current': current, 'size': size},
|
||||
data: request.toJson(),
|
||||
options: dio.Options(headers: {'DEVICE-ID': '${request.hardId}'}),
|
||||
);
|
||||
if (res.data == null) return null;
|
||||
return ServiceTerminalOrderRecordPage.fromJson(res.data);
|
||||
}
|
||||
|
||||
/// 创建硬件订单
|
||||
static Future<String?> createHardwareOrder(dynamic formData) async {
|
||||
final res = await HttpManage().dio.post(
|
||||
|
||||
@@ -660,6 +660,7 @@ class VerificationRecordQueryRequest {
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
this.verificationType,
|
||||
this.scope,
|
||||
});
|
||||
|
||||
final int hardId;
|
||||
@@ -667,12 +668,118 @@ class VerificationRecordQueryRequest {
|
||||
final String endDate;
|
||||
final String? verificationType;
|
||||
|
||||
/// 数据范围:留空仅本机,MERCHANT 按终端所属商户查询全部设备。
|
||||
final String? scope;
|
||||
|
||||
factory VerificationRecordQueryRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$VerificationRecordQueryRequestFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$VerificationRecordQueryRequestToJson(this);
|
||||
}
|
||||
|
||||
/// 终端交易记录的服务端查询参数,数据范围由服务端按设备归属解析。
|
||||
@JsonSerializable(includeIfNull: false)
|
||||
class TerminalOrderRecordQueryRequest {
|
||||
const TerminalOrderRecordQueryRequest({
|
||||
required this.hardId,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
this.scope,
|
||||
this.orderStatusCodes,
|
||||
this.hourType,
|
||||
});
|
||||
|
||||
final int hardId;
|
||||
final String startDate;
|
||||
final String endDate;
|
||||
|
||||
/// 数据范围:留空仅本机,MERCHANT 按终端所属商户查询全部设备。
|
||||
final String? scope;
|
||||
|
||||
/// 订单状态编码集合,留空查询全部状态。
|
||||
final List<String>? orderStatusCodes;
|
||||
|
||||
/// 用餐时段编码,留空或 -1 不区分。
|
||||
final String? hourType;
|
||||
|
||||
factory TerminalOrderRecordQueryRequest.fromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$TerminalOrderRecordQueryRequestFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() =>
|
||||
_$TerminalOrderRecordQueryRequestToJson(this);
|
||||
}
|
||||
|
||||
/// 交易记录远端行只承载列表展示字段,金额与时间口径和本地订单一致。
|
||||
@JsonSerializable()
|
||||
class ServiceTerminalOrderRecord {
|
||||
const ServiceTerminalOrderRecord({
|
||||
required this.orderId,
|
||||
required this.orderNo,
|
||||
required this.orderType,
|
||||
required this.orderTypeName,
|
||||
required this.orderStatus,
|
||||
required this.orderStatusName,
|
||||
required this.payType,
|
||||
required this.payTypeName,
|
||||
required this.username,
|
||||
required this.productDesc,
|
||||
required this.totalPrice,
|
||||
required this.extraPrice,
|
||||
required this.payPrice,
|
||||
this.userDeptName,
|
||||
this.hourType,
|
||||
this.hardName,
|
||||
this.createTime,
|
||||
});
|
||||
|
||||
/// 后端 Long 为避免跨端精度丢失可能序列化为字符串,终端同时兼容数字返回。
|
||||
@JsonKey(fromJson: JsonTool.anyToInt)
|
||||
final int orderId;
|
||||
final String orderNo;
|
||||
final String orderType;
|
||||
final String orderTypeName;
|
||||
final String orderStatus;
|
||||
final String orderStatusName;
|
||||
final String payType;
|
||||
final String payTypeName;
|
||||
final String username;
|
||||
final String? userDeptName;
|
||||
final String productDesc;
|
||||
final String? hourType;
|
||||
|
||||
/// 完成交易的设备名称;全部数据范围下用于区分不同机器。
|
||||
final String? hardName;
|
||||
|
||||
/// 金额仍按整数分使用,同时兼容部分环境将数值序列化为字符串。
|
||||
@JsonKey(fromJson: JsonTool.anyToInt)
|
||||
final int totalPrice;
|
||||
@JsonKey(fromJson: JsonTool.anyToInt)
|
||||
final int extraPrice;
|
||||
@JsonKey(fromJson: JsonTool.anyToInt)
|
||||
final int payPrice;
|
||||
final DateTime? createTime;
|
||||
|
||||
factory ServiceTerminalOrderRecord.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceTerminalOrderRecordFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceTerminalOrderRecordToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceTerminalOrderRecordPage extends ServicePageBase {
|
||||
ServiceTerminalOrderRecordPage({this.records});
|
||||
|
||||
final List<ServiceTerminalOrderRecord>? records;
|
||||
|
||||
factory ServiceTerminalOrderRecordPage.fromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$ServiceTerminalOrderRecordPageFromJson(json);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => _$ServiceTerminalOrderRecordPageToJson(this);
|
||||
}
|
||||
|
||||
/// 核销记录只承载列表展示字段,不复用本地订单实体,避免混淆远端与本地状态。
|
||||
@JsonSerializable()
|
||||
class ServiceVerificationRecord {
|
||||
|
||||
@@ -368,6 +368,7 @@ VerificationRecordQueryRequest _$VerificationRecordQueryRequestFromJson(
|
||||
startDate: json['startDate'] as String,
|
||||
endDate: json['endDate'] as String,
|
||||
verificationType: json['verificationType'] as String?,
|
||||
scope: json['scope'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$VerificationRecordQueryRequestToJson(
|
||||
@@ -385,9 +386,112 @@ Map<String, dynamic> _$VerificationRecordQueryRequestToJson(
|
||||
}
|
||||
|
||||
writeNotNull('verificationType', instance.verificationType);
|
||||
writeNotNull('scope', instance.scope);
|
||||
return val;
|
||||
}
|
||||
|
||||
TerminalOrderRecordQueryRequest _$TerminalOrderRecordQueryRequestFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
TerminalOrderRecordQueryRequest(
|
||||
hardId: (json['hardId'] as num).toInt(),
|
||||
startDate: json['startDate'] as String,
|
||||
endDate: json['endDate'] as String,
|
||||
scope: json['scope'] as String?,
|
||||
orderStatusCodes: (json['orderStatusCodes'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
hourType: json['hourType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$TerminalOrderRecordQueryRequestToJson(
|
||||
TerminalOrderRecordQueryRequest instance) {
|
||||
final val = <String, dynamic>{
|
||||
'hardId': instance.hardId,
|
||||
'startDate': instance.startDate,
|
||||
'endDate': instance.endDate,
|
||||
};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
if (value != null) {
|
||||
val[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
writeNotNull('scope', instance.scope);
|
||||
writeNotNull('orderStatusCodes', instance.orderStatusCodes);
|
||||
writeNotNull('hourType', instance.hourType);
|
||||
return val;
|
||||
}
|
||||
|
||||
ServiceTerminalOrderRecord _$ServiceTerminalOrderRecordFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
ServiceTerminalOrderRecord(
|
||||
orderId: JsonTool.anyToInt(json['orderId']),
|
||||
orderNo: json['orderNo'] as String,
|
||||
orderType: json['orderType'] as String,
|
||||
orderTypeName: json['orderTypeName'] as String,
|
||||
orderStatus: json['orderStatus'] as String,
|
||||
orderStatusName: json['orderStatusName'] as String,
|
||||
payType: json['payType'] as String,
|
||||
payTypeName: json['payTypeName'] as String,
|
||||
username: json['username'] as String,
|
||||
productDesc: json['productDesc'] as String,
|
||||
totalPrice: JsonTool.anyToInt(json['totalPrice']),
|
||||
extraPrice: JsonTool.anyToInt(json['extraPrice']),
|
||||
payPrice: JsonTool.anyToInt(json['payPrice']),
|
||||
userDeptName: json['userDeptName'] as String?,
|
||||
hourType: json['hourType'] as String?,
|
||||
hardName: json['hardName'] as String?,
|
||||
createTime: json['createTime'] == null
|
||||
? null
|
||||
: DateTime.parse(json['createTime'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServiceTerminalOrderRecordToJson(
|
||||
ServiceTerminalOrderRecord instance) =>
|
||||
<String, dynamic>{
|
||||
'orderId': instance.orderId,
|
||||
'orderNo': instance.orderNo,
|
||||
'orderType': instance.orderType,
|
||||
'orderTypeName': instance.orderTypeName,
|
||||
'orderStatus': instance.orderStatus,
|
||||
'orderStatusName': instance.orderStatusName,
|
||||
'payType': instance.payType,
|
||||
'payTypeName': instance.payTypeName,
|
||||
'username': instance.username,
|
||||
'userDeptName': instance.userDeptName,
|
||||
'productDesc': instance.productDesc,
|
||||
'hourType': instance.hourType,
|
||||
'hardName': instance.hardName,
|
||||
'totalPrice': instance.totalPrice,
|
||||
'extraPrice': instance.extraPrice,
|
||||
'payPrice': instance.payPrice,
|
||||
'createTime': instance.createTime?.toIso8601String(),
|
||||
};
|
||||
|
||||
ServiceTerminalOrderRecordPage _$ServiceTerminalOrderRecordPageFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
ServiceTerminalOrderRecordPage(
|
||||
records: (json['records'] as List<dynamic>?)
|
||||
?.map((e) =>
|
||||
ServiceTerminalOrderRecord.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
)
|
||||
..current = (json['current'] as num).toInt()
|
||||
..pages = (json['pages'] as num).toInt()
|
||||
..size = (json['size'] as num).toInt()
|
||||
..total = (json['total'] as num).toInt();
|
||||
|
||||
Map<String, dynamic> _$ServiceTerminalOrderRecordPageToJson(
|
||||
ServiceTerminalOrderRecordPage instance) =>
|
||||
<String, dynamic>{
|
||||
'current': instance.current,
|
||||
'pages': instance.pages,
|
||||
'size': instance.size,
|
||||
'total': instance.total,
|
||||
'records': instance.records,
|
||||
};
|
||||
|
||||
ServiceVerificationRecord _$ServiceVerificationRecordFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
ServiceVerificationRecord(
|
||||
|
||||
@@ -16,6 +16,7 @@ export './page/order_page_models.dart';
|
||||
export './page/product_meal_period_models.dart';
|
||||
export './page/reservation_menu_page_models.dart';
|
||||
export './page/setting_page_enum_models.dart';
|
||||
export './page/terminal_data_scope.dart';
|
||||
export './page/verification_record_page_models.dart';
|
||||
export './page/wallet_page_enum_models.dart';
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/// 记录列表的数据范围。
|
||||
///
|
||||
/// [device] 仅查询本机数据;[merchant] 按当前终端权限(所属商户)查询全部设备数据,
|
||||
/// 需要网络可用,服务端负责解析设备归属,客户端不指定商户范围。
|
||||
enum TerminalDataScope {
|
||||
device('本机数据'),
|
||||
merchant('全部数据');
|
||||
|
||||
final String title;
|
||||
|
||||
const TerminalDataScope(this.title);
|
||||
|
||||
/// 服务端接口的 scope 协议值;本机范围沿用接口默认值,不额外传参。
|
||||
String? get requestCode =>
|
||||
this == TerminalDataScope.merchant ? 'MERCHANT' : null;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
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';
|
||||
|
||||
/// 记录列表顶部的数据范围切换(本机数据 / 全部数据)。
|
||||
///
|
||||
/// 采用与状态筛选一致的胶囊语言:外层灰底承载选项,选中项以主题色填充,
|
||||
/// 触控高度不小于 44,选中态同时用颜色和字重表达,避免仅靠颜色区分。
|
||||
class TerminalDataScopeTabs extends StatelessWidget {
|
||||
const TerminalDataScopeTabs({
|
||||
super.key,
|
||||
required this.scope,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final TerminalDataScope scope;
|
||||
final ValueChanged<TerminalDataScope> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(3.w),
|
||||
decoration: BoxDecoration(
|
||||
color: XfjDesignTokens.control,
|
||||
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: TerminalDataScope.values
|
||||
.map(
|
||||
(item) => _ScopeTabButton(
|
||||
label: item.title,
|
||||
selected: scope == item,
|
||||
onTap: () => onChanged(item),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ScopeTabButton extends StatelessWidget {
|
||||
const _ScopeTabButton({
|
||||
required this.label,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = FTheme.of(context);
|
||||
return Semantics(
|
||||
button: true,
|
||||
selected: selected,
|
||||
label: '数据范围:$label',
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius - 2),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
curve: Curves.easeOut,
|
||||
constraints: BoxConstraints(minHeight: 40.h),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? XfjDesignTokens.primary : Colors.transparent,
|
||||
borderRadius:
|
||||
BorderRadius.circular(XfjDesignTokens.controlRadius - 2),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: theme.typography.base.copyWith(
|
||||
color: selected ? Colors.white : const Color(0xFF344054),
|
||||
fontWeight: selected ? FontWeight.w700 : FontWeight.w600,
|
||||
),
|
||||
).center(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ export './app/app_wallet_selector.dart';
|
||||
export './app/terminal_shell.dart';
|
||||
export './app/terminal_device_status.dart';
|
||||
export './app/terminal_toolbar.dart';
|
||||
export './app/terminal_data_scope_tabs.dart';
|
||||
|
||||
export './cashier/cashier_workspace.dart';
|
||||
export './face/face_recognition_preview_layer.dart';
|
||||
|
||||
@@ -21,6 +21,9 @@ class OrderHistoryController extends GetxController {
|
||||
|
||||
Rx<OrderStatus> currentStatus = OrderStatus.TOTAL.obs;
|
||||
|
||||
/// 列表数据范围;全部数据按终端所属商户在线查询,本机数据读本地数据库。
|
||||
final scope = TerminalDataScope.device.obs;
|
||||
|
||||
RxList<OrderHistoryTableItem> tabList = <OrderHistoryTableItem>[].obs;
|
||||
|
||||
RxList<String> saleReasons = <String>[].obs;
|
||||
@@ -100,6 +103,10 @@ class OrderHistoryController extends GetxController {
|
||||
// 订单列表
|
||||
Future<void> getOrder() async {
|
||||
_collapseExpandedOrder();
|
||||
if (scope.value == TerminalDataScope.merchant) {
|
||||
await _getRemoteOrder();
|
||||
return;
|
||||
}
|
||||
final appSetting = await AppSettingTool.getSysSetting();
|
||||
final sysType = appSetting.sysType.name;
|
||||
final orderDao = appC.dataBase.getOrderDao;
|
||||
@@ -138,11 +145,160 @@ class OrderHistoryController extends GetxController {
|
||||
orderList.value = list;
|
||||
}
|
||||
|
||||
/// 展开列表订单并按需查询本地购物车;再次点击同一笔订单时收起。
|
||||
/// 切换数据范围;全部数据依赖网络,离线时提示并保持本机数据。
|
||||
Future<void> doScope(TerminalDataScope target) async {
|
||||
if (scope.value == target) return;
|
||||
if (target == TerminalDataScope.merchant && !AppNetworkTool.isAutoNetwork()) {
|
||||
Toast.error(msg: '当前没有网络,无法查询全部数据');
|
||||
return;
|
||||
}
|
||||
scope.value = target;
|
||||
currentPage.value = 1;
|
||||
EasyLoading.show(
|
||||
status: '切换中...',
|
||||
maskType: EasyLoadingMaskType.black,
|
||||
);
|
||||
try {
|
||||
await getTotalList();
|
||||
await getOrder();
|
||||
} finally {
|
||||
EasyLoading.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/// 全部数据范围:按终端权限在线分页查询,服务端负责解析商户范围。
|
||||
Future<void> _getRemoteOrder() async {
|
||||
final appSetting = await AppSettingTool.getSysSetting();
|
||||
final hardId = int.tryParse(appSetting.serviceId ?? '');
|
||||
if (hardId == null) {
|
||||
totalPage.value = 0;
|
||||
orderList.clear();
|
||||
Toast.error(msg: '设备尚未绑定,请先前往设置完成绑定');
|
||||
return;
|
||||
}
|
||||
final orderStatusCodes = currentStatus.value == OrderStatus.TOTAL
|
||||
? null
|
||||
: [currentStatus.value.code];
|
||||
String? hourTypeCode = hourType.value.code;
|
||||
if (appSetting.sysType == SysType.SUPERMARKET) {
|
||||
hourTypeCode = null;
|
||||
}
|
||||
try {
|
||||
final response = await HardApi.terminalOrderRecordPage(
|
||||
current: currentPage.value,
|
||||
size: pageSize.value,
|
||||
request: TerminalOrderRecordQueryRequest(
|
||||
hardId: hardId,
|
||||
startDate: DateUtil.formatDate(
|
||||
selectDateTime[0],
|
||||
format: DateFormats.y_mo_d,
|
||||
),
|
||||
endDate: DateUtil.formatDate(
|
||||
selectDateTime[1],
|
||||
format: DateFormats.y_mo_d,
|
||||
),
|
||||
scope: scope.value.requestCode,
|
||||
orderStatusCodes: orderStatusCodes,
|
||||
hourType: hourTypeCode,
|
||||
),
|
||||
);
|
||||
// 服务端暂不支持关键词模糊查询,与核销记录一致仅过滤当前页。
|
||||
final keyword = keywordC.text.trim().toLowerCase();
|
||||
final records = (response?.records ?? const [])
|
||||
.where((record) =>
|
||||
keyword.isEmpty ||
|
||||
record.orderNo.toLowerCase().contains(keyword) ||
|
||||
record.username.toLowerCase().contains(keyword) ||
|
||||
record.productDesc.toLowerCase().contains(keyword))
|
||||
.map(_toOrderEntity)
|
||||
.toList(growable: false);
|
||||
totalPage.value = response?.total ?? 0;
|
||||
orderList.value = records;
|
||||
} catch (error, stack) {
|
||||
totalPage.value = 0;
|
||||
orderList.clear();
|
||||
Toast.error(msg: '查询全部数据失败,请检查网络后重试');
|
||||
logger.e('|交易记录|查询全部数据失败|error:$error|stack:$stack|');
|
||||
}
|
||||
}
|
||||
|
||||
/// 远端交易记录映射为本地订单实体;枚举缺码时按语义就近降级。
|
||||
OrderEntity _toOrderEntity(ServiceTerminalOrderRecord record) {
|
||||
final orderType = OrderType.values.firstWhere(
|
||||
(item) => item.code == record.orderType,
|
||||
orElse: () => OrderType.BELOW,
|
||||
);
|
||||
final payType = OrderPayType.values.firstWhere(
|
||||
(item) => item.code == record.payType,
|
||||
orElse: () => OrderPayType.WALLET_DEFAULT,
|
||||
);
|
||||
final status = OrderStatus.values.firstWhere(
|
||||
(item) => item.code == record.orderStatus,
|
||||
orElse: () => OrderStatus.PAY,
|
||||
);
|
||||
return OrderEntity(
|
||||
uniqueCode: record.orderNo,
|
||||
serviceUniqueCode: '${record.orderId}',
|
||||
sysType: appSetting.sysType,
|
||||
userCode: '',
|
||||
username: record.username,
|
||||
hourType: record.hourType,
|
||||
price: record.totalPrice,
|
||||
extraPrice: record.extraPrice,
|
||||
payPrice: record.payPrice,
|
||||
type: orderType,
|
||||
status: status,
|
||||
payType: payType,
|
||||
productDesc: record.productDesc,
|
||||
count: 0,
|
||||
weight: 0,
|
||||
createTime: record.createTime ?? DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 远端购物车快照映射为本地购物车实体,供展开明细与详情抽屉复用展示。
|
||||
Future<List<ShopCardEntity>> _loadRemoteShopCards(int orderId) async {
|
||||
final cars = await HardApi.getCarByOrderId({'orderId': orderId}) ?? [];
|
||||
return cars
|
||||
.map(
|
||||
(car) => ShopCardEntity(
|
||||
orderId: orderId,
|
||||
productServiceUniqueCode: car.productId ?? '',
|
||||
productName: car.productName ?? '未知商品',
|
||||
productPic: car.productPic ?? '',
|
||||
productPrice: car.productPrice ?? 0,
|
||||
nature: car.productNature == ProductNature.WEIGHT.code
|
||||
? ProductNature.WEIGHT
|
||||
: ProductNature.DEFAULT,
|
||||
productNumber: car.productCount ?? 0,
|
||||
productWidget: car.productWeight ?? 0,
|
||||
totalPrice: car.totalPrice ?? 0,
|
||||
isPriceIncreased: false,
|
||||
),
|
||||
)
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
/// 展开列表订单并按需查询购物车;再次点击同一笔订单时收起。
|
||||
/// 本机订单读本地库,全部数据范围的远端订单在线取购物车快照。
|
||||
Future<void> toggleExpandedOrder(OrderEntity order) async {
|
||||
final id = order.id;
|
||||
if (id == null) {
|
||||
Toast.error(msg: '订单尚未写入本地数据库,无法查询购物车明细');
|
||||
if (scope.value != TerminalDataScope.merchant) {
|
||||
Toast.error(msg: '订单尚未写入本地数据库,无法查询购物车明细');
|
||||
return;
|
||||
}
|
||||
final remoteId = int.tryParse(order.serviceUniqueCode);
|
||||
if (remoteId == null) {
|
||||
Toast.error(msg: '订单缺少服务端标识,无法查询购物车明细');
|
||||
return;
|
||||
}
|
||||
if (expandedOrderId.value == remoteId) {
|
||||
_collapseExpandedOrder();
|
||||
return;
|
||||
}
|
||||
expandedOrderId.value = remoteId;
|
||||
await _loadExpandedRemoteOrder(remoteId);
|
||||
return;
|
||||
}
|
||||
if (expandedOrderId.value == id) {
|
||||
@@ -153,9 +309,38 @@ class OrderHistoryController extends GetxController {
|
||||
await _loadExpandedOrder(id);
|
||||
}
|
||||
|
||||
/// 全部数据范围下从服务端加载展开订单的购物车明细。
|
||||
Future<void> _loadExpandedRemoteOrder(int orderId) async {
|
||||
final requestId = ++_expandedDetailRequestId;
|
||||
expandedDetailStatus.value = OrderRecordDetailLoadStatus.loading;
|
||||
expandedDetailError.value = '';
|
||||
expandedShopCardList.clear();
|
||||
try {
|
||||
final items = await _loadRemoteShopCards(orderId);
|
||||
if (requestId != _expandedDetailRequestId ||
|
||||
expandedOrderId.value != orderId) {
|
||||
return;
|
||||
}
|
||||
expandedShopCardList.assignAll(items);
|
||||
expandedDetailStatus.value = OrderRecordDetailLoadStatus.success;
|
||||
} catch (error, stack) {
|
||||
if (requestId != _expandedDetailRequestId ||
|
||||
expandedOrderId.value != orderId) {
|
||||
return;
|
||||
}
|
||||
expandedDetailError.value = '服务端购物车读取失败,请检查网络后重试';
|
||||
expandedDetailStatus.value = OrderRecordDetailLoadStatus.failure;
|
||||
logger.e('|交易记录|展开远端购物车失败|orderId:$orderId|error:$error|stack:$stack|');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> retryExpandedOrder() async {
|
||||
final id = expandedOrderId.value;
|
||||
if (id == null) return;
|
||||
if (scope.value == TerminalDataScope.merchant) {
|
||||
await _loadExpandedRemoteOrder(id);
|
||||
return;
|
||||
}
|
||||
await _loadExpandedOrder(id);
|
||||
}
|
||||
|
||||
@@ -257,7 +442,10 @@ class OrderHistoryController extends GetxController {
|
||||
}
|
||||
|
||||
Future<void> getTotalList() async {
|
||||
final total = await _getTotal();
|
||||
// 全部数据范围没有本机统计口径,状态页签不展示数量。
|
||||
final total = scope.value == TerminalDataScope.merchant
|
||||
? <String, dynamic>{}
|
||||
: await _getTotal();
|
||||
List<OrderHistoryTableItem> tabs = OrderStatus.visibleList()
|
||||
.map((v) => OrderHistoryTableItem(status: v))
|
||||
.toList();
|
||||
@@ -360,10 +548,12 @@ class OrderHistoryController extends GetxController {
|
||||
};
|
||||
final data = await HardApi.timeLimitRefund(pa);
|
||||
if (!data) throw Error();
|
||||
// 修改本地订单为退款
|
||||
// 修改本地订单为退款;远端订单无本地记录,仅刷新列表
|
||||
v.status = OrderStatus.REFUND;
|
||||
final orderDao = appC.dataBase.getOrderDao;
|
||||
await orderDao.edit(v);
|
||||
if (v.id != null) {
|
||||
final orderDao = appC.dataBase.getOrderDao;
|
||||
await orderDao.edit(v);
|
||||
}
|
||||
await getTotalList();
|
||||
await getOrder();
|
||||
TtsManage.speak("退款成功总金额$price元");
|
||||
@@ -378,13 +568,31 @@ class OrderHistoryController extends GetxController {
|
||||
);
|
||||
}
|
||||
|
||||
/// 打开订单详情抽屉,并从本地数据库加载商品明细。
|
||||
/// 打开订单详情抽屉;本机订单读本地库,远端订单在线取购物车快照。
|
||||
Future<void> doDetail(OrderEntity v) async {
|
||||
selectedOrder.value = v;
|
||||
final id = v.id;
|
||||
selectedShopCardList.value = id == null
|
||||
? <ShopCardEntity>[]
|
||||
: await appC.dataBase.getShopCardDao.findByOrderId(id);
|
||||
if (id != null) {
|
||||
selectedShopCardList.value =
|
||||
await appC.dataBase.getShopCardDao.findByOrderId(id);
|
||||
return;
|
||||
}
|
||||
if (scope.value != TerminalDataScope.merchant) {
|
||||
selectedShopCardList.value = <ShopCardEntity>[];
|
||||
return;
|
||||
}
|
||||
final remoteId = int.tryParse(v.serviceUniqueCode);
|
||||
if (remoteId == null) {
|
||||
selectedShopCardList.value = <ShopCardEntity>[];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
selectedShopCardList.value = await _loadRemoteShopCards(remoteId);
|
||||
} catch (error, stack) {
|
||||
selectedShopCardList.value = <ShopCardEntity>[];
|
||||
logger.e('|交易记录|远端详情购物车失败|orderId:$remoteId|error:$error|stack:$stack|');
|
||||
Toast.error(msg: '服务端购物车读取失败,请检查网络后重试');
|
||||
}
|
||||
}
|
||||
|
||||
void closeDetail() {
|
||||
|
||||
@@ -83,6 +83,13 @@ class OrderHistoryPage extends GetView<OrderHistoryController> {
|
||||
SizedBox(height: 16.h),
|
||||
Row(
|
||||
children: [
|
||||
Obx(
|
||||
() => TerminalDataScopeTabs(
|
||||
scope: controller.scope.value,
|
||||
onChanged: controller.doScope,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
Obx(
|
||||
() => Row(
|
||||
children: controller.tabList
|
||||
@@ -90,6 +97,8 @@ class OrderHistoryPage extends GetView<OrderHistoryController> {
|
||||
(tab) => _StatusFilterButton(
|
||||
label: tab.status.title,
|
||||
count: tab.total,
|
||||
showCount: controller.scope.value ==
|
||||
TerminalDataScope.device,
|
||||
selected:
|
||||
controller.currentStatus.value == tab.status,
|
||||
onTap: () => controller.doTab(tab.status),
|
||||
@@ -489,6 +498,7 @@ class _StatusFilterButton extends StatelessWidget {
|
||||
required this.count,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
this.showCount = true,
|
||||
});
|
||||
|
||||
final String label;
|
||||
@@ -496,6 +506,9 @@ class _StatusFilterButton extends StatelessWidget {
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
/// 全部数据范围没有本机统计口径,隐藏数量角标。
|
||||
final bool showCount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = FTheme.of(context);
|
||||
@@ -518,22 +531,25 @@ class _StatusFilterButton extends StatelessWidget {
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8.w),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
|
||||
decoration: BoxDecoration(
|
||||
color: selected
|
||||
? Colors.white.withValues(alpha: 0.2)
|
||||
: Colors.white,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Text(
|
||||
'$count',
|
||||
style: theme.typography.sm.copyWith(
|
||||
color: selected ? Colors.white : XfjDesignTokens.muted,
|
||||
if (showCount) ...[
|
||||
SizedBox(width: 8.w),
|
||||
Container(
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
|
||||
decoration: BoxDecoration(
|
||||
color: selected
|
||||
? Colors.white.withValues(alpha: 0.2)
|
||||
: Colors.white,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Text(
|
||||
'$count',
|
||||
style: theme.typography.sm.copyWith(
|
||||
color: selected ? Colors.white : XfjDesignTokens.muted,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -11,6 +11,10 @@ class VerificationRecordsController extends GetxController {
|
||||
|
||||
final status = VerificationRecordLoadStatus.loading.obs;
|
||||
final records = <ServiceVerificationRecord>[].obs;
|
||||
|
||||
/// 列表数据范围;本机数据仅当前设备,全部数据按终端所属商户在线查询。
|
||||
final scope = TerminalDataScope.device.obs;
|
||||
|
||||
final selectDateTime = <DateTime>[
|
||||
DateTime.now(),
|
||||
DateTime.now(),
|
||||
@@ -135,6 +139,7 @@ class VerificationRecordsController extends GetxController {
|
||||
format: DateFormats.y_mo_d,
|
||||
),
|
||||
verificationType: verificationRecordReservationType,
|
||||
scope: scope.value.requestCode,
|
||||
),
|
||||
);
|
||||
if (response == null) throw StateError('服务端未返回核销记录');
|
||||
@@ -153,6 +158,18 @@ class VerificationRecordsController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
/// 切换数据范围;全部数据依赖网络,离线时提示并保持本机数据。
|
||||
Future<void> changeScope(TerminalDataScope target) async {
|
||||
if (scope.value == target) return;
|
||||
if (target == TerminalDataScope.merchant &&
|
||||
!AppNetworkTool.isAutoNetwork()) {
|
||||
Toast.error(msg: '当前没有网络,无法查询全部数据');
|
||||
return;
|
||||
}
|
||||
scope.value = target;
|
||||
await loadRecords(resetPage: true);
|
||||
}
|
||||
|
||||
Future<void> changeDateRange(List<DateTime>? dates) async {
|
||||
if (dates == null || dates.length < 2) return;
|
||||
selectDateTime.assignAll(dates.take(2));
|
||||
|
||||
@@ -100,12 +100,19 @@ class VerificationRecordsPage extends GetView<VerificationRecordsController> {
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
Text(
|
||||
'仅显示当前设备已完成的预约核销订单',
|
||||
style: theme.typography.sm.copyWith(
|
||||
color: XfjDesignTokens.muted,
|
||||
),
|
||||
),
|
||||
Obx(() => Text(
|
||||
controller.scope.value == TerminalDataScope.device
|
||||
? '仅显示当前设备已完成的预约核销订单'
|
||||
: '按当前终端权限显示全部设备的预约核销订单',
|
||||
style: theme.typography.sm.copyWith(
|
||||
color: XfjDesignTokens.muted,
|
||||
),
|
||||
)),
|
||||
SizedBox(width: 10.w),
|
||||
Obx(() => TerminalDataScopeTabs(
|
||||
scope: controller.scope.value,
|
||||
onChanged: controller.changeScope,
|
||||
)),
|
||||
SizedBox(width: 10.w),
|
||||
Obx(
|
||||
() => Text(
|
||||
|
||||
+341
-357
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user