@melt-js/docs-bag 0.0.0-stage → 2026.11.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +32 -2
  2. package/dist/404.html +22 -0
  3. package/dist/README.html +33 -0
  4. package/dist/assets/README.md.BEUJStMI.js +9 -0
  5. package/dist/assets/README.md.BEUJStMI.lean.js +1 -0
  6. package/dist/assets/app.CPZ-gtYR.js +1 -0
  7. package/dist/assets/chunks/@localSearchIndexroot.7ill5i5O.js +1 -0
  8. package/dist/assets/chunks/VPLocalSearchBox.BVA3GWxn.js +9 -0
  9. package/dist/assets/chunks/framework.B3lxS50j.js +20 -0
  10. package/dist/assets/chunks/theme.C4rSMcmz.js +2 -0
  11. package/dist/assets/guide_complete-host-app.md.CDRzLz0E.js +174 -0
  12. package/dist/assets/guide_complete-host-app.md.CDRzLz0E.lean.js +1 -0
  13. package/dist/assets/guide_create-project.md.LRNhDD9x.js +34 -0
  14. package/dist/assets/guide_create-project.md.LRNhDD9x.lean.js +1 -0
  15. package/dist/assets/guide_deployment.md.B8aEUabm.js +42 -0
  16. package/dist/assets/guide_deployment.md.B8aEUabm.lean.js +1 -0
  17. package/dist/assets/guide_dictionary-system.md.Dy0tQYMR.js +32 -0
  18. package/dist/assets/guide_dictionary-system.md.Dy0tQYMR.lean.js +1 -0
  19. package/dist/assets/guide_getting-started.md.B-mobXxi.js +9 -0
  20. package/dist/assets/guide_getting-started.md.B-mobXxi.lean.js +1 -0
  21. package/dist/assets/guide_host_auth-and-permissions.md.DWH3Vape.js +139 -0
  22. package/dist/assets/guide_host_auth-and-permissions.md.DWH3Vape.lean.js +1 -0
  23. package/dist/assets/guide_host_bootstrap.md.DpbxPIZw.js +87 -0
  24. package/dist/assets/guide_host_bootstrap.md.DpbxPIZw.lean.js +1 -0
  25. package/dist/assets/guide_host_routing-and-menu.md.BdCMIOxn.js +103 -0
  26. package/dist/assets/guide_host_routing-and-menu.md.BdCMIOxn.lean.js +1 -0
  27. package/dist/assets/guide_introduction.md.1MiCcWyO.js +14 -0
  28. package/dist/assets/guide_introduction.md.1MiCcWyO.lean.js +1 -0
  29. package/dist/assets/guide_local-debugging.md.CLJoKVb9.js +15 -0
  30. package/dist/assets/guide_local-debugging.md.CLJoKVb9.lean.js +1 -0
  31. package/dist/assets/guide_network-request.md.DDlA8bc0.js +256 -0
  32. package/dist/assets/guide_network-request.md.DDlA8bc0.lean.js +1 -0
  33. package/dist/assets/guide_official-plugins_index.md.DPwemCGy.js +15 -0
  34. package/dist/assets/guide_official-plugins_index.md.DPwemCGy.lean.js +1 -0
  35. package/dist/assets/guide_official-plugins_install-and-toggle.md.Eq2hWjYn.js +45 -0
  36. package/dist/assets/guide_official-plugins_install-and-toggle.md.Eq2hWjYn.lean.js +1 -0
  37. package/dist/assets/guide_official-plugins_shop.md.yBltEx-o.js +49 -0
  38. package/dist/assets/guide_official-plugins_shop.md.yBltEx-o.lean.js +1 -0
  39. package/dist/assets/guide_official-plugins_sys-setting.md.BmkuOqGv.js +26 -0
  40. package/dist/assets/guide_official-plugins_sys-setting.md.BmkuOqGv.lean.js +1 -0
  41. package/dist/assets/guide_package-core.md.-E29SYy8.js +45 -0
  42. package/dist/assets/guide_package-core.md.-E29SYy8.lean.js +1 -0
  43. package/dist/assets/guide_package-ecosystem.md.Di3RcVpF.js +1 -0
  44. package/dist/assets/guide_package-ecosystem.md.Di3RcVpF.lean.js +1 -0
  45. package/dist/assets/guide_package-plugin-shop.md.D-xXN7Yg.js +9 -0
  46. package/dist/assets/guide_package-plugin-shop.md.D-xXN7Yg.lean.js +1 -0
  47. package/dist/assets/guide_package-plugin-sys-setting.md.Dx3nxHVF.js +9 -0
  48. package/dist/assets/guide_package-plugin-sys-setting.md.Dx3nxHVF.lean.js +1 -0
  49. package/dist/assets/guide_package-publishing.md.B7hzTYDH.js +7 -0
  50. package/dist/assets/guide_package-publishing.md.B7hzTYDH.lean.js +1 -0
  51. package/dist/assets/guide_package-request.md.DxaD65eM.js +195 -0
  52. package/dist/assets/guide_package-request.md.DxaD65eM.lean.js +1 -0
  53. package/dist/assets/guide_package-ui.md.wBZ_iQLb.js +50 -0
  54. package/dist/assets/guide_package-ui.md.wBZ_iQLb.lean.js +1 -0
  55. package/dist/assets/guide_permissions.md.B_5re4AR.js +83 -0
  56. package/dist/assets/guide_permissions.md.B_5re4AR.lean.js +1 -0
  57. package/dist/assets/guide_plugin-development.md.BQzTwLRr.js +226 -0
  58. package/dist/assets/guide_plugin-development.md.BQzTwLRr.lean.js +1 -0
  59. package/dist/assets/guide_plugins_admin-plugin.md.d_XC5U-4.js +52 -0
  60. package/dist/assets/guide_plugins_admin-plugin.md.d_XC5U-4.lean.js +1 -0
  61. package/dist/assets/guide_plugins_create-plugin.md.C-wDq4Un.js +109 -0
  62. package/dist/assets/guide_plugins_create-plugin.md.C-wDq4Un.lean.js +1 -0
  63. package/dist/assets/guide_plugins_debug-and-publish.md.CeTc01QN.js +92 -0
  64. package/dist/assets/guide_plugins_debug-and-publish.md.CeTc01QN.lean.js +1 -0
  65. package/dist/assets/guide_plugins_lifecycle-and-dependencies.md.BtwKS5jY.js +45 -0
  66. package/dist/assets/guide_plugins_lifecycle-and-dependencies.md.BtwKS5jY.lean.js +1 -0
  67. package/dist/assets/guide_plugins_permissions-and-settings.md.05CsB1I6.js +78 -0
  68. package/dist/assets/guide_plugins_permissions-and-settings.md.05CsB1I6.lean.js +1 -0
  69. package/dist/assets/guide_plugins_routes-menus-locales.md.B2dzO2DN.js +100 -0
  70. package/dist/assets/guide_plugins_routes-menus-locales.md.B2dzO2DN.lean.js +1 -0
  71. package/dist/assets/guide_pro-form.md.DbSHbrMu.js +116 -0
  72. package/dist/assets/guide_pro-form.md.DbSHbrMu.lean.js +1 -0
  73. package/dist/assets/guide_pro-table.md.DBuYQYgG.js +54 -0
  74. package/dist/assets/guide_pro-table.md.DBuYQYgG.lean.js +1 -0
  75. package/dist/assets/guide_request-and-components.md.BQXI_ZxY.js +175 -0
  76. package/dist/assets/guide_request-and-components.md.BQXI_ZxY.lean.js +1 -0
  77. package/dist/assets/guide_routing-and-menu.md.-zrm3BV9.js +74 -0
  78. package/dist/assets/guide_routing-and-menu.md.-zrm3BV9.lean.js +1 -0
  79. package/dist/assets/guide_theme-tokens.md.DXpbrOjS.js +53 -0
  80. package/dist/assets/guide_theme-tokens.md.DXpbrOjS.lean.js +1 -0
  81. package/dist/assets/index.md.BUgI8r98.js +1 -0
  82. package/dist/assets/index.md.BUgI8r98.lean.js +1 -0
  83. package/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  84. package/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  85. package/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  86. package/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  87. package/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  88. package/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  89. package/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  90. package/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  91. package/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  92. package/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  93. package/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  94. package/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  95. package/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  96. package/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  97. package/dist/assets/style.0snOvk0r.css +1 -0
  98. package/dist/guide/complete-host-app.html +198 -0
  99. package/dist/guide/create-project.html +58 -0
  100. package/dist/guide/deployment.html +66 -0
  101. package/dist/guide/dictionary-system.html +56 -0
  102. package/dist/guide/getting-started.html +33 -0
  103. package/dist/guide/host/auth-and-permissions.html +163 -0
  104. package/dist/guide/host/bootstrap.html +111 -0
  105. package/dist/guide/host/routing-and-menu.html +127 -0
  106. package/dist/guide/introduction.html +38 -0
  107. package/dist/guide/local-debugging.html +39 -0
  108. package/dist/guide/network-request.html +280 -0
  109. package/dist/guide/official-plugins/index.html +39 -0
  110. package/dist/guide/official-plugins/install-and-toggle.html +69 -0
  111. package/dist/guide/official-plugins/shop.html +73 -0
  112. package/dist/guide/official-plugins/sys-setting.html +50 -0
  113. package/dist/guide/package-core.html +69 -0
  114. package/dist/guide/package-ecosystem.html +25 -0
  115. package/dist/guide/package-plugin-shop.html +33 -0
  116. package/dist/guide/package-plugin-sys-setting.html +33 -0
  117. package/dist/guide/package-publishing.html +31 -0
  118. package/dist/guide/package-request.html +219 -0
  119. package/dist/guide/package-ui.html +74 -0
  120. package/dist/guide/permissions.html +107 -0
  121. package/dist/guide/plugin-development.html +250 -0
  122. package/dist/guide/plugins/admin-plugin.html +76 -0
  123. package/dist/guide/plugins/create-plugin.html +133 -0
  124. package/dist/guide/plugins/debug-and-publish.html +116 -0
  125. package/dist/guide/plugins/lifecycle-and-dependencies.html +69 -0
  126. package/dist/guide/plugins/permissions-and-settings.html +102 -0
  127. package/dist/guide/plugins/routes-menus-locales.html +124 -0
  128. package/dist/guide/pro-form.html +140 -0
  129. package/dist/guide/pro-table.html +78 -0
  130. package/dist/guide/request-and-components.html +199 -0
  131. package/dist/guide/routing-and-menu.html +98 -0
  132. package/dist/guide/theme-tokens.html +77 -0
  133. package/dist/hashmap.json +1 -0
  134. package/dist/index.html +25 -0
  135. package/dist/logo.png +0 -0
  136. package/dist/logo.svg +1 -0
  137. package/dist/vp-icons.css +1 -0
  138. package/dist/wechat-group-qr.png +0 -0
  139. package/package.json +24 -4
