feat: 初始化音乐项目后台管理系统- 新增后台管理相关页面和功能组件

- 实现管理员评论管理、数据统计等功能
- 添加后台布局和路由管理
-配置数据库和Redis连接
- 实现文件上传和访问路径配置
- 添加Sa-Token安全配置
This commit is contained in:
ikmkj
2025-04-26 19:32:45 +08:00
commit b275ffab5a
185 changed files with 32763 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia

View 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
}
}
}
})

View 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 // 将错误向上抛出,以便调用者可以处理
}
}
}
})