# 评价管理 — 前端技术方案

> **依据：** 《评价管理功能需求.md》v1.2、《评价管理技术方案.md》v1.2  
> **前端规范：** `doc/前端设计/前端设计.md`  
> **范围：** 仅 **ruoyi-ui 商家端** 当前店铺评价列表、检索、详情查看、商家回复、逻辑删除；**不含** C 端发表评价、平台审核。  
> **实现状态：** `index.vue`、`detail.vue`、`api/agri/seller/review.js` **已按 v1.2 一行一评落地**；待菜单配置及 `/agri/seller/review` 联调。

---

## 1. 技术栈与写法约定

| 项 | 说明 |
|----|------|
| 框架 | Vue 2 + Element UI |
| 请求 | `@/utils/request` + `sellerShopHeaders()` 携带 **`X-Shop-Id`** |
| 参考页面 | `agri/seller/order/index.vue`（检索+页签+列表）、`agri/seller/goods/detail.vue`（详情抽屉） |
| 布局 | 检索 `el-card` + `<br/>` + 列表 `el-card` + `border` 表格 |
| 详情 | 右侧 `el-drawer`（`size="72%"`，`append-to-body`） |
| 图片 | 全局 `image-preview` 组件（列表缩略、详情多图） |
| 店铺切换 | **仅 Navbar**；业务页禁止展示店铺选择器 |

---

## 2. v1.2 业务变更（相对 v1.0/v1.1）

| 项 | 旧口径 | v1.2 定稿 |
|----|--------|-----------|
| 评价粒度 | 一单一评 | **一行一评**（`order_item_id` / `uk_order_item_review`） |
| 列表行 | 整单首商品 | **一条评价 = 一个订单商品行** 快照 |
| 商品名称 | — | 后端 **JOIN `biz_order_item`** 取 `goods_name` |
| 删除后买家 | 不可再评该订单 | 不可再评该 **商品行**（RM3） |
| C 端协作 | — | 与《我的订单》v1.2 `items[]` 按行评价一致 |

**前端不改接口路径**；列表/详情仍用 `goodsName`、`goodsSpec`、`goodsMainPic` 展示评价对象。

---

## 3. 文件清单

| 类型 | 路径 | 说明 |
|------|------|------|
| 列表页 | `ruoyi-ui/src/views/agri/seller/review/index.vue` | 检索、回复状态页签、表格、回复弹窗、详情抽屉宿主 |
| 详情抽屉 | `ruoyi-ui/src/views/agri/seller/review/detail.vue` | 分区展示、一行一评说明、回复/删除（事件上抛） |
| 评价 API | `ruoyi-ui/src/api/agri/seller/review.js` | list、detail、reply、remove |
| 店铺上下文 | `api/agri/seller/context.js` + `utils/sellerShop.js` | X-Shop-Id |

**组件 name（keep-alive）：**

| 组件 | name |
|------|------|
| 列表页 | `AgriSellerReview` |
| 详情抽屉 | `SellerReviewDetail` |

---

## 4. 菜单与路由

| 菜单名称 | 组件路径 | 路由 path（建议） | 权限标识 |
|----------|----------|-------------------|----------|
| 评价管理 | `agri/seller/review/index` | `seller/review` | `agri:seller:review:list` |

**上级菜单：** 店铺经营管理端 → **商品管理**

| 按钮权限 | 标识 | 页面落点 |
|----------|------|----------|
| 列表 | `agri:seller:review:list` | 进入列表页 |
| 详情 | `agri:seller:review:query` | 操作列「查看」 |
| 回复 | `agri:seller:review:reply` | 操作列「回复」、详情「回复评价/修改回复」 |
| 删除 | `agri:seller:review:remove` | 操作列「删除」、详情「删除评价」 |

---

## 5. 页面结构（与代码一致）

