QinGang_interested/lib/pages/main_tab.dart

491 lines
16 KiB
Dart
Raw Normal View History

2026-03-12 10:23:21 +08:00
// lib/pages/new/main_page.dart
import 'dart:convert';
2025-12-12 09:11:30 +08:00
import 'package:flutter/material.dart';
2026-03-12 10:23:21 +08:00
import 'package:flutter/services.dart' show rootBundle;
import 'package:qhd_prevention/common/route_service.dart';
2026-04-01 17:53:42 +08:00
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
2026-03-12 10:23:21 +08:00
import 'package:qhd_prevention/http/modules/appmenu_api.dart';
2026-02-28 16:18:08 +08:00
import 'package:qhd_prevention/pages/badge_manager.dart';
2025-12-12 09:11:30 +08:00
import 'package:qhd_prevention/pages/home/home_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
2026-02-28 16:18:08 +08:00
import 'package:qhd_prevention/pages/notif/notif_page.dart';
2026-04-01 17:53:42 +08:00
import 'package:qhd_prevention/pages/user/login_page.dart';
import 'package:qhd_prevention/services/SessionService.dart';
2025-12-12 09:11:30 +08:00
import 'package:qhd_prevention/services/heartbeat_service.dart';
2026-03-12 10:23:21 +08:00
import 'package:qhd_prevention/tools/tools.dart';
2026-04-01 17:53:42 +08:00
import 'package:shared_preferences/shared_preferences.dart';
2026-03-12 10:23:21 +08:00
import 'mine/mine_page.dart';
2025-12-12 09:11:30 +08:00
/// 用于向子树公布当前 tab 索引
class CurrentTabNotifier extends InheritedWidget {
final int currentIndex;
2026-04-01 17:53:42 +08:00
2025-12-12 09:11:30 +08:00
const CurrentTabNotifier({
required this.currentIndex,
required Widget child,
Key? key,
}) : super(key: key, child: child);
static CurrentTabNotifier? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CurrentTabNotifier>();
}
@override
bool updateShouldNotify(covariant CurrentTabNotifier oldWidget) {
return oldWidget.currentIndex != currentIndex;
}
}
class MainPage extends StatefulWidget {
const MainPage({Key? key, required this.isChooseFirm}) : super(key: key);
final bool isChooseFirm;
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
int _currentIndex = 0;
final GlobalKey<HomePageState> _homeKey = GlobalKey<HomePageState>();
2026-02-28 16:18:08 +08:00
final GlobalKey<NotifPageState> _notifKey = GlobalKey<NotifPageState>();
2025-12-12 09:11:30 +08:00
final GlobalKey<MinePageState> _mineKey = GlobalKey<MinePageState>();
2026-03-12 10:23:21 +08:00
// 固定页面顺序(不要修改顺序,否则对应 _tabVisibility 的索引会错)
late final List<Widget> _pages;
// 存储每个固定Tab是否可见对应 _pages 的顺序)
// [首页, 通知, 我的]
late List<bool> _tabVisibility;
2025-12-12 09:11:30 +08:00
2026-02-28 16:18:08 +08:00
// 添加 BadgeManager 实例
late final BadgeManager _badgeManager;
2025-12-12 09:11:30 +08:00
@override
void initState() {
super.initState();
2026-03-12 10:23:21 +08:00
WidgetsBinding.instance.addObserver(this);
2026-02-28 16:18:08 +08:00
_badgeManager = BadgeManager();
_badgeManager.addListener(_onBadgeChanged);
2026-03-13 15:19:09 +08:00
// 初始化 BadgeManager
if (widget.isChooseFirm) {
_badgeManager.initAllModules();
}
2026-03-12 10:23:21 +08:00
// 初始化固定页面(顺序固定) — **这里保持你要求的构造不变**
2026-01-14 09:55:23 +08:00
_pages = <Widget>[
HomePage(key: _homeKey, isChooseFirm: widget.isChooseFirm),
2026-02-28 16:18:08 +08:00
NotifPage(key: _notifKey),
2026-01-14 09:55:23 +08:00
MinePage(key: _mineKey, isChooseFirm: widget.isChooseFirm),
];
2026-03-12 10:23:21 +08:00
// 初始化时默认隐藏,等待路由加载后再决定显示哪些 tab
_tabVisibility = [false, false, false];
// 监听 RouteService 的更新:当路由数据加载/变更时更新可见性
RouteService().addListener(_onRoutesUpdated);
// 拉取路由(优先接口,失败则回退本地 assets
_getRoute();
}
/// 拉取路由:优先通过接口获取;如果接口失败或返回异常,回退到 assets/route/routes.txt
Future<void> _getRoute() async {
try {
Map? route;
// 接口获取
try {
2026-04-01 17:53:42 +08:00
LoadingDialogHelper.show(message: '加载中...');
final roleId = SessionService.instance.roleId;
final res = await AppMenuApi.getAppMenu({'roleId': roleId});
LoadingDialogHelper.hide();
if (res['success'] == true) {
route = res;
} else {}
2026-03-12 10:23:21 +08:00
} catch (e) {
2026-04-01 17:53:42 +08:00
debugPrint(
'AppMenuApi.getAppMenu error: $e -> fallback to local assets.',
);
2026-03-12 10:23:21 +08:00
}
2026-04-01 17:53:42 +08:00
// 本地获取
// try {
// final routeString = await loadFromAssets();
// route = jsonDecode(routeString) as Map<String, dynamic>;
// } catch (e) {
// debugPrint('loadFromAssets error: $e');
// }
final data = route?['data'] ?? [];
RouteService().initializeRoutes(data);
2026-03-12 10:23:21 +08:00
} catch (e) {
debugPrint('获取路由配置失败: $e');
} finally {
LoadingDialogHelper.hide();
}
}
Future<String> loadFromAssets() async {
return await rootBundle.loadString('assets/route/routes.txt');
}
void _onRoutesUpdated() {
// 路由服务更新时重新计算三个模块的可见性
_updateTabVisibilityFromRoutes();
}
/// 根据 RouteService 中解析的路由来设置 _tabVisibility
/// 规则:
/// - 首页menuPerms == 'dashboard'
/// - 通知menuPerms == 'notice'
/// - 我的menuPerms == 'my-center'
void _updateTabVisibilityFromRoutes() {
final routeService = RouteService();
// 使用 mainTabs如果为空尚未有路由或后端返回空保持当前 _tabVisibility即不自动显示
final mainTabs = routeService.mainTabs;
if (mainTabs.isEmpty) {
2026-04-01 17:53:42 +08:00
_showErrorDialog();
2026-03-12 10:23:21 +08:00
return;
}
bool homeVisible = false;
bool notifVisible = false;
bool mineVisible = false;
for (final m in mainTabs) {
final perms = (m.menuPerms ?? '').toString();
if (!homeVisible && perms == 'dashboard' && m.visible) {
homeVisible = true;
}
2026-04-01 17:53:42 +08:00
if (!notifVisible && perms == 'notice' && m.visible) {
2026-03-12 10:23:21 +08:00
notifVisible = true;
}
2026-04-01 17:53:42 +08:00
if (!mineVisible && perms == 'my-center' && m.visible) {
2026-03-12 10:23:21 +08:00
mineVisible = true;
}
if (homeVisible && notifVisible && mineVisible) break;
}
// 后端未匹配到就隐藏(不兜底)
setState(() {
2026-04-01 17:53:42 +08:00
_tabVisibility = [
homeVisible,
widget.isChooseFirm ? notifVisible : false,
mineVisible,
];
2026-03-12 10:23:21 +08:00
// 若当前激活的 tab 被隐藏,则切换到第一个可见 tab若没有可见 tab则保持当前索引为 0
if (!_isIndexVisible(_currentIndex)) {
final first = _firstVisibleIndexOrDefault(_currentIndex);
_currentIndex = first;
}
});
2026-04-01 17:53:42 +08:00
if (!homeVisible && !notifVisible && !mineVisible) {
_showErrorDialog();
}
}
Future<void> _showErrorDialog() async {
final confirmed = await CustomAlertDialog.showConfirm(
context,
title: '温馨提示',
content: '暂无登录权限,请联系管理员授权!',
force: true,
);
if (confirmed) {
// 清除用户登录状态
final prefs = await SharedPreferences.getInstance();
await prefs.remove('isLoggedIn');
// 清理 SessionService 中的内容(如果你需要)
SessionService.instance.clear();
// 跳转到登录页并清除所有历史路由
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
(Route<dynamic> route) => false, // 移除所有历史路由
);
}
2026-03-12 10:23:21 +08:00
}
// 返回第一个可见 tab 的索引;如果没有可见项,返回给定的 fallback默认0
int _firstVisibleIndexOrDefault([int fallback = 0]) {
for (int i = 0; i < _tabVisibility.length; i++) {
if (_tabVisibility[i]) return i;
}
return fallback;
}
bool _isIndexVisible(int index) {
if (index < 0 || index >= _tabVisibility.length) return false;
return _tabVisibility[index];
2025-12-12 09:11:30 +08:00
}
@override
void dispose() {
2026-03-12 10:23:21 +08:00
// 移除监听
2026-02-28 16:18:08 +08:00
_badgeManager.removeListener(_onBadgeChanged);
2026-03-12 10:23:21 +08:00
RouteService().removeListener(_onRoutesUpdated);
2025-12-12 09:11:30 +08:00
WidgetsBinding.instance.removeObserver(this);
HeartbeatService().stop();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.resumed:
HeartbeatService().resume();
break;
case AppLifecycleState.paused:
case AppLifecycleState.inactive:
case AppLifecycleState.detached:
case AppLifecycleState.hidden:
HeartbeatService().pause();
break;
}
}
Widget _buildIconWithBadge({required Widget icon, required int badgeCount}) {
if (badgeCount <= 0) return icon;
return Stack(
clipBehavior: Clip.none,
children: [
icon,
Positioned(
right: -12,
top: -4,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8),
),
constraints: const BoxConstraints(minWidth: 16, minHeight: 16),
child: Center(
child: Text(
'$badgeCount',
style: const TextStyle(
color: Colors.white,
fontSize: 11,
height: 1,
),
textAlign: TextAlign.center,
),
),
),
),
],
);
}
2026-03-12 10:23:21 +08:00
// 将原始索引(固定顺序 0..n) 转为 可见索引 (visiblePages 索引)
// 若 originalIndex 在当前不可见,则返回第一个可见的索引(若无可见则返回 0
int _originalToVisibleIndex(int originalIndex, List<bool> visibility) {
int visibleIndex = 0;
for (int i = 0; i < visibility.length; i++) {
if (!visibility[i]) continue;
if (i == originalIndex) return visibleIndex;
visibleIndex++;
}
// originalIndex 不可见 -> 返回第一个可见索引(如果没有可见返回 0
return visibility.contains(true) ? 0 : 0;
}
// 将可见索引映射回原始索引(用于 BottomNavigationBar 的 onTap
// 若 visibleIndex 超界,返回第一个可见原始索引或 0
int _visibleToOriginalIndex(int visibleIndex, List<bool> visibility) {
int count = 0;
for (int i = 0; i < visibility.length; i++) {
if (!visibility[i]) continue;
if (count == visibleIndex) return i;
count++;
}
// 若没有找到,返回第一个可见原始索引或 0
for (int i = 0; i < visibility.length; i++) {
if (visibility[i]) return i;
}
return 0;
}
2025-12-12 09:11:30 +08:00
@override
Widget build(BuildContext context) {
2026-03-12 10:23:21 +08:00
// 使用 _badgeManager 而不是 BadgeManager()
2026-02-28 16:18:08 +08:00
final bm = _badgeManager;
2025-12-12 09:11:30 +08:00
2026-03-12 10:23:21 +08:00
// 构建可见的底部导航项与页面(基于 _tabVisibility
2025-12-12 09:11:30 +08:00
final List<BottomNavigationBarItem> visibleItems = [];
final List<Widget> visiblePages = [];
2026-03-12 10:23:21 +08:00
for (int i = 0; i < _pages.length; i++) {
2025-12-12 09:11:30 +08:00
if (_tabVisibility[i]) {
switch (i) {
case 0:
2026-04-01 17:53:42 +08:00
visibleItems.add(
BottomNavigationBarItem(
icon: Image.asset(
'assets/tabbar/basics.png',
width: 24,
height: 24,
),
activeIcon: Image.asset(
'assets/tabbar/basics_cur.png',
width: 24,
height: 24,
),
label: '首页',
),
);
2025-12-12 09:11:30 +08:00
visiblePages.add(_pages[i]);
break;
case 1:
2026-04-01 17:53:42 +08:00
visibleItems.add(
BottomNavigationBarItem(
icon: _buildIconWithBadge(
icon: Image.asset(
'assets/tabbar/works.png',
width: 24,
height: 24,
),
badgeCount: bm.notifCount,
),
activeIcon: _buildIconWithBadge(
icon: Image.asset(
'assets/tabbar/works_cur.png',
width: 24,
height: 24,
),
badgeCount: bm.notifCount,
),
label: '通知',
2026-02-28 16:18:08 +08:00
),
2026-04-01 17:53:42 +08:00
);
2026-02-28 16:18:08 +08:00
visiblePages.add(_pages[i]);
break;
case 2:
2026-04-01 17:53:42 +08:00
visibleItems.add(
BottomNavigationBarItem(
icon: Image.asset(
'assets/tabbar/my.png',
width: 24,
height: 24,
),
activeIcon: Image.asset(
'assets/tabbar/my_cur.png',
width: 24,
height: 24,
),
label: '我的',
),
);
2025-12-12 09:11:30 +08:00
visiblePages.add(_pages[i]);
break;
}
}
}
2026-03-12 10:23:21 +08:00
// 如果没有任何可见页面body 显示空占位(按你之前要求不兜底)
final bool hasVisiblePages = visiblePages.isNotEmpty;
2025-12-12 09:11:30 +08:00
2026-03-12 10:23:21 +08:00
// 将当前索引映射到可见Tab的索引用于 IndexedStack/BottomNavigationBar
2026-04-01 17:53:42 +08:00
final visibleCurrentIndex = _originalToVisibleIndex(
_currentIndex,
_tabVisibility,
);
2026-03-12 10:23:21 +08:00
// ---------- 关键:根据 visibleItems 个数选择底栏渲染方式 ----------
Widget? bottomBarWidget;
if (visibleItems.length >= 2) {
// 正常使用 BottomNavigationBar至少 2 个 item
bottomBarWidget = BottomNavigationBar(
currentIndex: visibleCurrentIndex,
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
onTap: (visibleIndex) {
2026-04-01 17:53:42 +08:00
final originalIndex = _visibleToOriginalIndex(
visibleIndex,
_tabVisibility,
);
2026-03-12 10:23:21 +08:00
setState(() => _currentIndex = originalIndex);
},
items: visibleItems,
);
} else if (visibleItems.length == 1) {
// 自定义单个 tab 底栏(避免 BottomNavigationBar 的断言)
final single = visibleItems[0];
2026-04-01 17:53:42 +08:00
final singleVisibleOriginalIndex = _visibleToOriginalIndex(
0,
_tabVisibility,
);
2026-03-12 10:23:21 +08:00
final isSelected = _currentIndex == singleVisibleOriginalIndex;
// 取选中或未选中的 icon 小部件
2026-04-01 17:53:42 +08:00
final Widget iconWidget =
isSelected && single.activeIcon != null
? single.activeIcon!
: single.icon;
2026-03-12 10:23:21 +08:00
bottomBarWidget = Material(
elevation: 8,
child: InkWell(
onTap: () {
setState(() {
_currentIndex = singleVisibleOriginalIndex;
});
},
child: Container(
height: kBottomNavigationBarHeight,
color: Colors.white,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 图标(上)
SizedBox(
width: 28,
height: 28,
child: Center(child: iconWidget),
),
const SizedBox(height: 4),
// 文本(下)
Text(
single.label ?? '',
style: TextStyle(
color: isSelected ? Colors.blue : Colors.grey,
fontSize: 12,
),
),
],
),
),
),
),
);
} else {
// 没有可见项 -> 不显示底栏
bottomBarWidget = null;
}
2025-12-12 09:11:30 +08:00
return CurrentTabNotifier(
currentIndex: _currentIndex,
child: Scaffold(
appBar: null,
2026-04-01 17:53:42 +08:00
body:
hasVisiblePages
? IndexedStack(
index: visibleCurrentIndex,
children: visiblePages,
)
: const SizedBox.shrink(),
2026-03-12 10:23:21 +08:00
bottomNavigationBar: bottomBarWidget,
2025-12-12 09:11:30 +08:00
),
);
}
2026-02-28 16:18:08 +08:00
void _onBadgeChanged() {
// 当角标数据变化时,只更新需要重建的部分
2026-03-12 10:23:21 +08:00
if (mounted) setState(() {});
2026-02-28 16:18:08 +08:00
}
2026-04-01 17:53:42 +08:00
}