Files
medical-mall/layouts/admin/AdminLayout.uvue
2026-02-02 21:45:59 +08:00

229 lines
5.4 KiB
Plaintext

<template>
<view class="layout-root">
<!-- 主侧边栏 (CRMEB风格) -->
<AdminAside
:collapsed="isMainAsideCollapsed"
:topMenus="topMenus"
:activeTopMenuId="activeTopMenuId"
@toggle="toggleMainAsideCollapse"
@menu-click="onTopMenuClick"
:asideWidth="ASIDE_W"
/>
<!-- 二级侧边栏 (CRMEB风格 - 内容区左侧) -->
<AdminSubSider
v-if="showSubSider"
:topMenuTitle="activeTopMenuTitle"
:groups="activeGroups"
:routes="activeRoutes"
:activeRouteId="activeRouteId"
:asideWidth="ASIDE_W"
:siderWidth="SUB_W"
@route-click="onRouteClick"
/>
<!-- 右侧内容区 -->
<view
class="main"
:style="{ marginLeft: mainLeft }"
>
<!-- 顶部导航栏 -->
<AdminHeader
:breadcrumb="breadcrumb"
:hasNotification="hasNotification"
@search="onSearch"
@refresh="onRefresh"
@notify="onNotify"
/>
<!-- 标签页 (CRMEB风格) -->
<AdminTagsView
:tabs="tabs"
:activeTabId="activeRouteId"
@tab-click="onTabClick"
@tab-close="onTabClose"
@close-other="onCloseOther"
@close-all="onCloseAll"
/>
<!-- 内容展示区 (内部路由渲染) -->
<view class="content-scroll">
<view class="content-inner">
<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,
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()
})
</script>
<style scoped lang="scss">
.layout-root {
display: flex;
flex-direction: row;
width: 100%;
min-height: 100vh;
background: #f0f2f5;
}
.main {
flex: 1;
display: flex;
flex-direction: column;
min-height: 100vh;
transition: margin-left 0.3s ease;
background: #f0f2f5;
}
.content-scroll {
flex: 1;
overflow-y: scroll;
background: #f0f2f5;
}
.content-inner {
min-height: calc(100vh - 120px);
padding: 16px;
}
</style>