diff --git a/.image/common/pms-feature.png b/.image/common/pms-feature.png
new file mode 100644
index 000000000..25e12776b
Binary files /dev/null and b/.image/common/pms-feature.png differ
diff --git a/.image/common/pms-preview.png b/.image/common/pms-preview.png
new file mode 100644
index 000000000..c7a85aa76
Binary files /dev/null and b/.image/common/pms-preview.png differ
diff --git a/README.md b/README.md
index 303c5121f..b866c8331 100644
--- a/README.md
+++ b/README.md
@@ -31,8 +31,8 @@
| 【完整版】[yudao-cloud](https://gitee.com/zhijiantianya/yudao-cloud) | [`master`](https://gitee.com/zhijiantianya/yudao-cloud/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/zhijiantianya/yudao-cloud/tree/master-jdk17/) 分支 | [`master-jdk25`](https://gitee.com/zhijiantianya/yudao-cloud/tree/master-jdk25/) 分支 |
| 【精简版】[yudao-cloud-mini](https://gitee.com/yudaocode/yudao-cloud-mini) | [`master`](https://gitee.com/yudaocode/yudao-cloud-mini/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/yudaocode/yudao-cloud-mini/tree/master-jdk17/) 分支 | [`master-jdk25`](https://gitee.com/yudaocode/yudao-cloud-mini/tree/master-jdk25/) 分支 |
-* 【完整版】:包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、HRM、FMS、IM 即时通讯、AI 大模型、IoT 物联网等功能
-* 【精简版】:只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、HRM、FMS、IM 即时通讯、AI 大模型、IoT 物联网等功能
+* 【完整版】:包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、HRM、FMS、PMS、IM 即时通讯、AI 大模型、IoT 物联网等功能
+* 【精简版】:只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、HRM、FMS、PMS、IM 即时通讯、AI 大模型、IoT 物联网等功能
可参考 [《迁移文档》](https://cloud.iocoder.cn/migrate-module/) ,只需要 5-10 分钟,即可将【完整版】按需迁移到【精简版】
@@ -105,7 +105,7 @@
团队包含专业的项目经理、架构师、前端工程师、后端工程师、测试工程师、运维工程师,可以提供全流程的外包服务。
-项目可以是商城、SCRM 系统、OA 系统、物流系统、ERP 系统、HRM 人力资源系统、FMS 财务系统、CMS 系统、HIS 系统、支付系统、IM 即时通讯、微信公众号、微信小程序等等。
+项目可以是商城、SCRM 系统、OA 系统、物流系统、ERP 系统、HRM 人力资源系统、FMS 财务系统、PMS 项目管理系统、CMS 系统、HIS 系统、支付系统、IM 即时通讯、微信公众号、微信小程序等等。
## 🐼 内置功能
@@ -115,7 +115,7 @@
* 通用模块(必选):系统功能、基础设施
* 通用模块(可选):工作流程、支付系统、数据报表、会员中心
-* 业务系统(按需):Mall 电子商城、OA 办公自动化、ERP 企业资源计划系统、WMS 仓库管理系统、CRM 客户关系管理、CMS 内容管理系统、MES 执行制造系统、HRM 人力资源管理、FMS 财务管理、AI 大模型平台、IoT 物联网系统、IM 即时通讯系统、Mobile 手机移动端、Report 数据大屏
+* 业务系统(按需):Mall 电子商城、OA 办公自动化、ERP 企业资源计划系统、WMS 仓库管理系统、CRM 客户关系管理、CMS 内容管理系统、MES 执行制造系统、HRM 人力资源管理、FMS 财务管理、PMS 项目管理、AI 大模型平台、IoT 物联网系统、IM 即时通讯系统、Mobile 手机移动端、Report 数据大屏
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
>
@@ -311,6 +311,14 @@

+### PMS 项目管理系统
+
+演示地址:
+
+
+
+
+
### AI 大模型
演示地址:
@@ -361,6 +369,7 @@
| `yudao-module-wms` | WMS 系统的 Module 模块 |
| `yudao-module-hrm` | HRM 人力资源管理的 Module 模块 |
| `yudao-module-fms` | FMS 财务管理的 Module 模块 |
+| `yudao-module-pms` | PMS 项目管理的 Module 模块 |
| `yudao-module-im` | IM 即时通讯的 Module 模块 |
| `yudao-module-ai` | AI 大模型的 Module 模块 |
| `yudao-module-iot` | IoT 物联网的 Module 模块 |
diff --git a/pom.xml b/pom.xml
index 762c31731..bedfda5f3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,6 +30,7 @@
yudao-module-im
yudao-module-hrm
yudao-module-fms
+ yudao-module-pms
diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql
index c31b0d63b..b8fd2e995 100644
--- a/sql/mysql/ruoyi-vue-pro.sql
+++ b/sql/mysql/ruoyi-vue-pro.sql
@@ -4204,7 +4204,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8052, '知识库创建', 'pms:kb:library:create', 3, 2, 8050, '', '', '', '', 0, b'1', b'1', b'1', '1', '2026-08-19 00:28:42', '1', '2026-08-23 16:21:39', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8053, '知识库修改', 'pms:kb:library:update', 3, 3, 8050, '', '', '', '', 0, b'1', b'1', b'1', '1', '2026-08-19 00:28:42', '1', '2026-08-23 16:21:39', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8054, '知识库删除', 'pms:kb:library:delete', 3, 4, 8050, '', '', '', '', 0, b'1', b'1', b'1', '1', '2026-08-19 00:28:42', '1', '2026-08-23 16:21:39', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8060, '我的关注', 'pms:kb:knowledge-library:query', 2, 40, 8090, 'collection', 'ep:star', 'pms/kb/collection/index', 'PmsKnowledgeCollection', 0, b'1', b'1', b'0', '1', '2026-08-19 03:05:49', '1', '2026-08-23 16:12:56', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8060, '我的关注', 'pms:kb:knowledge-library:query', 2, 40, 8090, 'favorite', 'ep:star', 'pms/kb/favorite/index', 'PmsKnowledgeFavorite', 0, b'1', b'1', b'0', '1', '2026-08-19 03:05:49', '1', '2026-08-23 16:12:56', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8070, '最近使用', 'pms:kb:knowledge-library:query', 2, 50, 8090, 'recent', 'ep:clock', 'pms/kb/recent/index', 'PmsKnowledgeRecent', 0, b'1', b'1', b'0', '1', '2026-08-19 03:05:49', '1', '2026-08-23 16:12:56', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8080, '项目中心', '', 1, 10, 8000, 'pm', 'ep:grid', NULL, NULL, 0, b'1', b'1', b'1', '1', '2026-08-19 11:42:30', '1', '2026-08-20 09:05:58', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8081, '归档项目', 'pms:pm:project:query', 2, 30, 8080, 'project/archive', 'ep:box', 'pms/pm/project/archive/index', 'PmsProjectArchive', 0, b'1', b'0', b'0', '1', '2026-08-19 11:42:30', '1', '2026-08-27 10:01:06', b'0');
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
index 517fd603e..2702c703a 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
@@ -43,6 +43,12 @@ public class BannerApplicationRunner implements ApplicationRunner {
System.out.println("[CRM 系统 yudao-module-crm - 教程][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
// MES 系统
System.out.println("[MES 系统 yudao-module-mes - 教程][参考 https://cloud.iocoder.cn/mes/build/ 开启]");
+ // HRM 人力资源管理系统
+ System.out.println("[HRM 人力资源管理系统 yudao-module-hrm - 教程][参考 https://cloud.iocoder.cn/hrm/build/ 开启]");
+ // FMS 财务管理系统
+ System.out.println("[FMS 财务管理系统 yudao-module-fms - 教程][参考 https://cloud.iocoder.cn/fms/build/ 开启]");
+ // PMS 项目管理系统
+ System.out.println("[PMS 项目管理系统 yudao-module-pms - 教程][参考 https://cloud.iocoder.cn/pms/build/ 开启]");
// IM 即时通讯
System.out.println("[IM 即时通讯 yudao-module-im - 教程][参考 https://cloud.iocoder.cn/im/build/ 开启]");
// 微信公众号
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
index d17e06370..d787e7a37 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
@@ -417,43 +417,61 @@ public class GlobalExceptionHandler {
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
}
- // 6. WMS 仓库管理系统
+ // 6. HRM 人力资源管理系统
+ if (message.contains("hrm_")) {
+ log.error("[HRM 人力资源管理系统 yudao-module-hrm - 表结构未导入][参考 https://cloud.iocoder.cn/hrm/build/ 开启]");
+ return CommonResult.error(NOT_IMPLEMENTED.getCode(),
+ "[HRM 人力资源管理系统 yudao-module-hrm - 表结构未导入][参考 https://cloud.iocoder.cn/hrm/build/ 开启]");
+ }
+ // 7. FMS 财务管理系统
+ if (message.contains("fms_")) {
+ log.error("[FMS 财务管理系统 yudao-module-fms - 表结构未导入][参考 https://cloud.iocoder.cn/fms/build/ 开启]");
+ return CommonResult.error(NOT_IMPLEMENTED.getCode(),
+ "[FMS 财务管理系统 yudao-module-fms - 表结构未导入][参考 https://cloud.iocoder.cn/fms/build/ 开启]");
+ }
+ // 8. PMS 项目管理系统
+ if (message.contains("pms_")) {
+ log.error("[PMS 项目管理系统 yudao-module-pms - 表结构未导入][参考 https://cloud.iocoder.cn/pms/build/ 开启]");
+ return CommonResult.error(NOT_IMPLEMENTED.getCode(),
+ "[PMS 项目管理系统 yudao-module-pms - 表结构未导入][参考 https://cloud.iocoder.cn/pms/build/ 开启]");
+ }
+ // 9. WMS 仓库管理系统
if (message.contains("wms_")) {
log.error("[WMS 仓库管理系统 yudao-module-wms - 表结构未导入][参考 https://cloud.iocoder.cn/wms/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[WMS 仓库管理系统 yudao-module-wms - 表结构未导入][参考 https://cloud.iocoder.cn/wms/build/ 开启]");
}
- // 7. CRM 系统
+ // 10. CRM 系统
if (message.contains("crm_")) {
log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
}
- // 8. MES 系统
+ // 11. MES 系统
if (message.contains("mes_")) {
log.error("[MES 系统 yudao-module-mes - 表结构未导入][参考 https://cloud.iocoder.cn/mes/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[MES 系统 yudao-module-mes - 表结构未导入][参考 https://cloud.iocoder.cn/mes/build/ 开启]");
}
- // 9. IM 即时通讯
+ // 12. IM 即时通讯
if (message.contains("im_")) {
log.error("[IM 即时通讯 yudao-module-im - 表结构未导入][参考 https://cloud.iocoder.cn/im/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[IM 即时通讯 yudao-module-im - 表结构未导入][参考 https://cloud.iocoder.cn/im/build/ 开启]");
}
- // 10. 支付平台
+ // 13. 支付平台
if (message.contains("pay_")) {
log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
}
- // 11. AI 大模型
+ // 14. AI 大模型
if (message.contains("ai_")) {
log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
}
- // 12. IoT 物联网
+ // 15. IoT 物联网
if (message.contains("iot_")) {
log.error("[IoT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
diff --git a/yudao-gateway/src/main/resources/application.yaml b/yudao-gateway/src/main/resources/application.yaml
index 96515035d..b3a2b3958 100644
--- a/yudao-gateway/src/main/resources/application.yaml
+++ b/yudao-gateway/src/main/resources/application.yaml
@@ -227,6 +227,13 @@ spring:
- Path=/admin-api/fms/**
filters:
- RewritePath=/admin-api/fms/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
+ ## pms-server 服务
+ - id: pms-admin-api # 路由的编号
+ uri: grayLb://pms-server
+ predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
+ - Path=/admin-api/pms/**
+ filters:
+ - RewritePath=/admin-api/pms/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
x-forwarded:
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
default-filters: # 全局过滤器,对应 GatewayFilterDefinition 数组
@@ -301,6 +308,9 @@ knife4j:
- name: fms-server
service-name: fms-server
url: /admin-api/fms/v3/api-docs
+ - name: pms-server
+ service-name: pms-server
+ url: /admin-api/pms/v3/api-docs
--- #################### 芋道相关配置 ####################
diff --git a/yudao-module-pms/pom.xml b/yudao-module-pms/pom.xml
new file mode 100644
index 000000000..14333d7bc
--- /dev/null
+++ b/yudao-module-pms/pom.xml
@@ -0,0 +1,24 @@
+
+
+
+ cn.iocoder.cloud
+ yudao
+ ${revision}
+
+ 4.0.0
+
+ yudao-module-pms-api
+ yudao-module-pms-server
+
+ yudao-module-pms
+ pom
+
+ ${project.artifactId}
+
+ pms 包下,项目管理(Project Management System)。
+ 例如说:项目、工作事项、迭代、看板、工时和知识库等等
+
+
+
diff --git a/yudao-module-pms/yudao-module-pms-api/pom.xml b/yudao-module-pms/yudao-module-pms-api/pom.xml
new file mode 100644
index 000000000..ae6c5c95a
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/pom.xml
@@ -0,0 +1,33 @@
+
+
+
+ cn.iocoder.cloud
+ yudao-module-pms
+ ${revision}
+
+ 4.0.0
+ yudao-module-pms-api
+ jar
+
+ ${project.artifactId}
+
+ pms 模块 API,暴露给其它模块调用
+
+
+
+
+ cn.iocoder.cloud
+ yudao-common
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ true
+
+
+
+
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/api/package-info.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/api/package-info.java
new file mode 100644
index 000000000..163c18d5a
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/api/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * pms API 包,定义暴露给其它模块的 API
+ */
+package cn.iocoder.yudao.module.pms.api;
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/ApiConstants.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/ApiConstants.java
new file mode 100644
index 000000000..9147202f3
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/ApiConstants.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.pms.enums;
+
+import cn.iocoder.yudao.framework.common.enums.RpcConstants;
+
+/**
+ * API 相关的枚举
+ *
+ * @author 芋道源码
+ */
+public class ApiConstants {
+
+ /** 服务名,需要和 spring.application.name 保持一致。 */
+ public static final String NAME = "pms-server";
+
+ public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/pms";
+
+ public static final String VERSION = "1.0.0";
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/DictTypeConstants.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/DictTypeConstants.java
new file mode 100644
index 000000000..09f460044
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/DictTypeConstants.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.pms.enums;
+
+/**
+ * PMS 字典类型的枚举类
+ *
+ * @author 芋道源码
+ */
+public interface DictTypeConstants {
+
+ String PMS_WORK_ITEM_PRIORITY = "pms_work_item_priority"; // PMS 工作项优先级
+ String PMS_WORK_ITEM_DEFECT_TYPE = "pms_work_item_defect_type"; // PMS 工作项缺陷类型
+ String PMS_PROJECT_TYPE = "pms_project_type"; // PMS 项目类型
+ String PMS_PROJECT_STATUS = "pms_project_status"; // PMS 项目状态
+ String PMS_PROJECT_LEVEL = "pms_project_level"; // PMS 项目优先级
+ String PMS_PROJECT_GROUP_TYPE = "pms_project_group_type"; // PMS 项目分组类型
+ String PMS_PROJECT_SCENE_TYPE = "pms_project_scene_type"; // PMS 项目列表场景
+ String PMS_PROJECT_SORT_TYPE = "pms_project_sort_type"; // PMS 项目排序类型
+ String PMS_PROJECT_MEMBER_LEVEL = "pms_project_member_level"; // PMS 项目成员权限级别
+ String PMS_ITERATION_STATUS = "pms_iteration_status"; // PMS 迭代状态
+ String PMS_WORK_ITEM_TYPE = "pms_work_item_type"; // PMS 工作项类型
+ String PMS_WORK_ITEM_STATUS_TYPE = "pms_work_item_status_type"; // PMS 工作项语义状态
+ String PMS_WORK_ITEM_LIFECYCLE_STATUS = "pms_work_item_lifecycle_status"; // PMS 工作项生命周期状态
+ String PMS_KNOWLEDGE_DOCUMENT_STATUS = "pms_knowledge_document_status"; // PMS 知识文档状态
+ String PMS_KNOWLEDGE_CONTENT_LEVEL = "pms_knowledge_content_level"; // PMS 知识内容协作等级
+ String PMS_KNOWLEDGE_GROUP_TYPE = "pms_knowledge_group_type"; // PMS 知识库分组类型
+ String PMS_KNOWLEDGE_LIBRARY_MEMBER_LEVEL = "pms_knowledge_library_member_level"; // PMS 知识库成员等级
+ String PMS_KNOWLEDGE_OBJECT_TYPE = "pms_knowledge_object_type"; // PMS 知识对象类型
+ String PMS_KNOWLEDGE_DOCUMENT_TYPE = "pms_knowledge_document_type"; // PMS 知识文档类型
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/ErrorCodeConstants.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/ErrorCodeConstants.java
new file mode 100644
index 000000000..c7daa87b9
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/ErrorCodeConstants.java
@@ -0,0 +1,114 @@
+package cn.iocoder.yudao.module.pms.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * PMS 错误码常量
+ *
+ * PMS 系统,使用 1-053-000-000 段
+ *
+ * @author 芋道源码
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 项目 1-053-100-000 ==========
+ ErrorCode PROJECT_NOT_EXISTS = new ErrorCode(1_053_100_000, "项目不存在");
+ ErrorCode PROJECT_ACCESS_DENIED = new ErrorCode(1_053_100_001, "无权访问该项目");
+ ErrorCode PROJECT_ADMIN_REQUIRED = new ErrorCode(1_053_100_002, "只有项目管理员可以执行该操作");
+ ErrorCode PROJECT_STATUS_INVALID = new ErrorCode(1_053_100_004, "当前项目状态不允许执行该操作");
+ ErrorCode PROJECT_WRITE_ACCESS_DENIED = new ErrorCode(1_053_100_005, "当前项目角色无编辑权限");
+ ErrorCode PROJECT_ANNOUNCEMENT_NOT_EXISTS = new ErrorCode(1_053_100_006, "项目公告不存在");
+ ErrorCode PROJECT_OWNER_REQUIRED = new ErrorCode(1_053_100_007, "只有项目拥有者可以执行该操作");
+
+ // ========== 项目成员 1-053-105-000 ==========
+ ErrorCode PROJECT_MEMBER_NOT_EXISTS = new ErrorCode(1_053_105_000, "项目成员不存在");
+ ErrorCode PROJECT_MEMBER_CREATOR_CANNOT_REMOVE = new ErrorCode(1_053_105_001, "项目创建人不能移出项目");
+ ErrorCode PROJECT_MEMBER_OWNER_LEVEL_CANNOT_ASSIGN = new ErrorCode(1_053_105_002,
+ "项目拥有者级别不能直接分配");
+ ErrorCode PROJECT_MEMBER_OWNER_CANNOT_EXIT = new ErrorCode(1_053_105_003, "项目拥有者不能退出项目");
+
+ // ========== 项目分组 1-053-110-000 ==========
+ ErrorCode PROJECT_GROUP_NOT_EXISTS = new ErrorCode(1_053_110_000, "项目分组不存在");
+ ErrorCode PROJECT_GROUP_NAME_DUPLICATE = new ErrorCode(1_053_110_001, "项目分组名称已存在");
+ ErrorCode PROJECT_GROUP_DEFAULT_CANNOT_MODIFY = new ErrorCode(1_053_110_002, "默认项目分组不允许修改");
+ ErrorCode PROJECT_GROUP_DEFAULT_CANNOT_DELETE = new ErrorCode(1_053_110_003, "默认项目分组不允许删除");
+
+ // ========== 项目模板 1-053-112-000 ==========
+ ErrorCode PROJECT_TEMPLATE_NOT_EXISTS = new ErrorCode(1_053_112_000, "项目模板不存在");
+ ErrorCode PROJECT_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1_053_112_001, "同一项目类型下的模板名称已存在");
+ ErrorCode PROJECT_TEMPLATE_CONFIG_INVALID = new ErrorCode(1_053_112_002, "项目模板配置无效:{}");
+
+ // ========== 项目迭代 1-053-115-000 ==========
+ ErrorCode ITERATION_NOT_EXISTS = new ErrorCode(1_053_115_000, "项目迭代不存在");
+ ErrorCode ITERATION_STATUS_INVALID = new ErrorCode(1_053_115_002, "当前迭代状态不允许执行该操作");
+
+ // ========== 工作项 1-053-120-000 ==========
+ ErrorCode WORK_ITEM_NOT_EXISTS = new ErrorCode(1_053_120_000, "工作项不存在");
+ ErrorCode WORK_ITEM_TYPE_INVALID = new ErrorCode(1_053_120_001, "当前项目不支持该工作项类型");
+ ErrorCode WORK_ITEM_STATUS_NOT_EXISTS = new ErrorCode(1_053_120_002, "工作项状态不存在");
+ ErrorCode WORK_ITEM_STATUS_INVALID = new ErrorCode(1_053_120_003, "工作项状态与项目或类型不匹配");
+ ErrorCode WORK_ITEM_TIME_INVALID = new ErrorCode(1_053_120_004, "工作项开始时间必须早于截止时间");
+ ErrorCode WORK_ITEM_ITERATION_INVALID = new ErrorCode(1_053_120_005, "所属迭代与工作项项目不匹配");
+ ErrorCode WORK_ITEM_PARENT_INVALID = new ErrorCode(1_053_120_006, "父工作项与当前工作项不匹配");
+ ErrorCode WORK_ITEM_RELATED_REQUIREMENT_INVALID = new ErrorCode(1_053_120_007, "关联需求与当前工作项不匹配");
+ ErrorCode WORK_ITEM_DEFECT_TYPE_INVALID = new ErrorCode(1_053_120_008, "只有缺陷工作项可以设置缺陷类型");
+ ErrorCode WORK_ITEM_SORT_INVALID = new ErrorCode(1_053_120_009, "工作项看板排序数据无效");
+ ErrorCode WORK_ITEM_STATUS_NAME_DUPLICATE = new ErrorCode(1_053_120_010, "工作项状态名称已存在");
+ ErrorCode WORK_ITEM_STATUS_DEFAULT_CANNOT_DELETE = new ErrorCode(1_053_120_011, "工作项初始状态不能删除");
+ ErrorCode WORK_ITEM_STATUS_TRANSFER_INVALID = new ErrorCode(1_053_120_012, "工作项状态迁移目标无效");
+ ErrorCode WORK_ITEM_STATUS_SORT_INVALID = new ErrorCode(1_053_120_013, "工作项状态排序数据无效");
+ ErrorCode WORK_ITEM_WORK_LOG_NOT_EXISTS = new ErrorCode(1_053_120_014, "工作项工时记录不存在");
+ ErrorCode WORK_ITEM_WORK_LOG_INVALID = new ErrorCode(1_053_120_015, "工时记录与工作项不匹配");
+ ErrorCode WORK_ITEM_LABEL_NOT_EXISTS = new ErrorCode(1_053_120_016, "工作项标签不存在");
+ ErrorCode WORK_ITEM_LABELS_INVALID = new ErrorCode(1_053_120_017, "工作项标签数据无效");
+ ErrorCode WORK_ITEM_COMMENT_NOT_EXISTS = new ErrorCode(1_053_120_018, "工作项评论不存在");
+ ErrorCode WORK_ITEM_COMMENT_REPLY_INVALID = new ErrorCode(1_053_120_019, "回复的评论与工作项不匹配");
+ ErrorCode WORK_ITEM_COMMENT_ACCESS_DENIED = new ErrorCode(1_053_120_020, "只能修改或删除自己的评论");
+ ErrorCode WORK_ITEM_LIFECYCLE_STATUS_INVALID = new ErrorCode(1_053_120_021, "工作项当前生命周期状态不支持该操作");
+ ErrorCode WORK_ITEM_DELETE_STATUS_INVALID = new ErrorCode(1_053_120_022, "只有回收站中的工作项才能彻底删除");
+
+ // ========== 知识库 1-053-130-000 ==========
+ ErrorCode KNOWLEDGE_LIBRARY_NOT_EXISTS = new ErrorCode(1_053_130_000, "知识库不存在");
+ ErrorCode KNOWLEDGE_LIBRARY_ACCESS_DENIED = new ErrorCode(1_053_130_001, "无权访问该知识库");
+ ErrorCode KNOWLEDGE_LIBRARY_WRITE_ACCESS_DENIED = new ErrorCode(1_053_130_002, "无权编辑该知识库");
+ ErrorCode KNOWLEDGE_LIBRARY_ADMIN_REQUIRED = new ErrorCode(1_053_130_003, "只有知识库管理员可以执行该操作");
+ ErrorCode KNOWLEDGE_LIBRARY_CREATOR_CANNOT_REMOVE = new ErrorCode(1_053_130_004, "知识库创建人不能移出知识库");
+ ErrorCode KNOWLEDGE_LIBRARY_MEMBERS_INVALID = new ErrorCode(1_053_130_005, "知识库成员数据无效");
+ ErrorCode KNOWLEDGE_FOLDER_NOT_EXISTS = new ErrorCode(1_053_130_006, "知识库文件夹不存在");
+ ErrorCode KNOWLEDGE_FOLDER_PARENT_INVALID = new ErrorCode(1_053_130_007, "父文件夹与知识库不匹配");
+ ErrorCode KNOWLEDGE_FOLDER_MOVE_INVALID = new ErrorCode(1_053_130_008, "文件夹不能移动到自身或其子文件夹中");
+ ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_053_130_009, "知识库文档不存在");
+ ErrorCode KNOWLEDGE_DOCUMENT_PARENT_INVALID = new ErrorCode(1_053_130_010, "父文档与知识库不匹配");
+ ErrorCode KNOWLEDGE_DOCUMENT_FOLDER_INVALID = new ErrorCode(1_053_130_011, "文档文件夹与知识库不匹配");
+ ErrorCode KNOWLEDGE_DOCUMENT_MOVE_INVALID = new ErrorCode(1_053_130_012, "文档不能移动到自身或其子文档中");
+ ErrorCode KNOWLEDGE_DOCUMENT_STATUS_INVALID = new ErrorCode(1_053_130_013, "文档状态无效");
+ ErrorCode KNOWLEDGE_DOCUMENT_TYPE_INVALID = new ErrorCode(1_053_130_014, "文档类型无效");
+ ErrorCode KNOWLEDGE_DOCUMENT_MOVE_TARGET_INVALID = new ErrorCode(1_053_130_015, "文档移动目标只能选择文件夹或父文档之一");
+ ErrorCode KNOWLEDGE_GROUP_NOT_EXISTS = new ErrorCode(1_053_130_020, "知识库分组不存在");
+ ErrorCode KNOWLEDGE_GROUP_NAME_DUPLICATE = new ErrorCode(1_053_130_021, "知识库分组名称已存在");
+ ErrorCode KNOWLEDGE_GROUP_DEFAULT_CANNOT_MODIFY = new ErrorCode(1_053_130_022, "默认知识库分组不允许修改");
+ ErrorCode KNOWLEDGE_GROUP_DEFAULT_CANNOT_DELETE = new ErrorCode(1_053_130_023, "默认知识库分组不允许删除");
+ ErrorCode KNOWLEDGE_GROUP_SORT_DUPLICATE = new ErrorCode(1_053_130_024, "知识库分组排序列表存在重复分组");
+ ErrorCode KNOWLEDGE_DOCUMENT_LABEL_NOT_EXISTS = new ErrorCode(1_053_130_030, "知识库文档标签不存在");
+ ErrorCode KNOWLEDGE_RECYCLE_RECORD_NOT_EXISTS = new ErrorCode(1_053_130_040, "知识库回收站记录不存在");
+ ErrorCode KNOWLEDGE_RECYCLE_TYPE_INVALID = new ErrorCode(1_053_130_041, "知识库回收站对象类型无效");
+ ErrorCode KNOWLEDGE_INTERACTION_OBJECT_INVALID = new ErrorCode(1_053_130_050, "知识对象不存在或类型不匹配");
+ ErrorCode KNOWLEDGE_FAVORITE_DUPLICATE = new ErrorCode(1_053_130_051, "该知识对象已关注");
+ ErrorCode KNOWLEDGE_DOCUMENT_SHARE_ALREADY_OPEN = new ErrorCode(1_053_130_060, "该文档已开启分享");
+ ErrorCode KNOWLEDGE_DOCUMENT_SHARE_INVALID = new ErrorCode(1_053_130_061, "文档分享不存在或已失效");
+ ErrorCode KNOWLEDGE_CONTENT_PERMISSION_NOT_EXISTS = new ErrorCode(1_053_130_070, "知识内容协作权限不存在");
+ ErrorCode KNOWLEDGE_CONTENT_ACCESS_DENIED = new ErrorCode(1_053_130_071, "无权访问该知识内容");
+ ErrorCode KNOWLEDGE_CONTENT_WRITE_ACCESS_DENIED = new ErrorCode(1_053_130_072, "无权编辑该知识内容");
+ ErrorCode KNOWLEDGE_CONTENT_DELETE_ACCESS_DENIED = new ErrorCode(1_053_130_073, "无权删除该知识内容");
+ ErrorCode KNOWLEDGE_CONTENT_PERMISSION_MEMBERS_INVALID = new ErrorCode(1_053_130_074, "知识内容协作者数据无效");
+ ErrorCode KNOWLEDGE_DOCUMENT_COMMENT_NOT_EXISTS = new ErrorCode(1_053_130_080, "知识库文档评论不存在");
+ ErrorCode KNOWLEDGE_DOCUMENT_COMMENT_REPLY_INVALID = new ErrorCode(1_053_130_081, "回复的评论与文档不匹配");
+ ErrorCode KNOWLEDGE_DOCUMENT_COMMENT_ACCESS_DENIED = new ErrorCode(1_053_130_082, "只能删除自己的评论");
+ ErrorCode KNOWLEDGE_DOCUMENT_COMMENT_TYPE_INVALID = new ErrorCode(1_053_130_083, "只有富文本文档支持评论");
+ ErrorCode KNOWLEDGE_LIBRARY_TEMPLATE_NOT_EXISTS = new ErrorCode(1_053_130_084, "知识库模板不存在");
+ ErrorCode KNOWLEDGE_LIBRARY_TEMPLATE_INVALID = new ErrorCode(1_053_130_085, "知识库模板配置无效");
+ ErrorCode KNOWLEDGE_LIBRARY_DIRECT_MEMBER_NOT_EXISTS = new ErrorCode(1_053_130_086,
+ "当前账号不是该知识库的直接成员");
+ ErrorCode KNOWLEDGE_LIBRARY_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1_053_130_087, "知识库模板名称已存在");
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/MessageTemplateConstants.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/MessageTemplateConstants.java
new file mode 100644
index 000000000..95c82fe0f
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/MessageTemplateConstants.java
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.pms.enums;
+
+/**
+ * PMS 消息模板常量
+ *
+ * @author 芋道源码
+ */
+public interface MessageTemplateConstants {
+
+ // ======================= 站内信模板 =======================
+
+ String WORK_ITEM_ASSIGNED = "pms_work_item_assigned"; // 工作项指派通知
+ String KNOWLEDGE_DOCUMENT_SHARED = "pms_knowledge_document_shared"; // 知识文档分享通知
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/PmsKnowledgeObjectTypeEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/PmsKnowledgeObjectTypeEnum.java
new file mode 100644
index 000000000..08ddf221a
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/PmsKnowledgeObjectTypeEnum.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.pms.enums.kb;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 知识对象类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsKnowledgeObjectTypeEnum implements ArrayValuable {
+
+ LIBRARY(1), // 知识库
+ FOLDER(2), // 文件夹
+ DOCUMENT(3), // 文档
+ FILE(4); // 文件
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsKnowledgeObjectTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsKnowledgeObjectTypeEnum valueOf(Integer type) {
+ return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/content/PmsKnowledgeContentLevelEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/content/PmsKnowledgeContentLevelEnum.java
new file mode 100644
index 000000000..f1703d789
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/content/PmsKnowledgeContentLevelEnum.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.pms.enums.kb.content;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 知识内容协作等级枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsKnowledgeContentLevelEnum implements ArrayValuable {
+
+ MANAGE(1, "管理员"),
+ EDIT(2, "可编辑"),
+ PREVIEW(3, "仅预览"),
+ DOWNLOAD(4, "可下载"),
+ UPLOAD_DOWNLOAD(5, "可上传下载");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsKnowledgeContentLevelEnum::getLevel).toArray(Integer[]::new);
+
+ /**
+ * 等级
+ */
+ private final Integer level;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ public static PmsKnowledgeContentLevelEnum valueOf(Integer level) {
+ return ArrayUtil.firstMatch(item -> item.getLevel().equals(level), values());
+ }
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static boolean canEdit(Integer level) {
+ return MANAGE.level.equals(level) || EDIT.level.equals(level) || UPLOAD_DOWNLOAD.level.equals(level);
+ }
+
+ public static boolean canDelete(Integer level) {
+ return MANAGE.level.equals(level) || EDIT.level.equals(level);
+ }
+
+ public static boolean canDownload(Integer level) {
+ return level != null && ObjectUtil.notEqual(PREVIEW.level, level);
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/content/PmsKnowledgeDocumentStatusEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/content/PmsKnowledgeDocumentStatusEnum.java
new file mode 100644
index 000000000..bde69c576
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/content/PmsKnowledgeDocumentStatusEnum.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.pms.enums.kb.content;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * PMS 知识库文档状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsKnowledgeDocumentStatusEnum implements ArrayValuable {
+
+ RECYCLED(-1, "回收站"),
+ DRAFT(0, "草稿"),
+ NORMAL(1, "正常"),
+ TEMPLATE(2, "模板");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsKnowledgeDocumentStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 非回收站状态集合
+ */
+ public static final Set ACTIVE_STATUSES = SetUtils.asSet(
+ DRAFT.getStatus(), NORMAL.getStatus(), TEMPLATE.getStatus());
+
+ /**
+ * 状态
+ */
+ private final Integer status;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsKnowledgeDocumentStatusEnum valueOf(Integer status) {
+ return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/content/PmsKnowledgeDocumentTypeEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/content/PmsKnowledgeDocumentTypeEnum.java
new file mode 100644
index 000000000..afdcca26e
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/content/PmsKnowledgeDocumentTypeEnum.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.pms.enums.kb.content;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 知识库文档类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsKnowledgeDocumentTypeEnum implements ArrayValuable {
+
+ RICH_TEXT(3, "富文本"),
+ FILE(4, "文件");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsKnowledgeDocumentTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsKnowledgeDocumentTypeEnum valueOf(Integer type) {
+ return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/library/PmsKnowledgeGroupTypeEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/library/PmsKnowledgeGroupTypeEnum.java
new file mode 100644
index 000000000..9db1bed3c
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/library/PmsKnowledgeGroupTypeEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.pms.enums.kb.library;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 知识库分组类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsKnowledgeGroupTypeEnum implements ArrayValuable {
+
+ ALL(1, "全部知识库"),
+ UNGROUPED(2, "未分组"),
+ CUSTOM(3, "自定义分组");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsKnowledgeGroupTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsKnowledgeGroupTypeEnum valueOf(Integer type) {
+ return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/library/PmsKnowledgeLibraryMemberLevelEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/library/PmsKnowledgeLibraryMemberLevelEnum.java
new file mode 100644
index 000000000..2751e39f9
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/kb/library/PmsKnowledgeLibraryMemberLevelEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.pms.enums.kb.library;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 知识库成员等级枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsKnowledgeLibraryMemberLevelEnum implements ArrayValuable {
+
+ CREATOR(1, "创建人"),
+ ADMIN(2, "管理员"),
+ MEMBER(3, "成员");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsKnowledgeLibraryMemberLevelEnum::getLevel).toArray(Integer[]::new);
+
+ /**
+ * 等级
+ */
+ private final Integer level;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsKnowledgeLibraryMemberLevelEnum valueOf(Integer level) {
+ return ArrayUtil.firstMatch(item -> item.getLevel().equals(level), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/iteration/PmsIterationStatusEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/iteration/PmsIterationStatusEnum.java
new file mode 100644
index 000000000..32ce03130
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/iteration/PmsIterationStatusEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.pms.enums.pm.iteration;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 迭代状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsIterationStatusEnum implements ArrayValuable {
+
+ PLANNED(1, "未开始"),
+ ACTIVE(2, "进行中"),
+ COMPLETED(3, "已完成");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsIterationStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 状态
+ */
+ private final Integer status;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsIterationStatusEnum valueOf(Integer status) {
+ return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectGroupTypeEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectGroupTypeEnum.java
new file mode 100644
index 000000000..d21abd8ac
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectGroupTypeEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.pms.enums.pm.project;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 项目分组类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsProjectGroupTypeEnum implements ArrayValuable {
+
+ ALL(1, "全部项目"),
+ UNGROUPED(2, "未分组"),
+ CUSTOM(3, "自定义分组");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsProjectGroupTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsProjectGroupTypeEnum valueOf(Integer type) {
+ return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectLevelEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectLevelEnum.java
new file mode 100644
index 000000000..6eaaaf4c6
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectLevelEnum.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.pms.enums.pm.project;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 项目优先级枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsProjectLevelEnum implements ArrayValuable {
+
+ HIGHEST(1, "最高"),
+ HIGH(2, "较高"),
+ NORMAL(3, "普通"),
+ LOW(4, "较低"),
+ LOWEST(5, "最低");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(PmsProjectLevelEnum::getLevel)
+ .toArray(Integer[]::new);
+
+ /**
+ * 优先级
+ */
+ private final Integer level;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsProjectLevelEnum valueOf(Integer level) {
+ return ArrayUtil.firstMatch(item -> item.getLevel().equals(level), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectMemberLevelEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectMemberLevelEnum.java
new file mode 100644
index 000000000..3f115287b
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectMemberLevelEnum.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.pms.enums.pm.project;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * PMS 项目成员权限级别枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsProjectMemberLevelEnum implements ArrayValuable {
+
+ OWNER(1, "项目拥有者"),
+ ADMIN(2, "项目管理员"),
+ WRITE(3, "编辑"),
+ READ(4, "只读");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsProjectMemberLevelEnum::getLevel).toArray(Integer[]::new);
+
+ /**
+ * 可以管理项目的成员级别
+ */
+ public static final List MANAGER_LEVELS = Arrays.asList(OWNER.level, ADMIN.level);
+ /**
+ * 可以编辑项目业务数据的成员级别
+ */
+ public static final List WRITABLE_LEVELS = Arrays.asList(OWNER.level, ADMIN.level, WRITE.level);
+
+ /**
+ * 权限级别
+ */
+ private final Integer level;
+ /**
+ * 权限级别名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static boolean isOwner(Integer level) {
+ return ObjUtil.equal(OWNER.level, level);
+ }
+
+ public static boolean isAdmin(Integer level) {
+ return ObjUtil.equal(ADMIN.level, level);
+ }
+
+ public static boolean isManager(Integer level) {
+ return isOwner(level) || isAdmin(level);
+ }
+
+ public static boolean isWritable(Integer level) {
+ return isManager(level) || ObjUtil.equal(WRITE.level, level);
+ }
+
+ public static boolean isReadable(Integer level) {
+ return valueOf(level) != null;
+ }
+
+ public static PmsProjectMemberLevelEnum valueOf(Integer level) {
+ return ArrayUtil.firstMatch(item -> ObjUtil.equal(item.level, level), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectSceneTypeEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectSceneTypeEnum.java
new file mode 100644
index 000000000..c1bc5dc5e
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectSceneTypeEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.pms.enums.pm.project;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 项目列表场景枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsProjectSceneTypeEnum implements ArrayValuable {
+
+ ALL(1, "全部项目"),
+ MANAGED(2, "我负责的"),
+ PARTICIPATED(3, "我参与的");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(PmsProjectSceneTypeEnum::getType)
+ .toArray(Integer[]::new);
+
+ /**
+ * 场景类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsProjectSceneTypeEnum valueOf(Integer type) {
+ return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectSortTypeEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectSortTypeEnum.java
new file mode 100644
index 000000000..04a3101a2
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectSortTypeEnum.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.pms.enums.pm.project;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 项目排序类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsProjectSortTypeEnum implements ArrayValuable {
+
+ ACCESS_TIME(1, "按访问时间"),
+ CREATE_TIME(2, "按创建时间");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(PmsProjectSortTypeEnum::getType)
+ .toArray(Integer[]::new);
+
+ /**
+ * 排序类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsProjectSortTypeEnum valueOf(Integer type) {
+ return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectStatusEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectStatusEnum.java
new file mode 100644
index 000000000..ae40e12e3
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectStatusEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.pms.enums.pm.project;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 项目状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsProjectStatusEnum implements ArrayValuable {
+
+ ACTIVE(1, "进行中"),
+ ARCHIVED(2, "已归档"),
+ RECYCLED(3, "回收站");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsProjectStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 状态
+ */
+ private final Integer status;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsProjectStatusEnum valueOf(Integer status) {
+ return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectTypeEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectTypeEnum.java
new file mode 100644
index 000000000..61af5beab
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/project/PmsProjectTypeEnum.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.pms.enums.pm.project;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 项目类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsProjectTypeEnum implements ArrayValuable {
+
+ GENERAL(1, "通用项目"),
+ AGILE(2, "敏捷开发项目");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(PmsProjectTypeEnum::getType)
+ .toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsProjectTypeEnum valueOf(Integer type) {
+ return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemActivityContentEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemActivityContentEnum.java
new file mode 100644
index 000000000..79cb798f9
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemActivityContentEnum.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.pms.enums.pm.workitem;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * PMS 工作项动态内容枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsWorkItemActivityContentEnum {
+
+ WORK_ITEM_CREATED("创建了工作项"),
+ WORK_ITEM_FIELD_UPDATED("将{}从「{}」修改为「{}」"),
+ WORK_ITEM_DESCRIPTION_UPDATED("更新了工作项描述"),
+ WORK_ITEM_LABELS_UPDATED("将标签从「{}」修改为「{}」"),
+ WORK_ITEM_MEMBERS_UPDATED("将参与人从「{}」修改为「{}」"),
+ WORK_ITEM_ATTACHMENTS_UPDATED("将附件从「{}」修改为「{}」"),
+ WORK_ITEM_PARENT_UPDATED("将父工作项从「{}」修改为「{}」"),
+ WORK_ITEM_NAME_UPDATED("将工作项名称从「{}」修改为「{}」"),
+ WORK_ITEM_STATUS_UPDATED("将状态从「{}」修改为「{}」"),
+ WORK_ITEM_MOVED_TO_BACKLOG("将工作项移回待规划"),
+ WORK_ITEM_PLANNED_TO_ITERATION("将工作项规划到迭代"),
+ WORK_ITEM_ARCHIVED("归档了工作项"),
+ WORK_ITEM_RECYCLED("将工作项移入回收站"),
+ WORK_ITEM_RESTORED("恢复了工作项"),
+
+ COMMENT_CREATED("添加了评论"),
+ COMMENT_REPLIED("回复了评论"),
+ COMMENT_UPDATED("更新了评论"),
+ COMMENT_DELETED("删除了评论"),
+
+ WORK_LOG_CREATED("登记了 {} 小时工时"),
+ WORK_LOG_UPDATED("更新了工时记录");
+
+ /**
+ * 动态内容模板
+ */
+ private final String content;
+
+ /**
+ * 格式化动态内容
+ *
+ * @param arguments 模板参数
+ * @return 动态内容
+ */
+ public String format(Object... arguments) {
+ return StrUtil.format(content, arguments);
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemDefectTypeEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemDefectTypeEnum.java
new file mode 100644
index 000000000..5436b1f3a
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemDefectTypeEnum.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.pms.enums.pm.workitem;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 缺陷类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsWorkItemDefectTypeEnum implements ArrayValuable {
+
+ FUNCTION(1, "功能问题"),
+ UI(2, "界面优化"),
+ USABILITY(3, "易用性问题"),
+ SECURITY(4, "安全问题"),
+ PERFORMANCE(5, "性能问题"),
+ CODE(6, "代码错误");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsWorkItemDefectTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 缺陷类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsWorkItemDefectTypeEnum valueOf(Integer type) {
+ return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemLifecycleStatusEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemLifecycleStatusEnum.java
new file mode 100644
index 000000000..67f47ecd3
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemLifecycleStatusEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.pms.enums.pm.workitem;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 工作项生命周期状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsWorkItemLifecycleStatusEnum implements ArrayValuable {
+
+ ACTIVE(1, "正常"),
+ ARCHIVED(2, "已归档"),
+ RECYCLED(3, "回收站");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsWorkItemLifecycleStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 状态
+ */
+ private final Integer status;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsWorkItemLifecycleStatusEnum valueOf(Integer status) {
+ return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemPriorityEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemPriorityEnum.java
new file mode 100644
index 000000000..096d793d0
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemPriorityEnum.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.pms.enums.pm.workitem;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 工作项优先级枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsWorkItemPriorityEnum implements ArrayValuable {
+
+ NONE(0, "无"),
+ LOW(1, "低"),
+ MEDIUM(2, "中"),
+ HIGH(3, "高");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsWorkItemPriorityEnum::getPriority).toArray(Integer[]::new);
+
+ /**
+ * 优先级
+ */
+ private final Integer priority;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsWorkItemPriorityEnum valueOf(Integer priority) {
+ return ArrayUtil.firstMatch(item -> item.getPriority().equals(priority), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemStatusTypeEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemStatusTypeEnum.java
new file mode 100644
index 000000000..6a799514c
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemStatusTypeEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.pms.enums.pm.workitem;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 工作项语义状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsWorkItemStatusTypeEnum implements ArrayValuable {
+
+ PENDING(1, "未开始"),
+ PROCESSING(2, "进行中"),
+ COMPLETED(3, "已完成");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsWorkItemStatusTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 状态类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsWorkItemStatusTypeEnum valueOf(Integer type) {
+ return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemTypeEnum.java b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemTypeEnum.java
new file mode 100644
index 000000000..9e6d9c7ec
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/enums/pm/workitem/PmsWorkItemTypeEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.pms.enums.pm.workitem;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * PMS 工作项类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PmsWorkItemTypeEnum implements ArrayValuable {
+
+ REQUIREMENT(2, "需求"),
+ TASK(3, "任务"),
+ DEFECT(4, "缺陷");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(PmsWorkItemTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static PmsWorkItemTypeEnum valueOf(Integer type) {
+ return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/pom.xml b/yudao-module-pms/yudao-module-pms-server/pom.xml
new file mode 100644
index 000000000..876eff6d7
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/pom.xml
@@ -0,0 +1,125 @@
+
+
+
+ cn.iocoder.cloud
+ yudao-module-pms
+ ${revision}
+
+ 4.0.0
+ yudao-module-pms-server
+
+ ${project.artifactId}
+
+ pms 包下,项目管理(Project Management System)。
+ 例如说:项目、工作事项、迭代、看板、工时和知识库等等
+
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-env
+
+
+
+
+ cn.iocoder.cloud
+ yudao-module-pms-api
+ ${revision}
+
+
+ cn.iocoder.cloud
+ yudao-module-system-api
+ ${revision}
+
+
+ cn.iocoder.cloud
+ yudao-module-infra-api
+ ${revision}
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-tenant
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-security
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-mybatis
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-rpc
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-job
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-excel
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-monitor
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-test
+ test
+
+
+
+
+
+ ${project.artifactId}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+
+ repackage
+
+
+
+
+
+
+
+
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/PmsServerApplication.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/PmsServerApplication.java
new file mode 100644
index 000000000..85a11e6f6
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/PmsServerApplication.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.pms;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * PMS 项目的启动类
+ *
+ * @author 芋道源码
+ */
+@SpringBootApplication
+public class PmsServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(PmsServerApplication.class, args);
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeContentPermissionController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeContentPermissionController.java
new file mode 100644
index 000000000..9cd6ac046
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeContentPermissionController.java
@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.permission.PmsKnowledgeContentPermissionRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.permission.PmsKnowledgeContentPermissionUpdateReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeContentPermissionDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeContentPermissionMemberDO;
+import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeContentPermissionService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识内容协作权限")
+@RestController
+@RequestMapping("/pms/kb/content-permission")
+@Validated
+public class PmsKnowledgeContentPermissionController {
+
+ @Resource
+ private PmsKnowledgeContentPermissionService permissionService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private DeptApi deptApi;
+
+ @GetMapping("/get")
+ @Operation(summary = "获得知识内容协作权限")
+ @Parameter(name = "id", description = "协作权限编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult getContentPermission(@RequestParam("id") Long id) {
+ Long userId = getLoginUserId();
+ PmsKnowledgeContentPermissionDO permission = permissionService.getContentPermission(id, userId);
+ List members = permissionService.getContentPermissionMemberList(id, userId);
+ Integer currentUserLevel = permissionService.getCurrentUserContentPermissionLevel(
+ id, permission.getLibraryId(), userId);
+ return success(buildPermissionRespVO(permission, members, currentUserLevel));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新知识内容协作权限")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult updateContentPermission(@Valid @RequestBody PmsKnowledgeContentPermissionUpdateReqVO updateReqVO) {
+ permissionService.updateContentPermission(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private PmsKnowledgeContentPermissionRespVO buildPermissionRespVO(PmsKnowledgeContentPermissionDO permission,
+ List members,
+ Integer currentUserLevel) {
+ Map userMap = adminUserApi.getUserMap(
+ convertSet(members, PmsKnowledgeContentPermissionMemberDO::getUserId));
+ Map deptMap = deptApi.getDeptMap(
+ convertSet(members, PmsKnowledgeContentPermissionMemberDO::getDeptId));
+ // 拼接 VO 返回
+ PmsKnowledgeContentPermissionRespVO permissionVO = BeanUtils.toBean(permission, PmsKnowledgeContentPermissionRespVO.class)
+ .setCreatorUserId(NumberUtils.parseLong(permission.getCreator()))
+ .setCurrentUserLevel(currentUserLevel)
+ .setMembers(BeanUtils.toBean(members, PmsKnowledgeContentPermissionRespVO.Member.class));
+ permissionVO.getMembers().forEach(member -> {
+ findAndThen(userMap, member.getUserId(), user -> member.setUserName(user.getNickname()));
+ findAndThen(deptMap, member.getDeptId(), dept -> member.setDeptName(dept.getName()));
+ });
+ return permissionVO;
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeDocumentController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeDocumentController.java
new file mode 100644
index 000000000..aca906ca5
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeDocumentController.java
@@ -0,0 +1,202 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentCreateReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentMoveReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentSearchPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentUpdateReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeDocumentDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.interaction.PmsKnowledgeDocumentLikeDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryDO;
+import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeContentLevelEnum;
+import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeDocumentTypeEnum;
+import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeContentPermissionService;
+import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeDocumentService;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeDocumentLikeService;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeFavoriteService;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeViewRecordService;
+import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识库文档")
+@RestController
+@RequestMapping("/pms/kb/document")
+@Validated
+public class PmsKnowledgeDocumentController {
+
+ @Resource
+ private PmsKnowledgeDocumentService documentService;
+ @Resource
+ private PmsKnowledgeContentPermissionService contentPermissionService;
+ @Resource
+ private PmsKnowledgeFavoriteService favoriteService;
+ @Resource
+ private PmsKnowledgeDocumentLikeService documentLikeService;
+ @Resource
+ private PmsKnowledgeViewRecordService viewRecordService;
+ @Resource
+ private PmsKnowledgeLibraryService libraryService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建文档")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult createDocument(@Valid @RequestBody PmsKnowledgeDocumentCreateReqVO createReqVO) {
+ return success(documentService.createDocument(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新文档内容")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult updateDocument(@Valid @RequestBody PmsKnowledgeDocumentUpdateReqVO updateReqVO) {
+ documentService.updateDocument(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "将文档移入回收站")
+ @Parameter(name = "id", description = "文档编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult deleteDocument(@RequestParam("id") Long id) {
+ documentService.deleteDocument(id, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/move")
+ @Operation(summary = "移动文档")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult moveDocument(@Valid @RequestBody PmsKnowledgeDocumentMoveReqVO moveReqVO) {
+ documentService.moveDocument(moveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得文档详情")
+ @Parameter(name = "id", description = "文档编号", required = true, example = "1024")
+ @Parameter(name = "view", description = "是否记录本次浏览", example = "true")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult getDocument(
+ @RequestParam("id") Long id,
+ @RequestParam(value = "view", defaultValue = "false") Boolean view) {
+ // 查询文档
+ Long userId = getLoginUserId();
+ PmsKnowledgeDocumentDO document = documentService.getDocument(id, userId);
+ // 记录浏览
+ if (Boolean.TRUE.equals(view)) {
+ viewRecordService.createViewRecord(document.getLibraryId(), document.getType(), document.getId(), userId);
+ }
+ // 构建文档 VO
+ return success(buildDocumentRespVO(document, userId));
+ }
+
+ @GetMapping("/search-page")
+ @Operation(summary = "使用 MySQL 全局搜索文档")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult> getDocumentSearchPage(
+ @Valid PmsKnowledgeDocumentSearchPageReqVO pageReqVO) {
+ PageResult pageResult = documentService.getDocumentSearchPage(
+ pageReqVO, getLoginUserId());
+ return success(new PageResult<>(buildDocumentRespVOList(pageResult.getList()), pageResult.getTotal()));
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private PmsKnowledgeDocumentRespVO buildDocumentRespVO(PmsKnowledgeDocumentDO document, Long userId) {
+ // 1.1 拼接当前用户的内容权限和互动状态
+ Integer currentUserLevel = contentPermissionService.getCurrentUserContentPermissionLevel(
+ document.getPermissionId(), document.getLibraryId(), userId);
+ Long creatorUserId = NumberUtils.parseLong(document.getCreator());
+ PmsKnowledgeDocumentRespVO documentVO = BeanUtils.toBean(document, PmsKnowledgeDocumentRespVO.class)
+ .setCreatorUserId(creatorUserId).setCurrentUserLevel(currentUserLevel)
+ .setDownloadStatus(PmsKnowledgeContentLevelEnum.canDownload(currentUserLevel))
+ .setPreviewUrl(document.getContent())
+ .setFavoriteStatus(favoriteService.isFavorite(document.getType(), document.getId(), userId));
+ // 1.2 文件不可下载时,隐藏实际文件地址
+ if (PmsKnowledgeDocumentTypeEnum.FILE.getType().equals(document.getType())
+ && Boolean.FALSE.equals(documentVO.getDownloadStatus())) {
+ documentVO.setContent(null);
+ }
+
+ // 2. 一次查询并拼接创建人和点赞用户信息
+ List likes = documentLikeService.getDocumentLikeList(document.getId());
+ Set relatedUserIds = convertSet(likes, PmsKnowledgeDocumentLikeDO::getUserId);
+ relatedUserIds.add(creatorUserId);
+ Map userMap = adminUserApi.getUserMap(relatedUserIds);
+ findAndThen(userMap, creatorUserId, user -> documentVO.setCreatorUserName(user.getNickname()));
+ documentVO.setLikeStatus(CollUtil.anyMatch(likes, like -> userId.equals(like.getUserId())))
+ .setLikeUsers(convertList(likes, like -> {
+ PmsKnowledgeDocumentRespVO.LikeUser likeUser = new PmsKnowledgeDocumentRespVO.LikeUser()
+ .setId(like.getUserId());
+ findAndThen(userMap, like.getUserId(), user ->
+ likeUser.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
+ return likeUser;
+ }));
+ return documentVO;
+ }
+
+ private List buildDocumentRespVOList(List documents) {
+ if (CollUtil.isEmpty(documents)) {
+ return Collections.emptyList();
+ }
+ Map libraryMap = libraryService.getLibraryMap(
+ convertSet(documents, PmsKnowledgeDocumentDO::getLibraryId));
+ Map userMap = adminUserApi.getUserMap(convertSet(documents,
+ document -> NumberUtils.parseLong(document.getCreator())));
+ return convertList(documents, document -> {
+ PmsKnowledgeDocumentRespVO documentVO = BeanUtils.toBean(document, PmsKnowledgeDocumentRespVO.class)
+ .setContent(null).setContentSummary(buildContentSummary(document.getContent()))
+ .setCreatorUserId(NumberUtils.parseLong(document.getCreator()));
+ findAndThen(libraryMap, document.getLibraryId(),
+ library -> documentVO.setLibraryName(library.getName()));
+ findAndThen(userMap, documentVO.getCreatorUserId(),
+ user -> documentVO.setCreatorUserName(user.getNickname()));
+ return documentVO;
+ });
+ }
+
+ /**
+ * 构造搜索结果摘要,避免直接返回完整正文
+ */
+ private String buildContentSummary(String content) {
+ if (content == null || content.trim().isEmpty()) {
+ return null;
+ }
+ String summary = content.replaceAll("<[^>]+>", " ").replaceAll("\\s+", " ").trim();
+ return summary.length() > 120 ? summary.substring(0, 120) + "…" : summary;
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeDocumentLabelController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeDocumentLabelController.java
new file mode 100644
index 000000000..98452d826
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeDocumentLabelController.java
@@ -0,0 +1,132 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label.PmsKnowledgeDocumentLabelPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label.PmsKnowledgeDocumentLabelRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label.PmsKnowledgeDocumentLabelSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeDocumentDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryDO;
+import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeDocumentLabelService;
+import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识库文档标签")
+@RestController
+@RequestMapping("/pms/kb/document-label")
+@Validated
+public class PmsKnowledgeDocumentLabelController {
+
+ @Resource
+ private PmsKnowledgeDocumentLabelService documentLabelService;
+ @Resource
+ private PmsKnowledgeLibraryService libraryService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建文档标签")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult createDocumentLabel(
+ @Valid @RequestBody PmsKnowledgeDocumentLabelSaveReqVO saveReqVO) {
+ return success(documentLabelService.createDocumentLabel(saveReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改文档标签")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult updateDocumentLabel(
+ @Valid @RequestBody PmsKnowledgeDocumentLabelSaveReqVO saveReqVO) {
+ documentLabelService.updateDocumentLabel(saveReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除文档标签")
+ @Parameter(name = "id", description = "文档标签编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult deleteDocumentLabel(@RequestParam("id") Long id) {
+ documentLabelService.deleteDocumentLabel(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得文档标签详情")
+ @Parameter(name = "id", description = "文档标签编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult getDocumentLabel(@RequestParam("id") Long id) {
+ return success(BeanUtils.toBean(documentLabelService.getDocumentLabel(id),
+ PmsKnowledgeDocumentLabelRespVO.class));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得文档标签列表")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult> getDocumentLabelList() {
+ return success(BeanUtils.toBean(documentLabelService.getDocumentLabelList(),
+ PmsKnowledgeDocumentLabelRespVO.class));
+ }
+
+ @GetMapping("/document-page")
+ @Operation(summary = "获得指定标签的文档分页")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult> getDocumentPageByLabel(
+ @Valid PmsKnowledgeDocumentLabelPageReqVO pageReqVO) {
+ PageResult pageResult = documentLabelService.getDocumentPageByLabel(
+ pageReqVO, getLoginUserId());
+ return success(new PageResult<>(buildDocumentRespVOList(pageResult.getList()), pageResult.getTotal()));
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private List buildDocumentRespVOList(List documents) {
+ if (CollUtil.isEmpty(documents)) {
+ return Collections.emptyList();
+ }
+ Map libraryMap = libraryService.getLibraryMap(
+ convertSet(documents, PmsKnowledgeDocumentDO::getLibraryId));
+ Map userMap = adminUserApi.getUserMap(convertSet(documents,
+ document -> NumberUtils.parseLong(document.getCreator())));
+ return convertList(documents, document -> {
+ PmsKnowledgeDocumentRespVO documentVO = BeanUtils.toBean(document, PmsKnowledgeDocumentRespVO.class)
+ .setContent(null).setCreatorUserId(NumberUtils.parseLong(document.getCreator()));
+ findAndThen(libraryMap, document.getLibraryId(),
+ library -> documentVO.setLibraryName(library.getName()));
+ findAndThen(userMap, documentVO.getCreatorUserId(),
+ user -> documentVO.setCreatorUserName(user.getNickname()));
+ return documentVO;
+ });
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeFolderController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeFolderController.java
new file mode 100644
index 000000000..693f667b7
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeFolderController.java
@@ -0,0 +1,189 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder.PmsKnowledgeFolderMoveReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder.PmsKnowledgeFolderRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder.PmsKnowledgeFolderSaveReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder.PmsKnowledgeTreeRespVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeDocumentDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeFolderDO;
+import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
+import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeContentPermissionService;
+import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeDocumentService;
+import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeFolderService;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeFavoriteService;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeViewRecordService;
+import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryMemberService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识库文件夹")
+@RestController
+@RequestMapping("/pms/kb/folder")
+@Validated
+public class PmsKnowledgeFolderController {
+
+ @Resource
+ private PmsKnowledgeFolderService folderService;
+ @Resource
+ private PmsKnowledgeDocumentService documentService;
+ @Resource
+ private PmsKnowledgeContentPermissionService contentPermissionService;
+ @Resource
+ private PmsKnowledgeLibraryMemberService libraryMemberService;
+ @Resource
+ private PmsKnowledgeFavoriteService favoriteService;
+ @Resource
+ private PmsKnowledgeViewRecordService viewRecordService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建文件夹")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult createFolder(@Valid @RequestBody PmsKnowledgeFolderSaveReqVO saveReqVO) {
+ return success(folderService.createFolder(saveReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新文件夹")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult updateFolder(@Valid @RequestBody PmsKnowledgeFolderSaveReqVO saveReqVO) {
+ folderService.updateFolder(saveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "将文件夹移入回收站")
+ @Parameter(name = "id", description = "文件夹编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult deleteFolder(@RequestParam("id") Long id) {
+ folderService.deleteFolder(id, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/move")
+ @Operation(summary = "移动文件夹")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult moveFolder(@Valid @RequestBody PmsKnowledgeFolderMoveReqVO moveReqVO) {
+ folderService.moveFolder(moveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得文件夹详情")
+ @Parameter(name = "id", description = "文件夹编号", required = true, example = "1024")
+ @Parameter(name = "view", description = "是否记录本次浏览", example = "true")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult getFolder(
+ @RequestParam("id") Long id,
+ @RequestParam(value = "view", defaultValue = "false") Boolean view) {
+ Long userId = getLoginUserId();
+ PmsKnowledgeFolderDO folder = folderService.getFolder(id, userId);
+ if (Boolean.TRUE.equals(view)) {
+ viewRecordService.createViewRecord(folder.getLibraryId(), PmsKnowledgeObjectTypeEnum.FOLDER.getType(),
+ folder.getId(), userId);
+ }
+ return success(buildFolderRespVO(folder, userId));
+ }
+
+ @GetMapping("/tree")
+ @Operation(summary = "获得知识库目录树")
+ @Parameter(name = "libraryId", description = "知识库编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult getFolderTree(@RequestParam("libraryId") Long libraryId) {
+ Long userId = getLoginUserId();
+ List folders = folderService.getFolderList(libraryId, userId);
+ List documents = documentService.getDocumentList(libraryId, userId);
+ return success(buildTreeRespVO(libraryId, userId,
+ libraryMemberService.isLibraryWritable(libraryId, userId),
+ libraryMemberService.isLibraryAdmin(libraryId, userId), folders, documents));
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private PmsKnowledgeFolderRespVO buildFolderRespVO(PmsKnowledgeFolderDO folder, Long userId) {
+ List folders = folderService.getFolderList(folder.getLibraryId(), userId);
+ List documents = documentService.getDocumentList(folder.getLibraryId(), userId);
+ int childFolderCount = CollUtil.count(folders,
+ item -> folder.getId().equals(item.getParentId()));
+ int documentCount = (int) CollUtil.count(documents,
+ item -> folder.getId().equals(item.getFolderId())
+ && (item.getParentId() == null || item.getParentId() == 0L));
+ return BeanUtils.toBean(folder, PmsKnowledgeFolderRespVO.class)
+ .setChildFolderCount(childFolderCount).setDocumentCount(documentCount)
+ .setCurrentUserLevel(contentPermissionService.getCurrentUserContentPermissionLevel(folder.getPermissionId(), folder.getLibraryId(), userId))
+ .setFavoriteStatus(favoriteService.isFavorite(PmsKnowledgeObjectTypeEnum.FOLDER.getType(), folder.getId(), userId));
+ }
+
+ private PmsKnowledgeTreeRespVO buildTreeRespVO(Long libraryId, Long userId, boolean writeStatus,
+ boolean manageStatus,
+ List folders, List documents) {
+ // 1. 批量获得当前用户的内容权限等级
+ Set permissionIds = convertSet(folders, PmsKnowledgeFolderDO::getPermissionId);
+ permissionIds.addAll(convertSet(documents, PmsKnowledgeDocumentDO::getPermissionId));
+ Map levelMap = contentPermissionService.getCurrentUserContentPermissionLevelMap(permissionIds, libraryId, userId);
+
+ // 2. 批量转换文件夹和文档节点
+ Map folderNodeMap = convertMap(folders,
+ PmsKnowledgeFolderDO::getId, folder -> BeanUtils.toBean(folder, PmsKnowledgeTreeRespVO.FolderNode.class)
+ .setCurrentUserLevel(levelMap.get(folder.getPermissionId())).setChildren(new ArrayList<>()).setDocuments(new ArrayList<>()));
+ Map documentNodeMap = convertMap(documents,
+ PmsKnowledgeDocumentDO::getId, document -> BeanUtils.toBean(document, PmsKnowledgeTreeRespVO.DocumentNode.class)
+ .setCurrentUserLevel(levelMap.get(document.getPermissionId())).setChildren(new ArrayList<>()));
+
+ // 3. 按父文档或所属文件夹组装文档树
+ List rootDocuments = new ArrayList<>();
+ for (PmsKnowledgeDocumentDO document : documents) {
+ PmsKnowledgeTreeRespVO.DocumentNode node = documentNodeMap.get(document.getId());
+ PmsKnowledgeTreeRespVO.DocumentNode parentNode = documentNodeMap.get(document.getParentId());
+ PmsKnowledgeTreeRespVO.FolderNode folderNode = folderNodeMap.get(document.getFolderId());
+ if (parentNode != null) {
+ parentNode.getChildren().add(node);
+ } else if (folderNode != null) {
+ folderNode.getDocuments().add(node);
+ } else {
+ rootDocuments.add(node);
+ }
+ }
+
+ // 4. 按父文件夹组装文件夹树
+ List rootFolders = new ArrayList<>();
+ for (PmsKnowledgeFolderDO folder : folders) {
+ PmsKnowledgeTreeRespVO.FolderNode node = folderNodeMap.get(folder.getId());
+ PmsKnowledgeTreeRespVO.FolderNode parentNode = folderNodeMap.get(folder.getParentId());
+ if (parentNode != null) {
+ parentNode.getChildren().add(node);
+ } else {
+ rootFolders.add(node);
+ }
+ }
+ return new PmsKnowledgeTreeRespVO().setLibraryId(libraryId).setWriteStatus(writeStatus)
+ .setManageStatus(manageStatus)
+ .setFolders(rootFolders).setDocuments(rootDocuments);
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentCreateReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentCreateReqVO.java
new file mode 100644
index 000000000..e4ef99b5d
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentCreateReqVO.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeDocumentTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识库文档新增 Request VO")
+@Data
+public class PmsKnowledgeDocumentCreateReqVO {
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "知识库编号不能为空")
+ private Long libraryId;
+
+ @Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "文件夹编号不能为空")
+ private Long folderId;
+
+ @Schema(description = "父文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "父文档编号不能为空")
+ private Long parentId;
+
+ @Schema(description = "文档标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
+ @NotBlank(message = "文档标题不能为空")
+ @Size(max = 255, message = "文档标题不能超过 255 个字符")
+ private String title;
+
+ @Schema(description = "文档类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ @NotNull(message = "文档类型不能为空")
+ @InEnum(PmsKnowledgeDocumentTypeEnum.class)
+ private Integer type;
+
+ @Schema(description = "文档内容或文件地址")
+ private String content;
+
+ @Schema(description = "文件类型", example = "pdf")
+ @Size(max = 50, message = "文件类型不能超过 50 个字符")
+ private String fileType;
+
+ @Schema(description = "文件大小,单位:字节", example = "102400")
+ @Min(value = 0, message = "文件大小不能小于 0")
+ @Max(value = 104857600, message = "文件大小不能超过 100 MB")
+ private Long fileSize;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentMoveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentMoveReqVO.java
new file mode 100644
index 000000000..d5bc012ca
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentMoveReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识库文档移动 Request VO")
+@Data
+public class PmsKnowledgeDocumentMoveReqVO {
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "文档编号不能为空")
+ private Long id;
+
+ @Schema(description = "目标知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "目标知识库编号不能为空")
+ private Long targetLibraryId;
+
+ @Schema(description = "目标文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "目标文件夹编号不能为空")
+ private Long targetFolderId;
+
+ @Schema(description = "目标父文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "目标父文档编号不能为空")
+ private Long targetParentId;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentRespVO.java
new file mode 100644
index 000000000..d07fe31b5
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentRespVO.java
@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库文档 Response VO")
+@Data
+public class PmsKnowledgeDocumentRespVO {
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long libraryId;
+
+ @Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long permissionId;
+
+ @Schema(description = "当前用户协作等级", example = "3")
+ private Integer currentUserLevel;
+
+ @Schema(description = "当前用户是否可下载", example = "true")
+ private Boolean downloadStatus;
+
+ @Schema(description = "知识库名称", example = "产品知识库")
+ private String libraryName;
+
+ @Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long folderId;
+
+ @Schema(description = "父文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long parentId;
+
+ @Schema(description = "文档标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
+ private String title;
+
+ @Schema(description = "正文摘要")
+ private String contentSummary;
+
+ @Schema(description = "文档内容或文件地址")
+ private String content;
+
+ @Schema(description = "文件预览地址(下载权限不足时仍可用于在线预览)")
+ private String previewUrl;
+
+ @Schema(description = "文档类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer type;
+
+ @Schema(description = "文件类型", example = "pdf")
+ private String fileType;
+
+ @Schema(description = "文件大小,单位:字节", example = "102400")
+ private Long fileSize;
+
+ @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer status;
+
+ @Schema(description = "标签编号列表", example = "[1024, 2048]")
+ private List labelIds;
+
+ @Schema(description = "创建人用户编号", example = "1")
+ private Long creatorUserId;
+
+ @Schema(description = "创建人姓名", example = "芋道")
+ private String creatorUserName;
+
+ @Schema(description = "当前用户是否已关注")
+ private Boolean favoriteStatus;
+
+ @Schema(description = "当前用户是否已点赞")
+ private Boolean likeStatus;
+
+ @Schema(description = "点赞用户列表")
+ private List likeUsers;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime updateTime;
+
+ @Schema(description = "管理后台 - PMS 知识文档点赞用户 Response VO")
+ @Data
+ public static class LikeUser {
+
+ @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "用户昵称", example = "芋道")
+ private String nickname;
+
+ @Schema(description = "用户头像")
+ private String avatar;
+
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentSearchPageReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentSearchPageReqVO.java
new file mode 100644
index 000000000..07d9b8bbc
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentSearchPageReqVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - PMS 知识库文档全局搜索分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PmsKnowledgeDocumentSearchPageReqVO extends PageParam {
+
+ @Schema(description = "搜索关键字", example = "产品方案")
+ private String keyword;
+
+ @Schema(description = "知识库编号", example = "1024")
+ private Long libraryId;
+
+ @Schema(description = "创建人用户编号", example = "1")
+ private Long creatorUserId;
+
+ @Schema(description = "更新时间范围")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] updateTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentUpdateReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentUpdateReqVO.java
new file mode 100644
index 000000000..7afdd5fa3
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentUpdateReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库文档修改 Request VO")
+@Data
+public class PmsKnowledgeDocumentUpdateReqVO {
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "文档编号不能为空")
+ private Long id;
+
+ @Schema(description = "文档标题", example = "产品说明")
+ @Size(max = 255, message = "文档标题不能超过 255 个字符")
+ private String title;
+
+ @Schema(description = "文档内容或文件地址")
+ private String content;
+
+ @Schema(description = "标签编号列表;传空集合表示清空标签")
+ private List labelIds;
+
+ @Schema(description = "文件类型", example = "pdf")
+ @Size(max = 50, message = "文件类型不能超过 50 个字符")
+ private String fileType;
+
+ @Schema(description = "文件大小,单位:字节", example = "102400")
+ @Min(value = 0, message = "文件大小不能小于 0")
+ @Max(value = 104857600, message = "文件大小不能超过 100 MB")
+ private Long fileSize;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderMoveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderMoveReqVO.java
new file mode 100644
index 000000000..c30e02a87
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderMoveReqVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识库文件夹移动 Request VO")
+@Data
+public class PmsKnowledgeFolderMoveReqVO {
+
+ @Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "文件夹编号不能为空")
+ private Long id;
+
+ @Schema(description = "目标知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "目标知识库编号不能为空")
+ private Long targetLibraryId;
+
+ @Schema(description = "目标父文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "目标父文件夹编号不能为空")
+ private Long targetParentId;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderRespVO.java
new file mode 100644
index 000000000..86bf50e83
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderRespVO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - PMS 知识库文件夹 Response VO")
+@Data
+public class PmsKnowledgeFolderRespVO {
+
+ @Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long libraryId;
+
+ @Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long permissionId;
+
+ @Schema(description = "当前用户协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer currentUserLevel;
+
+ @Schema(description = "父文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long parentId;
+
+ @Schema(description = "文件夹标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品文档")
+ private String title;
+
+ @Schema(description = "直属子文件夹数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer childFolderCount;
+
+ @Schema(description = "直属文档数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
+ private Integer documentCount;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer status;
+
+ @Schema(description = "当前用户是否已关注", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean favoriteStatus;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime updateTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderSaveReqVO.java
new file mode 100644
index 000000000..5e3e81957
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderSaveReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识库文件夹新增/修改 Request VO")
+@Data
+public class PmsKnowledgeFolderSaveReqVO {
+
+ @Schema(description = "文件夹编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "知识库编号不能为空")
+ private Long libraryId;
+
+ @Schema(description = "父文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "父文件夹编号不能为空")
+ private Long parentId;
+
+ @Schema(description = "文件夹标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品文档")
+ @NotBlank(message = "文件夹标题不能为空")
+ @Size(max = 255, message = "文件夹标题不能超过 255 个字符")
+ private String title;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeTreeRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeTreeRespVO.java
new file mode 100644
index 000000000..9c3a9b207
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeTreeRespVO.java
@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库目录树 Response VO")
+@Data
+public class PmsKnowledgeTreeRespVO {
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long libraryId;
+
+ @Schema(description = "当前用户是否可编辑", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean writeStatus;
+
+ @Schema(description = "当前用户是否可管理目录结构", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean manageStatus;
+
+ @Schema(description = "根文件夹列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List folders;
+
+ @Schema(description = "根文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List documents;
+
+ @Schema(description = "管理后台 - PMS 知识库文件夹树节点 Response VO")
+ @Data
+ public static class FolderNode {
+
+ @Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long permissionId;
+
+ @Schema(description = "当前用户协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer currentUserLevel;
+
+ @Schema(description = "父文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long parentId;
+
+ @Schema(description = "文件夹标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品文档")
+ private String title;
+
+ @Schema(description = "子文件夹列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List children;
+
+ @Schema(description = "文件夹下的文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List documents;
+ }
+
+ @Schema(description = "管理后台 - PMS 知识库文档树节点 Response VO")
+ @Data
+ public static class DocumentNode {
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long permissionId;
+
+ @Schema(description = "当前用户协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer currentUserLevel;
+
+ @Schema(description = "父文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long parentId;
+
+ @Schema(description = "文档标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
+ private String title;
+
+ @Schema(description = "文档类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer type;
+
+ @Schema(description = "文件类型", example = "pdf")
+ private String fileType;
+
+ @Schema(description = "子文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List children;
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelPageReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelPageReqVO.java
new file mode 100644
index 000000000..e675209cb
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelPageReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - PMS 按标签查询文档分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PmsKnowledgeDocumentLabelPageReqVO extends PageParam {
+
+ @Schema(description = "文档标签编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "文档标签编号不能为空")
+ private Long labelId;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelRespVO.java
new file mode 100644
index 000000000..be6ddd1cd
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelRespVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - PMS 知识库文档标签 Response VO")
+@Data
+public class PmsKnowledgeDocumentLabelRespVO {
+
+ @Schema(description = "文档标签编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "标签名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品")
+ private String name;
+
+ @Schema(description = "标签颜色", requiredMode = Schema.RequiredMode.REQUIRED, example = "#409EFF")
+ private String color;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelSaveReqVO.java
new file mode 100644
index 000000000..1f77d0e68
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelSaveReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识库文档标签新增/修改 Request VO")
+@Data
+public class PmsKnowledgeDocumentLabelSaveReqVO {
+
+ @Schema(description = "文档标签编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "标签名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品")
+ @NotBlank(message = "标签名称不能为空")
+ @Size(max = 255, message = "标签名称不能超过 255 个字符")
+ private String name;
+
+ @Schema(description = "标签颜色", requiredMode = Schema.RequiredMode.REQUIRED, example = "#409EFF")
+ @NotBlank(message = "标签颜色不能为空")
+ @Size(max = 20, message = "标签颜色不能超过 20 个字符")
+ private String color;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/permission/PmsKnowledgeContentPermissionRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/permission/PmsKnowledgeContentPermissionRespVO.java
new file mode 100644
index 000000000..c06b94cc4
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/permission/PmsKnowledgeContentPermissionRespVO.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.permission;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识内容协作权限 Response VO")
+@Data
+public class PmsKnowledgeContentPermissionRespVO {
+
+ @Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long libraryId;
+
+ @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean openStatus;
+
+ @Schema(description = "公开协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer openLevel;
+
+ @Schema(description = "创建人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long creatorUserId;
+
+ @Schema(description = "当前用户协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer currentUserLevel;
+
+ @Schema(description = "协作者列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List members;
+
+ @Schema(description = "管理后台 - PMS 知识内容协作者 Response VO")
+ @Data
+ public static class Member {
+
+ @Schema(description = "协作者编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "用户编号", example = "1")
+ private Long userId;
+
+ @Schema(description = "用户昵称", example = "芋道")
+ private String userName;
+
+ @Schema(description = "部门编号", example = "100")
+ private Long deptId;
+
+ @Schema(description = "部门名称", example = "研发部")
+ private String deptName;
+
+ @Schema(description = "协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer level;
+
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/permission/PmsKnowledgeContentPermissionUpdateReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/permission/PmsKnowledgeContentPermissionUpdateReqVO.java
new file mode 100644
index 000000000..3ccedbdf3
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/permission/PmsKnowledgeContentPermissionUpdateReqVO.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.permission;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeContentLevelEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.AssertTrue;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 更新知识内容协作权限 Request VO")
+@Data
+public class PmsKnowledgeContentPermissionUpdateReqVO {
+
+ @Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "协作权限编号不能为空")
+ private Long id;
+
+ @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "是否公开不能为空")
+ private Boolean openStatus;
+
+ @Schema(description = "公开协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ @NotNull(message = "公开协作等级不能为空")
+ @InEnum(PmsKnowledgeContentLevelEnum.class)
+ private Integer openLevel;
+
+ @Schema(description = "协作者列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotNull(message = "协作者列表不能为空")
+ private List members;
+
+ @Schema(description = "管理后台 - PMS 知识内容协作者保存项")
+ @Data
+ public static class Member {
+
+ @Schema(description = "用户编号", example = "1")
+ private Long userId;
+
+ @Schema(description = "部门编号", example = "100")
+ private Long deptId;
+
+ @Schema(description = "协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ @NotNull(message = "协作等级不能为空")
+ @InEnum(PmsKnowledgeContentLevelEnum.class)
+ private Integer level;
+
+ @AssertTrue(message = "协作者必须且只能选择用户或部门")
+ @JsonIgnore
+ public boolean isUserOrDeptValid() {
+ return (userId != null && deptId == null) || (userId == null && deptId != null);
+ }
+
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentCommentController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentCommentController.java
new file mode 100644
index 000000000..235e07cf7
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentCommentController.java
@@ -0,0 +1,112 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.comment.PmsKnowledgeDocumentCommentRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.comment.PmsKnowledgeDocumentCommentSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.interaction.PmsKnowledgeDocumentCommentDO;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeDocumentCommentService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识库文档评论")
+@RestController
+@RequestMapping("/pms/kb/document-comment")
+@Validated
+public class PmsKnowledgeDocumentCommentController {
+
+ @Resource
+ private PmsKnowledgeDocumentCommentService commentService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建知识库文档评论")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult createDocumentComment(@Valid @RequestBody PmsKnowledgeDocumentCommentSaveReqVO saveReqVO) {
+ return success(commentService.createDocumentComment(saveReqVO, getLoginUserId()));
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除知识库文档评论")
+ @Parameter(name = "id", description = "评论编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult deleteDocumentComment(@RequestParam("id") Long id) {
+ commentService.deleteDocumentComment(id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得知识库文档评论列表")
+ @Parameter(name = "documentId", description = "文档编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult> getDocumentCommentList(
+ @RequestParam("documentId") Long documentId) {
+ List comments = commentService.getDocumentCommentList(documentId, getLoginUserId());
+ return success(buildCommentRespVOList(comments));
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private List buildCommentRespVOList(
+ List comments) {
+ if (CollUtil.isEmpty(comments)) {
+ return Collections.emptyList();
+ }
+ // 1. 批量查询评论人和回复对象
+ Map userMap = adminUserApi.getUserMap(convertSetByFlatMap(comments,
+ comment -> Stream.of(comment.getUserId(), comment.getReplyUserId())));
+
+ // 2. 批量转换评论,避免回复组装依赖查询顺序
+ Map commentMap = convertMap(comments,
+ PmsKnowledgeDocumentCommentDO::getId, comment -> {
+ PmsKnowledgeDocumentCommentRespVO commentVO = BeanUtils.toBean(comment,
+ PmsKnowledgeDocumentCommentRespVO.class).setChildren(new ArrayList<>());
+ findAndThen(userMap, comment.getUserId(), user -> commentVO.setUserName(user.getNickname()));
+ findAndThen(userMap, comment.getReplyUserId(),
+ user -> commentVO.setReplyUserName(user.getNickname()));
+ return commentVO;
+ });
+
+ // 3. 按主评论组织回复列表
+ List result = new ArrayList<>();
+ for (PmsKnowledgeDocumentCommentDO comment : comments) {
+ PmsKnowledgeDocumentCommentRespVO commentVO = commentMap.get(comment.getId());
+ if (PmsKnowledgeDocumentCommentDO.MAIN_ID_ROOT.equals(comment.getMainId())) {
+ result.add(commentVO);
+ continue;
+ }
+ findAndThen(commentMap, comment.getMainId(), mainComment -> mainComment.getChildren().add(commentVO));
+ }
+ return result;
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentLikeController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentLikeController.java
new file mode 100644
index 000000000..98cd56568
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentLikeController.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeDocumentLikeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识库文档点赞")
+@RestController
+@RequestMapping("/pms/kb/document-like")
+@Validated
+public class PmsKnowledgeDocumentLikeController {
+
+ @Resource
+ private PmsKnowledgeDocumentLikeService documentLikeService;
+
+ @PostMapping("/create")
+ @Operation(summary = "点赞文档")
+ @Parameter(name = "documentId", description = "文档编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult createDocumentLike(@RequestParam("documentId") Long documentId) {
+ documentLikeService.createDocumentLike(documentId, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "取消点赞文档")
+ @Parameter(name = "documentId", description = "文档编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult deleteDocumentLike(@RequestParam("documentId") Long documentId) {
+ documentLikeService.deleteDocumentLike(documentId, getLoginUserId());
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentShareController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentShareController.java
new file mode 100644
index 000000000..361c81d29
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentShareController.java
@@ -0,0 +1,101 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.module.infra.api.file.FileApi;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share.PmsKnowledgeDocumentSharePublicRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share.PmsKnowledgeDocumentShareRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share.PmsKnowledgeDocumentShareSaveReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share.PmsKnowledgeDocumentShareUpdateMemberListReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeDocumentDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.interaction.PmsKnowledgeDocumentShareDO;
+import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeDocumentTypeEnum;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeDocumentShareService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.annotation.security.PermitAll;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识库文档分享")
+@RestController
+@RequestMapping("/pms/kb/document-share")
+@Validated
+public class PmsKnowledgeDocumentShareController {
+
+ @Resource
+ private PmsKnowledgeDocumentShareService documentShareService;
+
+ @Resource
+ private FileApi fileApi;
+
+ @PostMapping("/open")
+ @Operation(summary = "开启文档分享")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult openShare(
+ @Valid @RequestBody PmsKnowledgeDocumentShareSaveReqVO reqVO) {
+ PmsKnowledgeDocumentShareDO share = documentShareService.openShare(reqVO.getDocumentId(),
+ reqVO.getShareUserIds(), getLoginUserId());
+ return success(BeanUtils.toBean(share, PmsKnowledgeDocumentShareRespVO.class));
+ }
+
+ @PutMapping("/update-member-list")
+ @Operation(summary = "更新文档内部分享成员")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult updateShareMemberList(
+ @Valid @RequestBody PmsKnowledgeDocumentShareUpdateMemberListReqVO reqVO) {
+ documentShareService.updateShareMemberList(reqVO.getDocumentId(), reqVO.getShareUserIds(), getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/close")
+ @Operation(summary = "关闭文档分享")
+ @Parameter(name = "documentId", description = "文档编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult closeShare(@RequestParam("documentId") Long documentId) {
+ documentShareService.closeShare(documentId, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得文档分享配置")
+ @Parameter(name = "documentId", description = "文档编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult getDocumentShare(@RequestParam("documentId") Long documentId) {
+ PmsKnowledgeDocumentShareDO share = documentShareService.getActiveDocumentShare(documentId, getLoginUserId());
+ return success(BeanUtils.toBean(share, PmsKnowledgeDocumentShareRespVO.class));
+ }
+
+ @GetMapping("/get-by-token")
+ @Operation(summary = "通过分享令牌获得文档")
+ @Parameter(name = "token", description = "分享令牌", required = true)
+ @PermitAll
+ @TenantIgnore
+ public CommonResult getDocumentByToken(
+ @RequestParam("token") @NotBlank(message = "分享令牌不能为空") String token) {
+ PmsKnowledgeDocumentDO document = documentShareService.getDocumentByShareToken(token);
+ PmsKnowledgeDocumentSharePublicRespVO documentVO = BeanUtils.toBean(
+ document, PmsKnowledgeDocumentSharePublicRespVO.class);
+ if (PmsKnowledgeDocumentTypeEnum.FILE.getType().equals(document.getType())) {
+ documentVO.setContent(null).setPreviewUrl(
+ fileApi.presignGetUrl(document.getContent(), null).getCheckedData());
+ }
+ return success(documentVO);
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeFavoriteController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeFavoriteController.java
new file mode 100644
index 000000000..0439bb2aa
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeFavoriteController.java
@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.PmsKnowledgeInteractionItemRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.favorite.PmsKnowledgeFavoritePageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.favorite.PmsKnowledgeFavoriteSaveReqVO;
+import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeFavoriteService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识关注")
+@RestController
+@RequestMapping("/pms/kb/favorite")
+@Validated
+public class PmsKnowledgeFavoriteController {
+
+ @Resource
+ private PmsKnowledgeFavoriteService favoriteService;
+
+ @PostMapping("/create")
+ @Operation(summary = "关注知识对象")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult createFavorite(@Valid @RequestBody PmsKnowledgeFavoriteSaveReqVO saveReqVO) {
+ favoriteService.createFavorite(saveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "取消关注知识对象")
+ @Parameter(name = "type", description = "对象类型", required = true, example = "3")
+ @Parameter(name = "entityId", description = "对象编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult deleteFavorite(
+ @RequestParam("type") @InEnum(PmsKnowledgeObjectTypeEnum.class) Integer type,
+ @RequestParam("entityId") Long entityId) {
+ favoriteService.deleteFavorite(type, entityId, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得我的关注分页")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult> getFavoritePage(
+ @Valid PmsKnowledgeFavoritePageReqVO pageReqVO) {
+ return success(favoriteService.getFavoritePage(pageReqVO, getLoginUserId()));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得知识库内我的关注列表")
+ @Parameter(name = "libraryId", description = "知识库编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult> getFavoriteListByLibraryId(
+ @RequestParam("libraryId") Long libraryId) {
+ return success(favoriteService.getFavoriteListByLibraryId(libraryId, getLoginUserId()));
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeViewRecordController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeViewRecordController.java
new file mode 100644
index 000000000..16fc9b3ac
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeViewRecordController.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.viewrecord.PmsKnowledgeRecentListRespVO;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeViewRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识最近浏览")
+@RestController
+@RequestMapping("/pms/kb/view-record")
+@Validated
+public class PmsKnowledgeViewRecordController {
+
+ @Resource
+ private PmsKnowledgeViewRecordService viewRecordService;
+
+ @GetMapping("/recent-list")
+ @Operation(summary = "获得最近浏览列表")
+ @Parameter(name = "libraryId", description = "知识库编号;不传时查询全部可读知识库", example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult getRecentViewRecordList(
+ @RequestParam(value = "libraryId", required = false) Long libraryId) {
+ return success(viewRecordService.getRecentViewRecordList(libraryId, getLoginUserId()));
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/PmsKnowledgeInteractionItemRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/PmsKnowledgeInteractionItemRespVO.java
new file mode 100644
index 000000000..ac61ad402
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/PmsKnowledgeInteractionItemRespVO.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - PMS 知识交互对象 Response VO")
+@Data
+public class PmsKnowledgeInteractionItemRespVO {
+
+ @Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "对象类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer type;
+
+ @Schema(description = "对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long entityId;
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long libraryId;
+
+ @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品知识库")
+ private String libraryName;
+
+ @Schema(description = "对象名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
+ private String name;
+
+ @Schema(description = "知识库简介")
+ private String description;
+
+ @Schema(description = "文件夹编号", example = "1024")
+ private Long folderId;
+
+ @Schema(description = "文档编号", example = "1024")
+ private Long documentId;
+
+ @Schema(description = "文件类型", example = "pdf")
+ private String fileType;
+
+ @Schema(description = "文件大小,单位:字节")
+ private Long fileSize;
+
+ @Schema(description = "对象更新时间")
+ private LocalDateTime targetUpdateTime;
+
+ @Schema(description = "记录时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/comment/PmsKnowledgeDocumentCommentRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/comment/PmsKnowledgeDocumentCommentRespVO.java
new file mode 100644
index 000000000..fb0c4f6e4
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/comment/PmsKnowledgeDocumentCommentRespVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.comment;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库文档评论 Response VO")
+@Data
+public class PmsKnowledgeDocumentCommentRespVO {
+
+ @Schema(description = "评论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long documentId;
+
+ @Schema(description = "评论人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long userId;
+
+ @Schema(description = "评论人昵称", example = "芋道")
+ private String userName;
+
+ @Schema(description = "主评论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long mainId;
+
+ @Schema(description = "回复对象用户编号", example = "2")
+ private Long replyUserId;
+
+ @Schema(description = "回复对象昵称", example = "源码")
+ private String replyUserName;
+
+ @Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String content;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ @Schema(description = "回复列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List children;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/comment/PmsKnowledgeDocumentCommentSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/comment/PmsKnowledgeDocumentCommentSaveReqVO.java
new file mode 100644
index 000000000..efbed8db2
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/comment/PmsKnowledgeDocumentCommentSaveReqVO.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.comment;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识库文档评论保存 Request VO")
+@Data
+public class PmsKnowledgeDocumentCommentSaveReqVO {
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "文档编号不能为空")
+ private Long documentId;
+
+ @Schema(description = "主评论编号", example = "0")
+ private Long mainId;
+
+ @Schema(description = "回复对象用户编号", example = "1")
+ private Long replyUserId;
+
+ @Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "文档内容很清楚")
+ @NotBlank(message = "评论内容不能为空")
+ @Size(max = 2000, message = "评论内容不能超过 2000 个字符")
+ private String content;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/favorite/PmsKnowledgeFavoritePageReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/favorite/PmsKnowledgeFavoritePageReqVO.java
new file mode 100644
index 000000000..a8bdf557b
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/favorite/PmsKnowledgeFavoritePageReqVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.favorite;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "管理后台 - PMS 知识收藏(关注)分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PmsKnowledgeFavoritePageReqVO extends PageParam {
+
+ @Schema(description = "对象类型", example = "3")
+ @InEnum(value = PmsKnowledgeObjectTypeEnum.class, message = "对象类型必须是 {value}")
+ private Integer type;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/favorite/PmsKnowledgeFavoriteSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/favorite/PmsKnowledgeFavoriteSaveReqVO.java
new file mode 100644
index 000000000..e77b3f251
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/favorite/PmsKnowledgeFavoriteSaveReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.favorite;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识收藏(关注)保存 Request VO")
+@Data
+public class PmsKnowledgeFavoriteSaveReqVO {
+
+ @Schema(description = "对象类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ @NotNull(message = "对象类型不能为空")
+ @InEnum(value = PmsKnowledgeObjectTypeEnum.class, message = "对象类型必须是 {value}")
+ private Integer type;
+
+ @Schema(description = "对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "对象编号不能为空")
+ private Long entityId;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentSharePublicRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentSharePublicRespVO.java
new file mode 100644
index 000000000..f93f2cf05
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentSharePublicRespVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "PMS 知识库文档公开分享 Response VO")
+@Data
+public class PmsKnowledgeDocumentSharePublicRespVO {
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "文档标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
+ private String title;
+
+ @Schema(description = "文档内容或文件地址")
+ private String content;
+
+ @Schema(description = "文件临时预览地址")
+ private String previewUrl;
+
+ @Schema(description = "文档类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer type;
+
+ @Schema(description = "文件类型", example = "pdf")
+ private String fileType;
+
+ @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime updateTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareRespVO.java
new file mode 100644
index 000000000..3c9fc5198
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareRespVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库文档分享 Response VO")
+@Data
+public class PmsKnowledgeDocumentShareRespVO {
+
+ @Schema(description = "分享编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long documentId;
+
+ @Schema(description = "内部分享成员用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List shareUserIds;
+
+ @Schema(description = "外部查看令牌", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String token;
+
+ @Schema(description = "分享状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer status;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ @Schema(description = "关闭人用户编号")
+ private Long closeUserId;
+
+ @Schema(description = "关闭时间")
+ private LocalDateTime closeTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareSaveReqVO.java
new file mode 100644
index 000000000..886e4a622
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareSaveReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库文档分享保存 Request VO")
+@Data
+public class PmsKnowledgeDocumentShareSaveReqVO {
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "文档编号不能为空")
+ private Long documentId;
+
+ @Schema(description = "内部分享成员用户编号列表")
+ private List<@NotNull(message = "分享成员用户编号不能为空") Long> shareUserIds;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareUpdateMemberListReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareUpdateMemberListReqVO.java
new file mode 100644
index 000000000..36f503568
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareUpdateMemberListReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库文档分享成员列表更新 Request VO")
+@Data
+public class PmsKnowledgeDocumentShareUpdateMemberListReqVO {
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "文档编号不能为空")
+ private Long documentId;
+
+ @Schema(description = "内部分享成员用户编号列表")
+ private List<@NotNull(message = "分享成员用户编号不能为空") Long> shareUserIds;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/viewrecord/PmsKnowledgeRecentListRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/viewrecord/PmsKnowledgeRecentListRespVO.java
new file mode 100644
index 000000000..b0c539fea
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/viewrecord/PmsKnowledgeRecentListRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.viewrecord;
+
+import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.PmsKnowledgeInteractionItemRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识最近浏览 Response VO")
+@Data
+public class PmsKnowledgeRecentListRespVO {
+
+ @Schema(description = "今天浏览列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List todayItems;
+
+ @Schema(description = "昨天浏览列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List yesterdayItems;
+
+ @Schema(description = "更早 30 天浏览列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List recent30DayItems;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeGroupController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeGroupController.java
new file mode 100644
index 000000000..c3b6424f0
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeGroupController.java
@@ -0,0 +1,106 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group.PmsKnowledgeGroupRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group.PmsKnowledgeGroupSaveReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group.PmsKnowledgeGroupSortReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group.PmsKnowledgeLibraryMoveGroupReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeGroupDO;
+import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeGroupService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识库分组")
+@RestController
+@RequestMapping("/pms/kb/group")
+@Validated
+public class PmsKnowledgeGroupController {
+
+ @Resource
+ private PmsKnowledgeGroupService knowledgeGroupService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建知识库分组")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:create')")
+ public CommonResult createGroup(@Valid @RequestBody PmsKnowledgeGroupSaveReqVO saveReqVO) {
+ return success(knowledgeGroupService.createGroup(saveReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改知识库分组")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult updateGroup(@Valid @RequestBody PmsKnowledgeGroupSaveReqVO saveReqVO) {
+ knowledgeGroupService.updateGroup(saveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/update-sort")
+ @Operation(summary = "修改知识库分组排序")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult updateGroupSort(
+ @Valid @RequestBody PmsKnowledgeGroupSortReqVO sortReqVO) {
+ knowledgeGroupService.updateGroupSort(sortReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除知识库分组")
+ @Parameter(name = "id", description = "知识库分组编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult deleteGroup(@RequestParam("id") Long id) {
+ knowledgeGroupService.deleteGroup(id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得知识库分组列表")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult> getGroupList() {
+ Long userId = getLoginUserId();
+ List groups = knowledgeGroupService.getGroupList(userId);
+ Map groupCountMap = knowledgeGroupService.getGroupLibraryCountMap(userId,
+ convertSet(groups, PmsKnowledgeGroupDO::getId));
+ return success(convertList(groups, group -> BeanUtils.toBean(group, PmsKnowledgeGroupRespVO.class)
+ .setLibraryCount(groupCountMap.getOrDefault(group.getId(), 0))));
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得知识库分组")
+ @Parameter(name = "id", description = "知识库分组编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult getGroup(@RequestParam("id") Long id) {
+ return success(BeanUtils.toBean(knowledgeGroupService.getGroup(id, getLoginUserId()),
+ PmsKnowledgeGroupRespVO.class));
+ }
+
+ @PutMapping("/move")
+ @Operation(summary = "移动知识库到个人分组")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult moveLibraryToGroup(
+ @Valid @RequestBody PmsKnowledgeLibraryMoveGroupReqVO moveReqVO) {
+ knowledgeGroupService.moveLibraryToGroup(moveReqVO, getLoginUserId());
+ return success(true);
+ }
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryController.java
new file mode 100644
index 000000000..2ef286af2
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryController.java
@@ -0,0 +1,159 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library.PmsKnowledgeLibraryPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library.PmsKnowledgeLibraryRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library.PmsKnowledgeLibrarySaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryMemberDO;
+import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
+import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeDocumentTypeEnum;
+import cn.iocoder.yudao.module.pms.enums.kb.library.PmsKnowledgeLibraryMemberLevelEnum;
+import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeDocumentService;
+import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeFavoriteService;
+import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryMemberService;
+import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryService;
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识库")
+@RestController
+@RequestMapping("/pms/kb/library")
+@Validated
+public class PmsKnowledgeLibraryController {
+
+ @Resource
+ private PmsKnowledgeLibraryService libraryService;
+ @Resource
+ private PmsKnowledgeLibraryMemberService memberService;
+ @Resource
+ private PmsKnowledgeDocumentService documentService;
+ @Resource
+ private PmsKnowledgeFavoriteService favoriteService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private PermissionApi permissionApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建知识库")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:create')")
+ public CommonResult createLibrary(@Valid @RequestBody PmsKnowledgeLibrarySaveReqVO saveReqVO) {
+ return success(libraryService.createLibrary(saveReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新知识库")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult updateLibrary(@Valid @RequestBody PmsKnowledgeLibrarySaveReqVO saveReqVO) {
+ libraryService.updateLibrary(saveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除知识库")
+ @Parameter(name = "id", description = "知识库编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult deleteLibrary(@RequestParam("id") Long id) {
+ libraryService.deleteLibrary(id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得知识库详情")
+ @Parameter(name = "id", description = "知识库编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult getLibrary(@RequestParam("id") Long id) {
+ Long userId = getLoginUserId();
+ PmsKnowledgeLibraryDO library = memberService.validateLibraryReadable(id, userId);
+ return success(CollUtil.getFirst(buildLibraryRespVOList(Collections.singletonList(library), userId)));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得知识库分页")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult> getLibraryPage(
+ @Valid PmsKnowledgeLibraryPageReqVO pageReqVO) {
+ Long userId = getLoginUserId();
+ PageResult pageResult = libraryService.getLibraryPage(pageReqVO, userId);
+ return success(new PageResult<>(buildLibraryRespVOList(pageResult.getList(), userId), pageResult.getTotal()));
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private List buildLibraryRespVOList(
+ List libraries, Long userId) {
+ if (CollUtil.isEmpty(libraries)) {
+ return Collections.emptyList();
+ }
+
+ // 1. 批量查询知识库成员、内容数量、创建人和当前用户权限信息
+ Set libraryIds = convertSet(libraries, PmsKnowledgeLibraryDO::getId);
+ Map> memberListMap = memberService.getLibraryMemberListMap(libraryIds);
+ Map> documentTypeCountMap = documentService.getDocumentTypeCountMap(libraryIds);
+ Set userIds = convertSet(libraries, library -> NumberUtils.parseLong(library.getCreator()));
+ Map userMap = adminUserApi.getUserMap(userIds);
+ AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData();
+ Long deptId = currentUser != null ? currentUser.getDeptId() : null;
+ boolean superAdmin = permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode()).getCheckedData();
+ Set favoriteLibraryIds = favoriteService.getFavoriteEntityIdSet(
+ PmsKnowledgeObjectTypeEnum.LIBRARY.getType(), libraryIds, userId);
+
+ // 2. 拼接每个知识库的成员权限、统计数量、收藏状态和创建人名称
+ return convertList(libraries, library -> {
+ List members = memberListMap.getOrDefault(library.getId(), Collections.emptyList());
+ PmsKnowledgeLibraryMemberDO directMember = CollUtil.findOne(members, member -> userId.equals(member.getUserId()));
+ PmsKnowledgeLibraryMemberDO currentMember = directMember;
+ if (currentMember == null && deptId != null) {
+ currentMember = CollUtil.findOne(members, member -> deptId.equals(member.getDeptId()));
+ }
+ boolean adminStatus = superAdmin || currentMember != null
+ && (PmsKnowledgeLibraryMemberLevelEnum.CREATOR.getLevel().equals(currentMember.getLevel())
+ || PmsKnowledgeLibraryMemberLevelEnum.ADMIN.getLevel().equals(currentMember.getLevel()));
+ PmsKnowledgeLibraryRespVO libraryVO = BeanUtils.toBean(library, PmsKnowledgeLibraryRespVO.class)
+ .setCreatorUserId(NumberUtils.parseLong(library.getCreator())).setMemberCount(members.size())
+ .setWriteStatus(superAdmin || currentMember != null).setAdminStatus(adminStatus)
+ .setExitStatus(directMember != null && !PmsKnowledgeLibraryMemberLevelEnum.CREATOR.getLevel().equals(directMember.getLevel()))
+ .setFavoriteStatus(favoriteLibraryIds.contains(library.getId()));
+ Map typeCountMap = documentTypeCountMap.getOrDefault(library.getId(), Collections.emptyMap());
+ libraryVO.setDocumentCount(typeCountMap.getOrDefault(PmsKnowledgeDocumentTypeEnum.RICH_TEXT.getType(), 0L))
+ .setFileCount(typeCountMap.getOrDefault(PmsKnowledgeDocumentTypeEnum.FILE.getType(), 0L));
+ findAndThen(userMap, libraryVO.getCreatorUserId(), user -> libraryVO.setCreatorUserName(user.getNickname()));
+ return libraryVO;
+ });
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryMemberController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryMemberController.java
new file mode 100644
index 000000000..8cf710a73
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryMemberController.java
@@ -0,0 +1,106 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.member.PmsKnowledgeLibraryMemberRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.member.PmsKnowledgeLibraryUpdateMemberListReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryMemberDO;
+import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryMemberService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识库成员")
+@RestController
+@RequestMapping("/pms/kb/library-member")
+@Validated
+public class PmsKnowledgeLibraryMemberController {
+
+ @Resource
+ private PmsKnowledgeLibraryMemberService memberService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private DeptApi deptApi;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得知识库成员列表")
+ @Parameter(name = "libraryId", description = "知识库编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult> getLibraryMemberList(@RequestParam("libraryId") Long libraryId) {
+ List members = memberService.getLibraryMemberList(libraryId, getLoginUserId());
+ return success(buildLibraryMemberRespVOList(members));
+ }
+
+ @PutMapping("/update-list")
+ @Operation(summary = "更新知识库成员")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
+ public CommonResult updateLibraryMemberList(@Valid @RequestBody PmsKnowledgeLibraryUpdateMemberListReqVO updateReqVO) {
+ memberService.updateLibraryMemberList(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/exit")
+ @Operation(summary = "主动退出知识库")
+ @Parameter(name = "libraryId", description = "知识库编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
+ public CommonResult exitLibrary(@RequestParam("libraryId") Long libraryId) {
+ memberService.exitLibrary(libraryId, getLoginUserId());
+ return success(true);
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private List buildLibraryMemberRespVOList(
+ List members) {
+ if (CollUtil.isEmpty(members)) {
+ return Collections.emptyList();
+ }
+
+ // 1. 批量查询用户和部门
+ Map userMap = adminUserApi.getUserMap(
+ convertSet(members, PmsKnowledgeLibraryMemberDO::getUserId));
+ Map deptMap = deptApi.getDeptMap(
+ convertSet(members, PmsKnowledgeLibraryMemberDO::getDeptId));
+ Map parentDeptMap = deptApi.getDeptMap(convertSet(deptMap.values(), DeptRespDTO::getParentId));
+ // 2. 转换成员响应,并拼接用户和部门名称
+ return convertList(members, member -> {
+ PmsKnowledgeLibraryMemberRespVO memberVO = BeanUtils.toBean(member, PmsKnowledgeLibraryMemberRespVO.class);
+ findAndThen(userMap, member.getUserId(), user ->
+ memberVO.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
+ findAndThen(deptMap, member.getDeptId(), dept -> {
+ memberVO.setDeptName(dept.getName()).setParentDeptId(dept.getParentId());
+ findAndThen(parentDeptMap, dept.getParentId(), parent -> memberVO.setParentDeptName(parent.getName()));
+ });
+ return memberVO;
+ });
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryTemplateController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryTemplateController.java
new file mode 100644
index 000000000..cf023f5e8
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryTemplateController.java
@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template.PmsKnowledgeLibraryTemplatePageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template.PmsKnowledgeLibraryTemplateRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template.PmsKnowledgeLibraryTemplateSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryTemplateDO;
+import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryTemplateService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+/**
+ * 管理后台 - PMS 知识库模板 Controller
+ *
+ * @author 芋道源码
+ */
+@Tag(name = "管理后台 - PMS 知识库模板")
+@RestController
+@RequestMapping("/pms/kb/library-template")
+@Validated
+public class PmsKnowledgeLibraryTemplateController {
+
+ @Resource
+ private PmsKnowledgeLibraryTemplateService libraryTemplateService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建知识库模板")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library-template:create')")
+ public CommonResult createLibraryTemplate(
+ @Valid @RequestBody PmsKnowledgeLibraryTemplateSaveReqVO createReqVO) {
+ return success(libraryTemplateService.createLibraryTemplate(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新知识库模板")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library-template:update')")
+ public CommonResult updateLibraryTemplate(
+ @Valid @RequestBody PmsKnowledgeLibraryTemplateSaveReqVO updateReqVO) {
+ libraryTemplateService.updateLibraryTemplate(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除知识库模板")
+ @Parameter(name = "id", description = "模板编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library-template:delete')")
+ public CommonResult deleteLibraryTemplate(@RequestParam("id") Long id) {
+ libraryTemplateService.deleteLibraryTemplate(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得知识库模板")
+ @Parameter(name = "id", description = "模板编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library-template:query')")
+ public CommonResult getLibraryTemplate(@RequestParam("id") Long id) {
+ PmsKnowledgeLibraryTemplateDO template = libraryTemplateService.getLibraryTemplate(id);
+ return success(BeanUtils.toBean(template, PmsKnowledgeLibraryTemplateRespVO.class));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获取知识库模板全列表", description = "只包含开启状态模板,返回模板选择所需的基本信息和文档标题")
+ public CommonResult> getSimpleLibraryTemplateList() {
+ return success(convertList(libraryTemplateService.getLibraryTemplateList(CommonStatusEnum.ENABLE.getStatus()),
+ template -> new PmsKnowledgeLibraryTemplateRespVO().setId(template.getId())
+ .setName(template.getName()).setCoverUrl(template.getCoverUrl()).setDescription(template.getDescription())
+ .setDocuments(convertList(template.getDocuments(), document ->
+ new PmsKnowledgeLibraryTemplateSaveReqVO.Document().setTitle(document.getTitle())))));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得知识库模板分页")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library-template:query')")
+ public CommonResult> getLibraryTemplatePage(
+ @Valid PmsKnowledgeLibraryTemplatePageReqVO pageReqVO) {
+ PageResult pageResult = libraryTemplateService
+ .getLibraryTemplatePage(pageReqVO);
+ return success(new PageResult<>(BeanUtils.toBean(pageResult.getList(), PmsKnowledgeLibraryTemplateRespVO.class),
+ pageResult.getTotal()));
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupRespVO.java
new file mode 100644
index 000000000..45e9c5f0a
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - PMS 知识库分组 Response VO")
+@Data
+public class PmsKnowledgeGroupRespVO {
+
+ @Schema(description = "知识库分组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品知识库")
+ private String name;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer sort;
+
+ @Schema(description = "分组类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer type;
+
+ @Schema(description = "分组内知识库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+ private Integer libraryCount;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupSaveReqVO.java
new file mode 100644
index 000000000..5c567bf38
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupSaveReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识库分组新增/修改 Request VO")
+@Data
+public class PmsKnowledgeGroupSaveReqVO {
+
+ @Schema(description = "知识库分组编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品知识库")
+ @NotBlank(message = "分组名称不能为空")
+ @Size(max = 100, message = "分组名称不能超过 100 个字符")
+ private String name;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupSortReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupSortReqVO.java
new file mode 100644
index 000000000..d44f561f1
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupSortReqVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库分组排序 Request VO")
+@Data
+public class PmsKnowledgeGroupSortReqVO {
+
+ @Schema(description = "知识库分组排序项", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "知识库分组排序项不能为空")
+ private List<@Valid Item> items;
+
+ @Schema(description = "管理后台 - PMS 知识库分组排序项 Request VO")
+ @Data
+ public static class Item {
+
+ @Schema(description = "知识库分组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "知识库分组编号不能为空")
+ private Long id;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "显示顺序不能为空")
+ @Min(value = 0, message = "显示顺序不能小于 0")
+ private Integer sort;
+
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeLibraryMoveGroupReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeLibraryMoveGroupReqVO.java
new file mode 100644
index 000000000..4581020b8
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeLibraryMoveGroupReqVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识库移动分组 Request VO")
+@Data
+public class PmsKnowledgeLibraryMoveGroupReqVO {
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "知识库编号不能为空")
+ private Long libraryId;
+
+ @Schema(description = "目标分组编号;为空表示移出分组", example = "2048")
+ private Long groupId;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibraryPageReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibraryPageReqVO.java
new file mode 100644
index 000000000..1de242347
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibraryPageReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - PMS 知识库分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PmsKnowledgeLibraryPageReqVO extends PageParam {
+
+ @Schema(description = "知识库名称", example = "产品")
+ private String name;
+
+ @Schema(description = "个人知识库分组编号", example = "1024")
+ private Long groupId;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibraryRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibraryRespVO.java
new file mode 100644
index 000000000..20e6d434e
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibraryRespVO.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - PMS 知识库 Response VO")
+@Data
+public class PmsKnowledgeLibraryRespVO {
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品知识库")
+ private String name;
+
+ @Schema(description = "知识库简介")
+ private String description;
+
+ @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean openStatus;
+
+ @Schema(description = "知识库封面")
+ private String coverUrl;
+
+ @Schema(description = "创建人用户编号", example = "1")
+ private Long creatorUserId;
+
+ @Schema(description = "创建人姓名", example = "芋道源码")
+ private String creatorUserName;
+
+ @Schema(description = "成员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+ private Integer memberCount;
+
+ @Schema(description = "文档数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
+ private Long documentCount;
+
+ @Schema(description = "文件数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
+ private Long fileCount;
+
+ @Schema(description = "当前用户是否可以编辑内容", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean writeStatus;
+
+ @Schema(description = "当前用户是否为知识库管理员", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean adminStatus;
+
+ @Schema(description = "当前用户是否可以主动退出知识库", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean exitStatus;
+
+ @Schema(description = "当前用户是否已关注", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean favoriteStatus;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibrarySaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibrarySaveReqVO.java
new file mode 100644
index 000000000..04daa730a
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibrarySaveReqVO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库新增/修改 Request VO")
+@Data
+public class PmsKnowledgeLibrarySaveReqVO {
+
+ @Schema(description = "知识库编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品知识库")
+ @NotBlank(message = "知识库名称不能为空")
+ @Size(max = 50, message = "知识库名称不能超过 50 个字符")
+ private String name;
+
+ @Schema(description = "知识库简介", example = "沉淀产品文档")
+ @Size(max = 300, message = "知识库简介不能超过 300 个字符")
+ private String description;
+
+ @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+ @NotNull(message = "知识库可见范围不能为空")
+ private Boolean openStatus;
+
+ @Schema(description = "知识库封面")
+ @Size(max = 255, message = "知识库封面地址不能超过 255 个字符")
+ private String coverUrl;
+
+ @Schema(description = "创建时的初始管理员用户编号列表", example = "[1, 2]")
+ private List adminUserIds;
+
+ @Schema(description = "创建时的初始普通成员用户编号列表", example = "[3, 4]")
+ private List memberUserIds;
+
+ @Schema(description = "模板编号,仅创建时有效", example = "1")
+ @Positive(message = "模板编号必须大于 0")
+ private Long templateId;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/member/PmsKnowledgeLibraryMemberRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/member/PmsKnowledgeLibraryMemberRespVO.java
new file mode 100644
index 000000000..3772f2789
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/member/PmsKnowledgeLibraryMemberRespVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.member;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识库成员 Response VO")
+@Data
+public class PmsKnowledgeLibraryMemberRespVO {
+
+ @Schema(description = "成员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "用户编号", example = "1")
+ private Long userId;
+
+ @Schema(description = "用户姓名", example = "芋道源码")
+ private String nickname;
+
+ @Schema(description = "用户头像")
+ private String avatar;
+
+ @Schema(description = "部门编号", example = "100")
+ private Long deptId;
+
+ @Schema(description = "部门名称", example = "研发部")
+ private String deptName;
+
+ @Schema(description = "父部门编号")
+ private Long parentDeptId;
+
+ @Schema(description = "父部门名称")
+ private String parentDeptName;
+
+ @Schema(description = "成员等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer level;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/member/PmsKnowledgeLibraryUpdateMemberListReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/member/PmsKnowledgeLibraryUpdateMemberListReqVO.java
new file mode 100644
index 000000000..849db7a25
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/member/PmsKnowledgeLibraryUpdateMemberListReqVO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.member;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.kb.library.PmsKnowledgeLibraryMemberLevelEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.AssertTrue;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 更新知识库成员列表 Request VO")
+@Data
+public class PmsKnowledgeLibraryUpdateMemberListReqVO {
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "知识库编号不能为空")
+ private Long libraryId;
+
+ @Schema(description = "成员列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotNull(message = "成员列表不能为空")
+ private List members;
+
+ @Schema(description = "管理后台 - PMS 知识库成员更新项")
+ @Data
+ public static class Member {
+
+ @Schema(description = "用户编号", example = "1")
+ private Long userId;
+
+ @Schema(description = "部门编号", example = "100")
+ private Long deptId;
+
+ @Schema(description = "成员等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ @NotNull(message = "成员等级不能为空")
+ @InEnum(PmsKnowledgeLibraryMemberLevelEnum.class)
+ private Integer level;
+
+ @AssertTrue(message = "知识库成员必须且只能选择用户或部门")
+ @JsonIgnore
+ public boolean isUserOrDeptValid() {
+ return (userId != null && deptId == null) || (userId == null && deptId != null);
+ }
+
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplatePageReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplatePageReqVO.java
new file mode 100644
index 000000000..1a1aecf17
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplatePageReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "管理后台 - PMS 知识库模板分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PmsKnowledgeLibraryTemplatePageReqVO extends PageParam {
+
+ @Schema(description = "模板名称,模糊匹配", example = "产品研发")
+ private String name;
+
+ @Schema(description = "模板状态", example = "0")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplateRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplateRespVO.java
new file mode 100644
index 000000000..f292aae76
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplateRespVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库模板 Response VO")
+@Data
+public class PmsKnowledgeLibraryTemplateRespVO {
+
+ @Schema(description = "模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品研发")
+ private String name;
+
+ @Schema(description = "模板简介", example = "提供完善的产品流程文档")
+ private String description;
+
+ @Schema(description = "模板封面地址", example = "/static/pms/kb/template/product.png")
+ private String coverUrl;
+
+ @Schema(description = "模板状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Integer sort;
+
+ @Schema(description = "模板文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List documents;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplateSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplateSaveReqVO.java
new file mode 100644
index 000000000..5dcf251de
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplateSaveReqVO.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库模板新增/修改 Request VO")
+@Data
+public class PmsKnowledgeLibraryTemplateSaveReqVO {
+
+ @Schema(description = "模板编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品研发")
+ @NotBlank(message = "模板名称不能为空")
+ @Size(max = 100, message = "模板名称不能超过 100 个字符")
+ private String name;
+
+ @Schema(description = "模板简介", example = "提供完善的产品流程文档")
+ @Size(max = 500, message = "模板简介不能超过 500 个字符")
+ private String description;
+
+ @Schema(description = "模板封面地址", example = "/static/pms/kb/template/product.png")
+ @Size(max = 255, message = "模板封面地址不能超过 255 个字符")
+ private String coverUrl;
+
+ @Schema(description = "模板状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "模板状态不能为空")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @NotNull(message = "显示顺序不能为空")
+ @PositiveOrZero(message = "显示顺序不能小于 0")
+ private Integer sort;
+
+ @Schema(description = "模板文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "模板文档不能为空")
+ @Valid
+ private List documents;
+
+ @Schema(description = "模板文档")
+ @Data
+ public static class Document {
+
+ @Schema(description = "文档标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品需求文档")
+ @NotBlank(message = "文档标题不能为空")
+ @Size(max = 255, message = "文档标题不能超过 255 个字符")
+ private String title;
+
+ @Schema(description = "文档内容", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotBlank(message = "文档内容不能为空")
+ private String content;
+
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/PmsKnowledgeRecycleController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/PmsKnowledgeRecycleController.java
new file mode 100644
index 000000000..7699700e7
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/PmsKnowledgeRecycleController.java
@@ -0,0 +1,125 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.recycle;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo.PmsKnowledgeRecycleDetailRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo.PmsKnowledgeRecyclePreviewRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo.PmsKnowledgeRecycleRespVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeDocumentDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.kb.recycle.PmsKnowledgeRecycleRecordDO;
+import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
+import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeDocumentService;
+import cn.iocoder.yudao.module.pms.service.kb.recycle.PmsKnowledgeRecycleService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 知识库回收站")
+@RestController
+@RequestMapping("/pms/kb/recycle")
+@Validated
+public class PmsKnowledgeRecycleController {
+
+ @Resource
+ private PmsKnowledgeRecycleService recycleService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @Resource
+ private PmsKnowledgeDocumentService documentService;
+
+ @GetMapping("/library-list")
+ @Operation(summary = "获得已删除知识库列表")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult> getLibraryRecycleList() {
+ List records = recycleService.getLibraryRecycleList(getLoginUserId());
+ return success(buildRecycleRespVOList(records));
+ }
+
+ @GetMapping("/content-list")
+ @Operation(summary = "获得知识库最近删除内容")
+ @Parameter(name = "libraryId", description = "知识库编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult> getContentRecycleList(
+ @RequestParam("libraryId") Long libraryId) {
+ List records = recycleService.getContentRecycleList(
+ libraryId, getLoginUserId());
+ return success(buildRecycleRespVOList(records));
+ }
+
+ @GetMapping("/content-detail")
+ @Operation(summary = "获得知识库最近删除详情")
+ @Parameter(name = "id", description = "回收站记录编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult getContentRecycleDetail(
+ @RequestParam("id") Long id) {
+ return success(recycleService.getContentRecycleDetail(id, getLoginUserId()));
+ }
+
+ @GetMapping("/content-preview")
+ @Operation(summary = "预览知识库最近删除内容")
+ @Parameter(name = "id", description = "回收站记录编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult getContentRecyclePreview(
+ @RequestParam("id") Long id,
+ @RequestParam(value = "entityId", required = false) Long entityId) {
+ PmsKnowledgeDocumentDO document = recycleService.getContentRecyclePreview(id, entityId, getLoginUserId());
+ return success(BeanUtils.toBean(document, PmsKnowledgeRecyclePreviewRespVO.class,
+ o -> o.setName(document.getTitle())));
+ }
+
+ @PutMapping("/restore")
+ @Operation(summary = "恢复回收站对象")
+ @Parameter(name = "id", description = "回收站记录编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult restoreContentRecycle(@RequestParam("id") Long id) {
+ recycleService.restoreContentRecycle(id, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/permanent-delete")
+ @Operation(summary = "彻底删除回收站对象")
+ @Parameter(name = "id", description = "回收站记录编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
+ public CommonResult deleteContentRecycle(@RequestParam("id") Long id) {
+ recycleService.deleteContentRecycle(id, getLoginUserId());
+ return success(true);
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private List buildRecycleRespVOList(List records) {
+ Map userMap = adminUserApi.getUserMap(
+ convertSet(records, PmsKnowledgeRecycleRecordDO::getDeleteUserId));
+ Map documentMap = documentService.getDocumentMap(convertSet(records, record ->
+ PmsKnowledgeObjectTypeEnum.FILE.getType().equals(record.getType()) ? record.getEntityId() : null));
+ return convertList(records, record -> {
+ PmsKnowledgeRecycleRespVO recordVO = BeanUtils.toBean(record, PmsKnowledgeRecycleRespVO.class);
+ findAndThen(documentMap, record.getEntityId(), document -> recordVO.setFileSize(document.getFileSize()));
+ findAndThen(userMap, record.getDeleteUserId(), user -> recordVO.setDeleteUserName(user.getNickname()));
+ return recordVO;
+ });
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecycleDetailRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecycleDetailRespVO.java
new file mode 100644
index 000000000..86f1f2553
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecycleDetailRespVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 知识库回收站详情 Response VO")
+@Data
+public class PmsKnowledgeRecycleDetailRespVO {
+
+ @Schema(description = "根回收站记录")
+ private PmsKnowledgeRecycleRespVO root;
+
+ @Schema(description = "级联删除内容")
+ private List- children;
+
+ @Data
+ public static class Item {
+
+ @Schema(description = "内容编号", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Long id;
+
+ @Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer type;
+
+ @Schema(description = "内容名称", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String name;
+
+ @Schema(description = "父文档或父文件夹编号")
+ private Long parentId;
+
+ @Schema(description = "所属文件夹编号")
+ private Long folderId;
+
+ @Schema(description = "删除时间")
+ private LocalDateTime deleteTime;
+
+ }
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecyclePreviewRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecyclePreviewRespVO.java
new file mode 100644
index 000000000..9f10dfc6d
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecyclePreviewRespVO.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 知识库回收站内容预览 Response VO")
+@Data
+public class PmsKnowledgeRecyclePreviewRespVO {
+
+ @Schema(description = "内容编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer type;
+
+ @Schema(description = "内容名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
+ private String name;
+
+ @Schema(description = "文档正文或文件地址")
+ private String content;
+
+ @Schema(description = "文件类型", example = "pdf")
+ private String fileType;
+
+ @Schema(description = "文件大小,单位:字节", example = "102400")
+ private Long fileSize;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecycleRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecycleRespVO.java
new file mode 100644
index 000000000..c561770b0
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecycleRespVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - PMS 知识库回收站记录 Response VO")
+@Data
+public class PmsKnowledgeRecycleRespVO {
+
+ @Schema(description = "回收站记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long libraryId;
+
+ @Schema(description = "对象类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer type;
+
+ @Schema(description = "对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+ private Long entityId;
+
+ @Schema(description = "对象名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
+ private String name;
+
+ @Schema(description = "文件大小,单位:字节")
+ private Long fileSize;
+
+ @Schema(description = "删除人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long deleteUserId;
+
+ @Schema(description = "删除人姓名", example = "芋道")
+ private String deleteUserName;
+
+ @Schema(description = "删除时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime deleteTime;
+
+ @Schema(description = "父对象编号,回收站详情中用于组装层级", example = "2048")
+ private Long parentId;
+
+ @Schema(description = "所属文件夹编号,回收站详情中用于组装层级", example = "2048")
+ private Long folderId;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/PmsIterationController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/PmsIterationController.java
new file mode 100644
index 000000000..06f215bcd
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/PmsIterationController.java
@@ -0,0 +1,152 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo.PmsIterationOverviewRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo.PmsIterationPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo.PmsIterationRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo.PmsIterationSaveReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo.PmsIterationStartReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.pm.iteration.PmsIterationDO;
+import cn.iocoder.yudao.module.pms.service.pm.iteration.PmsIterationService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 项目迭代")
+@RestController
+@RequestMapping("/pms/pm/iteration")
+@Validated
+public class PmsIterationController {
+
+ @Resource
+ private PmsIterationService iterationService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建项目迭代")
+ @PreAuthorize("@ss.hasPermission('pms:pm:iteration:create')")
+ public CommonResult createIteration(@Valid @RequestBody PmsIterationSaveReqVO saveReqVO) {
+ return success(iterationService.createIteration(saveReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新项目迭代")
+ @PreAuthorize("@ss.hasPermission('pms:pm:iteration:update')")
+ public CommonResult updateIteration(@Valid @RequestBody PmsIterationSaveReqVO saveReqVO) {
+ iterationService.updateIteration(saveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/start")
+ @Operation(summary = "开始项目迭代")
+ @PreAuthorize("@ss.hasPermission('pms:pm:iteration:update')")
+ public CommonResult startIteration(@Valid @RequestBody PmsIterationStartReqVO startReqVO) {
+ iterationService.startIteration(startReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/complete")
+ @Operation(summary = "完成项目迭代")
+ @Parameter(name = "id", description = "迭代编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:iteration:update')")
+ public CommonResult completeIteration(@RequestParam("id") Long id) {
+ iterationService.completeIteration(id, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除项目迭代")
+ @Parameter(name = "id", description = "迭代编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:iteration:delete')")
+ public CommonResult deleteIteration(@RequestParam("id") Long id) {
+ iterationService.deleteIteration(id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得项目迭代详情")
+ @Parameter(name = "id", description = "迭代编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:iteration:query')")
+ public CommonResult getIteration(@RequestParam("id") Long id) {
+ PmsIterationDO iteration = iterationService.getIteration(id, getLoginUserId());
+ PmsIterationRespVO iterationVO = buildIterationRespVO(iteration);
+ return success(iterationVO);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得项目迭代分页")
+ @PreAuthorize("@ss.hasPermission('pms:pm:iteration:query')")
+ public CommonResult> getIterationPage(
+ @Valid PmsIterationPageReqVO pageReqVO) {
+ PageResult pageResult = iterationService.getIterationPage(pageReqVO, getLoginUserId());
+ List iterationVOList = buildIterationRespVOList(pageResult.getList());
+ Map progressMap = iterationService.getIterationProgressMap(
+ convertSet(pageResult.getList(), PmsIterationDO::getId));
+ iterationVOList.forEach(iteration -> iteration.setProgress(progressMap.getOrDefault(iteration.getId(), 0)));
+ return success(new PageResult<>(iterationVOList, pageResult.getTotal()));
+ }
+
+ @GetMapping("/overview")
+ @Operation(summary = "获得迭代概览统计")
+ @Parameter(name = "id", description = "迭代编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:iteration:query')")
+ public CommonResult getIterationOverview(@RequestParam("id") Long id) {
+ PmsIterationOverviewRespVO overview = iterationService.getIterationOverview(id, getLoginUserId());
+ List activities = overview.getRecentActivities();
+ Map activityUserMap = adminUserApi.getUserMap(
+ convertSet(activities, PmsIterationOverviewRespVO.ActivityItem::getOperatorUserId));
+ activities.forEach(activity -> findAndThen(activityUserMap, activity.getOperatorUserId(),
+ user -> activity.setOperatorUserName(user.getNickname())));
+ return success(overview.setRecentActivities(activities));
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private PmsIterationRespVO buildIterationRespVO(PmsIterationDO iteration) {
+ return CollUtil.getFirst(buildIterationRespVOList(Collections.singletonList(iteration)));
+ }
+
+ private List buildIterationRespVOList(List iterations) {
+ if (CollUtil.isEmpty(iterations)) {
+ return Collections.emptyList();
+ }
+
+ // 1. 批量查询负责人
+ Map userMap = adminUserApi.getUserMap(
+ convertSet(iterations, PmsIterationDO::getOwnerUserId));
+
+ // 2. 转换迭代响应,并拼接负责人姓名
+ return BeanUtils.toBean(iterations, PmsIterationRespVO.class, iterationVO ->
+ findAndThen(userMap, iterationVO.getOwnerUserId(),
+ user -> iterationVO.setOwnerUserName(user.getNickname())));
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationOverviewRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationOverviewRespVO.java
new file mode 100644
index 000000000..98eacc198
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationOverviewRespVO.java
@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - PMS 迭代概览 Response VO")
+@Data
+public class PmsIterationOverviewRespVO {
+
+ @Schema(description = "工作项总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Integer totalCount;
+
+ @Schema(description = "未开始工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+ private Integer pendingCount;
+
+ @Schema(description = "进行中工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "30")
+ private Integer processingCount;
+
+ @Schema(description = "已完成工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
+ private Integer completedCount;
+
+ @Schema(description = "完成进度百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
+ private Integer progress;
+
+ @Schema(description = "工作项类型数量统计", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"1\": 60, \"2\": 40}")
+ private Map typeCountMap;
+
+ @Schema(description = "工作项类型和状态数量统计", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Map> typeStatusCountMap;
+
+ @Schema(description = "状态趋势", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List statusTrends;
+
+ @Schema(description = "燃尽图数据", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List burnDowns;
+
+ @Schema(description = "最近动态列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List recentActivities;
+
+ @Schema(description = "管理后台 - PMS 迭代概览状态趋势项 VO")
+ @Data
+ public static class TrendItem {
+
+ @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-01-01")
+ private String date;
+
+ @Schema(description = "当日未开始工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+ private Integer pendingCount;
+
+ @Schema(description = "当日进行中工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer processingCount;
+
+ @Schema(description = "当日已完成工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer completedCount;
+
+ }
+
+ @Schema(description = "管理后台 - PMS 迭代概览燃尽图项 VO")
+ @Data
+ public static class BurnDownItem {
+
+ @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-01-01")
+ private String date;
+
+ @Schema(description = "理想剩余工时,单位:小时", requiredMode = Schema.RequiredMode.REQUIRED, example = "40")
+ private Integer idealRemaining;
+
+ @Schema(description = "实际剩余工时,单位:小时", requiredMode = Schema.RequiredMode.REQUIRED, example = "38")
+ private Integer actualRemaining;
+
+ }
+
+ @Schema(description = "管理后台 - PMS 迭代概览动态项 VO")
+ @Data
+ public static class ActivityItem {
+
+ @Schema(description = "动态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "工作项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long workItemId;
+
+ @Schema(description = "工作项序号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer workItemSerialNumber;
+
+ @Schema(description = "工作项名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "优化登录页")
+ private String workItemName;
+
+ @Schema(description = "操作人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long operatorUserId;
+
+ @Schema(description = "操作人昵称", example = "芋道")
+ private String operatorUserName;
+
+ @Schema(description = "动态内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "将状态更新为「已完成」")
+ private String content;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationPageReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationPageReqVO.java
new file mode 100644
index 000000000..588fa4393
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationPageReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.iteration.PmsIterationStatusEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - PMS 项目迭代分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PmsIterationPageReqVO extends PageParam {
+
+ @Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "项目编号不能为空")
+ private Long projectId;
+
+ @Schema(description = "迭代名称", example = "第一期")
+ private String name;
+
+ @Schema(description = "迭代状态", example = "1")
+ @InEnum(PmsIterationStatusEnum.class)
+ private Integer status;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationRespVO.java
new file mode 100644
index 000000000..2d0e40143
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationRespVO.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - PMS 项目迭代 Response VO")
+@Data
+public class PmsIterationRespVO {
+
+ @Schema(description = "迭代编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long projectId;
+
+ @Schema(description = "迭代名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "第一期")
+ private String name;
+
+ @Schema(description = "负责人用户编号", example = "1")
+ private Long ownerUserId;
+
+ @Schema(description = "迭代状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer status;
+
+ @Schema(description = "负责人姓名", example = "芋道源码")
+ private String ownerUserName;
+
+ @Schema(description = "开始时间")
+ private LocalDateTime startTime;
+
+ @Schema(description = "结束时间")
+ private LocalDateTime endTime;
+
+ @Schema(description = "迭代目标", example = "完成核心流程")
+ private String target;
+
+ @Schema(description = "迭代描述")
+ private String description;
+
+ @Schema(description = "完成时间")
+ private LocalDateTime finishTime;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer sort;
+
+ @Schema(description = "完成进度百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
+ private Integer progress;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationSaveReqVO.java
new file mode 100644
index 000000000..6e3bf5b17
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationSaveReqVO.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.AssertTrue;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - PMS 项目迭代新增/修改 Request VO")
+@Data
+public class PmsIterationSaveReqVO {
+
+ @Schema(description = "迭代编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "项目编号不能为空")
+ private Long projectId;
+
+ @Schema(description = "迭代名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "第一期")
+ @NotBlank(message = "迭代名称不能为空")
+ @Size(max = 100, message = "迭代名称长度不能超过 100 个字符")
+ private String name;
+
+ @Schema(description = "负责人用户编号", example = "1")
+ private Long ownerUserId;
+
+ @Schema(description = "开始时间")
+ private LocalDateTime startTime;
+
+ @Schema(description = "结束时间")
+ private LocalDateTime endTime;
+
+ @Schema(description = "迭代目标", example = "完成核心流程")
+ @Size(max = 255, message = "迭代目标长度不能超过 255 个字符")
+ private String target;
+
+ @Schema(description = "迭代描述")
+ private String description;
+
+ @Schema(hidden = true)
+ @AssertTrue(message = "迭代结束时间必须晚于开始时间,且开始、结束时间需要同时填写")
+ @JsonIgnore
+ public boolean isTimeRangeValid() {
+ return startTime == null && endTime == null
+ || startTime != null && endTime != null && endTime.isAfter(startTime);
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationStartReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationStartReqVO.java
new file mode 100644
index 000000000..dcfe68c12
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationStartReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.AssertTrue;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - PMS 项目迭代开始 Request VO")
+@Data
+public class PmsIterationStartReqVO {
+
+ @Schema(description = "迭代编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "迭代编号不能为空")
+ private Long id;
+
+ @Schema(description = "开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "迭代开始时间不能为空")
+ private LocalDateTime startTime;
+
+ @Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "迭代结束时间不能为空")
+ private LocalDateTime endTime;
+
+ @Schema(hidden = true)
+ @AssertTrue(message = "迭代结束时间必须晚于开始时间")
+ @JsonIgnore
+ public boolean isTimeRangeValid() {
+ return startTime == null || endTime == null || endTime.isAfter(startTime);
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectAnnouncementController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectAnnouncementController.java
new file mode 100644
index 000000000..896afaaa0
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectAnnouncementController.java
@@ -0,0 +1,129 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.announcement.PmsProjectAnnouncementRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.announcement.PmsProjectAnnouncementSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectAnnouncementDO;
+import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectAnnouncementService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 项目公告")
+@RestController
+@RequestMapping("/pms/pm/project-announcement")
+@Validated
+public class PmsProjectAnnouncementController {
+
+ @Resource
+ private PmsProjectAnnouncementService projectAnnouncementService;
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建项目公告")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
+ public CommonResult createProjectAnnouncement(@Valid @RequestBody PmsProjectAnnouncementSaveReqVO saveReqVO) {
+ return success(projectAnnouncementService.createProjectAnnouncement(saveReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新项目公告")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
+ public CommonResult updateProjectAnnouncement(
+ @Valid @RequestBody PmsProjectAnnouncementSaveReqVO saveReqVO) {
+ projectAnnouncementService.updateProjectAnnouncement(saveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除项目公告")
+ @Parameter(name = "id", description = "公告编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
+ public CommonResult deleteProjectAnnouncement(@RequestParam("id") Long id) {
+ projectAnnouncementService.deleteProjectAnnouncement(id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得项目公告详情")
+ @Parameter(name = "id", description = "公告编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
+ public CommonResult getProjectAnnouncement(@RequestParam("id") Long id) {
+ PmsProjectAnnouncementDO announcement = projectAnnouncementService
+ .getProjectAnnouncement(id, getLoginUserId());
+ return success(buildProjectAnnouncementRespVO(announcement));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得项目公告列表")
+ @Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
+ public CommonResult
> getProjectAnnouncementList(
+ @RequestParam("projectId") Long projectId) {
+ List announcements = projectAnnouncementService
+ .getProjectAnnouncementList(projectId, getLoginUserId());
+ List announcementVOList = buildProjectAnnouncementRespVOList(announcements);
+ return success(announcementVOList);
+ }
+
+ // ==================== 拼接 VO ====================
+
+ /**
+ * 构建项目公告响应
+ *
+ * @param announcement 项目公告
+ * @return 项目公告响应
+ */
+ private PmsProjectAnnouncementRespVO buildProjectAnnouncementRespVO(PmsProjectAnnouncementDO announcement) {
+ return CollUtil.getFirst(buildProjectAnnouncementRespVOList(Collections.singletonList(announcement)));
+ }
+
+ /**
+ * 构建项目公告响应列表
+ *
+ * @param announcements 项目公告列表
+ * @return 项目公告响应列表
+ */
+ private List buildProjectAnnouncementRespVOList(
+ List announcements) {
+ Map userMap = adminUserApi.getUserMap(convertSet(announcements,
+ announcement -> NumberUtils.parseLong(announcement.getCreator())));
+ return convertList(announcements, announcement -> {
+ PmsProjectAnnouncementRespVO announcementVO = BeanUtils.toBean(
+ announcement, PmsProjectAnnouncementRespVO.class)
+ .setCreatorUserId(NumberUtils.parseLong(announcement.getCreator()));
+ findAndThen(userMap, announcementVO.getCreatorUserId(),
+ user -> announcementVO.setCreatorUserName(user.getNickname()));
+ return announcementVO;
+ });
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectController.java
new file mode 100644
index 000000000..d6ea74a09
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectController.java
@@ -0,0 +1,207 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project.PmsProjectOverviewRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project.PmsProjectPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project.PmsProjectRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project.PmsProjectSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectDO;
+import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemStatusTypeEnum;
+import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectFavoriteService;
+import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
+import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectService;
+import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemService;
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 项目")
+@RestController
+@RequestMapping("/pms/pm/project")
+@Validated
+public class PmsProjectController {
+
+ @Resource
+ private PmsProjectService projectService;
+ @Resource
+ private PmsProjectMemberService projectMemberService;
+ @Resource
+ private PmsProjectFavoriteService projectFavoriteService;
+ @Resource
+ private PmsWorkItemService workItemService;
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private PermissionApi permissionApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建项目")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:create')")
+ public CommonResult createProject(@Valid @RequestBody PmsProjectSaveReqVO saveReqVO) {
+ return success(projectService.createProject(saveReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新项目")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
+ public CommonResult updateProject(@Valid @RequestBody PmsProjectSaveReqVO saveReqVO) {
+ projectService.updateProject(saveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得项目详情")
+ @Parameter(name = "id", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
+ public CommonResult getProject(@RequestParam("id") Long id) {
+ Long userId = getLoginUserId();
+ PmsProjectDO project = projectService.getProjectAndUpdateVisitTime(id, userId);
+ return success(buildProjectRespVO(project, userId));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得项目分页")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
+ public CommonResult> getProjectPage(@Valid PmsProjectPageReqVO pageReqVO) {
+ Long userId = getLoginUserId();
+ PageResult pageResult = projectService.getProjectPage(pageReqVO, userId);
+ return success(new PageResult<>(buildProjectRespVOList(pageResult.getList(), userId), pageResult.getTotal()));
+ }
+
+ @GetMapping("/favorite-list")
+ @Operation(summary = "获得星标项目列表")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
+ public CommonResult> getFavoriteProjectList() {
+ Long userId = getLoginUserId();
+ return success(buildProjectRespVOList(projectService.getFavoriteProjectList(userId), userId));
+ }
+
+ @GetMapping("/overview")
+ @Operation(summary = "获得项目概况")
+ @Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
+ public CommonResult getProjectOverview(@RequestParam("projectId") Long projectId) {
+ return success(projectService.getProjectOverview(projectId, getLoginUserId()));
+ }
+
+ @PutMapping("/archive")
+ @Operation(summary = "归档项目")
+ @Parameter(name = "id", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
+ public CommonResult archiveProject(@RequestParam("id") Long id) {
+ projectService.archiveProject(id, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/recycle")
+ @Operation(summary = "将项目移入回收站")
+ @Parameter(name = "id", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
+ public CommonResult recycleProject(@RequestParam("id") Long id) {
+ projectService.recycleProject(id, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/restore")
+ @Operation(summary = "恢复项目")
+ @Parameter(name = "id", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
+ public CommonResult restoreProject(@RequestParam("id") Long id) {
+ projectService.restoreProject(id, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "彻底删除回收站项目")
+ @Parameter(name = "id", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:delete')")
+ public CommonResult deleteProject(@RequestParam("id") Long id) {
+ projectService.deleteProject(id, getLoginUserId());
+ return success(true);
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private PmsProjectRespVO buildProjectRespVO(PmsProjectDO project, Long userId) {
+ if (project == null) {
+ return null;
+ }
+ return CollUtil.getFirst(buildProjectRespVOList(Collections.singletonList(project), userId));
+ }
+
+ private List buildProjectRespVOList(List projects, Long userId) {
+ if (CollUtil.isEmpty(projects)) {
+ return Collections.emptyList();
+ }
+ // 1. 批量查询创建人、项目管理员、成员数量和工作项完成度
+ Set projectIds = convertSet(projects, PmsProjectDO::getId);
+ Map> managerUserIdListMap = projectMemberService.getProjectManagerUserIdListMap(projectIds);
+ Map memberCountMap = projectMemberService.getProjectMemberCountMap(projectIds);
+ Map> projectStatusCountMap = workItemService
+ .getProjectWorkItemStatusCountMap(projectIds);
+ Set userIds = convertSet(projects, project ->
+ NumberUtils.parseLong(project.getCreator()));
+ managerUserIdListMap.values().forEach(userIds::addAll);
+ Map userMap = adminUserApi.getUserMap(userIds);
+ Set ownerProjectIds = convertSet(projectMemberService.getOwnerProjectIdListByUserId(userId));
+ Set managedProjectIds = convertSet(projectMemberService.getManagedProjectIdListByUserId(userId));
+ Set memberProjectIds = convertSet(projectMemberService.getProjectIdListByUserId(userId));
+ Set writableProjectIds = convertSet(projectMemberService.getWritableProjectIdListByUserId(userId));
+ Set favoriteProjectIds = convertSet(projectFavoriteService.getFavoriteProjectIdListByUserId(userId));
+ favoriteProjectIds.retainAll(projectIds);
+ boolean superAdmin = permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode()).getCheckedData();
+ // 2. 转换项目响应,并拼接展示字段
+ return BeanUtils.toBean(projects, PmsProjectRespVO.class, projectVO -> {
+ findAndThen(userMap, NumberUtils.parseLong(projectVO.getCreator()),
+ creator -> projectVO.setCreatorName(creator.getNickname()));
+ List adminNames = new ArrayList<>();
+ for (Long adminUserId : managerUserIdListMap.getOrDefault(projectVO.getId(), Collections.emptyList())) {
+ findAndThen(userMap, adminUserId, admin -> adminNames.add(admin.getNickname()));
+ }
+ Map statusCountMap = projectStatusCountMap.getOrDefault(
+ projectVO.getId(), Collections.emptyMap());
+ projectVO.setAdminNames(adminNames).setMemberCount(memberCountMap.getOrDefault(projectVO.getId(), 0))
+ .setPendingWorkItemCount(statusCountMap.getOrDefault(PmsWorkItemStatusTypeEnum.PENDING.getType(), 0L))
+ .setProcessingWorkItemCount(statusCountMap.getOrDefault(PmsWorkItemStatusTypeEnum.PROCESSING.getType(), 0L))
+ .setCompletedWorkItemCount(statusCountMap.getOrDefault(PmsWorkItemStatusTypeEnum.COMPLETED.getType(), 0L))
+ .setOwnerStatus(superAdmin || ownerProjectIds.contains(projectVO.getId()))
+ .setAdminStatus(superAdmin || managedProjectIds.contains(projectVO.getId()))
+ .setMemberStatus(memberProjectIds.contains(projectVO.getId()))
+ .setExitStatus(memberProjectIds.contains(projectVO.getId()) && !ownerProjectIds.contains(projectVO.getId()))
+ .setWriteStatus(superAdmin || writableProjectIds.contains(projectVO.getId()))
+ .setFavoriteStatus(favoriteProjectIds.contains(projectVO.getId()));
+ });
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectFavoriteController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectFavoriteController.java
new file mode 100644
index 000000000..85db30053
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectFavoriteController.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectFavoriteService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 项目收藏")
+@RestController
+@RequestMapping("/pms/pm/project-favorite")
+@Validated
+public class PmsProjectFavoriteController {
+
+ @Resource
+ private PmsProjectFavoriteService projectFavoriteService;
+
+ @PostMapping("/create")
+ @Operation(summary = "收藏项目")
+ @Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
+ public CommonResult createProjectFavorite(@RequestParam("projectId") Long projectId) {
+ projectFavoriteService.createProjectFavorite(projectId, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "取消收藏项目")
+ @Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
+ public CommonResult deleteProjectFavorite(@RequestParam("projectId") Long projectId) {
+ projectFavoriteService.deleteProjectFavorite(projectId, getLoginUserId());
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectGroupController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectGroupController.java
new file mode 100644
index 000000000..ca2214da1
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectGroupController.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group.PmsProjectGroupMoveReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group.PmsProjectGroupRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group.PmsProjectGroupSaveReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group.PmsProjectGroupSortReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectGroupDO;
+import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectGroupService;
+import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 项目分组")
+@RestController
+@RequestMapping("/pms/pm/project-group")
+@Validated
+public class PmsProjectGroupController {
+
+ @Resource
+ private PmsProjectGroupService projectGroupService;
+ @Resource
+ private PmsProjectMemberService projectMemberService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建项目分组")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-group:create')")
+ public CommonResult createProjectGroup(@Valid @RequestBody PmsProjectGroupSaveReqVO createReqVO) {
+ return success(projectGroupService.createProjectGroup(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改项目分组")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-group:update')")
+ public CommonResult updateProjectGroup(@Valid @RequestBody PmsProjectGroupSaveReqVO updateReqVO) {
+ projectGroupService.updateProjectGroup(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/update-sort")
+ @Operation(summary = "修改项目分组排序")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-group:update')")
+ public CommonResult updateProjectGroupSort(@Valid @RequestBody PmsProjectGroupSortReqVO sortReqVO) {
+ projectGroupService.updateProjectGroupSort(sortReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除项目分组")
+ @Parameter(name = "id", description = "项目分组编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-group:delete')")
+ public CommonResult deleteProjectGroup(@RequestParam("id") Long id) {
+ projectGroupService.deleteProjectGroup(id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得项目分组列表")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-group:query')")
+ public CommonResult> getProjectGroupList() {
+ Long userId = getLoginUserId();
+ List groups = projectGroupService.getProjectGroupList(userId);
+ List projectIds = projectMemberService.getActiveProjectIdListByUserId(userId);
+ Map groupCountMap = projectGroupService.getProjectGroupCountMap(userId, groups, projectIds);
+ return success(BeanUtils.toBean(groups, PmsProjectGroupRespVO.class,
+ group -> group.setProjectCount(groupCountMap.getOrDefault(group.getId(), 0))));
+ }
+
+ @PutMapping("/move-project")
+ @Operation(summary = "移动项目到个人分组")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-group:update')")
+ public CommonResult moveProjectToGroup(@Valid @RequestBody PmsProjectGroupMoveReqVO moveReqVO) {
+ projectGroupService.moveProjectToGroup(moveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectMemberController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectMemberController.java
new file mode 100644
index 000000000..40f8840f6
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectMemberController.java
@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.member.PmsProjectMemberRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.member.PmsProjectMemberSaveReqVO;
+import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 项目成员")
+@RestController
+@RequestMapping("/pms/pm/project-member")
+@Validated
+public class PmsProjectMemberController {
+
+ @Resource
+ private PmsProjectMemberService projectMemberService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得项目成员列表")
+ @Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-member:query')")
+ public CommonResult> getProjectMemberList(
+ @RequestParam("projectId") Long projectId) {
+ List members = projectMemberService.getProjectMemberList(projectId, getLoginUserId());
+ return success(fillProjectMemberRespVOList(members));
+ }
+
+ @PutMapping("/update-list")
+ @Operation(summary = "保存项目成员及其权限级别")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-member:update')")
+ public CommonResult updateProjectMemberList(@Valid @RequestBody PmsProjectMemberSaveReqVO saveReqVO) {
+ projectMemberService.updateProjectMemberList(saveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "移除项目成员")
+ @Parameters({
+ @Parameter(name = "projectId", description = "项目编号", required = true, example = "1024"),
+ @Parameter(name = "userId", description = "后台用户编号", required = true, example = "1")
+ })
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-member:update')")
+ public CommonResult deleteProjectMember(@RequestParam("projectId") Long projectId,
+ @RequestParam("userId") Long userId) {
+ projectMemberService.deleteProjectMember(projectId, userId, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/exit")
+ @Operation(summary = "主动退出项目")
+ @Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-member:query')")
+ public CommonResult exitProject(@RequestParam("projectId") Long projectId) {
+ projectMemberService.exitProject(projectId, getLoginUserId());
+ return success(true);
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private List fillProjectMemberRespVOList(List members) {
+ if (CollUtil.isEmpty(members)) {
+ return Collections.emptyList();
+ }
+ // 1. 批量查询用户
+ Map userMap = adminUserApi.getUserMap(
+ convertSet(members, PmsProjectMemberRespVO::getUserId));
+ // 2. 拼接用户信息
+ for (PmsProjectMemberRespVO memberVO : members) {
+ MapUtils.findAndThen(userMap, memberVO.getUserId(),
+ user -> memberVO.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
+ }
+ return members;
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectTemplateController.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectTemplateController.java
new file mode 100644
index 000000000..cb7e932b0
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectTemplateController.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template.PmsProjectTemplatePageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template.PmsProjectTemplateRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template.PmsProjectTemplateSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectTemplateDO;
+import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectTemplateService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - PMS 项目模板")
+@RestController
+@RequestMapping("/pms/pm/project-template")
+@Validated
+public class PmsProjectTemplateController {
+
+ @Resource
+ private PmsProjectTemplateService projectTemplateService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建项目模板")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-template:create')")
+ public CommonResult createProjectTemplate(@Valid @RequestBody PmsProjectTemplateSaveReqVO createReqVO) {
+ return success(projectTemplateService.createProjectTemplate(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新项目模板")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-template:update')")
+ public CommonResult updateProjectTemplate(@Valid @RequestBody PmsProjectTemplateSaveReqVO updateReqVO) {
+ projectTemplateService.updateProjectTemplate(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除项目模板")
+ @Parameter(name = "id", description = "模板编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-template:delete')")
+ public CommonResult deleteProjectTemplate(@RequestParam("id") Long id) {
+ projectTemplateService.deleteProjectTemplate(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得项目模板")
+ @Parameter(name = "id", description = "模板编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-template:query')")
+ public CommonResult getProjectTemplate(@RequestParam("id") Long id) {
+ PmsProjectTemplateDO template = projectTemplateService.getProjectTemplate(id);
+ return success(BeanUtils.toBean(template, PmsProjectTemplateRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得项目模板分页")
+ @PreAuthorize("@ss.hasPermission('pms:pm:project-template:query')")
+ public CommonResult> getProjectTemplatePage(
+ @Valid PmsProjectTemplatePageReqVO pageReqVO) {
+ PageResult pageResult = projectTemplateService.getProjectTemplatePage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, PmsProjectTemplateRespVO.class));
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/announcement/PmsProjectAnnouncementRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/announcement/PmsProjectAnnouncementRespVO.java
new file mode 100644
index 000000000..3c9e4f88f
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/announcement/PmsProjectAnnouncementRespVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.announcement;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 项目公告 Response VO")
+@Data
+public class PmsProjectAnnouncementRespVO {
+
+ @Schema(description = "公告编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+ private Long projectId;
+
+ @Schema(description = "公告内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "项目启动")
+ private String content;
+
+ @Schema(description = "附件地址列表", example = "[\"https://example.com/a.pdf\"]")
+ private List fileUrls;
+
+ @Schema(description = "创建者用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long creatorUserId;
+
+ @Schema(description = "创建者用户昵称", example = "芋道")
+ private String creatorUserName;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime updateTime;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/announcement/PmsProjectAnnouncementSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/announcement/PmsProjectAnnouncementSaveReqVO.java
new file mode 100644
index 000000000..f3b280a6b
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/announcement/PmsProjectAnnouncementSaveReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.announcement;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 项目公告保存 Request VO")
+@Data
+public class PmsProjectAnnouncementSaveReqVO {
+
+ @Schema(description = "公告编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "项目编号不能为空")
+ private Long projectId;
+
+ @Schema(description = "公告内容", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotBlank(message = "公告内容不能为空")
+ @Size(max = 5000, message = "公告内容不能超过 5000 个字符")
+ private String content;
+
+ @Schema(description = "附件地址列表")
+ private List fileUrls;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupMoveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupMoveReqVO.java
new file mode 100644
index 000000000..58a74b009
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupMoveReqVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 项目移动分组 Request VO")
+@Data
+public class PmsProjectGroupMoveReqVO {
+
+ @Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "项目编号不能为空")
+ private Long projectId;
+
+ @Schema(description = "目标项目分组编号;不传表示移动到未分组", example = "2048")
+ private Long groupId;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupRespVO.java
new file mode 100644
index 000000000..c46dbef0f
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - PMS 项目分组 Response VO")
+@Data
+public class PmsProjectGroupRespVO {
+
+ @Schema(description = "项目分组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "重点项目")
+ private String name;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer sort;
+
+ @Schema(description = "分组类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer type;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+ @Schema(description = "项目数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+ private Integer projectCount;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupSaveReqVO.java
new file mode 100644
index 000000000..1f368f4c5
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupSaveReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 项目分组新增/修改 Request VO")
+@Data
+public class PmsProjectGroupSaveReqVO {
+
+ @Schema(description = "项目分组编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "重点项目")
+ @NotBlank(message = "分组名称不能为空")
+ @Size(max = 100, message = "分组名称不能超过 100 个字符")
+ private String name;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupSortReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupSortReqVO.java
new file mode 100644
index 000000000..464b5f8e1
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupSortReqVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 项目分组排序 Request VO")
+@Data
+public class PmsProjectGroupSortReqVO {
+
+ @Schema(description = "项目分组排序项", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotEmpty(message = "项目分组排序项不能为空")
+ private List- items;
+
+ @Schema(description = "管理后台 - PMS 项目分组排序项")
+ @Data
+ public static class Item {
+
+ @Schema(description = "项目分组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "项目分组编号不能为空")
+ private Long id;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "显示顺序不能为空")
+ @Min(value = 0, message = "显示顺序不能小于 0")
+ private Integer sort;
+
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/member/PmsProjectMemberRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/member/PmsProjectMemberRespVO.java
new file mode 100644
index 000000000..7913faa70
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/member/PmsProjectMemberRespVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.member;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - PMS 项目成员 Response VO")
+@Data
+public class PmsProjectMemberRespVO {
+
+ @Schema(description = "后台用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long userId;
+
+ @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
+ private String nickname;
+
+ @Schema(description = "用户头像")
+ private String avatar;
+
+ @Schema(description = "成员权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer level;
+
+ @Schema(description = "是否项目创建人", example = "true")
+ private Boolean creatorStatus;
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/member/PmsProjectMemberSaveReqVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/member/PmsProjectMemberSaveReqVO.java
new file mode 100644
index 000000000..cde7b2468
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/member/PmsProjectMemberSaveReqVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.member;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectMemberLevelEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 项目成员保存 Request VO")
+@Data
+public class PmsProjectMemberSaveReqVO {
+
+ @Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "项目编号不能为空")
+ private Long projectId;
+
+ @Schema(description = "项目成员列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "项目成员不能为空")
+ @Valid
+ private List members;
+
+ @Schema(description = "管理后台 - PMS 项目成员保存项")
+ @Data
+ public static class Member {
+
+ @Schema(description = "后台用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "后台用户编号不能为空")
+ private Long userId;
+
+ @Schema(description = "成员权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ @NotNull(message = "成员权限级别不能为空")
+ @InEnum(PmsProjectMemberLevelEnum.class)
+ private Integer level;
+
+ }
+
+}
diff --git a/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectOverviewRespVO.java b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectOverviewRespVO.java
new file mode 100644
index 000000000..b1a9d5e1f
--- /dev/null
+++ b/yudao-module-pms/yudao-module-pms-server/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectOverviewRespVO.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - PMS 项目概况 Response VO")
+@Data
+public class PmsProjectOverviewRespVO {
+
+ @Schema(description = "工作项总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+ private Long totalCount;
+
+ @Schema(description = "未开始工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8")
+ private Long pendingCount;
+
+ @Schema(description = "进行中工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "6")
+ private Long processingCount;
+
+ @Schema(description = "已完成工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "6")
+ private Long completedCount;
+
+ @Schema(description = "按工作项类型统计")
+ private Map typeCountMap;
+
+ @Schema(description = "近十四日完成趋势")
+ private List completedTrends;
+
+ @Schema(description = "分配给当前用户的未完成工作项")
+ private List