Browse Source

feat: 主动学习实验结果

pull/202/head
zhaowei 9 months ago
parent
commit
0820ac835b
12 changed files with 255 additions and 367 deletions
  1. +11
    -4
      react-ui/src/pages/ActiveLearn/Instance/index.tsx
  2. +75
    -92
      react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.tsx
  3. +34
    -66
      react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.tsx
  4. +0
    -120
      react-ui/src/pages/ActiveLearn/data.json
  5. +15
    -8
      react-ui/src/pages/ActiveLearn/types.ts
  6. +3
    -0
      react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx
  7. +1
    -1
      react-ui/src/pages/AutoML/components/ExperimentList/index.tsx
  8. +1
    -16
      react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.less
  9. +6
    -53
      react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.tsx
  10. +15
    -0
      react-ui/src/pages/HyperParameter/components/TrialFileTree/index.less
  11. +63
    -0
      react-ui/src/pages/HyperParameter/components/TrialFileTree/index.tsx
  12. +31
    -7
      react-ui/src/utils/downloadfile.ts

+ 11
- 4
react-ui/src/pages/ActiveLearn/Instance/index.tsx View File

@@ -1,5 +1,5 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus } from '@/enums';
import { AutoMLTaskType, ExperimentStatus } from '@/enums';
import { getActiveLearnInsReq } from '@/services/activeLearn';
import { NodeStatus } from '@/types';
import { parseJsonText } from '@/utils';
@@ -169,13 +169,20 @@ function ActiveLearnInstance() {
key: TabKeys.Result,
label: '实验结果',
icon: <KFIcon type="icon-shiyanjieguo1" />,
children: <ExperimentResult fileUrl={instanceInfo?.result_txt} />,
children: (
<ExperimentResult fileUrl={instanceInfo?.data} modelPath={instanceInfo?.result_path} />
),
},
{
key: TabKeys.History,
label: '寻优列表',
label: '训练列表',
icon: <KFIcon type="icon-Trialliebiao" />,
children: <ExperimentHistory trialList={instanceInfo?.trial_list ?? []} />,
children: (
<ExperimentHistory
trialList={instanceInfo?.trial_list ?? []}
isClassification={experimentInfo?.task_type === AutoMLTaskType.Classification}
/>
),
},
];



+ 75
- 92
react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.tsx View File

@@ -1,112 +1,89 @@
import { getFileReq } from '@/services/file';
import { to } from '@/utils/promise';
import tableCellRender from '@/utils/table';
import { Table, type TableProps } from 'antd';
import KFIcon from '@/components/KFIcon';
import { ActiveLearnTrial } from '@/pages/ActiveLearn/types';
import TrialFileTree from '@/pages/HyperParameter/components/TrialFileTree';
import { downloadCommonFile, MimeType } from '@/utils/downloadfile';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { Button, Table, type TableProps } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import styles from './index.less';

type ExperimentHistoryProps = {
fileUrl?: string;
trialList: ActiveLearnTrial[];
isClassification: boolean;
};

type TableData = {
id?: string;
accuracy?: number;
duration?: number;
train_loss?: number;
status?: string;
feature?: string;
althorithm?: string;
};

