# 古典文学网 · 前后端接口联调对照表

> 配套课程：《Python Web 全栈开发》—— 课程三《Vue 3 前端开发》
> 用途：前端每做一个页面，先在表里查到「调哪个接口、传什么、返回什么」，再去写代码；联调出问题时，也回来查这张表。
> 接口定义以 [`API接口文档.md`](./API接口文档.md) 为准；可直接导入的测试集合见 [`../assets/postman/hiwen.postman_collection.json`](../assets/postman/hiwen.postman_collection.json)。
> 版本：v1.0 ｜ 最后更新：2026-10-12

---

## 一、页面 ↔ 接口总表

> 说明：路径省略统一的 `/api` 前缀（`base_url = http://127.0.0.1:5000/api`）。「涉及 store/状态」指该操作要用到 Pinia 里的哪份状态。

| 前端页面 / 组件 | 操作 | 调用接口（方法 + 路径） | 是否需要登录 | 涉及 store / 状态 |
|---|---|---|---|---|
| `views/HomeView.vue`　首页 | 加载轮播图 | `GET /api/banner/list` | 否 | 页面局部 ref（`banners`） |
| `views/HomeView.vue`　首页 | 加载分类导航 | `GET /api/category/list` | 否 | 页面局部 ref（`categories`） |
| `views/HomeView.vue`　首页 | 加载推荐作品 | `GET /api/article/recommend?limit=6` | 否 | 页面局部 ref（`recommends`） |
| `views/ArticleListView.vue`　作品列表 | 分页查询 / 筛选 / 搜索 / 排序 | `GET /api/article/list` | 否 | 页面局部（`page` / `page_size` / `filters`）；若已登录会读 `userStore.isLogin` 决定是否展示收藏按钮 |
| `views/ArticleDetailView.vue`　作品详情 | 加载作品正文与状态 | `GET /api/article/detail/<id>` | 否 | 路由参数 `id`；`is_favorite` / `is_liked` 决定按钮高亮 |
| `views/ArticleDetailView.vue`　详情页 | 点赞 / 取消点赞作品 | `POST /api/article/like` | **是** | `userStore.token`；返回的 `liked` / `likes` 回写页面 |
| `views/ArticleDetailView.vue`　详情页 | 收藏 / 取消收藏 | `POST /api/favorite/toggle` | **是** | `userStore.token`；返回 `favorited` 回写页面；收藏成功后 `userStore` 可刷新 `favoriteCount` |
| `components/CommentItem.vue`　书评区 | 加载书评列表（含楼中楼） | `GET /api/comment/list?article_id=<id>` | 否 | 页面局部 ref（`comments` / `total`） |
| `components/CommentItem.vue`　书评区 | 发表书评 / 回复 | `POST /api/comment/add` | **是** | `userStore.token`；成功后重新拉列表或前插新条目 |
| `components/CommentItem.vue`　书评区 | 点赞书评 | `POST /api/comment/like` | **是** | `userStore.token`；返回 `liked` / `likes` 回写该条目 |
| `components/CommentItem.vue`　书评区 | 删除自己的书评 | `DELETE /api/comment/<id>` | **是** | `userStore.token`；比对 `comment.user.id === userStore.userInfo.id` 决定是否显示删除按钮 |
| `views/LoginView.vue`　登录页 | 登录 | `POST /api/user/login` | 否 | **写入** `userStore.login(token, user)`，同步 localStorage |
| `views/RegisterView.vue`　注册页 | 注册 | `POST /api/user/register` | 否 | 仅取 `data.user_id`；注册成功后引导去登录 |
| `views/UserCenterView.vue`　用户中心 | 加载个人资料 | `GET /api/user/profile` | **是** | **读取 / 刷新** `userStore.userInfo`（含 `stat`） |
| `views/UserCenterView.vue`　用户中心 | 修改资料 | `PUT /api/user/profile` | **是** | 更新 `userStore.userInfo` |
| `views/UserCenterView.vue`　用户中心 | 上传头像 | `POST /api/user/avatar`（`multipart/form-data`） | **是** | 拿到 `data.avatar` 后写回 `userStore.userInfo.avatar` |
| `views/FavoriteView.vue`　我的收藏 | 拉取收藏列表（分页） | `GET /api/favorite/list` | **是** | `userStore.token`；页面局部 ref（`list` / `total` / `page`） |
| `layouts/DefaultLayout.vue`　全局 | 退出登录 | `POST /api/user/logout` | **是** | **清空** `userStore`（`token` / `userInfo`）+ 清 localStorage，跳登录页 |

