| @@ -0,0 +1,44 @@ | |||||
| .kf-basic-info { | |||||
| display: flex; | |||||
| flex-direction: row; | |||||
| flex-wrap: wrap; | |||||
| gap: 20px 40px; | |||||
| align-items: flex-start; | |||||
| width: 80%; | |||||
| } | |||||
| .kf-basic-info-item { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| width: calc(50% - 20px); | |||||
| font-size: 16px; | |||||
| line-height: 1.6; | |||||
| &__label { | |||||
| position: relative; | |||||
| color: @text-color-secondary; | |||||
| text-align: justify; | |||||
| text-align-last: justify; | |||||
| &::after { | |||||
| position: absolute; | |||||
| content: ':'; | |||||
| } | |||||
| } | |||||
| &__value { | |||||
| flex: 1; | |||||
| margin-left: 16px; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| } | |||||
| &__text { | |||||
| color: @text-color; | |||||
| } | |||||
| &__link:hover { | |||||
| text-decoration: underline @underline-color; | |||||
| text-underline-offset: 3px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,73 @@ | |||||
| import { isEmptyString } from '@/utils'; | |||||
| import { Link } from '@umijs/max'; | |||||
| import classNames from 'classnames'; | |||||
| import './index.less'; | |||||
| export type BasicInfoData = { | |||||
| label: string; | |||||
| value?: any; | |||||
| link?: string; | |||||
| externalLink?: string; | |||||
| format?: (_value?: any) => string | undefined; | |||||
| }; | |||||
| type BasicInfoProps = { | |||||
| datas: BasicInfoData[]; | |||||
| className?: string; | |||||
| style?: React.CSSProperties; | |||||
| labelWidth?: number; | |||||
| }; | |||||
| function BasicInfo({ datas, className, style, labelWidth = 100 }: BasicInfoProps) { | |||||
| return ( | |||||
| <div className={classNames('kf-basic-info', className)} style={style}> | |||||
| {datas.map((item) => ( | |||||
| <BasicInfoItem key={item.label} data={item} labelWidth={labelWidth} /> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| type BasicInfoItemProps = { | |||||
| data: BasicInfoData; | |||||
| labelWidth?: number; | |||||
| }; | |||||
| function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) { | |||||
| const { label, value, externalLink, link, format } = data; | |||||
| const showValue = format ? format(value) : value; | |||||
| let valueComponent = undefined; | |||||
| if (externalLink && showValue) { | |||||
| valueComponent = ( | |||||
| <a | |||||
| className="kf-basic-info-item__value kf-basic-info-item__link" | |||||
| href={externalLink} | |||||
| target="_blank" | |||||
| rel="noopener noreferrer" | |||||
| > | |||||
| {showValue} | |||||
| </a> | |||||
| ); | |||||
| } else if (link && showValue) { | |||||
| valueComponent = ( | |||||
| <Link to={link} className="kf-basic-info-item__value kf-basic-info-item__link"> | |||||
| {showValue} | |||||
| </Link> | |||||
| ); | |||||
| } else { | |||||
| valueComponent = ( | |||||
| <div className="kf-basic-info-item__value kf-basic-info-item__text"> | |||||
| {isEmptyString(showValue) ? '--' : showValue} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| return ( | |||||
| <div className="kf-basic-info-item" key={label}> | |||||
| <div className="kf-basic-info-item__label" style={{ width: labelWidth }}> | |||||
| {label} | |||||
| </div> | |||||
| {valueComponent} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default BasicInfo; | |||||
| @@ -61,7 +61,7 @@ function IframePage({ type, className, style }: IframePageProps) { | |||||
| return ( | return ( | ||||
| <div className={classNames('kf-iframe-page', className)} style={style}> | <div className={classNames('kf-iframe-page', className)} style={style}> | ||||
| {loading && <KFSpin />} | |||||
| {loading && <KFSpin size="large" />} | |||||
| <FullScreenFrame url={iframeUrl} onload={hideLoading} onerror={hideLoading} /> | <FullScreenFrame url={iframeUrl} onload={hideLoading} onerror={hideLoading} /> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -0,0 +1,39 @@ | |||||
| .kf-empty { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| width: 100%; | |||||
| &__image { | |||||
| width: 475px; | |||||
| } | |||||
| &__title { | |||||
| margin-top: 15px; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: 30px; | |||||
| 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; | |||||
| margin-bottom: 30px; | |||||
| &__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; | |||||
| buttonTitle?: string; | |||||
| onRefresh?: () => 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, | |||||
| buttonTitle = '刷新', | |||||
| onRefresh, | |||||
| }: 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={onRefresh}> | |||||
| {buttonTitle} | |||||
| </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; | ||||
| @@ -14,7 +14,7 @@ const filterResourceStandard: SelectProps<string, ComputingResource>['filterOpti | |||||
| }; | }; | ||||
| // id 从 number 转换为 string | // id 从 number 转换为 string | ||||
| const convertId = (item: any) => ({ ...item, id: String(item.id) }); | |||||
| const convertId = (item: any) => ({ ...item, id: `${item.id}-${item.identifier}` }); | |||||
| export type SelectPropsConfig = { | export type SelectPropsConfig = { | ||||
| getOptions: () => Promise<any>; // 获取下拉数据 | getOptions: () => Promise<any>; // 获取下拉数据 | ||||
| @@ -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} | |||||
| buttonTitle="返回首页" | |||||
| onRefresh={() => 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,16 @@ 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="暂无数据" | |||||
| content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} | |||||
| hasFooter={true} | |||||
| onRefresh={getDataList} | |||||
| /> | |||||
| )} | )} | ||||
| </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,8 @@ | |||||
| 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 { CategoryData, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||||
| import { addDataset } 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,8 +18,7 @@ import { | |||||
| type UploadProps, | type UploadProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import { omit } from 'lodash'; | import { omit } from 'lodash'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { CategoryData } from '../../config'; | |||||
| import { useState } from 'react'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| @@ -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: resourceConfig[ResourceType.Dataset].uploadAction, | |||||
| 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(addDataset(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="数据集版本" | ||||
| @@ -116,6 +120,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| required: true, | required: true, | ||||
| message: '请输入数据集版本', | message: '请输入数据集版本', | ||||
| }, | }, | ||||
| { | |||||
| validator: (_rule, value) => { | |||||
| if (value === 'master') { | |||||
| return Promise.reject(`版本不能为 master`); | |||||
| } | |||||
| return Promise.resolve(); | |||||
| }, | |||||
| }, | |||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} /> | <Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} /> | ||||
| @@ -125,7 +137,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 +147,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 +168,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 +203,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> | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { CategoryData } from '@/pages/Dataset/config'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||||
| import { addModel } from '@/services/dataset/index.js'; | import { addModel } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| @@ -9,6 +9,7 @@ import { | |||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| Input, | Input, | ||||
| Radio, | |||||
| Select, | Select, | ||||
| Upload, | Upload, | ||||
| UploadFile, | UploadFile, | ||||
| @@ -31,7 +32,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| // 上传组件参数 | // 上传组件参数 | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| action: '/api/mmp/models/upload', | |||||
| action: resourceConfig[ResourceType.Model].uploadAction, | |||||
| headers: { | headers: { | ||||
| Authorization: getAccessToken() || '', | Authorization: getAccessToken() || '', | ||||
| }, | }, | ||||
| @@ -53,7 +54,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| if (validateUploadFiles(fileList)) { | if (validateUploadFiles(fileList)) { | ||||
| const params = { | const params = { | ||||
| ...omit(formData, ['fileList']), | ...omit(formData, ['fileList']), | ||||
| models_version_vos: fileList.map((item) => { | |||||
| model_version_vos: fileList.map((item) => { | |||||
| const data = item.response?.data?.[0] ?? {}; | const data = item.response?.data?.[0] ?? {}; | ||||
| return { | return { | ||||
| file_name: data.fileName, | file_name: data.fileName, | ||||
| @@ -77,7 +78,13 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| form: 'form', | form: 'form', | ||||
| }} | }} | ||||
| > | > | ||||
| <Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off"> | |||||
| <Form | |||||
| name="form" | |||||
| layout="vertical" | |||||
| onFinish={onFinish} | |||||
| autoComplete="off" | |||||
| initialValues={{ is_public: false }} | |||||
| > | |||||
| <Form.Item | <Form.Item | ||||
| label="模型名称" | label="模型名称" | ||||
| name="name" | name="name" | ||||
| @@ -88,9 +95,8 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={64} /> | |||||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={50} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="模型版本" | label="模型版本" | ||||
| name="version" | name="version" | ||||
| @@ -99,40 +105,24 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| required: true, | required: true, | ||||
| message: '请输入模型版本', | message: '请输入模型版本', | ||||
| }, | }, | ||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入模型版本" allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型简介" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | { | ||||
| required: true, | |||||
| message: '请输入模型简介', | |||||
| validator: (_rule, value) => { | |||||
| if (value === 'master') { | |||||
| return Promise.reject(`版本不能为 master`); | |||||
| } | |||||
| return Promise.resolve(); | |||||
| }, | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | |||||
| placeholder="请输入模型简介" | |||||
| showCount | |||||
| maxLength={256} | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| allowClear | |||||
| /> | |||||
| <Input placeholder="请输入模型版本" showCount allowClear maxLength={64} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| {/* <Form.Item label="可见范围" name="available_range"> | |||||
| <Radio.Group> | |||||
| <Radio value="0">仅自己可见</Radio> | |||||
| <Radio value="1">工作空间可见</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> */} | |||||
| <Form.Item label="模型框架" name="model_type"> | <Form.Item label="模型框架" name="model_type"> | ||||
| <Select | <Select | ||||
| allowClear | allowClear | ||||
| placeholder="请选择模型类型" | placeholder="请选择模型类型" | ||||
| options={typeList} | options={typeList} | ||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | optionFilterProp="name" | ||||
| showSearch | showSearch | ||||
| /> | /> | ||||
| @@ -142,11 +132,39 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| 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="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型简介', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入模型简介" | |||||
| maxLength={200} | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="可见性" | |||||
| name="is_public" | |||||
| rules={[{ required: true, message: '请选择可见性' }]} | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={false}>私有</Radio> | |||||
| <Radio value={true}>公开</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| <Form.Item | <Form.Item | ||||
| label="模型文件" | label="模型文件" | ||||
| name="fileList" | name="fileList" | ||||
| @@ -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 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, | |||||
| [config.filePropKey]: 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,235 @@ | |||||
| /* | |||||
| * @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 = { | |||||
| ...pick(info, ['id', 'owner', 'identifier', 'relative_paths']), | |||||
| version, | |||||
| }; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| 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} | |||||
| version={version} | |||||
| identifier={info.identifier} | |||||
| isActive={activeTab === ResourceInfoTabKeys.Evolution} | |||||
| onVersionChange={handleVersionChange} | |||||
| ></ModelEvolution> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| const typePropertyName = config.typeParamKey as keyof ResourceData; | |||||
| const tagPropertyName = config.tagParamKey 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[typePropertyName] && ( | |||||
| <div className={styles['resource-info__top__tag']}> | |||||
| {(info[typePropertyName] as string) || '--'} | |||||
| </div> | |||||
| )} | |||||
| {info[tagPropertyName] && ( | |||||
| <div className={styles['resource-info__top__tag']}> | |||||
| {(info[tagPropertyName] 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,168 @@ | |||||
| 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 formatArray = (arr?: string[]) => { | |||||
| if (!arr || arr.length === 0) { | |||||
| return '--'; | |||||
| } | |||||
| return arr.join('\n'); | |||||
| }; | |||||
| // 版本变化 | |||||
| const handleVersionChange = (value: string) => { | |||||
| setVersion(value); | |||||
| }; | |||||
| const formatMap = (map?: Record<string, string>) => { | |||||
| if (!map || Object.keys(map).length === 0) { | |||||
| return '--'; | |||||
| } | |||||
| return Object.entries(map) | |||||
| .map(([key, value]) => `${key} = ${value}`) | |||||
| .join('\n'); | |||||
| }; | |||||
| const items = [ | |||||
| { | |||||
| 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> | |||||
| </> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| 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> | |||||
| ), | |||||
| function ResourceIntro({ resourceType, info }: ResourceIntroProps) { | |||||
| const datasetDatas: BasicInfoData[] = [ | |||||
| { | |||||
| label: '数据集名称', | |||||
| value: info.name, | |||||
| }, | |||||
| { | |||||
| label: '版本', | |||||
| value: info.version, | |||||
| }, | |||||
| { | |||||
| label: '创建人', | |||||
| value: info.create_by, | |||||
| }, | |||||
| { | |||||
| label: '更新时间', | |||||
| value: info.update_time, | |||||
| }, | |||||
| { | |||||
| label: '数据来源', | |||||
| value: info.dataset_source, | |||||
| }, | |||||
| { | |||||
| 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 modelDatas: BasicInfoData[] = [ | |||||
| { | |||||
| label: '模型名称', | |||||
| value: info.name, | |||||
| }, | |||||
| { | |||||
| label: '版本', | |||||
| value: info.version, | |||||
| }, | |||||
| { | |||||
| label: '创建人', | |||||
| value: info.create_by, | |||||
| }, | |||||
| { | |||||
| label: '更新时间', | |||||
| value: info.update_time, | |||||
| }, | |||||
| { | |||||
| label: '训练镜像', | |||||
| value: info.image, | |||||
| }, | |||||
| { | |||||
| label: '训练代码', | |||||
| value: info.code, | |||||
| }, | |||||
| { | |||||
| label: '训练数据集', | |||||
| value: info.train_datasets, | |||||
| format: formatArray, | |||||
| }, | |||||
| { | |||||
| label: '测试数据集', | |||||
| value: info.test_datasets, | |||||
| format: formatArray, | |||||
| }, | |||||
| { | |||||
| label: '参数', | |||||
| value: info.params, | |||||
| format: formatMap, | |||||
| }, | |||||
| { | |||||
| label: '指标', | |||||
| value: info.metrics, | |||||
| format: formatMap, | |||||
| }, | |||||
| { | |||||
| label: '训练任务', | |||||
| value: info.train_task, | |||||
| }, | |||||
| { | |||||
| label: '模型来源', | |||||
| value: info.model_source, | |||||
| }, | |||||
| { | |||||
| label: '模型框架', | |||||
| value: info.model_type, | |||||
| }, | |||||
| { | |||||
| label: '模型能力', | |||||
| value: info.model_tag, | |||||
| }, | |||||
| { | |||||
| label: '模型描述', | |||||
| value: info.description, | |||||
| }, | |||||
| { | |||||
| label: '版本描述', | |||||
| value: info.version_desc, | |||||
| }, | |||||
| ]; | |||||
| const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData; | |||||
| const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData; | |||||
| const basicDatas: BasicInfoData[] = | |||||
| resourceType === ResourceType.Dataset ? datasetDatas : modelDatas; | |||||
| 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} labelWidth={86}></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; | ||||
| @@ -40,11 +40,14 @@ function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) | |||||
| <Flex justify="space-between"> | <Flex justify="space-between"> | ||||
| <div className={styles['resource-item__time']}> | <div className={styles['resource-item__time']}> | ||||
| <img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" /> | <img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" /> | ||||
| <span>{item.create_by}</span> | |||||
| <span>{item.create_by ?? ''}</span> | |||||
| </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,12 +84,12 @@ 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, | ||||
| is_public: isPublic, | |||||
| [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, | ||||
| }; | }; | ||||
| const request = config.getList; | const request = config.getList; | ||||
| @@ -93,13 +97,16 @@ function ResourceList( | |||||
| 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; repo_id?: number }) => { | |||||
| 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 +123,7 @@ function ResourceList( | |||||
| modalConfirm({ | modalConfirm({ | ||||
| title: config.deleteModalTitle, | title: config.deleteModalTitle, | ||||
| onOk: () => { | onOk: () => { | ||||
| deleteRecord(record.id); | |||||
| deleteRecord(pick(record, ['owner', 'identifier', 'id'])); | |||||
| }, | }, | ||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -131,6 +138,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 +166,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 +190,40 @@ 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="暂无数据" | |||||
| content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} | |||||
| hasFooter={true} | |||||
| onRefresh={getDataList} | |||||
| /> | |||||
| )} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,125 +1,36 @@ | |||||
| 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 filePropKey = config.filePropKey as keyof ResourceData; | |||||
| const fileList = (info[filePropKey] ?? []) as ResourceFileData[]; | |||||
| 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 +53,6 @@ function ResourceVersion({ | |||||
| </a> | </a> | ||||
| ), | ), | ||||
| }, | }, | ||||
| { | |||||
| title: '版本号', | |||||
| dataIndex: 'version', | |||||
| key: 'version', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | { | ||||
| title: '文件大小', | title: '文件大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| @@ -163,7 +68,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 +88,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,12 +98,7 @@ 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="url" /> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,20 +1,18 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { | import { | ||||
| addDatasetVersionDetail, | |||||
| addModelsVersionDetail, | |||||
| addDatasetVersion, | |||||
| addModelVersion, | |||||
| deleteDataset, | deleteDataset, | ||||
| deleteDatasetVersion, | deleteDatasetVersion, | ||||
| deleteModel, | deleteModel, | ||||
| deleteModelVersion, | deleteModelVersion, | ||||
| getDatasetById, | |||||
| getDatasetInfo, | |||||
| getDatasetList, | getDatasetList, | ||||
| getDatasetVersionIdList, | |||||
| getDatasetVersionsById, | |||||
| getModelById, | |||||
| getDatasetVersionList, | |||||
| getModelInfo, | |||||
| getModelList, | getModelList, | ||||
| getModelVersionIdList, | |||||
| getModelVersionsById, | |||||
| getModelVersionList, | |||||
| } 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>; // 删除版本 | ||||
| @@ -34,7 +31,7 @@ type ResourceTypeInfo = { | |||||
| name: string; // 名称 | name: string; // 名称 | ||||
| typeParamKey: string; // 类型参数名称,获取资源列表接口使用 | typeParamKey: string; // 类型参数名称,获取资源列表接口使用 | ||||
| tagParamKey: string; // 标签参数名称,获取资源列表接口使用 | tagParamKey: string; // 标签参数名称,获取资源列表接口使用 | ||||
| fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用 | |||||
| filePropKey: string; | |||||
| tabItems: TabsProps['items']; // tab 列表 | tabItems: TabsProps['items']; // tab 列表 | ||||
| typeTitle: string; // 类型标题 | typeTitle: string; // 类型标题 | ||||
| tagTitle: string; // 标签标题 | tagTitle: string; // 标签标题 | ||||
| @@ -43,28 +40,24 @@ type ResourceTypeInfo = { | |||||
| prefix: string; // 图片资源、详情 url 的前缀 | prefix: string; // 图片资源、详情 url 的前缀 | ||||
| deleteModalTitle: string; // 删除弹框的title | deleteModalTitle: string; // 删除弹框的title | ||||
| addBtnTitle: string; // 新增按钮的title | addBtnTitle: string; // 新增按钮的title | ||||
| idParamKey: 'models_id' | 'dataset_id'; // 新建版本、删除版本接口,版本 id 的参数名称 | |||||
| uploadAction: string; // 上传接口 url | uploadAction: string; // 上传接口 url | ||||
| uploadAccept?: string; // 上传文件类型 | uploadAccept?: string; // 上传文件类型 | ||||
| downloadAllAction: string; // 批量下载接口 url | downloadAllAction: string; // 批量下载接口 url | ||||
| downloadSingleAction: string; // 单个下载接口 url | downloadSingleAction: string; // 单个下载接口 url | ||||
| infoTypePropertyName: string; // 详情数据中,类型属性名称 | |||||
| infoTagPropertyName: string; // 详情数据中,标签属性名称 | |||||
| }; | }; | ||||
| 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', | ||||
| fileReqParamKey: 'dataset_id', | |||||
| filePropKey: 'dataset_version_vos', | |||||
| tabItems: [ | tabItems: [ | ||||
| { | { | ||||
| key: CommonTabKeys.Public, | key: CommonTabKeys.Public, | ||||
| @@ -84,26 +77,22 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| prefix: 'dataset', | prefix: 'dataset', | ||||
| deleteModalTitle: '确定删除该条数据集实例吗?', | deleteModalTitle: '确定删除该条数据集实例吗?', | ||||
| addBtnTitle: '新建数据集', | addBtnTitle: '新建数据集', | ||||
| 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', | |||||
| }, | }, | ||||
| [ResourceType.Model]: { | [ResourceType.Model]: { | ||||
| getList: getModelList, | getList: getModelList, | ||||
| getVersions: getModelVersionsById, | |||||
| getFiles: getModelVersionIdList, | |||||
| getVersions: getModelVersionList, | |||||
| deleteRecord: deleteModel, | deleteRecord: deleteModel, | ||||
| addVersion: addModelsVersionDetail, | |||||
| addVersion: addModelVersion, | |||||
| deleteVersion: deleteModelVersion, | deleteVersion: deleteModelVersion, | ||||
| getInfo: getModelById, | |||||
| getInfo: getModelInfo, | |||||
| name: '模型', | name: '模型', | ||||
| typeParamKey: 'model_type', | typeParamKey: 'model_type', | ||||
| tagParamKey: 'model_tag', | tagParamKey: 'model_tag', | ||||
| fileReqParamKey: 'models_id', | |||||
| filePropKey: 'model_version_vos', | |||||
| tabItems: [ | tabItems: [ | ||||
| { | { | ||||
| key: CommonTabKeys.Public, | key: CommonTabKeys.Public, | ||||
| @@ -123,13 +112,10 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| prefix: 'model', | prefix: 'model', | ||||
| deleteModalTitle: '确定删除该条模型实例吗?', | deleteModalTitle: '确定删除该条模型实例吗?', | ||||
| addBtnTitle: '新建模型', | addBtnTitle: '新建模型', | ||||
| idParamKey: 'models_id', | |||||
| uploadAction: '/api/mmp/models/upload', | |||||
| uploadAction: '/api/mmp/newmodel/upload', | |||||
| uploadAccept: undefined, | uploadAccept: undefined, | ||||
| downloadAllAction: '/api/mmp/models/downloadAllFiles', | |||||
| downloadSingleAction: '/api/mmp/models/download_model', | |||||
| infoTypePropertyName: 'model_type_name', | |||||
| infoTagPropertyName: 'model_tag_name', | |||||
| downloadAllAction: '/api/mmp/newmodel/downloadAllFiles', | |||||
| downloadSingleAction: '/api/mmp/newmodel/downloadSingleFile', | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -141,36 +127,53 @@ export type CategoryData = { | |||||
| path: string; | path: string; | ||||
| }; | }; | ||||
| // 资源数据 | |||||
| // 数据集、模型列表数据 | |||||
| export type ResourceData = { | export type ResourceData = { | ||||
| id: number; | id: number; | ||||
| name: string; | name: string; | ||||
| description: string; | |||||
| create_by: string; | |||||
| update_time: string; | |||||
| available_range: number; | |||||
| model_type_name?: string; | |||||
| model_tag_name?: string; | |||||
| dataset_type_name?: string; | |||||
| dataset_tag_name?: string; | |||||
| identifier: string; | |||||
| owner: string; | |||||
| version: string; | |||||
| is_public: boolean; | |||||
| description?: string; | |||||
| create_by?: string; | |||||
| update_time?: string; | |||||
| time_ago?: string; | |||||
| version_desc?: string; | |||||
| usage?: string; | |||||
| relative_paths?: string; | |||||
| // 数据集 | |||||
| data_type?: string; // 数据集分类 | |||||
| data_tag?: string; // 研究方向 | |||||
| processing_code?: string; // 处理代码 | |||||
| dataset_source?: string; // 数据来源 | |||||
| dataset_version_vos: ResourceFileData[]; | |||||
| // 模型 | |||||
| model_type?: string; // 模型框架 | |||||
| model_tag?: string; // 模型能力 | |||||
| image?: string; // 训练镜像 | |||||
| code?: string; // 训练镜像 | |||||
| train_datasets?: string[]; // 训练数据集 | |||||
| test_datasets?: string[]; // 测试数据集 | |||||
| params?: Record<string, string>; // 参数 | |||||
| metrics?: Record<string, string>; // 指标 | |||||
| train_task?: string; // 训练任务 | |||||
| model_source?: string; // 模型来源 | |||||
| model_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 }} | ||||
| @@ -2,10 +2,10 @@ import editExperimentIcon from '@/assets/img/edit-experiment.png'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type ResourceData } from '@/pages/Dataset/config'; | import { type ResourceData } from '@/pages/Dataset/config'; | ||||
| import { | import { | ||||
| addModelsVersionDetail, | |||||
| addModelVersion, | |||||
| exportModelReq, | exportModelReq, | ||||
| getModelList, | getModelList, | ||||
| getModelVersionsById, | |||||
| getModelVersionList, | |||||
| } from '@/services/dataset'; | } from '@/services/dataset'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { InfoCircleOutlined } from '@ant-design/icons'; | import { InfoCircleOutlined } from '@ant-design/icons'; | ||||
| @@ -85,7 +85,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { | |||||
| // 获取模型版本列表 | // 获取模型版本列表 | ||||
| const getModelVersions = async (id: number) => { | const getModelVersions = async (id: number) => { | ||||
| const [res] = await to(getModelVersionsById(id)); | |||||
| const [res] = await to(getModelVersionList(id)); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setVersions(res.data); | setVersions(res.data); | ||||
| } | } | ||||
| @@ -118,7 +118,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { | |||||
| // 创建模型版本 | // 创建模型版本 | ||||
| const createModelVersion = async (params: CreateModelVersionParams[]) => { | const createModelVersion = async (params: CreateModelVersionParams[]) => { | ||||
| const [res] = await to(addModelsVersionDetail(params)); | |||||
| const [res] = await to(addModelVersion(params)); | |||||
| if (res) { | if (res) { | ||||
| onOk(); | onOk(); | ||||
| } | } | ||||
| @@ -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() { | ||||
| @@ -5,13 +5,11 @@ | |||||
| */ | */ | ||||
| import { useEffectWhen } from '@/hooks'; | import { useEffectWhen } from '@/hooks'; | ||||
| import { ResourceVersionData } from '@/pages/Dataset/config'; | |||||
| import { getModelAtlasReq } from '@/services/dataset/index.js'; | import { getModelAtlasReq } from '@/services/dataset/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6, { G6GraphEvent, Graph, INode } from '@antv/g6'; | import G6, { G6GraphEvent, Graph, INode } from '@antv/g6'; | ||||
| // @ts-ignore | |||||
| import { Flex, Select } from 'antd'; | |||||
| import { Flex } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import GraphLegend from '../GraphLegend'; | import GraphLegend from '../GraphLegend'; | ||||
| import NodeTooltips from '../NodeTooltips'; | import NodeTooltips from '../NodeTooltips'; | ||||
| @@ -29,7 +27,7 @@ import { | |||||
| type modeModelEvolutionProps = { | type modeModelEvolutionProps = { | ||||
| resourceId: number; | resourceId: number; | ||||
| versionList: ResourceVersionData[]; | |||||
| identifier: string; | |||||
| version?: string; | version?: string; | ||||
| isActive: boolean; | isActive: boolean; | ||||
| onVersionChange: (version: string) => void; | onVersionChange: (version: string) => void; | ||||
| @@ -38,7 +36,7 @@ type modeModelEvolutionProps = { | |||||
| let graph: Graph; | let graph: Graph; | ||||
| function ModelEvolution({ | function ModelEvolution({ | ||||
| resourceId, | resourceId, | ||||
| versionList, | |||||
| identifier, | |||||
| version, | version, | ||||
| isActive, | isActive, | ||||
| onVersionChange, | onVersionChange, | ||||
| @@ -217,7 +215,8 @@ function ModelEvolution({ | |||||
| // 获取模型依赖 | // 获取模型依赖 | ||||
| const getModelAtlas = async () => { | const getModelAtlas = async () => { | ||||
| const params = { | const params = { | ||||
| current_model_id: resourceId, | |||||
| id: resourceId, | |||||
| identifier, | |||||
| version, | version, | ||||
| }; | }; | ||||
| const [res] = await to(getModelAtlasReq(params)); | const [res] = await to(getModelAtlasReq(params)); | ||||
| @@ -250,15 +249,6 @@ function ModelEvolution({ | |||||
| return ( | return ( | ||||
| <div className={styles['model-evolution']}> | <div className={styles['model-evolution']}> | ||||
| <Flex align="center" className={styles['model-evolution__top']}> | <Flex align="center" className={styles['model-evolution__top']}> | ||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ width: '160px', marginRight: '20px' }} | |||||
| value={version} | |||||
| allowClear | |||||
| onChange={onVersionChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <GraphLegend style={{ marginRight: 0, marginLeft: 'auto' }}></GraphLegend> | <GraphLegend style={{ marginRight: 0, marginLeft: 'auto' }}></GraphLegend> | ||||
| </Flex> | </Flex> | ||||
| <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | ||||
| @@ -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'; | ||||
| @@ -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 ModelIntro() { | |||||
| return <ResourceIntro resourceType={ResourceType.Model} />; | |||||
| function ModelInfo() { | |||||
| return <ResourceInfo resourceType={ResourceType.Model} />; | |||||
| } | } | ||||
| export default ModelIntro; | |||||
| export default ModelInfo; | |||||
| @@ -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, | |||||
| getModelInfo, | |||||
| getModelList, | getModelList, | ||||
| getModelVersionIdList, | |||||
| getModelVersionsById, | |||||
| getModelVersionList, | |||||
| } 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,344 @@ 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: getModelVersionList, | |||||
| // 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 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 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 res = await getModelVersionList(pick(parentNode, ['owner', 'identifier'])); | |||||
| if (res && res.data) { | |||||
| const list = res.data; | |||||
| return convertDatasetVersionToTreeData(key, parentNode, list); | |||||
| } else { | |||||
| return Promise.reject('获取模型版本列表失败'); | |||||
| } | |||||
| } | |||||
| async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) { | |||||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']); | |||||
| const res = await getModelInfo(params); | |||||
| if (res && res.data) { | |||||
| const path = res.data.relative_paths || ''; | |||||
| const list = res.data.model_version_vos || []; | |||||
| return { | |||||
| 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}`, | |||||
| url: url, | |||||
| 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(), | |||||
| }; | }; | ||||
| @@ -34,6 +34,11 @@ | |||||
| border-bottom: 1px solid @border-color-secondary; | border-bottom: 1px solid @border-color-secondary; | ||||
| border-radius: 0; | border-radius: 0; | ||||
| } | } | ||||
| &__tree-title { | |||||
| display: inline-block; | |||||
| .singleLine(); | |||||
| } | |||||
| } | } | ||||
| &__right { | &__right { | ||||
| @@ -6,34 +6,25 @@ | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { ResourceFileData } from '@/pages/Dataset/config'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Icon } from '@umijs/max'; | 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 = { | |||||
| id: number; // 文件 id | |||||
| file_name: string; // 文件 name | |||||
| }; | |||||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| type: ResourceSelectorType; // 数据集\模型\镜像 | type: ResourceSelectorType; // 数据集\模型\镜像 | ||||
| defaultExpandedKeys?: React.Key[]; | defaultExpandedKeys?: React.Key[]; | ||||
| @@ -44,39 +35,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 +51,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, | ||||
| @@ -112,11 +72,11 @@ function ResourceSelectorModal({ | |||||
| const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]); | const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]); | ||||
| const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]); | const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]); | ||||
| const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]); | const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]); | ||||
| const [files, setFiles] = useState<ResourceFile[]>([]); | |||||
| const [files, setFiles] = useState<ResourceFileData[]>([]); | |||||
| 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 +100,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 +113,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 +136,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 +148,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,14 +163,15 @@ 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 { | ||||
| setVersionPath(''); | |||||
| setFiles([]); | setFiles([]); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -229,10 +180,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 +194,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 +221,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, | ||||
| @@ -324,13 +276,23 @@ function ResourceSelectorModal({ | |||||
| expandedKeys={expandedKeys} | expandedKeys={expandedKeys} | ||||
| onExpand={onExpand} | onExpand={onExpand} | ||||
| checkable | checkable | ||||
| titleRender={(nodeData) => { | |||||
| return ( | |||||
| <span | |||||
| className={styles['model-selector__left__tree-title']} | |||||
| style={{ width: nodeData.isLeaf ? '370px' : '420px' }} | |||||
| > | |||||
| {nodeData.title as string} | |||||
| </span> | |||||
| ); | |||||
| }} | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| <div className={styles['model-selector__right']}> | <div className={styles['model-selector__right']}> | ||||
| <div className={styles['model-selector__right__title']}>{fileTitle}</div> | <div className={styles['model-selector__right__title']}>{fileTitle}</div> | ||||
| <div className={styles['model-selector__right__files']}> | <div className={styles['model-selector__right__files']}> | ||||
| {files.map((v) => ( | {files.map((v) => ( | ||||
| <div key={v.id} className={styles['model-selector__right__files__file']}> | |||||
| <div key={v.url} className={styles['model-selector__right__files__file']}> | |||||
| {v.file_name} | {v.file_name} | ||||
| </div> | </div> | ||||
| ))} | ))} | ||||
| @@ -129,7 +129,7 @@ const AuthUserTableList: React.FC = () => { | |||||
| { | { | ||||
| title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />, | title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />, | ||||
| dataIndex: 'option', | dataIndex: 'option', | ||||
| width: '60px', | |||||
| width: '160px', | |||||
| valueType: 'option', | valueType: 'option', | ||||
| render: (_, record) => [ | render: (_, record) => [ | ||||
| <Button | <Button | ||||
| @@ -111,10 +111,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => { | |||||
| grid={true} | grid={true} | ||||
| layout="horizontal" | layout="horizontal" | ||||
| onFinish={handleFinish} | onFinish={handleFinish} | ||||
| initialValues={{ | |||||
| login_password: '', | |||||
| confirm_password: '', | |||||
| }} | |||||
| submitter={false} | |||||
| {...formLayout} | {...formLayout} | ||||
| size="large" | size="large" | ||||
| labelAlign="right" | labelAlign="right" | ||||
| @@ -203,8 +200,9 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => { | |||||
| })} | })} | ||||
| required={dataScopeType === '1'} | required={dataScopeType === '1'} | ||||
| hidden={dataScopeType !== '1'} | hidden={dataScopeType !== '1'} | ||||
| style={{ width: '100%', padding: '0 4px' }} | |||||
| > | > | ||||
| <Row gutter={[16, 16]}> | |||||
| <Row gutter={[16, 16]} style={{ marginTop: '10px' }}> | |||||
| <Col md={24}> | <Col md={24}> | ||||
| <Checkbox.Group | <Checkbox.Group | ||||
| options={[ | options={[ | ||||
| @@ -235,7 +233,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => { | |||||
| } | } | ||||
| }} | }} | ||||
| onExpand={(expandedKeys: Key[]) => { | onExpand={(expandedKeys: Key[]) => { | ||||
| setDeptTreeExpandKey(deptTreeExpandKey.concat(expandedKeys)); | |||||
| setDeptTreeExpandKey(expandedKeys); | |||||
| }} | }} | ||||
| /> | /> | ||||
| </Col> | </Col> | ||||
| @@ -0,0 +1,12 @@ | |||||
| .user-selector-modal { | |||||
| :global { | |||||
| // 输入框高度为46px | |||||
| .ant-input-affix-wrapper { | |||||
| padding-top: 4px !important; | |||||
| padding-bottom: 4px !important; | |||||
| .ant-input { | |||||
| height: 22px !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -10,6 +10,7 @@ import { | |||||
| } from '@ant-design/pro-components'; | } from '@ant-design/pro-components'; | ||||
| import { FormattedMessage, useIntl } from '@umijs/max'; | import { FormattedMessage, useIntl } from '@umijs/max'; | ||||
| import React, { useEffect, useRef, useState } from 'react'; | import React, { useEffect, useRef, useState } from 'react'; | ||||
| import styles from './UserSelectorModal.less'; | |||||
| /* * | /* * | ||||
| * | * | ||||
| * @author whiteshader@163.com | * @author whiteshader@163.com | ||||
| @@ -90,7 +91,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => { | |||||
| return ( | return ( | ||||
| <KFModal | <KFModal | ||||
| width={800} | |||||
| width={920} | |||||
| title={intl.formatMessage({ | title={intl.formatMessage({ | ||||
| id: 'system.role.auth.user', | id: 'system.role.auth.user', | ||||
| defaultMessage: '选择用户', | defaultMessage: '选择用户', | ||||
| @@ -99,6 +100,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => { | |||||
| destroyOnClose | destroyOnClose | ||||
| onOk={handleOk} | onOk={handleOk} | ||||
| onCancel={handleCancel} | onCancel={handleCancel} | ||||
| className={styles['user-selector-modal']} | |||||
| > | > | ||||
| <ProTable<API.System.User> | <ProTable<API.System.User> | ||||
| headerTitle={intl.formatMessage({ | headerTitle={intl.formatMessage({ | ||||
| @@ -34,7 +34,7 @@ const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => { | |||||
| return ( | return ( | ||||
| <KFModal | <KFModal | ||||
| width={640} | |||||
| width={680} | |||||
| title={intl.formatMessage({ | title={intl.formatMessage({ | ||||
| id: 'system.user.auth.role', | id: 'system.user.auth.role', | ||||
| defaultMessage: '分配角色', | defaultMessage: '分配角色', | ||||
| @@ -50,10 +50,10 @@ const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => { | |||||
| grid={true} | grid={true} | ||||
| layout="horizontal" | layout="horizontal" | ||||
| onFinish={handleFinish} | onFinish={handleFinish} | ||||
| initialValues={{ | |||||
| login_password: '', | |||||
| confirm_password: '', | |||||
| }} | |||||
| submitter={false} | |||||
| size="large" | |||||
| labelAlign="right" | |||||
| autoComplete="off" | |||||
| > | > | ||||
| <ProFormSelect | <ProFormSelect | ||||
| name="roleIds" | name="roleIds" | ||||
| @@ -39,7 +39,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => { | |||||
| return ( | return ( | ||||
| <KFModal | <KFModal | ||||
| width={640} | |||||
| width={680} | |||||
| title={intl.formatMessage({ | title={intl.formatMessage({ | ||||
| id: 'system.user.reset.password', | id: 'system.user.reset.password', | ||||
| defaultMessage: '密码重置', | defaultMessage: '密码重置', | ||||
| @@ -58,8 +58,12 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => { | |||||
| password: '', | password: '', | ||||
| confirm_password: '', | confirm_password: '', | ||||
| }} | }} | ||||
| submitter={false} | |||||
| size="large" | |||||
| labelAlign="right" | |||||
| autoComplete="off" | |||||
| > | > | ||||
| <p>请输入用户{props.values.userName}的新密码!</p> | |||||
| <p>请输入用户 {props.values.userName} 的新密码!</p> | |||||
| <ProFormText.Password | <ProFormText.Password | ||||
| name="password" | name="password" | ||||
| label="登录密码" | label="登录密码" | ||||
| @@ -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} | |||||
| buttonTitle="返回首页" | |||||
| onRefresh={() => 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. | ||||
| @@ -25,6 +26,7 @@ const popupError = (error: string, skipErrorHandler: boolean | undefined = false | |||||
| * @doc https://umijs.org/docs/max/request#配置 | * @doc https://umijs.org/docs/max/request#配置 | ||||
| */ | */ | ||||
| export const requestConfig: RequestConfig = { | export const requestConfig: RequestConfig = { | ||||
| timeout: 120 * 1000, | |||||
| requestInterceptors: [ | requestInterceptors: [ | ||||
| (url: string, options: AxiosRequestConfig) => { | (url: string, options: AxiosRequestConfig) => { | ||||
| const headers = options.headers ?? {}; | const headers = options.headers ?? {}; | ||||
| @@ -36,12 +38,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 +67,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,140 +1,153 @@ | |||||
| import { request } from '@umijs/max'; | import { request } from '@umijs/max'; | ||||
| // 分页查询数据集 | |||||
| export function getDatasetList(params) { | |||||
| return request(`/api/mmp/dataset`, { | |||||
| // 查询数据集、模型分类 | |||||
| export function getAssetIcon(params) { | |||||
| return request(`/api/mmp/assetIcon`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 分页查询模型 | |||||
| export function getModelList(params) { | |||||
| return request(`/api/mmp/models`, { | |||||
| // ----------------------------数据集--------------------------------- | |||||
| // 分页查询数据集列表 | |||||
| export function getDatasetList(params) { | |||||
| return request(`/api/mmp/newdataset/queryDatasets`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 新增数据集 | |||||
| export function addDatesetAndVesion(data) { | |||||
| return request(`/api/mmp/dataset/addDatasetAndVersion`, { | |||||
| method: 'POST', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json;charset=UTF-8', | |||||
| }, | |||||
| data, | |||||
| // 查询数据集详情 | |||||
| export function getDatasetInfo(params) { | |||||
| return request(`/api/mmp/newdataset/getDatasetDetail`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | }); | ||||
| } | } | ||||
| // 新增模型 | |||||
| export function addModel(data) { | |||||
| return request(`/api/mmp/models/addModelAndVersion`, { | |||||
| // 新增数据集 | |||||
| export function addDataset(data) { | |||||
| return request(`/api/mmp/newdataset/addDatasetAndVersion`, { | |||||
| method: 'POST', | method: 'POST', | ||||
| headers: { | |||||
| 'Content-Type': 'application/json;charset=UTF-8', | |||||
| }, | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 查询数据集简介 | |||||
| export function getDatasetById(id) { | |||||
| return request(`/api/mmp/dataset/${id}`, { | |||||
| method: 'GET', | |||||
| // 删除数据集 | |||||
| export function deleteDataset(params) { | |||||
| return request(`/api/mmp/newdataset/deleteDataset`, { | |||||
| method: 'DELETE', | |||||
| params, | |||||
| }); | }); | ||||
| } | } | ||||
| // 查询左侧列表 | |||||
| export function getAssetIcon(params) { | |||||
| return request(`/api/mmp/assetIcon`, { | |||||
| // 查询数据集版本列表 | |||||
| export function getDatasetVersionList(params) { | |||||
| return request(`/api/mmp/newdataset/getVersionList`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 查询模型简介 | |||||
| export function getModelById(id) { | |||||
| return request(`/api/mmp/models/${id}`, { | |||||
| method: 'GET', | |||||
| // 新增数据集版本 | |||||
| export function addDatasetVersion(data) { | |||||
| return request(`/api/mmp/newdataset/addVersion`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | }); | ||||
| } | } | ||||
| // 查询数据版本集 | |||||
| export function getDatasetVersionsById(id) { | |||||
| return request(`/api/mmp/dataset/versions/${id}`, { | |||||
| // 下载数据集所有文件 | |||||
| export function downloadAllFiles(params) { | |||||
| return request(`/api/mmp/newdataset/downloadAllFiles`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params | |||||
| }); | }); | ||||
| } | } | ||||
| // 查询模型版本集 | |||||
| export function getModelVersionsById(id) { | |||||
| return request(`/api/mmp/models/versions/${id}`, { | |||||
| // 下载数据集单个文件 | |||||
| export function downloadSingleFile(params) { | |||||
| return request(`/api/mmp/newdataset/downloadSinggerFile`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | |||||
| }); | }); | ||||
| } | } | ||||
| // 分页查询数据集 | |||||
| export function getDatasetVersionIdList(params) { | |||||
| return request(`/api/mmp/datasetVersion/versions`, { | |||||
| method: 'GET', | |||||
| // 删除数据集版本 | |||||
| export function deleteDatasetVersion(params) { | |||||
| return request(`/api/mmp/newdataset/deleteDatasetVersion`, { | |||||
| method: 'DELETE', | |||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 根据版本查询模型 | |||||
| export function getModelVersionIdList(params) { | |||||
| return request(`/api/mmp/modelsVersion/versions`, { | |||||
| // ----------------------------模型--------------------------------- | |||||
| // 分页查询模型列表 | |||||
| export function getModelList(params) { | |||||
| return request(`/api/mmp/newmodel/queryModels`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 删除数据集 | |||||
| export function deleteDatasetVersion(params) { | |||||
| return request(`/api/mmp/datasetVersion/deleteVersion`, { | |||||
| method: 'DELETE', | |||||
| params, | |||||
| // 新增模型 | |||||
| export function addModel(data) { | |||||
| return request(`/api/mmp/newmodel/addModel`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | }); | ||||
| } | } | ||||
| // 删除模型 | // 删除模型 | ||||
| export function deleteModelVersion(params) { | |||||
| return request(`/api/mmp/modelsVersion/deleteVersion`, { | |||||
| export function deleteModel(params) { | |||||
| return request(`/api/mmp/newmodel/delete`, { | |||||
| method: 'DELETE', | method: 'DELETE', | ||||
| params, | 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) { | |||||
| return request(`/api/mmp/modelsVersion/addModelVersions`, { | |||||
| method: 'POST', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json;charset=UTF-8', | |||||
| }, | |||||
| data, | |||||
| // 查询模型详情 | |||||
| export function getModelInfo(params) { | |||||
| return request(`/api/mmp/newmodel/getModelDetail`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | }); | ||||
| } | } | ||||
| // 下载数据集 | |||||
| export function exportDataset(id) { | |||||
| return request(`/api/mmp/dataset/download/${id}`, { | |||||
| // 查询模型版本列表 | |||||
| export function getModelVersionList(params) { | |||||
| return request(`/api/mmp/newmodel/getVersionList`, { | |||||
| method: 'GET', | method: 'GET', | ||||
| params, | |||||
| }); | }); | ||||
| } | } | ||||
| // 删除模型集 | |||||
| export function deleteModel(id) { | |||||
| return request(`/api/mmp/models/${id}`, { | |||||
| method: 'DELETE', | |||||
| // 新增模型版本 | |||||
| export function addModelVersion(data) { | |||||
| return request(`/api/mmp/newmodel/addVersion`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | }); | ||||
| } | } | ||||
| // 删除数据集 | |||||
| export function deleteDataset(id) { | |||||
| return request(`/api/mmp/dataset/${id}`, { | |||||
| // 删除模型版本 | |||||
| export function deleteModelVersion(params) { | |||||
| return request(`/api/mmp/newmodel/deleteVersion`, { | |||||
| method: 'DELETE', | method: 'DELETE', | ||||
| params, | |||||
| }); | }); | ||||
| } | } | ||||
| // 获取模型依赖 | // 获取模型依赖 | ||||
| export function getModelAtlasReq(data) { | |||||
| return request(`/api/mmp/modelDependency/queryModelAtlas`, { | |||||
| method: 'POST', | |||||
| data | |||||
| export function getModelAtlasReq(params) { | |||||
| return request(`/api/mmp/newmodel/getModelDependencyTree`, { | |||||
| method: 'GET', | |||||
| params | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -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 { | ||||
| @@ -190,3 +190,13 @@ export const fittingString = (str: string, maxWidth: number, fontSize: number) = | |||||
| }); | }); | ||||
| return res; | return res; | ||||
| }; | }; | ||||
| /** | |||||
| * Checks if a given string is empty, undefined, or null. | |||||
| * | |||||
| * @param {any} str - the string to be checked | |||||
| * @return {boolean} true if the string is empty, undefined, or null, false otherwise | |||||
| */ | |||||
| export const isEmptyString = (str: any): boolean => { | |||||
| return str === '' || str === undefined || str === null; | |||||
| }; | |||||
| @@ -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; | ||||