feat: 初始化音乐项目后台管理系统- 新增后台管理相关页面和功能组件
- 实现管理员评论管理、数据统计等功能 - 添加后台布局和路由管理 -配置数据库和Redis连接 - 实现文件上传和访问路径配置 - 添加Sa-Token安全配置
This commit is contained in:
852
music-qianduan/src/views/admin/Singer.vue
Normal file
852
music-qianduan/src/views/admin/Singer.vue
Normal file
@@ -0,0 +1,852 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getSingerList, addSinger as apiAddSinger, updateSinger, deleteSinger } from '../../api/singer'
|
||||
import { getSingerTypeList } from '../../api/admin/type'
|
||||
import { getMusicList } from '../../api/music'
|
||||
import { addMusicToSinger, removeMusicFromSinger } from '../../api/singer'
|
||||
|
||||
// 歌手列表
|
||||
const singerList = 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 currentSinger = ref(null)
|
||||
|
||||
// 表单数据
|
||||
const form = ref({
|
||||
name: '',
|
||||
typeId: '',
|
||||
introduction: '',
|
||||
avatar: null
|
||||
})
|
||||
|
||||
// 获取歌手列表
|
||||
const fetchSingerList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.value.currentPage,
|
||||
size: pagination.value.pageSize,
|
||||
keyword: searchKeyword.value,
|
||||
typeId: selectedType.value || null
|
||||
}
|
||||
|
||||
const res = await getSingerList(params.page, params.size, params.keyword, params.typeId)
|
||||
|
||||
if (res.code === 200) {
|
||||
singerList.value = res.data.list
|
||||
pagination.value.total = res.data.total
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取歌手列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取歌手列表错误:', error)
|
||||
ElMessage.error('获取歌手列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取类型列表
|
||||
const fetchTypeList = async () => {
|
||||
try {
|
||||
const res = await getSingerTypeList()
|
||||
if (res.code === 200) {
|
||||
typeList.value = res.data
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取歌手类型列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取歌手类型列表错误:', error)
|
||||
ElMessage.error('获取歌手类型列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理页码变化
|
||||
const handleCurrentChange = (page) => {
|
||||
pagination.value.currentPage = page
|
||||
fetchSingerList()
|
||||
}
|
||||
|
||||
// 处理每页条数变化
|
||||
const handleSizeChange = (size) => {
|
||||
pagination.value.pageSize = size
|
||||
pagination.value.currentPage = 1
|
||||
fetchSingerList()
|
||||
}
|
||||
|
||||
// 搜索歌手
|
||||
const searchSinger = () => {
|
||||
pagination.value.currentPage = 1
|
||||
fetchSingerList()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
searchKeyword.value = ''
|
||||
selectedType.value = ''
|
||||
pagination.value.currentPage = 1
|
||||
fetchSingerList()
|
||||
}
|
||||
|
||||
// 添加歌手
|
||||
const addSinger = () => {
|
||||
dialogTitle.value = '添加歌手'
|
||||
currentSinger.value = null
|
||||
form.value = {
|
||||
name: '',
|
||||
typeId: '',
|
||||
introduction: '',
|
||||
avatar: null
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑歌手
|
||||
const editSinger = (row) => {
|
||||
dialogTitle.value = '编辑歌手'
|
||||
currentSinger.value = row
|
||||
form.value = {
|
||||
name: row.name,
|
||||
typeId: row.type ? row.type.id : row.typeId,
|
||||
introduction: row.introduction || '',
|
||||
avatar: null
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 删除歌手
|
||||
const handleDeleteSinger = (row) => {
|
||||
ElMessageBox.confirm(`确定要删除歌手 ${row.name} 吗?此操作不可恢复`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const res = await deleteSinger(row.id)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.message || '删除成功')
|
||||
// 刷新歌手列表
|
||||
fetchSingerList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除歌手错误:', error)
|
||||
ElMessage.error('删除歌手失败')
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 管理音乐对话框相关状态
|
||||
const musicDialogVisible = ref(false)
|
||||
const currentSingerId = ref(null)
|
||||
const currentSingerName = 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 fetchSingerMusic = async (singerId) => {
|
||||
try {
|
||||
const res = await getSingerList(1, 1, '', singerId)
|
||||
if (res.code === 200 && res.data.list[0].musics) {
|
||||
selectedMusicIds.value = res.data.list[0].musics.map(m => m.id)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取歌手音乐失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 管理歌手音乐
|
||||
const manageSingerMusic = async (row) => {
|
||||
currentSingerId.value = row.id
|
||||
currentSingerName.value = row.name
|
||||
musicSearchKeyword.value = ''
|
||||
musicDialogVisible.value = true
|
||||
|
||||
// 加载音乐列表和已选音乐
|
||||
await Promise.all([
|
||||
fetchMusicList(),
|
||||
fetchSingerMusic(row.id)
|
||||
])
|
||||
}
|
||||
|
||||
// 添加音乐到歌手
|
||||
const addMusicToSingerLocal = async (musicId) => {
|
||||
try {
|
||||
const res = await addMusicToSinger(currentSingerId.value, musicId)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('添加成功')
|
||||
await fetchSingerMusic(currentSingerId.value)
|
||||
} else {
|
||||
ElMessage.error(res.message || '添加失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加音乐失败:', error)
|
||||
ElMessage.error('添加失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 从歌手移除音乐
|
||||
const removeMusicFromSingerLocal = async (musicId) => {
|
||||
try {
|
||||
const res = await removeMusicFromSinger(currentSingerId.value, musicId)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('移除成功')
|
||||
await fetchSingerMusic(currentSingerId.value)
|
||||
} else {
|
||||
ElMessage.error(res.message || '移除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('移除音乐失败:', error)
|
||||
ElMessage.error('移除失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索音乐
|
||||
const searchMusic = () => {
|
||||
fetchMusicList()
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
try {
|
||||
// 构建歌手数据
|
||||
const singerData = {
|
||||
name: form.value.name,
|
||||
introduction: form.value.introduction,
|
||||
type: form.value.typeId ? { id: form.value.typeId } : null
|
||||
}
|
||||
|
||||
let res
|
||||
if (currentSinger.value) {
|
||||
// 编辑歌手
|
||||
res = await updateSinger(currentSinger.value.id, singerData, form.value.avatar)
|
||||
} else {
|
||||
// 添加歌手
|
||||
res = await apiAddSinger(singerData, form.value.avatar)
|
||||
}
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.message || (currentSinger.value ? '编辑成功' : '添加成功'))
|
||||
dialogVisible.value = false
|
||||
// 刷新歌手列表
|
||||
fetchSingerList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交歌手表单错误:', error)
|
||||
ElMessage.error('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化类型
|
||||
const formatType = (typeId) => {
|
||||
const type = typeList.value.find(t => t.id === typeId)
|
||||
return type ? type.name : '-'
|
||||
}
|
||||
|
||||
// 格式化粉丝数
|
||||
const formatFans = (count) => {
|
||||
if (count < 10000) {
|
||||
return count.toString()
|
||||
} else if (count < 100000000) {
|
||||
return (count / 10000).toFixed(1) + '万'
|
||||
} else {
|
||||
return (count / 100000000).toFixed(1) + '亿'
|
||||
}
|
||||
}
|
||||
|
||||
// 处理头像上传
|
||||
const handleAvatarChange = (file) => {
|
||||
if (file && file.raw) {
|
||||
// 验证文件类型
|
||||
const isImage = file.raw.type.startsWith('image/')
|
||||
if (!isImage) {
|
||||
ElMessage.error('只能上传图片文件!')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件大小
|
||||
const isLt2M = file.raw.size / 1024 / 1024 < 2
|
||||
if (!isLt2M) {
|
||||
ElMessage.error('图片大小不能超过 2MB!')
|
||||
return
|
||||
}
|
||||
|
||||
form.value.avatar = file.raw
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchSingerList()
|
||||
fetchTypeList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="admin-singer-container">
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入歌手名称/简介"
|
||||
clearable
|
||||
@clear="resetSearch"
|
||||
@keyup.enter="searchSinger"
|
||||
class="search-input"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="searchSinger">
|
||||
<el-icon><search /></el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-select
|
||||
v-model="selectedType"
|
||||
placeholder="选择类型"
|
||||
clearable
|
||||
@change="searchSinger"
|
||||
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="addSinger">
|
||||
<el-icon><plus /></el-icon>
|
||||
添加歌手
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 歌手列表 -->
|
||||
<el-table
|
||||
:data="singerList"
|
||||
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">
|
||||
<img
|
||||
:src="scope.row.avatar"
|
||||
:alt="scope.row.name"
|
||||
class="singer-avatar"
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="name" label="歌手名称" min-width="150"></el-table-column>
|
||||
|
||||
<el-table-column label="类型" width="120">
|
||||
<template #default="scope">
|
||||
{{ formatType(scope.row.type ? scope.row.type.id : scope.row.typeId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="introduction" label="简介" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-tooltip
|
||||
:content="scope.row.introduction"
|
||||
placement="top"
|
||||
:show-after="500"
|
||||
>
|
||||
<div class="introduction-text">{{ scope.row.introduction }}</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="musicCount" label="单曲数" width="80" align="center"></el-table-column>
|
||||
|
||||
<el-table-column prop="fansCount" label="粉丝数" width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ formatFans(scope.row.fansCount || 0) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="createTime" label="创建时间" width="120"></el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
@click="manageSingerMusic(scope.row)"
|
||||
>
|
||||
管理音乐
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="editSinger(scope.row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="handleDeleteSinger(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="类型" 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="简介">
|
||||
<el-input
|
||||
v-model="form.introduction"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入歌手简介"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="头像">
|
||||
<!-- 当前头像预览 -->
|
||||
<div v-if="currentSinger && currentSinger.avatar" class="current-avatar">
|
||||
<el-image
|
||||
:src="currentSinger.avatar"
|
||||
fit="cover"
|
||||
class="avatar-preview"
|
||||
:preview-src-list="[currentSinger.avatar]"
|
||||
:initial-index="0"
|
||||
preview-teleported
|
||||
/>
|
||||
<div class="avatar-tip">当前头像</div>
|
||||
</div>
|
||||
|
||||
<el-upload
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:on-change="handleAvatarChange"
|
||||
:limit="1"
|
||||
list-type="picture-card"
|
||||
>
|
||||
<el-icon><plus /></el-icon>
|
||||
</el-upload>
|
||||
<div class="el-upload__tip">
|
||||
点击上传新头像,不上传则保持原头像
|
||||
</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="`管理歌手 ${currentSingerName} 的音乐`"
|
||||
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="addMusicToSingerLocal(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="removeMusicFromSingerLocal(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-singer-container {
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #e1f5fe 0%, #b3e5fc 100%);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 15px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.singer-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.singer-avatar:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.introduction-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--el-table-border-color: #b3e5fc;
|
||||
--el-table-header-bg-color: #e1f5fe;
|
||||
--el-table-row-hover-bg-color: #b3e5fc;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.el-table th.el-table__cell {
|
||||
background-color: #e1f5fe !important;
|
||||
color: #0d47a1 !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.el-table .el-table__row {
|
||||
background-color: rgba(255, 255, 255, 0.9) !important;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.el-table .el-table__row:hover {
|
||||
background-color: #b3e5fc !important;
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.el-table :deep(.el-button) {
|
||||
margin-left: 0;
|
||||
margin-right: 5px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.current-avatar {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar-preview {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #b3e5fc;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.avatar-preview:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.avatar-tip {
|
||||
font-size: 12px;
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
.el-upload :deep(.el-upload--picture-card) {
|
||||
border-radius: 8px;
|
||||
border: 1px dashed #b3e5fc;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.el-upload :deep(.el-upload--picture-card:hover) {
|
||||
border-color: #4dd0e1;
|
||||
}
|
||||
|
||||
.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>
|
||||
Reference in New Issue
Block a user