style: 统一患者端页面视觉并补齐入口跳转

- 调整全局颜色基调,提高灰色文字和边框对比度
- 新增企业级页面头部、统计块和卡片通用组件
- 优化用药管理、报告管理、个人信息、通知中心页面布局
- 新增多张页面插画和通用 UI 装饰素材
- 运动计划列表更紧凑,并新增运动计划详情路由
- 补齐医生端协议入口和原始报告查看入口
- 移除设置页未实现的字体大小和清除缓存入口
This commit is contained in:
MingNian
2026-06-25 22:13:42 +08:00
parent 53ce19e8c3
commit 18df2f3285
23 changed files with 1266 additions and 470 deletions

View File

@@ -10,6 +10,7 @@ import '../../core/app_theme.dart';
import '../../core/api_client.dart' show baseUrl;
import '../../core/navigation_provider.dart';
import '../../providers/auth_provider.dart';
import '../../widgets/enterprise_widgets.dart';
final reportProvider = NotifierProvider<ReportNotifier, ReportState>(
ReportNotifier.new,
@@ -321,13 +322,20 @@ class ReportNotifier extends Notifier<ReportState> {
Future<void> reanalyzeReport(String id) async {
try {
final res = await ref.read(apiClientProvider).post('/api/reports/$id/reanalyze');
final res = await ref
.read(apiClientProvider)
.post('/api/reports/$id/reanalyze');
final data = res.data;
if (data is Map && data['code'] != 0) {
state = state.copyWith(uploadError: data['message']?.toString() ?? '重新分析失败');
state = state.copyWith(
uploadError: data['message']?.toString() ?? '重新分析失败',
);
return;
}
state = state.copyWith(clearCurrentAnalysis: true, clearUploadError: true);
state = state.copyWith(
clearCurrentAnalysis: true,
clearUploadError: true,
);
await loadReports();
fetchReportDetail(id);
} catch (e) {
@@ -345,6 +353,9 @@ class ReportNotifier extends Notifier<ReportState> {
class ReportListPage extends ConsumerWidget {
const ReportListPage({super.key});
static const _reportBlue = Color(0xFF8B5CF6);
static const _reportCyan = Color(0xFF38BDF8);
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(reportProvider);
@@ -352,14 +363,17 @@ class ReportListPage extends ConsumerWidget {
if (state.isAnalyzing) {
return GradientScaffold(
appBar: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref)),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => popRoute(ref),
),
title: const Text('报告管理'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(color: AppColors.primary),
const CircularProgressIndicator(color: _reportBlue),
const SizedBox(height: 16),
Text(
state.uploadingImage == null ? 'AI 正在分析报告...' : '正在上传报告...',
@@ -389,6 +403,27 @@ class ReportListPage extends ConsumerWidget {
child: ListView(
padding: const EdgeInsets.all(16),
children: [
EnterpriseHeader(
title: '报告管理',
subtitle: '上传检查报告后自动进行 AI 结构化解读',
icon: Icons.description_outlined,
color: _reportBlue,
accent: _reportCyan,
stats: [
EnterpriseStat(
label: '报告总数',
value: '${state.reports.length}',
icon: Icons.folder_copy_outlined,
),
EnterpriseStat(
label: '待处理',
value:
'${state.reports.where((r) => r.aiStatus == 'Analyzing' || r.reviewStatus != 'Reviewed').length}',
icon: Icons.pending_actions_outlined,
),
],
),
const SizedBox(height: 10),
if (state.uploadError != null) ...[
_buildUploadError(state.uploadError!),
const SizedBox(height: 12),
@@ -435,7 +470,7 @@ class ReportListPage extends ConsumerWidget {
Widget _buildUploadButton(BuildContext context, WidgetRef ref) {
return FloatingActionButton(
onPressed: () => _showUploadOptions(context, ref),
backgroundColor: AppColors.primary,
backgroundColor: _reportBlue,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: const Icon(Icons.add, size: 28, color: Colors.white),
);
@@ -456,7 +491,7 @@ class ReportListPage extends ConsumerWidget {
ListTile(
leading: const Icon(
Icons.camera_alt_outlined,
color: AppColors.primary,
color: _reportBlue,
),
title: const Text('拍照上传', style: TextStyle(fontSize: 17)),
onTap: () async {
@@ -474,7 +509,7 @@ class ReportListPage extends ConsumerWidget {
ListTile(
leading: const Icon(
Icons.photo_library_outlined,
color: AppColors.primary,
color: _reportCyan,
),
title: const Text('从相册选择', style: TextStyle(fontSize: 17)),
onTap: () async {
@@ -505,13 +540,13 @@ class ReportListPage extends ConsumerWidget {
width: 100,
height: 100,
decoration: BoxDecoration(
color: AppColors.cardInner,
color: _reportBlue.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(50),
),
child: const Icon(
Icons.description_outlined,
size: 44,
color: AppColors.textHint,
color: _reportBlue,
),
),
const SizedBox(height: 20),
@@ -545,38 +580,34 @@ class ReportListPage extends ConsumerWidget {
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFFD5DCE5)),
boxShadow: [
BoxShadow(
color: const Color(0xFF101828).withValues(alpha: 0.08),
blurRadius: 18,
offset: const Offset(0, 6),
),
],
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.border, width: 1.1),
boxShadow: [AppTheme.shadowLight],
),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(16),
child: InkWell(
onTap: () => pushRoute(ref, 'aiAnalysis', params: {'id': report.id}),
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(14),
padding: const EdgeInsets.all(15),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: AppColors.iconBg,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.borderLight),
color: _reportBlue.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: _reportBlue.withValues(alpha: 0.10),
),
),
child: const Icon(
Icons.description_outlined,
size: 26,
color: AppColors.primary,
color: _reportBlue,
),
),
const SizedBox(width: 14),
@@ -586,9 +617,11 @@ class ReportListPage extends ConsumerWidget {
children: [
Text(
displayTitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w800,
color: AppColors.textPrimary,
),
),
@@ -619,15 +652,13 @@ class ReportListPage extends ConsumerWidget {
),
_buildStatusBadge(report),
const SizedBox(width: 4),
GestureDetector(
onTap: () => _confirmDelete(context, ref, report.id),
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(
Icons.delete_outline,
size: 20,
color: AppColors.textHint,
),
IconButton(
onPressed: () => _confirmDelete(context, ref, report.id),
visualDensity: VisualDensity.compact,
icon: const Icon(
Icons.delete_outline,
size: 20,
color: AppColors.textHint,
),
),
],
@@ -663,26 +694,39 @@ class ReportListPage extends ConsumerWidget {
Widget _buildStatusBadge(ReportItem report) {
final (label, bg, fg) = switch (report.status) {
_ when report.reviewStatus == 'Reviewed' => ('已审核', AppColors.successLight, AppColors.success),
_ when report.aiStatus == 'Analyzing' => ('分析中', AppColors.cardInner, AppColors.textHint),
_ when report.aiStatus == 'Failed' => ('分析失败', AppColors.error.withValues(alpha: 0.08), AppColors.error),
_ when report.aiStatus == 'Succeeded' => ('待审核', AppColors.warningLight, AppColors.warning),
_ => ('分析中', AppColors.cardInner, AppColors.textHint),
_ when report.reviewStatus == 'Reviewed' => (
'已审核',
AppColors.successLight,
AppColors.success,
),
_ when report.aiStatus == 'Analyzing' => (
'分析中',
_reportBlue.withValues(alpha: 0.08),
_reportBlue,
),
_ when report.aiStatus == 'Failed' => (
'分析失败',
AppColors.error.withValues(alpha: 0.08),
AppColors.error,
),
_ when report.aiStatus == 'Succeeded' => (
'待审核',
AppColors.warningLight,
AppColors.warning,
),
_ => ('分析中', _reportBlue.withValues(alpha: 0.08), _reportBlue),
};
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: bg,
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(999),
border: Border.all(color: fg.withValues(alpha: 0.12)),
),
child: Text(
label,
style: TextStyle(
fontSize: 13,
color: fg,
fontWeight: FontWeight.w500,
),
style: TextStyle(fontSize: 13, color: fg, fontWeight: FontWeight.w700),
),
);
}
@@ -719,18 +763,17 @@ class ReportOriginalPage extends StatelessWidget {
final String url;
final String title;
const ReportOriginalPage({
super.key,
required this.url,
this.title = '原始报告',
});
const ReportOriginalPage({super.key, required this.url, this.title = '原始报告'});
@override
Widget build(BuildContext context) {
final imageUrl = _absoluteUrl(url);
return GradientScaffold(
appBar: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context)),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
title: Text(title),
),
body: Center(