import 'package:flutter/material.dart'; import '../../core/app_colors.dart'; import '../../core/app_theme.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/navigation_provider.dart'; import 'report_pages.dart'; /// AI 解读页 — 从服务器获取真实报告数据 class AiAnalysisPage extends ConsumerStatefulWidget { final String id; const AiAnalysisPage({super.key, required this.id}); @override ConsumerState createState() => _AiAnalysisPageState(); } class _AiAnalysisPageState extends ConsumerState { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(reportProvider.notifier).fetchReportDetail(widget.id); }); } @override Widget build(BuildContext context) { final analysis = ref.watch(reportProvider.select((s) => s.currentAnalysis)); final reports = ref.watch(reportProvider.select((s) => s.reports)); final reportItem = reports.where((r) => r.id == widget.id).firstOrNull; if (analysis == null) { return GradientScaffold( appBar: AppBar( leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref)), title: const Text('报告解读'), ), body: const Center( child: CircularProgressIndicator(color: AppColors.primary), ), ); } final displayType = _displayName(analysis.reportType); final aiStatus = reportItem?.aiStatus ?? analysis.aiStatus; final reviewStatus = reportItem?.reviewStatus ?? analysis.reviewStatus; final fileUrl = reportItem?.fileUrl ?? analysis.fileUrl; final isReviewed = reviewStatus == 'Reviewed'; final isAnalyzing = aiStatus == 'Analyzing'; final isFailed = aiStatus == 'Failed'; return GradientScaffold( appBar: AppBar( title: Text( displayType, style: const TextStyle( fontSize: 19, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), leading: IconButton( icon: const Icon(Icons.arrow_back, color: AppColors.textPrimary), onPressed: () { ref.read(reportProvider.notifier).clearAnalysis(); popRoute(ref); }, ), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (fileUrl != null && fileUrl.isNotEmpty) ...[ SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: () => pushRoute( ref, 'reportOriginal', params: {'url': fileUrl, 'title': displayType}, ), icon: const Icon(Icons.image_outlined, size: 18), label: const Text('查看原始报告'), style: OutlinedButton.styleFrom( foregroundColor: AppColors.primary, side: const BorderSide(color: AppColors.primaryLight), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), const SizedBox(height: 12), ], if (isAnalyzing || isFailed) ...[ _analysisStateCard( isFailed ? (analysis.summary.isNotEmpty ? analysis.summary : 'AI 分析失败,请重新上传或稍后重试') : 'AI 正在分析报告,请稍后刷新查看。', isFailed: isFailed, onRetry: isFailed ? () => ref.read(reportProvider.notifier).reanalyzeReport(widget.id) : null, ), const SizedBox(height: 20), ], // ── 1. 指标分析 ── if (!isAnalyzing && !isFailed && analysis.indicators.isNotEmpty) ...[ _sectionTitle('指标分析'), const SizedBox(height: 8), Container( decoration: _cardDeco(), child: Column( children: analysis.indicators .map((ind) => _indicatorRow(ind)) .toList(), ), ), const SizedBox(height: 20), ], // ── 2. 综合解读 ── if (!isAnalyzing && !isFailed) ...[ _sectionTitle('综合解读'), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(16), decoration: _cardDeco(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( analysis.summary.isNotEmpty ? analysis.summary : 'AI 正在分析中,请稍后刷新查看', style: const TextStyle( fontSize: 16, color: AppColors.textPrimary, height: 1.7, ), ), const SizedBox(height: 10), Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppColors.cardInner, borderRadius: BorderRadius.circular(10), ), child: const Text( '以上为AI预解读,不能替代医生诊断和治疗建议', style: TextStyle(fontSize: 13, color: AppColors.textHint), ), ), ], ), ), const SizedBox(height: 20), ], if (!isAnalyzing && !isFailed) ...[ // ── 3. 医生审核意见 ── _sectionTitle('医生审核意见'), const SizedBox(height: 8), if (isReviewed) _doctorReviewCard(reportItem!) else Container( padding: const EdgeInsets.all(16), decoration: _cardDeco(), child: const Row( children: [ Icon( Icons.hourglass_empty, size: 18, color: AppColors.textHint, ), SizedBox(width: 8), Text( '待审核', style: TextStyle( fontSize: 16, color: AppColors.textSecondary, ), ), Spacer(), Text( 'AI 预解读', style: TextStyle(fontSize: 14, color: AppColors.textHint), ), ], ), ), ], const SizedBox(height: 30), ], ), ), ); } BoxDecoration _cardDeco() => BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border), boxShadow: AppColors.cardShadowLight, ); Widget _analysisStateCard( String message, { required bool isFailed, VoidCallback? onRetry, }) { final color = isFailed ? AppColors.error : AppColors.primary; return Container( padding: const EdgeInsets.all(16), decoration: _cardDeco(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( isFailed ? Icons.error_outline : Icons.hourglass_empty, color: color, ), const SizedBox(width: 10), Expanded( child: Text( message, style: TextStyle( fontSize: 16, height: 1.6, color: isFailed ? AppColors.error : AppColors.textPrimary, fontWeight: FontWeight.w600, ), ), ), ], ), if (onRetry != null) ...[ const SizedBox(height: 14), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: onRetry, icon: const Icon(Icons.refresh, size: 18), label: const Text('重新分析'), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), ], ], ), ); } Widget _sectionTitle(String text) => Row( children: [ Container( width: 4, height: 16, decoration: BoxDecoration( color: AppColors.primary, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), Text( text, style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), ], ); Widget _indicatorRow(Indicator ind) { final (color, icon) = switch (ind.status) { 'high' => (AppColors.error, Icons.arrow_upward), 'low' => (AppColors.warning, Icons.arrow_downward), _ => (AppColors.success, Icons.check_circle), }; return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: const BoxDecoration( border: Border(bottom: BorderSide(color: AppColors.borderLight)), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( ind.name, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: AppColors.textPrimary, ), ), if (ind.referenceRange != null && ind.referenceRange!.isNotEmpty) Text( '参考值: ${ind.referenceRange}', style: const TextStyle( fontSize: 13, color: AppColors.textHint, ), ), ], ), ), Icon(icon, size: 14, color: color), const SizedBox(width: 4), Text( '${ind.value} ${ind.unit}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: color, ), ), ], ), ); } Widget _doctorReviewCard(ReportItem report) { return Container( padding: const EdgeInsets.all(16), decoration: _cardDeco(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (report.doctorName != null) Text( '审核医生:${report.doctorName} | ${report.reviewedAt?.toLocal().toString().substring(0, 16) ?? ''}', style: const TextStyle(fontSize: 14, color: AppColors.textHint), ), if (report.doctorComment != null && report.doctorComment!.isNotEmpty) ...[ const SizedBox(height: 10), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.cardInner, borderRadius: BorderRadius.circular(12), ), child: Text( report.doctorComment!, style: const TextStyle( fontSize: 16, color: AppColors.textPrimary, height: 1.5, ), ), ), ], if (report.doctorRecommendation != null && report.doctorRecommendation!.isNotEmpty) ...[ const SizedBox(height: 8), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.cardInner, borderRadius: BorderRadius.circular(12), ), child: Text( report.doctorRecommendation!, style: const TextStyle( fontSize: 16, color: AppColors.textPrimary, height: 1.5, ), ), ), ], ], ), ); } String _displayName(String t) => (t == 'Other' || t == 'other') ? '检查报告' : t; }