Files
2026-10-25 18:52:36 +08:00

57 lines
16 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import{_ as i,o as a,c as n,a2 as l}from"./chunks/framework.B-QBKwc1.js";const c=JSON.parse('{"title":"布局系统","description":"前端布局采用经典的企业后台布局:左侧菜单 + 顶部导航 + 内容区域,支持多标签页导航。","frontmatter":{},"headers":[],"relativePath":"zh/frontend/layout.md","filePath":"zh/frontend/layout.md"}'),t={name:"zh/frontend/layout.md"};function p(e,s,h,k,r,d){return a(),n("div",null,[...s[0]||(s[0]=[l(`<h1 id="_9-15-布局系统" tabindex="-1">9.15 布局系统 <a class="header-anchor" href="#_9-15-布局系统" aria-label="Permalink to &quot;9.15 布局系统&quot;">​</a></h1><div class="tip custom-block"><p class="custom-block-title">概述</p><p>前端布局采用经典的企业后台布局:左侧菜单 + 顶部导航 + 内容区域,支持多标签页导航。</p></div><h2 id="布局结构" tabindex="-1">布局结构 <a class="header-anchor" href="#布局结构" aria-label="Permalink to &quot;布局结构&quot;">​</a></h2><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
<span class="line"><span>│ 顶部导航栏 │</span></span>
<span class="line"><span>│ [Logo] [面包屑] [搜索] [通知] [头像] │</span></span>
<span class="line"><span>├──────────┬──────────────────────────────────────────────────┤</span></span>
<span class="line"><span>│ │ │</span></span>
<span class="line"><span>│ 侧边栏 │ 内容区域 │</span></span>
<span class="line"><span>│ 菜单 │ │</span></span>
<span class="line"><span>│ │ ┌──────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ ──────── │ │ 标签页导航 │ │</span></span>
<span class="line"><span>│ 系统管理 │ ├──────────────────────────────────────────┤ │</span></span>
<span class="line"><span>│ ├ 用户 │ │ │ │</span></span>
<span class="line"><span>│ ├ 角色 │ │ 页面内容 │ │</span></span>
<span class="line"><span>│ └ 菜单 │ │ │ │</span></span>
<span class="line"><span>│ │ │ │ │</span></span>
<span class="line"><span>│ 内容管理 │ │ │ │</span></span>
<span class="line"><span>│ ├ 文章 │ │ │ │</span></span>
<span class="line"><span>│ └ 分类 │ │ │ │</span></span>
<span class="line"><span>│ │ └──────────────────────────────────────────┘ │</span></span>
<span class="line"><span>└──────────┴──────────────────────────────────────────────────┘</span></span></code></pre></div><h2 id="布局组件" tabindex="-1">布局组件 <a class="header-anchor" href="#布局组件" aria-label="Permalink to &quot;布局组件&quot;">​</a></h2><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>ui/src/layout/</span></span>
<span class="line"><span>├── index.vue # 布局入口</span></span>
<span class="line"><span>├── parentLayout.vue # 父级布局(路由嵌套用)</span></span>
<span class="line"><span>└── components/</span></span>
<span class="line"><span> ├── Header/ # 顶部导航(AmendPwd 修改密码、Notifier 通知、ProjectSetting 项目设置)</span></span>
<span class="line"><span> ├── Sider/ # 侧边栏菜单(动态菜单、折叠、主题)</span></span>
<span class="line"><span> ├── Main/ # 内容区域(keep-alive 缓存)</span></span>
<span class="line"><span> ├── TagsView/ # 标签页导航(多标签切换、关闭)</span></span>
<span class="line"><span> ├── Logo/ # 品牌标识</span></span>
<span class="line"><span> └── Footer/ # 页脚</span></span></code></pre></div><h2 id="侧边栏菜单" tabindex="-1">侧边栏菜单 <a class="header-anchor" href="#侧边栏菜单" aria-label="Permalink to &quot;侧边栏菜单&quot;">​</a></h2><p>菜单数据从后端 <code>GET /api/index/getMenus</code> 接口获取,由 <code>asyncRouteStore</code> 管理:</p><div class="language-typescript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 菜单渲染逻辑</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">const</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> menuList</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> asyncRouteStore</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">getMenus</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 菜单数据结构</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">interface</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#2E8F82;--shiki-light-font-style:inherit;"> MenuItem</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> id</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#2E8F82;--shiki-light-font-style:inherit;"> number</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> parentId</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#2E8F82;--shiki-light-font-style:inherit;"> number</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#2E8F82;--shiki-light-font-style:inherit;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> path</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#2E8F82;--shiki-light-font-style:inherit;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> component</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#2E8F82;--shiki-light-font-style:inherit;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> icon</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#2E8F82;--shiki-light-font-style:inherit;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> sort</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#2E8F82;--shiki-light-font-style:inherit;"> number</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> hide</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#2E8F82;--shiki-light-font-style:inherit;"> boolean</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> children</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">?</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#2E8F82;--shiki-light-font-style:inherit;"> MenuItem</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">[];</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><div class="tip custom-block"><p class="custom-block-title">菜单数据即路由</p><p>侧边栏与动态路由消费<strong>同一份</strong> <code>getMenus</code> 数据(仅 <code>type=0</code> 可导航节点、<code>status=0</code>、<code>hide=0</code>、<code>is_delete=0</code>)——菜单管理里改 <code>hide</code> 即可隐藏入口而不删除路由。见 <a href="/zh/frontend/router.html">9.5 路由与动态菜单</a>。</p></div><h2 id="标签页导航" tabindex="-1">标签页导航 <a class="header-anchor" href="#标签页导航" aria-label="Permalink to &quot;标签页导航&quot;">​</a></h2><p>标签页自动记录访问过的页面,支持:</p><ul><li>点击切换</li><li>关闭当前/其他/全部</li><li>右键菜单操作</li><li>keep-alive 缓存</li></ul><div class="language-typescript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 标签页 Store</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">const</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> tabsViewStore</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> useTabsViewStore</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 添加标签</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">tabsViewStore</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">addTab</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">route</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 关闭标签</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">tabsViewStore</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">removeTab</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">route</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span></code></pre></div><p>(keep-alive 组件列表由路由守卫 <code>afterEach</code> 统一维护,见 <a href="/zh/frontend/router.html">9.5 路由与动态菜单</a>。)</p><h2 id="响应式布局" tabindex="-1">响应式布局 <a class="header-anchor" href="#响应式布局" aria-label="Permalink to &quot;响应式布局&quot;">​</a></h2><p>布局支持桌面端和平板端:</p><ul><li>桌面端:侧边栏默认展开</li><li>平板端:侧边栏默认折叠</li><li>侧边栏支持手动折叠/展开</li></ul><div class="language-typescript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 侧边栏折叠状态</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">const</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> collapsed</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> ref</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;">false</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 响应式检测</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">const</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> width</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> useWindowSize</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">();</span></span>
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">watchEffect</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(()</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> collapsed</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">value</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> width</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">value</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> &lt;</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1024</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">});</span></span></code></pre></div>`,19)])])}const o=i(t,[["render",p]]);export{c as __pageData,o as default};