diff --git a/ln_jq_app/assets/images/welcome.png b/ln_jq_app/assets/images/welcome.png index 3a2d95e..3ea8db5 100644 Binary files a/ln_jq_app/assets/images/welcome.png and b/ln_jq_app/assets/images/welcome.png differ diff --git a/ln_jq_app/lib/common/model/station_model.dart b/ln_jq_app/lib/common/model/station_model.dart index 1292f07..8291298 100644 --- a/ln_jq_app/lib/common/model/station_model.dart +++ b/ln_jq_app/lib/common/model/station_model.dart @@ -4,7 +4,9 @@ class StationModel { final String address; final String price; final String siteStatusName; // 例如 "维修中" - final int isSelect; // 新增字段 1是可用 0是不可用 + final int isSelect; // 1是可用 0是不可用 + final String startBusiness; // 新增:可预约最早开始时间,如 "06:00:00" + final String endBusiness; // 新增:可预约最晚结束时间,如 "22:00:00" StationModel({ required this.hydrogenId, @@ -13,9 +15,10 @@ class StationModel { required this.price, required this.siteStatusName, required this.isSelect, + required this.startBusiness, + required this.endBusiness, }); - // 从 JSON map 创建对象的工厂构造函数 factory StationModel.fromJson(Map json) { return StationModel( hydrogenId: json['hydrogenId'] ?? '', @@ -23,7 +26,9 @@ class StationModel { address: json['address'] ?? '地址未知', price: json['price']?.toString() ?? '0.00', siteStatusName: json['siteStatusName'] ?? '', - isSelect: json['isSelect'] as int? ?? 0, // 新增字段的解析,默认为 0 + isSelect: json['isSelect'] as int? ?? 0, + startBusiness: json['startBusiness'] ?? '00:00:00', // 默认全天 + endBusiness: json['endBusiness'] ?? '23:59:59', // 默认全天 ); } } 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 ffad3af..1813f51 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/controller.dart @@ -69,239 +69,34 @@ class C_ReservationController extends GetxController with BaseControllerMixin { String get formattedTimeSlot => '${_formatTimeOfDay(startTime.value)} - ${_formatTimeOfDay(endTime.value)}'; - void pickDate(BuildContext context) { - DateTime tempDate = selectedDate.value; - - // 获取今天的日期 (不含时间) - final DateTime today = DateTime( - DateTime.now().year, - DateTime.now().month, - DateTime.now().day, - ); - - //计算明天的日期 - final DateTime tomorrow = today.add(const Duration(days: 1)); - - 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 bool isChangingToToday = - tempDate.isAtSameMomentAs(today) && - !selectedDate.value.isAtSameMomentAs(today); - final bool isDateChanged = !tempDate.isAtSameMomentAs( - selectedDate.value, - ); - - // 更新选中的日期 - selectedDate.value = tempDate; - Get.back(); // 先关闭弹窗 - - // 如果日期发生了变化,则重置时间 - if (isDateChanged) { - resetTimeForSelectedDate(); - } - }, - 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, - minimumDate: today, - // 最小可选日期为今天 - maximumDate: tomorrow, - // 最大可选日期为明天 - // --------------------- - onDateTimeChanged: (DateTime newDate) { - tempDate = newDate; - }, - ), - ), - ], - ), - ), - backgroundColor: Colors.transparent, - ); - } - void resetTimeForSelectedDate() { final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); - // 判断新选择的日期是不是今天 + // 1. 获取当前站点 + final station = stationOptions.firstWhereOrNull( + (s) => s.hydrogenId == selectedStationId.value, + ); + if (station == null) return; + + // 2. 解析营业开始和结束的小时 + final bizStartHour = int.tryParse(station.startBusiness.split(':')[0]) ?? 0; + final bizEndHour = int.tryParse(station.endBusiness.split(':')[0]) ?? 23; + if (selectedDate.value.isAtSameMomentAs(today)) { - // 如果是今天,就将时间重置为当前时间所在的半小时区间 - startTime.value = _calculateInitialStartTime(now); - endTime.value = TimeOfDay.fromDateTime( - _getDateTimeFromTimeOfDay(startTime.value).add(const Duration(minutes: 30)), - ); + // 如果是今天:起始时间 = max(当前小时, 营业开始小时),且上限为营业结束小时 + int targetHour = now.hour; + if (targetHour < bizStartHour) targetHour = bizStartHour; + if (targetHour > bizEndHour) targetHour = bizEndHour; + + startTime.value = TimeOfDay(hour: targetHour, minute: 0); } else { - // 如果是明天(或其他未来日期),则可以将时间重置为一天的最早可用时间,例如 00:00 - startTime.value = const TimeOfDay(hour: 0, minute: 0); - endTime.value = const TimeOfDay(hour: 0, minute: 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; - - final List availableSlots = []; - 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); - - // 如果不是今天,所有时间段都有效 - 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)); - } - } + // 如果是明天:起始时间直接重置为营业开始小时 + startTime.value = TimeOfDay(hour: bizStartHour, minute: 0); } - if (availableSlots.isEmpty) { - showToast('今天已没有可预约的时间段'); - return; - } - - // 查找当前选中的时间对应的新列表中的索引 - int initialItem = availableSlots.indexWhere( - (slot) => slot.start.hour == startTime.value.hour, - ); - - if (initialItem == -1) { - initialItem = 0; - } - - TimeSlot tempSlot = availableSlots[initialItem]; - - final FixedExtentScrollController scrollController = FixedExtentScrollController( - initialItem: initialItem, - ); - - Get.bottomSheet( - Container( - height: 300, - 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: () { - startTime.value = tempSlot.start; - endTime.value = tempSlot.end; - Get.back(); - }, - child: const Text( - '确认', - style: TextStyle( - color: AppTheme.themeColor, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - const Divider(height: 1, color: Color(0xFFE5E5E5)), - Expanded( - child: CupertinoPicker( - scrollController: scrollController, - itemExtent: 40.0, - onSelectedItemChanged: (index) { - tempSlot = availableSlots[index]; - }, - children: availableSlots - .map((slot) => Center(child: Text(slot.display))) - .toList(), - ), - ), - ], - ), - ), - backgroundColor: Colors.transparent, - ); + // 结束时间默认顺延1小时 + endTime.value = TimeOfDay(hour: (startTime.value.hour + 1) % 24, minute: 0); } // 用于存储上一次成功预约的信息 @@ -727,7 +522,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin { } void getSiteList() async { - if (StorageService.to.phone == "13344444444") { + if (StorageService.to.phone == "13888888888") { //该账号给stationOptions手动添加一个数据 final testStation = StationModel( hydrogenId: '1142167389150920704', @@ -737,7 +532,9 @@ class C_ReservationController extends GetxController with BaseControllerMixin { // 价格 siteStatusName: '营运中', // 状态 - isSelect: 1, // 默认可选 + isSelect: 1, + startBusiness: '08:00:00', + endBusiness: '20:00:00', // 默认可选 ); // 使用 assignAll 可以确保列表只包含这个测试数据 stationOptions.assignAll([testStation]); 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 8695ba4..bd67abc 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/view.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/view.dart @@ -457,8 +457,27 @@ class ReservationPage extends GetView { /// 时间 Slider 选择器 Widget _buildTimeSlider(BuildContext context) { return Obx(() { - // 这里的逻辑对应 Controller 中的 24 小时可用 Slot - int currentIdx = controller.startTime.value.hour; + // 获取当前小时作为滑块值 (0-23) + int currentHour = controller.startTime.value.hour; + + // 动态获取站点的营业范围限制 + final station = controller.stationOptions.firstWhereOrNull( + (s) => s.hydrogenId == controller.selectedStationId.value, + ); + // 解析营业时间 + // 处理格式如 "09:00" 或 "09:00:00" + final startParts = (station?.startBusiness ?? "00:00").split(':'); + final endParts = (station?.endBusiness ?? "23:59").split(':'); + + int bizStartHour = int.tryParse(startParts[0]) ?? 0; + int bizEndHour = int.tryParse(endParts[0]) ?? 23; + int bizEndMinute = (endParts.length > 1) ? (int.tryParse(endParts[1]) ?? 0) : 0; + + // 优化结束小时逻辑 + // 如果分钟为 0 (例如 18:00),说明该小时整点已关门,最大可选小时应减 1 + if (bizEndMinute == 0 && bizEndHour > 0) { + bizEndHour--; + } return Column( children: [ @@ -507,23 +526,23 @@ class ReservationPage extends GetView { overlayColor: const Color(0xFF006633).withOpacity(0.1), ), child: Slider( - value: currentIdx.toDouble(), - min: 0, - max: 23, - divisions: 23, + value: currentHour.toDouble().clamp( + bizStartHour.toDouble(), + bizEndHour.toDouble(), + ), + min: bizStartHour.toDouble(), + max: bizEndHour.toDouble(), + // divisions: bizEndHour - bizStartHour > 0 ? bizEndHour - bizStartHour : 1, onChanged: (val) { int hour = val.toInt(); - // 模拟 Controller 中的 pickTime 逻辑校验 final now = DateTime.now(); final isToday = controller.selectedDate.value.year == now.year && controller.selectedDate.value.month == now.month && controller.selectedDate.value.day == now.day; - if (isToday && hour < now.hour) { - // 如果是今天且小时数小于当前,则忽略 - return; - } + // 如果是今天,判断不可选过去的时间点 + if (isToday && hour < now.hour) return; controller.startTime.value = TimeOfDay(hour: hour, minute: 0); controller.endTime.value = TimeOfDay(hour: (hour + 1) % 24, minute: 0); @@ -650,6 +669,7 @@ class ReservationPage extends GetView { onChanged: (value) { if (value != null) { controller.selectedStationId.value = value; + controller.resetTimeForSelectedDate(); } }, diff --git a/ln_jq_app/lib/pages/welcome/view.dart b/ln_jq_app/lib/pages/welcome/view.dart index 3f67a90..9e11f12 100644 --- a/ln_jq_app/lib/pages/welcome/view.dart +++ b/ln_jq_app/lib/pages/welcome/view.dart @@ -23,7 +23,7 @@ class WelcomePage extends GetView { right: 0, child: Image.asset( 'assets/images/welcome.png', - fit: BoxFit.fill + fit: BoxFit.cover ), ), ],