flutter_integrated_whb/lib/pages/home/scan_page.dart

273 lines
8.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/pages/home/study/face_ecognition_page.dart';
import 'package:qhd_prevention/pages/home/work/risk_list_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:image_picker/image_picker.dart';
import 'package:qhd_prevention/tools/tools.dart';
class ScanPage extends StatefulWidget {
// const ScanPage({Key? key}) : super(key: key,);
const ScanPage({super.key, required this.totalList});
final List totalList;
@override
State<ScanPage> createState() => _ScanPageState();
}
class _ScanPageState extends State<ScanPage> {
final MobileScannerController _controller = MobileScannerController();
bool _torchOn = false;
@override
void initState() {
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future<void> _scanFromGallery() async {
final picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image == null) return;
try {
// ★ 新版:返回 BarcodeCapture?
final capture = await _controller.analyzeImage(image.path);
if (capture != null && capture.barcodes.isNotEmpty) {
final code = capture.barcodes.first.rawValue ?? '';
_showResult(code);
} else {
_showResult('未识别到二维码/条码');
}
} catch (e) {
_showResult('扫描失败:$e');
}
}
void _showResult(String result) {
try {
if (result.contains('STUDENT_ID')) {
final Map<String, dynamic> stuInfo = jsonDecode(result);
print('stuInfo: $stuInfo');
// 兼容性提取res.result.split("@")[1]
String? stuId;
final parts = result.split('@');
if (parts.length > 1) stuId = parts[1];
// userId = res.result.substring(0, res.result.indexOf('%_face'))
String? userId;
final idx = result.indexOf('%_face');
if (idx >= 0) {
userId = result.substring(0, idx);
}
print('stuId: $stuId, userId: $userId');
// 比较登录用户 id 与解析到的 stuInfo.USER_ID
if (SessionService.instance.loginUserId == stuInfo['USER_ID']) {
goToFace(stuInfo);
} else {
ToastUtil.showNormal(context, '当前登录账号不匹配,无法扫码学习,请切换至正确的账号后再尝试人脸识别!');
return;
}
} else {
// 不是 STUDENT_ID 的情况:按列表 id 匹配
bool found = false;
final listId = result;
for (final item in widget.totalList) {
if (item['LISTMANAGER_ID'] == listId) {
found = true;
goToList(listId: item['LISTMANAGER_ID'], listName: item['NAME']);
break;
}
}
if (!found) {
ToastUtil.showError(context, '无法检查该清单');
}
}
} catch (e, st) {
// 捕获解析或运行时错误
print('handleScanResult error: $e\n$st');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('扫码处理失败: ${e.toString()}')),
);
}
}
// 人脸识别跳转
void goToFace(Map<String, dynamic> stuInfo) async {
print('navigate to face with $stuInfo');
final passed = await pushPage<bool>(
FaceRecognitionPage(studentId: stuInfo['STUDENT_ID'], mode: FaceMode.auto),
context,
);
if (passed == true) {
ToastUtil.showSuccess(context, '验证成功');
} else {
ToastUtil.showError(context, '验证失败');
}
}
// 跳转到清单页面
void goToList({required String listId, required String listName}) {
print('navigate to list: $listId, name: $listName');
Navigator.pop(context,Animation);
pushPage(RiskListPage(1, listId), context);
}
@override
Widget build(BuildContext context) {
// 中心扫描框大小
const double scanSize = 250;
final Size screen = MediaQuery.of(context).size;
final double left = (screen.width - scanSize) / 2;
final double top = (screen.height - scanSize) / 3 - kToolbarHeight;
// 因为 SafeArea + AppBar 占了高度,所以减去 toolbar 高度
const double cornerSize = 20.0; // 角标正方形区域大小
const double strokeWidth = 4.0; // 边线宽度
return Scaffold(
appBar: MyAppbar(
title: "二维码/条码扫描",
actions: [
TextButton(
onPressed: _scanFromGallery,
child: const Text(
"相册",
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
],
),
body: Stack(
children: [
// 1. 摄像头预览
MobileScanner(
controller: _controller,
onDetect: (capture) {
for (final barcode in capture.barcodes) {
final code = barcode.rawValue;
if (code != null && mounted) {
_controller.stop();
_showResult(code);
break;
}
}
},
),
// 2. 半透明遮罩
// 顶部
// 1. 顶部遮罩
Positioned(
left: 0, right: 0, top: 0,
height: top, // 从顶到底部到扫描框上边缘
child: Container(color: Colors.black54),
),
// 2. 底部遮罩
Positioned(
left: 0, right: 0,
top: top + scanSize, // 从扫描框下边缘开始
bottom: 0,
child: Container(color: Colors.black54),
),
// 3. 左侧遮罩
Positioned(
left: 0,
top: top,
width: left, // 从屏幕左侧到扫描框左边缘
height: scanSize, // 和扫描框一样高
child: Container(color: Colors.black54),
),
// 4. 右侧遮罩
Positioned(
left: left + scanSize,
top: top,
right: 0,
height: scanSize, // 和扫描框一样高
child: Container(color: Colors.black54),
),
// 3. 扫描框四个角
// 左上
Positioned(
left: left,
top: top,
child: _corner(size: cornerSize, stroke: strokeWidth),
),
// 右上
Positioned(
left: left + scanSize - cornerSize,
top: top,
child: _corner(size: cornerSize, stroke: strokeWidth, rotation: 1),
),
// 左下
Positioned(
left: left,
top: top + scanSize - cornerSize,
child: _corner(size: cornerSize, stroke: strokeWidth, rotation: 3),
),
// 右下
Positioned(
left: left + scanSize - cornerSize,
top: top + scanSize - cornerSize,
child: _corner(size: cornerSize, stroke: strokeWidth, rotation: 2),
),
// 闪光灯按钮
Positioned(
left: (screen.width - 40) / 2,
top: top + scanSize - 60,
child: IconButton(
iconSize: 32,
color: Colors.white,
icon: Icon(_torchOn ? Icons.flashlight_off_outlined : Icons.flashlight_on_outlined),
onPressed: () {
_controller.toggleTorch();
setState(() {
_torchOn = !_torchOn;
});
},
),
),
],
),
);
}
/// 角装饰:一个 L 形的蓝色粗边
Widget _corner({
double size = 20,
double stroke = 4,
int rotation = 0, // 0=左上, 1=右上, 2=右下, 3=左下
}) {
return Transform.rotate(
angle: rotation * math.pi / 2,
child: Container(
width: size,
height: size,
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.blue, width: stroke),
left: BorderSide(color: Colors.blue, width: stroke),
),
),
),
);
}
}