Files
nl-mall-api/resources/views/route.blade.php

800 lines
45 KiB
PHP
Raw Normal View History

2025-05-19 16:54:25 +08:00
@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) }}
2025-05-19 16:54:25 +08:00
</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>全部路由
2025-05-19 16:54:25 +08:00
</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>