feat(app): 初始化 Flutter 前端项目 (Phase F0)

- Flutter 3.44.0 + Dart 3.12.0
- 设计系统: 7色Token×浅/深模式, NotoSansSC/Caveat字体, 圆角10/16/22/28/pill, 三级阴影
- ResponsiveScaffold: 手机底部TabBar / 平板侧边Rail / 桌面三栏
- go_router 路由表: 13个页面 (5个Tab + 8个全屏页面)
- 13个功能模块占位页面 (home/calendar/mood/search/profile/editor/auth/class/teacher/parent/achievement/stickers/templates)
- 依赖: flutter_bloc, go_router, freezed, isar, dio, perfect_freehand, fl_chart
- 中国镜像: PUB_HOSTED_URL + FLUTTER_STORAGE_BASE_URL
- flutter analyze: No issues found
This commit is contained in:
iven
2026-06-01 00:17:16 +08:00
parent 3d9896a676
commit ee5ce9bc56
90 changed files with 3933 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
// 暖记色彩系统 — 7 色 × 浅色/深色模式
// 设计规格 v1.2
import 'package:flutter/material.dart';
/// 暖记色彩 Token
class AppColors {
AppColors._();
// ===== 浅色模式 =====
/// 奶油白背景 #FFF8F0
static const Color bgLight = Color(0xFFFFF8F0);
/// 珊瑚色主色 #E07A5F
static const Color accent = Color(0xFFE07A5F);
/// 鼠尾草绿 #81B29A
static const Color secondary = Color(0xFF81B29A);
/// 暖金 #F2CC8F
static const Color tertiary = Color(0xFFF2CC8F);
/// 主文字 #2D2420
static const Color fgLight = Color(0xFF2D2420);
/// 卡片背景 #FFFFFF
static const Color surfaceLight = Color(0xFFFFFFFF);
/// 玫瑰粉 #D4A5A5
static const Color rose = Color(0xFFD4A5A5);
// ===== 深色模式 =====
/// 深色背景 #1A1614
static const Color bgDark = Color(0xFF1A1614);
/// 深色珊瑚 #E8907A
static const Color accentDark = Color(0xFFE8907A);
/// 深色鼠尾草 #8FBF9E
static const Color secondaryDark = Color(0xFF8FBF9E);
/// 深色暖金 #D4B878
static const Color tertiaryDark = Color(0xFFD4B878);
/// 深色主文字 #F0E8DF
static const Color fgDark = Color(0xFFF0E8DF);
/// 深色卡片 #2A2520
static const Color surfaceDark = Color(0xFF2A2520);
/// 深色玫瑰 #C4A0A0
static const Color roseDark = Color(0xFFC4A0A0);
// ===== 功能色 =====
/// 错误红
static const Color error = Color(0xFFD32F2F);
/// 成功绿
static const Color success = Color(0xFF4CAF50);
/// 警告黄
static const Color warning = Color(0xFFFFA726);
/// 信息蓝
static const Color info = Color(0xFF42A5F5);
// ===== 心情颜色映射 =====
/// 心情 → 颜色
static const Map<String, Color> moodColors = {
'happy': Color(0xFFFFD93D), // 😊 开心 — 暖黄
'calm': Color(0xFF81B29A), // 😌 平静 — 鼠尾草绿
'sad': Color(0xFF7B9CC4), // 😢 难过 — 灰蓝
'angry': Color(0xFFE07A5F), // 😠 生气 — 珊瑚
'thinking': Color(0xFFB8A9C9),// 🤔 思考 — 淡紫
};
// ===== 浅色主题色彩方案 =====
static const _light = ColorScheme(
brightness: Brightness.light,
primary: accent,
onPrimary: Colors.white,
primaryContainer: Color(0xFFFFE0D6),
onPrimaryContainer: fgLight,
secondary: secondary,
onSecondary: Colors.white,
secondaryContainer: Color(0xFFD4E8DC),
onSecondaryContainer: fgLight,
tertiary: tertiary,
onTertiary: fgLight,
error: error,
onError: Colors.white,
surface: surfaceLight,
onSurface: fgLight,
surfaceContainerHighest: bgLight,
);
// ===== 深色主题色彩方案 =====
static const _dark = ColorScheme(
brightness: Brightness.dark,
primary: accentDark,
onPrimary: fgDark,
primaryContainer: Color(0xFF5A2E22),
onPrimaryContainer: Color(0xFFFFD6CC),
secondary: secondaryDark,
onSecondary: fgDark,
secondaryContainer: Color(0xFF2A4A38),
onSecondaryContainer: Color(0xFFD4E8DC),
tertiary: tertiaryDark,
onTertiary: fgDark,
error: Color(0xFFEF5350),
onError: fgDark,
surface: surfaceDark,
onSurface: fgDark,
surfaceContainerHighest: bgDark,
);
/// 获取浅色色彩方案
static ColorScheme lightScheme() => _light;
/// 获取深色色彩方案
static ColorScheme darkScheme() => _dark;
}

