Files
nj/app/lib/widgets/responsive_scaffold.dart
iven 8e3e232278 fix: 全链路问题修复 — 编辑器返回/Tab导航/数据库编码/Token注入
修复内容:
- 编辑器返回按钮: 所有 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 个文件的编辑器导航调用点统一修改
2026-06-01 18:08:09 +08:00

277 lines
7.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 暖记响应式骨架 — 三级自适应布局
// 手机: 底部 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!,
),
],
],
),
);
}
}