feat: 预设评语按当前评委查询,去除活动筛选与重复评委活动接口
Made-with: Cursor
This commit is contained in:
parent
2c2ff602b0
commit
b323a82cbb
@ -47,6 +47,22 @@
|
||||
|
||||
避免「列表能进、详情 403」与隐式评委场景不一致。
|
||||
|
||||
## 预设评语(跨活动同步)
|
||||
|
||||
与 [菜单配置说明](../menu-config.md) 中「预设评语」能力一致:评委维护常用评语模板,**不按活动筛选**;列表与评审弹窗「老师点评 → 选择评语」使用**同一查询**。
|
||||
|
||||
**列表**:`GET /contests/preset-comments`
|
||||
|
||||
- 按当前登录用户的评委身份(`judge_id`)返回其全部有效预设评语。
|
||||
- **不要**使用 query 参数 `contestId`(接口不提供按活动筛选)。
|
||||
|
||||
**创建**:`POST /contests/preset-comments`
|
||||
|
||||
- 请求体中 `contestId` **可选**;不传或为 `null` 表示全局模板(库表 `t_biz_preset_comment.contest_id` 为空,所有活动评审可选用)。
|
||||
- `content` 必填。
|
||||
|
||||
**其它**:详情/修改/删除/批量删除/增加使用次数等见 `PresetCommentController`(Knife4j)。
|
||||
|
||||
## 标记作品违规(活动参赛作品)
|
||||
|
||||
**接口**:`POST /contests/reviews/work/{workId}/violation`
|
||||
|
||||
@ -3,7 +3,7 @@ package com.lesingle.modules.biz.review.controller;
|
||||
import com.lesingle.common.result.Result;
|
||||
import com.lesingle.common.util.SecurityUtil;
|
||||
import com.lesingle.modules.biz.review.dto.CreatePresetCommentDto;
|
||||
import com.lesingle.modules.biz.review.dto.SyncPresetCommentsDto;
|
||||
import com.lesingle.modules.biz.review.dto.PresetCommentVo;
|
||||
import com.lesingle.modules.biz.review.entity.BizPresetComment;
|
||||
import com.lesingle.modules.biz.review.service.IPresetCommentService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@ -31,22 +31,15 @@ public class PresetCommentController {
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "查询预设评语列表")
|
||||
public Result<List<Map<String, Object>>> findAll(@RequestParam(required = false) Long contestId) {
|
||||
@Operation(summary = "查询当前登录评委的预设评语列表(不按活动筛选)")
|
||||
public Result<List<PresetCommentVo>> findAll() {
|
||||
Long judgeId = SecurityUtil.getCurrentUserId();
|
||||
return Result.success(presetCommentService.findAll(contestId, judgeId));
|
||||
}
|
||||
|
||||
@GetMapping("/judge/contests")
|
||||
@Operation(summary = "获取评委参与的活动列表")
|
||||
public Result<List<Map<String, Object>>> getJudgeContests() {
|
||||
Long judgeId = SecurityUtil.getCurrentUserId();
|
||||
return Result.success(presetCommentService.getJudgeContests(judgeId));
|
||||
return Result.success(presetCommentService.findAll(judgeId));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "查询预设评语详情")
|
||||
public Result<Map<String, Object>> findDetail(@PathVariable Long id) {
|
||||
public Result<PresetCommentVo> findDetail(@PathVariable Long id) {
|
||||
Long judgeId = SecurityUtil.getCurrentUserId();
|
||||
return Result.success(presetCommentService.findDetail(id, judgeId));
|
||||
}
|
||||
@ -75,13 +68,6 @@ public class PresetCommentController {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@PostMapping("/sync")
|
||||
@Operation(summary = "同步评语到其他活动")
|
||||
public Result<Map<String, Object>> syncComments(@Valid @RequestBody SyncPresetCommentsDto dto) {
|
||||
Long judgeId = SecurityUtil.getCurrentUserId();
|
||||
return Result.success(presetCommentService.syncComments(dto.getSourceContestId(), dto.getTargetContestIds(), judgeId));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/use")
|
||||
@Operation(summary = "增加评语使用次数")
|
||||
public Result<Void> incrementUseCount(@PathVariable Long id) {
|
||||
|
||||
@ -2,7 +2,6 @@ package com.lesingle.modules.biz.review.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@ -11,8 +10,7 @@ import java.math.BigDecimal;
|
||||
@Schema(description = "创建预设评语DTO")
|
||||
public class CreatePresetCommentDto {
|
||||
|
||||
@NotNull(message = "活动ID不能为空")
|
||||
@Schema(description = "活动ID")
|
||||
@Schema(description = "活动ID;不传或为 null 表示全局模板(跨活动复用)")
|
||||
private Long contestId;
|
||||
|
||||
@NotBlank(message = "评语内容不能为空")
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
package com.lesingle.modules.biz.review.dto;
|
||||
|
||||
import com.lesingle.modules.biz.review.entity.BizPresetComment;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 预设评语展示对象,与「预设评语管理」列表列一致:序号(前端本地)、评语内容、使用次数、操作依赖 id。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "预设评语")
|
||||
public class PresetCommentVo {
|
||||
|
||||
@Schema(description = "主键")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "活动ID,可为空")
|
||||
private Long contestId;
|
||||
|
||||
@Schema(description = "评委用户ID")
|
||||
private Long judgeId;
|
||||
|
||||
@Schema(description = "评语内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "关联分数")
|
||||
private BigDecimal score;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sortOrder;
|
||||
|
||||
@Schema(description = "使用次数")
|
||||
private Integer useCount;
|
||||
|
||||
@Schema(description = "有效状态:1-有效")
|
||||
private Integer validState;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "修改时间")
|
||||
private LocalDateTime modifyTime;
|
||||
|
||||
public static PresetCommentVo fromEntity(BizPresetComment entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
PresetCommentVo vo = new PresetCommentVo();
|
||||
vo.setId(entity.getId());
|
||||
vo.setContestId(entity.getContestId());
|
||||
vo.setJudgeId(entity.getJudgeId());
|
||||
vo.setContent(entity.getContent());
|
||||
vo.setScore(entity.getScore());
|
||||
vo.setSortOrder(entity.getSortOrder());
|
||||
vo.setUseCount(entity.getUseCount());
|
||||
vo.setValidState(entity.getValidState());
|
||||
vo.setCreateTime(entity.getCreateTime());
|
||||
vo.setModifyTime(entity.getModifyTime());
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
package com.lesingle.modules.biz.review.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "同步预设评语DTO")
|
||||
public class SyncPresetCommentsDto {
|
||||
|
||||
@NotNull(message = "源活动ID不能为空")
|
||||
@Schema(description = "源活动ID")
|
||||
private Long sourceContestId;
|
||||
|
||||
@NotEmpty(message = "目标活动列表不能为空")
|
||||
@Schema(description = "目标活动ID列表")
|
||||
private List<Long> targetContestIds;
|
||||
}
|
||||
@ -2,18 +2,18 @@ package com.lesingle.modules.biz.review.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.lesingle.modules.biz.review.dto.CreatePresetCommentDto;
|
||||
import com.lesingle.modules.biz.review.dto.PresetCommentVo;
|
||||
import com.lesingle.modules.biz.review.entity.BizPresetComment;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IPresetCommentService extends IService<BizPresetComment> {
|
||||
|
||||
BizPresetComment createComment(CreatePresetCommentDto dto, Long judgeId);
|
||||
|
||||
List<Map<String, Object>> findAll(Long contestId, Long judgeId);
|
||||
List<PresetCommentVo> findAll(Long judgeId);
|
||||
|
||||
Map<String, Object> findDetail(Long id, Long judgeId);
|
||||
PresetCommentVo findDetail(Long id, Long judgeId);
|
||||
|
||||
BizPresetComment updateComment(Long id, CreatePresetCommentDto dto, Long judgeId);
|
||||
|
||||
@ -22,8 +22,4 @@ public interface IPresetCommentService extends IService<BizPresetComment> {
|
||||
void batchDelete(List<Long> ids, Long judgeId);
|
||||
|
||||
void incrementUseCount(Long id, Long judgeId);
|
||||
|
||||
List<Map<String, Object>> getJudgeContests(Long judgeId);
|
||||
|
||||
Map<String, Object> syncComments(Long sourceContestId, List<Long> targetContestIds, Long judgeId);
|
||||
}
|
||||
|
||||
@ -6,29 +6,20 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.lesingle.common.enums.ErrorCode;
|
||||
import com.lesingle.common.exception.BusinessException;
|
||||
import com.lesingle.modules.biz.review.dto.CreatePresetCommentDto;
|
||||
import com.lesingle.modules.biz.contest.entity.BizContest;
|
||||
import com.lesingle.modules.biz.contest.mapper.ContestMapper;
|
||||
import com.lesingle.modules.biz.review.entity.BizContestJudge;
|
||||
import com.lesingle.modules.biz.review.dto.PresetCommentVo;
|
||||
import com.lesingle.modules.biz.review.entity.BizPresetComment;
|
||||
import com.lesingle.modules.biz.review.mapper.ContestJudgeMapper;
|
||||
import com.lesingle.modules.biz.review.mapper.PresetCommentMapper;
|
||||
import com.lesingle.modules.biz.review.service.IPresetCommentService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PresetCommentServiceImpl extends ServiceImpl<PresetCommentMapper, BizPresetComment> implements IPresetCommentService {
|
||||
|
||||
private final PresetCommentMapper presetCommentMapper;
|
||||
private final ContestJudgeMapper contestJudgeMapper;
|
||||
private final ContestMapper contestMapper;
|
||||
|
||||
@Override
|
||||
public BizPresetComment createComment(CreatePresetCommentDto dto, Long judgeId) {
|
||||
log.info("创建预设评语,评委ID:{},活动ID:{}", judgeId, dto.getContestId());
|
||||
@ -47,24 +38,21 @@ public class PresetCommentServiceImpl extends ServiceImpl<PresetCommentMapper, B
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> findAll(Long contestId, Long judgeId) {
|
||||
log.info("查询预设评语列表,活动ID:{},评委ID:{}", contestId, judgeId);
|
||||
public List<PresetCommentVo> findAll(Long judgeId) {
|
||||
log.info("查询预设评语列表(当前评委全部),评委ID:{}", judgeId);
|
||||
|
||||
LambdaQueryWrapper<BizPresetComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(BizPresetComment::getValidState, 1);
|
||||
wrapper.eq(BizPresetComment::getJudgeId, judgeId);
|
||||
if (contestId != null) {
|
||||
wrapper.eq(BizPresetComment::getContestId, contestId);
|
||||
}
|
||||
wrapper.orderByAsc(BizPresetComment::getSortOrder);
|
||||
wrapper.orderByDesc(BizPresetComment::getUseCount);
|
||||
|
||||
List<BizPresetComment> list = presetCommentMapper.selectList(wrapper);
|
||||
return list.stream().map(this::entityToMap).collect(Collectors.toList());
|
||||
List<BizPresetComment> rows = list(wrapper);
|
||||
return rows.stream().map(PresetCommentVo::fromEntity).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> findDetail(Long id, Long judgeId) {
|
||||
public PresetCommentVo findDetail(Long id, Long judgeId) {
|
||||
log.info("查询预设评语详情,ID:{},评委ID:{}", id, judgeId);
|
||||
|
||||
BizPresetComment entity = getById(id);
|
||||
@ -74,7 +62,7 @@ public class PresetCommentServiceImpl extends ServiceImpl<PresetCommentMapper, B
|
||||
if (!entity.getJudgeId().equals(judgeId)) {
|
||||
throw BusinessException.of(ErrorCode.FORBIDDEN, "无权查看此评语");
|
||||
}
|
||||
return entityToMap(entity);
|
||||
return PresetCommentVo.fromEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -153,82 +141,4 @@ public class PresetCommentServiceImpl extends ServiceImpl<PresetCommentMapper, B
|
||||
update(updateWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getJudgeContests(Long judgeId) {
|
||||
log.info("查询评委关联活动(预设评语视角),评委ID:{}", judgeId);
|
||||
|
||||
LambdaQueryWrapper<BizContestJudge> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(BizContestJudge::getJudgeId, judgeId);
|
||||
wrapper.eq(BizContestJudge::getValidState, 1);
|
||||
|
||||
List<BizContestJudge> judgeRecords = contestJudgeMapper.selectList(wrapper);
|
||||
|
||||
return judgeRecords.stream().map(j -> {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("id", j.getContestId());
|
||||
// 查询活动详情获取名称和状态
|
||||
BizContest contest = contestMapper.selectById(j.getContestId());
|
||||
if (contest != null) {
|
||||
map.put("contestName", contest.getContestName());
|
||||
map.put("contestState", contest.getContestState());
|
||||
map.put("status", contest.getStatus());
|
||||
}
|
||||
return map;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> syncComments(Long sourceContestId, List<Long> targetContestIds, Long judgeId) {
|
||||
log.info("同步预设评语,源活动ID:{},目标活动数:{},评委ID:{}", sourceContestId, targetContestIds.size(), judgeId);
|
||||
|
||||
// 查询源活动的评语
|
||||
LambdaQueryWrapper<BizPresetComment> sourceWrapper = new LambdaQueryWrapper<>();
|
||||
sourceWrapper.eq(BizPresetComment::getContestId, sourceContestId);
|
||||
sourceWrapper.eq(BizPresetComment::getJudgeId, judgeId);
|
||||
sourceWrapper.eq(BizPresetComment::getValidState, 1);
|
||||
List<BizPresetComment> sourceComments = presetCommentMapper.selectList(sourceWrapper);
|
||||
|
||||
if (sourceComments.isEmpty()) {
|
||||
throw BusinessException.of(ErrorCode.BAD_REQUEST, "源活动没有预设评语");
|
||||
}
|
||||
|
||||
int created = 0;
|
||||
for (Long targetContestId : targetContestIds) {
|
||||
for (BizPresetComment source : sourceComments) {
|
||||
BizPresetComment copy = new BizPresetComment();
|
||||
copy.setContestId(targetContestId);
|
||||
copy.setJudgeId(judgeId);
|
||||
copy.setContent(source.getContent());
|
||||
copy.setScore(source.getScore());
|
||||
copy.setSortOrder(source.getSortOrder());
|
||||
copy.setUseCount(0);
|
||||
|
||||
save(copy);
|
||||
created++;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("预设评语同步完成,新建数量:{}", created);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("message", "同步成功");
|
||||
result.put("count", created);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ====== 私有辅助方法 ======
|
||||
|
||||
private Map<String, Object> entityToMap(BizPresetComment entity) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("id", entity.getId());
|
||||
map.put("contestId", entity.getContestId());
|
||||
map.put("judgeId", entity.getJudgeId());
|
||||
map.put("content", entity.getContent());
|
||||
map.put("score", entity.getScore());
|
||||
map.put("sortOrder", entity.getSortOrder());
|
||||
map.put("useCount", entity.getUseCount());
|
||||
map.put("createTime", entity.getCreateTime());
|
||||
map.put("modifyTime", entity.getModifyTime());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,21 +2,42 @@ import request from "@/utils/request";
|
||||
|
||||
export interface PresetComment {
|
||||
id: number;
|
||||
contestId: number;
|
||||
/** 可能为 null(库表允许活动为空) */
|
||||
contestId: number | null;
|
||||
judgeId: number;
|
||||
content: string;
|
||||
score?: number;
|
||||
sortOrder: number;
|
||||
useCount: number;
|
||||
validState: number;
|
||||
validState?: number;
|
||||
creator?: number;
|
||||
modifier?: number;
|
||||
createTime: string;
|
||||
modifyTime: string;
|
||||
createTime?: string;
|
||||
modifyTime?: string;
|
||||
}
|
||||
|
||||
/** 统一列表项字段类型,避免后端 Long/字符串与表格展示不一致 */
|
||||
function normalizePresetCommentRow(raw: Record<string, unknown>): PresetComment {
|
||||
return {
|
||||
id: Number(raw.id),
|
||||
contestId:
|
||||
raw.contestId === undefined || raw.contestId === null
|
||||
? null
|
||||
: Number(raw.contestId),
|
||||
judgeId: Number(raw.judgeId ?? 0),
|
||||
content: String(raw.content ?? ""),
|
||||
score: raw.score != null ? Number(raw.score) : undefined,
|
||||
sortOrder: Number(raw.sortOrder ?? 0),
|
||||
useCount: Number(raw.useCount ?? 0),
|
||||
validState: raw.validState != null ? Number(raw.validState) : undefined,
|
||||
createTime: raw.createTime as string | undefined,
|
||||
modifyTime: raw.modifyTime as string | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreatePresetCommentParams {
|
||||
contestId: number;
|
||||
/** 不传或为 null 表示全局模板(跨活动) */
|
||||
contestId?: number | null;
|
||||
content: string;
|
||||
score?: number;
|
||||
sortOrder?: number;
|
||||
@ -28,29 +49,13 @@ export interface UpdatePresetCommentParams {
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
export interface SyncPresetCommentsParams {
|
||||
sourceContestId: number;
|
||||
targetContestIds: number[];
|
||||
}
|
||||
|
||||
export interface JudgeContest {
|
||||
id: number;
|
||||
contestName: string;
|
||||
contestState: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
// 获取预设评语列表
|
||||
export async function getPresetCommentsList(
|
||||
contestId: number
|
||||
): Promise<PresetComment[]> {
|
||||
const response = await request.get<any, PresetComment[]>(
|
||||
"/contests/preset-comments",
|
||||
{
|
||||
params: { contestId },
|
||||
}
|
||||
/** 列表:不带 query,按当前登录评委返回全部预设评语(与评审弹窗「选择评语」一致) */
|
||||
export async function getPresetCommentsList(): Promise<PresetComment[]> {
|
||||
const response = await request.get<any, Record<string, unknown>[]>(
|
||||
"/contests/preset-comments"
|
||||
);
|
||||
return response;
|
||||
const list = Array.isArray(response) ? response : [];
|
||||
return list.map((row) => normalizePresetCommentRow(row));
|
||||
}
|
||||
|
||||
// 获取单个预设评语详情
|
||||
@ -98,25 +103,6 @@ export async function batchDeletePresetComments(ids: number[]): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
// 同步预设评语到其他活动
|
||||
export async function syncPresetComments(
|
||||
data: SyncPresetCommentsParams
|
||||
): Promise<{ message: string; count: number }> {
|
||||
const response = await request.post<any, { message: string; count: number }>(
|
||||
"/contests/preset-comments/sync",
|
||||
data
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 获取评委的活动列表
|
||||
export async function getJudgeContests(): Promise<JudgeContest[]> {
|
||||
const response = await request.get<any, JudgeContest[]>(
|
||||
"/contests/preset-comments/judge/contests"
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 增加使用次数
|
||||
export async function incrementUseCount(id: number): Promise<PresetComment> {
|
||||
const response = await request.post<any, PresetComment>(
|
||||
@ -133,7 +119,5 @@ export const presetCommentsApi = {
|
||||
update: updatePresetComment,
|
||||
delete: deletePresetComment,
|
||||
batchDelete: batchDeletePresetComments,
|
||||
sync: syncPresetComments,
|
||||
getJudgeContests: getJudgeContests,
|
||||
incrementUseCount: incrementUseCount,
|
||||
};
|
||||
|
||||
@ -4,11 +4,7 @@
|
||||
<template #title>预设评语管理</template>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!currentContestId"
|
||||
@click="handleAdd"
|
||||
>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增
|
||||
</a-button>
|
||||
@ -22,13 +18,6 @@
|
||||
删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
:disabled="!currentContestId || dataSource.length === 0"
|
||||
@click="handleOpenSync"
|
||||
>
|
||||
<template #icon><SyncOutlined /></template>
|
||||
同步到其他活动
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-card>
|
||||
@ -99,59 +88,15 @@
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 同步评语弹框 -->
|
||||
<a-modal
|
||||
v-model:open="syncModalVisible"
|
||||
title="同步评语到其他活动"
|
||||
:confirm-loading="syncLoading"
|
||||
@ok="handleSync"
|
||||
@cancel="syncModalVisible = false"
|
||||
>
|
||||
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<a-form-item label="目标活动">
|
||||
<a-select
|
||||
v-model:value="syncTargetContestIds"
|
||||
mode="multiple"
|
||||
placeholder="请选择要同步到的活动"
|
||||
style="width: 100%"
|
||||
:options="syncContestOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-alert
|
||||
type="info"
|
||||
show-icon
|
||||
message="提示"
|
||||
description="同步将把当前活动的所有预设评语复制到选中的目标活动中"
|
||||
style="margin-top: 16px"
|
||||
/>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from "vue"
|
||||
import { useRoute } from "vue-router"
|
||||
import { message } from "ant-design-vue"
|
||||
import type { FormInstance, TableProps } from "ant-design-vue"
|
||||
import {
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
SyncOutlined,
|
||||
} from "@ant-design/icons-vue"
|
||||
import {
|
||||
presetCommentsApi,
|
||||
type PresetComment,
|
||||
type JudgeContest,
|
||||
} from "@/api/preset-comments"
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 活动相关
|
||||
const contestsList = ref<JudgeContest[]>([])
|
||||
const contestsLoading = ref(false)
|
||||
const currentContestId = ref<number | undefined>(undefined)
|
||||
import { PlusOutlined, DeleteOutlined } from "@ant-design/icons-vue"
|
||||
import { presetCommentsApi, type PresetComment } from "@/api/preset-comments"
|
||||
|
||||
// 表格数据
|
||||
const loading = ref(false)
|
||||
@ -184,20 +129,6 @@ const rules = {
|
||||
content: [{ required: true, message: "请输入评语内容", trigger: "blur" }],
|
||||
}
|
||||
|
||||
// 同步弹框相关
|
||||
const syncModalVisible = ref(false)
|
||||
const syncLoading = ref(false)
|
||||
const syncTargetContestIds = ref<number[]>([])
|
||||
|
||||
const syncContestOptions = computed(() => {
|
||||
return contestsList.value
|
||||
.filter((c) => c.id !== currentContestId.value)
|
||||
.map((c) => ({
|
||||
value: c.id,
|
||||
label: c.contestName,
|
||||
}))
|
||||
})
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
@ -225,47 +156,23 @@ const columns = [
|
||||
},
|
||||
]
|
||||
|
||||
// 加载评委的活动列表
|
||||
const loadContests = async () => {
|
||||
contestsLoading.value = true
|
||||
try {
|
||||
const data = await presetCommentsApi.getJudgeContests()
|
||||
contestsList.value = data
|
||||
|
||||
// 从 URL 参数获取 contestId
|
||||
const urlContestId = route.query.contestId
|
||||
? Number(route.query.contestId)
|
||||
: null
|
||||
|
||||
// 如果 URL 有 contestId 且在列表中存在,选中它;否则选第一个
|
||||
if (urlContestId && data.some((c) => c.id === urlContestId)) {
|
||||
currentContestId.value = urlContestId
|
||||
} else if (data.length > 0) {
|
||||
currentContestId.value = data[0].id
|
||||
}
|
||||
|
||||
if (currentContestId.value) {
|
||||
loadComments()
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || "获取活动列表失败")
|
||||
} finally {
|
||||
contestsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载评语列表
|
||||
// 加载评语列表(序号防止快速返回时旧请求覆盖表格)
|
||||
let loadCommentsSeq = 0
|
||||
const loadComments = async () => {
|
||||
if (!currentContestId.value) return
|
||||
|
||||
const seq = ++loadCommentsSeq
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await presetCommentsApi.getList(currentContestId.value)
|
||||
const data = await presetCommentsApi.getList()
|
||||
if (seq !== loadCommentsSeq) return
|
||||
dataSource.value = data
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || "获取评语列表失败")
|
||||
if (seq === loadCommentsSeq) {
|
||||
message.error(error?.response?.data?.message || "获取评语列表失败")
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
if (seq === loadCommentsSeq) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,7 +229,6 @@ const handleSubmit = async () => {
|
||||
message.success("编辑成功")
|
||||
} else {
|
||||
await presetCommentsApi.create({
|
||||
contestId: currentContestId.value!,
|
||||
content: form.content,
|
||||
})
|
||||
message.success("创建成功")
|
||||
@ -349,36 +255,8 @@ const handleCancel = () => {
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 打开同步弹框
|
||||
const handleOpenSync = () => {
|
||||
syncTargetContestIds.value = []
|
||||
syncModalVisible.value = true
|
||||
}
|
||||
|
||||
// 同步评语
|
||||
const handleSync = async () => {
|
||||
if (syncTargetContestIds.value.length === 0) {
|
||||
message.warning("请选择目标活动")
|
||||
return
|
||||
}
|
||||
|
||||
syncLoading.value = true
|
||||
try {
|
||||
const result = await presetCommentsApi.sync({
|
||||
sourceContestId: currentContestId.value!,
|
||||
targetContestIds: syncTargetContestIds.value,
|
||||
})
|
||||
message.success(`${result.message},共同步 ${result.count} 条评语`)
|
||||
syncModalVisible.value = false
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || "同步失败")
|
||||
} finally {
|
||||
syncLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadContests()
|
||||
loadComments()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -473,19 +473,25 @@ const handlePresetSelect = (presetId: number | undefined) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 并发请求序号,避免快速切换作品或重复触发 watch 时旧请求覆盖新数据
|
||||
let fetchWorkDetailSeq = 0;
|
||||
|
||||
// 获取作品详情
|
||||
const fetchWorkDetail = async () => {
|
||||
if (!props.workId) return;
|
||||
|
||||
const seq = ++fetchWorkDetailSeq;
|
||||
loading.value = true;
|
||||
try {
|
||||
const detail = await worksApi.getDetail(props.workId);
|
||||
if (seq !== fetchWorkDetailSeq) return;
|
||||
workDetail.value = detail;
|
||||
|
||||
// 获取活动的评审规则
|
||||
if (props.contestId) {
|
||||
try {
|
||||
const contest = await reviewsApi.getJudgeContestDetail(props.contestId);
|
||||
if (seq !== fetchWorkDetailSeq) return;
|
||||
|
||||
if (contest.reviewRule) {
|
||||
// 确保 dimensions 是数组(可能是 JSON 字符串)
|
||||
@ -513,18 +519,23 @@ const fetchWorkDetail = async () => {
|
||||
// 获取评审规则失败,使用简单评分模式
|
||||
reviewRule.value = null;
|
||||
}
|
||||
} else {
|
||||
reviewRule.value = null;
|
||||
}
|
||||
|
||||
// 单独获取预设评语,不影响评审规则
|
||||
try {
|
||||
const presets = await presetCommentsApi.getList(props.contestId);
|
||||
presetComments.value = presets;
|
||||
} catch {
|
||||
presetComments.value = [];
|
||||
}
|
||||
// 预设评语:不传 contestId,拉取当前评委全部模板(与活动是否带 contestId 无关)
|
||||
try {
|
||||
const presets = await presetCommentsApi.getList();
|
||||
if (seq !== fetchWorkDetailSeq) return;
|
||||
presetComments.value = presets;
|
||||
} catch {
|
||||
if (seq !== fetchWorkDetailSeq) return;
|
||||
presetComments.value = [];
|
||||
}
|
||||
|
||||
// 获取当前评委的评分记录
|
||||
const scores = await reviewsApi.getWorkScores(props.workId);
|
||||
if (seq !== fetchWorkDetailSeq) return;
|
||||
const myScore = scores.find((s: any) => s.assignmentId === props.assignmentId);
|
||||
if (myScore && myScore.totalScore !== null && myScore.totalScore !== undefined) {
|
||||
existingScore.value = myScore;
|
||||
@ -553,11 +564,16 @@ const fetchWorkDetail = async () => {
|
||||
resetForm();
|
||||
}
|
||||
|
||||
if (seq !== fetchWorkDetailSeq) return;
|
||||
currentFileIndex.value = 0;
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || "获取作品详情失败");
|
||||
if (seq === fetchWorkDetailSeq) {
|
||||
message.error(error?.response?.data?.message || "获取作品详情失败");
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
if (seq === fetchWorkDetailSeq) {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -696,27 +712,20 @@ const handleClose = () => {
|
||||
emit("update:open", false);
|
||||
};
|
||||
|
||||
// 监听抽屉打开
|
||||
// 合并监听:避免同时设置 open=true 与 workId 时两个 watch 各调一次 fetch 造成重复请求
|
||||
watch(
|
||||
() => props.open,
|
||||
(val) => {
|
||||
if (val && props.workId) {
|
||||
fetchWorkDetail();
|
||||
} else {
|
||||
() =>
|
||||
[props.open, props.workId, props.contestId, props.assignmentId] as const,
|
||||
([open]) => {
|
||||
if (!open) {
|
||||
workDetail.value = null;
|
||||
existingScore.value = null;
|
||||
reviewRule.value = null;
|
||||
presetComments.value = [];
|
||||
resetForm();
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听 workId 变化(切换作品时)
|
||||
watch(
|
||||
() => props.workId,
|
||||
(val) => {
|
||||
if (props.open && val) {
|
||||
if (props.workId) {
|
||||
fetchWorkDetail();
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,7 +161,7 @@
|
||||
<a-button
|
||||
size="small"
|
||||
:disabled="isReviewEnded(contest)"
|
||||
@click="handlePresetComments(contest.id)"
|
||||
@click="handlePresetComments"
|
||||
>
|
||||
预设评语
|
||||
</a-button>
|
||||
@ -488,13 +488,9 @@ const handleReviewWorks = (id: number) => {
|
||||
router.push(`/${tenantCode}/activities/review/${id}`)
|
||||
}
|
||||
|
||||
// 预设评语
|
||||
const handlePresetComments = (id: number) => {
|
||||
console.log(
|
||||
"预设评审",
|
||||
`/${tenantCode}/activities/preset-comments?contestId=${id}`,
|
||||
)
|
||||
router.push(`/${tenantCode}/activities/preset-comments?contestId=${id}`)
|
||||
// 预设评语(全局模板,与活动无绑定)
|
||||
const handlePresetComments = () => {
|
||||
router.push(`/${tenantCode}/activities/preset-comments`)
|
||||
}
|
||||
|
||||
// 图片加载错误记录
|
||||
|
||||
Loading…
Reference in New Issue
Block a user