feat(emergency): 急救宣教资源管理v3 - 审批上架分离、分类标签树形重构

- approve()不再自动发布,需单独调用publish()上架
- publish()增加auditStatus=11前置校验
- batchPublish()跳过未审核通过的资源
- 分类管理支持树形结构(listRoot/listChildren/listAll)
- listAll返回分类+tags嵌套树
- listChildren返回子分类+标签
- 标签parentId改为必填
- 分类删除校验标签存在
- 分页列表填充resourceCount
- 审核记录按审核时间排序

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-25 15:50:24 +08:00
co-authored by Claude
parent 532041ff8d
commit 2cc0668391
7 changed files with 137 additions and 48 deletions
@@ -1,6 +1,7 @@
package com.renkang.emergency.entity;
import java.io.Serializable;
import java.util.List;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -100,4 +101,9 @@ public class FirstAidResourceCategory implements Serializable {
@TableField(exist = false)
@Schema(title = "关联资源数量")
private Integer resourceCount;
/** 分类下的标签列表(非持久字段,仅 listWithTags 接口填充) */
@TableField(exist = false)
@Schema(title = "分类下的标签列表")
private List<FirstAidResourceCategory> tags;
}
@@ -48,13 +48,14 @@ public class FirstAidResourceCategoryController extends JeecgController<FirstAid
if (parent != null) record.setParentName(parent.getName());
}
}
categoryService.fillCategoryResourceCounts(pageList.getRecords());
return Result.OK(pageList);
}
@Operation(summary = "急救宣教资源分类-全部列表")
@Operation(summary = "急救宣教资源分类-全部列表(树形)", description = "返回全部分类及其下的标签树")
@GetMapping(value = "/listAll")
public Result<List<FirstAidResourceCategory>> listAll() {
return Result.OK(categoryService.listCategories());
return Result.OK(categoryService.listCategoriesWithTags());
}
@Operation(summary = "急救宣教资源分类-根分类列表")
@@ -69,6 +70,12 @@ public class FirstAidResourceCategoryController extends JeecgController<FirstAid
return Result.OK(categoryService.listChildrenByParentId(parentId));
}
@Operation(summary = "急救宣教资源分类-分类及标签嵌套列表", description = "返回全部分类及其下的标签,适用于维护宣教时分类标签联动选择")
@GetMapping(value = "/listWithTags")
public Result<List<FirstAidResourceCategory>> listWithTags() {
return Result.OK(categoryService.listCategoriesWithTags());
}
@Operation(summary = "急救宣教资源分类-添加")
@AutoLog(value = "急救宣教资源分类-添加")
//@RequiresPermissions("emergency:firstAidCategory:add")
@@ -59,20 +59,20 @@ public class FirstAidResourceTagController extends JeecgController<FirstAidResou
entity.setType(FirstAidConstants.TYPE_TAG);
if (entity.getSortNo() == null) entity.setSortNo(0);
if (entity.getStatus() == null) entity.setStatus(FirstAidConstants.NORMAL);
// 所属分类必填
if (StrUtil.isBlank(entity.getParentId())) {
return Result.error("所属分类不能为空");
}
// 校验所属分类是否存在
if (StrUtil.isNotBlank(entity.getParentId())) {
FirstAidResourceCategory parent = categoryService.getById(entity.getParentId());
if (parent == null || !FirstAidConstants.TYPE_CATEGORY.equals(parent.getType())) {
return Result.error("所属分类不存在");
}
FirstAidResourceCategory parent = categoryService.getById(entity.getParentId());
if (parent == null || !FirstAidConstants.TYPE_CATEGORY.equals(parent.getType())) {
return Result.error("所属分类不存在");
}
// 同分类下标签名称不能重复
QueryWrapper<FirstAidResourceCategory> checkWrapper = new QueryWrapper<FirstAidResourceCategory>()
.eq("name", entity.getName())
.eq("type", FirstAidConstants.TYPE_TAG);
if (StrUtil.isNotBlank(entity.getParentId())) {
checkWrapper.eq("parent_id", entity.getParentId());
}
.eq("type", FirstAidConstants.TYPE_TAG)
.eq("parent_id", entity.getParentId());
if (categoryService.count(checkWrapper) > 0) {
return Result.error("标签名称已存在");
}
@@ -88,21 +88,21 @@ public class FirstAidResourceTagController extends JeecgController<FirstAidResou
FirstAidResourceCategory old = categoryService.getById(entity.getId());
if (old == null) return Result.error("数据不存在");
if (!FirstAidConstants.TYPE_TAG.equals(old.getType())) return Result.error("该记录不是标签");
// 所属分类必填
if (StrUtil.isBlank(entity.getParentId())) {
return Result.error("所属分类不能为空");
}
// 校验所属分类是否存在
if (StrUtil.isNotBlank(entity.getParentId())) {
FirstAidResourceCategory parent = categoryService.getById(entity.getParentId());
if (parent == null || !FirstAidConstants.TYPE_CATEGORY.equals(parent.getType())) {
return Result.error("所属分类不存在");
}
FirstAidResourceCategory parent = categoryService.getById(entity.getParentId());
if (parent == null || !FirstAidConstants.TYPE_CATEGORY.equals(parent.getType())) {
return Result.error("所属分类不存在");
}
// 同分类下标签名称不能重复(排除自身)
QueryWrapper<FirstAidResourceCategory> checkWrapper = new QueryWrapper<FirstAidResourceCategory>()
.eq("name", entity.getName())
.eq("type", FirstAidConstants.TYPE_TAG)
.eq("parent_id", entity.getParentId())
.ne("id", entity.getId());
if (StrUtil.isNotBlank(entity.getParentId())) {
checkWrapper.eq("parent_id", entity.getParentId());
}
if (categoryService.count(checkWrapper) > 0) {
return Result.error("标签名称已存在");
}
@@ -18,9 +18,9 @@ import java.util.List;
public interface FirstAidResourceCategoryMapper extends BaseMapper<FirstAidResourceCategory> {
/**
* 查询某分类下的子分类数量(级联删除校验用)
* 查询某分类下的子分类/标签数量(级联删除校验用,含TYPE=1子分类和TYPE=2标签
*/
@Select("SELECT COUNT(*) FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE PARENT_ID = #{parentId} AND TYPE = 1 AND DEL_FLAG = 0")
@Select("SELECT COUNT(*) FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE PARENT_ID = #{parentId} AND DEL_FLAG = 0")
Integer countChildrenByParentId(@Param("parentId") String parentId);
/**
@@ -35,6 +35,18 @@ public interface FirstAidResourceCategoryMapper extends BaseMapper<FirstAidResou
@Select("SELECT COUNT(*) FROM QH_EME_FIRST_AID_RESOURCE WHERE DEL_FLAG = 0 AND TAGS LIKE CONCAT('%', #{tagId}, '%')")
Integer countResourcesByTagId(@Param("tagId") String tagId);
/**
* 查询所有有效分类和标签(树形组装用,含parentId)
*/
@Select("SELECT * FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE STATUS = 1 AND DEL_FLAG = 0 ORDER BY TYPE ASC, SORT_NO ASC")
List<FirstAidResourceCategory> selectAllCategoriesAndTags();
/**
* 按父ID查询子节点(含子分类TYPE=1和标签TYPE=2
*/
@Select("SELECT * FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE PARENT_ID = #{parentId} AND STATUS = 1 AND DEL_FLAG = 0 ORDER BY TYPE ASC, SORT_NO ASC")
List<FirstAidResourceCategory> selectByParentId(@Param("parentId") String parentId);
/**
* 按类型查询分类/标签列表
*/
@@ -62,4 +74,5 @@ public interface FirstAidResourceCategoryMapper extends BaseMapper<FirstAidResou
*/
@Select("SELECT * FROM QH_EME_FIRST_AID_RESOURCE_CATEGORY WHERE TYPE = 1 AND PARENT_ID IS NULL AND STATUS = 1 AND DEL_FLAG = 0 ORDER BY SORT_NO ASC")
List<FirstAidResourceCategory> selectRootCategories();
}
@@ -14,10 +14,20 @@ import java.util.List;
public interface IFirstAidResourceCategoryService extends IService<FirstAidResourceCategory> {
/**
* 查询所有有效分类(根分类+子分类)
* 查询所有有效分类(扁平,一级分类)
*/
List<FirstAidResourceCategory> listCategories();
/**
* 查询所有分类和标签(扁平,通过parentId组装树)
*/
List<FirstAidResourceCategory> listAllCategoriesAndTags();
/**
* 按父ID查询子节点(含子分类和标签,树形展开用)
*/
List<FirstAidResourceCategory> listByParentId(String parentId);
/**
* 查询根分类列表(树形结构顶层)
*/
@@ -28,13 +38,23 @@ public interface IFirstAidResourceCategoryService extends IService<FirstAidResou
*/
List<FirstAidResourceCategory> listChildrenByParentId(String parentId);
/**
* 查询所有有效分类及其下标签(嵌套结构)
*/
List<FirstAidResourceCategory> listCategoriesWithTags();
/**
* 查询所有有效标签列表(可按分类筛选)
*/
List<FirstAidResourceCategory> listTags(String parentId);
/**
* 删除前校验(分类下有子分类/资源则拒绝;标签有资源引用则拒绝)
* 填充分类列表的资源使用量
*/
void fillCategoryResourceCounts(List<FirstAidResourceCategory> categories);
/**
* 删除前校验(分类下有标签/资源则拒绝;标签有资源引用则拒绝)
*/
void deleteWithCheck(String id);
}
@@ -33,6 +33,36 @@ public class FirstAidResourceCategoryServiceImpl extends ServiceImpl<FirstAidRes
return list;
}
@Override
public List<FirstAidResourceCategory> listAllCategoriesAndTags() {
List<FirstAidResourceCategory> list = categoryMapper.selectAllCategoriesAndTags();
for (FirstAidResourceCategory item : list) {
if (FirstAidConstants.TYPE_CATEGORY.equals(item.getType())) {
Integer count = resourceMapper.countByCategoryId(item.getId());
item.setResourceCount(count != null ? count : 0);
} else {
Integer count = categoryMapper.countResourcesByTagId(item.getId());
item.setResourceCount(count != null ? count : 0);
}
}
return list;
}
@Override
public List<FirstAidResourceCategory> listByParentId(String parentId) {
List<FirstAidResourceCategory> list = categoryMapper.selectByParentId(parentId);
for (FirstAidResourceCategory item : list) {
if (FirstAidConstants.TYPE_CATEGORY.equals(item.getType())) {
Integer count = resourceMapper.countByCategoryId(item.getId());
item.setResourceCount(count != null ? count : 0);
} else {
Integer count = categoryMapper.countResourcesByTagId(item.getId());
item.setResourceCount(count != null ? count : 0);
}
}
return list;
}
@Override
public List<FirstAidResourceCategory> listRootCategories() {
List<FirstAidResourceCategory> list = categoryMapper.selectRootCategories();
@@ -42,9 +72,23 @@ public class FirstAidResourceCategoryServiceImpl extends ServiceImpl<FirstAidRes
@Override
public List<FirstAidResourceCategory> listChildrenByParentId(String parentId) {
List<FirstAidResourceCategory> list = categoryMapper.selectChildrenByParentId(parentId);
fillResourceCounts(list);
return list;
// 返回子分类(type=1)和标签(type=2)
return listByParentId(parentId);
}
@Override
public List<FirstAidResourceCategory> listCategoriesWithTags() {
List<FirstAidResourceCategory> categories = categoryMapper.selectByType(FirstAidConstants.TYPE_CATEGORY);
fillResourceCounts(categories);
for (FirstAidResourceCategory cat : categories) {
List<FirstAidResourceCategory> tags = categoryMapper.selectByTypeAndParent(FirstAidConstants.TYPE_TAG, cat.getId());
for (FirstAidResourceCategory tag : tags) {
Integer count = categoryMapper.countResourcesByTagId(tag.getId());
tag.setResourceCount(count != null ? count : 0);
}
cat.setTags(tags);
}
return categories;
}
@Override
@@ -67,6 +111,11 @@ public class FirstAidResourceCategoryServiceImpl extends ServiceImpl<FirstAidRes
}
}
@Override
public void fillCategoryResourceCounts(List<FirstAidResourceCategory> categories) {
fillResourceCounts(categories);
}
@Override
public void deleteWithCheck(String id) {
FirstAidResourceCategory record = getById(id);
@@ -75,10 +124,10 @@ public class FirstAidResourceCategoryServiceImpl extends ServiceImpl<FirstAidRes
}
if (FirstAidConstants.TYPE_CATEGORY.equals(record.getType())) {
// 分类:检查是否有子分类
// 分类:检查是否有标签
Integer childCount = categoryMapper.countChildrenByParentId(id);
if (childCount != null && childCount > 0) {
throw new RuntimeException("该分类下存在子分类,无法删除");
throw new RuntimeException("该分类下存在标签,无法删除");
}
// 分类:检查是否有资源
Integer resourceCount = resourceMapper.countByCategoryId(id);
@@ -141,15 +141,11 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
if (resource == null) {
throw new RuntimeException("资源不存在");
}
if (!FirstAidConstants.AUDIT_STATUS_APPROVED.equals(resource.getAuditStatus())) {
throw new RuntimeException("该资源尚未通过审核,无法上架");
}
resource.setStatus(FirstAidConstants.STATUS_PUBLISHED);
resource.setAuditStatus(FirstAidConstants.AUDIT_STATUS_APPROVED);
resource.setAuditRejectReason(null);
resource.setAuditBy(GlobalUtils.getLoginUser().getRealname());
resource.setAuditById(GlobalUtils.getLoginUser().getId());
resource.setAuditTime(new Date());
updateById(resource);
// 记录审核日志(直接发布视为审核通过)
auditService.saveAudit(id, FirstAidConstants.AUDIT_STATUS_APPROVED, null);
}
@Override
@@ -161,7 +157,6 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
}
resource.setAuditStatus(FirstAidConstants.AUDIT_STATUS_APPROVED);
resource.setAuditRejectReason(null);
resource.setStatus(FirstAidConstants.STATUS_PUBLISHED);
resource.setAuditBy(GlobalUtils.getLoginUser().getRealname());
resource.setAuditById(GlobalUtils.getLoginUser().getId());
resource.setAuditTime(new Date());
@@ -190,20 +185,16 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
@Override
@Transactional(rollbackFor = Exception.class)
public void batchPublish(List<String> ids) {
Date now = new Date();
String auditBy = GlobalUtils.getLoginUser().getRealname();
String auditById = GlobalUtils.getLoginUser().getId();
for (String id : ids) {
FirstAidResource resource = getById(id);
if (resource != null) {
resource.setStatus(FirstAidConstants.STATUS_PUBLISHED);
resource.setAuditStatus(FirstAidConstants.AUDIT_STATUS_APPROVED);
resource.setAuditBy(auditBy);
resource.setAuditById(auditById);
resource.setAuditTime(now);
resource.setAuditRejectReason(null);
updateById(resource);
if (resource == null) {
continue;
}
if (!FirstAidConstants.AUDIT_STATUS_APPROVED.equals(resource.getAuditStatus())) {
continue;
}
resource.setStatus(FirstAidConstants.STATUS_PUBLISHED);
updateById(resource);
}
}
@@ -344,10 +335,13 @@ public class FirstAidResourceServiceImpl extends ServiceImpl<FirstAidResourceMap
return "待审核";
}
if (FirstAidConstants.AUDIT_STATUS_APPROVED.equals(auditStatus)) {
if (FirstAidConstants.STATUS_PUBLISHED.equals(status)) {
return "已发布";
}
if (FirstAidConstants.STATUS_DOWN.equals(status)) {
return "已下架";
}
return "发布";
return "通过";
}
if (FirstAidConstants.AUDIT_STATUS_REJECTED.equals(auditStatus)) {
return "已驳回";