|
|
|
<template>
|
|
|
|
<view class="waterfall-layout">
|
|
|
|
<!-- 空状态 -->
|
|
|
|
<view v-if="!leftItems.length && !rightItems.length" class="empty-state">
|
|
|
|
<text class="empty-title">暂无内容</text>
|
|
|
|
<text class="empty-desc">快来发布第一篇笔记吧~</text>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 瀑布流内容 -->
|
|
|
|
<view v-else class="waterfall-container">
|
|
|
|
<!-- 左列 -->
|
|
|
|
<view class="column">
|
|
|
|
<view
|
|
|
|
v-for="(item, index) in leftItems"
|
|
|
|
:key="item.id || index"
|
|
|
|
class="waterfall-item"
|
|
|
|
@click="handleItemClick(index, leftItems)"
|
|
|
|
>
|
|
|
|
<image
|
|
|
|
v-if="item.coverImage"
|
|
|
|
:src="item.coverImage && item.coverImage.split(',')[0]"
|
|
|
|
class="item-image"
|
|
|
|
mode="aspectFill"
|
|
|
|
/>
|
|
|
|
<view class="item-content">
|
|
|
|
<text v-if="item.title" class="item-title">{{ item.title }}</text>
|
|
|
|
<view class="item-footer">
|
|
|
|
<view class="user-info">
|
|
|
|
<image
|
|
|
|
:src="item.headImg"
|
|
|
|
class="user-avatar"
|
|
|
|
mode="aspectFill"
|
|
|
|
/>
|
|
|
|
<text class="username">{{ item.nickname }}</text>
|
|
|
|
</view>
|
|
|
|
<view class="like-info">
|
|
|
|
<image
|
|
|
|
v-if="!item.userLiked"
|
|
|
|
src="https://epic.js-dyyj.com/uploads/20250728/2f3ae212c01fa3b67be81abc5723cf5c.png"
|
|
|
|
style="height: 22rpx; width: 25rpx"
|
|
|
|
></image>
|
|
|
|
<image
|
|
|
|
v-else
|
|
|
|
src="https://epic.js-dyyj.com/uploads/20250728/dd7ed269b24e84a2dd141da6ab980fd6.png"
|
|
|
|
style="height: 22rpx; width: 25rpx"
|
|
|
|
></image>
|
|
|
|
<text class="like-count">{{ item.likeCount || 0 }}</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 右列 -->
|
|
|
|
<view class="column">
|
|
|
|
<view
|
|
|
|
v-for="(item, index) in rightItems"
|
|
|
|
:key="item.id || index"
|
|
|
|
class="waterfall-item"
|
|
|
|
@click="handleItemClick(index, rightItems)"
|
|
|
|
>
|
|
|
|
<image
|
|
|
|
v-if="item.coverImage"
|
|
|
|
:src="item.coverImage && item.coverImage.split(',')[0]"
|
|
|
|
class="item-image"
|
|
|
|
mode="aspectFill"
|
|
|
|
/>
|
|
|
|
<view class="item-content">
|
|
|
|
<text v-if="item.title" class="item-title">{{ item.title }}</text>
|
|
|
|
<view class="item-footer">
|
|
|
|
<view class="user-info">
|
|
|
|
<image
|
|
|
|
:src="item.headImg"
|
|
|
|
class="user-avatar"
|
|
|
|
mode="aspectFill"
|
|
|
|
/>
|
|
|
|
<text class="username"
|
|
|
|
>{{ item.nickname }}{{ item.nickname
|
|
|
|
}}{{ item.nickname }}</text
|
|
|
|
>
|
|
|
|
</view>
|
|
|
|
<view class="like-info">
|
|
|
|
<image
|
|
|
|
v-if="!item.userLiked"
|
|
|
|
src="https://epic.js-dyyj.com/uploads/20250728/2f3ae212c01fa3b67be81abc5723cf5c.png"
|
|
|
|
style="height: 22rpx; width: 25rpx"
|
|
|
|
></image>
|
|
|
|
<image
|
|
|
|
v-else
|
|
|
|
src="https://epic.js-dyyj.com/uploads/20250728/dd7ed269b24e84a2dd141da6ab980fd6.png"
|
|
|
|
style="height: 22rpx; width: 25rpx"
|
|
|
|
></image>
|
|
|
|
<text class="like-count">{{ item.likeCount || 0 }}</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
export default {
|
|
|
|
name: "WaterfallLayout",
|
|
|
|
props: {
|
|
|
|
// 数据源
|
|
|
|
items: {
|
|
|
|
type: Array,
|
|
|
|
default: () => [],
|
|
|
|
},
|
|
|
|
// 列数(固定为2列)
|
|
|
|
columnCount: {
|
|
|
|
type: Number,
|
|
|
|
default: 2,
|
|
|
|
},
|
|
|
|
// 列间距(rpx)
|
|
|
|
columnGap: {
|
|
|
|
type: Number,
|
|
|
|
default: 16,
|
|
|
|
},
|
|
|
|
// 项目间距(rpx)
|
|
|
|
itemGap: {
|
|
|
|
type: Number,
|
|
|
|
default: 16,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
leftItems: [],
|
|
|
|
rightItems: [],
|
|
|
|
};
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
items: {
|
|
|
|
handler(newItems) {
|
|
|
|
this.calculateLayout(newItems);
|
|
|
|
},
|
|
|
|
immediate: true,
|
|
|
|
deep: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
this.calculateLayout(this.items);
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
// 获取列的实际高度(通过DOM查询)
|
|
|
|
getColumnHeight(columnRef) {
|
|
|
|
if (!columnRef) return 0;
|
|
|
|
const query = uni.createSelectorQuery().in(this);
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
query
|
|
|
|
.select(columnRef)
|
|
|
|
.boundingClientRect((data) => {
|
|
|
|
resolve(data ? data.height : 0);
|
|
|
|
})
|
|
|
|
.exec();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
// 计算布局
|
|
|
|
calculateLayout(items) {
|
|
|
|
if (!items || !items.length) {
|
|
|
|
this.leftItems = [];
|
|
|
|
this.rightItems = [];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 清空现有数据
|
|
|
|
this.leftItems = [];
|
|
|
|
this.rightItems = [];
|
|
|
|
|
|
|
|
// 逐个添加项目
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
|
|
this.addItem(items[i]);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// 添加单个项目到合适的列
|
|
|
|
addItem(item) {
|
|
|
|
// 简单的交替分配逻辑:比较两列的项目数量
|
|
|
|
if (this.leftItems.length <= this.rightItems.length) {
|
|
|
|
this.leftItems.push(item);
|
|
|
|
} else {
|
|
|
|
this.rightItems.push(item);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// 清空所有项目
|
|
|
|
clearItems() {
|
|
|
|
this.leftItems = [];
|
|
|
|
this.rightItems = [];
|
|
|
|
this.$emit("items-cleared");
|
|
|
|
},
|
|
|
|
|
|
|
|
// 处理项目点击
|
|
|
|
handleItemClick(index, list) {
|
|
|
|
this.$emit("item-click", list[index]);
|
|
|
|
},
|
|
|
|
|
|
|
|
// 获取所有项目
|
|
|
|
getAllItems() {
|
|
|
|
return [...this.leftItems, ...this.rightItems];
|
|
|
|
},
|
|
|
|
|
|
|
|
// 移除项目
|
|
|
|
removeItem(itemId) {
|
|
|
|
// 从左列移除
|
|
|
|
let index = this.leftItems.findIndex((item) => item.id === itemId);
|
|
|
|
if (index !== -1) {
|
|
|
|
this.leftItems.splice(index, 1);
|
|
|
|
this.$emit("item-removed", itemId);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 从右列移除
|
|
|
|
index = this.rightItems.findIndex((item) => item.id === itemId);
|
|
|
|
if (index !== -1) {
|
|
|
|
this.rightItems.splice(index, 1);
|
|
|
|
this.$emit("item-removed", itemId);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
.waterfall-layout {
|
|
|
|
width: 100%;
|
|
|
|
box-sizing: border-box;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 空状态样式 */
|
|
|
|
.empty-state {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
padding: 160rpx 40rpx;
|
|
|
|
text-align: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.empty-icon {
|
|
|
|
width: 240rpx;
|
|
|
|
height: 240rpx;
|
|
|
|
margin-bottom: 40rpx;
|
|
|
|
opacity: 0.6;
|
|
|
|
}
|
|
|
|
|
|
|
|
.empty-title {
|
|
|
|
font-size: 32rpx;
|
|
|
|
color: #666;
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
font-weight: 500;
|
|
|
|
}
|
|
|
|
|
|
|
|
.empty-desc {
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #999;
|
|
|
|
line-height: 1.4;
|
|
|
|
}
|
|
|
|
|
|
|
|
.waterfall-container {
|
|
|
|
display: flex;
|
|
|
|
gap: 16rpx;
|
|
|
|
padding: 0 20rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
}
|
|
|
|
|
|
|
|
.column {
|
|
|
|
flex: 1;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
gap: 16rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.waterfall-item {
|
|
|
|
box-sizing: border-box;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
background: #fff;
|
|
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
|
|
|
overflow: hidden;
|
|
|
|
transition: transform 0.2s ease;
|
|
|
|
}
|
|
|
|
|
|
|
|
.waterfall-item:active {
|
|
|
|
transform: scale(0.98);
|
|
|
|
}
|
|
|
|
|
|
|
|
.item-image {
|
|
|
|
width: 100%;
|
|
|
|
height: 476rpx;
|
|
|
|
object-fit: cover;
|
|
|
|
}
|
|
|
|
|
|
|
|
.item-content {
|
|
|
|
padding: 16rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.item-title {
|
|
|
|
font-size: 28rpx;
|
|
|
|
font-weight: 600;
|
|
|
|
color: #333;
|
|
|
|
line-height: 1.3;
|
|
|
|
margin-bottom: 12rpx;
|
|
|
|
display: -webkit-box;
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
}
|
|
|
|
|
|
|
|
.item-desc {
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: #666;
|
|
|
|
line-height: 1.4;
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
display: -webkit-box;
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
}
|
|
|
|
|
|
|
|
.item-tags {
|
|
|
|
display: flex;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
gap: 8rpx;
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.tag {
|
|
|
|
padding: 4rpx 12rpx;
|
|
|
|
background: #f5f5f5;
|
|
|
|
color: #666;
|
|
|
|
font-size: 20rpx;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
white-space: nowrap;
|
|
|
|
}
|
|
|
|
|
|
|
|
.item-footer {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
margin-top: 16rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.user-info {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap: 12rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.user-avatar {
|
|
|
|
width: 32rpx;
|
|
|
|
height: 32rpx;
|
|
|
|
border-radius: 50%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.username {
|
|
|
|
font-size: 22rpx;
|
|
|
|
color: #666;
|
|
|
|
width: 160rpx;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
white-space: nowrap;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
.like-info {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap: 6rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.like-icon {
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: #ff6b6b;
|
|
|
|
}
|
|
|
|
|
|
|
|
.like-count {
|
|
|
|
font-size: 22rpx;
|
|
|
|
color: #666;
|
|
|
|
}
|
|
|
|
</style>
|