| @@ -1,8 +1,8 @@ | |||
| module.exports = { | |||
| extends: [ | |||
| require.resolve('@umijs/lint/dist/config/eslint'), | |||
| 'plugin:react/recommended', | |||
| "plugin:react-hooks/recommended" | |||
| require.resolve('@umijs/lint/dist/config/eslint'), | |||
| 'plugin:react/recommended', | |||
| 'plugin:react-hooks/recommended', | |||
| ], | |||
| globals: { | |||
| page: true, | |||
| @@ -11,6 +11,6 @@ module.exports = { | |||
| rules: { | |||
| '@typescript-eslint/no-use-before-define': 'off', | |||
| 'react/react-in-jsx-scope': 'off', | |||
| 'react/display-name': 'off' | |||
| 'react/display-name': 'off', | |||
| }, | |||
| }; | |||
| @@ -20,3 +20,4 @@ yarn-error.log | |||
| CNAME | |||
| /build | |||
| /public | |||
| /src/iconfont/ | |||
| @@ -1,4 +1,4 @@ | |||
| export default function(babel) { | |||
| export default function (babel) { | |||
| const { types: t } = babel; | |||
| return { | |||
| visitor: { | |||
| @@ -6,10 +6,10 @@ export default function(babel) { | |||
| const source = path.node.source.value; | |||
| if (source.endsWith('.less')) { | |||
| if (path.node.specifiers.length > 0) { | |||
| path.node.source.value += "?modules"; | |||
| path.node.source.value += '?modules'; | |||
| } | |||
| } | |||
| }, | |||
| }, | |||
| }; | |||
| }; | |||
| } | |||
| @@ -6,7 +6,7 @@ export const createWebSocketMock = () => { | |||
| this.listeners = {}; | |||
| this.count = 0; | |||
| console.log("Mock WebSocket connected to:", url); | |||
| console.log('Mock WebSocket connected to:', url); | |||
| // 模拟服务器推送消息 | |||
| this.intervalId = setInterval(() => { | |||
| @@ -21,8 +21,8 @@ export const createWebSocketMock = () => { | |||
| } | |||
| sendMessage(data) { | |||
| if (this.listeners["message"]) { | |||
| this.listeners["message"].forEach((callback) => callback({ data })); | |||
| if (this.listeners['message']) { | |||
| this.listeners['message'].forEach((callback) => callback({ data })); | |||
| } | |||
| } | |||
| @@ -41,7 +41,7 @@ export const createWebSocketMock = () => { | |||
| close() { | |||
| this.readyState = WebSocket.CLOSED; | |||
| console.log("Mock WebSocket closed"); | |||
| console.log('Mock WebSocket closed'); | |||
| } | |||
| } | |||
| @@ -89,4 +89,4 @@ export const logStreamData = { | |||
| ], | |||
| }, | |||
| ], | |||
| }; | |||
| }; | |||
| @@ -1 +1 @@ | |||
| # Documentation | |||
| # Documentation | |||
| @@ -1,5 +1,5 @@ | |||
| import { message } from 'antd'; | |||
| import ClipboardJS from 'clipboard'; | |||
| import { message } from "antd"; | |||
| const clipboard = new ClipboardJS('#copying'); | |||
| @@ -9,4 +9,4 @@ clipboard.on('success', () => { | |||
| clipboard.on('error', () => { | |||
| message.error('复制失败'); | |||
| }); | |||
| }); | |||
| @@ -86,6 +86,19 @@ export const serviceTypeOptions = [ | |||
| { label: '文本', value: ServiceType.Text }, | |||
| ]; | |||
| // 自动化类型 | |||
| export enum AutoMLType { | |||
| Table = 'auto_ml', | |||
| Text = 'text_classification', | |||
| Video = 'video_classification', | |||
| } | |||
| export const autoMLTypeOptions = [ | |||
| { label: '表格', value: AutoMLType.Table }, | |||
| { label: '文本分类', value: AutoMLType.Text }, | |||
| { label: '视频分类', value: AutoMLType.Video }, | |||
| ]; | |||
| // 自动化任务类型 | |||
| export enum AutoMLTaskType { | |||
| Classification = 'classification', | |||
| @@ -148,4 +161,5 @@ export enum AutoMLTrailStatus { | |||
| CRASHED = 'CRASHED', // 崩溃 | |||
| STOP = 'STOP', // 停止 | |||
| CANCELLED = 'CANCELLED', // 取消 | |||
| MEMOUT = 'MEMOUT', // 内存溢出 | |||
| } | |||
| @@ -30,27 +30,26 @@ function CreateActiveLearn() { | |||
| const isCopy = pathname.includes('copy'); | |||
| useEffect(() => { | |||
| // 获取服务详情 | |||
| const getActiveLearnInfo = async (id: number) => { | |||
| const [res] = await to(getActiveLearnInfoReq({ id })); | |||
| if (res && res.data) { | |||
| const info: ActiveLearnData = res.data; | |||
| const { name: name_str, ...rest } = info; | |||
| const name = isCopy ? `${name_str}-copy` : name_str; | |||
| const formData = { | |||
| ...rest, | |||
| name, | |||
| }; | |||
| form.setFieldsValue(formData); | |||
| } | |||
| }; | |||
| // 编辑,复制 | |||
| if (id && !Number.isNaN(id)) { | |||
| getActiveLearnInfo(id); | |||
| } | |||
| }, [id]); | |||
| // 获取服务详情 | |||
| const getActiveLearnInfo = async (id: number) => { | |||
| const [res] = await to(getActiveLearnInfoReq({ id })); | |||
| if (res && res.data) { | |||
| const info: ActiveLearnData = res.data; | |||
| const { name: name_str, ...rest } = info; | |||
| const name = isCopy ? `${name_str}-copy` : name_str; | |||
| const formData = { | |||
| ...rest, | |||
| name, | |||
| }; | |||
| form.setFieldsValue(formData); | |||
| } | |||
| }; | |||
| }, [id, isCopy, form]); | |||
| // 创建、更新、复制实验 | |||
| const createExperiment = async (formData: FormData) => { | |||
| @@ -19,18 +19,17 @@ function ActiveLearnInfo() { | |||
| const [info, setInfo] = useState<ActiveLearnData | undefined>(undefined); | |||
| useEffect(() => { | |||
| // 获取详情 | |||
| const getActiveLearnInfo = async () => { | |||
| const [res] = await to(getActiveLearnInfoReq({ id: id })); | |||
| if (res && res.data) { | |||
| setInfo(res.data); | |||
| } | |||
| }; | |||
| if (id) { | |||
| getActiveLearnInfo(); | |||
| } | |||
| }, []); | |||
| // 获取详情 | |||
| const getActiveLearnInfo = async () => { | |||
| const [res] = await to(getActiveLearnInfoReq({ id: id })); | |||
| if (res && res.data) { | |||
| setInfo(res.data); | |||
| } | |||
| }; | |||
| }, [id]); | |||
| return ( | |||
| <div className={styles['auto-ml-info']}> | |||
| @@ -245,7 +245,7 @@ function BasicInfo({ info, className, runStatus, isInstance = false }: BasicInfo | |||
| ellipsis: true, | |||
| }, | |||
| ]; | |||
| }, [runStatus]); | |||
| }, [runStatus, info]); | |||
| return ( | |||
| <div className={classNames(styles['active-learn-basic'], className)}> | |||
| @@ -39,7 +39,7 @@ export const regressorAlgorithms = [ | |||
| { | |||
| label: 'gaussian_process(高斯回归)', | |||
| value: 'gaussian_process', | |||
| } | |||
| }, | |||
| ]; | |||
| // 框架类型 | |||
| @@ -65,7 +65,6 @@ export const frameworkTypeOptions = [ | |||
| }, | |||
| ]; | |||
| // 查询策略 | |||
| export const queryStrategies = [ | |||
| { | |||
| @@ -87,6 +86,5 @@ export const queryStrategies = [ | |||
| { | |||
| label: 'upper_confidence_bound', | |||
| value: 'upper_confidence_bound', | |||
| } | |||
| }, | |||
| ]; | |||
| @@ -1,7 +1,7 @@ | |||
| import { type ParameterInputObject } from '@/components/ResourceSelect'; | |||
| import { type NodeStatus } from '@/types'; | |||
| import { AutoMLTaskType } from '@/enums'; | |||
| import { HyperParameterFile } from '@/pages/HyperParameter/types'; | |||
| import { type NodeStatus } from '@/types'; | |||
| // 操作类型 | |||
| export enum OperationType { | |||
| @@ -39,7 +39,6 @@ export type FormData = { | |||
| batch_size: number; // batch_size | |||
| epochs: number; // epochs | |||
| lr: number; // 学习率 | |||
| }; | |||
| // 主动学习 | |||
| @@ -75,8 +74,8 @@ export type ActiveLearnInstanceData = { | |||
| update_time: string; | |||
| finish_time: string; | |||
| nodeStatus?: NodeStatus; | |||
| data: string, | |||
| trial_list?: ActiveLearnTrial[] | |||
| data: string; | |||
| trial_list?: ActiveLearnTrial[]; | |||
| }; | |||
| export type ActiveLearnTrial = { | |||
| @@ -85,8 +84,8 @@ export type ActiveLearnTrial = { | |||
| data_num?: number; | |||
| accuracy?: number; | |||
| mse?: number; | |||
| query_idx?: string | |||
| query_idx?: string; | |||
| file?: HyperParameterFile; | |||
| }; | |||
| export {type HyperParameterFile} | |||
| export { type HyperParameterFile }; | |||
| @@ -4,17 +4,19 @@ | |||
| * @Description: 创建实验 | |||
| */ | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { AutoMLEnsembleClass, AutoMLTaskType } from '@/enums'; | |||
| import { AutoMLEnsembleClass, AutoMLTaskType, AutoMLType } from '@/enums'; | |||
| import { addAutoMLReq, getAutoMLInfoReq, updateAutoMLReq } from '@/services/autoML'; | |||
| import { convertEmptyStringToUndefined, parseJsonText, trimCharacter } from '@/utils'; | |||
| import { safeInvoke } from '@/utils/functional'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useLocation, useNavigate, useParams } from '@umijs/max'; | |||
| import { App, Button, Form } from 'antd'; | |||
| import { omit } from 'lodash'; | |||
| import { useEffect } from 'react'; | |||
| import BasicConfig from '../components/CreateForm/BasicConfig'; | |||
| import DatasetConfig from '../components/CreateForm/DatasetConfig'; | |||
| import ExecuteConfig from '../components/CreateForm/ExecuteConfig'; | |||
| import TextExecuteConfig from '../components/CreateForm/TextExecuteConfig'; | |||
| import TrialConfig from '../components/CreateForm/TrialConfig'; | |||
| import { AutoMLData, FormData } from '../types'; | |||
| import styles from './index.less'; | |||
| @@ -27,6 +29,7 @@ function CreateAutoML() { | |||
| const id = safeInvoke(Number)(params.id); | |||
| const { pathname } = useLocation(); | |||
| const isCopy = pathname.includes('copy'); | |||
| const type = Form.useWatch('type', form); | |||
| useEffect(() => { | |||
| // 获取服务详情 | |||
| @@ -34,6 +37,8 @@ function CreateAutoML() { | |||
| const [res] = await to(getAutoMLInfoReq({ id })); | |||
| if (res && res.data) { | |||
| const autoMLInfo: AutoMLData = res.data; | |||
| const { name: name_str, description, type, param } = autoMLInfo; | |||
| const paramObj = parseJsonText(param); | |||
| const { | |||
| include_classifier: include_classifier_str, | |||
| include_feature_preprocessor: include_feature_preprocessor_str, | |||
| @@ -42,9 +47,8 @@ function CreateAutoML() { | |||
| exclude_feature_preprocessor: exclude_feature_preprocessor_str, | |||
| exclude_regressor: exclude_regressor_str, | |||
| metrics: metrics_str, | |||
| ml_name: ml_name_str, | |||
| ...rest | |||
| } = autoMLInfo; | |||
| } = paramObj; | |||
| const include_classifier = include_classifier_str?.split(',').filter(Boolean); | |||
| const include_feature_preprocessor = include_feature_preprocessor_str | |||
| ?.split(',') | |||
| @@ -60,7 +64,7 @@ function CreateAutoML() { | |||
| name: key, | |||
| value, | |||
| })); | |||
| const ml_name = isCopy ? `${ml_name_str}-copy` : ml_name_str; | |||
| const name = isCopy ? `${name_str}-copy` : name_str; | |||
| const formData = { | |||
| ...rest, | |||
| @@ -71,7 +75,9 @@ function CreateAutoML() { | |||
| exclude_feature_preprocessor, | |||
| exclude_regressor, | |||
| metrics, | |||
| ml_name, | |||
| name, | |||
| description, | |||
| type, | |||
| }; | |||
| form.setFieldsValue(formData); | |||
| @@ -86,6 +92,7 @@ function CreateAutoML() { | |||
| // 创建、更新、复制实验 | |||
| const createExperiment = async (formData: FormData) => { | |||
| const { name, description, type } = formData; | |||
| const include_classifier = formData['include_classifier']?.join(','); | |||
| const include_feature_preprocessor = formData['include_feature_preprocessor']?.join(','); | |||
| const include_regressor = formData['include_regressor']?.join(','); | |||
| @@ -104,8 +111,8 @@ function CreateAutoML() { | |||
| const target_columns = trimCharacter(formData['target_columns'], ','); | |||
| // 根据后台要求,修改表单数据 | |||
| const object = { | |||
| ...formData, | |||
| const param = { | |||
| ...omit(formData, ['name', 'description', 'type']), | |||
| include_classifier: convertEmptyStringToUndefined(include_classifier), | |||
| include_feature_preprocessor: convertEmptyStringToUndefined(include_feature_preprocessor), | |||
| include_regressor: convertEmptyStringToUndefined(include_regressor), | |||
| @@ -116,6 +123,13 @@ function CreateAutoML() { | |||
| target_columns, | |||
| }; | |||
| const object = { | |||
| name, | |||
| description, | |||
| type, | |||
| param: JSON.stringify(param), | |||
| }; | |||
| const params = | |||
| id && !isCopy | |||
| ? { | |||
| @@ -169,6 +183,7 @@ function CreateAutoML() { | |||
| autoComplete="off" | |||
| scrollToFirstError | |||
| initialValues={{ | |||
| type: AutoMLType.Table, | |||
| task_type: AutoMLTaskType.Classification, | |||
| shuffle: false, | |||
| ensemble_class: AutoMLEnsembleClass.Default, | |||
| @@ -186,9 +201,16 @@ function CreateAutoML() { | |||
| }} | |||
| > | |||
| <BasicConfig /> | |||
| <ExecuteConfig /> | |||
| <TrialConfig /> | |||
| <DatasetConfig /> | |||
| {type === AutoMLType.Table ? ( | |||
| <> | |||
| <ExecuteConfig /> | |||
| <TrialConfig /> | |||
| <DatasetConfig /> | |||
| </> | |||
| ) : type === AutoMLType.Text ? ( | |||
| <TextExecuteConfig /> | |||
| ) : null} | |||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }} style={{ marginTop: '40px' }}> | |||
| <Button type="primary" htmlType="submit"> | |||
| @@ -5,6 +5,7 @@ | |||
| */ | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { getAutoMLInfoReq } from '@/services/autoML'; | |||
| import { parseJsonText } from '@/utils'; | |||
| import { safeInvoke } from '@/utils/functional'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useParams } from '@umijs/max'; | |||
| @@ -23,7 +24,14 @@ function AutoMLInfo() { | |||
| const getAutoMLInfo = async () => { | |||
| const [res] = await to(getAutoMLInfoReq({ id: autoMLId })); | |||
| if (res && res.data) { | |||
| setAutoMLInfo(res.data); | |||
| const info = res.data; | |||
| const param = info.param; | |||
| setAutoMLInfo({ | |||
| ...info, | |||
| param: undefined, | |||
| ...parseJsonText(param), | |||
| }); | |||
| } | |||
| }; | |||
| @@ -1,5 +1,11 @@ | |||
| import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | |||
| import { AutoMLTaskType, autoMLEnsembleClassOptions, autoMLTaskTypeOptions } from '@/enums'; | |||
| import { | |||
| AutoMLTaskType, | |||
| AutoMLType, | |||
| autoMLEnsembleClassOptions, | |||
| autoMLTaskTypeOptions, | |||
| } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { AutoMLData } from '@/pages/AutoML/types'; | |||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import { type NodeStatus } from '@/types'; | |||
| @@ -37,6 +43,7 @@ type AutoMLBasicProps = { | |||
| }; | |||
| function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLBasicProps) { | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const basicDatas: BasicInfoData[] = useMemo(() => { | |||
| if (!info) { | |||
| return []; | |||
| @@ -45,11 +52,11 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB | |||
| return [ | |||
| { | |||
| label: '实验名称', | |||
| value: info.ml_name, | |||
| value: info.name, | |||
| }, | |||
| { | |||
| label: '实验描述', | |||
| value: info.ml_description, | |||
| value: info.description, | |||
| }, | |||
| { | |||
| label: '创建人', | |||
| @@ -72,103 +79,136 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB | |||
| if (!info) { | |||
| return []; | |||
| } | |||
| return [ | |||
| { | |||
| label: '任务类型', | |||
| value: info.task_type, | |||
| format: formatEnum(autoMLTaskTypeOptions), | |||
| }, | |||
| { | |||
| label: '特征预处理算法', | |||
| value: info.include_feature_preprocessor, | |||
| }, | |||
| { | |||
| label: '排除的特征预处理算法', | |||
| value: info.exclude_feature_preprocessor, | |||
| }, | |||
| { | |||
| label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', | |||
| value: | |||
| info.task_type === AutoMLTaskType.Regression | |||
| ? info.include_regressor | |||
| : info.include_classifier, | |||
| }, | |||
| { | |||
| label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', | |||
| value: | |||
| info.task_type === AutoMLTaskType.Regression | |||
| ? info.exclude_regressor | |||
| : info.exclude_classifier, | |||
| }, | |||
| { | |||
| label: '集成方式', | |||
| value: info.ensemble_class, | |||
| format: formatEnum(autoMLEnsembleClassOptions), | |||
| }, | |||
| { | |||
| label: '集成模型数量', | |||
| value: info.ensemble_size, | |||
| }, | |||
| { | |||
| label: '集成最佳模型数量', | |||
| value: info.ensemble_nbest, | |||
| }, | |||
| { | |||
| label: '最大数量', | |||
| value: info.max_models_on_disc, | |||
| }, | |||
| { | |||
| label: '内存限制(MB)', | |||
| value: info.memory_limit, | |||
| }, | |||
| { | |||
| label: '单次时间限制(秒)', | |||
| value: info.per_run_time_limit, | |||
| }, | |||
| { | |||
| label: '搜索时间限制(秒)', | |||
| value: info.time_left_for_this_task, | |||
| }, | |||
| { | |||
| label: '重采样策略', | |||
| value: info.resampling_strategy, | |||
| }, | |||
| { | |||
| label: '交叉验证折数', | |||
| value: info.folds, | |||
| }, | |||
| { | |||
| label: '是否打乱', | |||
| value: info.shuffle, | |||
| format: formatBoolean, | |||
| }, | |||
| { | |||
| label: '训练集比率', | |||
| value: info.train_size, | |||
| }, | |||
| { | |||
| label: '测试集比率', | |||
| value: info.test_size, | |||
| }, | |||
| { | |||
| label: '计算指标', | |||
| value: info.scoring_functions, | |||
| }, | |||
| { | |||
| label: '随机种子', | |||
| value: info.seed, | |||
| }, | |||
| { | |||
| label: '数据集', | |||
| value: info.dataset, | |||
| format: formatDataset, | |||
| }, | |||
| { | |||
| label: '预测目标列', | |||
| value: info.target_columns, | |||
| }, | |||
| ]; | |||
| }, [info]); | |||
| if (info.type === AutoMLType.Table) { | |||
| return [ | |||
| { | |||
| label: '任务类型', | |||
| value: info.task_type, | |||
| format: formatEnum(autoMLTaskTypeOptions), | |||
| }, | |||
| { | |||
| label: '特征预处理算法', | |||
| value: info.include_feature_preprocessor, | |||
| }, | |||
| { | |||
| label: '排除的特征预处理算法', | |||
| value: info.exclude_feature_preprocessor, | |||
| }, | |||
| { | |||
| label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', | |||
| value: | |||
| info.task_type === AutoMLTaskType.Regression | |||
| ? info.include_regressor | |||
| : info.include_classifier, | |||
| }, | |||
| { | |||
| label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', | |||
| value: | |||
| info.task_type === AutoMLTaskType.Regression | |||
| ? info.exclude_regressor | |||
| : info.exclude_classifier, | |||
| }, | |||
| { | |||
| label: '集成方式', | |||
| value: info.ensemble_class, | |||
| format: formatEnum(autoMLEnsembleClassOptions), | |||
| }, | |||
| { | |||
| label: '集成模型数量', | |||
| value: info.ensemble_size, | |||
| }, | |||
| { | |||
| label: '集成最佳模型数量', | |||
| value: info.ensemble_nbest, | |||
| }, | |||
| { | |||
| label: '最大数量', | |||
| value: info.max_models_on_disc, | |||
| }, | |||
| { | |||
| label: '内存限制(MB)', | |||
| value: info.memory_limit, | |||
| }, | |||
| { | |||
| label: '单次时间限制(秒)', | |||
| value: info.per_run_time_limit, | |||
| }, | |||
| { | |||
| label: '搜索时间限制(秒)', | |||
| value: info.time_left_for_this_task, | |||
| }, | |||
| { | |||
| label: '重采样策略', | |||
| value: info.resampling_strategy, | |||
| }, | |||
| { | |||
| label: '交叉验证折数', | |||
| value: info.folds, | |||
| }, | |||
| { | |||
| label: '是否打乱', | |||
| value: info.shuffle, | |||
| format: formatBoolean, | |||
| }, | |||
| { | |||
| label: '训练集比率', | |||
| value: info.train_size, | |||
| }, | |||
| { | |||
| label: '测试集比率', | |||
| value: info.test_size, | |||
| }, | |||
| { | |||
| label: '计算指标', | |||
| value: info.scoring_functions, | |||
| }, | |||
| { | |||
| label: '随机种子', | |||
| value: info.seed, | |||
| }, | |||
| { | |||
| label: '数据集', | |||
| value: info.dataset, | |||
| format: formatDataset, | |||
| }, | |||
| { | |||
| label: '预测目标列', | |||
| value: info.target_columns, | |||
| }, | |||
| ]; | |||
| } else if (info.type === AutoMLType.Text) { | |||
| return [ | |||
| { | |||
| label: '模型', | |||
| value: info.model_type, | |||
| }, | |||
| { | |||
| label: '数据集', | |||
| value: info.dataset, | |||
| format: formatDataset, | |||
| }, | |||
| { | |||
| label: '资源规格', | |||
| value: info.computing_resource_id, | |||
| format: getResourceDescription, | |||
| }, | |||
| { | |||
| label: 'batch_size', | |||
| value: info.batch_size, | |||
| }, | |||
| { | |||
| label: 'epochs', | |||
| value: info.epochs, | |||
| }, | |||
| { | |||
| label: '学习率', | |||
| value: info.lr, | |||
| }, | |||
| ]; | |||
| } else { | |||
| return []; | |||
| } | |||
| }, [info, getResourceDescription]); | |||
| const metricsData = useMemo(() => { | |||
| if (!info) { | |||
| @@ -229,7 +269,7 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB | |||
| ), | |||
| }, | |||
| ]; | |||
| }, [runStatus]); | |||
| }, [runStatus, info]); | |||
| return ( | |||
| <div className={classNames(styles['auto-ml-basic'], className)}> | |||
| @@ -255,7 +295,9 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB | |||
| labelWidth={150} | |||
| style={{ marginBottom: '20px' }} | |||
| /> | |||
| <ConfigInfo title="优化指标" datas={metricsData} labelWidth={70} /> | |||
| {info?.type === AutoMLType.Table && ( | |||
| <ConfigInfo title="优化指标" datas={metricsData} labelWidth={70} /> | |||
| )} | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -1,5 +1,7 @@ | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { Col, Form, Input, Row } from 'antd'; | |||
| import { autoMLTypeOptions } from '@/enums'; | |||
| import { Col, Form, Input, Radio, Row } from 'antd'; | |||
| function BasicConfig() { | |||
| return ( | |||
| <> | |||
| @@ -12,7 +14,7 @@ function BasicConfig() { | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="实验名称" | |||
| name="ml_name" | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| @@ -32,7 +34,7 @@ function BasicConfig() { | |||
| <Col span={20}> | |||
| <Form.Item | |||
| label="实验描述" | |||
| name="ml_description" | |||
| name="description" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| @@ -50,6 +52,14 @@ function BasicConfig() { | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="类型" name="type" rules={[{ required: true, message: '请选择类型' }]}> | |||
| <Radio.Group options={autoMLTypeOptions}></Radio.Group> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,137 @@ | |||
| import ParameterSelect from '@/components/ParameterSelect'; | |||
| import ResourceSelect, { | |||
| ResourceSelectorType, | |||
| requiredValidator, | |||
| } from '@/components/ResourceSelect'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { Col, Form, InputNumber, Row, Select } from 'antd'; | |||
| // 模型 | |||
| const modelTypeOptions = [ | |||
| 'TextCNN', | |||
| 'TextRNN', | |||
| 'FasetText', | |||
| 'TextRCNN', | |||
| 'TextRNN_Att', | |||
| 'DPCNN', | |||
| 'Transformer', | |||
| ].map((name) => ({ label: name, value: name })); | |||
| function TextExecuteConfig() { | |||
| return ( | |||
| <> | |||
| <SubAreaTitle | |||
| title="执行配置" | |||
| image={require('@/assets/img/model-deployment.png')} | |||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="模型" | |||
| name="model_type" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择模型', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select allowClear placeholder="请选择模型" options={modelTypeOptions} showSearch /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="数据集" | |||
| name="dataset" | |||
| rules={[ | |||
| { | |||
| validator: requiredValidator, | |||
| message: '请选择数据集', | |||
| }, | |||
| ]} | |||
| required | |||
| > | |||
| <ResourceSelect | |||
| type={ResourceSelectorType.Dataset} | |||
| placeholder="请选择数据集" | |||
| canInput={false} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="computing_resource_id" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <ParameterSelect dataType="resource" placeholder="请选择资源规格" /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="batch_size" | |||
| name="batch_size" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入 batch_size', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入 batch_size" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="epochs" | |||
| name="epochs" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入epochs', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入epochs" min={0} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="学习率" | |||
| name="lr" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入学习率', | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入学习率" min={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| </> | |||
| ); | |||
| } | |||
| export default TextExecuteConfig; | |||
| @@ -55,7 +55,7 @@ function ExperimentHistory({ calcMetrics, fileUrl, isClassification }: Experimen | |||
| if (fileUrl) { | |||
| getHistoryFile(); | |||
| } | |||
| }, [fileUrl, isClassification]); | |||
| }, [fileUrl, isClassification, calcMetrics]); | |||
| const columns: TableProps<TableData>['columns'] = [ | |||
| { | |||
| @@ -64,8 +64,8 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo | |||
| stopInsReq: stopExperimentInsReq, | |||
| title: '自主机器学习', | |||
| pathPrefix: 'automl', | |||
| nameProperty: 'ml_name', | |||
| descProperty: 'ml_description', | |||
| nameProperty: 'name', | |||
| descProperty: 'description', | |||
| idProperty: 'autoMlId', | |||
| }, | |||
| [ExperimentListType.HyperParameter]: { | |||
| @@ -6,7 +6,7 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import { ExperimentStatus, autoMLTypeOptions } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { AutoMLData } from '@/pages/AutoML/types'; | |||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| @@ -244,6 +244,20 @@ function ExperimentList({ type }: ExperimentListProps) { | |||
| } | |||
| }; | |||
| const typeColumns = [ | |||
| { | |||
| title: '类型', | |||
| dataIndex: 'type', | |||
| key: 'type', | |||
| width: 120, | |||
| render: tableCellRender(false, TableCellValueType.Enum, { | |||
| options: autoMLTypeOptions, | |||
| }), | |||
| }, | |||
| ]; | |||
| const diffColumns = type === ExperimentListType.AutoML ? typeColumns : []; | |||
| const columns: TableProps<AutoMLData>['columns'] = [ | |||
| { | |||
| title: '实验名称', | |||
| @@ -260,6 +274,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||
| key: 'description', | |||
| render: tableCellRender(true), | |||
| }, | |||
| ...diffColumns, | |||
| { | |||
| title: '创建时间', | |||
| dataIndex: 'update_time', | |||
| @@ -9,8 +9,9 @@ export enum OperationType { | |||
| // 表单数据 | |||
| export type FormData = { | |||
| ml_name: string; // 实验名称 | |||
| ml_description: string; // 实验描述 | |||
| name: string; // 实验名称 | |||
| description: string; // 实验描述 | |||
| type: string; // 类型 | |||
| ensemble_class?: string; // 集成方式 | |||
| ensemble_nbest?: string; // 集成最佳模型数量 | |||
| ensemble_size?: number; // 集成模型数量 | |||
| @@ -37,6 +38,11 @@ export type FormData = { | |||
| metrics?: { name: string; value: number }[]; // 指标权重 | |||
| dataset: ParameterInputObject; // 数据集 | |||
| target_columns: string; // 预测目标列 | |||
| model_type?: string; // 文本模型 | |||
| computing_resource_id?: number; // 资源规格 | |||
| batch_size?: number; | |||
| epochs?: number; | |||
| lr?: number; | |||
| }; | |||
| export type AutoMLData = { | |||
| @@ -57,6 +63,7 @@ export type AutoMLData = { | |||
| update_by?: string; | |||
| update_time?: string; | |||
| status_list: string; // 最近五次运行状态 | |||
| param: string; // 参数json字符串 | |||
| } & Omit< | |||
| FormData, | |||
| 'metrics|dataset|include_classifier|include_feature_preprocessor|include_regressor|exclude_classifier|exclude_feature_preprocessor|exclude_regressor' | |||
| @@ -55,4 +55,4 @@ | |||
| .table-best-row { | |||
| color: @success-color; | |||
| font-weight: bold; | |||
| } | |||
| } | |||
| @@ -182,7 +182,7 @@ function HyperParameterBasic({ | |||
| ellipsis: true, | |||
| }, | |||
| ]; | |||
| }, [runStatus]); | |||
| }, [runStatus, info]); | |||
| return ( | |||
| <div className={classNames(styles['hyper-parameter-basic'], className)}> | |||
| @@ -1,15 +1,14 @@ | |||
| .trail-file-tree { | |||
| :global { | |||
| .ant-tree-node-selected { | |||
| .trail-file-tree__icon { | |||
| color: white; | |||
| } | |||
| } | |||
| :global { | |||
| .ant-tree-node-selected { | |||
| .trail-file-tree__icon { | |||
| margin-left: 8px; | |||
| color: @primary-color; | |||
| color: white; | |||
| } | |||
| } | |||
| .trail-file-tree__icon { | |||
| margin-left: 8px; | |||
| color: @primary-color; | |||
| } | |||
| } | |||
| } | |||
| @@ -31,7 +31,7 @@ type ModelMetricsProps = { | |||
| version: string; // 当前版本 | |||
| }; | |||
| function ModelMetrics({ resourceId, identifier, owner, version, refreshTag }: ModelMetricsProps) { | |||
| function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsProps) { | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | |||
| current: 1, | |||
| pageSize: 10, | |||
| @@ -9,6 +9,7 @@ import { serviceTypeOptions } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { ServiceCreatedMessage } from '@/utils/constant'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||
| @@ -27,13 +28,8 @@ import { | |||
| import { type SearchProps } from 'antd/es/input'; | |||
| import classNames from 'classnames'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| import { | |||
| CreateServiceVersionFrom, | |||
| ServiceData, | |||
| ServiceOperationType, | |||
| } from '../types'; | |||
| import { CreateServiceVersionFrom, ServiceData, ServiceOperationType } from '../types'; | |||
| import styles from './index.less'; | |||
| import { ServiceCreatedMessage } from '@/utils/constant'; | |||
| const allServiceTypeOptions = [{ label: '全部', value: '' }, ...serviceTypeOptions]; | |||
| @@ -79,7 +79,7 @@ function VersionCompareModal({ version1, version2, ...rest }: VersionCompareModa | |||
| }, | |||
| }, | |||
| { | |||
| key: 'resource', | |||
| key: 'computing_resource_id', | |||
| text: '资源规格', | |||
| format: getResourceDescription, | |||
| }, | |||
| @@ -32,7 +32,7 @@ export type UserFormProps = { | |||
| const UserForm: React.FC<UserFormProps> = (props) => { | |||
| const [form] = Form.useForm(); | |||
| const userId = Form.useWatch('userId', form); | |||
| // const userId = Form.useWatch('userId', form); | |||
| const { sexOptions, statusOptions } = props; | |||
| const { roles, posts, depts } = props; | |||
| const formLayout = { | |||
| @@ -6,7 +6,6 @@ | |||
| import { request } from '@umijs/max'; | |||
| // 分页查询超参数自动寻优 | |||
| export function getActiveLearnListReq(params) { | |||
| return request(`/api/mmp/activeLearn`, { | |||
| @@ -87,7 +86,6 @@ export function deleteActiveLearnInsReq(id) { | |||
| export function batchDeleteActiveLearnInsReq(data) { | |||
| return request(`/api/mmp/activeLearnIns/batchDelete`, { | |||
| method: 'DELETE', | |||
| data | |||
| data, | |||
| }); | |||
| } | |||
| @@ -13,4 +13,4 @@ export function getClientInfoReq() { | |||
| return request(`/api/auth/oauth2ClientInfo`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| } | |||
| @@ -6,10 +6,9 @@ | |||
| import { request } from '@umijs/max'; | |||
| // 分页查询自动学习 | |||
| export function getAutoMLListReq(params) { | |||
| return request(`/api/mmp/autoML`, { | |||
| return request(`/api/mmp/machineLearn`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| @@ -17,7 +16,7 @@ export function getAutoMLListReq(params) { | |||
| // 查询自动学习详情 | |||
| export function getAutoMLInfoReq(params) { | |||
| return request(`/api/mmp/autoML/getAutoMlDetail`, { | |||
| return request(`/api/mmp/machineLearn/getMLDetail`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| @@ -25,7 +24,7 @@ export function getAutoMLInfoReq(params) { | |||
| // 新增自动学习 | |||
| export function addAutoMLReq(data) { | |||
| return request(`/api/mmp/autoML`, { | |||
| return request(`/api/mmp/machineLearn`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| @@ -33,7 +32,7 @@ export function addAutoMLReq(data) { | |||
| // 编辑自动学习 | |||
| export function updateAutoMLReq(data) { | |||
| return request(`/api/mmp/autoML`, { | |||
| return request(`/api/mmp/machineLearn`, { | |||
| method: 'PUT', | |||
| data, | |||
| }); | |||
| @@ -41,14 +40,14 @@ export function updateAutoMLReq(data) { | |||
| // 删除自动学习 | |||
| export function deleteAutoMLReq(id) { | |||
| return request(`/api/mmp/autoML/${id}`, { | |||
| return request(`/api/mmp/machineLearn/${id}`, { | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| // 运行自动学习 | |||
| export function runAutoMLReq(id) { | |||
| return request(`/api/mmp/autoML/run/${id}`, { | |||
| return request(`/api/mmp/machineLearn/run/${id}`, { | |||
| method: 'POST', | |||
| }); | |||
| } | |||
| @@ -56,7 +55,7 @@ export function runAutoMLReq(id) { | |||
| // ----------------------- 实验实例 ----------------------- | |||
| // 获取实验实例列表 | |||
| export function getExperimentInsListReq(params) { | |||
| return request(`/api/mmp/autoMLIns`, { | |||
| return request(`/api/mmp/machineLearnIns`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| @@ -64,30 +63,29 @@ export function getExperimentInsListReq(params) { | |||
| // 查询实验实例详情 | |||
| export function getExperimentInsReq(id) { | |||
| return request(`/api/mmp/autoMLIns/${id}`, { | |||
| return request(`/api/mmp/machineLearnIns/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 停止实验实例 | |||
| export function stopExperimentInsReq(id) { | |||
| return request(`/api/mmp/autoMLIns/${id}`, { | |||
| return request(`/api/mmp/machineLearnIns/${id}`, { | |||
| method: 'PUT', | |||
| }); | |||
| } | |||
| // 删除实验实例 | |||
| export function deleteExperimentInsReq(id) { | |||
| return request(`/api/mmp/autoMLIns/${id}`, { | |||
| return request(`/api/mmp/machineLearnIns/${id}`, { | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| // 批量删除实验实例 | |||
| export function batchDeleteExperimentInsReq(data) { | |||
| return request(`/api/mmp/autoMLIns/batchDelete`, { | |||
| return request(`/api/mmp/machineLearnIns/batchDelete`, { | |||
| method: 'DELETE', | |||
| data | |||
| data, | |||
| }); | |||
| } | |||
| @@ -34,6 +34,6 @@ export function deleteCodeConfigReq(id) { | |||
| // 查询代码配置详情 | |||
| export function getCodeConfigDetailReq(id) { | |||
| return request(`/api/mmp/codeConfig/${id}`, { | |||
| method: 'GET' | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| } | |||
| @@ -42,7 +42,6 @@ export function deleteDataset(params) { | |||
| }); | |||
| } | |||
| // 查询数据集版本列表 | |||
| export function getDatasetVersionList(params) { | |||
| return request(`/api/mmp/newdataset/getVersionList`, { | |||
| @@ -63,7 +62,7 @@ export function addDatasetVersion(data) { | |||
| export function downloadAllFiles(params) { | |||
| return request(`/api/mmp/newdataset/downloadAllFiles`, { | |||
| method: 'GET', | |||
| params | |||
| params, | |||
| }); | |||
| } | |||
| @@ -109,7 +108,6 @@ export function deleteModel(params) { | |||
| }); | |||
| } | |||
| // 查询模型详情 | |||
| export function getModelInfo(params) { | |||
| return request(`/api/mmp/newmodel/getModelDetail`, { | |||
| @@ -134,7 +132,6 @@ export function addModelVersion(data) { | |||
| }); | |||
| } | |||
| // 删除模型版本 | |||
| export function deleteModelVersion(params) { | |||
| return request(`/api/mmp/newmodel/deleteVersion`, { | |||
| @@ -147,7 +144,7 @@ export function deleteModelVersion(params) { | |||
| export function getModelAtlasReq(params) { | |||
| return request(`/api/mmp/newmodel/getModelDependencyTree`, { | |||
| method: 'GET', | |||
| params | |||
| params, | |||
| }); | |||
| } | |||
| @@ -155,7 +152,7 @@ export function getModelAtlasReq(params) { | |||
| export function exportModelReq(data) { | |||
| return request(`/api/mmp/models/exportModel`, { | |||
| method: 'POST', | |||
| data | |||
| data, | |||
| }); | |||
| } | |||
| @@ -163,7 +160,7 @@ export function exportModelReq(data) { | |||
| export function getModelPageVersionsReq(params) { | |||
| return request(`/api/mmp/newmodel/queryVersions`, { | |||
| method: 'GET', | |||
| params | |||
| params, | |||
| }); | |||
| } | |||
| @@ -171,7 +168,7 @@ export function getModelPageVersionsReq(params) { | |||
| export function getModelVersionsMetricsReq(data) { | |||
| return request(`/api/mmp/newmodel/queryVersionsMetrics`, { | |||
| method: 'POST', | |||
| data | |||
| data, | |||
| }); | |||
| } | |||
| @@ -183,7 +180,6 @@ export function compareModelVersion(data) { | |||
| }); | |||
| } | |||
| // 删除上传的文件 | |||
| export function deleteUploadFileReq(params) { | |||
| return request(`/api/mmp/newdataset/deleteFile`, { | |||
| @@ -132,7 +132,7 @@ export function getTensorBoardStatusReq(data) { | |||
| export function getExpEvaluateInfosReq(experimentId, params) { | |||
| return request(`/api/mmp/aim/getExpEvaluateInfos/${experimentId}`, { | |||
| method: 'GET', | |||
| params | |||
| params, | |||
| }); | |||
| } | |||
| @@ -140,7 +140,7 @@ export function getExpEvaluateInfosReq(experimentId, params) { | |||
| export function getExpTrainInfosReq(experimentId, params) { | |||
| return request(`/api/mmp/aim/getExpTrainInfos/${experimentId}`, { | |||
| method: 'GET', | |||
| params | |||
| params, | |||
| }); | |||
| } | |||
| @@ -148,6 +148,6 @@ export function getExpTrainInfosReq(experimentId, params) { | |||
| export function getExpMetricsReq(data) { | |||
| return request(`/api/mmp/aim/getExpMetrics`, { | |||
| method: 'POST', | |||
| data | |||
| data, | |||
| }); | |||
| } | |||
| @@ -4,7 +4,6 @@ | |||
| * @Description: 请求文件,比如 json 文件 | |||
| */ | |||
| import { request } from '@umijs/max'; | |||
| // 获取文件,不需要token,非结构化数据 | |||
| @@ -15,6 +14,6 @@ export function getFileReq(url, config) { | |||
| isToken: false, | |||
| }, | |||
| skipValidating: true, | |||
| ...config | |||
| ...config, | |||
| }); | |||
| } | |||
| } | |||
| @@ -6,7 +6,6 @@ | |||
| import { request } from '@umijs/max'; | |||
| // 分页查询超参数自动寻优 | |||
| export function getRayListReq(params) { | |||
| return request(`/api/mmp/ray`, { | |||
| @@ -87,7 +86,7 @@ export function deleteRayInsReq(id) { | |||
| export function batchDeleteRayInsReq(data) { | |||
| return request(`/api/mmp/rayIns/batchDelete`, { | |||
| method: 'DELETE', | |||
| data | |||
| data, | |||
| }); | |||
| } | |||
| @@ -95,6 +94,6 @@ export function batchDeleteRayInsReq(data) { | |||
| export function getExpMetricsReq(data) { | |||
| return request(`/api/mmp/rayIns/getExpMetrics`, { | |||
| method: 'POST', | |||
| data | |||
| data, | |||
| }); | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-03-25 13:52:54 | |||
| * @Description: | |||
| * @Description: | |||
| */ | |||
| import { request } from '@umijs/max'; | |||
| // 查询流水线列表 | |||
| @@ -73,6 +73,6 @@ export function getWorkflowById(id) { | |||
| export function getComputingResourceReq(params) { | |||
| return request(`/api/mmp/computingResource`, { | |||
| method: 'GET', | |||
| params | |||
| params, | |||
| }); | |||
| } | |||
| @@ -14,7 +14,6 @@ export function createIcon(icon: string | any): React.ReactNode | string { | |||
| } | |||
| const ele = allIcons[icon]; | |||
| if (ele) { | |||
| return React.createElement(allIcons[icon]); | |||
| } | |||
| return ''; | |||
| @@ -4,15 +4,15 @@ import { request } from '@umijs/max'; | |||
| export enum MimeType { | |||
| XLSX = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | |||
| ZIP = 'application/zip', | |||
| JSON = 'application/json' | |||
| }; | |||
| JSON = 'application/json', | |||
| } | |||
| /** | |||
| * 解析blob响应内容并下载 | |||
| * @param res - blob响应内容 | |||
| * @param mimeType - MIME类型 | |||
| */ | |||
| export function resolveBlob(res: any, mimeType: string, specifiedFileName: string = "file") { | |||
| 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, | |||
| @@ -77,7 +77,13 @@ export async function downloadXlsx( | |||
| * @param method - 请求方法 | |||
| * @param options - 请求选项 | |||
| */ | |||
| export function downloadCommonFile(url: string, type: string, fileName: string = "file", method: string = 'GET', options?: Record<string, any>) { | |||
| export function downloadCommonFile( | |||
| url: string, | |||
| type: string, | |||
| fileName: string = 'file', | |||
| method: string = 'GET', | |||
| options?: Record<string, any>, | |||
| ) { | |||
| request(url, { | |||
| method: method, | |||
| headers: { | |||
| @@ -172,7 +172,14 @@ export const formatBoolean = (value: boolean): string => { | |||
| return value ? '是' : '否'; | |||
| }; | |||
| type FormatEnumFunc = (value: string | number) => React.ReactNode; | |||
| export type FormatEnumFunc = (value: string | number) => React.ReactNode; | |||
| // 枚举选项 | |||
| export type EnumOptions = { | |||
| label?: React.ReactNode; | |||
| value?: string | number | null; | |||
| [key: string | number]: any; | |||
| }; | |||
| /** | |||
| * 格式化枚举 | |||
| @@ -180,9 +187,7 @@ type FormatEnumFunc = (value: string | number) => React.ReactNode; | |||
| * @param options - 枚举选项数组 | |||
| * @return 一个函数,参数是枚举值,从选项数组中找到对应的项,然后返回该项的 label | |||
| */ | |||
| export const formatEnum = ( | |||
| options: { value?: string | number | null; label?: React.ReactNode }[], | |||
| ): FormatEnumFunc => { | |||
| export const formatEnum = (options: EnumOptions[]): FormatEnumFunc => { | |||
| return (value: string | number) => { | |||
| const option = options.find((item) => item.value === value); | |||
| return option && option.label ? option.label : '--'; | |||
| @@ -8,6 +8,8 @@ import { isEmpty } from '@/utils'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { Tooltip, TooltipProps, Typography } from 'antd'; | |||
| import dayjs from 'dayjs'; | |||
| import React from 'react'; | |||
| import { formatEnum, type EnumOptions } from './format'; | |||
| export enum TableCellValueType { | |||
| /** 序号 */ | |||
| @@ -20,6 +22,8 @@ export enum TableCellValueType { | |||
| Array = 'Array', | |||
| /** 链接 */ | |||
| Link = 'Link', | |||
| /** 枚举 */ | |||
| Enum = 'Enum', | |||
| /** 自定义 */ | |||
| Custom = 'Custom', | |||
| } | |||
| @@ -35,8 +39,14 @@ export type TableCellValueOptions<T> = { | |||
| dateFormat?: string; | |||
| /** 链接点击回调,类型为 Link 时有效 */ | |||
| onClick?: (record: T, e: React.MouseEvent) => void; | |||
| /** 枚举选项,类型为 Enum 时有效*/ | |||
| options?: EnumOptions[]; | |||
| /** 自定义函数,类型为 Custom 时有效*/ | |||
| format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; | |||
| format?: ( | |||
| value: any | undefined | null, | |||
| record?: T, | |||
| index?: number, | |||
| ) => React.ReactNode | undefined | null; | |||
| /** 省略时是否可以复制 */ | |||
| copyable?: boolean; | |||
| }; | |||
| @@ -110,6 +120,11 @@ function tableCellRender<T>( | |||
| case TableCellValueType.Array: | |||
| text = formatArray(options?.property)(value); | |||
| break; | |||
| case TableCellValueType.Enum: | |||
| if (options?.options) { | |||
| text = formatEnum(options.options)(value); | |||
| } | |||
| break; | |||
| case TableCellValueType.Custom: | |||
| text = options?.format?.(value, record, index); | |||
| break; | |||