clickgo 3.16.16 → 3.16.18
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/README.md +1 -1
- package/dist/app/demo/form/method/native/native.js +26 -0
- package/dist/app/demo/form/method/native/native.xml +2 -0
- package/dist/clickgo.d.ts +17 -0
- package/dist/clickgo.js +1 -1
- package/dist/control/arteditor.cgc +0 -0
- package/dist/control/box.cgc +0 -0
- package/dist/control/captcha.cgc +0 -0
- package/dist/control/common.cgc +0 -0
- package/dist/control/desc.cgc +0 -0
- package/dist/control/drawer.cgc +0 -0
- package/dist/control/echarts.cgc +0 -0
- package/dist/control/form.cgc +0 -0
- package/dist/control/iconview.cgc +0 -0
- package/dist/control/jodit.cgc +0 -0
- package/dist/control/map.cgc +0 -0
- package/dist/control/monaco.cgc +0 -0
- package/dist/control/mpegts.cgc +0 -0
- package/dist/control/nav.cgc +0 -0
- package/dist/control/page.cgc +0 -0
- package/dist/control/pdf.cgc +0 -0
- package/dist/control/property.cgc +0 -0
- package/dist/control/qrcode.cgc +0 -0
- package/dist/control/table.cgc +0 -0
- package/dist/control/task.cgc +0 -0
- package/dist/control/tplink.cgc +0 -0
- package/dist/control/tuieditor.cgc +0 -0
- package/dist/control/tuiviewer.cgc +0 -0
- package/dist/control/xterm.cgc +0 -0
- package/dist/index.d.ts +51 -0
- package/dist/lib/control.d.ts +53 -0
- package/dist/lib/core.d.ts +47 -0
- package/dist/lib/dom.d.ts +74 -0
- package/dist/lib/dom.js +7 -7
- package/dist/lib/form.d.ts +222 -0
- package/dist/lib/fs.d.ts +35 -0
- package/dist/lib/fs.js +2 -2
- package/dist/lib/native.d.ts +36 -0
- package/dist/lib/native.js +8 -0
- package/dist/lib/storage.d.ts +6 -0
- package/dist/lib/task.d.ts +32 -0
- package/dist/lib/task.js +7 -1
- package/dist/lib/theme.d.ts +8 -0
- package/dist/lib/tool.d.ts +120 -0
- package/dist/lib/zip.d.ts +40 -0
- package/dist/theme/blue.cgt +0 -0
- package/dist/theme/byterun.cgt +0 -0
- package/dist/theme/light.cgt +0 -0
- package/package.json +7 -5
- package/dist/clickgo.ts +0 -68
- package/dist/index.ts +0 -282
- package/dist/lib/control.ts +0 -751
- package/dist/lib/core.ts +0 -1145
- package/dist/lib/dom.ts +0 -2728
- package/dist/lib/form.ts +0 -3829
- package/dist/lib/fs.ts +0 -1324
- package/dist/lib/native.ts +0 -236
- package/dist/lib/storage.ts +0 -229
- package/dist/lib/task.ts +0 -2160
- package/dist/lib/theme.ts +0 -199
- package/dist/lib/tool.ts +0 -1278
- package/dist/lib/zip.ts +0 -444
- package/eslint.config.js +0 -22
package/dist/lib/tool.ts
DELETED
|
@@ -1,1278 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright 2024 Han Guoshuai <zohegs@gmail.com>
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
* you may not use this file except in compliance with the License.
|
|
6
|
-
* You may obtain a copy of the License at
|
|
7
|
-
*
|
|
8
|
-
* https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
*
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
import * as types from '../../types';
|
|
17
|
-
import * as core from './core';
|
|
18
|
-
|
|
19
|
-
/** --- compressorjs, mit --- */
|
|
20
|
-
let compressorjs: any = null;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* --- 压缩一个图片 ---
|
|
24
|
-
* @param file 文件或 blob 类型 ---
|
|
25
|
-
* @param options 参数
|
|
26
|
-
*/
|
|
27
|
-
export async function compressor<T extends File | Blob>(file: T, options: {
|
|
28
|
-
/** --- 最大宽度,默认无限 --- */
|
|
29
|
-
'maxWidth'?: number;
|
|
30
|
-
/** --- 最高高度,默认无限 --- */
|
|
31
|
-
'maxHeight'?: number;
|
|
32
|
-
/** --- 压缩质量,默认 0.8 --- */
|
|
33
|
-
'quality'?: number;
|
|
34
|
-
} = {}): Promise<File | Blob | false> {
|
|
35
|
-
if (!compressorjs) {
|
|
36
|
-
try {
|
|
37
|
-
compressorjs = await core.getModule('compressorjs');
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return new Promise((resolve) => {
|
|
44
|
-
new compressorjs(file, {
|
|
45
|
-
'quality': options.quality,
|
|
46
|
-
'maxWidth': options.maxWidth,
|
|
47
|
-
'maxHeight': options.maxHeight,
|
|
48
|
-
success: (result: T) => {
|
|
49
|
-
resolve(result);
|
|
50
|
-
},
|
|
51
|
-
error: () => {
|
|
52
|
-
resolve(false);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface IClassPrototype {
|
|
59
|
-
'method': Record<string, any>;
|
|
60
|
-
'access': Record<string, {
|
|
61
|
-
'get'?: any;
|
|
62
|
-
'set'?: any;
|
|
63
|
-
}>;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* --- 获取 class 的所有 method 和 get/set ---
|
|
68
|
-
* @param obj 实例化 class 对象
|
|
69
|
-
* @param over 不传入此参数
|
|
70
|
-
* @param level 不传入此参数
|
|
71
|
-
*/
|
|
72
|
-
export function getClassPrototype(obj: object, over: string[] = [], level: number = 0): IClassPrototype {
|
|
73
|
-
if (level === 0) {
|
|
74
|
-
return getClassPrototype(Object.getPrototypeOf(obj), over, level + 1);
|
|
75
|
-
}
|
|
76
|
-
const rtn: IClassPrototype = {
|
|
77
|
-
'method': {},
|
|
78
|
-
'access': {}
|
|
79
|
-
};
|
|
80
|
-
const names = Object.getOwnPropertyNames(obj);
|
|
81
|
-
if (names.includes('toString')) {
|
|
82
|
-
return rtn;
|
|
83
|
-
}
|
|
84
|
-
for (const item of names) {
|
|
85
|
-
if (item === 'constructor') {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (over.includes(item)) {
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const des = Object.getOwnPropertyDescriptor(obj, item);
|
|
92
|
-
if (!des) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
over.push(item);
|
|
96
|
-
if (des.value) {
|
|
97
|
-
// --- method ---
|
|
98
|
-
rtn.method[item] = des.value;
|
|
99
|
-
}
|
|
100
|
-
else if (des.get ?? des.set) {
|
|
101
|
-
if (!rtn.access[item]) {
|
|
102
|
-
rtn.access[item] = {};
|
|
103
|
-
}
|
|
104
|
-
if (des.get) {
|
|
105
|
-
rtn.access[item].get = (des as any).get;
|
|
106
|
-
}
|
|
107
|
-
if (des.set) {
|
|
108
|
-
rtn.access[item].set = (des as any).set;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// --- 往上级检查 ---
|
|
113
|
-
const rtn2 = getClassPrototype(Object.getPrototypeOf(obj), over, level + 1);
|
|
114
|
-
Object.assign(rtn.method, rtn2.method);
|
|
115
|
-
Object.assign(rtn.access, rtn2.access);
|
|
116
|
-
return rtn;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* --- 将 blob 对象转换为 ArrayBuffer ---
|
|
121
|
-
* @param blob 对象
|
|
122
|
-
*/
|
|
123
|
-
export function blob2ArrayBuffer(blob: Blob): Promise<ArrayBuffer> {
|
|
124
|
-
return new Promise(function(resove) {
|
|
125
|
-
const fr = new FileReader();
|
|
126
|
-
fr.addEventListener('load', function() {
|
|
127
|
-
resove(fr.result as ArrayBuffer);
|
|
128
|
-
});
|
|
129
|
-
fr.readAsArrayBuffer(blob);
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* --- 将文件大小格式化为带单位的字符串 ---
|
|
135
|
-
* @param size 文件大小
|
|
136
|
-
* @param spliter 分隔符
|
|
137
|
-
*/
|
|
138
|
-
export function sizeFormat(size: number, spliter: string = ' '): string {
|
|
139
|
-
const units = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
140
|
-
let i = 0;
|
|
141
|
-
for (; i < 6 && size >= 1024.0; ++i) {
|
|
142
|
-
size /= 1024.0;
|
|
143
|
-
}
|
|
144
|
-
return (Math.round(size * 100) / 100).toString() + spliter + units[i];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* --- 将毫克重量格式化为带单位的字符串 ---
|
|
149
|
-
* @param weight 毫克重量
|
|
150
|
-
* @param spliter 分隔符
|
|
151
|
-
*/
|
|
152
|
-
export function weightFormat(weight: number, spliter: string = ' '): string {
|
|
153
|
-
const units = ['mg', 'g', 'kg'];
|
|
154
|
-
let i = 0;
|
|
155
|
-
for (; i < 3 && weight >= 1000; ++i) {
|
|
156
|
-
weight /= 1000;
|
|
157
|
-
}
|
|
158
|
-
return (Math.round(weight * 100) / 100).toString() + spliter + units[i];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* --- 完整的克隆一份数组/对象 ---
|
|
163
|
-
* @param obj 要克隆的对象
|
|
164
|
-
*/
|
|
165
|
-
export function clone(obj: Record<string, any> | any[]): any[] | any {
|
|
166
|
-
let newObj: any = {};
|
|
167
|
-
if (obj instanceof Array) {
|
|
168
|
-
newObj = [];
|
|
169
|
-
for (let i = 0; i < obj.length; ++i) {
|
|
170
|
-
if (obj[i] instanceof Date) {
|
|
171
|
-
newObj[i] = new Date(obj[i].getTime());
|
|
172
|
-
}
|
|
173
|
-
else if (obj[i] instanceof FormData) {
|
|
174
|
-
const fd = new FormData();
|
|
175
|
-
for (const item of obj[i]) {
|
|
176
|
-
fd.append(item[0], item[1]);
|
|
177
|
-
}
|
|
178
|
-
newObj[i] = fd;
|
|
179
|
-
}
|
|
180
|
-
else if (obj[i] === null) {
|
|
181
|
-
newObj[i] = null;
|
|
182
|
-
}
|
|
183
|
-
else if (typeof obj[i] === 'object') {
|
|
184
|
-
newObj[i] = clone(obj[i]);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
newObj[i] = obj[i];
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
for (const key in obj) {
|
|
193
|
-
if (obj[key] instanceof Date) {
|
|
194
|
-
newObj[key] = new Date(obj[key].getTime());
|
|
195
|
-
}
|
|
196
|
-
else if (obj[key] instanceof FormData) {
|
|
197
|
-
const fd = new FormData();
|
|
198
|
-
for (const item of obj[key]) {
|
|
199
|
-
fd.append(item[0], item[1]);
|
|
200
|
-
}
|
|
201
|
-
newObj[key] = fd;
|
|
202
|
-
}
|
|
203
|
-
else if (obj[key] === null) {
|
|
204
|
-
newObj[key] = null;
|
|
205
|
-
}
|
|
206
|
-
else if (typeof obj[key] === 'object') {
|
|
207
|
-
newObj[key] = clone(obj[key]);
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
newObj[key] = obj[key];
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return newObj;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* --- 等待毫秒 ---
|
|
219
|
-
* @param ms 等待的毫秒,默认 0,最大 30 秒
|
|
220
|
-
*/
|
|
221
|
-
export function sleep(ms: number = 0): Promise<boolean> {
|
|
222
|
-
return new Promise(function(resolve) {
|
|
223
|
-
if (ms > 30_000) {
|
|
224
|
-
resolve(false);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
window.setTimeout(function() {
|
|
228
|
-
resolve(true);
|
|
229
|
-
}, ms);
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* --- 等待浏览器帧 ---
|
|
235
|
-
*/
|
|
236
|
-
export function nextFrame(): Promise<void> {
|
|
237
|
-
return new Promise(function(resolve) {
|
|
238
|
-
requestAnimationFrame(() => {
|
|
239
|
-
resolve();
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* --- 等待浏览器帧 ---
|
|
246
|
-
* @param count 等待帧数最高 10 帧
|
|
247
|
-
*/
|
|
248
|
-
export async function sleepFrame(count: number): Promise<void> {
|
|
249
|
-
if (count > 10) {
|
|
250
|
-
count = 10;
|
|
251
|
-
}
|
|
252
|
-
for (let i = 0; i < count; ++i) {
|
|
253
|
-
await nextFrame();
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* --- 去除 html 的空白符、换行以及注释 ---
|
|
259
|
-
* @param text 要纯净的字符串
|
|
260
|
-
*/
|
|
261
|
-
export function purify(text: string): string {
|
|
262
|
-
text = '>' + text + '<';
|
|
263
|
-
const scripts: string[] = [];
|
|
264
|
-
let num: number = -1;
|
|
265
|
-
text = text.replace(/<!--([\s\S]*?)-->/g, '').replace(/<script[\s\S]+?<\/script>/g, function(t: string): string {
|
|
266
|
-
scripts.push(t);
|
|
267
|
-
return '[SCRIPT]';
|
|
268
|
-
}).replace(/>([\s\S]*?)</g, function(t: string, t1: string): string {
|
|
269
|
-
return '>' + t1.replace(/\t|\r\n| {2}/g, '').replace(/\n|\r/g, '') + '<';
|
|
270
|
-
}).replace(/\[SCRIPT\]/g, function(): string {
|
|
271
|
-
++num;
|
|
272
|
-
return scripts[num];
|
|
273
|
-
});
|
|
274
|
-
return text.slice(1, -1);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* --- 传入正则进行匹配 str 是否有一项满足 ---
|
|
279
|
-
* @param str 要检测的字符串
|
|
280
|
-
* @param regs 正则列表
|
|
281
|
-
*/
|
|
282
|
-
export function match(str: string, regs: RegExp[]): boolean {
|
|
283
|
-
for (const reg of regs) {
|
|
284
|
-
if (reg.test(str)) {
|
|
285
|
-
return true;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* --- 将 style 中的 url 转换成 base64 data url ---
|
|
293
|
-
* @param path 路径基准或以文件的路径为基准,以 / 结尾
|
|
294
|
-
* @param style 样式表
|
|
295
|
-
* @param files 在此文件列表中查找
|
|
296
|
-
*/
|
|
297
|
-
export async function styleUrl2DataUrl(
|
|
298
|
-
path: string,
|
|
299
|
-
style: string,
|
|
300
|
-
files: Record<string, Blob | string>
|
|
301
|
-
): Promise<string> {
|
|
302
|
-
const reg = /url\(["']{0,1}(.+?)["']{0,1}\)/ig;
|
|
303
|
-
let match: RegExpExecArray | null = null;
|
|
304
|
-
while ((match = reg.exec(style))) {
|
|
305
|
-
let realPath = urlResolve(path, match[1]);
|
|
306
|
-
if (realPath.startsWith('/package/')) {
|
|
307
|
-
// --- 处理 form 里面的路径 ---
|
|
308
|
-
realPath = realPath.slice(8);
|
|
309
|
-
}
|
|
310
|
-
if (!files[realPath]) {
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
if (typeof files[realPath] !== 'string') {
|
|
314
|
-
style = style.replace(match[0], `url('${await blob2DataUrl(files[realPath] as Blob)}')`);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
return style;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* --- 给标签增加 tag-tagname 的 class,同时给标签增加 cg- 前导(仅字符串,不是操作真实 dom) ---
|
|
322
|
-
* @param layout layout
|
|
323
|
-
* @param retagname 是否更改 tagname 为 cg-tagname
|
|
324
|
-
*/
|
|
325
|
-
export function layoutAddTagClassAndReTagName(layout: string, retagname: boolean): string {
|
|
326
|
-
// --- "" '' 引号中的内容先替换为 placeholder 排除掉干扰 ---
|
|
327
|
-
const list: string[] = [];
|
|
328
|
-
layout = layout.replace(/(\S+)=(".+?"|'.+?')/g, function(t, t1): string {
|
|
329
|
-
// --- t1 不是标签名,而是 attr 名,例如 class="xxx"、style="xxx" ---
|
|
330
|
-
if (t1 === 'class') {
|
|
331
|
-
return t;
|
|
332
|
-
}
|
|
333
|
-
list.push(t);
|
|
334
|
-
return '"CG-PLACEHOLDER"';
|
|
335
|
-
});
|
|
336
|
-
// --- 开始添加 class tag ---
|
|
337
|
-
layout = layout.replace(/<(\/{0,1})([\w-]+)([\s\S]*?>)/g, function(t, t1, t2: string, t3: string): string {
|
|
338
|
-
// --- t1 是 /,t2 是 tagname,t3 是标签其他内容 ---
|
|
339
|
-
if (['template', 'slot', 'teleport'].includes(t2)) {
|
|
340
|
-
return t;
|
|
341
|
-
}
|
|
342
|
-
else {
|
|
343
|
-
// --- 需要给 class 添加 cg-xxx ---
|
|
344
|
-
if (t1 === '/') {
|
|
345
|
-
if (t2 === 'block') {
|
|
346
|
-
return '</div' + t3;
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
return retagname ? ('</cg-' + t2 + t3) : t;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (t3.toLowerCase().includes(' class')) {
|
|
353
|
-
// --- 有 class,前置增加 ---
|
|
354
|
-
t3 = t3.replace(/ class=(["']{0,1})/i, ' class=$1tag-' + t2 + ' ');
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
// --- 无 class 的 attr,增加 attr ---
|
|
358
|
-
t3 = ` class="tag-${t2}"` + t3;
|
|
359
|
-
}
|
|
360
|
-
if (t2 === 'block') {
|
|
361
|
-
return '<div' + t3;
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
return retagname ? ('<cg-' + t2 + t3) : ('<' + t2 + t3);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
// --- 恢复 placeholder ---
|
|
369
|
-
let i = -1;
|
|
370
|
-
return layout.replace(/"CG-PLACEHOLDER"/g, function() {
|
|
371
|
-
return list[++i];
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* --- 给标签追加 attr,即使 attr 存在也会追加上一个新的(非真实 DOM 操作,仅仅是对字符串进行处理) ---
|
|
377
|
-
* @param layout 被追加
|
|
378
|
-
* @param insert 要追加
|
|
379
|
-
* @param opt 选项, ignore 忽略的标签,include 包含的标签
|
|
380
|
-
*/
|
|
381
|
-
export function layoutInsertAttr(layout: string, insert: string, opt: { 'ignore'?: RegExp[]; 'include'?: RegExp[]; } = {}): string {
|
|
382
|
-
return layout.replace(/<([\w-]+)[\s\S]*?>/g, function(t, t1): string {
|
|
383
|
-
if (opt.ignore) {
|
|
384
|
-
for (const item of opt.ignore) {
|
|
385
|
-
if (item.test(t1)) {
|
|
386
|
-
return t;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
if (opt.include) {
|
|
391
|
-
let found = false;
|
|
392
|
-
for (const item of opt.include) {
|
|
393
|
-
if (item.test(t1)) {
|
|
394
|
-
found = true;
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (!found) {
|
|
399
|
-
return t;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return t.replace(/<[\w-]+/, function(t) {
|
|
403
|
-
return t + ' ' + insert;
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* --- 对 :class 的对象增加实时 css 的前缀 ---
|
|
410
|
-
* @param object :class 中的 {'xxx': xxx, [yyy]: yyy} 字符串
|
|
411
|
-
*/
|
|
412
|
-
function layoutClassPrependObject(object: string): string {
|
|
413
|
-
object = object.slice(1, -1).trim();
|
|
414
|
-
/*
|
|
415
|
-
return '{' + object.replace(/(.+?):(.+?)(,|$)/g, function(t, t1: string, t2: string, t3: string) {
|
|
416
|
-
// --- t1 是 'xxx', t2 是 xxx,t3 是结尾或者 , 分隔符 ---
|
|
417
|
-
t1 = t1.trim();
|
|
418
|
-
if (t1.startsWith('[')) {
|
|
419
|
-
t1 = '[classPrepend(' + t1.slice(1, -1) + ')]';
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
let sp = '';
|
|
423
|
-
if (t1.startsWith('\'') || t1.startsWith('"')) {
|
|
424
|
-
sp = t1[0];
|
|
425
|
-
t1 = t1.slice(1, -1);
|
|
426
|
-
}
|
|
427
|
-
t1 = `[classPrepend(${sp}${t1}${sp})]`;
|
|
428
|
-
}
|
|
429
|
-
return t1 + ':' + t2 + t3;
|
|
430
|
-
}) + '}';
|
|
431
|
-
//*/
|
|
432
|
-
return '{' + object.replace(/([ a-zA-Z0-9'"`[\]\-_]+)(\s*:)/g, function(t, t1: string, t2: string) {
|
|
433
|
-
// --- t1 是 'xxx', t2 是 xxx,t3 是结尾或者 , 分隔符 ---
|
|
434
|
-
t1 = t1.trim();
|
|
435
|
-
if (t1.startsWith('[')) {
|
|
436
|
-
t1 = '[classPrepend(' + t1.slice(1, -1) + ')]';
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
let sp = '';
|
|
440
|
-
if (t1.startsWith('\'') || t1.startsWith('"') || t1.startsWith('`')) {
|
|
441
|
-
sp = t1[0];
|
|
442
|
-
t1 = t1.slice(1, -1);
|
|
443
|
-
}
|
|
444
|
-
t1 = `[classPrepend(${sp}${t1}${sp})]`;
|
|
445
|
-
}
|
|
446
|
-
return t1 + t2;
|
|
447
|
-
}) + '}';
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* --- 给 class 增加 scope 的随机前缀,给 id 新增前缀 ---
|
|
451
|
-
* @param layout layout
|
|
452
|
-
* @param preps 前置标识符列表,特殊字符串 scope 会被替换为随机前缀
|
|
453
|
-
*/
|
|
454
|
-
export function layoutClassPrepend(layout: string, preps: string[]): string {
|
|
455
|
-
return layout.replace(/ class=["'](.+?)["']/gi, function(t, t1: string) {
|
|
456
|
-
// --- t1 为 xxx yyy zzz 这样的 ----
|
|
457
|
-
t1 = t1.trim();
|
|
458
|
-
const classList = t1.split(' ');
|
|
459
|
-
const resultList: string[] = [];
|
|
460
|
-
for (const item of classList) {
|
|
461
|
-
for (const prep of preps) {
|
|
462
|
-
resultList.push(prep + item);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
return ` class="${resultList.join(' ')}"`;
|
|
466
|
-
//}).replace(/ :class=(["']).+?>/gi, function(t, sp) {
|
|
467
|
-
}).replace(/ :class=(["']).+?["']((\s+[a-zA-Z0-9-_:@]+(=|\s*>))|(\s*)>)/gi, function(t, sp) {
|
|
468
|
-
return t.replace(new RegExp(` :class=${sp}(.+?)${sp}((\\s+[a-zA-Z0-9-_:@]+(=|\\s*>))|(\\s*)>)`, 'gi'), function(t, t1: string, t2: string) {
|
|
469
|
-
// --- t1 为 [] 或 {} ---
|
|
470
|
-
t1 = t1.trim();
|
|
471
|
-
if (t1.startsWith('[')) {
|
|
472
|
-
// --- ['xxx', yyy && 'yyy'] ---
|
|
473
|
-
t1 = t1.slice(1, -1);
|
|
474
|
-
/** --- 'xxx', yyy && 'yyy' 的数组 --- */
|
|
475
|
-
const t1a = t1.split(',');
|
|
476
|
-
for (let i = 0; i < t1a.length; ++i) {
|
|
477
|
-
t1a[i] = t1a[i].trim();
|
|
478
|
-
if (t1a[i].startsWith('{')) {
|
|
479
|
-
t1a[i] = layoutClassPrependObject(t1a[i]);
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
t1a[i] = 'classPrepend(' + t1a[i] + ')';
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
t1 = '[' + t1a.join(',') + ']';
|
|
486
|
-
}
|
|
487
|
-
else {
|
|
488
|
-
t1 = layoutClassPrependObject(t1);
|
|
489
|
-
}
|
|
490
|
-
return ` :class="${t1}"${t2}`;
|
|
491
|
-
});
|
|
492
|
-
}).replace(/ id=(["'])/gi, ' id=$1' + preps[0]);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* --- 对 layout 的 @events 事件进行包裹 ---
|
|
497
|
-
* @param layout 要包裹的 layout
|
|
498
|
-
*/
|
|
499
|
-
export function eventsAttrWrap(layout: string): string {
|
|
500
|
-
const events = ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mouseup', 'touchstart', 'touchmove', 'touchend', 'keydown', 'keypress', 'keyup', 'contextmenu'];
|
|
501
|
-
const reg = new RegExp(`@(${events.join('|')})="(.+?)"`, 'g');
|
|
502
|
-
return layout.replace(reg, function(t, t1, t2): string {
|
|
503
|
-
if (/^[\w]+$/.test(t2)) {
|
|
504
|
-
return `@${t1}="allowEvent($event) && ${t2}($event)"`;
|
|
505
|
-
}
|
|
506
|
-
return `@${t1}=";if(allowEvent($event)){${t2}}"`;
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* --- 对 layout 的 teleport 做转义处理为 vue 识别的内容 ---
|
|
512
|
-
* @param layout 要处理的窗体或控件的 layout
|
|
513
|
-
* @param formId 要加入的 formId
|
|
514
|
-
*/
|
|
515
|
-
export function teleportGlue(layout: string, formId: number | string): string {
|
|
516
|
-
if (typeof formId !== 'string') {
|
|
517
|
-
formId = formId.toString();
|
|
518
|
-
}
|
|
519
|
-
const fid = formId;
|
|
520
|
-
return layout.replace(/<teleport([\s\S]+?)to="(.+?)"([\s\S]+?<[\w-]+)/g, (v, v1, v2, v3): string => {
|
|
521
|
-
if (v2 !== 'system') {
|
|
522
|
-
return v;
|
|
523
|
-
}
|
|
524
|
-
// --- 给 teleport 的第一个子元素增加 cg-pop、cg-pop-none 的 data ---
|
|
525
|
-
return '<teleport' + v1 + 'to="#cg-pop-list > [data-form-id=\'' + fid + '\']"' + v3 + ' data-cg-pop data-cg-pop-none';
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* --- 给 class 前部增加唯一标识符 ---
|
|
531
|
-
* @param style 样式内容
|
|
532
|
-
* @param prep 给 class、font 等增加前置
|
|
533
|
-
*/
|
|
534
|
-
export function stylePrepend(style: string, prep: string = ''): { 'style': string; 'prep': string; } {
|
|
535
|
-
if (prep === '') {
|
|
536
|
-
prep = 'cg-scope' + Math.round(Math.random() * 1000000000000000).toString() + '_';
|
|
537
|
-
}
|
|
538
|
-
style = style.replace(/([\s\S]+?){([\s\S]+?)}/g, function(t, t1: string, t2: string) {
|
|
539
|
-
// --- xxx { xxx; } ---
|
|
540
|
-
// --- 将 element 模式的 css 变为 class 模式,如 div 变为 .tag-div ---
|
|
541
|
-
// --- 这里面遇到了一个 bug, @keyframe 也被转换了,这是不对的,因此在下面修复 ---
|
|
542
|
-
t1 = t1.replace(/(^|[ >,}\r\n])([a-zA-Z-_][a-zA-Z0-9-_]*)/g,
|
|
543
|
-
function(t: string, t1: string, t2: string) {
|
|
544
|
-
if (t2 === 'global') {
|
|
545
|
-
return '[CGTMP-GLOBAL]';
|
|
546
|
-
}
|
|
547
|
-
return t1 + '.tag-' + t2;
|
|
548
|
-
}
|
|
549
|
-
);
|
|
550
|
-
t1 = t1.replace(/keyframes \.tag-([a-zA-Z0-9-_]+)/g, function(t: string, t1: string) {
|
|
551
|
-
return 'keyframes ' + t1;
|
|
552
|
-
});
|
|
553
|
-
// --- 给 style 的 class 前添加 scope ---
|
|
554
|
-
t1 = t1.replace(/([.#])([a-zA-Z0-9-_]+)/g, function(t: string, t1: string, t2: string) {
|
|
555
|
-
/*
|
|
556
|
-
if (t2.startsWith('cg-')) {
|
|
557
|
-
return t;
|
|
558
|
-
}
|
|
559
|
-
*/
|
|
560
|
-
return t1 + prep + t2;
|
|
561
|
-
}) + '{' + t2 + '}';
|
|
562
|
-
return t1;
|
|
563
|
-
});
|
|
564
|
-
// --- 自定义 font 名添加 scope ---
|
|
565
|
-
const fontList: string[] = [];
|
|
566
|
-
style = style.replace(/(@font-face[\s\S]+?font-family\s*:\s*["']{0,1})(.+?)(["']{0,1}\s*[;\r\n }])/gi,
|
|
567
|
-
function(t, t1: string, t2: string, t3: string) {
|
|
568
|
-
fontList.push(t2);
|
|
569
|
-
return t1 + prep + t2 + t3;
|
|
570
|
-
}
|
|
571
|
-
);
|
|
572
|
-
// --- 对自定义 font 进行更名 ---
|
|
573
|
-
for (const font of fontList) {
|
|
574
|
-
const reg = new RegExp(`(font.+?[: "'])(${font})`, 'gi');
|
|
575
|
-
style = style.replace(reg, function(t, t1: string, t2: string) {
|
|
576
|
-
return t1 + prep + t2;
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
// --- keyframes ---
|
|
580
|
-
const keyframeList: string[] = [];
|
|
581
|
-
style = style.replace(/([-@]keyframes *["']{0,1})([\w-]+)(["']{0,1}\s*?\{)/gi,
|
|
582
|
-
function(t, t1: string, t2: string, t3: string) {
|
|
583
|
-
if (!keyframeList.includes(t2)) {
|
|
584
|
-
keyframeList.push(t2);
|
|
585
|
-
}
|
|
586
|
-
return t1 + prep + t2 + t3;
|
|
587
|
-
}
|
|
588
|
-
);
|
|
589
|
-
// --- 对自定义 keyframe 进行更名 ---
|
|
590
|
-
for (const keyframe of keyframeList) {
|
|
591
|
-
let reg = new RegExp(`(animation[ :\\r\\n]+)(${keyframe})([ ;}\\r\\n])`, 'gi');
|
|
592
|
-
style = style.replace(reg, function(t, t1: string, t2: string, t3: string) {
|
|
593
|
-
return t1 + prep + t2 + t3;
|
|
594
|
-
});
|
|
595
|
-
reg = new RegExp(`(animation-name[ :\\r\\n]+)(${keyframe})([ ;}\\r\\n])`, 'gi');
|
|
596
|
-
style = style.replace(reg, function(t, t1: string, t2: string, t3: string) {
|
|
597
|
-
return t1 + prep + t2 + t3;
|
|
598
|
-
});
|
|
599
|
-
// --- 为什么分两个呢?一个加 (-name)? 也行,但是后面的 tx 不好合并 ---
|
|
600
|
-
}
|
|
601
|
-
return {
|
|
602
|
-
'prep': prep,
|
|
603
|
-
'style': style
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* --- 根据后缀、文件名或路径获取 mime 类型(简单版,完整版请使用 @litert/mime.js) ---
|
|
609
|
-
* @param path 后缀、文件名或路径
|
|
610
|
-
*/
|
|
611
|
-
export function getMimeByPath(path: string): { 'mime': string; 'ext': string; } {
|
|
612
|
-
const lio = path.lastIndexOf('.');
|
|
613
|
-
const ext: string = (lio === -1 ? path : path.slice(lio + 1)).toLowerCase();
|
|
614
|
-
const exts: Record<string, string> = {
|
|
615
|
-
'eot': 'application/vnd.ms-fontobject',
|
|
616
|
-
'woff': 'font/woff',
|
|
617
|
-
'ttf': 'font/ttf',
|
|
618
|
-
'svg': 'image/svg+xml',
|
|
619
|
-
'jpg': 'image/jpeg',
|
|
620
|
-
'jpeg': 'image/jpeg',
|
|
621
|
-
'gif': 'image/gif',
|
|
622
|
-
'png': 'image/png'
|
|
623
|
-
};
|
|
624
|
-
const mime: string = exts[ext] ?? 'application/octet-stream';
|
|
625
|
-
return {
|
|
626
|
-
'mime': mime,
|
|
627
|
-
'ext': ext
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* --- 生成范围内的随机数 ---
|
|
633
|
-
* @param min 最新范围
|
|
634
|
-
* @param max 最大范围
|
|
635
|
-
*/
|
|
636
|
-
export function rand(min: number, max: number): number {
|
|
637
|
-
if (min > max) {
|
|
638
|
-
[min, max] = [max, min];
|
|
639
|
-
}
|
|
640
|
-
return min + Math.round(Math.random() * (max - min));
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
export const RANDOM_N = '0123456789';
|
|
644
|
-
export const RANDOM_U = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
645
|
-
export const RANDOM_L = 'abcdefghijklmnopqrstuvwxyz';
|
|
646
|
-
|
|
647
|
-
export const RANDOM_UN = RANDOM_U + RANDOM_N;
|
|
648
|
-
export const RANDOM_LN = RANDOM_L + RANDOM_N;
|
|
649
|
-
export const RANDOM_LU = RANDOM_L + RANDOM_U;
|
|
650
|
-
export const RANDOM_LUN = RANDOM_L + RANDOM_U + RANDOM_N;
|
|
651
|
-
export const RANDOM_V = 'ACEFGHJKLMNPRSTWXY34567';
|
|
652
|
-
export const RANDOM_LUNS = RANDOM_LUN + '()`~!@#$%^&*-+=_|{}[]:;\'<>,.?/]"';
|
|
653
|
-
export function random(length: number = 8, source: string = RANDOM_LN, block: string = ''): string {
|
|
654
|
-
// --- 剔除 block 字符 ---
|
|
655
|
-
let len = block.length;
|
|
656
|
-
if (len > 0) {
|
|
657
|
-
for (let i = 0; i < len; ++i) {
|
|
658
|
-
source = source.replace(block[i], '');
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
len = source.length;
|
|
662
|
-
if (len === 0) {
|
|
663
|
-
return '';
|
|
664
|
-
}
|
|
665
|
-
let temp = '';
|
|
666
|
-
for (let i = 0; i < length; ++i) {
|
|
667
|
-
temp += source[rand(0, len - 1)];
|
|
668
|
-
}
|
|
669
|
-
return temp;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* --- 根据参数获取最终的布尔值 ---
|
|
674
|
-
* @param param 参数
|
|
675
|
-
*/
|
|
676
|
-
export function getBoolean(param: boolean | string | number | undefined): boolean {
|
|
677
|
-
const t = typeof param;
|
|
678
|
-
if (t === 'boolean') {
|
|
679
|
-
return param as boolean;
|
|
680
|
-
}
|
|
681
|
-
if (t === 'string') {
|
|
682
|
-
return param === 'false' ? false : true;
|
|
683
|
-
}
|
|
684
|
-
return param ? true : false;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/**
|
|
688
|
-
* --- 根据参数获取最终的数字型 ---
|
|
689
|
-
* @param param 参数
|
|
690
|
-
*/
|
|
691
|
-
export function getNumber(param: string | number): number {
|
|
692
|
-
if (typeof param === 'number') {
|
|
693
|
-
return param;
|
|
694
|
-
}
|
|
695
|
-
return parseFloat(param);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
/**
|
|
699
|
-
* --- 根据参数获取最终的数组型,可传入类似 [1,2,3] 或 1,2,3 ---
|
|
700
|
-
* @param param 参数
|
|
701
|
-
*/
|
|
702
|
-
export function getArray(param: string | any[]): any[] {
|
|
703
|
-
if (typeof param !== 'string') {
|
|
704
|
-
return param;
|
|
705
|
-
}
|
|
706
|
-
param = param.trim();
|
|
707
|
-
let rtn: any[] = [];
|
|
708
|
-
if (param.startsWith('[')) {
|
|
709
|
-
try {
|
|
710
|
-
rtn = JSON.parse(param);
|
|
711
|
-
}
|
|
712
|
-
catch {
|
|
713
|
-
return [];
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
else {
|
|
717
|
-
param = param.replace(/ /g, '');
|
|
718
|
-
rtn = param.split(',');
|
|
719
|
-
}
|
|
720
|
-
return rtn;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* --- 转义 HTML ---
|
|
725
|
-
* @param html HTML 字符
|
|
726
|
-
*/
|
|
727
|
-
export function escapeHTML(html: string): string {
|
|
728
|
-
return html.replace(/</g, '<').replace(/>/g, '>');
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* --- 将 rgb 或 hsl 等颜色转换为数字数组 ---
|
|
733
|
-
* @param color 颜色字符串
|
|
734
|
-
*/
|
|
735
|
-
export function formatColor(color: string): number[] {
|
|
736
|
-
const matc = /[0-9.%, ]+/.exec(color);
|
|
737
|
-
if (!matc) {
|
|
738
|
-
return [];
|
|
739
|
-
}
|
|
740
|
-
const arr = matc[0].split(',');
|
|
741
|
-
return arr.map((v: string) => {
|
|
742
|
-
return parseFloat(v);
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* --- 将 r, g, b 转换为 hex 字符串,不含 # ---
|
|
748
|
-
* @param r r 或 rgb 用 , 分隔的字符串
|
|
749
|
-
* @param g 可留空,g
|
|
750
|
-
* @param b 可留空,b
|
|
751
|
-
*/
|
|
752
|
-
export function rgb2hex(r: string | number, g?: string | number, b?: string | number, a: string | number = 1): string {
|
|
753
|
-
if (g === undefined || b === undefined) {
|
|
754
|
-
if (typeof r !== 'string') {
|
|
755
|
-
return '';
|
|
756
|
-
}
|
|
757
|
-
const rgb = formatColor(r);
|
|
758
|
-
r = Math.round(rgb[0]);
|
|
759
|
-
g = Math.round(rgb[1]);
|
|
760
|
-
b = Math.round(rgb[2]);
|
|
761
|
-
a = rgb[3] ?? 1;
|
|
762
|
-
}
|
|
763
|
-
else {
|
|
764
|
-
if (typeof r === 'string') {
|
|
765
|
-
r = Math.round(parseFloat(r));
|
|
766
|
-
}
|
|
767
|
-
if (typeof g === 'string') {
|
|
768
|
-
g = Math.round(parseFloat(g));
|
|
769
|
-
}
|
|
770
|
-
if (typeof b === 'string') {
|
|
771
|
-
b = Math.round(parseFloat(b));
|
|
772
|
-
}
|
|
773
|
-
if (typeof a === 'string') {
|
|
774
|
-
a = parseFloat(a);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
return ((r << 16) + (g << 8) + b).toString(16).padStart(6, '0') + (a === 1 ? '' : Math.round(a * 255).toString(16).padStart(2, '0'));
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
/**
|
|
781
|
-
* --- hex 转换为 rgba,#27ae60ff, 27ae60 #fff
|
|
782
|
-
* @param hex hex 字符串,无所谓带不带 #
|
|
783
|
-
*/
|
|
784
|
-
export function hex2rgb(hex: string): {
|
|
785
|
-
'r': number;
|
|
786
|
-
'g': number;
|
|
787
|
-
'b': number;
|
|
788
|
-
'a': number;
|
|
789
|
-
'rgb': string;
|
|
790
|
-
} {
|
|
791
|
-
const rgb = {
|
|
792
|
-
'r': 0,
|
|
793
|
-
'g': 0,
|
|
794
|
-
'b': 0,
|
|
795
|
-
'a': 1,
|
|
796
|
-
'rgb': 'rgb'
|
|
797
|
-
};
|
|
798
|
-
let alpha = false,
|
|
799
|
-
h = hex.slice(hex.startsWith('#') ? 1 : 0);
|
|
800
|
-
if (h.length === 3) {
|
|
801
|
-
h = [...h].map(x => x + x).join('');
|
|
802
|
-
}
|
|
803
|
-
else if (h.length === 8) {
|
|
804
|
-
alpha = true;
|
|
805
|
-
}
|
|
806
|
-
const hn = parseInt(h, 16);
|
|
807
|
-
rgb.r = (hn >>> (alpha ? 24 : 16));
|
|
808
|
-
rgb.g = (hn & (alpha ? 0x00ff0000 : 0x00ff00)) >>> (alpha ? 16 : 8);
|
|
809
|
-
rgb.b = (hn & (alpha ? 0x0000ff00 : 0x0000ff)) >>> (alpha ? 8 : 0);
|
|
810
|
-
if (alpha) {
|
|
811
|
-
rgb.a = Math.round((hn & 0x000000ff) / 255 * 100) / 1000;
|
|
812
|
-
}
|
|
813
|
-
rgb.rgb = `${alpha ? 'a' : ''}(${rgb.r},${rgb.g},${rgb.b}${alpha ? ',' + rgb.a : ''})`;
|
|
814
|
-
return rgb;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
/**
|
|
818
|
-
* --- rgb 字符串转 hsl 数组 ---
|
|
819
|
-
* @param rgb rgb(x, x, x) 或直接 x,x,x
|
|
820
|
-
*/
|
|
821
|
-
export function rgb2hsl(
|
|
822
|
-
r: string | number, g?: string | number, b?: string | number, a: string | number = 1, decimal: boolean = false
|
|
823
|
-
): {
|
|
824
|
-
'h': number;
|
|
825
|
-
's': number;
|
|
826
|
-
'l': number;
|
|
827
|
-
'a': number;
|
|
828
|
-
'hsl': string;
|
|
829
|
-
} {
|
|
830
|
-
const hsl = {
|
|
831
|
-
'h': 0,
|
|
832
|
-
's': 0,
|
|
833
|
-
'l': 0,
|
|
834
|
-
'a': 1,
|
|
835
|
-
'hsl': 'hsl'
|
|
836
|
-
};
|
|
837
|
-
if (g === undefined || b === undefined) {
|
|
838
|
-
if (typeof r !== 'string') {
|
|
839
|
-
return hsl;
|
|
840
|
-
}
|
|
841
|
-
const rgb = formatColor(r);
|
|
842
|
-
r = rgb[0];
|
|
843
|
-
g = rgb[1];
|
|
844
|
-
b = rgb[2];
|
|
845
|
-
a = rgb[3] ?? 1;
|
|
846
|
-
}
|
|
847
|
-
else {
|
|
848
|
-
if (typeof r === 'string') {
|
|
849
|
-
r = parseFloat(r);
|
|
850
|
-
}
|
|
851
|
-
if (typeof g === 'string') {
|
|
852
|
-
g = parseFloat(g);
|
|
853
|
-
}
|
|
854
|
-
if (typeof b === 'string') {
|
|
855
|
-
b = parseFloat(b);
|
|
856
|
-
}
|
|
857
|
-
if (typeof a === 'string') {
|
|
858
|
-
a = parseFloat(a);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
r /= 255;
|
|
863
|
-
g /= 255;
|
|
864
|
-
b /= 255;
|
|
865
|
-
const cmin = Math.min(r, g, b),
|
|
866
|
-
cmax = Math.max(r, g, b),
|
|
867
|
-
delta = cmax - cmin;
|
|
868
|
-
let h = 0,
|
|
869
|
-
s = 0,
|
|
870
|
-
l = 0;
|
|
871
|
-
if (delta == 0) {
|
|
872
|
-
h = 0;
|
|
873
|
-
}
|
|
874
|
-
// --- Red is max ---
|
|
875
|
-
else if (cmax == r) {
|
|
876
|
-
h = ((g - b) / delta) % 6;
|
|
877
|
-
}
|
|
878
|
-
// --- Green is max ---
|
|
879
|
-
else if (cmax == g) {
|
|
880
|
-
h = (b - r) / delta + 2;
|
|
881
|
-
}
|
|
882
|
-
// --- Blue is max ---
|
|
883
|
-
else {
|
|
884
|
-
h = (r - g) / delta + 4;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
h = Math.round(h * 60);
|
|
888
|
-
|
|
889
|
-
// --- Make negative hues positive behind 360° ---
|
|
890
|
-
if (h < 0) {
|
|
891
|
-
h += 360;
|
|
892
|
-
}
|
|
893
|
-
// --- Calculate lightness ---
|
|
894
|
-
l = (cmax + cmin) / 2;
|
|
895
|
-
|
|
896
|
-
// --- Calculate saturation ---
|
|
897
|
-
s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
|
|
898
|
-
|
|
899
|
-
// --- Multiply l and s by 100 ---
|
|
900
|
-
s = s * 100;
|
|
901
|
-
l = l * 100;
|
|
902
|
-
|
|
903
|
-
hsl.h = h;
|
|
904
|
-
hsl.s = s;
|
|
905
|
-
hsl.l = l;
|
|
906
|
-
hsl.a = a;
|
|
907
|
-
if (!decimal) {
|
|
908
|
-
hsl.h = Math.round(hsl.h);
|
|
909
|
-
hsl.s = Math.round(hsl.s);
|
|
910
|
-
hsl.l = Math.round(hsl.l);
|
|
911
|
-
}
|
|
912
|
-
hsl.hsl += (hsl.a === 1 ? '' : 'a') + `(${hsl.h},${hsl.s}%,${hsl.l}%${hsl.a === 1 ? '' : ',' + hsl.a})`;
|
|
913
|
-
return hsl;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
/**
|
|
917
|
-
* --- hsl 字符串转 rgb 数组 ---
|
|
918
|
-
* @param hsl hsl(x, x, x) 或直接 x,x,x
|
|
919
|
-
*/
|
|
920
|
-
export function hsl2rgb(
|
|
921
|
-
h: string | number, s?: string | number, l?: string | number, a: string | number = 1, decimal: boolean = false
|
|
922
|
-
): {
|
|
923
|
-
'r': number;
|
|
924
|
-
'g': number;
|
|
925
|
-
'b': number;
|
|
926
|
-
'a': number;
|
|
927
|
-
'rgb': string;
|
|
928
|
-
} {
|
|
929
|
-
const rgb = {
|
|
930
|
-
'r': 0,
|
|
931
|
-
'g': 0,
|
|
932
|
-
'b': 0,
|
|
933
|
-
'a': 1,
|
|
934
|
-
'rgb': 'rgb'
|
|
935
|
-
};
|
|
936
|
-
if (s === undefined || l === undefined) {
|
|
937
|
-
if (typeof h !== 'string') {
|
|
938
|
-
return rgb;
|
|
939
|
-
}
|
|
940
|
-
const hsl = formatColor(h);
|
|
941
|
-
h = hsl[0];
|
|
942
|
-
s = hsl[1];
|
|
943
|
-
l = hsl[2];
|
|
944
|
-
a = hsl[3] ?? 1;
|
|
945
|
-
}
|
|
946
|
-
else {
|
|
947
|
-
if (typeof h === 'string') {
|
|
948
|
-
h = parseFloat(h);
|
|
949
|
-
}
|
|
950
|
-
if (typeof s === 'string') {
|
|
951
|
-
s = parseFloat(s);
|
|
952
|
-
}
|
|
953
|
-
if (typeof l === 'string') {
|
|
954
|
-
l = parseFloat(l);
|
|
955
|
-
}
|
|
956
|
-
if (typeof a === 'string') {
|
|
957
|
-
a = parseFloat(a);
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
s /= 100;
|
|
962
|
-
l /= 100;
|
|
963
|
-
const k = (n: number): number => (n + h / 30) % 12;
|
|
964
|
-
const aa = s * Math.min(l, 1 - l);
|
|
965
|
-
const f = (n: number): number =>
|
|
966
|
-
l - aa * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
967
|
-
rgb.r = 255 * f(0);
|
|
968
|
-
rgb.g = 255 * f(8);
|
|
969
|
-
rgb.b = 255 * f(4);
|
|
970
|
-
rgb.a = a;
|
|
971
|
-
if (!decimal) {
|
|
972
|
-
rgb.r = Math.round(rgb.r);
|
|
973
|
-
rgb.g = Math.round(rgb.g);
|
|
974
|
-
rgb.b = Math.round(rgb.b);
|
|
975
|
-
}
|
|
976
|
-
rgb.rgb += (rgb.a === 1 ? '' : 'a') + `(${rgb.r},${rgb.g},${rgb.b}${rgb.a === 1 ? '' : ',' + rgb.a})`;
|
|
977
|
-
return rgb;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
/**
|
|
981
|
-
* --- 发起一个网络请求,若是返回值是 JSON 则自动解析,否则直接返回字符串 ---
|
|
982
|
-
* @param url 网址
|
|
983
|
-
* @param opt 选项
|
|
984
|
-
*/
|
|
985
|
-
export function request(url: string, opt: types.IRequestOptions): Promise<null | any> {
|
|
986
|
-
return new Promise(function(resove) {
|
|
987
|
-
const xhr = new XMLHttpRequest();
|
|
988
|
-
if (opt.credentials === false) {
|
|
989
|
-
xhr.withCredentials = false;
|
|
990
|
-
}
|
|
991
|
-
xhr.upload.onloadstart = function(e: ProgressEvent): void {
|
|
992
|
-
const r = opt.uploadStart?.(e.total);
|
|
993
|
-
if (r && (r instanceof Promise)) {
|
|
994
|
-
r.catch(function(e1) {
|
|
995
|
-
console.log(e1);
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
};
|
|
999
|
-
xhr.upload.onprogress = function(e: ProgressEvent): void {
|
|
1000
|
-
const r = opt.uploadProgress?.(e.loaded, e.total);
|
|
1001
|
-
if (r && (r instanceof Promise)) {
|
|
1002
|
-
r.catch(function(e1) {
|
|
1003
|
-
console.log(e1);
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
};
|
|
1007
|
-
xhr.upload.onloadend = function(): void {
|
|
1008
|
-
const r = opt.uploadEnd?.();
|
|
1009
|
-
if (r && (r instanceof Promise)) {
|
|
1010
|
-
r.catch(function(e) {
|
|
1011
|
-
console.log(e);
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
};
|
|
1015
|
-
xhr.onloadstart = function(e: ProgressEvent): void {
|
|
1016
|
-
const r = opt.start?.(e.total);
|
|
1017
|
-
if (r && (r instanceof Promise)) {
|
|
1018
|
-
r.catch(function(e1) {
|
|
1019
|
-
console.log(e1);
|
|
1020
|
-
});
|
|
1021
|
-
}
|
|
1022
|
-
};
|
|
1023
|
-
xhr.onprogress = function(e: ProgressEvent): void {
|
|
1024
|
-
const r = opt.progress?.(e.loaded, e.total);
|
|
1025
|
-
if (r && (r instanceof Promise)) {
|
|
1026
|
-
r.catch(function(e1) {
|
|
1027
|
-
console.log(e1);
|
|
1028
|
-
});
|
|
1029
|
-
}
|
|
1030
|
-
};
|
|
1031
|
-
xhr.onloadend = function(): void {
|
|
1032
|
-
const r = opt.end?.();
|
|
1033
|
-
if (r && (r instanceof Promise)) {
|
|
1034
|
-
r.catch(function(e) {
|
|
1035
|
-
console.log(e);
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
};
|
|
1039
|
-
xhr.onload = function(): void {
|
|
1040
|
-
let res = this.response;
|
|
1041
|
-
if (this.getResponseHeader('content-type')?.includes('json')) {
|
|
1042
|
-
try {
|
|
1043
|
-
res = JSON.parse(res);
|
|
1044
|
-
}
|
|
1045
|
-
catch {
|
|
1046
|
-
res = this.response;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
const r = opt.load?.(res);
|
|
1050
|
-
if (r && (r instanceof Promise)) {
|
|
1051
|
-
r.catch(function(e) {
|
|
1052
|
-
console.log(e);
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
resove(res);
|
|
1056
|
-
};
|
|
1057
|
-
xhr.onerror = function(): void {
|
|
1058
|
-
const r = opt.error?.();
|
|
1059
|
-
if (r && (r instanceof Promise)) {
|
|
1060
|
-
r.catch(function(e) {
|
|
1061
|
-
console.log(e);
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
resove(null);
|
|
1065
|
-
};
|
|
1066
|
-
if (opt.responseType) {
|
|
1067
|
-
xhr.responseType = opt.responseType;
|
|
1068
|
-
}
|
|
1069
|
-
if (opt.timeout) {
|
|
1070
|
-
xhr.timeout = opt.timeout;
|
|
1071
|
-
}
|
|
1072
|
-
if (opt.headers && !Array.isArray(opt.headers)) {
|
|
1073
|
-
for (const k in opt.headers) {
|
|
1074
|
-
xhr.setRequestHeader(k, (opt.headers as any)[k]);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
xhr.open(opt.method ?? 'GET', url, true);
|
|
1078
|
-
xhr.send(opt.body);
|
|
1079
|
-
});
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
export function fetch(url: string, init?: RequestInit): Promise<string | Blob | null> {
|
|
1083
|
-
return loader.fetch(url, init);
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
export function get(url: string, opt?: {
|
|
1087
|
-
'credentials'?: 'include' | 'same-origin' | 'omit';
|
|
1088
|
-
'headers'?: HeadersInit;
|
|
1089
|
-
}): Promise<Response | null> {
|
|
1090
|
-
return loader.get(url, opt);
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
export function post(url: string, data: Record<string, any> | FormData, opt?: {
|
|
1094
|
-
'credentials'?: 'include' | 'same-origin' | 'omit';
|
|
1095
|
-
'headers'?: HeadersInit;
|
|
1096
|
-
}): Promise<Response | null> {
|
|
1097
|
-
return loader.post(url, data, opt);
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
/** --- 发送 get 响应为 json 的网络数据,无需 try,失败返回 null */
|
|
1101
|
-
export async function getResponseJson(url: string, opt?: {
|
|
1102
|
-
'credentials'?: 'include' | 'same-origin' | 'omit';
|
|
1103
|
-
'headers'?: HeadersInit;
|
|
1104
|
-
}): Promise<any | null> {
|
|
1105
|
-
return loader.getResponseJson(url, opt);
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
/** --- 发送响应为 json 的网络数据,无需 try,失败返回 null --- */
|
|
1109
|
-
export async function postResponseJson(url: string, data: Record<string, any> | FormData, opt?: {
|
|
1110
|
-
'credentials'?: 'include' | 'same-origin' | 'omit';
|
|
1111
|
-
'headers'?: HeadersInit;
|
|
1112
|
-
}): Promise<any | null> {
|
|
1113
|
-
return loader.postResponseJson(url, data, opt);
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
export function parseUrl(url: string): ILoaderUrl {
|
|
1117
|
-
return loader.parseUrl(url);
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
export function urlResolve(from: string, to: string): string {
|
|
1121
|
-
return loader.urlResolve(from, to);
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
export function urlAtom(url: string): string {
|
|
1125
|
-
return loader.urlAtom(url);
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
export function blob2Text(blob: Blob): Promise<string> {
|
|
1129
|
-
return loader.blob2Text(blob);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
export function blob2DataUrl(blob: Blob): Promise<string> {
|
|
1133
|
-
return loader.blob2DataUrl(blob);
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
export function execCommand(ac: string): void {
|
|
1137
|
-
if (!['copy', 'cut'].includes(ac)) {
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1141
|
-
document.execCommand(ac);
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
/**
|
|
1145
|
-
* ---- 对比老值和新值,看看新值中哪些移除了,哪些新增了 ---
|
|
1146
|
-
* @param before 老值
|
|
1147
|
-
* @param after 新值
|
|
1148
|
-
*/
|
|
1149
|
-
export function compar(before: Array<string | number>, after: Array<string | number>): {
|
|
1150
|
-
'remove': Record<string, number>;
|
|
1151
|
-
'add': Record<string, number>;
|
|
1152
|
-
'length': {
|
|
1153
|
-
'remove': number;
|
|
1154
|
-
'add': number;
|
|
1155
|
-
};
|
|
1156
|
-
} {
|
|
1157
|
-
const rtn: {
|
|
1158
|
-
'remove': Record<string, number>;
|
|
1159
|
-
'add': Record<string, number>;
|
|
1160
|
-
'length': {
|
|
1161
|
-
'remove': number;
|
|
1162
|
-
'add': number;
|
|
1163
|
-
};
|
|
1164
|
-
} = {
|
|
1165
|
-
'remove': {},
|
|
1166
|
-
'add': {},
|
|
1167
|
-
'length': {
|
|
1168
|
-
'remove': 0,
|
|
1169
|
-
'add': 0
|
|
1170
|
-
}
|
|
1171
|
-
};
|
|
1172
|
-
for (let i = 0; i < before.length; ++i) {
|
|
1173
|
-
const item = before[i];
|
|
1174
|
-
if (after.includes(item)) {
|
|
1175
|
-
continue;
|
|
1176
|
-
}
|
|
1177
|
-
rtn.remove[item] = i;
|
|
1178
|
-
++rtn.length.remove;
|
|
1179
|
-
}
|
|
1180
|
-
for (let i = 0; i < after.length; ++i) {
|
|
1181
|
-
const item = after[i];
|
|
1182
|
-
if (before.includes(item)) {
|
|
1183
|
-
continue;
|
|
1184
|
-
}
|
|
1185
|
-
rtn.add[item] = i;
|
|
1186
|
-
++rtn.length.add;
|
|
1187
|
-
}
|
|
1188
|
-
return rtn;
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
/** --- 将秒数格式化为 0:0:0 的字符串 --- */
|
|
1192
|
-
export function formatSecond(second: number): string {
|
|
1193
|
-
const h = Math.floor(second / 3600);
|
|
1194
|
-
second = second - h * 3600;
|
|
1195
|
-
const m = Math.floor(second / 60);
|
|
1196
|
-
const s = Math.floor(second - m * 60);
|
|
1197
|
-
return (h ? h.toString().padStart(2, '0') + ':' : '') + m.toString().padStart(2, '0') + ':' + s.toString().padStart(2, '0');
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
/**
|
|
1201
|
-
* --- 将日期对象或毫秒级时间戳转换为字符串 ---
|
|
1202
|
-
* @param ts 时间戳或日期对象
|
|
1203
|
-
* @param tz 传入要显示的时区,小时,如 8,默认以当前客户端时区为准
|
|
1204
|
-
*/
|
|
1205
|
-
export function formatTime(ts: number | Date, tz?: number): {
|
|
1206
|
-
'date': string;
|
|
1207
|
-
'time': string;
|
|
1208
|
-
'zone': string;
|
|
1209
|
-
} {
|
|
1210
|
-
const rtn = {
|
|
1211
|
-
'date': '',
|
|
1212
|
-
'time': '',
|
|
1213
|
-
'zone': ''
|
|
1214
|
-
};
|
|
1215
|
-
// --- 代码开始 ---
|
|
1216
|
-
if (typeof ts === 'number') {
|
|
1217
|
-
ts = new Date(ts);
|
|
1218
|
-
}
|
|
1219
|
-
/** --- 当前设定的时区 --- */
|
|
1220
|
-
const ntz = tz ?? -(ts.getTimezoneOffset() / 60);
|
|
1221
|
-
ts.setTime(ts.getTime() + ntz * 60 * 60_000);
|
|
1222
|
-
rtn.date = ts.getUTCFullYear().toString() + '-' + (ts.getUTCMonth() + 1).toString().padStart(2, '0') + '-' + ts.getUTCDate().toString().padStart(2, '0');
|
|
1223
|
-
rtn.time = ts.getUTCHours().toString().padStart(2, '0') + ':' + ts.getUTCMinutes().toString().padStart(2, '0') + ':' + ts.getUTCSeconds().toString().padStart(2, '0');
|
|
1224
|
-
rtn.zone = 'UTC' + (ntz >= 0 ? '+' : '') + ntz.toString();
|
|
1225
|
-
return rtn;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
/**
|
|
1229
|
-
* --- 是否是毫秒 ---
|
|
1230
|
-
* @param time 要判断的时间戳
|
|
1231
|
-
*/
|
|
1232
|
-
export function isMs(time: number): boolean {
|
|
1233
|
-
return time > 1000000000000 ? true : false;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
/**
|
|
1237
|
-
* --- 将对象转换为 query string ---
|
|
1238
|
-
* @param query 要转换的对象
|
|
1239
|
-
*/
|
|
1240
|
-
export function queryStringify(query: Record<string, any>): string {
|
|
1241
|
-
return Object.entries(query).map(([k, v]) => {
|
|
1242
|
-
if (Array.isArray(v)) {
|
|
1243
|
-
return v.map((i) => `${encodeURIComponent(k)}=${encodeURIComponent(`${i}`)}`).join('&');
|
|
1244
|
-
}
|
|
1245
|
-
return `${encodeURIComponent(k)}=${encodeURIComponent(`${v}`)}`;
|
|
1246
|
-
}).join('&');
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
/**
|
|
1250
|
-
* --- 将 query string 转换为对象 ---
|
|
1251
|
-
* @param query 要转换的字符串
|
|
1252
|
-
*/
|
|
1253
|
-
export function queryParse(query: string): Record<string, string | string[]> {
|
|
1254
|
-
const ret: Record<string, string | string[]> = {};
|
|
1255
|
-
const arrayKeys: Record<string, boolean> = {};
|
|
1256
|
-
for (const i of query.split('&')) {
|
|
1257
|
-
if (!i.length) {
|
|
1258
|
-
continue;
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
const pos = i.indexOf('=');
|
|
1262
|
-
|
|
1263
|
-
const key = decodeURIComponent(pos === -1 ? i : i.slice(0, pos));
|
|
1264
|
-
const value = pos === -1 ? '' : decodeURIComponent(i.slice(pos + 1));
|
|
1265
|
-
|
|
1266
|
-
if (arrayKeys[key]) {
|
|
1267
|
-
(ret[key] as string[]).push(value);
|
|
1268
|
-
}
|
|
1269
|
-
else if (undefined === ret[key]) {
|
|
1270
|
-
ret[key] = value;
|
|
1271
|
-
}
|
|
1272
|
-
else {
|
|
1273
|
-
ret[key] = [ret[key] as string, value];
|
|
1274
|
-
arrayKeys[key] = true;
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
return ret;
|
|
1278
|
-
}
|