306 lines
7.2 KiB
Plaintext
306 lines
7.2 KiB
Plaintext
<template>
|
|
<view class="layout-root">
|
|
<!-- 移动端遮罩层 -->
|
|
<view
|
|
v-if="isMobile && isMobileMenuOpen"
|
|
class="mobile-mask"
|
|
@click="isMobileMenuOpen = false"
|
|
></view>
|
|
|
|
<!-- 主侧边栏 (CRMEB风格) -->
|
|
<AdminAside
|
|
class="admin-sidebar"
|
|
:class="{ 'mobile-aside-open': isMobileMenuOpen }"
|
|
:collapsed="isMainAsideCollapsed"
|
|
:topMenus="topMenus"
|
|
:activeTopMenuId="activeTopMenuId"
|
|
@toggle="toggleMainAsideCollapse"
|
|
@menu-click="onTopMenuClick"
|
|
:asideWidth="ASIDE_W"
|
|
/>
|
|
|
|
<!-- 二级侧边栏 (CRMEB风格 - 内容区左侧) -->
|
|
<AdminSubSider
|
|
v-if="showSubSider && !isMobile"
|
|
:topMenuTitle="activeTopMenuTitle"
|
|
:groups="activeGroups"
|
|
:routes="activeRoutes"
|
|
:activeRouteId="activeRouteId"
|
|
:asideWidth="ASIDE_W"
|
|
:siderWidth="SUB_W"
|
|
@route-click="onRouteClick"
|
|
/>
|
|
|
|
<!-- 右侧内容区 -->
|
|
<view
|
|
class="main"
|
|
:style="{ marginLeft: isMobile ? '0' : mainLeft }"
|
|
>
|
|
<!-- 顶部导航栏 -->
|
|
<AdminHeader
|
|
:breadcrumb="breadcrumb"
|
|
:hasNotification="hasNotification"
|
|
:isMobile="isMobile"
|
|
@toggle-mobile-menu="isMobileMenuOpen = !isMobileMenuOpen"
|
|
@search="onSearch"
|
|
@refresh="onRefresh"
|
|
@notify="onNotify"
|
|
/>
|
|
|
|
<!-- 标签页 (CRMEB风格) - 移动端可以隐藏或滚动 -->
|
|
<AdminTagsView
|
|
v-if="!isMobile"
|
|
:tabs="tabs"
|
|
:activeTabId="activeRouteId"
|
|
@tab-click="onTabClick"
|
|
@tab-close="onTabClose"
|
|
@close-other="onCloseOther"
|
|
@close-all="onCloseAll"
|
|
/>
|
|
|
|
<!-- 内容展示区 (内部路由渲染) -->
|
|
<view class="content-scroll">
|
|
<view class="content-inner" :style="{ padding: isMobile ? '12px' : '16px' }">
|
|
<component :is="currentComponent" />
|
|
</view>
|
|
<AdminFooter />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import AdminAside from '@/layouts/admin/components/AdminAside.uvue'
|
|
import AdminSubSider from '@/layouts/admin/components/AdminSubSider.uvue'
|
|
import AdminHeader from '@/layouts/admin/components/AdminHeader.uvue'
|
|
import AdminTagsView from '@/layouts/admin/components/AdminTagsView.uvue'
|
|
import AdminFooter from '@/layouts/admin/components/AdminFooter.uvue'
|
|
|
|
import {
|
|
getTopMenus,
|
|
getGroupsByTopMenu,
|
|
getRoutesByGroup,
|
|
findRouteById,
|
|
getBreadcrumb
|
|
} from '@/layouts/admin/router/adminRoutes.uts'
|
|
import type { TopMenu, MenuGroup, RouteRecord } from '@/layouts/admin/router/adminRoutes.uts'
|
|
|
|
import {
|
|
activeTopMenuId,
|
|
activeRouteId,
|
|
tabs,
|
|
isMainAsideCollapsed,
|
|
showSubSider,
|
|
windowWidth,
|
|
isMobile,
|
|
isMobileMenuOpen,
|
|
openRoute,
|
|
closeTab,
|
|
closeOtherTabs,
|
|
closeAllTabs,
|
|
toggleMainAsideCollapse as storeToggleCollapse,
|
|
initNavState
|
|
} from '@/layouts/admin/store/adminNavStore.uts'
|
|
import type { TabItem } from '@/layouts/admin/store/adminNavStore.uts'
|
|
|
|
import { getComponent } from '@/layouts/admin/router/adminComponentMap.uts'
|
|
|
|
// 侧边栏宽度配置
|
|
const ASIDE_W = 96 // 主侧边栏宽度
|
|
const SUB_W = 180 // 二级侧边栏宽度
|
|
|
|
const hasNotification = ref<boolean>(false)
|
|
|
|
// 计算主内容区左边距
|
|
const mainLeft = computed<string>(() => {
|
|
const asideWidth = isMainAsideCollapsed.value ? 0 : ASIDE_W
|
|
const subWidth = showSubSider.value ? SUB_W : 0
|
|
return (asideWidth + subWidth) + 'px'
|
|
})
|
|
|
|
// 获取一级菜单列表
|
|
const topMenus = computed<TopMenu[]>(() => {
|
|
return getTopMenus()
|
|
})
|
|
|
|
// 当前选中一级菜单的标题
|
|
const activeTopMenuTitle = computed<string>(() => {
|
|
const menu = topMenus.value.find(m => m.id === activeTopMenuId.value)
|
|
return menu ? menu.title : ''
|
|
})
|
|
|
|
// 当前一级菜单的分组列表
|
|
const activeGroups = computed<MenuGroup[]>(() => {
|
|
return getGroupsByTopMenu(activeTopMenuId.value)
|
|
})
|
|
|
|
// 当前一级菜单的所有路由
|
|
const activeRoutes = computed<Map<string, RouteRecord[]>>(() => {
|
|
const result = new Map<string, RouteRecord[]>()
|
|
activeGroups.value.forEach(group => {
|
|
result.set(group.id, getRoutesByGroup(group.id))
|
|
})
|
|
return result
|
|
})
|
|
|
|
// 面包屑导航
|
|
const breadcrumb = computed<Array<{id: string, title: string}>>(() => {
|
|
return getBreadcrumb(activeRouteId.value)
|
|
})
|
|
|
|
// 当前渲染的组件
|
|
const currentComponent = computed<any>(() => {
|
|
const route = findRouteById(activeRouteId.value)
|
|
if (!route) return null
|
|
return getComponent(route.componentKey)
|
|
})
|
|
|
|
// ============================================
|
|
// 事件处理
|
|
// ============================================
|
|
|
|
function onTopMenuClick(menu: TopMenu): void {
|
|
activeTopMenuId.value = menu.id
|
|
if (menu.groups.length === 0) {
|
|
openRoute(menu.id + '_index')
|
|
}
|
|
}
|
|
|
|
function onRouteClick(routeId: string): void {
|
|
openRoute(routeId)
|
|
}
|
|
|
|
function onTabClick(tab: TabItem): void {
|
|
openRoute(tab.id, false)
|
|
}
|
|
|
|
function onTabClose(tabId: string): void {
|
|
closeTab(tabId)
|
|
}
|
|
|
|
function onCloseOther(tabId: string): void {
|
|
closeOtherTabs(tabId)
|
|
}
|
|
|
|
function onCloseAll(): void {
|
|
closeAllTabs()
|
|
}
|
|
|
|
function toggleMainAsideCollapse(): void {
|
|
storeToggleCollapse()
|
|
}
|
|
|
|
function onSearch(): void {
|
|
uni.showToast({ title: '搜索', icon: 'none' })
|
|
}
|
|
|
|
function onRefresh(): void {
|
|
uni.showToast({ title: '刷新', icon: 'none' })
|
|
}
|
|
|
|
function onNotify(): void {
|
|
uni.showToast({ title: '通知', icon: 'none' })
|
|
}
|
|
|
|
// ============================================
|
|
// 生命周期
|
|
// ============================================
|
|
|
|
onMounted(() => {
|
|
initNavState()
|
|
|
|
// 初始化窗口宽度
|
|
windowWidth.value = uni.getWindowInfo().windowWidth
|
|
|
|
// 监听窗口变化
|
|
uni.onWindowResize((res) => {
|
|
windowWidth.value = res.size.windowWidth
|
|
// 窗口变大时自动关闭移动端菜单
|
|
if (windowWidth.value >= 768) {
|
|
isMobileMenuOpen.value = false
|
|
}
|
|
})
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.layout-root {
|
|
display: flex;
|
|
flex-direction: row;
|
|
width: 100%;
|
|
min-height: 100vh;
|
|
background: #f0f2f5;
|
|
position: relative;
|
|
}
|
|
|
|
/* 移动端侧边栏样式 */
|
|
.mobile-aside {
|
|
position: absolute;
|
|
left: -100px; /* 隐藏在左侧 */
|
|
top: 0;
|
|
bottom: 0;
|
|
z-index: 1001;
|
|
transition: transform 0.3s ease;
|
|
background: #fff;
|
|
}
|
|
|
|
.mobile-aside-open {
|
|
transform: translateX(100px); /* 移入视图 */
|
|
}
|
|
|
|
.mobile-mask {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
z-index: 1000;
|
|
}
|
|
|
|
.main {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
transition: margin-left 0.3s ease;
|
|
background: #f0f2f5;
|
|
width: 100%;
|
|
}
|
|
|
|
/* 响应式强制覆盖 */
|
|
@media screen and (max-width: 768px) {
|
|
.main {
|
|
margin-left: 0 !important;
|
|
}
|
|
|
|
/* 强行改变侧边栏布局模式 */
|
|
.admin-sidebar {
|
|
position: absolute !important;
|
|
left: -100px !important; /* 隐藏在左侧,假设 ASIDE_W 是 96 */
|
|
top: 0;
|
|
bottom: 0;
|
|
z-index: 1001;
|
|
transition: transform 0.3s ease !important;
|
|
}
|
|
|
|
/* 展开时的状态 */
|
|
.mobile-aside-open {
|
|
transform: translateX(100px) !important;
|
|
}
|
|
}
|
|
|
|
.content-scroll {
|
|
flex: 1;
|
|
overflow-y: scroll;
|
|
overflow-x: auto; /* 允许横向滚动,兼容极端窄屏 */
|
|
background: #f0f2f5;
|
|
}
|
|
|
|
.content-inner {
|
|
min-height: calc(100vh - 120px);
|
|
padding: 16px;
|
|
}
|
|
</style>
|