Files
nl-mall-api/resources/views/route.blade.php
lq 5bd5938e5c
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
初始化项目
2025-05-19 16:54:25 +08:00

800 lines
45 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@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>应用路由信息
</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>