> 复用的两个动作也在这里：**点赞作品**、**收藏** 在作品详情页调用；**点赞书评**、**删除书评** 在书评组件里调用。全站共 **18 个接口**，与接口文档一一对应。

---

## 二、返回字段 → 前端用途对照

### 2.1 分页结构（所有列表接口通用）

`GET /api/article/list`、`GET /api/comment/list`、`GET /api/favorite/list` 的 `data` 都是这个形状：

| 返回字段 | 类型 | 前端用途 |
|---|---|---|
| `data.list` | array | 渲染列表，`v-for` 遍历的数据源 |
| `data.total` | int | 分页组件总条数，`<el-pagination :total>` |
| `data.page` | int | 当前页，回写分页组件 `v-model:current-page` |
| `data.page_size` | int | 每页条数，回写分页组件 `v-model:page-size` |
| `list[].id` | int | `:key`、跳详情的路由参数 |

### 2.2 作品列表条目（`data.list[]`）

| 返回字段 | 前端用途 |
|---|---|
| `title` | 卡片标题 |
| `author` | 卡片副标题「作者」 |
| `dynasty` | 朝代标签 |
| `category_name` | 分类标签（来自视图 `v_article_list` 的 JOIN 结果，前端不必再查分类名） |
| `cover` | 卡片封面图；为空时用占位样式，**不要拼 `http://` 前缀**，后端给的是站内路径 |
| `summary` | 卡片摘要，长文本用 CSS 截断 |
| `views` | 浏览量（`hot` 排序依据） |
| `likes` | 点赞数（`likes` 排序依据） |
| `created_at` | 发布时间，格式化后展示 |

### 2.3 作品详情（`data`）

| 返回字段 | 前端用途 |
|---|---|
| `content` | 正文渲染 |
| `views` | 浏览量（后端已 +1，前端直接展示） |
| `likes` | 点赞数，点赞接口返回后覆盖 |
| `is_favorite` | 收藏按钮初始高亮状态 |
| `is_liked` | 点赞按钮初始高亮状态 |
| `comment_count` | 书评数量徽标 |

### 2.4 书评（`data.list[]`）

| 返回字段 | 前端用途 |
|---|---|
| `content` | 书评正文 |
| `likes` | 点赞数 |
| `user.id` | 判断「这条是不是我发的」→ 决定是否显示删除按钮 |
| `user.nickname` | 显示昵称（不要直接用 `user.username`） |
| `user.avatar` | 头像；缺失时用默认头像 |
| `created_at` | 发表时间 |
| `children` | 楼中楼回复数组，递归渲染同款结构 |

### 2.5 用户与登录

| 返回字段 | 前端用途 |
|---|---|
| `data.token`（登录） | 存 `userStore.token` + localStorage，后续请求头带 `Authorization: Bearer <token>` |
| `data.user`（登录） | 存 `userStore.userInfo`，顶栏显示昵称 / 头像 |
| `data.user_id`（注册） | 仅需提示注册成功，引导跳登录 |
| `data.avatar`（上传头像） | 回写 `userStore.userInfo.avatar`，页面立即刷新 |
| `stat.comment_count` / `stat.favorite_count` | 用户中心统计卡片 |
| `data.favorited`（收藏 toggle） | 收藏按钮状态翻转 |
| `data.liked` / `data.likes`（点赞） | 点赞按钮状态 + 数字翻转 |

### 2.6 首页

