@melt-js/libs-vben 0.0.0-stage → 2026.11.8-2

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 (174) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +86 -2
  3. package/package.json +98 -3
  4. package/src/access/access-control.vue +47 -0
  5. package/src/access/accessible.ts +236 -0
  6. package/src/access/directive.ts +42 -0
  7. package/src/access/index.ts +4 -0
  8. package/src/access/use-access.ts +53 -0
  9. package/src/composables/index.ts +14 -0
  10. package/src/composables/use-is-mobile.ts +7 -0
  11. package/src/composables/use-layout-style.ts +51 -0
  12. package/src/composables/use-layout-viewport-height.ts +71 -0
  13. package/src/composables/use-namespace.ts +106 -0
  14. package/src/composables/use-priority-value.ts +96 -0
  15. package/src/composables/use-scroll-lock.ts +151 -0
  16. package/src/composables/use-simple-locale/README.md +3 -0
  17. package/src/composables/use-simple-locale/index.ts +27 -0
  18. package/src/composables/use-simple-locale/messages.ts +31 -0
  19. package/src/composables/use-sortable.ts +32 -0
  20. package/src/constants/core.ts +76 -0
  21. package/src/constants/index.ts +2 -0
  22. package/src/core-icons/create-icon.ts +14 -0
  23. package/src/core-icons/index.ts +11 -0
  24. package/src/core-icons/lucide.ts +95 -0
  25. package/src/core-preferences/config.ts +172 -0
  26. package/src/core-preferences/constants.ts +116 -0
  27. package/src/core-preferences/index.ts +24 -0
  28. package/src/core-preferences/preferences.ts +503 -0
  29. package/src/core-preferences/types.ts +471 -0
  30. package/src/core-preferences/update-css-variables.ts +129 -0
  31. package/src/core-preferences/use-preferences.ts +282 -0
  32. package/src/design/css/global.css +1 -0
  33. package/src/design/css/nprogress.css +61 -0
  34. package/src/design/css/transition.css +238 -0
  35. package/src/design/css/ui.css +101 -0
  36. package/src/design/design-tokens/dark.css +446 -0
  37. package/src/design/design-tokens/default.css +491 -0
  38. package/src/design/design-tokens/index.ts +2 -0
  39. package/src/design/index.ts +6 -0
  40. package/src/design/scss-bem/bem.scss +34 -0
  41. package/src/design/scss-bem/constants.scss +5 -0
  42. package/src/hooks/index.ts +9 -0
  43. package/src/hooks/use-app-config.ts +39 -0
  44. package/src/hooks/use-content-maximize.ts +24 -0
  45. package/src/hooks/use-design-tokens.ts +421 -0
  46. package/src/hooks/use-hover-toggle.ts +163 -0
  47. package/src/hooks/use-pagination.ts +72 -0
  48. package/src/hooks/use-refresh.ts +16 -0
  49. package/src/hooks/use-tabs.ts +143 -0
  50. package/src/hooks/use-watermark.ts +84 -0
  51. package/src/icons/iconify/index.ts +5 -0
  52. package/src/icons/icons/empty-icon.vue +27 -0
  53. package/src/icons/index.ts +3 -0
  54. package/src/icons/svg/icons/antdv-logo.svg +29 -0
  55. package/src/icons/svg/icons/antdv-next-logo.svg +1 -0
  56. package/src/icons/svg/icons/avatar-1.svg +1 -0
  57. package/src/icons/svg/icons/avatar-2.svg +1 -0
  58. package/src/icons/svg/icons/avatar-3.svg +1 -0
  59. package/src/icons/svg/icons/avatar-4.svg +1 -0
  60. package/src/icons/svg/icons/bell.svg +1 -0
  61. package/src/icons/svg/icons/cake.svg +1 -0
  62. package/src/icons/svg/icons/card.svg +1 -0
  63. package/src/icons/svg/icons/dingding.svg +1 -0
  64. package/src/icons/svg/icons/download.svg +1 -0
  65. package/src/icons/svg/icons/github.svg +1 -0
  66. package/src/icons/svg/icons/google.svg +1 -0
  67. package/src/icons/svg/icons/qqchat.svg +1 -0
  68. package/src/icons/svg/icons/tdesign-logo.svg +39 -0
  69. package/src/icons/svg/icons/wechat.svg +1 -0
  70. package/src/icons/svg/index.ts +39 -0
  71. package/src/icons/svg/load.ts +81 -0
  72. package/src/locales/i18n.ts +147 -0
  73. package/src/locales/index.ts +30 -0
  74. package/src/locales/langs/en-US/authentication.json +65 -0
  75. package/src/locales/langs/en-US/common.json +27 -0
  76. package/src/locales/langs/en-US/preferences.json +245 -0
  77. package/src/locales/langs/en-US/profile.json +4 -0
  78. package/src/locales/langs/en-US/ui.json +165 -0
  79. package/src/locales/langs/zh-CN/authentication.json +65 -0
  80. package/src/locales/langs/zh-CN/common.json +27 -0
  81. package/src/locales/langs/zh-CN/preferences.json +245 -0
  82. package/src/locales/langs/zh-CN/profile.json +4 -0
  83. package/src/locales/langs/zh-CN/ui.json +165 -0
  84. package/src/locales/langs/zh-TW/authentication.json +65 -0
  85. package/src/locales/langs/zh-TW/common.json +27 -0
  86. package/src/locales/langs/zh-TW/preferences.json +245 -0
  87. package/src/locales/langs/zh-TW/profile.json +4 -0
  88. package/src/locales/langs/zh-TW/ui.json +165 -0
  89. package/src/locales/typing.ts +27 -0
  90. package/src/preferences/index.ts +42 -0
  91. package/src/request/index.ts +2 -0
  92. package/src/request/request-client/index.ts +3 -0
  93. package/src/request/request-client/modules/downloader.ts +60 -0
  94. package/src/request/request-client/modules/interceptor.ts +40 -0
  95. package/src/request/request-client/modules/sse.ts +134 -0
  96. package/src/request/request-client/modules/uploader.ts +79 -0
  97. package/src/request/request-client/preset-interceptors.ts +178 -0
  98. package/src/request/request-client/request-client.ts +163 -0
  99. package/src/request/request-client/types.ts +90 -0
  100. package/src/shared/cache/README.md +418 -0
  101. package/src/shared/cache/index.ts +5 -0
  102. package/src/shared/cache/indexeddb-driver.ts +137 -0
  103. package/src/shared/cache/local-storage-driver.ts +70 -0
  104. package/src/shared/cache/memory-storage-driver.ts +32 -0
  105. package/src/shared/cache/storage-manager.ts +146 -0
  106. package/src/shared/cache/types.ts +39 -0
  107. package/src/shared/color/color.ts +9 -0
  108. package/src/shared/color/convert.ts +62 -0
  109. package/src/shared/color/generator.ts +45 -0
  110. package/src/shared/color/index.ts +3 -0
  111. package/src/shared/constants/globals.ts +20 -0
  112. package/src/shared/constants/index.ts +2 -0
  113. package/src/shared/constants/vben.ts +30 -0
  114. package/src/shared/global-state.ts +45 -0
  115. package/src/shared/store.ts +1 -0
  116. package/src/shared/utils/cn.ts +10 -0
  117. package/src/shared/utils/date.ts +80 -0
  118. package/src/shared/utils/diff.ts +114 -0
  119. package/src/shared/utils/dom.ts +126 -0
  120. package/src/shared/utils/download.ts +186 -0
  121. package/src/shared/utils/error.ts +19 -0
  122. package/src/shared/utils/index.ts +22 -0
  123. package/src/shared/utils/inference.ts +164 -0
  124. package/src/shared/utils/letter.ts +47 -0
  125. package/src/shared/utils/merge.ts +11 -0
  126. package/src/shared/utils/nprogress.ts +52 -0
  127. package/src/shared/utils/resources.ts +21 -0
  128. package/src/shared/utils/stack.ts +103 -0
  129. package/src/shared/utils/state-handler.ts +61 -0
  130. package/src/shared/utils/to.ts +21 -0
  131. package/src/shared/utils/tree.ts +159 -0
  132. package/src/shared/utils/unique.ts +15 -0
  133. package/src/shared/utils/update-css-variables.ts +47 -0
  134. package/src/shared/utils/util.ts +50 -0
  135. package/src/shared/utils/window.ts +37 -0
  136. package/src/stores/index.ts +3 -0
  137. package/src/stores/modules/access.ts +130 -0
  138. package/src/stores/modules/dict.ts +244 -0
  139. package/src/stores/modules/index.ts +5 -0
  140. package/src/stores/modules/tabbar.ts +797 -0
  141. package/src/stores/modules/timezone.ts +132 -0
  142. package/src/stores/modules/user.ts +42 -0
  143. package/src/stores/setup.ts +79 -0
  144. package/src/stores/shim-pinia.d.ts +11 -0
  145. package/src/styles/antd/index.css +78 -0
  146. package/src/styles/antdv-next/index.css +90 -0
  147. package/src/styles/ele/index.css +44 -0
  148. package/src/styles/global/index.scss +1 -0
  149. package/src/styles/index.ts +1 -0
  150. package/src/styles/naive/index.css +26 -0
  151. package/src/styles/style-exports.d.ts +3 -0
  152. package/src/theme.css +592 -0
  153. package/src/types/global.d.ts +28 -0
  154. package/src/types/index.ts +2 -0
  155. package/src/types/shims.d.ts +1 -0
  156. package/src/types/user.ts +20 -0
  157. package/src/typings/app.d.ts +128 -0
  158. package/src/typings/basic.d.ts +42 -0
  159. package/src/typings/helper.d.ts +147 -0
  160. package/src/typings/index.ts +16 -0
  161. package/src/typings/languages.ts +28 -0
  162. package/src/typings/menu-record.ts +82 -0
  163. package/src/typings/tabs.ts +8 -0
  164. package/src/typings/vue-router.d.ts +169 -0
  165. package/src/utils/helpers/find-menu-by-path.ts +37 -0
  166. package/src/utils/helpers/generate-menus.ts +89 -0
  167. package/src/utils/helpers/generate-routes-backend.ts +108 -0
  168. package/src/utils/helpers/generate-routes-frontend.ts +58 -0
  169. package/src/utils/helpers/get-popup-container.ts +10 -0
  170. package/src/utils/helpers/index.ts +8 -0
  171. package/src/utils/helpers/merge-route-modules.ts +28 -0
  172. package/src/utils/helpers/reset-routes.ts +30 -0
  173. package/src/utils/helpers/unmount-global-loading.ts +31 -0
  174. package/src/utils/index.ts +4 -0