```text
评价管理 index.vue
├── 检索区 search-card（el-card，v-show 受 right-toolbar 控制）
│   ├── 会员名称 memberNickName（140px，回车搜索）
│   ├── 商品名称 goodsName（placeholder「评价对象名称」）
│   ├── 评分 score（el-select 1～5 星，可清空）
│   ├── 评价时间 dateRange（daterange → beginTime/endTime）
│   └── 搜索 / 重置
├── 列表区 table-card（el-card）
│   ├── 回复状态页签 replyTab：全部(all) | 未回复(UNREPLIED) | 已回复(REPLIED)
│   ├── right-toolbar（显隐检索、刷新列表）
│   ├── el-table border（empty-text 动态）
│   │   ├── 会员名称 width=110
│   │   ├── 评价对象 min-width=220（图+名+规格+一行一评提示）
│   │   ├── 评价内容 min-width=180（截断 60 字）
│   │   ├── 晒图 width=90（首图 +N 角标）
│   │   ├── 评分 width=130（el-rate disabled）
│   │   ├── 评价时间 width=160（parseTime）
│   │   ├── 回复状态 width=90（Tag）
│   │   └── 操作 width=200 fixed=right：查看 / 回复 / 删除
│   └── pagination
├── 详情抽屉 SellerReviewDetail（见 §6）
└── 回复弹窗 el-dialog width=560px（由 index 统一承载）
    └── replyContent textarea，maxlength=500，show-word-limit
```

**不提供：** 页内店铺选择、修改买家评价、批量回复/删除、导出。

---

## 6. 详情抽屉 detail.vue

### 6.1 对外接口

| 类型 | 名称 | 说明 |
|------|------|------|
| props | `visible` | 抽屉显隐（`.sync`） |
| props | `reviewId` | 当前评价主键 |
| emit | `update:visible` | 关闭抽屉 |
| emit | `reply` | 点击回复/修改回复，payload 为详情对象 |
| emit | `view-order` | 点击订单编号，payload 含 `orderNo` |
| emit | `success` | 删除成功后通知父页刷新列表 |
| 方法 | `loadDetail()` | 父页回复成功后可选调用重载详情 |

### 6.2 分区结构

```text
el-drawer 标题：「评价详情 · {memberNickName}」
├── 评价信息（el-descriptions 2 列）
│   ├── 评分：el-rate disabled + show-score
│   ├── 评价时间
│   ├── 评价内容（pre-wrap 全文）
│   └── 晒图（有图时 span=2，72×72 多图 image-preview）
├── 评价对象
│   ├── section-tip：一行一评说明文案
│   └── 灰底卡片：主图 64×64 + 商品名 + 规格（默认「默认」）
├── 买家信息：会员名称、头像
├── 关联订单：订单编号（el-button text 可点）
├── 商家回复
│   ├── 回复状态 Tag
│   ├── 回复内容 / 回复人 / 回复时间（有则展示）
│   └── 未回复时提示：「待回复，买家评价已在 C 端展示」
└── action-bar：回复评价或修改回复、删除评价
```

**删除：** 抽屉内二次确认（同列表 RM3 文案）→ `delSellerReview` → 关闭抽屉 → `$emit('success')`。

---

## 7. 父子协作与数据流

### 7.1 页面初始化

```text
created → initPage()
    → loadShopContext()（GET /agri/seller/context → setSellerShopContext）
        ├── 成功 / 失败均继续
        └── getList()
```

### 7.2 列表检索与页签

```text
handleQuery / handleTabClick / pagination
    → 组装 queryParams + dateRange → beginTime/endTime
    → listSellerReview(params)
    → reviewList = rows；total

replyTab 与 queryParams.replyStatus 同步：
    all → replyStatus 不传
    UNREPLIED / REPLIED → 原样传参

resetQuery：清空 dateRange、replyTab=all、resetForm、replyStatus=undefined
```

### 7.3 查看详情

```text
handleDetail(row) → currentReviewId = row.reviewId → detailOpen = true
    → detail watch visible → getSellerReview(reviewId)
```

