2级sidebar
This commit is contained in:
@@ -149,33 +149,60 @@
|
||||
|
||||
// 用户信息
|
||||
.user-section {
|
||||
padding: 16px 20px;
|
||||
padding: 16px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(124, 58, 237, 0.05);
|
||||
}
|
||||
|
||||
.user-info-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.user-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
:deep(.el-avatar) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.user-name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
color: #7c3aed;
|
||||
background: rgba(124, 58, 237, 0.1);
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 主内容区 ====================
|
||||
@@ -143,13 +143,12 @@ function loadMenuFromStorage(): MenuItem[] {
|
||||
const userViews = loginDomain.userViews || []
|
||||
|
||||
|
||||
// 过滤出 SidebarLayout 的顶级菜单(没有 parentId,且属于 platform 服务,且不是admin路由)
|
||||
// 过滤出 AdminIframeSidebarLayout 的菜单(3个 iframe 管理后台入口)
|
||||
const sidebarViews = userViews.filter((view: any) =>
|
||||
view.layout === 'SidebarLayout' &&
|
||||
!view.parentId &&
|
||||
view.layout === 'AdminIframeSidebarLayout' && // AdminIframeSidebarLayout 布局
|
||||
view.viewType === 'iframe' && // iframe 类型
|
||||
view.type === 1 && // type 1 是侧边栏菜单
|
||||
view.service === 'platform' && // 只显示 platform 服务的视图
|
||||
view.url?.startsWith('/admin') // 只留admin 路由(由 AdminSidebar 管理)
|
||||
view.service === 'platform' // platform 服务
|
||||
)
|
||||
|
||||
// 按 orderNum 排序
|
||||
@@ -288,5 +287,5 @@ watch(
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import url("./SidebarLayout.scss");
|
||||
@import url("./AdminIframeSidebarLayout.scss");
|
||||
</style>
|
||||
@@ -165,7 +165,7 @@ function loadMenuFromStorage(): MenuItem[] {
|
||||
!view.url?.startsWith('/admin') // 排除 admin 路由(由 AdminSidebar 管理)
|
||||
)
|
||||
hasAdmin.value = userViews.filter((view: any) =>
|
||||
view.layout === 'SidebarLayout' &&
|
||||
view.layout === 'AdminIframeSidebarLayout' &&
|
||||
!view.parentId &&
|
||||
view.type === 1 && // type 1 是侧边栏菜单
|
||||
view.service === 'platform' && // 只显示 platform 服务的视图
|
||||
@@ -266,7 +266,7 @@ const handleUserCommand = (command: string) => {
|
||||
const loginDomain = JSON.parse(loginDomainStr)
|
||||
const userViews = loginDomain.userViews || []
|
||||
const adminViews = userViews.filter((view: any) =>
|
||||
view.service === 'platform' && view.url?.startsWith('/admin')
|
||||
view.service === 'platform' && view.url?.startsWith('/admin') && view.viewType === 'iframe'
|
||||
)
|
||||
if (adminViews.length > 0) {
|
||||
// 按 orderNum 排序,跳转到第一个
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as SidebarLayout } from "./SidebarLayout/SidebarLayout.vue";
|
||||
export { default as AdminIframeSidebarLayout } from "./AdminIframeSidebarLayout/AdminIframeSidebarLayout.vue";
|
||||
// BlankLayout从shared导入
|
||||
export { BlankLayout } from 'shared/layouts';
|
||||
export { BlankLayout, AdminSidebarLayout } from 'shared/layouts';
|
||||
@@ -18,14 +18,15 @@ import {
|
||||
import type { TbSysViewDTO } from 'shared/types'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import router from './index'
|
||||
import { SidebarLayout, BlankLayout } from '@/layouts'
|
||||
import { SidebarLayout, BlankLayout, AdminIframeSidebarLayout } from '@/layouts'
|
||||
|
||||
// Platform 布局组件映射
|
||||
const platformLayoutMap: Record<string, () => Promise<any>> = {
|
||||
'SidebarLayout': () => Promise.resolve({ default: SidebarLayout }),
|
||||
'BlankLayout': () => Promise.resolve({ default: BlankLayout }),
|
||||
'NavigationLayout': () => Promise.resolve({ default: SidebarLayout }),
|
||||
'BasicLayout': () => Promise.resolve({ default: SidebarLayout })
|
||||
'BasicLayout': () => Promise.resolve({ default: SidebarLayout }),
|
||||
'AdminIframeSidebarLayout': () => Promise.resolve({ default: AdminIframeSidebarLayout })
|
||||
}
|
||||
|
||||
// 视图组件加载器
|
||||
|
||||
@@ -54,9 +54,34 @@ router.beforeEach((to, from, next) => {
|
||||
dynamicRoutesLoaded = true
|
||||
const loaded = loadRoutesFromStorage()
|
||||
|
||||
if (loaded && to.path !== '/') {
|
||||
// 动态路由已加载,重新导航到目标路由
|
||||
next({ ...to, replace: true })
|
||||
if (loaded) {
|
||||
if (to.path === '/') {
|
||||
// 访问根路径,重定向到第一个可用路由
|
||||
const firstRoute = getFirstAvailableRoute()
|
||||
if (firstRoute && firstRoute !== '/') {
|
||||
// 只有当第一个路由不是 / 时才重定向,避免无限循环
|
||||
console.log('[Platform Router] 根路径重定向到:', firstRoute)
|
||||
next({ path: firstRoute, replace: true })
|
||||
return
|
||||
} else {
|
||||
// 第一个路由就是 /,直接放行
|
||||
console.log('[Platform Router] 第一个路由就是根路径,直接放行')
|
||||
}
|
||||
} else {
|
||||
// 动态路由已加载,重新导航到目标路由
|
||||
next({ ...to, replace: true })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已登录且访问根路径,但动态路由已加载,重定向到第一个可用路由
|
||||
if (hasToken && to.path === '/' && dynamicRoutesLoaded) {
|
||||
const firstRoute = getFirstAvailableRoute()
|
||||
if (firstRoute && firstRoute !== '/') {
|
||||
// 只有当第一个路由不是 / 时才重定向,避免无限循环
|
||||
console.log('[Platform Router] 已登录访问根路径,重定向到:', firstRoute)
|
||||
next({ path: firstRoute, replace: true })
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -64,6 +89,52 @@ router.beforeEach((to, from, next) => {
|
||||
next()
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取第一个可用的路由路径
|
||||
*/
|
||||
function getFirstAvailableRoute(): string | null {
|
||||
try {
|
||||
console.log('[Platform Router] 开始获取第一个可用路由...')
|
||||
|
||||
const loginDomainStr = localStorage.getItem('loginDomain')
|
||||
if (!loginDomainStr) {
|
||||
console.warn('[Platform Router] localStorage 中没有 loginDomain')
|
||||
return null
|
||||
}
|
||||
|
||||
const loginDomain = JSON.parse(loginDomainStr)
|
||||
const userViews = loginDomain.userViews || []
|
||||
|
||||
console.log('[Platform Router] 所有用户视图:', userViews.length)
|
||||
|
||||
// 过滤出 platform 服务的非 admin 视图
|
||||
// 注意:不限制 type,因为首页路由可能是 type=3(路由类型)而不是 type=1(菜单类型)
|
||||
const platformViews = userViews.filter((view: any) =>
|
||||
view.service === 'platform' &&
|
||||
!view.url?.startsWith('/admin') &&
|
||||
view.url // 必须有 url 字段
|
||||
)
|
||||
|
||||
console.log('[Platform Router] Platform 服务视图:', platformViews)
|
||||
|
||||
if (platformViews.length === 0) {
|
||||
console.warn('[Platform Router] 没有找到 platform 服务的视图')
|
||||
return null
|
||||
}
|
||||
|
||||
// 按 orderNum 排序
|
||||
platformViews.sort((a: any, b: any) => (a.orderNum || 0) - (b.orderNum || 0))
|
||||
|
||||
const firstRoute = platformViews[0].url
|
||||
console.log('[Platform Router] 第一个路由:', firstRoute, '视图:', platformViews[0].name)
|
||||
|
||||
return firstRoute
|
||||
} catch (error) {
|
||||
console.error('[Platform Router] 获取首页路由失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 导出动态路由生成函数
|
||||
export { addDynamicRoutes, loadRoutesFromStorage } from './dynamicRoute'
|
||||
|
||||
|
||||
@@ -181,4 +181,6 @@ declare module 'shared/layouts' {
|
||||
import { DefineComponent } from 'vue'
|
||||
|
||||
export const BlankLayout: DefineComponent<{}, {}, any>
|
||||
export const AdminSidebarLayout: DefineComponent<{}, {}, any>
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="overview">
|
||||
<!-- 数据概览视图 -->
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import url("./OverviewView.scss");
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="user-management">
|
||||
<!-- 用户管理视图 -->
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import url("./UserManagementView.scss");
|
||||
</style>
|
||||
@@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<div class="iframe-view">
|
||||
<iframe
|
||||
v-if="iframeUrl"
|
||||
:src="iframeUrl"
|
||||
class="iframe-content"
|
||||
frameborder="0"
|
||||
@load="handleLoad"
|
||||
/>
|
||||
<div v-else class="iframe-error">
|
||||
<el-icon class="error-icon"><WarningFilled /></el-icon>
|
||||
<p>无效的 iframe 地址</p>
|
||||
</div>
|
||||
<div v-if="loading" class="iframe-loading">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { Loading, WarningFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(true)
|
||||
|
||||
// 从路由 meta 中获取 iframe URL
|
||||
const iframeUrl = computed(() => {
|
||||
return route.meta.iframeUrl as string || ''
|
||||
})
|
||||
|
||||
function handleLoad() {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('[IframeView] 加载 iframe:', iframeUrl.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.iframe-view {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.iframe-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.iframe-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
.error-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
}
|
||||
|
||||
.iframe-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--el-bg-color);
|
||||
gap: 12px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user