@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
@@ -0,0 +1,71 @@
1
+ import { onMounted, onUnmounted } from 'vue';
2
+
3
+ import { CSS_VARIABLE_LAYOUT_VIEWPORT_HEIGHT } from '@melt-js/libs-vben/shared/constants';
4
+
5
+ import { useCssVar, useEventListener } from '@vueuse/core';
6
+
7
+ function supportsDynamicViewportHeight() {
8
+ return (
9
+ globalThis.CSS !== undefined &&
10
+ typeof globalThis.CSS.supports === 'function' &&
11
+ globalThis.CSS.supports('height', '1dvh')
12
+ );
13
+ }
14
+
15
+ function readViewportHeight() {
16
+ return Math.round(window.visualViewport?.height ?? window.innerHeight);
17
+ }
18
+
19
+ /**
20
+ * 仅在不支持 dvh 时,把 --vben-viewport-height 写成像素值。
21
+ * 支持 dvh 时保持 CSS 的 100vh → 100dvh 级联,避免 useCssVar 把单位冻成 px。
22
+ */
23
+ export function useLayoutViewportHeight() {
24
+ if (typeof window === 'undefined' || supportsDynamicViewportHeight()) {
25
+ return;
26
+ }
27
+
28
+ const viewportHeight = useCssVar(
29
+ CSS_VARIABLE_LAYOUT_VIEWPORT_HEIGHT,
30
+ document.documentElement,
31
+ { observe: false },
32
+ );
33
+
34
+ let frameId = 0;
35
+
36
+ function applyViewportHeight() {
37
+ viewportHeight.value = `${readViewportHeight()}px`;
38
+ }
39
+
40
+ function scheduleApplyViewportHeight() {
41
+ if (frameId) {
42
+ return;
43
+ }
44
+
45
+ frameId = window.requestAnimationFrame(() => {
46
+ frameId = 0;
47
+ applyViewportHeight();
48
+ });
49
+ }
50
+
51
+ applyViewportHeight();
52
+ onMounted(applyViewportHeight);
53
+ useEventListener(window, 'resize', scheduleApplyViewportHeight);
54
+
55
+ if (window.visualViewport) {
56
+ useEventListener(
57
+ window.visualViewport,
58
+ 'resize',
59
+ scheduleApplyViewportHeight,
60
+ );
61
+ }
62
+
63
+ onUnmounted(() => {
64
+ if (!frameId) {
65
+ return;
66
+ }
67
+
68
+ window.cancelAnimationFrame(frameId);
69
+ frameId = 0;
70
+ });
71
+ }
@@ -0,0 +1,106 @@
1
+ import { DEFAULT_NAMESPACE } from '@melt-js/libs-vben/shared/constants';
2
+
3
+ /**
4
+ * @see copy https://github.com/element-plus/element-plus/blob/dev/packages/hooks/use-namespace/index.ts
5
+ */
6
+
7
+ const statePrefix = 'is-';
8
+
9
+ const _bem = (
10
+ namespace: string,
11
+ block: string,
12
+ blockSuffix: string,
13
+ element: string,
14
+ modifier: string,
15
+ ) => {
16
+ let cls = `${namespace}-${block}`;
17
+ if (blockSuffix) {
18
+ cls += `-${blockSuffix}`;
19
+ }
20
+ if (element) {
21
+ cls += `__${element}`;
22
+ }
23
+ if (modifier) {
24
+ cls += `--${modifier}`;
25
+ }
26
+ return cls;
27
+ };
28
+
29
+ const is: {
30
+ (name: string): string;
31
+ // oxlint-disable-next-line typescript/unified-signatures
32
+ (name: string, state: boolean | undefined): string;
33
+ } = (name: string, ...args: [] | [boolean | undefined]) => {
34
+ const state = args.length > 0 ? args[0] : true;
35
+ return name && state ? `${statePrefix}${name}` : '';
36
+ };
37
+
38
+ const useNamespace = (block: string) => {
39
+ const namespace = DEFAULT_NAMESPACE;
40
+ const b = (blockSuffix = '') => _bem(namespace, block, blockSuffix, '', '');
41
+ const e = (element?: string) =>
42
+ element ? _bem(namespace, block, '', element, '') : '';
43
+ const m = (modifier?: string) =>
44
+ modifier ? _bem(namespace, block, '', '', modifier) : '';
45
+ const be = (blockSuffix?: string, element?: string) =>
46
+ blockSuffix && element
47
+ ? _bem(namespace, block, blockSuffix, element, '')
48
+ : '';
49
+ const em = (element?: string, modifier?: string) =>
50
+ element && modifier ? _bem(namespace, block, '', element, modifier) : '';
51
+ const bm = (blockSuffix?: string, modifier?: string) =>
52
+ blockSuffix && modifier
53
+ ? _bem(namespace, block, blockSuffix, '', modifier)
54
+ : '';
55
+ const bem = (blockSuffix?: string, element?: string, modifier?: string) =>
56
+ blockSuffix && element && modifier
57
+ ? _bem(namespace, block, blockSuffix, element, modifier)
58
+ : '';
59
+
60
+ // for css var
61
+ // --el-xxx: value;
62
+ const cssVar = (object: Record<string, string>) => {
63
+ const styles: Record<string, string> = {};
64
+ for (const key in object) {
65
+ if (object[key]) {
66
+ styles[`--${namespace}-${key}`] = object[key];
67
+ }
68
+ }
69
+ return styles;
70
+ };
71
+ // with block
72
+ const cssVarBlock = (object: Record<string, string>) => {
73
+ const styles: Record<string, string> = {};
74
+ for (const key in object) {
75
+ if (object[key]) {
76
+ styles[`--${namespace}-${block}-${key}`] = object[key];
77
+ }
78
+ }
79
+ return styles;
80
+ };
81
+
82
+ const cssVarName = (name: string) => `--${namespace}-${name}`;
83
+ const cssVarBlockName = (name: string) => `--${namespace}-${block}-${name}`;
84
+
85
+ return {
86
+ b,
87
+ be,
88
+ bem,
89
+ bm,
90
+ // css
91
+ cssVar,
92
+ cssVarBlock,
93
+ cssVarBlockName,
94
+ cssVarName,
95
+ e,
96
+ em,
97
+ is,
98
+ m,
99
+ namespace,
100
+ };
101
+ };
102
+
103
+ type UseNamespaceReturn = ReturnType<typeof useNamespace>;
104
+
105
+ export type { UseNamespaceReturn };
106
+ export { useNamespace };
@@ -0,0 +1,96 @@
1
+ import type { ComputedRef, Ref } from 'vue';
2
+
3
+ import { computed, getCurrentInstance, unref, useAttrs, useSlots } from 'vue';
4
+
5
+ import {
6
+ getFirstNonNullOrUndefined,
7
+ kebabToCamelCase,
8
+ } from '@melt-js/libs-vben/shared/utils';
9
+
10
+ /**
11
+ * 依次从插槽、attrs、props、state 中获取值
12
+ * @param key
13
+ * @param props
14
+ * @param state
15
+ */
16
+ export function usePriorityValue<
17
+ T extends Record<string, any>,
18
+ S extends Record<string, any>,
19
+ K extends keyof T = keyof T,
20
+ >(key: K, props: T, state: Readonly<Ref<NoInfer<S>>> | undefined) {
21
+ const instance = getCurrentInstance();
22
+ const slots = useSlots();
23
+ const attrs = useAttrs() as T;
24
+
25
+ const value = computed((): T[K] => {
26
+ // props不管有没有传,都会有默认值,会影响这里的顺序,
27
+ // 通过判断原始props是否有值来判断是否传入
28
+ const rawProps = (instance?.vnode?.props || {}) as T;
29
+
30
+ const standardRawProps = {} as T;
31
+
32
+ for (const [key, value] of Object.entries(rawProps)) {
33
+ standardRawProps[kebabToCamelCase(key) as K] = value;
34
+ }
35
+ const propsKey =
36
+ standardRawProps?.[key] === undefined ? undefined : props[key];
37
+
38
+ // slot可以关闭
39
+ return getFirstNonNullOrUndefined(
40
+ slots[key as string],
41
+ attrs[key],
42
+ propsKey,
43
+ state?.value?.[key as keyof S],
44
+ ) as T[K];
45
+ });
46
+
47
+ return value;
48
+ }
49
+
50
+ /**
51
+ * 批量获取state中的值(每个值都是ref)
52
+ * @param props
53
+ * @param state
54
+ */
55
+ export function usePriorityValues<
56
+ T extends Record<string, any>,
57
+ // S 是已发布的公共 API 契约:调用方以 usePriorityValues<Props, StateRef>(…)
58
+ // 显式传参指定 state 的 Ref 类型;默认值依赖 T 推断(NoInfer<T>),
59
+ // 规则只看签名内出现次数、不评估跨包调用方,不能据此删除。
60
+ // oxlint-disable-next-line typescript/no-unnecessary-type-parameters
61
+ S extends Ref<Record<string, any>> = Readonly<Ref<NoInfer<T>, NoInfer<T>>>,
62
+ >(props: T, state: S | undefined) {
63
+ const result: { [K in keyof T]: ComputedRef<T[K]> } = {} as never;
64
+
65
+ (Object.keys(props) as (keyof T)[]).forEach((key) => {
66
+ result[key] = usePriorityValue(key, props, state);
67
+ });
68
+
69
+ return result;
70
+ }
71
+
72
+ /**
73
+ * 批量获取state中的值(集中在一个computed,用于透传)
74
+ * @param props
75
+ * @param state
76
+ */
77
+ export function useForwardPriorityValues<
78
+ T extends Record<string, any>,
79
+ // 同 usePriorityValues:两个类型实参的调用形态是已发布契约,保留 S。
80
+ // oxlint-disable-next-line typescript/no-unnecessary-type-parameters
81
+ S extends Ref<Record<string, any>> = Readonly<Ref<NoInfer<T>, NoInfer<T>>>,
82
+ >(props: T, state: S | undefined) {
83
+ const computedResult: { [K in keyof T]: ComputedRef<T[K]> } = {} as never;
84
+
85
+ (Object.keys(props) as (keyof T)[]).forEach((key) => {
86
+ computedResult[key] = usePriorityValue(key, props, state);
87
+ });
88
+
89
+ return computed(() => {
90
+ const unwrapResult: Record<string, any> = {};
91
+ Object.keys(props).forEach((key) => {
92
+ unwrapResult[key] = unref(computedResult[key]);
93
+ });
94
+ return unwrapResult as { [K in keyof T]: T[K] };
95
+ });
96
+ }
@@ -0,0 +1,151 @@
1
+ import { computed, effectScope, nextTick, shallowRef } from 'vue';
2
+
3
+ import {
4
+ getLayoutScrollElement,
5
+ getScrollbarWidth,
6
+ needsScrollbar,
7
+ } from '@melt-js/libs-vben/shared/utils';
8
+
9
+ import {
10
+ useScrollLock as _useScrollLock,
11
+ tryOnBeforeUnmount,
12
+ tryOnMounted,
13
+ } from '@vueuse/core';
14
+
15
+ export const SCROLL_FIXED_CLASS = `_scroll__fixed_`;
16
+
17
+ interface ScrollLockOptions {
18
+ immediate?: boolean;
19
+ }
20
+
21
+ let lockCount = 0;
22
+ let releaseLock: (() => void) | undefined;
23
+ const pendingTransitionRestores = new WeakMap<
24
+ HTMLElement,
25
+ { frame: number; restore: () => void }
26
+ >();
27
+
28
+ function getScrollLockTarget() {
29
+ return getLayoutScrollElement() ?? document.body;
30
+ }
31
+
32
+ function captureStyle(element: HTMLElement, property: string) {
33
+ const value = element.style.getPropertyValue(property);
34
+ const priority = element.style.getPropertyPriority(property);
35
+ return () => {
36
+ if (value) {
37
+ element.style.setProperty(property, value, priority);
38
+ } else {
39
+ element.style.removeProperty(property);
40
+ }
41
+ };
42
+ }
43
+
44
+ function disableTransition(node: HTMLElement) {
45
+ const pending = pendingTransitionRestores.get(node);
46
+ if (pending) {
47
+ cancelAnimationFrame(pending.frame);
48
+ pendingTransitionRestores.delete(node);
49
+ }
50
+ // Reuse the original style when a new lock starts before the restore frame.
51
+ const restore = pending?.restore ?? captureStyle(node, 'transition');
52
+ node.style.transition = 'none';
53
+
54
+ return () => {
55
+ const frame = requestAnimationFrame(() => {
56
+ pendingTransitionRestores.delete(node);
57
+ restore();
58
+ });
59
+ pendingTransitionRestores.set(node, { frame, restore });
60
+ };
61
+ }
62
+
63
+ function applyScrollbarStyles(target: HTMLElement): (() => void)[] {
64
+ if (!needsScrollbar(target)) {
65
+ return [];
66
+ }
67
+
68
+ if (target !== document.body) {
69
+ const restore = captureStyle(target, 'scrollbar-gutter');
70
+ target.style.setProperty('scrollbar-gutter', 'stable');
71
+ return [restore];
72
+ }
73
+
74
+ if (!needsScrollbar()) {
75
+ return [];
76
+ }
77
+
78
+ const width = `${getScrollbarWidth()}px`;
79
+ const restore = [captureStyle(target, 'padding-right')];
80
+ target.style.paddingRight = width;
81
+ document
82
+ .querySelectorAll<HTMLElement>(`.${SCROLL_FIXED_CLASS}`)
83
+ .forEach((node) => {
84
+ restore.push(
85
+ captureStyle(node, 'padding-right'),
86
+ disableTransition(node),
87
+ );
88
+ node.style.paddingRight = width;
89
+ });
90
+
91
+ return restore;
92
+ }
93
+
94
+ function acquireLock(target: HTMLElement) {
95
+ const restore = applyScrollbarStyles(target);
96
+
97
+ // Detach so one overlay's unmount cannot release the shared lock.
98
+ const scope = effectScope(true);
99
+ scope.run(() => {
100
+ const locked = _useScrollLock(target);
101
+ locked.value = true;
102
+ });
103
+ return () => {
104
+ scope.stop();
105
+ restore.forEach((reset) => reset());
106
+ };
107
+ }
108
+
109
+ export function useScrollLock(options: ScrollLockOptions = {}) {
110
+ const { immediate = true } = options;
111
+ const ownsLock = shallowRef(false);
112
+ let disposed = false;
113
+
114
+ const isLocked = computed({
115
+ get: () => ownsLock.value,
116
+ set(value: boolean) {
117
+ // Count each caller once; ignore repeated assignments and unused unlocks.
118
+ if (value === ownsLock.value) {
119
+ return;
120
+ }
121
+ if (value) {
122
+ releaseLock ??= acquireLock(getScrollLockTarget());
123
+ lockCount += 1;
124
+ } else if (--lockCount === 0) {
125
+ releaseLock?.();
126
+ releaseLock = undefined;
127
+ }
128
+ ownsLock.value = value;
129
+ },
130
+ });
131
+
132
+ tryOnMounted(async () => {
133
+ // Wait for mounted hooks and their DOM updates before measuring overflow.
134
+ await nextTick();
135
+ if (disposed) {
136
+ return;
137
+ }
138
+ const target = getScrollLockTarget();
139
+ // Join an existing lock even when overflow: hidden makes needsScrollbar false.
140
+ if (immediate && (lockCount > 0 || needsScrollbar(target))) {
141
+ isLocked.value = true;
142
+ }
143
+ });
144
+
145
+ tryOnBeforeUnmount(() => {
146
+ disposed = true;
147
+ isLocked.value = false;
148
+ });
149
+
150
+ return isLocked;
151
+ }
@@ -0,0 +1,3 @@
1
+ # Simple i18n
2
+
3
+ Simple i18 implementation
@@ -0,0 +1,27 @@
1
+ import type { Locale } from './messages';
2
+
3
+ import { computed, ref } from 'vue';
4
+
5
+ import { createSharedComposable } from '@vueuse/core';
6
+
7
+ import { getMessages } from './messages';
8
+
9
+ export const useSimpleLocale = createSharedComposable(() => {
10
+ const currentLocale = ref<Locale>('zh-CN');
11
+
12
+ const setSimpleLocale = (locale: Locale) => {
13
+ currentLocale.value = locale;
14
+ };
15
+
16
+ const $t = computed(() => {
17
+ const localeMessages = getMessages(currentLocale.value);
18
+ return (key: string) => {
19
+ return localeMessages[key] || key;
20
+ };
21
+ });
22
+ return {
23
+ $t,
24
+ currentLocale,
25
+ setSimpleLocale,
26
+ };
27
+ });
@@ -0,0 +1,31 @@
1
+ import type { SupportedLanguagesType } from '@melt-js/libs-vben/typings';
2
+
3
+ export type Locale = SupportedLanguagesType;
4
+
5
+ export const messages: Partial<Record<Locale, Record<string, string>>> = {
6
+ 'en-US': {
7
+ cancel: 'Cancel',
8
+ collapse: 'Collapse',
9
+ confirm: 'Confirm',
10
+ expand: 'Expand',
11
+ prompt: 'Prompt',
12
+ reset: 'Reset',
13
+ submit: 'Submit',
14
+ toggleSidebar: 'Toggle sidebar',
15
+ confirmTitle: 'Please Confirm',
16
+ },
17
+ 'zh-CN': {
18
+ cancel: '取消',
19
+ collapse: '收起',
20
+ confirm: '确认',
21
+ expand: '展开',
22
+ prompt: '提示',
23
+ reset: '重置',
24
+ submit: '提交',
25
+ toggleSidebar: '切换侧边栏',
26
+ confirmTitle: '请确认',
27
+ },
28
+ };
29
+
30
+ export const getMessages = (locale: Locale) =>
31
+ messages[locale] ?? messages['en-US'] ?? {};
@@ -0,0 +1,32 @@
1
+ import type { SortableOptions } from 'sortablejs';
2
+ import type Sortable from 'sortablejs';
3
+
4
+ // 泛型参数是已发布的公共 API 契约(调用方以 useSortable<HTMLUListElement>(el)
5
+ // 显式指定容器元素类型),规则只看签名内出现次数,不能据此删除。
6
+ // oxlint-disable-next-line typescript/no-unnecessary-type-parameters
7
+ function useSortable<T extends HTMLElement>(
8
+ sortableContainer: T,
9
+ options: SortableOptions = {},
10
+ ) {
11
+ const initializeSortable = async () => {
12
+ const Sortable = await import(
13
+ // @ts-expect-error - This is a dynamic import
14
+ 'sortablejs/modular/sortable.complete.esm.js'
15
+ );
16
+ const sortable = Sortable?.default?.create?.(sortableContainer, {
17
+ animation: 300,
18
+ delay: 400,
19
+ delayOnTouchOnly: true,
20
+ ...options,
21
+ });
22
+ return sortable as Sortable;
23
+ };
24
+
25
+ return {
26
+ initializeSortable,
27
+ };
28
+ }
29
+
30
+ export { useSortable };
31
+
32
+ export type { Sortable };
@@ -0,0 +1,76 @@
1
+ import type { SupportedLanguagesType } from '@melt-js/libs-vben/typings';
2
+
3
+ /**
4
+ * @zh_CN 登录页面 url 地址
5
+ */
6
+ export const LOGIN_PATH = '/auth/login';
7
+
8
+ /**
9
+ * 语言选项;value 默认为 string 以支持运行时注册自定义语言(见 setSupportLanguages)
10
+ */
11
+ export interface LanguageOption<TValue extends string = string> {
12
+ label: string;
13
+ value: TValue;
14
+ }
15
+
16
+ /**
17
+ * 支持的语言(默认值,可在运行时通过 setSupportLanguages 覆盖)。
18
+ * value 约束到 SupportedLanguagesType,应用通过模块增强扩展语言后,
19
+ * 默认列表同样接受新语言,拼写错误会在编译期报错
20
+ */
21
+ export const SUPPORT_LANGUAGES: LanguageOption<SupportedLanguagesType>[] = [
22
+ {
23
+ label: '简体中文',
24
+ value: 'zh-CN',
25
+ },
26
+ {
27
+ label: 'English',
28
+ value: 'en-US',
29
+ },
30
+ ];
31
+
32
+ // --- 动态语言列表管理 ---
33
+ type LanguageListListener = (languages: LanguageOption[]) => void;
34
+
35
+ // 深拷贝语言列表,外部拿到的永远是独立快照,
36
+ // 避免绕过 setSupportLanguages 直接修改注册表内部状态
37
+ function cloneLanguages(languages: LanguageOption[]): LanguageOption[] {
38
+ return languages.map((language) => ({ ...language }));
39
+ }
40
+
41
+ let currentLanguages: LanguageOption[] = cloneLanguages(SUPPORT_LANGUAGES);
42
+ const listeners: LanguageListListener[] = [];
43
+
44
+ /**
45
+ * 获取当前动态语言列表
46
+ */
47
+ export function getSupportLanguages(): LanguageOption[] {
48
+ return cloneLanguages(currentLanguages);
49
+ }
50
+
51
+ /**
52
+ * 更新语言列表并通知所有监听器
53
+ */
54
+ export function setSupportLanguages(languages: LanguageOption[]) {
55
+ // 先固化本次快照,即使监听器回调中嵌套调用 setSupportLanguages,
56
+ // 后续监听器收到的仍是本次更新的列表
57
+ const nextLanguages = cloneLanguages(languages);
58
+ currentLanguages = nextLanguages;
59
+ // 迭代监听器快照,避免监听器在回调中取消订阅导致跳过后续监听器
60
+ for (const listener of [...listeners]) {
61
+ listener(cloneLanguages(nextLanguages));
62
+ }
63
+ }
64
+
65
+ /**
66
+ * 订阅语言列表变化,返回取消订阅函数
67
+ */
68
+ export function onSupportLanguagesChange(listener: LanguageListListener) {
69
+ listeners.push(listener);
70
+ // 订阅时立即回调当前值,便于消费方初始化
71
+ listener(cloneLanguages(currentLanguages));
72
+ return () => {
73
+ const index = listeners.indexOf(listener);
74
+ if (index !== -1) listeners.splice(index, 1);
75
+ };
76
+ }
@@ -0,0 +1,2 @@
1
+ export * from './core';
2
+ export * from '@melt-js/libs-vben/shared/constants';
@@ -0,0 +1,14 @@
1
+ import { defineComponent, h } from 'vue';
2
+
3
+ import { Icon } from '@iconify/vue';
4
+
5
+ function createIconifyIcon(icon: string) {
6
+ return defineComponent({
7
+ name: `Icon-${icon}`,
8
+ setup(props, { attrs }) {
9
+ return () => h(Icon, { icon, ...props, ...attrs });
10
+ },
11
+ });
12
+ }
13
+
14
+ export { createIconifyIcon };
@@ -0,0 +1,11 @@
1
+ export * from './create-icon';
2
+
3
+ export * from './lucide';
4
+
5
+ export type { IconifyIcon as IconifyIconStructure } from '@iconify/vue';
6
+ export {
7
+ addCollection,
8
+ addIcon,
9
+ Icon as IconifyIcon,
10
+ listIcons,
11
+ } from '@iconify/vue';