SxAppShell (应用外壳)

  • Implemented

提供响应式应用基础布局,包含侧边栏、顶部导航栏和主内容区域。支持自动响应式切换、内部导航、多 Tab 历史栈等高级功能。

使用场景

  • 应用级响应式布局(桌面/移动端自适应)
  • 侧边栏 + 顶部导航栏布局
  • 单页应用(SPA)内部导航
  • 多 Tab 页面缓存与历史管理

基本用法

最简单的用法

<SxAppRoot>
    <SxAppShell TitleText="我的应用">
        <Navigation>
            <SxNavMenu>
                <SxNavMenuLink Text="首页" IconName="house" Href="/" />
                <SxNavMenuLink Text="设置" IconName="gear" Href="/settings" />
            </SxNavMenu>
        </Navigation>
        <Content>
            @(context => @<div>主内容区域</div>)
        </Content>
    </SxAppShell>
</SxAppRoot>

完整示例

<SxAppRoot>
    <SxAppShell TitleText="企业管理系统"
                ShowCollapseToggle="true"
                @bind-Collapsed="_collapsed"
                SizingMode="SxAppShellSizingMode.Viewport"
                BreakpointChanged="OnBreakpointChanged">
        <Navigation>
            @RenderNavigation
        </Navigation>
        <Content>
            @(isWide => @<div class="@(isWide ? "wide-layout" : "narrow-layout")">
                @Body
            </div>)
        </Content>
    </SxAppShell>
</SxAppRoot>

@code {
    private bool _collapsed;

    private RenderFragment RenderNavigation => __builder =>
    {
        <SxNavMenu>
            <SxNavMenuGroup Text="用户管理" IconName="users">
                <SxNavMenuLink Text="用户列表" Href="/users" />
                <SxNavMenuLink Text="角色管理" Href="/roles" />
            </SxNavMenuGroup>
            <SxNavMenuLink Text="系统设置" IconName="gear" Href="/settings" />
        </SxNavMenu>
    };

    private void OnBreakpointChanged(SxAppShell.Breakpoint breakpoint)
    {
        Console.WriteLine($"断点变化: {breakpoint}");
    }
}

布局模式

SxAppShell 支持三种布局模式,通过 LayoutMode 参数控制:

自动模式(默认)

<SxAppShell LayoutMode="null">
    <!-- 宽屏(LG/XL)使用侧边栏,窄屏(XS/SM/MD)使用抽屉 -->
</SxAppShell>
  • 宽屏(≥LG):固定侧边栏 + 可拖拽分割条
  • 窄屏(<LG):抽屉式导航 + 汉堡菜单按钮

强制侧边栏模式

<SxAppShell LayoutMode="SxAppShellLayoutMode.Sidebar">
    <!-- 始终使用侧边栏布局,即使在移动端 -->
</SxAppShell>

强制抽屉模式

<SxAppShell LayoutMode="SxAppShellLayoutMode.Drawer">
    <!-- 始终使用抽屉布局,侧边显示窄条(Rail) -->
</SxAppShell>

响应式断点

默认断点

断点 宽度 布局行为
XS < 576px 抽屉模式,侧边栏完全隐藏
SM ≥ 576px 抽屉模式,侧边栏完全隐藏
MD ≥ 768px 抽屉模式,显示窄条(Rail)
LG ≥ 992px 侧边栏模式
XL ≥ 1200px 侧边栏模式

自定义断点

<SxAppShell BreakpointConfig="@_breakpointConfig">
    ...
</SxAppShell>

@code {
    private SxAppShellBreakpointConfig _breakpointConfig = new()
    {
        XS = "0px",
        SM = "480px",
        MD = "768px",
        LG = "1024px",
        XL = "1280px"
    };
}

容器尺寸模式

默认情况下,断点基于视口(Viewport)宽度计算。如果 SxAppShell 嵌入在容器中,可以使用容器尺寸:

<div style="width: 800px; height: 600px;">
    <SxAppShell SizingMode="SxAppShellSizingMode.Container">
        <!-- 断点基于容器宽度而非视口宽度 -->
    </SxAppShell>
</div>

侧边栏折叠

基本折叠控制

