diff --git a/ln_jq_app/assets/images/ic_attention@2x.png b/ln_jq_app/assets/images/ic_attention@2x.png new file mode 100644 index 0000000..81c56dc Binary files /dev/null and b/ln_jq_app/assets/images/ic_attention@2x.png differ diff --git a/ln_jq_app/assets/images/ic_upload@2x.png b/ln_jq_app/assets/images/ic_upload@2x.png new file mode 100644 index 0000000..cb0583c Binary files /dev/null and b/ln_jq_app/assets/images/ic_upload@2x.png differ diff --git a/ln_jq_app/lib/pages/c_page/car_info/controller.dart b/ln_jq_app/lib/pages/c_page/car_info/controller.dart index a2e99b3..dd957cb 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/controller.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/controller.dart @@ -1,5 +1,8 @@ import 'package:get/get.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:getx_scaffold/getx_scaffold.dart' as dio; +import 'package:image_picker/image_picker.dart'; +import 'package:image_picker_platform_interface/src/types/image_source.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/c_page/car_info/attachment_viewer_page.dart'; @@ -89,6 +92,114 @@ class CarInfoController extends GetxController with BaseControllerMixin { } } + //上传证件照 + void pickImage(String title, ImageSource source) async { + if (source == ImageSource.camera) { + takePhotoAndRecognize(title); + } else if (source == ImageSource.gallery) { + //相册选择逻辑 + var status = await Permission.photos.request(); + if (!status.isGranted) { + if (status.isPermanentlyDenied) openAppSettings(); + showErrorToast("需要相册权限才能拍照上传"); + return; + } + final XFile? image = await ImagePicker().pickImage( + source: ImageSource.gallery, + imageQuality: 80, + ); + if (image != null) { + _uploadAndSaveCertificate(title, image.path); + } + } + } + + void takePhotoAndRecognize(String title) async { + var status = await Permission.camera.request(); + if (!status.isGranted) { + if (status.isPermanentlyDenied) openAppSettings(); + showErrorToast("需要相机权限才能拍照上传"); + return; + } + + final XFile? photo = await ImagePicker().pickImage( + source: ImageSource.camera, + imageQuality: 80, // 压缩图片质量以加快上传 + ); + if (photo == null) return; + + _uploadAndSaveCertificate(title, photo.path); + } + + /// 提取共用的上传与关联证件逻辑 + void _uploadAndSaveCertificate(String title, String filePath) async { + // 上传文件 + String? imageUrl = await uploadFile(filePath); + if (imageUrl == null) return; + + // 根据标题映射业务类型 + Logger.d("message-$title-$imageUrl"); + String type = ""; + switch (title) { + case "行驶证": + type = "9"; + break; + case "营运证": + type = "10"; + break; + case "加氢证": + type = "12"; + break; + case "登记证": + type = "13"; + break; + default: + return; + } + + // 调用后台接口关联证件 + var response = await HttpService.to.post( + "appointment/truck/uploadCertificatePic", + data: { + "plateNumber": plateNumber, + "imageUrl": imageUrl, + "type": type, + }, + ); + + if (response != null) { + final result = BaseModel.fromJson(response.data); + if (result.code == 0) { + showSuccessToast("上传成功"); + getUserBindCarInfo(); // 重新拉取数据更新UI + } else { + showErrorToast(result.error); + } + } + } + + /// 上传图片 + Future uploadFile(String filePath) async { + showLoading("正在上传..."); + try { + dio.FormData formData = dio.FormData.fromMap({ + 'file': await dio.MultipartFile.fromFile(filePath, filename: 'ocr_identity.jpg'), + }); + + var response = await HttpService.to.post("appointment/ocr/upload", data: formData); + if (response != null) { + final result = BaseModel.fromJson(response.data); + if (result.code == 0) return result.data.toString(); + showErrorToast(result.error); + } + } catch (e) { + showErrorToast("图片上传失败"); + } finally { + dismissLoading(); + } + return null; + } + void getUserBindCarInfo() async { if (StorageService.to.hasVehicleInfo) { VehicleInfo? bean = StorageService.to.vehicleInfo; @@ -102,7 +213,7 @@ class CarInfoController extends GetxController with BaseControllerMixin { // 获取证件信息 final response = await HttpService.to.get( - 'appointment/vehicle/getPicInfoByVin?vin=$vin', + 'appointment/vehicle/getPicInfoByVin?vin=$vin&plateNumber=$plateNumber', ); if (response != null && response.data != null) { @@ -134,10 +245,10 @@ class CarInfoController extends GetxController with BaseControllerMixin { ...registerAttachments, ]; - color = data['color'].toString(); - hydrogenCapacity = data['hydrogenCapacity'].toString(); - rentFromCompany = data['rentFromCompany'].toString(); - address = data['address'].toString(); + color = data['color'].toString(); + hydrogenCapacity = data['hydrogenCapacity'].toString(); + rentFromCompany = data['rentFromCompany'].toString(); + address = data['address'].toString(); loadAllPdfs(); } diff --git a/ln_jq_app/lib/pages/c_page/car_info/view.dart b/ln_jq_app/lib/pages/c_page/car_info/view.dart index e5a7ab3..ee0d8b3 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/view.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_pdfview/flutter_pdfview.dart'; import 'package:get/get.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:ln_jq_app/common/login_util.dart'; import 'package:ln_jq_app/pages/c_page/message/view.dart'; import 'package:ln_jq_app/storage_service.dart'; @@ -368,7 +369,7 @@ class CarInfoPage extends GetView { children: [ _buildCertificateContent('行驶证', controller.drivingAttachments), _buildCertificateContent('营运证', controller.operationAttachments), - _buildCertificateContent('加氢资格证', controller.hydrogenationAttachments), + _buildCertificateContent('加氢证', controller.hydrogenationAttachments), _buildCertificateContent('登记证', controller.registerAttachments), ], ), @@ -388,7 +389,7 @@ class CarInfoPage extends GetView { child: Padding( padding: EdgeInsets.all(16.0), child: attachments.isEmpty - ? const Center(child: Text('暂无相关证件信息')) + ? _buildEmptyCertificateState(title) : Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, @@ -440,6 +441,158 @@ class CarInfoPage extends GetView { }); } + Widget _buildEmptyCertificateState(String title) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/ic_attention@2x.png', // 请替换为您的实际图片路径 + width: 120, + height: 120, + ), + const SizedBox(height: 16), + Text( + '您未上传“$title”', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Color.fromRGBO(51, 51, 51, 1), + ), + ), + const SizedBox(height: 8), + Text( + '上传后可提前通知加氢站报备\n大幅减少加氢站等待时间', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12.sp, + color: Color.fromRGBO(156, 163, 175, 1), + height: 1.5, + ), + ), + const SizedBox(height: 24), + SizedBox( + width: 200, + height: 44, + child: ElevatedButton.icon( + onPressed: () { + _showUploadDialog(title); + }, + icon: const Text( + '立即上传', + style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), + ), + label: Image.asset( + 'assets/images/ic_upload@2x.png', + height: 20.h, + width: 20.w, + ), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF017137), + foregroundColor: Colors.white, + shape: StadiumBorder(), + elevation: 0, + ), + ), + ), + ], + ); + } + + void _showUploadDialog(String title) { + Get.dialog( + Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Container( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '上传$title', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + const SizedBox(height: 12), + Text( + '请确保拍摄证件清晰可见,关键文字无反光遮挡,这将有助于快速通过审核', + style: TextStyle(fontSize: 13, color: Colors.grey[400], height: 1.5), + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: _buildUploadOption( + icon: Icons.camera_alt_outlined, + label: '拍照上传', + onTap: () { + controller.pickImage(title, ImageSource.camera); + Get.back(); + }, + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildUploadOption( + icon: Icons.image_outlined, + label: '相册上传', + onTap: () { + controller.pickImage(title, ImageSource.gallery); + Get.back(); + }, + ), + ), + ], + ), + ], + ), + ), + ), + ); + } + + // 构建弹窗内的选择按钮 + Widget _buildUploadOption({ + required IconData icon, + required String label, + required VoidCallback onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 24), + decoration: BoxDecoration( + color: const Color(0xFFF2F9F7), // 浅绿色背景 + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: const BoxDecoration( + color: Color(0xFF017137), // 深绿色圆圈 + shape: BoxShape.circle, + ), + child: Icon(icon, color: Colors.white, size: 28), + ), + const SizedBox(height: 12), + Text( + label, + style: const TextStyle( + fontSize: 14, + color: Color(0xFF333333), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ); + } + Widget _buildCertDetailItem( String label, String value, {