| @@ -43,7 +43,6 @@ target/ | |||||
| ._* | ._* | ||||
| .Spotlight-V100 | .Spotlight-V100 | ||||
| .Trashes | .Trashes | ||||
| Icon? | |||||
| ehthumbs.db | ehthumbs.db | ||||
| Thumbs.db | Thumbs.db | ||||
| .factorypath | .factorypath | ||||
| @@ -19,7 +19,7 @@ const Settings: ProLayoutProps & { | |||||
| title: '复杂智能软件', | title: '复杂智能软件', | ||||
| pwa: true, | pwa: true, | ||||
| logo: '/assets/images/left-top-logo.png', | logo: '/assets/images/left-top-logo.png', | ||||
| iconfontUrl: '', | |||||
| iconfontUrl: '//at.alicdn.com/t/c/font_4509211_dfghcwme8ki.js', | |||||
| token: { | token: { | ||||
| // 参见ts声明,demo 见文档,通过token 修改样式 | // 参见ts声明,demo 见文档,通过token 修改样式 | ||||
| //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F | //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F | ||||
| @@ -135,6 +135,17 @@ export default [ | |||||
| path: '/dataset/datasetIntro/:id', | path: '/dataset/datasetIntro/:id', | ||||
| component: './Dataset/datasetIntro', | component: './Dataset/datasetIntro', | ||||
| }, | }, | ||||
| { | |||||
| name: '镜像', | |||||
| path: 'mirror', | |||||
| routes: [ | |||||
| { | |||||
| name: '镜像列表', | |||||
| path: '', | |||||
| component: './Mirror/list', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | { | ||||
| name: '模型管理', | name: '模型管理', | ||||
| path: '/dataset/modelIndex', | path: '/dataset/modelIndex', | ||||
| @@ -1,7 +1,9 @@ | |||||
| import RightContent from '@/components/RightContent'; | import RightContent from '@/components/RightContent'; | ||||
| import themes from '@/styles/theme.less'; | |||||
| import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | ||||
| import type { RunTimeLayoutConfig } from '@umijs/max'; | import type { RunTimeLayoutConfig } from '@umijs/max'; | ||||
| import { history } from '@umijs/max'; | import { history } from '@umijs/max'; | ||||
| import { RuntimeAntdConfig } from 'umi'; | |||||
| import defaultSettings from '../config/defaultSettings'; | import defaultSettings from '../config/defaultSettings'; | ||||
| import '../public/fonts/font.css'; | import '../public/fonts/font.css'; | ||||
| import { getAccessToken } from './access'; | import { getAccessToken } from './access'; | ||||
| @@ -182,3 +184,24 @@ export function render(oldRender: () => void) { | |||||
| oldRender(); | oldRender(); | ||||
| }); | }); | ||||
| } | } | ||||
| // 主题修改 | |||||
| export const antd: RuntimeAntdConfig = (memo) => { | |||||
| memo.theme ??= {}; | |||||
| memo.theme.token = { | |||||
| colorPrimary: themes['primaryColor'], | |||||
| }; | |||||
| memo.theme.components ??= {}; | |||||
| memo.theme.components.Tabs = {}; | |||||
| // memo.theme.cssVar = true; | |||||
| // memo.theme.hashed = false; | |||||
| // memo.appConfig = { | |||||
| // message: { | |||||
| // // 配置 message 最大显示数,超过限制时,最早的消息会被自动关闭 | |||||
| // maxCount: 3, | |||||
| // }, | |||||
| // }; | |||||
| return memo; | |||||
| }; | |||||
| @@ -0,0 +1 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="13.545" height="15.046" viewBox="0 0 13.545 15.046"><defs><style>.a{fill:#1664ff;}</style></defs><path class="a" d="M143.094,101.214h5.614v3.363a1.006,1.006,0,0,0,1.139,1.139h3.363v7.115a.559.559,0,0,1-.075.248,1.247,1.247,0,0,1-.195.267.832.832,0,0,1-.542.3h-9.3a.832.832,0,0,1-.542-.3,1.247,1.247,0,0,1-.195-.267.559.559,0,0,1-.075-.248V102.025h0a.559.559,0,0,1,.075-.248,1.247,1.247,0,0,1,.195-.267.832.832,0,0,1,.542-.3Zm3.546,6.358.344-.344a.577.577,0,1,0-.816-.816l-1.315,1.315h0l0,0a.577.577,0,0,0,.4,1h3.037a.818.818,0,1,1,0,1.635H146.91a.577.577,0,0,0,0,1.154h1.374a1.971,1.971,0,1,0,0-3.943Zm3.327-7.4a.889.889,0,0,0-.634-.265h-6.239a2.116,2.116,0,0,0-2.12,2.12v10.805a2.116,2.116,0,0,0,2.12,2.12h9.3a2.166,2.166,0,0,0,2.12-2.12v-7.673a.889.889,0,0,0-.256-.624Zm.05,4.237v-2.317l2.317,2.317h-2.317Z" transform="translate(-140.974 -99.905)"/></svg> | |||||
| @@ -1,22 +1,35 @@ | |||||
| // 自定义 Modal | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-15 10:01:29 | |||||
| * @Description: 自定义 Modal | |||||
| */ | |||||
| import ModalTitle from '@/components/ModalTitle'; | import ModalTitle from '@/components/ModalTitle'; | ||||
| import { Modal, type ModalProps } from 'antd'; | |||||
| import { ConfigProvider, Modal, theme, type ModalProps } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useAntdConfig } from 'umi'; | |||||
| import './index.less'; | import './index.less'; | ||||
| type KFModalProps = ModalProps & { | |||||
| const { useToken } = theme; | |||||
| export interface KFModalProps extends ModalProps { | |||||
| image: string; | image: string; | ||||
| }; | |||||
| function KFModal({ title, image, children, className, ...rest }: KFModalProps) { | |||||
| } | |||||
| function KFModal({ title, image, children, className = '', ...rest }: KFModalProps) { | |||||
| const { token } = useToken(); | |||||
| console.log('token', token); | |||||
| const antdConfig = useAntdConfig(); | |||||
| console.log('antdConfig', antdConfig); | |||||
| return ( | return ( | ||||
| <Modal | |||||
| className={classNames(['kf-modal', className])} | |||||
| {...rest} | |||||
| title={<ModalTitle title={title} image={image}></ModalTitle>} | |||||
| > | |||||
| {children} | |||||
| </Modal> | |||||
| <ConfigProvider {...antdConfig}> | |||||
| <Modal | |||||
| className={classNames(['kf-modal', className])} | |||||
| {...rest} | |||||
| title={<ModalTitle title={title} image={image}></ModalTitle>} | |||||
| > | |||||
| {children} | |||||
| </Modal> | |||||
| </ConfigProvider> | |||||
| ); | ); | ||||
| } | } | ||||
| @@ -0,0 +1,32 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 14:55:40 | |||||
| * @Description: | |||||
| */ | |||||
| // import { useEffect, useState } from 'react'; | |||||
| // import styles from './index.less'; | |||||
| // import { Tabs } from "antd" | |||||
| // function KFTabs() { | |||||
| // const [iframeUrl, setIframeUrl] = useState(''); | |||||
| // useEffect(() => { | |||||
| // }, []); | |||||
| // return ( | |||||
| // <div className={styles.container}> | |||||
| // <div className={Styles.datasetAllBox}> | |||||
| // <Tabs defaultActiveKey="1"> | |||||
| // <TabPane tab="数据广场" key="1"> | |||||
| // <PublicData /> | |||||
| // </TabPane> | |||||
| // <TabPane tab="个人数据" key="2"> | |||||
| // <PersonalData /> | |||||
| // </TabPane> | |||||
| // </Tabs> | |||||
| // </div> | |||||
| // </div> | |||||
| // ); | |||||
| // } | |||||
| // export default KFTabs; | |||||
| @@ -31,10 +31,10 @@ body { | |||||
| -webkit-font-smoothing: antialiased; | -webkit-font-smoothing: antialiased; | ||||
| -moz-osx-font-smoothing: grayscale; | -moz-osx-font-smoothing: grayscale; | ||||
| } | } | ||||
| a{ | |||||
| color:#1664ff; | |||||
| a { | |||||
| color: #1664ff; | |||||
| } | } | ||||
| .ant-btn-link{ | |||||
| .ant-btn-link { | |||||
| color: #1664ff; | color: #1664ff; | ||||
| } | } | ||||
| .ant-pro-layout .ant-pro-layout-content { | .ant-pro-layout .ant-pro-layout-content { | ||||
| @@ -52,9 +52,8 @@ a{ | |||||
| .ant-menu-light .ant-menu-item-selected { | .ant-menu-light .ant-menu-item-selected { | ||||
| background: rgba(197, 232, 255, 0.8) !important; | background: rgba(197, 232, 255, 0.8) !important; | ||||
| } | } | ||||
| .ant-pro-layout .ant-pro-sider .ant-layout-sider-children{ | |||||
| background:#f2f5f7; | |||||
| .ant-pro-layout .ant-pro-sider .ant-layout-sider-children { | |||||
| background: #f2f5f7; | |||||
| } | } | ||||
| .ant-pro-base-menu-inline { | .ant-pro-base-menu-inline { | ||||
| // height: 87vh; | // height: 87vh; | ||||
| @@ -64,12 +63,12 @@ a{ | |||||
| .ant-pro-layout .ant-pro-layout-content { | .ant-pro-layout .ant-pro-layout-content { | ||||
| background-color: transparent; | background-color: transparent; | ||||
| } | } | ||||
| .ant-table-wrapper .ant-table-pagination.ant-pagination{ | |||||
| background-color: #fff; | |||||
| .ant-table-wrapper .ant-table-pagination.ant-pagination { | |||||
| margin: 0; | margin: 0; | ||||
| padding: 21px 16px; | padding: 21px 16px; | ||||
| background-color: #fff; | |||||
| } | } | ||||
| .ant-table-wrapper .ant-table{ | |||||
| .ant-table-wrapper .ant-table { | |||||
| height: 75vh; | height: 75vh; | ||||
| } | } | ||||
| .ant-pro-global-header-logo img { | .ant-pro-global-header-logo img { | ||||
| @@ -81,38 +80,96 @@ a{ | |||||
| .ant-pro-layout .ant-pro-layout-container { | .ant-pro-layout .ant-pro-layout-container { | ||||
| height: 98vh; | height: 98vh; | ||||
| } | } | ||||
| .ant-modal .ant-modal-close-x{ | |||||
| border: 2px solid #272536; | |||||
| border-radius: 50%; | |||||
| .ant-modal-confirm .ant-modal-confirm-paragraph{ | |||||
| margin: 54px 0 auto; | |||||
| text-align: center; | |||||
| } | |||||
| .ant-modal-confirm-confirm .ant-modal-confirm-body > .anticon { | |||||
| display: none; | |||||
| } | |||||
| .ant-modal-confirm .ant-modal-confirm-btns { | |||||
| margin-top: 30px; | |||||
| text-align: center; | |||||
| } | |||||
| .ant-modal-confirm-btns .ant-btn-default{ | |||||
| width:110px; | |||||
| height:40px; | |||||
| background:rgba(22, 100, 255, 0.06); | |||||
| border-radius:10px; | |||||
| color:#1d1d20; | |||||
| font-size:18px; | |||||
| margin-right: 10px; | |||||
| border-color: transparent; | |||||
| } | |||||
| .ant-modal-confirm-btns .ant-btn-default:hover { | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border-color: transparent; | |||||
| } | |||||
| .ant-modal-confirm-btns .ant-btn-primary{ | |||||
| width:110px; | |||||
| height:40px; | |||||
| background:#1664ff; | |||||
| border-radius:10px; | |||||
| font-size: 18px; | |||||
| } | |||||
| .ant-modal .ant-input-affix-wrapper{ | |||||
| height: 46px; | |||||
| padding: 1px 11px; | |||||
| } | |||||
| .ant-modal .ant-select-single{ | |||||
| height: 46px; | |||||
| } | |||||
| .ant-modal .ant-select-single .ant-select-selector .ant-select-selection-placeholder{ | |||||
| line-height: 46px; | |||||
| } | |||||
| .ant-modal .ant-modal-close-x { | |||||
| width: 26px; | width: 26px; | ||||
| height: 26px; | height: 26px; | ||||
| color: #272536; | color: #272536; | ||||
| border: 2px solid #272536; | |||||
| border-radius: 50%; | |||||
| } | } | ||||
| .ant-modal-content{ | |||||
| margin-left: -130px; | |||||
| .ant-modal-content { | |||||
| margin-top: 50px; | margin-top: 50px; | ||||
| margin-left: -130px; | |||||
| } | |||||
| .ant-modal .ant-modal-content { | |||||
| padding: 0; | |||||
| } | |||||
| .ant-modal-confirm-body-wrapper{ | |||||
| height:303px; | |||||
| border-radius:21px; | |||||
| background-image: url(/assets/images/modal-back.png); | |||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-position: top center; | |||||
| border-radius: 0; | |||||
| } | |||||
| .ant-modal .ant-modal-content { | |||||
| border-radius: 20px; | |||||
| } | } | ||||
| .ant-modal .ant-modal-close:hover { | .ant-modal .ant-modal-close:hover { | ||||
| background-color: transparent; | background-color: transparent; | ||||
| } | } | ||||
| .ant-modal .ant-modal-footer >.ant-btn+.ant-btn{ | |||||
| .ant-modal .ant-modal-footer > .ant-btn + .ant-btn { | |||||
| margin-left: 20px; | margin-left: 20px; | ||||
| } | } | ||||
| .ant-pagination .ant-pagination-item-active a { | .ant-pagination .ant-pagination-item-active a { | ||||
| color: #fff; | color: #fff; | ||||
| background: #1664ff; | background: #1664ff; | ||||
| border-color: #1664ff; | border-color: #1664ff; | ||||
| border-radius:6px; | |||||
| border-radius: 6px; | |||||
| } | } | ||||
| .ant-pagination .ant-pagination-item-active:hover { | .ant-pagination .ant-pagination-item-active:hover { | ||||
| color: #fff; | color: #fff; | ||||
| background: rgba(22, 100, 255, 0.8); | background: rgba(22, 100, 255, 0.8); | ||||
| border-color: rgba(22, 100, 255, 0.8); | border-color: rgba(22, 100, 255, 0.8); | ||||
| border-radius:6px; | |||||
| border-radius: 6px; | |||||
| } | } | ||||
| .ant-pagination .ant-pagination-item { | .ant-pagination .ant-pagination-item { | ||||
| border: 1px solid #e6e6e6; | border: 1px solid #e6e6e6; | ||||
| border-radius:6px; | |||||
| border-radius: 6px; | |||||
| } | } | ||||
| // ::-webkit-scrollbar-button { | // ::-webkit-scrollbar-button { | ||||
| // background: #97a1bd; | // background: #97a1bd; | ||||
| @@ -152,3 +209,7 @@ ol { | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| .umi-local-svg { | |||||
| vertical-align: -1px; | |||||
| } | |||||
| @@ -24,7 +24,7 @@ export function useStateRef<T>(initialValue: T) { | |||||
| * @param initialValue - The initial visibility state of the modal. | * @param initialValue - The initial visibility state of the modal. | ||||
| * @return An array containing the visibility state and functions to open and close the modal. | * @return An array containing the visibility state and functions to open and close the modal. | ||||
| */ | */ | ||||
| export function useAntdModal(initialValue: boolean) { | |||||
| export function useVisible(initialValue: boolean) { | |||||
| const [visible, setVisible] = useState(initialValue); | const [visible, setVisible] = useState(initialValue); | ||||
| const open = useCallback(() => { | const open = useCallback(() => { | ||||
| @@ -0,0 +1 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="12.572" height="12.993" viewBox="0 0 12.572 12.993" fill="currentColor"><path class="a" d="M88.236,67l-5.66-2.951a.627.627,0,0,0-.579,0l-5.655,2.906a.627.627,0,0,0-.342.558v6.009a.629.629,0,0,0,.348.563l5.658,2.82a.627.627,0,0,0,.561,0l5.657-2.82a.629.629,0,0,0,.348-.563V67.555A.626.626,0,0,0,88.236,67Zm-6,2.842-2.124-1.046,4.811-2.577L87,67.3Zm.045-5,1.742.909-4.836,2.59-1.9-.934Zm-5.447,3.276,2.007.988v2.2a.419.419,0,0,0,.838,0V69.516l2.184,1.075V75.9l-5.029-2.509ZM82.705,75.9V70.54l5.029-2.681v5.531Z" transform="translate(-76 -63.975)"/></svg> | |||||
| @@ -0,0 +1,3 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="15.429" height="15.429" viewBox="0 0 15.429 15.429"> | |||||
| <path id="搜索" d="M98.21,97.392a7.248,7.248,0,1,0-3.422,2.26.579.579,0,1,0-.338-1.107,6.218,6.218,0,1,1,2.6-1.6.669.669,0,0,0,.007.939l2.712,2.712a.579.579,0,0,0,.818-.818Z" transform="translate(-85.333 -85.333)" fill="rgba(22,100,255,0.4)"/> | |||||
| </svg> | |||||
| @@ -0,0 +1 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="11.246" height="11.246" viewBox="0 0 11.246 11.246" fill="currentColor"><path class="a" d="M70.025,64a.2.2,0,0,1,.2.2V75.045a.2.2,0,0,1-.2.2h-.8a.2.2,0,0,1-.2-.2V64.2a.2.2,0,0,1,.2-.2Zm-2.008,1.2a.2.2,0,0,1,.2.2v.8a.2.2,0,0,1-.2.2H65.2v6.426h2.812a.2.2,0,0,1,.2.2v.8a.2.2,0,0,1-.2.2H64.2a.2.2,0,0,1-.2-.2V65.406a.2.2,0,0,1,.2-.2Zm6.226,7.631v1.2h-1.2v-1.2Zm-2.008,0v1.2h-1.2v-1.2Zm3.012-1.807v1.2h-1.2v-1.2Zm0-2.008v1.2h-1.2v-1.2Zm0-2.008v1.2h-1.2v-1.2ZM72.234,65.2v1.2h-1.2V65.2Zm2.008,0v1.2h-1.2V65.2Z" transform="translate(-64 -64)"/></svg> | |||||
| @@ -0,0 +1 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="currentColor"><path class="a" d="M872.8,755.637h0v-.012Z" transform="translate(-848.576 -735.009)"/><path class="a" d="M122.155,109.158a13,13,0,1,0-13,13,13.015,13.015,0,0,0,13-13m-13,11.135a11.134,11.134,0,1,1,11.134-11.135,11.149,11.149,0,0,1-11.134,11.135" transform="translate(-96.155 -96.158)"/><path class="a" d="M344.559,345.281l-4.141-4.154,4.137-4.091a.957.957,0,1,0-1.346-1.36l-4.141,4.1-4.08-4.092a.957.957,0,0,0-1.355,1.351l4.075,4.087-4.107,4.062a.956.956,0,0,0,1.345,1.36l4.113-4.068,4.145,4.16a.957.957,0,0,0,1.355-1.352" transform="translate(-326.072 -328.091)"/></svg> | |||||
| @@ -0,0 +1 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="12.036" height="13.5" viewBox="0 0 12.036 13.5" fill="currentColor" stroke="currentColor"><defs><style>.a{stroke-width:0.1px;}</style></defs><g transform="translate(-205.25 -176.646)"><path class="a" d="M211.268,190.1a1.261,1.261,0,0,1-.626-.167l-4.717-2.721a1.255,1.255,0,0,1-.626-1.084v-5.442a1.258,1.258,0,0,1,.626-1.084l4.717-2.721a1.271,1.271,0,0,1,1.251,0l4.717,2.721a1.255,1.255,0,0,1,.626,1.084v5.442a1.255,1.255,0,0,1-.626,1.084l-4.717,2.721A1.276,1.276,0,0,1,211.268,190.1Zm0-12.6a.478.478,0,0,0-.232.062l-4.717,2.721a.466.466,0,0,0-.232.4v5.442a.464.464,0,0,0,.232.4l4.717,2.721a.469.469,0,0,0,.463,0l4.717-2.721a.466.466,0,0,0,.232-.4V180.68a.464.464,0,0,0-.232-.4l-4.717-2.721A.465.465,0,0,0,211.268,177.5Z" transform="translate(0 0)"/><path class="a" d="M282.168,379.033a1.242,1.242,0,0,1-.616-.163l-3.722-2.1a.394.394,0,1,1,.387-.685l3.722,2.1a.461.461,0,0,0,.451,0l3.847-2.105a.394.394,0,1,1,.377.691l-3.845,2.107A1.251,1.251,0,0,1,282.168,379.033Z" transform="translate(-70.894 -195.36)"/><path class="a" d="M486.392,383.935a.392.392,0,0,1-.393-.4l.016-4.236a1.252,1.252,0,0,1,.653-1.094l3.8-2.067a.393.393,0,1,1,.375.691l-3.8,2.067a.467.467,0,0,0-.242.405l-.016,4.236A.394.394,0,0,1,486.392,383.935Z" transform="translate(-275.124 -195.422)"/><path class="a" d="M282.23,383.9a.392.392,0,0,1-.393-.391l-.016-4.236a.463.463,0,0,0-.242-.407l-3.678-2.069a.394.394,0,0,1,.385-.687l3.676,2.067a1.245,1.245,0,0,1,.647,1.092l.016,4.234a.4.4,0,0,1-.4.4Z" transform="translate(-70.962 -195.384)"/></g></svg> | |||||
| @@ -0,0 +1 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="14.893" height="13.83" viewBox="0 0 14.893 13.83" fill="currentColor"><path class="a" d="M60.718,92.155a.532.532,0,0,1,.532.532v1.064h2.66a.532.532,0,0,1,0,1.064H61.25v1.064a.532.532,0,0,1-1.064,0V92.687a.532.532,0,0,1,.532-.532Zm-2.128,1.6a.532.532,0,0,1,0,1.064H50.08a.532.532,0,1,1,0-1.064h8.51Zm-5.319-6.383a.532.532,0,0,1,.532.532v3.191a.532.532,0,1,1-1.064,0V90.027H50.08a.532.532,0,1,1,0-1.064h2.66V87.9a.532.532,0,0,1,.532-.532Zm10.638,1.6a.532.532,0,0,1,0,1.064H55.4a.532.532,0,1,1,0-1.064Zm-3.191-6.383a.532.532,0,0,1,.532.532v1.064h2.66a.532.532,0,0,1,0,1.064H61.25V86.3a.532.532,0,0,1-1.064,0V83.113a.532.532,0,0,1,.532-.532Zm-2.128,1.6a.532.532,0,0,1,0,1.064H50.08a.532.532,0,1,1,0-1.064h8.51Z" transform="translate(-49.548 -82.581)"/></svg> | |||||
| @@ -0,0 +1 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="13.247" height="13.247" viewBox="0 0 13.247 13.247" fill="currentColor"><g transform="translate(-370.617 -114.129)"><path class="a" d="M93.531,82H83.716A1.718,1.718,0,0,0,82,83.716v9.815a1.718,1.718,0,0,0,1.716,1.716h9.815a1.718,1.718,0,0,0,1.716-1.716V83.716A1.718,1.718,0,0,0,93.531,82Zm.792,11.531a.793.793,0,0,1-.792.792H83.716a.793.793,0,0,1-.792-.792V83.716a.793.793,0,0,1,.792-.792h9.815a.793.793,0,0,1,.792.792ZM84,86.549a.462.462,0,0,1,.462-.462h4.148a.462.462,0,1,1,0,.924H84.465A.462.462,0,0,1,84,86.549Zm9.215,0a.462.462,0,0,1-.462.462h-1.7V87.8a.462.462,0,1,1-.924,0V85.3a.462.462,0,0,1,.924,0v.787h1.7a.462.462,0,0,1,.462.462Zm0,4.31a.462.462,0,0,1-.462.462H87.54a.462.462,0,1,1,0-.924h5.215A.462.462,0,0,1,93.217,90.859ZM85.926,89.61v2.5a.462.462,0,0,1-.924,0v-.787h-.537a.462.462,0,1,1,0-.924H85V89.61a.462.462,0,0,1,.924,0Z" transform="translate(288.617 32.129)"/></g></svg> | |||||
| @@ -59,12 +59,14 @@ const Dataset = () => { | |||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| const [wordList, setWordList] = useState([]); | const [wordList, setWordList] = useState([]); | ||||
| const [activeTabKey, setActiveTabKey] = useState('1'); | const [activeTabKey, setActiveTabKey] = useState('1'); | ||||
| const [uuid, setUuid] = useState(Date.now()); | |||||
| const getDatasetByDetail = () => { | const getDatasetByDetail = () => { | ||||
| getDatasetById(locationParams.id).then((ret) => { | getDatasetById(locationParams.id).then((ret) => { | ||||
| console.log(ret); | console.log(ret); | ||||
| setDatasetDetailObj(ret.data); | setDatasetDetailObj(ret.data); | ||||
| }); | }); | ||||
| }; | }; | ||||
| // 获取数据集版本 | |||||
| const getDatasetVersionList = () => { | const getDatasetVersionList = () => { | ||||
| getDatasetVersionsById(locationParams.id).then((ret) => { | getDatasetVersionsById(locationParams.id).then((ret) => { | ||||
| console.log(ret); | console.log(ret); | ||||
| @@ -77,6 +79,8 @@ const Dataset = () => { | |||||
| }; | }; | ||||
| }), | }), | ||||
| ); | ); | ||||
| setVersion(ret.data[0]); | |||||
| getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id }); | |||||
| } | } | ||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -90,6 +94,7 @@ const Dataset = () => { | |||||
| form.setFieldsValue({ name: datasetDetailObj.name }); | form.setFieldsValue({ name: datasetDetailObj.name }); | ||||
| setDialogTitle('创建新版本'); | setDialogTitle('创建新版本'); | ||||
| setUuid(Date.now()); | |||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| }; | }; | ||||
| const handleCancel = () => { | const handleCancel = () => { | ||||
| @@ -102,16 +107,23 @@ const Dataset = () => { | |||||
| }; | }; | ||||
| const deleteDataset = () => { | const deleteDataset = () => { | ||||
| Modal.confirm({ | Modal.confirm({ | ||||
| title: '删除', | |||||
| content: '确定删除数据集版本?', | |||||
| title: ( | |||||
| <div> | |||||
| <img | |||||
| src="/assets/images/delete-icon.png" | |||||
| style={{ width: '120px', marginBottom: '24px' }} | |||||
| alt="" | |||||
| /> | |||||
| <div style={{ color: '#1d1d20', fontSize: '16px' }}>删除后,该数据集版本将不可恢复</div> | |||||
| </div> | |||||
| ), | |||||
| content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>, | |||||
| okText: '确认', | okText: '确认', | ||||
| cancelText: '取消', | cancelText: '取消', | ||||
| onOk: () => { | onOk: () => { | ||||
| deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => { | deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => { | ||||
| setVersion(null); | |||||
| getDatasetVersionList(); | getDatasetVersionList(); | ||||
| getDatasetVersions({ version, dataset_id: locationParams.id }); | |||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| }); | }); | ||||
| }, | }, | ||||
| @@ -124,6 +136,7 @@ const Dataset = () => { | |||||
| message.success('创建成功'); | message.success('创建成功'); | ||||
| }); | }); | ||||
| }; | }; | ||||
| // 获取版本下的文件列表 | |||||
| const getDatasetVersions = (params) => { | const getDatasetVersions = (params) => { | ||||
| getDatasetVersionIdList(params).then((res) => { | getDatasetVersionIdList(params).then((res) => { | ||||
| setWordList(res?.data?.content ?? []); | setWordList(res?.data?.content ?? []); | ||||
| @@ -368,7 +381,7 @@ const Dataset = () => { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Upload {...props}> | |||||
| <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Button | <Button | ||||
| style={{ | style={{ | ||||
| fontSize: '14px', | fontSize: '14px', | ||||
| @@ -62,7 +62,7 @@ export const getParamRules = (paramType: number, required: boolean = false): For | |||||
| }; | }; | ||||
| // 根据参数设置 label | // 根据参数设置 label | ||||
| const getParamType = (param: PipelineGlobalParam): string => { | |||||
| export const getParamType = (param: PipelineGlobalParam): string => { | |||||
| const paramTypes: Readonly<Record<number, string>> = { | const paramTypes: Readonly<Record<number, string>> = { | ||||
| 1: '字符串', | 1: '字符串', | ||||
| 2: '整型', | 2: '整型', | ||||
| @@ -1,6 +1,7 @@ | |||||
| .params_container { | .params_container { | ||||
| max-height: 230px; | max-height: 230px; | ||||
| padding: 15px 15px 0; | padding: 15px 15px 0; | ||||
| overflow-y: auto; | |||||
| border: 1px solid #e6e6e6; | border: 1px solid #e6e6e6; | ||||
| border-radius: 8px; | border-radius: 8px; | ||||
| @@ -1,6 +1,12 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-09 15:59:14 | |||||
| * @Description: 查看实验使用的参数 | |||||
| */ | |||||
| import parameterImg from '@/assets/img/modal-parameter.png'; | import parameterImg from '@/assets/img/modal-parameter.png'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { getParamType } from './addExperimentModal'; | |||||
| import styles from './paramsModal.less'; | import styles from './paramsModal.less'; | ||||
| type ParamsModalProps = { | type ParamsModalProps = { | ||||
| @@ -22,7 +28,7 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| <div className={styles.params_container}> | <div className={styles.params_container}> | ||||
| {globalParam.map((item) => ( | {globalParam.map((item) => ( | ||||
| <div key={item.param_name} className={styles.params_container_line}> | <div key={item.param_name} className={styles.params_container_line}> | ||||
| <span className={styles.params_container_line_label}>{item.param_name}</span> | |||||
| <span className={styles.params_container_line_label}>{getParamType(item)}</span> | |||||
| <span className={styles.params_container_line_value}>{item.param_value}</span> | <span className={styles.params_container_line_value}>{item.param_value}</span> | ||||
| </div> | </div> | ||||
| ))} | ))} | ||||
| @@ -390,6 +390,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| <Drawer | <Drawer | ||||
| title="任务执行详情" | title="任务执行详情" | ||||
| placement="right" | placement="right" | ||||
| rootStyle={{ marginTop: '45px' }} | |||||
| closeIcon={false} | closeIcon={false} | ||||
| onClose={onClose} | onClose={onClose} | ||||
| afterOpenChange={afterOpenChange} | afterOpenChange={afterOpenChange} | ||||
| @@ -143,16 +143,16 @@ function Experiment() { | |||||
| const [res] = await to(getTensorBoardStatusReq(params)); | const [res] = await to(getTensorBoardStatusReq(params)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setExperimentInList((prevList) => { | setExperimentInList((prevList) => { | ||||
| const newList = [...prevList]; | |||||
| const index = prevList.findIndex((item) => item.id === experimentIn.id); | |||||
| const preObj = prevList[index]; | |||||
| const newObj = { | |||||
| ...preObj, | |||||
| tensorBoardStatus: res.data.status, | |||||
| tensorboardUrl: res.data.url, | |||||
| }; | |||||
| newList.splice(index, 1, newObj); | |||||
| return newList; | |||||
| return prevList.map((item) => { | |||||
| if (item.id === experimentIn.id) { | |||||
| return { | |||||
| ...item, | |||||
| tensorBoardStatus: res.data.status, | |||||
| tensorboardUrl: res.data.url, | |||||
| }; | |||||
| } | |||||
| return item; | |||||
| }); | |||||
| }); | }); | ||||
| const timerId = setTimeout(() => { | const timerId = setTimeout(() => { | ||||
| getTensorBoardStatus(experimentIn); | getTensorBoardStatus(experimentIn); | ||||
| @@ -350,8 +350,20 @@ function Experiment() { | |||||
| icon={<DeleteOutlined />} | icon={<DeleteOutlined />} | ||||
| onClick={async () => { | onClick={async () => { | ||||
| Modal.confirm({ | Modal.confirm({ | ||||
| title: '删除', | |||||
| content: '确定删除该条实验吗?', | |||||
| title: ( | |||||
| <div> | |||||
| <img | |||||
| src="/assets/images/delete-icon.png" | |||||
| style={{ width: '120px', marginBottom: '24px' }} | |||||
| alt="" | |||||
| /> | |||||
| <div style={{ color: '#1d1d20', fontSize: '16px' }}> | |||||
| 删除后,该实验将不可恢复 | |||||
| </div> | |||||
| </div> | |||||
| ), | |||||
| content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>, | |||||
| closable: true, | |||||
| okText: '确认', | okText: '确认', | ||||
| cancelText: '取消', | cancelText: '取消', | ||||
| @@ -0,0 +1,42 @@ | |||||
| .mirror-list { | |||||
| height: 100%; | |||||
| background-color: #f9fafb; | |||||
| &__tabs-container { | |||||
| height: 59px; | |||||
| background-image: url('../../assets/img/mirror-tabs-bg.png'); | |||||
| } | |||||
| &__content { | |||||
| height: calc(100% - 69px); | |||||
| margin-top: 10px; | |||||
| padding: 20px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__filter { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| padding: 0 10px; | |||||
| } | |||||
| &__table { | |||||
| height: calc(100% - 44px); | |||||
| margin-top: 12px; | |||||
| :global { | |||||
| .ant-table-wrapper { | |||||
| height: 100%; | |||||
| .ant-spin-nested-loading { | |||||
| height: 100%; | |||||
| } | |||||
| .ant-spin-container { | |||||
| height: 100%; | |||||
| } | |||||
| .ant-table { | |||||
| height: calc(100% - 74px); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,155 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 镜像列表 | |||||
| */ | |||||
| import { getMirrorListReq } from '@/services/mirror'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Button, Input, Table } from 'antd'; | |||||
| import dayjs from 'dayjs'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import styles from './list.less'; | |||||
| const tabItems = [ | |||||
| { | |||||
| key: 'Public', | |||||
| label: '公共镜像', | |||||
| }, | |||||
| { | |||||
| key: 'Private', | |||||
| label: '个人镜像', | |||||
| }, | |||||
| ]; | |||||
| function MirrorList() { | |||||
| const [activeTab, setActiveTab] = useState('Public'); | |||||
| const [tableData, setTableData] = useState([]); | |||||
| const contentRef = useRef<HTMLDivElement>(null); | |||||
| const [tableHeight, setTableHeight] = useState(0); | |||||
| const [pagination, setPagination] = useState({ | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| total: 0, | |||||
| }); | |||||
| useEffect(() => { | |||||
| getMirrorList(''); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| const setTableScollHeight = () => { | |||||
| if (contentRef.current) { | |||||
| setTableHeight(contentRef.current.offsetHeight - 74 - 55); | |||||
| } | |||||
| }; | |||||
| setTableScollHeight(); | |||||
| window.addEventListener('resize', setTableScollHeight); | |||||
| return () => { | |||||
| window.removeEventListener('resize', setTableScollHeight); | |||||
| }; | |||||
| }, [contentRef]); | |||||
| const columns = [ | |||||
| { | |||||
| title: '镜像名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: '10%', | |||||
| }, | |||||
| { | |||||
| title: '版本数据', | |||||
| dataIndex: 'version_count', | |||||
| key: 'version_count', | |||||
| width: '10%', | |||||
| }, | |||||
| { | |||||
| title: '镜像描述', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| width: '50%', | |||||
| }, | |||||
| { | |||||
| title: '创建时间', | |||||
| dataIndex: 'create_time', | |||||
| key: 'create_time', | |||||
| width: '20%', | |||||
| render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: '100px', | |||||
| key: 'operation', | |||||
| render: (_: any, record: any) => [ | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="download" | |||||
| // onClick={(e) => downloadAlone(e, record)} | |||||
| > | |||||
| 查看详情 | |||||
| </Button>, | |||||
| ], | |||||
| }, | |||||
| ]; | |||||
| const getMirrorList = async (name: string) => { | |||||
| const params = { | |||||
| page: pagination.current - 1, | |||||
| size: pagination.pageSize, | |||||
| name, | |||||
| image_type: 1, | |||||
| }; | |||||
| const [res] = await to(getMirrorListReq(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| console.log(res); | |||||
| setTableData(content); | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| total: totalElements, | |||||
| })); | |||||
| } | |||||
| }; | |||||
| const onSearch = (value: string) => { | |||||
| getMirrorList(value); | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['mirror-list']}> | |||||
| <div className={styles['mirror-list__tabs-container']}> | |||||
| {/* <Tabs | |||||
| activeKey={activeTab} | |||||
| items={tabItems} | |||||
| onChange={setActiveTab} | |||||
| className={styles['model-tabs']} | |||||
| /> */} | |||||
| </div> | |||||
| <div className={styles['mirror-list__content']}> | |||||
| <div className={styles['mirror-list__content__filter']}> | |||||
| <Input.Search | |||||
| placeholder="按数据集名称筛选" | |||||
| allowClear | |||||
| onSearch={onSearch} | |||||
| style={{ width: 300 }} | |||||
| /> | |||||
| <Button type="default">刷新</Button> | |||||
| </div> | |||||
| <div className={styles['mirror-list__content__table']} ref={contentRef}> | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columns} | |||||
| scroll={{ y: tableHeight }} | |||||
| pagination={pagination} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default MirrorList; | |||||
| @@ -101,8 +101,17 @@ const Dataset = () => { | |||||
| }; | }; | ||||
| const deleteDataset = () => { | const deleteDataset = () => { | ||||
| Modal.confirm({ | Modal.confirm({ | ||||
| title: '删除', | |||||
| content: '确定删除模型版本?', | |||||
| title: ( | |||||
| <div> | |||||
| <img | |||||
| src="/assets/images/delete-icon.png" | |||||
| style={{ width: '120px', marginBottom: '24px' }} | |||||
| alt="" | |||||
| /> | |||||
| <div style={{ color: '#1d1d20', fontSize: '16px' }}>删除后,该模型版本将不可恢复</div> | |||||
| </div> | |||||
| ), | |||||
| content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>, | |||||
| okText: '确认', | okText: '确认', | ||||
| cancelText: '取消', | cancelText: '取消', | ||||
| @@ -0,0 +1,83 @@ | |||||
| @import '@/styles/theme.less'; | |||||
| .model-tabs { | |||||
| margin-left: 8px; | |||||
| :global { | |||||
| .ant-tabs-tab { | |||||
| padding-top: 0; | |||||
| } | |||||
| .ant-tabs-nav::before, | |||||
| div > .ant-tabs-nav::before { | |||||
| border: none; | |||||
| } | |||||
| .ant-tabs-nav { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| .model-selector { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| :global { | |||||
| .ant-input-affix-wrapper .ant-input-prefix { | |||||
| margin-inline-end: 12px; | |||||
| } | |||||
| } | |||||
| &__left { | |||||
| width: 488px; | |||||
| height: 398px; | |||||
| margin-right: 15px; | |||||
| padding: 15px; | |||||
| background-color: @background-color-primay; | |||||
| border: 1px solid @border-color; | |||||
| border-radius: 8px; | |||||
| &__search { | |||||
| margin-bottom: 14px; | |||||
| padding-left: 0; | |||||
| background-color: transparent; | |||||
| border-width: 0; | |||||
| border-bottom: 1px solid @border-color-second; | |||||
| border-radius: 0; | |||||
| } | |||||
| } | |||||
| &__right { | |||||
| width: calc(100% - 488px - 15px); | |||||
| height: 398px; | |||||
| padding: 15px; | |||||
| background-color: @background-color-primay; | |||||
| border: 1px solid @border-color; | |||||
| border-radius: 8px; | |||||
| &__title { | |||||
| margin-bottom: 15px; | |||||
| padding: 3px 0 6px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| border-bottom: 1px solid @border-color-second; | |||||
| } | |||||
| &__files { | |||||
| height: calc(100% - 75px); | |||||
| overflow-y: auto; | |||||
| &__file { | |||||
| height: 24px; | |||||
| margin-bottom: 10px; | |||||
| padding-left: 10px; | |||||
| overflow: hidden; | |||||
| color: #575757; | |||||
| font-size: 13px; | |||||
| line-height: 24px; | |||||
| white-space: nowrap; | |||||
| text-overflow: ellipsis; | |||||
| background: @background-color-gray; | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,410 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-11 16:31:18 | |||||
| * @Description: 选择数据集和模型 | |||||
| */ | |||||
| import datasetImg from '@/assets/img/modal-select-dataset.png'; | |||||
| import modelImg from '@/assets/img/modal-select-model.png'; | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { | |||||
| getDatasetList, | |||||
| getDatasetVersionIdList, | |||||
| getDatasetVersionsById, | |||||
| getModelList, | |||||
| getModelVersionIdList, | |||||
| getModelVersionsById, | |||||
| } from '@/services/dataset/index.js'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Icon } from '@umijs/max'; | |||||
| import type { GetRef, ModalProps, TabsProps, TreeDataNode, TreeProps } from 'antd'; | |||||
| import { Input, Tabs, Tree } from 'antd'; | |||||
| import React, { useEffect, useMemo, useRef, useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| export enum ResourceSelectorType { | |||||
| Model = 'Model', // 模型 | |||||
| Dataset = 'Dataset', // 数据集 | |||||
| } | |||||
| type ResourceSelectorTypeKeys = keyof typeof ResourceSelectorType; | |||||
| type ResourceSelectorTypeValues = (typeof ResourceSelectorType)[ResourceSelectorTypeKeys]; | |||||
| type GetModelFilesReqParam = { | |||||
| models_id: number; | |||||
| version: string; | |||||
| }; | |||||
| type GetDatasetFilesReqParam = { | |||||
| dataset_id: number; | |||||
| version: string; | |||||
| }; | |||||
| type GetFilesReqParam = GetModelFilesReqParam | GetDatasetFilesReqParam; | |||||
| export type SelectorTypeInfo = { | |||||
| getList: (params: { page: number; size: number; available_range: string }) => Promise<any>; | |||||
| getVersions: (params: number) => Promise<any>; | |||||
| getFiles: (params: GetFilesReqParam) => Promise<any>; | |||||
| modalIcon: string; | |||||
| buttonIcon: string; | |||||
| name: string; | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; | |||||
| tabItems: TabsProps['items']; | |||||
| }; | |||||
| enum TabItemKeys { | |||||
| Private = 'Private', // 我的 | |||||
| Public = 'Public', // 公开 | |||||
| } | |||||
| export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeInfo> = { | |||||
| Model: { | |||||
| getList: getModelList, | |||||
| getVersions: getModelVersionsById, | |||||
| getFiles: getModelVersionIdList, | |||||
| name: '模型', | |||||
| modalIcon: modelImg, | |||||
| buttonIcon: 'local:model-select-button', | |||||
| fileReqParamKey: 'models_id', | |||||
| tabItems: [ | |||||
| { | |||||
| key: TabItemKeys.Private, | |||||
| label: '我的模型', | |||||
| }, | |||||
| { | |||||
| key: TabItemKeys.Public, | |||||
| label: '公开模型', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| Dataset: { | |||||
| getList: getDatasetList, | |||||
| getVersions: getDatasetVersionsById, | |||||
| getFiles: getDatasetVersionIdList, | |||||
| name: '数据集', | |||||
| modalIcon: datasetImg, | |||||
| buttonIcon: 'local:dataset-select-button', | |||||
| fileReqParamKey: 'dataset_id', | |||||
| tabItems: [ | |||||
| { | |||||
| key: TabItemKeys.Private, | |||||
| label: '我的数据集', | |||||
| }, | |||||
| { | |||||
| key: TabItemKeys.Public, | |||||
| label: '公开数据集', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| }; | |||||
| type ResourceSelectorResponse = { | |||||
| id: number; // 数据集或者模型 id | |||||
| name: string; // 数据集或者模型 name | |||||
| version: string; // 数据集或者模型版本 | |||||
| path: string; // 数据集或者模型版本路径 | |||||
| activeTab: TabItemKeys; // 是我的还是公开的 | |||||
| }; | |||||
| interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| type: ResourceSelectorType; // 模型 | 数据集 | |||||
| defaultExpandedKeys: React.Key[]; | |||||
| defaultCheckedKeys: React.Key[]; | |||||
| defaultActiveTab: TabItemKeys; | |||||
| onOk?: (params: ResourceSelectorResponse | null) => void; | |||||
| } | |||||
| type ResourceGroup = { | |||||
| id: number; // 数据集或者模型 id | |||||
| name: string; // 数据集或者模型 id | |||||
| }; | |||||
| type ResourceFile = { | |||||
| id: number; // 文件 id | |||||
| file_name: string; // 文件 name | |||||
| }; | |||||
| 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, | |||||
| })); | |||||
| }; | |||||
| // 更新树形结构的 children | |||||
| const updateChildren = (parentId: number, children: TreeDataNode[]) => { | |||||
| return (node: TreeDataNode) => { | |||||
| if (node.key === parentId) { | |||||
| return { | |||||
| ...node, | |||||
| children, | |||||
| }; | |||||
| } | |||||
| return node; | |||||
| }; | |||||
| }; | |||||
| // 得到数据集或者模型 id 和下属版本号 | |||||
| const getIdAndVersion = (versionKey: string) => { | |||||
| const index = versionKey.indexOf('-'); | |||||
| const id = Number(versionKey.slice(0, index)); | |||||
| const version = versionKey.slice(index + 1); | |||||
| return { | |||||
| id, | |||||
| version, | |||||
| }; | |||||
| }; | |||||
| function ResourceSelectorModal({ | |||||
| type, | |||||
| defaultExpandedKeys = [], | |||||
| defaultCheckedKeys = [], | |||||
| defaultActiveTab = TabItemKeys.Private, | |||||
| onOk, | |||||
| ...rest | |||||
| }: ResourceSelectorModalProps) { | |||||
| const [activeTab, setActiveTab] = useState<string>(defaultActiveTab); | |||||
| const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]); | |||||
| const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]); | |||||
| const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]); | |||||
| const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]); | |||||
| const [files, setFiles] = useState<ResourceFile[]>([]); | |||||
| const [versionPath, setVersionPath] = useState(''); | |||||
| const [searchText, setSearchText] = useState(''); | |||||
| const [fisrtLoadList, setFisrtLoadList] = useState(false); | |||||
| const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false); | |||||
| const treeRef = useRef<TreeRef>(null); | |||||
| useEffect(() => { | |||||
| setExpandedKeys([]); | |||||
| setCheckedKeys([]); | |||||
| setLoadedKeys([]); | |||||
| setFiles([]); | |||||
| setVersionPath(''); | |||||
| setSearchText(''); | |||||
| getTreeData(); | |||||
| }, [activeTab, type]); | |||||
| const treeData = useMemo( | |||||
| () => | |||||
| originTreeData.filter((v) => | |||||
| (v.title as string).toLowerCase()?.includes(searchText.toLowerCase()), | |||||
| ), | |||||
| [originTreeData, searchText], | |||||
| ); | |||||
| // 获取数据集或模型列表 | |||||
| const getTreeData = async () => { | |||||
| const available_range = activeTab === TabItemKeys.Private ? '0' : '1'; | |||||
| const params = { | |||||
| page: 0, | |||||
| size: 200, | |||||
| available_range: available_range, | |||||
| }; | |||||
| const getListReq = selectorTypeData[type].getList; | |||||
| const [res] = await to(getListReq(params)); | |||||
| if (res) { | |||||
| const list = res.data?.content || []; | |||||
| const treeData = convertToTreeData(list); | |||||
| setOriginTreeData(treeData); | |||||
| // 恢复上一次的 Expand 操作 | |||||
| restoreLastExpand(); | |||||
| } else { | |||||
| setOriginTreeData([]); | |||||
| } | |||||
| }; | |||||
| // 获取数据集或模型版本列表 | |||||
| const getVersions = async (parentId: number) => { | |||||
| const getVersionsReq = selectorTypeData[type].getVersions; | |||||
| const [res, error] = await to(getVersionsReq(parentId)); | |||||
| if (res) { | |||||
| const list = res.data || []; | |||||
| const children = list.map((v: string) => ({ | |||||
| title: v, | |||||
| key: `${parentId}-${v}`, | |||||
| isLeaf: true, | |||||
| checkable: true, | |||||
| })); | |||||
| // 更新 treeData children | |||||
| setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); | |||||
| // 缓存 loadedKeys | |||||
| const index = loadedKeys.find((v) => v === parentId); | |||||
| if (!index) { | |||||
| setLoadedKeys((prev) => prev.concat(parentId)); | |||||
| } | |||||
| // 恢复上一次的 Check 操作 | |||||
| restoreLastCheck(parentId); | |||||
| } else { | |||||
| setExpandedKeys([]); | |||||
| return Promise.reject(error); | |||||
| } | |||||
| }; | |||||
| // 获取版本下的文件 | |||||
| const getFiles = async (id: number, version: string) => { | |||||
| const getFilesReq = selectorTypeData[type].getFiles; | |||||
| const paramsKey = selectorTypeData[type].fileReqParamKey; | |||||
| const params = { version: version, [paramsKey]: id } as GetFilesReqParam; | |||||
| const [res] = await to(getFilesReq(params)); | |||||
| if (res) { | |||||
| setVersionPath(res.data?.path || ''); | |||||
| setFiles(res.data?.content || []); | |||||
| } else { | |||||
| setVersionPath(''); | |||||
| setFiles([]); | |||||
| } | |||||
| }; | |||||
| // 动态加载 tree children | |||||
| const onLoadData = ({ key, children }: TreeDataNode) => { | |||||
| if (children) { | |||||
| return Promise.resolve(); | |||||
| } else { | |||||
| return getVersions(key as number); | |||||
| } | |||||
| }; | |||||
| // 扩展 | |||||
| const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => { | |||||
| const lastKeys = expandedKeysValue.slice(-1); | |||||
| setExpandedKeys(lastKeys); | |||||
| }; | |||||
| // 选中 | |||||
| const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => { | |||||
| const lastKeys = (checkedKeysValue as React.Key[]).slice(-1); | |||||
| setCheckedKeys(lastKeys); | |||||
| if (lastKeys.length) { | |||||
| const last = lastKeys[0] as string; | |||||
| const { id, version } = getIdAndVersion(last); | |||||
| getFiles(id, version); | |||||
| } else { | |||||
| setFiles([]); | |||||
| } | |||||
| }; | |||||
| // 恢复上一次的 Expand 操作 | |||||
| // 判断是否有 defaultExpandedKeys,如果有,设置 expandedKeys | |||||
| // fisrtLoadList 标志位 | |||||
| const restoreLastExpand = () => { | |||||
| if (!fisrtLoadList && defaultExpandedKeys.length > 0) { | |||||
| setTimeout(() => { | |||||
| setExpandedKeys(defaultExpandedKeys); | |||||
| setFisrtLoadList(true); | |||||
| setTimeout(() => { | |||||
| treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' }); | |||||
| }, 100); | |||||
| }, 0); | |||||
| } | |||||
| }; | |||||
| // 恢复上一次的 Check 操作 | |||||
| // 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口 | |||||
| // fisrtLoadVersions 标志位 | |||||
| const restoreLastCheck = (parentId: number) => { | |||||
| if (!fisrtLoadVersions && defaultCheckedKeys.length > 0) { | |||||
| const last = defaultCheckedKeys[0] as string; | |||||
| const { id, version } = getIdAndVersion(last); | |||||
| // 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致 | |||||
| if (id === parentId) { | |||||
| setTimeout(() => { | |||||
| setCheckedKeys(defaultCheckedKeys); | |||||
| getFiles(id, version); | |||||
| setFisrtLoadVersions(true); | |||||
| setTimeout(() => { | |||||
| treeRef?.current?.scrollTo({ | |||||
| key: defaultCheckedKeys[0], | |||||
| align: 'bottom', | |||||
| }); | |||||
| }, 100); | |||||
| }, 0); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const handleOk = () => { | |||||
| if (checkedKeys.length > 0) { | |||||
| const last = checkedKeys[0] as string; | |||||
| const { id, version } = getIdAndVersion(last); | |||||
| const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string; | |||||
| const res = { | |||||
| id, | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| activeTab: activeTab as TabItemKeys, | |||||
| }; | |||||
| onOk?.(res); | |||||
| } else { | |||||
| onOk?.(null); | |||||
| } | |||||
| }; | |||||
| const title = `选择${selectorTypeData[type].name}`; | |||||
| const palceholder = `请输入${selectorTypeData[type].name}名称`; | |||||
| const fileTitle = `已选${selectorTypeData[type].name}文件(${files.length})`; | |||||
| const tabItems = selectorTypeData[type].tabItems; | |||||
| const titleImg = selectorTypeData[type].modalIcon; | |||||
| return ( | |||||
| <KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose> | |||||
| <div> | |||||
| <Tabs | |||||
| activeKey={activeTab} | |||||
| items={tabItems} | |||||
| onChange={setActiveTab} | |||||
| className={styles['model-tabs']} | |||||
| /> | |||||
| <div className={styles['model-selector']}> | |||||
| <div className={styles['model-selector__left']}> | |||||
| <Input | |||||
| className={styles['model-selector__left__search']} | |||||
| placeholder={palceholder} | |||||
| allowClear | |||||
| variant="borderless" | |||||
| value={searchText} | |||||
| onChange={(e) => setSearchText(e.target.value)} | |||||
| prefix={<Icon icon="local:magnifying-glass" style={{ height: '15px' }} />} | |||||
| /> | |||||
| <Tree | |||||
| ref={treeRef} | |||||
| rootStyle={{ backgroundColor: 'transparent' }} | |||||
| loadData={onLoadData} | |||||
| treeData={treeData} | |||||
| onCheck={onCheck} | |||||
| checkedKeys={checkedKeys} | |||||
| multiple={false} | |||||
| selectable={false} | |||||
| height={324} | |||||
| loadedKeys={loadedKeys} | |||||
| expandedKeys={expandedKeys} | |||||
| onExpand={onExpand} | |||||
| checkable | |||||
| /> | |||||
| </div> | |||||
| <div className={styles['model-selector__right']}> | |||||
| <div className={styles['model-selector__right__title']}>{fileTitle}</div> | |||||
| <div className={styles['model-selector__right__files']}> | |||||
| {files.map((v) => ( | |||||
| <div key={v.id} className={styles['model-selector__right__files__file']}> | |||||
| {v.file_name} | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default ResourceSelectorModal; | |||||
| @@ -51,3 +51,19 @@ | |||||
| color: #ffffff; | color: #ffffff; | ||||
| background-color: rgba(24, 144, 255, 0.3); | background-color: rgba(24, 144, 255, 0.3); | ||||
| } | } | ||||
| .ref-row { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| .select-button { | |||||
| display: flex; | |||||
| flex: none; | |||||
| align-items: center; | |||||
| justify-content: flex-start; | |||||
| width: 100px; | |||||
| margin-left: 10px; | |||||
| padding-right: 0; | |||||
| padding-left: 0; | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| import { ReactComponent as ParameterIcon } from '@/assets/svg/parameter.svg'; | import { ReactComponent as ParameterIcon } from '@/assets/svg/parameter.svg'; | ||||
| import { ReactComponent as SaveAndReturn } from '@/assets/svg/save--return.svg'; | |||||
| import { useAntdModal } from '@/hooks'; | import { useAntdModal } from '@/hooks'; | ||||
| import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| @@ -60,7 +61,7 @@ const EditPipeline = () => { | |||||
| }); | }); | ||||
| const graphRef = useRef(); | const graphRef = useRef(); | ||||
| const paramsDrawerRef = useRef(); | const paramsDrawerRef = useRef(); | ||||
| const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useAntdModal(false); | |||||
| const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false); | |||||
| const [globalParam, setGlobalParam] = useState([]); | const [globalParam, setGlobalParam] = useState([]); | ||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| @@ -97,6 +98,7 @@ const EditPipeline = () => { | |||||
| const [res, error] = await to(paramsDrawerRef.current.getFieldsValue()); | const [res, error] = await to(paramsDrawerRef.current.getFieldsValue()); | ||||
| if (error) { | if (error) { | ||||
| message.error('全局参数配置有误'); | message.error('全局参数配置有误'); | ||||
| openParamsDrawer(); | |||||
| return; | return; | ||||
| } | } | ||||
| const data = graph.save(); | const data = graph.save(); | ||||
| @@ -606,8 +608,8 @@ const EditPipeline = () => { | |||||
| }, | }, | ||||
| }, | }, | ||||
| // linkCenter: true, | // linkCenter: true, | ||||
| fitView: false, | |||||
| fitViewPadding: [60, 60, 60, 80], | |||||
| fitView: true, | |||||
| fitViewPadding: [320, 320, 220, 320], | |||||
| }); | }); | ||||
| graph.on('dblclick', (e) => { | graph.on('dblclick', (e) => { | ||||
| console.log(e.item); | console.log(e.item); | ||||
| @@ -722,7 +724,13 @@ const EditPipeline = () => { | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| type="primary" | type="primary" | ||||
| icon={<SaveOutlined />} | |||||
| style={{ | |||||
| border: '1px solid', | |||||
| borderColor: '#1664ff', | |||||
| background: '#fff', | |||||
| color: '#1664ff', | |||||
| }} | |||||
| icon={<SaveAndReturn />} | |||||
| onClick={() => { | onClick={() => { | ||||
| savePipeline(true); | savePipeline(true); | ||||
| }} | }} | ||||
| @@ -24,7 +24,7 @@ const modelMenus = ({ onParDragEnd }) => { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getComponentAll().then((ret) => { | getComponentAll().then((ret) => { | ||||
| console.log(ret); | console.log(ret); | ||||
| if (ret.code == 200) { | |||||
| if (ret.code === 200) { | |||||
| setModelMenusList(ret.data); | setModelMenusList(ret.data); | ||||
| } | } | ||||
| }); | }); | ||||
| @@ -43,32 +43,38 @@ const modelMenus = ({ onParDragEnd }) => { | |||||
| return ( | return ( | ||||
| <div style={{ width: '250px', height: '99%' }} className={Styles.collapse}> | <div style={{ width: '250px', height: '99%' }} className={Styles.collapse}> | ||||
| <div className={Styles.modelMenusTitle}>组件库</div> | <div className={Styles.modelMenusTitle}>组件库</div> | ||||
| <Collapse collapsible="header" defaultActiveKey={['1']} expandIconPosition="end"> | |||||
| {modelMenusList && modelMenusList.length > 0 | |||||
| ? modelMenusList.map((item) => ( | |||||
| <Panel header={<div>{item.name}</div>} key={item.key}> | |||||
| {item.value && item.value.length > 0 | |||||
| ? item.value.map((ele) => ( | |||||
| <div | |||||
| draggable="true" | |||||
| onDragEnd={(e) => { | |||||
| dragEnd(e, ele); | |||||
| }} | |||||
| className={Styles.collapseItem} | |||||
| > | |||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| {ele.component_label} | |||||
| </div> | |||||
| )) | |||||
| : ''} | |||||
| </Panel> | |||||
| )) | |||||
| : ''} | |||||
| </Collapse> | |||||
| {modelMenusList && modelMenusList.length > 0 ? ( | |||||
| <Collapse | |||||
| collapsible="header" | |||||
| defaultActiveKey={modelMenusList.map((item) => item.key + '')} | |||||
| expandIconPosition="end" | |||||
| > | |||||
| {modelMenusList && modelMenusList.length > 0 | |||||
| ? modelMenusList.map((item) => ( | |||||
| <Panel header={<div>{item.name}</div>} key={item.key}> | |||||
| {item.value && item.value.length > 0 | |||||
| ? item.value.map((ele) => ( | |||||
| <div | |||||
| draggable="true" | |||||
| onDragEnd={(e) => { | |||||
| dragEnd(e, ele); | |||||
| }} | |||||
| className={Styles.collapseItem} | |||||
| > | |||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| alt="" | |||||
| /> | |||||
| {ele.component_label} | |||||
| </div> | |||||
| )) | |||||
| : ''} | |||||
| </Panel> | |||||
| )) | |||||
| : ''} | |||||
| </Collapse> | |||||
| ) : null} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,16 +1,23 @@ | |||||
| import { Drawer, Form, Input } from 'antd'; | |||||
| import { pick } from '@/utils/index'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { Icon } from '@umijs/max'; | |||||
| import { Button, Drawer, Form, Input } from 'antd'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | import { forwardRef, useImperativeHandle, useState } from 'react'; | ||||
| import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | |||||
| import Styles from './editPipeline.less'; | import Styles from './editPipeline.less'; | ||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| const Props = forwardRef(({ onParentChange }, ref) => { | const Props = forwardRef(({ onParentChange }, ref) => { | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [stagingItem, setStagingItem] = useState({}); | const [stagingItem, setStagingItem] = useState({}); | ||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [selectedModel, setSelectedModel] = useState(undefined); | |||||
| const [selectedDataset, setSelectedDataset] = useState(undefined); | |||||
| const afterOpenChange = () => { | const afterOpenChange = () => { | ||||
| if (!open) { | if (!open) { | ||||
| console.log(111, open); | |||||
| console.log(stagingItem, form.getFieldsValue()); | console.log(stagingItem, form.getFieldsValue()); | ||||
| // 禁止校验 guard-for-in | |||||
| /* eslint-disable */ | |||||
| for (let i in form.getFieldsValue()) { | for (let i in form.getFieldsValue()) { | ||||
| for (let j in stagingItem.in_parameters) { | for (let j in stagingItem.in_parameters) { | ||||
| if (i == j) { | if (i == j) { | ||||
| @@ -29,6 +36,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* eslint-enable */ | |||||
| // setStagingItem({...stagingItem,}) | // setStagingItem({...stagingItem,}) | ||||
| console.log(stagingItem.control_strategy); | console.log(stagingItem.control_strategy); | ||||
| onParentChange({ | onParentChange({ | ||||
| @@ -76,30 +84,84 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| } | } | ||||
| }, | }, | ||||
| })); | })); | ||||
| // 选择数据集、模型 | |||||
| const selectResource = (name, item) => { | |||||
| const type = | |||||
| item.item_type === 'dataset' ? ResourceSelectorType.Dataset : ResourceSelectorType.Model; | |||||
| const resource = type === ResourceSelectorType.Dataset ? selectedDataset : selectedModel; | |||||
| const { close } = openAntdModal( | |||||
| ResourceSelectorModal, | |||||
| { | |||||
| type, | |||||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||||
| defaultActiveTab: resource?.activeTab, | |||||
| onOk: (res) => { | |||||
| if (res) { | |||||
| const jsonObj = pick(res, ['id', 'version', 'path']); | |||||
| const value = JSON.stringify(jsonObj); | |||||
| form.setFieldValue(name, value); | |||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(res); | |||||
| } else { | |||||
| setSelectedModel(res); | |||||
| } | |||||
| } else { | |||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(null); | |||||
| } else { | |||||
| setSelectedModel(null); | |||||
| } | |||||
| form.setFieldValue(name, ''); | |||||
| } | |||||
| close(); | |||||
| }, | |||||
| }, | |||||
| true, | |||||
| ); | |||||
| }; | |||||
| // 获取选择数据集、模型后面按钮 icon | |||||
| const getSelectBtnIcon = (item) => { | |||||
| const type = item.item_type; | |||||
| if (type === 'dataset') { | |||||
| return <Icon icon="local:dataset-select-button" className="umi-local-svg" />; | |||||
| } else if (type === 'model') { | |||||
| return <Icon icon="local:model-select-button" className="umi-local-svg" />; | |||||
| } else { | |||||
| return <Icon icon="local:mirror-select-button" className="umi-local-svg" />; | |||||
| } | |||||
| }; | |||||
| // 控制策略 | |||||
| const controlStrategy = stagingItem.control_strategy; | |||||
| // 输入参数 | |||||
| const inParameters = stagingItem.in_parameters; | |||||
| // 输出参数 | |||||
| const outParameters = stagingItem.out_parameters; | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Drawer | <Drawer | ||||
| title="编辑任务" | title="编辑任务" | ||||
| placement="right" | placement="right" | ||||
| rootStyle={{ marginTop: '45px' }} | |||||
| getContainer={false} | getContainer={false} | ||||
| closeIcon={false} | closeIcon={false} | ||||
| onClose={onClose} | onClose={onClose} | ||||
| afterOpenChange={afterOpenChange} | afterOpenChange={afterOpenChange} | ||||
| open={open} | open={open} | ||||
| width={540} | |||||
| > | > | ||||
| <Form | <Form | ||||
| name="form" | name="form" | ||||
| form={form} | form={form} | ||||
| layout="vertical" | |||||
| labelCol={{ | |||||
| span: 16, | |||||
| }} | |||||
| wrapperCol={{ | |||||
| span: 16, | |||||
| }} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| }} | |||||
| // layout="vertical" | |||||
| labelCol={{ span: 10 }} | |||||
| wrapperCol={{ span: 20 }} | |||||
| // initialValues={{ global_param: globalParam }} | |||||
| labelAlign="left" | |||||
| initialValues={{ | initialValues={{ | ||||
| remember: true, | remember: true, | ||||
| }} | }} | ||||
| @@ -184,11 +246,9 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| <Form.Item label="环境变量" name="env_variables"> | <Form.Item label="环境变量" name="env_variables"> | ||||
| <TextArea /> | <TextArea /> | ||||
| </Form.Item> | </Form.Item> | ||||
| {stagingItem.control_strategy && | |||||
| Object.keys(stagingItem.control_strategy) && | |||||
| Object.keys(stagingItem.control_strategy).length > 0 | |||||
| ? Object.keys(stagingItem.control_strategy).map((item) => ( | |||||
| <Form.Item label={stagingItem.control_strategy[item].label} name={item}> | |||||
| {controlStrategy && Object.keys(controlStrategy).length > 0 | |||||
| ? Object.keys(controlStrategy).map((item) => ( | |||||
| <Form.Item key={item} label={controlStrategy[item].label} name={item}> | |||||
| <Input /> | <Input /> | ||||
| </Form.Item> | </Form.Item> | ||||
| )) | )) | ||||
| @@ -201,16 +261,34 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| /> | /> | ||||
| 输入参数 | 输入参数 | ||||
| </div> | </div> | ||||
| {stagingItem.in_parameters && | |||||
| Object.keys(stagingItem.in_parameters) && | |||||
| Object.keys(stagingItem.in_parameters).length > 0 | |||||
| ? Object.keys(stagingItem.in_parameters).map((item) => ( | |||||
| {inParameters && Object.keys(inParameters).length > 0 | |||||
| ? Object.keys(inParameters).map((item) => ( | |||||
| <Form.Item | <Form.Item | ||||
| label={stagingItem.in_parameters[item].label + '(' + item + ')'} | |||||
| name={item} | |||||
| rules={[{ required: stagingItem.in_parameters[item].require ? true : false }]} | |||||
| key={item} | |||||
| label={inParameters[item].label + '(' + item + ')'} | |||||
| required={inParameters[item].require ? true : false} | |||||
| > | > | ||||
| <Input /> | |||||
| <div className={Styles['ref-row']}> | |||||
| <Form.Item | |||||
| name={item} | |||||
| noStyle | |||||
| rules={[{ required: inParameters[item].require ? true : false }]} | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| {inParameters[item].type === 'ref' && ( | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(inParameters[item])} | |||||
| onClick={() => selectResource(item, inParameters[item])} | |||||
| className={Styles['select-button']} | |||||
| > | |||||
| {inParameters[item].label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </div> | |||||
| </Form.Item> | </Form.Item> | ||||
| )) | )) | ||||
| : ''} | : ''} | ||||
| @@ -222,13 +300,12 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| /> | /> | ||||
| 输出参数 | 输出参数 | ||||
| </div> | </div> | ||||
| {stagingItem.out_parameters && | |||||
| Object.keys(stagingItem.out_parameters) && | |||||
| Object.keys(stagingItem.out_parameters).length > 0 | |||||
| ? Object.keys(stagingItem.out_parameters).map((item) => ( | |||||
| {outParameters && Object.keys(outParameters).length > 0 | |||||
| ? Object.keys(outParameters).map((item) => ( | |||||
| <Form.Item | <Form.Item | ||||
| label={stagingItem.out_parameters[item].label + '(' + item + ')'} | |||||
| rules={[{ required: stagingItem.out_parameters[item].require ? true : false }]} | |||||
| key={item} | |||||
| label={outParameters[item].label + '(' + item + ')'} | |||||
| rules={[{ required: outParameters[item].require ? true : false }]} | |||||
| name={item} | name={item} | ||||
| > | > | ||||
| <Input /> | <Input /> | ||||
| @@ -201,14 +201,27 @@ const Pipeline = () => { | |||||
| icon={<DeleteOutlined />} | icon={<DeleteOutlined />} | ||||
| onClick={async () => { | onClick={async () => { | ||||
| Modal.confirm({ | Modal.confirm({ | ||||
| title: '删除', | |||||
| content: '确定删除该条流水线吗?', | |||||
| title: ( | |||||
| <div> | |||||
| <img | |||||
| src="/assets/images/delete-icon.png" | |||||
| style={{ width: '120px', marginBottom: '24px' }} | |||||
| alt="" | |||||
| /> | |||||
| <div style={{ color: '#1d1d20', fontSize: '16px' }}> | |||||
| 删除后,该流水线将不可恢复 | |||||
| </div> | |||||
| </div> | |||||
| ), | |||||
| content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>, | |||||
| closable: true, | |||||
| okText: '确认', | okText: '确认', | ||||
| cancelText: '取消', | cancelText: '取消', | ||||
| onOk: () => { | onOk: () => { | ||||
| console.log(record); | console.log(record); | ||||
| removeWorkflow(record.id).then((ret) => { | removeWorkflow(record.id).then((ret) => { | ||||
| if (ret.code == 200) { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| getList(); | getList(); | ||||
| } else { | } else { | ||||
| @@ -62,5 +62,6 @@ | |||||
| .ant-btn-primary { | .ant-btn-primary { | ||||
| background: #1664ff; | background: #1664ff; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,9 @@ | |||||
| import type { RequestConfig } from '@umijs/max'; | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-03-25 13:52:54 | |||||
| * @Description: | |||||
| */ | |||||
| import type { RequestConfig } from '@umijs/max'; | |||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access'; | import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access'; | ||||
| @@ -0,0 +1,22 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 14:29:44 | |||||
| * @Description: | |||||
| */ | |||||
| import { request } from '@umijs/max'; | |||||
| // 分页查询镜像列表 | |||||
| export function getMirrorListReq(params: any) { | |||||
| return request(`/api/mmp/image/`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // // 分页查询镜像列表 | |||||
| // export function getMirrorList(params: any) { | |||||
| // return request(`/image/`, { | |||||
| // method: 'GET', | |||||
| // params, | |||||
| // }); | |||||
| // } | |||||
| @@ -2,13 +2,15 @@ import { createIcon } from '@/utils/IconUtil'; | |||||
| import { MenuDataItem } from '@ant-design/pro-components'; | import { MenuDataItem } from '@ant-design/pro-components'; | ||||
| import { request } from '@umijs/max'; | import { request } from '@umijs/max'; | ||||
| import React, { lazy } from 'react'; | import React, { lazy } from 'react'; | ||||
| import { createFromIconfontCN } from '@ant-design/icons'; | |||||
| let remoteMenu: any = null; | let remoteMenu: any = null; | ||||
| export function getRemoteMenu() { | export function getRemoteMenu() { | ||||
| return remoteMenu; | return remoteMenu; | ||||
| } | } | ||||
| const IconFont = createFromIconfontCN({ | |||||
| scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js', // 在 iconfont.cn 上生成 | |||||
| }); | |||||
| export function setRemoteMenu(data: any) { | export function setRemoteMenu(data: any) { | ||||
| remoteMenu = data; | remoteMenu = data; | ||||
| } | } | ||||
| @@ -100,7 +102,8 @@ export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] { | |||||
| return childrens.map((item: API.RoutersMenuItem) => { | return childrens.map((item: API.RoutersMenuItem) => { | ||||
| return { | return { | ||||
| path: item.path, | path: item.path, | ||||
| icon: createIcon(item.meta.icon), | |||||
| // icon:'icon-a-057_fenlei', | |||||
| icon: 'icon-'+item.meta.icon, | |||||
| // icon: item.meta.icon, | // icon: item.meta.icon, | ||||
| name: item.meta.title, | name: item.meta.title, | ||||
| routes: item.children ? convertCompatRouters(item.children) : undefined, | routes: item.children ? convertCompatRouters(item.children) : undefined, | ||||
| @@ -1,6 +1,12 @@ | |||||
| // 全局颜色变量 | // 全局颜色变量 | ||||
| // FIXME: 不能设置 @primary-color 不起作用,感觉是哪里被重置了 | // FIXME: 不能设置 @primary-color 不起作用,感觉是哪里被重置了 | ||||
| @kf-primary-color: #1664ff; // 主色调 | @kf-primary-color: #1664ff; // 主色调 | ||||
| @text-color: #1d1d20; | |||||
| @font-size: 15px; | |||||
| @border-color: rgba(22, 100, 255, 0.3); | |||||
| @border-color-second: rgba(22, 100, 255, 0.1); | |||||
| @background-color-primay: rgba(22, 100, 255, 0.03); | |||||
| @background-color-gray: rgba(4, 3, 3, 0.06); | |||||
| // 导出变量 | // 导出变量 | ||||
| :export { | :export { | ||||
| @@ -14,6 +14,7 @@ export function createIcon(icon: string | any): React.ReactNode | string { | |||||
| } | } | ||||
| const ele = allIcons[icon]; | const ele = allIcons[icon]; | ||||
| if (ele) { | if (ele) { | ||||
| return React.createElement(allIcons[icon]); | return React.createElement(allIcons[icon]); | ||||
| } | } | ||||
| return ''; | return ''; | ||||
| @@ -1,12 +0,0 @@ | |||||
| export function s8() { | |||||
| return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); | |||||
| } | |||||
| export function getNameByCode(list, code) { | |||||
| let name = ''; | |||||
| list.forEach((item) => { | |||||
| if (item.dictValue === code) name = item.dictLabel; | |||||
| }); | |||||
| return name; | |||||
| } | |||||
| @@ -0,0 +1,50 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-03-25 13:52:54 | |||||
| * @Description: | |||||
| */ | |||||
| export function s8() { | |||||
| return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); | |||||
| } | |||||
| export function getNameByCode(list: any[], code: any) { | |||||
| let name = ''; | |||||
| list.forEach((item) => { | |||||
| if (item.dictValue === code) name = item.dictLabel; | |||||
| }); | |||||
| 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,88 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-13 10:08:35 | |||||
| * @Description: | |||||
| */ | |||||
| import { type ModalProps } from 'antd'; | |||||
| import React, { useState } from 'react'; | |||||
| import { createRoot } from 'react-dom/client'; | |||||
| /** | |||||
| * Function to open an Ant Design modal. | |||||
| * | |||||
| * @param modal - The function that renders the modal content. | |||||
| * @param modalProps - The modal properties. | |||||
| * @return An object with a destroy method to close the modal. | |||||
| */ | |||||
| export const openAntdModal = <T extends ModalProps>( | |||||
| modal: (props: T) => React.ReactNode, | |||||
| modalProps: T, | |||||
| ) => { | |||||
| const CustomModel = modal; | |||||
| const element = document.createElement('div'); | |||||
| element.id = 'modal-container'; | |||||
| document.body.appendChild(element); | |||||
| const root = createRoot(element); | |||||
| const { afterClose, onCancel } = modalProps; | |||||
| function destroy() { | |||||
| root.unmount(); | |||||
| document.body.removeChild(element); | |||||
| } | |||||
| function handleAfterClose() { | |||||
| afterClose?.(); | |||||
| setTimeout(() => { | |||||
| destroy(); | |||||
| }, 0); | |||||
| } | |||||
| function handleCancel(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) { | |||||
| if (onCancel) { | |||||
| onCancel(e); | |||||
| } else { | |||||
| close(); | |||||
| } | |||||
| } | |||||
| function render(props: T) { | |||||
| root.render(<CustomModel {...props} onCancel={handleCancel}></CustomModel>); | |||||
| } | |||||
| function close() { | |||||
| render({ ...modalProps, open: false, afterClose: handleAfterClose }); | |||||
| } | |||||
| render({ ...modalProps, open: true }); | |||||
| return { | |||||
| close, | |||||
| }; | |||||
| }; | |||||
| /** | |||||
| * Generates a custom hook for managing an Ant Design modal. | |||||
| * | |||||
| * @param modal - The function that renders the modal content. | |||||
| * @param key - The key for the modal. | |||||
| * @return The modal component, open function, and close function. | |||||
| */ | |||||
| export const useAntdModal = <T extends ModalProps>( | |||||
| modal: (props: T) => React.ReactNode, | |||||
| key: React.Key, | |||||
| ) => { | |||||
| const [visible, setVisible] = useState(false); | |||||
| const [props, setProps] = useState<T>({} as T); | |||||
| const CustomModel = modal; | |||||
| const open = (props: T) => { | |||||
| setProps(props); | |||||
| setVisible(true); | |||||
| }; | |||||
| const close = () => { | |||||
| setVisible(false); | |||||
| }; | |||||
| return [<CustomModel key={key} open={visible} {...props} />, open, close] as const; | |||||
| }; | |||||
| @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.*; | |||||
| import org.springframework.web.multipart.MultipartFile; | import org.springframework.web.multipart.MultipartFile; | ||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||
| import java.util.Map; | |||||
| /** | /** | ||||
| * (Dataset)表控制层 | * (Dataset)表控制层 | ||||
| @@ -194,6 +195,18 @@ public class DatasetController { | |||||
| } | } | ||||
| @PostMapping("/exportDataset") | |||||
| @ApiOperation(value = "导出数据集", notes = "将流水线产物导出到数据集。") | |||||
| public AjaxResult exportDataset(@RequestBody Map map) throws Exception { | |||||
| String path = (String) map.get("path"); | |||||
| String uuid = (String) map.get("uuid"); | |||||
| return AjaxResult.success(datasetService.exportDataset(path, uuid)); | |||||
| } | |||||
| /** | /** | ||||
| * 从流水线上传数据集,不会给二进制文件,这边只存路径 | * 从流水线上传数据集,不会给二进制文件,这边只存路径 | ||||
| * @return 上传结果 | * @return 上传结果 | ||||
| @@ -40,8 +40,11 @@ public class ImageController extends BaseController { | |||||
| * @return 查询结果 | * @return 查询结果 | ||||
| */ | */ | ||||
| @GetMapping | @GetMapping | ||||
| @ApiOperation("分页查询") | |||||
| public GenericsAjaxResult<Page<Image>> queryByPage(Image image, int page, int size) { | |||||
| @ApiOperation("分页查询,根据image_type查询公开镜像和自定义镜像; 1公开0私有") | |||||
| public GenericsAjaxResult<Page<Image>> queryByPage(Image image, @RequestParam("page") int page, | |||||
| @RequestParam("size") int size, | |||||
| @RequestParam(value = "image_type") int imageType) { | |||||
| image.setImageType(imageType); | |||||
| PageRequest pageRequest = PageRequest.of(page,size); | PageRequest pageRequest = PageRequest.of(page,size); | ||||
| return genericsSuccess(this.imageService.queryByPage(image, pageRequest)); | return genericsSuccess(this.imageService.queryByPage(image, pageRequest)); | ||||
| } | } | ||||
| @@ -76,7 +79,7 @@ public class ImageController extends BaseController { | |||||
| * @return 新增结果 | * @return 新增结果 | ||||
| */ | */ | ||||
| @PostMapping | @PostMapping | ||||
| @ApiOperation("新增镜像") | |||||
| @ApiOperation("新增镜像,不包含镜像版本") | |||||
| public GenericsAjaxResult<Image> add(@RequestBody Image image) { | public GenericsAjaxResult<Image> add(@RequestBody Image image) { | ||||
| return genericsSuccess(this.imageService.insert(image)); | return genericsSuccess(this.imageService.insert(image)); | ||||
| } | } | ||||
| @@ -101,6 +104,7 @@ public class ImageController extends BaseController { | |||||
| * @return 编辑结果 | * @return 编辑结果 | ||||
| */ | */ | ||||
| @PutMapping | @PutMapping | ||||
| @ApiOperation("编辑镜像") | |||||
| public GenericsAjaxResult<Image> edit(@RequestBody Image image) { | public GenericsAjaxResult<Image> edit(@RequestBody Image image) { | ||||
| return genericsSuccess(this.imageService.update(image)); | return genericsSuccess(this.imageService.update(image)); | ||||
| } | } | ||||
| @@ -117,7 +121,7 @@ public class ImageController extends BaseController { | |||||
| } | } | ||||
| @PostMapping("/net") | @PostMapping("/net") | ||||
| @ApiOperation("从本地上传构建镜像") | |||||
| @ApiOperation("从网络上传构建镜像") | |||||
| public GenericsAjaxResult<String> createImageFromNet(@RequestParam("name") String imageName, | public GenericsAjaxResult<String> createImageFromNet(@RequestParam("name") String imageName, | ||||
| @RequestParam("tag") String imageTag, | @RequestParam("tag") String imageTag, | ||||
| @RequestParam("path") String path) throws Exception { | @RequestParam("path") String path) throws Exception { | ||||
| @@ -2,6 +2,7 @@ package com.ruoyi.platform.controller.model; | |||||
| import com.ruoyi.common.core.web.controller.BaseController; | import com.ruoyi.common.core.web.controller.BaseController; | ||||
| import com.ruoyi.common.core.web.domain.AjaxResult; | |||||
| import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | ||||
| import com.ruoyi.common.security.utils.SecurityUtils; | import com.ruoyi.common.security.utils.SecurityUtils; | ||||
| import com.ruoyi.platform.domain.Models; | import com.ruoyi.platform.domain.Models; | ||||
| @@ -50,7 +51,7 @@ public class ModelsController extends BaseController { | |||||
| @ApiOperation("模型广场分页查询,可传model_type进行筛选") | @ApiOperation("模型广场分页查询,可传model_type进行筛选") | ||||
| public GenericsAjaxResult<Page<Models>> queryByPage(Models models, @RequestParam("page") int page, | public GenericsAjaxResult<Page<Models>> queryByPage(Models models, @RequestParam("page") int page, | ||||
| @RequestParam("size") int size, | @RequestParam("size") int size, | ||||
| //@RequestParam("available_range") int availableRange, | |||||
| @RequestParam("available_range") int availableRange, | |||||
| @RequestParam(value = "model_type", required = false) String modelType, | @RequestParam(value = "model_type", required = false) String modelType, | ||||
| @RequestParam(value = "model_tag", required = false) String modelTag) { | @RequestParam(value = "model_tag", required = false) String modelTag) { | ||||
| if (modelType != null){ | if (modelType != null){ | ||||
| @@ -59,9 +60,10 @@ public class ModelsController extends BaseController { | |||||
| if (modelTag != null){ | if (modelTag != null){ | ||||
| models.setModelTag(modelTag); // 设置筛选条件 | models.setModelTag(modelTag); // 设置筛选条件 | ||||
| } | } | ||||
| models.setAvailableRange(1); // 设置筛选条件 | |||||
| models.setAvailableRange(availableRange); // 设置筛选条件 | |||||
| PageRequest pageRequest = PageRequest.of(page, size); | PageRequest pageRequest = PageRequest.of(page, size); | ||||
| return genericsSuccess(this.modelsService.queryByPage(models, pageRequest)); | return genericsSuccess(this.modelsService.queryByPage(models, pageRequest)); | ||||
| } | } | ||||
| @@ -225,6 +227,15 @@ public class ModelsController extends BaseController { | |||||
| } | } | ||||
| @PostMapping("/exportModel") | |||||
| @ApiOperation(value = "导出模型", notes = "将流水线产物导出到模型。") | |||||
| public AjaxResult exportModels(@RequestBody Map map) throws Exception { | |||||
| String path = (String) map.get("path"); | |||||
| String uuid = (String) map.get("uuid"); | |||||
| return AjaxResult.success(this.modelsService.exportModels(path, uuid)); | |||||
| } | |||||
| } | } | ||||
| @@ -29,7 +29,7 @@ public interface DatasetDao { | |||||
| * @param pageable 分页对象 | * @param pageable 分页对象 | ||||
| * @return 对象列表 | * @return 对象列表 | ||||
| */ | */ | ||||
| List<Dataset> queryAllByLimit(@Param("dataset")Dataset dataset, @Param("pageable") Pageable pageable); | |||||
| List<Dataset> queryAllByLimit(@Param("dataset") Dataset dataset, @Param("pageable") Pageable pageable); | |||||
| /** | /** | ||||
| * 统计总行数 | * 统计总行数 | ||||
| @@ -82,4 +82,6 @@ DatasetService { | |||||
| public void checkDeclaredName(Dataset insert) throws Exception; | public void checkDeclaredName(Dataset insert) throws Exception; | ||||
| ResponseEntity<InputStreamResource> downloadAllDatasetFiles(Integer datasetId, String version) throws Exception; | ResponseEntity<InputStreamResource> downloadAllDatasetFiles(Integer datasetId, String version) throws Exception; | ||||
| List<Map<String, String>> exportDataset(String path, String uuid) throws Exception; | |||||
| } | } | ||||
| @@ -11,4 +11,5 @@ public interface MinioService { | |||||
| ResponseEntity<InputStreamResource> downloadZipFile(String bucketName , String path); | ResponseEntity<InputStreamResource> downloadZipFile(String bucketName , String path); | ||||
| Map<String, String> uploadFile(String bucketName, String objectName, MultipartFile file ) throws Exception; | Map<String, String> uploadFile(String bucketName, String objectName, MultipartFile file ) throws Exception; | ||||
| } | } | ||||
| @@ -81,4 +81,5 @@ public interface ModelsService { | |||||
| String readFileContent(Integer modelsId, String version) throws Exception; | String readFileContent(Integer modelsId, String version) throws Exception; | ||||
| List<Map<String, String>> exportModels(String path, String uuid) throws Exception; | |||||
| } | } | ||||
| @@ -9,12 +9,14 @@ import com.ruoyi.platform.mapper.DatasetDao; | |||||
| import com.ruoyi.platform.mapper.DatasetVersionDao; | import com.ruoyi.platform.mapper.DatasetVersionDao; | ||||
| import com.ruoyi.platform.service.DatasetService; | import com.ruoyi.platform.service.DatasetService; | ||||
| import com.ruoyi.platform.service.DatasetVersionService; | import com.ruoyi.platform.service.DatasetVersionService; | ||||
| import com.ruoyi.platform.service.MinioService; | |||||
| import com.ruoyi.platform.utils.BeansUtils; | import com.ruoyi.platform.utils.BeansUtils; | ||||
| import com.ruoyi.platform.utils.FileUtil; | import com.ruoyi.platform.utils.FileUtil; | ||||
| import com.ruoyi.platform.utils.MinioUtil; | import com.ruoyi.platform.utils.MinioUtil; | ||||
| import com.ruoyi.platform.vo.VersionVo; | import com.ruoyi.platform.vo.VersionVo; | ||||
| import com.ruoyi.platform.vo.DatasetVo; | import com.ruoyi.platform.vo.DatasetVo; | ||||
| import com.ruoyi.system.api.model.LoginUser; | import com.ruoyi.system.api.model.LoginUser; | ||||
| import io.minio.messages.Item; | |||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.core.io.InputStreamResource; | import org.springframework.core.io.InputStreamResource; | ||||
| import org.springframework.data.domain.Page; | import org.springframework.data.domain.Page; | ||||
| @@ -56,6 +58,9 @@ public class DatasetServiceImpl implements DatasetService { | |||||
| @Resource | @Resource | ||||
| private DatasetVersionService datasetVersionService; | private DatasetVersionService datasetVersionService; | ||||
| @Resource | |||||
| private MinioService minioService; | |||||
| // 固定存储桶名 | // 固定存储桶名 | ||||
| private final String bucketName = "platform-data"; | private final String bucketName = "platform-data"; | ||||
| @@ -204,44 +209,20 @@ public class DatasetServiceImpl implements DatasetService { | |||||
| * 上传数据集 | * 上传数据集 | ||||
| * | * | ||||
| * @param files 文件 | * @param files 文件 | ||||
| * @param uuid | |||||
| * @param uuid 唯一标识 | |||||
| * @return 是否成功 | * @return 是否成功 | ||||
| */ | */ | ||||
| @Override | @Override | ||||
| public List<Map<String, String>> uploadDataset(MultipartFile[] files, String uuid) throws Exception { | public List<Map<String, String>> uploadDataset(MultipartFile[] files, String uuid) throws Exception { | ||||
| List<Map<String, String>> results = new ArrayList<>(); | List<Map<String, String>> results = new ArrayList<>(); | ||||
| // //时间戳统一定在外面,一次上传就定好 | |||||
| // Date createTime = new Date(); | |||||
| // String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(createTime); | |||||
| for (MultipartFile file:files){ | for (MultipartFile file:files){ | ||||
| if (file.isEmpty()) { | |||||
| throw new Exception("文件为空,无法上传"); | |||||
| } | |||||
| // 获取文件大小并转换为可读形式 | |||||
| long sizeInBytes = file.getSize(); | |||||
| String formattedSize = FileUtil.formatFileSize(sizeInBytes); // 格式化文件大小 | |||||
| // 其余操作基于 modelsVersionToUse | |||||
| // 构建objectName | |||||
| String username = SecurityUtils.getLoginUser().getUsername(); | String username = SecurityUtils.getLoginUser().getUsername(); | ||||
| String fileName = file.getOriginalFilename(); | String fileName = file.getOriginalFilename(); | ||||
| String objectName = "datasets/" + username + "/" + uuid + "/" + fileName; | String objectName = "datasets/" + username + "/" + uuid + "/" + fileName; | ||||
| // 上传文件到MinIO并将记录新增到数据库中 | |||||
| try (InputStream inputStream = file.getInputStream()) { | |||||
| minioUtil.uploadObject(bucketName, objectName, inputStream); | |||||
| Map<String, String> fileResult = new HashMap<>(); | |||||
| fileResult.put("fileName", file.getOriginalFilename()); | |||||
| fileResult.put("url", objectName); // objectName根据实际情况定义 | |||||
| fileResult.put("fileSize", formattedSize); | |||||
| results.add(fileResult); | |||||
| } catch (Exception e) { | |||||
| throw new Exception("上传数据集失败: " + e.getMessage(), e); | |||||
| } | |||||
| results.add(minioService.uploadFile(bucketName, objectName, file)); | |||||
| } | } | ||||
| return results; | return results; | ||||
| } | } | ||||
| @@ -286,19 +267,15 @@ public class DatasetServiceImpl implements DatasetService { | |||||
| public List<String> getDatasetVersions(Integer datasetId) throws Exception { | public List<String> getDatasetVersions(Integer datasetId) throws Exception { | ||||
| // 获取所有相同模型ID的记录 | // 获取所有相同模型ID的记录 | ||||
| List<DatasetVersion> versions = datasetVersionDao.queryByDatasetId(datasetId); | List<DatasetVersion> versions = datasetVersionDao.queryByDatasetId(datasetId); | ||||
| if (versions.isEmpty()) { | if (versions.isEmpty()) { | ||||
| throw new Exception("未找到相关数据集版本记录"); | throw new Exception("未找到相关数据集版本记录"); | ||||
| } | } | ||||
| // 使用Stream API提取version字段,并去重 | // 使用Stream API提取version字段,并去重 | ||||
| return versions.stream() | return versions.stream() | ||||
| .map(DatasetVersion::getVersion) // 提取每个DatasetVersion对象的version属性 | .map(DatasetVersion::getVersion) // 提取每个DatasetVersion对象的version属性 | ||||
| .filter(version -> version != null && !version.isEmpty()) //忽略null或空字符串的version | .filter(version -> version != null && !version.isEmpty()) //忽略null或空字符串的version | ||||
| .distinct() // 去重 | .distinct() // 去重 | ||||
| .collect(Collectors.toList()); // 收集到List中 | .collect(Collectors.toList()); // 收集到List中 | ||||
| } | } | ||||
| @@ -308,6 +285,7 @@ public class DatasetServiceImpl implements DatasetService { | |||||
| List<VersionVo> datasetVersionVos = datasetVo.getDatasetVersionVos(); | List<VersionVo> datasetVersionVos = datasetVo.getDatasetVersionVos(); | ||||
| if (datasetVersionVos==null || datasetVersionVos.isEmpty()){ | if (datasetVersionVos==null || datasetVersionVos.isEmpty()){ | ||||
| throw new Exception("数据集版本信息错误"); | throw new Exception("数据集版本信息错误"); | ||||
| } | } | ||||
| Dataset dataset = new Dataset(); | Dataset dataset = new Dataset(); | ||||
| @@ -334,7 +312,6 @@ public class DatasetServiceImpl implements DatasetService { | |||||
| throw new Exception("新增数据集版本失败"); | throw new Exception("新增数据集版本失败"); | ||||
| } | } | ||||
| } | } | ||||
| return "新增数据集成功"; | return "新增数据集成功"; | ||||
| } | } | ||||
| @@ -396,7 +373,6 @@ public class DatasetServiceImpl implements DatasetService { | |||||
| zos.closeEntry(); | zos.closeEntry(); | ||||
| } | } | ||||
| } | } | ||||
| // 转换为输入流 | // 转换为输入流 | ||||
| ByteArrayInputStream inputStream = new ByteArrayInputStream(baos.toByteArray()); | ByteArrayInputStream inputStream = new ByteArrayInputStream(baos.toByteArray()); | ||||
| InputStreamResource resource = new InputStreamResource(inputStream); | InputStreamResource resource = new InputStreamResource(inputStream); | ||||
| @@ -412,4 +388,33 @@ public class DatasetServiceImpl implements DatasetService { | |||||
| } | } | ||||
| } | } | ||||
| @Override | |||||
| public List<Map<String, String>> exportDataset(String path, String uuid) throws Exception { | |||||
| List<Map<String, String>> results = new ArrayList<>(); | |||||
| //根据path得到源文件所在桶名 | |||||
| String srcBucketName = path.substring(0, path.indexOf("/")); | |||||
| //构建目标目录路径 | |||||
| String username = SecurityUtils.getLoginUser().getUsername(); | |||||
| String srcDir = path.substring(path.indexOf("/") + 1); | |||||
| String targetDir = "datasets/" + username + "/" + uuid + "/"; | |||||
| // 递归拷贝整个原目录到目标目录下 | |||||
| minioUtil.copyDirectory(srcBucketName, srcDir, bucketName, targetDir); | |||||
| List<Item> movedItems = minioUtil.getAllObjectsByPrefix(bucketName, targetDir, true); | |||||
| for (Item movedItem : movedItems) { | |||||
| if(!movedItem.isDir() && movedItem.size() > 0){ // 检查是否为非目录且文件大小大于0 | |||||
| Map<String, String> result = new HashMap<>(); | |||||
| String url = movedItem.objectName(); | |||||
| String fileName = extractFileName(url); | |||||
| // 获取文件大小并转换为可读形式 | |||||
| long sizeInBytes = movedItem.size(); | |||||
| String formattedSize = FileUtil.formatFileSize(sizeInBytes); // 格式化文件大小 | |||||
| result.put("fileName", fileName); | |||||
| result.put("url", url); // objectName根据实际情况定义 | |||||
| result.put("fileSize", formattedSize); | |||||
| results.add(result); | |||||
| } | |||||
| } | |||||
| return results; | |||||
| } | |||||
| } | } | ||||
| @@ -113,7 +113,7 @@ public class ImageServiceImpl implements ImageService { | |||||
| public Image update(Image image) { | public Image update(Image image) { | ||||
| int currentState = image.getState(); | int currentState = image.getState(); | ||||
| if(currentState == 0){ | if(currentState == 0){ | ||||
| throw new RuntimeException("模型已被删除,无法更新。"); | |||||
| throw new RuntimeException("镜像已被删除,无法更新。"); | |||||
| } | } | ||||
| LoginUser loginUser = SecurityUtils.getLoginUser(); | LoginUser loginUser = SecurityUtils.getLoginUser(); | ||||
| image.setUpdateBy(loginUser.getUsername()); | image.setUpdateBy(loginUser.getUsername()); | ||||
| @@ -50,7 +50,7 @@ public class MinioServiceImpl implements MinioService { | |||||
| } | } | ||||
| @Override | @Override | ||||
| public Map<String, String> uploadFile(String bucketName, String objectName,MultipartFile file ) throws Exception { | |||||
| public Map<String, String> uploadFile(String bucketName, String objectName, MultipartFile file ) throws Exception { | |||||
| if (file.isEmpty()) { | if (file.isEmpty()) { | ||||
| throw new Exception("文件为空,无法上传"); | throw new Exception("文件为空,无法上传"); | ||||
| } | } | ||||
| @@ -65,7 +65,7 @@ public class MinioServiceImpl implements MinioService { | |||||
| result.put("url", objectName); // objectName根据实际情况定义 | result.put("url", objectName); // objectName根据实际情况定义 | ||||
| result.put("fileSize", formattedSize); | result.put("fileSize", formattedSize); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| throw new Exception("上传数据集失败: " + e.getMessage(), e); | |||||
| throw new Exception("上传文件失败: " + e.getMessage(), e); | |||||
| } | } | ||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -5,6 +5,7 @@ import com.ruoyi.platform.domain.Models; | |||||
| import com.ruoyi.platform.domain.ModelsVersion; | import com.ruoyi.platform.domain.ModelsVersion; | ||||
| import com.ruoyi.platform.mapper.ModelsDao; | import com.ruoyi.platform.mapper.ModelsDao; | ||||
| import com.ruoyi.platform.mapper.ModelsVersionDao; | import com.ruoyi.platform.mapper.ModelsVersionDao; | ||||
| import com.ruoyi.platform.service.MinioService; | |||||
| import com.ruoyi.platform.service.ModelsService; | import com.ruoyi.platform.service.ModelsService; | ||||
| import com.ruoyi.platform.service.ModelsVersionService; | import com.ruoyi.platform.service.ModelsVersionService; | ||||
| import com.ruoyi.platform.utils.BeansUtils; | import com.ruoyi.platform.utils.BeansUtils; | ||||
| @@ -13,6 +14,7 @@ import com.ruoyi.platform.utils.MinioUtil; | |||||
| import com.ruoyi.platform.vo.ModelsVo; | import com.ruoyi.platform.vo.ModelsVo; | ||||
| import com.ruoyi.platform.vo.VersionVo; | import com.ruoyi.platform.vo.VersionVo; | ||||
| import com.ruoyi.system.api.model.LoginUser; | import com.ruoyi.system.api.model.LoginUser; | ||||
| import io.minio.messages.Item; | |||||
| import io.netty.util.Version; | import io.netty.util.Version; | ||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.core.io.InputStreamResource; | import org.springframework.core.io.InputStreamResource; | ||||
| @@ -52,6 +54,9 @@ public class ModelsServiceImpl implements ModelsService { | |||||
| @Resource | @Resource | ||||
| private ModelsVersionService modelsVersionService; | private ModelsVersionService modelsVersionService; | ||||
| @Resource | |||||
| private MinioService minioService; | |||||
| // 固定存储桶名 | // 固定存储桶名 | ||||
| private final String bucketName = "platform-data"; | private final String bucketName = "platform-data"; | ||||
| @@ -194,50 +199,23 @@ public class ModelsServiceImpl implements ModelsService { | |||||
| } | } | ||||
| /** | /** | ||||
| * 上传模型 | |||||
| * 上传模型文件 | |||||
| * | * | ||||
| * @param files 文件 | * @param files 文件 | ||||
| * @param uuid | |||||
| * @param uuid 唯一标识 | |||||
| * @return 是否成功 | * @return 是否成功 | ||||
| */ | */ | ||||
| @Override | @Override | ||||
| public List<Map<String, String>> uploadModels(MultipartFile[] files, String uuid) throws Exception { | public List<Map<String, String>> uploadModels(MultipartFile[] files, String uuid) throws Exception { | ||||
| List<Map<String, String>> results = new ArrayList<>(); | List<Map<String, String>> results = new ArrayList<>(); | ||||
| // //时间戳统一定在外面,一次上传就定好 | |||||
| // Date createTime = new Date(); | |||||
| // String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(createTime); | |||||
| for (MultipartFile file:files){ | |||||
| if (file.isEmpty()) { | |||||
| throw new Exception("文件为空,无法上传"); | |||||
| } | |||||
| // 获取文件大小并转换为KB | |||||
| long sizeInBytes = file.getSize(); | |||||
| String formattedSize = FileUtil.formatFileSize(sizeInBytes); // 格式化文件大小 | |||||
| // 其余操作基于 modelsVersionToUse | |||||
| for (MultipartFile file:files) { | |||||
| // 构建objectName | |||||
| String username = SecurityUtils.getLoginUser().getUsername(); | String username = SecurityUtils.getLoginUser().getUsername(); | ||||
| String fileName = file.getOriginalFilename(); | String fileName = file.getOriginalFilename(); | ||||
| String objectName = "models/" + username + "/" + uuid + "/" + fileName; | String objectName = "models/" + username + "/" + uuid + "/" + fileName; | ||||
| // 上传文件到MinIO并将记录新增到数据库中 | |||||
| try (InputStream inputStream = file.getInputStream()) { | |||||
| minioUtil.uploadObject(bucketName, objectName, inputStream); | |||||
| // | |||||
| Map<String, String> fileResult = new HashMap<>(); | |||||
| fileResult.put("fileName", file.getOriginalFilename()); | |||||
| fileResult.put("url", objectName); // objectName根据实际情况定义 | |||||
| fileResult.put("fileSize", formattedSize); | |||||
| results.add(fileResult); | |||||
| } catch (Exception e) { | |||||
| throw new Exception("上传模型失败: " + e.getMessage(), e); | |||||
| } | |||||
| results.add(minioService.uploadFile(bucketName, objectName, file)); | |||||
| } | } | ||||
| return results; | return results; | ||||
| } | } | ||||
| @@ -432,6 +410,35 @@ public class ModelsServiceImpl implements ModelsService { | |||||
| } | } | ||||
| @Override | |||||
| public List<Map<String, String>> exportModels(String path, String uuid) throws Exception { | |||||
| List<Map<String, String>> results = new ArrayList<>(); | |||||
| //根据path得到源文件所在桶名 | |||||
| String srcBucketName = path.substring(0, path.indexOf("/")); | |||||
| //构建目标目录路径 | |||||
| String username = SecurityUtils.getLoginUser().getUsername(); | |||||
| String srcDir = path.substring(path.indexOf("/") + 1); | |||||
| String targetDir = "models/" + username + "/" + uuid + "/"; | |||||
| // 递归拷贝整个原目录到目标目录下 | |||||
| minioUtil.copyDirectory(srcBucketName, srcDir, bucketName, targetDir); | |||||
| List<Item> movedItems = minioUtil.getAllObjectsByPrefix(bucketName, targetDir, true); | |||||
| for (Item movedItem : movedItems) { | |||||
| if(!movedItem.isDir() && movedItem.size() > 0){ // 检查是否为非目录且文件大小大于0 | |||||
| Map<String, String> result = new HashMap<>(); | |||||
| String url = movedItem.objectName(); | |||||
| String fileName = extractFileName(url); | |||||
| // 获取文件大小并转换为可读形式 | |||||
| long sizeInBytes = movedItem.size(); | |||||
| String formattedSize = FileUtil.formatFileSize(sizeInBytes); // 格式化文件大小 | |||||
| result.put("fileName", fileName); | |||||
| result.put("url", url); // objectName根据实际情况定义 | |||||
| result.put("fileSize", formattedSize); | |||||
| results.add(result); | |||||
| } | |||||
| } | |||||
| return results; | |||||
| } | |||||
| private String extractFileName(String urlStr) { | private String extractFileName(String urlStr) { | ||||
| return urlStr.substring(urlStr.lastIndexOf('/') + 1); | return urlStr.substring(urlStr.lastIndexOf('/') + 1); | ||||
| } | } | ||||
| @@ -3,27 +3,26 @@ package com.ruoyi.platform.utils; | |||||
| import io.minio.*; | import io.minio.*; | ||||
| import io.minio.errors.MinioException; | import io.minio.errors.MinioException; | ||||
| import io.minio.messages.DeleteObject; | |||||
| import io.minio.messages.Item; | import io.minio.messages.Item; | ||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.multipart.MultipartFile; | |||||
| import java.io.*; | import java.io.*; | ||||
| import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Path; | import java.nio.file.Path; | ||||
| import java.nio.file.Paths; | import java.nio.file.Paths; | ||||
| import java.util.ArrayList; | |||||
| import java.util.HashMap; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| import java.util.*; | |||||
| import java.util.zip.ZipEntry; | import java.util.zip.ZipEntry; | ||||
| import java.util.zip.ZipOutputStream; | import java.util.zip.ZipOutputStream; | ||||
| @Slf4j | @Slf4j | ||||
| @Component | @Component | ||||
| public class MinioUtil { | public class MinioUtil { | ||||
| private MinioClient minioClient; | |||||
| private static MinioClient minioClient; | |||||
| @Autowired | @Autowired | ||||
| public MinioUtil(@Value("${minio.endpoint}")String minioEndpoint,@Value("${minio.accessKey}")String minioAccessKey,@Value("${minio.secretKey}") String minioSecretKey) { | public MinioUtil(@Value("${minio.endpoint}")String minioEndpoint,@Value("${minio.accessKey}")String minioAccessKey,@Value("${minio.secretKey}") String minioSecretKey) { | ||||
| @@ -43,6 +42,84 @@ public class MinioUtil { | |||||
| minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); | minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); | ||||
| } | } | ||||
| /** | |||||
| * 验证bucketName是否存在 | |||||
| * | |||||
| * @return boolean true:存在 | |||||
| */ | |||||
| public boolean bucketExists(String bucketName) | |||||
| throws Exception { | |||||
| return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); | |||||
| } | |||||
| /** | |||||
| * 判断文件是否存在 | |||||
| * | |||||
| * @param bucketName 存储桶 | |||||
| * @param objectName 对象 | |||||
| * @return true:存在 | |||||
| */ | |||||
| public boolean doesObjectExist(String bucketName, String objectName) { | |||||
| boolean exist = true; | |||||
| try { | |||||
| minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()); | |||||
| } catch (Exception e) { | |||||
| exist = false; | |||||
| } | |||||
| return exist; | |||||
| } | |||||
| /** | |||||
| * 判断文件夹是否存在 | |||||
| * | |||||
| * @param bucketName 存储桶 | |||||
| * @param objectName 文件夹名称(去掉/) | |||||
| * @return true:存在 | |||||
| */ | |||||
| public boolean doesFolderExist(String bucketName, String objectName) { | |||||
| boolean exist = false; | |||||
| try { | |||||
| Iterable<Result<Item>> results = minioClient.listObjects( | |||||
| ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build()); | |||||
| for (Result<Item> result : results) { | |||||
| Item item = result.get(); | |||||
| if (item.isDir() && objectName.equals(item.objectName())) { | |||||
| exist = true; | |||||
| } | |||||
| } | |||||
| } catch (Exception e) { | |||||
| exist = false; | |||||
| } | |||||
| return exist; | |||||
| } | |||||
| /** | |||||
| * 根据文件前置查询文件 | |||||
| * | |||||
| * @param bucketName bucket名称 | |||||
| * @param prefix 前缀 | |||||
| * @param recursive 是否递归查询 | |||||
| * @return MinioItem 列表 | |||||
| */ | |||||
| public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) throws Exception { | |||||
| List<Item> list = new ArrayList<>(); | |||||
| Iterable<Result<Item>> objectsIterator = minioClient.listObjects( | |||||
| ListObjectsArgs. | |||||
| builder(). | |||||
| bucket(bucketName). | |||||
| prefix(prefix). | |||||
| recursive(recursive). | |||||
| build()); | |||||
| if (objectsIterator != null) { | |||||
| for (Result<Item> o : objectsIterator) { | |||||
| Item item = o.get(); | |||||
| list.add(item); | |||||
| } | |||||
| } | |||||
| return list; | |||||
| } | |||||
| public void uploadObject(String bucketName, String objectName, InputStream stream) throws Exception { | public void uploadObject(String bucketName, String objectName, InputStream stream) throws Exception { | ||||
| long size = stream.available(); | long size = stream.available(); | ||||
| minioClient.putObject( | minioClient.putObject( | ||||
| @@ -54,6 +131,36 @@ public class MinioUtil { | |||||
| ); | ); | ||||
| } | } | ||||
| /** | |||||
| * 通过MultipartFile,上传文件 | |||||
| * | |||||
| * @param bucketName 存储桶 | |||||
| * @param file 文件 | |||||
| * @param objectName 对象名 | |||||
| * @param contentType 文件类型 | |||||
| */ | |||||
| public static ObjectWriteResponse putObject(String bucketName, MultipartFile file, String objectName, String contentType) throws Exception { | |||||
| InputStream inputStream = file.getInputStream(); | |||||
| return minioClient.putObject( | |||||
| PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType) | |||||
| .stream(inputStream, inputStream.available(), -1).build()); | |||||
| } | |||||
| /** | |||||
| * 通过MultipartFile,上传文件 | |||||
| * | |||||
| * @param bucketName 存储桶 | |||||
| * @param file 文件 | |||||
| * @param objectName 对象名 | |||||
| */ | |||||
| public static ObjectWriteResponse putObject(String bucketName, MultipartFile file, String objectName) throws Exception { | |||||
| InputStream inputStream = file.getInputStream(); | |||||
| return minioClient.putObject( | |||||
| PutObjectArgs.builder().bucket(bucketName).object(objectName) | |||||
| .stream(inputStream, inputStream.available(), -1).build()); | |||||
| } | |||||
| public void downloadObject(String bucketName, String objectName, OutputStream stream) throws Exception { | public void downloadObject(String bucketName, String objectName, OutputStream stream) throws Exception { | ||||
| try (InputStream inStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build())) { | try (InputStream inStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build())) { | ||||
| byte[] buffer = new byte[1024]; | byte[] buffer = new byte[1024]; | ||||
| @@ -64,6 +171,102 @@ public class MinioUtil { | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * 创建文件夹或目录 | |||||
| * | |||||
| * @param bucketName 存储桶 | |||||
| * @param objectName 目录路径 | |||||
| */ | |||||
| public ObjectWriteResponse putDirObject(String bucketName, String objectName) throws Exception { | |||||
| return minioClient.putObject( | |||||
| PutObjectArgs.builder().bucket(bucketName).object(objectName).stream( | |||||
| new ByteArrayInputStream(new byte[]{}), 0, -1).build()); | |||||
| } | |||||
| /** | |||||
| * 拷贝文件 | |||||
| * | |||||
| * @param bucketName bucket名称 | |||||
| * @param objectName 文件名称 | |||||
| * @param srcBucketName 目标bucket名称 | |||||
| * @param srcObjectName 目标文件名称 | |||||
| */ | |||||
| public ObjectWriteResponse copyObject(String bucketName, String objectName, | |||||
| String srcBucketName, String srcObjectName) throws Exception { | |||||
| return minioClient.copyObject( | |||||
| CopyObjectArgs.builder() | |||||
| .source(CopySource.builder().bucket(bucketName).object(objectName).build()) | |||||
| .bucket(srcBucketName) | |||||
| .object(srcObjectName) | |||||
| .build()); | |||||
| } | |||||
| /** | |||||
| * 递归拷贝 | |||||
| * | |||||
| * @param sourceBucketName 源bucket名称 | |||||
| * @param sourceKeyPrefix 源目录路径 | |||||
| * @param targetBucketName 目标bucket名称 | |||||
| * @param targetKeyPrefix 目标目录名称 | |||||
| */ | |||||
| public void copyDirectory(String sourceBucketName, String sourceKeyPrefix, | |||||
| String targetBucketName, String targetKeyPrefix) throws Exception { | |||||
| // 列出所有源目录下的对象 | |||||
| Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder() | |||||
| .bucket(sourceBucketName) | |||||
| .prefix(sourceKeyPrefix) | |||||
| .recursive(true) | |||||
| .build()); | |||||
| for (Result<Item> result : results) { | |||||
| Item item = result.get(); | |||||
| String sourceKey = item.objectName(); //文件的原始完整路径 | |||||
| String targetKey = targetKeyPrefix + sourceKey.substring(sourceKeyPrefix.length()); | |||||
| // 拷贝每个对象到目标路径 | |||||
| minioClient.copyObject(CopyObjectArgs.builder() | |||||
| .bucket(targetBucketName) | |||||
| .object(targetKey) | |||||
| .source(CopySource.builder() | |||||
| .bucket(sourceBucketName) | |||||
| .object(sourceKey) | |||||
| .build()) | |||||
| .build()); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 获取文件流 | |||||
| * | |||||
| * @param bucketName bucket名称 | |||||
| * @param objectName 文件名称 | |||||
| * @return 二进制流 | |||||
| */ | |||||
| public static InputStream getObject(String bucketName, String objectName) throws Exception { | |||||
| return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build()); | |||||
| } | |||||
| /** | |||||
| * 断点下载 | |||||
| * | |||||
| * @param bucketName bucket名称 | |||||
| * @param objectName 文件名称 | |||||
| * @param offset 起始字节的位置 | |||||
| * @param length 要读取的长度 | |||||
| * @return 流 | |||||
| */ | |||||
| public InputStream getObject(String bucketName, String objectName, long offset, long length) | |||||
| throws Exception { | |||||
| return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build()); | |||||
| } | |||||
| public List<Map> listFilesInDirectory(String bucketName, String prefix) throws Exception { | public List<Map> listFilesInDirectory(String bucketName, String prefix) throws Exception { | ||||
| List<Map> fileInfoList = new ArrayList<>(); | List<Map> fileInfoList = new ArrayList<>(); | ||||
| @@ -89,6 +292,37 @@ public class MinioUtil { | |||||
| return fileInfoList; | return fileInfoList; | ||||
| } | } | ||||
| /** | |||||
| * 删除文件 | |||||
| * | |||||
| * @param bucketName bucket名称 | |||||
| * @param objectName 文件名称 | |||||
| */ | |||||
| public static void removeObject(String bucketName, String objectName) throws Exception { | |||||
| minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build()); | |||||
| } | |||||
| /** | |||||
| * 批量删除文件 | |||||
| * | |||||
| * @param bucketName bucket | |||||
| * @param keys 需要删除的文件列表 | |||||
| */ | |||||
| public static void removeObjects(String bucketName, List<String> keys) { | |||||
| List<DeleteObject> objects = new LinkedList<>(); | |||||
| keys.forEach(s -> { | |||||
| objects.add(new DeleteObject(s)); | |||||
| try { | |||||
| removeObject(bucketName, s); | |||||
| } catch (Exception e) { | |||||
| System.err.println("批量删除失败!"); | |||||
| } | |||||
| }); | |||||
| } | |||||
| public String readObjectAsString(String bucketName, String objectName) throws Exception { | public String readObjectAsString(String bucketName, String objectName) throws Exception { | ||||
| try (InputStream inputStream = minioClient.getObject( | try (InputStream inputStream = minioClient.getObject( | ||||
| @@ -34,7 +34,7 @@ | |||||
| and img.id = #{image.id} | and img.id = #{image.id} | ||||
| </if> | </if> | ||||
| <if test="image.name != null and image.name != ''"> | <if test="image.name != null and image.name != ''"> | ||||
| and img.name = #{image.name} | |||||
| and img.name like "%"#{image.name}"%" | |||||
| </if> | </if> | ||||
| <if test="image.description != null and image.description != ''"> | <if test="image.description != null and image.description != ''"> | ||||
| and img.description = #{image.description} | and img.description = #{image.description} | ||||
| @@ -86,7 +86,7 @@ | |||||
| and id = #{image.id} | and id = #{image.id} | ||||
| </if> | </if> | ||||
| <if test="image.name != null and image.name != ''"> | <if test="image.name != null and image.name != ''"> | ||||
| and name = #{image.name} | |||||
| and name like "%"#{image.name}"%" | |||||
| </if> | </if> | ||||
| <if test="image.description != null and image.description != ''"> | <if test="image.description != null and image.description != ''"> | ||||
| and description = #{image.description} | and description = #{image.description} | ||||