import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:file_picker/file_picker.dart'; import '../../core/navigation_provider.dart'; final reportProvider = NotifierProvider(ReportNotifier.new); class ReportState { final List reports; final String? uploadingImage; final bool isAnalyzing; final ReportAnalysis? currentAnalysis; ReportState({ this.reports = const [], this.uploadingImage, this.isAnalyzing = false, this.currentAnalysis, }); ReportState copyWith({ List? reports, String? uploadingImage, bool? isAnalyzing, ReportAnalysis? currentAnalysis, }) { return ReportState( reports: reports ?? this.reports, uploadingImage: uploadingImage ?? this.uploadingImage, isAnalyzing: isAnalyzing ?? this.isAnalyzing, currentAnalysis: currentAnalysis ?? this.currentAnalysis, ); } } class ReportItem { final String id; final String title; final String type; final DateTime uploadedAt; final String? imagePath; final bool hasAnalysis; ReportItem({ required this.id, required this.title, required this.type, required this.uploadedAt, this.imagePath, this.hasAnalysis = false, }); } class ReportAnalysis { final String reportId; final String reportType; final List indicators; final String summary; ReportAnalysis({ required this.reportId, required this.reportType, required this.indicators, required this.summary, }); } class Indicator { final String name; final String value; final String unit; final String status; final String? referenceRange; Indicator({ required this.name, required this.value, required this.unit, required this.status, this.referenceRange, }); } class ReportNotifier extends Notifier { static final _mockReports = [ ReportItem( id: '1', title: '血常规检查', type: '血液检查', uploadedAt: DateTime.now().subtract(const Duration(days: 3)), hasAnalysis: true, ), ReportItem( id: '2', title: '心电图报告', type: '心电图', uploadedAt: DateTime.now().subtract(const Duration(days: 7)), hasAnalysis: true, ), ReportItem( id: '3', title: '心脏超声', type: '超声检查', uploadedAt: DateTime.now().subtract(const Duration(days: 14)), hasAnalysis: false, ), ]; @override ReportState build() => ReportState(reports: _mockReports); void uploadImage(String path) async { state = state.copyWith(uploadingImage: path, isAnalyzing: true); await Future.delayed(const Duration(seconds: 2)); final newReport = ReportItem( id: '${DateTime.now().millisecondsSinceEpoch}', title: '检查报告', type: '影像报告', uploadedAt: DateTime.now(), imagePath: path, hasAnalysis: true, ); state = state.copyWith( reports: [newReport, ...state.reports], uploadingImage: null, isAnalyzing: false, currentAnalysis: _mockAnalysis, ); } void uploadFile(String path) async { state = state.copyWith(isAnalyzing: true); await Future.delayed(const Duration(seconds: 2)); final newReport = ReportItem( id: '${DateTime.now().millisecondsSinceEpoch}', title: '检查报告', type: 'PDF文档', uploadedAt: DateTime.now(), hasAnalysis: true, ); state = state.copyWith( reports: [newReport, ...state.reports], isAnalyzing: false, currentAnalysis: _mockAnalysis, ); } void viewAnalysis(String reportId) { state = state.copyWith(currentAnalysis: _mockAnalysis); } void clearAnalysis() { state = state.copyWith(currentAnalysis: null); } } final _mockAnalysis = ReportAnalysis( reportId: '1', reportType: '血常规检查', indicators: [ Indicator(name: '白细胞计数', value: '7.5', unit: '×10^9/L', status: 'normal', referenceRange: '4.0-10.0'), Indicator(name: '红细胞计数', value: '4.2', unit: '×10^12/L', status: 'normal', referenceRange: '3.5-5.5'), Indicator(name: '血红蛋白', value: '128', unit: 'g/L', status: 'low', referenceRange: '130-175'), Indicator(name: '血小板计数', value: '185', unit: '×10^9/L', status: 'normal', referenceRange: '100-300'), Indicator(name: '中性粒细胞百分比', value: '65', unit: '%', status: 'normal', referenceRange: '50-70'), Indicator(name: '淋巴细胞百分比', value: '28', unit: '%', status: 'normal', referenceRange: '20-40'), ], summary: '整体来看,您的血常规检查基本正常。血红蛋白略低于正常范围,建议适当补充营养,多吃富含铁质的食物如红肉、动物肝脏等。如有疲劳、头晕等症状,建议咨询医生进一步检查。', ); /// 报告列表页 class ReportListPage extends ConsumerWidget { const ReportListPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(reportProvider); if (state.isAnalyzing) { return Scaffold( appBar: AppBar(title: const Text('看报告')), body: const Center( child: Column(mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(color: Color(0xFF635BFF)), SizedBox(height: 16), Text('AI 正在分析报告...'), ]), ), ); } return Scaffold( appBar: AppBar( title: const Text('看报告'), centerTitle: true, ), floatingActionButton: _buildUploadButton(context, ref), body: state.reports.isEmpty ? _buildEmptyState(context) : ListView.builder( padding: const EdgeInsets.all(16), itemCount: state.reports.length, itemBuilder: (context, index) => _buildReportCard(context, ref, state.reports[index]), ), ); } Widget _buildUploadButton(BuildContext context, WidgetRef ref) { return FloatingActionButton( onPressed: () => _showUploadOptions(context, ref), backgroundColor: const Color(0xFF635BFF), child: const Icon(Icons.add), ); } void _showUploadOptions(BuildContext context, WidgetRef ref) { showModalBottomSheet( context: context, builder: (ctx) => SafeArea( child: Wrap(children: [ ListTile( leading: const Icon(Icons.camera_alt), title: const Text('拍照上传'), onTap: () async { Navigator.pop(ctx); final picker = ImagePicker(); final picked = await picker.pickImage(source: ImageSource.camera, imageQuality: 85); if (picked != null) { ref.read(reportProvider.notifier).uploadImage(picked.path); } }, ), ListTile( leading: const Icon(Icons.photo_library), title: const Text('从相册选择'), onTap: () async { Navigator.pop(ctx); final picker = ImagePicker(); final picked = await picker.pickImage(source: ImageSource.gallery, imageQuality: 85); if (picked != null) { ref.read(reportProvider.notifier).uploadImage(picked.path); } }, ), ListTile( leading: const Icon(Icons.file_open), title: const Text('上传PDF文件'), onTap: () async { Navigator.pop(ctx); final result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['pdf']); if (result != null && result.files.isNotEmpty) { ref.read(reportProvider.notifier).uploadFile(result.files.first.path!); } }, ), ]), ), ); } Widget _buildEmptyState(BuildContext context) { return Center( child: Column(mainAxisSize: MainAxisSize.min, children: [ Container( width: 120, height: 120, decoration: BoxDecoration( color: const Color(0xFFF5F3FF), borderRadius: BorderRadius.circular(60), ), child: const Icon(Icons.file_open, size: 48, color: Color(0xFF635BFF)), ), const SizedBox(height: 20), const Text('暂无检查报告', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), const SizedBox(height: 8), const Text('点击下方按钮上传报告', style: TextStyle(fontSize: 14, color: Color(0xFF999999))), ]), ); } Widget _buildReportCard(BuildContext context, WidgetRef ref, ReportItem report) { return Container( margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(10), blurRadius: 4, offset: const Offset(0, 2))], ), child: ListTile( leading: Container( width: 48, height: 48, decoration: BoxDecoration( color: const Color(0xFFF5F3FF), borderRadius: BorderRadius.circular(12), ), child: _getReportIcon(report.type), ), title: Text(report.title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(report.type, style: TextStyle(fontSize: 14, color: Colors.grey[500])), Text(_formatDate(report.uploadedAt), style: TextStyle(fontSize: 12, color: Colors.grey[400])), ]), trailing: report.hasAnalysis ? const Icon(Icons.check_circle, size: 20, color: Color(0xFF43A047)) : const Icon(Icons.arrow_forward_ios, size: 18, color: Color(0xFFCCCCCC)), onTap: () { ref.read(reportProvider.notifier).viewAnalysis(report.id); pushRoute(ref, 'reportDetail', params: {'id': report.id}); }, ), ); } Widget _getReportIcon(String type) { final icons = { '血液检查': const Icon(Icons.bloodtype, size: 24, color: Color(0xFF635BFF)), '心电图': const Icon(Icons.monitor_heart, size: 24, color: Color(0xFF635BFF)), '超声检查': const Icon(Icons.image, size: 24, color: Color(0xFF635BFF)), '影像报告': const Icon(Icons.image, size: 24, color: Color(0xFF635BFF)), 'PDF文档': const Icon(Icons.picture_as_pdf, size: 24, color: Color(0xFF635BFF)), }; return icons[type] ?? const Icon(Icons.description, size: 24, color: Color(0xFF635BFF)); } String _formatDate(DateTime date) { final now = DateTime.now(); final diff = now.difference(date); if (diff.inDays == 0) return '今天'; if (diff.inDays == 1) return '昨天'; if (diff.inDays < 7) return '${diff.inDays}天前'; return '${date.month}月${date.day}日'; } } /// 报告详情页 class ReportDetailPage extends ConsumerWidget { final String id; const ReportDetailPage({super.key, required this.id}); @override Widget build(BuildContext context, WidgetRef ref) { final analysis = ref.watch(reportProvider.select((s) => s.currentAnalysis)); if (analysis == null) { return Scaffold( appBar: AppBar(title: const Text('报告详情')), body: const Center(child: Text('暂无分析数据')), ); } return Scaffold( appBar: AppBar( title: const Text('报告解读'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { ref.read(reportProvider.notifier).clearAnalysis(); popRoute(ref); }, ), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildReportHeader(analysis), const SizedBox(height: 20), _buildAnalysisSection(analysis), const SizedBox(height: 20), _buildSummarySection(analysis), const SizedBox(height: 30), ]), ), ); } Widget _buildReportHeader(ReportAnalysis analysis) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFF5F3FF), borderRadius: BorderRadius.circular(16), ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ const Text('📋', style: TextStyle(fontSize: 24)), const SizedBox(width: 12), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(analysis.reportType, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), const SizedBox(height: 4), const Text('AI 预解读结果', style: TextStyle(fontSize: 14, color: Color(0xFF666666))), ]), ]), ]), ); } Widget _buildAnalysisSection(ReportAnalysis analysis) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFE8E6FF), width: 1.5), ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(16), child: Row(children: [ const Text('🧪', style: TextStyle(fontSize: 20)), const SizedBox(width: 8), const Text('指标分析', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), ]), ), ...analysis.indicators.map((ind) => _buildIndicatorRow(ind)), ]), ); } Widget _buildIndicatorRow(Indicator ind) { Color statusColor; IconData statusIcon; switch (ind.status) { case 'high': statusColor = const Color(0xFFE53935); statusIcon = Icons.arrow_upward; break; case 'low': statusColor = const Color(0xFFF9A825); statusIcon = Icons.arrow_downward; break; default: statusColor = const Color(0xFF43A047); statusIcon = Icons.check_circle; } return Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 12), decoration: const BoxDecoration(border: Border(bottom: BorderSide(color: Color(0xFFF0F0F0)))), child: Row(children: [ Expanded( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(ind.name, style: const TextStyle(fontSize: 14)), if (ind.referenceRange != null) Text('参考值: ${ind.referenceRange}', style: TextStyle(fontSize: 12, color: Colors.grey[400])), ]), ), const SizedBox(width: 16), Column(children: [ Text('${ind.value} ${ind.unit}', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: statusColor)), Icon(statusIcon, size: 16, color: statusColor), ]), ]), ); } Widget _buildSummarySection(ReportAnalysis analysis) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFFEF3C7), borderRadius: BorderRadius.circular(16), ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ const Text('💡', style: TextStyle(fontSize: 20)), const SizedBox(width: 8), const Text('综合解读', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFFD97706))), ]), const SizedBox(height: 12), Text(analysis.summary, style: const TextStyle(fontSize: 14, color: Color(0xFF92400E), height: 1.6)), const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: const Text('⚠️ AI 解读仅供参考,请以医生诊断为准', style: TextStyle(fontSize: 13, color: Color(0xFFD97706))), ), ]), ); } }