Merge branch 'dev'
司机端-加氢预约 预约时间段修改 1、开始结束时间整合成一个 2、时间只开放当日和次日 司机端-加氢预约 提交预约不可提交限制 1、非营业站点 2、预约时间不可重复 司机端-预约列表 1、新增到站时间、氢量修改 2、显示拒绝加氢原因 加氢站-加氢预约 1、新增当日预约加车牌/手机号筛选 2、新增加氢总量 未加氢量 3、 确认拒绝流程优化:完成可填写具体加氢量,新增拒绝原因 # Conflicts: # ln_jq_app/lib/common/styles/theme.dart
This commit is contained in:
BIN
ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.jpg
Normal file
BIN
ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 74 KiB |
@@ -2,16 +2,10 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key></key>
|
||||||
<string>需要您的位置信息以在地图上展示</string>
|
<string></string>
|
||||||
<!-- 建议添加:即使你只申请WhenInUse,有些插件逻辑可能会检查这个key -->
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
<true/>
|
||||||
<string>我们需要您的位置来规划路线</string>
|
|
||||||
<!-- 建议添加:旧版本iOS兼容 -->
|
|
||||||
<key>NSLocationAlwaysUsageDescription</key>
|
|
||||||
<string>我们需要您的位置来规划路线</string>
|
|
||||||
|
|
||||||
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
@@ -32,8 +26,24 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>需要访问您的相机以扫描二维码</string>
|
||||||
|
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||||
|
<string>我们需要您的位置来规划路线</string>
|
||||||
|
<key>NSLocationAlwaysUsageDescription</key>
|
||||||
|
<string>我们需要您的位置来规划路线</string>
|
||||||
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
|
<string>需要您的位置信息以在地图上展示</string>
|
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
<string>需要访问您的相册以选择二维码图片进行识别</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>需要访问您的相册以选择二维码图片进行识别</string>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@@ -51,17 +61,7 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>uses</key>
|
||||||
<key>NSCameraUsageDescription</key>
|
<string></string>
|
||||||
<string>需要访问您的相机以扫描二维码</string>
|
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
|
||||||
<string>需要访问您的相册以选择二维码图片进行识别</string>
|
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
|
||||||
<string>需要访问您的相册以选择二维码图片进行识别</string>
|
|
||||||
|
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ void initHttpSet() {
|
|||||||
await StorageService.to.clearLoginInfo();
|
await StorageService.to.clearLoginInfo();
|
||||||
Get.offAll(() => LoginPage());
|
Get.offAll(() => LoginPage());
|
||||||
return baseModel.message;
|
return baseModel.message;
|
||||||
} else {
|
|
||||||
return baseModel.message;
|
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
e.printInfo();
|
e.printInfo();
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||||
|
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||||
import 'package:ln_jq_app/storage_service.dart';
|
import 'package:ln_jq_app/storage_service.dart';
|
||||||
|
|
||||||
enum ReservationStatus {
|
enum ReservationStatus {
|
||||||
pending, // 待处理 ( addStatus: 1)
|
pending, // 待处理 ( addStatus: 0)
|
||||||
completed, // 已完成 ( addStatus: 2)
|
completed, // 完成 ( addStatus: 1)
|
||||||
rejected, // 已拒绝 ( 3)
|
rejected, // 拒绝 ( -1)
|
||||||
|
unadded, // 未加 ( 2)
|
||||||
unknown, // 未知状态
|
unknown, // 未知状态
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReservationModel {
|
class ReservationModel {
|
||||||
final String id;
|
final String id;
|
||||||
final String plateNumber;
|
final String plateNumber;
|
||||||
final String amount;
|
String amount;
|
||||||
final String time;
|
final String time;
|
||||||
final String contactPerson;
|
final String contactPerson;
|
||||||
final String contactPhone;
|
final String contactPhone;
|
||||||
@@ -22,6 +25,7 @@ class ReservationModel {
|
|||||||
|
|
||||||
final String contacts;
|
final String contacts;
|
||||||
final String phone;
|
final String phone;
|
||||||
|
final String rejectReason;
|
||||||
final String stationName;
|
final String stationName;
|
||||||
final String startTime;
|
final String startTime;
|
||||||
final String endTime;
|
final String endTime;
|
||||||
@@ -31,6 +35,7 @@ class ReservationModel {
|
|||||||
final String stateName;
|
final String stateName;
|
||||||
final String addStatus;
|
final String addStatus;
|
||||||
final String addStatusName;
|
final String addStatusName;
|
||||||
|
bool hasEdit;
|
||||||
|
|
||||||
ReservationModel({
|
ReservationModel({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -39,6 +44,7 @@ class ReservationModel {
|
|||||||
required this.time,
|
required this.time,
|
||||||
required this.contactPerson,
|
required this.contactPerson,
|
||||||
required this.contactPhone,
|
required this.contactPhone,
|
||||||
|
required this.hasEdit,
|
||||||
this.status = ReservationStatus.pending,
|
this.status = ReservationStatus.pending,
|
||||||
required this.contacts,
|
required this.contacts,
|
||||||
required this.phone,
|
required this.phone,
|
||||||
@@ -51,32 +57,39 @@ class ReservationModel {
|
|||||||
required this.stateName,
|
required this.stateName,
|
||||||
required this.addStatus,
|
required this.addStatus,
|
||||||
required this.addStatusName,
|
required this.addStatusName,
|
||||||
|
required this.rejectReason,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 工厂构造函数,用于从JSON创建ReservationModel实例
|
/// 工厂构造函数,用于从JSON创建ReservationModel实例
|
||||||
factory ReservationModel.fromJson(Map<String, dynamic> json) {
|
factory ReservationModel.fromJson(Map<String, dynamic> json) {
|
||||||
//1完成 0待处理 2已拒绝
|
// 1完成 2未加 -1拒绝 0是待加氢
|
||||||
ReservationStatus currentStatus;
|
ReservationStatus currentStatus;
|
||||||
int statusFromServer = json['addStatus'] as int? ?? 0;
|
int statusFromServer = json['addStatus'] as int? ?? 0;
|
||||||
switch (statusFromServer) {
|
int state = json['state'] as int? ?? 0;
|
||||||
case 0:
|
if (state == -1) {
|
||||||
currentStatus = ReservationStatus.pending;
|
currentStatus = ReservationStatus.rejected;
|
||||||
break;
|
} else {
|
||||||
case 1:
|
switch (statusFromServer) {
|
||||||
currentStatus = ReservationStatus.completed;
|
case 0:
|
||||||
break;
|
currentStatus = ReservationStatus.pending;
|
||||||
case 2:
|
break;
|
||||||
currentStatus = ReservationStatus.rejected;
|
case 1:
|
||||||
break;
|
currentStatus = ReservationStatus.completed;
|
||||||
default:
|
break;
|
||||||
currentStatus = ReservationStatus.unknown;
|
case 2:
|
||||||
|
currentStatus = ReservationStatus.unadded;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
currentStatus = ReservationStatus.unknown;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化时间显示
|
// 格式化时间显示
|
||||||
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
|
||||||
: '时间未定';
|
: '时间未定';
|
||||||
|
|
||||||
@@ -102,6 +115,8 @@ class ReservationModel {
|
|||||||
addStatus: statusFromServer.toString(),
|
addStatus: statusFromServer.toString(),
|
||||||
addStatusName: json['addStatusName']?.toString() ?? '',
|
addStatusName: json['addStatusName']?.toString() ?? '',
|
||||||
stateName: json['stateName']?.toString() ?? '',
|
stateName: json['stateName']?.toString() ?? '',
|
||||||
|
rejectReason: json['rejectReason']?.toString() ?? '',
|
||||||
|
hasEdit: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,6 +134,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 +147,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
stopAutoRefresh();
|
stopAutoRefresh();
|
||||||
|
searchController.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +157,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
// 创建一个每5分钟执行一次的周期性定时器
|
// 创建一个每5分钟执行一次的周期性定时器
|
||||||
_refreshTimer = Timer.periodic(const Duration(minutes: 5), (timer) {
|
_refreshTimer = Timer.periodic(const Duration(minutes: 5), (timer) {
|
||||||
fetchReservationData();
|
renderData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,13 +173,16 @@ 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条
|
||||||
|
'keyword': searchText, // 加氢站名称、手机号
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -179,7 +200,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
// 【核心修改】处理接口返回的列表数据
|
// 【核心修改】处理接口返回的列表数据
|
||||||
final dataMap = baseModel.data as Map<String, dynamic>;
|
final dataMap = baseModel.data as Map<String, dynamic>;
|
||||||
|
|
||||||
final List<dynamic> listFromServer = dataMap['list'] ?? [];
|
final List<dynamic> listFromServer = dataMap['records'] ?? [];
|
||||||
|
|
||||||
// 使用 .map() 遍历列表,将每个 item 转换为一个 ReservationModel 对象
|
// 使用 .map() 遍历列表,将每个 item 转换为一个 ReservationModel 对象
|
||||||
reservationList = listFromServer.map((item) {
|
reservationList = listFromServer.map((item) {
|
||||||
@@ -208,38 +229,289 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
/// 确认预约
|
/// 确认预约
|
||||||
Future<void> confirmReservation(String id) async {
|
Future<void> confirmReservation(String id) async {
|
||||||
final item = reservationList.firstWhere((item) => item.id == id);
|
final item = reservationList.firstWhere(
|
||||||
DialogX.to.showConfirmDialog(
|
(item) => item.id == id,
|
||||||
title: '确认预约',
|
orElse: () => throw Exception('Reservation not found'),
|
||||||
message: '确定要确认车牌号${item.plateNumber}的预约吗',
|
);
|
||||||
onConfirm: () {
|
final TextEditingController amountController = TextEditingController(
|
||||||
upDataService(id, 1, item);
|
text: item.hydAmount,
|
||||||
},
|
);
|
||||||
onCancel: () {},
|
Get.dialog(
|
||||||
|
Dialog(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), // 圆角
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min, // 高度自适应
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'确认加氢状态',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
// content 部分
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'车牌号 ${item.plateNumber}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: AppTheme.themeColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'加氢量',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
SizedBox(
|
||||||
|
width: 100,
|
||||||
|
child: TextField(
|
||||||
|
controller: amountController,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
keyboardType: const TextInputType.numberWithOptions(
|
||||||
|
decimal: true,
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Get.theme.primaryColor,
|
||||||
|
),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
suffixText: 'kg',
|
||||||
|
suffixStyle: TextStyle(fontSize: 16, color: Colors.grey),
|
||||||
|
enabledBorder: UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Colors.grey),
|
||||||
|
),
|
||||||
|
focusedBorder: UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Colors.grey, width: 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
const Text(
|
||||||
|
'请选择本次加氢的实际状态\n用于更新预约记录。',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
// actions 部分 (按钮)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.back(); // 关闭弹窗
|
||||||
|
final num addHydAmount = num.tryParse(amountController.text) ?? 0;
|
||||||
|
upDataService(id, 0, 1, addHydAmount, "", item);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
minimumSize: const Size(double.infinity, 48),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('加氢完成', style: TextStyle(fontSize: 16)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.back(); // 关闭弹窗
|
||||||
|
upDataService(id, 0, 2, 0, "", item);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
minimumSize: const Size(double.infinity, 48),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('未加氢', style: TextStyle(fontSize: 16)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(), // 只关闭弹窗
|
||||||
|
child: const Text(
|
||||||
|
'暂不处理',
|
||||||
|
style: TextStyle(color: Colors.grey, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
barrierDismissible: false, // 点击外部不关闭弹窗
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 拒绝预约
|
|
||||||
Future<void> rejectReservation(String id) async {
|
Future<void> rejectReservation(String id) async {
|
||||||
final item = reservationList.firstWhere((item) => item.id == id);
|
final item = reservationList.firstWhere((item) => item.id == id);
|
||||||
DialogX.to.showConfirmDialog(
|
final TextEditingController reasonController = TextEditingController();
|
||||||
title: '拒绝预约',
|
final RxString selectedReason = ''.obs;
|
||||||
message: '确定要拒绝车牌号${item.plateNumber}的预约吗',
|
|
||||||
onConfirm: () {
|
// 预设的拒绝原因列表
|
||||||
upDataService(id, 2, item);
|
final List<String> presetReasons = ['车辆未到场', '司机联系不上', '设备故障无法加氢'];
|
||||||
},
|
|
||||||
onCancel: () {},
|
Get.dialog(
|
||||||
|
Dialog(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'拒绝预约',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'正在处理车牌号 ${item.plateNumber} 的预约',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 16, color: AppTheme.themeColor),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 4. 预设原因选择区域
|
||||||
|
const Text('选择或填写拒绝原因:', style: TextStyle(color: Colors.grey)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Obx(
|
||||||
|
() => Wrap(
|
||||||
|
// 使用 Wrap 自动换行
|
||||||
|
spacing: 8.0, // 水平间距
|
||||||
|
children: presetReasons.map((reason) {
|
||||||
|
final isSelected = selectedReason.value == reason;
|
||||||
|
return ChoiceChip(
|
||||||
|
label: Text(reason),
|
||||||
|
selected: isSelected,
|
||||||
|
onSelected: (selected) {
|
||||||
|
if (selected) {
|
||||||
|
selectedReason.value = reason;
|
||||||
|
reasonController.clear(); // 选择预设原因时,清空自定义输入
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedColor: Get.theme.primaryColor.withOpacity(0.2),
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
color: isSelected ? Get.theme.primaryColor : Colors.black,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// 5. 自定义原因输入框
|
||||||
|
TextField(
|
||||||
|
controller: reasonController,
|
||||||
|
maxLines: 2,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: '输入其它原因',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (text) {
|
||||||
|
if (text.isNotEmpty) {
|
||||||
|
selectedReason.value = ''; // 输入自定义原因时,取消预设选择
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 6. 按钮区域
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
// 获取最终的拒绝原因
|
||||||
|
String finalReason = reasonController.text.trim();
|
||||||
|
if (finalReason.isEmpty) {
|
||||||
|
finalReason = selectedReason.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalReason.isEmpty) {
|
||||||
|
showToast('请选择或填写一个拒绝原因');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Get.back(); // 关闭弹窗
|
||||||
|
upDataService(id, 1, -1, 0, finalReason, item);
|
||||||
|
},
|
||||||
|
child: const Text('确认拒绝', style: TextStyle(color: Colors.red)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text('暂不处理', style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
barrierDismissible: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void upDataService(String id, int status, ReservationModel item) async {
|
//addStatus 1完成 2未加 -1拒绝
|
||||||
|
void upDataService(
|
||||||
|
String id,
|
||||||
|
int status,
|
||||||
|
int addStatus,
|
||||||
|
num addHydAmount,
|
||||||
|
String rejectReason,
|
||||||
|
ReservationModel item,
|
||||||
|
) async {
|
||||||
showLoading("确认中");
|
showLoading("确认中");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var responseData = await HttpService.to.post(
|
var responseData;
|
||||||
'appointment/orderAddHyd/completeOrder',
|
if (addStatus == -1) {
|
||||||
data: {'id': id, 'addStatus': status},
|
responseData = await HttpService.to.post(
|
||||||
);
|
'appointment/orderAddHyd/rejectOrder',
|
||||||
|
data: {
|
||||||
|
'id': id,
|
||||||
|
'state': -1, //拒绝使用
|
||||||
|
"rejectReason": rejectReason,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
responseData = await HttpService.to.post(
|
||||||
|
'appointment/orderAddHyd/completeOrder',
|
||||||
|
data: {
|
||||||
|
'id': id,
|
||||||
|
'addStatus': addStatus, //完成使用 完成1,未加2
|
||||||
|
"addHydAmount": addHydAmount,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (responseData == null && responseData!.data == null) {
|
if (responseData == null && responseData!.data == null) {
|
||||||
dismissLoading();
|
dismissLoading();
|
||||||
@@ -252,12 +524,16 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
}
|
}
|
||||||
dismissLoading();
|
dismissLoading();
|
||||||
|
|
||||||
if (status == 1) {
|
//1完成 2未加 -1拒绝
|
||||||
|
if (addStatus == 1) {
|
||||||
item.status = ReservationStatus.completed;
|
item.status = ReservationStatus.completed;
|
||||||
} else if (status == 2) {
|
item.amount = "${addHydAmount}kg";
|
||||||
|
} else if (addStatus == -1) {
|
||||||
item.status = ReservationStatus.rejected;
|
item.status = ReservationStatus.rejected;
|
||||||
|
} else if (addStatus == 2) {
|
||||||
|
item.status = ReservationStatus.unadded;
|
||||||
}
|
}
|
||||||
updateUi();
|
renderData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dismissLoading();
|
dismissLoading();
|
||||||
}
|
}
|
||||||
@@ -267,6 +543,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,14 +564,22 @@ 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"].toString();
|
||||||
|
orderUnfinishedAmount = result.data["orderUnfinishedAmount"].toString();
|
||||||
|
|
||||||
leftHydrogen = leftHydrogen.isEmpty ? "统计中" : leftHydrogen.toString();
|
leftHydrogen = leftHydrogen.isEmpty ? "统计中" : leftHydrogen.toString();
|
||||||
|
orderTotalAmount = orderTotalAmount.isEmpty ? "统计中" : orderTotalAmount.toString();
|
||||||
//加载列表数据
|
orderUnfinishedAmount = orderUnfinishedAmount.isEmpty
|
||||||
fetchReservationData();
|
? "统计中"
|
||||||
|
: orderUnfinishedAmount.toString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast('数据异常');
|
showToast('数据异常');
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
|
||||||
|
//加载列表数据
|
||||||
|
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, '未加氢总量'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -134,7 +145,7 @@ class SitePage extends GetView<SiteController> {
|
|||||||
),
|
),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
controller.fetchReservationData();
|
controller.renderData();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.refresh, size: 16),
|
icon: const Icon(Icons.refresh, size: 16),
|
||||||
label: const Text('刷新'),
|
label: const Text('刷新'),
|
||||||
@@ -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(
|
||||||
@@ -336,7 +408,7 @@ class SitePage extends GetView<SiteController> {
|
|||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () => controller.confirmReservation(item.id),
|
onPressed: () => controller.confirmReservation(item.id),
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.blue),
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.blue),
|
||||||
child: const Text('确定'),
|
child: const Text('确认'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
@@ -361,15 +433,19 @@ class SitePage extends GetView<SiteController> {
|
|||||||
Color color;
|
Color color;
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ReservationStatus.pending:
|
case ReservationStatus.pending:
|
||||||
text = '待处理';
|
text = '待加氢';
|
||||||
color = Colors.orange;
|
color = Colors.orange;
|
||||||
break;
|
break;
|
||||||
case ReservationStatus.completed:
|
case ReservationStatus.completed:
|
||||||
text = '已完成';
|
text = '已加氢';
|
||||||
color = Colors.greenAccent;
|
color = Colors.greenAccent;
|
||||||
break;
|
break;
|
||||||
case ReservationStatus.rejected:
|
case ReservationStatus.rejected:
|
||||||
text = '已拒绝';
|
text = '拒绝加氢';
|
||||||
|
color = Colors.red;
|
||||||
|
break;
|
||||||
|
case ReservationStatus.unadded:
|
||||||
|
text = '未加氢';
|
||||||
color = Colors.red;
|
color = Colors.red;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
|||||||
void onReady() {
|
void onReady() {
|
||||||
super.onReady();
|
super.onReady();
|
||||||
// 如果未绑定车辆,且本次会话尚未提示过,则弹出提示
|
// 如果未绑定车辆,且本次会话尚未提示过,则弹出提示
|
||||||
if (!StorageService.to.hasShownBindVehicleDialog) {
|
if (!StorageService.to.hasShownBindVehicleDialog && StorageService.to.isLoggedIn) {
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
DialogX.to.showConfirmDialog(
|
DialogX.to.showConfirmDialog(
|
||||||
title: '当前尚未绑定车辆',
|
title: '当前尚未绑定车辆',
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -9,36 +11,86 @@ 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;
|
||||||
|
|
||||||
|
// 获取今天的日期 (不含时间)
|
||||||
|
final DateTime today = DateTime(
|
||||||
|
DateTime.now().year,
|
||||||
|
DateTime.now().month,
|
||||||
|
DateTime.now().day,
|
||||||
|
);
|
||||||
|
|
||||||
|
//计算明天的日期
|
||||||
|
final DateTime tomorrow = today.add(const Duration(days: 1));
|
||||||
|
|
||||||
Get.bottomSheet(
|
Get.bottomSheet(
|
||||||
Container(
|
Container(
|
||||||
height: 300,
|
height: 300,
|
||||||
@@ -66,8 +118,21 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
),
|
),
|
||||||
CupertinoButton(
|
CupertinoButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
final bool isChangingToToday =
|
||||||
|
tempDate.isAtSameMomentAs(today) &&
|
||||||
|
!selectedDate.value.isAtSameMomentAs(today);
|
||||||
|
final bool isDateChanged = !tempDate.isAtSameMomentAs(
|
||||||
|
selectedDate.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 更新选中的日期
|
||||||
selectedDate.value = tempDate;
|
selectedDate.value = tempDate;
|
||||||
Get.back();
|
Get.back(); // 先关闭弹窗
|
||||||
|
|
||||||
|
// 如果日期发生了变化,则重置时间
|
||||||
|
if (isDateChanged) {
|
||||||
|
resetTimeForSelectedDate();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'确认',
|
'确认',
|
||||||
@@ -84,28 +149,12 @@ 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(
|
minimumDate: today,
|
||||||
DateTime(
|
// 最小可选日期为今天
|
||||||
DateTime.now().year,
|
maximumDate: tomorrow,
|
||||||
DateTime.now().month,
|
// 最大可选日期为明天
|
||||||
DateTime.now().day,
|
// ---------------------
|
||||||
),
|
|
||||||
)
|
|
||||||
? DateTime(
|
|
||||||
DateTime.now().year,
|
|
||||||
DateTime.now().month,
|
|
||||||
DateTime.now().day,
|
|
||||||
)
|
|
||||||
: selectedDate.value,
|
|
||||||
|
|
||||||
// 设置最小可选日期为“今天凌晨0点”
|
|
||||||
minimumDate: DateTime(
|
|
||||||
DateTime.now().year,
|
|
||||||
DateTime.now().month,
|
|
||||||
DateTime.now().day,
|
|
||||||
),
|
|
||||||
maximumDate: DateTime.now().add(const Duration(days: 365)),
|
|
||||||
onDateTimeChanged: (DateTime newDate) {
|
onDateTimeChanged: (DateTime newDate) {
|
||||||
tempDate = newDate;
|
tempDate = newDate;
|
||||||
},
|
},
|
||||||
@@ -118,52 +167,90 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pickTime(BuildContext context, bool isStartTime) {
|
void resetTimeForSelectedDate() {
|
||||||
// 1. 确定当前操作的时间和初始值
|
final now = DateTime.now();
|
||||||
TimeOfDay initialTime = isStartTime ? startTime.value : endTime.value;
|
|
||||||
DateTime now = DateTime.now();
|
|
||||||
|
|
||||||
// 2. 准备小时和分钟的数据源
|
|
||||||
List<int> hours = List<int>.generate(24, (index) => index);
|
|
||||||
List<int> minutes = [0, 30];
|
|
||||||
|
|
||||||
// 3. 计算初始选中的索引
|
|
||||||
int initialHour = initialTime.hour;
|
|
||||||
// 将初始分钟校准到0或30,并找到对应的索引
|
|
||||||
int initialMinute = initialTime.minute;
|
|
||||||
int minuteIndex = initialMinute < 30 ? 0 : 1;
|
|
||||||
initialMinute = minutes[minuteIndex]; // 校准后的分钟
|
|
||||||
|
|
||||||
// 如果校准后导致时间早于当前时间,需要向上调整
|
|
||||||
final selectedDay = DateTime(selectedDate.value.year, selectedDate.value.month, selectedDate.value.day);
|
|
||||||
final today = DateTime(now.year, now.month, now.day);
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
if (selectedDay.isAtSameMomentAs(today)) {
|
|
||||||
if (initialHour < now.hour || (initialHour == now.hour && initialMinute < now.minute)) {
|
// 判断新选择的日期是不是今天
|
||||||
initialHour = now.hour;
|
if (selectedDate.value.isAtSameMomentAs(today)) {
|
||||||
if (now.minute > 30) {
|
// 如果是今天,就将时间重置为当前时间所在的半小时区间
|
||||||
// 如果当前分钟>30, 则进位到下一小时的0分
|
startTime.value = _calculateInitialStartTime(now);
|
||||||
initialHour = (now.hour + 1) % 24;
|
endTime.value = TimeOfDay.fromDateTime(
|
||||||
initialMinute = 0;
|
_getDateTimeFromTimeOfDay(startTime.value).add(const Duration(minutes: 30)),
|
||||||
} else {
|
);
|
||||||
// 否则,取30分
|
} else {
|
||||||
initialMinute = 30;
|
// 如果是明天(或其他未来日期),则可以将时间重置为一天的最早可用时间,例如 00:00
|
||||||
|
startTime.value = const TimeOfDay(hour: 0, minute: 0);
|
||||||
|
endTime.value = const TimeOfDay(hour: 0, minute: 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///30 分钟为间隔 时间选择器
|
||||||
|
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 startTime = TimeOfDay(hour: startMinutes ~/ 60, minute: startMinutes % 60);
|
||||||
|
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));
|
||||||
|
} else {
|
||||||
|
// 如果是今天,需要判断该时间段是否可选
|
||||||
|
// 创建时间段的结束时间对象
|
||||||
|
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,55 +271,23 @@ 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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -240,36 +295,15 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
),
|
),
|
||||||
const Divider(height: 1, color: Color(0xFFE5E5E5)),
|
const Divider(height: 1, color: Color(0xFFE5E5E5)),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: CupertinoPicker(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
scrollController: scrollController,
|
||||||
children: [
|
itemExtent: 40.0,
|
||||||
// 小时选择器
|
onSelectedItemChanged: (index) {
|
||||||
Expanded(
|
tempSlot = availableSlots[index];
|
||||||
child: CupertinoPicker(
|
},
|
||||||
scrollController: hourController,
|
children: availableSlots
|
||||||
itemExtent: 32.0,
|
.map((slot) => Center(child: Text(slot.display)))
|
||||||
onSelectedItemChanged: (index) {
|
.toList(),
|
||||||
tempHour = hours[index];
|
|
||||||
},
|
|
||||||
children: hours
|
|
||||||
.map((h) => Center(child: Text(h.toString().padLeft(2, '0'))))
|
|
||||||
.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 +313,6 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 用于存储上一次成功预约的信息
|
// 用于存储上一次成功预约的信息
|
||||||
ReservationModel? lastSuccessfulReservation;
|
ReservationModel? lastSuccessfulReservation;
|
||||||
|
|
||||||
@@ -311,37 +343,46 @@ 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) {
|
||||||
showToast("请勿重复提交相同的预约");
|
showToast("请勿重复提交相同时间段的预约,可在“查看预约”中修改");
|
||||||
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 +410,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,
|
||||||
@@ -388,14 +428,16 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
stateName: '',
|
stateName: '',
|
||||||
addStatus: '',
|
addStatus: '',
|
||||||
addStatusName: '',
|
addStatusName: '',
|
||||||
|
rejectReason: '',
|
||||||
|
hasEdit: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
//打开预约列表
|
//打开预约列表
|
||||||
Future.delayed(const Duration(milliseconds: 800), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
getReservationList();
|
getReservationList();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
showErrorToast(result.message);
|
showToast(result.error);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dismissLoading();
|
dismissLoading();
|
||||||
@@ -409,8 +451,16 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
// 新增预约数据列表
|
// 新增预约数据列表
|
||||||
List<ReservationModel> reservationList = [];
|
List<ReservationModel> reservationList = [];
|
||||||
|
|
||||||
|
// --- 用于防抖的 Timer ---
|
||||||
|
Timer? _debounce;
|
||||||
|
|
||||||
//查看预约列表
|
//查看预约列表
|
||||||
void getReservationList() async {
|
void getReservationList() async {
|
||||||
|
if (_debounce?.isActive ?? false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_debounce = Timer(const Duration(seconds: 1), () {});
|
||||||
|
|
||||||
showLoading("加载中");
|
showLoading("加载中");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -434,13 +484,32 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
if (baseModel.code == 0 && baseModel.data != null) {
|
if (baseModel.code == 0 && baseModel.data != null) {
|
||||||
final dataMap = baseModel.data as Map<String, dynamic>;
|
final dataMap = baseModel.data as Map<String, dynamic>;
|
||||||
final List<dynamic> listFromServer = dataMap['list'] ?? [];
|
final List<dynamic> listFromServer = dataMap['records'] ?? [];
|
||||||
reservationList = listFromServer.map((item) {
|
reservationList = listFromServer.map((item) {
|
||||||
return ReservationModel.fromJson(item as Map<String, dynamic>);
|
return ReservationModel.fromJson(item as Map<String, dynamic>);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
// 根据列表是否为空来更新 hasReservationData 状态
|
// 根据列表是否为空来更新 hasReservationData 状态
|
||||||
hasReservationData = reservationList.isNotEmpty;
|
hasReservationData = reservationList.isNotEmpty;
|
||||||
|
|
||||||
|
for (var reservation in reservationList) {
|
||||||
|
try {
|
||||||
|
// 获取当前时间和预约的结束时间
|
||||||
|
final now = DateTime.now();
|
||||||
|
final endDateTime = DateTime.parse(reservation.endTime);
|
||||||
|
|
||||||
|
// 如果当前时间在结束时间之后,则不能编辑
|
||||||
|
if (now.isAfter(endDateTime) ||
|
||||||
|
plateNumber.isEmpty ||
|
||||||
|
reservation.addStatus != "0") {
|
||||||
|
reservation.hasEdit = false;
|
||||||
|
} else {
|
||||||
|
reservation.hasEdit = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reservation.hasEdit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showToast(baseModel.message);
|
showToast(baseModel.message);
|
||||||
hasReservationData = false;
|
hasReservationData = false;
|
||||||
@@ -537,33 +606,56 @@ 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(
|
!reservation.hasEdit
|
||||||
padding: const EdgeInsets.symmetric(
|
? SizedBox()
|
||||||
horizontal: 12,
|
: GestureDetector(
|
||||||
vertical: 4,
|
onTap: () async {
|
||||||
),
|
var result = await Get.to(
|
||||||
decoration: BoxDecoration(
|
() => ReservationEditPage(),
|
||||||
color: const Color(
|
arguments: {
|
||||||
0xFFFFF7E6,
|
'reservation': reservation,
|
||||||
), // Light orange background
|
'difference': difference,
|
||||||
borderRadius: BorderRadius.circular(12),
|
},
|
||||||
),
|
binding: BindingsBuilder(() {
|
||||||
child: Text(
|
Get.put(ReservationEditController());
|
||||||
reservation.addStatusName,
|
}),
|
||||||
style: const TextStyle(
|
preventDuplicates: false,
|
||||||
color: Color(0xFFFA8C16),
|
);
|
||||||
fontWeight: FontWeight.bold,
|
if (result == true) {
|
||||||
fontSize: 12,
|
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),
|
const SizedBox(height: 12),
|
||||||
@@ -575,6 +667,9 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
_buildDetailRow('结束时间:', reservation.endTime),
|
_buildDetailRow('结束时间:', reservation.endTime),
|
||||||
_buildDetailRow('联系人:', reservation.contacts),
|
_buildDetailRow('联系人:', reservation.contacts),
|
||||||
_buildDetailRow('联系电话:', reservation.phone),
|
_buildDetailRow('联系电话:', reservation.phone),
|
||||||
|
reservation.addStatus == "5"
|
||||||
|
? _buildDetailRow('拒绝原因:', reservation.rejectReason)
|
||||||
|
: SizedBox(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -764,7 +859,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
HttpService.to.dio.options.headers = originalHeaders;
|
HttpService.to.dio.options.headers = originalHeaders;
|
||||||
|
|
||||||
// 如果未绑定车辆,且本次会话尚未提示过,则弹出提示
|
// 如果未绑定车辆,且本次会话尚未提示过,则弹出提示
|
||||||
if (!StorageService.to.hasShownBindVehicleDialog) {
|
if (!StorageService.to.hasShownBindVehicleDialog && StorageService.to.isLoggedIn) {
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
DialogX.to.showConfirmDialog(
|
DialogX.to.showConfirmDialog(
|
||||||
title: '当前尚未绑定车辆',
|
title: '当前尚未绑定车辆',
|
||||||
@@ -791,6 +886,9 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
void onClose() {
|
void onClose() {
|
||||||
amountController.dispose();
|
amountController.dispose();
|
||||||
plateNumberController.dispose();
|
plateNumberController.dispose();
|
||||||
|
if (_debounce != null) {
|
||||||
|
_debounce?.cancel();
|
||||||
|
}
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
showToast(result.error);
|
||||||
|
}
|
||||||
|
} 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