Files
graduation-design/music-qianduan/src/views/Home.vue
ikmkj e4b828ead9 feat(comment): 添加音乐评论功能并优化热门音乐相关逻辑
- 在 CommentServiceImpl 中增加针对音乐的评论计数逻辑
- 在 Home.vue 中添加热门音乐推荐模块
- 修改 Music.vue 中的热门音乐获取逻辑,支持分页
- 在 MusicDetail.vue 中实现评论列表展示和评论提交功能
- 更新 MusicList.vue,增加音乐详情查看按钮
- 在 MusicServiceImpl 中同步更新播放次数和点赞数
2025-07-25 21:47:18 +08:00

553 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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, getHotMusic } 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 hotSongs = ref([])
const loadingHotSongs = 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 fetchHotMusic = async () => {
loadingHotSongs.value = true
try {
// 注意:这里调用的是新的 hot-music API
const res = await getHotMusic(8) // 获取8首热门音乐
if (res.code === 200) {
hotSongs.value = res.data.map(item => ({
...item,
cover: processMusicUrl(item.cover),
url: processMusicUrl(item.url)
}))
} else {
ElMessage.error(res.message || '获取热门音乐失败')
}
} catch (error) {
console.error('获取热门音乐失败:', error)
ElMessage.error('获取热门音乐失败')
} finally {
loadingHotSongs.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()
fetchHotMusic() // 获取热门音乐
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 hot-songs-section" v-loading="loadingHotSongs">
<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 hotSongs" :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>