- 创建 ThemeProvider 管理主题状态 - 配置浅色和深色主题(Vercel/Linear 风格) - 集成 Google Fonts(Inter + JetBrains Mono) - 在我的页面添加主题切换开关 - 更新颜色系统符合 modernization-v2.md 规范 - 优化间距和圆角系统 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
10 KiB
10 KiB
Flutter Monisuo 现代化改造规范 v2.0
目标
将 Flutter Monisuo 应用打造为现代化、简约、专业的虚拟货币交易平台,参考 SuperDesign 设计原则。
设计原则
1. 现代化简约风格
- Vercel/Linear 风格:干净的深色主题,微妙的阴影,大量留白
- 避免过时设计:不使用 Bootstrap 蓝、沉重的阴影、复杂的渐变
- 微交互:细腻的动画反馈(150-400ms)
2. 明暗主题支持
- 完整的 Light/Dark 主题切换
- 使用 ColorScheme 管理主题
- 主题切换时平滑过渡
3. 颜色系统(基于 OKLCH 转换)
现代深色主题
// Vercel/Linear 风格
background: Color(0xFF0A0A0B) // oklch(0.098 0.005 270)
cardBackground: Color(0xFF111113) // oklch(0.148 0.004 270)
primary: Color(0xFF00D4AA) // 品牌青绿色
primaryForeground: Color(0xFFFFFFFF)
secondary: Color(0xFF1C1C1F)
muted: Color(0xFF27272A)
border: Color(0xFF27272A)
现代浅色主题
background: Color(0xFFFFFFFF) // oklch(1 0 0)
cardBackground: Color(0xFFFAFAFA) // oklch(0.98 0 0)
primary: Color(0xFF00B894) // 品牌青绿色(深色版)
primaryForeground: Color(0xFFFFFFFF)
secondary: Color(0xFFF4F4F5)
muted: Color(0xFFE4E4E7)
border: Color(0xFFE4E4E7)
涨跌色(明暗通用)
up: Color(0xFF00C853) // 涨/买入(绿色)
down: Color(0xFFFF5252) // 跌/卖出(红色)
4. 字体系统
使用 Google Fonts
dependencies:
google_fonts: ^6.1.0
字体选择
- 主字体:Inter(现代、清晰)
- 数字字体:JetBrains Mono(等宽,用于价格/数量)
- 回退字体:system-ui
字号系统
// 标题
displayLarge: 32sp, weight: 700
displayMedium: 24sp, weight: 600
displaySmall: 20sp, weight: 600
// 正文
bodyLarge: 16sp, weight: 400
bodyMedium: 14sp, weight: 400
bodySmall: 12sp, weight: 400
// 数字(等宽)
numberLarge: 20sp, JetBrains Mono
numberMedium: 16sp, JetBrains Mono
numberSmall: 14sp, JetBrains Mono
5. 间距系统
class Spacing {
static const double xs = 4.0; // 0.25rem
static const double sm = 8.0; // 0.5rem
static const double md = 16.0; // 1rem
static const double lg = 24.0; // 1.5rem
static const double xl = 32.0; // 2rem
static const double xxl = 48.0; // 3rem
}
6. 圆角系统
class BorderRadius {
static const double sm = 4.0;
static const double md = 8.0;
static const double lg = 12.0;
static const double xl = 16.0;
static const double xxl = 24.0;
static const double full = 9999.0;
}
7. 阴影系统
// 微妙的阴影(Vercel 风格)
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 4,
offset: Offset(0, 2),
)
// 悬停阴影
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: Offset(0, 4),
)
8. 动画系统
class AnimationDurations {
static const Duration fast = Duration(milliseconds: 150);
static const Duration normal = Duration(milliseconds: 250);
static const Duration slow = Duration(milliseconds: 400);
static const Duration verySlow = Duration(milliseconds: 600);
}
// Curves
Curves.easeOutCubic // 入场动画
Curves.easeInOutCubic // 过渡动画
Curves.elasticOut // 弹性反馈
组件设计规范
1. 按钮
主要按钮(Primary)
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
minimumSize: Size(44, 44), // 触摸目标
padding: EdgeInsets.symmetric(horizontal: Spacing.lg, vertical: Spacing.md),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(BorderRadius.md),
),
elevation: 0, // 现代风格:无阴影或微阴影
),
)
次要按钮(Secondary)
OutlinedButton(
style: OutlinedButton.styleFrom(
minimumSize: Size(44, 44),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(BorderRadius.md),
),
side: BorderSide(color: Theme.of(context).colorScheme.border),
),
)
幽灵按钮(Ghost)
TextButton(
style: TextButton.styleFrom(
minimumSize: Size(44, 44),
),
)
2. 卡片
Card(
elevation: 0, // 使用边框代替阴影
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(BorderRadius.lg),
side: BorderSide(
color: Theme.of(context).colorScheme.border,
width: 1,
),
),
color: Theme.of(context).colorScheme.cardBackground,
child: Padding(
padding: EdgeInsets.all(Spacing.md),
child: Column(...),
),
)
3. 输入框
TextFormField(
decoration: InputDecoration(
filled: true,
fillColor: Theme.of(context).colorScheme.cardBackground,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(BorderRadius.md),
borderSide: BorderSide(color: Theme.of(context).colorScheme.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(BorderRadius.md),
borderSide: BorderSide(color: Theme.of(context).colorScheme.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(BorderRadius.md),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 2,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.sm,
),
),
)
4. 现代弹窗
标准弹窗
showDialog(
context: context,
builder: (context) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(BorderRadius.xl),
),
backgroundColor: Theme.of(context).colorScheme.cardBackground,
child: Container(
padding: EdgeInsets.all(Spacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题
Text(
'Dialog Title',
style: Theme.of(context).textTheme.displaySmall,
),
SizedBox(height: Spacing.md),
// 内容
Text('Dialog content here...'),
SizedBox(height: Spacing.lg),
// 按钮
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(child: Text('Cancel')),
SizedBox(width: Spacing.sm),
ElevatedButton(child: Text('Confirm')),
],
),
],
),
),
),
);
底部抽屉(Bottom Sheet)
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.cardBackground,
borderRadius: BorderRadius.vertical(
top: Radius.circular(BorderRadius.xl),
),
),
padding: EdgeInsets.all(Spacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 拖动指示器
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.muted,
borderRadius: BorderRadius.circular(2),
),
),
SizedBox(height: Spacing.md),
// 内容
...
],
),
),
);
5. 列表项
ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.sm,
),
leading: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondary,
borderRadius: BorderRadius.circular(BorderRadius.md),
),
child: Icon(icon, size: 20),
),
title: Text(
title,
style: Theme.of(context).textTheme.bodyLarge,
),
subtitle: Text(
subtitle,
style: Theme.of(context).textTheme.bodySmall,
),
trailing: Icon(Icons.chevron_right, size: 20),
)
页面布局规范
1. 通用布局
Scaffold(
backgroundColor: Theme.of(context).colorScheme.background,
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.background,
elevation: 0,
title: Text('Page Title'),
),
body: SafeArea(
child: SingleChildScrollView(
padding: EdgeInsets.all(Spacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 内容
],
),
),
),
)
2. 响应式布局
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 1024) {
// 桌面布局
return _DesktopLayout();
} else if (constraints.maxWidth >= 768) {
// 平板布局
return _TabletLayout();
} else {
// 移动布局
return _MobileLayout();
}
},
)
主题切换实现
1. 主题 Provider
class ThemeProvider extends ChangeNotifier {
ThemeMode _themeMode = ThemeMode.system;
ThemeMode get themeMode => _themeMode;
void toggleTheme() {
_themeMode = _themeMode == ThemeMode.light
? ThemeMode.dark
: ThemeMode.light;
notifyListeners();
}
void setTheme(ThemeMode mode) {
_themeMode = mode;
notifyListeners();
}
}
2. 主题切换按钮
IconButton(
icon: Icon(
Provider.of<ThemeProvider>(context).themeMode == ThemeMode.light
? Icons.dark_mode
: Icons.light_mode,
),
onPressed: () {
Provider.of<ThemeProvider>(context, listen: false).toggleTheme();
},
)
无障碍设计
1. 对比度
- 所有文字/背景组合 >= 4.5:1(WCAG AA)
- 大文字(18sp+)>= 3:1
2. 触摸目标
- 最小触摸目标 44x44
3. 语义化
Semantics(
label: 'Submit button',
button: true,
child: ElevatedButton(...),
)
禁止事项
- ❌ 使用过时的 Bootstrap 蓝 (#007bff)
- ❌ 沉重的阴影
- ❌ 复杂的渐变
- ❌ 文字与背景颜色相同
- ❌ 对比度 < 4.5:1
- ❌ 触摸目标 < 44x44
- ❌ 硬编码颜色值
- ❌ 不一致的间距/圆角
验证清单
- 明暗主题切换正常
- 所有页面风格一致
- 对比度 >= 4.5:1
- 触摸目标 >= 44x44
- flutter analyze 无错误
- 响应式布局正常
- 动画流畅(60fps)
- 无硬编码颜色
- 间距/圆角一致