1060 lines
26 KiB
Vue
1060 lines
26 KiB
Vue
<script setup>
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { getPlaylistList, getPlaylistById, addPlaylist as apiAddPlaylist, updatePlaylist as apiUpdatePlaylist, deletePlaylist as apiDeletePlaylist } from '../../api/playlist'
|
||
import { getPlaylistTypeList } from '../../api/playlistType'
|
||
import { processMusicUrl } from '../../api/music'
|
||
|
||
// 歌单列表
|
||
const playlistList = ref([])
|
||
|
||
// 加载状态
|
||
const loading = ref(false)
|
||
|
||
// 分页信息
|
||
const pagination = ref({
|
||
currentPage: 1,
|
||
pageSize: 5,
|
||
total: 0
|
||
})
|
||
|
||
// 搜索关键字
|
||
const searchKeyword = ref('')
|
||
|
||
// 类型筛选
|
||
const selectedType = ref('')
|
||
|
||
// 类型列表
|
||
const typeList = ref([])
|
||
|
||
// 添加/编辑对话框
|
||
const dialogVisible = ref(false)
|
||
|
||
// 对话框标题
|
||
const dialogTitle = ref('添加歌单')
|
||
|
||
// 当前编辑的歌单
|
||
const currentPlaylist = ref(null)
|
||
|
||
// 表单数据
|
||
const form = ref({
|
||
name: '',
|
||
description: '',
|
||
typeId: '',
|
||
isOfficial: 1,
|
||
cover: null,
|
||
currentCover: null // 当前封面URL,用于显示
|
||
})
|
||
|
||
// 获取歌单列表
|
||
const fetchPlaylistList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const { currentPage, pageSize } = pagination.value
|
||
const res = await getPlaylistList(
|
||
currentPage,
|
||
pageSize,
|
||
searchKeyword.value,
|
||
selectedType.value || null
|
||
)
|
||
|
||
console.log('歌单列表响应:', res)
|
||
|
||
if (res.code === 200) {
|
||
// 处理歌单列表数据
|
||
const data = res.data
|
||
// 兼容不同的数据结构
|
||
const records = data.records || data.list || []
|
||
|
||
playlistList.value = records.map(item => ({
|
||
...item,
|
||
// 处理封面URL
|
||
cover: processMusicUrl(item.cover),
|
||
// 确保类型ID是数字
|
||
typeId: item.typeId || (item.type ? item.type.id : null),
|
||
// 确保音乐数量存在
|
||
songCount: item.musicCount || item.songCount || 0
|
||
}))
|
||
|
||
pagination.value.total = data.total || 0
|
||
console.log('处理后的歌单列表:', playlistList.value)
|
||
} else {
|
||
ElMessage.error(res.message || '获取歌单列表失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('获取歌单列表失败:', error)
|
||
ElMessage.error('获取歌单列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取类型列表
|
||
const fetchTypeList = async () => {
|
||
try {
|
||
const res = await getPlaylistTypeList()
|
||
if (res.code === 200) {
|
||
typeList.value = res.data || []
|
||
console.log('歌单类型列表:', typeList.value)
|
||
} else {
|
||
ElMessage.error(res.message || '获取类型列表失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('获取类型列表失败:', error)
|
||
ElMessage.error('获取类型列表失败')
|
||
}
|
||
}
|
||
|
||
// 处理页码变化
|
||
const handleCurrentChange = (page) => {
|
||
pagination.value.currentPage = page
|
||
fetchPlaylistList()
|
||
}
|
||
|
||
// 处理每页条数变化
|
||
const handleSizeChange = (size) => {
|
||
pagination.value.pageSize = size
|
||
pagination.value.currentPage = 1
|
||
fetchPlaylistList()
|
||
}
|
||
|
||
// 搜索歌单
|
||
const searchPlaylist = () => {
|
||
pagination.value.currentPage = 1
|
||
fetchPlaylistList()
|
||
}
|
||
|
||
// 重置搜索
|
||
const resetSearch = () => {
|
||
searchKeyword.value = ''
|
||
selectedType.value = ''
|
||
pagination.value.currentPage = 1
|
||
fetchPlaylistList()
|
||
}
|
||
|
||
// 添加歌单
|
||
const addPlaylist = () => {
|
||
dialogTitle.value = '添加歌单'
|
||
currentPlaylist.value = null
|
||
form.value = {
|
||
name: '',
|
||
description: '',
|
||
typeId: '',
|
||
isOfficial: 1,
|
||
cover: null,
|
||
currentCover: null
|
||
}
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
// 编辑歌单
|
||
const editPlaylist = async (row) => {
|
||
dialogTitle.value = '编辑歌单'
|
||
currentPlaylist.value = row
|
||
|
||
// 先获取类型列表,确保类型数据已加载
|
||
await fetchTypeList()
|
||
console.log('类型列表:', typeList.value)
|
||
|
||
try {
|
||
// 获取歌单详情
|
||
const res = await getPlaylistById(row.id)
|
||
if (res.code === 200) {
|
||
const playlist = res.data
|
||
console.log('歌单详情:', playlist)
|
||
|
||
// 处理类型ID
|
||
let typeId = null
|
||
if (playlist.type && playlist.type.id) {
|
||
// 如果是对象形式
|
||
typeId = Number(playlist.type.id)
|
||
} else if (playlist.typeId) {
|
||
// 如果是直接的ID
|
||
typeId = Number(playlist.typeId)
|
||
}
|
||
|
||
// 如果没有类型,默认选择第一个类型(如果存在)
|
||
if (typeId === null && typeList.value.length > 0) {
|
||
typeId = typeList.value[0].id
|
||
}
|
||
|
||
console.log('歌单类型ID:', typeId)
|
||
|
||
// 设置表单数据
|
||
form.value = {
|
||
name: playlist.name,
|
||
description: playlist.description || '',
|
||
typeId: typeId,
|
||
isOfficial: playlist.isOfficial,
|
||
cover: null,
|
||
// 保存当前封面URL,用于显示
|
||
currentCover: playlist.cover
|
||
}
|
||
|
||
// 强制触发响应式更新
|
||
setTimeout(() => {
|
||
form.value = { ...form.value }
|
||
}, 0)
|
||
|
||
console.log('设置的表单数据:', form.value)
|
||
} else {
|
||
form.value = {
|
||
name: row.name,
|
||
description: row.description || '',
|
||
typeId: row.typeId || '',
|
||
isOfficial: row.isOfficial || 0,
|
||
cover: null,
|
||
currentCover: row.cover
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('获取歌单详情失败:', error)
|
||
ElMessage.error('获取歌单详情失败,使用列表数据')
|
||
|
||
form.value = {
|
||
name: row.name,
|
||
description: row.description || '',
|
||
typeId: row.typeId || '',
|
||
isOfficial: row.isOfficial || 0,
|
||
cover: null,
|
||
currentCover: row.cover
|
||
}
|
||
}
|
||
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
// 删除歌单
|
||
const deletePlaylist = (row) => {
|
||
ElMessageBox.confirm(`确定要删除歌单 ${row.name} 吗?此操作不可恢复`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(async () => {
|
||
try {
|
||
const res = await apiDeletePlaylist(row.id)
|
||
if (res.code === 200) {
|
||
ElMessage.success('删除成功')
|
||
fetchPlaylistList() // 重新加载列表
|
||
} else {
|
||
ElMessage.error(res.message || '删除失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('删除歌单失败:', error)
|
||
ElMessage.error('删除失败,请稍后重试')
|
||
}
|
||
}).catch(() => {})
|
||
}
|
||
|
||
// 管理音乐对话框相关状态
|
||
const musicDialogVisible = ref(false)
|
||
const currentPlaylistId = ref(null)
|
||
const currentPlaylistName = ref('')
|
||
const musicList = ref([])
|
||
const selectedMusicIds = ref([])
|
||
const musicSearchKeyword = ref('')
|
||
const musicLoading = ref(false)
|
||
|
||
// 获取音乐列表
|
||
const fetchMusicList = async () => {
|
||
musicLoading.value = true
|
||
try {
|
||
const res = await getMusicList(1, 100, musicSearchKeyword.value)
|
||
if (res.code === 200) {
|
||
musicList.value = res.data.list || res.data.records || []
|
||
} else {
|
||
ElMessage.error(res.message || '获取音乐列表失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('获取音乐列表失败:', error)
|
||
ElMessage.error('获取音乐列表失败')
|
||
} finally {
|
||
musicLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取歌单中的音乐
|
||
const fetchPlaylistMusic = async (playlistId) => {
|
||
try {
|
||
const res = await getPlaylistById(playlistId)
|
||
if (res.code === 200 && res.data.musics) {
|
||
selectedMusicIds.value = res.data.musics.map(m => m.id)
|
||
}
|
||
} catch (error) {
|
||
console.error('获取歌单音乐失败:', error)
|
||
}
|
||
}
|
||
|
||
// 管理歌单音乐
|
||
const managePlaylistMusic = async (row) => {
|
||
currentPlaylistId.value = row.id
|
||
currentPlaylistName.value = row.name
|
||
musicSearchKeyword.value = ''
|
||
musicDialogVisible.value = true
|
||
|
||
// 加载音乐列表和已选音乐
|
||
await Promise.all([
|
||
fetchMusicList(),
|
||
fetchPlaylistMusic(row.id)
|
||
])
|
||
}
|
||
|
||
// 添加音乐到歌单
|
||
const addMusicToPlaylistLocal = async (musicId) => {
|
||
try {
|
||
const res = await addMusicToPlaylist(currentPlaylistId.value, musicId)
|
||
if (res.code === 200) {
|
||
ElMessage.success('添加成功')
|
||
await fetchPlaylistMusic(currentPlaylistId.value)
|
||
} else {
|
||
ElMessage.error(res.message || '添加失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('添加音乐失败:', error)
|
||
ElMessage.error('添加失败')
|
||
}
|
||
}
|
||
|
||
// 从歌单移除音乐
|
||
const removeMusicFromPlaylistLocal = async (musicId) => {
|
||
try {
|
||
const res = await removeMusicFromPlaylist(currentPlaylistId.value, musicId)
|
||
if (res.code === 200) {
|
||
ElMessage.success('移除成功')
|
||
await fetchPlaylistMusic(currentPlaylistId.value)
|
||
} else {
|
||
ElMessage.error(res.message || '移除失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('移除音乐失败:', error)
|
||
ElMessage.error('移除失败')
|
||
}
|
||
}
|
||
|
||
// 搜索音乐
|
||
const searchMusic = () => {
|
||
fetchMusicList()
|
||
}
|
||
|
||
// 提交表单
|
||
const submitForm = async () => {
|
||
// 表单验证
|
||
if (!form.value.name) {
|
||
ElMessage.warning('请输入歌单名称')
|
||
return
|
||
}
|
||
|
||
if (!form.value.typeId) {
|
||
ElMessage.warning('请选择歌单类型')
|
||
return
|
||
}
|
||
|
||
try {
|
||
let res
|
||
const formData = {
|
||
name: form.value.name,
|
||
description: form.value.description,
|
||
typeId: form.value.typeId,
|
||
isOfficial: form.value.isOfficial
|
||
}
|
||
|
||
console.log('提交的歌单数据:', formData)
|
||
|
||
if (currentPlaylist.value) {
|
||
// 编辑歌单
|
||
res = await apiUpdatePlaylist(
|
||
currentPlaylist.value.id,
|
||
formData,
|
||
form.value.cover
|
||
)
|
||
} else {
|
||
// 添加歌单
|
||
res = await apiAddPlaylist(
|
||
formData,
|
||
form.value.cover
|
||
)
|
||
}
|
||
|
||
if (res.code === 200) {
|
||
ElMessage.success(currentPlaylist.value ? '编辑成功' : '添加成功')
|
||
dialogVisible.value = false
|
||
fetchPlaylistList() // 重新加载列表
|
||
} else {
|
||
ElMessage.error(res.message || (currentPlaylist.value ? '编辑失败' : '添加失败'))
|
||
}
|
||
} catch (error) {
|
||
console.error(currentPlaylist.value ? '编辑歌单失败:' : '添加歌单失败:', error)
|
||
ElMessage.error(currentPlaylist.value ? '编辑失败,请稍后重试' : '添加失败,请稍后重试')
|
||
}
|
||
}
|
||
|
||
// 格式化类型
|
||
const formatType = (typeId) => {
|
||
const type = typeList.value.find(t => t.id === typeId)
|
||
return type ? type.name : '-'
|
||
}
|
||
|
||
// 处理封面上传
|
||
const handleCoverChange = (file) => {
|
||
if (file && file.raw) {
|
||
// 检查文件类型
|
||
const isImage = file.raw.type.startsWith('image/')
|
||
if (!isImage) {
|
||
ElMessage.error('只能上传图片文件!')
|
||
return
|
||
}
|
||
|
||
// 检查文件大小 (2MB)
|
||
const isLt2M = file.raw.size / 1024 / 1024 < 2
|
||
if (!isLt2M) {
|
||
ElMessage.error('图片大小不能超过 2MB!')
|
||
return
|
||
}
|
||
|
||
form.value.cover = file.raw
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchPlaylistList()
|
||
fetchTypeList()
|
||
})
|
||
|
||
// 导入音乐相关API
|
||
import { getMusicList } from '../../api/music'
|
||
import { addMusicToPlaylist, removeMusicFromPlaylist } from '../../api/playlist'
|
||
</script>
|
||
|
||
<template>
|
||
<div class="admin-playlist-container">
|
||
<!-- 搜索栏 -->
|
||
<div class="search-bar">
|
||
<el-input
|
||
v-model="searchKeyword"
|
||
placeholder="请输入歌单名称/描述"
|
||
clearable
|
||
@clear="resetSearch"
|
||
@keyup.enter="searchPlaylist"
|
||
class="search-input"
|
||
>
|
||
<template #append>
|
||
<el-button @click="searchPlaylist">
|
||
<el-icon><search /></el-icon>
|
||
搜索
|
||
</el-button>
|
||
</template>
|
||
</el-input>
|
||
|
||
<el-select
|
||
v-model="selectedType"
|
||
placeholder="选择类型"
|
||
clearable
|
||
@change="searchPlaylist"
|
||
class="type-select"
|
||
>
|
||
<el-option
|
||
v-for="type in typeList"
|
||
:key="type.id"
|
||
:label="type.name"
|
||
:value="type.id"
|
||
/>
|
||
</el-select>
|
||
|
||
<el-button type="primary" @click="addPlaylist">
|
||
<el-icon><plus /></el-icon>
|
||
添加歌单
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 歌单列表 -->
|
||
<el-table
|
||
:data="playlistList"
|
||
stripe
|
||
style="width: 100%"
|
||
v-loading="loading"
|
||
>
|
||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||
|
||
<el-table-column label="封面" width="80" align="center">
|
||
<template #default="scope">
|
||
<el-image
|
||
:src="scope.row.cover || '/default-cover.jpg'"
|
||
:alt="scope.row.name"
|
||
class="playlist-cover"
|
||
fit="cover"
|
||
:preview-src-list="scope.row.cover ? [scope.row.cover] : []"
|
||
:initial-index="0"
|
||
preview-teleported
|
||
>
|
||
<template #error>
|
||
<div class="image-error">
|
||
<el-icon><picture /></el-icon>
|
||
</div>
|
||
</template>
|
||
</el-image>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column prop="name" label="歌单名称" min-width="150"></el-table-column>
|
||
|
||
<el-table-column prop="description" label="描述" min-width="200">
|
||
<template #default="scope">
|
||
<el-tooltip
|
||
:content="scope.row.description"
|
||
placement="top"
|
||
:show-after="500"
|
||
>
|
||
<div class="description-text">{{ scope.row.description || '-' }}</div>
|
||
</el-tooltip>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="创建者" width="100">
|
||
<template #default="scope">
|
||
{{ scope.row.creator ? scope.row.creator.username : '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column prop="typeId" label="类型" width="100">
|
||
<template #default="scope">
|
||
{{ formatType(scope.row.typeId) }}
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column prop="isOfficial" label="是否官方" width="100" align="center">
|
||
<template #default="scope">
|
||
<el-tag :type="scope.row.isOfficial === 1 ? 'success' : 'info'">
|
||
{{ scope.row.isOfficial === 1 ? '是' : '否' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column prop="songCount" label="歌曲数" width="80" align="center"></el-table-column>
|
||
|
||
<el-table-column prop="playCount" label="播放次数" width="100" align="center"></el-table-column>
|
||
|
||
<el-table-column prop="createTime" label="创建时间" width="120">
|
||
<template #default="scope">
|
||
{{ scope.row.createTime ? new Date(scope.row.createTime).toLocaleDateString() : '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="操作" width="200" fixed="right">
|
||
<template #default="scope">
|
||
<el-button
|
||
size="small"
|
||
@click="managePlaylistMusic(scope.row)"
|
||
>
|
||
管理音乐
|
||
</el-button>
|
||
|
||
<el-button
|
||
size="small"
|
||
type="primary"
|
||
@click="editPlaylist(scope.row)"
|
||
>
|
||
编辑
|
||
</el-button>
|
||
|
||
<el-button
|
||
size="small"
|
||
type="danger"
|
||
@click="deletePlaylist(scope.row)"
|
||
>
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="pagination">
|
||
<el-pagination
|
||
v-model:current-page="pagination.currentPage"
|
||
v-model:page-size="pagination.pageSize"
|
||
:page-sizes="[5, 10, 15, 20]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:total="pagination.total"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 添加/编辑对话框 -->
|
||
<el-dialog
|
||
v-model="dialogVisible"
|
||
:title="dialogTitle"
|
||
width="600px"
|
||
>
|
||
<el-form
|
||
:model="form"
|
||
label-width="100px"
|
||
>
|
||
<el-form-item label="歌单名称" required>
|
||
<el-input v-model="form.name" placeholder="请输入歌单名称" />
|
||
</el-form-item>
|
||
|
||
<el-form-item label="描述">
|
||
<el-input
|
||
v-model="form.description"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入歌单描述"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="类型" required>
|
||
<el-select
|
||
v-model="form.typeId"
|
||
placeholder="请选择歌单类型"
|
||
style="width: 100%"
|
||
>
|
||
<el-option
|
||
v-for="type in typeList"
|
||
:key="type.id"
|
||
:label="type.name"
|
||
:value="type.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="是否官方">
|
||
<div class="official-switch-container">
|
||
<el-switch
|
||
v-model="form.isOfficial"
|
||
:active-value="1"
|
||
:inactive-value="0"
|
||
style="width: 40px"
|
||
/>
|
||
<span class="switch-value-text">
|
||
{{ form.isOfficial === 1 ? '当前状态: 官方歌单' : '当前状态: 非官方歌单' }}
|
||
</span>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="封面">
|
||
<div class="cover-container">
|
||
<!-- 当前封面预览 -->
|
||
<div class="cover-section">
|
||
<div class="section-title">当前封面:</div>
|
||
<div v-if="form.currentCover" class="current-cover">
|
||
<el-image
|
||
:src="form.currentCover"
|
||
fit="cover"
|
||
class="cover-preview"
|
||
:preview-src-list="[form.currentCover]"
|
||
:initial-index="0"
|
||
preview-teleported
|
||
/>
|
||
</div>
|
||
<div v-else class="no-cover">
|
||
<el-icon><picture /></el-icon>
|
||
<span>暂无封面</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 上传新封面 -->
|
||
<div class="upload-section">
|
||
<div class="section-title">上传新封面(建议尺寸 300x300 像素):</div>
|
||
|
||
<el-upload
|
||
action="#"
|
||
:auto-upload="false"
|
||
:on-change="handleCoverChange"
|
||
:limit="1"
|
||
list-type="picture-card"
|
||
class="cover-upload"
|
||
>
|
||
<el-icon><plus /></el-icon>
|
||
</el-upload>
|
||
<div class="el-upload__tip">
|
||
点击上传新封面,不上传则保持原封面
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="dialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 管理音乐对话框 -->
|
||
<el-dialog
|
||
v-model="musicDialogVisible"
|
||
:title="`管理歌单 ${currentPlaylistName} 的音乐`"
|
||
width="800px"
|
||
>
|
||
<div class="music-management-container">
|
||
<!-- 搜索栏 -->
|
||
<div class="search-bar">
|
||
<el-input
|
||
v-model="musicSearchKeyword"
|
||
placeholder="搜索音乐名称"
|
||
clearable
|
||
@keyup.enter="searchMusic"
|
||
class="search-input"
|
||
>
|
||
<template #append>
|
||
<el-button @click="searchMusic">
|
||
<el-icon><search /></el-icon>
|
||
搜索
|
||
</el-button>
|
||
</template>
|
||
</el-input>
|
||
</div>
|
||
|
||
<!-- 音乐列表 -->
|
||
<div class="music-list-container">
|
||
<el-table
|
||
:data="musicList"
|
||
stripe
|
||
style="width: 100%"
|
||
v-loading="musicLoading"
|
||
>
|
||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||
<el-table-column prop="name" label="音乐名称" min-width="150"></el-table-column>
|
||
<el-table-column label="操作" width="120">
|
||
<template #default="scope">
|
||
<el-button
|
||
size="small"
|
||
type="primary"
|
||
@click="addMusicToPlaylistLocal(scope.row.id)"
|
||
:disabled="selectedMusicIds.includes(scope.row.id)"
|
||
>
|
||
{{ selectedMusicIds.includes(scope.row.id) ? '已添加' : '添加' }}
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- 已添加音乐 -->
|
||
<div class="selected-music-container">
|
||
<h3>已添加的音乐</h3>
|
||
<el-table
|
||
:data="musicList.filter(m => selectedMusicIds.includes(m.id))"
|
||
stripe
|
||
style="width: 100%"
|
||
empty-text="暂无音乐"
|
||
>
|
||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||
<el-table-column prop="name" label="音乐名称" min-width="150"></el-table-column>
|
||
<el-table-column label="操作" width="120">
|
||
<template #default="scope">
|
||
<el-button
|
||
size="small"
|
||
type="danger"
|
||
@click="removeMusicFromPlaylistLocal(scope.row.id)"
|
||
>
|
||
移除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="musicDialogVisible = false">关闭</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.music-management-container {
|
||
padding: 10px;
|
||
}
|
||
|
||
.search-bar {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.search-input {
|
||
width: 300px;
|
||
}
|
||
|
||
.music-list-container,
|
||
.selected-music-container {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.selected-music-container h3 {
|
||
margin-bottom: 10px;
|
||
color: #606266;
|
||
}
|
||
</style>
|
||
|
||
<style scoped>
|
||
.admin-playlist-container {
|
||
padding: 20px;
|
||
background: rgba(255, 255, 255, 0.8);
|
||
border-radius: 16px;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.search-bar {
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
padding: 15px;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.search-input {
|
||
width: 300px;
|
||
}
|
||
|
||
.search-input :deep(.el-input__inner) {
|
||
border-radius: 8px;
|
||
border: 1px solid #b3e5fc;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.search-input :deep(.el-input__inner):focus {
|
||
border-color: #4dd0e1;
|
||
box-shadow: 0 0 0 2px rgba(77, 208, 225, 0.2);
|
||
}
|
||
|
||
.type-select {
|
||
width: 150px;
|
||
}
|
||
|
||
.type-select :deep(.el-input__inner) {
|
||
border-radius: 8px;
|
||
border: 1px solid #b3e5fc;
|
||
}
|
||
|
||
.el-button {
|
||
border-radius: 8px !important;
|
||
font-weight: bold !important;
|
||
transition: all 0.3s !important;
|
||
}
|
||
|
||
.el-button--primary {
|
||
background: linear-gradient(135deg, #4dd0e1 0%, #26c6da 100%) !important;
|
||
border: none !important;
|
||
}
|
||
|
||
.el-button--primary:hover {
|
||
transform: translateY(-2px) !important;
|
||
box-shadow: 0 4px 8px rgba(38, 198, 218, 0.3) !important;
|
||
}
|
||
|
||
.el-table {
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.el-table :deep(th) {
|
||
background-color: #e1f5fe !important;
|
||
color: #0d47a1;
|
||
}
|
||
|
||
.el-table :deep(.el-table__row) {
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.el-table :deep(.el-table__row:hover) {
|
||
background-color: #f5f5f5 !important;
|
||
transform: scale(1.01);
|
||
}
|
||
|
||
.pagination {
|
||
margin-top: 20px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
padding: 15px;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.playlist-cover {
|
||
width: 50px;
|
||
height: 50px;
|
||
border-radius: 8px;
|
||
object-fit: cover;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.playlist-cover:hover {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.image-error {
|
||
width: 50px;
|
||
height: 50px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
background-color: #f5f7fa;
|
||
color: #909399;
|
||
font-size: 20px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.description-text {
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
max-width: 200px;
|
||
}
|
||
|
||
.el-dialog {
|
||
border-radius: 16px !important;
|
||
}
|
||
|
||
.el-dialog :deep(.el-dialog__header) {
|
||
background: linear-gradient(135deg, #e1f5fe 0%, #b3e5fc 100%);
|
||
border-radius: 16px 16px 0 0 !important;
|
||
padding: 15px 20px;
|
||
}
|
||
|
||
.el-dialog :deep(.el-dialog__title) {
|
||
color: #0d47a1;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.el-form-item :deep(.el-form-item__label) {
|
||
color: #4a4a4a;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.el-input :deep(.el-input__inner) {
|
||
border-radius: 8px;
|
||
border: 1px solid #b3e5fc;
|
||
}
|
||
|
||
.el-input :deep(.el-input__inner):focus {
|
||
border-color: #4dd0e1;
|
||
box-shadow: 0 0 0 2px rgba(77, 208, 225, 0.2);
|
||
}
|
||
|
||
.cover-container {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 30px;
|
||
}
|
||
|
||
.cover-section,
|
||
.upload-section {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 14px;
|
||
color: #4a4a4a;
|
||
margin-bottom: 10px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.current-cover {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.cover-preview {
|
||
width: 100px;
|
||
height: 100px;
|
||
border-radius: 8px;
|
||
object-fit: cover;
|
||
border: 1px solid #b3e5fc;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.cover-preview:hover {
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.no-cover {
|
||
width: 100px;
|
||
height: 100px;
|
||
border-radius: 8px;
|
||
background-color: #f5f7fa;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
color: #909399;
|
||
border: 1px dashed #b3e5fc;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.no-cover:hover {
|
||
border-color: #4dd0e1;
|
||
}
|
||
|
||
.no-cover .el-icon {
|
||
font-size: 24px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.cover-upload :deep(.el-upload--picture-card) {
|
||
width: 100px;
|
||
height: 100px;
|
||
line-height: 100px;
|
||
border-radius: 8px;
|
||
border: 1px dashed #b3e5fc;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.cover-upload :deep(.el-upload--picture-card:hover) {
|
||
border-color: #4dd0e1;
|
||
}
|
||
|
||
.official-switch-container {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.switch-value-text {
|
||
margin-left: 15px;
|
||
font-size: 13px;
|
||
color: #4a4a4a;
|
||
}
|
||
|
||
.el-table :deep(.el-button) {
|
||
margin-left: 0;
|
||
margin-right: 5px;
|
||
padding: 4px 8px;
|
||
border-radius: 8px !important;
|
||
}
|
||
|
||
.music-management-container {
|
||
padding: 10px;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.music-management-container .search-bar {
|
||
background: transparent;
|
||
padding: 0;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.music-list-container,
|
||
.selected-music-container {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.selected-music-container h3 {
|
||
margin-bottom: 10px;
|
||
color: #4a4a4a;
|
||
}
|
||
</style>
|