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:
128
app/lib/core/theme/app_colors.dart
Normal file
128
app/lib/core/theme/app_colors.dart
Normal 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;
|
||||
}
|
||||
28
app/lib/core/theme/app_radius.dart
Normal file
28
app/lib/core/theme/app_radius.dart
Normal 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);
|
||||
}
|
||||
49
app/lib/core/theme/app_shadows.dart
Normal file
49
app/lib/core/theme/app_shadows.dart
Normal 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,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
159
app/lib/core/theme/app_theme.dart
Normal file
159
app/lib/core/theme/app_theme.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
114
app/lib/core/theme/app_typography.dart
Normal file
114
app/lib/core/theme/app_typography.dart
Normal 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();
|
||||
}
|
||||
Reference in New Issue
Block a user