<SxAppShell @bind-Collapsed="_collapsed"
            ShowCollapseToggle="true">
    ...
</SxAppShell>

@code {
    private bool _collapsed;

    private void ExpandSidebar() => _collapsed = false;
    private void CollapseSidebar() => _collapsed = true;
}

折叠宽度

<SxAppShell Collapsed="true"
            CollapsedWidth="60px">
    <!-- 折叠时侧边栏宽度为 60px -->
</SxAppShell>

窄条(Rail)显示控制

<!-- 自动:MD 及以上显示窄条,SM/XS 隐藏 -->
<SxAppShell CollapsedRailMode="SxAppShellCollapsedRailMode.Auto" />

<!-- 始终显示窄条 -->
<SxAppShell CollapsedRailMode="SxAppShellCollapsedRailMode.Show" />

<!-- 始终隐藏窄条 -->
<SxAppShell CollapsedRailMode="SxAppShellCollapsedRailMode.Hidden" />

始终显示侧边栏

在小屏幕上也保持显示折叠的侧边栏(窄条):

<SxAppShell AlwaysShowSidebar="true">
    <!-- 即使在 XS/SM 断点也显示窄条 -->
</SxAppShell>

折叠时显示内容区导航栏

<!-- 默认行为:折叠时在内容区顶部显示导航栏 -->
<SxAppShell Collapsed="true" ShowContentNavBarWhenCollapsed="true" />

<!-- 禁用:折叠时不显示内容区导航栏 -->
<SxAppShell Collapsed="true" ShowContentNavBarWhenCollapsed="false" />

抽屉遮罩

控制抽屉展开时的遮罩层行为:

<!-- 透明遮罩,点击可关闭抽屉 -->
<SxAppShell DrawerOverlayMode="SxAppShellDrawerOverlayMode.Transparent" />

<!-- 隐藏遮罩,抽屉不遮挡内容区 -->
<SxAppShell DrawerOverlayMode="SxAppShellDrawerOverlayMode.Hidden" />

自定义导航栏

NavBarTemplate 参数接收一个布尔值,表示当前是否为侧边栏模式:

<SxAppShell>
    <NavBarTemplate>
        @{ var isSidebarMode = context; }
        <SxNavBar TitleText="我的应用"
                  IsSidebarMode="@isSidebarMode"
                  ShowHamburger="@isSidebarMode">
            <LeftActions>
                @if (!isSidebarMode)
                {
                    <SxButton Appearance="ButtonAppearance.Stealth"
                              IconStart="bars"
                              OnClick="ToggleSidebar" />
                }
            </LeftActions>
            <FunctionalMenu>
                <SxMenuItem Text="设置" Icon="gear" />
                <SxMenuItem Text="退出" Icon="sign-out" />
            </FunctionalMenu>
        </SxNavBar>
    </NavBarTemplate>
    <Navigation>
        <SxNavMenu>...</SxNavMenu>
    </Navigation>
</SxAppShell>

自定义导航菜单

<SxAppShell>
    <Navigation>
        <SxNavMenu>
            <!-- 普通链接 -->
            <SxNavMenuLink Text="首页" IconName="house" Href="/" />

            <!-- 分组 -->
            <SxNavMenuGroup Text="用户管理" IconName="users" DefaultOpen="true">
                <SxNavMenuLink Text="用户列表" Href="/users" />
                <SxNavMenuLink Text="角色管理" Href="/roles" />
                <SxNavMenuLink Text="权限设置" Href="/permissions" />
            </SxNavMenuGroup>

            <!-- 带徽标 -->
            <SxNavMenuLink Text="消息" IconName="envelope" Href="/messages">
                <Badge>
                    <SxBadge>5</SxBadge>
                </Badge>
            </SxNavMenuLink>

            <!-- 分隔线 -->
            <SxNavMenuDivider />

            <!-- 外部链接 -->
            <SxNavMenuLink Text="帮助文档"
                           IconName="question-circle"
                           Href="https://docs.example.com"
                           Target="_blank" />
        </SxNavMenu>
    </Navigation>
</SxAppShell>

底部栏 (BottomBar)

