Browse Source

Merge pull request 'refactor: 重构模型和数据集列表' (#45) from dev-zw into dev

pull/46/head^2
cp3hnu 1 year ago
parent
commit
715db8656b
57 changed files with 1559 additions and 2694 deletions
  1. +1
    -1
      react-ui/config/defaultSettings.ts
  2. +19
    -28
      react-ui/src/app.tsx
  3. +0
    -2
      react-ui/src/components/KFModal/index.less
  4. +5
    -0
      react-ui/src/components/KFRadio/index.less
  5. +1
    -1
      react-ui/src/components/PageTitle/index.less
  6. +10
    -0
      react-ui/src/hooks/index.ts
  7. +1
    -1
      react-ui/src/iconfont/iconfont.js
  8. +29
    -13
      react-ui/src/overrides.less
  9. +9
    -0
      react-ui/src/pages/Dataset/components/AddDatasetModal/index.less
  10. +202
    -0
      react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx
  11. +41
    -0
      react-ui/src/pages/Dataset/components/CategoryItem/index.less
  12. +37
    -0
      react-ui/src/pages/Dataset/components/CategoryItem/index.tsx
  13. +22
    -0
      react-ui/src/pages/Dataset/components/CategoryList/index.less
  14. +71
    -0
      react-ui/src/pages/Dataset/components/CategoryList/index.tsx
  15. +39
    -0
      react-ui/src/pages/Dataset/components/ResourceList/index.less
  16. +210
    -0
      react-ui/src/pages/Dataset/components/ResourceList/index.tsx
  17. +8
    -0
      react-ui/src/pages/Dataset/components/ResourcePage/index.less
  18. +113
    -0
      react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
  19. +61
    -0
      react-ui/src/pages/Dataset/components/Resourcetem/index.less
  20. +54
    -0
      react-ui/src/pages/Dataset/components/Resourcetem/index.tsx
  21. +28
    -38
      react-ui/src/pages/Dataset/datasetIntro.jsx
  22. +5
    -88
      react-ui/src/pages/Dataset/index.jsx
  23. +3
    -261
      react-ui/src/pages/Dataset/index.less
  24. +0
    -479
      react-ui/src/pages/Dataset/personalData.jsx
  25. +0
    -284
      react-ui/src/pages/Dataset/publicData.jsx
  26. +118
    -0
      react-ui/src/pages/Dataset/type.tsx
  27. +0
    -13
      react-ui/src/pages/Experiment/experimentText/addExperimentModal.less
  28. +2
    -2
      react-ui/src/pages/Experiment/experimentText/props.jsx
  29. +3
    -3
      react-ui/src/pages/Mirror/create.less
  30. +14
    -26
      react-ui/src/pages/Mirror/create.tsx
  31. +2
    -2
      react-ui/src/pages/Mirror/list.less
  32. +11
    -17
      react-ui/src/pages/Mirror/list.tsx
  33. +4
    -0
      react-ui/src/pages/Model/components/AddModelModal/index.less
  34. +180
    -0
      react-ui/src/pages/Model/components/AddModelModal/index.tsx
  35. +5
    -87
      react-ui/src/pages/Model/index.jsx
  36. +2
    -251
      react-ui/src/pages/Model/index.less
  37. +30
    -37
      react-ui/src/pages/Model/modelIntro.jsx
  38. +0
    -525
      react-ui/src/pages/Model/personalData.jsx
  39. +0
    -384
      react-ui/src/pages/Model/publicData.jsx
  40. +0
    -4
      react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx
  41. +0
    -1
      react-ui/src/pages/Pipeline/editPipeline/globalParamsDrawer.tsx
  42. +28
    -32
      react-ui/src/pages/Pipeline/editPipeline/props.jsx
  43. +9
    -14
      react-ui/src/pages/Pipeline/index.jsx
  44. +4
    -47
      react-ui/src/pages/Pipeline/index.less
  45. +6
    -7
      react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx
  46. +1
    -1
      react-ui/src/pages/Workspace/components/ExperimentChart/index.less
  47. +6
    -5
      react-ui/src/pages/Workspace/components/ExperimentChart/index.tsx
  48. +4
    -4
      react-ui/src/pages/Workspace/components/ExperimentTable/index.less
  49. +2
    -6
      react-ui/src/pages/Workspace/components/TotalStatistics/index.less
  50. +1
    -7
      react-ui/src/pages/Workspace/components/UserSpace/index.less
  51. +15
    -5
      react-ui/src/pages/Workspace/components/UserSpace/index.tsx
  52. +2
    -1
      react-ui/src/pages/Workspace/index.less
  53. +2
    -15
      react-ui/src/requestConfig.ts
  54. +59
    -0
      react-ui/src/styles/menu.less
  55. +22
    -1
      react-ui/src/styles/theme.less
  56. +31
    -0
      react-ui/src/utils/menuRender.tsx
  57. +27
    -1
      react-ui/src/utils/ui.tsx

+ 1
- 1
react-ui/config/defaultSettings.ts View File

@@ -19,7 +19,7 @@ const Settings: ProLayoutProps & {
title: '智能软件开发平台',
pwa: true,
logo: '/assets/images/left-top-logo.png',
iconfontUrl: '//at.alicdn.com/t/c/font_4511326_1cmi0j3dj1x.js',
iconfontUrl: '//at.alicdn.com/t/c/font_4511326_a182r7rksx5.js',
token: {
// 参见ts声明,demo 见文档,通过token 修改样式
//https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F


+ 19
- 28
react-ui/src/app.tsx View File

@@ -17,9 +17,10 @@ import {
patchRouteWithRemoteMenus,
setRemoteMenu,
} from './services/session';
import './styles/menu.less';
export { requestConfig as request } from './requestConfig';
// const isDev = process.env.NODE_ENV === 'development';
import { menuItemRender } from '@/utils/menuRender';
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
@@ -139,10 +140,8 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
onClick: () => {
// 点击菜单项,删除所有的页面 state 缓存
removeAllPageCacheState();
// console.log('click menu');
},
// onSelect: (e) => {
// console.log(e);
// },
},
...initialState?.settings,
token: {
@@ -150,51 +149,36 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
colorTextMenu: themes['textColor'],
colorTextMenuSelected: themes['primaryColor'],
colorTextMenuActive: themes['primaryColor'],
colorTextMenuItemHover: themes['primaryColor'],
colorBgMenuItemSelected: 'rgba(197, 232, 255, 0.8)',
colorMenuBackground: themes['siderBGColor'],
},
},
// menuItemRender: (itemProps, defaultDom, props) => {
// console.log('menuItemProps', itemProps);
// console.log('defaultDom', defaultDom);
// console.log('props', props);

// const { pathname } = window.location;
// const isSelected = pathname === itemProps.path;

// // 根据菜单项的状态动态显示不同的 icon
// const icon = isSelected ? 'icon-developmentEnvironment-icon' : 'icon-kaifahuanjing';
// return (
// <Link to={itemProps.path || ''} target={itemProps.target}>
// <KFIcon type={icon} />
// {itemProps.name}
// </Link>
// );
// },
menuItemRender: menuItemRender(false),
subMenuItemRender: menuItemRender(true),
};
};

export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => {
const { location } = e;
const menus = getRemoteMenu();
console.log('onRouteChange', e);
// console.log('onRouteChange', e);
if (menus === null && location.pathname !== PageEnum.LOGIN) {
console.log('refresh');
history.go(0);
}
};

export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => {
console.log('patchRoutes', e);
//console.log('patchRoutes', e);
};

export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => {
console.log('patchClientRoutes', e);
//console.log('patchClientRoutes', e);
patchRouteWithRemoteMenus(e.routes);
};

export function render(oldRender: () => void) {
console.log('render');
//console.log('render');
const token = getAccessToken();
if (!token || token?.length === 0) {
oldRender();
@@ -215,6 +199,7 @@ export const antd: RuntimeAntdConfig = (memo) => {
colorError: themes['errorColor'],
colorWarning: themes['warningColor'],
colorLink: themes['primaryColor'],
colorText: themes['textColor'],
};
memo.theme.components ??= {};
memo.theme.components.Tabs = {};
@@ -229,10 +214,11 @@ export const antd: RuntimeAntdConfig = (memo) => {
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
contentFontSize: parseInt(themes['fontSize']),
controlHeight: 34,
};
memo.theme.components.Input = {
inputFontSize: parseInt(themes['fontSize']),
inputFontSize: parseInt(themes['fontSizeInput']),
inputFontSizeLG: parseInt(themes['fontSizeInputLg']),
paddingBlockLG: 10,
};
memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)',
@@ -241,6 +227,11 @@ export const antd: RuntimeAntdConfig = (memo) => {
memo.theme.components.Tabs = {
titleFontSize: 16,
};

memo.theme.components.Form = {
labelColor: 'rgba(29, 29, 32, 0.8);',
};

memo.theme.cssVar = true;
// memo.theme.hashed = false;



+ 0
- 2
react-ui/src/components/KFModal/index.less View File

@@ -23,8 +23,6 @@
border-radius: 10px;
}
.ant-btn-default {
color: @text-color;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
}
.ant-btn + .ant-btn {


+ 5
- 0
react-ui/src/components/KFRadio/index.less View File

@@ -23,6 +23,11 @@
&--active {
color: @primary-color;
border: 1px solid @primary-color;

&:hover {
color: @primary-color;
border: 1px solid @primary-color;
}
}

& + & {


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

@@ -3,5 +3,5 @@
align-items: center;
height: 50px;
padding-left: 30px;
background-image: url('../../assets/img/page-title-bg.png');
background-image: url(@/assets/img/page-title-bg.png);
}

+ 10
- 0
react-ui/src/hooks/index.ts View File

@@ -126,3 +126,13 @@ export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => {
}
}, [form, prevOpen, open]);
};

export const useInputModel = <T>(initialValue: T) => {
const [value, setValue] = useState<T>(initialValue);

const updateValue = useCallback((e: any) => {
setValue(e.target?.value);
}, []);

return [value, updateValue];
};

+ 1
- 1
react-ui/src/iconfont/iconfont.js
File diff suppressed because it is too large
View File


+ 29
- 13
react-ui/src/overrides.less View File

@@ -57,11 +57,6 @@
overflow-y: auto;
}

// Input
.ant-input-textarea-affix-wrapper.ant-input-affix-wrapper {
padding: 0;
}

// Modal
.ant-modal {
.ant-modal-close {
@@ -81,18 +76,24 @@
}
}

.ant-form-item .ant-form-item-label > label {
font-size: @font-size;
}

// 输入框高度为46px
.ant-input-affix-wrapper {
height: 46px;
padding: 1px 11px;
padding-top: 2px;
padding-bottom: 2px;

.ant-input {
height: 40px;
}
}

// 选择框高度为46px
.ant-select-single {
height: 46px;
}

.ant-select-single .ant-select-selector .ant-select-selection-placeholder {
line-height: 46px;
}
}

// Confirm Modal
@@ -128,8 +129,6 @@
border-radius: 10px;
}
.ant-btn-default {
color: @text-color;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
}
.ant-btn + .ant-btn {
@@ -137,3 +136,20 @@
}
}
}

