feat: 初始化音乐项目后台管理系统- 新增后台管理相关页面和功能组件
- 实现管理员评论管理、数据统计等功能 - 添加后台布局和路由管理 -配置数据库和Redis连接 - 实现文件上传和访问路径配置 - 添加Sa-Token安全配置
This commit is contained in:
384
music-qianduan/src/layouts/MainLayout.vue
Normal file
384
music-qianduan/src/layouts/MainLayout.vue
Normal file
@@ -0,0 +1,384 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useUserStore } from '../stores/user'
|
||||
import { usePlayerStore } from '../stores/player'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import MusicPlayer from '../components/MusicPlayer.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
const playerStore = usePlayerStore()
|
||||
const activeIndex = ref('/')
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 监听路由变化,更新激活的菜单项
|
||||
watch(() => route.path, (newPath) => {
|
||||
activeIndex.value = newPath
|
||||
}, { immediate: true })
|
||||
|
||||
const isLoggedIn = computed(() => userStore.isLoggedIn)
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = () => {
|
||||
if (!searchKeyword.value.trim()) return
|
||||
|
||||
router.push({
|
||||
path: '/search',
|
||||
query: { keyword: searchKeyword.value }
|
||||
})
|
||||
|
||||
// 搜索后清空搜索框
|
||||
searchKeyword.value = ''
|
||||
}
|
||||
|
||||
// 跳转到登录页面
|
||||
const goToLogin = () => {
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
// 导航到个人中心
|
||||
const goToUserCenter = () => {
|
||||
router.push('/user')
|
||||
}
|
||||
|
||||
// 导航到后台管理
|
||||
const goToAdmin = () => {
|
||||
router.push('/admin')
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const logout = () => {
|
||||
userStore.logout()
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main-layout">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div class="header-content">
|
||||
<div class="logo">音乐应用</div>
|
||||
<!-- 自定义导航菜单 -->
|
||||
<div class="custom-nav">
|
||||
<router-link to="/" class="nav-item" :class="{ 'active': activeIndex === '/' }">首页</router-link>
|
||||
<router-link to="/music" class="nav-item" :class="{ 'active': activeIndex === '/music' }">音乐</router-link>
|
||||
<router-link to="/playlist" class="nav-item" :class="{ 'active': activeIndex === '/playlist' }">歌单</router-link>
|
||||
<router-link to="/singer" class="nav-item" :class="{ 'active': activeIndex === '/singer' }">歌手</router-link>
|
||||
<router-link to="/user" class="nav-item" :class="{ 'active': activeIndex === '/user' }" v-if="isLoggedIn">个人中心</router-link>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="search-box">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索音乐、歌单、歌手"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon class="el-input__icon" @click="handleSearch">
|
||||
<Search />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<div class="user-actions">
|
||||
<template v-if="isLoggedIn">
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link">
|
||||
{{ userStore.user?.nickname || userStore.user?.username }}
|
||||
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="userStore.user?.role === 1" @click="goToAdmin">后台管理</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button type="primary" size="small" @click="goToLogin">登录</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<el-main>
|
||||
<div class="content-container">
|
||||
<router-view />
|
||||
</div>
|
||||
</el-main>
|
||||
|
||||
<!-- 音乐播放器 -->
|
||||
<div class="player-container">
|
||||
<MusicPlayer />
|
||||
</div>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<style scoped>
|
||||
.main-layout {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; /* 防止出现双滚动条 */
|
||||
}
|
||||
|
||||
.el-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; /* 防止出现双滚动条 */
|
||||
}
|
||||
|
||||
.el-header {
|
||||
padding: 0;
|
||||
/* background-color: rgba(255, 255, 255, 0.8); /* 半透明白色背景 */
|
||||
background: linear-gradient(to bottom, rgba(255, 240, 245, 0.9), rgba(230, 247, 255, 0.9)); /* 淡粉到淡蓝的半透明渐变 */
|
||||
backdrop-filter: blur(5px); /* 添加模糊效果 */
|
||||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.08); /* 调整阴影 */
|
||||
height: auto !important;
|
||||
min-height: 70px;
|
||||
position: sticky; /* 使 header 固定在顶部 */
|
||||
top: 0;
|
||||
z-index: 100; /* 确保在最上层 */
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 30px; /* 增加左右内边距 */
|
||||
position: relative;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.6rem; /* 稍大字体 */
|
||||
font-weight: 600;
|
||||
color: #ff6b81; /* 可爱粉色 */
|
||||
z-index: 20;
|
||||
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.5); /* 添加轻微文字阴影 */
|
||||
}
|
||||
|
||||
/* 自定义导航菜单样式 */
|
||||
.custom-nav {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
height: 70px;
|
||||
line-height: 70px;
|
||||
}
|
||||
|
||||
.custom-nav .nav-item {
|
||||
display: inline-block;
|
||||
padding: 0 20px; /* 调整内边距 */
|
||||
font-size: 1rem; /* 调整字体大小 */
|
||||
font-weight: 500;
|
||||
color: #555; /* 深灰色 */
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
height: 70px;
|
||||
line-height: 70px;
|
||||
min-width: 80px; /* 调整最小宽度 */
|
||||
text-align: center;
|
||||
position: relative; /* 为下划线定位 */
|
||||
border-radius: 5px; /* 添加轻微圆角 */
|
||||
}
|
||||
|
||||
.custom-nav .nav-item::after { /* 添加下划线动画效果 */
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 3px;
|
||||
bottom: 15px; /* 调整下划线位置 */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #ffb6c1; /* 淡粉色 */
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.custom-nav .nav-item:hover {
|
||||
color: #ff6b81; /* 悬停时粉色 */
|
||||
background-color: rgba(255, 255, 255, 0.3); /* 悬停时轻微背景 */
|
||||
}
|
||||
|
||||
.custom-nav .nav-item:hover::after {
|
||||
width: 60%; /* 悬停时显示下划线 */
|
||||
}
|
||||
|
||||
.custom-nav .nav-item.active {
|
||||
color: #ff6b81; /* 激活时粉色 */
|
||||
font-weight: 600; /* 激活时加粗 */
|
||||
}
|
||||
|
||||
.custom-nav .nav-item.active::after {
|
||||
width: 60%; /* 激活时显示下划线 */
|
||||
background-color: #ff6b81; /* 激活时下划线颜色加深 */
|
||||
}
|
||||
|
||||
|
||||
/* 移除旧的 Element Plus 导航样式 (如果不再使用) */
|
||||
.main-nav .el-menu-item { display: none !important; }
|
||||
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 25px; /* 增加间距 */
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.search-box .el-input__wrapper { /* 美化搜索框 */
|
||||
border-radius: 20px !important;
|
||||
background-color: rgba(255, 255, 255, 0.7) !important;
|
||||
box-shadow: none !important;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
.search-box .el-input__inner {
|
||||
color: #555;
|
||||
}
|
||||
.search-box .el-input__icon {
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
}
|
||||
.search-box .el-input__icon:hover {
|
||||
color: #ff6b81;
|
||||
}
|
||||
|
||||
|
||||
.user-actions .el-button { /* 美化登录按钮 */
|
||||
border-radius: 20px;
|
||||
background-color: #ffb6c1;
|
||||
border-color: #ffb6c1;
|
||||
color: white;
|
||||
}
|
||||
.user-actions .el-button:hover {
|
||||
background-color: #ff8a9b;
|
||||
border-color: #ff8a9b;
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #555; /* 用户名颜色 */
|
||||
font-weight: 500;
|
||||
}
|
||||
.el-dropdown-link:hover {
|
||||
color: #ff6b81; /* 悬停时粉色 */
|
||||
}
|
||||
.el-dropdown-menu__item:hover {
|
||||
background-color: #fff0f5 !important; /* 下拉菜单悬停背景 */
|
||||
color: #ff6b81 !important; /* 下拉菜单悬停文字颜色 */
|
||||
}
|
||||
|
||||
.el-main {
|
||||
padding: 10px;
|
||||
background: linear-gradient(to bottom, #fff0f5, #e6f7ff); /* 淡粉到淡蓝的渐变 */
|
||||
flex: 1;
|
||||
margin-bottom: 0; /* 移除底部边距 */
|
||||
padding-bottom: 100px; /* 增加底部填充,为播放器留出空间 */
|
||||
overflow: hidden; /* 禁用外层滚动 */
|
||||
height: calc(100vh - 70px - 80px); /* 减去头部和播放器高度 */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 内容容器样式 - 将内容限制在70%宽度并居中 */
|
||||
.content-container {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
/* 隐藏滚动条 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
/* 隐藏Webkit浏览器的滚动条 */
|
||||
.content-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 音乐播放器容器样式 */
|
||||
.player-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 80px;
|
||||
z-index: 1000;
|
||||
background-color: #fff; /* 添加背景色 */
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
|
||||
}
|
||||
|
||||
/* 响应式样式 */
|
||||
@media screen and (max-width: 1200px) {
|
||||
.custom-nav .nav-item {
|
||||
padding: 0 12px;
|
||||
min-width: 60px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
width: 85%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
.custom-nav {
|
||||
position: relative;
|
||||
left: auto;
|
||||
transform: none;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
flex-wrap: wrap; /* 允许菜单项换行 */
|
||||
}
|
||||
|
||||
.custom-nav .nav-item {
|
||||
padding: 0 10px;
|
||||
min-width: 50px;
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user