Merge remote-tracking branch 'origin/feat/camcard' into feat/camcard

# Conflicts:
#	components-diy/diy-card-mini.vue
#	components-diy/diy-card-show.vue
#	tests/diy-card-ab-visual.test.js
#	tests/diy-card-qr-style.test.js
This commit is contained in:
2026-10-24 10:56:20 +08:00
12 changed files with 2077 additions and 58 deletions

File diff suppressed because one or more lines are too long

View File

@@ -14,7 +14,7 @@
<!-- 按钮(可选; link 模式为文字箭头) -->
<view v-if="buttonStyle !== 'none' && value.btnText" :class="['entry-btn', 'btn-' + buttonStyle]" :style="btnStyle" hover-class="btn-press" hover-stay-time="60">
<text class="entry-btn-text" :style="btnTextStyle">{{ value.btnText }}</text>
<text v-if="buttonStyle === 'link'" class="btn-arrow" :style="btnTextStyle">›</text>
<text v-if="buttonStyle === 'link'" class="btn-arrow" :style="btnArrowStyle">›</text>
</view>
</view>
</template>
@@ -103,11 +103,13 @@ export default {
titleStyle() {
return this.$util.objToStyle({
color: this.v.textColor || (this.isMinimal ? CardTokens.color.text : '#FFFFFF'),
fontSize: px2rpx(num(this.v.titleSize, 16)) + 'rpx',
});
},
subStyle() {
return this.$util.objToStyle({
color: this.v.textColor || (this.isMinimal ? CardTokens.color.textSub : '#FFFFFF'),
fontSize: px2rpx(num(this.v.subSize, 12)) + 'rpx',
opacity: this.isMinimal ? 1 : 0.85,
});
},
@@ -135,13 +137,24 @@ export default {
},
btnTextStyle() {
const v = this.v;
const fontSize = px2rpx(num(this.v.btnSize, 12)) + 'rpx';
if (this.buttonStyle === 'ghost') {
return this.$util.objToStyle({ color: v.accentColor || '#FFEB3B' });
return this.$util.objToStyle({ color: v.accentColor || '#FFEB3B', fontSize: fontSize });
}
if (this.buttonStyle === 'link') {
return this.$util.objToStyle({ color: v.textColor || CardTokens.color.primary });
return this.$util.objToStyle({ color: v.textColor || CardTokens.color.primary, fontSize: fontSize });
}
return this.$util.objToStyle({ color: '#333333' });
return this.$util.objToStyle({ color: '#333333', fontSize: fontSize });
},
/**
* 文字箭头按钮的箭头: 仅 link 档渲染, 颜色与该档按钮文字一致;
* 字号比按钮文字略大一档(默认 14px = 28rpx, 与历史渲染一致)
*/
btnArrowStyle() {
return this.$util.objToStyle({
color: this.v.textColor || CardTokens.color.primary,
fontSize: px2rpx(num(this.v.btnSize, 12) + 2) + 'rpx',
});
},
},
methods: {

View File

@@ -27,8 +27,8 @@
<image v-if="item.avatar" class="avatar-img" :src="$util.img(item.avatar)" mode="aspectFill" />
<text v-else class="avatar-initial">{{ initialOf(item.name) }}</text>
</view>
<text class="scroll-name" :style="nameStyle">{{ item.name || '未命名名片' }}</text>
<text v-if="infoLine(item)" class="scroll-meta" :style="infoStyle">{{ infoLine(item) }}</text>
<text class="scroll-name" :style="scrollNameStyle">{{ item.name || '未命名名片' }}</text>
<text v-if="infoLine(item)" class="scroll-meta" :style="scrollMetaStyle">{{ infoLine(item) }}</text>
<text v-if="isEnterprise(item)" class="item-badge" :style="badgeStyle(item)">企业</text>
</view>
</scroll-view>
@@ -46,10 +46,10 @@
</view>
<view class="item-info">
<view class="name-row">
<text class="item-name" :style="nameStyle">{{ item.name || '未命名名片' }}</text>
<text class="item-name" :style="gridNameStyle">{{ item.name || '未命名名片' }}</text>
<text v-if="isEnterprise(item)" class="item-badge" :style="badgeStyle(item)">企业</text>
</view>
<text v-if="infoLine(item)" class="item-meta" :style="infoStyle">{{ infoLine(item) }}</text>
<text v-if="infoLine(item)" class="item-meta" :style="metaStyle">{{ infoLine(item) }}</text>
</view>
<text v-if="showArrow" class="item-arrow" :style="infoStyle">›</text>
</view>
@@ -129,11 +129,40 @@ export default {
});
},
titleStyle() {
return this.$util.objToStyle({ color: this.v.titleColor || CardTokens.color.text });
return this.$util.objToStyle({
color: this.v.titleColor || CardTokens.color.text,
fontSize: px2rpx(num(this.v.titleSize, 16)) + 'rpx',
});
},
nameStyle() {
return this.$util.objToStyle({ color: this.v.nameColor || CardTokens.color.text });
/** 网格/列表卡片姓名: 字号可配(默认 14px = 28rpx) */
gridNameStyle() {
return this.$util.objToStyle({
color: this.v.nameColor || CardTokens.color.text,
fontSize: px2rpx(num(this.v.nameSize, 14)) + 'rpx',
});
},
/** 横滑竖卡姓名: 比网格小一档(默认 13px = 26rpx, 与历史渲染一致) */
scrollNameStyle() {
return this.$util.objToStyle({
color: this.v.nameColor || CardTokens.color.text,
fontSize: px2rpx(num(this.v.nameSize, 14) - 1) + 'rpx',
});
},
/** 网格/列表卡片信息行: 字号可配(默认 11px = 22rpx) */
metaStyle() {
return this.$util.objToStyle({
color: this.v.infoColor || CardTokens.color.textSub,
fontSize: px2rpx(num(this.v.infoSize, 11)) + 'rpx',
});
},
/** 横滑竖卡信息行: 比网格小一档(默认 10px = 20rpx, 与历史渲染一致) */
scrollMetaStyle() {
return this.$util.objToStyle({
color: this.v.infoColor || CardTokens.color.textSub,
fontSize: px2rpx(num(this.v.infoSize, 11) - 1) + 'rpx',
});
},
/** infoColor 纯色(箭头/空态用, 不带字号) */
infoStyle() {
return this.$util.objToStyle({ color: this.v.infoColor || CardTokens.color.textSub });
},
@@ -141,10 +170,15 @@ export default {
return this.$util.objToStyle({
color: CardTokens.color.primary,
background: 'rgba(47,107,255,0.08)',
// 人数徽标比信息行小一档(默认 10px = 20rpx, 与历史渲染一致)
fontSize: px2rpx(num(this.v.infoSize, 11) - 1) + 'rpx',
});
},
moreStyle() {
return this.$util.objToStyle({ color: CardTokens.color.textLabel });
return this.$util.objToStyle({
color: CardTokens.color.textLabel,
fontSize: px2rpx(num(this.v.infoSize, 11)) + 'rpx',
});
},
itemStyle() {
const gap = num(this.v.gap, 10);

View File

@@ -17,7 +17,7 @@
<!-- 空态: 未拿到名片 -->
<view v-else-if="!card" class="mini-empty" :style="wrapStyle">
<text class="empty-icon">💳</text>
<text class="empty-tip" :style="subTextStyle">{{ emptyTip || '暂无名片数据' }}</text>
<text class="empty-tip" :style="emptyTipStyle">{{ emptyTip || '暂无名片数据' }}</text>
<view v-if="emptyActionText" class="empty-btn" :style="emptyBtnStyle" hover-class="btn-press" @tap="onEmptyAction">
<text class="empty-btn-text">{{ emptyActionText }}</text>
</view>
@@ -43,11 +43,37 @@
<!-- 二维码·右上角浮层(corner 档: 外观由 qrStyle 决定, 资料行自动让位; 点击预览名片) -->
<view v-if="showQrCorner" class="mini-qr mini-qr-corner" :style="qrBlockStyle" @tap.stop="onQrTap">
<image class="qr-img" :style="qrImgStyle" :src="$util.img(card.qr_url)" mode="aspectFit" />
<image class="qr-img" :style="qrImgStyle" :src="$util.img(qrSrc)" mode="aspectFit" />
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
</view>
<view class="mini-head" :style="cornerHeadStyle">
<!-- 头部·主题渐变整块档(theme): 渐变通铺卡边 + 装饰圆, plain 档不渲染 -->
<view v-if="headTheme" class="mini-headtheme" :style="headThemeStyle">
<view class="headtheme-decor headtheme-decor--big"></view>
<view class="headtheme-decor headtheme-decor--small"></view>
<view class="mini-head" :style="cornerHeadStyle">
<view v-if="showAvatarBlock" class="mini-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="mini-head-info">
<view class="name-row">
<text class="mini-name" :style="nameStyle">{{ card.name || '未命名名片' }}</text>
<text v-if="isEnterprise" class="mini-badge" :style="badgeStyle">企业</text>
</view>
<text v-if="card.card_title" class="mini-card-title" :style="cardTitleStyle">{{ card.card_title }}</text>
<text v-if="subLine" class="mini-sub" :style="subTextStyle">{{ subLine }}</text>
</view>
<!-- 二维码·头部右侧(head 档: 头像|信息|二维码 三段 flex, 零遮挡) -->
<view v-if="showQrHead" class="mini-qr mini-qr-head" :style="qrBlockStyle">
<image class="qr-img" :style="qrImgStyle" :src="$util.img(qrSrc)" mode="aspectFit" />
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
</view>
</view>
</view>
<!-- 头部·白卡档(plain, 历史默认) -->
<view v-if="!headTheme" class="mini-head" :style="cornerHeadStyle">
<view v-if="showAvatarBlock" class="mini-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>
@@ -57,23 +83,59 @@
<text class="mini-name" :style="nameStyle">{{ card.name || '未命名名片' }}</text>
<text v-if="isEnterprise" class="mini-badge" :style="badgeStyle">企业</text>
</view>
<text v-if="card.card_title" class="mini-card-title" :style="subTextStyle">{{ card.card_title }}</text>
<text v-if="card.card_title" class="mini-card-title" :style="cardTitleStyle">{{ card.card_title }}</text>
<text v-if="subLine" class="mini-sub" :style="subTextStyle">{{ subLine }}</text>
</view>
<!-- 二维码·头部右侧(head 档: 头像|信息|二维码 三段 flex, 零遮挡; 点击预览名片) -->
<view v-if="showQrHead" class="mini-qr mini-qr-head" :style="qrBlockStyle" @tap.stop="onQrTap">
<image class="qr-img" :style="qrImgStyle" :src="$util.img(card.qr_url)" mode="aspectFit" />
<image class="qr-img" :style="qrImgStyle" :src="$util.img(qrSrc)" mode="aspectFit" />
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
</view>
</view>
<!-- 快捷操作·资料区上方档(2.18 actsPosition=above): below 时由下方同构块渲染 -->
<view v-if="actsAbove && showActs && acts.length && actsGrid" class="mini-acts-grid" :style="[actsBlockStyle, cornerClearStyle]">
<view v-for="(act, i) in acts" :key="'ta' + i" class="act-grid-item" :style="{ width: gridItemWidth }" hover-class="btn-press" @tap.stop="onAct(act)">
<view class="act-grid-icon" :style="actGridIconStyle(act)">
<!-- 自定义图标图片(2.18 媒体库, 优先) / SVG 线稿(2.18.1 与 wap detail 同源) / 首字兜底 -->
<image v-if="actImgOf(act)" class="act-grid-img" :src="$util.img(actImgOf(act))" mode="aspectFit" />
<view v-else-if="actLibOf(act)" class="act-grid-svg" :style="actLibStyle(act)"></view>
<view v-else-if="actSvgOf(act)" class="act-grid-svg" :style="actSvgStyle(act)"></view>
<text v-else class="act-grid-initial" :style="actGridGlyphStyle(act)">{{ actInitialOf(act) }}</text>
</view>
<text class="act-grid-label" :style="actGridLabelStyle(act)">{{ act.label }}</text>
</view>
</view>
<view v-if="actsAbove && showActs && acts.length && !actsGrid" class="mini-acts" :style="[actsBlockStyle, cornerClearStyle]">
<view v-for="(act, i) in acts" :key="'ta' + i" class="act-chip"
:style="i === 0 ? actPrimaryStyle : actGhostStyle" hover-class="btn-press" @tap.stop="onAct(act)">
<text class="act-text" :style="i === 0 ? actPrimaryTextStyle : actGhostTextStyle">{{ act.label }}</text>
</view>
</view>
<!-- 资料行(可折叠; 有字形字段显图标, 无字形退化为文字标签) -->
<view v-if="showRowsBlock && shownRows.length" class="mini-rows" :style="cornerClearStyle">
<view v-if="showRowsBlock && shownRows.length" class="mini-rows" :style="rowsClearStyle">
<!-- 资料区标题(2.17 可选): 主题色竖条 + 标题 -->
<view v-if="rowsTitleShow" class="mini-rows-title">
<view class="rows-title-bar" :style="rowsTitleBarStyle"></view>
<text class="rows-title-text" :style="rowsTitleStyle">{{ rowsTitle }}</text>
</view>
<view v-for="(row, i) in shownRows" :key="i" class="mini-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>
<!-- CSS 描边图标档(2.17.1): 邮箱/网站无 iconfont 字形, 纯 view 绘制 -->
<view v-if="rowCssIcon(row)" class="row-css-wrap">
<view v-if="rowCssIcon(row) === 'mail'" class="css-ico-mail css-ico-mail--row" :style="rowCssBorderStyle(row)">
<view class="css-ico-mail-flap" :style="rowCssBorderStyle(row)"></view>
</view>
<view v-else class="css-ico-web css-ico-web--row" :style="rowCssBorderStyle(row)">
<view class="css-ico-web-h" :style="rowCssFillStyle(row)"></view>
<view class="css-ico-web-v" :style="rowCssBorderStyle(row)"></view>
</view>
</view>
<text v-else-if="rowIcon(row)" class="row-icon iconfont" :class="rowIcon(row)" :style="labelStyle"></text>
<text v-else class="row-label" :style="rowLabelStyle">{{ row.label }}</text>
<text class="row-value" :style="valueStyle">{{ row.value }}</text>
<text v-if="showCopyBtn && row.value" class="row-copy" :style="copyBtnStyle" @tap.stop="onCopyRow(row)">复制</text>
</view>
<view v-if="hasMoreRows" class="row-toggle" hover-class="btn-press" @tap="expanded = !expanded">
<text class="toggle-text" :style="toggleStyle">{{ expanded ? '收起资料' : '展开全部资料(' + hiddenCount + ')' }}</text>
@@ -82,13 +144,26 @@
<!-- 二维码·资料行下方(rows 档, 默认; 点击预览名片); mini-qr-center: 块级居中 -->
<view v-if="showQrRows" class="mini-qr mini-qr-center" :style="qrBlockStyle" @tap.stop="onQrTap">
<image class="qr-img" :style="qrImgStyle" :src="$util.img(card.qr_url)" mode="aspectFit" />
<image class="qr-img" :style="qrImgStyle" :src="$util.img(qrSrc)" mode="aspectFit" />
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
</view>
<!-- 快捷操作(芯片; 2026-10-22 参考图定稿: 全部实心主题色胶囊(「打电话」同款);
尺寸/圆角走 .act-chip CSS 类, 内联只给背景色 —— 避免内联样式锁死导致 CSS 改动不生效) -->
<view v-if="showActs && acts.length" class="mini-acts" :style="cornerClearStyle">
<!-- 快捷操作·图标宫格档(grid): 彩色浅底图标 + 下方文字 -->
<view v-if="!actsAbove && showActs && acts.length && actsGrid" class="mini-acts-grid" :style="[actsBlockStyle, cornerClearStyle]">
<view v-for="(act, i) in acts" :key="i" class="act-grid-item" :style="{ width: gridItemWidth }" hover-class="btn-press" @tap.stop="onAct(act)">
<view class="act-grid-icon" :style="actGridIconStyle(act)">
<!-- 自定义图标图片(2.18 媒体库, 优先) / SVG 线稿(2.18.1 与 wap detail 同源) / 首字兜底 -->
<image v-if="actImgOf(act)" class="act-grid-img" :src="$util.img(actImgOf(act))" mode="aspectFit" />
<view v-else-if="actLibOf(act)" class="act-grid-svg" :style="actLibStyle(act)"></view>
<view v-else-if="actSvgOf(act)" class="act-grid-svg" :style="actSvgStyle(act)"></view>
<text v-else class="act-grid-initial" :style="actGridGlyphStyle(act)">{{ actInitialOf(act) }}</text>
</view>
<text class="act-grid-label" :style="actGridLabelStyle(act)">{{ act.label }}</text>
</view>
</view>
<!-- 快捷操作·胶囊芯片档(chips, 历史默认; 最小触达 72rpx; 首个为主动作) -->
<view v-if="!actsAbove && showActs && acts.length && !actsGrid" class="mini-acts" :style="[actsBlockStyle, cornerClearStyle]">
<view v-for="(act, i) in acts" :key="i" class="act-chip"
:style="actStyle" hover-class="btn-press" @tap.stop="onAct(act)">
<text class="act-text" :style="actTextStyle">{{ act.label }}</text>
@@ -158,7 +233,12 @@
// 消费名片自身设计资产: cover 封面 / logo / theme_color / 渐变末色与角度 / card_title
import {
CardTokens, px2rpx, flagOn, num, initialOf, avatarBg, themeGradient, darkenColor, visibleRows,
rowIconFor, rowActOf, normalizeQrPosition, normalizeQrStyle, resolveQrSize, qrTransform, QR_SIZES,
rowIconFor, rowCssIconFor, rowActOf, normalizeQrPosition, normalizeQrStyle, resolveQrSize, qrTransform, QR_SIZES,
normalizeHeadStyle, normalizeActsLayout, normalizeActGridColorMode, normalizeActsPosition, actIconCfgOf,
WAP_LINE_ICONS, svgLineIconBg,
ACT_GRID_PRESETS, ACT_GRID_PRESET_FALLBACK, hexToRgba,
// 2.19: 显隐/文案/自定义动作归一化 + 素材图标库(与后端 CardIconLibrary 同源)
normalizeActVis, normalizeActLabels, normalizeCustomActs, normalizeActOrder, libIconBg,
} from './diy-card-common.js';
export default {
@@ -203,9 +283,17 @@ export default {
// 资料行总开关(面板「显示资料行」); 历史配置无此字段时默认显示, 兼容 1.0
return flagOn(this.c.showRows, true);
},
/** 二维码总条件: 开关 + 名片有二维码地址 */
/**
* 二维码总条件: 开关 + 有码源(自定义图 qrImage 优先, 回落名片码 qr_url)
*/
showQrBase() {
return flagOn(this.c.showQr, false) && !!(this.card && this.card.qr_url);
return flagOn(this.c.showQr, false) && !!this.qrSrc;
},
/** 二维码码源(2.18): 用户自定义图片(媒体库)优先, 空=名片自动生成的码 */
qrSrc() {
const custom = String(this.c.qrImage || '').trim();
if (custom) return custom;
return (this.card && this.card.qr_url) || '';
},
qrPos() {
return normalizeQrPosition(this.c.qrPosition);
@@ -237,7 +325,8 @@ export default {
},
/**
* corner 档资料行/操作区让位量: 按二维码实际占高反算
* (浮层 top 20 + 块高[尺寸+样式衬底+tip] - 头部 104 + 间隙 10), 防遮挡;
* (浮层 top 20 + 块高[尺寸+样式衬底+tip] - 头部实际高 + 间隙 10), 防遮挡;
* 头部实际高: plain=104 / theme 档=32+104+28=164(上下 padding 增高, 2.17);
* 自定义大尺寸/关 tip 时自动跟随(不用 calc, 纯数值)
*/
cornerClearStyle() {
@@ -246,7 +335,8 @@ export default {
const pad = this.qrStyle === 'plain' ? 0 : (this.qrStyle === 'gradient' ? 6 : 10) * 2;
const tip = this.showQrTip ? 42 : 0;
const total = 20 + size + pad + tip;
return { marginTop: Math.max(24, total - 104 + 10) + 'rpx' };
const headHeight = this.headTheme ? 164 : 104;
return { marginTop: Math.max(24, total - headHeight + 10) + 'rpx' };
},
/**
* 二维码容器样式: 只管尺寸(自定义>档位自动) + 微调位移(transform, WXSS 支持)。
@@ -305,6 +395,37 @@ export default {
borderRadius: px2rpx(CardTokens.radius.pill) + 'rpx',
});
},
/** 头部形态(2.17): theme=渐变整块头部(历史配置无此字段时为 plain, 兼容 1.0) */
headTheme() {
return normalizeHeadStyle(this.c.headStyle) === 'theme';
},
/**
* theme 档头部块: 渐变通铺卡边(负 margin 抵消 wrapStyle 的 padding, WXSS 不用 calc)
* 内部左右 padding = 卡片原 padding, 保证头像与下方资料行左缘对齐
*/
headThemeStyle() {
const pad = px2rpx(num(this.c.padding, CardTokens.space.lg));
return this.$util.objToStyle({
background: themeGradient(this.card),
margin: '-' + pad + 'rpx -' + pad + 'rpx 0',
padding: '32rpx ' + pad + 'rpx 28rpx',
});
},
/** 资料区标题(2.17): 默认关(历史配置无此字段时不出标题) */
rowsTitleShow() {
return flagOn(this.c.showRowsTitle, false);
},
rowsTitle() {
return String(this.c.rowsTitle || '联系资料');
},
/** 快捷操作布局(2.17): 默认 chips 胶囊(历史配置无此字段时不变) */
actsGrid() {
return normalizeActsLayout(this.c.actsLayout) === 'grid';
},
/** 复制按钮(2.17): 默认关; 手机等资料行右侧浅底小按钮, 点击复制原文 */
showCopyBtn() {
return flagOn(this.c.showCopyBtn, false);
},
qrTip() {
return this.c.qrTip || '';
},
@@ -327,8 +448,56 @@ export default {
hiddenCount() {
return this.rowsInfo.rest.length;
},
/**
* 快捷操作(2.19 重构): 服务端 acts(资料驱动) + 组件配置合成
* 1. actVis 显隐过滤(缺省全显, 历史形态零变化)
* 2. actLabels 文案覆盖(空/缺 = 服务端 label)
* 3. customActs 自定义动作尾接(vis=0 隐藏)
* 每项合并 cfgImg/cfgKey/cfgColor/cfgBg 四字段 —— 图标渲染统一走
* 「媒体库图 > 素材库图标 > SVG 线稿 > 首字」优先级, 免去方法内重复取配置
*/
acts() {
return this.card && Array.isArray(this.card.acts) ? this.card.acts : [];
const raw = this.card && Array.isArray(this.card.acts) ? this.card.acts : [];
const vis = normalizeActVis(this.c.actVis);
const labels = normalizeActLabels(this.c.actLabels);
const icons = this.c.actIcons && typeof this.c.actIcons === 'object' ? this.c.actIcons : {};
const order = normalizeActOrder(this.c.actOrder);
const merge = (a) => {
const t = String(a.type);
const cfg = icons[t] && typeof icons[t] === 'object' ? icons[t] : {};
return Object.assign({}, a, {
label: labels[t] || a.label,
cfgImg: String(cfg.img || '').trim(),
cfgKey: String(cfg.iconKey || '').trim(),
cfgColor: String(cfg.color || '').trim(),
cfgBg: String(cfg.bg || '').trim(),
});
};
// 2.19.1: 按 actOrder 次序输出默认动作; 后端 quickActs 扩展类型(order 外)尾接兜底
const byType = {};
raw.forEach(a => {
if (a && a.type) byType[String(a.type)] = a;
});
const out = [];
order.forEach(t => {
if (byType[t] && vis[t] !== 0) out.push(merge(byType[t]));
});
raw.forEach(a => {
if (!a || !a.type) return;
const t = String(a.type);
if (order.indexOf(t) >= 0 || vis[t] === 0) return;
out.push(merge(a));
});
normalizeCustomActs(this.c.customActs).forEach(ca => {
if (ca.vis === 0) return;
out.push(Object.assign({}, ca, {
cfgImg: ca.img,
cfgKey: ca.iconKey,
cfgColor: ca.color,
cfgBg: ca.bg,
}));
});
return out;
},
isEnterprise() {
return this.card && this.card.card_type === 'enterprise';
@@ -356,16 +525,73 @@ export default {
return this.$util.objToStyle({ background: avatarBg(this.card) });
},
nameStyle() {
return this.$util.objToStyle({ color: this.c.nameColor || CardTokens.color.text });
// theme 渐变头部档: 深底上默认白字(headNameColor 可覆盖); plain 档沿用历史配色
const autoColor = this.headTheme ? (this.c.headNameColor || '#FFFFFF') : (this.c.nameColor || CardTokens.color.text);
return this.$util.objToStyle({
color: autoColor,
fontSize: px2rpx(num(this.c.nameSize, 17)) + 'rpx',
});
},
/** 名片头衔(设计器 card_title): 默认 13px = 26rpx, 与副行 24rpx 区分 */
cardTitleStyle() {
const autoColor = this.headTheme ? (this.c.headSubColor || 'rgba(255,255,255,0.9)') : (this.c.infoColor || CardTokens.color.textSub);
return this.$util.objToStyle({
color: autoColor,
fontSize: px2rpx(num(this.c.cardTitleSize, 13)) + 'rpx',
});
},
/** 副行(公司/职位)与空态提示共用: 默认 12px = 24rpx */
subTextStyle() {
return this.$util.objToStyle({ color: this.c.infoColor || CardTokens.color.textSub });
// theme 档副行默认白 85% 透明度(渐变深底可读); plain 档沿用历史配色
const autoColor = this.headTheme ? (this.c.headSubColor || 'rgba(255,255,255,0.85)') : (this.c.infoColor || CardTokens.color.textSub);
return this.$util.objToStyle({
color: autoColor,
fontSize: px2rpx(num(this.c.subSize, 12)) + 'rpx',
});
},
/** labelColor 纯色(资料行图标/二维码提示用, 不带字号) */
labelStyle() {
return this.$util.objToStyle({ color: this.c.labelColor || CardTokens.color.textLabel });
},
/** 资料行文字标签: 默认 12px = 24rpx */
rowLabelStyle() {
return this.$util.objToStyle({
color: this.c.labelColor || CardTokens.color.textLabel,
fontSize: px2rpx(num(this.c.labelSize, 12)) + 'rpx',
});
},
valueStyle() {
return this.$util.objToStyle({ color: this.c.infoColor || CardTokens.color.textSub });
return this.$util.objToStyle({
color: this.c.infoColor || CardTokens.color.textSub,
fontSize: px2rpx(num(this.c.valueSize, 12)) + 'rpx',
});
},
/** 空态提示: 固定历史配色(不随 theme 头部变白 —— 空态无渐变头) */
emptyTipStyle() {
return this.$util.objToStyle({
color: this.c.infoColor || CardTokens.color.textSub,
fontSize: px2rpx(num(this.c.subSize, 12)) + 'rpx',
});
},
/** 资料区标题(2.17): 主题色竖条 + 主文字色标题 */
rowsTitleBarStyle() {
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
return this.$util.objToStyle({ background: theme });
},
rowsTitleStyle() {
return this.$util.objToStyle({
color: this.c.nameColor || CardTokens.color.text,
fontSize: px2rpx(14) + 'rpx',
fontWeight: '600',
});
},
/** 复制按钮: 主题浅底 + 主题色字(hexToRgba 动态派生, WXSS 无 color-mix) */
copyBtnStyle() {
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
return this.$util.objToStyle({
background: hexToRgba(theme, 0.12) || '#F2F3F5',
color: theme,
});
},
badgeStyle() {
// 企业徽标固定红底(参考图 2026-10-22 口径), 与头像底色解耦
@@ -380,12 +606,81 @@ export default {
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
return this.$util.objToStyle({
background: '#FFFFFF',
height: px2rpx(CardTokens.touch.chipHeight) + 'rpx',
padding: '0 ' + px2rpx(CardTokens.space.xl) + 'rpx',
borderRadius: px2rpx(CardTokens.radius.pill) + 'rpx',
});
},
actPrimaryTextStyle() {
return this.$util.objToStyle({
color: '#FFFFFF',
fontSize: px2rpx(num(this.c.actSize, 12)) + 'rpx',
});
},
/** 首动作(索引 0)实心底: 主题色 + 胶囊尺寸(与 actStyle/actGhostStyle 同口径) */
actPrimaryStyle() {
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
return this.$util.objToStyle({
background: theme,
height: px2rpx(CardTokens.touch.chipHeight) + 'rpx',
padding: '0 ' + px2rpx(CardTokens.space.xl) + 'rpx',
borderRadius: px2rpx(CardTokens.radius.pill) + 'rpx',
});
},
actGhostStyle() {
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
return this.$util.objToStyle({
background: 'transparent',
border: '2rpx solid ' + theme,
});
},
actTextStyle() {
return this.$util.objToStyle({ color: (this.card && this.card.theme_color) || CardTokens.color.primary });
},
actGhostTextStyle() {
return this.$util.objToStyle({
color: (this.card && this.card.theme_color) || CardTokens.color.primary,
fontSize: px2rpx(num(this.c.actSize, 12)) + 'rpx',
});
},
/** 发送名片按钮文字: 默认 13px = 26rpx */
detailTextStyle() {
return this.$util.objToStyle({
color: '#FFFFFF',
fontSize: px2rpx(num(this.c.btnSize, 13)) + 'rpx',
});
},
/** 宫格配色模式(2.17): preset 多彩预设 / theme 主题色派生 */
gridColorMode() {
return normalizeActGridColorMode(this.c.actGridColorMode);
},
/** 宫格列宽: 5 个动作一行(存通讯录入列后), 4 个及以下仍四列均分 */
gridItemWidth() {
return this.acts.length > 4 ? '20%' : '25%';
},
/** 快捷操作区位置(2.18): above=资料区上方(below 历史默认零变化) */
actsAbove() {
return normalizeActsPosition(this.c.actsPosition) === 'above';
},
/**
* 快捷操作区独立块样式(2.18): 背景/圆角/内边距, 全为默认(历史形态)时输出空串零变化
*/
actsBlockStyle() {
const c = this.c;
const bg = String(c.actsBlockBg || '').trim();
const radius = num(c.actsBlockRadius, 0);
const pad = num(c.actsBlockPad, 0);
const style = {};
if (bg) style.background = bg;
if (radius > 0) style.borderRadius = px2rpx(radius) + 'rpx';
if (pad > 0) style.padding = px2rpx(pad) + 'rpx';
return this.$util.objToStyle(style);
},
/** 资料行让位: 操作区在上方时由操作区让位, 资料行不再重复位移 */
rowsClearStyle() {
return this.actsAbove ? {} : this.cornerClearStyle;
},
},
detailBtnStyle() {
// 发送名片大按钮: 纯色主题实心(2026-10-22 参考图定稿, 不再用渐变);
// 高度 104rpx(用户反馈: 比触达标准 80rpx 加高一档, 参考图观感)
@@ -412,6 +707,20 @@ export default {
rowIcon(row) {
return flagOn(this.c.showRowIcons, true) ? rowIconFor(row && row.field) : '';
},
/** 资料行 CSS 描边图标(2.17.1): 邮箱=信封 / 网站=线框地球, 同受行图标开关 */
rowCssIcon(row) {
return flagOn(this.c.showRowIcons, true) ? rowCssIconFor(row && row.field) : '';
},
/** 资料行 CSS 图标颜色: 跟随 labelColor(与 iconfont 行图标同色口径) */
rowCssColor() {
return this.c.labelColor || CardTokens.color.textLabel;
},
rowCssBorderStyle() {
return this.$util.objToStyle({ borderColor: this.rowCssColor() });
},
rowCssFillStyle() {
return this.$util.objToStyle({ background: this.rowCssColor() });
},
/** 资料行是否可点: 行点击开关 + 服务端 href 派生得出动作 */
rowTapable(row) {
return flagOn(this.c.rowTap, true) && !!rowActOf(row);
@@ -423,7 +732,6 @@ export default {
onAct(act) {
this.$emit('act', act);
},
/* ---------- 分享名片抽屉(「发送名片」弹出; 版式/逻辑与精选名片 show 完全同款) ---------- */
openCardShare() {
this.showShare = true;
@@ -490,6 +798,78 @@ export default {
onSend() {
this.$emit('send', this.card);
},
/** 每按钮自定义图标图片(2.18 媒体库, 优先级最高; acts 已合并 cfgImg) */
actImgOf(act) {
return String((act && act.cfgImg) || '').trim() || String(actIconCfgOf(this.c.actIcons, act && act.type).img || '').trim();
},
/**
* 素材库图标(2.19): acts 已合并 cfgKey(iconKey), 库缺 key 返回空串回落线稿档
*/
actLibOf(act) {
return String((act && act.cfgKey) || '').trim();
},
/** 素材库图标背景(实时配色: color 空回落 actIconColor 四级口径) */
actLibStyle(act) {
return this.$util.objToStyle({ backgroundImage: libIconBg(this.actLibOf(act), this.actIconColor(act)) });
},
/** 宫格 SVG 线稿(2.18.1): 与 wap detail 同源, 素材库/媒体库优先时不再进入 */
actSvgOf(act) {
return !this.actImgOf(act) && !this.actLibOf(act) && !!WAP_LINE_ICONS[String((act && act.type) || '')];
},
/**
* 图标前景色: act 合并的每按钮 color > detail 同源固定色(wx 微信绿/save 通讯录橙)
* > theme 档主题色 > preset 档预设深字
*/
actIconColor(act) {
const custom = String((act && act.cfgColor) || '').trim();
if (custom) return custom;
const ico = WAP_LINE_ICONS[String((act && act.type) || '')];
if (ico && ico.color) return ico.color;
if (this.gridColorMode === 'theme') {
return (this.card && this.card.theme_color) || CardTokens.color.primary;
}
const preset = ACT_GRID_PRESETS[(act && act.type) || ''] || ACT_GRID_PRESET_FALLBACK;
return preset.fg;
},
/** SVG 线稿背景(data URI, 颜色内嵌) */
actSvgStyle(act) {
return this.$util.objToStyle({ backgroundImage: svgLineIconBg(act && act.type, this.actIconColor(act)) });
},
actInitialOf(act) {
return String((act && act.label) || '·').charAt(0);
},
/** 宫格配色(每按钮 bg 覆盖 / preset 多彩预设 / theme 主题渐变) —— 带参, 走 methods */
actGridIconStyle(act) {
// 每按钮自定义底色优先(2.18), 空回落 preset/theme 档(acts 已合并 cfgBg)
const cfgBg = String((act && act.cfgBg) || '').trim() || String(actIconCfgOf(this.c.actIcons, act && act.type).bg || '').trim();
if (cfgBg) return this.$util.objToStyle({ background: cfgBg });
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
if (this.gridColorMode === 'theme') {
// detail 同款浅主题渐变底(145deg, 12%→20% 主题色混白)
return this.$util.objToStyle({
background: 'linear-gradient(145deg, ' + (hexToRgba(theme, 0.12) || '#F2F3F5') + ', ' + (hexToRgba(theme, 0.2) || '#F2F3F5') + ')',
});
}
const preset = ACT_GRID_PRESETS[(act && act.type) || ''] || ACT_GRID_PRESET_FALLBACK;
return this.$util.objToStyle({ background: preset.bg });
},
/** 首字兜底字色(未知类型), 与每按钮 color/固定色同口径 */
actGridGlyphStyle(act) {
return this.$util.objToStyle({ color: this.actIconColor(act) });
},
actGridLabelStyle() {
return this.$util.objToStyle({
color: this.c.labelColor || CardTokens.color.textSub,
fontSize: px2rpx(num(this.c.actSize, 12)) + 'rpx',
});
},
/** 复制按钮: 复用 row-act 的 copy 分支(rowTap 关闭时仍可独立复制) */
onCopyRow(row) {
this.$emit('row-act', { type: 'copy', value: row.value });
},
onDetail() {
this.$emit('detail', this.card);
},
onEmptyAction() {
this.$emit('empty-action');
},
@@ -602,6 +982,123 @@ export default {
}
}
/* theme 渐变头部档: 白描边白底字头像更贴合深底(仅 theme 档, plain 零变化) */
.mini-headtheme & {
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.10);
}
}
/* 头部·主题渐变整块档(2.17): 通铺卡边 + 装饰圆; 颜色固定白透明度, 任意主题色可见
圆角只给底部 —— 顶部贴卡边, 避免与卡片圆角叠加出双圆角 */
.mini-headtheme {
position: relative;
z-index: 2;
overflow: hidden;
border-radius: 0 0 16rpx 16rpx;
.headtheme-decor {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.12);
pointer-events: none;
&--big {
width: 240rpx;
height: 240rpx;
right: -60rpx;
top: -80rpx;
}
&--small {
width: 120rpx;
height: 120rpx;
left: -40rpx;
bottom: -50rpx;
background: rgba(255, 255, 255, 0.08);
}
}
}
/* 资料区标题(2.17): 主题色竖条(尺寸内联) + 600 字重标题 */
.mini-rows-title {
display: flex;
align-items: center;
margin-bottom: 16rpx;
.rows-title-bar {
flex-shrink: 0;
width: 8rpx;
height: 28rpx;
border-radius: 4rpx;
margin-right: 12rpx;
}
}
/* 复制按钮(2.17): 浅底主题色小按钮(色内联), 不挤压资料值换行 */
.row-copy {
flex-shrink: 0;
margin-left: 16rpx;
padding: 4rpx 20rpx;
border-radius: 999rpx;
font-size: 22rpx;
line-height: 1.4;
}
/* 快捷操作·图标宫格档(2.17): 均分多列, 图标浅底块 88rpx
列宽由内联 gridItemWidth 动态给出(5 动作 20% / 4 及以下 25%), 此处为兜底 */
.mini-acts-grid {
position: relative;
z-index: 2;
margin-top: 24rpx;
display: flex;
flex-wrap: wrap;
.act-grid-item {
width: 25%;
box-sizing: border-box;
padding: 8rpx 0;
display: flex;
flex-direction: column;
align-items: center;
.act-grid-icon {
width: 88rpx;
height: 88rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
/* SVG 线稿图标(2.18.1): data URI 背景呈现, 与 wap detail 同源 */
.act-grid-svg {
width: 44rpx;
height: 44rpx;
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
/* 自定义图标图片(2.18): 铺满图标块, 圆角继承 */
.act-grid-img {
width: 100%;
height: 100%;
display: block;
border-radius: inherit;
}
.act-grid-initial {
font-size: 36rpx;
font-weight: 600;
}
}
.act-grid-label {
margin-top: 12rpx;
line-height: 1.4;
}
}
}
.mini-head-info {
flex: 1;
min-width: 0;
@@ -658,6 +1155,46 @@ export default {
display: flex;
line-height: 2;
/* CSS 描边图标容器(2.17.1): 与 iconfont 图标列/文字标签列同宽 96rpx, 值列左缘对齐 */
.row-css-wrap {
flex-shrink: 0;
width: 96rpx;
display: flex;
align-items: center;
justify-content: center;
align-self: center;
}
/* 行内缩小版信封/地球(宫格同构图形, 边框色内联) */
.css-ico-mail--row {
width: 30rpx;
height: 22rpx;
border-width: 3rpx;
border-radius: 4rpx;
.css-ico-mail-flap {
width: 10rpx;
height: 10rpx;
border-width: 3rpx;
top: -2rpx;
}
}
.css-ico-web--row {
width: 26rpx;
height: 26rpx;
border-width: 3rpx;
.css-ico-web-h {
height: 3rpx;
}
.css-ico-web-v {
width: 10rpx;
border-width: 3rpx;
}
}
.row-label {
flex-shrink: 0;
width: 96rpx;
@@ -897,7 +1434,6 @@ export default {
height: 28rpx;
}
}
}
/* 入场动效(逐项延迟由父组件传 animation-delay) */
.fade-up {
@@ -910,6 +1446,63 @@ export default {
transform: scale(0.98);
}
/* CSS 描边图标(2.17.1): iconfont 无邮件/网站字形, 纯 view 边框绘制,
颜色由内联 style 跟随 preset/theme 配色档(H5+小程序通用)。
顶层定义 —— 宫格(88rpx 图标块内)与资料行(--row 缩小版)共用同一套图形 */
.css-ico-mail {
position: relative;
width: 44rpx;
height: 32rpx;
border: 4rpx solid #B0B4BC;
border-radius: 6rpx;
box-sizing: border-box;
/* 封面折角: 只留左+下边的方块旋转 -45° 成 V 形折线 */
.css-ico-mail-flap {
position: absolute;
left: 50%;
top: -2rpx;
width: 16rpx;
height: 16rpx;
border-left: 4rpx solid #B0B4BC;
border-bottom: 4rpx solid #B0B4BC;
transform: translateX(-50%) rotate(-45deg);
}
}
.css-ico-web {
position: relative;
width: 40rpx;
height: 40rpx;
border: 4rpx solid #B0B4BC;
border-radius: 50%;
box-sizing: border-box;
overflow: hidden;
/* 赤道横线(色由内联 background 给出) */
.css-ico-web-h {
position: absolute;
left: 0;
top: 50%;
width: 100%;
height: 4rpx;
transform: translateY(-50%);
}
/* 经线椭圆: 左右双边框 + 椭圆圆角 */
.css-ico-web-v {
position: absolute;
left: 50%;
top: -2rpx;
bottom: -2rpx;
width: 14rpx;
transform: translateX(-50%);
border-left: 4rpx solid #B0B4BC;
border-right: 4rpx solid #B0B4BC;
border-radius: 50%;
}
}
@keyframes sk-shimmer {
0% {
background-position: -200rpx 0;

View File

@@ -66,12 +66,20 @@ export default {
miniConfig() {
const v = this.value || {};
return {
bgColor: v.bgColor,
nameColor: v.nameColor,
infoColor: v.infoColor,
labelColor: v.labelColor,
borderRadius: v.borderRadius,
padding: v.padding,
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,
@@ -95,6 +103,27 @@ export default {
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,
actLabels: v.actLabels,
actOrder: v.actOrder,
customActs: v.customActs,
};
},
boxStyle() {
@@ -176,7 +205,13 @@ export default {
this.$util.redirectTo('/pages_tool/card/list');
},
doAct(act) {
if (!act || !act.value) return;
if (!act) return;
// save(存通讯录): value 为空, 字段由本组件从 card 组装(白名单口径)
if (act.type === 'save') {
this.goSaveContact();
return;
}
if (!act.value) return;
if (act.type === 'tel') {
uni.makePhoneCall({ phoneNumber: act.value, fail: () => {} });
} else if (act.type === 'wx') {
@@ -222,6 +257,37 @@ export default {
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>

View File

@@ -280,6 +280,7 @@ export default {
height: this.qrSide + 'rpx',
// 外观衬底只包二维码图(边距不撑大总尺寸), 提示文字在衬底外
boxSizing: 'border-box',
};
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
if (this.qrStyle === 'card') {
@@ -394,7 +395,13 @@ export default {
if (act) this.doAct(act);
},
doAct(act) {
if (!act || !act.value) return;
if (!act) return;
// save(存通讯录): value 为空, 字段由本组件从 card 组装(白名单口径)
if (act.type === 'save') {
this.goSaveContact();
return;
}
if (!act.value) return;
if (act.type === 'tel') {
uni.makePhoneCall({ phoneNumber: act.value, fail: () => {} });
} else if (act.type === 'wx') {
@@ -441,6 +448,7 @@ export default {
this.$util.redirectTo('/pages_tool/card/detail', { card_id: card.card_id });
},
/* ---------- 分享名片抽屉(点二维码弹出, 版式/文案对齐 pages_tool/card/detail.vue) ---------- */
openCardShare() {
this.showShare = true;
@@ -500,6 +508,39 @@ export default {
const src = this.$util.img(url);
uni.previewImage({ urls: [src], current: src });
},
/**
* 存通讯录(2.17.1): 字段全部从 card 组装 —— rows 已过 shouldShow
* 白名单(商家隐藏的字段不下发也不落通讯录, 隐私口径与后端一致),
* name/company/job_title 为公开头部字段直接取。
* 键名与 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>