新增用户管理页面
This commit is contained in:
123
backend/database/roles_init.sql
Normal file
123
backend/database/roles_init.sql
Normal file
@@ -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;
|
||||||
|
|
||||||
|
-- 初始化完成
|
||||||
257
backend/src/controllers/adminRoleController.ts
Normal file
257
backend/src/controllers/adminRoleController.ts
Normal file
@@ -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: '服务器内部错误'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
336
backend/src/controllers/adminUserController.ts
Normal file
336
backend/src/controllers/adminUserController.ts
Normal file
@@ -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: '服务器内部错误'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ const app: Express = express();
|
|||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
|
origin: ['http://localhost:5173', 'http://localhost:5174'],
|
||||||
credentials: true
|
credentials: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
23
backend/src/models/AdminRole.ts
Normal file
23
backend/src/models/AdminRole.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,14 +1,30 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { AdminAuthController } from '../controllers/adminAuthController';
|
import { AdminAuthController } from '../controllers/adminAuthController';
|
||||||
|
import { AdminUserController } from '../controllers/adminUserController';
|
||||||
|
import { AdminRoleController } from '../controllers/adminRoleController';
|
||||||
import { adminAuthMiddleware, AuthRequest } from '../middleware/adminAuth';
|
import { adminAuthMiddleware, AuthRequest } from '../middleware/adminAuth';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
const adminAuthController = new AdminAuthController();
|
const adminAuthController = new AdminAuthController();
|
||||||
|
const adminUserController = new AdminUserController();
|
||||||
|
const adminRoleController = new AdminRoleController();
|
||||||
|
|
||||||
|
// 认证相关路由
|
||||||
router.post('/login', (req, res) => adminAuthController.login(req, res));
|
router.post('/login', (req, res) => adminAuthController.login(req, res));
|
||||||
|
|
||||||
router.post('/logout', (req, res) => adminAuthController.logout(req, res));
|
router.post('/logout', (req, res) => adminAuthController.logout(req, res));
|
||||||
|
|
||||||
router.get('/me', adminAuthMiddleware, (req: AuthRequest, res) => adminAuthController.getCurrentUser(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;
|
export default router;
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-config-provider :theme="theme" :theme-overrides="themeOverrides" :locale="zhCN" :date-locale="dateZhCN">
|
<n-config-provider :theme="theme" :theme-overrides="themeOverrides" :locale="zhCN" :date-locale="dateZhCN">
|
||||||
<n-message-provider>
|
<n-message-provider>
|
||||||
<router-view />
|
<n-dialog-provider>
|
||||||
|
<router-view />
|
||||||
|
</n-dialog-provider>
|
||||||
</n-message-provider>
|
</n-message-provider>
|
||||||
</n-config-provider>
|
</n-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NConfigProvider, NMessageProvider, darkTheme, zhCN, dateZhCN } from 'naive-ui'
|
import { NConfigProvider, NMessageProvider, NDialogProvider, zhCN, dateZhCN } from 'naive-ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
// 主题配置,默认使用亮色主题
|
// 主题配置,默认使用亮色主题
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
|
import type { ApiResponse, LoginResponseData, UserInfo } from '@/types/api'
|
||||||
|
|
||||||
export const login = (username: string, password: string) => {
|
export const login = (username: string, password: string): Promise<ApiResponse<LoginResponseData>> => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/admin/login',
|
url: '/api/admin/login',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@@ -8,7 +9,7 @@ export const login = (username: string, password: string) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCurrentUser = () => {
|
export const getCurrentUser = (): Promise<ApiResponse<UserInfo>> => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/admin/me',
|
url: '/api/admin/me',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
|
|||||||
@@ -48,12 +48,14 @@ import { ref, h } from 'vue'
|
|||||||
import { NLayout, NLayoutSider, NLayoutHeader, NLayoutContent, NLayoutFooter, NMenu, NDropdown } from 'naive-ui'
|
import { NLayout, NLayoutSider, NLayoutHeader, NLayoutContent, NLayoutFooter, NMenu, NDropdown } from 'naive-ui'
|
||||||
import { RouterLink, useRoute, useRouter } from 'vue-router'
|
import { RouterLink, useRoute, useRouter } from 'vue-router'
|
||||||
import { useAdminStore } from '@/stores/admin'
|
import { useAdminStore } from '@/stores/admin'
|
||||||
import { RiDashboardLine, RiArrowDownSLine, RiUserLine, RiLogoutBoxRLine } from '@remixicon/vue'
|
import { RiDashboardLine, RiArrowDownSLine, RiUserLine, RiLogoutBoxRLine, RiSettings3Line, RiUserSettingsLine } from '@remixicon/vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const adminStore = useAdminStore()
|
const adminStore = useAdminStore()
|
||||||
|
|
||||||
|
console.log('AdminLayout - adminStore.userInfo:', adminStore.userInfo)
|
||||||
|
|
||||||
const activeKey = ref(String(route.name))
|
const activeKey = ref(String(route.name))
|
||||||
|
|
||||||
const menuOptions = [
|
const menuOptions = [
|
||||||
@@ -61,6 +63,18 @@ const menuOptions = [
|
|||||||
label: () => h(RouterLink, { to: '/admin/dashboard' }, { default: () => '工作台' }),
|
label: () => h(RouterLink, { to: '/admin/dashboard' }, { default: () => '工作台' }),
|
||||||
key: 'AdminDashboard',
|
key: 'AdminDashboard',
|
||||||
icon: () => h(RiDashboardLine, { size: '20px' })
|
icon: () => h(RiDashboardLine, { size: '20px' })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '系统管理',
|
||||||
|
key: 'SystemManagement',
|
||||||
|
icon: () => h(RiSettings3Line, { size: '20px' }),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: () => h(RouterLink, { to: '/admin/user-management' }, { default: () => '用户管理' }),
|
||||||
|
key: 'UserManagement',
|
||||||
|
icon: () => h(RiUserSettingsLine, { size: '20px' })
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,19 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: '/admin',
|
path: '/admin',
|
||||||
component: () => import('@/layouts/AdminLayout.vue'),
|
component: () => import('@/layouts/AdminLayout.vue'),
|
||||||
|
redirect: '/admin/dashboard',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
name: 'AdminDashboard',
|
name: 'AdminDashboard',
|
||||||
component: () => import('@/views/admin/Dashboard.vue'),
|
component: () => import('@/views/admin/Dashboard.vue'),
|
||||||
meta: { title: '管理控制台', requiresAdminAuth: true }
|
meta: { title: '管理控制台', requiresAdminAuth: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'user-management',
|
||||||
|
name: 'UserManagement',
|
||||||
|
component: () => import('@/views/admin/UserManagement.vue'),
|
||||||
|
meta: { title: '用户管理', requiresAdminAuth: true }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { login as loginApi } from '@/api/admin'
|
import { login as loginApi } from '@/api/admin'
|
||||||
|
import type { UserInfo } from '@/types/api'
|
||||||
|
|
||||||
export const useAdminStore = defineStore('admin', () => {
|
export const useAdminStore = defineStore('admin', () => {
|
||||||
const token = ref<string | null>(localStorage.getItem('admin_token'))
|
const token = ref<string | null>(localStorage.getItem('admin_token'))
|
||||||
const userInfo = ref<any>(JSON.parse(localStorage.getItem('admin_userInfo') || 'null'))
|
const userInfo = ref<UserInfo | null>(JSON.parse(localStorage.getItem('admin_userInfo') || 'null'))
|
||||||
|
|
||||||
const setToken = (newToken: string) => {
|
const setToken = (newToken: string) => {
|
||||||
token.value = newToken
|
token.value = newToken
|
||||||
localStorage.setItem('admin_token', newToken)
|
localStorage.setItem('admin_token', newToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setUserInfo = (info: any) => {
|
const setUserInfo = (info: UserInfo) => {
|
||||||
userInfo.value = info
|
userInfo.value = info
|
||||||
localStorage.setItem('admin_userInfo', JSON.stringify(info))
|
localStorage.setItem('admin_userInfo', JSON.stringify(info))
|
||||||
}
|
}
|
||||||
@@ -28,11 +29,14 @@ export const useAdminStore = defineStore('admin', () => {
|
|||||||
|
|
||||||
const login = async (username: string, password: string) => {
|
const login = async (username: string, password: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await loginApi(username, password)
|
const data = await loginApi(username, password)
|
||||||
const data = response.data
|
console.log('登录API返回的数据:', data)
|
||||||
if (data.success && data.data) {
|
if (data.success && data.data) {
|
||||||
|
console.log('设置token:', data.data.token)
|
||||||
|
console.log('设置用户信息:', data.data.user)
|
||||||
setToken(data.data.token)
|
setToken(data.data.token)
|
||||||
setUserInfo(data.data.user)
|
setUserInfo(data.data.user)
|
||||||
|
console.log('登录后userInfo:', userInfo.value)
|
||||||
return { success: true, message: data.message || '登录成功' }
|
return { success: true, message: data.message || '登录成功' }
|
||||||
}
|
}
|
||||||
return { success: false, message: data.message || '登录失败' }
|
return { success: false, message: data.message || '登录失败' }
|
||||||
|
|||||||
35
frontend/src/types/api.ts
Normal file
35
frontend/src/types/api.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* API响应类型定义
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用API响应接口
|
||||||
|
*/
|
||||||
|
export interface ApiResponse<T = any> {
|
||||||
|
success: boolean
|
||||||
|
message?: string
|
||||||
|
data?: T
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录响应数据
|
||||||
|
*/
|
||||||
|
export interface LoginResponseData {
|
||||||
|
token: string
|
||||||
|
user: {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
realName?: string
|
||||||
|
roleId: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息
|
||||||
|
*/
|
||||||
|
export interface UserInfo {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
realName?: string
|
||||||
|
roleId: number
|
||||||
|
}
|
||||||
326
frontend/src/views/admin/UserManagement.vue
Normal file
326
frontend/src/views/admin/UserManagement.vue
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
<template>
|
||||||
|
<div class="user-management-container">
|
||||||
|
<n-card title="用户管理">
|
||||||
|
<!-- 搜索表单 -->
|
||||||
|
<n-form inline :model="searchForm" label-placement="left" label-width="auto">
|
||||||
|
<n-grid :cols="24" :x-gap="12">
|
||||||
|
<n-gi :span="6">
|
||||||
|
<n-form-item label="用户名">
|
||||||
|
<n-input v-model:value="searchForm.username" placeholder="请输入用户名" clearable />
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi :span="6">
|
||||||
|
<n-form-item label="角色组">
|
||||||
|
<n-select
|
||||||
|
v-model:value="searchForm.roleId"
|
||||||
|
placeholder="请选择角色组"
|
||||||
|
:options="roleOptions"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi :span="6">
|
||||||
|
<n-form-item label="状态">
|
||||||
|
<n-select
|
||||||
|
v-model:value="searchForm.status"
|
||||||
|
placeholder="请选择状态"
|
||||||
|
:options="statusOptions"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi :span="6">
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleSearch">查询</n-button>
|
||||||
|
<n-button @click="handleReset">重置</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-form>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<n-space class="action-buttons" justify="space-between">
|
||||||
|
<n-space>
|
||||||
|
<n-button type="primary" @click="handleCreate">
|
||||||
|
<template #icon>
|
||||||
|
<RiAddLine />
|
||||||
|
</template>
|
||||||
|
新建用户
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="handleImport">
|
||||||
|
<template #icon>
|
||||||
|
<RiUploadLine />
|
||||||
|
</template>
|
||||||
|
导入
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-space>
|
||||||
|
|
||||||
|
<!-- 用户列表表格 -->
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="userList"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
:row-key="(row: any) => row.id"
|
||||||
|
class="user-table"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 新建/编辑用户对话框 -->
|
||||||
|
<UserFormModal
|
||||||
|
v-model:show="showUserFormModal"
|
||||||
|
:user="currentUser"
|
||||||
|
:mode="userFormMode"
|
||||||
|
:roles="roleList"
|
||||||
|
@success="handleUserFormSuccess"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted, h } from 'vue'
|
||||||
|
import { NCard, NForm, NFormItem, NInput, NSelect, NButton, NSpace, NGrid, NGi, NDataTable, useMessage, useDialog } from 'naive-ui'
|
||||||
|
import { RiAddLine, RiUploadLine, RiEditLine, RiDeleteBinLine, RiLockLine, RiLockUnlockLine } from '@remixicon/vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import UserFormModal from './components/UserFormModal.vue'
|
||||||
|
|
||||||
|
// 消息提示
|
||||||
|
const message = useMessage()
|
||||||
|
// 对话框
|
||||||
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
username: '',
|
||||||
|
roleId: null as number | null,
|
||||||
|
status: null as number | null
|
||||||
|
})
|
||||||
|
|
||||||
|
// 角色选项
|
||||||
|
const roleOptions = ref<any[]>([])
|
||||||
|
// 状态选项
|
||||||
|
const statusOptions = [
|
||||||
|
{ label: '启用', value: 1 },
|
||||||
|
{ label: '禁用', value: 0 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 用户列表
|
||||||
|
const userList = ref<any[]>([])
|
||||||
|
// 角色列表
|
||||||
|
const roleList = ref<any[]>([])
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 20, 30, 50],
|
||||||
|
onChange: (page: number) => {
|
||||||
|
pagination.page = page
|
||||||
|
fetchUserList()
|
||||||
|
},
|
||||||
|
onUpdatePageSize: (pageSize: number) => {
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
pagination.page = 1
|
||||||
|
fetchUserList()
|
||||||
|
},
|
||||||
|
itemCount: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{ title: 'ID', key: 'id', width: 80 },
|
||||||
|
{ title: '用户名', key: 'username', width: 150 },
|
||||||
|
{ title: '角色组', key: 'roleName', width: 150 },
|
||||||
|
{
|
||||||
|
title: '用户状态',
|
||||||
|
key: 'status',
|
||||||
|
width: 100,
|
||||||
|
render: (row: any) => {
|
||||||
|
return h('span', {
|
||||||
|
style: {
|
||||||
|
color: row.status === 1 ? '#18a058' : '#d03050'
|
||||||
|
}
|
||||||
|
}, row.status === 1 ? '启用' : '禁用')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: '创建时间', key: 'createdAt', width: 180 },
|
||||||
|
{ title: '更新时间', key: 'updatedAt', width: 180 },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 150,
|
||||||
|
fixed: 'right' as const,
|
||||||
|
render: (row: any) => {
|
||||||
|
return h(NSpace, { size: 'small' }, () => [
|
||||||
|
h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: 'primary',
|
||||||
|
onClick: () => handleEdit(row)
|
||||||
|
}, { icon: () => h(RiEditLine) }),
|
||||||
|
h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: row.status === 1 ? 'warning' : 'success',
|
||||||
|
onClick: () => handleToggleStatus(row)
|
||||||
|
}, { icon: () => row.status === 1 ? h(RiLockLine) : h(RiLockUnlockLine) }),
|
||||||
|
h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
onClick: () => handleDelete(row)
|
||||||
|
}, { icon: () => h(RiDeleteBinLine) })
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 用户表单对话框
|
||||||
|
const showUserFormModal = ref(false)
|
||||||
|
const userFormMode = ref<'create' | 'edit'>('create')
|
||||||
|
const currentUser = ref<any>(null)
|
||||||
|
|
||||||
|
// 获取用户列表
|
||||||
|
const fetchUserList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/api/admin/users', {
|
||||||
|
params: {
|
||||||
|
username: searchForm.username || undefined,
|
||||||
|
roleId: searchForm.roleId || undefined,
|
||||||
|
status: searchForm.status !== null ? searchForm.status : undefined,
|
||||||
|
page: pagination.page,
|
||||||
|
pageSize: pagination.pageSize
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
userList.value = response.data.data.list
|
||||||
|
pagination.itemCount = response.data.data.total
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取用户列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取角色列表
|
||||||
|
const fetchRoleList = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/api/admin/roles/list')
|
||||||
|
if (response.data.success) {
|
||||||
|
roleList.value = response.data.data
|
||||||
|
roleOptions.value = response.data.data.map((role: any) => ({
|
||||||
|
label: role.roleName,
|
||||||
|
value: role.id
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取角色列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.page = 1
|
||||||
|
fetchUserList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = () => {
|
||||||
|
searchForm.username = ''
|
||||||
|
searchForm.roleId = null
|
||||||
|
searchForm.status = null
|
||||||
|
pagination.page = 1
|
||||||
|
fetchUserList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新建用户
|
||||||
|
const handleCreate = () => {
|
||||||
|
userFormMode.value = 'create'
|
||||||
|
currentUser.value = null
|
||||||
|
showUserFormModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑用户
|
||||||
|
const handleEdit = (row: any) => {
|
||||||
|
userFormMode.value = 'edit'
|
||||||
|
currentUser.value = { ...row }
|
||||||
|
showUserFormModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换用户状态
|
||||||
|
const handleToggleStatus = (row: any) => {
|
||||||
|
const newStatus = row.status === 1 ? 0 : 1
|
||||||
|
dialog.warning({
|
||||||
|
title: '确认操作',
|
||||||
|
content: `确定要${newStatus === 1 ? '启用' : '禁用'}用户 "${row.username}" 吗?`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await axios.put(`/api/admin/users/${row.id}`, {
|
||||||
|
roleId: row.roleId,
|
||||||
|
status: newStatus
|
||||||
|
})
|
||||||
|
message.success(`${newStatus === 1 ? '启用' : '禁用'}成功`)
|
||||||
|
fetchUserList()
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.response?.data?.message || '操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除用户
|
||||||
|
const handleDelete = (row: any) => {
|
||||||
|
dialog.warning({
|
||||||
|
title: '确认删除',
|
||||||
|
content: `确定要删除用户 "${row.username}" 吗?此操作不可恢复。`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await axios.delete(`/api/admin/users/${row.id}`)
|
||||||
|
message.success('删除成功')
|
||||||
|
fetchUserList()
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.response?.data?.message || '删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入(预留)
|
||||||
|
const handleImport = () => {
|
||||||
|
message.info('导入功能开发中')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户表单成功回调
|
||||||
|
const handleUserFormSuccess = () => {
|
||||||
|
showUserFormModal.value = false
|
||||||
|
fetchUserList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时获取数据
|
||||||
|
onMounted(() => {
|
||||||
|
fetchRoleList()
|
||||||
|
fetchUserList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.user-management-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-table {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
199
frontend/src/views/admin/components/UserFormModal.vue
Normal file
199
frontend/src/views/admin/components/UserFormModal.vue
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showModal"
|
||||||
|
preset="card"
|
||||||
|
:title="mode === 'create' ? '新建用户' : '编辑用户'"
|
||||||
|
:style="{ width: '600px' }"
|
||||||
|
:mask-closable="false"
|
||||||
|
@after-leave="handleAfterLeave"
|
||||||
|
>
|
||||||
|
<n-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-placement="left"
|
||||||
|
label-width="100px"
|
||||||
|
require-mark-placement="right-hanging"
|
||||||
|
>
|
||||||
|
<n-form-item label="用户名" path="username">
|
||||||
|
<n-input
|
||||||
|
v-model:value="formData.username"
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
:disabled="mode === 'edit'"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
|
<n-form-item label="密码" path="password">
|
||||||
|
<n-input-group>
|
||||||
|
<n-input
|
||||||
|
v-model:value="formData.password"
|
||||||
|
type="password"
|
||||||
|
show-password-on="click"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
/>
|
||||||
|
<n-button type="primary" @click="generateRandomPassword">
|
||||||
|
生成随机密码
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
|
<n-form-item label="角色组" path="roleId">
|
||||||
|
<n-select
|
||||||
|
v-model:value="formData.roleId"
|
||||||
|
placeholder="请选择角色组"
|
||||||
|
:options="roleOptions"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="handleCancel">取消</n-button>
|
||||||
|
<n-button type="primary" :loading="loading" @click="handleSubmit">
|
||||||
|
确定
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, watch, computed } from 'vue'
|
||||||
|
import { NModal, NForm, NFormItem, NInput, NInputGroup, NSelect, NButton, NSpace, useMessage, type FormInst, type FormRules } from 'naive-ui'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
// 定义props
|
||||||
|
interface Props {
|
||||||
|
show: boolean
|
||||||
|
user: any
|
||||||
|
mode: 'create' | 'edit'
|
||||||
|
roles: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义emits
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:show', value: boolean): void
|
||||||
|
(e: 'success'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
// 消息提示
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
// 表单引用
|
||||||
|
const formRef = ref<FormInst | null>(null)
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 对话框显示状态
|
||||||
|
const showModal = computed({
|
||||||
|
get: () => props.show,
|
||||||
|
set: (value) => emit('update:show', value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = reactive({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
roleId: null as number | null
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const formRules: FormRules = {
|
||||||
|
username: [
|
||||||
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||||
|
{ min: 3, max: 20, message: '用户名长度应为3-20个字符', trigger: 'blur' },
|
||||||
|
{ pattern: /^[a-zA-Z0-9_]+$/, message: '用户名只能包含字母、数字和下划线', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
|
{ min: 6, max: 20, message: '密码长度应为6-20个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
roleId: [
|
||||||
|
{ required: true, message: '请选择角色组', trigger: 'change', type: 'number' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 角色选项
|
||||||
|
const roleOptions = computed(() => {
|
||||||
|
return props.roles.map(role => ({
|
||||||
|
label: role.roleName,
|
||||||
|
value: role.id
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听用户数据变化
|
||||||
|
watch(() => props.user, (newUser) => {
|
||||||
|
if (newUser) {
|
||||||
|
formData.username = newUser.username || ''
|
||||||
|
formData.password = ''
|
||||||
|
formData.roleId = newUser.roleId || null
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// 生成随机密码
|
||||||
|
const generateRandomPassword = () => {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
|
||||||
|
let password = ''
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
password += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||||
|
}
|
||||||
|
formData.password = password
|
||||||
|
message.success('随机密码已生成')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
if (props.mode === 'create') {
|
||||||
|
// 创建用户
|
||||||
|
await axios.post('/api/admin/users', {
|
||||||
|
username: formData.username,
|
||||||
|
password: formData.password,
|
||||||
|
roleId: formData.roleId
|
||||||
|
})
|
||||||
|
message.success('创建用户成功')
|
||||||
|
} else {
|
||||||
|
// 编辑用户
|
||||||
|
await axios.put(`/api/admin/users/${props.user.id}`, {
|
||||||
|
password: formData.password,
|
||||||
|
roleId: formData.roleId
|
||||||
|
})
|
||||||
|
message.success('更新用户成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('success')
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.errors) {
|
||||||
|
// 表单验证错误
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.error(error.response?.data?.message || '操作失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
showModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对话框关闭后重置表单
|
||||||
|
const handleAfterLeave = () => {
|
||||||
|
formData.username = ''
|
||||||
|
formData.password = ''
|
||||||
|
formData.roleId = null
|
||||||
|
formRef.value?.restoreValidation()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -9,5 +9,13 @@ export default defineConfig({
|
|||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, './src')
|
'@': path.resolve(__dirname, './src')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:3000',
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user