QinGang_interested/lib/pages/main_tab.dart

416 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// lib/pages/new/main_page.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:qhd_prevention/common/route_service.dart';
import 'package:qhd_prevention/http/modules/appmenu_api.dart';
import 'package:qhd_prevention/pages/badge_manager.dart';
import 'package:qhd_prevention/pages/home/home_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/pages/notif/notif_page.dart';
import 'package:qhd_prevention/services/heartbeat_service.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'mine/mine_page.dart';
/// 用于向子树公布当前 tab 索引
class CurrentTabNotifier extends InheritedWidget {
final int currentIndex;
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>();
final GlobalKey<NotifPageState> _notifKey = GlobalKey<NotifPageState>();
final GlobalKey<MinePageState> _mineKey = GlobalKey<MinePageState>();
// 固定页面顺序(不要修改顺序,否则对应 _tabVisibility 的索引会错)
late final List<Widget> _pages;
// 存储每个固定Tab是否可见对应 _pages 的顺序)
// [首页, 通知, 我的]
late List<bool> _tabVisibility;
// 添加 BadgeManager 实例
late final BadgeManager _badgeManager;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
// 初始化 BadgeManager
_badgeManager = BadgeManager();
_badgeManager.initAllModules();
_badgeManager.addListener(_onBadgeChanged);
// 初始化固定页面(顺序固定) — **这里保持你要求的构造不变**
_pages = <Widget>[
HomePage(key: _homeKey, isChooseFirm: widget.isChooseFirm),
NotifPage(key: _notifKey),
MinePage(key: _mineKey, isChooseFirm: widget.isChooseFirm),
];
// 初始化时默认隐藏,等待路由加载后再决定显示哪些 tab
_tabVisibility = [false, false, false];
// 监听 RouteService 的更新:当路由数据加载/变更时更新可见性
RouteService().addListener(_onRoutesUpdated);
// 拉取路由(优先接口,失败则回退本地 assets
_getRoute();
}
/// 拉取路由:优先通过接口获取;如果接口失败或返回异常,回退到 assets/route/routes.txt
Future<void> _getRoute() async {
try {
LoadingDialogHelper.show(message: '加载中...');
Map? route;
// 接口获取
// try {
// final res = await AppMenuApi.getAppMenu();
// if (res != null && res['success'] == true && res['data'] is List) {
// route = res;
// } else {
// debugPrint('AppMenuApi.getAppMenu returned no data or failed; fallback to local assets.');
// }
// } catch (e) {
// debugPrint('AppMenuApi.getAppMenu error: $e -> fallback to local assets.');
// }
// 本地获取
try {
final routeString = await loadFromAssets();
route = jsonDecode(routeString) as Map<String, dynamic>;
} catch (e) {
debugPrint('loadFromAssets error: $e');
}
if (route != null && route['data'] is List) {
final data = route['data'] as List<dynamic>;
RouteService().initializeRoutes(data);
// initializeRoutes 内部会 notifyListeners -> _onRoutesUpdated 被调用
} else {
debugPrint('No valid route data to initialize RouteService.');
}
} 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) {
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;
}
if (!notifVisible && perms == 'notice'&& m.visible) {
notifVisible = true;
}
if (!mineVisible && perms == 'my-center'&& m.visible) {
mineVisible = true;
}
if (homeVisible && notifVisible && mineVisible) break;
}
// 后端未匹配到就隐藏(不兜底)
setState(() {
_tabVisibility = [homeVisible, notifVisible, mineVisible];
// 若当前激活的 tab 被隐藏,则切换到第一个可见 tab若没有可见 tab则保持当前索引为 0
if (!_isIndexVisible(_currentIndex)) {
final first = _firstVisibleIndexOrDefault(_currentIndex);
_currentIndex = first;
}
});
}
// 返回第一个可见 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];
}
@override
void dispose() {
// 移除监听
_badgeManager.removeListener(_onBadgeChanged);
RouteService().removeListener(_onRoutesUpdated);
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,
),
),
),
),
],
);
}
// 将原始索引(固定顺序 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;
}
@override
Widget build(BuildContext context) {
// 使用 _badgeManager 而不是 BadgeManager()
final bm = _badgeManager;
// 构建可见的底部导航项与页面(基于 _tabVisibility
final List<BottomNavigationBarItem> visibleItems = [];
final List<Widget> visiblePages = [];
for (int i = 0; i < _pages.length; i++) {
if (_tabVisibility[i]) {
switch (i) {
case 0:
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: '首页',
));
visiblePages.add(_pages[i]);
break;
case 1:
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: '通知',
));
visiblePages.add(_pages[i]);
break;
case 2:
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: '我的',
));
visiblePages.add(_pages[i]);
break;
}
}
}
// 如果没有任何可见页面body 显示空占位(按你之前要求不兜底)
final bool hasVisiblePages = visiblePages.isNotEmpty;
// 将当前索引映射到可见Tab的索引用于 IndexedStack/BottomNavigationBar
final visibleCurrentIndex = _originalToVisibleIndex(_currentIndex, _tabVisibility);
// ---------- 关键:根据 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) {
final originalIndex = _visibleToOriginalIndex(visibleIndex, _tabVisibility);
setState(() => _currentIndex = originalIndex);
},
items: visibleItems,
);
} else if (visibleItems.length == 1) {
// 自定义单个 tab 底栏(避免 BottomNavigationBar 的断言)
final single = visibleItems[0];
final singleVisibleOriginalIndex = _visibleToOriginalIndex(0, _tabVisibility);
final isSelected = _currentIndex == singleVisibleOriginalIndex;
// 取选中或未选中的 icon 小部件
final Widget iconWidget = isSelected && single.activeIcon != null ? single.activeIcon! : single.icon;
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;
}
return CurrentTabNotifier(
currentIndex: _currentIndex,
child: Scaffold(
appBar: null,
body: hasVisiblePages
? IndexedStack(
index: visibleCurrentIndex,
children: visiblePages,
)
: const SizedBox.shrink(),
bottomNavigationBar: bottomBarWidget,
),
);
}
void _onBadgeChanged() {
// 当角标数据变化时,只更新需要重建的部分
if (mounted) setState(() {});
}
}