| 返回字段 | 前端用途 |
|---|---|
| `id` / `title` / `image` / `link`（轮播图） | `<el-carousel>` 项；`link` 直接作为跳转目标 |
| `id` / `name` / `description`（分类） | 分类导航；点击带 `category_id` 跳作品列表 |

---

## 三、联调常见问题

### 3.1 四类高频问题速查

| 现象 | 最可能的原因 | 排查步骤 |
|---|---|---|
| **404**：`{code:404,msg:"接口不存在"}` | 路径写错（大小写 / 复数 / 少了 `/api`） | ① 后端开 `flask --app app routes` 看已注册路由列表；② 比对接口文档的路径，注意是 `/api/article/list` 而不是 `/api/articles/list`；③ 确认 `base_url` 结尾没有多余斜杠 |
| **401**：`{code:401,msg:"请先登录"}` | 没带 token，或 `Bearer` 后少一个空格 | ① 浏览器 Network 看请求头有没有 `Authorization`；② 确认值是 `Bearer ` + 一个空格 + token；③ 确认登录接口成功返回过 token、`userStore.token` 不是空串；④ 检查 pinia 持久化，刷新后 token 是否还在 |
| **401 过期**：`msg:"登录已过期，请重新登录"` | token 超过 7 天有效期 | 重新登录；拦截器里遇到 `code===401` 统一清 token 并跳登录页（别在每个页面各写一遍） |
| **CORS 被浏览器拦截**：`blocked by CORS policy` | 开发期直接请求了 `http://127.0.0.1:5000`，绕过了 Vite 代理 | ① 前端代码里**只写 `/api/xxx`**，由 `vite.config.js` 的 `server.proxy` 转发；② 若确实要直连后端，确认后端 `cors.init_app(app, resources={r"/api/*": {"origins": "*"}})` 已生效；③ 自测时优先用 Postman / curl，它们的请求不受浏览器同源策略限制 |
| **字段名不符**：页面全空但接口有数据 | 前端取的字段名和后端返回的不一致 | ① Network → 看响应 JSON 的实际字段名；② 逐字比对本文第二节的「返回字段」；③ 常见误写：`category_name` 写成 `categoryName`、`created_at` 写成 `createdAt`、分页 `page_size` 写成 `pageSize`——后端一律**下划线命名**，前端要么原样用，要么在 `api/` 层做一次映射 |
| **分页参数不生效**：翻页没反应 / 总是第一页 | 参数没传、或页码从 0 开始 | ① `page` **从 1 开始**，不是 0；② 确认 `page` / `page_size` 是查询参数（`params`），不是请求体；③ `el-pagination` 的 `current-page` 与 `page` 双向绑定 |
| **图片不显示** | 直接用了 `/static/...` 却没经过后端 | 封面 / 头像路径是后端站内路径，开发期通过代理或完整后端地址访问；先确认后端 `static/` 目录下真有该文件 |
| **点赞 / 收藏点了没反应** | 接口需登录，但你未登录 | 先判 `userStore.isLogin`：未登录就弹登录提示，别直接发请求 |

### 3.2 一条通用排查路径

遇到任何联调问题，**按这个顺序走，不要跳步**：

1. **后端是不是好的？** 用 Postman 或 `curl` 直接打 `http://127.0.0.1:5000/api/...`。
   - 后端也报错 → 问题在后端，看后端终端日志。
   - 后端正常 → 往后走。
2. **请求有没有发出去、发到哪？** 浏览器 DevTools → Network，看这条请求的 URL、方法、请求头、请求体。
   - URL 是 5173（或你的前端端口）且被代理转发 → 正常。
   - URL 直接指向 5000 → 跨域配置问题，回到 3.1。
3. **请求头对不对？** 需要登录的接口，`Authorization: Bearer <token>` 在不在、格式对不对。
4. **响应体是什么？** 看 `code`：200 取 `data`；401 处理登录；400 看 `msg`（通常是参数名写错）；500 看后端日志。
5. **字段对得上吗？** 拿响应 JSON 的字段名，逐字比对本文第二节。

> 记住一个原则：**先分清是「前端没发对」还是「后端没返回对」**。Network 面板一眼就能看出来——请求参数错，是前端；请求对了但响应不对，是后端。

