优化左边栏移动端效果
This commit is contained in:
parent
569e4d3e45
commit
d49d265472
@ -2,27 +2,53 @@
|
|||||||
<n-config-provider :theme-overrides="themeOverrides" class="h-full">
|
<n-config-provider :theme-overrides="themeOverrides" class="h-full">
|
||||||
<div class="relative flex h-full flex-col">
|
<div class="relative flex h-full flex-col">
|
||||||
<n-layout class="h-full" has-sider>
|
<n-layout class="h-full" has-sider>
|
||||||
|
<!-- 移动端遮罩层 -->
|
||||||
|
<div
|
||||||
|
v-if="isMobile && !sidebarCollapsed && !isSignupFlow && !isHideSidebar && session.user"
|
||||||
|
class="mobile-overlay"
|
||||||
|
@click="sidebarCollapsed = true"
|
||||||
|
></div>
|
||||||
|
|
||||||
<n-layout-sider
|
<n-layout-sider
|
||||||
v-if="!isSignupFlow && !$isMobile && !isHideSidebar && $session.user"
|
v-if="!isSignupFlow && !isHideSidebar && session.user"
|
||||||
bordered
|
bordered
|
||||||
collapse-mode="width"
|
collapse-mode="width"
|
||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
:width="240"
|
:width="240"
|
||||||
v-model:collapsed="sidebarCollapsed"
|
v-model:collapsed="sidebarCollapsed"
|
||||||
:show-trigger="true"
|
:show-trigger="!isMobile"
|
||||||
:responsive="true"
|
:responsive="true"
|
||||||
:breakpoint="768"
|
:breakpoint="768"
|
||||||
class="app-sidebar-sider"
|
class="app-sidebar-sider"
|
||||||
@collapse="sidebarCollapsed = true"
|
@collapse="sidebarCollapsed = true"
|
||||||
@expand="sidebarCollapsed = false"
|
@expand="sidebarCollapsed = false"
|
||||||
>
|
>
|
||||||
<AppSidebar :collapsed="sidebarCollapsed" />
|
<AppSidebar :collapsed="sidebarCollapsed" @menu-select="handleMenuSelect" />
|
||||||
</n-layout-sider>
|
</n-layout-sider>
|
||||||
<n-layout class="h-full">
|
<n-layout class="h-full">
|
||||||
|
<!-- 移动端顶部导航栏 -->
|
||||||
|
<div
|
||||||
|
v-if="isMobile && !isSignupFlow && !isHideSidebar && session.user"
|
||||||
|
class="mobile-header"
|
||||||
|
>
|
||||||
|
<div class="mobile-header-content">
|
||||||
|
<JLogo class="mobile-logo" />
|
||||||
|
<div class="mobile-user-info">
|
||||||
|
{{ teamUserText }}
|
||||||
|
</div>
|
||||||
|
<n-button
|
||||||
|
quaternary
|
||||||
|
circle
|
||||||
|
@click="sidebarCollapsed = false"
|
||||||
|
class="mobile-menu-button"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon><MenuIcon /></n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="w-full h-full overflow-auto" id="scrollContainer">
|
<div class="w-full h-full overflow-auto" id="scrollContainer">
|
||||||
<MobileNav
|
|
||||||
v-if="!isSignupFlow && $isMobile && !isHideSidebar && $session.user"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
!isSignupFlow &&
|
!isSignupFlow &&
|
||||||
@ -47,20 +73,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent, computed, watch, ref, provide, onMounted } from 'vue';
|
import { defineAsyncComponent, computed, watch, ref, provide, onMounted, onUnmounted } from 'vue';
|
||||||
import { NLayout, NLayoutSider, NConfigProvider } from 'naive-ui';
|
import { NLayout, NLayoutSider, NConfigProvider, NButton, NIcon } from 'naive-ui';
|
||||||
import { Toaster } from 'vue-sonner';
|
import { Toaster } from 'vue-sonner';
|
||||||
import { dialogs } from './utils/components';
|
import { dialogs } from './utils/components';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { getTeam } from './data/team';
|
import { getTeam } from './data/team';
|
||||||
import { session } from './data/session.js';
|
import { session } from './data/session.js';
|
||||||
|
import JLogo from '@/components/icons/JLogo.vue';
|
||||||
|
import MenuIcon from '~icons/lucide/menu';
|
||||||
|
|
||||||
const AppSidebar = defineAsyncComponent(
|
const AppSidebar = defineAsyncComponent(
|
||||||
() => import('./components/AppSidebar.vue'),
|
() => import('./components/AppSidebar.vue'),
|
||||||
);
|
);
|
||||||
const MobileNav = defineAsyncComponent(
|
|
||||||
() => import('./components/MobileNav.vue'),
|
|
||||||
);
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const team = getTeam();
|
const team = getTeam();
|
||||||
@ -72,7 +97,6 @@ const themeOverrides = {
|
|||||||
textColor: '#4a5568',
|
textColor: '#4a5568',
|
||||||
padding: '6px 12px',
|
padding: '6px 12px',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
// 使用主题系统配置菜单样式,减少深度选择器
|
// 使用主题系统配置菜单样式,减少深度选择器
|
||||||
@ -94,20 +118,47 @@ const themeOverrides = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 移动端检测
|
||||||
|
const isMobile = ref(false);
|
||||||
|
|
||||||
// 侧边栏折叠状态
|
// 侧边栏折叠状态
|
||||||
const sidebarCollapsed = ref(false);
|
const sidebarCollapsed = ref(false);
|
||||||
|
|
||||||
|
// 团队用户文本(用于移动端显示)
|
||||||
|
const teamUserText = computed(() => {
|
||||||
|
if (team?.get?.loading) {
|
||||||
|
return '加载中...';
|
||||||
|
}
|
||||||
|
return team?.pg?.user || '';
|
||||||
|
});
|
||||||
|
|
||||||
// 响应式:在移动端默认折叠
|
// 响应式:在移动端默认折叠
|
||||||
|
const checkMobile = () => {
|
||||||
|
const wasMobile = isMobile.value;
|
||||||
|
isMobile.value = window.innerWidth < 768;
|
||||||
|
|
||||||
|
// 切换到移动端时自动折叠
|
||||||
|
if (!wasMobile && isMobile.value) {
|
||||||
|
sidebarCollapsed.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const checkMobile = () => {
|
|
||||||
if (window.innerWidth < 768) {
|
|
||||||
sidebarCollapsed.value = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
checkMobile();
|
checkMobile();
|
||||||
window.addEventListener('resize', checkMobile);
|
window.addEventListener('resize', checkMobile);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', checkMobile);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理菜单选择 - 移动端自动关闭侧边栏
|
||||||
|
const handleMenuSelect = () => {
|
||||||
|
if (isMobile.value) {
|
||||||
|
sidebarCollapsed.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isHideSidebar = computed(() => {
|
const isHideSidebar = computed(() => {
|
||||||
const alwaysHideSidebarRoutes = [
|
const alwaysHideSidebarRoutes = [
|
||||||
'Site Login',
|
'Site Login',
|
||||||
@ -159,23 +210,15 @@ provide('session', session);
|
|||||||
background: #fafafa !important;
|
background: #fafafa !important;
|
||||||
border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
|
border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
|
||||||
/* 使用平滑的缓动函数,避免抖动 */
|
/* 使用平滑的缓动函数,避免抖动 */
|
||||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
|
||||||
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-sidebar-sider:not(.n-layout-sider--collapsed) {
|
|
||||||
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.04) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 触发器样式优化 - 平滑过渡 */
|
/* 触发器样式优化 - 平滑过渡 */
|
||||||
.app-sidebar-sider .n-layout-sider-trigger {
|
.app-sidebar-sider .n-layout-sider-trigger {
|
||||||
background: #fff !important;
|
background: #fff !important;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
|
|
||||||
/* 使用平滑的缓动函数,避免抖动 */
|
/* 使用平滑的缓动函数,避免抖动 */
|
||||||
transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||||
box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
|
||||||
width: 32px !important;
|
width: 32px !important;
|
||||||
height: 32px !important;
|
height: 32px !important;
|
||||||
border-radius: 50% !important;
|
border-radius: 50% !important;
|
||||||
@ -186,7 +229,6 @@ provide('session', session);
|
|||||||
|
|
||||||
.app-sidebar-sider .n-layout-sider-trigger:hover {
|
.app-sidebar-sider .n-layout-sider-trigger:hover {
|
||||||
background: #f5f5f5 !important;
|
background: #f5f5f5 !important;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-sidebar-sider .n-layout-sider-trigger:active {
|
.app-sidebar-sider .n-layout-sider-trigger:active {
|
||||||
@ -220,4 +262,93 @@ provide('session', session);
|
|||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
min-height: 100vh !important;
|
min-height: 100vh !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 移动端遮罩层 */
|
||||||
|
.mobile-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 998;
|
||||||
|
transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端顶部导航栏 */
|
||||||
|
.mobile-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-logo {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-user-info {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1a1a1a;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端侧边栏样式优化 */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.app-sidebar-sider {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh !important;
|
||||||
|
z-index: 999 !important;
|
||||||
|
width: 280px !important;
|
||||||
|
max-width: 85vw;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar-sider:not(.n-layout-sider--collapsed) {
|
||||||
|
transform: translateX(0) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端主内容区域占满全宽 */
|
||||||
|
.n-layout {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 桌面端保持原有样式 */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.app-sidebar-sider {
|
||||||
|
position: relative !important;
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-overlay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -76,8 +76,8 @@ export default {
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ['update:collapsed'],
|
emits: ['update:collapsed', 'menu-select'],
|
||||||
setup(props) {
|
setup(props, { emit }) {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const instance = getCurrentInstance();
|
const instance = getCurrentInstance();
|
||||||
@ -214,6 +214,8 @@ export default {
|
|||||||
console.error('Navigation error:', err);
|
console.error('Navigation error:', err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 发出菜单选择事件,用于移动端自动关闭侧边栏
|
||||||
|
emit('menu-select');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user