95 lines
6.2 KiB
Vue
95 lines
6.2 KiB
Vue
<script setup>
|
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { House, VideoCamera, Film, Lock, Setting, SwitchButton, ArrowDown, FullScreen, Close, Moon, Sunny, Document, DataAnalysis, Picture, User } from '@element-plus/icons-vue'
|
|
import { useAuth } from '../stores/auth'
|
|
import { useRbac } from '../stores/rbac'
|
|
import { roleData } from '../data/roles'
|
|
import { useTheme } from '../stores/theme'
|
|
import { customMenus } from '../data/customMenus'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const { currentRole, logout, setRole } = useAuth()
|
|
const { can } = useRbac()
|
|
const customIconMap = { Document, DataAnalysis, Picture, User, Setting, VideoCamera }
|
|
const { isDark, toggleTheme } = useTheme()
|
|
const tabs = ref([{ name: 'dashboard', title: '工作台' }])
|
|
const isFullscreen = ref(Boolean(document.fullscreenElement))
|
|
const activeTab = computed({
|
|
get: () => String(route.name || 'dashboard'),
|
|
set: name => router.push({ name }),
|
|
})
|
|
|
|
watch(() => route.name, name => {
|
|
if (!name || name === 'login') return
|
|
const title = String(route.meta.title || '工作台')
|
|
if (!tabs.value.some(tab => tab.name === name)) tabs.value.push({ name, title })
|
|
})
|
|
|
|
function syncFullscreen() { isFullscreen.value = Boolean(document.fullscreenElement) }
|
|
async function toggleFullscreen() {
|
|
try {
|
|
if (document.fullscreenElement) await document.exitFullscreen()
|
|
else await document.documentElement.requestFullscreen()
|
|
} catch {
|
|
// Fullscreen may be unavailable in embedded previews or restricted browsers.
|
|
}
|
|
}
|
|
onMounted(() => document.addEventListener('fullscreenchange', syncFullscreen))
|
|
onUnmounted(() => document.removeEventListener('fullscreenchange', syncFullscreen))
|
|
|
|
function closeTab(name) {
|
|
if (name === 'dashboard') return
|
|
const index = tabs.value.findIndex(tab => tab.name === name)
|
|
tabs.value = tabs.value.filter(tab => tab.name !== name)
|
|
if (String(route.name) === name) router.push({ name: tabs.value[Math.max(0, index - 1)]?.name || 'dashboard' })
|
|
}
|
|
|
|
function chooseRole(role) {
|
|
setRole(role)
|
|
router.replace({ name: 'dashboard' })
|
|
}
|
|
|
|
function handleLogout() {
|
|
logout()
|
|
tabs.value = [{ name: 'dashboard', title: '工作台' }]
|
|
router.replace({ name: 'login' })
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="app-shell">
|
|
<aside class="sidebar">
|
|
<div class="brand"><div class="brand-mark"><span></span><span></span><span></span></div><div><b>reelshort</b><small>STUDIO CONSOLE</small></div></div>
|
|
<el-menu :default-active="String(route.name)" :default-openeds="['content','system']" class="side-menu" router>
|
|
<el-menu-item index="dashboard" :route="{name:'dashboard'}"><el-icon><House /></el-icon><span>工作台</span></el-menu-item>
|
|
<el-sub-menu index="content">
|
|
<template #title><el-icon><VideoCamera /></el-icon><span>内容管理</span></template>
|
|
<el-menu-item v-if="can('查看短剧列表')" index="dramas" :route="{name:'dramas'}">短剧管理</el-menu-item>
|
|
<el-menu-item v-if="can('查看剧集列表')" index="episodes" :route="{name:'episodes'}">剧集管理</el-menu-item>
|
|
</el-sub-menu>
|
|
<el-sub-menu v-if="can('角色与权限配置') || can('管理员用户列表') || can('菜单管理') || can('角色分类管理')" index="system">
|
|
<template #title><el-icon><Setting /></el-icon><span>后台管理</span></template>
|
|
<el-menu-item v-if="can('角色与权限配置')" index="access" :route="{name:'access'}">角色权限配置</el-menu-item>
|
|
<el-menu-item v-if="can('管理员用户列表')" index="admin-users" :route="{name:'admin-users'}">管理员用户列表</el-menu-item>
|
|
<el-menu-item v-if="can('菜单管理')" index="menus" :route="{name:'menus'}">菜单管理</el-menu-item>
|
|
<el-menu-item v-if="can('角色分类管理')" index="role-categories" :route="{name:'role-categories'}">角色分类</el-menu-item>
|
|
<el-menu-item v-if="can('角色与权限配置')" index="settings" :route="{name:'settings'}">系统设置</el-menu-item>
|
|
</el-sub-menu>
|
|
<el-sub-menu v-if="customMenus.some(menu => can(menu.permission))" index="custom-menus">
|
|
<template #title><el-icon><Document /></el-icon><span>自定义菜单</span></template>
|
|
<el-menu-item v-for="menu in customMenus.filter(item => item.type !== '按钮' && can(item.permission))" :key="menu.key" :index="`custom-menu-${menu.key}`" :route="{name:`custom-menu-${menu.key}`}"><el-icon><component :is="customIconMap[menu.icon] || Document" /></el-icon>{{menu.title}}</el-menu-item>
|
|
</el-sub-menu>
|
|
</el-menu>
|
|
</aside>
|
|
<main class="main-area">
|
|
<header class="topbar"><div class="breadcrumb"><span>ReelShort Studio</span><span class="slash">/</span><b>{{route.meta.title || '工作台'}}</b></div><div class="top-actions"><span class="current-user">林小满 · {{currentRole}}</span><el-dropdown v-if="currentRole==='超级管理员'" trigger="click" @command="chooseRole"><el-button text class="role-switch">切换角色<el-icon><ArrowDown /></el-icon></el-button><template #dropdown><el-dropdown-menu><el-dropdown-item v-for="role in roleData" :key="role.name" :command="role.name">{{role.name}}</el-dropdown-item></el-dropdown-menu></template></el-dropdown><el-tooltip :content="isDark?'切换浅色主题':'切换深色主题'"><el-button text class="theme-button" :aria-label="isDark?'切换浅色主题':'切换深色主题'" @click="toggleTheme"><el-icon><component :is="isDark ? Sunny : Moon" /></el-icon></el-button></el-tooltip><el-tooltip :content="isFullscreen?'退出全屏':'全屏显示'"><el-button text class="fullscreen-button" :aria-label="isFullscreen?'退出全屏':'全屏显示'" @click="toggleFullscreen"><el-icon><component :is="isFullscreen ? Close : FullScreen" /></el-icon></el-button></el-tooltip><el-button text class="logout-button" @click="handleLogout"><el-icon><SwitchButton /></el-icon>退出登录</el-button></div></header>
|
|
<el-tabs v-model="activeTab" type="card" class="page-tabs" @tab-remove="closeTab">
|
|
<el-tab-pane v-for="tab in tabs" :key="tab.name" :name="tab.name" :label="tab.title" :closable="tab.name!=='dashboard'" />
|
|
</el-tabs>
|
|
<section class="page-content"><RouterView /></section>
|
|
</main>
|
|
</div>
|
|
</template>
|