| @@ -1,14 +1,12 @@ | |||
| // https://umijs.org/config/ | |||
| import { defineConfig } from '@umijs/max'; | |||
| import { join, resolve } from 'path'; | |||
| import { join } from 'path'; | |||
| import defaultSettings from './defaultSettings'; | |||
| import proxy from './proxy'; | |||
| import routes from './routes'; | |||
| const { REACT_APP_ENV = 'dev' } = process.env; | |||
| console.log('zzz', resolve(__dirname, '../src/styles/theme.less')); | |||
| export default defineConfig({ | |||
| /** | |||
| * @name 开启 hash 模式 | |||
| @@ -42,7 +40,6 @@ export default defineConfig({ | |||
| // 如果不想要 configProvide 动态设置主题需要把这个设置为 default | |||
| // 只有设置为 variable, 才能使用 configProvide 动态设置主色调 | |||
| // 'root-entry-name': 'variable', | |||
| 'kf-success-color': '#ff0000', | |||
| }, | |||
| /** | |||
| * @name moment 的国际化配置 | |||
| @@ -193,7 +193,6 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||
| colorSuccess: themes['successColor'], | |||
| colorError: themes['errorColor'], | |||
| colorWarning: themes['warningColor'], | |||
| // fontSize: themes['fontSize'], | |||
| }; | |||
| memo.theme.components ??= {}; | |||
| memo.theme.components.Tabs = {}; | |||
| @@ -207,20 +206,19 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||
| defaultActiveBg: 'rgba(22, 100, 255, 0.12)', | |||
| defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)', | |||
| defaultActiveColor: themes['primaryColor'], | |||
| contentFontSize: themes['fontSize'], | |||
| paddingBlock: 4, | |||
| paddingInline: 15, | |||
| contentFontSize: parseInt(themes['fontSize']), | |||
| controlHeight: 34, | |||
| }; | |||
| memo.theme.components.Input = { | |||
| inputFontSize: themes['fontSize'], | |||
| paddingBlock: 4, | |||
| paddingInline: 15, | |||
| inputFontSize: parseInt(themes['fontSize']), | |||
| }; | |||
| memo.theme.components.Table = { | |||
| headerBg: 'rgba(242, 244, 247, 0.36)', | |||
| headerBorderRadius: 4, | |||
| }; | |||
| memo.theme.components.Tabs = { | |||
| titleFontSize: 16, | |||
| }; | |||
| memo.theme.cssVar = true; | |||
| // memo.theme.hashed = false; | |||
| @@ -0,0 +1,5 @@ | |||
| function CommonTableCell(text?: string | null) { | |||
| return <span>{text ?? '--'}</span>; | |||
| } | |||
| export default CommonTableCell; | |||
| @@ -0,0 +1,13 @@ | |||
| import dayjs from 'dayjs'; | |||
| function DateTableCell(text?: string | null) { | |||
| if (text === undefined || text === null || text === '') { | |||
| return <span>--</span>; | |||
| } | |||
| if (!dayjs(text).isValid()) { | |||
| return <span>日期无效</span>; | |||
| } | |||
| return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>; | |||
| } | |||
| export default DateTableCell; | |||
| @@ -17,6 +17,11 @@ | |||
| border: 1px solid @primary-color-hover; | |||
| } | |||
| &:active { | |||
| color: @primary-color; | |||
| border: 1px solid @primary-color; | |||
| } | |||
| &--active { | |||
| color: @primary-color; | |||
| border: 1px solid @primary-color; | |||
| @@ -1,7 +1,7 @@ | |||
| .kf-page-title { | |||
| display: flex; | |||
| align-items: center; | |||
| height: 49px; | |||
| height: 50px; | |||
| padding-left: 30px; | |||
| background-image: url('../../assets/img/page-title-bg.png'); | |||
| } | |||
| @@ -4,6 +4,7 @@ body, | |||
| height: 100%; | |||
| margin: 0; | |||
| padding: 0; | |||
| overflow-y: hidden; | |||
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, | |||
| 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', | |||
| 'Noto Color Emoji'; | |||
| @@ -181,13 +182,13 @@ body { | |||
| margin-left: 20px; | |||
| } | |||
| .ant-pagination .ant-pagination-item-active a { | |||
| color: #fff; | |||
| // color: #fff; | |||
| border-radius: 6px; | |||
| } | |||
| .ant-pagination .ant-pagination-item-active:hover { | |||
| color: #fff; | |||
| background: rgba(22, 100, 255, 0.8); | |||
| border-color: rgba(22, 100, 255, 0.8); | |||
| // color: #fff; | |||
| // background: rgba(22, 100, 255, 0.8); | |||
| // border-color: rgba(22, 100, 255, 0.8); | |||
| border-radius: 6px; | |||
| } | |||
| .ant-pagination .ant-pagination-item { | |||
| @@ -6,10 +6,6 @@ | |||
| import { MirrorVersionStatus } from '@/enums'; | |||
| import styles from './index.less'; | |||
| type MirrorStatusCellProps = { | |||
| status: MirrorVersionStatus; | |||
| }; | |||
| type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; | |||
| type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; | |||
| @@ -33,7 +29,10 @@ const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = { | |||
| }, | |||
| }; | |||
| function MirrorStatusCell({ status }: MirrorStatusCellProps) { | |||
| function MirrorStatusCell(status: MirrorVersionStatus) { | |||
| if (status === null || status === undefined || !statusInfo[status]) { | |||
| return <span>--</span>; | |||
| } | |||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | |||
| } | |||
| @@ -4,7 +4,7 @@ | |||
| height: 100%; | |||
| &__content { | |||
| height: calc(100% - 59px); | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| padding: 30px 30px 10px; | |||
| overflow: auto; | |||
| @@ -3,15 +3,20 @@ | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 创建镜像 | |||
| */ | |||
| import { getAccessToken } from '@/access'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { createPrivateMirrorReq, createPublicMirrorReq } from '@/services/mirror'; | |||
| import { createMirrorReq } from '@/services/mirror'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate, useSearchParams } from '@umijs/max'; | |||
| import { Button, Col, Form, Input, Row, message } from 'antd'; | |||
| import { mirrorNameKey } from '@/utils/sessionKeys'; | |||
| import { getFileListFromEvent } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd'; | |||
| import { omit } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './create.less'; | |||
| type FormData = { | |||
| @@ -19,7 +24,8 @@ type FormData = { | |||
| tag: string; | |||
| description: string; | |||
| path?: string; | |||
| type: string; | |||
| upload_type: string; | |||
| fileList?: UploadFile[]; | |||
| }; | |||
| const mirrorRadioItems: KFRadioItem[] = [ | |||
| @@ -37,15 +43,64 @@ const mirrorRadioItems: KFRadioItem[] = [ | |||
| function MirrorCreate() { | |||
| const navgite = useNavigate(); | |||
| const [seachParams] = useSearchParams(); | |||
| const [form] = Form.useForm(); | |||
| const isPublic = seachParams.get('isPublic') === 'true'; | |||
| const [nameDisabled, setNameDisabled] = useState(false); | |||
| const uploadProps: UploadProps = { | |||
| action: '/api/mmp/image/upload', | |||
| headers: { | |||
| Authorization: getAccessToken() || '', | |||
| }, | |||
| maxCount: 1, | |||
| defaultFileList: [], | |||
| }; | |||
| useEffect(() => { | |||
| const name = sessionStorage.getItem(mirrorNameKey); | |||
| if (name) { | |||
| form.setFieldValue('name', name); | |||
| setNameDisabled(true); | |||
| } | |||
| return () => { | |||
| sessionStorage.removeItem(mirrorNameKey); | |||
| }; | |||
| }, []); | |||
| // 创建公网、本地镜像 | |||
| const createPublicMirror = async (params: FormData) => { | |||
| // createPrivateMirrorReq | |||
| const req = isPublic ? createPublicMirrorReq : createPrivateMirrorReq; | |||
| const [res] = await to(req(params)); | |||
| const createPublicMirror = async (formData: FormData) => { | |||
| const upload_type = formData['upload_type']; | |||
| let params; | |||
| if (upload_type === CommonTabKeys.Public) { | |||
| params = { | |||
| ...omit(formData, ['upload_type']), | |||
| upload_type: 0, | |||
| image_type: 0, | |||
| }; | |||
| } else { | |||
| const fileList = formData['fileList'] ?? []; | |||
| if (fileList.length === 0) { | |||
| message.error('请上传文件'); | |||
| return; | |||
| } | |||
| const file = fileList[0]; | |||
| if (file.status === 'uploading') { | |||
| message.error('请等待文件上传完成'); | |||
| return; | |||
| } else if (file.status === 'error') { | |||
| message.error('文件上传失败,请重新上传文件'); | |||
| return; | |||
| } | |||
| params = { | |||
| ...omit(formData, ['fileList', 'upload_type']), | |||
| path: file.response.data.url, | |||
| file_size: file.response.data.fileSize, | |||
| upload_type: 1, | |||
| image_type: 0, | |||
| }; | |||
| } | |||
| const [res] = await to(createMirrorReq(params)); | |||
| if (res) { | |||
| message.success('创建成功'); | |||
| navgite(-1); | |||
| @@ -54,13 +109,26 @@ function MirrorCreate() { | |||
| // 提交 | |||
| const handleSubmit = (values: FormData) => { | |||
| console.log(values); | |||
| createPublicMirror(values); | |||
| }; | |||
| // 取消 | |||
| const cancel = () => { | |||
| navgite(-1); | |||
| }; | |||
| const beforeUpload: UploadProps['beforeUpload'] = () => { | |||
| const fileList = form.getFieldValue('fileList'); | |||
| if (fileList.length >= 1) { | |||
| message.error('只允许上传一个文件'); | |||
| return Upload.LIST_IGNORE; | |||
| } | |||
| return true; | |||
| }; | |||
| return ( | |||
| <div className={styles['mirror-create']}> | |||
| <PageTitle title="基本信息"></PageTitle> | |||
| <PageTitle title="创建镜像"></PageTitle> | |||
| <div className={styles['mirror-create__content']}> | |||
| <div> | |||
| <Form | |||
| @@ -69,7 +137,7 @@ function MirrorCreate() { | |||
| wrapperCol={{ flex: 1 }} | |||
| labelAlign="left" | |||
| form={form} | |||
| initialValues={{ type: CommonTabKeys.Public }} | |||
| initialValues={{ upload_type: CommonTabKeys.Public }} | |||
| onFinish={handleSubmit} | |||
| > | |||
| <SubAreaTitle | |||
| @@ -89,13 +157,19 @@ function MirrorCreate() { | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear /> | |||
| <Input | |||
| placeholder="请输入镜像名称" | |||
| maxLength={64} | |||
| disabled={nameDisabled} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label=" " | |||
| name="tag" | |||
| name="tag_name" | |||
| labelCol={{ flex: '20px' }} | |||
| wrapperCol={{ flex: 1 }} | |||
| required={false} | |||
| @@ -141,7 +215,7 @@ function MirrorCreate() { | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="构建方式" | |||
| name="type" | |||
| name="upload_type" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| @@ -155,10 +229,12 @@ function MirrorCreate() { | |||
| </Row> | |||
| <Form.Item | |||
| noStyle | |||
| shouldUpdate={(prevValues, curValues) => prevValues.type !== curValues.type} | |||
| shouldUpdate={(prevValues, curValues) => | |||
| prevValues.upload_type !== curValues.upload_type | |||
| } | |||
| > | |||
| {({ getFieldValue }) => { | |||
| const type = getFieldValue('type'); | |||
| const type = getFieldValue('upload_type'); | |||
| if (type === CommonTabKeys.Public) { | |||
| return ( | |||
| <> | |||
| @@ -199,7 +275,9 @@ function MirrorCreate() { | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="镜像文件" | |||
| name="path" | |||
| name="fileList" | |||
| valuePropName="fileList" | |||
| getValueFromEvent={getFileListFromEvent} | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| @@ -207,19 +285,11 @@ function MirrorCreate() { | |||
| }, | |||
| ]} | |||
| > | |||
| {/* <Upload {...props} data={{ uuid: uuid }}> | |||
| <Button | |||
| style={{ | |||
| fontSize: '14px', | |||
| border: '1px solid', | |||
| borderColor: '#1664ff', | |||
| background: '#fff', | |||
| }} | |||
| icon={<UploadOutlined style={{ color: '#1664ff' }} />} | |||
| > | |||
| 上传文件 | |||
| <Upload {...uploadProps} beforeUpload={beforeUpload}> | |||
| <Button type="link" style={{ paddingLeft: 0, paddingRight: 0 }}> | |||
| 选择镜像文件 | |||
| </Button> | |||
| </Upload> */} | |||
| </Upload> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| @@ -233,7 +303,12 @@ function MirrorCreate() { | |||
| <Button type="primary" htmlType="submit"> | |||
| 创建镜像 | |||
| </Button> | |||
| <Button type="default" htmlType="reset" style={{ marginLeft: '20px' }}> | |||
| <Button | |||
| type="default" | |||
| htmlType="button" | |||
| onClick={cancel} | |||
| style={{ marginLeft: '20px' }} | |||
| > | |||
| 取消 | |||
| </Button> | |||
| </Form.Item> | |||
| @@ -23,7 +23,7 @@ | |||
| } | |||
| &__content { | |||
| height: calc(100% - 59px); | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| padding: 30px 30px 0; | |||
| background-color: white; | |||
| @@ -3,16 +3,33 @@ | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 镜像详情 | |||
| */ | |||
| import CommonTableCell from '@/components/CommonTableCell'; | |||
| import DateTableCell from '@/components/DateTableCell'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { MirrorVersionStatus } from '@/enums'; | |||
| import { useDomSize } from '@/hooks'; | |||
| import { getMirrorInfoReq, getMirrorVersionListReq } from '@/services/mirror'; | |||
| import { | |||
| deleteMirrorVersionReq, | |||
| getMirrorInfoReq, | |||
| getMirrorVersionListReq, | |||
| } from '@/services/mirror'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useParams, useSearchParams } from '@umijs/max'; | |||
| import { Button, Col, ConfigProvider, Row, Table, TablePaginationConfig, TableProps } from 'antd'; | |||
| import { mirrorNameKey } from '@/utils/sessionKeys'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate, useParams, useSearchParams } from '@umijs/max'; | |||
| import { | |||
| Button, | |||
| Col, | |||
| ConfigProvider, | |||
| Flex, | |||
| Row, | |||
| Table, | |||
| message, | |||
| type TablePaginationConfig, | |||
| type TableProps, | |||
| } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import dayjs from 'dayjs'; | |||
| import { useEffect, useState } from 'react'; | |||
| @@ -27,6 +44,7 @@ type MirrorInfoData = { | |||
| }; | |||
| type MirrorVersionData = { | |||
| id: number; | |||
| version: string; | |||
| url: string; | |||
| status: string; | |||
| @@ -35,8 +53,9 @@ type MirrorVersionData = { | |||
| }; | |||
| function MirrorInfo() { | |||
| const navigate = useNavigate(); | |||
| const urlParams = useParams(); | |||
| const [seachParams] = useSearchParams(); | |||
| const [searchParams] = useSearchParams(); | |||
| const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); | |||
| const [tableData, setTableData] = useState<MirrorVersionData[]>([]); | |||
| const [topRef, { height: topHeight }] = useDomSize<HTMLDivElement>(0, 0, [mirrorInfo]); | |||
| @@ -47,7 +66,7 @@ function MirrorInfo() { | |||
| current: 1, | |||
| pageSize: 10, | |||
| }); | |||
| const isPublic = seachParams.get('isPublic') === 'true'; | |||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||
| useEffect(() => { | |||
| getMirrorInfo(); | |||
| }, []); | |||
| @@ -61,7 +80,8 @@ function MirrorInfo() { | |||
| const [res] = await to(getMirrorInfoReq(id)); | |||
| if (res && res.data) { | |||
| const { name = '', description = '', version_count = '', create_time: time } = res.data; | |||
| const create_time = time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : ''; | |||
| let create_time = | |||
| time && dayjs(time).isValid() ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '--'; | |||
| setMirrorInfo({ | |||
| name, | |||
| description, | |||
| @@ -87,6 +107,25 @@ function MirrorInfo() { | |||
| } | |||
| }; | |||
| // 删除镜像版本 | |||
| const deleteMirrorVersion = async (id: number) => { | |||
| const [res] = await to(deleteMirrorVersionReq(id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getMirrorVersionList(); | |||
| } | |||
| } | |||
| }; | |||
| // 分页切换 | |||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||
| if (action === 'paginate') { | |||
| @@ -94,8 +133,21 @@ function MirrorInfo() { | |||
| } | |||
| }; | |||
| const downloadVersion = (record: MirrorVersionData) => {}; | |||
| const removeVersion = (record: MirrorVersionData) => {}; | |||
| // 处理删除 | |||
| const handleVersionDelete = (record: MirrorVersionData) => { | |||
| modalConfirm({ | |||
| title: '删除后,该镜像版本将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteMirrorVersion(record.id); | |||
| }, | |||
| }); | |||
| }; | |||
| const createMirrorVersion = () => { | |||
| navigate(`/dataset/mirror/create`); | |||
| sessionStorage.setItem(mirrorNameKey, mirrorInfo.name || ''); | |||
| }; | |||
| const columns: TableProps<MirrorVersionData>['columns'] = [ | |||
| { | |||
| @@ -103,31 +155,34 @@ function MirrorInfo() { | |||
| dataIndex: 'tag_name', | |||
| key: 'tag_name', | |||
| width: '25%', | |||
| render: CommonTableCell, | |||
| }, | |||
| { | |||
| title: '镜像地址', | |||
| dataIndex: 'url', | |||
| key: 'url', | |||
| render: CommonTableCell, | |||
| }, | |||
| { | |||
| title: '状态', | |||
| dataIndex: 'status', | |||
| key: 'status', | |||
| width: 150, | |||
| render: (text: string) => <MirrorStatusCell status={text as MirrorVersionStatus} />, | |||
| render: MirrorStatusCell, | |||
| }, | |||
| { | |||
| title: '镜像大小', | |||
| dataIndex: 'file_size', | |||
| key: 'file_size', | |||
| width: 150, | |||
| render: CommonTableCell, | |||
| }, | |||
| { | |||
| title: '创建时间', | |||
| dataIndex: 'create_time', | |||
| key: 'create_time', | |||
| width: 200, | |||
| render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||
| render: DateTableCell, | |||
| }, | |||
| { | |||
| title: '操作', | |||
| @@ -135,7 +190,7 @@ function MirrorInfo() { | |||
| width: 150, | |||
| key: 'operation', | |||
| hidden: isPublic, | |||
| render: (_: any, record: any) => ( | |||
| render: (_: any, record: MirrorVersionData) => ( | |||
| <div> | |||
| {!isPublic && ( | |||
| <ConfigProvider | |||
| @@ -150,7 +205,7 @@ function MirrorInfo() { | |||
| size="small" | |||
| key="remove" | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| onClick={() => removeVersion(record)} | |||
| onClick={() => handleVersionDelete(record)} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| @@ -182,7 +237,7 @@ function MirrorInfo() { | |||
| <Col span={10}> | |||
| <div className={styles['mirror-info__basic__item']}> | |||
| <div className={styles['label']}>版本数:</div> | |||
| <div className={styles['value']}>{mirrorInfo.version_count || '--'}</div> | |||
| <div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| @@ -201,12 +256,21 @@ function MirrorInfo() { | |||
| </Col> | |||
| </Row> | |||
| </div> | |||
| <SubAreaTitle | |||
| title="镜像版本" | |||
| image={require('@/assets/img/mirror-version.png')} | |||
| style={{ marginTop: '40px' }} | |||
| ></SubAreaTitle> | |||
| <Flex justify="space-between" align="center" style={{ marginTop: '40px' }}> | |||
| <SubAreaTitle | |||
| title="镜像版本" | |||
| image={require('@/assets/img/mirror-version.png')} | |||
| ></SubAreaTitle> | |||
| {!isPublic && ( | |||
| <Button | |||
| type="default" | |||
| onClick={createMirrorVersion} | |||
| icon={<KFIcon type="icon-xinjian2" />} | |||
| > | |||
| 新增镜像版本 | |||
| </Button> | |||
| )} | |||
| </Flex> | |||
| </div> | |||
| <div | |||
| className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])} | |||
| @@ -1,13 +1,13 @@ | |||
| .mirror-list { | |||
| height: 100%; | |||
| &__tabs-container { | |||
| height: 49px; | |||
| height: 50px; | |||
| padding-left: 27px; | |||
| background-image: url('../../assets/img/page-title-bg.png'); | |||
| } | |||
| &__content { | |||
| height: calc(100% - 59px); | |||
| height: calc(100% - 60px); | |||
| margin-top: 10px; | |||
| padding: 20px 30px 0; | |||
| background-color: white; | |||
| @@ -17,12 +17,11 @@ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| padding: 0 10px; | |||
| } | |||
| &__table { | |||
| height: calc(100% - 44px); | |||
| margin-top: 12px; | |||
| height: calc(100% - 34px - 28px); | |||
| margin-top: 28px; | |||
| } | |||
| } | |||
| } | |||
| @@ -3,23 +3,28 @@ | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 镜像列表 | |||
| */ | |||
| import CommonTableCell from '@/components/CommonTableCell'; | |||
| import DateTableCell from '@/components/DateTableCell'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { getMirrorListReq } from '@/services/mirror'; | |||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate, useSearchParams } from '@umijs/max'; | |||
| import { | |||
| Button, | |||
| ConfigProvider, | |||
| Input, | |||
| Table, | |||
| TablePaginationConfig, | |||
| TableProps, | |||
| Tabs, | |||
| message, | |||
| type TablePaginationConfig, | |||
| type TableProps, | |||
| type TabsProps, | |||
| } from 'antd'; | |||
| import { type SearchProps } from 'antd/es/input'; | |||
| import classNames from 'classnames'; | |||
| import dayjs from 'dayjs'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './list.less'; | |||
| @@ -44,8 +49,12 @@ export type MirrorData = { | |||
| }; | |||
| function MirrorList() { | |||
| const navgite = useNavigate(); | |||
| const [activeTab, setActiveTab] = useState('Public'); | |||
| const navigate = useNavigate(); | |||
| const [searchParams, setSearchParams] = useSearchParams(); | |||
| const isPrivate = searchParams.get('isPublic') === 'false'; | |||
| const [activeTab, setActiveTab] = useState<string>( | |||
| isPrivate ? CommonTabKeys.Private : CommonTabKeys.Public, | |||
| ); | |||
| const [searchText, setSearchText] = useState(''); | |||
| const [tableData, setTableData] = useState<MirrorData[]>([]); | |||
| const [total, setTotal] = useState(0); | |||
| @@ -55,19 +64,37 @@ function MirrorList() { | |||
| current: 1, | |||
| pageSize: 10, | |||
| }); | |||
| useEffect(() => { | |||
| getMirrorList(); | |||
| }, [activeTab, pagination]); | |||
| // 切换 Tab,重置数据 | |||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | |||
| setSearchText(''); | |||
| setPagination({ | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| current: 1, | |||
| pageSize: 10, | |||
| }); | |||
| setTotal(0); | |||
| setTableData([]); | |||
| setActiveTab(value); | |||
| setSearchParams([['isPublic', value === CommonTabKeys.Public ? 'true' : 'false']], { | |||
| replace: true, | |||
| }); | |||
| }; | |||
| // 获取镜像列表 | |||
| const getMirrorList = async () => { | |||
| const params = { | |||
| const getMirrorList = async (params?: Record<string, any>) => { | |||
| const reqParams = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| name: searchText, | |||
| image_type: activeTab === CommonTabKeys.Public ? 1 : 0, | |||
| ...params, | |||
| }; | |||
| const [res] = await to(getMirrorListReq(params)); | |||
| const [res] = await to(getMirrorListReq(reqParams)); | |||
| if (res && res.data) { | |||
| const { content = [], totalElements = 0 } = res.data; | |||
| setTableData(content); | |||
| @@ -75,25 +102,57 @@ function MirrorList() { | |||
| } | |||
| }; | |||
| // 删除镜像 | |||
| const deleteMirror = async (id: number) => { | |||
| const [res] = await to(deleteMirrorReq(id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getMirrorList(); | |||
| } | |||
| } | |||
| }; | |||
| // 搜索 | |||
| const onSearch = () => { | |||
| getMirrorList(); | |||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||
| // 带参数是为了点清除时,searchText 更新不及时的问题 | |||
| getMirrorList({ | |||
| name: value, | |||
| }); | |||
| }; | |||
| // 查看详情 | |||
| const toDetail = (record: MirrorData) => { | |||
| console.log('record', record); | |||
| navgite({ | |||
| pathname: `/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`, | |||
| navigate(`/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`, { | |||
| state: { | |||
| isPublic: activeTab === CommonTabKeys.Public, | |||
| }, | |||
| }); | |||
| }; | |||
| // 删除镜像 | |||
| const deleteMirror = (record: MirrorData) => {}; | |||
| // 处理删除 | |||
| const handleMirrorDelete = (record: MirrorData) => { | |||
| modalConfirm({ | |||
| title: '删除后,该镜像将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteMirror(record.id); | |||
| }, | |||
| }); | |||
| }; | |||
| // 创建镜像 | |||
| const createMirror = () => { | |||
| navgite({ pathname: `/dataset/mirror/create?isPublic=${activeTab === CommonTabKeys.Public}` }); | |||
| navigate(`/dataset/mirror/create`); | |||
| }; | |||
| // 分页切换 | |||
| @@ -101,7 +160,7 @@ function MirrorList() { | |||
| if (action === 'paginate') { | |||
| setPagination(pagination); | |||
| } | |||
| console.log(pagination, filters, sorter, action); | |||
| // console.log(pagination, filters, sorter, action); | |||
| }; | |||
| const columns: TableProps<MirrorData>['columns'] = [ | |||
| @@ -110,32 +169,35 @@ function MirrorList() { | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| width: '30%', | |||
| render: CommonTableCell, | |||
| }, | |||
| { | |||
| title: '版本数据', | |||
| dataIndex: 'version_count', | |||
| key: 'version_count', | |||
| width: 100, | |||
| render: CommonTableCell, | |||
| }, | |||
| { | |||
| title: '镜像描述', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| //ellipsis: true, | |||
| render: CommonTableCell, | |||
| ellipsis: true, | |||
| }, | |||
| { | |||
| title: '创建时间', | |||
| dataIndex: 'create_time', | |||
| key: 'create_time', | |||
| width: 200, | |||
| render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||
| render: DateTableCell, | |||
| }, | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'operation', | |||
| width: activeTab === CommonTabKeys.Private ? 200 : 150, | |||
| key: 'operation', | |||
| render: (_: any, record: any) => ( | |||
| render: (_: any, record: MirrorData) => ( | |||
| <div> | |||
| <Button | |||
| type="link" | |||
| @@ -159,7 +221,7 @@ function MirrorList() { | |||
| size="small" | |||
| key="remove" | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| onClick={() => deleteMirror(record)} | |||
| onClick={() => handleMirrorDelete(record)} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| @@ -176,7 +238,7 @@ function MirrorList() { | |||
| <Tabs | |||
| activeKey={activeTab} | |||
| items={mirrorTabItems} | |||
| onChange={setActiveTab} | |||
| onChange={hanleTabChange} | |||
| className={styles['model-tabs']} | |||
| /> | |||
| </div> | |||
| @@ -1,9 +1,9 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||
| import { pick } from '@/utils/index'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Button, Drawer, Form, Input, Select } from 'antd'; | |||
| import { pick } from 'lodash'; | |||
| import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | |||
| import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | |||
| import Styles from './editPipeline.less'; | |||
| @@ -28,18 +28,24 @@ export function getMirrorVersionListReq(params: any) { | |||
| }); | |||
| } | |||
| // 创建公网镜像 | |||
| export function createPublicMirrorReq(data: any) { | |||
| return request(`/api/mmp/image/net`, { | |||
| // 创建镜像 | |||
| export function createMirrorReq(data: any) { | |||
| return request(`/api/mmp/image/addImageAndVersion`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 创建本地镜像 | |||
| export function createPrivateMirrorReq(data: any) { | |||
| return request(`/api/mmp/image/local`, { | |||
| method: 'POST', | |||
| data, | |||
| // 删除镜像 | |||
| export function deleteMirrorReq(id: number) { | |||
| return request(`/api/mmp/image/${id}`, { | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| // 删除镜像 | |||
| export function deleteMirrorVersionReq(id: number) { | |||
| return request(`/api/mmp/imageVersion/${id}`, { | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| @@ -14,37 +14,3 @@ export function getNameByCode(list: any[], code: any) { | |||
| }); | |||
| return name; | |||
| } | |||
| /** | |||
| * Picks specified properties from an object and returns a new object with only those properties. | |||
| * | |||
| * @param obj - The object to pick properties from. | |||
| * @param properties - An array of property names to pick from the object. | |||
| * @return A new object with only the picked properties. | |||
| */ | |||
| export function pick<T extends object, K extends keyof T>(obj: T, properties: K[]): Pick<T, K> { | |||
| const picked: Partial<T> = {}; | |||
| for (const key of properties) { | |||
| if (Object.prototype.hasOwnProperty.call(obj, key)) { | |||
| picked[key] = obj[key]; | |||
| } | |||
| } | |||
| return picked as Pick<T, K>; | |||
| } | |||
| /** | |||
| * Omit properties from an object and return a new object without those properties. | |||
| * | |||
| * @param obj - The object to omit properties from. | |||
| * @param properties - An array of property names to omit from the object. | |||
| * @return A new object without the omitted properties. | |||
| */ | |||
| export function omit<T extends object, K extends keyof T>(obj: T, properties: K[]): Omit<T, K> { | |||
| const omitted: Partial<T> = { ...obj }; | |||
| for (const key of properties) { | |||
| if (Object.prototype.hasOwnProperty.call(omitted, key)) { | |||
| delete omitted[key]; | |||
| } | |||
| } | |||
| return omitted as Omit<T, K>; | |||
| } | |||
| @@ -0,0 +1,2 @@ | |||
| // 用于新建镜像 | |||
| export const mirrorNameKey = 'mirror-name'; | |||
| @@ -4,7 +4,7 @@ | |||
| * @Description: UI 公共方法 | |||
| */ | |||
| import themes from '@/styles/theme.less'; | |||
| import { Modal, type ModalFuncProps } from 'antd'; | |||
| import { Modal, type ModalFuncProps, type UploadFile } from 'antd'; | |||
| // 自定义 Confirm 弹框 | |||
| export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) { | |||
| @@ -26,3 +26,20 @@ export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) | |||
| onOk: onOk, | |||
| }); | |||
| } | |||
| // 从事件中获取上传文件列表,用于 Upload + Form 中 | |||
| export const getFileListFromEvent = (e: any) => { | |||
| const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || []; | |||
| return fileList.map((item) => { | |||
| if (item.status === 'done') { | |||
| const { response } = item; | |||
| if (response?.code !== 200) { | |||
| return { | |||
| ...item, | |||
| status: 'error', | |||
| }; | |||
| } | |||
| } | |||
| return item; | |||
| }); | |||
| }; | |||