// 表单类型为large时,font-size为15px
.ant-form-large {
.ant-form-item-label {
label {
font-size: @font-size;
}
}
}

// 取消 hover 颜色变化
.ant-menu .ant-menu-title-content {
transition: color 0s;
a {
transition: color 0s;
}
}

+ 9
- 0
react-ui/src/pages/Dataset/components/AddDatasetModal/index.less View File

@@ -0,0 +1,9 @@
.upload-tip {
margin-top: 5px;
color: @error-color;
}

.upload-button {
height: 48px;
font-size: 15px;
}

+ 202
- 0
react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx View File

@@ -0,0 +1,202 @@
import { getAccessToken } from '@/access';
import { DictValueEnumObj } from '@/components/DictTag';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { addDatesetAndVesion } from '@/services/dataset/index.js';
import { getDictSelectOption } from '@/services/system/dict';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
Button,
Form,
Input,
Radio,
Select,
Upload,
UploadFile,
message,
type ModalProps,
type UploadProps,
} from 'antd';
import { omit } from 'lodash';
import { useEffect, useState } from 'react';
import { CategoryData } from '../../type';
import styles from './index.less';

interface AddDatasetModalProps extends ModalProps {
typeList: CategoryData[];
tagList: CategoryData[];
onOk: () => void;
}

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

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

// 上传组件参数
const uploadProps: UploadProps = {
action: '/api/mmp/dataset/upload',
headers: {
Authorization: getAccessToken() || '',
},
defaultFileList: [],
};

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

// 上传请求
const createDataset = async (params: any) => {
const [res] = await to(addDatesetAndVesion(params));
if (res) {
message.success('创建成功');
onOk?.();
}
};

// 提交
const onFinish = (formData: any) => {
const fileList: UploadFile[] = formData['fileList'] ?? [];
if (validateUploadFiles(fileList)) {
const params = {
...omit(formData, ['fileList']),
dataset_version_vos: fileList.map((item) => {
const data = item.response?.data?.[0] ?? {};
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
};
createDataset(params);
}
};

return (
<KFModal
{...rest}
title="新建数据集"
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
destroyOnClose
>
<Form
name="form"
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
label="数据集名称"
name="name"
required
rules={[
{
required: true,
message: '请输入数据集名称',
},
]}
>
<Input placeholder="请输入数据名称" showCount allowClear maxLength={64} />
</Form.Item>
<Form.Item
label="数据集版本"
name="version"
rules={[
{
required: true,
message: '请输入数据集版本',
},
]}
>
<Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} />
</Form.Item>
<Form.Item label="数据集分类" name="data_type">
<Select
allowClear
placeholder="请选择数据集分类"
options={typeList}
fieldNames={{ label: 'name', value: 'id' }}
/>
</Form.Item>
<Form.Item label="研究方向/应用领域" name="data_tag">
<Select
allowClear
placeholder="请选择研究方向/应用领域"
options={tagList}
fieldNames={{ label: 'name', value: 'id' }}
/>
</Form.Item>
<Form.Item label="集群版本" name="available_cluster">
<Select allowClear placeholder="请选择集群版本" options={clusterOptions} />
</Form.Item>
<Form.Item
label="数据集简介"
name="description"
rules={[
{
required: true,
message: '请输入数据集简介',
},
]}
>
<Input.TextArea
placeholder="请输入数据集简介"
showCount
maxLength={256}
autoSize={{ minRows: 2, maxRows: 6 }}
allowClear
/>
</Form.Item>
<Form.Item label="选择流水线" name="range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="数据集文件"
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[
{
required: true,
message: '请上传数据集文件',
},
]}
>
<Upload {...uploadProps} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
className={styles['upload-button']}
type="default"
icon={<KFIcon type="icon-shangchuan" />}
>
上传文件
</Button>
<div className={styles['upload-tip']}>只允许上传.zip,.tgz格式文件</div>
</Upload>
</Form.Item>
</Form>
</KFModal>
);
}

export default AddDatasetModal;

+ 41
- 0
react-ui/src/pages/Dataset/components/CategoryItem/index.less View File

@@ -0,0 +1,41 @@
.category-item {
display: flex;
flex-direction: column;
align-items: center;
width: 92px;
height: 62px;
padding: 11px 8px 7px;
color: @text-color;
font-size: 12px;
border: 1px solid rgba(22, 100, 255, 0.05);
border-radius: 4px;
cursor: pointer;

&__icon {
display: block;
}
&__active-icon {
display: none;
}
&__name {
width: 100%;
margin-top: 4px;
overflow: hidden;
white-space: nowrap;
text-align: center;
text-overflow: ellipsis;
}

&:hover,
&--active {
background: rgba(22, 100, 255, 0.03);
border: 1px solid @primary-color;

.category-item__icon {
display: none;
}
.category-item__active-icon {
display: block;
}
}
}

+ 37
- 0
react-ui/src/pages/Dataset/components/CategoryItem/index.tsx View File

@@ -0,0 +1,37 @@
import classNames from 'classnames';
import { CategoryData, ResourceType, resourceConfig } from '../../type';
import styles from './index.less';

type CategoryItemProps = {
resourceType: ResourceType;
item: CategoryData;
isSelected: boolean;
onClick: (item: CategoryData) => void;
};

function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemProps) {
return (
<div
className={classNames(styles['category-item'], {
[styles['category-item--active']]: isSelected,
})}
onClick={() => onClick(item)}
>
<img
className={styles['category-item__icon']}
style={{ width: '22px' }}
src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}.png`}
alt=""
/>
<img
className={styles['category-item__active-icon']}
style={{ width: '22px' }}
src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}-hover.png`}
alt=""
/>
<span className={styles['category-item__name']}>{item.name}</span>
</div>
);
}

export default CategoryItem;

+ 22
- 0
react-ui/src/pages/Dataset/components/CategoryList/index.less View File

@@ -0,0 +1,22 @@
.category-list {
width: 340px;
height: 100%;
margin-right: 10px;
padding: 15px 0;
background: white;
border-radius: 4px;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);

&__content {
height: calc(100% - 32px - 15px);
margin-top: 15px;
padding-left: 20px;
overflow-y: auto;

&__title {
margin-bottom: 15px;
color: @text-color;
font-size: 14px;
}
}
}

+ 71
- 0
react-ui/src/pages/Dataset/components/CategoryList/index.tsx View File

@@ -0,0 +1,71 @@
import { Flex, Input } from 'antd';
import { CategoryData, ResourceType, resourceConfig } from '../../type';
import CategoryItem from '../CategoryItem';
import styles from './index.less';

export type CategoryValue = {
dataType: number | undefined;
dataTag: number | undefined;
};

type CategoryProps = {
resourceType: ResourceType; // 资源类型,数据集还是模型
typeList: CategoryData[];
tagList: CategoryData[];
activeType?: number;
activeTag?: number;
onTypeSelect: (value: CategoryData) => void;
onTagSelect: (value: CategoryData) => void;
onSearch: (value: string) => void;
};

function CategoryList({
resourceType,
typeList,
tagList,
activeType,
activeTag,
onTypeSelect,
onTagSelect,
onSearch,
}: CategoryProps) {
return (
<div className={styles['category-list']}>
<div style={{ padding: '0 20px' }}>
<Input.Search placeholder="搜索" allowClear onSearch={onSearch} />
</div>
<div className={styles['category-list__content']}>
<div className={styles['category-list__content__title']}>
{resourceConfig[resourceType].typeTitle}
</div>
<Flex wrap="wrap" gap="20px 12px">
{typeList?.map((item) => (
<CategoryItem
key={item.id}
resourceType={resourceType}
item={item}
onClick={onTypeSelect}
isSelected={item.id === activeType}
></CategoryItem>
))}
</Flex>
<div className={styles['category-list__content__title']} style={{ marginTop: '25px' }}>
{resourceConfig[resourceType].tagTitle}
</div>
<Flex wrap="wrap" gap="20px 12px">
{tagList?.map((item) => (
<CategoryItem
key={item.id}
resourceType={resourceType}
item={item}
onClick={onTagSelect}
isSelected={item.id === activeTag}
></CategoryItem>
))}
</Flex>
</div>
</div>
);
}

export default CategoryList;

+ 39
- 0
react-ui/src/pages/Dataset/components/ResourceList/index.less View File

@@ -0,0 +1,39 @@
.resource-list {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
padding: 20px 0;
background: white;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);

&__header {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin-bottom: 30px;
padding: 0 30px;
color: @text-color;
font-size: 15px;
}

&__content {
display: flex;
flex: 1;
flex-wrap: wrap;
gap: 20px;
align-content: flex-start;
width: 100%;
margin-bottom: 30px;
padding: 0 30px;
overflow-y: auto;
}

:global {
.ant-pagination {
margin-right: 30px;
text-align: right;
}
}
}

+ 210
- 0
react-ui/src/pages/Dataset/components/ResourceList/index.tsx View File

@@ -0,0 +1,210 @@
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import AddModelModal from '@/pages/Model/components/AddModelModal';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { Button, Input, Pagination, PaginationProps, message } from 'antd';
import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../type';
import AddDatasetModal from '../AddDatasetModal';
import ResourceItem from '../Resourcetem';
import styles from './index.less';

export type ResourceListRef = {
reset: () => void;
};

type ResourceListProps = {
resourceType: ResourceType;
dataType?: number;
dataTag?: number;
isPublic: boolean;
typeList: CategoryData[];
tagList: CategoryData[];
initialSearchText?: string;
initialPagination?: PaginationProps;
setCacheState: (state?: any) => void;
};

