修复内容:
- 编辑器返回按钮: 所有 context.go('/editor') 改为 context.push(),pop() 加安全守卫 fallback 到 /home
- Tab 导航: Web 平台强制使用移动端底部 TabBar 布局 (kIsWeb 守卫)
- 数据库编码: db.rs 自动追加 client_encoding=utf8 参数,修复中文 display_name 乱码
- AuthBloc token: 清理冗余 TODO,token 注入已在 AuthRepository 中正常工作
- 影响 9 个文件的编辑器导航调用点统一修改
277 lines
7.7 KiB
Dart
277 lines
7.7 KiB
Dart
// 暖记响应式骨架 — 三级自适应布局
|
||
// 手机: 底部 TabBar | 平板: 侧边导航 | 桌面: 三栏
|
||
// Web 平台始终使用底部 TabBar(移动端布局)以保证导航交互正常
|
||
|
||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||
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;
|
||
// Web 平台:始终使用移动端底部 TabBar 布局
|
||
// 原因:Web 上 NavigationRail 点击事件可能被 Flutter CanvasKit 拦截
|
||
final deviceType = kIsWeb
|
||
? DeviceType.mobile
|
||
: 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!,
|
||
),
|
||
],
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|