同一套 Known 应用,桌面端跑 AdminLayout(侧边栏 + 顶栏),移动端则更适合底部 Tab 的 App 外壳。
框架把这件事拆成三块可组合的原语:AppLayout 母版、[AppMenu] 声明式底部导航、以及登录页的设备判定自动落位。
理念与 AdminLayout 一致——声明一次、自动推导,页面不必逐页接线。
AppLayout 母版
AppLayout 是移动端骨架:顶部 App 栏(标题 + 刷新 + 主题)+ 可滚动内容区 + 底部 Tab 导航。
页面用 @layout AppLayout 挂上它即可(AdminLayout 是路由默认布局,移动端页面需显式声明自己的布局):
@page "/app"
@layout AppLayout
<div class="app-hero">
<span class="app-hero-title">管理员,欢迎回来</span>
</div>
@* 你的移动端内容 *@
它内建:
- 登录门禁:进入时先取本地会话,未命中再向后端恢复;仍无身份则跳登录页(与
AdminLayout同一套EnsureAuth)。 - 标题自动推导:顶栏标题取当前 URL 命中的 Tab 名称,命中不到再回退
SiteTitle;要固定标题就传Title。 - 软刷新:右上角刷新按钮递增重挂键,令
@Body容器重建、页面重新初始化取数,不重载浏览器。 - 明暗主题 / 安全区:主题跟随
LocalService,底部栏用env(safe-area-inset-bottom)适配刘海屏。
参数一览见 AppLayout 组件文档。
用 [AppMenu] 注入底部 Tab
底部 Tab 不写在布局里,而是用类级特性 [AppMenu] 集中声明在 App 首页(外壳页)上。
[AppMenu] 可重复标注,一条 = 一个 Tab;框架扫描模块程序集收集它们,交给 AppMenuProvider 汇总为扁平列表:
@page "/app"
@layout AppLayout
@attribute [AppMenu("首页", Icon = "home", Url = "/app", Order = 1)]
@attribute [AppMenu("消息", Icon = "message-circle", Url = "/app/messages", Order = 2)]
@attribute [AppMenu("我的", Icon = "user", Url = "/app/mine", Order = 3)]
AppLayout 会自动把这三条渲染成底部三个 Tab,并按当前路由高亮命中的那一个。
各字段语义:
name:Tab 文字,可作为国际化 key(经LocalService解析,无译文回退原文)。icon:对应Icon组件的Type;留空回退circle。url:目标路由;留空时回退到标注所在页面自身的@page路由。order:排序,越小越靠前。roles:逗号分隔的可见角色,语义同[Menu].Roles——留空则所有已登录用户可见。hidden:为true时不进 Tab(路由仍可直达)。
Tab 的目标页同样 @layout AppLayout 即可共享这套外壳,切换时底部栏常驻、标题与高亮自动跟随:
@page "/app/mine"
@layout AppLayout
@* “我的”页无需再标 [AppMenu]——整组 Tab 已在 /app 上声明 *@
与
[Menu](侧边栏树)不同,[AppMenu]是扁平的,没有分组层级——底部 Tab 本就不该有二级菜单。
登录后按设备自动落位
移动端最自然的路径是:手机浏览器登录成功后直接进 /app,而不是先落到桌面后台再手动跳。
框架在 LocalService 里做了设备判定(known.device.isMobile():UA + 触屏 + 窄屏 + iPadOS 兜底),
登录页据此决定落位地址:
// 登录成功后(KnownApp/Pages/Login.razor 的默认实现)
// 优先级:显式 ?redirect > 移动端 Config.AppHomeRoute > 桌面 "/"
return await Local.IsMobileAsync() ? Config.AppHomeRoute : "/";
落位地址由 Config.AppHomeRoute 控制,默认 "/app";若你的移动端首页不叫 /app,启动时覆盖一次即可:
Config.AppHomeRoute = "/m/home";
判定结果缓存在 LocalService.Info.IsMobile,AppLayout、页面或任何注入 LocalService 的地方都能读到,
用于按端切换展示密度。
常见坑
- 移动端页面没加
@layout AppLayout→ 它会被路由默认布局AdminLayout接管,于是手机上看到的是桌面后台。每个 App 页都要显式声明。 [AppMenu]标了但底部没 Tab → 标注所在程序集须被Config.AddModule扫描到;放在未登记的程序集里,特性收集不到。- 以为要逐页传 Tab → 不用。整组 Tab 在外壳页声明一次,
AppMenuProvider全站装配;换页只换@Body,底部栏常驻。 roles过滤后 Tab 变少 → 正常,AppMenuProvider.Build(user)按当前登录用户角色裁剪;未登录时可见项为空。- 桌面端也跳到了
/app→ 检查设备判定:桌面窄窗口(pointer: coarse且宽度 ≤ 768)会被判为移动端;真桌面一般不触发。 - 想改移动端首页路由 → 改
Config.AppHomeRoute,并让那台页面上的[AppMenu]的Url与之对齐。
扫码关注公众号
扫码加入 QQ 群