create-tengits-app 1.0.0
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/PUBLISH.md +151 -0
- package/README.md +255 -0
- package/USAGE.md +154 -0
- package/bin/cli.js +195 -0
- package/eslint.config.mjs +33 -0
- package/global.d.ts +9 -0
- package/package.json +67 -0
- package/packages/main/.env +24 -0
- package/packages/main/.env.pre +3 -0
- package/packages/main/.env.test +4 -0
- package/packages/main/.eslintrc +9 -0
- package/packages/main/README.md +18 -0
- package/packages/main/dist/manifest.json +40 -0
- package/packages/main/index.html +80 -0
- package/packages/main/index.ts +12 -0
- package/packages/main/package.json +43 -0
- package/packages/main/postcss.config.js +7 -0
- package/packages/main/public/antd.dark.css +26419 -0
- package/packages/main/public/dark.css +32 -0
- package/packages/main/public/evaluation-template.csv +10 -0
- package/packages/main/rsbuild.config.ts +79 -0
- package/packages/main/src/App.tsx +108 -0
- package/packages/main/src/configSystemData.ts +27 -0
- package/packages/main/src/i18n.js +140 -0
- package/packages/main/src/index.jsx +22 -0
- package/packages/main/src/index.less +250 -0
- package/packages/main/src/menus.jsx +123 -0
- package/packages/main/src/pages/Home/index.tsx +102 -0
- package/packages/main/src/pages/Login/common.ts +26 -0
- package/packages/main/src/pages/Login/index.less +15 -0
- package/packages/main/src/pages/Login/index.tsx +238 -0
- package/packages/main/src/routes.tsx +75 -0
- package/packages/main/src/styles/index.css +38 -0
- package/packages/main/src/types/less.d.ts +4 -0
- package/packages/main/src/types/tengitsui.d.ts +7 -0
- package/packages/main/src/types/utils.d.ts +39 -0
- package/packages/main/src/utils/checkPermission.js +19 -0
- package/packages/main/src/utils/day.js +18 -0
- package/packages/main/src/utils/download.js +30 -0
- package/packages/main/src/utils/fileUtils.ts +45 -0
- package/packages/main/src/utils/guid.js +6 -0
- package/packages/main/src/utils/gzip.js +26 -0
- package/packages/main/src/utils/importFile.js +51 -0
- package/packages/main/src/utils/index.ts +9 -0
- package/packages/main/src/utils/request.js +40 -0
- package/packages/main/src/utils/uploadFileToCloud.js +102 -0
- package/packages/main/src/utils/utils.ts +331 -0
- package/packages/main/src/utils/uuid.js +13 -0
- package/packages/main/src/utils/version.js +158 -0
- package/packages/main/tailwind.config.js +27 -0
- package/packages/main/tsconfig.json +6 -0
- package/pnpm-workspace.yaml +16 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import advancedFormat from 'dayjs/plugin/advancedFormat'
|
|
3
|
+
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
|
4
|
+
import localeData from 'dayjs/plugin/localeData'
|
|
5
|
+
import weekday from 'dayjs/plugin/weekday'
|
|
6
|
+
import weekOfYear from 'dayjs/plugin/weekOfYear'
|
|
7
|
+
import weekYear from 'dayjs/plugin/weekYear'
|
|
8
|
+
|
|
9
|
+
dayjs.extend(customParseFormat)
|
|
10
|
+
dayjs.extend(advancedFormat)
|
|
11
|
+
dayjs.extend(weekday)
|
|
12
|
+
dayjs.extend(localeData)
|
|
13
|
+
dayjs.extend(weekOfYear)
|
|
14
|
+
dayjs.extend(weekYear)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export default dayjs;
|
|
18
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const download = (url, name, body) => {
|
|
2
|
+
const newOptions = { method: 'POST' };
|
|
3
|
+
newOptions.headers = {
|
|
4
|
+
Authorization:
|
|
5
|
+
localStorage.getItem('token') || sessionStorage.getItem('token'),
|
|
6
|
+
};
|
|
7
|
+
newOptions.credentials = 'same-origin';
|
|
8
|
+
if (body) {
|
|
9
|
+
newOptions.headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
10
|
+
newOptions.body = JSON.stringify(body);
|
|
11
|
+
}
|
|
12
|
+
return fetch(url, newOptions).then(res => {
|
|
13
|
+
let filename;
|
|
14
|
+
try {
|
|
15
|
+
const fileNameEncode = res.headers
|
|
16
|
+
.get('content-disposition')
|
|
17
|
+
.split('filename=')[1];
|
|
18
|
+
filename = decodeURIComponent(fileNameEncode);
|
|
19
|
+
} catch (e) {}
|
|
20
|
+
res.blob().then(blob => {
|
|
21
|
+
const blobUrl = window.URL.createObjectURL(blob);
|
|
22
|
+
let a = document.createElement('a');
|
|
23
|
+
a.href = blobUrl;
|
|
24
|
+
a.download = name || filename || '下载文件';
|
|
25
|
+
a.traget = '_blank';
|
|
26
|
+
a.click();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
export default download;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取文件图标类型
|
|
3
|
+
* @param file 文件信息,可以是字符串(文件路径/URL)或对象(包含name和type属性)
|
|
4
|
+
* @returns 对应的图标类型名称
|
|
5
|
+
*/
|
|
6
|
+
export const getFileIconType = (file: string | { name?: string; type?: string }): string => {
|
|
7
|
+
const fileName = typeof file === 'string' ? file : file.name || '';
|
|
8
|
+
const fileExtension = fileName.toLowerCase().split('.').pop() || '';
|
|
9
|
+
|
|
10
|
+
const iconMap: Record<string, string> = {
|
|
11
|
+
'ppt': 'file-ppt-fill',
|
|
12
|
+
'pptx': 'file-ppt-fill',
|
|
13
|
+
'xls': 'file-excel-fill',
|
|
14
|
+
'xlsx': 'file-excel-fill',
|
|
15
|
+
'doc': 'file-word-fill',
|
|
16
|
+
'docx': 'file-word-fill',
|
|
17
|
+
'pdf': 'file-pdf-2-fill',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExtension)) {
|
|
21
|
+
return 'image-fill';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return iconMap[fileExtension] || 'file';
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 获取文件扩展名
|
|
29
|
+
* @param file 文件信息
|
|
30
|
+
* @returns 文件扩展名(不包含点号)
|
|
31
|
+
*/
|
|
32
|
+
export const getFileExtension = (file: string | { name?: string }): string => {
|
|
33
|
+
const fileName = typeof file === 'string' ? file : file.name || '';
|
|
34
|
+
return fileName.toLowerCase().split('.').pop() || '';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 判断是否为图片文件
|
|
39
|
+
* @param file 文件信息
|
|
40
|
+
* @returns 是否为图片文件
|
|
41
|
+
*/
|
|
42
|
+
export const isImageFile = (file: string | { name?: string }): boolean => {
|
|
43
|
+
const extension = getFileExtension(file);
|
|
44
|
+
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(extension);
|
|
45
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import pako from "pako";
|
|
2
|
+
// 解压
|
|
3
|
+
export function unzip(b64Data) {
|
|
4
|
+
const strData = atob(b64Data);
|
|
5
|
+
const charData = strData.split('').map(function (x) {
|
|
6
|
+
return x.charCodeAt(0);
|
|
7
|
+
});
|
|
8
|
+
const binData = new Uint8Array(charData);
|
|
9
|
+
const data = pako.inflate(binData);
|
|
10
|
+
const array = new Uint8Array(data);
|
|
11
|
+
// 防止一次解压造成内存溢出,这里进行分段解压
|
|
12
|
+
let result = '';
|
|
13
|
+
let i = 0;
|
|
14
|
+
const maxRange = 10 * 1024;
|
|
15
|
+
for (i = 0; i < array.length / maxRange; i++) {
|
|
16
|
+
result += String.fromCharCode.apply(null, array.slice(i * maxRange, (i + 1) * maxRange));
|
|
17
|
+
}
|
|
18
|
+
result += String.fromCharCode.apply(null, array.slice(i * maxRange));
|
|
19
|
+
return decodeURIComponent(result);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 压缩
|
|
23
|
+
export function zip(str) {
|
|
24
|
+
const binaryString = pako.gzip(encodeURIComponent(str), {to: 'string'})
|
|
25
|
+
return btoa(binaryString);
|
|
26
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { message } from 'antd';
|
|
2
|
+
function createInputFile(opts = {}) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
var x = document.createElement('INPUT');
|
|
5
|
+
x.setAttribute('type', 'file');
|
|
6
|
+
opts.accept && x.setAttribute('accept', opts.accept);
|
|
7
|
+
x.click();
|
|
8
|
+
let lastfile = null;
|
|
9
|
+
x.onchange = () => {
|
|
10
|
+
let [file, ...rest] = x.files;
|
|
11
|
+
if (file && lastfile !== file) {
|
|
12
|
+
lastfile = file;
|
|
13
|
+
resolve(file);
|
|
14
|
+
} else {
|
|
15
|
+
reject();
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async function importFile(url, options = { method: 'POST' }) {
|
|
21
|
+
const file = await createInputFile();
|
|
22
|
+
const data = new FormData();
|
|
23
|
+
data.append('file', file);
|
|
24
|
+
const defaultOptions = {
|
|
25
|
+
credentials: 'include',
|
|
26
|
+
};
|
|
27
|
+
const newOptions = { ...defaultOptions, ...options };
|
|
28
|
+
newOptions.headers = {
|
|
29
|
+
Accept: 'application/json',
|
|
30
|
+
Authorization:
|
|
31
|
+
localStorage.getItem('token') || sessionStorage.getItem('token'),
|
|
32
|
+
...newOptions.headers,
|
|
33
|
+
};
|
|
34
|
+
newOptions.body = data;
|
|
35
|
+
newOptions.credentials = 'same-origin';
|
|
36
|
+
// }
|
|
37
|
+
const hide = message.loading('正在导入...', 0);
|
|
38
|
+
return fetch(url, newOptions)
|
|
39
|
+
.then(response => {
|
|
40
|
+
hide();
|
|
41
|
+
return response.json();
|
|
42
|
+
})
|
|
43
|
+
.then(res => {
|
|
44
|
+
if (res && res.status) throw res;
|
|
45
|
+
})
|
|
46
|
+
.catch(e => {
|
|
47
|
+
hide();
|
|
48
|
+
throw e;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export default importFile;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { message } from 'antd';
|
|
2
|
+
import { request as requestUtils } from 'tengits-ui5';
|
|
3
|
+
import { t } from '../i18n';
|
|
4
|
+
|
|
5
|
+
const { VITE_AI_APPLICATIONID } = import.meta.env;
|
|
6
|
+
const { addRequestHook, addRequestHookBefore, addRequestHookAfter, addRequestDataAfter, default: TUIRequest } = requestUtils;
|
|
7
|
+
|
|
8
|
+
export { addRequestDataAfter, addRequestHook, addRequestHookAfter, addRequestHookBefore, request };
|
|
9
|
+
|
|
10
|
+
const localeConfig = {
|
|
11
|
+
zh: {
|
|
12
|
+
locale: 'zhCN',
|
|
13
|
+
},
|
|
14
|
+
en: {
|
|
15
|
+
locale: 'enUS',
|
|
16
|
+
},
|
|
17
|
+
fr: {
|
|
18
|
+
locale: 'frFR',
|
|
19
|
+
},
|
|
20
|
+
de: {
|
|
21
|
+
locale: 'deDE',
|
|
22
|
+
},
|
|
23
|
+
es: {
|
|
24
|
+
locale: 'esES',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const request = (url, options) => {
|
|
29
|
+
return TUIRequest(url, {
|
|
30
|
+
...options,
|
|
31
|
+
applicationId: VITE_AI_APPLICATIONID || '1344658626716544',
|
|
32
|
+
}).catch((e) => {
|
|
33
|
+
message.error(e.message || t('abnormal'));
|
|
34
|
+
throw e;
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function getToken() {
|
|
39
|
+
return sessionStorage.getItem('token') || localStorage.getItem('token');
|
|
40
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import COS from 'cos-js-sdk-v5';
|
|
2
|
+
import { message } from 'antd';
|
|
3
|
+
import { request } from './request';
|
|
4
|
+
import guid from './guid';
|
|
5
|
+
|
|
6
|
+
let cos = null;
|
|
7
|
+
const createCos = () => {
|
|
8
|
+
const TencentCos = window.dataConfig.TencentCos;
|
|
9
|
+
const config = {
|
|
10
|
+
bucket: TencentCos.bucket,
|
|
11
|
+
region: TencentCos.region,
|
|
12
|
+
};
|
|
13
|
+
return new COS({
|
|
14
|
+
getAuthorization: function(options, callback) {
|
|
15
|
+
// 异步获取签名
|
|
16
|
+
return request(TencentCos.getCosCredentialUrl || '/web/thirdpartSdk/getCosCredential', {
|
|
17
|
+
...config,
|
|
18
|
+
})
|
|
19
|
+
.then(data => {
|
|
20
|
+
callback({
|
|
21
|
+
TmpSecretId: data.data.credentials.tmpSecretId,
|
|
22
|
+
TmpSecretKey: data.data.credentials.tmpSecretKey,
|
|
23
|
+
XCosSecurityToken: data.data.credentials.sessionToken,
|
|
24
|
+
ExpiredTime: data.data.expiredTime,
|
|
25
|
+
});
|
|
26
|
+
})
|
|
27
|
+
.catch(e => {
|
|
28
|
+
cos = null;
|
|
29
|
+
callback(e);
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
export default (file, onProgress) => {
|
|
35
|
+
const TencentCos = window.dataConfig.TencentCos;
|
|
36
|
+
const config = {
|
|
37
|
+
bucket: TencentCos.bucket,
|
|
38
|
+
region: TencentCos.region,
|
|
39
|
+
};
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
!cos && (cos = createCos());
|
|
42
|
+
// 分片上传文件
|
|
43
|
+
cos.sliceUploadFile(
|
|
44
|
+
{
|
|
45
|
+
Bucket: config.bucket,
|
|
46
|
+
Region: config.region,
|
|
47
|
+
Key: guid() + '/' + file.name,
|
|
48
|
+
Body: file,
|
|
49
|
+
onProgress: data => onProgress(data, file),
|
|
50
|
+
},
|
|
51
|
+
function(err, data) {
|
|
52
|
+
if (err) {
|
|
53
|
+
cos = null;
|
|
54
|
+
console.log(err);
|
|
55
|
+
message.error('文件上传失败,请重试!');
|
|
56
|
+
reject(err);
|
|
57
|
+
} else {
|
|
58
|
+
//console.log(data);
|
|
59
|
+
resolve(data);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
var decodeSvg = function (str) {
|
|
66
|
+
return str.replace(/%25/g, "%").replace(/%23/g, "#").replace(/%7B/g, "{").replace(/%7D/g, "}").replace(/%3C/g, "<").replace(/%3E/g, ">");
|
|
67
|
+
}
|
|
68
|
+
export const uploadSvgToCloud = (svg, onProgress = () => {}) => {
|
|
69
|
+
const TencentCos = window.dataConfig.TencentCos;
|
|
70
|
+
const config = {
|
|
71
|
+
bucket: TencentCos.bucket,
|
|
72
|
+
region: TencentCos.region,
|
|
73
|
+
};
|
|
74
|
+
// const buffer = Buffer.from(base64.replace('data:image/svg+xml;base64,', ''), 'base64');
|
|
75
|
+
const svgString = svg.replace('data:image/svg+xml,', '');
|
|
76
|
+
const decodedData = decodeSvg(svgString);
|
|
77
|
+
const blob = new Blob([decodedData], { type: 'image/svg+xml' });
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
!cos && (cos = createCos());
|
|
80
|
+
// 分片上传文件
|
|
81
|
+
cos.putObject(
|
|
82
|
+
{
|
|
83
|
+
Bucket: config.bucket,
|
|
84
|
+
Region: config.region,
|
|
85
|
+
Key: guid() + '/' + guid() + '.svg',
|
|
86
|
+
Body: blob,
|
|
87
|
+
onProgress: data => onProgress(data, base64),
|
|
88
|
+
},
|
|
89
|
+
function(err, data) {
|
|
90
|
+
if (err) {
|
|
91
|
+
cos = null;
|
|
92
|
+
console.log(err);
|
|
93
|
+
message.error('文件上传失败,请重试!');
|
|
94
|
+
reject(err);
|
|
95
|
+
} else {
|
|
96
|
+
//console.log(data);
|
|
97
|
+
resolve(data);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
};
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { getLocalStorageJSON, LOCAL_STORAGE_KEY } from 'tengits-ui5';
|
|
2
|
+
|
|
3
|
+
export function getPageQuery() {
|
|
4
|
+
const search = window.location.href.split('?')[1];
|
|
5
|
+
let res = {};
|
|
6
|
+
if (search) {
|
|
7
|
+
search.split('&').forEach((item) => {
|
|
8
|
+
const arr = item.split('=');
|
|
9
|
+
res[arr[0]] = arr[1];
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return res;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Date转换为目标格式
|
|
16
|
+
export function formatDate(date: Date, format: string): string {
|
|
17
|
+
const addZero = num => num < 10 ? `0${num}` : `${num}`;
|
|
18
|
+
const replacements = {
|
|
19
|
+
yyyy: date.getFullYear(),
|
|
20
|
+
MM: addZero(date.getMonth() + 1),
|
|
21
|
+
dd: addZero(date.getDate()),
|
|
22
|
+
HH: addZero(date.getHours()),
|
|
23
|
+
mm: addZero(date.getMinutes()),
|
|
24
|
+
ss: addZero(date.getSeconds()),
|
|
25
|
+
};
|
|
26
|
+
return format.replace(/yyyy|MM|dd|HH|mm|ss/g, match => replacements[match]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// param time: yyyy-mm-ddTxxxx
|
|
30
|
+
export function formatStrTime(time: string, notSameDayFormat: string): string {
|
|
31
|
+
if (!time)
|
|
32
|
+
return '';
|
|
33
|
+
const date1 = new Date(time);
|
|
34
|
+
const date2 = new Date();
|
|
35
|
+
return date1.getFullYear() === date2.getFullYear()
|
|
36
|
+
&& date1.getMonth() === date2.getMonth()
|
|
37
|
+
&& date1.getDate() === date2.getDate()
|
|
38
|
+
? formatDate(date1, 'HH:mm')
|
|
39
|
+
: formatDate(date1, notSameDayFormat);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const copyTextInDom = (dom) => {
|
|
43
|
+
const range = document.createRange();
|
|
44
|
+
|
|
45
|
+
range.selectNode(dom);
|
|
46
|
+
// window.getSelection().removeAllRanges();
|
|
47
|
+
// window.getSelection().addRange(range);
|
|
48
|
+
|
|
49
|
+
// return new Promise((res) => {
|
|
50
|
+
// document.execCommand('copy');
|
|
51
|
+
// window.getSelection().removeAllRanges();
|
|
52
|
+
// res(dom.innerText);
|
|
53
|
+
// })
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// 复制到剪切板
|
|
57
|
+
export const copyText = (text: string | HTMLElement) => {
|
|
58
|
+
// 复制 dom 内文本
|
|
59
|
+
if (typeof text !== 'string')
|
|
60
|
+
return copyTextInDom(text);
|
|
61
|
+
// 高级 API直接复制文本(需要 https 环境)
|
|
62
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
63
|
+
return navigator.clipboard.writeText(text);
|
|
64
|
+
}
|
|
65
|
+
// 通过把文本写入 dom, 间接通过选中 dom 复制文本
|
|
66
|
+
const areaDom = document.createElement('textarea');
|
|
67
|
+
// 设置样式使其不在屏幕上显示
|
|
68
|
+
areaDom.style.position = 'absolute';
|
|
69
|
+
areaDom.style.left = '-9999px';
|
|
70
|
+
areaDom.value = text;
|
|
71
|
+
document.body.appendChild(areaDom);
|
|
72
|
+
|
|
73
|
+
return copyTextInDom(areaDom).then((str) => {
|
|
74
|
+
document.body.removeChild(areaDom);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
interface languageMap {
|
|
79
|
+
[key: string]: string | undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const programmingLanguages: languageMap = {
|
|
83
|
+
'javascript': '.js',
|
|
84
|
+
'python': '.py',
|
|
85
|
+
'java': '.java',
|
|
86
|
+
'c': '.c',
|
|
87
|
+
'cpp': '.cpp',
|
|
88
|
+
'c++': '.cpp',
|
|
89
|
+
'c#': '.cs',
|
|
90
|
+
'ruby': '.rb',
|
|
91
|
+
'php': '.php',
|
|
92
|
+
'swift': '.swift',
|
|
93
|
+
'objective-c': '.m',
|
|
94
|
+
'kotlin': '.kt',
|
|
95
|
+
'typescript': '.ts',
|
|
96
|
+
'go': '.go',
|
|
97
|
+
'perl': '.pl',
|
|
98
|
+
'rust': '.rs',
|
|
99
|
+
'scala': '.scala',
|
|
100
|
+
'haskell': '.hs',
|
|
101
|
+
'lua': '.lua',
|
|
102
|
+
'shell': '.sh',
|
|
103
|
+
'sql': '.sql',
|
|
104
|
+
'html': '.html',
|
|
105
|
+
'css': '.css',
|
|
106
|
+
// add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// 根节点id为0 转树形结构
|
|
110
|
+
export function arrayToTree(array) {
|
|
111
|
+
const map = new Map();
|
|
112
|
+
const tree = [];
|
|
113
|
+
|
|
114
|
+
array.sort((a, b) => a.groupOrder - b.groupOrder).forEach((item) => {
|
|
115
|
+
map.set(item.id, { ...item, children: [] });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
array.forEach((item) => {
|
|
119
|
+
if (item.parentId) {
|
|
120
|
+
const parent = map.get(item.parentId);
|
|
121
|
+
if (parent) {
|
|
122
|
+
parent.children.push(map.get(item.id));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
tree.push(map.get(item.id));
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return tree;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 根节点不确定是否为0 转树形结构
|
|
134
|
+
export const arrayToTreeWithMultiParent = <T extends Record<string, any>>(
|
|
135
|
+
items: T[],
|
|
136
|
+
options: {
|
|
137
|
+
idKey?: keyof T;
|
|
138
|
+
parentIdKey?: keyof T;
|
|
139
|
+
childrenKey?: string;
|
|
140
|
+
sortKey?: keyof T;
|
|
141
|
+
} = {},
|
|
142
|
+
): T[] => {
|
|
143
|
+
const {
|
|
144
|
+
idKey = 'id' as keyof T,
|
|
145
|
+
parentIdKey = 'parentId' as keyof T,
|
|
146
|
+
childrenKey = 'children',
|
|
147
|
+
sortKey,
|
|
148
|
+
} = options;
|
|
149
|
+
const tree: T[] = [];
|
|
150
|
+
const itemMap: Record<any, T & { [key: string]: T[]; _deepLevel: number }> = {};
|
|
151
|
+
// 收集所有 id
|
|
152
|
+
const allIds = new Set(items.map(item => item[idKey]));
|
|
153
|
+
|
|
154
|
+
// 第一次遍历,创建所有节点的映射并初始化 _deepLevel 为 1
|
|
155
|
+
items.forEach((item) => {
|
|
156
|
+
itemMap[item[idKey]] = { ...item, [childrenKey]: [], _deepLevel: 1 };
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// 第二次遍历,构建树形结构并更新 _deepLevel
|
|
160
|
+
items.forEach((item) => {
|
|
161
|
+
const parentId = item[parentIdKey];
|
|
162
|
+
const currentNode = itemMap[item[idKey]];
|
|
163
|
+
if (!allIds.has(parentId)) {
|
|
164
|
+
tree.push(currentNode);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const parent = itemMap[parentId];
|
|
168
|
+
if (parent) {
|
|
169
|
+
// 子节点的 _deepLevel 为父节点的 _deepLevel 加 1
|
|
170
|
+
currentNode._deepLevel = parent._deepLevel + 1;
|
|
171
|
+
parent[childrenKey].push(currentNode);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
if (!sortKey)
|
|
176
|
+
return tree;
|
|
177
|
+
|
|
178
|
+
// 递归排序函数
|
|
179
|
+
const sortTree = (nodes: T[]): T[] => {
|
|
180
|
+
if (!sortKey)
|
|
181
|
+
return nodes;
|
|
182
|
+
|
|
183
|
+
return nodes.sort((a, b) => {
|
|
184
|
+
const aValue = a[sortKey];
|
|
185
|
+
const bValue = b[sortKey];
|
|
186
|
+
|
|
187
|
+
// 处理数字类型
|
|
188
|
+
if (typeof aValue === 'number' && typeof bValue === 'number') {
|
|
189
|
+
return aValue - bValue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 处理字符串类型
|
|
193
|
+
if (typeof aValue === 'string' && typeof bValue === 'string') {
|
|
194
|
+
return aValue.localeCompare(bValue);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 处理日期类型
|
|
198
|
+
if (aValue instanceof Date && bValue instanceof Date) {
|
|
199
|
+
return aValue.getTime() - bValue.getTime();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 处理字符串日期
|
|
203
|
+
if (typeof aValue === 'string' && typeof bValue === 'string') {
|
|
204
|
+
const dateA = new Date(aValue);
|
|
205
|
+
const dateB = new Date(bValue);
|
|
206
|
+
if (!Number.isNaN(dateA.getTime()) && !Number.isNaN(dateB.getTime())) {
|
|
207
|
+
return dateA.getTime() - dateB.getTime();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 默认字符串比较
|
|
212
|
+
return String(aValue).localeCompare(String(bValue));
|
|
213
|
+
}).map(node => ({
|
|
214
|
+
...node,
|
|
215
|
+
[childrenKey]: node[childrenKey] && node[childrenKey].length > 0
|
|
216
|
+
? sortTree(node[childrenKey])
|
|
217
|
+
: node[childrenKey],
|
|
218
|
+
}));
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return sortTree(tree);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// 递归删除空的 children 数组 将其[]设为 undefined
|
|
225
|
+
export function removeEmptyChildren(nodes: Array<any>): Array<any> {
|
|
226
|
+
return nodes.map((node) => {
|
|
227
|
+
if (node.children && node.children.length > 0) {
|
|
228
|
+
// 递归处理子节点
|
|
229
|
+
node.children = removeEmptyChildren(node.children);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// 若 children 为空数组,将其设为 undefined
|
|
233
|
+
delete node.children;
|
|
234
|
+
}
|
|
235
|
+
return node;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 查找指定节点的根节点
|
|
240
|
+
export const findRootNode = (nodeId: number, flatData: any[]): any | null => {
|
|
241
|
+
let currentNode = flatData.find(item => item.value === nodeId);
|
|
242
|
+
if (!currentNode)
|
|
243
|
+
return null;
|
|
244
|
+
|
|
245
|
+
while (currentNode.pId !== null) {
|
|
246
|
+
const parent = flatData.find(item => item.value === currentNode.pId);
|
|
247
|
+
if (parent) {
|
|
248
|
+
currentNode = parent;
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return currentNode;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// 获取指定节点及其所有子孙节点
|
|
258
|
+
export const getDescendantNodes = (nodeId: number, flatData: any[]): any[] => {
|
|
259
|
+
const descendants: any[] = [];
|
|
260
|
+
flatData.forEach((item) => {
|
|
261
|
+
if (item.pId === nodeId) {
|
|
262
|
+
descendants.push(item, ...getDescendantNodes(item.value!, flatData));
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
return descendants;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// 根据id递归查找树形结构中的指定节点
|
|
269
|
+
export const findNodeById = (id: number, tree: any[]): any | null => {
|
|
270
|
+
for (const node of tree) {
|
|
271
|
+
if (node.id === id) {
|
|
272
|
+
return node;
|
|
273
|
+
}
|
|
274
|
+
if (Array.isArray(node.children) && node.children.length > 0) {
|
|
275
|
+
const result = findNodeById(id, node.children);
|
|
276
|
+
if (result) {
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// 判断是否为租户端
|
|
285
|
+
export const getIsTenant = () => {
|
|
286
|
+
return (window.tenantParams?.platformType || 2) === 2;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export const getUserInfo = () => {
|
|
290
|
+
return getLocalStorageJSON(LOCAL_STORAGE_KEY.USERSTATUS) || {};
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// 下载链接文件
|
|
294
|
+
export const downloadFile = (url: string) => {
|
|
295
|
+
if (!url)
|
|
296
|
+
return;
|
|
297
|
+
// 创建一个 a 元素
|
|
298
|
+
const a = document.createElement('a');
|
|
299
|
+
a.href = url;
|
|
300
|
+
// 设置下载属性,指定下载文件名,可根据实际情况修改
|
|
301
|
+
a.download = 'downloaded_file';
|
|
302
|
+
// 将 a 元素添加到文档中
|
|
303
|
+
document.body.appendChild(a);
|
|
304
|
+
// 触发点击事件
|
|
305
|
+
a.click();
|
|
306
|
+
// 移除 a 元素
|
|
307
|
+
document.body.removeChild(a);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* 一组数据对指定日期字段倒序排序
|
|
312
|
+
* @param data - 包含日期字段的数据数组
|
|
313
|
+
* @param dateField - 要排序的日期字段名
|
|
314
|
+
* @returns 排序后的新数组
|
|
315
|
+
*/
|
|
316
|
+
export const sortByDate = <T extends Record<string, any>>(data: T[], dateField: keyof T): T[] => {
|
|
317
|
+
return [...data].sort((a, b) => {
|
|
318
|
+
const dateA = new Date(a[dateField] as string);
|
|
319
|
+
const dateB = new Date(b[dateField] as string);
|
|
320
|
+
return dateB.getTime() - dateA.getTime();
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
export const scrollElementIntoView = (element: HTMLElement | null, options: ScrollIntoViewOptions = { behavior: 'smooth', block: 'nearest', inline: 'nearest' }): void => {
|
|
325
|
+
if (!element)
|
|
326
|
+
return;
|
|
327
|
+
if (typeof element.scrollIntoView === 'function') {
|
|
328
|
+
// 使用原生 scrollIntoView 方法
|
|
329
|
+
element.scrollIntoView(options);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default function uuid() {
|
|
2
|
+
var s = [];
|
|
3
|
+
var hexDigits = "0123456789abcdef";
|
|
4
|
+
for (var i = 0; i < 36; i++) {
|
|
5
|
+
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
|
|
6
|
+
}
|
|
7
|
+
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
|
|
8
|
+
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
|
|
9
|
+
s[8] = s[13] = s[18] = s[23] = "-";
|
|
10
|
+
|
|
11
|
+
var uuid = s.join("");
|
|
12
|
+
return uuid;
|
|
13
|
+
}
|