function ResourceList(
{
resourceType,
dataType,
dataTag,
typeList,
tagList,
isPublic,
initialSearchText,
initialPagination,
setCacheState,
}: ResourceListProps,
ref: Ref<ResourceListRef>,
) {
const navigate = useNavigate();
const [dataList, setDataList] = useState<ResourceData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<PaginationProps>(
initialPagination ?? {
current: 1,
pageSize: 20,
},
);
const [searchText, setSearchText] = useState(initialSearchText);
const [inputText, setInputText] = useState(initialSearchText);

useEffect(() => {
getDataList();
}, [resourceType, dataType, dataTag, pagination, searchText, isPublic]);

useImperativeHandle(
ref,
() => {
return {
reset: () => {
setPagination({
current: 1,
pageSize: 20,
});
setSearchText('');
setInputText('');
setDataList([]);
},
};
},
[],
);

// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
[resourceConfig[resourceType].typeParamKey]: dataType,
[resourceConfig[resourceType].tagParamKey]: dataTag,
available_range: isPublic ? 1 : 0,
name: searchText !== '' ? searchText : undefined,
};
const request = resourceConfig[resourceType].getList;
const [res] = await to(request(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};

// 删除请求
const deleteRecord = async (id: number) => {
const request = resourceConfig[resourceType].deleteRecord;
const [res] = await to(request(id));
if (res) {
getDataList();
message.success('删除成功');
}
};

// 搜索
const handleSearch = (value: string) => {
setSearchText(value);
};

// 删除
const handleRemove = (record: ResourceData) => {
modalConfirm({
title: resourceConfig[resourceType].deleteModalTitle,
onOk: () => {
deleteRecord(record.id);
},
});
};

// 跳转
const handleClick = (record: ResourceData) => {
setCacheState({
activeTab: isPublic ? CommonTabKeys.Public : CommonTabKeys.Private,
pagination,
searchText,
activeType: dataType,
activeTag: dataTag,
});
if (resourceType === ResourceType.Dataset) {
navigate(`/dataset/dataset/${record.id}?isPublic=${isPublic}`);
} else {
navigate(`/dataset/model/${record.id}?isPublic=${isPublic}`);
}
};

// 分页切换
const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => {
setPagination({
current: page,
pageSize: pageSize,
});
};

// 新建弹框
const showModal = () => {
const Modal = resourceType === ResourceType.Dataset ? AddDatasetModal : AddModelModal;
const { close } = openAntdModal(Modal, {
typeList: typeList,
tagList: tagList,
onOk: () => {
getDataList();
close();
},
});
};

return (
<div className={styles['resource-list']}>
<div className={styles['resource-list__header']}>
<span>数据总数:{total}个</span>
<div>
<Input.Search
placeholder="按数据名称筛选"
allowClear
onSearch={handleSearch}
style={{
width: 300,
}}
onChange={(e) => setInputText(e.target.value)}
value={inputText}
/>
{!isPublic && (
<Button
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
{resourceConfig[resourceType].addBtnTitle}
</Button>
)}
</div>
</div>
<div className={styles['resource-list__content']}>
{dataList?.map((item) => (
<ResourceItem
item={item}
key={item.id}
isPublic={isPublic}
onRemove={handleRemove}
onClick={handleClick}
></ResourceItem>
))}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={handlePageChange}
{...pagination}
/>
</div>
);
}

export default forwardRef(ResourceList);

+ 8
- 0
react-ui/src/pages/Dataset/components/ResourcePage/index.less View File

@@ -0,0 +1,8 @@
.resource-page {
height: 100%;
&__tabs-container {
height: 50px;
padding-left: 27px;
background-image: url(@/assets/img/page-title-bg.png);
}
}

+ 113
- 0
react-ui/src/pages/Dataset/components/ResourcePage/index.tsx View File

@@ -0,0 +1,113 @@
import { CommonTabKeys } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { getAssetIcon } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { Flex, Tabs, type TabsProps } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { CategoryData, ResourceType, resourceConfig } from '../../type';
import CategoryList from '../CategoryList';
import ResourceList, { ResourceListRef } from '../ResourceList';
import styles from './index.less';

type ResourcePageProps = {
resourceType: ResourceType; // 资源类型,数据集还是模型
};

function ResourcePage({ resourceType }: ResourcePageProps) {
const [cacheState, setCacheState] = useCacheState();
const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public);
const [typeList, setTypeList] = useState<CategoryData[]>([]);
const [tagList, setTagList] = useState<CategoryData[]>([]);
const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType);
const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag);
const dataListRef = useRef<ResourceListRef>(null);

useEffect(() => {
getAssetIconList();
}, []);

// 分类搜索
const handleCategorySearch = (value: string) => {
getAssetIconList(value);
};

// 选择类型
const chooseType = (record: CategoryData) => {
setActiveType((prev) => (prev === record.id ? undefined : record.id));
};

// 选择 Tag
const chooseTag = (record: CategoryData) => {
setActiveTag((prev) => (prev === record.id ? undefined : record.id));
};

// 获取分类
const getAssetIconList = async (name: string = '') => {
const params = {
name: name,
page: 0,
size: 10000,
};
const [res] = await to(getAssetIcon(params));
if (res && res.data && res.data.content) {
const { content } = res.data;
setTypeList(
content.filter(
(item: CategoryData) =>
Number(item.category_id) === resourceConfig[resourceType].typeValue,
),
);
setTagList(
content.filter(
(item: CategoryData) =>
Number(item.category_id) === resourceConfig[resourceType].tagValue,
),
);
}
};

// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
dataListRef.current?.reset();
setActiveTab(value);
};

const isPublic = activeTab === CommonTabKeys.Public;
return (
<div className={styles['resource-page']}>
<div className={styles['resource-page__tabs-container']}>
<Tabs
activeKey={activeTab}
items={resourceConfig[resourceType].tabItems}
onChange={hanleTabChange}
/>
</div>
<Flex style={{ marginTop: '10px', height: 'calc(100% - 59px)' }}>
<CategoryList
resourceType={resourceType}
typeList={typeList}
tagList={tagList}
activeType={activeType}
activeTag={activeTag}
onTypeSelect={chooseType}
onTagSelect={chooseTag}
onSearch={handleCategorySearch}
/>
<ResourceList
ref={dataListRef}
resourceType={resourceType}
typeList={typeList}
tagList={tagList}
isPublic={isPublic}
dataType={activeType}
dataTag={activeTag}
initialSearchText={cacheState?.searchText}
initialPagination={cacheState?.initialPagination}
setCacheState={setCacheState}
></ResourceList>
</Flex>
</div>
);
}

export default ResourcePage;

+ 61
- 0
react-ui/src/pages/Dataset/components/Resourcetem/index.less View File

@@ -0,0 +1,61 @@
.resource-item {
position: relative;
width: calc(25% - 15px);
padding: 20px;
background: white;
border: 1px solid #eaeaea;
border-radius: 4px;
cursor: pointer;

@media screen and (max-width: 1860px) {
& {
width: calc(33.33% - 13.33px);
}
}

&__name {
position: relative;
display: inline-block;
height: 24px;
margin: 0 10px 0 0 !important;
color: @text-color;
font-size: 16px;
}

&__description {
height: 44px;
margin-bottom: 20px;
color: @text-color-secondary;
font-size: 14px;
.multiLine(2);
}
&__time {
display: flex;
flex: 0 1 content;
align-items: center;
width: 100%;
color: #808080;
font-size: 13px;
}

&:hover {
border-color: @primary-color;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);

.resource-item__name {
color: @primary-color;
}
}
}

.resource-item__name {
&::after {
position: absolute;
top: 14px;
left: 0;
width: 100%;
height: 6px;
background: linear-gradient(to right, rgba(22, 100, 255, 0.3) 0, rgba(22, 100, 255, 0) 100%);
content: '';
}
}

+ 54
- 0
react-ui/src/pages/Dataset/components/Resourcetem/index.tsx View File

@@ -0,0 +1,54 @@
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import KFIcon from '@/components/KFIcon';
import { formatDate } from '@/utils/date';
import { Button, Flex, Typography } from 'antd';
import { ResourceData } from '../../type';
import styles from './index.less';

type ResourceItemProps = {
item: ResourceData;
isPublic: boolean;
onRemove: (item: ResourceData) => void;
onClick: (item: ResourceData) => void;
};

function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) {
return (
<div className={styles['resource-item']} onClick={() => onClick(item)}>
<Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}>
<Typography.Paragraph
className={styles['resource-item__name']}
ellipsis={{ tooltip: item.name }}
>
{item.name}
</Typography.Paragraph>
{!isPublic && (
<Button
type="text"
shape="circle"
onClick={(e) => {
e.stopPropagation();
onRemove(item);
}}
>
<KFIcon type="icon-shanchu" font={17} />
</Button>
)}
</Flex>
<div className={styles['resource-item__description']}>{item.description}</div>
<Flex justify="space-between">
<div className={styles['resource-item__time']}>
<img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" />
<span>{item.create_by}</span>
</div>
<div className={styles['resource-item__time']}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span>
</div>
</Flex>
</div>
);
}

export default ResourceItem;

+ 28
- 38
react-ui/src/pages/Dataset/datasetIntro.jsx View File

