feat: 初始化音乐项目后台管理系统- 新增后台管理相关页面和功能组件
- 实现管理员评论管理、数据统计等功能 - 添加后台布局和路由管理 -配置数据库和Redis连接 - 实现文件上传和访问路径配置 - 添加Sa-Token安全配置
This commit is contained in:
502
music-qianduan/src/views/Home.vue
Normal file
502
music-qianduan/src/views/Home.vue
Normal file
@@ -0,0 +1,502 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { usePlayerStore } from '../stores/player'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import HomeBanner from '../components/HomeBanner.vue'
|
||||
import { getLatestMusic } from '../api/music'
|
||||
import { getLatestPlaylist } from '../api/playlist'
|
||||
import { getLatestSinger } from '../api/singer'
|
||||
import { processMusicUrl } from '../api/music'
|
||||
import { processImageUrl } from '../utils/imageUtils'
|
||||
|
||||
const router = useRouter()
|
||||
const playerStore = usePlayerStore()
|
||||
|
||||
// 新歌推荐
|
||||
const newSongs = ref([])
|
||||
const loadingNewSongs = ref(false)
|
||||
|
||||
// 新歌单推荐
|
||||
const newPlaylists = ref([])
|
||||
const loadingNewPlaylists = ref(false)
|
||||
|
||||
// 新歌手推荐
|
||||
const newSingers = ref([])
|
||||
const loadingNewSingers = ref(false)
|
||||
|
||||
// 获取最新音乐
|
||||
const fetchLatestMusic = async () => {
|
||||
loadingNewSongs.value = true
|
||||
try {
|
||||
const res = await getLatestMusic(8) // 获取8首最新音乐
|
||||
if (res.code === 200) {
|
||||
newSongs.value = res.data.map(item => ({
|
||||
...item,
|
||||
cover: processMusicUrl(item.cover),
|
||||
url: processMusicUrl(item.url)
|
||||
}))
|
||||
console.log('最新音乐数据:', newSongs.value)
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取最新音乐失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取最新音乐失败:', error)
|
||||
ElMessage.error('获取最新音乐失败')
|
||||
} finally {
|
||||
loadingNewSongs.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取最新歌单
|
||||
const fetchLatestPlaylist = async () => {
|
||||
loadingNewPlaylists.value = true
|
||||
try {
|
||||
const res = await getLatestPlaylist(8) // 获取8个最新歌单
|
||||
if (res.code === 200) {
|
||||
newPlaylists.value = res.data
|
||||
console.log('最新歌单数据:', newPlaylists.value)
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取最新歌单失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取最新歌单失败:', error)
|
||||
ElMessage.error('获取最新歌单失败')
|
||||
} finally {
|
||||
loadingNewPlaylists.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取最新歌手
|
||||
const fetchLatestSinger = async () => {
|
||||
loadingNewSingers.value = true
|
||||
try {
|
||||
const res = await getLatestSinger(8) // 获取8个最新歌手
|
||||
if (res.code === 200) {
|
||||
newSingers.value = res.data.map(item => ({
|
||||
...item,
|
||||
avatar: processImageUrl(item.avatar)
|
||||
}))
|
||||
console.log('最新歌手数据:', newSingers.value)
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取最新歌手失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取最新歌手失败:', error)
|
||||
ElMessage.error('获取最新歌手失败')
|
||||
} finally {
|
||||
loadingNewSingers.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 播放音乐
|
||||
const playSong = (song) => {
|
||||
playerStore.setCurrentMusic(song)
|
||||
}
|
||||
|
||||
// 跳转到歌单详情
|
||||
const goToPlaylist = (id) => {
|
||||
router.push(`/playlist/${id}`)
|
||||
}
|
||||
|
||||
// 跳转到歌手详情
|
||||
const goToSinger = (id) => {
|
||||
router.push(`/singer/${id}`)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 格式化播放次数
|
||||
const formatPlayCount = (count) => {
|
||||
if (!count) return '0'
|
||||
if (count < 10000) {
|
||||
return count.toString()
|
||||
} else if (count < 100000000) {
|
||||
return (count / 10000).toFixed(1) + '万'
|
||||
} else {
|
||||
return (count / 100000000).toFixed(1) + '亿'
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取数据
|
||||
fetchLatestMusic()
|
||||
fetchLatestPlaylist()
|
||||
fetchLatestSinger()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="home-container">
|
||||
<!-- 轮播图 -->
|
||||
<div class="banner-section">
|
||||
<HomeBanner />
|
||||
</div>
|
||||
|
||||
<!-- 新歌推荐 -->
|
||||
<div class="section new-songs-section" v-loading="loadingNewSongs">
|
||||
<div class="section-header">
|
||||
<h2>新歌推荐</h2>
|
||||
<el-button text @click="router.push('/music')">查看更多</el-button>
|
||||
</div>
|
||||
<div class="song-list">
|
||||
<div v-for="song in newSongs" :key="song.id" class="song-item" @dblclick="playSong(song)">
|
||||
<div class="song-cover">
|
||||
<img :src="song.cover" :alt="song.name">
|
||||
<div class="play-icon" @click="playSong(song)">
|
||||
<el-icon><video-play /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="song-info">
|
||||
<div class="song-name">{{ song.name }}</div>
|
||||
<div class="song-singer">{{ song.singer || (song.singers && song.singers.map(s => s.name).join(', ')) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新歌单推荐 -->
|
||||
<div class="section new-playlists-section" v-loading="loadingNewPlaylists">
|
||||
<div class="section-header">
|
||||
<h2>新歌单推荐</h2>
|
||||
<el-button text @click="router.push('/playlist')">查看更多</el-button>
|
||||
</div>
|
||||
<div class="playlist-list">
|
||||
<div v-for="playlist in newPlaylists" :key="playlist.id" class="playlist-item" @click="goToPlaylist(playlist.id)">
|
||||
<div class="playlist-cover">
|
||||
<img :src="playlist.cover" :alt="playlist.name">
|
||||
<div class="playlist-info-overlay">
|
||||
<div class="playlist-play-count" v-if="playlist.playCount">
|
||||
<el-icon><video-play /></el-icon>
|
||||
{{ formatPlayCount(playlist.playCount) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="playlist-info">
|
||||
<div class="playlist-name">{{ playlist.name }}</div>
|
||||
<div class="playlist-desc">{{ playlist.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新歌手推荐 -->
|
||||
<div class="section new-singers-section" v-loading="loadingNewSingers">
|
||||
<div class="section-header">
|
||||
<h2>新歌手推荐</h2>
|
||||
<el-button text @click="router.push('/singer')">查看更多</el-button>
|
||||
</div>
|
||||
<div class="singer-list">
|
||||
<div v-for="singer in newSingers" :key="singer.id" class="singer-item" @click="goToSinger(singer.id)">
|
||||
<div class="singer-avatar">
|
||||
<img :src="singer.avatar" :alt="singer.name">
|
||||
</div>
|
||||
<div class="singer-name">{{ singer.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home-container {
|
||||
padding: 20px 20px 80px 20px; /* 增加左右内边距,底部填充为播放器留出空间 */
|
||||
min-height: calc(100vh - 140px); /* 减去顶部导航和底部播放器的高度 */
|
||||
background-color: transparent; /* 设置为透明,让布局背景透出来 */
|
||||
border-radius: 15px; /* 更大的圆角 */
|
||||
box-shadow: none; /* 移除阴影,因为背景是渐变 */
|
||||
}
|
||||
|
||||
/* 轮播图样式 */
|
||||
.banner-section {
|
||||
margin-bottom: 30px; /* 增加间距 */
|
||||
border-radius: 10px; /* 添加圆角 */
|
||||
overflow: hidden; /* 确保轮播图内容也在圆角内 */
|
||||
}
|
||||
|
||||
/* 各类推荐区域样式 */
|
||||
.section {
|
||||
margin-bottom: 30px; /* 增加间距 */
|
||||
padding: 20px; /* 增加内边距 */
|
||||
background-color: rgba(255, 255, 255, 0.85); /* 半透明白色背景 */
|
||||
border-radius: 12px; /* 圆角 */
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); /* 稍微调整阴影 */
|
||||
border: none; /* 移除底部边框 */
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 25px; /* 增加间距 */
|
||||
padding: 0; /* 移除内边距,因为 section 已经有内边距 */
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.6rem; /* 稍大字体 */
|
||||
color: #ff6b81; /* 可爱的粉色 */
|
||||
font-weight: 600; /* 加粗一点 */
|
||||
}
|
||||
|
||||
/* 修改 Element Plus 按钮样式 */
|
||||
.section-header .el-button {
|
||||
background-color: #ffb6c1; /* 淡粉色背景 */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 20px; /* 圆润按钮 */
|
||||
padding: 8px 15px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.section-header .el-button:hover {
|
||||
background-color: #ff8a9b; /* 悬停时深一点的粉色 */
|
||||
}
|
||||
|
||||
/* 音乐列表样式 */
|
||||
.song-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); /* 稍微增大最小宽度 */
|
||||
gap: 20px; /* 增加间隙 */
|
||||
padding: 0; /* 移除内边距 */
|
||||
}
|
||||
|
||||
.song-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease-in-out;
|
||||
background-color: #fff9f9; /* 非常淡的粉色背景 */
|
||||
border-radius: 10px; /* 圆角 */
|
||||
padding: 10px;
|
||||
box-shadow: 0 2px 5px rgba(255, 107, 129, 0.1); /* 粉色阴影 */
|
||||
}
|
||||
|
||||
.song-item:hover {
|
||||
transform: translateY(-6px) scale(1.03); /* 悬停效果更明显 */
|
||||
box-shadow: 0 4px 10px rgba(255, 107, 129, 0.2);
|
||||
}
|
||||
|
||||
.song-cover {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 100%;
|
||||
border-radius: 8px; /* 圆角 */
|
||||
overflow: hidden;
|
||||
margin-bottom: 12px; /* 增加间距 */
|
||||
}
|
||||
|
||||
.song-cover img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease; /* 图片缩放过渡 */
|
||||
}
|
||||
|
||||
.song-item:hover .song-cover img {
|
||||
transform: scale(1.1); /* 悬停时图片放大 */
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 182, 193, 0.5); /* 淡粉色遮罩 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
border-radius: 8px; /* 保持圆角一致 */
|
||||
}
|
||||
|
||||
.play-icon .el-icon {
|
||||
font-size: 3.5rem; /* 稍大图标 */
|
||||
color: white; /* 白色图标 */
|
||||
}
|
||||
|
||||
.song-cover:hover .play-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.song-info {
|
||||
padding: 0; /* 移除内边距 */
|
||||
text-align: center; /* 居中显示 */
|
||||
}
|
||||
|
||||
.song-name {
|
||||
font-weight: 600; /* 加粗 */
|
||||
margin-bottom: 6px; /* 增加间距 */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #333; /* 深灰色 */
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.song-singer {
|
||||
font-size: 0.8rem;
|
||||
color: #888; /* 浅灰色 */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 歌单列表样式 */
|
||||
.playlist-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.playlist-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease-in-out;
|
||||
border-radius: 10px;
|
||||
overflow: hidden; /* 隐藏溢出的内容 */
|
||||
box-shadow: 0 2px 5px rgba(173, 216, 230, 0.2); /* 淡蓝色阴影 */
|
||||
background-color: #f0f8ff; /* 淡蓝色背景 */
|
||||
}
|
||||
|
||||
.playlist-item:hover {
|
||||
transform: translateY(-6px) scale(1.03);
|
||||
box-shadow: 0 4px 10px rgba(173, 216, 230, 0.4);
|
||||
}
|
||||
|
||||
.playlist-cover {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 100%;
|
||||
border-radius: 0; /* 移除这里的圆角,让item控制 */
|
||||
overflow: hidden;
|
||||
margin-bottom: 0; /* 移除间距 */
|
||||
}
|
||||
|
||||
.playlist-cover img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.playlist-item:hover .playlist-cover img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.playlist-info-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.6)); /* 渐变效果 */
|
||||
opacity: 1; /* 默认显示 */
|
||||
transition: background-color 0.3s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end; /* 播放次数放底部 */
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.playlist-cover:hover .playlist-info-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.2); /* 悬停时加深一点 */
|
||||
}
|
||||
|
||||
.playlist-play-count {
|
||||
color: white;
|
||||
font-size: 0.85rem; /* 稍大字体 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-shadow: 1px 1px 2px rgba(0,0,0,0.5); /* 添加文字阴影 */
|
||||
}
|
||||
|
||||
.playlist-play-count .el-icon {
|
||||
margin-right: 4px; /* 调整间距 */
|
||||
font-size: 1rem; /* 调整图标大小 */
|
||||
}
|
||||
|
||||
.playlist-info {
|
||||
padding: 10px; /* 增加内边距 */
|
||||
background-color: #f0f8ff; /* 与 item 背景色一致 */
|
||||
}
|
||||
|
||||
.playlist-name {
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #333;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.playlist-desc {
|
||||
font-size: 0.8rem;
|
||||
color: #777; /* 稍深的灰色 */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 歌手列表样式 */
|
||||
.singer-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); /* 调整大小 */
|
||||
gap: 25px; /* 增加间隙 */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.singer-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease-in-out;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.singer-item:hover {
|
||||
transform: translateY(-6px) scale(1.05); /* 悬停效果 */
|
||||
}
|
||||
|
||||
.singer-avatar {
|
||||
width: 100%;
|
||||
padding-top: 100%;
|
||||
border-radius: 50%; /* 圆形头像 */
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin-bottom: 12px; /* 增加间距 */
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 添加阴影 */
|
||||
border: 3px solid #fff; /* 添加白色边框 */
|
||||
}
|
||||
|
||||
.singer-avatar img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.singer-item:hover .singer-avatar img {
|
||||
transform: scale(1.15); /* 悬停时放大 */
|
||||
}
|
||||
|
||||
.singer-name {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #555; /* 稍深颜色 */
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Specific section backgrounds - REMOVED */
|
||||
</style>
|
||||
Reference in New Issue
Block a user