View File

@@ -0,0 +1,28 @@
// 暖记圆角系统
// 设计规格: 10 / 16 / 22 / 28 / pill
import 'package:flutter/material.dart';
class AppRadius {
AppRadius._();
/// 小圆角 10px — 按钮、输入框
static const double sm = 10;
static BorderRadius get smBorder => BorderRadius.circular(sm);
/// 中圆角 16px — 卡片、弹窗
static const double md = 16;
static BorderRadius get mdBorder => BorderRadius.circular(md);
/// 大圆角 22px — 大卡片、底部面板
static const double lg = 22;
static BorderRadius get lgBorder => BorderRadius.circular(lg);
/// 超大圆角 28px — 模态框、全屏面板
static const double xl = 28;
static BorderRadius get xlBorder => BorderRadius.circular(xl);
/// 胶囊型 — 标签、Chip
static const double pill = 100;
static BorderRadius get pillBorder => BorderRadius.circular(pill);
}

View File

@@ -0,0 +1,49 @@
// 暖记阴影系统 — soft / medium / float
import 'package:flutter/material.dart';
class AppShadows {
AppShadows._();
/// 柔和阴影 — 卡片默认
static List<BoxShadow> soft(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return [
BoxShadow(
color: isDark
? Colors.black.withValues(alpha: 0.3)
: const Color(0xFF2D2420).withValues(alpha: 0.08),
offset: const Offset(0, 2),
blurRadius: 8,
),
];
}
/// 中等阴影 — 浮动元素、FAB
static List<BoxShadow> medium(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return [
BoxShadow(
color: isDark
? Colors.black.withValues(alpha: 0.4)
: const Color(0xFF2D2420).withValues(alpha: 0.12),
offset: const Offset(0, 4),
blurRadius: 16,
),
];
}
/// 浮动阴影 — 弹窗、底部面板
static List<BoxShadow> floating(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return [
BoxShadow(
color: isDark
? Colors.black.withValues(alpha: 0.5)
: const Color(0xFF2D2420).withValues(alpha: 0.16),
offset: const Offset(0, 8),
blurRadius: 24,
),
];
}
}

View File

@@ -0,0 +1,159 @@
// 暖记主题入口 — 浅色/深色 ThemeData
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'app_colors.dart';
import 'app_typography.dart';
import 'app_radius.dart';
class AppTheme {
AppTheme._();
/// 浅色主题
static ThemeData light() => _buildTheme(AppColors.lightScheme());
/// 深色主题
static ThemeData dark() => _buildTheme(AppColors.darkScheme());
static ThemeData _buildTheme(ColorScheme colorScheme) {
final isLight = colorScheme.brightness == Brightness.light;
final textTheme = isLight
? AppTypography.lightTextTheme()
: AppTypography.darkTextTheme();
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
textTheme: textTheme,
scaffoldBackgroundColor: isLight ? AppColors.bgLight : AppColors.bgDark,
// AppBar
appBarTheme: AppBarTheme(
elevation: 0,
scrolledUnderElevation: 1,
centerTitle: true,
backgroundColor: isLight ? AppColors.bgLight : AppColors.bgDark,
foregroundColor: colorScheme.onSurface,
titleTextStyle: textTheme.titleLarge,
),
// Card
cardTheme: CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.mdBorder,
side: BorderSide(
color: colorScheme.outlineVariant.withValues(alpha: 0.3),
),
),
color: colorScheme.surface,
),
// ElevatedButton
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: AppRadius.smBorder,
),
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
textStyle: textTheme.labelLarge,
),
),
// OutlinedButton
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: AppRadius.smBorder,
),
side: BorderSide(color: colorScheme.primary),
textStyle: textTheme.labelLarge,
),
),
// InputDecoration
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
border: OutlineInputBorder(
borderRadius: AppRadius.smBorder,
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: AppRadius.smBorder,
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: AppRadius.smBorder,
borderSide: BorderSide(
color: colorScheme.primary,
width: 2,
),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
// BottomNavigationBar
bottomNavigationBarTheme: BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
selectedItemColor: colorScheme.primary,
unselectedItemColor: colorScheme.onSurface.withValues(alpha: 0.5),
backgroundColor: colorScheme.surface,
elevation: 8,
),
// Chip
chipTheme: ChipThemeData(
shape: RoundedRectangleBorder(
borderRadius: AppRadius.pillBorder,
),
side: BorderSide.none,
),
// FloatingActionButton
floatingActionButtonTheme: FloatingActionButtonThemeData(
shape: RoundedRectangleBorder(
borderRadius: AppRadius.mdBorder,
),
elevation: 4,
),
// Page transitions — 弹性曲线
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: _WarmCurveBuilder(),
TargetPlatform.iOS: const CupertinoPageTransitionsBuilder(),
},
),
);
}
}
/// 暖记弹性页面转场: cubic-bezier(0.34, 1.56, 0.64, 1)
class _WarmCurveBuilder extends PageTransitionsBuilder {
const _WarmCurveBuilder();
static const Curve _warmCurve = Curves.easeOutBack;
@override
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return SlideTransition(
position: animation.drive(
Tween(begin: const Offset(1.0, 0.0), end: Offset.zero)
.chain(CurveTween(curve: _warmCurve)),
),
child: child,
);
}
}

