393 lines
13 KiB
JavaScript
393 lines
13 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
// 一键裁剪脚本:根据功能模块删除或屏蔽对应的菜单配置和页面文件
|
||
// 设计原则:
|
||
// - 先通过运行时模块开关/页面确认要保留哪些模块
|
||
// - 然后运行本脚本,选择要“裁剪掉”的模块,以及裁剪模式:
|
||
// 1) 仅屏蔽(修改 moduleKey,使其永远不会被启用,保留页面文件)
|
||
// 2) 删除(在 1 的基础上,再删除对应 .vue 页面文件)
|
||
// - 脚本只操作前端代码,不影响后端
|
||
|
||
import fs from 'fs'
|
||
import path from 'path'
|
||
import readline from 'readline'
|
||
import { fileURLToPath } from 'url'
|
||
|
||
const __filename = fileURLToPath(import.meta.url)
|
||
const __dirname = path.dirname(__filename)
|
||
const projectRoot = path.resolve(__dirname, '..')
|
||
|
||
/** 模块定义(与 src/config/hertz_modules.ts 保持一致) */
|
||
const MODULES = [
|
||
{ key: 'admin.user-management', label: '管理端 · 用户管理', group: 'admin' },
|
||
{ key: 'admin.department-management', label: '管理端 · 部门管理', group: 'admin' },
|
||
{ key: 'admin.menu-management', label: '管理端 · 菜单管理', group: 'admin' },
|
||
{ key: 'admin.role-management', label: '管理端 · 角色管理', group: 'admin' },
|
||
{ key: 'admin.notification-management', label: '管理端 · 通知管理', group: 'admin' },
|
||
{ key: 'admin.log-management', label: '管理端 · 日志管理', group: 'admin' },
|
||
{ key: 'admin.knowledge-base', label: '管理端 · 文章管理', group: 'admin' },
|
||
{ key: 'admin.yolo-model', label: '管理端 · YOLO 模型相关', group: 'admin' },
|
||
|
||
{ key: 'user.system-monitor', label: '用户端 · 系统监控', group: 'user' },
|
||
{ key: 'user.ai-chat', label: '用户端 · AI 助手', group: 'user' },
|
||
{ key: 'user.yolo-detection', label: '用户端 · YOLO 检测', group: 'user' },
|
||
{ key: 'user.live-detection', label: '用户端 · 实时检测', group: 'user' },
|
||
{ key: 'user.detection-history', label: '用户端 · 检测历史', group: 'user' },
|
||
{ key: 'user.alert-center', label: '用户端 · 告警中心', group: 'user' },
|
||
{ key: 'user.notice-center', label: '用户端 · 通知中心', group: 'user' },
|
||
{ key: 'user.knowledge-center', label: '用户端 · 知识库中心', group: 'user' },
|
||
]
|
||
|
||
/**
|
||
* 每个模块对应的裁剪配置:
|
||
* - adminModuleKey / userModuleKey: 在路由配置文件中的 moduleKey 值
|
||
* - adminComponentNames / userComponentNames: 在组件映射对象中的组件名(*.vue)
|
||
* - viewFiles: 可以安全删除的页面文件(相对项目根路径)
|
||
*/
|
||
const PRUNE_CONFIG = {
|
||
'admin.user-management': {
|
||
adminModuleKey: 'admin.user-management',
|
||
adminComponentNames: ['UserManagement.vue'],
|
||
userModuleKey: null,
|
||
userComponentNames: [],
|
||
viewFiles: ['src/views/admin_page/UserManagement.vue'],
|
||
},
|
||
'admin.department-management': {
|
||
adminModuleKey: 'admin.department-management',
|
||
adminComponentNames: ['DepartmentManagement.vue'],
|
||
userModuleKey: null,
|
||
userComponentNames: [],
|
||
viewFiles: ['src/views/admin_page/DepartmentManagement.vue'],
|
||
},
|
||
'admin.menu-management': {
|
||
adminModuleKey: 'admin.menu-management',
|
||
adminComponentNames: ['MenuManagement.vue'],
|
||
userModuleKey: null,
|
||
userComponentNames: [],
|
||
viewFiles: ['src/views/admin_page/MenuManagement.vue'],
|
||
},
|
||
'admin.role-management': {
|
||
adminModuleKey: 'admin.role-management',
|
||
adminComponentNames: ['Role.vue'],
|
||
userModuleKey: null,
|
||
userComponentNames: [],
|
||
viewFiles: ['src/views/admin_page/Role.vue'],
|
||
},
|
||
'admin.notification-management': {
|
||
adminModuleKey: 'admin.notification-management',
|
||
adminComponentNames: ['NotificationManagement.vue'],
|
||
userModuleKey: null,
|
||
userComponentNames: [],
|
||
viewFiles: ['src/views/admin_page/NotificationManagement.vue'],
|
||
},
|
||
'admin.log-management': {
|
||
adminModuleKey: 'admin.log-management',
|
||
adminComponentNames: ['LogManagement.vue'],
|
||
userModuleKey: null,
|
||
userComponentNames: [],
|
||
viewFiles: ['src/views/admin_page/LogManagement.vue'],
|
||
},
|
||
'admin.knowledge-base': {
|
||
adminModuleKey: 'admin.knowledge-base',
|
||
adminComponentNames: ['KnowledgeBaseManagement.vue'],
|
||
userModuleKey: null,
|
||
userComponentNames: [],
|
||
viewFiles: ['src/views/admin_page/KnowledgeBaseManagement.vue'],
|
||
},
|
||
'admin.yolo-model': {
|
||
adminModuleKey: 'admin.yolo-model',
|
||
adminComponentNames: [
|
||
'ModelManagement.vue',
|
||
'AlertLevelManagement.vue',
|
||
'AlertProcessingCenter.vue',
|
||
'DetectionHistoryManagement.vue',
|
||
],
|
||
userModuleKey: null,
|
||
userComponentNames: [],
|
||
viewFiles: [
|
||
'src/views/admin_page/ModelManagement.vue',
|
||
'src/views/admin_page/AlertLevelManagement.vue',
|
||
'src/views/admin_page/AlertProcessingCenter.vue',
|
||
'src/views/admin_page/DetectionHistoryManagement.vue',
|
||
],
|
||
},
|
||
'user.system-monitor': {
|
||
adminModuleKey: null,
|
||
adminComponentNames: [],
|
||
userModuleKey: 'user.system-monitor',
|
||
userComponentNames: ['SystemMonitor.vue'],
|
||
viewFiles: ['src/views/user_pages/SystemMonitor.vue'],
|
||
},
|
||
'user.ai-chat': {
|
||
adminModuleKey: null,
|
||
adminComponentNames: [],
|
||
userModuleKey: 'user.ai-chat',
|
||
userComponentNames: ['AiChat.vue'],
|
||
viewFiles: ['src/views/user_pages/AiChat.vue'],
|
||
},
|
||
'user.yolo-detection': {
|
||
adminModuleKey: null,
|
||
adminComponentNames: [],
|
||
userModuleKey: 'user.yolo-detection',
|
||
userComponentNames: ['YoloDetection.vue'],
|
||
viewFiles: ['src/views/user_pages/YoloDetection.vue'],
|
||
},
|
||
'user.live-detection': {
|
||
adminModuleKey: null,
|
||
adminComponentNames: [],
|
||
userModuleKey: 'user.live-detection',
|
||
userComponentNames: ['LiveDetection.vue'],
|
||
viewFiles: ['src/views/user_pages/LiveDetection.vue'],
|
||
},
|
||
'user.detection-history': {
|
||
adminModuleKey: null,
|
||
adminComponentNames: [],
|
||
userModuleKey: 'user.detection-history',
|
||
userComponentNames: ['DetectionHistory.vue'],
|
||
viewFiles: ['src/views/user_pages/DetectionHistory.vue'],
|
||
},
|
||
'user.alert-center': {
|
||
adminModuleKey: null,
|
||
adminComponentNames: [],
|
||
userModuleKey: 'user.alert-center',
|
||
userComponentNames: ['AlertCenter.vue'],
|
||
viewFiles: ['src/views/user_pages/AlertCenter.vue'],
|
||
},
|
||
'user.notice-center': {
|
||
adminModuleKey: null,
|
||
adminComponentNames: [],
|
||
userModuleKey: 'user.notice-center',
|
||
userComponentNames: ['NoticeCenter.vue'],
|
||
viewFiles: ['src/views/user_pages/NoticeCenter.vue'],
|
||
},
|
||
'user.knowledge-center': {
|
||
adminModuleKey: null,
|
||
adminComponentNames: [],
|
||
userModuleKey: 'user.knowledge-center',
|
||
userComponentNames: ['KnowledgeCenter.vue'],
|
||
// 注意:这里只删除 KnowledgeCenter.vue,保留 KnowledgeDetail.vue,避免复杂路由修改
|
||
viewFiles: ['src/views/user_pages/KnowledgeCenter.vue'],
|
||
},
|
||
}
|
||
|
||
const ADMIN_MENU_FILE = 'src/router/admin_menu.ts'
|
||
const USER_MENU_FILE = 'src/router/user_menu_ai.ts'
|
||
|
||
/**
|
||
* 简单的 CLI 交互封装
|
||
*/
|
||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
||
|
||
function ask(question) {
|
||
return new Promise((resolve) => {
|
||
rl.question(question, (answer) => {
|
||
resolve(answer.trim())
|
||
})
|
||
})
|
||
}
|
||
|
||
function resolvePath(relativePath) {
|
||
return path.resolve(projectRoot, relativePath)
|
||
}
|
||
|
||
function readTextFile(relativePath) {
|
||
const full = resolvePath(relativePath)
|
||
if (!fs.existsSync(full)) {
|
||
return null
|
||
}
|
||
return fs.readFileSync(full, 'utf8')
|
||
}
|
||
|
||
function writeTextFile(relativePath, content) {
|
||
const full = resolvePath(relativePath)
|
||
fs.writeFileSync(full, content, 'utf8')
|
||
}
|
||
|
||
function commentComponentLines(content, componentNames) {
|
||
if (!componentNames || componentNames.length === 0) return content
|
||
const lines = content.split('\n')
|
||
const nameSet = new Set(componentNames)
|
||
|
||
const updated = lines.map((line) => {
|
||
const trimmed = line.trim()
|
||
if (trimmed.startsWith('//')) return line
|
||
|
||
for (const name of nameSet) {
|
||
if (line.includes(`'${name}'`)) {
|
||
return `// ${line}`
|
||
}
|
||
}
|
||
return line
|
||
})
|
||
|
||
return updated.join('\n')
|
||
}
|
||
|
||
function updateModuleKey(content, originalKey) {
|
||
if (!originalKey) return content
|
||
const patterns = [
|
||
`moduleKey: '${originalKey}'`,
|
||
`moduleKey: "${originalKey}"`,
|
||
]
|
||
const pruned = `moduleKey: '__pruned__${originalKey}'`
|
||
|
||
if (content.includes(pruned)) {
|
||
return content
|
||
}
|
||
|
||
let updated = content
|
||
let found = false
|
||
|
||
for (const p of patterns) {
|
||
if (updated.includes(p)) {
|
||
updated = updated.replace(p, pruned)
|
||
found = true
|
||
}
|
||
}
|
||
|
||
if (!found) {
|
||
console.warn(`⚠️ 未在文件中找到 moduleKey: ${originalKey}`)
|
||
}
|
||
|
||
return updated
|
||
}
|
||
|
||
function collectViewFilesForModules(selectedKeys) {
|
||
const files = new Set()
|
||
for (const key of selectedKeys) {
|
||
const cfg = PRUNE_CONFIG[key]
|
||
if (!cfg) continue
|
||
for (const f of cfg.viewFiles) {
|
||
files.add(f)
|
||
}
|
||
}
|
||
return Array.from(files)
|
||
}
|
||
|
||
async function main() {
|
||
console.log('===== Hertz 模板 · 一键裁剪脚本 =====')
|
||
console.log('说明:')
|
||
console.log('1. 建议先在浏览器里通过“模板模式 + 模块选择页”确认要保留的模块')
|
||
console.log('2. 然后关闭 dev 服务器,运行本脚本选择要裁剪掉的模块')
|
||
console.log('3. 先可选择“仅屏蔽”,确认无误后,再选择“删除”彻底缩减代码体积')
|
||
console.log('')
|
||
|
||
console.log('当前可裁剪模块:')
|
||
MODULES.forEach((m, index) => {
|
||
console.log(`${index + 1}. [${m.group}] ${m.label} (${m.key})`)
|
||
})
|
||
console.log('')
|
||
|
||
const indexAnswer = await ask('请输入要“裁剪掉”的模块序号(多个用逗号分隔,例如 2,4,7),或直接回车取消:')
|
||
if (!indexAnswer) {
|
||
console.log('未选择任何模块,退出。')
|
||
rl.close()
|
||
return
|
||
}
|
||
|
||
const indexes = indexAnswer
|
||
.split(',')
|
||
.map((s) => parseInt(s.trim(), 10))
|
||
.filter((n) => !Number.isNaN(n) && n >= 1 && n <= MODULES.length)
|
||
|
||
if (indexes.length === 0) {
|
||
console.log('未解析出有效的序号,退出。')
|
||
rl.close()
|
||
return
|
||
}
|
||
|
||
const selectedModules = Array.from(new Set(indexes.map((i) => MODULES[i - 1])))
|
||
console.log('\n将要裁剪的模块:')
|
||
selectedModules.forEach((m) => {
|
||
console.log(`- [${m.group}] ${m.label} (${m.key})`)
|
||
})
|
||
|
||
console.log('\n裁剪模式:')
|
||
console.log('1) 仅屏蔽模块:')
|
||
console.log(' - 修改 router 配置中的 moduleKey 为 __pruned__...')
|
||
console.log(' - 生成的菜单和路由中将完全隐藏这些模块')
|
||
console.log(' - 不删除任何 .vue 页面文件(可随时恢复)')
|
||
console.log('2) 删除模块:')
|
||
console.log(' - 在 1 的基础上,额外删除对应的 .vue 页面文件')
|
||
console.log(' - 删除操作不可逆,请确保已经提交或备份代码\n')
|
||
|
||
const modeAnswer = await ask('请选择裁剪模式(1 = 仅屏蔽,2 = 删除):')
|
||
const mode = modeAnswer === '2' ? 'delete' : 'comment'
|
||
|
||
const viewFiles = collectViewFilesForModules(selectedModules.map((m) => m.key))
|
||
|
||
console.log('\n即将进行如下修改:')
|
||
console.log('- 修改文件: src/router/admin_menu.ts(按需)')
|
||
console.log('- 修改文件: src/router/user_menu_ai.ts(按需)')
|
||
if (mode === 'delete') {
|
||
console.log('- 删除页面文件:')
|
||
viewFiles.forEach((f) => console.log(` · ${f}`))
|
||
} else {
|
||
console.log('- 不删除任何页面文件,仅屏蔽模块')
|
||
}
|
||
|
||
const confirm = await ask('\n确认执行这些修改吗?(y/N): ')
|
||
if (confirm.toLowerCase() !== 'y') {
|
||
console.log('已取消操作。')
|
||
rl.close()
|
||
return
|
||
}
|
||
|
||
// 1) 修改 admin_menu.ts
|
||
let adminMenuContent = readTextFile(ADMIN_MENU_FILE)
|
||
if (adminMenuContent) {
|
||
const adminKeys = selectedModules.map((m) => m.key).filter((k) => PRUNE_CONFIG[k]?.adminModuleKey)
|
||
if (adminKeys.length > 0) {
|
||
for (const key of adminKeys) {
|
||
const cfg = PRUNE_CONFIG[key]
|
||
adminMenuContent = updateModuleKey(adminMenuContent, cfg.adminModuleKey)
|
||
adminMenuContent = commentComponentLines(adminMenuContent, cfg.adminComponentNames)
|
||
}
|
||
writeTextFile(ADMIN_MENU_FILE, adminMenuContent)
|
||
console.log('✅ 已更新 src/router/admin_menu.ts')
|
||
}
|
||
}
|
||
|
||
// 2) 修改 user_menu_ai.ts
|
||
let userMenuContent = readTextFile(USER_MENU_FILE)
|
||
if (userMenuContent) {
|
||
const userKeys = selectedModules.map((m) => m.key).filter((k) => PRUNE_CONFIG[k]?.userModuleKey)
|
||
if (userKeys.length > 0) {
|
||
for (const key of userKeys) {
|
||
const cfg = PRUNE_CONFIG[key]
|
||
userMenuContent = updateModuleKey(userMenuContent, cfg.userModuleKey)
|
||
userMenuContent = commentComponentLines(userMenuContent, cfg.userComponentNames)
|
||
}
|
||
writeTextFile(USER_MENU_FILE, userMenuContent)
|
||
console.log('✅ 已更新 src/router/user_menu_ai.ts')
|
||
}
|
||
}
|
||
|
||
// 3) 删除 .vue 页面文件(仅在 delete 模式下)
|
||
if (mode === 'delete') {
|
||
console.log('\n开始删除页面文件...')
|
||
for (const relative of viewFiles) {
|
||
const full = resolvePath(relative)
|
||
if (fs.existsSync(full)) {
|
||
fs.rmSync(full)
|
||
console.log(`🗑️ 已删除: ${relative}`)
|
||
} else {
|
||
console.log(`⚠️ 文件不存在,跳过: ${relative}`)
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('\n🎉 裁剪完成。建议执行以下操作检查:')
|
||
console.log('- 重新运行: npm run dev')
|
||
console.log('- 在浏览器中确认菜单和路由是否符合预期')
|
||
console.log('- 如需恢复,请使用 Git 回退或重新拷贝模板')
|
||
|
||
rl.close()
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error('执行过程中发生错误:', err)
|
||
rl.close()
|
||
process.exit(1)
|
||
})
|