2026-03-06 16:15:20 +08:00
|
|
|
|
// route_service.dart
|
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
|
|
|
|
import 'package:flutter/foundation.dart';
|
2025-12-12 09:11:30 +08:00
|
|
|
|
import 'package:qhd_prevention/common/route_model.dart';
|
2026-03-06 16:15:20 +08:00
|
|
|
|
import 'package:qhd_prevention/tools/tools.dart';
|
|
|
|
|
|
|
|
|
|
|
|
class RouteService extends ChangeNotifier {
|
2025-12-12 09:11:30 +08:00
|
|
|
|
static final RouteService _instance = RouteService._internal();
|
|
|
|
|
|
factory RouteService() => _instance;
|
|
|
|
|
|
RouteService._internal();
|
|
|
|
|
|
|
2026-03-06 16:15:20 +08:00
|
|
|
|
// 存储顶级菜单(直接从接口解析的数组)
|
2025-12-12 09:11:30 +08:00
|
|
|
|
List<RouteModel> _allRoutes = [];
|
|
|
|
|
|
|
2026-03-06 16:15:20 +08:00
|
|
|
|
/// 对外暴露全部顶级 routes(如果需要遍历所有顶级项)
|
|
|
|
|
|
List<RouteModel> get allRoutes => _allRoutes;
|
|
|
|
|
|
|
|
|
|
|
|
/// 初始化路由配置(允许传 null)
|
|
|
|
|
|
void initializeRoutes(List<dynamic>? routeList) {
|
|
|
|
|
|
_allRoutes = [];
|
|
|
|
|
|
if (routeList == null) return;
|
|
|
|
|
|
for (final item in routeList) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (item is Map<String, dynamic>) {
|
|
|
|
|
|
_allRoutes.add(RouteModel.fromJson(item));
|
|
|
|
|
|
} else if (item is Map) {
|
|
|
|
|
|
_allRoutes.add(RouteModel.fromJson(Map<String, dynamic>.from(item)));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
debugPrint('RouteService: parse route item failed: $e');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 对顶级和子节点进行排序(如果有 sort 字段)
|
|
|
|
|
|
try {
|
|
|
|
|
|
_allRoutes.sort((a, b) => a.sort.compareTo(b.sort));
|
|
|
|
|
|
for (final r in _allRoutes) {
|
|
|
|
|
|
_sortRecursive(r);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
|
|
|
|
|
|
notifyListeners();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _sortRecursive(RouteModel node) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
node.children.sort((a, b) => a.sort.compareTo(b.sort));
|
|
|
|
|
|
for (final c in node.children) {
|
|
|
|
|
|
_sortRecursive(c);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
}
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-03-06 16:15:20 +08:00
|
|
|
|
/// 返回所有顶级(parentId == '0' 或 parentId 为空)的菜单作为主Tab(不在这里筛 visible)
|
|
|
|
|
|
List<RouteModel> get mainTabs {
|
|
|
|
|
|
final tabs = _allRoutes.where((m) {
|
|
|
|
|
|
final isTop = m.parentId == '0' || m.parentId.isEmpty;
|
|
|
|
|
|
return isTop && m.visible; // 只取可见的顶级项
|
|
|
|
|
|
}).toList();
|
|
|
|
|
|
try {
|
|
|
|
|
|
tabs.sort((a, b) => a.sort.compareTo(b.sort));
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
return tabs;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 16:15:20 +08:00
|
|
|
|
// 遍历查找(按 menuUrl)
|
2025-12-12 09:11:30 +08:00
|
|
|
|
RouteModel? findRouteByPath(String path) {
|
2026-03-06 16:15:20 +08:00
|
|
|
|
if (path.isEmpty) return null;
|
|
|
|
|
|
final needle = path.trim();
|
2025-12-12 09:11:30 +08:00
|
|
|
|
for (final route in _allRoutes) {
|
2026-03-06 16:15:20 +08:00
|
|
|
|
final found = _findRouteRecursive(route, needle);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
if (found != null) return found;
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RouteModel? _findRouteRecursive(RouteModel route, String path) {
|
2026-03-06 16:15:20 +08:00
|
|
|
|
// 如果当前节点不可见,则按照你的要求:不再查找其子级(直接返回 null)
|
|
|
|
|
|
if (!route.visible) return null;
|
|
|
|
|
|
|
|
|
|
|
|
final routeUrl = route.menuUrl.trim();
|
|
|
|
|
|
if (routeUrl == path) return route;
|
|
|
|
|
|
|
2025-12-12 09:11:30 +08:00
|
|
|
|
for (final child in route.children) {
|
|
|
|
|
|
final found = _findRouteRecursive(child, path);
|
|
|
|
|
|
if (found != null) return found;
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 16:15:20 +08:00
|
|
|
|
// 获取某个Tab下的所有可显示路由(visible == true,且收集叶子节点)
|
2025-12-12 09:11:30 +08:00
|
|
|
|
List<RouteModel> getRoutesForTab(RouteModel tab) {
|
|
|
|
|
|
final routes = <RouteModel>[];
|
2026-03-06 16:15:20 +08:00
|
|
|
|
_collectVisibleLeafRoutes(tab, routes);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
return routes;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 16:15:20 +08:00
|
|
|
|
/// 关键修改:如果当前节点不可见,则不再递归其 children(按你的要求)
|
|
|
|
|
|
void _collectVisibleLeafRoutes(RouteModel route, List<RouteModel> collector) {
|
|
|
|
|
|
if (!route.visible) return; // 如果父节点不可见,跳过整个子树
|
|
|
|
|
|
if (route.isLeaf) {
|
2025-12-12 09:11:30 +08:00
|
|
|
|
collector.add(route);
|
2026-03-06 16:15:20 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (final child in route.children) {
|
|
|
|
|
|
_collectVisibleLeafRoutes(child, collector);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --------------------- 权限检查相关 ---------------------
|
|
|
|
|
|
|
|
|
|
|
|
/// 判断整个路由树(所有顶级及其子孙)是否存在 menuPerms == perm 且可见的节点
|
|
|
|
|
|
/// 如果父节点不可见,会跳过该父及其子树(按你的要求)
|
|
|
|
|
|
bool hasPerm(String perm) {
|
|
|
|
|
|
if (perm.isEmpty) return false;
|
|
|
|
|
|
final needle = perm.trim();
|
|
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
|
|
|
|
void visit(RouteModel m) {
|
|
|
|
|
|
if (found) return;
|
|
|
|
|
|
// 若父节点不可见,跳过(不再遍历子节点)
|
|
|
|
|
|
if (!m.visible) return;
|
|
|
|
|
|
|
|
|
|
|
|
final mp = (m.menuPerms ?? '').trim();
|
|
|
|
|
|
if (mp.isNotEmpty && mp == needle) {
|
|
|
|
|
|
found = true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (final c in m.children) {
|
|
|
|
|
|
visit(c);
|
|
|
|
|
|
if (found) return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (final top in _allRoutes) {
|
|
|
|
|
|
visit(top);
|
|
|
|
|
|
if (found) break;
|
|
|
|
|
|
}
|
|
|
|
|
|
return found;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool hasAnyPerms(List<String> perms) {
|
|
|
|
|
|
for (final p in perms) {
|
|
|
|
|
|
if (hasPerm(p)) return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, bool> permsMap(List<String> perms) {
|
|
|
|
|
|
final Map<String, bool> map = {};
|
|
|
|
|
|
for (final p in perms) {
|
|
|
|
|
|
map[p] = hasPerm(p);
|
|
|
|
|
|
}
|
|
|
|
|
|
return map;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 尝试按 menuPerms 找到第一个匹配的 RouteModel(若需要路由信息)
|
|
|
|
|
|
/// 如果某个父节点不可见,则不会进入其子树
|
|
|
|
|
|
RouteModel? findRouteByPerm(String perm) {
|
|
|
|
|
|
if (perm.isEmpty) return null;
|
|
|
|
|
|
final needle = perm.trim();
|
|
|
|
|
|
RouteModel? result;
|
|
|
|
|
|
|
|
|
|
|
|
void visit(RouteModel m) {
|
|
|
|
|
|
// printLongString(json.encode(m.toJson()));
|
|
|
|
|
|
if (result != null) return;
|
|
|
|
|
|
if (!m.visible) return; // 父不可见,跳过
|
|
|
|
|
|
final mp = (m.menuPerms ?? '').trim();
|
|
|
|
|
|
if (mp.isNotEmpty && mp == needle) {
|
|
|
|
|
|
result = m;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (final c in m.children) {
|
|
|
|
|
|
visit(c);
|
|
|
|
|
|
if (result != null) return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (final top in _allRoutes) {
|
|
|
|
|
|
visit(top);
|
|
|
|
|
|
if (result != null) break;
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 可返回所有收集到的 menuPerms(仅包含可见节点及其可见子节点)
|
|
|
|
|
|
List<String> collectAllPerms() {
|
|
|
|
|
|
final List<String> perms = [];
|
|
|
|
|
|
void visit(RouteModel m) {
|
|
|
|
|
|
if (!m.visible) return; // 父不可见则跳过子树
|
|
|
|
|
|
final mp = (m.menuPerms ?? '').trim();
|
|
|
|
|
|
if (mp.isNotEmpty) perms.add(mp);
|
|
|
|
|
|
for (final c in m.children) visit(c);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (final top in _allRoutes) visit(top);
|
|
|
|
|
|
return perms;
|
|
|
|
|
|
}
|
|
|
|
|
|
/// 严格查找某个子树(仅包含可见节点及其可见子节点)
|
|
|
|
|
|
static Future<String> getMenuPath(parentPerm,targetPerm) async {
|
|
|
|
|
|
try {
|
|
|
|
|
|
final routeService = RouteService();
|
|
|
|
|
|
// 如果子节点为'',那么查父节点children中第一个
|
|
|
|
|
|
if (targetPerm.isEmpty) {
|
|
|
|
|
|
final route = routeService.findRouteByPerm(parentPerm);
|
|
|
|
|
|
if (route != null) {
|
|
|
|
|
|
// 优先在该节点的子孙中找第一个可见且有 menuUrl 的节点
|
|
|
|
|
|
final childUrl = findFirstVisibleChildUrl(route);
|
|
|
|
|
|
if (childUrl.isNotEmpty) return childUrl;
|
|
|
|
|
|
return '';
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//branchCompany-plan-execute-inspection-records
|
|
|
|
|
|
RouteModel? parent = routeService.findRouteByPerm(parentPerm);
|
|
|
|
|
|
if (parent != null) {
|
|
|
|
|
|
// 在 parent 子树中严格查找 targetPerm
|
|
|
|
|
|
final RouteModel? foundInParent = _findRouteInSubtreeByPerm(parent, targetPerm);
|
|
|
|
|
|
if (foundInParent != null && foundInParent.menuUrl.trim().isNotEmpty) {
|
|
|
|
|
|
return foundInParent.menuUrl.trim();
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-06 16:15:20 +08:00
|
|
|
|
|
|
|
|
|
|
// 未找到 -> 返回空字符串(调用方需做好空串处理)
|
|
|
|
|
|
return '';
|
|
|
|
|
|
} catch (e, st) {
|
|
|
|
|
|
debugPrint('_getMenuPath error: $e\n$st');
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/// 在给定节点的子树中(含自身)查找 menuPerm 完全匹配的节点(只返回可见节点)
|
|
|
|
|
|
static RouteModel? _findRouteInSubtreeByPerm(RouteModel node, String perm) {
|
|
|
|
|
|
if (node.menuPerms.trim() == perm && node.visible) return node;
|
|
|
|
|
|
for (final c in node.children) {
|
|
|
|
|
|
final res = _findRouteInSubtreeByPerm(c, perm);
|
|
|
|
|
|
if (res != null) return res;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
2026-03-06 16:15:20 +08:00
|
|
|
|
return null;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 16:15:20 +08:00
|
|
|
|
/// 在整个路由列表中查找 menuPerm 完全匹配的节点(只返回可见节点)
|
|
|
|
|
|
RouteModel? _findRouteInAllByPerm(List<RouteModel> roots, String perm) {
|
|
|
|
|
|
for (final r in roots) {
|
|
|
|
|
|
final res = _findRouteInSubtreeByPerm(r, perm);
|
|
|
|
|
|
if (res != null) return res;
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 递归在 node 的子孙中按顺序查找第一个 visible 且有 menuUrl 的节点
|
|
|
|
|
|
/// 如果没找到返回空字符串
|
|
|
|
|
|
static String findFirstVisibleChildUrl(RouteModel node) {
|
|
|
|
|
|
final children = node.children;
|
|
|
|
|
|
if (children == null || children.isEmpty) return '';
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-03-06 16:15:20 +08:00
|
|
|
|
for (final c in children) {
|
|
|
|
|
|
// 若该子节点可见并有 menuUrl,直接返回
|
|
|
|
|
|
if ((c.showFlag == 1) && (c.menuUrl ?? '').isNotEmpty) {
|
|
|
|
|
|
return c.menuUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
// return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|