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