- 报告模块:重写AI解读页,统一白色卡片+三栏布局(指标→解读→医生审核);加删除功能+后端删接口;VLM自动识别报告类型生成中文标题;去五颜六色
- 饮食分析:全面重设计,暖橙主题配色,图片自适应,餐次emoji选择器,去紫色
- 运动确认卡片:修复duration_minutes JSON类型转换,支持中文数字识别(三十分钟/半小时→30);主区域只显示运动名,字段行改为横向排列
- 流式输出:简化为最基础逐字追加,去buffer+Timer+淡入动画
- 欢迎卡片:每智能体独立渐变色(青/橙/蓝/紫/绿/粉),加卡片入场滑入+淡入动画(600ms)
- 确认卡片:头部去紫色背景改浅灰白,字段行去图标改纯信息横排,编辑框去双重边框
- 用药管理:胶囊改TabBar,添加按钮改右下FAB;打卡按钮改对号圆圈形式;药丸图标白底
- 侧边栏:加VIP服务/保险栏+图标,标题字体放大;服务包去查看更多+去VIP金色标签
- 胶囊:首页智能体胶囊白底深色字; side胶囊加阴影
- 对话流:reverse=false,今日健康出现在顶部; 对话页input bar匹配主页面样式
- 其他:个人资料去紫色+去多余入口;设置页白底图标;蓝牙页加返回按钮;报告管理改名
- 后端:加DELETE /api/reports/{id}; 修复manage_exercise数据类型转换;AI提示优化中文数字
217 lines
7.6 KiB
Dart
217 lines
7.6 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||
import 'app_colors.dart';
|
||
|
||
/// 渐变背景 Scaffold(紫→蓝 4:6 渐变)
|
||
class GradientScaffold extends StatelessWidget {
|
||
final PreferredSizeWidget? appBar;
|
||
final Widget? body;
|
||
final Widget? floatingActionButton;
|
||
final Widget? drawer;
|
||
final Widget? bottomNavigationBar;
|
||
final bool extendBody;
|
||
const GradientScaffold({super.key, this.appBar, this.body, this.floatingActionButton, this.drawer, this.bottomNavigationBar, this.extendBody = false});
|
||
|
||
@override Widget build(BuildContext context) => Container(
|
||
decoration: const BoxDecoration(gradient: AppColors.bgGradient),
|
||
child: Scaffold(
|
||
backgroundColor: Colors.transparent,
|
||
appBar: appBar,
|
||
body: body,
|
||
floatingActionButton: floatingActionButton,
|
||
drawer: drawer,
|
||
bottomNavigationBar: bottomNavigationBar,
|
||
extendBody: extendBody,
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 健康管家 — 设计规范 v1.0:紫色系 + 干净白灰 + 12px圆角
|
||
class AppTheme {
|
||
AppTheme._();
|
||
|
||
// ── 主色调 ──
|
||
static const Color primary = AppColors.primary;
|
||
static const Color primaryLight = AppColors.primaryLight;
|
||
static const Color primaryDark = AppColors.primaryDark;
|
||
static const Color primaryMid = AppColors.primaryMid;
|
||
static const Color accent = AppColors.success;
|
||
static const Color info = AppColors.blueMeasure;
|
||
|
||
// ── 中性色 ──
|
||
static const Color bg = AppColors.background;
|
||
static const Color bgSoft = AppColors.backgroundSoft;
|
||
static const Color surface = AppColors.cardBackground;
|
||
static const Color surfaceInner = AppColors.cardInner;
|
||
static const Color text = AppColors.textPrimary;
|
||
static const Color textSub = AppColors.textSecondary;
|
||
static const Color textHint = AppColors.textHint;
|
||
static const Color border = AppColors.border;
|
||
static const Color divider = AppColors.borderLight;
|
||
|
||
// ── 语义色 ──
|
||
static const Color success = AppColors.success;
|
||
static const Color successLight = AppColors.successLight;
|
||
static const Color error = AppColors.error;
|
||
static const Color errorLight = AppColors.errorLight;
|
||
static const Color warning = AppColors.warning;
|
||
static const Color warningLight = AppColors.warningLight;
|
||
|
||
// ── 圆角(规范: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 = 24;
|
||
|
||
// ── 阴影(轻阴影)──
|
||
static BoxShadow get shadowCard => BoxShadow(
|
||
color: Colors.black.withOpacity(0.06),
|
||
blurRadius: 8,
|
||
offset: const Offset(0, 2),
|
||
);
|
||
static BoxShadow get shadowLight => BoxShadow(
|
||
color: Colors.black.withOpacity(0.04),
|
||
blurRadius: 6,
|
||
offset: const Offset(0, 1),
|
||
);
|
||
static BoxShadow get shadowElevated => BoxShadow(
|
||
color: primary.withOpacity(0.08),
|
||
blurRadius: 12,
|
||
offset: const Offset(0, 4),
|
||
);
|
||
static BoxShadow get shadowButton => BoxShadow(
|
||
color: primary.withOpacity(0.25),
|
||
blurRadius: 12,
|
||
offset: const Offset(0, 4),
|
||
);
|
||
|
||
// ── 智能体配色 ──
|
||
static const Map<String, Color> agentColors = {
|
||
'default': Color(0xFFEDE9FE),
|
||
'consultation': Color(0xFFE0E7FF),
|
||
'health': Color(0xFFD1FAE5),
|
||
'diet': Color(0xFFFEF3C7),
|
||
'medication': Color(0xFFFEE2E2),
|
||
'report': Color(0xFFDBEAFE),
|
||
'exercise': Color(0xFFEDE9FE),
|
||
};
|
||
|
||
static Color agentLight(String? name) => agentColors[name] ?? primaryLight;
|
||
|
||
// ── Material 主题 ──
|
||
static ThemeData get lightTheme => ThemeData(
|
||
useMaterial3: true,
|
||
colorScheme: ColorScheme.fromSeed(
|
||
seedColor: primary,
|
||
primary: primary,
|
||
primaryContainer: primaryLight,
|
||
onPrimary: Colors.white,
|
||
secondary: accent,
|
||
surface: surface,
|
||
brightness: Brightness.light,
|
||
),
|
||
scaffoldBackgroundColor: bg,
|
||
splashColor: Colors.transparent,
|
||
highlightColor: Colors.transparent,
|
||
hoverColor: primaryLight.withOpacity(0.3),
|
||
fontFamily: null,
|
||
|
||
appBarTheme: AppBarTheme(
|
||
backgroundColor: surface,
|
||
foregroundColor: text,
|
||
elevation: 0,
|
||
centerTitle: true,
|
||
scrolledUnderElevation: 0,
|
||
titleTextStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: text),
|
||
toolbarHeight: 44,
|
||
),
|
||
|
||
cardTheme: CardThemeData(
|
||
color: surface,
|
||
elevation: 0,
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rMd)),
|
||
margin: EdgeInsets.zero,
|
||
),
|
||
|
||
inputDecorationTheme: InputDecorationTheme(
|
||
filled: true,
|
||
fillColor: bgSoft,
|
||
contentPadding: const EdgeInsets.symmetric(horizontal: sLg, vertical: 12),
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(10),
|
||
borderSide: BorderSide.none,
|
||
),
|
||
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, 52),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rMd)),
|
||
textStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
||
elevation: 0,
|
||
),
|
||
),
|
||
|
||
dialogTheme: DialogThemeData(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rLg))),
|
||
|
||
textTheme: const TextTheme(
|
||
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 主题 ──
|
||
static ShadThemeData get shadTheme => ShadThemeData(
|
||
brightness: Brightness.light,
|
||
colorScheme: ShadVioletColorScheme.light(
|
||
background: bg,
|
||
foreground: text,
|
||
card: surface,
|
||
cardForeground: text,
|
||
popover: surface,
|
||
popoverForeground: text,
|
||
primary: primary,
|
||
primaryForeground: Colors.white,
|
||
secondary: primaryLight,
|
||
secondaryForeground: primaryDark,
|
||
muted: bgSoft,
|
||
mutedForeground: textSub,
|
||
accent: accent,
|
||
accentForeground: Colors.white,
|
||
destructive: error,
|
||
destructiveForeground: Colors.white,
|
||
border: border,
|
||
input: border,
|
||
ring: primary,
|
||
selection: primaryLight,
|
||
),
|
||
radius: BorderRadius.circular(rMd),
|
||
);
|
||
}
|