502 lines
14 KiB
JavaScript
502 lines
14 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const bcrypt = require('bcryptjs');
|
|
const { User, Store, SystemUser, LadderUser, Match, PointOrder, StoreStaff } = require('../models');
|
|
const { success, error, getPagination, pageResult } = require('../utils/helper');
|
|
const { Op } = require('sequelize');
|
|
|
|
class AdminController {
|
|
// 后台登录
|
|
async login(req, res) {
|
|
try {
|
|
const { username, password } = req.body;
|
|
|
|
if (!username || !password) {
|
|
return res.status(400).json(error('请输入用户名和密码', 400));
|
|
}
|
|
|
|
const admin = await SystemUser.findOne({
|
|
where: { username },
|
|
include: [{ model: Store, as: 'store' }]
|
|
});
|
|
|
|
if (!admin) {
|
|
return res.status(401).json(error('用户名或密码错误', 401));
|
|
}
|
|
|
|
const isValid = await admin.validatePassword(password);
|
|
if (!isValid) {
|
|
return res.status(401).json(error('用户名或密码错误', 401));
|
|
}
|
|
|
|
if (admin.status !== 1) {
|
|
return res.status(403).json(error('账号已被禁用', 403));
|
|
}
|
|
|
|
// 更新最后登录时间
|
|
await admin.update({ last_login_at: new Date() });
|
|
|
|
// 生成token
|
|
const token = jwt.sign(
|
|
{ adminId: admin.id },
|
|
process.env.JWT_SECRET,
|
|
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
|
);
|
|
|
|
res.json(success({
|
|
token,
|
|
userInfo: {
|
|
id: admin.id,
|
|
username: admin.username,
|
|
realName: admin.real_name,
|
|
role: admin.role,
|
|
storeId: admin.store_id,
|
|
storeName: admin.store?.name,
|
|
avatar: admin.avatar
|
|
}
|
|
}, '登录成功'));
|
|
} catch (err) {
|
|
console.error('登录失败:', err);
|
|
res.status(500).json(error('登录失败'));
|
|
}
|
|
}
|
|
|
|
// 仪表盘数据
|
|
async getDashboard(req, res) {
|
|
try {
|
|
const admin = req.admin;
|
|
const storeWhere = admin.role === 'super_admin' ? {} : { store_id: admin.store_id };
|
|
|
|
// 统计数据
|
|
const [userCount, storeCount, ladderUserCount, matchCount, pendingOrderCount] = await Promise.all([
|
|
User.count({ where: { status: 1 } }),
|
|
Store.count({ where: { status: 1 } }),
|
|
LadderUser.count({ where: { status: 1, ...storeWhere } }),
|
|
Match.count({ where: storeWhere }),
|
|
PointOrder.count({ where: { status: 0, ...storeWhere } })
|
|
]);
|
|
|
|
// 近7天比赛数据
|
|
const last7Days = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
const recentMatches = await Match.count({
|
|
where: {
|
|
...storeWhere,
|
|
created_at: { [Op.gte]: last7Days }
|
|
}
|
|
});
|
|
|
|
res.json(success({
|
|
stats: {
|
|
userCount,
|
|
storeCount: admin.role === 'super_admin' ? storeCount : 1,
|
|
ladderUserCount,
|
|
matchCount,
|
|
pendingOrderCount,
|
|
recentMatches
|
|
}
|
|
}));
|
|
} catch (err) {
|
|
console.error('获取仪表盘数据失败:', err);
|
|
res.status(500).json(error('获取失败'));
|
|
}
|
|
}
|
|
|
|
// === 用户管理 ===
|
|
async getUsers(req, res) {
|
|
try {
|
|
const { page = 1, pageSize = 20, keyword, status } = req.query;
|
|
const { limit, offset } = getPagination(page, pageSize);
|
|
|
|
const where = {};
|
|
if (keyword) {
|
|
where[Op.or] = [
|
|
{ nickname: { [Op.like]: `%${keyword}%` } },
|
|
{ phone: { [Op.like]: `%${keyword}%` } },
|
|
{ member_code: { [Op.like]: `%${keyword}%` } }
|
|
];
|
|
}
|
|
if (status !== undefined && status !== '') {
|
|
where.status = parseInt(status);
|
|
}
|
|
|
|
const { rows, count } = await User.findAndCountAll({
|
|
where,
|
|
order: [['created_at', 'DESC']],
|
|
limit,
|
|
offset
|
|
});
|
|
|
|
res.json(pageResult(rows.map(user => ({
|
|
id: user.id,
|
|
nickname: user.nickname,
|
|
phone: user.phone,
|
|
avatar: user.avatar,
|
|
gender: user.gender,
|
|
memberCode: user.member_code,
|
|
totalPoints: user.total_points,
|
|
status: user.status,
|
|
createdAt: user.created_at
|
|
})), count, page, pageSize));
|
|
} catch (err) {
|
|
console.error('获取用户列表失败:', err);
|
|
res.status(500).json(error('获取失败'));
|
|
}
|
|
}
|
|
|
|
// 搜索用户(用于积分操作,不需要超级管理员权限)
|
|
async searchUsers(req, res) {
|
|
try {
|
|
const { keyword, pageSize = 10 } = req.query;
|
|
|
|
if (!keyword || keyword.trim().length < 2) {
|
|
return res.json(success([]));
|
|
}
|
|
|
|
const where = {
|
|
status: 1 // 只搜索正常状态的用户
|
|
};
|
|
|
|
where[Op.or] = [
|
|
{ nickname: { [Op.like]: `%${keyword.trim()}%` } },
|
|
{ phone: { [Op.like]: `%${keyword.trim()}%` } },
|
|
{ member_code: { [Op.like]: `%${keyword.trim()}%` } }
|
|
];
|
|
|
|
const users = await User.findAll({
|
|
where,
|
|
limit: parseInt(pageSize) || 10,
|
|
order: [['created_at', 'DESC']],
|
|
attributes: ['id', 'nickname', 'phone', 'avatar', 'member_code']
|
|
});
|
|
|
|
res.json(success(users.map(user => ({
|
|
id: user.id,
|
|
nickname: user.nickname,
|
|
phone: user.phone,
|
|
avatar: user.avatar,
|
|
memberCode: user.member_code
|
|
}))));
|
|
} catch (err) {
|
|
console.error('搜索用户失败:', err);
|
|
res.status(500).json(error('搜索失败'));
|
|
}
|
|
}
|
|
|
|
async getUserDetail(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const user = await User.findByPk(id, {
|
|
include: [{
|
|
model: LadderUser,
|
|
as: 'ladderUsers',
|
|
include: [{ model: Store, as: 'store' }]
|
|
}]
|
|
});
|
|
|
|
if (!user) {
|
|
return res.status(404).json(error('用户不存在', 404));
|
|
}
|
|
|
|
res.json(success({
|
|
id: user.id,
|
|
nickname: user.nickname,
|
|
phone: user.phone,
|
|
avatar: user.avatar,
|
|
gender: user.gender,
|
|
memberCode: user.member_code,
|
|
totalPoints: user.total_points,
|
|
status: user.status,
|
|
createdAt: user.created_at,
|
|
ladderUsers: user.ladderUsers.map(lu => ({
|
|
id: lu.id,
|
|
storeId: lu.store_id,
|
|
storeName: lu.store?.name,
|
|
realName: lu.real_name,
|
|
level: lu.level,
|
|
powerScore: lu.power_score,
|
|
matchCount: lu.match_count
|
|
}))
|
|
}));
|
|
} catch (err) {
|
|
console.error('获取用户详情失败:', err);
|
|
res.status(500).json(error('获取失败'));
|
|
}
|
|
}
|
|
|
|
async updateUserStatus(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { status } = req.body;
|
|
|
|
const user = await User.findByPk(id);
|
|
if (!user) {
|
|
return res.status(404).json(error('用户不存在', 404));
|
|
}
|
|
|
|
await user.update({ status });
|
|
res.json(success(null, '更新成功'));
|
|
} catch (err) {
|
|
console.error('更新用户状态失败:', err);
|
|
res.status(500).json(error('更新失败'));
|
|
}
|
|
}
|
|
|
|
// === 门店管理 ===
|
|
async getStores(req, res) {
|
|
try {
|
|
const { page = 1, pageSize = 20, keyword, status } = req.query;
|
|
const { limit, offset } = getPagination(page, pageSize);
|
|
|
|
const where = {};
|
|
if (keyword) {
|
|
where.name = { [Op.like]: `%${keyword}%` };
|
|
}
|
|
if (status !== undefined && status !== '') {
|
|
where.status = parseInt(status);
|
|
}
|
|
|
|
const { rows, count } = await Store.findAndCountAll({
|
|
where,
|
|
order: [['created_at', 'DESC']],
|
|
limit,
|
|
offset
|
|
});
|
|
|
|
res.json(pageResult(rows, count, page, pageSize));
|
|
} catch (err) {
|
|
console.error('获取门店列表失败:', err);
|
|
res.status(500).json(error('获取失败'));
|
|
}
|
|
}
|
|
|
|
async createStore(req, res) {
|
|
try {
|
|
const data = req.body;
|
|
const store = await Store.create(data);
|
|
res.json(success({ id: store.id }, '创建成功'));
|
|
} catch (err) {
|
|
console.error('创建门店失败:', err);
|
|
res.status(500).json(error('创建失败'));
|
|
}
|
|
}
|
|
|
|
async updateStore(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const data = req.body;
|
|
|
|
const store = await Store.findByPk(id);
|
|
if (!store) {
|
|
return res.status(404).json(error('门店不存在', 404));
|
|
}
|
|
|
|
await store.update(data);
|
|
res.json(success(null, '更新成功'));
|
|
} catch (err) {
|
|
console.error('更新门店失败:', err);
|
|
res.status(500).json(error('更新失败'));
|
|
}
|
|
}
|
|
|
|
async deleteStore(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const store = await Store.findByPk(id);
|
|
if (!store) {
|
|
return res.status(404).json(error('门店不存在', 404));
|
|
}
|
|
|
|
// 检查是否有关联数据
|
|
const ladderCount = await LadderUser.count({ where: { store_id: id } });
|
|
if (ladderCount > 0) {
|
|
return res.status(400).json(error('该门店下有天梯用户,无法删除', 400));
|
|
}
|
|
|
|
await store.destroy();
|
|
res.json(success(null, '删除成功'));
|
|
} catch (err) {
|
|
console.error('删除门店失败:', err);
|
|
res.status(500).json(error('删除失败'));
|
|
}
|
|
}
|
|
|
|
// === 系统用户管理 ===
|
|
async getSystemUsers(req, res) {
|
|
try {
|
|
const { page = 1, pageSize = 20, keyword, role } = req.query;
|
|
const { limit, offset } = getPagination(page, pageSize);
|
|
|
|
const where = {};
|
|
if (keyword) {
|
|
where[Op.or] = [
|
|
{ username: { [Op.like]: `%${keyword}%` } },
|
|
{ real_name: { [Op.like]: `%${keyword}%` } }
|
|
];
|
|
}
|
|
if (role) {
|
|
where.role = role;
|
|
}
|
|
|
|
const { rows, count } = await SystemUser.findAndCountAll({
|
|
where,
|
|
include: [{ model: Store, as: 'store', attributes: ['id', 'name'] }],
|
|
attributes: { exclude: ['password'] },
|
|
order: [['created_at', 'DESC']],
|
|
limit,
|
|
offset
|
|
});
|
|
|
|
res.json(pageResult(rows.map(user => ({
|
|
id: user.id,
|
|
username: user.username,
|
|
realName: user.real_name,
|
|
phone: user.phone,
|
|
role: user.role,
|
|
storeId: user.store_id,
|
|
storeName: user.store?.name,
|
|
status: user.status,
|
|
lastLoginAt: user.last_login_at,
|
|
createdAt: user.created_at
|
|
})), count, page, pageSize));
|
|
} catch (err) {
|
|
console.error('获取系统用户列表失败:', err);
|
|
res.status(500).json(error('获取失败'));
|
|
}
|
|
}
|
|
|
|
async createSystemUser(req, res) {
|
|
try {
|
|
const { username, password, real_name, phone, role, store_id } = req.body;
|
|
|
|
if (!username || !password) {
|
|
return res.status(400).json(error('用户名和密码不能为空', 400));
|
|
}
|
|
|
|
// 检查用户名是否已存在
|
|
const existing = await SystemUser.findOne({ where: { username } });
|
|
if (existing) {
|
|
return res.status(400).json(error('用户名已存在', 400));
|
|
}
|
|
|
|
const user = await SystemUser.create({
|
|
username,
|
|
password,
|
|
real_name,
|
|
phone,
|
|
role: role || 'staff',
|
|
store_id: role === 'staff' ? store_id : null,
|
|
status: 1
|
|
});
|
|
|
|
res.json(success({ id: user.id }, '创建成功'));
|
|
} catch (err) {
|
|
console.error('创建系统用户失败:', err);
|
|
res.status(500).json(error('创建失败'));
|
|
}
|
|
}
|
|
|
|
async updateSystemUser(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { real_name, phone, role, store_id, status, password } = req.body;
|
|
|
|
const user = await SystemUser.findByPk(id);
|
|
if (!user) {
|
|
return res.status(404).json(error('用户不存在', 404));
|
|
}
|
|
|
|
const updateData = { real_name, phone, role, status };
|
|
if (role === 'staff') {
|
|
updateData.store_id = store_id;
|
|
} else {
|
|
updateData.store_id = null;
|
|
}
|
|
if (password) {
|
|
updateData.password = password;
|
|
}
|
|
|
|
await user.update(updateData);
|
|
res.json(success(null, '更新成功'));
|
|
} catch (err) {
|
|
console.error('更新系统用户失败:', err);
|
|
res.status(500).json(error('更新失败'));
|
|
}
|
|
}
|
|
|
|
async deleteSystemUser(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
if (req.admin.id === parseInt(id)) {
|
|
return res.status(400).json(error('不能删除自己', 400));
|
|
}
|
|
|
|
const user = await SystemUser.findByPk(id);
|
|
if (!user) {
|
|
return res.status(404).json(error('用户不存在', 404));
|
|
}
|
|
|
|
await user.destroy();
|
|
res.json(success(null, '删除成功'));
|
|
} catch (err) {
|
|
console.error('删除系统用户失败:', err);
|
|
res.status(500).json(error('删除失败'));
|
|
}
|
|
}
|
|
|
|
// === 个人信息 ===
|
|
async getProfile(req, res) {
|
|
try {
|
|
const admin = req.admin;
|
|
res.json(success({
|
|
id: admin.id,
|
|
username: admin.username,
|
|
realName: admin.real_name,
|
|
phone: admin.phone,
|
|
role: admin.role,
|
|
storeId: admin.store_id,
|
|
storeName: admin.store?.name,
|
|
avatar: admin.avatar
|
|
}));
|
|
} catch (err) {
|
|
console.error('获取个人信息失败:', err);
|
|
res.status(500).json(error('获取失败'));
|
|
}
|
|
}
|
|
|
|
async updateProfile(req, res) {
|
|
try {
|
|
const { real_name, phone, avatar } = req.body;
|
|
await req.admin.update({ real_name, phone, avatar });
|
|
res.json(success(null, '更新成功'));
|
|
} catch (err) {
|
|
console.error('更新个人信息失败:', err);
|
|
res.status(500).json(error('更新失败'));
|
|
}
|
|
}
|
|
|
|
async updatePassword(req, res) {
|
|
try {
|
|
const { old_password, new_password } = req.body;
|
|
|
|
if (!old_password || !new_password) {
|
|
return res.status(400).json(error('请输入旧密码和新密码', 400));
|
|
}
|
|
|
|
const isValid = await req.admin.validatePassword(old_password);
|
|
if (!isValid) {
|
|
return res.status(400).json(error('旧密码错误', 400));
|
|
}
|
|
|
|
await req.admin.update({ password: new_password });
|
|
res.json(success(null, '密码修改成功'));
|
|
} catch (err) {
|
|
console.error('修改密码失败:', err);
|
|
res.status(500).json(error('修改失败'));
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new AdminController();
|