Files
YouduWiki/web/admin/scripts/generate-routes.js
2026-05-21 19:52:45 +08:00

250 lines
6.4 KiB
JavaScript
Raw Permalink 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.
import { readFileSync, writeFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* 按特殊性排序路由(最具体的在前)
* 规则:
* 1. 路径段数多的优先级高
* 2. 静态段优先于参数段
* 3. 必需参数优先于可选参数
*/
function sortRoutesBySpecificity(routes) {
return routes.sort((a, b) => {
const aParts = a.split('/').filter(Boolean);
const bParts = b.split('/').filter(Boolean);
// 首先按路径段数排序(段数多的在前)
if (aParts.length !== bParts.length) {
return bParts.length - aParts.length;
}
// 如果段数相同,比较每个段
for (let i = 0; i < aParts.length; i++) {
const aPart = aParts[i];
const bPart = bParts[i];
// 静态段优先于参数段
const aIsStatic = !aPart.startsWith(':');
const bIsStatic = !bPart.startsWith(':');
if (aIsStatic && !bIsStatic) return -1;
if (!aIsStatic && bIsStatic) return 1;
// 如果都是参数,必需参数优先于可选参数
if (!aIsStatic && !bIsStatic) {
const aIsOptional = aPart.endsWith('?');
const bIsOptional = bPart.endsWith('?');
if (!aIsOptional && bIsOptional) return -1;
if (aIsOptional && !bIsOptional) return 1;
}
// 如果都是静态段,按字母顺序(保持稳定性)
if (aIsStatic && bIsStatic) {
if (aPart !== bPart) {
return aPart.localeCompare(bPart);
}
}
}
return 0;
});
}
/**
* 构建完整路径
*/
function buildFullPath(path, parentPath) {
if (path === '/') {
return parentPath || '';
} else if (path.startsWith('/')) {
return path;
} else {
if (!parentPath || parentPath === '/') {
return `/${path}`;
} else {
return `${parentPath}/${path}`;
}
}
}
/**
* 规范化路径
*/
function normalizePath(path) {
if (!path || path === '/') return path;
return path.endsWith('/') ? path.slice(0, -1) : path;
}
/**
* 解析路由对象,返回路径和子路由内容
*/
function parseRouteObject(objContent, parentPath = '') {
const routes = [];
// 提取 path
const pathMatch = objContent.match(/path:\s*['"`]([^'"`]+)['"`]/);
if (!pathMatch) return routes;
const path = pathMatch[1];
const fullPath = normalizePath(buildFullPath(path, parentPath));
// 如果路径不为空且不是根路径,添加到列表
if (fullPath && fullPath !== '/') {
routes.push(fullPath);
}
// 检查是否有 children使用括号计数来正确匹配嵌套数组
const childrenIndex = objContent.indexOf('children:');
if (childrenIndex !== -1) {
// 找到 children: 后面的 [
let bracketStart = childrenIndex;
while (
bracketStart < objContent.length &&
objContent[bracketStart] !== '['
) {
bracketStart++;
}
if (bracketStart < objContent.length) {
// 使用括号计数找到匹配的 ]
let bracketCount = 1;
let bracketEnd = bracketStart + 1;
for (let i = bracketStart + 1; i < objContent.length; i++) {
if (objContent[i] === '[') bracketCount++;
if (objContent[i] === ']') {
bracketCount--;
if (bracketCount === 0) {
bracketEnd = i;
break;
}
}
}
// 提取 children 数组内容(不包括外层的 []
const childrenContent = objContent.substring(
bracketStart + 1,
bracketEnd,
);
// 分割 children 数组中的各个对象
const childObjects = [];
let depth = 0;
let start = 0;
for (let i = 0; i < childrenContent.length; i++) {
if (childrenContent[i] === '{') {
if (depth === 0) start = i;
depth++;
} else if (childrenContent[i] === '}') {
depth--;
if (depth === 0) {
const childObj = childrenContent.substring(start, i + 1);
childObjects.push(childObj);
}
}
}
// 递归处理每个子路由对象
for (const childObj of childObjects) {
const childRoutes = parseRouteObject(childObj, fullPath);
routes.push(...childRoutes);
}
}
}
return routes;
}
/**
* 解析路由配置
*/
function parseRoutes(content) {
const routes = [];
// 分割顶层路由数组中的各个对象
const topLevelObjects = [];
let depth = 0;
let start = 0;
for (let i = 0; i < content.length; i++) {
if (content[i] === '{') {
if (depth === 0) start = i;
depth++;
} else if (content[i] === '}') {
depth--;
if (depth === 0) {
const obj = content.substring(start, i + 1);
topLevelObjects.push(obj);
}
}
}
// 处理每个顶层路由对象
for (const obj of topLevelObjects) {
const objRoutes = parseRouteObject(obj);
routes.push(...objRoutes);
}
return routes;
}
/**
* 更新 index.html 中的路由列表
*/
function updateIndexHtml() {
const routerPath = resolve(__dirname, '../src/router.tsx');
const indexPath = resolve(__dirname, '../index.html');
// 读取 router.tsx 文件
const routerContent = readFileSync(routerPath, 'utf-8');
// 提取 router 数组的内容
const routerMatch = routerContent.match(
/const\s+router\s*=\s*\[([\s\S]*?)\];/,
);
if (!routerMatch) {
throw new Error('无法在 router.tsx 中找到 router 配置');
}
// 解析路由配置
const extractedRoutes = parseRoutes(routerMatch[1]);
// 去重并排序
const uniqueRoutes = [...new Set(extractedRoutes)];
const sortedRoutes = sortRoutesBySpecificity(uniqueRoutes);
// 读取 index.html
const htmlContent = readFileSync(indexPath, 'utf-8');
// 生成新的路由数组字符串
const routesString = sortedRoutes
.map(route => ` '${route}'`)
.join(',\n');
// 替换路由数组(匹配 var routes = [...] 部分)
const updatedContent = htmlContent.replace(
/var routes = \[[\s\S]*?\];/,
`var routes = [\n${routesString},\n ];`,
);
// 写回文件
writeFileSync(indexPath, updatedContent, 'utf-8');
console.log('✅ 路由列表已更新:');
sortedRoutes.forEach(route => console.log(` ${route}`));
}
// 执行更新
try {
updateIndexHtml();
} catch (error) {
console.error('❌ 更新路由列表失败:', error.message);
console.error(error.stack);
process.exit(1);
}