Merge branch 'feat/camcard' of http://git.aigc-quickapp.com/Uniapp/lucky_shop into feat/camcard
This commit is contained in:
@@ -249,7 +249,7 @@ const localDevConfig = ({
|
||||
// })['2811']; // 本地
|
||||
// })['5g-317']; // 电磁兼容
|
||||
// })['5g-351']; //
|
||||
})['5g-97']; // 本地
|
||||
})['2811']; // 本地
|
||||
// })['5g-248']; // 中瑞 工业称重系统垃圾吊电子秤,首页,图片链接部分的PDF文档打不开
|
||||
// })['2881']; // 本地
|
||||
// })['local-2']; // 本地
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<view data-component-name="diy-card-entry" class="diy-card-entry" :class="'entry-' + stylePreset" :style="wrapStyle" hover-class="entry-press" @tap="goLink">
|
||||
<view data-component-name="diy-card-entry" class="diy-card-entry" :class="['entry-' + stylePreset, { 'entry-center': isCenter }]" :style="wrapStyle" hover-class="entry-press" @tap="goLink">
|
||||
<!-- 图标装饰(可选) -->
|
||||
<view v-if="showIcon" class="entry-icon" :style="iconWrapStyle">
|
||||
<image v-if="iconType === 'image' && iconImage" class="icon-img" :src="$util.img(iconImage)" mode="aspectFit" />
|
||||
<text v-else class="icon-glyph">{{ iconGlyph || '💼' }}</text>
|
||||
<text v-else class="icon-glyph" :style="iconGlyphStyle">{{ iconGlyph || '名' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="entry-body">
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
},
|
||||
wrapStyle() {
|
||||
const v = this.v;
|
||||
const radius = px2rpx(num(v.borderRadius, CardTokens.radius.md)) + 'rpx';
|
||||
const radius = px2rpx(num(v.borderRadius, 12)) + 'rpx';
|
||||
const pad = px2rpx(num(v.padding, CardTokens.space.lg)) + 'rpx';
|
||||
if (this.isMinimal) {
|
||||
return this.$util.objToStyle({
|
||||
@@ -91,13 +91,18 @@ export default {
|
||||
background: v.bgColorStart || CardTokens.color.primary,
|
||||
borderRadius: radius,
|
||||
padding: pad,
|
||||
borderLeft: '8rpx solid ' + (v.bgColorEnd || 'rgba(255,255,255,0.35)'),
|
||||
borderLeft: '8rpx solid ' + (v.bgColorEnd || '#1E40AF'),
|
||||
minHeight: '120rpx',
|
||||
});
|
||||
},
|
||||
iconWrapStyle() {
|
||||
return this.$util.objToStyle({
|
||||
background: this.isGradient || !this.isMinimal ? 'rgba(255,255,255,0.18)' : 'rgba(47,107,255,0.10)',
|
||||
background: this.isMinimal ? 'rgba(47,107,255,0.10)' : 'rgba(255,255,255,0.18)',
|
||||
});
|
||||
},
|
||||
iconGlyphStyle() {
|
||||
return this.$util.objToStyle({
|
||||
color: this.v.textColor || (this.isMinimal ? CardTokens.color.text : '#FFFFFF'),
|
||||
});
|
||||
},
|
||||
titleStyle() {
|
||||
@@ -110,7 +115,7 @@ export default {
|
||||
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,
|
||||
opacity: this.isMinimal ? 0.65 : 0.85,
|
||||
});
|
||||
},
|
||||
btnStyle() {
|
||||
@@ -142,7 +147,10 @@ export default {
|
||||
return this.$util.objToStyle({ color: v.accentColor || '#FFEB3B', fontSize: fontSize });
|
||||
}
|
||||
if (this.buttonStyle === 'link') {
|
||||
return this.$util.objToStyle({ color: v.textColor || CardTokens.color.primary, fontSize: fontSize });
|
||||
return this.$util.objToStyle({
|
||||
color: this.isMinimal ? (v.accentColor || '#FFEB3B') : (v.textColor || '#FFFFFF'),
|
||||
fontSize: fontSize,
|
||||
});
|
||||
}
|
||||
return this.$util.objToStyle({ color: '#333333', fontSize: fontSize });
|
||||
},
|
||||
@@ -152,7 +160,7 @@ export default {
|
||||
*/
|
||||
btnArrowStyle() {
|
||||
return this.$util.objToStyle({
|
||||
color: this.v.textColor || CardTokens.color.primary,
|
||||
color: this.isMinimal ? (this.v.accentColor || '#FFEB3B') : (this.v.textColor || '#FFFFFF'),
|
||||
fontSize: px2rpx(num(this.v.btnSize, 12) + 2) + 'rpx',
|
||||
});
|
||||
},
|
||||
|
||||
@@ -124,13 +124,13 @@ export default {
|
||||
wallStyle() {
|
||||
return this.$util.objToStyle({
|
||||
background: this.v.bgColor || CardTokens.color.bg,
|
||||
borderRadius: px2rpx(num(this.v.borderRadius, CardTokens.radius.md)) + 'rpx',
|
||||
borderRadius: px2rpx(num(this.v.borderRadius, 12)) + 'rpx',
|
||||
padding: px2rpx(num(this.v.padding, CardTokens.space.md)) + 'rpx',
|
||||
});
|
||||
},
|
||||
titleStyle() {
|
||||
return this.$util.objToStyle({
|
||||
color: this.v.titleColor || CardTokens.color.text,
|
||||
color: this.v.titleColor || '#333333',
|
||||
fontSize: px2rpx(num(this.v.titleSize, 16)) + 'rpx',
|
||||
});
|
||||
},
|
||||
@@ -151,20 +151,20 @@ export default {
|
||||
/** 网格/列表卡片信息行: 字号可配(默认 11px = 22rpx) */
|
||||
metaStyle() {
|
||||
return this.$util.objToStyle({
|
||||
color: this.v.infoColor || CardTokens.color.textSub,
|
||||
color: this.v.infoColor || '#888888',
|
||||
fontSize: px2rpx(num(this.v.infoSize, 11)) + 'rpx',
|
||||
});
|
||||
},
|
||||
/** 横滑竖卡信息行: 比网格小一档(默认 10px = 20rpx, 与历史渲染一致) */
|
||||
scrollMetaStyle() {
|
||||
return this.$util.objToStyle({
|
||||
color: this.v.infoColor || CardTokens.color.textSub,
|
||||
color: this.v.infoColor || '#888888',
|
||||
fontSize: px2rpx(num(this.v.infoSize, 11) - 1) + 'rpx',
|
||||
});
|
||||
},
|
||||
/** infoColor 纯色(箭头/空态用, 不带字号) */
|
||||
infoStyle() {
|
||||
return this.$util.objToStyle({ color: this.v.infoColor || CardTokens.color.textSub });
|
||||
return this.$util.objToStyle({ color: this.v.infoColor || '#888888' });
|
||||
},
|
||||
countStyle() {
|
||||
return this.$util.objToStyle({
|
||||
@@ -211,12 +211,11 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 列表项最终样式: itemStyle + 逐项入场动画延迟 合并输出单对象。
|
||||
* uni-app 运行时会静默丢弃 :style="[A, B]" 数组绑定(2026-10-24 mini.vue
|
||||
* 操作区同根因修复的先例), 数组形态禁止回归
|
||||
* 列表项最终样式: itemStyle 字符串 + 逐项入场动画延迟合并输出单字符串。
|
||||
* 不能用 Object.assign 展开样式字符串(会拆成字符索引),也不能绑定样式数组。
|
||||
*/
|
||||
itemFinalStyle(index) {
|
||||
return Object.assign({}, this.itemStyle, { animationDelay: (index * 60) + 'ms' });
|
||||
return ((this.itemStyle || '') + ' animation-delay: ' + (index * 60) + 'ms;').trim();
|
||||
},
|
||||
initialOf,
|
||||
avatarStyle(item) {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<!-- 名片卡(精品名片 show 同款版式: 蓝底顶带 + 白卡压边; 角标压在左上) -->
|
||||
<view v-else class="mini-card-wrap">
|
||||
<view v-if="showBand" class="mini-band" :style="bandStyle"></view>
|
||||
<view v-if="bandShow" class="mini-band" :style="bandStyle"></view>
|
||||
<view class="mini-card fade-up" :style="wrapStyle" :class="{ 'qr-at-corner': qrPos === 'corner' }">
|
||||
<!-- 角标插槽(如 self 组件的「我的名片」) -->
|
||||
<view class="mini-badge-slot">
|
||||
@@ -44,13 +44,17 @@
|
||||
<!-- 二维码·右上角浮层(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(qrSrc)" mode="aspectFit" />
|
||||
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
|
||||
<text v-if="showQrTip" class="qr-tip" :style="labelStyle"><text v-if="qrTipEm" class="qr-tip-em" :style="qrTipEmStyle">{{ qrTipEm }}</text>{{ qrTipLine1 }}</text><text v-if="qrTipSub" class="qr-tip-sub" :style="labelStyle">{{ qrTipSub }}</text><text v-if="qrUrlShow && qrUrlText" class="qr-url" :style="qrUrlStyle">{{ qrUrlText }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 头部·主题渐变整块档(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 v-if="badgeShow" class="mini-hero-badge" :style="heroBadgeStyle">
|
||||
<view class="hero-badge-dot"></view>
|
||||
<text class="hero-badge-text" :style="heroBadgeTextStyle">{{ badgeText }}</text>
|
||||
</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" />
|
||||
@@ -67,13 +71,17 @@
|
||||
<!-- 二维码·头部右侧(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>
|
||||
<text v-if="showQrTip" class="qr-tip" :style="labelStyle"><text v-if="qrTipEm" class="qr-tip-em" :style="qrTipEmStyle">{{ qrTipEm }}</text>{{ qrTipLine1 }}</text><text v-if="qrTipSub" class="qr-tip-sub" :style="labelStyle">{{ qrTipSub }}</text><text v-if="qrUrlShow && qrUrlText" class="qr-url" :style="qrUrlStyle">{{ qrUrlText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 头部·白卡档(plain, 历史默认) -->
|
||||
<view v-if="!headTheme" class="mini-head" :style="cornerHeadStyle">
|
||||
<view v-if="!headTheme && badgeShow" class="mini-hero-badge" :style="heroBadgeStyle">
|
||||
<view class="hero-badge-dot"></view>
|
||||
<text class="hero-badge-text" :style="heroBadgeTextStyle">{{ badgeText }}</text>
|
||||
</view>
|
||||
<view v-if="!headTheme" class="mini-head" :style="plainHeadFinalStyle">
|
||||
<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>
|
||||
@@ -89,7 +97,7 @@
|
||||
<!-- 二维码·头部右侧(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(qrSrc)" mode="aspectFit" />
|
||||
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
|
||||
<text v-if="showQrTip" class="qr-tip" :style="labelStyle"><text v-if="qrTipEm" class="qr-tip-em" :style="qrTipEmStyle">{{ qrTipEm }}</text>{{ qrTipLine1 }}</text><text v-if="qrTipSub" class="qr-tip-sub" :style="labelStyle">{{ qrTipSub }}</text><text v-if="qrUrlShow && qrUrlText" class="qr-url" :style="qrUrlStyle">{{ qrUrlText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -114,14 +122,14 @@
|
||||
</view>
|
||||
|
||||
<!-- 资料行(可折叠; 有字形字段显图标, 无字形退化为文字标签) -->
|
||||
<view v-if="showRowsBlock && shownRows.length" class="mini-rows" :style="rowsCardStyle">
|
||||
<view v-if="showRowsBlock && shownRows.length" class="mini-rows" :style="rowsFinalStyle">
|
||||
<!-- 资料区标题(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)">
|
||||
:class="{ 'row-tappable': rowTapable(row), 'row-3col': showRowLabels }" :hover-class="rowTapable(row) ? 'btn-press' : 'none'" @tap="onRowTap(row)">
|
||||
<!-- 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)">
|
||||
@@ -133,9 +141,9 @@
|
||||
</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 v-if="showRowLabels || (!rowCssIcon(row) && !rowIcon(row))" 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>
|
||||
<text v-if="copyBtnVisible(row)" 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>
|
||||
@@ -143,9 +151,9 @@
|
||||
</view>
|
||||
|
||||
<!-- 二维码·资料行下方(rows 档, 默认; 点击预览名片); mini-qr-center: 块级居中 -->
|
||||
<view v-if="showQrRows" class="mini-qr mini-qr-center" :style="qrBlockStyle" @tap.stop="onQrTap">
|
||||
<view v-if="showQrRows" class="mini-qr mini-qr-center" :style="qrBlockFinalStyle" @tap.stop="onQrTap">
|
||||
<image class="qr-img" :style="qrImgStyle" :src="$util.img(qrSrc)" mode="aspectFit" />
|
||||
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
|
||||
<text v-if="showQrTip" class="qr-tip" :style="labelStyle"><text v-if="qrTipEm" class="qr-tip-em" :style="qrTipEmStyle">{{ qrTipEm }}</text>{{ qrTipLine1 }}</text><text v-if="qrTipSub" class="qr-tip-sub" :style="labelStyle">{{ qrTipSub }}</text><text v-if="qrUrlShow && qrUrlText" class="qr-url" :style="qrUrlStyle">{{ qrUrlText }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 快捷操作·图标宫格档(grid): 彩色浅底图标 + 下方文字 -->
|
||||
@@ -171,9 +179,9 @@
|
||||
</view>
|
||||
|
||||
<!-- 二维码·主按钮上方(bottom 档; 点击预览名片) -->
|
||||
<view v-if="showQrBottom" class="mini-qr mini-qr-center" :style="qrBlockStyle" @tap.stop="onQrTap">
|
||||
<view v-if="showQrBottom" class="mini-qr mini-qr-center" :style="qrBlockFinalStyle" @tap.stop="onQrTap">
|
||||
<image class="qr-img" :style="qrImgStyle" :src="$util.img(qrSrc)" mode="aspectFit" />
|
||||
<text v-if="showQrTip" class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
|
||||
<text v-if="showQrTip" class="qr-tip" :style="labelStyle"><text v-if="qrTipEm" class="qr-tip-em" :style="qrTipEmStyle">{{ qrTipEm }}</text>{{ qrTipLine1 }}</text><text v-if="qrTipSub" class="qr-tip-sub" :style="labelStyle">{{ qrTipSub }}</text><text v-if="qrUrlShow && qrUrlText" class="qr-url" :style="qrUrlStyle">{{ qrUrlText }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 发名片(对齐名片详情页: 主题色大按钮; 点击跳原生转发中转页拉起微信转发面板) -->
|
||||
@@ -268,6 +276,10 @@ export default {
|
||||
background: flagOn(c.useCover, false) && this.card && this.card.cover ? 'transparent' : bg,
|
||||
borderRadius: px2rpx(num(c.borderRadius, CardTokens.radius.md)) + 'rpx',
|
||||
padding: px2rpx(num(c.padding, CardTokens.space.lg)) + 'rpx',
|
||||
marginTop: px2rpx(num(c.wrapMarginTop, 0)) + 'rpx',
|
||||
marginRight: px2rpx(num(c.wrapMarginRight, 0)) + 'rpx',
|
||||
marginBottom: px2rpx(num(c.wrapMarginBottom, 0)) + 'rpx',
|
||||
marginLeft: px2rpx(num(c.wrapMarginLeft, 0)) + 'rpx',
|
||||
boxShadow: flagOn(c.showShadow, true) ? CardTokens.shadow.card : 'none',
|
||||
});
|
||||
},
|
||||
@@ -278,8 +290,8 @@ export default {
|
||||
return flagOn(this.c.showGradient, false);
|
||||
},
|
||||
showLogo() {
|
||||
// corner 档二维码占右上同一位, Logo 自动避让(面板有提示)
|
||||
return flagOn(this.c.showLogo, false) && this.qrPos !== 'corner';
|
||||
// 只有 corner 二维码实际显示时才避让;关闭二维码或无码源不隐藏 Logo。
|
||||
return flagOn(this.c.showLogo, false) && !this.showQrCorner;
|
||||
},
|
||||
showActs() {
|
||||
return flagOn(this.c.showQuick, true);
|
||||
@@ -412,18 +424,32 @@ export default {
|
||||
return normalizeHeadStyle(this.c.headStyle) === 'theme';
|
||||
},
|
||||
/**
|
||||
* theme 档头部块: 渐变通铺卡边(负 margin 抵消 wrapStyle 的 padding, WXSS 不用 calc)
|
||||
* 内部左右 padding = 卡片原 padding, 保证头像与下方资料行左缘对齐
|
||||
* 头部容器样式(plain/theme 共用)。未开启自定义间距时完整保留历史布局:
|
||||
* theme 负外边距抵消卡片 padding,plain 不额外设置间距。
|
||||
*/
|
||||
headBoxStyle() {
|
||||
const c = this.c;
|
||||
const custom = flagOn(c.headSpacingCustom, false);
|
||||
const style = {
|
||||
background: c.headBgMode === 'custom'
|
||||
? (String(c.headBg || '').trim() || 'transparent')
|
||||
: (this.headTheme ? themeGradient(this.card) : 'transparent'),
|
||||
marginTop: px2rpx(custom ? num(c.headMarginTop, 0) : (this.headTheme ? -num(c.padding, CardTokens.space.lg) : 0)) + 'rpx',
|
||||
marginRight: px2rpx(custom ? num(c.headMarginRight, 0) : (this.headTheme ? -num(c.padding, CardTokens.space.lg) : 0)) + 'rpx',
|
||||
marginBottom: px2rpx(custom ? num(c.headMarginBottom, 0) : 0) + 'rpx',
|
||||
marginLeft: px2rpx(custom ? num(c.headMarginLeft, 0) : (this.headTheme ? -num(c.padding, CardTokens.space.lg) : 0)) + 'rpx',
|
||||
paddingTop: px2rpx(custom ? num(c.headPaddingTop, 16) : (this.headTheme ? 16 : 0)) + 'rpx',
|
||||
paddingRight: px2rpx(custom ? num(c.headPaddingRight, 16) : (this.headTheme ? num(c.padding, CardTokens.space.lg) : 0)) + 'rpx',
|
||||
paddingBottom: px2rpx(custom ? num(c.headPaddingBottom, 14) : (this.headTheme ? 14 : 0)) + 'rpx',
|
||||
paddingLeft: px2rpx(custom ? num(c.headPaddingLeft, 16) : (this.headTheme ? num(c.padding, CardTokens.space.lg) : 0)) + 'rpx',
|
||||
borderRadius: c.headRadiusMode === 'custom'
|
||||
? px2rpx(num(c.headRadius, 8)) + 'rpx'
|
||||
: (this.headTheme ? '0 0 16rpx 16rpx' : '0'),
|
||||
};
|
||||
return this.$util.objToStyle(style);
|
||||
},
|
||||
headThemeStyle() {
|
||||
const pad = px2rpx(num(this.c.padding, CardTokens.space.lg));
|
||||
// 独立成块上浮时, 头部预留压边空间(参考 hero 的 200rpx 底内边距), 使蓝色姓名资料卡更高
|
||||
const bottom = this.actsFloat ? 200 : 28;
|
||||
return this.$util.objToStyle({
|
||||
background: themeGradient(this.card),
|
||||
margin: '-' + pad + 'rpx -' + pad + 'rpx 0',
|
||||
padding: '32rpx ' + pad + 'rpx ' + bottom + 'rpx',
|
||||
});
|
||||
return this.headBoxStyle;
|
||||
},
|
||||
/** 资料区标题(2.17): 默认关(历史配置无此字段时不出标题) */
|
||||
rowsTitleShow() {
|
||||
@@ -443,6 +469,47 @@ export default {
|
||||
qrTip() {
|
||||
return this.c.qrTip || '';
|
||||
},
|
||||
// ===== 2.19.10 detail 版式对齐字段 =====
|
||||
/** 资料行三列(图标+灰标签+值, detail 同款): 默认 0 = 历史图标/标签二选一 */
|
||||
showRowLabels() {
|
||||
return flagOn(this.c.showRowLabels, false);
|
||||
},
|
||||
/** hero 徽章(左上「数字名片」): 默认关 */
|
||||
badgeShow() {
|
||||
return flagOn(this.c.badgeShow, false);
|
||||
},
|
||||
badgeText() {
|
||||
return String(this.c.badgeText || '数字名片');
|
||||
},
|
||||
/** 提示加粗段(detail <b>扫码保存名片</b>); 空 = 整行走 qrTip(历史形态) */
|
||||
qrTipEm() {
|
||||
return String(this.c.qrTipEm || '').trim();
|
||||
},
|
||||
/** 提示第一行(加粗段之后): 有 em 时前置「 · 」分隔符 */
|
||||
qrTipLine1() {
|
||||
const tip = String(this.c.qrTip || '').trim();
|
||||
if (this.qrTipEm) return tip ? (' · ' + tip) : '';
|
||||
return tip || '扫码查看完整名片';
|
||||
},
|
||||
/** 提示第二行(detail「同一二维码可写入 NFC 名片贴」): 空 = 不出 */
|
||||
qrTipSub() {
|
||||
return String(this.c.qrTipSub || '').trim();
|
||||
},
|
||||
/** 二维码下 URL 文本行(detail qr-url 同款): 默认关 */
|
||||
qrUrlShow() {
|
||||
return flagOn(this.c.qrUrlShow, false);
|
||||
},
|
||||
qrUrlText() {
|
||||
return (this.card && this.card.wap_url) || '';
|
||||
},
|
||||
/** 复制按钮范围: all=全部行(历史) / mobile=仅手机微信行(detail 口径) */
|
||||
copyBtnScope() {
|
||||
return String(this.c.copyBtnScope || 'all');
|
||||
},
|
||||
/** 复制按钮外观: theme=主题浅底(历史) / gray=浅灰描边胶囊(detail 同款) */
|
||||
copyBtnLook() {
|
||||
return String(this.c.copyBtnLook || 'theme');
|
||||
},
|
||||
detailBtnText() {
|
||||
return this.c.detailBtnText || '';
|
||||
},
|
||||
@@ -524,13 +591,15 @@ export default {
|
||||
gradientStyle() {
|
||||
return this.$util.objToStyle({ background: themeGradient(this.card) });
|
||||
},
|
||||
/** 蓝色顶带开关(默认关): 参考图要求去掉卡片背后的蓝底, 只保留头部主题蓝 */
|
||||
showBand() {
|
||||
return flagOn(this.c.showBand, false);
|
||||
/** 顶部色带默认隐藏;背景可跟随名片设计或使用独立颜色。 */
|
||||
bandShow() {
|
||||
return flagOn(this.c.bandShow, false);
|
||||
},
|
||||
bandStyle() {
|
||||
// 蓝色顶带底色: 名片主题渐变(精选名片 show 同款)
|
||||
return this.$util.objToStyle({ background: themeGradient(this.card) });
|
||||
const custom = this.c.bandBgMode === 'custom';
|
||||
return this.$util.objToStyle({
|
||||
background: custom ? (String(this.c.bandBg || '').trim() || 'transparent') : themeGradient(this.card),
|
||||
});
|
||||
},
|
||||
shareBtnStyle() {
|
||||
// 分享抽屉主按钮(复制链接): 主题色
|
||||
@@ -604,13 +673,51 @@ export default {
|
||||
fontWeight: '600',
|
||||
});
|
||||
},
|
||||
/** 复制按钮(参考图): 浅灰底 + 灰字, 与主题色解耦 */
|
||||
/** 复制按钮: theme=主题浅底+主题色字(历史); gray=detail 同款浅灰描边胶囊 */
|
||||
copyBtnStyle() {
|
||||
if (this.copyBtnLook === 'gray') {
|
||||
return this.$util.objToStyle({
|
||||
background: '#F7F9FC',
|
||||
color: '#5A6478',
|
||||
border: '2rpx solid #EEF1F6',
|
||||
});
|
||||
}
|
||||
return this.$util.objToStyle({
|
||||
background: CardTokens.color.skeleton,
|
||||
color: CardTokens.color.textSub,
|
||||
});
|
||||
},
|
||||
/** 提示加粗段样式(detail qr-tip b 同款: ink-2 深灰 + 加粗) */
|
||||
qrTipEmStyle() {
|
||||
return this.$util.objToStyle({
|
||||
color: this.c.infoColor || '#5A6478',
|
||||
fontWeight: '600',
|
||||
});
|
||||
},
|
||||
/** 二维码 URL 行样式(detail qr-url 同款: 小字灰 + 断行) */
|
||||
qrUrlStyle() {
|
||||
return this.$util.objToStyle({
|
||||
color: this.c.labelColor || CardTokens.color.textLabel,
|
||||
fontSize: px2rpx(10) + 'rpx',
|
||||
marginTop: px2rpx(8) + 'rpx',
|
||||
wordBreak: 'break-all',
|
||||
textAlign: 'center',
|
||||
});
|
||||
},
|
||||
/** hero 徽章胶囊(theme 档白描边 / plain 档主题描边; 避让企业徽标 badgeStyle) */
|
||||
heroBadgeStyle() {
|
||||
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
|
||||
return this.$util.objToStyle({
|
||||
border: '2rpx solid ' + (this.headTheme ? 'rgba(255,255,255,0.35)' : (hexToRgba(theme, 0.35) || theme)),
|
||||
});
|
||||
},
|
||||
heroBadgeTextStyle() {
|
||||
return this.$util.objToStyle({
|
||||
color: this.headTheme ? 'rgba(255,255,255,0.85)' : (this.c.labelColor || CardTokens.color.textLabel),
|
||||
fontSize: px2rpx(11) + 'rpx',
|
||||
letterSpacing: '2rpx',
|
||||
});
|
||||
},
|
||||
badgeStyle() {
|
||||
// 企业徽标固定红底(参考图 2026-10-22 口径), 与头像底色解耦
|
||||
return this.$util.objToStyle({ background: '#FA5151' });
|
||||
@@ -738,6 +845,48 @@ export default {
|
||||
}
|
||||
return parts.join(' ').trim();
|
||||
},
|
||||
/** 资料区独立块样式(与操作区同款: Bg/Radius/Pad) */
|
||||
rowsBlockStyle() {
|
||||
const c = this.c;
|
||||
const bg = String(c.rowsBlockBg || '').trim();
|
||||
const radius = num(c.rowsBlockRadius, 0);
|
||||
const pad = num(c.rowsBlockPad, 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);
|
||||
},
|
||||
/** 二维码独立块样式(与操作区同款: Bg/Radius/Pad) */
|
||||
qrBlockCustomStyle() {
|
||||
const c = this.c;
|
||||
const bg = String(c.qrBlockBg || '').trim();
|
||||
const radius = num(c.qrBlockRadius, 0);
|
||||
const pad = num(c.qrBlockPad, 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);
|
||||
},
|
||||
/** 资料区最终样式: 独立块样式 + corner 让位合并 */
|
||||
rowsFinalStyle() {
|
||||
const clear = this.rowsClearStyle || {};
|
||||
const extra = clear.marginTop ? 'margin-top: ' + clear.marginTop + ';' : '';
|
||||
return ((this.rowsBlockStyle || '') + ' ' + extra).trim();
|
||||
},
|
||||
/** 二维码(rows/bottom 流内档)最终样式: 独立块样式 + 尺寸/位移合并 */
|
||||
qrBlockFinalStyle() {
|
||||
const transform = qrTransform(this.c.qrOffsetX, this.c.qrOffsetY);
|
||||
const parts = [this.qrBlockStyle, this.qrBlockCustomStyle];
|
||||
if (transform) parts.push('transform:' + transform + ';');
|
||||
return parts.filter(Boolean).join(' ').trim();
|
||||
},
|
||||
/** plain 头最终样式:头部容器配置与 corner 二维码让位合并为单字符串。 */
|
||||
plainHeadFinalStyle() {
|
||||
const corner = this.$util.objToStyle(this.cornerHeadStyle || {});
|
||||
return [this.headBoxStyle, corner].filter(Boolean).join(' ').trim();
|
||||
},
|
||||
detailBtnStyle() {
|
||||
// 发送名片大按钮: 纯色主题实心(2026-10-22 参考图定稿, 不再用渐变);
|
||||
// 高度 104rpx(用户反馈: 比触达标准 80rpx 加高一档, 参考图观感)
|
||||
@@ -921,6 +1070,15 @@ export default {
|
||||
fontSize: px2rpx(num(this.c.actSize, 12)) + 'rpx',
|
||||
});
|
||||
},
|
||||
/** 复制按钮可见性: 开关 + 值非空 + 范围(mobile=仅手机/微信行, detail 口径) */
|
||||
copyBtnVisible(row) {
|
||||
if (!this.showCopyBtn || !row || !String(row.value || '').trim()) return false;
|
||||
if (this.copyBtnScope === 'mobile') {
|
||||
const f = String(row.field || '').toLowerCase();
|
||||
return f === 'mobile' || f === 'phone' || f === 'wechat' || f === 'wx';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
/** 复制按钮: 复用 row-act 的 copy 分支(rowTap 关闭时仍可独立复制) */
|
||||
onCopyRow(row) {
|
||||
this.$emit('row-act', { type: 'copy', value: row.value });
|
||||
@@ -1092,7 +1250,43 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/* 复制按钮(参考图): 浅灰底灰字, 胶囊圆角, 小尺寸, 垂直居中, 不挤压资料值换行 */
|
||||
/* 2.19.10 hero 徽章(detail hero-badge 同款: 绿点 + 描边胶囊) */
|
||||
.mini-hero-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
align-self: flex-start;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 6rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
|
||||
.hero-badge-dot {
|
||||
width: 10rpx;
|
||||
height: 10rpx;
|
||||
border-radius: 50%;
|
||||
background: #07c160;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 提示多行(detail qr-tip 同款: 加粗段 + 换行) */
|
||||
.qr-tip-em {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.qr-tip-sub {
|
||||
font-size: 22rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
/* 二维码 URL 行(detail qr-url 同款) */
|
||||
.qr-url {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 复制按钮(2.17): 浅底主题色小按钮(色内联), 不挤压资料值换行 */
|
||||
.row-copy {
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
@@ -1267,6 +1461,18 @@ export default {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 三列档(2.19.10): 图标+灰标签+值, detail 资料行同款列宽 */
|
||||
&.row-3col {
|
||||
.row-icon,
|
||||
.row-css-wrap {
|
||||
width: 56rpx;
|
||||
}
|
||||
|
||||
.row-label {
|
||||
width: 88rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 图标列与文字标签列同宽, 保证值列左缘对齐(参考图口径) */
|
||||
.row-icon {
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -125,6 +125,42 @@ export default {
|
||||
actLabels: v.actLabels,
|
||||
actOrder: v.actOrder,
|
||||
customActs: v.customActs,
|
||||
// 2.19.10 detail 版式对齐(三列行/复制范围外观/qrTip 富文本/URL 行/hero 徽章/分卡)
|
||||
showRowLabels: v.showRowLabels,
|
||||
copyBtnScope: v.copyBtnScope,
|
||||
copyBtnLook: v.copyBtnLook,
|
||||
qrTipEm: v.qrTipEm,
|
||||
qrTipSub: v.qrTipSub,
|
||||
qrUrlShow: v.qrUrlShow,
|
||||
badgeShow: v.badgeShow,
|
||||
badgeText: v.badgeText,
|
||||
rowsBlockBg: v.rowsBlockBg,
|
||||
rowsBlockRadius: v.rowsBlockRadius,
|
||||
rowsBlockPad: v.rowsBlockPad,
|
||||
qrBlockBg: v.qrBlockBg,
|
||||
qrBlockRadius: v.qrBlockRadius,
|
||||
qrBlockPad: v.qrBlockPad,
|
||||
// 顶部色带 / 卡片四向外边距 / 头部背景与盒模型
|
||||
bandShow: v.bandShow,
|
||||
bandBgMode: v.bandBgMode,
|
||||
bandBg: v.bandBg,
|
||||
wrapMarginTop: v.wrapMarginTop,
|
||||
wrapMarginRight: v.wrapMarginRight,
|
||||
wrapMarginBottom: v.wrapMarginBottom,
|
||||
wrapMarginLeft: v.wrapMarginLeft,
|
||||
headBgMode: v.headBgMode,
|
||||
headBg: v.headBg,
|
||||
headRadiusMode: v.headRadiusMode,
|
||||
headRadius: v.headRadius,
|
||||
headSpacingCustom: v.headSpacingCustom,
|
||||
headMarginTop: v.headMarginTop,
|
||||
headMarginRight: v.headMarginRight,
|
||||
headMarginBottom: v.headMarginBottom,
|
||||
headMarginLeft: v.headMarginLeft,
|
||||
headPaddingTop: v.headPaddingTop,
|
||||
headPaddingRight: v.headPaddingRight,
|
||||
headPaddingBottom: v.headPaddingBottom,
|
||||
headPaddingLeft: v.headPaddingLeft,
|
||||
};
|
||||
},
|
||||
boxStyle() {
|
||||
|
||||
@@ -90,6 +90,42 @@ export default {
|
||||
actLabels: v.actLabels,
|
||||
actOrder: v.actOrder,
|
||||
customActs: v.customActs,
|
||||
// 2.19.10 detail 版式对齐(三列行/复制范围外观/qrTip 富文本/URL 行/hero 徽章/分卡)
|
||||
showRowLabels: v.showRowLabels,
|
||||
copyBtnScope: v.copyBtnScope,
|
||||
copyBtnLook: v.copyBtnLook,
|
||||
qrTipEm: v.qrTipEm,
|
||||
qrTipSub: v.qrTipSub,
|
||||
qrUrlShow: v.qrUrlShow,
|
||||
badgeShow: v.badgeShow,
|
||||
badgeText: v.badgeText,
|
||||
rowsBlockBg: v.rowsBlockBg,
|
||||
rowsBlockRadius: v.rowsBlockRadius,
|
||||
rowsBlockPad: v.rowsBlockPad,
|
||||
qrBlockBg: v.qrBlockBg,
|
||||
qrBlockRadius: v.qrBlockRadius,
|
||||
qrBlockPad: v.qrBlockPad,
|
||||
// 顶部色带 / 卡片四向外边距 / 头部背景与盒模型
|
||||
bandShow: v.bandShow,
|
||||
bandBgMode: v.bandBgMode,
|
||||
bandBg: v.bandBg,
|
||||
wrapMarginTop: v.wrapMarginTop,
|
||||
wrapMarginRight: v.wrapMarginRight,
|
||||
wrapMarginBottom: v.wrapMarginBottom,
|
||||
wrapMarginLeft: v.wrapMarginLeft,
|
||||
headBgMode: v.headBgMode,
|
||||
headBg: v.headBg,
|
||||
headRadiusMode: v.headRadiusMode,
|
||||
headRadius: v.headRadius,
|
||||
headSpacingCustom: v.headSpacingCustom,
|
||||
headMarginTop: v.headMarginTop,
|
||||
headMarginRight: v.headMarginRight,
|
||||
headMarginBottom: v.headMarginBottom,
|
||||
headMarginLeft: v.headMarginLeft,
|
||||
headPaddingTop: v.headPaddingTop,
|
||||
headPaddingRight: v.headPaddingRight,
|
||||
headPaddingBottom: v.headPaddingBottom,
|
||||
headPaddingLeft: v.headPaddingLeft,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -103,7 +103,7 @@ test('mini: 资料行图标列 + 文字标签退化', () => {
|
||||
// 2.17.1: 行首新增 CSS 描边图标档(邮箱/网站), iconfont 档退为 v-else-if
|
||||
assert.match(miniSrc, /<view v-if="rowCssIcon\(row\)" class="row-css-wrap">/);
|
||||
assert.match(miniSrc, /<text v-else-if="rowIcon\(row\)" class="row-icon iconfont" :class="rowIcon\(row\)"/);
|
||||
assert.match(miniSrc, /<text v-else class="row-label"/);
|
||||
assert.match(miniSrc, /<text v-if="showRowLabels \|\| \(!rowCssIcon\(row\) && !rowIcon\(row\)\)" class="row-label"/);
|
||||
// rowIcon 受 showRowIcons 开关控制(默认开); CSS 档同受此开关
|
||||
assert.match(miniSrc, /rowIcon\(row\)\s*\{\s*return flagOn\(this\.c\.showRowIcons,\s*true\)/);
|
||||
assert.match(miniSrc, /rowCssIcon\(row\)\s*\{\s*return flagOn\(this\.c\.showRowIcons,\s*true\)/);
|
||||
@@ -111,7 +111,7 @@ test('mini: 资料行图标列 + 文字标签退化', () => {
|
||||
|
||||
test('mini: 资料行可点(开关 + href 派生 + emit row-act)', () => {
|
||||
assert.match(miniSrc, /rowTapable\(row\)\s*\{\s*return flagOn\(this\.c\.rowTap,\s*true\)\s*&&\s*!!rowActOf\(row\)/);
|
||||
assert.match(miniSrc, /:class="\{ 'row-tappable': rowTapable\(row\) \}"/);
|
||||
assert.match(miniSrc, /:class="\{ 'row-tappable': rowTapable\(row\), 'row-3col': showRowLabels \}"/);
|
||||
assert.match(miniSrc, /@tap="onRowTap\(row\)"/);
|
||||
assert.match(miniSrc, /onRowTap\(row\)\s*\{[\s\S]*?this\.\$emit\('row-act',\s*act\)/);
|
||||
});
|
||||
@@ -164,6 +164,18 @@ test('self: miniConfig 透传 A+B 三字段', () => {
|
||||
assert.match(selfSrc, /qrPosition:\s*v\.qrPosition/);
|
||||
});
|
||||
|
||||
test('self: miniConfig 透传 2.19.10 detail 对齐 9 字段', () => {
|
||||
for (const f of ['showRowLabels', 'copyBtnScope', 'copyBtnLook', 'qrTipEm', 'qrTipSub', 'qrUrlShow', 'badgeShow', 'badgeText', 'rowsBlockBg', 'rowsBlockRadius', 'rowsBlockPad', 'qrBlockBg', 'qrBlockRadius', 'qrBlockPad']) {
|
||||
assert.match(selfSrc, new RegExp(f + ':\\s*v\\.' + f + ','), 'self 透传 ' + f);
|
||||
}
|
||||
});
|
||||
|
||||
test('show: miniConfig 透传 2.19.10 detail 对齐 9 字段', () => {
|
||||
for (const f of ['showRowLabels', 'copyBtnScope', 'copyBtnLook', 'qrTipEm', 'qrTipSub', 'qrUrlShow', 'badgeShow', 'badgeText', 'rowsBlockBg', 'rowsBlockRadius', 'rowsBlockPad', 'qrBlockBg', 'qrBlockRadius', 'qrBlockPad']) {
|
||||
assert.match(showSrc, new RegExp(f + ':\\s*v\\.' + f + ','), 'show 透传 ' + f);
|
||||
}
|
||||
});
|
||||
|
||||
test('self: 模板挂 @row-act="doAct"(行点击有接收方)', () => {
|
||||
assert.match(selfSrc, /@row-act="doAct"/);
|
||||
});
|
||||
|
||||
@@ -177,8 +177,8 @@ test('mini: 蓝色顶带版式(2026-10-23 与精选名片 show 同款: 自适应
|
||||
assert.match(miniSrc, /class="mini-card-wrap"/);
|
||||
assert.match(miniSrc, /class="mini-band" :style="bandStyle"/);
|
||||
assert.match(miniSrc, /class="mini-card fade-up" :style="wrapStyle"/);
|
||||
// 计算属性: 顶带底色走主题渐变(与 show bandStyle 同口径)
|
||||
assert.match(miniSrc, /bandStyle\(\)\s*\{\s*return this\.\$util\.objToStyle\(\{\s*background: themeGradient\(this\.card\)\s*\}\);/);
|
||||
// 计算属性: 顶带底色可自定义(bandBg)或回落主题渐变; 高度可配(bandHeight)
|
||||
assert.match(miniSrc, /bandStyle\(\)\s*\{[\s\S]*?bandBg[\s\S]*?themeGradient\(this\.card\)/);
|
||||
// CSS: wrap 撑露沿 + band 拉伸(top:-40rpx / bottom:88rpx) + 圆角(顶直底弧)
|
||||
assert.match(miniSrc, /\.mini-card-wrap \{\s*position: relative;\s*margin-top: 40rpx;/);
|
||||
assert.match(miniSrc, /\.mini-band \{\s*position: absolute;[\s\S]*?top: -40rpx;[\s\S]*?bottom: 88rpx;[\s\S]*?border-radius: 0 0 28rpx 28rpx;/);
|
||||
@@ -254,9 +254,11 @@ test('mini: cornerHeadStyle 头部水平让位按浮层实际宽度动态反算'
|
||||
assert.match(miniSrc, /<view class="mini-head" :style="cornerHeadStyle">/);
|
||||
});
|
||||
|
||||
test('mini: 四个二维码块统一绑 qrBlockStyle(尺寸/位移/外观单一事实来源)', () => {
|
||||
const binds = miniSrc.match(/:style="qrBlockStyle"/g) || [];
|
||||
assert.equal(binds.length, 5, 'qrBlockStyle 绑定(corner/rows/bottom 各 1 + head 双分支各一), 实际 ' + binds.length);
|
||||
test('mini: 二维码块样式绑定——corner/head 走 qrBlockStyle, rows/bottom 走 qrBlockFinalStyle(2.19.10 分卡合并)', () => {
|
||||
const direct = miniSrc.match(/:style="qrBlockStyle"/g) || [];
|
||||
assert.equal(direct.length, 3, 'qrBlockStyle 直绑(corner 1 + head 双分支 2), 实际 ' + direct.length);
|
||||
const final = miniSrc.match(/:style="qrBlockFinalStyle"/g) || [];
|
||||
assert.equal(final.length, 2, 'qrBlockFinalStyle(rows/bottom 各 1), 实际 ' + final.length);
|
||||
});
|
||||
|
||||
// =============================================================
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
* 名片 DIY 组件 · 2.17 双形态(渐变头部/宫格快捷操作/资料区标题/复制按钮)契约测试
|
||||
*
|
||||
* 锁死三层口径: diy_view.php 默认值 ↔ design.html 面板控件与预览 ↔ C 端 vue 消费
|
||||
* 兼容铁律: 新字段默认值 = 历史形态(plain/chips/关), 存量组件行缺字段时基件
|
||||
* flagOn/normalize 回落历史默认 —— 渲染零变化, 种子只补缺绝不 UPDATE
|
||||
* 兼容铁律(2026-10-24 起分两层):
|
||||
* ① 基件 flagOn/normalize 缺字段回落历史形态(plain/chips/关) —— 本测试 108-241 行
|
||||
* 断言保持不变, 保护任何缺字段的存量行渲染零变化;
|
||||
* ② diy_view.php config 默认值代际升级为参考图形态(theme/grid/开/above) ——
|
||||
* 配套迁移(test/card_diy_instance_style_migrate.php)把注册表行补缺 + 存量页面
|
||||
* 实例按整值指纹刷新, 存量行不再依赖①的回落, 故 config 默认值断言随代际更新。
|
||||
*
|
||||
* 同时锁死 2.17 修复: self.vue miniConfig 补齐 7 个字号字段透传
|
||||
* (diy-card-show.vue 自 2026-10-21 起改为自绘, 不再经 miniConfig 复用 diy-card-mini,
|
||||
@@ -19,21 +23,21 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const BE = resolve(__dirname, '../../backend/src/addon/card');
|
||||
const FE = resolve(__dirname, '../components-diy');
|
||||
|
||||
/** 2.17 新字段 → 默认值(= 历史形态, 兼容存量行) */
|
||||
/** 2.17 新字段 → 默认值(2026-10-24 代际升级: 参考图形态, 见文件头注释) */
|
||||
const V2_FIELDS = {
|
||||
headStyle: 'plain',
|
||||
headStyle: 'theme',
|
||||
headNameColor: '#FFFFFF',
|
||||
headSubColor: '#FFFFFF',
|
||||
actsLayout: 'chips',
|
||||
actsLayout: 'grid',
|
||||
actGridColorMode: 'preset',
|
||||
showRowsTitle: 0,
|
||||
showRowsTitle: 1,
|
||||
rowsTitle: '联系资料',
|
||||
showCopyBtn: 0,
|
||||
showCopyBtn: 1,
|
||||
};
|
||||
|
||||
/** 2.18 新字段 → 默认值(= 历史形态, 兼容存量行) */
|
||||
/** 2.18 新字段 → 默认值(2026-10-24 代际升级: actsPosition above) */
|
||||
const V18_FIELDS = {
|
||||
actsPosition: 'below',
|
||||
actsPosition: 'above',
|
||||
actsBlockBg: '',
|
||||
actsBlockRadius: 0,
|
||||
actsBlockPad: 0,
|
||||
@@ -58,7 +62,7 @@ function readCommonJs() {
|
||||
return readFileSync(resolve(FE, 'diy-card-common.js'), 'utf8');
|
||||
}
|
||||
|
||||
test('diy_view.php: Show/Self value 均含 8 个 2.17 字段且默认值=历史形态', () => {
|
||||
test('diy_view.php: Show/Self value 均含 8 个 2.17 字段且默认值=参考图形态(2026-10-24 代际升级)', () => {
|
||||
for (const comp of ['show', 'self']) {
|
||||
const v = readValueJson(comp);
|
||||
for (const [field, dft] of Object.entries(V2_FIELDS)) {
|
||||
@@ -68,7 +72,7 @@ test('diy_view.php: Show/Self value 均含 8 个 2.17 字段且默认值=历史
|
||||
}
|
||||
});
|
||||
|
||||
test('diy_view.php: Show/Self value 均含 2.18 字段(actIcons 五类型完整结构)', () => {
|
||||
test('diy_view.php: Show/Self value 均含 2.18 字段(actIcons 五类型完整结构, actsPosition=above 代际升级)', () => {
|
||||
for (const comp of ['show', 'self']) {
|
||||
const v = readValueJson(comp);
|
||||
for (const [field, dft] of Object.entries(V18_FIELDS)) {
|
||||
@@ -380,6 +384,24 @@ test('反例自测: 新形态标记不得出现在未改造组件中', () => {
|
||||
const V19_FIELDS = { actVis: 'object', actDeleted: 'object', actLabels: 'object', customActs: 'array', actOrder: 'array' };
|
||||
const V19_ACT_LABELS = { tel: '打电话', wx: '微信号', mail: '发邮件', web: '网站', save: '存通讯录' };
|
||||
|
||||
/** 2.19.10 detail 版式对齐字段 → 默认值(零变化) */
|
||||
const V21910_FIELDS = {
|
||||
showRowLabels: 0,
|
||||
copyBtnScope: 'all',
|
||||
copyBtnLook: 'theme',
|
||||
qrTipEm: '',
|
||||
qrTipSub: '',
|
||||
qrUrlShow: 0,
|
||||
badgeShow: 0,
|
||||
badgeText: '数字名片',
|
||||
rowsBlockBg: '',
|
||||
rowsBlockRadius: 0,
|
||||
rowsBlockPad: 0,
|
||||
qrBlockBg: '',
|
||||
qrBlockRadius: 0,
|
||||
qrBlockPad: 0,
|
||||
};
|
||||
|
||||
test('diy_view.php: Show/Self value 均含 2.19 字段(默认值=全显/默认文案/空数组/历史顺序=零变化)', () => {
|
||||
for (const comp of ['show', 'self']) {
|
||||
const v = readValueJson(comp);
|
||||
@@ -398,6 +420,62 @@ test('diy_view.php: Show/Self value 均含 2.19 字段(默认值=全显/默认
|
||||
}
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 2.19.10 detail 版式对齐: 三列行/复制范围外观/qrTip 富文本/URL 行/hero 徽章/分卡
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
test('diy_view.php: Show/Self value 均含 2.19.10 字段(默认值=历史形态零变化)', () => {
|
||||
for (const comp of ['show', 'self']) {
|
||||
const v = readValueJson(comp);
|
||||
for (const [field, dft] of Object.entries(V21910_FIELDS)) {
|
||||
assert.ok(field in v, comp + '.' + field + ' 已注册');
|
||||
assert.deepEqual(v[field], dft, comp + '.' + field + ' 默认=' + JSON.stringify(dft));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('show/self.vue: miniConfig 均透传 9 个 2.19.10 字段', () => {
|
||||
for (const file of ['diy-card-show.vue', 'diy-card-self.vue']) {
|
||||
const src = readFileSync(resolve(FE, file), 'utf8');
|
||||
for (const f of Object.keys(V21910_FIELDS)) {
|
||||
assert.match(src, new RegExp(f + ':\\s*v\\.' + f + ','), file + ' 透传 ' + f);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('mini.vue: 消费 2.19.10 字段(computed/模板/样式)', () => {
|
||||
const src = readFileSync(resolve(FE, 'diy-card-mini.vue'), 'utf8');
|
||||
// computed: 新字段 getter
|
||||
for (const f of ['showRowLabels', 'badgeShow', 'badgeText', 'qrTipEm', 'qrTipSub', 'qrUrlShow', 'qrUrlText', 'copyBtnScope', 'copyBtnLook', 'rowsBlockStyle', 'qrBlockCustomStyle']) {
|
||||
assert.match(src, new RegExp(f + '\\(\\)\\s*\\{'), 'mini.vue computed ' + f);
|
||||
}
|
||||
// 模板消费
|
||||
assert.match(src, /class="mini-hero-badge"/, 'hero 徽章渲染');
|
||||
assert.match(src, /row-3col.*showRowLabels|showRowLabels.*row-3col/, '三列行 class 绑定');
|
||||
assert.match(src, /copyBtnVisible\(row\)/, '复制按钮范围方法');
|
||||
assert.match(src, /qrTipEm/, 'qrTip 加粗段');
|
||||
assert.match(src, /qrTipSub/, 'qrTip 第二行');
|
||||
assert.match(src, /qrUrlShow/, 'URL 行开关');
|
||||
// 独立块样式消费(与操作区同款: Bg/Radius/Pad)
|
||||
assert.match(src, /rowsBlockBg/, 'rowsBlockStyle 消费 rowsBlockBg');
|
||||
assert.match(src, /qrBlockBg/, 'qrBlockCustomStyle 消费 qrBlockBg');
|
||||
assert.match(src, /rowsFinalStyle\(\)\s*\{[\s\S]*?rowsBlockStyle/, 'rowsFinalStyle 合并 rowsBlockStyle');
|
||||
assert.match(src, /qrBlockFinalStyle\(\)\s*\{[\s\S]*?qrBlockCustomStyle/, 'qrBlockFinalStyle 合并 qrBlockCustomStyle');
|
||||
// 样式
|
||||
assert.match(src, /\.mini-hero-badge/, 'hero 徽章 CSS');
|
||||
assert.match(src, /\.qr-tip-em/, 'qrTip 加粗 CSS');
|
||||
assert.match(src, /\.qr-tip-sub/, 'qrTip 第二行 CSS');
|
||||
assert.match(src, /\.qr-url/, 'URL 行 CSS');
|
||||
assert.match(src, /row-3col/, '三列行 CSS');
|
||||
// 反例: 禁数组 style 绑定(uni-app 静默丢弃)——先剥注释防误判
|
||||
const stripped = src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
||||
assert.doesNotMatch(stripped, /:style="\[/, '无数组 style 绑定');
|
||||
// 反例: layoutCards 已移除
|
||||
assert.doesNotMatch(stripped, /layoutCards/, 'layoutCards 已移除');
|
||||
// 徽章样式避让企业徽标 badgeStyle
|
||||
assert.match(src, /heroBadgeStyle\(\)/, 'heroBadgeStyle 避让 badgeStyle');
|
||||
});
|
||||
|
||||
test('素材图标库三源同步: PHP CardIconLibrary ↔ 面板 card-icon-lib.js ↔ C端 common.js', () => {
|
||||
// PHP 源: 解析 CardIconLibrary::ICONS(key => svg)
|
||||
const php = readFileSync(resolve(BE, 'service/CardIconLibrary.php'), 'utf8');
|
||||
@@ -597,7 +675,8 @@ test('design.html ×2: 图层式管理面板 + 素材图标库 + 预览显隐/
|
||||
// 默认行 color: wx#07C160/save#FF9500 固定 → theme#2F6BFF → a.fg; bg: theme#E6EDFF(rgba 叠白近似) → a.bg
|
||||
const previewColorChain = html.match(/color: \(nc\.actIcons && nc\.actIcons\[a\.k\] && nc\.actIcons\[a\.k\]\.color\) \|\| \(a\.k === 'wx' \? '#07C160'[^"]*\)/);
|
||||
assert.ok(previewColorChain, comp + ' 预览 color 回落链存在');
|
||||
assert.match(html, /nc\.actIcons\[a\.k\]\.color \? nc\.actIcons\[a\.k\]\.color : \(a\.k === 'wx' \? '#07C160' : a\.k === 'save' \? '#FF9500' : \(nc\.actGridColorMode === 'theme' \? '#2F6BFF' : a\.fg\)\)"/, comp + ' 默认行前景输入占位与预览同链(wx/save 固定色 + theme/preset 档)');
|
||||
// 2.19.9 起 theme 段可被所选名片设计字段覆盖(previewCard.theme_color 优先, 无则回落 #2F6BFF)
|
||||
assert.match(html, /nc\.actIcons\[a\.k\]\.color \? nc\.actIcons\[a\.k\]\.color : \(a\.k === 'wx' \? '#07C160' : a\.k === 'save' \? '#FF9500' : \(nc\.actGridColorMode === 'theme' \? \(\(nc\.tempData && nc\.tempData\.previewCard && nc\.tempData\.previewCard\.theme_color\) \|\| '#2F6BFF'\) : a\.fg\)\)"/, comp + ' 默认行前景输入占位与预览同链(wx/save 固定色 + theme 段 previewCard 覆盖/preset 档)');
|
||||
assert.match(html, /nc\.actIcons\[a\.k\]\.bg \? nc\.actIcons\[a\.k\]\.bg : \(nc\.actGridColorMode === 'theme' \? '#E6EDFF' : a\.bg\)"/, comp + ' 默认行底色输入占位与预览同链(theme 叠白近似/preset 浅底)');
|
||||
assert.doesNotMatch(html, /actIcons\[a\.k\]\.color \? nc\.actIcons\[a\.k\]\.color : '#2F6BFF'/, comp + ' 默认行前景占位不再恒 #2F6BFF(对多彩档是谎报)');
|
||||
assert.match(html, /:value="c\.color \|\| '#2F6BFF'"/, comp + ' 自定义行前景占位(与自定义预览回落一致)');
|
||||
@@ -618,6 +697,35 @@ test('design.html ×2: 图层式管理面板 + 素材图标库 + 预览显隐/
|
||||
}
|
||||
});
|
||||
|
||||
test('design.html ×2: 2.19.10 detail 对齐面板控件 + 预览消费齐备', () => {
|
||||
for (const comp of ['show', 'self']) {
|
||||
const html = readFileSync(resolve(BE, 'component/view/diy-card-' + comp + '/design.html'), 'utf8');
|
||||
// 面板控件: 9 字段 v-model / chips / toggle
|
||||
assert.match(html, /nc\.rowsBlockBg/, comp + ' rowsBlockBg chips');
|
||||
assert.match(html, /nc\.rowsBlockRadius/, comp + ' rowsBlockRadius slide');
|
||||
assert.match(html, /nc\.rowsBlockPad/, comp + ' rowsBlockPad slide');
|
||||
assert.match(html, /nc\.qrBlockBg/, comp + ' qrBlockBg chips');
|
||||
assert.match(html, /nc\.qrBlockRadius/, comp + ' qrBlockRadius slide');
|
||||
assert.match(html, /nc\.qrBlockPad/, comp + ' qrBlockPad slide');
|
||||
assert.doesNotMatch(html, /layoutCards/, comp + ' layoutCards 已移除');
|
||||
assert.match(html, /v-model="nc\.badgeShow"/, comp + ' badgeShow toggle');
|
||||
assert.match(html, /v-model="nc\.badgeText"/, comp + ' badgeText input');
|
||||
assert.match(html, /v-model="nc\.showRowLabels"/, comp + ' showRowLabels toggle');
|
||||
assert.match(html, /nc\.copyBtnScope/, comp + ' copyBtnScope chips');
|
||||
assert.match(html, /nc\.copyBtnLook/, comp + ' copyBtnLook chips');
|
||||
assert.match(html, /v-model="nc\.qrTipEm"/, comp + ' qrTipEm input');
|
||||
assert.match(html, /v-model="nc\.qrTipSub"/, comp + ' qrTipSub input');
|
||||
assert.match(html, /v-model="nc\.qrUrlShow"/, comp + ' qrUrlShow toggle');
|
||||
// 预览消费: 徽章 + 三列行 + 复制按钮 + qrTip 多段 + URL 行
|
||||
assert.match(html, /nc\.badgeShow.*badgeText|badgeText.*nc\.badgeShow/s, comp + ' 预览消费 badge');
|
||||
assert.match(html, /nc\.showRowLabels/, comp + ' 预览消费 showRowLabels');
|
||||
assert.match(html, /nc\.copyBtnLook/, comp + ' 预览消费 copyBtnLook');
|
||||
assert.match(html, /nc\.qrTipEm/, comp + ' 预览消费 qrTipEm');
|
||||
assert.match(html, /nc\.qrTipSub/, comp + ' 预览消费 qrTipSub');
|
||||
assert.match(html, /nc\.qrUrlShow/, comp + ' 预览消费 qrUrlShow');
|
||||
}
|
||||
});
|
||||
|
||||
test('card-act-icon.js: 类名委托修 2.18 选图标 Bug + $set 写回 + 素材库唯一图标来源 + 次序调整(2.19+2.19.1)', () => {
|
||||
const src = readFileSync(resolve(BE, 'component/view/public/js/card-act-icon.js'), 'utf8');
|
||||
assert.match(src, /on\('click', '\.js-act-lib'/, '素材图标库走类名委托');
|
||||
|
||||
Reference in New Issue
Block a user