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

@@ -104,7 +104,7 @@ class _AuthInterceptor extends Interceptor {
final retryResponse = await Dio(BaseOptions(baseUrl: baseUrl)).fetch(opts);
return handler.resolve(retryResponse);
}
} catch (_) {}
} catch (e) { print('[ApiClient] token刷新失败: $e'); }
}
await _client.clearTokens();
}

View File

@@ -1,197 +1,147 @@
import 'package:flutter/material.dart';
/// 统一配色方案 - 蚂蚁阿福风格:紫蓝渐变 + 淡紫背景 + 圆角卡片
/// 健康管家 — 蚂蚁阿福风格 v2.0
/// 柔和淡紫 + 大量留白 + 紫色仅作点缀
class AppColors {
AppColors._();
// ═══════════════════════════════════════════════════════════
// 主色调 - 紫蓝渐变系
// 主色 — 柔和淡紫(非浓烈深紫)
// ═══════════════════════════════════════════════════════════
/// 主紫色(鲜艳紫蓝,阿福风主按钮色
static const Color primary = Color(0xFF6366F1);
static const Color primary = Color(0xFF8B5CF6); // 主紫(柔和不刺眼
static const Color primaryLight = Color(0xFFA78BFA); // 浅紫(装饰/渐变)
static const Color primaryDark = Color(0xFF7C3AED); // 深紫(按压态)
static const Color primaryMid = Color(0xFFA78BFA); // 兼容旧名
/// 主紫色深色
static const Color primaryDark = Color(0xFF4F46E5);
/// 浅紫(用于背景、图标底)
static const Color primaryLight = Color(0xFFEEF2FF);
/// 中紫(渐变过渡)
static const Color primaryMid = Color(0xFFA78BFA);
/// 青色点缀(渐变右侧,标签色)
static const Color accentCyan = Color(0xFF22D3EE);
/// 青色深色
static const Color accentCyanDark = Color(0xFF06B6D4);
// 渐变 — 柔和淡紫,仅垂直
static const LinearGradient primaryGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF8B5CF6), Color(0xFFA78BFA)],
);
// ═══════════════════════════════════════════════════════════
// 背景色
// ═══════════════════════════════════════════════════════════
/// 页面背景(淡紫蓝,营造漂浮感
static const Color background = Color(0xFFF8FAFF);
static const Color background = Color(0xFFF0ECFF); // 页面全局背景(淡紫)
static const Color backgroundSoft = Color(0xFFF0ECFF); // 输入框底色(淡紫,同页面背景)
static const Color cardBackground = Color(0xFFFFFFFF); // 卡片白色
static const Color cardInner = Color(0xFFF5F2FF); // 卡片内区域底色
/// 更淡的紫背景(最底层,带渐变感
static const Color backgroundSoft = Color(0xFFF5F3FF);
// 主背景渐变紫→蓝4:6比例
static const LinearGradient bgGradient = LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Color(0xFFF0ECFF), Color(0xFFEBF4FF)],
stops: [0.0, 0.4],
);
/// 卡片背景 - 纯白
static const Color cardBackground = Color(0xFFFFFFFF);
/// 卡片内小卡片背景(极淡紫灰)
static const Color cardInner = Color(0xFFF8FAFF);
// ═══════════════════════════════════════════════════════════
// 图标渐变配色
// ═══════════════════════════════════════════════════════════
/// 报告/数据图标:蓝紫渐变
static const Color iconBlueStart = Color(0xFF818CF8);
static const Color iconBlueEnd = Color(0xFF6366F1);
/// 药管家图标:青蓝渐变
static const Color iconCyanStart = Color(0xFF67E8F9);
static const Color iconCyanEnd = Color(0xFF22D3EE);
/// 拍皮肤图标:紫蓝渐变
static const Color iconPurpleStart = Color(0xFFA78BFA);
static const Color iconPurpleEnd = Color(0xFF7C3AED);
/// 通用图标底色
static const Color iconBg = Color(0xFFEEF2FF);
// 导航栏背景(极淡蓝紫)
static const LinearGradient navGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFFE0E7FF), Color(0xFFC7D2FE)],
);
// ═══════════════════════════════════════════════════════════
// 文字颜色
// ═══════════════════════════════════════════════════════════
/// 主要文字(深紫黑)
static const Color textPrimary = Color(0xFF1E1B4B);
/// 次要文字(深灰)
static const Color textSecondary = Color(0xFF64748B);
/// 辅助文字(浅灰)
static const Color textHint = Color(0xFF94A3B8);
/// 渐变上的白色文字
static const Color textPrimary = Color(0xFF1F2937);
static const Color textSecondary = Color(0xFF6B7280);
static const Color textHint = Color(0xFF9CA3AF);
static const Color textOnGradient = Color(0xFFFFFFFF);
// ═══════════════════════════════════════════════════════════
// 边框分割线
// 边框/分割线
// ═══════════════════════════════════════════════════════════
/// 默认边框色(极淡)
static const Color border = Color(0xFFE2E8F0);
/// 更浅边框色
static const Color borderLight = Color(0xFFF1F5F9);
static const Color border = Color(0xFFE5E7EB);
static const Color borderLight = Color(0xFFF3F4F6);
// ═══════════════════════════════════════════════════════════
// 功能色
// ═══════════════════════════════════════════════════════════
/// 成功色(柔和绿)
static const Color success = Color(0xFF22C55E);
/// 成功浅色
static const Color successLight = Color(0xFFDCFCE7);
/// 错误色(柔和红)
static const Color success = Color(0xFF10B981);
static const Color successLight = Color(0xFFD1FAE5);
static const Color error = Color(0xFFEF4444);
/// 错误浅色
static const Color errorLight = Color(0xFFFEE2E2);
/// 警告色(橙)
static const Color warning = Color(0xFFF59E0B);
/// 警告浅色
static const Color warningLight = Color(0xFFFEF3C7);
static const Color blueMeasure = Color(0xFF3B82F6);
// ═══════════════════════════════════════════════════════════
// 渐变组
// 图标 / 小区域底色(极淡紫蓝)
// ═══════════════════════════════════════════════════════════
/// 主渐变:紫 → 蓝紫(主按钮填充)
static const LinearGradient primaryGradient = LinearGradient(
colors: [Color(0xFF818CF8), Color(0xFF6366F1)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const Color iconBg = Color(0xFFE8E0FA); // 卡片内底色,比页面背景稍深
/// 边框渐变:蓝紫 → 紫 → 青(按钮/卡片描边)
static const LinearGradient borderGradient = LinearGradient(
colors: [Color(0xFF818CF8), Color(0xFFA78BFA), Color(0xFF22D3EE)],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
);
/// 青色渐变(药管家等)
static const LinearGradient cyanGradient = LinearGradient(
colors: [Color(0xFF67E8F9), Color(0xFF22D3EE)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
/// 紫色渐变(拍皮肤等)
static const LinearGradient purpleGradient = LinearGradient(
colors: [Color(0xFFC4B5FD), Color(0xFF8B5CF6)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
/// 页面背景渐变(淡紫 → 淡蓝,营造漂浮感)
static const LinearGradient pageBgGradient = LinearGradient(
colors: [Color(0xFFF5F3FF), Color(0xFFEFF6FF)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
);
/// 卡片内轻渐变
static const LinearGradient cardLightGradient = LinearGradient(
colors: [Color(0xFFEEF2FF), Color(0xFFF0F9FF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
// 兼容旧名字
static const Color iconBlueStart = Color(0xFF8B5CF6);
static const Color iconBlueEnd = Color(0xFFA78BFA);
static const Color iconCyanStart = Color(0xFF10B981);
static const Color iconCyanEnd = Color(0xFF34D399);
static const Color iconPurpleStart = Color(0xFF8B5CF6);
static const Color iconPurpleEnd = Color(0xFFA78BFA);
static const Color accentCyan = Color(0xFF10B981);
static const Color accentCyanDark = Color(0xFF059669);
// ═══════════════════════════════════════════════════════════
// 阴影
// 渐变 — 所有旧名字统一为柔和淡紫
// ═══════════════════════════════════════════════════════════
/// 卡片阴影(淡紫色柔和阴影)
static List<BoxShadow> cardShadow = [
BoxShadow(
color: Color(0xFF6366F1).withOpacity(0.08),
blurRadius: 24,
offset: const Offset(0, 8),
),
static LinearGradient get borderGradient => primaryGradient;
static LinearGradient get cyanGradient => primaryGradient;
static LinearGradient get purpleGradient => primaryGradient;
static LinearGradient get pageBgGradient => bgGradient;
static LinearGradient get cardLightGradient => bgGradient;
static LinearGradient get gWelcomeHeader => primaryGradient;
// 各种旧花渐变全改为极淡色或纯色
static LinearGradient get gPurplePink => primaryGradient;
static LinearGradient get gBluePurple => primaryGradient;
static LinearGradient get gPinkPurple => primaryGradient;
static LinearGradient get gBlueRed => primaryGradient;
static LinearGradient get gBlueGreen => primaryGradient;
static LinearGradient get gPurpleCyan => primaryGradient;
static LinearGradient get gRedPurple => primaryGradient;
static LinearGradient get gCyanBlue => primaryGradient;
static LinearGradient get gPinkCyan => primaryGradient;
static LinearGradient get gLightPurple => bgGradient;
static LinearGradient get gLightBlue => bgGradient;
// ═══════════════════════════════════════════════════════════
// 阴影 — 轻柔
// ═══════════════════════════════════════════════════════════
static List<BoxShadow> get cardShadow => [
BoxShadow(color: Colors.black.withOpacity(0.06), blurRadius: 8, offset: const Offset(0, 2)),
];
static List<BoxShadow> get cardShadowLight => [
BoxShadow(color: Colors.black.withOpacity(0.04), blurRadius: 6, offset: const Offset(0, 1)),
];
static List<BoxShadow> get buttonShadow => [
BoxShadow(color: Color(0xFF8B5CF6).withOpacity(0.25), blurRadius: 12, offset: const Offset(0, 4)),
];
static List<BoxShadow> get fabShadow => [
BoxShadow(color: Color(0xFF8B5CF6).withOpacity(0.30), blurRadius: 16, offset: const Offset(0, 6)),
];
/// 轻卡片阴影
static List<BoxShadow> cardShadowLight = [
BoxShadow(
color: Color(0xFF6366F1).withOpacity(0.05),
blurRadius: 16,
offset: const Offset(0, 4),
),
];
// ═══════════════════════════════════════════════════════════
// 旧代码兼容别名chat_messages_view 原始代码引用)
// ═══════════════════════════════════════════════════════════
/// 按钮阴影
static List<BoxShadow> buttonShadow = [
BoxShadow(
color: Color(0xFF6366F1).withOpacity(0.25),
blurRadius: 16,
offset: const Offset(0, 6),
),
];
/// 悬浮操作按钮阴影(右下角大号按钮)
static List<BoxShadow> fabShadow = [
BoxShadow(
color: Color(0xFF6366F1).withOpacity(0.35),
blurRadius: 20,
offset: const Offset(0, 8),
),
];
static const Color purpleGradientStart = Color(0xFF8B5CF6);
static const Color blueGradientEnd = Color(0xFF3B82F6);
static LinearGradient get purpleBlueGradient => primaryGradient;
static Color get primaryPurple => primary;
static Color get iconColor => primary;
static Color get iconBackground => iconBg;
static Color get primaryBlue => blueMeasure;
static LinearGradient get lightGradient => gLightPurple;
static Color get backgroundSecondary => background;
static LinearGradient get successButtonGradient => primaryGradient;
}

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'app_colors.dart';
/// 健康管家 —— 蚂蚁阿福风格:紫蓝渐变 + 超大圆角 + 漂浮卡片
/// 健康管家 — 设计规范 v1.0:紫色系 + 干净白灰 + 12px圆角
class AppTheme {
AppTheme._();
@@ -11,8 +11,8 @@ class AppTheme {
static const Color primaryLight = AppColors.primaryLight;
static const Color primaryDark = AppColors.primaryDark;
static const Color primaryMid = AppColors.primaryMid;
static const Color accent = AppColors.accentCyan;
static const Color info = AppColors.accentCyan;
static const Color accent = AppColors.success;
static const Color info = AppColors.blueMeasure;
// ── 中性色 ──
static const Color bg = AppColors.background;
@@ -33,51 +33,51 @@ class AppTheme {
static const Color warning = AppColors.warning;
static const Color warningLight = AppColors.warningLight;
// ── 圆角(阿福风格:超大圆角)──
static const double rXs = 8;
static const double rSm = 12;
static const double rMd = 18;
static const double rLg = 24;
static const double rXl = 32;
// ── 圆角(规范12px 卡片)──
static const double rXs = 4;
static const double rSm = 8;
static const double rMd = 12;
static const double rLg = 16;
static const double rXl = 20;
static const double rPill = 999;
// ── 间距 ──
// ── 间距8px 网格)──
static const double sXs = 4;
static const double sSm = 8;
static const double sMd = 12;
static const double sLg = 16;
static const double sXl = 20;
static const double sXxl = 28;
static const double sXxl = 24;
// ── 阴影(淡紫柔和阴影)──
// ── 阴影(阴影)──
static BoxShadow get shadowCard => BoxShadow(
color: AppColors.primary.withOpacity(0.08),
blurRadius: 24,
offset: const Offset(0, 8),
color: Colors.black.withOpacity(0.06),
blurRadius: 8,
offset: const Offset(0, 2),
);
static BoxShadow get shadowLight => BoxShadow(
color: AppColors.primary.withOpacity(0.05),
blurRadius: 16,
offset: const Offset(0, 4),
color: Colors.black.withOpacity(0.04),
blurRadius: 6,
offset: const Offset(0, 1),
);
static BoxShadow get shadowElevated => BoxShadow(
color: AppColors.primary.withOpacity(0.15),
blurRadius: 32,
offset: const Offset(0, 10),
color: primary.withOpacity(0.08),
blurRadius: 12,
offset: const Offset(0, 4),
);
static BoxShadow get shadowButton => BoxShadow(
color: AppColors.primary.withOpacity(0.25),
blurRadius: 16,
offset: const Offset(0, 6),
color: primary.withOpacity(0.25),
blurRadius: 12,
offset: const Offset(0, 4),
);
// ── 智能体配色 ──
static const Map<String, Color> agentColors = {
'default': AppColors.primaryLight,
'default': Color(0xFFEDE9FE),
'consultation': Color(0xFFE0E7FF),
'health': Color(0xFFDCFCE7),
'health': Color(0xFFD1FAE5),
'diet': Color(0xFFFEF3C7),
'medication': Color(0xFFFCE7F3),
'medication': Color(0xFFFEE2E2),
'report': Color(0xFFDBEAFE),
'exercise': Color(0xFFEDE9FE),
};
@@ -94,70 +94,75 @@ class AppTheme {
onPrimary: Colors.white,
secondary: accent,
surface: surface,
background: bg,
brightness: Brightness.light,
),
scaffoldBackgroundColor: bg,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: primaryLight.withOpacity(0.5),
hoverColor: primaryLight.withOpacity(0.3),
fontFamily: null,
appBarTheme: AppBarTheme(
backgroundColor: bg,
backgroundColor: surface,
foregroundColor: text,
elevation: 0,
centerTitle: false,
centerTitle: true,
scrolledUnderElevation: 0,
titleTextStyle: TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: text),
titleTextStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: text),
toolbarHeight: 44,
),
cardTheme: CardThemeData(
color: surface,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rLg)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rMd)),
margin: EdgeInsets.zero,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: bgSoft,
contentPadding: const EdgeInsets.symmetric(horizontal: sLg, vertical: 16),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(rMd), borderSide: BorderSide.none),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(rMd), borderSide: BorderSide.none),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(rMd),
borderSide: const BorderSide(color: primary, width: 2),
contentPadding: const EdgeInsets.symmetric(horizontal: sLg, vertical: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
hintStyle: TextStyle(color: textHint, fontSize: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: primary, width: 1),
),
hintStyle: TextStyle(color: textHint, fontSize: 15),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: primary,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 56),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rPill)),
textStyle: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600),
minimumSize: const Size(double.infinity, 52),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rMd)),
textStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
elevation: 0,
shadowColor: primary.withOpacity(0.3),
),
),
dialogTheme: DialogThemeData(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rXl))),
dialogTheme: DialogThemeData(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rLg))),
textTheme: const TextTheme(
headlineLarge: TextStyle(fontSize: 28, fontWeight: FontWeight.w700, color: text, letterSpacing: -0.3),
titleLarge: TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: text),
titleMedium: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: text),
bodyLarge: TextStyle(fontSize: 16, color: text, height: 1.5),
bodyMedium: TextStyle(fontSize: 14, color: textSub, height: 1.4),
headlineLarge: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: text),
titleLarge: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: text),
titleMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: text),
bodyLarge: TextStyle(fontSize: 15, color: text, height: 1.5),
bodyMedium: TextStyle(fontSize: 13, color: textSub, height: 1.4),
labelMedium: TextStyle(fontSize: 13, color: textSub),
labelSmall: TextStyle(fontSize: 12, color: textHint),
),
);
// ── Shadcn UI 主题(紫蓝系) ──
// ── Shadcn UI 主题 ──
static ShadThemeData get shadTheme => ShadThemeData(
brightness: Brightness.light,
colorScheme: ShadVioletColorScheme.light(