refactor(diy-article): 优化文章组件,完善空状态和鲁棒性
1. 新增空数据占位提示 2. 给props添加默认值避免空指针 3. 完善样式和判断逻辑的默认值处理 4. 增加请求失败兜底逻辑 5. 优化响应式更新图片错误的处理 6. 抽离class计算逻辑提升代码可读性
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user