修改选择器

This commit is contained in:
2025-12-01 15:27:00 +08:00
parent cdc5af7f45
commit 4f99ab4164
2 changed files with 67 additions and 152 deletions

View File

@@ -14,6 +14,20 @@ import 'package:ln_jq_app/storage_service.dart';
import '../../../common/styles/theme.dart';
/// Helper class for managing time slots
class TimeSlot {
final TimeOfDay start;
final TimeOfDay end;
TimeSlot(this.start, this.end);
String get display {
final startStr = '${start.hour.toString().padLeft(2, '0')}:${start.minute.toString().padLeft(2, '0')}';
final endStr = '${end.hour.toString().padLeft(2, '0')}:${end.minute.toString().padLeft(2, '0')}';
return '$startStr - $endStr';
}
}
class C_ReservationController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'reservation';
@@ -28,7 +42,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
TextEditingController plateNumberController = TextEditingController();
final RxList<StationModel> stationOptions = <StationModel>[].obs;
final Rxn<String> selectedStationId = Rxn<String>(); // 用 ID 来选择
final Rxn<String> selectedStationId = Rxn<String>();
String get formattedDate => DateFormat('yyyy-MM-dd').format(selectedDate.value);
@@ -59,23 +73,14 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
children: [
CupertinoButton(
onPressed: () => Get.back(),
child: const Text(
'取消',
style: TextStyle(color: CupertinoColors.systemGrey),
),
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,
),
),
child: const Text('确认', style: TextStyle(color: AppTheme.themeColor, fontWeight: FontWeight.bold)),
),
],
),
@@ -84,27 +89,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
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,
),
initialDateTime: selectedDate.value,
minimumDate: DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day),
maximumDate: DateTime.now().add(const Duration(days: 365)),
onDateTimeChanged: (DateTime newDate) {
tempDate = newDate;
@@ -118,52 +104,50 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
);
}
void pickTime(BuildContext context, bool isStartTime) {
// 1. 确定当前操作的时间和初始值
TimeOfDay initialTime = isStartTime ? startTime.value : endTime.value;
DateTime now = DateTime.now();
///30 分钟为间隔 时间选择器
void pickTime(BuildContext context) {
final now = DateTime.now();
final isToday = selectedDate.value.year == now.year &&
selectedDate.value.month == now.month &&
selectedDate.value.day == now.day;
// 2. 准备小时和分钟的数据源
List<int> hours = List<int>.generate(24, (index) => index);
List<int> minutes = [0, 30];
final List<TimeSlot> availableSlots = [];
for (int i = 0; i < 48; i++) {
final startMinutes = i * 30;
final endMinutes = startMinutes + 30;
// 3. 计算初始选中的索引
int initialHour = initialTime.hour;
// 将初始分钟校准到0或30并找到对应的索引
int initialMinute = initialTime.minute;
int minuteIndex = initialMinute < 30 ? 0 : 1;
initialMinute = minutes[minuteIndex]; // 校准后的分钟
final startTime = TimeOfDay(hour: startMinutes ~/ 60, minute: startMinutes % 60);
final endTime = TimeOfDay(hour: (endMinutes ~/ 60) % 24, minute: endMinutes % 60);
// 如果校准后导致时间早于当前时间,需要向上调整
final selectedDay = DateTime(selectedDate.value.year, selectedDate.value.month, selectedDate.value.day);
final today = DateTime(now.year, now.month, now.day);
if (selectedDay.isAtSameMomentAs(today)) {
if (initialHour < now.hour || (initialHour == now.hour && initialMinute < now.minute)) {
initialHour = now.hour;
if (now.minute > 30) {
// 如果当前分钟>30, 则进位到下一小时的0分
initialHour = (now.hour + 1) % 24;
initialMinute = 0;
} else {
// 否则取30分
initialMinute = 30;
}
final slotStartDateTime = DateTime(
selectedDate.value.year,
selectedDate.value.month,
selectedDate.value.day,
startTime.hour,
startTime.minute,
);
if (!isToday || slotStartDateTime.isAfter(now)) {
availableSlots.add(TimeSlot(startTime, endTime));
}
}
// 重新获取校准后的索引
minuteIndex = minutes.indexOf(initialMinute);
if (availableSlots.isEmpty) {
showToast('今天已没有可预约的时间段');
return;
}
int initialItem = availableSlots.indexWhere((slot) =>
slot.start.hour == startTime.value.hour &&
(startTime.value.minute < 30 ? slot.start.minute == 0 : slot.start.minute == 30));
if (initialItem == -1) {
initialItem = 0;
}
// 4. 创建 FixedExtentScrollController 来控制滚轮的初始位置
final FixedExtentScrollController hourController =
FixedExtentScrollController(initialItem: hours.indexOf(initialHour));
final FixedExtentScrollController minuteController =
FixedExtentScrollController(initialItem: minuteIndex);
TimeSlot tempSlot = availableSlots[initialItem];
// 5. 存储临时选择的值
int tempHour = initialHour;
int tempMinute = initialMinute;
final FixedExtentScrollController scrollController =
FixedExtentScrollController(initialItem: initialItem);
Get.bottomSheet(
Container(
@@ -188,88 +172,25 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
),
CupertinoButton(
onPressed: () {
final pickedTempTime = TimeOfDay(hour: tempHour, minute: tempMinute);
final now = DateTime.now();
// --- 合并和简化校验逻辑 ---
final selectedDateTime = DateTime(
selectedDate.value.year,
selectedDate.value.month,
selectedDate.value.day,
pickedTempTime.hour,
pickedTempTime.minute,
);
// 验证1: 不能选择过去的时间(留出一分钟缓冲)
if (selectedDateTime.isBefore(now.subtract(const Duration(minutes: 1)))) {
showToast('不能选择过去的时间');
return;
}
// 验证2: 结束时间必须晚于开始时间
if (isStartTime) {
startTime.value = pickedTempTime;
final startInMinutes = pickedTempTime.hour * 60 + pickedTempTime.minute;
final endInMinutes = endTime.value.hour * 60 + endTime.value.minute;
// 如果新的开始时间大于等于结束时间,自动将结束时间设置为开始时间+30分钟
if (startInMinutes >= endInMinutes) {
final newEndDateTime = selectedDateTime.add(const Duration(minutes: 30));
endTime.value = TimeOfDay.fromDateTime(newEndDateTime);
}
} else { // 正在设置结束时间
final startInMinutes = startTime.value.hour * 60 + startTime.value.minute;
final endInMinutes = pickedTempTime.hour * 60 + pickedTempTime.minute;
if (endInMinutes <= startInMinutes) {
showToast('结束时间必须晚于开始时间');
return;
}
endTime.value = pickedTempTime;
}
startTime.value = tempSlot.start;
endTime.value = tempSlot.end;
Get.back();
},
child: const Text(
'确认',
style: TextStyle(color: AppTheme.themeColor, fontWeight: FontWeight.bold),
),
child: const Text('确认', style: TextStyle(color: AppTheme.themeColor, fontWeight: FontWeight.bold)),
),
],
),
),
const Divider(height: 1, color: Color(0xFFE5E5E5)),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 小时选择器
Expanded(
child: CupertinoPicker(
scrollController: hourController,
itemExtent: 32.0,
onSelectedItemChanged: (index) {
tempHour = hours[index];
},
children: hours
.map((h) => Center(child: Text(h.toString().padLeft(2, '0'))))
.toList(),
),
),
// 分钟选择器
Expanded(
child: CupertinoPicker(
scrollController: minuteController,
itemExtent: 32.0,
onSelectedItemChanged: (index) {
tempMinute = minutes[index];
},
children: minutes
.map((m) => Center(child: Text(m.toString().padLeft(2, '0'))))
.toList(),
),
),
],
child: CupertinoPicker(
scrollController: scrollController,
itemExtent: 40.0,
onSelectedItemChanged: (index) {
tempSlot = availableSlots[index];
},
children:
availableSlots.map((slot) => Center(child: Text(slot.display))).toList(),
),
),
],