Files
AI-Health/health_app/lib/pages/report/ai_analysis_page.dart
MingNian c70d5d4be6 fix: 管理员系统 + 注册流程重构 + UI改版 + 全链路修复
- 新增管理员角色(手机号12345678910),管理员可增删医生、查看患者
- 注册页重构:去掉角色选择+审核码,改为选医生+填姓名
- 医生端点按DoctorId过滤患者,Patient↔Doctor关系建立
- Doctor/DoctorProfile/User实体新增关联字段
- JSON循环引用修复(IgnoreCycles)
- /api/doctors改为公开接口
- 登录闪屏修复+原生Android启动页
- 输入框全局白底+灰框
- 蓝牙重连同步修复
- Web端(doctor_web+health_app/web)全部删除
- 全局UI改版:白底企业风,新配色和组件系统
- 新品牌图标和启动图
2026-06-16 15:16:44 +08:00

293 lines
9.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<AiAnalysisPage> createState() => _AiAnalysisPageState();
}
class _AiAnalysisPageState extends ConsumerState<AiAnalysisPage> {
@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(title: const Text('报告解读')),
body: const Center(
child: CircularProgressIndicator(color: AppColors.primary),
),
);
}
final displayType = _displayName(analysis.reportType);
final isReviewed = reportItem?.status == 'DoctorReviewed';
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: [
// ── 1. 指标分析 ──
if (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. 综合解读 ──
_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),
// ── 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 _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;
}