dev #3
@ -2,27 +2,53 @@
|
||||
<n-config-provider :theme-overrides="themeOverrides" class="h-full">
|
||||
<div class="relative flex h-full flex-col">
|
||||
<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
|
||||
v-if="!isSignupFlow && !$isMobile && !isHideSidebar && $session.user"
|
||||
v-if="!isSignupFlow && !isHideSidebar && session.user"
|
||||
bordered
|
||||
collapse-mode="width"
|
||||
:collapsed-width="64"
|
||||
:width="240"
|
||||
v-model:collapsed="sidebarCollapsed"
|
||||
:show-trigger="true"
|
||||
:show-trigger="!isMobile"
|
||||
:responsive="true"
|
||||
:breakpoint="768"
|
||||
class="app-sidebar-sider"
|
||||
@collapse="sidebarCollapsed = true"
|
||||
@expand="sidebarCollapsed = false"
|
||||
>
|
||||
<AppSidebar :collapsed="sidebarCollapsed" />
|
||||
<AppSidebar :collapsed="sidebarCollapsed" @menu-select="handleMenuSelect" />
|
||||
</n-layout-sider>
|
||||
<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">
|
||||
<MobileNav
|
||||
v-if="!isSignupFlow && $isMobile && !isHideSidebar && $session.user"
|
||||
/>
|
||||
<div
|
||||
v-if="
|
||||
!isSignupFlow &&
|
||||
@ -47,20 +73,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineAsyncComponent, computed, watch, ref, provide, onMounted } from 'vue';
|
||||
import { NLayout, NLayoutSider, NConfigProvider } from 'naive-ui';
|
||||
import { defineAsyncComponent, computed, watch, ref, provide, onMounted, onUnmounted } from 'vue';
|
||||
import { NLayout, NLayoutSider, NConfigProvider, NButton, NIcon } from 'naive-ui';
|
||||
import { Toaster } from 'vue-sonner';
|
||||
import { dialogs } from './utils/components';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { getTeam } from './data/team';
|
||||
import { session } from './data/session.js';
|
||||
import JLogo from '@/components/icons/JLogo.vue';
|
||||
import MenuIcon from '~icons/lucide/menu';
|
||||
|
||||
const AppSidebar = defineAsyncComponent(
|
||||
() => import('./components/AppSidebar.vue'),
|
||||
);
|
||||
const MobileNav = defineAsyncComponent(
|
||||
() => import('./components/MobileNav.vue'),
|
||||
);
|
||||
|
||||
const route = useRoute();
|
||||
const team = getTeam();
|
||||
@ -72,7 +97,6 @@ const themeOverrides = {
|
||||
textColor: '#4a5568',
|
||||
padding: '6px 12px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||
},
|
||||
Menu: {
|
||||
// 使用主题系统配置菜单样式,减少深度选择器
|
||||
@ -94,20 +118,47 @@ const themeOverrides = {
|
||||
},
|
||||
};
|
||||
|
||||
// 移动端检测
|
||||
const isMobile = 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(() => {
|
||||
const checkMobile = () => {
|
||||
if (window.innerWidth < 768) {
|
||||
sidebarCollapsed.value = true;
|
||||
}
|
||||
};
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
});
|
||||
|
||||
// 处理菜单选择 - 移动端自动关闭侧边栏
|
||||
const handleMenuSelect = () => {
|
||||
if (isMobile.value) {
|
||||
sidebarCollapsed.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const isHideSidebar = computed(() => {
|
||||
const alwaysHideSidebarRoutes = [
|
||||
'Site Login',
|
||||
@ -159,23 +210,15 @@ provide('session', session);
|
||||
background: #fafafa !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),
|
||||
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;
|
||||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
}
|
||||
|
||||
/* 触发器样式优化 - 平滑过渡 */
|
||||
.app-sidebar-sider .n-layout-sider-trigger {
|
||||
background: #fff !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),
|
||||
box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
border-radius: 50% !important;
|
||||
@ -186,7 +229,6 @@ provide('session', session);
|
||||
|
||||
.app-sidebar-sider .n-layout-sider-trigger:hover {
|
||||
background: #f5f5f5 !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
.app-sidebar-sider .n-layout-sider-trigger:active {
|
||||
@ -220,4 +262,93 @@ provide('session', session);
|
||||
height: 100% !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>
|
||||
|
||||
@ -76,8 +76,8 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:collapsed'],
|
||||
setup(props) {
|
||||
emits: ['update:collapsed', 'menu-select'],
|
||||
setup(props, { emit }) {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const instance = getCurrentInstance();
|
||||
@ -214,6 +214,8 @@ export default {
|
||||
console.error('Navigation error:', err);
|
||||
}
|
||||
});
|
||||
// 发出菜单选择事件,用于移动端自动关闭侧边栏
|
||||
emit('menu-select');
|
||||
};
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user