fix: 优化移动端全屏侧栏

移动端侧栏改为全屏固定导航,关闭后释放全部正文宽度,并统一使用菜单图标入口。
This commit is contained in:
GouJ
2026-09-08 18:14:15 +08:00
committed by GitHub
parent fbf2690f2a
commit b4e1e6873c
4 changed files with 144 additions and 88 deletions
+84 -39
View File
@@ -23,25 +23,21 @@ import {
FolderOpen,
FileText,
Layers,
PanelLeftClose,
PanelLeftOpen,
Settings2
Menu,
Settings2,
X
} from '@lucide/vue'
import { AppDialog } from './components/ui'
import { useTheme } from './composables/useTheme'
import ThemeToggle from './components/ui/ThemeToggle.vue'
import { projectAccessKey, type ProjectAccess } from './features/projects/access'
/** 窄屏与桌面共用同一侧栏;窄屏展开时主栏右移,宽度保持折叠时的尺寸。 */
/** 窄屏将侧栏改为全屏覆盖层,关闭后不再占用正文宽度。 */
const NARROW_NAV = '(max-width: 800px)'
/** 折叠侧栏宽度,与菜单 collapsed-width 对齐。 */
const SIDER_COLLAPSED_WIDTH = 64
/** 展开侧栏宽度,窄屏裁切动画以内层保持该宽度。 */
/** 桌面端展开侧栏宽度。 */
const SIDER_EXPANDED_WIDTH = 208
/** 窄屏折叠侧栏宽度,给正文多留空间,仍覆盖 44px 点击区。 */
const SIDER_COLLAPSED_WIDTH_NARROW = 48
/** 窄屏展开侧栏宽度。 */
const SIDER_EXPANDED_WIDTH_NARROW = 168
/** 菜单图标尺寸,折叠/展开共用,避免切换时缩放。 */
const NAV_ICON_SIZE = 20
@@ -51,14 +47,14 @@ const narrowMedia = window.matchMedia(NARROW_NAV)
const narrow = ref(narrowMedia.matches)
const collapsed = ref(narrow.value)
const settingsOpen = ref(false)
/** 折叠只裁切文字,不把菜单收成图标,宽屏与窄屏共用同一套宽度动画。 */
/** 桌面折叠只裁切文字;移动端侧栏打开时始终显示完整菜单。 */
const menuCollapsed = false
/** 当前视口下的折叠侧栏宽度。 */
const siderCollapsedWidth = computed(() => (narrow.value ? SIDER_COLLAPSED_WIDTH_NARROW : SIDER_COLLAPSED_WIDTH))
/** 当前视口下的展开侧栏宽度。 */
const siderExpandedWidth = computed(() => (narrow.value ? SIDER_EXPANDED_WIDTH_NARROW : SIDER_EXPANDED_WIDTH))
/** Naive Menu 的桌面折叠宽度;移动端覆盖层不参与正文宽度计算。 */
const siderCollapsedWidth = SIDER_COLLAPSED_WIDTH
/** 侧栏展开宽度仅用于桌面布局。 */
const siderExpandedWidth = SIDER_EXPANDED_WIDTH
/** 折叠与展开共用的图标左偏移,避免切换时图标跳动。 */
const navIconIndent = computed(() => siderCollapsedWidth.value / 2 - NAV_ICON_SIZE / 2)
const navIconIndent = computed(() => (narrow.value ? 20 : siderCollapsedWidth / 2 - NAV_ICON_SIZE / 2))
/** 视口跨过断点时:进入窄屏收回侧栏。 */
function syncNarrow(event: MediaQueryListEvent) {
@@ -93,8 +89,11 @@ function onKeydown(event: KeyboardEvent) {
window.addEventListener('keydown', onKeydown)
onScopeDispose(() => window.removeEventListener('keydown', onKeydown))
/** 顶栏切换按钮的可访问名称。 */
const navToggleLabel = computed(() => (collapsed.value ? '展开侧栏' : '折叠侧栏'))
/** 顶栏菜单按钮在移动端使用更符合抽屉导航语义的可访问名称。 */
const navToggleLabel = computed(() => {
if (narrow.value) return collapsed.value ? '打开主菜单' : '关闭主菜单'
return collapsed.value ? '展开侧栏' : '折叠侧栏'
})
const { preference, theme, overrides } = useTheme()
const projectId = computed(() => String(route.params.projectId || ''))
const projectAccess = shallowRef<ProjectAccess | null>(null)
@@ -156,24 +155,40 @@ const menuOptions = computed<MenuOption[]>(() => [
'--admin-sider-expanded': siderExpandedWidth + 'px'
}"
>
<!-- 以下是主导航侧栏宽屏与窄屏共用同一份 -->
<!-- 以下是主导航侧栏桌面参与分栏移动端固定覆盖整个视口 -->
<aside
id="main-navigation"
class="admin-sider"
:class="{ 'is-collapsed': collapsed }"
:style="{ width: (collapsed ? siderCollapsedWidth : siderExpandedWidth) + 'px' }"
:style="{
width: narrow ? '100%' : (collapsed ? siderCollapsedWidth : siderExpandedWidth) + 'px'
}"
aria-label="主导航"
:aria-hidden="narrow && collapsed ? 'true' : undefined"
:inert="narrow && collapsed ? true : undefined"
>
<!-- 以下是侧栏品牌折叠裁切文字图标位置保持不变 -->
<RouterLink
to="/projects"
class="admin-brand"
:class="{ 'is-collapsed': collapsed }"
aria-label="短剧工作台首页"
>
<Clapperboard :size="narrow ? NAV_ICON_SIZE : 24" /><span
>短剧工作台<small>SHORT DRAMA STUDIO</small></span
<header class="admin-sider-header">
<!-- 以下是侧栏品牌桌面折叠裁切文字移动端始终展示完整品牌 -->
<RouterLink
to="/projects"
class="admin-brand"
:class="{ 'is-collapsed': collapsed }"
aria-label="短剧工作台首页"
>
</RouterLink>
<Clapperboard :size="narrow ? NAV_ICON_SIZE : 24" /><span
>短剧工作台<small>SHORT DRAMA STUDIO</small></span
>
</RouterLink>
<!-- 移动端全屏侧栏覆盖顶栏因此在侧栏内部提供明确关闭入口 -->
<NButton
quaternary
class="icon-button mobile-sider-close"
aria-label="关闭主菜单"
@click="toggleNav"
>
<template #icon><X :size="20" /></template>
</NButton>
</header>
<NScrollbar class="admin-nav-scroll">
<NMenu
:value="route.path"
@@ -216,11 +231,10 @@ const menuOptions = computed<MenuOption[]>(() => [
:aria-label="navToggleLabel"
:title="navToggleLabel"
:aria-expanded="!collapsed"
aria-controls="main-navigation"
@click="toggleNav"
>
<template #icon
><PanelLeftOpen v-if="collapsed" :size="18" /><PanelLeftClose v-else :size="18"
/></template>
<template #icon><Menu :size="20" /></template>
</NButton>
<span class="truncate text-sm">{{ route.meta.title || '工作空间' }}</span>
</div>
@@ -270,6 +284,12 @@ const menuOptions = computed<MenuOption[]>(() => [
@apply flex flex-col h-full w-[208px] shrink-0 overflow-hidden bg-(--app-subtle);
transition: width 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
.admin-sider-header {
@apply flex shrink-0 items-center overflow-hidden;
}
.mobile-sider-close {
@apply hidden shrink-0;
}
.admin-sider.is-collapsed {
@apply w-16;
}
@@ -334,17 +354,42 @@ const menuOptions = computed<MenuOption[]>(() => [
}
@media (max-width: 800px) {
.admin-sider {
overflow: hidden;
@apply fixed inset-0 z-50 overflow-hidden;
width: 100% !important;
transform: translateX(0);
transition:
transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
visibility 0s;
}
/* 收起后整块导航移出视口且禁止交互,让正文使用完整屏幕宽度。 */
.admin-sider.is-collapsed {
transform: translateX(-100%);
visibility: hidden;
pointer-events: none;
transition:
transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
visibility 0s 0.28s;
}
/* 主栏宽度锁在折叠态剩余空间,侧栏变宽时把主栏推到右侧而不是压窄。 */
.admin-layout > .admin-main {
flex: 0 0 auto;
width: calc(100% - var(--admin-sider-collapsed));
min-width: calc(100% - var(--admin-sider-collapsed));
max-width: calc(100% - var(--admin-sider-collapsed));
@apply w-full min-w-0 max-w-full flex-1;
}
.admin-nav-scroll.n-scrollbar,
.admin-brand {
min-width: 100%;
}
.admin-sider-header {
@apply h-12;
}
.admin-brand {
@apply h-12 justify-start px-3.5;
@apply h-12 min-w-0 flex-1 justify-start px-3.5;
}
.admin-sider.is-collapsed .admin-brand span,
.admin-sider.is-collapsed .n-menu-item-content-header,
.admin-sider.is-collapsed .admin-settings-label {
opacity: 1;
}
.mobile-sider-close {
@apply flex mr-2;
}
.admin-topbar {
@apply py-0 px-2.5;