Files
medical-mall/layouts/admin/components/AdminSubsider.uvue
2026-02-05 17:11:41 +08:00

177 lines
3.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<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>
<view class="subsider-menu">
<view v-for="group in groups" :key="group.id" class="menu-group">
<view class="group-title">
<text>{{ group.title }}</text>
</view>
<view
v-for="route in getGroupRoutes(group.id)"
:key="route.id"
class="menu-item"
:class="{ active: route.id === activeRouteId }"
@click="onRouteClick(route.id)"
>
<text class="item-title">{{ route.title }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
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[]>
activeRouteId: string
asideWidth: number
siderWidth: number
}>()
const emit = defineEmits<{
(e: 'route-click', routeId: string): void
}>()
function getGroupRoutes(groupId: string): RouteRecord[] {
return props.routes.get(groupId) || []
}
function onRouteClick(routeId: string): void {
emit('route-click', routeId)
}
</script>
<style scoped lang="scss">
.admin-subsider {
position: fixed;
top: 0;
bottom: 0;
background: #fff;
border-right: 1px solid #e8e8e8;
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 {
height: 64px;
display: flex;
align-items: center;
padding: 0 16px;
border-bottom: 1px solid #e8e8e8;
.header-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
}
.subsider-menu {
flex: 1;
padding: 8px 0;
/* ✅ 用 auto避免一直显示滚动条影响布局 */
overflow-y: auto;
}
.menu-group {
margin-bottom: 16px;
}
.group-title {
padding: 8px 16px;
font-size: 12px;
color: #999;
font-weight: 500;
text {
display: block;
}
}
.menu-item {
height: 36px;
padding: 0 16px 0 24px;
display: flex;
align-items: center;
cursor: pointer;
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;
right: 0;
top: 0;
bottom: 0;
width: 3px;
background: #1890ff;
}
}
.item-title {
font-size: 14px;
}
}
</style>