feat: 添加密码修改功能并集成Tailwind CSS
refactor: 重构UI组件使用Tailwind CSS feat(router): 添加密码修改路由 feat(views): 实现密码修改页面 feat(api): 添加密码修改API端点 style: 移除旧CSS文件并配置Tailwind chore: 添加Tailwind CSS相关依赖
This commit is contained in:
+227
-14
@@ -186,41 +186,254 @@ async function startServer() {
|
||||
}
|
||||
})
|
||||
|
||||
// Manager: GET All Workers with Search and Pagination
|
||||
app.put('/api/worker/change-password', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.user // Get user ID from JWT
|
||||
const { currentPassword, newPassword } = req.body
|
||||
|
||||
if (!currentPassword || !newPassword) {
|
||||
return res.status(400).json({ message: 'Current password and new password are required.' })
|
||||
}
|
||||
if (newPassword.length < 6) {
|
||||
return res.status(400).json({ message: 'New password must be at least 6 characters long.' })
|
||||
}
|
||||
|
||||
// Get user's current password hash
|
||||
const [rows] = await db.execute('SELECT password_hash FROM workers WHERE id = ?', [userId])
|
||||
|
||||
if (rows.length === 0) {
|
||||
return res.status(404).json({ message: 'User not found.' })
|
||||
}
|
||||
|
||||
const user = rows[0]
|
||||
|
||||
// Verify current password
|
||||
const passwordMatch = await bcrypt.compare(currentPassword, user.password_hash)
|
||||
if (!passwordMatch) {
|
||||
return res.status(401).json({ message: 'Incorrect current password.' })
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
const saltRounds = 10
|
||||
const newHashedPassword = await bcrypt.hash(newPassword, saltRounds)
|
||||
|
||||
// Update password in DB
|
||||
await db.execute('UPDATE workers SET password_hash = ? WHERE id = ?', [
|
||||
newHashedPassword,
|
||||
userId,
|
||||
])
|
||||
|
||||
res.json({ message: 'Password updated successfully.' })
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error)
|
||||
res.status(500).json({ message: 'Database error during password change.' })
|
||||
}
|
||||
})
|
||||
|
||||
// Manager: PUT (Update) a Worker's Password
|
||||
app.put('/api/managers/workers/:workerId/password', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
// Ensure the user performing the action is a manager
|
||||
if (req.user.role !== 'manager') {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: 'Forbidden: You do not have permission to perform this action.' })
|
||||
}
|
||||
|
||||
const { workerId } = req.params
|
||||
const { newPassword } = req.body
|
||||
|
||||
if (!newPassword || newPassword.length < 6) {
|
||||
return res.status(400).json({ message: 'Password must be at least 6 characters long.' })
|
||||
}
|
||||
|
||||
const saltRounds = 10
|
||||
const hashedPassword = await bcrypt.hash(newPassword, saltRounds)
|
||||
|
||||
const [result] = await db.execute(
|
||||
"UPDATE workers SET password_hash = ? WHERE id = ? AND role = 'worker'",
|
||||
[hashedPassword, workerId],
|
||||
)
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ message: 'Worker not found or you cannot change the password for this user.' })
|
||||
}
|
||||
|
||||
res.status(200).json({ message: 'Password updated successfully.' })
|
||||
} catch (error) {
|
||||
console.error('Update password error:', error)
|
||||
res.status(500).json({ message: 'Database error while updating password.' })
|
||||
}
|
||||
})
|
||||
// GET all tags
|
||||
app.get('/api/managers/tags', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const [tags] = await db.execute('SELECT * FROM tags ORDER BY tag_name ASC')
|
||||
res.json(tags)
|
||||
} catch (error) {
|
||||
console.error('Get tags error:', error)
|
||||
res.status(500).json({ message: 'Database error fetching tags.' })
|
||||
}
|
||||
})
|
||||
|
||||
// POST a new tag
|
||||
app.post('/api/managers/tags', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { tag_name } = req.body
|
||||
if (!tag_name) {
|
||||
return res.status(400).json({ message: 'Tag name is required.' })
|
||||
}
|
||||
const [result] = await db.execute('INSERT INTO tags (tag_name) VALUES (?)', [tag_name])
|
||||
res.status(201).json({ id: result.insertId, tag_name })
|
||||
} catch (error) {
|
||||
if (error.code === 'ER_DUP_ENTRY') {
|
||||
return res.status(409).json({ message: 'This tag already exists.' })
|
||||
}
|
||||
console.error('Add tag error:', error)
|
||||
res.status(500).json({ message: 'Database error adding tag.' })
|
||||
}
|
||||
})
|
||||
|
||||
// POST to assign a tag to a worker
|
||||
app.post('/api/managers/workers/:workerId/tags', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { workerId } = req.params
|
||||
const { tagId } = req.body // Expects a single tag ID
|
||||
|
||||
if (!tagId) {
|
||||
return res.status(400).json({ message: 'Tag ID is required.' })
|
||||
}
|
||||
|
||||
// INSERT IGNORE prevents errors if the tag is already assigned to the worker
|
||||
await db.query('INSERT IGNORE INTO worker_tags (worker_id, tag_id) VALUES (?, ?)', [
|
||||
workerId,
|
||||
tagId,
|
||||
])
|
||||
|
||||
res.status(200).json({ message: 'Tag assigned successfully.' })
|
||||
} catch (error) {
|
||||
console.error('Assign tag error:', error)
|
||||
res.status(500).json({ message: 'Database error assigning tag.' })
|
||||
}
|
||||
})
|
||||
|
||||
// DELETE to remove a tag from a worker
|
||||
app.delete('/api/managers/workers/:workerId/tags/:tagId', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { workerId, tagId } = req.params
|
||||
await db.query('DELETE FROM worker_tags WHERE worker_id = ? AND tag_id = ?', [
|
||||
workerId,
|
||||
tagId,
|
||||
])
|
||||
res.status(204).send() // 204 No Content for successful deletion
|
||||
} catch (error) {
|
||||
console.error('Remove tag error:', error)
|
||||
res.status(500).json({ message: 'Database error removing tag.' })
|
||||
}
|
||||
})
|
||||
|
||||
// Find this endpoint in your server.js and replace it with the code below.
|
||||
|
||||
// Manager: GET All Workers (FIXED for older MySQL versions)
|
||||
app.get('/api/managers/workers', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { search = '', page = 1, limit = 20 } = req.query
|
||||
const { search = '', page = 1, limit = 20, tags = '' } = req.query
|
||||
const offset = (parseInt(page) - 1) * parseInt(limit)
|
||||
const searchTerm = `%${search}%`
|
||||
const [workers] = await db.execute(
|
||||
`SELECT id, username, full_name, created_at FROM workers WHERE role = 'worker' AND (full_name LIKE ? OR username LIKE ?) ORDER BY created_at DESC LIMIT ? OFFSET ?`,
|
||||
[searchTerm, searchTerm, parseInt(limit), offset],
|
||||
)
|
||||
const [[{ totalCount }]] = await db.execute(
|
||||
`SELECT COUNT(*) as totalCount FROM workers WHERE role = 'worker' AND (full_name LIKE ? OR username LIKE ?)`,
|
||||
[searchTerm, searchTerm],
|
||||
)
|
||||
|
||||
const tagIds = tags
|
||||
.split(',')
|
||||
.filter((id) => id)
|
||||
.map(Number)
|
||||
const hasTagFilter = tagIds.length > 0
|
||||
|
||||
// Base queries
|
||||
let baseQuery = `
|
||||
SELECT
|
||||
w.id, w.username, w.full_name, w.created_at,
|
||||
(SELECT GROUP_CONCAT(t.tag_name SEPARATOR ', ')
|
||||
FROM worker_tags wt_sub
|
||||
JOIN tags t ON wt_sub.tag_id = t.id
|
||||
WHERE wt_sub.worker_id = w.id) as tags
|
||||
FROM workers w
|
||||
`
|
||||
let countQuery = `SELECT COUNT(DISTINCT w.id) as totalCount FROM workers w`
|
||||
|
||||
// Parameters for the queries
|
||||
const params = []
|
||||
const countParams = []
|
||||
|
||||
// Join with worker_tags if filtering
|
||||
if (hasTagFilter) {
|
||||
const joinClause = ` JOIN worker_tags wt ON w.id = wt.worker_id`
|
||||
baseQuery += joinClause
|
||||
countQuery += joinClause
|
||||
}
|
||||
|
||||
// Common WHERE clause
|
||||
const whereClause = ` WHERE w.role = 'worker' AND (w.full_name LIKE ? OR w.username LIKE ?)`
|
||||
baseQuery += whereClause
|
||||
countQuery += whereClause
|
||||
params.push(searchTerm, searchTerm)
|
||||
countParams.push(searchTerm, searchTerm)
|
||||
|
||||
// Add tag filtering logic
|
||||
if (hasTagFilter) {
|
||||
const tagPlaceholders = tagIds.map(() => '?').join(',')
|
||||
|
||||
const tagFilterClause = ` AND wt.tag_id IN (${tagPlaceholders})`
|
||||
baseQuery += tagFilterClause
|
||||
countQuery += tagFilterClause
|
||||
|
||||
// Add the tag IDs to the parameters individually
|
||||
params.push(...tagIds)
|
||||
countParams.push(...tagIds)
|
||||
// --- FIX END ---
|
||||
}
|
||||
|
||||
// Grouping and pagination for the main query
|
||||
if (hasTagFilter) {
|
||||
baseQuery += ` GROUP BY w.id HAVING COUNT(DISTINCT wt.tag_id) = ?`
|
||||
params.push(tagIds.length)
|
||||
}
|
||||
|
||||
baseQuery += ` ORDER BY w.created_at DESC LIMIT ? OFFSET ?`
|
||||
params.push(parseInt(limit), offset)
|
||||
|
||||
// Execute queries
|
||||
const [workers] = await db.execute(baseQuery, params)
|
||||
const [[{ totalCount }]] = await db.execute(countQuery, countParams)
|
||||
|
||||
res.json({ workers, totalCount })
|
||||
} catch (error) {
|
||||
// This is the error you are seeing
|
||||
console.error('Get workers error:', error)
|
||||
res.status(500).json({ message: 'Database error fetching workers.' })
|
||||
}
|
||||
})
|
||||
|
||||
// Manager: POST (Add new) Worker
|
||||
|
||||
app.post('/api/managers/workers', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { username, password, fullName } = req.body
|
||||
const { username, password, fullName, role = 'worker' } = req.body
|
||||
if (!username || !password || !fullName) {
|
||||
return res.status(400).json({ message: 'Username, password, and full name are required.' })
|
||||
}
|
||||
|
||||
if (!['worker', 'manager'].includes(role)) {
|
||||
return res.status(400).json({ message: 'Invalid role specified.' })
|
||||
}
|
||||
const saltRounds = 10
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds)
|
||||
|
||||
const [result] = await db.execute(
|
||||
"INSERT INTO workers (username, password_hash, full_name, role) VALUES (?, ?, ?, 'worker')",
|
||||
[username, hashedPassword, fullName],
|
||||
'INSERT INTO workers (username, password_hash, full_name, role) VALUES (?, ?, ?, ?)',
|
||||
[username, hashedPassword, fullName, role], // Pass role to query
|
||||
)
|
||||
res.status(201).json({ id: result.insertId, username, fullName })
|
||||
res.status(201).json({ id: result.insertId, username, fullName, role })
|
||||
} catch (error) {
|
||||
if (error.code === 'ER_DUP_ENTRY') {
|
||||
return res.status(409).json({ message: 'Username already exists.' })
|
||||
|
||||
Reference in New Issue
Block a user