568 lines
23 KiB
JavaScript
568 lines
23 KiB
JavaScript
/**
|
||
* 名片云(card addon)API 收口层
|
||
*
|
||
* 单一事实来源:后端《名片云(card addon)API 对接文档》CARD_API_REFERENCE.md(2026-10-13)
|
||
* 文档与源码如有出入,以源码为准;URL 路径一个字符都不能变,只做收口不做改名。
|
||
*
|
||
* 收口要求(来自后端 checklist):
|
||
* 1. 所有 /card/api/* URL 必须集中在本层,页面不得再直连字面量;
|
||
* 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(数字),不要混用。
|
||
*
|
||
* 用法(页面内 this.$cardApi):
|
||
* this.$cardApi.cardLists({ data: { page: 1 }, success: res => {}, fail: () => {} })
|
||
*
|
||
* 注意:本层只做"URL 收口 + 通道选择",不做业务加工;
|
||
* 业务字典(状态/来源/字段白名单)随端口导出,供各页面复用。
|
||
*/
|
||
import Http from './http.js';
|
||
|
||
/** 所有端点路径(与后端文档逐一比对,改动需两边同步) */
|
||
export const CARD_API_URL = {
|
||
// === 名片夹 /card/api/card/* ===
|
||
CARD_LISTS: '/card/api/card/lists',
|
||
CARD_DETAIL: '/card/api/card/detail',
|
||
CARD_UPDATE: '/card/api/card/update',
|
||
CARD_FAVORITE: '/card/api/card/toggleFavorite', // 收藏(双向 toggle, 后端 2.9.12)
|
||
CARD_CREATE: '/card/api/card/create', // 新建数字名片(后端源码已有, 文档未列; 以源码为准)
|
||
CARD_CLAIM: '/card/api/card/claim',
|
||
CARD_RECOGNIZE: '/card/api/card/recognize',
|
||
CARD_RETRY: '/card/api/card/retry',
|
||
CARD_RECOGNIZE_BATCH: '/card/api/card/recognizeBatch',
|
||
CARD_DELETE: '/card/api/card/delete', // 删除自己的名片(软删, 2.11.91)
|
||
|
||
// === 跟进完成态 2.11.3 /card/api/card/* ===
|
||
CARD_FOLLOW_DONE: '/card/api/card/followDone',
|
||
// === 访客轨迹 2.11.4 /card/api/card/* ===
|
||
CARD_VIEWS: '/card/api/card/views',
|
||
|
||
// === 员工建卡申请 2.11.15 /card/api/apply/* ===
|
||
APPLY_SUBMIT: '/card/api/apply/submit',
|
||
APPLY_STATUS: '/card/api/apply/status',
|
||
|
||
// === 人脉库 /card/api/contact/* ===
|
||
CONTACT_LISTS: '/card/api/contact/lists',
|
||
CONTACT_ADD: '/card/api/contact/add',
|
||
CONTACT_INFO: '/card/api/contact/info',
|
||
CONTACT_UPDATE: '/card/api/contact/update',
|
||
CONTACT_DELETE: '/card/api/contact/delete',
|
||
CONTACT_TO_LEAD: '/card/api/contact/toLead',
|
||
|
||
// === 潜在客户 /card/api/lead/* ===
|
||
LEAD_LISTS: '/card/api/lead/lists',
|
||
LEAD_SET_STATUS: '/card/api/lead/setStatus',
|
||
|
||
// === 交换名片 /card/api/exchange/* ===
|
||
EXCHANGE_LISTS: '/card/api/exchange/lists',
|
||
EXCHANGE_AGREE: '/card/api/exchange/agree',
|
||
EXCHANGE_IGNORE: '/card/api/exchange/ignore',
|
||
|
||
// === 名片设计(员工端可选样式, 后端 v2) /card/api/design/* ===
|
||
// 预设列表必须与商家后台 CardTemplatePresets 同源(避免前端自建漂移)
|
||
// ⚠ v2 路径无 /card/ 段: 初版 /card/api/card/design/* 在本框架 action 单段路由下 404
|
||
CARD_DESIGN_PRESETS: '/card/api/design/presets', // 预设列表(同源后台 CardTemplatePresets)
|
||
CARD_DESIGN_SAVE: '/card/api/design/save', // 应用预设/保存本卡样式
|
||
|
||
// === 企业名片 /card/api/enterprise/* (2.13.6) ===
|
||
// 业务模型: 企业卡片 owner_id=0(店铺级), 同一时刻仅 1 张 is_active=1 生效;
|
||
// 名片区三模式 header_mode: official(指定/默认官方企业卡) / self(员工自选个人卡) / staff(员工轮播)
|
||
ENTERPRISE_DETAIL: '/card/api/enterprise/detail', // GET ?card_id= 不传=当前生效卡
|
||
ENTERPRISE_SAVE: '/card/api/enterprise/save', // POST {card_id?,fields?,design?,header_mode?,official_card_id?}
|
||
ENTERPRISE_MINE: '/card/api/enterprise/mine', // GET 自己名下企业卡列表
|
||
ENTERPRISE_ACTIVATE: '/card/api/enterprise/activate', // POST {card_id} 切换生效(互斥)
|
||
ENTERPRISE_CHOOSE_SELF: '/card/api/enterprise/chooseSelf', // POST {card_id} 员工自选展示个人卡
|
||
ENTERPRISE_DELETE: '/card/api/enterprise/delete' // POST {card_id} 删候选卡(生效卡禁删)
|
||
};
|
||
|
||
/**
|
||
* 社交媒体类型白名单:必须与后端 CardRichFields::SOCIAL_TYPES 保持一致(增减需两边同步)。
|
||
* type 非法或 value 为空的条目会被后端静默丢弃。
|
||
* ⚠️ 2026-10-13:telegram / messenger 由独立标量字段软下线,统一并入本数组;
|
||
* 旧数据后端读取时自动合并进 socials_list(见 CARD_API_REFERENCE.md 3.3)。
|
||
*/
|
||
export const SOCIAL_TYPES = [
|
||
{ key: 'alipay', label: '支付宝' },
|
||
{ key: 'douyin', label: '抖音' },
|
||
{ key: 'wxvideo', label: '视频号' },
|
||
{ key: 'xiaohongshu', label: '小红书' },
|
||
{ key: 'weibo', label: '微博' },
|
||
{ key: 'kuaishou', label: '快手' },
|
||
{ key: 'bilibili', label: '哔哩哔哩' },
|
||
{ key: 'qq', label: 'QQ' },
|
||
{ key: 'linkedin', label: 'LinkedIn' },
|
||
{ key: 'facebook', label: 'Facebook' },
|
||
{ key: 'instagram', label: 'Instagram' },
|
||
{ key: 'x', label: 'X' },
|
||
{ key: 'telegram', label: 'Telegram' },
|
||
{ key: 'messenger', label: 'Messenger' },
|
||
{ key: 'other', label: '其他' }
|
||
];
|
||
|
||
/** 富化字段上限:与后端 CardRichFields::MAX_* 一致 */
|
||
export const RICH_FIELD_LIMITS = {
|
||
MAX_MOBILES: 5,
|
||
MAX_SOCIALS: 10,
|
||
MAX_CUSTOM: 10
|
||
};
|
||
|
||
/** 线索跟进状态:与后端 Lead::STATUSES 一致(0-3,其它值报「参数不合法」) */
|
||
export const LEAD_STATUS = [
|
||
{ value: 0, label: '新客户' },
|
||
{ value: 1, label: '已联系' },
|
||
{ value: 2, label: '已成交' },
|
||
{ value: 3, label: '无效' }
|
||
];
|
||
|
||
/** 人脉来源:与后端 Contact::SOURCES 一致(scan/exchange 暂无产生入口) */
|
||
export const CONTACT_SOURCES = [
|
||
{ value: 'manual', label: '手工录入' },
|
||
{ value: 'lead_in', label: '潜在客户转入' },
|
||
{ value: 'scan', label: '扫描' },
|
||
{ value: 'exchange', label: '交换' }
|
||
];
|
||
|
||
/** 名片来源:与后端 relation.source_type 一致 */
|
||
export const CARD_SOURCE_TYPES = [
|
||
{ value: 'manual', label: '手动创建' },
|
||
{ value: 'manual_batch', label: '批量制作' },
|
||
{ value: 'scan', label: '扫描识别' },
|
||
{ value: 'import_csv', label: 'CSV 导入' },
|
||
{ value: 'import_xlsx', label: 'Excel 导入' },
|
||
{ value: 'clone', label: '克隆' },
|
||
{ value: 'api', label: '接口创建' }
|
||
];
|
||
|
||
/**
|
||
* 名片编辑标量字段:与后端 BusinessCardFieldNormalizer 输出对齐。
|
||
* ⚠️ 2026-10-13:telegram / messenger 已软下线为独立标量(传了也被忽略),
|
||
* 统一走 fields.socials 数组按类型添加;读取时旧值自动合并进 socials_list。
|
||
*/
|
||
export const CARD_FIELDS = [
|
||
'name', 'company', 'job_title', 'avatar',
|
||
'mobile', 'phone', 'email', 'website', 'wechat',
|
||
'country', 'province', 'city', 'address', 'postal_code',
|
||
'bio'
|
||
];
|
||
|
||
/** 人脉可更新白名单:与后端 Contact::EDITABLE_FIELDS 一致 */
|
||
export const CONTACT_FIELDS = [
|
||
'name', 'mobile', 'company', 'job_title', 'wechat', 'email', 'website', 'address', 'remark', 'status'
|
||
];
|
||
|
||
const CardApi = {
|
||
url: CARD_API_URL,
|
||
|
||
/* ================= 名片夹 ================= */
|
||
|
||
/**
|
||
* 我的名片列表
|
||
* @param {Object} p { page, page_size(<=100), keyword, success, fail }
|
||
* 返回 data: { list, count, page_count }
|
||
*/
|
||
cardLists(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CARD_LISTS }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 卡片详情
|
||
* @param {Object} p { card_id, success, fail }
|
||
* 返回 data: { relation, source, share_list }
|
||
*/
|
||
cardDetail(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CARD_DETAIL }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 更新卡片(normalize 语义:键存在才写;空串=清空;键缺席=不擦除)
|
||
* 局部更新时只传用户实际编辑过的键。走 JSON body(fields 为嵌套对象)。
|
||
* @param {Object} p { card_id, fields, success, fail }
|
||
*/
|
||
cardUpdate(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.CARD_UPDATE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 新建数字名片(商家端批量制作/单张新建入口)
|
||
* 入参:`fields`(与 card/update 同结构,`name` 必填)+ 可选 `owner_id`(指定归属员工,0=未归属待认领)
|
||
* 返回 `data`:`{ card_id, ...relation }`(前端用 data.card_id 跳详情)
|
||
* ⚠️ 该端点后端源码已实现但旧文档未列出,按「文档与源码出入以源码为准」收口。
|
||
*/
|
||
cardCreate(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.CARD_CREATE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 认领名片(商家后台建的卡未归属 owner_id=0,员工认领到自己名下)
|
||
* 入参(二选一, claim_code 优先): { data: { claim_code } } 或 { data: { card_id } }
|
||
* 成功 data.claimed: true=新认领, false=早已是自己的(幂等)
|
||
* 文档: card_claim_api.md(POST /card/api/card/claim,token 自动带)
|
||
* @param {Object} p { data: { claim_code | card_id }, success, fail }
|
||
*/
|
||
cardClaim(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CARD_CLAIM }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 切换收藏(双向 toggle,幂等可逆:未收藏→置1,已收藏→置0)
|
||
* 直接用返回值 data.is_favorite 回填 UI,前端不自行判断状态。
|
||
* @param {Object} p { data: { card_id }, success, fail }
|
||
* 返回 data: { card_id, is_favorite: 0|1 }
|
||
* 拒绝(message 直接 toast): 卡不存在 / 非自己名下卡(统一「名片不存在」) / 参数不合法 / 未登录
|
||
*/
|
||
cardToggleFavorite(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.CARD_FAVORITE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 删除自己的名片(软删, 2.11.91)
|
||
* 文档: CARD_DELETE_API.md(POST /card/api/card/delete,token 自动带,不传 site_id)
|
||
* 通道: 与 cardClaim/cardFollowDone 同款 sendRequest(表单编码);只需 card_id 一个字段
|
||
* 交互: 调用前页面必须已做二次确认;成功后 toast「已删除」并刷新列表(接口不再返回该卡)
|
||
* 口径: 「卡片不存在」是故意模糊(他人/已删/不存在同一文案),前端统一按 message toast,不特殊处理
|
||
* @param {Object} p { data: { card_id }, success, fail }
|
||
*/
|
||
cardDelete(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CARD_DELETE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 识别建卡
|
||
* @param {Object} p { media_path, media_type?, source_type?, success, fail }
|
||
* 返回 data: { source_id, relation_id, card_id, deduped, fields }
|
||
* deduped=true 表示站点内已有同手机号名片,未新建,前端提示"已存在同名片"
|
||
*/
|
||
cardRecognize(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CARD_RECOGNIZE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 识别失败重试(仅本站 + status=failed 的来源可重试,有次数上限)
|
||
* @param {Object} p { source_id, success, fail }
|
||
*/
|
||
cardRetry(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CARD_RETRY }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 批量识别(1~10 条,逐项独立成败)
|
||
* @param {Object} p { items: [{ source_type?, media_type?, media_path, media_key? }], success, fail }
|
||
* 返回 data: { items: [{ ok, message, data }], ok, failed }
|
||
*/
|
||
cardRecognizeBatch(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.CARD_RECOGNIZE_BATCH }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 标记跟进完成(2.11.3 跟进闭环)
|
||
* 行级归属: 只能标记自己名下的名片(与收藏/认领同一套口径)
|
||
* 成功 data: { card_id, follow_up_done: 1 }
|
||
* 失败 message 直接 toast: 卡片不存在 / 只能标记自己名下的名片
|
||
* @param {Object} p { data: { card_id }, success, fail }
|
||
*/
|
||
cardFollowDone(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CARD_FOLLOW_DONE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 访客轨迹(2.11.4 谁看了我)
|
||
* 入参(全部可选): { data: { card_id?, page?, page_size?(<=50, 建议20) } }
|
||
* 不传 card_id = 当前员工名下全部名片; 成功 data: { list, total, page, page_size,
|
||
* summary: { today_pv, today_uv, total_pv, total_uv } }
|
||
* @param {Object} p { data: {...}, success, fail }
|
||
*/
|
||
cardViews(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CARD_VIEWS }, p || {}));
|
||
},
|
||
|
||
/* ================= 人脉库 ================= */
|
||
|
||
/**
|
||
* 人脉列表
|
||
* @param {Object} p { page, page_size(<=100), keyword, source?, status?, success, fail }
|
||
* 返回 data: { list, count, page_count }
|
||
*/
|
||
contactLists(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CONTACT_LISTS }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 新增人脉
|
||
* @param {Object} p { fields: { name(必填), company?, ... }, success, fail }
|
||
*/
|
||
contactAdd(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.CONTACT_ADD }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 人脉详情(编辑页回填;id 为数字自增主键,注意与名片 card_id 字符串不同)
|
||
* @param {Object} p { id, success, fail }
|
||
*/
|
||
contactInfo(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CONTACT_INFO }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 更新人脉(兼容 fields 对象或顶层平铺字段)
|
||
* @param {Object} p { id, fields?, ...平铺字段, success, fail }
|
||
*/
|
||
contactUpdate(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.CONTACT_UPDATE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 删除人脉(真删不可恢复,前端需二次确认弹窗)
|
||
* @param {Object} p { id, success, fail }
|
||
*/
|
||
contactDelete(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CONTACT_DELETE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 人脉 → 潜在客户(不删人脉行;同员工同手机号已转过则报错)
|
||
* @param {Object} p { id, success, fail }
|
||
*/
|
||
contactToLead(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CONTACT_TO_LEAD }, p || {}));
|
||
},
|
||
|
||
/* ================= 潜在客户 ================= */
|
||
|
||
/**
|
||
* 线索列表(自己名下名片收到的线索 + 自己从人脉转入的线索)
|
||
* @param {Object} p { page, page_size(<=100), status?(0-3), keyword, success, fail }
|
||
* 返回 data: { list, count, page_count }
|
||
*/
|
||
leadLists(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.LEAD_LISTS }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 更新跟进状态(status 只能是 0/1/2/3)
|
||
* @param {Object} p { id, status, success, fail }
|
||
* 返回 data: { id, status }
|
||
*/
|
||
leadSetStatus(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.LEAD_SET_STATUS }, p || {}));
|
||
},
|
||
|
||
/* ================= 交换名片 ================= */
|
||
|
||
/**
|
||
* 待处理交换请求列表(返回 status=0;已同意/已忽略员工端不可见)
|
||
* @param {Object} p { page, page_size(<=100), success, fail }
|
||
* 返回 data: { list:[{ id, from_card_id, name, avatar, job_title, company,
|
||
* create_time, create_time_txt, status }], count, page_count }
|
||
* ⚠️ 详情页红点计数技巧: lists?page=1&page_size=1 取 count 即可
|
||
* ⚠️ 后端契约要求 lists 走 GET(分页在 query); 而 Http.sendRequest 是
|
||
* 「带 data 就发 POST」的语义, 所以这里把分页拼进 url、不传 data,
|
||
* 否则会被发成 POST 导致接口不匹配、列表恒为空。
|
||
*/
|
||
exchangeLists(p) {
|
||
const opt = Object.assign({}, p || {});
|
||
const src = opt.data || opt;
|
||
const page = src.page || 1;
|
||
const pageSize = src.page_size || 20;
|
||
delete opt.data;
|
||
delete opt.page;
|
||
delete opt.page_size;
|
||
opt.url = CARD_API_URL.EXCHANGE_LISTS + '?page=' + page + '&page_size=' + pageSize;
|
||
Http.sendRequest(opt);
|
||
},
|
||
|
||
/**
|
||
* 同意交换(后端自动双方建人脉,前端无需再调 contact/add)
|
||
* 幂等: 同侧已存在同手机号人脉时 receiver_created=0,仍是成功,不当失败
|
||
* @param {Object} p { data: { id }, success, fail }
|
||
* 返回 data: { id, status:1, receiver_created, initiator_created }
|
||
*/
|
||
exchangeAgree(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.EXCHANGE_AGREE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 忽略交换(仅本方隐藏 status=2,对方不发通知、可再次发起)
|
||
* @param {Object} p { data: { id }, success, fail }
|
||
* 返回 data: { id, status:2 }
|
||
*/
|
||
exchangeIgnore(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.EXCHANGE_IGNORE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 同意交换后「校验 + 兜底」建人脉(页面点「同意」后必调;幂等,可安全重复调用)。
|
||
* 背景: 后端声明 CardExchange::agree 会双向互存人脉(我方人脉收对方名片 / 对方人脉收我方名片),
|
||
* 但实测出现「同意成功、对方名片却没进我的人脉」, 故前端不再只信 receiver_created,
|
||
* 统一在同意后按「对方名片编号」校验一次: 我的人脉里没有同手机号记录才补建。
|
||
* 流程: 取对方名片详情 → 按手机号查我的人脉 → 不存在则以 source=exchange 新建人脉。
|
||
* 幂等: 同手机号人脉已存在(含后端已建)时 created=0, 与后台已建不会重复。
|
||
* @param {Object} p { fromCardId, success, fail }
|
||
* 回调 done(data): { code, created, existed } created=1 新建 / 0 已存在或无需建;
|
||
* existed = 命中的同手机号人脉行(未命中为 null), 供页面提示「人脉中已有该联系人」。
|
||
*/
|
||
ensureExchangeContact(p) {
|
||
const self = this;
|
||
const done = p.success, fail = p.fail;
|
||
const finish = (d) => { typeof done === 'function' && done(d); };
|
||
const fromCardId = p.fromCardId;
|
||
if (!fromCardId) { finish({ code: 0, created: 0 }); return; }
|
||
this.cardDetail({
|
||
data: { card_id: fromCardId },
|
||
success: res => {
|
||
if (res.code != 0) { typeof fail === 'function' && fail(res); return; }
|
||
const r = (res.data && (res.data.relation || res.data)) || {};
|
||
const mobile = r.mobile || '';
|
||
const fields = {
|
||
name: r.name || '',
|
||
company: r.company || '',
|
||
job_title: r.job_title || '',
|
||
mobile: mobile,
|
||
source: 'exchange'
|
||
};
|
||
// 富化字段仅在对方有名片值时带上(空串=清空语义, 新建人脉时不传空键)
|
||
if (r.wechat) fields.wechat = r.wechat;
|
||
if (r.email) fields.email = r.email;
|
||
if (r.website) fields.website = r.website;
|
||
if (r.address) fields.address = r.address;
|
||
const add = () => self.contactAdd({
|
||
data: { source: 'exchange', fields: fields },
|
||
success: res2 => {
|
||
// code!=0(如手机号已存在被拒): 交换已同意成功, 兜底不打扰用户, 按无需新建收口
|
||
const ok = res2 && res2.code == 0;
|
||
finish({ code: ok ? 0 : ((res2 && res2.code) || 0), created: ok ? 1 : 0, raw: res2 });
|
||
},
|
||
fail: err => { typeof fail === 'function' && fail(err); }
|
||
});
|
||
// 按手机号去重: 已存在同手机号人脉则不重复建(兼容后台已建 / 重复同意)
|
||
if (mobile) {
|
||
self.contactLists({
|
||
data: { page: 1, page_size: 20, keyword: mobile },
|
||
success: res3 => {
|
||
const d3 = res3.data || {};
|
||
const list = Array.isArray(d3) ? d3 : (d3.list || []);
|
||
const hit = list.find(c => (c.mobile || '') === mobile);
|
||
// 命中: 对方已在我的人脉里(可能显示为旧姓名), 不覆盖用户维护过的数据, 只回传命中行
|
||
if (hit) { finish({ code: 0, created: 0, existed: hit }); return; }
|
||
add();
|
||
},
|
||
fail: () => add()
|
||
});
|
||
} else {
|
||
add();
|
||
}
|
||
},
|
||
fail: err => { typeof fail === 'function' && fail(err); }
|
||
});
|
||
},
|
||
|
||
/* ================= 员工建卡申请 2.11.15 ================= */
|
||
|
||
/**
|
||
* 提交建卡申请(空态申请 → 商家审批 → 自动建卡归属)
|
||
* 约束: 同一会员同时只允许一条待审申请(重复提交后端会拒)
|
||
* 入参: { data: { name, mobile } }
|
||
* @param {Object} p { data: { name, mobile }, success, fail }
|
||
*/
|
||
applySubmit(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.APPLY_SUBMIT }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 查询我的建卡申请状态(进入名片夹/空态时调用或轮询)
|
||
* 成功 data: { status: 0 待审/1 已同意/2 已拒绝, status_text, note, has_card }
|
||
* @param {Object} p { success, fail }
|
||
*/
|
||
applyStatus(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.APPLY_STATUS }, p || {}));
|
||
},
|
||
|
||
/* ================= 名片设计(员工端可选样式) ================= */
|
||
|
||
/**
|
||
* 拉取名片预设列表(与商家后台 CardTemplatePresets 同一份, 同源)
|
||
* 成功 data: { list:[{ key, name, desc, thumb,
|
||
* design:{ theme_color, text_color, bg_color, layout, cover, font_family,
|
||
* font_scale, card_radius, header_gradient_end, header_gradient_angle, quick_style } }] }
|
||
* @param {Object} p { success, fail }
|
||
*/
|
||
cardDesignPresets(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.CARD_DESIGN_PRESETS }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 应用预设 / 保存本卡样式(员工端选择后台预设后落库)
|
||
* 入参(包在 data 里): { card_id, preset_key } —— 后端按 preset_key 取 CardTemplatePresets
|
||
* 的 design 合并进本卡 lucky_card_design(非空覆盖), 返回 { code:0 }
|
||
* @param {Object} p { data: { card_id, preset_key }, success, fail }
|
||
*/
|
||
cardDesignSave(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.CARD_DESIGN_SAVE }, p || {}));
|
||
},
|
||
|
||
/* ================= 企业名片 2.13.6 ================= */
|
||
|
||
/**
|
||
* 企业名片详情(核心)
|
||
* GET ?card_id= 不传返回当前生效企业名片;传候选卡 id 可预览(is_active=0)
|
||
* 成功 data: { card_id, is_active, is_owner, fields:{name,company,job_title,mobile,email,
|
||
* website,address,wechat,avatar,bio,name_en,company_en,job_title_en,bio_en},
|
||
* design:{theme_color,bg_color,text_color,template_id,canvas,header_mode,official_card_id},
|
||
* header:{mode,official_card_id,official,self,staff} }
|
||
* 渲染: 名片区按 header.mode 取 official/self/staff 数据, 内容区用 fields+design.canvas
|
||
* 失败「企业名片不存在」→ 前端空态引导去管理端创建
|
||
* @param {Object} p { data: { card_id? }, success, fail }
|
||
*/
|
||
enterpriseDetail(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.ENTERPRISE_DETAIL }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 保存企业名片(后台/会员;owner_id=0 的卡会员不可改)
|
||
* @param {Object} p { data: { card_id?, fields?, design?, header_mode?, official_card_id? }, success, fail }
|
||
*/
|
||
enterpriseSave(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.ENTERPRISE_SAVE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 我的企业名片列表(自己名下)
|
||
* 成功 data: { list: [{ card_id, name, is_active }] }
|
||
* @param {Object} p { success, fail }
|
||
*/
|
||
enterpriseMine(p) {
|
||
Http.sendRequest(Object.assign({ url: CARD_API_URL.ENTERPRISE_MINE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 切换生效企业名片(互斥:全店仅 1 张 is_active=1,仅能切自己名下的卡)
|
||
* @param {Object} p { data: { card_id }, success, fail }
|
||
*/
|
||
enterpriseActivate(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.ENTERPRISE_ACTIVATE }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 员工自选展示名片(self 模式):card_id 必须是自己名下的 contact 卡
|
||
* 选择按会员存储;detail 的 header.self.chosen=1 表示已自选
|
||
* @param {Object} p { data: { card_id }, success, fail }
|
||
*/
|
||
enterpriseChooseSelf(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.ENTERPRISE_CHOOSE_SELF }, p || {}));
|
||
},
|
||
|
||
/**
|
||
* 删除企业名片(会员端;生效卡不可删,删除候选卡会清理官方名片引用)
|
||
* @param {Object} p { data: { card_id }, success, fail }
|
||
*/
|
||
enterpriseDelete(p) {
|
||
Http.sendJson(Object.assign({ url: CARD_API_URL.ENTERPRISE_DELETE }, p || {}));
|
||
}
|
||
};
|
||
|
||
export default CardApi;
|