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} - 路径名到标题的映射 */ 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));