diff --git a/.env b/.env index 6118cfa..4f2da5e 100644 --- a/.env +++ b/.env @@ -3,13 +3,14 @@ SECRET_KEY=django-insecure-0a1bx*8!97l^4z#ml#ufn_*9ut*)zlso$*k-g^h&(2=p@^51md DEBUG=True #ALLOWED_HOSTS=localhost,127.0.0.1,django-host,192.168.1.22 ALLOWED_HOSTS=* -# Database Configuration +# 切换数据源,支持sqlite/mysql +DB_ENGINE=sqlite USE_REDIS_AS_DB=True # MySQL Configuration (when USE_REDIS_AS_DB=False) DB_NAME=hertz_server DB_USER=root -DB_PASSWORD=root +DB_PASSWORD=123456 DB_HOST=localhost DB_PORT=3306 @@ -27,9 +28,9 @@ EMAIL_HOST=smtp.qq.com EMAIL_PORT=465 EMAIL_USE_SSL=True EMAIL_USE_TLS=False -EMAIL_HOST_USER=your_email@example.com -EMAIL_HOST_PASSWORD=your_email_password_or_app_key -DEFAULT_FROM_EMAIL=your_email@example.com +EMAIL_HOST_USER=your_email@qq.com +EMAIL_HOST_PASSWORD=your_email_password +DEFAULT_FROM_EMAIL=your_email@qq.com # 注册邮箱验证码开关(0=关闭,1=开启) REGISTER_EMAIL_VERIFICATION=0 @@ -48,4 +49,4 @@ HERTZ_CAPTCHA_REDIS_KEY_PREFIX=hertz_captcha: # Auth Middleware Configuration - 不需要登录验证的URL模式(支持正则表达式) # 格式:使用逗号分隔的正则表达式模式 # 示例:/api/demo 表示demo接口,/api/.* 表示/api路径下的所有 -NO_AUTH_PATTERNS=^/api/auth/login/?$,^/api/auth/register/?$,^/api/auth/email/code/?$,^/api/auth/send-email-code/?$,^/api/auth/password/reset/?$,^/api/captcha/.*$,^/api/docs/.*$,^/api/redoc/.*$,^/api/schema/.*$,^/admin/.*$,^/static/.*$,^/media/.*$,^/demo/.*$,^/websocket/.*$,^/api/system/.*$,^/yolo/.*$, \ No newline at end of file +NO_AUTH_PATTERNS=^/api/auth/login/?$,^/api/auth/register/?$,^/api/auth/email/code/?$,^/api/auth/send-email-code/?$,^/api/auth/password/reset/?$,^/api/captcha/.*$,^/api/docs/.*$,^/api/redoc/.*$,^/api/schema/.*$,^/admin/.*$,^/static/.*$,^/media/.*$,^/demo/.*$,^/websocket/.*$,^/api/system/.*$,^/yolo/.*$, diff --git a/.gitignore b/.gitignore index 9c52905..85f7b13 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +# python envs +venv/ + # Distribution / packaging .Python build/ diff --git a/data/db.sqlite3 b/data/db.sqlite3 index 045100f..d59a5ee 100644 Binary files a/data/db.sqlite3 and b/data/db.sqlite3 differ diff --git a/generate_menu.py b/generate_menu.py index 34c9776..a55e725 100644 --- a/generate_menu.py +++ b/generate_menu.py @@ -58,7 +58,7 @@ def generate_crud_menu(args): print("请重启服务器以同步菜单到数据库") -def main(): +def menu_generator_main(): parser = argparse.ArgumentParser(description='菜单生成器') subparsers = parser.add_subparsers(dest='command', help='可用命令') @@ -81,4 +81,4 @@ def main(): if __name__ == "__main__": - main() + menu_generator_main() diff --git a/hertz_server_diango_ui/.env b/hertz_server_diango_ui/.env index 407ac53..1c234cc 100644 --- a/hertz_server_diango_ui/.env +++ b/hertz_server_diango_ui/.env @@ -1,5 +1,5 @@ # API 基础地址 -VITE_API_BASE_URL=http://127.0.0.1:8000 +VITE_API_BASE_URL=http://192.168.124.23:8000 # 应用配置 VITE_APP_TITLE=Hertz Admin diff --git a/hertz_server_diango_ui/.env.development b/hertz_server_diango_ui/.env.development index 6c9bbfd..638a3b8 100644 --- a/hertz_server_diango_ui/.env.development +++ b/hertz_server_diango_ui/.env.development @@ -1 +1,2 @@ -VITE_API_BASE_URL=http://127.0.0.1:8000 +VITE_API_BASE_URL=http://localhost:8000 +VITE_TEMPLATE_SETUP_MODE=true \ No newline at end of file diff --git a/hertz_server_diango_ui/.env.production b/hertz_server_diango_ui/.env.production index 6c9bbfd..7cb2aca 100644 --- a/hertz_server_diango_ui/.env.production +++ b/hertz_server_diango_ui/.env.production @@ -1 +1,2 @@ -VITE_API_BASE_URL=http://127.0.0.1:8000 +VITE_API_BASE_URL=http://192.168.124.40:8002 +VITE_TEMPLATE_SETUP_MODE=true \ No newline at end of file diff --git a/hertz_server_diango_ui/README.md b/hertz_server_diango_ui/README.md index 366c161..7a240ea 100644 --- a/hertz_server_diango_ui/README.md +++ b/hertz_server_diango_ui/README.md @@ -17,7 +17,7 @@ - **工程化完善**:TS 强类型、模块化 API、统一请求封装、权限化菜单/路由 - **设计统一**:全局“超现代风格”主题,卡片 / 弹窗 / 按钮 / 输入 / 分页风格一致 - **业务可复用**: - - 知识库管理:分类树 + 列表搜索 + 编辑/发布 + - 文章管理:分类树 + 列表搜索 + 编辑/发布 - YOLO 模型:模型管理、模型类别管理、告警处理中心、检测历史管理 - AI 助手:多会话列表 + 消息记录 + 多布局对话界面(含错误调试信息) - 认证体系:登录/注册、验证码 @@ -246,6 +246,72 @@ npm run dev - 修改 `src/styles/variables.scss` 中的主色、背景色、圆角、阴影 - 如需大改导航栏、卡片风格,优先在全局样式里做统一,而不是每页重新写 +## 🧩 模块选择与模板模式 + +- **模块配置文件** + - 路径:`src/config/hertz_modules.ts` + - 内容: + - 使用 `HERTZ_MODULES` 统一管理“管理端 / 用户端”各功能模块 + - 每个模块包含:`key`(模块标识)、`label`(展示名称)、`group`(admin/user)、`defaultEnabled`(是否默认启用) + - 运行时通过 `isModuleEnabled` / `getEnabledModuleKeys` 控制路由和菜单是否展示对应模块。 + +- **模块选择页面(功能 DIY)** + - 页面:`src/views/ModuleSetup.vue` + - 路由:`/template/modules` + - 说明: + 1. 勾选需要启用的模块,未勾选的模块在菜单和路由中隐藏(仅运行时屏蔽,不改动源码)。 + 2. 点击“保存配置并刷新”可多次预览效果;点击“保存并跳转登录”会在保存后跳转到登录页。 + 3. 选择结果会以 `hertz_enabled_modules` 的形式保存在浏览器 Local Storage 中。 + +- **模板模式开关** + - 通过环境变量控制:`VITE_TEMPLATE_SETUP_MODE` + - 建议在开发环境 (`.env.development`) 中开启: + + ```bash + VITE_TEMPLATE_SETUP_MODE=true + ``` + + - 当模板模式开启且浏览器中 **没有** `hertz_enabled_modules` 记录时,路由守卫会在首次进入时自动重定向到 `/template/modules`,强制先完成模块选择。 + - 如果已经配置过模块,下次 `npm run dev` 将直接进入系统。如需重新进入模块选择页: + 1. 打开浏览器开发者工具 → Application → Local Storage + 2. 选择当前站点,删除键 `hertz_enabled_modules` + 3. 刷新页面即可再次进入模块选择流程。 + +## ✂️ 一键裁剪(npm run prune) + +> 适用于已经确定“哪些功能模块不再需要”的场景,用于真正瘦身前端代码体积。建议在执行前先提交一次 Git。 + +- **脚本位置与命令** + - 脚本:`scripts/prune-modules.mjs` + - 命令: + + ```bash + npm run prune + ``` + +- **推荐使用流程** + 1. 启动开发环境:`npm run dev`。 + 2. 打开 `/template/modules`,通过勾选确认“需要保留的模块”,用“保存配置并刷新”反复调试菜单/路由效果。 + 3. 确认无误后,关闭开发服务器。 + 4. 在终端执行 `npm run prune`,按照 CLI 提示: + - 选择要“裁剪掉”的模块(通常是你在模块选择页面中未勾选的模块)。 + - 选择裁剪模式: + - **模式 1:仅屏蔽** + - 修改 `admin_menu.ts` / `user_menu_ai.ts` 中对应模块的 `moduleKey`,加上 `__pruned__` 前缀 + - 注释组件映射行,使这些模块在菜单和路由中完全隐藏 + - **不删除任何 `.vue` 文件**,方便后续恢复 + - **模式 2:删除** + - 在模式 1 的基础上,额外删除对应模块的视图文件,如 `src/views/admin_page/UserManagement.vue` 等 + - 这是不可逆操作,建议先在模式 1 下验证,再使用模式 2 做最终瘦身 + +- **影响范围(前端)** + - 管理端: + - `src/router/admin_menu.ts` 中对应模块的菜单配置和组件映射 + - `src/views/admin_page/*.vue` 中不需要的页面(仅在删除模式下移除) + - 用户端: + - `src/router/user_menu_ai.ts` 中对应模块配置 + - `src/views/user_pages/*.vue` 中不需要的页面(仅在删除模式下移除) + ## 📜 NPM 脚本 ```json @@ -253,7 +319,8 @@ npm run dev "scripts": { "dev": "vite", "build": "vue-tsc -b && vite build", - "preview": "vite preview" + "preview": "vite preview", + "prune": "node scripts/prune-modules.mjs" } } ``` diff --git a/hertz_server_diango_ui/components.d.ts b/hertz_server_diango_ui/components.d.ts index bedf380..8a3f626 100644 --- a/hertz_server_diango_ui/components.d.ts +++ b/hertz_server_diango_ui/components.d.ts @@ -16,6 +16,7 @@ declare module 'vue' { AButton: typeof import('ant-design-vue/es')['Button'] ACard: typeof import('ant-design-vue/es')['Card'] ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup'] ACol: typeof import('ant-design-vue/es')['Col'] ADatePicker: typeof import('ant-design-vue/es')['DatePicker'] ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] diff --git a/hertz_server_diango_ui/package-lock.json b/hertz_server_diango_ui/package-lock.json index 277b2fb..cd5d47b 100644 --- a/hertz_server_diango_ui/package-lock.json +++ b/hertz_server_diango_ui/package-lock.json @@ -25,12 +25,10 @@ "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", "autoprefixer": "^10.4.21", - "daisyui": "^5.1.13", "eslint": "^9.36.0", "eslint-plugin-vue": "^10.4.0", "postcss": "^8.5.6", "sass-embedded": "^1.93.0", - "tailwindcss": "^4.1.13", "typescript": "~5.8.3", "typescript-eslint": "^8.44.0", "unplugin-vue-components": "^29.1.0", @@ -2663,16 +2661,6 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, - "node_modules/daisyui": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.1.13.tgz", - "integrity": "sha512-KWPF/4R+EHTJRqKZFNmSDPfAZ5xeS6YWB/2kS7Y6wGKg+atscUi2DOp6HoDD/OgGML0PJTtTpgwpTfeHVfjk7w==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/saadeghi/daisyui?sponsor=1" - } - }, "node_modules/dayjs": { "version": "1.11.18", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", @@ -5141,13 +5129,6 @@ "node": ">=16.0.0" } }, - "node_modules/tailwindcss": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", - "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", - "dev": true, - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/hertz_server_diango_ui/package.json b/hertz_server_diango_ui/package.json index 2e4d7e1..a260e42 100644 --- a/hertz_server_diango_ui/package.json +++ b/hertz_server_diango_ui/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite", "build": "vue-tsc -b && vite build", - "preview": "vite preview" + "preview": "vite preview", + "prune": "node scripts/prune-modules.mjs" }, "dependencies": { "@types/node": "^24.5.2", diff --git a/hertz_server_diango_ui/scripts/prune-modules.mjs b/hertz_server_diango_ui/scripts/prune-modules.mjs new file mode 100644 index 0000000..ca50d94 --- /dev/null +++ b/hertz_server_diango_ui/scripts/prune-modules.mjs @@ -0,0 +1,392 @@ +#!/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) +}) diff --git a/hertz_server_diango_ui/src/api/index.ts b/hertz_server_diango_ui/src/api/index.ts index 5190e44..7909944 100644 --- a/hertz_server_diango_ui/src/api/index.ts +++ b/hertz_server_diango_ui/src/api/index.ts @@ -14,3 +14,4 @@ export * from './ai' // export * from './admin' export * from './log' export * from './knowledge' +export * from './kb' diff --git a/hertz_server_diango_ui/src/api/kb.ts b/hertz_server_diango_ui/src/api/kb.ts new file mode 100644 index 0000000..ee1b721 --- /dev/null +++ b/hertz_server_diango_ui/src/api/kb.ts @@ -0,0 +1,131 @@ +import { request } from '@/utils/hertz_request' + +// 通用响应结构(与后端 HertzResponse 对齐) +export interface KbApiResponse { + success: boolean + code: number + message: string + data: T +} + +// 知识库条目 +export interface KbItem { + id: number + title: string + modality: 'text' | 'code' | 'image' | 'audio' | 'video' | string + source_type: 'text' | 'file' | 'url' | string + chunk_count?: number + created_at?: string + updated_at?: string + created_chunk_count?: number + // 允许后端扩展字段 + [key: string]: any +} + +export interface KbItemListParams { + query?: string + page?: number + page_size?: number +} + +export interface KbItemListData { + total: number + page: number + page_size: number + list: KbItem[] +} + +// 语义搜索 +export interface KbSearchParams { + q: string + k?: number +} + +// 问答(RAG) +export interface KbQaPayload { + question: string + k?: number +} + +export interface KbQaData { + answer: string + [key: string]: any +} + +// 图谱查询参数(实体 / 关系) +export interface KbGraphListParams { + query?: string + page?: number + page_size?: number + // 关系检索可选参数 + source?: number + target?: number + relation_type?: string +} + +export const kbApi = { + // 知识库条目:列表 + listItems(params?: KbItemListParams): Promise> { + return request.get('/api/kb/items/list/', { params }) + }, + + // 语义搜索 + search(params: KbSearchParams): Promise> { + return request.get('/api/kb/search/', { params }) + }, + + // 问答(RAG) + qa(payload: KbQaPayload): Promise> { + return request.post('/api/kb/qa/', payload) + }, + + // 图谱:实体列表 + listEntities(params?: KbGraphListParams): Promise> { + return request.get('/api/kb/graph/entities/', { params }) + }, + + // 图谱:关系列表 + listRelations(params?: KbGraphListParams): Promise> { + return request.get('/api/kb/graph/relations/', { params }) + }, + + // 知识库条目:创建(JSON 文本) + createItemJson(payload: { title: string; modality?: string; source_type?: string; content?: string; metadata?: any }): Promise> { + return request.post('/api/kb/items/create/', payload) + }, + + // 知识库条目:创建(文件上传) + createItemFile(formData: FormData): Promise> { + return request.post('/api/kb/items/create/', formData) + }, + + // 图谱:创建实体 + createEntity(payload: { name: string; type: string; properties?: any }): Promise> { + return request.post('/api/kb/graph/entities/', payload) + }, + + // 图谱:更新实体 + updateEntity(id: number, payload: { name?: string; type?: string; properties?: any }): Promise> { + return request.put(`/api/kb/graph/entities/${id}/`, payload) + }, + + // 图谱:删除实体 + deleteEntity(id: number): Promise> { + return request.delete(`/api/kb/graph/entities/${id}/`) + }, + + // 图谱:创建关系 + createRelation(payload: { source: number; target: number; relation_type: string; properties?: any; source_chunk?: number }): Promise> { + return request.post('/api/kb/graph/relations/', payload) + }, + + // 图谱:删除关系 + deleteRelation(id: number): Promise> { + return request.delete(`/api/kb/graph/relations/${id}/`) + }, + + // 图谱:自动抽取实体与关系 + extractGraph(payload: { text?: string; item_id?: number }): Promise> { + return request.post('/api/kb/graph/extract/', payload) + }, +} diff --git a/hertz_server_diango_ui/src/api/user.ts b/hertz_server_diango_ui/src/api/user.ts index 8d0c178..9ddf0f2 100644 --- a/hertz_server_diango_ui/src/api/user.ts +++ b/hertz_server_diango_ui/src/api/user.ts @@ -103,6 +103,12 @@ export const userApi = { return request.put('/api/auth/user/info/update/', data) }, + uploadAvatar: (file: File): Promise> => { + const formData = new FormData() + formData.append('avatar', file) + return request.upload('/api/auth/user/avatar/upload/', formData) + }, + // 分配用户角色 assignRoles: (data: AssignRolesParams): Promise> => { return request.post('/api/users/assign-roles/', data) diff --git a/hertz_server_diango_ui/src/api/yolo.ts b/hertz_server_diango_ui/src/api/yolo.ts index 3cde52b..d74139e 100644 --- a/hertz_server_diango_ui/src/api/yolo.ts +++ b/hertz_server_diango_ui/src/api/yolo.ts @@ -67,6 +67,114 @@ export interface YoloModelListResponse { } } +// 数据集管理相关类型 +export interface YoloDatasetSummary { + id: number + name: string + version?: string + root_folder_path: string + data_yaml_path: string + nc?: number + description?: string + created_at?: string +} + +export interface YoloDatasetDetail extends YoloDatasetSummary { + names?: string[] + train_images_count?: number + train_labels_count?: number + val_images_count?: number + val_labels_count?: number + test_images_count?: number + test_labels_count?: number +} + +export interface YoloDatasetSampleItem { + image: string + image_size?: number + label?: string + filename: string +} + +// YOLO 训练任务相关类型 +export type YoloTrainStatus = + | 'queued' + | 'running' + | 'canceling' + | 'completed' + | 'failed' + | 'canceled' + +export interface YoloTrainDatasetOption { + id: number + name: string + version?: string + yaml: string +} + +export interface YoloTrainVersionOption { + family: 'v8' | '11' | '12' + config_path: string + sizes: string[] +} + +export interface YoloTrainOptionsResponse { + success: boolean + code?: number + message?: string + data?: { + datasets: YoloTrainDatasetOption[] + versions: YoloTrainVersionOption[] + } +} + +export interface YoloTrainingJob { + id: number + dataset: number + dataset_name: string + model_family: 'v8' | '11' | '12' + model_size?: 'n' | 's' | 'm' | 'l' | 'x' + weight_path?: string + config_path?: string + status: YoloTrainStatus + logs_path?: string + runs_path?: string + best_model_path?: string + last_model_path?: string + progress: number + epochs: number + imgsz: number + batch: number + device: string + optimizer: 'SGD' | 'Adam' | 'AdamW' | 'RMSProp' + error_message?: string + created_at: string + started_at?: string | null + finished_at?: string | null +} + +export interface StartTrainingPayload { + dataset_id: number + model_family: 'v8' | '11' | '12' + model_size?: 'n' | 's' | 'm' | 'l' | 'x' + epochs?: number + imgsz?: number + batch?: number + device?: string + optimizer?: 'SGD' | 'Adam' | 'AdamW' | 'RMSProp' +} + +export interface YoloTrainLogsResponse { + success: boolean + code?: number + message?: string + data?: { + content: string + next_offset: number + finished: boolean + } +} + // YOLO检测API export const yoloApi = { // 执行YOLO检测 @@ -111,7 +219,8 @@ export const yoloApi = { // 获取当前启用的YOLO模型信息 async getCurrentEnabledModel(): Promise<{ success: boolean; data?: YoloModel; message?: string }> { - return request.get('/api/yolo/models/enabled/') + // 关闭全局错误提示,由调用方(如 YOLO 检测页面)自行处理“未启用模型”等业务文案 + return request.get('/api/yolo/models/enabled/', { showError: false }) }, // 获取模型详情 @@ -196,6 +305,112 @@ export const yoloApi = { return request.get('/api/yolo/stats/') }, + // 数据集管理相关接口 + // 上传数据集 + async uploadDataset(formData: FormData): Promise<{ success: boolean; data?: YoloDatasetDetail; message?: string }> { + return request.upload('/api/yolo/datasets/upload/', formData) + }, + + // 获取数据集列表 + async getDatasets(): Promise<{ success: boolean; data?: YoloDatasetSummary[]; message?: string }> { + return request.get('/api/yolo/datasets/') + }, + + // 获取数据集详情 + async getDatasetDetail(datasetId: number): Promise<{ success: boolean; data?: YoloDatasetDetail; message?: string }> { + return request.get(`/api/yolo/datasets/${datasetId}/`) + }, + + // 删除数据集 + async deleteDataset(datasetId: number): Promise<{ success: boolean; message?: string }> { + return request.post(`/api/yolo/datasets/${datasetId}/delete/`) + }, + + // 获取数据集样本 + async getDatasetSamples( + datasetId: number, + params: { split?: 'train' | 'val' | 'test'; limit?: number; offset?: number } = {} + ): Promise<{ + success: boolean + data?: { items: YoloDatasetSampleItem[]; total: number } + message?: string + }> { + return request.get(`/api/yolo/datasets/${datasetId}/samples/`, { params }) + }, + + // YOLO 训练任务相关接口 + // 获取训练选项(可用数据集与模型版本) + async getTrainOptions(): Promise { + return request.get('/api/yolo/train/options/') + }, + + // 获取训练任务列表 + async getTrainJobs(): Promise<{ + success: boolean + code?: number + message?: string + data?: YoloTrainingJob[] + }> { + return request.get('/api/yolo/train/jobs/') + }, + + // 创建并启动训练任务 + async startTrainJob(payload: StartTrainingPayload): Promise<{ + success: boolean + code?: number + message?: string + data?: YoloTrainingJob + }> { + return request.post('/api/yolo/train/jobs/start/', payload) + }, + + // 获取训练任务详情 + async getTrainJobDetail(id: number): Promise<{ + success: boolean + code?: number + message?: string + data?: YoloTrainingJob + }> { + return request.get(`/api/yolo/train/jobs/${id}/`) + }, + + // 获取训练任务日志(分页读取) + async getTrainJobLogs( + id: number, + params: { offset?: number; max?: number } = {} + ): Promise { + return request.get(`/api/yolo/train/jobs/${id}/logs/`, { params }) + }, + + // 取消训练任务 + async cancelTrainJob(id: number): Promise<{ + success: boolean + code?: number + message?: string + data?: YoloTrainingJob + }> { + return request.post(`/api/yolo/train/jobs/${id}/cancel/`) + }, + + // 下载训练结果(ZIP) + async downloadTrainJobResult(id: number): Promise<{ + success: boolean + code?: number + message?: string + data?: { url: string; size: number } + }> { + return request.get(`/api/yolo/train/jobs/${id}/download/`) + }, + + // 删除训练任务 + async deleteTrainJob(id: number): Promise<{ + success: boolean + code?: number + message?: string + }> { + return request.post(`/api/yolo/train/jobs/${id}/delete/`) + }, + // 警告等级管理相关接口 // 获取警告等级列表 async getAlertLevels(): Promise<{ success: boolean; data?: AlertLevel[]; message?: string }> { diff --git a/hertz_server_diango_ui/src/assets/vue.svg b/hertz_server_diango_ui/src/assets/vue.svg deleted file mode 100644 index 770e9d3..0000000 --- a/hertz_server_diango_ui/src/assets/vue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/hertz_server_diango_ui/src/components/HelloWorld.vue b/hertz_server_diango_ui/src/components/HelloWorld.vue deleted file mode 100644 index fca8a68..0000000 --- a/hertz_server_diango_ui/src/components/HelloWorld.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - - - \ No newline at end of file diff --git a/hertz_server_diango_ui/src/config/hertz_modules.ts b/hertz_server_diango_ui/src/config/hertz_modules.ts new file mode 100644 index 0000000..1d1ed92 --- /dev/null +++ b/hertz_server_diango_ui/src/config/hertz_modules.ts @@ -0,0 +1,85 @@ +export type HertzModuleGroup = 'admin' | 'user' + +export interface HertzModule { + key: string + label: string + group: HertzModuleGroup + description?: string + defaultEnabled: boolean +} + +export const HERTZ_MODULES: HertzModule[] = [ + { key: 'admin.user-management', label: '管理端 · 用户管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.department-management', label: '管理端 · 部门管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.menu-management', label: '管理端 · 菜单管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.role-management', label: '管理端 · 角色管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.notification-management', label: '管理端 · 通知管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.log-management', label: '管理端 · 日志管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.knowledge-base', label: '管理端 · 文章管理', group: 'admin', defaultEnabled: true }, + { key: 'admin.yolo-model', label: '管理端 · YOLO 模型相关', group: 'admin', defaultEnabled: true }, + + { key: 'user.system-monitor', label: '用户端 · 系统监控', group: 'user', defaultEnabled: true }, + { key: 'user.ai-chat', label: '用户端 · AI 助手', group: 'user', defaultEnabled: true }, + { key: 'user.yolo-detection', label: '用户端 · YOLO 检测', group: 'user', defaultEnabled: true }, + { key: 'user.live-detection', label: '用户端 · 实时检测', group: 'user', defaultEnabled: true }, + { key: 'user.detection-history', label: '用户端 · 检测历史', group: 'user', defaultEnabled: true }, + { key: 'user.alert-center', label: '用户端 · 告警中心', group: 'user', defaultEnabled: true }, + { key: 'user.notice-center', label: '用户端 · 通知中心', group: 'user', defaultEnabled: true }, + { key: 'user.knowledge-center', label: '用户端 · 文章中心', group: 'user', defaultEnabled: true }, + { key: 'user.kb-center', label: '用户端 · 知识库中心', group: 'user', defaultEnabled: true }, +] + +const LOCAL_STORAGE_KEY = 'hertz_enabled_modules' + +export function getEnabledModuleKeys(): string[] { + const fallback = HERTZ_MODULES.filter(m => m.defaultEnabled).map(m => m.key) + + if (typeof window === 'undefined') { + return fallback + } + + try { + const stored = window.localStorage.getItem(LOCAL_STORAGE_KEY) + if (!stored) return fallback + const parsed = JSON.parse(stored) + if (Array.isArray(parsed)) { + const valid = parsed.filter((k): k is string => typeof k === 'string') + // 自动合并新增的默认启用模块,避免新模块在已有选择下被永久隐藏 + const missingDefaults = HERTZ_MODULES + .filter(m => m.defaultEnabled && !valid.includes(m.key)) + .map(m => m.key) + return [...valid, ...missingDefaults] + } + return fallback + } catch { + return fallback + } +} + +export function setEnabledModuleKeys(keys: string[]): void { + if (typeof window === 'undefined') return + try { + window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(keys)) + } catch { + // ignore + } +} + +export function isModuleEnabled(moduleKey?: string, enabledKeys?: string[]): boolean { + if (!moduleKey) return true + const keys = enabledKeys ?? getEnabledModuleKeys() + return keys.indexOf(moduleKey) !== -1 +} + +export function getModulesByGroup(group: HertzModuleGroup): HertzModule[] { + return HERTZ_MODULES.filter(m => m.group === group) +} + +export function hasModuleSelection(): boolean { + if (typeof window === 'undefined') return false + try { + return window.localStorage.getItem(LOCAL_STORAGE_KEY) !== null + } catch { + return false + } +} diff --git a/hertz_server_diango_ui/src/outer_src/api/ai.ts b/hertz_server_diango_ui/src/outer_src/api/ai.ts deleted file mode 100644 index b6faa29..0000000 --- a/hertz_server_diango_ui/src/outer_src/api/ai.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { request } from '@/utils/hertz_request' - -// 通用响应类型 -export interface ApiResponse { - success: boolean - code: number - message: string - data: T -} - -// 会话与消息类型 -export interface AIChatItem { - id: number - title: string - created_at: string - updated_at: string - latest_message?: string -} - -export interface AIChatDetail { - id: number - title: string - created_at: string - updated_at: string -} - -export interface AIChatMessage { - id: number - role: 'user' | 'assistant' | 'system' - content: string - created_at: string -} - -export interface ChatListData { - total: number - page: number - page_size: number - chats: AIChatItem[] -} - -export interface ChatDetailData { - chat: AIChatDetail - messages: AIChatMessage[] -} - -export interface SendMessageData { - user_message: AIChatMessage - ai_message: AIChatMessage -} - -export const aiApi = { - listChats: (params?: { query?: string; page?: number; page_size?: number }): Promise> => - request.get('/api/ai/chats/', { params, showError: false }), - - createChat: (body?: { title?: string }): Promise> => - request.post('/api/ai/chats/create/', body || { title: '新对话' }), - - getChatDetail: (chatId: number): Promise> => - request.get(`/api/ai/chats/${chatId}/`), - - updateChat: (chatId: number, body: { title: string }): Promise> => - request.put(`/api/ai/chats/${chatId}/update/`, body), - - deleteChats: (chatIds: number[]): Promise> => - request.post('/api/ai/chats/delete/', { chat_ids: chatIds }), - - sendMessage: (chatId: number, body: { content: string }): Promise> => - request.post(`/api/ai/chats/${chatId}/send/`, body), -} \ No newline at end of file diff --git a/hertz_server_diango_ui/src/outer_src/router/user_menu_ai.ts b/hertz_server_diango_ui/src/outer_src/router/user_menu_ai.ts deleted file mode 100644 index be27394..0000000 --- a/hertz_server_diango_ui/src/outer_src/router/user_menu_ai.ts +++ /dev/null @@ -1,231 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router' -import { defineAsyncComponent } from 'vue' - -// 统一的菜单项配置接口 -export interface UserMenuConfig { - key: string - label: string - icon?: string - path: string - component: string // 组件路径,相对于 @/views/user_pages/ - children?: UserMenuConfig[] - disabled?: boolean - meta?: { - title?: string - requiresAuth?: boolean - roles?: string[] - [key: string]: any - } -} - -// 菜单项接口定义(用于前端显示) -export interface MenuItem { - key: string - label: string - icon?: string - path?: string - children?: MenuItem[] - disabled?: boolean -} - -// 统一配置 - 同时用于菜单和路由 -export const userMenuConfigs: UserMenuConfig[] = [ - { - key: 'dashboard', - label: '首页', - icon: 'DashboardOutlined', - path: '/dashboard', - component: 'index.vue', - meta: { title: '用户首页', requiresAuth: true } - }, - { - key: 'profile', - label: '个人信息', - icon: 'UserOutlined', - path: '/user/profile', - component: 'Profile.vue', - meta: { title: '个人信息', requiresAuth: true } - }, - { - key: 'documents', - label: '文档管理', - icon: 'FileTextOutlined', - path: '/user/documents', - component: 'Documents.vue', - meta: { title: '文档管理', requiresAuth: true } - }, - { - key: 'messages', - label: '消息中心', - icon: 'MessageOutlined', - path: '/user/messages', - component: 'Messages.vue', - meta: { title: '消息中心', requiresAuth: true } - }, - { - key: 'system-monitor', - label: '系统监控', - icon: 'DashboardOutlined', - path: '/user/system-monitor', - component: 'SystemMonitor.vue', - meta: { title: '系统监控', requiresAuth: true } - }, - { - key: 'ai-chat', - label: 'AI助手', - icon: 'MessageOutlined', - path: '/user/ai-chat', - component: 'AiChat.vue', - meta: { title: 'AI助手', requiresAuth: true } - }, -] - -// 显式组件映射 - 避免Vite动态导入限制 -const explicitComponentMap: Record = { - 'index.vue': defineAsyncComponent(() => import('@/views/user_pages/index.vue')), - 'Profile.vue': defineAsyncComponent(() => import('@/views/user_pages/Profile.vue')), - 'Documents.vue': defineAsyncComponent(() => import('@/views/user_pages/Documents.vue')), - 'Messages.vue': defineAsyncComponent(() => import('@/views/user_pages/Messages.vue')), - 'SystemMonitor.vue': defineAsyncComponent(() => import('@/views/user_pages/SystemMonitor.vue')), - 'AiChat.vue': defineAsyncComponent(() => import('@/views/user_pages/AiChat.vue')), -} - -// 自动生成菜单项(用于前端显示) -export const userMenuItems: MenuItem[] = userMenuConfigs.map(config => ({ - key: config.key, - label: config.label, - icon: config.icon, - path: config.path, - disabled: config.disabled, - children: config.children?.map(child => ({ - key: child.key, - label: child.label, - icon: child.icon, - path: child.path, - disabled: child.disabled - })) -})) - -// 组件映射表 - 用于解决Vite动态导入限制 -const componentMap: Record Promise> = { - 'index.vue': () => import('@/views/user_pages/index.vue'), - 'Profile.vue': () => import('@/views/user_pages/Profile.vue'), - 'Documents.vue': () => import('@/views/user_pages/Documents.vue'), - 'Messages.vue': () => import('@/views/user_pages/Messages.vue'), - 'SystemMonitor.vue': () => import('@/views/user_pages/SystemMonitor.vue'), - 'AiChat.vue': () => import('@/views/user_pages/AiChat.vue'), -} - -// 自动生成路由配置 -export const userRoutes: RouteRecordRaw[] = userMenuConfigs.map(config => { - const route: RouteRecordRaw = { - path: config.path, - name: `User${config.key.charAt(0).toUpperCase() + config.key.slice(1)}`, - component: componentMap[config.component] || (() => import('@/views/NotFound.vue')), - meta: { - title: config.meta?.title || config.label, - requiresAuth: config.meta?.requiresAuth ?? true, - ...config.meta - } - } - - if (config.children && config.children.length > 0) { - route.children = config.children.map(child => ({ - path: child.path, - name: `User${child.key.charAt(0).toUpperCase() + child.key.slice(1)}`, - component: componentMap[child.component] || (() => import('@/views/NotFound.vue')), - meta: { - title: child.meta?.title || child.label, - requiresAuth: child.meta?.requiresAuth ?? true, - ...child.meta - } - })) - } - - return route -}) - -// 根据菜单项生成路由路径 -export function getMenuPath(menuKey: string): string { - const findPath = (items: MenuItem[], key: string): string | null => { - for (const item of items) { - if (item.key === key && item.path) return item.path - if (item.children) { - const childPath = findPath(item.children, key) - if (childPath) return childPath - } - } - return null - } - return findPath(userMenuItems, menuKey) || '/dashboard' -} - -// 获取菜单的面包屑路径 -export function getMenuBreadcrumb(menuKey: string): string[] { - const findBreadcrumb = (items: MenuItem[], key: string, path: string[] = []): string[] | null => { - for (const item of items) { - const currentPath = [...path, item.label] - if (item.key === menuKey) return currentPath - if (item.children) { - const childPath = findBreadcrumb(item.children, key, currentPath) - if (childPath) return childPath - } - } - return null - } - return findBreadcrumb(userMenuItems, menuKey) || ['仪表盘'] -} - -// 自动生成组件映射(基于配置和显式映射) -export const generateComponentMap = () => { - const map: Record = {} - const processConfigs = (configs: UserMenuConfig[]) => { - configs.forEach(config => { - if (explicitComponentMap[config.component]) { - map[config.key] = explicitComponentMap[config.component] - } else { - map[config.key] = defineAsyncComponent(() => import('@/views/NotFound.vue')) - } - if (config.children) processConfigs(config.children) - }) - } - processConfigs(userMenuConfigs) - return map -} - -// 导出自动生成的组件映射 -export const userComponentMap = generateComponentMap() - -// 根据用户权限过滤菜单项 -export const getFilteredUserMenuItems = (userRoles: string[], userPermissions: string[]): MenuItem[] => { - return userMenuConfigs - .filter(config => { - if (!config.meta?.roles || config.meta.roles.length === 0) return true - return config.meta.roles.some(requiredRole => userRoles.includes(requiredRole)) - }) - .map(config => ({ - key: config.key, - label: config.label, - icon: config.icon, - path: config.path, - disabled: config.disabled, - children: config.children?.filter(child => { - if (!child.meta?.roles || child.meta.roles.length === 0) return true - return child.meta.roles.some(requiredRole => userRoles.includes(requiredRole)) - }).map(child => ({ - key: child.key, - label: child.label, - icon: child.icon, - path: child.path, - disabled: child.disabled - })) - })) -} - -// 检查用户是否有访问特定菜单的权限 -export const hasUserMenuPermission = (menuKey: string, userRoles: string[]): boolean => { - const menuConfig = userMenuConfigs.find(config => config.key === menuKey) - if (!menuConfig) return false - if (!menuConfig.meta?.roles || menuConfig.meta.roles.length === 0) return true - return menuConfig.meta.roles.some(requiredRole => userRoles.includes(requiredRole)) -} \ No newline at end of file diff --git a/hertz_server_diango_ui/src/outer_src/views/user_pages/AiChat.vue b/hertz_server_diango_ui/src/outer_src/views/user_pages/AiChat.vue deleted file mode 100644 index 6ef6299..0000000 --- a/hertz_server_diango_ui/src/outer_src/views/user_pages/AiChat.vue +++ /dev/null @@ -1,261 +0,0 @@ - - - - - \ No newline at end of file diff --git a/hertz_server_diango_ui/src/public/img/logo-蓝.png b/hertz_server_diango_ui/src/public/img/logo-蓝.png deleted file mode 100644 index 2040f6c..0000000 Binary files a/hertz_server_diango_ui/src/public/img/logo-蓝.png and /dev/null differ diff --git a/hertz_server_diango_ui/src/router/admin_menu.ts b/hertz_server_diango_ui/src/router/admin_menu.ts index cca8462..e9235aa 100644 --- a/hertz_server_diango_ui/src/router/admin_menu.ts +++ b/hertz_server_diango_ui/src/router/admin_menu.ts @@ -1,4 +1,5 @@ import type { RouteRecordRaw } from "vue-router"; +import { getEnabledModuleKeys, isModuleEnabled } from "@/config/hertz_modules"; // 角色权限枚举 export enum UserRole { @@ -19,6 +20,7 @@ export interface AdminMenuItem { roles?: UserRole[]; // 允许访问的角色,不设置则使用默认管理员角色 permission?: string; // 所需权限标识符 children?: AdminMenuItem[]; // 子菜单 + moduleKey?: string; } // 🎯 统一配置中心 - 只需要在这里修改菜单配置 @@ -38,6 +40,7 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/user-management", component: "UserManagement.vue", permission: "system:user:list", // 需要用户列表权限 + moduleKey: "admin.user-management", }, { key: "department-management", @@ -45,7 +48,8 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ icon: "SettingOutlined", path: "/admin/department-management", component: "DepartmentManagement.vue", - permission: "system:dept:list", // 需要部门列表权限 + permission: "system:dept:list", // 需要部门列表权限 + moduleKey: "admin.department-management", }, { key: "menu-management", @@ -54,6 +58,7 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/menu-management", component: "MenuManagement.vue", permission: "system:menu:list", // 需要菜单列表权限 + moduleKey: "admin.menu-management", }, { key: "teacher", @@ -62,6 +67,7 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/teacher", component: "Role.vue", permission: "system:role:list", // 需要角色列表权限 + moduleKey: "admin.role-management", }, { key: "notification-management", @@ -70,6 +76,7 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/notification-management", component: "NotificationManagement.vue", permission: "studio:notice:list", // 需要通知列表权限 + moduleKey: "admin.notification-management", }, { key: "log-management", @@ -78,15 +85,17 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/log-management", component: "LogManagement.vue", permission: "log.view_operationlog", // 查看操作日志权限 + moduleKey: "admin.log-management", }, { key: "knowledge-base", - title: "知识库管理", + title: "文章管理", icon: "DatabaseOutlined", - path: "/admin/knowledge-base", - component: "KnowledgeBaseManagement.vue", + path: "/admin/article-management", + component: "ArticleManagement.vue", // 菜单访问权限:需要具备文章列表权限 permission: "system:knowledge:article:list", + moduleKey: "admin.knowledge-base", }, { key: "yolo-model", @@ -95,6 +104,7 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ path: "/admin/yolo-model", component: "ModelManagement.vue", // 默认显示模型管理页面 // 父菜单不设置权限,由子菜单的权限决定是否显示 + moduleKey: "admin.yolo-model", children: [ { key: "model-management", @@ -104,6 +114,20 @@ export const ADMIN_MENU_CONFIG: AdminMenuItem[] = [ component: "ModelManagement.vue", permission: "system:yolo:model:list", }, + { + key: "dataset-management", + title: "数据集管理", + icon: "DatabaseOutlined", + path: "/admin/dataset-management", + component: "DatasetManagement.vue", + }, + { + key: "yolo-train-management", + title: "YOLO训练", + icon: "HistoryOutlined", + path: "/admin/yolo-train", + component: "YoloTrainManagement.vue", + }, { key: "alert-level-management", title: "模型类别管理", @@ -144,8 +168,10 @@ const COMPONENT_MAP: { [key: string]: () => Promise } = { 'MenuManagement.vue': () => import("@/views/admin_page/MenuManagement.vue"), 'NotificationManagement.vue': () => import("@/views/admin_page/NotificationManagement.vue"), 'LogManagement.vue': () => import("@/views/admin_page/LogManagement.vue"), - 'KnowledgeBaseManagement.vue': () => import("@/views/admin_page/KnowledgeBaseManagement.vue"), + 'ArticleManagement.vue': () => import("@/views/admin_page/ArticleManagement.vue"), 'ModelManagement.vue': () => import("@/views/admin_page/ModelManagement.vue"), + 'DatasetManagement.vue': () => import("@/views/admin_page/DatasetManagement.vue"), + 'YoloTrainManagement.vue': () => import("@/views/admin_page/YoloTrainManagement.vue"), 'AlertLevelManagement.vue': () => import("@/views/admin_page/AlertLevelManagement.vue"), 'AlertProcessingCenter.vue': () => import("@/views/admin_page/AlertProcessingCenter.vue"), 'DetectionHistoryManagement.vue': () => import("@/views/admin_page/DetectionHistoryManagement.vue"), @@ -154,8 +180,12 @@ const COMPONENT_MAP: { [key: string]: () => Promise } = { // 🚀 自动生成路由配置 function generateAdminRoutes(): RouteRecordRaw { const children: RouteRecordRaw[] = []; + const enabledModuleKeys = getEnabledModuleKeys(); ADMIN_MENU_CONFIG.forEach(item => { + if (!isModuleEnabled(item.moduleKey, enabledModuleKeys)) { + return; + } // 如果有子菜单,将子菜单作为独立的路由项 if (item.children && item.children.length > 0) { // 为每个子菜单创建独立的路由 @@ -334,8 +364,13 @@ export const getFilteredMenuConfig = (userRoles: string[], userPermissions: stri // 对 super_admin / system_admin 开放所有管理菜单(忽略权限字符串过滤) const isPrivilegedAdmin = userRoles.includes('super_admin') || userRoles.includes('system_admin'); - // 过滤菜单项 - 基于权限字符串检查 + const enabledModuleKeys = getEnabledModuleKeys(); + + // 过滤菜单项 - 基于模块开关和权限字符串检查 const filteredMenus = ADMIN_MENU_CONFIG.filter(menuItem => { + if (!isModuleEnabled(menuItem.moduleKey, enabledModuleKeys)) { + return false; + } console.log(`🔍 检查菜单项: ${menuItem.title} (${menuItem.key})`, { hasPermission: !!menuItem.permission, permission: menuItem.permission, diff --git a/hertz_server_diango_ui/src/router/index.ts b/hertz_server_diango_ui/src/router/index.ts index 38a307b..b9f8549 100644 --- a/hertz_server_diango_ui/src/router/index.ts +++ b/hertz_server_diango_ui/src/router/index.ts @@ -3,6 +3,7 @@ import type { RouteRecordRaw } from "vue-router"; import { useUserStore } from "@/stores/hertz_user"; import { adminMenuRoutes, UserRole } from "./admin_menu"; import { userRoutes } from "./user_menu_ai"; +import { hasModuleSelection } from "@/config/hertz_modules"; // 固定路由配置 const fixedRoutes: RouteRecordRaw[] = [ @@ -25,6 +26,15 @@ const fixedRoutes: RouteRecordRaw[] = [ requiresAuth: false, }, }, + { + path: "/template/modules", + name: "ModuleSetup", + component: () => import("@/views/ModuleSetup.vue"), + meta: { + title: "模块配置", + requiresAuth: false, + }, + }, { path: "/register", name: "Register", @@ -160,6 +170,16 @@ router.beforeEach((to, _from, next) => { console.log('📋 用户信息:', userStore.userInfo); console.log('🔄 重定向计数:', redirectCount); + // 模板模式:首次必须先完成模块选择 + const isTemplateMode = import.meta.env.VITE_TEMPLATE_SETUP_MODE === 'true'; + if (isTemplateMode && to.name !== "ModuleSetup") { + if (!hasModuleSelection()) { + console.log('🧩 模板模式开启,尚未选择模块,重定向到模块配置页'); + next({ name: "ModuleSetup", query: { redirect: to.fullPath } }); + return; + } + } + // 设置页面标题 if (to.meta.title) { document.title = `${to.meta.title} - 管理系统`; diff --git a/hertz_server_diango_ui/src/router/user_menu_ai.ts b/hertz_server_diango_ui/src/router/user_menu_ai.ts index 8ba59cf..e228d4e 100644 --- a/hertz_server_diango_ui/src/router/user_menu_ai.ts +++ b/hertz_server_diango_ui/src/router/user_menu_ai.ts @@ -1,5 +1,6 @@ import type { RouteRecordRaw } from 'vue-router' import { defineAsyncComponent } from 'vue' +import { getEnabledModuleKeys, isModuleEnabled } from '@/config/hertz_modules' export interface UserMenuConfig { key: string @@ -15,6 +16,7 @@ export interface UserMenuConfig { roles?: string[] [key: string]: any } + moduleKey?: string } export interface MenuItem { @@ -30,16 +32,23 @@ export const userMenuConfigs: UserMenuConfig[] = [ { key: 'dashboard', label: '首页', icon: 'DashboardOutlined', path: '/dashboard', component: 'index.vue', meta: { title: '用户首页', requiresAuth: true } }, { key: 'profile', label: '个人信息', icon: 'UserOutlined', path: '/user/profile', component: 'Profile.vue', meta: { title: '个人信息', requiresAuth: true, hideInMenu: true } }, // { key: 'documents', label: '文档管理', icon: 'FileTextOutlined', path: '/user/documents', component: 'Documents.vue', meta: { title: '文档管理', requiresAuth: true } }, - { key: 'system-monitor', label: '系统监控', icon: 'DashboardOutlined', path: '/user/system-monitor', component: 'SystemMonitor.vue', meta: { title: '系统监控', requiresAuth: true } }, - { key: 'ai-chat', label: 'AI助手', icon: 'MessageOutlined', path: '/user/ai-chat', component: 'AiChat.vue', meta: { title: 'AI助手', requiresAuth: true } }, - { key: 'yolo-detection', label: 'YOLO检测', icon: 'ScanOutlined', path: '/user/yolo-detection', component: 'YoloDetection.vue', meta: { title: 'YOLO检测中心', requiresAuth: true } }, - { key: 'live-detection', label: '实时检测', icon: 'VideoCameraOutlined', path: '/user/live-detection', component: 'LiveDetection.vue', meta: { title: '实时检测', requiresAuth: true } }, - { key: 'detection-history', label: '检测历史', icon: 'HistoryOutlined', path: '/user/detection-history', component: 'DetectionHistory.vue', meta: { title: '检测历史记录', requiresAuth: true } }, - { key: 'alert-center', label: '告警中心', icon: 'ExclamationCircleOutlined', path: '/user/alert-center', component: 'AlertCenter.vue', meta: { title: '告警中心', requiresAuth: true } }, - { key: 'notice-center', label: '通知中心', icon: 'BellOutlined', path: '/user/notice', component: 'NoticeCenter.vue', meta: { title: '通知中心', requiresAuth: true } }, - { key: 'knowledge-center', label: '知识库中心', icon: 'DatabaseOutlined', path: '/user/knowledge', component: 'KnowledgeCenter.vue', meta: { title: '知识库中心', requiresAuth: true } }, + { key: 'system-monitor', label: '系统监控', icon: 'DashboardOutlined', path: '/user/system-monitor', component: 'SystemMonitor.vue', meta: { title: '系统监控', requiresAuth: true }, moduleKey: 'user.system-monitor' }, + { key: 'ai-chat', label: 'AI助手', icon: 'MessageOutlined', path: '/user/ai-chat', component: 'AiChat.vue', meta: { title: 'AI助手', requiresAuth: true }, moduleKey: 'user.ai-chat' }, + { key: 'yolo-detection', label: 'YOLO检测', icon: 'ScanOutlined', path: '/user/yolo-detection', component: 'YoloDetection.vue', meta: { title: 'YOLO检测中心', requiresAuth: true }, moduleKey: 'user.yolo-detection' }, + { key: 'live-detection', label: '实时检测', icon: 'VideoCameraOutlined', path: '/user/live-detection', component: 'LiveDetection.vue', meta: { title: '实时检测', requiresAuth: true }, moduleKey: 'user.live-detection' }, + { key: 'detection-history', label: '检测历史', icon: 'HistoryOutlined', path: '/user/detection-history', component: 'DetectionHistory.vue', meta: { title: '检测历史记录', requiresAuth: true }, moduleKey: 'user.detection-history' }, + { key: 'alert-center', label: '告警中心', icon: 'ExclamationCircleOutlined', path: '/user/alert-center', component: 'AlertCenter.vue', meta: { title: '告警中心', requiresAuth: true }, moduleKey: 'user.alert-center' }, + { key: 'notice-center', label: '通知中心', icon: 'BellOutlined', path: '/user/notice', component: 'NoticeCenter.vue', meta: { title: '通知中心', requiresAuth: true }, moduleKey: 'user.notice-center' }, + { key: 'knowledge-center', label: '文章中心', icon: 'DatabaseOutlined', path: '/user/knowledge', component: 'ArticleCenter.vue', meta: { title: '文章中心', requiresAuth: true }, moduleKey: 'user.knowledge-center' }, + { key: 'kb-center', label: '知识库中心', icon: 'DatabaseOutlined', path: '/user/kb-center', component: 'KbCenter.vue', meta: { title: '知识库中心', requiresAuth: true }, moduleKey: 'user.kb-center' }, ] +const enabledModuleKeys = getEnabledModuleKeys() + +const effectiveUserMenuConfigs: UserMenuConfig[] = userMenuConfigs.filter(config => + isModuleEnabled(config.moduleKey, enabledModuleKeys) +) + const explicitComponentMap: Record = { 'index.vue': defineAsyncComponent(() => import('@/views/user_pages/index.vue')), 'Profile.vue': defineAsyncComponent(() => import('@/views/user_pages/Profile.vue')), @@ -52,10 +61,11 @@ const explicitComponentMap: Record = { 'DetectionHistory.vue': defineAsyncComponent(() => import('@/views/user_pages/DetectionHistory.vue')), 'AlertCenter.vue': defineAsyncComponent(() => import('@/views/user_pages/AlertCenter.vue')), 'NoticeCenter.vue': defineAsyncComponent(() => import('@/views/user_pages/NoticeCenter.vue')), - 'KnowledgeCenter.vue': defineAsyncComponent(() => import('@/views/user_pages/KnowledgeCenter.vue')), + 'ArticleCenter.vue': defineAsyncComponent(() => import('@/views/user_pages/ArticleCenter.vue')), + 'KbCenter.vue': defineAsyncComponent(() => import('@/views/user_pages/KbCenter.vue')), } -export const userMenuItems: MenuItem[] = userMenuConfigs.map(config => ({ +export const userMenuItems: MenuItem[] = effectiveUserMenuConfigs.map(config => ({ key: config.key, label: config.label, icon: config.icon, @@ -76,10 +86,11 @@ const componentMap: Record Promise> = { 'DetectionHistory.vue': () => import('@/views/user_pages/DetectionHistory.vue'), 'AlertCenter.vue': () => import('@/views/user_pages/AlertCenter.vue'), 'NoticeCenter.vue': () => import('@/views/user_pages/NoticeCenter.vue'), - 'KnowledgeCenter.vue': () => import('@/views/user_pages/KnowledgeCenter.vue'), + 'ArticleCenter.vue': () => import('@/views/user_pages/ArticleCenter.vue'), + 'KbCenter.vue': () => import('@/views/user_pages/KbCenter.vue'), } -const baseRoutes: RouteRecordRaw[] = userMenuConfigs.map(config => { +const baseRoutes: RouteRecordRaw[] = effectiveUserMenuConfigs.map(config => { const route: RouteRecordRaw = { path: config.path, name: `User${config.key.charAt(0).toUpperCase() + config.key.slice(1)}`, @@ -101,7 +112,7 @@ const baseRoutes: RouteRecordRaw[] = userMenuConfigs.map(config => { const knowledgeDetailRoute: RouteRecordRaw = { path: '/user/knowledge/:id', name: 'UserKnowledgeDetail', - component: () => import('@/views/user_pages/KnowledgeDetail.vue'), + component: () => import('@/views/user_pages/ArticleDetail.vue'), meta: { title: '文章详情', requiresAuth: true, hideInMenu: true } } @@ -148,14 +159,14 @@ export const generateComponentMap = () => { if (config.children) processConfigs(config.children) }) } - processConfigs(userMenuConfigs) + processConfigs(effectiveUserMenuConfigs) return map } export const userComponentMap = generateComponentMap() export const getFilteredUserMenuItems = (userRoles: string[], userPermissions: string[]): MenuItem[] => { - return userMenuConfigs + return effectiveUserMenuConfigs .filter(config => { // 隐藏菜单中不显示的项(如个人信息,只在用户下拉菜单中显示) if (config.meta?.hideInMenu) return false diff --git a/hertz_server_diango_ui/src/stores/hertz_user.ts b/hertz_server_diango_ui/src/stores/hertz_user.ts index f0cef2c..1cc8d41 100644 --- a/hertz_server_diango_ui/src/stores/hertz_user.ts +++ b/hertz_server_diango_ui/src/stores/hertz_user.ts @@ -6,6 +6,7 @@ import type { ChangePasswordParams } from '@/api/password' import { roleApi } from '@/api/role' import { initializeMenuMapping } from '@/utils/menu_mapping' import { logoutUser } from '@/api/auth' +import { hasModuleSelection } from '@/config/hertz_modules' // 用户信息接口 interface UserInfo { @@ -69,8 +70,11 @@ export const useUserStore = defineStore('user', () => { localStorage.setItem('userInfo', JSON.stringify(response.user_info)) } - // 获取用户菜单权限 - await fetchUserMenuPermissions() + // 获取用户菜单权限(模板模式首次运行时跳过) + const isTemplateMode = import.meta.env.VITE_TEMPLATE_SETUP_MODE === 'true' + if (!isTemplateMode || hasModuleSelection()) { + await fetchUserMenuPermissions() + } return response } catch (error) { @@ -142,9 +146,12 @@ export const useUserStore = defineStore('user', () => { console.log('✅ 用户状态恢复成功') console.log('👤 恢复的用户信息:', parsedUserInfo) console.log('🔐 登录状态:', isLoggedIn.value) - - // 获取用户菜单权限 - await fetchUserMenuPermissions() + + // 获取用户菜单权限(模板模式首次运行时跳过) + const isTemplateMode = import.meta.env.VITE_TEMPLATE_SETUP_MODE === 'true' + if (!isTemplateMode || hasModuleSelection()) { + await fetchUserMenuPermissions() + } } catch (error) { console.error('❌ 解析用户信息失败:', error) clearAuth() @@ -181,6 +188,14 @@ export const useUserStore = defineStore('user', () => { } try { + const adminRoleCodes = ['admin', 'system_admin', 'super_admin'] + const hasAdminRole = userInfo.value.roles.some(role => adminRoleCodes.includes(role.role_code)) + + if (!hasAdminRole) { + userMenuPermissions.value = [] + return [] + } + // 获取用户所有角色的菜单权限 const allMenuPermissions = new Set() diff --git a/hertz_server_diango_ui/src/style.css b/hertz_server_diango_ui/src/style.css deleted file mode 100644 index f691315..0000000 --- a/hertz_server_diango_ui/src/style.css +++ /dev/null @@ -1,79 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -.card { - padding: 2em; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/hertz_server_diango_ui/src/views/ModuleSetup.vue b/hertz_server_diango_ui/src/views/ModuleSetup.vue new file mode 100644 index 0000000..a540684 --- /dev/null +++ b/hertz_server_diango_ui/src/views/ModuleSetup.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/hertz_server_diango_ui/src/views/admin_page/KnowledgeBaseManagement.vue b/hertz_server_diango_ui/src/views/admin_page/ArticleManagement.vue similarity index 78% rename from hertz_server_diango_ui/src/views/admin_page/KnowledgeBaseManagement.vue rename to hertz_server_diango_ui/src/views/admin_page/ArticleManagement.vue index d44c17a..4c34ffa 100644 --- a/hertz_server_diango_ui/src/views/admin_page/KnowledgeBaseManagement.vue +++ b/hertz_server_diango_ui/src/views/admin_page/ArticleManagement.vue @@ -7,8 +7,8 @@
-

