New file |
| | |
| | | <template> |
| | | <view class="search-page"> |
| | | <!-- 搜索区域 --> |
| | | <view class="search-bar"> |
| | | <input |
| | | class="search-input" |
| | | v-model="searchQuery.keyword" |
| | | placeholder="请输入搜索关键词" |
| | | placeholder-class="placeholder-style" |
| | | @confirm="handleSearch" |
| | | /> |
| | | <button class="search-btn" @click="handleSearch">搜索</button> |
| | | </view> |
| | | |
| | | <!-- 瀑布流视频列表 --> |
| | | <view class="waterfall-container"> |
| | | <view class="waterfall-left"> |
| | | <view |
| | | class="video-item" |
| | | v-for="(item, index) in leftList" |
| | | :key="item.id" |
| | | @click="playVideo(item)" |
| | | > |
| | | <image class="video-cover" :src="item.coverUrl" mode="widthFix"></image> |
| | | <view class="video-info"> |
| | | <text class="video-title">{{item.title}}</text> |
| | | <view class="video-tags"> |
| | | <text class="tag" v-for="(tag, i) in item.tagList" :key="i">{{tag.tagName}}</text> |
| | | </view> |
| | | <view class="video-stats"> |
| | | <text class="like-count">❤️ {{item.thumbsUpNum}}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="waterfall-right"> |
| | | <view |
| | | class="video-item" |
| | | v-for="(item, index) in rightList" |
| | | :key="item.id" |
| | | @click="playVideo(item)" |
| | | > |
| | | <image class="video-cover" :src="item.coverUrl" mode="widthFix"></image> |
| | | <view class="video-info"> |
| | | <text class="video-title">{{item.title}}</text> |
| | | <view class="video-tags"> |
| | | <text class="tag" v-for="(tag, i) in item.tagList" :key="i">{{tag.tagName}}</text> |
| | | </view> |
| | | <view class="video-stats"> |
| | | <text class="like-count">❤️ {{item.thumbsUpNum}}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 加载更多提示 --> |
| | | <view class="load-more" v-if="loading"> |
| | | <text>加载中...</text> |
| | | </view> |
| | | <view class="load-more" v-if="noMore"> |
| | | <text>没有更多了</text> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import {videoSearch} from "@/api/video.js" |
| | | export default { |
| | | data() { |
| | | return { |
| | | videoList: [], // 所有视频数据 |
| | | leftList: [], // 左侧列视频 |
| | | rightList: [], // 右侧列视频 |
| | | searchQuery: { |
| | | keyword: '', |
| | | pageNumber: 1, // 虽然es是从第0页开始的,但是为了在跳转到video-play页之后和其它类型的查询保持一致,这里从1开始,后端默认-1 |
| | | pageSize: 10 |
| | | }, |
| | | loading: false, |
| | | noMore: false |
| | | } |
| | | }, |
| | | onLoad() { |
| | | // 初始加载一些数据 |
| | | this.loadVideos(); |
| | | }, |
| | | methods: { |
| | | handleSearch() { |
| | | if (!this.searchQuery.keyword.trim()) { |
| | | uni.showToast({ |
| | | title: '请输入搜索关键词', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // 重置数据 |
| | | this.searchQuery.pageNumber = 1; |
| | | this.videoList = []; |
| | | this.leftList = []; |
| | | this.rightList = []; |
| | | this.noMore = false; |
| | | |
| | | // 执行搜索 |
| | | this.loadVideos(); |
| | | }, |
| | | |
| | | async loadVideos() { |
| | | if (this.loading || this.noMore) return; |
| | | |
| | | this.loading = true; |
| | | uni.showLoading({ title: '加载中' }); |
| | | |
| | | try { |
| | | videoSearch(this.searchQuery).then(res => { |
| | | if (this.searchQuery.pageNumber === 1) { |
| | | this.videoList = res.data.data |
| | | } else { |
| | | this.videoList = [ |
| | | ...this.videoList, |
| | | ...res.data.data.filter( |
| | | (newItem) => !this.videoList.some((oldItem) => oldItem.id === newItem.id) |
| | | ), |
| | | ]; |
| | | } |
| | | // 分配瀑布流 |
| | | this.distributeWaterfall(); |
| | | |
| | | if (res.data.data.length < this.searchQuery.pageSize) { |
| | | this.noMore = true |
| | | } else { |
| | | this.searchQuery.pageNumber += 1 |
| | | } |
| | | }) |
| | | } catch (error) { |
| | | console.error('加载视频失败:', error); |
| | | uni.showToast({ |
| | | title: '加载失败', |
| | | icon: 'none' |
| | | }); |
| | | } finally { |
| | | this.loading = false; |
| | | uni.hideLoading(); |
| | | } |
| | | }, |
| | | |
| | | // 分配瀑布流左右列 |
| | | distributeWaterfall() { |
| | | let leftHeight = this.getColumnHeight(this.leftList); |
| | | let rightHeight = this.getColumnHeight(this.rightList); |
| | | |
| | | // 从当前videoList中未分配的开始 |
| | | const startIndex = this.leftList.length + this.rightList.length; |
| | | |
| | | for (let i = startIndex; i < this.videoList.length; i++) { |
| | | const item = this.videoList[i]; |
| | | item["index"] = i; |
| | | // 计算这个item的大概高度(根据封面比例) |
| | | const itemHeight = 200 + Math.random() * 50; // 简单模拟高度差异 |
| | | |
| | | if (leftHeight <= rightHeight) { |
| | | this.leftList.push(item); |
| | | leftHeight += itemHeight; |
| | | } else { |
| | | this.rightList.push(item); |
| | | rightHeight += itemHeight; |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // 获取列的高度(简单实现) |
| | | getColumnHeight(list) { |
| | | return list.length * 250; // 假设每个item大约250高度 |
| | | }, |
| | | |
| | | // 播放视频 |
| | | playVideo(item) { |
| | | const searchPlayInfo = { |
| | | videoList: this.videoList, |
| | | nomore: this.noMore, |
| | | pageNumber: this.searchQuery.pageNumber, |
| | | playIndex: item.index, |
| | | keyword: this.searchQuery.keyword |
| | | } |
| | | uni.setStorageSync("searchPlayInfo", searchPlayInfo) |
| | | uni.navigateTo({ |
| | | url: `/pages/video/video-play?videoFrom=search` |
| | | }); |
| | | }, |
| | | }, |
| | | |
| | | // 上拉加载更多 |
| | | onReachBottom() { |
| | | this.loadVideos(); |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-page { |
| | | padding: 20rpx; |
| | | } |
| | | |
| | | .search-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | height: 70rpx; |
| | | padding: 0 20rpx; |
| | | background-color: #efefef; |
| | | border-radius: 35rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .placeholder-style { |
| | | color: #999; |
| | | } |
| | | |
| | | .search-btn { |
| | | width: 120rpx; |
| | | height: 70rpx; |
| | | margin-left: 20rpx; |
| | | line-height: 70rpx; |
| | | font-size: 28rpx; |
| | | background-color: #07c160; |
| | | color: white; |
| | | border-radius: 35rpx; |
| | | } |
| | | |
| | | .waterfall-container { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .waterfall-left, |
| | | .waterfall-right { |
| | | width: 48%; |
| | | } |
| | | |
| | | .video-item { |
| | | margin-bottom: 20rpx; |
| | | background: #fff; |
| | | border-radius: 12rpx; |
| | | overflow: hidden; |
| | | box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .video-cover { |
| | | width: 100%; |
| | | display: block; |
| | | } |
| | | |
| | | .video-info { |
| | | padding: 16rpx; |
| | | } |
| | | |
| | | .video-title { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | display: -webkit-box; |
| | | -webkit-box-orient: vertical; |
| | | -webkit-line-clamp: 2; |
| | | overflow: hidden; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .video-tags { |
| | | margin-top: 10rpx; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 20rpx; |
| | | } |
| | | |
| | | .tag { |
| | | font-size: 24rpx; |
| | | color: #07c160; |
| | | background: #e8f5e9; |
| | | border-radius: 20rpx; |
| | | flex: 1 1 calc(50% - 20rpx); /* 考虑gap的宽度 */ |
| | | min-width: calc(50% - 20rpx); |
| | | max-width: 100%; |
| | | } |
| | | |
| | | .video-stats { |
| | | margin-top: 10rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .like-count { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | } |
| | | |
| | | .load-more { |
| | | text-align: center; |
| | | padding: 20rpx; |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | </style> |