feat: UI全面改造 + 后端多项修复

【后端修复】
- 删除diet/consultation agent假工具声明
- 修复DayOfWeek实体注释
- 修复Vision API content序列化
- cleanup_service级联删除修复
- 用药提醒时区偏差修复
- 统一DateTime处理(UtcNow+8)
- 新增UTC DateTime JSON转换器

【前端UI重构】
- 配色体系全面更新(#8B5CF6淡紫+#F0ECFF背景)
- 登录页重设计
- 首页重设计(透明顶栏、渐变背景、胶囊输入区)
- 聊天卡片加白蓝边框、渐变标题
- 侧边栏重构(渐变背景、合并顶部、删除底部设置)
- 确认卡片可编辑字段恢复
- 所有子页面加返回按钮
- catch异常加日志
- 删除后refresh provider缓存
This commit is contained in:
MingNian
2026-06-10 18:27:38 +08:00
parent 3964cf2bcb
commit 63d2092c24
46 changed files with 2003 additions and 1431 deletions

View File

@@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:file_picker/file_picker.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../../core/app_colors.dart';
import '../../core/app_theme.dart';
import '../../core/navigation_provider.dart';
import '../../providers/auth_provider.dart';
@@ -129,7 +130,9 @@ class ReportNotifier extends Notifier<ReportState> {
);
}).toList();
state = state.copyWith(reports: reports);
} catch (_) {}
} catch (e) {
debugPrint('[Report] 加载报告列表失败: $e');
}
}
void fetchReportDetail(String reportId) async {
@@ -234,6 +237,7 @@ class ReportListPage extends ConsumerWidget {
return Scaffold(
appBar: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref)),
title: const Text('看报告'),
centerTitle: true,
),
@@ -311,7 +315,7 @@ class ReportListPage extends ConsumerWidget {
width: 120,
height: 120,
decoration: BoxDecoration(
color: const Color(0xFFF0F2FF),
color: AppColors.primaryLight,
borderRadius: BorderRadius.circular(60),
),
child: const Icon(Icons.file_open, size: 48, color: AppTheme.primaryLight),
@@ -319,7 +323,7 @@ class ReportListPage extends ConsumerWidget {
const SizedBox(height: 20),
const Text('暂无检查报告', style: TextStyle(fontSize: 21, fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
const Text('点击下方按钮上传报告', style: TextStyle(fontSize: 17, color: Color(0xFF999999))),
const Text('点击下方按钮上传报告', style: TextStyle(fontSize: 17, color: AppColors.textHint)),
]),
);
}
@@ -328,24 +332,24 @@ class ReportListPage extends ConsumerWidget {
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(10), blurRadius: 4, offset: const Offset(0, 2))],
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.rLg),
boxShadow: [AppTheme.shadowCard],
),
child: ListTile(
leading: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: const Color(0xFFF0F2FF),
color: AppColors.primaryLight,
borderRadius: BorderRadius.circular(12),
),
child: _getReportIcon(report.type),
),
title: Text(report.title, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600)),
subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(report.type, style: TextStyle(fontSize: 17, color: Colors.grey[500])),
Text(_formatDate(report.uploadedAt), style: TextStyle(fontSize: 15, color: Colors.grey[400])),
Text(report.type, style: TextStyle(fontSize: 17, color: AppColors.textSecondary)),
Text(_formatDate(report.uploadedAt), style: TextStyle(fontSize: 15, color: AppColors.textHint)),
]),
trailing: Row(
mainAxisSize: MainAxisSize.min,
@@ -354,38 +358,36 @@ class ReportListPage extends ConsumerWidget {
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFDCFCE7),
color: AppColors.successLight,
borderRadius: BorderRadius.circular(4),
),
child: const Text('已审核', style: TextStyle(fontSize: 13, color: Color(0xFF43A047))),
child: const Text('已审核', style: TextStyle(fontSize: 13, color: AppColors.success)),
)
else if (!report.hasAnalysis)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFE3F2FD),
borderRadius: BorderRadius.circular(4),
color: AppTheme.primaryLight,
borderRadius: BorderRadius.circular(AppTheme.rXs),
),
child: const Text('分析中', style: TextStyle(fontSize: 13, color: Color(0xFF2196F3))),
child: Text('分析中', style: TextStyle(fontSize: 13, color: AppTheme.primary)),
)
else if (report.status == 'PendingDoctor')
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFFFF3E0),
color: AppColors.warningLight,
borderRadius: BorderRadius.circular(4),
),
child: const Text('待审核', style: TextStyle(fontSize: 13, color: Color(0xFFF59E0B))),
child: const Text('待审核', style: TextStyle(fontSize: 13, color: AppColors.warning)),
),
const SizedBox(width: 8),
report.hasAnalysis
? const Icon(Icons.check_circle, size: 23, color: Color(0xFF43A047))
: const Icon(Icons.arrow_forward_ios, size: 21, color: Color(0xFFCCCCCC)),
? Icon(Icons.check_circle, size: 23, color: AppColors.success)
: Icon(Icons.arrow_forward_ios, size: 21, color: AppColors.textHint),
],
),
onTap: () {
pushRoute(ref, 'reportDetail', params: {'id': report.id});
},
onTap: () => pushRoute(ref, 'reportDetail', params: {'id': report.id}),
),
);
}
@@ -487,23 +489,23 @@ class _ReportDetailPageState extends ConsumerState<ReportDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFDCFCE7),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF43A047).withAlpha(50)),
color: AppColors.successLight,
borderRadius: BorderRadius.circular(AppTheme.rLg),
border: Border.all(color: AppColors.success.withAlpha(50)),
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
const Text('', style: TextStyle(fontSize: 23)),
const SizedBox(width: 8),
const Text('医生审核意见', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: Color(0xFF2E7D32))),
const Text('医生审核意见', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: AppColors.success)),
]),
if (report.doctorName != null) ...[
const SizedBox(height: 4),
Text('审核医生:${report.doctorName}', style: const TextStyle(fontSize: 16, color: Color(0xFF4CAF50))),
Text('审核医生:${report.doctorName}', style: TextStyle(fontSize: 16, color: AppColors.success)),
],
if (report.reviewedAt != null) ...[
const SizedBox(height: 2),
Text('审核时间:${report.reviewedAt!.toLocal().toString().substring(0, 19)}', style: const TextStyle(fontSize: 15, color: Color(0xFF81C784))),
Text('审核时间:${report.reviewedAt!.toLocal().toString().substring(0, 19)}', style: TextStyle(fontSize: 15, color: AppColors.success.withAlpha(180))),
],
if (report.severity != null) ...[
const SizedBox(height: 6),
@@ -515,10 +517,10 @@ class _ReportDetailPageState extends ConsumerState<ReportDetailPage> {
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.rMd),
),
child: Text('💬 ${report.doctorComment!}', style: const TextStyle(fontSize: 18, color: Color(0xFF1A1A1A), height: 1.5)),
child: Text('💬 ${report.doctorComment!}', style: TextStyle(fontSize: 18, color: AppColors.textPrimary, height: 1.5)),
),
],
if (report.doctorRecommendation != null && report.doctorRecommendation!.isNotEmpty) ...[
@@ -527,10 +529,10 @@ class _ReportDetailPageState extends ConsumerState<ReportDetailPage> {
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.rMd),
),
child: Text('📝 ${report.doctorRecommendation!}', style: const TextStyle(fontSize: 17, color: Color(0xFF333333), height: 1.5)),
child: Text('📝 ${report.doctorRecommendation!}', style: TextStyle(fontSize: 17, color: AppColors.textPrimary, height: 1.5)),
),
],
]),
@@ -539,15 +541,15 @@ class _ReportDetailPageState extends ConsumerState<ReportDetailPage> {
Widget _severityBadge(String severity) {
final (label, color, bg) = switch (severity) {
'Normal' => ('🟢 正常', const Color(0xFF43A047), const Color(0xFFDCFCE7)),
'Abnormal' => ('🟡 轻度异常', const Color(0xFFF59E0B), const Color(0xFFFFF3E0)),
'Severe' => ('🟠 中度异常', const Color(0xFFEF4444), const Color(0xFFFEE2E2)),
'Critical' => ('🔴 重度异常', const Color(0xFFDC2626), const Color(0xFFFEE2E2)),
_ => (severity, Colors.grey, Colors.grey.withAlpha(30)),
'Normal' => ('🟢 正常', AppColors.success, AppColors.successLight),
'Abnormal' => ('🟡 轻度异常', AppColors.warning, AppColors.warningLight),
'Severe' => ('🟠 中度异常', AppColors.error, AppColors.errorLight),
'Critical' => ('🔴 重度异常', AppColors.error, AppColors.errorLight),
_ => (severity, AppColors.textSecondary, AppColors.border),
};
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(6)),
decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(AppTheme.rXs)),
child: Text(label, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: color)),
);
}
@@ -556,8 +558,8 @@ class _ReportDetailPageState extends ConsumerState<ReportDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFF0F2FF),
borderRadius: BorderRadius.circular(16),
color: AppColors.primaryLight,
borderRadius: BorderRadius.circular(AppTheme.rLg),
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
@@ -566,7 +568,7 @@ class _ReportDetailPageState extends ConsumerState<ReportDetailPage> {
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(analysis.reportType, style: const TextStyle(fontSize: 21, fontWeight: FontWeight.w600)),
const SizedBox(height: 4),
const Text('AI 预解读结果', style: TextStyle(fontSize: 17, color: Color(0xFF666666))),
Text('AI 预解读结果', style: TextStyle(fontSize: 17, color: AppColors.textSecondary)),
]),
]),
]),
@@ -576,9 +578,9 @@ class _ReportDetailPageState extends ConsumerState<ReportDetailPage> {
Widget _buildAnalysisSection(ReportAnalysis analysis) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFD8DCFD), width: 1.5),
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.rLg),
border: Border.all(color: AppColors.border, width: 1),
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Padding(
@@ -593,18 +595,18 @@ class _ReportDetailPageState extends ConsumerState<ReportDetailPage> {
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFFFFF3E0),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFFFE0B2)),
color: AppColors.warningLight,
borderRadius: BorderRadius.circular(AppTheme.rXs),
border: Border.all(color: AppColors.warning.withAlpha(80)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.info_outline, size: 19, color: Color(0xFFE65100)),
Icon(Icons.info_outline, size: 19, color: AppColors.warning),
const SizedBox(width: 6),
const Text(
Text(
'AI 预解读 · 待医生确认',
style: TextStyle(fontSize: 16, color: Color(0xFFE65100), fontWeight: FontWeight.w500),
style: TextStyle(fontSize: 16, color: AppColors.warning, fontWeight: FontWeight.w500),
),
],
),
@@ -620,28 +622,28 @@ class _ReportDetailPageState extends ConsumerState<ReportDetailPage> {
IconData statusIcon;
switch (ind.status) {
case 'high':
statusColor = const Color(0xFFE53935);
statusColor = AppColors.error;
statusIcon = Icons.arrow_upward;
break;
case 'low':
statusColor = const Color(0xFFF9A825);
statusColor = AppColors.warning;
statusIcon = Icons.arrow_downward;
break;
default:
statusColor = const Color(0xFF43A047);
statusColor = AppColors.success;
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)))),
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: AppColors.border))),
child: Row(children: [
Expanded(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(ind.name, style: const TextStyle(fontSize: 17)),
if (ind.referenceRange != null)
Text('参考值: ${ind.referenceRange}', style: TextStyle(fontSize: 15, color: Colors.grey[400])),
Text('参考值: ${ind.referenceRange}', style: TextStyle(fontSize: 15, color: AppColors.textHint)),
]),
),
const SizedBox(width: 16),
@@ -657,25 +659,25 @@ class _ReportDetailPageState extends ConsumerState<ReportDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFFEF3C7),
borderRadius: BorderRadius.circular(16),
color: AppColors.warningLight,
borderRadius: BorderRadius.circular(AppTheme.rLg),
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
const Text('💡', style: TextStyle(fontSize: 23)),
const SizedBox(width: 8),
const Text('综合解读', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: Color(0xFFD97706))),
Text('综合解读', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: AppColors.warning)),
]),
const SizedBox(height: 12),
Text(analysis.summary, style: const TextStyle(fontSize: 17, color: Color(0xFF92400E), height: 1.6)),
Text(analysis.summary, style: TextStyle(fontSize: 17, color: AppColors.warning, height: 1.6)),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.rSm),
),
child: const Text('⚠️ AI 解读仅供参考,请以医生诊断为准', style: TextStyle(fontSize: 16, color: Color(0xFFD97706))),
child: Text('⚠️ AI 解读仅供参考,请以医生诊断为准', style: TextStyle(fontSize: 16, color: AppColors.warning)),
),
]),
);