优化细节

This commit is contained in:
2026-02-05 17:11:41 +08:00
parent 821205b18a
commit 151f5a5e1a
10 changed files with 634 additions and 194 deletions

View File

@@ -21,16 +21,18 @@
</view>
</view>
<view class="aside-footer" @click="onToggle">
<!-- 1:1 复刻 CRMEB: 一级侧边栏通常不单独折叠,由顶部汉堡菜单控制整体 -->
<!-- <view class="aside-footer" @click="onToggle">
<view class="toggle-btn">
<text class="toggle-icon">{{ collapsed ? '>' : '<' }}</text>
</view>
</view>
</view> -->
</view>
</template>
<script setup lang="uts">
import type { TopMenu } from '@/layouts/admin/router/adminRoutes.uts'
import { isMobile } from '@/layouts/admin/store/adminNavStore.uts'
const props = defineProps<{
collapsed: boolean
@@ -86,7 +88,7 @@ function onLogoClick(): void {
display: flex;
flex-direction: column;
transition: width 0.3s ease;
z-index: 1000;
z-index: 1002; /* 确保在遮罩层之上 */
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
}

View File

@@ -2,7 +2,12 @@
<view class="header">
<view class="header-left">
<!-- 移动端菜单切换按钮 (CSS 控制显隐) -->
<view class="menu-toggle mobile-only" @click="$emit('toggle-mobile-menu')">
<view class="menu-toggle mobile-only" @click="onToggleSubSider">
<text class="menu-icon">☰</text>
</view>
<!-- Desktop/Tablet Hamburger (1:1 复刻 CRMEB 切换二级侧边栏) -->
<view class="menu-toggle desktop-only" @click="onToggleSubSider">
<text class="menu-icon">☰</text>
</view>
@@ -30,6 +35,13 @@
<script setup lang="uts">
import { computed } from 'vue'
import {
toggleSubSider,
showSubSider,
layoutMode,
isOverlayVisible,
isMobileMenuOpen
} from '@/layouts/admin/store/adminNavStore.uts'
const props = defineProps<{
breadcrumb: Array<{id: string, title: string}>
@@ -44,6 +56,22 @@ defineEmits<{
(e:'toggle-mobile-menu'): void
}>()
/**
* 核心切换逻辑:
* 1. Desktop: 切换 showSubSider (Dock状态)
* 2. Tablet: 切换 isOverlayVisible (Overlay状态)
* 3. Mobile: 切换 isMobileMenuOpen (Mobile Aside)
*/
function onToggleSubSider(): void {
if (layoutMode.value === 'desktop') {
toggleSubSider()
} else if (layoutMode.value === 'tablet') {
isOverlayVisible.value = !isOverlayVisible.value
} else {
isMobileMenuOpen.value = !isMobileMenuOpen.value
}
}
const currentTitle = computed((): string => {
if (props.breadcrumb.length > 0) {
return props.breadcrumb[props.breadcrumb.length - 1].title

View File

@@ -1,5 +1,9 @@
<template>
<view class="admin-subsider" :style="{ left: asideWidth + 'px', width: siderWidth + 'px' }">
<view
class="admin-subsider"
:class="{ 'is-hidden': !visible }"
:style="{ left: asideWidth + 'px', width: siderWidth + 'px' }"
>
<view class="subsider-header">
<text class="header-title">{{ topMenuTitle }}</text>
</view>
@@ -10,8 +14,8 @@
<text>{{ group.title }}</text>
</view>
<view
v-for="route in getGroupRoutes(group.id)"
<view
v-for="route in getGroupRoutes(group.id)"
:key="route.id"
class="menu-item"
:class="{ active: route.id === activeRouteId }"
@@ -28,6 +32,7 @@
import type { MenuGroup, RouteRecord } from '@/layouts/admin/router/adminRoutes.uts'
const props = defineProps<{
visible: boolean // ✅ 新增:由父层控制显示/隐藏(不要用 v-if 卸载)
topMenuTitle: string
groups: MenuGroup[]
routes: Map<string, RouteRecord[]>
@@ -56,10 +61,45 @@ function onRouteClick(routeId: string): void {
bottom: 0;
background: #fff;
border-right: 1px solid #e8e8e8;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.06);
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
/* ✅ 动画只用 GPU 友好属性,避免 width/left 导致卡顿 */
opacity: 1;
transform: translate3d(0, 0, 0);
will-change: transform, opacity;
/* ✅ 可点击时才需要高 z-index */
z-index: 999;
/* ✅ 注意visibility 默认可见 */
visibility: visible;
/* 显示态visibility 不延迟 */
transition: transform 300ms ease, opacity 250ms ease, visibility 0s linear 0s;
}
/* 悬浮/抽屉模式:复刻 CRMEB 核心逻辑 */
.sub-sider-overlay {
z-index: 1001;
box-shadow: 6px 0 16px rgba(0, 0, 0, 0.08);
}
.admin-subsider.is-hidden {
/* ✅ 立即禁止拦截点击,彻底解决“遮挡可操作区” */
pointer-events: none;
/* ✅ 先淡出 + 滑出(推荐滑出 100%:完全离开可视区) */
opacity: 0;
transform: translate3d(-100%, 0, 0);
/* ✅ 动画结束后再隐藏(避免那一帧还挡住) */
visibility: hidden;
transition: transform 300ms ease, opacity 250ms ease, visibility 0s linear 300ms;
/* 可选:进一步避免层叠影响 */
z-index: 0;
}
.subsider-header {
@@ -68,7 +108,7 @@ function onRouteClick(routeId: string): void {
align-items: center;
padding: 0 16px;
border-bottom: 1px solid #e8e8e8;
.header-title {
font-size: 16px;
font-weight: 600;
@@ -79,7 +119,9 @@ function onRouteClick(routeId: string): void {
.subsider-menu {
flex: 1;
padding: 8px 0;
overflow-y: scroll;
/* ✅ 用 auto避免一直显示滚动条影响布局 */
overflow-y: auto;
}
.menu-group {
@@ -91,7 +133,7 @@ function onRouteClick(routeId: string): void {
font-size: 12px;
color: #999;
font-weight: 500;
text {
display: block;
}
@@ -103,17 +145,19 @@ function onRouteClick(routeId: string): void {
display: flex;
align-items: center;
cursor: pointer;
transition: all 0.2s;
position: relative;
/* ✅ 不要 transition: all会引发布局属性动画/回流) */
transition: background-color 0.2s ease, color 0.2s ease;
&:hover {
background: #f5f5f5;
}
&.active {
background: #e6f7ff;
color: #1890ff;
&::after {
content: '';
position: absolute;
@@ -124,7 +168,7 @@ function onRouteClick(routeId: string): void {
background: #1890ff;
}
}
.item-title {
font-size: 14px;
}