Files
urbanLifeline/urbanLifelineWeb/packages/platform/src/layouts/AdminSidebarLayout/AdminSidebarLayout.vue
2025-12-20 17:12:42 +08:00

255 lines
8.0 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>
<div class="sidebar-layout">
<!-- 侧边栏 -->
<aside class="sidebar" :class="{ collapsed: collapsed }">
<div class="sidebar-header">
<div class="logo" v-if="!collapsed">
<img src="/logo.jpg" alt="Logo" class="logo-img" />
<!-- <span v-if="!collapsed" class="logo-text">城市生命线</span> -->
</div>
<button
class="collapse-btn"
@click="toggleSidebar"
:title="collapsed ? '展开侧边栏' : '收起侧边栏'"
>
<!-- 收起图标 PanelLeftClose -->
<PanelLeftClose v-if="!collapsed"/>
<!-- 展开图标 PanelLeftOpen -->
<img v-else src="/logo.jpg" alt="Logo" class="logo-img" />
</button>
</div>
<nav class="nav-menu">
<div class="nav-section">
<div
v-for="item in menuItems"
:key="item.key"
class="nav-item"
:class="{ active: activeMenu === item.key }"
@click="handleMenuClick(item)"
>
<el-icon><component :is="item.icon" /></el-icon>
<span v-if="!collapsed">{{ item.label }}</span>
</div>
</div>
</nav>
<!-- 用户信息和返回按钮 -->
<div class="user-section">
<div class="user-avatar">
<el-avatar :size="36" src="/avatar.svg" @error="handleAvatarError" />
</div>
<span v-if="!collapsed" class="user-name">{{ userName }}</span>
<el-tooltip content="返回主系统" placement="top">
<div class="back-icon" @click="backToMain">
<el-icon><Back /></el-icon>
</div>
</el-tooltip>
</div>
</aside>
<!-- 主内容区 -->
<main class="main-content">
<!-- iframe 模式 -->
<IframeView
v-if="currentIframeUrl"
:url="currentIframeUrl"
:title="currentMenuItem?.label"
:show-header="false"
/>
<!-- 路由模式 -->
<router-view v-else />
</main>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import {
MessageCircle as ChatDotRound,
LayoutGrid as Grid,
Link as Connection,
FileText as Document,
Headphones as Service,
ChevronsLeft as DArrowLeft,
ChevronsRight as DArrowRight,
User,
Settings as Setting,
Power as SwitchButton,
RefreshCw as Refresh,
ArrowLeft as Back,
PanelLeftClose,
PanelLeftOpen
} from 'lucide-vue-next'
import { IframeView } from 'shared/components'
import { ElMessage } from 'element-plus'
import type { MenuItem } from 'shared/types'
const router = useRouter()
const route = useRoute()
// 状态管理
const collapsed = ref(false)
const activeMenu = ref('home')
// 从 LocalStorage 获取用户名
function getUserName(): string {
try {
const loginDomainStr = localStorage.getItem('loginDomain')
if (loginDomainStr) {
const loginDomain = JSON.parse(loginDomainStr)
return loginDomain.user?.username || loginDomain.userInfo?.username || '管理员'
}
} catch (error) {
}
return '管理员'
}
const userName = ref(getUserName())
/**
* 从 LocalStorage 加载菜单
*/
function loadMenuFromStorage(): MenuItem[] {
try {
const loginDomainStr = localStorage.getItem('loginDomain')
if (!loginDomainStr) {
return []
}
const loginDomain = JSON.parse(loginDomainStr)
const userViews = loginDomain.userViews || []
// 过滤出 AdminSidebarLayout 的菜单3个 iframe 管理后台入口)
const sidebarViews = userViews.filter((view: any) =>
view.layout === 'AdminSidebarLayout' && // AdminSidebarLayout 布局
view.viewType === 'iframe' && // iframe 类型
view.type === 1 && // type 1 是侧边栏菜单
view.service === 'platform' // platform 服务
)
// 按 orderNum 排序
sidebarViews.sort((a: any, b: any) => (a.orderNum || 0) - (b.orderNum || 0))
// 转换为 MenuItem 格式
const menuItems: MenuItem[] = sidebarViews.map((view: any) => {
// 根据 viewType 或 iframeUrl 判断是 route 还是 iframe
const isIframe = view.viewType === 'iframe' || !!view.iframeUrl
// 确定菜单的路由路径
let menuUrl = view.url
if (isIframe && view.url && (view.url.startsWith('http://') || view.url.startsWith('https://'))) {
// iframe 类型且 url 是外部链接,使用 viewId 作为路由路径
menuUrl = `/${view.viewId}`
}
return {
key: view.viewId || view.name,
label: view.name,
icon: view.icon || 'Grid',
url: menuUrl,
type: isIframe ? 'iframe' : 'route'
}
})
return menuItems
} catch (error) {
return []
}
}
// 菜单配置(从 LocalStorage 加载)
const menuItems = ref<MenuItem[]>(loadMenuFromStorage())
// 当前菜单项
const currentMenuItem = computed(() => {
return menuItems.value.find((item: MenuItem) => item.key === activeMenu.value)
})
// 当前 iframe URL从路由 meta 读取)
const currentIframeUrl = computed(() => {
const meta = route.meta as any
return meta?.iframeUrl || null
})
// 切换侧边栏
const toggleSidebar = () => {
collapsed.value = !collapsed.value
}
// 处理菜单点击
const handleMenuClick = (item: MenuItem) => {
activeMenu.value = item.key
// 所有菜单都通过路由跳转
if (item.url) {
router.push(item.url)
}
}
// 用户头像加载错误
const handleAvatarError = () => {
return true
}
// 返回主系统SidebarLayout
const backToMain = () => {
// 查找第一个非admin路由
const loginDomainStr = localStorage.getItem('loginDomain')
if (loginDomainStr) {
const loginDomain = JSON.parse(loginDomainStr)
const userViews = loginDomain.userViews || []
const mainViews = userViews.filter((view: any) =>
view.service === 'platform' && !view.url?.startsWith('/admin')
)
if (mainViews.length > 0) {
// 按 orderNum 排序,跳转到第一个
mainViews.sort((a: any, b: any) => (a.orderNum || 0) - (b.orderNum || 0))
router.push(mainViews[0].url)
}
}
}
// 用户操作
const handleUserCommand = (command: string) => {
switch (command) {
case 'profile':
router.push('/profile')
break
case 'settings':
router.push('/settings')
break
case 'logout':
localStorage.clear()
ElMessage.success('退出成功')
router.push('/login')
break
}
}
// 监听路由变化,同步激活菜单
watch(
() => route.path,
(newPath) => {
// 查找匹配的菜单项route 或 iframe 类型)
const menuItem = menuItems.value.find((item: MenuItem) => item.url === newPath)
if (menuItem) {
activeMenu.value = menuItem.key
} else {
// 如果路径不匹配,尝试通过 route.name 匹配 viewId
const menuByName = menuItems.value.find((item: MenuItem) => item.key === route.name)
if (menuByName) {
activeMenu.value = menuByName.key
}
}
},
{ immediate: true }
)
</script>
<style lang="scss" scoped>
@import url("./AdminSidebarLayout.scss");
</style>