diff --git a/electron/db/config.js b/electron/db/config.js index 47b27e2..c74aa58 100644 --- a/electron/db/config.js +++ b/electron/db/config.js @@ -1,11 +1,7 @@ -// 删除或注释掉原来的路径相关代码 -// import path from 'path'; -// import fs from 'fs'; -// import { app } from 'electron'; - // 导入统一的路径工具函数 import { getSystemDbPath } from './path.js'; -import { openDatabase, executeWithRetry } from './utils.js'; +import { executeWithRetry } from './utils.js'; +import { getDbConnection } from './index.js'; /** * 从config表获取配置项 @@ -14,10 +10,7 @@ import { openDatabase, executeWithRetry } from './utils.js'; */ async function getConfig(key) { try { - // 使用统一的数据库路径 - const dbPath = getSystemDbPath(); - - const db = await openDatabase(dbPath); + const db = await getDbConnection(getSystemDbPath()); const result = await executeWithRetry(db, async () => { return await db.getAsync('SELECT * FROM config WHERE key = ?', [key]); @@ -30,6 +23,45 @@ async function getConfig(key) { } } +/** + * 获取所有配置项列表 + * @returns {Promise>} 配置项列表 + */ +async function getAllConfigs() { + try { + const db = await getDbConnection(getSystemDbPath()); + + const result = await executeWithRetry(db, async () => { + return await db.allAsync('SELECT * FROM config'); + }); + + return result; + } catch (error) { + console.error('获取配置项列表失败:', error); + throw error; + } +} + +/** + * 通过ID获取配置项 + * @param {number} id - 配置项ID + * @returns {Promise<{id: number, key: string, value: string, protected: number} | null>} 配置项对象,如果不存在返回null + */ +async function getConfigById(id) { + try { + const db = await getDbConnection(getSystemDbPath()); + + const result = await executeWithRetry(db, async () => { + return await db.getAsync('SELECT * FROM config WHERE id = ?', [id]); + }); + + return result; + } catch (error) { + console.error(`通过ID获取配置项${id}失败:`, error); + throw error; + } +} + /** * 更新或插入配置项 * @param {string} key - 配置项键名 @@ -38,10 +70,7 @@ async function getConfig(key) { */ async function setConfig(key, value) { try { - // 使用统一的数据库路径 - const dbPath = getSystemDbPath(); - - const db = await openDatabase(dbPath); + const db = await getDbConnection(getSystemDbPath()); // 先检查是否存在 const existing = await executeWithRetry(db, async () => { @@ -49,15 +78,19 @@ async function setConfig(key, value) { }); if (existing) { + // 检查是否受保护 + if (existing.protected === 1) { + throw new Error(`配置项${key}是受保护的,无法修改`); + } // 更新 await executeWithRetry(db, async () => { await db.runAsync('UPDATE config SET value = ? WHERE key = ?', [value, key]); console.log(`成功更新配置项: ${key}`); }); } else { - // 插入 + // 插入 (默认不保护) await executeWithRetry(db, async () => { - await db.runAsync('INSERT INTO config (key, value) VALUES (?, ?)', [key, value]); + await db.runAsync('INSERT INTO config (key, value, protected) VALUES (?, ?, 0)', [key, value]); console.log(`成功插入配置项: ${key}`); }); } @@ -67,7 +100,46 @@ async function setConfig(key, value) { } } +/** + * 删除配置项 + * @param {number} id - 配置项ID + * @returns {Promise} + */ +async function deleteConfig(id) { + try { + const db = await getDbConnection(getSystemDbPath()); + + // 先检查是否存在 + const existing = await executeWithRetry(db, async () => { + return await db.getAsync('SELECT * FROM config WHERE id = ?', [id]); + }); + + if (!existing) { + throw new Error(`配置项ID ${id} 不存在`); + } + + // 检查是否受保护 + if (existing.protected === 1) { + throw new Error(`配置项 ${existing.key} 是受保护的,无法删除`); + } + + // 删除 + await executeWithRetry(db, async () => { + await db.runAsync('DELETE FROM config WHERE id = ?', [id]); + console.log(`成功删除配置项ID: ${id}`); + }); + } catch (error) { + console.error(`删除配置项ID ${id} 失败:`, error); + throw error; + } +} + +// 导出保持不变 + export { getConfig, - setConfig + setConfig, + getAllConfigs, + getConfigById, + deleteConfig }; \ No newline at end of file diff --git a/electron/db/index.js b/electron/db/index.js index a1f2c15..f612a61 100644 --- a/electron/db/index.js +++ b/electron/db/index.js @@ -260,4 +260,5 @@ process.on('exit', closeAllConnections); export { initializeDatabase, checkDatabaseInitialized, + getDbConnection, }; \ No newline at end of file diff --git a/electron/main.js b/electron/main.js index 6f1a560..40973c6 100644 --- a/electron/main.js +++ b/electron/main.js @@ -3,15 +3,22 @@ import { app, BrowserWindow, ipcMain } from 'electron'; import path from 'path'; import { fileURLToPath } from 'url'; import { checkDatabaseInitialized, initializeDatabase } from './db/index.js'; -import { getSystemConfig } from './service/system.js'; +// 导入配置项服务 +import { + fetchAllConfigs, + fetchConfigById, + saveConfig, + removeConfig, + getSystemConfig, + updateSystemConfig, + increaseQuestionBandVersion, + initAuthIpc +} from './service/configService.js'; // 定义 __dirname 和 __filename const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -// 引入认证服务 -import { initAuthIpc } from './service/auth.service.js'; - // 确保在应用最开始处添加单实例锁检查 // 尝试获取单实例锁 const gotTheLock = app.requestSingleInstanceLock(); @@ -84,18 +91,12 @@ async function setupApp() { console.log('数据库初始化状态:', isInitialized); // 只检查状态,不自动初始化 - // if (!isInitialized) { - // console.log('数据库未初始化,开始初始化...'); - // const initResult = await initializeDatabase(); - // console.log('数据库初始化结果:', initResult ? '成功' : '失败'); - // } } catch (error) { console.error('数据库检查过程中出错:', error); } } // Setup IPC communication -// 在文件适当位置添加以下代码 function setupIpcMain() { // 数据库相关 ipcMain.handle('check-database-initialized', async () => { @@ -116,16 +117,6 @@ function setupIpcMain() { } }); - // 认证相关 - ipcMain.handle('auth-admin-login', async (event, credentials) => { - try { - return await adminLogin(credentials); - } catch (error) { - console.error('Login failed:', error); - return null; - } - }); - // 系统相关 ipcMain.handle('system-get-config', async () => { try { @@ -135,14 +126,69 @@ function setupIpcMain() { return null; } }); + + ipcMain.handle('system-update-config', async (event, config) => { + try { + return await updateSystemConfig(config); + } catch (error) { + console.error('Failed to update system config:', error); + return false; + } + }); + + ipcMain.handle('system-increase-question-band-version', async () => { + try { + return await increaseQuestionBandVersion(); + } catch (error) { + console.error('Failed to increase question band version:', error); + return false; + } + }); + // 初始化认证相关IPC - initAuthIpc(); + initAuthIpc(ipcMain); + + // 配置项管理相关IPC + ipcMain.handle('config-fetch-all', async () => { + try { + return await fetchAllConfigs(); + } catch (error) { + console.error('Failed to fetch all configs:', error); + throw error; + } + }); + + ipcMain.handle('config-fetch-by-id', async (event, id) => { + try { + return await fetchConfigById(id); + } catch (error) { + console.error(`Failed to fetch config by id ${id}:`, error); + throw error; + } + }); + + ipcMain.handle('config-save', async (event, { key, value }) => { + try { + await saveConfig(key, value); + return true; + } catch (error) { + console.error(`Failed to save config ${key}:`, error); + throw error; + } + }); + + ipcMain.handle('config-delete', async (event, id) => { + try { + await removeConfig(id); + return true; + } catch (error) { + console.error(`Failed to delete config ${id}:`, error); + throw error; + } + }); } // 确保在 app.whenReady() 中调用 setupIpcMain() -// 删除重复的app.whenReady()调用,只保留一个 -// 保留一个正确的初始化代码块 -// 保留一个app.whenReady()调用 app.whenReady().then(() => { setupApp() createWindow() diff --git a/electron/preload.js b/electron/preload.js index 4a08733..822cb5f 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -10,7 +10,15 @@ contextBridge.exposeInMainWorld('electronAPI', { adminLogin: (credentials) => ipcRenderer.invoke('admin-login', credentials), // 系统相关 - getSystemConfig: () => ipcRenderer.invoke('system-get-config') + getSystemConfig: () => ipcRenderer.invoke('system-get-config'), + updateSystemConfig: (config) => ipcRenderer.invoke('system-update-config', config), + increaseQuestionBandVersion: () => ipcRenderer.invoke('system-increase-question-band-version'), + + // 配置项管理相关API + fetchAllConfigs: () => ipcRenderer.invoke('config-fetch-all'), + fetchConfigById: (id) => ipcRenderer.invoke('config-fetch-by-id', id), + saveConfig: (key, value) => ipcRenderer.invoke('config-save', { key, value }), + deleteConfig: (id) => ipcRenderer.invoke('config-delete', id) }); // 这里可以添加预加载脚本 diff --git a/electron/service/auth.service.js b/electron/service/auth.service.js deleted file mode 100644 index a1e3516..0000000 --- a/electron/service/auth.service.js +++ /dev/null @@ -1,43 +0,0 @@ -import argon2 from 'argon2'; -import { getConfig } from '../db/config.js'; -import { ipcMain } from 'electron'; - -/** - * 管理员登录验证 - * @param {string} password - 用户输入的密码 - * @returns {Promise<{success: boolean, message: string}>} - */ -async function verifyAdminPassword(password) { - try { - const config = await getConfig('admin_password'); - if (!config || !config.value) { - return { success: false, message: '管理员密码未设置' }; - } - - const isMatch = await argon2.verify(config.value, password); - if (isMatch) { - return { success: true, message: '登录成功' }; - } else { - return { success: false, message: '密码错误' }; - } - } catch (error) { - console.error('验证管理员密码失败:', error); - return { success: false, message: '验证过程发生错误' }; - } -} - -/** - * 初始化认证相关的IPC处理程序 - */ -function initAuthIpc() { - // 管理员登录验证 - 使用正确的通道名称 - ipcMain.handle('admin-login', async (event, credentials) => { - // 从credentials对象中获取密码 - return await verifyAdminPassword(credentials.password); - }); -} - -export { - verifyAdminPassword, - initAuthIpc -}; \ No newline at end of file diff --git a/electron/service/configService.js b/electron/service/configService.js new file mode 100644 index 0000000..2446f16 --- /dev/null +++ b/electron/service/configService.js @@ -0,0 +1,162 @@ +// 导入数据库操作方法 +import { getConfig, setConfig, getAllConfigs, getConfigById, deleteConfig } from '../db/config.js'; +import argon2 from 'argon2'; + +// 原有接口保持不变 +/** + * 服务层:获取配置项 + * @param {string} key - 配置项键名 + * @returns {Promise<{key: string, value: string} | null>} + */ +export async function fetchConfig(key) { + try { + return await getConfig(key); + } catch (error) { + console.error('服务层: 获取配置项失败', error); + throw error; + } +} + +/** + * 服务层:获取所有配置项 + * @returns {Promise>} + */ +export async function fetchAllConfigs() { + try { + return await getAllConfigs(); + } catch (error) { + console.error('服务层: 获取所有配置项失败', error); + throw error; + } +} + +/** + * 服务层:通过ID获取配置项 + * @param {number} id - 配置项ID + * @returns {Promise<{id: number, key: string, value: string, protected: number} | null>} + */ +export async function fetchConfigById(id) { + try { + return await getConfigById(id); + } catch (error) { + console.error('服务层: 通过ID获取配置项失败', error); + throw error; + } +} + +/** + * 服务层:保存配置项 + * @param {string} key - 配置项键名 + * @param {string} value - 配置项值 + * @returns {Promise} + */ +export async function saveConfig(key, value) { + try { + await setConfig(key, value); + } catch (error) { + console.error('服务层: 保存配置项失败', error); + throw error; + } +} + +/** + * 服务层:删除配置项 + * @param {number} id - 配置项ID + * @returns {Promise} + */ +export async function removeConfig(id) { + try { + await deleteConfig(id); + } catch (error) { + console.error('服务层: 删除配置项失败', error); + throw error; + } +} + +// 从 system.js 整合的接口 +/** + * 获取系统配置并转为Map + * @returns {Promise<{[key: string]: string}>} + */ +export async function getSystemConfig() { + try { + const configs = await getAllConfigs(); + const configMap = {}; + configs.forEach(config => { + configMap[config.key] = config.value; + }); + return configMap; + } catch (error) { + console.error('获取系统配置失败:', error); + throw error; + } +} + +/** + * 批量更新系统配置 + * @param {{[key: string]: string}} config - 配置对象 + * @returns {Promise} + */ +export async function updateSystemConfig(config) { + try { + for (const [key, value] of Object.entries(config)) { + await setConfig(key, value); + } + return true; + } catch (error) { + console.error('更新系统配置失败:', error); + throw error; + } +} + +/** + * 增加题库版本号 + * @returns {Promise} + */ +export async function increaseQuestionBandVersion() { + try { + const currentVersion = await getConfig('question_bank_version'); + const newVersion = currentVersion ? parseInt(currentVersion.value) + 1 : 1; + await setConfig('question_bank_version', newVersion.toString()); + return true; + } catch (error) { + console.error('增加题库版本号失败:', error); + throw error; + } +} + +// 从 auth.service.js 整合的接口 +/** + * 管理员登录验证 + * @param {string} password - 用户输入的密码 + * @returns {Promise<{success: boolean, message: string}>} + */ +export async function verifyAdminPassword(password) { + try { + const config = await getConfig('admin_password'); + if (!config || !config.value) { + return { success: false, message: '管理员密码未设置' }; + } + + const isMatch = await argon2.verify(config.value, password); + if (isMatch) { + return { success: true, message: '登录成功' }; + } else { + return { success: false, message: '密码错误' }; + } + } catch (error) { + console.error('验证管理员密码失败:', error); + return { success: false, message: '验证过程发生错误' }; + } +} + +/** + * 初始化认证相关的IPC处理程序 + * @param {import('electron').IpcMain} ipcMain - IPC主进程实例 + */ +export function initAuthIpc(ipcMain) { + // 管理员登录验证 + ipcMain.handle('admin-login', async (event, credentials) => { + return await verifyAdminPassword(credentials.password); + }); +} \ No newline at end of file diff --git a/electron/service/system.js b/electron/service/system.js deleted file mode 100644 index 8b914fc..0000000 --- a/electron/service/system.js +++ /dev/null @@ -1,61 +0,0 @@ -// 将 CommonJS 导入改为 ES 模块导入 -import { getSystemDbPath } from '../db/path.js'; -import { openDatabase } from '../db/utils.js'; - -// 获取系统配置 -async function getSystemConfig() { - try { - const systemDb = await openDatabase(getSystemDbPath()); - const configs = await systemDb.allAsync('SELECT key, value FROM config'); - // 不要关闭连接,由连接池管理 - - const configMap = {}; - configs.forEach(config => { - configMap[config.key] = config.value; - }); - return configMap; - } catch (error) { - console.error('Get system config failed:', error); - throw error; - } -} - -// 更新系统配置 -async function updateSystemConfig(config) { - try { - const systemDb = await openDatabase(getSystemDbPath()); - await systemDb.runAsync('BEGIN TRANSACTION'); - - for (const [key, value] of Object.entries(config)) { - await systemDb.runAsync('UPDATE config SET value = ? WHERE key = ?', [value, key]); - } - - await systemDb.runAsync('COMMIT'); - // 不要关闭连接,由连接池管理 - return true; - } catch (error) { - await systemDb.runAsync('ROLLBACK'); - // 不要关闭连接,由连接池管理 - console.error('Update system config failed:', error); - throw error; - } -} - -// 增加题库版本号 -async function increaseQuestionBandVersion() { - try { - const systemDb = await openDatabase(getSystemDbPath()); - await systemDb.runAsync('UPDATE config SET value = value + 1 WHERE key = ?', ['question_bank_version']); - // 不要关闭连接,由连接池管理 - return true; - } catch (error) { - console.error('Increase question bank version failed:', error); - throw error; - } -} - -export { - getSystemConfig, - updateSystemConfig, - increaseQuestionBandVersion -}; \ No newline at end of file diff --git a/src/components/admin/AdminLayout.vue b/src/components/admin/AdminLayout.vue new file mode 100644 index 0000000..debbc60 --- /dev/null +++ b/src/components/admin/AdminLayout.vue @@ -0,0 +1,52 @@ + + + + + \ No newline at end of file diff --git a/src/components/admin/Sider.vue b/src/components/admin/Sider.vue new file mode 100644 index 0000000..e9bfd45 --- /dev/null +++ b/src/components/admin/Sider.vue @@ -0,0 +1,187 @@ + + + + + \ No newline at end of file diff --git a/src/components/common/Footer.vue b/src/components/common/Footer.vue index 5c52eac..8609140 100644 --- a/src/components/common/Footer.vue +++ b/src/components/common/Footer.vue @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index 335c658..9d7b2eb 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,7 +1,12 @@ import { createRouter, createWebHistory } from 'vue-router' -import WelcomeView from '../views/WelcomeView.vue' -import StudentHomeView from '../views/StudentHomeView.vue' -import AdminHomeView from '../views/AdminHomeView.vue' +import WelcomeView from '@/views/WelcomeView.vue' +import StudentHomeView from '@/views/user/StudentHomeView.vue' +import AdminHomeView from '@/views/admin/AdminHomeView.vue' +// 导入QuestionManagementView +import QuestionManagementView from '@/views/admin/QuestionManagementView.vue' +// 导入ConfigManagementView +import ConfigManagementView from '@/views/admin/ConfigManagementView.vue' + const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ @@ -15,11 +20,24 @@ const router = createRouter({ name: 'student-home', component: StudentHomeView, }, + // admin/AdminHomeView路由 { - path: '/admin-home', + path: '/admin/home', name: 'admin-home', component: AdminHomeView, }, + // admin/QuestionManagementView + { + path: '/admin/question-management', + name: 'admin-question-management', + component: QuestionManagementView, + }, + // 添加ConfigView路由 + { + path: '/admin/config-management', + name: 'admin-config-management', + component: ConfigManagementView, + }, ], }) diff --git a/src/views/AdminHomeView.vue b/src/views/AdminHomeView.vue deleted file mode 100644 index d6af1f1..0000000 --- a/src/views/AdminHomeView.vue +++ /dev/null @@ -1,187 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/views/WelcomeView.vue b/src/views/WelcomeView.vue index e5b2e8e..ca27108 100644 --- a/src/views/WelcomeView.vue +++ b/src/views/WelcomeView.vue @@ -210,7 +210,7 @@ const handleAdminLogin = async () => { if (result && result.success) { console.log('管理员登录 - 成功,跳转到管理首页'); ElMessage.success('登录成功'); - router.push('/admin-home'); + router.push('/admin/home'); } else { const errorMessage = result && result.message ? result.message : '登录失败'; console.warn('管理员登录 - 失败:', errorMessage); diff --git a/src/views/admin/AdminHomeView.vue b/src/views/admin/AdminHomeView.vue new file mode 100644 index 0000000..54218a5 --- /dev/null +++ b/src/views/admin/AdminHomeView.vue @@ -0,0 +1,50 @@ + + + + + \ No newline at end of file diff --git a/src/views/admin/ConfigManagementView.vue b/src/views/admin/ConfigManagementView.vue new file mode 100644 index 0000000..bb02283 --- /dev/null +++ b/src/views/admin/ConfigManagementView.vue @@ -0,0 +1,205 @@ + + + + + \ No newline at end of file diff --git a/src/views/admin/QuestionManagementView.vue b/src/views/admin/QuestionManagementView.vue new file mode 100644 index 0000000..275ebeb --- /dev/null +++ b/src/views/admin/QuestionManagementView.vue @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/src/views/StudentHomeView.vue b/src/views/user/StudentHomeView.vue similarity index 100% rename from src/views/StudentHomeView.vue rename to src/views/user/StudentHomeView.vue