feat: 初始化音乐项目后台管理系统- 新增后台管理相关页面和功能组件
- 实现管理员评论管理、数据统计等功能 - 添加后台布局和路由管理 -配置数据库和Redis连接 - 实现文件上传和访问路径配置 - 添加Sa-Token安全配置
This commit is contained in:
5
music-qianduan/src/stores/index.js
Normal file
5
music-qianduan/src/stores/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
export default pinia
|
||||
285
music-qianduan/src/stores/player.js
Normal file
285
music-qianduan/src/stores/player.js
Normal file
@@ -0,0 +1,285 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { playMusic } from '../api/music'
|
||||
|
||||
export const usePlayerStore = defineStore('player', {
|
||||
state: () => ({
|
||||
currentMusic: null,
|
||||
playlist: [],
|
||||
isPlaying: false,
|
||||
currentTime: 0,
|
||||
duration: 0,
|
||||
volume: 80,
|
||||
showPlaylist: false,
|
||||
loop: 'none', // none, single, list
|
||||
audio: null,
|
||||
currentIndex: -1 // 当前播放的索引
|
||||
}),
|
||||
|
||||
getters: {
|
||||
getCurrentMusic: (state) => state.currentMusic,
|
||||
getPlaylist: (state) => state.playlist,
|
||||
getIsPlaying: (state) => state.isPlaying,
|
||||
getCurrentTime: (state) => state.currentTime,
|
||||
getDuration: (state) => state.duration,
|
||||
getVolume: (state) => state.volume,
|
||||
getShowPlaylist: (state) => state.showPlaylist,
|
||||
getLoop: (state) => state.loop,
|
||||
getCurrentIndex: (state) => state.currentIndex
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 初始化音频对象
|
||||
initAudio() {
|
||||
if (!this.audio) {
|
||||
this.audio = new Audio()
|
||||
|
||||
// 监听事件
|
||||
this.audio.addEventListener('timeupdate', this.handleTimeUpdate)
|
||||
this.audio.addEventListener('ended', this.handleEnded)
|
||||
this.audio.addEventListener('loadedmetadata', this.handleLoadedMetadata)
|
||||
this.audio.addEventListener('error', this.handleError)
|
||||
this.audio.volume = this.volume / 100
|
||||
}
|
||||
},
|
||||
|
||||
// 设置当前音乐
|
||||
async setCurrentMusic(music) {
|
||||
if (!music) return
|
||||
|
||||
this.initAudio()
|
||||
this.currentMusic = music
|
||||
|
||||
// 检查播放列表中是否已存在该音乐
|
||||
const index = this.playlist.findIndex(item => item.id === music.id)
|
||||
if (index !== -1) {
|
||||
this.currentIndex = index
|
||||
} else {
|
||||
this.addToPlaylist(music)
|
||||
this.currentIndex = this.playlist.length - 1
|
||||
}
|
||||
|
||||
// 设置音频源并播放
|
||||
if (music.url) {
|
||||
this.audio.src = music.url
|
||||
this.play()
|
||||
|
||||
// 调用播放API,记录播放次数
|
||||
try {
|
||||
await playMusic(music.id)
|
||||
} catch (error) {
|
||||
console.error('记录播放失败:', error)
|
||||
}
|
||||
} else {
|
||||
console.error('音乐URL不存在')
|
||||
}
|
||||
},
|
||||
|
||||
// 播放
|
||||
play() {
|
||||
if (this.currentMusic) {
|
||||
this.initAudio()
|
||||
const playPromise = this.audio.play()
|
||||
|
||||
if (playPromise !== undefined) {
|
||||
playPromise.then(() => {
|
||||
this.isPlaying = true
|
||||
}).catch(error => {
|
||||
console.error('播放失败:', error)
|
||||
this.isPlaying = false
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 暂停
|
||||
pause() {
|
||||
if (this.audio) {
|
||||
this.audio.pause()
|
||||
this.isPlaying = false
|
||||
}
|
||||
},
|
||||
|
||||
// 切换播放状态
|
||||
togglePlay() {
|
||||
if (this.isPlaying) {
|
||||
this.pause()
|
||||
} else {
|
||||
this.play()
|
||||
}
|
||||
},
|
||||
|
||||
// 设置音量
|
||||
setVolume(volume) {
|
||||
this.volume = volume
|
||||
if (this.audio) {
|
||||
this.audio.volume = volume / 100
|
||||
}
|
||||
},
|
||||
|
||||
// 设置当前播放时间
|
||||
setCurrentTime(time) {
|
||||
this.currentTime = time
|
||||
if (this.audio) {
|
||||
try {
|
||||
this.audio.currentTime = time
|
||||
// 如果当前是暂停状态,尝试播放一小段时间再暂停,以确保进度条变化生效
|
||||
if (!this.isPlaying) {
|
||||
const wasPlaying = false
|
||||
this.audio.play().then(() => {
|
||||
setTimeout(() => {
|
||||
if (!wasPlaying) {
|
||||
this.audio.pause()
|
||||
}
|
||||
}, 100)
|
||||
}).catch(err => console.error('Temporary play failed:', err))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to set currentTime:', err)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 添加到播放列表
|
||||
addToPlaylist(music) {
|
||||
// 检查是否已存在
|
||||
const exists = this.playlist.some(item => item.id === music.id)
|
||||
if (!exists) {
|
||||
this.playlist.push(music)
|
||||
}
|
||||
},
|
||||
|
||||
// 从播放列表中移除
|
||||
removeFromPlaylist(musicId) {
|
||||
const index = this.playlist.findIndex(item => item.id === musicId)
|
||||
if (index !== -1) {
|
||||
this.playlist.splice(index, 1)
|
||||
|
||||
// 如果移除的是当前播放的音乐,则播放下一首
|
||||
if (this.currentMusic && this.currentMusic.id === musicId) {
|
||||
this.playNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 清空播放列表
|
||||
clearPlaylist() {
|
||||
this.playlist = []
|
||||
this.setCurrentMusic(null)
|
||||
},
|
||||
|
||||
// 播放上一首
|
||||
playPrev() {
|
||||
if (this.playlist.length === 0) return
|
||||
|
||||
const currentIndex = this.playlist.findIndex(item => this.currentMusic && item.id === this.currentMusic.id)
|
||||
let prevIndex = currentIndex - 1
|
||||
|
||||
if (prevIndex < 0) {
|
||||
prevIndex = this.playlist.length - 1
|
||||
}
|
||||
|
||||
this.setCurrentMusic(this.playlist[prevIndex])
|
||||
},
|
||||
|
||||
// 播放下一首
|
||||
playNext() {
|
||||
if (this.playlist.length === 0) return
|
||||
|
||||
const currentIndex = this.playlist.findIndex(item => this.currentMusic && item.id === this.currentMusic.id)
|
||||
let nextIndex = currentIndex + 1
|
||||
|
||||
if (nextIndex >= this.playlist.length) {
|
||||
nextIndex = 0
|
||||
}
|
||||
|
||||
this.setCurrentMusic(this.playlist[nextIndex])
|
||||
},
|
||||
|
||||
// 切换播放列表显示状态
|
||||
togglePlaylist() {
|
||||
this.showPlaylist = !this.showPlaylist
|
||||
},
|
||||
|
||||
// 设置循环模式
|
||||
setLoop(mode) {
|
||||
this.loop = mode
|
||||
},
|
||||
|
||||
// 处理时间更新事件
|
||||
handleTimeUpdate() {
|
||||
this.currentTime = this.audio.currentTime
|
||||
},
|
||||
|
||||
// 处理播放结束事件
|
||||
handleEnded() {
|
||||
if (this.loop === 'single') {
|
||||
// 单曲循环
|
||||
this.audio.currentTime = 0
|
||||
this.play()
|
||||
} else if (this.loop === 'list') {
|
||||
// 列表循环
|
||||
this.playNext()
|
||||
} else {
|
||||
// 不循环,播放下一首
|
||||
this.playNext()
|
||||
}
|
||||
},
|
||||
|
||||
// 处理元数据加载事件
|
||||
handleLoadedMetadata() {
|
||||
this.duration = this.audio.duration
|
||||
},
|
||||
|
||||
// 处理错误事件
|
||||
handleError(e) {
|
||||
console.error('音频加载错误:', e)
|
||||
// 如果当前音乐加载失败,尝试播放下一首
|
||||
if (this.playlist.length > 1) {
|
||||
this.playNext()
|
||||
}
|
||||
},
|
||||
|
||||
// 设置播放列表显示状态
|
||||
setShowPlaylist(show) {
|
||||
this.showPlaylist = show
|
||||
},
|
||||
|
||||
// 从播放列表中播放
|
||||
playFromPlaylist(index) {
|
||||
if (index >= 0 && index < this.playlist.length) {
|
||||
this.setCurrentMusic(this.playlist[index])
|
||||
}
|
||||
},
|
||||
|
||||
// 更新音乐URL
|
||||
updateMusicUrl(id, url) {
|
||||
// 更新当前音乐的URL
|
||||
if (this.currentMusic && this.currentMusic.id === id) {
|
||||
this.currentMusic.url = url
|
||||
if (this.audio) {
|
||||
this.audio.src = url
|
||||
}
|
||||
}
|
||||
|
||||
// 更新播放列表中的URL
|
||||
const index = this.playlist.findIndex(item => item.id === id)
|
||||
if (index !== -1) {
|
||||
this.playlist[index].url = url
|
||||
}
|
||||
},
|
||||
|
||||
// 更新音乐封面
|
||||
updateMusicCover(id, cover) {
|
||||
// 更新当前音乐的封面
|
||||
if (this.currentMusic && this.currentMusic.id === id) {
|
||||
this.currentMusic.cover = cover
|
||||
}
|
||||
|
||||
// 更新播放列表中的封面
|
||||
const index = this.playlist.findIndex(item => item.id === id)
|
||||
if (index !== -1) {
|
||||
this.playlist[index].cover = cover
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
145
music-qianduan/src/stores/user.js
Normal file
145
music-qianduan/src/stores/user.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { getCurrentUser, logout } from '../api/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => {
|
||||
// 从本地存储中获取用户信息
|
||||
const storedUser = localStorage.getItem('user')
|
||||
const token = localStorage.getItem('token')
|
||||
|
||||
return {
|
||||
user: storedUser ? JSON.parse(storedUser) : null,
|
||||
isLoggedIn: !!token,
|
||||
token: token || ''
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
getUser: (state) => state.user,
|
||||
getLoginStatus: (state) => state.isLoggedIn,
|
||||
isAdmin: (state) => state.user?.role === 1
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 登录
|
||||
login(userData) {
|
||||
// 确保 userData 包含 user 属性
|
||||
if (userData && userData.user) {
|
||||
this.user = userData.user
|
||||
this.isLoggedIn = true
|
||||
this.token = userData.token || ''
|
||||
|
||||
// 打印登录数据,便于调试
|
||||
console.log('登录数据:', userData)
|
||||
|
||||
// 存储到本地
|
||||
localStorage.setItem('user', JSON.stringify(userData.user))
|
||||
localStorage.setItem('token', this.token)
|
||||
localStorage.setItem('tokenName', userData.tokenName || 'satoken')
|
||||
localStorage.setItem('tokenPrefix', userData.tokenPrefix || '')
|
||||
localStorage.setItem('isAdmin', userData.user.role === 1 ? 'true' : 'false')
|
||||
|
||||
// 打印存储的信息,便于调试
|
||||
console.log('存储的token:', localStorage.getItem('token'))
|
||||
console.log('存储的tokenName:', localStorage.getItem('tokenName'))
|
||||
console.log('存储的tokenPrefix:', localStorage.getItem('tokenPrefix'))
|
||||
console.log('存储的isAdmin:', localStorage.getItem('isAdmin'))
|
||||
} else {
|
||||
console.error('登录数据格式错误:', userData)
|
||||
}
|
||||
},
|
||||
|
||||
// 登出
|
||||
async logout() {
|
||||
try {
|
||||
// 调用登出API
|
||||
await logout()
|
||||
|
||||
// 重置状态
|
||||
this.user = null
|
||||
this.isLoggedIn = false
|
||||
this.token = ''
|
||||
|
||||
// 清除本地存储
|
||||
localStorage.removeItem('user')
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('tokenName')
|
||||
localStorage.removeItem('tokenPrefix')
|
||||
localStorage.removeItem('isAdmin')
|
||||
|
||||
ElMessage.success('登出成功')
|
||||
} catch (error) {
|
||||
console.error('登出失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取当前用户信息
|
||||
async fetchCurrentUser() {
|
||||
const token = localStorage.getItem('token')
|
||||
if (!token) {
|
||||
console.warn('没有token,无法获取用户信息')
|
||||
return
|
||||
}
|
||||
|
||||
// 确保存储在store中的token与localStorage中的一致
|
||||
this.token = token
|
||||
|
||||
console.log('开始获取用户信息,token:', token)
|
||||
|
||||
try {
|
||||
// 尝试从本地存储中获取用户信息
|
||||
const userStr = localStorage.getItem('user')
|
||||
if (userStr) {
|
||||
try {
|
||||
const user = JSON.parse(userStr)
|
||||
if (user && user.id) {
|
||||
this.user = user
|
||||
this.isLoggedIn = true
|
||||
console.log('从本地存储中获取用户信息成功:', user)
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('解析本地存储的用户信息失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果本地存储中没有用户信息,则从服务器获取
|
||||
const res = await getCurrentUser()
|
||||
console.log('获取用户信息响应:', res)
|
||||
|
||||
if (res.code === 200) {
|
||||
// 如果返回的数据中有user属性,说明是登录接口返回的数据格式
|
||||
if (res.data && res.data.user) {
|
||||
this.user = res.data.user
|
||||
} else {
|
||||
// 否则是获取当前用户接口返回的数据格式
|
||||
this.user = res.data
|
||||
}
|
||||
|
||||
this.isLoggedIn = true
|
||||
localStorage.setItem('user', JSON.stringify(this.user))
|
||||
localStorage.setItem('isAdmin', this.user.role === 1 ? 'true' : 'false')
|
||||
console.log('用户信息已更新:', this.user)
|
||||
} else {
|
||||
console.error('获取用户信息失败,响应码非200:', res.code, res.msg)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
// 如果获取用户信息失败,可能是token已过期,清除本地存储
|
||||
if (error.message && (error.message.includes('token') || error.message.includes('登录'))) {
|
||||
console.warn('可能是token已过期,清除本地存储')
|
||||
this.user = null
|
||||
this.isLoggedIn = false
|
||||
this.token = ''
|
||||
localStorage.removeItem('user')
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('tokenName')
|
||||
localStorage.removeItem('tokenPrefix')
|
||||
localStorage.removeItem('isAdmin')
|
||||
}
|
||||
throw error // 将错误向上抛出,以便调用者可以处理
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user