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,323 @@
<template>
<header class="app-header">
<div class="header-content">
<!-- Logo -->
<div class="logo" @click="router.push('/')">
<svg viewBox="0 0 100 100" class="logo-icon">
<defs>
<linearGradient id="logoGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#00a1d6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#fb7299;stop-opacity:1" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="45" fill="url(#logoGradient)" />
<polygon points="40,30 40,70 75,50" fill="white" />
</svg>
<span class="logo-text">HAOVP</span>
</div>
<!-- 搜索框 -->
<div class="search-box">
<van-search
v-model="searchKeyword"
placeholder="搜索视频、用户"
shape="round"
@search="handleSearch"
>
<template #right-icon>
<van-icon name="search" @click="handleSearch" />
</template>
</van-search>
</div>
<!-- 右侧操作区 -->
<div class="header-actions">
<!-- 投稿按钮 -->
<van-button
v-if="userStore.isLoggedIn"
type="primary"
size="small"
round
@click="router.push('/upload')"
>
<van-icon name="plus" />
投稿
</van-button>
<!-- 未登录 -->
<template v-if="!userStore.isLoggedIn">
<van-button type="primary" size="small" round @click="router.push('/login')">
登录
</van-button>
</template>
<!-- 已登录 -->
<template v-else>
<!-- 消息 -->
<van-popover v-model:show="showMessagePopover" trigger="click" placement="bottom-end">
<div class="message-popover">
<div class="message-item" @click="goToMessages">
<van-icon name="chat-o" />
<span>私信</span>
<van-badge v-if="messageStore.unreadMessageCount" :content="messageStore.unreadMessageCount" />
</div>
<div class="message-item" @click="goToNotifications">
<van-icon name="bell" />
<span>通知</span>
<van-badge v-if="messageStore.unreadNotificationCount" :content="messageStore.unreadNotificationCount" />
</div>
</div>
<template #reference>
<van-badge :content="messageStore.totalUnreadCount || ''" :show-zero="false">
<van-icon name="bell" size="24" class="action-icon" />
</van-badge>
</template>
</van-popover>
<!-- 用户头像 -->
<van-popover v-model:show="showUserPopover" trigger="click" placement="bottom-end">
<div class="user-popover">
<div class="user-info">
<van-image
:src="userStore.userInfo?.avatar || defaultAvatar"
round
width="48"
height="48"
/>
<div class="user-detail">
<div class="nickname">{{ userStore.userInfo?.nickname }}</div>
<div class="level">Lv.{{ userStore.userInfo?.level || 1 }}</div>
</div>
</div>
<van-cell-group inset>
<van-cell title="个人主页" icon="user-o" @click="goToProfile" />
<van-cell title="我的视频" icon="video-o" @click="router.push('/my/videos')" />
<van-cell title="我的收藏" icon="star-o" @click="router.push('/my/favorites')" />
<van-cell title="播放历史" icon="clock-o" @click="router.push('/my/history')" />
<van-cell title="设置" icon="setting-o" @click="router.push('/settings')" />
<van-cell title="退出登录" icon="revoke" @click="handleLogout" />
</van-cell-group>
</div>
<template #reference>
<van-image
:src="userStore.userInfo?.avatar || defaultAvatar"
round
width="32"
height="32"
class="avatar"
/>
</template>
</van-popover>
</template>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore, useMessageStore } from '@/stores'
import { showConfirmDialog, showToast } from 'vant'
const router = useRouter()
const userStore = useUserStore()
const messageStore = useMessageStore()
const searchKeyword = ref('')
const showMessagePopover = ref(false)
const showUserPopover = ref(false)
const defaultAvatar = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI1MCIgZmlsbD0iI2NjYyIvPjxjaXJjbGUgY3g9IjUwIiBjeT0iMzUiIHI9IjE1IiBmaWxsPSIjOTk5Ii8+PGVsbGlwc2UgY3g9IjUwIiBjeT0iNzUiIHJ4PSIyNSIgcnk9IjIwIiBmaWxsPSIjOTk5Ii8+PC9zdmc+'
// 搜索
const handleSearch = () => {
if (searchKeyword.value.trim()) {
router.push({ path: '/search', query: { keyword: searchKeyword.value.trim() } })
}
}
// 跳转到私信
const goToMessages = () => {
showMessagePopover.value = false
router.push('/messages')
}
// 跳转到通知
const goToNotifications = () => {
showMessagePopover.value = false
router.push('/notifications')
}
// 跳转到个人主页
const goToProfile = () => {
showUserPopover.value = false
if (userStore.userId) {
router.push(`/user/${userStore.userId}`)
}
}
// 退出登录
const handleLogout = async () => {
try {
await showConfirmDialog({
title: '提示',
message: '确定要退出登录吗?'
})
await userStore.logout()
showToast('已退出登录')
router.push('/')
} catch {
// 取消操作
}
}
// 初始化
onMounted(() => {
if (userStore.isLoggedIn) {
messageStore.fetchAllUnreadCount()
}
})
</script>
<style scoped lang="scss">
.app-header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--header-height);
background: var(--bg-secondary);
box-shadow: var(--shadow-sm);
z-index: 100;
.header-content {
max-width: var(--max-content-width);
height: 100%;
margin: 0 auto;
padding: 0 var(--spacing-md);
display: flex;
align-items: center;
gap: var(--spacing-lg);
}
}
.logo {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
.logo-icon {
width: 36px;
height: 36px;
}
.logo-text {
font-size: 20px;
font-weight: bold;
background: linear-gradient(to right, var(--primary-color), var(--pink-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
@media (max-width: 768px) {
display: none;
}
}
}
.search-box {
flex: 1;
max-width: 400px;
:deep(.van-search) {
padding: 0;
background: transparent;
.van-search__content {
background: var(--bg-tertiary);
}
}
}
.header-actions {
display: flex;
align-items: center;
gap: var(--spacing-md);
.action-icon {
cursor: pointer;
color: var(--text-secondary);
&:hover {
color: var(--primary-color);
}
}
.avatar {
cursor: pointer;
border: 2px solid transparent;
transition: border-color 0.3s;
&:hover {
border-color: var(--primary-color);
}
}
}
.message-popover,
.user-popover {
padding: var(--spacing-sm);
min-width: 160px;
}
.message-popover {
.message-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
cursor: pointer;
border-radius: var(--radius-sm);
&:hover {
background: var(--bg-tertiary);
}
span {
flex: 1;
}
}
}
.user-popover {
.user-info {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-md);
border-bottom: 1px solid var(--border-light);
.user-detail {
.nickname {
font-weight: 500;
}
.level {
font-size: 12px;
color: var(--primary-color);
}
}
}
:deep(.van-cell-group) {
margin-top: var(--spacing-sm);
}
}
@media (max-width: 768px) {
.search-box {
display: none;
}
}
</style>