Files
lucky_shop/components-diy/diy-card-show.vue

936 lines
28 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">
<!-- 封面图作背景(面板可关): 铺满整卡 + 压暗遮罩, 内容层 z-index 抬升防遮挡 -->
<image v-if="showCover" class="card-cover" :src="$util.img(card.cover)" mode="aspectFill" />
<view v-if="showCover" class="card-cover-scrim"></view>
<!-- 主题渐变饰条(面板可关): 名片主题渐变横条, 置于卡片顶部 -->
<view v-if="showGradBar" class="card-grad-bar" :style="gradBarStyle"></view>
<!-- 头部: 头像(面板可关, 关后只留姓名/职位) + 姓名/职位 左, 小二维码"预览名片" 右 -->
<view class="card-head">
<view v-if="showAvatarBlock" class="card-avatar" :style="avatarStyle">
<image v-if="card.avatar" class="avatar-img" :src="$util.img(card.avatar)" mode="aspectFill" />
<text v-else class="avatar-initial">{{ initialOf(card.name) }}</text>
</view>
<view class="head-info" :class="{ 'with-avatar': showAvatarBlock }">
<text class="card-name" :style="nameStyle">{{ card.name || '未命名名片' }}</text>
<text v-if="jobLine" class="card-job" :style="subStyle">{{ jobLine }}</text>
</view>
<!-- 头部右侧档 -->
<view v-if="showQrHead" class="card-qr" :style="qrBlockStyle" hover-class="btn-press" @tap.stop="openCardShare">
<image class="qr-img" :style="qrImgStyle" :src="$util.img(card.qr_url)" mode="aspectFit" />
<text class="qr-tip" :style="subStyle">{{ qrTipText }}</text>
</view>
<!-- 名片 Logo(面板可关): 头部右侧, 与头部档二维码同位自动避让 -->
<image v-if="showLogo" class="card-logo" :src="$util.img(card.logo)" mode="aspectFit" />
</view>
<!-- 资料行(面板「显示资料行」可关; 有字形字段显图标, 无字形退化为文字标签; href 派生点击动作) -->
<view v-if="showRowsBlock && 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 v-if="showQrRows" class="qr-center" :style="qrBlockStyle" hover-class="btn-press" @tap.stop="openCardShare">
<image class="qr-img-c" :style="qrImgStyle" :src="$util.img(card.qr_url)" mode="aspectFit" />
<text class="qr-tip-c" :style="subStyle">{{ qrTipText }}</text>
</view>
<!-- 右上角浮层档: 白底描边小卡浮在名片右上角, 点击弹分享抽屉 -->
<view v-if="showQrCorner" class="card-qr-corner" :style="qrBlockStyle" hover-class="btn-press" @tap.stop="openCardShare">
<image class="qr-img-c" :style="qrImgStyle" :src="$util.img(card.qr_url)" mode="aspectFit" />
<text class="qr-tip-corner" :style="subStyle">{{ qrTipText }}</text>
</view>
<!-- 快捷操作(面板「显示快捷操作」可关; 服务端按资料派生;
2026-10-23 参考图定稿: 统一「发邮件」样式 白底+主题色描边) -->
<view v-if="showActsBlock && acts.length" class="card-acts">
<view v-for="(act, i) in acts" :key="i" class="act-chip"
:style="actChipStyle"
hover-class="btn-press" @tap.stop="doAct(act)">
<text class="act-text" :style="actChipTextStyle">{{ act.label }}</text>
</view>
</view>
</view>
<!-- 主按钮上方档: 大码居中卡于白卡与发名片按钮之间 -->
<view v-if="showQrBottom" class="qr-center qr-center-bottom" :style="qrBlockStyle" hover-class="btn-press" @tap.stop="openCardShare">
<image class="qr-img-c" :style="qrImgStyle" :src="$util.img(card.qr_url)" mode="aspectFit" />
<text class="qr-tip-c" :style="subStyle">{{ qrTipText }}</text>
</view>
<!-- 发名片: 卡片外下方胶囊(详情页 send-bar 同款观感, 主题渐变色; 面板「发送名片按钮」可关; 点击弹分享抽屉) -->
<view v-if="showSendBtn" class="show-send" :style="sendBtnStyle" hover-class="btn-press" @tap="openCardShare">
<text class="show-send-text">{{ sendBtnText }}</text>
</view>
</view>
<!-- 分享名片抽屉: 点二维码弹出(pages_tool/card/detail.vue 同款版式/文案) -->
<view class="cs-mask" v-if="showShare" @tap="closeShare"></view>
<view class="cs-sheet" :class="{ 'cs-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="card && card.qr_url" :src="$util.img(card.qr_url)" class="ss-qr" mode="widthFix" show-menu-by-longpress="true" />
</view>
<view class="ss-link">{{ (card && card.wap_url) || '' }}</view>
<view class="ss-btns">
<view class="ss-btn ss-btn-main" :style="shareBtnStyle" @tap="copyCardLink">复制链接</view>
<!-- #ifdef H5 -->
<view class="ss-btn" @tap="h5CardShare">浏览器分享</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || MP-HARMONY -->
<view class="ss-btn" @tap="saveCardQr">保存二维码</view>
<!-- #endif -->
</view>
<view class="ss-tip">
<!-- #ifdef MP-WEIXIN -->长按二维码也可保存到相册<!-- #endif -->
<!-- #ifdef H5 -->长按二维码可保存到相册<!-- #endif -->
<!-- #ifdef MP-HARMONY -->点击「保存二维码」存入相册<!-- #endif -->
</view>
</view>
</view>
</template>
<script>
// 数字名片 · 精选名片(装修组件)
// 2026-10-21 换思路: 不再经 miniConfig 复用 diy-card-mini 通用布局, 改为本组件
// 自绘参考图版式 —— 主题色顶栏衬底 + 白卡压边 + 右上小二维码(预览名片) +
// 图标资料行 + 卡外「发名片」胶囊。面板仅颜色/底色/圆角/阴影/行数继续生效。
import {
CardTokens, px2rpx, num, flagOn, initialOf, avatarBg, themeGradient, darkenColor, visibleRows, rowIconFor, rowActOf,
resolveQrSize, normalizeQrPosition, qrTransform, normalizeQrStyle,
} from './diy-card-common.js';
export default {
name: 'diy-card-show',
props: {
value: { type: Object }
},
data() {
return {
card: null,
loading: false,
expanded: false,
showShare: false,
};
},
computed: {
c() {
return this.value || {};
},
/** 白卡外壳: 面板底色/圆角/阴影可调(内边距固定对齐参考图) */
cardShellStyle() {
const c = this.c;
return this.$util.objToStyle({
// 封面图作背景时白底让位(封面图铺满整卡, mini 基件同口径)
background: this.showCover ? 'transparent' : (c.bgColor || CardTokens.color.bg),
borderRadius: px2rpx(num(c.borderRadius, 16)) + 'rpx',
boxShadow: c.showShadow === false ? 'none' : CardTokens.shadow.card,
});
},
/** 封面图作背景: 面板「名片设计资产 → 封面图作背景」(默认关; 名片未配封面图时不显示) */
showCover() {
return flagOn(this.c.useCover, false) && !!(this.card && this.card.cover);
},
/** 主题渐变饰条: 面板「名片设计资产 → 主题渐变饰条」(默认关) */
showGradBar() {
return flagOn(this.c.showGradient, false);
},
/** 名片 Logo: 面板「名片设计资产 → 名片 Logo」(默认关; 名片未配 Logo 不显示;
* 与头部档二维码同在右上, 自动避让 —— mini 基件同口径) */
showLogo() {
return flagOn(this.c.showLogo, false) && !!(this.card && this.card.logo) && !this.showQrHead;
},
/** 饰条底色: 名片主题渐变(未配渐变副色时 themeGradient 退化为纯主题色) */
gradBarStyle() {
return this.$util.objToStyle({
background: themeGradient(this.card),
});
},
/** 主题色顶栏: 名片主题渐变, 无主题退化默认主色 */
bandStyle() {
return this.$util.objToStyle({ background: themeGradient(this.card) });
},
/** 头像开关: 面板「显示头像」(历史配置无此字段默认显示; 关后头部只留姓名/职位) */
showAvatarBlock() {
return flagOn(this.c.showAvatar, true);
},
/** 资料行总开关: 面板「显示资料行」(历史配置无此字段默认显示; 关后整块隐藏) */
showRowsBlock() {
return flagOn(this.c.showRows, true);
},
/** 二维码开关: 面板「显示二维码」(历史配置无此字段默认显示; 关后头部只留姓名/职位) */
showQrBlock() {
return flagOn(this.c.showQr, true);
},
/** 二维码位置(四档, 与面板下拉/mini 基件同口径): rows 资料行下方 / head 头部右侧 /
* corner 右上角浮层 / bottom 主按钮上方; 未配置回落 rows(面板默认档) */
qrPos() {
return normalizeQrPosition(this.c.qrPosition);
},
/** 位置分档显隐(共用 showQrBlock + qr_url 前置) */
showQrBase() {
return this.showQrBlock && !!(this.card && this.card.qr_url);
},
showQrHead() { return this.showQrBase && this.qrPos === 'head'; },
showQrRows() { return this.showQrBase && this.qrPos === 'rows'; },
showQrCorner() { return this.showQrBase && this.qrPos === 'corner'; },
showQrBottom() { return this.showQrBase && this.qrPos === 'bottom'; },
/** 二维码外观(四档): plain 原图 / card 白卡描边 / theme 主题色衬底 / gradient 渐变圆角。
* 多彩只装饰外围衬底, 码本身保持黑白保可扫(与 mini 基件 qrBlockStyle 同口径)。
* 注意: 必须输出单一字符串样式 —— uni-app 运行时不支持 :style="[字符串, 字符串]"
* 数组绑定(会被静默丢弃, 衬底/微调全部失效), 故 transform 合并在同一对象里 */
qrStyle() {
return normalizeQrStyle(this.c.qrStyle);
},
qrBlockStyle() {
const style = {};
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
if (this.qrStyle === 'card') {
style.background = '#ffffff';
style.border = '2rpx solid #EEF0F3';
style.borderRadius = '12rpx';
style.padding = '10rpx';
} else if (this.qrStyle === 'theme') {
style.background = theme;
style.borderRadius = '12rpx';
style.padding = '10rpx';
} else if (this.qrStyle === 'gradient') {
// 名片仅单主题色(无 gradient_end)时, themeGradient 返回纯色渐变(看不出渐变),
// 故用压暗一档的终止色保证「渐变圆角」始终可见明暗过渡
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
const end = (this.card && this.card.gradient_end) || darkenColor(theme, 0.18);
style.background = 'linear-gradient(135deg, ' + theme + ' 0%, ' + end + ' 100%)';
style.borderRadius = '16rpx';
style.padding = '6rpx';
}
// plain 不进任何分支(无衬底, 即原图)
// 微调位移(qrOffsetX/Y): 与外观合并成同一段内联样式
const t = qrTransform(this.c.qrOffsetX, this.c.qrOffsetY);
if (t) style.transform = t;
return this.$util.objToStyle(style);
},
/** 二维码提示文字: 面板 qrTip, 未配置回落「扫码查看完整名片」 */
qrTipText() {
return this.c.qrTip || '扫码查看完整名片';
},
/** 分享抽屉主按钮: 主题色(禁 CSS 变量, 小程序端内联具体值) */
shareBtnStyle() {
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
return this.$util.objToStyle({
background: theme,
border: '2rpx solid ' + theme,
});
},
/** 快捷操作开关: 面板「显示快捷操作」(历史配置无此字段默认显示; 关后芯片整块隐藏) */
showActsBlock() {
return flagOn(this.c.showQuick, true);
},
/** 快捷操作芯片: 服务端按名片实际资料派生(首个为主动作, 填充样式) */
acts() {
return this.card && Array.isArray(this.card.acts) ? this.card.acts : [];
},
actChipStyle() {
// 快捷芯片统一「发邮件」样式(2026-10-23 参考图定稿): 白底 + 主题色描边;
// 尺寸/圆角由 .act-chip CSS 类控制(避免内联锁死)
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
return this.$util.objToStyle({
background: '#FFFFFF',
border: '2rpx solid ' + theme,
});
},
actChipTextStyle() {
return this.$util.objToStyle({ color: (this.card && this.card.theme_color) || CardTokens.color.primary });
},
avatarStyle() {
return this.$util.objToStyle({ background: avatarBg(this.card) });
},
/** 职位行: 职位优先, 缺失退公司/名片标题(参考图只显一行) */
jobLine() {
const card = this.card || {};
return card.job_title || card.company || card.card_title || '';
},
/** 二维码边长: 面板 qrSize > 0 用自定义(px), 0/空 = 「按位自动」按位置档位取值
* (rows/bottom 180rpx, head/corner 120rpx, 见 common.QR_SIZES)。
* 之前用 num(qrSize, 48) 兜底 —— 面板默认 0 是合法数字不触发默认值,
* 算出 0rpx 宽高, 二维码图被渲染成 0 尺寸「看不见」 */
qrSide() {
return resolveQrSize(this.qrPos, this.c.qrSize);
},
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 });
},
/** 发名片按钮开关: 面板「底部按钮 → 发送名片按钮」(历史配置无此字段默认显示; 关后白卡下不留按钮) */
showSendBtn() {
return flagOn(this.c.showDetailBtn, true);
},
/** 发名片按钮文案: 面板「按钮文案」(默认「发送名片」, 与面板提示一致) */
sendBtnText() {
return this.c.detailBtnText || '发送名片';
},
/** 发名片按钮: 名片主题渐变色(无主题退化为默认主色), 详情页 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: {
// 模板无法访问模块导入, 需挂到实例(diy-card-mini 同款写法); 头像缺图时显示姓氏首字
initialOf,
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 flagOn(this.c.showRowIcons, true) ? 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/detail.vue) ---------- */
openCardShare() {
this.showShare = true;
},
closeShare() {
this.showShare = false;
},
copyCardLink() {
const url = (this.card && this.card.wap_url) || '';
if (!url) {
uni.showToast({ title: '名片链接未返回,请稍后重试', icon: 'none' });
return;
}
uni.setClipboardData({
data: url,
success: () => uni.showToast({ title: '链接已复制', icon: 'success' }),
});
},
// H5: 优先 navigator.share(移动浏览器), 不支持则复制链接兜底
h5CardShare() {
// #ifdef H5
const url = (this.card && this.card.wap_url) || '';
if (navigator && typeof navigator.share === 'function' && url) {
navigator.share({
title: ((this.card && this.card.name) || '我的名片') + '的名片',
url: url
}).catch(() => {});
return;
}
// #endif
this.copyCardLink();
},
// 小程序/鸿蒙: 下载二维码后存相册(长按图片也可存, 抽屉底部有提示)
saveCardQr() {
const qrUrl = (this.card && this.card.qr_url) || '';
if (!qrUrl) return;
uni.downloadFile({
url: this.$util.img(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' }),
fail: () => uni.showToast({ title: '保存失败,可复制链接分享', icon: 'none' })
});
},
fail: () => uni.showToast({ title: '二维码下载失败', icon: 'none' })
});
},
},
};
</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;
/* 封面图作背景(面板「封面图作背景」开 + 名片已配封面图才渲染):
铺满整卡 + 半透明压暗遮罩(白字可读), mini 基件同款参数 */
.card-cover {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.card-cover-scrim {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(15, 23, 42, 0.45);
z-index: 1;
}
/* 内容层压过封面/遮罩(绝对定位的封面默认盖住静态内容) */
.card-head,
.card-rows,
.card-acts,
.qr-center {
position: relative;
z-index: 2;
}
/* 主题渐变饰条: 卡片顶部横条(细高圆角, 底色走 gradBarStyle 内联主题渐变) */
.card-grad-bar {
position: relative;
z-index: 2;
height: 14rpx;
border-radius: 8rpx;
margin-bottom: 28rpx;
}
/* 名片 Logo: 头部右侧小图(自动推到行尾; 头部档二维码显示时不渲染, 自动避让) */
.card-logo {
position: relative;
z-index: 2;
width: 96rpx;
height: 96rpx;
border-radius: 12rpx;
margin-left: auto;
flex-shrink: 0;
}
.card-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
/* 头像(面板「显示头像」可关): 圆形, 底色主题色, 缺图显示姓名首字 */
.card-avatar {
flex-shrink: 0;
width: 104rpx;
height: 104rpx;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
.avatar-img {
width: 100%;
height: 100%;
display: block;
}
.avatar-initial {
font-size: 42rpx;
font-weight: 700;
color: #ffffff;
}
}
.head-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
/* 与头像的间距只在头像显示时存在(关闭后姓名列与资料行左缘对齐) */
&.with-avatar {
margin-left: 24rpx;
}
.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;
}
}
}
/* 快捷操作芯片: 高度/圆角对齐参考图(首个填充, 其余描边) */
.card-acts {
margin-top: 32rpx;
display: flex;
flex-wrap: wrap;
.act-chip {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
/* 小胶囊(2026-10-22 参考图定稿): 矮一点/收窄内距, 内容短时不再近似圆形 */
height: 56rpx;
padding: 0 24rpx;
margin: 0 16rpx 16rpx 0;
border-radius: 28rpx;
.act-text {
font-size: 24rpx;
font-weight: 500;
}
}
}
/* 右上角浮层档: 白底描边小卡浮在名片右上角(z-index 压过头部信息) */
.card-qr-corner {
position: absolute;
top: 20rpx;
right: 20rpx;
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
padding: 10rpx;
background-color: #ffffff;
border: 2rpx solid #EEF0F3;
border-radius: 12rpx;
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.10);
.qr-tip-corner {
font-size: 20rpx;
margin-top: 4rpx;
white-space: nowrap;
}
}
}
/* 资料行下方 / 主按钮上方档: 大码居中(边长走 qrImgStyle 内联) */
.qr-center {
margin-top: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
.qr-tip-c {
font-size: 22rpx;
margin-top: 8rpx;
}
/* 卡外(主按钮上方): 与上下元素拉开间距 */
&.qr-center-bottom {
margin: 32rpx 24rpx 0;
}
}
/* 发名片: 卡片外下方胶囊(背景色由 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);
}
/* 分享名片抽屉(复刻 pages_tool/card/detail.vue 的 share-mask/share-sheet, 类名加 cs- 前缀防撞) */
.cs-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 100;
}
.cs-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;
&.cs-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;
// 小程序端如有原生 button(当前无, 保险起见同样清默认样式)
margin: 0;
padding: 0;
background-color: transparent;
overflow: visible;
&::after {
border: none;
}
& + .ss-btn {
margin-left: 20rpx;
}
}
// 主按钮颜色走 shareBtnStyle 内联(主题色), 此处只兜底默认蓝
.ss-btn-main {
background-color: #119dff;
border-color: #119dff;
color: #ffffff;
}
}
.ss-tip {
text-align: center;
font-size: 22rpx;
color: #c0c4cc;
margin-top: 20rpx;
}
}
@keyframes sk-shimmer {
0% {
background-position: -200rpx 0;
}
100% {
background-position: 200rpx 0;
}
}
</style>