564 lines
13 KiB
JavaScript
564 lines
13 KiB
JavaScript
|
|
const { Op } = require('sequelize');
|
|||
|
|
const Menu = require('../models/Menu');
|
|||
|
|
const RoleMenu = require('../models/RoleMenu');
|
|||
|
|
const { logOperation } = require('../utils/logger');
|
|||
|
|
|
|||
|
|
// 获取菜单树
|
|||
|
|
exports.getMenuTree = async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { type, status } = req.query;
|
|||
|
|
|
|||
|
|
const where = { isDeleted: false };
|
|||
|
|
if (type) {
|
|||
|
|
where.type = type;
|
|||
|
|
}
|
|||
|
|
if (status) {
|
|||
|
|
where.status = status;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const menus = await Menu.findAll({
|
|||
|
|
where,
|
|||
|
|
order: [['sort', 'ASC'], ['createdAt', 'ASC']]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const menuTree = buildTree(menus);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '获取成功',
|
|||
|
|
data: menuTree
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取菜单树失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
code: 500,
|
|||
|
|
message: '获取菜单树失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 获取菜单列表(平铺)
|
|||
|
|
exports.getMenuList = async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { page = 1, pageSize = 10, name, type, status } = req.query;
|
|||
|
|
|
|||
|
|
const where = { isDeleted: false };
|
|||
|
|
if (name) {
|
|||
|
|
where.name = { [Op.like]: `%${name}%` };
|
|||
|
|
}
|
|||
|
|
if (type) {
|
|||
|
|
where.type = type;
|
|||
|
|
}
|
|||
|
|
if (status) {
|
|||
|
|
where.status = status;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const { count, rows } = await Menu.findAndCountAll({
|
|||
|
|
where,
|
|||
|
|
order: [['sort', 'ASC'], ['createdAt', 'ASC']],
|
|||
|
|
offset: (page - 1) * pageSize,
|
|||
|
|
limit: parseInt(pageSize)
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '获取成功',
|
|||
|
|
data: {
|
|||
|
|
list: rows,
|
|||
|
|
total: count,
|
|||
|
|
page: parseInt(page),
|
|||
|
|
pageSize: parseInt(pageSize)
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取菜单列表失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
code: 500,
|
|||
|
|
message: '获取菜单列表失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 获取菜单详情
|
|||
|
|
exports.getMenuById = async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { id } = req.params;
|
|||
|
|
|
|||
|
|
const menu = await Menu.findByPk(id, {
|
|||
|
|
where: { isDeleted: false }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!menu) {
|
|||
|
|
return res.status(404).json({
|
|||
|
|
code: 404,
|
|||
|
|
message: '菜单不存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '获取成功',
|
|||
|
|
data: menu
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取菜单详情失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
code: 500,
|
|||
|
|
message: '获取菜单详情失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 创建菜单
|
|||
|
|
exports.createMenu = async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { parentId, name, code, type, path, component, icon, sort, visible, status } = req.body;
|
|||
|
|
|
|||
|
|
if (!name || !code || !type) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
code: 400,
|
|||
|
|
message: '菜单名称、编码和类型不能为空'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (type === 'menu' && !path) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
code: 400,
|
|||
|
|
message: '菜单类型必须填写路由路径'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const existingMenu = await Menu.findOne({
|
|||
|
|
where: { code, isDeleted: false }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (existingMenu) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
code: 400,
|
|||
|
|
message: '菜单编码已存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (parentId) {
|
|||
|
|
const parentMenu = await Menu.findByPk(parentId, {
|
|||
|
|
where: { isDeleted: false }
|
|||
|
|
});
|
|||
|
|
if (!parentMenu) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
code: 400,
|
|||
|
|
message: '父菜单不存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const menu = await Menu.create({
|
|||
|
|
parentId: parentId || null,
|
|||
|
|
name,
|
|||
|
|
code,
|
|||
|
|
type,
|
|||
|
|
path: type === 'menu' ? path : null,
|
|||
|
|
component,
|
|||
|
|
icon,
|
|||
|
|
sort: sort || 0,
|
|||
|
|
visible: visible || 'show',
|
|||
|
|
status: status || 'active',
|
|||
|
|
createBy: req.user.id,
|
|||
|
|
updateBy: req.user.id
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await logOperation({
|
|||
|
|
userId: req.user.id,
|
|||
|
|
username: req.user.username,
|
|||
|
|
module: '菜单管理',
|
|||
|
|
action: '创建',
|
|||
|
|
description: `创建菜单: ${name}`,
|
|||
|
|
method: req.method,
|
|||
|
|
path: req.path,
|
|||
|
|
ip: req.ip,
|
|||
|
|
status: 'success'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '创建成功',
|
|||
|
|
data: menu
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('创建菜单失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
code: 500,
|
|||
|
|
message: '创建菜单失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 更新菜单
|
|||
|
|
exports.updateMenu = async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { id } = req.params;
|
|||
|
|
const { parentId, name, code, type, path, component, icon, sort, visible, status } = req.body;
|
|||
|
|
|
|||
|
|
const menu = await Menu.findByPk(id, {
|
|||
|
|
where: { isDeleted: false }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!menu) {
|
|||
|
|
return res.status(404).json({
|
|||
|
|
code: 404,
|
|||
|
|
message: '菜单不存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (code && code !== menu.code) {
|
|||
|
|
const existingMenu = await Menu.findOne({
|
|||
|
|
where: { code, isDeleted: false, id: { [Op.ne]: id } }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (existingMenu) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
code: 400,
|
|||
|
|
message: '菜单编码已存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (parentId) {
|
|||
|
|
if (parseInt(parentId) === parseInt(id)) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
code: 400,
|
|||
|
|
message: '不能将菜单设置为自己的子菜单'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const parentMenu = await Menu.findByPk(parentId, {
|
|||
|
|
where: { isDeleted: false }
|
|||
|
|
});
|
|||
|
|
if (!parentMenu) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
code: 400,
|
|||
|
|
message: '父菜单不存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const updateData = {
|
|||
|
|
updateBy: req.user.id
|
|||
|
|
};
|
|||
|
|
if (parentId !== undefined) updateData.parentId = parentId || null;
|
|||
|
|
if (name !== undefined) updateData.name = name;
|
|||
|
|
if (code !== undefined) updateData.code = code;
|
|||
|
|
if (type !== undefined) updateData.type = type;
|
|||
|
|
if (path !== undefined) updateData.path = path;
|
|||
|
|
if (component !== undefined) updateData.component = component;
|
|||
|
|
if (icon !== undefined) updateData.icon = icon;
|
|||
|
|
if (sort !== undefined) updateData.sort = sort;
|
|||
|
|
if (visible !== undefined) updateData.visible = visible;
|
|||
|
|
if (status !== undefined) updateData.status = status;
|
|||
|
|
|
|||
|
|
await menu.update(updateData);
|
|||
|
|
|
|||
|
|
await logOperation({
|
|||
|
|
userId: req.user.id,
|
|||
|
|
username: req.user.username,
|
|||
|
|
module: '菜单管理',
|
|||
|
|
action: '更新',
|
|||
|
|
description: `更新菜单: ${menu.name}`,
|
|||
|
|
method: req.method,
|
|||
|
|
path: req.path,
|
|||
|
|
ip: req.ip,
|
|||
|
|
status: 'success'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '更新成功',
|
|||
|
|
data: menu
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('更新菜单失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
code: 500,
|
|||
|
|
message: '更新菜单失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 删除菜单
|
|||
|
|
exports.deleteMenu = async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { id } = req.params;
|
|||
|
|
|
|||
|
|
const menu = await Menu.findByPk(id, {
|
|||
|
|
where: { isDeleted: false }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!menu) {
|
|||
|
|
return res.status(404).json({
|
|||
|
|
code: 404,
|
|||
|
|
message: '菜单不存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const children = await Menu.findAll({
|
|||
|
|
where: { parentId: id, isDeleted: false }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (children.length > 0) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
code: 400,
|
|||
|
|
message: '该菜单下有子菜单,无法删除'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await menu.update({
|
|||
|
|
isDeleted: true,
|
|||
|
|
updateBy: req.user.id
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await RoleMenu.destroy({
|
|||
|
|
where: { menuId: id }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await logOperation({
|
|||
|
|
userId: req.user.id,
|
|||
|
|
username: req.user.username,
|
|||
|
|
module: '菜单管理',
|
|||
|
|
action: '删除',
|
|||
|
|
description: `删除菜单: ${menu.name}`,
|
|||
|
|
method: req.method,
|
|||
|
|
path: req.path,
|
|||
|
|
ip: req.ip,
|
|||
|
|
status: 'success'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '删除成功'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('删除菜单失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
code: 500,
|
|||
|
|
message: '删除菜单失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 获取角色的菜单权限
|
|||
|
|
exports.getRoleMenus = async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { roleId } = req.params;
|
|||
|
|
|
|||
|
|
const role = await Menu.findOne({
|
|||
|
|
where: { id: roleId, isDeleted: false }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!role) {
|
|||
|
|
return res.status(404).json({
|
|||
|
|
code: 404,
|
|||
|
|
message: '角色不存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const Role = require('../models/Role');
|
|||
|
|
const roleData = await Role.findByPk(roleId, {
|
|||
|
|
include: [{
|
|||
|
|
model: Menu,
|
|||
|
|
as: 'menus',
|
|||
|
|
where: { isDeleted: false },
|
|||
|
|
through: { attributes: [] }
|
|||
|
|
}]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const menus = roleData ? roleData.menus : [];
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '获取成功',
|
|||
|
|
data: menus
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取角色菜单失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
code: 500,
|
|||
|
|
message: '获取角色菜单失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 分配菜单权限给角色
|
|||
|
|
exports.assignMenusToRole = async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { roleId } = req.params;
|
|||
|
|
const { menuIds } = req.body;
|
|||
|
|
|
|||
|
|
if (!Array.isArray(menuIds)) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
code: 400,
|
|||
|
|
message: '菜单ID必须是数组'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const Role = require('../models/Role');
|
|||
|
|
const role = await Role.findByPk(roleId);
|
|||
|
|
|
|||
|
|
if (!role) {
|
|||
|
|
return res.status(404).json({
|
|||
|
|
code: 404,
|
|||
|
|
message: '角色不存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await RoleMenu.destroy({
|
|||
|
|
where: { roleId }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (menuIds.length > 0) {
|
|||
|
|
const roleMenus = menuIds.map(menuId => ({
|
|||
|
|
roleId,
|
|||
|
|
menuId
|
|||
|
|
}));
|
|||
|
|
await RoleMenu.bulkCreate(roleMenus);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await logOperation({
|
|||
|
|
userId: req.user.id,
|
|||
|
|
username: req.user.username,
|
|||
|
|
module: '菜单管理',
|
|||
|
|
action: '分配权限',
|
|||
|
|
description: `为角色 ${role.name} 分配菜单权限`,
|
|||
|
|
method: req.method,
|
|||
|
|
path: req.path,
|
|||
|
|
ip: req.ip,
|
|||
|
|
status: 'success'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '分配成功'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('分配菜单权限失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
code: 500,
|
|||
|
|
message: '分配菜单权限失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 获取当前用户的菜单权限
|
|||
|
|
exports.getUserMenus = async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const userId = req.user.id;
|
|||
|
|
const roleId = req.user.roleId;
|
|||
|
|
|
|||
|
|
// 获取用户角色
|
|||
|
|
const User = require('../models/User');
|
|||
|
|
const user = await User.findByPk(userId, {
|
|||
|
|
include: [{
|
|||
|
|
model: require('../models/Role'),
|
|||
|
|
as: 'role'
|
|||
|
|
}]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 超级管理员(isSuperAdmin为1或角色code为admin)返回所有菜单
|
|||
|
|
if (user.isSuperAdmin === 1 || (user.role && user.role.code === 'admin')) {
|
|||
|
|
// 先获取所有可见的菜单
|
|||
|
|
const menuList = await Menu.findAll({
|
|||
|
|
where: {
|
|||
|
|
isDeleted: false,
|
|||
|
|
status: 'active',
|
|||
|
|
visible: 'show'
|
|||
|
|
},
|
|||
|
|
order: [['sort', 'ASC']]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 再获取所有按钮
|
|||
|
|
const buttonList = await Menu.findAll({
|
|||
|
|
where: {
|
|||
|
|
isDeleted: false,
|
|||
|
|
status: 'active',
|
|||
|
|
type: 'button'
|
|||
|
|
},
|
|||
|
|
order: [['sort', 'ASC']]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 合并菜单和按钮
|
|||
|
|
const allMenus = [...menuList, ...buttonList];
|
|||
|
|
const menuTree = buildTree(allMenus);
|
|||
|
|
|
|||
|
|
return res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '获取成功',
|
|||
|
|
data: menuTree
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!user.role) {
|
|||
|
|
return res.status(403).json({
|
|||
|
|
code: 403,
|
|||
|
|
message: '用户没有分配角色'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 普通用户返回角色分配的菜单
|
|||
|
|
const Role = require('../models/Role');
|
|||
|
|
const roleData = await Role.findByPk(roleId, {
|
|||
|
|
include: [{
|
|||
|
|
model: Menu,
|
|||
|
|
as: 'menus',
|
|||
|
|
where: {
|
|||
|
|
isDeleted: false,
|
|||
|
|
status: 'active'
|
|||
|
|
},
|
|||
|
|
through: { attributes: [] }
|
|||
|
|
}]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!roleData || !roleData.menus) {
|
|||
|
|
return res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '获取成功',
|
|||
|
|
data: []
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按sort排序
|
|||
|
|
const menus = roleData.menus.sort((a, b) => a.sort - b.sort);
|
|||
|
|
|
|||
|
|
// 构建菜单树
|
|||
|
|
const menuTree = buildTree(menus);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
code: 200,
|
|||
|
|
message: '获取成功',
|
|||
|
|
data: menuTree
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户菜单失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
code: 500,
|
|||
|
|
message: '获取用户菜单失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 构建菜单树
|
|||
|
|
function buildTree(menus, parentId = null) {
|
|||
|
|
return menus
|
|||
|
|
.filter(menu => menu.parentId === parentId)
|
|||
|
|
.map(menu => ({
|
|||
|
|
...menu.dataValues,
|
|||
|
|
children: buildTree(menus, menu.id)
|
|||
|
|
}));
|
|||
|
|
}
|