---
url: /docs/api-docs/v1/frontend/agreement.md
---
# 前端联调 · 用户协议（agreement.ts）

> 归属：[前端联调 API 手册](./) → `src/api/agreement.ts`
> 端点：**2 个** · 路由前缀 `/api/v1/content`（**模块名是 `agreement`，路由前缀是 `content`**）
> 鉴权：**两个都免登录**（登录页 / 协议页在认证前可见）
> 错误码：**无** —— 非法入参走 `data: null`，不报错（见下）
> 命名风格：🔴 **snake\_case（`updated_at`）** —— 全项目唯一的 snake\_case 响应字段组，前端已对齐，**勿改成 camelCase**
> 取值语义：**[取值速查 · agreement](./values#agreement)** —— `type` 白名单仅两值、**大小写/空白会被归一接受**（`Privacy` 合法）、`version` 的自增语义
> 🆕 **富文本（2026-09-24）**：响应新增 `content_format`（`plain` | `html`）——
> `plain` 为存量（纯文本 `\n\n` 分段，按段渲染照旧）；`html` 为服务端已 sanitize 的
> 富文本（**C 端用 `rich-text` 组件渲染，勿 `v-html`**）。旧版 App 不认识该字段 → 行为不变。

## 本页 curl 变量

```bash
BASE_URL=https://api.qingmulife.cn   # 本地：http://localhost:3000
ENV=Prd                              # Dev | Prd —— 协议表也走 X-Env 双库路由
```

## 端点一览

| # | 方法 | 路径 | 场景 |
|---|---|---|---|
| [1](#listagreements) | GET | `/api/v1/content/agreements` | 登录页一次拉两类协议（用户协议 + 隐私政策） |
| [2](#getagreement) | GET | `/api/v1/content/agreements/{type}` | 协议详情页按 type 取单条 |

***

## listAgreements

**一句话**：拉全部协议 —— 登录页一次拉两类，本地缓存展示。

### curl

```bash
curl -s "$BASE_URL/api/v1/content/agreements" \
  -H 'X-Env: Prd'
```

### 请求

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| — | — | — | **无入参、免登录**（`X-Env` 决定读哪个库） |

### 响应 `data` —— **顶层是数组**

| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
| `[].type` | string | 否 | `'user_agreement'` | `'privacy'` |
| `[].title` | string | 否 | 协议标题 |
| `[].content` | string | 否 | **全文正文** —— `content_format=plain` 时为纯文本（`\n\n` 分段）；`html` 时为已 sanitize 的 HTML |
| `[].content_format` | string | 否 | 🆕 **`plain` | `html`** —— 渲染分支依据（见头注）；存量数据恒 `plain` |
| `[].version` | number | 否 | int，版本号（后台更新自增） |
| `[].updated_at` | string ISO | 否 | 🔴 **snake\_case** —— 与前端 `AgreementContent` 声明一致 ✓ |

> ✅ **契约干净**（`asdict` 直出，前后端已对齐）。

### 错误码

**无业务错误码**（公开只读）。服务不可用 → HTTP 5xx。

***

## getAgreement

**一句话**：按 type 取单条协议 —— 协议详情页。

### curl

```bash
# 用户协议
curl -s "$BASE_URL/api/v1/content/agreements/user_agreement" \
  -H 'X-Env: Prd'

# 隐私政策
curl -s "$BASE_URL/api/v1/content/agreements/privacy" \
  -H 'X-Env: Prd'
```

### 请求

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `{type}` (path) | string | ✔ | 白名单仅两值：`'user_agreement'` | `'privacy'` |

### 响应 `data`

| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
| （整个 `data`） | object | **null** | **是** | 🔴 **null = 非法 type 或无记录**（HTTP 200 + code `"0"`，**不 500**） |
| `data.type` | string | 否 | |
| `data.title` | string | 否 | |
| `data.content` | string | 否 | |
| `data.version` | number | 否 | |
| `data.updated_at` | string ISO | 否 | snake\_case（同列表接口） |
| `data.content_format` | string | 否 | 🆕 `plain` | `html`（同列表接口；渲染分支依据） |

> ⚠️ **`data: null` 不是异常**：写成 `user-agreement`（连字符）/ `privacy_policy` 之类都会静默返回 null。
> 前端已有 `res?.code === '0' && res.data` 判空 ✅ 容错 —— **保持这个写法**（类型声明写的是非空 `AgreementContent`，别直接 `res.data.content`）。

### 错误码

| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
| — | **200** | **非法 type / 无记录** | ⚠️ **不是错误码** —— 判 `data === null`，展示兜底文案或退回列表 |

***

## 本页合同要点速记

| 要点 | 值 |
|---|---|
| 响应字段命名 | **`updated_at`（snake\_case）** —— 全项目唯一，勿"顺手修正" |
| 免登录 | 两个端点都公开（协议在登录前就要看） |
| 空结果 | `{type}` 非法 / 无记录 → **`data: null` + HTTP 200 + code "0"**，不是错误码 |
