feat: 饮食记录逻辑重构 + 通知管理分隔线修复 + 多页面 UI 调整

- 饮食: 新增 DietCommentaryPolicy + diet_record_logic, 饮食记录逻辑抽取复用
- 通知管理: 分隔线改为仿通知中心样式, 跳过图标区域只留文字下方
- 侧边栏: 对话记录操作栏浮层修复 + 常用功能间距收紧
- 智能体: 欢迎卡片去 400ms 延迟 + 胶囊去阴影
- 后端: AI 会话仓库新增方法 + 饮食评论策略 + 健康记录规则调整
- 其他: api_client IP 适配 + 多页面 UI 微调 + 新增测试
This commit is contained in:
MingNian
2026-07-12 22:49:38 +08:00
parent d82e006cf4
commit 335a3e6440
33 changed files with 2056 additions and 1283 deletions

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import '../core/app_colors.dart';
import 'app_gradient_widgets.dart';
class AppEmptyState extends StatelessWidget {
final IconData icon;
@@ -32,7 +33,7 @@ class AppEmptyState extends StatelessWidget {
border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight,
),
child: Icon(icon, size: 34, color: AppColors.primaryDark),
child: AppGradientIcon(icon: icon, size: 34),
),
const SizedBox(height: 18),
Text(

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import '../core/app_colors.dart';
import 'app_gradient_widgets.dart';
/// 统一的"加载失败"状态——与 AppEmptyState 风格一致,区别在于明确表达"出错了,可重试"
/// 避免把网络/服务失败误显示成"暂无数据"。
@@ -32,10 +33,9 @@ class AppErrorState extends StatelessWidget {
border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight,
),
child: const Icon(
Icons.cloud_off_outlined,
child: const AppGradientIcon(
icon: Icons.cloud_off_outlined,
size: 34,
color: AppColors.primaryDark,
),
),
const SizedBox(height: 18),
@@ -61,17 +61,9 @@ class AppErrorState extends StatelessWidget {
],
if (onRetry != null) ...[
const SizedBox(height: 20),
OutlinedButton.icon(
onPressed: onRetry,
icon: const Icon(Icons.refresh, size: 18),
label: const Text('重试'),
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryDark,
side: const BorderSide(color: AppColors.borderLight),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
SizedBox(
width: 116,
child: AppGradientOutlineButton(label: '重试', onTap: onRetry!),
),
],
],

View File

@@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
import '../core/app_colors.dart';
class AppGradientText extends StatelessWidget {
final String text;
final TextStyle style;
final Gradient gradient;
final TextAlign? textAlign;
const AppGradientText(
this.text, {
super.key,
required this.style,
this.gradient = AppColors.primaryGradient,
this.textAlign,
});
@override
Widget build(BuildContext context) {
return ShaderMask(
shaderCallback: gradient.createShader,
blendMode: BlendMode.srcIn,
child: Text(
text,
textAlign: textAlign,
style: style.copyWith(color: Colors.white),
),
);
}
}
class AppGradientIcon extends StatelessWidget {
final IconData icon;
final double size;
final Gradient gradient;
const AppGradientIcon({
super.key,
required this.icon,
required this.size,
this.gradient = AppColors.primaryGradient,
});
@override
Widget build(BuildContext context) {
return ShaderMask(
shaderCallback: gradient.createShader,
blendMode: BlendMode.srcIn,
child: Icon(icon, size: size, color: Colors.white),
);
}
}
class AppGradientOutlineButton extends StatelessWidget {
final String label;
final VoidCallback onTap;
final Gradient gradient;
const AppGradientOutlineButton({
super.key,
required this.label,
required this.onTap,
this.gradient = AppColors.primaryGradient,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
height: 50,
decoration: BoxDecoration(
gradient: gradient,
borderRadius: BorderRadius.circular(18),
),
padding: const EdgeInsets.all(1.2),
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(17),
),
child: AppGradientText(
label,
gradient: gradient,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w800),
),
),
),
);
}
}

View File

@@ -380,25 +380,25 @@ class _NavigationSection extends StatelessWidget {
icon: LucideIcons.folderHeart,
title: '档案',
route: 'healthArchive',
colors: const [Color(0xFF93C5FD), Color(0xFF60A5FA)],
colors: AppColors.healthGradient.colors,
),
_NavItem(
icon: LucideIcons.fileText,
title: '报告',
route: 'reports',
colors: const [Color(0xFFBFDBFE), Color(0xFF93C5FD)],
colors: AppColors.reportGradient.colors,
),
_NavItem(
icon: LucideIcons.pill,
title: '用药',
route: 'medications',
colors: const [Color(0xFFDDD6FE), Color(0xFFC4B5FD)],
colors: AppColors.medicationGradient.colors,
),
_NavItem(
icon: LucideIcons.utensils,
title: '饮食',
route: 'dietRecords',
colors: const [Color(0xFFFBCFE8), Color(0xFFF472B6)],
colors: AppColors.dietGradient.colors,
),
_NavItem(
icon: LucideIcons.calendarDays,
@@ -416,13 +416,13 @@ class _NavigationSection extends StatelessWidget {
icon: LucideIcons.footprints,
title: '运动',
route: 'exercisePlan',
colors: const [Color(0xFFA5F3FC), Color(0xFF67E8F9)],
colors: AppColors.exerciseGradient.colors,
),
_NavItem(
icon: LucideIcons.bluetooth,
title: '设备',
route: 'devices',
colors: const [Color(0xFF60A5FA), Color(0xFFC4B5FD)],
colors: AppColors.deviceGradient.colors,
),
];
@@ -466,14 +466,14 @@ class _NavTile extends StatelessWidget {
};
List<Color> get _colors => switch (item.route) {
'healthArchive' => const [AppColors.healthLight, AppColors.health],
'reports' => const [AppColors.reportLight, AppColors.report],
'medications' => const [AppColors.medicationLight, AppColors.medication],
'dietRecords' => const [AppColors.dietLight, AppColors.diet],
'healthArchive' => AppColors.healthGradient.colors,
'reports' => AppColors.reportGradient.colors,
'medications' => AppColors.medicationGradient.colors,
'dietRecords' => AppColors.dietGradient.colors,
'calendar' => const [AppColors.calendarLight, AppColors.calendar],
'followups' => const [AppColors.followupLight, AppColors.followup],
'exercisePlan' => const [AppColors.exerciseLight, AppColors.exercise],
'devices' => const [AppColors.deviceLight, AppColors.device],
'exercisePlan' => AppColors.exerciseGradient.colors,
'devices' => AppColors.deviceGradient.colors,
_ => item.colors,
};