refactor(project): Reorganize frontend and backend directories
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user