Files
urbanLifeline/urbanLifelineWeb/packages/workcase/src/layouts/SidebarLayout/SidebarLayout.vue
2025-12-13 15:56:12 +08:00

268 lines
7.9 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">
<img src="/logo.jpg" alt="Logo" class="logo-img" />
<span v-if="!collapsed" class="logo-text">城市生命线</span>
</div>
<div class="collapse-btn" @click="toggleSidebar">
<el-icon>
<DArrowLeft v-if="!collapsed" />
<DArrowRight v-else />
</el-icon>
</div>
</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>
</aside>
<!-- 主内容区 -->
<main class="main-content">
<!-- iframe 模式 -->
<div v-if="currentIframeUrl" class="iframe-container">
<div class="iframe-header">
<span class="iframe-title">{{ currentMenuItem?.label }}</span>
<el-button
text
@click="handleRefreshIframe"
:icon="Refresh"
>
刷新
</el-button>
</div>
<iframe
ref="iframeRef"
:src="currentIframeUrl"
class="content-iframe"
frameborder="0"
@load="handleIframeLoad"
/>
<div v-if="iframeLoading" class="iframe-loading">
<el-icon class="is-loading"><Loading /></el-icon>
<span>加载中...</span>
</div>
</div>
<!-- 路由模式 -->
<router-view v-else />
</main>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import {
ChatDotRound,
Grid,
Connection,
Document,
Service,
DArrowLeft,
DArrowRight,
User,
Setting,
SwitchButton,
Refresh,
Loading
} from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
interface MenuItem {
key: string
label: string
icon: string
url?: string
type: 'route' | 'iframe'
}
const router = useRouter()
const route = useRoute()
// 状态管理
const collapsed = ref(false)
const activeMenu = ref('home')
const iframeLoading = ref(false)
const iframeRef = ref<HTMLIFrameElement>()
// 从 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) {
console.error('❌ 获取用户名失败:', error)
}
return '管理员'
}
const userName = ref(getUserName())
/**
* 从 LocalStorage 加载菜单
*/
function loadMenuFromStorage(): MenuItem[] {
try {
const loginDomainStr = localStorage.getItem('loginDomain')
if (!loginDomainStr) {
console.warn('⚠️ 未找到 loginDomain')
return []
}
const loginDomain = JSON.parse(loginDomainStr)
const userViews = loginDomain.userViews || []
console.log('📋 加载用户视图:', userViews)
// 过滤出 SidebarLayout 的顶级菜单(没有 parentId且属于 workcase 服务且不是admin路由
const sidebarViews = userViews.filter((view: any) =>
view.layout === 'SidebarLayout' &&
!view.parentId &&
view.type === 1 && // type 1 是侧边栏菜单
view.service === 'workcase' && // 只显示 workcase 服务的视图
!view.url?.startsWith('/admin') // 排除 admin 路由(由 AdminSidebar 管理)
)
console.log('🔍 过滤后的 workcase 视图:', sidebarViews)
// 按 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'
}
})
console.log('✅ 侧边栏菜单:', menuItems)
return menuItems
} catch (error) {
console.error('❌ 加载菜单失败:', error)
return []
}
}
// 菜单配置(从 LocalStorage 加载)
const menuItems = ref<MenuItem[]>(loadMenuFromStorage())
// 当前菜单项
const currentMenuItem = computed(() => {
return menuItems.value.find(item => 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)
if (item.type === 'iframe') {
iframeLoading.value = true
}
}
}
// iframe 加载完成
const handleIframeLoad = () => {
iframeLoading.value = false
}
// 刷新 iframe
const handleRefreshIframe = () => {
if (iframeRef.value) {
iframeLoading.value = true
iframeRef.value.src = iframeRef.value.src
}
}
// 用户头像加载错误
const handleAvatarError = () => {
return true
}
// 用户操作
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("./SidebarLayout.scss");
</style>