refactor(project): Reorganize frontend and backend directories

This commit is contained in:
2026-03-18 10:45:15 +08:00
parent cc9eaf4cdf
commit 92a139e470
513 changed files with 28251 additions and 287 deletions
@@ -0,0 +1,205 @@
<template>
<div class="home-page">
<!-- 顶部导航 -->
<van-nav-bar title="HAOVP" fixed placeholder>
<template #right>
<van-icon name="search" size="22" @click="router.push('/search')" />
</template>
</van-nav-bar>
<!-- 分类导航 -->
<div class="category-nav">
<van-tabs v-model:active="activeCategory" sticky shrink @change="onCategoryChange">
<van-tab title="推荐" name="recommend" />
<van-tab title="热门" name="hot" />
<van-tab v-for="cat in categories" :key="cat.id" :title="cat.name" :name="cat.id" />
</van-tabs>
</div>
<!-- 视频列表 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="loadMore"
class="video-list"
>
<div class="video-grid">
<VideoCard
v-for="video in videos"
:key="video.id"
:video="video"
@click="goToVideo(video.id)"
/>
</div>
</van-list>
</van-pull-refresh>
<!-- 底部导航 -->
<AppTabbar />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { showToast } from 'vant'
import VideoCard from '@/components/video/VideoCard.vue'
import AppTabbar from '@/components/layout/AppTabbar.vue'
import { videoApi } from '@/api/video.ts'
import { categoryApi, type Category } from '@/api/category.ts'
import { recommendApi } from '@/api/recommend.ts'
import type { VideoInfo } from '@/types'
const router = useRouter()
// 分类
const categories = ref<Category[]>([])
const activeCategory = ref<string | number>('recommend')
// 视频列表
const videos = ref<VideoInfo[]>([])
const loading = ref(false)
const finished = ref(false)
const refreshing = ref(false)
const currentPage = ref(1)
const pageSize = 20
// 加载分类
const loadCategories = async () => {
try {
const res = await categoryApi.getList()
categories.value = res
} catch (error) {
console.error('加载分类失败:', error)
}
}
// 加载视频列表
const loadVideos = async (isRefresh = false) => {
if (isRefresh) {
currentPage.value = 1
videos.value = []
finished.value = false
}
loading.value = true
try {
let res
const params = {
page: currentPage.value,
size: pageSize,
...(typeof activeCategory.value === 'number' && { categoryId: activeCategory.value })
}
if (activeCategory.value === 'recommend') {
res = await recommendApi.getHome(params)
} else if (activeCategory.value === 'hot') {
res = await recommendApi.getHot(params)
} else {
res = await videoApi.getList(params)
}
if (isRefresh) {
videos.value = res.records
} else {
videos.value.push(...res.records)
}
// 判断是否还有更多
if (videos.value.length >= res.total || res.records.length < pageSize) {
finished.value = true
}
currentPage.value++
} catch (error: any) {
showToast(error.message || '加载失败')
finished.value = true
} finally {
loading.value = false
refreshing.value = false
}
}
// 加载更多
const loadMore = () => {
if (!finished.value) {
loadVideos()
}
}
// 下拉刷新
const onRefresh = () => {
loadVideos(true)
}
// 分类切换
const onCategoryChange = () => {
loadVideos(true)
}
// 跳转视频详情
const goToVideo = (id: number) => {
router.push(`/video/${id}`)
}
onMounted(() => {
loadCategories()
loadVideos()
})
</script>
<style scoped lang="scss">
.home-page {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 60px;
@media (min-width: 768px) {
padding-bottom: 0;
}
}
.category-nav {
background: #fff;
position: sticky;
top: 46px;
z-index: 100;
:deep(.van-tabs__nav) {
padding: 0 12px;
}
:deep(.van-tab) {
padding: 0 12px;
font-size: 14px;
}
:deep(.van-tabs__line) {
background: #00a1d6;
}
}
.video-list {
padding: 12px;
}
.video-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
@media (min-width: 768px) {
grid-template-columns: repeat(4, 1fr);
}
@media (min-width: 1200px) {
grid-template-columns: repeat(5, 1fr);
}
@media (min-width: 1600px) {
grid-template-columns: repeat(6, 1fr);
}
}
</style>