chore:新增精选名片组件,样式

This commit is contained in:
2026-10-21 15:53:15 +08:00
parent d188737a6c
commit 37cd3c2f30
6 changed files with 458 additions and 88 deletions

View File

@@ -39,8 +39,8 @@
<!-- Logo(可选, 右上; corner 档二维码占同一位, 自动避让) -->
<image v-if="showLogo && card.logo" class="mini-logo" :src="$util.img(card.logo)" mode="aspectFit" />
<!-- 二维码·右上角浮层(corner 档: 外观由 qrStyle 决定, 资料行自动让位) -->
<view v-if="showQrCorner" class="mini-qr mini-qr-corner" :style="qrBlockStyle">
<!-- 二维码·右上角浮层(corner 档: 外观由 qrStyle 决定, 资料行自动让位; 点击预览名片) -->
<view v-if="showQrCorner" class="mini-qr mini-qr-corner" :style="qrBlockStyle" @tap.stop="onQrTap">
<image class="qr-img" :src="$util.img(card.qr_url)" mode="aspectFit" />
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
</view>
@@ -58,8 +58,8 @@
<text v-if="card.card_title" class="mini-card-title" :style="subTextStyle">{{ 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">
<!-- 二维码·头部右侧(head 档: 头像|信息|二维码 三段 flex, 零遮挡; 点击预览名片) -->
<view v-if="showQrHead" class="mini-qr mini-qr-head" :style="qrBlockStyle" @tap.stop="onQrTap">
<image class="qr-img" :src="$util.img(card.qr_url)" mode="aspectFit" />
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
</view>
@@ -78,8 +78,8 @@
</view>
</view>
<!-- 二维码·资料行下方(rows 档, 默认) -->
<view v-if="showQrRows" class="mini-qr" :style="qrBlockStyle">
<!-- 二维码·资料行下方(rows 档, 默认; 点击预览名片) -->
<view v-if="showQrRows" class="mini-qr" :style="qrBlockStyle" @tap.stop="onQrTap">
<image class="qr-img" :src="$util.img(card.qr_url)" mode="aspectFit" />
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
</view>
@@ -92,8 +92,8 @@
</view>
</view>
<!-- 二维码·主按钮上方(bottom 档) -->
<view v-if="showQrBottom" class="mini-qr" :style="qrBlockStyle">
<!-- 二维码·主按钮上方(bottom 档; 点击预览名片) -->
<view v-if="showQrBottom" class="mini-qr" :style="qrBlockStyle" @tap.stop="onQrTap">
<image class="qr-img" :src="$util.img(card.qr_url)" mode="aspectFit" />
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
</view>
@@ -102,6 +102,11 @@
<view v-if="showDetailBtn" class="mini-detail" :style="detailBtnStyle" hover-class="btn-press" @tap.stop="onDetail">
<text class="detail-text">{{ detailBtnText || '查看完整名片' }}</text>
</view>
<!-- 发名片(对齐名片详情页: 主题色大按钮; 点击跳原生转发中转页拉起微信转发面板) -->
<view v-if="showSendBtn" class="mini-send" :style="sendBtnStyle" hover-class="btn-press" @tap.stop="onSend">
<text class="send-text">{{ sendBtnText || '发名片' }}</text>
</view>
</view>
</view>
</template>
@@ -233,6 +238,20 @@ export default {
showDetailBtn() {
return flagOn(this.c.showDetailBtn, true);
},
/** 发名片按钮(名片详情页同款主按钮; 默认关, 由 show 组件打开) */
showSendBtn() {
return flagOn(this.c.showSendBtn, false);
},
sendBtnText() {
return this.c.sendBtnText || '发名片';
},
sendBtnStyle() {
return this.$util.objToStyle({
background: themeGradient(this.card),
height: px2rpx(CardTokens.touch.btnHeight) + 'rpx',
borderRadius: px2rpx(CardTokens.radius.pill) + 'rpx',
});
},
qrTip() {
return this.c.qrTip || '';
},
@@ -331,6 +350,8 @@ export default {
},
},
methods: {
// 模板无法访问模块导入, 需挂到实例(list.vue 同款写法); 头像缺图时显示姓氏首字
initialOf,
/** 资料行图标(开关关时退化为文字标签; field 无字形同样退化) */
rowIcon(row) {
return flagOn(this.c.showRowIcons, true) ? rowIconFor(row && row.field) : '';
@@ -349,6 +370,13 @@ export default {
onDetail() {
this.$emit('detail', this.card);
},
/** 二维码点击 → 预览名片(进详情页; qrTapPreview 开关控制, 默认开) */
onQrTap() {
if (flagOn(this.c.qrTapPreview, true)) this.$emit('detail', this.card);
},
onSend() {
this.$emit('send', this.card);
},
onEmptyAction() {
this.$emit('empty-action');
},
@@ -621,6 +649,22 @@ export default {
color: #ffffff;
}
}
/* 发名片主按钮(名片详情页 send-bar 同款观感) */
.mini-send {
position: relative;
z-index: 2;
margin-top: 24rpx;
display: flex;
align-items: center;
justify-content: center;
.send-text {
font-size: 30rpx;
font-weight: 600;
color: #ffffff;
}
}
}
/* 空态 */

View File

@@ -1,20 +1,70 @@
<template>
<view data-component-name="diy-card-show" class="diy-card-show">
<diy-card-mini
:card="card"
:config="miniConfig"
:loading="loading"
empty-tip="商家还未选择名片"
@act="doAct"
@row-act="doAct"
@detail="goDetail"
></diy-card-mini>
<!-- 骨架屏: 首屏加载/切换名片 -->
<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>
// 数字名片 · 精选名片(装修组件)
// 视觉基件 diy-card-mini(封面/渐变/Logo/主题色/折叠/芯片/二维码), 本文件只管数据
// 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: {
@@ -24,42 +74,79 @@ export default {
return {
card: null,
loading: false,
expanded: false,
};
},
computed: {
miniConfig() {
const v = this.value || {};
return {
bgColor: v.bgColor,
nameColor: v.nameColor,
infoColor: v.infoColor,
labelColor: v.labelColor,
borderRadius: v.borderRadius,
padding: v.padding,
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,
};
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: {
@@ -91,6 +178,18 @@ export default {
},
});
},
/** 资料行图标(字段无字形返回空串, 模板退化为文字标签) */
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') {
@@ -121,6 +220,26 @@ export default {
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>
@@ -128,5 +247,201 @@ export default {
<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>

View File

@@ -864,7 +864,6 @@ export default {
align-items: center;
justify-content: center;
border-radius: 16rpx;
background: rgba(0, 30, 80, 0.04);
.iconfont {
font-size: 110rpx;

View File

@@ -123,19 +123,26 @@ test('mini: corner 档 Logo 避让 + 资料行/操作区动态让位', () => {
});
// =============================================================
// 3. show/self 透传 + 接线
// 3. self miniConfig 透传 + 接线 / show 自绘资料行
// =============================================================
for (const [name, src] of [['show', showSrc], ['self', selfSrc]]) {
test(name + ': miniConfig 透传 A+B 三字段', () => {
assert.match(src, /showRowIcons:\s*v\.showRowIcons/);
assert.match(src, /rowTap:\s*v\.rowTap/);
assert.match(src, /qrPosition:\s*v\.qrPosition/);
});
test(name + ': 模板挂 @row-act="doAct"(行点击有接收方)', () => {
assert.match(src, /@row-act="doAct"/);
});
}
test('self: miniConfig 透传 A+B 三字段', () => {
assert.match(selfSrc, /showRowIcons:\s*v\.showRowIcons/);
assert.match(selfSrc, /rowTap:\s*v\.rowTap/);
assert.match(selfSrc, /qrPosition:\s*v\.qrPosition/);
});
test('self: 模板挂 @row-act="doAct"(行点击有接收方)', () => {
assert.match(selfSrc, /@row-act="doAct"/);
});
test('show: 自绘资料行(图标/文字标签退化 + href 派生动作 + doAct 接收)', () => {
assert.match(showSrc, /<text v-if="rowIcon\(row\)" class="row-icon iconfont" :class="rowIcon\(row\)"/);
assert.match(showSrc, /<text v-else class="row-label"/);
assert.match(showSrc, /rowTapable\(row\)\s*\{\s*return !!rowActOf\(row\)/);
assert.match(showSrc, /@tap="onRowTap\(row\)"/);
assert.match(showSrc, /onRowTap\(row\)\s*\{[\s\S]*?this\.doAct\(act\)/);
});
// =============================================================
// 反例(防恒真)

View File

@@ -9,7 +9,8 @@
* fallbackToPublicPage 落地 wap 公开页(保存通讯录/分享/二维码), 不是死路。
*
* 本测试把这条链路的源码契约固化为断言, 防回归:
* 1. show/self 两个单卡组件的 miniConfig 透传 showDetailBtn + detailBtnText
* 1. self 组件 miniConfig 透传 showDetailBtn + detailBtnText; show 组件自绘
* 二维码块直挂 goDetail(2026-10-21 换思路: show 不再复用 mini 基件)
* 2. 两个组件模板都挂 @detail="goDetail"(基件 emit('detail') 有接收方)
* 3. goDetail 跳 /pages_tool/card/detail 且带 card_id 参数
* 4. mini 基件: showDetailBtn flagOn 默认开(开箱即用) + 全宽按钮 emit detail
@@ -48,27 +49,22 @@ const miniSrc = stripComments(read('diy-card-mini.vue'));
const detailSrc = stripComments(readCard('detail.vue'));
// =============================================================
// 1. miniConfig 透传(面板能配 → C 端才读得到; 三层字段口径之一)
// 1. 详情页入口(self 走 mini 基件透传; show 自绘二维码直点进详情)
// =============================================================
test('show: miniConfig 透传 showDetailBtn 与 detailBtnText', () => {
assert.match(showSrc, /showDetailBtn:\s*v\.showDetailBtn/);
assert.match(showSrc, /detailBtnText:\s*v\.detailBtnText/);
});
test('self: miniConfig 透传 showDetailBtn 与 detailBtnText', () => {
assert.match(selfSrc, /showDetailBtn:\s*v\.showDetailBtn/);
assert.match(selfSrc, /detailBtnText:\s*v\.detailBtnText/);
});
test('show: 自绘二维码块挂 @tap.stop="goDetail(card)"(点「预览名片」进详情)', () => {
assert.match(showSrc, /@tap\.stop="goDetail\(card\)"/);
});
// =============================================================
// 2. 模板挂 @detail(基件 emit('detail') 的接收方, 断链则按钮变哑巴)
// =============================================================
test('show: 模板挂 @detail="goDetail"', () => {
assert.match(showSrc, /@detail="goDetail"/);
});
test('self: 模板挂 @detail="goDetail"', () => {
assert.match(selfSrc, /@detail="goDetail"/);
});

View File

@@ -36,8 +36,10 @@ const strip = s => s
const showSrc = strip(read('diy-card-show.vue'));
const selfSrc = strip(read('diy-card-self.vue'));
const miniSrc = strip(read('diy-card-mini.vue'));
const showPanel = strip(fs.readFileSync(path.join(PANEL_DIR, 'diy-card-show/design.html'), 'utf8'));
const selfPanel = strip(fs.readFileSync(path.join(PANEL_DIR, 'diy-card-self/design.html'), 'utf8'));
// 面板目录属 backend 仓库, 可能不在本工作区: 缺失时跳过面板断言(组件契约照常跑)
const hasPanels = fs.existsSync(path.join(PANEL_DIR, 'diy-card-self/design.html'));
const showPanel = hasPanels ? strip(fs.readFileSync(path.join(PANEL_DIR, 'diy-card-show/design.html'), 'utf8')) : '';
const selfPanel = hasPanels ? strip(fs.readFileSync(path.join(PANEL_DIR, 'diy-card-self/design.html'), 'utf8')) : '';
// =============================================================
// 1. common.js 纯函数(真值断言)
@@ -168,25 +170,32 @@ test('mini: 四个二维码块统一绑 qrBlockStyle(尺寸/位移/外观单一
});
// =============================================================
// 3. show/self 6 字段透传
// 3. self miniConfig 透传 / show 自绘版式二维码契约
// =============================================================
for (const [name, src] of [['show', showSrc], ['self', selfSrc]]) {
test(name + ': miniConfig 透传头像+二维码新六字段', () => {
assert.match(src, /showAvatar:\s*v\.showAvatar/);
assert.match(src, /qrStyle:\s*v\.qrStyle/);
assert.match(src, /qrSize:\s*v\.qrSize/);
assert.match(src, /qrOffsetX:\s*v\.qrOffsetX/);
assert.match(src, /qrOffsetY:\s*v\.qrOffsetY/);
assert.match(src, /qrTipShow:\s*v\.qrTipShow/);
});
}
test('self: miniConfig 透传头像+二维码新六字段', () => {
assert.match(selfSrc, /showAvatar:\s*v\.showAvatar/);
assert.match(selfSrc, /qrStyle:\s*v\.qrStyle/);
assert.match(selfSrc, /qrSize:\s*v\.qrSize/);
assert.match(selfSrc, /qrOffsetX:\s*v\.qrOffsetX/);
assert.match(selfSrc, /qrOffsetY:\s*v\.qrOffsetY/);
assert.match(selfSrc, /qrTipShow:\s*v\.qrTipShow/);
});
test('show: 自绘版式自带小二维码(预览名片 + 点击进详情, 尺寸面板可调)', () => {
assert.match(showSrc, /class="card-qr"/);
assert.match(showSrc, /\$util\.img\(card\.qr_url\)/);
assert.match(showSrc, /预览名片/);
assert.match(showSrc, /@tap\.stop="goDetail\(card\)"/);
// 尺寸仍消费面板 qrSize(默认 48px)
assert.match(showSrc, /qrSide\(\)\s*\{\s*return px2rpx\(num\(this\.c\.qrSize,\s*48\)\)/);
});
// =============================================================
// 4. design.html 面板: 控件绑定 + 预览 mock 消费
// =============================================================
for (const [name, panel] of [['show', showPanel], ['self', selfPanel]]) {
for (const [name, panel] of hasPanels ? [['show', showPanel], ['self', selfPanel]] : []) {
test(name + ' 面板: 头像开关 + 二维码新六字段有 v-model 绑定', () => {
assert.match(panel, /v-model="nc\.showAvatar"/);
assert.match(panel, /v-model="nc\.qrStyle"/);