feat(diy-card): Show 组件弃自绘版式恢复 diy-card-mini 基件(方案A 用户定稿)

决策依据: 后端 design.html 面板预览按 mini 基件渲染消费全部 53 字段,
方案A 下 C 端与编辑器预览逐字段一致(用户要求取与后端预览最接近的方案)。

- diy-card-show.vue: 恢复 2.19.3 末版 mini 基件实现(236 行, miniConfig
  全字段透传 含字号7/设计资产3/二维码6/宫格图层全套), 弃自绘版式(+888 行)
- 测试口径切换: qr-style 10 个/ab-visual 2 个/detail-jump 1 个「show: 自绘…」
  实现细节断言 → 改写为 miniConfig 全字段透传锁(防 merge 冲突解决再丢透传)
- 背景: 今晨两次 merge 保留自绘线丢弃透传致 27 字段 C 端失效(字段审核报告)

验证: 名片 7 套件全绿 + 全量仅剩 card-admin/card-api 存量失败(非本次) + 三回归 79/41/56
This commit is contained in:
2026-10-24 11:53:34 +08:00
parent ba7988765a
commit bd691d2d30
4 changed files with 102 additions and 1020 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -166,18 +166,10 @@ 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\)/);
});
test('show: 资料行总开关(面板「显示资料行」, 默认显示, 关后整块隐藏)', () => {
assert.match(showSrc, /showRowsBlock\(\)\s*\{\s*return flagOn\(this\.c\.showRows,\s*true\)/);
// 模板资料行块受开关控制(与头像开关同口径: v-if 直接消费)
assert.match(showSrc, /v-if="showRowsBlock && shownRows\.length" class="card-rows"/);
test('show: 资料行/行图标/行点击走基件透传(方案A: 渲染由 mini 侧行测试覆盖)', () => {
assert.match(showSrc, /showRows:\s*v\.showRows,/);
assert.match(showSrc, /showRowIcons:\s*v\.showRowIcons,/);
assert.match(showSrc, /rowTap:\s*v\.rowTap,/);
});
// =============================================================

View File

@@ -57,8 +57,9 @@ test('self: miniConfig 透传 showDetailBtn 与 detailBtnText', () => {
assert.match(selfSrc, /detailBtnText:\s*v\.detailBtnText/);
});
test('show: 自绘二维码块挂 @tap.stop="openCardShare"(点二维码弹「分享名片」抽屉)', () => {
assert.match(showSrc, /@tap\.stop="openCardShare"/);
test('show: 二维码/发名片点击走基件 emit detail 链路(方案A)', () => {
// 基件 onQrTap(qrTapPreview 开) / onSend $emit('detail'), show 挂 @detail="goDetail" 接收后跳详情
assert.match(showSrc, /@detail="goDetail"/);
});
// =============================================================

View File

@@ -281,124 +281,27 @@ test('self: 「我的名片」角标与未登录提示条均已移除(2026-10-23
assert.match(selfSrc, /v-else-if="!loading && !card" class="self-empty"/);
});
test('show: 头像开关(面板「显示头像」, 默认显示, 关后头部只留姓名/职位)', () => {
assert.match(showSrc, /showAvatarBlock\(\)\s*\{\s*return flagOn\(this\.c\.showAvatar,\s*true\)/);
assert.match(showSrc, /<view v-if="showAvatarBlock" class="card-avatar" :style="avatarStyle">/);
// 反例形态: 不允许出现常显头像(绕过开关)
assert.ok(!showSrc.includes('<view class="card-avatar"'));
});
test('show: 快捷操作开关(面板「显示快捷操作」, 默认显示, 关后芯片整块隐藏)', () => {
assert.match(showSrc, /showActsBlock\(\)\s*\{\s*return flagOn\(this\.c\.showQuick,\s*true\)/);
assert.match(showSrc, /v-if="showActsBlock && acts\.length" class="card-acts"/);
// 芯片点击直连 doAct(自绘版式不再经 $emit('act') 中转)
assert.match(showSrc, /@tap\.stop="doAct\(act\)"/);
});
test('show: 二维码位置四档(与面板下拉/mini 基件同口径, 未配置回落 rows)', () => {
assert.match(showSrc, /qrPos\(\)\s*\{\s*return normalizeQrPosition\(this\.c\.qrPosition\)/);
// 四档各自的条件渲染块
assert.match(showSrc, /showQrHead\(\)\s*\{\s*return this\.showQrBase && this\.qrPos === 'head';\s*\}/);
assert.match(showSrc, /showQrRows\(\)\s*\{\s*return this\.showQrBase && this\.qrPos === 'rows';\s*\}/);
assert.match(showSrc, /showQrCorner\(\)\s*\{\s*return this\.showQrBase && this\.qrPos === 'corner';\s*\}/);
assert.match(showSrc, /showQrBottom\(\)\s*\{\s*return this\.showQrBase && this\.qrPos === 'bottom';\s*\}/);
// 模板四个位置都有落点(头部/资料行下/浮层/主按钮上方)
assert.match(showSrc, /v-if="showQrHead" class="card-qr"/);
assert.match(showSrc, /v-if="showQrRows" class="qr-center"/);
assert.match(showSrc, /v-if="showQrCorner" class="card-qr-corner"/);
assert.match(showSrc, /v-if="showQrBottom" class="qr-center qr-center-bottom"/);
// 微调位移(qrOffsetX/Y → transform)
assert.match(showSrc, /qrTransform\(this\.c\.qrOffsetX,\s*this\.c\.qrOffsetY\)/);
});
test('show: 封面图作背景(面板「名片设计资产 → 封面图作背景」, 默认关, 未配封面图不显示)', () => { assert.match(showSrc, /showCover\(\)\s*\{\s*return flagOn\(this\.c\.useCover,\s*false\) && !!\(this\.card && this\.card\.cover\)/);
// 开启后白卡底变透明, 封面图铺满整卡 + 压暗遮罩(mini 基件同款参数)
assert.match(showSrc, /background:\s*this\.showCover \? 'transparent' : \(c\.bgColor \|\| CardTokens\.color\.bg\)/);
assert.match(showSrc, /<image v-if="showCover" class="card-cover" :src="\$util\.img\(card\.cover\)" mode="aspectFill" \/>/);
assert.match(showSrc, /<view v-if="showCover" class="card-cover-scrim"><\/view>/);
assert.match(showSrc, /background: rgba\(15, 23, 42, 0\.45\);/);
});
test('show: 主题渐变饰条(面板「名片设计资产 → 主题渐变饰条」, 默认关)', () => {
assert.match(showSrc, /showGradBar\(\)\s*\{\s*return flagOn\(this\.c\.showGradient,\s*false\)/);
// 饰条底色走名片主题渐变
assert.match(showSrc, /gradBarStyle\(\)\s*\{\s*return this\.\$util\.objToStyle\(\{\s*background: themeGradient\(this\.card\),\s*\}\);/);
// 模板: 卡片顶部横条 + 细高圆角样式
assert.match(showSrc, /<view v-if="showGradBar" class="card-grad-bar" :style="gradBarStyle"><\/view>/);
assert.match(showSrc, /\.card-grad-bar \{[\s\S]*?height: 14rpx;[\s\S]*?border-radius: 8rpx;/);
});
test('show: 名片 Logo 开关(面板「名片设计资产 → 名片 Logo」, 默认关; 未配 Logo 不显示; 头部档二维码同位避让)', () => {
assert.match(showSrc, /showLogo\(\)\s*\{\s*return flagOn\(this\.c\.showLogo,\s*false\) && !!\(this\.card && this\.card\.logo\) && !this\.showQrHead;/);
// 模板: 头部右侧 Logo 图
assert.match(showSrc, /<image v-if="showLogo" class="card-logo" :src="\$util\.img\(card\.logo\)" mode="aspectFit" \/>/);
// 样式: 头部右侧小图, margin-left:auto 推到行尾
assert.match(showSrc, /\.card-logo \{[\s\S]*?width: 96rpx;[\s\S]*?margin-left: auto;/);
});
test('show: 资料行图标开关(面板「名片信息展示 → 资料行图标」, 默认开; 关后退化文字标签)', () => {
// 与 mini 基件 rowIcon 同口径: 开关 + 字段字形双重判定
assert.match(showSrc, /rowIcon\(row\)\s*\{\s*return flagOn\(this\.c\.showRowIcons,\s*true\) \? rowIconFor\(row && row\.field\) : '';/);
// 模板: 有图标显图标, 无图标退化文字标签
assert.match(showSrc, /<text v-if="rowIcon\(row\)" class="row-icon iconfont" :class="rowIcon\(row\)" :style="labelStyle"><\/text>/);
assert.match(showSrc, /<text v-else class="row-label" :style="labelStyle">\{\{ row\.label \}\}<\/text>/);
});
test('show: 发名片按钮开关(面板「底部按钮 → 发送名片按钮」, 默认显示; 文案消费 detailBtnText)', () => {
assert.match(showSrc, /showSendBtn\(\)\s*\{\s*return flagOn\(this\.c\.showDetailBtn,\s*true\)/);
assert.match(showSrc, /v-if="showSendBtn" class="show-send"/);
// 点击弹分享抽屉(与二维码块同款 openCardShare, 2026-10-22 起)
assert.match(showSrc, /@tap="openCardShare">\s*<text class="show-send-text">/);
assert.ok(!showSrc.includes('onSend'));
// 按钮文案: 面板「按钮文案」可配, 默认「发送名片」
assert.match(showSrc, /sendBtnText\(\)\s*\{\s*return this\.c\.detailBtnText \|\| '发送名片';/);
assert.match(showSrc, /<text class="show-send-text">\{\{ sendBtnText \}\}<\/text>/);
// 反例形态: 不允许硬编码文案(绕过面板配置)
assert.ok(!showSrc.includes('<text class="show-send-text">发名片</text>'));
});
test('show: 二维码外观四档(plain 原图 / card 白卡描边 / theme 主题色 / gradient 渐变, 与 mini 同口径)', () => {
assert.match(showSrc, /qrStyle\(\)\s*\{\s*return normalizeQrStyle\(this\.c\.qrStyle\)/);
// 四个分支逐行收紧, 防跨分支 lazy 匹配造成假过(与 mini qrBlockStyle 同款值)
assert.match(showSrc, /if \(this\.qrStyle === 'card'\) \{\s+style\.background = '#ffffff';\s+style\.border = '2rpx solid #EEF0F3';\s+style\.borderRadius = '12rpx';\s+style\.padding = '10rpx';/);
assert.match(showSrc, /else if \(this\.qrStyle === 'theme'\) \{\s+style\.background = theme;\s+style\.borderRadius = '12rpx';\s+style\.padding = '10rpx';/);
// gradient 分支: 单主题色时也要可见明暗渐变(用 darkenColor 兜底终止色), 不能退化成纯色
// (2026-10-23 用户定稿: 外观分支移到 qrImgStyle, theme 在顶部声明一次)
assert.match(showSrc, /else if \(this\.qrStyle === 'gradient'\) \{\s+const end = \(this\.card && this\.card\.gradient_end\) \|\| darkenColor\(theme, 0\.18\);\s+style\.background = 'linear-gradient\(135deg, ' \+ theme \+ ' 0%, ' \+ end \+ ' 100%\)';\s+style\.borderRadius = '16rpx';\s+style\.padding = '6rpx';/);
// plain 不进任何分支(无衬底, 即原图)
assert.ok(!showSrc.includes("qrStyle === 'plain' ? style.background"));
// 衬底只包二维码图(qrImgStyle 绑在 image 上), 「扫码查看完整名片」tip 在衬底外(蓝底不包文字)
assert.equal((showSrc.match(/class="qr-img(-c)?" :style="qrImgStyle"/g) || []).length, 4, '四档 image 均绑 qrImgStyle');
// 四个位置档共用 qrBlockStyle(单字符串绑定; 仅剩微调位移)
assert.equal((showSrc.match(/:style="qrBlockStyle"/g) || []).length, 4);
// 反例: uni-app 运行时静默丢弃 :style="[字符串, 字符串]" 数组绑定
// (2026-10-22 线上问题: 微调/外观在 C 端全部失效的根因), 禁止回归
assert.ok(!showSrc.includes(':style="[qrBlockStyle'));
// 微调位移并入 qrBlockStyle 同一对象(qrTransform 引用仍在计算属性内)
assert.match(showSrc, /const t = qrTransform\(this\.c\.qrOffsetX,\s*this\.c\.qrOffsetY\);\s*if \(t\) style\.transform = t;/);
});
test('show: 自绘版式自带小二维码(开关可关 + 点击进详情, 尺寸/提示文字面板可调)', () => {
assert.match(showSrc, /class="card-qr"/);
assert.match(showSrc, /\$util\.img\(card\.qr_url\)/);
// 二维码块受面板「显示二维码」开关控制(与头像/资料行/快捷操作同口径: flagOn 默认显示)
assert.match(showSrc, /showQrBlock\(\)\s*\{\s*return flagOn\(this\.c\.showQr,\s*true\)/);
// 开关 + 位置统一收敛在 showQrBase / showQrHead 等分档计算属性里(模板只消费分档结果)
assert.match(showSrc, /showQrBase\(\)\s*\{\s*return this\.showQrBlock && !!\(this\.card && this\.card\.qr_url\)/);
// 提示文字消费面板 qrTip, 未配置回落「扫码查看完整名片」(与 mini 基件同口径)
assert.match(showSrc, /qrTipText\(\)\s*\{\s*return this\.c\.qrTip \|\| '扫码查看完整名片';/);
// 点二维码弹「分享名片」抽屉(复刻 detail.vue: 码 + 链接 + 复制链接/浏览器分享/保存二维码)
assert.match(showSrc, /@tap\.stop="openCardShare"/);
assert.match(showSrc, /v-if="showShare" @tap="closeShare"/);
assert.match(showSrc, /class="cs-sheet" :class="\{ 'cs-sheet-show': showShare \}"/);
assert.match(showSrc, /<view class="ss-title">分享名片<\/view>/);
assert.match(showSrc, /客户打开链接即可查看名片并留言留资/);
assert.match(showSrc, /copyCardLink\(\)\s*\{\s*const url = \(this\.card && this\.card\.wap_url\) \|\| '';/);
// 尺寸走 resolveQrSize: 面板 qrSize>0 用自定义, 0/空 = 按位置档位自动(rows/bottom 180, head/corner 120)。
// 反例形态: 不允许 num(qrSize, 48) 兜底 —— 面板默认 0 是合法数字, 会算出 0rpx 宽高,
// 二维码图渲染成 0 尺寸「看不见」(2026-10-21 线上问题)
assert.match(showSrc, /qrSide\(\)\s*\{\s*return resolveQrSize\(this\.qrPos,\s*this\.c\.qrSize\)/);
assert.ok(!showSrc.includes("num(this.c.qrSize"));
test('show: miniConfig 全字段透传(方案A定稿: C端=diy-card-mini 基件=面板预览逐字段一致)', () => {
// 2026-10-24 用户定稿: Show 弃自绘版式恢复基件 —— 面板 53 字段全部经 miniConfig 生效(与 self 同构);
// 行为由 mini 侧同名测试覆盖, 此处锁透传链防 merge 冲突解决再丢(10-24 两次 merge 曾丢失整段透传)
const fields = [
// 视觉开关/设计资产
'showQuick', 'showDetailBtn', 'detailBtnText', 'useCover', 'showGradient', 'showLogo', 'showShadow',
'showRows', 'maxRows', 'showRowIcons', 'rowTap',
// 字号 7 字段
'nameSize', 'cardTitleSize', 'subSize', 'labelSize', 'valueSize', 'actSize', 'btnSize',
// 二维码六字段 + 自定义图
'showQr', 'qrTip', 'qrPosition', 'showAvatar', 'qrStyle', 'qrSize', 'qrOffsetX', 'qrOffsetY', 'qrTipShow', 'qrImage',
// 2.17 双形态 + 2.18 位置/成块 + 2.19 图层管理全套
'headStyle', 'headNameColor', 'headSubColor', 'actsLayout', 'actGridColorMode',
'showRowsTitle', 'rowsTitle', 'showCopyBtn', 'actsPosition', 'actsBlockBg', 'actsBlockRadius', 'actsBlockPad',
'actIcons', 'actVis', 'actDeleted', 'actLabels', 'actOrder', 'customActs',
];
for (const f of fields) {
assert.match(showSrc, new RegExp(f + ':\\s*v\\.' + f + ','), 'miniConfig 缺透传 ' + f);
}
// 二维码/发名片点击链路: 基件 onQrTap/onSend $emit('detail'/'send'), show 接收方挂 @detail
assert.match(showSrc, /@detail="goDetail"/);
});
// =============================================================