Compare commits
5 Commits
dev_map
...
756bf53cf5
| Author | SHA1 | Date | |
|---|---|---|---|
| 756bf53cf5 | |||
| f68c2d0938 | |||
| 211d0225e4 | |||
| 7d9b4d99e8 | |||
| 3dd583a278 |
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 545 KiB |
@@ -12,6 +12,8 @@ PODS:
|
|||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
|
- flutter_app_update (0.0.1):
|
||||||
|
- Flutter
|
||||||
- flutter_inappwebview_ios (0.0.1):
|
- flutter_inappwebview_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_inappwebview_ios/Core (= 0.0.1)
|
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||||
@@ -50,6 +52,7 @@ DEPENDENCIES:
|
|||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
|
||||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_pdfview (from `.symlinks/plugins/flutter_pdfview/ios`)
|
- flutter_pdfview (from `.symlinks/plugins/flutter_pdfview/ios`)
|
||||||
@@ -80,6 +83,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
flutter_app_update:
|
||||||
|
:path: ".symlinks/plugins/flutter_app_update/ios"
|
||||||
flutter_inappwebview_ios:
|
flutter_inappwebview_ios:
|
||||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
@@ -111,6 +116,7 @@ SPEC CHECKSUMS:
|
|||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
|
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
|
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
|
||||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||||
flutter_pdfview: 32bf27bda6fd85b9dd2c09628a824df5081246cf
|
flutter_pdfview: 32bf27bda6fd85b9dd2c09628a824df5081246cf
|
||||||
|
|||||||
22
ln_jq_app/lib/common/AuthGuard.dart
Normal file
22
ln_jq_app/lib/common/AuthGuard.dart
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:getx_scaffold/common/index.dart';
|
||||||
|
import 'package:ln_jq_app/pages/login/view.dart';
|
||||||
|
import 'package:ln_jq_app/storage_service.dart';
|
||||||
|
|
||||||
|
class AuthGuard {
|
||||||
|
static bool _handling401 = false;
|
||||||
|
|
||||||
|
static Future<void> handle401(String? message) async {
|
||||||
|
if (_handling401) return;
|
||||||
|
_handling401 = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await StorageService.to.clearLoginInfo();
|
||||||
|
Get.offAll(() => const LoginPage());
|
||||||
|
} finally {
|
||||||
|
// 防止意外卡死,可视情况是否延迟重置
|
||||||
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
|
_handling401 = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ class StationModel {
|
|||||||
final String address;
|
final String address;
|
||||||
final String price;
|
final String price;
|
||||||
final String siteStatusName; // 例如 "维修中"
|
final String siteStatusName; // 例如 "维修中"
|
||||||
final int isSelect; // 新增字段 1是可用 0是不可用
|
final int isSelect; // 1是可用 0是不可用
|
||||||
|
final String startBusiness; // 新增:可预约最早开始时间,如 "06:00:00"
|
||||||
|
final String endBusiness; // 新增:可预约最晚结束时间,如 "22:00:00"
|
||||||
|
|
||||||
StationModel({
|
StationModel({
|
||||||
required this.hydrogenId,
|
required this.hydrogenId,
|
||||||
@@ -13,9 +15,10 @@ class StationModel {
|
|||||||
required this.price,
|
required this.price,
|
||||||
required this.siteStatusName,
|
required this.siteStatusName,
|
||||||
required this.isSelect,
|
required this.isSelect,
|
||||||
|
required this.startBusiness,
|
||||||
|
required this.endBusiness,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 从 JSON map 创建对象的工厂构造函数
|
|
||||||
factory StationModel.fromJson(Map<String, dynamic> json) {
|
factory StationModel.fromJson(Map<String, dynamic> json) {
|
||||||
return StationModel(
|
return StationModel(
|
||||||
hydrogenId: json['hydrogenId'] ?? '',
|
hydrogenId: json['hydrogenId'] ?? '',
|
||||||
@@ -23,7 +26,9 @@ class StationModel {
|
|||||||
address: json['address'] ?? '地址未知',
|
address: json['address'] ?? '地址未知',
|
||||||
price: json['price']?.toString() ?? '0.00',
|
price: json['price']?.toString() ?? '0.00',
|
||||||
siteStatusName: json['siteStatusName'] ?? '',
|
siteStatusName: json['siteStatusName'] ?? '',
|
||||||
isSelect: json['isSelect'] as int? ?? 0, // 新增字段的解析,默认为 0
|
isSelect: json['isSelect'] as int? ?? 0,
|
||||||
|
startBusiness: json['startBusiness'] ?? '00:00:00', // 默认全天
|
||||||
|
endBusiness: json['endBusiness'] ?? '23:59:59', // 默认全天
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
|
import 'package:ln_jq_app/common/AuthGuard.dart';
|
||||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||||
import 'package:ln_jq_app/common/token_interceptor.dart';
|
import 'package:ln_jq_app/common/token_interceptor.dart';
|
||||||
import 'package:ln_jq_app/storage_service.dart';
|
import 'package:ln_jq_app/storage_service.dart';
|
||||||
@@ -20,7 +21,7 @@ void main() async {
|
|||||||
logTag: '小羚羚',
|
logTag: '小羚羚',
|
||||||
supportedLocales: [const Locale('zh', 'CN')],
|
supportedLocales: [const Locale('zh', 'CN')],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 保持原生闪屏页,直到 WelcomeController 调用 remove()
|
// 保持原生闪屏页,直到 WelcomeController 调用 remove()
|
||||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ void main() async {
|
|||||||
void initHttpSet() {
|
void initHttpSet() {
|
||||||
AppTheme.test_service_url = StorageService.to.hostUrl ?? AppTheme.test_service_url;
|
AppTheme.test_service_url = StorageService.to.hostUrl ?? AppTheme.test_service_url;
|
||||||
|
|
||||||
|
HttpService.to.init(timeout: 15);
|
||||||
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
||||||
HttpService.to.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token'));
|
HttpService.to.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token'));
|
||||||
HttpService.to.setOnResponseHandler((response) async {
|
HttpService.to.setOnResponseHandler((response) async {
|
||||||
@@ -73,8 +75,7 @@ void initHttpSet() {
|
|||||||
if (baseModel.code == 0 || baseModel.code == 200) {
|
if (baseModel.code == 0 || baseModel.code == 200) {
|
||||||
return null;
|
return null;
|
||||||
} else if (baseModel.code == 401) {
|
} else if (baseModel.code == 401) {
|
||||||
await StorageService.to.clearLoginInfo();
|
await AuthGuard.handle401(baseModel.message);
|
||||||
Get.offAll(() => const LoginPage());
|
|
||||||
return baseModel.message;
|
return baseModel.message;
|
||||||
} else {
|
} else {
|
||||||
return (baseModel.error.toString()).isEmpty
|
return (baseModel.error.toString()).isEmpty
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
// --- 车辆基本信息 ---
|
// --- 车辆基本信息 ---
|
||||||
String plateNumber = "";
|
String plateNumber = "";
|
||||||
String vin = "未知";
|
String vin = "-";
|
||||||
String modelName = "未知";
|
String modelName = "-";
|
||||||
String brandName = "未知";
|
String brandName = "-";
|
||||||
|
|
||||||
// --- 证件附件列表 ---
|
// --- 证件附件列表 ---
|
||||||
final RxList<String> drivingAttachments = <String>[].obs;
|
final RxList<String> drivingAttachments = <String>[].obs;
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async{
|
onPressed: () async {
|
||||||
var scanResult = await Get.to(() => const MessagePage());
|
var scanResult = await Get.to(() => const MessagePage());
|
||||||
if (scanResult == null) {
|
if (scanResult == null) {
|
||||||
controller.msgNotice();
|
controller.msgNotice();
|
||||||
@@ -163,11 +163,26 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
_buildModernStatItem('本月里程数', 'Accumulated', '2,852km', ''),
|
_buildModernStatItem(
|
||||||
|
'本月里程数',
|
||||||
|
'Accumulated',
|
||||||
|
StorageService.to.hasVehicleInfo ? '2,852km' : '-',
|
||||||
|
'',
|
||||||
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildModernStatItem('总里程', 'Refuel Count', "2.5W km", ''),
|
_buildModernStatItem(
|
||||||
|
'总里程',
|
||||||
|
'Refuel Count',
|
||||||
|
StorageService.to.hasVehicleInfo ? "2.5W km" : '-',
|
||||||
|
'',
|
||||||
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildModernStatItem('服务评分', 'Driver rating', "4.9分", ''),
|
_buildModernStatItem(
|
||||||
|
'服务评分',
|
||||||
|
'Driver rating',
|
||||||
|
StorageService.to.hasVehicleInfo ? "4.9分" : '-',
|
||||||
|
'',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -300,20 +315,20 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
child: const LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
value: 0.75,
|
value: StorageService.to.hasVehicleInfo ? 0.75 : 0,
|
||||||
minHeight: 8,
|
minHeight: 8,
|
||||||
backgroundColor: Color(0xFFF0F2F5),
|
backgroundColor: Color(0xFFF0F2F5),
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(16, 185, 129, 1)),
|
valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(16, 185, 129, 1)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
const Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text("H2 Level", style: TextStyle(fontSize: 11, color: Colors.grey)),
|
Text("H2 Level", style: TextStyle(fontSize: 11, color: Colors.grey)),
|
||||||
Text(
|
Text(
|
||||||
"75%",
|
StorageService.to.hasVehicleInfo ? "75%" : "0%",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Color.fromRGBO(16, 185, 129, 1),
|
color: Color.fromRGBO(16, 185, 129, 1),
|
||||||
@@ -382,7 +397,11 @@ class CarInfoPage extends GetView<CarInfoController> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
_buildCertDetailItem('所属公司', controller.rentFromCompany, isFull: false),
|
_buildCertDetailItem(
|
||||||
|
'所属公司',
|
||||||
|
controller.rentFromCompany,
|
||||||
|
isFull: false,
|
||||||
|
),
|
||||||
_buildCertDetailItem('运营城市', controller.address),
|
_buildCertDetailItem('运营城市', controller.address),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -69,239 +69,34 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
String get formattedTimeSlot =>
|
String get formattedTimeSlot =>
|
||||||
'${_formatTimeOfDay(startTime.value)} - ${_formatTimeOfDay(endTime.value)}';
|
'${_formatTimeOfDay(startTime.value)} - ${_formatTimeOfDay(endTime.value)}';
|
||||||
|
|
||||||
void pickDate(BuildContext context) {
|
|
||||||
DateTime tempDate = selectedDate.value;
|
|
||||||
|
|
||||||
// 获取今天的日期 (不含时间)
|
|
||||||
final DateTime today = DateTime(
|
|
||||||
DateTime.now().year,
|
|
||||||
DateTime.now().month,
|
|
||||||
DateTime.now().day,
|
|
||||||
);
|
|
||||||
|
|
||||||
//计算明天的日期
|
|
||||||
final DateTime tomorrow = today.add(const Duration(days: 1));
|
|
||||||
|
|
||||||
Get.bottomSheet(
|
|
||||||
Container(
|
|
||||||
height: 300,
|
|
||||||
padding: const EdgeInsets.only(top: 6.0),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(16),
|
|
||||||
topRight: Radius.circular(16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
CupertinoButton(
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
child: const Text(
|
|
||||||
'取消',
|
|
||||||
style: TextStyle(color: CupertinoColors.systemGrey),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
CupertinoButton(
|
|
||||||
onPressed: () {
|
|
||||||
final bool isChangingToToday =
|
|
||||||
tempDate.isAtSameMomentAs(today) &&
|
|
||||||
!selectedDate.value.isAtSameMomentAs(today);
|
|
||||||
final bool isDateChanged = !tempDate.isAtSameMomentAs(
|
|
||||||
selectedDate.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 更新选中的日期
|
|
||||||
selectedDate.value = tempDate;
|
|
||||||
Get.back(); // 先关闭弹窗
|
|
||||||
|
|
||||||
// 如果日期发生了变化,则重置时间
|
|
||||||
if (isDateChanged) {
|
|
||||||
resetTimeForSelectedDate();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
'确认',
|
|
||||||
style: TextStyle(
|
|
||||||
color: AppTheme.themeColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 1, color: Color(0xFFE5E5E5)),
|
|
||||||
Expanded(
|
|
||||||
child: CupertinoDatePicker(
|
|
||||||
mode: CupertinoDatePickerMode.date,
|
|
||||||
initialDateTime: selectedDate.value,
|
|
||||||
minimumDate: today,
|
|
||||||
// 最小可选日期为今天
|
|
||||||
maximumDate: tomorrow,
|
|
||||||
// 最大可选日期为明天
|
|
||||||
// ---------------------
|
|
||||||
onDateTimeChanged: (DateTime newDate) {
|
|
||||||
tempDate = newDate;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetTimeForSelectedDate() {
|
void resetTimeForSelectedDate() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final today = DateTime(now.year, now.month, now.day);
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
// 判断新选择的日期是不是今天
|
// 1. 获取当前站点
|
||||||
|
final station = stationOptions.firstWhereOrNull(
|
||||||
|
(s) => s.hydrogenId == selectedStationId.value,
|
||||||
|
);
|
||||||
|
if (station == null) return;
|
||||||
|
|
||||||
|
// 2. 解析营业开始和结束的小时
|
||||||
|
final bizStartHour = int.tryParse(station.startBusiness.split(':')[0]) ?? 0;
|
||||||
|
final bizEndHour = int.tryParse(station.endBusiness.split(':')[0]) ?? 23;
|
||||||
|
|
||||||
if (selectedDate.value.isAtSameMomentAs(today)) {
|
if (selectedDate.value.isAtSameMomentAs(today)) {
|
||||||
// 如果是今天,就将时间重置为当前时间所在的半小时区间
|
// 如果是今天:起始时间 = max(当前小时, 营业开始小时),且上限为营业结束小时
|
||||||
startTime.value = _calculateInitialStartTime(now);
|
int targetHour = now.hour;
|
||||||
endTime.value = TimeOfDay.fromDateTime(
|
if (targetHour < bizStartHour) targetHour = bizStartHour;
|
||||||
_getDateTimeFromTimeOfDay(startTime.value).add(const Duration(minutes: 30)),
|
if (targetHour > bizEndHour) targetHour = bizEndHour;
|
||||||
);
|
|
||||||
|
startTime.value = TimeOfDay(hour: targetHour, minute: 0);
|
||||||
} else {
|
} else {
|
||||||
// 如果是明天(或其他未来日期),则可以将时间重置为一天的最早可用时间,例如 00:00
|
// 如果是明天:起始时间直接重置为营业开始小时
|
||||||
startTime.value = const TimeOfDay(hour: 0, minute: 0);
|
startTime.value = TimeOfDay(hour: bizStartHour, minute: 0);
|
||||||
endTime.value = const TimeOfDay(hour: 0, minute: 30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///60 分钟为间隔 时间选择器
|
|
||||||
void pickTime(BuildContext context) {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final isToday =
|
|
||||||
selectedDate.value.year == now.year &&
|
|
||||||
selectedDate.value.month == now.month &&
|
|
||||||
selectedDate.value.day == now.day;
|
|
||||||
|
|
||||||
final List<TimeSlot> availableSlots = [];
|
|
||||||
for (int i = 0; i < 24; i++) {
|
|
||||||
// 每次增加 60 分钟
|
|
||||||
final startMinutes = i * 60;
|
|
||||||
final endMinutes = startMinutes + 60;
|
|
||||||
|
|
||||||
final startTime = TimeOfDay(hour: startMinutes ~/ 60, minute: startMinutes % 60);
|
|
||||||
// 注意:endMinutes % 60 始终为 0,因为间隔是整小时
|
|
||||||
final endTime = TimeOfDay(hour: (endMinutes ~/ 60) % 24, minute: endMinutes % 60);
|
|
||||||
|
|
||||||
// 如果不是今天,所有时间段都有效
|
|
||||||
if (!isToday) {
|
|
||||||
availableSlots.add(TimeSlot(startTime, endTime));
|
|
||||||
} else {
|
|
||||||
// 如果是今天,需要判断该时间段是否可选
|
|
||||||
// 创建时间段的结束时间对象
|
|
||||||
final slotEndDateTime = DateTime(
|
|
||||||
selectedDate.value.year,
|
|
||||||
selectedDate.value.month,
|
|
||||||
selectedDate.value.day,
|
|
||||||
endTime.hour,
|
|
||||||
endTime.minute,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 注意:如果是跨天的 00:00 (例如 23:00 - 00:00),需要将日期加一天,否则 isAfter 判断会出错
|
|
||||||
// 但由于我们用的是 endTime.hour % 24,当变成 0 时,日期还是 selectedDate
|
|
||||||
// 这里做一个特殊处理:如果 endTime 是 00:00,意味着它实际上是明天的开始
|
|
||||||
DateTime realEndDateTime = slotEndDateTime;
|
|
||||||
if (endTime.hour == 0 && endTime.minute == 0) {
|
|
||||||
realEndDateTime = slotEndDateTime.add(const Duration(days: 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只要时间段的结束时间晚于当前时间,这个时间段就是可预约的
|
|
||||||
if (realEndDateTime.isAfter(now)) {
|
|
||||||
availableSlots.add(TimeSlot(startTime, endTime));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (availableSlots.isEmpty) {
|
// 结束时间默认顺延1小时
|
||||||
showToast('今天已没有可预约的时间段');
|
endTime.value = TimeOfDay(hour: (startTime.value.hour + 1) % 24, minute: 0);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找当前选中的时间对应的新列表中的索引
|
|
||||||
int initialItem = availableSlots.indexWhere(
|
|
||||||
(slot) => slot.start.hour == startTime.value.hour,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (initialItem == -1) {
|
|
||||||
initialItem = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeSlot tempSlot = availableSlots[initialItem];
|
|
||||||
|
|
||||||
final FixedExtentScrollController scrollController = FixedExtentScrollController(
|
|
||||||
initialItem: initialItem,
|
|
||||||
);
|
|
||||||
|
|
||||||
Get.bottomSheet(
|
|
||||||
Container(
|
|
||||||
height: 300,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(16),
|
|
||||||
topRight: Radius.circular(16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
CupertinoButton(
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
child: const Text(
|
|
||||||
'取消',
|
|
||||||
style: TextStyle(color: CupertinoColors.systemGrey),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
CupertinoButton(
|
|
||||||
onPressed: () {
|
|
||||||
startTime.value = tempSlot.start;
|
|
||||||
endTime.value = tempSlot.end;
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
'确认',
|
|
||||||
style: TextStyle(
|
|
||||||
color: AppTheme.themeColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 1, color: Color(0xFFE5E5E5)),
|
|
||||||
Expanded(
|
|
||||||
child: CupertinoPicker(
|
|
||||||
scrollController: scrollController,
|
|
||||||
itemExtent: 40.0,
|
|
||||||
onSelectedItemChanged: (index) {
|
|
||||||
tempSlot = availableSlots[index];
|
|
||||||
},
|
|
||||||
children: availableSlots
|
|
||||||
.map((slot) => Center(child: Text(slot.display)))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用于存储上一次成功预约的信息
|
// 用于存储上一次成功预约的信息
|
||||||
@@ -536,12 +331,13 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String workEfficiency = "0";
|
String workEfficiency = "-";
|
||||||
String fillingWeight = "0";
|
String fillingWeight = "-";
|
||||||
String fillingTimes = "0";
|
String fillingTimes = "-";
|
||||||
|
String modeImage = "";
|
||||||
String plateNumber = "";
|
String plateNumber = "";
|
||||||
String vin = "";
|
String vin = "";
|
||||||
String leftHydrogen = "0";
|
String leftHydrogen = "-";
|
||||||
num maxHydrogen = 0;
|
num maxHydrogen = 0;
|
||||||
String difference = "";
|
String difference = "";
|
||||||
var progressValue = 0.0;
|
var progressValue = 0.0;
|
||||||
@@ -650,7 +446,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
try {
|
try {
|
||||||
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
||||||
var responseData = await HttpService.to.get(
|
var responseData = await HttpService.to.get(
|
||||||
'appointment/truck/history-filling-summary?vin=$vin',
|
'appointment/truck/history-filling-summary?vin=$vin&plateNumber=$plateNumber',
|
||||||
);
|
);
|
||||||
if (responseData == null || responseData.data == null) {
|
if (responseData == null || responseData.data == null) {
|
||||||
showToast('服务暂不可用,请稍后');
|
showToast('服务暂不可用,请稍后');
|
||||||
@@ -664,6 +460,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
fillingWeight = "$formatted${result.data["fillingWeightUnit"]}";
|
fillingWeight = "$formatted${result.data["fillingWeightUnit"]}";
|
||||||
fillingTimes = "${result.data["fillingTimes"]}${result.data["fillingTimesUnit"]}";
|
fillingTimes = "${result.data["fillingTimes"]}${result.data["fillingTimesUnit"]}";
|
||||||
|
modeImage = result.data["modeImage"].toString();
|
||||||
|
|
||||||
updateUi();
|
updateUi();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -689,8 +486,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
var result = BaseModel.fromJson(responseData.data);
|
var result = BaseModel.fromJson(responseData.data);
|
||||||
|
|
||||||
leftHydrogen = result.data["leftHydrogen"].toString();
|
leftHydrogen = "${result.data["leftHydrogen"]}Kg";
|
||||||
workEfficiency = result.data["workEfficiency"].toString();
|
workEfficiency = "${result.data["workEfficiency"]}Kg";
|
||||||
|
|
||||||
final leftHydrogenNum = double.tryParse(leftHydrogen) ?? 0.0;
|
final leftHydrogenNum = double.tryParse(leftHydrogen) ?? 0.0;
|
||||||
difference = (maxHydrogen - leftHydrogenNum).toStringAsFixed(2);
|
difference = (maxHydrogen - leftHydrogenNum).toStringAsFixed(2);
|
||||||
@@ -725,7 +522,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void getSiteList() async {
|
void getSiteList() async {
|
||||||
if (StorageService.to.phone == "13344444444") {
|
if (StorageService.to.phone == "13888888888") {
|
||||||
//该账号给stationOptions手动添加一个数据
|
//该账号给stationOptions手动添加一个数据
|
||||||
final testStation = StationModel(
|
final testStation = StationModel(
|
||||||
hydrogenId: '1142167389150920704',
|
hydrogenId: '1142167389150920704',
|
||||||
@@ -735,7 +532,9 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
|||||||
// 价格
|
// 价格
|
||||||
siteStatusName: '营运中',
|
siteStatusName: '营运中',
|
||||||
// 状态
|
// 状态
|
||||||
isSelect: 1, // 默认可选
|
isSelect: 1,
|
||||||
|
startBusiness: '08:00:00',
|
||||||
|
endBusiness: '20:00:00', // 默认可选
|
||||||
);
|
);
|
||||||
// 使用 assignAll 可以确保列表只包含这个测试数据
|
// 使用 assignAll 可以确保列表只包含这个测试数据
|
||||||
stationOptions.assignAll([testStation]);
|
stationOptions.assignAll([testStation]);
|
||||||
|
|||||||
@@ -148,9 +148,11 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
controller.stopAutoRefresh();
|
||||||
var scanResult = await Get.to(() => const MessagePage());
|
var scanResult = await Get.to(() => const MessagePage());
|
||||||
if (scanResult == null) {
|
if (scanResult == null) {
|
||||||
controller.msgNotice();
|
controller.msgNotice();
|
||||||
|
controller.startAutoRefresh();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: Badge(
|
icon: Badge(
|
||||||
@@ -177,7 +179,12 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildModernStatItem('总加氢次数', '', controller.fillingTimes, ''),
|
_buildModernStatItem('总加氢次数', '', controller.fillingTimes, ''),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildModernStatItem('今日里程', '', "7kg", ''),
|
_buildModernStatItem(
|
||||||
|
'今日里程',
|
||||||
|
'',
|
||||||
|
StorageService.to.hasVehicleInfo ? "7kg" : "-",
|
||||||
|
'',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -237,17 +244,33 @@ 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: Image.network(
|
||||||
|
controller.modeImage,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Center(child: LoginUtil.getAssImg('ic_car_select@2x'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 6,
|
flex: 6,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildCarDataItem('剩余电量', '36.8%'),
|
_buildCarDataItem(
|
||||||
|
'剩余电量',
|
||||||
|
StorageService.to.hasVehicleInfo ? '36.8%' : '-',
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildCarDataItem('剩余氢量', '${controller.leftHydrogen}Kg'),
|
_buildCarDataItem('剩余氢量', controller.leftHydrogen),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildCarDataItem('百公里氢耗', '${controller.workEfficiency}Kg'),
|
_buildCarDataItem('百公里氢耗', controller.workEfficiency),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -275,7 +298,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"${controller.leftHydrogen}Kg",
|
controller.leftHydrogen,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: Color(0xFF006633),
|
color: Color(0xFF006633),
|
||||||
@@ -434,8 +457,27 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
/// 时间 Slider 选择器
|
/// 时间 Slider 选择器
|
||||||
Widget _buildTimeSlider(BuildContext context) {
|
Widget _buildTimeSlider(BuildContext context) {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
// 这里的逻辑对应 Controller 中的 24 小时可用 Slot
|
// 获取当前小时作为滑块值 (0-23)
|
||||||
int currentIdx = controller.startTime.value.hour;
|
int currentHour = controller.startTime.value.hour;
|
||||||
|
|
||||||
|
// 动态获取站点的营业范围限制
|
||||||
|
final station = controller.stationOptions.firstWhereOrNull(
|
||||||
|
(s) => s.hydrogenId == controller.selectedStationId.value,
|
||||||
|
);
|
||||||
|
// 解析营业时间
|
||||||
|
// 处理格式如 "09:00" 或 "09:00:00"
|
||||||
|
final startParts = (station?.startBusiness ?? "00:00").split(':');
|
||||||
|
final endParts = (station?.endBusiness ?? "23:59").split(':');
|
||||||
|
|
||||||
|
int bizStartHour = int.tryParse(startParts[0]) ?? 0;
|
||||||
|
int bizEndHour = int.tryParse(endParts[0]) ?? 23;
|
||||||
|
int bizEndMinute = (endParts.length > 1) ? (int.tryParse(endParts[1]) ?? 0) : 0;
|
||||||
|
|
||||||
|
// 优化结束小时逻辑
|
||||||
|
// 如果分钟为 0 (例如 18:00),说明该小时整点已关门,最大可选小时应减 1
|
||||||
|
if (bizEndMinute == 0 && bizEndHour > 0) {
|
||||||
|
bizEndHour--;
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -484,23 +526,23 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
overlayColor: const Color(0xFF006633).withOpacity(0.1),
|
overlayColor: const Color(0xFF006633).withOpacity(0.1),
|
||||||
),
|
),
|
||||||
child: Slider(
|
child: Slider(
|
||||||
value: currentIdx.toDouble(),
|
value: currentHour.toDouble().clamp(
|
||||||
min: 0,
|
bizStartHour.toDouble(),
|
||||||
max: 23,
|
bizEndHour.toDouble(),
|
||||||
divisions: 23,
|
),
|
||||||
|
min: bizStartHour.toDouble(),
|
||||||
|
max: bizEndHour.toDouble(),
|
||||||
|
// divisions: bizEndHour - bizStartHour > 0 ? bizEndHour - bizStartHour : 1,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
int hour = val.toInt();
|
int hour = val.toInt();
|
||||||
// 模拟 Controller 中的 pickTime 逻辑校验
|
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final isToday =
|
final isToday =
|
||||||
controller.selectedDate.value.year == now.year &&
|
controller.selectedDate.value.year == now.year &&
|
||||||
controller.selectedDate.value.month == now.month &&
|
controller.selectedDate.value.month == now.month &&
|
||||||
controller.selectedDate.value.day == now.day;
|
controller.selectedDate.value.day == now.day;
|
||||||
|
|
||||||
if (isToday && hour < now.hour) {
|
// 如果是今天,判断不可选过去的时间点
|
||||||
// 如果是今天且小时数小于当前,则忽略
|
if (isToday && hour < now.hour) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.startTime.value = TimeOfDay(hour: hour, minute: 0);
|
controller.startTime.value = TimeOfDay(hour: hour, minute: 0);
|
||||||
controller.endTime.value = TimeOfDay(hour: (hour + 1) % 24, minute: 0);
|
controller.endTime.value = TimeOfDay(hour: (hour + 1) % 24, minute: 0);
|
||||||
@@ -627,6 +669,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
|||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
controller.selectedStationId.value = value;
|
controller.selectedStationId.value = value;
|
||||||
|
controller.resetTimeForSelectedDate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:aliyun_push_flutter/aliyun_push_flutter.dart';
|
import 'package:aliyun_push_flutter/aliyun_push_flutter.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_app_update/flutter_app_update.dart';
|
||||||
|
import 'package:flutter_app_update/result_model.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
|
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||||
import 'package:ln_jq_app/pages/b_page/base_widgets/view.dart';
|
import 'package:ln_jq_app/pages/b_page/base_widgets/view.dart';
|
||||||
import 'package:ln_jq_app/pages/c_page/base_widgets/view.dart';
|
import 'package:ln_jq_app/pages/c_page/base_widgets/view.dart';
|
||||||
@@ -20,107 +23,195 @@ class HomeController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
final _aliyunPush = AliyunPushFlutter();
|
final _aliyunPush = AliyunPushFlutter();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get listenLifecycleEvent => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
initAliyunPush();
|
initAliyunPush();
|
||||||
addPushCallback();
|
addPushCallback();
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
|
log('page-init');
|
||||||
|
|
||||||
|
// 页面初始化后执行版本检查
|
||||||
|
checkVersionInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
String downloadUrl = "";
|
||||||
|
|
||||||
|
/// 检查 App 更新信息,增加版本号比对逻辑
|
||||||
|
void checkVersionInfo() async {
|
||||||
|
try {
|
||||||
|
final response = await HttpService.to.get('appointment/appConfig/get');
|
||||||
|
if (response != null) {
|
||||||
|
final result = BaseModel.fromJson(response.data);
|
||||||
|
if (result.code == 0 && result.data != null) {
|
||||||
|
final data = result.data as Map<String, dynamic>;
|
||||||
|
|
||||||
|
bool hasUpdate = data['hasUpdate']?.toString().toLowerCase() == "true";
|
||||||
|
bool isForce = data['isForce']?.toString().toLowerCase() == "true";
|
||||||
|
String versionName = data['versionName'] ?? "新版本";
|
||||||
|
String updateContent = data['updateContent'] ?? "优化系统性能,提升用户体验";
|
||||||
|
downloadUrl = data['downloadUrl'].toString();
|
||||||
|
|
||||||
|
// 获取服务器配置的目标构建号
|
||||||
|
int serverVersionCode =
|
||||||
|
int.tryParse(data['versionCode']?.toString() ?? "0") ?? 0;
|
||||||
|
int serverIosBuildId = int.tryParse(data['iosBuildId']?.toString() ?? "0") ?? 0;
|
||||||
|
|
||||||
|
// 获取本地当前的构建号
|
||||||
|
String currentBuildStr = await getBuildNumber();
|
||||||
|
int currentBuild = int.tryParse(currentBuildStr) ?? 0;
|
||||||
|
|
||||||
|
bool needUpdate = false;
|
||||||
|
if (GetPlatform.isAndroid) {
|
||||||
|
needUpdate = currentBuild < serverVersionCode;
|
||||||
|
} else if (GetPlatform.isIOS) {
|
||||||
|
needUpdate = currentBuild < serverIosBuildId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果服务器标记有更新,且本地版本号确实较低,则弹出更新
|
||||||
|
if (hasUpdate && needUpdate) {
|
||||||
|
_showUpdateDialog("版本:$versionName\n\n更新内容:\n$updateContent", isForce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Logger.d("版本检查失败: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 显示更新弹窗
|
||||||
|
void _showUpdateDialog(String content, bool isForce) {
|
||||||
|
DialogX.to.showConfirmDialog(
|
||||||
|
title: '升级提醒',
|
||||||
|
confirmText: '立即升级',
|
||||||
|
content: _buildDialogContent(content),
|
||||||
|
// 如果是强制更新,取消按钮显示为空,即隐藏
|
||||||
|
cancelText: isForce ? "" : '以后再说',
|
||||||
|
// 设置为 false,禁止点击背景和物理返回键关闭
|
||||||
|
barrierDismissible: false,
|
||||||
|
onConfirm: () {
|
||||||
|
jumpUpdateApp();
|
||||||
|
|
||||||
|
// ios如果是强制更新,点击后维持弹窗,防止用户进入 App
|
||||||
|
if (isForce && GetPlatform.isIOS) {
|
||||||
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
|
_showUpdateDialog(content, isForce);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDialogContent(String content) {
|
||||||
|
return PopScope(
|
||||||
|
canPop: false, // 关键:禁止 pop
|
||||||
|
child: TextX.bodyMedium(content).padding(bottom: 16.h),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void jumpUpdateApp() {
|
||||||
|
if (GetPlatform.isIOS) {
|
||||||
|
// 跳转到 iOS 应用商店网页
|
||||||
|
openWebPage("https://apps.apple.com/cn/app/羚牛氢能/6756245815");
|
||||||
|
} else if (GetPlatform.isAndroid) {
|
||||||
|
// Android 执行下载安装流程
|
||||||
|
showAndroidDownloadDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showAndroidDownloadDialog() {
|
||||||
|
AzhonAppUpdate.listener((ResultModel model) {
|
||||||
|
if (model.type == ResultType.start) {
|
||||||
|
DialogX.to.showConfirmDialog(
|
||||||
|
content: PopScope(
|
||||||
|
canPop: false,
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextX.bodyMedium('升级中...').padding(bottom: 45.h),
|
||||||
|
CircularProgressIndicator(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
confirmText: '',
|
||||||
|
cancelText: "",
|
||||||
|
barrierDismissible: false,
|
||||||
|
);
|
||||||
|
} else if (model.type == ResultType.done) {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
UpdateModel model = UpdateModel(downloadUrl, "xll.apk", "logo", '正在下载最新版本...');
|
||||||
|
AzhonAppUpdate.update(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据登录状态和登录渠道返回不同的首页
|
// 根据登录状态和登录渠道返回不同的首页
|
||||||
Widget getHomePage() {
|
Widget getHomePage() {
|
||||||
requestPermission();
|
requestPermission();
|
||||||
//登录状态跳转
|
|
||||||
if (StorageService.to.isLoggedIn) {
|
if (StorageService.to.isLoggedIn) {
|
||||||
// 如果已登录,再判断是哪个渠道
|
|
||||||
if (StorageService.to.loginChannel == LoginChannel.station) {
|
if (StorageService.to.loginChannel == LoginChannel.station) {
|
||||||
return B_BaseWidgetsPage(); // 站点首页
|
return B_BaseWidgetsPage();
|
||||||
} else if (StorageService.to.loginChannel == LoginChannel.driver) {
|
} else if (StorageService.to.loginChannel == LoginChannel.driver) {
|
||||||
return BaseWidgetsPage(); // 司机首页
|
return BaseWidgetsPage();
|
||||||
} else {
|
} else {
|
||||||
return LoginPage();
|
return LoginPage();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 未登录,直接去登录页
|
|
||||||
return LoginPage();
|
return LoginPage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void requestPermission() async {
|
void requestPermission() async {
|
||||||
PermissionStatus status = await Permission.notification.status;
|
PermissionStatus status = await Permission.notification.status;
|
||||||
if (status.isGranted) {
|
if (status.isGranted) return;
|
||||||
Logger.d("通知权限已开启");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.isDenied) {
|
if (status.isDenied) {
|
||||||
// 建议此处增加一个应用内的 Rationale (解释说明) 弹窗
|
|
||||||
status = await Permission.notification.request();
|
status = await Permission.notification.request();
|
||||||
}
|
}
|
||||||
if (status.isGranted) {
|
if (status.isGranted) {
|
||||||
// 授权成功
|
|
||||||
Logger.d('通知已开启');
|
Logger.d('通知已开启');
|
||||||
} else if (status.isPermanentlyDenied) {
|
} else if (status.isPermanentlyDenied) {
|
||||||
Logger.d('通知权限已被拒绝,请到系统设置中开启');
|
Logger.d('通知权限已被拒绝,请到系统设置中开启');
|
||||||
} else if (status.isDenied) {
|
|
||||||
Logger.d('请授予通知权限,以便接收加氢站通知');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///推送相关
|
///推送相关初始化 (保持原样)
|
||||||
Future<void> initAliyunPush() async {
|
Future<void> initAliyunPush() async {
|
||||||
// 1. 配置分离:建议将 Key 提取到外部或配置文件中
|
|
||||||
final String appKey = Platform.isIOS ? AppTheme.ios_key : AppTheme.android_key;
|
final String appKey = Platform.isIOS ? AppTheme.ios_key : AppTheme.android_key;
|
||||||
final String appSecret = Platform.isIOS
|
final String appSecret = Platform.isIOS
|
||||||
? AppTheme.ios_appsecret
|
? AppTheme.ios_appsecret
|
||||||
: AppTheme.android_appsecret;
|
: AppTheme.android_appsecret;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 初始化推送
|
|
||||||
final result = await _aliyunPush.initPush(appKey: appKey, appSecret: appSecret);
|
final result = await _aliyunPush.initPush(appKey: appKey, appSecret: appSecret);
|
||||||
|
if (result['code'] != kAliyunPushSuccessCode) return;
|
||||||
if (result['code'] != kAliyunPushSuccessCode) {
|
|
||||||
Logger.d('初始化推送失败: ${result['code']} - ${result['errorMsg']}');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.d('阿里云推送初始化成功');
|
|
||||||
// 分平台配置
|
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
await _setupIOSConfig();
|
await _setupIOSConfig();
|
||||||
} else if (Platform.isAndroid) {
|
} else if (Platform.isAndroid) {
|
||||||
await _setupAndroidConfig();
|
await _setupAndroidConfig();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.d('初始化过程中发生异常: $e');
|
Logger.d('初始化异常: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// iOS 专属配置
|
|
||||||
Future<void> _setupIOSConfig() async {
|
Future<void> _setupIOSConfig() async {
|
||||||
final res = await _aliyunPush.showIOSNoticeWhenForeground(true);
|
await _aliyunPush.showIOSNoticeWhenForeground(true);
|
||||||
if (res['code'] == kAliyunPushSuccessCode) {
|
|
||||||
Logger.d('iOS 前台通知展示已开启');
|
|
||||||
} else {
|
|
||||||
Logger.d('iOS 前台通知开启失败: ${res['errorMsg']}');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Android 专属配置
|
|
||||||
Future<void> _setupAndroidConfig() async {
|
Future<void> _setupAndroidConfig() async {
|
||||||
await _aliyunPush.setNotificationInGroup(true);
|
await _aliyunPush.setNotificationInGroup(true);
|
||||||
final res = await _aliyunPush.createAndroidChannel(
|
await _aliyunPush.createAndroidChannel(
|
||||||
"xll_push_android",
|
"xll_push_android",
|
||||||
'新消息通知',
|
'新消息通知',
|
||||||
4,
|
4,
|
||||||
'用于接收加氢站实时状态提醒',
|
'用于接收加氢站实时状态提醒',
|
||||||
);
|
);
|
||||||
if (res['code'] == kAliyunPushSuccessCode) {
|
|
||||||
Logger.d('Android 通知通道创建成功');
|
|
||||||
} else {
|
|
||||||
Logger.d('Android 通道创建失败: ${res['code']} - ${res['errorMsg']}');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void addPushCallback() {
|
void addPushCallback() {
|
||||||
@@ -139,40 +230,23 @@ class HomeController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
Future<void> _onAndroidNotificationClickedWithNoAction(
|
Future<void> _onAndroidNotificationClickedWithNoAction(
|
||||||
Map<dynamic, dynamic> message,
|
Map<dynamic, dynamic> message,
|
||||||
) async {
|
) async {}
|
||||||
Logger.d('onAndroidNotificationClickedWithNoAction ====> $message');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onAndroidNotificationReceivedInApp(Map<dynamic, dynamic> message) async {
|
Future<void> _onAndroidNotificationReceivedInApp(Map<dynamic, dynamic> message) async {}
|
||||||
Logger.d('onAndroidNotificationReceivedInApp ====> $message');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onMessage(Map<dynamic, dynamic> message) async {
|
Future<void> _onMessage(Map<dynamic, dynamic> message) async {}
|
||||||
Logger.d('onMessage ====> $message');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onNotification(Map<dynamic, dynamic> message) async {
|
Future<void> _onNotification(Map<dynamic, dynamic> message) async {}
|
||||||
Logger.d('onNotification ====> $message');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onNotificationOpened(Map<dynamic, dynamic> message) async {
|
Future<void> _onNotificationOpened(Map<dynamic, dynamic> message) async {
|
||||||
Logger.d('onNotificationOpened ====> $message');
|
|
||||||
await Get.to(() => const MessagePage());
|
await Get.to(() => const MessagePage());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onNotificationRemoved(Map<dynamic, dynamic> message) async {
|
Future<void> _onNotificationRemoved(Map<dynamic, dynamic> message) async {}
|
||||||
Logger.d('onNotificationRemoved ====> $message');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onIOSChannelOpened(Map<dynamic, dynamic> message) async {
|
Future<void> _onIOSChannelOpened(Map<dynamic, dynamic> message) async {}
|
||||||
Logger.d('onIOSChannelOpened ====> $message');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onIOSRegisterDeviceTokenSuccess(Map<dynamic, dynamic> message) async {
|
Future<void> _onIOSRegisterDeviceTokenSuccess(Map<dynamic, dynamic> message) async {}
|
||||||
Logger.d('onIOSRegisterDeviceTokenSuccess ====> $message');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onIOSRegisterDeviceTokenFailed(Map<dynamic, dynamic> message) async {
|
Future<void> _onIOSRegisterDeviceTokenFailed(Map<dynamic, dynamic> message) async {}
|
||||||
Logger.d('onIOSRegisterDeviceTokenFailed====> $message');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,13 @@ import 'package:ln_jq_app/pages/home/controller.dart';
|
|||||||
class HomePage extends GetView<HomeController> {
|
class HomePage extends GetView<HomeController> {
|
||||||
const HomePage({super.key});
|
const HomePage({super.key});
|
||||||
|
|
||||||
// 主视图
|
|
||||||
Widget _buildView() {
|
|
||||||
return <Widget>[Text('主页面')].toColumn(mainAxisSize: MainAxisSize.min).center();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GetBuilder<HomeController>(
|
return GetBuilder<HomeController>(
|
||||||
init: HomeController(),
|
init: HomeController(),
|
||||||
id: 'home',
|
id: 'home',
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
return controller.getHomePage();
|
return Scaffold(body: controller.getHomePage());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,9 +204,9 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
"欢迎使用 ",
|
"欢迎使用小羚羚 ",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 22,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color.fromRGBO(51, 51, 51, 1),
|
color: Color.fromRGBO(51, 51, 51, 1),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class WelcomePage extends GetView<WelcomeController> {
|
|||||||
right: 0,
|
right: 0,
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'assets/images/welcome.png',
|
'assets/images/welcome.png',
|
||||||
fit: BoxFit.fill
|
fit: BoxFit.cover
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -302,6 +302,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_app_update:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_app_update
|
||||||
|
sha256: "09290240949c4651581cd6fc535e52d019e189e694d6019c56b5a56c2e69ba65"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
flutter_easyloading:
|
flutter_easyloading:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ dependencies:
|
|||||||
geolocator: ^14.0.2 # 获取精确定位
|
geolocator: ^14.0.2 # 获取精确定位
|
||||||
aliyun_push_flutter: ^1.3.6
|
aliyun_push_flutter: ^1.3.6
|
||||||
pull_to_refresh: ^2.0.0
|
pull_to_refresh: ^2.0.0
|
||||||
|
flutter_app_update: ^3.2.2
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user