rentease-backend-new/controllers/menuController.js

479 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { Op } = require('sequelize');
const Menu = require('../models/Menu');
const RoleMenu = require('../models/RoleMenu');
const { logOperation } = require('../utils/logger');
const response = require('../utils/response');
// 格式化日期时间(年月日时分秒)
const formatDateTime = (date) => {
if (!date) return null;
const dateObj = date instanceof Date ? date : new Date(date);
if (isNaN(dateObj.getTime())) return null;
const beijingDate = new Date(dateObj.getTime() + 8 * 60 * 60 * 1000);
return beijingDate.toISOString().replace('T', ' ').slice(0, 19);
};
// 格式化菜单数据递归处理children
const formatMenuData = (menu) => {
const data = menu.toJSON ? menu.toJSON() : menu;
const result = {
...data,
createTime: formatDateTime(data.createTime),
updateTime: formatDateTime(data.updateTime)
};
if (data.children && Array.isArray(data.children)) {
result.children = data.children.map(formatMenuData);
}
return result;
};
// 获取菜单树
exports.getMenuTree = async (req, res) => {
try {
const { type, status } = req.query;
const userType = req.user.userType;
const where = { isDeleted: false };
if (type) {
where.type = type;
}
if (status) {
where.status = status;
}
// 普通管理员只能获取基础菜单
if (userType === 'tenant_admin') {
where.isBasic = 1;
}
const menus = await Menu.findAll({
where,
order: [['sort', 'ASC'], ['createTime', 'ASC']]
});
const menuTree = buildTree(menus);
response.success(res, '获取成功', menuTree);
} catch (error) {
console.error('获取菜单树失败:', error);
response.serverError(res, '获取菜单树失败', error);
}
};
// 获取菜单列表(平铺)
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'], ['createTime', 'ASC']],
offset: (page - 1) * pageSize,
limit: parseInt(pageSize)
});
response.success(res, '获取成功', {
list: rows.map(formatMenuData),
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
});
} catch (error) {
console.error('获取菜单列表失败:', error);
response.serverError(res, '获取菜单列表失败', error);
}
};
// 获取菜单详情
exports.getMenuById = async (req, res) => {
try {
const { id } = req.params;
const menu = await Menu.findByPk(id, {
where: { isDeleted: false }
});
if (!menu) {
return response.notFound(res, '菜单不存在');
}
response.success(res, '获取成功', formatMenuData(menu));
} catch (error) {
console.error('获取菜单详情失败:', error);
response.serverError(res, '获取菜单详情失败', error);
}
};
// 创建菜单
exports.createMenu = async (req, res) => {
try {
const { parentId, name, code, type, path, component, icon, sort, visible, status } = req.body;
if (!name || !code || !type) {
return response.badRequest(res, '菜单名称、编码和类型不能为空');
}
if (type === 'menu' && !path) {
return response.badRequest(res, '菜单类型必须填写路由路径');
}
const existingMenu = await Menu.findOne({
where: { code, isDeleted: false }
});
if (existingMenu) {
return response.badRequest(res, '菜单编码已存在');
}
if (parentId) {
const parentMenu = await Menu.findByPk(parentId, {
where: { isDeleted: false }
});
if (!parentMenu) {
return response.badRequest(res, '父菜单不存在');
}
}
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'
});
response.success(res, '创建成功', formatMenuData(menu));
} catch (error) {
console.error('创建菜单失败:', error);
response.serverError(res, '创建菜单失败', error);
}
};
// 更新菜单
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 response.notFound(res, '菜单不存在');
}
if (code && code !== menu.code) {
const existingMenu = await Menu.findOne({
where: { code, isDeleted: false, id: { [Op.ne]: id } }
});
if (existingMenu) {
return response.badRequest(res, '菜单编码已存在');
}
}
if (parentId) {
if (parseInt(parentId) === parseInt(id)) {
return response.badRequest(res, '不能将菜单设置为自己的子菜单');
}
const parentMenu = await Menu.findByPk(parentId, {
where: { isDeleted: false }
});
if (!parentMenu) {
return response.badRequest(res, '父菜单不存在');
}
}
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'
});
response.success(res, '更新成功', formatMenuData(menu));
} catch (error) {
console.error('更新菜单失败:', error);
response.serverError(res, '更新菜单失败', error);
}
};
// 删除菜单
exports.deleteMenu = async (req, res) => {
try {
const { id } = req.params;
const menu = await Menu.findByPk(id, {
where: { isDeleted: false }
});
if (!menu) {
return response.notFound(res, '菜单不存在');
}
const children = await Menu.findAll({
where: { parentId: id, isDeleted: false }
});
if (children.length > 0) {
return response.badRequest(res, '该菜单下有子菜单,无法删除');
}
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'
});
response.success(res, '删除成功');
} catch (error) {
console.error('删除菜单失败:', error);
response.serverError(res, '删除菜单失败', error);
}
};
// 获取角色的菜单权限
exports.getRoleMenus = async (req, res) => {
try {
const { roleId } = req.params;
const Role = require('../models/Role');
// 先检查角色是否存在
const role = await Role.findOne({
where: { id: roleId, isDeleted: 0 }
});
if (!role) {
return response.notFound(res, '角色不存在');
}
// 获取角色的菜单权限
const roleData = await Role.findByPk(roleId, {
include: [{
model: Menu,
as: 'menus',
where: { isDeleted: 0 },
required: false,
through: { attributes: [] }
}]
});
const menus = roleData ? roleData.menus : [];
response.success(res, '获取成功', menus.map(formatMenuData));
} catch (error) {
console.error('获取角色菜单失败:', error);
response.serverError(res, '获取角色菜单失败', error);
}
};
// 分配菜单权限给角色
exports.assignMenusToRole = async (req, res) => {
try {
const { roleId } = req.params;
const { menuIds } = req.body;
if (!Array.isArray(menuIds)) {
return response.badRequest(res, '菜单ID必须是数组');
}
const Role = require('../models/Role');
const role = await Role.findByPk(roleId);
if (!role) {
return response.notFound(res, '角色不存在');
}
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'
});
response.success(res, '分配成功');
} catch (error) {
console.error('分配菜单权限失败:', error);
response.serverError(res, '分配菜单权限失败', error);
}
};
// 获取当前用户的菜单权限
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'
}]
});
// 系统管理员或租户管理员返回所有菜单
if (user.userType === 'super_admin' || user.userType === 'tenant_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 response.success(res, '获取成功', menuTree);
}
if (!user.role) {
return response.forbidden(res, '用户没有分配角色');
}
// 普通用户返回角色分配的菜单
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 response.success(res, '获取成功', []);
}
// 按sort排序
const menus = roleData.menus.sort((a, b) => a.sort - b.sort);
// 构建菜单树
const menuTree = buildTree(menus);
response.success(res, '获取成功', menuTree);
} catch (error) {
console.error('获取用户菜单失败:', error);
response.serverError(res, '获取用户菜单失败', error);
}
};
// 构建菜单树
function buildTree(menus, parentId = null) {
return menus
.filter(menu => menu.parentId === parentId)
.map(menu => {
// 获取菜单的原始数据
const menuData = formatMenuData(menu);
return {
...menuData,
children: buildTree(menus, menu.id)
};
});
}