chore:和后端发的文档同步

This commit is contained in:
2026-10-14 16:29:56 +08:00
parent 67af165c14
commit 022a8bb5bb

View File

@@ -77,7 +77,7 @@
</view>
<!-- 交换名片提醒条(参考图: 宫格下方出现对方要交换的名片) -->
<!-- 数据优先级: ① exchange/lists 的真实待处理请求 ② 最近一次真实交换来的人脉(接口无请求时兜底) -->
<!-- 数据来源: exchange/lists 的待处理请求(只含 status=0, 已同意/已忽略不可见), 不做其它兜底 -->
<!-- 头部文案与忽略/同意按钮按参考图固定渲染, 不改动名片数据 -->
<view class="exchange-bar" v-if="relation && exchangeItem" @tap="onExchangeBarTap">
<view class="eb-head">
@@ -92,7 +92,7 @@
</view>
<view class="eb-card" @tap.stop="viewExchangeCard(exchangeItem)">
<image v-if="exchangeItem.avatar" class="eb-avatar" :src="exchangeItem.avatar" mode="aspectFill" />
<view v-else class="eb-avatar eb-avatar-ph">{{ exchangeItem.name.charAt(0) }}</view>
<view v-else class="eb-avatar eb-avatar-ph">{{ (exchangeItem.name || '?').charAt(0) }}</view>
<view class="eb-info">
<view class="eb-name-row">
<text class="eb-name">{{ exchangeItem.name }}</text>
@@ -183,10 +183,9 @@ export default {
showShare: false,
shareUrl: '',
qrUrl: '',
// 交换名片: ① 待处理请求(真实, exchange/lists) ② 最近一次交换来的人脉(真实, contact/lists?source=exchange)
// 交换名片: 待处理请求(真实, exchange/lists, 只含 status=0)
exchangeList: [],
exchangeCount: 0,
latestExchange: null
exchangeCount: 0
};
},
computed: {
@@ -239,10 +238,9 @@ export default {
exchangeIsRequest() {
return this.exchangeList.length > 0;
},
// 提醒条展示对象: 优先真实待处理请求(最新的那条), 否则最近一次真实交换来的人脉
// 提醒条展示对象: 最新的那条待处理请求(交换成功后该条从列表消失, 提醒条自动收起)
exchangeItem() {
if (this.exchangeList.length > 0) return this.exchangeList[0];
return this.latestExchange;
return this.exchangeList[0] || null;
}
},
onLoad(options) {
@@ -262,7 +260,8 @@ export default {
// 因为 onShareAppMessage 的 path 只能指向小程序页面, 不能直接分享 H5 链接
onShareAppMessage() {
// 右上角「···」转发与原生转发按钮都会直接走这里, 不经过 openShare;
// 兜底生成一次链接, 否则 path 里的 url 为空 → 中转页只会显示「链接无效」
// shareUrl 已由 loadDetail 从后端 data.wap_url 取得(此处仅做兜底校验),
// 空值会让中转页显示「链接无效」
this.buildShareInfo();
const name = (this.relation && this.relation.name) || '我的名片';
return {
@@ -310,24 +309,30 @@ export default {
this.$cardApi.cardDetail({
data: { card_id: this.cardId },
success: res => {
this.isLoading = false;
const data = res.data || {};
if (res.code == 0) {
const data = res.data || {};
this.isLoading = false;
this.relation = data.relation || null;
this.source = data.source || null;
this.shareList = data.share_list || [];
// 详情一到手就算出分享链接/二维码: 用户可能直接点「发名片」或右上角转发
this.buildShareInfo();
// 公开页/二维码地址由后端统一拼装返回(含 site_id/域名), 前端不自行拼接
this.shareUrl = data.wap_url || '';
this.qrUrl = data.qr_url || '';
} else {
// 后端对"不存在"与"非本人名下"同一报错口径,不区分展示
this.errorMsg = res.message || '卡片不存在或已被删除';
this.relation = null;
// 未登录 / 非本人名下 / 卡片不存在: 后端报错信封同样返回 wap_url,
// 据此匿名落地到公开页(可看、可提交交换请求), 不出现死路
if (data.wap_url) {
this.fallbackToPublicPage(data.wap_url);
} else {
this.isLoading = false;
this.errorMsg = res.message || '卡片不存在或已被删除';
this.relation = null;
}
}
},
fail: () => {
this.isLoading = false;
this.errorMsg = '网络异常,请稍后重试';
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
}
});
},
@@ -344,30 +349,77 @@ export default {
});
},
/* ---------- 分享 ---------- */
siteId() {
return uni.getStorageSync('uniacid') || this.$config.uniacid || 0;
},
/* ---------- 分享 / 公开页 ---------- */
/**
* 生成分享链接与二维码(幂等, 可重复调用)
* ⚠️ 必须在 onShareAppMessage 触发前就绪: 微信右上角「···」转发与本页原生转发按钮
* 都直接读 shareUrl, 不经过 openShare(), 空值会让转出去的名片打不开。
* 分享链接与二维码由 loadDetail 时后端返回的 data.wap_url / qr_url 提供,
* 这里只做兜底校验, 不再本地拼接(域名/site_id/路径结构由后端统一控制, 换环境不失效)。
*/
buildShareInfo() {
if (!this.relation) return;
const sid = this.siteId();
const rid = this.relation.id;
const base = this.$config.baseUrl || '';
// wap 公开名片页(客户视角, 无需登录)
this.shareUrl = base + '/card/wap/index/detail?site_id=' + sid + '&id=' + rid;
// 二维码由后端同源生成(项目内置 extend\QRcode), 与链接一一对应
this.qrUrl = base + '/card/wap/index/qr?site_id=' + sid + '&id=' + rid;
if (!this.shareUrl || !this.qrUrl) {
uni.showToast({ title: '分享链接未返回,请稍后重试', icon: 'none' });
}
},
openShare() {
if (!this.relation) return;
this.buildShareInfo();
this.showShare = true;
},
/**
* 跳对方公开名片页(客户视角 H5, 含「企业介绍 / 收下名片」面板)
* 契约: 公开页地址由后端 card/detail 按对方 card_id 返回, 前端不自行拼接。
* 故先拉一次对方名片详情拿到 wap_url, 再按平台跳转;
* card/detail 对非本人/不存在也会返回 wap_url(防探测), 同样可直达。
*/
openPublicCard(cardId) {
if (!cardId) return;
this.$cardApi.cardDetail({
data: { card_id: cardId },
success: res => {
const data = res.data || {};
this.gotoPublic(data.wap_url || '');
},
fail: () => {
uni.showToast({ title: '打开名片失败,请稍后重试', icon: 'none' });
}
});
},
/**
* 按平台打开公开页: H5 直接 location; 小程序/鸿蒙/App 经 share.vue 的 web-view 中转。
* 拿不到地址(老后端/网络异常)时降级报错, 禁止本地拼 URL。
*/
gotoPublic(wapUrl) {
if (!wapUrl) {
uni.showToast({ title: '名片链接未返回', icon: 'none' });
return;
}
// #ifdef H5
window.location.href = wapUrl;
// #endif
// #ifndef H5
uni.navigateTo({
url: '/pages_tool/card/share?url=' + encodeURIComponent(wapUrl)
});
// #endif
},
/**
* 详情接口非 0(未登录/非本人名下/卡片不存在): 后端报错信封照样返回 wap_url,
* 据此匿名落地到公开页(可看、可提交交换请求), 不出现死路。
* 用 redirectTo 替换掉当前详情页, 避免回退又回到打不开的死页。
*/
fallbackToPublicPage(wapUrl) {
if (!wapUrl) {
this.errorMsg = this.errorMsg || '卡片不存在或已被删除';
return;
}
// #ifdef H5
window.location.href = wapUrl;
// #endif
// #ifndef H5
uni.redirectTo({
url: '/pages_tool/card/share?url=' + encodeURIComponent(wapUrl)
});
// #endif
},
// 「名片样式」: 进入样式选择页(名片模板 / 分享封面)
onStyleTap() {
const url = this.cardId
@@ -383,81 +435,52 @@ export default {
uni.navigateTo({ url: url });
},
// 拉取待处理交换请求(只含 status=0), 回填 exchangeList / exchangeCount
// 没有待处理请求时, 再取「最近一次交换来的人脉」作为提醒条的第二个真实数据源
loadExchange() {
// 已同意/已忽略的请求后端不再返回, 列表为空时提醒条自然不渲染(不兜底其它数据源)
loadExchange(retry) {
this.$cardApi.exchangeLists({
data: { page: 1, page_size: 20 },
success: res => {
const list = (res.code == 0 && res.data) ? (res.data.list || []) : [];
if (list.length > 0) {
// 展示最新那条: create_time / id 都可能缺失, 取到可比较的值再倒序
const ordered = list.slice().sort((a, b) => {
const av = Number(a.create_time) || Number(a.id) || 0;
const bv = Number(b.create_time) || Number(b.id) || 0;
return bv - av;
});
this.exchangeList = ordered;
this.exchangeCount = (res.data && res.data.count) || ordered.length;
this.latestExchange = null;
} else {
if (res.code != 0) {
// -1 = 尚未登录: 首次进入本页时 store 里的 token 可能还没恢复完成, 静默重试一次
if (res.code == -1 && !retry) {
setTimeout(() => this.loadExchange(true), 800);
return;
}
// 其它失败: 契约允许直接透出后端 message, 便于定位(-1 是上面的瞬时态, 不打扰用户)
if (res.code != -1 && res.message) {
uni.showToast({ title: res.message, icon: 'none' });
}
console.warn('[card/detail] exchange/lists 返回失败:', res);
this.exchangeList = [];
this.exchangeCount = 0;
this.loadLatestExchange();
return;
}
// 兼容 { list, count, page_count } 与裸数组两种返回体
const data = res.data || {};
const list = Array.isArray(data) ? data : (data.list || []);
// id 降序: 新的在前(后端已是 id desc, 这里兜底防不一致)
const ordered = list.slice().sort((a, b) => Number(b.id) - Number(a.id));
this.exchangeList = ordered;
this.exchangeCount = (Array.isArray(data) ? 0 : data.count) || ordered.length;
},
fail: () => {
this.exchangeList = [];
this.exchangeCount = 0;
this.loadLatestExchange();
}
});
},
// 第二个真实数据源: 人脉库里来源=交换的最新一条(交换成功后后端会自动双方建人脉)
loadLatestExchange() {
this.$cardApi.contactLists({
data: { page: 1, page_size: 1, source: 'exchange' },
success: res => {
if (res.code != 0 || !res.data) return;
const row = (res.data.list || [])[0];
this.latestExchange = row ? {
id: row.id,
name: row.name || '',
avatar: row.avatar || '',
job_title: row.job_title || '',
company: row.company || '',
create_time_txt: row.create_time_txt || ''
} : null;
// 无待处理请求时的兜底展示: 头部件数按 1 渲染, 与参考图「有 1 人请求与您交换名片」一致
this.exchangeCount = this.latestExchange ? 1 : 0;
},
fail: () => { this.latestExchange = null; this.exchangeCount = 0; }
});
},
// 提醒条整条点击: 待处理请求 → 交换请求列表页; 最近交换 → 我的人脉
// 提醒条整条点击 → 交换请求列表页(这里只展示真实待处理请求)
onExchangeBarTap() {
if (this.exchangeIsRequest) {
this.goExchangeReq();
} else {
this.goContacts();
}
this.goExchangeReq();
},
// 点对方名片 → 待处理请求跳对方名片详情; 兜底展示的人脉则回「我的人脉」
// 点对方名片 → 打开对方公开名片页(非本人卡)
viewExchangeCard(item) {
if (!this.exchangeIsRequest) {
this.goContacts();
return;
}
if (!item || !item.from_card_id) return;
uni.navigateTo({
url: '/pages_tool/card/detail?card_id=' + encodeURIComponent(item.from_card_id)
});
if (!item) return;
this.openPublicCard(item.from_card_id);
},
onIgnoreExchange(item) {
// 兜底展示项不是真实待处理请求, 不能拿人脉 id 去调交换接口
if (!this.exchangeIsRequest || !item || !item.id) {
uni.showToast({ title: '当前没有待处理的交换请求', icon: 'none' });
return;
}
// 提醒条只展示真实待处理请求, item.id 即请求 id(非名片 id)
if (!item || !item.id) return;
this.$cardApi.exchangeIgnore({
data: { id: item.id },
success: res => {
@@ -472,11 +495,8 @@ export default {
});
},
onAgreeExchange(item) {
// 兜底展示项不是真实待处理请求, 不能拿人脉 id 去调交换接口
if (!this.exchangeIsRequest || !item || !item.id) {
uni.showToast({ title: '当前没有待处理的交换请求', icon: 'none' });
return;
}
// 提醒条只展示真实待处理请求, item.id 即请求 id(非名片 id)
if (!item || !item.id) return;
this.$cardApi.exchangeAgree({
data: { id: item.id },
success: res => {
@@ -486,7 +506,10 @@ export default {
const txt = d.receiver_created === 0 ? '已交换(人脉已存在)' : '已交换,人脉已互加';
uni.showToast({ title: txt, icon: 'none' });
this.loadExchange();
// 参考图: 同意后展示对方的公开名片页(含「企业介绍 / 收下名片」面板)
this.openPublicCard(item.from_card_id);
} else if (res.message) {
// 请求已被处理/不存在/对方名片失效: 直接提示后端文案, 不二次弹「没有待处理请求」
uni.showToast({ title: res.message, icon: 'none' });
}
},