Files
graduation-design/music-qianduan/src/views/Music.vue
ikmkj 4b4aaedeef fix(music): 切换标签页后重置页码并优化数据请求逻辑
- 切换标签页时重置当前页码为第 1 页
- 根据不同的标签页请求对应的数据
- 增加对其他标签页的数据请求处理
2025-07-26 11:12:05 +08:00

369 lines
8.8 KiB
Vue

<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { Search } from '@element-plus/icons-vue'
import { getMusicList, getHotMusic, getLatestMusic, processMusicUrl, getMusicStatus } from '../api/music'
import { getMusicTypeList } from '../api/musicType'
import MusicList from '../components/MusicList.vue'
import { ElMessage } from 'element-plus'
import { useUserStore } from '../stores/user'
const router = useRouter()
const userStore = useUserStore()
// 音乐列表数据
const musicList = ref([])
const loading = ref(false)
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
// 筛选条件
const keyword = ref('')
const selectedType = ref(null)
const musicTypes = ref([])
// 标签页
const activeTab = ref('all')
const hotMusicList = ref([])
const latestMusicList = ref([])
const loadingHot = ref(false)
const loadingLatest = ref(false)
// 获取音乐列表
const fetchMusicList = async (params = {}) => {
loading.value = true
try {
const page = params.page || currentPage.value
const size = params.size || pageSize.value
const res = await getMusicList(page, size, keyword.value, selectedType.value)
if (res.code === 200) {
// 兼容不同的数据结构,后端可能返回 list 或 records 字段
musicList.value = res.data.list || res.data.records || []
total.value = res.data.total || 0
console.log('音乐列表数据:', res.data)
// 确保音乐URL和封面URL正确处理
musicList.value.forEach(music => {
if (music.url) {
music.url = processMusicUrl(music.url)
}
if (music.cover) {
music.cover = processMusicUrl(music.cover)
}
})
// 更新音乐状态
updateMusicStatuses(musicList.value)
} else {
ElMessage.error(res.message || '获取音乐列表失败')
}
} catch (error) {
console.error('获取音乐列表失败:', error)
ElMessage.error('获取音乐列表失败,请稍后重试')
} finally {
loading.value = false
}
}
// 获取音乐类型列表
const fetchMusicTypes = async () => {
try {
const res = await getMusicTypeList()
if (res.code === 200) {
musicTypes.value = res.data || []
}
} catch (error) {
console.error('获取音乐类型失败:', error)
}
}
// 处理搜索
const handleSearch = () => {
currentPage.value = 1
fetchMusicList()
}
// 处理类型变化
const handleTypeChange = () => {
currentPage.value = 1
fetchMusicList()
}
// 处理收藏变化
const handleCollectChange = (music) => {
// 可以在这里处理收藏状态变化后的逻辑
console.log('收藏状态变化:', music)
}
// 更新音乐状态
const updateMusicStatuses = async (list) => {
if (!userStore.isLoggedIn || !list || list.length === 0) {
return
}
// 为列表中的每首歌曲获取状态
const promises = list.map(music => getMusicStatus(music.id))
try {
const results = await Promise.all(promises)
results.forEach((res, index) => {
if (res.code === 200 && res.data) {
list[index].collected = res.data.collected
list[index].liked = res.data.liked
}
})
} catch (error) {
console.error('批量获取音乐状态失败:', error)
}
}
// 获取热门音乐
const fetchHotMusic = async (params = {}) => {
loadingHot.value = true
try {
// 从缓存中获取全部50条热门音乐
const res = await getHotMusic(50)
if (res.code === 200) {
const allHotMusic = res.data || []
total.value = allHotMusic.length // 总数是50
// 处理URL
allHotMusic.forEach(music => {
if (music.url) music.url = processMusicUrl(music.url)
if (music.cover) music.cover = processMusicUrl(music.cover)
})
// 更新状态
updateMusicStatuses(allHotMusic)
// 根据当前页码和每页数量进行前端分页
const page = params.page || currentPage.value
const size = params.size || pageSize.value
const startIndex = (page - 1) * size
const endIndex = startIndex + size
hotMusicList.value = allHotMusic.slice(startIndex, endIndex)
} else {
ElMessage.error(res.message || '获取热门音乐失败')
}
} catch (error) {
console.error('获取热门音乐失败:', error)
ElMessage.error('获取热门音乐失败')
} finally {
loadingHot.value = false
}
}
// 获取最新音乐
const fetchLatestMusic = async () => {
loadingLatest.value = true
try {
const res = await getLatestMusic(20) // 获取20首最新音乐
if (res.code === 200) {
latestMusicList.value = res.data || []
// 处理音乐URL和封面URL
latestMusicList.value.forEach(music => {
if (music.url) {
music.url = processMusicUrl(music.url)
}
if (music.cover) {
music.cover = processMusicUrl(music.cover)
}
})
// 更新状态
updateMusicStatuses(latestMusicList.value)
} else {
ElMessage.error(res.message || '获取最新音乐失败')
}
} catch (error) {
console.error('获取最新音乐失败:', error)
ElMessage.error('获取最新音乐失败')
} finally {
loadingLatest.value = false
}
}
// 切换标签页
const handleTabChange = (tab) => {
currentPage.value = 1; // 重置页码
if (tab.paneName === 'hot') {
fetchHotMusic();
} else if (tab.paneName === 'latest') {
fetchLatestMusic();
} else {
fetchMusicList();
}
}
// 初始化
onMounted(() => {
fetchMusicList()
fetchMusicTypes()
})
</script>
<template>
<div class="music-page">
<div class="page-header">
<h1>音乐库</h1>
<div class="filter-container">
<el-select v-model="selectedType" placeholder="音乐类型" clearable @change="handleTypeChange">
<el-option
v-for="item in musicTypes"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
<el-input
v-model="keyword"
placeholder="搜索音乐"
clearable
@keyup.enter="handleSearch"
>
<template #suffix>
<el-icon class="el-input__icon" @click="handleSearch">
<Search />
</el-icon>
</template>
</el-input>
</div>
</div>
<el-tabs v-model="activeTab" @tab-click="handleTabChange">
<el-tab-pane label="全部音乐" name="all">
<MusicList
:list="musicList"
:loading="loading"
:total="total"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
@page-change="fetchMusicList"
@collect-change="handleCollectChange"
/>
</el-tab-pane>
<el-tab-pane label="热门音乐" name="hot">
<MusicList
:list="hotMusicList"
:loading="loadingHot"
:total="total"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
@page-change="fetchHotMusic"
@collect-change="handleCollectChange"
/>
</el-tab-pane>
<el-tab-pane label="最新音乐" name="latest">
<MusicList
:list="latestMusicList"
:loading="loadingLatest"
:showPagination="false"
@collect-change="handleCollectChange"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<style scoped>
.music-page {
padding: 20px 20px 80px 20px;
min-height: calc(100vh - 140px);
background-color: transparent;
border-radius: 15px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
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);
}
.page-header h1 {
margin: 0;
font-size: 1.8rem;
color: #ff6b81;
font-weight: 600;
}
.filter-container {
display: flex;
gap: 15px;
}
.filter-container .el-select {
width: 180px;
}
.filter-container .el-input {
width: 250px;
}
.filter-container :deep(.el-input__wrapper) {
border-radius: 20px;
}
.filter-container .el-icon {
cursor: pointer;
color: #ff6b81;
transition: all 0.3s;
}
.filter-container .el-icon:hover {
color: #ff4757;
transform: scale(1.1);
}
.el-tabs {
margin-top: 20px;
}
.el-tabs :deep(.el-tabs__item) {
font-size: 1rem;
font-weight: 500;
color: #666;
}
.el-tabs :deep(.el-tabs__item.is-active) {
color: #ff6b81;
}
.el-tabs :deep(.el-tabs__active-bar) {
background-color: #ff6b81;
}
.el-tabs :deep(.el-tabs__nav-wrap::after) {
background-color: #ffb6c1;
}
@media (max-width: 768px) {
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.filter-container {
width: 100%;
flex-direction: column;
}
.filter-container .el-select,
.filter-container .el-input {
width: 100%;
}
}
</style>