@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.
- package/LICENSE +10 -0
- package/README.md +86 -2
- package/package.json +98 -3
- package/src/access/access-control.vue +47 -0
- package/src/access/accessible.ts +236 -0
- package/src/access/directive.ts +42 -0
- package/src/access/index.ts +4 -0
- package/src/access/use-access.ts +53 -0
- package/src/composables/index.ts +14 -0
- package/src/composables/use-is-mobile.ts +7 -0
- package/src/composables/use-layout-style.ts +51 -0
- package/src/composables/use-layout-viewport-height.ts +71 -0
- package/src/composables/use-namespace.ts +106 -0
- package/src/composables/use-priority-value.ts +96 -0
- package/src/composables/use-scroll-lock.ts +151 -0
- package/src/composables/use-simple-locale/README.md +3 -0
- package/src/composables/use-simple-locale/index.ts +27 -0
- package/src/composables/use-simple-locale/messages.ts +31 -0
- package/src/composables/use-sortable.ts +32 -0
- package/src/constants/core.ts +76 -0
- package/src/constants/index.ts +2 -0
- package/src/core-icons/create-icon.ts +14 -0
- package/src/core-icons/index.ts +11 -0
- package/src/core-icons/lucide.ts +95 -0
- package/src/core-preferences/config.ts +172 -0
- package/src/core-preferences/constants.ts +116 -0
- package/src/core-preferences/index.ts +24 -0
- package/src/core-preferences/preferences.ts +503 -0
- package/src/core-preferences/types.ts +471 -0
- package/src/core-preferences/update-css-variables.ts +129 -0
- package/src/core-preferences/use-preferences.ts +282 -0
- package/src/design/css/global.css +1 -0
- package/src/design/css/nprogress.css +61 -0
- package/src/design/css/transition.css +238 -0
- package/src/design/css/ui.css +101 -0
- package/src/design/design-tokens/dark.css +446 -0
- package/src/design/design-tokens/default.css +491 -0
- package/src/design/design-tokens/index.ts +2 -0
- package/src/design/index.ts +6 -0
- package/src/design/scss-bem/bem.scss +34 -0
- package/src/design/scss-bem/constants.scss +5 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/use-app-config.ts +39 -0
- package/src/hooks/use-content-maximize.ts +24 -0
- package/src/hooks/use-design-tokens.ts +421 -0
- package/src/hooks/use-hover-toggle.ts +163 -0
- package/src/hooks/use-pagination.ts +72 -0
- package/src/hooks/use-refresh.ts +16 -0
- package/src/hooks/use-tabs.ts +143 -0
- package/src/hooks/use-watermark.ts +84 -0
- package/src/icons/iconify/index.ts +5 -0
- package/src/icons/icons/empty-icon.vue +27 -0
- package/src/icons/index.ts +3 -0
- package/src/icons/svg/icons/antdv-logo.svg +29 -0
- package/src/icons/svg/icons/antdv-next-logo.svg +1 -0
- package/src/icons/svg/icons/avatar-1.svg +1 -0
- package/src/icons/svg/icons/avatar-2.svg +1 -0
- package/src/icons/svg/icons/avatar-3.svg +1 -0
- package/src/icons/svg/icons/avatar-4.svg +1 -0
- package/src/icons/svg/icons/bell.svg +1 -0
- package/src/icons/svg/icons/cake.svg +1 -0
- package/src/icons/svg/icons/card.svg +1 -0
- package/src/icons/svg/icons/dingding.svg +1 -0
- package/src/icons/svg/icons/download.svg +1 -0
- package/src/icons/svg/icons/github.svg +1 -0
- package/src/icons/svg/icons/google.svg +1 -0
- package/src/icons/svg/icons/qqchat.svg +1 -0
- package/src/icons/svg/icons/tdesign-logo.svg +39 -0
- package/src/icons/svg/icons/wechat.svg +1 -0
- package/src/icons/svg/index.ts +39 -0
- package/src/icons/svg/load.ts +81 -0
- package/src/locales/i18n.ts +147 -0
- package/src/locales/index.ts +30 -0
- package/src/locales/langs/en-US/authentication.json +65 -0
- package/src/locales/langs/en-US/common.json +27 -0
- package/src/locales/langs/en-US/preferences.json +245 -0
- package/src/locales/langs/en-US/profile.json +4 -0
- package/src/locales/langs/en-US/ui.json +165 -0
- package/src/locales/langs/zh-CN/authentication.json +65 -0
- package/src/locales/langs/zh-CN/common.json +27 -0
- package/src/locales/langs/zh-CN/preferences.json +245 -0
- package/src/locales/langs/zh-CN/profile.json +4 -0
- package/src/locales/langs/zh-CN/ui.json +165 -0
- package/src/locales/langs/zh-TW/authentication.json +65 -0
- package/src/locales/langs/zh-TW/common.json +27 -0
- package/src/locales/langs/zh-TW/preferences.json +245 -0
- package/src/locales/langs/zh-TW/profile.json +4 -0
- package/src/locales/langs/zh-TW/ui.json +165 -0
- package/src/locales/typing.ts +27 -0
- package/src/preferences/index.ts +42 -0
- package/src/request/index.ts +2 -0
- package/src/request/request-client/index.ts +3 -0
- package/src/request/request-client/modules/downloader.ts +60 -0
- package/src/request/request-client/modules/interceptor.ts +40 -0
- package/src/request/request-client/modules/sse.ts +134 -0
- package/src/request/request-client/modules/uploader.ts +79 -0
- package/src/request/request-client/preset-interceptors.ts +178 -0
- package/src/request/request-client/request-client.ts +163 -0
- package/src/request/request-client/types.ts +90 -0
- package/src/shared/cache/README.md +418 -0
- package/src/shared/cache/index.ts +5 -0
- package/src/shared/cache/indexeddb-driver.ts +137 -0
- package/src/shared/cache/local-storage-driver.ts +70 -0
- package/src/shared/cache/memory-storage-driver.ts +32 -0
- package/src/shared/cache/storage-manager.ts +146 -0
- package/src/shared/cache/types.ts +39 -0
- package/src/shared/color/color.ts +9 -0
- package/src/shared/color/convert.ts +62 -0
- package/src/shared/color/generator.ts +45 -0
- package/src/shared/color/index.ts +3 -0
- package/src/shared/constants/globals.ts +20 -0
- package/src/shared/constants/index.ts +2 -0
- package/src/shared/constants/vben.ts +30 -0
- package/src/shared/global-state.ts +45 -0
- package/src/shared/store.ts +1 -0
- package/src/shared/utils/cn.ts +10 -0
- package/src/shared/utils/date.ts +80 -0
- package/src/shared/utils/diff.ts +114 -0
- package/src/shared/utils/dom.ts +126 -0
- package/src/shared/utils/download.ts +186 -0
- package/src/shared/utils/error.ts +19 -0
- package/src/shared/utils/index.ts +22 -0
- package/src/shared/utils/inference.ts +164 -0
- package/src/shared/utils/letter.ts +47 -0
- package/src/shared/utils/merge.ts +11 -0
- package/src/shared/utils/nprogress.ts +52 -0
- package/src/shared/utils/resources.ts +21 -0
- package/src/shared/utils/stack.ts +103 -0
- package/src/shared/utils/state-handler.ts +61 -0
- package/src/shared/utils/to.ts +21 -0
- package/src/shared/utils/tree.ts +159 -0
- package/src/shared/utils/unique.ts +15 -0
- package/src/shared/utils/update-css-variables.ts +47 -0
- package/src/shared/utils/util.ts +50 -0
- package/src/shared/utils/window.ts +37 -0
- package/src/stores/index.ts +3 -0
- package/src/stores/modules/access.ts +130 -0
- package/src/stores/modules/dict.ts +244 -0
- package/src/stores/modules/index.ts +5 -0
- package/src/stores/modules/tabbar.ts +797 -0
- package/src/stores/modules/timezone.ts +132 -0
- package/src/stores/modules/user.ts +42 -0
- package/src/stores/setup.ts +79 -0
- package/src/stores/shim-pinia.d.ts +11 -0
- package/src/styles/antd/index.css +78 -0
- package/src/styles/antdv-next/index.css +90 -0
- package/src/styles/ele/index.css +44 -0
- package/src/styles/global/index.scss +1 -0
- package/src/styles/index.ts +1 -0
- package/src/styles/naive/index.css +26 -0
- package/src/styles/style-exports.d.ts +3 -0
- package/src/theme.css +592 -0
- package/src/types/global.d.ts +28 -0
- package/src/types/index.ts +2 -0
- package/src/types/shims.d.ts +1 -0
- package/src/types/user.ts +20 -0
- package/src/typings/app.d.ts +128 -0
- package/src/typings/basic.d.ts +42 -0
- package/src/typings/helper.d.ts +147 -0
- package/src/typings/index.ts +16 -0
- package/src/typings/languages.ts +28 -0
- package/src/typings/menu-record.ts +82 -0
- package/src/typings/tabs.ts +8 -0
- package/src/typings/vue-router.d.ts +169 -0
- package/src/utils/helpers/find-menu-by-path.ts +37 -0
- package/src/utils/helpers/generate-menus.ts +89 -0
- package/src/utils/helpers/generate-routes-backend.ts +108 -0
- package/src/utils/helpers/generate-routes-frontend.ts +58 -0
- package/src/utils/helpers/get-popup-container.ts +10 -0
- package/src/utils/helpers/index.ts +8 -0
- package/src/utils/helpers/merge-route-modules.ts +28 -0
- package/src/utils/helpers/reset-routes.ts +30 -0
- package/src/utils/helpers/unmount-global-loading.ts +31 -0
- package/src/utils/index.ts +4 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ELEMENT_ID_LAYOUT_SCROLL } from '../constants/globals';
|
|
2
|
+
|
|
3
|
+
export interface VisibleDomRect {
|
|
4
|
+
bottom: number;
|
|
5
|
+
height: number;
|
|
6
|
+
left: number;
|
|
7
|
+
right: number;
|
|
8
|
+
top: number;
|
|
9
|
+
width: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 获取元素可见信息
|
|
14
|
+
* @param element
|
|
15
|
+
*/
|
|
16
|
+
export function getElementVisibleRect(
|
|
17
|
+
element?: HTMLElement | null,
|
|
18
|
+
): VisibleDomRect {
|
|
19
|
+
if (!element) {
|
|
20
|
+
return {
|
|
21
|
+
bottom: 0,
|
|
22
|
+
height: 0,
|
|
23
|
+
left: 0,
|
|
24
|
+
right: 0,
|
|
25
|
+
top: 0,
|
|
26
|
+
width: 0,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const rect = element.getBoundingClientRect();
|
|
30
|
+
const viewHeight = Math.max(
|
|
31
|
+
document.documentElement.clientHeight,
|
|
32
|
+
window.innerHeight,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const top = Math.max(rect.top, 0);
|
|
36
|
+
const bottom = Math.min(rect.bottom, viewHeight);
|
|
37
|
+
|
|
38
|
+
const viewWidth = Math.max(
|
|
39
|
+
document.documentElement.clientWidth,
|
|
40
|
+
window.innerWidth,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const left = Math.max(rect.left, 0);
|
|
44
|
+
const right = Math.min(rect.right, viewWidth);
|
|
45
|
+
|
|
46
|
+
// 如果元素完全不可见,则返回一个空的矩形
|
|
47
|
+
if (top >= viewHeight || bottom <= 0 || left >= viewWidth || right <= 0) {
|
|
48
|
+
return {
|
|
49
|
+
bottom: 0,
|
|
50
|
+
height: 0,
|
|
51
|
+
left: 0,
|
|
52
|
+
right: 0,
|
|
53
|
+
top: 0,
|
|
54
|
+
width: 0,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
bottom,
|
|
60
|
+
height: Math.max(0, bottom - top),
|
|
61
|
+
left,
|
|
62
|
+
right,
|
|
63
|
+
top,
|
|
64
|
+
width: Math.max(0, right - left),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getScrollbarWidth() {
|
|
69
|
+
const scrollDiv = document.createElement('div');
|
|
70
|
+
|
|
71
|
+
scrollDiv.style.visibility = 'hidden';
|
|
72
|
+
scrollDiv.style.overflow = 'scroll';
|
|
73
|
+
scrollDiv.style.position = 'absolute';
|
|
74
|
+
scrollDiv.style.top = '-9999px';
|
|
75
|
+
|
|
76
|
+
document.body.append(scrollDiv);
|
|
77
|
+
|
|
78
|
+
const innerDiv = document.createElement('div');
|
|
79
|
+
scrollDiv.append(innerDiv);
|
|
80
|
+
|
|
81
|
+
const scrollbarWidth = scrollDiv.offsetWidth - innerDiv.offsetWidth;
|
|
82
|
+
|
|
83
|
+
scrollDiv.remove();
|
|
84
|
+
return scrollbarWidth;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getLayoutScrollElement() {
|
|
88
|
+
return document.querySelector<HTMLElement>(`#${ELEMENT_ID_LAYOUT_SCROLL}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function elementNeedsScrollbar(element: HTMLElement) {
|
|
92
|
+
const overflowY = window.getComputedStyle(element).overflowY;
|
|
93
|
+
if (overflowY === 'hidden' || overflowY === 'clip') {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return element.scrollHeight > element.clientHeight;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function needsScrollbar(target?: HTMLElement | null) {
|
|
101
|
+
if (target) {
|
|
102
|
+
return elementNeedsScrollbar(target);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const doc = document.documentElement;
|
|
106
|
+
const body = document.body;
|
|
107
|
+
|
|
108
|
+
// 检查 body 的 overflow-y 样式
|
|
109
|
+
const overflowY = window.getComputedStyle(body).overflowY;
|
|
110
|
+
|
|
111
|
+
// 如果明确设置了需要滚动条的样式
|
|
112
|
+
if (overflowY === 'scroll' || overflowY === 'auto') {
|
|
113
|
+
return doc.scrollHeight > window.innerHeight;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 在其他情况下,根据 scrollHeight 和 innerHeight 比较判断
|
|
117
|
+
return doc.scrollHeight > window.innerHeight;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function triggerWindowResize(): void {
|
|
121
|
+
// 创建一个新的 resize 事件
|
|
122
|
+
const resizeEvent = new Event('resize');
|
|
123
|
+
|
|
124
|
+
// 触发 window 的 resize 事件
|
|
125
|
+
window.dispatchEvent(resizeEvent);
|
|
126
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { openWindow } from './window';
|
|
2
|
+
|
|
3
|
+
interface DownloadOptions<T = string> {
|
|
4
|
+
fileName?: string;
|
|
5
|
+
source: T;
|
|
6
|
+
target?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const DEFAULT_FILENAME = 'downloaded_file';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 通过 URL 下载文件,支持跨域
|
|
13
|
+
* @throws {Error} - 当下载失败时抛出错误
|
|
14
|
+
*/
|
|
15
|
+
export async function downloadFileFromUrl({
|
|
16
|
+
fileName,
|
|
17
|
+
source,
|
|
18
|
+
target = '_blank',
|
|
19
|
+
}: DownloadOptions): Promise<void> {
|
|
20
|
+
if (!source || typeof source !== 'string') {
|
|
21
|
+
throw new Error('Invalid URL.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const isChrome = window.navigator.userAgent.toLowerCase().includes('chrome');
|
|
25
|
+
const isSafari = window.navigator.userAgent.toLowerCase().includes('safari');
|
|
26
|
+
|
|
27
|
+
if (/iP/.test(window.navigator.userAgent)) {
|
|
28
|
+
console.error('Your browser does not support download!');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isChrome || isSafari) {
|
|
33
|
+
triggerDownload(source, resolveFileName(source, fileName));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!source.includes('?')) {
|
|
37
|
+
source += '?download';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
openWindow(source, { target });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 通过 Base64 下载文件
|
|
45
|
+
*/
|
|
46
|
+
export function downloadFileFromBase64({ fileName, source }: DownloadOptions) {
|
|
47
|
+
if (!source || typeof source !== 'string') {
|
|
48
|
+
throw new Error('Invalid Base64 data.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const resolvedFileName = fileName || DEFAULT_FILENAME;
|
|
52
|
+
triggerDownload(source, resolvedFileName);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 通过图片 URL 下载图片文件
|
|
57
|
+
*/
|
|
58
|
+
export async function downloadFileFromImageUrl({
|
|
59
|
+
fileName,
|
|
60
|
+
source,
|
|
61
|
+
}: DownloadOptions) {
|
|
62
|
+
const base64 = await urlToBase64(source);
|
|
63
|
+
// urlToBase64 总是把图片重新编码为 data url,缺省名要跟随产物真实扩展名,
|
|
64
|
+
// 否则保存下来的文件没有后缀,双击打不开
|
|
65
|
+
const extension =
|
|
66
|
+
base64.slice(base64.indexOf('/') + 1, base64.indexOf(';')) || 'png';
|
|
67
|
+
const derivedName = resolveFileName(source).replace(/\.[^.]*$/, '');
|
|
68
|
+
|
|
69
|
+
downloadFileFromBase64({
|
|
70
|
+
fileName: fileName || `${derivedName}.${extension}`,
|
|
71
|
+
source: base64,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 通过 Blob 下载文件
|
|
77
|
+
*/
|
|
78
|
+
export function downloadFileFromBlob({
|
|
79
|
+
fileName = DEFAULT_FILENAME,
|
|
80
|
+
source,
|
|
81
|
+
}: DownloadOptions<Blob>): void {
|
|
82
|
+
if (!(source instanceof Blob)) {
|
|
83
|
+
throw new TypeError('Invalid Blob data.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const url = URL.createObjectURL(source);
|
|
87
|
+
triggerDownload(url, fileName);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 下载文件,支持 Blob、字符串和其他 BlobPart 类型
|
|
92
|
+
*/
|
|
93
|
+
export function downloadFileFromBlobPart({
|
|
94
|
+
fileName = DEFAULT_FILENAME,
|
|
95
|
+
source,
|
|
96
|
+
}: DownloadOptions<BlobPart>): void {
|
|
97
|
+
// 如果 data 不是 Blob,则转换为 Blob
|
|
98
|
+
const blob =
|
|
99
|
+
source instanceof Blob
|
|
100
|
+
? source
|
|
101
|
+
: new Blob([source], { type: 'application/octet-stream' });
|
|
102
|
+
|
|
103
|
+
// 创建对象 URL 并触发下载
|
|
104
|
+
const url = URL.createObjectURL(blob);
|
|
105
|
+
triggerDownload(url, fileName);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* img url to base64
|
|
110
|
+
* @param url
|
|
111
|
+
*/
|
|
112
|
+
export function urlToBase64(url: string, mineType?: string): Promise<string> {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null;
|
|
115
|
+
const ctx = canvas?.getContext('2d');
|
|
116
|
+
const img = new Image();
|
|
117
|
+
img.crossOrigin = '';
|
|
118
|
+
img.addEventListener('load', () => {
|
|
119
|
+
if (!canvas || !ctx) {
|
|
120
|
+
return reject(new Error('Failed to create canvas.'));
|
|
121
|
+
}
|
|
122
|
+
canvas.height = img.height;
|
|
123
|
+
canvas.width = img.width;
|
|
124
|
+
ctx.drawImage(img, 0, 0);
|
|
125
|
+
const dataURL = canvas.toDataURL(mineType || 'image/png');
|
|
126
|
+
canvas = null;
|
|
127
|
+
resolve(dataURL);
|
|
128
|
+
});
|
|
129
|
+
img.src = url;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 通用下载触发函数
|
|
135
|
+
* @param href - 文件下载的 URL
|
|
136
|
+
* @param fileName - 下载文件的名称,如果未提供则自动识别
|
|
137
|
+
* @param revokeDelay - 清理 URL 的延迟时间 (毫秒)
|
|
138
|
+
*/
|
|
139
|
+
export function triggerDownload(
|
|
140
|
+
href: string,
|
|
141
|
+
fileName: string | undefined,
|
|
142
|
+
revokeDelay: number = 100,
|
|
143
|
+
): void {
|
|
144
|
+
const defaultFileName = 'downloaded_file';
|
|
145
|
+
const finalFileName = fileName || defaultFileName;
|
|
146
|
+
|
|
147
|
+
const link = document.createElement('a');
|
|
148
|
+
link.href = href;
|
|
149
|
+
link.download = finalFileName;
|
|
150
|
+
link.style.display = 'none';
|
|
151
|
+
|
|
152
|
+
if (link.download === undefined) {
|
|
153
|
+
link.setAttribute('target', '_blank');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
document.body.append(link);
|
|
157
|
+
link.click();
|
|
158
|
+
link.remove();
|
|
159
|
+
|
|
160
|
+
// 清理临时 URL 以释放内存
|
|
161
|
+
setTimeout(() => URL.revokeObjectURL(href), revokeDelay);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 解析下载文件名:显式传入优先,其次取 URL 路径的最后一段,都没有则用默认名
|
|
166
|
+
* @param url - 文件地址,data url 不携带文件名信息
|
|
167
|
+
* @param fileName - 调用方显式指定的文件名
|
|
168
|
+
*/
|
|
169
|
+
function resolveFileName(url: string, fileName?: string): string {
|
|
170
|
+
if (fileName) {
|
|
171
|
+
return fileName;
|
|
172
|
+
}
|
|
173
|
+
// data: 的"路径"是文件内容本身,不能当文件名,否则编码串会溜进下载名
|
|
174
|
+
if (url.startsWith('data:')) {
|
|
175
|
+
return DEFAULT_FILENAME;
|
|
176
|
+
}
|
|
177
|
+
// 查询串与锚点不属于文件名;签名链接普遍带 ?token=...
|
|
178
|
+
const path = url.split('#')[0]?.split('?')[0] ?? '';
|
|
179
|
+
const lastSegment = path.slice(path.lastIndexOf('/') + 1);
|
|
180
|
+
try {
|
|
181
|
+
return decodeURIComponent(lastSegment) || DEFAULT_FILENAME;
|
|
182
|
+
} catch {
|
|
183
|
+
// 非法百分号转义序列,退回原始片段
|
|
184
|
+
return lastSegment || DEFAULT_FILENAME;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 从任意抛出值中提取可读的错误消息。
|
|
3
|
+
*
|
|
4
|
+
* 统一各处 `error instanceof Error ? error.message : String(error)` 的
|
|
5
|
+
* 重复三元:Error 取 message,其余值(字符串、数字、对象等)String() 化
|
|
6
|
+
* 作为兜底文案(对象会得到 '[object Object]',调用方如需结构化信息应
|
|
7
|
+
* 自行处理,这里只保证「任何失败都有一条可打印的消息」)。
|
|
8
|
+
*/
|
|
9
|
+
function getErrorMessage(error: unknown): string {
|
|
10
|
+
try {
|
|
11
|
+
return error instanceof Error ? error.message : String(error);
|
|
12
|
+
} catch {
|
|
13
|
+
// String()/message 读取本身可能抛(如带抛异常 Symbol.toPrimitive 的
|
|
14
|
+
// 对象):兜底常量保证「任何失败都有一条可打印的消息」这一契约。
|
|
15
|
+
return 'Unknown error';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { getErrorMessage };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export * from './cn';
|
|
2
|
+
export * from './date';
|
|
3
|
+
export * from './diff';
|
|
4
|
+
export * from './dom';
|
|
5
|
+
export * from './download';
|
|
6
|
+
export * from './error';
|
|
7
|
+
export * from './inference';
|
|
8
|
+
export * from './letter';
|
|
9
|
+
export * from './merge';
|
|
10
|
+
export * from './nprogress';
|
|
11
|
+
export * from './resources';
|
|
12
|
+
export * from './stack';
|
|
13
|
+
export * from './state-handler';
|
|
14
|
+
export * from './to';
|
|
15
|
+
export * from './tree';
|
|
16
|
+
export * from './unique';
|
|
17
|
+
export * from './update-css-variables';
|
|
18
|
+
export * from './util';
|
|
19
|
+
export * from './window';
|
|
20
|
+
export { debounce, get, isEqual, set } from 'es-toolkit/compat';
|
|
21
|
+
// export { cloneDeep } from 'es-toolkit/object';
|
|
22
|
+
export { default as cloneDeep } from 'lodash.clonedeep';
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { isFunction, isObject, isString } from '@vue/shared';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 检查传入的值是否为undefined。
|
|
5
|
+
*
|
|
6
|
+
* @param {unknown} value 要检查的值。
|
|
7
|
+
* @returns {boolean} 如果值是undefined,返回true,否则返回false。
|
|
8
|
+
*/
|
|
9
|
+
function isUndefined(value?: unknown): value is undefined {
|
|
10
|
+
return value === undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 检查传入的值是否为boolean
|
|
15
|
+
* @param value
|
|
16
|
+
* @returns 如果值是布尔值,返回true,否则返回false。
|
|
17
|
+
*/
|
|
18
|
+
function isBoolean(value: unknown): value is boolean {
|
|
19
|
+
return typeof value === 'boolean';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 检查传入的值是否为空。
|
|
24
|
+
*
|
|
25
|
+
* 以下情况将被认为是空:
|
|
26
|
+
* - 值为null。
|
|
27
|
+
* - 值为undefined。
|
|
28
|
+
* - 值为一个空字符串。
|
|
29
|
+
* - 值为一个长度为0的数组。
|
|
30
|
+
* - 值为一个没有元素的Map或Set。
|
|
31
|
+
* - 值为一个没有属性的对象。
|
|
32
|
+
*
|
|
33
|
+
* @param {T} value 要检查的值。
|
|
34
|
+
* @returns {boolean} 如果值为空,返回true,否则返回false。
|
|
35
|
+
*/
|
|
36
|
+
function isEmpty<T = unknown>(value?: T): value is T {
|
|
37
|
+
if (value === null || value === undefined) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (Array.isArray(value) || isString(value)) {
|
|
42
|
+
return value.length === 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (value instanceof Map || value instanceof Set) {
|
|
46
|
+
return value.size === 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (isObject(value)) {
|
|
50
|
+
return Object.keys(value).length === 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 检查传入的字符串是否为有效的HTTP或HTTPS URL。
|
|
58
|
+
*
|
|
59
|
+
* @param {string} url 要检查的字符串。
|
|
60
|
+
* @return {boolean} 如果字符串是有效的HTTP或HTTPS URL,返回true,否则返回false。
|
|
61
|
+
*/
|
|
62
|
+
function isHttpUrl(url?: string): boolean {
|
|
63
|
+
if (!url) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
// 使用正则表达式测试URL是否以http:// 或 https:// 开头
|
|
67
|
+
const httpRegex = /^https?:\/\/.*$/;
|
|
68
|
+
return httpRegex.test(url);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 检查传入的值是否为window对象。
|
|
73
|
+
*
|
|
74
|
+
* @param {any} value 要检查的值。
|
|
75
|
+
* @returns {boolean} 如果值是window对象,返回true,否则返回false。
|
|
76
|
+
*/
|
|
77
|
+
function isWindow(value: any): value is Window {
|
|
78
|
+
return (
|
|
79
|
+
typeof window !== 'undefined' && value !== null && value === value.window
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 检查当前运行环境是否为Mac OS。
|
|
85
|
+
*
|
|
86
|
+
* 这个函数通过检查navigator.userAgent字符串来判断当前运行环境。
|
|
87
|
+
* 如果userAgent字符串中包含"macintosh"或"mac os x"(不区分大小写),则认为当前环境是Mac OS。
|
|
88
|
+
*
|
|
89
|
+
* @returns {boolean} 如果当前环境是Mac OS,返回true,否则返回false。
|
|
90
|
+
*/
|
|
91
|
+
function isMacOs(): boolean {
|
|
92
|
+
const macRegex = /macintosh|mac os x/i;
|
|
93
|
+
return macRegex.test(navigator.userAgent);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 检查当前运行环境是否为Windows OS。
|
|
98
|
+
*
|
|
99
|
+
* 这个函数通过检查navigator.userAgent字符串来判断当前运行环境。
|
|
100
|
+
* 如果userAgent字符串中包含"windows"或"win32"(不区分大小写),则认为当前环境是Windows OS。
|
|
101
|
+
*
|
|
102
|
+
* @returns {boolean} 如果当前环境是Windows OS,返回true,否则返回false。
|
|
103
|
+
*/
|
|
104
|
+
function isWindowsOs(): boolean {
|
|
105
|
+
const windowsRegex = /windows|win32/i;
|
|
106
|
+
return windowsRegex.test(navigator.userAgent);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 检查传入的值是否为数字
|
|
111
|
+
* @param value
|
|
112
|
+
*/
|
|
113
|
+
function isNumber(value: any): value is number {
|
|
114
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Returns the first value in the provided list that is neither `null` nor `undefined`.
|
|
119
|
+
*
|
|
120
|
+
* This function iterates over the input values and returns the first one that is
|
|
121
|
+
* not strictly equal to `null` or `undefined`. If all values are either `null` or
|
|
122
|
+
* `undefined`, it returns `undefined`.
|
|
123
|
+
*
|
|
124
|
+
* @template T - The type of the input values.
|
|
125
|
+
* @param {...(T | null | undefined)[]} values - A list of values to evaluate.
|
|
126
|
+
* @returns {T | undefined} - The first value that is not `null` or `undefined`, or `undefined` if none are found.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* // Returns 42 because it is the first non-null, non-undefined value.
|
|
130
|
+
* getFirstNonNullOrUndefined(undefined, null, 42, 'hello'); // 42
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* // Returns 'hello' because it is the first non-null, non-undefined value.
|
|
134
|
+
* getFirstNonNullOrUndefined(null, undefined, 'hello', 123); // 'hello'
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* // Returns undefined because all values are either null or undefined.
|
|
138
|
+
* getFirstNonNullOrUndefined(undefined, null); // undefined
|
|
139
|
+
*/
|
|
140
|
+
function getFirstNonNullOrUndefined<T>(
|
|
141
|
+
...values: (null | T | undefined)[]
|
|
142
|
+
): T | undefined {
|
|
143
|
+
for (const value of values) {
|
|
144
|
+
if (value !== undefined && value !== null) {
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export {
|
|
152
|
+
getFirstNonNullOrUndefined,
|
|
153
|
+
isBoolean,
|
|
154
|
+
isEmpty,
|
|
155
|
+
isFunction,
|
|
156
|
+
isHttpUrl,
|
|
157
|
+
isMacOs,
|
|
158
|
+
isNumber,
|
|
159
|
+
isObject,
|
|
160
|
+
isString,
|
|
161
|
+
isUndefined,
|
|
162
|
+
isWindow,
|
|
163
|
+
isWindowsOs,
|
|
164
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将字符串的首字母大写
|
|
3
|
+
* @param string
|
|
4
|
+
*/
|
|
5
|
+
function capitalizeFirstLetter(string: string): string {
|
|
6
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 将字符串的首字母转换为小写。
|
|
11
|
+
*
|
|
12
|
+
* @param str 要转换的字符串
|
|
13
|
+
* @returns 首字母小写的字符串
|
|
14
|
+
*/
|
|
15
|
+
function toLowerCaseFirstLetter(str: string): string {
|
|
16
|
+
if (!str) return str; // 如果字符串为空,直接返回
|
|
17
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 生成驼峰命名法的键名
|
|
22
|
+
* @param key
|
|
23
|
+
* @param parentKey
|
|
24
|
+
*/
|
|
25
|
+
function toCamelCase(key: string, parentKey: string): string {
|
|
26
|
+
if (!parentKey) {
|
|
27
|
+
return key;
|
|
28
|
+
}
|
|
29
|
+
return parentKey + key.charAt(0).toUpperCase() + key.slice(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function kebabToCamelCase(str: string): string {
|
|
33
|
+
return str
|
|
34
|
+
.split('-')
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.map((word, index) =>
|
|
37
|
+
index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1),
|
|
38
|
+
)
|
|
39
|
+
.join('');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
capitalizeFirstLetter,
|
|
44
|
+
kebabToCamelCase,
|
|
45
|
+
toCamelCase,
|
|
46
|
+
toLowerCaseFirstLetter,
|
|
47
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createDefu } from 'defu';
|
|
2
|
+
|
|
3
|
+
export { createDefu as createMerge, defu as merge } from 'defu';
|
|
4
|
+
|
|
5
|
+
export const mergeWithArrayOverride = createDefu((originObj, key, updates) => {
|
|
6
|
+
if (Array.isArray(originObj[key]) && Array.isArray(updates)) {
|
|
7
|
+
originObj[key] = updates;
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
return false;
|
|
11
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type NProgress from 'nprogress';
|
|
2
|
+
|
|
3
|
+
// 创建一个NProgress实例的变量,初始值为null
|
|
4
|
+
let nProgressInstance: null | typeof NProgress = null;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 动态加载NProgress库,并进行配置。
|
|
8
|
+
* 此函数首先检查是否已经加载过NProgress库,如果已经加载过,则直接返回NProgress实例。
|
|
9
|
+
* 否则,动态导入NProgress库,进行配置,然后返回NProgress实例。
|
|
10
|
+
*
|
|
11
|
+
* @returns NProgress实例的Promise对象。
|
|
12
|
+
*/
|
|
13
|
+
async function loadNprogress() {
|
|
14
|
+
if (nProgressInstance) {
|
|
15
|
+
return nProgressInstance;
|
|
16
|
+
}
|
|
17
|
+
nProgressInstance = await import('nprogress');
|
|
18
|
+
nProgressInstance.configure({
|
|
19
|
+
showSpinner: true,
|
|
20
|
+
speed: 300,
|
|
21
|
+
});
|
|
22
|
+
return nProgressInstance;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 开始显示进度条。
|
|
27
|
+
* 此函数首先加载NProgress库,然后调用NProgress的start方法开始显示进度条。
|
|
28
|
+
*/
|
|
29
|
+
async function startProgress() {
|
|
30
|
+
try {
|
|
31
|
+
const nprogress = await loadNprogress();
|
|
32
|
+
nprogress?.start();
|
|
33
|
+
} catch {
|
|
34
|
+
// 进度条是增强体验:nprogress 加载失败时静默降级(不抛出),
|
|
35
|
+
// 调用方可放心 fire-and-forget,不会产生 unhandled rejection。
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 停止显示进度条,并隐藏进度条。
|
|
41
|
+
* 此函数首先加载NProgress库,然后调用NProgress的done方法停止并隐藏进度条。
|
|
42
|
+
*/
|
|
43
|
+
async function stopProgress() {
|
|
44
|
+
try {
|
|
45
|
+
const nprogress = await loadNprogress();
|
|
46
|
+
nprogress?.done();
|
|
47
|
+
} catch {
|
|
48
|
+
// nprogress 未加载成功时无进度条可停,保持静默
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { startProgress, stopProgress };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 加载js文件
|
|
3
|
+
* @param src js文件地址
|
|
4
|
+
*/
|
|
5
|
+
function loadScript(src: string) {
|
|
6
|
+
return new Promise<void>((resolve, reject) => {
|
|
7
|
+
if (document.querySelector(`script[src="${src}"]`)) {
|
|
8
|
+
// 如果已经加载过,直接 resolve
|
|
9
|
+
return resolve();
|
|
10
|
+
}
|
|
11
|
+
const script = document.createElement('script');
|
|
12
|
+
script.src = src;
|
|
13
|
+
script.addEventListener('load', () => resolve());
|
|
14
|
+
script.addEventListener('error', () =>
|
|
15
|
+
reject(new Error(`Failed to load script: ${src}`)),
|
|
16
|
+
);
|
|
17
|
+
document.head.append(script);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { loadScript };
|