Merge branch 'dev'

v1.1.0
This commit is contained in:
2025-11-28 10:12:44 +08:00
18 changed files with 702 additions and 446 deletions

View File

@@ -45,8 +45,6 @@ class B_BaseWidgetsPage extends GetView<B_BaseWidgetsController> {
label: '加氢预约',
icon: AntdIcon.orderedlist,
selectedIcon: AntdIcon.calendar_fill,
badge: '99+',
dot: true,
),
NavigationItemModel(
label: '站点信息',

View File

@@ -71,7 +71,7 @@ class ReservationPage extends GetView<ReservationController> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildHeaderStat(controller.costPrice, '氢气价格'),
_buildHeaderStat(controller.customerPrice, '氢气价格'),
_buildHeaderStat(controller.timeStr, '营业时间'),
_buildHeaderStat('98%', '设备状态'),
],
@@ -119,7 +119,7 @@ class ReservationPage extends GetView<ReservationController> {
// --- 价格信息 ---
_buildSectionTitle('价格信息'),
_buildDisplayField(label: '氢气价格 (元/kg)', value: controller.costPrice),
// _buildDisplayField(label: '氢气价格 (元/kg)', value: controller.costPrice),
_buildDisplayField(label: '官方价格 (元/kg)', value: controller.customerPrice),
const SizedBox(height: 16),

View File

@@ -51,14 +51,11 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
label: '地图',
icon: AntdIcon.location,
selectedIcon: AntdIcon.location_fill,
dot: false,
),
NavigationItemModel(
label: '加氢预约',
icon: AntdIcon.orderedlist,
selectedIcon: AntdIcon.calendar_fill,
badge: '99+',
dot: true,
),
NavigationItemModel(
label: '车辆信息',

View File

@@ -2,6 +2,7 @@ import 'package:get/get.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/vehicle_info.dart';
import 'package:ln_jq_app/pages/qr_code/view.dart';
import 'package:ln_jq_app/storage_service.dart';
import 'certificate_viewer_page.dart';
@@ -27,6 +28,33 @@ class CarInfoController extends GetxController with BaseControllerMixin {
super.onInit();
getUserBindCarInfo();
}
@override
void onReady() {
super.onReady();
// 如果未绑定车辆,且本次会话尚未提示过,则弹出提示
if (!StorageService.to.hasShownBindVehicleDialog) {
Future.delayed(const Duration(milliseconds: 500), () {
DialogX.to.showConfirmDialog(
title: '当前尚未绑定车辆',
confirmText: "去绑定",
cancelText: "稍后",
onConfirm: () {
doQrCode();
},
);
// 标记为已显示,本次会话不再提示
StorageService.to.markBindVehicleDialogAsShown();
});
}
}
void doQrCode() async {
var scanResult = await Get.to(() => const QrCodePage());
if (scanResult == true) {
getUserBindCarInfo();
refreshAppui();
}
}
void getUserBindCarInfo() async {
if (StorageService.to.hasVehicleInfo) {

View File

@@ -173,12 +173,7 @@ class CarInfoPage extends GetView<CarInfoController> {
isButton
? GestureDetector(
onTap: () async {
//判断是否绑定成功
var scanResult = await Get.to(() => const QrCodePage());
if (scanResult == true) {
controller.getUserBindCarInfo();
refreshAppui();
}
controller.doQrCode();
},
child: Container(
margin: EdgeInsetsGeometry.only(left: 10.w),

View File

@@ -4,124 +4,133 @@ import 'dart:convert';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:geolocator/geolocator.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:permission_handler/permission_handler.dart'; // 确保引用这个做权限请求
import 'package:permission_handler/permission_handler.dart';
class MapController extends GetxController with BaseControllerMixin {
class MapController extends GetxController with BaseControllerMixin ,WidgetsBindingObserver{
@override
String get builderId => 'map';
InAppWebViewController? webViewController;
StreamSubscription<Position>? positionStream;
final RxBool isLocationPermissionGranted = false.obs;
// 缓存最后一次的位置,防止 WebView 加载慢于定位
// --- 状态标志 ---
final RxBool isLocationPermissionGranted = false.obs;
final RxBool isMapReady = false.obs; // 跟踪地图是否加载完成
Position? _lastKnownPosition;
// 是否已经初始化过地图中心
bool _hasInitializedMapCenter = false;
@override
void onInit() {
super.onInit();
WidgetsBinding.instance.addObserver(this); // 注册监听
requestLocationPermission();
}
@override
void onClose() {
WidgetsBinding.instance.removeObserver(this); // 移除监听
positionStream?.cancel();
super.onClose();
}
// 请求权限
void requestLocationPermission() async {
var status = await Permission.locationWhenInUse.request();
print("权限状态: $status"); // 在控制台看这个输出
if (status.isGranted) {
isLocationPermissionGranted.value = true;
showToast('定位权限已获取');
_startLocationTracking();
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// 回到前台,如果之前有权限且开启过,恢复定位
if(isLocationPermissionGranted.value) _startLocationTracking();
} else if (state == AppLifecycleState.paused) {
// 切到后台,取消定位流
positionStream?.cancel();
}
else if (status.isPermanentlyDenied) {
// iOS 特性:如果你之前点过“不允许”,之后再请求都会直接进这里
isLocationPermissionGranted.value = false;
// 引导用户去设置页面开启
openAppSettings();
}
else {
// 第一次被拒绝,或者其他情况
isLocationPermissionGranted.value = false;
showErrorToast('需要定位权限才能显示您的位置');
}
}
// 开启定位监听
void _startLocationTracking() async {
// 再次检查服务是否开启
/// WebView 创建完成时调用
void onWebViewCreated(InAppWebViewController controller) {
webViewController = controller;
// 添加 JS Handler 来监听来自网页的 'mapReady' 事件
controller.addJavaScriptHandler(
handlerName: 'mapReady',
callback: (args) {
print("Flutter<-JS: Map is ready, starting location tracking.");
isMapReady.value = true;
// 地图就绪后,如果已有权限和缓存的定位,则开始定位
if (isLocationPermissionGranted.value) {
_startLocationTracking();
}
},
);
}
/// 优化后的权限请求函数
void requestLocationPermission() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
showErrorToast('请开启手机的定位服务');
return;
}
var status = await Permission.locationWhenInUse.request();
switch (status) {
case PermissionStatus.granted:
isLocationPermissionGranted.value = true;
// 权限已获取,如果地图也已就绪,则开始定位
if (isMapReady.value) {
_startLocationTracking();
}
break;
case PermissionStatus.denied:
showErrorToast('需要定位权限才能显示您的位置');
break;
case PermissionStatus.permanentlyDenied:
showErrorToast('定位权限已被永久拒绝,请到设置中手动开启');
openAppSettings();
break;
default:
showErrorToast('获取定位权限时发生未知错误');
break;
}
}
/// 开始定位监听 (不再需要 delay)
void _startLocationTracking() async {
if (!isLocationPermissionGranted.value ||
!await Geolocator.isLocationServiceEnabled()) {
return;
}
const LocationSettings locationSettings = LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 50, // 移动更新范围
distanceFilter: 10,
);
// 获取一次当前位置,用于快速初始化中心
Geolocator.getCurrentPosition().then((position) {
// 立即获取一次当前位置,用于快速初始化
try {
Position position = await Geolocator.getCurrentPosition();
_lastKnownPosition = position;
_syncLocationToMap(position, isInit: true);
});
_syncLocationToMap(position);
} catch (e) {
print("Error getting initial position: $e");
}
// 持续监听
// 持续监听位置变化
positionStream?.cancel(); // 先取消旧的监听
positionStream = Geolocator.getPositionStream(locationSettings: locationSettings)
.listen((Position position) {
_lastKnownPosition = position;
_syncLocationToMap(position, isInit: false);
});
_lastKnownPosition = position;
_syncLocationToMap(position);
});
}
// 将位置同步给 JS
void _syncLocationToMap(Position position, {required bool isInit}) {
if (webViewController == null) return;
// 将 Flutter 的定位数据同步给 JS
void _syncLocationToMap(Position position) {
if (webViewController == null || !isMapReady.value) return;
// 只有当 heading 有效且大于 0 时才传给 JS否则传 null让 JS 保持上一次的角度
String headingVal = (position.heading > 0) ? "${position.heading}" : "null";
// 如果是第一次定位,或者 WebView 刚加载完且从未设置过中心
if (isInit || !_hasInitializedMapCenter) {
webViewController!.evaluateJavascript(
source: "initLocation(${position.latitude}, ${position.longitude})"
);
_hasInitializedMapCenter = true;
} else {
// 后续只更新图标位置,不强行移动地图中心(防止用户拖动地图看别处时被拉回)
webViewController!.evaluateJavascript(
source: "updateMyLocation(${position.latitude}, ${position.longitude}, ${position.heading})"
);
}
webViewController!.evaluateJavascript(
source:
"updateMyLocation(${position.latitude}, ${position.longitude}, ${position.heading})",
);
}
// WebView 加载完成回调
void onWebViewLoadStop() {
// 页面加载完了,如果我们已经拿到了定位数据,立刻设置地图中心
if (_lastKnownPosition != null) {
_syncLocationToMap(_lastKnownPosition!, isInit: true);
}
// 如果业务需要,可以在这里加载默认的推荐路径
// loadRecommendedRoute();
}
// 模拟:获取你的后台推荐路径
void loadRecommendedRoute() {
// 示例数据
List<List<double>> routePoints = [
[116.397428, 39.90923],
[116.398000, 39.90950],
[116.400000, 39.91000],
];
String jsonPoints = jsonEncode(routePoints);
webViewController?.evaluateJavascript(source: "drawCustomRoute($jsonPoints)");
}
}
}

View File

@@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
@@ -10,23 +11,21 @@ class MapPage extends GetView<MapController> {
return Stack(
children: [
InAppWebView(
// 确保 pubspec.yaml 中声明了 assets/html/map.html
initialFile: 'assets/html/map.html',
initialSettings: InAppWebViewSettings(
isInspectable: true,
geolocationEnabled: true,
// 允许 JS 弹窗 (Alert) 用于调试
javaScriptCanOpenWindowsAutomatically: true,
// 既然完全依赖 Flutter 定位,建议把 WebView 的定位彻底关掉,防止 JS 意外触发
geolocationEnabled: false,
javaScriptEnabled: true,
// Android 混合开发推荐配置
useHybridComposition: true, // 提升地图渲染性能(重要)
allowFileAccessFromFileURLs: true, // 允许本地 html 访问本地资源
allowUniversalAccessFromFileURLs: true,
),
onWebViewCreated: (c) {
controller.webViewController = c;
},
onLoadStop: (c, url) {
// 通知 Controller 页面加载完毕
controller.onWebViewLoadStop();
},
// 当 WebView 创建完成后,将 controller 实例传递给我们的 MapController
onWebViewCreated: controller.onWebViewCreated,
onConsoleMessage: (controller, consoleMessage) {
// 方便在 Flutter 控制台 JS 日志
// 方便在 Flutter 控制台查看来自 JS 日志
print("JS Log: ${consoleMessage.message}");
},
),

View File

@@ -9,6 +9,7 @@ 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/vehicle_info.dart';
import 'package:ln_jq_app/pages/b_page/site/controller.dart';
import 'package:ln_jq_app/pages/qr_code/view.dart';
import 'package:ln_jq_app/storage_service.dart';
import '../../../common/styles/theme.dart';
@@ -118,68 +119,55 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
}
void pickTime(BuildContext context, bool isStartTime) {
// 确定当前操作的时间和初始值
// 1. 确定当前操作的时间和初始值
TimeOfDay initialTime = isStartTime ? startTime.value : endTime.value;
DateTime now = DateTime.now();
// 计算最小可选时间
DateTime? minimumDateTime; // 默认为 null即可选任意时间
// 2. 准备小时和分钟的数据源
List<int> hours = List<int>.generate(24, (index) => index);
List<int> minutes = [0, 30];
// 获取当前选择的日期(年月日)
final selectedDay = DateTime(
selectedDate.value.year,
selectedDate.value.month,
selectedDate.value.day,
);
// 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);
if (isStartTime) {
// 如果是选择开始时间并且日期是 今天
if (selectedDay.isAtSameMomentAs(today)) {
minimumDateTime = now; // 最小可选时间就是现在
}
} else {
// 如果是选择结束时间
// 将开始时间转换为 DateTime 对象
final startDateTime = DateTime(
selectedDate.value.year,
selectedDate.value.month,
selectedDate.value.day,
startTime.value.hour,
startTime.value.minute,
);
// 结束时间的最小值必须晚于开始时间
minimumDateTime = startDateTime;
// 如果日期是今天,并且开始时间早于现在,那么结束时间的最小值也应该是现在
if (selectedDay.isAtSameMomentAs(today) && startDateTime.isBefore(now)) {
minimumDateTime = now;
if (selectedDay.isAtSameMomentAs(today)) {
if (initialHour < now.hour || (initialHour == now.hour && initialMinute < now.minute)) {
initialHour = now.hour;
if (now.minute > 30) {
// 如果当前分钟>30, 则进位到下一小时的0分
initialHour = (now.hour + 1) % 24;
initialMinute = 0;
} else {
// 否则取30分
initialMinute = 30;
}
}
}
// 初始显示时间
DateTime initialDateTime = DateTime(
selectedDate.value.year,
selectedDate.value.month,
selectedDate.value.day,
initialTime.hour,
initialTime.minute,
);
// 重新获取校准后的索引
minuteIndex = minutes.indexOf(initialMinute);
// 确保初始时间不早于最小时间
if (minimumDateTime != null && initialDateTime.isBefore(minimumDateTime)) {
initialDateTime = minimumDateTime;
}
DateTime tempTime = initialDateTime;
// 4. 创建 FixedExtentScrollController 来控制滚轮的初始位置
final FixedExtentScrollController hourController =
FixedExtentScrollController(initialItem: hours.indexOf(initialHour));
final FixedExtentScrollController minuteController =
FixedExtentScrollController(initialItem: minuteIndex);
// 5. 存储临时选择的值
int tempHour = initialHour;
int tempMinute = initialMinute;
Get.bottomSheet(
Container(
height: 300,
padding: const EdgeInsets.only(top: 6.0),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
@@ -196,75 +184,55 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
children: [
CupertinoButton(
onPressed: () => Get.back(),
child: const Text(
'取消',
style: TextStyle(color: CupertinoColors.systemGrey),
),
child: const Text('取消', style: TextStyle(color: CupertinoColors.systemGrey)),
),
CupertinoButton(
onPressed: () {
final pickedTempTime = TimeOfDay.fromDateTime(tempTime);
final pickedTempTime = TimeOfDay(hour: tempHour, minute: tempMinute);
final now = DateTime.now();
// 验证条件1不能选择过去的时间
// 这个验证只在选择【今天】的【开始时间】或【结束时间】时有意义
final selectedDay = DateTime(
// --- 合并和简化校验逻辑 ---
final selectedDateTime = DateTime(
selectedDate.value.year,
selectedDate.value.month,
selectedDate.value.day,
);
final today = DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
pickedTempTime.hour,
pickedTempTime.minute,
);
if (selectedDay.isAtSameMomentAs(today) &&
tempTime.isBefore(DateTime.now())) {
// 验证1: 不能选择过去的时间(留出一分钟缓冲)
if (selectedDateTime.isBefore(now.subtract(const Duration(minutes: 1)))) {
showToast('不能选择过去的时间');
return; // 中断执行,不关闭弹窗
return;
}
//结束时间必须晚于开始时间
// 验证2: 结束时间必须晚于开始时间
if (isStartTime) {
// 已有的结束时间 比较
final pickedStartInMinutes =
pickedTempTime.hour * 60 + pickedTempTime.minute;
final endInMinutes =
endTime.value.hour * 60 + endTime.value.minute;
startTime.value = pickedTempTime;
final startInMinutes = pickedTempTime.hour * 60 + pickedTempTime.minute;
final endInMinutes = endTime.value.hour * 60 + endTime.value.minute;
if (pickedStartInMinutes >= endInMinutes) {
// 整结束时间
startTime.value = pickedTempTime;
final newEndDateTime = tempTime.add(
const Duration(minutes: 30),
);
// 如果新的开始时间大于等于结束时间,自动将结束时间设置为开始时间+30分钟
if (startInMinutes >= endInMinutes) {
final newEndDateTime = selectedDateTime.add(const Duration(minutes: 30));
endTime.value = TimeOfDay.fromDateTime(newEndDateTime);
} else {
//设置开始时间
startTime.value = pickedTempTime;
}
} else {
// 如果当前正在设置结束时间,我们来和已有的开始时间比较
final pickedEndInMinutes =
pickedTempTime.hour * 60 + pickedTempTime.minute;
final startInMinutes =
startTime.value.hour * 60 + startTime.value.minute;
} else { // 正在设置结束时间
final startInMinutes = startTime.value.hour * 60 + startTime.value.minute;
final endInMinutes = pickedTempTime.hour * 60 + pickedTempTime.minute;
if (pickedEndInMinutes <= startInMinutes) {
if (endInMinutes <= startInMinutes) {
showToast('结束时间必须晚于开始时间');
return; // 中断执行,不关闭弹窗
} else {
endTime.value = pickedTempTime;
return;
}
endTime.value = pickedTempTime;
}
Get.back();
},
child: const Text(
'确认',
style: TextStyle(
color: AppTheme.themeColor,
fontWeight: FontWeight.bold,
),
style: TextStyle(color: AppTheme.themeColor, fontWeight: FontWeight.bold),
),
),
],
@@ -272,14 +240,36 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
),
const Divider(height: 1, color: Color(0xFFE5E5E5)),
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.time,
use24hFormat: true,
initialDateTime: initialDateTime,
minimumDate: minimumDateTime,
onDateTimeChanged: (DateTime newTime) {
tempTime = newTime;
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 小时选择器
Expanded(
child: CupertinoPicker(
scrollController: hourController,
itemExtent: 32.0,
onSelectedItemChanged: (index) {
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(),
),
),
],
),
),
],
@@ -289,6 +279,11 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
);
}
// 用于存储上一次成功预约的信息
ReservationModel? lastSuccessfulReservation;
/// 提交预约
void submitReservation() async {
if (plateNumber.isEmpty) {
@@ -300,11 +295,31 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
showToast("请输入需要预约的氢量");
return;
}
double ampuntDouble = (double.tryParse(ampuntStr) ?? 0.0);
if (ampuntDouble == 0) {
showToast("请输入需要预约的氢量");
return;
}
if (ampuntDouble > (double.tryParse(difference) ?? 0.0)) {
showToast('当前最大可预约氢量为${difference}(KG)');
return;
}
if (selectedStationId.value == null || selectedStationId.value!.isEmpty) {
showToast("请先选择加氢站");
return;
}
// 将选择的日期和时间组合成一个完整的 DateTime 对象
final dateStr = formattedDate;
final startTimeStr = '$dateStr ${formattedStartTime}:00';
if (lastSuccessfulReservation != null &&
lastSuccessfulReservation!.id == selectedStationId.value &&
lastSuccessfulReservation!.startTime == startTimeStr) {
showToast("请勿重复提交相同的预约");
return;
}
// 将选择的日期和时间组合成一个完整的 DateTime 对象
final reservationStartDateTime = DateTime(
selectedDate.value.year,
selectedDate.value.month,
@@ -353,6 +368,32 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
var result = BaseModel.fromJson(responseData.data);
if (result.code == 0) {
showSuccessToast("预约成功");
// 预约成功后,保存当前预约信息
lastSuccessfulReservation = ReservationModel(
id: selectedStationId.value!,
hydAmount: ampuntStr,
startTime: startTimeStr,
endTime: endTimeStr,
stationName: selectedStation.name,
plateNumber: '',
amount: '',
time: '',
contactPerson: '',
contactPhone: '',
contacts: '',
phone: '',
date: '',
state: '',
stateName: '',
addStatus: '',
addStatusName: '',
);
//打开预约列表
Future.delayed(const Duration(milliseconds: 800), () {
getReservationList();
});
} else {
showErrorToast(result.message);
}
@@ -575,12 +616,14 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
);
}
String leftHydrogen = "0";
String workEfficiency = "0";
String fillingWeight = "0";
String fillingTimes = "0";
String plateNumber = "";
String vin = "";
String leftHydrogen = "0";
num maxHydrogen = 0;
String difference = "";
@override
bool get listenLifecycleEvent => true;
@@ -601,11 +644,20 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
plateNumber = bean.plateNumber;
vin = bean.vin;
plateNumberController = TextEditingController(text: plateNumber);
maxHydrogen = bean.maxHydrogen;
getCatinfo();
getJqinfo();
}
}
void doQrCode() async {
var scanResult = await Get.to(() => const QrCodePage());
if (scanResult == true) {
getUserBindCarInfo();
refreshAppui();
}
}
void getJqinfo() async {
try {
HttpService.to.setBaseUrl(AppTheme.test_service_url);
@@ -651,6 +703,9 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
leftHydrogen = result.data["leftHydrogen"].toString();
workEfficiency = result.data["workEfficiency"].toString();
final leftHydrogenNum = double.tryParse(leftHydrogen) ?? 0.0;
difference = (maxHydrogen - leftHydrogenNum).toStringAsFixed(2);
updateUi();
} catch (e) {
} finally {
@@ -707,6 +762,22 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
dismissLoading();
HttpService.to.setBaseUrl(AppTheme.test_service_url);
HttpService.to.dio.options.headers = originalHeaders;
// 如果未绑定车辆,且本次会话尚未提示过,则弹出提示
if (!StorageService.to.hasShownBindVehicleDialog) {
Future.delayed(const Duration(milliseconds: 500), () {
DialogX.to.showConfirmDialog(
title: '当前尚未绑定车辆',
confirmText: "去绑定",
cancelText: "稍后",
onConfirm: () {
doQrCode();
},
);
// 标记为已显示,本次会话不再提示
StorageService.to.markBindVehicleDialogAsShown();
});
}
}
}

View File

@@ -201,12 +201,7 @@ class ReservationPage extends GetView<C_ReservationController> {
isButton
? GestureDetector(
onTap: () async {
//判断是否绑定成功
var scanResult = await Get.to(() => const QrCodePage());
if (scanResult == true) {
controller.getUserBindCarInfo();
refreshAppui();
}
controller.doQrCode();
},
child: Container(
margin: EdgeInsetsGeometry.only(left: 10.w),
@@ -275,9 +270,9 @@ class ReservationPage extends GetView<C_ReservationController> {
onTap: () => controller.pickTime(context, false),
),
_buildTextField(
label: '预约氢量(kg)',
label: '预约氢量(KG)',
controller: controller.amountController,
hint: '请输入氢量(kg)',
hint: '当前最大可预约氢量${controller.difference}(KG)',
keyboardType: TextInputType.number,
),
_buildTextField(

View File

@@ -20,9 +20,10 @@ class StorageService extends GetxService {
static const String _phoneKey = 'user_phone';
static const String _idCardKey = 'user_id_card';
static const String _vehicleInfoKey = 'vehicle_info';
// 加氢站登录凭证的键名
static const String _stationAccountKey = 'station_account';
static const String _stationPasswordKey = 'station_password';
// 新增:用于标记“绑定车辆”弹窗是否已在本会话中显示过
static const String _bindDialogShownKey = 'bind_vehicle_dialog_shown';
static StorageService get to => Get.find();
@@ -41,10 +42,13 @@ class StorageService extends GetxService {
String? get idCard => _box.read<String?>(_idCardKey);
bool get hasVehicleInfo => _box.hasData(_vehicleInfoKey);
// 记住密码:获取加氢站账号和密码
String? get stationAccount => _box.read<String?>(_stationAccountKey);
String? get stationPassword => _box.read<String?>(_stationPasswordKey);
// 新增:获取“绑定车辆”弹窗是否已显示的标志
bool get hasShownBindVehicleDialog => _box.read<bool>(_bindDialogShownKey) ?? false;
VehicleInfo? get vehicleInfo {
final vehicleJson = _box.read<String?>(_vehicleInfoKey);
if (vehicleJson != null) {
@@ -84,17 +88,21 @@ class StorageService extends GetxService {
await _box.write(_vehicleInfoKey, vehicleInfoToJson(data));
}
// 保存加氢站登录凭证
Future<void> saveStationCredentials(String account, String password) async {
await _box.write(_stationAccountKey, account);
await _box.write(_stationPasswordKey, password);
}
// 新增:标记“绑定车辆”弹窗已显示
Future<void> markBindVehicleDialogAsShown() async {
await _box.write(_bindDialogShownKey, true);
}
Future<void> clearVehicleInfo() async {
await _box.remove(_vehicleInfoKey);
}
// 清除加氢站登录凭证
Future<void> clearStationCredentials() async {
await _box.remove(_stationAccountKey);
await _box.remove(_stationPasswordKey);
@@ -108,6 +116,7 @@ class StorageService extends GetxService {
await _box.remove(_phoneKey);
await _box.remove(_idCardKey);
await clearVehicleInfo();
// 注意:登出时我们不清除“记住的密码”,以便下次用户登录时仍然可以回填
// 登出时,清除“绑定车辆”弹窗的显示记录,以便下次登录时可以再次弹出
await _box.remove(_bindDialogShownKey);
}
}