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 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 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 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.fromJson(response.data); if (baseModel.code == 0 && baseModel.data != null) { // 【核心修改】处理接口返回的列表数据 final dataMap = baseModel.data as Map; final List listFromServer = dataMap['list'] ?? []; // 使用 .map() 遍历列表,将每个 item 转换为一个 ReservationModel 对象 reservationList = listFromServer.map((item) { return ReservationModel.fromJson(item as Map); }).toList(); // 根据列表是否为空来更新 hasReservationData 状态 hasReservationData = reservationList.isNotEmpty; } else { // 接口返回业务错误 showToast(baseModel.message); hasReservationData = false; reservationList = []; // 清空列表 } } catch (e) { // 捕获网络或解析异常 showToast('获取预约数据失败'); hasReservationData = false; reservationList = []; // 清空列表 } finally { // 无论成功失败,最后都要关闭加载动画并更新UI dismissLoading(); updateUi(); } } /// 确认预约 Future 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 rejectReservation(String id) async { final item = reservationList.firstWhere((item) => item.id == id); final TextEditingController reasonController = TextEditingController(); final RxString selectedReason = ''.obs; // 预设的拒绝原因列表 final List 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 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) {} } }