# 评委端：评审任务

> 所属端：评委端（`tenant_id` 对应评委租户，如 `code=judge`）  
> 菜单与定位见 [菜单配置说明](../menu-config.md) 中「评委端」章节：仅能查看**被分配**的活动与作品。

## 活动列表

**接口**：`GET /contests/reviews/judge/contests`

**活动来源（并集）**：

1. `t_biz_contest_judge` 中 `judge_id` 且 `valid_state=1` 的赛事（机构显式添加的评委）；
2. `t_biz_contest_work_judge_assignment` 中该评委出现过的 `contest_id`（含仅通过作品分配参与的隐式场景）。

**响应字段（与前端 `activities/Review.vue` 对齐）**：

| 字段 | 说明 |
|------|------|
| `contestId` | 活动 ID |
| `contestName` | 活动名称 |
| `contestState` / `status` | 活动状态 |
| `reviewStartTime` / `reviewEndTime` | 评审时间窗 |
| `totalAssigned` | 该评委在该活动下的分配记录总数 |
| `reviewed` | 其中 `status=completed` 的数量（已提交评分） |
| `pending` | `totalAssigned - reviewed`（待评审） |

## 活动下作品列表

**接口**：`GET /contests/reviews/judge/contests/{contestId}/works`

**分配状态 `reviewStatus` 查询参数（与库表兼容）**：

- 库中 `t_biz_contest_work_judge_assignment.status` 实际使用：`assigned`（已分配未评完）、`completed`（已评审）。
- 前端下拉「未评审」传 `pending` → 后端按 **`status != completed`** 筛选。
- 前端「已评审」传 `reviewed` → 后端按 **`status = completed`** 筛选。

**作品编号**：`workNo` 为空时，前端可用作品 `workId` 展示兜底（如 `#123`）。

## 活动详情（含评审规则）

**接口**：`GET /contests/reviews/judge/contests/{contestId}/detail`

**权限**：满足以下**任一**即可：

- 存在有效的 `t_biz_contest_judge` 关联；或  
- 存在该 `contestId` + `judgeId` 的作品分配记录。

避免「列表能进、详情 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`

**权限**：`review:score`（与提交评分一致）

**请求体（JSON）**：

| 字段 | 必填 | 说明 |
|------|------|------|
| `assignmentId` | 是 | 当前评委在该作品上的分配记录 ID（`t_biz_contest_work_judge_assignment.id`），与打分接口一致 |
| `reason` | 否 | 违规说明，写入 `t_biz_contest_work.violation_reason` |

**行为**：

- 将 `t_biz_contest_work.status` 置为 `violation`，并写入 `violation_mark_time`、`violation_judge_id`（及可选 `violation_reason`）。
- 将该评委对应的分配记录 `status` 置为 `completed`，与「已处理该评审任务」一致，避免长期占用「待评审」。
- 若作品已是 `violation`，幂等返回成功。

**列表字段**：`GET .../works` 响应中增加 `workStatus`（作品表 `status`），便于前端展示「违规」等状态；与分配维度上的 `status`（`assignment` 是否 completed）区分。

**说明**：此为**活动赛事投稿作品**（`t_biz_contest_work`）的违规标记，与 UGC 绘本广场超管审核（`/content-review/...`）不同。

**公众端**：`GET /public/activities/{id}/my-registration` 返回最新参赛作品 `latestWorkStatus`、`violationReason` 等；若最新作品为 `violation`，参赛者在活动提交期内可重新从作品库提交（`submitRule=once` 时亦允许覆盖违规版本）。

---

## 与租户端「评审进度」的口径对齐

| 维度 | 租户机构端 `contests/reviews/progress`（活动列表行） | 评委端 `activities/review`（上表） |
|------|------------------------------------------------------|-------------------------------------|
| 数据来源 | `GET /contests` 列表项中的 `reviewedCount` / `totalWorksCount` | `GET /contests/reviews/judge/contests` |
| 含义 | `totalWorksCount`：该活动最新有效作品总数。`reviewedCount`：**该活动下已分配评委且全部分配记录均为 `completed` 的作品数**（与分配表 `t_biz_contest_work_judge_assignment` 一致，与作品表 `accepted`/`awarded` 终态无关） | **评委维度**：`reviewed`/`totalAssigned`/`pending` 为该评委在分配表上的任务数；与租户「整作品是否全部评委评完」为不同聚合粒度 |
| 作品列表/详情 | `GET /contests/works` 每条作品含 `reviewedCount`/`totalJudgesCount`（按该作品分配条数统计） | 评委在单活动下作品列表同样基于分配 `completed` |

说明：顶部「作品统计」卡片若仍按作品 `status` 汇总，可能与逐活动行「分配完成作品数」不完全同数，属汇总维度不同；列表/详情/评委任务以分配表为准。
