司机 预约 样式修改
This commit is contained in:
@@ -24,6 +24,10 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
|||||||
final RxList<String> operationAttachments = <String>[].obs;
|
final RxList<String> operationAttachments = <String>[].obs;
|
||||||
final RxList<String> hydrogenationAttachments = <String>[].obs;
|
final RxList<String> hydrogenationAttachments = <String>[].obs;
|
||||||
final RxList<String> registerAttachments = <String>[].obs;
|
final RxList<String> registerAttachments = <String>[].obs;
|
||||||
|
String color = "";
|
||||||
|
String hydrogenCapacity = "";
|
||||||
|
String rentFromCompany = "";
|
||||||
|
String address = "";
|
||||||
bool isNotice = false;
|
bool isNotice = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -127,6 +131,12 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
|||||||
...hydrogenationAttachments,
|
...hydrogenationAttachments,
|
||||||
...registerAttachments,
|
...registerAttachments,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
color = data['color'].toString();
|
||||||
|
hydrogenCapacity = data['hydrogenCapacity'].toString();
|
||||||
|
rentFromCompany = data['rentFromCompany'].toString();
|
||||||
|
address = data['address'].toString();
|
||||||
|
|
||||||
loadAllPdfs();
|
loadAllPdfs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
tabs: const [
|
tabs: const [
|
||||||
Tab(text: '行驶证'),
|
Tab(text: '行驶证'),
|
||||||
Tab(text: '营运证'),
|
Tab(text: '营运证'),
|
||||||
Tab(text: '加氢资格证'),
|
Tab(text: '加氢证'),
|
||||||
Tab(text: '登记证'),
|
Tab(text: '登记证'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -379,8 +379,8 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
_buildCertDetailItem('所属公司', '上海羚牛氢运物联网科技有限公司', isFull: true),
|
_buildCertDetailItem('所属公司', controller.rentFromCompany, isFull: true),
|
||||||
_buildCertDetailItem('运营城市', controller.vin),
|
_buildCertDetailItem('运营城市', controller.address),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -389,10 +389,10 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
children: [
|
children: [
|
||||||
_buildCertDetailItem(
|
_buildCertDetailItem(
|
||||||
'车辆颜色',
|
'车辆颜色',
|
||||||
'2028-08-14',
|
controller.color,
|
||||||
valueColor: const Color(0xFF52C41A),
|
valueColor: const Color(0xFF52C41A),
|
||||||
),
|
),
|
||||||
_buildCertDetailItem('氢瓶容量', '货运'),
|
_buildCertDetailItem('氢瓶容量', controller.hydrogenCapacity),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|||||||
@@ -689,6 +689,18 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
} finally {
|
} finally {
|
||||||
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
||||||
}
|
}
|
||||||
|
renderSliderTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
double current = 0.0;
|
||||||
|
double maxVal = 0.0;
|
||||||
|
|
||||||
|
void renderSliderTheme() {
|
||||||
|
current = double.tryParse(amountController.text) ?? 0.0;
|
||||||
|
maxVal = double.tryParse(difference) ?? 100.0;
|
||||||
|
if (maxVal <= 0) maxVal = 100.0;
|
||||||
|
|
||||||
|
updateUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
void getSiteList() async {
|
void getSiteList() async {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
import 'package:ln_jq_app/common/login_util.dart';
|
||||||
import 'package:ln_jq_app/common/model/station_model.dart';
|
import 'package:ln_jq_app/common/model/station_model.dart';
|
||||||
@@ -25,29 +26,24 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Color.fromRGBO(247, 249, 251, 1),
|
backgroundColor: Color.fromRGBO(247, 249, 251, 1),
|
||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
onTap: () {
|
onTap: () => unfocus(),
|
||||||
hideKeyboard();
|
|
||||||
},
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned.fill(
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 10.h,
|
|
||||||
top: 0,
|
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildUserInfoCard(),
|
_buildUserInfoCard(),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(left: 20.w, right: 20.w),
|
padding: EdgeInsets.symmetric(horizontal: 18.w),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
_buildCarInfoCard(),
|
_buildCarInfoCard(),
|
||||||
SizedBox(height: 32.h),
|
SizedBox(height: 24.h),
|
||||||
_buildReservationFormCard(context),
|
_buildReservationFormCard(context),
|
||||||
|
SizedBox(height: 180.h),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -58,7 +54,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
Positioned(
|
Positioned(
|
||||||
left: 20.w,
|
left: 20.w,
|
||||||
right: 20.w,
|
right: 20.w,
|
||||||
bottom: 90.h,
|
bottom: 110.h,
|
||||||
child: _buildReservationItem(context),
|
child: _buildReservationItem(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -156,13 +152,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () => Get.to(() => const MessagePage()),
|
||||||
Get.to(() => const MessagePage());
|
|
||||||
},
|
|
||||||
style: IconButton.styleFrom(
|
|
||||||
backgroundColor: Colors.grey[100],
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
),
|
|
||||||
icon: Badge(
|
icon: Badge(
|
||||||
smallSize: 8,
|
smallSize: 8,
|
||||||
backgroundColor: controller.isNotice
|
backgroundColor: controller.isNotice
|
||||||
@@ -196,9 +186,6 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Widget _buildModernStatItem(String title, String subtitle, String value, String unit) {
|
Widget _buildModernStatItem(String title, String subtitle, String value, String unit) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -241,7 +228,6 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建车辆信息卡片
|
|
||||||
Widget _buildCarInfoCard() {
|
Widget _buildCarInfoCard() {
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
@@ -251,13 +237,8 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
// 左侧:车辆图片
|
Expanded(flex: 4, child: LoginUtil.getAssImg('ic_car_bg@2x')),
|
||||||
Expanded(
|
|
||||||
flex: 4,
|
|
||||||
child: LoginUtil.getAssImg('ic_car_bg@2x'),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
// 右侧:信息与进度条
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 6,
|
flex: 6,
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -268,7 +249,6 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildCarDataItem('百公里氢耗', '${controller.workEfficiency}Kg'),
|
_buildCarDataItem('百公里氢耗', '${controller.workEfficiency}Kg'),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// 进度条部分
|
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
@@ -276,16 +256,32 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
value: controller.progressValue,
|
value: controller.progressValue,
|
||||||
minHeight: 6,
|
minHeight: 6,
|
||||||
backgroundColor: Color(0xFFF0F2F5),
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF006633)),
|
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||||
|
Color(0xFF006633),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Text("剩余氢量", style: TextStyle(fontSize: 10, color: Colors.grey)),
|
const Text(
|
||||||
Text("${controller.leftHydrogen}Kg", style: const TextStyle(fontSize: 10, color: Color(0xFF006633), fontWeight: FontWeight.bold)),
|
"剩余氢量",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${controller.leftHydrogen}Kg",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Color(0xFF006633),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -304,196 +300,440 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)),
|
Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)),
|
||||||
Text(value, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black87)),
|
Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建预约表单卡片
|
/// --- 构建预约表单卡片---
|
||||||
Widget _buildReservationFormCard(BuildContext context) {
|
Widget _buildReservationFormCard(BuildContext context) {
|
||||||
return Card(
|
return Column(
|
||||||
elevation: 2,
|
children: [
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
// 1. 顶部:日期与时间选择
|
||||||
|
Card(
|
||||||
|
elevation: 0,
|
||||||
|
color: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Obx(
|
child: Column(
|
||||||
() => Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildPickerRow(
|
const Text(
|
||||||
label: '日期',
|
"预约日期与时间",
|
||||||
value: controller.formattedDate,
|
style: TextStyle(
|
||||||
icon: Icons.calendar_today_outlined,
|
fontSize: 14,
|
||||||
onTap: () => controller.pickDate(context),
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
_buildPickerRow(
|
|
||||||
label: '预约时间',
|
|
||||||
value: controller.formattedTimeSlot,
|
|
||||||
icon: Icons.access_time_outlined,
|
|
||||||
onTap: () => controller.pickTime(context),
|
|
||||||
),
|
),
|
||||||
_buildTextField(
|
|
||||||
label: '预约氢量(KG)',
|
|
||||||
controller: controller.amountController,
|
|
||||||
hint: '当前最大可预约氢量${controller.difference}(KG)',
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
/*_buildTextField(
|
|
||||||
label: '车牌号',
|
|
||||||
controller: controller.plateNumberController,
|
|
||||||
hint: '请输入车牌号', // 修改提示文案
|
|
||||||
enabled: false, // 设置为不可编辑
|
|
||||||
),*/
|
|
||||||
_buildStationSelector(),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
_buildHorizontalDateSelector(),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
_buildTimeSlider(context),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
// 2. 底部:氢量与站点
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(child: _buildAmountSliderSection()),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(child: _buildStationCardSection(context)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildReservationItem(BuildContext context) {
|
/// 水平日期选择器
|
||||||
return Row(
|
Widget _buildHorizontalDateSelector() {
|
||||||
children: [
|
final DateTime today = DateTime(
|
||||||
Expanded(
|
DateTime.now().year,
|
||||||
flex: 1,
|
DateTime.now().month,
|
||||||
child: OutlinedButton(
|
DateTime.now().day,
|
||||||
onPressed: () {
|
);
|
||||||
controller.getReservationList(showPopup: true, addStatus: '');
|
final DateTime tomorrow = today.add(const Duration(days: 1));
|
||||||
},
|
final List<DateTime> dates = List.generate(
|
||||||
style: OutlinedButton.styleFrom(
|
5,
|
||||||
minimumSize: Size(double.infinity, 40.h), // 高度与另一个按钮保持一致
|
(index) => today.add(Duration(days: index)),
|
||||||
side: const BorderSide(color: Color.fromRGBO(226, 232, 240, 1)),
|
);
|
||||||
backgroundColor: Colors.white,
|
const List<String> weekMap = ['日', '一', '二', '三', '四', '五', '六'];
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
||||||
|
return SizedBox(
|
||||||
|
height: 75,
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: dates.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
DateTime date = dates[index];
|
||||||
|
bool isSelectable =
|
||||||
|
date.isAtSameMomentAs(today) || date.isAtSameMomentAs(tomorrow);
|
||||||
|
bool isSelected =
|
||||||
|
controller.selectedDate.value.year == date.year &&
|
||||||
|
controller.selectedDate.value.month == date.month &&
|
||||||
|
controller.selectedDate.value.day == date.day;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: isSelectable
|
||||||
|
? () {
|
||||||
|
controller.selectedDate.value = date;
|
||||||
|
controller.resetTimeForSelectedDate();
|
||||||
|
controller.updateUi();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: Container(
|
||||||
|
width: 58,
|
||||||
|
margin: const EdgeInsets.only(right: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? const Color(0xFF006633) : const Color(0xFFF8F9FA),
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: Column(
|
||||||
'查看预约',
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
weekMap[date.weekday % 7],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color.fromRGBO(119, 119, 119, 1),
|
fontSize: 12,
|
||||||
|
color: isSelected
|
||||||
|
? Colors.white70
|
||||||
|
: (isSelectable ? Colors.grey : Colors.grey[300]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
"${date.day}",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: isSelected
|
||||||
|
? Colors.white
|
||||||
|
: (isSelectable ? Colors.black87 : Colors.grey[300]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 时间 Slider 选择器
|
||||||
|
Widget _buildTimeSlider(BuildContext context) {
|
||||||
|
return Obx(() {
|
||||||
|
// 这里的逻辑对应 Controller 中的 24 小时可用 Slot
|
||||||
|
int currentIdx = controller.startTime.value.hour;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFE6F4EA),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(color: Colors.white, width: 2),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
controller.formattedTimeSlot,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF006633),
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
],
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: controller.submitReservation,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
minimumSize: Size(double.infinity, 40.h),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
||||||
backgroundColor: AppTheme.themeColor,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
),
|
),
|
||||||
child: const Text(
|
SliderTheme(
|
||||||
'提交预约',
|
data: SliderTheme.of(context).copyWith(
|
||||||
style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
|
trackHeight: 10,
|
||||||
|
activeTrackColor: const Color(0xFF006633),
|
||||||
|
inactiveTrackColor: const Color(0xFFF0F2F5),
|
||||||
|
thumbColor: Colors.white,
|
||||||
|
thumbShape: const RoundSliderThumbShape(
|
||||||
|
enabledThumbRadius: 12,
|
||||||
|
elevation: 4,
|
||||||
),
|
),
|
||||||
|
overlayColor: const Color(0xFF006633).withOpacity(0.1),
|
||||||
|
),
|
||||||
|
child: Slider(
|
||||||
|
value: currentIdx.toDouble(),
|
||||||
|
min: 0,
|
||||||
|
max: 23,
|
||||||
|
divisions: 23,
|
||||||
|
onChanged: (val) {
|
||||||
|
int hour = val.toInt();
|
||||||
|
// 模拟 Controller 中的 pickTime 逻辑校验
|
||||||
|
final now = DateTime.now();
|
||||||
|
final isToday =
|
||||||
|
controller.selectedDate.value.year == now.year &&
|
||||||
|
controller.selectedDate.value.month == now.month &&
|
||||||
|
controller.selectedDate.value.day == now.day;
|
||||||
|
|
||||||
|
if (isToday && hour < now.hour) {
|
||||||
|
// 如果是今天且小时数小于当前,则忽略
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.startTime.value = TimeOfDay(hour: hour, minute: 0);
|
||||||
|
controller.endTime.value = TimeOfDay(hour: (hour + 1) % 24, minute: 0);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表单中的可点击行 (用于日期和时间选择)
|
/// 氢量滑块区域
|
||||||
Widget _buildPickerRow({
|
Widget _buildAmountSliderSection() {
|
||||||
required String label,
|
return Card(
|
||||||
required String value,
|
elevation: 0,
|
||||||
required IconData icon,
|
color: Colors.white,
|
||||||
required VoidCallback onTap,
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
}) {
|
child: Padding(
|
||||||
return Padding(
|
padding: const EdgeInsets.all(13.0),
|
||||||
padding: const EdgeInsets.only(bottom: 12.0),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 13)),
|
const Text(
|
||||||
const SizedBox(height: 8),
|
"预约氢量",
|
||||||
InkWell(
|
style: TextStyle(
|
||||||
onTap: onTap,
|
fontSize: 14,
|
||||||
child: Container(
|
fontWeight: FontWeight.bold,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"Refuel Amount",
|
||||||
|
style: TextStyle(fontSize: 10, color: Colors.grey),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
SliderTheme(
|
||||||
|
data: SliderTheme.of(Get.context!).copyWith(
|
||||||
|
trackHeight: 6,
|
||||||
|
activeTrackColor: const Color(0xFF006633),
|
||||||
|
inactiveTrackColor: const Color(0xFFF0F2F5),
|
||||||
|
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 9),
|
||||||
|
),
|
||||||
|
child: Slider(
|
||||||
|
value: controller.current > controller.maxVal
|
||||||
|
? controller.maxVal
|
||||||
|
: controller.current,
|
||||||
|
min: 0,
|
||||||
|
max: controller.maxVal,
|
||||||
|
onChanged: (val) {
|
||||||
|
final safeVal = val < 1 ? 1 : val; // 最小 1
|
||||||
|
controller.amountController.text = safeVal.toStringAsFixed(0);
|
||||||
|
controller.renderSliderTheme();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Container(
|
||||||
|
height: 40.h,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: Colors.grey[400]!),
|
color: const Color(0xFFF8F9FA),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(value, style: const TextStyle(fontSize: 14)),
|
IconButton(
|
||||||
Icon(icon, color: Colors.grey, size: 20),
|
onPressed: () => _updateAmount(-1),
|
||||||
],
|
icon: const Icon(Icons.remove, size: 18, color: Colors.grey),
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
"${controller.amountController.text} Kg",
|
||||||
|
style: const TextStyle(fontSize: 14, color: Colors.black87),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _updateAmount(1),
|
||||||
|
icon: const Icon(Icons.add, size: 18, color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表单中的文本输入框
|
/// 站点卡片区域
|
||||||
Widget _buildTextField({
|
|
||||||
required String label,
|
Widget _buildStationCardSection(BuildContext context) {
|
||||||
required TextEditingController controller,
|
return Obx(() {
|
||||||
required String hint,
|
return DropdownButtonHideUnderline(
|
||||||
TextInputType? keyboardType,
|
child: DropdownButton2<String>(
|
||||||
bool enabled = true,
|
isExpanded: true,
|
||||||
}) {
|
|
||||||
bool showCounter = keyboardType == TextInputType.number;
|
/// 当前选中值
|
||||||
return Padding(
|
value: controller.selectedStationId.value,
|
||||||
padding: const EdgeInsets.only(bottom: 12.0),
|
|
||||||
|
hint: const Text('请选择加氢站', style: TextStyle(fontSize: 14, color: Colors.grey)),
|
||||||
|
|
||||||
|
/// 下拉数据
|
||||||
|
items: controller.stationOptions
|
||||||
|
.map(
|
||||||
|
(station) => DropdownMenuItem<String>(
|
||||||
|
value: station.hydrogenId,
|
||||||
|
enabled: station.isSelect == 1,
|
||||||
|
child: _buildDropdownItem(station),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
|
||||||
|
/// 选中回调
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
controller.selectedStationId.value = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
///作为 Dropdown 的触发按钮
|
||||||
|
customButton: _buildStationCard(),
|
||||||
|
|
||||||
|
/// 隐藏按钮自身样式
|
||||||
|
buttonStyleData: const ButtonStyleData(padding: EdgeInsets.zero),
|
||||||
|
|
||||||
|
/// 隐藏默认箭头
|
||||||
|
iconStyleData: const IconStyleData(icon: SizedBox.shrink()),
|
||||||
|
|
||||||
|
/// 下拉样式
|
||||||
|
dropdownStyleData: DropdownStyleData(
|
||||||
|
maxHeight: 300,
|
||||||
|
width: MediaQuery.of(context).size.width / 1.3,
|
||||||
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// 下拉项高度
|
||||||
|
menuItemStyleData: const MenuItemStyleData(height: 60),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStationCard() {
|
||||||
|
final stationId = controller.selectedStationId.value;
|
||||||
|
final station = controller.stationOptions.firstWhereOrNull(
|
||||||
|
(s) => s.hydrogenId == stationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 0,
|
||||||
|
color: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(13.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 13)),
|
Row(
|
||||||
const SizedBox(height: 8),
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
TextFormField(
|
children: [
|
||||||
controller: controller,
|
const Column(
|
||||||
keyboardType: keyboardType,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
inputFormatters: [
|
children: [
|
||||||
FilteringTextInputFormatter.digitsOnly, // 只允许数字输入
|
Text(
|
||||||
|
"加氢站",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("Station", style: TextStyle(fontSize: 10, color: Colors.grey)),
|
||||||
],
|
],
|
||||||
enabled: enabled,
|
),
|
||||||
style: const TextStyle(fontSize: 14),
|
Icon(Icons.more_vert, color: Colors.grey[300], size: 20),
|
||||||
decoration: InputDecoration(
|
],
|
||||||
isDense: true,
|
),
|
||||||
hintText: hint,
|
const SizedBox(height: 24),
|
||||||
hintStyle: TextStyle(fontSize: 14),
|
Container(
|
||||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)),
|
width: double.infinity,
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
height: 100,
|
||||||
filled: !enabled,
|
decoration: BoxDecoration(
|
||||||
fillColor: Colors.grey[100],
|
borderRadius: BorderRadius.circular(16),
|
||||||
// 左侧减号按钮
|
image: const DecorationImage(
|
||||||
prefixIcon: showCounter
|
image: AssetImage("assets/images/bg_map@2x.png"),
|
||||||
? IconButton(
|
fit: BoxFit.cover,
|
||||||
icon: const Icon(Icons.remove, color: Colors.blue),
|
),
|
||||||
onPressed: () => _updateAmount(-1),
|
),
|
||||||
padding: EdgeInsets.zero,
|
child: Stack(
|
||||||
constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
|
children: [
|
||||||
)
|
Center(
|
||||||
: null,
|
child: Icon(
|
||||||
|
Icons.location_on_outlined,
|
||||||
// 右侧加号按钮
|
color: Colors.grey[300],
|
||||||
suffixIcon: showCounter
|
size: 40,
|
||||||
? IconButton(
|
),
|
||||||
icon: const Icon(Icons.add, color: Colors.blue),
|
),
|
||||||
onPressed: () => _updateAmount(1),
|
Align(
|
||||||
padding: EdgeInsets.zero,
|
alignment: Alignment.bottomCenter,
|
||||||
constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
|
child: Container(
|
||||||
)
|
width: double.infinity,
|
||||||
: null,
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(16),
|
||||||
|
bottomRight: Radius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"${station?.name ?? '请选择站点'} | ${station?.price ?? '0.00'}/Kg",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// :更新氢量逻辑
|
|
||||||
void _updateAmount(int change) {
|
void _updateAmount(int change) {
|
||||||
// 获取当前输入框的值,默认为 0
|
// 获取当前输入框的值,默认为 0
|
||||||
double currentAmount = double.tryParse(controller.amountController.text) ?? 0;
|
double currentAmount = double.tryParse(controller.amountController.text) ?? 0;
|
||||||
@@ -522,98 +762,48 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
controller.amountController.selection = TextSelection.fromPosition(
|
controller.amountController.selection = TextSelection.fromPosition(
|
||||||
TextPosition(offset: controller.amountController.text.length),
|
TextPosition(offset: controller.amountController.text.length),
|
||||||
);
|
);
|
||||||
|
controller.updateUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStationSelector() {
|
Widget _buildReservationItem(BuildContext context) {
|
||||||
return Column(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Expanded(
|
||||||
padding: EdgeInsets.all(0),
|
flex: 1,
|
||||||
child: Row(
|
child: OutlinedButton(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
onPressed: () =>
|
||||||
children: [
|
controller.getReservationList(showPopup: true, addStatus: ''),
|
||||||
Text('加氢站', style: TextStyle(color: Colors.grey[600], fontSize: 14)),
|
style: OutlinedButton.styleFrom(
|
||||||
TextButton(
|
minimumSize: Size(double.infinity, 50.h),
|
||||||
onPressed: () {
|
side: const BorderSide(color: Color.fromRGBO(226, 232, 240, 1)),
|
||||||
controller.getSiteList();
|
backgroundColor: Colors.white,
|
||||||
},
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
|
||||||
child: const Text('刷新'),
|
|
||||||
),
|
),
|
||||||
],
|
child: const Text(
|
||||||
|
'查看预约',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color.fromRGBO(119, 119, 119, 1),
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Obx(
|
|
||||||
() => DropdownButtonHideUnderline(
|
|
||||||
child: DropdownButton2<String>(
|
|
||||||
isExpanded: true,
|
|
||||||
hint: const Text(
|
|
||||||
'请选择加氢站',
|
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
|
||||||
),
|
|
||||||
// items 列表现在从 stationOptions (StationModel列表) 构建
|
|
||||||
items: controller.stationOptions
|
|
||||||
.map(
|
|
||||||
(station) => DropdownMenuItem<String>(
|
|
||||||
value: station.hydrogenId, // value 是站点的唯一ID
|
|
||||||
enabled: station.isSelect == 1,
|
|
||||||
child: _buildDropdownItem(station), // child 是自定义的 Widget
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
value:
|
|
||||||
// 当前的站点 处理默认
|
|
||||||
controller.selectedStationId.value ??
|
|
||||||
(controller.stationOptions.isNotEmpty
|
|
||||||
? controller.stationOptions.first.hydrogenId
|
|
||||||
: null),
|
|
||||||
// 当前选中的是站点ID
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
controller.selectedStationId.value = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
customButton: Obx(() {
|
|
||||||
// 优先从已选中的 ID 查找
|
|
||||||
var selectedStation = controller.stationOptions.firstWhereOrNull(
|
|
||||||
(s) => s.hydrogenId == controller.selectedStationId.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 如果找不到已选中的(比如 ID 为空或列表里没有),并且列表不为空,则取第一个作为默认
|
|
||||||
final stationToShow =
|
|
||||||
selectedStation ??
|
|
||||||
(controller.stationOptions.isNotEmpty
|
|
||||||
? controller.stationOptions.first
|
|
||||||
: null);
|
|
||||||
|
|
||||||
// 如果有要显示的站点,就构建按钮
|
|
||||||
if (stationToShow != null) {
|
|
||||||
return _buildSelectedStationButton(stationToShow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 否则,返回一个空占位符,让 hint 生效
|
|
||||||
// DropdownButton2 内部会判断,如果 customButton 返回的不是一个有效Widget(或根据其内部逻辑),就会显示 hint
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}),
|
|
||||||
buttonStyleData: ButtonStyleData(
|
|
||||||
height: 40, // 增加高度以容纳两行文字
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(color: Colors.grey[400]!),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
iconStyleData: const IconStyleData(
|
const SizedBox(width: 16),
|
||||||
icon: Icon(Icons.arrow_drop_down, color: Colors.grey),
|
Expanded(
|
||||||
iconSize: 24,
|
flex: 2,
|
||||||
),
|
child: ElevatedButton(
|
||||||
dropdownStyleData: DropdownStyleData(
|
onPressed: controller.submitReservation,
|
||||||
maxHeight: 300,
|
style: ElevatedButton.styleFrom(
|
||||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
|
minimumSize: Size(double.infinity, 50.h),
|
||||||
),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
|
||||||
menuItemStyleData: const MenuItemStyleData(
|
backgroundColor: const Color(0xFF006633),
|
||||||
height: 60, // 增加下拉项的高度
|
foregroundColor: Colors.white,
|
||||||
|
elevation: 4,
|
||||||
),
|
),
|
||||||
|
child: const Text(
|
||||||
|
'提交预约',
|
||||||
|
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user