feat: 更新名片云 API 文档,移除 Telegram 和 Messenger 独立字段,统一为社交媒体账号
This commit is contained in:
216
docs/CARD_API_REFERENCE.md
Normal file
216
docs/CARD_API_REFERENCE.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# 名片云(card addon)API 对接文档
|
||||
|
||||
> 读者:UniApp 前端开发人员(微信小程序 / H5 / 鸿蒙元服务三端)。
|
||||
> 事实来源:`backend/src/addon/card/api/controller/`(Card.php / Contact.php / Lead.php / Recognize.php / BaseCardApi.php)。
|
||||
> 文档与源码如有出入,以源码为准。最后更新:2026-10-13。
|
||||
|
||||
---
|
||||
|
||||
## 1. 总览
|
||||
|
||||
| 模块 | URL 前缀 | 用途 |
|
||||
|---|---|---|
|
||||
| 名片夹 | `/card/api/card/*` | 我的名片(列表 / 详情 / 编辑 / 认领 / 识别建卡) |
|
||||
| 人脉库 | `/card/api/contact/*` | 我的人脉(增删改查 / 转线索) |
|
||||
| 潜在客户 | `/card/api/lead/*` | 名片收到的留资线索(列表 / 跟进状态) |
|
||||
| 识别(旧) | `/card/api/recognize/execute` | 旧版识别入口,新代码请用 `/card/api/card/recognize` |
|
||||
|
||||
**前端收口要求**:所有 URL 必须集中在 API 层统一管理(项目内已有页面直连这些地址,路径一个字符都不能变,只做收口不做改名)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 通用约定
|
||||
|
||||
### 2.1 URL 规则
|
||||
|
||||
addon 标准路由:`/{addon}/api/{controller}/{action}`,例如:
|
||||
|
||||
```
|
||||
POST https://<域名>/card/api/card/lists
|
||||
```
|
||||
|
||||
### 2.2 鉴权(全部接口强制)
|
||||
|
||||
- 所有接口都要求 member token(后端 `BaseCardApi::requireAuth()` 强制,匿名一律 `code < 0` 拒绝)。
|
||||
- 走项目现有请求工具(自动携带 `token` 与 `uniacid` 参数/头),**不要额外传 `site_id`**——后端 site_id 只认 token 绑定值,入参传了也会被忽略。
|
||||
|
||||
### 2.3 返回信封
|
||||
|
||||
统一 JSON:
|
||||
|
||||
```json
|
||||
{ "code": 0, "message": "success", "data": { } }
|
||||
```
|
||||
|
||||
- `code === 0`:成功,业务数据在 `data`。
|
||||
- `code !== 0`:失败,`message` 可直接 toast(用户可读文案)。
|
||||
|
||||
### 2.4 行级权限语义(重要,影响 UI)
|
||||
|
||||
- 所有数据按 `owner_id = token 会员` 行级隔离:列表只返回自己的卡/人脉/线索。
|
||||
- 访问**不存在**或**不属于自己**的资源,后端统一报「不存在」(如「卡片不存在」「人脉不存在」「线索不存在」),不区分两种情况(防探测)。前端提示文案按"内容不存在或已删除"处理,不要提示"无权限"。
|
||||
- `owner_id`、`source_type` 等系统字段不可通过入参修改(后端显式收口)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 名片夹 `/card/api/card/*`
|
||||
|
||||
### 3.1 我的名片列表 `card/lists`
|
||||
|
||||
- 入参:`page`(默认 1)、`page_size`(默认 20,**上限 100**)、`keyword`(姓名/手机/公司模糊,选填)
|
||||
- 返回 `data`:
|
||||
|
||||
```json
|
||||
{ "list": [ /* relation 行,见 3.2 字段说明;不含 access_password */ ],
|
||||
"count": 0, "page_count": 0 }
|
||||
```
|
||||
|
||||
### 3.2 卡片详情 `card/detail`
|
||||
|
||||
- 入参:`card_id`(必填,字符串,形如 `c_1_shop_20261013...`)
|
||||
- 返回 `data`:
|
||||
|
||||
```json
|
||||
{
|
||||
"relation": { /* 卡片主行,字段见下方"卡片字段表" */ },
|
||||
"source": { /* 识别来源行,可为 null */ },
|
||||
"share_list": [ /* 关联分享配置 */ ]
|
||||
}
|
||||
```
|
||||
|
||||
卡片字段表(`relation`,渲染用带 `_list` 后缀的键):
|
||||
|
||||
| 键 | 说明 |
|
||||
|---|---|
|
||||
| `card_id` | 卡片唯一标识(页面路由参数用它,不用自增 id) |
|
||||
| `name / company / job_title / mobile / phone / email / website / address / postal_code / country / city / province / wechat / telegram / messenger / avatar / bio` | 标量字段 |
|
||||
| `mobiles_list` | `[{value}]` 多手机号(解码后) |
|
||||
| `socials_list` | `[{type, label, value}]` 社交账号(`label` 是后端翻译好的中文名,直接展示)。**Telegram/Messenger 已并入此处**(旧独立字段软下线,后端读取时自动合并旧列值) |
|
||||
| `custom_list` | `[{label, value}]` 自定义字段 |
|
||||
| `locked_fields` | `[{key}]` 站点锁定字段清单,**编辑页据此渲染只读**,提交时后端也会剥离(静默丢弃,不报错) |
|
||||
| `source_type` | 来源:manual / manual_batch / scan / import_csv / import_xlsx / clone / api |
|
||||
| `owner_id` | 归属会员 id(只读) |
|
||||
|
||||
- 原始 JSON 串 `mobiles / socials / custom_fields` 仍在返回中(兼容保留),**新代码一律消费 `*_list`**。
|
||||
|
||||
### 3.3 更新卡片 `card/update`
|
||||
|
||||
- 入参:`card_id`(必填)+ `fields`(嵌套对象;后端按 `fields/a` 收)
|
||||
- ⚠️ **normalize 语义(历史事故定稿,必须遵守)**:
|
||||
- **键存在才写**:`fields` 里带了哪个键就更新哪个键;
|
||||
- 空字符串 `""` = 显式清空该字段;
|
||||
- **键缺席 = 不擦除**(保持原值);
|
||||
- 因此局部更新时**只传用户实际编辑过的键**,不要把整包表单无脑回传。
|
||||
- 富化字段入参 wire format(放在 `fields` 内):
|
||||
|
||||
```json
|
||||
{
|
||||
"mobiles": ["13800000000", { "value": "13911112222" }],
|
||||
"socials": [ { "type": "douyin", "value": "xxx" } ],
|
||||
"custom_fields": [ { "label": "展厅号", "value": "E3-108" } ]
|
||||
}
|
||||
```
|
||||
|
||||
- `socials.type` 白名单:`alipay / douyin / wxvideo / xiaohongshu / weibo / kuaishou / bilibili / qq / linkedin / facebook / instagram / x / telegram / messenger / other`(type 非法或 value 空的条目被后端静默丢弃);
|
||||
- ⚠️ `telegram / messenger` 已**不再是独立标量字段**(2026-10-13 软下线):`fields.telegram` / `fields.messenger` 传了也会被忽略,统一走 `socials` 数组按类型添加;旧数据由后端在 `socials_list` 中自动合并返回;
|
||||
- `custom_fields`:label 与 value 必须同时非空,否则该条被丢弃;label 截断 20 字,value 截断 255 字。
|
||||
- 返回 `data`:更新后的 relation 行。
|
||||
- 嵌套对象跨端序列化:请用 JSON body(或项目 `sendJson()` 通道);后端收 `fields` 为对象/数组。
|
||||
|
||||
### 3.4 认领名片 `card/claim`
|
||||
|
||||
- 入参:`card_id`(必填)
|
||||
- 场景:商家后台建的卡未归属(owner_id=0),员工把它认领到自己名下。
|
||||
- 返回 `data`:认领结果(失败时 `message` 可直接展示)。
|
||||
|
||||
### 3.5 识别建卡 `card/recognize`
|
||||
|
||||
- 入参:`media_path`(必填,图片/媒体地址)、`media_type`(默认 `image`)、`source_type`(默认 `scan`)
|
||||
- 返回 `data`:
|
||||
|
||||
```json
|
||||
{ "source_id": 1, "relation_id": 2, "card_id": "c_1_shop_...",
|
||||
"deduped": false, "fields": { /* 归一化后的识别字段 */ } }
|
||||
```
|
||||
|
||||
- ⚠️ `deduped === true` 表示站点内已有同手机号名片,**未新建**、绑到了既有卡上——前端应提示用户"已存在同名片"。
|
||||
- 注意:当前后端识别服务为桩实现(`识别服务未注册` 会作为错误返回),对接时请按失败分支兜底 UI。
|
||||
|
||||
### 3.6 识别失败重试 `card/retry`
|
||||
|
||||
- 入参:`source_id`(必填)
|
||||
- 约束:仅本站 + status=failed 的来源可重试,有次数上限(超限报错,message 可直接展示)。
|
||||
- 返回 `data`:`{ source_id, relation_id, card_id, retry_count, deduped, message }`。
|
||||
|
||||
### 3.7 批量识别 `card/recognizeBatch`
|
||||
|
||||
- 入参:`items`:非空数组,1~10 条,每项 `{ source_type?, media_type?, media_path, media_key? }`。
|
||||
- 返回 `data`:`{ items: [ { ok: bool, message, data } ], ok: 成功数, failed: 失败数 }`——逐项独立成败,单条失败不影响其它。
|
||||
|
||||
### 3.8 旧入口 `recognize/execute`
|
||||
|
||||
- 入参与 `card/recognize` 相同。**已上线页面可能仍在用,新代码一律用 `card/recognize`。**
|
||||
|
||||
---
|
||||
|
||||
## 4. 人脉库 `/card/api/contact/*`
|
||||
|
||||
### 4.1 人脉列表 `contact/lists`
|
||||
|
||||
- 入参:`page`、`page_size`(≤100)、`keyword`(姓名/手机/公司模糊)、`source`(manual/scan/exchange/lead_in,选填)、`status`(0/1,选填)
|
||||
- 返回 `data`:`{ list, count, page_count }`
|
||||
|
||||
### 4.2 新增人脉 `contact/add`
|
||||
|
||||
- 入参:`name`(**必填**);选填:`company / job_title / mobile / email / wechat / website / address / remark`
|
||||
- 返回 `data`:新增行。
|
||||
|
||||
### 4.3 人脉详情 `contact/info`
|
||||
|
||||
- 入参:`id`(数字自增主键,注意与名片的 `card_id` 字符串不同)
|
||||
- 返回 `data`:人脉行(编辑页回填用)。
|
||||
|
||||
### 4.4 更新人脉 `contact/update`
|
||||
|
||||
- 入参:`id` + 字段。兼容两种传参:`fields` 对象,或顶层平铺字段(顶层会剔除 `id/token/uniacid/fields`)。
|
||||
- 可更新白名单:`name / company / job_title / mobile / email / wechat / website / address / remark / status`
|
||||
- 返回 `data`:更新后行。
|
||||
|
||||
### 4.5 删除人脉 `contact/delete`
|
||||
|
||||
- 入参:`id`。**真删**(不可恢复),前端建议二次确认弹窗。
|
||||
|
||||
### 4.6 人脉 → 潜在客户 `contact/toLead`
|
||||
|
||||
- 入参:`id`(人脉 id)
|
||||
- 行为:写一条潜在客户线索(归属当前员工),**不删人脉行**;同员工同手机号已转过则报错(手机号为空时直接插入)。
|
||||
- 返回 `data`:新线索信息。
|
||||
|
||||
---
|
||||
|
||||
## 5. 潜在客户 `/card/api/lead/*`
|
||||
|
||||
### 5.1 线索列表 `lead/lists`
|
||||
|
||||
- 入参:`page`、`page_size`(≤100)、`status`(0-3,选填)、`keyword`(姓名/手机模糊)
|
||||
- 可见范围:**自己名下名片收到的线索**(按名片归属过滤)+ 自己从人脉转入的线索。没有名片时列表为空属正常现象。
|
||||
- 返回 `data`:`{ list, count, page_count }`
|
||||
|
||||
### 5.2 更新跟进状态 `lead/setStatus`
|
||||
|
||||
- 入参:`id`(线索 id)、`status`(**只能是 0/1/2/3**,其它值报「参数不合法」)
|
||||
- 返回 `data`:`{ id, status }`
|
||||
|
||||
---
|
||||
|
||||
## 6. 对接 checklist(前端收口时逐条核对)
|
||||
|
||||
1. [ ] 所有 `/card/api/*` URL 收口到 API 层,与本文档路径逐一比对(现有 7 个页面 12 处直连,路径不可变)。
|
||||
2. [ ] 统一走项目请求工具(自动带 token / uniacid),任何接口不传 `site_id`。
|
||||
3. [ ] 失败分支统一 `toast(message)`;「不存在」类错误按"内容不存在或已删除"提示。
|
||||
4. [ ] `card/update` 只传编辑过的键(键存在才写语义)。
|
||||
5. [ ] 详情/列表渲染富化字段用 `*_list` 解码键,不解析原始 JSON 串。
|
||||
6. [ ] 编辑页对 `locked_fields` 命中的字段渲染只读。
|
||||
7. [ ] `recognize` 成功后检查 `deduped`,为 true 时提示"已存在同手机号名片"。
|
||||
8. [ ] 分页参数 `page_size` 不超过 100。
|
||||
9. [ ] 名片路由参数用 `card_id`(字符串),人脉/线索用 `id`(数字),不要混用。
|
||||
@@ -105,8 +105,7 @@ export default {
|
||||
push('网站', r.website);
|
||||
push('地址', [r.country, r.province, r.city, r.address].filter(Boolean).join(' '));
|
||||
push('邮编', r.postal_code);
|
||||
push('Telegram', r.telegram);
|
||||
push('Messenger', r.messenger);
|
||||
// Telegram/Messenger 已软下线为社交流, 经 socials_list 统一渲染
|
||||
|
||||
// 附加手机号(mobiles_list, 后端 CardRichFields 解码) → 可拨打
|
||||
(r.mobiles_list || []).forEach((m, i) => {
|
||||
|
||||
@@ -66,16 +66,7 @@
|
||||
<input class="row-input" :value="form.wechat" placeholder="微信号(选填)"
|
||||
@input="e => form.wechat = e.detail.value" />
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="row-label">Telegram</view>
|
||||
<input class="row-input" :value="form.telegram" placeholder="Telegram 账号(选填)"
|
||||
@input="e => form.telegram = e.detail.value" />
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="row-label">Messenger</view>
|
||||
<input class="row-input" :value="form.messenger" placeholder="Messenger 账号(选填)"
|
||||
@input="e => form.messenger = e.detail.value" />
|
||||
</view>
|
||||
<!-- Telegram/Messenger 已软下线为社交流: 在下方「添加社交媒体」里选择类型添加 -->
|
||||
</view>
|
||||
|
||||
<!-- 社交媒体账号 -->
|
||||
@@ -164,10 +155,12 @@
|
||||
// 后端 Card::update 的 richFieldSaves 仅对显式携带的键做 CardRichFields 清洗。
|
||||
const FIELDS = [
|
||||
'name', 'company', 'job_title', 'avatar',
|
||||
'mobile', 'phone', 'email', 'website', 'wechat', 'telegram', 'messenger',
|
||||
'mobile', 'phone', 'email', 'website', 'wechat',
|
||||
'country', 'province', 'city', 'address', 'postal_code',
|
||||
'bio'
|
||||
];
|
||||
// telegram/messenger 已软下线(2026-10-13): 不再是独立标量字段,
|
||||
// 统一在「社交媒体账号」动态行里以类型方式添加(见下方 SOCIAL_TYPES)。
|
||||
|
||||
// 社交媒体类型: 必须与后端 CardRichFields::SOCIAL_TYPES 白名单保持一致(增减需两边同步)
|
||||
const SOCIAL_TYPES = [
|
||||
@@ -183,6 +176,8 @@ const SOCIAL_TYPES = [
|
||||
{ key: 'facebook', label: 'Facebook' },
|
||||
{ key: 'instagram', label: 'Instagram' },
|
||||
{ key: 'x', label: 'X' },
|
||||
{ key: 'telegram', label: 'Telegram' },
|
||||
{ key: 'messenger', label: 'Messenger' },
|
||||
{ key: 'other', label: '其他' }
|
||||
];
|
||||
|
||||
@@ -197,7 +192,7 @@ export default {
|
||||
cardId: '',
|
||||
form: {
|
||||
name: '', company: '', job_title: '', avatar: '',
|
||||
mobile: '', phone: '', email: '', website: '', wechat: '', telegram: '', messenger: '',
|
||||
mobile: '', phone: '', email: '', website: '', wechat: '',
|
||||
country: '', province: '', city: '', address: '', postal_code: '',
|
||||
bio: ''
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user