449 lines
15 KiB
Dart
449 lines
15 KiB
Dart
import 'dart:math';
|
||
import '../models/user.dart';
|
||
import '../models/message.dart';
|
||
import '../models/event.dart';
|
||
import '../models/work_order.dart';
|
||
import '../models/order.dart';
|
||
|
||
class ApiService {
|
||
static final ApiService _instance = ApiService._internal();
|
||
factory ApiService() => _instance;
|
||
ApiService._internal();
|
||
|
||
final _random = Random();
|
||
|
||
Future<Map<String, dynamic>> login(String phone, String password) async {
|
||
await Future.delayed(const Duration(milliseconds: 800));
|
||
|
||
if (phone.length != 11) {
|
||
throw Exception('手机号格式不正确');
|
||
}
|
||
if (password.length < 6) {
|
||
throw Exception('密码不能少于6位');
|
||
}
|
||
|
||
final isBoss = phone.endsWith('88');
|
||
return {
|
||
'user': {
|
||
'id': 'user_${_random.nextInt(99999)}',
|
||
'phone': phone,
|
||
'name': isBoss ? '张老板' : '李员工',
|
||
'avatar': 'https://api.dicebear.com/7.x/avataaars/svg?seed=$phone',
|
||
'role': isBoss ? 'boss' : 'employee',
|
||
'token': 'token_${DateTime.now().millisecondsSinceEpoch}',
|
||
'hotelName': '智念度假酒店',
|
||
'scenicName': '智念风景区',
|
||
},
|
||
'token': 'mock_token_${DateTime.now().millisecondsSinceEpoch}',
|
||
};
|
||
}
|
||
|
||
Future<List<ChatMessage>> getChatHistory() async {
|
||
await Future.delayed(const Duration(milliseconds: 500));
|
||
return [
|
||
ChatMessage(
|
||
id: 'msg_1',
|
||
content: '您好,我是智念AI助手,有什么可以帮您的吗?',
|
||
type: MessageType.markdown,
|
||
sender: MessageSender.ai,
|
||
timestamp: DateTime.now().subtract(const Duration(minutes: 5)),
|
||
),
|
||
];
|
||
}
|
||
|
||
Future<ChatMessage> sendMessage(String content) async {
|
||
await Future.delayed(const Duration(milliseconds: 1200));
|
||
|
||
final tableTrigger = RegExp(r'表格|列表|清单|明细');
|
||
if (tableTrigger.hasMatch(content)) {
|
||
return ChatMessage(
|
||
id: 'msg_${DateTime.now().millisecondsSinceEpoch}',
|
||
content: _tableReport(),
|
||
type: MessageType.markdown,
|
||
sender: MessageSender.ai,
|
||
timestamp: DateTime.now(),
|
||
);
|
||
}
|
||
|
||
final imageTrigger = RegExp(r'图片|照片|图像|实景|环境照|相册');
|
||
if (imageTrigger.hasMatch(content)) {
|
||
return ChatMessage(
|
||
id: 'msg_${DateTime.now().millisecondsSinceEpoch}',
|
||
content: _imageGallery(),
|
||
type: MessageType.markdown,
|
||
sender: MessageSender.ai,
|
||
timestamp: DateTime.now(),
|
||
);
|
||
}
|
||
|
||
final chartTrigger = RegExp(r'报表|数据|营收|趋势|占比|图表|统计|分析');
|
||
if (chartTrigger.hasMatch(content)) {
|
||
return ChatMessage(
|
||
id: 'msg_${DateTime.now().millisecondsSinceEpoch}',
|
||
content: _chartReport(),
|
||
type: MessageType.markdown,
|
||
sender: MessageSender.ai,
|
||
timestamp: DateTime.now(),
|
||
);
|
||
}
|
||
|
||
final responses = [
|
||
'收到您的消息,我已记录并会尽快处理。\n\n**当前状态**:已受理\n**预计处理时间**:2小时内',
|
||
'好的,我来帮您查询一下。\n\n| 项目 | 状态 | 时间 |\n|------|------|------|\n| 订单核销 | 已完成 | 10:30 |\n| 工单处理 | 进行中 | 11:00 |',
|
||
'感谢您的反馈!根据系统记录,今日共有 **15笔订单** 待处理,**3个工单** 需要跟进。',
|
||
'已为您生成今日数据报表:\n\n- 总订单数:128笔\n- 核销完成:96笔\n- 待处理:32笔\n- 退款申请:2笔',
|
||
'事件已发布成功!\n\n> 事件将在设定的时间自动生效,系统会提前30分钟发送提醒。',
|
||
];
|
||
|
||
return ChatMessage(
|
||
id: 'msg_${DateTime.now().millisecondsSinceEpoch}',
|
||
content: responses[_random.nextInt(responses.length)],
|
||
type: MessageType.markdown,
|
||
sender: MessageSender.ai,
|
||
timestamp: DateTime.now(),
|
||
);
|
||
}
|
||
|
||
String _imageGallery() {
|
||
return '''### 智念度假酒店 · 环境实景
|
||
|
||
**大堂吧** — 全息投影 + 智能引导
|
||

|
||
|
||
**无边泳池** — 山景一线,270° 全景视野
|
||

|
||
|
||
**SPA 养生** — 古法中医结合现代理疗
|
||

|
||
|
||
> 提示:可继续询问"客房实景""餐厅照片"查看更多。''';
|
||
}
|
||
|
||
String _tableReport() {
|
||
return '''### 今日订单明细
|
||
|
||
共 **5 条** 待跟进订单:
|
||
|
||
| 订单号 | 客户 | 商品 | 金额 | 状态 |
|
||
|---|---|---|---|---|
|
||
| ZN20240507001 | 王先生 | 风景区成人票 ×2 | ¥128 | 🟡 待核销 |
|
||
| ZN20240507002 | 李女士 | 豪华套房 ×1 | ¥888 | ⚪ 待支付 |
|
||
| ZN20240507003 | 张 family | 亲子套票 ×3 | ¥388 | 🟢 已核销 |
|
||
| ZN20240507004 | 陈先生 | SPA 养生套餐 | ¥598 | 🔴 退款中 |
|
||
| ZN20240507005 | 赵女士 | 景区+酒店套餐 | ¥1588 | ⚫ 已退款 |
|
||
|
||
**汇总**
|
||
|
||
| 维度 | 数值 |
|
||
|---|---|
|
||
| 订单总数 | 5 |
|
||
| 应收金额 | ¥3,590 |
|
||
| 已退款 | ¥1,588 |
|
||
| 净收入 | ¥2,002 |
|
||
|
||
> 提示:点击订单行可跳转至详情,或继续询问"图表"查看可视化分析。''';
|
||
}
|
||
|
||
String _chartReport() {
|
||
return '''### 今日数据报表
|
||
|
||
总订单 **128 笔**,核销率 **75%**,环比上周 **+12.4%**。
|
||
|
||
```chart
|
||
type: bar
|
||
title: 订单状态分布(笔)
|
||
labels: 已核销,待处理,待支付,退款
|
||
data: 96,32,18,2
|
||
```
|
||
|
||
```chart
|
||
type: line
|
||
title: 近7日订单趋势
|
||
labels: 周一,周二,周三,周四,周五,周六,周日
|
||
data: 88,92,105,98,120,135,128
|
||
```
|
||
|
||
```chart
|
||
type: pie
|
||
title: 营收构成占比
|
||
labels: 客房,餐饮,SPA,门票
|
||
data: 58,22,12,8
|
||
```
|
||
|
||
> 提示:可继续询问"工单趋势""营收对比"等查看更多分析。''';
|
||
}
|
||
|
||
Future<List<EventItem>> getEvents() async {
|
||
await Future.delayed(const Duration(milliseconds: 600));
|
||
return List.generate(8, (index) {
|
||
final now = DateTime.now();
|
||
return EventItem(
|
||
id: 'evt_$index',
|
||
entityName: ['大堂吧', '游泳池', '健身房', '餐厅', 'SPA中心', '会议室', '停车场', '花园'][index],
|
||
description: '这是关于${_getEntityName(index)}的维护通知,请相关人员注意安排时间。',
|
||
images: [],
|
||
publishTime: now.subtract(Duration(days: index)),
|
||
effectiveTime: now.add(Duration(hours: index * 2)),
|
||
endTime: now.add(Duration(hours: index * 2 + 4)),
|
||
popupReminder: index % 2 == 0,
|
||
status: ['published', 'draft', 'published', 'expired'][index % 4],
|
||
creatorName: index % 2 == 0 ? '张老板' : '李员工',
|
||
createdAt: now.subtract(Duration(days: index)),
|
||
);
|
||
});
|
||
}
|
||
|
||
String _getEntityName(int index) {
|
||
return ['大堂吧', '游泳池', '健身房', '餐厅', 'SPA中心', '会议室', '停车场', '花园'][index];
|
||
}
|
||
|
||
Future<EventItem> publishEvent(Map<String, dynamic> data) async {
|
||
await Future.delayed(const Duration(milliseconds: 800));
|
||
return EventItem(
|
||
id: 'evt_${DateTime.now().millisecondsSinceEpoch}',
|
||
entityName: data['entityName'] ?? '',
|
||
description: data['description'] ?? '',
|
||
images: List<String>.from(data['images'] ?? []),
|
||
publishTime: DateTime.now(),
|
||
effectiveTime: data['effectiveTime'] != null ? DateTime.parse(data['effectiveTime']) : null,
|
||
endTime: data['endTime'] != null ? DateTime.parse(data['endTime']) : null,
|
||
popupReminder: data['popupReminder'] ?? false,
|
||
status: 'published',
|
||
creatorName: '当前用户',
|
||
createdAt: DateTime.now(),
|
||
);
|
||
}
|
||
|
||
Future<List<WorkOrder>> getWorkOrders(WorkOrderStatus status) async {
|
||
await Future.delayed(const Duration(milliseconds: 600));
|
||
final allOrders = [
|
||
WorkOrder(
|
||
id: 'wo_1',
|
||
title: '客房空调维修',
|
||
description: '308房间空调制冷效果不佳,需要安排维修人员检查。',
|
||
status: WorkOrderStatus.pending,
|
||
creatorName: '前台小王',
|
||
assigneeName: '维修部',
|
||
createdAt: DateTime.now().subtract(const Duration(hours: 2)),
|
||
priority: 'high',
|
||
category: '设备维修',
|
||
images: [],
|
||
location: '3楼308房间',
|
||
),
|
||
WorkOrder(
|
||
id: 'wo_2',
|
||
title: '花园草坪修剪',
|
||
description: '后花园草坪需要定期修剪,预计耗时2小时。',
|
||
status: WorkOrderStatus.processing,
|
||
creatorName: '张老板',
|
||
assigneeName: '保洁部',
|
||
createdAt: DateTime.now().subtract(const Duration(hours: 5)),
|
||
priority: 'normal',
|
||
category: '环境维护',
|
||
images: [],
|
||
location: '后花园',
|
||
),
|
||
WorkOrder(
|
||
id: 'wo_3',
|
||
title: '餐厅设备清洁',
|
||
description: '厨房排风系统需要深度清洁。',
|
||
status: WorkOrderStatus.completed,
|
||
creatorName: '厨师长',
|
||
assigneeName: '保洁部',
|
||
createdAt: DateTime.now().subtract(const Duration(days: 1)),
|
||
completedAt: DateTime.now().subtract(const Duration(hours: 3)),
|
||
priority: 'normal',
|
||
category: '清洁',
|
||
images: [],
|
||
location: '主厨房',
|
||
),
|
||
WorkOrder(
|
||
id: 'wo_4',
|
||
title: '游泳池水质检测',
|
||
description: '每日例行水质检测,PH值和氯含量需要记录。',
|
||
status: WorkOrderStatus.pending,
|
||
creatorName: '救生员小张',
|
||
createdAt: DateTime.now().subtract(const Duration(minutes: 30)),
|
||
priority: 'high',
|
||
category: '安全检查',
|
||
images: [],
|
||
location: '游泳池',
|
||
),
|
||
WorkOrder(
|
||
id: 'wo_5',
|
||
title: '会议室投影仪调试',
|
||
description: '下午有重要会议,需要提前调试投影设备。',
|
||
status: WorkOrderStatus.processing,
|
||
creatorName: '行政小刘',
|
||
assigneeName: '行政部',
|
||
createdAt: DateTime.now().subtract(const Duration(hours: 1)),
|
||
priority: 'urgent',
|
||
category: '设备调试',
|
||
images: [],
|
||
location: 'A座会议室',
|
||
),
|
||
];
|
||
|
||
if (status == WorkOrderStatus.all) return allOrders;
|
||
return allOrders.where((o) => o.status == status).toList();
|
||
}
|
||
|
||
Future<WorkOrder> getWorkOrderDetail(String id) async {
|
||
await Future.delayed(const Duration(milliseconds: 400));
|
||
final orders = await getWorkOrders(WorkOrderStatus.all);
|
||
return orders.firstWhere((o) => o.id == id, orElse: () => orders.first);
|
||
}
|
||
|
||
Future<WorkOrder> acceptWorkOrder(String id) async {
|
||
await Future.delayed(const Duration(milliseconds: 700));
|
||
if (_random.nextInt(20) == 0) {
|
||
throw Exception('网络异常,请稍后重试');
|
||
}
|
||
final order = await getWorkOrderDetail(id);
|
||
return order.copyWith(
|
||
status: WorkOrderStatus.processing,
|
||
assigneeName: order.assigneeName ?? '维修部',
|
||
);
|
||
}
|
||
|
||
Future<WorkOrder> transferWorkOrder(String id, String department) async {
|
||
await Future.delayed(const Duration(milliseconds: 700));
|
||
if (_random.nextInt(20) == 0) {
|
||
throw Exception('网络异常,请稍后重试');
|
||
}
|
||
final order = await getWorkOrderDetail(id);
|
||
return order.copyWith(assigneeName: department);
|
||
}
|
||
|
||
Future<WorkOrder> completeWorkOrder(String id) async {
|
||
await Future.delayed(const Duration(milliseconds: 700));
|
||
if (_random.nextInt(20) == 0) {
|
||
throw Exception('网络异常,请稍后重试');
|
||
}
|
||
final order = await getWorkOrderDetail(id);
|
||
return order.copyWith(
|
||
status: WorkOrderStatus.completed,
|
||
completedAt: DateTime.now(),
|
||
);
|
||
}
|
||
|
||
Future<List<OrderItem>> getOrders(OrderStatus status) async {
|
||
await Future.delayed(const Duration(milliseconds: 600));
|
||
final allOrders = [
|
||
OrderItem(
|
||
id: 'ord_1',
|
||
orderNo: 'ZN20240507001',
|
||
customerName: '王先生',
|
||
customerPhone: '138****8888',
|
||
productName: '智念风景区成人票',
|
||
amount: 128.00,
|
||
status: OrderStatus.pendingVerification,
|
||
createdAt: DateTime.now().subtract(const Duration(hours: 2)),
|
||
paidAt: DateTime.now().subtract(const Duration(hours: 2)),
|
||
verifyCode: 'VK8823',
|
||
quantity: 2,
|
||
scenicSpot: '智念风景区',
|
||
),
|
||
OrderItem(
|
||
id: 'ord_2',
|
||
orderNo: 'ZN20240507002',
|
||
customerName: '李女士',
|
||
customerPhone: '139****6666',
|
||
productName: '度假酒店豪华套房',
|
||
amount: 888.00,
|
||
status: OrderStatus.pendingPayment,
|
||
createdAt: DateTime.now().subtract(const Duration(minutes: 30)),
|
||
quantity: 1,
|
||
scenicSpot: '智念度假酒店',
|
||
),
|
||
OrderItem(
|
||
id: 'ord_3',
|
||
orderNo: 'ZN20240507003',
|
||
customerName: '张 family',
|
||
customerPhone: '137****5555',
|
||
productName: '家庭亲子套票',
|
||
amount: 388.00,
|
||
status: OrderStatus.verified,
|
||
createdAt: DateTime.now().subtract(const Duration(days: 1)),
|
||
paidAt: DateTime.now().subtract(const Duration(days: 1)),
|
||
verifiedAt: DateTime.now().subtract(const Duration(hours: 5)),
|
||
verifyCode: 'VK9912',
|
||
quantity: 3,
|
||
scenicSpot: '智念风景区',
|
||
),
|
||
OrderItem(
|
||
id: 'ord_4',
|
||
orderNo: 'ZN20240507004',
|
||
customerName: '陈先生',
|
||
customerPhone: '136****4444',
|
||
productName: 'SPA养生套餐',
|
||
amount: 598.00,
|
||
status: OrderStatus.pendingRefund,
|
||
createdAt: DateTime.now().subtract(const Duration(days: 2)),
|
||
paidAt: DateTime.now().subtract(const Duration(days: 2)),
|
||
verifyCode: 'VK7734',
|
||
quantity: 1,
|
||
remark: '客户行程变更申请退款',
|
||
scenicSpot: '智念度假酒店',
|
||
),
|
||
OrderItem(
|
||
id: 'ord_5',
|
||
orderNo: 'ZN20240507005',
|
||
customerName: '赵女士',
|
||
customerPhone: '135****3333',
|
||
productName: '景区门票+酒店套餐',
|
||
amount: 1588.00,
|
||
status: OrderStatus.refunded,
|
||
createdAt: DateTime.now().subtract(const Duration(days: 3)),
|
||
paidAt: DateTime.now().subtract(const Duration(days: 3)),
|
||
refundedAt: DateTime.now().subtract(const Duration(days: 1)),
|
||
verifyCode: 'VK6651',
|
||
quantity: 2,
|
||
scenicSpot: '智念度假区',
|
||
),
|
||
];
|
||
|
||
if (status == OrderStatus.all) return allOrders;
|
||
return allOrders.where((o) => o.status == status).toList();
|
||
}
|
||
|
||
Future<OrderItem> getOrderDetail(String id) async {
|
||
await Future.delayed(const Duration(milliseconds: 400));
|
||
final orders = await getOrders(OrderStatus.all);
|
||
return orders.firstWhere((o) => o.id == id, orElse: () => orders.first);
|
||
}
|
||
|
||
Future<OrderItem> verifyOrder(String id) async {
|
||
await Future.delayed(const Duration(milliseconds: 600));
|
||
final order = await getOrderDetail(id);
|
||
return order.copyWith(
|
||
status: OrderStatus.verified,
|
||
verifiedAt: DateTime.now(),
|
||
);
|
||
}
|
||
|
||
Future<OrderItem> scanVerify(String code) async {
|
||
await Future.delayed(const Duration(milliseconds: 800));
|
||
final orders = await getOrders(OrderStatus.all);
|
||
final order = orders.firstWhere(
|
||
(o) => o.verifyCode == code || o.orderNo == code,
|
||
orElse: () => OrderItem(
|
||
id: 'ord_scan_${DateTime.now().millisecondsSinceEpoch}',
|
||
orderNo: code,
|
||
customerName: '扫码客户',
|
||
customerPhone: '138****0000',
|
||
productName: '景区门票',
|
||
amount: 128.00,
|
||
status: OrderStatus.pendingVerification,
|
||
createdAt: DateTime.now(),
|
||
verifyCode: code,
|
||
quantity: 1,
|
||
scenicSpot: '智念风景区',
|
||
),
|
||
);
|
||
return order;
|
||
}
|
||
}
|
||
|
||
final apiService = ApiService();
|