| @@ -0,0 +1,35 @@ | |||||
| .kf-basic-info { | |||||
| display: flex; | |||||
| flex-direction: row; | |||||
| flex-wrap: wrap; | |||||
| gap: 20px 40px; | |||||
| align-items: flex-start; | |||||
| width: 80%; | |||||
| &__item { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| width: calc(50% - 20px); | |||||
| font-size: 16px; | |||||
| line-height: 1.6; | |||||
| &__label { | |||||
| width: 100px; | |||||
| color: @text-color-secondary; | |||||
| text-align: justify; | |||||
| text-align-last: justify; | |||||
| } | |||||
| &__value { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| color: @text-color; | |||||
| word-break: break-all; | |||||
| &::before { | |||||
| margin-right: 16px; | |||||
| content: ':'; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,27 @@ | |||||
| import './index.less'; | |||||
| export type BasicInfoData = { | |||||
| label: string; | |||||
| value?: any; | |||||
| format?: (_value: any) => string; | |||||
| }; | |||||
| type BasicInfoProps = { | |||||
| datas: BasicInfoData[]; | |||||
| }; | |||||
| function BasicInfo({ datas }: BasicInfoProps) { | |||||
| return ( | |||||
| <div className="kf-basic-info"> | |||||
| {datas.map((item) => ( | |||||
| <div className="kf-basic-info__item" key={item.label}> | |||||
| <div className="kf-basic-info__item__label">{item.label}</div> | |||||
| <div className="kf-basic-info__item__value"> | |||||
| {item.format ? item.format(item.value) ?? '--' : item.value ?? '--'} | |||||
| </div> | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default BasicInfo; | |||||
| @@ -0,0 +1,40 @@ | |||||
| .kf-empty { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| width: 100%; | |||||
| &__image { | |||||
| width: 475px; | |||||
| height: 292px; | |||||
| } | |||||
| &__title { | |||||
| margin-top: 15px; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: 30px; | |||||
| letter-spacing: 5px; | |||||
| text-align: center; | |||||
| } | |||||
| &__content { | |||||
| margin-top: 15px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 15px; | |||||
| white-space: pre-line; | |||||
| text-align: center; | |||||
| } | |||||
| &__footer { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| margin-top: 20px; | |||||
| &__back-btn { | |||||
| height: 32px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| import { Button } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import './index.less'; | |||||
| export enum EmptyType { | |||||
| NoData = 'NoData', | |||||
| NotFound = 'NotFound', | |||||
| Developing = 'Developing', | |||||
| } | |||||
| type EmptyProps = { | |||||
| className?: string; | |||||
| style?: React.CSSProperties; | |||||
| type: EmptyType; | |||||
| title?: string; | |||||
| content?: string; | |||||
| hasFooter?: boolean; | |||||
| footer?: () => React.ReactNode; | |||||
| backTitle?: string; | |||||
| onBack?: () => void; | |||||
| }; | |||||
| function getEmptyImage(type: EmptyType) { | |||||
| switch (type) { | |||||
| case EmptyType.NoData: | |||||
| return require('@/assets/img/no-data.png'); | |||||
| case EmptyType.NotFound: | |||||
| return require('@/assets/img/404.png'); | |||||
| case EmptyType.Developing: | |||||
| return require('@/assets/img/missing-back.png'); | |||||
| } | |||||
| } | |||||
| function KFEmpty({ | |||||
| className, | |||||
| style, | |||||
| type, | |||||
| title, | |||||
| content, | |||||
| hasFooter = false, | |||||
| footer, | |||||
| backTitle = '返回', | |||||
| onBack, | |||||
| }: EmptyProps) { | |||||
| const image = getEmptyImage(type); | |||||
| return ( | |||||
| <div className={classNames('kf-empty', className)} style={style}> | |||||
| <img className="kf-empty__image" src={image} /> | |||||
| <div className="kf-empty__title">{title}</div> | |||||
| <div className="kf-empty__content">{content}</div> | |||||
| {hasFooter && ( | |||||
| <div className="kf-empty__footer"> | |||||
| {footer ? ( | |||||
| footer() | |||||
| ) : ( | |||||
| <Button className="kf-empty__footer__back-btn" type="primary" onClick={onBack}> | |||||
| {backTitle} | |||||
| </Button> | |||||
| )} | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default KFEmpty; | |||||
| @@ -4,7 +4,7 @@ | |||||
| right: 0; | right: 0; | ||||
| bottom: 0; | bottom: 0; | ||||
| left: 0; | left: 0; | ||||
| z-index: 1000; | |||||
| z-index: 1001; | |||||
| display: flex; | display: flex; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| align-items: center; | align-items: center; | ||||
| @@ -11,6 +11,7 @@ import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | |||||
| import './index.less'; | import './index.less'; | ||||
| export { requiredValidator, type ParameterInputObject } from '../ParameterInput'; | export { requiredValidator, type ParameterInputObject } from '../ParameterInput'; | ||||
| export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse }; | |||||
| type ResourceSelectProps = { | type ResourceSelectProps = { | ||||
| type: ResourceSelectorType; | type: ResourceSelectorType; | ||||
| @@ -116,6 +116,10 @@ | |||||
| } | } | ||||
| } | } | ||||
| .ant-input.ant-input-disabled { | |||||
| height: 46px; | |||||
| } | |||||
| // 选择框高度为46px | // 选择框高度为46px | ||||
| .ant-select-single { | .ant-select-single { | ||||
| height: 46px; | height: 46px; | ||||
| @@ -1,18 +1,20 @@ | |||||
| import { history } from '@umijs/max'; | |||||
| import { Button, Result } from 'antd'; | |||||
| import React from 'react'; | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| const NoFoundPage: React.FC = () => ( | |||||
| <Result | |||||
| status="404" | |||||
| title="404" | |||||
| subTitle="Sorry, the page you visited does not exist." | |||||
| extra={ | |||||
| <Button type="primary" onClick={() => history.push('/')}> | |||||
| Back Home | |||||
| </Button> | |||||
| } | |||||
| /> | |||||
| ); | |||||
| const NoFoundPage = () => { | |||||
| const navigate = useNavigate(); | |||||
| return ( | |||||
| <KFEmpty | |||||
| style={{ height: '100vh' }} | |||||
| type={EmptyType.NotFound} | |||||
| title="404" | |||||
| content={'很抱歉,您访问的页面地址有误,\n或者该页面不存在。'} | |||||
| hasFooter={true} | |||||
| backTitle="返回首页" | |||||
| onBack={() => navigate('/')} | |||||
| ></KFEmpty> | |||||
| ); | |||||
| }; | |||||
| export default NoFoundPage; | export default NoFoundPage; | ||||
| @@ -1,9 +1,10 @@ | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig'; | import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { App, Button, Empty, Input, Pagination, PaginationProps } from 'antd'; | |||||
| import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal'; | import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal'; | ||||
| import CodeConfigItem from '../components/CodeConfigItem'; | import CodeConfigItem from '../components/CodeConfigItem'; | ||||
| @@ -31,7 +32,7 @@ export type ResourceListRef = { | |||||
| }; | }; | ||||
| function CodeConfigList() { | function CodeConfigList() { | ||||
| const [dataList, setDataList] = useState<CodeConfigData[]>([]); | |||||
| const [dataList, setDataList] = useState<CodeConfigData[] | undefined>(undefined); | |||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [pagination, setPagination] = useState<PaginationProps>({ | const [pagination, setPagination] = useState<PaginationProps>({ | ||||
| current: 1, | current: 1, | ||||
| @@ -56,6 +57,9 @@ function CodeConfigList() { | |||||
| if (res && res.data && res.data.content) { | if (res && res.data && res.data.content) { | ||||
| setDataList(res.data.content); | setDataList(res.data.content); | ||||
| setTotal(res.data.totalElements); | setTotal(res.data.totalElements); | ||||
| } else { | |||||
| setDataList([]); | |||||
| setTotal(0); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -117,7 +121,7 @@ function CodeConfigList() { | |||||
| return ( | return ( | ||||
| <div className={styles['code-config-list']}> | <div className={styles['code-config-list']}> | ||||
| <div className={styles['code-config-list__header']}> | <div className={styles['code-config-list__header']}> | ||||
| <span>数据总数:{total}个</span> | |||||
| <span>数据总数:{total} 个</span> | |||||
| <div> | <div> | ||||
| <Input.Search | <Input.Search | ||||
| placeholder="按代码仓库名称筛选" | placeholder="按代码仓库名称筛选" | ||||
| @@ -139,10 +143,10 @@ function CodeConfigList() { | |||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {dataList?.length !== 0 ? ( | |||||
| {dataList && dataList.length !== 0 && ( | |||||
| <> | <> | ||||
| <div className={styles['code-config-list__content']}> | <div className={styles['code-config-list__content']}> | ||||
| {dataList?.map((item) => ( | |||||
| {dataList.map((item) => ( | |||||
| <CodeConfigItem | <CodeConfigItem | ||||
| item={item} | item={item} | ||||
| key={item.id} | key={item.id} | ||||
| @@ -161,10 +165,13 @@ function CodeConfigList() { | |||||
| {...pagination} | {...pagination} | ||||
| /> | /> | ||||
| </> | </> | ||||
| ) : ( | |||||
| <div className={styles['code-config-list__empty']}> | |||||
| <Empty></Empty> | |||||
| </div> | |||||
| )} | |||||
| {dataList && dataList.length === 0 && ( | |||||
| <KFEmpty | |||||
| className={styles['code-config-list__empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| /> | |||||
| )} | )} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -1,6 +1,7 @@ | |||||
| .upload-tip { | .upload-tip { | ||||
| margin-top: 5px; | margin-top: 5px; | ||||
| color: @error-color; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| } | } | ||||
| .upload-button { | .upload-button { | ||||
| @@ -1,9 +1,7 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { addDatesetAndVesion } from '@/services/dataset/index.js'; | |||||
| import { getDictSelectOption } from '@/services/system/dict'; | |||||
| import { addDateset } from '@/services/dataset/index.js'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { | import { | ||||
| @@ -19,7 +17,7 @@ import { | |||||
| type UploadProps, | type UploadProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import { omit } from 'lodash'; | import { omit } from 'lodash'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useState } from 'react'; | |||||
| import { CategoryData } from '../../config'; | import { CategoryData } from '../../config'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -31,15 +29,15 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||||
| // const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||||
| useEffect(() => { | |||||
| getClusterOptions(); | |||||
| }, []); | |||||
| // useEffect(() => { | |||||
| // getClusterOptions(); | |||||
| // }, []); | |||||
| // 上传组件参数 | // 上传组件参数 | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| action: '/api/mmp/dataset/upload', | |||||
| action: '/api/mmp/newdataset/upload', | |||||
| headers: { | headers: { | ||||
| Authorization: getAccessToken() || '', | Authorization: getAccessToken() || '', | ||||
| }, | }, | ||||
| @@ -47,16 +45,16 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| }; | }; | ||||
| // 获取集群版本数据 | // 获取集群版本数据 | ||||
| const getClusterOptions = async () => { | |||||
| const [res] = await to(getDictSelectOption('available_cluster')); | |||||
| if (res) { | |||||
| setClusterOptions(res); | |||||
| } | |||||
| }; | |||||
| // const getClusterOptions = async () => { | |||||
| // const [res] = await to(getDictSelectOption('available_cluster')); | |||||
| // if (res) { | |||||
| // setClusterOptions(res); | |||||
| // } | |||||
| // }; | |||||
| // 上传请求 | // 上传请求 | ||||
| const createDataset = async (params: any) => { | const createDataset = async (params: any) => { | ||||
| const [res] = await to(addDatesetAndVesion(params)); | |||||
| const [res] = await to(addDateset(params)); | |||||
| if (res) { | if (res) { | ||||
| message.success('创建成功'); | message.success('创建成功'); | ||||
| onOk?.(); | onOk?.(); | ||||
| @@ -94,7 +92,13 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| }} | }} | ||||
| destroyOnClose | destroyOnClose | ||||
| > | > | ||||
| <Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off"> | |||||
| <Form | |||||
| name="form" | |||||
| layout="vertical" | |||||
| onFinish={onFinish} | |||||
| initialValues={{ is_public: false }} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | <Form.Item | ||||
| label="数据集名称" | label="数据集名称" | ||||
| name="name" | name="name" | ||||
| @@ -106,7 +110,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={64} /> | |||||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={50} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="数据集版本" | label="数据集版本" | ||||
| @@ -125,7 +129,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| allowClear | allowClear | ||||
| placeholder="请选择数据集分类" | placeholder="请选择数据集分类" | ||||
| options={typeList} | options={typeList} | ||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | optionFilterProp="name" | ||||
| showSearch | showSearch | ||||
| /> | /> | ||||
| @@ -135,14 +139,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| allowClear | allowClear | ||||
| placeholder="请选择研究方向/应用领域" | placeholder="请选择研究方向/应用领域" | ||||
| options={tagList} | options={tagList} | ||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | optionFilterProp="name" | ||||
| showSearch | showSearch | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="集群版本" name="available_cluster"> | |||||
| {/* <Form.Item label="集群版本" name="available_cluster"> | |||||
| <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | ||||
| </Form.Item> | |||||
| </Form.Item> */} | |||||
| <Form.Item | <Form.Item | ||||
| label="数据集简介" | label="数据集简介" | ||||
| name="description" | name="description" | ||||
| @@ -156,15 +160,19 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入数据集简介" | placeholder="请输入数据集简介" | ||||
| showCount | showCount | ||||
| maxLength={256} | |||||
| maxLength={200} | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="选择流水线" name="range"> | |||||
| <Form.Item | |||||
| label="可见性" | |||||
| name="is_public" | |||||
| rules={[{ required: true, message: '请选择可见性' }]} | |||||
| > | |||||
| <Radio.Group> | <Radio.Group> | ||||
| <Radio value="0">仅自己可见</Radio> | |||||
| <Radio value="1">工作空间可见</Radio> | |||||
| <Radio value={false}>私有</Radio> | |||||
| <Radio value={true}>公开</Radio> | |||||
| </Radio.Group> | </Radio.Group> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| @@ -187,7 +195,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| > | > | ||||
| 上传文件 | 上传文件 | ||||
| </Button> | </Button> | ||||
| <div className={styles['upload-tip']}>只允许上传.zip,.tgz格式文件</div> | |||||
| <div className={styles['upload-tip']}>只允许上传 .zip 和 .tgz 格式文件</div> | |||||
| </Upload> | </Upload> | ||||
| </Form.Item> | </Form.Item> | ||||
| </Form> | </Form> | ||||
| @@ -21,14 +21,16 @@ import styles from '../AddDatasetModal/index.less'; | |||||
| interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> { | interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| resourceType: ResourceType; | resourceType: ResourceType; | ||||
| resourceId: number; | resourceId: number; | ||||
| initialName: string; | |||||
| identifier: string; | |||||
| resoureName: string; | |||||
| onOk: () => void; | onOk: () => void; | ||||
| } | } | ||||
| function AddVersionModal({ | function AddVersionModal({ | ||||
| resourceType, | resourceType, | ||||
| resourceId, | resourceId, | ||||
| initialName, | |||||
| resoureName, | |||||
| identifier, | |||||
| onOk, | onOk, | ||||
| ...rest | ...rest | ||||
| }: AddVersionModalProps) { | }: AddVersionModalProps) { | ||||
| @@ -58,17 +60,20 @@ function AddVersionModal({ | |||||
| const onFinish = (formData: any) => { | const onFinish = (formData: any) => { | ||||
| const fileList: UploadFile[] = formData['fileList'] ?? []; | const fileList: UploadFile[] = formData['fileList'] ?? []; | ||||
| if (validateUploadFiles(fileList)) { | if (validateUploadFiles(fileList)) { | ||||
| const otherParams = omit(formData, ['fileList']); | |||||
| const params = fileList.map((item) => { | |||||
| const dataset_version_vos = fileList.map((item) => { | |||||
| const data = item.response?.data?.[0] ?? {}; | const data = item.response?.data?.[0] ?? {}; | ||||
| return { | return { | ||||
| ...otherParams, | |||||
| [config.idParamKey]: resourceId, | |||||
| file_name: data.fileName, | file_name: data.fileName, | ||||
| file_size: data.fileSize, | file_size: data.fileSize, | ||||
| url: data.url, | url: data.url, | ||||
| }; | }; | ||||
| }); | }); | ||||
| const params = { | |||||
| id: resourceId, | |||||
| identifier, | |||||
| dataset_version_vos, | |||||
| ...omit(formData, 'fileList'), | |||||
| }; | |||||
| createDatasetVersion(params); | createDatasetVersion(params); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -90,7 +95,7 @@ function AddVersionModal({ | |||||
| name="form" | name="form" | ||||
| layout="vertical" | layout="vertical" | ||||
| initialValues={{ | initialValues={{ | ||||
| name: initialName, | |||||
| name: resoureName, | |||||
| }} | }} | ||||
| onFinish={onFinish} | onFinish={onFinish} | ||||
| autoComplete="off" | autoComplete="off" | ||||
| @@ -115,13 +120,21 @@ function AddVersionModal({ | |||||
| required: true, | required: true, | ||||
| message: `请输入${name}版本`, | message: `请输入${name}版本`, | ||||
| }, | }, | ||||
| { | |||||
| validator: (_rule, value) => { | |||||
| if (value === 'master') { | |||||
| return Promise.reject(`版本号不能为 master`); | |||||
| } | |||||
| return Promise.resolve(); | |||||
| }, | |||||
| }, | |||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear /> | <Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="版本描述" | label="版本描述" | ||||
| name="description" | |||||
| name="version_desc" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| @@ -158,7 +171,7 @@ function AddVersionModal({ | |||||
| 上传文件 | 上传文件 | ||||
| </Button> | </Button> | ||||
| {resourceType === ResourceType.Dataset && ( | {resourceType === ResourceType.Dataset && ( | ||||
| <div className={styles['upload-tip']}>只允许上传.zip格式文件</div> | |||||
| <div className={styles['upload-tip']}>只允许上传 .zip 格式文件</div> | |||||
| )} | )} | ||||
| </Upload> | </Upload> | ||||
| </Form.Item> | </Form.Item> | ||||
| @@ -0,0 +1,59 @@ | |||||
| .resource-info { | |||||
| height: 100%; | |||||
| &__top { | |||||
| width: 100%; | |||||
| height: 125px; | |||||
| margin-bottom: 10px; | |||||
| padding: 20px 30px; | |||||
| background-image: url(@/assets/img/dataset-intro-top.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| &__name { | |||||
| margin-right: 10px; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: 20px; | |||||
| } | |||||
| &__tag { | |||||
| padding: 4px 10px; | |||||
| color: @primary-color; | |||||
| font-size: 14px; | |||||
| background: .addAlpha(@primary-color, 0.1) []; | |||||
| border-radius: 4px; | |||||
| } | |||||
| :global { | |||||
| .ant-btn-dangerous { | |||||
| background-color: transparent !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__bottom { | |||||
| height: calc(100% - 135px); | |||||
| padding: 8px 30px 20px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| :global { | |||||
| .ant-tabs { | |||||
| height: 100%; | |||||
| .ant-tabs-content-holder { | |||||
| height: 100%; | |||||
| .ant-tabs-content { | |||||
| height: 100%; | |||||
| .ant-tabs-tabpane { | |||||
| height: 100%; | |||||
| overflow-y: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,237 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-09-06 09:23:15 | |||||
| * @Description: 数据集、模型详情 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { | |||||
| ResourceData, | |||||
| ResourceType, | |||||
| ResourceVersionData, | |||||
| resourceConfig, | |||||
| } from '@/pages/Dataset/config'; | |||||
| import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { getSessionStorageItem, resourceItemKey } from '@/utils/sessionStorage'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { App, Button, Flex, Select, Tabs } from 'antd'; | |||||
| import { pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import AddVersionModal from '../AddVersionModal'; | |||||
| import ResourceIntro from '../ResourceIntro'; | |||||
| import ResourceVersion from '../ResourceVersion'; | |||||
| import styles from './index.less'; | |||||
| // 这里值小写是因为值会写在 url 中 | |||||
| export enum ResourceInfoTabKeys { | |||||
| Introduction = 'introduction', // 简介 | |||||
| Version = 'version', // 版本 | |||||
| Evolution = 'evolution', // 演化 | |||||
| } | |||||
| type ResourceInfoProps = { | |||||
| resourceType: ResourceType; | |||||
| }; | |||||
| const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| const [info, setInfo] = useState<ResourceData>({} as ResourceData); | |||||
| const locationParams = useParams(); | |||||
| const [searchParams] = useSearchParams(); | |||||
| // 模型演化传入的 tab | |||||
| const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction; | |||||
| // 模型演化传入的版本 | |||||
| let versionParam = searchParams.get('version'); | |||||
| const [versionList, setVersionList] = useState<ResourceVersionData[]>([]); | |||||
| const [version, setVersion] = useState<string | undefined>(undefined); | |||||
| const [activeTab, setActiveTab] = useState<string>(defaultTab); | |||||
| const resourceId = Number(locationParams.id); | |||||
| const config = resourceConfig[resourceType]; | |||||
| const typeName = config.name; // 数据集/模型 | |||||
| const { message } = App.useApp(); | |||||
| useEffect(() => { | |||||
| const info = getSessionStorageItem(resourceItemKey, true); | |||||
| if (info) { | |||||
| setInfo(info); | |||||
| getVersionList(pick(info, ['owner', 'identifier'])); | |||||
| } | |||||
| }, [resourceId]); | |||||
| useEffect(() => { | |||||
| if (version) { | |||||
| getResourceDetail({ | |||||
| ...pick(info, ['owner', 'name', 'id', 'identifier']), | |||||
| version, | |||||
| }); | |||||
| } | |||||
| }, [version]); | |||||
| // 获取详情 | |||||
| const getResourceDetail = async (params: { | |||||
| owner: string; | |||||
| name: string; | |||||
| id: number; | |||||
| identifier: string; | |||||
| version?: string; | |||||
| }) => { | |||||
| const request = config.getInfo; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| setInfo(res.data); | |||||
| } | |||||
| }; | |||||
| // 获取版本列表 | |||||
| const getVersionList = async (params: { owner: string; identifier: string }) => { | |||||
| const request = config.getVersions; | |||||
| const [res] = await to(request(params)); | |||||
| if (res && res.data && res.data.length > 0) { | |||||
| setVersionList(res.data); | |||||
| if ( | |||||
| versionParam && | |||||
| res.data.find((item: ResourceVersionData) => item.name === versionParam) | |||||
| ) { | |||||
| setVersion(versionParam); | |||||
| versionParam = null; | |||||
| } else { | |||||
| setVersion(res.data[0].name); | |||||
| } | |||||
| } else { | |||||
| setVersion(undefined); | |||||
| } | |||||
| }; | |||||
| // 新建版本 | |||||
| const showModal = () => { | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: resourceType, | |||||
| resourceId: resourceId, | |||||
| resoureName: info.name, | |||||
| identifier: info.identifier, | |||||
| onOk: () => { | |||||
| getVersionList(pick(info, ['owner', 'identifier'])); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 版本变化 | |||||
| const handleVersionChange = (value: string) => { | |||||
| setVersion(value); | |||||
| }; | |||||
| // 删除版本 | |||||
| const deleteVersion = async () => { | |||||
| const request = config.deleteVersion; | |||||
| const params = { | |||||
| identifier: info.identifier, | |||||
| owner: info.owner, | |||||
| version, | |||||
| }; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| setVersion(undefined); | |||||
| getVersionList(pick(info, ['owner', 'identifier'])); | |||||
| } | |||||
| }; | |||||
| // 处理删除 | |||||
| const hanldeDelete = () => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| deleteVersion(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const items = [ | |||||
| { | |||||
| key: ResourceInfoTabKeys.Introduction, | |||||
| label: `${typeName}简介`, | |||||
| icon: <KFIcon type="icon-moxingjianjie" />, | |||||
| children: <ResourceIntro resourceType={resourceType} info={info}></ResourceIntro>, | |||||
| }, | |||||
| { | |||||
| key: ResourceInfoTabKeys.Version, | |||||
| label: `${typeName}文件`, | |||||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||||
| children: <ResourceVersion resourceType={resourceType} info={info}></ResourceVersion>, | |||||
| }, | |||||
| ]; | |||||
| if (resourceType === ResourceType.Model) { | |||||
| items.push({ | |||||
| key: ResourceInfoTabKeys.Evolution, | |||||
| label: `模型演化`, | |||||
| icon: <KFIcon type="icon-moxingyanhua1" />, | |||||
| children: ( | |||||
| <ModelEvolution | |||||
| resourceId={resourceId} | |||||
| versionList={versionList} | |||||
| version={version} | |||||
| isActive={activeTab === ResourceInfoTabKeys.Evolution} | |||||
| onVersionChange={handleVersionChange} | |||||
| ></ModelEvolution> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData; | |||||
| const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData; | |||||
| return ( | |||||
| <div className={styles['resource-info']}> | |||||
| <div className={styles['resource-info__top']}> | |||||
| <Flex align="center" gap={10} style={{ marginBottom: '20px' }}> | |||||
| <div className={styles['resource-info__top__name']}>{info.name}</div> | |||||
| {info[infoTypePropertyName] && ( | |||||
| <div className={styles['resource-info__top__tag']}> | |||||
| {(info[infoTypePropertyName] as string) || '--'} | |||||
| </div> | |||||
| )} | |||||
| {info[infoTagPropertyName] && ( | |||||
| <div className={styles['resource-info__top__tag']}> | |||||
| {(info[infoTagPropertyName] as string) || '--'} | |||||
| </div> | |||||
| )} | |||||
| </Flex> | |||||
| <Flex align="center"> | |||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ width: '160px', marginRight: '20px' }} | |||||
| value={version} | |||||
| onChange={handleVersionChange} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| options={versionList} | |||||
| /> | |||||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 创建新版本 | |||||
| </Button> | |||||
| <Button | |||||
| type="default" | |||||
| style={{ marginLeft: 'auto', marginRight: 0 }} | |||||
| onClick={hanldeDelete} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| disabled={!version} | |||||
| danger | |||||
| > | |||||
| 删除版本 | |||||
| </Button> | |||||
| </Flex> | |||||
| </div> | |||||
| <div className={styles['resource-info__bottom']}> | |||||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ResourceInfo; | |||||
| @@ -1,65 +1,10 @@ | |||||
| .resource-intro { | .resource-intro { | ||||
| height: 100%; | |||||
| &__top { | |||||
| width: 100%; | |||||
| margin-top: 24px; | |||||
| &__basic { | |||||
| width: 100%; | width: 100%; | ||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 20px 30px 0; | |||||
| background-image: url(@/assets/img/dataset-intro-top.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| &__name { | |||||
| margin-bottom: 12px; | |||||
| color: @text-color; | |||||
| font-size: 20px; | |||||
| } | |||||
| &__tag { | |||||
| margin-right: 10px; | |||||
| padding: 4px 10px; | |||||
| color: @primary-color; | |||||
| font-size: 14px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | } | ||||
| &__bottom { | |||||
| height: calc(100% - 120px); | |||||
| padding: 8px 30px 20px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| :global { | |||||
| .ant-tabs { | |||||
| height: 100%; | |||||
| .ant-tabs-content-holder { | |||||
| height: 100%; | |||||
| .ant-tabs-content { | |||||
| height: 100%; | |||||
| .ant-tabs-tabpane { | |||||
| height: 100%; | |||||
| overflow-y: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__title { | |||||
| margin: 30px 0 10px; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: @font-size; | |||||
| } | |||||
| &__intro { | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| &__usage { | |||||
| width: 100%; | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,156 +1,78 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { Flex, Tabs } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { ResourceData, ResourceType, resourceConfig } from '../../config'; | |||||
| import ResourceVersion from '../ResourceVersion'; | |||||
| import BasicInfo, { BasicInfoData } from '@/components/BasicInfo'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { ResourceData, ResourceType } from '@/pages/Dataset/config'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| // 这里值小写是因为值会写在 url 中 | |||||
| export enum ResourceInfoTabKeys { | |||||
| Introduction = 'introduction', // 简介 | |||||
| Version = 'version', // 版本 | |||||
| Evolution = 'evolution', // 演化 | |||||
| } | |||||
| type ResourceIntroProps = { | type ResourceIntroProps = { | ||||
| resourceType: ResourceType; | resourceType: ResourceType; | ||||
| info: ResourceData; | |||||
| }; | }; | ||||
| const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| const [info, setInfo] = useState<ResourceData>({} as ResourceData); | |||||
| const locationParams = useParams(); | |||||
| const [searchParams] = useSearchParams(); | |||||
| const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction; | |||||
| let versionParam = searchParams.get('version'); | |||||
| const [versionList, setVersionList] = useState([]); | |||||
| const [version, setVersion] = useState<string | undefined>(undefined); | |||||
| const [activeTab, setActiveTab] = useState<string>(defaultTab); | |||||
| const resourceId = Number(locationParams.id); | |||||
| const config = resourceConfig[resourceType]; | |||||
| const typeName = config.name; // 数据集/模型 | |||||
| useEffect(() => { | |||||
| getModelByDetail(); | |||||
| getVersionList(); | |||||
| }, [resourceId]); | |||||
| // 获取详情 | |||||
| const getModelByDetail = async () => { | |||||
| const request = config.getInfo; | |||||
| const [res] = await to(request(resourceId)); | |||||
| if (res) { | |||||
| setInfo(res.data); | |||||
| } | |||||
| }; | |||||
| // 获取版本列表 | |||||
| const getVersionList = async () => { | |||||
| const request = config.getVersions; | |||||
| const [res] = await to(request(resourceId)); | |||||
| if (res && res.data && res.data.length > 0) { | |||||
| setVersionList( | |||||
| res.data.map((item: string) => { | |||||
| return { | |||||
| label: item, | |||||
| value: item, | |||||
| }; | |||||
| }), | |||||
| ); | |||||
| if (versionParam && res.data.includes(versionParam)) { | |||||
| setVersion(versionParam); | |||||
| versionParam = null; | |||||
| } else { | |||||
| setVersion(res.data[0]); | |||||
| } | |||||
| } else { | |||||
| setVersion(undefined); | |||||
| } | |||||
| }; | |||||
| // 版本变化 | |||||
| const handleVersionChange = (value: string) => { | |||||
| setVersion(value); | |||||
| }; | |||||
| const items = [ | |||||
| function ResourceIntro({ info }: ResourceIntroProps) { | |||||
| const basicDatas: BasicInfoData[] = [ | |||||
| { | |||||
| label: '数据集名称', | |||||
| value: info.name, | |||||
| }, | |||||
| { | |||||
| label: '版本', | |||||
| value: info.version, | |||||
| }, | |||||
| { | |||||
| label: '创建人', | |||||
| value: info.create_by, | |||||
| }, | |||||
| { | |||||
| label: '更新时间', | |||||
| value: info.update_time, | |||||
| }, | |||||
| { | { | ||||
| key: ResourceInfoTabKeys.Introduction, | |||||
| label: `${typeName}简介`, | |||||
| icon: <KFIcon type="icon-moxingjianjie" />, | |||||
| children: ( | |||||
| <> | |||||
| <div className={styles['resource-intro__title']}>简介</div> | |||||
| <div className={styles['resource-intro__intro']}>{info.description}</div> | |||||
| </> | |||||
| ), | |||||
| label: '数据来源', | |||||
| value: info.dataset_source, | |||||
| }, | }, | ||||
| { | { | ||||
| key: ResourceInfoTabKeys.Version, | |||||
| label: `${typeName}文件/版本`, | |||||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||||
| children: ( | |||||
| <ResourceVersion | |||||
| resourceType={resourceType} | |||||
| resourceId={resourceId} | |||||
| resourceName={info.name} | |||||
| isPublic={info.available_range === 1} | |||||
| versionList={versionList} | |||||
| version={version} | |||||
| isActive={activeTab === ResourceInfoTabKeys.Version} | |||||
| getVersionList={getVersionList} | |||||
| onVersionChange={handleVersionChange} | |||||
| ></ResourceVersion> | |||||
| ), | |||||
| label: '处理代码', | |||||
| value: info.processing_code, | |||||
| }, | |||||
| { | |||||
| label: '数据集分类', | |||||
| value: info.data_type, | |||||
| }, | |||||
| { | |||||
| label: '研究方向', | |||||
| value: info.data_tag, | |||||
| }, | |||||
| { | |||||
| label: '数据集描述', | |||||
| value: info.description, | |||||
| }, | |||||
| { | |||||
| label: '版本描述', | |||||
| value: info.version_desc, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| if (resourceType === ResourceType.Model) { | |||||
| items.push({ | |||||
| key: ResourceInfoTabKeys.Evolution, | |||||
| label: `模型演化`, | |||||
| icon: <KFIcon type="icon-moxingyanhua1" />, | |||||
| children: ( | |||||
| <ModelEvolution | |||||
| resourceId={resourceId} | |||||
| versionList={versionList} | |||||
| version={version} | |||||
| isActive={activeTab === ResourceInfoTabKeys.Evolution} | |||||
| onVersionChange={handleVersionChange} | |||||
| ></ModelEvolution> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData; | |||||
| const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData; | |||||
| return ( | return ( | ||||
| <div className={styles['resource-intro']}> | <div className={styles['resource-intro']}> | ||||
| <div className={styles['resource-intro__top']}> | |||||
| <div className={styles['resource-intro__top__name']}>{info.name}</div> | |||||
| <Flex align="center"> | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {typeName} id:{info.id} | |||||
| </div> | |||||
| {info[infoTypePropertyName] && ( | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {info[infoTypePropertyName] || '--'} | |||||
| </div> | |||||
| )} | |||||
| {info[infoTagPropertyName] && ( | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {info[infoTagPropertyName] || '--'} | |||||
| </div> | |||||
| )} | |||||
| </Flex> | |||||
| </div> | |||||
| <div className={styles['resource-intro__bottom']}> | |||||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['resource-intro__basic']}> | |||||
| <BasicInfo datas={basicDatas}></BasicInfo> | |||||
| </div> | </div> | ||||
| <SubAreaTitle | |||||
| title="实例用法" | |||||
| image={require('@/assets/img/usage-icon.png')} | |||||
| style={{ margin: '40px 0 24px' }} | |||||
| ></SubAreaTitle> | |||||
| <div | |||||
| className={styles['resource-intro__usage']} | |||||
| dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }} | |||||
| ></div> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | |||||
| } | |||||
| export default ResourceIntro; | export default ResourceIntro; | ||||
| @@ -44,7 +44,10 @@ function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) | |||||
| </div> | </div> | ||||
| <div className={styles['resource-item__time']}> | <div className={styles['resource-item__time']}> | ||||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" /> | <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" /> | ||||
| <span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span> | |||||
| <span> | |||||
| {'最近更新: '} | |||||
| {item.update_time ? formatDate(item.update_time, 'YYYY-MM-DD') : item.time_ago ?? ''} | |||||
| </span> | |||||
| </div> | </div> | ||||
| </Flex> | </Flex> | ||||
| </div> | </div> | ||||
| @@ -36,4 +36,8 @@ | |||||
| text-align: right; | text-align: right; | ||||
| } | } | ||||
| } | } | ||||
| &__empty { | |||||
| flex: 1; | |||||
| } | |||||
| } | } | ||||
| @@ -1,11 +1,14 @@ | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import AddModelModal from '@/pages/Dataset/components/AddModelModal'; | import AddModelModal from '@/pages/Dataset/components/AddModelModal'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { resourceItemKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | ||||
| import { pick } from 'lodash'; | |||||
| import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | ||||
| import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config'; | import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config'; | ||||
| import AddDatasetModal from '../AddDatasetModal'; | import AddDatasetModal from '../AddDatasetModal'; | ||||
| @@ -43,7 +46,7 @@ function ResourceList( | |||||
| ref: Ref<ResourceListRef>, | ref: Ref<ResourceListRef>, | ||||
| ) { | ) { | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [dataList, setDataList] = useState<ResourceData[]>([]); | |||||
| const [dataList, setDataList] = useState<ResourceData[] | undefined>(undefined); | |||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [pagination, setPagination] = useState<PaginationProps>( | const [pagination, setPagination] = useState<PaginationProps>( | ||||
| initialPagination ?? { | initialPagination ?? { | ||||
| @@ -71,7 +74,8 @@ function ResourceList( | |||||
| }); | }); | ||||
| setSearchText(''); | setSearchText(''); | ||||
| setInputText(''); | setInputText(''); | ||||
| setDataList([]); | |||||
| setDataList(undefined); | |||||
| setTotal(0); | |||||
| }, | }, | ||||
| }; | }; | ||||
| }, | }, | ||||
| @@ -80,26 +84,33 @@ function ResourceList( | |||||
| // 获取数据请求 | // 获取数据请求 | ||||
| const getDataList = async () => { | const getDataList = async () => { | ||||
| const params = { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| [config.typeParamKey]: dataType, | [config.typeParamKey]: dataType, | ||||
| [config.tagParamKey]: dataTag, | [config.tagParamKey]: dataTag, | ||||
| available_range: isPublic ? 1 : 0, | |||||
| name: searchText !== '' ? searchText : undefined, | name: searchText !== '' ? searchText : undefined, | ||||
| }; | }; | ||||
| if (resourceType === ResourceType.Dataset) { | |||||
| params['is_public'] = isPublic; | |||||
| } else { | |||||
| params['available_range'] = isPublic ? 1 : 0; | |||||
| } | |||||
| const request = config.getList; | const request = config.getList; | ||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| if (res && res.data && res.data.content) { | if (res && res.data && res.data.content) { | ||||
| setDataList(res.data.content); | setDataList(res.data.content); | ||||
| setTotal(res.data.totalElements); | setTotal(res.data.totalElements); | ||||
| } else { | |||||
| setDataList([]); | |||||
| setTotal(0); | |||||
| } | } | ||||
| }; | }; | ||||
| // 删除请求 | // 删除请求 | ||||
| const deleteRecord = async (id: number) => { | |||||
| const deleteRecord = async (params: { owner: string; identifier: string }) => { | |||||
| const request = config.deleteRecord; | const request = config.deleteRecord; | ||||
| const [res] = await to(request(id)); | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | if (res) { | ||||
| getDataList(); | getDataList(); | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| @@ -116,7 +127,7 @@ function ResourceList( | |||||
| modalConfirm({ | modalConfirm({ | ||||
| title: config.deleteModalTitle, | title: config.deleteModalTitle, | ||||
| onOk: () => { | onOk: () => { | ||||
| deleteRecord(record.id); | |||||
| deleteRecord(pick(record, ['owner', 'identifier'])); | |||||
| }, | }, | ||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -131,6 +142,7 @@ function ResourceList( | |||||
| activeTag: dataTag, | activeTag: dataTag, | ||||
| }); | }); | ||||
| const prefix = config.prefix; | const prefix = config.prefix; | ||||
| setSessionStorageItem(resourceItemKey, record, true); | |||||
| navigate(`/dataset/${prefix}/info/${record.id}`); | navigate(`/dataset/${prefix}/info/${record.id}`); | ||||
| }; | }; | ||||
| @@ -158,7 +170,7 @@ function ResourceList( | |||||
| return ( | return ( | ||||
| <div className={styles['resource-list']}> | <div className={styles['resource-list']}> | ||||
| <div className={styles['resource-list__header']}> | <div className={styles['resource-list__header']}> | ||||
| <span>数据总数:{total}个</span> | |||||
| <span>数据总数:{total} 个</span> | |||||
| <div> | <div> | ||||
| <Input.Search | <Input.Search | ||||
| placeholder={`按${config.name}名称筛选`} | placeholder={`按${config.name}名称筛选`} | ||||
| @@ -182,26 +194,37 @@ function ResourceList( | |||||
| )} | )} | ||||
| </div> | </div> | ||||
| </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} | |||||
| /> | |||||
| {dataList && dataList.length > 0 && ( | |||||
| <> | |||||
| <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} | |||||
| /> | |||||
| </> | |||||
| )} | |||||
| {dataList && dataList.length === 0 && ( | |||||
| <KFEmpty | |||||
| className={styles['resource-list__empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| /> | |||||
| )} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,125 +1,35 @@ | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | import CommonTableCell from '@/components/CommonTableCell'; | ||||
| import DateTableCell from '@/components/DateTableCell'; | import DateTableCell from '@/components/DateTableCell'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { useEffectWhen } from '@/hooks'; | |||||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | |||||
| import { | import { | ||||
| ResourceData, | |||||
| ResourceFileData, | ResourceFileData, | ||||
| ResourceType, | ResourceType, | ||||
| ResourceVersionData, | |||||
| resourceConfig, | resourceConfig, | ||||
| } from '@/pages/Dataset/config'; | } from '@/pages/Dataset/config'; | ||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { App, Button, Flex, Select, Table } from 'antd'; | |||||
| import { useState } from 'react'; | |||||
| import { Button, Flex, Table } from 'antd'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ResourceVersionProps = { | type ResourceVersionProps = { | ||||
| resourceType: ResourceType; | resourceType: ResourceType; | ||||
| resourceId: number; | |||||
| resourceName: string; | |||||
| isPublic: boolean; | |||||
| versionList: ResourceVersionData[]; | |||||
| version?: string; | |||||
| isActive: boolean; | |||||
| getVersionList: () => void; | |||||
| onVersionChange: (version: string) => void; | |||||
| info: ResourceData; | |||||
| }; | }; | ||||
| function ResourceVersion({ | |||||
| resourceType, | |||||
| resourceId, | |||||
| resourceName, | |||||
| isPublic, | |||||
| versionList, | |||||
| version, | |||||
| isActive, | |||||
| getVersionList, | |||||
| onVersionChange, | |||||
| }: ResourceVersionProps) { | |||||
| const [fileList, setFileList] = useState<ResourceFileData[]>([]); | |||||
| const { message } = App.useApp(); | |||||
| function ResourceVersion({ resourceType, info }: ResourceVersionProps) { | |||||
| const config = resourceConfig[resourceType]; | const config = resourceConfig[resourceType]; | ||||
| // 获取版本文件列表 | |||||
| useEffectWhen( | |||||
| () => { | |||||
| if (version) { | |||||
| getFileList(version); | |||||
| } else { | |||||
| setFileList([]); | |||||
| } | |||||
| }, | |||||
| [resourceId, version], | |||||
| isActive, | |||||
| ); | |||||
| // 获取版本下的文件列表 | |||||
| const getFileList = async (version: string) => { | |||||
| const params = { | |||||
| version, | |||||
| [config.fileReqParamKey]: resourceId, | |||||
| }; | |||||
| const request = config.getFiles; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| setFileList(res?.data?.content ?? []); | |||||
| } | |||||
| }; | |||||
| // 删除版本 | |||||
| const deleteVersion = async () => { | |||||
| const request = config.deleteVersion; | |||||
| const params = { | |||||
| [config.idParamKey]: resourceId, | |||||
| version, | |||||
| }; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| getVersionList(); | |||||
| message.success('删除成功'); | |||||
| } | |||||
| }; | |||||
| // 新建版本 | |||||
| const showModal = () => { | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: resourceType, | |||||
| resourceId: resourceId, | |||||
| initialName: resourceName, | |||||
| onOk: () => { | |||||
| getVersionList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 处理删除 | |||||
| const hanldeDelete = () => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| deleteVersion(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const fileList = info.dataset_version_vos ?? []; | |||||
| fileList.forEach((item) => (item.update_time = info.update_time)); | |||||
| // 全部导出 | // 全部导出 | ||||
| const handleExport = async () => { | const handleExport = async () => { | ||||
| const url = config.downloadAllAction; | const url = config.downloadAllAction; | ||||
| downLoadZip(url, { models_id: resourceId, version }); | |||||
| downLoadZip(url, { name: info.name, id: info.id, version: info.version }); | |||||
| }; | }; | ||||
| // 单个导出 | // 单个导出 | ||||
| const downloadAlone = (record: ResourceFileData) => { | |||||
| const downloadAlone = async (record: ResourceFileData) => { | |||||
| const url = config.downloadSingleAction; | const url = config.downloadSingleAction; | ||||
| downLoadZip(`${url}/${record.id}`); | |||||
| downLoadZip(url, { url: record.url }); | |||||
| }; | }; | ||||
| const columns = [ | const columns = [ | ||||
| @@ -142,12 +52,6 @@ function ResourceVersion({ | |||||
| </a> | </a> | ||||
| ), | ), | ||||
| }, | }, | ||||
| { | |||||
| title: '版本号', | |||||
| dataIndex: 'version', | |||||
| key: 'version', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | { | ||||
| title: '文件大小', | title: '文件大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| @@ -163,7 +67,7 @@ function ResourceVersion({ | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'option', | dataIndex: 'option', | ||||
| width: '100px', | |||||
| width: 160, | |||||
| key: 'option', | key: 'option', | ||||
| render: (_: any, record: ResourceFileData) => [ | render: (_: any, record: ResourceFileData) => [ | ||||
| <Button | <Button | ||||
| @@ -183,32 +87,9 @@ function ResourceVersion({ | |||||
| <div className={styles['resource-version']}> | <div className={styles['resource-version']}> | ||||
| <Flex justify="space-between" align="center" style={{ margin: '30px 0' }}> | <Flex justify="space-between" align="center" style={{ margin: '30px 0' }}> | ||||
| <Flex align="center"> | <Flex align="center"> | ||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ width: '160px', marginRight: '20px' }} | |||||
| value={version} | |||||
| onChange={onVersionChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 创建新版本 | |||||
| </Button> | |||||
| </Flex> | |||||
| <Flex align="center"> | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| style={{ marginRight: '20px' }} | |||||
| onClick={hanldeDelete} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| disabled={!version} | |||||
| disabled={fileList.length === 0} | |||||
| onClick={handleExport} | onClick={handleExport} | ||||
| icon={<KFIcon type="icon-xiazai" />} | icon={<KFIcon type="icon-xiazai" />} | ||||
| > | > | ||||
| @@ -216,11 +97,6 @@ function ResourceVersion({ | |||||
| </Button> | </Button> | ||||
| </Flex> | </Flex> | ||||
| </Flex> | </Flex> | ||||
| <div style={{ marginBottom: '30px', fontSize: '15px' }}> | |||||
| {fileList.length > 0 && fileList[0].description | |||||
| ? '版本描述:' + fileList[0].description | |||||
| : null} | |||||
| </div> | |||||
| <Table columns={columns} dataSource={fileList} pagination={false} rowKey="id" /> | <Table columns={columns} dataSource={fileList} pagination={false} rowKey="id" /> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -1,19 +1,17 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { | import { | ||||
| addDatasetVersionDetail, | |||||
| addDatasetVersion, | |||||
| addModelsVersionDetail, | addModelsVersionDetail, | ||||
| deleteDataset, | deleteDataset, | ||||
| deleteDatasetVersion, | deleteDatasetVersion, | ||||
| deleteModel, | deleteModel, | ||||
| deleteModelVersion, | deleteModelVersion, | ||||
| getDatasetById, | |||||
| getDatasetInfo, | |||||
| getDatasetList, | getDatasetList, | ||||
| getDatasetVersionIdList, | |||||
| getDatasetVersionsById, | |||||
| getDatasetVersionList, | |||||
| getModelById, | getModelById, | ||||
| getModelList, | getModelList, | ||||
| getModelVersionIdList, | |||||
| getModelVersionsById, | getModelVersionsById, | ||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import type { TabsProps } from 'antd'; | import type { TabsProps } from 'antd'; | ||||
| @@ -26,7 +24,6 @@ export enum ResourceType { | |||||
| type ResourceTypeInfo = { | type ResourceTypeInfo = { | ||||
| getList: (params: any) => Promise<any>; // 获取资源列表 | getList: (params: any) => Promise<any>; // 获取资源列表 | ||||
| getVersions: (params: any) => Promise<any>; // 获取版本列表 | getVersions: (params: any) => Promise<any>; // 获取版本列表 | ||||
| getFiles: (params: any) => Promise<any>; // 获取版本下的文件列表 | |||||
| deleteRecord: (params: any) => Promise<any>; // 删除 | deleteRecord: (params: any) => Promise<any>; // 删除 | ||||
| addVersion: (params: any) => Promise<any>; // 新增版本 | addVersion: (params: any) => Promise<any>; // 新增版本 | ||||
| deleteVersion: (params: any) => Promise<any>; // 删除版本 | deleteVersion: (params: any) => Promise<any>; // 删除版本 | ||||
| @@ -55,12 +52,11 @@ type ResourceTypeInfo = { | |||||
| export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | ||||
| [ResourceType.Dataset]: { | [ResourceType.Dataset]: { | ||||
| getList: getDatasetList, | getList: getDatasetList, | ||||
| getVersions: getDatasetVersionsById, | |||||
| getFiles: getDatasetVersionIdList, | |||||
| getVersions: getDatasetVersionList, | |||||
| deleteRecord: deleteDataset, | deleteRecord: deleteDataset, | ||||
| addVersion: addDatasetVersionDetail, | |||||
| addVersion: addDatasetVersion, | |||||
| deleteVersion: deleteDatasetVersion, | deleteVersion: deleteDatasetVersion, | ||||
| getInfo: getDatasetById, | |||||
| getInfo: getDatasetInfo, | |||||
| name: '数据集', | name: '数据集', | ||||
| typeParamKey: 'data_type', | typeParamKey: 'data_type', | ||||
| tagParamKey: 'data_tag', | tagParamKey: 'data_tag', | ||||
| @@ -85,17 +81,16 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| deleteModalTitle: '确定删除该条数据集实例吗?', | deleteModalTitle: '确定删除该条数据集实例吗?', | ||||
| addBtnTitle: '新建数据集', | addBtnTitle: '新建数据集', | ||||
| idParamKey: 'dataset_id', | idParamKey: 'dataset_id', | ||||
| uploadAction: '/api/mmp/dataset/upload', | |||||
| uploadAction: '/api/mmp/newdataset/upload', | |||||
| uploadAccept: '.zip,.tgz', | uploadAccept: '.zip,.tgz', | ||||
| downloadAllAction: '/api/mmp/dataset/downloadAllFilesl', | |||||
| downloadSingleAction: '/api/mmp/dataset/download', | |||||
| infoTypePropertyName: 'dataset_type_name', | |||||
| infoTagPropertyName: 'dataset_tag_name', | |||||
| downloadAllAction: '/api/mmp/newdataset/downloadAllFiles', | |||||
| downloadSingleAction: '/api/mmp/newdataset/downloadSinggerFile', | |||||
| infoTypePropertyName: 'data_type', | |||||
| infoTagPropertyName: 'data_tag', | |||||
| }, | }, | ||||
| [ResourceType.Model]: { | [ResourceType.Model]: { | ||||
| getList: getModelList, | getList: getModelList, | ||||
| getVersions: getModelVersionsById, | getVersions: getModelVersionsById, | ||||
| getFiles: getModelVersionIdList, | |||||
| deleteRecord: deleteModel, | deleteRecord: deleteModel, | ||||
| addVersion: addModelsVersionDetail, | addVersion: addModelsVersionDetail, | ||||
| deleteVersion: deleteModelVersion, | deleteVersion: deleteModelVersion, | ||||
| @@ -145,32 +140,37 @@ export type CategoryData = { | |||||
| export type ResourceData = { | export type ResourceData = { | ||||
| id: number; | id: number; | ||||
| name: string; | name: string; | ||||
| identifier: string; | |||||
| description: string; | description: string; | ||||
| create_by: string; | create_by: string; | ||||
| owner: string; | |||||
| update_time: string; | update_time: string; | ||||
| available_range: number; | |||||
| time_ago: string; | |||||
| is_public: boolean; | |||||
| model_type_name?: string; | model_type_name?: string; | ||||
| model_tag_name?: string; | model_tag_name?: string; | ||||
| dataset_type_name?: string; | |||||
| dataset_tag_name?: string; | |||||
| data_type?: string; | |||||
| data_tag?: string; | |||||
| version?: string; | |||||
| version_desc?: string; | |||||
| processing_code?: string; | |||||
| dataset_source?: string; | |||||
| usage?: string; | |||||
| dataset_version_vos: ResourceFileData[]; | |||||
| }; | }; | ||||
| // 版本数据 | // 版本数据 | ||||
| export type ResourceVersionData = { | export type ResourceVersionData = { | ||||
| label: string; | |||||
| value: string; | |||||
| name: string; | |||||
| http_url: string; | |||||
| tar_url: string; | |||||
| zip_url: string; | |||||
| }; | }; | ||||
| // 版本文件数据 | // 版本文件数据 | ||||
| export type ResourceFileData = { | export type ResourceFileData = { | ||||
| id: number; | |||||
| file_name: string; | file_name: string; | ||||
| file_size: string; | file_size: string; | ||||
| description: string; | |||||
| create_by: string; | |||||
| create_time: string; | |||||
| update_by: string; | |||||
| update_time: string; | |||||
| url: string; | url: string; | ||||
| version: string; | |||||
| update_time?: string; | |||||
| }; | }; | ||||
| @@ -1,8 +1,8 @@ | |||||
| import ResourceIntro from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import ResourceInfo from '@/pages/Dataset/components/ResourceInfo'; | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | import { ResourceType } from '@/pages/Dataset/config'; | ||||
| function DatasetIntro() { | |||||
| return <ResourceIntro resourceType={ResourceType.Dataset} />; | |||||
| function DatasetInfo() { | |||||
| return <ResourceInfo resourceType={ResourceType.Dataset} />; | |||||
| } | } | ||||
| export default DatasetIntro; | |||||
| export default DatasetInfo; | |||||
| @@ -8,11 +8,11 @@ import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | |||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import ResourceSelect, { | import ResourceSelect, { | ||||
| requiredValidator, | requiredValidator, | ||||
| ResourceSelectorType, | |||||
| type ParameterInputObject, | type ParameterInputObject, | ||||
| } from '@/components/ResourceSelect'; | } from '@/components/ResourceSelect'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||||
| import { createEditorReq } from '@/services/developmentEnvironment'; | import { createEditorReq } from '@/services/developmentEnvironment'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| @@ -90,7 +90,6 @@ function EditorCreate() { | |||||
| <Form | <Form | ||||
| name="editor-create" | name="editor-create" | ||||
| labelCol={{ flex: '100px' }} | labelCol={{ flex: '100px' }} | ||||
| wrapperCol={{ flex: 1 }} | |||||
| labelAlign="left" | labelAlign="left" | ||||
| form={form} | form={form} | ||||
| initialValues={{ computing_resource: ComputingResourceType.GPU }} | initialValues={{ computing_resource: ComputingResourceType.GPU }} | ||||
| @@ -8,6 +8,7 @@ import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { MirrorVersionStatus } from '@/enums'; | |||||
| import { useDomSize } from '@/hooks'; | import { useDomSize } from '@/hooks'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | import { useCacheState } from '@/hooks/pageCacheState'; | ||||
| import { | import { | ||||
| @@ -36,7 +37,7 @@ import { useEffect, useMemo, useState } from 'react'; | |||||
| import MirrorStatusCell from '../components/MirrorStatusCell'; | import MirrorStatusCell from '../components/MirrorStatusCell'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type MirrorInfoData = { | |||||
| export type MirrorInfoData = { | |||||
| name?: string; | name?: string; | ||||
| description?: string; | description?: string; | ||||
| version_count?: string; | version_count?: string; | ||||
| @@ -44,13 +45,14 @@ type MirrorInfoData = { | |||||
| image_type?: number; | image_type?: number; | ||||
| }; | }; | ||||
| type MirrorVersionData = { | |||||
| export type MirrorVersionData = { | |||||
| id: number; | id: number; | ||||
| version: string; | version: string; | ||||
| url: string; | url: string; | ||||
| status: string; | |||||
| status: MirrorVersionStatus; | |||||
| file_size: string; | file_size: string; | ||||
| create_time: string; | create_time: string; | ||||
| tag_name: string; | |||||
| }; | }; | ||||
| function MirrorInfo() { | function MirrorInfo() { | ||||
| @@ -1,4 +1,4 @@ | |||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; | import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; | ||||
| @@ -7,12 +7,12 @@ import KFIcon from '@/components/KFIcon'; | |||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import ResourceSelect, { | import ResourceSelect, { | ||||
| requiredValidator, | requiredValidator, | ||||
| ResourceSelectorType, | |||||
| type ParameterInputObject, | type ParameterInputObject, | ||||
| } from '@/components/ResourceSelect'; | } from '@/components/ResourceSelect'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||||
| import { | import { | ||||
| createModelDeploymentReq, | createModelDeploymentReq, | ||||
| restartModelDeploymentReq, | restartModelDeploymentReq, | ||||
| @@ -67,7 +67,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| <KFModal | <KFModal | ||||
| {...rest} | {...rest} | ||||
| title="选择代码配置" | title="选择代码配置" | ||||
| image={require('@/assets/img/edit-experiment.png')} | |||||
| image={require('@/assets/img/modal-code-config.png')} | |||||
| width={920} | width={920} | ||||
| footer={null} | footer={null} | ||||
| destroyOnClose | destroyOnClose | ||||
| @@ -245,6 +245,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| // 获取选择数据集、模型后面按钮 icon | // 获取选择数据集、模型后面按钮 icon | ||||
| const getSelectBtnIcon = (item: { item_type: string }) => { | const getSelectBtnIcon = (item: { item_type: string }) => { | ||||
| const type = item.item_type; | const type = item.item_type; | ||||
| if (type === 'code') { | |||||
| return <KFIcon type="icon-xuanzedaimapeizhi" />; | |||||
| } | |||||
| let selectorType: ResourceSelectorType; | let selectorType: ResourceSelectorType; | ||||
| if (type === 'dataset') { | if (type === 'dataset') { | ||||
| selectorType = ResourceSelectorType.Dataset; | selectorType = ResourceSelectorType.Dataset; | ||||
| @@ -1,17 +1,21 @@ | |||||
| import datasetImg from '@/assets/img/modal-select-dataset.png'; | import datasetImg from '@/assets/img/modal-select-dataset.png'; | ||||
| import mirrorImg from '@/assets/img/modal-select-mirror.png'; | import mirrorImg from '@/assets/img/modal-select-mirror.png'; | ||||
| import modelImg from '@/assets/img/modal-select-model.png'; | import modelImg from '@/assets/img/modal-select-model.png'; | ||||
| import { CommonTabKeys, MirrorVersionStatus } from '@/enums'; | |||||
| import { AvailableRange, CommonTabKeys } from '@/enums'; | |||||
| import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config'; | |||||
| import { MirrorVersionData } from '@/pages/Mirror/Info'; | |||||
| import { MirrorData } from '@/pages/Mirror/List'; | |||||
| import { | import { | ||||
| getDatasetInfo, | |||||
| getDatasetList, | getDatasetList, | ||||
| getDatasetVersionIdList, | |||||
| getDatasetVersionsById, | |||||
| getDatasetVersionList, | |||||
| getModelList, | getModelList, | ||||
| getModelVersionIdList, | getModelVersionIdList, | ||||
| getModelVersionsById, | getModelVersionsById, | ||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror'; | import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror'; | ||||
| import type { TabsProps } from 'antd'; | |||||
| import type { TabsProps, TreeDataNode } from 'antd'; | |||||
| import { pick } from 'lodash'; | |||||
| export enum ResourceSelectorType { | export enum ResourceSelectorType { | ||||
| Model = 'Model', // 模型 | Model = 'Model', // 模型 | ||||
| @@ -19,111 +23,347 @@ export enum ResourceSelectorType { | |||||
| Mirror = 'Mirror', //镜像 | Mirror = 'Mirror', //镜像 | ||||
| } | } | ||||
| export type MirrorVersion = { | |||||
| id: number; // 镜像版本 id | |||||
| status: MirrorVersionStatus; // 镜像版本状态 | |||||
| tag_name: string; // 镜像版本 name | |||||
| url: string; // 镜像版本路径 | |||||
| // 数据集、模型列表转为树形结构 | |||||
| const convertDatasetToTreeData = (list: ResourceData[]): TreeDataNode[] => { | |||||
| return list.map((v) => ({ | |||||
| ...v, | |||||
| key: `${v.id}`, | |||||
| title: v.name, | |||||
| isLeaf: false, | |||||
| checkable: false, | |||||
| })); | |||||
| }; | }; | ||||
| export type SelectorTypeInfo = { | |||||
| getList: (params: any) => Promise<any>; // 获取资源列表 | |||||
| getVersions: (params: any) => Promise<any>; // 获取资源版本列表 | |||||
| getFiles: (params: any) => Promise<any>; // 获取资源文件列表 | |||||
| handleVersionResponse: (res: any) => any[]; // 处理版本列表接口数据 | |||||
| modalIcon: string; // modal icon | |||||
| buttonIcon: string; // button icon | |||||
| name: string; // 名称 | |||||
| litReqParamKey: 'available_range' | 'image_type'; // 表示是公开还是私有的参数名称,获取资源列表接口使用 | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用 | |||||
| tabItems: TabsProps['items']; // tab 列表 | |||||
| buttontTitle: string; // 按钮 title | |||||
| // 镜像列表转为树形结构 | |||||
| const convertMirrorToTreeData = (list: MirrorData[]): TreeDataNode[] => { | |||||
| return list.map((v) => ({ | |||||
| key: `${v.id}`, | |||||
| title: v.name, | |||||
| isLeaf: false, | |||||
| checkable: false, | |||||
| })); | |||||
| }; | }; | ||||
| // 获取镜像文件列表,为了兼容数据集和模型 | |||||
| const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => { | |||||
| const index = version.indexOf('-'); | |||||
| const url = version.slice(index + 1); | |||||
| return Promise.resolve({ | |||||
| data: { | |||||
| // 数据集版本列表转为树形结构 | |||||
| const convertDatasetVersionToTreeData = ( | |||||
| parentId: string, | |||||
| info: ResourceData, | |||||
| list: ResourceVersionData[], | |||||
| ): TreeDataNode[] => { | |||||
| return list.map((item: ResourceVersionData) => ({ | |||||
| ...pick(info, ['id', 'name', 'owner', 'identifier']), | |||||
| version: item.name, | |||||
| title: item.name, | |||||
| key: `${parentId}-${item.name}`, | |||||
| isLeaf: true, | |||||
| checkable: true, | |||||
| })); | |||||
| }; | |||||
| // 镜像版本列表转为树形结构 | |||||
| const convertMirrorVersionToTreeData = ( | |||||
| parentId: string, | |||||
| list: MirrorVersionData[], | |||||
| ): TreeDataNode[] => { | |||||
| return list.map((item: MirrorVersionData) => ({ | |||||
| url: item.url, | |||||
| title: item.tag_name, | |||||
| key: `${parentId}-${item.id}`, | |||||
| isLeaf: true, | |||||
| checkable: true, | |||||
| })); | |||||
| }; | |||||
| // 从树形数据节点 id 中获取数据集版本列表的参数 | |||||
| // const parseDatasetVersionId = (id: string) => { | |||||
| // const list = id.split('-'); | |||||
| // return { | |||||
| // id: Number(list[0]), | |||||
| // name: list[1], | |||||
| // owner: list[2], | |||||
| // identifier: list[3], | |||||
| // version: list[4], | |||||
| // }; | |||||
| // }; | |||||
| // 从树形数据节点 id 中获取数据集版本列表的参数 | |||||
| // const parseMirrorVersionId = (id: string) => { | |||||
| // const list = id.split('-'); | |||||
| // return { | |||||
| // parentId: Number(list[0]), | |||||
| // id: list[1], | |||||
| // url: list[2], | |||||
| // }; | |||||
| // }; | |||||
| // export type MirrorVersion = { | |||||
| // id: number; // 镜像版本 id | |||||
| // status: MirrorVersionStatus; // 镜像版本状态 | |||||
| // tag_name: string; // 镜像版本 name | |||||
| // url: string; // 镜像版本路径 | |||||
| // }; | |||||
| // export type SelectorTypeInfo = { | |||||
| // getList: (params: any) => Promise<any>; // 获取资源列表 | |||||
| // getVersions: (params: any) => Promise<any>; // 获取资源版本列表 | |||||
| // getFiles: (params: any) => Promise<any>; // 获取资源文件列表 | |||||
| // handleVersionResponse: (res: any) => any[]; // 处理版本列表接口数据 | |||||
| // dataToTreeData: (data: any) => TreeDataNode[]; // 数据转树形结构 | |||||
| // parseTreeNodeId: (id: string) => any; // 获取版本列表请求参数 | |||||
| // modalIcon: string; // modal icon | |||||
| // buttonIcon: string; // button icon | |||||
| // name: string; // 名称 | |||||
| // litReqParamKey: 'available_range' | 'image_type'; // 表示是公开还是私有的参数名称,获取资源列表接口使用 | |||||
| // tabItems: TabsProps['items']; // tab 列表 | |||||
| // buttontTitle: string; // 按钮 title | |||||
| // }; | |||||
| // export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> = { | |||||
| // [ResourceSelectorType.Model]: { | |||||
| // getList: getModelList, | |||||
| // getVersions: getModelVersionsById, | |||||
| // getFiles: getModelVersionIdList, | |||||
| // name: '模型', | |||||
| // modalIcon: modelImg, | |||||
| // buttonIcon: 'icon-xuanzemoxing', | |||||
| // litReqParamKey: 'available_range', | |||||
| // tabItems: [ | |||||
| // { | |||||
| // key: CommonTabKeys.Private, | |||||
| // label: '我的模型', | |||||
| // }, | |||||
| // { | |||||
| // key: CommonTabKeys.Public, | |||||
| // label: '公开模型', | |||||
| // }, | |||||
| // ], | |||||
| // buttontTitle: '选择模型', | |||||
| // }, | |||||
| // [ResourceSelectorType.Dataset]: { | |||||
| // getList: getDatasetList, | |||||
| // getVersions: getDatasetVersionList, | |||||
| // getFiles: getDatasetInfo, | |||||
| // name: '数据集', | |||||
| // modalIcon: datasetImg, | |||||
| // buttonIcon: 'icon-xuanzeshujuji', | |||||
| // litReqParamKey: 'available_range', | |||||
| // tabItems: [ | |||||
| // { | |||||
| // key: CommonTabKeys.Private, | |||||
| // label: '我的数据集', | |||||
| // }, | |||||
| // { | |||||
| // key: CommonTabKeys.Public, | |||||
| // label: '公开数据集', | |||||
| // }, | |||||
| // ], | |||||
| // buttontTitle: '选择数据集', | |||||
| // }, | |||||
| // [ResourceSelectorType.Mirror]: { | |||||
| // getList: getMirrorListReq, | |||||
| // getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }), | |||||
| // getFiles: getMirrorFilesReq, | |||||
| // handleVersionResponse: (res) => | |||||
| // res.data?.content?.filter( | |||||
| // (v: MirrorVersionData) => v.status === MirrorVersionStatus.Available, | |||||
| // ) || [], | |||||
| // dataToTreeData: convertMirrorToTreeData, | |||||
| // parseTreeNodeId: (id: string) => id, | |||||
| // name: '镜像', | |||||
| // modalIcon: mirrorImg, | |||||
| // buttonIcon: 'icon-xuanzejingxiang', | |||||
| // litReqParamKey: 'image_type', | |||||
| // tabItems: [ | |||||
| // { | |||||
| // key: CommonTabKeys.Private, | |||||
| // label: '我的镜像', | |||||
| // }, | |||||
| // { | |||||
| // key: CommonTabKeys.Public, | |||||
| // label: '公开镜像', | |||||
| // }, | |||||
| // ], | |||||
| // buttontTitle: '选择镜像', | |||||
| // }, | |||||
| // }; | |||||
| interface SelectorTypeInfo { | |||||
| getList: (isPublic: boolean) => Promise<any>; // 获取资源列表 | |||||
| getVersions: (parentKey: string, parentNode: any) => Promise<any>; // 获取资源版本列表 | |||||
| getFiles: (parentKey: string, parentNode: any) => Promise<any>; // 获取资源文件列表 | |||||
| readonly modalIcon: string; // modal icon | |||||
| readonly buttonIcon: string; // button icon | |||||
| readonly name: string; // 名称 | |||||
| readonly tabItems: TabsProps['items']; // tab 列表 | |||||
| readonly buttontTitle: string; // 按钮 title | |||||
| } | |||||
| export class DatasetSelector implements SelectorTypeInfo { | |||||
| readonly name = '数据集'; | |||||
| readonly modalIcon = datasetImg; | |||||
| readonly buttonIcon = 'icon-xuanzeshujuji'; | |||||
| readonly tabItems = [ | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '我的数据集', | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '公开数据集', | |||||
| }, | |||||
| ]; | |||||
| readonly buttontTitle = '选择数据集'; | |||||
| async getList(isPublic: boolean) { | |||||
| const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 }); | |||||
| if (res && res.data) { | |||||
| const list = res.data.content || []; | |||||
| return convertDatasetToTreeData(list); | |||||
| } else { | |||||
| return Promise.reject('获取数据集列表失败'); | |||||
| } | |||||
| } | |||||
| async getVersions(parentKey: string, parentNode: ResourceData) { | |||||
| // const obj = parseDatasetVersionId(id); | |||||
| const res = await getDatasetVersionList(pick(parentNode, ['owner', 'identifier'])); | |||||
| if (res && res.data) { | |||||
| const list = res.data; | |||||
| return convertDatasetVersionToTreeData(parentKey, parentNode, list); | |||||
| } else { | |||||
| return Promise.reject('获取数据集版本列表失败'); | |||||
| } | |||||
| } | |||||
| async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) { | |||||
| //const obj = parseDatasetVersionId(parentKey); | |||||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']); | |||||
| const res = await getDatasetInfo(params); | |||||
| if (res && res.data) { | |||||
| const path = res.data.relative_paths || ''; | |||||
| const list = res.data.dataset_version_vos || []; | |||||
| return { | |||||
| path, | |||||
| content: list, | |||||
| }; | |||||
| } else { | |||||
| return Promise.reject('获取数据集文件列表失败'); | |||||
| } | |||||
| } | |||||
| } | |||||
| export class ModelSelector implements SelectorTypeInfo { | |||||
| readonly name = '模型'; | |||||
| readonly modalIcon = modelImg; | |||||
| readonly buttonIcon = 'icon-xuanzemoxing'; | |||||
| readonly tabItems = [ | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '我的模型', | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '公开模型', | |||||
| }, | |||||
| ]; | |||||
| readonly buttontTitle = '选择模型'; | |||||
| async getList(isPublic: boolean) { | |||||
| const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 }); | |||||
| if (res && res.data) { | |||||
| const list = res.data.content || []; | |||||
| return convertDatasetToTreeData(list); | |||||
| } else { | |||||
| return Promise.reject('获取数据集列表失败'); | |||||
| } | |||||
| } | |||||
| async getVersions(key: string, parentNode: ResourceData) { | |||||
| //const obj = parseDatasetVersionId(id); | |||||
| const res = await getModelVersionIdList(pick(parentNode, ['owner', 'identifier'])); | |||||
| if (res && res.data) { | |||||
| const list = res.data.content || []; | |||||
| return convertDatasetVersionToTreeData(key, parentNode, list); | |||||
| } else { | |||||
| return Promise.reject('获取数据集版本列表失败'); | |||||
| } | |||||
| } | |||||
| async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) { | |||||
| // const obj = parseDatasetVersionId(id); | |||||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']); | |||||
| const res = await getModelVersionsById(params); | |||||
| if (res && res.data) { | |||||
| const list = res.data.dataset_version_vos || []; | |||||
| return { | |||||
| path: res.data.path || '', | |||||
| content: list, | |||||
| }; | |||||
| } else { | |||||
| return Promise.reject('获取数据集文件列表失败'); | |||||
| } | |||||
| } | |||||
| } | |||||
| export class MirrorSelector implements SelectorTypeInfo { | |||||
| readonly name = '镜像'; | |||||
| readonly modalIcon = mirrorImg; | |||||
| readonly buttonIcon = 'icon-xuanzejingxiang'; | |||||
| readonly tabItems = [ | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '我的镜像', | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '公开镜像', | |||||
| }, | |||||
| ]; | |||||
| readonly buttontTitle = '选择镜像'; | |||||
| async getList(isPublic: boolean) { | |||||
| const res = await getMirrorListReq({ | |||||
| image_type: isPublic ? AvailableRange.Public : AvailableRange.Private, | |||||
| page: 0, | |||||
| size: 2000, | |||||
| }); | |||||
| if (res && res.data) { | |||||
| const list = res.data.content || []; | |||||
| return convertMirrorToTreeData(list); | |||||
| } else { | |||||
| return Promise.reject('获取镜像列表失败'); | |||||
| } | |||||
| } | |||||
| async getVersions(parentKey: string) { | |||||
| const res = await getMirrorVersionListReq({ | |||||
| image_id: parentKey, | |||||
| page: 0, | |||||
| size: 2000, | |||||
| }); | |||||
| if (res && res.data) { | |||||
| const list = res.data.content || []; | |||||
| return convertMirrorVersionToTreeData(parentKey, list); | |||||
| } else { | |||||
| return Promise.reject('获取镜像版本列表失败'); | |||||
| } | |||||
| } | |||||
| async getFiles(parentKey: string, parentNode: MirrorVersionData) { | |||||
| const { url } = parentNode; | |||||
| return { | |||||
| path: url, | path: url, | ||||
| content: [ | content: [ | ||||
| { | { | ||||
| id: `${id}-${version}`, | |||||
| id: parentKey, | |||||
| file_name: `${url}`, | file_name: `${url}`, | ||||
| }, | }, | ||||
| ], | ], | ||||
| }, | |||||
| }); | |||||
| }; | |||||
| }; | |||||
| } | |||||
| } | |||||
| export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> = { | export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> = { | ||||
| [ResourceSelectorType.Model]: { | |||||
| getList: getModelList, | |||||
| getVersions: getModelVersionsById, | |||||
| getFiles: getModelVersionIdList, | |||||
| handleVersionResponse: (res) => res.data || [], | |||||
| name: '模型', | |||||
| modalIcon: modelImg, | |||||
| buttonIcon: 'icon-xuanzemoxing', | |||||
| litReqParamKey: 'available_range', | |||||
| fileReqParamKey: 'models_id', | |||||
| tabItems: [ | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '我的模型', | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '公开模型', | |||||
| }, | |||||
| ], | |||||
| buttontTitle: '选择模型', | |||||
| }, | |||||
| [ResourceSelectorType.Dataset]: { | |||||
| getList: getDatasetList, | |||||
| getVersions: getDatasetVersionsById, | |||||
| getFiles: getDatasetVersionIdList, | |||||
| handleVersionResponse: (res) => res.data || [], | |||||
| name: '数据集', | |||||
| modalIcon: datasetImg, | |||||
| buttonIcon: 'icon-xuanzeshujuji', | |||||
| litReqParamKey: 'available_range', | |||||
| fileReqParamKey: 'dataset_id', | |||||
| tabItems: [ | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '我的数据集', | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '公开数据集', | |||||
| }, | |||||
| ], | |||||
| buttontTitle: '选择数据集', | |||||
| }, | |||||
| [ResourceSelectorType.Mirror]: { | |||||
| getList: getMirrorListReq, | |||||
| getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }), | |||||
| getFiles: getMirrorFilesReq, | |||||
| handleVersionResponse: (res) => | |||||
| res.data?.content?.filter((v: MirrorVersion) => v.status === MirrorVersionStatus.Available) || | |||||
| [], | |||||
| name: '镜像', | |||||
| modalIcon: mirrorImg, | |||||
| buttonIcon: 'icon-xuanzejingxiang', | |||||
| litReqParamKey: 'image_type', | |||||
| fileReqParamKey: 'dataset_id', | |||||
| tabItems: [ | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '我的镜像', | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '公开镜像', | |||||
| }, | |||||
| ], | |||||
| buttontTitle: '选择镜像', | |||||
| }, | |||||
| [ResourceSelectorType.Model]: new ModelSelector(), | |||||
| [ResourceSelectorType.Dataset]: new DatasetSelector(), | |||||
| [ResourceSelectorType.Mirror]: new MirrorSelector(), | |||||
| }; | }; | ||||
| @@ -67,3 +67,8 @@ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| .kf-tree-title { | |||||
| display: inline-block; | |||||
| .singleLine(); | |||||
| } | |||||
| @@ -11,24 +11,19 @@ import { Icon } from '@umijs/max'; | |||||
| import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | ||||
| import { Input, Tabs, Tree } from 'antd'; | import { Input, Tabs, Tree } from 'antd'; | ||||
| import React, { useEffect, useMemo, useRef, useState } from 'react'; | import React, { useEffect, useMemo, useRef, useState } from 'react'; | ||||
| import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './config'; | |||||
| import { ResourceSelectorType, selectorTypeConfig } from './config'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export { ResourceSelectorType, selectorTypeConfig }; | export { ResourceSelectorType, selectorTypeConfig }; | ||||
| // 选择数据集\模型\镜像的返回类型 | // 选择数据集\模型\镜像的返回类型 | ||||
| export type ResourceSelectorResponse = { | export type ResourceSelectorResponse = { | ||||
| id: number; // 数据集\模型\镜像 id | |||||
| id: string; // 数据集\模型\镜像 id | |||||
| name: string; // 数据集\模型\镜像 name | name: string; // 数据集\模型\镜像 name | ||||
| version: string; // 数据集\模型\镜像版本 | version: string; // 数据集\模型\镜像版本 | ||||
| path: string; // 数据集\模型\镜像版本路径 | path: string; // 数据集\模型\镜像版本路径 | ||||
| activeTab: CommonTabKeys; // 是我的还是公开的 | activeTab: CommonTabKeys; // 是我的还是公开的 | ||||
| }; | }; | ||||
| type ResourceGroup = { | |||||
| id: number; // 数据集\模型\镜像 id | |||||
| name: string; // 数据集\模型\镜像 name | |||||
| }; | |||||
| type ResourceFile = { | type ResourceFile = { | ||||
| id: number; // 文件 id | id: number; // 文件 id | ||||
| file_name: string; // 文件 name | file_name: string; // 文件 name | ||||
| @@ -44,39 +39,8 @@ export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| type TreeRef = GetRef<typeof Tree<TreeDataNode>>; | type TreeRef = GetRef<typeof Tree<TreeDataNode>>; | ||||
| // list 数据转成 treeData | |||||
| const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | |||||
| return list.map((v) => ({ | |||||
| title: v.name, | |||||
| key: v.id, | |||||
| isLeaf: false, | |||||
| checkable: false, | |||||
| })); | |||||
| }; | |||||
| // 版本数据转成 treeData | |||||
| const convertVersionToTreeData = (parentId: number) => { | |||||
| return (item: string | MirrorVersion): TreeDataNode => { | |||||
| if (typeof item === 'string') { | |||||
| return { | |||||
| title: item, | |||||
| key: `${parentId}-${item}`, | |||||
| isLeaf: true, | |||||
| checkable: true, | |||||
| }; | |||||
| } else { | |||||
| return { | |||||
| title: item.tag_name, | |||||
| key: `${parentId}-${item.id}-${item.url}`, | |||||
| isLeaf: true, | |||||
| checkable: true, | |||||
| }; | |||||
| } | |||||
| }; | |||||
| }; | |||||
| // 更新树形结构的 children | // 更新树形结构的 children | ||||
| const updateChildren = (parentId: number, children: TreeDataNode[]) => { | |||||
| const updateChildren = (parentId: string, children: TreeDataNode[]) => { | |||||
| return (node: TreeDataNode) => { | return (node: TreeDataNode) => { | ||||
| if (node.key === parentId) { | if (node.key === parentId) { | ||||
| return { | return { | ||||
| @@ -91,7 +55,7 @@ const updateChildren = (parentId: number, children: TreeDataNode[]) => { | |||||
| // 得到数据集\模型\镜像 id 和下属版本号 | // 得到数据集\模型\镜像 id 和下属版本号 | ||||
| const getIdAndVersion = (versionKey: string) => { | const getIdAndVersion = (versionKey: string) => { | ||||
| const index = versionKey.indexOf('-'); | const index = versionKey.indexOf('-'); | ||||
| const id = Number(versionKey.slice(0, index)); | |||||
| const id = versionKey.slice(0, index); | |||||
| const version = versionKey.slice(index + 1); | const version = versionKey.slice(index + 1); | ||||
| return { | return { | ||||
| id, | id, | ||||
| @@ -115,8 +79,8 @@ function ResourceSelectorModal({ | |||||
| const [files, setFiles] = useState<ResourceFile[]>([]); | const [files, setFiles] = useState<ResourceFile[]>([]); | ||||
| const [versionPath, setVersionPath] = useState(''); | const [versionPath, setVersionPath] = useState(''); | ||||
| const [searchText, setSearchText] = useState(''); | const [searchText, setSearchText] = useState(''); | ||||
| const [fisrtLoadList, setFisrtLoadList] = useState(false); | |||||
| const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false); | |||||
| const [firstLoadList, setFirstLoadList] = useState(false); | |||||
| const [firstLoadVersions, setFirstLoadVersions] = useState(false); | |||||
| const treeRef = useRef<TreeRef>(null); | const treeRef = useRef<TreeRef>(null); | ||||
| const config = selectorTypeConfig[type]; | const config = selectorTypeConfig[type]; | ||||
| @@ -140,18 +104,10 @@ function ResourceSelectorModal({ | |||||
| // 获取数据集\模型\镜像列表 | // 获取数据集\模型\镜像列表 | ||||
| const getTreeData = async () => { | const getTreeData = async () => { | ||||
| const available_range = activeTab === CommonTabKeys.Private ? 0 : 1; | |||||
| const params = { | |||||
| page: 0, | |||||
| size: 1000, | |||||
| [config.litReqParamKey]: available_range, | |||||
| }; | |||||
| const getListReq = config.getList; | |||||
| const [res] = await to(getListReq(params)); | |||||
| const isPublic = activeTab === CommonTabKeys.Private ? false : true; | |||||
| const [res] = await to(config.getList(isPublic)); | |||||
| if (res) { | if (res) { | ||||
| const list = res.data?.content || []; | |||||
| const treeData = convertToTreeData(list); | |||||
| setOriginTreeData(treeData); | |||||
| setOriginTreeData(res); | |||||
| // 恢复上一次的 Expand 操作 | // 恢复上一次的 Expand 操作 | ||||
| restoreLastExpand(); | restoreLastExpand(); | ||||
| @@ -161,21 +117,22 @@ function ResourceSelectorModal({ | |||||
| }; | }; | ||||
| // 获取数据集\模型\镜像版本列表 | // 获取数据集\模型\镜像版本列表 | ||||
| const getVersions = async (parentId: number) => { | |||||
| const getVersionsReq = config.getVersions; | |||||
| const [res, error] = await to(getVersionsReq(parentId)); | |||||
| const getVersions = async (parentId: string, parentNode: any) => { | |||||
| const [res, error] = await to(config.getVersions(parentId, parentNode)); | |||||
| if (res) { | if (res) { | ||||
| const list = config.handleVersionResponse(res); | |||||
| const children = list.map(convertVersionToTreeData(parentId)); | |||||
| // 更新 treeData children | // 更新 treeData children | ||||
| setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); | |||||
| setOriginTreeData((prev) => prev.map(updateChildren(parentId, res))); | |||||
| // 缓存 loadedKeys | // 缓存 loadedKeys | ||||
| const index = loadedKeys.find((v) => v === parentId); | const index = loadedKeys.find((v) => v === parentId); | ||||
| if (!index) { | if (!index) { | ||||
| setLoadedKeys((prev) => prev.concat(parentId)); | setLoadedKeys((prev) => prev.concat(parentId)); | ||||
| } | } | ||||
| // 恢复上一次的 Check 操作 | // 恢复上一次的 Check 操作 | ||||
| restoreLastCheck(parentId); | |||||
| setTimeout(() => { | |||||
| restoreLastCheck(parentId, res); | |||||
| }, 300); | |||||
| } else { | } else { | ||||
| setExpandedKeys([]); | setExpandedKeys([]); | ||||
| return Promise.reject(error); | return Promise.reject(error); | ||||
| @@ -183,14 +140,11 @@ function ResourceSelectorModal({ | |||||
| }; | }; | ||||
| // 获取版本下的文件 | // 获取版本下的文件 | ||||
| const getFiles = async (id: number, version: string) => { | |||||
| const getFilesReq = config.getFiles; | |||||
| const paramsKey = config.fileReqParamKey; | |||||
| const params = { version: version, [paramsKey]: id }; | |||||
| const [res] = await to(getFilesReq(params)); | |||||
| const getFiles = async (parentId: string, parentNode: any) => { | |||||
| const [res] = await to(config.getFiles(parentId, parentNode)); | |||||
| if (res) { | if (res) { | ||||
| setVersionPath(res.data?.path || ''); | |||||
| setFiles(res.data?.content || []); | |||||
| setVersionPath(res.path); | |||||
| setFiles(res.content); | |||||
| } else { | } else { | ||||
| setVersionPath(''); | setVersionPath(''); | ||||
| setFiles([]); | setFiles([]); | ||||
| @@ -198,11 +152,11 @@ function ResourceSelectorModal({ | |||||
| }; | }; | ||||
| // 动态加载 tree children | // 动态加载 tree children | ||||
| const onLoadData = ({ key, children }: TreeDataNode) => { | |||||
| const onLoadData = ({ key, children, ...rest }: TreeDataNode) => { | |||||
| if (children) { | if (children) { | ||||
| return Promise.resolve(); | return Promise.resolve(); | ||||
| } else { | } else { | ||||
| return getVersions(key as number); | |||||
| return getVersions(key as string, rest); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -213,13 +167,13 @@ function ResourceSelectorModal({ | |||||
| }; | }; | ||||
| // 选中 | // 选中 | ||||
| const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => { | |||||
| const onCheck: TreeProps['onCheck'] = (checkedKeysValue, { checkedNodes }) => { | |||||
| const lastKeys = (checkedKeysValue as React.Key[]).slice(-1); | const lastKeys = (checkedKeysValue as React.Key[]).slice(-1); | ||||
| setCheckedKeys(lastKeys); | setCheckedKeys(lastKeys); | ||||
| if (lastKeys.length) { | |||||
| if (lastKeys.length && checkedNodes.length) { | |||||
| const last = lastKeys[0] as string; | const last = lastKeys[0] as string; | ||||
| const { id, version } = getIdAndVersion(last); | |||||
| getFiles(id, version); | |||||
| const lastNode = checkedNodes[checkedNodes.length - 1]; | |||||
| getFiles(last, lastNode); | |||||
| } else { | } else { | ||||
| setFiles([]); | setFiles([]); | ||||
| } | } | ||||
| @@ -229,10 +183,10 @@ function ResourceSelectorModal({ | |||||
| // 判断是否有 defaultExpandedKeys,如果有,设置 expandedKeys | // 判断是否有 defaultExpandedKeys,如果有,设置 expandedKeys | ||||
| // fisrtLoadList 标志位 | // fisrtLoadList 标志位 | ||||
| const restoreLastExpand = () => { | const restoreLastExpand = () => { | ||||
| if (!fisrtLoadList && defaultExpandedKeys.length > 0) { | |||||
| if (!firstLoadList && defaultExpandedKeys.length > 0) { | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| setExpandedKeys(defaultExpandedKeys); | setExpandedKeys(defaultExpandedKeys); | ||||
| setFisrtLoadList(true); | |||||
| setFirstLoadList(true); | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' }); | treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' }); | ||||
| }, 100); | }, 100); | ||||
| @@ -243,16 +197,17 @@ function ResourceSelectorModal({ | |||||
| // 恢复上一次的 Check 操作 | // 恢复上一次的 Check 操作 | ||||
| // 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口 | // 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口 | ||||
| // fisrtLoadVersions 标志位 | // fisrtLoadVersions 标志位 | ||||
| const restoreLastCheck = (parentId: number) => { | |||||
| if (!fisrtLoadVersions && defaultCheckedKeys.length > 0) { | |||||
| const restoreLastCheck = (parentId: string, versions: TreeDataNode[]) => { | |||||
| if (!firstLoadVersions && defaultCheckedKeys.length > 0) { | |||||
| const last = defaultCheckedKeys[0] as string; | const last = defaultCheckedKeys[0] as string; | ||||
| const { id, version } = getIdAndVersion(last); | |||||
| const { id } = getIdAndVersion(last); | |||||
| // 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致 | // 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致 | ||||
| if (id === parentId) { | if (id === parentId) { | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| setCheckedKeys(defaultCheckedKeys); | setCheckedKeys(defaultCheckedKeys); | ||||
| getFiles(id, version); | |||||
| setFisrtLoadVersions(true); | |||||
| const parentNode = versions.find((v) => v.key === last); | |||||
| getFiles(last, parentNode); | |||||
| setFirstLoadVersions(true); | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| treeRef?.current?.scrollTo({ | treeRef?.current?.scrollTo({ | ||||
| key: defaultCheckedKeys[0], | key: defaultCheckedKeys[0], | ||||
| @@ -269,7 +224,7 @@ function ResourceSelectorModal({ | |||||
| if (checkedKeys.length > 0) { | if (checkedKeys.length > 0) { | ||||
| const last = checkedKeys[0] as string; | const last = checkedKeys[0] as string; | ||||
| const { id, version } = getIdAndVersion(last); | const { id, version } = getIdAndVersion(last); | ||||
| const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string; | |||||
| const name = (treeData.find((v) => v.key === id)?.title ?? '') as string; | |||||
| const res = { | const res = { | ||||
| id, | id, | ||||
| name, | name, | ||||
| @@ -323,6 +278,18 @@ function ResourceSelectorModal({ | |||||
| loadedKeys={loadedKeys} | loadedKeys={loadedKeys} | ||||
| expandedKeys={expandedKeys} | expandedKeys={expandedKeys} | ||||
| onExpand={onExpand} | onExpand={onExpand} | ||||
| titleRender={(nodeData) => { | |||||
| console.log(nodeData); | |||||
| return ( | |||||
| <span | |||||
| className={styles['kf-tree-title']} | |||||
| style={{ width: nodeData.isLeaf ? '370px' : '420px' }} | |||||
| > | |||||
| {nodeData.title as string} | |||||
| </span> | |||||
| ); | |||||
| }} | |||||
| checkable | checkable | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| @@ -62,6 +62,8 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| loginIp: props.values.loginIp, | loginIp: props.values.loginIp, | ||||
| loginDate: props.values.loginDate, | loginDate: props.values.loginDate, | ||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| gitLinkUsername: props.values.gitLinkUsername, | |||||
| gitLinkPassword: props.values.gitLinkPassword, | |||||
| }); | }); | ||||
| }, [form, props]); | }, [form, props]); | ||||
| @@ -275,6 +277,28 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| colProps={{ md: 12, xl: 12 }} | colProps={{ md: 12, xl: 12 }} | ||||
| rules={[{ required: true, message: '请选择角色!' }]} | rules={[{ required: true, message: '请选择角色!' }]} | ||||
| /> | /> | ||||
| <ProFormText | |||||
| name="gitLinkUsername" | |||||
| label="Git 用户名" | |||||
| placeholder="请输入 Git 用户名" | |||||
| colProps={{ xs: 24, md: 12, xl: 12 }} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入 Git 用户名!', | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <ProFormText.Password | |||||
| name="gitLinkPassword" | |||||
| label="Git 密码" | |||||
| placeholder="请输入 Git 密码" | |||||
| colProps={{ xs: 24, md: 12, xl: 12 }} | |||||
| fieldProps={{ | |||||
| autoComplete: 'new-password', | |||||
| }} | |||||
| rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]} | |||||
| /> | |||||
| <ProFormTextArea | <ProFormTextArea | ||||
| name="remark" | name="remark" | ||||
| label={intl.formatMessage({ | label={intl.formatMessage({ | ||||
| @@ -1,10 +1,20 @@ | |||||
| import missingPage from '@/assets/img/missing-back.png'; | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| const MissingPage = () => ( | |||||
| <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}> | |||||
| <img src={missingPage} style={{ width: '575px', margin: '278px 0 44px 0' }} alt="" /> | |||||
| <span style={{ color: '#575757', fontSize: '16px' }}>页面开发中,敬请期待......</span> | |||||
| </div> | |||||
| ); | |||||
| const MissingPage = () => { | |||||
| const navigate = useNavigate(); | |||||
| return ( | |||||
| <KFEmpty | |||||
| style={{ height: '100%' }} | |||||
| type={EmptyType.Developing} | |||||
| title="敬请期待~" | |||||
| content={'很抱歉,您访问的正在开发中,\n请耐心等待。'} | |||||
| hasFooter={true} | |||||
| backTitle="返回首页" | |||||
| onBack={() => navigate('/')} | |||||
| ></KFEmpty> | |||||
| ); | |||||
| }; | |||||
| export default MissingPage; | export default MissingPage; | ||||
| @@ -7,6 +7,7 @@ import type { AxiosRequestConfig, AxiosResponse, RequestConfig, RequestOptions } | |||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import { clearSessionToken, getAccessToken } from './access'; | import { clearSessionToken, getAccessToken } from './access'; | ||||
| import { setRemoteMenu } from './services/session'; | import { setRemoteMenu } from './services/session'; | ||||
| import Loading from './utils/loading'; | |||||
| import { gotoLoginPage } from './utils/ui'; | import { gotoLoginPage } from './utils/ui'; | ||||
| // [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead. | // [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead. | ||||
| @@ -36,12 +37,14 @@ export const requestConfig: RequestConfig = { | |||||
| headers['Authorization'] = `Bearer ${accessToken}`; | headers['Authorization'] = `Bearer ${accessToken}`; | ||||
| } | } | ||||
| } | } | ||||
| Loading.show(); | |||||
| return { url, options }; | return { url, options }; | ||||
| }, | }, | ||||
| ], | ], | ||||
| responseInterceptors: [ | responseInterceptors: [ | ||||
| [ | [ | ||||
| (response: AxiosResponse) => { | (response: AxiosResponse) => { | ||||
| Loading.hide(); | |||||
| const { status, data, config } = response || {}; | const { status, data, config } = response || {}; | ||||
| const skipErrorHandler = (config as RequestOptions)?.skipErrorHandler; | const skipErrorHandler = (config as RequestOptions)?.skipErrorHandler; | ||||
| if (status >= 200 && status < 300) { | if (status >= 200 && status < 300) { | ||||
| @@ -63,6 +66,7 @@ export const requestConfig: RequestConfig = { | |||||
| } | } | ||||
| }, | }, | ||||
| (error: Error) => { | (error: Error) => { | ||||
| Loading.hide(); | |||||
| popupError(error.message ?? '请求失败'); | popupError(error.message ?? '请求失败'); | ||||
| return Promise.reject(error); | return Promise.reject(error); | ||||
| }, | }, | ||||
| @@ -1,21 +1,34 @@ | |||||
| import { request } from '@umijs/max'; | import { request } from '@umijs/max'; | ||||
| // 分页查询数据集 | |||||
| // 查询数据集、模型分类 | |||||
| export function getAssetIcon(params) { | |||||
| return request(`/api/mmp/assetIcon`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // ----------------------------数据集--------------------------------- | |||||
| // 分页查询数据集列表 | |||||
| export function getDatasetList(params) { | export function getDatasetList(params) { | ||||
| return request(`/api/mmp/dataset`, { | |||||
| return request(`/api/mmp/newdataset/queryDatasets`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 分页查询模型 | |||||
| export function getModelList(params) { | |||||
| return request(`/api/mmp/models`, { | |||||
| // 查询数据集详情 | |||||
| export function getDatasetInfo(params) { | |||||
| return request(`/api/mmp/newdataset/getDatasetDetail`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 新增数据集 | // 新增数据集 | ||||
| export function addDatesetAndVesion(data) { | |||||
| return request(`/api/mmp/dataset/addDatasetAndVersion`, { | |||||
| export function addDateset(data) { | |||||
| return request(`/api/mmp/newdataset/addDatasetAndVersion`, { | |||||
| method: 'POST', | method: 'POST', | ||||
| headers: { | headers: { | ||||
| 'Content-Type': 'application/json;charset=UTF-8', | 'Content-Type': 'application/json;charset=UTF-8', | ||||
| @@ -23,9 +36,35 @@ export function addDatesetAndVesion(data) { | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 新增模型 | |||||
| export function addModel(data) { | |||||
| return request(`/api/mmp/models/addModelAndVersion`, { | |||||
| // 删除数据集 | |||||
| export function deleteDataset(params) { | |||||
| return request(`/api/mmp/newdataset/deleteDataset`, { | |||||
| method: 'DELETE', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 查询数据集版本列表 | |||||
| export function getDatasetVersionList(params) { | |||||
| return request(`/api/mmp/newdataset/getVersionList`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 查询数据集版本文件列表 | |||||
| // export function getDatasetVersionFiles(params) { | |||||
| // return request(`/api/mmp/datasetVersion/versions`, { | |||||
| // method: 'GET', | |||||
| // params, | |||||
| // }); | |||||
| // } | |||||
| // 新增数据集版本 | |||||
| export function addDatasetVersion(data) { | |||||
| return request(`/api/mmp/newdataset/addVersion`, { | |||||
| method: 'POST', | method: 'POST', | ||||
| headers: { | headers: { | ||||
| 'Content-Type': 'application/json;charset=UTF-8', | 'Content-Type': 'application/json;charset=UTF-8', | ||||
| @@ -33,75 +72,74 @@ export function addModel(data) { | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 查询数据集简介 | |||||
| export function getDatasetById(id) { | |||||
| return request(`/api/mmp/dataset/${id}`, { | |||||
| // 下载数据集所有文件 | |||||
| export function downloadAllFiles(params) { | |||||
| return request(`/api/mmp/newdataset/downloadAllFiles`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params | |||||
| }); | }); | ||||
| } | } | ||||
| // 查询左侧列表 | |||||
| export function getAssetIcon(params) { | |||||
| return request(`/api/mmp/assetIcon`, { | |||||
| // 下载数据集单个文件 | |||||
| export function downloadSingleFile(params) { | |||||
| return request(`/api/mmp/newdataset/downloadSinggerFile`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 查询模型简介 | |||||
| export function getModelById(id) { | |||||
| return request(`/api/mmp/models/${id}`, { | |||||
| method: 'GET', | |||||
| // 删除数据集版本 | |||||
| export function deleteDatasetVersion(params) { | |||||
| return request(`/api/mmp/newdataset/deleteDatasetVersion`, { | |||||
| method: 'DELETE', | |||||
| params, | |||||
| }); | }); | ||||
| } | } | ||||
| // 查询数据版本集 | |||||
| export function getDatasetVersionsById(id) { | |||||
| return request(`/api/mmp/dataset/versions/${id}`, { | |||||
| // ----------------------------模型--------------------------------- | |||||
| // 分页查询模型列表 | |||||
| export function getModelList(params) { | |||||
| return request(`/api/mmp/models`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | |||||
| }); | }); | ||||
| } | } | ||||
| // 查询模型版本集 | |||||
| export function getModelVersionsById(id) { | |||||
| return request(`/api/mmp/models/versions/${id}`, { | |||||
| // 新增模型 | |||||
| export function addModel(data) { | |||||
| return request(`/api/mmp/models/addModelAndVersion`, { | |||||
| method: 'POST', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json;charset=UTF-8', | |||||
| }, | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 查询模型简介 | |||||
| export function getModelById(id) { | |||||
| return request(`/api/mmp/models/${id}`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| }); | }); | ||||
| } | } | ||||
| // 分页查询数据集 | |||||
| export function getDatasetVersionIdList(params) { | |||||
| return request(`/api/mmp/datasetVersion/versions`, { | |||||
| // 查询模型版本列表 | |||||
| export function getModelVersionsById(id) { | |||||
| return request(`/api/mmp/models/versions/${id}`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | |||||
| }); | }); | ||||
| } | } | ||||
| // 根据版本查询模型 | |||||
| // 根据版本查询文件列表 | |||||
| export function getModelVersionIdList(params) { | export function getModelVersionIdList(params) { | ||||
| return request(`/api/mmp/modelsVersion/versions`, { | return request(`/api/mmp/modelsVersion/versions`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 删除数据集 | |||||
| export function deleteDatasetVersion(params) { | |||||
| return request(`/api/mmp/datasetVersion/deleteVersion`, { | |||||
| method: 'DELETE', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 删除模型 | |||||
| export function deleteModelVersion(params) { | |||||
| return request(`/api/mmp/modelsVersion/deleteVersion`, { | |||||
| method: 'DELETE', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 新增数据集版本 | |||||
| export function addDatasetVersionDetail(data) { | |||||
| return request(`/api/mmp/datasetVersion/addDatasetVersions`, { | |||||
| method: 'POST', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json;charset=UTF-8', | |||||
| }, | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 新增模型版本 | // 新增模型版本 | ||||
| export function addModelsVersionDetail(data) { | export function addModelsVersionDetail(data) { | ||||
| return request(`/api/mmp/modelsVersion/addModelVersions`, { | return request(`/api/mmp/modelsVersion/addModelVersions`, { | ||||
| @@ -112,24 +150,22 @@ export function addModelsVersionDetail(data) { | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 下载数据集 | |||||
| export function exportDataset(id) { | |||||
| return request(`/api/mmp/dataset/download/${id}`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| // 删除模型集 | |||||
| // 删除模型 | |||||
| export function deleteModel(id) { | export function deleteModel(id) { | ||||
| return request(`/api/mmp/models/${id}`, { | return request(`/api/mmp/models/${id}`, { | ||||
| method: 'DELETE', | method: 'DELETE', | ||||
| }); | }); | ||||
| } | } | ||||
| // 删除数据集 | |||||
| export function deleteDataset(id) { | |||||
| return request(`/api/mmp/dataset/${id}`, { | |||||
| // 删除模型版本 | |||||
| export function deleteModelVersion(params) { | |||||
| return request(`/api/mmp/modelsVersion/deleteVersion`, { | |||||
| method: 'DELETE', | method: 'DELETE', | ||||
| params, | |||||
| }); | }); | ||||
| } | } | ||||
| // 获取模型依赖 | // 获取模型依赖 | ||||
| export function getModelAtlasReq(data) { | export function getModelAtlasReq(data) { | ||||
| return request(`/api/mmp/modelDependency/queryModelAtlas`, { | return request(`/api/mmp/modelDependency/queryModelAtlas`, { | ||||
| @@ -19,6 +19,8 @@ declare namespace API.System { | |||||
| updateBy: string; | updateBy: string; | ||||
| updateTime: Date; | updateTime: Date; | ||||
| remark: string; | remark: string; | ||||
| gitLinkUsername?: string; | |||||
| gitLinkPassword?: string; | |||||
| } | } | ||||
| export interface UserListParams { | export interface UserListParams { | ||||
| @@ -11,29 +11,37 @@ import zhCN from 'antd/locale/zh_CN'; | |||||
| import { createRoot } from 'react-dom/client'; | import { createRoot } from 'react-dom/client'; | ||||
| export class Loading { | export class Loading { | ||||
| static total = 0; | |||||
| static total: number = 0; | |||||
| static isShowing: boolean = false; | |||||
| static startTime: Date = new Date(); | |||||
| static removeTimeout: ReturnType<typeof setTimeout> | undefined; | |||||
| static show(props?: SpinProps) { | static show(props?: SpinProps) { | ||||
| Loading.total += 1; | |||||
| if (Loading.total > 1) { | |||||
| this.total += 1; | |||||
| if (this.total > 1) { | |||||
| return; | |||||
| } | |||||
| // 是否有延时未关闭的 loading | |||||
| if (this.isShowing) { | |||||
| this.clearRemoveTimeout(); | |||||
| return; | return; | ||||
| } | } | ||||
| const container = document.createElement('div'); | const container = document.createElement('div'); | ||||
| container.id = 'loading'; | container.id = 'loading'; | ||||
| const rootContainer = document.getElementsByTagName('main')[0]; | |||||
| const rootContainer = document.body; //document.getElementsByTagName('main')[0]; | |||||
| rootContainer?.appendChild(container); | rootContainer?.appendChild(container); | ||||
| const root = createRoot(container); | const root = createRoot(container); | ||||
| const global = globalConfig(); | const global = globalConfig(); | ||||
| let timeoutId: ReturnType<typeof setTimeout>; | |||||
| let renderTimeoutId: ReturnType<typeof setTimeout>; | |||||
| function render(spinProps: SpinProps) { | |||||
| clearTimeout(timeoutId); | |||||
| const render = (spinProps: SpinProps) => { | |||||
| clearTimeout(renderTimeoutId); | |||||
| timeoutId = setTimeout(() => { | |||||
| renderTimeoutId = setTimeout(() => { | |||||
| const rootPrefixCls = global.getPrefixCls(); | const rootPrefixCls = global.getPrefixCls(); | ||||
| const iconPrefixCls = global.getIconPrefixCls(); | const iconPrefixCls = global.getIconPrefixCls(); | ||||
| const theme = global.getTheme(); | const theme = global.getTheme(); | ||||
| const dom = <KFSpin {...spinProps} />; | const dom = <KFSpin {...spinProps} />; | ||||
| root.render( | root.render( | ||||
| <ConfigProvider | <ConfigProvider | ||||
| prefixCls={rootPrefixCls} | prefixCls={rootPrefixCls} | ||||
| @@ -45,21 +53,41 @@ export class Loading { | |||||
| </ConfigProvider>, | </ConfigProvider>, | ||||
| ); | ); | ||||
| }); | }); | ||||
| } | |||||
| }; | |||||
| render({ size: 'large', ...props, spinning: true }); | render({ size: 'large', ...props, spinning: true }); | ||||
| this.startTime = new Date(); | |||||
| this.isShowing = true; | |||||
| } | |||||
| static clearRemoveTimeout() { | |||||
| if (this.removeTimeout) { | |||||
| clearTimeout(this.removeTimeout); | |||||
| this.removeTimeout = undefined; | |||||
| } | |||||
| } | |||||
| static removeLoading() { | |||||
| this.clearRemoveTimeout(); | |||||
| const rootContainer = document.body; //document.getElementsByTagName('main')[0]; | |||||
| const container = document.getElementById('loading'); | |||||
| if (container) { | |||||
| rootContainer?.removeChild(container); | |||||
| } | |||||
| this.isShowing = false; | |||||
| } | } | ||||
| static hide(force: boolean = false) { | static hide(force: boolean = false) { | ||||
| Loading.total -= 1; | |||||
| if (Loading.total <= 0 || force) { | |||||
| Loading.total = 0; | |||||
| const rootContainer = document.getElementsByTagName('main')[0]; | |||||
| const container = document.getElementById('loading'); | |||||
| if (container) { | |||||
| rootContainer?.removeChild(container); | |||||
| } | |||||
| this.total -= 1; | |||||
| if (this.total > 0 && !force) { | |||||
| return; | |||||
| } | } | ||||
| this.total = 0; | |||||
| const duration = new Date().getTime() - this.startTime.getTime(); | |||||
| this.removeTimeout = setTimeout(() => { | |||||
| this.removeLoading(); | |||||
| }, Math.max(300 - duration, 0)); | |||||
| } | } | ||||
| } | } | ||||
| @@ -4,8 +4,20 @@ export const mirrorNameKey = 'mirror-name'; | |||||
| export const modelDeploymentInfoKey = 'model-deployment-info'; | export const modelDeploymentInfoKey = 'model-deployment-info'; | ||||
| // 编辑器 url | // 编辑器 url | ||||
| export const editorUrlKey = 'editor-url'; | export const editorUrlKey = 'editor-url'; | ||||
| // 数据集、模型资源 | |||||
| export const resourceItemKey = 'resource-item'; | |||||
| export const getSessionStorageItem = (key: string, isObject: boolean = false) => { | |||||
| /** | |||||
| * Retrieves an item from session storage by key. | |||||
| * | |||||
| * If `isObject` is true, the function attempts to parse the stored value as JSON. | |||||
| * If parsing fails, the function returns undefined. | |||||
| * | |||||
| * @param {string} key - The key of the item to retrieve | |||||
| * @param {boolean} [isObject=false] - Whether to parse the stored value as JSON | |||||
| * @return {any} The retrieved item, or undefined if not found or parsing fails | |||||
| */ | |||||
| export const getSessionStorageItem = (key: string, isObject: boolean = false): any => { | |||||
| const jsonStr = sessionStorage.getItem(key); | const jsonStr = sessionStorage.getItem(key); | ||||
| if (!isObject) { | if (!isObject) { | ||||
| return jsonStr; | return jsonStr; | ||||
| @@ -20,18 +32,40 @@ export const getSessionStorageItem = (key: string, isObject: boolean = false) => | |||||
| return undefined; | return undefined; | ||||
| }; | }; | ||||
| /** | |||||
| * Sets an item in session storage by key. | |||||
| * | |||||
| * If `isObject` is true, the function stringifies the state as JSON before storing. | |||||
| * | |||||
| * @param {string} key - The key of the item to set | |||||
| * @param {any} [state] - The value of the item to set | |||||
| * @param {boolean} [isObject=false] - Whether to stringify the state as JSON | |||||
| */ | |||||
| export const setSessionStorageItem = (key: string, state?: any, isObject: boolean = false) => { | export const setSessionStorageItem = (key: string, state?: any, isObject: boolean = false) => { | ||||
| if (state) { | if (state) { | ||||
| sessionStorage.setItem(key, isObject ? JSON.stringify(state) : state); | sessionStorage.setItem(key, isObject ? JSON.stringify(state) : state); | ||||
| } | } | ||||
| }; | }; | ||||
| /** | |||||
| * Removes an item from session storage by key. | |||||
| * | |||||
| * @param {string} key - The key of the item to remove | |||||
| */ | |||||
| export const removeSessionStorageItem = (key: string) => { | export const removeSessionStorageItem = (key: string) => { | ||||
| sessionStorage.removeItem(key); | sessionStorage.removeItem(key); | ||||
| }; | }; | ||||
| // 获取之后就删除,多用于上一个页面传递数据到下一个页面 | |||||
| export const getSessionItemThenRemove = (key: string, isObject: boolean = false) => { | |||||
| /** | |||||
| * Retrieves an item from session storage by key and then removes it. | |||||
| * | |||||
| * This function is useful for passing data from one page to another. | |||||
| * | |||||
| * @param {string} key - The key of the item to retrieve | |||||
| * @param {boolean} [isObject=false] - Whether to parse the stored value as JSON | |||||
| * @return {any} The retrieved item, or undefined if not found or parsing fails | |||||
| */ | |||||
| export const getSessionItemThenRemove = (key: string, isObject: boolean = false): any => { | |||||
| const res = getSessionStorageItem(key, isObject); | const res = getSessionStorageItem(key, isObject); | ||||
| sessionStorage.removeItem(key); | sessionStorage.removeItem(key); | ||||
| return res; | return res; | ||||