Files
ln-ios/ln_jq_app/lib/pages/b_page/site/controller.dart
2025-12-08 09:32:06 +08:00

565 lines
20 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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) {}
}
}