refactor(diy-article): 优化文章组件,完善空状态和鲁棒性

1. 新增空数据占位提示
2. 给props添加默认值避免空指针
3. 完善样式和判断逻辑的默认值处理
4. 增加请求失败兜底逻辑
5. 优化响应式更新图片错误的处理
6. 抽离class计算逻辑提升代码可读性
This commit is contained in:
2026-06-13 14:26:52 +08:00
parent ebe2963466
commit 01e7c3e981

View File

@@ -1,265 +1,302 @@
<template>
<x-skeleton data-component-name="diy-article" type="list" :loading="loading" :configs="skeletonConfig">
<view class="article-wrap" :style="warpCss">
<view :class="['list-wrap', value.style]" :style="warpCss">
<!-- 自动垂直滚动 -->
<swiper v-if="list.length > 0" class="auto-scroll-swiper" :style="[swiperStyle]" :vertical="swiperConfig.vertical !== false"
:autoplay="swiperConfig.autoplay !== false" :circular="swiperConfig.circular !== false"
:interval="swiperConfig.interval || 3000" :duration="swiperConfig.duration || 500"
:display-multiple-items="safeDisplayMultipleItems">
<swiper-item v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)">
<view class="swiper-item-content">
<view :class="['item', value.ornament.type]" :style="itemCss">
<view class="article-img">
<image class="cover-img" :src="$util.img(item.cover_img)" mode="widthFix"
@error="imgError(index)" show-menu-by-longpress/>
</view>
<view class="info-wrap">
<text class="title">{{ item.article_title }}</text>
<text class="desc"
style="color:#888;font-size: 24rpx; display: -webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp: 2;overflow: hidden;text-overflow: ellipsis;">{{
item.article_abstract }}</text>
<view class="read-wrap">
<block v-if="item.category_name">
<text class="category-icon"></text>
<text>{{ item.category_name }}</text>
</block>
<text class="date">{{ $util.timeStampTurnTime(item.create_time, 'date')
}}</text>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
</x-skeleton>
</template>
<script>
import DiyMinx from './minx.js'
// 文章
export default {
name: 'diy-article',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
list: [],
loading: true,
skeletonConfig: {
gridRows: 1,
gridRowsGap: '40rpx',
headWidth: '160rpx',
headHeight: '160rpx',
textRows: 2
}
};
},
created() {
this.getList();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getList();
}
},
computed: {
// 获取swiper配置,默认空对象
swiperConfig() {
return this.value?.swiperConfig || {};
},
// 安全的display-multiple-items值,确保不大于swiper-item数量
safeDisplayMultipleItems() {
const displayCount = this.swiperConfig.displayMultipleItems || 3;
const actualCount = this.list.length;
return actualCount > 0 ? Math.min(displayCount, actualCount) : 1;
},
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// swiper容器样式
swiperStyle() {
// 从swiperConfig获取高度,默认600rpx
const height = this.swiperConfig.swiperHeight || 600;
return {
height: height + 'rpx',
width: '100%'
};
},
// 子项样式
itemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color;
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color;
}
return obj;
}
},
methods: {
getList() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.article_id_arr = this.value.articleIds.toString();
}
this.$api.sendRequest({
url: '/api/article/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages_tool/article/detail', {
article_id: item.article_id
});
},
imgError(index) {
if (this.list[index]) this.list[index].cover_img = this.$util.getDefaultImage().article;
}
}
};
</script>
<style lang="scss">
.article-wrap {
.list-wrap {
.auto-scroll-swiper {
height: 600rpx;
/* 可以根据需要调整高度 */
width: 100%;
.swiper-item-content {
padding: 10rpx 20rpx;
box-sizing: border-box;
}
}
.item {
margin: 0;
margin-bottom: 24rpx;
}
.style-1 .item {
padding: 20rpx;
}
&.style-1 {
.item {
display: flex;
margin-top: 0;
&:first-of-type {
margin-top: 0;
}
.article-img {
margin-right: 20rpx;
width: 160rpx;
height: 160rpx;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
}
}
.info-wrap {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
font-weight: bold;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 30rpx;
line-height: 1.5;
}
.abstract {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: $font-size-tag;
}
.read-wrap {
display: flex;
color: #999ca7;
justify-content: flex-start;
align-items: center;
margin-top: 10rpx;
line-height: 1;
text {
font-size: $font-size-tag;
}
.iconfont {
font-size: 36rpx;
vertical-align: bottom;
margin-right: 10rpx;
}
.category-icon {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $base-color;
margin-right: 10rpx;
}
.date {
margin-left: 20rpx;
}
}
}
}
}
}
}
<template>
<x-skeleton data-component-name="diy-article" type="list" :loading="loading" :configs="skeletonConfig">
<view class="article-wrap" :style="warpCss">
<view :class="listClass" :style="warpCss">
<!-- 自动垂直滚动 -->
<swiper v-if="list.length > 0" class="auto-scroll-swiper" :style="swiperStyle"
:vertical="swiperConfig.vertical !== false"
:autoplay="swiperConfig.autoplay !== false" :circular="swiperConfig.circular !== false"
:interval="swiperConfig.interval || 3000" :duration="swiperConfig.duration || 500"
:display-multiple-items="safeDisplayMultipleItems">
<swiper-item v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)">
<view class="swiper-item-content">
<view :class="itemClass" :style="itemCss">
<view class="article-img">
<image class="cover-img" :src="$util.img(item.cover_img)" mode="widthFix"
@error="imgError(index)" show-menu-by-longpress></image>
</view>
<view class="info-wrap">
<text class="title">{{ item.article_title }}</text>
<text class="desc">{{ item.article_abstract }}</text>
<view class="read-wrap">
<block v-if="item.category_name">
<text class="category-icon"></text>
<text class="category-name">{{ item.category_name }}</text>
</block>
<text class="date">{{ $util.timeStampTurnTime(item.create_time, 'date') }}</text>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
<view v-if="list.length === 0 && !loading" class="empty-placeholder">
<text class="empty-text">暂无相关文章</text>
</view>
</view>
</view>
</x-skeleton>
</template>
<script>
// 依赖 ./minx.js 提供 componentRefresh、$util 等装修通用能力
import DiyMinx from './minx.js';
// 文章
export default {
name: 'diy-article',
props: {
value: {
type: Object,
default: () => ({})
}
},
mixins: [DiyMinx],
data() {
return {
list: [],
loading: true,
skeletonConfig: {
gridRows: 1,
gridRowsGap: '40rpx',
headWidth: '160rpx',
headHeight: '160rpx',
textRows: 2
}
};
},
created() {
this.getList();
},
watch: {
// 组件刷新监听(来自 DiyMinx mixin)
componentRefresh() {
this.getList();
}
},
computed: {
// 获取swiper配置,默认空对象
swiperConfig() {
return this.value.swiperConfig || {};
},
// 安全的display-multiple-items值,确保不大于swiper-item数量
safeDisplayMultipleItems() {
const displayCount = this.swiperConfig.displayMultipleItems || 3;
const actualCount = this.list.length;
return actualCount > 0 ? Math.min(displayCount, actualCount) : 1;
},
// 列表容器 class
listClass() {
return ['list-wrap', this.value.style];
},
// 单项 class
itemClass() {
return ['item', (this.value.ornament || {}).type];
},
warpCss() {
var obj = '';
obj += 'background-color:' + (this.value.componentBgColor || 'transparent') + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + (this.value.topAroundRadius || 0) * 2 + 'rpx;';
obj += 'border-top-right-radius:' + (this.value.topAroundRadius || 0) * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + (this.value.bottomAroundRadius || 0) * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + (this.value.bottomAroundRadius || 0) * 2 + 'rpx;';
}
return obj;
},
// swiper容器样式
swiperStyle() {
var height = parseInt(this.swiperConfig.swiperHeight) || 600;
return {
height: height + 'rpx',
width: '100%'
};
},
// 子项样式
itemCss() {
var obj = '';
obj += 'background-color:' + (this.value.elementBgColor || 'transparent') + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + (this.value.topElementAroundRadius || 0) * 2 + 'rpx;';
obj += 'border-top-right-radius:' + (this.value.topElementAroundRadius || 0) * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + (this.value.bottomElementAroundRadius || 0) * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + (this.value.bottomElementAroundRadius || 0) * 2 + 'rpx;';
}
var ornament = this.value.ornament || {};
if (ornament.type == 'shadow') {
obj += 'box-shadow:0 0 10rpx ' + (ornament.color || 'rgba(0, 0, 0, 0.1)') + ';';
}
if (ornament.type == 'stroke') {
obj += 'border:2rpx solid ' + (ornament.color || 'transparent') + ';';
}
return obj;
}
},
methods: {
getList() {
this.loading = true;
var data = {
num: this.value.count || 0
};
if (this.value.sources == 'diy') {
data.num = 0;
data.article_id_arr = (this.value.articleIds || []).toString();
}
this.$api.sendRequest({
url: '/api/article/lists',
data: data,
success: res => {
this.list = (res.code == 0 && res.data) ? res.data : [];
this.loading = false;
},
fail: () => {
this.list = [];
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages_tool/article/detail', {
article_id: item.article_id
});
},
imgError(index) {
if (!this.list[index]) return;
var defaultImg = (this.$util.getDefaultImage() || {}).article;
// 使用 $set 保证响应式更新
this.$set(this.list, index, {
...this.list[index],
cover_img: defaultImg
});
}
}
};
</script>
<style lang="scss">
.article-wrap {
.list-wrap {
.auto-scroll-swiper {
height: 600rpx;
width: 100%;
.swiper-item-content {
padding: 10rpx 20rpx;
box-sizing: border-box;
}
}
.item {
margin: 0;
margin-bottom: 24rpx;
}
.style-1 .item {
padding: 20rpx;
}
&.style-1 {
.item {
display: flex;
margin-top: 0;
&:first-of-type {
margin-top: 0;
}
.article-img {
margin-right: 20rpx;
width: 160rpx;
height: 160rpx;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
}
}
.info-wrap {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
font-weight: bold;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 30rpx;
line-height: 1.5;
}
.desc {
color: #888;
font-size: 24rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.read-wrap {
display: flex;
color: #999ca7;
justify-content: flex-start;
align-items: center;
margin-top: 10rpx;
line-height: 1;
text {
font-size: $font-size-tag;
}
.iconfont {
font-size: 36rpx;
vertical-align: bottom;
margin-right: 10rpx;
}
.category-icon {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $base-color;
margin-right: 10rpx;
}
.category-name {
margin-right: 10rpx;
}
.date {
margin-left: 20rpx;
}
}
}
}
}
// 空数据占位
.empty-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
.empty-text {
color: #999;
font-size: 26rpx;
}
}
}
}
</style>