@@ -1,5 +1,6 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import {
addDatasetVersionDetail,
deleteDatasetVersion,
@@ -8,11 +9,12 @@ import {
getDatasetVersionsById,
} from '@/services/dataset/index.js';
import { downLoadZip } from '@/utils/downloadfile';
import { modalConfirm } from '@/utils/ui';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd';
import { useParams, useSearchParams } from '@umijs/max';
import { Button, Form, Input, Select, Table, Tabs, Upload, message } from 'antd';
import moment from 'moment';
import { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import Styles from './index.less';
const { Search } = Input;
const { TabPane } = Tabs;
@@ -53,9 +55,11 @@ const Dataset = () => {
const [version, setVersion] = useState(null);
const [versionList, setVersionList] = useState([]);
const locationParams = useParams(); //新版本获取路由参数接口
const [searchParams] = useSearchParams();
const [wordList, setWordList] = useState([]);
const [activeTabKey, setActiveTabKey] = useState('1');
const [uuid, setUuid] = useState(Date.now());
const isPublic = searchParams.get('isPublic') === 'true';
const getDatasetByDetail = () => {
getDatasetById(locationParams.id).then((ret) => {
console.log(ret);
@@ -77,6 +81,9 @@ const Dataset = () => {
);
setVersion(ret.data[0]);
getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id });
} else {
setVersion(null);
setWordList([]);
}
});
};
@@ -102,21 +109,9 @@ const Dataset = () => {
downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version });
};
const deleteDataset = () => {
Modal.confirm({
title: (
<div>
<img
src="/assets/images/delete-icon.png"
style={{ width: '120px', marginBottom: '24px' }}
alt=""
/>
<div style={{ color: '#1d1d20', fontSize: '16px' }}>删除后,该数据集版本将不可恢复</div>
</div>
),
content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>,
okText: '确认',
cancelText: '取消',

modalConfirm({
title: '删除后,该数据集版本将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => {
getDatasetVersionList();
@@ -264,15 +259,17 @@ const Dataset = () => {
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
{!isPublic && (
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
)}
<Button
type="default"
disabled={!version}
@@ -295,17 +292,10 @@ const Dataset = () => {
</TabPane>
</Tabs>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
<KFModal
title={dialogTitle}
width={825}
image={require('@/assets/img/create-experiment.png')}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
@@ -393,7 +383,7 @@ const Dataset = () => {
</Upload>
</Form.Item>
</Form>
</Modal>
</KFModal>
</div>
);
};


+ 5
- 88
react-ui/src/pages/Dataset/index.jsx View File

@@ -1,90 +1,7 @@
import { getDatasetList } from '@/services/dataset/index.js';
import { Form, Input, Tabs } from 'antd';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
import PersonalData from './personalData';
import PublicData from './publicData';
const { Search } = Input;
const { TabPane } = Tabs;
const leftdataList = [1, 2, 3];
import ResourcePage from './components/ResourcePage';
import { ResourceType } from './type';

const Dataset = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
name: null,
});
const navgite = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const getDatasetlist = () => {
getDatasetList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = (values) => {};
const routeToIntro = (e, record) => {
e.stopPropagation();
navgite({ pathname: '/dataset/dataset' });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
useEffect(() => {
//getDatasetlist();
return () => {};
}, []);
return (
<div className={Styles.datasetBox}>
<div className={Styles.datasetTopBox}></div>
<div className={Styles.datasetAllBox}>
<Tabs defaultActiveKey="1">
<TabPane
tab="数据广场"
key="1"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-shujujiguangchang"></use>
</svg>
}
>
<PublicData />
</TabPane>
<TabPane
tab="个人数据"
key="2"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-gerenshujuji"></use>
</svg>
}
>
<PersonalData />
</TabPane>
</Tabs>
</div>
</div>
);
const DatasetPage = () => {
return <ResourcePage resourceType={ResourceType.Dataset} />;
};
export default Dataset;
export default DatasetPage;

+ 3
- 261
react-ui/src/pages/Dataset/index.less View File

@@ -1,14 +1,3 @@
.datasetTopBox {
display: flex;
align-items: center;
width: 100%;
height: 49px;
padding: 0 30px;
padding-right: 30px;
font-family: 'Alibaba';
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;
}
.datasetIntroTopBox {
display: flex;
flex-direction: column;
@@ -68,7 +57,6 @@
font-size: 14px;
}
.datasetBox {
font-family: 'Alibaba';
background: #f9fafb;

:global {
@@ -80,258 +68,12 @@
}
}
}
.datasetAllBox {
:global {
.ant-tabs-nav .ant-tabs-nav-wrap {
margin: -48px 0 0 30px;
}
}
}

.plusButton {
margin: 0 18px 0 20px;
}

.datasetCneterBox {
display: flex;
justify-content: space-between;
width: 100%;
height: 87.5vh;
:global {
.ant-btn {
color: #1d1d20;
font-size: 14px;
}
}
.datasetCneterLeftBox {
width: 340px;
height: 100%;
margin-right: 10px;
padding-top: 15px;
font-family: 'Alibaba';
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.custTab {
display: flex;
height: 32px;
border-bottom: 1px solid #e0eaff;
.tabItem {
width: 52px;
height: 100%;
color: #808080;
font-size: 15px;
text-align: center;
cursor: pointer;
}
}
.leftContentBox {
max-height: 80vh;
padding: 15px 20px;
overflow-x: hidden;
overflow-y: auto;
font-family: 'Alibaba';
.itemTitle {
margin-bottom: 15px;
color: #1d1d20;
font-size: 14px;
}
.itemBox {
display: flex;
flex-wrap: wrap;
align-content: start;
width: 110%;
.messageBox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 92px;
height: 62px;
margin: 0 12px 20px 0;
padding: 11px 0px 7px 0px;
color: #1d1d20;
font-size: 12px;
border: 1px solid;
border-color: rgba(22, 100, 255, 0.05);
border-radius: 4px;
cursor: pointer;
.ptIcon {
display: block;
}
.hoverIcon {
display: none;
}
.messageText {
width: 65px;
overflow: hidden;
white-space: nowrap;
text-align: center;
text-overflow: ellipsis;
}
}
.messageBox:hover {
background: rgba(22, 100, 255, 0.03);
border: 1px solid;
border-color: #1664ff;
.ptIcon {
display: none;
}
.hoverIcon {
display: block;
}
}
.active {
background: rgba(22, 100, 255, 0.03) !important;
border: 1px solid !important;
border-color: #1664ff !important;
.ptIcon {
display: none !important;
}
.hoverIcon {
display: block !important;
}
}
}
}
}

.datasetCneterRightBox {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: 22px 30px 26px 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.dataSource {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin-bottom: 30px;
color: rgba(29, 29, 32, 0.8);
font-size: 15px;
}
.dataContent {
display: flex;
flex: 1;
flex-wrap: wrap;
align-content: flex-start;

width: 100%;
.dataItem {
position: relative;
width: 23.8%;
height:164px;
margin: 0 20px 25px 0;
background: #ffffff;
border: 1px solid;
border-color: #eaeaea;
border-radius: 4px;
cursor: pointer;
.dropdown{
position: absolute;
right: 20px;
top: 15px;
}
.itemText {
position: absolute;
top: 20px;
left: 20px;
height: 6px;
color: #1d1d20;
font-size: 16px;
font-family: 'Alibaba';
line-height: 0px;
background: linear-gradient(
to right,
rgba(22, 100, 255, 0.3) 0,
rgba(22, 100, 255, 0) 100%
);
}
.itemDescripition {
position: absolute;
top: 57px;
left: 20px;
display: -webkit-box;
padding-right: 28px;
overflow: hidden;
color: #575757;
font-size: 14px;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.itemTime {
position: absolute;
bottom: 22px;
left: 20px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
.itemIcon {
position: absolute;
right: 20px;
bottom: 22px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
}
.dataItem:hover {
border-color: #1664ff;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);
}
.dataItem:hover .itemText {
color: #1664ff;
}
}
}
}
.tipContent{
color: #c73131;
.tipContent {
margin-top: 5px;
}
.modal {
:global {
.ant-modal-content {
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {
margin: 20px 0;
background-color: transparent;
}
.ant-input {
height: 40px;
border-color: #e6e6e6;
}
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
.ant-modal-footer {
display: flex;
justify-content: center;
margin: 40px 0 30px 0;
}
.ant-btn {
width: 110px;
height: 40px;
font-size: 18px;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
border-radius: 10px;
}
.ant-btn-primary {
background: #1664ff;
}
}
color: #c73131;
}

+ 0
- 479
react-ui/src/pages/Dataset/personalData.jsx View File

@@ -1,479 +0,0 @@
import { getAccessToken } from '@/access';
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import KFIcon from '@/components/KFIcon';
import {
addDatesetAndVesion,
deleteDataset,
getAssetIcon,
getDatasetList,
} from '@/services/dataset/index.js';
import { getDictSelectOption } from '@/services/system/dict';
import { modalConfirm } from '@/utils/ui';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';

import './index.less';
import Styles from './index.less';
const { Search } = Input;
const leftdataList = [1, 2, 3];

const PublicData = (React.FC = () => {
const props = {
action: '/api/mmp/dataset/upload',
// headers: {
// 'X-Requested-With': null
// },
headers: {
Authorization: getAccessToken(),
'X-Requested-With': null,
},
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
form.setFieldsValue({
dataset_version_vos: fileList.map((item) => {
const data = item.response.data[0];
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
});
}
},
defaultFileList: [],
};
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 20,
name: null,
available_range: 0,
});
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [datasetTypeList, setDatasetTypeList] = useState([]);
const [datasetDirectionList, setDatasetDirectionList] = useState([]);
const navgite = useNavigate();
const [clusterOptions, setClusterOptions] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const [uuid, setUuid] = useState(Date.now());
const getDatasetlist = (queryFlow) => {
getDatasetList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setUuid(Date.now());
setIsModalOpen(true);
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 1));
setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 2));
} else {
setDatasetTypeList([]);
setDatasetDirectionList([]);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const nameSearch = (values) => {
console.log(values);
getDatasetlist({ ...queryFlow, name: values });
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const chooseDatasetType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, data_type: null });
getDatasetlist({ ...queryFlow, data_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, data_type: item.id });
getDatasetlist({ ...queryFlow, data_type: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseDatasetTag = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, data_tag: null });
getDatasetlist({ ...queryFlow, data_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, data_tag: item.id });
getDatasetlist({ ...queryFlow, data_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const onFinish = (values) => {
addDatesetAndVesion(values).then((ret) => {
console.log(ret);
setIsModalOpen(false);
getDatasetlist(queryFlow);
});
};
const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/dataset/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getDatasetlist({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getDictSelectOption('available_cluster').then((data) => {
setClusterOptions(data);
});
getAssetIconList(iconParams);
getDatasetlist(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>分类</div>
<div className={Styles.itemBox}>
{datasetTypeList && datasetTypeList.length > 0
? datasetTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>研究方向/应用领域</div>
<div className={Styles.itemBox}>
{datasetDirectionList && datasetDirectionList.length > 0
? datasetDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按数据名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
<Button
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
新建数据集
</Button>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条数据集实例吗?',
onOk: () => {
deleteDataset(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div>

<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="数据名称"
name="name"
required
rules={[
{
required: true,
message: '请输入数据名称e!',
},
]}
>
<Input placeholder="请输入数据名称" showCount maxLength={64} />
</Form.Item>
<Form.Item
label="数据集版本"
name="version"
rules={[
{
required: true,
message: '请输入数据集版本!',
},
]}
>
<Input placeholder="请输入数据集版本" showCount maxLength={64} />
</Form.Item>
<Form.Item
label="数据集分类"
name="data_type"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择数据集分类"
options={datasetTypeList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item
label="研究方向/应用领域"
name="data_tag"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择研究方向/应用领域"
options={datasetDirectionList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item label="集群版本" name="available_cluster">
<Select allowClear placeholder="请选择集群版本" options={clusterOptions} />
</Form.Item>
<Form.Item
label="数据简介"
name="description"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据简介" showCount maxLength={256} />
</Form.Item>
<Form.Item label="选择流水线" name="range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="数据文件" name="dataset_version_vos">
<Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff', fontSize: '14px' }} />}
>
上传文件
</Button>
<div className={Styles.tipContent}>只允许上传.zip,.tgz格式文件</div>
</Upload>
</Form.Item>
</Form>
</Modal>
</>
);
});
export default PublicData;

+ 0
- 284
react-ui/src/pages/Dataset/publicData.jsx View File

@@ -1,284 +0,0 @@
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import { deleteDataset, getAssetIcon, getDatasetList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { Form, Input, Pagination } from 'antd';
import moment from 'moment';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
const { Search } = Input;
const leftdataList = [1, 2, 3];

const PublicData = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
name: null,
available_range: 1,
});
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const navgite = useNavigate();
const [datasetTypeList, setDatasetTypeList] = useState([]);
const [datasetDirectionList, setDatasetDirectionList] = useState([]);
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const getDatasetlist = (queryFlow) => {
getDatasetList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 1));
setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 2));
} else {
setDatasetTypeList([]);
setDatasetDirectionList([]);
}
});
};
const nameSearch = (values) => {
console.log(values);
getDatasetlist({ ...queryFlow, name: values });
};
const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const chooseDatasetType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, data_type: null });
getDatasetlist({ ...queryFlow, data_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, data_type: item.id });
getDatasetlist({ ...queryFlow, data_type: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseDatasetTag = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, data_tag: null });
getDatasetlist({ ...queryFlow, data_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, data_tag: item.id });
getDatasetlist({ ...queryFlow, data_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};

const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/dataset/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getDatasetlist({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getAssetIconList(iconParams);
getDatasetlist(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>分类</div>
<div className={Styles.itemBox}>
{datasetTypeList && datasetTypeList.length > 0
? datasetTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>研究方向/应用领域</div>
<div className={Styles.itemBox}>
{datasetDirectionList && datasetDirectionList.length > 0
? datasetDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按数据名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条数据集实例吗?',
onOk: () => {
deleteDataset(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
</>
);
};
export default PublicData;

+ 118
- 0
react-ui/src/pages/Dataset/type.tsx View File

@@ -0,0 +1,118 @@
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import {
deleteDataset,
deleteModel,
getDatasetList,
getDatasetVersionIdList,
getDatasetVersionsById,
getModelList,
getModelVersionIdList,
getModelVersionsById,
} from '@/services/dataset/index.js';
import type { TabsProps } from 'antd';

export enum ResourceType {
Model = 'Model', // 模型
Dataset = 'Dataset', // 数据集
}

type ResourceTypeKeys = keyof typeof ResourceType;
type ResourceTypeValues = (typeof ResourceType)[ResourceTypeKeys];

export type ResourceTypeInfo = {
getList: (params: any) => Promise<any>;
getVersions: (params: any) => Promise<any>;
getFiles: (params: any) => Promise<any>;
deleteRecord: (params: any) => Promise<any>;
name: string;
typeParamKey: string;
tagParamKey: string;
fileReqParamKey: 'models_id' | 'dataset_id';
tabItems: TabsProps['items'];
typeTitle: string;
tagTitle: string;
typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue)
tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue)
iconPathPrefix: string; // 图标路径前缀
deleteModalTitle: string; // 删除弹框的title
addBtnTitle: string; // 新增按钮的title
};

export const resourceConfig: Record<ResourceTypeValues, ResourceTypeInfo> = {
[ResourceType.Dataset]: {
getList: getDatasetList,
getVersions: getDatasetVersionsById,
getFiles: getDatasetVersionIdList,
deleteRecord: deleteDataset,
name: '数据集',
typeParamKey: 'data_type',
tagParamKey: 'data_tag',
fileReqParamKey: 'dataset_id',
tabItems: [
{
key: CommonTabKeys.Public,
label: '数据广场',
icon: <KFIcon type="icon-shujuguangchang" />,
},
{
key: CommonTabKeys.Private,
label: '个人数据',
icon: <KFIcon type="icon-gerenshuju" />,
},
],
typeTitle: '分类',
tagTitle: '研究方向/应用领域',
typeValue: 1,
tagValue: 2,
iconPathPrefix: 'dataset',
deleteModalTitle: '确定删除该条数据集实例吗?',
addBtnTitle: '新建数据集',
},
[ResourceType.Model]: {
getList: getModelList,
getVersions: getModelVersionsById,
getFiles: getModelVersionIdList,
deleteRecord: deleteModel,
name: '模型',
typeParamKey: 'model_type',
tagParamKey: 'model_tag',
fileReqParamKey: 'models_id',
tabItems: [
{
key: CommonTabKeys.Public,
label: '模型广场',
icon: <KFIcon type="icon-moxingguangchang" />,
},
{
key: CommonTabKeys.Private,
label: '个人模型',
icon: <KFIcon type="icon-gerenmoxing" />,
},
],
typeTitle: '模型框架',
tagTitle: '模型能力',
typeValue: 3,
tagValue: 4,
iconPathPrefix: 'model',
deleteModalTitle: '确定删除该条模型实例吗?',
addBtnTitle: '新建模型',
},
};

// 分类数据
export type CategoryData = {
id: number;
category_id: number;
name: string;
path: string;
};

// 数据类型
export type ResourceData = {
id: number;
name: string;
description: string;
create_by: string;
update_time: string;
};

+ 0
- 13
react-ui/src/pages/Experiment/experimentText/addExperimentModal.less View File

@@ -1,17 +1,4 @@
.modal {
:global {
// .ant-input {
// height: 30px;
// border-color: #e6e6e6;
// }
// .ant-select-single {
// height: 40px;
// }
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
}

.global_param_item {
max-height: 230px;
padding: 24px 12px 0;


+ 2
- 2
react-ui/src/pages/Experiment/experimentText/props.jsx View File

@@ -246,8 +246,8 @@ const Props = forwardRef(({ onParentChange }, ref) => {
>
下载
</a>
<a style={{ marginRight: '10px' }}>导出到模型库</a>
<a style={{ marginRight: '10px' }}>导出到数据集</a>
{/* <a style={{ marginRight: '10px' }}>导出到模型库</a>
<a style={{ marginRight: '10px' }}>导出到数据集</a> */}
</div>
</div>
<div style={{ margin: '15px 0' }} className={Styles.resultContent}>


+ 3
- 3
react-ui/src/pages/Mirror/create.less View File

@@ -9,9 +9,9 @@
background-color: white;
border-radius: 10px;

&__title {
display: flex;
align-items: center;
&__type {
color: @text-color;
font-size: @font-size-input-lg;
}
}
}

+ 14
- 26
react-ui/src/pages/Mirror/create.tsx View File

@@ -12,7 +12,7 @@ import { CommonTabKeys } from '@/enums';
import { createMirrorReq } from '@/services/mirror';
import { to } from '@/utils/promise';
import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage';
import { getFileListFromEvent } from '@/utils/ui';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd';
import { omit } from 'lodash';
@@ -75,30 +75,16 @@ function MirrorCreate() {
};
} else {
const fileList = formData['fileList'] ?? [];
if (fileList.length === 0) {
message.error('请上传文件');
return;
if (validateUploadFiles(fileList)) {
const file = fileList[0];
params = {
...omit(formData, ['fileList', 'upload_type']),
path: file.response.data.url,
file_size: file.response.data.fileSize,
upload_type: 1,
image_type: 0,
};
}
const file = fileList[0];
if (file.status === 'uploading') {
message.error('请等待文件上传完成');
return;
} else if (file.status === 'error') {
message.error('文件上传失败,请重新上传文件');
return;
}
if (!file.response || !file.response.data) {
message.error('文件上传失败,请重新上传文件');
return;
}

params = {
...omit(formData, ['fileList', 'upload_type']),
path: file.response.data.url,
file_size: file.response.data.fileSize,
upload_type: 1,
image_type: 0,
};
}

const [res] = await to(createMirrorReq(params));
@@ -118,6 +104,7 @@ function MirrorCreate() {
navgite(-1);
};

// 上传前认证
const beforeUpload: UploadProps['beforeUpload'] = () => {
const fileList = form.getFieldValue('fileList');
if (Array.isArray(fileList) && fileList.length >= 1) {
@@ -134,12 +121,13 @@ function MirrorCreate() {
<div>
<Form
name="mirror-create"
labelCol={{ flex: '120px' }}
labelCol={{ flex: '130px' }}
wrapperCol={{ flex: 1 }}
labelAlign="left"
form={form}
initialValues={{ upload_type: CommonTabKeys.Public }}
onFinish={handleSubmit}
size="large"
>
<SubAreaTitle
title="基本信息"
@@ -242,7 +230,7 @@ function MirrorCreate() {
<Row gutter={10}>
<Col span={10}>
<Form.Item label="仓库类型" required>
<span>公网</span>
<span className={styles['mirror-create__content__type']}>公网</span>
</Form.Item>
</Col>
</Row>


+ 2
- 2
react-ui/src/pages/Mirror/list.less View File

@@ -3,7 +3,7 @@
&__tabs-container {
height: 50px;
padding-left: 27px;
background-image: url('../../assets/img/page-title-bg.png');
background-image: url(@/assets/img/page-title-bg.png);
}

&__content {
@@ -20,7 +20,7 @@
}

&__table {
height: calc(100% - 34px - 28px);
height: calc(100% - 32px - 28px);
margin-top: 28px;
}
}


+ 11
- 17
react-ui/src/pages/Mirror/list.tsx View File

@@ -54,6 +54,7 @@ function MirrorList() {
const [cacheState, setCacheState] = useCacheState();
const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public);
const [searchText, setSearchText] = useState(cacheState?.searchText);
const [inputText, setInputText] = useState(cacheState?.searchText);
const [tableData, setTableData] = useState<MirrorData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>(
@@ -65,11 +66,12 @@ function MirrorList() {

useEffect(() => {
getMirrorList();
}, [activeTab, pagination]);
}, [activeTab, pagination, searchText]);

// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
setSearchText('');
setInputText('');
setPagination({
current: 1,
pageSize: 10,
@@ -78,16 +80,16 @@ function MirrorList() {
setTableData([]);
setActiveTab(value);
};

// 获取镜像列表
const getMirrorList = async (params?: Record<string, any>) => {
const reqParams = {
const getMirrorList = async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
name: searchText,
image_type: activeTab === CommonTabKeys.Public ? 1 : 0,
...params,
};
const [res] = await to(getMirrorListReq(reqParams));
const [res] = await to(getMirrorListReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
@@ -116,10 +118,7 @@ function MirrorList() {

// 搜索
const onSearch: SearchProps['onSearch'] = (value) => {
// 带参数是为了点清除时,searchText 更新不及时的问题
getMirrorList({
name: value,
});
setSearchText(value);
};

// 查看详情
@@ -233,12 +232,7 @@ function MirrorList() {
return (
<div className={styles['mirror-list']}>
<div className={styles['mirror-list__tabs-container']}>
<Tabs
activeKey={activeTab}
items={mirrorTabItems}
onChange={hanleTabChange}
className={styles['model-tabs']}
/>
<Tabs activeKey={activeTab} items={mirrorTabItems} onChange={hanleTabChange} />
</div>
<div className={styles['mirror-list__content']}>
<div className={styles['mirror-list__content__filter']}>
@@ -246,9 +240,9 @@ function MirrorList() {
placeholder="按数据集名称筛选"
allowClear
onSearch={onSearch}
onChange={(e) => setSearchText(e.target.value)}
onChange={(e) => setInputText(e.target.value)}
style={{ width: 300 }}
value={searchText}
value={inputText}
/>
{activeTab === CommonTabKeys.Private && (
<Button


+ 4
- 0
react-ui/src/pages/Model/components/AddModelModal/index.less View File

@@ -0,0 +1,4 @@
.upload-button {
height: 48px;
font-size: 15px;
}

+ 180
- 0
react-ui/src/pages/Model/components/AddModelModal/index.tsx View File

@@ -0,0 +1,180 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { CategoryData } from '@/pages/Dataset/type';
import { addModel } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
Button,
Form,
Input,
Select,
Upload,
UploadFile,
message,
type ModalProps,
type UploadProps,
} from 'antd';
import { omit } from 'lodash';
import { useState } from 'react';
import styles from './index.less';

interface AddModelModalProps extends ModalProps {
typeList: CategoryData[];
tagList: CategoryData[];
onOk: () => void;
}

function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) {
const [uuid] = useState(Date.now());
// 上传组件参数
const uploadProps: UploadProps = {
action: '/api/mmp/models/upload',
headers: {
Authorization: getAccessToken() || '',
},
defaultFileList: [],
};

// 上传请求
const createModel = async (params: any) => {
const [res] = await to(addModel(params));
if (res) {
message.success('创建成功');
onOk?.();
}
};

// 提交
const onFinish = (formData: any) => {
const fileList: UploadFile[] = formData['fileList'] ?? [];
if (validateUploadFiles(fileList)) {
const params = {
...omit(formData, ['fileList']),
models_version_vos: fileList.map((item) => {
const data = item.response?.data?.[0] ?? {};
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
};
createModel(params);
}
};

return (
<KFModal
{...rest}
title="新建模型"
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
>
<Form
name="form"
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
label="模型名称"
name="name"
rules={[
{
required: true,
message: '请输入模型名称!',
},
]}
>
<Input placeholder="请输入模型名称" showCount allowClear maxLength={64} />
</Form.Item>

<Form.Item
label="模型版本"
name="version"
rules={[
{
required: true,
message: '请输入模型版本',
},
]}
>
<Input placeholder="请输入模型版本" allowClear maxLength={64} />
</Form.Item>
<Form.Item
label="模型简介"
name="description"
rules={[
{
required: true,
message: '请输入模型简介',
},
]}
>
<Input.TextArea
placeholder="请输入模型简介"
showCount
maxLength={256}
autoSize={{ minRows: 2, maxRows: 6 }}
allowClear
/>
</Form.Item>
{/* <Form.Item label="可见范围" name="available_range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item> */}
<Form.Item label="模型框架" name="model_type">
<Select
allowClear
placeholder="请选择模型类型"
options={typeList}
fieldNames={{ label: 'name', value: 'id' }}
/>
</Form.Item>
<Form.Item label="模型能力" name="model_tag">
<Select
allowClear
placeholder="请选择模型标签"
options={tagList}
fieldNames={{ label: 'name', value: 'id' }}
/>
</Form.Item>
<Form.Item
label="模型文件"
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[
{
required: true,
message: '请上传模型文件',
},
]}
>
<Upload {...uploadProps} data={{ uuid: uuid }}>
<Button
className={styles['upload-button']}
type="default"
icon={<KFIcon type="icon-shangchuan" />}
>
上传文件
</Button>
</Upload>
</Form.Item>
</Form>
</KFModal>
);
}

export default AddModelModal;

+ 5
- 87
react-ui/src/pages/Model/index.jsx View File

@@ -1,89 +1,7 @@
import { Form, Input, Tabs } from 'antd';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
import PersonalData from './personalData';
import PublicData from './publicData';
// import {getModelList} from '@/services/dataset/index.js'
const { Search } = Input;
const { TabPane } = Tabs;
const leftdataList = [1, 2, 3];
import ResourcePage from '@/pages/Dataset/components/ResourcePage';
import { ResourceType } from '@/pages/Dataset/type';

const Dataset = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
name: null,
});
const navgite = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
// const getModelLists=()=>{
// getModelList(queryFlow).then(ret=>{
// console.log(ret);
// if(ret.code==200){
// setDatasetList(ret.data.content)
// setTotal(ret.data.totalElements)
// }
// })
// }

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = (values) => {};
const routeToIntro = (e, record) => {
e.stopPropagation();
navgite({ pathname: '/dataset/dataset' });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
useEffect(() => {
return () => {};
}, []);
return (
<div className={Styles.datasetBox}>
<div className={Styles.datasetTopBox}></div>
<div className={Styles.datasetAllBox}>
<Tabs defaultActiveKey="1">
<TabPane
tab="模型广场"
key="1"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-shujujiguangchang"></use>
</svg>
}
>
<PublicData />
</TabPane>
<TabPane
tab="个人模型"
key="2"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-gerenshujuji"></use>
</svg>
}
>
<PersonalData />
</TabPane>
</Tabs>
</div>
</div>
);
const ModelPage = () => {
return <ResourcePage resourceType={ResourceType.Model} />;
};
export default Dataset;
export default ModelPage;

+ 2
- 251
react-ui/src/pages/Model/index.less View File

@@ -1,13 +1,3 @@
.datasetTopBox {
display: flex;
align-items: center;
width: 100%;
height: 49px;
padding: 0 30px;
padding-right: 30px;
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;
}
.datasetIntroTopBox {
display: flex;
flex-direction: column;
@@ -78,250 +68,11 @@
}
}
}
.datasetAllBox {
:global {
.ant-tabs-nav .ant-tabs-nav-wrap {
margin: -48px 0 0 30px;
}
}
}
.plusButton {
margin: 0 18px 0 20px;
}

.datasetCneterBox {
display: flex;
justify-content: space-between;
width: 100%;
height: 87.5vh;

.datasetCneterLeftBox {
width: 340px;
height: 100%;
margin-right: 10px;
padding-top: 15px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.custTab {
display: flex;
height: 32px;
border-bottom: 1px solid #e0eaff;
.tabItem {
width: 52px;
height: 100%;
color: #808080;
font-size: 15px;
text-align: center;
cursor: pointer;
}
}
.leftContentBox {
max-height: 80vh;
padding: 15px 20px;
overflow-x: hidden;
overflow-y: auto;
.itemTitle {
margin-bottom: 15px;
color: #1d1d20;
font-size: 14px;
}
.itemBox {
display: flex;
flex-wrap: wrap;
align-content: start;
width: 110%;
.messageBox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 92px;
height: 62px;
margin: 0 12px 20px 0;
padding: 11px 0px 7px 0px;
color: #1d1d20;
font-size: 12px;
border: 1px solid;
border-color: rgba(22, 100, 255, 0.05);
border-radius: 4px;
cursor: pointer;
.ptIcon {
display: block;
}
.hoverIcon {
display: none;
}
.messageText {
width: 65px;
overflow: hidden;
white-space: nowrap;
text-align: center;
text-overflow: ellipsis;
transition: all 0.2s;
}
}
.messageBox:hover {
background: rgba(22, 100, 255, 0.03);
border: 1px solid;
border-color: #1664ff;
.ptIcon {
display: none;
}
.hoverIcon {
display: block;
}
}
.active {
background: rgba(22, 100, 255, 0.03) !important;
border: 1px solid !important;
border-color: #1664ff !important;
.ptIcon {
display: none !important;
}
.hoverIcon {
display: block !important;
}
}
}
}
}
.datasetCneterRightBox {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: 22px 30px 26px 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.dataSource {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin-bottom: 30px;
color: rgba(29, 29, 32, 0.8);
font-size: 15px;
}
.dataContent {
display: flex;
flex: 1;
flex-wrap: wrap;
align-content: flex-start;
width: 100%;
.dataItem {
position: relative;
width: 23.8%;
height:164px;
margin: 0 20px 25px 0;
background: #ffffff;
border: 1px solid;
border-color: #eaeaea;
border-radius: 4px;
cursor: pointer;
.dropdown{
position: absolute;
right: 20px;
top: 15px;
}
.itemText {
position: absolute;
top: 20px;
left: 20px;
height: 6px;
color: #1d1d20;
font-size: 16px;
font-family: 'Alibaba';
line-height: 0px;
background: linear-gradient(
to right,
rgba(22, 100, 255, 0.3) 0,
rgba(22, 100, 255, 0) 100%
);
}
.itemDescripition {
position: absolute;
top: 57px;
left: 20px;
display: -webkit-box;
padding-right: 28px;
overflow: hidden;
color: #575757;
font-size: 14px;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.itemTime {
position: absolute;
bottom: 22px;
left: 20px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
.itemIcon {
position: absolute;
right: 20px;
bottom: 22px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
}
.dataItem:hover {
border-color: #1664ff;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);
}
.dataItem:hover .itemText {
color: #1664ff;
}
}
}
}
.tipContent{
color: #c73131;
.tipContent {
margin-top: 5px;
}
.modal {
:global {
.ant-modal-content {
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {
margin: 20px 0;
background-color: transparent;
}
.ant-input {
height: 40px;
border-color: #e6e6e6;
}
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
.ant-modal-footer {
display: flex;
justify-content: center;
margin: 40px 0 30px 0;
}
.ant-btn {
width: 110px;
height: 40px;
font-size: 18px;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
border-radius: 10px;
}
.ant-btn-primary {
background: #1664ff;
}
}
color: #c73131;
}

+ 30
- 37
react-ui/src/pages/Model/modelIntro.jsx View File

@@ -1,5 +1,6 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import {
addModelsVersionDetail,
deleteModelVersion,
@@ -8,18 +9,19 @@ import {
getModelVersionsById,
} from '@/services/dataset/index.js';
import { downLoadZip } from '@/utils/downloadfile';
import { modalConfirm } from '@/utils/ui';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd';
import { useParams, useSearchParams } from '@umijs/max';
import { Button, Form, Input, Select, Table, Tabs, Upload, message } from 'antd';
import moment from 'moment';
import { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import Styles from './index.less';
const { Search } = Input;
const { TabPane } = Tabs;

const Dataset = () => {
const props = {
action: '/api/mmp/dataset/upload',
action: '/api/mmp/models/upload',
// headers: {
// 'X-Requested-With': null
// },
@@ -53,9 +55,11 @@ const Dataset = () => {
const [version, setVersion] = useState(null);
const [versionList, setVersionList] = useState([]);
const locationParams = useParams(); //新版本获取路由参数接口
const [searchParams] = useSearchParams();
console.log(locationParams);
const [wordList, setWordList] = useState([]);
const [uuid, setUuid] = useState(Date.now());
const isPublic = searchParams.get('isPublic') === 'true';
const getModelByDetail = () => {
getModelById(locationParams.id).then((ret) => {
console.log(ret);
@@ -76,6 +80,9 @@ const Dataset = () => {
);
setVersion(ret.data[0]);
getModelVersions({ version: ret.data[0], models_id: locationParams.id });
} else {
setVersion(null);
setWordList([]);
}
});
};
@@ -96,18 +103,9 @@ const Dataset = () => {
setIsModalOpen(false);
};
const deleteDataset = () => {
Modal.confirm({
title: (
<div>
<img
src="/assets/images/delete-icon.png"
style={{ width: '120px', marginBottom: '24px' }}
alt=""
/>
<div style={{ color: '#1d1d20', fontSize: '16px' }}>删除后,该模型版本将不可恢复</div>
</div>
),
content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>,
modalConfirm({
title: '删除后,该版本将不可恢复',
content: '是否确认删除?',
okText: '确认',
cancelText: '取消',

@@ -262,15 +260,18 @@ const Dataset = () => {
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
{!isPublic && (
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
)}

<Button
type="default"
className={Styles.plusButton}
@@ -293,19 +294,11 @@ const Dataset = () => {
</TabPane>
</Tabs>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
<KFModal
title={dialogTitle}
image={require('@/assets/img/create-experiment.png')}
width={825}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
@@ -390,7 +383,7 @@ const Dataset = () => {
</Upload>
</Form.Item>
</Form>
</Modal>
</KFModal>
</div>
);
};


+ 0
- 525
react-ui/src/pages/Model/personalData.jsx View File

@@ -1,525 +0,0 @@
import { getAccessToken } from '@/access';
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import KFIcon from '@/components/KFIcon';
import { addModel, deleteModel, getAssetIcon, getModelList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Pagination, Select, Upload, message } from 'antd';
import moment from 'moment';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
const { Search } = Input;

const leftdataList = [1, 2, 3];

const PublicData = () => {
const props = {
action: '/api/mmp/dataset/upload',
// headers: {
// 'X-Requested-With': null
// },
headers: {
Authorization: getAccessToken(),
'X-Requested-With': null,
},
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
form.setFieldsValue({
models_version_vos: fileList.map((item) => {
const data = item.response.data[0];
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
});
}
},
defaultFileList: [],
};
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 20,
name: null,
available_range: 0,
});
const navgite = useNavigate();
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [modelTypeList, setmodelTypeList] = useState([]);
const [modelDirectionList, setmodelDirectionList] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建模型');
const [uuid, setUuid] = useState(Date.now());
const getModelLists = (queryFlow) => {
getModelList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建模型');
setUuid(Date.now());
setIsModalOpen(true);
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setmodelTypeList(ret.data.content.filter((item) => item.category_id == 3));
setmodelDirectionList(ret.data.content.filter((item) => item.category_id == 4));
} else {
setmodelTypeList([]);
setmodelDirectionList([]);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const nameSearch = (values) => {
console.log(values);
getModelLists({ ...queryFlow, name: values });
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = (values) => {
const params = {
...values,
available_range: 0,
};
addModel(values).then((ret) => {
console.log(ret);
getModelLists(queryFlow);
setIsModalOpen(false);
});
};

const chooseModelType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.model_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, model_type: null });
getModelLists({ ...queryFlow, model_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, model_type: item.id });
getModelLists({ ...queryFlow, model_type: item.id });
}

// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseModelTag = (val, item) => {
if (item.id == queryFlow.model_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, model_tag: null });
getModelLists({ ...queryFlow, model_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, model_tag: item.id });
getModelLists({ ...queryFlow, model_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/model/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getModelLists({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getAssetIconList(iconParams);
getModelLists(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>模型框架</div>
<div className={Styles.itemBox}>
{modelTypeList && modelTypeList.length > 0
? modelTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span
className={Styles.messageText}
onClick={(e) => {
chooseModelTag(e, item);
}}
>
{item.name}
</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>模型能力</div>
<div className={Styles.itemBox}>
{modelDirectionList && modelDirectionList.length > 0
? modelDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按模型名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
<Button
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
模型注册
</Button>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div
className={Styles.dataItem}
onClick={(e) => {
routeToIntro(e, item);
}}
>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
{/* <Dropdown
className={Styles.dropdown}
key={item.name}
menu={{
items: [
{
label: '详情',
key: 'detail',
},
{
label: '删除',
key: 'delete',
},
],
onClick: (e) => {
console.log(e);
if (e.key === 'detail') {
routeToIntro(e, item);
} else if (e.key === 'delete') {
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}
},
}}
>
<div>
<img
style={{ width: '17px', marginRight: '6px' }}
src={moreBack}
alt=""
/>
</div>
</Dropdown> */}
,<div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="模型名称"
name="name"
rules={[
{
required: true,
message: '请输入模型名称!',
},
]}
>
<Input placeholder="请输入模型名称" showCount maxLength={64} />
</Form.Item>

<Form.Item
label="模型版本"
name="version"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入模型版本" />
</Form.Item>

<Form.Item
label="模型描述"
name="description"
rules={[
{
required: true,
message: '请输入模型描述!',
},
]}
>
<Input placeholder="请输入模型描述" showCount maxLength={256} />
</Form.Item>
{/* <Form.Item label="可见范围" name="available_range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item> */}
<Form.Item
label="模型框架"
name="model_type"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择模型类型"
options={modelTypeList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item
label="模型能力"
name="model_tag"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择模型标签"
options={modelDirectionList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item label="模型文件" name="models_version_vos">
<Upload {...props} data={{ uuid: uuid }}>
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff', fontSize: '14px' }} />}
>
上传文件
</Button>
</Upload>
</Form.Item>
</Form>
</Modal>
</>
);
};
export default PublicData;

+ 0
- 384
react-ui/src/pages/Model/publicData.jsx View File

@@ -1,384 +0,0 @@
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import { deleteModel, getAssetIcon, getModelList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { Form, Input, Modal, Pagination, Radio, message } from 'antd';
import moment from 'moment';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
const { Search } = Input;
const leftdataList = [1, 2, 3];

const PublicData = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 20,
name: null,
available_range: 1,
});
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [datasetTypeList, setDatasetTypeList] = useState([]);
const [datasetDirectionList, setDatasetDirectionList] = useState([]);
const navgite = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const getModelLists = (queryFlow) => {
getModelList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const nameSearch = (values) => {
console.log(values);
getModelLists({ ...queryFlow, name: values });
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 3));
setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 4));
} else {
setDatasetTypeList([]);
setDatasetDirectionList([]);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const chooseModelType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.model_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, model_type: null });
getModelLists({ ...queryFlow, model_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, model_type: item.id });
getModelLists({ ...queryFlow, model_type: item.id });
}

// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseModelTag = (val, item) => {
if (item.id == queryFlow.model_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, model_tag: null });
getModelLists({ ...queryFlow, model_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, model_tag: item.id });
getModelLists({ ...queryFlow, model_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const onFinish = (values) => {};
const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/model/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getModelLists({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getAssetIconList(iconParams);
getModelLists(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>模型框架</div>
<div className={Styles.itemBox}>
{datasetTypeList && datasetTypeList.length > 0
? datasetTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>模型能力</div>
<div className={Styles.itemBox}>
{datasetDirectionList && datasetDirectionList.length > 0
? datasetDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按数据名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div
className={Styles.dataItem}
onClick={(e) => {
routeToIntro(e, item);
}}
>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="数据名称"
name="name"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据名称" />
</Form.Item>
<Form.Item
label="数据集版本"
name="data_type"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据集版本" />
</Form.Item>
<Form.Item
label="数据描述"
name="description"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据描述" />
</Form.Item>
<Form.Item label="选择流水线" name="description1">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="数据标签"
name="description3"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据标签" />
</Form.Item>
</Form>
</Modal>
</>
);
};
export default PublicData;

+ 0
- 4
react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx View File

@@ -40,7 +40,6 @@ export type SelectorTypeInfo = {
getFiles: (params: any) => Promise<any>;
handleVersionResponse: (res: any) => any[];
modalIcon: string;
buttonIcon: string;
name: string;
litReqParamKey: 'available_range' | 'image_type';
fileReqParamKey: 'models_id' | 'dataset_id';
@@ -71,7 +70,6 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn
handleVersionResponse: (res) => res.data || [],
name: '模型',
modalIcon: modelImg,
buttonIcon: 'local:model-select-button',
litReqParamKey: 'available_range',
fileReqParamKey: 'models_id',
tabItems: [
@@ -92,7 +90,6 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn
handleVersionResponse: (res) => res.data || [],
name: '数据集',
modalIcon: datasetImg,
buttonIcon: 'local:dataset-select-button',
litReqParamKey: 'available_range',
fileReqParamKey: 'dataset_id',
tabItems: [
@@ -115,7 +112,6 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn
[],
name: '镜像',
modalIcon: mirrorImg,
buttonIcon: 'local:mirror-select-button',
litReqParamKey: 'image_type',
fileReqParamKey: 'dataset_id',
tabItems: [


+ 0
- 1
react-ui/src/pages/Pipeline/editPipeline/globalParamsDrawer.tsx View File

@@ -162,7 +162,6 @@ const GlobalParamsDrawer = forwardRef(
)}
</Form.List>
</Form>
{/* //{contextHolder} */}
</Drawer>
);
},


+ 28
- 32
react-ui/src/pages/Pipeline/editPipeline/props.jsx View File

@@ -142,41 +142,37 @@ const Props = forwardRef(({ onParentChange }, ref) => {
type = ResourceSelectorType.Mirror;
break;
}
const { close } = openAntdModal(
ResourceSelectorModal,
{
type,
defaultExpandedKeys: resource ? [resource.id] : [],
defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [],
defaultActiveTab: resource?.activeTab,
onOk: (res) => {
if (res) {
if (type === ResourceSelectorType.Mirror) {
form.setFieldValue(name, res);
} else {
const jsonObj = pick(res, ['id', 'version', 'path']);
const value = JSON.stringify(jsonObj);
form.setFieldValue(name, value);
}

if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(res);
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(res);
}
const { close } = openAntdModal(ResourceSelectorModal, {
type,
defaultExpandedKeys: resource ? [resource.id] : [],
defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [],
defaultActiveTab: resource?.activeTab,
onOk: (res) => {
if (res) {
if (type === ResourceSelectorType.Mirror) {
form.setFieldValue(name, res);
} else {
if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(null);
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(null);
}
form.setFieldValue(name, '');
const jsonObj = pick(res, ['id', 'version', 'path']);
const value = JSON.stringify(jsonObj);
form.setFieldValue(name, value);
}

if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(res);
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(res);
}
close();
},
} else {
if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(null);
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(null);
}
form.setFieldValue(name, '');
}
close();
},
true,
);
});
};

// 获取选择数据集、模型后面按钮 icon


+ 9
- 14
react-ui/src/pages/Pipeline/index.jsx View File

@@ -1,4 +1,5 @@
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import {
addWorkflow,
cloneWorkflow,
@@ -9,12 +10,13 @@ import {
} from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { modalConfirm } from '@/utils/ui';
import { Button, ConfigProvider, Form, Input, Modal, Space, Table, message } from 'antd';
import { Button, ConfigProvider, Form, Input, Space, Table, message } from 'antd';
import classNames from 'classnames';
import momnet from 'moment';
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';

const { TextArea } = Input;
const Pipeline = () => {
const [form] = Form.useForm();
@@ -168,7 +170,7 @@ const Pipeline = () => {
key="clone"
icon={<KFIcon type="icon-fuzhi" />}
onClick={async () => {
Modal.confirm({
modalConfirm({
title: '复制',
content: '确定复制该条流水线吗?',
okText: '确认',
@@ -254,17 +256,10 @@ const Pipeline = () => {
scroll={{ y: 'calc(100% - 55px)' }}
/>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
<KFModal
title={dialogTitle}
image={require('@/assets/img/create-experiment.png')}
width={825}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
@@ -314,7 +309,7 @@ const Pipeline = () => {
/>
</Form.Item>
</Form>
</Modal>
</KFModal>
</div>
);
};


+ 4
- 47
react-ui/src/pages/Pipeline/index.less View File

@@ -8,60 +8,17 @@
padding-right: 30px;
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;

}

.modal {
:global {
.ant-modal-content {
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {
margin: 20px 0;
background-color: transparent;
}
.ant-input {
height: 40px;
border-color: #e6e6e6;
}
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
.ant-modal-footer {
display: flex;
justify-content: center;
margin: 40px 0 30px 0;
}
.ant-btn {
width: 110px;
height: 40px;
font-size: 18px;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
border-radius: 10px;
}
.ant-btn-primary {
background: #1664ff;
}
}
}
.PipelineBox{
.PipelineBox {
height: calc(100% - 20px);
.PipelineTable{
.PipelineTable {
height: calc(100% - 60px);
:global{
.ant-table-wrapper .ant-table{
:global {
.ant-table-wrapper .ant-table {
// overflow-y: auto;
height: calc(100% - 48px);
}
}
}

}


+ 6
- 7
react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx View File

@@ -35,10 +35,10 @@ function AssetsManagement() {
title: '组件',
value: component,
},
{
title: '代码配置',
value: 0,
},
// {
// title: '代码配置',
// value: 0,
// },
{
title: '流水线模版',
value: workflow,
@@ -64,9 +64,8 @@ function AssetsManagement() {
]}
/>
</Flex>

<div className={styles['assets-management__increase']}>今日新增数量:5</div>
<Flex justify="space-between" gap="22px 0" wrap="wrap">
{/* <div className={styles['assets-management__increase']}>今日新增数量:5</div> */}
<Flex gap="22px 0" wrap="wrap" style={{ marginTop: '30px' }}>
{assetCounts.map((item, index) => (
<div className={styles['assets-management__summary']} key={index}>
<div className={styles['assets-management__summary__title']}>{item.title}</div>


+ 1
- 1
react-ui/src/pages/Workspace/components/ExperimentChart/index.less View File

@@ -2,6 +2,6 @@
width: 295px;
min-width: 295px;
height: 140px;
background-color: @workspace-background;
background: @workspace-background;
border-radius: 4px;
}

+ 6
- 5
react-ui/src/pages/Workspace/components/ExperimentChart/index.tsx View File

@@ -140,6 +140,7 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) {
itemStyle: {
borderRadius: 3,
},
minAngle: 5,
label: {
show: false,
},
@@ -152,11 +153,11 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) {
show: false,
},
data: [
{ value: chartData.Failed, name: '失败' },
{ value: chartData.Succeeded, name: '成功' },
{ value: chartData.Terminated, name: '中止' },
{ value: chartData.Pending, name: '等待' },
{ value: chartData.Running, name: '运行中' },
{ value: chartData.Failed > 0 ? chartData.Failed : null, name: '失败' },
{ value: chartData.Succeeded > 0 ? chartData.Succeeded : null, name: '成功' },
{ value: chartData.Terminated > 0 ? chartData.Terminated : null, name: '中止' },
{ value: chartData.Pending > 0 ? chartData.Pending : null, name: '等待' },
{ value: chartData.Running > 0 ? chartData.Running : null, name: '运行中' },
],
},
{


+ 4
- 4
react-ui/src/pages/Workspace/components/ExperimentTable/index.less View File

@@ -3,7 +3,7 @@
min-width: 500px;
height: 140px;
padding: 12px 24px;
background-color: @workspace-background;
background: @workspace-background;
border-radius: 4px;

&__header {
@@ -36,15 +36,15 @@
}

&__duration {
width: 25%;
width: 30%;
}

&__date {
width: 35%;
width: calc(50% - 60px);
}

&__operation {
width: 20%;
width: 60px;

:global {
.ant-btn-link {


+ 2
- 6
react-ui/src/pages/Workspace/components/TotalStatistics/index.less View File

@@ -4,7 +4,7 @@
justify-content: center;
width: 400px;
height: 140px;
background-color: @workspace-background;
background: @workspace-background;
border-radius: 4px;

&__icon {
@@ -26,11 +26,7 @@
left: 0;
width: 79px;
height: 6px;
background-color: linear-gradient(
87.07deg,
rgba(22, 100, 255, 0.6) 0%,
rgba(22, 100, 255, 0) 100%
);
background: linear-gradient(87.07deg, rgba(22, 100, 255, 0.6) 0%, rgba(22, 100, 255, 0) 100%);
}

&__count {


+ 1
- 7
react-ui/src/pages/Workspace/components/UserSpace/index.less View File

@@ -20,8 +20,7 @@
&__avatar {
position: relative;
top: -28px;
width: 56px;
height: 56px;
background-color: white;
}

&__name {
@@ -56,10 +55,5 @@
background-color: rgba(153, 153, 153, 0.13);
border-radius: 50%;
}

&__user {
width: 36px;
height: 36px;
}
}
}

+ 15
- 5
react-ui/src/pages/Workspace/components/UserSpace/index.tsx View File

@@ -1,5 +1,5 @@
import { useModel } from '@umijs/max';
import { Divider, Flex, Space } from 'antd';
import { Avatar, Divider, Flex, Space } from 'antd';
import styles from './index.less';

type UserSpaceProps = {
@@ -14,7 +14,14 @@ function UserSpace({ users = [] }: UserSpaceProps) {
<div className={styles['user-space']}>
<div className={styles['user-space__title']}>工作空间管理</div>
<div style={{ padding: '0 20px' }}>
<img className={styles['user-space__avatar']} src={currentUser?.avatar} alt="" />
<Avatar
size={56}
shape="circle"
className={styles['user-space__avatar']}
src={currentUser?.avatar}
alt=""
icon={<img src={require('@/assets/img/avatar-default.png')} width={56} height={56} />}
></Avatar>
<div className={styles['user-space__name']}>{currentUser?.nickName}</div>
<div className={styles['user-space__role']}>{currentUser?.roleNames?.[0]?.roleName}</div>
<Divider
@@ -29,12 +36,15 @@ function UserSpace({ users = [] }: UserSpaceProps) {
<Flex align="center" gap={12} wrap="wrap">
{users?.map((item, index) => {
return (
<img
className={styles['user-space__participant__user']}
<Avatar
key={index}
size={36}
src={require(`@/assets/img/user-avatar/${index + 1}.png`)}
alt=""
/>
icon={
<img src={require('@/assets/img/avatar-default.png')} width={36} height={36} />
}
></Avatar>
);
})}
</Flex>


+ 2
- 1
react-ui/src/pages/Workspace/index.less View File

@@ -2,9 +2,10 @@
height: 100%;
padding: 20px 30px 10px;
overflow-y: auto;
background-color: linear-gradient(#ecf2fe, #f9fafb);
background: linear-gradient(#ecf2fe, #f9fafb);

&__overview {
gap: 15px;
margin-bottom: 16px;
padding: 20px 30px;
background-color: white;


+ 2
- 15
react-ui/src/requestConfig.ts View File

@@ -25,21 +25,6 @@ export const requestConfig: RequestConfig = {
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
}
// const expireTime = getTokenExpireTime();
// if (expireTime) {
// const left = Number(expireTime) - new Date().getTime();
// const refreshToken = getRefreshToken();
// if (left < 0 && refreshToken) {
// clearSessionToken();
// } else {
// const accessToken = getAccessToken();
// if (accessToken) {
// headers['Authorization'] = `Bearer ${accessToken}`;
// }
// }
// } else {
// clearSessionToken();
// }
}
return { url, options };
},
@@ -55,6 +40,8 @@ export const requestConfig: RequestConfig = {
clearSessionToken();
setRemoteMenu(null);
gotoLoginPage(false);
message.error('请重新登录');
return Promise.reject(response);
} else {
message.error(data?.msg ?? '请求失败');
return Promise.reject(response);


+ 59
- 0
react-ui/src/styles/menu.less View File

@@ -0,0 +1,59 @@
.ant-menu-item,
.ant-menu-submenu {
.kf-menu-item {
display: flex;
align-items: center;
justify-content: flex-start;
font-size: 16px;

.anticon.kf-menu-item__default-icon {
display: inline !important;
opacity: 1;
}

.anticon.kf-menu-item__active-icon {
display: none !important;
margin-left: 0 !important;
}

&:hover {
.anticon.kf-menu-item__default-icon {
display: none !important;
}
.anticon.kf-menu-item__active-icon {
display: inline !important;
opacity: 1;
}
}
}
}

.ant-menu-item.ant-menu-item-selected,
.ant-menu-submenu.ant-menu-submenu-selected {
.kf-menu-item {
.anticon.kf-menu-item__default-icon {
display: none !important;
}

.anticon.kf-menu-item__active-icon {
display: inline !important;
opacity: 1;
}
}
}

.ant-pro-base-menu-vertical-collapsed {
.kf-menu-item {
justify-content: center;

.kf-menu-item__name {
display: none !important;
}
}
}

.ant-menu-submenu {
.ant-menu-submenu-title:hover {
color: #1664ff !important;
}
}

+ 22
- 1
react-ui/src/styles/theme.less View File

@@ -36,16 +36,33 @@
@font-size: 15px;
@font-size-title: 18px;
@font-size-content: 16px;
@font-size-input: 14px;
@font-size-input-lg: 16px;

// 函数
.addAlpha(@color, @alpha) {
@red: red(@color);
@green: green(@color);
@blue: blue(@color);

@result: rgba(@red, @green, @blue, @alpha);
}

// 混合
.singleLine() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
}

.multiLine(@line) {
display: -webkit-box;
overflow: hidden;
word-break: break-all;
-webkit-box-orient: vertical;
-webkit-line-clamp: @line;
}

// 导出变量
:export {
primaryColor: @primary-color;
@@ -55,5 +72,9 @@
textColor: @text-color;
textColorSecondary: @text-color-secondary;
fontSize: @font-size;
fontSizeTitle: @font-size-title;
fontSizeContent: @font-size-content;
fontSizeInput: @font-size-input;
fontSizeInputLg: @font-size-input-lg;
siderBGColor: @sider-background-color;
}

+ 31
- 0
react-ui/src/utils/menuRender.tsx View File

@@ -0,0 +1,31 @@
import KFIcon from '@/components/KFIcon';
import { MenuDataItem } from '@ant-design/pro-components';
import { Link } from '@umijs/max';

export const menuItemRender = (isSubMenu: boolean) => {
return (item: MenuDataItem) => {
const defaultIcon: string = item.icon as string;
const activeIcon = defaultIcon + '-active';
const hasParent = item.pro_layout_parentKeys?.length > 0;
const childen = (
<>
{!hasParent && defaultIcon && (
<>
<KFIcon type={defaultIcon} font={17} className="kf-menu-item__default-icon" />
<KFIcon type={activeIcon} font={17} className="kf-menu-item__active-icon" />
</>
)}
<span className="kf-menu-item__name">{item.name}</span>
</>
);
if (isSubMenu) {
return <div className="kf-menu-item">{childen}</div>;
} else {
return (
<Link to={item.path || ''} className="kf-menu-item">
{childen}
</Link>
);
}
};
};

+ 27
- 1
react-ui/src/utils/ui.tsx View File

@@ -6,7 +6,7 @@
import { PageEnum } from '@/enums/pagesEnums';
import themes from '@/styles/theme.less';
import { history } from '@umijs/max';
import { Modal, type ModalFuncProps, type UploadFile } from 'antd';
import { Modal, message, type ModalFuncProps, type UploadFile } from 'antd';

// 自定义 Confirm 弹框
export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) {
@@ -54,6 +54,7 @@ export const gotoLoginPage = (toHome: boolean = true) => {
const urlParams = new URLSearchParams();
urlParams.append('redirect', pathname + search);
const newSearch = toHome && pathname && pathname !== PageEnum.LOGIN ? '' : urlParams.toString();
console.log('gotoLoginPage', pathname, search);
if (window.location.pathname !== PageEnum.LOGIN) {
history.replace({
pathname: PageEnum.LOGIN,
@@ -61,3 +62,28 @@ export const gotoLoginPage = (toHome: boolean = true) => {
});
}
};

// 上传文件校验
export const validateUploadFiles = (files: UploadFile[], required: boolean = true): boolean => {
if (required && files.length === 0) {
message.error('请上传文件');
return false;
}

const hasError = files.some((file) => {
if (file.status === 'uploading') {
message.error('请等待文件上传完成');
return true;
}
if (file.status === 'error') {
message.error('存在上传失败的文件,请删除后重新上传文件');
return true;
}
if (!file.response || file.response.code !== 200 || !file.response.data) {
message.error('存在上传失败的文件,请删除后重新上传文件');
return true;
}
return false;
});
return !hasError;
};

Loading…
Cancel
Save