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:
270
app/lib/widgets/responsive_scaffold.dart
Normal file
270
app/lib/widgets/responsive_scaffold.dart
Normal file
@@ -0,0 +1,270 @@
|
||||
// 暖记响应式骨架 — 三级自适应布局
|
||||
// 手机: 底部 TabBar | 平板: 侧边导航 | 桌面: 三栏
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../core/constants/breakpoints.dart';
|
||||
|
||||
/// 暖记自适应 Scaffold
|
||||
class ResponsiveScaffold extends StatefulWidget {
|
||||
const ResponsiveScaffold({
|
||||
super.key,
|
||||
required this.selectedIndex,
|
||||
required this.onDestinationSelected,
|
||||
required this.body,
|
||||
this.floatingActionButton,
|
||||
this.appBarTitle,
|
||||
this.secondaryBody,
|
||||
});
|
||||
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onDestinationSelected;
|
||||
final Widget body;
|
||||
final Widget? floatingActionButton;
|
||||
final String? appBarTitle;
|
||||
final Widget? secondaryBody;
|
||||
|
||||
@override
|
||||
State<ResponsiveScaffold> createState() => _ResponsiveScaffoldState();
|
||||
}
|
||||
|
||||
class _ResponsiveScaffoldState extends State<ResponsiveScaffold> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.sizeOf(context).width;
|
||||
final deviceType = Breakpoints.getDeviceType(width);
|
||||
|
||||
switch (deviceType) {
|
||||
case DeviceType.mobile:
|
||||
return _MobileLayout(
|
||||
selectedIndex: widget.selectedIndex,
|
||||
onDestinationSelected: widget.onDestinationSelected,
|
||||
body: widget.body,
|
||||
floatingActionButton: widget.floatingActionButton,
|
||||
appBarTitle: widget.appBarTitle,
|
||||
);
|
||||
case DeviceType.tablet:
|
||||
return _TabletLayout(
|
||||
selectedIndex: widget.selectedIndex,
|
||||
onDestinationSelected: widget.onDestinationSelected,
|
||||
body: widget.body,
|
||||
appBarTitle: widget.appBarTitle,
|
||||
);
|
||||
case DeviceType.desktop:
|
||||
return _DesktopLayout(
|
||||
selectedIndex: widget.selectedIndex,
|
||||
onDestinationSelected: widget.onDestinationSelected,
|
||||
body: widget.body,
|
||||
secondaryBody: widget.secondaryBody,
|
||||
appBarTitle: widget.appBarTitle,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 导航项定义 =====
|
||||
|
||||
final _navItems = [
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
selectedIcon: const Icon(Icons.home),
|
||||
label: '首页',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.calendar_month_outlined),
|
||||
selectedIcon: const Icon(Icons.calendar_month),
|
||||
label: '日历',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.auto_awesome_outlined),
|
||||
selectedIcon: const Icon(Icons.auto_awesome),
|
||||
label: '心情',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
selectedIcon: const Icon(Icons.search),
|
||||
label: '搜索',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.person_outline),
|
||||
selectedIcon: const Icon(Icons.person),
|
||||
label: '我的',
|
||||
),
|
||||
];
|
||||
|
||||
const _railItems = [
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.home_outlined),
|
||||
selectedIcon: Icon(Icons.home),
|
||||
label: Text('首页'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.calendar_month_outlined),
|
||||
selectedIcon: Icon(Icons.calendar_month),
|
||||
label: Text('日历'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.auto_awesome_outlined),
|
||||
selectedIcon: Icon(Icons.auto_awesome),
|
||||
label: Text('心情'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.search_outlined),
|
||||
selectedIcon: Icon(Icons.search),
|
||||
label: Text('搜索'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.person_outline),
|
||||
selectedIcon: Icon(Icons.person),
|
||||
label: Text('我的'),
|
||||
),
|
||||
];
|
||||
|
||||
// ===== 手机布局 — 底部 TabBar =====
|
||||
|
||||
class _MobileLayout extends StatelessWidget {
|
||||
const _MobileLayout({
|
||||
required this.selectedIndex,
|
||||
required this.onDestinationSelected,
|
||||
required this.body,
|
||||
this.floatingActionButton,
|
||||
this.appBarTitle,
|
||||
});
|
||||
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onDestinationSelected;
|
||||
final Widget body;
|
||||
final Widget? floatingActionButton;
|
||||
final String? appBarTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: appBarTitle != null
|
||||
? AppBar(title: Text(appBarTitle!))
|
||||
: null,
|
||||
body: body,
|
||||
floatingActionButton: floatingActionButton,
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: onDestinationSelected,
|
||||
destinations: _navItems,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 平板布局 — 侧边 NavigationRail =====
|
||||
|
||||
class _TabletLayout extends StatelessWidget {
|
||||
const _TabletLayout({
|
||||
required this.selectedIndex,
|
||||
required this.onDestinationSelected,
|
||||
required this.body,
|
||||
this.appBarTitle,
|
||||
});
|
||||
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onDestinationSelected;
|
||||
final Widget body;
|
||||
final String? appBarTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: appBarTitle != null
|
||||
? AppBar(title: Text(appBarTitle!))
|
||||
: null,
|
||||
body: Row(
|
||||
children: [
|
||||
NavigationRail(
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: onDestinationSelected,
|
||||
destinations: _railItems,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Icon(
|
||||
Icons.edit_note_rounded,
|
||||
size: 32,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
Expanded(child: body),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 桌面布局 — 三栏 =====
|
||||
|
||||
class _DesktopLayout extends StatelessWidget {
|
||||
const _DesktopLayout({
|
||||
required this.selectedIndex,
|
||||
required this.onDestinationSelected,
|
||||
required this.body,
|
||||
this.secondaryBody,
|
||||
this.appBarTitle,
|
||||
});
|
||||
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onDestinationSelected;
|
||||
final Widget body;
|
||||
final Widget? secondaryBody;
|
||||
final String? appBarTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: appBarTitle != null
|
||||
? AppBar(title: Text(appBarTitle!))
|
||||
: null,
|
||||
body: Row(
|
||||
children: [
|
||||
NavigationRail(
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: onDestinationSelected,
|
||||
destinations: _railItems,
|
||||
extended: true,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.edit_note_rounded,
|
||||
size: 32,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'暖记',
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontFamily: 'Caveat',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
// 主内容区
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: body,
|
||||
),
|
||||
// 第二面板(日记详情/预览)
|
||||
if (secondaryBody != null) ...[
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: secondaryBody!,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user