### 7.4 回复（列表与详情共用弹窗）

```text
列表 handleReply(row) / 详情 @reply
    → currentReplyReviewId = reviewId
    → replyDialogTitle：已回复「修改回复」否则「回复评价」
    → replyForm.replyContent 回显 row.replyContent
    → replyOpen = true

submitReply：校验 → trim → replySellerReview(id, { replyContent })
    → 成功：关闭弹窗、getList()
    → 若详情抽屉打开且为同一条评价：refs.reviewDetail.loadDetail()
```

### 7.5 删除

```text
列表 handleDelete(row) → confirm(RM3) → delSellerReview → getList()

详情 handleDelete → confirm(RM3) → delSellerReview → 关抽屉 → @success → getList()
```

### 7.6 跳转订单

```text
详情 @view-order → index handleViewOrder
    → $router.push({ path: '/seller/order', query: { orderNo } })
```

---

## 8. 回复状态页签与 Query

| 页签 | `replyTab` | 传参 `replyStatus` |
|------|------------|-------------------|
| 全部 | `all` | 不传（`undefined`） |
| 未回复 | `UNREPLIED` | `UNREPLIED` |
| 已回复 | `REPLIED` | `REPLIED` |

页签与高级检索 **AND** 组合；`handleTabClick` 重置 `pageNum=1` 并刷新。

**列表排序：** 后端 `create_time DESC`；前端不再二次排序。

**一行一评展示（列表评价对象列）：**

- `image-preview` 44×44（有 `goodsMainPic` 时）
- 商品名 + 规格（`goodsSpec` 空则「默认」）
- 灰色小字：`一行一评 · 单商品行快照`（class `item-tip`）

---

## 9. 展示与空态（页面已实现）

| 场景 | 实现 |
|------|------|
| 评价内容列表 | `contentSummary`：空→「—」；超 60 字加「…」 |
| 晒图列表 | 首图 40×40；多于 1 张右下角 `+N` 角标 |
| 回复状态 Tag | `UNREPLIED`→warning「未回复」；`REPLIED`→success「已回复」 |
| 空表格文案 | 计算属性 `hasSearchFilter`：含检索字段、日期区间、**或** `replyTab !== 'all'` |
| 无筛选空态 | `暂无买家评价` |
| 有筛选空态 | `未找到符合条件的评价` |

**删除确认文案（RM3，列表与详情一致）：**

```text
删除后 C 端将不再展示该评价，且买家无法再次评价该商品行（一行一评），是否继续？
```

---

## 10. 店铺上下文（X-Shop-Id）

| 步骤 | 说明 |
|------|------|
| `created` | `GET /agri/seller/context` → `setSellerShopContext` |
| 列表/详情/回复/删除 | `sellerShopHeaders()` |
| Navbar 切店 | `location.reload()` 整页刷新 |

---

## 11. API 封装

**模块：** `ruoyi-ui/src/api/agri/seller/review.js`  
**Base URL：** `/agri/seller/review`

| 前端方法 | HTTP | 路径 | 用途 |
|----------|------|------|------|
| `listSellerReview(query)` | GET | `/list` | 分页列表 |
| `getSellerReview(reviewId)` | GET | `/{reviewId}` | 详情 |
| `replySellerReview(reviewId, data)` | PUT | `/{reviewId}/reply` | 商家回复 |
| `delSellerReview(reviewId)` | DELETE | `/{reviewId}` | 逻辑删除 |

### 11.1 列表 Query（`queryParams`）

| 字段 | 页面绑定 | 说明 |
|------|----------|------|
| pageNum, pageSize | pagination | 默认 1 / 10 |
| replyStatus | `replyTab` 同步 | 空=全部 |
| score | 检索表单项 | 1～5 |
| goodsName, memberNickName | 检索表单项 | 模糊 |
| beginTime, endTime | `dateRange[0/1]` | `yyyy-MM-dd` |

