diff --git a/ln_jq_app/assets/html/map.html b/ln_jq_app/assets/html/map.html index 5e71f6b..7e79fb2 100644 --- a/ln_jq_app/assets/html/map.html +++ b/ln_jq_app/assets/html/map.html @@ -131,13 +131,13 @@ diff --git a/ln_jq_app/lib/pages/b_page/site/controller.dart b/ln_jq_app/lib/pages/b_page/site/controller.dart index 29448e5..ca92f57 100644 --- a/ln_jq_app/lib/pages/b_page/site/controller.dart +++ b/ln_jq_app/lib/pages/b_page/site/controller.dart @@ -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, 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 e1ae31a..f6a8c70 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/controller.dart @@ -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 { 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 0d95a51..9179e88 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/view.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/view.dart @@ -13,7 +13,7 @@ import 'reservation_list_bottomsheet.dart'; class ReservationPage extends GetView { ReservationPage({super.key}); - bool init = false; + @override Widget build(BuildContext context) { @@ -21,10 +21,7 @@ class ReservationPage extends GetView { 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 { ); } - 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({ diff --git a/ln_jq_app/lib/pages/qr_code/controller.dart b/ln_jq_app/lib/pages/qr_code/controller.dart index f43f9ac..b7ea3ff 100644 --- a/ln_jq_app/lib/pages/qr_code/controller.dart +++ b/ln_jq_app/lib/pages/qr_code/controller.dart @@ -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 { diff --git a/ln_jq_app/lib/pages/qr_code/view.dart b/ln_jq_app/lib/pages/qr_code/view.dart index 2fe4d59..1dd37d0 100644 --- a/ln_jq_app/lib/pages/qr_code/view.dart +++ b/ln_jq_app/lib/pages/qr_code/view.dart @@ -29,27 +29,77 @@ class QrCodePage extends GetView { ), ], ), - 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()), ], ), );