Browse Source

Merge pull request '合并dev-zw' (#193) from dev-zw into dev

dev-active_learn
cp3hnu 10 months ago
parent
commit
4cbee7b8f1
40 changed files with 451 additions and 147 deletions
  1. +5
    -0
      react-ui/config/routes.ts
  2. +1
    -1
      react-ui/package.json
  3. BIN
      react-ui/src/assets/img/confirm-icon.png
  4. +0
    -0
      react-ui/src/assets/img/copy-icon.png
  5. +12
    -1
      react-ui/src/components/IFramePage/index.tsx
  6. +1
    -1
      react-ui/src/components/ParameterInput/index.less
  7. +7
    -2
      react-ui/src/components/ParameterInput/index.tsx
  8. +2
    -0
      react-ui/src/components/ResourceSelectorModal/config.tsx
  9. +6
    -1
      react-ui/src/hooks/useCacheState.ts
  10. +3
    -9
      react-ui/src/hooks/useCheck.ts
  11. +3
    -3
      react-ui/src/hooks/useComputingResource.ts
  12. +3
    -1
      react-ui/src/hooks/useDraggable.ts
  13. +15
    -2
      react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx
  14. +8
    -15
      react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx
  15. +3
    -2
      react-ui/src/pages/Dataset/components/AddModelModal/index.tsx
  16. +2
    -1
      react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx
  17. +12
    -0
      react-ui/src/pages/Experiment/Aim/index.tsx
  18. +6
    -2
      react-ui/src/pages/Experiment/Comparison/index.tsx
  19. +4
    -2
      react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx
  20. +0
    -2
      react-ui/src/pages/Experiment/index.jsx
  21. +4
    -4
      react-ui/src/pages/Points/index.tsx
  22. +2
    -1
      react-ui/src/pages/System/User/components/ResetPwd.tsx
  23. +21
    -11
      react-ui/src/pages/System/User/edit.tsx
  24. +6
    -9
      react-ui/src/pages/System/User/index.tsx
  25. +10
    -1
      react-ui/src/services/dataset/index.js
  26. +12
    -12
      react-ui/src/services/system/user.ts
  27. +39
    -0
      react-ui/src/stories/ParameterInput.stories.tsx
  28. +14
    -0
      react-ui/src/types.ts
  29. +1
    -0
      react-ui/src/types/system/user.d.ts
  30. +12
    -2
      react-ui/src/utils/constant.ts
  31. +1
    -1
      react-ui/src/utils/date.ts
  32. +30
    -17
      react-ui/src/utils/downloadfile.ts
  33. +69
    -16
      react-ui/src/utils/format.ts
  34. +15
    -0
      react-ui/src/utils/localStorage.ts
  35. +3
    -2
      react-ui/src/utils/promise.ts
  36. +21
    -4
      react-ui/src/utils/sessionStorage.ts
  37. +4
    -0
      react-ui/src/utils/statusTableCell.tsx
  38. +33
    -9
      react-ui/src/utils/table.tsx
  39. +51
    -12
      react-ui/src/utils/ui.tsx
  40. +10
    -1
      react-ui/typedoc.json

+ 5
- 0
react-ui/config/routes.ts View File

@@ -143,6 +143,11 @@ export default [
path: 'compare', path: 'compare',
component: './Experiment/Comparison/index', component: './Experiment/Comparison/index',
}, },
{
name: '实验可视化对比',
path: 'compare-visual',
component: './Experiment/Aim/index',
},
], ],
}, },
{ {


+ 1
- 1
react-ui/package.json View File

@@ -16,7 +16,7 @@
"docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up", "docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up",
"docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro", "docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro",
"docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro", "docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro",
"docs": "typedoc --entryPointStrategy expand --entryPoints 'src/utils' --skipErrorChecking --out docs",
"docs": "typedoc",
"gh-pages": "gh-pages -d dist", "gh-pages": "gh-pages -d dist",
"i18n-remove": "pro i18n-remove --locale=zh-CN --write", "i18n-remove": "pro i18n-remove --locale=zh-CN --write",
"postinstall": "max setup", "postinstall": "max setup",


BIN
react-ui/src/assets/img/confirm-icon.png View File

Before After
Width: 462  |  Height: 276  |  Size: 45 kB

react-ui/src/assets/img/comfirm-icon.png → react-ui/src/assets/img/copy-icon.png View File


+ 12
- 1
react-ui/src/components/IFramePage/index.tsx View File

@@ -3,6 +3,7 @@ import KFSpin from '@/components/KFSpin';
import { getLabelStudioUrl } from '@/services/developmentEnvironment'; import { getLabelStudioUrl } from '@/services/developmentEnvironment';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage'; import SessionStorage from '@/utils/sessionStorage';
import { FloatButton } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
@@ -13,6 +14,7 @@ export enum IframePageType {
AppDevelopment = 'AppDevelopment', // 应用开发 AppDevelopment = 'AppDevelopment', // 应用开发
DevEnv = 'DevEnv', // 开发环境 DevEnv = 'DevEnv', // 开发环境
GitLink = 'GitLink', // git link GitLink = 'GitLink', // git link
Aim = 'Aim', // 实验对比
} }


const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
@@ -29,12 +31,20 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
}); });
case IframePageType.GitLink: // git link case IframePageType.GitLink: // git link
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' }); return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' });
case IframePageType.Aim: // Aim
return () =>
Promise.resolve({
code: 200,
data: SessionStorage.getItem(SessionStorage.aimUrlKey) || '',
});
} }
}; };


type IframePageProps = { type IframePageProps = {
/** 子系统 */ /** 子系统 */
type: IframePageType; type: IframePageType;
/** 是否可以在页签上打开 */
openInTab: boolean;
/** 自定义样式类名 */ /** 自定义样式类名 */
className?: string; className?: string;
/** 自定义样式 */ /** 自定义样式 */
@@ -42,7 +52,7 @@ type IframePageProps = {
}; };


/** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ /** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */
function IframePage({ type, className, style }: IframePageProps) {
function IframePage({ type, openInTab = false, className, style }: IframePageProps) {
const [iframeUrl, setIframeUrl] = useState(''); const [iframeUrl, setIframeUrl] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);


@@ -65,6 +75,7 @@ function IframePage({ type, className, style }: IframePageProps) {
<div className={classNames('kf-iframe-page', className)} style={style}> <div className={classNames('kf-iframe-page', className)} style={style}>
{loading && createPortal(<KFSpin size="large" />, document.body)} {loading && createPortal(<KFSpin size="large" />, document.body)}
<FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} /> <FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} />
{openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />}
</div> </div>
); );
} }


+ 1
- 1
react-ui/src/components/ParameterInput/index.less View File

@@ -22,7 +22,7 @@
border-radius: 4px; border-radius: 4px;


