后端: - config.ts 使用 requireAdmin 中间件,统一响应格式 - feedback.ts 使用 requireAdmin 中间件,移除重复权限检查 - feedback.ts 添加 affectedRows 检查(反馈不存在时返回 404) - 全局错误处理中间件兜底未捕获异常 前端: - request.ts 添加 15 秒请求超时设置
129 lines
3.9 KiB
TypeScript
129 lines
3.9 KiB
TypeScript
import { Router } from 'express'
|
||
import pool from '../db/connection'
|
||
import { authMiddleware } from '../middleware/auth'
|
||
import { requireAdmin } from '../middleware/requireAdmin'
|
||
|
||
const router = Router()
|
||
|
||
/** 用户提交反馈 */
|
||
router.post('/', authMiddleware, async (req, res) => {
|
||
try {
|
||
const userId = (req as any).userId
|
||
const { type, content, contact } = req.body
|
||
|
||
if (!content || !content.trim()) {
|
||
return res.status(400).json({ code: 40001, message: '请填写反馈内容' })
|
||
}
|
||
|
||
const validTypes = ['bug', 'feature', 'ux', 'other']
|
||
const feedbackType = validTypes.includes(type) ? type : 'other'
|
||
|
||
await pool.execute(
|
||
'INSERT INTO feedbacks (user_id, type, content, contact) VALUES (?, ?, ?, ?)',
|
||
[userId, feedbackType, content.trim(), contact || '']
|
||
)
|
||
|
||
res.json({ code: 0 })
|
||
} catch (error) {
|
||
console.error('Submit feedback error:', error)
|
||
res.status(500).json({ code: 50000, message: '提交失败' })
|
||
}
|
||
})
|
||
|
||
/** 管理员获取反馈列表 */
|
||
router.get('/', requireAdmin, async (req, res) => {
|
||
try {
|
||
const page = Math.max(1, parseInt(req.query.page as string) || 1)
|
||
const pageSize = Math.min(100, Math.max(1, parseInt(req.query.pageSize as string) || 20))
|
||
const status = req.query.status as string
|
||
const offset = (page - 1) * pageSize
|
||
|
||
let where = '1=1'
|
||
const params: any[] = []
|
||
|
||
if (status && ['pending', 'processed', 'ignored'].includes(status)) {
|
||
where += ' AND f.status = ?'
|
||
params.push(status)
|
||
}
|
||
|
||
const [countResult] = await pool.execute(
|
||
`SELECT COUNT(*) as total FROM feedbacks f WHERE ${where}`,
|
||
params
|
||
)
|
||
const total = (countResult as any[])[0].total
|
||
|
||
const [rows] = await pool.execute(
|
||
`SELECT f.*, u.nickname, u.avatar_url
|
||
FROM feedbacks f
|
||
LEFT JOIN users u ON f.user_id = u.id
|
||
WHERE ${where}
|
||
ORDER BY f.created_at DESC
|
||
LIMIT ? OFFSET ?`,
|
||
[...params, pageSize, offset]
|
||
)
|
||
|
||
res.json({ code: 0, data: { list: rows, total, page, pageSize } })
|
||
} catch (error) {
|
||
console.error('Get feedback list error:', error)
|
||
res.status(500).json({ code: 50000, message: '获取失败' })
|
||
}
|
||
})
|
||
|
||
/** 管理员更新反馈状态 */
|
||
router.put('/:id/status', requireAdmin, async (req, res) => {
|
||
try {
|
||
const { id } = req.params
|
||
const { status, admin_reply } = req.body
|
||
|
||
const validStatuses = ['pending', 'processed', 'ignored']
|
||
if (!validStatuses.includes(status)) {
|
||
return res.status(400).json({ code: 40001, message: '无效的状态' })
|
||
}
|
||
|
||
// 查询反馈信息(用于发送通知)
|
||
const [feedbackRows] = await pool.execute(
|
||
'SELECT user_id, content FROM feedbacks WHERE id = ?',
|
||
[id]
|
||
)
|
||
const feedback = (feedbackRows as any[])[0]
|
||
|
||
if (!feedback) {
|
||
return res.status(404).json({ code: 40400, message: '反馈不存在' })
|
||
}
|
||
|
||
await pool.execute(
|
||
'UPDATE feedbacks SET status = ?, admin_reply = ? WHERE id = ?',
|
||
[status, admin_reply || '', id]
|
||
)
|
||
|
||
// 如果有管理员回复,发送通知给反馈提交者
|
||
if (feedback && admin_reply && admin_reply.trim()) {
|
||
const statusText: Record<string, string> = {
|
||
processed: '已处理',
|
||
ignored: '已忽略',
|
||
pending: '待处理'
|
||
}
|
||
const replyPreview = admin_reply.trim().length > 50
|
||
? admin_reply.trim().slice(0, 50) + '...'
|
||
: admin_reply.trim()
|
||
|
||
await pool.execute(
|
||
`INSERT INTO notifications (user_id, type, title, content)
|
||
VALUES (?, 'personal', ?, ?)`,
|
||
[
|
||
feedback.user_id,
|
||
`您的反馈已${statusText[status] || '更新'}`,
|
||
`反馈内容:${feedback.content.slice(0, 30)}...\n\n管理员回复:${replyPreview}`
|
||
]
|
||
)
|
||
}
|
||
|
||
res.json({ code: 0 })
|
||
} catch (error) {
|
||
console.error('Update feedback status error:', error)
|
||
res.status(500).json({ code: 50000, message: '更新失败' })
|
||
}
|
||
})
|
||
|
||
export default router
|