feat(card-diy): 四组件字号定制化消费 + 契约测试(12 项)

- entry: 主/副标题与按钮字号内联消费(titleSize/subSize/btnSize, px→rpx ×2),
  文字箭头拆 btnArrowStyle(比按钮大一档=28rpx)
- list: 拆 gridNameStyle/scrollNameStyle/metaStyle/scrollMetaStyle, 横滑与
  人数徽标按 -1px 派生保持历史渲染; infoStyle 保持纯色(箭头/空态共用)
- mini(Show/Self 基件): name/cardTitle/sub/label/value/act/btn 七项字号内联,
  拆 cardTitleStyle/rowLabelStyle/detailTextStyle; labelStyle 保持纯色给图标/qr tip
- self: miniConfig 透传 7 个字号字段
- 新增 tests/diy-card-font-size.test.js: 三层口径契约(diy_view 默认值 ↔
  design.html 面板/重置 ↔ vue 消费) + 派生关系 + 颜色 value 兜底 + 反例自测

验证: 契约 12/12 绿; diy-card 全套测试 0 失败; E2E 截图目检通过
This commit is contained in:
2026-10-23 14:21:36 +08:00
parent d64e989c25
commit 0c0a6a1cb8
5 changed files with 334 additions and 26 deletions

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

@@ -55,7 +55,7 @@
<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, 零遮挡) -->
@@ -70,7 +70,7 @@
<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>
<text v-else class="row-label" :style="rowLabelStyle">{{ 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="expanded = !expanded">
@@ -100,7 +100,7 @@
<!-- 发送名片 -->
<view v-if="showDetailBtn" class="mini-detail" :style="detailBtnStyle" hover-class="btn-press" @tap.stop="onDetail">
<text class="detail-text">{{ detailBtnText || '发送名片' }}</text>
<text class="detail-text" :style="detailTextStyle">{{ detailBtnText || '发送名片' }}</text>
</view>
</view>
</view>
@@ -272,16 +272,41 @@ export default {
return this.$util.objToStyle({ background: avatarBg(this.card) });
},
nameStyle() {
return this.$util.objToStyle({ color: this.c.nameColor || CardTokens.color.text });
return this.$util.objToStyle({
color: this.c.nameColor || CardTokens.color.text,
fontSize: px2rpx(num(this.c.nameSize, 17)) + 'rpx',
});
},
/** 名片头衔(设计器 card_title): 默认 13px = 26rpx, 与副行 24rpx 区分 */
cardTitleStyle() {
return this.$util.objToStyle({
color: this.c.infoColor || CardTokens.color.textSub,
fontSize: px2rpx(num(this.c.cardTitleSize, 13)) + 'rpx',
});
},
/** 副行(公司/职位)与空态提示共用: 默认 12px = 24rpx */
subTextStyle() {
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.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',
});
},
badgeStyle() {
return this.$util.objToStyle({ background: avatarBg(this.card) });
@@ -299,7 +324,10 @@ export default {
});
},
actPrimaryTextStyle() {
return this.$util.objToStyle({ color: '#FFFFFF' });
return this.$util.objToStyle({
color: '#FFFFFF',
fontSize: px2rpx(num(this.c.actSize, 12)) + 'rpx',
});
},
actGhostStyle() {
const theme = (this.card && this.card.theme_color) || CardTokens.color.primary;
@@ -312,7 +340,17 @@ export default {
});
},
actGhostTextStyle() {
return this.$util.objToStyle({ color: (this.card && this.card.theme_color) || CardTokens.color.primary });
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',
});
},
detailBtnStyle() {
return this.$util.objToStyle({

View File

@@ -68,12 +68,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,