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,33 +11,57 @@
<!-- 顶部搜索栏 -->
<div class="search-bar">
<el-input
v-model="searchQuery"
placeholder="搜索视频..."
class="search-input"
clearable
<div class="search-wrapper">
<div class="search-box">
<el-input
v-model="searchQuery"
placeholder="搜索视频..."
class="search-input"
clearable
>
<template #prefix>
<el-icon class="search-icon"><Search /></el-icon>
</template>
<template #append>
<el-button type="primary" :icon="Search" @click="handleSearch">
<span class="button-text">搜索</span>
</el-button>
</template>
</el-input>
</div>
<div class="search-tags">
<div class="hot-searches">
<div class="hot-label">
<el-icon><Histogram /></el-icon>
<span>热门搜索</span>
</div>
<el-tag
v-for="tag in hotSearches"
:key="tag"
size="small"
effect="plain"
class="hot-tag"
@click="searchQuery = tag"
>
<el-icon><Search /></el-icon>
{{ tag }}
</el-tag>
</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"
>
<template #prefix>
<el-icon class="search-icon"><Search /></el-icon>
</template>
<template #append>
<el-button type="primary" :icon="Search" @click="handleSearch">
<span class="button-text">搜索</span>
</el-button>
</template>
</el-input>
<div class="hot-searches">
<span class="label">热门搜索</span>
<el-tag
v-for="tag in hotSearches"
:key="tag"
size="small"
effect="plain"
class="hot-tag"
@click="searchQuery = tag"
>
{{ tag }}
</el-tag>
<el-icon><component :is="item.icon" /></el-icon>
<span>{{ item.label }}</span>
</div>
</div>
@ -123,13 +147,24 @@
</div>
<div class="video-grid">
<video-card
v-for="video in displayVideos"
:key="video.id"
:video="video"
@click="playVideo(video)"
@favorite="toggleFavorite"
/>
<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
v-for="video in displayVideos"
:key="video.id"
:video="video"
@click="playVideo(video)"
@favorite="toggleFavorite"
/>
</template>
</div>
<!-- 分页器 -->
@ -147,7 +182,7 @@
</template>
<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 VideoCard from '@/components/video/VideoCard.vue'
import VideoGrid from '@/components/video/VideoGrid.vue'
@ -159,6 +194,7 @@ const activeCategory = ref('recommended')
const sortBy = ref('latest')
const currentPage = ref(1)
const pageSize = ref(12)
const isLoading = ref(false)
//
const featuredVideos = ref([
@ -389,10 +425,16 @@ const handleSearch = () => {
// TODO:
}
const handleCategoryChange = (category: string) => {
const handleCategoryChange = async (category: string) => {
activeCategory.value = category
currentPage.value = 1
// TODO:
isLoading.value = true
try {
// TODO:
await new Promise(resolve => setTimeout(resolve, 1000))
} finally {
isLoading.value = false
}
}
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>
<style scoped>
@ -487,60 +563,214 @@ const hotSearches = [
}
.search-bar {
margin-bottom: 30px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
margin-bottom: 24px;
background: #fff;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
}
.hot-searches {
display: flex;
align-items: center;
gap: 8px;
.label {
color: #909399;
font-size: 13px;
}
.hot-tag {
cursor: pointer;
transition: all 0.3s;
&:hover {
color: #409EFF;
border-color: #409EFF;
}
}
.search-wrapper {
max-width: 800px;
margin: 0 auto;
}
.search-box {
margin-bottom: 20px;
}
.search-input {
width: 100%;
max-width: 600px;
:deep(.el-input__wrapper) {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
border-radius: 8px;
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;
font-size: 15px;
}
:deep(.search-icon) {
font-size: 18px;
color: #909399;
}
:deep(.el-input-group__append) {
padding: 0;
.el-button {
border-radius: 0 8px 8px 0;
padding: 0 24px;
height: 44px;
padding: 0 24px;
font-size: 15px;
border-radius: 0 4px 4px 0;
.el-icon {
font-size: 18px;
}
.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 {
.custom-tabs {
:deep(.el-tabs__item) {
@ -677,4 +907,20 @@ const hotSearches = [
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>