565 lines
20 KiB
Dart
565 lines
20 KiB
Dart
import 'dart:async';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||
import 'package:ln_jq_app/common/styles/theme.dart';
|
||
import 'package:ln_jq_app/storage_service.dart';
|
||
|
||
enum ReservationStatus {
|
||
pending, // 待处理 ( addStatus: 1)
|
||
completed, // 已完成 ( addStatus: 2)
|
||
rejected, // 已拒绝 ( 3)
|
||
unknown, // 未知状态
|
||
}
|
||
|
||
class ReservationModel {
|
||
final String id;
|
||
final String plateNumber;
|
||
final String amount;
|
||
final String time;
|
||
final String contactPerson;
|
||
final String contactPhone;
|
||
ReservationStatus status; // 状态是可变的
|
||
|
||
final String contacts;
|
||
final String phone;
|
||
final String stationName;
|
||
final String startTime;
|
||
final String endTime;
|
||
final String date;
|
||
final String hydAmount;
|
||
final String state;
|
||
final String stateName;
|
||
final String addStatus;
|
||
final String addStatusName;
|
||
|
||
ReservationModel({
|
||
required this.id,
|
||
required this.plateNumber,
|
||
required this.amount,
|
||
required this.time,
|
||
required this.contactPerson,
|
||
required this.contactPhone,
|
||
this.status = ReservationStatus.pending,
|
||
required this.contacts,
|
||
required this.phone,
|
||
required this.stationName,
|
||
required this.startTime,
|
||
required this.endTime,
|
||
required this.date,
|
||
required this.hydAmount,
|
||
required this.state,
|
||
required this.stateName,
|
||
required this.addStatus,
|
||
required this.addStatusName,
|
||
});
|
||
|
||
/// 工厂构造函数,用于从JSON创建ReservationModel实例
|
||
factory ReservationModel.fromJson(Map<String, dynamic> json) {
|
||
//1完成 0待处理 2已拒绝
|
||
ReservationStatus currentStatus;
|
||
int statusFromServer = json['addStatus'] as int? ?? 0;
|
||
switch (statusFromServer) {
|
||
case 0:
|
||
currentStatus = ReservationStatus.pending;
|
||
break;
|
||
case 1:
|
||
currentStatus = ReservationStatus.completed;
|
||
break;
|
||
case 2:
|
||
currentStatus = ReservationStatus.rejected;
|
||
break;
|
||
default:
|
||
currentStatus = ReservationStatus.unknown;
|
||
}
|
||
|
||
// 格式化时间显示
|
||
String startTimeStr = json['startTime']?.toString() ?? '';
|
||
String endTimeStr = json['endTime']?.toString() ?? '';
|
||
String dateStr = json['date']?.toString() ?? '';
|
||
String timeRange =
|
||
(startTimeStr.isNotEmpty && endTimeStr.isNotEmpty && dateStr.isNotEmpty)
|
||
? '$dateStr ${startTimeStr.substring(11, 16)}-${endTimeStr.substring(11, 16)}' // 截取 HH:mm
|
||
: '时间未定';
|
||
|
||
return ReservationModel(
|
||
// 原始字段,用于UI兼容
|
||
id: json['id']?.toString() ?? '',
|
||
plateNumber: json['plateNumber']?.toString() ?? '未知车牌',
|
||
amount: '${json['hydAmount']?.toString() ?? '0'}kg',
|
||
time: timeRange,
|
||
contactPerson: json['contacts']?.toString() ?? '无联系人',
|
||
contactPhone: json['phone']?.toString() ?? '无联系电话',
|
||
status: currentStatus,
|
||
|
||
// 新增的完整字段
|
||
contacts: json['contacts']?.toString() ?? '',
|
||
phone: json['phone']?.toString() ?? '',
|
||
stationName: json['stationName']?.toString() ?? '',
|
||
startTime: startTimeStr,
|
||
endTime: endTimeStr,
|
||
date: dateStr,
|
||
hydAmount: json['hydAmount']?.toString() ?? '0',
|
||
state: json['state']?.toString() ?? '',
|
||
addStatus: statusFromServer.toString(),
|
||
addStatusName: json['addStatusName']?.toString() ?? '',
|
||
stateName: json['stateName']?.toString() ?? '',
|
||
);
|
||
}
|
||
}
|
||
|
||
class SiteController extends GetxController with BaseControllerMixin {
|
||
@override
|
||
String get builderId => 'site';
|
||
|
||
SiteController();
|
||
|
||
/// 状态变量:是否有预约数据
|
||
bool hasReservationData = false;
|
||
|
||
// 新增预约数据列表
|
||
List<ReservationModel> reservationList = [];
|
||
Timer? _refreshTimer;
|
||
|
||
final TextEditingController searchController = TextEditingController();
|
||
|
||
@override
|
||
void onInit() {
|
||
super.onInit();
|
||
renderData();
|
||
|
||
startAutoRefresh();
|
||
}
|
||
|
||
@override
|
||
void onClose() {
|
||
stopAutoRefresh();
|
||
searchController.dispose();
|
||
super.onClose();
|
||
}
|
||
|
||
void startAutoRefresh() {
|
||
// 先停止已存在的定时器,防止重复启动
|
||
stopAutoRefresh();
|
||
|
||
// 创建一个每5分钟执行一次的周期性定时器
|
||
_refreshTimer = Timer.periodic(const Duration(minutes: 5), (timer) {
|
||
fetchReservationData();
|
||
});
|
||
}
|
||
|
||
/// 【6. 新增】停止定时器的方法
|
||
void stopAutoRefresh() {
|
||
// 如果定时器存在并且是激活状态,就取消它
|
||
_refreshTimer?.cancel();
|
||
_refreshTimer = null; // 置为null,方便判断
|
||
print("【自动刷新】定时器已停止。");
|
||
}
|
||
|
||
/// 获取预约数据的方法
|
||
Future<void> fetchReservationData() async {
|
||
showLoading("加载中");
|
||
|
||
final String searchText = searchController.text.trim();
|
||
|
||
try {
|
||
var response = await HttpService.to.post(
|
||
"appointment/orderAddHyd/sitOrderPage",
|
||
data: {
|
||
'stationName': name, // 使用从 renderData 中获取到的 name
|
||
'pageNum': 1,
|
||
'pageSize': 50, // 暂时不考虑分页,一次获取30条
|
||
'plateNumber': searchText, // 加氢站名称
|
||
'phone': searchText, //手机号
|
||
},
|
||
);
|
||
|
||
// 安全校验
|
||
if (response == null || response.data == null) {
|
||
showToast('暂时无法获取预约数据');
|
||
hasReservationData = false;
|
||
reservationList = [];
|
||
return;
|
||
}
|
||
|
||
final baseModel = BaseModel<dynamic>.fromJson(response.data);
|
||
|
||
if (baseModel.code == 0 && baseModel.data != null) {
|
||
// 【核心修改】处理接口返回的列表数据
|
||
final dataMap = baseModel.data as Map<String, dynamic>;
|
||
|
||
final List<dynamic> listFromServer = dataMap['list'] ?? [];
|
||
|
||
// 使用 .map() 遍历列表,将每个 item 转换为一个 ReservationModel 对象
|
||
reservationList = listFromServer.map((item) {
|
||
return ReservationModel.fromJson(item as Map<String, dynamic>);
|
||
}).toList();
|
||
|
||
// 根据列表是否为空来更新 hasReservationData 状态
|
||
hasReservationData = reservationList.isNotEmpty;
|
||
} else {
|
||
// 接口返回业务错误
|
||
showToast(baseModel.message);
|
||
hasReservationData = false;
|
||
reservationList = []; // 清空列表
|
||
}
|
||
} catch (e) {
|
||
// 捕获网络或解析异常
|
||
showToast('获取预约数据失败');
|
||
hasReservationData = false;
|
||
reservationList = []; // 清空列表
|
||
} finally {
|
||
// 无论成功失败,最后都要关闭加载动画并更新UI
|
||
dismissLoading();
|
||
updateUi();
|
||
}
|
||
}
|
||
|
||
/// 确认预约
|
||
Future<void> confirmReservation(String id) async {
|
||
final item = reservationList.firstWhere(
|
||
(item) => item.id == id,
|
||
orElse: () => throw Exception('Reservation not found'),
|
||
);
|
||
final TextEditingController amountController = TextEditingController(
|
||
text: item.hydAmount,
|
||
);
|
||
Get.dialog(
|
||
Dialog(
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), // 圆角
|
||
child: Padding(
|
||
padding: const EdgeInsets.only(top: 24.0),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min, // 高度自适应
|
||
children: [
|
||
const Text(
|
||
'确认加氢状态',
|
||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||
),
|
||
const SizedBox(height: 5),
|
||
// content 部分
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||
child: Column(
|
||
children: [
|
||
Text(
|
||
'车牌号 ${item.plateNumber}',
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
color: AppTheme.themeColor,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Text(
|
||
'加氢量',
|
||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||
),
|
||
const SizedBox(width: 16),
|
||
SizedBox(
|
||
width: 100,
|
||
child: TextField(
|
||
controller: amountController,
|
||
textAlign: TextAlign.center,
|
||
keyboardType: const TextInputType.numberWithOptions(
|
||
decimal: true,
|
||
),
|
||
style: TextStyle(
|
||
fontSize: 22,
|
||
fontWeight: FontWeight.bold,
|
||
color: Get.theme.primaryColor,
|
||
),
|
||
decoration: const InputDecoration(
|
||
suffixText: 'kg',
|
||
suffixStyle: TextStyle(fontSize: 16, color: Colors.grey),
|
||
enabledBorder: UnderlineInputBorder(
|
||
borderSide: BorderSide(color: Colors.grey),
|
||
),
|
||
focusedBorder: UnderlineInputBorder(
|
||
borderSide: BorderSide(color: Colors.grey, width: 2),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
const Text(
|
||
'请选择本次加氢的实际状态\n用于更新预约记录。',
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 24),
|
||
// actions 部分 (按钮)
|
||
Padding(
|
||
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||
child: Column(
|
||
children: [
|
||
ElevatedButton(
|
||
onPressed: () {
|
||
Get.back(); // 关闭弹窗
|
||
final num addHydAmount = num.tryParse(amountController.text) ?? 0;
|
||
upDataService(id, 0, 1, addHydAmount, "", item);
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
minimumSize: const Size(double.infinity, 48),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(5),
|
||
),
|
||
),
|
||
child: const Text('加氢完成', style: TextStyle(fontSize: 16)),
|
||
),
|
||
const SizedBox(height: 12),
|
||
ElevatedButton(
|
||
onPressed: () {
|
||
Get.back(); // 关闭弹窗
|
||
upDataService(id, 0, 2, 0, "", item);
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: Colors.orange,
|
||
minimumSize: const Size(double.infinity, 48),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(5),
|
||
),
|
||
),
|
||
child: const Text('未加氢', style: TextStyle(fontSize: 16)),
|
||
),
|
||
const SizedBox(height: 12),
|
||
TextButton(
|
||
onPressed: () => Get.back(), // 只关闭弹窗
|
||
child: const Text(
|
||
'暂不处理',
|
||
style: TextStyle(color: Colors.grey, fontSize: 14),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
barrierDismissible: false, // 点击外部不关闭弹窗
|
||
);
|
||
}
|
||
|
||
Future<void> rejectReservation(String id) async {
|
||
final item = reservationList.firstWhere((item) => item.id == id);
|
||
final TextEditingController reasonController = TextEditingController();
|
||
final RxString selectedReason = ''.obs;
|
||
|
||
// 预设的拒绝原因列表
|
||
final List<String> presetReasons = ['车辆未到场', '司机联系不上', '设备故障无法加氢'];
|
||
|
||
Get.dialog(
|
||
Dialog(
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||
child: SingleChildScrollView(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(24.0),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
const Text(
|
||
'拒绝预约',
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
'正在处理车牌号 ${item.plateNumber} 的预约',
|
||
textAlign: TextAlign.center,
|
||
style: const TextStyle(fontSize: 16, color: AppTheme.themeColor),
|
||
),
|
||
const SizedBox(height: 24),
|
||
|
||
// 4. 预设原因选择区域
|
||
const Text('选择或填写拒绝原因:', style: TextStyle(color: Colors.grey)),
|
||
const SizedBox(height: 8),
|
||
Obx(
|
||
() => Wrap(
|
||
// 使用 Wrap 自动换行
|
||
spacing: 8.0, // 水平间距
|
||
children: presetReasons.map((reason) {
|
||
final isSelected = selectedReason.value == reason;
|
||
return ChoiceChip(
|
||
label: Text(reason),
|
||
selected: isSelected,
|
||
onSelected: (selected) {
|
||
if (selected) {
|
||
selectedReason.value = reason;
|
||
reasonController.clear(); // 选择预设原因时,清空自定义输入
|
||
}
|
||
},
|
||
selectedColor: Get.theme.primaryColor.withOpacity(0.2),
|
||
labelStyle: TextStyle(
|
||
color: isSelected ? Get.theme.primaryColor : Colors.black,
|
||
),
|
||
);
|
||
}).toList(),
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
|
||
// 5. 自定义原因输入框
|
||
TextField(
|
||
controller: reasonController,
|
||
maxLines: 2,
|
||
decoration: InputDecoration(
|
||
hintText: '输入其它原因',
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||
),
|
||
),
|
||
onChanged: (text) {
|
||
if (text.isNotEmpty) {
|
||
selectedReason.value = ''; // 输入自定义原因时,取消预设选择
|
||
}
|
||
},
|
||
),
|
||
const SizedBox(height: 24),
|
||
|
||
// 6. 按钮区域
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: TextButton(
|
||
onPressed: () {
|
||
// 获取最终的拒绝原因
|
||
String finalReason = reasonController.text.trim();
|
||
if (finalReason.isEmpty) {
|
||
finalReason = selectedReason.value;
|
||
}
|
||
|
||
if (finalReason.isEmpty) {
|
||
showToast('请选择或填写一个拒绝原因');
|
||
return;
|
||
}
|
||
|
||
Get.back(); // 关闭弹窗
|
||
upDataService(id, 1, -1, 0, finalReason, item);
|
||
},
|
||
child: const Text('确认拒绝', style: TextStyle(color: Colors.red)),
|
||
),
|
||
),
|
||
const SizedBox(width: 16),
|
||
Expanded(
|
||
child: TextButton(
|
||
onPressed: () => Get.back(),
|
||
child: const Text('暂不处理', style: TextStyle(color: Colors.grey)),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
barrierDismissible: false,
|
||
);
|
||
}
|
||
|
||
//addStatus 1完成 2未加 -1拒绝
|
||
void upDataService(
|
||
String id,
|
||
int status,
|
||
int addStatus,
|
||
num addHydAmount,
|
||
String rejectReason,
|
||
ReservationModel item,
|
||
) async {
|
||
showLoading("确认中");
|
||
|
||
try {
|
||
var responseData = await HttpService.to.post(
|
||
'appointment/orderAddHyd/completeOrder',
|
||
data: status == 0
|
||
? {
|
||
'id': id,
|
||
'addStatus': addStatus, //完成使用 完成1,未加2
|
||
"addHydAmount": addHydAmount,
|
||
}
|
||
: {
|
||
'id': id,
|
||
'state': addStatus, //拒绝使用 -1
|
||
"rejectReason": rejectReason,
|
||
},
|
||
);
|
||
|
||
if (responseData == null && responseData!.data == null) {
|
||
dismissLoading();
|
||
showToast('服务暂不可用,请稍后');
|
||
return;
|
||
}
|
||
var result = BaseModel.fromJson(responseData.data);
|
||
if (result.code == 0) {
|
||
showSuccessToast("操作成功");
|
||
}
|
||
dismissLoading();
|
||
|
||
if (addStatus == 1) {
|
||
item.status = ReservationStatus.completed;
|
||
} else if (addStatus == -1) {
|
||
item.status = ReservationStatus.rejected;
|
||
} else if (addStatus == -2) {
|
||
item.status = ReservationStatus.pending;
|
||
}
|
||
updateUi();
|
||
} catch (e) {
|
||
dismissLoading();
|
||
}
|
||
}
|
||
|
||
String leftHydrogen = "";
|
||
String orderAmount = "";
|
||
String completedAmount = "";
|
||
String name = "";
|
||
String orderTotalAmount = "";
|
||
String orderUnfinishedAmount = "";
|
||
|
||
Future<void> renderData() async {
|
||
try {
|
||
var responseData = await HttpService.to.get(
|
||
'appointment/station/getStationInfoById?hydrogenId=${StorageService.to.userId}',
|
||
);
|
||
|
||
if (responseData == null && responseData!.data == null) {
|
||
showToast('暂时无法获取站点信息');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
var result = BaseModel.fromJson(responseData.data);
|
||
|
||
leftHydrogen = result.data["leftHydrogen"] ?? "";
|
||
orderAmount = result.data["orderAmount"].toString();
|
||
completedAmount = result.data["completedAmount"].toString();
|
||
name = result.data["name"].toString();
|
||
orderTotalAmount = result.data["orderTotalAmount"] ?? "";
|
||
orderUnfinishedAmount = result.data["orderUnfinishedAmount"] ?? "";
|
||
|
||
leftHydrogen = leftHydrogen.isEmpty ? "统计中" : leftHydrogen.toString();
|
||
orderTotalAmount = orderTotalAmount.isEmpty ? "统计中" : orderTotalAmount.toString();
|
||
orderUnfinishedAmount = orderUnfinishedAmount.isEmpty
|
||
? "统计中"
|
||
: orderUnfinishedAmount.toString();
|
||
|
||
//加载列表数据
|
||
fetchReservationData();
|
||
} catch (e) {
|
||
showToast('数据异常');
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
}
|