diff --git a/ln_jq_app/lib/common/styles/theme.dart b/ln_jq_app/lib/common/styles/theme.dart index 7b1fbe2..bf760ea 100644 --- a/ln_jq_app/lib/common/styles/theme.dart +++ b/ln_jq_app/lib/common/styles/theme.dart @@ -8,6 +8,7 @@ class AppTheme { static const Color themeColor = Color(0xFF0c83c3); //http://192.168.110.222:8080/ + //http://192.168.110.44:8080/ static const String test_service_url = "https://beta-esg.api.lnh2e.com/"; static const String release_service_url = ""; diff --git a/ln_jq_app/lib/pages/c_page/reservation/controller.dart b/ln_jq_app/lib/pages/c_page/reservation/controller.dart index c493200..589a36d 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/controller.dart @@ -41,10 +41,10 @@ class C_ReservationController extends GetxController with BaseControllerMixin { final DateTime _now = DateTime.now(); - // 计算当前时间属于哪个半小时区间 + // 计算当前时间属于哪个1小时区间 late final TimeOfDay _initialStartTime = _calculateInitialStartTime(_now); late final TimeOfDay _initialEndTime = TimeOfDay.fromDateTime( - _getDateTimeFromTimeOfDay(_initialStartTime).add(const Duration(minutes: 30)), + _getDateTimeFromTimeOfDay(_initialStartTime).add(const Duration(minutes: 60)), ); late final Rx selectedDate = DateTime(_now.year, _now.month, _now.day).obs; late final Rx startTime = _initialStartTime.obs; @@ -52,13 +52,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin { /// 静态辅助方法,用于计算初始的开始时间 static TimeOfDay _calculateInitialStartTime(DateTime now) { - if (now.minute < 30) { - // 如果当前分钟小于30,则开始时间为当前小时的0分 - return TimeOfDay(hour: now.hour, minute: 0); - } else { - // 如果当前分钟大于等于30,则开始时间为当前小时的30分 - return TimeOfDay(hour: now.hour, minute: 30); - } + return TimeOfDay(hour: now.hour, minute: 0); } /// 静态辅助方法,将TimeOfDay转换为DateTime @@ -186,30 +180,24 @@ class C_ReservationController extends GetxController with BaseControllerMixin { } } - ///30 分钟为间隔 时间选择器 + ///60 分钟为间隔 时间选择器 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; + selectedDate.value.month == now.month && + selectedDate.value.day == now.day; final List availableSlots = []; - for (int i = 0; i < 48; i++) { - final startMinutes = i * 30; - final endMinutes = startMinutes + 30; + for (int i = 0; i < 24; i++) { + // 每次增加 60 分钟 + final startMinutes = i * 60; + final endMinutes = startMinutes + 60; final startTime = TimeOfDay(hour: startMinutes ~/ 60, minute: startMinutes % 60); + // 注意:endMinutes % 60 始终为 0,因为间隔是整小时 final endTime = TimeOfDay(hour: (endMinutes ~/ 60) % 24, minute: endMinutes % 60); - final slotStartDateTime = DateTime( - selectedDate.value.year, - selectedDate.value.month, - selectedDate.value.day, - startTime.hour, - startTime.minute, - ); - // 如果不是今天,所有时间段都有效 if (!isToday) { availableSlots.add(TimeSlot(startTime, endTime)); @@ -224,8 +212,16 @@ class C_ReservationController extends GetxController with BaseControllerMixin { endTime.minute, ); + // 注意:如果是跨天的 00:00 (例如 23:00 - 00:00),需要将日期加一天,否则 isAfter 判断会出错 + // 但由于我们用的是 endTime.hour % 24,当变成 0 时,日期还是 selectedDate + // 这里做一个特殊处理:如果 endTime 是 00:00,意味着它实际上是明天的开始 + DateTime realEndDateTime = slotEndDateTime; + if (endTime.hour == 0 && endTime.minute == 0) { + realEndDateTime = slotEndDateTime.add(const Duration(days: 1)); + } + // 只要时间段的结束时间晚于当前时间,这个时间段就是可预约的 - if (slotEndDateTime.isAfter(now)) { + if (realEndDateTime.isAfter(now)) { availableSlots.add(TimeSlot(startTime, endTime)); } } @@ -236,13 +232,11 @@ class C_ReservationController extends GetxController with BaseControllerMixin { return; } + // 查找当前选中的时间对应的新列表中的索引 int initialItem = availableSlots.indexWhere( - (slot) => - slot.start.hour == startTime.value.hour && - (startTime.value.minute < 30 - ? slot.start.minute == 0 - : slot.start.minute == 30), + (slot) => slot.start.hour == startTime.value.hour, ); + if (initialItem == -1) { initialItem = 0; } diff --git a/ln_jq_app/lib/pages/c_page/reservation/view.dart b/ln_jq_app/lib/pages/c_page/reservation/view.dart index 9179e88..53f6f8e 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/view.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/view.dart @@ -13,15 +13,12 @@ import 'reservation_list_bottomsheet.dart'; class ReservationPage extends GetView { ReservationPage({super.key}); - - @override Widget build(BuildContext context) { return GetBuilder( init: C_ReservationController(), id: 'reservation', builder: (_) { - return Scaffold( backgroundColor: Colors.grey[100], body: GestureDetector( @@ -273,12 +270,12 @@ class ReservationPage extends GetView { hint: '当前最大可预约氢量${controller.difference}(KG)', keyboardType: TextInputType.number, ), - _buildTextField( + /*_buildTextField( label: '车牌号', controller: controller.plateNumberController, hint: '请输入车牌号', // 修改提示文案 enabled: false, // 设置为不可编辑 - ), + ),*/ _buildStationSelector(), const SizedBox(height: 20), Row( @@ -332,8 +329,6 @@ class ReservationPage extends GetView { ); } - - // 表单中的可点击行 (用于日期和时间选择) Widget _buildPickerRow({ required String label, @@ -378,6 +373,7 @@ class ReservationPage extends GetView { TextInputType? keyboardType, bool enabled = true, }) { + bool showCounter = keyboardType == TextInputType.number; return Padding( padding: const EdgeInsets.only(bottom: 12.0), child: Column( @@ -401,6 +397,25 @@ class ReservationPage extends GetView { contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), filled: !enabled, fillColor: Colors.grey[100], + // 左侧减号按钮 + prefixIcon: showCounter + ? IconButton( + icon: const Icon(Icons.remove, color: Colors.blue), + onPressed: () => _updateAmount(-1), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 40, minHeight: 40), + ) + : null, + + // 右侧加号按钮 + suffixIcon: showCounter + ? IconButton( + icon: const Icon(Icons.add, color: Colors.blue), + onPressed: () => _updateAmount(1), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 40, minHeight: 40), + ) + : null, ), ), ], @@ -408,6 +423,37 @@ class ReservationPage extends GetView { ); } + // :更新氢量逻辑 + void _updateAmount(int change) { + // 获取当前输入框的值,默认为 0 + double currentAmount = double.tryParse(controller.amountController.text) ?? 0; + + // 获取最大值,注意处理 difference 可能为空或非数字的情况 + double maxAmount = double.tryParse(controller.difference.toString()) ?? 9999; + + // 计算新值 + double newAmount = currentAmount + change; + + // 边界检查 + if (newAmount < 1) { + newAmount = 1; // 最小不能小于 1 + } else if (newAmount > maxAmount) { + newAmount = maxAmount; // 最大不能超过 difference + } + + // 如果是整数,去掉小数点显示 + if (newAmount == newAmount.toInt()) { + controller.amountController.text = newAmount.toInt().toString(); + } else { + controller.amountController.text = newAmount.toStringAsFixed(2); + } + + // 移动光标到末尾,防止光标跳到前面 + controller.amountController.selection = TextSelection.fromPosition( + TextPosition(offset: controller.amountController.text.length), + ); + } + Widget _buildStationSelector() { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -428,7 +474,7 @@ class ReservationPage extends GetView { ), ), Obx( - () => DropdownButtonHideUnderline( + () => DropdownButtonHideUnderline( child: DropdownButton2( isExpanded: true, hint: const Text( @@ -439,15 +485,15 @@ class ReservationPage extends GetView { items: controller.stationOptions .map( (station) => DropdownMenuItem( - value: station.hydrogenId, // value 是站点的唯一ID - enabled: station.isSelect == 1, - child: _buildDropdownItem(station), // child 是自定义的 Widget - ), - ) + value: station.hydrogenId, // value 是站点的唯一ID + enabled: station.isSelect == 1, + child: _buildDropdownItem(station), // child 是自定义的 Widget + ), + ) .toList(), value: - // 当前的站点 处理默认 - controller.selectedStationId.value ?? + // 当前的站点 处理默认 + controller.selectedStationId.value ?? (controller.stationOptions.isNotEmpty ? controller.stationOptions.first.hydrogenId : null), @@ -460,15 +506,15 @@ class ReservationPage extends GetView { customButton: Obx(() { // 优先从已选中的 ID 查找 var selectedStation = controller.stationOptions.firstWhereOrNull( - (s) => s.hydrogenId == controller.selectedStationId.value, + (s) => s.hydrogenId == controller.selectedStationId.value, ); // 如果找不到已选中的(比如 ID 为空或列表里没有),并且列表不为空,则取第一个作为默认 final stationToShow = selectedStation ?? - (controller.stationOptions.isNotEmpty - ? controller.stationOptions.first - : null); + (controller.stationOptions.isNotEmpty + ? controller.stationOptions.first + : null); // 如果有要显示的站点,就构建按钮 if (stationToShow != null) { diff --git a/ln_jq_app/lib/pages/c_page/reservation_edit/controller.dart b/ln_jq_app/lib/pages/c_page/reservation_edit/controller.dart index 1451b7a..584edf7 100644 --- a/ln_jq_app/lib/pages/c_page/reservation_edit/controller.dart +++ b/ln_jq_app/lib/pages/c_page/reservation_edit/controller.dart @@ -74,32 +74,45 @@ class ReservationEditController extends GetxController with BaseControllerMixin final now = DateTime.now(); final isToday = selectedDate.value.year == now.year && - selectedDate.value.month == now.month && - selectedDate.value.day == now.day; + selectedDate.value.month == now.month && + selectedDate.value.day == now.day; final List availableSlots = []; - for (int i = 0; i < 48; i++) { - final startMinutes = i * 30; - final endMinutes = startMinutes + 30; - final slotStartTime = TimeOfDay( - hour: startMinutes ~/ 60, - minute: startMinutes % 60, - ); - final slotEndTime = TimeOfDay( - hour: (endMinutes ~/ 60) % 24, - minute: endMinutes % 60, - ); + for (int i = 0; i < 24; i++) { + // 每次增加 60 分钟 + final startMinutes = i * 60; + final endMinutes = startMinutes + 60; - final slotStartDateTime = DateTime( - selectedDate.value.year, - selectedDate.value.month, - selectedDate.value.day, - slotStartTime.hour, - slotStartTime.minute, - ); + final startTime = TimeOfDay(hour: startMinutes ~/ 60, minute: startMinutes % 60); + // 注意:endMinutes % 60 始终为 0,因为间隔是整小时 + final endTime = TimeOfDay(hour: (endMinutes ~/ 60) % 24, minute: endMinutes % 60); - if (!isToday || slotStartDateTime.isAfter(now)) { - availableSlots.add(TimeSlot(slotStartTime, slotEndTime)); + // 如果不是今天,所有时间段都有效 + if (!isToday) { + availableSlots.add(TimeSlot(startTime, endTime)); + } else { + // 如果是今天,需要判断该时间段是否可选 + // 创建时间段的结束时间对象 + final slotEndDateTime = DateTime( + selectedDate.value.year, + selectedDate.value.month, + selectedDate.value.day, + endTime.hour, + endTime.minute, + ); + + // 注意:如果是跨天的 00:00 (例如 23:00 - 00:00),需要将日期加一天,否则 isAfter 判断会出错 + // 但由于我们用的是 endTime.hour % 24,当变成 0 时,日期还是 selectedDate + // 这里做一个特殊处理:如果 endTime 是 00:00,意味着它实际上是明天的开始 + DateTime realEndDateTime = slotEndDateTime; + if (endTime.hour == 0 && endTime.minute == 0) { + realEndDateTime = slotEndDateTime.add(const Duration(days: 1)); + } + + // 只要时间段的结束时间晚于当前时间,这个时间段就是可预约的 + if (realEndDateTime.isAfter(now)) { + availableSlots.add(TimeSlot(startTime, endTime)); + } } } @@ -108,17 +121,18 @@ class ReservationEditController extends GetxController with BaseControllerMixin return; } + // 查找当前选中的时间对应的新列表中的索引 int initialItem = availableSlots.indexWhere( - (slot) => - slot.start.hour == startTime.value.hour && - (startTime.value.minute < 30 - ? slot.start.minute == 0 - : slot.start.minute == 30), + (slot) => slot.start.hour == startTime.value.hour, ); - if (initialItem == -1) initialItem = 0; + + if (initialItem == -1) { + initialItem = 0; + } TimeSlot tempSlot = availableSlots[initialItem]; + Get.bottomSheet( Container( height: 300,