From b19acbd6d50a569d2715f1dad6e9bba8a437bcdb Mon Sep 17 00:00:00 2001 From: zhonghua Date: Thu, 9 Apr 2026 11:04:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BD=9C=E5=93=81=E5=88=86=E9=85=8D?= =?UTF-8?q?=E4=BB=85=E9=99=90=E6=B4=BB=E5=8A=A8=E8=AF=84=E5=A7=94=E3=80=81?= =?UTF-8?q?=E8=AF=84=E5=A7=94=E5=BA=93=E4=BB=85=E5=90=AF=E7=94=A8=E5=8F=8A?= =?UTF-8?q?=20UGC=20=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 作品管理分配评委仅使用活动显式名单,assignWork 校验 t_biz_contest_judge - 添加评委/评审进度选择评委时仅查询启用账号;接口文档与 API 注释 - UGC 作品分页与公开创作服务相关改动 Made-with: Cursor --- .../JudgesManagementController.java | 3 +- .../controller/ContestJudgeController.java | 2 +- .../impl/ContestReviewServiceImpl.java | 15 +++ .../pub/service/PublicCreationService.java | 2 + .../pub/service/PublicUserWorkService.java | 29 +++++ .../modules/ugc/entity/UgcWork.java | 16 +++ .../ugc/mapper/UgcWorkPageCountRow.java | 12 ++ .../modules/ugc/mapper/UgcWorkPageMapper.java | 14 +++ frontend/src/api/contests.ts | 7 +- frontend/src/api/judges-management.ts | 2 +- .../contests/components/AddJudgeDrawer.vue | 2 + .../views/contests/reviews/ProgressDetail.vue | 1 + .../src/views/contests/works/WorksDetail.vue | 116 ++++++++++++++---- 13 files changed, 194 insertions(+), 27 deletions(-) create mode 100644 backend-java/src/main/java/com/competition/modules/ugc/mapper/UgcWorkPageCountRow.java diff --git a/backend-java/src/main/java/com/competition/modules/biz/judge/controller/JudgesManagementController.java b/backend-java/src/main/java/com/competition/modules/biz/judge/controller/JudgesManagementController.java index 87338ff..055dffb 100644 --- a/backend-java/src/main/java/com/competition/modules/biz/judge/controller/JudgesManagementController.java +++ b/backend-java/src/main/java/com/competition/modules/biz/judge/controller/JudgesManagementController.java @@ -30,7 +30,8 @@ public class JudgesManagementController { @GetMapping @RequirePermission("judge:read") - @Operation(summary = "查询评委列表") + @Operation(summary = "查询评委列表", + description = "status:enabled/disabled,不传则返回全部状态。向活动添加评委、评审进度等「仅用于选择评委」的场景请传 status=enabled,不包含停用账号。") public Result>> findAll( @RequestParam(defaultValue = "1") Long page, @RequestParam(defaultValue = "10") Long pageSize, diff --git a/backend-java/src/main/java/com/competition/modules/biz/review/controller/ContestJudgeController.java b/backend-java/src/main/java/com/competition/modules/biz/review/controller/ContestJudgeController.java index c2abf68..efaef3a 100644 --- a/backend-java/src/main/java/com/competition/modules/biz/review/controller/ContestJudgeController.java +++ b/backend-java/src/main/java/com/competition/modules/biz/review/controller/ContestJudgeController.java @@ -36,7 +36,7 @@ public class ContestJudgeController { @GetMapping("/contest/{contestId}") @RequirePermission("contest:read") @Operation(summary = "查询活动评委列表", - description = "返回 assigned(显式关联)与 implicitPool(平台隐式池)。添加评委抽屉仅用 assigned 回显;作品分配可选池为 assigned ∪ implicitPool(前端合并)。") + description = "返回 assigned(t_biz_contest_judge 显式关联)与 implicitPool(平台评委租户下启用用户,未写入关联表时由业务视为可选池)。添加评委抽屉仅用 assigned 回显;作品管理「分配评委」仅应使用 assigned。") public Result findByContest(@PathVariable Long contestId) { return Result.success(contestJudgeService.findByContest(contestId)); } diff --git a/backend-java/src/main/java/com/competition/modules/biz/review/service/impl/ContestReviewServiceImpl.java b/backend-java/src/main/java/com/competition/modules/biz/review/service/impl/ContestReviewServiceImpl.java index ae3245e..6d0a21f 100644 --- a/backend-java/src/main/java/com/competition/modules/biz/review/service/impl/ContestReviewServiceImpl.java +++ b/backend-java/src/main/java/com/competition/modules/biz/review/service/impl/ContestReviewServiceImpl.java @@ -50,6 +50,19 @@ public class ContestReviewServiceImpl implements IContestReviewService { private final ContestReviewRuleMapper reviewRuleMapper; private final SysUserMapper sysUserMapper; + /** + * 仅允许将「已在本活动评委表 t_biz_contest_judge 中显式配置」的评委新分配到作品。 + */ + private void assertJudgeAssignedToContest(Long contestId, Long judgeId) { + LambdaQueryWrapper w = new LambdaQueryWrapper<>(); + w.eq(BizContestJudge::getContestId, contestId); + w.eq(BizContestJudge::getJudgeId, judgeId); + w.eq(BizContestJudge::getValidState, 1); + if (judgeMapper.selectCount(w) == 0) { + throw BusinessException.of(ErrorCode.BAD_REQUEST, "所选评委未加入本活动,请先在活动评委管理中配置"); + } + } + // ====== 作品分配 ====== @Override @@ -99,6 +112,8 @@ public class ContestReviewServiceImpl implements IContestReviewService { continue; } + assertJudgeAssignedToContest(contestId, judgeId); + BizContestWorkJudgeAssignment assignment = new BizContestWorkJudgeAssignment(); assignment.setContestId(contestId); assignment.setWorkId(workId); diff --git a/backend-java/src/main/java/com/competition/modules/pub/service/PublicCreationService.java b/backend-java/src/main/java/com/competition/modules/pub/service/PublicCreationService.java index 1a5463b..153b823 100644 --- a/backend-java/src/main/java/com/competition/modules/pub/service/PublicCreationService.java +++ b/backend-java/src/main/java/com/competition/modules/pub/service/PublicCreationService.java @@ -24,6 +24,7 @@ public class PublicCreationService { private final UgcWorkMapper ugcWorkMapper; private final UgcWorkPageMapper ugcWorkPageMapper; + private final PublicUserWorkService publicUserWorkService; /** * 提交 AI 创作(保留但降级为辅助接口) @@ -118,6 +119,7 @@ public class PublicCreationService { .orderByDesc(UgcWork::getCreateTime); IPage result = ugcWorkMapper.selectPage(new Page<>(page, pageSize), wrapper); + publicUserWorkService.attachPageCounts(result.getRecords()); return PageResult.from(result); } } diff --git a/backend-java/src/main/java/com/competition/modules/pub/service/PublicUserWorkService.java b/backend-java/src/main/java/com/competition/modules/pub/service/PublicUserWorkService.java index cade25d..843b7d1 100644 --- a/backend-java/src/main/java/com/competition/modules/pub/service/PublicUserWorkService.java +++ b/backend-java/src/main/java/com/competition/modules/pub/service/PublicUserWorkService.java @@ -11,6 +11,7 @@ import com.competition.modules.ugc.entity.UgcWork; import com.competition.modules.ugc.entity.UgcWorkPage; import com.competition.modules.ugc.entity.UgcWorkTag; import com.competition.modules.ugc.mapper.UgcWorkMapper; +import com.competition.modules.ugc.mapper.UgcWorkPageCountRow; import com.competition.modules.ugc.mapper.UgcWorkPageMapper; import com.competition.modules.ugc.mapper.UgcWorkTagMapper; import lombok.RequiredArgsConstructor; @@ -21,6 +22,7 @@ import org.springframework.util.StringUtils; import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; @Slf4j @Service @@ -89,9 +91,36 @@ public class PublicUserWorkService { wrapper.orderByDesc(UgcWork::getCreateTime); IPage result = ugcWorkMapper.selectPage(new Page<>(page, pageSize), wrapper); + fillPageCounts(result.getRecords()); return PageResult.from(result); } + /** + * 为作品列表填充 _count.pages(供其它模块复用,如创作历史) + */ + public void attachPageCounts(List works) { + fillPageCounts(works); + } + + /** 为列表中的每条作品填充 _count.pages(来自 t_ugc_work_page 行数) */ + private void fillPageCounts(List works) { + if (works == null || works.isEmpty()) { + return; + } + List ids = works.stream().map(UgcWork::getId).collect(Collectors.toList()); + List rows = ugcWorkPageMapper.countPagesGroupedByWorkIds(ids); + Map idToPages = rows.stream() + .collect(Collectors.toMap( + UgcWorkPageCountRow::getWorkId, + r -> r.getPageCount() != null ? r.getPageCount().intValue() : 0, + (a, b) -> a)); + for (UgcWork work : works) { + UgcWork.WorkCountMeta meta = new UgcWork.WorkCountMeta(); + meta.setPages(idToPages.getOrDefault(work.getId(), 0)); + work.setCount(meta); + } + } + /** * 获取作品详情 */ diff --git a/backend-java/src/main/java/com/competition/modules/ugc/entity/UgcWork.java b/backend-java/src/main/java/com/competition/modules/ugc/entity/UgcWork.java index 2ef0462..7df5643 100644 --- a/backend-java/src/main/java/com/competition/modules/ugc/entity/UgcWork.java +++ b/backend-java/src/main/java/com/competition/modules/ugc/entity/UgcWork.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -137,4 +138,19 @@ public class UgcWork implements Serializable { @Schema(description = "修改时间") @TableField("modify_time") private LocalDateTime modifyTime; + + /** + * 列表接口填充:绘本页数等(非表字段,与前端 _count.pages 对齐) + */ + @Schema(description = "统计信息(列表接口填充)") + @TableField(exist = false) + @JsonProperty("_count") + private WorkCountMeta count; + + @Data + @Schema(description = "作品计数") + public static class WorkCountMeta implements Serializable { + @Schema(description = "绘本页数") + private Integer pages; + } } diff --git a/backend-java/src/main/java/com/competition/modules/ugc/mapper/UgcWorkPageCountRow.java b/backend-java/src/main/java/com/competition/modules/ugc/mapper/UgcWorkPageCountRow.java new file mode 100644 index 0000000..11fac78 --- /dev/null +++ b/backend-java/src/main/java/com/competition/modules/ugc/mapper/UgcWorkPageCountRow.java @@ -0,0 +1,12 @@ +package com.competition.modules.ugc.mapper; + +import lombok.Data; + +/** + * 按作品分组的绘本页数统计(列表接口用) + */ +@Data +public class UgcWorkPageCountRow { + private Long workId; + private Long pageCount; +} diff --git a/backend-java/src/main/java/com/competition/modules/ugc/mapper/UgcWorkPageMapper.java b/backend-java/src/main/java/com/competition/modules/ugc/mapper/UgcWorkPageMapper.java index 3f33757..e0924ce 100644 --- a/backend-java/src/main/java/com/competition/modules/ugc/mapper/UgcWorkPageMapper.java +++ b/backend-java/src/main/java/com/competition/modules/ugc/mapper/UgcWorkPageMapper.java @@ -3,7 +3,21 @@ package com.competition.modules.ugc.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.competition.modules.ugc.entity.UgcWorkPage; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; @Mapper public interface UgcWorkPageMapper extends BaseMapper { + + /** + * 批量统计各作品的绘本页数(用于作品列表展示) + */ + @Select("") + List countPagesGroupedByWorkIds(@Param("workIds") List workIds); } diff --git a/frontend/src/api/contests.ts b/frontend/src/api/contests.ts index 519d055..f76e51c 100644 --- a/frontend/src/api/contests.ts +++ b/frontend/src/api/contests.ts @@ -551,6 +551,8 @@ export interface ContestJudge { validState?: number; /** 隐式平台评委(未写入关联表时由后端追加) */ isPlatform?: boolean; + /** 作品分配抽屉:历史隐式池分配回显(未在活动评委表中显式配置) */ + legacyImplicit?: boolean; judgeName?: string; judgeUsername?: string; assignedCount?: number; @@ -591,7 +593,10 @@ export interface ContestJudgesForContestResponse { implicitPool: ContestJudge[]; } -/** 作品分配等场景:合并为可选评委池(assigned ∪ implicitPool) */ +/** + * 合并 assigned 与 implicitPool。作品管理「分配评委」应仅使用 `assigned`,勿用本函数。 + * 添加评委抽屉等场景如需「全平台可选」再按需合并。 + */ export function flattenContestJudgePool( r: ContestJudgesForContestResponse, ): ContestJudge[] { diff --git a/frontend/src/api/judges-management.ts b/frontend/src/api/judges-management.ts index e8090b5..263a707 100644 --- a/frontend/src/api/judges-management.ts +++ b/frontend/src/api/judges-management.ts @@ -74,7 +74,7 @@ export interface JudgeListResponse { pageSize: number; } -// 获取评委列表 +// 获取评委列表(评委管理页可不传 status 以查看全部;添加评委/选择评委等场景传 status: 'enabled') export async function getJudgesList( params: QueryJudgeParams ): Promise { diff --git a/frontend/src/views/contests/components/AddJudgeDrawer.vue b/frontend/src/views/contests/components/AddJudgeDrawer.vue index 26f1f65..caefe83 100644 --- a/frontend/src/views/contests/components/AddJudgeDrawer.vue +++ b/frontend/src/views/contests/components/AddJudgeDrawer.vue @@ -195,6 +195,8 @@ const loadJudges = async () => { pageSize: judgePagination.pageSize, nickname: searchParams.nickname || undefined, organization: searchParams.organization || undefined, + /** 仅可选择启用中的评委,停用账号不展示 */ + status: "enabled", } const res = await judgesManagementApi.getList(params) judgeList.value = res.list diff --git a/frontend/src/views/contests/reviews/ProgressDetail.vue b/frontend/src/views/contests/reviews/ProgressDetail.vue index 4a30a42..7632db7 100644 --- a/frontend/src/views/contests/reviews/ProgressDetail.vue +++ b/frontend/src/views/contests/reviews/ProgressDetail.vue @@ -464,6 +464,7 @@ const fetchJudgeList = async () => { page: judgePagination.current, pageSize: judgePagination.pageSize, nickname: judgeSearchParams.nickname || undefined, + status: "enabled", }) judgeList.value = response.list judgePagination.total = response.total diff --git a/frontend/src/views/contests/works/WorksDetail.vue b/frontend/src/views/contests/works/WorksDetail.vue index caa5b2f..e4731de 100644 --- a/frontend/src/views/contests/works/WorksDetail.vue +++ b/frontend/src/views/contests/works/WorksDetail.vue @@ -169,17 +169,28 @@ - + + + - -