@cbjwwwcode/common 0.1.0 → 0.1.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/dist/es/cn.d.mts +5 -0
- package/dist/es/cn.mjs +8 -0
- package/dist/es/common.d.mts +1 -0
- package/dist/es/common.mjs +11 -1
- package/dist/es/copy.d.mts +2 -1
- package/dist/es/copy.mjs +27 -7
- package/dist/es/download.d.mts +16 -0
- package/dist/es/download.mjs +103 -0
- package/dist/es/index.d.mts +6 -4
- package/dist/es/index.mjs +5 -3
- package/dist/es/lowlight/hljs.d.mts +2 -2
- package/dist/es/lowlight/index.d.mts +2 -2
- package/dist/es/vtj/node.d.mts +2 -1
- package/dist/es/window.d.mts +8 -1
- package/dist/es/window.mjs +21 -1
- package/dist/umd/common.min.js +247 -247
- package/package.json +15 -13
- package/src/cn.ts +10 -0
- package/src/common.ts +12 -0
- package/src/copy.ts +33 -9
- package/src/download.ts +165 -0
- package/src/index.ts +2 -0
- package/src/window.ts +38 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cbjwwwcode/common",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "ISC",
|
|
@@ -32,16 +32,16 @@
|
|
|
32
32
|
"./umd/": "./dist/umd/"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@tsdown/css": "^0.23.
|
|
35
|
+
"@tsdown/css": "^0.23.1",
|
|
36
36
|
"@types/crypto-js": "^4.2.2",
|
|
37
|
-
"@types/node": "^26.6.
|
|
37
|
+
"@types/node": "^26.6.5",
|
|
38
38
|
"@types/nprogress": "^0.2.3",
|
|
39
|
-
"concurrently": "^10.0.
|
|
39
|
+
"concurrently": "^10.0.6",
|
|
40
40
|
"laravel-vite-plugin": "^3.2.0",
|
|
41
41
|
"rollup-plugin-bundle-stats": "^4.22.3",
|
|
42
42
|
"sass": "^1.105.1",
|
|
43
43
|
"sonda": "^0.14.0",
|
|
44
|
-
"tsdown": "^0.23.
|
|
44
|
+
"tsdown": "^0.23.1",
|
|
45
45
|
"typescript": "^6.0.3",
|
|
46
46
|
"unplugin-vue": "^7.2.0",
|
|
47
47
|
"vite": "8.3.0",
|
|
@@ -53,33 +53,35 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@alova/adapter-axios": "^2.0.20",
|
|
56
|
-
"@vtj/core": "^0.20.
|
|
57
|
-
"@vtj/parser": "^0.20.
|
|
58
|
-
"@vtj/renderer": "^0.20.
|
|
59
|
-
"@vtj/utils": "^0.20.
|
|
56
|
+
"@vtj/core": "^0.20.7",
|
|
57
|
+
"@vtj/parser": "^0.20.7",
|
|
58
|
+
"@vtj/renderer": "^0.20.7",
|
|
59
|
+
"@vtj/utils": "^0.20.7",
|
|
60
60
|
"@vueuse/core": "^14.4.0",
|
|
61
61
|
"@vueuse/integrations": "^14.4.0",
|
|
62
62
|
"aieditor": "^2.0.0",
|
|
63
63
|
"alova": "^3.5.5",
|
|
64
64
|
"axios": "^1.20.0",
|
|
65
|
+
"clsx": "^2.1.1",
|
|
65
66
|
"crypto-js": "^4.2.0",
|
|
66
67
|
"dayjs": "^1.11.23",
|
|
67
68
|
"dompurify": "^3.4.16",
|
|
68
69
|
"es-toolkit": "^1.52.0",
|
|
69
70
|
"esthetic": "0.6.4-beta.1",
|
|
70
71
|
"highlight.js": "^11.12.0",
|
|
71
|
-
"hucre": "^1.
|
|
72
|
+
"hucre": "^1.2.0",
|
|
72
73
|
"idb-keyval": "^6.3.0",
|
|
73
74
|
"jsbarcode": "^3.12.3",
|
|
74
75
|
"mitt": "^3.0.1",
|
|
75
|
-
"motion-v": "^2.
|
|
76
|
+
"motion-v": "^2.6.0",
|
|
76
77
|
"nprogress": "^0.2.0",
|
|
77
78
|
"path-browserify": "^1.0.1",
|
|
79
|
+
"tailwind-merge": "^3.7.0",
|
|
78
80
|
"vee-validate": "^4.15.1",
|
|
79
81
|
"vue": "3.6.0-rc.2",
|
|
80
82
|
"vue-demi": "^0.14.10",
|
|
81
|
-
"vue-router": "^5.
|
|
83
|
+
"vue-router": "^5.4.0",
|
|
82
84
|
"vue3-toastify": "^0.2.9",
|
|
83
|
-
"zod": "^4.6.
|
|
85
|
+
"zod": "^4.6.6"
|
|
84
86
|
}
|
|
85
87
|
}
|
package/src/cn.ts
ADDED
package/src/common.ts
CHANGED
|
@@ -199,3 +199,15 @@ export function isHttpUrl<T extends string>(str: T) {
|
|
|
199
199
|
|
|
200
200
|
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* 判断是否为Vue组件定义
|
|
204
|
+
*/
|
|
205
|
+
export function isVueComponent(val: unknown): val is Component {
|
|
206
|
+
if (typeof val !== 'object' || val === null) return false
|
|
207
|
+
// 1. 有 render / setup 函数(SFC编译后组件)
|
|
208
|
+
const hasRender = 'render' in val && typeof (val as any).render === 'function'
|
|
209
|
+
const hasSetup = 'setup' in val && typeof (val as any).setup === 'function'
|
|
210
|
+
// 2. 异步组件是函数,带 __asyncLoader 标记
|
|
211
|
+
if (typeof val === 'function' && '__asyncLoader' in val) return true
|
|
212
|
+
return hasRender || hasSetup
|
|
213
|
+
}
|
package/src/copy.ts
CHANGED
|
@@ -9,6 +9,21 @@ interface UseCopyOptions {
|
|
|
9
9
|
onError?: (error: Error) => void
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// 降级方案:创建临时textarea
|
|
13
|
+
function fallbackCopy(text: string): Promise<boolean> {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const textarea = document.createElement('textarea');
|
|
16
|
+
textarea.value = text;
|
|
17
|
+
textarea.style.position = 'fixed';
|
|
18
|
+
textarea.style.opacity = '0';
|
|
19
|
+
document.body.appendChild(textarea);
|
|
20
|
+
textarea.select();
|
|
21
|
+
const result = document.execCommand('copy');
|
|
22
|
+
document.body.removeChild(textarea);
|
|
23
|
+
resolve(result);
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
export function useCopy(options: UseCopyOptions = {}) {
|
|
13
28
|
const {
|
|
14
29
|
successMessage = '复制成功',
|
|
@@ -20,31 +35,40 @@ export function useCopy(options: UseCopyOptions = {}) {
|
|
|
20
35
|
|
|
21
36
|
const { copy, copied, isSupported } = useClipboard()
|
|
22
37
|
|
|
38
|
+
const doFallbackCopy = async (content: string, errorMessage: string)=>{
|
|
39
|
+
try {
|
|
40
|
+
await fallbackCopy(content)
|
|
41
|
+
showMessage && toast.success(successMessage)
|
|
42
|
+
onSuccess?.(content)
|
|
43
|
+
return true
|
|
44
|
+
} catch (error) {
|
|
45
|
+
showMessage && toast.warning(errorMessage)
|
|
46
|
+
onError?.(error as Error)
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
23
51
|
/**
|
|
24
52
|
* 复制文本
|
|
25
53
|
*/
|
|
26
54
|
const handleCopy = async (text: string | number): Promise<boolean> => {
|
|
27
|
-
if (!isSupported.value) {
|
|
28
|
-
showMessage && toast.warning('当前环境不支持复制')
|
|
29
|
-
return false
|
|
30
|
-
}
|
|
31
|
-
|
|
32
55
|
const content = String(text ?? '')
|
|
33
|
-
|
|
34
56
|
if (!content) {
|
|
35
57
|
showMessage && toast.warning('复制内容为空')
|
|
36
58
|
return false
|
|
37
59
|
}
|
|
38
60
|
|
|
61
|
+
if (!isSupported.value) {
|
|
62
|
+
return await doFallbackCopy(content, '当前环境不支持复制')
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
try {
|
|
40
66
|
await copy(content)
|
|
41
67
|
showMessage && toast.success(successMessage)
|
|
42
68
|
onSuccess?.(content)
|
|
43
69
|
return true
|
|
44
70
|
} catch (error) {
|
|
45
|
-
|
|
46
|
-
onError?.(error as Error)
|
|
47
|
-
return false
|
|
71
|
+
return await doFallbackCopy(content, errorMessage)
|
|
48
72
|
}
|
|
49
73
|
}
|
|
50
74
|
|
package/src/download.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
|
|
13
|
+
export function downloadImageFromCanvas(canvas: HTMLCanvasElement, name: string, type?:string, quality?: number){
|
|
14
|
+
const url = canvas.toDataURL(type, quality);
|
|
15
|
+
downloadFileFromUrl({ fileName: name, source: url });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 通过 URL 下载文件,支持跨域
|
|
21
|
+
* @throws {Error} - 当下载失败时抛出错误
|
|
22
|
+
*/
|
|
23
|
+
export async function downloadFileFromUrl({
|
|
24
|
+
fileName,
|
|
25
|
+
source,
|
|
26
|
+
target = '_blank',
|
|
27
|
+
}: DownloadOptions): Promise<void> {
|
|
28
|
+
if (!source || typeof source !== 'string') {
|
|
29
|
+
throw new Error('Invalid URL.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isChrome = window.navigator.userAgent.toLowerCase().includes('chrome');
|
|
33
|
+
const isSafari = window.navigator.userAgent.toLowerCase().includes('safari');
|
|
34
|
+
|
|
35
|
+
if (/iP/.test(window.navigator.userAgent)) {
|
|
36
|
+
console.error('Your browser does not support download!');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (isChrome || isSafari) {
|
|
41
|
+
triggerDownload(source, resolveFileName(source, fileName));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!source.includes('?')) {
|
|
45
|
+
source += '?download';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
openWindow(source, { target });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 通过 Base64 下载文件
|
|
53
|
+
*/
|
|
54
|
+
export function downloadFileFromBase64({ fileName, source }: DownloadOptions) {
|
|
55
|
+
if (!source || typeof source !== 'string') {
|
|
56
|
+
throw new Error('Invalid Base64 data.');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const resolvedFileName = fileName || DEFAULT_FILENAME;
|
|
60
|
+
triggerDownload(source, resolvedFileName);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 通过图片 URL 下载图片文件
|
|
65
|
+
*/
|
|
66
|
+
export async function downloadFileFromImageUrl({
|
|
67
|
+
fileName,
|
|
68
|
+
source,
|
|
69
|
+
}: DownloadOptions) {
|
|
70
|
+
const base64 = await urlToBase64(source);
|
|
71
|
+
downloadFileFromBase64({ fileName, source: base64 });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 通过 Blob 下载文件
|
|
76
|
+
*/
|
|
77
|
+
export function downloadFileFromBlob({
|
|
78
|
+
fileName = DEFAULT_FILENAME,
|
|
79
|
+
source,
|
|
80
|
+
}: DownloadOptions<Blob>): void {
|
|
81
|
+
if (!(source instanceof Blob)) {
|
|
82
|
+
throw new TypeError('Invalid Blob data.');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const url = URL.createObjectURL(source);
|
|
86
|
+
triggerDownload(url, fileName);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 下载文件,支持 Blob、字符串和其他 BlobPart 类型
|
|
91
|
+
*/
|
|
92
|
+
export function downloadFileFromBlobPart({
|
|
93
|
+
fileName = DEFAULT_FILENAME,
|
|
94
|
+
source,
|
|
95
|
+
}: DownloadOptions<BlobPart>): void {
|
|
96
|
+
// 如果 data 不是 Blob,则转换为 Blob
|
|
97
|
+
const blob =
|
|
98
|
+
source instanceof Blob
|
|
99
|
+
? source
|
|
100
|
+
: new Blob([source], { type: 'application/octet-stream' });
|
|
101
|
+
|
|
102
|
+
// 创建对象 URL 并触发下载
|
|
103
|
+
const url = URL.createObjectURL(blob);
|
|
104
|
+
triggerDownload(url, fileName);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* img url to base64
|
|
109
|
+
* @param url
|
|
110
|
+
*/
|
|
111
|
+
export function urlToBase64(url: string, mineType?: string): Promise<string> {
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null;
|
|
114
|
+
const ctx = canvas?.getContext('2d');
|
|
115
|
+
const img = new Image();
|
|
116
|
+
img.crossOrigin = '';
|
|
117
|
+
img.addEventListener('load', () => {
|
|
118
|
+
if (!canvas || !ctx) {
|
|
119
|
+
return reject(new Error('Failed to create canvas.'));
|
|
120
|
+
}
|
|
121
|
+
canvas.height = img.height;
|
|
122
|
+
canvas.width = img.width;
|
|
123
|
+
ctx.drawImage(img, 0, 0);
|
|
124
|
+
const dataURL = canvas.toDataURL(mineType || 'image/png');
|
|
125
|
+
canvas = null;
|
|
126
|
+
resolve(dataURL);
|
|
127
|
+
});
|
|
128
|
+
img.src = url;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 通用下载触发函数
|
|
134
|
+
* @param href - 文件下载的 URL
|
|
135
|
+
* @param fileName - 下载文件的名称,如果未提供则自动识别
|
|
136
|
+
* @param revokeDelay - 清理 URL 的延迟时间 (毫秒)
|
|
137
|
+
*/
|
|
138
|
+
export function triggerDownload(
|
|
139
|
+
href: string,
|
|
140
|
+
fileName: string | undefined,
|
|
141
|
+
revokeDelay: number = 100,
|
|
142
|
+
): void {
|
|
143
|
+
const defaultFileName = 'downloaded_file';
|
|
144
|
+
const finalFileName = fileName || defaultFileName;
|
|
145
|
+
|
|
146
|
+
const link = document.createElement('a');
|
|
147
|
+
link.href = href;
|
|
148
|
+
link.download = finalFileName;
|
|
149
|
+
link.style.display = 'none';
|
|
150
|
+
|
|
151
|
+
if (link.download === undefined) {
|
|
152
|
+
link.setAttribute('target', '_blank');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
document.body.append(link);
|
|
156
|
+
link.click();
|
|
157
|
+
link.remove();
|
|
158
|
+
|
|
159
|
+
// 清理临时 URL 以释放内存
|
|
160
|
+
setTimeout(() => URL.revokeObjectURL(href), revokeDelay);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function resolveFileName(url: string, fileName?: string): string {
|
|
164
|
+
return fileName || url.slice(url.lastIndexOf('/') + 1) || DEFAULT_FILENAME;
|
|
165
|
+
}
|
package/src/index.ts
CHANGED
package/src/window.ts
CHANGED
|
@@ -58,3 +58,41 @@ registerWindow('process', {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
interface OpenWindowOptions {
|
|
62
|
+
noopener?: boolean;
|
|
63
|
+
noreferrer?: boolean;
|
|
64
|
+
target?: '_blank' | '_parent' | '_self' | '_top' | string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 新窗口打开URL。
|
|
69
|
+
*
|
|
70
|
+
* @param url - 需要打开的网址。
|
|
71
|
+
* @param options - 打开窗口的选项。
|
|
72
|
+
*/
|
|
73
|
+
function openWindow(url: string, options: OpenWindowOptions = {}): void {
|
|
74
|
+
// 解构并设置默认值
|
|
75
|
+
const { noopener = true, noreferrer = true, target = '_blank' } = options;
|
|
76
|
+
|
|
77
|
+
// 基于选项创建特性字符串
|
|
78
|
+
const features = [noopener && 'noopener=yes', noreferrer && 'noreferrer=yes']
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.join(',');
|
|
81
|
+
|
|
82
|
+
// 打开窗口
|
|
83
|
+
window.open(url, target, features);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 在新窗口中打开路由。
|
|
88
|
+
* @param path
|
|
89
|
+
*/
|
|
90
|
+
function openRouteInNewWindow(path: string) {
|
|
91
|
+
const { hash, origin } = location;
|
|
92
|
+
const fullPath = path.startsWith('/') ? path : `/${path}`;
|
|
93
|
+
const url = `${origin}${hash && !fullPath.startsWith('/#') ? '/#' : ''}${fullPath}`;
|
|
94
|
+
openWindow(url, { target: '_blank' });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { openRouteInNewWindow, openWindow };
|
|
98
|
+
|