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