This commit is contained in:
ovo 2024-12-06 01:04:30 +08:00
parent 46419e68bc
commit 9bc552cfb8
1 changed files with 314 additions and 68 deletions

View File

@ -11,6 +11,8 @@
<!-- 顶部搜索栏 --> <!-- 顶部搜索栏 -->
<div class="search-bar"> <div class="search-bar">
<div class="search-wrapper">
<div class="search-box">
<el-input <el-input
v-model="searchQuery" v-model="searchQuery"
placeholder="搜索视频..." placeholder="搜索视频..."
@ -26,8 +28,14 @@
</el-button> </el-button>
</template> </template>
</el-input> </el-input>
</div>
<div class="search-tags">
<div class="hot-searches"> <div class="hot-searches">
<span class="label">热门搜索</span> <div class="hot-label">
<el-icon><Histogram /></el-icon>
<span>热门搜索</span>
</div>
<el-tag <el-tag
v-for="tag in hotSearches" v-for="tag in hotSearches"
:key="tag" :key="tag"
@ -36,10 +44,26 @@
class="hot-tag" class="hot-tag"
@click="searchQuery = tag" @click="searchQuery = tag"
> >
<el-icon><Search /></el-icon>
{{ tag }} {{ tag }}
</el-tag> </el-tag>
</div> </div>
</div> </div>
</div>
</div>
<!-- 分类导航 -->
<div class="category-nav">
<div class="nav-item"
v-for="item in videoCategories"
:key="item.value"
:class="{ active: currentCategory === item.value }"
@click="currentCategory = item.value"
>
<el-icon><component :is="item.icon" /></el-icon>
<span>{{ item.label }}</span>
</div>
</div>
<!-- 视频分类标签 --> <!-- 视频分类标签 -->
<div class="category-tags"> <div class="category-tags">
@ -123,6 +147,16 @@
</div> </div>
<div class="video-grid"> <div class="video-grid">
<template v-if="isLoading">
<div v-for="n in 8" :key="n" class="video-card loading-skeleton">
<div class="thumbnail-wrapper"></div>
<div class="video-info">
<div class="title-skeleton"></div>
<div class="meta-skeleton"></div>
</div>
</div>
</template>
<template v-else>
<video-card <video-card
v-for="video in displayVideos" v-for="video in displayVideos"
:key="video.id" :key="video.id"
@ -130,6 +164,7 @@
@click="playVideo(video)" @click="playVideo(video)"
@favorite="toggleFavorite" @favorite="toggleFavorite"
/> />
</template>
</div> </div>
<!-- 分页器 --> <!-- 分页器 -->
@ -147,7 +182,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed, onMounted } from 'vue'
import { Search, Star, VideoPlay, Calendar, Histogram as Fire, Collection, Timer, VideoCamera, Trophy } from '@element-plus/icons-vue' import { Search, Star, VideoPlay, Calendar, Histogram as Fire, Collection, Timer, VideoCamera, Trophy } from '@element-plus/icons-vue'
import VideoCard from '@/components/video/VideoCard.vue' import VideoCard from '@/components/video/VideoCard.vue'
import VideoGrid from '@/components/video/VideoGrid.vue' import VideoGrid from '@/components/video/VideoGrid.vue'
@ -159,6 +194,7 @@ const activeCategory = ref('recommended')
const sortBy = ref('latest') const sortBy = ref('latest')
const currentPage = ref(1) const currentPage = ref(1)
const pageSize = ref(12) const pageSize = ref(12)
const isLoading = ref(false)
// //
const featuredVideos = ref([ const featuredVideos = ref([
@ -389,10 +425,16 @@ const handleSearch = () => {
// TODO: // TODO:
} }
const handleCategoryChange = (category: string) => { const handleCategoryChange = async (category: string) => {
activeCategory.value = category activeCategory.value = category
currentPage.value = 1 currentPage.value = 1
isLoading.value = true
try {
// TODO: // TODO:
await new Promise(resolve => setTimeout(resolve, 1000))
} finally {
isLoading.value = false
}
} }
const handleSort = () => { const handleSort = () => {
@ -446,6 +488,40 @@ const hotSearches = [
'心理健康', '心理健康',
'智能设备' '智能设备'
] ]
//
const videoCategories = [
{ label: '全部', value: 'all', icon: 'Grid' },
{ label: '健康养生', value: 'health', icon: 'FirstAid' },
{ label: '运动健身', value: 'fitness', icon: 'Position' },
{ label: '营养饮食', value: 'diet', icon: 'Bowl' },
{ label: '心理健康', value: 'mental', icon: 'SwitchButton' },
{ label: '医疗保健', value: 'medical', icon: 'Stethoscope' },
{ label: '智能设备', value: 'device', icon: 'Monitor' },
{ label: '生活技巧', value: 'life', icon: 'House' },
{ label: '文化娱乐', value: 'entertainment', icon: 'Film' },
{ label: '旅游出行', value: 'travel', icon: 'Van' }
]
const currentCategory = ref('all')
//
const hasScroll = ref(false)
//
onMounted(() => {
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const { scrollWidth, clientWidth } = entry.target as HTMLElement
hasScroll.value = scrollWidth > clientWidth
}
})
const scrollContainer = document.querySelector('.tags-container')
if (scrollContainer) {
observer.observe(scrollContainer)
}
})
</script> </script>
<style scoped> <style scoped>
@ -487,60 +563,214 @@ const hotSearches = [
} }
.search-bar { .search-bar {
margin-bottom: 30px; margin-bottom: 24px;
display: flex; background: #fff;
flex-direction: column; border-radius: 12px;
align-items: center; padding: 24px;
gap: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
} }
.hot-searches { .search-wrapper {
display: flex; max-width: 800px;
align-items: center; margin: 0 auto;
gap: 8px;
.label {
color: #909399;
font-size: 13px;
} }
.hot-tag { .search-box {
cursor: pointer; margin-bottom: 20px;
transition: all 0.3s;
&:hover {
color: #409EFF;
border-color: #409EFF;
}
}
} }
.search-input { .search-input {
width: 100%;
max-width: 600px;
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
border-radius: 8px;
padding-left: 16px; padding-left: 16px;
background: #f5f7fa;
box-shadow: none !important;
border: 2px solid transparent;
transition: all 0.3s;
&:hover {
background: #ecf5ff;
}
&.is-focus {
background: #fff;
border-color: #409EFF;
}
}
:deep(.el-input__inner) {
height: 44px; height: 44px;
font-size: 15px;
}
:deep(.search-icon) {
font-size: 18px;
color: #909399;
} }
:deep(.el-input-group__append) { :deep(.el-input-group__append) {
padding: 0; padding: 0;
.el-button { .el-button {
border-radius: 0 8px 8px 0;
padding: 0 24px;
height: 44px; height: 44px;
padding: 0 24px;
font-size: 15px; font-size: 15px;
border-radius: 0 4px 4px 0;
.el-icon {
font-size: 18px;
}
.button-text { .button-text {
margin-left: 4px; margin-left: 8px;
} }
} }
} }
} }
.search-tags {
position: relative;
padding-left: 100px;
}
.hot-searches {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 24px;
.hot-label {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
gap: 6px;
color: #606266;
font-size: 13px;
font-weight: 500;
.el-icon {
font-size: 16px;
color: #f56c6c;
}
}
.hot-tag {
cursor: pointer;
padding: 0 12px;
height: 28px;
border-radius: 14px;
border-color: #e4e7ed;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 4px;
.el-icon {
font-size: 12px;
color: #909399;
}
&:hover {
color: #409EFF;
border-color: #409EFF;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
.el-icon {
color: #409EFF;
}
}
}
}
@media (max-width: 768px) {
.search-tags {
padding-left: 0;
}
.hot-searches {
.hot-label {
position: static;
transform: none;
margin-bottom: 8px;
}
}
}
.category-nav {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
background: #fff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
margin-bottom: 24px;
gap: 12px;
}
.nav-item {
display: flex;
flex-direction: row;
align-items: center;
gap: 12px;
padding: 12px;
cursor: pointer;
border-radius: 8px;
transition: all 0.3s;
.el-icon {
font-size: 18px;
color: #606266;
transition: all 0.3s;
}
span {
font-size: 14px;
color: #606266;
}
&:hover {
background: #f5f7fa;
.el-icon {
color: #409EFF;
transform: scale(1.1);
}
span {
color: #409EFF;
}
}
&.active {
background: #ecf5ff;
.el-icon {
color: #409EFF;
transform: scale(1.1);
}
span {
color: #409EFF;
font-weight: 500;
}
}
}
@media (max-width: 1200px) {
.category-nav {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 8px;
padding: 12px;
}
.nav-item {
padding: 8px;
}
}
.category-tags { .category-tags {
.custom-tabs { .custom-tabs {
:deep(.el-tabs__item) { :deep(.el-tabs__item) {
@ -677,4 +907,20 @@ const hotSearches = [
justify-content: center; justify-content: center;
} }
} }
/* 添加加载动画 */
.loading-skeleton {
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 50%, #f2f2f2 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
</style> </style>