serv\web- 多租户修改
This commit is contained in:
@@ -38,7 +38,7 @@ CREATE TABLE `tb_achievement` (
|
||||
KEY `idx_condition_type` (`condition_type`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='成就定义表';
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='成就定义表';
|
||||
|
||||
-- =============================================
|
||||
-- 2. 用户成就表 (tb_user_achievement)
|
||||
@@ -56,7 +56,7 @@ CREATE TABLE `tb_user_achievement` (
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_achievement_id` (`achievement_id`),
|
||||
KEY `idx_obtain_time` (`obtain_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户成就表';
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户成就表';
|
||||
|
||||
-- =============================================
|
||||
-- 3. 用户成就进度表 (tb_user_achievement_progress)
|
||||
@@ -80,7 +80,7 @@ CREATE TABLE `tb_user_achievement_progress` (
|
||||
KEY `idx_achievement_id` (`achievement_id`),
|
||||
KEY `idx_completed` (`completed`),
|
||||
KEY `idx_last_update_time` (`last_update_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户成就进度表';
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户成就进度表';
|
||||
|
||||
-- =============================================
|
||||
-- 初始化成就数据
|
||||
@@ -93,14 +93,6 @@ CREATE TABLE `tb_user_achievement_progress` (
|
||||
-- 学习时长类成就
|
||||
-- =============================================
|
||||
|
||||
INSERT INTO `tb_achievement` (`id`, `achievement_id`, `name`,`icon`, `description`, `type`, `level`, `condition_type`, `condition_value`, `points`, `order_num`, `deleted`) VALUES
|
||||
('ACH001', 'learning_time_l1', '初学者', 'v1-icon.svg', '累计学习时长达到10小时', 1, 1, 1, 10*60*60, 10, 1, 0),
|
||||
('ACH002', 'learning_time_l2', '勤学者', 'v2-icon.svg', '累计学习时长达到50小时', 1, 2, 1, 50*60*60, 50, 2, 0),
|
||||
('ACH003', 'learning_time_l3', '学习达人', 'v3-icon.svg', '累计学习时长达到100小时', 1, 3, 1, 100*60*60, 100, 3, 0),
|
||||
('ACH004', 'learning_time_l4', '学习狂人', 'v4-icon.svg', '累计学习时长达到500小时', 1, 4, 1, 500*60*60, 500, 4, 0),
|
||||
('ACH005', 'learning_time_l5', '学习大师', 'v5-icon.svg', '累计学习时长达到1000小时', 1, 5, 1, 1000*60*60, 1000, 5, 0),
|
||||
('ACH006', 'learning_time_l6', '学习宗师', 'v6-icon.svg', '累计学习时长达到2000小时', 1, 6, 1, 2000*60*60, 2000, 6, 0);
|
||||
|
||||
-- -- =============================================
|
||||
-- -- 课程完成类成就
|
||||
-- -- =============================================
|
||||
|
||||
@@ -56,4 +56,4 @@ CREATE TABLE `tb_crontab_log` (
|
||||
KEY `idx_execute_status` (`execute_status`),
|
||||
KEY `idx_start_time` (`start_time`),
|
||||
KEY `idx_deleted` (`deleted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='定时任务执行日志表';
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务执行日志表';
|
||||
|
||||
@@ -6,6 +6,7 @@ CREATE TABLE `tb_sys_dept` (
|
||||
`dept_id` VARCHAR(50) NOT NULL COMMENT '部门ID',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '部门名称',
|
||||
`parent_id` VARCHAR(50) DEFAULT NULL COMMENT '父部门ID',
|
||||
`dept_path` VARCHAR(500) DEFAULT NULL COMMENT '部门路径,格式:/root_department/dept_001/,用于快速判断父子关系',
|
||||
`description` VARCHAR(255) DEFAULT NULL COMMENT '部门描述',
|
||||
`creator` VARCHAR(50) DEFAULT NULL COMMENT '创建者',
|
||||
`updater` VARCHAR(50) DEFAULT NULL COMMENT '更新者',
|
||||
@@ -15,8 +16,9 @@ CREATE TABLE `tb_sys_dept` (
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_dept_id` (`dept_id`),
|
||||
KEY `idx_dept_parent` (`parent_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
KEY `idx_dept_parent` (`parent_id`),
|
||||
KEY `idx_dept_path` (`dept_path`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='部门表';
|
||||
|
||||
|
||||
-- 角色表
|
||||
@@ -34,7 +36,7 @@ CREATE TABLE `tb_sys_role` (
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_role_id` (`role_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
-- 部门-角色关联
|
||||
@@ -51,7 +53,7 @@ CREATE TABLE `tb_sys_dept_role` (
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_dept_role` (`dept_id`, `role_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
-- 用户-角色关联
|
||||
@@ -69,7 +71,7 @@ CREATE TABLE `tb_sys_user_dept_role` (
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_user_dept_role` (`user_id`, `dept_id`, `role_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
-- 模块表
|
||||
@@ -92,7 +94,7 @@ CREATE TABLE `tb_sys_module` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_module_id` (`module_id`),
|
||||
UNIQUE KEY `uk_module_code` (`code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- 权限表
|
||||
DROP TABLE IF EXISTS `tb_sys_permission`;
|
||||
@@ -112,7 +114,7 @@ CREATE TABLE `tb_sys_permission` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_permission_id` (`permission_id`),
|
||||
KEY `idx_permission_module` (`module_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
-- 角色-权限关联
|
||||
@@ -129,7 +131,7 @@ CREATE TABLE `tb_sys_role_permission` (
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_role_permission` (`role_id`, `permission_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
-- 菜单表
|
||||
@@ -154,7 +156,7 @@ CREATE TABLE `tb_sys_menu` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_menu_id` (`menu_id`),
|
||||
KEY `idx_menu_parent` (`parent_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `tb_sys_menu_permission`;
|
||||
@@ -170,4 +172,4 @@ CREATE TABLE `tb_sys_menu_permission` (
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_menu_permission` (`menu_id`, `permission_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
@@ -3,7 +3,7 @@ use school_news;
|
||||
DROP TABLE IF EXISTS `tb_resource_permission`;
|
||||
CREATE TABLE `tb_resource_permission` (
|
||||
`id` VARCHAR(50) NOT NULL COMMENT '权限ID',
|
||||
`resource_type` INT(4) NOT NULL COMMENT '资源类型(1资源/新闻 2课程 3课程章节 4学习任务)',
|
||||
`resource_type` INT(4) NOT NULL COMMENT '资源类型(1新闻 2课程 3学习任务 4部门 5角色 6成就 7定时任务 8轮播图 9标签)',
|
||||
`resource_id` VARCHAR(50) NOT NULL COMMENT '资源ID',
|
||||
`dept_id` VARCHAR(50) DEFAULT NULL COMMENT '部门ID(NULL表示不限制部门)',
|
||||
`role_id` VARCHAR(50) DEFAULT NULL COMMENT '角色ID(NULL表示不限制角色)',
|
||||
@@ -25,30 +25,45 @@ CREATE TABLE `tb_resource_permission` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='统一资源权限控制表';
|
||||
|
||||
-- 说明:
|
||||
-- 1. resource_type 资源类型说明:
|
||||
-- 1 - 资源/新闻
|
||||
-- 2 - 课程
|
||||
-- 3 - 课程章节
|
||||
-- 4 - 学习任务
|
||||
-- 可根据实际业务扩展
|
||||
-- 1. resource_type 资源类型说明(对应ResourceType枚举):
|
||||
-- 1 - NEWS (新闻/资源)
|
||||
-- 2 - COURSE (课程)
|
||||
-- 3 - TASK (学习任务)
|
||||
-- 4 - DEPT (部门)
|
||||
-- 5 - ROLE (角色)
|
||||
-- 6 - ACHIEVEMENT (成就)
|
||||
-- 7 - CRONTAB_TASK (定时任务)
|
||||
-- 8 - BANNER (轮播图)
|
||||
-- 9 - TAG (标签)
|
||||
-- 注意:这些值必须与 common-core/enums/ResourceType.java 中的枚举定义完全一致
|
||||
--
|
||||
-- 2. dept_id 和 role_id 组合使用:
|
||||
-- - 都为NULL:不限制,所有人可访问
|
||||
-- - 都为NULL:超级管理员权限,所有人可访问
|
||||
-- - dept_id有值,role_id为NULL:该部门所有人可访问
|
||||
-- - dept_id为NULL,role_id有值:该角色所有人可访问
|
||||
-- - 都有值:该部门的该角色可访问
|
||||
-- - dept_id为NULL,role_id有值:该角色所有人可访问(跨部门)
|
||||
-- - 都有值:该部门的该角色可访问(精确控制)
|
||||
--
|
||||
-- 3. 权限说明:
|
||||
-- - can_read:查看权限(浏览、阅读)
|
||||
-- - can_read:查看权限(浏览、阅读、查询)
|
||||
-- - can_write:编辑权限(修改、删除)
|
||||
-- - can_execute:执行权限(发布、审核等操作)
|
||||
-- - can_execute:执行权限(发布、审核、执行等高级操作)
|
||||
--
|
||||
-- 4. 查询示例:
|
||||
-- 4. 权限创建逻辑:
|
||||
-- - root_department的superadmin创建资源时:为所有部门和角色创建权限
|
||||
-- - 普通用户创建资源时:为父部门管理员+子部门所有角色创建权限
|
||||
-- - 始终为root_department的superadmin创建全权限记录
|
||||
--
|
||||
-- 5. 查询示例:
|
||||
-- 查询用户对某资源的权限:
|
||||
-- SELECT * FROM tb_resource_permission
|
||||
-- WHERE resource_type = 1
|
||||
-- AND resource_id = 'xxx'
|
||||
-- AND (dept_id IS NULL OR dept_id = '用户部门')
|
||||
-- AND (role_id IS NULL OR role_id IN ('用户角色列表'))
|
||||
-- AND (
|
||||
-- (dept_id IS NULL AND role_id IS NULL) -- 超级权限
|
||||
-- OR (dept_id = '用户部门' AND role_id IS NULL) -- 部门权限
|
||||
-- OR (role_id = '用户角色' AND dept_id IS NULL) -- 角色权限
|
||||
-- OR (dept_id = '用户部门' AND role_id = '用户角色') -- 精确权限
|
||||
-- )
|
||||
-- AND can_read = 1
|
||||
-- AND deleted = 0;
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ CREATE TABLE IF NOT EXISTS `tb_sys_file` (
|
||||
INDEX `idx_storage_type` (`storage_type`),
|
||||
INDEX `idx_deleted` (`deleted`),
|
||||
INDEX `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文件上传记录表';
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文件上传记录表';
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ CREATE TABLE `tb_sys_user` (
|
||||
UNIQUE KEY `uk_user_username` (`username`),
|
||||
UNIQUE KEY `uk_user_email` (`email`),
|
||||
KEY `idx_user_phone` (`phone`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- 推荐:把默认 admin 密码替换为已哈希的值
|
||||
|
||||
@@ -40,7 +40,7 @@ CREATE TABLE `tb_sys_user_info` (
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_user_info_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `tb_sys_login_log`;
|
||||
@@ -61,5 +61,5 @@ CREATE TABLE `tb_sys_login_log` (
|
||||
PRIMARY KEY (`id`),
|
||||
index `idx_user_id` (`user_id`) USING BTREE,
|
||||
index `idx_login_time` (`login_time`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
@@ -45,11 +45,72 @@ INSERT INTO `tb_sys_config` (id, config_key, config_value, config_type, config_g
|
||||
('12', 'system.resource.auto_publish_time', '08:00', 'string', 'resource', '自动发布时间', 0, '1', now()),
|
||||
('13', 'system.ai.enabled', 'true', 'boolean', 'ai', '是否启用智能体', 0, '1', now());
|
||||
|
||||
-- 注意:默认superadmin用户已在 initMenuData.sql 中创建,此处无需重复创建
|
||||
|
||||
-- 插入默认用户数据
|
||||
INSERT INTO `tb_sys_user` (id, username, password, email, status) VALUES
|
||||
('1', 'superadmin', '$2a$10$/Bo2SXboVUpYfR6EA.y8puYQaMGBcuNYFY/EkQRY3w27IH56EuEcS', '3223905473@qq.com', 0);
|
||||
-- =====================================================
|
||||
-- 初始化资源权限数据
|
||||
-- =====================================================
|
||||
|
||||
-- 插入默认用户信息数据
|
||||
INSERT INTO `tb_sys_user_info` (id, user_id, full_name, avatar) VALUES
|
||||
('1', '1', '管理员', 'default');
|
||||
|
||||
INSERT INTO `tb_achievement` (`id`, `achievement_id`, `name`,`icon`, `description`, `type`, `level`, `condition_type`, `condition_value`, `points`, `order_num`, `deleted`) VALUES
|
||||
('ACH001', 'learning_time_l1', '初学者', 'v1-icon.svg', '累计学习时长达到10小时', 1, 1, 1, 10*60*60, 10, 1, 0),
|
||||
('ACH002', 'learning_time_l2', '勤学者', 'v2-icon.svg', '累计学习时长达到50小时', 1, 2, 1, 50*60*60, 50, 2, 0),
|
||||
('ACH003', 'learning_time_l3', '学习达人', 'v3-icon.svg', '累计学习时长达到100小时', 1, 3, 1, 100*60*60, 100, 3, 0),
|
||||
('ACH004', 'learning_time_l4', '学习狂人', 'v4-icon.svg', '累计学习时长达到500小时', 1, 4, 1, 500*60*60, 500, 4, 0),
|
||||
('ACH005', 'learning_time_l5', '学习大师', 'v5-icon.svg', '累计学习时长达到1000小时', 1, 5, 1, 1000*60*60, 1000, 5, 0),
|
||||
('ACH006', 'learning_time_l6', '学习宗师', 'v6-icon.svg', '累计学习时长达到2000小时', 1, 6, 1, 2000*60*60, 2000, 6, 0);
|
||||
|
||||
INSERT INTO `tb_resource_permission` (id, resource_type, resource_id, dept_id, role_id, can_read, can_write, can_execute, creator, create_time) VALUES
|
||||
('perm_achievement_001', 6, 'learning_time_l1', NULL, NULL, 1, 0, 0, '1', now()),
|
||||
('perm_achievement_002', 6, 'learning_time_l2', NULL, NULL, 1, 0, 0, '1', now()),
|
||||
('perm_achievement_003', 6, 'learning_time_l3', NULL, NULL, 1, 0, 0, '1', now()),
|
||||
('perm_achievement_004', 6, 'learning_time_l4', NULL, NULL, 1, 0, 0, '1', now()),
|
||||
('perm_achievement_005', 6, 'learning_time_l5', NULL, NULL, 1, 0, 0, '1', now()),
|
||||
('perm_achievement_006', 6, 'learning_time_l6', NULL, NULL, 1, 0, 0, '1', now());
|
||||
|
||||
-- 为默认标签创建超级管理员权限(文章标签)
|
||||
INSERT INTO `tb_resource_permission` (id, resource_type, resource_id, dept_id, role_id, can_read, can_write, can_execute, creator, create_time) VALUES
|
||||
-- 文章标签权限(resource_type=9,TAG)
|
||||
('perm_tag_001', 9, 'tag_article_001', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_002', 9, 'tag_article_002', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_003', 9, 'tag_article_003', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_004', 9, 'tag_article_004', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_005', 9, 'tag_article_005', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_006', 9, 'tag_article_006', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
|
||||
-- 课程标签权限(resource_type=9,TAG)
|
||||
('perm_tag_101', 9, 'tag_course_001', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_102', 9, 'tag_course_002', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_103', 9, 'tag_course_003', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_104', 9, 'tag_course_004', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_105', 9, 'tag_course_005', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
|
||||
-- 学习任务标签权限(resource_type=9,TAG)
|
||||
('perm_tag_201', 9, 'tag_task_001', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_202', 9, 'tag_task_002', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_203', 9, 'tag_task_003', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_204', 9, 'tag_task_004', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_tag_205', 9, 'tag_task_005', NULL, NULL, 1, 1, 1, '1', now());
|
||||
|
||||
-- 为默认部门创建权限(resource_type=4,DEPT)
|
||||
INSERT INTO `tb_resource_permission` (id, resource_type, resource_id, dept_id, role_id, can_read, can_write, can_execute, creator, create_time) VALUES
|
||||
('perm_dept_001', 4, 'root_department', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_dept_002', 4, 'default_department', NULL, NULL, 1, 1, 1, '1', now());
|
||||
|
||||
-- 为默认角色创建权限(resource_type=5,ROLE)
|
||||
INSERT INTO `tb_resource_permission` (id, resource_type, resource_id, dept_id, role_id, can_read, can_write, can_execute, creator, create_time) VALUES
|
||||
('perm_role_001', 5, 'superadmin', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_role_002', 5, 'admin', NULL, NULL, 1, 1, 1, '1', now()),
|
||||
('perm_role_003', 5, 'freedom', NULL, NULL, 1, 1, 1, '1', now());
|
||||
|
||||
|
||||
-- 说明:
|
||||
-- 1. 这些初始权限都是超级管理员权限(dept_id和role_id都为NULL)
|
||||
-- 2. 所有权限都是全权限(can_read=1, can_write=1, can_execute=1)
|
||||
-- 3. 后续创建的资源会自动根据创建者的部门和角色创建相应的权限
|
||||
-- 4. resource_type说明:
|
||||
-- 1-NEWS, 2-COURSE, 3-TASK, 4-DEPT, 5-ROLE, 6-ACHIEVEMENT, 7-CRONTAB_TASK, 8-BANNER, 9-TAG
|
||||
-- 5. 权限创建规则:
|
||||
-- - root_department的superadmin创建资源:为所有部门和角色创建权限
|
||||
-- - 普通用户创建资源:为父部门管理员+子部门角色创建权限
|
||||
-- - 所有资源都会为root_department的superadmin创建全权限
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
use school_news;
|
||||
|
||||
-- 插入部门数据
|
||||
INSERT INTO `tb_sys_dept` (id,dept_id,name, description) VALUES ('1','root_department', '超级部门', '系统超级部门');
|
||||
INSERT INTO `tb_sys_dept` (id,dept_id,name, parent_id, description) VALUES ('2','default_department', '默认部门', 'root_department', '系统默认创建的部门');
|
||||
-- 插入默认超级管理员用户(必须最先创建,因为后续数据的creator都需要引用此用户)
|
||||
INSERT INTO `tb_sys_user` (id, username, password, email, status, create_time) VALUES
|
||||
('1', 'superadmin', '$2a$10$/Bo2SXboVUpYfR6EA.y8puYQaMGBcuNYFY/EkQRY3w27IH56EuEcS', 'superadmin@example.com', 1, now());
|
||||
|
||||
-- 插入默认用户信息数据
|
||||
INSERT INTO `tb_sys_user_info` (id, user_id, full_name, avatar, create_time) VALUES
|
||||
('1', '1', '超级管理员', 'default', now());
|
||||
|
||||
-- 插入部门数据(包含dept_path字段)
|
||||
INSERT INTO `tb_sys_dept` (id, dept_id, name, parent_id, dept_path, description, creator, create_time) VALUES
|
||||
('1', 'root_department', '超级部门', NULL, '/root_department/', '系统超级部门', '1', now());
|
||||
INSERT INTO `tb_sys_dept` (id, dept_id, name, parent_id, dept_path, description, creator, create_time) VALUES
|
||||
('2', 'default_department', '默认部门', 'root_department', '/root_department/default_department/', '系统默认创建的部门', '1', now());
|
||||
|
||||
-- 插入角色数据
|
||||
INSERT INTO `tb_sys_role` (id,role_id, name, description) VALUES ('1','superadmin', '超级管理员', '超级管理员角色');
|
||||
INSERT INTO `tb_sys_role` (id,role_id, name, description) VALUES ('2','admin', '管理员', '管理员角色');
|
||||
INSERT INTO `tb_sys_role` (id,role_id, name, description) VALUES ('3','freedom', '自由角色', '自由角色');
|
||||
INSERT INTO `tb_sys_role` (id,role_id, name, description, creator, create_time) VALUES
|
||||
('1','superadmin', '超级管理员', '超级管理员角色', '1', now());
|
||||
INSERT INTO `tb_sys_role` (id,role_id, name, description, creator, create_time) VALUES
|
||||
('2','admin', '管理员', '管理员角色', '1', now());
|
||||
INSERT INTO `tb_sys_role` (id,role_id, name, description, creator, create_time) VALUES
|
||||
('3','freedom', '自由角色', '自由角色', '1', now());
|
||||
|
||||
-- 插入部门-角色关联数据
|
||||
INSERT INTO `tb_sys_dept_role` (id, dept_id, role_id, creator, create_time) VALUES
|
||||
@@ -15,8 +28,9 @@ INSERT INTO `tb_sys_dept_role` (id, dept_id, role_id, creator, create_time) VALU
|
||||
('2', 'default_department', 'admin', '1', now()),
|
||||
('3', 'default_department', 'freedom', '1', now());
|
||||
|
||||
-- 插入用户-角色关联数据
|
||||
INSERT INTO `tb_sys_user_dept_role` (id, user_id, dept_id, role_id, creator, create_time) VALUES ('1', '1', 'root_department', 'superadmin', '1', now());
|
||||
-- 插入用户-部门-角色关联数据(root_department的superadmin)
|
||||
INSERT INTO `tb_sys_user_dept_role` (id, user_id, dept_id, role_id, creator, create_time) VALUES
|
||||
('1', '1', 'root_department', 'superadmin', '1', now());
|
||||
|
||||
-- 插入模块数据
|
||||
INSERT INTO `tb_sys_module` (id, module_id, name, code, description, icon, order_num, status, creator, create_time) VALUES
|
||||
@@ -72,87 +86,62 @@ INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, creat
|
||||
('18', 'admin', 'perm_crontab_execute', '1', now());
|
||||
|
||||
-- 插入前端菜单数据
|
||||
INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, order_num, type, layout, creator, create_time) VALUES
|
||||
('100', 'menu_home', '首页', NULL, '/home', 'user/home/HomeView', 'el-icon-house', 1, 1, 'NavigationLayout', '1', now()),
|
||||
-- 资源中心
|
||||
('200', 'menu_resource_center', '资源中心', NULL, '/resource-center', 'user/resource-center/ResourceCenterView', 'el-icon-folder-opened', 2, 1, 'NavigationLayout', '1', now()),
|
||||
-- 学习计划
|
||||
('300', 'menu_study_plan', '学习计划', NULL, '/study-plan', 'user/study-plan/StudyPlanView', 'el-icon-reading', 3, 1, 'NavigationLayout', '1', now()),
|
||||
('301', 'menu_study_tasks', '学习任务', 'menu_study_plan', '/study-plan/tasks', 'user/study-plan/StudyTasksView', 'el-icon-s-order', 1, 1, 'NavigationLayout', '1', now()),
|
||||
('302', 'menu_course_center', '课程中心', 'menu_study_plan', '/study-plan/course', 'user/study-plan/CourseCenterView', 'el-icon-video-play', 2, 1, 'NavigationLayout', '1', now()),
|
||||
('303', 'menu_task_detail', '任务详情', 'menu_study_plan', '/study-plan/task-detail', 'user/study-plan/LearningTaskDetailView', 'el-icon-document', 3, 3, 'NavigationLayout', '1', now()),
|
||||
('304', 'menu_course_detail', '课程详情', 'menu_study_plan', '/study-plan/course-detail', 'user/study-plan/CourseDetailView', 'el-icon-video-play', 4, 3, 'NavigationLayout', '1', now()),
|
||||
('305', 'menu_course_study', '课程学习', 'menu_study_plan', '/study-plan/course-study', 'user/study-plan/CourseStudyView', 'el-icon-video-play', 5, 3, 'NavigationLayout', '1', now()),
|
||||
('400', 'menu_user_dropdown', '用户下拉菜单', NULL, '', '', 'el-icon-user', 4, 0, 'NavigationLayout', '1', now()),
|
||||
-- 个人中心
|
||||
('401', 'menu_user_center', '个人中心', 'menu_user_dropdown', '/user-center', 'user/user-center/UserCenterView', 'el-icon-user', 4, 1, 'NavigationLayout', '1', now()),
|
||||
('402', 'menu_learning_records', '学习记录', 'menu_user_center', '/user-center/learning-records', 'user/user-center/LearningRecordsView', 'el-icon-document', 1, 0, 'NavigationLayout', '1', now()),
|
||||
('403', 'menu_my_favorites', '我的收藏', 'menu_user_center', '/user-center/favorites', 'user/user-center/MyFavoritesView', 'el-icon-star-on', 2, 0, 'NavigationLayout', '1', now()),
|
||||
('404', 'menu_my_achievements', '我的成就', 'menu_user_center', '/user-center/achievements', 'user/user-center/MyAchievementsView', 'el-icon-trophy', 3, 0, 'NavigationLayout', '1', now()),
|
||||
|
||||
-- 账号中心
|
||||
('500', 'menu_profile', '账号中心', 'menu_user_dropdown', '/profile', 'user/profile/ProfileView', 'el-icon-user-solid', 5, 1, 'NavigationLayout', '1', now()),
|
||||
('501', 'menu_personal_info', '个人信息', 'menu_profile', '/profile/personal-info', 'user/profile/PersonalInfoView', 'el-icon-user', 1, 0, 'NavigationLayout', '1', now()),
|
||||
('502', 'menu_account_settings', '账号设置', 'menu_profile', '/profile/account-settings', 'user/profile/AccountSettingsView', 'el-icon-setting', 2, 0, 'NavigationLayout', '1', now()),
|
||||
|
||||
-- 智能体模块
|
||||
('600', 'menu_ai_assistant', '智能体模块', NULL, '/ai-assistant', 'user/ai-assistant/AIAssistantView', 'el-icon-cpu', 6, 1, 'NavigationLayout', '1', now());
|
||||
|
||||
-- 插入后端管理菜单数据 (type=0 侧边栏菜单)
|
||||
INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, order_num, type, layout, creator, create_time) VALUES
|
||||
-- 系统总览
|
||||
('1000', 'menu_admin_overview', '系统总览', NULL, '/admin/overview', 'admin/overview/SystemOverviewView', 'el-icon-data-analysis', 1, 0, 'SidebarLayout', '1', now()),
|
||||
|
||||
-- 用户管理
|
||||
('2000', 'menu_sys_manage', '系统管理', NULL, '', '', 'el-icon-user', 2, 0, 'SidebarLayout', '1', now()),
|
||||
('2001', 'menu_admin_user', '用户管理', 'menu_sys_manage', '/admin/manage/system/user', 'admin/manage/system/UserManageView', 'el-icon-user', 1, 0, 'SidebarLayout', '1', now()),
|
||||
('2002', 'menu_admin_dept', '部门管理', 'menu_sys_manage', '/admin/manage/system/dept', 'admin/manage/system/DeptManageView', 'el-icon-office-building', 2, 0, 'SidebarLayout', '1', now()),
|
||||
('2003', 'menu_admin_role', '角色管理', 'menu_sys_manage', '/admin/manage/system/role', 'admin/manage/system/RoleManageView', 'el-icon-user-solid', 3, 0, 'SidebarLayout', '1', now()),
|
||||
('2005', 'menu_admin_menu', '菜单管理', 'menu_sys_manage', '/admin/manage/system/menu', 'admin/manage/system/MenuManageView', 'el-icon-menu', 4, 0, 'SidebarLayout', '1', now()),
|
||||
('2006', 'menu_admin_module', '模块权限管理', 'menu_sys_manage', '/admin/manage/system/module-permission', 'admin/manage/system/ModulePermissionManageView', 'el-icon-s-grid', 5, 0, 'SidebarLayout', '1', now()),
|
||||
|
||||
-- 资源管理
|
||||
('3000', 'menu_admin_resource_manage', '资源管理', NULL, '', '', 'el-icon-folder', 3, 0, 'SidebarLayout', '1', now()),
|
||||
('3001', 'menu_admin_resource', '资源管理', 'menu_admin_resource_manage', '/admin/manage/resource/resource', 'admin/manage/resource/ResourceManagementView', 'el-icon-folder', 1, 0, 'SidebarLayout', '1', now()),
|
||||
('3002', 'menu_admin_article', '文章管理', 'menu_admin_resource_manage', '/admin/manage/resource/article', 'admin/manage/resource/ArticleManagementView', 'el-icon-document', 2, 0, 'SidebarLayout', '1', now()),
|
||||
('3003', 'menu_admin_data_records', '数据记录', 'menu_admin_resource_manage', '/admin/manage/resource/data-records', 'admin/manage/resource/DataRecordsView', 'el-icon-data-line', 3, 0, 'SidebarLayout', '1', now()),
|
||||
|
||||
|
||||
-- 文章相关
|
||||
('3010', 'menu_article_add', '文章添加', 'menu_admin_article', '/article/add', 'public/article/ArticleAddView', 'el-icon-plus', 1, 3, 'SidebarLayout', '1', now()),
|
||||
('3011', 'menu_article_show', '文章展示', 'menu_admin_article', '/article/show', 'public/article/ArticleShowView', 'el-icon-document', 2, 3, 'SidebarLayout', '1', now()),
|
||||
-- 运营管理
|
||||
('4000', 'menu_admin_content_manage', '运营管理', NULL, '', '', 'el-icon-s-operation', 4, 0, 'SidebarLayout', '1', now()),
|
||||
('4001', 'menu_admin_banner', 'Banner管理', 'menu_admin_content_manage', '/admin/manage/content/banner', 'admin/manage/content/BannerManagementView', 'el-icon-picture', 1, 0, 'SidebarLayout', '1', now()),
|
||||
('4002', 'menu_admin_tag', '标签管理', 'menu_admin_content_manage', '/admin/manage/content/tag', 'admin/manage/content/TagManagementView', 'el-icon-price-tag', 2, 0, 'SidebarLayout', '1', now()),
|
||||
('4003', 'menu_admin_column', '栏目管理', 'menu_admin_content_manage', '/admin/manage/content/column', 'admin/manage/content/ColumnManagementView', 'el-icon-menu', 3, 0, 'SidebarLayout', '1', now()),
|
||||
('4004', 'menu_admin_content', '内容管理', 'menu_admin_content_manage', '/admin/manage/content/content', 'admin/manage/content/ContentManagementView', 'el-icon-document', 4, 0, 'SidebarLayout', '1', now()),
|
||||
|
||||
-- 学习管理
|
||||
('5000', 'menu_admin_study_manage', '学习管理', NULL, '', '', 'el-icon-reading', 5, 0, 'SidebarLayout', '1', now()),
|
||||
('5002', 'menu_admin_task_manage', '任务管理', 'menu_admin_study_manage', '/admin/manage/study/task-manage', 'admin/manage/study/TaskManageView', 'el-icon-s-order', 2, 0, 'SidebarLayout', '1', now()),
|
||||
('5003', 'menu_admin_study_records', '学习记录', 'menu_admin_study_manage', '/admin/manage/study/study-records', 'admin/manage/study/StudyRecordsView', 'el-icon-document', 3, 0, 'SidebarLayout', '1', now()),
|
||||
('5004', 'menu_admin_course_manage', '课程管理', 'menu_admin_study_manage', '/admin/manage/study/course', 'admin/manage/study/CourseManagementView', 'el-icon-video-play', 4, 0, 'SidebarLayout', '1', now()),
|
||||
('5005', 'menu_admin_achievement_manage', '成就管理', 'menu_admin_study_manage', '/admin/manage/study/achievement', 'admin/manage/achievement/AchievementManagementView', 'el-icon-trophy', 5, 0, 'SidebarLayout', '1', now()),
|
||||
-- 智能体管理
|
||||
('6000', 'menu_admin_ai_manage', '智能体管理', NULL, '', '', 'el-icon-cpu', 6, 0, 'SidebarLayout', '1', now()),
|
||||
('6001', 'menu_admin_ai', 'AI管理', 'menu_admin_ai_manage', '/admin/manage/ai/ai', 'admin/manage/ai/AIManagementView', 'el-icon-cpu', 1, 0, 'SidebarLayout', '1', now()),
|
||||
('6002', 'menu_admin_ai_config', 'AI配置', 'menu_admin_ai_manage', '/admin/manage/ai/config', 'admin/manage/ai/AIConfigView', 'el-icon-setting', 2, 0, 'SidebarLayout', '1', now()),
|
||||
('6003', 'menu_admin_knowledge', '知识库管理', 'menu_admin_ai_manage', '/admin/manage/ai/knowledge', 'admin/manage/ai/KnowledgeManagementView', 'el-icon-collection', 3, 0, 'SidebarLayout', '1', now()),
|
||||
|
||||
-- 系统日志
|
||||
('7000', 'menu_admin_logs_manage', '系统日志', NULL, '', '', 'el-icon-document', 7, 0, 'SidebarLayout', '1', now()),
|
||||
('7001', 'menu_admin_system_logs', '系统日志', 'menu_admin_logs_manage', '/admin/manage/logs/system', 'admin/manage/logs/SystemLogsView', 'el-icon-document', 1, 0, 'SidebarLayout', '1', now()),
|
||||
('7002', 'menu_admin_login_logs', '登录日志', 'menu_admin_logs_manage', '/admin/manage/logs/login', 'admin/manage/logs/LoginLogsView', 'el-icon-key', 2, 0, 'SidebarLayout', '1', now()),
|
||||
('7003', 'menu_admin_operation_logs', '操作日志', 'menu_admin_logs_manage', '/admin/manage/logs/operation', 'admin/manage/logs/OperationLogsView', 'el-icon-s-operation', 3, 0, 'SidebarLayout', '1', now()),
|
||||
('7004', 'menu_admin_system_config', '系统配置', 'menu_admin_logs_manage', '/admin/manage/logs/config', 'admin/manage/logs/SystemConfigView', 'el-icon-setting', 4, 0, 'SidebarLayout', '1', now()),
|
||||
|
||||
-- 定时任务管理
|
||||
('8000', 'menu_admin_crontab_manage', '定时任务管理', NULL, '', '', 'el-icon-alarm-clock', 8, 0, 'SidebarLayout', '1', now()),
|
||||
('8001', 'menu_admin_crontab_task', '任务管理', 'menu_admin_crontab_manage', '/admin/manage/crontab/task', 'admin/manage/crontab/TaskManagementView', 'el-icon-s-order', 1, 0, 'SidebarLayout', '1', now()),
|
||||
('8002', 'menu_admin_crontab_log', '执行日志', 'menu_admin_crontab_manage', '/admin/manage/crontab/log', 'admin/manage/crontab/LogManagementView', 'el-icon-document', 2, 0, 'SidebarLayout', '1', now()),
|
||||
('8003', 'menu_admin_news_crawler', '新闻爬虫配置', 'menu_admin_crontab_manage', '/admin/manage/crontab/news-crawler', 'admin/manage/crontab/NewsCrawlerView', 'el-icon-share', 3, 0, 'SidebarLayout', '1', now());
|
||||
|
||||
INSERT INTO `tb_sys_menu` VALUES
|
||||
-- 用户前端菜单 (100-699)
|
||||
('100', 'menu_home', '首页', NULL, '/home', 'user/home/HomeView', NULL, 1, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('200', 'menu_resource_center', '资源中心', NULL, '/resource-center', 'user/resource-center/ResourceCenterView', NULL, 2, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('300', 'menu_study_plan', '学习计划', NULL, '/study-plan', 'user/study-plan/StudyPlanView', NULL, 3, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('301', 'menu_study_tasks', '学习任务', 'menu_study_plan', '/study-plan/tasks', 'user/study-plan/StudyTasksView', NULL, 1, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('302', 'menu_course_center', '课程中心', 'menu_study_plan', '/study-plan/course', 'user/study-plan/CourseCenterView', NULL, 2, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('303', 'menu_task_detail', '任务详情', 'menu_study_plan', '/study-plan/task-detail', 'user/study-plan/LearningTaskDetailView', NULL, 3, 3, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('304', 'menu_course_detail', '课程详情', 'menu_study_plan', '/study-plan/course-detail', 'user/study-plan/CourseDetailView', NULL, 4, 3, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('305', 'menu_course_study', '课程学习', 'menu_study_plan', '/study-plan/course-study', 'user/study-plan/CourseStudyView', NULL, 5, 3, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('400', 'menu_user_dropdown', '用户下拉菜单', NULL, '', '', NULL, 4, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('401', 'menu_user_center', '个人中心', 'menu_user_dropdown', '/user-center', 'user/user-center/UserCenterView', NULL, 4, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('402', 'menu_learning_records', '学习记录', 'menu_user_center', '/user-center/learning-records', 'user/user-center/LearningRecordsView', NULL, 1, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('403', 'menu_my_favorites', '我的收藏', 'menu_user_center', '/user-center/favorites', 'user/user-center/MyFavoritesView', NULL, 2, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('404', 'menu_my_achievements', '我的成就', 'menu_user_center', '/user-center/achievements', 'user/user-center/MyAchievementsView', NULL, 3, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('500', 'menu_profile', '账号中心', 'menu_user_dropdown', '/profile', 'user/profile/ProfileView', NULL, 5, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('501', 'menu_personal_info', '个人信息', 'menu_profile', '/profile/personal-info', 'user/profile/PersonalInfoView', NULL, 1, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('502', 'menu_account_settings', '账号设置', 'menu_profile', '/profile/account-settings', 'user/profile/AccountSettingsView', NULL, 2, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('600', 'menu_ai_assistant', '智能体模块', NULL, '/ai-assistant', 'user/ai-assistant/AIAssistantView', NULL, 6, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
-- 管理后台菜单 (1000-8999)
|
||||
('1000', 'menu_admin_overview', '系统总览', NULL, '/admin/overview', 'admin/overview/SystemOverviewView', 'admin/overview.svg', 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:32', NULL, 0),
|
||||
('2000', 'menu_sys_manage', '系统管理', NULL, '', '', 'admin/settings.svg', 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:35', NULL, 0),
|
||||
('2001', 'menu_admin_user', '用户管理', 'menu_sys_manage', '/admin/manage/system/user', 'admin/manage/system/UserManageView', 'admin/usermange.svg', 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:38', NULL, 0),
|
||||
('2002', 'menu_admin_dept', '部门管理', 'menu_sys_manage', '/admin/manage/system/dept', 'admin/manage/system/DeptManageView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('2003', 'menu_admin_role', '角色管理', 'menu_sys_manage', '/admin/manage/system/role', 'admin/manage/system/RoleManageView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('2005', 'menu_admin_menu', '菜单管理', 'menu_sys_manage', '/admin/manage/system/menu', 'admin/manage/system/MenuManageView', NULL, 4, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('2006', 'menu_admin_module', '模块权限管理', 'menu_sys_manage', '/admin/manage/system/module-permission', 'admin/manage/system/ModulePermissionManageView', NULL, 5, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('3000', 'menu_admin_resource_manage', '资源管理', NULL, '', '', 'admin/resource.svg', 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:40', NULL, 0),
|
||||
('3001', 'menu_admin_resource', '数据采集', 'menu_admin_resource_manage', '/admin/manage/resource/resource', 'admin/manage/resource/ResourceManagementView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('3002', 'menu_admin_article', '文章管理', 'menu_admin_resource_manage', '/admin/manage/resource/article', 'admin/manage/resource/ArticleManagementView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('3003', 'menu_admin_data_records', '数据记录', 'menu_admin_resource_manage', '/admin/manage/resource/data-records', 'admin/manage/resource/DataRecordsView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('3010', 'menu_article_add', '文章添加', 'menu_admin_article', '/article/add', 'public/article/ArticleAddView', NULL, 1, 3, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('3011', 'menu_article_show', '文章展示', 'menu_admin_article', '/article/show', 'public/article/ArticleShowView', NULL, 2, 3, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('4000', 'menu_admin_content_manage', '运营管理', NULL, '', '', 'admin/maintain.svg', 4, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:42', NULL, 0),
|
||||
('4001', 'menu_admin_banner', 'Banner管理', 'menu_admin_content_manage', '/admin/manage/content/banner', 'admin/manage/content/BannerManagementView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('4002', 'menu_admin_tag', '标签管理', 'menu_admin_content_manage', '/admin/manage/content/tag', 'admin/manage/content/TagManagementView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('4003', 'menu_admin_column', '栏目管理', 'menu_admin_content_manage', '/admin/manage/content/column', 'admin/manage/content/ColumnManagementView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('4004', 'menu_admin_content', '内容管理', 'menu_admin_content_manage', '/admin/manage/content/content', 'admin/manage/content/ContentManagementView', NULL, 4, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||
('5000', 'menu_admin_study_manage', '学习管理', NULL, '', '', 'admin/study.svg', 5, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:46', NULL, 0),
|
||||
('5002', 'menu_admin_task_manage', '任务管理', 'menu_admin_study_manage', '/admin/manage/study/task-manage', 'admin/manage/study/TaskManageView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('5003', 'menu_admin_study_records', '学习记录', 'menu_admin_study_manage', '/admin/manage/study/study-records', 'admin/manage/study/StudyRecordsView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('5004', 'menu_admin_course_manage', '课程管理', 'menu_admin_study_manage', '/admin/manage/study/course', 'admin/manage/study/CourseManagementView', 'admin/course.svg', 4, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:48', NULL, 0),
|
||||
('5005', 'menu_admin_achievement_manage', '成就管理', 'menu_admin_study_manage', '/admin/manage/study/achievement', 'admin/manage/achievement/AchievementManagementView', NULL, 5, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('6000', 'menu_admin_ai_manage', '智能体管理', NULL, '', '', 'admin/agent.svg', 6, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:50', NULL, 0),
|
||||
('6001', 'menu_admin_ai', 'AI管理', 'menu_admin_ai_manage', '/admin/manage/ai/ai', 'admin/manage/ai/AIManagementView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('6002', 'menu_admin_ai_config', 'AI配置', 'menu_admin_ai_manage', '/admin/manage/ai/config', 'admin/manage/ai/AIConfigView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('6003', 'menu_admin_knowledge', '知识库管理', 'menu_admin_ai_manage', '/admin/manage/ai/knowledge', 'admin/manage/ai/KnowledgeManagementView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('7000', 'menu_admin_logs_manage', '系统日志', NULL, '', '', 'admin/logs.svg', 7, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:53', NULL, 0),
|
||||
('7001', 'menu_admin_system_logs', '系统日志', 'menu_admin_logs_manage', '/admin/manage/logs/system', 'admin/manage/logs/SystemLogsView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('7002', 'menu_admin_login_logs', '登录日志', 'menu_admin_logs_manage', '/admin/manage/logs/login', 'admin/manage/logs/LoginLogsView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('7003', 'menu_admin_operation_logs', '操作日志', 'menu_admin_logs_manage', '/admin/manage/logs/operation', 'admin/manage/logs/OperationLogsView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('7004', 'menu_admin_system_config', '系统配置', 'menu_admin_logs_manage', '/admin/manage/logs/config', 'admin/manage/logs/SystemConfigView', NULL, 4, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('8000', 'menu_admin_crontab_manage', '定时任务管理', NULL, '', '', NULL, 8, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('8001', 'menu_admin_crontab_task', '任务管理', 'menu_admin_crontab_manage', '/admin/manage/crontab/task', 'admin/manage/crontab/TaskManagementView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('8002', 'menu_admin_crontab_log', '执行日志', 'menu_admin_crontab_manage', '/admin/manage/crontab/log', 'admin/manage/crontab/LogManagementView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('8003', 'menu_admin_news_crawler', '新闻爬虫配置', 'menu_admin_crontab_manage', '/admin/manage/crontab/news-crawler', 'admin/manage/crontab/NewsCrawlerView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0);
|
||||
-- 插入菜单权限关联数据
|
||||
INSERT INTO `tb_sys_menu_permission` (id, permission_id, menu_id, creator, create_time) VALUES
|
||||
-- 前端菜单权限关联
|
||||
|
||||
@@ -5,6 +5,8 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.usercenter.TbAchievement;
|
||||
import org.xyzh.common.vo.AchievementVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -21,9 +23,10 @@ public interface AchievementMapper extends BaseMapper<TbAchievement> {
|
||||
/**
|
||||
* @description 查询成就列表
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbAchievement> 成就列表
|
||||
*/
|
||||
List<TbAchievement> selectAchievements(@Param("filter") TbAchievement filter);
|
||||
List<TbAchievement> selectAchievements(@Param("filter") TbAchievement filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据成就ID查询成就信息
|
||||
@@ -108,15 +111,30 @@ public interface AchievementMapper extends BaseMapper<TbAchievement> {
|
||||
* @description 分页查询成就
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbAchievement> 成就列表
|
||||
*/
|
||||
List<TbAchievement> selectAchievementsPage(@Param("filter") TbAchievement filter, @Param("pageParam") PageParam pageParam);
|
||||
List<TbAchievement> selectAchievementsPage(@Param("filter") TbAchievement filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 统计成就总数
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return long 总数
|
||||
*/
|
||||
long countAchievements(@Param("filter") TbAchievement filter);
|
||||
long countAchievements(@Param("filter") TbAchievement filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 联表查询用户成就列表(包含进度信息)- 带权限过滤
|
||||
* @param userId 用户ID
|
||||
* @param type 成就类型(可选)
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<AchievementVO> 用户成就列表(包含进度)
|
||||
*/
|
||||
List<AchievementVO> selectUserAchievementsWithProgress(
|
||||
@Param("userId") String userId,
|
||||
@Param("type") Integer type,
|
||||
@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ import org.xyzh.common.dto.usercenter.TbUserAchievementProgress;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.common.vo.AchievementVO;
|
||||
import org.xyzh.system.utils.LoginUtil;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.core.enums.ResourceType;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -51,6 +54,9 @@ public class ACHAchievementServiceImpl implements AchievementService {
|
||||
@Autowired
|
||||
private List<AchievementChecker> checkers;
|
||||
|
||||
@Autowired
|
||||
private ResourcePermissionService resourcePermissionService;
|
||||
|
||||
// ==================== 成就定义管理 ====================
|
||||
|
||||
@Override
|
||||
@@ -84,6 +90,21 @@ public class ACHAchievementServiceImpl implements AchievementService {
|
||||
// 插入数据库
|
||||
int result = achievementMapper.insertAchievement(achievement);
|
||||
if (result > 0) {
|
||||
// 创建成就资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
// 使用用户的第一个部门角色创建权限
|
||||
resourcePermissionService.createResourcePermission(
|
||||
ResourceType.ACHIEVEMENT.getCode(),
|
||||
achievement.getAchievementID(),
|
||||
userDeptRoles.get(0)
|
||||
);
|
||||
logger.info("创建成就权限成功: {}", achievement.getAchievementID());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建成就权限异常,但不影响成就创建: {}", e.getMessage(), e);
|
||||
}
|
||||
resultDomain.success("创建成就成功", achievement);
|
||||
} else {
|
||||
resultDomain.fail("创建成就失败");
|
||||
@@ -176,7 +197,9 @@ public class ACHAchievementServiceImpl implements AchievementService {
|
||||
public ResultDomain<TbAchievement> getAllAchievements(TbAchievement filter) {
|
||||
ResultDomain<TbAchievement> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
List<TbAchievement> list = achievementMapper.selectAchievements(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbAchievement> list = achievementMapper.selectAchievements(filter, userDeptRoles);
|
||||
|
||||
resultDomain.success("获取成就列表成功", list);
|
||||
return resultDomain;
|
||||
@@ -196,8 +219,10 @@ public class ACHAchievementServiceImpl implements AchievementService {
|
||||
filter.setDeleted(false);
|
||||
}
|
||||
|
||||
List<TbAchievement> list = achievementMapper.selectAchievementsPage(filter, pageParam);
|
||||
long total = achievementMapper.countAchievements(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbAchievement> list = achievementMapper.selectAchievementsPage(filter, pageParam, userDeptRoles);
|
||||
long total = achievementMapper.countAchievements(filter, userDeptRoles);
|
||||
|
||||
pageParam.setTotalElements(total);
|
||||
pageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
|
||||
@@ -286,87 +311,13 @@ public class ACHAchievementServiceImpl implements AchievementService {
|
||||
|
||||
String userID = user.getID();
|
||||
|
||||
// 1. 获取所有成就列表(根据type过滤)
|
||||
List<TbAchievement> allAchievements;
|
||||
if (type != null) {
|
||||
allAchievements = achievementMapper.selectByType(type);
|
||||
} else {
|
||||
TbAchievement filter = new TbAchievement();
|
||||
filter.setDeleted(false);
|
||||
allAchievements = achievementMapper.selectAchievements(filter);
|
||||
}
|
||||
|
||||
// 2. 获取用户已获得的成就列表
|
||||
List<TbUserAchievement> userAchievements = type != null
|
||||
? userAchievementMapper.selectByUserIdAndType(userID, type)
|
||||
: userAchievementMapper.selectByUserId(userID);
|
||||
|
||||
// 转换为Map,key为achievementID
|
||||
Map<String, TbUserAchievement> userAchievementMap = userAchievements.stream()
|
||||
.collect(Collectors.toMap(TbUserAchievement::getAchievementID, ua -> ua));
|
||||
|
||||
// 3. 获取用户的成就进度列表
|
||||
List<TbUserAchievementProgress> progressList = progressMapper.selectByUserId(userID);
|
||||
|
||||
// 转换为Map,key为achievementID
|
||||
Map<String, TbUserAchievementProgress> progressMap = progressList.stream()
|
||||
.collect(Collectors.toMap(TbUserAchievementProgress::getAchievementID, p -> p));
|
||||
|
||||
// 4. 组装AchievementVO列表
|
||||
List<AchievementVO> achievementVOList = new ArrayList<>();
|
||||
for (TbAchievement achievement : allAchievements) {
|
||||
AchievementVO vo = new AchievementVO();
|
||||
|
||||
// 复制成就基本信息
|
||||
vo.setID(achievement.getID());
|
||||
vo.setAchievementID(achievement.getAchievementID());
|
||||
vo.setName(achievement.getName());
|
||||
vo.setDescription(achievement.getDescription());
|
||||
vo.setType(achievement.getType());
|
||||
vo.setLevel(achievement.getLevel());
|
||||
vo.setIcon(achievement.getIcon());
|
||||
vo.setPoints(achievement.getPoints());
|
||||
vo.setConditionType(achievement.getConditionType());
|
||||
vo.setConditionValue(achievement.getConditionValue());
|
||||
vo.setOrderNum(achievement.getOrderNum());
|
||||
vo.setCreator(achievement.getCreator());
|
||||
vo.setUpdater(achievement.getUpdater());
|
||||
vo.setCreateTime(achievement.getCreateTime());
|
||||
vo.setUpdateTime(achievement.getUpdateTime());
|
||||
vo.setDeleted(achievement.getDeleted());
|
||||
|
||||
// 设置用户ID
|
||||
vo.setUserID(userID);
|
||||
|
||||
// 填充用户成就信息(如果已获得)
|
||||
TbUserAchievement userAchievement = userAchievementMap.get(achievement.getAchievementID());
|
||||
if (userAchievement != null) {
|
||||
vo.setUserAchievementID(userAchievement.getID());
|
||||
vo.setObtainTime(userAchievement.getObtainTime());
|
||||
vo.setObtained(true);
|
||||
} else {
|
||||
vo.setObtained(false);
|
||||
}
|
||||
|
||||
// 填充进度信息
|
||||
TbUserAchievementProgress progress = progressMap.get(achievement.getAchievementID());
|
||||
if (progress != null) {
|
||||
vo.setProgressID(progress.getID());
|
||||
vo.setCurrentValue(progress.getCurrentValue());
|
||||
vo.setTargetValue(progress.getTargetValue());
|
||||
vo.setProgressPercentage(progress.getProgressPercentage());
|
||||
vo.setCompleted(progress.getCompleted());
|
||||
vo.setLastUpdateTime(progress.getLastUpdateTime());
|
||||
} else {
|
||||
// 没有进度记录,设置默认值
|
||||
vo.setCurrentValue(0);
|
||||
vo.setTargetValue(achievement.getConditionValue());
|
||||
vo.setProgressPercentage(0);
|
||||
vo.setCompleted(false);
|
||||
}
|
||||
|
||||
achievementVOList.add(vo);
|
||||
}
|
||||
// 使用联表查询一次性获取所有数据(包含成就、用户成就、进度信息及权限过滤)
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<AchievementVO> achievementVOList = achievementMapper.selectUserAchievementsWithProgress(
|
||||
userID,
|
||||
type,
|
||||
userDeptRoles
|
||||
);
|
||||
|
||||
resultDomain.success("获取成就列表成功", achievementVOList);
|
||||
return resultDomain;
|
||||
@@ -652,7 +603,8 @@ public class ACHAchievementServiceImpl implements AchievementService {
|
||||
// 获取所有成就
|
||||
TbAchievement filter = new TbAchievement();
|
||||
filter.setDeleted(false);
|
||||
List<TbAchievement> allAchievements = achievementMapper.selectAchievements(filter);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbAchievement> allAchievements = achievementMapper.selectAchievements(filter, userDeptRoles);
|
||||
|
||||
// 获取用户进度
|
||||
List<TbUserAchievementProgress> progressList = progressMapper.selectIncompletedByUserId(userID);
|
||||
@@ -815,7 +767,8 @@ public class ACHAchievementServiceImpl implements AchievementService {
|
||||
// 获取所有成就
|
||||
TbAchievement filter = new TbAchievement();
|
||||
filter.setDeleted(false);
|
||||
List<TbAchievement> allAchievements = achievementMapper.selectAchievements(filter);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbAchievement> allAchievements = achievementMapper.selectAchievements(filter, userDeptRoles);
|
||||
|
||||
// 筛选支持该事件类型的成就
|
||||
return allAchievements.stream()
|
||||
|
||||
@@ -65,13 +65,57 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- 查询成就列表 -->
|
||||
<!-- 权限过滤条件(基于dept_path的高效继承) -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_resource_permission rp ON a.achievement_id = rp.resource_id
|
||||
AND rp.resource_type = 6
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM (
|
||||
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||
SELECT #{udr.deptID} AS dept_id, #{udr.deptPath} AS dept_path, #{udr.roleID} AS role_id
|
||||
</foreach>
|
||||
) user_roles
|
||||
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||
WHERE
|
||||
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
)
|
||||
</sql>
|
||||
|
||||
<!-- 查询成就列表 - 添加权限过滤 -->
|
||||
<select id="selectAchievements" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_achievement
|
||||
<include refid="Base_Where_Clause" />
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT a.*
|
||||
FROM tb_achievement a
|
||||
<include refid="Permission_Filter" />
|
||||
WHERE 1=1
|
||||
<if test="filter != null">
|
||||
<if test="filter.achievementID != null and filter.achievementID != ''">
|
||||
AND a.achievement_id = #{filter.achievementID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND a.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.type != null">
|
||||
AND a.type = #{filter.type}
|
||||
</if>
|
||||
<if test="filter.level != null">
|
||||
AND a.level = #{filter.level}
|
||||
</if>
|
||||
<if test="filter.deleted != null">
|
||||
AND a.deleted = #{filter.deleted}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY a.order_num ASC, a.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据成就ID查询成就信息 -->
|
||||
@@ -227,20 +271,133 @@
|
||||
</delete>
|
||||
|
||||
<!-- 分页查询成就 -->
|
||||
<!-- selectAchievementsPage - 添加权限过滤 -->
|
||||
<select id="selectAchievementsPage" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_achievement
|
||||
<include refid="Base_Where_Clause" />
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT a.*
|
||||
FROM tb_achievement a
|
||||
<include refid="Permission_Filter" />
|
||||
WHERE 1=1
|
||||
<if test="filter != null">
|
||||
<if test="filter.achievementID != null and filter.achievementID != ''">
|
||||
AND a.achievement_id = #{filter.achievementID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND a.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.type != null">
|
||||
AND a.type = #{filter.type}
|
||||
</if>
|
||||
<if test="filter.level != null">
|
||||
AND a.level = #{filter.level}
|
||||
</if>
|
||||
<if test="filter.deleted != null">
|
||||
AND a.deleted = #{filter.deleted}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY a.order_num ASC, a.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<!-- 统计成就总数 -->
|
||||
<!-- 统计成就总数 - 添加权限过滤 -->
|
||||
<select id="countAchievements" resultType="long">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_achievement
|
||||
<include refid="Base_Where_Clause" />
|
||||
SELECT COUNT(DISTINCT a.id)
|
||||
FROM tb_achievement a
|
||||
<include refid="Permission_Filter" />
|
||||
WHERE 1=1
|
||||
<if test="filter != null">
|
||||
<if test="filter.achievementID != null and filter.achievementID != ''">
|
||||
AND a.achievement_id = #{filter.achievementID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND a.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.type != null">
|
||||
AND a.type = #{filter.type}
|
||||
</if>
|
||||
<if test="filter.level != null">
|
||||
AND a.level = #{filter.level}
|
||||
</if>
|
||||
<if test="filter.deleted != null">
|
||||
AND a.deleted = #{filter.deleted}
|
||||
</if>
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- AchievementVO 结果映射(包含用户成就和进度信息) -->
|
||||
<resultMap id="AchievementVOResultMap" type="org.xyzh.common.vo.AchievementVO">
|
||||
<!-- 成就基本信息 -->
|
||||
<id column="id" property="ID" />
|
||||
<result column="achievement_id" property="achievementID" />
|
||||
<result column="name" property="name" />
|
||||
<result column="description" property="description" />
|
||||
<result column="icon" property="icon" />
|
||||
<result column="type" property="type" />
|
||||
<result column="level" property="level" />
|
||||
<result column="condition_type" property="conditionType" />
|
||||
<result column="condition_value" property="conditionValue" />
|
||||
<result column="points" property="points" />
|
||||
<result column="order_num" property="orderNum" />
|
||||
<result column="creator" property="creator" />
|
||||
<result column="updater" property="updater" />
|
||||
<result column="create_time" property="createTime" />
|
||||
<result column="update_time" property="updateTime" />
|
||||
<result column="deleted" property="deleted" />
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<result column="user_id" property="userID" />
|
||||
|
||||
<!-- 用户成就信息 -->
|
||||
<result column="user_achievement_id" property="userAchievementID" />
|
||||
<result column="obtain_time" property="obtainTime" />
|
||||
<result column="obtained" property="obtained" />
|
||||
|
||||
<!-- 进度信息 -->
|
||||
<result column="progress_id" property="progressID" />
|
||||
<result column="current_value" property="currentValue" />
|
||||
<result column="target_value" property="targetValue" />
|
||||
<result column="progress_percentage" property="progressPercentage" />
|
||||
<result column="completed" property="completed" />
|
||||
<result column="last_update_time" property="lastUpdateTime" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 联表查询用户成就列表(包含进度信息)- 带权限过滤 -->
|
||||
<select id="selectUserAchievementsWithProgress" resultMap="AchievementVOResultMap">
|
||||
SELECT
|
||||
a.id,
|
||||
a.achievement_id,
|
||||
a.name,
|
||||
a.description,
|
||||
a.icon,
|
||||
a.type,
|
||||
a.level,
|
||||
a.condition_type,
|
||||
a.condition_value,
|
||||
a.points,
|
||||
a.order_num,
|
||||
a.creator,
|
||||
a.updater,
|
||||
a.create_time,
|
||||
a.update_time,
|
||||
a.deleted,
|
||||
#{userId} as user_id,
|
||||
ua.id as user_achievement_id,
|
||||
ua.obtain_time,
|
||||
CASE WHEN ua.id IS NOT NULL THEN 1 ELSE 0 END as obtained,
|
||||
p.id as progress_id,
|
||||
COALESCE(p.current_value, 0) as current_value,
|
||||
COALESCE(p.target_value, a.condition_value) as target_value,
|
||||
COALESCE(p.progress_percentage, 0) as progress_percentage,
|
||||
COALESCE(p.completed, 0) as completed,
|
||||
p.last_update_time
|
||||
FROM tb_achievement a
|
||||
<include refid="Permission_Filter"/>
|
||||
LEFT JOIN tb_user_achievement ua ON a.achievement_id = ua.achievement_id AND ua.user_id = #{userId}
|
||||
LEFT JOIN tb_user_achievement_progress p ON a.achievement_id = p.achievement_id AND p.user_id = #{userId}
|
||||
WHERE a.deleted = 0
|
||||
<if test="type != null">
|
||||
AND a.type = #{type}
|
||||
</if>
|
||||
ORDER BY a.order_num ASC, a.create_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -83,8 +83,8 @@
|
||||
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
|
||||
<loggers>
|
||||
|
||||
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
|
||||
<logger name="org.mybatis" level="info" additivity="false">
|
||||
<!--过滤掉spring的一些无用的DEBUG信息-->
|
||||
<logger name="org.mybatis" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
</logger>
|
||||
<!--监控系统信息-->
|
||||
@@ -92,6 +92,17 @@
|
||||
<Logger name="org.springframework" level="info" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Logger>
|
||||
|
||||
<!-- MyBatis Mapper 日志配置 - 打印SQL -->
|
||||
<Logger name="org.xyzh.achievement.mapper" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Logger>
|
||||
<Logger name="org.xyzh.system.mapper" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Logger>
|
||||
<Logger name="org.xyzh.news.mapper" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Logger>
|
||||
|
||||
<!-- 项目包日志配置 - Auth模块 -->
|
||||
<Logger name="org.xyzh.auth" level="debug" additivity="false">
|
||||
@@ -129,6 +140,15 @@
|
||||
<AppenderRef ref="RollingFileError"/>
|
||||
</Logger>
|
||||
|
||||
<!-- 项目包日志配置 - Achievement模块 -->
|
||||
<Logger name="org.xyzh.achievement" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="Filelog"/>
|
||||
<AppenderRef ref="RollingFileInfo"/>
|
||||
<AppenderRef ref="RollingFileWarn"/>
|
||||
<AppenderRef ref="RollingFileError"/>
|
||||
</Logger>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="Console"/>
|
||||
<appender-ref ref="Filelog"/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.dept.TbSysDept;
|
||||
import org.xyzh.common.dto.dept.TbSysDeptRole;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
/**
|
||||
* @description DepartmentService.java文件描述 部门服务接口
|
||||
@@ -98,12 +99,12 @@ public interface DepartmentService {
|
||||
ResultDomain<TbSysRole> getDeptByRole(String deptId);
|
||||
|
||||
/**
|
||||
* @description 查询部门绑定角色
|
||||
* @return ResultDomain<TbSysDeptRole> 角色信息
|
||||
* @description 查询部门绑定角色列表(包含名称)
|
||||
* @return ResultDomain<UserDeptRoleVO> 部门角色信息
|
||||
* @author yslg
|
||||
* @since 2025-10-06
|
||||
*/
|
||||
ResultDomain<TbSysDeptRole> getDeptByRoleList();
|
||||
ResultDomain<UserDeptRoleVO> getDeptByRoleList();
|
||||
|
||||
/**
|
||||
* @description 绑定部门角色
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.xyzh.api.system.permission;
|
||||
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.permission.TbResourcePermission;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
/**
|
||||
* @description 资源权限控制服务接口
|
||||
* @filename ResourcePermissionService.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-29
|
||||
*/
|
||||
public interface ResourcePermissionService {
|
||||
|
||||
/**
|
||||
* @description 创建资源权限 根据用户
|
||||
* @param resource_type 资源类型
|
||||
* @param resource_id 资源ID
|
||||
* @param creatorID 创建者ID
|
||||
* @return ResultDomain<TbResourcePermission> 资源权限
|
||||
* @author yslg
|
||||
* @since 2025-10-29
|
||||
*/
|
||||
ResultDomain<TbResourcePermission> createResourcePermission(Integer resource_type, String resource_id, UserDeptRoleVO userDeptRole);
|
||||
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.permission.TbSysPermission;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.dto.role.TbSysRolePermission;
|
||||
import org.xyzh.common.vo.DeptRoleVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
|
||||
/**
|
||||
@@ -72,11 +72,11 @@ public interface RoleService {
|
||||
/**
|
||||
* @description 根据用户ID查询部门角色列表
|
||||
* @param userId 用户ID
|
||||
* @return ResultDomain<DeptRoleVO> 角色列表
|
||||
* @return ResultDomain<UserDeptRoleVO> 角色列表
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
ResultDomain<DeptRoleVO> getDeptRolesByUserId(String userId);
|
||||
ResultDomain<UserDeptRoleVO> getDeptRolesByUserId(String userId);
|
||||
|
||||
/**
|
||||
* @description 检查角色名称是否存在
|
||||
|
||||
@@ -169,7 +169,7 @@ public interface UserService {
|
||||
* @author yslg
|
||||
* @since 2025-10-09
|
||||
*/
|
||||
ResultDomain<TbSysUserDeptRole> getBindUserDeptRoleList(TbSysUserDeptRole filter);
|
||||
ResultDomain<UserDeptRoleVO> getBindUserDeptRoleList(TbSysUserDeptRole filter);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.xyzh.auth.config;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
@@ -26,6 +27,7 @@ public class SecurityConfig {
|
||||
@Autowired
|
||||
private AuthProperties authProperties;
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.xyzh.api.system.role.RoleService;
|
||||
import org.xyzh.api.system.permission.PermissionService;
|
||||
import org.xyzh.common.redis.service.RedisService;
|
||||
import org.xyzh.api.system.menu.MenuService;
|
||||
import org.xyzh.common.vo.DeptRoleVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -206,9 +206,9 @@ public class LoginServiceImpl implements LoginService {
|
||||
loginDomain.setIpAddress(ipAddress);
|
||||
// 获取用户角色和权限(如果服务可用)
|
||||
try {
|
||||
ResultDomain<DeptRoleVO> resultDomain = roleService.getDeptRolesByUserId(user.getID());
|
||||
ResultDomain<UserDeptRoleVO> resultDomain = roleService.getDeptRolesByUserId(user.getID());
|
||||
if (resultDomain.isSuccess()) {
|
||||
List<DeptRoleVO> roles = resultDomain.getDataList();
|
||||
List<UserDeptRoleVO> roles = resultDomain.getDataList();
|
||||
loginDomain.setRoles(roles);
|
||||
} else {
|
||||
loginDomain.setRoles(new ArrayList<>());
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.dto.permission.TbSysPermission;
|
||||
import org.xyzh.common.dto.dept.TbSysDeptRole;
|
||||
import org.xyzh.common.dto.menu.TbSysMenu;
|
||||
import org.xyzh.common.vo.DeptRoleVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
/**
|
||||
* @description LoginDomain.java文件描述 登录域对象
|
||||
* @filename LoginDomain.java
|
||||
@@ -40,7 +40,7 @@ public class LoginDomain implements Serializable {
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
private List<DeptRoleVO> roles;
|
||||
private List<UserDeptRoleVO> roles;
|
||||
|
||||
/**
|
||||
* @description 用户权限列表
|
||||
@@ -132,7 +132,7 @@ public class LoginDomain implements Serializable {
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
public List<DeptRoleVO> getRoles() {
|
||||
public List<UserDeptRoleVO> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ public class LoginDomain implements Serializable {
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
public void setRoles(List<DeptRoleVO> roles) {
|
||||
public void setRoles(List<UserDeptRoleVO> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.xyzh.common.core.enums;
|
||||
|
||||
public enum ResourceType {
|
||||
|
||||
NEWS(1, "新闻", "新闻"),
|
||||
COURSE(2, "课程", "课程"),
|
||||
TASK(3, "任务", "任务"),
|
||||
DEPT(4, "部门", "部门"),
|
||||
ROLE(5, "角色", "角色"),
|
||||
ACHIEVEMENT(6, "成就", "成就"),
|
||||
CRONTAB_TASK(7, "定时任务", "定时任务"),
|
||||
BANNER(8, "轮播图", "轮播图"),
|
||||
TAG(9, "标签", "标签");
|
||||
|
||||
private int code;
|
||||
private String name;
|
||||
private String description;
|
||||
|
||||
ResourceType(int code, String name, String description) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,13 @@ public class TbSysDept extends BaseDTO{
|
||||
*/
|
||||
private String parentID;
|
||||
|
||||
/**
|
||||
* @description 部门路径,格式:/root_department/dept_001/
|
||||
* @author yslg
|
||||
* @since 2025-10-29
|
||||
*/
|
||||
private String deptPath;
|
||||
|
||||
/**
|
||||
* @description 部门名称
|
||||
* @author yslg
|
||||
@@ -71,6 +78,14 @@ public class TbSysDept extends BaseDTO{
|
||||
this.parentID = parentID;
|
||||
}
|
||||
|
||||
public String getDeptPath() {
|
||||
return deptPath;
|
||||
}
|
||||
|
||||
public void setDeptPath(String deptPath) {
|
||||
this.deptPath = deptPath;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -109,6 +124,7 @@ public class TbSysDept extends BaseDTO{
|
||||
"id='" + getID() + '\'' +
|
||||
", deptID='" + deptID + '\'' +
|
||||
", parentID='" + parentID + '\'' +
|
||||
", deptPath='" + deptPath + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
", creator='" + creator + '\'' +
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package org.xyzh.common.vo;
|
||||
|
||||
import org.xyzh.common.dto.dept.TbSysDept;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DeptRoleVO {
|
||||
|
||||
private TbSysDept dept;
|
||||
private TbSysRole role;
|
||||
|
||||
private List<TbSysDept> depts;
|
||||
private List<TbSysRole> roles;
|
||||
private List<TbSysUser> users;
|
||||
|
||||
public TbSysDept getDept() {
|
||||
return dept;
|
||||
}
|
||||
|
||||
public void setDept(TbSysDept dept) {
|
||||
this.dept = dept;
|
||||
}
|
||||
|
||||
public TbSysRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(TbSysRole role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public List<TbSysDept> getDepts() {
|
||||
return depts;
|
||||
}
|
||||
|
||||
public void setDepts(List<TbSysDept> depts) {
|
||||
this.depts = depts;
|
||||
}
|
||||
|
||||
public List<TbSysRole> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(List<TbSysRole> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public List<TbSysUser> getUsers() {
|
||||
return users;
|
||||
}
|
||||
|
||||
public void setUsers(List<TbSysUser> users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.xyzh.common.vo;
|
||||
|
||||
import org.xyzh.common.dto.permission.TbResourcePermission;
|
||||
|
||||
/**
|
||||
* @description 资源权限视图对象
|
||||
* @filename ResourcePermissionVO.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-29
|
||||
*/
|
||||
public class ResourcePermissionVO extends TbResourcePermission {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* @description 部门名称
|
||||
*/
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* @description 角色名称
|
||||
*/
|
||||
private String roleName;
|
||||
|
||||
/**
|
||||
* @description 资源标题(根据资源类型获取)
|
||||
*/
|
||||
private String resourceTitle;
|
||||
|
||||
/**
|
||||
* @description 查询用的用户ID(用于权限校验)
|
||||
*/
|
||||
private String userID;
|
||||
|
||||
/**
|
||||
* @description 用户的部门ID列表(用于权限校验)
|
||||
*/
|
||||
private String[] userDeptIDs;
|
||||
|
||||
/**
|
||||
* @description 用户的角色ID列表(用于权限校验)
|
||||
*/
|
||||
private String[] userRoleIDs;
|
||||
|
||||
public ResourcePermissionVO() {
|
||||
super();
|
||||
}
|
||||
|
||||
public String getDeptName() {
|
||||
return deptName;
|
||||
}
|
||||
|
||||
public void setDeptName(String deptName) {
|
||||
this.deptName = deptName;
|
||||
}
|
||||
|
||||
public String getRoleName() {
|
||||
return roleName;
|
||||
}
|
||||
|
||||
public void setRoleName(String roleName) {
|
||||
this.roleName = roleName;
|
||||
}
|
||||
|
||||
public String getResourceTitle() {
|
||||
return resourceTitle;
|
||||
}
|
||||
|
||||
public void setResourceTitle(String resourceTitle) {
|
||||
this.resourceTitle = resourceTitle;
|
||||
}
|
||||
|
||||
public String getUserID() {
|
||||
return userID;
|
||||
}
|
||||
|
||||
public void setUserID(String userID) {
|
||||
this.userID = userID;
|
||||
}
|
||||
|
||||
public String[] getUserDeptIDs() {
|
||||
return userDeptIDs;
|
||||
}
|
||||
|
||||
public void setUserDeptIDs(String[] userDeptIDs) {
|
||||
this.userDeptIDs = userDeptIDs;
|
||||
}
|
||||
|
||||
public String[] getUserRoleIDs() {
|
||||
return userRoleIDs;
|
||||
}
|
||||
|
||||
public void setUserRoleIDs(String[] userRoleIDs) {
|
||||
this.userRoleIDs = userRoleIDs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResourcePermissionVO{" +
|
||||
"id=" + getID() +
|
||||
", resourceType=" + getResourceType() +
|
||||
", resourceID='" + getResourceID() + '\'' +
|
||||
", resourceTitle='" + resourceTitle + '\'' +
|
||||
", deptID='" + getDeptID() + '\'' +
|
||||
", deptName='" + deptName + '\'' +
|
||||
", roleID='" + getRoleID() + '\'' +
|
||||
", roleName='" + roleName + '\'' +
|
||||
", canRead=" + getCanRead() +
|
||||
", canWrite=" + getCanWrite() +
|
||||
", canExecute=" + getCanExecute() +
|
||||
", creator='" + getCreator() + '\'' +
|
||||
", updater='" + getUpdater() + '\'' +
|
||||
", createTime=" + getCreateTime() +
|
||||
", updateTime=" + getUpdateTime() +
|
||||
", deleted=" + getDeleted() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,159 @@
|
||||
package org.xyzh.common.vo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
import org.xyzh.common.dto.dept.TbSysDept;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.dto.user.TbSysUserDeptRole;
|
||||
|
||||
public class UserDeptRoleVO extends BaseDTO{
|
||||
private TbSysUser user;
|
||||
private List<TbSysUser> users;
|
||||
import java.util.List;
|
||||
|
||||
public class UserDeptRoleVO {
|
||||
private List<TbSysDept> depts;
|
||||
private List<TbSysRole> roles;
|
||||
private List<TbSysUser> users;
|
||||
private List<TbSysUserDeptRole> userDeptRoles;
|
||||
|
||||
public TbSysUser getUser() {
|
||||
return user;
|
||||
}
|
||||
public void setUser(TbSysUser user) {
|
||||
this.user = user;
|
||||
}
|
||||
public List<TbSysUser> getUsers() {
|
||||
return users;
|
||||
}
|
||||
public void setUsers(List<TbSysUser> users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
|
||||
// 扁平化字段,用于权限查询优化
|
||||
private String userID;
|
||||
private String username;
|
||||
private String deptID;
|
||||
private String deptName;
|
||||
private String deptDescription;
|
||||
private String parentID;
|
||||
private String parentName;
|
||||
private String parentDescription;
|
||||
private String roleID;
|
||||
private String roleName;
|
||||
private String roleDescription;
|
||||
private String deptPath; // 部门路径,用于快速权限继承判断
|
||||
|
||||
public List<TbSysDept> getDepts() {
|
||||
return depts;
|
||||
}
|
||||
|
||||
public void setDepts(List<TbSysDept> depts) {
|
||||
this.depts = depts;
|
||||
}
|
||||
|
||||
public List<TbSysRole> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(List<TbSysRole> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
public List<TbSysUserDeptRole> getUserDeptRoles() {
|
||||
return userDeptRoles;
|
||||
}
|
||||
public void setUserDeptRoles(List<TbSysUserDeptRole> userDeptRoles) {
|
||||
this.userDeptRoles = userDeptRoles;
|
||||
|
||||
public List<TbSysUser> getUsers() {
|
||||
return users;
|
||||
}
|
||||
|
||||
|
||||
public void setUsers(List<TbSysUser> users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
public String getUserID() {
|
||||
return userID;
|
||||
}
|
||||
|
||||
public void setUserID(String userID) {
|
||||
this.userID = userID;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getDeptID() {
|
||||
return deptID;
|
||||
}
|
||||
|
||||
public void setDeptID(String deptID) {
|
||||
this.deptID = deptID;
|
||||
}
|
||||
|
||||
public String getDeptName() {
|
||||
return deptName;
|
||||
}
|
||||
|
||||
public void setDeptName(String deptName) {
|
||||
this.deptName = deptName;
|
||||
}
|
||||
|
||||
public String getDeptDescription() {
|
||||
return deptDescription;
|
||||
}
|
||||
|
||||
public void setDeptDescription(String deptDescription) {
|
||||
this.deptDescription = deptDescription;
|
||||
}
|
||||
|
||||
public String getParentID() {
|
||||
return parentID;
|
||||
}
|
||||
|
||||
public void setParentID(String parentID) {
|
||||
this.parentID = parentID;
|
||||
}
|
||||
|
||||
public String getParentName() {
|
||||
return parentName;
|
||||
}
|
||||
|
||||
public void setParentName(String parentName) {
|
||||
this.parentName = parentName;
|
||||
}
|
||||
|
||||
public String getParentDescription() {
|
||||
return parentDescription;
|
||||
}
|
||||
|
||||
public void setParentDescription(String parentDescription) {
|
||||
this.parentDescription = parentDescription;
|
||||
}
|
||||
|
||||
public String getRoleID() {
|
||||
return roleID;
|
||||
}
|
||||
|
||||
public void setRoleID(String roleID) {
|
||||
this.roleID = roleID;
|
||||
}
|
||||
|
||||
public String getRoleName() {
|
||||
return roleName;
|
||||
}
|
||||
|
||||
public void setRoleName(String roleName) {
|
||||
this.roleName = roleName;
|
||||
}
|
||||
|
||||
public String getRoleDescription() {
|
||||
return roleDescription;
|
||||
}
|
||||
|
||||
public void setRoleDescription(String roleDescription) {
|
||||
this.roleDescription = roleDescription;
|
||||
}
|
||||
|
||||
public String getDeptPath() {
|
||||
return deptPath;
|
||||
}
|
||||
|
||||
public void setDeptPath(String deptPath) {
|
||||
this.deptPath = deptPath;
|
||||
}
|
||||
|
||||
public List<TbSysUserDeptRole> getUserDeptRoles() {
|
||||
return userDeptRoles;
|
||||
}
|
||||
|
||||
public void setUserDeptRoles(List<TbSysUserDeptRole> userDeptRoles) {
|
||||
this.userDeptRoles = userDeptRoles;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@
|
||||
<artifactId>common-all</artifactId>
|
||||
<version>${school-news.version}</version>
|
||||
</dependency>
|
||||
<!-- System模块依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.xyzh</groupId>
|
||||
<artifactId>system</artifactId>
|
||||
<version>${school-news.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Web -->
|
||||
<dependency>
|
||||
|
||||
@@ -13,9 +13,15 @@ import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.crontab.TbCrontabTask;
|
||||
import org.xyzh.common.dto.crontab.TbCrontabLog;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.crontab.mapper.CrontabTaskMapper;
|
||||
import org.xyzh.crontab.mapper.CrontabLogMapper;
|
||||
import org.xyzh.crontab.scheduler.SchedulerManager;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.dto.user.TbSysUserDeptRole;
|
||||
import org.xyzh.common.core.enums.ResourceType;
|
||||
import org.xyzh.system.utils.LoginUtil;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Calendar;
|
||||
@@ -43,6 +49,9 @@ public class CrontabServiceImpl implements CrontabService {
|
||||
@Autowired
|
||||
private SchedulerManager schedulerManager;
|
||||
|
||||
@Autowired
|
||||
private ResourcePermissionService resourcePermissionService;
|
||||
|
||||
// ----------------定时任务管理--------------------------------
|
||||
|
||||
@Override
|
||||
@@ -77,6 +86,21 @@ public class CrontabServiceImpl implements CrontabService {
|
||||
if (result > 0) {
|
||||
logger.info("创建定时任务成功: {}", task.getTaskName());
|
||||
|
||||
// 创建定时任务资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
resourcePermissionService.createResourcePermission(
|
||||
ResourceType.CRONTAB_TASK.getCode(),
|
||||
task.getTaskId(),
|
||||
userDeptRoles.get(0)
|
||||
);
|
||||
logger.info("创建定时任务权限成功: {}", task.getTaskName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建定时任务权限异常,但不影响任务创建: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 如果任务状态为启动,则立即调度
|
||||
if (task.getStatus() == 1) {
|
||||
schedulerManager.scheduleTask(task);
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.resource.TbBanner;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -21,24 +22,26 @@ public interface BannerMapper extends BaseMapper<TbBanner> {
|
||||
/**
|
||||
* @description 查询Banner列表
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbBanner> Banner列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbBanner> selectBanners(TbBanner filter);
|
||||
List<TbBanner> selectBanners(@Param("filter") TbBanner filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
List<TbBanner> selectBannersLimit(@Param("filter") TbBanner filter, @Param("limit") Integer limit);
|
||||
List<TbBanner> selectBannersLimit(@Param("filter") TbBanner filter, @Param("limit") Integer limit, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
|
||||
/**
|
||||
* @description 分页查询Banner
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbBanner> Banner列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbBanner> selectBannersPage(@Param("filter") TbBanner filter, @Param("pageParam") PageParam pageParam);
|
||||
List<TbBanner> selectBannersPage(@Param("filter") TbBanner filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据Banner ID查询Banner信息
|
||||
@@ -133,9 +136,10 @@ public interface BannerMapper extends BaseMapper<TbBanner> {
|
||||
/**
|
||||
* @description 统计Banner总数
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return long 总数
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
long countBanners(@Param("filter") TbBanner filter);
|
||||
long countBanners(@Param("filter") TbBanner filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.resource.TbResource;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -21,11 +22,12 @@ public interface ResourceMapper extends BaseMapper<TbResource> {
|
||||
/**
|
||||
* @description 查询资源列表
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbResource> 资源列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbResource> selectResources(TbResource filter);
|
||||
List<TbResource> selectResources(@Param("filter") TbResource filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据资源ID查询资源信息
|
||||
@@ -149,20 +151,22 @@ public interface ResourceMapper extends BaseMapper<TbResource> {
|
||||
* @description 分页查询资源
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbResource> 资源列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbResource> selectResourcesPage(@Param("filter") TbResource filter, @Param("pageParam") PageParam pageParam);
|
||||
List<TbResource> selectResourcesPage(@Param("filter") TbResource filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 统计资源总数
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return long 总数
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
long countResources(TbResource filter);
|
||||
long countResources(@Param("filter") TbResource filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 更新资源收藏次数
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.resource.TbTag;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -21,11 +22,12 @@ public interface TagMapper extends BaseMapper<TbTag> {
|
||||
/**
|
||||
* @description 查询标签列表
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbTag> 标签列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbTag> selectTags(TbTag filter);
|
||||
List<TbTag> selectTags(@Param("filter") TbTag filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据标签ID查询标签信息
|
||||
|
||||
@@ -17,6 +17,11 @@ import org.xyzh.common.dto.resource.TbBanner;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.news.mapper.BannerMapper;
|
||||
import org.xyzh.api.news.banner.BannerService;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.core.enums.ResourceType;
|
||||
import org.xyzh.system.utils.LoginUtil;
|
||||
|
||||
/**
|
||||
* @description 横幅服务实现类
|
||||
@@ -33,11 +38,16 @@ public class NCBannerServiceImpl implements BannerService {
|
||||
@Autowired
|
||||
private BannerMapper bannerMapper;
|
||||
|
||||
@Autowired
|
||||
private ResourcePermissionService resourcePermissionService;
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbBanner> getBannerList(TbBanner filter) {
|
||||
ResultDomain<TbBanner> resultDomain = new ResultDomain<>();
|
||||
|
||||
List<TbBanner> list = bannerMapper.selectBanners(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbBanner> list = bannerMapper.selectBanners(filter, userDeptRoles);
|
||||
resultDomain.success("获取横幅列表成功", list);
|
||||
return resultDomain;
|
||||
}
|
||||
@@ -45,9 +55,11 @@ public class NCBannerServiceImpl implements BannerService {
|
||||
@Override
|
||||
public ResultDomain<TbBanner> getBannerPage(PageParam pageParam,TbBanner filter) {
|
||||
ResultDomain<TbBanner> resultDomain = new ResultDomain<>();
|
||||
List<TbBanner> list = bannerMapper.selectBannersPage(filter, pageParam);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbBanner> list = bannerMapper.selectBannersPage(filter, pageParam, userDeptRoles);
|
||||
PageDomain<TbBanner> pageDomain = new PageDomain<>();
|
||||
int total = (int)bannerMapper.countBanners(filter);
|
||||
int total = (int)bannerMapper.countBanners(filter, userDeptRoles);
|
||||
pageParam.setTotalElements(total);
|
||||
pageParam.setTotalPages( (int)Math.ceil((double)total / pageParam.getPageSize()));
|
||||
pageDomain.setDataList(list);
|
||||
@@ -100,6 +112,23 @@ public class NCBannerServiceImpl implements BannerService {
|
||||
int result = bannerMapper.insertBanner(banner);
|
||||
if (result > 0) {
|
||||
logger.info("创建横幅成功: {}", banner.getTitle());
|
||||
|
||||
// 创建横幅资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
// 使用用户的第一个部门角色创建权限
|
||||
resourcePermissionService.createResourcePermission(
|
||||
ResourceType.BANNER.getCode(),
|
||||
banner.getBannerID(),
|
||||
userDeptRoles.get(0)
|
||||
);
|
||||
logger.info("创建横幅权限成功: {}", banner.getBannerID());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建横幅权限异常,但不影响横幅创建: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
resultDomain.success("创建横幅成功", banner);
|
||||
return resultDomain;
|
||||
} else {
|
||||
@@ -363,8 +392,9 @@ public class NCBannerServiceImpl implements BannerService {
|
||||
ResultDomain<TbBanner> resultDomain = new ResultDomain<>();
|
||||
TbBanner filter = new TbBanner();
|
||||
filter.setStatus(1);
|
||||
|
||||
List<TbBanner> list = bannerMapper.selectBannersLimit(filter, 5);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbBanner> list = bannerMapper.selectBannersLimit(filter, 5, userDeptRoles);
|
||||
resultDomain.success("获取首页横幅列表成功", list);
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ import org.xyzh.news.mapper.ResourceTagMapper;
|
||||
import org.xyzh.system.utils.LoginUtil;
|
||||
import org.xyzh.api.news.resource.ResourceService;
|
||||
import org.xyzh.api.usercenter.collection.UserCollectionService;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.core.enums.ResourceType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
@@ -49,6 +52,9 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
@Autowired
|
||||
private UserCollectionService userCollectionService;
|
||||
|
||||
@Autowired
|
||||
private ResourcePermissionService resourcePermissionService;
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbResource> getResourceList(TbResource filter) {
|
||||
ResultDomain<TbResource> resultDomain = new ResultDomain<>();
|
||||
@@ -56,7 +62,9 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
if (filter == null) {
|
||||
filter = new TbResource();
|
||||
}
|
||||
List<TbResource> list = resourceMapper.selectResources(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbResource> list = resourceMapper.selectResources(filter, userDeptRoles);
|
||||
resultDomain.success("获取资源列表成功", list);
|
||||
return resultDomain;
|
||||
} catch (Exception e) {
|
||||
@@ -74,8 +82,10 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
filter = new TbResource();
|
||||
}
|
||||
|
||||
List<TbResource> list = resourceMapper.selectResourcesPage(filter, pageParam);
|
||||
long total = resourceMapper.countResources(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbResource> list = resourceMapper.selectResourcesPage(filter, pageParam, userDeptRoles);
|
||||
long total = resourceMapper.countResources(filter, userDeptRoles);
|
||||
pageParam.setTotalElements(total);
|
||||
pageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
|
||||
resultDomain.success("获取资源分页成功", new PageDomain<TbResource>(pageParam, list));
|
||||
@@ -192,6 +202,23 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("创建资源成功: {}", resourceVO.getResource().getTitle());
|
||||
|
||||
// 创建资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
// 使用用户的第一个部门角色创建权限
|
||||
resourcePermissionService.createResourcePermission(
|
||||
ResourceType.NEWS.getCode(),
|
||||
resourceVO.getResource().getResourceID(),
|
||||
userDeptRoles.get(0)
|
||||
);
|
||||
logger.info("创建资源权限成功: {}", resourceVO.getResource().getResourceID());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建资源权限异常,但不影响资源创建: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
resultDomain.success("创建资源成功", resourceVO);
|
||||
return resultDomain;
|
||||
} else {
|
||||
@@ -679,7 +706,9 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
filter.setIsRecommend(true);
|
||||
filter.setStatus(1); // 只查询已发布的
|
||||
|
||||
List<TbResource> list = resourceMapper.selectResources(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbResource> list = resourceMapper.selectResources(filter, userDeptRoles);
|
||||
|
||||
// 如果指定了limit,截取列表
|
||||
if (limit != null && limit > 0 && list != null && list.size() > limit) {
|
||||
@@ -704,7 +733,9 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
filter.setIsBanner(true);
|
||||
filter.setStatus(1); // 只查询已发布的
|
||||
|
||||
List<TbResource> list = resourceMapper.selectResources(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbResource> list = resourceMapper.selectResources(filter, userDeptRoles);
|
||||
|
||||
// 如果指定了limit,截取列表
|
||||
if (limit != null && limit > 0 && list != null && list.size() > limit) {
|
||||
|
||||
@@ -19,6 +19,9 @@ import org.xyzh.news.mapper.ResourceTagMapper;
|
||||
import org.xyzh.news.mapper.TagMapper;
|
||||
import org.xyzh.system.utils.LoginUtil;
|
||||
import org.xyzh.api.news.tag.TagService;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.core.enums.ResourceType;
|
||||
|
||||
/**
|
||||
* @description 标签服务实现类
|
||||
@@ -38,6 +41,9 @@ public class NCTagServiceImpl implements TagService {
|
||||
@Autowired
|
||||
private ResourceTagMapper resourceTagMapper;
|
||||
|
||||
@Autowired
|
||||
private ResourcePermissionService resourcePermissionService;
|
||||
|
||||
// ----------------标签管理相关--------------------------------
|
||||
|
||||
@Override
|
||||
@@ -74,6 +80,23 @@ public class NCTagServiceImpl implements TagService {
|
||||
int result = tagMapper.insertTag(tag);
|
||||
if (result > 0) {
|
||||
logger.info("创建标签成功: {}", tag.getName());
|
||||
|
||||
// 创建标签资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
// 使用用户的第一个部门角色创建权限
|
||||
resourcePermissionService.createResourcePermission(
|
||||
ResourceType.TAG.getCode(),
|
||||
tag.getTagID(),
|
||||
userDeptRoles.get(0)
|
||||
);
|
||||
logger.info("创建标签权限成功: {}", tag.getTagID());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建标签权限异常,但不影响标签创建: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
resultDomain.success("创建标签成功", tag);
|
||||
return resultDomain;
|
||||
} else {
|
||||
@@ -195,7 +218,9 @@ public class NCTagServiceImpl implements TagService {
|
||||
public ResultDomain<TbTag> getAllTags() {
|
||||
ResultDomain<TbTag> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
List<TbTag> tags = tagMapper.selectTags(new TbTag());
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbTag> tags = tagMapper.selectTags(new TbTag(), userDeptRoles);
|
||||
resultDomain.success("查询成功", tags);
|
||||
return resultDomain;
|
||||
} catch (Exception e) {
|
||||
@@ -216,7 +241,9 @@ public class NCTagServiceImpl implements TagService {
|
||||
|
||||
TbTag filter = new TbTag();
|
||||
filter.setName(name);
|
||||
List<TbTag> tags = tagMapper.selectTags(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbTag> tags = tagMapper.selectTags(filter, userDeptRoles);
|
||||
|
||||
resultDomain.success("查询成功", tags);
|
||||
return resultDomain;
|
||||
|
||||
@@ -69,21 +69,83 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- selectBanners -->
|
||||
<!-- 权限过滤条件(基于dept_path的高效继承) -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_resource_permission rp ON b.banner_id = rp.resource_id
|
||||
AND rp.resource_type = 8
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
-- 全局权限:所有用户可访问
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM (
|
||||
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||
SELECT #{udr.deptID} AS dept_id, #{udr.deptPath} AS dept_path, #{udr.roleID} AS role_id
|
||||
</foreach>
|
||||
) user_roles
|
||||
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||
WHERE
|
||||
-- 部门级权限:当前部门或父部门(通过dept_path判断继承关系)
|
||||
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||
-- 角色级权限:跨部门的角色权限
|
||||
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||
-- 精确权限:特定部门的特定角色
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
)
|
||||
</sql>
|
||||
|
||||
<!-- selectBanners - 添加权限过滤 -->
|
||||
<select id="selectBanners" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_banner
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT b.*
|
||||
FROM tb_banner b
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE b.deleted = 0
|
||||
<if test="filter.bannerID != null and filter.bannerID != ''">
|
||||
AND b.banner_id = #{filter.bannerID}
|
||||
</if>
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND b.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.linkType != null">
|
||||
AND b.link_type = #{filter.linkType}
|
||||
</if>
|
||||
<if test="filter.linkID != null and filter.linkID != ''">
|
||||
AND b.link_id = #{filter.linkID}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND b.status = #{filter.status}
|
||||
</if>
|
||||
ORDER BY b.order_num ASC, b.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- selectBannersLimit - 添加权限过滤 -->
|
||||
<select id="selectBannersLimit" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_banner
|
||||
<include refid="Filter_Clause"/>
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT b.*
|
||||
FROM tb_banner b
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE b.deleted = 0
|
||||
<if test="filter.bannerID != null and filter.bannerID != ''">
|
||||
AND b.banner_id = #{filter.bannerID}
|
||||
</if>
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND b.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.linkType != null">
|
||||
AND b.link_type = #{filter.linkType}
|
||||
</if>
|
||||
<if test="filter.linkID != null and filter.linkID != ''">
|
||||
AND b.link_id = #{filter.linkID}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND b.status = #{filter.status}
|
||||
</if>
|
||||
ORDER BY b.order_num ASC, b.create_time DESC
|
||||
LIMIT #{limit}
|
||||
</select>
|
||||
|
||||
@@ -218,20 +280,56 @@
|
||||
</delete>
|
||||
|
||||
<!-- 分页查询Banner -->
|
||||
<!-- selectBannersPage - 添加权限过滤 -->
|
||||
<select id="selectBannersPage" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_banner
|
||||
<include refid="Filter_Clause" />
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT b.*
|
||||
FROM tb_banner b
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE b.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.bannerID != null and filter.bannerID != ''">
|
||||
AND b.banner_id = #{filter.bannerID}
|
||||
</if>
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND b.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.linkType != null">
|
||||
AND b.link_type = #{filter.linkType}
|
||||
</if>
|
||||
<if test="filter.linkID != null and filter.linkID != ''">
|
||||
AND b.link_id = #{filter.linkID}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND b.status = #{filter.status}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY b.order_num ASC, b.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<!-- 统计Banner总数 -->
|
||||
<!-- 统计Banner总数 - 添加权限过滤 -->
|
||||
<select id="countBanners" resultType="long">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_banner
|
||||
<include refid="Filter_Clause" />
|
||||
SELECT COUNT(DISTINCT b.id)
|
||||
FROM tb_banner b
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE b.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.bannerID != null and filter.bannerID != ''">
|
||||
AND b.banner_id = #{filter.bannerID}
|
||||
</if>
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND b.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.linkType != null">
|
||||
AND b.link_type = #{filter.linkType}
|
||||
</if>
|
||||
<if test="filter.linkID != null and filter.linkID != ''">
|
||||
AND b.link_id = #{filter.linkID}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND b.status = #{filter.status}
|
||||
</if>
|
||||
</if>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -62,13 +62,62 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- selectResources -->
|
||||
<!-- 权限过滤条件(基于dept_path的高效继承) -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_resource_permission rp ON r.resource_id = rp.resource_id
|
||||
AND rp.resource_type = 1
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
-- 全局权限:所有用户可访问
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM (
|
||||
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||
SELECT #{udr.deptID} AS dept_id, #{udr.deptPath} AS dept_path, #{udr.roleID} AS role_id
|
||||
</foreach>
|
||||
) user_roles
|
||||
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||
WHERE
|
||||
-- 部门级权限:当前部门或父部门(通过dept_path判断继承关系)
|
||||
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||
-- 角色级权限:跨部门的角色权限
|
||||
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||
-- 精确权限:特定部门的特定角色
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
)
|
||||
</sql>
|
||||
|
||||
<!-- selectResources - 添加权限过滤 -->
|
||||
<select id="selectResources" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_resource
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY publish_time DESC, create_time DESC
|
||||
SELECT DISTINCT r.*
|
||||
FROM tb_resource r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE r.deleted = 0
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND r.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.tagID != null and filter.tagID != ''">
|
||||
AND r.tag_id = #{filter.tagID}
|
||||
</if>
|
||||
<if test="filter.author != null and filter.author != ''">
|
||||
AND r.author LIKE CONCAT('%', #{filter.author}, '%')
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND r.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.isRecommend != null">
|
||||
AND r.is_recommend = #{filter.isRecommend}
|
||||
</if>
|
||||
<if test="filter.isBanner != null">
|
||||
AND r.is_banner = #{filter.isBanner}
|
||||
</if>
|
||||
ORDER BY r.publish_time DESC, r.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据资源ID查询资源信息 -->
|
||||
@@ -266,41 +315,58 @@
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<!-- 分页查询资源 -->
|
||||
<!-- 分页查询资源 - 添加权限过滤 -->
|
||||
<select id="selectResourcesPage" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_resource
|
||||
<where>
|
||||
deleted = 0
|
||||
SELECT DISTINCT r.*
|
||||
FROM tb_resource r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE r.deleted = 0
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
AND r.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.tagID != null and filter.tagID != ''">
|
||||
AND tag_id = #{filter.tagID}
|
||||
AND r.tag_id = #{filter.tagID}
|
||||
</if>
|
||||
<if test="filter.author != null and filter.author != ''">
|
||||
AND author LIKE CONCAT('%', #{filter.author}, '%')
|
||||
AND r.author LIKE CONCAT('%', #{filter.author}, '%')
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND status = #{filter.status}
|
||||
AND r.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.isRecommend != null">
|
||||
AND is_recommend = #{filter.isRecommend}
|
||||
AND r.is_recommend = #{filter.isRecommend}
|
||||
</if>
|
||||
<if test="filter.isBanner != null">
|
||||
AND is_banner = #{filter.isBanner}
|
||||
AND r.is_banner = #{filter.isBanner}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY publish_time DESC, create_time DESC
|
||||
ORDER BY r.publish_time DESC, r.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<!-- 统计资源总数 -->
|
||||
<!-- 统计资源总数 - 添加权限过滤 -->
|
||||
<select id="countResources" resultType="long">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_resource
|
||||
<include refid="Where_Clause" />
|
||||
SELECT COUNT(DISTINCT r.id)
|
||||
FROM tb_resource r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE r.deleted = 0
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND r.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.tagID != null and filter.tagID != ''">
|
||||
AND r.tag_id = #{filter.tagID}
|
||||
</if>
|
||||
<if test="filter.author != null and filter.author != ''">
|
||||
AND r.author LIKE CONCAT('%', #{filter.author}, '%')
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND r.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.isRecommend != null">
|
||||
AND r.is_recommend = #{filter.isRecommend}
|
||||
</if>
|
||||
<if test="filter.isBanner != null">
|
||||
AND r.is_banner = #{filter.isBanner}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- updateResourceCollectCount -->
|
||||
@@ -318,4 +384,6 @@
|
||||
SET view_count = view_count + 1
|
||||
WHERE resource_id = #{resourceID}
|
||||
</update>
|
||||
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -43,13 +43,56 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- selectTags -->
|
||||
<!-- 权限过滤条件(基于dept_path的高效继承) -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_resource_permission rp ON t.tag_id = rp.resource_id
|
||||
AND rp.resource_type = 9
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
-- 全局权限:所有用户可访问
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM (
|
||||
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||
SELECT #{udr.deptID} AS dept_id, #{udr.deptPath} AS dept_path, #{udr.roleID} AS role_id
|
||||
</foreach>
|
||||
) user_roles
|
||||
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||
WHERE
|
||||
-- 部门级权限:当前部门或父部门(通过dept_path判断继承关系)
|
||||
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||
-- 角色级权限:跨部门的角色权限
|
||||
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||
-- 精确权限:特定部门的特定角色
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
)
|
||||
</sql>
|
||||
|
||||
<!-- selectTags - 添加权限过滤 -->
|
||||
<select id="selectTags" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_tag
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT t.*
|
||||
FROM tb_tag t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.deleted = 0
|
||||
<if test="filter.tagID != null and filter.tagID != ''">
|
||||
AND t.tag_id = #{filter.tagID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND t.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.color != null and filter.color != ''">
|
||||
AND t.color = #{filter.color}
|
||||
</if>
|
||||
<if test="filter.tagType != null">
|
||||
AND t.tag_type = #{filter.tagType}
|
||||
</if>
|
||||
ORDER BY t.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据标签ID查询标签信息 -->
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.study.TbCourse;
|
||||
import org.xyzh.common.vo.CourseItemVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -22,11 +22,12 @@ public interface CourseMapper extends BaseMapper<TbCourse> {
|
||||
/**
|
||||
* @description 查询课程列表
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCourse> 课程列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbCourse> selectCourses(TbCourse filter);
|
||||
List<TbCourse> selectCourses(@Param("filter") TbCourse filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据课程ID查询课程信息
|
||||
@@ -159,20 +160,22 @@ public interface CourseMapper extends BaseMapper<TbCourse> {
|
||||
* @description 分页查询课程
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCourse> 课程列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbCourse> selectCoursesPage(@Param("filter") TbCourse filter, @Param("pageParam") PageParam pageParam);
|
||||
List<TbCourse> selectCoursesPage(@Param("filter") TbCourse filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 统计课程总数
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return long 总数
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
long countCourses(@Param("filter") TbCourse filter);
|
||||
long countCourses(@Param("filter") TbCourse filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 增加课程浏览次数
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.study.TbLearningTask;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.vo.TaskItemVO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -22,11 +23,12 @@ public interface LearningTaskMapper extends BaseMapper<TbLearningTask> {
|
||||
/**
|
||||
* @description 查询学习任务列表
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbLearningTask> 学习任务列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbLearningTask> selectLearningTasks(TbLearningTask filter);
|
||||
List<TbLearningTask> selectLearningTasks(@Param("filter") TbLearningTask filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据任务ID查询任务信息
|
||||
@@ -150,20 +152,22 @@ public interface LearningTaskMapper extends BaseMapper<TbLearningTask> {
|
||||
* @description 分页查询学习任务
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbLearningTask> 学习任务列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbLearningTask> selectLearningTasksPage(@Param("filter") TbLearningTask filter, @Param("pageParam") PageParam pageParam);
|
||||
List<TbLearningTask> selectLearningTasksPage(@Param("filter") TbLearningTask filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
List<TbLearningTask> selectUserLearningTasksPage(@Param("filter") TaskItemVO filter, @Param("pageParam") PageParam pageParam);
|
||||
List<TbLearningTask> selectUserLearningTasksPage(@Param("filter") TaskItemVO filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 统计学习任务总数
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return long 总数
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
long countLearningTasks(@Param("filter") TbLearningTask filter);
|
||||
long countLearningTasks(@Param("filter") TbLearningTask filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ import org.xyzh.study.mapper.CourseChapterMapper;
|
||||
import org.xyzh.study.mapper.CourseNodeMapper;
|
||||
import org.xyzh.study.service.SCCourseService;
|
||||
import org.xyzh.system.utils.LoginUtil;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.core.enums.ResourceType;
|
||||
|
||||
/**
|
||||
* @description 课程服务实现类
|
||||
@@ -50,10 +53,15 @@ public class SCCourseServiceImpl implements SCCourseService {
|
||||
@Autowired
|
||||
private CourseNodeMapper courseNodeMapper;
|
||||
|
||||
@Autowired
|
||||
private ResourcePermissionService resourcePermissionService;
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbCourse> getCourseList(TbCourse filter) {
|
||||
ResultDomain<TbCourse> resultDomain = new ResultDomain<>();
|
||||
List<TbCourse> courses = courseMapper.selectCourses(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbCourse> courses = courseMapper.selectCourses(filter, userDeptRoles);
|
||||
resultDomain.success("获取课程列表成功", courses);
|
||||
return resultDomain;
|
||||
}
|
||||
@@ -63,8 +71,10 @@ public class SCCourseServiceImpl implements SCCourseService {
|
||||
ResultDomain<TbCourse> resultDomain = new ResultDomain<>();
|
||||
TbCourse filter = pageRequest.getFilter();
|
||||
PageParam pageParam = pageRequest.getPageParam();
|
||||
List<TbCourse> courses = courseMapper.selectCoursesPage(filter, pageParam);
|
||||
int total = (int) courseMapper.countCourses(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbCourse> courses = courseMapper.selectCoursesPage(filter, pageParam, userDeptRoles);
|
||||
int total = (int) courseMapper.countCourses(filter, userDeptRoles);
|
||||
int totalPages = (int) Math.ceil((double) total / pageParam.getPageSize());
|
||||
pageParam.setTotalPages(totalPages);
|
||||
pageParam.setTotalElements(total);
|
||||
@@ -194,6 +204,22 @@ public class SCCourseServiceImpl implements SCCourseService {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建课程资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
// 使用用户的第一个部门角色创建权限
|
||||
resourcePermissionService.createResourcePermission(
|
||||
ResourceType.COURSE.getCode(),
|
||||
courseID,
|
||||
userDeptRoles.get(0)
|
||||
);
|
||||
logger.info("创建课程权限成功: {}", courseID);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建课程权限异常,但不影响课程创建: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
resultDomain.success("创建课程成功", courseItemVO);
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ import org.xyzh.system.utils.LoginUtil;
|
||||
import org.xyzh.study.mapper.TaskItemMapper;
|
||||
import org.xyzh.api.study.task.LearningTaskService;
|
||||
import org.xyzh.common.core.enums.TaskItemType;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.core.enums.ResourceType;
|
||||
|
||||
/**
|
||||
* @description 学习任务服务实现类
|
||||
@@ -55,6 +58,9 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
|
||||
@Autowired
|
||||
private TaskItemMapper taskItemMapper;
|
||||
|
||||
@Autowired
|
||||
private ResourcePermissionService resourcePermissionService;
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbTaskItem> addTaskCourse(TbTaskItem taskItem) {
|
||||
// TODO Auto-generated method stub
|
||||
@@ -105,8 +111,10 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
|
||||
@Override
|
||||
public ResultDomain<TbLearningTask> getTaskPage(TbLearningTask filter, PageParam pageParam) {
|
||||
ResultDomain<TbLearningTask> resultDomain = new ResultDomain<>();
|
||||
List<TbLearningTask> taskList = learningTaskMapper.selectLearningTasksPage(filter, pageParam);
|
||||
long total = learningTaskMapper.countLearningTasks(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbLearningTask> taskList = learningTaskMapper.selectLearningTasksPage(filter, pageParam, userDeptRoles);
|
||||
long total = learningTaskMapper.countLearningTasks(filter, userDeptRoles);
|
||||
pageParam.setTotalElements(total);
|
||||
pageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
|
||||
PageDomain<TbLearningTask> pageDomain = new PageDomain<>();
|
||||
@@ -125,8 +133,10 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
|
||||
return resultDomain;
|
||||
}
|
||||
filter.setUserID(user.getID());
|
||||
List<TbLearningTask> taskList = learningTaskMapper.selectUserLearningTasksPage(filter, pageParam);
|
||||
long total = learningTaskMapper.countLearningTasks(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbLearningTask> taskList = learningTaskMapper.selectUserLearningTasksPage(filter, pageParam, userDeptRoles);
|
||||
long total = learningTaskMapper.countLearningTasks(filter, userDeptRoles);
|
||||
pageParam.setTotalElements(total);
|
||||
pageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
|
||||
PageDomain<TbLearningTask> pageDomain = new PageDomain<>();
|
||||
@@ -188,7 +198,22 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
|
||||
taskUserMapper.batchInsertTaskUsers(taskUsers);
|
||||
for(TbTaskItem item : taskCourses) {
|
||||
int learnCount = courseMapper.incrementLearnCount(item.getItemID(), taskUsers.size());
|
||||
|
||||
}
|
||||
|
||||
// 创建任务资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
// 使用用户的第一个部门角色创建权限
|
||||
resourcePermissionService.createResourcePermission(
|
||||
ResourceType.TASK.getCode(),
|
||||
taskID,
|
||||
userDeptRoles.get(0)
|
||||
);
|
||||
logger.info("创建任务权限成功: {}", taskID);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建任务权限异常,但不影响任务创建: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
resultDomain.success("创建任务成功", taskVO);
|
||||
@@ -515,7 +540,9 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
|
||||
@Override
|
||||
public ResultDomain<TbLearningTask> getTaskList(TbLearningTask filter) {
|
||||
ResultDomain<TbLearningTask> resultDomain = new ResultDomain<>();
|
||||
List<TbLearningTask> taskList = learningTaskMapper.selectLearningTasks(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbLearningTask> taskList = learningTaskMapper.selectLearningTasks(filter, userDeptRoles);
|
||||
resultDomain.success("获取任务列表成功", taskList);
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
@@ -78,13 +78,56 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- selectCourses -->
|
||||
<!-- 权限过滤条件(基于dept_path的高效继承) -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_resource_permission rp ON c.course_id = rp.resource_id
|
||||
AND rp.resource_type = 2
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
-- 全局权限:所有用户可访问
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM (
|
||||
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||
SELECT #{udr.deptID} AS dept_id, #{udr.deptPath} AS dept_path, #{udr.roleID} AS role_id
|
||||
</foreach>
|
||||
) user_roles
|
||||
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||
WHERE
|
||||
-- 部门级权限:当前部门或父部门(通过dept_path判断继承关系)
|
||||
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||
-- 角色级权限:跨部门的角色权限
|
||||
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||
-- 精确权限:特定部门的特定角色
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
)
|
||||
</sql>
|
||||
|
||||
<!-- selectCourses - 添加权限过滤 -->
|
||||
<select id="selectCourses" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_course
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT c.*
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE c.deleted = 0
|
||||
<if test="filter.courseID != null and filter.courseID != ''">
|
||||
AND c.course_id = #{filter.courseID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND c.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.teacher != null and filter.teacher != ''">
|
||||
AND c.teacher LIKE CONCAT('%', #{filter.teacher}, '%')
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND c.status = #{filter.status}
|
||||
</if>
|
||||
ORDER BY c.order_num ASC, c.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据课程ID查询课程信息 -->
|
||||
@@ -270,20 +313,62 @@
|
||||
</delete>
|
||||
|
||||
<!-- 分页查询课程 -->
|
||||
<!-- selectCoursesPage - 添加权限过滤 -->
|
||||
<select id="selectCoursesPage" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_course
|
||||
<include refid="Filter_Clause" />
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT c.*
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE c.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.courseID != null and filter.courseID != ''">
|
||||
AND c.course_id = #{filter.courseID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND c.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.teacher != null and filter.teacher != ''">
|
||||
AND c.teacher LIKE CONCAT('%', #{filter.teacher}, '%')
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND c.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.orderNum != null">
|
||||
AND c.order_num = #{filter.orderNum}
|
||||
</if>
|
||||
<if test="filter.creator != null and filter.creator != ''">
|
||||
AND c.creator = #{filter.creator}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY c.order_num ASC, c.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<!-- 统计课程总数 -->
|
||||
<!-- 统计课程总数 - 添加权限过滤 -->
|
||||
<select id="countCourses" resultType="long">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_course
|
||||
<include refid="Filter_Clause" />
|
||||
SELECT COUNT(DISTINCT c.id)
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE c.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.courseID != null and filter.courseID != ''">
|
||||
AND c.course_id = #{filter.courseID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND c.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.teacher != null and filter.teacher != ''">
|
||||
AND c.teacher LIKE CONCAT('%', #{filter.teacher}, '%')
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND c.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.orderNum != null">
|
||||
AND c.order_num = #{filter.orderNum}
|
||||
</if>
|
||||
<if test="filter.creator != null and filter.creator != ''">
|
||||
AND c.creator = #{filter.creator}
|
||||
</if>
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<update id="incrementViewCount">
|
||||
|
||||
@@ -56,13 +56,53 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- selectLearningTasks -->
|
||||
<!-- 权限过滤条件(基于dept_path的高效继承) -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_resource_permission rp ON t.task_id = rp.resource_id
|
||||
AND rp.resource_type = 3
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
-- 全局权限:所有用户可访问
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM (
|
||||
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||
SELECT #{udr.deptID} AS dept_id, #{udr.deptPath} AS dept_path, #{udr.roleID} AS role_id
|
||||
</foreach>
|
||||
) user_roles
|
||||
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||
WHERE
|
||||
-- 部门级权限:当前部门或父部门(通过dept_path判断继承关系)
|
||||
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||
-- 角色级权限:跨部门的角色权限
|
||||
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||
-- 精确权限:特定部门的特定角色
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
)
|
||||
</sql>
|
||||
|
||||
<!-- selectLearningTasks - 添加权限过滤 -->
|
||||
<select id="selectLearningTasks" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_learning_task
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT t.*
|
||||
FROM tb_learning_task t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.deleted = 0
|
||||
<if test="filter.taskID != null and filter.taskID != ''">
|
||||
AND t.task_id = #{filter.taskID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND t.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND t.status = #{filter.status}
|
||||
</if>
|
||||
ORDER BY t.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据任务ID查询任务信息 -->
|
||||
@@ -215,33 +255,78 @@
|
||||
</delete>
|
||||
|
||||
<!-- 分页查询学习任务 -->
|
||||
<!-- selectLearningTasksPage - 添加权限过滤 -->
|
||||
<select id="selectLearningTasksPage" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_learning_task
|
||||
<include refid="Filter_Clause" />
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT t.*
|
||||
FROM tb_learning_task t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.taskID != null and filter.taskID != ''">
|
||||
AND t.task_id = #{filter.taskID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND t.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND t.status = #{filter.status}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY t.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<!-- selectUserLearningTasksPage - 添加权限过滤 -->
|
||||
<select id="selectUserLearningTasksPage" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
SELECT DISTINCT
|
||||
tlt.id, tlt.task_id, tlt.name, tlt.description, tlt.start_time, tlt.end_time, ttu.status,
|
||||
tlt.creator, tlt.updater, tlt.create_time, tlt.update_time
|
||||
FROM tb_task_user ttu
|
||||
INNER JOIN tb_learning_task tlt ON ttu.task_id = tlt.task_id
|
||||
INNER JOIN tb_resource_permission rp ON tlt.task_id = rp.resource_id
|
||||
AND rp.resource_type = 3
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM (
|
||||
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||
SELECT #{udr.deptID} AS dept_id, #{udr.roleID} AS role_id
|
||||
</foreach>
|
||||
) user_roles
|
||||
WHERE (rp.dept_id = user_roles.dept_id AND rp.role_id IS NULL)
|
||||
OR (rp.role_id = user_roles.role_id AND rp.dept_id IS NULL)
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
)
|
||||
WHERE ttu.user_id = #{filter.userID}
|
||||
AND tlt.deleted = 0
|
||||
AND ttu.deleted = 0
|
||||
ORDER BY create_time DESC
|
||||
ORDER BY tlt.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<!-- 统计学习任务总数 -->
|
||||
<!-- 统计学习任务总数 - 添加权限过滤 -->
|
||||
<select id="countLearningTasks" resultType="long">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_learning_task
|
||||
<include refid="Filter_Clause" />
|
||||
SELECT COUNT(DISTINCT t.id)
|
||||
FROM tb_learning_task t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.taskID != null and filter.taskID != ''">
|
||||
AND t.task_id = #{filter.taskID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND t.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND t.status = #{filter.status}
|
||||
</if>
|
||||
</if>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.dept.TbSysDept;
|
||||
import org.xyzh.common.dto.dept.TbSysDeptRole;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.vo.DeptRoleVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@@ -110,14 +110,13 @@ public class DeptController {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询部门绑定角色
|
||||
* @param dept 部门信息
|
||||
* @return ResultDomain<TbSysRole> 角色信息
|
||||
* @description 查询部门绑定角色列表(包含名称)
|
||||
* @return ResultDomain<UserDeptRoleVO> 部门角色信息
|
||||
* @author yslg
|
||||
* @since 2025-10-06
|
||||
*/
|
||||
@PostMapping("/role/list")
|
||||
public ResultDomain<TbSysDeptRole> getDeptByRoleList() {
|
||||
public ResultDomain<UserDeptRoleVO> getDeptByRoleList() {
|
||||
return deptService.getDeptByRoleList();
|
||||
}
|
||||
|
||||
@@ -129,7 +128,7 @@ public class DeptController {
|
||||
* @since 2025-10-06
|
||||
*/
|
||||
@PostMapping("/bind/role")
|
||||
public ResultDomain<TbSysDeptRole> bindDeptRole(@RequestBody DeptRoleVO deptRole) {
|
||||
public ResultDomain<TbSysDeptRole> bindDeptRole(@RequestBody UserDeptRoleVO deptRole) {
|
||||
List<String> deptIDs = deptRole.getDepts().stream().map(TbSysDept::getDeptID).collect(Collectors.toList());
|
||||
List<String> roleIDs = deptRole.getRoles().stream().map(TbSysRole::getRoleID).collect(Collectors.toList());
|
||||
return deptService.bindDeptRole(deptIDs, roleIDs);
|
||||
@@ -143,7 +142,7 @@ public class DeptController {
|
||||
* @since 2025-10-06
|
||||
*/
|
||||
@PostMapping("/unbind/role")
|
||||
public ResultDomain<TbSysDeptRole> unbindDeptRole(@RequestBody DeptRoleVO deptRole) {
|
||||
public ResultDomain<TbSysDeptRole> unbindDeptRole(@RequestBody UserDeptRoleVO deptRole) {
|
||||
List<String> deptIDs = deptRole.getDepts().stream().map(TbSysDept::getDeptID).collect(Collectors.toList());
|
||||
List<String> roleIDs = deptRole.getRoles().stream().map(TbSysRole::getRoleID).collect(Collectors.toList());
|
||||
return deptService.unbindDeptRole(deptIDs, roleIDs);
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.permission.TbSysPermission;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.dto.role.TbSysRolePermission;
|
||||
import org.xyzh.common.vo.DeptRoleVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
/**
|
||||
* @description RoleController.java文件描述 角色控制器
|
||||
|
||||
@@ -116,7 +116,7 @@ public class UserController {
|
||||
* @since 2025-10-09
|
||||
*/
|
||||
@PostMapping("/bind/deptrole/list")
|
||||
public ResultDomain<TbSysUserDeptRole> getBindUserDeptRoleList(@RequestBody TbSysUserDeptRole filter) {
|
||||
public ResultDomain<UserDeptRoleVO> getBindUserDeptRoleList(@RequestBody TbSysUserDeptRole filter) {
|
||||
return userService.getBindUserDeptRoleList(filter);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,18 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.enums.ResourceType;
|
||||
import org.xyzh.common.dto.dept.TbSysDept;
|
||||
import org.xyzh.common.dto.dept.TbSysDeptRole;
|
||||
import org.xyzh.common.dto.permission.TbResourcePermission;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.dto.user.TbSysUserDeptRole;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.system.department.service.SysDepartmentService;
|
||||
import org.xyzh.system.mapper.DepartmentMapper;
|
||||
@@ -38,6 +44,9 @@ public class SysDepartmentServiceImpl implements SysDepartmentService {
|
||||
@Autowired
|
||||
private DeptRoleMapper deptRoleMapper;
|
||||
|
||||
@Autowired
|
||||
private ResourcePermissionService resourcePermissionService;
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysDept> getAllDepartments() {
|
||||
ResultDomain<TbSysDept> resultDomain = new ResultDomain<>();
|
||||
@@ -46,7 +55,9 @@ public class SysDepartmentServiceImpl implements SysDepartmentService {
|
||||
logger.info("开始查询所有部门");
|
||||
TbSysDept filter = new TbSysDept();
|
||||
filter.setDeleted(false);
|
||||
List<TbSysDept> departments = departmentMapper.selectDepts(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbSysDept> departments = departmentMapper.selectDepts(filter, userDeptRoles);
|
||||
|
||||
logger.info("查询所有部门完成,共找到{}个部门", departments.size());
|
||||
resultDomain.success("查询成功", departments);
|
||||
@@ -65,7 +76,9 @@ public class SysDepartmentServiceImpl implements SysDepartmentService {
|
||||
ResultDomain<TbSysDept> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始查询部门列表");
|
||||
List<TbSysDept> departments = departmentMapper.selectDepts(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbSysDept> departments = departmentMapper.selectDepts(filter, userDeptRoles);
|
||||
if (departments.isEmpty()) {
|
||||
resultDomain.fail("未找到部门");
|
||||
return resultDomain;
|
||||
@@ -80,11 +93,19 @@ public class SysDepartmentServiceImpl implements SysDepartmentService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysDeptRole> getDeptByRoleList() {
|
||||
ResultDomain<TbSysDeptRole> resultDomain = new ResultDomain<>();
|
||||
List<TbSysDeptRole> deptRoles = deptRoleMapper.selectDeptRoleList();
|
||||
resultDomain.success("查询成功", deptRoles);
|
||||
return resultDomain;
|
||||
public ResultDomain<UserDeptRoleVO> getDeptByRoleList() {
|
||||
ResultDomain<UserDeptRoleVO> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始查询部门角色关联列表");
|
||||
List<UserDeptRoleVO> deptRoles = deptRoleMapper.selectDeptRoleList();
|
||||
logger.info("查询部门角色关联列表完成,共找到{}条记录", deptRoles.size());
|
||||
resultDomain.success("查询成功", deptRoles);
|
||||
return resultDomain;
|
||||
} catch (Exception e) {
|
||||
logger.error("查询部门角色关联列表失败", e);
|
||||
resultDomain.fail("查询部门角色关联列表失败:" + e.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,7 +121,9 @@ public class SysDepartmentServiceImpl implements SysDepartmentService {
|
||||
|
||||
TbSysDept filter = new TbSysDept();
|
||||
filter.setDeptID(deptId);
|
||||
List<TbSysDept> departments = departmentMapper.selectDepts(filter);
|
||||
// 获取当前用户的部门角色
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbSysDept> departments = departmentMapper.selectDepts(filter, userDeptRoles);
|
||||
TbSysDept department = departments.isEmpty() ? null : departments.get(0);
|
||||
|
||||
if (department == null) {
|
||||
@@ -140,6 +163,7 @@ public class SysDepartmentServiceImpl implements SysDepartmentService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbSysDept> createDepartment(TbSysDept department) {
|
||||
ResultDomain<TbSysDept> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
@@ -162,9 +186,17 @@ public class SysDepartmentServiceImpl implements SysDepartmentService {
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 获取当前用户
|
||||
TbSysUser currentUser = LoginUtil.getCurrentUser();
|
||||
if (currentUser == null) {
|
||||
resultDomain.fail("请先登录");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 设置基础信息
|
||||
department.setID(IDUtils.generateID());
|
||||
department.setDeptID(IDUtils.generateID());
|
||||
department.setCreator(currentUser.getID());
|
||||
department.setCreateTime(new Date());
|
||||
department.setDeleted(false);
|
||||
|
||||
@@ -173,6 +205,24 @@ public class SysDepartmentServiceImpl implements SysDepartmentService {
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("创建部门成功:{}", department.getName());
|
||||
|
||||
// 创建资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
ResultDomain<TbResourcePermission> permissionResult = resourcePermissionService.createResourcePermission(
|
||||
ResourceType.DEPT.getCode(),
|
||||
department.getDeptID(),
|
||||
userDeptRoles.get(0)
|
||||
);
|
||||
if (!permissionResult.isSuccess()) {
|
||||
logger.warn("创建部门权限失败:{}", permissionResult.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建部门权限异常", e);
|
||||
}
|
||||
|
||||
resultDomain.success("创建部门成功", department);
|
||||
return resultDomain;
|
||||
} else {
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.dept.TbSysDept;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -20,11 +21,12 @@ public interface DepartmentMapper extends BaseMapper<TbSysDept> {
|
||||
/**
|
||||
* @description 查询部门列表
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbSysDept> 部门列表
|
||||
* @author yslg
|
||||
* @since 2025-10-06
|
||||
*/
|
||||
List<TbSysDept> selectDepts(TbSysDept filter);
|
||||
List<TbSysDept> selectDepts(@Param("filter") TbSysDept filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据父部门ID查询子部门列表
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.dept.TbSysDeptRole;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
@Mapper
|
||||
public interface DeptRoleMapper extends BaseMapper<TbSysDeptRole> {
|
||||
@@ -22,12 +23,12 @@ public interface DeptRoleMapper extends BaseMapper<TbSysDeptRole> {
|
||||
List<TbSysRole> selectDeptRole(String deptId);
|
||||
|
||||
/**
|
||||
* @description 查询部门绑定角色
|
||||
* @return List<TbSysDeptRole> 部门角色列表
|
||||
* @description 查询部门绑定角色列表(包含名称)
|
||||
* @return List<UserDeptRoleVO> 部门角色列表
|
||||
* @author yslg
|
||||
* @since 2025-10-06
|
||||
*/
|
||||
List<TbSysDeptRole> selectDeptRoleList();
|
||||
List<UserDeptRoleVO> selectDeptRoleList();
|
||||
|
||||
/**
|
||||
* @description 批量绑定部门角色
|
||||
@@ -39,4 +40,8 @@ public interface DeptRoleMapper extends BaseMapper<TbSysDeptRole> {
|
||||
int batchBindDeptRole(@Param("deptRoles") List<TbSysDeptRole> deptRoles);
|
||||
|
||||
int batchUnbindDeptRole(@Param("deptRoles") List<TbSysDeptRole> deptRoles);
|
||||
|
||||
List<TbSysDeptRole> selectParentDeptAdmin(UserDeptRoleVO userDeptRole);
|
||||
|
||||
List<TbSysDeptRole> selectChildDeptRole(UserDeptRoleVO userDeptRole);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.xyzh.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.permission.TbResourcePermission;
|
||||
import org.xyzh.common.vo.ResourcePermissionVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 资源权限Mapper接口
|
||||
* @filename ResourcePermissionMapper.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-29
|
||||
*/
|
||||
@Mapper
|
||||
public interface ResourcePermissionMapper extends BaseMapper<TbResourcePermission> {
|
||||
|
||||
/**
|
||||
* @description 插入资源权限
|
||||
* @param permission 资源权限对象
|
||||
* @return int 插入结果
|
||||
* @author yslg
|
||||
* @since 2025-10-29
|
||||
*/
|
||||
int insertResourcePermission(TbResourcePermission permission);
|
||||
|
||||
/**
|
||||
* @description 批量插入资源权限
|
||||
* @param permissions 资源权限列表
|
||||
* @return int 插入结果
|
||||
* @author yslg
|
||||
* @since 2025-10-29
|
||||
*/
|
||||
int batchInsertResourcePermission(@Param("list") List<TbResourcePermission> permissions);
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.vo.DeptRoleVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -65,11 +65,11 @@ public interface RoleMapper extends BaseMapper<TbSysRole> {
|
||||
/**
|
||||
* @description 根据用户ID查询角色列表
|
||||
* @param userId 用户ID
|
||||
* @return List<DeptRoleVO> 部门角色列表
|
||||
* @return List<UserDeptRoleVO> 部门角色列表
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
List<DeptRoleVO> selectDeptRolesByUserId(@Param("userId") String userId);
|
||||
List<UserDeptRoleVO> selectDeptRolesByUserId(@Param("userId") String userId);
|
||||
|
||||
/**
|
||||
* @description 根据角色编码查询角色
|
||||
|
||||
@@ -5,19 +5,38 @@ import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.user.TbSysUserDeptRole;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
@Mapper
|
||||
public interface UserDeptRoleMapper extends BaseMapper<TbSysUserDeptRole> {
|
||||
|
||||
/**
|
||||
* @description 查询用户部门角色
|
||||
* @param userId 用户ID
|
||||
* @return List<TbSysUserDeptRole> 用户部门角色列表
|
||||
* @description 查询用户部门角色(包含名称)
|
||||
* @param filter 过滤条件
|
||||
* @return List<UserDeptRoleVO> 用户部门角色列表
|
||||
* @author yslg
|
||||
* @since 2025-10-09
|
||||
*/
|
||||
List<TbSysUserDeptRole> selectByFilter(TbSysUserDeptRole filter);
|
||||
List<UserDeptRoleVO> selectByFilter(TbSysUserDeptRole filter);
|
||||
|
||||
/**
|
||||
* @description 删除指定用户的所有部门角色绑定
|
||||
* @param userID 用户ID
|
||||
* @return int 影响行数
|
||||
* @author yslg
|
||||
* @since 2025-10-09
|
||||
*/
|
||||
int deleteUserDeptRole(String userID);
|
||||
|
||||
/**
|
||||
* @description 批量删除多个用户的部门角色绑定
|
||||
* @param userIds 用户ID列表
|
||||
* @return int 影响行数
|
||||
* @author yslg
|
||||
* @since 2025-10-29
|
||||
*/
|
||||
int deleteUserDeptRoleByUserIds(@Param("userIds") List<String> userIds);
|
||||
|
||||
/**
|
||||
* @description 绑定用户
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package org.xyzh.system.permission.service.impl;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.dept.TbSysDeptRole;
|
||||
import org.xyzh.common.dto.permission.TbResourcePermission;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.system.mapper.DeptRoleMapper;
|
||||
import org.xyzh.system.mapper.ResourcePermissionMapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @description 资源权限控制服务实现类
|
||||
* @filename SysResourcePermissionServiceImpl.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-29
|
||||
*/
|
||||
@Service
|
||||
public class SysResourcePermissionServiceImpl implements ResourcePermissionService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SysResourcePermissionServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private ResourcePermissionMapper resourcePermissionMapper;
|
||||
|
||||
@Autowired
|
||||
private DeptRoleMapper deptRoleMapper;
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public ResultDomain<TbResourcePermission> createResourcePermission(Integer resource_type, String resource_id,
|
||||
UserDeptRoleVO userDeptRole) {
|
||||
ResultDomain<TbResourcePermission> resultDomain = new ResultDomain<>();
|
||||
|
||||
Set<TbResourcePermission> resourcePermissions = new HashSet<>();
|
||||
Date now = new Date();
|
||||
|
||||
// 判断是否为root_department的superadmin
|
||||
if (isRootSuperAdmin(userDeptRole)) {
|
||||
// root_department的superadmin创建资源:创建全局读权限
|
||||
logger.info("用户为root_department的superadmin,创建全局读权限,所有用户(包括未来新增的部门/角色)都能访问");
|
||||
|
||||
// 1. 创建全局读权限(所有人可读,包括未来新增的部门/角色)
|
||||
TbResourcePermission globalReadPermission = createGlobalReadPermission(resource_type, resource_id, userDeptRole.getUserID());
|
||||
globalReadPermission.setCreateTime(now);
|
||||
resourcePermissions.add(globalReadPermission);
|
||||
|
||||
// 2. 为superadmin创建全权限(读写执行)
|
||||
TbResourcePermission superAdminPermission = createSuperAdminPermission(resource_type, resource_id, userDeptRole.getUserID());
|
||||
superAdminPermission.setCreateTime(now);
|
||||
resourcePermissions.add(superAdminPermission);
|
||||
|
||||
} else {
|
||||
// 普通用户创建资源:为父部门管理员+当前部门创建权限
|
||||
logger.info("普通用户创建资源,为父部门管理员和当前部门创建权限(子部门通过递归查询自动继承)");
|
||||
|
||||
// 1. 为父部门的管理员角色创建精确权限(dept + admin role)
|
||||
List<TbSysDeptRole> parentDeptRoles = deptRoleMapper.selectParentDeptAdmin(userDeptRole);
|
||||
for (TbSysDeptRole deptRole : parentDeptRoles) {
|
||||
TbResourcePermission temp = createResourcePermission(resource_type, resource_id, deptRole.getDeptID(), deptRole.getRoleID());
|
||||
temp.setCreateTime(now);
|
||||
resourcePermissions.add(temp);
|
||||
}
|
||||
|
||||
// 2. 为当前用户所在部门创建部门级权限(dept + NULL)
|
||||
// 所有角色都能访问(包括未来新增的角色)
|
||||
// 所有子部门都能访问(包括未来新增的子部门,通过查询时递归实现)
|
||||
TbResourcePermission deptPermission = createDeptLevelPermission(resource_type, resource_id, userDeptRole.getDeptID());
|
||||
deptPermission.setCreateTime(now);
|
||||
resourcePermissions.add(deptPermission);
|
||||
|
||||
// 3. 为superadmin创建全权限(确保超级管理员始终可以管理所有资源)
|
||||
TbResourcePermission superAdminPermission = createSuperAdminPermission(resource_type, resource_id, userDeptRole.getUserID());
|
||||
superAdminPermission.setCreateTime(now);
|
||||
resourcePermissions.add(superAdminPermission);
|
||||
}
|
||||
|
||||
List<TbResourcePermission> resourcePermissionsList = new ArrayList<>(resourcePermissions);
|
||||
int result = resourcePermissionMapper.batchInsertResourcePermission(resourcePermissionsList);
|
||||
if (result > 0) {
|
||||
resultDomain.success("创建资源权限成功", resourcePermissionsList);
|
||||
return resultDomain;
|
||||
} else {
|
||||
resultDomain.fail("创建资源权限失败");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为root_department的superadmin
|
||||
*/
|
||||
private boolean isRootSuperAdmin(UserDeptRoleVO userDeptRole) {
|
||||
return "root_department".equals(userDeptRole.getDeptID())
|
||||
&& "superadmin".equals(userDeptRole.getRoleID());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建全局读权限(所有人可读,包括未来新增的部门/角色)
|
||||
*/
|
||||
private TbResourcePermission createGlobalReadPermission(Integer resource_type, String resource_id, String creatorID) {
|
||||
TbResourcePermission resourcePermission = new TbResourcePermission();
|
||||
resourcePermission.setResourceType(resource_type);
|
||||
resourcePermission.setResourceID(resource_id);
|
||||
resourcePermission.setCreator(creatorID);
|
||||
resourcePermission.setDeptID(null); // NULL表示不限制部门
|
||||
resourcePermission.setRoleID(null); // NULL表示不限制角色
|
||||
resourcePermission.setCanRead(true);
|
||||
resourcePermission.setCanWrite(false);
|
||||
resourcePermission.setCanExecute(false);
|
||||
return resourcePermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建精确部门角色权限(dept + role,读权限)
|
||||
*/
|
||||
private TbResourcePermission createResourcePermission(Integer resource_type, String resource_id, String deptID, String roleID) {
|
||||
TbResourcePermission resourcePermission = new TbResourcePermission();
|
||||
resourcePermission.setResourceType(resource_type);
|
||||
resourcePermission.setResourceID(resource_id);
|
||||
resourcePermission.setDeptID(deptID);
|
||||
resourcePermission.setRoleID(roleID);
|
||||
resourcePermission.setCanRead(true);
|
||||
resourcePermission.setCanWrite(false);
|
||||
resourcePermission.setCanExecute(false);
|
||||
return resourcePermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建部门级权限(dept + NULL,该部门所有角色可读,包括未来新增的角色)
|
||||
*/
|
||||
private TbResourcePermission createDeptLevelPermission(Integer resource_type, String resource_id, String deptID) {
|
||||
TbResourcePermission resourcePermission = new TbResourcePermission();
|
||||
resourcePermission.setResourceType(resource_type);
|
||||
resourcePermission.setResourceID(resource_id);
|
||||
resourcePermission.setDeptID(deptID);
|
||||
resourcePermission.setRoleID(null); // NULL表示该部门所有角色都能访问
|
||||
resourcePermission.setCanRead(true);
|
||||
resourcePermission.setCanWrite(false);
|
||||
resourcePermission.setCanExecute(false);
|
||||
return resourcePermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建超级管理员全权限(读写执行)
|
||||
*/
|
||||
private TbResourcePermission createSuperAdminPermission(Integer resource_type, String resource_id, String creatorID) {
|
||||
TbResourcePermission resourcePermission = new TbResourcePermission();
|
||||
resourcePermission.setResourceType(resource_type);
|
||||
resourcePermission.setResourceID(resource_id);
|
||||
resourcePermission.setCreator(creatorID);
|
||||
resourcePermission.setDeptID("root_department");
|
||||
resourcePermission.setRoleID("superadmin");
|
||||
resourcePermission.setCanRead(true);
|
||||
resourcePermission.setCanWrite(true);
|
||||
resourcePermission.setCanExecute(true);
|
||||
return resourcePermission;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.permission.TbSysPermission;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.common.vo.DeptRoleVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.system.mapper.RolePermissionMapper;
|
||||
import org.xyzh.system.mapper.RoleMapper;
|
||||
import org.xyzh.system.mapper.UserDeptRoleMapper;
|
||||
@@ -257,8 +257,8 @@ public class SysRoleServiceImpl implements SysRoleService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<DeptRoleVO> getDeptRolesByUserId(String userId) {
|
||||
ResultDomain<DeptRoleVO> resultDomain = new ResultDomain<>();
|
||||
public ResultDomain<UserDeptRoleVO> getDeptRolesByUserId(String userId) {
|
||||
ResultDomain<UserDeptRoleVO> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始根据用户ID查询部门角色列表:{}", userId);
|
||||
|
||||
@@ -267,7 +267,7 @@ public class SysRoleServiceImpl implements SysRoleService {
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
List<DeptRoleVO> roles = roleMapper.selectDeptRolesByUserId(userId);
|
||||
List<UserDeptRoleVO> roles = roleMapper.selectDeptRolesByUserId(userId);
|
||||
|
||||
logger.info("根据用户ID查询部门角色列表完成,共找到{}个部门角色", roles.size());
|
||||
resultDomain.success("查询成功", roles);
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.xyzh.system.user.service.impl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -46,6 +47,8 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
|
||||
@Autowired
|
||||
private UserDeptRoleMapper userDeptRoleMapper;
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -69,18 +72,18 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
userInfo.setUserID(user.getID());
|
||||
userInfo.setCreateTime(now);
|
||||
userInfo.setAvatar("default");
|
||||
|
||||
|
||||
TbSysUserDeptRole userDeptRole = new TbSysUserDeptRole();
|
||||
userDeptRole.setUserID(user.getID());
|
||||
userDeptRole.setDeptID("-1");
|
||||
userDeptRole.setDeptID("default_department");
|
||||
userDeptRole.setRoleID("freedom");
|
||||
userDeptRole.setCreateTime(now);
|
||||
|
||||
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||
userMapper.insertUser(user);
|
||||
userInfoMapper.insertUserInfo(userInfo);
|
||||
userDeptRoleMapper.bindUser(Arrays.asList(userDeptRole));
|
||||
resultDomain.success("注册用户成功", user);
|
||||
return resultDomain;
|
||||
return resultDomain;
|
||||
} catch (Exception e) {
|
||||
logger.error("注册用户失败:{}", user.getUsername(), e);
|
||||
resultDomain.fail("注册用户失败:" + e.getMessage());
|
||||
@@ -94,7 +97,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
|
||||
try {
|
||||
logger.info("开始检查用户是否已存在:{}", user.getUsername());
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("检查用户是否已存在失败:{}", user.getUsername(), e);
|
||||
resultDomain.fail("检查用户是否已存在失败:" + e.getMessage());
|
||||
@@ -109,14 +112,14 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
|
||||
try {
|
||||
logger.info("开始查询所有用户");
|
||||
|
||||
|
||||
TbSysUser filter = new TbSysUser();
|
||||
List<TbSysUser> users = userMapper.selectByFilter(filter);
|
||||
|
||||
|
||||
logger.info("查询所有用户完成,共找到{}个用户", users.size());
|
||||
resultDomain.success("查询成功", users);
|
||||
return resultDomain;
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("查询所有用户失败", e);
|
||||
resultDomain.fail("查询用户失败:" + e.getMessage());
|
||||
@@ -129,28 +132,28 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始根据ID查询用户:{}", userId);
|
||||
|
||||
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
resultDomain.fail("用户ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
TbSysUser filter = new TbSysUser();
|
||||
filter.setID(userId);
|
||||
filter.setDeleted(false);
|
||||
|
||||
|
||||
List<TbSysUser> users = userMapper.selectByFilter(filter);
|
||||
|
||||
|
||||
if (users.isEmpty()) {
|
||||
logger.warn("未找到用户:{}", userId);
|
||||
resultDomain.fail("未找到指定用户");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
logger.info("根据ID查询用户完成:{}", userId);
|
||||
resultDomain.success("查询成功", users.get(0));
|
||||
return resultDomain;
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("根据ID查询用户失败:{}", userId, e);
|
||||
resultDomain.fail("查询用户失败:" + e.getMessage());
|
||||
@@ -163,24 +166,24 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始根据用户名查询用户:{}", username);
|
||||
|
||||
|
||||
if (!StringUtils.hasText(username)) {
|
||||
resultDomain.fail("用户名不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
TbSysUser user = userMapper.selectByUsername(username);
|
||||
|
||||
|
||||
if (user == null) {
|
||||
logger.warn("未找到用户:{}", username);
|
||||
resultDomain.fail("未找到指定用户");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
logger.info("根据用户名查询用户完成:{}", username);
|
||||
resultDomain.success("查询成功", user);
|
||||
return resultDomain;
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("根据用户名查询用户失败:{}", username, e);
|
||||
resultDomain.fail("查询用户失败:" + e.getMessage());
|
||||
@@ -193,35 +196,35 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始根据过滤条件查询用户:{}", filter);
|
||||
|
||||
|
||||
if (filter == null) {
|
||||
resultDomain.fail("过滤条件不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
// 检查至少有一个查询条件
|
||||
// boolean hasFilter = StringUtils.hasText(filter.getID()) ||
|
||||
// StringUtils.hasText(filter.getUsername()) ||
|
||||
// StringUtils.hasText(filter.getEmail()) ||
|
||||
// StringUtils.hasText(filter.getPhone());
|
||||
|
||||
// StringUtils.hasText(filter.getUsername()) ||
|
||||
// StringUtils.hasText(filter.getEmail()) ||
|
||||
// StringUtils.hasText(filter.getPhone());
|
||||
|
||||
// if (!hasFilter) {
|
||||
// resultDomain.fail("至少需要提供一个查询条件");
|
||||
// return resultDomain;
|
||||
// resultDomain.fail("至少需要提供一个查询条件");
|
||||
// return resultDomain;
|
||||
// }
|
||||
|
||||
|
||||
List<TbSysUser> users = userMapper.selectByFilter(filter);
|
||||
|
||||
|
||||
if (users.isEmpty()) {
|
||||
logger.warn("未找到符合条件的用户:{}", filter);
|
||||
resultDomain.fail("未找到指定用户");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
logger.info("根据过滤条件查询用户完成:{}", filter);
|
||||
resultDomain.success("查询成功", users);
|
||||
return resultDomain;
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("根据过滤条件查询用户失败:{}", filter, e);
|
||||
resultDomain.fail("查询用户失败:" + e.getMessage());
|
||||
@@ -246,13 +249,13 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始创建用户:{}", user.getUsername());
|
||||
|
||||
|
||||
// 参数校验
|
||||
if (!StringUtils.hasText(user.getUsername())) {
|
||||
resultDomain.fail("用户名不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
// 检查用户名是否已存在
|
||||
ResultDomain<Boolean> checkResult = checkUsernameExists(user.getUsername(), null);
|
||||
if (!checkResult.isSuccess()) {
|
||||
@@ -263,7 +266,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
resultDomain.fail("用户名已存在");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if (StringUtils.hasText(user.getEmail())) {
|
||||
ResultDomain<Boolean> emailCheckResult = checkEmailExists(user.getEmail(), null);
|
||||
@@ -276,7 +279,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
return resultDomain;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 设置基础信息
|
||||
user.setID(IDUtils.generateID());
|
||||
user.setCreateTime(new Date());
|
||||
@@ -284,10 +287,9 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
if (user.getStatus() == null) {
|
||||
user.setStatus(1); // 默认启用状态
|
||||
}
|
||||
|
||||
// 插入数据库
|
||||
ResultDomain<TbSysUser> result = registerUser(user);
|
||||
|
||||
|
||||
if (result.isSuccess()) {
|
||||
logger.info("创建用户成功:{}", user.getUsername());
|
||||
resultDomain.success("创建用户成功", result.getData());
|
||||
@@ -296,7 +298,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
logger.warn("创建用户失败:{}", user.getUsername());
|
||||
resultDomain.fail("创建用户失败:" + result.getMessage());
|
||||
}
|
||||
|
||||
|
||||
return resultDomain;
|
||||
} catch (Exception e) {
|
||||
logger.error("创建用户异常:{}", user.getUsername(), e);
|
||||
@@ -308,150 +310,159 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
@Override
|
||||
public ResultDomain<TbSysUser> updateUser(TbSysUser user) {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始更新用户:{}", user.getID());
|
||||
|
||||
// 参数校验
|
||||
if (!StringUtils.hasText(user.getID())) {
|
||||
resultDomain.fail("用户ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
if (!StringUtils.hasText(user.getUsername())) {
|
||||
resultDomain.fail("用户名不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
ResultDomain<TbSysUser> existResult = getUserById(user.getID());
|
||||
if (!existResult.isSuccess()) {
|
||||
resultDomain.fail(existResult.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在(排除自身)
|
||||
ResultDomain<Boolean> checkResult = checkUsernameExists(user.getUsername(), user.getID());
|
||||
if (!checkResult.isSuccess()) {
|
||||
resultDomain.fail(checkResult.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
if (checkResult.getData()) {
|
||||
resultDomain.fail("用户名已存在");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在(排除自身)
|
||||
if (StringUtils.hasText(user.getEmail())) {
|
||||
ResultDomain<Boolean> emailCheckResult = checkEmailExists(user.getEmail(), user.getID());
|
||||
if (!emailCheckResult.isSuccess()) {
|
||||
resultDomain.fail(emailCheckResult.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
if (emailCheckResult.getData()) {
|
||||
resultDomain.fail("邮箱已存在");
|
||||
return resultDomain;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置更新时间
|
||||
user.setUpdateTime(new Date());
|
||||
|
||||
// 更新数据库
|
||||
int result = userMapper.updateUser(user);
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("更新用户成功:{}", user.getID());
|
||||
resultDomain.success("更新用户成功", user);
|
||||
return resultDomain;
|
||||
} else {
|
||||
logger.warn("更新用户失败:{}", user.getID());
|
||||
resultDomain.fail("更新用户失败");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("更新用户异常:{}", user.getID(), e);
|
||||
resultDomain.fail("更新用户失败:" + e.getMessage());
|
||||
|
||||
logger.info("开始更新用户:{}", user.getID());
|
||||
|
||||
// 参数校验
|
||||
if (!StringUtils.hasText(user.getID())) {
|
||||
resultDomain.fail("用户ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
ResultDomain<TbSysUser> existResult = getUserById(user.getID());
|
||||
if (!existResult.isSuccess()) {
|
||||
resultDomain.fail(existResult.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在(排除自身)
|
||||
if (StringUtils.hasText(user.getEmail())) {
|
||||
ResultDomain<Boolean> emailCheckResult = checkEmailExists(user.getEmail(), user.getID());
|
||||
if (!emailCheckResult.isSuccess()) {
|
||||
resultDomain.fail(emailCheckResult.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
if (emailCheckResult.getData()) {
|
||||
resultDomain.fail("邮箱已存在");
|
||||
return resultDomain;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置更新时间
|
||||
user.setUpdateTime(new Date());
|
||||
|
||||
// 更新数据库
|
||||
int result = userMapper.updateUser(user);
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("更新用户成功:{}", user.getID());
|
||||
resultDomain.success("更新用户成功", user);
|
||||
return resultDomain;
|
||||
} else {
|
||||
logger.warn("更新用户失败:{}", user.getID());
|
||||
resultDomain.fail("更新用户失败");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysUser> deleteUser(String userId) {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
|
||||
logger.info("开始删除用户:{}", userId);
|
||||
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
resultDomain.fail("用户ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
ResultDomain<TbSysUser> existResult = getUserById(userId);
|
||||
if (!existResult.isSuccess()) {
|
||||
resultDomain.fail(existResult.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 逻辑删除
|
||||
// TbSysUser user = existResult.getData();
|
||||
// user.setDeleted(true);
|
||||
// user.setDeleteTime(new Date());
|
||||
|
||||
// int result = userMapper.updateUser(user);
|
||||
int result = userMapper.deleteUser(userId);
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("删除用户成功:{}", userId);
|
||||
resultDomain.success("删除用户成功", new TbSysUser());
|
||||
return resultDomain;
|
||||
} else {
|
||||
logger.warn("删除用户失败:{}", userId);
|
||||
resultDomain.fail("删除用户失败");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<UserDeptRoleVO> getBindUserDeptRoleList(TbSysUserDeptRole filter) {
|
||||
ResultDomain<UserDeptRoleVO> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始删除用户:{}", userId);
|
||||
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
resultDomain.fail("用户ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
ResultDomain<TbSysUser> existResult = getUserById(userId);
|
||||
if (!existResult.isSuccess()) {
|
||||
resultDomain.fail(existResult.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 逻辑删除
|
||||
TbSysUser user = existResult.getData();
|
||||
user.setDeleted(true);
|
||||
user.setDeleteTime(new Date());
|
||||
|
||||
int result = userMapper.updateUser(user);
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("删除用户成功:{}", userId);
|
||||
resultDomain.success("删除用户成功", user);
|
||||
return resultDomain;
|
||||
} else {
|
||||
logger.warn("删除用户失败:{}", userId);
|
||||
resultDomain.fail("删除用户失败");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
logger.info("开始查询用户部门角色绑定列表");
|
||||
List<UserDeptRoleVO> userDeptRoles = userDeptRoleMapper.selectByFilter(filter);
|
||||
logger.info("查询用户部门角色绑定列表完成,共找到{}条记录", userDeptRoles.size());
|
||||
resultDomain.success("查询成功", userDeptRoles);
|
||||
return resultDomain;
|
||||
} catch (Exception e) {
|
||||
logger.error("删除用户异常:{}", userId, e);
|
||||
resultDomain.fail("删除用户失败:" + e.getMessage());
|
||||
logger.error("查询用户部门角色绑定列表失败", e);
|
||||
resultDomain.fail("查询用户部门角色绑定列表失败:" + e.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysUserDeptRole> getBindUserDeptRoleList(TbSysUserDeptRole filter) {
|
||||
ResultDomain<TbSysUserDeptRole> resultDomain = new ResultDomain<>();
|
||||
List<TbSysUserDeptRole> userDeptRoles = userDeptRoleMapper.selectByFilter(filter);
|
||||
resultDomain.success("查询成功", userDeptRoles);
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public ResultDomain<UserDeptRoleVO> bindUserDeptRole(UserDeptRoleVO userDeptRoleVO) {
|
||||
ResultDomain<UserDeptRoleVO> resultDomain = new ResultDomain<>();
|
||||
TbSysUser currentUser = LoginUtil.getCurrentUser();
|
||||
try {
|
||||
|
||||
|
||||
// 收集所有用户ID
|
||||
List<String> userIds = new ArrayList<>();
|
||||
for (TbSysUser user : userDeptRoleVO.getUsers()) {
|
||||
userIds.add(user.getID());
|
||||
}
|
||||
|
||||
logger.info("准备为 {} 个用户绑定部门角色", userIds.size());
|
||||
|
||||
// 批量删除所有涉及用户的旧绑定关系(物理删除,包括软删除的记录)
|
||||
int deleteCount = userDeptRoleMapper.deleteUserDeptRoleByUserIds(userIds);
|
||||
if (deleteCount <= 0) {
|
||||
resultDomain.fail("批量删除旧绑定记录失败:没有记录被删除");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 准备新的绑定数据
|
||||
List<TbSysUserDeptRole> userDeptRoles = new ArrayList<>();
|
||||
logger.info("开始绑定用户部门角色:{}", userDeptRoleVO.getID());
|
||||
Date now = new Date();
|
||||
for (TbSysUser user : userDeptRoleVO.getUsers()) {
|
||||
for(TbSysUserDeptRole userDeptRole : userDeptRoleVO.getUserDeptRoles()) {
|
||||
userDeptRole.setID(IDUtils.generateID());
|
||||
userDeptRole.setUserID(user.getID());
|
||||
userDeptRole.setCreateTime(now);
|
||||
userDeptRole.setCreator(currentUser.getID());
|
||||
userDeptRoles.add(userDeptRole);
|
||||
for (TbSysUserDeptRole userDeptRole : userDeptRoleVO.getUserDeptRoles()) {
|
||||
TbSysUserDeptRole newUserDeptRole = new TbSysUserDeptRole();
|
||||
newUserDeptRole.setID(IDUtils.generateID());
|
||||
newUserDeptRole.setUserID(user.getID());
|
||||
newUserDeptRole.setDeptID(userDeptRole.getDeptID());
|
||||
newUserDeptRole.setRoleID(userDeptRole.getRoleID());
|
||||
newUserDeptRole.setCreateTime(now);
|
||||
newUserDeptRole.setCreator(currentUser.getID());
|
||||
userDeptRoles.add(newUserDeptRole);
|
||||
}
|
||||
}
|
||||
|
||||
userDeptRoleMapper.bindUser(userDeptRoles);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("绑定用户部门角色异常:{}", userDeptRoleVO.getID(), e);
|
||||
resultDomain.fail("绑定用户部门角色失败:" + e.getMessage());
|
||||
logger.info("准备插入 {} 条新绑定记录", userDeptRoles.size());
|
||||
|
||||
// 插入新的绑定关系
|
||||
int result = userDeptRoleMapper.bindUser(userDeptRoles);
|
||||
logger.info("成功插入 {} 条绑定记录", result);
|
||||
|
||||
if (result > 0) {
|
||||
resultDomain.success("绑定用户部门角色成功", userDeptRoleVO);
|
||||
} else {
|
||||
resultDomain.fail("绑定用户部门角色失败:没有记录被插入");
|
||||
}
|
||||
|
||||
return resultDomain;
|
||||
}
|
||||
return resultDomain;
|
||||
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -459,30 +470,26 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
public ResultDomain<UserDeptRoleVO> unbindUserDeptRole(UserDeptRoleVO userDeptRoleVO) {
|
||||
ResultDomain<UserDeptRoleVO> resultDomain = new ResultDomain<>();
|
||||
TbSysUser currentUser = LoginUtil.getCurrentUser();
|
||||
try {
|
||||
List<TbSysUserDeptRole> userDeptRoles = new ArrayList<>();
|
||||
logger.info("开始解绑用户部门角色:{}", userDeptRoleVO.getID());
|
||||
Date now = new Date();
|
||||
for(TbSysUser user:userDeptRoleVO.getUsers()) {
|
||||
for(TbSysUserDeptRole userDeptRole : userDeptRoleVO.getUserDeptRoles()) {
|
||||
userDeptRole.setUserID(user.getID());
|
||||
userDeptRoles.add(userDeptRole);
|
||||
}
|
||||
|
||||
List<TbSysUserDeptRole> userDeptRoles = new ArrayList<>();
|
||||
logger.info("开始解绑用户部门角色:{}", userDeptRoleVO.getDeptID());
|
||||
Date now = new Date();
|
||||
for (TbSysUser user : userDeptRoleVO.getUsers()) {
|
||||
for (TbSysUserDeptRole userDeptRole : userDeptRoleVO.getUserDeptRoles()) {
|
||||
userDeptRole.setUserID(user.getID());
|
||||
userDeptRoles.add(userDeptRole);
|
||||
}
|
||||
int result = userDeptRoleMapper.unbindUser(userDeptRoles);
|
||||
if(result > 0) {
|
||||
logger.info("解绑用户部门角色成功:{}", userDeptRoleVO.getID());
|
||||
resultDomain.success("解绑用户部门角色成功", userDeptRoleVO);
|
||||
} else {
|
||||
logger.warn("解绑用户部门角色失败:{}", userDeptRoleVO.getID());
|
||||
resultDomain.fail("解绑用户部门角色失败");
|
||||
}
|
||||
return resultDomain;
|
||||
} catch (Exception e) {
|
||||
logger.error("解绑用户部门角色异常:{}", userDeptRoleVO.getID(), e);
|
||||
resultDomain.fail("解绑用户部门角色失败:" + e.getMessage());
|
||||
}
|
||||
int result = userDeptRoleMapper.unbindUser(userDeptRoles);
|
||||
if (result > 0) {
|
||||
logger.info("解绑用户部门角色成功:{}", userDeptRoleVO.getDeptID());
|
||||
resultDomain.success("解绑用户部门角色成功", userDeptRoleVO);
|
||||
} else {
|
||||
logger.warn("解绑用户部门角色失败:{}", userDeptRoleVO.getDeptID());
|
||||
resultDomain.fail("解绑用户部门角色失败");
|
||||
}
|
||||
return resultDomain;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -490,7 +497,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("检查用户名是否存在:{}", username);
|
||||
|
||||
|
||||
if (!StringUtils.hasText(username)) {
|
||||
resultDomain.fail("用户名不能为空");
|
||||
return resultDomain;
|
||||
@@ -502,14 +509,14 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
}
|
||||
filter.setUsername(username);
|
||||
filter.setDeleted(false);
|
||||
|
||||
|
||||
long count = userMapper.selectByFilter(filter).size();
|
||||
boolean exists = count > 0;
|
||||
|
||||
|
||||
logger.info("用户名存在性检查完成:{},存在:{}", username, exists);
|
||||
resultDomain.success("检查完成", exists);
|
||||
return resultDomain;
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("检查用户名存在性失败:{}", username, e);
|
||||
resultDomain.fail("检查失败:" + e.getMessage());
|
||||
@@ -522,27 +529,27 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("检查邮箱是否存在:{}", email);
|
||||
|
||||
|
||||
if (!StringUtils.hasText(email)) {
|
||||
resultDomain.fail("邮箱不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
TbSysUser filter = new TbSysUser();
|
||||
filter.setEmail(email);
|
||||
filter.setDeleted(false);
|
||||
|
||||
|
||||
if (StringUtils.hasText(excludeId)) {
|
||||
filter.setID(excludeId);
|
||||
}
|
||||
|
||||
|
||||
long count = userMapper.selectByFilter(filter).size();
|
||||
boolean exists = count > 0;
|
||||
|
||||
|
||||
logger.info("邮箱存在性检查完成:{},存在:{}", email, exists);
|
||||
resultDomain.success("检查完成", exists);
|
||||
return resultDomain;
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("检查邮箱存在性失败:{}", email, e);
|
||||
resultDomain.fail("检查失败:" + e.getMessage());
|
||||
@@ -555,13 +562,13 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始搜索用户,用户名:{},邮箱:{},状态:{}", username, email, status);
|
||||
|
||||
|
||||
List<TbSysUser> users = userMapper.selectUserList(username, email, status);
|
||||
|
||||
|
||||
logger.info("搜索用户完成,共找到{}个用户", users.size());
|
||||
resultDomain.success("搜索成功", users);
|
||||
return resultDomain;
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("搜索用户失败", e);
|
||||
resultDomain.fail("搜索用户失败:" + e.getMessage());
|
||||
@@ -574,30 +581,30 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始修改用户状态:{},状态:{}", userId, status);
|
||||
|
||||
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
resultDomain.fail("用户ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
if (status == null) {
|
||||
resultDomain.fail("用户状态不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
// 检查用户是否存在
|
||||
ResultDomain<TbSysUser> existResult = getUserById(userId);
|
||||
if (!existResult.isSuccess()) {
|
||||
resultDomain.fail(existResult.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
TbSysUser user = existResult.getData();
|
||||
user.setStatus(status);
|
||||
user.setUpdateTime(new Date());
|
||||
|
||||
|
||||
int result = userMapper.updateUser(user);
|
||||
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("修改用户状态成功:{}", userId);
|
||||
resultDomain.success("修改用户状态成功", user);
|
||||
@@ -607,7 +614,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
resultDomain.fail("修改用户状态失败");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("修改用户状态异常:{}", userId, e);
|
||||
resultDomain.fail("修改用户状态失败:" + e.getMessage());
|
||||
@@ -620,31 +627,31 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始重置用户密码:{}", userId);
|
||||
|
||||
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
resultDomain.fail("用户ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
if (!StringUtils.hasText(newPassword)) {
|
||||
resultDomain.fail("新密码不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
// 检查用户是否存在
|
||||
ResultDomain<TbSysUser> existResult = getUserById(userId);
|
||||
if (!existResult.isSuccess()) {
|
||||
resultDomain.fail(existResult.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
TbSysUser user = existResult.getData();
|
||||
// TODO: 这里应该对密码进行加密处理
|
||||
user.setPassword(newPassword);
|
||||
user.setUpdateTime(new Date());
|
||||
|
||||
|
||||
int result = userMapper.updateUser(user);
|
||||
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("重置用户密码成功:{}", userId);
|
||||
resultDomain.success("重置密码成功", user);
|
||||
@@ -654,7 +661,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
resultDomain.fail("重置密码失败");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("重置用户密码异常:{}", userId, e);
|
||||
resultDomain.fail("重置密码失败:" + e.getMessage());
|
||||
@@ -662,7 +669,6 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------用户信息相关--------------------------------
|
||||
|
||||
@Override
|
||||
@@ -682,11 +688,11 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
resultDomain.fail("未找到指定用户信息");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
logger.info("根据ID查询用户信息完成:{}", userId);
|
||||
resultDomain.success("查询成功", userInfo);
|
||||
return resultDomain;
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("根据ID查询用户信息失败:{}", userId, e);
|
||||
resultDomain.fail("查询用户信息失败:" + e.getMessage());
|
||||
@@ -705,9 +711,9 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
return resultDomain;
|
||||
}
|
||||
userInfo.setUpdateTime(new Date());
|
||||
|
||||
|
||||
int result = userMapper.updateUserInfo(userInfo);
|
||||
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("更新用户信息成功:{}", userInfo.getUserID());
|
||||
TbSysUserInfo newUserInfo = userMapper.selectUserInfoById(userInfo.getUserID());
|
||||
@@ -718,7 +724,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
resultDomain.fail("更新用户信息失败");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("更新用户信息异常:{}", userInfo.getUserID(), e);
|
||||
resultDomain.fail("更新用户信息失败:" + e.getMessage());
|
||||
@@ -726,7 +732,6 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ResultDomain<UserVO> getUserInfoTotal(String userId) {
|
||||
ResultDomain<UserVO> resultDomain = new ResultDomain<>();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.xyzh.system.utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xyzh.common.core.domain.LoginDomain;
|
||||
@@ -9,6 +12,7 @@ import org.xyzh.common.dto.user.TbSysUserInfo;
|
||||
import org.xyzh.common.redis.service.RedisService;
|
||||
import org.xyzh.common.utils.NonUtils;
|
||||
import org.xyzh.common.utils.ServletUtil;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
/**
|
||||
* @description LoginUtil.java文件描述 登录信息工具类
|
||||
@@ -72,6 +76,26 @@ public class LoginUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户的部门角色列表(扁平化)
|
||||
* UserDeptRoleVO已包含deptPath字段,用于高效的权限继承判断
|
||||
* @return 部门角色列表
|
||||
*/
|
||||
public static List<UserDeptRoleVO> getCurrentDeptRole() {
|
||||
LoginDomain loginDomain = getCurrentLoginDomain();
|
||||
List<UserDeptRoleVO> roles = loginDomain.getRoles();
|
||||
|
||||
// UserDeptRoleVO应该在登录时就已经扁平化填充好了
|
||||
// 这里只需要确保userID被设置(如果还没设置的话)
|
||||
String userId = loginDomain.getUser().getID();
|
||||
return roles.stream().map(item -> {
|
||||
if (item.getUserID() == null) {
|
||||
item.setUserID(userId);
|
||||
}
|
||||
return item;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前登录用户
|
||||
* @return TbSysUser 当前登录用户,未登录返回null
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="name" property="name" jdbcType="VARCHAR"/>
|
||||
<result column="parent_id" property="parentID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_path" property="deptPath" jdbcType="VARCHAR"/>
|
||||
<result column="description" property="description" jdbcType="VARCHAR"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
@@ -19,7 +20,7 @@
|
||||
|
||||
<!-- 基础字段 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, dept_id, name, parent_id, description, creator, updater,
|
||||
id, dept_id, name, parent_id, dept_path, description, creator, updater,
|
||||
create_time, update_time, delete_time, deleted
|
||||
</sql>
|
||||
|
||||
@@ -39,14 +40,54 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- selectDepts -->
|
||||
<!-- 权限过滤条件(基于dept_path的高效继承) -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_resource_permission rp ON d.dept_id = rp.resource_id
|
||||
AND rp.resource_type = 4
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
-- 全局权限:所有用户可访问
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM (
|
||||
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||
SELECT #{udr.deptID} AS dept_id, #{udr.deptPath} AS dept_path, #{udr.roleID} AS role_id
|
||||
</foreach>
|
||||
) user_roles
|
||||
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||
WHERE
|
||||
-- 部门级权限:当前部门或父部门(通过dept_path判断继承关系)
|
||||
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||
-- 角色级权限:跨部门的角色权限
|
||||
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||
-- 精确权限:特定部门的特定角色
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
)
|
||||
</sql>
|
||||
|
||||
<!-- selectDepts - 添加权限过滤 -->
|
||||
|
||||
<select id="selectDepts">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_sys_dept
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY dept_id,create_time DESC
|
||||
SELECT DISTINCT d.*
|
||||
FROM tb_sys_dept d
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE d.deleted = 0
|
||||
<if test="filter.deptID != null and filter.deptID != ''">
|
||||
AND d.dept_id = #{filter.deptID}
|
||||
</if>
|
||||
<if test="filter.parentID != null and filter.parentID != ''">
|
||||
AND d.parent_id = #{filter.parentID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND d.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
ORDER BY d.dept_id, d.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据父部门ID查询子部门列表 -->
|
||||
@@ -95,64 +136,63 @@
|
||||
create_time ASC
|
||||
</select>
|
||||
|
||||
<!-- 批量删除部门(逻辑删除) -->
|
||||
<update id="batchDeleteByIds">
|
||||
UPDATE tb_sys_dept
|
||||
SET deleted = 1,
|
||||
delete_time = NOW(),
|
||||
updater = #{updater}
|
||||
WHERE deleted = 0
|
||||
AND dept_id IN
|
||||
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
|
||||
#{deptId}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<!-- 插入部门 -->
|
||||
<insert id="insert" parameterType="org.xyzh.common.dto.dept.TbSysDept">
|
||||
INSERT INTO tb_sys_dept
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">id,</if>
|
||||
<if test="deptID != null">dept_id,</if>
|
||||
<if test="parentID != null">parent_id,</if>
|
||||
<if test="name != null">name,</if>
|
||||
<if test="description != null">description,</if>
|
||||
<if test="creator != null">creator,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<!-- insertDept -->
|
||||
<insert id="insertDept" parameterType="org.xyzh.common.dto.dept.TbSysDept">
|
||||
INSERT INTO tb_sys_dept (
|
||||
id,
|
||||
dept_id,
|
||||
name,
|
||||
parent_id,
|
||||
dept_path,
|
||||
description,
|
||||
creator,
|
||||
create_time,
|
||||
deleted
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">#{id},</if>
|
||||
<if test="deptID != null">#{deptID},</if>
|
||||
<if test="parentID != null">#{parentID},</if>
|
||||
<if test="name != null">#{name},</if>
|
||||
<if test="description != null">#{description},</if>
|
||||
<if test="creator != null">#{creator},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
0
|
||||
</trim>
|
||||
) VALUES (
|
||||
#{id},
|
||||
#{deptID},
|
||||
#{name},
|
||||
#{parentID},
|
||||
#{deptPath},
|
||||
#{description},
|
||||
#{creator},
|
||||
#{createTime},
|
||||
#{deleted}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 更新部门 -->
|
||||
<update id="updateById" parameterType="org.xyzh.common.dto.dept.TbSysDept">
|
||||
<!-- updateDept -->
|
||||
<update id="updateDept" parameterType="org.xyzh.common.dto.dept.TbSysDept">
|
||||
UPDATE tb_sys_dept
|
||||
<set>
|
||||
<if test="deptID != null">dept_id = #{deptID},</if>
|
||||
<if test="parentID != null">parent_id = #{parentID},</if>
|
||||
<if test="name != null">name = #{name},</if>
|
||||
<if test="description != null">description = #{description},</if>
|
||||
<if test="updater != null">updater = #{updater},</if>
|
||||
<if test="name != null and name != ''">
|
||||
name = #{name},
|
||||
</if>
|
||||
<if test="parentID != null">
|
||||
parent_id = #{parentID},
|
||||
</if>
|
||||
<if test="deptPath != null">
|
||||
dept_path = #{deptPath},
|
||||
</if>
|
||||
<if test="description != null">
|
||||
description = #{description},
|
||||
</if>
|
||||
<if test="updater != null">
|
||||
updater = #{updater},
|
||||
</if>
|
||||
update_time = NOW()
|
||||
</set>
|
||||
WHERE dept_id = #{deptID} AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 根据ID删除(逻辑删除) -->
|
||||
<update id="deleteById">
|
||||
UPDATE tb_sys_dept
|
||||
<!-- deleteDept - 逻辑删除 -->
|
||||
<update id="deleteDept" parameterType="org.xyzh.common.dto.dept.TbSysDept">
|
||||
UPDATE tb_sys_dept
|
||||
SET deleted = 1,
|
||||
delete_time = NOW()
|
||||
<if test="updater != null">
|
||||
, updater = #{updater}
|
||||
</if>
|
||||
WHERE dept_id = #{deptID} AND deleted = 0
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -39,11 +39,29 @@
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectDeptRoleList">
|
||||
<!-- 部门角色VO结果映射 -->
|
||||
<resultMap id="DeptRoleVOResultMap" type="org.xyzh.common.vo.UserDeptRoleVO">
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_name" property="deptName" jdbcType="VARCHAR"/>
|
||||
<result column="dept_description" property="deptDescription" jdbcType="VARCHAR"/>
|
||||
<result column="role_id" property="roleID" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="roleName" jdbcType="VARCHAR"/>
|
||||
<result column="role_description" property="roleDescription" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectDeptRoleList" resultMap="DeptRoleVOResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_sys_dept_role
|
||||
ORDER BY dept_id, role_id, create_time DESC
|
||||
dr.dept_id,
|
||||
d.name AS dept_name,
|
||||
d.description AS dept_description,
|
||||
dr.role_id,
|
||||
r.name AS role_name,
|
||||
r.description AS role_description
|
||||
FROM tb_sys_dept_role dr
|
||||
LEFT JOIN tb_sys_dept d ON dr.dept_id = d.dept_id AND d.deleted = 0
|
||||
LEFT JOIN tb_sys_role r ON dr.role_id = r.role_id AND r.deleted = 0
|
||||
WHERE dr.deleted = 0
|
||||
ORDER BY dr.dept_id, dr.role_id, dr.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- batchBindDeptRole -->
|
||||
@@ -65,4 +83,81 @@
|
||||
(#{deptRole.deptID}, #{deptRole.roleID})
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<!-- selectParentDeptAdmin -->
|
||||
|
||||
<select id="selectParentDeptAdmin">
|
||||
WITH RECURSIVE dept_hierarchy AS (
|
||||
-- 基础查询:查询起始部门
|
||||
SELECT
|
||||
dept_id,
|
||||
parent_id,
|
||||
name,
|
||||
description,
|
||||
1 AS level
|
||||
FROM tb_sys_dept
|
||||
WHERE dept_id = #{deptID}
|
||||
AND deleted = 0
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- 递归查询:查询父级部门
|
||||
SELECT
|
||||
d.dept_id,
|
||||
d.parent_id,
|
||||
d.name,
|
||||
d.description,
|
||||
dh.level + 1 AS level
|
||||
FROM tb_sys_dept d
|
||||
INNER JOIN dept_hierarchy dh ON d.dept_id = dh.parent_id
|
||||
WHERE d.deleted = 0
|
||||
AND d.parent_id IS NOT NULL
|
||||
)
|
||||
SELECT
|
||||
dh.dept_id AS deptID,
|
||||
tsdr.role_id
|
||||
FROM dept_hierarchy dh
|
||||
INNER JOIN tb_sys_dept_role tsdr ON dh.dept_id = tsdr.dept_id
|
||||
WHERE tsdr.role_id = 'admin'
|
||||
AND tsdr.deleted = 0
|
||||
ORDER BY level DESC
|
||||
</select>
|
||||
|
||||
<!-- selectChildDeptRole -->
|
||||
|
||||
<select id="selectChildDeptRole">
|
||||
WITH RECURSIVE dept_hierarchy AS (
|
||||
-- 基础查询:查询起始部门
|
||||
SELECT
|
||||
dept_id,
|
||||
parent_id,
|
||||
name,
|
||||
description,
|
||||
1 AS level
|
||||
FROM tb_sys_dept
|
||||
WHERE dept_id = #{deptID}
|
||||
AND deleted = 0
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- 递归查询:查询子级部门
|
||||
SELECT
|
||||
d.dept_id,
|
||||
d.parent_id,
|
||||
d.name,
|
||||
d.description,
|
||||
dh.level + 1 AS level
|
||||
FROM tb_sys_dept d
|
||||
INNER JOIN dept_hierarchy dh ON d.parent_id = dh.dept_id
|
||||
WHERE d.deleted = 0
|
||||
AND d.parent_id IS NOT NULL
|
||||
)
|
||||
SELECT
|
||||
dh.dept_id AS deptID,
|
||||
tsdr.role_id
|
||||
FROM dept_hierarchy dh
|
||||
INNER JOIN tb_sys_dept_role tsdr ON dh.dept_id = tsdr.dept_id
|
||||
AND tsdr.deleted = 0
|
||||
ORDER BY level DESC
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.xyzh.system.mapper.ResourcePermissionMapper">
|
||||
|
||||
<!-- 基础结果映射 -->
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.permission.TbResourcePermission">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="resource_type" property="resourceType" jdbcType="INTEGER"/>
|
||||
<result column="resource_id" property="resourceID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="role_id" property="roleID" jdbcType="VARCHAR"/>
|
||||
<result column="can_read" property="canRead" jdbcType="TINYINT"/>
|
||||
<result column="can_write" property="canWrite" jdbcType="TINYINT"/>
|
||||
<result column="can_execute" property="canExecute" jdbcType="TINYINT"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="TINYINT"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 视图对象映射 -->
|
||||
<resultMap id="ResourcePermissionVO" type="org.xyzh.common.vo.ResourcePermissionVO">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="resource_type" property="resourceType" jdbcType="INTEGER"/>
|
||||
<result column="resource_id" property="resourceID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="role_id" property="roleID" jdbcType="VARCHAR"/>
|
||||
<result column="can_read" property="canRead" jdbcType="TINYINT"/>
|
||||
<result column="can_write" property="canWrite" jdbcType="TINYINT"/>
|
||||
<result column="can_execute" property="canExecute" jdbcType="TINYINT"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="TINYINT"/>
|
||||
<result column="dept_name" property="deptName" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="roleName" jdbcType="VARCHAR"/>
|
||||
<result column="resource_title" property="resourceTitle" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 基础字段 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, resource_type, resource_id, dept_id, role_id,
|
||||
can_read, can_write, can_execute,
|
||||
creator, updater, create_time, update_time, delete_time, deleted
|
||||
</sql>
|
||||
|
||||
<!-- 插入资源权限 -->
|
||||
<insert id="insertResourcePermission" parameterType="org.xyzh.common.dto.permission.TbResourcePermission">
|
||||
INSERT INTO tb_resource_permission (
|
||||
id, resource_type, resource_id, dept_id, role_id,
|
||||
can_read, can_write, can_execute,
|
||||
creator, create_time, deleted
|
||||
) VALUES (
|
||||
#{id}, #{resourceType}, #{resourceID}, #{deptID}, #{roleID},
|
||||
#{canRead}, #{canWrite}, #{canExecute},
|
||||
#{creator}, #{createTime}, #{deleted}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 批量插入资源权限 -->
|
||||
<insert id="batchInsertResourcePermission" parameterType="java.util.List">
|
||||
INSERT INTO tb_resource_permission (
|
||||
id, resource_type, resource_id, dept_id, role_id,
|
||||
can_read, can_write, can_execute,
|
||||
creator, create_time, deleted
|
||||
) VALUES
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(
|
||||
#{item.id}, #{item.resourceType}, #{item.resourceID}, #{item.deptID}, #{item.roleID},
|
||||
#{item.canRead}, #{item.canWrite}, #{item.canExecute},
|
||||
#{item.creator}, #{item.createTime}, #{item.deleted}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
@@ -17,42 +17,22 @@
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="deptRoleVOResultMap" type="org.xyzh.common.vo.DeptRoleVO">
|
||||
<result column="dept_id" property="dept.deptID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_name" property="dept.name" jdbcType="VARCHAR"/>
|
||||
<result column="dept_description" property="dept.description" jdbcType="VARCHAR"/>
|
||||
<result column="dept_creator" property="dept.creator" jdbcType="VARCHAR"/>
|
||||
<result column="dept_updater" property="dept.updater" jdbcType="VARCHAR"/>
|
||||
<result column="dept_create_time" property="dept.createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="dept_update_time" property="dept.updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="dept_delete_time" property="dept.deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="dept_deleted" property="dept.deleted" jdbcType="BOOLEAN"/>
|
||||
<result column="role_id" property="role.roleID" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="role.name" jdbcType="VARCHAR"/>
|
||||
<result column="role_description" property="role.description" jdbcType="VARCHAR"/>
|
||||
<result column="role_creator" property="role.creator" jdbcType="VARCHAR"/>
|
||||
<result column="role_updater" property="role.updater" jdbcType="VARCHAR"/>
|
||||
<result column="role_create_time" property="role.createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="role_update_time" property="role.updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="role_delete_time" property="role.deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="role_deleted" property="role.deleted" jdbcType="BOOLEAN"/>
|
||||
<resultMap id="deptRoleVOResultMap" type="org.xyzh.common.vo.UserDeptRoleVO">
|
||||
<result column="user_id" property="userID" jdbcType="VARCHAR"/>
|
||||
<result column="username" property="username" jdbcType="VARCHAR"/>
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_name" property="deptName" jdbcType="VARCHAR"/>
|
||||
<result column="dept_description" property="deptDescription" jdbcType="VARCHAR"/>
|
||||
<result column="dept_path" property="deptPath" jdbcType="VARCHAR"/>
|
||||
<result column="role_id" property="roleID" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="roleName" jdbcType="VARCHAR"/>
|
||||
<result column="role_description" property="roleDescription" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
<!-- 基础字段 -->
|
||||
<sql id="TbSysRole_Column_List">
|
||||
id, role_id, name, description, creator, updater,
|
||||
create_time, update_time, delete_time, deleted
|
||||
</sql>
|
||||
<sql id="TbSysDeptRole_Column_List">
|
||||
dr.id, dr.dept_id, dr.role_id,
|
||||
r.name as role_name, d.name as dept_name,
|
||||
r.description as role_description, d.description as dept_description,
|
||||
r.creator as role_creator, d.creator as dept_creator,
|
||||
r.updater as role_updater, d.updater as dept_updater,
|
||||
r.create_time as role_create_time, d.create_time as dept_create_time,
|
||||
r.update_time as role_update_time, d.update_time as dept_update_time,
|
||||
r.delete_time as role_delete_time, d.delete_time as dept_delete_time,
|
||||
r.deleted as role_deleted, d.deleted as dept_deleted
|
||||
</sql>
|
||||
|
||||
<!-- 通用条件 -->
|
||||
<sql id="Where_Clause">
|
||||
@@ -110,10 +90,19 @@
|
||||
<!-- 根据用户ID查询角色列表 -->
|
||||
<select id="selectDeptRolesByUserId" resultMap="deptRoleVOResultMap">
|
||||
SELECT
|
||||
<include refid="TbSysDeptRole_Column_List"/>
|
||||
dr.user_id,
|
||||
u.username,
|
||||
dr.dept_id,
|
||||
d.name AS dept_name,
|
||||
d.description AS dept_description,
|
||||
d.dept_path,
|
||||
dr.role_id,
|
||||
r.name AS role_name,
|
||||
r.description AS role_description
|
||||
FROM tb_sys_user_dept_role dr
|
||||
INNER JOIN tb_sys_role r ON r.role_id = dr.role_id
|
||||
INNER JOIN tb_sys_dept d ON d.dept_id = dr.dept_id
|
||||
LEFT JOIN tb_sys_user u ON dr.user_id = u.id AND u.deleted = 0
|
||||
LEFT JOIN tb_sys_role r ON dr.role_id = r.role_id AND r.deleted = 0
|
||||
LEFT JOIN tb_sys_dept d ON dr.dept_id = d.dept_id AND d.deleted = 0
|
||||
WHERE dr.deleted = 0
|
||||
AND dr.user_id = #{userId}
|
||||
ORDER BY dr.create_time ASC
|
||||
|
||||
@@ -44,14 +44,47 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- 用户部门角色VO结果映射 -->
|
||||
<resultMap id="UserDeptRoleVOResultMap" type="org.xyzh.common.vo.UserDeptRoleVO">
|
||||
<result column="user_id" property="userID" jdbcType="VARCHAR"/>
|
||||
<result column="username" property="username" jdbcType="VARCHAR"/>
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_name" property="deptName" jdbcType="VARCHAR"/>
|
||||
<result column="dept_description" property="deptDescription" jdbcType="VARCHAR"/>
|
||||
<result column="role_id" property="roleID" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="roleName" jdbcType="VARCHAR"/>
|
||||
<result column="role_description" property="roleDescription" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- selectByFilter -->
|
||||
|
||||
<select id="selectByFilter">
|
||||
<select id="selectByFilter" resultMap="UserDeptRoleVOResultMap">
|
||||
SELECT
|
||||
<include refid="UserDeptRole_Column_List"/>
|
||||
FROM tb_sys_user_dept_role
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY user_id, dept_id, role_id, create_time DESC
|
||||
udr.user_id,
|
||||
u.username AS username,
|
||||
udr.dept_id,
|
||||
d.name AS dept_name,
|
||||
d.description AS dept_description,
|
||||
udr.role_id,
|
||||
r.name AS role_name,
|
||||
r.description AS role_description
|
||||
FROM tb_sys_user_dept_role udr
|
||||
LEFT JOIN tb_sys_user u ON udr.user_id = u.id AND u.deleted = 0
|
||||
LEFT JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
LEFT JOIN tb_sys_role r ON udr.role_id = r.role_id AND r.deleted = 0
|
||||
<where>
|
||||
udr.deleted = 0
|
||||
<if test="userID != null">
|
||||
AND udr.user_id = #{userID}
|
||||
</if>
|
||||
<if test="deptID != null">
|
||||
AND udr.dept_id = #{deptID}
|
||||
</if>
|
||||
<if test="roleID != null">
|
||||
AND udr.role_id = #{roleID}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY udr.user_id, udr.dept_id, udr.role_id, udr.create_time DESC
|
||||
</select>
|
||||
|
||||
<insert id="bindUser" parameterType="TbSysUserDeptRole">
|
||||
@@ -70,4 +103,20 @@
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<!-- deleteUserDeptRole - 物理删除所有记录(包括软删除的) -->
|
||||
|
||||
<delete id="deleteUserDeptRole">
|
||||
DELETE FROM tb_sys_user_dept_role
|
||||
WHERE user_id = #{userID}
|
||||
</delete>
|
||||
|
||||
<!-- deleteUserDeptRoleByUserIds - 批量删除多个用户的绑定 -->
|
||||
|
||||
<delete id="deleteUserDeptRoleByUserIds">
|
||||
DELETE FROM tb_sys_user_dept_role
|
||||
WHERE user_id IN
|
||||
<foreach collection="userIds" item="userId" open="(" separator="," close=")">
|
||||
#{userId}
|
||||
</foreach>
|
||||
</delete>
|
||||
</mapper>
|
||||
@@ -242,12 +242,10 @@
|
||||
</update>
|
||||
|
||||
<!-- 根据ID删除(逻辑删除) -->
|
||||
<update id="deleteUser">
|
||||
UPDATE tb_sys_user
|
||||
SET deleted = 1,
|
||||
delete_time = NOW()
|
||||
WHERE id = #{id} AND deleted = 0
|
||||
</update>
|
||||
<delete id="deleteUser">
|
||||
DELETE FROM tb_sys_user
|
||||
WHERE id = #{userID} AND deleted = 0
|
||||
</delete>
|
||||
|
||||
<!-- 用户信息相关 -->
|
||||
|
||||
@@ -293,10 +291,7 @@
|
||||
INNER JOIN tb_sys_user_dept_role tsudr ON tsui.user_id = tsudr.user_id
|
||||
INNER JOIN tb_sys_dept tsd ON tsudr.dept_id = tsd.dept_id
|
||||
WHERE tsui.user_id = #{userId} AND tsui.deleted = 0
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- 递归查询:向上查找父部门
|
||||
SELECT
|
||||
p.dept_id,
|
||||
p.name,
|
||||
@@ -304,12 +299,12 @@
|
||||
CONCAT(p.name, '/', dh.dept_path) as dept_path,
|
||||
dh.level + 1 as level
|
||||
FROM tb_sys_dept p
|
||||
INNER JOIN dept_hierarchy dh ON p.dept_id = dh.parent_id
|
||||
INNER JOIN dept_hierarchy dh ON dh.parent_id = p.dept_id
|
||||
WHERE p.deleted = 0
|
||||
)
|
||||
SELECT dh.dept_path
|
||||
FROM dept_hierarchy dh
|
||||
WHERE dh.parent_id IS NULL -- 只取最顶层的部门路径
|
||||
WHERE dh.parent_id IS NULL
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
@@ -317,7 +312,6 @@
|
||||
|
||||
<select id="selectUserInfoTotal" resultMap="UserInfoTotalResultMap">
|
||||
WITH RECURSIVE dept_hierarchy AS (
|
||||
-- 基础查询:获取用户直接所属的部门
|
||||
SELECT
|
||||
tsd.dept_id,
|
||||
tsd.name,
|
||||
@@ -328,10 +322,7 @@
|
||||
INNER JOIN tb_sys_user_dept_role tsudr ON tsui.user_id = tsudr.user_id
|
||||
INNER JOIN tb_sys_dept tsd ON tsudr.dept_id = tsd.dept_id
|
||||
WHERE tsui.user_id = #{userId} AND tsui.deleted = 0
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- 递归查询:向上查找父部门
|
||||
SELECT
|
||||
p.dept_id,
|
||||
p.name,
|
||||
@@ -339,7 +330,7 @@
|
||||
CONCAT(p.name, '/', dh.dept_path) as dept_path,
|
||||
dh.level + 1 as level
|
||||
FROM tb_sys_dept p
|
||||
INNER JOIN dept_hierarchy dh ON p.dept_id = dh.parent_id
|
||||
INNER JOIN dept_hierarchy dh ON dh.parent_id = p.dept_id
|
||||
WHERE p.deleted = 0
|
||||
)
|
||||
SELECT
|
||||
@@ -349,7 +340,7 @@
|
||||
tus.email,
|
||||
tsui.avatar,
|
||||
tsui.gender,
|
||||
dh.dept_path as dept_name,
|
||||
(SELECT dept_path FROM dept_hierarchy WHERE parent_id IS NULL LIMIT 1) as dept_name,
|
||||
tsr.name as role_name,
|
||||
tsui.level,
|
||||
tsui.id_card,
|
||||
@@ -357,10 +348,8 @@
|
||||
FROM tb_sys_user_info tsui
|
||||
INNER JOIN tb_sys_user tus ON tsui.user_id = tus.id
|
||||
INNER JOIN tb_sys_user_dept_role tsudr ON tsui.user_id = tsudr.user_id
|
||||
INNER JOIN dept_hierarchy dh ON tsudr.dept_id = dh.dept_id
|
||||
INNER JOIN tb_sys_role tsr ON tsudr.role_id = tsr.role_id
|
||||
INNER JOIN tb_sys_role tsr ON tsudr.role_id = tsr.role_id
|
||||
WHERE tsui.user_id = #{userId}
|
||||
AND tsui.deleted = 0
|
||||
AND dh.parent_id IS NULL -- 只取最顶层的部门路径
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
10
schoolNewsWeb/package-lock.json
generated
10
schoolNewsWeb/package-lock.json
generated
@@ -27,7 +27,7 @@
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"sass": "^1.32.7",
|
||||
"typescript": "~4.5.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-pwa": "^0.19.0"
|
||||
}
|
||||
@@ -7505,9 +7505,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.5.5",
|
||||
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.5.5.tgz",
|
||||
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.2.2.tgz",
|
||||
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -7515,7 +7515,7 @@
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"sass": "^1.32.7",
|
||||
"typescript": "~4.5.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-pwa": "^0.19.0"
|
||||
}
|
||||
|
||||
6
schoolNewsWeb/public/img/admin/maintain.svg
Normal file
6
schoolNewsWeb/public/img/admin/maintain.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.6667 1.8335H2.33334V11.1668H13.6667V1.8335Z" stroke="#333333" stroke-width="1.33333" stroke-linejoin="round"/>
|
||||
<path d="M5.33334 13.8332L8.00001 11.1665L10.6667 13.8332" stroke="#333333" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.6413 8.22084L6.52213 6.3849L8.00276 7.8332L11.3223 4.50684" stroke="#333333" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1.33334 1.8335H14.6667" stroke="#333333" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 659 B |
7
schoolNewsWeb/public/img/admin/resource.svg
Normal file
7
schoolNewsWeb/public/img/admin/resource.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 1.3335H4.00001C3.64638 1.3335 3.30724 1.47397 3.0572 1.72402C2.80715 1.97407 2.66667 2.31321 2.66667 2.66683V13.3335C2.66667 13.6871 2.80715 14.0263 3.0572 14.2763C3.30724 14.5264 3.64638 14.6668 4.00001 14.6668H12C12.3536 14.6668 12.6928 14.5264 12.9428 14.2763C13.1929 14.0263 13.3333 13.6871 13.3333 13.3335V4.66683L10 1.3335Z" stroke="#0A0A0A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.33333 1.3335V4.00016C9.33333 4.35378 9.4738 4.69292 9.72385 4.94297C9.9739 5.19302 10.313 5.3335 10.6667 5.3335H13.3333" stroke="#0A0A0A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.66666 6H5.33333" stroke="#0A0A0A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.6667 8.6665H5.33333" stroke="#0A0A0A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.6667 11.3335H5.33333" stroke="#0A0A0A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -83,11 +83,11 @@ export const learningTaskApi = {
|
||||
},
|
||||
|
||||
/**
|
||||
* 发布学习任务
|
||||
* 发布\下架学习任务
|
||||
* @param taskID 任务ID
|
||||
* @returns Promise<ResultDomain<LearningTask>>
|
||||
*/
|
||||
async publishTask(task: LearningTask): Promise<ResultDomain<LearningTask>> {
|
||||
async changeTaskStatus(task: LearningTask): Promise<ResultDomain<LearningTask>> {
|
||||
const response = await api.put<LearningTask>(`${this.learningTaskPrefix}/status`, task);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
@@ -96,13 +96,13 @@ export const deptApi = {
|
||||
|
||||
/**
|
||||
* @description 查询部门角色列表
|
||||
* @param dept 部门角色信息
|
||||
* @param deptRole 部门角色信息
|
||||
* @returns Promise<ResultDomain<SysDeptRole>> 部门角色列表
|
||||
* @author yslg
|
||||
* @ since 2025-10-06
|
||||
*/
|
||||
async getDeptRoleList(dept: SysDeptRole): Promise<ResultDomain<SysDeptRole>> {
|
||||
const response = await api.post<SysDeptRole>('/depts/role/list', dept);
|
||||
async getDeptRoleList(deptRole: SysDeptRole): Promise<ResultDomain<SysDeptRole>> {
|
||||
const response = await api.post<SysDeptRole>('/depts/role/list', deptRole);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
||||
@@ -97,8 +97,8 @@ export const userApi = {
|
||||
* @param user 用户信息
|
||||
* @returns Promise<ResultDomain<SysUserDeptRole>>
|
||||
*/
|
||||
async getUserDeptRole(user: SysUserDeptRole): Promise<ResultDomain<SysUserDeptRole>> {
|
||||
const response = await api.post<SysUserDeptRole>('/users/bind/deptrole/list', user);
|
||||
async getUserDeptRole(user: UserDeptRoleVO): Promise<ResultDomain<UserDeptRoleVO>> {
|
||||
const response = await api.post<UserDeptRoleVO>('/users/bind/deptrole/list', user);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
||||
862
schoolNewsWeb/src/components/base/GenericSelector.vue
Normal file
862
schoolNewsWeb/src/components/base/GenericSelector.vue
Normal file
@@ -0,0 +1,862 @@
|
||||
<template>
|
||||
<div v-if="visible" class="modal-overlay" @click.self="handleCancel">
|
||||
<div class="modal-content large">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">{{ title }}</h3>
|
||||
<button class="modal-close" @click="handleCancel">✕</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="generic-selector">
|
||||
<!-- 左侧:可选项 -->
|
||||
<div class="selector-panel">
|
||||
<div class="panel-header">
|
||||
<h4 class="panel-title">{{ leftTitle }}</h4>
|
||||
<span class="panel-count">
|
||||
{{ countText(availableList.length) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="panel-search" v-if="showSearch">
|
||||
<input
|
||||
v-model="searchAvailable"
|
||||
type="text"
|
||||
:placeholder="searchPlaceholder"
|
||||
class="search-input-small"
|
||||
/>
|
||||
</div>
|
||||
<div class="panel-body left-panel">
|
||||
<!-- 列表模式 -->
|
||||
<div v-if="!useTree" class="item-list">
|
||||
<div
|
||||
v-for="item in filteredAvailable"
|
||||
:key="getItemId(item)"
|
||||
class="list-item"
|
||||
:class="{ selected: getItemId(item) && selectedAvailable.includes(getItemId(item)) }"
|
||||
@click="getItemId(item) && toggleAvailable(getItemId(item))"
|
||||
@dblclick="getItemId(item) && moveToTarget(getItemId(item))"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="!!(getItemId(item) && selectedAvailable.includes(getItemId(item)))"
|
||||
@click.stop="getItemId(item) && toggleAvailable(getItemId(item))"
|
||||
/>
|
||||
<span class="item-label">{{ getItemLabel(item) }}</span>
|
||||
<span class="item-sublabel" v-if="getItemSublabel(item)">{{ getItemSublabel(item) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<div v-if="usePagination && loadingMore" class="loading-more">
|
||||
<div class="loading-spinner-small"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-if="usePagination && !hasMore && availableList.length > 0" class="no-more">
|
||||
已加载全部数据
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 树形模式 -->
|
||||
<div v-else class="tree-list">
|
||||
<TreeNode
|
||||
v-for="node in treeData"
|
||||
:key="getNodeId(node)"
|
||||
:node="node"
|
||||
:tree-props="treeProps"
|
||||
:expanded-keys="expandedKeys"
|
||||
:selected-ids="selectedAvailable"
|
||||
:only-leaf-selectable="onlyLeafSelectable"
|
||||
@toggle-expand="toggleExpand"
|
||||
@toggle-select="toggleAvailable"
|
||||
@dblclick="moveToTarget"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间:操作按钮 -->
|
||||
<div class="selector-actions">
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="moveSelectedToTarget"
|
||||
:disabled="selectedAvailable.length === 0"
|
||||
:title="mode === 'add' ? '添加选中' : '删除选中'"
|
||||
>
|
||||
<span class="arrow">→</span>
|
||||
<span class="btn-text">{{ mode === 'add' ? '添加' : '删除' }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="moveAllToTarget"
|
||||
:disabled="availableList.length === 0"
|
||||
:title="mode === 'add' ? '全部添加' : '全部删除'"
|
||||
>
|
||||
<span class="arrow">⇒</span>
|
||||
<span class="btn-text">全部</span>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="moveBackSelected"
|
||||
:disabled="selectedTarget.length === 0"
|
||||
title="移回选中"
|
||||
>
|
||||
<span class="arrow">←</span>
|
||||
<span class="btn-text">移回</span>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="moveBackAll"
|
||||
:disabled="targetList.length === 0"
|
||||
title="全部移回"
|
||||
>
|
||||
<span class="arrow">⇐</span>
|
||||
<span class="btn-text">全部</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:已选项 -->
|
||||
<div class="selector-panel">
|
||||
<div class="panel-header">
|
||||
<h4 class="panel-title">{{ rightTitle }}</h4>
|
||||
<span class="panel-count">{{ countText(targetList.length) }}</span>
|
||||
</div>
|
||||
<div class="panel-search" v-if="showSearch">
|
||||
<input
|
||||
v-model="searchTarget"
|
||||
type="text"
|
||||
:placeholder="searchPlaceholder"
|
||||
class="search-input-small"
|
||||
/>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="item-list">
|
||||
<div
|
||||
v-for="item in filteredTarget"
|
||||
:key="getItemId(item)"
|
||||
class="list-item"
|
||||
:class="{ selected: getItemId(item) && selectedTarget.includes(getItemId(item)) }"
|
||||
@click="getItemId(item) && toggleTarget(getItemId(item))"
|
||||
@dblclick="getItemId(item) && moveBackToAvailable(getItemId(item))"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="!!(getItemId(item) && selectedTarget.includes(getItemId(item)))"
|
||||
@click.stop="getItemId(item) && toggleTarget(getItemId(item))"
|
||||
/>
|
||||
<span class="item-label">{{ getItemLabel(item) }}</span>
|
||||
<span class="item-sublabel" v-if="getItemSublabel(item)">{{ getItemSublabel(item) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn-primary" @click="handleConfirm" :disabled="loading">
|
||||
{{ loading ? '处理中...' : '确定' }}
|
||||
</button>
|
||||
<button class="btn-default" @click="handleCancel">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T extends Record<string, any>">
|
||||
import { ref, computed, watch, nextTick } from 'vue';
|
||||
import type { ResultDomain, PageParam } from '@/types';
|
||||
import { TreeNode } from '@/components/base';
|
||||
|
||||
// 泛型项类型
|
||||
type GenericItem = Record<string, any>;
|
||||
|
||||
interface ItemConfig {
|
||||
/** ID 字段名 */
|
||||
id: string;
|
||||
/** 显示标签字段名 */
|
||||
label: string;
|
||||
/** 副标签字段名(可选) */
|
||||
sublabel?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
visible?: boolean;
|
||||
mode?: 'add' | 'remove';
|
||||
title?: string;
|
||||
leftTitle?: string;
|
||||
rightTitle?: string;
|
||||
/** 【新】获取所有可选项的接口 */
|
||||
fetchAvailableApi?: () => Promise<ResultDomain<GenericItem>>;
|
||||
/** 【新】获取已选项的接口 */
|
||||
fetchSelectedApi?: () => Promise<ResultDomain<GenericItem>>;
|
||||
/** 【新】过滤已选项的方法(返回过滤后的可选项) */
|
||||
filterSelected?: (available: GenericItem[], selected: GenericItem[]) => GenericItem[];
|
||||
/** 可选项列表(兼容旧方式,如果提供了fetchAvailableApi则忽略) */
|
||||
availableItems?: GenericItem[];
|
||||
/** 初始已选项列表(兼容旧方式,如果提供了fetchSelectedApi则忽略) */
|
||||
initialTargetItems?: GenericItem[];
|
||||
loading?: boolean;
|
||||
/** 字段配置 */
|
||||
itemConfig: ItemConfig;
|
||||
/** 单位名称(用于计数显示) */
|
||||
unitName?: string;
|
||||
/** 搜索占位符 */
|
||||
searchPlaceholder?: string;
|
||||
/** 是否显示搜索框 */
|
||||
showSearch?: boolean;
|
||||
/** 分页加载API方法 */
|
||||
fetchApi?: (pageParam: PageParam, filter?: any) => Promise<ResultDomain<GenericItem>>;
|
||||
/** 过滤参数 */
|
||||
filterParams?: any;
|
||||
/** 每页数量 */
|
||||
pageSize?: number;
|
||||
/** 是否使用分页加载 */
|
||||
usePagination?: boolean;
|
||||
/** 是否使用树形展示 */
|
||||
useTree?: boolean;
|
||||
/** 树形数据转换函数 */
|
||||
treeTransform?: (data: GenericItem[]) => any[];
|
||||
/** 树节点的配置 */
|
||||
treeProps?: {
|
||||
children?: string;
|
||||
label?: string;
|
||||
id?: string;
|
||||
};
|
||||
/** 是否只允许选择叶子节点(树形模式下有效) */
|
||||
onlyLeafSelectable?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
mode: 'add',
|
||||
title: '选择',
|
||||
leftTitle: '可选项',
|
||||
rightTitle: '已选项',
|
||||
availableItems: () => [],
|
||||
initialTargetItems: () => [],
|
||||
loading: false,
|
||||
unitName: '项',
|
||||
searchPlaceholder: '搜索...',
|
||||
showSearch: true,
|
||||
pageSize: 20,
|
||||
usePagination: false,
|
||||
useTree: false,
|
||||
treeProps: () => ({
|
||||
children: 'children',
|
||||
label: 'label',
|
||||
id: 'id'
|
||||
}),
|
||||
onlyLeafSelectable: false
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean];
|
||||
confirm: [items: GenericItem[]];
|
||||
cancel: [];
|
||||
}>();
|
||||
|
||||
// 数据列表
|
||||
const availableList = ref<GenericItem[]>([]);
|
||||
const targetList = ref<GenericItem[]>([]);
|
||||
|
||||
// 树形数据相关
|
||||
const treeData = ref<any[]>([]);
|
||||
const expandedKeys = ref<Set<string>>(new Set());
|
||||
|
||||
// 选中状态
|
||||
const selectedAvailable = ref<string[]>([]);
|
||||
const selectedTarget = ref<string[]>([]);
|
||||
|
||||
// 搜索关键词
|
||||
const searchAvailable = ref('');
|
||||
const searchTarget = ref('');
|
||||
|
||||
// 分页相关
|
||||
const currentPage = ref(1);
|
||||
const totalElements = ref(0);
|
||||
const hasMore = ref(true);
|
||||
const loadingMore = ref(false);
|
||||
const availablePanelRef = ref<HTMLElement | null>(null);
|
||||
|
||||
// 获取项的 ID
|
||||
function getItemId(item: GenericItem): string {
|
||||
return String(item[props.itemConfig.id] || '');
|
||||
}
|
||||
|
||||
// 获取项的标签
|
||||
function getItemLabel(item: GenericItem): string {
|
||||
return String(item[props.itemConfig.label] || '');
|
||||
}
|
||||
|
||||
// 获取项的副标签
|
||||
function getItemSublabel(item: GenericItem): string {
|
||||
if (!props.itemConfig.sublabel) return '';
|
||||
return String(item[props.itemConfig.sublabel] || '');
|
||||
}
|
||||
|
||||
// 获取树节点 ID
|
||||
function getNodeId(node: any): string {
|
||||
const idProp = props.treeProps?.id || 'id';
|
||||
return String(node[idProp] || '');
|
||||
}
|
||||
|
||||
// 获取树节点标签
|
||||
// function getNodeLabel(node: any): string {
|
||||
// const labelProp = props.treeProps?.label || 'label';
|
||||
// return String(node[labelProp] || '');
|
||||
// }
|
||||
|
||||
// 获取树节点子节点
|
||||
// function getNodeChildren(node: any): any[] {
|
||||
// const childrenProp = props.treeProps?.children || 'children';
|
||||
// return node[childrenProp] || [];
|
||||
// }
|
||||
|
||||
// 计数文本
|
||||
function countText(count: number): string {
|
||||
if (props.usePagination && totalElements.value > 0) {
|
||||
return `${count}/${totalElements.value} ${props.unitName}`;
|
||||
}
|
||||
return `${count} ${props.unitName}`;
|
||||
}
|
||||
|
||||
// 监听props变化,初始化数据
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
initializeData();
|
||||
} else {
|
||||
resetData();
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 监听搜索关键词变化
|
||||
watch(searchAvailable, () => {
|
||||
if (props.usePagination && props.fetchApi) {
|
||||
resetPaginationAndLoad();
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化数据
|
||||
async function initializeData() {
|
||||
try {
|
||||
// 优先使用接口方式加载数据
|
||||
if (props.fetchAvailableApi || props.fetchSelectedApi) {
|
||||
await loadDataFromApis();
|
||||
} else if (props.usePagination && props.fetchApi) {
|
||||
// 使用分页方式
|
||||
currentPage.value = 1;
|
||||
hasMore.value = true;
|
||||
availableList.value = [];
|
||||
await loadAvailableItems();
|
||||
targetList.value = [...props.initialTargetItems];
|
||||
} else {
|
||||
// 使用传入的数据
|
||||
availableList.value = [...props.availableItems];
|
||||
targetList.value = [...props.initialTargetItems];
|
||||
}
|
||||
|
||||
// 如果使用树形展示,转换数据
|
||||
if (props.useTree && props.treeTransform) {
|
||||
treeData.value = props.treeTransform(availableList.value);
|
||||
expandAllNodes(treeData.value);
|
||||
}
|
||||
|
||||
selectedAvailable.value = [];
|
||||
selectedTarget.value = [];
|
||||
searchAvailable.value = '';
|
||||
searchTarget.value = '';
|
||||
|
||||
await nextTick();
|
||||
bindScrollEvent();
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 从接口加载数据
|
||||
async function loadDataFromApis() {
|
||||
try {
|
||||
// 加载所有可选项
|
||||
if (props.fetchAvailableApi) {
|
||||
const availableResult = await props.fetchAvailableApi();
|
||||
if (availableResult.success) {
|
||||
const allAvailable = availableResult.dataList || [];
|
||||
|
||||
// 加载已选项
|
||||
if (props.fetchSelectedApi) {
|
||||
const selectedResult = await props.fetchSelectedApi();
|
||||
if (selectedResult.success) {
|
||||
targetList.value = selectedResult.dataList || [];
|
||||
|
||||
// 过滤已选项
|
||||
if (props.filterSelected) {
|
||||
availableList.value = props.filterSelected(allAvailable, targetList.value);
|
||||
} else {
|
||||
// 默认过滤逻辑:根据ID过滤
|
||||
const selectedIds = new Set(targetList.value.map(item => getItemId(item)));
|
||||
availableList.value = allAvailable.filter(item => !selectedIds.has(getItemId(item)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果没有提供已选接口,使用传入的初始数据
|
||||
targetList.value = [...props.initialTargetItems];
|
||||
const selectedIds = new Set(targetList.value.map(item => getItemId(item)));
|
||||
availableList.value = allAvailable.filter(item => !selectedIds.has(getItemId(item)));
|
||||
}
|
||||
}
|
||||
} else if (props.fetchSelectedApi) {
|
||||
// 只有已选接口
|
||||
const selectedResult = await props.fetchSelectedApi();
|
||||
if (selectedResult.success) {
|
||||
targetList.value = selectedResult.dataList || [];
|
||||
}
|
||||
availableList.value = [...props.availableItems];
|
||||
const selectedIds = new Set(targetList.value.map(item => getItemId(item)));
|
||||
availableList.value = availableList.value.filter(item => !selectedIds.has(getItemId(item)));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('从接口加载数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 展开所有树节点
|
||||
function expandAllNodes(nodes: any[]) {
|
||||
const childrenProp = props.treeProps?.children || 'children';
|
||||
nodes.forEach(node => {
|
||||
const nodeId = getNodeId(node);
|
||||
if (nodeId) {
|
||||
expandedKeys.value.add(nodeId);
|
||||
}
|
||||
const children = node[childrenProp] || [];
|
||||
if (children && children.length > 0) {
|
||||
expandAllNodes(children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 重置数据
|
||||
function resetData() {
|
||||
availableList.value = [];
|
||||
targetList.value = [];
|
||||
treeData.value = [];
|
||||
expandedKeys.value.clear();
|
||||
selectedAvailable.value = [];
|
||||
selectedTarget.value = [];
|
||||
searchAvailable.value = '';
|
||||
searchTarget.value = '';
|
||||
currentPage.value = 1;
|
||||
totalElements.value = 0;
|
||||
hasMore.value = true;
|
||||
loadingMore.value = false;
|
||||
unbindScrollEvent();
|
||||
}
|
||||
|
||||
// 加载可选项
|
||||
async function loadAvailableItems() {
|
||||
if (!props.fetchApi) return;
|
||||
|
||||
try {
|
||||
loadingMore.value = true;
|
||||
const pageParam: PageParam = {
|
||||
pageNumber: currentPage.value,
|
||||
pageSize: props.pageSize
|
||||
};
|
||||
|
||||
const filter = searchAvailable.value
|
||||
? { ...props.filterParams, keyword: searchAvailable.value }
|
||||
: props.filterParams;
|
||||
|
||||
const res = await props.fetchApi(pageParam, filter);
|
||||
|
||||
if (res.success) {
|
||||
const newItems = res.dataList || [];
|
||||
const targetItemIds = targetList.value.map(item => getItemId(item));
|
||||
const filteredItems = newItems.filter(item => !targetItemIds.includes(getItemId(item)));
|
||||
|
||||
if (currentPage.value === 1) {
|
||||
availableList.value = filteredItems;
|
||||
} else {
|
||||
availableList.value.push(...filteredItems);
|
||||
}
|
||||
|
||||
totalElements.value = res.pageParam?.totalElements || 0;
|
||||
const totalPages = Math.ceil(totalElements.value / props.pageSize);
|
||||
hasMore.value = currentPage.value < totalPages;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
} finally {
|
||||
loadingMore.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置分页并重新加载
|
||||
async function resetPaginationAndLoad() {
|
||||
currentPage.value = 1;
|
||||
hasMore.value = true;
|
||||
availableList.value = [];
|
||||
await loadAvailableItems();
|
||||
}
|
||||
|
||||
// 绑定滚动事件
|
||||
function bindScrollEvent() {
|
||||
const panelBody = document.querySelector('.panel-body.left-panel');
|
||||
if (panelBody) {
|
||||
availablePanelRef.value = panelBody as HTMLElement;
|
||||
panelBody.addEventListener('scroll', handleScroll);
|
||||
}
|
||||
}
|
||||
|
||||
// 解绑滚动事件
|
||||
function unbindScrollEvent() {
|
||||
if (availablePanelRef.value) {
|
||||
availablePanelRef.value.removeEventListener('scroll', handleScroll);
|
||||
availablePanelRef.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理滚动事件
|
||||
function handleScroll(event: Event) {
|
||||
if (!props.usePagination || !props.fetchApi) return;
|
||||
|
||||
const target = event.target as HTMLElement;
|
||||
const scrollTop = target.scrollTop;
|
||||
const scrollHeight = target.scrollHeight;
|
||||
const clientHeight = target.clientHeight;
|
||||
|
||||
if (scrollHeight - scrollTop - clientHeight < 50 && hasMore.value && !loadingMore.value) {
|
||||
currentPage.value++;
|
||||
loadAvailableItems();
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤可选项
|
||||
const filteredAvailable = computed(() => {
|
||||
if (props.usePagination) {
|
||||
return availableList.value;
|
||||
}
|
||||
|
||||
if (!searchAvailable.value) {
|
||||
return availableList.value;
|
||||
}
|
||||
|
||||
const keyword = searchAvailable.value.toLowerCase();
|
||||
return availableList.value.filter(item => {
|
||||
const label = getItemLabel(item).toLowerCase();
|
||||
const sublabel = getItemSublabel(item).toLowerCase();
|
||||
return label.includes(keyword) || sublabel.includes(keyword);
|
||||
});
|
||||
});
|
||||
|
||||
// 过滤已选项
|
||||
const filteredTarget = computed(() => {
|
||||
if (!searchTarget.value) {
|
||||
return targetList.value;
|
||||
}
|
||||
|
||||
const keyword = searchTarget.value.toLowerCase();
|
||||
return targetList.value.filter(item => {
|
||||
const label = getItemLabel(item).toLowerCase();
|
||||
const sublabel = getItemSublabel(item).toLowerCase();
|
||||
return label.includes(keyword) || sublabel.includes(keyword);
|
||||
});
|
||||
});
|
||||
|
||||
// 切换树节点展开/折叠
|
||||
function toggleExpand(nodeId: string) {
|
||||
if (expandedKeys.value.has(nodeId)) {
|
||||
expandedKeys.value.delete(nodeId);
|
||||
} else {
|
||||
expandedKeys.value.add(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查节点是否展开(暂未使用,保留供TreeNode组件使用)
|
||||
// function isExpanded(nodeId: string): boolean {
|
||||
// return expandedKeys.value.has(nodeId);
|
||||
// }
|
||||
|
||||
// 获取树节点的所有子节点ID(递归)
|
||||
function getAllChildrenIds(node: any): string[] {
|
||||
const childrenProp = props.treeProps?.children || 'children';
|
||||
const children = node[childrenProp] || [];
|
||||
const ids: string[] = [];
|
||||
|
||||
children.forEach((child: any) => {
|
||||
const childId = getNodeId(child);
|
||||
if (childId) {
|
||||
ids.push(childId);
|
||||
// 递归获取子节点的子节点
|
||||
ids.push(...getAllChildrenIds(child));
|
||||
}
|
||||
});
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
// 在树形数据中查找节点
|
||||
function findNodeInTree(nodeId: string, nodes: any[]): any | null {
|
||||
for (const node of nodes) {
|
||||
const id = getNodeId(node);
|
||||
if (id === nodeId) {
|
||||
return node;
|
||||
}
|
||||
const childrenProp = props.treeProps?.children || 'children';
|
||||
const children = node[childrenProp] || [];
|
||||
if (children.length > 0) {
|
||||
const found = findNodeInTree(nodeId, children);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查节点是否为叶子节点
|
||||
function isLeafNode(node: any): boolean {
|
||||
const childrenProp = props.treeProps?.children || 'children';
|
||||
const children = node[childrenProp] || [];
|
||||
return children.length === 0;
|
||||
}
|
||||
|
||||
// 切换可选项的选中状态(支持级联选择)
|
||||
function toggleAvailable(itemId: string) {
|
||||
const index = selectedAvailable.value.indexOf(itemId);
|
||||
|
||||
if (props.useTree && treeData.value.length > 0) {
|
||||
// 树形模式下的级联选择
|
||||
const node = findNodeInTree(itemId, treeData.value);
|
||||
if (node) {
|
||||
// 如果只允许选择叶子节点,检查是否为叶子节点
|
||||
if (props.onlyLeafSelectable && !isLeafNode(node)) {
|
||||
// 非叶子节点,不允许选择
|
||||
return;
|
||||
}
|
||||
|
||||
const childrenIds = getAllChildrenIds(node);
|
||||
|
||||
if (index > -1) {
|
||||
// 取消选中:移除当前节点和所有子节点
|
||||
selectedAvailable.value = selectedAvailable.value.filter(
|
||||
id => id !== itemId && !childrenIds.includes(id)
|
||||
);
|
||||
} else {
|
||||
// 选中:添加当前节点和所有子节点
|
||||
selectedAvailable.value.push(itemId);
|
||||
childrenIds.forEach(childId => {
|
||||
if (!selectedAvailable.value.includes(childId)) {
|
||||
selectedAvailable.value.push(childId);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 节点未找到,按普通方式处理
|
||||
if (index > -1) {
|
||||
selectedAvailable.value.splice(index, 1);
|
||||
} else {
|
||||
selectedAvailable.value.push(itemId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 非树形模式,普通切换
|
||||
if (index > -1) {
|
||||
selectedAvailable.value.splice(index, 1);
|
||||
} else {
|
||||
selectedAvailable.value.push(itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 切换已选项的选中状态(暂不支持级联,因为已选项通常不需要级联)
|
||||
function toggleTarget(itemId: string) {
|
||||
const index = selectedTarget.value.indexOf(itemId);
|
||||
if (index > -1) {
|
||||
selectedTarget.value.splice(index, 1);
|
||||
} else {
|
||||
selectedTarget.value.push(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
// 从树形数据中收集所有节点(扁平化)
|
||||
function flattenTree(nodes: any[]): any[] {
|
||||
const result: any[] = [];
|
||||
const childrenProp = props.treeProps?.children || 'children';
|
||||
|
||||
nodes.forEach(node => {
|
||||
result.push(node);
|
||||
const children = node[childrenProp] || [];
|
||||
if (children.length > 0) {
|
||||
result.push(...flattenTree(children));
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 移动选中项到目标区域
|
||||
function moveSelectedToTarget() {
|
||||
let itemsToMove: any[] = [];
|
||||
|
||||
if (props.useTree && treeData.value.length > 0) {
|
||||
// 树形模式:从扁平化的树数据中查找
|
||||
const flatItems = flattenTree(treeData.value);
|
||||
itemsToMove = flatItems.filter(item =>
|
||||
selectedAvailable.value.includes(getNodeId(item))
|
||||
);
|
||||
} else {
|
||||
// 列表模式:从可选列表中查找
|
||||
itemsToMove = availableList.value.filter(item =>
|
||||
selectedAvailable.value.includes(getItemId(item))
|
||||
);
|
||||
}
|
||||
|
||||
targetList.value.push(...itemsToMove);
|
||||
|
||||
if (props.useTree && treeData.value.length > 0) {
|
||||
// 树形模式:需要重新构建树(移除已选项)
|
||||
availableList.value = availableList.value.filter(item =>
|
||||
!selectedAvailable.value.includes(getItemId(item))
|
||||
);
|
||||
// 重新转换为树形结构
|
||||
if (props.treeTransform) {
|
||||
treeData.value = props.treeTransform(availableList.value);
|
||||
}
|
||||
} else {
|
||||
// 列表模式:直接过滤
|
||||
availableList.value = availableList.value.filter(item =>
|
||||
!selectedAvailable.value.includes(getItemId(item))
|
||||
);
|
||||
}
|
||||
|
||||
selectedAvailable.value = [];
|
||||
}
|
||||
|
||||
// 移动单个项到目标区域(双击)
|
||||
function moveToTarget(itemId: string) {
|
||||
let item: any = null;
|
||||
let itemsToMove: any[] = [];
|
||||
|
||||
if (props.useTree && treeData.value.length > 0) {
|
||||
// 树形模式:查找节点和所有子节点
|
||||
const node = findNodeInTree(itemId, treeData.value);
|
||||
if (node) {
|
||||
// 如果只允许选择叶子节点,检查是否为叶子节点
|
||||
if (props.onlyLeafSelectable && !isLeafNode(node)) {
|
||||
// 非叶子节点,不允许移动
|
||||
return;
|
||||
}
|
||||
|
||||
item = node;
|
||||
const childrenIds = getAllChildrenIds(node);
|
||||
const flatItems = flattenTree(treeData.value);
|
||||
|
||||
// 收集当前节点和所有子节点
|
||||
itemsToMove = flatItems.filter(i => {
|
||||
const id = getNodeId(i);
|
||||
return id === itemId || childrenIds.includes(id);
|
||||
});
|
||||
|
||||
// 移动到已选列表
|
||||
targetList.value.push(...itemsToMove);
|
||||
|
||||
// 从可选列表中移除
|
||||
const idsToRemove = new Set([itemId, ...childrenIds]);
|
||||
availableList.value = availableList.value.filter(i =>
|
||||
!idsToRemove.has(getItemId(i))
|
||||
);
|
||||
|
||||
// 重新构建树
|
||||
if (props.treeTransform) {
|
||||
treeData.value = props.treeTransform(availableList.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 列表模式
|
||||
item = availableList.value.find(i => getItemId(i) === itemId);
|
||||
if (item) {
|
||||
targetList.value.push(item);
|
||||
availableList.value = availableList.value.filter(i => getItemId(i) !== itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动所有项到目标区域
|
||||
function moveAllToTarget() {
|
||||
if (props.useTree && treeData.value.length > 0) {
|
||||
// 树形模式:扁平化所有节点
|
||||
const flatItems = flattenTree(treeData.value);
|
||||
targetList.value.push(...flatItems);
|
||||
availableList.value = [];
|
||||
treeData.value = [];
|
||||
} else {
|
||||
// 列表模式
|
||||
targetList.value.push(...availableList.value);
|
||||
availableList.value = [];
|
||||
}
|
||||
selectedAvailable.value = [];
|
||||
}
|
||||
|
||||
// 移回选中项到可选区域
|
||||
function moveBackSelected() {
|
||||
const itemsToMoveBack = targetList.value.filter(item =>
|
||||
selectedTarget.value.includes(getItemId(item))
|
||||
);
|
||||
|
||||
availableList.value.push(...itemsToMoveBack);
|
||||
targetList.value = targetList.value.filter(item =>
|
||||
!selectedTarget.value.includes(getItemId(item))
|
||||
);
|
||||
|
||||
// 如果是树形模式,重新构建树
|
||||
if (props.useTree && props.treeTransform) {
|
||||
treeData.value = props.treeTransform(availableList.value);
|
||||
expandAllNodes(treeData.value);
|
||||
}
|
||||
|
||||
selectedTarget.value = [];
|
||||
}
|
||||
|
||||
// 移回单个项到可选区域(双击)
|
||||
function moveBackToAvailable(itemId: string) {
|
||||
const item = targetList.value.find(i => getItemId(i) === itemId);
|
||||
if (item) {
|
||||
availableList.value.push(item);
|
||||
targetList.value = targetList.value.filter(i => getItemId(i) !== itemId);
|
||||
|
||||
// 如果是树形模式,重新构建树
|
||||
if (props.useTree && props.treeTransform) {
|
||||
treeData.value = props.treeTransform(availableList.value);
|
||||
expandAllNodes(treeData.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移回所有项到可选区域
|
||||
function moveBackAll() {
|
||||
availableList.value.push(...targetList.value);
|
||||
targetList.value = [];
|
||||
|
||||
// 如果是树形模式,重新构建树
|
||||
if (props.useTree && props.treeTransform) {
|
||||
treeData.value = props.treeTransform(availableList.value);
|
||||
expandAllNodes(treeData.value);
|
||||
}
|
||||
|
||||
selectedTarget.value = [];
|
||||
}
|
||||
|
||||
// 确认
|
||||
function handleConfirm() {
|
||||
emit('confirm', targetList.value);
|
||||
emit('update:visible', false);
|
||||
}
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
emit('cancel');
|
||||
emit('update:visible', false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './selector-styles.scss';
|
||||
</style>
|
||||
|
||||
221
schoolNewsWeb/src/components/base/TreeNode.vue
Normal file
221
schoolNewsWeb/src/components/base/TreeNode.vue
Normal file
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<div class="tree-node">
|
||||
<div
|
||||
class="tree-node-content"
|
||||
:style="{ paddingLeft: `${level * 20}px` }"
|
||||
:class="{
|
||||
selected: selectedIds.includes(nodeId),
|
||||
disabled: isCheckboxDisabled
|
||||
}"
|
||||
@click.stop="handleClick"
|
||||
@dblclick.stop="handleDblClick"
|
||||
>
|
||||
<!-- 展开/折叠图标 -->
|
||||
<span
|
||||
v-if="hasChildren"
|
||||
class="expand-icon"
|
||||
:class="{ expanded }"
|
||||
@click.stop="$emit('toggle-expand', nodeId)"
|
||||
>
|
||||
<img src="@/assets/imgs/arrow-down.svg" :class="{expanded}"/>
|
||||
</span>
|
||||
<span v-else class="expand-icon-placeholder"></span>
|
||||
|
||||
<!-- 复选框 -->
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="selectedIds.includes(nodeId)"
|
||||
:disabled="isCheckboxDisabled"
|
||||
@click.stop="handleCheckboxClick"
|
||||
/>
|
||||
|
||||
<!-- 节点标签 -->
|
||||
<span class="node-label">{{ nodeLabel }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 子节点 -->
|
||||
<div v-if="hasChildren && expanded" class="tree-node-children">
|
||||
<TreeNode
|
||||
v-for="child in children"
|
||||
:key="getChildId(child)"
|
||||
:node="child"
|
||||
:level="level + 1"
|
||||
:selected-ids="selectedIds"
|
||||
:tree-props="treeProps"
|
||||
:expanded-keys="expandedKeys"
|
||||
:only-leaf-selectable="onlyLeafSelectable"
|
||||
@toggle-expand="$emit('toggle-expand', $event)"
|
||||
@toggle-select="$emit('toggle-select', $event)"
|
||||
@dblclick="$emit('dblclick', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'TreeNode'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
node: any;
|
||||
level?: number;
|
||||
selectedIds: string[];
|
||||
treeProps?: {
|
||||
children?: string;
|
||||
label?: string;
|
||||
id?: string;
|
||||
};
|
||||
expandedKeys: Set<string>;
|
||||
onlyLeafSelectable?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
level: 0,
|
||||
treeProps: () => ({
|
||||
children: 'children',
|
||||
label: 'label',
|
||||
id: 'id'
|
||||
}),
|
||||
onlyLeafSelectable: false
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'toggle-expand': [nodeId: string];
|
||||
'toggle-select': [nodeId: string];
|
||||
'dblclick': [nodeId: string];
|
||||
}>();
|
||||
|
||||
const nodeId = computed(() => {
|
||||
const idProp = props.treeProps?.id || 'id';
|
||||
return String(props.node[idProp] || '');
|
||||
});
|
||||
|
||||
const nodeLabel = computed(() => {
|
||||
const labelProp = props.treeProps?.label || 'label';
|
||||
return String(props.node[labelProp] || '');
|
||||
});
|
||||
|
||||
const children = computed(() => {
|
||||
const childrenProp = props.treeProps?.children || 'children';
|
||||
return props.node[childrenProp] || [];
|
||||
});
|
||||
|
||||
const hasChildren = computed(() => children.value && children.value.length > 0);
|
||||
|
||||
const expanded = computed(() => props.expandedKeys.has(nodeId.value));
|
||||
|
||||
// 判断复选框是否应该被禁用
|
||||
const isCheckboxDisabled = computed(() => {
|
||||
// 如果只允许选择叶子节点,且当前节点有子节点,则禁用复选框
|
||||
return props.onlyLeafSelectable && hasChildren.value;
|
||||
});
|
||||
|
||||
function getChildId(child: any): string {
|
||||
const idProp = props.treeProps?.id || 'id';
|
||||
return String(child[idProp] || '');
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
// 点击节点主体时切换选中状态(如果复选框未禁用)
|
||||
if (!isCheckboxDisabled.value) {
|
||||
emit('toggle-select', nodeId.value);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCheckboxClick() {
|
||||
// 复选框点击事件(如果未禁用)
|
||||
if (!isCheckboxDisabled.value) {
|
||||
emit('toggle-select', nodeId.value);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDblClick() {
|
||||
// 双击时触发(如果复选框未禁用)
|
||||
if (!isCheckboxDisabled.value) {
|
||||
emit('dblclick', nodeId.value);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tree-node {
|
||||
user-select: none;
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
color: #606266;
|
||||
|
||||
img {
|
||||
transform: rotate(-90deg);
|
||||
&.expanded {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.expand-icon-placeholder {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin: 0 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
.node-label {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
.node-label {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-children {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,4 +7,6 @@ export { default as MenuSidebar } from './MenuSidebar.vue';
|
||||
export { default as TopNavigation } from './TopNavigation.vue';
|
||||
export { default as UserDropdown } from './UserDropdown.vue';
|
||||
export { default as Search } from './Search.vue';
|
||||
export { default as CenterHead } from './CenterHead.vue';
|
||||
export { default as CenterHead } from './CenterHead.vue';
|
||||
export { default as GenericSelector } from './GenericSelector.vue';
|
||||
export { default as TreeNode } from './TreeNode.vue';
|
||||
301
schoolNewsWeb/src/components/base/selector-styles.scss
Normal file
301
schoolNewsWeb/src/components/base/selector-styles.scss
Normal file
@@ -0,0 +1,301 @@
|
||||
// 通用选择器样式
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 3000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
|
||||
&.large {
|
||||
width: 900px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.modal-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
|
||||
&:hover {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 12px 24px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.generic-selector {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.selector-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 4px;
|
||||
background: #fafafa;
|
||||
|
||||
.panel-header {
|
||||
padding: 12px 16px;
|
||||
background: #f0f0f0;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.panel-title {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.panel-count {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-search {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
background: white;
|
||||
|
||||
.search-input-small {
|
||||
width: 100%;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
border-color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: white;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-list,
|
||||
.tree-list {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.tree-notice {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-sublabel {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.selector-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
|
||||
.action-btn {
|
||||
padding: 8px 16px;
|
||||
background: white;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
transition: all 0.2s;
|
||||
min-width: 60px;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: #409EFF;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-more,
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.loading-spinner-small {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid #f3f3f3;
|
||||
border-top: 2px solid #409EFF;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 8px 20px;
|
||||
background-color: #409EFF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: #66b1ff;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
padding: 8px 20px;
|
||||
background-color: white;
|
||||
color: #606266;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #409EFF;
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,5 +8,4 @@ export * from './text';
|
||||
export * from './file';
|
||||
|
||||
// 导出 user 用户组件
|
||||
export * from './user';
|
||||
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
# UserSelect 用户选择组件
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持双栏选择器布局
|
||||
- ✅ 支持搜索功能(左右两侧独立搜索)
|
||||
- ✅ 三种操作方式:双击、勾选+按钮、全部按钮
|
||||
- ✅ **支持滚动分页加载**(性能优化)
|
||||
- ✅ 支持传入静态数据或API方法
|
||||
- ✅ 完全封装的样式和逻辑
|
||||
|
||||
## Props 配置
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| visible | Boolean | false | 控制弹窗显示/隐藏 |
|
||||
| mode | 'add' \| 'remove' | 'add' | 选择模式 |
|
||||
| title | String | '人员选择' | 弹窗标题 |
|
||||
| leftTitle | String | '可选人员' | 左侧面板标题 |
|
||||
| rightTitle | String | '已选人员' | 右侧面板标题 |
|
||||
| availableUsers | UserVO[] | [] | 左区域静态数据 |
|
||||
| initialTargetUsers | UserVO[] | [] | 初始已选人员 |
|
||||
| loading | Boolean | false | 确认按钮加载状态 |
|
||||
| **usePagination** | Boolean | false | **是否启用分页加载** |
|
||||
| **fetchApi** | Function | undefined | **分页加载API方法** |
|
||||
| **filterParams** | Object | {} | **API过滤参数** |
|
||||
| **pageSize** | Number | 20 | **每页数量** |
|
||||
|
||||
## Events 事件
|
||||
|
||||
| 事件名 | 参数 | 说明 |
|
||||
|--------|------|------|
|
||||
| update:visible | (value: boolean) | 更新弹窗显示状态 |
|
||||
| confirm | (users: UserVO[]) | 确认提交,返回选中的用户列表 |
|
||||
| cancel | - | 取消操作 |
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 方式一:传入静态数据(适合数据量少的场景)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<UserSelect
|
||||
v-model:visible="showSelector"
|
||||
mode="add"
|
||||
title="选择用户"
|
||||
:available-users="allUsers"
|
||||
:initial-target-users="[]"
|
||||
@confirm="handleConfirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { UserSelect } from '@/components';
|
||||
import type { UserVO } from '@/types';
|
||||
|
||||
const showSelector = ref(false);
|
||||
const allUsers = ref<UserVO[]>([]);
|
||||
|
||||
function handleConfirm(selectedUsers: UserVO[]) {
|
||||
console.log('选中的用户:', selectedUsers);
|
||||
showSelector.value = false;
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 方式二:使用分页加载(推荐,适合数据量大的场景)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<UserSelect
|
||||
v-model:visible="showSelector"
|
||||
mode="add"
|
||||
title="选择用户"
|
||||
:use-pagination="true"
|
||||
:fetch-api="userApi.getUserPage"
|
||||
:filter-params="filterParams"
|
||||
:page-size="20"
|
||||
:loading="saving"
|
||||
@confirm="handleConfirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { UserSelect } from '@/components';
|
||||
import { userApi } from '@/apis/system';
|
||||
import type { UserVO } from '@/types';
|
||||
|
||||
const showSelector = ref(false);
|
||||
const saving = ref(false);
|
||||
const filterParams = ref({
|
||||
// 可以添加额外的过滤条件
|
||||
status: 0
|
||||
});
|
||||
|
||||
async function handleConfirm(selectedUsers: UserVO[]) {
|
||||
saving.value = true;
|
||||
try {
|
||||
// 处理业务逻辑
|
||||
for (const user of selectedUsers) {
|
||||
await someApi.addUser(user.id);
|
||||
}
|
||||
showSelector.value = false;
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 方式三:混合模式(添加模式用分页,删除模式用静态)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<UserSelect
|
||||
v-model:visible="showSelector"
|
||||
:mode="selectorMode"
|
||||
:title="selectorMode === 'add' ? '添加人员' : '删除人员'"
|
||||
:available-users="selectorMode === 'remove' ? currentUsers : []"
|
||||
:use-pagination="selectorMode === 'add'"
|
||||
:fetch-api="selectorMode === 'add' ? userApi.getUserPage : undefined"
|
||||
:filter-params="filterParams"
|
||||
@confirm="handleConfirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { UserSelect } from '@/components';
|
||||
import { userApi } from '@/apis/system';
|
||||
import type { UserVO } from '@/types';
|
||||
|
||||
const showSelector = ref(false);
|
||||
const selectorMode = ref<'add' | 'remove'>('add');
|
||||
const currentUsers = ref<UserVO[]>([]);
|
||||
const filterParams = ref({});
|
||||
|
||||
function handleConfirm(selectedUsers: UserVO[]) {
|
||||
if (selectorMode.value === 'add') {
|
||||
// 添加用户逻辑
|
||||
} else {
|
||||
// 删除用户逻辑
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## API 方法要求
|
||||
|
||||
使用 `usePagination` 时,`fetchApi` 方法需要符合以下签名:
|
||||
|
||||
```typescript
|
||||
async function fetchApi(
|
||||
pageParam: PageParam,
|
||||
filter?: any
|
||||
): Promise<ResultDomain<UserVO>>
|
||||
```
|
||||
|
||||
### PageParam 类型
|
||||
|
||||
```typescript
|
||||
interface PageParam {
|
||||
page: number; // 当前页码(从1开始)
|
||||
size: number; // 每页数量
|
||||
}
|
||||
```
|
||||
|
||||
### ResultDomain 类型
|
||||
|
||||
```typescript
|
||||
interface ResultDomain<T> {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
dataList?: T[];
|
||||
pageParam?: {
|
||||
totalElements: number; // 总记录数
|
||||
// ... 其他分页信息
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 特性说明
|
||||
|
||||
### 滚动分页加载
|
||||
|
||||
- 当启用 `usePagination` 时,左侧面板支持滚动加载更多数据
|
||||
- 滚动到距离底部 50px 时自动加载下一页
|
||||
- 显示加载状态和"已加载全部数据"提示
|
||||
- 搜索时自动重置分页并重新加载
|
||||
|
||||
### 搜索功能
|
||||
|
||||
- **分页模式**:搜索关键词会传递给 API,在服务端进行过滤
|
||||
- **静态模式**:搜索在前端进行过滤
|
||||
|
||||
### 数据过滤
|
||||
|
||||
组件会自动过滤掉右侧已选择的用户,避免重复选择。
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
1. **数据量 < 100**:使用静态数据模式
|
||||
2. **数据量 > 100**:使用分页加载模式
|
||||
3. **数据量 > 1000**:使用分页加载 + 服务端搜索
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 使用分页模式时,`availableUsers` 会被忽略
|
||||
2. 删除模式下通常使用静态数据(当前已分配的用户)
|
||||
3. `filterParams` 支持传入额外的过滤条件,会合并到 API 请求中
|
||||
|
||||
@@ -1,781 +0,0 @@
|
||||
<template>
|
||||
<div v-if="visible" class="modal-overlay" @click.self="handleCancel">
|
||||
<div class="modal-content large">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">{{ title }}</h3>
|
||||
<button class="modal-close" @click="handleCancel">✕</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="user-selector">
|
||||
<!-- 左侧:可选人员 -->
|
||||
<div class="selector-panel">
|
||||
<div class="panel-header">
|
||||
<h4 class="panel-title">{{ leftTitle }}</h4>
|
||||
<span class="panel-count">
|
||||
{{ usePagination && totalElements > 0 ? `${availableList.length}/${totalElements}` : `${availableList.length}` }} 人
|
||||
</span>
|
||||
</div>
|
||||
<div class="panel-search">
|
||||
<input
|
||||
v-model="searchAvailable"
|
||||
type="text"
|
||||
placeholder="搜索人员..."
|
||||
class="search-input-small"
|
||||
/>
|
||||
</div>
|
||||
<div class="panel-body left-panel">
|
||||
<div class="user-list">
|
||||
<div
|
||||
v-for="user in filteredAvailable"
|
||||
:key="user.id"
|
||||
class="user-item"
|
||||
:class="{ selected: user.id && selectedAvailable.includes(user.id) }"
|
||||
@click="user.id && toggleAvailable(user.id)"
|
||||
@dblclick="user.id && moveToTarget(user.id)"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="!!(user.id && selectedAvailable.includes(user.id))"
|
||||
@click.stop="user.id && toggleAvailable(user.id)"
|
||||
/>
|
||||
<span class="user-name">{{ user.username }}</span>
|
||||
<span class="user-dept" v-if="user.deptName">({{ user.deptName }})</span>
|
||||
</div>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<div v-if="usePagination && loadingMore" class="loading-more">
|
||||
<div class="loading-spinner-small"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-if="usePagination && !hasMore && availableList.length > 0" class="no-more">
|
||||
已加载全部数据
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间:操作按钮 -->
|
||||
<div class="selector-actions">
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="moveSelectedToTarget"
|
||||
:disabled="selectedAvailable.length === 0"
|
||||
:title="mode === 'add' ? '添加选中' : '删除选中'"
|
||||
>
|
||||
<span class="arrow">→</span>
|
||||
<span class="btn-text">{{ mode === 'add' ? '添加' : '删除' }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="moveAllToTarget"
|
||||
:disabled="availableList.length === 0"
|
||||
:title="mode === 'add' ? '全部添加' : '全部删除'"
|
||||
>
|
||||
<span class="arrow">⇒</span>
|
||||
<span class="btn-text">全部</span>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="moveBackSelected"
|
||||
:disabled="selectedTarget.length === 0"
|
||||
title="移回选中"
|
||||
>
|
||||
<span class="arrow">←</span>
|
||||
<span class="btn-text">移回</span>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="moveBackAll"
|
||||
:disabled="targetList.length === 0"
|
||||
title="全部移回"
|
||||
>
|
||||
<span class="arrow">⇐</span>
|
||||
<span class="btn-text">全部</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:目标人员 -->
|
||||
<div class="selector-panel">
|
||||
<div class="panel-header">
|
||||
<h4 class="panel-title">{{ rightTitle }}</h4>
|
||||
<span class="panel-count">{{ targetList.length }} 人</span>
|
||||
</div>
|
||||
<div class="panel-search">
|
||||
<input
|
||||
v-model="searchTarget"
|
||||
type="text"
|
||||
placeholder="搜索人员..."
|
||||
class="search-input-small"
|
||||
/>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="user-list">
|
||||
<div
|
||||
v-for="user in filteredTarget"
|
||||
:key="user.id"
|
||||
class="user-item"
|
||||
:class="{ selected: user.id && selectedTarget.includes(user.id) }"
|
||||
@click="user.id && toggleTarget(user.id)"
|
||||
@dblclick="user.id && moveBackToAvailable(user.id)"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="!!(user.id && selectedTarget.includes(user.id))"
|
||||
@click.stop="user.id && toggleTarget(user.id)"
|
||||
/>
|
||||
<span class="user-name">{{ user.username }}</span>
|
||||
<span class="user-dept" v-if="user.deptName">({{ user.deptName }})</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn-primary" @click="handleConfirm" :disabled="loading">
|
||||
{{ loading ? '处理中...' : '确定' }}
|
||||
</button>
|
||||
<button class="btn-default" @click="handleCancel">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, nextTick } from 'vue';
|
||||
import type { UserVO, ResultDomain, PageParam } from '@/types';
|
||||
|
||||
interface Props {
|
||||
visible?: boolean;
|
||||
mode?: 'add' | 'remove';
|
||||
title?: string;
|
||||
leftTitle?: string;
|
||||
rightTitle?: string;
|
||||
availableUsers?: UserVO[];
|
||||
initialTargetUsers?: UserVO[];
|
||||
loading?: boolean;
|
||||
// 分页加载API方法
|
||||
fetchApi?: (pageParam: PageParam, filter?: any) => Promise<ResultDomain<UserVO>>;
|
||||
// 过滤参数
|
||||
filterParams?: any;
|
||||
// 每页数量
|
||||
pageSize?: number;
|
||||
// 是否使用分页加载
|
||||
usePagination?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
mode: 'add',
|
||||
title: '人员选择',
|
||||
leftTitle: '可选人员',
|
||||
rightTitle: '已选人员',
|
||||
availableUsers: () => [],
|
||||
initialTargetUsers: () => [],
|
||||
loading: false,
|
||||
pageSize: 20,
|
||||
usePagination: false
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean];
|
||||
confirm: [users: UserVO[]];
|
||||
cancel: [];
|
||||
}>();
|
||||
|
||||
// 数据列表
|
||||
const availableList = ref<UserVO[]>([]);
|
||||
const targetList = ref<UserVO[]>([]);
|
||||
|
||||
// 选中状态
|
||||
const selectedAvailable = ref<string[]>([]);
|
||||
const selectedTarget = ref<string[]>([]);
|
||||
|
||||
// 搜索关键词
|
||||
const searchAvailable = ref('');
|
||||
const searchTarget = ref('');
|
||||
|
||||
// 分页相关
|
||||
const currentPage = ref(1);
|
||||
const totalElements = ref(0);
|
||||
const hasMore = ref(true);
|
||||
const loadingMore = ref(false);
|
||||
const availablePanelRef = ref<HTMLElement | null>(null);
|
||||
|
||||
// 监听props变化,初始化数据
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
initializeData();
|
||||
} else {
|
||||
resetData();
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 监听搜索关键词变化
|
||||
watch(searchAvailable, () => {
|
||||
if (props.usePagination && props.fetchApi) {
|
||||
resetPaginationAndLoad();
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化数据
|
||||
async function initializeData() {
|
||||
if (props.usePagination && props.fetchApi) {
|
||||
// 使用分页加载
|
||||
currentPage.value = 1;
|
||||
hasMore.value = true;
|
||||
availableList.value = [];
|
||||
await loadAvailableUsers();
|
||||
} else {
|
||||
// 使用传入的数据
|
||||
availableList.value = [...props.availableUsers];
|
||||
}
|
||||
|
||||
targetList.value = [...props.initialTargetUsers];
|
||||
selectedAvailable.value = [];
|
||||
selectedTarget.value = [];
|
||||
searchAvailable.value = '';
|
||||
searchTarget.value = '';
|
||||
|
||||
// 绑定滚动事件
|
||||
await nextTick();
|
||||
bindScrollEvent();
|
||||
}
|
||||
|
||||
// 重置数据
|
||||
function resetData() {
|
||||
availableList.value = [];
|
||||
targetList.value = [];
|
||||
selectedAvailable.value = [];
|
||||
selectedTarget.value = [];
|
||||
searchAvailable.value = '';
|
||||
searchTarget.value = '';
|
||||
currentPage.value = 1;
|
||||
totalElements.value = 0;
|
||||
hasMore.value = true;
|
||||
loadingMore.value = false;
|
||||
|
||||
// 解绑滚动事件
|
||||
unbindScrollEvent();
|
||||
}
|
||||
|
||||
// 加载可选用户数据
|
||||
async function loadAvailableUsers() {
|
||||
if (!props.fetchApi || loadingMore.value || !hasMore.value) return;
|
||||
|
||||
loadingMore.value = true;
|
||||
|
||||
try {
|
||||
const pageParam: PageParam = {
|
||||
page: currentPage.value,
|
||||
size: props.pageSize
|
||||
};
|
||||
|
||||
// 构建过滤参数
|
||||
const filter = {
|
||||
...props.filterParams,
|
||||
...(searchAvailable.value ? { username: searchAvailable.value } : {})
|
||||
};
|
||||
|
||||
const res = await props.fetchApi(pageParam, filter);
|
||||
|
||||
if (res.success) {
|
||||
const newUsers = res.dataList || [];
|
||||
|
||||
// 过滤掉已在右侧的用户
|
||||
const targetUserIds = targetList.value.map(u => u.id);
|
||||
const filteredUsers = newUsers.filter(u => !targetUserIds.includes(u.id));
|
||||
|
||||
if (currentPage.value === 1) {
|
||||
availableList.value = filteredUsers;
|
||||
} else {
|
||||
availableList.value.push(...filteredUsers);
|
||||
}
|
||||
|
||||
totalElements.value = res.pageParam?.totalElements || 0;
|
||||
const totalPages = Math.ceil(totalElements.value / props.pageSize);
|
||||
hasMore.value = currentPage.value < totalPages;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载用户数据失败:', error);
|
||||
} finally {
|
||||
loadingMore.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置分页并重新加载
|
||||
async function resetPaginationAndLoad() {
|
||||
currentPage.value = 1;
|
||||
hasMore.value = true;
|
||||
availableList.value = [];
|
||||
await loadAvailableUsers();
|
||||
}
|
||||
|
||||
// 绑定滚动事件
|
||||
function bindScrollEvent() {
|
||||
const panelBody = document.querySelector('.panel-body.left-panel');
|
||||
if (panelBody) {
|
||||
availablePanelRef.value = panelBody as HTMLElement;
|
||||
panelBody.addEventListener('scroll', handleScroll);
|
||||
}
|
||||
}
|
||||
|
||||
// 解绑滚动事件
|
||||
function unbindScrollEvent() {
|
||||
if (availablePanelRef.value) {
|
||||
availablePanelRef.value.removeEventListener('scroll', handleScroll);
|
||||
availablePanelRef.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理滚动事件
|
||||
function handleScroll(event: Event) {
|
||||
if (!props.usePagination || !props.fetchApi) return;
|
||||
|
||||
const target = event.target as HTMLElement;
|
||||
const scrollTop = target.scrollTop;
|
||||
const scrollHeight = target.scrollHeight;
|
||||
const clientHeight = target.clientHeight;
|
||||
|
||||
// 距离底部50px时加载更多
|
||||
if (scrollHeight - scrollTop - clientHeight < 50 && hasMore.value && !loadingMore.value) {
|
||||
currentPage.value++;
|
||||
loadAvailableUsers();
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤可选人员
|
||||
const filteredAvailable = computed(() => {
|
||||
// 如果使用分页加载,搜索在API层面处理,不需要前端过滤
|
||||
if (props.usePagination) {
|
||||
return availableList.value;
|
||||
}
|
||||
|
||||
// 前端过滤
|
||||
if (!searchAvailable.value) {
|
||||
return availableList.value;
|
||||
}
|
||||
const keyword = searchAvailable.value.toLowerCase();
|
||||
return availableList.value.filter(user =>
|
||||
user.username?.toLowerCase().includes(keyword) ||
|
||||
user.deptName?.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
// 过滤目标人员
|
||||
const filteredTarget = computed(() => {
|
||||
if (!searchTarget.value) {
|
||||
return targetList.value;
|
||||
}
|
||||
const keyword = searchTarget.value.toLowerCase();
|
||||
return targetList.value.filter(user =>
|
||||
user.username?.toLowerCase().includes(keyword) ||
|
||||
user.deptName?.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
// 切换可选用户的选中状态
|
||||
function toggleAvailable(userId: string) {
|
||||
const index = selectedAvailable.value.indexOf(userId);
|
||||
if (index > -1) {
|
||||
selectedAvailable.value.splice(index, 1);
|
||||
} else {
|
||||
selectedAvailable.value.push(userId);
|
||||
}
|
||||
}
|
||||
|
||||
// 切换目标用户的选中状态
|
||||
function toggleTarget(userId: string) {
|
||||
const index = selectedTarget.value.indexOf(userId);
|
||||
if (index > -1) {
|
||||
selectedTarget.value.splice(index, 1);
|
||||
} else {
|
||||
selectedTarget.value.push(userId);
|
||||
}
|
||||
}
|
||||
|
||||
// 移动选中用户到目标区域
|
||||
function moveSelectedToTarget() {
|
||||
const usersToMove = availableList.value.filter(user =>
|
||||
selectedAvailable.value.includes(user.id!)
|
||||
);
|
||||
|
||||
targetList.value.push(...usersToMove);
|
||||
availableList.value = availableList.value.filter(user =>
|
||||
!selectedAvailable.value.includes(user.id!)
|
||||
);
|
||||
|
||||
selectedAvailable.value = [];
|
||||
}
|
||||
|
||||
// 移动单个用户到目标区域(双击)
|
||||
function moveToTarget(userId: string) {
|
||||
const user = availableList.value.find(u => u.id === userId);
|
||||
if (user) {
|
||||
targetList.value.push(user);
|
||||
availableList.value = availableList.value.filter(u => u.id !== userId);
|
||||
}
|
||||
}
|
||||
|
||||
// 移动所有用户到目标区域
|
||||
function moveAllToTarget() {
|
||||
targetList.value.push(...availableList.value);
|
||||
availableList.value = [];
|
||||
selectedAvailable.value = [];
|
||||
}
|
||||
|
||||
// 移回选中用户到可选区域
|
||||
function moveBackSelected() {
|
||||
const usersToMoveBack = targetList.value.filter(user =>
|
||||
selectedTarget.value.includes(user.id!)
|
||||
);
|
||||
|
||||
availableList.value.push(...usersToMoveBack);
|
||||
targetList.value = targetList.value.filter(user =>
|
||||
!selectedTarget.value.includes(user.id!)
|
||||
);
|
||||
|
||||
selectedTarget.value = [];
|
||||
}
|
||||
|
||||
// 移回单个用户到可选区域(双击)
|
||||
function moveBackToAvailable(userId: string) {
|
||||
const user = targetList.value.find(u => u.id === userId);
|
||||
if (user) {
|
||||
availableList.value.push(user);
|
||||
targetList.value = targetList.value.filter(u => u.id !== userId);
|
||||
}
|
||||
}
|
||||
|
||||
// 移回所有用户到可选区域
|
||||
function moveBackAll() {
|
||||
availableList.value.push(...targetList.value);
|
||||
targetList.value = [];
|
||||
selectedTarget.value = [];
|
||||
}
|
||||
|
||||
// 确认
|
||||
function handleConfirm() {
|
||||
emit('confirm', targetList.value);
|
||||
}
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 弹窗遮罩
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: #f5f7fa;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
color: #909399;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
// 人员选择器样式
|
||||
.user-selector {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.selector-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: #f5f7fa;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panel-count {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
background: #fff;
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.panel-search {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.search-input-small {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.user-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: #ecf5ff;
|
||||
border: 1px solid #b3d8ff;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.user-dept {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.loading-spinner-small {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid #f0f0f0;
|
||||
border-top-color: #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
color: #c0c4cc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.selector-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
min-width: 80px;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #409eff;
|
||||
border-color: #409eff;
|
||||
color: #fff;
|
||||
|
||||
.arrow {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-default {
|
||||
padding: 10px 24px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border: none;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #66b1ff;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
border: 1px solid #dcdfe6;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* @description 用户相关组件
|
||||
* @author yslg
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
|
||||
export { default as UserSelect } from './UserSelect.vue';
|
||||
|
||||
@@ -51,6 +51,6 @@ export const APP_CONFIG = {
|
||||
refreshThreshold: 5 * 60 * 1000 // 提前5分钟刷新
|
||||
}
|
||||
};
|
||||
export const PUBLIC_IMG_PATH = '/schoolNewsWeb/img';
|
||||
export const PUBLIC_IMG_PATH = 'http://localhost:8080/schoolNewsWeb/img';
|
||||
export default APP_CONFIG;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { BaseDTO } from '../base';
|
||||
import { SysRole } from '../role';
|
||||
import { SysUserDeptRole } from '../user';
|
||||
|
||||
/**
|
||||
* 系统部门
|
||||
@@ -15,6 +16,8 @@ export interface SysDept extends BaseDTO {
|
||||
deptID?: string;
|
||||
/** 父部门ID */
|
||||
parentID?: string;
|
||||
/** 部门路径,格式:/root_department/dept_001/ */
|
||||
deptPath?: string;
|
||||
/** 部门名称 */
|
||||
name?: string;
|
||||
/** 部门描述 */
|
||||
@@ -30,13 +33,35 @@ export interface SysDept extends BaseDTO {
|
||||
/**
|
||||
* 部门角色VO
|
||||
*/
|
||||
export interface DeptRoleVO {
|
||||
/** 部门信息 */
|
||||
dept?: SysDept;
|
||||
/** 角色信息 */
|
||||
role?: SysRole;
|
||||
export interface UserDeptRoleVO {
|
||||
|
||||
depts?: SysDept[];
|
||||
roles?: SysRole[];
|
||||
userDeptRoles?: SysUserDeptRole[];
|
||||
|
||||
// 扁平化字段,用于权限查询优化
|
||||
/** 用户ID */
|
||||
userID?: string;
|
||||
/** 部门ID */
|
||||
deptID?: string;
|
||||
/** 部门名称 */
|
||||
deptName?: string;
|
||||
/** 部门描述 */
|
||||
deptDescription?: string;
|
||||
/** 父部门ID */
|
||||
parentID?: string;
|
||||
/** 父部门名称 */
|
||||
parentName?: string;
|
||||
/** 父部门描述 */
|
||||
parentDescription?: string;
|
||||
/** 角色ID */
|
||||
roleID?: string;
|
||||
/** 角色名称 */
|
||||
roleName?: string;
|
||||
/** 角色描述 */
|
||||
roleDescription?: string;
|
||||
/** 部门路径,用于快速权限继承判断 */
|
||||
deptPath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,5 +73,19 @@ export interface SysDeptRole extends BaseDTO {
|
||||
deptID?: string;
|
||||
/** 角色ID */
|
||||
roleID?: string;
|
||||
|
||||
// 扁平化字段(用于前端展示,从关联查询中获取)
|
||||
/** 部门名称 */
|
||||
deptName?: string;
|
||||
/** 部门描述 */
|
||||
deptDescription?: string;
|
||||
/** 父部门ID */
|
||||
parentID?: string;
|
||||
/** 部门路径 */
|
||||
deptPath?: string;
|
||||
/** 角色名称 */
|
||||
roleName?: string;
|
||||
/** 角色描述 */
|
||||
roleDescription?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -93,17 +93,4 @@ export interface SysUserDeptRole extends BaseDTO {
|
||||
deptID?: string;
|
||||
/** 角色ID */
|
||||
roleID?: string;
|
||||
}
|
||||
|
||||
export interface UserDeptRoleVO extends BaseDTO {
|
||||
/** 单个用户信息 */
|
||||
user?: SysUser;
|
||||
/** 用户列表 */
|
||||
users?: SysUser[];
|
||||
/** 部门列表 */
|
||||
depts?: SysDept[];
|
||||
/** 角色列表 */
|
||||
roles?: SysRole[];
|
||||
/** 用户部门角色关联列表 */
|
||||
userDeptRoles?: SysUserDeptRole[];
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
<el-tabs v-model="activeTab">
|
||||
|
||||
<el-tab-pane label="学习记录" name="task-records">
|
||||
<StudyRecords />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
@@ -17,7 +16,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ElTabs, ElTabPane } from 'element-plus';
|
||||
import StudyRecords from './components/StudyRecords.vue';
|
||||
import { AdminLayout } from '@/views/admin';
|
||||
defineOptions({
|
||||
name: 'StudyManagementView'
|
||||
|
||||
@@ -112,60 +112,19 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 绑定角色对话框 -->
|
||||
<el-dialog v-model="bindRoleDialogVisible" title="绑定角色" width="800px" @close="resetBindList">
|
||||
<div class="role-binding-container">
|
||||
<!-- 部门信息显示 -->
|
||||
<div class="dept-info" v-if="currentDept">
|
||||
<h4>部门信息:{{ currentDept.name }}</h4>
|
||||
<p>部门ID:{{ currentDept.deptID }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 角色绑定状态表格 -->
|
||||
<el-table :data="roleList" style="width: 100%" border stripe>
|
||||
<el-table-column width="80" label="绑定状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="isRoleSelected(row.roleID) ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ isRoleSelected(row.roleID) ? '已绑定' : '未绑定' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="角色名称" min-width="150" />
|
||||
<el-table-column prop="roleID" label="角色ID" min-width="120" />
|
||||
<el-table-column prop="description" label="角色描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
:type="isRoleSelected(row.roleID) ? 'danger' : 'primary'"
|
||||
size="small"
|
||||
@click="toggleRoleSelection(row)"
|
||||
>
|
||||
{{ isRoleSelected(row.roleID) ? '解绑' : '绑定' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="binding-stats">
|
||||
<el-alert
|
||||
:title="`已绑定 ${selectedRoles.length} 个角色,未绑定 ${roleList.length - selectedRoles.length} 个角色`"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="bindRoleDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveRoleBinding" :loading="submitting">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<GenericSelector
|
||||
v-model:visible="bindRoleDialogVisible"
|
||||
:title="`绑定角色 - ${currentDept?.name || ''}`"
|
||||
left-title="可选角色"
|
||||
right-title="已选角色"
|
||||
:fetch-available-api="fetchAllRoles"
|
||||
:fetch-selected-api="fetchDeptRoles"
|
||||
:item-config="{ id: 'roleID', label: 'name', sublabel: 'description' }"
|
||||
unit-name="个"
|
||||
search-placeholder="搜索角色名称或描述..."
|
||||
@confirm="handleRoleConfirm"
|
||||
@cancel="resetBindList"
|
||||
/>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
@@ -175,6 +134,7 @@ import { deptApi } from '@/apis/system/dept';
|
||||
import { roleApi } from '@/apis/system/role';
|
||||
import { SysDept, SysRole } from '@/types';
|
||||
import { AdminLayout } from '@/views/admin';
|
||||
import { GenericSelector } from '@/components/base';
|
||||
|
||||
defineOptions({
|
||||
name: 'DeptManageView'
|
||||
@@ -190,12 +150,7 @@ const submitting = ref(false);
|
||||
const treeRef = ref();
|
||||
|
||||
// 角色绑定相关数据
|
||||
const roleList = ref<SysRole[]>([]);
|
||||
const selectedRoles = ref<string[]>([]);
|
||||
const currentDept = ref<SysDept | null>(null);
|
||||
const bindList = ref<{ roles: SysRole[] }>({
|
||||
roles: []
|
||||
});
|
||||
|
||||
// 对话框状态
|
||||
const dialogVisible = ref(false);
|
||||
@@ -452,59 +407,39 @@ function resetForm() {
|
||||
});
|
||||
}
|
||||
|
||||
// 获取所有可选角色的接口
|
||||
async function fetchAllRoles() {
|
||||
return await roleApi.getAllRoles();
|
||||
}
|
||||
|
||||
// 获取部门已绑定角色的接口
|
||||
async function fetchDeptRoles() {
|
||||
if (!currentDept.value) {
|
||||
return {
|
||||
success: true,
|
||||
dataList: [],
|
||||
code: 200,
|
||||
message: '',
|
||||
login: true,
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
return await deptApi.getDeptByRole(currentDept.value);
|
||||
}
|
||||
|
||||
// 查看绑定角色
|
||||
async function handleBindRole(row: SysDept) {
|
||||
currentDept.value = row;
|
||||
|
||||
try {
|
||||
// 获取所有角色
|
||||
const roleResult = await roleApi.getAllRoles();
|
||||
roleList.value = roleResult.dataList || [];
|
||||
|
||||
// 获取已绑定的角色
|
||||
const bindingResult = await deptApi.getDeptByRole(row);
|
||||
bindList.value.roles = bindingResult.dataList || [];
|
||||
|
||||
// 设置已选中的角色
|
||||
selectedRoles.value = bindList.value.roles.map(role => role.roleID).filter((id): id is string => !!id);
|
||||
|
||||
console.log('已绑定的角色:', bindList.value.roles);
|
||||
console.log('所有角色:', roleList.value);
|
||||
bindRoleDialogVisible.value = true;
|
||||
} catch (error) {
|
||||
console.error('获取角色绑定信息失败:', error);
|
||||
ElMessage.error('获取角色绑定信息失败');
|
||||
}
|
||||
bindRoleDialogVisible.value = true;
|
||||
}
|
||||
|
||||
// 重置绑定列表
|
||||
function resetBindList() {
|
||||
bindList.value = {
|
||||
roles: []
|
||||
};
|
||||
selectedRoles.value = [];
|
||||
currentDept.value = null;
|
||||
}
|
||||
|
||||
// 检查角色是否已选中
|
||||
function isRoleSelected(roleID: string | undefined): boolean {
|
||||
return roleID ? selectedRoles.value.includes(roleID) : false;
|
||||
}
|
||||
|
||||
// 切换角色选择状态
|
||||
function toggleRoleSelection(role: SysRole) {
|
||||
if (!role.roleID) return;
|
||||
|
||||
const index = selectedRoles.value.indexOf(role.roleID);
|
||||
if (index > -1) {
|
||||
selectedRoles.value.splice(index, 1);
|
||||
} else {
|
||||
selectedRoles.value.push(role.roleID);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存角色绑定
|
||||
async function saveRoleBinding() {
|
||||
// 角色选择确认 - 在confirm时提交请求
|
||||
async function handleRoleConfirm(items: SysRole[]) {
|
||||
if (!currentDept.value || !currentDept.value.deptID) {
|
||||
ElMessage.error('部门信息不完整');
|
||||
return;
|
||||
@@ -513,21 +448,22 @@ async function saveRoleBinding() {
|
||||
try {
|
||||
submitting.value = true;
|
||||
|
||||
// 获取当前已绑定的角色ID
|
||||
const currentBoundRoles = (bindList.value.roles || []).map(role => role.roleID).filter((id): id is string => !!id);
|
||||
// 获取当前已绑定的角色
|
||||
const currentBoundResult = await deptApi.getDeptByRole(currentDept.value);
|
||||
const currentBoundIds = (currentBoundResult.dataList || []).map(r => r.roleID).filter((id): id is string => !!id);
|
||||
|
||||
// 新选择的角色ID
|
||||
const newSelectedIds = items.map(r => r.roleID).filter((id): id is string => !!id);
|
||||
|
||||
// 找出需要绑定的角色(新增的)
|
||||
const rolesToBind = selectedRoles.value.filter(roleID => !currentBoundRoles.includes(roleID));
|
||||
const rolesToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
|
||||
|
||||
// 找出需要解绑的角色(移除的)
|
||||
const rolesToUnbind = currentBoundRoles.filter(roleID => !selectedRoles.value.includes(roleID));
|
||||
const rolesToUnbind = currentBoundIds.filter(id => !newSelectedIds.includes(id));
|
||||
|
||||
// 构建需要绑定的角色对象数组
|
||||
if (rolesToBind.length > 0) {
|
||||
const rolesToBindObjects = rolesToBind.map(roleID => {
|
||||
const role = roleList.value.find(r => r.roleID === roleID);
|
||||
return role || { roleID: roleID };
|
||||
});
|
||||
const rolesToBindObjects = items.filter(r => r.roleID && rolesToBind.includes(r.roleID));
|
||||
|
||||
const bindDept = {
|
||||
dept: currentDept.value,
|
||||
@@ -539,10 +475,7 @@ async function saveRoleBinding() {
|
||||
|
||||
// 构建需要解绑的角色对象数组
|
||||
if (rolesToUnbind.length > 0) {
|
||||
const rolesToUnbindObjects = rolesToUnbind.map(roleID => {
|
||||
const role = roleList.value.find(r => r.roleID === roleID);
|
||||
return role || { roleID: roleID };
|
||||
});
|
||||
const rolesToUnbindObjects = (currentBoundResult.dataList || []).filter(r => r.roleID && rolesToUnbind.includes(r.roleID));
|
||||
|
||||
const unbindDept = {
|
||||
dept: currentDept.value,
|
||||
@@ -553,7 +486,6 @@ async function saveRoleBinding() {
|
||||
}
|
||||
|
||||
ElMessage.success('角色绑定保存成功');
|
||||
bindRoleDialogVisible.value = false;
|
||||
|
||||
// 刷新部门列表
|
||||
await loadDeptList();
|
||||
@@ -799,30 +731,4 @@ async function handleNodeDrop(draggingNode: any, dropNode: any, dropType: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 角色绑定容器样式
|
||||
.role-binding-container {
|
||||
.dept-info {
|
||||
background: #f5f7fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.binding-stats {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -30,9 +30,12 @@
|
||||
<template #default="{ data }">
|
||||
<div class="custom-tree-node">
|
||||
<div class="node-label">
|
||||
<el-icon v-if="data.icon" class="node-icon">
|
||||
<component :is="data.icon" />
|
||||
</el-icon>
|
||||
<img
|
||||
v-if="data.icon"
|
||||
:src="PUBLIC_IMG_PATH + '/' + data.icon"
|
||||
class="node-icon"
|
||||
:alt="data.name"
|
||||
/>
|
||||
<span class="node-name">{{ data.name }}</span>
|
||||
<el-tag
|
||||
:type="getMenuTypeTagType(data.type)"
|
||||
@@ -156,61 +159,19 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 绑定权限对话框 -->
|
||||
<el-dialog v-model="bindPermissionDialogVisible" title="绑定权限" width="800px" @close="resetBindList">
|
||||
<div class="permission-binding-container">
|
||||
<!-- 菜单信息显示 -->
|
||||
<div class="menu-info" v-if="currentMenu">
|
||||
<h4>菜单信息:{{ currentMenu.name }}</h4>
|
||||
<p>菜单ID:{{ currentMenu.menuID }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 权限绑定状态表格 -->
|
||||
<el-table :data="permissionList" style="width: 100%" border stripe>
|
||||
<el-table-column width="80" label="绑定状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="isPermissionSelected(row.permissionID) ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ isPermissionSelected(row.permissionID) ? '已绑定' : '未绑定' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="权限名称" min-width="150" />
|
||||
<el-table-column prop="permissionID" label="权限ID" min-width="120" />
|
||||
<el-table-column prop="code" label="权限编码" min-width="150" />
|
||||
<el-table-column prop="description" label="权限描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
:type="isPermissionSelected(row.permissionID) ? 'danger' : 'primary'"
|
||||
size="small"
|
||||
@click="togglePermissionSelection(row)"
|
||||
>
|
||||
{{ isPermissionSelected(row.permissionID) ? '解绑' : '绑定' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="binding-stats">
|
||||
<el-alert
|
||||
:title="`已绑定 ${selectedPermissions.length} 个权限,未绑定 ${permissionList.length - selectedPermissions.length} 个权限`"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="bindPermissionDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="savePermissionBinding" :loading="submitting">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<GenericSelector
|
||||
v-model:visible="bindPermissionDialogVisible"
|
||||
:title="`绑定权限 - ${currentMenu?.name || ''}`"
|
||||
left-title="可选权限"
|
||||
right-title="已选权限"
|
||||
:fetch-available-api="fetchAllPermissions"
|
||||
:fetch-selected-api="fetchMenuPermissions"
|
||||
:item-config="{ id: 'permissionID', label: 'name', sublabel: 'code' }"
|
||||
unit-name="个"
|
||||
search-placeholder="搜索权限名称或编码..."
|
||||
@confirm="handlePermissionConfirm"
|
||||
@cancel="resetBindList"
|
||||
/>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
@@ -220,6 +181,8 @@ import { menuApi } from '@/apis/system/menu';
|
||||
import { permissionApi } from '@/apis/system/permission';
|
||||
import { SysMenu, SysPermission } from '@/types';
|
||||
import { AdminLayout } from '@/views/admin';
|
||||
import { PUBLIC_IMG_PATH } from '@/config';
|
||||
import { GenericSelector } from '@/components/base';
|
||||
|
||||
defineOptions({
|
||||
name: 'MenuManageView'
|
||||
@@ -235,12 +198,7 @@ const submitting = ref(false);
|
||||
const treeRef = ref();
|
||||
|
||||
// 权限绑定相关数据
|
||||
const permissionList = ref<SysPermission[]>([]);
|
||||
const selectedPermissions = ref<string[]>([]);
|
||||
const currentMenu = ref<SysMenu | null>(null);
|
||||
const bindList = ref<{ permissions: SysPermission[] }>({
|
||||
permissions: []
|
||||
});
|
||||
|
||||
// 对话框状态
|
||||
const dialogVisible = ref(false);
|
||||
@@ -533,65 +491,38 @@ function resetForm() {
|
||||
});
|
||||
}
|
||||
|
||||
// 获取所有可选权限的接口
|
||||
async function fetchAllPermissions() {
|
||||
const permission: SysPermission = {
|
||||
permissionID: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
description: undefined,
|
||||
};
|
||||
return await permissionApi.getPermissionList(permission);
|
||||
}
|
||||
|
||||
// 获取菜单已绑定权限的接口
|
||||
async function fetchMenuPermissions() {
|
||||
if (!currentMenu.value?.menuID) {
|
||||
return { success: true, dataList: [] };
|
||||
}
|
||||
return await menuApi.getMenuPermission(currentMenu.value.menuID);
|
||||
}
|
||||
|
||||
// 查看绑定权限
|
||||
async function handleBindPermission(row: SysMenu) {
|
||||
currentMenu.value = row;
|
||||
|
||||
try {
|
||||
// 获取所有权限
|
||||
let permission:SysPermission = {
|
||||
permissionID: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
description: undefined,
|
||||
};
|
||||
const permissionResult = await permissionApi.getPermissionList(permission);
|
||||
permissionList.value = permissionResult.dataList || [];
|
||||
|
||||
// 获取已绑定的权限
|
||||
const bindingResult = await menuApi.getMenuPermission(row.menuID!);
|
||||
bindList.value.permissions = bindingResult.dataList || [];
|
||||
|
||||
// 设置已选中的权限
|
||||
selectedPermissions.value = bindList.value.permissions.map(permission => permission.permissionID).filter((id): id is string => !!id);
|
||||
|
||||
console.log('已绑定的权限:', bindList.value.permissions);
|
||||
console.log('所有权限:', permissionList.value);
|
||||
bindPermissionDialogVisible.value = true;
|
||||
} catch (error) {
|
||||
console.error('获取权限绑定信息失败:', error);
|
||||
ElMessage.error('获取权限绑定信息失败');
|
||||
}
|
||||
bindPermissionDialogVisible.value = true;
|
||||
}
|
||||
|
||||
// 重置绑定列表
|
||||
function resetBindList() {
|
||||
bindList.value = {
|
||||
permissions: []
|
||||
};
|
||||
selectedPermissions.value = [];
|
||||
currentMenu.value = null;
|
||||
}
|
||||
|
||||
// 检查权限是否已选中
|
||||
function isPermissionSelected(permissionID: string | undefined): boolean {
|
||||
return permissionID ? selectedPermissions.value.includes(permissionID) : false;
|
||||
}
|
||||
|
||||
// 切换权限选择状态
|
||||
function togglePermissionSelection(permission: SysPermission) {
|
||||
if (!permission.permissionID) return;
|
||||
|
||||
const index = selectedPermissions.value.indexOf(permission.permissionID);
|
||||
if (index > -1) {
|
||||
selectedPermissions.value.splice(index, 1);
|
||||
} else {
|
||||
selectedPermissions.value.push(permission.permissionID);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存权限绑定
|
||||
async function savePermissionBinding() {
|
||||
// 权限选择确认 - 在confirm时提交请求
|
||||
async function handlePermissionConfirm(items: SysPermission[]) {
|
||||
if (!currentMenu.value || !currentMenu.value.menuID) {
|
||||
ElMessage.error('菜单信息不完整');
|
||||
return;
|
||||
@@ -600,21 +531,22 @@ async function savePermissionBinding() {
|
||||
try {
|
||||
submitting.value = true;
|
||||
|
||||
// 获取当前已绑定的权限ID
|
||||
const currentBoundPermissions = (bindList.value.permissions || []).map(permission => permission.permissionID).filter((id): id is string => !!id);
|
||||
// 获取当前已绑定的权限
|
||||
const currentBoundResult = await menuApi.getMenuPermission(currentMenu.value.menuID);
|
||||
const currentBoundIds = (currentBoundResult.dataList || []).map(p => p.permissionID).filter((id): id is string => !!id);
|
||||
|
||||
// 新选择的权限ID
|
||||
const newSelectedIds = items.map(p => p.permissionID).filter((id): id is string => !!id);
|
||||
|
||||
// 找出需要绑定的权限(新增的)
|
||||
const permissionsToBind = selectedPermissions.value.filter(permissionID => !currentBoundPermissions.includes(permissionID));
|
||||
const permissionsToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
|
||||
|
||||
// 找出需要解绑的权限(移除的)
|
||||
const permissionsToUnbind = currentBoundPermissions.filter(permissionID => !selectedPermissions.value.includes(permissionID));
|
||||
const permissionsToUnbind = currentBoundIds.filter(id => !newSelectedIds.includes(id));
|
||||
|
||||
// 构建需要绑定的权限对象数组
|
||||
if (permissionsToBind.length > 0) {
|
||||
const permissionsToBindObjects = permissionsToBind.map(permissionID => {
|
||||
const permission = permissionList.value.find(p => p.permissionID === permissionID);
|
||||
return permission || { permissionID: permissionID };
|
||||
});
|
||||
const permissionsToBindObjects = items.filter(p => p.permissionID && permissionsToBind.includes(p.permissionID));
|
||||
|
||||
const bindMenu = {
|
||||
...currentMenu.value,
|
||||
@@ -626,10 +558,7 @@ async function savePermissionBinding() {
|
||||
|
||||
// 构建需要解绑的权限对象数组
|
||||
if (permissionsToUnbind.length > 0) {
|
||||
const permissionsToUnbindObjects = permissionsToUnbind.map(permissionID => {
|
||||
const permission = permissionList.value.find(p => p.permissionID === permissionID);
|
||||
return permission || { permissionID: permissionID };
|
||||
});
|
||||
const permissionsToUnbindObjects = (currentBoundResult.dataList || []).filter(p => p.permissionID && permissionsToUnbind.includes(p.permissionID));
|
||||
|
||||
const unbindMenu = {
|
||||
...currentMenu.value,
|
||||
@@ -640,7 +569,6 @@ async function savePermissionBinding() {
|
||||
}
|
||||
|
||||
ElMessage.success('权限绑定保存成功');
|
||||
bindPermissionDialogVisible.value = false;
|
||||
|
||||
// 刷新菜单列表
|
||||
await loadMenuList();
|
||||
@@ -742,8 +670,9 @@ async function handleNodeDrop(draggingNode: any, dropNode: any, dropType: string
|
||||
|
||||
.node-icon {
|
||||
margin-right: 8px;
|
||||
color: #409EFF;
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.node-name {
|
||||
@@ -890,30 +819,4 @@ async function handleNodeDrop(draggingNode: any, dropNode: any, dropType: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 权限绑定容器样式
|
||||
.permission-binding-container {
|
||||
.menu-info {
|
||||
background: #f5f7fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.binding-stats {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -279,120 +279,34 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 绑定菜单对话框 -->
|
||||
<el-dialog
|
||||
v-model="bindMenuDialogVisible"
|
||||
title="绑定菜单"
|
||||
width="800px"
|
||||
@close="resetBindList"
|
||||
>
|
||||
<div class="menu-binding-container">
|
||||
<div class="permission-info" v-if="currentPermission">
|
||||
<h4>权限信息:{{ currentPermission.name }}</h4>
|
||||
<p>权限编码:{{ currentPermission.code }}</p>
|
||||
</div>
|
||||
|
||||
<el-table :data="menuList" style="width: 100%" border stripe>
|
||||
<el-table-column width="80" label="绑定状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="isMenuSelected(row.menuID) ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ isMenuSelected(row.menuID) ? '已绑定' : '未绑定' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="菜单名称" min-width="150" />
|
||||
<el-table-column prop="menuID" label="菜单ID" min-width="120" />
|
||||
<el-table-column prop="url" label="菜单路径" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
:type="isMenuSelected(row.menuID) ? 'danger' : 'primary'"
|
||||
size="small"
|
||||
@click="toggleMenuSelection(row)"
|
||||
>
|
||||
{{ isMenuSelected(row.menuID) ? '解绑' : '绑定' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="binding-stats">
|
||||
<el-alert
|
||||
:title="`已绑定 ${selectedMenus.length} 个菜单`"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="bindMenuDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveMenuBinding" :loading="submitting">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<GenericSelector
|
||||
v-model:visible="bindMenuDialogVisible"
|
||||
:title="`绑定菜单 - ${currentPermission?.name || ''}`"
|
||||
left-title="可选菜单"
|
||||
right-title="已选菜单"
|
||||
:fetch-available-api="fetchAllMenus"
|
||||
:fetch-selected-api="fetchPermissionMenus"
|
||||
:item-config="{ id: 'menuID', label: 'name', sublabel: 'url' }"
|
||||
unit-name="个"
|
||||
search-placeholder="搜索菜单名称..."
|
||||
@confirm="handleMenuConfirm"
|
||||
@cancel="resetBindList"
|
||||
/>
|
||||
|
||||
<!-- 绑定角色对话框 -->
|
||||
<el-dialog
|
||||
v-model="bindRoleDialogVisible"
|
||||
title="绑定角色"
|
||||
width="800px"
|
||||
@close="resetBindList"
|
||||
>
|
||||
<div class="role-binding-container">
|
||||
<div class="permission-info" v-if="currentPermission">
|
||||
<h4>权限信息:{{ currentPermission.name }}</h4>
|
||||
<p>权限编码:{{ currentPermission.code }}</p>
|
||||
</div>
|
||||
|
||||
<el-table :data="roleList" style="width: 100%" border stripe>
|
||||
<el-table-column width="80" label="绑定状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="isRoleSelected(row.roleID) ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ isRoleSelected(row.roleID) ? '已绑定' : '未绑定' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="角色名称" min-width="150" />
|
||||
<el-table-column prop="roleID" label="角色ID" min-width="120" />
|
||||
<el-table-column prop="description" label="角色描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
:type="isRoleSelected(row.roleID) ? 'danger' : 'primary'"
|
||||
size="small"
|
||||
@click="toggleRoleSelection(row)"
|
||||
>
|
||||
{{ isRoleSelected(row.roleID) ? '解绑' : '绑定' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="binding-stats">
|
||||
<el-alert
|
||||
:title="`已绑定 ${selectedRoles.length} 个角色`"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="bindRoleDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveRoleBinding" :loading="submitting">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<GenericSelector
|
||||
v-model:visible="bindRoleDialogVisible"
|
||||
:title="`绑定角色 - ${currentPermission?.name || ''}`"
|
||||
left-title="可选角色"
|
||||
right-title="已选角色"
|
||||
:fetch-available-api="fetchAllRoles"
|
||||
:fetch-selected-api="fetchPermissionRoles"
|
||||
:item-config="{ id: 'roleID', label: 'name', sublabel: 'description' }"
|
||||
unit-name="个"
|
||||
search-placeholder="搜索角色名称..."
|
||||
@confirm="handleRoleConfirm"
|
||||
@cancel="resetBindList"
|
||||
/>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
@@ -407,6 +321,7 @@ import { menuApi } from '@/apis/system/menu';
|
||||
import { roleApi } from '@/apis/system/role';
|
||||
import type { SysModule, SysPermission, SysMenu, SysRole } from '@/types';
|
||||
import { AdminLayout } from '@/views/admin';
|
||||
import { GenericSelector } from '@/components/base';
|
||||
|
||||
defineOptions({
|
||||
name: 'ModulePermissionManageView'
|
||||
@@ -418,10 +333,6 @@ const permissionLoading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const moduleList = ref<SysModule[]>([]);
|
||||
const permissions = ref<SysPermission[]>([]);
|
||||
const menuList = ref<SysMenu[]>([]);
|
||||
const roleList = ref<SysRole[]>([]);
|
||||
const selectedMenus = ref<string[]>([]);
|
||||
const selectedRoles = ref<string[]>([]);
|
||||
|
||||
// 当前选中的模块和权限
|
||||
const currentModule = ref<SysModule | null>(null);
|
||||
@@ -686,80 +597,78 @@ async function handleSubmitPermission() {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有菜单的接口
|
||||
async function fetchAllMenus() {
|
||||
return await menuApi.getAllMenuList();
|
||||
}
|
||||
|
||||
// 获取权限已绑定菜单的接口
|
||||
async function fetchPermissionMenus() {
|
||||
if (!currentPermission.value) {
|
||||
return {
|
||||
success: true,
|
||||
dataList: [],
|
||||
code: 200,
|
||||
message: '',
|
||||
login: true,
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
const permission = { ...currentPermission.value, bindType: 'menu' as const };
|
||||
const result = await permissionApi.getPermissionBindingList(permission);
|
||||
return {
|
||||
code: result.code || 200,
|
||||
message: result.message || '',
|
||||
success: result.success,
|
||||
login: result.login ?? true,
|
||||
auth: result.auth ?? true,
|
||||
dataList: result.data?.menus || []
|
||||
};
|
||||
}
|
||||
|
||||
// 获取所有角色的接口
|
||||
async function fetchAllRoles() {
|
||||
return await roleApi.getAllRoles();
|
||||
}
|
||||
|
||||
// 获取权限已绑定角色的接口
|
||||
async function fetchPermissionRoles() {
|
||||
if (!currentPermission.value) {
|
||||
return {
|
||||
success: true,
|
||||
dataList: [],
|
||||
code: 200,
|
||||
message: '',
|
||||
login: true,
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
const permission = { ...currentPermission.value, bindType: 'role' as const };
|
||||
const result = await permissionApi.getPermissionBindingList(permission);
|
||||
return {
|
||||
code: result.code || 200,
|
||||
message: result.message || '',
|
||||
success: result.success,
|
||||
login: result.login ?? true,
|
||||
auth: result.auth ?? true,
|
||||
dataList: result.data?.roles || []
|
||||
};
|
||||
}
|
||||
|
||||
// 绑定菜单
|
||||
async function handleBindMenu(permission: SysPermission) {
|
||||
currentPermission.value = permission;
|
||||
permission.bindType = 'menu';
|
||||
|
||||
try {
|
||||
const bindingResult = await permissionApi.getPermissionBindingList(permission);
|
||||
const bindList = bindingResult.data?.menus || [];
|
||||
|
||||
const menuResult = await menuApi.getAllMenuList();
|
||||
menuList.value = menuResult.dataList || [];
|
||||
|
||||
selectedMenus.value = bindList.map(menu => menu.menuID).filter((id): id is string => !!id);
|
||||
|
||||
bindMenuDialogVisible.value = true;
|
||||
} catch (error) {
|
||||
console.error('获取菜单绑定信息失败:', error);
|
||||
ElMessage.error('获取菜单绑定信息失败');
|
||||
}
|
||||
bindMenuDialogVisible.value = true;
|
||||
}
|
||||
|
||||
// 绑定角色
|
||||
async function handleBindRole(permission: SysPermission) {
|
||||
currentPermission.value = permission;
|
||||
permission.bindType = 'role';
|
||||
|
||||
try {
|
||||
const bindingResult = await permissionApi.getPermissionBindingList(permission);
|
||||
const bindList = bindingResult.data?.roles || [];
|
||||
|
||||
const roleResult = await roleApi.getAllRoles();
|
||||
roleList.value = roleResult.dataList || [];
|
||||
|
||||
selectedRoles.value = bindList.map(role => role.roleID).filter((id): id is string => !!id);
|
||||
|
||||
bindRoleDialogVisible.value = true;
|
||||
} catch (error) {
|
||||
console.error('获取角色绑定信息失败:', error);
|
||||
ElMessage.error('获取角色绑定信息失败');
|
||||
}
|
||||
bindRoleDialogVisible.value = true;
|
||||
}
|
||||
|
||||
// 菜单选择相关
|
||||
function isMenuSelected(menuID: string | undefined): boolean {
|
||||
return menuID ? selectedMenus.value.includes(menuID) : false;
|
||||
}
|
||||
|
||||
function toggleMenuSelection(menu: SysMenu) {
|
||||
if (!menu.menuID) return;
|
||||
const index = selectedMenus.value.indexOf(menu.menuID);
|
||||
if (index > -1) {
|
||||
selectedMenus.value.splice(index, 1);
|
||||
} else {
|
||||
selectedMenus.value.push(menu.menuID);
|
||||
}
|
||||
}
|
||||
|
||||
// 角色选择相关
|
||||
function isRoleSelected(roleID: string | undefined): boolean {
|
||||
return roleID ? selectedRoles.value.includes(roleID) : false;
|
||||
}
|
||||
|
||||
function toggleRoleSelection(role: SysRole) {
|
||||
if (!role.roleID) return;
|
||||
const index = selectedRoles.value.indexOf(role.roleID);
|
||||
if (index > -1) {
|
||||
selectedRoles.value.splice(index, 1);
|
||||
} else {
|
||||
selectedRoles.value.push(role.roleID);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存菜单绑定
|
||||
async function saveMenuBinding() {
|
||||
// 菜单选择确认 - 在confirm时提交请求
|
||||
async function handleMenuConfirm(items: SysMenu[]) {
|
||||
if (!currentPermission.value?.permissionID) {
|
||||
ElMessage.error('权限信息不完整');
|
||||
return;
|
||||
@@ -768,16 +677,20 @@ async function saveMenuBinding() {
|
||||
try {
|
||||
submitting.value = true;
|
||||
|
||||
// 获取原有绑定和新绑定的差异
|
||||
// 获取当前已绑定的菜单
|
||||
const permission: SysPermission = { ...currentPermission.value, bindType: 'menu' };
|
||||
const bindingResult = await permissionApi.getPermissionBindingList(permission);
|
||||
const currentBound = (bindingResult.data?.menus || []).map(m => m.menuID).filter((id): id is string => !!id);
|
||||
const currentBoundIds = (bindingResult.data?.menus || []).map(m => m.menuID).filter((id): id is string => !!id);
|
||||
|
||||
const menusToBind = selectedMenus.value.filter(id => !currentBound.includes(id));
|
||||
const menusToUnbind = currentBound.filter(id => !selectedMenus.value.includes(id));
|
||||
// 新选择的菜单ID
|
||||
const newSelectedIds = items.map(m => m.menuID).filter((id): id is string => !!id);
|
||||
|
||||
// 找出需要绑定和解绑的菜单
|
||||
const menusToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
|
||||
const menusToUnbind = currentBoundIds.filter(id => !newSelectedIds.includes(id));
|
||||
|
||||
if (menusToBind.length > 0) {
|
||||
const menusToBindObjects = menusToBind.map(id => ({ menuID: id }));
|
||||
const menusToBindObjects = items.filter(m => m.menuID && menusToBind.includes(m.menuID));
|
||||
await permissionApi.bindMenu({
|
||||
...currentPermission.value,
|
||||
menus: menusToBindObjects,
|
||||
@@ -795,7 +708,6 @@ async function saveMenuBinding() {
|
||||
}
|
||||
|
||||
ElMessage.success('菜单绑定保存成功');
|
||||
bindMenuDialogVisible.value = false;
|
||||
} catch (error) {
|
||||
console.error('保存菜单绑定失败:', error);
|
||||
ElMessage.error('保存菜单绑定失败');
|
||||
@@ -804,8 +716,8 @@ async function saveMenuBinding() {
|
||||
}
|
||||
}
|
||||
|
||||
// 保存角色绑定
|
||||
async function saveRoleBinding() {
|
||||
// 角色选择确认 - 在confirm时提交请求
|
||||
async function handleRoleConfirm(items: SysRole[]) {
|
||||
if (!currentPermission.value?.permissionID) {
|
||||
ElMessage.error('权限信息不完整');
|
||||
return;
|
||||
@@ -814,15 +726,20 @@ async function saveRoleBinding() {
|
||||
try {
|
||||
submitting.value = true;
|
||||
|
||||
// 获取当前已绑定的角色
|
||||
const permission: SysPermission = { ...currentPermission.value, bindType: 'role' };
|
||||
const bindingResult = await permissionApi.getPermissionBindingList(permission);
|
||||
const currentBound = (bindingResult.data?.roles || []).map(r => r.roleID).filter((id): id is string => !!id);
|
||||
const currentBoundIds = (bindingResult.data?.roles || []).map(r => r.roleID).filter((id): id is string => !!id);
|
||||
|
||||
const rolesToBind = selectedRoles.value.filter(id => !currentBound.includes(id));
|
||||
const rolesToUnbind = currentBound.filter(id => !selectedRoles.value.includes(id));
|
||||
// 新选择的角色ID
|
||||
const newSelectedIds = items.map(r => r.roleID).filter((id): id is string => !!id);
|
||||
|
||||
// 找出需要绑定和解绑的角色
|
||||
const rolesToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
|
||||
const rolesToUnbind = currentBoundIds.filter(id => !newSelectedIds.includes(id));
|
||||
|
||||
if (rolesToBind.length > 0) {
|
||||
const rolesToBindObjects = rolesToBind.map(id => ({ roleID: id }));
|
||||
const rolesToBindObjects = items.filter(r => r.roleID && rolesToBind.includes(r.roleID));
|
||||
await permissionApi.bindRole({
|
||||
...currentPermission.value,
|
||||
roles: rolesToBindObjects,
|
||||
@@ -840,7 +757,6 @@ async function saveRoleBinding() {
|
||||
}
|
||||
|
||||
ElMessage.success('角色绑定保存成功');
|
||||
bindRoleDialogVisible.value = false;
|
||||
} catch (error) {
|
||||
console.error('保存角色绑定失败:', error);
|
||||
ElMessage.error('保存角色绑定失败');
|
||||
@@ -870,8 +786,6 @@ function resetPermissionForm() {
|
||||
}
|
||||
|
||||
function resetBindList() {
|
||||
selectedMenus.value = [];
|
||||
selectedRoles.value = [];
|
||||
currentPermission.value = null;
|
||||
}
|
||||
|
||||
@@ -1138,32 +1052,6 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定对话框
|
||||
.menu-binding-container,
|
||||
.role-binding-container {
|
||||
.permission-info {
|
||||
background: #f5f7fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.binding-stats {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -97,61 +97,19 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 绑定权限对话框 -->
|
||||
<el-dialog v-model="bindPermissionDialogVisible" title="绑定权限" width="800px" @close="resetBindList">
|
||||
<div class="permission-binding-container">
|
||||
<!-- 角色信息显示 -->
|
||||
<div class="role-info" v-if="currentRole">
|
||||
<h4>角色信息:{{ currentRole.name }}</h4>
|
||||
<p>角色ID:{{ currentRole.roleID }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 权限绑定状态表格 -->
|
||||
<el-table :data="permissionList" style="width: 100%" border stripe>
|
||||
<el-table-column width="80" label="绑定状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="isPermissionSelected(row.permissionID) ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ isPermissionSelected(row.permissionID) ? '已绑定' : '未绑定' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="权限名称" min-width="150" />
|
||||
<el-table-column prop="permissionID" label="权限ID" min-width="120" />
|
||||
<el-table-column prop="code" label="权限编码" min-width="150" />
|
||||
<el-table-column prop="description" label="权限描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
:type="isPermissionSelected(row.permissionID) ? 'danger' : 'primary'"
|
||||
size="small"
|
||||
@click="togglePermissionSelection(row)"
|
||||
>
|
||||
{{ isPermissionSelected(row.permissionID) ? '解绑' : '绑定' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="binding-stats">
|
||||
<el-alert
|
||||
:title="`已绑定 ${selectedPermissions.length} 个权限,未绑定 ${permissionList.length - selectedPermissions.length} 个权限`"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="bindPermissionDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="savePermissionBinding" :loading="submitting">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<GenericSelector
|
||||
v-model:visible="bindPermissionDialogVisible"
|
||||
:title="`绑定权限 - ${currentRole?.name || ''}`"
|
||||
left-title="可选权限"
|
||||
right-title="已选权限"
|
||||
:available-items="availablePermissions"
|
||||
:initial-target-items="initialBoundPermissions"
|
||||
:item-config="{ id: 'permissionID', label: 'name', sublabel: 'code' }"
|
||||
unit-name="个"
|
||||
search-placeholder="搜索权限名称或编码..."
|
||||
@confirm="handlePermissionConfirm"
|
||||
@cancel="resetBindList"
|
||||
/>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
@@ -161,11 +119,12 @@ import { roleApi } from '@/apis/system/role';
|
||||
import { permissionApi } from '@/apis/system/permission';
|
||||
import { SysRole, SysPermission } from '@/types';
|
||||
import { AdminLayout } from '@/views/admin';
|
||||
import { GenericSelector } from '@/components/base';
|
||||
|
||||
defineOptions({
|
||||
name: 'RoleManageView'
|
||||
});
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
import { ref, onMounted, reactive, computed } from 'vue';
|
||||
import { ElMessage, ElMessageBox, FormInstance, FormRules } from 'element-plus';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
|
||||
@@ -176,11 +135,8 @@ const submitting = ref(false);
|
||||
|
||||
// 权限绑定相关数据
|
||||
const permissionList = ref<SysPermission[]>([]);
|
||||
const selectedPermissions = ref<string[]>([]);
|
||||
const currentRole = ref<SysRole | null>(null);
|
||||
const bindList = ref<{ permissions: SysPermission[] }>({
|
||||
permissions: []
|
||||
});
|
||||
const initialBoundPermissions = ref<SysPermission[]>([]);
|
||||
|
||||
// 对话框状态
|
||||
const dialogVisible = ref(false);
|
||||
@@ -295,6 +251,12 @@ function resetForm() {
|
||||
});
|
||||
}
|
||||
|
||||
// 计算可选权限(过滤掉已绑定的)
|
||||
const availablePermissions = computed(() => {
|
||||
const boundIds = new Set(initialBoundPermissions.value.map(p => p.permissionID));
|
||||
return permissionList.value.filter(p => !boundIds.has(p.permissionID));
|
||||
});
|
||||
|
||||
// 查看绑定权限
|
||||
async function handleBindPermission(row: SysRole) {
|
||||
currentRole.value = row;
|
||||
@@ -314,14 +276,8 @@ async function handleBindPermission(row: SysRole) {
|
||||
const bindingResult = await roleApi.getRolePermission({
|
||||
roleID: row.roleID
|
||||
});
|
||||
console.log('已绑定的权限:', bindingResult);
|
||||
bindList.value.permissions = bindingResult.dataList || [];
|
||||
initialBoundPermissions.value = bindingResult.dataList || [];
|
||||
|
||||
// 设置已选中的权限
|
||||
selectedPermissions.value = bindList.value.permissions.map(permission => permission.permissionID).filter((id): id is string => !!id);
|
||||
|
||||
console.log('已绑定的权限:', bindList.value.permissions);
|
||||
console.log('所有权限:', permissionList.value);
|
||||
bindPermissionDialogVisible.value = true;
|
||||
} catch (error) {
|
||||
console.error('获取权限绑定信息失败:', error);
|
||||
@@ -331,32 +287,12 @@ async function handleBindPermission(row: SysRole) {
|
||||
|
||||
// 重置绑定列表
|
||||
function resetBindList() {
|
||||
bindList.value = {
|
||||
permissions: []
|
||||
};
|
||||
selectedPermissions.value = [];
|
||||
initialBoundPermissions.value = [];
|
||||
currentRole.value = null;
|
||||
}
|
||||
|
||||
// 检查权限是否已选中
|
||||
function isPermissionSelected(permissionID: string | undefined): boolean {
|
||||
return permissionID ? selectedPermissions.value.includes(permissionID) : false;
|
||||
}
|
||||
|
||||
// 切换权限选择状态
|
||||
function togglePermissionSelection(permission: SysPermission) {
|
||||
if (!permission.permissionID) return;
|
||||
|
||||
const index = selectedPermissions.value.indexOf(permission.permissionID);
|
||||
if (index > -1) {
|
||||
selectedPermissions.value.splice(index, 1);
|
||||
} else {
|
||||
selectedPermissions.value.push(permission.permissionID);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存权限绑定
|
||||
async function savePermissionBinding() {
|
||||
// 权限选择确认 - 在confirm时提交请求
|
||||
async function handlePermissionConfirm(items: SysPermission[]) {
|
||||
if (!currentRole.value || !currentRole.value.roleID) {
|
||||
ElMessage.error('角色信息不完整');
|
||||
return;
|
||||
@@ -366,13 +302,16 @@ async function savePermissionBinding() {
|
||||
submitting.value = true;
|
||||
|
||||
// 获取当前已绑定的权限ID
|
||||
const currentBoundPermissions = (bindList.value.permissions || []).map(permission => permission.permissionID).filter((id): id is string => !!id);
|
||||
const currentBoundIds = initialBoundPermissions.value.map(p => p.permissionID).filter((id): id is string => !!id);
|
||||
|
||||
// 新选择的权限ID
|
||||
const newSelectedIds = items.map(p => p.permissionID).filter((id): id is string => !!id);
|
||||
|
||||
// 找出需要绑定的权限(新增的)
|
||||
const permissionsToBind = selectedPermissions.value.filter(permissionID => !currentBoundPermissions.includes(permissionID));
|
||||
const permissionsToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
|
||||
|
||||
// 找出需要解绑的权限(移除的)
|
||||
const permissionsToUnbind = currentBoundPermissions.filter(permissionID => !selectedPermissions.value.includes(permissionID));
|
||||
const permissionsToUnbind = currentBoundIds.filter(id => !newSelectedIds.includes(id));
|
||||
|
||||
// 构建需要绑定的权限对象数组
|
||||
if (permissionsToBind.length > 0) {
|
||||
@@ -405,7 +344,6 @@ async function savePermissionBinding() {
|
||||
}
|
||||
|
||||
ElMessage.success('权限绑定保存成功');
|
||||
bindPermissionDialogVisible.value = false;
|
||||
|
||||
// 刷新角色列表
|
||||
await loadRoleList();
|
||||
@@ -515,30 +453,4 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 权限绑定容器样式
|
||||
.permission-binding-container {
|
||||
.role-info {
|
||||
background: #f5f7fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.binding-stats {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -129,45 +129,25 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 绑定部门角色对话框 -->
|
||||
<el-dialog
|
||||
v-model="bindDialogVisible"
|
||||
title="绑定部门角色"
|
||||
width="600px"
|
||||
@close="resetBindForm"
|
||||
>
|
||||
<el-form
|
||||
ref="bindFormRef"
|
||||
:model="bindForm"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="选择部门">
|
||||
<el-tree-select
|
||||
v-model="bindForm.deptId"
|
||||
:data="deptTree"
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
placeholder="请选择部门"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择角色">
|
||||
<el-select v-model="bindForm.roleIds" multiple placeholder="请选择角色" style="width: 100%">
|
||||
<el-option
|
||||
v-for="role in roles"
|
||||
:key="role.roleID"
|
||||
:label="role.name"
|
||||
:value="role.roleID"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="bindDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitBindForm" :loading="binding">
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 部门角色选择器 -->
|
||||
<GenericSelector
|
||||
v-model:visible="showDeptRoleSelector"
|
||||
:title="`绑定部门角色 - ${currentUser.username || ''}`"
|
||||
left-title="可选的部门角色组合"
|
||||
right-title="已选的部门角色"
|
||||
:fetch-available-api="fetchAllDeptRoles"
|
||||
:fetch-selected-api="fetchUserDeptRoles"
|
||||
:filter-selected="filterDeptRoles"
|
||||
:item-config="{ id: 'combinedId', label: 'displayName', sublabel: 'deptDescription' }"
|
||||
:use-tree="true"
|
||||
:tree-transform="transformDeptRolesToTree"
|
||||
:tree-props="{ children: 'children', label: 'displayName', id: 'combinedId' }"
|
||||
:only-leaf-selectable="true"
|
||||
unit-name="个"
|
||||
search-placeholder="搜索部门或角色..."
|
||||
@confirm="handleDeptRoleConfirm"
|
||||
@cancel="closeDeptRoleSelector"
|
||||
/>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
@@ -176,9 +156,10 @@
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
import { userApi, deptApi, roleApi } from '@/apis/system';
|
||||
import type { SysUser, SysRole, PageParam, UserVO, UserDeptRoleVO } from '@/types';
|
||||
import { userApi, deptApi } from '@/apis/system';
|
||||
import type { SysUser, PageParam, UserVO, UserDeptRoleVO, SysUserDeptRole } from '@/types';
|
||||
import { AdminLayout } from '@/views/admin';
|
||||
import { GenericSelector } from '@/components/base';
|
||||
|
||||
defineOptions({
|
||||
name: 'UserManageView'
|
||||
@@ -189,8 +170,6 @@ const submitting = ref(false);
|
||||
const binding = ref(false);
|
||||
|
||||
const userList = ref<UserVO[]>([]);
|
||||
const deptTree = ref<any[]>([]);
|
||||
const roles = ref<SysRole[]>([]);
|
||||
|
||||
// 分页参数
|
||||
const pageParam = ref<PageParam>({
|
||||
@@ -200,23 +179,147 @@ const pageParam = ref<PageParam>({
|
||||
const total = ref(0);
|
||||
|
||||
const userDialogVisible = ref(false);
|
||||
const bindDialogVisible = ref(false);
|
||||
const isEdit = ref(false);
|
||||
|
||||
const userFormRef = ref<FormInstance>();
|
||||
const bindFormRef = ref<FormInstance>();
|
||||
|
||||
const currentUser = ref<UserVO & { password?: string }>({
|
||||
status: 0
|
||||
});
|
||||
|
||||
const bindForm = ref<{
|
||||
userId?: string;
|
||||
deptId?: string;
|
||||
roleIds: string[];
|
||||
}>({
|
||||
roleIds: []
|
||||
});
|
||||
// 保存原始用户数据,用于比较变更
|
||||
const originalUser = ref<UserVO & { password?: string }>({});
|
||||
|
||||
// 选择器控制
|
||||
const showDeptRoleSelector = ref(false);
|
||||
const currentBindingUserId = ref<string>();
|
||||
|
||||
// 获取所有部门角色组合的接口
|
||||
async function fetchAllDeptRoles() {
|
||||
const result = await deptApi.getDeptRoleList({} as SysUserDeptRole);
|
||||
if (result.success) {
|
||||
const deptRoleList = result.dataList || [];
|
||||
// 转换为带有combinedId和displayName的格式
|
||||
const transformed = deptRoleList
|
||||
.filter(item => item.deptID && item.roleID)
|
||||
.map(item => ({
|
||||
...item,
|
||||
combinedId: `${item.deptID}-${item.roleID}`,
|
||||
displayName: `${item.deptName || ''} - ${item.roleName || ''}`
|
||||
}));
|
||||
return { ...result, dataList: transformed };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取用户已选的部门角色接口
|
||||
async function fetchUserDeptRoles() {
|
||||
if (!currentBindingUserId.value) {
|
||||
return {
|
||||
success: true,
|
||||
dataList: [],
|
||||
code: 200,
|
||||
message: '',
|
||||
login: true,
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
const result = await userApi.getUserDeptRole({ userID: currentBindingUserId.value } as SysUserDeptRole);
|
||||
if (result.success) {
|
||||
const selectedList = result.dataList || [];
|
||||
// 转换为带有combinedId和displayName的格式
|
||||
const transformed = selectedList.map(item => ({
|
||||
...item,
|
||||
combinedId: `${item.deptID}-${item.roleID}`,
|
||||
displayName: `${item.deptName || ''} - ${item.roleName || ''}`
|
||||
}));
|
||||
return { ...result, dataList: transformed };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 过滤已选项的方法
|
||||
function filterDeptRoles(available: any[], selected: any[]) {
|
||||
const selectedIds = new Set(selected.map(item => item.combinedId));
|
||||
return available.filter(item => !selectedIds.has(item.combinedId));
|
||||
}
|
||||
|
||||
// 将部门角色扁平数据转换为树形结构
|
||||
function transformDeptRolesToTree(flatData: any[]) {
|
||||
if (!flatData || flatData.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 按部门分组
|
||||
const deptMap = new Map<string, any>();
|
||||
const tree: any[] = [];
|
||||
|
||||
flatData.forEach(item => {
|
||||
if (!item.deptID) return;
|
||||
|
||||
if (!deptMap.has(item.deptID)) {
|
||||
// 创建部门节点
|
||||
deptMap.set(item.deptID, {
|
||||
combinedId: item.deptID,
|
||||
displayName: item.deptName || '',
|
||||
deptID: item.deptID,
|
||||
deptName: item.deptName,
|
||||
parentID: item.parentID,
|
||||
deptPath: item.deptPath,
|
||||
children: [],
|
||||
isDept: true // 标记这是部门节点
|
||||
});
|
||||
}
|
||||
|
||||
// 添加角色到部门的children中
|
||||
const deptNode = deptMap.get(item.deptID);
|
||||
if (deptNode && item.roleID) {
|
||||
deptNode.children.push({
|
||||
...item,
|
||||
isDept: false // 标记这是角色节点
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 构建树形结构
|
||||
const allDepts = Array.from(deptMap.values());
|
||||
const deptTreeMap = new Map<string, any>();
|
||||
|
||||
// 初始化所有部门节点
|
||||
allDepts.forEach(dept => {
|
||||
deptTreeMap.set(dept.deptID, { ...dept });
|
||||
});
|
||||
|
||||
// 构建部门层级关系
|
||||
allDepts.forEach(dept => {
|
||||
const node = deptTreeMap.get(dept.deptID);
|
||||
if (!node) return;
|
||||
|
||||
if (!dept.parentID || dept.parentID === '0' || dept.parentID === '') {
|
||||
// 根部门
|
||||
tree.push(node);
|
||||
} else {
|
||||
// 子部门
|
||||
const parent = deptTreeMap.get(dept.parentID);
|
||||
if (parent) {
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
// 将角色节点添加到部门节点之前
|
||||
const roles = node.children || [];
|
||||
node.children = [];
|
||||
parent.children.push(node);
|
||||
// 将角色添加到部门的children末尾
|
||||
node.children = roles;
|
||||
} else {
|
||||
// 找不到父节点,作为根节点
|
||||
tree.push(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
const userFormRules: FormRules = {
|
||||
username: [
|
||||
@@ -232,10 +335,9 @@ const userFormRules: FormRules = {
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
loadUsers();
|
||||
loadDepts();
|
||||
loadRoles();
|
||||
});
|
||||
|
||||
async function loadUsers() {
|
||||
@@ -254,37 +356,20 @@ async function loadUsers() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDepts() {
|
||||
try {
|
||||
const result = await deptApi.getAllDepts();
|
||||
if (result.success) {
|
||||
deptTree.value = result.dataList || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载部门列表失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRoles() {
|
||||
try {
|
||||
const result = await roleApi.getRoleList({});
|
||||
if (result.success) {
|
||||
roles.value = result.dataList || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载角色列表失败:', error);
|
||||
}
|
||||
}
|
||||
// 不再需要预加载,由GenericSelector在打开时自动调用接口加载
|
||||
|
||||
function handleAdd() {
|
||||
isEdit.value = false;
|
||||
currentUser.value = { status: 0 };
|
||||
originalUser.value = {};
|
||||
userDialogVisible.value = true;
|
||||
}
|
||||
|
||||
function handleEdit(row: UserVO) {
|
||||
isEdit.value = true;
|
||||
currentUser.value = { ...row };
|
||||
// 保存原始数据用于比较
|
||||
originalUser.value = { ...row };
|
||||
userDialogVisible.value = true;
|
||||
}
|
||||
|
||||
@@ -316,12 +401,76 @@ async function handleDelete(row: UserVO) {
|
||||
}
|
||||
|
||||
function handleBindDeptRole(row: UserVO) {
|
||||
bindForm.value = {
|
||||
userId: row.id,
|
||||
deptId: undefined,
|
||||
roleIds: []
|
||||
};
|
||||
bindDialogVisible.value = true;
|
||||
currentBindingUserId.value = row.id;
|
||||
currentUser.value = { ...row };
|
||||
showDeptRoleSelector.value = true;
|
||||
}
|
||||
|
||||
// 部门角色选择确认 - 在confirm时提交请求
|
||||
async function handleDeptRoleConfirm(items: any[]) {
|
||||
if (!currentBindingUserId.value) {
|
||||
ElMessage.error('用户信息不完整');
|
||||
return;
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
ElMessage.warning('请至少选择一个部门角色');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
binding.value = true;
|
||||
|
||||
// 移除临时添加的字段,只保留原始字段
|
||||
const userDeptRoles: SysUserDeptRole[] = items.map(item => ({
|
||||
deptID: item.deptID,
|
||||
roleID: item.roleID,
|
||||
userID: currentBindingUserId.value
|
||||
}));
|
||||
|
||||
// 构建 UserDeptRoleVO 对象(用于批量绑定)
|
||||
const userDeptRoleVO = {
|
||||
users: [{ id: currentBindingUserId.value } as SysUser],
|
||||
userDeptRoles: userDeptRoles
|
||||
} as UserDeptRoleVO;
|
||||
|
||||
const result = await userApi.bindUserDeptRole(userDeptRoleVO);
|
||||
|
||||
if (result.success) {
|
||||
ElMessage.success(`成功绑定 ${items.length} 个部门角色`);
|
||||
loadUsers();
|
||||
} else {
|
||||
ElMessage.error(result.message || '绑定失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('绑定失败:', error);
|
||||
ElMessage.error('绑定失败');
|
||||
} finally {
|
||||
binding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭部门角色选择器
|
||||
function closeDeptRoleSelector() {
|
||||
currentBindingUserId.value = undefined;
|
||||
}
|
||||
|
||||
// 获取修改过的字段
|
||||
function getChangedFields(): Partial<SysUser> {
|
||||
const changed: Partial<SysUser> = { id: currentUser.value.id };
|
||||
|
||||
// 比较每个字段,只包含修改过的字段
|
||||
Object.keys(currentUser.value).forEach((key) => {
|
||||
const currentValue = (currentUser.value as any)[key];
|
||||
const originalValue = (originalUser.value as any)[key];
|
||||
|
||||
// 如果值发生变化,则添加到变更对象中
|
||||
if (currentValue !== originalValue) {
|
||||
(changed as any)[key] = currentValue;
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
async function submitUserForm() {
|
||||
@@ -333,7 +482,10 @@ async function submitUserForm() {
|
||||
|
||||
let result;
|
||||
if (isEdit.value) {
|
||||
result = await userApi.updateUser(currentUser.value as SysUser);
|
||||
// 只传入修改过的字段
|
||||
const changedData = getChangedFields();
|
||||
console.log('更新用户 - 修改的字段:', changedData);
|
||||
result = await userApi.updateUser(changedData as SysUser);
|
||||
} else {
|
||||
result = await userApi.createUser(currentUser.value as SysUser);
|
||||
}
|
||||
@@ -352,40 +504,11 @@ async function submitUserForm() {
|
||||
}
|
||||
}
|
||||
|
||||
async function submitBindForm() {
|
||||
try {
|
||||
binding.value = true;
|
||||
|
||||
// 构建 UserDeptRoleVO 对象
|
||||
const userDeptRoleVO: UserDeptRoleVO = {
|
||||
user: { id: bindForm.value.userId } as SysUser,
|
||||
depts: bindForm.value.deptId ? [{ id: bindForm.value.deptId }] : [],
|
||||
roles: bindForm.value.roleIds.map(roleId => ({ id: roleId }))
|
||||
};
|
||||
|
||||
const result = await userApi.bindUserDeptRole(userDeptRoleVO);
|
||||
|
||||
if (result.success) {
|
||||
ElMessage.success('绑定成功');
|
||||
bindDialogVisible.value = false;
|
||||
loadUsers();
|
||||
} else {
|
||||
ElMessage.error(result.message || '绑定失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('绑定失败:', error);
|
||||
ElMessage.error('绑定失败');
|
||||
} finally {
|
||||
binding.value = false;
|
||||
}
|
||||
}
|
||||
// submitBindForm 已合并到 handleDeptRoleConfirm 中
|
||||
|
||||
function resetForm() {
|
||||
userFormRef.value?.resetFields();
|
||||
}
|
||||
|
||||
function resetBindForm() {
|
||||
bindFormRef.value?.resetFields();
|
||||
originalUser.value = {};
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
|
||||
@@ -76,14 +76,16 @@
|
||||
<td>{{ formatDate(task.endTime) }}</td>
|
||||
<td>
|
||||
<span class="status-tag" :class="getStatusClass(task.status)">
|
||||
{{ getStatusText(task.status) }}
|
||||
{{ getStatusText(task.status, task.startTime, task.endTime) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatDate(task.createTime) }}</td>
|
||||
<td class="action-cell">
|
||||
<button class="btn-link btn-primary" @click="handleView(task)">查看</button>
|
||||
<button class="btn-link btn-warning" @click="handleEdit(task)" v-if="task.status === 0">编辑</button>
|
||||
<button class="btn-link btn-success" @click="handlePublish(task)" v-if="task.status === 0">发布</button>
|
||||
<button class="btn-link btn-success" @click="handleStateChange(task, 'publish')" v-if="task.status !== 1">发布</button>
|
||||
<button class="btn-link btn-warning" @click="handleStateChange(task, 'unpublish')" v-if="task.status === 1">下架</button>
|
||||
<button class="btn-link btn-primary" @click="handleStatistics(task)">统计</button>
|
||||
<button class="btn-link btn-warning" @click="handleUpdateUser(task)" v-if="task.status !== 2">修改人员</button>
|
||||
<button class="btn-link btn-danger" @click="handleDelete(task)" v-if="task.status === 0">删除</button>
|
||||
</td>
|
||||
@@ -184,7 +186,7 @@
|
||||
<span class="detail-label">任务状态:</span>
|
||||
<span class="detail-value">
|
||||
<span class="status-badge" :class="getStatusClass(viewingTask.learningTask.status)">
|
||||
{{ getStatusText(viewingTask.learningTask.status) }}
|
||||
{{ getStatusText(viewingTask.learningTask.status, viewingTask.learningTask.startTime, viewingTask.learningTask.endTime) }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -311,15 +313,18 @@
|
||||
</div>
|
||||
|
||||
<!-- 人员选择器组件 -->
|
||||
<UserSelect
|
||||
<GenericSelector
|
||||
v-model:visible="showUserSelector"
|
||||
:mode="selectorMode"
|
||||
:title="selectorMode === 'add' ? '添加人员' : '删除人员'"
|
||||
:left-title="selectorMode === 'add' ? '可添加人员' : '当前人员'"
|
||||
:right-title="selectorMode === 'add' ? '待添加人员' : '待删除人员'"
|
||||
:available-users="selectorMode === 'remove' ? availableUsers : []"
|
||||
:initial-target-users="[]"
|
||||
:available-items="selectorMode === 'remove' ? availableUsers : []"
|
||||
:initial-target-items="[]"
|
||||
:loading="saving"
|
||||
:item-config="{ id: 'id', label: 'username', sublabel: 'deptName' }"
|
||||
unit-name="人"
|
||||
search-placeholder="搜索人员..."
|
||||
:use-pagination="selectorMode === 'add'"
|
||||
:fetch-api="selectorMode === 'add' ? userApi.getUserPage : undefined"
|
||||
:filter-params="userFilterParams"
|
||||
@@ -335,7 +340,7 @@ import { ref, computed, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { learningTaskApi } from '@/apis/study';
|
||||
import { userApi } from '@/apis/system';
|
||||
import { UserSelect } from '@/components';
|
||||
import { GenericSelector } from '@/components/base';
|
||||
import type { LearningTask, TaskVO, PageParam, UserVO } from '@/types';
|
||||
|
||||
defineOptions({
|
||||
@@ -518,6 +523,10 @@ function handleEdit(task: LearningTask) {
|
||||
emit('edit', task);
|
||||
}
|
||||
|
||||
function handleStatistics(task: LearningTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 修改人员 - 显示当前人员列表
|
||||
async function handleUpdateUser(task: LearningTask) {
|
||||
managingTask.value = task;
|
||||
@@ -583,7 +592,7 @@ function closeSelectorModal() {
|
||||
}
|
||||
|
||||
// 处理用户选择器确认事件
|
||||
async function handleUserSelectConfirm(selectedUsers: UserVO[]) {
|
||||
async function handleUserSelectConfirm(selectedUsers: any[]) {
|
||||
if (!managingTask.value || selectedUsers.length === 0) {
|
||||
ElMessage.warning('请选择要操作的人员');
|
||||
return;
|
||||
@@ -602,7 +611,7 @@ async function handleUserSelectConfirm(selectedUsers: UserVO[]) {
|
||||
ElMessage.success(`成功添加 ${userIds.length} 位人员`);
|
||||
|
||||
// 更新当前用户列表
|
||||
currentUsers.value.push(...selectedUsers);
|
||||
currentUsers.value.push(...(selectedUsers as UserVO[]));
|
||||
} else {
|
||||
// 执行删除操作
|
||||
for (const userID of userIds) {
|
||||
@@ -627,21 +636,21 @@ async function handleUserSelectConfirm(selectedUsers: UserVO[]) {
|
||||
}
|
||||
|
||||
// 发布任务
|
||||
async function handlePublish(task: LearningTask) {
|
||||
async function handleStateChange(task: LearningTask, state: 'publish' | 'unpublish') {
|
||||
try {
|
||||
const res = await learningTaskApi.publishTask({
|
||||
taskID: task.taskID!,
|
||||
status: 1
|
||||
const res = await learningTaskApi.changeTaskStatus({
|
||||
...task,
|
||||
status: state === 'publish' ? 1 : 2
|
||||
});
|
||||
if (res.success) {
|
||||
ElMessage.success('任务发布成功');
|
||||
ElMessage.success('任务状态更新成功');
|
||||
loadTaskList();
|
||||
} else {
|
||||
ElMessage.error(res.message || '发布失败');
|
||||
ElMessage.error(res.message || '状态更新失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发布任务失败:', error);
|
||||
ElMessage.error('发布任务失败');
|
||||
console.error('状态更新失败:', error);
|
||||
ElMessage.error('状态更新失败');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -689,18 +698,27 @@ function getStatusClass(status?: number) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
function getStatusText(status?: number) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return '草稿';
|
||||
case 1:
|
||||
return '进行中';
|
||||
case 2:
|
||||
return '已结束';
|
||||
default:
|
||||
return '未知';
|
||||
|
||||
function getStatusText(status?: number, startTime?: string, endTime?: string): string {
|
||||
if (status === 0) {
|
||||
return '草稿';
|
||||
}
|
||||
if (status === 1) {
|
||||
let now = new Date();
|
||||
let startTimeDate = new Date(startTime!);
|
||||
let endTimeDate = new Date(endTime!);
|
||||
if (now >= startTimeDate && now <= endTimeDate) {
|
||||
return '进行中';
|
||||
} else if (now < startTimeDate) {
|
||||
return '未开始';
|
||||
} else {
|
||||
return '已结束';
|
||||
}
|
||||
}
|
||||
if (status === 2) {
|
||||
return '下架';
|
||||
}
|
||||
return '未知';
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
@@ -810,28 +828,6 @@ defineExpose({
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 10px 20px;
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #66b1ff;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.task-table-wrapper {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
@@ -913,46 +909,45 @@ defineExpose({
|
||||
|
||||
.action-cell {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
border: none;
|
||||
padding: 4px 8px;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border-radius: 4px;
|
||||
min-width: 64px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.btn-primary {
|
||||
&:hover {
|
||||
background: #ecf5ff;
|
||||
}
|
||||
background: #409eff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&.btn-warning {
|
||||
color: #e6a23c;
|
||||
|
||||
&:hover {
|
||||
background: #fdf6ec;
|
||||
}
|
||||
background: #e6a23c;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&.btn-success {
|
||||
color: #67c23a;
|
||||
|
||||
&:hover {
|
||||
background: #f0f9ff;
|
||||
}
|
||||
background: #67c23a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&.btn-danger {
|
||||
background: #f56c6c;
|
||||
color: #ffffff;
|
||||
|
||||
&:hover {
|
||||
background: #fef0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1073,9 +1068,11 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
// 通用按钮样式
|
||||
.btn-default,
|
||||
.btn-danger {
|
||||
// 通用按钮样式(排除表格中的 btn-link)
|
||||
.btn-primary:not(.btn-link),
|
||||
.btn-success:not(.btn-link),
|
||||
.btn-danger:not(.btn-link),
|
||||
.btn-default:not(.btn-link) {
|
||||
padding: 10px 24px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
@@ -1083,13 +1080,44 @@ defineExpose({
|
||||
transition: all 0.3s;
|
||||
border: none;
|
||||
|
||||
.icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
.btn-primary:not(.btn-link) {
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #66b1ff;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-success:not(.btn-link) {
|
||||
background: #67c23a;
|
||||
color: #fff;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #85ce61;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-danger:not(.btn-link) {
|
||||
background: #f56c6c;
|
||||
color: #fff;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #f78989;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-default:not(.btn-link) {
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
border: 1px solid #dcdfe6;
|
||||
@@ -1100,15 +1128,6 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #f56c6c;
|
||||
color: #fff;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #f78989;
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗样式
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
@@ -1300,46 +1319,6 @@ defineExpose({
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn-success,
|
||||
.btn-danger {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #67c23a;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: #85ce61;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #f56c6c;
|
||||
color: #fff;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #f78989;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.current-user-list {
|
||||
border: 1px solid #e0e0e0;
|
||||
|
||||
@@ -3775,10 +3775,10 @@ typed-array-length@^1.0.7:
|
||||
possible-typed-array-names "^1.0.0"
|
||||
reflect.getprototypeof "^1.0.6"
|
||||
|
||||
typescript@*, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@~4.5.5:
|
||||
version "4.5.5"
|
||||
resolved "https://registry.npmmirror.com/typescript/-/typescript-4.5.5.tgz"
|
||||
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
|
||||
typescript@*, typescript@^5.2.2, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta":
|
||||
version "5.2.2"
|
||||
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.2.2.tgz"
|
||||
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
|
||||
|
||||
unbox-primitive@^1.1.0:
|
||||
version "1.1.0"
|
||||
|
||||
Reference in New Issue
Block a user