import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:qhd_prevention/constants/app_enums.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/http/ApiService.dart'; import 'package:qhd_prevention/http/modules/file_api.dart'; import 'package:qhd_prevention/pages/my_appbar.dart'; // 单个隐患数据模型 class HiddenItem { String? rectificationSuggestions; String? hiddenDescr; String? legalBasis; bool isSelect=false; HiddenItem({ this.rectificationSuggestions, this.hiddenDescr, this.legalBasis, }); factory HiddenItem.fromJson(Map json) { return HiddenItem( rectificationSuggestions: json['rectificationSuggestions'] as String?, hiddenDescr: json['hiddenDescr'] as String?, legalBasis: json['legalBasis'] as String?, ); } Map toJson() { return { 'rectificationSuggestions': rectificationSuggestions, 'hiddenDescr': hiddenDescr, 'legalBasis': legalBasis, }; } } class AiPage extends StatefulWidget { const AiPage(this.imagePath, {super.key}); final String imagePath; @override State createState() => _AiPageState(); } class _AiPageState extends State with SingleTickerProviderStateMixin { Image? _selectedImage; bool _isScanning = false; bool _showResult = false; late AnimationController _animationController; late Animation _animation; List hiddenItems = []; // List serialNumbers = [ // 'SN-001-2024', // 'SN-002-2024', // 'SN-003-2024', // 'SN-004-2024', // 'SN-005-2024', // 'SN-006-2024', // 'SN-007-2024', // ]; // Map selectedItems = {}; @override void initState() { super.initState(); _animationController = AnimationController( duration: Duration(seconds: 2), // 单次扫描时间改为2秒 vsync: this, ); // 使用往复动画,让扫描线来回移动 _animation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); // // 初始化选择状态 // for (var serial in serialNumbers) { // selectedItems[serial] = false; // } _pickImage(); _getAiRecognize(); } /// 手动拍照并上传 Future _getAiRecognize() async { try { final raw = await FileApi.uploadFile( widget.imagePath, UploadFileType.aiRecognitionImages,''); if (raw['success'] ) { // String imagePath= ApiService.baseImgPath+raw['data']['filePath']; String imagePath= 'https://jpfz.qhdsafety.com/gbsFileTest/20251201171655.jpg'; final res = await HiddenDangerApi.aiRecognitionImages(imagePath); if (res['success'] ) { List data=res['data']['aiHiddens']??[]; if(data.isNotEmpty){ for(int i=0;i; hiddenItems.add(HiddenItem.fromJson(itemJson)); } catch (e) { print('解析失败: $e, 原始数据: $item'); } } else if (data[i] is Map) { hiddenItems.add(HiddenItem.fromJson(data[i])); } } _animationController.stop(); setState(() { _isScanning = false; _showResult = true; }); }else { ToastUtil.showError(context, '未获取到隐患,请重新拍照'); } } else { ToastUtil.showError(context, '识别失败,请重试'); } }else{ // _showMessage('反馈提交失败'); ToastUtil.showError(context, '识别失败,请重试'); } } catch (e, st) { debugPrint('[FaceRecognition] manual capture error: $e\n$st'); ToastUtil.showError(context, '识别失败,请重试'); } } @override void dispose() { _animationController.dispose(); super.dispose(); } Future _pickImage() async { // 这里使用 image_picker 包来选择图片 // final pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery); // 为了演示,我们使用一个网络图片 setState(() { _selectedImage = Image.file( File(widget.imagePath),//'https://picsum.photos/400/600', fit: BoxFit.cover, ); _isScanning = true; }); // 重置动画到开始位置(顶部) _animationController.value = 0.0; // 开始往复扫描动画 _animationController.repeat(reverse: true); // 5秒后结束扫描 // Future.delayed(Duration(seconds: 5), () { // _animationController.stop(); // setState(() { // _isScanning = false; // _showResult = true; // }); // }); } Widget _buildImagePickerBox() { return GestureDetector( onTap: _pickImage, child: Container( width: 200, height: 150, decoration: BoxDecoration( border: Border.all(color: Colors.grey, width: 2), borderRadius: BorderRadius.circular(12), ), child: _selectedImage == null ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.photo_library, size: 40, color: Colors.grey), SizedBox(height: 8), Text('选择图片', style: TextStyle(color: Colors.grey)), ], ) : ClipRRect( borderRadius: BorderRadius.circular(10), child: _selectedImage, ), ), ); } Widget _buildScanLine() { return AnimatedBuilder( animation: _animation, builder: (context, child) { // 计算扫描线位置,从顶部开始,确保不超出屏幕底部 double screenHeight = (MediaQuery.of(context).size.height)-100; double scanLineHeight = 4.0; // 扫描线高度 // 计算扫描线顶部位置,确保扫描线底部不超出屏幕 double maxTopPosition = screenHeight - scanLineHeight; double scanTopPosition = screenHeight * _animation.value; // 限制扫描线位置在屏幕范围内 scanTopPosition = scanTopPosition.clamp(0.0, maxTopPosition); return Positioned( top: scanTopPosition, child: Container( width: MediaQuery.of(context).size.width, height: 3, decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.transparent, Colors.blue, Colors.blue, Colors.blue, Colors.transparent, ], stops: [0.0, 0.2, 0.5, 0.8, 1.0], ), boxShadow: [ BoxShadow( color: Colors.blue.withOpacity(0.5), blurRadius: 8, spreadRadius: 2, ), ], ), ), ); }, ); } Widget _buildScanOverlay() { return AnimatedBuilder( animation: _animation, builder: (context, child) { return Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withOpacity(0.7), Colors.black.withOpacity(0.3), Colors.transparent, Colors.black.withOpacity(0.3), Colors.black.withOpacity(0.7), ], stops: [0.0, 0.2, 0.5, 0.8, 1.0], transform: GradientRotation(_animation.value * 3.14159), // 旋转渐变方向 ), ), ); }, ); } Widget _buildResultPanel() { return Positioned( top: 20, left: 20, child: AnimatedContainer( duration: Duration(milliseconds: 500), width: _showResult ? 80 : 200, height: _showResult ? 60 : 150, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey), boxShadow: [ BoxShadow( color: Colors.black26, blurRadius: 8, offset: Offset(2, 2), ), ], ), child: _selectedImage != null ? ClipRRect( borderRadius: BorderRadius.circular(8), child: _selectedImage, ) : SizedBox(), ), ); } Widget _buildSerialNumberList() { return AnimatedOpacity( opacity: _showResult ? 1.0 : 0.0, duration: Duration(milliseconds: 500), child: Container( margin: EdgeInsets.only(top: 40, left: 20, right: 20, bottom: 15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '检测到的隐患:', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), SizedBox(height: 10), Expanded( child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8), ), child: ListView.builder( itemCount: hiddenItems.length, itemBuilder: (context, index) { final serial = hiddenItems[index].hiddenDescr; return Container( decoration: BoxDecoration( border: Border( bottom: index < hiddenItems.length - 1 ? BorderSide(color: Colors.grey.shade300) : BorderSide.none, ), ), child: CheckboxListTile( title: Text(serial??'', style: TextStyle(fontSize: 14)), value: hiddenItems[index].isSelect , onChanged: (bool? value) { setState(() { hiddenItems[index].isSelect=value ?? false; // selectedItems[serial??''] = value ?? false; }); }, controlAffinity: ListTileControlAffinity.leading, dense: true, ), ); }, ), ), ), SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( child: Padding( padding: EdgeInsets.symmetric(horizontal: 10), child: ElevatedButton( onPressed: () { // 取消操作 _resetPage(); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey.shade400, foregroundColor: Colors.white, minimumSize: Size(0, 50), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('合并', style: TextStyle(fontSize: 16)), ), ), ), Expanded( child: Padding( padding: EdgeInsets.symmetric(horizontal: 10), child: ElevatedButton( onPressed: () { // 确认操作 _showConfirmationDialog(); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, minimumSize: Size(0, 50), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('处理', style: TextStyle(fontSize: 16)), ), ), ), ], ), ], ), ), ); } void _showConfirmationDialog() { // final selectedSerials = // selectedItems.entries // .where((entry) => entry.value) // .map((entry) => entry.key) // .toList(); // // showDialog( // context: context, // builder: // (context) => AlertDialog( // title: Text('确认选择'), // content: Column( // mainAxisSize: MainAxisSize.min, // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // Text('已选择 ${selectedSerials.length} 个序列号:'), // SizedBox(height: 10), // if (selectedSerials.isNotEmpty) // ...selectedSerials // .map((serial) => Text('• $serial')) // .toList(), // ], // ), // actions: [ // TextButton( // onPressed: () => Navigator.of(context).pop(), // child: Text('确定'), // ), // ], // ), // ); } void _resetPage() { // setState(() { // _selectedImage = null; // _isScanning = false; // _showResult = false; // _animationController.reset(); // // // // 重置选择状态 // // for (var key in selectedItems.keys) { // // selectedItems[key] = false; // // } // }); } @override Widget build(BuildContext context) { return Scaffold( appBar: MyAppbar(title: 'Ai识别'), body: Stack( children: [ // 主内容 Column( children: [ SizedBox(height: 50), // if (!_showResult) // Center(child: _buildImagePickerBox()), if (_showResult) Expanded(child: _buildSerialNumberList()), ], ), // 全屏扫描效果 if (_isScanning && _selectedImage != null) Container( color: Colors.black, child: Stack( children: [ // 全屏图片 SizedBox.expand(child: _selectedImage), // 扫描遮罩效果 _buildScanOverlay(), // 扫描线 _buildScanLine(), // 扫描提示 Positioned( top: 50, left: 0, right: 0, child: Center( child: Column( children: [ Text( '扫描中...', style: TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), SizedBox(height: 10), Container( width: 30, height: 30, child: CircularProgressIndicator( color: Colors.blue, strokeWidth: 3, ), ), ], ), ), ), ], ), ), // 结果面板 if (_showResult) _buildResultPanel(), ], ), ); } }