docs: 标签管理点击查看对应题目的设计文档

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 05:51:04 -06:00
parent 3a9ab83ba5
commit a8399ad201

View File

@@ -0,0 +1,64 @@
# 标签管理:点击查看对应题目
## 背景
后台标签管理页(`ojnext/src/admin/problem/tags.vue`)目前只显示每个标签的 `problem_count` 数字,管理员无法知道具体是哪些题目在用这个标签。重命名/合并/删除标签时缺少判断依据。
后台题目列表 API`ProblemAdminAPI.get`)当前只支持 `keyword``author` 两个筛选条件,没有按标签筛选的能力。
## 目标
在标签管理页点击标签名或题目数,弹窗列出该标签下的题目,并支持跳转编辑、按标题搜索、直接移除该题的这个标签。
## 方案
复用后台题目列表 API给它加一个 `tag_id` 筛选参数。弹窗因此白拿分页、标题搜索和 `can_mgmt_all_problem` 权限过滤,不需要新建接口。移除标签复用已有的 `BatchProblemTagAPI`
### 后端
`OnlineJudge/problem/views/admin.py``ProblemAdminAPI.get`,在现有 `author` / `keyword` 过滤旁边加:
```python
tag_id = request.GET.get("tag_id")
if tag_id:
problems = problems.filter(tags__id=tag_id)
```
没有其他后端改动。移除标签走已有的 `POST admin/problem/batch_tag``action="remove"`,该接口已经会 `clear_tag_cache()`
### 前端 API
`ojnext/src/admin/api.ts``getProblemList` 末尾加可选参数 `tagId`,为空时不传给后端。
### 弹窗组件
新建 `ojnext/src/admin/problem/components/TagProblemsModal.vue`
- props`tagId: number``tagName: string``v-model:show`
- emits`changed` —— 移除标签成功后触发,父组件据此刷新题目数
- 顶部搜索框debounce 500ms走后端 `keyword` 参数
- 表格列:
- 显示 ID`_id`
- 标题 —— 点击 `router.push``admin problem edit`,同时关闭弹窗
- 是否公开(`visible`
- 「移除标签」按钮 —— 调 `batchTagProblems([row.id], [tagName], "remove")`,成功后刷新弹窗列表并 `emit("changed")`
- 底部:分页,每页 10 条
- 打开弹窗时重置到第 1 页、清空搜索词
### tags.vue 接入
标签名列和题目数列都渲染成可点击(`n-button text`),点击时记录当前行的 `id` / `name` 并打开弹窗。弹窗 `changed` 事件触发时调用现有的 `listTags()` 同步题目数。
标签名列在编辑态(`editingId === row.id`)下仍然渲染 `NInput`,不可点击。
## 已知不一致
标签表的 `problem_count` 来自 `TagAdminAPI``Count("problem")`,是全量统计;而弹窗走 `ProblemAdminAPI`,非超管只能看到自己创建的题。因此普通 admin 可能看到「题目数 12」但弹窗只列出 3 道。
本次不处理。当前使用场景以超管为主,两个数字一致。若将来需要抹平,给 `TagAdminAPI` 的计数加上同样的创建者过滤即可。
## 不做的事
- 不给后台题目列表页加标签筛选的 UI本次只有弹窗用 `tag_id`
- 不在弹窗里做批量移除
- 不改前台(`oj/`)的标签相关页面