@@ -0,0 +1,83 @@
1
+ import{_ as i,o as a,c as n,a2 as h}from"./chunks/framework.B3lxS50j.js";const g=JSON.parse('{"title":"权限管理","description":"","frontmatter":{},"headers":[],"relativePath":"guide/permissions.md","filePath":"guide/permissions.md","lastUpdated":1794045200000}'),t={name:"guide/permissions.md"};function l(p,s,k,e,E,r){return a(),n("div",null,[...s[0]||(s[0]=[h(`<h1 id="权限管理" tabindex="-1">权限管理 <a class="header-anchor" href="#权限管理" aria-label="Permalink to &quot;权限管理&quot;">​</a></h1><p>Vue-Bag-Admin 的权限模型基于 RBAC,覆盖这几块:</p><ul><li>页面级路由权限</li><li>菜单级权限过滤</li><li>按钮和组件级权限控制</li></ul><h2 id="权限数据流转" tabindex="-1">权限数据流转 <a class="header-anchor" href="#权限数据流转" aria-label="Permalink to &quot;权限数据流转&quot;">​</a></h2><p>前端鉴权主要看 <code>userStore</code> 里的两个状态:<code>roles</code>(角色列表)和 <code>permissions</code>(权限标识列表)。</p><p>不管后端怎么写,前端这边通常按这条链路走:</p><ol><li>用户登录成功后,前端请求获取当前用户信息(如 <code>/api/users/me</code> 接口)。</li><li>后端需要在返回信息中,带上该用户的所属角色(如 <code>[&#39;admin&#39;]</code>)以及具体的权限点(如 <code>[&#39;shop.read&#39;, &#39;shop.write&#39;]</code>)。</li><li>前端将这些角色和权限点数据格式化后,存储在 Pinia 的 <code>userStore</code> 中。</li></ol><p>如果你正在使用默认的 Strapi 5 后端,框架已经处理好了这一步的数据适配。</p><h2 id="页面级权限控制-路由鉴权" tabindex="-1">页面级权限控制 (路由鉴权) <a class="header-anchor" href="#页面级权限控制-路由鉴权" aria-label="Permalink to &quot;页面级权限控制 (路由鉴权)&quot;">​</a></h2><p>在路由里配置 <code>meta.roles</code> 或 <code>meta.permissions</code>,就能限制谁可以访问该页面。</p><h3 id="按角色拦截" tabindex="-1">按角色拦截 <a class="header-anchor" href="#按角色拦截" aria-label="Permalink to &quot;按角色拦截&quot;">​</a></h3><div class="language-typescript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
2
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> path</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/admin/settings&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
3
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> component</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;./views/Settings.vue&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
4
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
5
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> title</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;系统设置&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
6
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> roles</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;admin&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;super-admin&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 只有 admin 和 super-admin 角色可访问</span></span>
7
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
8
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><h3 id="按权限标识拦截" tabindex="-1">按权限标识拦截 <a class="header-anchor" href="#按权限标识拦截" aria-label="Permalink to &quot;按权限标识拦截&quot;">​</a></h3><p>如果权限划分比较细,直接用权限标识(Permissions):</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 github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
9
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> path</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/shop/products&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
10
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> component</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;./views/Products.vue&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
11
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
12
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> title</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;商品列表&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
13
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> permissions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;shop.product.read&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;shop.product.write&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 拥有其中任意一个权限即可访问</span></span>
14
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
15
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><h3 id="鉴权逻辑" tabindex="-1">鉴权逻辑 <a class="header-anchor" href="#鉴权逻辑" aria-label="Permalink to &quot;鉴权逻辑&quot;">​</a></h3><p><code>createHostRouter()</code> 支持三种鉴权模式:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">createHostRouter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({ routes, auth: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> })</span></span></code></pre></div><p>关闭登录与页面权限守卫,适合只想使用宿主布局、路由和插件壳层的项目。</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">createHostRouter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({ routes, auth: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> })</span></span></code></pre></div><p>使用默认后台鉴权约定。未登录访问非公开页面时跳到默认登录页 <code>/login</code>。</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">createHostRouter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
16
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> routes,</span></span>
17
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> auth: {</span></span>
18
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> enabled: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
19
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> loginPath: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/login&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
20
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> homePath: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/dashboard&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
21
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> forbiddenPath: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/403&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
22
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> onProfileError</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">() {</span></span>
23
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;logout&#39;</span></span>
24
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
25
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
26
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p>自定义鉴权路径与异常处理,适合已有登录系统、SSO 或企业统一门户。</p><p>开启鉴权后,系统的全局前置路由守卫会拦截路由跳转:</p><ol><li>未登录访问非公开页面时,跳转到登录页</li><li>已登录但尚未拉取用户信息时,先请求用户资料</li><li>角色或权限不满足时,跳转到 <code>/403</code></li></ol><p>如果已经有 token,但用户资料加载失败,默认会清空登录态并回到登录页。你可以通过 <code>onProfileError</code> 改成忽略、抛错或跳到自己的处理页。</p><p>默认情况下,<code>roles</code> 和 <code>permissions</code> 数组内部都是“满足任意一个即可”。如果页面需要同时满足多个角色或多个权限,可以配置 <code>roleMode</code> 或 <code>permissionMode</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 github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
27
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> path</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/finance/audit&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
28
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> component</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;./views/FinanceAudit.vue&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
29
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
30
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> title</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;财务审核&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
31
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> roles</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;finance&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;manager&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
32
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> roleMode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;all&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
33
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> permissions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;finance.audit&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;finance.read&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
34
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> permissionMode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;all&#39;</span></span>
35
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
36
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>更复杂的规则可以交给 <code>policy</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 github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
37
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> path</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/team/:id&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
38
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> component</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;./views/TeamDetail.vue&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
39
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
40
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> title</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;团队详情&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
41
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> policy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: ({ </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">roles</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">permissions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span></span>
42
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> roles.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">includes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;admin&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> permissions.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">includes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;team.owner&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
43
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
44
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><h2 id="菜单级权限控制" tabindex="-1">菜单级权限控制 <a class="header-anchor" href="#菜单级权限控制" aria-label="Permalink to &quot;菜单级权限控制&quot;">​</a></h2><p>菜单本身也支持 <code>roles</code> 与 <code>permissions</code>,侧边栏会按当前用户权限过滤菜单树。</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
45
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> path</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/finance/flow/daily&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
46
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> title</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;shop.finance.daily&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
47
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> roles</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;authenticated&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
48
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> permissions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;finance.read&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span>
49
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><h2 id="按钮-组件级权限控制" tabindex="-1">按钮/组件级权限控制 <a class="header-anchor" href="#按钮-组件级权限控制" aria-label="Permalink to &quot;按钮/组件级权限控制&quot;">​</a></h2><p>实际开发里,最常见的是控制按钮、操作栏、表格操作列这些局部 UI 的显示与隐藏。</p><h3 id="方式一-直接使用-userstore" tabindex="-1">方式一:直接使用 userStore <a class="header-anchor" href="#方式一-直接使用-userstore" aria-label="Permalink to &quot;方式一:直接使用 userStore&quot;">​</a></h3><p>可以直接使用 <code>userStore</code> 暴露的方法:</p><div class="language-vue vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">vue</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
50
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">div</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;actions&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
51
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> v-if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">userStore.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">hasPermission</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">([</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;shop.product.write&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">])</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;btn-primary&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
52
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> 新建商品</span></span>
53
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
54
+ <span class="line"></span>
55
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> v-if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">userStore.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">hasRole</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">([</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;admin&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">])</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;btn-danger&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;危险操作&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
56
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
57
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
58
+ <span class="line"></span>
59
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">script</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> setup</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> lang</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;ts&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
60
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { useUserStore } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;@/stores/user&#39;</span></span>
61
+ <span class="line"></span>
62
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> userStore</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> useUserStore</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span>
63
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">script</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span></code></pre></div><h3 id="方式二-使用-v-permission-指令" tabindex="-1">方式二:使用 v-permission 指令 <a class="header-anchor" href="#方式二-使用-v-permission-指令" aria-label="Permalink to &quot;方式二:使用 v-permission 指令&quot;">​</a></h3><p>宿主已经全局注册了 <code>v-permission</code> 指令,用它控制单个元素最省事。</p><div class="language-vue vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">vue</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
64
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> v-permission</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;&#39;shop.product.publish&#39;&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;发布商品&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
65
+ <span class="line"></span>
66
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> v-permission</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;shop.product.delete&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;shop.product.write&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;删除商品&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
67
+ <span class="line"></span>
68
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> v-permission</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{ roles: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;admin&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">], permissions: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;system.manage&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] }</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;系统管理&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
69
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span></code></pre></div><p>支持三种写法:</p><ul><li>单个权限字符串</li><li>权限数组</li><li>完整权限对象 <code>{ roles, roleMode, permissions, permissionMode, policy }</code></li></ul><h3 id="方式三-使用-permissionaccess-组件" tabindex="-1">方式三:使用 PermissionAccess 组件 <a class="header-anchor" href="#方式三-使用-permissionaccess-组件" aria-label="Permalink to &quot;方式三:使用 PermissionAccess 组件&quot;">​</a></h3><p>想包裹一整块内容时,<code>PermissionAccess</code> 更直观。</p><div class="language-vue vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">vue</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
70
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">PermissionAccess</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> :</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">permissions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;shop.product.publish&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
71
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;发布商品&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
72
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;导入商品&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
73
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">PermissionAccess</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
74
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span></code></pre></div><h3 id="方式四-使用-canaccess-useaccess" tabindex="-1">方式四:使用 canAccess / useAccess <a class="header-anchor" href="#方式四-使用-canaccess-useaccess" aria-label="Permalink to &quot;方式四:使用 canAccess / useAccess&quot;">​</a></h3><p>如果要在组合式函数、渲染函数或复杂逻辑里复用判断,可以使用这两个公共入口。</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { canAccess, useAccess } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;@/access&#39;</span></span>
75
+ <span class="line"></span>
76
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> visible</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> canAccess</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
77
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> roles: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;authenticated&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
78
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> permissions: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;shop.product.publish&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span>
79
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span>
80
+ <span class="line"></span>
81
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> canDelete</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> useAccess</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
82
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> permissions: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;shop.product.delete&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span>
83
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><h2 id="方法说明" tabindex="-1">方法说明 <a class="header-anchor" href="#方法说明" aria-label="Permalink to &quot;方法说明&quot;">​</a></h2><ul><li><code>userStore.hasRole(roles?: string[], mode?: &#39;any&#39; | &#39;all&#39;)</code>: 判断当前用户是否拥有指定的角色。默认满足其一即返回 <code>true</code>。</li><li><code>userStore.hasPermission(perms?: string[], mode?: &#39;any&#39; | &#39;all&#39;)</code>: 判断当前用户是否拥有指定的权限。如果用户权限中包含 <code>*</code>(超级管理员标志),将始终返回 <code>true</code>。</li><li><code>canAccess(requirement)</code>: 权限判断方法,供路由、组件、指令复用。</li><li><code>useAccess(requirement)</code>: 响应式权限判断组合函数。</li></ul><h2 id="使用建议" tabindex="-1">使用建议 <a class="header-anchor" href="#使用建议" aria-label="Permalink to &quot;使用建议&quot;">​</a></h2><ul><li>页面路由:优先配置 <code>meta.roles</code> 或 <code>meta.permissions</code></li><li>菜单节点:和页面保持相同权限口径</li><li>单个按钮:优先使用 <code>v-permission</code></li><li>一整块区域:优先使用 <code>PermissionAccess</code></li><li>复杂组合逻辑:使用 <code>canAccess()</code> 或 <code>useAccess()</code></li></ul>`,53)])])}const o=i(t,[["render",l]]);export{g as __pageData,o as default};
@@ -0,0 +1 @@
1
+ import{_ as i,o as a,c as n,a2 as h}from"./chunks/framework.B3lxS50j.js";const g=JSON.parse('{"title":"权限管理","description":"","frontmatter":{},"headers":[],"relativePath":"guide/permissions.md","filePath":"guide/permissions.md","lastUpdated":1794045200000}'),t={name:"guide/permissions.md"};function l(p,s,k,e,E,r){return a(),n("div",null,[...s[0]||(s[0]=[h("",53)])])}const o=i(t,[["render",l]]);export{g as __pageData,o as default};
@@ -0,0 +1,226 @@
1
+ import{_ as i,o as a,c as n,a2 as l}from"./chunks/framework.B3lxS50j.js";const g=JSON.parse('{"title":"插件化开发","description":"","frontmatter":{},"headers":[],"relativePath":"guide/plugin-development.md","filePath":"guide/plugin-development.md","lastUpdated":1794045200000}'),p={name:"guide/plugin-development.md"};function h(t,s,k,e,E,d){return a(),n("div",null,[...s[0]||(s[0]=[l(`<h1 id="插件化开发" tabindex="-1">插件化开发 <a class="header-anchor" href="#插件化开发" aria-label="Permalink to &quot;插件化开发&quot;">​</a></h1><p>Vue-Bag-Admin 把应用拆成两块,宿主应用负责跑起来,业务插件负责放页面和配置。相对独立的业务域,放进 <code>comps-bag-*</code> 包里会更好维护。</p><p>如果你准备把插件单独发到 npm,这篇可以直接当起点。仓库里的插件包已经按发布形态收过一轮,新插件照这个结构走,后面少补很多边角。</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><li>多语言词条</li><li>初始化安装钩子</li><li>权限点声明</li><li>插件配置项声明</li><li>扩展点贡献</li><li>插件元信息(启用状态、顺序、依赖)</li></ul><h2 id="adminplugin-结构" tabindex="-1">AdminPlugin 结构 <a class="header-anchor" href="#adminplugin-结构" aria-label="Permalink to &quot;AdminPlugin 结构&quot;">​</a></h2><p>当前插件接口除了基础的 <code>id</code>、<code>name</code>、<code>version</code> 外,还支持这些元信息:</p><ul><li><code>enabled</code>:是否默认启用,设为 <code>false</code> 时不会注册</li><li><code>order</code>:插件排序,值越小越先注册</li><li><code>dependsOn</code>:依赖的插件 <code>id</code> 列表</li><li><code>routes</code>:插件提供的路由</li><li><code>menus</code>:插件提供的菜单</li><li><code>locales</code>:插件提供的语言包</li><li><code>permissions</code>:插件声明的权限点,宿主可以汇总到角色/权限后台</li><li><code>settings</code>:插件声明的配置项,宿主可以生成插件配置页</li><li><code>contributes</code>:插件贡献到宿主扩展点的能力,例如快捷入口、工具栏动作、卡片等</li><li><code>compatibility</code>:插件声明的宿主或 Vue 版本兼容范围</li><li><code>install</code>:安装钩子,能拿到 <code>app</code>、<code>router</code> 和当前已启用插件列表</li><li><code>dispose</code>:卸载或应用重引导前的清理钩子</li></ul><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { AdminPlugin } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;@melt-js/libs-bag-core&#39;</span></span>
2
+ <span class="line"></span>
3
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> plugin</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> AdminPlugin</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
4
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> id: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;plugin-report&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
5
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> name: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;报表插件&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
6
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> version: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;1.0.0&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
7
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> enabled: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
8
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> order: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">20</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
9
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> dependsOn: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;plugin-shop&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
10
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> routes: [],</span></span>
11
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> menus: [],</span></span>
12
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> locales: {},</span></span>
13
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> permissions: [</span></span>
14
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { code: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.read&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;查看报表&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, group: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;报表&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
15
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { code: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.export&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;导出报表&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, group: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;报表&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
16
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
17
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> settings: [</span></span>
18
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
19
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> field: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;defaultRange&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
20
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> label: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;默认统计周期&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
21
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> component: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;select&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
22
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> defaultValue: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;7d&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
23
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> options: [</span></span>
24
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { label: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;最近 7 天&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, value: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;7d&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
25
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { label: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;最近 30 天&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, value: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;30d&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
26
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ]</span></span>
27
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
28
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
29
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> contributes: {</span></span>
30
+ <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;dashboard.widgets&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [{ key: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report-summary&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;报表摘要&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }]</span></span>
31
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
32
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> install</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">context</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) {</span></span>
33
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(context.enabledPluginIds)</span></span>
34
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
35
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> dispose</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">context</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) {</span></span>
36
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;dispose&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, context.enabledPluginIds)</span></span>
37
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
38
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
39
+ <span class="line"></span>
40
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> plugin</span></span></code></pre></div><p>插件上下文中的 <code>enabledPluginIds</code> 表示当前已经通过启用过滤、依赖校验和冲突校验的插件列表,适合用来做跨插件联动。</p><h2 id="插件启停状态" tabindex="-1">插件启停状态 <a class="header-anchor" href="#插件启停状态" aria-label="Permalink to &quot;插件启停状态&quot;">​</a></h2><p>默认情况下,插件启停状态会写入浏览器本地存储,够本地开发和演示使用。生产环境如果要按租户、角色、环境或后端配置控制插件启停,可以在宿主启动时注入 <code>pluginStateProvider</code>:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> bootstrapPlugins</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
41
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> app,</span></span>
42
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> router,</span></span>
43
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> plugins,</span></span>
44
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> pluginStateProvider: {</span></span>
45
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> getDisabledPluginIds</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> tenantConfig.disabledPluginIds,</span></span>
46
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> setPluginEnabledState</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">pluginId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">enabled</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
47
+ <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 可以在这里调用后端接口,或者写入你的配置中心。</span></span>
48
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(pluginId, enabled)</span></span>
49
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
50
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
51
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p>这样插件管理页、菜单和路由注册会读同一份状态。</p><h2 id="路由与菜单协议" tabindex="-1">路由与菜单协议 <a class="header-anchor" href="#路由与菜单协议" aria-label="Permalink to &quot;路由与菜单协议&quot;">​</a></h2><p>插件里的 <code>routes</code> 和 <code>menus</code> 都有一层框架约定,定义时尽量一次写清楚。</p><h3 id="路由-meta" tabindex="-1">路由 meta <a class="header-anchor" href="#路由-meta" aria-label="Permalink to &quot;路由 meta&quot;">​</a></h3><p>插件路由使用的是 <code>AdminRouteRecordRaw</code>,常用的 <code>meta</code> 字段包括:</p><ul><li><code>title</code>:页面标题,通常写 i18n key</li><li><code>layout</code>:布局名,默认后台页一般使用 <code>default</code></li><li><code>roles</code>:角色访问控制</li><li><code>permissions</code>:权限点访问控制</li><li><code>public</code>:是否是公开页面</li><li><code>hidden</code>:是否从菜单和标签页等导航区域隐藏</li><li><code>activeMenu</code>:当前页高亮归属的菜单路径,详情页和编辑页建议显式声明</li><li><code>noCache</code>:是否跳过标签缓存</li></ul><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
52
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> path</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/report/detail/:id&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
53
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;ReportDetail&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
54
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> component</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;./views/ReportDetail.vue&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
55
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
56
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> title</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.detail&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
57
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> layout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;default&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
58
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> roles</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;authenticated&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
59
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> permissions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.read&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
60
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> activeMenu</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/report/list&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
61
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> noCache</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
62
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> hidden</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span></span>
63
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
64
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><h3 id="菜单配置" tabindex="-1">菜单配置 <a class="header-anchor" href="#菜单配置" aria-label="Permalink to &quot;菜单配置&quot;">​</a></h3><p>插件菜单使用 <code>MenuConfig</code>,除了基础的 <code>path</code>、<code>title</code>、<code>icon</code>、<code>sort</code> 外,还支持:</p><ul><li><code>hidden</code>:隐藏菜单项,但仍可保留路由能力</li><li><code>badge</code>:菜单徽标,例如 <code>New</code>、<code>HOT</code> 或数字</li><li><code>roles</code>:角色级菜单可见性</li><li><code>permissions</code>:权限点级菜单可见性</li><li><code>children</code>:子菜单配置</li></ul><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">menus</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span></span>
65
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
66
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/report&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
67
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.title&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
68
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> icon: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;chart&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
69
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> sort: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">80</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
70
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> children: [</span></span>
71
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
72
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/report/list&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
73
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.list&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
74
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> permissions: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.read&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span>
75
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
76
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ]</span></span>
77
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
78
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span></code></pre></div><h2 id="创建一个新插件" tabindex="-1">创建一个新插件 <a class="header-anchor" href="#创建一个新插件" aria-label="Permalink to &quot;创建一个新插件&quot;">​</a></h2><h3 id="_1-新建目录" tabindex="-1">1. 新建目录 <a class="header-anchor" href="#_1-新建目录" aria-label="Permalink to &quot;1. 新建目录&quot;">​</a></h3><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">mkdir</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> comps-bag-report/src/views</span></span></code></pre></div><h3 id="_2-配置-package-json" tabindex="-1">2. 配置 package.json <a class="header-anchor" href="#_2-配置-package-json" aria-label="Permalink to &quot;2. 配置 package.json&quot;">​</a></h3><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
79
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;name&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;@melt-js/comps-bag-report&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
80
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;version&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;0.1.0&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
81
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;type&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;module&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
82
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;files&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;dist&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
83
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;main&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;./dist/index.js&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
84
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;module&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;./dist/index.js&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
85
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;types&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;./dist/index.d.ts&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
86
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;exports&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
87
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;.&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
88
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;types&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;./dist/index.d.ts&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
89
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;import&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;./dist/index.js&quot;</span></span>
90
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
91
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
92
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;sideEffects&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
93
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;peerDependencies&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
94
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;vue&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^3.5.0&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
95
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;vue-router&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^4.4.0&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
96
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;@melt-js/libs-bag-core&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^0.1.0&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
97
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;@melt-js/libs-bag-ui&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^0.1.0&quot;</span></span>
98
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
99
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;devDependencies&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
100
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;vue&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^3.5.0&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
101
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;vue-router&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^4.4.0&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
102
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;@melt-js/libs-bag-core&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;workspace:*&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
103
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;@melt-js/libs-bag-ui&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;workspace:*&quot;</span></span>
104
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
105
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;scripts&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
106
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;build&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;vite build &amp;&amp; vue-tsc --project tsconfig.build.json --emitDeclarationOnly&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
107
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;check-types&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;vue-tsc --noEmit&quot;</span></span>
108
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
109
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>这份配置按“真的要发到 npm”来写:</p><ul><li><code>peerDependencies</code> 声明宿主必须提供的运行时依赖,避免外部项目装出多份 <code>vue</code></li><li><code>devDependencies</code> 里的 <code>workspace:*</code> 方便你在 Monorepo 内联调</li><li><code>main / module / types / exports</code> 都指向 <code>dist</code>,而不是源码目录</li></ul><p>临时试验可以先简单点。只要准备长期维护,最好一开始就按这套口径写。</p><h3 id="_3-编写页面" tabindex="-1">3. 编写页面 <a class="header-anchor" href="#_3-编写页面" aria-label="Permalink to &quot;3. 编写页面&quot;">​</a></h3><div class="language-vue vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">vue</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
110
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">div</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;p-6 bg-white rounded shadow-sm&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
111
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">h2</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;text-2xl font-bold mb-4&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;报表中心&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">h2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
112
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;这是一个通过插件接入的业务页面。&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
113
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
114
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span></code></pre></div><h3 id="_4-导出插件定义" tabindex="-1">4. 导出插件定义 <a class="header-anchor" href="#_4-导出插件定义" aria-label="Permalink to &quot;4. 导出插件定义&quot;">​</a></h3><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { AdminPlugin } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;@melt-js/libs-bag-core&#39;</span></span>
115
+ <span class="line"></span>
116
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> plugin</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> AdminPlugin</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
117
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> id: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;plugin-report&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
118
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> name: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;报表插件&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
119
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> version: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;1.0.0&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
120
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> order: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">20</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
121
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> routes: [</span></span>
122
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
123
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/report/list&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
124
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> name: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;ReportList&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
125
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> component</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;./views/ReportList.vue&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
126
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> meta: {</span></span>
127
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.list&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
128
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> layout: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;default&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
129
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> roles: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;authenticated&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
130
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> permissions: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.read&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span>
131
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
132
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
133
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
134
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/report/detail/:id&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
135
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> name: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;ReportDetail&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
136
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> component</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;./views/ReportDetail.vue&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
137
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> meta: {</span></span>
138
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.detail&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
139
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> layout: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;default&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
140
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> roles: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;authenticated&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
141
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> permissions: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.read&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
142
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> activeMenu: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/report/list&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
143
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> noCache: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
144
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> hidden: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span></span>
145
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
146
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
147
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
148
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> menus: [</span></span>
149
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
150
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/report&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
151
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.title&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
152
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> icon: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;chart&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
153
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> sort: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">80</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
154
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> roles: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;authenticated&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
155
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> children: [</span></span>
156
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
157
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;/report/list&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
158
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.list&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
159
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> permissions: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;report.read&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span>
160
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
161
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ]</span></span>
162
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
163
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
164
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> locales: {</span></span>
165
+ <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;zh-CN&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
166
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> report: {</span></span>
167
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;报表管理&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
168
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> list: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;报表中心&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
169
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> detail: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;报表详情&#39;</span></span>
170
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
171
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
172
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> en: {</span></span>
173
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> report: {</span></span>
174
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> title: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;Reports&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
175
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> list: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;Reports&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
176
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> detail: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;Report Detail&#39;</span></span>
177
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
178
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
179
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
180
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
181
+ <span class="line"></span>
182
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> plugin</span></span></code></pre></div><h3 id="_5-增加构建配置" tabindex="-1">5. 增加构建配置 <a class="header-anchor" href="#_5-增加构建配置" aria-label="Permalink to &quot;5. 增加构建配置&quot;">​</a></h3><p>如果你的插件准备发布到 npm,至少再补两份文件:</p><p><code>tsconfig.build.json</code></p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
183
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;extends&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;./tsconfig.json&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
184
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;compilerOptions&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
185
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;declaration&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
186
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;emitDeclarationOnly&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
187
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;outDir&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;dist&quot;</span></span>
188
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
189
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;include&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;src/**/*.ts&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;src/**/*.vue&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span>
190
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p><code>vite.config.mjs</code></p><div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { defineConfig } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;vite&#39;</span></span>
191
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> vue </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;@vitejs/plugin-vue&#39;</span></span>
192
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { resolve } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;node:path&#39;</span></span>
193
+ <span class="line"></span>
194
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> defineConfig</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
195
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> plugins: [</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">vue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()],</span></span>
196
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> build: {</span></span>
197
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> lib: {</span></span>
198
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> entry: </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(process.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">cwd</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(), </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;src/index.ts&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
199
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> formats: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;es&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
200
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> fileName</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;index.js&#39;</span></span>
201
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
202
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> rollupOptions: {</span></span>
203
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> external: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;vue&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;vue-router&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;@melt-js/libs-bag-core&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;@melt-js/libs-bag-ui&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span>
204
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
205
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
206
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p>这样处理后,插件包的使用者拿到的是明确的 <code>dist</code> 产物,宿主消费起来也更稳。</p><h2 id="在宿主中挂载" tabindex="-1">在宿主中挂载 <a class="header-anchor" href="#在宿主中挂载" aria-label="Permalink to &quot;在宿主中挂载&quot;">​</a></h2><p>在宿主应用里引入并传给 <code>bootstrapPlugins()</code>。如果你是从脚手架生成项目,一般会在 <code>src/main.ts</code> 里做这一步:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { createApp } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;vue&#39;</span></span>
207
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> reportPlugin </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;@melt-js/comps-bag-report&#39;</span></span>
208
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { bootstrapPlugins } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;@melt-js/libs-bag-host&#39;</span></span>
209
+ <span class="line"></span>
210
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> app</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> createApp</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(App)</span></span>
211
+ <span class="line"></span>
212
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> bootstrapPlugins</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
213
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> app,</span></span>
214
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> router,</span></span>
215
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> i18n,</span></span>
216
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> plugins: [reportPlugin]</span></span>
217
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p>如果你是在当前仓库里联调,也可以参考示例宿主 <code>tpl-vue3-admin-naive-bag/src/main.ts</code> 的接入方式。</p><h2 id="插件注册规则" tabindex="-1">插件注册规则 <a class="header-anchor" href="#插件注册规则" aria-label="Permalink to &quot;插件注册规则&quot;">​</a></h2><p>宿主注册插件时会走这几步:</p><ol><li>按 <code>order</code> 升序排序</li><li>基于 <code>enabled</code> 与本地禁用列表过滤插件</li><li>校验 <code>dependsOn</code> 依赖是否已在启用插件集合中</li><li>对启用插件集合执行冲突校验</li><li>注册路由、菜单和多语言</li><li>执行插件 <code>install</code> 钩子</li></ol><h2 id="冲突检测" tabindex="-1">冲突检测 <a class="header-anchor" href="#冲突检测" aria-label="Permalink to &quot;冲突检测&quot;">​</a></h2><p>当前内置了这些冲突保护,校验范围覆盖“已启用的插件集合”和宿主已经注册的基础路由:</p><ul><li>重复插件 <code>id</code></li><li>重复路由 <code>path</code></li><li>重复路由 <code>name</code></li><li>重复菜单 <code>path</code></li></ul><p>一旦冲突,启动阶段会直接抛错,不会静默覆盖。</p><h2 id="插件启用与禁用" tabindex="-1">插件启用与禁用 <a class="header-anchor" href="#插件启用与禁用" aria-label="Permalink to &quot;插件启用与禁用&quot;">​</a></h2><p>插件是否启用由两层共同决定:</p><ul><li>插件自身的 <code>enabled</code></li><li>宿主本地存储中的禁用插件列表</li></ul><p>也就是说,你可以保留插件代码,只在运行时禁用它,不必删除依赖。</p><p>当前禁用状态持久化在浏览器本地存储中,默认存储键为 <code>bag.admin.disabledPlugins</code>。如果你要做插件管理页,可以直接围绕这个运行时语义去设计启停逻辑。</p><p>这里容易踩坑:插件启停会影响路由、菜单和依赖关系。切换状态后,通常要重新执行一次应用引导流程,或者刷新后再生效。</p><p>如果你要在界面层做插件管理中心,可以直接复用 <code>@melt-js/libs-bag-entry</code> 暴露的运行时 API:</p><blockquote><p>本地联调时要留心:插件页面、宿主入口和 <code>bootstrapPlugins()</code> 必须从同一个运行时入口拿 API。业务项目最好都从 <code>@melt-js/libs-bag-entry</code> 导入;如果 Vite alias 把插件源码接进来了,也要把 <code>@melt-js/libs-bag-entry</code> alias 到当前仓库的 <code>libs-bag-entry/src/index.ts</code>,不然会出现插件已经注册、管理页却读到空列表的情况。</p></blockquote><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { listRuntimePlugins, setPluginEnabledState } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;@melt-js/libs-bag-entry&#39;</span></span>
218
+ <span class="line"></span>
219
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> plugins</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> listRuntimePlugins</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span>
220
+ <span class="line"></span>
221
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">setPluginEnabledState</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;plugin-shop&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
222
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">window.location.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">reload</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span></code></pre></div><p><code>listRuntimePlugins()</code> 会返回当前宿主注册过的插件清单,以及启用状态、依赖信息、路由数量、菜单数量、权限点数量、设置项数量和贡献点数量,管理页可以直接拿来渲染。</p><h2 id="开发态与发布态的区别" tabindex="-1">开发态与发布态的区别 <a class="header-anchor" href="#开发态与发布态的区别" aria-label="Permalink to &quot;开发态与发布态的区别&quot;">​</a></h2><p>刚拆包时这里很容易弄混,单独说一下:</p><ul><li>开发态:更关心 Monorepo 内联调,允许使用 <code>workspace:*</code></li><li>发布态:更关心外部项目消费,入口应统一指向 <code>dist</code></li></ul><p>更稳的做法是:</p><ul><li>在仓库里开发时,通过 workspace 依赖做源码联调</li><li>对外发布时,让 npm 消费者只接触 <code>dist</code>、<code>exports</code> 和 <code>peerDependencies</code></li></ul><p>这样既方便仓库内部开发,也不会把工作区里的临时写法带到公开包里。</p><h2 id="install-钩子" tabindex="-1">install 钩子 <a class="header-anchor" href="#install-钩子" aria-label="Permalink to &quot;install 钩子&quot;">​</a></h2><p><code>install(app, context)</code> 适合做以下事情:</p><ul><li>注册全局组件</li><li>注册布局</li><li>注入全局配置</li><li>根据 <code>enabledPluginIds</code> 做跨插件联动</li></ul><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">install</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(app, context) {</span></span>
223
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (context.enabledPluginIds.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">includes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;plugin-shop&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)) {</span></span>
224
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;商城插件已启用&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
225
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
226
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div>`,75)])])}const o=i(p,[["render",h]]);export{g as __pageData,o as default};