feat(data): 统一数据结构处理并优化音乐播放功能
- 添加 extractList 函数统一处理不同数据结构(list、records、items) - 添加 extractPageData 函数标准化分页数据处理逻辑 - 修复音乐播放器移除播放列表项时的索引问题 - 优化播放列表清空时的音频暂停和状态重置 - 调整收藏按钮图标显示逻辑(实心星和空心星对调) - 更新前端API调用路径 /user/profile 为 /user/current - 修复后端收藏歌单查询的字段映射和排序问题 - 在主布局中引入 ArrowDown 图标用于下拉功能 - 改进用户中心头像上传的认证头构建方式 - 优化用户统计数据显示的数据提取逻辑
This commit is contained in:
@@ -36,6 +36,6 @@ public class UserCollectPlaylist {
|
||||
* 收藏时间
|
||||
*/
|
||||
@CreationTimestamp
|
||||
@Column(name = "collect_time", nullable = false, updatable = false)
|
||||
@Column(name = "create_time", nullable = false, updatable = false)
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
|
||||
@@ -3,19 +3,15 @@ package com.test.musichouduan.repository;
|
||||
import com.test.musichouduan.entity.Playlist;
|
||||
import com.test.musichouduan.entity.User;
|
||||
import com.test.musichouduan.entity.UserCollectPlaylist;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -89,7 +85,7 @@ public interface UserCollectPlaylistRepository extends JpaRepository<UserCollect
|
||||
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query(value = "INSERT INTO user_collect_playlist (user_id, playlist_id, collect_time) VALUES (?1, ?2, NOW())", nativeQuery = true)
|
||||
@Query(value = "INSERT INTO user_collect_playlist (user_id, playlist_id, create_time) VALUES (?1, ?2, NOW())", nativeQuery = true)
|
||||
void collectPlaylist(Long userId, Long playlistId);
|
||||
|
||||
@Modifying
|
||||
@@ -97,7 +93,10 @@ public interface UserCollectPlaylistRepository extends JpaRepository<UserCollect
|
||||
@Query(value = "DELETE FROM user_collect_playlist WHERE user_id = ?1 AND playlist_id = ?2", nativeQuery = true)
|
||||
void uncollectPlaylist(Long userId, Long playlistId);
|
||||
|
||||
@Query("SELECT ucp.playlist FROM UserCollectPlaylist ucp WHERE ucp.user.id = ?1")
|
||||
@Query(
|
||||
value = "SELECT ucp.playlist FROM UserCollectPlaylist ucp WHERE ucp.user.id = ?1 ORDER BY ucp.createTime DESC",
|
||||
countQuery = "SELECT COUNT(ucp) FROM UserCollectPlaylist ucp WHERE ucp.user.id = ?1"
|
||||
)
|
||||
Page<Playlist> findCollectedPlaylist(Long userId, Pageable pageable);
|
||||
|
||||
Optional<UserCollectPlaylist> findByUserIdAndPlaylistId(Long userId, Long playlistId);
|
||||
|
||||
@@ -483,11 +483,11 @@ public class PlaylistServiceImpl implements PlaylistService {
|
||||
// 获取当前登录用户
|
||||
Long userId = StpUtil.getLoginIdAsLong();
|
||||
|
||||
// 获取用户收藏的歌单
|
||||
Page<Playlist> playlistPage = userCollectPlaylistRepository.findCollectedPlaylist(
|
||||
userId,
|
||||
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
|
||||
);
|
||||
// 获取用户收藏的歌单
|
||||
Page<Playlist> playlistPage = userCollectPlaylistRepository.findCollectedPlaylist(
|
||||
userId,
|
||||
PageRequest.of(page - 1, size)
|
||||
);
|
||||
|
||||
// 转换为DTO
|
||||
List<PlaylistDTO> playlistDTOs = playlistPage.getContent().stream()
|
||||
|
||||
@@ -39,7 +39,7 @@ export function getCurrentUser() {
|
||||
* @returns {Promise} 请求结果
|
||||
*/
|
||||
export function getUserInfo() {
|
||||
return api.get('/user/profile')
|
||||
return api.get('/user/current')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -508,7 +508,11 @@ const toggleLike = async () => {
|
||||
// 播放列表相关
|
||||
const togglePlaylist = () => playerStore.setShowPlaylist(!showPlaylist.value)
|
||||
const playFromPlaylist = (index) => playerStore.playFromPlaylist(index)
|
||||
const removeFromPlaylist = (index) => playerStore.removeFromPlaylist(index)
|
||||
const removeFromPlaylist = (index) => {
|
||||
const music = playlist.value[index]
|
||||
if (!music) return
|
||||
playerStore.removeFromPlaylist(music.id)
|
||||
}
|
||||
const clearPlaylist = () => playerStore.clearPlaylist()
|
||||
|
||||
// 展开/收起播放器
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 { Search, ArrowDown } from '@element-plus/icons-vue'
|
||||
import MusicPlayer from '../components/MusicPlayer.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -173,8 +173,17 @@ export const usePlayerStore = defineStore('player', {
|
||||
|
||||
// 清空播放列表
|
||||
clearPlaylist() {
|
||||
if (this.audio) {
|
||||
this.audio.pause()
|
||||
this.audio.src = ''
|
||||
}
|
||||
|
||||
this.playlist = []
|
||||
this.setCurrentMusic(null)
|
||||
this.currentMusic = null
|
||||
this.isPlaying = false
|
||||
this.currentTime = 0
|
||||
this.duration = 0
|
||||
this.currentIndex = -1
|
||||
},
|
||||
|
||||
// 播放上一首
|
||||
|
||||
@@ -13,6 +13,14 @@ import { processImageUrl } from '../utils/imageUtils'
|
||||
const router = useRouter()
|
||||
const playerStore = usePlayerStore()
|
||||
|
||||
const extractList = (data) => {
|
||||
if (Array.isArray(data)) return data
|
||||
if (Array.isArray(data?.list)) return data.list
|
||||
if (Array.isArray(data?.records)) return data.records
|
||||
if (Array.isArray(data?.items)) return data.items
|
||||
return []
|
||||
}
|
||||
|
||||
// 新歌推荐
|
||||
const newSongs = ref([])
|
||||
const loadingNewSongs = ref(false)
|
||||
@@ -35,7 +43,7 @@ const fetchLatestMusic = async () => {
|
||||
try {
|
||||
const res = await getLatestMusic(5) // 获取5首最新音乐
|
||||
if (res.code === 200) {
|
||||
newSongs.value = res.data.map(item => ({
|
||||
newSongs.value = extractList(res.data).map(item => ({
|
||||
...item,
|
||||
cover: processMusicUrl(item.cover),
|
||||
url: processMusicUrl(item.url)
|
||||
@@ -59,7 +67,7 @@ const fetchHotMusic = async () => {
|
||||
// 注意:这里调用的是新的 hot-music API
|
||||
const res = await getHotMusic(5) // 获取5首热门音乐
|
||||
if (res.code === 200) {
|
||||
hotSongs.value = res.data.map(item => ({
|
||||
hotSongs.value = extractList(res.data).map(item => ({
|
||||
...item,
|
||||
cover: processMusicUrl(item.cover),
|
||||
url: processMusicUrl(item.url)
|
||||
@@ -81,7 +89,7 @@ const fetchLatestPlaylist = async () => {
|
||||
try {
|
||||
const res = await getLatestPlaylist(5) // 获取5个最新歌单
|
||||
if (res.code === 200) {
|
||||
newPlaylists.value = res.data
|
||||
newPlaylists.value = extractList(res.data)
|
||||
console.log('最新歌单数据:', newPlaylists.value)
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取最新歌单失败')
|
||||
@@ -100,7 +108,7 @@ const fetchLatestSinger = async () => {
|
||||
try {
|
||||
const res = await getLatestSinger(5) // 获取5个最新歌手
|
||||
if (res.code === 200) {
|
||||
newSingers.value = res.data.map(item => ({
|
||||
newSingers.value = extractList(res.data).map(item => ({
|
||||
...item,
|
||||
avatar: processImageUrl(item.avatar)
|
||||
}))
|
||||
|
||||
@@ -11,6 +11,22 @@ import { useUserStore } from '../stores/user'
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const extractList = (data) => {
|
||||
if (Array.isArray(data)) return data
|
||||
if (Array.isArray(data?.list)) return data.list
|
||||
if (Array.isArray(data?.records)) return data.records
|
||||
if (Array.isArray(data?.items)) return data.items
|
||||
return []
|
||||
}
|
||||
|
||||
const extractPageData = (data) => {
|
||||
const list = extractList(data)
|
||||
return {
|
||||
list,
|
||||
total: Number.isFinite(Number(data?.total)) ? Number(data.total) : list.length
|
||||
}
|
||||
}
|
||||
|
||||
// 音乐列表数据
|
||||
const musicList = ref([])
|
||||
const loading = ref(false)
|
||||
@@ -119,9 +135,9 @@ const fetchMusicList = async (params = {}) => {
|
||||
|
||||
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
|
||||
const pageData = extractPageData(res.data)
|
||||
musicList.value = pageData.list
|
||||
total.value = pageData.total
|
||||
|
||||
// 确保音乐URL和封面URL正确处理
|
||||
musicList.value.forEach(music => {
|
||||
@@ -261,7 +277,7 @@ const fetchHotMusic = async (params = {}) => {
|
||||
// 从缓存中获取全部50条热门音乐
|
||||
const res = await getHotMusic(50)
|
||||
if (res.code === 200) {
|
||||
hotMusicSource.value = res.data || []
|
||||
hotMusicSource.value = extractList(res.data)
|
||||
|
||||
// 处理URL
|
||||
hotMusicSource.value.forEach(music => {
|
||||
@@ -291,7 +307,7 @@ const fetchLatestMusic = async () => {
|
||||
try {
|
||||
const res = await getLatestMusic(20) // 获取20首最新音乐
|
||||
if (res.code === 200) {
|
||||
latestMusicSource.value = res.data || []
|
||||
latestMusicSource.value = extractList(res.data)
|
||||
|
||||
// 处理音乐URL和封面URL
|
||||
latestMusicSource.value.forEach(music => {
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<el-button
|
||||
:type="music.collected ? 'danger' : 'default'"
|
||||
@click="toggleCollect"
|
||||
:icon="music.collected ? 'Star' : 'StarFilled'"
|
||||
:icon="music.collected ? 'StarFilled' : 'Star'"
|
||||
>
|
||||
{{ music.collected ? `已收藏 (${music.collectCount ?? 0})` : `收藏 (${music.collectCount ?? 0})` }}
|
||||
</el-button>
|
||||
|
||||
@@ -11,6 +11,22 @@ import { getPlaylistTypeList } from '../api/playlistType'
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const extractList = (data) => {
|
||||
if (Array.isArray(data)) return data
|
||||
if (Array.isArray(data?.list)) return data.list
|
||||
if (Array.isArray(data?.records)) return data.records
|
||||
if (Array.isArray(data?.items)) return data.items
|
||||
return []
|
||||
}
|
||||
|
||||
const extractPageData = (data) => {
|
||||
const list = extractList(data)
|
||||
return {
|
||||
list,
|
||||
total: Number.isFinite(Number(data?.total)) ? Number(data.total) : list.length
|
||||
}
|
||||
}
|
||||
|
||||
// 歌单类型列表
|
||||
const playlistTypes = ref([
|
||||
{ id: null, name: '全部' }
|
||||
@@ -89,8 +105,9 @@ const fetchPlaylists = async () => {
|
||||
)
|
||||
|
||||
if (res.code === 200) {
|
||||
playlists.value = res.data.list || res.data.records || []
|
||||
total.value = res.data.total || 0
|
||||
const pageData = extractPageData(res.data)
|
||||
playlists.value = pageData.list
|
||||
total.value = pageData.total
|
||||
updatePlaylistStatuses(playlists.value)
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取歌单列表失败')
|
||||
@@ -109,7 +126,7 @@ const fetchHotPlaylists = async () => {
|
||||
try {
|
||||
const res = await getHotPlaylist(8) // 获取8个热门歌单
|
||||
if (res.code === 200) {
|
||||
hotPlaylists.value = res.data || []
|
||||
hotPlaylists.value = extractList(res.data)
|
||||
updatePlaylistStatuses(hotPlaylists.value)
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取热门歌单失败')
|
||||
|
||||
@@ -165,6 +165,22 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const extractList = (data) => {
|
||||
if (Array.isArray(data)) return data
|
||||
if (Array.isArray(data?.list)) return data.list
|
||||
if (Array.isArray(data?.records)) return data.records
|
||||
if (Array.isArray(data?.items)) return data.items
|
||||
return []
|
||||
}
|
||||
|
||||
const extractPageData = (data) => {
|
||||
const list = extractList(data)
|
||||
return {
|
||||
list,
|
||||
total: Number.isFinite(Number(data?.total)) ? Number(data.total) : list.length
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索关键词
|
||||
const keyword = computed(() => route.query.keyword || '')
|
||||
const searchKeyword = ref(keyword.value)
|
||||
@@ -214,8 +230,9 @@ const fetchMusicList = async () => {
|
||||
try {
|
||||
const res = await searchMusic(keyword.value, musicPage.value, musicPageSize.value)
|
||||
if (res.code === 200) {
|
||||
musicList.value = res.data.list || res.data.records || []
|
||||
musicTotal.value = res.data.total || 0
|
||||
const pageData = extractPageData(res.data)
|
||||
musicList.value = pageData.list
|
||||
musicTotal.value = pageData.total
|
||||
|
||||
// 处理音乐URL和封面URL
|
||||
musicList.value = musicList.value.map(item => ({
|
||||
@@ -245,8 +262,9 @@ const fetchPlaylistList = async () => {
|
||||
try {
|
||||
const res = await searchPlaylist(keyword.value, playlistPage.value, playlistPageSize.value)
|
||||
if (res.code === 200) {
|
||||
playlistList.value = res.data.list || res.data.records || []
|
||||
playlistTotal.value = res.data.total || 0
|
||||
const pageData = extractPageData(res.data)
|
||||
playlistList.value = pageData.list
|
||||
playlistTotal.value = pageData.total
|
||||
|
||||
// 处理封面URL
|
||||
playlistList.value = playlistList.value.map(item => ({
|
||||
@@ -272,8 +290,9 @@ const fetchSingerList = async () => {
|
||||
try {
|
||||
const res = await searchSinger(keyword.value, singerPage.value, singerPageSize.value)
|
||||
if (res.code === 200) {
|
||||
singerList.value = res.data.list || res.data.records || []
|
||||
singerTotal.value = res.data.total || 0
|
||||
const pageData = extractPageData(res.data)
|
||||
singerList.value = pageData.list
|
||||
singerTotal.value = pageData.total
|
||||
|
||||
// 处理头像URL
|
||||
singerList.value = singerList.value.map(item => ({
|
||||
|
||||
@@ -12,6 +12,22 @@ import { processImageUrl } from '../utils/imageUtils'
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const extractList = (data) => {
|
||||
if (Array.isArray(data)) return data
|
||||
if (Array.isArray(data?.list)) return data.list
|
||||
if (Array.isArray(data?.records)) return data.records
|
||||
if (Array.isArray(data?.items)) return data.items
|
||||
return []
|
||||
}
|
||||
|
||||
const extractPageData = (data) => {
|
||||
const list = extractList(data)
|
||||
return {
|
||||
list,
|
||||
total: Number.isFinite(Number(data?.total)) ? Number(data.total) : list.length
|
||||
}
|
||||
}
|
||||
|
||||
// 歌手类型列表
|
||||
const singerTypes = ref([{ id: null, name: '全部' }])
|
||||
|
||||
@@ -75,8 +91,9 @@ const fetchSingers = async () => {
|
||||
)
|
||||
|
||||
if (res.code === 200) {
|
||||
singers.value = res.data.list || res.data.records || []
|
||||
total.value = res.data.total || 0
|
||||
const pageData = extractPageData(res.data)
|
||||
singers.value = pageData.list
|
||||
total.value = pageData.total
|
||||
|
||||
// 处理图片URL
|
||||
singers.value = singers.value.map(singer => ({
|
||||
@@ -101,7 +118,7 @@ const fetchHotSingers = async () => {
|
||||
try {
|
||||
const res = await getHotSinger(8) // 获取8个热门歌手
|
||||
if (res.code === 200) {
|
||||
hotSingers.value = res.data || []
|
||||
hotSingers.value = extractList(res.data)
|
||||
|
||||
// 处理图片URL
|
||||
hotSingers.value = hotSingers.value.map(singer => ({
|
||||
|
||||
@@ -12,6 +12,22 @@ import { processImageUrl } from '../utils/imageUtils'
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const extractList = (data) => {
|
||||
if (Array.isArray(data)) return data
|
||||
if (Array.isArray(data?.list)) return data.list
|
||||
if (Array.isArray(data?.records)) return data.records
|
||||
if (Array.isArray(data?.items)) return data.items
|
||||
return []
|
||||
}
|
||||
|
||||
const extractPageData = (data) => {
|
||||
const list = extractList(data)
|
||||
return {
|
||||
list,
|
||||
total: Number.isFinite(Number(data?.total)) ? Number(data.total) : list.length
|
||||
}
|
||||
}
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref(null)
|
||||
|
||||
@@ -43,7 +59,7 @@ const avatarUploadUrl = ref(`${import.meta.env.VITE_API_BASE_URL || ''}/api/user
|
||||
|
||||
// 头像上传headers
|
||||
const uploadHeaders = ref({
|
||||
[localStorage.getItem('tokenName') || 'satoken']: userStore.token
|
||||
[localStorage.getItem('tokenName') || 'satoken']: `${localStorage.getItem('tokenPrefix') || ''}${userStore.token || localStorage.getItem('token') || ''}`
|
||||
})
|
||||
|
||||
// 当前激活的菜单
|
||||
@@ -168,25 +184,25 @@ const fetchUserStats = async () => {
|
||||
// 获取收藏的音乐数量
|
||||
const musicRes = await getCollectedMusic()
|
||||
if (musicRes.code === 200) {
|
||||
userStats.value.collectMusicCount = musicRes.data.total || musicRes.data.length || 0
|
||||
userStats.value.collectMusicCount = extractPageData(musicRes.data).total
|
||||
}
|
||||
|
||||
// 获取收藏的歌单数量
|
||||
const playlistRes = await getCollectedPlaylist()
|
||||
if (playlistRes.code === 200) {
|
||||
userStats.value.collectPlaylistCount = playlistRes.data.total || playlistRes.data.length || 0
|
||||
userStats.value.collectPlaylistCount = extractPageData(playlistRes.data).total
|
||||
}
|
||||
|
||||
// 获取收藏的歌手数量
|
||||
const singerRes = await getCollectedSinger()
|
||||
if (singerRes.code === 200) {
|
||||
userStats.value.collectSingerCount = singerRes.data.total || singerRes.data.length || 0
|
||||
userStats.value.collectSingerCount = extractPageData(singerRes.data).total
|
||||
}
|
||||
|
||||
// 获取创建的歌单数量
|
||||
const userPlaylistRes = await getUserPlaylist()
|
||||
if (userPlaylistRes.code === 200) {
|
||||
userStats.value.createPlaylistCount = userPlaylistRes.data.total || userPlaylistRes.data.length || 0
|
||||
userStats.value.createPlaylistCount = extractPageData(userPlaylistRes.data).total
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户统计数据失败:', error)
|
||||
|
||||
Reference in New Issue
Block a user