Files
lucky_shop/components-diy/diy-article.vue
ZF sun 01e7c3e981 refactor(diy-article): 优化文章组件,完善空状态和鲁棒性
1. 新增空数据占位提示
2. 给props添加默认值避免空指针
3. 完善样式和判断逻辑的默认值处理
4. 增加请求失败兜底逻辑
5. 优化响应式更新图片错误的处理
6. 抽离class计算逻辑提升代码可读性
2026-06-13 14:26:52 +08:00

302 lines
7.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>