View File

@@ -0,0 +1,114 @@
// 暖记字体系统 — Noto Sans SC + Caveat
import 'package:flutter/material.dart';
class AppTypography {
AppTypography._();
/// 字体族
static const String displayFont = 'Caveat'; // 手写风格(标题装饰)
static const String bodyFont = 'NotoSansSC'; // 正文(中文优先)
static const String monoFont = 'JetBrains Mono'; // 等宽(暂用系统回退)
/// 浅色主题文字主题
static TextTheme lightTextTheme() => TextTheme(
// 大标题 — 手写风格
displayLarge: TextStyle(
fontFamily: displayFont,
fontSize: 57,
height: 1.12,
fontWeight: FontWeight.w700,
),
displayMedium: TextStyle(
fontFamily: displayFont,
fontSize: 45,
height: 1.16,
fontWeight: FontWeight.w700,
),
displaySmall: TextStyle(
fontFamily: displayFont,
fontSize: 36,
height: 1.22,
fontWeight: FontWeight.w700,
),
// 标题 — 正文衬线
headlineLarge: TextStyle(
fontFamily: bodyFont,
fontSize: 32,
height: 1.25,
fontWeight: FontWeight.w700,
),
headlineMedium: TextStyle(
fontFamily: bodyFont,
fontSize: 28,
height: 1.29,
fontWeight: FontWeight.w600,
),
headlineSmall: TextStyle(
fontFamily: bodyFont,
fontSize: 24,
height: 1.33,
fontWeight: FontWeight.w600,
),
// 副标题
titleLarge: TextStyle(
fontFamily: bodyFont,
fontSize: 22,
height: 1.27,
fontWeight: FontWeight.w600,
),
titleMedium: TextStyle(
fontFamily: bodyFont,
fontSize: 16,
height: 1.5,
fontWeight: FontWeight.w600,
),
titleSmall: TextStyle(
fontFamily: bodyFont,
fontSize: 14,
height: 1.43,
fontWeight: FontWeight.w600,
),
// 正文
bodyLarge: TextStyle(
fontFamily: bodyFont,
fontSize: 16,
height: 1.5,
fontWeight: FontWeight.w400,
),
bodyMedium: TextStyle(
fontFamily: bodyFont,
fontSize: 14,
height: 1.43,
fontWeight: FontWeight.w400,
),
bodySmall: TextStyle(
fontFamily: bodyFont,
fontSize: 12,
height: 1.33,
fontWeight: FontWeight.w400,
),
// 标签
labelLarge: TextStyle(
fontFamily: bodyFont,
fontSize: 14,
height: 1.43,
fontWeight: FontWeight.w500,
),
labelMedium: TextStyle(
fontFamily: bodyFont,
fontSize: 12,
height: 1.33,
fontWeight: FontWeight.w500,
),
labelSmall: TextStyle(
fontFamily: bodyFont,
fontSize: 11,
height: 1.45,
fontWeight: FontWeight.w500,
),
);
/// 深色主题文字主题
static TextTheme darkTextTheme() => lightTextTheme();
}