From 0bfedd54cb9d2bf23693c03dd56d80ee3fb3c077 Mon Sep 17 00:00:00 2001 From: userGyl Date: Wed, 3 Dec 2025 10:39:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=AF=E7=BC=96=E8=BE=91=E9=A2=84=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ln_jq_app/lib/common/styles/theme.dart | 2 +- .../pages/c_page/reservation/controller.dart | 68 +++-- .../c_page/reservation_edit/controller.dart | 254 ++++++++++++++++++ .../pages/c_page/reservation_edit/view.dart | 127 +++++++++ 4 files changed, 429 insertions(+), 22 deletions(-) create mode 100644 ln_jq_app/lib/pages/c_page/reservation_edit/controller.dart create mode 100644 ln_jq_app/lib/pages/c_page/reservation_edit/view.dart diff --git a/ln_jq_app/lib/common/styles/theme.dart b/ln_jq_app/lib/common/styles/theme.dart index e5ec909..80299a9 100644 --- a/ln_jq_app/lib/common/styles/theme.dart +++ b/ln_jq_app/lib/common/styles/theme.dart @@ -8,7 +8,7 @@ class AppTheme { static const Color themeColor = Color(0xFF0c83c3); - static const String test_service_url = "http://beta-esg.api.lnh2e.com/"; + static const String test_service_url = "https://beta-esg.api.lnh2e.com/"; static const String release_service_url = ""; //加氢站相关查询 static const String jiaqing_service_url = "https://beta.lnh2e.com/api/lingniu-manager-v1/v1/"; 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 650ff3b..a94c0ab 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/controller.dart @@ -9,6 +9,8 @@ import 'package:ln_jq_app/common/model/base_model.dart'; import 'package:ln_jq_app/common/model/station_model.dart'; 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/pages/c_page/reservation_edit/controller.dart'; +import 'package:ln_jq_app/pages/c_page/reservation_edit/view.dart'; import 'package:ln_jq_app/pages/qr_code/view.dart'; import 'package:ln_jq_app/storage_service.dart'; @@ -533,33 +535,57 @@ class C_ReservationController extends GetxController with BaseControllerMixin { ), // Blue border ), child: Text( - reservation.stateName+"-"+reservation.addStatusName, + reservation.stateName + + "-" + + reservation.addStatusName, 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( - "修改", - style: const TextStyle( - color: Color(0xFFFA8C16), - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - ), + plateNumber.isEmpty + ? SizedBox() + : GestureDetector( + onTap: () async { + var result = await Get.to( + () => ReservationEditPage(), + arguments: { + 'reservation': reservation, + 'difference': difference, + }, + binding: BindingsBuilder(() { + Get.put(ReservationEditController()); + }), + preventDuplicates: false, + ); + showToast("555$result"); + if (result == true) { + Get.back(); + getReservationList(); + } + }, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), + decoration: BoxDecoration( + color: const Color( + 0xFFFFF7E6, + ), // Light orange background + borderRadius: BorderRadius.circular(12), + ), + child: Text( + "修改", + style: const TextStyle( + color: Color(0xFFFA8C16), + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ), ], ), const SizedBox(height: 12), 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 new file mode 100644 index 0000000..b986cb7 --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/reservation_edit/controller.dart @@ -0,0 +1,254 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:intl/intl.dart'; +import 'package:ln_jq_app/common/model/base_model.dart'; +import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // For ReservationModel +import 'package:ln_jq_app/common/styles/theme.dart'; + +// Reusing the TimeSlot helper class from the reservation creation page. +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 ReservationEditController extends GetxController with BaseControllerMixin { + late final ReservationModel reservation; + late final String difference; + + // --- State Variables --- + final Rx selectedDate = DateTime.now().obs; + final Rx startTime = TimeOfDay.now().obs; + final Rx endTime = TimeOfDay.now().obs; + final TextEditingController amountController = TextEditingController(); + + // --- Getters for UI display --- + String get formattedTimeSlot => + '${_formatTimeOfDay(startTime.value)} - ${_formatTimeOfDay(endTime.value)}'; + + @override + void onInit() { + super.onInit(); + // Expect a Map containing both the reservation and the difference + final args = Get.arguments as Map; + reservation = args['reservation'] as ReservationModel; + difference = args['difference'] as String? ?? '0'; + + try { + // Initialize the UI with the data from the passed reservation object + selectedDate.value = DateFormat('yyyy-MM-dd').parse(reservation.date); + + final startDateTime = DateFormat( + 'yyyy-MM-dd HH:mm:ss', + ).parse(reservation.startTime); + startTime.value = TimeOfDay.fromDateTime(startDateTime); + + final endDateTime = DateFormat('yyyy-MM-dd HH:mm:ss').parse(reservation.endTime); + endTime.value = TimeOfDay.fromDateTime(endDateTime); + + amountController.text = reservation.hydAmount.replaceAll('kg', ''); + } catch (e) { + showErrorToast("加载预约数据时出错"); + Get.back(); // Go back if data is invalid + } + } + + @override + void onClose() { + amountController.dispose(); + super.onClose(); + } + + /// Reusable time slot picker logic, copied from the reservation creation page. + 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 < 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, + ); + + final slotStartDateTime = DateTime( + selectedDate.value.year, + selectedDate.value.month, + selectedDate.value.day, + slotStartTime.hour, + slotStartTime.minute, + ); + + if (!isToday || slotStartDateTime.isAfter(now)) { + availableSlots.add(TimeSlot(slotStartTime, slotEndTime)); + } + } + + 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; + + TimeSlot tempSlot = availableSlots[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: () { + if (Get.isBottomSheetOpen ?? false) { + Navigator.of(Get.overlayContext!).pop(); // 更加安全的关闭当前弹窗的方式 + } + }, + child: const Text( + '取消', + style: TextStyle(color: CupertinoColors.systemGrey), + ), + ), + CupertinoButton( + onPressed: () { + startTime.value = tempSlot.start; + endTime.value = tempSlot.end; + if (Get.isBottomSheetOpen ?? false) { + Navigator.of(Get.overlayContext!).pop(); // 更加安全的关闭当前弹窗的方式 + } + }, + child: const Text( + '确认', + style: TextStyle( + color: AppTheme.themeColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + const Divider(height: 1, color: Color(0xFFE5E5E5)), + Expanded( + child: CupertinoPicker( + scrollController: FixedExtentScrollController(initialItem: initialItem), + itemExtent: 40.0, + onSelectedItemChanged: (index) { + tempSlot = availableSlots[index]; + }, + children: availableSlots + .map((slot) => Center(child: Text(slot.display))) + .toList(), + ), + ), + ], + ), + ), + backgroundColor: Colors.transparent, + ); + } + + /// This function will be called when the 'Save Changes' button is pressed. + void updateReservation() async { + String amountStr = amountController.text.toString(); + if (amountStr.isEmpty) { + showToast("请输入需要预约的氢量"); + return; + } + double amountDouble = (double.tryParse(amountStr) ?? 0.0); + if (amountDouble <= 0) { + showToast("预约氢量必须大于0"); + return; + } + if (amountDouble > (double.tryParse(difference) ?? 0.0)) { + showToast('当前最大可预约氢量为$difference(KG)'); + return; + } + + showLoading("正在保存修改..."); + + final dateStr = DateFormat('yyyy-MM-dd').format(selectedDate.value); + final startTimeStr = '$dateStr ${_formatTimeOfDay(startTime.value)}:00'; + final endTimeStr = '$dateStr ${_formatTimeOfDay(endTime.value)}:00'; + + try { + var responseData = await HttpService.to.post( + 'appointment/orderAddHyd/saveOrUpdate', + data: { + 'id': reservation.id, + 'startTime': startTimeStr, + 'endTime': endTimeStr, + 'hydAmount': amountStr, + }, + ); + + if (responseData == null || responseData.data == null) { + showToast('服务暂不可用,请稍后'); + dismissLoading(); + return; + } + + var result = BaseModel.fromJson(responseData.data); + + if (result.code == 0) { + showSuccessToast("修改成功"); + //弹窗刷新数据 + Get.back(result: true); + } else { + showErrorToast(result.message); + } + } catch (e) { + showErrorToast("保存失败,请稍后再试"); + } finally { + dismissLoading(); + } + } + + String _formatTimeOfDay(TimeOfDay time) { + final hour = time.hour.toString().padLeft(2, '0'); + final minute = time.minute.toString().padLeft(2, '0'); + return '$hour:$minute'; + } + + @override + String get builderId => "reservationeditpage"; +} diff --git a/ln_jq_app/lib/pages/c_page/reservation_edit/view.dart b/ln_jq_app/lib/pages/c_page/reservation_edit/view.dart new file mode 100644 index 0000000..47332e2 --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/reservation_edit/view.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'controller.dart'; + +class ReservationEditPage extends GetView { + const ReservationEditPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GetBuilder( + init: ReservationEditController(), + id: 'reservationeditpage', + builder: (_) { + return Scaffold( + appBar: AppBar(title: const Text('修改预约'), centerTitle: true), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Obx( + () => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildPickerRow( + label: '预约时间', + value: controller.formattedTimeSlot, + icon: Icons.access_time_outlined, + onTap: () => controller.pickTime(context), + ), + _buildTextField( + label: '预约氢量(KG)', + controller: controller.amountController, + // Use Obx to make the hint text responsive if needed, though here it's static. + hint: '当前最大可预约氢量${controller.difference}(KG)', + keyboardType: TextInputType.number, + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: controller.updateReservation, + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 48), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + ), + child: const Text( + '保存修改', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + ), + ), + ), + ); + }, + ); + } + + Widget _buildPickerRow({ + required String label, + required String value, + required IconData icon, + required VoidCallback onTap, + }) { + return Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 14)), + const SizedBox(height: 8), + InkWell( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey[400]!), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(value, style: const TextStyle(fontSize: 16)), + Icon(icon, color: Colors.grey, size: 20), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildTextField({ + required String label, + required TextEditingController controller, + required String hint, + TextInputType? keyboardType, + }) { + return Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 14)), + const SizedBox(height: 8), + TextFormField( + controller: controller, + keyboardType: keyboardType, + decoration: InputDecoration( + hintText: hint, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), + contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + ), + ), + ], + ), + ); + } +}