diff --git a/ln_jq_app/README.md b/ln_jq_app/README.md index 912724e..7aec585 100644 --- a/ln_jq_app/README.md +++ b/ln_jq_app/README.md @@ -5,4 +5,9 @@ android jks 小羚羚 -Ln123456. \ No newline at end of file +Ln123456. + + +高德key +安卓 92495660f7bc990cb475426c47c03b65 +苹果:3ac08e5e14df9d7a52e98d40e21a0189 \ No newline at end of file diff --git a/ln_jq_app/lib/pages/b_page/site/view.dart b/ln_jq_app/lib/pages/b_page/site/view.dart index 046dad5..d8f6eb5 100644 --- a/ln_jq_app/lib/pages/b_page/site/view.dart +++ b/ln_jq_app/lib/pages/b_page/site/view.dart @@ -117,7 +117,7 @@ class SitePage extends GetView { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '预约信息', + '今日预约信息', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, diff --git a/ln_jq_app/lib/pages/c_page/base_widgets/view.dart b/ln_jq_app/lib/pages/c_page/base_widgets/view.dart index 76e1ae9..0617b90 100644 --- a/ln_jq_app/lib/pages/c_page/base_widgets/view.dart +++ b/ln_jq_app/lib/pages/c_page/base_widgets/view.dart @@ -5,7 +5,7 @@ import 'package:ln_jq_app/pages/c_page/car_info/view.dart'; import 'package:ln_jq_app/pages/c_page/map/view.dart'; import 'package:ln_jq_app/pages/c_page/mine/view.dart'; import 'package:ln_jq_app/pages/c_page/reservation/view.dart'; -import 'package:ln_jq_app/pages/demo/index.dart'; + import 'index.dart'; class BaseWidgetsPage extends GetView { diff --git a/ln_jq_app/lib/pages/c_page/car_info/view.dart b/ln_jq_app/lib/pages/c_page/car_info/view.dart index ad97f46..aaf2380 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/view.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/view.dart @@ -1,26 +1,284 @@ import 'package:flutter/material.dart'; -import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:get/get.dart'; +// import 'package:getx_scaffold/getx_scaffold.dart'; // 如果不使用其中的扩展,可以注释掉 import 'controller.dart'; class CarInfoPage extends GetView { const CarInfoPage({super.key}); - // 主视图 - Widget _buildView() { - return [ - TextX.titleLarge('车辆信息'), - ].toColumn(mainAxisSize: MainAxisSize.min).center(); - } - @override Widget build(BuildContext context) { return GetBuilder( init: CarInfoController(), id: 'car_info', builder: (_) { - return _buildView(); + // 将所有 UI 构建逻辑都放在这里 + return Scaffold( + backgroundColor: Colors.grey[100], + // 我们不再使用单独的 AppBar,而是通过自定义的 Container 来实现类似效果 + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildDriverInfoCard(), + const SizedBox(height: 12), + _buildCarBindingCard(), + const SizedBox(height: 12), + _buildCertificatesCard(), + const SizedBox(height: 12), + _buildTipsCard(), + ], + ), + ), + ), + ); }, ); } + + /// 1. 构建顶部的司机信息卡片(包含蓝色背景) + Widget _buildDriverInfoCard() { + return Card( + elevation: 4, + margin: EdgeInsets.zero, // Card 在蓝色背景内,不需要外边距 + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + const CircleAvatar( + radius: 28, + backgroundColor: Colors.blue, + child: Icon(Icons.person, color: Colors.white, size: 36), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '王小龙', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 4), + const Text( + '15888332828', + style: TextStyle(color: Colors.grey, fontSize: 14), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.blue[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.blue, width: 0.5), + ), + child: const Row( + children: [ + Icon(Icons.shield_outlined, color: Colors.blue, size: 14), + SizedBox(width: 4), + Text( + '已认证', + style: TextStyle( + color: Colors.blue, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + ), + const Divider(height: 1, indent: 16, endIndent: 16), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildStatItem('156', '服务天数'), + _buildStatItem('4.9', '评分'), + _buildStatItem('98%', '准时率'), + ], + ), + ), + ], + ), + ); + } + + // 司机信息卡片中的小统计项 + Widget _buildStatItem(String value, String label) { + return Column( + children: [ + Text( + value, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.blue, + ), + ), + const SizedBox(height: 4), + Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)), + ], + ); + } + + /// 2. 构建车辆绑定信息卡片 + Widget _buildCarBindingCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded( + child: Column( + children: [ + _buildInfoRow('车牌号:', '扫码绑定'), + const SizedBox(height: 12), + _buildInfoRow('车架号:', '未知'), + const SizedBox(height: 12), + _buildInfoRow('车辆型号:', '未知'), + const SizedBox(height: 12), + _buildInfoRow('车辆品牌:', '未知'), + ], + ), + ), + const SizedBox(width: 16), + // 您图片中的图标类似储氢罐,这里用一个相近的图标代替 + Icon( + Icons.propane_tank_outlined, + size: 80, + color: Colors.blue.withOpacity(0.5), + ), + ], + ), + ), + ); + } + + // 车辆绑定卡片中的信息行 + Widget _buildInfoRow(String label, String value) { + bool isButton = value == '扫码绑定'; + return Row( + children: [ + Text(label, style: const TextStyle(color: Colors.grey, fontSize: 14)), + const SizedBox(width: 8), + isButton + ? ElevatedButton.icon( + onPressed: () { + // TODO: 实现扫码绑定逻辑 + }, + icon: const Icon(Icons.qr_code_scanner, size: 16), + label: Text(value), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + ), + ) + : Text( + value, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + ), + ], + ); + } + + /// 3. 构建车辆证件卡片 + Widget _buildCertificatesCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '车辆证件', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + _buildCertificateRow(Icons.local_gas_station, '驾驶证', '车辆驾驶证相关证件'), + const Divider(), + _buildCertificateRow(Icons.article_outlined, '营运证', '道路运输经营许可证'), + const Divider(), + _buildCertificateRow(Icons.person_pin_outlined, '加氢证', '车辆加氢许可证'), + const Divider(), + _buildCertificateRow(Icons.credit_card_outlined, '行驶证', '车辆行驶证相关证件'), + ], + ), + ), + ); + } + + // 车辆证件列表项 + Widget _buildCertificateRow(IconData icon, String title, String subtitle) { + return ListTile( + contentPadding: EdgeInsets.zero, + leading: CircleAvatar( + radius: 24, + backgroundColor: Colors.blue.withOpacity(0.1), + child: Icon(icon, color: Colors.blue, size: 28), + ), + title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), + subtitle: Text(subtitle, style: const TextStyle(color: Colors.grey, fontSize: 12)), + trailing: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8), + ), + // 图片中的图标是“查看”的意思,这里用一个类似的图标代替 + child: const Icon(Icons.find_in_page_outlined, color: Colors.black54), + ), + onTap: () { + // TODO: 查看证件详情逻辑 + }, + ); + } + + /// 4. 构建提示信息卡片 + Widget _buildTipsCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + _buildTipItem(Icons.info_outline, '请确保车辆证件齐全有效'), + const SizedBox(height: 10), + _buildTipItem(Icons.rule, '定期检查车辆状态和证件有效期'), + const SizedBox(height: 10), + _buildTipItem(Icons.headset_mic_outlined, '如有疑问请联系客服: 400-123-4567'), + ], + ), + ), + ); + } + + // 提示信息卡片中的列表项 + Widget _buildTipItem(IconData icon, String text) { + return Row( + children: [ + Icon(icon, color: Colors.blue, size: 20), + const SizedBox(width: 10), + Expanded( + child: Text(text, style: const TextStyle(fontSize: 12, color: Colors.black54)), + ), + ], + ); + } } diff --git a/ln_jq_app/lib/pages/c_page/mine/controller.dart b/ln_jq_app/lib/pages/c_page/mine/controller.dart index ca7dab7..3bebccc 100644 --- a/ln_jq_app/lib/pages/c_page/mine/controller.dart +++ b/ln_jq_app/lib/pages/c_page/mine/controller.dart @@ -1,4 +1,7 @@ import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:ln_jq_app/storage_service.dart'; + +import '../../login/view.dart'; class MineController extends GetxController with BaseControllerMixin { @override @@ -15,4 +18,10 @@ class MineController extends GetxController with BaseControllerMixin { void onClose() { super.onClose(); } + + void logout() async{ + await StorageService.to.clearLoginInfo(); + + Get.offAll(() => LoginPage()); + } } diff --git a/ln_jq_app/lib/pages/c_page/mine/view.dart b/ln_jq_app/lib/pages/c_page/mine/view.dart index b06c79e..d3c22c7 100644 --- a/ln_jq_app/lib/pages/c_page/mine/view.dart +++ b/ln_jq_app/lib/pages/c_page/mine/view.dart @@ -1,26 +1,325 @@ import 'package:flutter/material.dart'; -import 'package:getx_scaffold/getx_scaffold.dart'; - +import 'package:get/get.dart'; import 'controller.dart'; +// 假设您的 Controller 在这里,如果还没有,可以先创建一个空类 +// import 'controller.dart'; + +// 为了演示,我们先创建一个空的 Controller +// class MineController extends GetxController {} + class MinePage extends GetView { const MinePage({super.key}); - // 主视图 - Widget _buildView() { - return [ - TextX.titleLarge('我的'), - ].toColumn(mainAxisSize: MainAxisSize.min).center(); - } - @override Widget build(BuildContext context) { return GetBuilder( init: MineController(), id: 'mine', builder: (_) { - return _buildView(); + // 将所有 UI 构建逻辑放在这里 + return Scaffold( + backgroundColor: Colors.grey[100], + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildUserInfoCard(), + const SizedBox(height: 12), + _buildDriverScoreCard(), + const SizedBox(height: 12), + _buildMonthlyRecordCard(), + const SizedBox(height: 12), + _buildTipsCard(), + const SizedBox(height: 20), + _buildLogoutButton(), + ], + ), + ), + ), + ); }, ); } + + /// 1. 构建顶部用户信息卡片 + Widget _buildUserInfoCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + const CircleAvatar( + radius: 30, + backgroundColor: Colors.blue, + child: Icon(Icons.person, color: Colors.white, size: 40), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '王小龙', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 4), + const Text( + '15888332828', + style: TextStyle(color: Colors.grey, fontSize: 14), + ), + const SizedBox(height: 4), + const Text( + '未绑定车辆', + style: TextStyle(color: Colors.orange, fontSize: 12), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.blue[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.blue, width: 0.5), + ), + child: const Row( + children: [ + Icon(Icons.shield_outlined, color: Colors.blue, size: 14), + SizedBox(width: 4), + Text('已认证', style: TextStyle(color: Colors.blue, fontSize: 10, fontWeight: FontWeight.bold)), + ], + ), + ) + ], + ), + ), + const Divider(height: 1), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildStatItem('0', '违章总数'), + _buildStatItem('0', '扣分总数'), + _buildStatItem('0', '已处理'), + ], + ), + ), + ], + ), + ); + } + + // 用户信息卡片中的小统计项 + Widget _buildStatItem(String value, String label) { + return Column( + children: [ + Text(value, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 4), + Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)), + ], + ); + } + + /// 2. 构建驾驶得分卡片 + Widget _buildDriverScoreCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('驾驶得分', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const Text('本月表现', style: TextStyle(fontSize: 12, color: Colors.grey)), + const SizedBox(height: 20), + Center( + child: SizedBox( + width: 100, + height: 100, + child: Stack( + fit: StackFit.expand, + children: [ + CircularProgressIndicator( + value: 0.8, // 假设得分80% + strokeWidth: 8, + backgroundColor: Colors.grey[200], + valueColor: AlwaysStoppedAnimation(Colors.blue), + ), + Center( + child: Text( + '10', + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Colors.blue, + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 20), + _buildScoreDetailRow(Icons.directions_car, '安全驾驶', '无违章记录', true), + const Divider(), + _buildScoreDetailRow(Icons.timer, '准时率', '100%准时到达', true), + const Divider(), + _buildScoreDetailRow(Icons.thumb_up, '服务质量', '用户满意度高', true), + const Divider(), + Padding( + padding: const EdgeInsets.only(top: 12.0), + child: Row( + children: [ + const Text('优秀驾驶员', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(16), + ), + child: const Text('A+', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), + ) + ], + ), + ) + ], + ), + ), + ); + } + + // 驾驶得分卡片中的评分项 + Widget _buildScoreDetailRow(IconData icon, String title, String subtitle, bool isCompleted) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + CircleAvatar( + radius: 20, + backgroundColor: Colors.blue.withOpacity(0.1), + child: Icon(icon, color: Colors.blue, size: 24), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + Text(subtitle, style: const TextStyle(fontSize: 12, color: Colors.grey)), + ], + ), + ), + if (isCompleted) + const Icon(Icons.check_circle, color: Colors.blue), + ], + ), + ); + } + + /// 3. 构建本月记录卡片 + Widget _buildMonthlyRecordCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('本月记录', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + _buildRecordRow(Icons.autorenew, '加氢预约践行率', '100%'), + const Divider(), + _buildRecordRow(Icons.report_problem_outlined, '违章', '0起'), + const Divider(), + _buildRecordRow(Icons.warning_amber_rounded, '危险驾驶', '0起'), + const Divider(), + _buildRecordRow(Icons.car_crash_outlined, '交通事故', '0起'), + ], + ), + ), + ); + } + + // 本月记录中的列表项 + Widget _buildRecordRow(IconData icon, String title, String value) { + return ListTile( + contentPadding: EdgeInsets.zero, + leading: CircleAvatar( + radius: 20, + backgroundColor: Colors.blue.withOpacity(0.1), + child: Icon(icon, color: Colors.blue, size: 24), + ), + title: Text(title, style: const TextStyle(fontSize: 14)), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(value, style: const TextStyle(color: Colors.grey, fontSize: 14)), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_ios, size: 14, color: Colors.grey), + ], + ), + onTap: () { + // TODO: 处理点击事件 + }, + ); + } + + /// 4. 构建提示信息卡片 + Widget _buildTipsCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + _buildInfoItem(Icons.info_outline, '保持良好的驾驶习惯,提高安全评分'), + const SizedBox(height: 10), + _buildInfoItem(Icons.rule, '遵守交通规则,避免违章扣分'), + const SizedBox(height: 10), + _buildInfoItem(Icons.headset_mic_outlined, '如有疑问请联系客服: 400-123-4567'), + ], + ), + ), + ); + } + + // 提示信息卡片中的列表项 + Widget _buildInfoItem(IconData icon, String text) { + return Row( + children: [ + Icon(icon, color: Colors.blue, size: 20), + const SizedBox(width: 10), + Expanded(child: Text(text, style: const TextStyle(fontSize: 12, color: Colors.black54))), + ], + ); + } + + /// 5. 构建退出登录按钮 + Widget _buildLogoutButton() { + return ElevatedButton( + onPressed: () { + controller.logout(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red[400], + foregroundColor: Colors.white, + minimumSize: const Size(double.infinity, 48), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + elevation: 2, + ), + child: const Text('退出登录', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + ); + } } diff --git a/ln_jq_app/lib/pages/c_page/reservation/controller.dart b/ln_jq_app/lib/pages/c_page/reservation/controller.dart index d52e071..bff7011 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/controller.dart @@ -1,18 +1,113 @@ -import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; // 用于日期格式化 -class ReservationController extends GetxController with BaseControllerMixin { - @override - String get builderId => 'reservation'; +class ReservationController extends GetxController { + /// --- 状态变量 --- - ReservationController(); + // 【修改】使用 Rx 变量,让 GetX 的 Obx/GetX 能够自动监听变化,UI更新更简单 + // 日期,默认为今天 + final Rx selectedDate = DateTime.now().obs; - @override - void onInit() { - super.onInit(); + // 开始时间,默认为当前时分 + final Rx startTime = TimeOfDay.now().obs; + + // 结束时间,默认为开始时间后30分钟 + final Rx endTime = TimeOfDay.fromDateTime(DateTime.now().add(const Duration(minutes: 30))).obs; + + // 预约氢量 + final TextEditingController amountController = TextEditingController(); + + // 车牌号 + final TextEditingController plateNumberController = TextEditingController(); + + // 加氢站 + final List stationOptions = ['诚志AP银河路加氢站', '站点B', '站点C']; + final Rx selectedStation = '诚志AP银河路加氢站'.obs; + + // --- 用于UI显示的格式化字符串 (Getters) --- + String get formattedDate => DateFormat('yyyy-MM-dd').format(selectedDate.value); + // 使用 context-aware 的 format 方法 + String get formattedStartTime => startTime.value.format(Get.context!); + String get formattedEndTime => endTime.value.format(Get.context!); + + /// --- 交互方法 --- + + /// 【修改】显示 Flutter 内置的日期选择器 + void pickDate(BuildContext context) async { // 改为 async + final DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: selectedDate.value, + firstDate: DateTime.now(), // 只能从今天开始选 + lastDate: DateTime.now().add(const Duration(days: 365)), // 假设最多预约一年内 + helpText: '选择预约日期', + confirmText: '确定', + cancelText: '取消', + ); + + if (pickedDate != null && pickedDate != selectedDate.value) { + selectedDate.value = pickedDate; + } + } + + /// 【修改】显示 Flutter 内置的时间选择器 + void pickTime(BuildContext context, bool isStartTime) async { // 改为 async + TimeOfDay initialTime = isStartTime ? startTime.value : endTime.value; + + final TimeOfDay? pickedTime = await showTimePicker( + context: context, + initialTime: initialTime, + helpText: isStartTime ? '选择开始时间' : '选择结束时间', + confirmText: '确定', + cancelText: '取消', + ); + + if (pickedTime != null) { + if (isStartTime) { + startTime.value = pickedTime; + // 如果新的开始时间晚于或等于结束时间,自动将结束时间设置为开始时间后30分钟 + if ((pickedTime.hour * 60 + pickedTime.minute) >= (endTime.value.hour * 60 + endTime.value.minute)) { + final newDateTime = DateTime( + selectedDate.value.year, selectedDate.value.month, selectedDate.value.day, pickedTime.hour, pickedTime.minute) + .add(const Duration(minutes: 30)); + endTime.value = TimeOfDay.fromDateTime(newDateTime); + } + } else { + // 确保结束时间不早于开始时间 + if ((pickedTime.hour * 60 + pickedTime.minute) > (startTime.value.hour * 60 + startTime.value.minute)) { + endTime.value = pickedTime; + } else { + // 如果选择了更早的时间,给出提示 + Get.snackbar( + '时间选择错误', + '结束时间必须晚于开始时间', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } + } + } + } + + /// 提交预约 + void submitReservation() { + // TODO: 在这里执行提交预约的逻辑 + print('提交预约:'); + print('日期: $formattedDate'); + print('开始时间: $formattedStartTime'); + print('结束时间: $formattedEndTime'); + print('预约氢量: ${amountController.text}'); + print('车牌号: ${plateNumberController.text}'); + print('加氢站: ${selectedStation.value}'); + + Get.snackbar('成功', '预约已提交!', snackPosition: SnackPosition.BOTTOM); } @override void onClose() { + amountController.dispose(); + plateNumberController.dispose(); super.onClose(); } } diff --git a/ln_jq_app/lib/pages/c_page/reservation/view.dart b/ln_jq_app/lib/pages/c_page/reservation/view.dart index 9299ffd..52e8091 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/view.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/view.dart @@ -1,27 +1,358 @@ import 'package:flutter/material.dart'; -import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:get/get.dart'; +// import 'package:getx_scaffold/getx_scaffold.dart'; // 如果不使用其中的扩展,可以注释掉 import 'controller.dart'; - class ReservationPage extends GetView { const ReservationPage({super.key}); - // 主视图 - Widget _buildView() { - return [ - TextX.titleLarge('H2加氢站'), - ].toColumn(mainAxisSize: MainAxisSize.min).center(); - } - @override Widget build(BuildContext context) { - return GetBuilder( - init: ReservationController(), - id: 'reservation', - builder: (_) { - return _buildView(); - }, + // 【修改】使用 Get.put 来初始化 Controller,而不是在 GetBuilder 中。这是更推荐的做法。 + // Get.lazyPut 会在第一次使用时才创建实例。 + Get.lazyPut(() => ReservationController()); + + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildUserInfoCard(), + const SizedBox(height: 12), + _buildCarInfoCard(), + const SizedBox(height: 12), + _buildReservationFormCard(context), // 将 context 传入 + const SizedBox(height: 12), + _buildTipsCard(), + ], + ), + ), + ); + } + + /// 构建顶部用户信息卡片 + Widget _buildUserInfoCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + const CircleAvatar( + radius: 24, + backgroundColor: Colors.blue, + child: Icon(Icons.person, color: Colors.white, size: 30), + ), + const SizedBox(width: 12), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '王小龙', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + SizedBox(height: 4), + Text( + '15888332828', + style: TextStyle(color: Colors.grey, fontSize: 12), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.blue[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.blue, width: 0.5), + ), + child: const Row( + children: [ + Icon(Icons.shield_outlined, color: Colors.blue, size: 14), + SizedBox(width: 4), + Text('已认证', style: TextStyle(color: Colors.blue, fontSize: 10, fontWeight: FontWeight.bold)), + ], + ), + ) + ], + ), + ), + const Divider(height: 1, indent: 16, endIndent: 16), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildStatItem('0Kg', '累计加氢'), + _buildStatItem('0次', '加氢次数'), + ], + ), + ), + ], + ), + ); + } + + // 用户信息卡片中的小统计项 + Widget _buildStatItem(String value, String label) { + return Column( + children: [ + Text(value, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 4), + Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)), + ], + ); + } + + /// 构建车辆信息卡片 + Widget _buildCarInfoCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded( + child: Column( + children: [ + _buildInfoRow('车牌号:', '扫码绑定'), + const SizedBox(height: 12), + _buildInfoRow('剩余氢量:', '0Kg'), + const SizedBox(height: 12), + _buildInfoRow('百公里氢耗:', '0KG/100KM'), + ], + ), + ), + const SizedBox(width: 16), + Icon(Icons.propane_tank_outlined, size: 80, color: Colors.blue.withOpacity(0.5)), + ], + ), + ), + ); + } + + // 车辆信息卡片中的信息行 + Widget _buildInfoRow(String label, String value) { + bool isButton = value == '扫码绑定'; + return Row( + children: [ + Text(label, style: const TextStyle(color: Colors.grey, fontSize: 14)), + const SizedBox(width: 8), + isButton + ? ElevatedButton.icon( + onPressed: () { /* TODO: 扫码绑定逻辑 */ }, + icon: const Icon(Icons.qr_code_scanner, size: 16), + label: Text(value), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + ), + ) + : Text(value, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + ], + ); + } + + /// 构建预约表单卡片 + Widget _buildReservationFormCard(BuildContext context) { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + // 【修改】使用 Obx 包裹以自动响应 Rx 变量的变化 + child: Obx(() => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildPickerRow( + label: '日期', + value: controller.formattedDate, + icon: Icons.calendar_today_outlined, + onTap: () => controller.pickDate(context), + ), + _buildPickerRow( + label: '开始时间', + value: controller.formattedStartTime, + icon: Icons.access_time_outlined, + onTap: () => controller.pickTime(context, true), + ), + _buildPickerRow( + label: '结束时间', + value: controller.formattedEndTime, + icon: Icons.access_time_outlined, + onTap: () => controller.pickTime(context, false), + ), + _buildTextField(label: '预约氢量(kg)', controller: controller.amountController, hint: '请输入氢量(kg)', keyboardType: TextInputType.number), + _buildTextField(label: '车牌号', controller: controller.plateNumberController, hint: '请输入车牌号'), + _buildStationSelector(), + const SizedBox(height: 24), + ElevatedButton( + onPressed: controller.submitReservation, + 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), + ), + ), + ], + ), + ); + } + + // 构建加氢站选择器 + Widget _buildStationSelector() { + return Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('加氢站', style: TextStyle(color: Colors.grey[600], fontSize: 14)), + TextButton(onPressed: () { /* TODO: 刷新站点列表 */ }, child: const Text('刷新')), + ], + ), + // 【修改】使用 Obx 包裹 DropdownButtonFormField 以响应 Rx 变量 + Obx(() => DropdownButtonFormField( + value: controller.selectedStation.value, + isExpanded: true, + items: controller.stationOptions.map((String value) { + return DropdownMenuItem( + value: value, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Text(value, style: const TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(width: 8), + if (value.contains('银河路')) // 示例:给特定站点加标签 + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.orange[100], + borderRadius: BorderRadius.circular(4), + ), + child: const Text('维修中', style: TextStyle(color: Colors.orange, fontSize: 10)), + ), + ], + ), + const Text( + '江苏省苏州市常熟市东南街道银河路80号 | ¥45.00/kg', + style: TextStyle(color: Colors.grey, fontSize: 12), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ); + }).toList(), + onChanged: (newValue) { + if (newValue != null) { + controller.selectedStation.value = newValue; + } + }, + decoration: InputDecoration( + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), + contentPadding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + ), + )), + ], + ), + ); + } + + /// 构建提示信息卡片 + Widget _buildTipsCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + _buildTipItem(Icons.info_outline, '请提前30分钟到达加氢站'), + const SizedBox(height: 10), + _buildTipItem(Icons.rule, '请确保车辆证件齐全'), + const SizedBox(height: 10), + _buildTipItem(Icons.headset_mic_outlined, '如有疑问请联系客服: 400-123-4567'), + ], + ), + ), + ); + } + + // 提示信息卡片中的列表项 + Widget _buildTipItem(IconData icon, String text) { + return Row( + children: [ + Icon(icon, color: Colors.blue, size: 20), + const SizedBox(width: 10), + Expanded(child: Text(text, style: const TextStyle(fontSize: 12, color: Colors.black54))), + ], ); } } diff --git a/ln_jq_app/lib/pages/login/view.dart b/ln_jq_app/lib/pages/login/view.dart index 42d462c..3452a21 100644 --- a/ln_jq_app/lib/pages/login/view.dart +++ b/ln_jq_app/lib/pages/login/view.dart @@ -155,9 +155,58 @@ class _LoginPageState extends State with SingleTickerProviderStateMix ), SizedBox(height: 20.h), ElevatedButton( - onPressed: () { + onPressed: () async { // 司机端登录 - Get.offAll(() => BaseWidgetsPage()); + String password = controller.driverIdentityController.text; + + if (password.isEmpty) { + showToast("请输入密码"); + return; + } + + showLoading('登录中...'); + + try { + // 调用登录接口 + var responseData = await HttpService.to.post( + 'appointment/login/loginForDriver', + data: {'idNo': password}, + ); + + if (responseData == null && responseData!.data == null) { + dismissLoading(); + showToast('登录失败:无法获取凭证'); + return; + } + + try { + var result = BaseModel.fromJson(responseData.data); + + //保存用户信息 + String token = result.data['token'] ?? ''; + String idCard = result.data['idCard'] ?? ''; + String name = result.data['name'] ?? ''; + String phone = result.data['phone'] ?? ''; + + await StorageService.to.saveLoginInfo( + token: token, + userId: "", + channel: "driver", + ); + + dismissLoading(); + showToast('登录成功,欢迎您'); + + // 跳转到主页,并清除所有历史页面 + Get.offAll(() => BaseWidgetsPage()); + } catch (e) { + // 如果解析 JSON 失败 + dismissLoading(); + showToast('登录失败:数据异常'); + } + } catch (e) { + dismissLoading(); + } }, style: ElevatedButton.styleFrom( backgroundColor: AppTheme.themeColor,