SxAppShell 提供了一个 BottomBar RenderFragment 插槽,用于在侧边栏底部放置固定内容。这是一个通用容器,可以放置任意组件,最常见的用途是放置 SxUserBar 用户栏组件。

设计理念

  • 容器与内容分离:BottomBar 只是一个容器,不包含业务逻辑
  • 固定定位:BottomBar 固定在侧边栏底部,不随导航内容滚动
  • 毛玻璃效果:内置半透明背景和模糊效果,滚动内容从其下方穿过
  • 灵活组合:可放置 SxUserBar、自定义按钮或任何其他组件

基本用法

<SxAppShell>
    <Navigation>
        <SxNavMenu>
            <SxNavMenuLink Text="首页" IconName="house" Href="/" />
            <SxNavMenuLink Text="设置" IconName="gear" Href="/settings" />
        </SxNavMenu>
    </Navigation>
    <BottomBar>
        <SxUserBar UserName="张三"
                   UserSubtitle="admin@example.com"
                   UserPresence="AvatarPresence.Online"
                   OnLogout="HandleLogout"
                   OnSettings="HandleSettings" />
    </BottomBar>
</SxAppShell>

@code {
    private async Task HandleLogout()
    {
        await AuthService.LogoutAsync();
        NavigationManager.NavigateTo("/login");
    }

    private void HandleSettings()
    {
        NavigationManager.NavigateTo("/settings");
    }
}

SxUserBar 配置

SxUserBar 是一个独立组件,自动响应 SxAppShell 的折叠状态:

<SxAppShell @bind-Collapsed="_collapsed">
    <Navigation>...</Navigation>
    <BottomBar>
        <SxUserBar UserName="张三"
                   UserSubtitle="admin@example.com"
                   UserAvatarUrl="/avatars/user.jpg"
                   ShowSettings="true"
                   ShowQuickSettings="true"
                   ShowLogout="true"
                   OnLogout="HandleLogout"
                   OnSettings="HandleSettings"
                   OnUserClick="HandleUserClick">
            <MenuContent>
                <!-- 自定义菜单项(显示在内置项之前) -->
                <SxMenuItem Text="我的订单" Icon="shopping-cart" OnClick="..." />
                <SxMenuItem Text="消息中心" Icon="bell" OnClick="..." />
                <SxMenuDivider />
            </MenuContent>
        </SxUserBar>
    </BottomBar>
</SxAppShell>

SxUserBar 参数

参数 类型 默认值 说明
UserName string? "User" 用户名
UserSubtitle string? null 副标题(邮箱/角色)
UserAvatarUrl string? null 头像 URL
UserPresence AvatarPresence None 在线状态(默认不显示)
MenuContent RenderFragment? null 自定义菜单项
ShowSettings bool true 显示设置菜单项
ShowQuickSettings bool true 显示快捷设置(主题/语言)
ShowLogout bool true 显示退出登录菜单项
OnLogout EventCallback 退出登录点击事件
OnSettings EventCallback 设置点击事件
OnUserClick EventCallback 用户信息区域点击事件

交互行为

SxUserBar 分为两个独立的可点击区域:

  • 用户信息区域(头像 + 用户名):点击触发 OnUserClick 事件,可用于跳转到用户资料页
  • 箭头按钮:点击弹出菜单,箭头图标会旋转指示菜单状态
<SxUserBar UserName="张三"
           OnUserClick="@(() => Nav.NavigateTo("/profile"))"
           OnLogout="HandleLogout"
           OnSettings="HandleSettings" />

折叠状态适配

SxUserBar 通过 CascadingParameter 接收 SxAppShellContext,自动响应折叠状态:

  • 展开状态:显示头像、用户名、副标题和箭头按钮
  • 折叠状态:仅显示头像,点击头像弹出菜单

自定义底部栏内容

BottomBar 可以放置任意内容,不限于 SxUserBar:

<SxAppShell>
    <Navigation>...</Navigation>
    <BottomBar>
        <!-- 自定义底部栏:版本信息 -->
        <div style="padding: var(--sx-spacing-m) var(--sx-shell-rail-pad-x); text-align: center;">
            <SxTypography Variant="TypographyVariant.Caption">v1.0.0</SxTypography>
        </div>
    </BottomBar>
</SxAppShell>