### 11.2 列表行 VO（SellerReviewListRowVO）

| 字段 | 前端用途 |
|------|----------|
| reviewId | 主键、详情/回复/删除入参 |
| memberNickName | 会员名称列（空→「—」） |
| goodsName, goodsMainPic, goodsSpec | 评价对象列 |
| content | `contentSummary` 截断 |
| reviewPics | 晒图数组；首图 + 角标 |
| score | `el-rate` 只读 |
| createTime | `parseTime` |
| replyStatus | Tag + 回复弹窗标题判断 |
| replyContent | 已回复时弹窗回显 |

### 11.3 详情 VO（SellerReviewDetailVO）

| 分区 | 字段 |
|------|------|
| 评价 | score, content, reviewPics[], createTime |
| 对象 | goodsName, goodsMainPic, goodsSpec |
| 买家 | memberNickName, memberAvatar |
| 订单 | orderNo（跳转全部订单） |
| 回复 | replyStatus, replyContent, replyBy, replyTime |

### 11.4 回复 Body

```json
{ "replyContent": "感谢您的认可，欢迎再次光临。" }
```

| 规则 | 前端 |
|------|------|
| 必填 | `replyRules.replyContent` |
| 长度 ≤500 | `maxlength` + `show-word-limit` |
| 提交前 trim | `submitReply` 内 `(replyContent).trim()` |
| 已回复可修改 | 弹窗标题「修改回复」；PUT 覆盖 |

---

## 12. 样式要点（scoped）

| class | 位置 | 说明 |
|-------|------|------|
| `goods-target` | 列表/详情 | flex 横排：图 + 文案 |
| `sub-text` | 列表/详情 | 规格 12px 灰色 |
| `item-tip` | 列表评价对象 | 11px 浅灰「一行一评」提示 |
| `pic-cell` / `pic-count` | 列表晒图 | 相对定位 +N 角标 |
| `section-header` | 详情 | 左侧蓝色竖线分区标题 |
| `section-tip` | 详情评价对象 | 一行一评说明 |
| `goods-target`（详情） | 灰底 `#fafafa` 圆角卡片 |
| `pending-tip` | 详情未回复 | 橙色提示文案 |
| `action-bar` | 详情底部 | 上边框 + 回复/删除按钮 |

---

## 13. 联调检查清单

- [ ] 菜单组件路径 `agri/seller/review/index` 及四个按钮权限
- [ ] Navbar 切换店铺后列表数据隔离
- [ ] 回复状态页签与高级检索组合；重置清空页签
- [ ] 空态：无筛选「暂无买家评价」；有筛选「未找到符合条件的评价」
- [ ] 一单多商品：列表 **多行评价**，评价对象列快照与提示文案正确
- [ ] 查看详情：五分区、晒图、一行一评说明、未回复橙色提示
- [ ] 列表/详情均可打开回复弹窗；已回复回显与「修改回复」标题
- [ ] 回复成功：列表刷新；详情抽屉同 ID 时 `loadDetail` 重载
- [ ] 删除：RM3 文案；列表移除；详情关抽屉并刷新
- [ ] 订单编号跳转 `/seller/order?orderNo=xxx`
- [ ] 未选店铺时后端返回「请先选择当前店铺」

---

## 14. 修订记录

| 版本 | 说明 |
|------|------|
| **v1.3** | **按已落地页面回写**：列表列宽与组件、详情抽屉 props/emit/分区、父子协作数据流、空态计算属性、样式 class、回复弹窗由 index 统一承载 |
| **v1.2** | 对齐需求 v1.2 **一行一评**：删除文案、评价对象说明、空态分场景、列表行语义 |
| **v1.0** | 首版：列表 + 回复状态页签 + 详情抽屉 + 回复弹窗 + 删除 |

---

*文档版本：v1.3 · 关联《评价管理功能需求.md》v1.2、《评价管理技术方案.md》v1.2*
