191 lines
5.2 KiB
Plaintext
191 lines
5.2 KiB
Plaintext
<template>
|
||
<view class="layout-root">
|
||
<!-- 主侧边栏 -->
|
||
<AdminAside
|
||
:collapsed="isCollapsed"
|
||
:menuList="menuList"
|
||
:activeMenuId="activeMenuId"
|
||
@toggle="toggleCollapse"
|
||
@menu-click="onMenuClick"
|
||
/>
|
||
|
||
<!-- 二级侧边栏:固定在内容区左侧(独立层级) -->
|
||
<AdminSubSider
|
||
v-if="activeGroups.length > 0"
|
||
:activeMenuTitle="activeMenuTitle"
|
||
:groups="activeGroups"
|
||
:activeSubId="activeSubId"
|
||
@sub-click="onSubClick"
|
||
/>
|
||
|
||
<!-- 右侧内容区(Header + Tags + 内容展示区 + Footer) -->
|
||
<view
|
||
class="main"
|
||
:style="{ marginLeft: activeGroups.length > 0 ? '336px' : '96px' }"
|
||
>
|
||
<AdminHeader
|
||
:breadcrumb="breadcrumb"
|
||
:hasNotification="hasNotification"
|
||
@search="onSearch"
|
||
@refresh="onRefresh"
|
||
@notify="onNotify"
|
||
/>
|
||
|
||
<AdminTagsView
|
||
:tabs="tabs"
|
||
:activeTabId="activeTabId"
|
||
@tab-click="onTabClick"
|
||
@tab-close="onTabClose"
|
||
/>
|
||
|
||
<!-- 展示区:只渲染 slot 内容(你的页面内容都在这里展示) -->
|
||
<scroll-view class="content" scroll-y="true">
|
||
<view class="content-inner">
|
||
<slot></slot>
|
||
</view>
|
||
<AdminFooter />
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, computed } from 'vue'
|
||
import AdminAside from './components/AdminAside.uvue'
|
||
import AdminSubSider from './components/AdminSubSider.uvue'
|
||
import AdminHeader from './components/AdminHeader.uvue'
|
||
import AdminTagsView from './components/AdminTagsView.uvue'
|
||
import AdminFooter from './components/AdminFooter.uvue'
|
||
|
||
import { menuList as menuConst } from './utils/menu.uts'
|
||
import { findActiveByCurrentPage, getCurrentRoutePath } from './utils/nav.uts'
|
||
import { makeTabFromPath, upsertTab, removeTab } from './utils/tabs.uts'
|
||
import type { MenuItem, TabItem } from './types.uts'
|
||
|
||
// 你页面传进来的 currentPage:可能是顶级 id,也可能是子页面 id(user-list)
|
||
const props = defineProps<{ currentPage: string }>()
|
||
|
||
const menuList = ref<MenuItem[]>(menuConst)
|
||
|
||
const isCollapsed = ref(false)
|
||
const hasNotification = ref(true)
|
||
|
||
// active states
|
||
const activeMenuId = ref('home')
|
||
const activeSubId = ref('')
|
||
|
||
// tabs
|
||
const tabs = ref<TabItem[]>([
|
||
{ id: 'home', title: '首页', path: '/pages/mall/admin/homePage/index' }
|
||
])
|
||
const activeTabId = ref('home')
|
||
|
||
// 每次 layout 渲染时,同步高亮(靠 currentPage)
|
||
const syncActiveByCurrentPage = () => {
|
||
const r = findActiveByCurrentPage(menuList.value, props.currentPage)
|
||
activeMenuId.value = r.activeMenuId
|
||
activeSubId.value = r.activeSubId
|
||
}
|
||
|
||
// 同步 tabs(靠当前 route)
|
||
const syncTabsByRoute = () => {
|
||
const path = getCurrentRoutePath()
|
||
if (!path) return
|
||
|
||
const tab = makeTabFromPath(menuList.value, path)
|
||
tabs.value = upsertTab(tabs.value, tab)
|
||
activeTabId.value = tab.id
|
||
}
|
||
|
||
// 初始化同步(setup 执行一次)
|
||
syncActiveByCurrentPage()
|
||
syncTabsByRoute()
|
||
|
||
// computed
|
||
const activeMenu = computed(() => menuList.value.find(m => m.id === activeMenuId.value))
|
||
const activeMenuTitle = computed(() => activeMenu.value?.title || '商城后台')
|
||
const activeGroups = computed(() => activeMenu.value?.groups || [])
|
||
|
||
const breadcrumb = computed(() => {
|
||
// “一级 / 二级”
|
||
let subTitle = ''
|
||
const groups = activeGroups.value
|
||
for (const g of groups) {
|
||
const hit = g.children.find(c => c.id === activeSubId.value)
|
||
if (hit) { subTitle = hit.title; break }
|
||
}
|
||
return subTitle ? `${activeMenuTitle.value} / ${subTitle}` : `${activeMenuTitle.value}`
|
||
})
|
||
|
||
// handlers
|
||
const toggleCollapse = () => {
|
||
isCollapsed.value = !isCollapsed.value
|
||
}
|
||
|
||
const go = (url: string) => {
|
||
// 你明确要用 navigateTo:页面栈会增长(这是正常行为):contentReference[oaicite:4]{index=4}
|
||
uni.navigateTo({ url })
|
||
}
|
||
|
||
const onMenuClick = (menuId: string) => {
|
||
const m = menuList.value.find(x => x.id === menuId)
|
||
if (!m) return
|
||
activeMenuId.value = m.id
|
||
// 默认激活该菜单第一个子项
|
||
const g0 = (m.groups && m.groups.length > 0) ? m.groups[0] : null
|
||
const c0 = g0 && g0.children.length > 0 ? g0.children[0] : null
|
||
activeSubId.value = c0 ? c0.id : ''
|
||
go(m.path)
|
||
}
|
||
|
||
const onSubClick = (child: any) => {
|
||
activeSubId.value = child.id
|
||
go(child.path)
|
||
}
|
||
|
||
const onTabClick = (tab: TabItem) => {
|
||
activeTabId.value = tab.id
|
||
go(tab.path)
|
||
}
|
||
|
||
const onTabClose = (tabId: string) => {
|
||
// 关闭当前 tab:删除后回到最后一个 tab
|
||
const wasActive = activeTabId.value === tabId
|
||
tabs.value = removeTab(tabs.value, tabId)
|
||
if (wasActive) {
|
||
const last = tabs.value[tabs.value.length - 1]
|
||
if (last) {
|
||
activeTabId.value = last.id
|
||
go(last.path)
|
||
}
|
||
}
|
||
}
|
||
|
||
const onSearch = () => uni.showToast({ title: '搜索', icon: 'none' })
|
||
const onRefresh = () => uni.showToast({ title: '刷新', icon: 'none' })
|
||
const onNotify = () => uni.showToast({ title: '通知', icon: 'none' })
|
||
</script>
|
||
|
||
<style>
|
||
.layout-root{
|
||
width: 100%;
|
||
min-height: 100vh;
|
||
background:#f3f4f6;
|
||
}
|
||
|
||
/* 右侧主区域:左边距由 template 动态控制(96 或 336) */
|
||
.main{
|
||
min-height: 100vh;
|
||
display:flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 展示区 */
|
||
.content{
|
||
height: calc(100vh - 56px - 44px);
|
||
}
|
||
.content-inner{
|
||
padding: 16px;
|
||
}
|
||
</style>
|