378 lines
11 KiB
Vue
378 lines
11 KiB
Vue
<template>
|
||
<view data-component-name="diy-card-self" class="diy-card-self">
|
||
<!-- 未登录 + 登录引导兜底 -->
|
||
<view v-if="!hasToken && fallbackMode === 'none'" class="self-login" :style="boxStyle" @tap="goLogin" hover-class="btn-press">
|
||
<text class="login-tip" :style="nameStyle">{{ value.loginTip || '登录后这里展示我的名片' }}</text>
|
||
<view class="login-btn" :style="loginBtnStyle">
|
||
<text class="login-btn-text">{{ value.loginBtnText || '去登录' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 名片卡(已登录=本人名片 / 未登录=兜底名片); loading 时基件出骨架屏;
|
||
「我的名片」角标已按 2026-10-23 参考图定稿移除 -->
|
||
<diy-card-mini
|
||
v-else-if="loading || card"
|
||
:card="card"
|
||
:config="miniConfig"
|
||
:loading="loading"
|
||
@act="doAct"
|
||
@row-act="doAct"
|
||
@detail="goDetail"
|
||
>
|
||
</diy-card-mini>
|
||
|
||
<!-- 未登录提示条已按 2026-10-23 用户要求移除(不再显示「登录后即可展示我的名片」) -->
|
||
|
||
<!-- 没有任何名片可显示(非登录引导模式; 登录引导已由第一个分支承接) -->
|
||
<view v-else-if="!loading && !card" class="self-empty" :style="boxStyle">
|
||
<text class="empty-icon">🪪</text>
|
||
<text class="empty-tip" :style="subTextStyle">{{ value.noCardTip || '登录后即可展示我的名片' }}</text>
|
||
<view class="empty-btn" :style="loginBtnStyle" hover-class="btn-press" @tap="goCardList">
|
||
<text class="empty-btn-text">{{ value.noCardBtnText || '去看看名片' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
// 数字名片 · 我的名片(装修组件)
|
||
// 运行时判定: 已登录 → 本人名片(optionalAuth); 未登录 → 配置的兜底名片
|
||
// fallbackMode: enterprise=店铺企业名片 / card=指定 fallbackCardId / none=登录引导
|
||
// 视觉基件 diy-card-mini; 本文件管登录态/兜底/空态 CTA
|
||
export default {
|
||
name: 'diy-card-self',
|
||
props: {
|
||
value: { type: Object }
|
||
},
|
||
data() {
|
||
return {
|
||
card: null,
|
||
isSelf: false,
|
||
loading: false,
|
||
};
|
||
},
|
||
computed: {
|
||
hasToken() {
|
||
try {
|
||
return !!(this.$store && this.$store.state && this.$store.state.token);
|
||
} catch (e) {
|
||
return false;
|
||
}
|
||
},
|
||
fallbackMode() {
|
||
const m = String((this.value || {}).fallbackMode || 'enterprise');
|
||
return ['enterprise', 'card', 'none'].includes(m) ? m : 'enterprise';
|
||
},
|
||
miniConfig() {
|
||
const v = this.value || {};
|
||
return {
|
||
bgColor: v.bgColor,
|
||
nameColor: v.nameColor,
|
||
infoColor: v.infoColor,
|
||
labelColor: v.labelColor,
|
||
borderRadius: v.borderRadius,
|
||
padding: v.padding,
|
||
// 字号定制(px 口径, 缺省时基件按历史默认值渲染)
|
||
nameSize: v.nameSize,
|
||
cardTitleSize: v.cardTitleSize,
|
||
subSize: v.subSize,
|
||
labelSize: v.labelSize,
|
||
valueSize: v.valueSize,
|
||
actSize: v.actSize,
|
||
btnSize: v.btnSize,
|
||
showQuick: v.showQuick,
|
||
showDetailBtn: v.showDetailBtn,
|
||
detailBtnText: v.detailBtnText,
|
||
// 2.0 新增视觉开关
|
||
useCover: v.useCover,
|
||
showGradient: v.showGradient,
|
||
showLogo: v.showLogo,
|
||
showShadow: v.showShadow,
|
||
showRows: v.showRows,
|
||
maxRows: v.maxRows,
|
||
showQr: v.showQr,
|
||
qrTip: v.qrTip,
|
||
// 3.0 A+B 视觉升级
|
||
showRowIcons: v.showRowIcons,
|
||
rowTap: v.rowTap,
|
||
qrPosition: v.qrPosition,
|
||
// 3.1 头像开关 + 二维码样式/微调
|
||
showAvatar: v.showAvatar,
|
||
qrStyle: v.qrStyle,
|
||
qrSize: v.qrSize,
|
||
qrOffsetX: v.qrOffsetX,
|
||
qrOffsetY: v.qrOffsetY,
|
||
qrTipShow: v.qrTipShow,
|
||
// 2.17 双形态: 渐变头部 / 宫格快捷操作 / 资料区标题 / 复制按钮
|
||
headStyle: v.headStyle,
|
||
headNameColor: v.headNameColor,
|
||
headSubColor: v.headSubColor,
|
||
actsLayout: v.actsLayout,
|
||
actGridColorMode: v.actGridColorMode,
|
||
showRowsTitle: v.showRowsTitle,
|
||
rowsTitle: v.rowsTitle,
|
||
showCopyBtn: v.showCopyBtn,
|
||
// 2.18: 操作区位置/独立块样式/每按钮图标/自定义二维码图
|
||
actsPosition: v.actsPosition,
|
||
actsBlockBg: v.actsBlockBg,
|
||
actsBlockRadius: v.actsBlockRadius,
|
||
actsBlockPad: v.actsBlockPad,
|
||
actIcons: v.actIcons,
|
||
qrImage: v.qrImage,
|
||
// 2.19: 默认动作显隐/文案 + 自定义动作(基件 acts computed 合成)
|
||
actVis: v.actVis,
|
||
actDeleted: v.actDeleted,
|
||
actLabels: v.actLabels,
|
||
actOrder: v.actOrder,
|
||
customActs: v.customActs,
|
||
};
|
||
},
|
||
boxStyle() {
|
||
const v = this.value || {};
|
||
return this.$util.objToStyle({
|
||
background: v.bgColor || '#FFFFFF',
|
||
borderRadius: this.px2rpx(v.borderRadius || 16) + 'rpx',
|
||
padding: this.px2rpx(v.padding || 16) + 'rpx',
|
||
boxShadow: '0 4rpx 16rpx rgba(15,23,42,0.06)',
|
||
});
|
||
},
|
||
nameStyle() {
|
||
return this.$util.objToStyle({ color: (this.value || {}).nameColor || '#1F2329' });
|
||
},
|
||
subTextStyle() {
|
||
return this.$util.objToStyle({ color: (this.value || {}).infoColor || '#8A8F99' });
|
||
},
|
||
loginBtnStyle() {
|
||
return this.$util.objToStyle({
|
||
background: (this.card || {}).theme_color || '#2F6BFF',
|
||
height: '80rpx',
|
||
borderRadius: '999rpx',
|
||
padding: '0 48rpx',
|
||
});
|
||
},
|
||
},
|
||
watch: {
|
||
hasToken() {
|
||
this.fetchData();
|
||
},
|
||
'value.fallbackMode'() {
|
||
this.fetchData();
|
||
},
|
||
'value.fallbackCardId'() {
|
||
this.fetchData();
|
||
},
|
||
},
|
||
mounted() {
|
||
this.fetchData();
|
||
},
|
||
methods: {
|
||
px2rpx(v) {
|
||
const n = Number(v);
|
||
return isNaN(n) ? 0 : Math.round(n * 2);
|
||
},
|
||
fetchData() {
|
||
if (!this.hasToken && this.fallbackMode === 'none') {
|
||
this.card = null;
|
||
return;
|
||
}
|
||
this.loading = true;
|
||
this.$api.sendRequest({
|
||
url: '/card/api/diy/mycard',
|
||
data: {
|
||
fallback_mode: this.fallbackMode,
|
||
fallback_card_id: String((this.value || {}).fallbackCardId || '').trim(),
|
||
},
|
||
success: res => {
|
||
this.loading = false;
|
||
if (res.code == 0 && res.data) {
|
||
this.card = res.data.card || null;
|
||
this.isSelf = res.data.is_self == 1;
|
||
} else {
|
||
this.card = null;
|
||
this.isSelf = false;
|
||
}
|
||
},
|
||
fail: () => {
|
||
this.loading = false;
|
||
this.card = null;
|
||
this.isSelf = false;
|
||
},
|
||
});
|
||
},
|
||
goLogin() {
|
||
this.$util.redirectTo('/pages_tool/login/login');
|
||
},
|
||
goCardList() {
|
||
this.$util.redirectTo('/pages_tool/card/list');
|
||
},
|
||
doAct(act) {
|
||
if (!act) return;
|
||
// save(存通讯录): value 为空, 字段由本组件从 card 组装(白名单口径)
|
||
if (act.type === 'save') {
|
||
this.goSaveContact();
|
||
return;
|
||
}
|
||
// map(导航): value 留空回落名片地址(2.19.3)
|
||
if (act.type === 'map' && !act.value && this.card && this.card.address) {
|
||
act = Object.assign({}, act, { value: this.card.address });
|
||
}
|
||
if (!act.value) return;
|
||
if (act.type === 'tel') {
|
||
uni.makePhoneCall({ phoneNumber: act.value, fail: () => {} });
|
||
} else if (act.type === 'wx') {
|
||
uni.setClipboardData({
|
||
data: act.value,
|
||
success: () => uni.showToast({ title: '微信号已复制', icon: 'none' }),
|
||
});
|
||
} else if (act.type === 'mail') {
|
||
// H5/公众号: 直接调起系统邮件客户端(满足「点击邮箱发邮件」)
|
||
// #ifdef H5
|
||
window.location.href = 'mailto:' + act.value;
|
||
// #endif
|
||
// 小程序/App(非 H5): 微信沙箱不支持 mailto 拉起, 复制地址并引导去邮件 App 粘贴
|
||
// #ifndef H5
|
||
uni.setClipboardData({
|
||
data: act.value,
|
||
success: () => uni.showToast({ title: '邮箱已复制,请前往邮件App粘贴发送', icon: 'none' }),
|
||
});
|
||
// #endif
|
||
} else if (act.type === 'web') {
|
||
// 网站行点击 → 跳转到该网址
|
||
// H5/公众号: 直接在本页打开(微信内置浏览器同)
|
||
// #ifdef H5
|
||
window.location.href = act.value;
|
||
// #endif
|
||
// 小程序/App(非 H5): 经 web-view 承载页在微信内打开(需微信后台配置业务域名)
|
||
// #ifndef H5
|
||
const title = (this.card && this.card.name) || '网站';
|
||
uni.navigateTo({
|
||
url: '/pages_tool/webview/webview?src=' + encodeURIComponent(act.value) +
|
||
'&title=' + encodeURIComponent(title) + '&enable_set_title=1',
|
||
fail: () => uni.showToast({ title: '打开网站失败', icon: 'none' }),
|
||
});
|
||
// #endif
|
||
} else if (act.type === 'sms') {
|
||
// #ifdef H5
|
||
window.location.href = 'sms:' + act.value;
|
||
// #endif
|
||
// #ifndef H5
|
||
uni.setClipboardData({
|
||
data: act.value,
|
||
success: () => uni.showToast({ title: '号码已复制,请到短信粘贴', icon: 'none' }),
|
||
});
|
||
// #endif
|
||
} else if (act.type === 'map') {
|
||
uni.setClipboardData({
|
||
data: act.value,
|
||
success: () => uni.showToast({ title: '地址已复制,可粘贴到地图导航', icon: 'none' }),
|
||
});
|
||
} else if (act.type === 'link') {
|
||
var isHttp = act.value.indexOf('http://') === 0 || act.value.indexOf('https://') === 0;
|
||
if (isHttp) {
|
||
// #ifdef H5
|
||
window.location.href = act.value;
|
||
// #endif
|
||
// #ifndef H5
|
||
uni.setClipboardData({
|
||
data: act.value,
|
||
success: () => uni.showToast({ title: '链接已复制,请在浏览器打开', icon: 'none' }),
|
||
});
|
||
// #endif
|
||
} else {
|
||
this.$util.redirectTo(act.value, {});
|
||
}
|
||
} else {
|
||
uni.setClipboardData({
|
||
data: act.value,
|
||
success: () => uni.showToast({ title: '已复制', icon: 'none' }),
|
||
});
|
||
}
|
||
},
|
||
goDetail(card) {
|
||
if (!card || !card.card_id) return;
|
||
this.$util.redirectTo('/pages_tool/card/detail', { card_id: card.card_id });
|
||
},
|
||
/**
|
||
* 存通讯录(2.17.1): 与 diy-card-show 同构 —— 字段全部从 card 组装
|
||
* (rows 已过 shouldShow 白名单), 键名与 save-contact.vue onLoad 的 keys 一一对应。
|
||
*/
|
||
goSaveContact() {
|
||
const c = this.card || {};
|
||
const map = {};
|
||
(Array.isArray(c.rows) ? c.rows : []).forEach(r => {
|
||
if (r && r.field) map[r.field] = String(r.value || '');
|
||
});
|
||
// #ifdef H5
|
||
// H5 无通讯录 API: 复制资料文本兜底(小程序端走 save-contact 原生页)
|
||
const text = [c.name, map.mobile, map.email, c.company, c.job_title].filter(Boolean).join(' ');
|
||
uni.setClipboardData({
|
||
data: text,
|
||
success: () => uni.showToast({ title: '名片资料已复制', icon: 'none' }),
|
||
});
|
||
return;
|
||
// #endif
|
||
this.$util.redirectTo('/pages_tool/card/save-contact', {
|
||
name: c.name || '',
|
||
mobile: map.mobile || '',
|
||
company: c.company || '',
|
||
job_title: c.job_title || '',
|
||
email: map.email || '',
|
||
website: map.website || '',
|
||
address: map.address || '',
|
||
remark: map.remark || '',
|
||
wechat: map.wechat || '',
|
||
});
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.diy-card-self {
|
||
width: 100%;
|
||
position: relative;
|
||
|
||
.self-login,
|
||
.self-empty {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 48rpx 24rpx;
|
||
|
||
.empty-icon {
|
||
font-size: 64rpx;
|
||
}
|
||
|
||
.login-tip,
|
||
.empty-tip {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
}
|
||
|
||
.login-btn,
|
||
.empty-btn {
|
||
margin-top: 28rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.login-btn-text,
|
||
.empty-btn-text {
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #ffffff;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.btn-press {
|
||
opacity: 0.72;
|
||
transform: scale(0.98);
|
||
}
|
||
</style>
|