package/LICENSE ADDED
@@ -0,0 +1,10 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present, Vben
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10
+
package/README.md CHANGED
@@ -1,3 +1,87 @@
1
- # Temporary Holding Version
1
+ # @melt-js/libs-vben
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 由 [vue-vben-admin](https://github.com/vbenjs/vue-vben-admin) 5.8 的 `packages/` 合并而来的基础库包:把原来 16 个 `@vben/*` / `@vben-core/*` 基础包收进一个包,用子路径导出保持原模块边界。
4
+
5
+ 组件层(`@core/ui-kit/*`、`common-ui`、`layouts`、`plugins`)拆到 [`comps-vben`](../comps-vben),模板层来自 `apps/web-ele`,见 [`tpl-vue3-admin-ele-vben`](../tpl-vue3-admin-ele-vben)。
6
+
7
+ ## 合并范围
8
+
9
+ | 子路径 | 上游包 | 内容 |
10
+ | --- | --- | --- |
11
+ | `design` | `@vben-core/design` | 设计变量、Tailwind 主题、BEM、全局样式 |
12
+ | `shared/*` | `@vben-core/shared` | `utils` / `constants` / `color` / `cache` / `store` / `global-state` |
13
+ | `typings` | `@vben-core/typings` | 全局类型、`RouteMeta` 增强 |
14
+ | `core-icons` | `@vben-core/icons` | Iconify / Lucide 基础图标 |
15
+ | `composables` | `@vben-core/composables` | 组合式函数 |
16
+ | `core-preferences` | `@vben-core/preferences` | 偏好设置内核 |
17
+ | `constants` | `@vben/constants` | 登录路径、默认首页等常量 |
18
+ | `access` | `@vben/access` | 权限指令、动态路由 |
19
+ | `hooks` | `@vben/hooks` | 业务 hooks(水印、设计变量等) |
20
+ | `locales` | `@vben/locales` | i18n 初始化与语言包加载 |
21
+ | `preferences` | `@vben/preferences` | 偏好设置门面 |
22
+ | `request` | `@vben/request` | axios / fetch 请求客户端与拦截器 |
23
+ | `stores` | `@vben/stores` | pinia store(access / user / tab) |
24
+ | `styles` | `@vben/styles` | 全局样式与 Element Plus 适配 |
25
+ | `types` | `@vben/types` | 业务类型 |
26
+ | `utils` | `@vben/utils` | 工具函数 |
27
+ | `icons` | `@vben/icons` | 项目图标集合 |
28
+
29
+ ## 使用
30
+
31
+ ```ts
32
+ import { preferences } from '@melt-js/libs-vben/preferences';
33
+ import { useAccessStore } from '@melt-js/libs-vben/stores';
34
+ import { resetStaticRoutes } from '@melt-js/libs-vben/utils';
35
+ import '@melt-js/libs-vben/styles';
36
+ import '@melt-js/libs-vben/styles/ele';
37
+ ```
38
+
39
+ 样式子路径直接指向 CSS/SCSS 源码,由使用方的 Vite / Sass 编译:
40
+
41
+ - `@melt-js/libs-vben/styles`
42
+ - `@melt-js/libs-vben/styles/ele`
43
+ - `@melt-js/libs-vben/styles/global`
44
+ - `@melt-js/libs-vben/design/bem`
45
+
46
+ ## 边界
47
+
48
+ - 本包只放非组件层:不包含 `shadcn-ui`、`form-ui`、`common-ui` 等 UI 组件。
49
+ - `comps-vben` 依赖本包,本包不反向依赖 `comps-vben`,避免循环。
50
+ - 源码按子目录保存,`package.json` 的 `exports` 与上游包名一一对应;内部相互引用已改为 `@melt-js/libs-vben/*`。
51
+ - 依赖版本以 `vue-vben-admin` 5.8 的 `pnpm-lock.yaml` catalog 为准;`vue` / `vue-router` / `pinia` / `vue-i18n` 以 peerDependency 形式声明。
52
+
53
+ ## 目录
54
+
55
+ ```text
56
+ src/
57
+ access/ # @vben/access
58
+ composables/ # @vben-core/composables
59
+ constants/ # @vben/constants
60
+ core-icons/ # @vben-core/icons
61
+ core-preferences/ # @vben-core/preferences
62
+ design/ # @vben-core/design
63
+ hooks/ # @vben/hooks
64
+ icons/ # @vben/icons
65
+ locales/ # @vben/locales
66
+ preferences/ # @vben/preferences
67
+ request/ # @vben/request
68
+ shared/ # @vben-core/shared
69
+ stores/ # @vben/stores
70
+ styles/ # @vben/styles
71
+ theme.css # 上游 internal/tailwind-config 的主题
72
+ types/ # @vben/types
73
+ typings/ # @vben-core/typings
74
+ utils/ # @vben/utils
75
+ ```
76
+
77
+
78
+ ## 发布
79
+
80
+ 本包是源码包(与 `libs-fan` / `comps-vue3-fan` 一样),`files` 发布 `src`。
81
+
82
+ ```bash
83
+ pnpm publish:stage
84
+ ```
85
+
86
+ 脚本会先用 `pnpm pack` 把 `workspace:*` 重写成当前版本,再提交 npm stage;
87
+ 批准后可由 unpkg 提供源码文件(需要消费方自行用 Vite / bundler 编译)。
package/package.json CHANGED
@@ -1,6 +1,101 @@
1
1
  {
2
2
  "name": "@melt-js/libs-vben",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "appGroupName": "libs-vben",
4
+ "version": "2026.11.8-2",
5
+ "type": "module",
6
+ "description": "Vben Admin 5.8 基础库合集:design / shared / typings / icons / composables / preferences / access / hooks / locales / stores / styles / types / utils 合并为一个包",
7
+ "license": "MIT",
8
+ "exports": {
9
+ "./access": "./src/access/index.ts",
10
+ "./composables": "./src/composables/index.ts",
11
+ "./constants": "./src/constants/index.ts",
12
+ "./core-icons": "./src/core-icons/index.ts",
13
+ "./core-preferences": "./src/core-preferences/index.ts",
14
+ "./design": "./src/design/index.ts",
15
+ "./design/bem": "./src/design/scss-bem/bem.scss",
16
+ "./design/theme": "./src/design/css/global.css",
17
+ "./hooks": "./src/hooks/index.ts",
18
+ "./icons": "./src/icons/index.ts",
19
+ "./locales": "./src/locales/index.ts",
20
+ "./preferences": "./src/preferences/index.ts",
21
+ "./request": "./src/request/index.ts",
22
+ "./shared/cache": "./src/shared/cache/index.ts",
23
+ "./shared/color": "./src/shared/color/index.ts",
24
+ "./shared/constants": "./src/shared/constants/index.ts",
25
+ "./shared/global-state": "./src/shared/global-state.ts",
26
+ "./shared/store": "./src/shared/store.ts",
27
+ "./shared/utils": "./src/shared/utils/index.ts",
28
+ "./stores": "./src/stores/index.ts",
29
+ "./styles": "./src/styles/index.ts",
30
+ "./styles/antd": "./src/styles/antd/index.css",
31
+ "./styles/antdv-next": "./src/styles/antdv-next/index.css",
32
+ "./styles/ele": "./src/styles/ele/index.css",
33
+ "./styles/global": "./src/styles/global/index.scss",
34
+ "./styles/naive": "./src/styles/naive/index.css",
35
+ "./theme": "./src/theme.css",
36
+ "./typings": "./src/typings/index.ts",
37
+ "./types": "./src/types/index.ts",
38
+ "./types/global": "./src/types/global.d.ts",
39
+ "./utils": "./src/utils/index.ts"
40
+ },
41
+ "files": [
42
+ "src",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "registry": "https://registry.npmjs.org",
49
+ "tag": "latest"
50
+ },
51
+ "dependencies": {
52
+ "@ctrl/tinycolor": "4.2.1",
53
+ "@tanstack/vue-store": "0.11.2",
54
+ "@vue/shared": "3.5.44",
55
+ "clsx": "2.1.1",
56
+ "dayjs": "1.11.23",
57
+ "defu": "6.1.7",
58
+ "es-toolkit": "1.51.0",
59
+ "lodash.clonedeep": "4.5.0",
60
+ "nprogress": "0.2.0",
61
+ "tailwind-merge": "3.6.0",
62
+ "theme-colors": "0.1.0",
63
+ "vue": "3.5.41",
64
+ "vue-router": "5.2.0",
65
+ "@iconify/vue": "5.0.4",
66
+ "@lucide/vue": "1.60.0",
67
+ "@vueuse/core": "15.0.0",
68
+ "reka-ui": "2.10.4",
69
+ "sortablejs": "1.15.7",
70
+ "watermark-js-plus": "1.6.6",
71
+ "axios": "1.19.0",
72
+ "qs": "6.15.3",
73
+ "@intlify/core-base": "11.4.16",
74
+ "vue-i18n": "11.4.10",
75
+ "pinia": "4.0.3",
76
+ "pinia-plugin-persistedstate": "4.7.1",
77
+ "secure-ls": "2.0.0"
78
+ },
79
+ "peerDependencies": {
80
+ "@vueuse/core": ">=10.0.0",
81
+ "pinia": ">=2.0.0",
82
+ "vue": ">=3.4.0",
83
+ "vue-i18n": ">=9.0.0",
84
+ "vue-router": ">=4.0.0"
85
+ },
86
+ "peerDependenciesMeta": {
87
+ "pinia": {
88
+ "optional": true
89
+ },
90
+ "vue-i18n": {
91
+ "optional": true
92
+ },
93
+ "vue-router": {
94
+ "optional": true
95
+ }
96
+ },
97
+ "scripts": {
98
+ "typecheck": "vue-tsc --noEmit",
99
+ "publish:stage": "node scripts/publish-stage.mjs"
100
+ }
6
101
  }
@@ -0,0 +1,47 @@
1
+ <!--
2
+ Access control component for fine-grained access control.
3
+ TODO: 可以扩展更完善的功能:
4
+ 1. 支持多个权限码,只要有一个权限码满足即可 或者 多个权限码全部满足
5
+ 2. 支持多个角色,只要有一个角色满足即可 或者 多个角色全部满足
6
+ 3. 支持自定义权限码和角色的判断逻辑
7
+ -->
8
+ <script lang="ts" setup>
9
+ import { computed } from 'vue';
10
+
11
+ import { useAccess } from './use-access';
12
+
13
+ interface Props {
14
+ /**
15
+ * Specified codes is visible
16
+ * @default []
17
+ */
18
+ codes?: string[];
19
+
20
+ /**
21
+ * 通过什么方式来控制组件,如果是 role,则传入角色,如果是 code,则传入权限码
22
+ * @default 'role'
23
+ */
24
+ type?: 'code' | 'role';
25
+ }
26
+
27
+ defineOptions({
28
+ name: 'AccessControl',
29
+ });
30
+
31
+ const props = withDefaults(defineProps<Props>(), {
32
+ codes: () => [],
33
+ type: 'role',
34
+ });
35
+
36
+ const { hasAccessByCodes, hasAccessByRoles } = useAccess();
37
+
38
+ const hasAuth = computed(() => {
39
+ const { codes, type } = props;
40
+ return type === 'role' ? hasAccessByRoles(codes) : hasAccessByCodes(codes);
41
+ });
42
+ </script>
43
+
44
+ <template>
45
+ <slot v-if="!codes"></slot>
46
+ <slot v-else-if="hasAuth"></slot>
47
+ </template>
@@ -0,0 +1,236 @@
1
+ import type { Component, DefineComponent } from 'vue';
2
+
3
+ import type {
4
+ AccessModeType,
5
+ GenerateMenuAndRoutesOptions,
6
+ RouteRecordRaw,
7
+ } from '@melt-js/libs-vben/types';
8
+
9
+ import { defineComponent, h } from 'vue';
10
+
11
+ import {
12
+ cloneDeep,
13
+ generateMenus,
14
+ generateRoutesByBackend,
15
+ generateRoutesByFrontend,
16
+ isFunction,
17
+ isString,
18
+ mapTree,
19
+ } from '@melt-js/libs-vben/utils';
20
+
21
+ async function generateAccessible(
22
+ mode: AccessModeType,
23
+ options: GenerateMenuAndRoutesOptions,
24
+ ) {
25
+ const { router } = options;
26
+
27
+ options.routes = cloneDeep(options.routes);
28
+
29
+ // 生成路由
30
+ const accessibleRoutes = await generateRoutes(mode, options);
31
+
32
+ const root = router.getRoutes().find((item) => item.path === '/');
33
+
34
+ // 获取已有的路由名称列表
35
+ const names = root?.children?.map((item) => item.name) ?? [];
36
+
37
+ // 动态添加到router实例内
38
+ accessibleRoutes.forEach((route) => {
39
+ if (root && !route.meta?.noBasicLayout) {
40
+ // 为了兼容之前的版本用法,如果包含子路由,则将component移除,以免出现多层BasicLayout
41
+ // 如果你的项目已经跟进了本次修改,移除了所有自定义菜单首级的BasicLayout,可以将这段if代码删除
42
+ if (route.children && route.children.length > 0) {
43
+ delete route.component;
44
+ }
45
+ // 根据router name判断,如果路由已经存在,则不再添加
46
+ if (names?.includes(route.name)) {
47
+ // 找到已存在的路由索引并更新,不更新会造成切换用户时,一级目录未更新,homePath 在二级目录导致的404问题
48
+ const index = root.children?.findIndex(
49
+ (item) => item.name === route.name,
50
+ );
51
+ if (index !== undefined && index !== -1 && root.children) {
52
+ root.children[index] = route;
53
+ }
54
+ } else {
55
+ root.children?.push(route);
56
+ }
57
+ } else {
58
+ router.addRoute(route);
59
+ }
60
+ });
61
+
62
+ if (root) {
63
+ if (root.name) {
64
+ router.removeRoute(root.name);
65
+ }
66
+ router.addRoute(root);
67
+ }
68
+
69
+ // 生成菜单
70
+ const accessibleMenus = generateMenus(accessibleRoutes, options.router);
71
+
72
+ return { accessibleMenus, accessibleRoutes };
73
+ }
74
+
75
+ /**
76
+ * Generate routes
77
+ * @param mode
78
+ * @param options
79
+ */
80
+ async function generateRoutes(
81
+ mode: AccessModeType,
82
+ options: GenerateMenuAndRoutesOptions,
83
+ ) {
84
+ const { forbiddenComponent, roles, routes } = options;
85
+
86
+ let resultRoutes: RouteRecordRaw[] = routes;
87
+ switch (mode) {
88
+ case 'backend': {
89
+ resultRoutes = await generateRoutesByBackend(options);
90
+ break;
91
+ }
92
+ case 'frontend': {
93
+ resultRoutes = await generateRoutesByFrontend(
94
+ routes,
95
+ roles || [],
96
+ forbiddenComponent,
97
+ );
98
+ break;
99
+ }
100
+ case 'mixed': {
101
+ const [frontend_resultRoutes, backend_resultRoutes] = await Promise.all([
102
+ generateRoutesByFrontend(routes, roles || [], forbiddenComponent),
103
+ generateRoutesByBackend(options),
104
+ ]);
105
+ resultRoutes = mergeRoutesByName(
106
+ backend_resultRoutes,
107
+ frontend_resultRoutes,
108
+ );
109
+ break;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * 调整路由树,做以下处理:
115
+ * 1. 对未添加redirect的路由添加redirect
116
+ * 2. 将懒加载的组件名称修改为当前路由的名称(如果启用了keep-alive的话)
117
+ */
118
+ resultRoutes = mapTree(resultRoutes, (route, parent) => {
119
+ // 重新包装component,使用与路由名称相同的name以支持keep-alive的条件缓存。
120
+ if (
121
+ route.meta?.keepAlive &&
122
+ isFunction(route.component) &&
123
+ route.name &&
124
+ isString(route.name)
125
+ ) {
126
+ const originalComponent = route.component as () => Promise<{
127
+ default: Component | DefineComponent;
128
+ }>;
129
+ route.component = async () => {
130
+ const component = await originalComponent();
131
+ if (!component.default) return component;
132
+ return defineComponent({
133
+ name: route.name as string,
134
+ setup(props, { attrs, slots }) {
135
+ return () => h(component.default, { ...props, ...attrs }, slots);
136
+ },
137
+ });
138
+ };
139
+ }
140
+
141
+ // 如果有redirect或者没有子路由,则直接返回
142
+ if (route.redirect || !route.children || route.children.length === 0) {
143
+ return route;
144
+ }
145
+ const firstChild = route.children[0];
146
+
147
+ if (!firstChild?.path || firstChild.path.startsWith('/')) {
148
+ return route;
149
+ }
150
+
151
+ // fork 定制:如果第一个子路由是动态路由(如 :id),说明当前路由本身是一个
152
+ // “列表+详情”页面(渲染自身组件),不应自动重定向到未填充的动态参数,
153
+ // 否则地址栏会出现字面量 ":id" 或匹配失败导致 404。
154
+ // 详见对上游重构 commit f00a8812 的修复。
155
+ if (firstChild.path.startsWith(':')) {
156
+ return route;
157
+ }
158
+
159
+ // 拼接子路由的重定向绝对路径。
160
+ // - 当 parent.redirect 为字符串时,它已经是累计好的绝对路径,直接替换最后一段
161
+ // 即可正确支持任意层级的深层嵌套(如 /demos/nested/menu2/menu2-1)。
162
+ // - fork 定制:后端菜单可能传入对象形式的 redirect(如 { name }),无法 split,
163
+ // 此时回退到使用 parent.path 拼接(这类 parent 为顶级路由,path 为绝对路径)。
164
+ if (parent && parent.redirect && isString(parent.redirect)) {
165
+ const parentSplit = parent.redirect.split('/');
166
+ parentSplit.splice(-1, 2, route.path, firstChild.path);
167
+ const redirectPath = parentSplit.join('/');
168
+ route.redirect = redirectPath;
169
+ } else if (parent && parent.redirect) {
170
+ route.redirect = `${parent.path}/${route.path}/${firstChild.path}`;
171
+ } else {
172
+ route.redirect = `${route.path}/${firstChild.path}`;
173
+ }
174
+
175
+ return route;
176
+ });
177
+
178
+ return resultRoutes;
179
+ }
180
+
181
+ /**
182
+ * 根据 name 合并前后端路由
183
+ * @param baseRoutes 后端路由
184
+ * @param extraRoutes 前端路由
185
+ */
186
+ function mergeRoutesByName(
187
+ baseRoutes: RouteRecordRaw[],
188
+ extraRoutes: RouteRecordRaw[],
189
+ ): RouteRecordRaw[] {
190
+ const result: RouteRecordRaw[] = [];
191
+ const routeMap = new Map<string, RouteRecordRaw>();
192
+
193
+ for (const route of baseRoutes) {
194
+ const clone = { ...route };
195
+ result.push(clone);
196
+ if (clone.name && isString(clone.name)) {
197
+ routeMap.set(clone.name, clone);
198
+ }
199
+ }
200
+
201
+ for (const route of extraRoutes) {
202
+ if (route.name && isString(route.name) && routeMap.has(route.name)) {
203
+ const existing = routeMap.get(route.name);
204
+ if (!existing) {
205
+ continue;
206
+ }
207
+ const existingChildren = existing.children ?? [];
208
+ const routeChildren = route.children ?? [];
209
+
210
+ const merged = {
211
+ ...route,
212
+ ...existing, // keep backend as base
213
+ meta: {
214
+ ...route.meta,
215
+ ...existing.meta, // backend meta wins on conflicts
216
+ },
217
+ } as RouteRecordRaw;
218
+
219
+ if (existingChildren.length > 0 || routeChildren.length > 0) {
220
+ merged.children = mergeRoutesByName(existingChildren, routeChildren);
221
+ }
222
+
223
+ Object.assign(existing, merged);
224
+ } else {
225
+ const clone = { ...route };
226
+ result.push(clone);
227
+ if (clone.name && isString(clone.name)) {
228
+ routeMap.set(clone.name, clone);
229
+ }
230
+ }
231
+ }
232
+
233
+ return result;
234
+ }
235
+
236
+ export { generateAccessible };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Global authority directive
3
+ * Used for fine-grained control of component permissions
4
+ * @Example v-access:role="[ROLE_NAME]" or v-access:role="ROLE_NAME"
5
+ * @Example v-access:code="[ROLE_CODE]" or v-access:code="ROLE_CODE"
6
+ */
7
+ import type { App, Directive, DirectiveBinding } from 'vue';
8
+
9
+ import { useAccess } from './use-access';
10
+
11
+ function isAccessible(
12
+ el: Element,
13
+ binding: DirectiveBinding<string | string[]>,
14
+ ) {
15
+ const { accessMode, hasAccessByCodes, hasAccessByRoles } = useAccess();
16
+
17
+ const value = binding.value;
18
+
19
+ if (!value) return;
20
+ const authMethod =
21
+ accessMode.value === 'frontend' && binding.arg === 'role'
22
+ ? hasAccessByRoles
23
+ : hasAccessByCodes;
24
+
25
+ const values = Array.isArray(value) ? value : [value];
26
+
27
+ if (!authMethod(values)) {
28
+ el?.remove();
29
+ }
30
+ }
31
+
32
+ const mounted = (el: Element, binding: DirectiveBinding<string | string[]>) => {
33
+ isAccessible(el, binding);
34
+ };
35
+
36
+ const authDirective: Directive = {
37
+ mounted,
38
+ };
39
+
40
+ export function registerAccessDirective(app: App) {
41
+ app.directive('access', authDirective);
42
+ }
@@ -0,0 +1,4 @@
1
+ export { default as AccessControl } from './access-control.vue';
2
+ export * from './accessible';
3
+ export * from './directive';
4
+ export * from './use-access';
@@ -0,0 +1,53 @@
1
+ import { computed } from 'vue';
2
+
3
+ import { preferences, updatePreferences } from '@melt-js/libs-vben/preferences';
4
+ import { useAccessStore, useUserStore } from '@melt-js/libs-vben/stores';
5
+
6
+ function useAccess() {
7
+ const accessStore = useAccessStore();
8
+ const userStore = useUserStore();
9
+ const accessMode = computed(() => {
10
+ return preferences.app.accessMode;
11
+ });
12
+
13
+ /**
14
+ * 基于角色判断是否有权限
15
+ * @description: Determine whether there is permission,The role is judged by the user's role
16
+ * @param roles
17
+ */
18
+ function hasAccessByRoles(roles: string[]) {
19
+ const userRoleSet = new Set(userStore.userRoles);
20
+ const intersection = roles.filter((item) => userRoleSet.has(item));
21
+ return intersection.length > 0;
22
+ }
23
+
24
+ /**
25
+ * 基于权限码判断是否有权限
26
+ * @description: Determine whether there is permission,The permission code is judged by the user's permission code
27
+ * @param codes
28
+ */
29
+ function hasAccessByCodes(codes: string[]) {
30
+ const userCodesSet = new Set(accessStore.accessCodes);
31
+
32
+ const intersection = codes.filter((item) => userCodesSet.has(item));
33
+ return intersection.length > 0;
34
+ }
35
+
36
+ async function toggleAccessMode() {
37
+ updatePreferences({
38
+ app: {
39
+ accessMode:
40
+ preferences.app.accessMode === 'frontend' ? 'backend' : 'frontend',
41
+ },
42
+ });
43
+ }
44
+
45
+ return {
46
+ accessMode,
47
+ hasAccessByCodes,
48
+ hasAccessByRoles,
49
+ toggleAccessMode,
50
+ };
51
+ }
52
+
53
+ export { useAccess };
@@ -0,0 +1,14 @@
1
+ export * from './use-is-mobile';
2
+ export * from './use-layout-style';
3
+ export * from './use-layout-viewport-height';
4
+ export * from './use-namespace';
5
+ export * from './use-priority-value';
6
+ export * from './use-scroll-lock';
7
+ export * from './use-simple-locale';
8
+ export * from './use-sortable';
9
+ export {
10
+ useEmitAsProps,
11
+ useForwardExpose,
12
+ useForwardProps,
13
+ useForwardPropsEmits,
14
+ } from 'reka-ui';
@@ -0,0 +1,7 @@
1
+ import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
2
+
3
+ export function useIsMobile() {
4
+ const breakpoints = useBreakpoints(breakpointsTailwind);
5
+ const isMobile = breakpoints.smaller('md');
6
+ return { isMobile };
7
+ }
@@ -0,0 +1,51 @@
1
+ import type { CSSProperties } from 'vue';
2
+
3
+ import { computed, ref } from 'vue';
4
+
5
+ import {
6
+ CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT,
7
+ CSS_VARIABLE_LAYOUT_HEADER_HEIGHT,
8
+ } from '@melt-js/libs-vben/shared/constants';
9
+
10
+ import { useCssVar } from '@vueuse/core';
11
+
12
+ /**
13
+ * @zh_CN content style
14
+ */
15
+ export function useLayoutContentStyle() {
16
+ const contentElement = ref<HTMLDivElement | null>(null);
17
+
18
+ const overlayStyle = computed((): CSSProperties => ({
19
+ inset: 0,
20
+ position: 'absolute',
21
+ zIndex: 150,
22
+ }));
23
+
24
+ return { contentElement, overlayStyle };
25
+ }
26
+
27
+ export function useLayoutHeaderStyle() {
28
+ const headerHeight = useCssVar(CSS_VARIABLE_LAYOUT_HEADER_HEIGHT);
29
+
30
+ return {
31
+ getLayoutHeaderHeight: () => {
32
+ return Number.parseInt(`${headerHeight.value}`, 10);
33
+ },
34
+ setLayoutHeaderHeight: (height: number) => {
35
+ headerHeight.value = `${height}px`;
36
+ },
37
+ };
38
+ }
39
+
40
+ export function useLayoutFooterStyle() {
41
+ const footerHeight = useCssVar(CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT);
42
+
43
+ return {
44
+ getLayoutFooterHeight: () => {
45
+ return Number.parseInt(`${footerHeight.value}`, 10);
46
+ },
47
+ setLayoutFooterHeight: (height: number) => {
48
+ footerHeight.value = `${height}px`;
49
+ },
50
+ };
51
+ }