1. 新增空数据占位提示 2. 给props添加默认值避免空指针 3. 完善样式和判断逻辑的默认值处理 4. 增加请求失败兜底逻辑 5. 优化响应式更新图片错误的处理 6. 抽离class计算逻辑提升代码可读性
302 lines
7.4 KiB
Vue
302 lines
7.4 KiB
Vue
<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> |