12 changed files with 2891 additions and 1426 deletions
@ -1,299 +1,384 @@ |
|||
<template> |
|||
<view class="waterfall-layout"> |
|||
<view class="waterfall-container"> |
|||
<!-- 左列 --> |
|||
<view class="column"> |
|||
<view v-for="(item, index) in leftItems" :key="item.id || index" class="waterfall-item" |
|||
@click="handleItemClick(item)"> |
|||
<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 class="item-footer"> |
|||
<view class="user-info"> |
|||
<image |
|||
src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&w=100" |
|||
class="user-avatar" mode="aspectFill" /> |
|||
<text class="username">风景之旅</text> |
|||
</view> |
|||
<view class="like-info"> |
|||
<image :src="showImg('/uploads/20250731/0260884d7a44a483885a026da524e0b8.png')" |
|||
style="height: 22rpx;width: 25rpx;"></image> |
|||
<text class="like-count">100</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(item)"> |
|||
<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 class="item-footer"> |
|||
<view class="user-info"> |
|||
<image |
|||
src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&w=100" |
|||
class="user-avatar" mode="aspectFill" /> |
|||
<text class="username">风景之旅</text> |
|||
</view> |
|||
<view class="like-info"> |
|||
<image :src="showImg('/uploads/20250731/0260884d7a44a483885a026da524e0b8.png')" |
|||
style="height: 22rpx;width: 25rpx;"></image> |
|||
<text class="like-count">120</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<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(item) { |
|||
this.$emit("item-click", item); |
|||
}, |
|||
|
|||
// 获取所有项目 |
|||
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); |
|||
} |
|||
}, |
|||
}, |
|||
}; |
|||
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; |
|||
} |
|||
|
|||
.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; |
|||
} |
|||
|
|||
.like-info { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6rpx; |
|||
} |
|||
|
|||
.like-icon { |
|||
font-size: 24rpx; |
|||
color: #ff6b6b; |
|||
} |
|||
|
|||
.like-count { |
|||
font-size: 22rpx; |
|||
color: #666; |
|||
} |
|||
</style> |
|||
.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> |
|||
|
File diff suppressed because it is too large
File diff suppressed because it is too large
Loading…
Reference in new issue