function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) {
const [tableData, setTableData] = useState<TableData[]>([]);
useEffect(() => {
if (fileUrl) {
getHistoryFile();
}
}, [fileUrl]);

// 获取实验运行历史记录
const getHistoryFile = async () => {
const [res] = await to(getFileReq(fileUrl));
if (res) {
const data: any[] = res.data;
const list: TableData[] = data.map((item) => {
return {
id: item[0]?.[0],
accuracy: item[1]?.[5]?.accuracy,
duration: item[1]?.[5]?.duration,
train_loss: item[1]?.[5]?.train_loss,
status: item[1]?.[2]?.['__enum__']?.split('.')?.[1],
};
});
list.forEach((item) => {
if (!item.id) return;
const config = (res as any).configs?.[item.id];
item.feature = config?.['feature_preprocessor:__choice__'];
item.althorithm = isClassification
? config?.['classifier:__choice__']
: config?.['regressor:__choice__'];
});
setTableData(list);
}
};

const columns: TableProps<TableData>['columns'] = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
render: tableCellRender(false),
},
{
title: '准确率',
dataIndex: 'accuracy',
key: 'accuracy',
render: tableCellRender(true),
ellipsis: { showTitle: false },
},
function ExperimentHistory({ trialList, isClassification }: ExperimentHistoryProps) {
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
const columns: TableProps<ActiveLearnTrial>['columns'] = [
{
title: '耗时',
dataIndex: 'duration',
key: 'duration',
render: tableCellRender(true),
ellipsis: { showTitle: false },
title: '序号',
dataIndex: 'index',
key: 'index',
width: 100,
render: tableCellRender(false, TableCellValueType.Index),
},
{
title: '训练损失',
dataIndex: 'train_loss',
key: 'train_loss',
render: tableCellRender(true),
ellipsis: { showTitle: false },
title: '查询数据量',
dataIndex: 'n_instances',
key: 'n_instances',
render: tableCellRender(false),
},
{
title: '特征处理',
dataIndex: 'feature',
key: 'feature',
render: tableCellRender(true),
ellipsis: { showTitle: false },
title: '总数据量',
dataIndex: 'data_num',
key: 'data_num',
render: tableCellRender(false),
},
{
title: '算法',
dataIndex: 'althorithm',
key: 'althorithm',
render: tableCellRender(true),
ellipsis: { showTitle: false },
title: isClassification ? '准确率' : '均方误差',
dataIndex: isClassification ? 'accuracy' : 'mse',
key: 'accuracy',
render: tableCellRender(false),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 120,
render: tableCellRender(false),
title: '查询数据 id',
dataIndex: 'query_idx',
key: 'query_idx',
render: (query_idx: string) => (
<div>
<Button
type="link"
size="small"
key="start"
icon={<KFIcon type="icon-xiazai" />}
onClick={() => {
if (query_idx) {
const fileName = query_idx.split('/').slice(-1)[0];
let url = query_idx;
if (process.env.NODE_ENV === 'development') {
url = query_idx.replace('172.168.15.197:31213', 'localhost:8000');
}
downloadCommonFile(url, MimeType.JSON, fileName);
}
}}
>
下载
</Button>
</div>
),
},
];

// 自定义展开视图
const expandedRowRender = (record: ActiveLearnTrial) => {
return <TrialFileTree title="训练结果" trial={record}></TrialFileTree>;
};

// 展开实例
const handleExpandChange = (expanded: boolean, record: ActiveLearnTrial) => {
if (expanded) {
setExpandedRowKeys([record.trial_id]);
} else {
setExpandedRowKeys([]);
}
};

return (
<div className={styles['experiment-history']}>
<div className={styles['experiment-history__content']}>
@@ -117,11 +94,17 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps
)}
>
<Table
dataSource={tableData}
dataSource={trialList}
columns={columns}
pagination={false}
scroll={{ y: 'calc(100% - 55px)' }}
rowKey="id"
rowKey="trial_id"
expandable={{
expandedRowRender: expandedRowRender,
onExpand: handleExpandChange,
expandedRowKeys: expandedRowKeys,
rowExpandable: (record: ActiveLearnTrial) => !!record.file,
}}
/>
</div>
</div>


+ 34
- 66
react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.tsx View File

@@ -1,81 +1,49 @@
import InfoGroup from '@/components/InfoGroup';
import { getFileReq } from '@/services/file';
import { to } from '@/utils/promise';
import { Button, Image } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { downloadCommonFile, MimeType } from '@/utils/downloadfile';
import { Button } from 'antd';
import styles from './index.less';

type ExperimentResultProps = {
fileUrl?: string;
imageUrl?: string;
modelPath?: string;
};

