From 8469cd6ff3c111b60d34fe80d2c859aaac69153e Mon Sep 17 00:00:00 2001 From: bao <2771008024@qq.com> Date: Fri, 19 Dec 2025 09:32:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=9C=80=E6=96=B0=E7=89=88?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hello/backend/routes/AdminNotice.js | 1 - src/hello/backend/routes/auth.js | 75 +- src/hello/backend/routes/feedback.js | 66 ++ src/hello/backend/routes/mynotice.js | 198 +--- src/hello/src/Admin.vue | 515 ++++++++ src/hello/src/App.vue | 89 +- src/hello/src/components/AdminLogin.vue | 226 ++++ src/hello/src/components/AdminNavbar.vue | 131 +++ src/hello/src/components/TheAuth.vue | 48 +- src/hello/src/router/index.js | 18 +- src/hello/src/views/AdminFeedback.vue | 1361 ++++++++++++++++++++++ 11 files changed, 2555 insertions(+), 173 deletions(-) delete mode 100644 src/hello/backend/routes/AdminNotice.js create mode 100644 src/hello/src/Admin.vue create mode 100644 src/hello/src/components/AdminLogin.vue create mode 100644 src/hello/src/components/AdminNavbar.vue create mode 100644 src/hello/src/views/AdminFeedback.vue diff --git a/src/hello/backend/routes/AdminNotice.js b/src/hello/backend/routes/AdminNotice.js deleted file mode 100644 index 2c074623..00000000 --- a/src/hello/backend/routes/AdminNotice.js +++ /dev/null @@ -1 +0,0 @@ -// 管理员获取通知,发布通知的后端接口文件 \ No newline at end of file diff --git a/src/hello/backend/routes/auth.js b/src/hello/backend/routes/auth.js index f9e1cc6b..cb320011 100644 --- a/src/hello/backend/routes/auth.js +++ b/src/hello/backend/routes/auth.js @@ -7,7 +7,78 @@ const dbModule = require('../db'); const db = dbModule.promisePool; const { authenticateToken } = require('../middleware/auth'); const verificationService = require('../services/verificationService'); -// 注册接口 + +//管理员登录接口 +router.post('/admin/login', async (req, res) => { + try { + const { adminAccount, password } = req.body; + + // 验证输入 + if (!adminAccount || !password) { + return res.status(400).json({ + success: false, + message: '请填写管理员账号和密码' + }); + } + + // 从数据库查找管理员用户(role='admin') + const [users] = await db.query( + 'SELECT * FROM users WHERE (username = ?) AND role = "admin"', + [adminAccount] + ); + + if (users.length === 0) { + return res.status(400).json({ + success: false, + message: '管理员账号不存在或权限不足' + }); + } + + const user = users[0]; + + // 验证密码 + const isValidPassword = await bcrypt.compare(password, user.password); + if (!isValidPassword) { + return res.status(400).json({ + success: false, + message: '密码错误' + }); + } + + // 生成JWT token(可以设置不同的密钥或更长的有效期) + const token = jwt.sign( + { + userId: user.user_id, + email: user.email, + role: user.role, // 添加角色信息 + isAdmin: true // 添加管理员标识 + }, + process.env.JWT_SECRET || 'your-secret-key', + { expiresIn: '7d' } // 管理员token有效期更长 + ); + + res.json({ + success: true, + message: '管理员登录成功', + user: { + user_id: user.user_id, + username: user.username, + email: user.email, + role: user.role + }, + token + }); + + } catch (error) { + console.error('管理员登录错误:', error); + res.status(500).json({ + success: false, + message: '服务器错误' + }); + } +}); + +// 用户注册接口 router.post('/register', async (req, res) => { try { const { username, email, password, verificationCode } = req.body; @@ -89,7 +160,7 @@ router.post('/register', async (req, res) => { } }); -// 登录接口 +// 用户登录接口 router.post('/login', async (req, res) => { try { const { email, password } = req.body; diff --git a/src/hello/backend/routes/feedback.js b/src/hello/backend/routes/feedback.js index f400265b..3d404a30 100644 --- a/src/hello/backend/routes/feedback.js +++ b/src/hello/backend/routes/feedback.js @@ -189,6 +189,72 @@ router.get('/:id', authenticateToken, checkAdmin, async (req, res) => { } }); +// 回复反馈 - 需要认证和管理员权限 +router.put('/:id/reply', authenticateToken, checkAdmin, async (req, res) => { + try { + const feedbackId = parseInt(req.params.id); + const { answer } = req.body; + + // 验证参数 + if (!answer || answer.trim() === '') { + return res.status(400).json({ + success: false, + message: '回复内容不能为空' + }); + } + + if (answer.length > 500) { + return res.status(400).json({ + success: false, + message: '回复内容不能超过500字' + }); + } + + // 先检查反馈是否存在 + const checkQuery = `SELECT feedback_id FROM feedback WHERE feedback_id = ?`; + const [checkRows] = await db.execute(checkQuery, [feedbackId]); + + if (checkRows.length === 0) { + return res.status(404).json({ + success: false, + message: '反馈不存在' + }); + } + + // 更新反馈回复 + const updateQuery = ` + UPDATE feedback + SET answer = ?, answer_time = CURRENT_TIMESTAMP + WHERE feedback_id = ? + `; + + await db.execute(updateQuery, [answer.trim(), feedbackId]); + + // 获取更新后的反馈信息 + const getQuery = ` + SELECT f.*, u.username + FROM feedback f + JOIN users u ON f.user_id = u.user_id + WHERE f.feedback_id = ? + `; + + const [updatedRows] = await db.execute(getQuery, [feedbackId]); + + res.json({ + success: true, + data: updatedRows[0], + message: '回复成功' + }); + + } catch (error) { + console.error('回复反馈错误:', error); + res.status(500).json({ + success: false, + message: '服务器错误: ' + error.message + }); + } +}); + // 根据ID删除反馈 - 需要认证和管理员权限 router.delete('/:id', authenticateToken, checkAdmin, async (req, res) => { try { diff --git a/src/hello/backend/routes/mynotice.js b/src/hello/backend/routes/mynotice.js index a81c0634..a788521c 100644 --- a/src/hello/backend/routes/mynotice.js +++ b/src/hello/backend/routes/mynotice.js @@ -1,129 +1,30 @@ // backend/routes/mynotice.js const express = require('express'); const router = express.Router(); - -// 临时模拟数据 -let mockNotices = [ - { - id: 1, - type: 'feedback', - title: '关于图表导出功能的建议', - content: '建议增加导出为PDF格式的功能,这样更方便分享和打印。', - feedbackType: 'suggestion', - time: new Date(Date.now() - 3600000 * 2), - isRead: false, - reply: '感谢您的建议!我们已经在开发计划中,预计下个版本会增加PDF导出功能。', - replyTime: new Date(Date.now() - 3600000 * 1) - }, - { - id: 2, - type: 'feedback', - title: '界面颜色太刺眼', - content: '数据可视化页面的背景色太亮,长时间使用眼睛容易疲劳。', - feedbackType: 'ui', - time: new Date(Date.now() - 86400000 * 2), - isRead: true, - reply: '我们已收到您的反馈,会在下个版本中增加深色模式选项。', - replyTime: new Date(Date.now() - 86400000 * 1) - }, - { - id: 3, - type: 'system', - title: '系统维护通知', - content: '为了提升系统性能,我们将于本周六凌晨2:00-4:00进行系统维护,期间服务将不可用。', - time: new Date(Date.now() - 86400000 * 3), - isRead: false - }, - { - id: 4, - type: 'system', - title: '新功能上线', - content: '图表智能推荐功能已上线,系统会根据您的数据自动推荐最合适的图表类型。', - time: new Date(Date.now() - 86400000 * 5), - isRead: true - }, - { - id: 5, - type: 'feedback', - title: '数据导入失败问题', - content: '导入超过10MB的CSV文件时系统会报错,提示内存不足。', - feedbackType: 'bug', - time: new Date(Date.now() - 86400000 * 7), - isRead: false, - reply: null - } -]; - -// 模拟用户数据 -const mockUsers = [ - { id: 1, username: '张三', email: 'zhangsan@example.com' }, - { id: 2, username: '李四', email: 'lisi@example.com' }, - { id: 3, username: '王五', email: 'wangwu@example.com' } -]; - -// 模拟notice表数据 -let mockSystemNotices = [ - { - id: 1, - title: '系统维护通知', - content: '为了提升系统性能,我们将于本周六凌晨2:00-4:00进行系统维护,期间服务将不可用。', - admin_id: 1, - is_read: false, - created_time: new Date(Date.now() - 86400000 * 3) - }, - { - id: 2, - title: '新功能上线', - content: '图表智能推荐功能已上线,系统会根据您的数据自动推荐最合适的图表类型。', - admin_id: 1, - is_read: true, - created_time: new Date(Date.now() - 86400000 * 5) - } -]; - -// 模拟feedback表数据 -let mockFeedbackList = [ - { - feedback_id: 1, - user_id: 1, - type: 'suggestion', - content: '建议增加导出为PDF格式的功能,这样更方便分享和打印。', - feedback_time: new Date(Date.now() - 3600000 * 2), - answer: '感谢您的建议!我们已经在开发计划中,预计下个版本会增加PDF导出功能。', - answer_time: new Date(Date.now() - 3600000 * 1) - }, - { - feedback_id: 2, - user_id: 1, - type: 'ui', - content: '数据可视化页面的背景色太亮,长时间使用眼睛容易疲劳。', - feedback_time: new Date(Date.now() - 86400000 * 2), - answer: '我们已收到您的反馈,会在下个版本中增加深色模式选项。', - answer_time: new Date(Date.now() - 86400000 * 1) - }, - { - feedback_id: 3, - user_id: 1, - type: 'bug', - content: '导入超过10MB的CSV文件时系统会报错,提示内存不足。', - feedback_time: new Date(Date.now() - 86400000 * 7), - answer: null, - answer_time: null - } -]; +const dbModule = require('../db'); +const db = dbModule.promisePool; // 获取用户通知列表 -router.get('/api/mynotice', (req, res) => { +router.get('/', async (req, res) => { try { - // 模拟从数据库获取数据 - // 这里应该根据当前登录用户ID获取数据 - const userId = 1; // 假设当前用户ID为1 + // 从请求中获取用户ID(假设通过认证中间件设置) + const userId = req.user?.id || 1; // 默认为1用于测试 // 1. 获取用户的反馈信息 - const userFeedback = mockFeedbackList.filter(feedback => feedback.user_id === userId); + const [userFeedback] = await db.execute( + `SELECT f.feedback_id, f.type, f.content, f.feedback_time, f.answer, f.answer_time + FROM feedback f + WHERE f.user_id = ? + ORDER BY f.feedback_time DESC`, + [userId] + ); // 2. 获取系统通知 - const systemNotices = mockSystemNotices; + const [systemNotices] = await db.execute( + `SELECT n.notice_id, n.title, n.content, n.admin_id, n.is_read, n.created_time + FROM notice n + ORDER BY n.created_time DESC` + ); // 3. 合并数据并格式化 const formattedNotices = []; @@ -137,7 +38,7 @@ router.get('/api/mynotice', (req, res) => { content: feedback.content, feedbackType: feedback.type, time: feedback.feedback_time, - isRead: false, // 这里应该从数据库读取已读状态 + isRead: feedback.answer !== null, // 有回复视为已读 reply: feedback.answer, replyTime: feedback.answer_time }; @@ -147,7 +48,7 @@ router.get('/api/mynotice', (req, res) => { // 添加系统通知 systemNotices.forEach(notice => { const formattedNotice = { - id: `system_${notice.id}`, + id: `system_${notice.notice_id}`, type: 'system', title: notice.title, content: notice.content, @@ -183,15 +84,23 @@ router.get('/api/mynotice', (req, res) => { }); // 标记单条通知为已读 -router.post('/api/mynotice/read/:id', (req, res) => { +router.post('/read/:id', async (req, res) => { try { const { id } = req.params; - // 在实际应用中,这里应该更新数据库中的已读状态 - // 更新mock数据 - const notice = mockNotices.find(n => n.id == parseInt(id) || n.id == id); - if (notice) { - notice.isRead = true; + // 判断通知类型并更新对应的数据库记录 + if (id.startsWith('feedback_')) { + // 反馈通知 - 不需要标记已读,因为反馈的已读状态由是否有回复决定 + console.log(`用户查看了反馈 ${id}`); + } else if (id.startsWith('system_')) { + // 系统通知 - 更新notice表的is_read字段 + const noticeId = id.replace('system_', ''); + + // 更新系统通知为已读 + await db.execute( + 'UPDATE notice SET is_read = TRUE WHERE notice_id = ?', + [noticeId] + ); } res.json({ @@ -210,18 +119,14 @@ router.post('/api/mynotice/read/:id', (req, res) => { }); // 标记所有通知为已读 -router.post('/api/mynotice/read-all', (req, res) => { +router.post('/read-all', async (req, res) => { try { - // 在实际应用中,这里应该批量更新数据库中的已读状态 - // 更新mock数据 - mockNotices.forEach(notice => { - notice.isRead = true; - }); + // 1. 将所有系统通知标记为已读 + await db.execute( + 'UPDATE notice SET is_read = TRUE WHERE is_read = FALSE' + ); - // 模拟更新系统通知已读状态 - mockSystemNotices.forEach(notice => { - notice.is_read = true; - }); + // 注意:反馈通知的已读状态由是否有回复决定,无法批量标记 res.json({ code: 200, @@ -239,27 +144,30 @@ router.post('/api/mynotice/read-all', (req, res) => { }); // 获取未读通知数量 -router.get('/api/mynotice/unread-count', (req, res) => { +router.get('/unread-count', async (req, res) => { try { - const userId = 1; // 假设当前用户ID为1 + const userId = req.user?.id || 1; // 默认为1用于测试 - // 获取用户的未读反馈 - const unreadFeedback = mockFeedbackList.filter(feedback => - feedback.user_id === userId && !feedback.is_read - ).length; + // 1. 获取未读反馈数量(没有回复的反馈) + const [unreadFeedback] = await db.execute( + 'SELECT COUNT(*) as count FROM feedback WHERE user_id = ? AND answer IS NULL', + [userId] + ); - // 获取未读系统通知 - const unreadSystemNotices = mockSystemNotices.filter(notice => !notice.is_read).length; + // 2. 获取未读系统通知数量 + const [unreadSystemNotices] = await db.execute( + 'SELECT COUNT(*) as count FROM notice WHERE is_read = FALSE' + ); - const totalUnread = unreadFeedback + unreadSystemNotices; + const totalUnread = parseInt(unreadFeedback[0].count) + parseInt(unreadSystemNotices[0].count); res.json({ code: 200, message: '获取未读数量成功', data: { unreadCount: totalUnread, - feedbackUnread: unreadFeedback, - systemUnread: unreadSystemNotices + feedbackUnread: parseInt(unreadFeedback[0].count), + systemUnread: parseInt(unreadSystemNotices[0].count) } }); } catch (error) { diff --git a/src/hello/src/Admin.vue b/src/hello/src/Admin.vue new file mode 100644 index 00000000..e2ef0a9a --- /dev/null +++ b/src/hello/src/Admin.vue @@ -0,0 +1,515 @@ + + + + + + + \ No newline at end of file diff --git a/src/hello/src/App.vue b/src/hello/src/App.vue index cf7a25c4..37dbe8c8 100644 --- a/src/hello/src/App.vue +++ b/src/hello/src/App.vue @@ -1,25 +1,35 @@ @@ -40,18 +56,23 @@ \ No newline at end of file diff --git a/src/hello/src/components/AdminNavbar.vue b/src/hello/src/components/AdminNavbar.vue new file mode 100644 index 00000000..8716a2b0 --- /dev/null +++ b/src/hello/src/components/AdminNavbar.vue @@ -0,0 +1,131 @@ + + + + + + \ No newline at end of file diff --git a/src/hello/src/components/TheAuth.vue b/src/hello/src/components/TheAuth.vue index 1484e11f..c190b7ec 100644 --- a/src/hello/src/components/TheAuth.vue +++ b/src/hello/src/components/TheAuth.vue @@ -1,4 +1,4 @@ - + - + + \ No newline at end of file