diff --git a/ln_jq_app/devtools_options.yaml b/ln_jq_app/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/ln_jq_app/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/ln_jq_app/lib/common/model/base_model.dart b/ln_jq_app/lib/common/model/base_model.dart index a163834..02f020d 100644 --- a/ln_jq_app/lib/common/model/base_model.dart +++ b/ln_jq_app/lib/common/model/base_model.dart @@ -4,6 +4,7 @@ class BaseModel { final int code; // 状态码,0正常,其他异常 final bool status; // 状态布尔值,true正常,false异常 final String message; // 消息,例如 "success" + final String msg; // 消息,例如 "success" final T? data; // 核心数据,使用泛型 T,可以是任何类型 final int time; // 时间戳 final dynamic error; // 错误信息,可以是任何类型或 null @@ -12,6 +13,7 @@ class BaseModel { required this.code, required this.status, required this.message, + required this.msg, this.data, // data 可以为 null required this.time, this.error, // error 可以为 null @@ -19,9 +21,9 @@ class BaseModel { /// fromJson 工厂构造函数(重构后) factory BaseModel.fromJson( - Map json, { - T? Function(dynamic dataJson)? dataBuilder, - }) { + Map json, { + T? Function(dynamic dataJson)? dataBuilder, + }) { // 使用一个辅助函数来安全地转换类型,防止因类型不匹配(如 "0" vs 0)而崩溃 int _parseInt(dynamic value) { if (value is int) return value; @@ -42,7 +44,7 @@ class BaseModel { if (T != dynamic) { try { finalData = json['data'] as T?; - } catch(e) { + } catch (e) { // 如果直接转换失败,保持为 null,避免崩溃 finalData = null; } @@ -56,7 +58,8 @@ class BaseModel { return BaseModel( code: _parseInt(json['code']), status: json['status'] as bool? ?? false, - message: json['message']?.toString() ?? 'No message', + message: json['message'] ?? '暂不可用,请稍后', + msg: json['msg'] ?? '暂不可用,请稍后', time: _parseInt(json['time']), data: finalData, error: json['error'], @@ -68,6 +71,7 @@ class BaseModel { 'code': code, 'status': status, 'message': message, + 'msg': msg, 'time': time, 'data': data, 'error': error, diff --git a/ln_jq_app/lib/common/styles/theme.dart b/ln_jq_app/lib/common/styles/theme.dart index 97f6426..e5ec909 100644 --- a/ln_jq_app/lib/common/styles/theme.dart +++ b/ln_jq_app/lib/common/styles/theme.dart @@ -10,6 +10,10 @@ class AppTheme { static const String test_service_url = "http://beta-esg.api.lnh2e.com/"; static const String release_service_url = ""; + //加氢站相关查询 + static const String jiaqing_service_url = "https://beta.lnh2e.com/api/lingniu-manager-v1/v1/"; + //车辆信息 + static const String car_service_url = "http://47.99.166.38:20000/"; static const Color secondaryColor = Colors.orange; diff --git a/ln_jq_app/lib/main.dart b/ln_jq_app/lib/main.dart index bf362a7..22f6098 100644 --- a/ln_jq_app/lib/main.dart +++ b/ln_jq_app/lib/main.dart @@ -60,7 +60,7 @@ void initHttpSet() { HttpService.to.setOnResponseHandler((response) async { try { final baseModel = BaseModel.fromJson(response.data); - if (baseModel.code == 0) { + if (baseModel.code == 0 || baseModel.code == 200) { return null; } else if (baseModel.code == 401) { await StorageService.to.clearLoginInfo(); 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 dbe0a25..4e1625d 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/controller.dart @@ -2,14 +2,17 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:getx_scaffold/common/common.dart'; import 'package:getx_scaffold/common/services/http.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/common/styles/theme.dart'; import 'package:ln_jq_app/pages/b_page/site/controller.dart'; -import 'package:ln_jq_app/storage_service.dart'; // 用于日期格式化 +import 'package:ln_jq_app/storage_service.dart'; -class ReservationController extends GetxController { - /// --- 状态变量 --- +import '../../../common/styles/theme.dart'; // 用于日期格式化 + +class ReservationController extends GetxController with BaseControllerMixin { + @override + String get builderId => 'reservation'; // 【修改】使用 Rx 变量,让 GetX 的 Obx/GetX 能够自动监听变化,UI更新更简单 // 日期,默认为今天 @@ -27,7 +30,7 @@ class ReservationController extends GetxController { final TextEditingController amountController = TextEditingController(); // 车牌号 - final TextEditingController plateNumberController = TextEditingController(); + final TextEditingController plateNumberController = TextEditingController(text: "浙F"); // 加氢站 final List stationOptions = [ @@ -152,7 +155,6 @@ class ReservationController extends GetxController { }, ); - // 安全校验 if (response == null || response.data == null) { showToast('暂时无法获取预约数据'); hasReservationData = false; @@ -163,12 +165,8 @@ class ReservationController extends GetxController { final baseModel = BaseModel.fromJson(response.data); if (baseModel.code == 0 && baseModel.data != null) { - // 【核心修改】处理接口返回的列表数据 final dataMap = baseModel.data as Map; - final List listFromServer = dataMap['list'] ?? []; - - // 使用 .map() 遍历列表,将每个 item 转换为一个 ReservationModel 对象 reservationList = listFromServer.map((item) { return ReservationModel.fromJson(item as Map); }).toList(); @@ -176,18 +174,15 @@ class ReservationController extends GetxController { // 根据列表是否为空来更新 hasReservationData 状态 hasReservationData = reservationList.isNotEmpty; } else { - // 接口返回业务错误 showToast(baseModel.message); hasReservationData = false; reservationList = []; // 清空列表 } } catch (e) { - // 捕获网络或解析异常 showToast('获取预约数据失败'); hasReservationData = false; reservationList = []; // 清空列表 } finally { - // 无论成功失败,最后都要关闭加载动画并更新UI dismissLoading(); } @@ -344,6 +339,103 @@ class ReservationController extends GetxController { ); } + String phone = ""; + String name = ""; + String leftHydrogen = ""; + String workEfficiency = ""; + //累计数据 + String fillingWeight = ""; + String fillingTimes = ""; + String plateNumber = "沪AGZ8967"; + + @override + void onInit() { + phone = StorageService.to.phone ?? ""; + name = StorageService.to.name ?? ""; + + getCatinfo(); + getJqinfo(); + // getSiteList(); + super.onInit(); + } + + void getJqinfo() async { + try { + HttpService.to.setBaseUrl(AppTheme.test_service_url); + var responseData = await HttpService.to.get( + 'appointment/truck/history-filling-summary?vin=LSFGL23Z2ND214377' + ); + if (responseData == null || responseData.data == null) { + showToast('服务暂不可用,请稍后'); + return; + } + + var result = BaseModel.fromJson(responseData.data); + + fillingWeight = "${result.data["fillingWeight"]}${result.data["fillingWeightUnit"]}"; + fillingTimes = "${result.data["fillingTimes"]}${result.data["fillingTimesUnit"]}"; + + updateUi(); + } catch (e) { + } finally { + HttpService.to.setBaseUrl(AppTheme.test_service_url); + } + } + + void getCatinfo() async { + try { + HttpService.to.setBaseUrl(AppTheme.car_service_url); + var responseData = await HttpService.to.post( + 'VehicleData/getHydrogenInfoByPlateNumber', + data: { + 'userName': "xll@lingniu", + 'password': "4q%3!l6s0p", + 'plateNumber': plateNumber, + }, + ); + if (responseData == null || responseData.data == null) { + showToast('服务暂不可用,请稍后'); + return; + } + + var result = BaseModel.fromJson(responseData.data); + + leftHydrogen = result.data["leftHydrogen"].toString(); + workEfficiency = result.data["workEfficiency"].toString(); + + updateUi(); + } catch (e) { + } finally { + HttpService.to.setBaseUrl(AppTheme.test_service_url); + } + } + + void getSiteList() async { + final originalHeaders = Map.from(HttpService.to.dio.options.headers); + try { + HttpService.to.setBaseUrl(AppTheme.jiaqing_service_url); + HttpService.to.dio.options.headers['appId'] = '97ad10eeb6b346f79e0d6ffd81e4d3c3'; + + var responseData = await HttpService.to.get("hydrogen/queryHydrogenSiteInfo"); + + if (responseData == null && responseData!.data == null) { + showToast('暂时无法获取站点信息'); + return; + } + + try { + var result = BaseModel.fromJson(responseData.data); + // showToast(result.data["data"].toString()); + } catch (e) { + showToast('数据异常'); + } + } catch (e) { + } finally { + HttpService.to.setBaseUrl(AppTheme.test_service_url); + HttpService.to.dio.options.headers = originalHeaders; + } + } + @override void onClose() { amountController.dispose(); 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 d4ba0d1..7b7474a 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/view.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/view.dart @@ -1,10 +1,9 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:getx_scaffold/common/index.dart'; -import 'package:getx_scaffold/common/widgets/text_x.dart'; -// import 'package:getx_scaffold/getx_scaffold.dart'; // 如果不使用其中的扩展,可以注释掉 +import 'package:getx_scaffold/getx_scaffold.dart'; +import '../../../storage_service.dart'; import 'controller.dart'; class ReservationPage extends GetView { @@ -12,33 +11,35 @@ class ReservationPage extends GetView { @override Widget build(BuildContext context) { - // 【修改】使用 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(), - ], - ), - ), + return GetBuilder( + init: ReservationController(), + id: 'reservation', + builder: (_) { + return Scaffold( + backgroundColor: Colors.grey[100], + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildUserInfoCard(), + const SizedBox(height: 16), + _buildCarInfoCard(), + const SizedBox(height: 16), + _buildReservationFormCard(context), + ], + ), + ), + ); + }, ); } - /// 构建顶部用户信息卡片 + /// 构建用户信息卡片 Widget _buildUserInfoCard() { return Card( - elevation: 2, + elevation: 2, // 轻微的阴影 + shadowColor: Colors.black.withOpacity(0.1), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Column( children: [ @@ -52,17 +53,17 @@ class ReservationPage extends GetView { child: Icon(Icons.person, color: Colors.white, size: 30), ), const SizedBox(width: 12), - const Expanded( + Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '王小龙', + controller.name, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), SizedBox(height: 4), Text( - '15888332828', + controller.phone, style: TextStyle(color: Colors.grey, fontSize: 12), ), ], @@ -98,7 +99,10 @@ class ReservationPage extends GetView { padding: const EdgeInsets.symmetric(vertical: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [_buildStatItem('0Kg', '累计加氢'), _buildStatItem('0次', '加氢次数')], + children: [ + _buildStatItem(controller.fillingWeight, '累计加氢'), + _buildStatItem(controller.fillingTimes, '加氢次数'), + ], ), ), ], @@ -129,11 +133,11 @@ class ReservationPage extends GetView { Expanded( child: Column( children: [ - _buildInfoRow('车牌号:', '扫码绑定'), + _buildInfoRow('车牌号: ${controller.plateNumber}', '扫码绑定'), const SizedBox(height: 12), - _buildInfoRow('剩余氢量:', '0Kg'), + _buildInfoRow('剩余氢量:', '${controller.leftHydrogen}Kg'), const SizedBox(height: 12), - _buildInfoRow('百公里氢耗:', '0KG/100KM'), + _buildInfoRow('百公里氢耗:', '${controller.workEfficiency}KG/100KM'), ], ), ), @@ -215,7 +219,8 @@ class ReservationPage extends GetView { _buildTextField( label: '车牌号', controller: controller.plateNumberController, - hint: '请输入车牌号', + hint: '请输入车牌号', // 修改提示文案 + enabled: false, // 设置为不可编辑 ), _buildStationSelector(), const SizedBox(height: 24), @@ -310,6 +315,7 @@ class ReservationPage extends GetView { required TextEditingController controller, required String hint, TextInputType? keyboardType, + bool enabled = true, }) { return Padding( padding: const EdgeInsets.only(bottom: 12.0), @@ -321,10 +327,14 @@ class ReservationPage extends GetView { TextFormField( controller: controller, keyboardType: keyboardType, + enabled: enabled, // 使用 enabled 参数 decoration: InputDecoration( hintText: hint, border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + // 【新增】禁用时,使用填充色 + filled: !enabled, + fillColor: Colors.grey[100], ), ), ], @@ -344,7 +354,7 @@ class ReservationPage extends GetView { Text('加氢站', style: TextStyle(color: Colors.grey[600], fontSize: 14)), TextButton( onPressed: () { - showToast("msg"); + controller.getSiteList(); }, child: const Text('刷新'), ), @@ -382,18 +392,13 @@ class ReservationPage extends GetView { ), iconStyleData: const IconStyleData( icon: Icon(Icons.arrow_drop_down), - iconSize: 24, + iconSize: 20, ), dropdownStyleData: DropdownStyleData( maxHeight: 200, decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), - offset: const Offset(0, -5), - elevation: 8, - ), - menuItemStyleData: const MenuItemStyleData( - height: 55, // 控制每个下拉选项的高度 - padding: EdgeInsets.symmetric(horizontal: 14), ), + menuItemStyleData: const MenuItemStyleData(height: 40), ), ), ), @@ -402,78 +407,12 @@ class ReservationPage extends GetView { ); } - ///用于构建 DropdownMenuItem 内部的自定义 Widget - Widget _buildDropdownItem(String value) { - bool isSpecial = value.contains('银河路'); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - children: [ - Flexible( - child: Text( - value, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), - overflow: TextOverflow.ellipsis, - ), - ), - if (isSpecial) ...[ - const SizedBox(width: 8), - 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 SizedBox(height: 2), - const Text( - '江苏省苏州市常熟市东南街道银河路80号 | ¥45.00/kg', - style: TextStyle(color: Colors.grey, fontSize: 11), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ], - ); - } - - /// 构建提示信息卡片 - 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) { + Widget _buildDropdownItem(String item) { return Row( children: [ - Icon(icon, color: Colors.blue, size: 20), + const Icon(Icons.local_gas_station_outlined, size: 18, color: Colors.grey), const SizedBox(width: 10), - Expanded( - child: Text(text, style: const TextStyle(fontSize: 12, color: Colors.black54)), - ), + Text(item, style: const TextStyle(fontSize: 14)), ], ); } diff --git a/ln_jq_app/lib/pages/login/view.dart b/ln_jq_app/lib/pages/login/view.dart index b25a5f7..844c495 100644 --- a/ln_jq_app/lib/pages/login/view.dart +++ b/ln_jq_app/lib/pages/login/view.dart @@ -187,11 +187,20 @@ class _LoginPageState extends State with SingleTickerProviderStateMix String idCard = result.data['idCard'] ?? ''; String name = result.data['name'] ?? ''; String phone = result.data['phone'] ?? ''; + /*{ + "token": "a615cf38a066473ebf8e9a956fc51928", + "idCard": "330324200010241024", + "name": "王小龙", + "phone": "15888331828" + }*/ await StorageService.to.saveLoginInfo( token: token, - userId: phone, + userId: "", channel: "driver", + idCard: idCard, + name: name, + phone: phone, ); dismissLoading(); diff --git a/ln_jq_app/lib/storage_service.dart b/ln_jq_app/lib/storage_service.dart index f19af6c..7636bf2 100644 --- a/ln_jq_app/lib/storage_service.dart +++ b/ln_jq_app/lib/storage_service.dart @@ -14,8 +14,11 @@ class StorageService extends GetxService { // --- 定义存储时使用的键名 (Key) --- static const String _tokenKey = 'user_token'; static const String _userIdKey = 'user_id'; - // 定义登录渠道的存储键名 static const String _channelKey = 'login_channel'; + // 为新字段增加键名 + static const String _nameKey = 'user_name'; + static const String _phoneKey = 'user_phone'; + static const String _idCardKey = 'user_id_card'; // 提供一个静态的 'to' 方法,方便全局访问 static StorageService get to => Get.find(); @@ -40,6 +43,16 @@ class StorageService extends GetxService { /// 获取 UserId String? get userId => _box.read(_userIdKey); + /// 获取 Name + String? get name => _box.read(_nameKey); + + /// 获取 Phone + String? get phone => _box.read(_phoneKey); + + /// 获取 ID Card + String? get idCard => _box.read(_idCardKey); + + ///获取当前登录渠道 /// 从存储中读取字符串,并转换为枚举类型 LoginChannel get loginChannel { @@ -59,18 +72,27 @@ class StorageService extends GetxService { required String token, required String userId, required String channel, // 接收一个字符串类型的渠道 + String? name, + String? phone, + String? idCard, }) async { await _box.write(_tokenKey, token); await _box.write(_userIdKey, userId); - // 将渠道字符串存入本地 await _box.write(_channelKey, channel); + // 保存新增的字段,如果为 null, GetStorage 会移除该键 + await _box.write(_nameKey, name); + await _box.write(_phoneKey, phone); + await _box.write(_idCardKey, idCard); } ///删除用户信息,增加删除渠道信息 Future clearLoginInfo() async { await _box.remove(_tokenKey); await _box.remove(_userIdKey); - // 退出登录时,一并清除渠道信息 await _box.remove(_channelKey); + // 退出时一并清除新增的字段 + await _box.remove(_nameKey); + await _box.remove(_phoneKey); + await _box.remove(_idCardKey); } }