frontend-notice(v3): 评价举报队列接入说明改写为前端友好版
mmg 反馈前两版看不懂, 改写为: - 一句话需求 + ASCII 改之前/之后图 - 完整 axios 调用 + 真实测试服 response JSON 示例 - 操作按钮按 reportStatus 状态机表格 - 字典翻译用法 + 实测返回 - 验收清单 8 条具体到点 - 联调环境 + 测试数据 reviewId 直接给 后端 0 改动, 所有接口测试服已就绪, mmg 可直接联调。
这个提交包含在:
父节点
7e4b6365f1
当前提交
f4682fc56e
@ -1,182 +1,266 @@
|
|||||||
# frontend-notice: 「人工复审队列」页面加 REVIEW tab(融合评价举报,后端 #1668 已就绪)
|
# 前端任务: 「人工复审队列」加一个 tab 显示评价举报
|
||||||
|
|
||||||
**通知对象**: @mmg
|
@mmg —— **此文件已是第 3 版,前两版你说看不懂,这版尽量直白。如果还有不清楚的当面问我。**
|
||||||
**关联**: [PR #1668](https://git.1814.love:8443/wx/HL/pulls/1668)(后端 admin API 5 个)+ [PR #1755](https://git.1814.love:8443/wx/HL/pulls/1755)(mp BFF 透传)
|
|
||||||
**问题反馈**: 2026-05-06 用户反馈"客户举报成功了应该在人工复审队列中展示 现在没有"
|
|
||||||
**方向修正**: ⚠️ 此前同名 changelog 让你"新建独立页面"——方向改为 **在现有「人工复审队列」页面加 tab 融合**,以下为最终方案
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 用户产品语义
|
## 一句话需求
|
||||||
|
|
||||||
进「内容安全 → 人工复审队列」页面就能看到所有需要复审的内容(包括出行人姓名、用户昵称、头像,**也包括评价举报**)+ 在同页面就能处理(隐藏/恢复/忽略/警告)。**不要新建独立的"评价举报处理"二级菜单**。
|
「内容安全 → 人工复审队列」页面 **顶部加一个 tab 叫"评价举报"**,切到这个 tab 就调下面的接口,数据展示出来,操作按钮按下面的规则做。**只改这一个页面,不要新建别的菜单。**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 现状
|
## 改之前 vs 改之后
|
||||||
|
|
||||||
后台「内容安全 → 人工复审队列」页面当前调:
|
### 现在的页面
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /admin/wx-security/manual-review/page?bizType=TRAVELER
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 人工复审队列 │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ [出行人姓名] [用户昵称] [用户头像] │ ← 这是当前已有的 tab
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ 列表(调 /admin/wx-security/manual-review/page) │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
支持 `bizType` ∈ {TRAVELER, USER_NICKNAME, USER_AVATAR},三类的查询/通过/驳回操作 5 个 API。
|
### 改成这样
|
||||||
|
|
||||||
举报评价数据走另一套独立 admin API(已就绪,后端 0 改动):
|
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /admin/review/report-queue/page?reportStatus=PENDING_REVIEW
|
┌─────────────────────────────────────────────────────┐
|
||||||
POST /admin/review/report-queue/{reviewId}/hide Body: { "reason": "..." }
|
│ 人工复审队列 │
|
||||||
POST /admin/review/report-queue/{reviewId}/restore
|
├─────────────────────────────────────────────────────┤
|
||||||
POST /admin/review/report-queue/report/{reportId}/ignore
|
│ [出行人姓名] [用户昵称] [用户头像] [评价举报] │ ← 加一个新 tab
|
||||||
POST /admin/review/report-queue/{reviewId}/warn-author
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ 切到"评价举报"时: │
|
||||||
|
│ - 列表调 /admin/review/report-queue/page │
|
||||||
|
│ - 操作按钮换成 隐藏/恢复/警告作者/忽略 │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
前 3 个 tab 你不用动,保持现状。**只加第 4 个 tab + 这个 tab 内的逻辑。**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 前端融合方案
|
## tab=评价举报 时调什么接口
|
||||||
|
|
||||||
### 1. 页面顶部 tab 加第 4 项 "评价举报"
|
### 1. 加载列表
|
||||||
|
|
||||||
```
|
```js
|
||||||
[出行人姓名] [用户昵称] [用户头像] [评价举报] ← 新增
|
// GET 请求
|
||||||
|
axios.get('/admin/review/report-queue/page', {
|
||||||
|
params: {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
reportStatus: 'PENDING_REVIEW', // 可选: NORMAL / PENDING_REVIEW / HIDDEN
|
||||||
|
keyword: '违规' // 可选: 模糊搜评价内容/作者昵称
|
||||||
|
}
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
对应 `bizType` 内部值: `TRAVELER` / `USER_NICKNAME` / `USER_AVATAR` / `REVIEW`
|
### 响应实例(测试服真数据,reviewId=2048001001001001005 的那条举报)
|
||||||
|
|
||||||
### 2. tab 路由不同 API
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": {
|
||||||
|
"total": 1,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"reviewId": "2048001001001001005",
|
||||||
|
"content": "出发前一直犹豫值不值得去……(评价正文)",
|
||||||
|
"userNickname": "罗子昂",
|
||||||
|
"userId": "2031234567890123456",
|
||||||
|
"productName": "粉色风蚀古堡",
|
||||||
|
"status": "PUBLISHED",
|
||||||
|
"reportCount": 1,
|
||||||
|
"reportStatus": "NORMAL",
|
||||||
|
"hiddenAt": null,
|
||||||
|
"hiddenReason": null,
|
||||||
|
"createTime": "2026-05-05 14:23:11",
|
||||||
|
"reasonStats": { "SPAM": 1 },
|
||||||
|
"reporters": [
|
||||||
|
{
|
||||||
|
"reportId": "2052007674911895553",
|
||||||
|
"reporterUserId": "2031999888777666555",
|
||||||
|
"reporterOpenid": "oK****n8",
|
||||||
|
"reason": "SPAM",
|
||||||
|
"detail": null,
|
||||||
|
"status": "PENDING",
|
||||||
|
"createTime": "2026-05-06 20:25:42"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
| 当前 tab | 列表接口 | 操作接口 |
|
**注意**: `reviewId / userId / reporterUserId / reportId` 都是字符串(后端用 `@JsonFormat(shape=STRING)` 防 JS 精度丢失),前端**不要 parseInt**,直接当字符串用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 表格列建议
|
||||||
|
|
||||||
|
| 列 | 字段 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| TRAVELER / USER_NICKNAME / USER_AVATAR | `GET /admin/wx-security/manual-review/page?bizType=X` | `/admin/wx-security/manual-review/{approve,reject}` |
|
| 评价内容 | `content` | 超过 100 字省略号,鼠标 hover tooltip 看全文 |
|
||||||
| **REVIEW** | `GET /admin/review/report-queue/page` | `/admin/review/report-queue/{reviewId}/{hide,restore,warn-author}` + `report/{reportId}/ignore` |
|
| 作者 | `userNickname` | 链接到用户详情(可选,用 `userId`) |
|
||||||
|
| 关联产品 | `productName` | |
|
||||||
|
| 举报数 | `reportCount` | |
|
||||||
|
| 状态 | `reportStatus` | 见下面"状态枚举"中文化 |
|
||||||
|
| 举报原因 | `reasonStats` | 例如 `广告 ×3 / 辱骂 ×1`,通过字典翻译(下面有字典接口) |
|
||||||
|
| 隐藏时间 | `hiddenAt` | `reportStatus=HIDDEN` 时显示,否则空 |
|
||||||
|
| 创建时间 | `createTime` | 评价发布时间 |
|
||||||
|
| 操作 | (按钮) | 见下面"操作按钮规则" |
|
||||||
|
|
||||||
### 3. tab=REVIEW 时表格列建议
|
### 状态枚举(中文化)
|
||||||
|
|
||||||
| 列名 | 数据来源 |
|
| `reportStatus` | 中文 | tag 颜色建议 |
|
||||||
|
|---|---|---|
|
||||||
|
| `NORMAL` | 正常 | 灰 / 默认 |
|
||||||
|
| `PENDING_REVIEW` | 待复审 | 橙 / warning |
|
||||||
|
| `HIDDEN` | 已隐藏 | 红 / error |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 操作按钮规则
|
||||||
|
|
||||||
|
按当前行的 `reportStatus` 显示不同按钮:
|
||||||
|
|
||||||
|
| `reportStatus` | 显示这些按钮 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 评价内容 | `content` (≤ 100 字省略号) |
|
| `NORMAL` | `警告作者` `忽略全部` |
|
||||||
| 作者 | `userNickname`(超链接到用户详情用 `userId`) |
|
| `PENDING_REVIEW` | **`隐藏评价`(主)** `警告作者` `忽略全部` |
|
||||||
| 关联产品 | `productName` |
|
| `HIDDEN` | **`恢复评价`(主)** |
|
||||||
| 举报数 | `reportCount` |
|
|
||||||
| 状态 | `reportStatus` 中文化(详见下文枚举) |
|
|
||||||
| 举报原因统计 | `reasonStats`(图表或 `广告 3 / 辱骂 1` 格式),通过字典 `review_report_reason` 翻译 label |
|
|
||||||
| 隐藏时间 | `hiddenAt`(状态 = HIDDEN 时显示) |
|
|
||||||
| 创建时间 | `createTime` |
|
|
||||||
| 操作 | 按 `reportStatus` 显示不同按钮(下文) |
|
|
||||||
|
|
||||||
### 4. 操作按钮(按 reportStatus 路由)
|
#### 各按钮对应的接口
|
||||||
|
|
||||||
| reportStatus | 中文 | 显示按钮 |
|
```js
|
||||||
|---|---|---|
|
// 隐藏评价(弹个框让 admin 输 reason)
|
||||||
| `NORMAL` | 正常 | 警告作者 / 忽略全部 |
|
axios.post(`/admin/review/report-queue/${reviewId}/hide`, {
|
||||||
| `PENDING_REVIEW` | 待复审 | **隐藏评价**(主按钮) / 警告作者 / 忽略全部 |
|
reason: '违规广告' // 必填
|
||||||
| `HIDDEN` | 已隐藏 | **恢复评价**(主按钮) |
|
})
|
||||||
|
|
||||||
> "忽略全部" = 列表内每条 `reporters[]` 行循环调 `POST /admin/review/report-queue/report/{reportId}/ignore`(批处理)
|
// 恢复评价
|
||||||
> "警告作者" → `POST /admin/review/report-queue/{reviewId}/warn-author`(后端目前仅落日志,站内信 SDK 待集成)
|
axios.post(`/admin/review/report-queue/${reviewId}/restore`)
|
||||||
> "隐藏评价" 弹框输入 reason → `POST /admin/review/report-queue/{reviewId}/hide` body `{reason}`
|
|
||||||
> "恢复评价" → `POST /admin/review/report-queue/{reviewId}/restore`
|
|
||||||
|
|
||||||
### 5. 展开行(可选,推荐)
|
// 警告作者(后端目前只落日志,以后接站内信 SDK 再说)
|
||||||
|
axios.post(`/admin/review/report-queue/${reviewId}/warn-author`)
|
||||||
|
|
||||||
点击行展开看 `reporters[]` 数组,每个举报人:
|
// 忽略全部 = 把当前行 reporters[] 数组里每个 reportId 循环调忽略接口
|
||||||
|
for (const r of row.reporters) {
|
||||||
|
await axios.post(`/admin/review/report-queue/report/${r.reportId}/ignore`)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
| openid(脱敏) | 举报原因 | 详细说明 | 状态 | 时间 | 操作 |
|
**所有操作完成后请刷新当前页列表。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 展开行(可选,推荐有)
|
||||||
|
|
||||||
|
点击行旁边的展开图标,展开看 `reporters[]` 数组,每个举报人一行:
|
||||||
|
|
||||||
|
| 举报人 | 原因 | 详情 | 状态 | 时间 | 操作 |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `oABCD***1234` | 垃圾营销 | (用户填的 detail) | PENDING / IGNORED / HANDLED | createTime | [忽略本条] |
|
| `oK****n8` (脱敏 openid) | 垃圾营销 | (用户填的 detail,可能为空) | `PENDING` | createTime | `[忽略本条]` |
|
||||||
|
|
||||||
`[忽略本条]` → `POST /admin/review/report-queue/report/{reportId}/ignore`
|
**`[忽略本条]` 按钮**:
|
||||||
|
|
||||||
|
```js
|
||||||
|
axios.post(`/admin/review/report-queue/report/${reportId}/ignore`)
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 接口字段清单
|
### 5. 字典翻译(reasonStats 里的 key)
|
||||||
|
|
||||||
### `AdminReportQueueItemRespVO`(列表项)
|
`reasonStats` 的 key 是英文 value(SPAM/INSULT/...),展示时翻译成中文 label:
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
```js
|
||||||
|---|---|---|
|
// 拿字典(整个项目通用接口)
|
||||||
| `reviewId` | Long (`@JsonFormat(shape=STRING)`) | 评价 ID |
|
axios.get('/admin/system/dict-data/by-type', {
|
||||||
| `content` | String | 评价正文 |
|
params: { dictType: 'review_report_reason' }
|
||||||
| `userNickname` | String | 评价作者昵称 |
|
})
|
||||||
| `userId` | Long (JsonString) | 评价作者 ID |
|
|
||||||
| `productName` | String | 关联产品名 |
|
|
||||||
| `status` | String | 评价本身状态(PUBLISHED 等) |
|
|
||||||
| `reportCount` | Integer | 总举报数 |
|
|
||||||
| `reportStatus` | String | NORMAL / PENDING_REVIEW / HIDDEN |
|
|
||||||
| `hiddenAt` | LocalDateTime | 隐藏时间(null 表示未隐藏) |
|
|
||||||
| `hiddenReason` | String | 隐藏原因(管理员填的) |
|
|
||||||
| `createTime` | LocalDateTime | 评价创建时间 |
|
|
||||||
| `reasonStats` | `Map<String, Integer>` | 各 reason 统计 e.g. `{"SPAM":3,"OFFENSIVE":1}` |
|
|
||||||
| `reporters` | List | 举报人列表(展开行用) |
|
|
||||||
|
|
||||||
`reporters[]` 每项: `reportId / reporterUserId(JsonString) / reporterOpenid(已脱敏) / reason / detail / status / createTime`
|
|
||||||
|
|
||||||
### `AdminReportQueuePageReqVO`(查询参数)
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /admin/review/report-queue/page
|
|
||||||
?page=1
|
|
||||||
&pageSize=20
|
|
||||||
&reportStatus=PENDING_REVIEW (可选,前端 tab 内嵌"全部/待复审/已隐藏"子筛选时用)
|
|
||||||
&keyword=违规广告 (可选,模糊匹配评价内容/作者昵称)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 字典 `review_report_reason`
|
返回(测试服实测):
|
||||||
|
|
||||||
通过 `GET /admin/system/dict-data/by-type?dictType=review_report_reason` 拿:
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": [
|
||||||
|
{ "value": "SPAM", "label": "广告/营销" },
|
||||||
|
{ "value": "INSULT", "label": "辱骂/人身攻击" },
|
||||||
|
{ "value": "PORN", "label": "色情低俗" },
|
||||||
|
{ "value": "POLITICS", "label": "政治敏感" },
|
||||||
|
{ "value": "FAKE", "label": "虚假评价" },
|
||||||
|
{ "value": "LEAK_PRIVACY", "label": "泄露他人隐私" },
|
||||||
|
{ "value": "OTHER", "label": "其他" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
| value | label |
|
**不要硬编码这个映射**, 进页面拉一次缓存到 vuex/pinia 就行。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. 顶部筛选区(REVIEW tab 时)
|
||||||
|
|
||||||
|
页面顶部本来已经有"建议 / 场景 / 关键词"3 个筛选,在 REVIEW tab 时:
|
||||||
|
|
||||||
|
| 原字段 | 改成 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `SPAM` | 垃圾营销 |
|
| 建议(下拉) | 改成 `reportStatus` 下拉(全部 / 正常 / 待复审 / 已隐藏),传 `reportStatus` 参数 |
|
||||||
| `OFFENSIVE` | 不友善/辱骂(注: 后端实际入库 dictValue 也可能是 `INSULT` 见 V20260507_001) |
|
| 场景 | **隐藏**(评价举报无 scene) |
|
||||||
| `FRAUD` | 欺诈/虚假 |
|
| 关键词 | 保留,传 `keyword` 参数 |
|
||||||
| `OTHER` | 其他 |
|
|
||||||
|
|
||||||
> 字典值后端权威以 `/admin/system/dict-data/by-type` 实际返回为准,前端动态渲染不要硬编码。
|
切回前 3 个 tab 时恢复原本的筛选字段。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 列表排序与分页
|
## 你不用关心的(后端已搞定的)
|
||||||
|
|
||||||
后端默认按 `reportCount DESC, create_time DESC` 排序,**前端不要重排**。
|
- 用户从小程序点举报(`POST /mp/review/report`)的数据已经 100% 落到后端 DB,实测 `reviewId=2048001001001001005` 已经有 1 条 SPAM 举报数据,你接入后能直接看到。
|
||||||
分页用标准 `page` / `pageSize`,响应 `total` / `records`。
|
- 网关路由、JWT 认证、字典初始化 全部就绪。
|
||||||
|
- 排序后端默认 `reportCount DESC, createTime DESC`,**前端不要 sort 重排**。
|
||||||
|
- `reporterOpenid` 已经脱敏(`oK****n8`),**不要再 mask**。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 兼容现有 3 类的 UX
|
## 验收清单(全部对了再说做完)
|
||||||
|
|
||||||
切换 tab 时:
|
- [ ] 「人工复审队列」页面顶部 4 个 tab,默认还是"出行人姓名"
|
||||||
- TRAVELER/USER_NICKNAME/USER_AVATAR tab 保留现有页面的 列定义/操作按钮(approve/reject)不动。
|
- [ ] 点"评价举报" tab,列表能加载,看到 reviewId=2048001001001001005 的那条数据
|
||||||
- REVIEW tab 是**完全独立的列定义 + 操作按钮**(因为 VO 字段完全不同)。
|
- [ ] 评价内容/作者昵称/产品名 都是中文显示
|
||||||
- 顶部筛选区 "建议 / 场景 / 关键词" 在 REVIEW tab 时:
|
- [ ] `reasonStats` 的 SPAM 翻译成 "广告/营销"
|
||||||
- "建议" 字段 → 复用为 `reportStatus` (NORMAL / PENDING_REVIEW / HIDDEN)
|
- [ ] 状态显示"正常"(因为目前 reportCount=1 < 3)
|
||||||
- "场景" 字段 → REVIEW tab 隐藏(评价举报无 scene 概念)
|
- [ ] 操作按钮显示"警告作者 / 忽略全部"(NORMAL 状态)
|
||||||
- "关键词" 字段 → 走后端 `keyword` 参数,模糊评价内容/作者昵称
|
- [ ] 展开行能看到 1 条 reporters,openid `oK****n8`,原因 "广告/营销"
|
||||||
|
- [ ] 切回"出行人姓名" tab,原列表还能正常加载(没把老 tab 弄坏)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 验收
|
## 工作量
|
||||||
|
|
||||||
1. 「内容安全 → 人工复审队列」页面顶部 tab 出现 "评价举报",权限正常
|
预计 1.5 天。
|
||||||
2. 默认 tab 仍为出行人姓名(不影响现有用户习惯)
|
|
||||||
3. 切到 "评价举报" tab,列表能加载,字段渲染对齐上方表格
|
后端 0 改动。**所有接口都已部署到测试服,你可以直接联调。**
|
||||||
4. `reasonStats` 字典 label 翻译正确,中文显示 "广告/营销" "辱骂/人身攻击" 等
|
|
||||||
5. 操作按钮按 `reportStatus` 显示且功能调通,操作完列表自动刷新
|
|
||||||
6. 展开行显示 `reporters[]`,每条"忽略本条"按钮调 `report/{reportId}/ignore` 通
|
|
||||||
7. 测试服 `reviewId=2048001001001001005` 已有 1 条 SPAM 举报数据,接入后能直接看到
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 不在本次范围
|
## 联调环境
|
||||||
|
|
||||||
- 站内信通知作者(`warnAuthor` 后端 TODO)
|
- 测试服域名: `https://api.test.1814.love:9443`
|
||||||
- 后端跨服务统一聚合(Phase 2D-B,已评估优先级低)
|
- admin 账号: `admin / Admin@123456`
|
||||||
- 评价举报独立的二级菜单(已废弃,统一走 tab)
|
- 已有测试数据: `reviewId=2048001001001001005`(粉色风蚀古堡产品的一条评价,被 SPAM 举报 1 次)
|
||||||
|
- 想再造数据: 用小程序登录,POST `/mp/review/report` body `{"reviewId":"...","reason":"INSULT"}`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 工作量预估
|
## 历史 changelog 注意
|
||||||
|
|
||||||
前端 ~1.5 天(VO 适配 + tab 切换 + 操作按钮按状态路由 + 展开行 + 字典翻译)。**后端 0 改动**。
|
之前的 2 版让你"新建独立菜单 评价举报处理",**那个方向作废**。最终方向就是这版: **现有页面加 tab**。
|
||||||
|
|||||||
正在加载...
x
在新工单中引用
屏蔽一个用户