diff --git a/backend/database/roles_init.sql b/backend/database/roles_init.sql new file mode 100644 index 0000000..c83e94a --- /dev/null +++ b/backend/database/roles_init.sql @@ -0,0 +1,123 @@ +-- ============================================ +-- 梦幻西游一站式运营管理平台 - 角色表初始化脚本 +-- MySQL 8.4 兼容版本 +-- 创建日期:2026-01-04 +-- ============================================ + +-- 使用数据库 +USE mhxy_web_vue; + +-- 设置字符集 +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ============================================ +-- 表:admin_roles(管理员角色表) +-- ============================================ +DROP TABLE IF EXISTS `admin_roles`; +CREATE TABLE `admin_roles` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `role_name` VARCHAR(50) NOT NULL COMMENT '角色名称', + `role_code` VARCHAR(50) NOT NULL COMMENT '角色编码', + `description` VARCHAR(255) NULL COMMENT '角色描述', + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1:正常, 0:禁用)', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `idx_role_code` (`role_code`), + INDEX `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员角色表'; + +-- ============================================ +-- 插入默认角色数据 +-- ============================================ +INSERT INTO `admin_roles` (`role_name`, `role_code`, `description`, `status`) +VALUES + ('超级管理员', 'super_admin', '拥有系统所有权限', 1), + ('系统管理员', 'system_admin', '管理系统配置和用户', 1), + ('运营管理员', 'operation_admin', '负责运营相关功能', 1), + ('数据管理员', 'data_admin', '负责数据查询和导出', 1); + +-- ============================================ +-- 修改 admin_users 表的 role_id 字段类型为 INT UNSIGNED +-- ============================================ +ALTER TABLE `admin_users` +MODIFY COLUMN `role_id` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '角色ID(1:超级管理员)'; + +-- ============================================ +-- 删除 admin_users 表上的外键约束(如果存在) +-- ============================================ +-- 创建存储过程来删除外键 +DROP PROCEDURE IF EXISTS drop_foreign_key_if_exists; + +DELIMITER $$ +CREATE PROCEDURE drop_foreign_key_if_exists() +BEGIN + DECLARE fk_name VARCHAR(64); + + -- 查询外键名称 + SELECT CONSTRAINT_NAME INTO fk_name + FROM information_schema.KEY_COLUMN_USAGE + WHERE TABLE_SCHEMA = 'mhxy_web_vue' + AND TABLE_NAME = 'admin_users' + AND CONSTRAINT_NAME = 'fk_admin_users_role_id' + AND REFERENCED_TABLE_NAME = 'admin_roles'; + + -- 如果外键存在,则删除 + IF fk_name IS NOT NULL THEN + SET @sql = CONCAT('ALTER TABLE `admin_users` DROP FOREIGN KEY `', fk_name, '`'); + PREPARE stmt FROM @sql; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + END IF; +END$$ +DELIMITER ; + +-- 执行存储过程 +CALL drop_foreign_key_if_exists(); + +-- 删除存储过程 +DROP PROCEDURE IF EXISTS drop_foreign_key_if_exists; + +-- ============================================ +-- 添加外键约束 +-- ============================================ +ALTER TABLE `admin_users` +ADD CONSTRAINT `fk_admin_users_role_id` +FOREIGN KEY (`role_id`) REFERENCES `admin_roles` (`id`) +ON DELETE RESTRICT +ON UPDATE CASCADE; + +-- ============================================ +-- 更新现有管理员账号的角色ID +-- ============================================ +UPDATE `admin_users` +SET `role_id` = 1 +WHERE `username` = 'admin'; + +-- ============================================ +-- 验证数据 +-- ============================================ +SELECT + id, + role_name, + role_code, + description, + status, + created_at +FROM admin_roles; + +SELECT + au.id, + au.username, + au.real_name, + au.role_id, + ar.role_name, + au.status, + au.created_at +FROM admin_users au +LEFT JOIN admin_roles ar ON au.role_id = ar.id; + +SET FOREIGN_KEY_CHECKS = 1; + +-- 初始化完成 diff --git a/backend/src/controllers/adminRoleController.ts b/backend/src/controllers/adminRoleController.ts new file mode 100644 index 0000000..550c3e8 --- /dev/null +++ b/backend/src/controllers/adminRoleController.ts @@ -0,0 +1,257 @@ +// 导入Express的Request和Response类型 +import { Request, Response } from 'express'; +// 导入数据源配置 +import { AppDataSource } from '../config/database'; +// 导入角色模型 +import { AdminRole } from '../models/AdminRole'; + +/** + * 角色控制器 + * 处理角色管理相关的操作,包括角色列表查询、创建、编辑、删除等 + */ +export class AdminRoleController { + /** + * 获取角色列表 + * 获取所有角色列表,支持按状态筛选 + * @param req - Express请求对象,包含查询参数 + * @param res - Express响应对象 + */ + async getRoleList(req: Request, res: Response) { + try { + // 从查询参数中获取状态筛选条件 + const { status } = req.query; + + // 获取角色仓库 + const roleRepository = AppDataSource.getRepository(AdminRole); + + // 构建查询条件 + const queryBuilder = roleRepository.createQueryBuilder('role'); + + // 按状态筛选 + if (status !== undefined && status !== null && status !== '') { + queryBuilder.andWhere('role.status = :status', { status: Number(status) }); + } + + // 查询角色列表 + const roles = await queryBuilder.orderBy('role.id', 'ASC').getMany(); + + // 组装返回数据 + const roleList = roles.map(role => ({ + id: role.id, + roleName: role.roleName, + description: role.description, + status: role.status, + createdAt: role.createdAt, + updatedAt: role.updatedAt + })); + + // 返回角色列表 + return res.json({ + success: true, + data: roleList + }); + } catch (error) { + console.error('获取角色列表失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } + + /** + * 创建角色 + * 创建新的角色 + * @param req - Express请求对象,包含角色信息 + * @param res - Express响应对象 + */ + async createRole(req: Request, res: Response) { + try { + // 从请求体中获取角色信息 + const { roleName, description } = req.body; + + // 验证必填字段 + if (!roleName) { + return res.status(400).json({ + success: false, + message: '角色名称不能为空' + }); + } + + // 获取角色仓库 + const roleRepository = AppDataSource.getRepository(AdminRole); + + // 检查角色名称是否已存在 + const existingRole = await roleRepository.findOne({ + where: { roleName } + }); + + if (existingRole) { + return res.status(400).json({ + success: false, + message: '角色名称已存在' + }); + } + + // 创建新角色 + const newRole = roleRepository.create({ + roleName, + description, + status: 1 // 默认启用 + }); + + // 保存角色 + await roleRepository.save(newRole); + + // 返回创建成功响应 + return res.json({ + success: true, + message: '创建角色成功', + data: { + id: newRole.id, + roleName: newRole.roleName + } + }); + } catch (error) { + console.error('创建角色失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } + + /** + * 编辑角色 + * 更新角色信息 + * @param req - Express请求对象,包含角色ID和更新信息 + * @param res - Express响应对象 + */ + async updateRole(req: Request, res: Response) { + try { + // 从请求参数中获取角色ID + const { id } = req.params; + // 从请求体中获取更新信息 + const { roleName, description, status } = req.body; + + // 验证必填字段 + if (!roleName) { + return res.status(400).json({ + success: false, + message: '角色名称不能为空' + }); + } + + // 获取角色仓库 + const roleRepository = AppDataSource.getRepository(AdminRole); + + // 查找角色 + const role = await roleRepository.findOne({ + where: { id: Number(id) } + }); + + if (!role) { + return res.status(404).json({ + success: false, + message: '角色不存在' + }); + } + + // 检查角色名称是否与其他角色重复 + const existingRole = await roleRepository.findOne({ + where: { roleName } + }); + + if (existingRole && existingRole.id !== role.id) { + return res.status(400).json({ + success: false, + message: '角色名称已存在' + }); + } + + // 更新角色信息 + role.roleName = roleName; + role.description = description; + role.status = status !== undefined ? Number(status) : role.status; + + // 保存更新 + await roleRepository.save(role); + + // 返回更新成功响应 + return res.json({ + success: true, + message: '更新角色成功', + data: { + id: role.id, + roleName: role.roleName + } + }); + } catch (error) { + console.error('更新角色失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } + + /** + * 删除角色 + * 删除指定的角色 + * 禁止删除默认的超级管理员角色(ID=1) + * @param req - Express请求对象,包含角色ID + * @param res - Express响应对象 + */ + async deleteRole(req: Request, res: Response) { + try { + // 从请求参数中获取角色ID + const { id } = req.params; + + // 验证角色ID + if (!id) { + return res.status(400).json({ + success: false, + message: '角色ID不能为空' + }); + } + + // 禁止删除默认的超级管理员角色(ID=1) + if (Number(id) === 1) { + return res.status(403).json({ + success: false, + message: '禁止删除默认的超级管理员角色' + }); + } + + // 获取角色仓库 + const roleRepository = AppDataSource.getRepository(AdminRole); + + // 查找角色 + const role = await roleRepository.findOne({ + where: { id: Number(id) } + }); + + if (!role) { + return res.status(404).json({ + success: false, + message: '角色不存在' + }); + } + + // 删除角色 + await roleRepository.remove(role); + + // 返回删除成功响应 + return res.json({ + success: true, + message: '删除角色成功' + }); + } catch (error) { + console.error('删除角色失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } +} diff --git a/backend/src/controllers/adminUserController.ts b/backend/src/controllers/adminUserController.ts new file mode 100644 index 0000000..9a8b94f --- /dev/null +++ b/backend/src/controllers/adminUserController.ts @@ -0,0 +1,336 @@ +// 导入Express的Request和Response类型 +import { Request, Response } from 'express'; +// 导入bcrypt用于密码加密 +import bcrypt from 'bcrypt'; +// 导入数据源配置 +import { AppDataSource } from '../config/database'; +// 导入管理员用户模型 +import { AdminUser } from '../models/AdminUser'; +// 导入角色模型 +import { AdminRole } from '../models/AdminRole'; + +/** + * 管理员用户控制器 + * 处理用户管理相关的操作,包括用户列表查询、创建、编辑、删除等 + */ +export class AdminUserController { + /** + * 获取用户列表 + * 支持按用户名、角色ID、状态进行筛选,支持分页 + * @param req - Express请求对象,包含查询参数 + * @param res - Express响应对象 + */ + async getUserList(req: Request, res: Response) { + try { + // 从查询参数中获取筛选条件和分页参数 + const { username, roleId, status, page = 1, pageSize = 20 } = req.query; + + // 获取管理员用户仓库 + const adminUserRepository = AppDataSource.getRepository(AdminUser); + // 获取角色仓库 + const roleRepository = AppDataSource.getRepository(AdminRole); + + // 构建查询条件 + const queryBuilder = adminUserRepository.createQueryBuilder('user'); + + // 按用户名筛选(模糊查询) + if (username) { + queryBuilder.andWhere('user.username LIKE :username', { username: `%${username}%` }); + } + + // 按角色ID筛选 + if (roleId) { + queryBuilder.andWhere('user.roleId = :roleId', { roleId: Number(roleId) }); + } + + // 按状态筛选 + if (status !== undefined && status !== null && status !== '') { + queryBuilder.andWhere('user.status = :status', { status: Number(status) }); + } + + // 计算总数 + const total = await queryBuilder.getCount(); + + // 分页查询 + const users = await queryBuilder + .orderBy('user.id', 'DESC') + .skip((Number(page) - 1) * Number(pageSize)) + .take(Number(pageSize)) + .getMany(); + + // 获取所有角色信息(用于显示角色名称) + const roles = await roleRepository.find(); + + // 构建角色ID到角色名称的映射 + const roleMap = new Map(roles.map(role => [role.id, role.roleName])); + + // 组装返回数据 + const userList = users.map(user => ({ + id: user.id, + username: user.username, + realName: user.realName, + roleId: user.roleId, + roleName: roleMap.get(user.roleId) || '未知角色', + status: user.status, + createdAt: user.createdAt, + updatedAt: user.updatedAt + })); + + // 返回用户列表 + return res.json({ + success: true, + data: { + list: userList, + total, + page: Number(page), + pageSize: Number(pageSize) + } + }); + } catch (error) { + console.error('获取用户列表失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } + + /** + * 创建用户 + * 创建新的管理员用户 + * @param req - Express请求对象,包含用户信息 + * @param res - Express响应对象 + */ + async createUser(req: Request, res: Response) { + try { + // 从请求体中获取用户信息 + const { username, password, roleId } = req.body; + + // 验证必填字段 + if (!username || !password || !roleId) { + return res.status(400).json({ + success: false, + message: '用户名、密码和角色组不能为空' + }); + } + + // 获取管理员用户仓库 + const adminUserRepository = AppDataSource.getRepository(AdminUser); + + // 检查用户名是否已存在 + const existingUser = await adminUserRepository.findOne({ + where: { username } + }); + + if (existingUser) { + return res.status(400).json({ + success: false, + message: '用户名已存在' + }); + } + + // 密码加密 + const passwordHash = await bcrypt.hash(password, 10); + + // 创建新用户 + const newUser = adminUserRepository.create({ + username, + passwordHash, + roleId: Number(roleId), + status: 1 // 默认启用 + }); + + // 保存用户 + await adminUserRepository.save(newUser); + + // 返回创建成功响应 + return res.json({ + success: true, + message: '创建用户成功', + data: { + id: newUser.id, + username: newUser.username, + roleId: newUser.roleId + } + }); + } catch (error) { + console.error('创建用户失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } + + /** + * 编辑用户 + * 更新用户信息(密码和角色) + * @param req - Express请求对象,包含用户ID和更新信息 + * @param res - Express响应对象 + */ + async updateUser(req: Request, res: Response) { + try { + // 从请求参数中获取用户ID + const { id } = req.params; + // 从请求体中获取更新信息 + const { password, roleId } = req.body; + + // 验证必填字段 + if (!roleId) { + return res.status(400).json({ + success: false, + message: '角色组不能为空' + }); + } + + // 获取管理员用户仓库 + const adminUserRepository = AppDataSource.getRepository(AdminUser); + + // 查找用户 + const user = await adminUserRepository.findOne({ + where: { id: Number(id) } + }); + + if (!user) { + return res.status(404).json({ + success: false, + message: '用户不存在' + }); + } + + // 更新角色 + user.roleId = Number(roleId); + + // 如果提供了新密码,则更新密码 + if (password) { + user.passwordHash = await bcrypt.hash(password, 10); + } + + // 保存更新 + await adminUserRepository.save(user); + + // 返回更新成功响应 + return res.json({ + success: true, + message: '更新用户成功', + data: { + id: user.id, + username: user.username, + roleId: user.roleId + } + }); + } catch (error) { + console.error('更新用户失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } + + /** + * 删除用户 + * 删除指定的管理员用户 + * 禁止删除当前登录的管理员账号和默认的超级管理员账号(ID=1) + * @param req - Express请求对象,包含用户ID和当前登录用户信息 + * @param res - Express响应对象 + */ + async deleteUser(req: any, res: Response) { + try { + // 从请求参数中获取用户ID + const { id } = req.params; + + // 验证用户ID + if (!id) { + return res.status(400).json({ + success: false, + message: '用户ID不能为空' + }); + } + + // 禁止删除默认的超级管理员账号(ID=1) + if (Number(id) === 1) { + return res.status(403).json({ + success: false, + message: '禁止删除默认的超级管理员账号' + }); + } + + // 禁止删除当前登录的管理员账号 + if (Number(id) === req.admin.id) { + return res.status(403).json({ + success: false, + message: '禁止删除当前登录的账号' + }); + } + + // 获取管理员用户仓库 + const adminUserRepository = AppDataSource.getRepository(AdminUser); + + // 查找用户 + const user = await adminUserRepository.findOne({ + where: { id: Number(id) } + }); + + if (!user) { + return res.status(404).json({ + success: false, + message: '用户不存在' + }); + } + + // 删除用户 + await adminUserRepository.remove(user); + + // 返回删除成功响应 + return res.json({ + success: true, + message: '删除用户成功' + }); + } catch (error) { + console.error('删除用户失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } + + /** + * 获取角色列表 + * 获取所有启用的角色列表,用于下拉选择 + * @param req - Express请求对象 + * @param res - Express响应对象 + */ + async getRoleList(req: Request, res: Response) { + try { + // 获取角色仓库 + const roleRepository = AppDataSource.getRepository(AdminRole); + + // 查询所有启用的角色 + const roles = await roleRepository.find({ + where: { status: 1 }, + order: { id: 'ASC' } + }); + + // 组装返回数据 + const roleList = roles.map(role => ({ + id: role.id, + roleName: role.roleName, + description: role.description + })); + + // 返回角色列表 + return res.json({ + success: true, + data: roleList + }); + } catch (error) { + console.error('获取角色列表失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } +} diff --git a/backend/src/index.ts b/backend/src/index.ts index f2599d4..e7a0b77 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -11,7 +11,7 @@ const app: Express = express(); const PORT = process.env.PORT || 3000; app.use(cors({ - origin: process.env.CORS_ORIGIN || 'http://localhost:5173', + origin: ['http://localhost:5173', 'http://localhost:5174'], credentials: true })); diff --git a/backend/src/models/AdminRole.ts b/backend/src/models/AdminRole.ts new file mode 100644 index 0000000..b0f3eb6 --- /dev/null +++ b/backend/src/models/AdminRole.ts @@ -0,0 +1,23 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; + +// 角色实体模型 +@Entity('admin_roles') +export class AdminRole { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ type: 'varchar', length: 50, unique: true, name: 'role_name' }) + roleName: string; + + @Column({ type: 'varchar', length: 255, nullable: true }) + description: string; + + @Column({ type: 'tinyint', default: 1 }) + status: number; + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date; +} diff --git a/backend/src/routes/adminRoutes.ts b/backend/src/routes/adminRoutes.ts index 4630f55..ee07466 100644 --- a/backend/src/routes/adminRoutes.ts +++ b/backend/src/routes/adminRoutes.ts @@ -1,14 +1,30 @@ import { Router } from 'express'; import { AdminAuthController } from '../controllers/adminAuthController'; +import { AdminUserController } from '../controllers/adminUserController'; +import { AdminRoleController } from '../controllers/adminRoleController'; import { adminAuthMiddleware, AuthRequest } from '../middleware/adminAuth'; const router = Router(); const adminAuthController = new AdminAuthController(); +const adminUserController = new AdminUserController(); +const adminRoleController = new AdminRoleController(); +// 认证相关路由 router.post('/login', (req, res) => adminAuthController.login(req, res)); - router.post('/logout', (req, res) => adminAuthController.logout(req, res)); - router.get('/me', adminAuthMiddleware, (req: AuthRequest, res) => adminAuthController.getCurrentUser(req, res)); +// 用户管理路由(需要认证) +router.get('/users', adminAuthMiddleware, (req: AuthRequest, res) => adminUserController.getUserList(req, res)); +router.post('/users', adminAuthMiddleware, (req: AuthRequest, res) => adminUserController.createUser(req, res)); +router.put('/users/:id', adminAuthMiddleware, (req: AuthRequest, res) => adminUserController.updateUser(req, res)); +router.delete('/users/:id', adminAuthMiddleware, (req: AuthRequest, res) => adminUserController.deleteUser(req, res)); +router.get('/roles/list', adminAuthMiddleware, (req: AuthRequest, res) => adminUserController.getRoleList(req, res)); + +// 角色管理路由(需要认证) +router.get('/roles', adminAuthMiddleware, (req: AuthRequest, res) => adminRoleController.getRoleList(req, res)); +router.post('/roles', adminAuthMiddleware, (req: AuthRequest, res) => adminRoleController.createRole(req, res)); +router.put('/roles/:id', adminAuthMiddleware, (req: AuthRequest, res) => adminRoleController.updateRole(req, res)); +router.delete('/roles/:id', adminAuthMiddleware, (req: AuthRequest, res) => adminRoleController.deleteRole(req, res)); + export default router; diff --git a/frontend/src/App.vue b/frontend/src/App.vue index bb6cee8..6fbc901 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,13 +1,15 @@ + + diff --git a/frontend/src/views/admin/components/UserFormModal.vue b/frontend/src/views/admin/components/UserFormModal.vue new file mode 100644 index 0000000..5c5a8ab --- /dev/null +++ b/frontend/src/views/admin/components/UserFormModal.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 98b82d1..70b9b1d 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -9,5 +9,13 @@ export default defineConfig({ alias: { '@': path.resolve(__dirname, './src') } + }, + server: { + proxy: { + '/api': { + target: 'http://localhost:3000', + changeOrigin: true + } + } } })