---

## 四、推荐联调顺序

按里程碑 **M3 → M4 → M5** 走，每一步都有明确的验收点。

### M3 · 后端接口可跑（课程二末）

**目标**：不碰前端，只用 Postman 走通「注册 → 登录 → 拿 token → 带 token 查个人信息」。

1. 导入 [`hiwen.postman_collection.json`](../assets/postman/hiwen.postman_collection.json)，确认 `base_url`。
2. 跑「用户 → 注册」，成功拿到 `data.user_id`。
3. 跑「用户 → 登录」，**Tests 脚本会自动把 token 存进 `{{token}}`**。
4. 跑「用户 → 获取个人信息」，能返回资料即 M3 通过。
5. 顺手把首页 3 个公开接口也跑一遍，确认数据库连得上、种子数据在。

**验收**：`{{token}}` 有值，带 token 的接口返回 `code:200`；漏带 token 时返回 `code:401`。

### M4 · 前后端首次联调（课程三中）

**目标**：Vue 页面调用 Flask 接口，把数据库里的作品列表渲染到页面上。

1. 先后端：`python app.py`（5000 端口）。
2. 再前端：`npm run dev`（5173 端口，`/api` 由 Vite 代理）。
3. 从**最不需要登录的接口**开始接：首页 `banner/list` → `category/list` → `article/recommend`。
4. 再接分页列表 `article/list`，把 `data.list` 渲染成卡片、`data.total` 接分页组件。
5. 最后接 `article/detail/<id>`，确认详情能从列表点进去。

**验收**：作品列表能显示 6 篇种子作品、翻页能工作；Network 里所有请求都是 200 且 `code:200`。

### M5 · 业务闭环（课程三末）

**目标**：完成「浏览 → 详情 → 写书评 → 点赞 → 收藏 → 个人中心」完整链路。

1. **登录态**：先接 `user/login`，让 `userStore` 存住 token，并在 axios 拦截器里自动带上。
2. **书写类接口**（都需登录）：`article/like` → `favorite/toggle` → `comment/add`。
3. **个人中心**：`user/profile`（GET / PUT）→ `user/avatar` → `user/logout`。
4. **我的收藏**：`favorite/list`，与 `favorite/toggle` 配对验证。
5. **删除书评**：`DELETE /comment/<id>`，注意用 `user.id` 判断只有自己的书评能删。

**验收**：点赞 / 收藏 / 书评数值实时翻转；退出登录后，需登录的按钮按预期提示或隐藏；整条链路无 401 / 400。

---

## 五、附：接口清单（18 个）

| # | 模块 | 方法 | 路径 | 需登录 |
|---|---|---|---|---|
| 1 | 用户 | POST | `/api/user/register` | 否 |
| 2 | 用户 | POST | `/api/user/login` | 否 |
| 3 | 用户 | GET | `/api/user/profile` | 是 |
| 4 | 用户 | PUT | `/api/user/profile` | 是 |
| 5 | 用户 | POST | `/api/user/avatar` | 是 |
| 6 | 用户 | POST | `/api/user/logout` | 是 |
| 7 | 首页 | GET | `/api/banner/list` | 否 |
| 8 | 首页 | GET | `/api/category/list` | 否 |
| 9 | 首页 | GET | `/api/article/recommend` | 否 |
| 10 | 作品 | GET | `/api/article/list` | 否 |
| 11 | 作品 | GET | `/api/article/detail/<id>` | 否 |
| 12 | 作品 | POST | `/api/article/like` | 是 |
| 13 | 书评 | GET | `/api/comment/list` | 否 |
| 14 | 书评 | POST | `/api/comment/add` | 是 |
| 15 | 书评 | POST | `/api/comment/like` | 是 |
| 16 | 书评 | DELETE | `/api/comment/<id>` | 是 |
| 17 | 收藏 | POST | `/api/favorite/toggle` | 是 |
| 18 | 收藏 | GET | `/api/favorite/list` | 是 |
