Compare commits
8 Commits
ec1c554eb3
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 88b16ca69e | |||
| ba3467810c | |||
| 0bfedd54cb | |||
| f25d7e4567 | |||
| 4cdedff654 | |||
| 4f99ab4164 | |||
| cdc5af7f45 | |||
| 20e5e58ded |
@@ -4,6 +4,7 @@ class StationModel {
|
|||||||
final String address;
|
final String address;
|
||||||
final String price;
|
final String price;
|
||||||
final String siteStatusName; // 例如 "维修中"
|
final String siteStatusName; // 例如 "维修中"
|
||||||
|
final int isSelect; // 新增字段 1是可用 0是不可用
|
||||||
|
|
||||||
StationModel({
|
StationModel({
|
||||||
required this.hydrogenId,
|
required this.hydrogenId,
|
||||||
@@ -11,6 +12,7 @@ class StationModel {
|
|||||||
required this.address,
|
required this.address,
|
||||||
required this.price,
|
required this.price,
|
||||||
required this.siteStatusName,
|
required this.siteStatusName,
|
||||||
|
required this.isSelect,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 从 JSON map 创建对象的工厂构造函数
|
// 从 JSON map 创建对象的工厂构造函数
|
||||||
@@ -21,6 +23,7 @@ class StationModel {
|
|||||||
address: json['address'] ?? '地址未知',
|
address: json['address'] ?? '地址未知',
|
||||||
price: json['price']?.toString() ?? '0.00',
|
price: json['price']?.toString() ?? '0.00',
|
||||||
siteStatusName: json['siteStatusName'] ?? '',
|
siteStatusName: json['siteStatusName'] ?? '',
|
||||||
|
isSelect: json['isSelect'] as int? ?? 0, // 新增字段的解析,默认为 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ class AppTheme {
|
|||||||
|
|
||||||
static const Color themeColor = Color(0xFF0c83c3);
|
static const Color themeColor = Color(0xFF0c83c3);
|
||||||
|
|
||||||
|
//http://192.168.110.222:8080/
|
||||||
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 release_service_url = "";
|
||||||
|
|
||||||
//加氢站相关查询
|
//加氢站相关查询
|
||||||
static const String jiaqing_service_url = "https://beta.lnh2e.com/api/lingniu-manager-v1/v1/";
|
static const String jiaqing_service_url =
|
||||||
|
"https://beta.lnh2e.com/api/lingniu-manager-v1/v1/";
|
||||||
|
|
||||||
//车辆信息
|
//车辆信息
|
||||||
static const String car_service_url = "http://47.99.166.38:20000/";
|
static const String car_service_url = "http://47.99.166.38:20000/";
|
||||||
|
|
||||||
@@ -56,10 +59,7 @@ class AppTheme {
|
|||||||
appBarTheme: const AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
backgroundColor: Color.fromARGB(255, 34, 34, 34),
|
backgroundColor: Color.fromARGB(255, 34, 34, 34),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
titleTextStyle: TextStyle(
|
titleTextStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
bottomAppBarTheme: BottomAppBarThemeData(
|
bottomAppBarTheme: BottomAppBarThemeData(
|
||||||
color: Color.fromARGB(255, 34, 34, 34),
|
color: Color.fromARGB(255, 34, 34, 34),
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ class ReservationModel {
|
|||||||
String startTimeStr = json['startTime']?.toString() ?? '';
|
String startTimeStr = json['startTime']?.toString() ?? '';
|
||||||
String endTimeStr = json['endTime']?.toString() ?? '';
|
String endTimeStr = json['endTime']?.toString() ?? '';
|
||||||
String dateStr = json['date']?.toString() ?? '';
|
String dateStr = json['date']?.toString() ?? '';
|
||||||
String timeRange = (startTimeStr.isNotEmpty && endTimeStr.isNotEmpty && dateStr.isNotEmpty)
|
String timeRange =
|
||||||
|
(startTimeStr.isNotEmpty && endTimeStr.isNotEmpty && dateStr.isNotEmpty)
|
||||||
? '$dateStr ${startTimeStr.substring(11, 16)}-${endTimeStr.substring(11, 16)}' // 截取 HH:mm
|
? '$dateStr ${startTimeStr.substring(11, 16)}-${endTimeStr.substring(11, 16)}' // 截取 HH:mm
|
||||||
: '时间未定';
|
: '时间未定';
|
||||||
|
|
||||||
@@ -119,6 +120,8 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
List<ReservationModel> reservationList = [];
|
List<ReservationModel> reservationList = [];
|
||||||
Timer? _refreshTimer;
|
Timer? _refreshTimer;
|
||||||
|
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
@@ -130,6 +133,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
stopAutoRefresh();
|
stopAutoRefresh();
|
||||||
|
searchController.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,13 +159,17 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
Future<void> fetchReservationData() async {
|
Future<void> fetchReservationData() async {
|
||||||
showLoading("加载中");
|
showLoading("加载中");
|
||||||
|
|
||||||
|
final String searchText = searchController.text.trim();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var response = await HttpService.to.post(
|
var response = await HttpService.to.post(
|
||||||
"appointment/orderAddHyd/sitOrderPage",
|
"appointment/orderAddHyd/sitOrderPage",
|
||||||
data: {
|
data: {
|
||||||
'stationName': name, // 使用从 renderData 中获取到的 name
|
'stationName': name, // 使用从 renderData 中获取到的 name
|
||||||
'pageNum': 1,
|
'pageNum': 1,
|
||||||
'pageSize': 30, // 暂时不考虑分页,一次获取30条
|
'pageSize': 50, // 暂时不考虑分页,一次获取30条
|
||||||
|
'plateNumber': searchText, // 加氢站名称
|
||||||
|
'phone': searchText, //手机号
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -267,6 +275,8 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
String orderAmount = "";
|
String orderAmount = "";
|
||||||
String completedAmount = "";
|
String completedAmount = "";
|
||||||
String name = "";
|
String name = "";
|
||||||
|
String orderTotalAmount = "";
|
||||||
|
String orderUnfinishedAmount = "";
|
||||||
|
|
||||||
Future<void> renderData() async {
|
Future<void> renderData() async {
|
||||||
try {
|
try {
|
||||||
@@ -286,8 +296,14 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
orderAmount = result.data["orderAmount"].toString();
|
orderAmount = result.data["orderAmount"].toString();
|
||||||
completedAmount = result.data["completedAmount"].toString();
|
completedAmount = result.data["completedAmount"].toString();
|
||||||
name = result.data["name"].toString();
|
name = result.data["name"].toString();
|
||||||
|
orderTotalAmount = result.data["orderTotalAmount"] ?? "";
|
||||||
|
orderUnfinishedAmount = result.data["orderUnfinishedAmount"] ?? "";
|
||||||
|
|
||||||
leftHydrogen = leftHydrogen.isEmpty ? "统计中" : leftHydrogen.toString();
|
leftHydrogen = leftHydrogen.isEmpty ? "统计中" : leftHydrogen.toString();
|
||||||
|
orderTotalAmount = orderTotalAmount.isEmpty ? "统计中" : orderTotalAmount.toString();
|
||||||
|
orderUnfinishedAmount = orderUnfinishedAmount.isEmpty
|
||||||
|
? "统计中"
|
||||||
|
: orderUnfinishedAmount.toString();
|
||||||
|
|
||||||
//加载列表数据
|
//加载列表数据
|
||||||
fetchReservationData();
|
fetchReservationData();
|
||||||
|
|||||||
@@ -94,6 +94,17 @@ class SitePage extends GetView<SiteController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
_buildStatItem(controller.orderTotalAmount, '加氢总量'),
|
||||||
|
_buildStatItem(controller.orderUnfinishedAmount, '未加氢总量'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -150,6 +161,7 @@ class SitePage extends GetView<SiteController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
_buildSearchView(),
|
||||||
controller.hasReservationData
|
controller.hasReservationData
|
||||||
? _buildReservationListView()
|
? _buildReservationListView()
|
||||||
: _buildEmptyReservationView(),
|
: _buildEmptyReservationView(),
|
||||||
@@ -179,6 +191,66 @@ class SitePage extends GetView<SiteController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//搜索输入框,提示可以输入车牌或者手机
|
||||||
|
Widget _buildSearchView() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 44,
|
||||||
|
child: TextField(
|
||||||
|
controller: controller.searchController, // 绑定控制器
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: '输入车牌号或完整手机号查询',
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
borderSide: BorderSide(color: Get.theme.primaryColor, width: 1.5),
|
||||||
|
),
|
||||||
|
// 清除按钮
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: const Icon(Icons.clear, size: 20),
|
||||||
|
onPressed: () {
|
||||||
|
controller.searchController.clear();
|
||||||
|
controller.fetchReservationData(); // 清除后也刷新一次
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (value) {
|
||||||
|
// 用户在键盘上点击“完成”或“搜索”时触发
|
||||||
|
controller.fetchReservationData();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// 点击“搜索”按钮时触发
|
||||||
|
FocusScope.of(Get.context!).unfocus(); // 收起键盘
|
||||||
|
controller.fetchReservationData();
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.search_rounded),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 构建单个统计项
|
/// 构建单个统计项
|
||||||
Widget _buildStatItem(String value, String label, {Color valueColor = Colors.blue}) {
|
Widget _buildStatItem(String value, String label, {Color valueColor = Colors.blue}) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
|
|||||||
@@ -9,32 +9,72 @@ 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/station_model.dart';
|
||||||
import 'package:ln_jq_app/common/model/vehicle_info.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/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/pages/qr_code/view.dart';
|
||||||
import 'package:ln_jq_app/storage_service.dart';
|
import 'package:ln_jq_app/storage_service.dart';
|
||||||
|
|
||||||
import '../../../common/styles/theme.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 {
|
class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||||
@override
|
@override
|
||||||
String get builderId => 'reservation';
|
String get builderId => 'reservation';
|
||||||
|
|
||||||
final Rx<DateTime> selectedDate = DateTime.now().obs;
|
final DateTime _now = DateTime.now();
|
||||||
final Rx<TimeOfDay> startTime = TimeOfDay.now().obs;
|
|
||||||
final Rx<TimeOfDay> endTime = TimeOfDay.fromDateTime(
|
// 计算当前时间属于哪个半小时区间
|
||||||
DateTime.now().add(const Duration(minutes: 30)),
|
late final TimeOfDay _initialStartTime = _calculateInitialStartTime(_now);
|
||||||
).obs;
|
late final TimeOfDay _initialEndTime = TimeOfDay.fromDateTime(
|
||||||
|
_getDateTimeFromTimeOfDay(_initialStartTime).add(const Duration(minutes: 30)),
|
||||||
|
);
|
||||||
|
late final Rx<DateTime> selectedDate = DateTime(_now.year, _now.month, _now.day).obs;
|
||||||
|
late final Rx<TimeOfDay> startTime = _initialStartTime.obs;
|
||||||
|
late final Rx<TimeOfDay> endTime = _initialEndTime.obs;
|
||||||
|
|
||||||
|
/// 静态辅助方法,用于计算初始的开始时间
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 静态辅助方法,将TimeOfDay转换为DateTime
|
||||||
|
static DateTime _getDateTimeFromTimeOfDay(TimeOfDay time) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
return DateTime(now.year, now.month, now.day, time.hour, time.minute);
|
||||||
|
}
|
||||||
|
|
||||||
final TextEditingController amountController = TextEditingController();
|
final TextEditingController amountController = TextEditingController();
|
||||||
TextEditingController plateNumberController = TextEditingController();
|
TextEditingController plateNumberController = TextEditingController();
|
||||||
|
|
||||||
final RxList<StationModel> stationOptions = <StationModel>[].obs;
|
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);
|
String get formattedDate => DateFormat('yyyy-MM-dd').format(selectedDate.value);
|
||||||
|
|
||||||
String get formattedStartTime => _formatTimeOfDay(startTime.value);
|
/// 时间段
|
||||||
|
String get formattedTimeSlot =>
|
||||||
String get formattedEndTime => _formatTimeOfDay(endTime.value);
|
'${_formatTimeOfDay(startTime.value)} - ${_formatTimeOfDay(endTime.value)}';
|
||||||
|
|
||||||
void pickDate(BuildContext context) {
|
void pickDate(BuildContext context) {
|
||||||
DateTime tempDate = selectedDate.value;
|
DateTime tempDate = selectedDate.value;
|
||||||
@@ -84,22 +124,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: CupertinoDatePicker(
|
child: CupertinoDatePicker(
|
||||||
mode: CupertinoDatePickerMode.date,
|
mode: CupertinoDatePickerMode.date,
|
||||||
initialDateTime:
|
initialDateTime: selectedDate.value,
|
||||||
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(
|
minimumDate: DateTime(
|
||||||
DateTime.now().year,
|
DateTime.now().year,
|
||||||
DateTime.now().month,
|
DateTime.now().month,
|
||||||
@@ -118,52 +143,72 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pickTime(BuildContext context, bool isStartTime) {
|
///30 分钟为间隔 时间选择器
|
||||||
// 1. 确定当前操作的时间和初始值
|
void pickTime(BuildContext context) {
|
||||||
TimeOfDay initialTime = isStartTime ? startTime.value : endTime.value;
|
final now = DateTime.now();
|
||||||
DateTime now = DateTime.now();
|
final isToday =
|
||||||
|
selectedDate.value.year == now.year &&
|
||||||
|
selectedDate.value.month == now.month &&
|
||||||
|
selectedDate.value.day == now.day;
|
||||||
|
|
||||||
// 2. 准备小时和分钟的数据源
|
final List<TimeSlot> availableSlots = [];
|
||||||
List<int> hours = List<int>.generate(24, (index) => index);
|
for (int i = 0; i < 48; i++) {
|
||||||
List<int> minutes = [0, 30];
|
final startMinutes = i * 30;
|
||||||
|
final endMinutes = startMinutes + 30;
|
||||||
|
|
||||||
// 3. 计算初始选中的索引
|
final startTime = TimeOfDay(hour: startMinutes ~/ 60, minute: startMinutes % 60);
|
||||||
int initialHour = initialTime.hour;
|
final endTime = TimeOfDay(hour: (endMinutes ~/ 60) % 24, minute: endMinutes % 60);
|
||||||
// 将初始分钟校准到0或30,并找到对应的索引
|
|
||||||
int initialMinute = initialTime.minute;
|
|
||||||
int minuteIndex = initialMinute < 30 ? 0 : 1;
|
|
||||||
initialMinute = minutes[minuteIndex]; // 校准后的分钟
|
|
||||||
|
|
||||||
// 如果校准后导致时间早于当前时间,需要向上调整
|
final slotStartDateTime = DateTime(
|
||||||
final selectedDay = DateTime(selectedDate.value.year, selectedDate.value.month, selectedDate.value.day);
|
selectedDate.value.year,
|
||||||
final today = DateTime(now.year, now.month, now.day);
|
selectedDate.value.month,
|
||||||
if (selectedDay.isAtSameMomentAs(today)) {
|
selectedDate.value.day,
|
||||||
if (initialHour < now.hour || (initialHour == now.hour && initialMinute < now.minute)) {
|
startTime.hour,
|
||||||
initialHour = now.hour;
|
startTime.minute,
|
||||||
if (now.minute > 30) {
|
);
|
||||||
// 如果当前分钟>30, 则进位到下一小时的0分
|
|
||||||
initialHour = (now.hour + 1) % 24;
|
// 如果不是今天,所有时间段都有效
|
||||||
initialMinute = 0;
|
if (!isToday) {
|
||||||
|
availableSlots.add(TimeSlot(startTime, endTime));
|
||||||
} else {
|
} else {
|
||||||
// 否则,取30分
|
// 如果是今天,需要判断该时间段是否可选
|
||||||
initialMinute = 30;
|
// 创建时间段的结束时间对象
|
||||||
|
final slotEndDateTime = DateTime(
|
||||||
|
selectedDate.value.year,
|
||||||
|
selectedDate.value.month,
|
||||||
|
selectedDate.value.day,
|
||||||
|
endTime.hour,
|
||||||
|
endTime.minute,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 只要时间段的结束时间晚于当前时间,这个时间段就是可预约的
|
||||||
|
if (slotEndDateTime.isAfter(now)) {
|
||||||
|
availableSlots.add(TimeSlot(startTime, endTime));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新获取校准后的索引
|
if (availableSlots.isEmpty) {
|
||||||
minuteIndex = minutes.indexOf(initialMinute);
|
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 来控制滚轮的初始位置
|
TimeSlot tempSlot = availableSlots[initialItem];
|
||||||
final FixedExtentScrollController hourController =
|
|
||||||
FixedExtentScrollController(initialItem: hours.indexOf(initialHour));
|
|
||||||
final FixedExtentScrollController minuteController =
|
|
||||||
FixedExtentScrollController(initialItem: minuteIndex);
|
|
||||||
|
|
||||||
// 5. 存储临时选择的值
|
final FixedExtentScrollController scrollController = FixedExtentScrollController(
|
||||||
int tempHour = initialHour;
|
initialItem: initialItem,
|
||||||
int tempMinute = initialMinute;
|
);
|
||||||
|
|
||||||
Get.bottomSheet(
|
Get.bottomSheet(
|
||||||
Container(
|
Container(
|
||||||
@@ -184,94 +229,41 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
children: [
|
children: [
|
||||||
CupertinoButton(
|
CupertinoButton(
|
||||||
onPressed: () => Get.back(),
|
onPressed: () => Get.back(),
|
||||||
child: const Text('取消', style: TextStyle(color: CupertinoColors.systemGrey)),
|
child: const Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: CupertinoColors.systemGrey),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
CupertinoButton(
|
CupertinoButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final pickedTempTime = TimeOfDay(hour: tempHour, minute: tempMinute);
|
startTime.value = tempSlot.start;
|
||||||
final now = DateTime.now();
|
endTime.value = tempSlot.end;
|
||||||
|
|
||||||
// --- 合并和简化校验逻辑 ---
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Get.back();
|
Get.back();
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'确认',
|
'确认',
|
||||||
style: TextStyle(color: AppTheme.themeColor, fontWeight: FontWeight.bold),
|
style: TextStyle(
|
||||||
|
color: AppTheme.themeColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(height: 1, color: Color(0xFFE5E5E5)),
|
const Divider(height: 1, color: Color(0xFFE5E5E5)),
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
// 小时选择器
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CupertinoPicker(
|
child: CupertinoPicker(
|
||||||
scrollController: hourController,
|
scrollController: scrollController,
|
||||||
itemExtent: 32.0,
|
itemExtent: 40.0,
|
||||||
onSelectedItemChanged: (index) {
|
onSelectedItemChanged: (index) {
|
||||||
tempHour = hours[index];
|
tempSlot = availableSlots[index];
|
||||||
},
|
},
|
||||||
children: hours
|
children: availableSlots
|
||||||
.map((h) => Center(child: Text(h.toString().padLeft(2, '0'))))
|
.map((slot) => Center(child: Text(slot.display)))
|
||||||
.toList(),
|
.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(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -279,8 +271,6 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 用于存储上一次成功预约的信息
|
// 用于存储上一次成功预约的信息
|
||||||
ReservationModel? lastSuccessfulReservation;
|
ReservationModel? lastSuccessfulReservation;
|
||||||
|
|
||||||
@@ -311,7 +301,9 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final dateStr = formattedDate;
|
final dateStr = formattedDate;
|
||||||
final startTimeStr = '$dateStr ${formattedStartTime}:00';
|
final startTimeStr =
|
||||||
|
'$dateStr ${_formatTimeOfDay(startTime.value)}:00'; // Use helper directly
|
||||||
|
|
||||||
if (lastSuccessfulReservation != null &&
|
if (lastSuccessfulReservation != null &&
|
||||||
lastSuccessfulReservation!.id == selectedStationId.value &&
|
lastSuccessfulReservation!.id == selectedStationId.value &&
|
||||||
lastSuccessfulReservation!.startTime == startTimeStr) {
|
lastSuccessfulReservation!.startTime == startTimeStr) {
|
||||||
@@ -319,29 +311,36 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将选择的日期和时间组合成一个完整的 DateTime 对象
|
final reservationEndDateTime = DateTime(
|
||||||
final reservationStartDateTime = DateTime(
|
|
||||||
selectedDate.value.year,
|
selectedDate.value.year,
|
||||||
selectedDate.value.month,
|
selectedDate.value.month,
|
||||||
selectedDate.value.day,
|
selectedDate.value.day,
|
||||||
startTime.value.hour,
|
endTime.value.hour,
|
||||||
startTime.value.minute,
|
endTime.value.minute,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 检查预约时间是否在当前时间之前
|
//判断预约区间的结束时间是否早于当前时间(留出1分钟缓冲)
|
||||||
if (reservationStartDateTime.isBefore(DateTime.now())) {
|
if (reservationEndDateTime.isBefore(
|
||||||
showToast("不可预约过去的时间");
|
DateTime.now().subtract(const Duration(minutes: 1)),
|
||||||
|
)) {
|
||||||
|
showToast("无法预约已过去的时间段");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
showLoading("提交中");
|
|
||||||
final selectedStation = stationOptions.firstWhere(
|
final selectedStation = stationOptions.firstWhere(
|
||||||
(s) => s.hydrogenId == selectedStationId.value,
|
(s) => s.hydrogenId == selectedStationId.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
final dateStr = formattedDate; // "yyyy-MM-dd"
|
if (selectedStation.siteStatusName != "营运中") {
|
||||||
final startTimeStr = '$dateStr ${formattedStartTime}:00'; // "yyyy-MM-dd HH:mm:ss"
|
showToast("该站点${selectedStation.siteStatusName},暂无法预约");
|
||||||
final endTimeStr = '$dateStr ${formattedEndTime}:00';
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading("提交中");
|
||||||
|
|
||||||
|
final endTimeStr =
|
||||||
|
'$dateStr ${_formatTimeOfDay(endTime.value)}:00'; // Use helper directly
|
||||||
|
|
||||||
var responseData = await HttpService.to.post(
|
var responseData = await HttpService.to.post(
|
||||||
'appointment/orderAddHyd/saveOrUpdate',
|
'appointment/orderAddHyd/saveOrUpdate',
|
||||||
@@ -369,7 +368,6 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
if (result.code == 0) {
|
if (result.code == 0) {
|
||||||
showSuccessToast("预约成功");
|
showSuccessToast("预约成功");
|
||||||
|
|
||||||
// 预约成功后,保存当前预约信息
|
|
||||||
lastSuccessfulReservation = ReservationModel(
|
lastSuccessfulReservation = ReservationModel(
|
||||||
id: selectedStationId.value!,
|
id: selectedStationId.value!,
|
||||||
hydAmount: ampuntStr,
|
hydAmount: ampuntStr,
|
||||||
@@ -537,14 +535,37 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
), // Blue border
|
), // Blue border
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
reservation.stateName,
|
reservation.stateName +
|
||||||
|
"-" +
|
||||||
|
reservation.addStatusName,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Color(0xFF1890FF),
|
color: Color(0xFF1890FF),
|
||||||
fontWeight: FontWeight.bold,
|
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(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
@@ -556,11 +577,12 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
reservation.addStatusName,
|
"修改",
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Color(0xFFFA8C16),
|
color: Color(0xFFFA8C16),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 12,
|
fontSize: 14,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -258,16 +258,10 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
onTap: () => controller.pickDate(context),
|
onTap: () => controller.pickDate(context),
|
||||||
),
|
),
|
||||||
_buildPickerRow(
|
_buildPickerRow(
|
||||||
label: '开始时间',
|
label: '预约时间',
|
||||||
value: controller.formattedStartTime,
|
value: controller.formattedTimeSlot,
|
||||||
icon: Icons.access_time_outlined,
|
icon: Icons.access_time_outlined,
|
||||||
onTap: () => controller.pickTime(context, true),
|
onTap: () => controller.pickTime(context),
|
||||||
),
|
|
||||||
_buildPickerRow(
|
|
||||||
label: '结束时间',
|
|
||||||
value: controller.formattedEndTime,
|
|
||||||
icon: Icons.access_time_outlined,
|
|
||||||
onTap: () => controller.pickTime(context, false),
|
|
||||||
),
|
),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
label: '预约氢量(KG)',
|
label: '预约氢量(KG)',
|
||||||
@@ -438,6 +432,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
.map(
|
.map(
|
||||||
(station) => DropdownMenuItem<String>(
|
(station) => DropdownMenuItem<String>(
|
||||||
value: station.hydrogenId, // value 是站点的唯一ID
|
value: station.hydrogenId, // value 是站点的唯一ID
|
||||||
|
enabled: station.isSelect == 1,
|
||||||
child: _buildDropdownItem(station), // child 是自定义的 Widget
|
child: _buildDropdownItem(station), // child 是自定义的 Widget
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -484,7 +479,10 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
|
|
||||||
/// 构建下拉菜单中的每一项
|
/// 构建下拉菜单中的每一项
|
||||||
Widget _buildDropdownItem(StationModel station) {
|
Widget _buildDropdownItem(StationModel station) {
|
||||||
bool isMaintenance = (station.siteStatusName != '营运中');
|
bool isSelectable = (station.isSelect == 1);
|
||||||
|
//营运并且可用
|
||||||
|
bool isMaintenance = ((station.siteStatusName != '营运中') && isSelectable);
|
||||||
|
final textColor = isSelectable ? Colors.black : Colors.grey;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -501,14 +499,22 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
station.name,
|
station.name,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
style: TextStyle(
|
||||||
|
color: textColor,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
' | ¥${station.price}/kg',
|
' | ¥${station.price}/kg',
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
style: TextStyle(
|
||||||
|
color: textColor,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (isMaintenance) const SizedBox(width: 8),
|
if (isMaintenance) const SizedBox(width: 8),
|
||||||
if (isMaintenance)
|
if (isMaintenance)
|
||||||
|
|||||||
253
ln_jq_app/lib/pages/c_page/reservation_edit/controller.dart
Normal file
253
ln_jq_app/lib/pages/c_page/reservation_edit/controller.dart
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
127
ln_jq_app/lib/pages/c_page/reservation_edit/view.dart
Normal file
127
ln_jq_app/lib/pages/c_page/reservation_edit/view.dart
Normal 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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user