毛玻璃效果

BottomBar 容器自带毛玻璃效果(backdrop-filter),当导航菜单滚动时,内容会从底部栏下方透过:

.sx-sidebar-bottom-bar {
    background: color-mix(in srgb, var(--sx-colorNeutralBackground2) 80%, transparent);
    backdrop-filter: blur(12px);
}

内部导航

SxAppShell 支持内部导航模式,页面切换在外壳内部完成,不触发浏览器导航。

<SxAppShell NavigationMode="SxAppShellNavigationMode.Internal"
            InitialRoute="/dashboard"
            OnNavigate="HandleNavigate">
    <Navigation>
        <SxNavMenu>
            <SxNavMenuLink Text="仪表盘" IconName="chart-line" Href="/dashboard" />
            <SxNavMenuLink Text="用户" IconName="users" Href="/users" />
        </SxNavMenu>
    </Navigation>
    <NavigationContent>
        @{ var route = context; }
        @switch (route)
        {
            case "/dashboard":
                <DashboardPage />
                break;
            case "/users":
                <UsersPage />
                break;
            default:
                <NotFoundPage />
                break;
        }
    </NavigationContent>
</SxAppShell>

@code {
    private void HandleNavigate(string? route)
    {
        Console.WriteLine($"导航到: {route}");
    }
}

基于路由程序集

<SxAppShell NavigationMode="SxAppShellNavigationMode.Auto"
            RouteAppAssembly="@typeof(Program).Assembly"
            InitialRoute="/dashboard">
    <Navigation>
        <SxNavMenu>
            <SxNavMenuLink Text="仪表盘" Href="/dashboard" />
            <SxNavMenuLink Text="用户" Href="/users" />
        </SxNavMenu>
    </Navigation>
</SxAppShell>

浏览器 URL 同步

<!-- 默认:同步浏览器 URL -->
<SxAppShell SyncBrowserUrl="true" />

<!-- 禁用同步(适用于嵌套或演示场景) -->
<SxAppShell SyncBrowserUrl="false" />

历史栈限制

<SxAppShell MaxHistoryDepth="10"
            MaxCachedPages="20">
    <!-- 每个 Tab 最多保留 10 条历史记录 -->
    <!-- 全局最多缓存 20 个页面 -->
</SxAppShell>

Blazor Server 预渲染

ServerPrerendered 模式

在使用 render-mode="ServerPrerendered" 时,必须为 SxAppShell 提供稳定的 Id 参数,以确保服务端预渲染和客户端水合使用相同的 ID:

<!-- _Layout.cshtml -->
<body>
    <component type="typeof(AppShell)" render-mode="ServerPrerendered" />
    <script src="/_framework/blazor.server.js"></script>
</body>
<!-- AppShell.razor -->
<SxAppRoot>
    <SxAppShell Id="main-shell"
                ShowCollapseToggle="true"
                @bind-Collapsed="_collapsed"
                TitleText="我的应用">
        <Navigation>
            <SxNavMenu>
                <SxNavMenuLink Text="首页" IconName="house" Href="/" />
            </SxNavMenu>
        </Navigation>
        <Content>
            @(isWide => @<main id="content-slot"></main>)
        </Content>
    </SxAppShell>
</SxAppRoot>

@code {
    private bool _collapsed;
}

注意事项

  1. 必须提供 Id 参数:避免预渲染和水合时 ID 不一致导致事件处理器失效
  2. 确保服务注册IUserPreferencesINavStackService 等服务必须在 DI 容器中注册
  3. 避免依赖 JS 初始状态:断点检测等 JS 功能在预渲染时不可用,组件会在水合后初始化

不同渲染模式对比

渲染模式 描述 注意事项
ServerPrerendered HTML 预渲染 + SignalR 交互 需要稳定 Id
Server 仅 SignalR 交互 无预渲染闪烁,首屏稍慢
WebAssembly 客户端渲染 无特殊注意事项
WebAssemblyPrerendered HTML 预渲染 + WASM 交互 需要稳定 Id

嵌套 Shell

SxAppShell 支持嵌套使用(如演示场景)。嵌套的 Shell 会自动禁用浏览器 URL 同步:

<SxAppShell Id="outer-shell">
    <Content>
        @(isWide => @<div class="demo-container">
            <!-- 嵌套的 Shell,不会影响浏览器 URL -->
            <SxAppShell Id="inner-shell"
                        SyncBrowserUrl="false"
                        SizingMode="SxAppShellSizingMode.Container">
                ...
            </SxAppShell>
        </div>)
    </Content>
</SxAppShell>

API

Parameters

参数名 类型 默认值 描述
Id string? null 组件 ID,ServerPrerendered 模式必须提供
Navigation RenderFragment? null 侧边栏导航内容
NavBarTemplate RenderFragment<bool>? null 导航栏模板(参数为是否侧边栏模式)
Content RenderFragment<bool>? null 主内容模板(参数为是否宽屏)
TitleText string? null 默认导航栏标题文本
ShowCollapseToggle bool true 是否显示内置折叠切换按钮
Collapsed bool false 侧边栏折叠状态
CollapsedWidth string? null 折叠宽度(如 "60px"
CollapsedRailMode SxAppShellCollapsedRailMode Auto 窄条显示策略
AlwaysShowSidebar bool? null 是否始终显示侧边栏(小屏幕也显示窄条)
ShowContentNavBarWhenCollapsed bool true 折叠时是否显示内容区导航栏
SizingMode SxAppShellSizingMode Viewport 尺寸策略(Viewport/Container
LayoutMode SxAppShellLayoutMode? null 布局模式(null=自动, Sidebar, Drawer
BreakpointConfig SxAppShellBreakpointConfig? null 自定义断点配置
DrawerOverlayMode SxAppShellDrawerOverlayMode Transparent 抽屉遮罩模式
EnableSafeArea bool true 是否启用安全区(移动端)
NavigationMode SxAppShellNavigationMode Auto 导航模式
NavigationContent RenderFragment<string?>? null 内部导航内容模板
RouteAppAssembly Assembly? null 内部路由程序集
RouteAdditionalAssemblies IEnumerable<Assembly>? null 额外路由程序集
RouteDefaultLayout Type? null 默认布局类型
RouteFound RenderFragment<RouteData>? null 路由匹配模板
RouteNotFound RenderFragment? null 路由未匹配模板
InitialRoute string? null 初始路由路径
SyncBrowserUrl bool true 是否同步浏览器 URL
MaxHistoryDepth int? null 单 Tab 历史栈深度限制
MaxCachedPages int? null 缓存页面数量限制
BottomBar RenderFragment? null 底部栏内容(固定在侧边栏底部,可放置 SxUserBar 等组件)

Events

事件名 类型 描述
BreakpointChanged EventCallback<Breakpoint> 断点变化时触发
CollapsedChanged EventCallback<bool> 折叠状态变化时触发
OnNavigate EventCallback<string?> 内部导航时触发
OnBack EventCallback 返回时触发
OnHome EventCallback 返回首页时触发

Methods

方法名 返回值 描述
ToggleSidebar() void 切换侧边栏折叠状态
OnBreakpointChanged(string) void JS 调用的断点变化回调

枚举类型

public enum Breakpoint { XS, SM, MD, LG, XL }

public enum SxAppShellSizingMode { Viewport, Container }

public enum SxAppShellLayoutMode { Auto, Sidebar, Drawer }

public enum SxAppShellCollapsedRailMode { Auto, Show, Hidden }

public enum SxAppShellDrawerOverlayMode { Transparent, Hidden }

public enum SxAppShellNavigationMode { Auto, Internal, External }

常见问题

Q: 点击折叠按钮无响应?

检查以下几点:

  1. ServerPrerendered 模式下是否提供了 Id 参数
  2. IUserPreferencesINavStackService 服务是否已注册
  3. 浏览器控制台是否有 JS 错误

Q: 断点变化不触发?

确保:

  1. blazor.server.jsblazor.webassembly.js 已正确加载
  2. 组件已完成水合(预渲染模式)
  3. BreakpointConfig 配置正确

Q: 内部导航不工作?

检查:

  1. NavigationMode 是否正确设置
  2. NavigationContentRouteAppAssembly 是否提供
  3. SyncBrowserUrl 设置是否符合预期

参考设计