ocr识别,历史新增类型
This commit is contained in:
@@ -1,7 +1,4 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:intl/intl.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/pages/b_page/site/controller.dart'; // Reuse ReservationModel
|
import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
|
||||||
|
|
||||||
@@ -25,12 +22,14 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
|||||||
final RxBool hasData = false.obs;
|
final RxBool hasData = false.obs;
|
||||||
|
|
||||||
String get formattedStartDate => DateFormat('yyyy/MM/dd').format(startDate.value);
|
String get formattedStartDate => DateFormat('yyyy/MM/dd').format(startDate.value);
|
||||||
|
|
||||||
String get formattedEndDate => DateFormat('yyyy/MM/dd').format(endDate.value);
|
String get formattedEndDate => DateFormat('yyyy/MM/dd').format(endDate.value);
|
||||||
|
|
||||||
String stationName = "";
|
String stationName = "";
|
||||||
|
|
||||||
final Map<String, String> statusOptions = {
|
final Map<String, String> statusOptions = {
|
||||||
'': '全部',
|
'': '全部',
|
||||||
|
'100': '未预约加氢',
|
||||||
'0': '待加氢',
|
'0': '待加氢',
|
||||||
'1': '已加氢',
|
'1': '已加氢',
|
||||||
'2': '未加氢',
|
'2': '未加氢',
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:getx_scaffold/getx_scaffold.dart' as dio;
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||||
import 'package:ln_jq_app/common/login_util.dart';
|
import 'package:image_picker/image_picker.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/styles/theme.dart';
|
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||||
import 'package:ln_jq_app/storage_service.dart';
|
import 'package:ln_jq_app/storage_service.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
import 'package:photo_view/photo_view_gallery.dart';
|
import 'package:photo_view/photo_view_gallery.dart';
|
||||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
@@ -182,6 +181,8 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
// 加氢枪列表
|
// 加氢枪列表
|
||||||
final RxList<String> gasGunList = <String>[].obs;
|
final RxList<String> gasGunList = <String>[].obs;
|
||||||
|
final RxMap<String, Map<String, dynamic>> gasGunMap =
|
||||||
|
<String, Map<String, dynamic>>{}.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get listenLifecycleEvent => true;
|
bool get listenLifecycleEvent => true;
|
||||||
@@ -229,11 +230,9 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 创建一个每5分钟执行一次的周期性定时器
|
||||||
void startAutoRefresh() {
|
void startAutoRefresh() {
|
||||||
// 先停止已存在的定时器,防止重复启动
|
|
||||||
stopAutoRefresh();
|
stopAutoRefresh();
|
||||||
|
|
||||||
// 创建一个每5分钟执行一次的周期性定时器
|
|
||||||
_refreshTimer = Timer.periodic(const Duration(minutes: 5), (timer) {
|
_refreshTimer = Timer.periodic(const Duration(minutes: 5), (timer) {
|
||||||
renderData();
|
renderData();
|
||||||
});
|
});
|
||||||
@@ -241,12 +240,10 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
void onRefresh() => renderData(isRefresh: true);
|
void onRefresh() => renderData(isRefresh: true);
|
||||||
|
|
||||||
///停止定时器的方法
|
///停止定时器
|
||||||
void stopAutoRefresh() {
|
void stopAutoRefresh() {
|
||||||
// 如果定时器存在并且是激活状态,就取消它
|
|
||||||
_refreshTimer?.cancel();
|
_refreshTimer?.cancel();
|
||||||
_refreshTimer = null; // 置为null,方便判断
|
_refreshTimer = null;
|
||||||
print("【自动刷新】定时器已停止。");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取加氢枪列表
|
/// 获取加氢枪列表
|
||||||
@@ -259,7 +256,17 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
final result = BaseModel<dynamic>.fromJson(response.data);
|
final result = BaseModel<dynamic>.fromJson(response.data);
|
||||||
if (result.code == 0 && result.data != null) {
|
if (result.code == 0 && result.data != null) {
|
||||||
List dataList = result.data as List;
|
List dataList = result.data as List;
|
||||||
gasGunList.assignAll(dataList.map((e) => e['deviceName'].toString()).toList());
|
|
||||||
|
gasGunList.clear();
|
||||||
|
gasGunMap.clear();
|
||||||
|
|
||||||
|
for (var item in dataList) {
|
||||||
|
String name = item['deviceName'].toString();
|
||||||
|
// 将名称加入列表供 Dropdown/Picker 使用
|
||||||
|
gasGunList.add(name);
|
||||||
|
// 将完整对象存入 Map,方便后续通过 name 获取 sign
|
||||||
|
gasGunMap[name] = Map<String, dynamic>.from(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -327,14 +334,20 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 确认预约弹窗重构
|
/// 确认预约弹窗
|
||||||
Future<void> confirmReservation(
|
Future<void> confirmReservation(
|
||||||
String id, {
|
String id, {
|
||||||
bool isEdit = false,
|
bool isEdit = false,
|
||||||
bool isAdd = false,
|
bool isAdd = false,
|
||||||
}) async {
|
}) async {
|
||||||
ReservationModel item;
|
ReservationModel item;
|
||||||
if (isAdd) {
|
//处理是否是无预约车辆加氢数据
|
||||||
|
if (!isAdd) {
|
||||||
|
item = reservationList.firstWhere(
|
||||||
|
(item) => item.id == id,
|
||||||
|
orElse: () => throw Exception('Reservation not found'),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
// 如果是无预约车辆加氢,创建一个临时 model
|
// 如果是无预约车辆加氢,创建一个临时 model
|
||||||
item = ReservationModel(
|
item = ReservationModel(
|
||||||
id: "",
|
id: "",
|
||||||
@@ -364,13 +377,13 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
drivingAttachments: [],
|
drivingAttachments: [],
|
||||||
hydrogenationAttachments: [],
|
hydrogenationAttachments: [],
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
item = reservationList.firstWhere(
|
|
||||||
(item) => item.id == id,
|
|
||||||
orElse: () => throw Exception('Reservation not found'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//车牌输入
|
||||||
|
final TextEditingController plateController = TextEditingController(
|
||||||
|
text: item.plateNumber == "---" ? "" : item.plateNumber,
|
||||||
|
);
|
||||||
|
|
||||||
// 加氢量保留3位小数
|
// 加氢量保留3位小数
|
||||||
double initialAmount = double.tryParse(item.hydAmount) ?? 0.0;
|
double initialAmount = double.tryParse(item.hydAmount) ?? 0.0;
|
||||||
final TextEditingController amountController = TextEditingController(
|
final TextEditingController amountController = TextEditingController(
|
||||||
@@ -382,6 +395,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
Dialog(
|
Dialog(
|
||||||
|
insetPadding: EdgeInsets.only(left: 20.w, right: 20.w),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -399,19 +413,38 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
// 车牌号及标签
|
// 车牌号及标签
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Container(
|
||||||
item.plateNumber == "---" ? '-------' : item.plateNumber,
|
width: 80.w,
|
||||||
|
child: TextField(
|
||||||
|
controller: plateController,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.sp,
|
color: const Color.fromRGBO(51, 51, 51, 1),
|
||||||
fontWeight: FontWeight.w500,
|
fontSize: 14.sp,
|
||||||
color: item.plateNumber == "---" ? Colors.grey : Colors.black,
|
fontWeight: FontWeight.bold,
|
||||||
letterSpacing: item.plateNumber == "---" ? 2 : 0,
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: item.plateNumber == "---" ? '_ _ _ _ _ _' : '修正车牌',
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: const Color.fromRGBO(51, 51, 51, 1),
|
||||||
|
fontSize: 13.sp,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
isEdit
|
isEdit
|
||||||
? SizedBox()
|
? SizedBox()
|
||||||
: Container(
|
: GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
String? temp = await takePhotoAndRecognize(true);
|
||||||
|
if (temp != null && temp.isNotEmpty) {
|
||||||
|
plateController.text = temp;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 8,
|
horizontal: 8,
|
||||||
vertical: 2,
|
vertical: 2,
|
||||||
@@ -429,6 +462,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
SizedBox(width: 16.w),
|
SizedBox(width: 16.w),
|
||||||
if (item.plateNumber != "---" && item.hasDrivingAttachment)
|
if (item.plateNumber != "---" && item.hasDrivingAttachment)
|
||||||
buildInfoTag('行驶证', item.drivingAttachments),
|
buildInfoTag('行驶证', item.drivingAttachments),
|
||||||
@@ -539,15 +573,34 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
String? temp = await takePhotoAndRecognize(
|
||||||
|
false,
|
||||||
|
deviceName: selectedGun.value,
|
||||||
|
sign: getSignByDeviceName(selectedGun.value),
|
||||||
|
);
|
||||||
|
if (temp != null && temp.isNotEmpty) {
|
||||||
|
amountController.text = temp;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF017143),
|
color: const Color(0xFF017143),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: const Row(
|
child: const Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.qr_code_scanner, color: Colors.white, size: 18),
|
Icon(
|
||||||
|
Icons.camera_alt_outlined,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
SizedBox(width: 4),
|
SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'识别',
|
'识别',
|
||||||
@@ -556,6 +609,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -637,7 +691,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
"",
|
"",
|
||||||
item,
|
item,
|
||||||
gunNumber: selectedGun.value,
|
gunNumber: selectedGun.value,
|
||||||
plateNumber: item.plateNumber,
|
plateNumber: plateController.text,
|
||||||
isAdd: true,
|
isAdd: true,
|
||||||
);
|
);
|
||||||
Get.back();
|
Get.back();
|
||||||
@@ -655,7 +709,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
"",
|
"",
|
||||||
item,
|
item,
|
||||||
gunNumber: selectedGun.value,
|
gunNumber: selectedGun.value,
|
||||||
plateNumber: item.plateNumber,
|
plateNumber: plateController.text,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@@ -687,7 +741,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
"",
|
"",
|
||||||
item!,
|
item!,
|
||||||
gunNumber: selectedGun.value,
|
gunNumber: selectedGun.value,
|
||||||
plateNumber: item.plateNumber,
|
plateNumber: plateController.text,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -736,7 +790,7 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
barrierDismissible: true,
|
barrierDismissible: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1122,6 +1176,110 @@ class SiteController extends GetxController with BaseControllerMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//车牌&加氢量 识别
|
||||||
|
Future<String?> takePhotoAndRecognize(
|
||||||
|
bool isPlate, {
|
||||||
|
String deviceName = "",
|
||||||
|
String sign = "",
|
||||||
|
}) 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 "";
|
||||||
|
}
|
||||||
|
|
||||||
|
//上传文件
|
||||||
|
String? imageUrl = await uploadFile(photo.path);
|
||||||
|
String? ocrStr = "";
|
||||||
|
if (imageUrl != null) {
|
||||||
|
// 获取车牌号
|
||||||
|
if (isPlate) {
|
||||||
|
ocrStr = await getPlateNumber(imageUrl);
|
||||||
|
} else {
|
||||||
|
ocrStr = await getHyd(imageUrl, deviceName, sign);
|
||||||
|
}
|
||||||
|
return ocrStr;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSignByDeviceName(String deviceName) {
|
||||||
|
return gasGunMap[deviceName]?['sign']?.toString() ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 上传图片
|
||||||
|
Future<String?> uploadFile(String filePath) async {
|
||||||
|
showLoading("正在上传图片...");
|
||||||
|
try {
|
||||||
|
dio.FormData formData = dio.FormData.fromMap({
|
||||||
|
'file': await dio.MultipartFile.fromFile(filePath, filename: 'ocr_plate.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// OCR 识别
|
||||||
|
Future<String?> getPlateNumber(String imageUrl) async {
|
||||||
|
showLoading("正在识别车牌...");
|
||||||
|
try {
|
||||||
|
var response = await HttpService.to.get(
|
||||||
|
"appointment/ocr/getPlateNumber",
|
||||||
|
params: {'imageUrl': imageUrl},
|
||||||
|
);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//加氢量识别 (加油枪列表接口返回的deviceName) (加油枪列表接口返回的sign)
|
||||||
|
Future<String?> getHyd(String imageUrl, String deviceName, String sign) async {
|
||||||
|
showLoading("正在识别加氢量...");
|
||||||
|
try {
|
||||||
|
var response = await HttpService.to.post(
|
||||||
|
"appointment/hyd-ocr/get-info",
|
||||||
|
data: {"url": imageUrl, "deviceName": deviceName, "sign": sign},
|
||||||
|
);
|
||||||
|
if (response != null) {
|
||||||
|
final result = BaseModel.fromJson(response.data);
|
||||||
|
if (result.code == 0) return result.data["mass"].toString();
|
||||||
|
showErrorToast(result.error);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showErrorToast("车牌识别失败");
|
||||||
|
} finally {
|
||||||
|
dismissLoading();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
String leftHydrogen = "";
|
String leftHydrogen = "";
|
||||||
String orderAmount = "";
|
String orderAmount = "";
|
||||||
String completedAmount = "";
|
String completedAmount = "";
|
||||||
|
|||||||
Reference in New Issue
Block a user