- 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
271 lines
7.4 KiB
Dart
271 lines
7.4 KiB
Dart
// 暖记响应式骨架 — 三级自适应布局
|
|
// 手机: 底部 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!,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|