Files
video/admin/src/layout/MainLayout.vue
T
2026-10-15 20:36:59 +08:00

187 lines
5.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-container class="layout">
<!-- 侧边菜单:按当前管理员角色过滤 -->
<el-aside width="220px" class="aside">
<div class="logo">短视频管理后台</div>
<el-menu :default-active="activeMenu" router background-color="#1f2d3d" text-color="#c0c4cc"
active-text-color="#409eff" unique-opened>
<template v-for="item in menus" :key="item.path || item.title">
<!-- 单项菜单 -->
<el-menu-item v-if="!item.children" :index="item.path">
<el-icon><component :is="item.icon" /></el-icon>
<span>{{ item.title }}</span>
</el-menu-item>
<!-- 分组菜单 -->
<el-sub-menu v-else :index="item.title">
<template #title>
<el-icon><component :is="item.icon" /></el-icon>
<span>{{ item.title }}</span>
</template>
<el-menu-item v-for="child in item.children" :key="child.path" :index="child.path">
<el-icon><component :is="child.icon" /></el-icon>
<span>{{ child.title }}</span>
</el-menu-item>
</el-sub-menu>
</template>
</el-menu>
</el-aside>
<el-container>
<el-header class="header">
<div class="header-title">{{ currentTitle }}</div>
<div class="header-right">
<el-tag size="small" type="info">{{ auth.roleText }}</el-tag>
<span class="admin-name">{{ auth.adminName }}</span>
<el-button link type="primary" @click="handleLogout">退出登录</el-button>
</div>
</el-header>
<el-main class="main">
<router-view v-slot="{ Component }">
<keep-alive :max="5">
<component :is="Component" />
</keep-alive>
</router-view>
</el-main>
</el-container>
</el-container>
</template>
<script setup>
import { ElMessage, ElMessageBox } from 'element-plus'
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '@/store/auth'
import { canAccess } from '@/router'
const auth = useAuthStore()
const route = useRoute()
const router = useRouter()
// 菜单定义:roles 为空数组表示仅 super 可见(与路由 meta.roles 保持一致)
const menuConfig = [
{ title: '仪表盘', path: '/dashboard', icon: 'DataLine', roles: ['operator'] },
{
title: '内容管理',
icon: 'VideoCamera',
children: [
{ title: '视频审核', path: '/videos/pending', icon: 'VideoPlay', roles: ['reviewer'] },
{ title: '视频管理', path: '/videos', icon: 'Film', roles: [] },
{ title: '评论管理', path: '/comments', icon: 'ChatDotRound', roles: [] },
{ title: '举报处理', path: '/reports', icon: 'Warning', roles: [] },
],
},
{
title: '用户管理',
icon: 'User',
children: [
{ title: '用户列表', path: '/users', icon: 'User', roles: [] },
{ title: '积分流水', path: '/points/records', icon: 'Coin', roles: [] },
],
},
{
title: '交易管理',
icon: 'Money',
children: [
{ title: '充值订单', path: '/orders', icon: 'List', roles: [] },
{ title: '充值套餐', path: '/packages', icon: 'Goods', roles: ['operator'] },
{ title: '提现审核', path: '/withdrawals', icon: 'CreditCard', roles: [] },
{ title: '赠送管理', path: '/gift', icon: 'Present', roles: ['operator'] },
],
},
{
title: '运营管理',
icon: 'Operation',
children: [
{ title: '推荐风控', path: '/referrals', icon: 'Share', roles: [] },
{ title: '敏感词库', path: '/sensitive-words', icon: 'Collection', roles: [] },
{ title: '平台配置', path: '/configs', icon: 'Setting', roles: [] },
{ title: '操作日志', path: '/logs', icon: 'Document', roles: [] },
],
},
]
// 按角色过滤菜单(分组内全部子项无权限时隐藏整组)
const menus = computed(() => {
const role = auth.role
return menuConfig
.map((item) => {
if (!item.children) {
return canAccess(role, item.roles) ? item : null
}
const children = item.children.filter((c) => canAccess(role, c.roles))
return children.length ? { ...item, children } : null
})
.filter(Boolean)
})
// 当前高亮菜单:详情页等隐藏路由回退到父级
const activeMenu = computed(() => {
if (route.name === 'user-detail') return '/users'
return route.path
})
const currentTitle = computed(() => route.meta.title || '')
async function handleLogout() {
await ElMessageBox.confirm('确认退出登录?', '提示', { type: 'warning' })
auth.logout()
ElMessage.success('已退出登录')
router.replace('/login')
}
</script>
<style scoped>
.layout {
height: 100vh;
}
.aside {
background-color: #1f2d3d;
overflow-x: hidden;
}
.logo {
height: 56px;
line-height: 56px;
text-align: center;
color: #fff;
font-size: 16px;
font-weight: 600;
background-color: #16222f;
}
.aside :deep(.el-menu) {
border-right: none;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
border-bottom: 1px solid #e4e7ed;
}
.header-title {
font-size: 16px;
font-weight: 600;
}
.header-right {
display: flex;
align-items: center;
gap: 12px;
}
.admin-name {
font-size: 14px;
}
.main {
padding: 0;
overflow-y: auto;
}
</style>