chore: initial import
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const bcrypt = require('bcryptjs');
|
||||
const db = require('../db');
|
||||
const { verifyToken, isSuperAdmin, isSystemAdmin } = require('../middleware/auth');
|
||||
|
||||
// 搜索用户 (所有登录用户可用,用于成果录入选择作者)
|
||||
router.get('/search', verifyToken, async (req, res) => {
|
||||
const { keyword, leaderOnly } = req.query;
|
||||
if (!keyword) return res.json([]);
|
||||
try {
|
||||
let query = `
|
||||
SELECT id, username as phone, real_name, department
|
||||
FROM users
|
||||
WHERE (username ILIKE $1 OR real_name ILIKE $1)
|
||||
`;
|
||||
const params = [`%${keyword}%`];
|
||||
|
||||
if (leaderOnly === 'true') {
|
||||
// 获取所有领导层部门
|
||||
const leaderDeptsRes = await db.query('SELECT name FROM dict_leader_departments');
|
||||
const leaderDepts = leaderDeptsRes.rows.map(r => r.name);
|
||||
|
||||
if (leaderDepts.length > 0) {
|
||||
query += ` AND department = ANY($${params.length + 1})`;
|
||||
params.push(leaderDepts);
|
||||
} else {
|
||||
// 如果没有配置领导层部门,则返回空
|
||||
return res.json([]);
|
||||
}
|
||||
}
|
||||
|
||||
query += ` LIMIT 20`;
|
||||
const result = await db.query(query, params);
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ message: '服务器错误' });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取所有待定状态用户的手机号列表 (用于前端标红显示)
|
||||
router.get('/pending-list', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const result = await db.query('SELECT username FROM users WHERE dept_status = \'pending\'');
|
||||
const phones = result.rows.map(row => row.username);
|
||||
res.json(phones);
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: '服务器错误' });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取所有用户列表 (系统管理员:超管或维护员)
|
||||
router.get('/', verifyToken, isSystemAdmin, async (req, res) => {
|
||||
const { keyword } = req.query;
|
||||
try {
|
||||
let query = 'SELECT id, username, role, real_name, department, dept_status, created_at FROM users WHERE 1=1';
|
||||
const params = [];
|
||||
|
||||
if (keyword) {
|
||||
query += ` AND (username ILIKE $1 OR real_name ILIKE $1 OR department ILIKE $1)`;
|
||||
params.push(`%${keyword}%`);
|
||||
}
|
||||
|
||||
query += ' ORDER BY created_at DESC';
|
||||
const result = await db.query(query, params);
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: '服务器错误' });
|
||||
}
|
||||
});
|
||||
|
||||
// 创建新用户 (仅超管)
|
||||
router.post('/', verifyToken, isSuperAdmin, async (req, res) => {
|
||||
const { username, password, role, real_name, department: inputDepartment } = req.body;
|
||||
let department = inputDepartment;
|
||||
|
||||
if (!username || !password || !role) {
|
||||
return res.status(400).json({ message: '缺少必填字段 (username, password, role)' });
|
||||
}
|
||||
|
||||
try {
|
||||
// 手机号格式校验
|
||||
if (!/^1[3-9]\d{9}$/.test(username)) {
|
||||
return res.status(400).json({ message: '手机号格式不正确' });
|
||||
}
|
||||
|
||||
const userExists = await db.query('SELECT * FROM users WHERE username = $1', [username]);
|
||||
if (userExists.rows.length > 0) {
|
||||
return res.status(400).json({ message: '该手机号已注册' });
|
||||
}
|
||||
|
||||
// 校验部门
|
||||
let deptStatus = 'verified';
|
||||
if (['admin', 'user'].includes(role)) {
|
||||
if (!department) {
|
||||
return res.status(400).json({ message: '该角色必须指定部门' });
|
||||
}
|
||||
const deptCheck = await db.query('SELECT * FROM departments WHERE name = $1', [department]);
|
||||
deptStatus = deptCheck.rows.length > 0 ? 'verified' : 'pending';
|
||||
} else {
|
||||
// super_admin 或 maintainer 可以没有部门
|
||||
if (!department) department = null;
|
||||
}
|
||||
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const passwordHash = await bcrypt.hash(password, salt);
|
||||
|
||||
const result = await db.query(
|
||||
'INSERT INTO users (username, password_hash, role, real_name, department, dept_status) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, username, role, real_name, department, dept_status',
|
||||
[username, passwordHash, role, real_name, department, deptStatus]
|
||||
);
|
||||
res.status(201).json(result.rows[0]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: '服务器错误' });
|
||||
}
|
||||
});
|
||||
|
||||
// 修改用户信息 (系统管理员:超管或维护员)
|
||||
router.put('/:id', verifyToken, isSystemAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
let { username, role, real_name, department, password } = req.body;
|
||||
|
||||
// 如果是维护员,只能修改密码,忽略其他字段
|
||||
if (req.user.role === 'maintainer') {
|
||||
if (!password) {
|
||||
return res.json({ message: '未提供新密码,无任何修改' });
|
||||
}
|
||||
try {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const passwordHash = await bcrypt.hash(password, salt);
|
||||
await db.query(
|
||||
'UPDATE users SET password_hash=$1, token_version = CASE WHEN token_version >= 2000000000 THEN 1 ELSE token_version + 1 END WHERE id=$2',
|
||||
[passwordHash, id]
|
||||
);
|
||||
return res.json({ message: '密码重置成功' });
|
||||
} catch (err) {
|
||||
return res.status(500).json({ message: '服务器错误' });
|
||||
}
|
||||
}
|
||||
|
||||
// 以下逻辑仅适用于超级管理员 (super_admin)
|
||||
try {
|
||||
// 手机号格式校验
|
||||
if (!/^1[3-9]\d{9}$/.test(username)) {
|
||||
return res.status(400).json({ message: '手机号格式不正确' });
|
||||
}
|
||||
|
||||
// 校验部门
|
||||
let deptStatus = 'verified';
|
||||
if (['admin', 'user'].includes(role)) {
|
||||
if (!department) {
|
||||
return res.status(400).json({ message: '该角色必须指定部门' });
|
||||
}
|
||||
const deptCheck = await db.query('SELECT * FROM departments WHERE name = $1', [department]);
|
||||
deptStatus = deptCheck.rows.length > 0 ? 'verified' : 'pending';
|
||||
} else {
|
||||
// super_admin 或 maintainer 可以没有部门
|
||||
if (!department) department = null;
|
||||
}
|
||||
|
||||
let query = 'UPDATE users SET username=$1, role=$2, real_name=$3, department=$4, dept_status=$5';
|
||||
let params = [username, role, real_name, department, deptStatus, id];
|
||||
|
||||
if (password) {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const passwordHash = await bcrypt.hash(password, salt);
|
||||
query += ', password_hash=$6, token_version = CASE WHEN token_version >= 2000000000 THEN 1 ELSE token_version + 1 END WHERE id=$7';
|
||||
params = [username, role, real_name, department, deptStatus, passwordHash, id];
|
||||
} else {
|
||||
query += ' WHERE id=$6';
|
||||
}
|
||||
|
||||
await db.query(query, params);
|
||||
res.json({ message: '修改成功' });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: '服务器错误' });
|
||||
}
|
||||
});
|
||||
|
||||
// 删除用户 (仅超管)
|
||||
router.delete('/:id', verifyToken, isSuperAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
// 防止删除自己
|
||||
if (parseInt(id) === req.user.id) {
|
||||
return res.status(400).json({ message: '不能删除当前登录账号' });
|
||||
}
|
||||
|
||||
// 检查是否有关联的成果
|
||||
const achievementCheck = await db.query('SELECT id FROM achievements WHERE user_id = $1 LIMIT 1', [id]);
|
||||
if (achievementCheck.rows.length > 0) {
|
||||
return res.status(400).json({ message: '该用户有关联的成果数据,无法直接删除。请先处理关联数据。' });
|
||||
}
|
||||
|
||||
await db.query('DELETE FROM users WHERE id = $1', [id]);
|
||||
res.json({ message: '删除成功' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ message: '服务器错误' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user