You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							384 lines
						
					
					
						
							8.4 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							384 lines
						
					
					
						
							8.4 KiB
						
					
					
				| <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>
 | |
| 
 |