已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
851
apps/user_app/test/support/a1_fixture.dart
Normal file
851
apps/user_app/test/support/a1_fixture.dart
Normal file
@@ -0,0 +1,851 @@
|
||||
// 功能描述:A1 自动测试与截图共用的明确 Fixture,禁止作为运行入口使用。
|
||||
// 版本:1.0.0
|
||||
import 'package:user_app/data/repositories/client_repository.dart';
|
||||
import 'package:user_app/data/services/api_client.dart';
|
||||
import 'package:user_app/domain/models/client_models.dart';
|
||||
import 'package:user_app/domain/models/shipping_address.dart';
|
||||
import 'package:user_app/domain/models/product_detail.dart';
|
||||
import 'package:user_app/domain/models/cart_item.dart';
|
||||
import 'package:user_app/domain/models/product_favorite.dart';
|
||||
import 'package:user_app/domain/models/primary_models.dart';
|
||||
import 'package:user_app/domain/models/product_recommendations.dart';
|
||||
import 'package:user_app/domain/models/shop_order_detail.dart';
|
||||
import 'package:user_app/domain/models/gas_order_detail.dart';
|
||||
import 'package:user_app/domain/models/gas_order_checkout.dart';
|
||||
import 'package:user_app/domain/models/wallet_bill.dart';
|
||||
import 'package:user_app/domain/models/wallet_account.dart';
|
||||
import 'package:user_app/domain/models/deposit.dart';
|
||||
import 'package:user_app/domain/models/recharge.dart';
|
||||
import 'package:user_app/domain/models/delivery_detail.dart';
|
||||
import 'package:user_app/domain/models/delivery_track.dart';
|
||||
import 'package:user_app/domain/models/usage_statistics.dart';
|
||||
import 'package:user_app/domain/models/message_center.dart';
|
||||
import 'package:user_app/domain/models/family_sharing.dart';
|
||||
|
||||
/// 提供可控数据及失败状态,测试不依赖本地数据库。
|
||||
class A1FixtureRepository extends ClientRepository {
|
||||
A1FixtureRepository() : super(ApiClient(() => ''));
|
||||
bool fail = false;
|
||||
int withdrawalCalls = 0;
|
||||
int depositReturnCalls = 0;
|
||||
int gasOrderCreateCalls = 0;
|
||||
final markedMessageKeys = <String>[];
|
||||
|
||||
@override
|
||||
Future<FamilyDashboardData> familyDashboard() async => FamilyDashboardData(
|
||||
householdName: '薛海路之家',
|
||||
ownerName: '张先生',
|
||||
memberCount: 3,
|
||||
deviceCount: 3,
|
||||
members: [
|
||||
const FamilyMember(
|
||||
identity: 'owner',
|
||||
name: '张先生',
|
||||
phoneMasked: '138****5678',
|
||||
relationship: '房主',
|
||||
inviteStatus: 20,
|
||||
isOwner: true,
|
||||
permissions: [],
|
||||
),
|
||||
const FamilyMember(
|
||||
identity: 'member-1',
|
||||
name: '张女士',
|
||||
phoneMasked: '139****6078',
|
||||
relationship: '家人',
|
||||
inviteStatus: 20,
|
||||
isOwner: false,
|
||||
permissions: [
|
||||
FamilyDevicePermission(
|
||||
deviceIdentity: 'device-1',
|
||||
deviceName: '厨房角阀',
|
||||
deviceKind: 'valve',
|
||||
canView: true,
|
||||
canAlert: true,
|
||||
canControl: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
const FamilyMember(
|
||||
identity: 'member-2',
|
||||
name: '李先生',
|
||||
phoneMasked: '138****0028',
|
||||
relationship: '房东',
|
||||
inviteStatus: 20,
|
||||
isOwner: false,
|
||||
permissions: [
|
||||
FamilyDevicePermission(
|
||||
deviceIdentity: 'device-2',
|
||||
deviceName: '厨房报警器',
|
||||
deviceKind: 'alarm',
|
||||
canView: true,
|
||||
canAlert: true,
|
||||
canControl: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
FamilyMember(
|
||||
identity: 'member-3',
|
||||
name: '王女士',
|
||||
phoneMasked: '137****9056',
|
||||
relationship: '家人',
|
||||
inviteStatus: 10,
|
||||
isOwner: false,
|
||||
permissions: const [],
|
||||
expiresAt: DateTime(2026, 9, 20),
|
||||
),
|
||||
],
|
||||
devices: const [
|
||||
FamilyDevice(
|
||||
identity: 'device-1',
|
||||
name: '厨房角阀',
|
||||
kind: 'valve',
|
||||
shareCount: 3,
|
||||
mappingConfigured: true,
|
||||
),
|
||||
FamilyDevice(
|
||||
identity: 'device-2',
|
||||
name: '厨房报警器',
|
||||
kind: 'alarm',
|
||||
shareCount: 3,
|
||||
mappingConfigured: true,
|
||||
),
|
||||
FamilyDevice(
|
||||
identity: 'device-3',
|
||||
name: '客厅报警器',
|
||||
kind: 'alarm',
|
||||
shareCount: 2,
|
||||
mappingConfigured: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<MessageCenterData> messages() async => MessageCenterData(
|
||||
counts: const {'all': 5, 'unread': 2, 'safety': 1, 'order': 2, 'service': 1, 'notice': 1},
|
||||
items: [
|
||||
UserMessage(
|
||||
key: 'safety-1',
|
||||
category: 'safety',
|
||||
title: '厨房燃气浓度异常',
|
||||
summary: '厨房报警器1:检测到燃气浓度异常',
|
||||
statusText: '已解除',
|
||||
target: '',
|
||||
targetIdentity: '',
|
||||
occurredAt: DateTime(2026, 9, 12, 9, 18),
|
||||
read: false,
|
||||
),
|
||||
UserMessage(
|
||||
key: 'order-2',
|
||||
category: 'order',
|
||||
title: '厨房角阀已远程关闭',
|
||||
summary: '厨房角阀已为您远程关闭,保障用气安全',
|
||||
statusText: '已完成',
|
||||
target: '',
|
||||
targetIdentity: '',
|
||||
occurredAt: DateTime(2026, 9, 12, 8, 52),
|
||||
read: true,
|
||||
),
|
||||
UserMessage(
|
||||
key: 'service-1',
|
||||
category: 'service',
|
||||
title: '报修工单已受理',
|
||||
summary: '您的报修工单已受理,工程师将尽快与您联系',
|
||||
statusText: '进行中',
|
||||
target: 'ticket',
|
||||
targetIdentity: 'ticket-1',
|
||||
occurredAt: DateTime(2026, 9, 11, 16, 30),
|
||||
read: true,
|
||||
),
|
||||
UserMessage(
|
||||
key: 'order-1',
|
||||
category: 'order',
|
||||
title: '配送员已出发',
|
||||
summary: '您的燃气商品订单配送状态已更新',
|
||||
statusText: '配送中',
|
||||
target: 'gas_order',
|
||||
targetIdentity: 'gas-1',
|
||||
occurredAt: DateTime(2026, 9, 3, 9, 42),
|
||||
read: false,
|
||||
),
|
||||
UserMessage(
|
||||
key: 'notice-1',
|
||||
category: 'notice',
|
||||
title: '秋季安全用气提醒',
|
||||
summary: '天气转凉,请注意用气通风,定期检查燃气设备。',
|
||||
statusText: '平台公告',
|
||||
target: 'content',
|
||||
targetIdentity: 'notice-1',
|
||||
occurredAt: DateTime(2026, 9, 1, 10),
|
||||
read: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> markMessagesRead(List<String> keys) async => markedMessageKeys.addAll(keys);
|
||||
|
||||
@override
|
||||
Future<List<ClientRecord>> devices() async => const [
|
||||
ClientRecord(
|
||||
identity: 'device-1',
|
||||
title: '厨房15kg气瓶',
|
||||
subtitle: 'GZ-15-0286',
|
||||
raw: {'kind': 'valve'},
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<UsageStatistics> usageStatistics({
|
||||
required String deviceIdentity,
|
||||
required String period,
|
||||
DateTime? anchor,
|
||||
}) async {
|
||||
final start = DateTime(2026, 9, 1);
|
||||
final values = [
|
||||
0.12,
|
||||
0.20,
|
||||
0.13,
|
||||
0.15,
|
||||
0.08,
|
||||
0.16,
|
||||
0.30,
|
||||
0.40,
|
||||
0.28,
|
||||
0.31,
|
||||
0.42,
|
||||
0.30,
|
||||
0.29,
|
||||
0.20,
|
||||
0.28,
|
||||
0.35,
|
||||
0.20,
|
||||
0.27,
|
||||
0.39,
|
||||
0.33,
|
||||
0.19,
|
||||
0.32,
|
||||
0.49,
|
||||
0.62,
|
||||
0.35,
|
||||
0.29,
|
||||
0.37,
|
||||
0.31,
|
||||
0.17,
|
||||
0.23,
|
||||
];
|
||||
return UsageStatistics(
|
||||
available: true,
|
||||
deviceIdentity: deviceIdentity,
|
||||
deviceName: '厨房15kg气瓶',
|
||||
deviceCode: 'GZ-15-0286',
|
||||
period: period,
|
||||
periodStart: start,
|
||||
periodEnd: DateTime(2026, 9, 30),
|
||||
unit: 'kg',
|
||||
points: [
|
||||
for (var index = 0; index < values.length; index++)
|
||||
UsagePoint(
|
||||
date: start.add(Duration(days: index)),
|
||||
usage: values[index],
|
||||
),
|
||||
],
|
||||
totalUsage: values.fold(0.0, (sum, value) => sum + value),
|
||||
averageUsage: values.fold(0.0, (sum, value) => sum + value) / values.length,
|
||||
composition: const UsageComposition(breakfast: 2.2, lunch: 2.5, dinner: 3.9),
|
||||
source: 'device',
|
||||
calcVersion: 'v1',
|
||||
updatedAt: DateTime(2026, 9, 30, 23, 50),
|
||||
unavailableReason: '',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DeliveryDetail> gasOrderDelivery(String identity) async => DeliveryDetail.fromJson({
|
||||
'identity': identity,
|
||||
'order_no': '178509050920001',
|
||||
'status_code': 33,
|
||||
'status_name': '配送中',
|
||||
'status_message': '配送员正在前往您指定地点,请留意电话或消息通知',
|
||||
'appointment_at': '2026-09-05T10:30:00+08:00',
|
||||
'track_updated_at': '2026-09-05T09:56:00+08:00',
|
||||
'address': '北京市朝阳区望京中环南路2号星源国际B座1201室',
|
||||
'contact_name': '张女士',
|
||||
'contact_phone_masked': '138****5678',
|
||||
'station_name': '薛海气站',
|
||||
'delivery_name': '薛海气站配送点',
|
||||
'delivery_address': '北京市朝阳区薛海路8号',
|
||||
'staff_name': '张师傅',
|
||||
'staff_avatar': 'asset:assets/staff/delivery-worker.png',
|
||||
'staff_assigned': true,
|
||||
'credential_verified': true,
|
||||
'credential_type': 'hazmat',
|
||||
'track_available': true,
|
||||
'controlled_call_available': false,
|
||||
'message_available': false,
|
||||
'vehicle_configured': false,
|
||||
'products': [
|
||||
{'name': '15kg液化气', 'quantity': 1},
|
||||
],
|
||||
});
|
||||
|
||||
@override
|
||||
Future<DeliveryTrack> gasOrderDeliveryTrack(String identity) async => DeliveryTrack.fromJson({
|
||||
'identity': identity,
|
||||
'status_code': 33,
|
||||
'status_name': '配送中',
|
||||
'status_message': '配送员正在为您配送,请耐心等待',
|
||||
'appointment_at': '2026-09-05T10:30:00+08:00',
|
||||
'updated_at': '2026-09-05T09:56:00+08:00',
|
||||
'station_name': '薛海气站',
|
||||
'staff_name': '张师傅',
|
||||
'staff_avatar': 'asset:assets/staff/delivery-worker.png',
|
||||
'staff_phone_masked': '138****5678',
|
||||
'controlled_call_available': false,
|
||||
'route_available': true,
|
||||
'destination_available': true,
|
||||
'destination_longitude': 116.472,
|
||||
'destination_latitude': 39.997,
|
||||
'points': [
|
||||
{'longitude': 116.451, 'latitude': 40.002, 'occurred_at': '2026-09-05T09:42:00+08:00'},
|
||||
{'longitude': 116.456, 'latitude': 40.001, 'occurred_at': '2026-09-05T09:47:00+08:00'},
|
||||
{'longitude': 116.461, 'latitude': 39.999, 'occurred_at': '2026-09-05T09:51:00+08:00'},
|
||||
{'longitude': 116.466, 'latitude': 39.998, 'occurred_at': '2026-09-05T09:56:00+08:00'},
|
||||
],
|
||||
'timeline': [
|
||||
{
|
||||
'status_code': 18,
|
||||
'title': '气站已接单',
|
||||
'detail': '薛海气站已接收您的订单',
|
||||
'occurred_at': '2026-09-05T09:21:00+08:00',
|
||||
},
|
||||
{
|
||||
'status_code': 20,
|
||||
'title': '商品装车完成',
|
||||
'detail': '商品已装车,准备出发',
|
||||
'occurred_at': '2026-09-05T09:35:00+08:00',
|
||||
},
|
||||
{
|
||||
'status_code': 33,
|
||||
'title': '配送员已出发',
|
||||
'detail': '配送员已从薛海气站出发',
|
||||
'occurred_at': '2026-09-05T09:42:00+08:00',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@override
|
||||
Future<RechargeOptions> rechargeOptions() async => const RechargeOptions(
|
||||
min: 1,
|
||||
max: 500000,
|
||||
channels: [
|
||||
RechargeChannel('wechat', app: true, wap: true),
|
||||
RechargeChannel('alipay', app: true, wap: true),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GasOrderCheckoutData> gasOrderOptions() async => GasOrderCheckoutData(
|
||||
stationName: '薛海气站',
|
||||
stationStatus: '营业中',
|
||||
deliveryScope: '薛海镇及周边区域',
|
||||
deliveryFee: 0,
|
||||
products: const [
|
||||
GasOrderProductOption(
|
||||
name: '5kg 便携瓶',
|
||||
description: '小巧轻便,适合单人或短期使用',
|
||||
unitPrice: 5800,
|
||||
depositAmount: 10000,
|
||||
itemIdentities: ['g5'],
|
||||
),
|
||||
GasOrderProductOption(
|
||||
name: '10kg 家用瓶',
|
||||
description: '家庭日常使用,经济实惠',
|
||||
unitPrice: 9600,
|
||||
depositAmount: 10000,
|
||||
itemIdentities: ['g10'],
|
||||
),
|
||||
GasOrderProductOption(
|
||||
name: '15kg 家用瓶',
|
||||
description: '大容量更耐用,适合多人家庭',
|
||||
unitPrice: 12800,
|
||||
depositAmount: 10000,
|
||||
itemIdentities: ['g15'],
|
||||
),
|
||||
],
|
||||
addresses: const [
|
||||
ShippingAddress(
|
||||
identity: 'a1',
|
||||
address: '薛海镇泰山路188号 薛海花园3栋602室',
|
||||
contactName: '张先生',
|
||||
contactPhone: '13812345678',
|
||||
isDefault: true,
|
||||
),
|
||||
],
|
||||
slots: [
|
||||
GasOrderAppointmentSlot(startAt: DateTime(2026, 9, 12, 9), label: '明日 09:00-11:00'),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<GasOrderCreateResult> createGasOrder({
|
||||
required String requestNo,
|
||||
required String addressIdentity,
|
||||
required List<String> itemIdentities,
|
||||
required DateTime appointmentAt,
|
||||
required int expectedAmount,
|
||||
}) async {
|
||||
gasOrderCreateCalls++;
|
||||
return GasOrderCreateResult(identity: 'gas-created', payableAmount: expectedAmount);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DepositSummary> deposits() async => DepositSummary(
|
||||
refundableAmount: 20000,
|
||||
usingCount: 2,
|
||||
ruleText: '气瓶完好、回收检测合格后,押金原路退回至余额账户。',
|
||||
items: [
|
||||
DepositRecord(
|
||||
identity: 'd1',
|
||||
depositNo: 'YJ20260905001',
|
||||
status: 10,
|
||||
statusName: '使用中',
|
||||
amount: 10000,
|
||||
productName: '15kg 液化气钢瓶',
|
||||
productCode: 'GZ-15-0286',
|
||||
paidAt: DateTime(2026, 8, 18),
|
||||
refundedAt: null,
|
||||
allowedActions: const ['return_bottle'],
|
||||
),
|
||||
DepositRecord(
|
||||
identity: 'd2',
|
||||
depositNo: 'YJ20260904002',
|
||||
status: 20,
|
||||
statusName: '退款中',
|
||||
amount: 10000,
|
||||
productName: '15kg 液化气钢瓶',
|
||||
productCode: 'GZ-15-0193',
|
||||
paidAt: DateTime(2026, 8, 12),
|
||||
refundedAt: null,
|
||||
allowedActions: const [],
|
||||
),
|
||||
DepositRecord(
|
||||
identity: 'd3',
|
||||
depositNo: 'YJ20260828003',
|
||||
status: 23,
|
||||
statusName: '已退回',
|
||||
amount: 10000,
|
||||
productName: '15kg 液化气钢瓶',
|
||||
productCode: 'GZ-15-0127',
|
||||
paidAt: DateTime(2026, 7, 28),
|
||||
refundedAt: DateTime(2026, 7, 30),
|
||||
allowedActions: const [],
|
||||
),
|
||||
],
|
||||
);
|
||||
@override
|
||||
Future<DepositReturnRequest> createDepositReturn({
|
||||
required String depositIdentity,
|
||||
required ShippingAddress address,
|
||||
required DateTime appointmentStart,
|
||||
required DateTime appointmentEnd,
|
||||
required String requestNo,
|
||||
}) async {
|
||||
depositReturnCalls++;
|
||||
return const DepositReturnRequest(
|
||||
identity: 'return-1',
|
||||
status: 10,
|
||||
statusName: '待上门',
|
||||
estimatedAmount: 10000,
|
||||
refundAmount: 0,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WalletBillPage> walletBills({String direction = '', String cursor = ''}) async {
|
||||
if (fail) throw const ApiException(500, '测试账单错误');
|
||||
final items = [
|
||||
for (final (id, type, amount, flow) in [
|
||||
('1', 'gas_order', 22800, 'expense'),
|
||||
('2', 'refund', 5900, 'income'),
|
||||
('3', 'recharge', 30000, 'income'),
|
||||
])
|
||||
WalletBill(
|
||||
identity: id,
|
||||
number: 'TEST-$id',
|
||||
direction: flow,
|
||||
tradeType: type,
|
||||
amount: amount,
|
||||
fee: 0,
|
||||
balanceAfter: 56800,
|
||||
createdAt: DateTime(2026, 9, 6 - int.parse(id)),
|
||||
channel: 'wallet',
|
||||
),
|
||||
];
|
||||
return WalletBillPage(
|
||||
items.where((i) => direction.isEmpty || i.direction == direction).toList(),
|
||||
'',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GasOrderDetail> gasOrderDetail(String identity) async {
|
||||
final payment = identity == 'payment-1';
|
||||
return GasOrderDetail.fromJson({
|
||||
'identity': identity,
|
||||
'order_no': payment ? '202609051628001' : 'G202609050920001',
|
||||
'status_name': payment ? '待付款' : '待签收',
|
||||
'allowed_actions': payment ? ['pay', 'cancel'] : ['confirm_receipt'],
|
||||
'station_name': '薛海气站',
|
||||
'delivery_staff_name': '李师傅',
|
||||
'contract_identity': 'contract-1',
|
||||
'contract_no': 'HT20260905001',
|
||||
'product_amount': 12800,
|
||||
'deposit_amount': payment ? 10000 : 0,
|
||||
'delivery_fee': 0,
|
||||
'discount_amount': 0,
|
||||
'payable_amount': payment ? 22800 : 12800,
|
||||
'contact_name': '张女士',
|
||||
'contact_phone': '13800005678',
|
||||
'address': '北京市朝阳区望京街道望京中环南路2号星源国际B座1201室',
|
||||
'created_at': '2026-09-05T09:20:00+08:00',
|
||||
'items': [
|
||||
{'identity': 'gas-item', 'name': '15kg家用液化气', 'quantity': 1, 'sale_amount': 12800},
|
||||
],
|
||||
'timeline': payment
|
||||
? []
|
||||
: [
|
||||
{'status_name': '已分配', 'occurred_at': '2026-09-05T09:21:00+08:00'},
|
||||
{'status_name': '配送中', 'occurred_at': '2026-09-05T09:30:00+08:00'},
|
||||
{'status_name': '待签收', 'occurred_at': '2026-09-05T10:30:00+08:00'},
|
||||
],
|
||||
'payments': payment
|
||||
? []
|
||||
: [
|
||||
{'amount': 12800, 'channel': 'wechat', 'paid_at': '2026-09-05T09:22:00+08:00'},
|
||||
],
|
||||
}, (s) => s);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<GasContractSummary>> gasContracts() async => [
|
||||
GasContractSummary.fromJson({
|
||||
'identity': 'contract-1',
|
||||
'contract_no': 'HT20260905001',
|
||||
'title': '居民瓶装燃气供用气合同',
|
||||
'terms': '测试合同正文',
|
||||
'contract_status': 11,
|
||||
'station_name': '薛海气站',
|
||||
'signed_at': '2026-08-18T00:00:00+08:00',
|
||||
'effective_at': '2026-08-18T00:00:00+08:00',
|
||||
'expired_at': '2027-08-17T00:00:00+08:00',
|
||||
'has_attachment': false,
|
||||
}),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<GasContractDetail> gasContractDetail(String identity) async => GasContractDetail.fromJson({
|
||||
'identity': identity,
|
||||
'contract_no': 'HT20260905001',
|
||||
'title': '居民瓶装燃气供用气合同',
|
||||
'terms': '测试合同正文,仅用于自动验证,不作为真实合同。',
|
||||
'contract_status': 11,
|
||||
'signed_at': '2026-08-18T00:00:00+08:00',
|
||||
'effective_at': '2026-08-18T00:00:00+08:00',
|
||||
'expired_at': '2027-08-17T00:00:00+08:00',
|
||||
'has_attachment': false,
|
||||
});
|
||||
@override
|
||||
Future<ShopOrderDetail> shopOrderDetail(String identity) async {
|
||||
final payment = identity == 'payment-1';
|
||||
return ShopOrderDetail.fromJson({
|
||||
'identity': identity,
|
||||
'order_no': payment ? 'EC202609051628001' : 'EC202609050920001',
|
||||
'status_name': payment ? '待付款' : '待收货',
|
||||
'allowed_actions': payment ? ['pay', 'cancel'] : ['refund', 'confirm_receipt'],
|
||||
'contact_name': '张女士',
|
||||
'contact_phone': '13800005678',
|
||||
'address': '北京市朝阳区望京街道望京中环南路2号星源国际B座1201室',
|
||||
'product_amount': payment ? 23800 : 12800,
|
||||
'discount_amount': payment ? 1000 : 0,
|
||||
'payable_amount': payment ? 22800 : 12800,
|
||||
'created_at': '2026-09-05T09:20:00+08:00',
|
||||
'paid_at': payment ? null : '2026-09-05T09:21:00+08:00',
|
||||
'shipped_at': payment ? null : '2026-09-05T09:30:00+08:00',
|
||||
'received_at': null,
|
||||
'remark': '送达前电话联系',
|
||||
'items': [
|
||||
{
|
||||
'identity': 'item-1',
|
||||
'product_identity': 'p1',
|
||||
'name': '15kg家用液化气',
|
||||
'quantity': 1,
|
||||
'sale_amount': payment ? 23800 : 12800,
|
||||
'image_url': '',
|
||||
},
|
||||
],
|
||||
}, (s) => s);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ProductRecommendations> recommendations({required String source, int page = 1}) async =>
|
||||
ProductRecommendations(
|
||||
items: const [
|
||||
ProductSummary(identity: 'r1', name: '家用燃气减压阀', price: 3900, stock: 10, category: '燃气配件'),
|
||||
ProductSummary(identity: 'r2', name: '不锈钢燃气管', price: 5900, stock: 10, category: '燃气配件'),
|
||||
],
|
||||
page: page,
|
||||
hasMore: false,
|
||||
);
|
||||
@override
|
||||
Future<ProductFavoriteState> favoriteState(String identity) async =>
|
||||
ProductFavoriteState(productIdentity: identity, active: false, revision: '');
|
||||
@override
|
||||
Future<List<ProductFavorite>> favorites() async => [
|
||||
for (final p in await products())
|
||||
ProductFavorite(
|
||||
state: ProductFavoriteState(productIdentity: p.identity, active: true, revision: 'r1'),
|
||||
product: ProductSummary(
|
||||
identity: p.identity,
|
||||
name: p.title,
|
||||
price: p.raw['price_amount'] as int,
|
||||
stock: 20,
|
||||
category: p.raw['category_name'] as String,
|
||||
),
|
||||
available: true,
|
||||
),
|
||||
const ProductFavorite(
|
||||
state: ProductFavoriteState(productIdentity: 'p5', active: true, revision: 'r1'),
|
||||
product: ProductSummary(
|
||||
identity: 'p5',
|
||||
name: '家用燃气灶 双灶',
|
||||
price: 69900,
|
||||
stock: 0,
|
||||
category: '灶具',
|
||||
),
|
||||
available: false,
|
||||
specifications: ['天然气'],
|
||||
),
|
||||
];
|
||||
@override
|
||||
Future<List<CartItem>> cart() async => const [
|
||||
CartItem(
|
||||
product: ProductSummary(identity: 'p1', name: '15kg家用液化气', price: 12800, stock: 30),
|
||||
quantity: 1,
|
||||
selected: true,
|
||||
available: true,
|
||||
revision: 'r1',
|
||||
),
|
||||
CartItem(
|
||||
product: ProductSummary(identity: 'p2', name: '燃气报警器', price: 16900, stock: 20),
|
||||
quantity: 1,
|
||||
selected: true,
|
||||
available: true,
|
||||
revision: 'r2',
|
||||
),
|
||||
];
|
||||
@override
|
||||
Future<ProductDetail> productDetail(String identity) async => ProductDetail(
|
||||
identity: identity,
|
||||
name: '15kg家用液化气',
|
||||
price: 12800,
|
||||
stock: 30,
|
||||
category: '家用液化气',
|
||||
attributes: const [(name: '净重', value: '15kg'), (name: '适用家庭', value: '3—5人家庭日常烹饪使用')],
|
||||
);
|
||||
int relationCalls = 0;
|
||||
@override
|
||||
Future<List<ShippingAddress>> shippingAddresses() async => const [
|
||||
ShippingAddress(
|
||||
identity: 'a1',
|
||||
address: '广东省广州市白云区薛海路88号\n6栋502',
|
||||
contactName: '张先生',
|
||||
contactPhone: '13800005678',
|
||||
isDefault: true,
|
||||
),
|
||||
ShippingAddress(
|
||||
identity: 'a2',
|
||||
address: '广东省广州市白云区泰山路188号\n薛海花园3栋602',
|
||||
contactName: '李女士',
|
||||
contactPhone: '13900006078',
|
||||
),
|
||||
ShippingAddress(
|
||||
identity: 'a3',
|
||||
address: '广东省广州市白云区同和街道同泰路28号\n同泰大厦1栋901',
|
||||
contactName: '王先生',
|
||||
contactPhone: '13700008910',
|
||||
),
|
||||
];
|
||||
@override
|
||||
Future<List<ClientRecord>> contents() async {
|
||||
if (fail) throw const ApiException(500, '测试网络失败');
|
||||
return const [
|
||||
ClientRecord(
|
||||
identity: 'notice-1',
|
||||
title: '夏季用气安全提醒',
|
||||
subtitle: '',
|
||||
raw: {
|
||||
'body': '高温天气注意通风,定期检查燃气设备',
|
||||
'content_type': 'notice',
|
||||
'version_no': 1,
|
||||
'created_at': '2026-07-26T10:00:00+08:00',
|
||||
},
|
||||
),
|
||||
ClientRecord(
|
||||
identity: 'notice-2',
|
||||
title: '燃气设施维护通知',
|
||||
subtitle: '',
|
||||
raw: {
|
||||
'body': '本周将对部分区域进行例行维护',
|
||||
'content_type': 'notice',
|
||||
'version_no': 1,
|
||||
'created_at': '2026-07-24T10:00:00+08:00',
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, Object?>?> serviceRelation() async {
|
||||
relationCalls++;
|
||||
return {'gas_name': '薛海气站', 'delivery_name': '城南配送点'};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ClientRecord>> products() async => [
|
||||
for (final (identity, name, price, category) in [
|
||||
('p1', '15kg家用液化气', 12800, '液化气'),
|
||||
('p2', '燃气报警器', 16900, '安全设备'),
|
||||
('p3', '不锈钢减压阀', 5900, '燃气配件'),
|
||||
('p4', '燃气软管2米', 2990, '燃气配件'),
|
||||
])
|
||||
ClientRecord(
|
||||
identity: identity,
|
||||
title: name,
|
||||
subtitle: '',
|
||||
raw: {'price_amount': price, 'stock_quantity': 20, 'category_name': category},
|
||||
),
|
||||
];
|
||||
@override
|
||||
Future<UserProfile> profile() async => const UserProfile(
|
||||
identity: 'user-fixture',
|
||||
name: '张先生',
|
||||
phone: '13800005678',
|
||||
avatar: '',
|
||||
realName: '张先生',
|
||||
);
|
||||
@override
|
||||
Future<WalletSummary> wallet() async =>
|
||||
const WalletSummary(balance: 56800, withdrawalBalance: 56800, paymentPasswordSet: true);
|
||||
@override
|
||||
Future<List<WalletBank>> walletBanks() async => const [
|
||||
WalletBank(
|
||||
identity: 'bank-1',
|
||||
maskedNumber: '**** **** **** 2868',
|
||||
bankName: '中国建设银行',
|
||||
owner: '张先生',
|
||||
type: 'debit',
|
||||
isDefault: true,
|
||||
),
|
||||
WalletBank(
|
||||
identity: 'bank-2',
|
||||
maskedNumber: '**** **** **** 6078',
|
||||
bankName: '中国工商银行',
|
||||
owner: '张先生',
|
||||
type: 'debit',
|
||||
isDefault: false,
|
||||
),
|
||||
];
|
||||
@override
|
||||
Future<List<WalletWithdrawal>> walletWithdrawals() async => const [];
|
||||
@override
|
||||
Future<WalletWithdrawal> createWalletWithdrawal({
|
||||
required String bankIdentity,
|
||||
required int amount,
|
||||
required String requestNo,
|
||||
required String paymentPassword,
|
||||
}) async {
|
||||
withdrawalCalls++;
|
||||
return WalletWithdrawal(
|
||||
identity: 'withdraw-1',
|
||||
number: 'WD20260911001',
|
||||
amount: amount,
|
||||
fee: 0,
|
||||
status: 10,
|
||||
createdAt: DateTime(2026, 9, 11, 12),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ClientRecord>> addresses() async => [];
|
||||
@override
|
||||
Future<List<ClientRecord>> emergencyContacts() async => const [
|
||||
ClientRecord(identity: 'contact-1', title: '家人一', subtitle: '', raw: {}),
|
||||
ClientRecord(identity: 'contact-2', title: '家人二', subtitle: '', raw: {}),
|
||||
];
|
||||
@override
|
||||
Future<List<ClientRecord>> shopOrders() async => const [
|
||||
ClientRecord(
|
||||
identity: 'order-1',
|
||||
title: '订单号:1785051399865',
|
||||
subtitle: '',
|
||||
status: 16,
|
||||
raw: {
|
||||
'payable_amount': 22800,
|
||||
'status_name': '待付款',
|
||||
'allowed_actions': ['pay', 'cancel'],
|
||||
'created_at': '2026-07-26T15:36:00+08:00',
|
||||
'items': [
|
||||
{'identity': 'item-1', 'quantity': 1, 'product_snapshot': '{"name":"15kg家用液化气"}'},
|
||||
],
|
||||
},
|
||||
),
|
||||
];
|
||||
@override
|
||||
Future<List<ClientRecord>> gasOrders() async => (await shopOrders())
|
||||
.map(
|
||||
(r) => ClientRecord(
|
||||
identity: r.identity,
|
||||
title: r.title,
|
||||
subtitle: r.subtitle,
|
||||
status: r.status,
|
||||
raw: {
|
||||
...r.raw,
|
||||
'station_name': '薛海气站',
|
||||
'items': [
|
||||
{
|
||||
'identity': 'item-1',
|
||||
'quantity': 1,
|
||||
'product_type_name': '15kg家用液化气',
|
||||
'product_params': '{"weight":"15kg"}',
|
||||
},
|
||||
],
|
||||
'allowed_actions': ['pay', 'cancel'],
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
@override
|
||||
Future<List<ClientRecord>> refunds() async => [];
|
||||
@override
|
||||
Future<List<ClientRecord>> tickets() async => [
|
||||
const ClientRecord(
|
||||
identity: 't1',
|
||||
title: 'BX202609040123',
|
||||
subtitle: '厨房角阀关闭后仍有轻微异响',
|
||||
status: 34,
|
||||
raw: {
|
||||
'ticket_no': 'BX202609040123',
|
||||
'ticket_status': 34,
|
||||
'status_name': '待确认',
|
||||
'allowed_actions': ['cancel', 'confirm'],
|
||||
'category': 'repair',
|
||||
'description': '厨房角阀关闭后仍有轻微异响',
|
||||
'address': '四川省成都市武侯区万科·公园传奇南区3栋2单元1804',
|
||||
'created_at': '2026-09-04T01:15:00Z',
|
||||
'started_at': '2026-09-04T08:00:00Z',
|
||||
'appointment_at': '2026-09-04T08:00:00Z',
|
||||
'result': '已检查阀门并更换密封件,请确认处理结果。',
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user