补充选择器逻辑
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/common/common.dart';
|
||||
@@ -9,117 +10,283 @@ import 'package:ln_jq_app/common/model/vehicle_info.dart';
|
||||
import 'package:ln_jq_app/pages/b_page/site/controller.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
|
||||
import '../../../common/styles/theme.dart'; // 用于日期格式化
|
||||
import '../../../common/styles/theme.dart';
|
||||
|
||||
class ReservationController extends GetxController with BaseControllerMixin {
|
||||
@override
|
||||
String get builderId => 'reservation';
|
||||
|
||||
// 【修改】使用 Rx 变量,让 GetX 的 Obx/GetX 能够自动监听变化,UI更新更简单
|
||||
// 日期,默认为今天
|
||||
final Rx<DateTime> selectedDate = DateTime.now().obs;
|
||||
|
||||
// 开始时间,默认为当前时分
|
||||
final Rx<TimeOfDay> startTime = TimeOfDay.now().obs;
|
||||
|
||||
// 结束时间,默认为开始时间后30分钟
|
||||
final Rx<TimeOfDay> endTime = TimeOfDay.fromDateTime(
|
||||
DateTime.now().add(const Duration(minutes: 30)),
|
||||
).obs;
|
||||
|
||||
// 预约氢量
|
||||
final TextEditingController amountController = TextEditingController();
|
||||
|
||||
// 车牌号
|
||||
TextEditingController plateNumberController = TextEditingController();
|
||||
|
||||
// 加氢站
|
||||
final List<String> stationOptions = [
|
||||
'诚志AP银河路加氢站',
|
||||
'站点B',
|
||||
'站点C',
|
||||
'站点C',
|
||||
'站点C',
|
||||
'站点C',
|
||||
'站点C',
|
||||
];
|
||||
final List<String> stationOptions = ['诚志AP银河路加氢站', '站点B', '站点C'];
|
||||
final Rx<String> selectedStation = '诚志AP银河路加氢站'.obs;
|
||||
|
||||
// --- 用于UI显示的格式化字符串 (Getters) ---
|
||||
String get formattedDate => DateFormat('yyyy-MM-dd').format(selectedDate.value);
|
||||
|
||||
// 使用 context-aware 的 format 方法
|
||||
String get formattedStartTime => startTime.value.format(Get.context!);
|
||||
String get formattedStartTime => _formatTimeOfDay(startTime.value);
|
||||
|
||||
String get formattedEndTime => endTime.value.format(Get.context!);
|
||||
String get formattedEndTime => _formatTimeOfDay(endTime.value);
|
||||
|
||||
/// --- 交互方法 ---
|
||||
void pickDate(BuildContext context) {
|
||||
DateTime tempDate = selectedDate.value;
|
||||
|
||||
/// 【修改】显示 Flutter 内置的日期选择器
|
||||
void pickDate(BuildContext context) async {
|
||||
// 改为 async
|
||||
final DateTime? pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: selectedDate.value,
|
||||
firstDate: DateTime.now(),
|
||||
// 只能从今天开始选
|
||||
lastDate: DateTime.now().add(const Duration(days: 365)),
|
||||
// 假设最多预约一年内
|
||||
helpText: '选择预约日期',
|
||||
confirmText: '确定',
|
||||
cancelText: '取消',
|
||||
Get.bottomSheet(
|
||||
Container(
|
||||
height: 300,
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
CupertinoButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text(
|
||||
'取消',
|
||||
style: TextStyle(color: CupertinoColors.systemGrey),
|
||||
),
|
||||
),
|
||||
CupertinoButton(
|
||||
onPressed: () {
|
||||
selectedDate.value = tempDate;
|
||||
Get.back();
|
||||
},
|
||||
child: const Text(
|
||||
'确认',
|
||||
style: TextStyle(
|
||||
color: AppTheme.themeColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1, color: Color(0xFFE5E5E5)),
|
||||
Expanded(
|
||||
child: CupertinoDatePicker(
|
||||
mode: CupertinoDatePickerMode.date,
|
||||
initialDateTime:
|
||||
selectedDate.value.isBefore(
|
||||
DateTime(
|
||||
DateTime.now().year,
|
||||
DateTime.now().month,
|
||||
DateTime.now().day,
|
||||
),
|
||||
)
|
||||
? DateTime(
|
||||
DateTime.now().year,
|
||||
DateTime.now().month,
|
||||
DateTime.now().day,
|
||||
)
|
||||
: selectedDate.value,
|
||||
|
||||
// 【核心】设置最小可选日期为“今天凌晨0点”
|
||||
minimumDate: DateTime(
|
||||
DateTime.now().year,
|
||||
DateTime.now().month,
|
||||
DateTime.now().day,
|
||||
),
|
||||
maximumDate: DateTime.now().add(const Duration(days: 365)),
|
||||
onDateTimeChanged: (DateTime newDate) {
|
||||
tempDate = newDate;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
);
|
||||
|
||||
if (pickedDate != null && pickedDate != selectedDate.value) {
|
||||
selectedDate.value = pickedDate;
|
||||
}
|
||||
}
|
||||
|
||||
/// 【修改】显示 Flutter 内置的时间选择器
|
||||
void pickTime(BuildContext context, bool isStartTime) async {
|
||||
// 改为 async
|
||||
void pickTime(BuildContext context, bool isStartTime) {
|
||||
// 1. 确定当前操作的时间和初始值
|
||||
TimeOfDay initialTime = isStartTime ? startTime.value : endTime.value;
|
||||
DateTime now = DateTime.now();
|
||||
|
||||
final TimeOfDay? pickedTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: initialTime,
|
||||
helpText: isStartTime ? '选择开始时间' : '选择结束时间',
|
||||
confirmText: '确定',
|
||||
cancelText: '取消',
|
||||
// 2.【核心】计算最小可选时间 (Minimum Date)
|
||||
DateTime? minimumDateTime; // 默认为 null,即可选任意时间
|
||||
|
||||
// 获取当前选择的日期(年月日)
|
||||
final selectedDay = DateTime(
|
||||
selectedDate.value.year,
|
||||
selectedDate.value.month,
|
||||
selectedDate.value.day,
|
||||
);
|
||||
|
||||
if (pickedTime != null) {
|
||||
if (isStartTime) {
|
||||
startTime.value = pickedTime;
|
||||
// 如果新的开始时间晚于或等于结束时间,自动将结束时间设置为开始时间后30分钟
|
||||
if ((pickedTime.hour * 60 + pickedTime.minute) >=
|
||||
(endTime.value.hour * 60 + endTime.value.minute)) {
|
||||
final newDateTime = DateTime(
|
||||
selectedDate.value.year,
|
||||
selectedDate.value.month,
|
||||
selectedDate.value.day,
|
||||
pickedTime.hour,
|
||||
pickedTime.minute,
|
||||
).add(const Duration(minutes: 30));
|
||||
endTime.value = TimeOfDay.fromDateTime(newDateTime);
|
||||
}
|
||||
} else {
|
||||
// 确保结束时间不早于开始时间
|
||||
if ((pickedTime.hour * 60 + pickedTime.minute) >
|
||||
(startTime.value.hour * 60 + startTime.value.minute)) {
|
||||
endTime.value = pickedTime;
|
||||
} else {
|
||||
// 如果选择了更早的时间,给出提示
|
||||
Get.snackbar(
|
||||
'时间选择错误',
|
||||
'结束时间必须晚于开始时间',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
// 获取今天的日期(年月日)
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
if (isStartTime) {
|
||||
// 如果是选择【开始时间】并且日期是【今天】
|
||||
if (selectedDay.isAtSameMomentAs(today)) {
|
||||
minimumDateTime = now; // 最小可选时间就是现在
|
||||
}
|
||||
} else {
|
||||
// 如果是选择【结束时间】
|
||||
// 将开始时间转换为 DateTime 对象
|
||||
final startDateTime = DateTime(
|
||||
selectedDate.value.year,
|
||||
selectedDate.value.month,
|
||||
selectedDate.value.day,
|
||||
startTime.value.hour,
|
||||
startTime.value.minute,
|
||||
);
|
||||
|
||||
// 结束时间的最小值必须晚于开始时间
|
||||
minimumDateTime = startDateTime;
|
||||
|
||||
// 如果日期是今天,并且开始时间早于现在,那么结束时间的最小值也应该是现在
|
||||
if (selectedDay.isAtSameMomentAs(today) && startDateTime.isBefore(now)) {
|
||||
minimumDateTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 准备 DatePicker 的初始显示时间
|
||||
DateTime initialDateTime = DateTime(
|
||||
selectedDate.value.year,
|
||||
selectedDate.value.month,
|
||||
selectedDate.value.day,
|
||||
initialTime.hour,
|
||||
initialTime.minute,
|
||||
);
|
||||
|
||||
// 确保初始时间不早于最小时间
|
||||
if (minimumDateTime != null && initialDateTime.isBefore(minimumDateTime)) {
|
||||
initialDateTime = minimumDateTime;
|
||||
}
|
||||
|
||||
DateTime tempTime = initialDateTime;
|
||||
|
||||
Get.bottomSheet(
|
||||
Container(
|
||||
height: 300,
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
CupertinoButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text(
|
||||
'取消',
|
||||
style: TextStyle(color: CupertinoColors.systemGrey),
|
||||
),
|
||||
),
|
||||
CupertinoButton(
|
||||
onPressed: () {
|
||||
final pickedTempTime = TimeOfDay.fromDateTime(tempTime);
|
||||
|
||||
// 验证条件1:不能选择过去的时间
|
||||
// 这个验证只在选择【今天】的【开始时间】或【结束时间】时有意义
|
||||
final selectedDay = DateTime(
|
||||
selectedDate.value.year,
|
||||
selectedDate.value.month,
|
||||
selectedDate.value.day,
|
||||
);
|
||||
final today = DateTime(
|
||||
DateTime.now().year,
|
||||
DateTime.now().month,
|
||||
DateTime.now().day,
|
||||
);
|
||||
|
||||
if (selectedDay.isAtSameMomentAs(today) &&
|
||||
tempTime.isBefore(DateTime.now())) {
|
||||
showToast('不能选择过去的时间');
|
||||
return; // 中断执行,不关闭弹窗
|
||||
}
|
||||
|
||||
//结束时间必须晚于开始时间
|
||||
if (isStartTime) {
|
||||
// 和【已有的结束时间】比较
|
||||
final pickedStartInMinutes =
|
||||
pickedTempTime.hour * 60 + pickedTempTime.minute;
|
||||
final endInMinutes =
|
||||
endTime.value.hour * 60 + endTime.value.minute;
|
||||
|
||||
if (pickedStartInMinutes >= endInMinutes) {
|
||||
// 在这里,我们不提示错误,而是智能地调整结束时间
|
||||
startTime.value = pickedTempTime;
|
||||
final newEndDateTime = tempTime.add(
|
||||
const Duration(minutes: 30),
|
||||
);
|
||||
endTime.value = TimeOfDay.fromDateTime(newEndDateTime);
|
||||
} else {
|
||||
// 合法,直接设置开始时间
|
||||
startTime.value = pickedTempTime;
|
||||
}
|
||||
} else {
|
||||
// 如果当前正在设置【结束时间】,我们来和【已有的开始时间】比较
|
||||
final pickedEndInMinutes =
|
||||
pickedTempTime.hour * 60 + pickedTempTime.minute;
|
||||
final startInMinutes =
|
||||
startTime.value.hour * 60 + startTime.value.minute;
|
||||
|
||||
if (pickedEndInMinutes <= startInMinutes) {
|
||||
showToast('结束时间必须晚于开始时间');
|
||||
return; // 中断执行,不关闭弹窗
|
||||
} else {
|
||||
// 合法,直接设置结束时间
|
||||
endTime.value = pickedTempTime;
|
||||
}
|
||||
}
|
||||
Get.back();
|
||||
},
|
||||
child: const Text(
|
||||
'确认',
|
||||
style: TextStyle(
|
||||
color: AppTheme.themeColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1, color: Color(0xFFE5E5E5)),
|
||||
Expanded(
|
||||
child: CupertinoDatePicker(
|
||||
mode: CupertinoDatePickerMode.time,
|
||||
use24hFormat: true,
|
||||
initialDateTime: initialDateTime,
|
||||
minimumDate: minimumDateTime,
|
||||
onDateTimeChanged: (DateTime newTime) {
|
||||
tempTime = newTime;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
);
|
||||
}
|
||||
|
||||
/// 提交预约
|
||||
@@ -150,7 +317,7 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
var response = await HttpService.to.post(
|
||||
"appointment/orderAddHyd/driverOrderPage",
|
||||
data: {
|
||||
'phone': StorageService.to.userId, // 使用从 renderData 中获取到的 name
|
||||
'phone': StorageService.to.phone, // 使用从 renderData 中获取到的 name
|
||||
'pageNum': 1,
|
||||
'pageSize': 30, // 暂时不考虑分页,一次获取30条
|
||||
},
|
||||
@@ -189,7 +356,7 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
|
||||
Get.bottomSheet(
|
||||
Container(
|
||||
height: Get.height * 0.45,
|
||||
height: Get.height * 0.55,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
@@ -199,6 +366,7 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
//标题
|
||||
Container(
|
||||
padding: const EdgeInsets.fromLTRB(20, 15, 20, 15),
|
||||
child: Row(
|
||||
@@ -207,7 +375,7 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
const Text(
|
||||
'我的预约',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
@@ -228,83 +396,91 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: reservationList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final ReservationModel reservation = reservationList[index];
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12.0),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE6F7FF), // Light blue background
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: const Color(0xFF91D5FF),
|
||||
), // Blue border
|
||||
),
|
||||
child: Text(
|
||||
reservation.stateName,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF1890FF),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(
|
||||
0xFFFFF7E6,
|
||||
), // Light orange background
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
reservation.addStatusName,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFFA8C16),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: !hasReservationData
|
||||
? Container(
|
||||
margin: EdgeInsets.only(top: 40),
|
||||
child: TextX.bodyLarge('暂无预约', weight: FontWeight.w500),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: reservationList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final ReservationModel reservation = reservationList[index];
|
||||
return Card(
|
||||
color: Color.fromARGB(255, 246, 248, 250),
|
||||
margin: const EdgeInsets.only(bottom: 12.0),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildDetailRow('车牌号:', reservation.plateNumber),
|
||||
_buildDetailRow('预约日期:', reservation.date),
|
||||
_buildDetailRow('预约氢量:', reservation.hydAmount),
|
||||
_buildDetailRow('加氢站:', reservation.stationName),
|
||||
_buildDetailRow('开始时间:', reservation.startTime),
|
||||
_buildDetailRow('结束时间:', reservation.endTime),
|
||||
_buildDetailRow('联系人:', reservation.contacts),
|
||||
_buildDetailRow('联系电话:', reservation.phone),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(
|
||||
0xFFE6F7FF,
|
||||
), // Light blue background
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: const Color(0xFF91D5FF),
|
||||
), // Blue border
|
||||
),
|
||||
child: Text(
|
||||
reservation.stateName,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF1890FF),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(
|
||||
0xFFFFF7E6,
|
||||
), // Light orange background
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
reservation.addStatusName,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFFA8C16),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildDetailRow('车牌号:', reservation.plateNumber),
|
||||
_buildDetailRow('预约日期:', reservation.date),
|
||||
_buildDetailRow('预约氢量:', reservation.hydAmount),
|
||||
_buildDetailRow('加氢站:', reservation.stationName),
|
||||
_buildDetailRow('开始时间:', reservation.startTime),
|
||||
_buildDetailRow('结束时间:', reservation.endTime),
|
||||
_buildDetailRow('联系人:', reservation.contacts),
|
||||
_buildDetailRow('联系电话:', reservation.phone),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -344,8 +520,6 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
String name = "";
|
||||
String leftHydrogen = "0";
|
||||
String workEfficiency = "0";
|
||||
|
||||
//累计数据
|
||||
String fillingWeight = "0";
|
||||
String fillingTimes = "0";
|
||||
String plateNumber = "";
|
||||
@@ -355,7 +529,6 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
void onInit() {
|
||||
phone = StorageService.to.phone ?? "";
|
||||
name = StorageService.to.name ?? "";
|
||||
|
||||
getUserBindCarInfo();
|
||||
getSiteList();
|
||||
super.onInit();
|
||||
@@ -457,6 +630,12 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
}
|
||||
|
||||
String _formatTimeOfDay(TimeOfDay time) {
|
||||
final hour = time.hour.toString().padLeft(2, '0');
|
||||
final minute = time.minute.toString().padLeft(2, '0');
|
||||
return '$hour:$minute';
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
amountController.dispose();
|
||||
|
||||
Reference in New Issue
Block a user