diff --git a/pom.xml b/pom.xml
index c801ec0..c2cb378 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,8 @@
-
+ yudao-module-pms
+
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/StringListConvert.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/StringListConvert.java
index edb243e..5d391b0 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/StringListConvert.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/StringListConvert.java
@@ -1,11 +1,14 @@
package cn.iocoder.yudao.framework.excel.core.convert;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
import cn.idev.excel.converters.Converter;
import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
+import java.util.Collections;
import java.util.List;
/**
@@ -15,6 +18,25 @@ import java.util.List;
*/
public class StringListConvert implements Converter> {
+ /**
+ * 将 Excel 单元格中的分隔字符串解析为字符串列表。
+ *
+ * @param value Excel 单元格数据
+ * @param contentProperty Excel 字段属性
+ * @param globalConfiguration Excel 全局配置
+ * @return 字符串列表
+ */
+ @Override
+ public List convertToJavaData(ReadCellData> value, ExcelContentProperty contentProperty,
+ GlobalConfiguration globalConfiguration) {
+ String text = value == null ? null : value.getStringValue();
+ if (StrUtil.isBlank(text)) {
+ return Collections.emptyList();
+ }
+ // 兼容导入模板常用的英文逗号、中文逗号、顿号,以及导出时使用的斜杠。
+ return StrUtil.splitTrim(text.replace(',', ',').replace('、', ',').replace('/', ','), ',');
+ }
+
@Override
public WriteCellData convertToExcelData(List value,
ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
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 83430c2..92092bb 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
@@ -50,6 +50,10 @@ public class BannerApplicationRunner implements ApplicationRunner {
if (isNotPresent("cn.iocoder.yudao.module.wms.framework.web.config.WmsWebConfiguration")) {
System.out.println("[WMS 仓库管理系统 yudao-module-wms - 已禁用][参考 https://doc.iocoder.cn/wms/build/ 开启]");
}
+ // PMS 系统
+ if (isNotPresent("cn.iocoder.yudao.module.pms.framework.web.config.PmsWebConfiguration")) {
+ System.out.println("[PMS 项目管理系统 yudao-module-pms - 已禁用][请在 yudao-server 中开启]");
+ }
// CRM 系统
if (isNotPresent("cn.iocoder.yudao.module.crm.framework.web.config.CrmWebConfiguration")) {
System.out.println("[CRM 系统 yudao-module-crm - 已禁用][参考 https://doc.iocoder.cn/crm/build/ 开启]");
diff --git a/yudao-module-pms/pom.xml b/yudao-module-pms/pom.xml
new file mode 100644
index 0000000..8babb35
--- /dev/null
+++ b/yudao-module-pms/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+ cn.iocoder.boot
+ yudao
+ ${revision}
+
+ 4.0.0
+
+ yudao-module-pms
+ jar
+
+ ${project.artifactId}
+
+ pms 包下,项目管理(Project Management System)
+ 例如说:项目、工作事项、迭代、看板、工时和知识库等等
+
+
+
+
+ cn.iocoder.boot
+ yudao-module-system
+ ${revision}
+
+
+ cn.iocoder.boot
+ yudao-module-infra
+ ${revision}
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-web
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-security
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-mybatis
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-job
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-test
+ test
+
+
+
+
diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeContentPermissionController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeContentPermissionController.java
new file mode 100644
index 0000000..9cd6ac0
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeDocumentController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeDocumentController.java
new file mode 100644
index 0000000..aca906c
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeDocumentLabelController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeDocumentLabelController.java
new file mode 100644
index 0000000..98452d8
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeFolderController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/PmsKnowledgeFolderController.java
new file mode 100644
index 0000000..693f667
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentCreateReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentCreateReqVO.java
new file mode 100644
index 0000000..e4ef99b
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentMoveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentMoveReqVO.java
new file mode 100644
index 0000000..d5bc012
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentRespVO.java
new file mode 100644
index 0000000..d07fe31
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentSearchPageReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentSearchPageReqVO.java
new file mode 100644
index 0000000..07d9b8b
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentUpdateReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/document/PmsKnowledgeDocumentUpdateReqVO.java
new file mode 100644
index 0000000..7afdd5f
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderMoveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderMoveReqVO.java
new file mode 100644
index 0000000..c30e02a
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderRespVO.java
new file mode 100644
index 0000000..86bf50e
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeFolderSaveReqVO.java
new file mode 100644
index 0000000..5e3e819
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeTreeRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/folder/PmsKnowledgeTreeRespVO.java
new file mode 100644
index 0000000..9c3a9b2
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelPageReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelPageReqVO.java
new file mode 100644
index 0000000..e675209
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelRespVO.java
new file mode 100644
index 0000000..be6ddd1
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/label/PmsKnowledgeDocumentLabelSaveReqVO.java
new file mode 100644
index 0000000..1f77d0e
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/permission/PmsKnowledgeContentPermissionRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/permission/PmsKnowledgeContentPermissionRespVO.java
new file mode 100644
index 0000000..c06b94c
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/permission/PmsKnowledgeContentPermissionUpdateReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/content/vo/permission/PmsKnowledgeContentPermissionUpdateReqVO.java
new file mode 100644
index 0000000..3ccedbd
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentCommentController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentCommentController.java
new file mode 100644
index 0000000..235e07c
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentLikeController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentLikeController.java
new file mode 100644
index 0000000..98cd565
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentShareController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeDocumentShareController.java
new file mode 100644
index 0000000..170d828
--- /dev/null
+++ b/yudao-module-pms/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));
+ }
+ return success(documentVO);
+ }
+
+}
diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeFavoriteController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeFavoriteController.java
new file mode 100644
index 0000000..0439bb2
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeViewRecordController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/PmsKnowledgeViewRecordController.java
new file mode 100644
index 0000000..16fc9b3
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/PmsKnowledgeInteractionItemRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/PmsKnowledgeInteractionItemRespVO.java
new file mode 100644
index 0000000..ac61ad4
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/comment/PmsKnowledgeDocumentCommentRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/comment/PmsKnowledgeDocumentCommentRespVO.java
new file mode 100644
index 0000000..fb0c4f6
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/comment/PmsKnowledgeDocumentCommentSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/comment/PmsKnowledgeDocumentCommentSaveReqVO.java
new file mode 100644
index 0000000..efbed8d
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/favorite/PmsKnowledgeFavoritePageReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/favorite/PmsKnowledgeFavoritePageReqVO.java
new file mode 100644
index 0000000..a8bdf55
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/favorite/PmsKnowledgeFavoriteSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/favorite/PmsKnowledgeFavoriteSaveReqVO.java
new file mode 100644
index 0000000..e77b3f2
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentSharePublicRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentSharePublicRespVO.java
new file mode 100644
index 0000000..f93f2cf
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareRespVO.java
new file mode 100644
index 0000000..3c9fc51
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareSaveReqVO.java
new file mode 100644
index 0000000..886e4a6
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareUpdateMemberListReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/share/PmsKnowledgeDocumentShareUpdateMemberListReqVO.java
new file mode 100644
index 0000000..36f5035
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/viewrecord/PmsKnowledgeRecentListRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/interaction/vo/viewrecord/PmsKnowledgeRecentListRespVO.java
new file mode 100644
index 0000000..b0c539f
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeGroupController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeGroupController.java
new file mode 100644
index 0000000..c3b6424
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryController.java
new file mode 100644
index 0000000..4066bda
--- /dev/null
+++ b/yudao-module-pms/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);
+ Long deptId = currentUser != null ? currentUser.getDeptId() : null;
+ boolean superAdmin = permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode());
+ 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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryMemberController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryMemberController.java
new file mode 100644
index 0000000..8cf710a
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryTemplateController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/PmsKnowledgeLibraryTemplateController.java
new file mode 100644
index 0000000..cf023f5
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupRespVO.java
new file mode 100644
index 0000000..45e9c5f
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupSaveReqVO.java
new file mode 100644
index 0000000..5c567bf
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupSortReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeGroupSortReqVO.java
new file mode 100644
index 0000000..d44f561
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeLibraryMoveGroupReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/group/PmsKnowledgeLibraryMoveGroupReqVO.java
new file mode 100644
index 0000000..4581020
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibraryPageReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibraryPageReqVO.java
new file mode 100644
index 0000000..1de2423
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibraryRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibraryRespVO.java
new file mode 100644
index 0000000..20e6d43
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibrarySaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/library/PmsKnowledgeLibrarySaveReqVO.java
new file mode 100644
index 0000000..04daa73
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/member/PmsKnowledgeLibraryMemberRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/member/PmsKnowledgeLibraryMemberRespVO.java
new file mode 100644
index 0000000..3772f27
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/member/PmsKnowledgeLibraryUpdateMemberListReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/member/PmsKnowledgeLibraryUpdateMemberListReqVO.java
new file mode 100644
index 0000000..849db7a
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplatePageReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplatePageReqVO.java
new file mode 100644
index 0000000..1a1aecf
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplateRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplateRespVO.java
new file mode 100644
index 0000000..f292aae
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplateSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/library/vo/template/PmsKnowledgeLibraryTemplateSaveReqVO.java
new file mode 100644
index 0000000..5dcf251
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/PmsKnowledgeRecycleController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/PmsKnowledgeRecycleController.java
new file mode 100644
index 0000000..7699700
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecycleDetailRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecycleDetailRespVO.java
new file mode 100644
index 0000000..86f1f25
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecyclePreviewRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecyclePreviewRespVO.java
new file mode 100644
index 0000000..9f10dfc
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecycleRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/kb/recycle/vo/PmsKnowledgeRecycleRespVO.java
new file mode 100644
index 0000000..c561770
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/PmsIterationController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/PmsIterationController.java
new file mode 100644
index 0000000..06f215b
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationOverviewRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationOverviewRespVO.java
new file mode 100644
index 0000000..98eacc1
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationPageReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationPageReqVO.java
new file mode 100644
index 0000000..588fa43
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationRespVO.java
new file mode 100644
index 0000000..2d0e401
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationSaveReqVO.java
new file mode 100644
index 0000000..6e3bf5b
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationStartReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/iteration/vo/PmsIterationStartReqVO.java
new file mode 100644
index 0000000..dcfe68c
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectAnnouncementController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectAnnouncementController.java
new file mode 100644
index 0000000..896afaa
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectController.java
new file mode 100644
index 0000000..42d7f67
--- /dev/null
+++ b/yudao-module-pms/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());
+ // 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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectFavoriteController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectFavoriteController.java
new file mode 100644
index 0000000..85db300
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectGroupController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectGroupController.java
new file mode 100644
index 0000000..ca2214d
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectMemberController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectMemberController.java
new file mode 100644
index 0000000..40f8840
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectTemplateController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/PmsProjectTemplateController.java
new file mode 100644
index 0000000..cb7e932
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/announcement/PmsProjectAnnouncementRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/announcement/PmsProjectAnnouncementRespVO.java
new file mode 100644
index 0000000..3c9e4f8
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/announcement/PmsProjectAnnouncementSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/announcement/PmsProjectAnnouncementSaveReqVO.java
new file mode 100644
index 0000000..f3b280a
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupMoveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupMoveReqVO.java
new file mode 100644
index 0000000..58a74b0
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupRespVO.java
new file mode 100644
index 0000000..c46dbef
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupSaveReqVO.java
new file mode 100644
index 0000000..1f368f4
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupSortReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/group/PmsProjectGroupSortReqVO.java
new file mode 100644
index 0000000..464b5f8
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/member/PmsProjectMemberRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/member/PmsProjectMemberRespVO.java
new file mode 100644
index 0000000..7913faa
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/member/PmsProjectMemberSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/member/PmsProjectMemberSaveReqVO.java
new file mode 100644
index 0000000..cde7b24
--- /dev/null
+++ b/yudao-module-pms/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/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectOverviewRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectOverviewRespVO.java
new file mode 100644
index 0000000..b1a9d5e
--- /dev/null
+++ b/yudao-module-pms/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 assignedWorkItems;
+
+ @Schema(description = "PMS 项目概况 - 趋势点")
+ @Data
+ public static class TrendPoint {
+
+ @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String date;
+
+ @Schema(description = "当日完成数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Long count;
+
+ }
+
+ @Schema(description = "PMS 项目概况 - 分配给我的工作项")
+ @Data
+ public static class AssignedWorkItem {
+
+ @Schema(description = "工作项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "项目内序号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
+ private Integer serialNumber;
+
+ @Schema(description = "工作项类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer type;
+
+ @Schema(description = "工作项标题", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String name;
+
+ @Schema(description = "语义状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer status;
+
+ @Schema(description = "截止时间")
+ private LocalDateTime endTime;
+
+ @Schema(description = "完成进度", requiredMode = Schema.RequiredMode.REQUIRED, example = "60")
+ private Integer progress;
+
+ }
+
+}
diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectPageReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectPageReqVO.java
new file mode 100644
index 0000000..86832d4
--- /dev/null
+++ b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectPageReqVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectSceneTypeEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectSortTypeEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectStatusEnum;
+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 PmsProjectPageReqVO extends PageParam {
+
+ @Schema(description = "项目名称", example = "官网")
+ private String name;
+
+ @Schema(description = "列表场景", example = "1")
+ @InEnum(PmsProjectSceneTypeEnum.class)
+ private Integer sceneType = PmsProjectSceneTypeEnum.ALL.getType();
+
+ @Schema(description = "个人项目分组编号", example = "1024")
+ private Long groupId;
+
+ @Schema(description = "项目状态", example = "1")
+ @InEnum(PmsProjectStatusEnum.class)
+ private Integer status = PmsProjectStatusEnum.ACTIVE.getStatus();
+
+ @Schema(description = "排序类型", example = "1")
+ @InEnum(PmsProjectSortTypeEnum.class)
+ private Integer sortType = PmsProjectSortTypeEnum.ACCESS_TIME.getType();
+
+}
diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectRespVO.java
new file mode 100644
index 0000000..0dbefef
--- /dev/null
+++ b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectRespVO.java
@@ -0,0 +1,94 @@
+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;
+
+@Schema(description = "管理后台 - PMS 项目 Response VO")
+@Data
+public class PmsProjectRespVO {
+
+ @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 status;
+
+ @Schema(description = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer type;
+
+ @Schema(description = "项目优先级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer level;
+
+ @Schema(description = "项目描述", example = "完成官网前后端重构")
+ private String description;
+
+ @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean openStatus;
+
+ @Schema(description = "项目图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "ep:folder")
+ private String icon;
+
+ @Schema(description = "开始时间")
+ private LocalDateTime startTime;
+
+ @Schema(description = "截止时间")
+ private LocalDateTime endTime;
+
+ @Schema(description = "归档时间")
+ private LocalDateTime archiveTime;
+
+ @Schema(description = "移入回收站时间")
+ private LocalDateTime recycleTime;
+
+ @Schema(description = "最近访问时间")
+ private LocalDateTime accessTime;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+ @Schema(description = "创建人编号", example = "1")
+ private String creator;
+
+ @Schema(description = "创建人姓名", example = "芋道源码")
+ private String creatorName;
+
+ @Schema(description = "项目管理员姓名列表")
+ private List adminNames;
+
+ @Schema(description = "项目成员数量", example = "5")
+ private Integer memberCount;
+
+ @Schema(description = "未开始工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Long pendingWorkItemCount;
+
+ @Schema(description = "进行中工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Long processingWorkItemCount;
+
+ @Schema(description = "已完成工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+ private Long completedWorkItemCount;
+
+ @Schema(description = "当前用户是否为项目管理员", example = "true")
+ private Boolean adminStatus;
+
+ @Schema(description = "当前用户是否拥有项目所有者权限", example = "true")
+ private Boolean ownerStatus;
+
+ @Schema(description = "当前用户是否为项目成员", example = "true")
+ private Boolean memberStatus;
+
+ @Schema(description = "当前用户是否可以主动退出项目", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean exitStatus;
+
+ @Schema(description = "当前用户是否可以编辑项目业务数据", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean writeStatus;
+
+ @Schema(description = "当前用户是否已收藏", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean favoriteStatus;
+
+}
diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectSaveReqVO.java
new file mode 100644
index 0000000..8094bc2
--- /dev/null
+++ b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/project/PmsProjectSaveReqVO.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectLevelEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectTypeEnum;
+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 org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - PMS 项目新增/修改 Request VO")
+@Data
+public class PmsProjectSaveReqVO {
+
+ @Schema(description = "项目编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "官网重构")
+ @NotBlank(message = "项目名称不能为空")
+ @Size(max = 31, message = "项目名称不能超过 31 个字符")
+ private String name;
+
+ @Schema(description = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "项目类型不能为空")
+ @InEnum(PmsProjectTypeEnum.class)
+ private Integer type;
+
+ @Schema(description = "项目优先级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ @NotNull(message = "项目优先级不能为空")
+ @InEnum(PmsProjectLevelEnum.class)
+ private Integer level;
+
+ @Schema(description = "项目描述", example = "完成官网前后端重构")
+ @Size(max = 500, message = "项目描述不能超过 500 个字符")
+ private String description;
+
+ @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "项目可见范围不能为空")
+ private Boolean openStatus;
+
+ @Schema(description = "项目图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "ep:folder")
+ @NotBlank(message = "项目图标不能为空")
+ private String icon;
+
+ @Schema(description = "开始时间", example = "2026-08-01 00:00:00")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime startTime;
+
+ @Schema(description = "截止时间", example = "2026-12-31 23:59:59")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime endTime;
+
+ @Schema(description = "初始成员编号列表", example = "[1, 2]")
+ private List memberUserIds;
+
+ @JsonIgnore
+ @Schema(hidden = true)
+ @AssertTrue(message = "项目截止时间必须晚于开始时间,且开始、截止时间需要同时填写")
+ public boolean isTimeValid() {
+ return startTime == null && endTime == null
+ || startTime != null && endTime != null && startTime.isBefore(endTime);
+ }
+
+}
diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/template/PmsProjectTemplatePageReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/template/PmsProjectTemplatePageReqVO.java
new file mode 100644
index 0000000..e754e6b
--- /dev/null
+++ b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/template/PmsProjectTemplatePageReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectTypeEnum;
+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 PmsProjectTemplatePageReqVO extends PageParam {
+
+ @Schema(description = "模板名称,模糊匹配", example = "敏捷研发模板")
+ private String name;
+
+ @Schema(description = "项目类型", example = "2")
+ @InEnum(PmsProjectTypeEnum.class)
+ private Integer projectType;
+
+ @Schema(description = "模板状态", example = "0")
+ private Integer status;
+
+}
diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/template/PmsProjectTemplateRespVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/template/PmsProjectTemplateRespVO.java
new file mode 100644
index 0000000..6f647ac
--- /dev/null
+++ b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/template/PmsProjectTemplateRespVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.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 PmsProjectTemplateRespVO {
+
+ @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 = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer projectType;
+
+ @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 itemTypes;
+
+ @Schema(description = "工作项状态模板列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List statuses;
+
+ @Schema(description = "看板列模板列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List boards;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/template/PmsProjectTemplateSaveReqVO.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/template/PmsProjectTemplateSaveReqVO.java
new file mode 100644
index 0000000..4202291
--- /dev/null
+++ b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/project/vo/template/PmsProjectTemplateSaveReqVO.java
@@ -0,0 +1,126 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectTypeEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemStatusTypeEnum;
+import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemTypeEnum;
+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.Size;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS 项目模板新增/修改 Request VO")
+@Data
+public class PmsProjectTemplateSaveReqVO {
+
+ @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 = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "项目类型不能为空")
+ @InEnum(PmsProjectTypeEnum.class)
+ private Integer projectType;
+
+ @Schema(description = "模板状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "模板状态不能为空")
+ private Integer status;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @NotNull(message = "显示顺序不能为空")
+ private Integer sort;
+
+ @Schema(description = "启用的工作项类型列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2, 3, 4]")
+ @NotEmpty(message = "工作项类型不能为空")
+ private List itemTypes;
+
+ @Schema(description = "工作项状态模板列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotEmpty(message = "工作项状态不能为空")
+ private List statuses;
+
+ @Schema(description = "看板列模板列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotEmpty(message = "看板列不能为空")
+ private List boards;
+
+ @Schema(description = "工作项状态模板")
+ @Data
+ public static class StatusTemplate {
+
+ @Schema(description = "模板内稳定编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "task_todo")
+ @NotBlank(message = "状态编码不能为空")
+ @Size(max = 50, message = "状态编码不能超过 50 个字符")
+ private String code;
+
+ @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "待处理")
+ @NotBlank(message = "状态名称不能为空")
+ @Size(max = 50, message = "状态名称不能超过 50 个字符")
+ private String name;
+
+ @Schema(description = "工作项类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ @NotNull(message = "状态工作项类型不能为空")
+ @InEnum(PmsWorkItemTypeEnum.class)
+ private Integer workItemType;
+
+ @Schema(description = "语义状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "语义状态不能为空")
+ @InEnum(PmsWorkItemStatusTypeEnum.class)
+ private Integer statusType;
+
+ @Schema(description = "是否为初始状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "是否为初始状态不能为空")
+ private Boolean defaultStatus;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @NotNull(message = "状态显示顺序不能为空")
+ private Integer sort;
+
+ @Schema(description = "所属看板列编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "todo")
+ @NotBlank(message = "状态所属看板列不能为空")
+ private String boardCode;
+ }
+
+ @Schema(description = "看板列模板")
+ @Data
+ public static class BoardTemplate {
+
+ @Schema(description = "模板内稳定编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "todo")
+ @NotBlank(message = "看板列编码不能为空")
+ @Size(max = 50, message = "看板列编码不能超过 50 个字符")
+ private String code;
+
+ @Schema(description = "看板列名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "待处理")
+ @NotBlank(message = "看板列名称不能为空")
+ @Size(max = 50, message = "看板列名称不能超过 50 个字符")
+ private String name;
+
+ @Schema(description = "工作项类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ @NotNull(message = "看板列工作项类型不能为空")
+ @InEnum(PmsWorkItemTypeEnum.class)
+ private Integer workItemType;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @NotNull(message = "看板列显示顺序不能为空")
+ private Integer sort;
+
+ @Schema(description = "关联的状态编码列表", requiredMode = Schema.RequiredMode.REQUIRED,
+ example = "[\"task_todo\"]")
+ @NotEmpty(message = "看板列关联状态不能为空")
+ private List statusCodes;
+ }
+
+}
diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/workbench/PmsWorkbenchController.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/workbench/PmsWorkbenchController.java
new file mode 100644
index 0000000..078758b
--- /dev/null
+++ b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/pm/workbench/PmsWorkbenchController.java
@@ -0,0 +1,124 @@
+package cn.iocoder.yudao.module.pms.controller.admin.pm.workbench;
+
+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.workbench.vo.PmsWorkbenchCountRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo.PmsWorkbenchIterationRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo.PmsWorkbenchPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo.PmsWorkbenchWorkItemRespVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.pm.iteration.PmsIterationDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemStatusDO;
+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.workbench.PmsWorkbenchService;
+import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemStatusService;
+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.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.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+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.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - PMS 工作台")
+@RestController
+@RequestMapping("/pms/pm/workbench")
+@Validated
+public class PmsWorkbenchController {
+
+ @Resource
+ private PmsWorkbenchService workbenchService;
+ @Resource
+ private PmsProjectService projectService;
+ @Resource
+ private PmsProjectMemberService projectMemberService;
+ @Resource
+ private PmsWorkItemStatusService workItemStatusService;
+ @Resource
+ private PermissionApi permissionApi;
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @GetMapping("/work-item-page")
+ @Operation(summary = "获得我负责的工作项分页")
+ @PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
+ public CommonResult> getWorkbenchWorkItemPage(
+ @Valid PmsWorkbenchPageReqVO pageReqVO) {
+ // 1. 查询当前用户负责的工作项分页
+ Long userId = getLoginUserId();
+ PageResult pageResult = workbenchService.getWorkbenchWorkItemPage(pageReqVO, userId);
+ // 2.1 批量查询项目、状态及项目写权限
+ Map projectMap = projectService.getProjectMap(
+ convertSet(pageResult.getList(), PmsWorkItemDO::getProjectId));
+ Map statusMap = workItemStatusService.getWorkItemStatusMap(
+ convertSet(pageResult.getList(), PmsWorkItemDO::getStatusId));
+ Set writableProjectIds = convertSet(projectMemberService.getWritableProjectIdListByUserId(userId));
+ boolean superAdmin = permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode());
+ // 2.2 批量查询负责人和创建人
+ Set userIds = convertSet(pageResult.getList(), PmsWorkItemDO::getAssigneeUserId);
+ userIds.addAll(convertSet(pageResult.getList(), workItem -> NumberUtils.parseLong(workItem.getCreator())));
+ Map userMap = adminUserApi.getUserMap(userIds);
+ Map creatorUserIdMap = convertMap(pageResult.getList(), PmsWorkItemDO::getId,
+ workItem -> NumberUtils.parseLong(workItem.getCreator()));
+ // 3. 转换响应 VO,并拼接项目、状态、用户及写权限
+ List