feat(card): add leads and share pages with platform-specific handling
- Implemented leads.vue for managing lead statuses and displaying lead information. - Added share.vue for sharing card links with platform-specific web-view handling for WeChat Mini Programs, HarmonyOS, and H5. - Introduced tests for cross-platform compatibility, ensuring proper handling of conditional compilation and API usage across WeChat, H5, and HarmonyOS. - Ensured that sharing functionality correctly encodes and decodes URLs for different platforms.
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
<!-- 名片头部 -->
|
||||
<view class="detail-head" v-if="relation">
|
||||
<view class="head-avatar">
|
||||
<text>{{ avatarText(relation.name) }}</text>
|
||||
<image v-if="relation.avatar" :src="$util.img(relation.avatar)" class="head-img" mode="aspectFill" />
|
||||
<text v-else>{{ avatarText(relation.name) }}</text>
|
||||
</view>
|
||||
<view class="head-info">
|
||||
<view class="head-name">{{ relation.name || '未命名名片' }}</view>
|
||||
@@ -11,13 +12,17 @@
|
||||
{{ [relation.job_title, relation.company].filter(Boolean).join(' · ') }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="head-actions">
|
||||
<view class="head-btn" @tap="openShare">分享</view>
|
||||
<view class="head-btn" @tap="goEdit">编辑</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 字段列表 -->
|
||||
<view class="detail-fields" v-if="fieldRows.length > 0">
|
||||
<view v-for="(row, index) in fieldRows" :key="index" class="field-row">
|
||||
<view v-for="(row, index) in fieldRows" :key="index" class="field-row" :class="{ 'field-row-multi': row.multiline }">
|
||||
<view class="field-label">{{ row.label }}</view>
|
||||
<view class="field-value">{{ row.value }}</view>
|
||||
<view class="field-value" :class="{ 'field-value-multi': row.multiline }">{{ row.value }}</view>
|
||||
<view class="field-action" v-if="row.action"
|
||||
@tap="row.action.type === 'call' ? callPhone(row.value) : copyText2(row.value)">
|
||||
{{ row.action.text }}
|
||||
@@ -25,6 +30,38 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分享面板(微信式底部抽屉) -->
|
||||
<view class="share-mask" v-if="showShare" @tap="closeShare"></view>
|
||||
<view class="share-sheet" :class="{ 'share-sheet-show': showShare }">
|
||||
<view class="ss-bar"></view>
|
||||
<view class="ss-title">分享名片</view>
|
||||
<view class="ss-sub">客户打开链接即可查看名片并留言留资</view>
|
||||
<view class="ss-qr-box">
|
||||
<image v-if="qrUrl" :src="qrUrl" class="ss-qr" mode="widthFix" show-menu-by-longpress="true" />
|
||||
</view>
|
||||
<view class="ss-link">{{ shareUrl }}</view>
|
||||
<view class="ss-btns">
|
||||
<view class="ss-btn ss-btn-main" @tap="copyLink">复制链接</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="ss-btn" @tap="saveQr">保存二维码</view>
|
||||
<view class="ss-btn" @tap="shareToFriend">发给好友</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="ss-btn" @tap="h5Share">浏览器分享</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-HARMONY -->
|
||||
<!-- 鸿蒙元服务:uni.saveImageToPhotosAlbum/downloadFile 均支持(先下载后保存的模式不变);
|
||||
无小程序右上角转发菜单,系统级分享用「复制链接」代替;原生 image 无长按菜单 -->
|
||||
<view class="ss-btn" @tap="saveQr">保存二维码</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="ss-tip">
|
||||
<!-- #ifdef MP-WEIXIN -->长按二维码也可保存到相册<!-- #endif -->
|
||||
<!-- #ifdef H5 -->长按二维码可保存到相册<!-- #endif -->
|
||||
<!-- #ifdef MP-HARMONY -->点击「保存二维码」存入相册<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<view class="detail-loading" v-if="isLoading">加载中…</view>
|
||||
|
||||
@@ -43,7 +80,10 @@ export default {
|
||||
source: null,
|
||||
shareList: [],
|
||||
isLoading: true,
|
||||
errorMsg: ''
|
||||
errorMsg: '',
|
||||
showShare: false,
|
||||
shareUrl: '',
|
||||
qrUrl: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -68,6 +108,27 @@ export default {
|
||||
push('Telegram', r.telegram);
|
||||
push('Messenger', r.messenger);
|
||||
|
||||
// 附加手机号(mobiles_list, 后端 CardRichFields 解码) → 可拨打
|
||||
(r.mobiles_list || []).forEach((m, i) => {
|
||||
push('手机' + (i + 2), m, { type: 'call', text: '拨打' });
|
||||
});
|
||||
|
||||
// 社交账号(socials_list: {type,label,value}) → label 作行名, 值可复制
|
||||
// (完整主页链接与纯账号统一复制 value, 链接可粘贴到浏览器打开)
|
||||
(r.socials_list || []).forEach(s => {
|
||||
push(s.label || s.type, s.value, { type: 'copy', text: '复制' });
|
||||
});
|
||||
|
||||
// 自定义字段(custom_list: {label,value})
|
||||
(r.custom_list || []).forEach(c => {
|
||||
push(c.label, c.value);
|
||||
});
|
||||
|
||||
// 简介(多行, 放最后)
|
||||
if (r.bio && String(r.bio).trim() !== '') {
|
||||
rows.push({ label: '简介', value: String(r.bio), action: null, multiline: true });
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
},
|
||||
@@ -75,7 +136,30 @@ export default {
|
||||
this.cardId = decodeURIComponent(options.card_id || '');
|
||||
this.loadDetail();
|
||||
},
|
||||
onShow() {
|
||||
// 首次进入由 onLoad 负责加载; 从编辑页返回时(onShow 再次触发)刷新
|
||||
if (this._loadedOnce) {
|
||||
this.loadDetail();
|
||||
}
|
||||
this._loadedOnce = true;
|
||||
},
|
||||
// 小程序端转发: 分享 web-view 中转页(其内部加载 wap H5 名片页),
|
||||
// 因为 onShareAppMessage 的 path 只能指向小程序页面, 不能直接分享 H5 链接
|
||||
onShareAppMessage() {
|
||||
const name = (this.relation && this.relation.name) || '我的名片';
|
||||
return {
|
||||
title: name + '的名片',
|
||||
path: '/pages_tool/card/share?url=' + encodeURIComponent(this.shareUrl || ''),
|
||||
imageUrl: (this.relation && this.relation.avatar) ? this.$util.img(this.relation.avatar) : ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
goEdit() {
|
||||
if (!this.cardId) return;
|
||||
uni.navigateTo({
|
||||
url: '/pages_tool/card/edit?card_id=' + encodeURIComponent(this.cardId)
|
||||
});
|
||||
},
|
||||
avatarText(name) {
|
||||
return (name || '?').trim().charAt(0).toUpperCase();
|
||||
},
|
||||
@@ -120,6 +204,68 @@ export default {
|
||||
uni.showToast({ title: '已复制', icon: 'success' });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/* ---------- 分享 ---------- */
|
||||
siteId() {
|
||||
return uni.getStorageSync('uniacid') || this.$config.uniacid || 0;
|
||||
},
|
||||
openShare() {
|
||||
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;
|
||||
this.showShare = true;
|
||||
},
|
||||
closeShare() {
|
||||
this.showShare = false;
|
||||
},
|
||||
copyLink() {
|
||||
if (!this.shareUrl) return;
|
||||
uni.setClipboardData({
|
||||
data: this.shareUrl,
|
||||
success: () => {
|
||||
uni.showToast({ title: '链接已复制', icon: 'success' });
|
||||
}
|
||||
});
|
||||
},
|
||||
saveQr() {
|
||||
if (!this.qrUrl) return;
|
||||
uni.downloadFile({
|
||||
url: this.qrUrl,
|
||||
success: res => {
|
||||
if (res.statusCode !== 200 || !res.tempFilePath) {
|
||||
uni.showToast({ title: '二维码下载失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => uni.showToast({ title: '已保存到相册', icon: 'success' }),
|
||||
// 文案不提「长按保存」: 鸿蒙元服务的原生 image 无长按菜单, 只微信/H5 有
|
||||
fail: () => uni.showToast({ title: '保存失败,可复制链接分享', icon: 'none' })
|
||||
});
|
||||
},
|
||||
fail: () => uni.showToast({ title: '二维码下载失败', icon: 'none' })
|
||||
});
|
||||
},
|
||||
shareToFriend() {
|
||||
// 小程序右上角菜单转发(onshareappmessage 已配置)或提示用户点击右上角
|
||||
uni.showToast({ title: '请点击右上角「···」转发给好友', icon: 'none' });
|
||||
},
|
||||
h5Share() {
|
||||
// H5: 优先 navigator.share(移动浏览器), 不支持则复制链接兜底
|
||||
if (navigator && typeof navigator.share === 'function') {
|
||||
navigator.share({
|
||||
title: ((this.relation && this.relation.name) || '我的名片') + '的名片',
|
||||
url: this.shareUrl
|
||||
}).catch(() => {});
|
||||
return;
|
||||
}
|
||||
this.copyLink();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -152,9 +298,16 @@ export default {
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-right: 28rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.head-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.head-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.head-name {
|
||||
@@ -168,6 +321,139 @@ export default {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.head-actions {
|
||||
flex-shrink: 0;
|
||||
margin-left: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.head-btn {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.8);
|
||||
border-radius: 28rpx;
|
||||
padding: 8rpx 24rpx;
|
||||
|
||||
& + .head-btn {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 分享面板(微信式底部抽屉, 与 wap 留资抽屉同风格) */
|
||||
.share-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 100;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
.share-mask {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.share-sheet {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ffffff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
padding: 20rpx 32rpx 40rpx;
|
||||
z-index: 101;
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.25s;
|
||||
|
||||
&.share-sheet-show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.ss-bar {
|
||||
width: 36rpx;
|
||||
height: 4rpx;
|
||||
border-radius: 2rpx;
|
||||
background-color: #e3e7ee;
|
||||
margin: 0 auto 20rpx;
|
||||
}
|
||||
|
||||
.ss-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1f36;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ss-sub {
|
||||
font-size: 24rpx;
|
||||
color: #9aa4b8;
|
||||
text-align: center;
|
||||
margin: 8rpx 0 24rpx;
|
||||
}
|
||||
|
||||
.ss-qr-box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.ss-qr {
|
||||
width: 320rpx;
|
||||
height: 320rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.ss-link {
|
||||
font-size: 22rpx;
|
||||
color: #9aa4b8;
|
||||
background-color: #f6f7f9;
|
||||
border-radius: 8rpx;
|
||||
padding: 14rpx 20rpx;
|
||||
margin-bottom: 24rpx;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ss-btns {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.ss-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
border-radius: 12rpx;
|
||||
|
||||
& + .ss-btn {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.ss-btn-main {
|
||||
background-color: var(--main-color, #0054a5);
|
||||
border-color: var(--main-color, #0054a5);
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.ss-tip {
|
||||
text-align: center;
|
||||
font-size: 22rpx;
|
||||
color: #c0c4cc;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-fields {
|
||||
@@ -182,6 +468,10 @@ export default {
|
||||
padding: 28rpx 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&.field-row-multi {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
@@ -200,6 +490,11 @@ export default {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.field-value-multi {
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.field-action {
|
||||
margin-left: 20rpx;
|
||||
flex-shrink: 0;
|
||||
|
||||
Reference in New Issue
Block a user