first commit

This commit is contained in:
Vicky committed 2026-10-12 17:35:07 +08:00
commit db15eb3ee0
42 files changed
+4620

No files matched your search

+94
View File
@@ -0,0 +1,94 @@
<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>