800 lines
45 KiB
PHP
Executable File
800 lines
45 KiB
PHP
Executable File
@extends('layouts.app')
|
||
|
||
@section('title', '应用路由信息')
|
||
|
||
@section('content')
|
||
<div class="container mx-auto px-4 py-8">
|
||
<div class="bg-white rounded-xl shadow-lg p-6 mb-8">
|
||
<h1 class="text-3xl font-bold text-gray-800 mb-4 flex items-center">
|
||
<i class="fa fa-road mr-3 text-primary"></i>应用路由信息 共{{ count($routes) }} 个
|
||
</h1>
|
||
<p class="text-gray-600 mb-6">这里展示了应用中所有已注册的路由信息,包括请求方法、URI、名称、控制器和中间件。</p>
|
||
|
||
<!-- 分组和视图切换选项 -->
|
||
<div class="mb-6 flex flex-col md:flex-row justify-between items-start md:items-center space-y-4 md:space-y-0">
|
||
<div class="flex flex-wrap gap-2">
|
||
<button class="view-toggle-btn active px-4 py-2 rounded-lg bg-primary text-white font-medium transition-all duration-200" data-view="all">
|
||
<i class="fa fa-list-ul mr-1"></i>全部路由
|
||
</button>
|
||
<button class="view-toggle-btn px-4 py-2 rounded-lg bg-gray-100 text-gray-700 font-medium transition-all duration-200 hover:bg-gray-200" data-view="groups">
|
||
<i class="fa fa-th-large mr-1"></i>分组视图
|
||
</button>
|
||
</div>
|
||
|
||
<div class="relative w-full md:w-64">
|
||
<input type="text" id="route-search" placeholder="搜索路由..."
|
||
class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all duration-300">
|
||
<i class="fa fa-search absolute left-3 top-3 text-gray-400"></i>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分组卡片视图 (默认隐藏) -->
|
||
<div id="groups-view" class="mb-8 hidden">
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||
@php
|
||
// 优化内存使用的路由分组方法 - 支持多级分组
|
||
function getGroupedRoutes($routes) {
|
||
$groupedRoutes = [];
|
||
|
||
foreach ($routes as $route) {
|
||
$action = $route->getAction();
|
||
$controllerName = null;
|
||
$groupName = '其他';
|
||
$subgroupName = null;
|
||
$subsubgroupName = null;
|
||
|
||
// 获取控制器和方法
|
||
if (isset($action['controller'])) {
|
||
$controllerParts = explode('@', $action['controller']);
|
||
$controllerName = $controllerParts[0];
|
||
|
||
// 解析控制器命名空间获取分组信息
|
||
$namespaceParts = explode('\\', $controllerName);
|
||
|
||
// 主要分组 - 通常是控制器所在的顶级目录
|
||
$groupName = count($namespaceParts) > 3 ? $namespaceParts[3] : '其他';
|
||
|
||
// 二级分组
|
||
$subgroupName = count($namespaceParts) > 4 ? $namespaceParts[4] : null;
|
||
|
||
// 三级分组
|
||
$subsubgroupName = count($namespaceParts) > 5 ? $namespaceParts[5] : null;
|
||
}
|
||
|
||
// 构建分组结构
|
||
if (!isset($groupedRoutes[$groupName])) {
|
||
$groupedRoutes[$groupName] = [
|
||
'routes' => [],
|
||
'subgroups' => []
|
||
];
|
||
}
|
||
|
||
if ($subgroupName) {
|
||
if (!isset($groupedRoutes[$groupName]['subgroups'][$subgroupName])) {
|
||
$groupedRoutes[$groupName]['subgroups'][$subgroupName] = [
|
||
'routes' => [],
|
||
'subgroups' => []
|
||
];
|
||
}
|
||
|
||
if ($subsubgroupName) {
|
||
if (!isset($groupedRoutes[$groupName]['subgroups'][$subgroupName]['subgroups'][$subsubgroupName])) {
|
||
$groupedRoutes[$groupName]['subgroups'][$subgroupName]['subgroups'][$subsubgroupName] = [];
|
||
}
|
||
$groupedRoutes[$groupName]['subgroups'][$subgroupName]['subgroups'][$subsubgroupName][] = $route;
|
||
} else {
|
||
$groupedRoutes[$groupName]['subgroups'][$subgroupName]['routes'][] = $route;
|
||
}
|
||
} else {
|
||
$groupedRoutes[$groupName]['routes'][] = $route;
|
||
}
|
||
|
||
// 每处理100个路由释放一次内存
|
||
if (count($groupedRoutes[$groupName]['routes']) % 100 === 0) {
|
||
gc_collect_cycles();
|
||
}
|
||
}
|
||
|
||
return $groupedRoutes;
|
||
}
|
||
|
||
$groupedRoutes = getGroupedRoutes($routes);
|
||
@endphp
|
||
|
||
@foreach ($groupedRoutes as $group => $groupData)
|
||
<div class="group-card bg-white rounded-lg shadow-md overflow-hidden border border-gray-100 transition-all duration-300 hover:shadow-lg hover:border-gray-200 cursor-pointer" data-group="{{ $group }}" data-level="1">
|
||
<div class="p-5">
|
||
<div class="flex justify-between items-start mb-3">
|
||
<h3 class="font-bold text-lg text-gray-800">{{ $group }}</h3>
|
||
<span class="bg-primary/10 text-primary px-2 py-1 rounded-full text-xs font-medium">
|
||
{{ count($groupData['routes']) + array_sum(array_map(function($subgroup) {
|
||
return count($subgroup['routes']) + array_sum(array_map('count', $subgroup['subgroups']));
|
||
}, $groupData['subgroups'])) }} 条路由
|
||
</span>
|
||
</div>
|
||
<div class="flex flex-wrap gap-2 mb-3">
|
||
@php
|
||
$methodCounts = [];
|
||
// 统计主组路由的方法
|
||
foreach ($groupData['routes'] as $route) {
|
||
$methods = $route->methods();
|
||
if (($key = array_search('HEAD', $methods)) !== false) {
|
||
unset($methods[$key]);
|
||
}
|
||
foreach ($methods as $method) {
|
||
if (!isset($methodCounts[$method])) {
|
||
$methodCounts[$method] = 0;
|
||
}
|
||
$methodCounts[$method]++;
|
||
}
|
||
}
|
||
// 统计子组路由的方法
|
||
foreach ($groupData['subgroups'] as $subgroupRoutes) {
|
||
foreach ($subgroupRoutes['routes'] as $route) {
|
||
$methods = $route->methods();
|
||
if (($key = array_search('HEAD', $methods)) !== false) {
|
||
unset($methods[$key]);
|
||
}
|
||
foreach ($methods as $method) {
|
||
if (!isset($methodCounts[$method])) {
|
||
$methodCounts[$method] = 0;
|
||
}
|
||
$methodCounts[$method]++;
|
||
}
|
||
}
|
||
// 统计子子组路由的方法
|
||
foreach ($subgroupRoutes['subgroups'] as $subsubgroupRoutes) {
|
||
foreach ($subsubgroupRoutes as $route) {
|
||
$methods = $route->methods();
|
||
if (($key = array_search('HEAD', $methods)) !== false) {
|
||
unset($methods[$key]);
|
||
}
|
||
foreach ($methods as $method) {
|
||
if (!isset($methodCounts[$method])) {
|
||
$methodCounts[$method] = 0;
|
||
}
|
||
$methodCounts[$method]++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
@endphp
|
||
|
||
@foreach ($methodCounts as $method => $count)
|
||
@if ($method == 'GET')
|
||
<span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800">{{ $method }} ({{ $count }})</span>
|
||
@elseif ($method == 'POST')
|
||
<span class="px-2 py-1 text-xs rounded-full bg-blue-100 text-blue-800">{{ $method }} ({{ $count }})</span>
|
||
@elseif ($method == 'PUT')
|
||
<span class="px-2 py-1 text-xs rounded-full bg-yellow-100 text-yellow-800">{{ $method }} ({{ $count }})</span>
|
||
@elseif ($method == 'DELETE')
|
||
<span class="px-2 py-1 text-xs rounded-full bg-red-100 text-red-800">{{ $method }} ({{ $count }})</span>
|
||
@elseif ($method == 'PATCH')
|
||
<span class="px-2 py-1 text-xs rounded-full bg-purple-100 text-purple-800">{{ $method }} ({{ $count }})</span>
|
||
@else
|
||
<span class="px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800">{{ $method }} ({{ $count }})</span>
|
||
@endif
|
||
@endforeach
|
||
</div>
|
||
<div class="text-sm text-gray-500 line-clamp-2">
|
||
@php
|
||
$routePaths = [];
|
||
// 添加主组路由示例
|
||
foreach (array_slice($groupData['routes'], 0, 1) as $route) {
|
||
$routePaths[] = $route->uri();
|
||
}
|
||
// 添加子组路由示例
|
||
foreach ($groupData['subgroups'] as $subgroup => $subgroupData) {
|
||
if (count($routePaths) >= 3) break;
|
||
if (count($subgroupData['routes']) > 0) {
|
||
$routePaths[] = "[$subgroup] " . $subgroupData['routes'][0]->uri();
|
||
} elseif (count($subgroupData['subgroups']) > 0) {
|
||
$firstSubsubgroup = array_key_first($subgroupData['subgroups']);
|
||
$routePaths[] = "[$subgroup/$firstSubsubgroup] " . $subgroupData['subgroups'][$firstSubsubgroup][0]->uri();
|
||
}
|
||
}
|
||
echo implode(', ', $routePaths) . (count($groupData['routes']) + count($groupData['subgroups']) > 3 ? '...' : '');
|
||
@endphp
|
||
</div>
|
||
</div>
|
||
<div class="bg-gray-50 px-5 py-3 border-t border-gray-100 flex justify-between items-center">
|
||
<span class="text-xs text-gray-500">点击查看详情</span>
|
||
<i class="fa fa-chevron-right text-gray-400"></i>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分组路由详情视图 (默认隐藏) -->
|
||
<div id="group-details-view" class="mb-8 hidden">
|
||
<div class="flex items-center mb-6">
|
||
<button id="back-to-groups" class="text-primary hover:text-primary/80 flex items-center transition-colors duration-200">
|
||
<i class="fa fa-arrow-left mr-2"></i> 返回分组
|
||
</button>
|
||
<h2 id="current-group-name" class="text-xl font-bold text-gray-800 ml-4"></h2>
|
||
<span id="group-breadcrumb" class="text-sm text-gray-500 ml-2"></span>
|
||
</div>
|
||
|
||
<div id="group-content" class="space-y-6">
|
||
<!-- 主组路由表格 -->
|
||
<div id="main-group-routes" class="overflow-x-auto rounded-lg shadow-inner">
|
||
<h3 class="text-lg font-semibold text-gray-800 p-4 bg-gray-50 rounded-t-lg">主组路由</h3>
|
||
<table class="min-w-full divide-y divide-gray-200">
|
||
<thead class="bg-gray-50">
|
||
<tr>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">URI</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden md:table-cell">名称</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden lg:table-cell">控制器</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden xl:table-cell">中间件</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="bg-white divide-y divide-gray-200" id="main-group-routes-body">
|
||
<!-- 主组路由内容将通过JavaScript动态填充 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 子组卡片视图 -->
|
||
<div id="subgroups-view" class="space-y-4">
|
||
<h3 class="text-lg font-semibold text-gray-800">子分组</h3>
|
||
<div id="subgroups-container" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
<!-- 子组卡片将通过JavaScript动态填充 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 全部路由表格视图 (默认显示) -->
|
||
<div id="all-routes-view" class="mb-8">
|
||
<div class="overflow-x-auto rounded-lg shadow-inner">
|
||
<table class="min-w-full divide-y divide-gray-200">
|
||
<thead class="bg-gray-50">
|
||
<tr>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">URI</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden md:table-cell">名称</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden lg:table-cell">控制器</th>
|
||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden xl:table-cell">中间件</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="bg-white divide-y divide-gray-200" id="routes-table-body">
|
||
@foreach ($routes as $route)
|
||
@php
|
||
$methods = $route->methods();
|
||
// 移除 HEAD 方法,因为它通常与 GET 一起使用
|
||
if (($key = array_search('HEAD', $methods)) !== false) {
|
||
unset($methods[$key]);
|
||
}
|
||
$method = implode('|', $methods);
|
||
|
||
// 根据不同的 HTTP 方法设置不同的颜色
|
||
$methodClass = 'bg-gray-100 text-gray-800';
|
||
if (in_array('GET', $methods)) $methodClass = 'bg-green-100 text-green-800';
|
||
if (in_array('POST', $methods)) $methodClass = 'bg-blue-100 text-blue-800';
|
||
if (in_array('PUT', $methods)) $methodClass = 'bg-yellow-100 text-yellow-800';
|
||
if (in_array('DELETE', $methods)) $methodClass = 'bg-red-100 text-red-800';
|
||
if (in_array('PATCH', $methods)) $methodClass = 'bg-purple-100 text-purple-800';
|
||
|
||
// 获取控制器和方法
|
||
$action = $route->getAction();
|
||
$controller = null;
|
||
if (isset($action['controller'])) {
|
||
$controllerParts = explode('@', $action['controller']);
|
||
$controller = count($controllerParts) > 1 ?
|
||
'<span class="text-gray-600">' . $controllerParts[0] . '</span>@<span class="font-medium">' . $controllerParts[1] . '</span>' :
|
||
$action['controller'];
|
||
}
|
||
|
||
// 获取中间件
|
||
$middleware = isset($action['middleware']) ? $action['middleware'] : [];
|
||
if (is_string($middleware)) {
|
||
$middleware = explode('|', $middleware);
|
||
}
|
||
|
||
// 确定路由所属组和子组
|
||
$routeGroup = '其他';
|
||
$routeSubgroup = null;
|
||
$routeSubsubgroup = null;
|
||
if (isset($action['controller'])) {
|
||
$controllerName = explode('@', $action['controller'])[0];
|
||
$parts = explode('\\', $controllerName);
|
||
$routeGroup = count($parts) > 3 ? $parts[3] : '其他';
|
||
$routeSubgroup = count($parts) > 4 ? $parts[4] : null;
|
||
$routeSubsubgroup = count($parts) > 5 ? $parts[5] : null;
|
||
}
|
||
@endphp
|
||
<tr class="hover:bg-gray-50 transition-colors duration-200"
|
||
data-group="{{ $routeGroup }}"
|
||
data-subgroup="{{ $routeSubgroup ?: '' }}"
|
||
data-subsubgroup="{{ $routeSubsubgroup ?: '' }}">
|
||
<td class="px-6 py-4">
|
||
<div class="font-medium text-gray-900">
|
||
{{ $route->uri() }}
|
||
<button data-uri="{{ $route->uri() }}" class="copy-btn ml-2 inline-flex items-center px-2.5 py-0.5 border border-transparent text-xs font-medium rounded-full text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||
<i class="fa fa-copy"></i> 复制
|
||
</button>
|
||
</div>
|
||
<span class="mt-5 px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ $methodClass }}">
|
||
{{ $method }}
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 hidden md:table-cell">
|
||
<div class="text-sm text-gray-500">
|
||
{{ $route->getName() ?: '-' }}
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 hidden lg:table-cell">
|
||
<div class="text-sm text-gray-500" style="max-width: 300px; word-wrap: break-word;">
|
||
{!! $controller ?: '-' !!}
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 hidden xl:table-cell">
|
||
<div class="text-xs text-gray-500">
|
||
@if (count($middleware) > 0)
|
||
@foreach ($middleware as $mw)
|
||
<span class="inline-block px-2 py-0.5 bg-gray-100 rounded-full mr-1 mb-1">
|
||
{{ $mw }}
|
||
</span>
|
||
@endforeach
|
||
@else
|
||
-
|
||
@endif
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
@if (count($routes) === 0)
|
||
<div class="mt-8 text-center py-12 border-2 border-dashed border-gray-300 rounded-lg">
|
||
<i class="fa fa-road text-4xl text-gray-300 mb-4"></i>
|
||
<h3 class="text-lg font-medium text-gray-900 mb-2">没有找到路由</h3>
|
||
<p class="text-gray-500 max-w-md mx-auto">应用中尚未注册任何路由。请确保你的路由文件已正确定义。</p>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
<div class="bg-white rounded-xl shadow-lg p-6">
|
||
<h2 class="text-xl font-bold text-gray-800 mb-4 flex items-center">
|
||
<i class="fa fa-legend mr-3 text-primary"></i>路由方法图例
|
||
</h2>
|
||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
|
||
<div class="flex items-center">
|
||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800 mr-2">GET</span>
|
||
<span class="text-sm text-gray-600">读取操作</span>
|
||
</div>
|
||
<div class="flex items-center">
|
||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800 mr-2">POST</span>
|
||
<span class="text-sm text-gray-600">创建操作</span>
|
||
</div>
|
||
<div class="flex items-center">
|
||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800 mr-2">PUT</span>
|
||
<span class="text-sm text-gray-600">更新操作</span>
|
||
</div>
|
||
<div class="flex items-center">
|
||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800 mr-2">DELETE</span>
|
||
<span class="text-sm text-gray-600">删除操作</span>
|
||
</div>
|
||
<div class="flex items-center">
|
||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-purple-100 text-purple-800 mr-2">PATCH</span>
|
||
<span class="text-sm text-gray-600">部分更新</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 视图切换功能
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const viewToggleBtns = document.querySelectorAll('.view-toggle-btn');
|
||
const allRoutesView = document.getElementById('all-routes-view');
|
||
const groupsView = document.getElementById('groups-view');
|
||
const groupDetailsView = document.getElementById('group-details-view');
|
||
const groupCards = document.querySelectorAll('.group-card');
|
||
const backToGroupsBtn = document.getElementById('back-to-groups');
|
||
const currentGroupName = document.getElementById('current-group-name');
|
||
const groupBreadcrumb = document.getElementById('group-breadcrumb');
|
||
const mainGroupRoutesBody = document.getElementById('main-group-routes-body');
|
||
const subgroupsContainer = document.getElementById('subgroups-container');
|
||
const routeSearch = document.getElementById('route-search');
|
||
const routesTableBody = document.getElementById('routes-table-body');
|
||
const copyButtons = document.querySelectorAll('.copy-btn');
|
||
|
||
copyButtons.forEach(button => {
|
||
button.addEventListener('click', function() {
|
||
// 获取当前按钮的data-uri属性值
|
||
const uriToCopy = this.getAttribute('data-uri');
|
||
|
||
if (uriToCopy) {
|
||
// 执行复制操作
|
||
navigator.clipboard.writeText(uriToCopy)
|
||
.then(() => {
|
||
// 复制成功后的视觉反馈
|
||
this.innerHTML = '<i class="fa fa-check"></i> 已复制';
|
||
this.classList.add('bg-green-500', 'text-white');
|
||
|
||
// 3秒后恢复原状
|
||
setTimeout(() => {
|
||
this.classList.remove('bg-green-500', 'text-white');
|
||
this.innerHTML = '<i class="fa fa-copy"></i> 复制';
|
||
}, 3000);
|
||
})
|
||
.catch(err => {
|
||
console.error('复制失败: ', err);
|
||
this.innerHTML = '<i class="fa fa-times"></i> 复制失败';
|
||
this.classList.add('bg-red-500', 'text-white');
|
||
});
|
||
} else {
|
||
console.error('未找到data-uri属性');
|
||
this.innerHTML = '<i class="fa fa-exclamation"></i> 无内容';
|
||
}
|
||
});
|
||
});
|
||
// 当前选中的分组路径
|
||
let currentGroupPath = [];
|
||
|
||
// 视图切换
|
||
viewToggleBtns.forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
// 移除所有按钮的活跃状态
|
||
viewToggleBtns.forEach(b => {
|
||
b.classList.remove('active', 'bg-primary', 'text-white');
|
||
b.classList.add('bg-gray-100', 'text-gray-700');
|
||
});
|
||
|
||
// 添加当前按钮的活跃状态
|
||
this.classList.add('active', 'bg-primary', 'text-white');
|
||
this.classList.remove('bg-gray-100', 'text-gray-700');
|
||
|
||
// 显示对应视图
|
||
const view = this.getAttribute('data-view');
|
||
if (view === 'all') {
|
||
allRoutesView.classList.remove('hidden');
|
||
groupsView.classList.add('hidden');
|
||
groupDetailsView.classList.add('hidden');
|
||
} else if (view === 'groups') {
|
||
allRoutesView.classList.add('hidden');
|
||
groupsView.classList.remove('hidden');
|
||
groupDetailsView.classList.add('hidden');
|
||
currentGroupPath = [];
|
||
}
|
||
});
|
||
});
|
||
|
||
// 分组卡片点击事件
|
||
document.addEventListener('click', function(e) {
|
||
if (e.target.closest('.group-card')) {
|
||
const card = e.target.closest('.group-card');
|
||
const groupName = card.getAttribute('data-group');
|
||
const level = parseInt(card.getAttribute('data-level'));
|
||
|
||
// 更新当前分组路径
|
||
if (level === 1) {
|
||
currentGroupPath = [groupName];
|
||
} else if (level === 2) {
|
||
currentGroupPath = [currentGroupPath[0], groupName];
|
||
} else if (level === 3) {
|
||
currentGroupPath = [currentGroupPath[0], currentGroupPath[1], groupName];
|
||
}
|
||
|
||
showGroupDetails(currentGroupPath);
|
||
}
|
||
});
|
||
|
||
// 返回分组按钮点击事件
|
||
backToGroupsBtn.addEventListener('click', function() {
|
||
allRoutesView.classList.add('hidden');
|
||
groupsView.classList.remove('hidden');
|
||
groupDetailsView.classList.add('hidden');
|
||
currentGroupPath = [];
|
||
});
|
||
|
||
// 显示分组详情
|
||
function showGroupDetails(groupPath) {
|
||
// 更新当前组名和面包屑
|
||
currentGroupName.textContent = groupPath[groupPath.length - 1];
|
||
groupBreadcrumb.textContent = groupPath.length > 1 ?
|
||
' > ' + groupPath.slice(0, -1).join(' > ') : '';
|
||
|
||
// 获取该组的所有路由
|
||
const allRoutes = Array.from(routesTableBody.querySelectorAll('tr'));
|
||
let groupRoutes;
|
||
|
||
if (groupPath.length === 1) {
|
||
// 一级分组
|
||
groupRoutes = allRoutes.filter(row =>
|
||
row.getAttribute('data-group') === groupPath[0] &&
|
||
!row.getAttribute('data-subgroup')
|
||
);
|
||
|
||
// 获取子组信息
|
||
const subgroups = {};
|
||
allRoutes.forEach(row => {
|
||
if (row.getAttribute('data-group') === groupPath[0] && row.getAttribute('data-subgroup')) {
|
||
const subgroup = row.getAttribute('data-subgroup');
|
||
if (!subgroups[subgroup]) {
|
||
subgroups[subgroup] = {
|
||
routes: 0,
|
||
subsubgroups: new Set()
|
||
};
|
||
}
|
||
subgroups[subgroup].routes++;
|
||
|
||
if (row.getAttribute('data-subsubgroup')) {
|
||
subgroups[subgroup].subsubgroups.add(row.getAttribute('data-subsubgroup'));
|
||
}
|
||
}
|
||
});
|
||
|
||
// 渲染子组卡片
|
||
renderSubgroupCards(subgroups, 2);
|
||
} else if (groupPath.length === 2) {
|
||
// 二级分组
|
||
groupRoutes = allRoutes.filter(row =>
|
||
row.getAttribute('data-group') === groupPath[0] &&
|
||
row.getAttribute('data-subgroup') === groupPath[1] &&
|
||
!row.getAttribute('data-subsubgroup')
|
||
);
|
||
|
||
// 获取子子组信息
|
||
const subsubgroups = {};
|
||
allRoutes.forEach(row => {
|
||
if (
|
||
row.getAttribute('data-group') === groupPath[0] &&
|
||
row.getAttribute('data-subgroup') === groupPath[1] &&
|
||
row.getAttribute('data-subsubgroup')
|
||
) {
|
||
const subsubgroup = row.getAttribute('data-subsubgroup');
|
||
if (!subsubgroups[subsubgroup]) {
|
||
subsubgroups[subsubgroup] = 0;
|
||
}
|
||
subsubgroups[subsubgroup]++;
|
||
}
|
||
});
|
||
|
||
// 渲染子子组卡片
|
||
renderSubgroupCards(subsubgroups, 3);
|
||
} else if (groupPath.length === 3) {
|
||
// 三级分组
|
||
groupRoutes = allRoutes.filter(row =>
|
||
row.getAttribute('data-group') === groupPath[0] &&
|
||
row.getAttribute('data-subgroup') === groupPath[1] &&
|
||
row.getAttribute('data-subsubgroup') === groupPath[2]
|
||
);
|
||
|
||
// 三级分组没有子组,隐藏子组视图
|
||
document.getElementById('subgroups-view').classList.add('hidden');
|
||
}
|
||
|
||
// 填充主组路由表格
|
||
mainGroupRoutesBody.innerHTML = '';
|
||
if (groupRoutes && groupRoutes.length > 0) {
|
||
groupRoutes.forEach(row => {
|
||
mainGroupRoutesBody.appendChild(row.cloneNode(true));
|
||
});
|
||
} else {
|
||
mainGroupRoutesBody.innerHTML = `
|
||
<tr>
|
||
<td colspan="5" class="px-6 py-12 text-center">
|
||
<div class="flex flex-col items-center">
|
||
<i class="fa fa-folder-open text-4xl text-gray-300 mb-4"></i>
|
||
<p class="text-gray-500">此分组下没有路由</p>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}
|
||
|
||
// 显示分组详情视图
|
||
allRoutesView.classList.add('hidden');
|
||
groupsView.classList.add('hidden');
|
||
groupDetailsView.classList.remove('hidden');
|
||
|
||
// 如果有子组,显示子组视图
|
||
if (groupPath.length < 3) {
|
||
document.getElementById('subgroups-view').classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
// 渲染子组卡片
|
||
function renderSubgroupCards(groups, level) {
|
||
subgroupsContainer.innerHTML = '';
|
||
|
||
if (Object.keys(groups).length > 0) {
|
||
Object.keys(groups).forEach(groupName => {
|
||
const groupData = groups[groupName];
|
||
let routeCount, hasSubgroups;
|
||
|
||
if (level === 2) {
|
||
routeCount = groupData.routes;
|
||
hasSubgroups = groupData.subsubgroups.size > 0;
|
||
routeCount += Array.from(groupData.subsubgroups).reduce((total, subsubgroup) => {
|
||
return total + Array.from(routesTableBody.querySelectorAll('tr')).filter(row =>
|
||
row.getAttribute('data-group') === currentGroupPath[0] &&
|
||
row.getAttribute('data-subgroup') === groupName &&
|
||
row.getAttribute('data-subsubgroup') === subsubgroup
|
||
).length;
|
||
}, 0);
|
||
} else {
|
||
routeCount = groupData;
|
||
hasSubgroups = false;
|
||
}
|
||
|
||
// 创建子组卡片
|
||
const card = document.createElement('div');
|
||
card.className = 'group-card bg-white rounded-lg shadow-md overflow-hidden border border-gray-100 transition-all duration-300 hover:shadow-lg hover:border-gray-200 cursor-pointer';
|
||
card.setAttribute('data-group', groupName);
|
||
card.setAttribute('data-level', level);
|
||
|
||
card.innerHTML = `
|
||
<div class="p-5">
|
||
<div class="flex justify-between items-start mb-3">
|
||
<h3 class="font-bold text-lg text-gray-800">${groupName}</h3>
|
||
<span class="bg-primary/10 text-primary px-2 py-1 rounded-full text-xs font-medium">
|
||
${routeCount} 条路由
|
||
</span>
|
||
</div>
|
||
<div class="flex flex-wrap gap-2 mb-3">
|
||
${getMethodBadges(currentGroupPath, groupName, level)}
|
||
</div>
|
||
<div class="text-sm text-gray-500 line-clamp-2">
|
||
${getRoutePreviews(currentGroupPath, groupName, level)}
|
||
</div>
|
||
</div>
|
||
<div class="bg-gray-50 px-5 py-3 border-t border-gray-100 flex justify-between items-center">
|
||
<span class="text-xs text-gray-500">点击查看详情</span>
|
||
<i class="fa fa-chevron-right text-gray-400"></i>
|
||
</div>
|
||
`;
|
||
|
||
subgroupsContainer.appendChild(card);
|
||
});
|
||
} else {
|
||
subgroupsContainer.innerHTML = `
|
||
<div class="col-span-full bg-white rounded-lg shadow-md p-6 text-center">
|
||
<i class="fa fa-folder-open text-3xl text-gray-300 mb-3"></i>
|
||
<p class="text-gray-500">此分组下没有子分组</p>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// 获取HTTP方法徽章
|
||
function getMethodBadges(groupPath, subgroupName, level) {
|
||
const allRoutes = Array.from(routesTableBody.querySelectorAll('tr'));
|
||
let filteredRoutes;
|
||
|
||
if (level === 2) {
|
||
filteredRoutes = allRoutes.filter(row =>
|
||
row.getAttribute('data-group') === groupPath[0] &&
|
||
row.getAttribute('data-subgroup') === subgroupName
|
||
);
|
||
} else {
|
||
filteredRoutes = allRoutes.filter(row =>
|
||
row.getAttribute('data-group') === groupPath[0] &&
|
||
row.getAttribute('data-subgroup') === groupPath[1] &&
|
||
row.getAttribute('data-subsubgroup') === subgroupName
|
||
);
|
||
}
|
||
|
||
const methodCounts = {};
|
||
filteredRoutes.forEach(row => {
|
||
const methodSpan = row.querySelector('td:first-child span');
|
||
const method = methodSpan.textContent.trim();
|
||
|
||
if (!methodCounts[method]) {
|
||
methodCounts[method] = 0;
|
||
}
|
||
methodCounts[method]++;
|
||
});
|
||
|
||
return Object.keys(methodCounts).map(method => {
|
||
let bgClass = 'bg-gray-100 text-gray-800';
|
||
if (method.includes('GET')) bgClass = 'bg-green-100 text-green-800';
|
||
if (method.includes('POST')) bgClass = 'bg-blue-100 text-blue-800';
|
||
if (method.includes('PUT')) bgClass = 'bg-yellow-100 text-yellow-800';
|
||
if (method.includes('DELETE')) bgClass = 'bg-red-100 text-red-800';
|
||
if (method.includes('PATCH')) bgClass = 'bg-purple-100 text-purple-800';
|
||
|
||
return `<span class="px-2 py-1 text-xs rounded-full ${bgClass}">${method} (${methodCounts[method]})</span>`;
|
||
}).join('');
|
||
}
|
||
|
||
// 获取路由预览
|
||
function getRoutePreviews(groupPath, subgroupName, level) {
|
||
const allRoutes = Array.from(routesTableBody.querySelectorAll('tr'));
|
||
let filteredRoutes;
|
||
|
||
if (level === 2) {
|
||
filteredRoutes = allRoutes.filter(row =>
|
||
row.getAttribute('data-group') === groupPath[0] &&
|
||
row.getAttribute('data-subgroup') === subgroupName
|
||
);
|
||
} else {
|
||
filteredRoutes = allRoutes.filter(row =>
|
||
row.getAttribute('data-group') === groupPath[0] &&
|
||
row.getAttribute('data-subgroup') === groupPath[1] &&
|
||
row.getAttribute('data-subsubgroup') === subgroupName
|
||
);
|
||
}
|
||
|
||
const routePaths = [];
|
||
filteredRoutes.slice(0, 3).forEach(row => {
|
||
routePaths.push(row.querySelector('td:nth-child(2) div').textContent);
|
||
});
|
||
|
||
return routePaths.length > 0 ? routePaths.join(', ') + (filteredRoutes.length > 3 ? '...' : '') : '无路由';
|
||
}
|
||
|
||
// 路由搜索功能
|
||
routeSearch.addEventListener('input', function() {
|
||
const searchTerm = this.value.toLowerCase().trim();
|
||
const allRoutes = Array.from(routesTableBody.querySelectorAll('tr'));
|
||
|
||
allRoutes.forEach(row => {
|
||
const uri = row.querySelector('td:nth-child(2) div').textContent.toLowerCase();
|
||
const controller = row.querySelector('td:nth-child(4) div')?.textContent.toLowerCase() || '';
|
||
const name = row.querySelector('td:nth-child(3) div').textContent.toLowerCase();
|
||
|
||
if (uri.includes(searchTerm) || controller.includes(searchTerm) || name.includes(searchTerm)) {
|
||
row.classList.remove('hidden');
|
||
} else {
|
||
row.classList.add('hidden');
|
||
}
|
||
});
|
||
});
|
||
|
||
// 为路由表格添加加载动画
|
||
const observer = new MutationObserver(function(mutations) {
|
||
mutations.forEach(function(mutation) {
|
||
if (mutation.type === 'childList' && mutation.target.id === 'routes-table-body') {
|
||
if (mutation.addedNodes.length > 0) {
|
||
// 有新路由添加时,为每行添加淡入动画
|
||
Array.from(mutation.addedNodes).forEach((node, index) => {
|
||
if (node.nodeType === 1) { // 确保是元素节点
|
||
node.style.opacity = '0';
|
||
node.style.transform = 'translateY(10px)';
|
||
node.style.transition = 'opacity 300ms ease, transform 300ms ease';
|
||
|
||
setTimeout(() => {
|
||
node.style.opacity = '1';
|
||
node.style.transform = 'translateY(0)';
|
||
}, 50 * index);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// 开始观察路由表格
|
||
observer.observe(routesTableBody, { childList: true });
|
||
|
||
// 初始化表格行动画
|
||
Array.from(routesTableBody.querySelectorAll('tr')).forEach((row, index) => {
|
||
row.style.opacity = '0';
|
||
row.style.transform = 'translateY(10px)';
|
||
row.style.transition = 'opacity 300ms ease, transform 300ms ease';
|
||
|
||
setTimeout(() => {
|
||
row.style.opacity = '1';
|
||
row.style.transform = 'translateY(0)';
|
||
}, 50 * index);
|
||
});
|
||
});
|
||
</script>
|
||
@endsection
|
||
<style>
|
||
#routes-table-body::-webkit-scrollbar {
|
||
display: none !important; /* 针对Chrome、Safari和Edge浏览器 */
|
||
}
|
||
|
||
#routes-table-body {
|
||
-ms-overflow-style: none; /* 针对IE和Edge浏览器 */
|
||
scrollbar-width: none; /* 针对Firefox浏览器 */
|
||
}
|
||
</style>
|