Compare commits

...

2 Commits

Author SHA1 Message Date
6629c8047f Merge branch 'dev' 2025-12-25 10:41:04 +08:00
bfa615a7f4 扫码无权限优化,司机预约多弹窗 2025-12-25 10:40:26 +08:00
6 changed files with 128 additions and 46 deletions

View File

@@ -131,13 +131,13 @@
<!-- 1. 配置安全密钥 -->
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: '0529b72df6bf0c577ff2182cb8b1d970',
securityJsCode: 'aa3a22c19ed76b27f8a587555d6981c8',
}
</script>
<!-- 2. 加载地图和插件 (去掉了 Geolocation 插件,避免弹窗) -->
<script
src="https://webapi.amap.com/maps?v=2.0&key=2cc1d822e313307fe311c3127a1deeb5&plugin=AMap.MoveAnimation,AMap.Driving,AMap.TruckDriving,AMap.AutoComplete,AMap.ToolBar,AMap.Scale,AMap.Geocoder">
src="https://webapi.amap.com/maps?v=2.0&key=ecd74ece8cb14c9dad67675f83c3274d&plugin=AMap.MoveAnimation,AMap.Driving,AMap.TruckDriving,AMap.AutoComplete,AMap.ToolBar,AMap.Scale,AMap.Geocoder">
</script>
</head>

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.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';
@@ -277,9 +278,10 @@ class SiteController extends GetxController with BaseControllerMixin {
child: TextField(
controller: amountController,
textAlign: TextAlign.center,
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly, // 只允许数字输入
],
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,

View File

@@ -17,6 +17,7 @@ import 'package:ln_jq_app/pages/qr_code/view.dart';
import 'package:ln_jq_app/storage_service.dart';
import '../../../common/styles/theme.dart';
import 'reservation_list_bottomsheet.dart';
/// Helper class for managing time slots
class TimeSlot {
@@ -547,6 +548,10 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
num maxHydrogen = 0;
String difference = "";
//用来管理查看预约的弹窗
Worker? _sheetWorker;
bool init = false;
@override
bool get listenLifecycleEvent => true;
@@ -555,6 +560,32 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
super.onInit();
getUserBindCarInfo();
getSiteList();
if (!init) {
_setupListener();
init = true;
}
}
@override
void dispose() {
_sheetWorker?.dispose();
super.dispose();
}
void _setupListener() {
_sheetWorker = ever(shouldShowReservationList, (bool shouldShow) {
if (shouldShow) {
Get.bottomSheet(
const ReservationListBottomSheet(),
isScrollControlled: true, // 允许弹窗使用更多屏幕高度
backgroundColor: Colors.transparent,
);
// 重要:显示后立即将信号重置为 false防止不必要的重复弹出
shouldShowReservationList.value = false;
}
});
}
void getUserBindCarInfo() {
@@ -628,6 +659,11 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
final leftHydrogenNum = double.tryParse(leftHydrogen) ?? 0.0;
difference = (maxHydrogen - leftHydrogenNum).toStringAsFixed(2);
int flooredDifference = (maxHydrogen - leftHydrogenNum).floor();
if (flooredDifference > 0) {
amountController.text = flooredDifference.toString();
}
updateUi();
} catch (e) {
} finally {

View File

@@ -13,7 +13,7 @@ import 'reservation_list_bottomsheet.dart';
class ReservationPage extends GetView<C_ReservationController> {
ReservationPage({super.key});
bool init = false;
@override
Widget build(BuildContext context) {
@@ -21,10 +21,7 @@ class ReservationPage extends GetView<C_ReservationController> {
init: C_ReservationController(),
id: 'reservation',
builder: (_) {
if (!init) {
_setupListener(context);
init = true;
}
return Scaffold(
backgroundColor: Colors.grey[100],
body: GestureDetector(
@@ -335,20 +332,7 @@ class ReservationPage extends GetView<C_ReservationController> {
);
}
void _setupListener(BuildContext context) {
ever(controller.shouldShowReservationList, (bool shouldShow) {
if (shouldShow) {
Get.bottomSheet(
const ReservationListBottomSheet(),
isScrollControlled: true, // 允许弹窗使用更多屏幕高度
backgroundColor: Colors.transparent,
);
// 重要:显示后立即将信号重置为 false防止不必要的重复弹出
controller.shouldShowReservationList.value = false;
}
});
}
// 表单中的可点击行 (用于日期和时间选择)
Widget _buildPickerRow({

View File

@@ -24,6 +24,8 @@ class QrCodeController extends GetxController
final RxBool isFlashOn = false.obs;
final RxBool isProcessingResult = false.obs;
final RxBool hasPermission = false.obs;
@override
void onInit() {
super.onInit();
@@ -47,7 +49,6 @@ class QrCodeController extends GetxController
isProcessingResult.value = true;
scannerController.stop();
animationController.stop();
print("相机识别到的内容: ${barcode.rawValue!}");
renderResult(barcode.rawValue!);
}
}
@@ -57,10 +58,10 @@ class QrCodeController extends GetxController
isProcessingResult.value = false;
try {
scannerController.start();
animationController.repeat(reverse: false);
} catch (e) {
print("无法重启相机: $e");
}
animationController.repeat(reverse: false);
}
/// 从相册选择图片并扫描二维码
@@ -120,10 +121,19 @@ class QrCodeController extends GetxController
/// 请求相机权限
void requestPermission() async {
var status = await Permission.camera.request();
hasPermission.value = status.isGranted;
if (!status.isGranted) {
showErrorToast('请授予相机权限以使用扫描功能');
Get.back();
if (status.isPermanentlyDenied) {
showErrorToast('相机权限已被永久拒绝,请到系统设置中开启');
// 延迟一会再引导用户去设置
Future.delayed(const Duration(seconds: 2), () => openAppSettings());
} else {
showErrorToast('请授予相机权限以使用扫描功能');
}
}
}
void requestPhotoPermission() async {

View File

@@ -29,27 +29,77 @@ class QrCodePage extends GetView<QrCodeController> {
),
],
),
body: Stack(
alignment: Alignment.center,
children: [
// 1. 使用 MobileScanner 作为扫描视图
MobileScanner(
controller: controller.scannerController,
onDetect: controller.onDetect,
// 您可以自定义扫描框的样式
scanWindow: Rect.fromCenter(
center: Offset(
MediaQuery.of(context).size.width / 2,
MediaQuery.of(context).size.height / 2 - 50,
),
width: 250,
height: 250,
body: Obx(() { // 1. 使用 Obx 包裹整个 body
// 根据权限状态来决定显示什么
if (controller.hasPermission.value) {
// 如果有权限,显示扫描器
return _buildScannerView(context);
} else {
// 如果没有权限,显示引导界面
return _buildPermissionDeniedView();
}
}),
);
}
Widget _buildScannerView(BuildContext context){
if (!controller.animationController.isAnimating) {
controller.animationController.repeat(reverse: false);
}
return Stack(
alignment: Alignment.center,
children: [
// 使用 MobileScanner 作为扫描视图
MobileScanner(
controller: controller.scannerController,
onDetect: controller.onDetect,
// 您可以自定义扫描框的样式
scanWindow: Rect.fromCenter(
center: Offset(
MediaQuery.of(context).size.width / 2,
MediaQuery.of(context).size.height / 2 - 50,
),
width: 250,
height: 250,
),
),
// 扫描动画和覆盖层
_buildScannerOverlay(context),
// 底部的功能按钮
Positioned(bottom: 80, left: 0, right: 0, child: _buildActionButtons()),
],
);
}
Widget _buildPermissionDeniedView() {
// 确保动画是停止的
if (controller.animationController.isAnimating) {
controller.animationController.stop();
}
return Container(
color: Colors.black,
alignment: Alignment.center,
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.no_photography, color: Colors.white70, size: 64),
const SizedBox(height: 16),
const Text(
'需要相机权限',
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
'请授予相机权限以使用扫码功能。',
style: TextStyle(color: Colors.white70, fontSize: 14),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: controller.requestPermission, // 点击按钮重新请求权限
child: const Text('授予权限'),
),
// 扫描动画和覆盖层
_buildScannerOverlay(context),
// 底部的功能按钮
Positioned(bottom: 80, left: 0, right: 0, child: _buildActionButtons()),
],
),
);