From f22892968f3c14680f8ac5f20b477bc6a4318113 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Tue, 13 Oct 2026 10:22:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E5=90=8D=E7=89=87?= =?UTF-8?q?=E4=BA=91=20API=20=E6=96=87=E6=A1=A3=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=20Telegram=20=E5=92=8C=20Messenger=20=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=8C=E7=BB=9F=E4=B8=80=E4=B8=BA=E7=A4=BE?= =?UTF-8?q?=E4=BA=A4=E5=AA=92=E4=BD=93=E8=B4=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CARD_API_REFERENCE.md | 216 +++++++++++++++++++++++++++++++++++++ pages_tool/card/detail.vue | 3 +- pages_tool/card/edit.vue | 19 ++-- 3 files changed, 224 insertions(+), 14 deletions(-) create mode 100644 docs/CARD_API_REFERENCE.md diff --git a/docs/CARD_API_REFERENCE.md b/docs/CARD_API_REFERENCE.md new file mode 100644 index 0000000..c293667 --- /dev/null +++ b/docs/CARD_API_REFERENCE.md @@ -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`(数字),不要混用。 diff --git a/pages_tool/card/detail.vue b/pages_tool/card/detail.vue index cf1b690..b2253f7 100644 --- a/pages_tool/card/detail.vue +++ b/pages_tool/card/detail.vue @@ -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) => { diff --git a/pages_tool/card/edit.vue b/pages_tool/card/edit.vue index fa91523..023dc31 100644 --- a/pages_tool/card/edit.vue +++ b/pages_tool/card/edit.vue @@ -66,16 +66,7 @@ - - Telegram - - - - Messenger - - + @@ -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: '' },