可编辑预约

This commit is contained in:
2025-12-03 10:39:34 +08:00
parent f25d7e4567
commit 0bfedd54cb
4 changed files with 429 additions and 22 deletions

View File

@@ -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/";

View File

@@ -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,14 +535,37 @@ 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(
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,
@@ -560,6 +585,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
),
),
),
),
],
),
const SizedBox(height: 12),

View File

@@ -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<DateTime> selectedDate = DateTime.now().obs;
final Rx<TimeOfDay> startTime = TimeOfDay.now().obs;
final Rx<TimeOfDay> 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<String, dynamic>;
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<TimeSlot> 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";
}

View File

@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controller.dart';
class ReservationEditPage extends GetView<ReservationEditController> {
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),
),
),
],
),
);
}
}