知识库管理

-

集中管理内部知识文档,支持分类、标签和搜索

+

文章管理

+

集中管理文章内容,支持分类、标签和搜索

@@ -60,9 +60,9 @@
- + - +
- +
- +
@@ -144,7 +154,13 @@ - + @@ -162,93 +178,112 @@ - - - - - - - -
- - - - 新建分类 - - - - - -
- - - - - - - - - - - - - - - - - - - - - - + -
+ + + +
+ + + + 新建分类 + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
- \ No newline at end of file + diff --git a/hertz_server_diango_ui/src/views/admin_page/Dashboard.vue b/hertz_server_diango_ui/src/views/admin_page/Dashboard.vue index 05d26a0..4a5f1a5 100644 --- a/hertz_server_diango_ui/src/views/admin_page/Dashboard.vue +++ b/hertz_server_diango_ui/src/views/admin_page/Dashboard.vue @@ -506,7 +506,7 @@ const quickActions = [ { key: 'role', label: '角色管理', icon: DatabaseOutlined, gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' }, { key: 'notification', label: '通知管理', icon: BellOutlined, gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' }, { key: 'log', label: '日志管理', icon: FileSearchOutlined, gradient: 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)' }, - { key: 'knowledge', label: '知识库管理', icon: BookOutlined, gradient: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)' }, + { key: 'knowledge', label: '文章管理', icon: BookOutlined, gradient: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)' }, { key: 'model-management', label: '模型管理', icon: RobotOutlined, gradient: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)' }, { key: 'alert-level-management', label: '类别管理', icon: WarningOutlined, gradient: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)' }, { key: 'alert-processing-center', label: '告警中心', icon: BellOutlined, gradient: 'linear-gradient(135deg, #ff6e7f 0%, #bfe9ff 100%)' }, @@ -594,8 +594,8 @@ const handleQuickAction = (action: string) => { message.success('跳转到日志管理页面') break case 'knowledge': - router.push('/admin/knowledge-base') - message.success('跳转到知识库管理页面') + router.push('/admin/article-management') + message.success('跳转到文章管理页面') break case 'model-management': router.push('/admin/model-management') diff --git a/hertz_server_diango_ui/src/views/admin_page/DatasetManagement.vue b/hertz_server_diango_ui/src/views/admin_page/DatasetManagement.vue new file mode 100644 index 0000000..1e1c1cb --- /dev/null +++ b/hertz_server_diango_ui/src/views/admin_page/DatasetManagement.vue @@ -0,0 +1,1404 @@ + + + + + diff --git a/hertz_server_diango_ui/src/views/admin_page/DepartmentManagement.vue b/hertz_server_diango_ui/src/views/admin_page/DepartmentManagement.vue index f3939ec..da2caa9 100644 --- a/hertz_server_diango_ui/src/views/admin_page/DepartmentManagement.vue +++ b/hertz_server_diango_ui/src/views/admin_page/DepartmentManagement.vue @@ -47,7 +47,6 @@ @change="handleTableChange" row-key="dept_id" size="middle" - :scroll="{ x: 1200 }" >