function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProps) {
const [result, setResult] = useState<string | undefined>('');

const images = useMemo(() => {
if (imageUrl) {
return imageUrl.split(',').map((item) => item.trim());
}
return [];
}, [imageUrl]);

useEffect(() => {
if (fileUrl) {
getResultFile();
}
}, [fileUrl]);

// 获取实验运行历史记录
const getResultFile = async () => {
const [res] = await to(getFileReq(fileUrl));
if (res) {
setResult(res as any as string);
}
};

function ExperimentResult({ fileUrl, modelPath }: ExperimentResultProps) {
return (
<div className={styles['experiment-result']}>
<InfoGroup title="实验结果" height={420} width="100%">
<div className={styles['experiment-result__text']}>{result}</div>
<InfoGroup title="数据选择查看" width="100%">
{fileUrl && (
<div>
<Button
type="primary"
onClick={() => {
const fileName = 'data.json';
let url = fileUrl;
if (process.env.NODE_ENV === 'development') {
url = fileUrl.replace('172.168.15.197:31213', 'localhost:8000');
}
downloadCommonFile(url, MimeType.JSON, fileName);
}}
>
数据选择下载
</Button>
</div>
)}
</InfoGroup>
<InfoGroup title="可视化结果" style={{ margin: '16px 0' }}>
<div className={styles['experiment-result__images']}>
<Image.PreviewGroup
preview={{
onChange: (current, prev) =>
console.log(`current index: ${current}, prev index: ${prev}`),
}}
>
{images.map((item) => (
<Image
key={item}
className={styles['experiment-result__images__item']}
src={item}
height={248}
draggable={false}
alt=""
/>
))}
</Image.PreviewGroup>
</div>
<InfoGroup title="模型下载" style={{ margin: '16px 0' }}>
{modelPath && (
<div>
<Button
type="primary"
onClick={() => {
window.location.href = modelPath;
}}
>
模型下载
</Button>
</div>
)}
</InfoGroup>
{modelPath && (
<div className={styles['experiment-result__download']}>
<span style={{ marginRight: '12px', color: '#606b7a' }}>文件名</span>
<span>save_model.joblib</span>
<Button
type="primary"
className={styles['experiment-result__download__btn']}
onClick={() => {
window.location.href = modelPath;
}}
>
模型下载
</Button>
</div>
)}
</div>
);
}


+ 0
- 120
react-ui/src/pages/ActiveLearn/data.json View File

@@ -1,120 +0,0 @@
{
"workflow-xwnb8": {
"id": "workflow-xwnb8",
"name": "workflow-xwnb8",
"type": "DAG",
"phase": "Failed",
"children": [
"workflow-xwnb8-1083129199"
],
"progress": "2/3",
"startedAt": "2025-04-18T06:56:18Z",
"finishedAt": "2025-04-18T06:57:32Z",
"displayName": "workflow-xwnb8",
"templateName": "ml-workflow",
"outboundNodes": [
"workflow-xwnb8-1355608520"
],
"templateScope": "local/workflow-xwnb8",
"resourcesDuration": {
"cpu": 42,
"memory": 851,
"nvidia.com/gpu": 10
}
},
"git-clone-9d0c5965": {
"id": "workflow-xwnb8-514970004",
"name": "workflow-xwnb8.git-clone-9d0c5965",
"type": "Pod",
"phase": "Succeeded",
"outputs": {
"exitCode": "0",
"artifacts": [
{
"s3": {
"key": "workflow-xwnb8/workflow-xwnb8-git-clone-9d0c5965-514970004/main.log"
},
"name": "main-logs"
}
]
},
"children": [
"workflow-xwnb8-1355608520"
],
"progress": "1/1",
"startedAt": "2025-04-18T06:56:38Z",
"boundaryID": "workflow-xwnb8",
"finishedAt": "2025-04-18T06:56:49Z",
"displayName": "git-clone-9d0c5965",
"hostNodeName": "k8s-node01",
"templateName": "git-clone-9d0c5965",
"templateScope": "local/workflow-xwnb8",
"resourcesDuration": {
"cpu": 1,
"memory": 11
}
},
"git-clone-e28c560c": {
"id": "workflow-xwnb8-1083129199",
"name": "workflow-xwnb8.git-clone-e28c560c",
"type": "Pod",
"phase": "Succeeded",
"outputs": {
"exitCode": "0",
"artifacts": [
{
"s3": {
"key": "workflow-xwnb8/workflow-xwnb8-git-clone-e28c560c-1083129199/main.log"
},
"name": "main-logs"
}
]
},
"children": [
"workflow-xwnb8-514970004"
],
"progress": "1/1",
"startedAt": "2025-04-18T06:56:18Z",
"boundaryID": "workflow-xwnb8",
"finishedAt": "2025-04-18T06:56:27Z",
"displayName": "git-clone-e28c560c",
"hostNodeName": "k8s-node01",
"templateName": "git-clone-e28c560c",
"templateScope": "local/workflow-xwnb8",
"resourcesDuration": {
"cpu": 1,
"memory": 11
}
},
"active-learn-b708ed0b": {
"id": "workflow-xwnb8-1355608520",
"name": "workflow-xwnb8.active-learn-b708ed0b",
"type": "Pod",
"phase": "Failed",
"message": "Error (exit code 1)",
"outputs": {
"exitCode": "1",
"artifacts": [
{
"s3": {
"key": "workflow-xwnb8/workflow-xwnb8-active-learn-b708ed0b-1355608520/main.log"
},
"name": "main-logs"
}
]
},
"progress": "0/1",
"startedAt": "2025-04-18T06:57:00Z",
"boundaryID": "workflow-xwnb8",
"finishedAt": "2025-04-18T06:57:27Z",
"displayName": "active-learn-b708ed0b",
"hostNodeName": "k8s-node01",
"templateName": "active-learn-b708ed0b",
"templateScope": "local/workflow-xwnb8",
"resourcesDuration": {
"cpu": 40,
"memory": 829,
"nvidia.com/gpu": 10
}
}
}

+ 15
- 8
react-ui/src/pages/ActiveLearn/types.ts View File

@@ -1,14 +1,7 @@
/*
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @Date: 2025-04-18 08:40:03
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @LastEditTime: 2025-04-18 11:30:21
* @FilePath: \ci4s\react-ui\src\pages\ActiveLearn\types.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { type ParameterInputObject } from '@/components/ResourceSelect';
import { type NodeStatus } from '@/types';
import { AutoMLTaskType } from '@/enums';
import { HyperParameterFile } from '@/pages/HyperParameter/types';

// 操作类型
export enum OperationType {
@@ -82,4 +75,18 @@ export type ActiveLearnInstanceData = {
update_time: string;
finish_time: string;
nodeStatus?: NodeStatus;
data: string,
trial_list?: ActiveLearnTrial[]
};

export type ActiveLearnTrial = {
trial_id: string;
n_instances?: number;
data_num?: number;
accuracy?: number;
mse?: number;
query_idx?: string
file?: HyperParameterFile;
};

export {type HyperParameterFile}

+ 3
- 0
react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx View File

@@ -159,6 +159,9 @@ function ExperimentInstanceComponent({
<Checkbox
checked={isSingleChecked(item.id)}
onChange={() => checkSingle(item.id)}
disabled={
item.status === ExperimentStatus.Running || item.status === ExperimentStatus.Pending
}
></Checkbox>
</div>
<a


+ 1
- 1
react-ui/src/pages/AutoML/components/ExperimentList/index.tsx View File

@@ -357,7 +357,7 @@ function ExperimentList({ type }: ExperimentListProps) {

return (
<div className={styles['experiment-list']}>
<PageTitle title={config.title + '列表'}></PageTitle>
<PageTitle title={config.title}></PageTitle>
<div className={styles['experiment-list__content']}>
<div className={styles['experiment-list__content__filter']}>
<Input.Search


+ 1
- 16
react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.less View File

@@ -55,19 +55,4 @@
.table-best-row {
color: @success-color;
font-weight: bold;
}

.trail-result {
:global {
.ant-tree-node-selected {
.trail-result__icon {
color: white;
}
}

.trail-result__icon {
margin-left: 8px;
color: @primary-color;
}
}
}
}

+ 6
- 53
react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.tsx View File

@@ -1,19 +1,15 @@
import InfoGroup from '@/components/InfoGroup';
import KFIcon from '@/components/KFIcon';
import TableColTitle from '@/components/TableColTitle';
import TrialStatusCell from '@/pages/HyperParameter/components/TrialStatusCell';
import { HyperParameterFile, HyperParameterTrial } from '@/pages/HyperParameter/types';
import { HyperParameterTrial } from '@/pages/HyperParameter/types';
import { getExpMetricsReq } from '@/services/hyperParameter';
import { downLoadZip } from '@/utils/downloadfile';
import { to } from '@/utils/promise';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { App, Button, Table, Tree, type TableProps, type TreeDataNode } from 'antd';
import { App, Button, Table, type TableProps } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import TrialFileTree from '../TrialFileTree';
import styles from './index.less';

const { DirectoryTree } = Tree;

type ExperimentHistoryProps = {
trialList?: HyperParameterTrial[];
};
@@ -110,7 +106,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {

if (metricNames.length) {
trialColumns.push({
title: `指标分析(${first.metric ?? ''})`,
title: `指标分析(${first?.metric ?? ''})`,
dataIndex: 'metrics',
key: 'metrics',
align: 'center',
@@ -127,51 +123,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {

// 自定义展开视图
const expandedRowRender = (record: HyperParameterTrial) => {
const filesToTreeData = (
files: HyperParameterFile[],
parent?: HyperParameterFile,
): TreeDataNode[] =>
files.map((file) => {
const key = parent ? `${parent.name}/${file.name}` : file.name;
return {
...file,
key,
title: file.name,
children: file.children ? filesToTreeData(file.children, file) : undefined,
};
});

const treeData: TreeDataNode[] = filesToTreeData([record.file]);
return (
<InfoGroup title="寻优结果" className={styles['trail-result']}>
<DirectoryTree
// @ts-ignore
treeData={treeData}
defaultExpandAll
titleRender={(record: TreeDataNode & HyperParameterFile) => {
const label = record.title + (record.isFile ? `(${record.size})` : '');
return (
<>
<span style={{ fontSize: 14 }}>{label}</span>
<KFIcon
type="icon-xiazai"
className="trail-result__icon"
onClick={(e) => {
e.stopPropagation();
downLoadZip(
record.isFile
? `/api/mmp/minioStorage/downloadFile`
: `/api/mmp/minioStorage/download`,
{ path: record.url },
);
}}
/>
</>
);
}}
/>
</InfoGroup>
);
return <TrialFileTree title="寻优结果" trial={record}></TrialFileTree>;
};

// 展开实例
@@ -237,6 +189,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
expandedRowRender: expandedRowRender,
onExpand: handleExpandChange,
expandedRowKeys: expandedRowKeys,
rowExpandable: (record: HyperParameterTrial) => !!record.file,
}}
rowSelection={rowSelection}
/>


+ 15
- 0
react-ui/src/pages/HyperParameter/components/TrialFileTree/index.less View File

@@ -0,0 +1,15 @@
.trail-file-tree {
:global {
.ant-tree-node-selected {
.trail-file-tree__icon {
color: white;
}
}
.trail-file-tree__icon {
margin-left: 8px;
color: @primary-color;
}
}
}

+ 63
- 0
react-ui/src/pages/HyperParameter/components/TrialFileTree/index.tsx View File

@@ -0,0 +1,63 @@
import InfoGroup from '@/components/InfoGroup';
import KFIcon from '@/components/KFIcon';
import { HyperParameterFile } from '@/pages/HyperParameter/types';
import { downLoadZip } from '@/utils/downloadfile';
import { Tree, type TreeDataNode } from 'antd';
import styles from './index.less';

const { DirectoryTree } = Tree;

export type TrialFileTreeProps = {
title: string;
trial: { file?: HyperParameterFile };
};

function TrialFileTree({ trial, title }: TrialFileTreeProps) {
const filesToTreeData = (
files: HyperParameterFile[],
parent?: HyperParameterFile,
): TreeDataNode[] =>
files.map((file) => {
const key = parent ? `${parent.name}/${file.name}` : file.name;
return {
...file,
key,
title: file.name,
children: file.children ? filesToTreeData(file.children, file) : undefined,
};
});

const treeData: TreeDataNode[] = filesToTreeData(trial.file ? [trial.file] : []);
return (
<InfoGroup title={title} className={styles['trail-file-tree']}>
<DirectoryTree
// @ts-ignore
treeData={treeData}
defaultExpandAll
titleRender={(record: TreeDataNode & HyperParameterFile) => {
const label = record.title + (record.isFile ? `(${record.size})` : '');
return (
<>
<span style={{ fontSize: 14 }}>{label}</span>
<KFIcon
type="icon-xiazai"
className="trail-file-tree__icon"
onClick={(e) => {
e.stopPropagation();
downLoadZip(
record.isFile
? `/api/mmp/minioStorage/downloadFile`
: `/api/mmp/minioStorage/download`,
{ path: record.url },
);
}}
/>
</>
);
}}
/>
</InfoGroup>
);
}

export default TrialFileTree;

+ 31
- 7
react-ui/src/utils/downloadfile.ts View File

@@ -1,9 +1,10 @@
import { request } from '@umijs/max';

/** MimeType */
export const mimeMap = {
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
zip: 'application/zip',
export enum MimeType {
XLSX = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
ZIP = 'application/zip',
JSON = 'application/json'
};

/**
@@ -11,7 +12,7 @@ export const mimeMap = {
* @param res - blob响应内容
* @param mimeType - MIME类型
*/
export function resolveBlob(res: any, mimeType: string) {
export function resolveBlob(res: any, mimeType: string, specifiedFileName: string = "file") {
const aLink = document.createElement('a');
const blob = new Blob([res.data], { type: mimeType });
// 从response的headers中获取filename,
@@ -20,7 +21,7 @@ export function resolveBlob(res: any, mimeType: string) {
// console.log(res);
const contentDisposition = decodeURI(res.headers['content-disposition']);
const result = patt.exec(contentDisposition);
let fileName = result ? result[1] : 'file';
let fileName = result ? result[1] : specifiedFileName;
fileName = fileName.replace(/"/g, '');
aLink.style.display = 'none';
aLink.href = URL.createObjectURL(blob);
@@ -43,7 +44,7 @@ export function downLoadZip(url: string, params?: any) {
responseType: 'blob',
getResponse: true,
}).then((res) => {
resolveBlob(res, mimeMap.zip);
resolveBlob(res, MimeType.ZIP);
});
}

@@ -64,7 +65,30 @@ export async function downloadXlsx(
responseType: 'blob',
getResponse: true,
}).then((res) => {
resolveBlob(res, mimeMap.xlsx);
resolveBlob(res, MimeType.XLSX);
});
}

/**
* 下载文本文件
* @param url - url 地址
* @param type - mime-type
* @param fileName - 文件名
* @param method - 请求方法
* @param options - 请求选项
*/
export function downloadCommonFile(url: string, type: string, fileName: string = "file", method: string = 'GET', options?: Record<string, any>) {
request(url, {
method: method,
headers: {
isToken: false,
},
skipValidating: true,
...options,
responseType: 'blob',
getResponse: true,
}).then((res) => {
resolveBlob(res, type, fileName);
});
}



Loading…
Cancel
Save