476 lines
11 KiB
Vue
476 lines
11 KiB
Vue
<script setup>
|
|
import { ref, onMounted, watch } 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 hotMusicSource = ref([])
|
|
const hotMusicList = ref([])
|
|
const latestMusicSource = ref([])
|
|
const latestMusicList = ref([])
|
|
const loadingHot = ref(false)
|
|
const loadingLatest = ref(false)
|
|
|
|
const getMusicTypeId = (music) => {
|
|
return music?.typeId ?? music?.musicTypeId ?? music?.type?.id ?? null
|
|
}
|
|
|
|
const applyMusicFilter = (list = []) => {
|
|
const normalizedKeyword = keyword.value.trim().toLowerCase()
|
|
const selectedTypeItem = musicTypes.value.find(item => item.id === selectedType.value)
|
|
const selectedTypeName = selectedTypeItem?.name || ''
|
|
|
|
return list.filter((music) => {
|
|
const musicTypeId = getMusicTypeId(music)
|
|
const musicTypeName = music?.typeName || music?.type?.name || ''
|
|
|
|
const matchType = selectedType.value == null
|
|
|| (musicTypeId != null && Number(musicTypeId) === Number(selectedType.value))
|
|
|| (musicTypeId == null && selectedTypeName && musicTypeName === selectedTypeName)
|
|
|
|
if (!normalizedKeyword) {
|
|
return matchType
|
|
}
|
|
|
|
const searchText = [
|
|
music.name,
|
|
musicTypeName,
|
|
music.album,
|
|
music.singer,
|
|
...(Array.isArray(music.singerNames) ? music.singerNames : [])
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')
|
|
.toLowerCase()
|
|
|
|
return matchType && searchText.includes(normalizedKeyword)
|
|
})
|
|
}
|
|
|
|
// 获取音乐列表
|
|
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
|
|
|
|
// 确保音乐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 updateHotMusicView = (params = {}) => {
|
|
const filteredList = applyMusicFilter(hotMusicSource.value)
|
|
const page = params.page || currentPage.value
|
|
const size = params.size || pageSize.value
|
|
|
|
total.value = filteredList.length
|
|
const startIndex = (page - 1) * size
|
|
const endIndex = startIndex + size
|
|
hotMusicList.value = filteredList.slice(startIndex, endIndex)
|
|
}
|
|
|
|
const updateLatestMusicView = () => {
|
|
latestMusicList.value = applyMusicFilter(latestMusicSource.value)
|
|
}
|
|
|
|
// 获取音乐类型列表
|
|
const fetchMusicTypes = async () => {
|
|
try {
|
|
const res = await getMusicTypeList()
|
|
if (res.code === 200) {
|
|
musicTypes.value = [
|
|
{ id: null, name: '全部' },
|
|
...(res.data || [])
|
|
]
|
|
}
|
|
} catch (error) {
|
|
console.error('获取音乐类型失败:', error)
|
|
}
|
|
}
|
|
|
|
// 处理搜索
|
|
const handleSearch = () => {
|
|
currentPage.value = 1
|
|
|
|
if (activeTab.value === 'hot') {
|
|
updateHotMusicView({ page: 1, size: pageSize.value })
|
|
return
|
|
}
|
|
|
|
if (activeTab.value === 'latest') {
|
|
updateLatestMusicView()
|
|
return
|
|
}
|
|
|
|
fetchMusicList()
|
|
}
|
|
|
|
const handleKeywordClear = () => {
|
|
keyword.value = ''
|
|
currentPage.value = 1
|
|
|
|
if (activeTab.value === 'hot') {
|
|
updateHotMusicView({ page: 1, size: pageSize.value })
|
|
return
|
|
}
|
|
|
|
if (activeTab.value === 'latest') {
|
|
updateLatestMusicView()
|
|
return
|
|
}
|
|
|
|
fetchMusicList()
|
|
}
|
|
|
|
// 处理类型变化
|
|
const handleTypeChange = () => {
|
|
currentPage.value = 1
|
|
|
|
if (activeTab.value === 'hot') {
|
|
updateHotMusicView({ page: 1, size: pageSize.value })
|
|
return
|
|
}
|
|
|
|
if (activeTab.value === 'latest') {
|
|
updateLatestMusicView()
|
|
return
|
|
}
|
|
|
|
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) {
|
|
hotMusicSource.value = res.data || []
|
|
|
|
// 处理URL
|
|
hotMusicSource.value.forEach(music => {
|
|
if (music.url) music.url = processMusicUrl(music.url)
|
|
if (music.cover) music.cover = processMusicUrl(music.cover)
|
|
})
|
|
|
|
// 更新状态
|
|
await updateMusicStatuses(hotMusicSource.value)
|
|
|
|
// 根据筛选和分页展示
|
|
updateHotMusicView(params)
|
|
} 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) {
|
|
latestMusicSource.value = res.data || []
|
|
|
|
// 处理音乐URL和封面URL
|
|
latestMusicSource.value.forEach(music => {
|
|
if (music.url) {
|
|
music.url = processMusicUrl(music.url)
|
|
}
|
|
if (music.cover) {
|
|
music.cover = processMusicUrl(music.cover)
|
|
}
|
|
})
|
|
|
|
// 更新状态
|
|
await updateMusicStatuses(latestMusicSource.value)
|
|
updateLatestMusicView()
|
|
} 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({ page: 1, size: pageSize.value })
|
|
} else if (tab.paneName === 'latest') {
|
|
fetchLatestMusic()
|
|
} else {
|
|
fetchMusicList({ page: 1, size: pageSize.value })
|
|
}
|
|
}
|
|
|
|
watch(keyword, (val) => {
|
|
if (val === '') {
|
|
currentPage.value = 1
|
|
|
|
if (activeTab.value === 'hot') {
|
|
updateHotMusicView({ page: 1, size: pageSize.value })
|
|
return
|
|
}
|
|
|
|
if (activeTab.value === 'latest') {
|
|
updateLatestMusicView()
|
|
return
|
|
}
|
|
|
|
fetchMusicList({ page: 1, size: pageSize.value })
|
|
}
|
|
})
|
|
|
|
// 初始化
|
|
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
|
|
@clear="handleKeywordClear"
|
|
@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="updateHotMusicView"
|
|
@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>
|