448 lines
12 KiB
Vue
448 lines
12 KiB
Vue
<template>
|
|
<view data-component-name="diy-card-show" class="diy-card-show">
|
|
<!-- 骨架屏: 首屏加载/切换名片 -->
|
|
<view v-if="loading" class="show-skeleton" :style="cardShellStyle">
|
|
<view class="sk sk-line" style="width:42%"></view>
|
|
<view class="sk sk-line" style="width:26%;margin-top:16rpx;"></view>
|
|
<view class="sk sk-line" style="width:70%;margin-top:40rpx;"></view>
|
|
<view class="sk sk-line" style="width:58%;margin-top:16rpx;"></view>
|
|
<view class="sk sk-line" style="width:40%;margin-top:16rpx;"></view>
|
|
</view>
|
|
|
|
<!-- 空态: 未拿到名片 -->
|
|
<view v-else-if="!card" class="show-empty" :style="cardShellStyle">
|
|
<text class="empty-icon">💳</text>
|
|
<text class="empty-tip">商家还未选择名片</text>
|
|
</view>
|
|
|
|
<!-- 名片: 主题色顶栏 + 白卡压边(参考图版式, 自绘不复用 mini 基件) -->
|
|
<view v-else class="show-block">
|
|
<view class="show-band" :style="bandStyle"></view>
|
|
|
|
<view class="show-card" :style="cardShellStyle">
|
|
<!-- 头部: 姓名/职位 左, 小二维码"预览名片" 右 -->
|
|
<view class="card-head">
|
|
<view class="head-info">
|
|
<text class="card-name" :style="nameStyle">{{ card.name || '未命名名片' }}</text>
|
|
<text v-if="jobLine" class="card-job" :style="subStyle">{{ jobLine }}</text>
|
|
</view>
|
|
<view v-if="card.qr_url" class="card-qr" hover-class="btn-press" @tap.stop="goDetail(card)">
|
|
<image class="qr-img" :style="qrImgStyle" :src="$util.img(card.qr_url)" mode="aspectFit" />
|
|
<text class="qr-tip" :style="subStyle">预览名片</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 资料行(有字形字段显图标, 无字形退化为文字标签; href 派生点击动作) -->
|
|
<view v-if="shownRows.length" class="card-rows">
|
|
<view v-for="(row, i) in shownRows" :key="i" class="card-row"
|
|
:class="{ 'row-tappable': rowTapable(row) }"
|
|
:hover-class="rowTapable(row) ? 'btn-press' : 'none'"
|
|
@tap="onRowTap(row)">
|
|
<text v-if="rowIcon(row)" class="row-icon iconfont" :class="rowIcon(row)" :style="labelStyle"></text>
|
|
<text v-else class="row-label" :style="labelStyle">{{ row.label }}</text>
|
|
<text class="row-value" :style="valueStyle">{{ row.value }}</text>
|
|
</view>
|
|
<view v-if="hasMoreRows" class="row-toggle" hover-class="btn-press" @tap.stop="expanded = !expanded">
|
|
<text class="toggle-text" :style="themeTextStyle">{{ expanded ? '收起资料' : '展开全部资料(' + hiddenCount + ')' }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 发名片: 卡片外下方胶囊(详情页 send-bar 同款观感, 主题渐变色) -->
|
|
<view class="show-send" :style="sendBtnStyle" hover-class="btn-press" @tap="onSend(card)">
|
|
<text class="show-send-text">发名片</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
// 数字名片 · 精选名片(装修组件)
|
|
// 2026-10-21 换思路: 不再经 miniConfig 复用 diy-card-mini 通用布局, 改为本组件
|
|
// 自绘参考图版式 —— 主题色顶栏衬底 + 白卡压边 + 右上小二维码(预览名片) +
|
|
// 图标资料行 + 卡外「发名片」胶囊。面板仅颜色/底色/圆角/阴影/行数继续生效。
|
|
import {
|
|
CardTokens, px2rpx, num, themeGradient, visibleRows, rowIconFor, rowActOf,
|
|
} from './diy-card-common.js';
|
|
|
|
export default {
|
|
name: 'diy-card-show',
|
|
props: {
|
|
value: { type: Object }
|
|
},
|
|
data() {
|
|
return {
|
|
card: null,
|
|
loading: false,
|
|
expanded: false,
|
|
};
|
|
},
|
|
computed: {
|
|
c() {
|
|
return this.value || {};
|
|
},
|
|
/** 白卡外壳: 面板底色/圆角/阴影可调(内边距固定对齐参考图) */
|
|
cardShellStyle() {
|
|
const c = this.c;
|
|
return this.$util.objToStyle({
|
|
background: c.bgColor || CardTokens.color.bg,
|
|
borderRadius: px2rpx(num(c.borderRadius, 16)) + 'rpx',
|
|
boxShadow: c.showShadow === false ? 'none' : CardTokens.shadow.card,
|
|
});
|
|
},
|
|
/** 主题色顶栏: 名片主题渐变, 无主题退化默认主色 */
|
|
bandStyle() {
|
|
return this.$util.objToStyle({ background: themeGradient(this.card) });
|
|
},
|
|
/** 职位行: 职位优先, 缺失退公司/名片标题(参考图只显一行) */
|
|
jobLine() {
|
|
const card = this.card || {};
|
|
return card.job_title || card.company || card.card_title || '';
|
|
},
|
|
/** 二维码边长: 面板自定义 qrSize(px) > 0 时生效, 否则固定 48px */
|
|
qrSide() {
|
|
return px2rpx(num(this.c.qrSize, 48));
|
|
},
|
|
qrImgStyle() {
|
|
return this.$util.objToStyle({
|
|
width: this.qrSide + 'rpx',
|
|
height: this.qrSide + 'rpx',
|
|
});
|
|
},
|
|
nameStyle() {
|
|
return this.$util.objToStyle({ color: this.c.nameColor || CardTokens.color.text });
|
|
},
|
|
subStyle() {
|
|
return this.$util.objToStyle({ color: this.c.infoColor || CardTokens.color.textSub });
|
|
},
|
|
labelStyle() {
|
|
return this.$util.objToStyle({ color: this.c.labelColor || CardTokens.color.textLabel });
|
|
},
|
|
valueStyle() {
|
|
return this.$util.objToStyle({ color: this.c.infoColor || CardTokens.color.textSub });
|
|
},
|
|
themeTextStyle() {
|
|
return this.$util.objToStyle({ color: (this.card && this.card.theme_color) || CardTokens.color.primary });
|
|
},
|
|
/** 发名片按钮: 名片主题渐变色(无主题退化为默认主色), 详情页 send-bar 同款胶囊 */
|
|
sendBtnStyle() {
|
|
return this.$util.objToStyle({
|
|
background: themeGradient(this.card),
|
|
height: '88rpx',
|
|
borderRadius: '44rpx',
|
|
});
|
|
},
|
|
maxRows() {
|
|
return num(this.c.maxRows, 4);
|
|
},
|
|
rowsInfo() {
|
|
if (!this.card) return { shown: [], hasMore: false };
|
|
return visibleRows(this.card.rows, this.expanded ? 0 : this.maxRows);
|
|
},
|
|
shownRows() {
|
|
return this.rowsInfo.shown;
|
|
},
|
|
hasMoreRows() {
|
|
return !this.expanded && this.rowsInfo.hasMore;
|
|
},
|
|
hiddenCount() {
|
|
return this.rowsInfo.rest.length;
|
|
},
|
|
},
|
|
watch: {
|
|
'value.cardId'() {
|
|
this.fetchData();
|
|
},
|
|
},
|
|
mounted() {
|
|
this.fetchData();
|
|
},
|
|
methods: {
|
|
fetchData() {
|
|
const cardId = String((this.value || {}).cardId || '').trim();
|
|
if (cardId === '') {
|
|
this.card = null;
|
|
return;
|
|
}
|
|
this.loading = true;
|
|
this.$api.sendRequest({
|
|
url: '/card/api/diy/card',
|
|
data: { card_id: cardId },
|
|
success: res => {
|
|
this.loading = false;
|
|
this.card = (res.code == 0 && res.data && res.data.card) ? res.data.card : null;
|
|
},
|
|
fail: () => {
|
|
this.loading = false;
|
|
this.card = null;
|
|
},
|
|
});
|
|
},
|
|
/** 资料行图标(字段无字形返回空串, 模板退化为文字标签) */
|
|
rowIcon(row) {
|
|
return rowIconFor(row && row.field);
|
|
},
|
|
/** 资料行是否可点: 服务端 href 派生得出动作 */
|
|
rowTapable(row) {
|
|
return !!rowActOf(row);
|
|
},
|
|
onRowTap(row) {
|
|
const act = rowActOf(row);
|
|
if (act) this.doAct(act);
|
|
},
|
|
doAct(act) {
|
|
if (!act || !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') {
|
|
// #ifdef H5
|
|
window.location.href = 'mailto:' + act.value;
|
|
// #endif
|
|
// #ifndef H5
|
|
uni.setClipboardData({
|
|
data: act.value,
|
|
success: () => uni.showToast({ title: '邮箱已复制', icon: 'none' }),
|
|
});
|
|
// #endif
|
|
} 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 });
|
|
},
|
|
/**
|
|
* 发名片: 跳原生转发中转页 /pages_tool/card/forward(内含 open-type=share 按钮,
|
|
* 直接拉起微信转发面板, 接收方落地 card/share 的 web-view 名片页)。
|
|
* 依赖 diy/card 返回 wap_url; 未返回时退化进详情页(详情页里可发名片)。
|
|
*/
|
|
onSend(card) {
|
|
if (!card) return;
|
|
const wapUrl = card.wap_url || card.share_url || '';
|
|
if (!wapUrl) {
|
|
this.goDetail(card);
|
|
return;
|
|
}
|
|
const q = [
|
|
'title=' + encodeURIComponent(card.name || ''),
|
|
'avatar=' + encodeURIComponent(card.avatar ? this.$util.img(card.avatar) : ''),
|
|
'url=' + encodeURIComponent(wapUrl)
|
|
].join('&');
|
|
// navigateTo 保留返回: 转发完可回到装修页
|
|
uni.navigateTo({ url: '/pages_tool/card/forward?' + q });
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.diy-card-show {
|
|
width: 100%;
|
|
|
|
/* 顶栏 + 白卡压边版式 */
|
|
.show-block {
|
|
position: relative;
|
|
width: 100%;
|
|
/* 卡片上方露蓝: 用 padding 撑开(子元素 margin-top 会塌陷出容器,
|
|
把蓝色背景层一起推下去, 顶部就露出页面白底了) */
|
|
padding-top: 40rpx;
|
|
|
|
/* 主题色顶栏: 只铺上半段, 下半段回落页面底色(白卡自行压边) */
|
|
.show-band {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 300rpx;
|
|
border-radius: 24rpx 24rpx 0 0;
|
|
}
|
|
|
|
.show-card {
|
|
position: relative;
|
|
z-index: 1;
|
|
margin: 0 24rpx;
|
|
padding: 40rpx;
|
|
box-sizing: border-box;
|
|
width: auto;
|
|
overflow: hidden;
|
|
|
|
.card-head {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
|
|
.head-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
.card-name {
|
|
font-size: 38rpx;
|
|
font-weight: 700;
|
|
line-height: 1.3;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.card-job {
|
|
font-size: 26rpx;
|
|
margin-top: 10rpx;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
/* 右上小二维码 + 「预览名片」: 点按进完整名片页 */
|
|
.card-qr {
|
|
flex-shrink: 0;
|
|
margin-left: 24rpx;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
|
|
.qr-img {
|
|
width: 96rpx;
|
|
height: 96rpx;
|
|
display: block;
|
|
}
|
|
|
|
.qr-tip {
|
|
font-size: 22rpx;
|
|
margin-top: 8rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
.card-rows {
|
|
margin-top: 36rpx;
|
|
|
|
.card-row {
|
|
display: flex;
|
|
align-items: center;
|
|
line-height: 2.1;
|
|
|
|
/* 图标/文字标签共槽, 值列左缘对齐(参考图口径) */
|
|
.row-icon {
|
|
flex-shrink: 0;
|
|
width: 88rpx;
|
|
text-align: left;
|
|
font-size: 28rpx;
|
|
}
|
|
|
|
.row-label {
|
|
flex-shrink: 0;
|
|
width: 88rpx;
|
|
font-size: 24rpx;
|
|
}
|
|
|
|
.row-value {
|
|
flex: 1;
|
|
font-size: 26rpx;
|
|
word-break: break-all;
|
|
}
|
|
|
|
&.row-tappable {
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
|
|
.row-toggle {
|
|
margin-top: 8rpx;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
|
|
.toggle-text {
|
|
font-size: 22rpx;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 发名片: 卡片外下方胶囊(背景色由 sendBtnStyle 内联) */
|
|
.show-send {
|
|
position: relative;
|
|
z-index: 1;
|
|
margin: 32rpx 24rpx 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
.show-send-text {
|
|
font-size: 30rpx;
|
|
font-weight: 600;
|
|
color: #ffffff;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 空态 */
|
|
.show-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;
|
|
}
|
|
|
|
.empty-tip {
|
|
font-size: 24rpx;
|
|
margin-top: 16rpx;
|
|
color: #8a8f99;
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
/* 骨架屏 */
|
|
.show-skeleton {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
padding: 40rpx;
|
|
}
|
|
|
|
.sk {
|
|
background: linear-gradient(90deg, #f2f3f5 25%, #e8eaee 37%, #f2f3f5 63%);
|
|
background-size: 400rpx 100%;
|
|
animation: sk-shimmer 1.2s linear infinite;
|
|
border-radius: 8rpx;
|
|
|
|
&.sk-line {
|
|
height: 28rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 按压反馈 */
|
|
.btn-press {
|
|
opacity: 0.72;
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
@keyframes sk-shimmer {
|
|
0% {
|
|
background-position: -200rpx 0;
|
|
}
|
|
|
|
100% {
|
|
background-position: 200rpx 0;
|
|
}
|
|
}
|
|
</style>
|