| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- <template>
- <div class="main-layout">
- <el-container>
- <!-- 头部 -->
- <el-header class="header">
- <div class="header-left">
- <h1>SSO 认证中心</h1>
- </div>
- <div class="header-right">
- <el-dropdown @command="handleCommand">
- <span class="user-info">
- <el-avatar :src="authStore.user?.avatar_url" :size="32">
- {{ authStore.user?.username?.charAt(0).toUpperCase() }}
- </el-avatar>
- <span class="username">{{ authStore.user?.username }}</span>
- <el-icon><ArrowDown /></el-icon>
- </span>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item command="profile">
- <el-icon><User /></el-icon>
- 个人资料
- </el-dropdown-item>
- <el-dropdown-item command="settings">
- <el-icon><Setting /></el-icon>
- 设置
- </el-dropdown-item>
- <el-dropdown-item divided command="logout">
- <el-icon><SwitchButton /></el-icon>
- 退出登录
- </el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </div>
- </el-header>
- <el-container>
- <!-- 侧边栏 -->
- <el-aside width="200px" class="sidebar">
- <el-menu
- :default-active="activeMenu"
- class="sidebar-menu"
- router
- v-loading="menuLoading"
- >
- <!-- 动态渲染菜单 -->
- <template v-for="menu in userMenus" :key="menu.id">
- <!-- 只显示菜单类型,过滤掉按钮类型 -->
- <template v-if="menu.menu_type === 'menu'">
- <!-- 有子菜单的情况(父级目录) -->
- <el-sub-menu v-if="hasMenuChildren(menu)" :index="menu.path || menu.name">
- <template #title>
- <el-icon v-if="menu.icon">
- <component :is="getIconComponent(menu.icon)" />
- </el-icon>
- <span>{{ menu.title }}</span>
- </template>
- <el-menu-item
- v-for="child in getMenuChildren(menu)"
- :key="child.id"
- :index="child.path || child.name"
- v-show="!child.is_hidden"
- >
- <el-icon v-if="child.icon">
- <component :is="getIconComponent(child.icon)" />
- </el-icon>
- <span>{{ child.title }}</span>
- </el-menu-item>
- </el-sub-menu>
-
- <!-- 没有子菜单的情况(具体菜单页面) -->
- <el-menu-item
- v-else
- :index="menu.path || menu.name"
- v-show="!menu.is_hidden"
- >
- <el-icon v-if="menu.icon">
- <component :is="getIconComponent(menu.icon)" />
- </el-icon>
- <span>{{ menu.title }}</span>
- </el-menu-item>
- </template>
- </template>
- </el-menu>
- </el-aside>
- <!-- 主内容区 -->
- <el-main class="main-content">
- <router-view />
- </el-main>
- </el-container>
- </el-container>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, ref, onMounted } from 'vue'
- import { useRouter, useRoute } from 'vue-router'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { useAuthStore } from '@/stores/auth'
- import request from '@/api/request'
- import * as ElementPlusIcons from '@element-plus/icons-vue'
- const router = useRouter()
- const route = useRoute()
- const authStore = useAuthStore()
- // 接口定义
- interface MenuItem {
- id: string | number
- name: string
- title: string
- path: string
- icon?: string
- menu_type?: string
- is_hidden?: boolean
- is_active?: boolean
- children?: MenuItem[]
- parent_id?: string | number | null
- }
- interface ApiResponse<T = any> {
- code: number
- message: string
- data: T
- timestamp: string
- }
- // 响应式数据
- const userMenus = ref<MenuItem[]>([])
- const menuLoading = ref(false)
- const activeMenu = computed(() => {
- // 处理子路由的菜单激活状态
- const path = route.path
- if (path.startsWith('/admin')) {
- return path
- }
- if (path.startsWith('/apps')) {
- return '/apps'
- }
- return path
- })
- // 获取图标组件
- const getIconComponent = (iconName: string) => {
- return (ElementPlusIcons as any)[iconName] || ElementPlusIcons.Menu
- }
- // 检查是否有菜单类型的子项
- const hasMenuChildren = (menu: MenuItem) => {
- return menu.children && menu.children.some((child: MenuItem) => child.menu_type === 'menu')
- }
- // 获取菜单类型的子项
- const getMenuChildren = (menu: MenuItem) => {
- return menu.children ? menu.children.filter((child: MenuItem) => child.menu_type === 'menu') : []
- }
- // 获取用户菜单
- const loadUserMenus = async () => {
- menuLoading.value = true
- try {
- const result = await request.get<any, ApiResponse<MenuItem[]>>('/api/v1/system/user/menus')
- if (result.code === 0) {
- userMenus.value = result.data
- console.log('用户菜单加载成功:', result.data)
- } else {
- throw new Error(result.message || '获取菜单失败')
- }
- } catch (error) {
- console.error('获取用户菜单失败:', error)
- ElMessage.error('获取菜单失败,请刷新页面重试')
-
- // 如果获取菜单失败,使用默认菜单
- userMenus.value = getDefaultMenus()
- } finally {
- menuLoading.value = false
- }
- }
- // 获取默认菜单(兜底方案)
- const getDefaultMenus = (): MenuItem[] => {
- const defaultMenus: MenuItem[] = [
- {
- id: 'dashboard',
- name: 'dashboard',
- title: '仪表盘',
- path: '/dashboard',
- icon: 'House',
- children: []
- },
- {
- id: 'profile',
- name: 'profile',
- title: '个人资料',
- path: '/profile',
- icon: 'User',
- children: []
- },
- {
- id: 'apps',
- name: 'apps',
- title: '我的应用',
- path: '/apps',
- icon: 'Grid',
- children: []
- }
- ]
-
- // 如果是管理员,添加系统管理菜单
- if (authStore.isAdmin) {
- // 管理员默认菜单中包含文档中心
- defaultMenus.push({
- id: 'documents',
- name: 'documents',
- title: '文档管理中心',
- path: '/admin/documents',
- icon: 'Document',
- children: [
- {
- id: 'kb-list',
- name: 'kb-list',
- title: '知识库管理',
- path: '/admin/documents/kb',
- icon: 'Collection',
- menu_type: 'menu',
- is_hidden: false
- },
- {
- id: 'kb-snippet',
- name: 'kb-snippet',
- title: '知识片段',
- path: '/admin/documents/snippet',
- icon: 'Tickets',
- menu_type: 'menu',
- is_hidden: false
- },
- {
- id: 'search-engine',
- name: 'search-engine',
- title: '检索引擎',
- path: '/admin/documents/search-engine',
- icon: 'Search',
- menu_type: 'menu',
- is_hidden: false
- }
- ]
- })
- defaultMenus.push({
- id: 'admin',
- name: 'admin',
- title: '系统管理',
- path: '/admin',
- icon: 'Setting',
- children: [
- {
- id: 'admin-dashboard',
- name: 'admin-dashboard',
- title: '管理概览',
- path: '/admin/dashboard',
- icon: 'Monitor',
- is_hidden: false
- },
- {
- id: 'admin-users',
- name: 'admin-users',
- title: '用户管理',
- path: '/admin/users',
- icon: 'UserFilled',
- is_hidden: false
- },
- {
- id: 'admin-roles',
- name: 'admin-roles',
- title: '角色管理',
- path: '/admin/roles',
- icon: 'Avatar',
- is_hidden: false
- },
- {
- id: 'admin-menus',
- name: 'admin-menus',
- title: '菜单管理',
- path: '/admin/menus',
- icon: 'Menu',
- is_hidden: false
- }
- ]
- })
- }
-
- return defaultMenus
- }
- // 处理下拉菜单命令
- const handleCommand = async (command: string) => {
- switch (command) {
- case 'profile':
- router.push('/profile')
- break
- case 'settings':
- router.push('/settings')
- break
- case 'logout':
- try {
- await ElMessageBox.confirm('确定要退出登录吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- })
-
- await authStore.logout()
- ElMessage.success('已退出登录')
- router.push('/login')
- } catch (error) {
- // 用户取消
- }
- break
- }
- }
- onMounted(() => {
- // 加载用户菜单
- loadUserMenus()
- })
- </script>
- <style scoped>
- .main-layout {
- height: 100vh;
- }
- .header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- background: #fff;
- border-bottom: 1px solid #e6e6e6;
- padding: 0 20px;
- }
- .header-left h1 {
- margin: 0;
- color: #333;
- font-size: 20px;
- font-weight: 600;
- }
- .header-right {
- display: flex;
- align-items: center;
- }
- .user-info {
- display: flex;
- align-items: center;
- gap: 8px;
- cursor: pointer;
- padding: 8px;
- border-radius: 4px;
- transition: background-color 0.3s;
- }
- .user-info:hover {
- background-color: #f5f5f5;
- }
- .username {
- font-size: 14px;
- color: #333;
- }
- .sidebar {
- background: #fff;
- border-right: 1px solid #e6e6e6;
- }
- .sidebar-menu {
- border-right: none;
- }
- .main-content {
- background: #f5f5f5;
- padding: 0;
- }
- </style>
|