feat: 迁移自定义功能至 1.5.9.9 版本
包含音乐源限制、CI 防 OOM 优化、数据库连接池优化、管理端权限及 UI 细节定制等功能
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<aside
|
||||
:class="[
|
||||
'fixed inset-y-0 left-0 z-50 w-64 bg-bg-primary border-r border-border-secondary transform transition-transform duration-300 ease-in-out lg:translate-x-0',
|
||||
'fixed inset-y-0 left-0 z-50 w-64 bg-[#09090b] border-r border-zinc-800 transform transition-transform duration-300 ease-in-out lg:translate-x-0',
|
||||
isOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
]"
|
||||
>
|
||||
@@ -12,26 +12,34 @@
|
||||
<NuxtLink to="/" class="flex items-center gap-2.5 group">
|
||||
<!-- Logo 图标 -->
|
||||
<div class="flex-shrink-0 group-hover:scale-110 transition-all duration-300">
|
||||
<img src="/assets/logo.png" alt="VoiceHub Logo" class="w-8 h-8 object-contain" >
|
||||
<img
|
||||
:src="logo"
|
||||
alt="VoiceHub Logo"
|
||||
class="w-8 h-8 object-contain"
|
||||
width="32"
|
||||
height="32"
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
<!-- 品牌文字 -->
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 class="font-bold text-lg text-text-primary leading-none tracking-tight">VoiceHub</h1>
|
||||
<h1 class="font-bold text-lg text-zinc-100 leading-none tracking-tight">VoiceHub</h1>
|
||||
<p
|
||||
class="text-[10px] text-text-tertiary mt-1.5 uppercase tracking-widest font-bold leading-none"
|
||||
class="text-[10px] text-zinc-500 mt-1.5 uppercase tracking-widest font-bold leading-none"
|
||||
>
|
||||
{{ locale.console }}
|
||||
管理控制台
|
||||
</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- 导航菜单区域 -->
|
||||
<nav class="flex-1 space-y-6 overflow-y-auto pr-2 custom-scrollbar">
|
||||
<nav class="admin-sidebar-nav flex-1 space-y-6 overflow-y-auto pr-2 custom-scrollbar">
|
||||
<div v-for="(group, idx) in menuGroups" :key="idx" class="space-y-1">
|
||||
<template v-if="shouldShowGroup(group)">
|
||||
<!-- 分组标题 -->
|
||||
<h3 class="px-3 text-[10px] font-bold text-text-disabled uppercase tracking-[0.2em] mb-2">
|
||||
<h3 class="px-3 text-[10px] font-bold text-zinc-600 uppercase tracking-[0.2em] mb-2">
|
||||
{{ group.section }}
|
||||
</h3>
|
||||
<!-- 菜单项列表 -->
|
||||
@@ -39,10 +47,11 @@
|
||||
<button
|
||||
v-if="permissions.canAccessPage(item.permissionId || item.id)"
|
||||
:class="[
|
||||
'w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-[13px] font-bold transition-all group border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-10 focus-visible:ring-inset',
|
||||
'w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-[13px] font-bold transition-all group border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/80 focus-visible:ring-inset',
|
||||
activeTab === item.id
|
||||
? 'bg-primary-hover-10 text-primary border-primary-20'
|
||||
: 'text-text-tertiary hover:text-text-primary hover:bg-bg-tertiary-40 border-transparent'
|
||||
? 'bg-blue-600/10 text-blue-400 border-blue-500/20'
|
||||
: 'text-zinc-500 hover:text-zinc-200 hover:bg-zinc-800/40 border-transparent',
|
||||
getTourClass(item.id)
|
||||
]"
|
||||
@click="onNavigate(item.id)"
|
||||
>
|
||||
@@ -52,15 +61,15 @@
|
||||
:size="18"
|
||||
:class="
|
||||
activeTab === item.id
|
||||
? 'text-primary'
|
||||
: 'text-text-tertiary group-hover:text-text-secondary'
|
||||
? 'text-blue-400'
|
||||
: 'text-zinc-500 group-hover:text-zinc-300'
|
||||
"
|
||||
/>
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
<!-- 选中状态指示器 -->
|
||||
<div
|
||||
v-if="activeTab === item.id"
|
||||
class="ml-auto w-1 h-1 bg-primary rounded-full shadow-[0_0_8px_var(--primary-60)]"
|
||||
class="ml-auto w-1 h-1 bg-blue-400 rounded-full shadow-[0_0_8px_rgba(96,165,250,0.6)]"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
@@ -69,38 +78,38 @@
|
||||
</nav>
|
||||
|
||||
<!-- 用户信息及退出登录 -->
|
||||
<div class="mt-4 pt-4 border-t border-border-secondary">
|
||||
<div class="mt-4 pt-4 border-t border-zinc-800">
|
||||
<div
|
||||
class="flex items-center gap-3 p-3 rounded-lg bg-bg-secondary-50 border border-border-secondary-50 hover:bg-bg-tertiary-30 transition-colors"
|
||||
class="flex items-center gap-3 p-3 rounded-lg bg-zinc-900/50 border border-zinc-800/50 hover:bg-zinc-800/30 transition-colors"
|
||||
>
|
||||
<!-- 用户头像/首字母 -->
|
||||
<img
|
||||
v-if="currentUser?.avatar && !avatarError"
|
||||
:src="currentUser.avatar"
|
||||
class="w-10 h-10 rounded-lg object-cover border border-border-tertiary shrink-0"
|
||||
class="w-10 h-10 rounded-lg object-cover border border-zinc-700 shrink-0"
|
||||
@error="avatarError = true"
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
class="w-10 h-10 rounded-lg bg-bg-tertiary flex items-center justify-center text-text-tertiary font-bold border border-border-tertiary shrink-0"
|
||||
class="w-10 h-10 rounded-lg bg-zinc-800 flex items-center justify-center text-zinc-400 font-bold border border-zinc-700 shrink-0"
|
||||
>
|
||||
{{ (currentUser?.name || locale.avatarFallback || '管').charAt(0) }}
|
||||
{{ (currentUser?.name || '管').charAt(0) }}
|
||||
</div>
|
||||
<!-- 用户详细信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs font-black truncate text-text-primary">
|
||||
{{ currentUser?.name || locale.adminFallback }}
|
||||
<p class="text-xs font-black truncate text-zinc-100">
|
||||
{{ currentUser?.name || '管理员' }}
|
||||
</p>
|
||||
<p
|
||||
class="text-[10px] text-text-tertiary truncate uppercase tracking-wider font-medium mt-0.5"
|
||||
class="text-[10px] text-zinc-500 truncate uppercase tracking-wider font-medium mt-0.5"
|
||||
>
|
||||
{{ getRoleDisplayName(currentUser?.role || 'ADMIN') }}
|
||||
{{ currentUser?.role?.replace('_', ' ') || 'ADMIN' }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- 退出按钮 -->
|
||||
<button
|
||||
class="p-2 text-text-disabled hover:text-error hover:bg-error-10 rounded-lg transition-all"
|
||||
:title="locale.logout"
|
||||
class="p-2 text-zinc-600 hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-all"
|
||||
title="退出登录"
|
||||
@click="$emit('logout')"
|
||||
>
|
||||
<LogOut :size="16" />
|
||||
@@ -128,20 +137,20 @@ import {
|
||||
LogOut,
|
||||
Clock,
|
||||
FileEdit,
|
||||
ListChecks,
|
||||
BookOpen,
|
||||
Ban,
|
||||
ListMusic,
|
||||
Globe,
|
||||
Image as ImageIcon,
|
||||
ScrollText,
|
||||
Database,
|
||||
Lock,
|
||||
Ticket,
|
||||
GraduationCap
|
||||
Shield
|
||||
} from '@lucide/vue'
|
||||
import { useLocale } from '~/utils/locale'
|
||||
import logo from '~~/public/images/logo.png'
|
||||
import { rolesConfig } from '~/composables/usePermissions'
|
||||
|
||||
const avatarError = ref(false)
|
||||
const { admin } = useLocale()
|
||||
const locale = computed(() => admin.value?.sidebar || {})
|
||||
|
||||
const props = defineProps({
|
||||
// 侧边栏是否打开(移动端)
|
||||
@@ -166,56 +175,57 @@ watch(
|
||||
const emit = defineEmits(['navigate', 'close', 'logout'])
|
||||
|
||||
// 菜单分组配置
|
||||
const menuGroups = computed(() => [
|
||||
const menuGroups = [
|
||||
{
|
||||
section: locale.value.sections?.overview || '概览',
|
||||
items: [{ icon: LayoutDashboard, label: locale.value.menu?.overview || '数据概览', id: 'overview' }]
|
||||
},
|
||||
{
|
||||
section: locale.value.sections?.content || '内容管理',
|
||||
section: '数据中心',
|
||||
items: [
|
||||
{ icon: CalendarDays, label: locale.value.menu?.schedule || '排班管理', id: 'schedule' },
|
||||
{ icon: Printer, label: locale.value.menu?.print || '打印管理', id: 'print' },
|
||||
{ icon: Music2, label: locale.value.menu?.songs || '点歌管理', id: 'songs' },
|
||||
{
|
||||
icon: BarChart3,
|
||||
label: locale.value.menu?.dataAnalysis || '数据分析',
|
||||
id: 'data-analysis',
|
||||
permissionId: 'data-analysis'
|
||||
}
|
||||
{ icon: LayoutDashboard, label: '数据概览', id: 'overview' },
|
||||
{ icon: BookOpen, label: '流程与教程', id: 'workflow-tutorial' },
|
||||
{ icon: BarChart3, label: '数据分析', id: 'data-analysis', permissionId: 'data-analysis' }
|
||||
]
|
||||
},
|
||||
{
|
||||
section: locale.value.sections?.users || '用户管理',
|
||||
section: '播音管理',
|
||||
items: [
|
||||
{ icon: Users, label: locale.value.menu?.users || '用户管理', id: 'users' },
|
||||
{ icon: GraduationCap, label: locale.value.menu?.gradeClass || '年级班级', id: 'grade-class' }
|
||||
{ icon: Music2, label: '歌曲管理', id: 'songs' },
|
||||
{ icon: CalendarDays, label: '排期管理', id: 'schedule' },
|
||||
{ icon: Printer, label: '打印排期', id: 'print' },
|
||||
{ icon: ImageIcon, label: '图片歌单', id: 'playlist-card' }
|
||||
]
|
||||
},
|
||||
{
|
||||
section: locale.value.sections?.api || 'API',
|
||||
items: [{ icon: Key, label: locale.value.menu?.apiKeys || 'API 密钥', id: 'api-keys' }]
|
||||
},
|
||||
{
|
||||
section: locale.value.sections?.system || '系统设置',
|
||||
section: '业务设置',
|
||||
items: [
|
||||
{ icon: Bell, label: locale.value.menu?.notifications || '通知管理', id: 'notifications' },
|
||||
{ icon: Mail, label: locale.value.menu?.smtpConfig || 'SMTP 配置', id: 'smtp-config' },
|
||||
{ icon: Clock, label: locale.value.menu?.playtimes || '播放时段', id: 'playtimes' },
|
||||
{ icon: FileEdit, label: locale.value.menu?.requestTimes || '点歌时段', id: 'request-times' },
|
||||
{ icon: BookOpen, label: locale.value.menu?.semesters || '学期管理', id: 'semesters' },
|
||||
{ icon: Ban, label: locale.value.menu?.blacklist || '黑名单', id: 'blacklist' },
|
||||
{ icon: Ticket, label: locale.value.menu?.cardCodes || '卡密管理', id: 'card-codes' },
|
||||
{ icon: ListMusic, label: locale.value.menu?.musicSource || '音源控制', id: 'music-source' },
|
||||
{ icon: Globe, label: locale.value.menu?.siteConfig || '站点配置', id: 'site-config' },
|
||||
{ icon: Database, label: locale.value.menu?.database || '数据库', id: 'database' }
|
||||
{ icon: CalendarDays, label: '选曲日期', id: 'expected-play-dates', permissionId: 'settings' },
|
||||
{ icon: FileEdit, label: '投稿管理', id: 'request-times' },
|
||||
{ icon: BookOpen, label: '学期管理', id: 'semesters' },
|
||||
{ icon: Clock, label: '播出时段', id: 'playtimes' }
|
||||
]
|
||||
},
|
||||
{
|
||||
section: locale.value.sections?.account || '账户',
|
||||
items: [{ icon: Lock, label: locale.value.menu?.password || '修改密码', id: 'password' }]
|
||||
section: '用户与权限',
|
||||
items: [
|
||||
{ icon: Users, label: '用户管理', id: 'users' },
|
||||
{ icon: Shield, label: '权限组管理', id: 'roles', permissionId: 'roles' },
|
||||
{ icon: Ban, label: '黑名单管理', id: 'blacklist' },
|
||||
{ icon: Key, label: 'API密钥管理', id: 'api-keys' }
|
||||
]
|
||||
},
|
||||
{
|
||||
section: '系统运维',
|
||||
items: [
|
||||
{ icon: Globe, label: '站点配置', id: 'site-config' },
|
||||
{ icon: ScrollText, label: '更新日志', id: 'changelog' },
|
||||
{ icon: Bell, label: '通知管理', id: 'notifications' },
|
||||
{ icon: Mail, label: '邮件配置', id: 'smtp-config' },
|
||||
{ icon: Database, label: '数据库操作', id: 'database' }
|
||||
]
|
||||
},
|
||||
{
|
||||
section: '账户管理',
|
||||
items: [{ icon: Lock, label: '修改密码', id: 'password' }]
|
||||
}
|
||||
])
|
||||
]
|
||||
|
||||
/**
|
||||
* 判断是否应该显示该菜单组
|
||||
@@ -238,12 +248,25 @@ const onNavigate = (id) => {
|
||||
emit('navigate', id)
|
||||
}
|
||||
|
||||
const getTourClass = (id) => `tour-nav-${id}`
|
||||
|
||||
/**
|
||||
* 获取角色显示名称
|
||||
* @param {string} role 角色标识
|
||||
*/
|
||||
const getRoleDisplayName = (role) => {
|
||||
return locale.value.roles?.[role] || role
|
||||
if (rolesConfig.value && rolesConfig.value[role]?.displayName) {
|
||||
return rolesConfig.value[role].displayName
|
||||
}
|
||||
const roleNames = {
|
||||
USER: '学生(普通用户)',
|
||||
SONG_ADMIN: '工作人员',
|
||||
VICE_MINISTER: '副部',
|
||||
ADMIN: '部长',
|
||||
RESIDENT: '常驻',
|
||||
SUPER_ADMIN: '技术运维'
|
||||
}
|
||||
return roleNames[role] || role
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -256,10 +279,10 @@ const getRoleDisplayName = (role) => {
|
||||
background: transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: var(--panel-bg-alt);
|
||||
background: var(--bg-tertiary, #27272a);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--panel-bg-hover);
|
||||
background: #3f3f46;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user