Appearance
前端联调 · 设备线(device.ts)
归属:前端联调 API 手册 →
src/api/device.ts端点:4 个(授权中心 2 + 设备绑定 action 2) 错误码段:40403xxx(全表见 错误码注册表) 错误契约:HTTP 400 / 401 / 404 +{code, msg, data}取值语义:取值速查 · device —— 12 家厂商meta.id清单、authType四值各自的交互形态、status四态的判定优先级、platform的静默归一(iOS→web) 深入说明:Device 设备线 API Docs · 授权中心四态
本页 curl 变量
bash
BASE_URL=https://api.qingmulife.cn # 本地:http://localhost:3000
ACCESS=<access_token>
ENV=Prd # Dev | Prd —— 全程必须一致
VENDOR=honor_oauth # meta.id —— 全表固定值,勿硬编码中文名全部端点需
Bearer access+X-Env。
端点一览
| # | 方法 | 路径 | 场景 |
|---|---|---|---|
| 1 | GET | /api/v1/device/vendors | 授权中心列表(12 平台 + 四态) |
| 2 | POST | /api/v1/device/vendors/{id}/auth-url | 发起厂商授权(拿 authUrl 给 webview) |
| 3 | POST | /api/v1/device myBindings | 我的绑定设备列表 |
| 4 | POST | /api/v1/device unbind | 解绑设备 |
listVendors
一句话:拉厂商清单 + 每家的健康状态与四态绑定状态 —— 授权中心页(mine/data/device-list)进入时。
curl
bash
curl -s "$BASE_URL/api/v1/device/vendors" \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS"请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
Authorization | header | ✔ | Bearer <access> |
X-Env | header | ✔ | 双库路由(厂商清单与健康状态也按库算) |
无 body / query。
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
vendors[].meta.id | string | 否 | 机器键:honor_oauth / zepp / xiaomi / suunto + 9 家占位 —— unbind 与 auth-url 都传这个值 |
vendors[].meta.displayName.zh / .en | string | 否 | 展示名 |
vendors[].meta.logoUrl | string | null | 是 | 厂商 logo(CDN) |
vendors[].meta.authType | string | 否 | oauth | ble | import | sync |
vendors[].meta.dataWindowDays | number | 否 | 可回溯数据天数 |
vendors[].meta.capabilities.sportStat / sampleSets / healthRecord / realtime | boolean ×4 | 否 | 能力位 |
vendors[].health.ok | boolean | 否 | 厂商服务是否可用 |
vendors[].health.detail | string | null | undefined | — | 🔴 后端不存在此键!对应物是 health.message |
vendors[].health.configured / health.serviceReady | boolean | 否 | 后端返回、前端未声明(不消费无影响) |
vendors[].health.message | string | null | 是 | 健康状态说明 —— 要展示文案就用这个,不是 detail |
vendors[].status | string | 否 | 四态:'bound' | 'available' | 'reviewing' | 'error'(优先级 bound > available > error > reviewing) |
🔴 坑位 ·
health.detail:前端VendorEntry声明了health.detail?(optional),但后端没有这个键 —— 展示时恒undefined。 因 optional 且当前 UI 只消费meta/status,运行时不炸;要用健康文案请改读health.message(或让后端补detail别名)。
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40403002 | 401 | 未登录 / token 无效 | 走拦截器 401 分档 |
status 四态 → UI 建议
| 状态 | 含义 | 前端展示 |
|---|---|---|
bound | 已绑定 | 「已连接」+ 可解绑 |
available | 可授权 | 「去授权」按钮可点 |
reviewing | 厂家审核中(占位厂家) | ⚠️ 12 平台全部展示(2026-09-19 决策回滚了更早的「隐藏」口径)—— 整行可点,点击 toast 引导预期,见 device-list.vue:207 |
error | 服务异常 / 未配置 | 置灰 + 提示稍后再试 |
getAuthUrl
一句话:发起厂商授权 —— 拿 authUrl 丢给 webview 加载(授权中心点「去授权」)。
curl
bash
# body 传空对象即可(zepp 三端才需要 platform)
curl -s "$BASE_URL/api/v1/device/vendors/honor_oauth/auth-url" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{}'
# zepp 三端(platform 可选)
curl -s "$BASE_URL/api/v1/device/vendors/zepp/auth-url" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"platform":"android"}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
{vendor_id} (path) | string | ✔ | 传 vendors[].meta.id(如 honor_oauth)—— 不是中文名 |
platform | string | ✖ | 仅 zepp 三端需要;缺省合法 |
Authorization / X-Env | header | ✔ |
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
authUrl | string | 否 | 厂商授权页 URL → webview 加载 |
sessionKey | string | null | 是 | 授权会话键(部分厂商返回) |
requireProfileCompletion | boolean | 否 | 后端恒返回第三键,前端未声明(不消费,无影响) |
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40403004 | 404 | 未知 vendor id(registry 查不到) | 用 meta.id,别用中文名拼 |
40403002 | 401 | 未登录 | 走拦截器 401 分档 |
40403006 | 400 | 厂家审核中(占位厂家) | 「该设备正在接入中」置灰 |
40403007 | 400 | 厂商形态不支持该方法 | 按 authType 隐藏入口 |
myBindings
一句话:我的绑定设备列表 —— 设备页显示已连接设备。
curl
bash
curl -s "$BASE_URL/api/v1/device" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"action":"myBindings","payload":{}}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
action | string | ✔ | 固定 "myBindings" |
payload | object | ✖ | 忽略 |
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
list[].id | string | 否 | 绑定记录 id(前端类型写成 number | string,更宽 ✓) |
list[].vendor | string | 否 | 厂商键(honor_oauth / zepp / xiaomi / suunto…) |
list[].status | string | 否 | active | revoked… |
list[].vendorUserId | string | null | 是 | 厂商侧用户 id |
list[].lastSyncAt | string ISO | null | 是 | 最后同步时刻 |
list[].createdAt | string ISO | 否 | 非空(前端类型声明 string | null,更宽 ✓) |
✅ 契约干净。注意
list[].vendor与vendors[].meta.id同值域 ——unbind直接传它即可精确匹配。
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40403002 | 401 | 未登录 | 走拦截器 401 分档 |
unbind
一句话:解绑设备 —— 设备列表点「解除绑定」。
curl
bash
curl -s "$BASE_URL/api/v1/device" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"action":"unbind","payload":{"vendor":"honor_oauth"}}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
action | string | ✔ | 固定 "unbind" |
payload.vendor | string | ✔ | 1–40 字符;传 meta.id / binding.vendor(页面传 device.id)—— 精确匹配,无模糊 |
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
ok | boolean | 否 | 恒 true |
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40403005 | 404 | 该用户未绑定此厂商(BINDING_NOT_FOUND) | toast「未找到绑定记录」+ 刷新列表 |
40403001 | 400 | vendor 缺失 / 超 40 字符 / 未知 action | 检查调用点 |
40403002 | 401 | 未登录 | 走拦截器 401 分档 |
⚠️ 40403005 实测记录(2026-09-21 真机联调)
实测报文(18569401013 / 无任何 IoT 绑定的账号):
json
{"code":"40403005","msg":"binding not found","data":null}两个必须知道的点:
msg是英文技术黑话,且会直接弹给用户。utils/request.ts在业务码非 0 时统一toast(body.msg)—— 所以用户看到的就是binding not found这句英文。本手册的「前端建议处置」列并不生效: 前端若不自造文案,透传的就是后端原文。 → 契约层面应要求后端返回中文用户文案(如「该设备尚未绑定」), 而不是让每个客户端各写一份映射(那就是 DBG-009 那类「改一处漏一处」)。「刷新列表」这一列不是可选建议,是必需。 本码的语义是**「你的本地状态已过期」**(TOCTOU:下拉列表是
onShow的快照, 用户读到点击之间状态可能已变)。不刷新 = 幽灵状态持续存在,用户会重复撞同一个错。
边界说明:本码的存在是刻意的 —— 后端 DeviceErrorCode.BINDING_NOT_FOUND → HTTP 404 是 V1 兼容(注释自陈「V1 Errors.notFound」),不是缺陷。缺陷只在 msg 与前端处置。