You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
6.3 KiB
JavaScript

import { globSync } from 'glob';
import fs from 'fs';
import path from 'path';
// 脚本从项目根目录运行,因此路径是相对于根目录的。
const projectRoot = process.cwd();
const viewsPath = path.join(projectRoot, 'src', 'views');
const routerModulesPath = path.join(projectRoot, 'src', 'router', 'modules');
/**
* 从动态路由模块文件中解析出 titleArr 变量
* @param {string} filePath - 路由模块文件的路径
* @returns {Array} - 包含标题映射的数组
*/
function parseTitleArr(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf-8');
const match = content.match(/const\s+titleArr\s*=\s*(\[[\s\S]*?\]);/);
if (match && match[1]) {
return new Function(`return ${match[1]}`)();
}
} catch (e) {
console.error(`读取或解析文件失败: ${filePath}`, e);
}
return [];
}
/**
* 从静态路由模块文件中解析出路径和标题的映射
* @param {string} filePath - 路由模块文件的路径
* @returns {Map<string, string>} - 路径名到标题的映射
*/
function parseStaticRouteTitles(filePath) {
const titleMap = new Map();
try {
const content = fs.readFileSync(filePath, 'utf-8');
const childrenMatch = content.match(/children:\s*(\[[\s\S]*?\])/);
if (!childrenMatch) return titleMap;
const routesRegex = /{\s*path:[^,]+,\s*name:[^,]+,\s*component:[^,]+,\s*meta:\s*{\s*title:\s*['|"]([\s\S]+?)['|"]/g;
let match;
while ((match = routesRegex.exec(childrenMatch[1])) !== null) {
// This is a bit of a hack, we need to get the path from the component string
const componentMatch = match[0].match(/component:\s*\(\)\s*=>\s*import\(['|"]@\/views([\s\S]+?)['|"]\)/);
if (componentMatch && componentMatch[1]) {
const routePathKey = path.basename(path.dirname(componentMatch[1]));
titleMap.set(routePathKey, match[1]);
}
}
} catch (e) {
console.error(`读取或解析静态路由文件失败: ${filePath}`, e);
}
return titleMap;
}
/**
* 根据新规则获取页面标题
* @param {string} file - 文件的绝对路径
* @param {Array|Map} titleSource - 标题数组或标题Map
* @returns {string} - 最终的页面标题
*/
function getTitle(file, titleSource) {
const parentDirName = path.basename(path.dirname(file));
const fileName = path.basename(file, '.vue');
const key = (fileName === 'index') ? parentDirName : fileName;
if (Array.isArray(titleSource)) { // 处理 titleArr
const foundTitle = titleSource.find(item => item.key === key);
if (foundTitle && foundTitle.title) {
return foundTitle.title;
}
} else if (titleSource instanceof Map) { // 处理静态路由的 titleMap
if (titleSource.has(key)) {
return titleSource.get(key);
}
}
// Fallback 逻辑
return key;
}
// --- 解析所有模块的标题信息 ---
const demoTitleArr = parseTitleArr(path.join(routerModulesPath, 'demo.ts'));
const pythonTitleArr = parseTitleArr(path.join(routerModulesPath, 'python.ts'));
const templateTitleMap = parseStaticRouteTitles(path.join(routerModulesPath, 'template.ts'));
// --- 统计计算 ---
let allNotes = [];
// 1. 统计 demo 模块
const demoFiles = globSync(path.join(viewsPath, 'demo', '**/*.vue')).filter(p => !p.includes('/components/'));
demoFiles.forEach(file => {
const stats = fs.statSync(file);
allNotes.push({
path: path.relative(viewsPath, file).replace(/\\/g, '/'),
title: getTitle(file, demoTitleArr),
category: 'demo',
date: stats.mtime.toISOString(),
});
});
// 2. 统计 python 模块
const pythonFiles = globSync(path.join(viewsPath, 'python', '**/*.vue'));
pythonFiles.forEach(file => {
const stats = fs.statSync(file);
allNotes.push({
path: path.relative(viewsPath, file).replace(/\\/g, '/'),
title: getTitle(file, pythonTitleArr),
category: 'python',
date: stats.mtime.toISOString(),
});
});
// 3. 统计 template 模块
const templateFiles = globSync(path.join(viewsPath, 'template', '**/*.vue')).filter(p => !p.includes('/components/'));
templateFiles.forEach(file => {
const stats = fs.statSync(file);
allNotes.push({
path: path.relative(viewsPath, file).replace(/\\/g, '/'),
title: getTitle(file, templateTitleMap),
category: 'template',
date: stats.mtime.toISOString(),
});
});
// 4. 统计 screen 和 utils (根据 remaining.ts)
const screenFile = path.join(viewsPath, 'screen', 'index.vue');
if (fs.existsSync(screenFile)) {
const stats = fs.statSync(screenFile);
allNotes.push({
path: 'screen/index.vue',
title: '大屏可视化', // 硬编码
category: 'screen',
date: stats.mtime.toISOString(),
});
}
const utilsFile = path.join(viewsPath, 'utils', 'htmlToImg.vue');
if (fs.existsSync(utilsFile)) {
const stats = fs.statSync(utilsFile);
allNotes.push({
path: 'utils/htmlToImg.vue',
title: 'html转图片', // 硬编码
category: 'utils',
date: stats.mtime.toISOString(),
});
}
// --- 数据整合 ---
allNotes.sort((a, b) => new Date(b.date) - new Date(a.date));
const categoryStats = allNotes.reduce((acc, note) => {
acc[note.category] = (acc[note.category] || 0) + 1;
return acc;
}, {});
const oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
const weeklyAdded = allNotes.filter(note => new Date(note.date) > oneWeekAgo).length;
const finalData = {
totalNotes: allNotes.length,
categories: Object.keys(categoryStats).length,
lastUpdated: allNotes.length > 0 ? allNotes[0].date : new Date().toISOString(),
weeklyAdded: weeklyAdded,
categoryChartData: Object.entries(categoryStats).map(([name, value]) => ({ name, value })),
recentNotes: allNotes.slice(0, 5).map(note => ({
path: note.path,
title: note.title,
category: note.category,
date: note.date.split('T')[0]
}))
};
// --- 写入 JSON 文件 ---
const outputPath = path.join(viewsPath, 'welcome', 'config.json');
fs.writeFileSync(outputPath, JSON.stringify(finalData, null, 4));
console.log(`页面统计信息已成功生成到: ${outputPath}`);
console.log("分类统计:", JSON.stringify(finalData.categoryChartData, null, 4));