fix(diy-article): 修复无图文章样式异常,新增字号样式配置

1. 为无图文章项添加no-image类适配样式,统一与有图项的视觉高度
2. 新增标签、摘要、阅读区的字号CSS变量支持,替换硬编码字号
3. 调整列表项间距默认值和swiper行间距,优化布局紧凑度
4. 补充默认字号配置项,兼容老数据缺失字段的情况
This commit is contained in:
2026-06-15 10:03:26 +08:00
parent f9d144159a
commit f7f1b1191e

View File

@@ -14,7 +14,7 @@
:display-multiple-items="safeDisplayMultipleItems"> :display-multiple-items="safeDisplayMultipleItems">
<swiper-item v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"> <swiper-item v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)">
<view class="swiper-item-content"> <view class="swiper-item-content">
<view :class="['item', value.ornament && value.ornament.type]" :style="itemStyle"> <view :class="['item', value.ornament && value.ornament.type, !item.cover_img && value.style !== 'style-4' ? 'no-image' : '']" :style="itemStyle">
<!-- 封面图:无图时隐藏图位;style-4 纯文字始终不显示 --> <!-- 封面图:无图时隐藏图位;style-4 纯文字始终不显示 -->
<view class="article-img" v-if="item.cover_img && value.style !== 'style-4'"> <view class="article-img" v-if="item.cover_img && value.style !== 'style-4'">
<image class="cover-img" :src="$util.img(item.cover_img)" mode="aspectFill" <image class="cover-img" :src="$util.img(item.cover_img)" mode="aspectFill"
@@ -52,7 +52,7 @@
</swiper> </swiper>
<!-- 非滚动模式(scrollMode==='none')时的普通列表 --> <!-- 非滚动模式(scrollMode==='none')时的普通列表 -->
<template v-if="!useScroll"> <template v-if="!useScroll">
<view v-for="(item, index) in list" :key="index" :class="['item', value.ornament && value.ornament.type]" :style="itemStyle" <view v-for="(item, index) in list" :key="index" :class="['item', value.ornament && value.ornament.type, !item.cover_img && value.style !== 'style-4' ? 'no-image' : '']" :style="itemStyle"
@tap.stop="toDetail(item)"> @tap.stop="toDetail(item)">
<view class="article-img" v-if="item.cover_img && value.style !== 'style-4'"> <view class="article-img" v-if="item.cover_img && value.style !== 'style-4'">
<image class="cover-img" :src="$util.img(item.cover_img)" mode="aspectFill" <image class="cover-img" :src="$util.img(item.cover_img)" mode="aspectFill"
@@ -141,16 +141,21 @@ export default {
// 注: 用 objToStyle 自动过滤 undefined/空值,避免输出 background-color:; 这类脏 CSS // 注: 用 objToStyle 自动过滤 undefined/空值,避免输出 background-color:; 这类脏 CSS
warpWrapStyle() { warpWrapStyle() {
const v = this.value || {}; const v = this.value || {};
// ========== 单位约定 ==========
// 数据里所有「字号/图标/间距/圆角」类数字字段统一存为 rpx(uni-app 响应式像素);
// 老数据若是字符串("24rpx")或缺字段,经 r() 归一化为数字,再拼 'rpx' 后缀;
// 老数据若用 px 存(早期 DIY 圆角),需在设计面板里手动改为 rpx 量级再保存(否则会被 r() 截断)。
// 后端预览(PC 浏览器)做 /2 折算,把 rpx 转 px。
const r = (val, fb) => this._num(val, fb); const r = (val, fb) => this._num(val, fb);
const isRootRound = v.componentAngle === 'round'; const isRootRound = v.componentAngle === 'round';
const isItemRound = v.elementAngle === 'round'; const isItemRound = v.elementAngle === 'round';
// 标签胶囊 3 色(默认值与后端 design.js nestedDefaults 对齐) // 标签胶囊 3 色 + 字号(默认值与后端 design.js nestedDefaults 对齐)
const tag = v.tagStyle || {}; const tag = v.tagStyle || {};
// 摘要文字色(默认值与后端 design.js nestedDefaults 对齐) // 摘要文字色 + 字号
const abs = v.abstractStyle || {}; const abs = v.abstractStyle || {};
// 查看更多(字号 + 图标大小) // 查看更多(字号 + 图标大小)
const vm = v.viewMore || {}; const vm = v.viewMore || {};
// 阅读区(文字色 + 图标大小) // 阅读区(文字色 + 图标大小 + 文字大小)
const rw = v.readWrapStyle || {}; const rw = v.readWrapStyle || {};
// 标题字号(每风格独立 1/2/3/4) // 标题字号(每风格独立 1/2/3/4)
const tfs1 = r(v.titleFontSize1, 30); const tfs1 = r(v.titleFontSize1, 30);
@@ -166,25 +171,26 @@ export default {
'--article-bottom-around-radius': isRootRound ? `${r(v.bottomAroundRadius, 0) * 2}rpx` : '0rpx', '--article-bottom-around-radius': isRootRound ? `${r(v.bottomAroundRadius, 0) * 2}rpx` : '0rpx',
'--article-top-element-around-radius': isItemRound ? `${r(v.topElementAroundRadius, 0) * 2}rpx` : '0rpx', '--article-top-element-around-radius': isItemRound ? `${r(v.topElementAroundRadius, 0) * 2}rpx` : '0rpx',
'--article-bottom-element-around-radius': isItemRound ? `${r(v.bottomElementAroundRadius, 0) * 2}rpx` : '0rpx', '--article-bottom-element-around-radius': isItemRound ? `${r(v.bottomElementAroundRadius, 0) * 2}rpx` : '0rpx',
// 标签胶囊 3 色 // 标签胶囊 3 色 + 字号(rpx)
'--article-tag-bg-color': tag.bgColor || '#f0f9ff', '--article-tag-bg-color': tag.bgColor || '#f0f9ff',
'--article-tag-text-color': tag.textColor || '#1e6bb8', '--article-tag-text-color': tag.textColor || '#1e6bb8',
'--article-tag-border-color': tag.borderColor || '#b3d8ff', '--article-tag-border-color': tag.borderColor || '#b3d8ff',
// 摘要文字色 '--article-tag-font-size': r(tag.fontSize, 22) + 'rpx',
// 摘要文字色 + 字号(rpx)
'--article-abstract-color': abs.textColor || '#888888', '--article-abstract-color': abs.textColor || '#888888',
// 阅读区文字色 '--article-abstract-font-size': r(abs.fontSize, 24) + 'rpx',
// 阅读区文字色 + 图标大小 + 文字大小(rpx)
'--article-meta-color': rw.color || '#999ca7', '--article-meta-color': rw.color || '#999ca7',
// 阅读区图标大小(rpx)
'--article-meta-icon-size': r(rw.iconSize, 24) + 'rpx', '--article-meta-icon-size': r(rw.iconSize, 24) + 'rpx',
// 查看更多字号(rpx) '--article-meta-font-size': r(rw.fontSize, 24) + 'rpx',
'--article-view-more-font-size': (typeof vm.fontSize === 'number' ? vm.fontSize : r(vm.fontSize, 26)) + 'rpx', // 查看更多字号 + 图标大小(rpx,统一走 r() 兜底,不再 typeof 区分)
// 查看更多图标大小(rpx) '--article-view-more-font-size': r(vm.fontSize, 26) + 'rpx',
'--article-view-more-icon-size': r(vm.iconSize, 24) + 'rpx', '--article-view-more-icon-size': r(vm.iconSize, 24) + 'rpx',
// 列表项间距(rpx) // 列表项间距(rpx)
'--article-item-gap': r(v.itemGap, 24) + 'rpx', '--article-item-gap': r(v.itemGap, 16) + 'rpx',
// 分割线颜色(主要用于 style-4) // 分割线颜色(主要用于 style-4)
'--article-divider-color': v.dividerColor || '#eeeeee', '--article-divider-color': v.dividerColor || '#eeeeee',
// 标题字号 4 风格独立 // 标题字号 4 风格独立(rpx)
'--article-title-fs-1': tfs1 + 'rpx', '--article-title-fs-1': tfs1 + 'rpx',
'--article-title-fs-2': tfs2 + 'rpx', '--article-title-fs-2': tfs2 + 'rpx',
'--article-title-fs-3': tfs3 + 'rpx', '--article-title-fs-3': tfs3 + 'rpx',
@@ -221,7 +227,7 @@ export default {
}, },
// ========== 字段显示配置(带默认值,避免老数据缺字段时报错) ========== // ========== 字段显示配置(带默认值,避免老数据缺字段时报错) ==========
abstractStyle() { abstractStyle() {
return Object.assign({ support: true, control: true }, this.value.abstractStyle || {}); return Object.assign({ support: true, control: true, fontSize: 24 }, this.value.abstractStyle || {});
}, },
readNumStyle() { readNumStyle() {
return Object.assign({ support: true, control: true }, this.value.readNumStyle || {}); return Object.assign({ support: true, control: true }, this.value.readNumStyle || {});
@@ -230,15 +236,15 @@ export default {
return Object.assign({ support: true, control: false }, this.value.dianzanStyle || {}); return Object.assign({ support: true, control: false }, this.value.dianzanStyle || {});
}, },
tagStyle() { tagStyle() {
return Object.assign({ support: true, control: true }, this.value.tagStyle || {}); return Object.assign({ support: true, control: true, fontSize: 22 }, this.value.tagStyle || {});
}, },
viewMore() { viewMore() {
return Object.assign({ support: true, control: false, text: '查看更多', color: '#999999', iconSize: 24 }, return Object.assign({ support: true, control: false, text: '查看更多', color: '#999999', iconSize: 24 },
this.value.viewMore || {}); this.value.viewMore || {});
}, },
// 阅读区样式(文字色 + 图标大小) // 阅读区样式(文字色 + 图标大小 + 文字大小)
readWrapStyle() { readWrapStyle() {
return Object.assign({ color: '#999ca7', iconSize: 24 }, return Object.assign({ color: '#999ca7', iconSize: 24, fontSize: 24 },
this.value.readWrapStyle || {}); this.value.readWrapStyle || {});
}, },
// ========== 滚动配置(同时兼容老数据 swiperConfig 和新字段 scrollMode) ========== // ========== 滚动配置(同时兼容老数据 swiperConfig 和新字段 scrollMode) ==========
@@ -261,8 +267,12 @@ export default {
return actualCount > 0 ? Math.min(displayCount, actualCount) : 1; return actualCount > 0 ? Math.min(displayCount, actualCount) : 1;
}, },
// swiper 高度(rpx)优先级: // swiper 高度(rpx)优先级:
// 1. 老/新数据显式设置了 swiperConfig.swiperHeight(>0) → 用配置值 // 1. 老/新数据显式设置了 swiperConfig.swiperHeight(>0) → 配置值
// 2. 否则按 style 算单行高度 × 实际显示行数 + 行间距 // 2. 否则按 style 算单行高度 × 显示行数 + 行间距
// 注:风格1 perRow=210,正好容纳:
// 有图 item 200(图160+info 160 被 stretch+padding40)
// 无图 item ~200(标题/摘要单行截断+space-between 上下分布+padding40)
// swiper 整体 = 210*3+10 = 640rpx,行间紧凑,有图无图视觉高度统一。
swiperHeight() { swiperHeight() {
// 1. 显式设置了 swiperHeight (>0) // 1. 显式设置了 swiperHeight (>0)
var configHeight = parseInt(this.swiperConfig.swiperHeight) || 0; var configHeight = parseInt(this.swiperConfig.swiperHeight) || 0;
@@ -271,13 +281,13 @@ export default {
// 注:swiper 内 item 已去掉 margin-bottom,swiper-item 高度 = perRow // 注:swiper 内 item 已去掉 margin-bottom,swiper-item 高度 = perRow
var perRow; var perRow;
switch (this.value.style) { switch (this.value.style) {
case 'style-1': perRow = 200; break; // 图160 + padding 上下20*2 = 200 case 'style-1': perRow = 210; break; // 容纳有图200/无图200+10rpx 缓冲
case 'style-2': perRow = 540; break; // 图320 + info 220 case 'style-2': perRow = 540; break; // 图320 + info 220
case 'style-3': perRow = 300; break; // 图200 + info 100 case 'style-3': perRow = 300; break; // 图200 + info 100
case 'style-4': perRow = 120; break; // 纯文字 case 'style-4': perRow = 120; break; // 纯文字
default: perRow = 200; break; default: perRow = 200; break;
} }
return perRow * this.safeDisplayMultipleItems + 30; // N 行 + 30rpx 行间距总空间 return perRow * this.safeDisplayMultipleItems + 10; // N 行 + 10rpx 行间距总空间(更紧凑)
} }
// 注: :class 一律在模板里用 :class="['static', dynamic]" 内联数组写法 // 注: :class 一律在模板里用 :class="['static', dynamic]" 内联数组写法
// 不再用 listClass/itemClass computed (computed 返回 array 时,uni-app WXML 编译 // 不再用 listClass/itemClass computed (computed 返回 array 时,uni-app WXML 编译
@@ -365,12 +375,15 @@ export default {
--article-tag-bg-color: #f0f9ff; --article-tag-bg-color: #f0f9ff;
--article-tag-text-color: #1e6bb8; --article-tag-text-color: #1e6bb8;
--article-tag-border-color: #b3d8ff; --article-tag-border-color: #b3d8ff;
--article-tag-font-size: 22rpx;
--article-abstract-color: #888888; --article-abstract-color: #888888;
--article-abstract-font-size: 24rpx;
--article-meta-color: #999ca7; --article-meta-color: #999ca7;
--article-meta-icon-size: 24rpx; --article-meta-icon-size: 24rpx;
--article-meta-font-size: 24rpx;
--article-view-more-font-size: 26rpx; --article-view-more-font-size: 26rpx;
--article-view-more-icon-size: 24rpx; --article-view-more-icon-size: 24rpx;
--article-item-gap: 24rpx; --article-item-gap: 16rpx;
--article-divider-color: #eeeeee; --article-divider-color: #eeeeee;
--article-title-fs-1: 30rpx; --article-title-fs-1: 30rpx;
--article-title-fs-2: 30rpx; --article-title-fs-2: 30rpx;
@@ -402,7 +415,7 @@ export default {
} }
.item { .item {
margin-bottom: var(--article-item-gap, 24rpx); margin-bottom: var(--article-item-gap, 16rpx);
/* 背景 / 圆角通过 CSS 变量从 .article-wrap 继承(由 warpWrapStyle 注入) */ /* 背景 / 圆角通过 CSS 变量从 .article-wrap 继承(由 warpWrapStyle 注入) */
background-color: var(--article-element-bg-color, transparent); background-color: var(--article-element-bg-color, transparent);
border-top-left-radius: var(--article-top-element-around-radius, 0); border-top-left-radius: var(--article-top-element-around-radius, 0);
@@ -432,7 +445,8 @@ export default {
border-radius: 20rpx; border-radius: 20rpx;
padding: 2rpx 14rpx; padding: 2rpx 14rpx;
margin: 0 8rpx 8rpx 0; margin: 0 8rpx 8rpx 0;
font-size: 22rpx; /* 标签字号由 CSS 变量驱动 */
font-size: var(--article-tag-font-size, 22rpx);
} }
} }
@@ -444,7 +458,8 @@ export default {
margin-top: 10rpx; margin-top: 10rpx;
line-height: 1; line-height: 1;
flex-wrap: wrap; flex-wrap: wrap;
font-size: $font-size-tag; /* 阅读区文字大小由 CSS 变量驱动(覆盖原 SCSS 变量 $font-size-tag) */
font-size: var(--article-meta-font-size, 24rpx);
.category-icon { .category-icon {
width: 8rpx; width: 8rpx;
@@ -534,7 +549,8 @@ export default {
.desc { .desc {
/* 颜色由 CSS 变量驱动(来自 warpWrapStyle 注入;老数据走默认值) */ /* 颜色由 CSS 变量驱动(来自 warpWrapStyle 注入;老数据走默认值) */
color: var(--article-abstract-color, #888888); color: var(--article-abstract-color, #888888);
font-size: 24rpx; /* 摘要字号由 CSS 变量驱动 */
font-size: var(--article-abstract-font-size, 24rpx);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
display: -webkit-box; display: -webkit-box;
@@ -543,6 +559,31 @@ export default {
} }
} }
} }
// ========== 无图紧凑样式:让 item 高度跟有图一致 (~200rpx) ==========
// 背景:无图时 info-wrap 无 image 撑高,内容自然展开 ~232rpx + padding 40 = 272rpx,
// 远超有图时的 200rpx,导致 swiper-item 内行间空白巨大或被裁。
// 解决:无图时 info-wrap 显式 min-height: 160rpx(与有图时 image 撑高一致),
// 标题/摘要单行截断 + 去掉多余 margin,内容 ~124rpx 在 160rpx 容器内用
// space-between 上下分布(标题顶/摘要中/阅读底),与有图场景视觉高度统一。
.item.no-image {
.info-wrap {
min-height: 160rpx;
.title {
-webkit-line-clamp: 1;
margin-bottom: 0;
}
.desc {
-webkit-line-clamp: 1;
}
.tag-list {
margin-top: 0;
}
.read-wrap {
margin-top: 0;
}
}
}
} }
// ========== style-2: 大图卡片(上图下文) ========== // ========== style-2: 大图卡片(上图下文) ==========
@@ -581,7 +622,8 @@ export default {
.desc { .desc {
/* 颜色由 CSS 变量驱动(来自 warpWrapStyle 注入;老数据走默认值) */ /* 颜色由 CSS 变量驱动(来自 warpWrapStyle 注入;老数据走默认值) */
color: var(--article-abstract-color, #888888); color: var(--article-abstract-color, #888888);
font-size: 24rpx; /* 摘要字号由 CSS 变量驱动 */
font-size: var(--article-abstract-font-size, 24rpx);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
display: -webkit-box; display: -webkit-box;
@@ -642,8 +684,7 @@ export default {
.category-name { .category-name {
display: none; display: none;
} }
// style-3 不再硬覆写字号,跟随 --article-meta-font-size(用户配多小就多小)
font-size: 20rpx;
} }
} }
} }