&__value { &__value {
.singleLine();
//.singleLine();
margin-right: 8px; margin-right: 8px;
font-size: @font-size-input; font-size: @font-size-input;
line-height: 1.5714285714285714; line-height: 1.5714285714285714;


+ 7
- 2
react-ui/src/components/ParameterInput/index.tsx View File

@@ -6,7 +6,7 @@


import { CommonTabKeys } from '@/enums'; import { CommonTabKeys } from '@/enums';
import { CloseOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons';
import { ConfigProvider, Form, Input } from 'antd';
import { ConfigProvider, Form, Input, Typography } from 'antd';
import { RuleObject } from 'antd/es/form'; import { RuleObject } from 'antd/es/form';
import classNames from 'classnames'; import classNames from 'classnames';
import './index.less'; import './index.less';
@@ -120,7 +120,12 @@ function ParameterInput({
> >
{valueObj?.showValue ? ( {valueObj?.showValue ? (
<div className="parameter-input__content"> <div className="parameter-input__content">
<span className="parameter-input__content__value">{valueObj?.showValue}</span>
<Typography.Text
className="parameter-input__content__value"
ellipsis={{ tooltip: valueObj.showValue }}
>
{valueObj.showValue}
</Typography.Text>
<CloseOutlined <CloseOutlined
className="parameter-input__content__close-icon" className="parameter-input__content__close-icon"
onClick={handleRemove} onClick={handleRemove}


+ 2
- 0
react-ui/src/components/ResourceSelectorModal/config.tsx View File

@@ -224,6 +224,8 @@ export class MirrorSelector implements SelectorTypeInfo {
image_id: parentKey, image_id: parentKey,
page: 0, page: 0,
size: 2000, size: 2000,
status: 'available',
state: 1,
}); });
if (res && res.data) { if (res && res.data) {
const list = res.data.content || []; const list = res.data.content || [];


+ 6
- 1
react-ui/src/hooks/useCacheState.ts View File

@@ -29,13 +29,18 @@ const removeCacheState = (key: string) => {
} }
}; };


// 移除所有页面 state 缓存
/**
* 移除所有页面 state 缓存
*/
export const removeAllPageCacheState = () => { export const removeAllPageCacheState = () => {
pageKeys.forEach((key) => { pageKeys.forEach((key) => {
sessionStorage.removeItem(key); sessionStorage.removeItem(key);
}); });
}; };


/**
* 缓存页面数据
*/
export const useCacheState = () => { export const useCacheState = () => {
const { pathname } = window.location; const { pathname } = window.location;
const key = 'pagecache:' + pathname; const key = 'pagecache:' + pathname;


+ 3
- 9
react-ui/src/hooks/useCheck.ts View File

@@ -1,15 +1,9 @@
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';


/** /**
* @description 选择、全选操作
* @param list 需要进行选择的列表
* @returns selected 选中的项
* setSelected 设置 selected 的方法
* checked 是否全选
* indeterminate 是否部分选中
* checkAll 全选
* isSingleChecked 是否单个选中
* checkSingle 单个选中
* 选择、全选操作
* @param list - 需要进行选择的列表
* @return [选中的项, 设置选中的方法, 是否全选, 是否部分选中, 全选方法,是否单个选中,选中单个方法]
*/ */
export const useCheck = <T>(list: T[]) => { export const useCheck = <T>(list: T[]) => {
const [selected, setSelected] = useState<T[]>([]); const [selected, setSelected] = useState<T[]>([]);


+ 3
- 3
react-ui/src/hooks/useComputingResource.ts View File

@@ -12,7 +12,7 @@ import { useCallback, useEffect, useState } from 'react';


const computingResource: ComputingResource[] = []; const computingResource: ComputingResource[] = [];


// 过滤资源规格
/** 过滤资源规格 */
export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = (
input: string, input: string,
option?: ComputingResource, option?: ComputingResource,
@@ -22,13 +22,13 @@ export const filterResourceStandard: SelectProps<string, ComputingResource>['fil
); );
}; };


// 资源规格字段
/** 资源规格字段 */
export const resourceFieldNames = { export const resourceFieldNames = {
label: 'description', label: 'description',
value: 'id', value: 'id',
}; };


// 获取资源规格
/** 获取资源规格 */
export function useComputingResource() { export function useComputingResource() {
const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]);




+ 3
- 1
react-ui/src/hooks/useDraggable.ts View File

@@ -1,6 +1,8 @@
// 处理 react-draggable 组件拖动结束时,响应了点击事件的
import { useState } from 'react'; import { useState } from 'react';


/**
* 处理 react-draggable 组件拖动结束时,响应了点击事件的
*/
export const useDraggable = (onClick: () => void) => { export const useDraggable = (onClick: () => void) => {
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);




+ 15
- 2
react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx View File

@@ -58,7 +58,8 @@ function ExperimentInstanceComponent({
// 删除实验实例确认 // 删除实验实例确认
const handleRemove = (instance: ExperimentInstance) => { const handleRemove = (instance: ExperimentInstance) => {
modalConfirm({ modalConfirm({
title: '确定删除该条实例吗?',
title: '删除后,该实验实例将不可恢复',
content: '是否确认删除?',
onOk: () => { onOk: () => {
deleteExperimentInstance(instance.id); deleteExperimentInstance(instance.id);
}, },
@@ -96,6 +97,18 @@ function ExperimentInstanceComponent({
} }
}; };


// 终止实验实例
const handleTerminate = (instance: ExperimentInstance) => {
modalConfirm({
title: '终止后,该次实验运行将不可恢复',
content: '是否确认终止?',
isDelete: false,
onOk: () => {
terminateExperimentInstance(instance);
},
});
};

// 终止实验实例 // 终止实验实例
const terminateExperimentInstance = async (instance: ExperimentInstance) => { const terminateExperimentInstance = async (instance: ExperimentInstance) => {
const request = config.stopInsReq; const request = config.stopInsReq;
@@ -188,7 +201,7 @@ function ExperimentInstanceComponent({
item.status === ExperimentStatus.Terminated item.status === ExperimentStatus.Terminated
} }
icon={<KFIcon type="icon-zhongzhi" />} icon={<KFIcon type="icon-zhongzhi" />}
onClick={() => terminateExperimentInstance(item)}
onClick={() => handleTerminate(item)}
> >
终止 终止
</Button> </Button>


+ 8
- 15
react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx View File

@@ -4,7 +4,12 @@ import KFModal from '@/components/KFModal';
import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { addDataset } from '@/services/dataset/index.js'; import { addDataset } from '@/services/dataset/index.js';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { getFileListFromEvent, limitUploadFileType, validateUploadFiles } from '@/utils/ui';
import {
getFileListFromEvent,
limitUploadFileType,
removeUploadedFile,
validateUploadFiles,
} from '@/utils/ui';
import { import {
Button, Button,
Form, Form,
@@ -29,11 +34,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {


function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) {
const [uuid] = useState(Date.now()); const [uuid] = useState(Date.now());
// const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]);

// useEffect(() => {
// getClusterOptions();
// }, []);


// 上传组件参数 // 上传组件参数
const uploadProps: UploadProps = { const uploadProps: UploadProps = {
@@ -44,16 +44,9 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
defaultFileList: [], defaultFileList: [],
accept: '.zip,.tgz', accept: '.zip,.tgz',
beforeUpload: limitUploadFileType('zip,tgz'), beforeUpload: limitUploadFileType('zip,tgz'),
onRemove: removeUploadedFile,
}; };


// 获取集群版本数据
// const getClusterOptions = async () => {
// const [res] = await to(getDictSelectOption('available_cluster'));
// if (res) {
// setClusterOptions(res);
// }
// };

// 上传请求 // 上传请求
const createDataset = async (params: any) => { const createDataset = async (params: any) => {
const [res] = await to(addDataset(params)); const [res] = await to(addDataset(params));
@@ -113,7 +106,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
}, },
]} ]}
> >
<Input placeholder="请输入数据名称" showCount allowClear maxLength={50} />
<Input placeholder="请输入数据名称" showCount allowClear maxLength={40} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="数据集版本" label="数据集版本"


+ 3
- 2
react-ui/src/pages/Dataset/components/AddModelModal/index.tsx View File

@@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal';
import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { addModel } from '@/services/dataset/index.js'; import { addModel } from '@/services/dataset/index.js';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui';
import { import {
Button, Button,
Form, Form,
@@ -37,6 +37,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
Authorization: getAccessToken() || '', Authorization: getAccessToken() || '',
}, },
defaultFileList: [], defaultFileList: [],
onRemove: removeUploadedFile,
}; };


// 上传请求 // 上传请求
@@ -96,7 +97,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
}, },
]} ]}
> >
<Input placeholder="请输入模型名称" showCount allowClear maxLength={50} />
<Input placeholder="请输入模型名称" showCount allowClear maxLength={40} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="模型版本" label="模型版本"


+ 2
- 1
react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx View File

@@ -3,7 +3,7 @@ import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal'; import KFModal from '@/components/KFModal';
import { DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; import { DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui';
import { import {
Button, Button,
Form, Form,
@@ -50,6 +50,7 @@ function AddVersionModal({
defaultFileList: [], defaultFileList: [],
beforeUpload: config.beforeUpload, beforeUpload: config.beforeUpload,
accept: config.uploadAccept, accept: config.uploadAccept,
onRemove: removeUploadedFile,
}; };


// 上传请求 // 上传请求


+ 12
- 0
react-ui/src/pages/Experiment/Aim/index.tsx View File

@@ -0,0 +1,12 @@
/*
* @Author: 赵伟
* @Date: 2025-03-31 16:38:59
* @Description: 实验对比 Aim
*/

import IframePage, { IframePageType } from '@/components/IFramePage';

function AimPage() {
return <IframePage type={IframePageType.Aim}></IframePage>;
}
export default AimPage;

+ 6
- 2
react-ui/src/pages/Experiment/Comparison/index.tsx View File

@@ -12,8 +12,9 @@ import {
} from '@/services/experiment'; } from '@/services/experiment';
import { tableSorter } from '@/utils'; import { tableSorter } from '@/utils';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table'; import tableCellRender, { TableCellValueType } from '@/utils/table';
import { useSearchParams } from '@umijs/max';
import { useNavigate, useSearchParams } from '@umijs/max';
import { App, Button, Table, TablePaginationConfig, TableProps } from 'antd'; import { App, Button, Table, TablePaginationConfig, TableProps } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
@@ -46,6 +47,7 @@ function ExperimentComparison() {
}); });


const { message } = App.useApp(); const { message } = App.useApp();
const navigate = useNavigate();
const config = comparisonConfig[comparisonType]; const config = comparisonConfig[comparisonType];


useEffect(() => { useEffect(() => {
@@ -73,7 +75,9 @@ function ExperimentComparison() {
const [res] = await to(getExpMetricsReq(selectedRowKeys)); const [res] = await to(getExpMetricsReq(selectedRowKeys));
if (res && res.data) { if (res && res.data) {
const url = res.data; const url = res.data;
window.open(url, '_blank');
// window.open(url, '_blank');
SessionStorage.setItem(SessionStorage.aimUrlKey, url);
navigate('../compare-visual');
} }
}; };




+ 4
- 2
react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx View File

@@ -62,7 +62,8 @@ function ExperimentInstanceComponent({
// 删除实验实例确认 // 删除实验实例确认
const handleRemove = (instance: ExperimentInstance) => { const handleRemove = (instance: ExperimentInstance) => {
modalConfirm({ modalConfirm({
title: '确定删除该条实例吗?',
title: '删除后,该实验实例将不可恢复',
content: '是否确认删除?',
onOk: () => { onOk: () => {
deleteExperimentInstance(instance.id); deleteExperimentInstance(instance.id);
}, },
@@ -101,7 +102,8 @@ function ExperimentInstanceComponent({
// 终止实验实例 // 终止实验实例
const handleTerminate = (instance: ExperimentInstance) => { const handleTerminate = (instance: ExperimentInstance) => {
modalConfirm({ modalConfirm({
title: '确定要终止此次实验运行吗?',
title: '终止后,该次实验运行将不可恢复',
content: '是否确认终止?',
isDelete: false, isDelete: false,
onOk: () => { onOk: () => {
terminateExperimentInstance(instance); terminateExperimentInstance(instance);


+ 0
- 2
react-ui/src/pages/Experiment/index.jsx View File

@@ -286,8 +286,6 @@ function Experiment() {
message.success('运行成功'); message.success('运行成功');
refreshExperimentList(); refreshExperimentList();
refreshExperimentIns(id); refreshExperimentIns(id);
} else {
message.error('运行失败');
} }
}; };




+ 4
- 4
react-ui/src/pages/Points/index.tsx View File

@@ -25,19 +25,19 @@ enum TaskType {


const taskTypeOptions = [ const taskTypeOptions = [
{ {
value: 'dev_environment',
value: TaskType.DevEnvironment,
label: '开发环境', label: '开发环境',
}, },
{ {
value: 'workflow',
value: TaskType.Workflow,
label: '实验', label: '实验',
}, },
{ {
value: 'ray',
value: TaskType.Ray,
label: '超参数自动寻优', label: '超参数自动寻优',
}, },
{ {
value: 'service',
value: TaskType.Service,
label: '服务', label: '服务',
}, },
]; ];


+ 2
- 1
react-ui/src/pages/System/User/components/ResetPwd.tsx View File

@@ -17,6 +17,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const loginPassword = Form.useWatch('password', form); const loginPassword = Form.useWatch('password', form);
const userId = props.values.userId; const userId = props.values.userId;
const originPassword = props.values.originPassword;


const intl = useIntl(); const intl = useIntl();
const handleOk = () => { const handleOk = () => {
@@ -26,7 +27,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
props.onCancel(); props.onCancel();
}; };
const handleFinish = async (values: Record<string, any>) => { const handleFinish = async (values: Record<string, any>) => {
props.onSubmit({ ...values, userId } as FormValueType);
props.onSubmit({ password: values.password, userId, originPassword } as FormValueType);
}; };


const checkPassword = (rule: any, value: string) => { const checkPassword = (rule: any, value: string) => {


+ 21
- 11
react-ui/src/pages/System/User/edit.tsx View File

@@ -63,8 +63,8 @@ const UserForm: React.FC<UserFormProps> = (props) => {
loginIp: props.values.loginIp, loginIp: props.values.loginIp,
loginDate: props.values.loginDate, loginDate: props.values.loginDate,
remark: props.values.remark, remark: props.values.remark,
gitLinkUsername: props.values.gitLinkUsername,
gitLinkPassword: props.values.gitLinkPassword,
// gitLinkUsername: props.values.gitLinkUsername,
// gitLinkPassword: props.values.gitLinkPassword,
credit: props.values.credit, credit: props.values.credit,
}); });
}, [form, props, statusOptions]); }, [form, props, statusOptions]);
@@ -80,6 +80,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
const params = { const params = {
...values, ...values,
userId: props.values.userId, userId: props.values.userId,
originPassword: props.values.originPassword,
}; };
props.onSubmit(params as UserFormData); props.onSubmit(params as UserFormData);
}; };
@@ -150,7 +151,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
colProps={{ md: 12, xl: 12 }} colProps={{ md: 12, xl: 12 }}
rules={[ rules={[
{ {
required: false,
required: true,
message: <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />, message: <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />,
}, },
{ {
@@ -174,7 +175,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
colProps={{ md: 12, xl: 12 }} colProps={{ md: 12, xl: 12 }}
rules={[ rules={[
{ {
required: false,
required: true,
message: <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />, message: <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />,
}, },
{ {
@@ -194,7 +195,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
id: 'system.user.user_name', id: 'system.user.user_name',
defaultMessage: '用户账号', defaultMessage: '用户账号',
})} })}
hidden={userId}
disabled={!!props.values.userId}
placeholder="请输入用户账号" placeholder="请输入用户账号"
colProps={{ md: 12, xl: 12 }} colProps={{ md: 12, xl: 12 }}
rules={[ rules={[
@@ -202,9 +203,9 @@ const UserForm: React.FC<UserFormProps> = (props) => {
required: true, required: true,
}, },
{ {
pattern: /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/,
pattern: /^[a-zA-Z](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/,
message: message:
'只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以数字或字母开头与结尾',
'只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以字母开头,数字或字母结尾',
}, },
]} ]}
/> />
@@ -214,14 +215,23 @@ const UserForm: React.FC<UserFormProps> = (props) => {
id: 'system.user.password', id: 'system.user.password',
defaultMessage: '密码', defaultMessage: '密码',
})} })}
hidden={userId}
placeholder="请输入密码" placeholder="请输入密码"
colProps={{ md: 12, xl: 12 }} colProps={{ md: 12, xl: 12 }}
fieldProps={{ fieldProps={{
autoComplete: 'new-password', autoComplete: 'new-password',
}} }}
allowClear allowClear
rules={props.values.userId ? [] : [{ required: true, message: '请输入密码!' }]}
rules={
props.values.userId
? []
: [
{ required: true, message: '请输入密码!' },
{
pattern: /^[A-Za-z0-9!"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]{8,16}$/,
message: '密码长度为8 ~ 16位,只支持字母数字和符号',
},
]
}
/> />
<ProFormSelect <ProFormSelect
valueEnum={sexOptions} valueEnum={sexOptions}
@@ -279,7 +289,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
colProps={{ md: 12, xl: 12 }} colProps={{ md: 12, xl: 12 }}
rules={[{ required: true, message: '请选择角色!' }]} rules={[{ required: true, message: '请选择角色!' }]}
/> />
<ProFormText
{/* <ProFormText
name="gitLinkUsername" name="gitLinkUsername"
label="Git 用户名" label="Git 用户名"
placeholder="请输入 Git 用户名" placeholder="请输入 Git 用户名"
@@ -300,7 +310,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
autoComplete: 'new-password', autoComplete: 'new-password',
}} }}
rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]} rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]}
/>
/> */}
<ProFormDigit <ProFormDigit
name="credit" name="credit"
label="算力积分" label="算力积分"


+ 6
- 9
react-ui/src/pages/System/User/index.tsx View File

@@ -4,7 +4,6 @@ import { getRoleList } from '@/services/system/role';
import { import {
addUser, addUser,
changeUserStatus, changeUserStatus,
exportUser,
getDeptTree, getDeptTree,
getUser, getUser,
getUserList, getUserList,
@@ -13,6 +12,7 @@ import {
updateAuthRole, updateAuthRole,
updateUser, updateUser,
} from '@/services/system/user'; } from '@/services/system/user';
import { downloadXlsx } from '@/utils/downloadfile';
import { import {
DeleteOutlined, DeleteOutlined,
DownOutlined, DownOutlined,
@@ -132,15 +132,12 @@ const handleRemoveOne = async (selectedRow: API.System.User) => {


/** /**
* 导出数据 * 导出数据
*
*
*/ */
const handleExport = async () => {
const handleExport = async (deptId: string) => {
const hide = message.loading('正在导出'); const hide = message.loading('正在导出');
try { try {
await exportUser();
await downloadXlsx('/api/system/user/export', 'POST', { data: { deptId: deptId } });
hide(); hide();
message.success('导出成功');
return true; return true;
} catch (error) { } catch (error) {
hide(); hide();
@@ -470,7 +467,7 @@ const UserTableList: React.FC = () => {
key="export" key="export"
hidden={!access.hasPerms('system:user:export')} hidden={!access.hasPerms('system:user:export')}
onClick={async () => { onClick={async () => {
handleExport();
handleExport(selectDept.id);
}} }}
> >
<PlusOutlined />{' '} <PlusOutlined />{' '}
@@ -563,7 +560,7 @@ const UserTableList: React.FC = () => {
/> />
<ResetPwd <ResetPwd
onSubmit={async (values: any) => { onSubmit={async (values: any) => {
const success = await resetUserPwd(values.userId, values.password);
const success = await resetUserPwd(values);
if (success) { if (success) {
setResetPwdModalVisible(false); setResetPwdModalVisible(false);
setSelectedRows([]); setSelectedRows([]);
@@ -581,7 +578,7 @@ const UserTableList: React.FC = () => {
/> />
<AuthRoleForm <AuthRoleForm
onSubmit={async (values: any) => { onSubmit={async (values: any) => {
const success = await updateAuthRole(values);
const success = await updateAuthRole(currentRow!.userId, values.roleIds);
if (success) { if (success) {
setAuthRoleModalVisible(false); setAuthRoleModalVisible(false);
setSelectedRows([]); setSelectedRows([]);


+ 10
- 1
react-ui/src/services/dataset/index.js View File

@@ -181,4 +181,13 @@ export function compareModelVersion(data) {
method: 'POST', method: 'POST',
data, data,
}); });
}
}


// 删除上传的文件
export function deleteUploadFileReq(params) {
return request(`/api/mmp/newdataset/deleteFile`, {
method: 'DELETE',
params,
});
}

+ 12
- 12
react-ui/src/services/system/user.ts View File

@@ -60,8 +60,9 @@ export async function removeUser(ids: string, options?: { [key: string]: any })
// 导出用户信息 // 导出用户信息
export function exportUser(params?: API.System.UserListParams, options?: { [key: string]: any }) { export function exportUser(params?: API.System.UserListParams, options?: { [key: string]: any }) {
return request<API.Result>(`/api/system/user/export`, { return request<API.Result>(`/api/system/user/export`, {
method: 'GET',
params,
method: 'POST',
data: params,
skipValidating: true,
...(options || {}), ...(options || {}),
}); });
} }
@@ -93,11 +94,7 @@ export function updateUserProfile(data: API.CurrentUser) {
} }


// 用户密码重置 // 用户密码重置
export function resetUserPwd(userId: number, password: string) {
const data = {
userId,
password,
};
export function resetUserPwd(data: any) {
return request<API.Result>('/api/system/user/resetPwd', { return request<API.Result>('/api/system/user/resetPwd', {
method: 'put', method: 'put',
data: data, data: data,
@@ -126,16 +123,19 @@ export function uploadAvatar(data: any) {


// 查询授权角色 // 查询授权角色
export function getAuthRole(userId: number) { export function getAuthRole(userId: number) {
return request('/system/user/authRole/' + userId, {
return request('/api/system/user/authRole/' + userId, {
method: 'get', method: 'get',
}); });
} }


// 保存授权角色 // 保存授权角色
export function updateAuthRole(data: Record<string, any>) {
return request('/system/user/authRole', {
method: 'put',
params: data,
export function updateAuthRole(userId: number, data: Record<string, any>) {
return request(`/api/system/user/authRole/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
data: data,
}); });
} }




+ 39
- 0
react-ui/src/stories/ParameterInput.stories.tsx View File

@@ -76,6 +76,45 @@ export const Select: Story = {
}, },
}; };


export const Ellipse: Story = {
args: {
placeholder: '请输入工作目录',
style: { width: 300 },
canInput: true,
size: 'large',
},
render: function Render(args) {
const [value, setValue] = useState<ParameterInputValue | undefined>('');

const onClick = () => {
const value = {
value: 'storybook',
showValue:
'storybookstorybookstorybookstorybookstorybookstorybookstorybookstorybookstorybookstorybook',
fromSelect: true,
otherValue: 'others',
};
setValue(value);
action('onChange')(value);
};
return (
<>
<ParameterInput
{...args}
value={value}
onChange={(value) => {
setValue(value);
action('onChange')(value);
}}
></ParameterInput>
<Button type="primary" style={{ display: 'block', marginTop: 10 }} onClick={onClick}>
模拟从全局参数选择
</Button>
</>
);
},
};

export const Disabled: Story = { export const Disabled: Story = {
args: { args: {
placeholder: '请输入工作目录', placeholder: '请输入工作目录',


+ 14
- 0
react-ui/src/types.ts View File

@@ -136,3 +136,17 @@ export type NodeStatus = {
startedAt: string; startedAt: string;
finishedAt: string; finishedAt: string;
}; };

// 应用响应
export type AppResponse<T> = {
code: number;
msg: string;
data: T;
};

// 上传文件的响应
export type UploadFileRes = {
fileName: string;
fileSize: number;
url: string;
};

+ 1
- 0
react-ui/src/types/system/user.d.ts View File

@@ -22,6 +22,7 @@ declare namespace API.System {
gitLinkUsername?: string; gitLinkUsername?: string;
gitLinkPassword?: string; gitLinkPassword?: string;
credit?: number; credit?: number;
originPassword?: string;
} }


export interface UserListParams { export interface UserListParams {


+ 12
- 2
react-ui/src/utils/constant.ts View File

@@ -1,3 +1,13 @@
export const xlCols = { span: 12 };
export const xllCols = { span: 10 };
/*
* @Author: 赵伟
* @Date: 2025-02-21 09:52:50
* @Description: 通用表单项输入控件宽度
*/

const xlCols = { span: 12 };
const xllCols = { span: 10 };

/**
* 输入控件宽度,xl: 12, xll: 10
*/
export const formCols = { xl: xlCols, xxl: xllCols }; export const formCols = { xl: xlCols, xxl: xllCols };

+ 1
- 1
react-ui/src/utils/date.ts View File

@@ -1,7 +1,7 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';


/** /**
* Calculates the elapsed time between two dates and returns a formatted string representing the duration.
* 计算两个日期之间经过的时间,如 "3分12秒"
* *
* @param {string | null | undefined} begin - The starting date. * @param {string | null | undefined} begin - The starting date.
* @param {string | null | undefined} end - The ending date. * @param {string | null | undefined} end - The ending date.


+ 30
- 17
react-ui/src/utils/downloadfile.ts View File

@@ -1,19 +1,21 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';


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


/** /**
* 解析blob响应内容并下载 * 解析blob响应内容并下载
* @param {*} res blob响应内容
* @param {String} mimeType MIME类型
* @param res - blob响应内容
* @param mimeType - MIME类型
*/ */
export function resolveBlob(res: any, mimeType: string) { export function resolveBlob(res: any, mimeType: string) {
const aLink = document.createElement('a'); const aLink = document.createElement('a');
const blob = new Blob([res.data], { type: mimeType }); const blob = new Blob([res.data], { type: mimeType });
// //从response的headers中获取filename, 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名;
// 从response的headers中获取filename,
// 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名;
const patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*'); const patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*');
// console.log(res); // console.log(res);
const contentDisposition = decodeURI(res.headers['content-disposition']); const contentDisposition = decodeURI(res.headers['content-disposition']);
@@ -29,6 +31,11 @@ export function resolveBlob(res: any, mimeType: string) {
document.body.removeChild(aLink); document.body.removeChild(aLink);
} }


/**
* 下载 Zip 文件
* @param url - url 地址
* @param options - 请求参数
*/
export function downLoadZip(url: string, params?: any) { export function downLoadZip(url: string, params?: any) {
request(url, { request(url, {
method: 'GET', method: 'GET',
@@ -40,24 +47,30 @@ export function downLoadZip(url: string, params?: any) {
}); });
} }


export async function downLoadXlsx(url: string, params: any, fileName: string) {
/**
* 下载 XLSX 文件
* @param url - url 地址
* @param method - 请求方式
* @param options - 请求选项
*/
export async function downloadXlsx(
url: string,
method: string = 'GET',
options?: Record<string, any>,
) {
return request(url, { return request(url, {
...params,
method: 'POST',
method: method,
...options,
responseType: 'blob', responseType: 'blob',
}).then((data) => {
const aLink = document.createElement('a');
const blob = data as any; // new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
aLink.style.display = 'none';
aLink.href = URL.createObjectURL(blob);
aLink.setAttribute('download', fileName); // 设置下载文件名称
document.body.appendChild(aLink);
aLink.click();
URL.revokeObjectURL(aLink.href); // 清除引用
document.body.removeChild(aLink);
getResponse: true,
}).then((res) => {
resolveBlob(res, mimeMap.xlsx);
}); });
} }


/**
* @deprecated 无效
*/
export function download(fileName: string) { export function download(fileName: string) {
window.location.href = `/api/common/download?fileName=${encodeURI(fileName)}&delete=${true}`; window.location.href = `/api/common/download?fileName=${encodeURI(fileName)}&delete=${true}`;
} }

+ 69
- 16
react-ui/src/utils/format.ts View File

@@ -1,3 +1,4 @@
import { BasicInfoLink } from '@/components/BasicInfo/types';
import { ResourceSelectorResponse } from '@/components/ResourceSelectorModal'; import { ResourceSelectorResponse } from '@/components/ResourceSelectorModal';
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo';
import { import {
@@ -18,8 +19,13 @@ type SelectedCodeConfig = {
show_value?: string; // 后端使用的 show_value?: string; // 后端使用的
}; };


// 格式化数据集数组
export const formatDatasets = (datasets?: DatasetData[]) => {
/**
* 格式化数据集数组
*
* @param datasets - 数据集数组
* @return 基本信息链接对象数组
*/
export const formatDatasets = (datasets?: DatasetData[]): BasicInfoLink[] | undefined => {
if (!datasets || datasets.length === 0) { if (!datasets || datasets.length === 0) {
return undefined; return undefined;
} }
@@ -29,8 +35,13 @@ export const formatDatasets = (datasets?: DatasetData[]) => {
})); }));
}; };


// 格式化数据集
export const formatDataset = (dataset?: DatasetData) => {
/**
* 格式化数据集
*
* @param dataset - 数据集
* @return 基本信息链接对象
*/
export const formatDataset = (dataset?: DatasetData): BasicInfoLink | undefined => {
if (!dataset) { if (!dataset) {
return undefined; return undefined;
} }
@@ -40,8 +51,13 @@ export const formatDataset = (dataset?: DatasetData) => {
}; };
}; };


// 格式化模型
export const formatModel = (model: ModelData) => {
/**
* 格式化模型
*
* @param model - 模型
* @return 基本信息链接对象
*/
export const formatModel = (model: ModelData): BasicInfoLink | undefined => {
if (!model) { if (!model) {
return undefined; return undefined;
} }
@@ -51,16 +67,28 @@ export const formatModel = (model: ModelData) => {
}; };
}; };


// 格式化镜像
export const formatMirror = (mirror: ResourceSelectorResponse) => {
/**
* 格式化镜像
*
* @param mirror - 选择的镜像
* @return 镜像地址
*/
export const formatMirror = (mirror: ResourceSelectorResponse): string | undefined => {
if (!mirror) { if (!mirror) {
return undefined; return undefined;
} }
return mirror.path; return mirror.path;
}; };


// 格式化代码配置
export const formatCodeConfig = (project?: ProjectDependency | SelectedCodeConfig) => {
/**
* 格式化代码配置
*
* @param project - 代码配置或者选择的代码配置
* @return 基本信息链接对象
*/
export const formatCodeConfig = (
project?: ProjectDependency | SelectedCodeConfig,
): BasicInfoLink | undefined => {
if (!project) { if (!project) {
return undefined; return undefined;
} }
@@ -81,7 +109,12 @@ export const formatCodeConfig = (project?: ProjectDependency | SelectedCodeConfi
} }
}; };


// 格式化训练任务(实验实例)
/**
* 格式化训练任务(实验实例)
*
* @param task - 训练任务
* @return 基本信息链接对象
*/
export const formatTrainTask = (task?: TrainTask) => { export const formatTrainTask = (task?: TrainTask) => {
if (!task) { if (!task) {
return undefined; return undefined;
@@ -92,8 +125,13 @@ export const formatTrainTask = (task?: TrainTask) => {
}; };
}; };


// 格式化数据来源
export const formatSource = (source?: string) => {
/**
* 格式化数据来源
*
* @param source - 数据来源枚举值
* @return 数据来源中文名称
*/
export const formatSource = (source?: string): string | undefined => {
if (source === DataSource.Create) { if (source === DataSource.Create) {
return '用户上传'; return '用户上传';
} else if (source === DataSource.HandExport) { } else if (source === DataSource.HandExport) {
@@ -106,7 +144,12 @@ export const formatSource = (source?: string) => {
return source; return source;
}; };


// 格式化字符串数组,以逗号分隔
/**
* 格式化字符串数组,以逗号分隔
*
* @param value - 字符串数组
* @return 字符串,以逗号分隔
*/
export const formatList = (value: string[] | null | undefined): string => { export const formatList = (value: string[] | null | undefined): string => {
if ( if (
value === undefined || value === undefined ||
@@ -119,14 +162,24 @@ export const formatList = (value: string[] | null | undefined): string => {
return value.join(','); return value.join(',');
}; };


// 格式化布尔值
/**
* 格式化布尔值
*
* @param value - 布尔值
* @return true 为 "是",false 为 "否"
*/
export const formatBoolean = (value: boolean): string => { export const formatBoolean = (value: boolean): string => {
return value ? '是' : '否'; return value ? '是' : '否';
}; };


type FormatEnumFunc = (value: string | number) => React.ReactNode; type FormatEnumFunc = (value: string | number) => React.ReactNode;


// 格式化枚举
/**
* 格式化枚举
*
* @param options - 枚举选项数组
* @return 一个函数,参数是枚举值,从选项数组中找到对应的项,然后返回该项的 label
*/
export const formatEnum = ( export const formatEnum = (
options: { value?: string | number | null; label?: React.ReactNode }[], options: { value?: string | number | null; label?: React.ReactNode }[],
): FormatEnumFunc => { ): FormatEnumFunc => {


+ 15
- 0
react-ui/src/utils/localStorage.ts View File

@@ -6,6 +6,11 @@ export default class LocalStorage {
// 记住密码 // 记住密码
static readonly rememberPasswordKey = 'login-remember-password'; static readonly rememberPasswordKey = 'login-remember-password';


/**
* 获取 LocalStorage 值
* @param key - LocalStorage key
* @param isObject - 是不是对象
*/
static getItem(key: string, isObject: boolean = false) { static getItem(key: string, isObject: boolean = false) {
const jsonStr = localStorage.getItem(key); const jsonStr = localStorage.getItem(key);
if (!isObject) { if (!isObject) {
@@ -17,12 +22,22 @@ export default class LocalStorage {
return null; return null;
} }


/**
* 设置 LocalStorage 值
* @param key - LocalStorage key
* @param state - SessionStorage state
* @param isObject - 是不是对象
*/
static setItem(key: string, state?: any, isObject: boolean = false) { static setItem(key: string, state?: any, isObject: boolean = false) {
if (state) { if (state) {
localStorage.setItem(key, isObject ? JSON.stringify(state) : state); localStorage.setItem(key, isObject ? JSON.stringify(state) : state);
} }
} }


/**
* 移除 LocalStorage 值
* @param key - LocalStorage key
*/
static removeItem(key: string) { static removeItem(key: string) {
localStorage.removeItem(key); localStorage.removeItem(key);
} }


+ 3
- 2
react-ui/src/utils/promise.ts View File

@@ -1,6 +1,7 @@
/** /**
* @param { Promise } promise
* @return { Promise }
* 封装 Promise,不会抛异常,resolve 的时候返回 [data, null], reject 的时候返回 [null, error]
* @param promise
* @return resolve 的时候返回 [data, null], reject 的时候返回 [null, error]
*/ */
export async function to<T, U = any>(promise: Promise<T>): Promise<[T, null] | [null, U]> { export async function to<T, U = any>(promise: Promise<T>): Promise<[T, null] | [null, U]> {
try { try {


+ 21
- 4
react-ui/src/utils/sessionStorage.ts View File

@@ -1,15 +1,22 @@
import { parseJsonText } from './index'; import { parseJsonText } from './index';


export default class SessionStorage { export default class SessionStorage {
// 用于新建镜像
/** 用于新建镜像 */
static readonly mirrorNameKey = 'mirror-name'; static readonly mirrorNameKey = 'mirror-name';
// 模型部署服务版本
/** 模型部署服务版本 */
static readonly serviceVersionInfoKey = 'service-version-info'; static readonly serviceVersionInfoKey = 'service-version-info';
// 编辑器 url
/** 编辑器 url */
static readonly editorUrlKey = 'editor-url'; static readonly editorUrlKey = 'editor-url';
// 客户端信息
/** 客户端信息 */
static readonly clientInfoKey = 'client-info'; static readonly clientInfoKey = 'client-info';
/** aim url */
static readonly aimUrlKey = 'aim-url';


/**
* 获取 SessionStorage 值
* @param key - SessionStorage key
* @param isObject - 是不是对象
*/
static getItem(key: string, isObject: boolean = false) { static getItem(key: string, isObject: boolean = false) {
const jsonStr = sessionStorage.getItem(key); const jsonStr = sessionStorage.getItem(key);
if (!isObject) { if (!isObject) {
@@ -21,12 +28,22 @@ export default class SessionStorage {
return null; return null;
} }


/**
* 设置 SessionStorage 值
* @param key - SessionStorage key
* @param state - SessionStorage state
* @param isObject - 是不是对象
*/
static setItem(key: string, state?: any, isObject: boolean = false) { static setItem(key: string, state?: any, isObject: boolean = false) {
if (state) { if (state) {
sessionStorage.setItem(key, isObject ? JSON.stringify(state) : state); sessionStorage.setItem(key, isObject ? JSON.stringify(state) : state);
} }
} }


/**
* 移除 SessionStorage 值
* @param key - SessionStorage key
*/
static removeItem(key: string) { static removeItem(key: string) {
sessionStorage.removeItem(key); sessionStorage.removeItem(key);
} }


+ 4
- 0
react-ui/src/utils/statusTableCell.tsx View File

@@ -10,6 +10,10 @@ export type StatusInfo = {
color?: string; color?: string;
}; };


/**
* 通用的 Table 状态单元格
* @param infos - 选项数组
*/
function statusTableCell(infos: StatusInfo[]) { function statusTableCell(infos: StatusInfo[]) {
return function (status?: string | number | null) { return function (status?: string | number | null) {
const info = infos.find((item) => item.value === status); const info = infos.find((item) => item.value === status);


+ 33
- 9
react-ui/src/utils/table.tsx View File

@@ -10,27 +10,44 @@ import { Tooltip, TooltipProps, Typography } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';


export enum TableCellValueType { export enum TableCellValueType {
/** 序号 */
Index = 'Index', Index = 'Index',
/** 文本 */
Text = 'Text', Text = 'Text',
/** 日期 */
Date = 'Date', Date = 'Date',
/** 数组 */
Array = 'Array', Array = 'Array',
/** 链接 */
Link = 'Link', Link = 'Link',
/** 自定义 */
Custom = 'Custom', Custom = 'Custom',
} }


export type TableCellValueOptions<T> = { export type TableCellValueOptions<T> = {
page?: number; // 类型为 Index 时有效
pageSize?: number; // 类型为 Index 时有效
property?: string; // 类型为 Array 时有效
dateFormat?: string; // 类型为 Date 时有效
onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效
format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效
copyable?: boolean; // 省略时是否可以复制
/** 页数,类型为 Index 时有效 */
page?: number;
/** 分页大小,类型为 Index 时有效 */
pageSize?: number;
/** 取数组对象的哪个属性值,类型为 Array 时有效 */
property?: string;
/** 日期格式,类型为 Date 时有效*/
dateFormat?: string;
/** 链接点击回调,类型为 Link 时有效 */
onClick?: (record: T, e: React.MouseEvent) => void;
/** 自定义函数,类型为 Custom 时有效*/
format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null;
/** 省略时是否可以复制 */
copyable?: boolean;
}; };


type TableCellFormatter = (value: any | undefined | null) => string | undefined | null; type TableCellFormatter = (value: any | undefined | null) => string | undefined | null;


// 日期转换函数
/**
* 日期转换函数
* @param {string | undefined} dateFormat - 日期格式
* @returns {TableCellFormatter} Table cell 渲染函数
*/
function formatDateText(dateFormat?: string): TableCellFormatter { function formatDateText(dateFormat?: string): TableCellFormatter {
return (value: any | undefined | null): ReturnType<TableCellFormatter> => { return (value: any | undefined | null): ReturnType<TableCellFormatter> => {
if (value === undefined || value === null || value === '') { if (value === undefined || value === null || value === '') {
@@ -45,7 +62,7 @@ function formatDateText(dateFormat?: string): TableCellFormatter {


/** /**
* 数组转换函数,将数组元素转换为字符串,用逗号分隔 * 数组转换函数,将数组元素转换为字符串,用逗号分隔
* @param {string} property 如果数组元素是对象,那么取数组元素的某个属性
* @param {string} property - 如果数组元素是对象,那么取数组元素的某个属性
* @returns {TableCellFormatter} Table cell 渲染函数 * @returns {TableCellFormatter} Table cell 渲染函数
*/ */
function formatArray(property?: string): TableCellFormatter { function formatArray(property?: string): TableCellFormatter {
@@ -65,6 +82,13 @@ function formatArray(property?: string): TableCellFormatter {
}; };
} }


/**
* Table cell render 函数
* @param ellipsis - 是否省略
* @param type - 类型
* @param options - 选项
* @returns React 节点
*/
function tableCellRender<T>( function tableCellRender<T>(
ellipsis: boolean | TooltipProps | 'auto' = false, ellipsis: boolean | TooltipProps | 'auto' = false,
type: TableCellValueType = TableCellValueType.Text, type: TableCellValueType = TableCellValueType.Text,


+ 51
- 12
react-ui/src/utils/ui.tsx View File

@@ -5,8 +5,10 @@
*/ */
import { PageEnum } from '@/enums/pagesEnums'; import { PageEnum } from '@/enums/pagesEnums';
import { removeAllPageCacheState } from '@/hooks/useCacheState'; import { removeAllPageCacheState } from '@/hooks/useCacheState';
import { deleteUploadFileReq } from '@/services/dataset/index.js';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { type ClientInfo } from '@/types';
import { type AppResponse, type ClientInfo, type UploadFileRes } from '@/types';
import { to } from '@/utils/promise';
import { history } from '@umijs/max'; import { history } from '@umijs/max';
import { import {
Modal, Modal,
@@ -25,7 +27,9 @@ type ModalConfirmProps = ModalFuncProps & {
isDelete?: boolean; isDelete?: boolean;
}; };


// 自定义删除 Confirm 弹框
/**
* 自定义 Confirm 弹框
*/
export function modalConfirm({ export function modalConfirm({
title, title,
content, content,
@@ -45,7 +49,7 @@ export function modalConfirm({
src={ src={
isDelete isDelete
? require('@/assets/img/delete-icon.png') ? require('@/assets/img/delete-icon.png')
: require('@/assets/img/comfirm-icon.png')
: require('@/assets/img/confirm-icon.png')
} }
style={{ width: '120px', marginBottom: '24px' }} style={{ width: '120px', marginBottom: '24px' }}
draggable={false} draggable={false}
@@ -63,7 +67,7 @@ export function modalConfirm({


/** /**
* 跳转到登录页 * 跳转到登录页
* @param toHome 是否跳转到首页
* @param toHome - 是否跳转到首页
*/ */
export const gotoLoginPage = (toHome: boolean = true) => { export const gotoLoginPage = (toHome: boolean = true) => {
const { pathname, search } = location; const { pathname, search } = location;
@@ -80,6 +84,9 @@ export const gotoLoginPage = (toHome: boolean = true) => {
} }
}; };


/**
* 跳转到 OAuth2 登录页
*/
export const gotoOAuth2 = () => { export const gotoOAuth2 = () => {
const clientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true) as ClientInfo; const clientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true) as ClientInfo;
if (clientInfo) { if (clientInfo) {
@@ -89,7 +96,10 @@ export const gotoOAuth2 = () => {
} }
}; };


// 从事件中获取上传文件列表,用于 Upload + Form 中
/**
* 从事件中获取上传文件列表,用于 Upload + Form 中
* @param e - 事件,包含文件列表 fileList
*/
export const getFileListFromEvent = (e: any) => { export const getFileListFromEvent = (e: any) => {
const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || []; const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || [];
return fileList.map((item) => { return fileList.map((item) => {
@@ -137,7 +147,10 @@ export const validateUploadFiles = (files: UploadFile[], required: boolean = tru
return !hasError; return !hasError;
}; };


// 限制上传文件类型
/**
* 限制上传文件类型
* @param type - 只允许上次的的文件类型
*/
export const limitUploadFileType = (type: string) => { export const limitUploadFileType = (type: string) => {
return (file: UploadFile): boolean | string => { return (file: UploadFile): boolean | string => {
const acceptTypes = type.split(',').map((item) => item.trim()); const acceptTypes = type.split(',').map((item) => item.trim());
@@ -151,14 +164,40 @@ export const limitUploadFileType = (type: string) => {
}; };
}; };


/**
* 删除已上传的文件
* @param file - 已上传的文件
*/
export const removeUploadedFile = async (file: UploadFile<AppResponse<UploadFileRes[]>>) => {
const { status, response } = file;
const { code, data } = response ?? {};
if (status === 'done' && code === 200 && Array.isArray(data) && data.length > 0) {
const uploadRes = data[0];
const { fileName, url } = uploadRes;
const [res] = await to(
deleteUploadFileReq({
fileName,
url,
}),
);
if (res) {
return true;
} else {
return false;
}
}

return true;
};

/** /**
* 删除 FormList 表单项,如果表单项没有值,则直接删除,否则弹出确认框 * 删除 FormList 表单项,如果表单项没有值,则直接删除,否则弹出确认框
* @param form From实例
* @param listName FormList 的 name
* @param name FormList 的其中一项
* @param remove FormList 的删除方法
* @param fieldNames FormList 的子项名称数组
* @param confirmTitle 弹出确认框的标题
* @param form - From实例
* @param listName - FormList 的 name
* @param name - FormList 的其中一项
* @param remove - FormList 的删除方法
* @param fieldNames - FormList 的子项名称数组
* @param confirmTitle - 弹出确认框的标题
*/ */
export const removeFormListItem = ( export const removeFormListItem = (
form: FormInstance, form: FormInstance,


+ 10
- 1
react-ui/typedoc.json View File

@@ -1,5 +1,5 @@
{ {
"entryPoints": ["./src/utils"],
"entryPoints": ["./src/utils", "./src/hooks"],
"entryPointStrategy": "expand", "entryPointStrategy": "expand",
"out": "docs", "out": "docs",
"excludePrivate": true, "excludePrivate": true,
@@ -7,5 +7,14 @@
"excludeExternals": true, "excludeExternals": true,
"includeVersion": true, "includeVersion": true,
"categorizeByGroup": true, "categorizeByGroup": true,
"skipErrorChecking": true,
"exclude": [
"src/utils/formRules.ts",
"src/utils/loading.tsx",
"src/utils/menuRender.tsx",
"src/utils/IconUtil.ts",
"src/utils/permission.ts",
"src/utils/tree.ts"
],
"name": "工具类文档" "name": "工具类文档"
} }

Loading…
Cancel
Save