| @@ -39,7 +39,7 @@ export default defineConfig({ | |||||
| theme: { | theme: { | ||||
| // 如果不想要 configProvide 动态设置主题需要把这个设置为 default | // 如果不想要 configProvide 动态设置主题需要把这个设置为 default | ||||
| // 只有设置为 variable, 才能使用 configProvide 动态设置主色调 | // 只有设置为 variable, 才能使用 configProvide 动态设置主色调 | ||||
| 'root-entry-name': 'variable', | |||||
| // 'root-entry-name': 'variable', | |||||
| }, | }, | ||||
| /** | /** | ||||
| * @name moment 的国际化配置 | * @name moment 的国际化配置 | ||||
| @@ -157,4 +157,10 @@ export default defineConfig({ | |||||
| }, | }, | ||||
| requestRecord: {}, | requestRecord: {}, | ||||
| icons: {}, | icons: {}, | ||||
| lessLoader: { | |||||
| modifyVars: { | |||||
| hack: 'true; @import "@/styles/theme.less";', | |||||
| }, | |||||
| javascriptEnabled: true, | |||||
| }, | |||||
| }); | }); | ||||
| @@ -1,3 +1,8 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-17 08:48:09 | |||||
| * @Description: | |||||
| */ | |||||
| /** | /** | ||||
| * @name 代理的配置 | * @name 代理的配置 | ||||
| * @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 | * @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 | ||||
| @@ -1,11 +0,0 @@ | |||||
| { | |||||
| "compilerOptions": { | |||||
| "jsx": "react-jsx", | |||||
| "emitDecoratorMetadata": true, | |||||
| "experimentalDecorators": true, | |||||
| "baseUrl": ".", | |||||
| "paths": { | |||||
| "@/*": ["./src/*"] | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -156,7 +156,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => { | |||||
| export async function onRouteChange({ clientRoutes, location }: any) { | export async function onRouteChange({ clientRoutes, location }: any) { | ||||
| const menus = getRemoteMenu(); | const menus = getRemoteMenu(); | ||||
| console.log('onRouteChange', clientRoutes, location, menus); | |||||
| // console.log('onRouteChange', clientRoutes, location, menus); | |||||
| if (menus === null && location.pathname !== PageEnum.LOGIN) { | if (menus === null && location.pathname !== PageEnum.LOGIN) { | ||||
| console.log('refresh'); | console.log('refresh'); | ||||
| history.go(0); | history.go(0); | ||||
| @@ -164,16 +164,16 @@ export async function onRouteChange({ clientRoutes, location }: any) { | |||||
| } | } | ||||
| export function patchRoutes({ routes, routeComponents }: any) { | export function patchRoutes({ routes, routeComponents }: any) { | ||||
| console.log('patchRoutes', routes, routeComponents); | |||||
| //console.log('patchRoutes', routes, routeComponents); | |||||
| } | } | ||||
| export async function patchClientRoutes({ routes }: any) { | export async function patchClientRoutes({ routes }: any) { | ||||
| console.log('patchClientRoutes', routes); | |||||
| // console.log('patchClientRoutes', routes); | |||||
| patchRouteWithRemoteMenus(routes); | patchRouteWithRemoteMenus(routes); | ||||
| } | } | ||||
| export function render(oldRender: () => void) { | export function render(oldRender: () => void) { | ||||
| console.log('render get routers', oldRender); | |||||
| // console.log('render get routers', oldRender); | |||||
| const token = getAccessToken(); | const token = getAccessToken(); | ||||
| if (!token || token?.length === 0) { | if (!token || token?.length === 0) { | ||||
| oldRender(); | oldRender(); | ||||
| @@ -190,10 +190,36 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||||
| memo.theme ??= {}; | memo.theme ??= {}; | ||||
| memo.theme.token = { | memo.theme.token = { | ||||
| colorPrimary: themes['primaryColor'], | colorPrimary: themes['primaryColor'], | ||||
| colorSuccess: themes['successColor'], | |||||
| colorError: themes['errorColor'], | |||||
| colorWarning: themes['warningColor'], | |||||
| }; | }; | ||||
| memo.theme.components ??= {}; | memo.theme.components ??= {}; | ||||
| memo.theme.components.Tabs = {}; | memo.theme.components.Tabs = {}; | ||||
| // memo.theme.cssVar = true; | |||||
| memo.theme.components.Button = { | |||||
| defaultBg: 'rgba(22, 100, 255, 0.06)', | |||||
| defaultBorderColor: 'rgba(22, 100, 255, 0.11)', | |||||
| defaultColor: themes['textColor'], | |||||
| defaultHoverBg: 'rgba(22, 100, 255, 0.06)', | |||||
| defaultHoverBorderColor: 'rgba(22, 100, 255, 0.5)', | |||||
| defaultHoverColor: '#3F7FFF ', | |||||
| defaultActiveBg: 'rgba(22, 100, 255, 0.12)', | |||||
| defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)', | |||||
| defaultActiveColor: themes['primaryColor'], | |||||
| contentFontSize: parseInt(themes['fontSize']), | |||||
| controlHeight: 34, | |||||
| }; | |||||
| memo.theme.components.Input = { | |||||
| inputFontSize: parseInt(themes['fontSize']), | |||||
| }; | |||||
| memo.theme.components.Table = { | |||||
| headerBg: 'rgba(242, 244, 247, 0.36)', | |||||
| headerBorderRadius: 4, | |||||
| }; | |||||
| memo.theme.components.Tabs = { | |||||
| titleFontSize: 16, | |||||
| }; | |||||
| memo.theme.cssVar = true; | |||||
| // memo.theme.hashed = false; | // memo.theme.hashed = false; | ||||
| // memo.appConfig = { | // memo.appConfig = { | ||||
| @@ -1 +0,0 @@ | |||||
| <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> | |||||
| @@ -1 +0,0 @@ | |||||
| <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> | |||||
| @@ -1 +0,0 @@ | |||||
| <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 +0,0 @@ | |||||
| <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> | |||||
| @@ -0,0 +1,5 @@ | |||||
| function CommonTableCell(text?: string | null) { | |||||
| return <span>{text ?? '--'}</span>; | |||||
| } | |||||
| export default CommonTableCell; | |||||
| @@ -0,0 +1,13 @@ | |||||
| import dayjs from 'dayjs'; | |||||
| function DateTableCell(text?: string | null) { | |||||
| if (text === undefined || text === null || text === '') { | |||||
| return <span>--</span>; | |||||
| } | |||||
| if (!dayjs(text).isValid()) { | |||||
| return <span>日期无效</span>; | |||||
| } | |||||
| return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>; | |||||
| } | |||||
| export default DateTableCell; | |||||
| @@ -1,4 +1,4 @@ | |||||
| @import '@/styles/theme.less'; | |||||
| // @import '@/styles/theme.less'; | |||||
| .kf-radio { | .kf-radio { | ||||
| display: flex; | display: flex; | ||||
| @@ -8,7 +8,7 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| padding: 12px 20px; | padding: 12px 20px; | ||||
| color: @text-color-second; | |||||
| color: @text-color-secondary; | |||||
| border: 1px solid #e0e0e0; | border: 1px solid #e0e0e0; | ||||
| border-radius: 8px; | border-radius: 8px; | ||||
| @@ -17,9 +17,14 @@ | |||||
| border: 1px solid @primary-color-hover; | border: 1px solid @primary-color-hover; | ||||
| } | } | ||||
| &:active { | |||||
| color: @primary-color; | |||||
| border: 1px solid @primary-color; | |||||
| } | |||||
| &--active { | &--active { | ||||
| color: @kf-primary-color; | |||||
| border: 1px solid @kf-primary-color; | |||||
| color: @primary-color; | |||||
| border: 1px solid @primary-color; | |||||
| } | } | ||||
| & + & { | & + & { | ||||
| @@ -2,7 +2,7 @@ | |||||
| .modal_title { | .modal_title { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| color: @kf-primary-color; | |||||
| color: @primary-color; | |||||
| font-weight: 400; | font-weight: 400; | ||||
| font-size: 20px; | font-size: 20px; | ||||
| @@ -1,7 +1,7 @@ | |||||
| .kf-page-title { | .kf-page-title { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| height: 49px; | |||||
| height: 50px; | |||||
| padding-left: 30px; | padding-left: 30px; | ||||
| background-image: url('../../assets/img/page-title-bg.png'); | background-image: url('../../assets/img/page-title-bg.png'); | ||||
| } | } | ||||
| @@ -50,7 +50,7 @@ const GlobalHeaderRight: React.FC = () => { | |||||
| <QuestionCircleOutlined /> | <QuestionCircleOutlined /> | ||||
| </span> | </span> | ||||
| <Avatar menu={true} /> | <Avatar menu={true} /> | ||||
| <SelectLang className={actionClassName} /> | |||||
| {/* <SelectLang className={actionClassName} /> */} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -3,3 +3,10 @@ export enum CommonTabKeys { | |||||
| Private = 'Private', // 私有 | Private = 'Private', // 私有 | ||||
| Public = 'Public', // 公开 | Public = 'Public', // 公开 | ||||
| } | } | ||||
| // 镜像状态 | |||||
| export enum MirrorVersionStatus { | |||||
| Available = 'available', // 可用 | |||||
| Building = 'building', // 构建中 | |||||
| Failed = 'failed', // 构建中 | |||||
| } | |||||
| @@ -4,6 +4,7 @@ body, | |||||
| height: 100%; | height: 100%; | ||||
| margin: 0; | margin: 0; | ||||
| padding: 0; | padding: 0; | ||||
| overflow-y: hidden; | |||||
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, | ||||
| 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', | 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', | ||||
| 'Noto Color Emoji'; | 'Noto Color Emoji'; | ||||
| @@ -31,54 +32,46 @@ body { | |||||
| -webkit-font-smoothing: antialiased; | -webkit-font-smoothing: antialiased; | ||||
| -moz-osx-font-smoothing: grayscale; | -moz-osx-font-smoothing: grayscale; | ||||
| } | } | ||||
| a { | |||||
| color: #1664ff; | |||||
| } | |||||
| .ant-btn-link { | |||||
| color: #1664ff; | |||||
| } | |||||
| .ant-pro-layout .ant-pro-layout-content { | .ant-pro-layout .ant-pro-layout-content { | ||||
| padding: 10px; | padding: 10px; | ||||
| } | } | ||||
| .ant-pro-layout .ant-pro-layout-bg-list { | .ant-pro-layout .ant-pro-layout-bg-list { | ||||
| background: #f9fafb; | background: #f9fafb; | ||||
| } | } | ||||
| .ant-table-wrapper .ant-table-thead > tr > th { | |||||
| background-color: #fff; | |||||
| } | |||||
| .ant-table-wrapper .ant-table-thead > tr > td { | |||||
| background-color: #fff; | |||||
| } | |||||
| .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-menu-light .ant-menu-item-selected .ant-pro-base-menu-inline-item-text{ | |||||
| color:#1664ff; | |||||
| .ant-menu-light .ant-menu-item-selected .ant-pro-base-menu-inline-item-text { | |||||
| // color: #1664ff; | |||||
| } | } | ||||
| .ant-pro-layout .ant-pro-sider .ant-layout-sider-children { | .ant-pro-layout .ant-pro-sider .ant-layout-sider-children { | ||||
| background: #f2f5f7; | background: #f2f5f7; | ||||
| } | } | ||||
| .ant-pro-base-menu-inline-item-title .ant-pro-base-menu-inline-item-text{ | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| .ant-pro-base-menu-inline-item-title .ant-pro-base-menu-inline-item-text { | |||||
| // color: #1d1d20; | |||||
| font-size: 16px; | |||||
| } | } | ||||
| // .ant-menu-light .ant-menu-item-selected{ | // .ant-menu-light .ant-menu-item-selected{ | ||||
| // color:#1664ff; | // color:#1664ff; | ||||
| // } | // } | ||||
| .ant-pro-layout .ant-pro-sider-menu{ | |||||
| .ant-pro-layout .ant-pro-sider-menu { | |||||
| padding-top: 40px; | padding-top: 40px; | ||||
| } | } | ||||
| .ant-pro-global-header-logo-mix{ | |||||
| .ant-table-wrapper .ant-table-container table>thead>tr:first-child >*:first-child,.ant-table-wrapper .ant-table-container table>tbody>tr:first-child{ | |||||
| padding: 0 30px; | |||||
| } | |||||
| .ant-pro-global-header-logo-mix { | |||||
| width: 257px; | |||||
| height: 75px; | height: 75px; | ||||
| border-bottom: 1px solid rgba(233, 237, 240, 1); | |||||
| margin-left: -16px; | margin-left: -16px; | ||||
| width: 257px; | |||||
| background:#f2f5f7; | |||||
| border-top-right-radius: 20px; | |||||
| padding-left: 28px; | padding-left: 28px; | ||||
| background: #f2f5f7; | |||||
| border-bottom: 1px solid rgba(233, 237, 240, 1); | |||||
| border-top-right-radius: 20px; | |||||
| } | } | ||||
| .ant-pro-layout .ant-pro-sider .ant-layout-sider-children{ | |||||
| .ant-pro-layout .ant-pro-sider .ant-layout-sider-children { | |||||
| border-right: unset; | border-right: unset; | ||||
| border-bottom-right-radius: 20px; | border-bottom-right-radius: 20px; | ||||
| } | } | ||||
| @@ -90,13 +83,13 @@ font-size:16px; | |||||
| .ant-pro-layout .ant-pro-layout-content { | .ant-pro-layout .ant-pro-layout-content { | ||||
| background-color: transparent; | background-color: transparent; | ||||
| } | } | ||||
| .ant-drawer .ant-drawer-body{ | |||||
| .ant-drawer .ant-drawer-body { | |||||
| padding: 0; | padding: 0; | ||||
| } | } | ||||
| .ant-drawer .ant-drawer-body .ant-row{ | |||||
| .ant-drawer .ant-drawer-body .ant-row { | |||||
| padding: 0 24px; | padding: 0 24px; | ||||
| } | } | ||||
| .ant-drawer .ant-drawer-body .ant-form-item{ | |||||
| .ant-drawer .ant-drawer-body .ant-form-item { | |||||
| margin-bottom: 20px; | margin-bottom: 20px; | ||||
| } | } | ||||
| .ant-menu .ant-menu-submenu-title .anticon { | .ant-menu .ant-menu-submenu-title .anticon { | ||||
| @@ -107,9 +100,10 @@ font-size:16px; | |||||
| padding: 21px 16px; | padding: 21px 16px; | ||||
| background-color: #fff; | background-color: #fff; | ||||
| } | } | ||||
| .ant-table-wrapper .ant-table { | |||||
| height: 81vh; | |||||
| } | |||||
| // .ant-table-wrapper .ant-table { | |||||
| // height: 81vh; | |||||
| // // overflow-y: auto; | |||||
| // } | |||||
| .ant-pro-global-header-logo img { | .ant-pro-global-header-logo img { | ||||
| height: 21px; | height: 21px; | ||||
| } | } | ||||
| @@ -117,9 +111,9 @@ font-size:16px; | |||||
| height: 94vh; | height: 94vh; | ||||
| } | } | ||||
| .ant-pro-layout .ant-pro-layout-container { | .ant-pro-layout .ant-pro-layout-container { | ||||
| height: 98vh; | |||||
| height: 100vh; | |||||
| } | } | ||||
| .ant-modal-confirm .ant-modal-confirm-paragraph{ | |||||
| .ant-modal-confirm .ant-modal-confirm-paragraph { | |||||
| margin: 54px 0 auto; | margin: 54px 0 auto; | ||||
| text-align: center; | text-align: center; | ||||
| } | } | ||||
| @@ -130,38 +124,39 @@ font-size:16px; | |||||
| margin-top: 30px; | margin-top: 30px; | ||||
| text-align: center; | 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 { | |||||
| width: 110px; | |||||
| height: 40px; | |||||
| margin-right: 10px; | |||||
| // color: #1d1d20; | |||||
| font-size: 18px; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border-color: transparent; | |||||
| border-radius: 10px; | |||||
| } | } | ||||
| .ant-modal-confirm-btns .ant-btn-default:hover { | .ant-modal-confirm-btns .ant-btn-default:hover { | ||||
| background: rgba(22, 100, 255, 0.06); | background: rgba(22, 100, 255, 0.06); | ||||
| border-color: transparent; | border-color: transparent; | ||||
| } | } | ||||
| .ant-modal-confirm-btns .ant-btn-primary{ | |||||
| width:110px; | |||||
| height:40px; | |||||
| background:#1664ff; | |||||
| border-radius:10px; | |||||
| .ant-modal-confirm-btns .ant-btn-primary { | |||||
| width: 110px; | |||||
| height: 40px; | |||||
| font-size: 18px; | font-size: 18px; | ||||
| border-radius: 10px; | |||||
| border-radius: 10px; | |||||
| } | } | ||||
| .ant-modal .ant-input-affix-wrapper{ | |||||
| .ant-modal .ant-input-affix-wrapper { | |||||
| height: 46px; | height: 46px; | ||||
| padding: 1px 11px; | padding: 1px 11px; | ||||
| } | } | ||||
| .ant-modal .ant-select-single{ | |||||
| .ant-modal .ant-select-single { | |||||
| height: 46px; | height: 46px; | ||||
| } | } | ||||
| .ant-modal .ant-select-single .ant-select-selector .ant-select-selection-placeholder{ | |||||
| .ant-modal .ant-select-single .ant-select-selector .ant-select-selection-placeholder { | |||||
| line-height: 46px; | line-height: 46px; | ||||
| } | } | ||||
| .ant-modal .ant-modal-close-x { | .ant-modal .ant-modal-close-x { | ||||
| width: 26px; | width: 26px; | ||||
| height: 26px; | height: 26px; | ||||
| @@ -176,13 +171,13 @@ border-color: transparent; | |||||
| .ant-modal .ant-modal-content { | .ant-modal .ant-modal-content { | ||||
| padding: 0; | padding: 0; | ||||
| } | } | ||||
| .ant-modal-confirm-body-wrapper{ | |||||
| height:303px; | |||||
| background-image: url(/assets/images/modal-back.png); | |||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-position: top center; | |||||
| border-radius: 0; | |||||
| .ant-modal-confirm-body-wrapper { | |||||
| height: 303px; | |||||
| background-image: url(/assets/images/modal-back.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100%; | |||||
| border-radius: 0; | |||||
| } | } | ||||
| .ant-modal .ant-modal-content { | .ant-modal .ant-modal-content { | ||||
| border-radius: 20px; | border-radius: 20px; | ||||
| @@ -195,14 +190,14 @@ border-radius: 0; | |||||
| } | } | ||||
| .ant-pagination .ant-pagination-item-active a { | .ant-pagination .ant-pagination-item-active a { | ||||
| color: #fff; | color: #fff; | ||||
| background: #1664ff; | |||||
| border-color: #1664ff; | |||||
| background: rgba(22, 100, 255, 0.8); | |||||
| // color: #fff; | |||||
| border-radius: 6px; | border-radius: 6px; | ||||
| } | } | ||||
| .ant-pagination .ant-pagination-item-active:hover { | .ant-pagination .ant-pagination-item-active:hover { | ||||
| color: #fff; | |||||
| background: rgba(22, 100, 255, 0.8); | |||||
| border-color: rgba(22, 100, 255, 0.8); | |||||
| // color: #fff; | |||||
| // background: rgba(22, 100, 255, 0.8); | |||||
| // border-color: rgba(22, 100, 255, 0.8); | |||||
| border-radius: 6px; | border-radius: 6px; | ||||
| } | } | ||||
| .ant-pagination .ant-pagination-item { | .ant-pagination .ant-pagination-item { | ||||
| @@ -259,7 +254,3 @@ ol { | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| .umi-local-svg { | |||||
| vertical-align: -1px; | |||||
| } | |||||
| @@ -3,14 +3,14 @@ | |||||
| * @Date: 2024-04-15 10:01:29 | * @Date: 2024-04-15 10:01:29 | ||||
| * @Description: | * @Description: | ||||
| */ | */ | ||||
| import { FormInstance } from 'antd'; | |||||
| import { debounce } from 'lodash'; | import { debounce } from 'lodash'; | ||||
| import { useCallback, useEffect, useRef, useState } from 'react'; | import { useCallback, useEffect, useRef, useState } from 'react'; | ||||
| /** | /** | ||||
| * Generates a state reference with the initial value. | |||||
| * 生成具有初始值的状态引用 | |||||
| * | * | ||||
| * @param initialValue - The initial value for the state | |||||
| * @return An array containing the state value, state setter function, and a mutable reference object | |||||
| * @param initialValue - 状态的初始值 | |||||
| * @return 包含状态值、状态设置函数和可变引用对象的数组 | |||||
| */ | */ | ||||
| export function useStateRef<T>(initialValue: T) { | export function useStateRef<T>(initialValue: T) { | ||||
| const [value, setValue] = useState(initialValue); | const [value, setValue] = useState(initialValue); | ||||
| @@ -25,10 +25,10 @@ export function useStateRef<T>(initialValue: T) { | |||||
| } | } | ||||
| /** | /** | ||||
| * Generates a custom hook for managing the visibility state of a 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. | |||||
| * @param initialValue - 模态框的初始可见性状态。 | |||||
| * @return 一个数组,包含可见性状态和打开和关闭模态框的函数。 | |||||
| */ | */ | ||||
| export function useVisible(initialValue: boolean) { | export function useVisible(initialValue: boolean) { | ||||
| const [visible, setVisible] = useState(initialValue); | const [visible, setVisible] = useState(initialValue); | ||||
| @@ -47,34 +47,34 @@ export function useVisible(initialValue: boolean) { | |||||
| type Callback<T> = (state: T) => void; | type Callback<T> = (state: T) => void; | ||||
| /** | /** | ||||
| * Generates a stateful value and a function to update it that triggers callbacks. | |||||
| * 生成一个具有回调机制的可变状态值和更新它的函数。 | |||||
| * | * | ||||
| * @param initialValue - The initial value of the state. | |||||
| * @return A tuple containing the current state value and a function to update the state. | |||||
| * @param initialValue - 初始状态值。 | |||||
| * @return 一个元组,包含当前状态值和用于更新状态的函数。 | |||||
| */ | */ | ||||
| export function useCallbackState<T>(initialValue: T) { | export function useCallbackState<T>(initialValue: T) { | ||||
| const [state, _setState] = useState(initialValue); | |||||
| const [state, _setState] = useState<T>(initialValue); | |||||
| const callbackQueue = useRef<Callback<T>[]>([]); | const callbackQueue = useRef<Callback<T>[]>([]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| callbackQueue.current.forEach((cb) => cb(state)); | callbackQueue.current.forEach((cb) => cb(state)); | ||||
| callbackQueue.current = []; | callbackQueue.current = []; | ||||
| }, [state]); | }, [state]); | ||||
| const setState = (newValue: T, callback: Callback<T>) => { | |||||
| const setState = (newValue: T | ((prevState: T) => T), callback?: Callback<T>) => { | |||||
| _setState(newValue); | _setState(newValue); | ||||
| if (callback && typeof callback === 'function') { | if (callback && typeof callback === 'function') { | ||||
| callbackQueue.current.push(callback); | callbackQueue.current.push(callback); | ||||
| } | } | ||||
| }; | }; | ||||
| return [state, setState]; | |||||
| return [state, setState] as const; | |||||
| } | } | ||||
| /** | /** | ||||
| * A hook that tracks the size of a DOM element. | |||||
| * 用于追踪 DOM 元素尺寸的 hook。 | |||||
| * | * | ||||
| * @param initialWidth - The initial width of the element. | |||||
| * @param initialHeight - The initial height of the element. | |||||
| * @param deps - dependency list. | |||||
| * @return - A tuple containing the ref to the DOM element, the current width, and the current height. | |||||
| * @param initialWidth - 初始宽度。 | |||||
| * @param initialHeight - 初始高度。 | |||||
| * @param deps - 依赖列表。 | |||||
| * @return 一个元组,包含 DOM 元素的 ref、当前宽度和当前高度。 | |||||
| */ | */ | ||||
| export function useDomSize<T extends HTMLElement>( | export function useDomSize<T extends HTMLElement>( | ||||
| initialWidth: number, | initialWidth: number, | ||||
| @@ -86,8 +86,6 @@ export function useDomSize<T extends HTMLElement>( | |||||
| const [height, setHeight] = useState(initialHeight); | const [height, setHeight] = useState(initialHeight); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| console.log('dddddd'); | |||||
| const setDomHeight = () => { | const setDomHeight = () => { | ||||
| if (domRef.current) { | if (domRef.current) { | ||||
| setHeight(domRef.current.offsetHeight); | setHeight(domRef.current.offsetHeight); | ||||
| @@ -106,3 +104,25 @@ export function useDomSize<T extends HTMLElement>( | |||||
| return [domRef, { width, height }] as const; | return [domRef, { width, height }] as const; | ||||
| } | } | ||||
| /** | |||||
| * 用于在 modal 关闭时重置 Form 表单的 hook。 | |||||
| * | |||||
| * @param form - Ant Design Form 表单实例 | |||||
| * @param open - modal 是否打开 | |||||
| */ | |||||
| export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => { | |||||
| const prevOpenRef = useRef<boolean>(); | |||||
| useEffect(() => { | |||||
| prevOpenRef.current = open; | |||||
| }, [open]); | |||||
| const prevOpen = prevOpenRef.current; | |||||
| useEffect(() => { | |||||
| if (!open && prevOpen) { | |||||
| form.resetFields(); | |||||
| } | |||||
| }, [form, prevOpen, open]); | |||||
| }; | |||||
| @@ -1 +0,0 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="14.902" height="14.902" viewBox="0 0 14.902 14.902"><defs><style>.a{fill:#1664ff;}</style></defs><g transform="translate(-75.735 -76.787)"><path class="a" d="M87.7,88.735a6.355,6.355,0,1,0-8.985,0A6.355,6.355,0,0,0,87.7,88.735Zm.763.763a7.451,7.451,0,1,1,0-10.519A7.451,7.451,0,0,1,88.464,89.5Z" transform="translate(0 0)"/><path class="a" d="M283.541,282.446H286.5a.552.552,0,0,1,0,1.1h-2.954V286.5a.552.552,0,0,1-1.1,0v-2.954h-2.963a.552.552,0,0,1,0-1.1h2.963v-2.963a.552.552,0,0,1,1.1,0Z" transform="translate(-199.785 -198.751)"/></g></svg> | |||||
| @@ -1 +0,0 @@ | |||||
| <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> | |||||
| @@ -1 +0,0 @@ | |||||
| <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1713324052180" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1059" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M440.167543 988.161377h-269.463331c-24.790626 0-45.970444-8.784505-63.539453-26.299621A86.551622 86.551622 0 0 1 80.865138 898.322302v-808.389993C80.865138 65.141682 89.649642 43.961865 107.164759 26.446748A86.551622 86.551622 0 0 1 170.704212 0.147127h471.56083a44.407557 44.407557 0 0 1 33.35956 14.874376l202.097498 224.516847a44.515342 44.515342 0 0 1 11.533031 30.072108v202.097498h-89.839075v-202.097498h44.946484l-33.413453 30.018215-202.097498-224.57074 33.413453-30.018215v44.892591h-471.56083v808.389993h269.463331v89.839075z m45.000377-44.892591a44.946484 44.946484 0 0 1-82.45578 25.006197 44.946484 44.946484 0 1 1 74.856914-50.012394 44.730913 44.730913 0 0 1 7.544973 25.006197z m404.194996-471.56083a45.000376 45.000376 0 1 1-90.000752 0 45.000376 45.000376 0 0 1 90.000752 0zM838.757703 933.460321l-112.312317-89.892968 56.156159-70.114358 112.258423 89.839074-56.102265 70.114359z m73.024562-35.138019a44.892591 44.892591 0 0 1-62.192136 41.605139 44.892591 44.892591 0 0 1-7.814437-79.006649 44.892591 44.892591 0 0 1 66.611335 20.20975 44.892591 44.892591 0 0 1 3.395238 17.245653z m-112.258423-89.785182a44.892591 44.892591 0 0 1-53.784881 44.138094 44.892591 44.892591 0 0 1-32.766741-61.383747 44.892591 44.892591 0 0 1 66.557443-20.20975 44.892591 44.892591 0 0 1 19.994179 37.455403z" fill="#D47AEF" p-id="1060"></path><path d="M844.36254 314.503049H619.7918a44.784806 44.784806 0 0 1-44.892591-44.892591V45.039718h89.785182v224.57074H619.7918v-44.946484h224.57074v89.839075z m45.000376-44.892591a44.892591 44.892591 0 0 1-45.000376 45.000376 44.892591 44.892591 0 0 1-45.000376-45.000376 44.892591 44.892591 0 0 1 62.192137-41.605138 44.892591 44.892591 0 0 1 27.808615 41.605138zM664.792176 45.039718a44.892591 44.892591 0 0 1-53.784881 44.138094A44.892591 44.892591 0 0 1 578.294447 27.794065a44.892591 44.892591 0 0 1 66.557443-20.155857 44.892591 44.892591 0 0 1 19.994179 37.40151zM619.7918 494.127306a188.624332 188.624332 0 1 0 0 377.248663 188.624332 188.624332 0 0 0 0-377.248663z m0 88.922899a99.701433 99.701433 0 1 1 0 199.402865 99.701433 99.701433 0 0 1 0-199.402865z" fill="#D47AEF" p-id="1061"></path></svg> | |||||
| @@ -1 +0,0 @@ | |||||
| <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> | |||||
| @@ -1 +0,0 @@ | |||||
| <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> | |||||
| @@ -1 +0,0 @@ | |||||
| <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> | |||||
| @@ -1 +0,0 @@ | |||||
| <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> | |||||
| @@ -1 +0,0 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="14.313" height="13.129" viewBox="0 0 14.313 13.129" fill="currentColor" stroke="currentColor"><defs><style>.a{stroke-width:0.2px;}</style></defs><g transform="translate(-106.567 -129.178)"><path class="a" d="M114.2,378.558H108a1.332,1.332,0,0,1-1.328-1.328v-6.2A1.328,1.328,0,0,1,108,369.707h4.255v.724H108a.6.6,0,0,0-.6.6v6.2a.6.6,0,0,0,.6.6h6.2a.6.6,0,0,0,.6-.6v-3.1h.724v3.1a1.328,1.328,0,0,1-1.328,1.328Z" transform="translate(0 -236.351)"/><path class="a" d="M423.317,138.14h-6.2a1.332,1.332,0,0,1-1.328-1.328v-6.2a1.332,1.332,0,0,1,1.328-1.332h6.2a1.328,1.328,0,0,1,1.339,1.332v6.2a1.328,1.328,0,0,1-1.339,1.328Zm-6.2-8.138a.608.608,0,0,0-.6.608v6.2a.6.6,0,0,0,.6.6h6.2a.6.6,0,0,0,.615-.6v-6.2a.608.608,0,0,0-.615-.608Z" transform="translate(-303.876)"/></g></svg> | |||||
| @@ -1 +0,0 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="14.239" height="14.266" viewBox="0 0 14.239 14.266" fill="currentColor"><defs><style></style></defs><g transform="translate(59 -12320.6)"><path class="a" d="M325.069,355.086a6.46,6.46,0,0,1-4.983-2.344.445.445,0,1,1,.684-.57,5.573,5.573,0,0,0,7.689.869.447.447,0,0,1,.542.712A6.341,6.341,0,0,1,325.069,355.086Zm5.9-4.826a.388.388,0,0,1-.085-.006.445.445,0,0,1-.35-.526,5.641,5.641,0,0,0,.107-1.1,5.548,5.548,0,0,0-3.09-4.993.442.442,0,0,1-.2-.6.447.447,0,0,1,.6-.2,6.443,6.443,0,0,1,3.582,5.79,6.374,6.374,0,0,1-.129,1.276A.442.442,0,0,1,330.972,350.26Zm-11.926-1.389h-.016a.448.448,0,0,1-.435-.463,6.5,6.5,0,0,1,4.965-6.08.448.448,0,0,1,.214.869,5.586,5.586,0,0,0-4.272,5.242A.461.461,0,0,1,319.046,348.871Zm0,0" transform="translate(-376.902 11979.78)"/><path class="a" d="M469.186,298.366a1.783,1.783,0,1,1,1.786-1.78A1.787,1.787,0,0,1,469.186,298.366Z" transform="translate(-521.02 12025.8)"/><path class="a" d="M303.767,323a.891.891,0,1,0,.891.891A.893.893,0,0,0,303.767,323Zm-5.38,11.287a1.786,1.786,0,1,1,1.786-1.786A1.787,1.787,0,0,1,298.386,334.287Z" transform="translate(-355.6 11998.488)"/><path class="a" d="M325.891,568.795a.892.892,0,1,0,.891.892A.893.893,0,0,0,325.891,568.795Zm10.666,2.678a1.786,1.786,0,1,1,1.786-1.786A1.787,1.787,0,0,1,336.558,571.472Z" transform="translate(-383.105 11761.302)"/><path class="a" d="M664.492,596.3a.891.891,0,1,0,.891.891A.893.893,0,0,0,664.492,596.3Z" transform="translate(-711.039 11733.797)"/></g></svg> | |||||
| @@ -1 +0,0 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="14.34" height="14.34" viewBox="0 0 14.34 14.34" fill="currentColor"><path class="a" d="M7.17,0a7.17,7.17,0,1,0,7.17,7.17.664.664,0,1,0-1.328,0A5.894,5.894,0,0,1,7.17,13.012,5.894,5.894,0,0,1,1.328,7.17,5.894,5.894,0,0,1,7.17,1.328a4.9,4.9,0,0,1,3.054.929V2.39h0l-.664.8c-.266.266,0,.8.4.8l3.054.133c.266,0,.531-.4.531-.664L12.614.531A.515.515,0,0,0,11.685.4l-.531.8A7.026,7.026,0,0,0,7.17,0Z"/></svg> | |||||
| @@ -1 +0,0 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17" height="17" viewBox="0 0 17 17"><defs><style>.a{fill:#fff;stroke:#707070;}.b{clip-path:url(#a);}.c,.d{fill:#f98e1b;}.c{stroke:#f98e1b;stroke-width:0.2px;}.d{opacity:0.21;}</style><clipPath id="a"><rect class="a" width="17" height="17" transform="translate(1789 324)"/></clipPath></defs><g class="b" transform="translate(-1789 -324)"><g transform="translate(1790.273 325.273)"><path class="c" d="M9.8,11.963H1.359A1.335,1.335,0,0,1,0,10.653V.522A.532.532,0,0,1,.542,0a.538.538,0,0,1,.542.522V10.653a.271.271,0,0,0,.276.266H9.788a.271.271,0,0,0,.276-.266V.522A.532.532,0,0,1,10.605,0a.538.538,0,0,1,.542.522V10.653A1.326,1.326,0,0,1,9.8,11.963Z" transform="translate(1.644 2.491)"/><path class="c" d="M13.9,1.044H.542A.532.532,0,0,1,0,.522.532.532,0,0,1,.542,0H13.912a.532.532,0,0,1,.542.522A.54.54,0,0,1,13.9,1.044Z" transform="translate(0 2.098)"/><path class="c" d="M.542,7.859A.532.532,0,0,1,0,7.337V.522A.532.532,0,0,1,.542,0a.538.538,0,0,1,.542.522V7.337A.538.538,0,0,1,.542,7.859Z" transform="translate(5.246 4.241)"/><path class="c" d="M.542,7.859A.532.532,0,0,1,0,7.337V.522A.532.532,0,0,1,.542,0a.538.538,0,0,1,.542.522V7.337A.526.526,0,0,1,.542,7.859Z" transform="translate(8.458 4.241)"/><path class="c" d="M2.708,1.044H.542A.532.532,0,0,1,0,.522.538.538,0,0,1,.542,0H2.718A.532.532,0,0,1,3.26.522.54.54,0,0,1,2.708,1.044Z" transform="translate(5.597)"/><rect class="d" width="6" height="7" transform="translate(6 2.454)"/></g></g></svg> | |||||
| @@ -1 +0,0 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17" height="17" viewBox="0 0 17 17" fill="currentColor"><defs><clipPath id="a"><rect class="a" width="17" height="17" transform="translate(1607 208)"/></clipPath></defs><g class="b" transform="translate(-1607 -208)"><g transform="translate(-0.136 -0.214)"><rect class="c" opacity="0.21" width="4.75" height="8.011" rx="2" transform="translate(1617.532 209.427)"/><g transform="translate(1511.991 145.627)"><path class="d" d="M282,256.622a.524.524,0,0,0-.522-.522h-6.657a.522.522,0,0,0,0,1.044h6.657A.524.524,0,0,0,282,256.622Zm-7.179,2.6a.522.522,0,0,0,0,1.044h3.109a.522.522,0,0,0,0-1.044Zm7.487,5.349a.464.464,0,0,0-.112-.085,2.565,2.565,0,0,0,.467-1.479,2.631,2.631,0,1,0-2.631,2.6,2.649,2.649,0,0,0,1.443-.425.521.521,0,0,0,.094.13l1.355,1.355a.522.522,0,0,0,.739-.739Zm-2.276.01a1.573,1.573,0,1,1,1.594-1.573A1.584,1.584,0,0,1,280.033,264.581Z" transform="translate(-174.416 -189.172)"/><path class="d" d="M104.819,77.329h-5.8a1.044,1.044,0,0,1-1.041-1.041V65.9a1.044,1.044,0,0,1,1.041-1.041h8.9a1.044,1.044,0,0,1,1.041,1.041v5.18h0v0a.524.524,0,0,0,1.048,0c0-.02,0-.037,0-.057V65.882c0-1.145-.449-2.082-1.594-2.082H99.082A2.089,2.089,0,0,0,97,65.882V76.291a2.089,2.089,0,0,0,2.082,2.082h5.73a.522.522,0,0,0,.007-1.044Z" transform="translate(0 0)"/><path class="d" d="M832.7,607.722a.524.524,0,1,0,.524-.522A.523.523,0,0,0,832.7,607.722Z" transform="translate(-723.731 -534.559)"/></g></g></g></svg> | |||||
| @@ -1 +0,0 @@ | |||||
| <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> | |||||
| @@ -1 +0,0 @@ | |||||
| <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1713324237000" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1430" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M912.384 668.551529v88.545883a99.448471 99.448471 0 0 1-99.568941 99.508706H211.184941a99.448471 99.448471 0 0 1-99.568941-99.508706v-88.545883a49.814588 49.814588 0 0 0-99.568941 0v87.100236a199.800471 199.800471 0 0 0 199.80047 199.80047h600.184471a199.800471 199.800471 0 0 0 199.800471-199.80047v-87.100236a49.814588 49.814588 0 0 0-99.568942 0z" fill="#1664FF" p-id="1431"></path><path d="M728.003765 398.516706a50.778353 50.778353 0 0 0-71.740236 0l-95.171764 95.171765V125.530353a49.814588 49.814588 0 1 0-99.568941 0v368.158118l-95.171765-95.171765a50.718118 50.718118 0 1 0-71.740235 71.740235l177.995294 177.995294a54.753882 54.753882 0 0 0 77.462588 0l177.874823-177.995294a50.537412 50.537412 0 0 0 0.060236-71.740235z" fill="#1664FF" p-id="1432"></path></svg> | |||||
| @@ -1 +0,0 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17" height="17" viewBox="0 0 17 17"><defs><style>.a{fill:#fff;stroke:#1431b3;}.b{clip-path:url(#a);}.c,.d{fill:#1664ff;}.c{opacity:0.21;}</style><clipPath id="a"><rect class="a" width="17" height="17" transform="translate(1607 208)"/></clipPath></defs><g class="b" transform="translate(-1607 -208)"><g transform="translate(-0.136 -0.214)"><rect class="c" width="4.75" height="8.011" rx="2" transform="translate(1617.532 209.427)"/><g transform="translate(1511.991 145.627)"><path class="d" d="M282,256.622a.524.524,0,0,0-.522-.522h-6.657a.522.522,0,0,0,0,1.044h6.657A.524.524,0,0,0,282,256.622Zm-7.179,2.6a.522.522,0,0,0,0,1.044h3.109a.522.522,0,0,0,0-1.044Zm7.487,5.349a.464.464,0,0,0-.112-.085,2.565,2.565,0,0,0,.467-1.479,2.631,2.631,0,1,0-2.631,2.6,2.649,2.649,0,0,0,1.443-.425.521.521,0,0,0,.094.13l1.355,1.355a.522.522,0,0,0,.739-.739Zm-2.276.01a1.573,1.573,0,1,1,1.594-1.573A1.584,1.584,0,0,1,280.033,264.581Z" transform="translate(-174.416 -189.172)"/><path class="d" d="M104.819,77.329h-5.8a1.044,1.044,0,0,1-1.041-1.041V65.9a1.044,1.044,0,0,1,1.041-1.041h8.9a1.044,1.044,0,0,1,1.041,1.041v5.18h0v0a.524.524,0,0,0,1.048,0c0-.02,0-.037,0-.057V65.882c0-1.145-.449-2.082-1.594-2.082H99.082A2.089,2.089,0,0,0,97,65.882V76.291a2.089,2.089,0,0,0,2.082,2.082h5.73a.522.522,0,0,0,.007-1.044Z" transform="translate(0 0)"/><path class="d" d="M832.7,607.722a.524.524,0,1,0,.524-.522A.523.523,0,0,0,832.7,607.722Z" transform="translate(-723.731 -534.559)"/></g></g></g></svg> | |||||
| @@ -36,3 +36,13 @@ | |||||
| margin-bottom: 0; | margin-bottom: 0; | ||||
| } | } | ||||
| } | } | ||||
| // 表格样式 | |||||
| .ant-table-header { | |||||
| border: 1px solid rgba(167, 178, 194, 0.17); | |||||
| border-bottom: none; | |||||
| } | |||||
| .ant-table-wrapper .ant-table-thead > tr > td { | |||||
| background-color: #fff; | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { | import { | ||||
| addDatasetVersionDetail, | addDatasetVersionDetail, | ||||
| deleteDatasetVersion, | deleteDatasetVersion, | ||||
| @@ -7,12 +8,7 @@ import { | |||||
| getDatasetVersionsById, | getDatasetVersionsById, | ||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { | |||||
| DeleteOutlined, | |||||
| DownloadOutlined, | |||||
| PlusCircleOutlined, | |||||
| UploadOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | ||||
| import moment from 'moment'; | import moment from 'moment'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| @@ -203,7 +199,7 @@ const Dataset = () => { | |||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="download" | key="download" | ||||
| icon={<DownloadOutlined />} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| onClick={(e) => downloadAlone(e, record)} | onClick={(e) => downloadAlone(e, record)} | ||||
| > | > | ||||
| 下载 | 下载 | ||||
| @@ -227,9 +223,9 @@ const Dataset = () => { | |||||
| <div className={Styles.datasetIntroTopBox}> | <div className={Styles.datasetIntroTopBox}> | ||||
| <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | ||||
| <div className={Styles.smallTagBox}> | <div className={Styles.smallTagBox}> | ||||
| <div className={Styles.tagItem}>数据集 id:{datasetDetailObj.id}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> | <div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> | ||||
| <div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> | <div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> | ||||
| {/* <div className={Styles.tagItem}>English</div> */} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className={Styles.datasetIntroCneterBox}> | <div className={Styles.datasetIntroCneterBox}> | ||||
| @@ -257,10 +253,10 @@ const Dataset = () => { | |||||
| options={versionList} | options={versionList} | ||||
| /> | /> | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| onClick={showModal} | onClick={showModal} | ||||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | > | ||||
| 创建新版本 | 创建新版本 | ||||
| </Button> | </Button> | ||||
| @@ -269,21 +265,21 @@ const Dataset = () => { | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | ||||
| > | > | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| style={{ margin: '0 20px 0 0' }} | style={{ margin: '0 20px 0 0' }} | ||||
| onClick={deleteDataset} | onClick={deleteDataset} | ||||
| icon={<DeleteOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | > | ||||
| 删除 | 删除 | ||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| disabled={!version} | disabled={!version} | ||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| style={{ margin: '0 20px 0 0' }} | style={{ margin: '0 20px 0 0' }} | ||||
| onClick={handleExport} | onClick={handleExport} | ||||
| icon={<DownloadOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| > | > | ||||
| 下载 | 下载 | ||||
| </Button> | </Button> | ||||
| @@ -381,7 +377,7 @@ const Dataset = () => { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz"> | |||||
| <Button | <Button | ||||
| style={{ | style={{ | ||||
| fontSize: '14px', | fontSize: '14px', | ||||
| @@ -5,9 +5,9 @@ | |||||
| height: 49px; | height: 49px; | ||||
| padding: 0 30px; | padding: 0 30px; | ||||
| padding-right: 30px; | padding-right: 30px; | ||||
| font-family: 'Alibaba'; | |||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| font-family: 'Alibaba'; | |||||
| } | } | ||||
| .datasetIntroTopBox { | .datasetIntroTopBox { | ||||
| display: flex; | display: flex; | ||||
| @@ -36,10 +36,10 @@ | |||||
| padding: 20px 30px; | padding: 20px 30px; | ||||
| color: #1d1d20; | color: #1d1d20; | ||||
| font-size: 16px; | font-size: 16px; | ||||
| font-family: alibaba; | |||||
| background: #ffffff; | background: #ffffff; | ||||
| border-radius: 10px; | border-radius: 10px; | ||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | ||||
| font-family: alibaba; | |||||
| .dataButtonList { | .dataButtonList { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| @@ -70,7 +70,7 @@ | |||||
| .datasetBox { | .datasetBox { | ||||
| font-family: 'Alibaba'; | font-family: 'Alibaba'; | ||||
| background: #f9fafb; | background: #f9fafb; | ||||
| :global { | :global { | ||||
| .ant-tabs-top > .ant-tabs-nav { | .ant-tabs-top > .ant-tabs-nav { | ||||
| margin: 0; | margin: 0; | ||||
| @@ -89,20 +89,8 @@ | |||||
| } | } | ||||
| .plusButton { | .plusButton { | ||||
| margin: 0 18px 0 20px; | margin: 0 18px 0 20px; | ||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| font-family: 'Alibaba'; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border: 1px solid; | |||||
| border-color: rgba(22, 100, 255, 0.11); | |||||
| border-radius: 4px; | |||||
| } | |||||
| .plusButton:hover { | |||||
| color: #1d1d20 !important; | |||||
| background: rgba(22, 100, 255, 0.06) !important; | |||||
| border: 1px solid !important; | |||||
| border-color: rgba(22, 100, 255, 0.11) !important; | |||||
| } | } | ||||
| .datasetCneterBox { | .datasetCneterBox { | ||||
| display: flex; | display: flex; | ||||
| justify-content: space-between; | justify-content: space-between; | ||||
| @@ -119,8 +107,8 @@ | |||||
| height: 100%; | height: 100%; | ||||
| margin-right: 10px; | margin-right: 10px; | ||||
| padding-top: 15px; | padding-top: 15px; | ||||
| background: #ffffff; | |||||
| font-family: 'Alibaba'; | font-family: 'Alibaba'; | ||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | ||||
| .custTab { | .custTab { | ||||
| display: flex; | display: flex; | ||||
| @@ -210,8 +198,8 @@ | |||||
| display: flex; | display: flex; | ||||
| flex: 1; | flex: 1; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| font-family: 'Alibaba'; | |||||
| height: 100%; | height: 100%; | ||||
| overflow-y: auto; | |||||
| padding: 22px 30px 26px 30px; | padding: 22px 30px 26px 30px; | ||||
| background: #ffffff; | background: #ffffff; | ||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | ||||
| @@ -229,68 +217,71 @@ | |||||
| flex: 1; | flex: 1; | ||||
| flex-wrap: wrap; | flex-wrap: wrap; | ||||
| align-content: flex-start; | align-content: flex-start; | ||||
| font-family: 'Alibaba'; | |||||
| width: 103%; | |||||
| width: 100%; | |||||
| .dataItem { | .dataItem { | ||||
| position: relative; | position: relative; | ||||
| width: 23%; | |||||
| width: 23.8%; | |||||
| height:164px; | height:164px; | ||||
| background:#ffffff; | |||||
| border:1px solid; | |||||
| border-color:#eaeaea; | |||||
| border-radius:4px; | |||||
| margin: 0 20px 25px 0; | margin: 0 20px 25px 0; | ||||
| background: #ffffff; | |||||
| border: 1px solid; | |||||
| border-color: #eaeaea; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | cursor: pointer; | ||||
| .itemText { | .itemText { | ||||
| position: absolute; | position: absolute; | ||||
| top: 20px; | top: 20px; | ||||
| left: 20px; | left: 20px; | ||||
| background: linear-gradient(to right ,rgba(22, 100, 255,0.6) 0,rgba(22, 100, 255,0) 100%); | |||||
| height: 6px; | height: 6px; | ||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| font-family: 'Alibaba'; | |||||
| line-height: 0px; | line-height: 0px; | ||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| rgba(22, 100, 255, 0.3) 0, | |||||
| rgba(22, 100, 255, 0) 100% | |||||
| ); | |||||
| } | } | ||||
| .itemDescripition{ | |||||
| .itemDescripition { | |||||
| position: absolute; | position: absolute; | ||||
| top: 57px; | top: 57px; | ||||
| left: 20px; | left: 20px; | ||||
| display: -webkit-box; | |||||
| padding-right: 28px; | padding-right: 28px; | ||||
| color:#575757; | |||||
| font-size:14px; | |||||
| word-break: break-all; | |||||
| overflow: hidden; | overflow: hidden; | ||||
| display: -webkit-box; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| word-break: break-all; | |||||
| -webkit-line-clamp: 2; | -webkit-line-clamp: 2; | ||||
| -webkit-box-orient: vertical; | -webkit-box-orient: vertical; | ||||
| } | } | ||||
| .itemTime { | .itemTime { | ||||
| position: absolute; | position: absolute; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| bottom: 22px; | bottom: 22px; | ||||
| left: 20px; | left: 20px; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| .itemIcon { | .itemIcon { | ||||
| position: absolute; | position: absolute; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| right: 20px; | right: 20px; | ||||
| bottom: 22px; | bottom: 22px; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| } | } | ||||
| .dataItem:hover{ | |||||
| border-color: #1664FF; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.2) | |||||
| .dataItem:hover { | |||||
| border-color: #1664ff; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| } | } | ||||
| .dataItem:hover .itemText{ | |||||
| color: #1664FF; | |||||
| .dataItem:hover .itemText { | |||||
| color: #1664ff; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -301,9 +292,9 @@ | |||||
| width: 825px; | width: 825px; | ||||
| padding: 20px 67px; | padding: 20px 67px; | ||||
| background-image: url(/assets/images/modal-back.png); | background-image: url(/assets/images/modal-back.png); | ||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | background-position: top center; | ||||
| background-size: 100%; | |||||
| border-radius: 21px; | border-radius: 21px; | ||||
| } | } | ||||
| .ant-modal-header { | .ant-modal-header { | ||||
| @@ -1,9 +1,10 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import clock from '@/assets/img/clock.png'; | import clock from '@/assets/img/clock.png'; | ||||
| import creatByImg from '@/assets/img/creatBy.png'; | import creatByImg from '@/assets/img/creatBy.png'; | ||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { addDatesetAndVesion, getAssetIcon, getDatasetList } from '@/services/dataset/index.js'; | import { addDatesetAndVesion, getAssetIcon, getDatasetList } from '@/services/dataset/index.js'; | ||||
| import { getDictSelectOption } from '@/services/system/dict'; | import { getDictSelectOption } from '@/services/system/dict'; | ||||
| import { PlusCircleOutlined, UploadOutlined } from '@ant-design/icons'; | |||||
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd'; | import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd'; | ||||
| import moment from 'moment'; | import moment from 'moment'; | ||||
| import React, { useEffect, useState } from 'react'; | import React, { useEffect, useState } from 'react'; | ||||
| @@ -261,10 +262,10 @@ const PublicData = (React.FC = () => { | |||||
| }} | }} | ||||
| /> | /> | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| className={Styles.plusButton} | |||||
| type="default" | |||||
| style={{ marginLeft: '20px' }} | |||||
| onClick={showModal} | onClick={showModal} | ||||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | > | ||||
| 新建数据集 | 新建数据集 | ||||
| </Button> | </Button> | ||||
| @@ -424,7 +425,7 @@ const PublicData = (React.FC = () => { | |||||
| </Radio.Group> | </Radio.Group> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="数据文件" name="dataset_version_vos"> | <Form.Item label="数据文件" name="dataset_version_vos"> | ||||
| <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz"> | |||||
| <Button | <Button | ||||
| style={{ | style={{ | ||||
| fontSize: '14px', | fontSize: '14px', | ||||
| @@ -3,13 +3,13 @@ import creatByImg from '@/assets/img/creatBy.png'; | |||||
| import { getAssetIcon, getDatasetList } from '@/services/dataset/index.js'; | import { getAssetIcon, getDatasetList } from '@/services/dataset/index.js'; | ||||
| import { Form, Input, Pagination } from 'antd'; | import { Form, Input, Pagination } from 'antd'; | ||||
| import moment from 'moment'; | import moment from 'moment'; | ||||
| import React, { useEffect, useState } from 'react'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| import Styles from './index.less'; | import Styles from './index.less'; | ||||
| const { Search } = Input; | const { Search } = Input; | ||||
| const leftdataList = [1, 2, 3]; | const leftdataList = [1, 2, 3]; | ||||
| const PublicData = (React.FC = () => { | |||||
| const PublicData = () => { | |||||
| const [queryFlow, setQueryFlow] = useState({ | const [queryFlow, setQueryFlow] = useState({ | ||||
| page: 0, | page: 0, | ||||
| size: 10, | size: 10, | ||||
| @@ -256,5 +256,5 @@ const PublicData = (React.FC = () => { | |||||
| </div> | </div> | ||||
| </> | </> | ||||
| ); | ); | ||||
| }); | |||||
| }; | |||||
| export default PublicData; | export default PublicData; | ||||
| @@ -1,4 +1,3 @@ | |||||
| import { ReactComponent as ViewParam } from '@/assets/svg/view-param.svg'; | |||||
| import { useVisible } from '@/hooks'; | import { useVisible } from '@/hooks'; | ||||
| import { getExperimentIns } from '@/services/experiment/index.js'; | import { getExperimentIns } from '@/services/experiment/index.js'; | ||||
| import { getWorkflowById } from '@/services/pipeline/index.js'; | import { getWorkflowById } from '@/services/pipeline/index.js'; | ||||
| @@ -425,11 +424,7 @@ function ExperimentText() { | |||||
| {experimentStatusInfo[message.status]?.label} | {experimentStatusInfo[message.status]?.label} | ||||
| </span> | </span> | ||||
| </div> | </div> | ||||
| <Button | |||||
| icon={<ViewParam style={{ verticalAlign: '-1px' }}></ViewParam>} | |||||
| className={styles.param_button} | |||||
| onClick={openParamsModal} | |||||
| > | |||||
| <Button className={styles.param_button} onClick={openParamsModal}> | |||||
| 执行参数 | 执行参数 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| @@ -1,3 +1,4 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { | import { | ||||
| deleteExperimentById, | deleteExperimentById, | ||||
| deleteQueryByExperimentInsId, | deleteQueryByExperimentInsId, | ||||
| @@ -12,16 +13,12 @@ import { | |||||
| runTensorBoardReq, | runTensorBoardReq, | ||||
| } from '@/services/experiment/index.js'; | } from '@/services/experiment/index.js'; | ||||
| import { getWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflow } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | |||||
| import { elapsedTime } from '@/utils/date'; | import { elapsedTime } from '@/utils/date'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { | |||||
| DeleteOutlined, | |||||
| EditOutlined, | |||||
| FieldTimeOutlined, | |||||
| PlayCircleOutlined, | |||||
| PlusCircleOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { Button, Modal, Space, Table, message } from 'antd'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { Button, ConfigProvider, Space, Table, message } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import momnet from 'moment'; | import momnet from 'moment'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| @@ -227,6 +224,7 @@ function Experiment() { | |||||
| }; | }; | ||||
| const pageOption = useRef({ page: 1, size: 10 }); | const pageOption = useRef({ page: 1, size: 10 }); | ||||
| const paginationProps = { | const paginationProps = { | ||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | showQuickJumper: true, | ||||
| showTotal: () => `共${total}条`, | showTotal: () => `共${total}条`, | ||||
| total: total, | total: total, | ||||
| @@ -277,12 +275,14 @@ function Experiment() { | |||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| render: (text) => <div>{text}</div>, | render: (text) => <div>{text}</div>, | ||||
| width: '20%', | |||||
| }, | }, | ||||
| { | { | ||||
| title: '关联流水线名称', | title: '关联流水线名称', | ||||
| dataIndex: 'workflow_name', | dataIndex: 'workflow_name', | ||||
| key: 'workflow_name', | key: 'workflow_name', | ||||
| render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>, | ||||
| width: '20%', | |||||
| }, | }, | ||||
| { | { | ||||
| title: '实验描述', | title: '实验描述', | ||||
| @@ -293,6 +293,7 @@ function Experiment() { | |||||
| title: '最近五次运行状态', | title: '最近五次运行状态', | ||||
| dataIndex: 'status_list', | dataIndex: 'status_list', | ||||
| key: 'status_list', | key: 'status_list', | ||||
| width: 200, | |||||
| render: (text) => { | render: (text) => { | ||||
| let newText = text && text.replace(/\s+/g, '').split(','); | let newText = text && text.replace(/\s+/g, '').split(','); | ||||
| return ( | return ( | ||||
| @@ -323,7 +324,7 @@ function Experiment() { | |||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="run" | key="run" | ||||
| icon={<PlayCircleOutlined />} | |||||
| icon={<KFIcon type="icon-yunhang" />} | |||||
| onClick={() => { | onClick={() => { | ||||
| runExperiment(record.id); | runExperiment(record.id); | ||||
| }} | }} | ||||
| @@ -334,213 +335,197 @@ function Experiment() { | |||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="edit" | key="edit" | ||||
| icon={<EditOutlined />} | |||||
| icon={<KFIcon type="icon-bianji" />} | |||||
| onClick={() => { | onClick={() => { | ||||
| editExperiment(record.id); | editExperiment(record.id); | ||||
| }} | }} | ||||
| > | > | ||||
| 编辑 | 编辑 | ||||
| </Button> | </Button> | ||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| danger | |||||
| key="batchRemove" | |||||
| style={{ color: '#f98e1b' }} | |||||
| icon={<DeleteOutlined />} | |||||
| onClick={async () => { | |||||
| Modal.confirm({ | |||||
| title: ( | |||||
| <div> | |||||
| <img | |||||
| src="/assets/images/delete-icon.png" | |||||
| style={{ width: '120px', marginBottom: '24px' }} | |||||
| alt="" | |||||
| /> | |||||
| <div style={{ color: '#1d1d20', fontSize: '16px' }}> | |||||
| 删除后,该实验将不可恢复 | |||||
| </div> | |||||
| </div> | |||||
| ), | |||||
| content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>, | |||||
| closable: true, | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| console.log(record); | |||||
| deleteExperimentById(record.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getList(); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| // if (success) { | |||||
| // if (actionRef.current) { | |||||
| // actionRef.current.reload(); | |||||
| // } | |||||
| // } | |||||
| }, | |||||
| }); | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | |||||
| }} | }} | ||||
| > | > | ||||
| 删除 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="batchRemove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该实验将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteExperimentById(record.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getList(); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| </Space> | </Space> | ||||
| ), | ), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| return ( | return ( | ||||
| <div> | |||||
| {/* <div > | |||||
| <Button type="primary" onClick={createExperiment} icon = {< PlusOutlined />}> | |||||
| 新建实验 | |||||
| </Button> | |||||
| </div> */} | |||||
| <div className={Styles.experimentBox}> | |||||
| <div className={Styles.pipelineTopBox}> | <div className={Styles.pipelineTopBox}> | ||||
| <Button | |||||
| type="primary" | |||||
| className={Styles.plusButton} | |||||
| onClick={createExperiment} | |||||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||||
| > | |||||
| <Button type="default" onClick={createExperiment} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 新建实验 | 新建实验 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| <Table | |||||
| columns={columns} | |||||
| dataSource={experimentList} | |||||
| pagination={paginationProps} | |||||
| rowKey="id" | |||||
| expandable={{ | |||||
| expandedRowRender: (record) => ( | |||||
| <div> | |||||
| {experimentInList && experimentInList.length > 0 ? ( | |||||
| <div className={Styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | |||||
| <div style={{ width: '150px' }}>序号</div> | |||||
| <div style={{ width: '300px' }}>TensorBoard</div> | |||||
| <div style={{ width: '300px' }}>运行时长</div> | |||||
| <div style={{ width: '300px' }}>开始时间</div> | |||||
| <div style={{ width: '200px' }}>状态</div> | |||||
| <div style={{ width: '200px' }}>操作</div> | |||||
| </div> | |||||
| ) : ( | |||||
| '' | |||||
| )} | |||||
| <div className={classNames('vertical-scroll-table', Styles.experimentTable)}> | |||||
| <Table | |||||
| columns={columns} | |||||
| dataSource={experimentList} | |||||
| pagination={paginationProps} | |||||
| rowKey="id" | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| expandable={{ | |||||
| expandedRowRender: (record) => ( | |||||
| <div> | |||||
| {experimentInList && experimentInList.length > 0 ? ( | |||||
| <div className={Styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | |||||
| <div className={Styles.index}>序号</div> | |||||
| <div className={Styles.tensorBoard}>可视化</div> | |||||
| <div className={Styles.description}> | |||||
| <div style={{ width: '50%' }}>运行时长</div> | |||||
| <div style={{ width: '50%' }}>开始时间</div> | |||||
| </div> | |||||
| <div className={Styles.status}>状态</div> | |||||
| <div className={Styles.operation}>操作</div> | |||||
| </div> | |||||
| ) : ( | |||||
| '' | |||||
| )} | |||||
| {experimentInList && experimentInList.length > 0 | |||||
| ? experimentInList.map((item, index) => ( | |||||
| <div | |||||
| key={item.id} | |||||
| className={Styles.tableExpandBox} | |||||
| style={{ | |||||
| border: '1px solid #eaeaea', | |||||
| backgroundColor: '#fff', | |||||
| height: '45px', | |||||
| }} | |||||
| > | |||||
| <a style={{ width: '150px' }} onClick={(e) => routerToText(e, item, record)}> | |||||
| {index + 1} | |||||
| </a> | |||||
| <div style={{ width: '300px' }}> | |||||
| {item.nodes_result?.tensorboard_log ? ( | |||||
| <TensorBoardStatus | |||||
| status={item.tensorBoardStatus} | |||||
| onClick={() => handleTensorboard(item)} | |||||
| ></TensorBoardStatus> | |||||
| ) : ( | |||||
| '-' | |||||
| )} | |||||
| </div> | |||||
| <div style={{ width: '300px' }}> | |||||
| {item.finish_time | |||||
| ? elapsedTime(new Date(item.create_time), new Date(item.finish_time)) | |||||
| : elapsedTime(new Date(item.create_time), new Date())} | |||||
| </div> | |||||
| <div style={{ width: '300px' }}> | |||||
| {momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||||
| </div> | |||||
| <div className={Styles.statusBox} style={{ width: '200px' }}> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '7px' }} | |||||
| src={experimentStatusInfo[item.status]?.icon} | |||||
| />{' '} | |||||
| <span | |||||
| style={{ color: experimentStatusInfo[item.status]?.color }} | |||||
| className={Styles.statusIcon} | |||||
| {experimentInList && experimentInList.length > 0 | |||||
| ? experimentInList.map((item, index) => ( | |||||
| <div | |||||
| key={item.id} | |||||
| className={classNames(Styles.tableExpandBox, Styles.tableExpandBoxContent)} | |||||
| > | |||||
| <a | |||||
| className={Styles.index} | |||||
| style={{ padding: '0 16px' }} | |||||
| onClick={(e) => routerToText(e, item, record)} | |||||
| > | > | ||||
| {experimentStatusInfo[item.status]?.label} | |||||
| </span> | |||||
| </div> | |||||
| <div style={{ width: '200px' }}> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="stop" | |||||
| disabled={ | |||||
| item.status === 'Succeeded' || | |||||
| item.status === 'Failed' || | |||||
| item.status === 'Terminated' | |||||
| } | |||||
| icon={<FieldTimeOutlined />} | |||||
| onClick={async () => { | |||||
| putQueryByExperimentInsId(item.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('终止成功'); | |||||
| getQueryByExperiment(record.id); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }} | |||||
| > | |||||
| 终止 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| danger | |||||
| key="batchRemove" | |||||
| style={{ color: '#f98e1b' }} | |||||
| disabled={item.status === 'Running' || item.status === 'Pending'} | |||||
| icon={<DeleteOutlined />} | |||||
| onClick={async () => { | |||||
| Modal.confirm({ | |||||
| title: '删除', | |||||
| content: '确定删除该条实例吗?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| deleteQueryByExperimentInsId(item.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getQueryByExperiment(record.id); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| {index + 1} | |||||
| </a> | |||||
| <div className={Styles.tensorBoard}> | |||||
| {item.nodes_result?.tensorboard_log ? ( | |||||
| <TensorBoardStatus | |||||
| status={item.tensorBoardStatus} | |||||
| onClick={() => handleTensorboard(item)} | |||||
| ></TensorBoardStatus> | |||||
| ) : ( | |||||
| '-' | |||||
| )} | |||||
| </div> | |||||
| <div className={Styles.description}> | |||||
| <div style={{ width: '50%' }}> | |||||
| {item.finish_time | |||||
| ? elapsedTime(new Date(item.create_time), new Date(item.finish_time)) | |||||
| : elapsedTime(new Date(item.create_time), new Date())} | |||||
| </div> | |||||
| <div style={{ width: '50%' }}> | |||||
| {momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')} | |||||
| </div> | |||||
| </div> | |||||
| <div className={Styles.statusBox}> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '7px' }} | |||||
| src={experimentStatusInfo[item.status]?.icon} | |||||
| />{' '} | |||||
| <span | |||||
| style={{ color: experimentStatusInfo[item.status]?.color }} | |||||
| className={Styles.statusIcon} | |||||
| > | |||||
| {experimentStatusInfo[item.status]?.label} | |||||
| </span> | |||||
| </div> | |||||
| <div className={Styles.operation}> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="stop" | |||||
| disabled={ | |||||
| item.status === 'Succeeded' || | |||||
| item.status === 'Failed' || | |||||
| item.status === 'Terminated' | |||||
| } | |||||
| icon={<KFIcon type="icon-zhongzhi" />} | |||||
| onClick={async () => { | |||||
| putQueryByExperimentInsId(item.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('终止成功'); | |||||
| getQueryByExperiment(record.id); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }} | |||||
| > | |||||
| 终止 | |||||
| </Button> | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | }, | ||||
| }); | |||||
| }} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| }} | |||||
| > | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="batchRemove" | |||||
| disabled={item.status === 'Running' || item.status === 'Pending'} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => { | |||||
| modalConfirm({ | |||||
| title: '确定删除该条实例吗?', | |||||
| onOk: () => { | |||||
| deleteQueryByExperimentInsId(item.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getQueryByExperiment(record.id); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | |||||
| )) | |||||
| : ''} | |||||
| </div> | |||||
| ), | |||||
| onExpand: (e, a) => { | |||||
| expandChange(e, a); | |||||
| }, | |||||
| expandedRowKeys: [expandedRowKeys], | |||||
| rowExpandable: (record) => true, | |||||
| }} | |||||
| /> | |||||
| )) | |||||
| : ''} | |||||
| </div> | |||||
| ), | |||||
| onExpand: (e, a) => { | |||||
| expandChange(e, a); | |||||
| }, | |||||
| expandedRowKeys: [expandedRowKeys], | |||||
| rowExpandable: (record) => true, | |||||
| }} | |||||
| /> | |||||
| </div> | |||||
| {isModalOpen && ( | {isModalOpen && ( | ||||
| <AddExperimentModal | <AddExperimentModal | ||||
| isAdd={isAdd} | isAdd={isAdd} | ||||
| @@ -19,35 +19,54 @@ | |||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| } | } | ||||
| .plusButton { | |||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| font-family: 'Alibaba'; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border: 1px solid; | |||||
| border-color: rgba(22, 100, 255, 0.11); | |||||
| border-radius: 4px; | |||||
| } | |||||
| .plusButton:hover { | |||||
| color: #1d1d20 !important; | |||||
| background: rgba(22, 100, 255, 0.06) !important; | |||||
| border: 1px solid !important; | |||||
| border-color: rgba(22, 100, 255, 0.11) !important; | |||||
| } | |||||
| .tableExpandBox { | .tableExpandBox { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: space-between; | |||||
| width: 100%; | width: 100%; | ||||
| padding: 0 65px 0 40px; | |||||
| padding: 0 0 0 33px; | |||||
| color: #1d1d20; | color: #1d1d20; | ||||
| font-size: 15px; | font-size: 15px; | ||||
| & > div { | |||||
| padding: 0 16px; | |||||
| } | |||||
| .index { | |||||
| width: calc((100% + 32px + 33px) / 5); | |||||
| } | |||||
| .tensorBoard { | |||||
| width: calc((100% + 32px + 33px) / 5); | |||||
| } | |||||
| .description { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| align-items: center; | |||||
| } | |||||
| .status { | |||||
| width: 200px; | |||||
| } | |||||
| .operation { | |||||
| width: 284px; | |||||
| } | |||||
| } | |||||
| .tableExpandBoxContent { | |||||
| height: 45px; | |||||
| background-color: #fff; | |||||
| border: 1px solid #eaeaea; | |||||
| & + & { | |||||
| border-top: none; | |||||
| } | |||||
| } | } | ||||
| .statusBox { | .statusBox { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| width: 200px; | |||||
| .statusIcon { | .statusIcon { | ||||
| visibility: hidden; | visibility: hidden; | ||||
| @@ -57,3 +76,16 @@ | |||||
| .statusBox:hover .statusIcon { | .statusBox:hover .statusIcon { | ||||
| visibility: visible; | visibility: visible; | ||||
| } | } | ||||
| .experimentBox{ | |||||
| height: calc(100% - 20px); | |||||
| .experimentTable{ | |||||
| height: calc(100% - 60px); | |||||
| :global{ | |||||
| .ant-table-wrapper .ant-table{ | |||||
| // overflow-y: auto; | |||||
| height: calc(100% - 48px); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -15,7 +15,10 @@ export enum ExperimentStatus { | |||||
| Omitted = 'Omitted', | Omitted = 'Omitted', | ||||
| } | } | ||||
| export const experimentStatusInfo: Record<string, StatusInfo | undefined> = { | |||||
| type ExperimentStatusKeys = keyof typeof ExperimentStatus; | |||||
| type ExperimentStatusValues = (typeof ExperimentStatus)[ExperimentStatusKeys]; | |||||
| export const experimentStatusInfo: Record<ExperimentStatusValues, StatusInfo | undefined> = { | |||||
| Running: { | Running: { | ||||
| label: '运行中', | label: '运行中', | ||||
| color: '#165bff', | color: '#165bff', | ||||
| @@ -0,0 +1,13 @@ | |||||
| //@import '@/styles/theme.less'; | |||||
| .mirror-status-cell { | |||||
| color: @text-color; | |||||
| &--success { | |||||
| color: @success-color; | |||||
| } | |||||
| &--error { | |||||
| color: @error-color; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,39 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-18 18:35:41 | |||||
| * @Description: | |||||
| */ | |||||
| import { MirrorVersionStatus } from '@/enums'; | |||||
| import styles from './index.less'; | |||||
| type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; | |||||
| type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; | |||||
| export type MirrorVersionStatusInfo = { | |||||
| text: string; | |||||
| classname: string; | |||||
| }; | |||||
| const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = { | |||||
| [MirrorVersionStatus.Building]: { | |||||
| text: '构建中', | |||||
| classname: styles['mirror-status-cell'], | |||||
| }, | |||||
| [MirrorVersionStatus.Available]: { | |||||
| classname: styles['mirror-status-cell--success'], | |||||
| text: '可用', | |||||
| }, | |||||
| [MirrorVersionStatus.Failed]: { | |||||
| classname: styles['mirror-status-cell--error'], | |||||
| text: '构建失败', | |||||
| }, | |||||
| }; | |||||
| function MirrorStatusCell(status: MirrorVersionStatus) { | |||||
| if (status === null || status === undefined || !statusInfo[status]) { | |||||
| return <span>--</span>; | |||||
| } | |||||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | |||||
| } | |||||
| export default MirrorStatusCell; | |||||
| @@ -4,7 +4,7 @@ | |||||
| height: 100%; | height: 100%; | ||||
| &__content { | &__content { | ||||
| height: calc(100% - 59px); | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 30px 30px 10px; | padding: 30px 30px 10px; | ||||
| overflow: auto; | overflow: auto; | ||||
| @@ -1,16 +1,22 @@ | |||||
| /* | /* | ||||
| * @Author: 赵伟 | * @Author: 赵伟 | ||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 镜像详情 | |||||
| * @Description: 创建镜像 | |||||
| */ | */ | ||||
| import { getAccessToken } from '@/access'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { createPrivateMirrorReq, createPublicMirrorReq } from '@/services/mirror'; | |||||
| import { createMirrorReq } from '@/services/mirror'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useNavigate, useSearchParams } from '@umijs/max'; | |||||
| import { Button, Col, Form, Input, Row, message } from 'antd'; | |||||
| import { mirrorNameKey } from '@/utils/sessionKeys'; | |||||
| import { getFileListFromEvent } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd'; | |||||
| import { omit } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './create.less'; | import styles from './create.less'; | ||||
| type FormData = { | type FormData = { | ||||
| @@ -18,31 +24,83 @@ type FormData = { | |||||
| tag: string; | tag: string; | ||||
| description: string; | description: string; | ||||
| path?: string; | path?: string; | ||||
| type: string; | |||||
| upload_type: string; | |||||
| fileList?: UploadFile[]; | |||||
| }; | }; | ||||
| const mirrorRadioItems: KFRadioItem[] = [ | const mirrorRadioItems: KFRadioItem[] = [ | ||||
| { | { | ||||
| key: CommonTabKeys.Public, | key: CommonTabKeys.Public, | ||||
| title: '基于公网镜像', | title: '基于公网镜像', | ||||
| icon: <KFIcon type="icon-jiyugongwangjingxiang" />, | |||||
| }, | }, | ||||
| { | { | ||||
| key: CommonTabKeys.Private, | key: CommonTabKeys.Private, | ||||
| title: '本地上传', | title: '本地上传', | ||||
| icon: <KFIcon type="icon-bendishangchuan" />, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| function MirrorCreate() { | function MirrorCreate() { | ||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| const [seachParams] = useSearchParams(); | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const isPublic = seachParams.get('isPublic') === 'true'; | |||||
| const [nameDisabled, setNameDisabled] = useState(false); | |||||
| const uploadProps: UploadProps = { | |||||
| action: '/api/mmp/image/upload', | |||||
| headers: { | |||||
| Authorization: getAccessToken() || '', | |||||
| }, | |||||
| maxCount: 1, | |||||
| defaultFileList: [], | |||||
| }; | |||||
| useEffect(() => { | |||||
| const name = sessionStorage.getItem(mirrorNameKey); | |||||
| if (name) { | |||||
| form.setFieldValue('name', name); | |||||
| setNameDisabled(true); | |||||
| } | |||||
| return () => { | |||||
| sessionStorage.removeItem(mirrorNameKey); | |||||
| }; | |||||
| }, []); | |||||
| // 创建公网、本地镜像 | // 创建公网、本地镜像 | ||||
| const createPublicMirror = async (params: FormData) => { | |||||
| // createPrivateMirrorReq | |||||
| const req = isPublic ? createPublicMirrorReq : createPrivateMirrorReq; | |||||
| const [res] = await to(req(params)); | |||||
| const createPublicMirror = async (formData: FormData) => { | |||||
| const upload_type = formData['upload_type']; | |||||
| let params; | |||||
| if (upload_type === CommonTabKeys.Public) { | |||||
| params = { | |||||
| ...omit(formData, ['upload_type']), | |||||
| upload_type: 0, | |||||
| image_type: 0, | |||||
| }; | |||||
| } else { | |||||
| const fileList = formData['fileList'] ?? []; | |||||
| if (fileList.length === 0) { | |||||
| message.error('请上传文件'); | |||||
| return; | |||||
| } | |||||
| const file = fileList[0]; | |||||
| if (file.status === 'uploading') { | |||||
| message.error('请等待文件上传完成'); | |||||
| return; | |||||
| } else if (file.status === 'error') { | |||||
| message.error('文件上传失败,请重新上传文件'); | |||||
| return; | |||||
| } | |||||
| params = { | |||||
| ...omit(formData, ['fileList', 'upload_type']), | |||||
| path: file.response.data.url, | |||||
| file_size: file.response.data.fileSize, | |||||
| upload_type: 1, | |||||
| image_type: 0, | |||||
| }; | |||||
| } | |||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | if (res) { | ||||
| message.success('创建成功'); | message.success('创建成功'); | ||||
| navgite(-1); | navgite(-1); | ||||
| @@ -51,13 +109,26 @@ function MirrorCreate() { | |||||
| // 提交 | // 提交 | ||||
| const handleSubmit = (values: FormData) => { | const handleSubmit = (values: FormData) => { | ||||
| console.log(values); | |||||
| createPublicMirror(values); | createPublicMirror(values); | ||||
| }; | }; | ||||
| // 取消 | |||||
| const cancel = () => { | |||||
| navgite(-1); | |||||
| }; | |||||
| const beforeUpload: UploadProps['beforeUpload'] = () => { | |||||
| const fileList = form.getFieldValue('fileList'); | |||||
| if (fileList.length >= 1) { | |||||
| message.error('只允许上传一个文件'); | |||||
| return Upload.LIST_IGNORE; | |||||
| } | |||||
| return true; | |||||
| }; | |||||
| return ( | return ( | ||||
| <div className={styles['mirror-create']}> | <div className={styles['mirror-create']}> | ||||
| <PageTitle title="基本信息"></PageTitle> | |||||
| <PageTitle title="创建镜像"></PageTitle> | |||||
| <div className={styles['mirror-create__content']}> | <div className={styles['mirror-create__content']}> | ||||
| <div> | <div> | ||||
| <Form | <Form | ||||
| @@ -66,7 +137,7 @@ function MirrorCreate() { | |||||
| wrapperCol={{ flex: 1 }} | wrapperCol={{ flex: 1 }} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| form={form} | form={form} | ||||
| initialValues={{ type: CommonTabKeys.Public }} | |||||
| initialValues={{ upload_type: CommonTabKeys.Public }} | |||||
| onFinish={handleSubmit} | onFinish={handleSubmit} | ||||
| > | > | ||||
| <SubAreaTitle | <SubAreaTitle | ||||
| @@ -86,13 +157,19 @@ function MirrorCreate() { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear /> | |||||
| <Input | |||||
| placeholder="请输入镜像名称" | |||||
| maxLength={64} | |||||
| disabled={nameDisabled} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label=" " | label=" " | ||||
| name="tag" | |||||
| name="tag_name" | |||||
| labelCol={{ flex: '20px' }} | labelCol={{ flex: '20px' }} | ||||
| wrapperCol={{ flex: 1 }} | wrapperCol={{ flex: 1 }} | ||||
| required={false} | required={false} | ||||
| @@ -138,7 +215,7 @@ function MirrorCreate() { | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="构建方式" | label="构建方式" | ||||
| name="type" | |||||
| name="upload_type" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| @@ -152,10 +229,12 @@ function MirrorCreate() { | |||||
| </Row> | </Row> | ||||
| <Form.Item | <Form.Item | ||||
| noStyle | noStyle | ||||
| shouldUpdate={(prevValues, curValues) => prevValues.type !== curValues.type} | |||||
| shouldUpdate={(prevValues, curValues) => | |||||
| prevValues.upload_type !== curValues.upload_type | |||||
| } | |||||
| > | > | ||||
| {({ getFieldValue }) => { | {({ getFieldValue }) => { | ||||
| const type = getFieldValue('type'); | |||||
| const type = getFieldValue('upload_type'); | |||||
| if (type === CommonTabKeys.Public) { | if (type === CommonTabKeys.Public) { | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -196,7 +275,9 @@ function MirrorCreate() { | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像文件" | label="镜像文件" | ||||
| name="path" | |||||
| name="fileList" | |||||
| valuePropName="fileList" | |||||
| getValueFromEvent={getFileListFromEvent} | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| @@ -204,19 +285,11 @@ function MirrorCreate() { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| {/* <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Button | |||||
| style={{ | |||||
| fontSize: '14px', | |||||
| border: '1px solid', | |||||
| borderColor: '#1664ff', | |||||
| background: '#fff', | |||||
| }} | |||||
| icon={<UploadOutlined style={{ color: '#1664ff' }} />} | |||||
| > | |||||
| 上传文件 | |||||
| <Upload {...uploadProps} beforeUpload={beforeUpload}> | |||||
| <Button type="link" style={{ paddingLeft: 0, paddingRight: 0 }}> | |||||
| 选择镜像文件 | |||||
| </Button> | </Button> | ||||
| </Upload> */} | |||||
| </Upload> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -230,7 +303,12 @@ function MirrorCreate() { | |||||
| <Button type="primary" htmlType="submit"> | <Button type="primary" htmlType="submit"> | ||||
| 创建镜像 | 创建镜像 | ||||
| </Button> | </Button> | ||||
| <Button type="default" htmlType="reset" style={{ marginLeft: '20px' }}> | |||||
| <Button | |||||
| type="default" | |||||
| htmlType="button" | |||||
| onClick={cancel} | |||||
| style={{ marginLeft: '20px' }} | |||||
| > | |||||
| 取消 | 取消 | ||||
| </Button> | </Button> | ||||
| </Form.Item> | </Form.Item> | ||||
| @@ -12,7 +12,7 @@ | |||||
| .label { | .label { | ||||
| width: 80px; | width: 80px; | ||||
| color: @text-color-second; | |||||
| color: @text-color-secondary; | |||||
| } | } | ||||
| .value { | .value { | ||||
| @@ -23,7 +23,7 @@ | |||||
| } | } | ||||
| &__content { | &__content { | ||||
| height: calc(100% - 59px); | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 30px 30px 0; | padding: 30px 30px 0; | ||||
| background-color: white; | background-color: white; | ||||
| @@ -3,16 +3,37 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 镜像详情 | * @Description: 镜像详情 | ||||
| */ | */ | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { useDomSize } from '@/hooks'; | import { useDomSize } from '@/hooks'; | ||||
| import { getMirrorInfoReq, getMirrorVersionListReq } from '@/services/mirror'; | |||||
| import { | |||||
| deleteMirrorVersionReq, | |||||
| getMirrorInfoReq, | |||||
| getMirrorVersionListReq, | |||||
| } from '@/services/mirror'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { Button, Col, Row, Table, TablePaginationConfig, TableProps } from 'antd'; | |||||
| import { mirrorNameKey } from '@/utils/sessionKeys'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate, useParams, useSearchParams } from '@umijs/max'; | |||||
| import { | |||||
| Button, | |||||
| Col, | |||||
| ConfigProvider, | |||||
| Flex, | |||||
| Row, | |||||
| Table, | |||||
| message, | |||||
| type TablePaginationConfig, | |||||
| type TableProps, | |||||
| } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import MirrorStatusCell from './components/MirrorStatusCell'; | |||||
| import styles from './info.less'; | import styles from './info.less'; | ||||
| type MirrorInfoData = { | type MirrorInfoData = { | ||||
| @@ -23,6 +44,7 @@ type MirrorInfoData = { | |||||
| }; | }; | ||||
| type MirrorVersionData = { | type MirrorVersionData = { | ||||
| id: number; | |||||
| version: string; | version: string; | ||||
| url: string; | url: string; | ||||
| status: string; | status: string; | ||||
| @@ -31,23 +53,26 @@ type MirrorVersionData = { | |||||
| }; | }; | ||||
| function MirrorInfo() { | function MirrorInfo() { | ||||
| const navigate = useNavigate(); | |||||
| const urlParams = useParams(); | const urlParams = useParams(); | ||||
| const [seachParams] = useSearchParams(); | |||||
| const [searchParams] = useSearchParams(); | |||||
| const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); | const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); | ||||
| const [tableData, setTableData] = useState<MirrorVersionData[]>([]); | const [tableData, setTableData] = useState<MirrorVersionData[]>([]); | ||||
| const [topRef, { height: topHeight }] = useDomSize<HTMLDivElement>(0, 0, [mirrorInfo]); | const [topRef, { height: topHeight }] = useDomSize<HTMLDivElement>(0, 0, [mirrorInfo]); | ||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | const [pagination, setPagination] = useState<TablePaginationConfig>({ | ||||
| showSizeChanger: true, | showSizeChanger: true, | ||||
| showQuickJumper: true, | showQuickJumper: true, | ||||
| current: 1, | current: 1, | ||||
| pageSize: 10, | pageSize: 10, | ||||
| total: 0, | |||||
| }); | }); | ||||
| const isPublic = seachParams.get('isPublic') === 'true'; | |||||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getMirrorInfo(); | getMirrorInfo(); | ||||
| getMirrorVersionList(); | |||||
| }, []); | }, []); | ||||
| useEffect(() => { | |||||
| getMirrorVersionList(); | |||||
| }, [pagination]); | |||||
| // 获取镜像详情 | // 获取镜像详情 | ||||
| const getMirrorInfo = async () => { | const getMirrorInfo = async () => { | ||||
| @@ -55,7 +80,8 @@ function MirrorInfo() { | |||||
| const [res] = await to(getMirrorInfoReq(id)); | const [res] = await to(getMirrorInfoReq(id)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { name = '', description = '', version_count = '', create_time: time } = res.data; | const { name = '', description = '', version_count = '', create_time: time } = res.data; | ||||
| const create_time = time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : ''; | |||||
| let create_time = | |||||
| time && dayjs(time).isValid() ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '--'; | |||||
| setMirrorInfo({ | setMirrorInfo({ | ||||
| name, | name, | ||||
| description, | description, | ||||
| @@ -77,10 +103,26 @@ function MirrorInfo() { | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { content = [], totalElements = 0 } = res.data; | const { content = [], totalElements = 0 } = res.data; | ||||
| setTableData(content); | setTableData(content); | ||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| total: totalElements, | |||||
| })); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| // 删除镜像版本 | |||||
| const deleteMirrorVersion = async (id: number) => { | |||||
| const [res] = await to(deleteMirrorVersionReq(id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getMirrorVersionList(); | |||||
| } | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -88,75 +130,86 @@ function MirrorInfo() { | |||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | ||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| getMirrorVersionList(); | |||||
| } | } | ||||
| console.log(pagination, filters, sorter, action); | |||||
| }; | }; | ||||
| const downloadVersion = (record: MirrorVersionData) => {}; | |||||
| const removeVersion = (record: MirrorVersionData) => {}; | |||||
| // 处理删除 | |||||
| const handleVersionDelete = (record: MirrorVersionData) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该镜像版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteMirrorVersion(record.id); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const createMirrorVersion = () => { | |||||
| navigate(`/dataset/mirror/create`); | |||||
| sessionStorage.setItem(mirrorNameKey, mirrorInfo.name || ''); | |||||
| }; | |||||
| const columns: TableProps<MirrorVersionData>['columns'] = [ | const columns: TableProps<MirrorVersionData>['columns'] = [ | ||||
| { | { | ||||
| title: '镜像版本', | title: '镜像版本', | ||||
| dataIndex: 'version', | |||||
| key: 'version', | |||||
| width: '20%', | |||||
| dataIndex: 'tag_name', | |||||
| key: 'tag_name', | |||||
| width: '25%', | |||||
| render: CommonTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像地址', | title: '镜像地址', | ||||
| dataIndex: 'url', | dataIndex: 'url', | ||||
| key: 'url', | key: 'url', | ||||
| width: '26%', | |||||
| render: CommonTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| dataIndex: 'status', | dataIndex: 'status', | ||||
| key: 'status', | key: 'status', | ||||
| width: '7%', | |||||
| width: 150, | |||||
| render: MirrorStatusCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像大小', | title: '镜像大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| key: 'file_size', | key: 'file_size', | ||||
| width: '7%', | |||||
| width: 150, | |||||
| render: CommonTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| width: '20%', | |||||
| render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| width: 200, | |||||
| render: DateTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'operation', | dataIndex: 'operation', | ||||
| width: '20%', | |||||
| width: 150, | |||||
| key: 'operation', | key: 'operation', | ||||
| render: (_: any, record: any) => ( | |||||
| hidden: isPublic, | |||||
| render: (_: any, record: MirrorVersionData) => ( | |||||
| <div> | <div> | ||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="info" | |||||
| // icon={<Icon icon="local:view-detail" style={{ verticalAlign: '-4px' }} />} | |||||
| //icon={<MyIcon type="icon-shiyanduibi" style={{ fontSize: '16px' }}></MyIcon>} | |||||
| onClick={() => downloadVersion(record)} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| {!isPublic && ( | {!isPublic && ( | ||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| // icon={<MyIcon type="icon-shiyanduibi" style={{ fontSize: '16px' }}></MyIcon>} | |||||
| // icon={<Icon icon="local:view-detail" style={{ verticalAlign: '-2px' }} />} | |||||
| onClick={() => removeVersion(record)} | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | |||||
| }} | |||||
| > | > | ||||
| 删除 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleVersionDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| )} | )} | ||||
| </div> | </div> | ||||
| ), | ), | ||||
| @@ -184,7 +237,7 @@ function MirrorInfo() { | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <div className={styles['mirror-info__basic__item']}> | <div className={styles['mirror-info__basic__item']}> | ||||
| <div className={styles['label']}>版本数:</div> | <div className={styles['label']}>版本数:</div> | ||||
| <div className={styles['value']}>{mirrorInfo.version_count}</div> | |||||
| <div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div> | |||||
| </div> | </div> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -203,12 +256,21 @@ function MirrorInfo() { | |||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| </div> | </div> | ||||
| <SubAreaTitle | |||||
| title="镜像版本" | |||||
| image={require('@/assets/img/mirror-version.png')} | |||||
| style={{ marginTop: '40px' }} | |||||
| ></SubAreaTitle> | |||||
| <Flex justify="space-between" align="center" style={{ marginTop: '40px' }}> | |||||
| <SubAreaTitle | |||||
| title="镜像版本" | |||||
| image={require('@/assets/img/mirror-version.png')} | |||||
| ></SubAreaTitle> | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| onClick={createMirrorVersion} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 新增镜像版本 | |||||
| </Button> | |||||
| )} | |||||
| </Flex> | |||||
| </div> | </div> | ||||
| <div | <div | ||||
| className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])} | className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])} | ||||
| @@ -218,7 +280,7 @@ function MirrorInfo() { | |||||
| dataSource={tableData} | dataSource={tableData} | ||||
| columns={columns} | columns={columns} | ||||
| scroll={{ y: 'calc(100% - 55px)' }} | scroll={{ y: 'calc(100% - 55px)' }} | ||||
| pagination={pagination} | |||||
| pagination={{ ...pagination, total }} | |||||
| onChange={handleTableChange} | onChange={handleTableChange} | ||||
| rowKey="id" | rowKey="id" | ||||
| /> | /> | ||||
| @@ -1,15 +1,13 @@ | |||||
| @import '@/styles/theme.less'; | |||||
| .mirror-list { | .mirror-list { | ||||
| height: 100%; | height: 100%; | ||||
| &__tabs-container { | &__tabs-container { | ||||
| height: 49px; | |||||
| height: 50px; | |||||
| padding-left: 27px; | padding-left: 27px; | ||||
| background-image: url('../../assets/img/page-title-bg.png'); | background-image: url('../../assets/img/page-title-bg.png'); | ||||
| } | } | ||||
| &__content { | &__content { | ||||
| height: calc(100% - 59px); | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 20px 30px 0; | padding: 20px 30px 0; | ||||
| background-color: white; | background-color: white; | ||||
| @@ -19,12 +17,11 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: space-between; | justify-content: space-between; | ||||
| padding: 0 10px; | |||||
| } | } | ||||
| &__table { | &__table { | ||||
| height: calc(100% - 44px); | |||||
| margin-top: 12px; | |||||
| height: calc(100% - 34px - 28px); | |||||
| margin-top: 28px; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,14 +3,28 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 镜像列表 | * @Description: 镜像列表 | ||||
| */ | */ | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { getMirrorListReq } from '@/services/mirror'; | |||||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Icon, useNavigate } from '@umijs/max'; | |||||
| import { Button, Input, Table, TablePaginationConfig, TableProps, Tabs } from 'antd'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate, useSearchParams } from '@umijs/max'; | |||||
| import { | |||||
| Button, | |||||
| ConfigProvider, | |||||
| Input, | |||||
| Table, | |||||
| Tabs, | |||||
| message, | |||||
| type TablePaginationConfig, | |||||
| type TableProps, | |||||
| type TabsProps, | |||||
| } from 'antd'; | |||||
| import { type SearchProps } from 'antd/es/input'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import dayjs from 'dayjs'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './list.less'; | import styles from './list.less'; | ||||
| @@ -18,12 +32,12 @@ const mirrorTabItems = [ | |||||
| { | { | ||||
| key: CommonTabKeys.Public, | key: CommonTabKeys.Public, | ||||
| label: '公共镜像', | label: '公共镜像', | ||||
| icon: <Icon icon="local:public-mirror-tab" className="umi-local-svg" />, | |||||
| icon: <KFIcon type="icon-gonggongjingxiang" />, | |||||
| }, | }, | ||||
| { | { | ||||
| key: CommonTabKeys.Private, | key: CommonTabKeys.Private, | ||||
| label: '个人镜像', | label: '个人镜像', | ||||
| icon: <Icon icon="local:private-mirror-tab" className="umi-local-svg" />, | |||||
| icon: <KFIcon type="icon-zidingyijingxiang" />, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -35,65 +49,118 @@ export type MirrorData = { | |||||
| }; | }; | ||||
| function MirrorList() { | function MirrorList() { | ||||
| const navgite = useNavigate(); | |||||
| const [activeTab, setActiveTab] = useState('Public'); | |||||
| const navigate = useNavigate(); | |||||
| const [searchParams, setSearchParams] = useSearchParams(); | |||||
| const isPrivate = searchParams.get('isPublic') === 'false'; | |||||
| const [activeTab, setActiveTab] = useState<string>( | |||||
| isPrivate ? CommonTabKeys.Private : CommonTabKeys.Public, | |||||
| ); | |||||
| const [searchText, setSearchText] = useState(''); | const [searchText, setSearchText] = useState(''); | ||||
| const [tableData, setTableData] = useState<MirrorData[]>([]); | const [tableData, setTableData] = useState<MirrorData[]>([]); | ||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | const [pagination, setPagination] = useState<TablePaginationConfig>({ | ||||
| showSizeChanger: true, | showSizeChanger: true, | ||||
| showQuickJumper: true, | showQuickJumper: true, | ||||
| current: 1, | current: 1, | ||||
| pageSize: 10, | pageSize: 10, | ||||
| total: 0, | |||||
| }); | }); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getMirrorList(); | getMirrorList(); | ||||
| }, [activeTab]); | |||||
| }, [activeTab, pagination]); | |||||
| // 切换 Tab,重置数据 | |||||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | |||||
| setSearchText(''); | |||||
| setPagination({ | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| setTotal(0); | |||||
| setTableData([]); | |||||
| setActiveTab(value); | |||||
| setSearchParams([['isPublic', value === CommonTabKeys.Public ? 'true' : 'false']], { | |||||
| replace: true, | |||||
| }); | |||||
| }; | |||||
| // 获取镜像列表 | // 获取镜像列表 | ||||
| const getMirrorList = async () => { | |||||
| const params = { | |||||
| const getMirrorList = async (params?: Record<string, any>) => { | |||||
| const reqParams = { | |||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| name: searchText, | name: searchText, | ||||
| image_type: activeTab === CommonTabKeys.Public ? 1 : 0, | image_type: activeTab === CommonTabKeys.Public ? 1 : 0, | ||||
| ...params, | |||||
| }; | }; | ||||
| const [res] = await to(getMirrorListReq(params)); | |||||
| const [res] = await to(getMirrorListReq(reqParams)); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { content = [], totalElements = 0 } = res.data; | const { content = [], totalElements = 0 } = res.data; | ||||
| setTableData(content); | setTableData(content); | ||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| total: totalElements, | |||||
| })); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| // 删除镜像 | |||||
| const deleteMirror = async (id: number) => { | |||||
| const [res] = await to(deleteMirrorReq(id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getMirrorList(); | |||||
| } | |||||
| } | } | ||||
| }; | }; | ||||
| // 搜索 | // 搜索 | ||||
| const onSearch = () => { | |||||
| getMirrorList(); | |||||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||||
| // 带参数是为了点清除时,searchText 更新不及时的问题 | |||||
| getMirrorList({ | |||||
| name: value, | |||||
| }); | |||||
| }; | }; | ||||
| // 查看详情 | // 查看详情 | ||||
| const toDetail = (record: MirrorData) => { | const toDetail = (record: MirrorData) => { | ||||
| console.log('record', record); | console.log('record', record); | ||||
| navgite({ | |||||
| pathname: `/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`, | |||||
| navigate(`/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`, { | |||||
| state: { | |||||
| isPublic: activeTab === CommonTabKeys.Public, | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 处理删除 | |||||
| const handleMirrorDelete = (record: MirrorData) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该镜像将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteMirror(record.id); | |||||
| }, | |||||
| }); | }); | ||||
| }; | }; | ||||
| // 创建镜像 | // 创建镜像 | ||||
| const createMirror = () => { | const createMirror = () => { | ||||
| navgite({ pathname: `/dataset/mirror/create?isPublic=${activeTab === CommonTabKeys.Public}` }); | |||||
| navigate(`/dataset/mirror/create`); | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | ||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| getMirrorList(); | |||||
| } | } | ||||
| console.log(pagination, filters, sorter, action); | |||||
| // console.log(pagination, filters, sorter, action); | |||||
| }; | }; | ||||
| const columns: TableProps<MirrorData>['columns'] = [ | const columns: TableProps<MirrorData>['columns'] = [ | ||||
| @@ -101,57 +168,64 @@ function MirrorList() { | |||||
| title: '镜像名称', | title: '镜像名称', | ||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| width: '10%', | |||||
| width: '30%', | |||||
| render: CommonTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '版本数据', | title: '版本数据', | ||||
| dataIndex: 'version_count', | dataIndex: 'version_count', | ||||
| key: 'version_count', | key: 'version_count', | ||||
| width: '10%', | |||||
| width: 100, | |||||
| render: CommonTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像描述', | title: '镜像描述', | ||||
| dataIndex: 'description', | dataIndex: 'description', | ||||
| key: 'description', | key: 'description', | ||||
| width: '50%', | |||||
| //ellipsis: true, | |||||
| render: CommonTableCell, | |||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| width: '20%', | |||||
| render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>, | |||||
| width: 200, | |||||
| render: DateTableCell, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'operation', | dataIndex: 'operation', | ||||
| width: '15%', | |||||
| width: activeTab === CommonTabKeys.Private ? 200 : 150, | |||||
| key: 'operation', | key: 'operation', | ||||
| render: (_: any, record: any) => ( | |||||
| render: (_: any, record: MirrorData) => ( | |||||
| <div> | <div> | ||||
| <Button | <Button | ||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="info" | key="info" | ||||
| // icon={<Icon icon="local:view-detail" style={{ verticalAlign: '-4px' }} />} | |||||
| icon={<KFIcon type="icon-chakanxiangqing" />} | icon={<KFIcon type="icon-chakanxiangqing" />} | ||||
| onClick={() => toDetail(record)} | onClick={() => toDetail(record)} | ||||
| > | > | ||||
| 查看详情 | 查看详情 | ||||
| </Button> | </Button> | ||||
| {activeTab === CommonTabKeys.Private && ( | {activeTab === CommonTabKeys.Private && ( | ||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shiyanduibi1" />} | |||||
| // icon={<Icon icon="local:view-detail" style={{ verticalAlign: '-2px' }} />} | |||||
| // onClick={(e) => downloadAlone(e, record)} | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | |||||
| }} | |||||
| > | > | ||||
| 删除 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleMirrorDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| )} | )} | ||||
| </div> | </div> | ||||
| ), | ), | ||||
| @@ -164,7 +238,7 @@ function MirrorList() { | |||||
| <Tabs | <Tabs | ||||
| activeKey={activeTab} | activeKey={activeTab} | ||||
| items={mirrorTabItems} | items={mirrorTabItems} | ||||
| onChange={setActiveTab} | |||||
| onChange={hanleTabChange} | |||||
| className={styles['model-tabs']} | className={styles['model-tabs']} | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| @@ -183,7 +257,7 @@ function MirrorList() { | |||||
| style={{ marginLeft: '20px' }} | style={{ marginLeft: '20px' }} | ||||
| type="default" | type="default" | ||||
| onClick={createMirror} | onClick={createMirror} | ||||
| icon={<Icon icon="local:refresh" style={{ verticalAlign: '-2px' }} />} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | > | ||||
| 制作镜像 | 制作镜像 | ||||
| </Button> | </Button> | ||||
| @@ -192,7 +266,7 @@ function MirrorList() { | |||||
| style={{ marginRight: 0, marginLeft: 'auto' }} | style={{ marginRight: 0, marginLeft: 'auto' }} | ||||
| type="default" | type="default" | ||||
| onClick={getMirrorList} | onClick={getMirrorList} | ||||
| icon={<Icon icon="local:refresh" style={{ verticalAlign: '-2px' }} />} | |||||
| icon={<KFIcon type="icon-shuaxin" />} | |||||
| > | > | ||||
| 刷新 | 刷新 | ||||
| </Button> | </Button> | ||||
| @@ -202,7 +276,7 @@ function MirrorList() { | |||||
| dataSource={tableData} | dataSource={tableData} | ||||
| columns={columns} | columns={columns} | ||||
| scroll={{ y: 'calc(100% - 55px)' }} | scroll={{ y: 'calc(100% - 55px)' }} | ||||
| pagination={pagination} | |||||
| pagination={{ ...pagination, total: total }} | |||||
| onChange={handleTableChange} | onChange={handleTableChange} | ||||
| rowKey="id" | rowKey="id" | ||||
| /> | /> | ||||
| @@ -87,20 +87,8 @@ | |||||
| } | } | ||||
| .plusButton { | .plusButton { | ||||
| margin: 0 18px 0 20px; | margin: 0 18px 0 20px; | ||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| font-family: 'Alibaba'; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border: 1px solid; | |||||
| border-color: rgba(22, 100, 255, 0.11); | |||||
| border-radius: 4px; | |||||
| } | |||||
| .plusButton:hover { | |||||
| color: #1d1d20 !important; | |||||
| background: rgba(22, 100, 255, 0.06) !important; | |||||
| border: 1px solid !important; | |||||
| border-color: rgba(22, 100, 255, 0.11) !important; | |||||
| } | } | ||||
| .datasetCneterBox { | .datasetCneterBox { | ||||
| display: flex; | display: flex; | ||||
| justify-content: space-between; | justify-content: space-between; | ||||
| @@ -202,6 +190,7 @@ | |||||
| flex: 1; | flex: 1; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| height: 100%; | height: 100%; | ||||
| overflow-y: auto; | |||||
| padding: 22px 30px 26px 30px; | padding: 22px 30px 26px 30px; | ||||
| background: #ffffff; | background: #ffffff; | ||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | ||||
| @@ -222,64 +211,67 @@ | |||||
| width: 100%; | width: 100%; | ||||
| .dataItem { | .dataItem { | ||||
| position: relative; | position: relative; | ||||
| width: 23%; | |||||
| width: 23.8%; | |||||
| height:164px; | height:164px; | ||||
| background:#ffffff; | |||||
| border:1px solid; | |||||
| border-color:#eaeaea; | |||||
| border-radius:4px; | |||||
| margin: 0 20px 25px 0; | margin: 0 20px 25px 0; | ||||
| background: #ffffff; | |||||
| border: 1px solid; | |||||
| border-color: #eaeaea; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | cursor: pointer; | ||||
| .itemText { | .itemText { | ||||
| position: absolute; | position: absolute; | ||||
| top: 20px; | top: 20px; | ||||
| left: 20px; | left: 20px; | ||||
| background: linear-gradient(to right ,rgba(22, 100, 255,0.6) 0,rgba(22, 100, 255,0) 100%); | |||||
| height: 6px; | height: 6px; | ||||
| color: #1d1d20; | |||||
| font-size: 16px; | |||||
| font-family: 'Alibaba'; | |||||
| line-height: 0px; | line-height: 0px; | ||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| rgba(22, 100, 255, 0.3) 0, | |||||
| rgba(22, 100, 255, 0) 100% | |||||
| ); | |||||
| } | } | ||||
| .itemDescripition{ | |||||
| .itemDescripition { | |||||
| position: absolute; | position: absolute; | ||||
| top: 57px; | top: 57px; | ||||
| left: 20px; | left: 20px; | ||||
| display: -webkit-box; | |||||
| padding-right: 28px; | padding-right: 28px; | ||||
| color:#575757; | |||||
| font-size:14px; | |||||
| word-break: break-all; | |||||
| overflow: hidden; | overflow: hidden; | ||||
| display: -webkit-box; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| word-break: break-all; | |||||
| -webkit-line-clamp: 2; | -webkit-line-clamp: 2; | ||||
| -webkit-box-orient: vertical; | -webkit-box-orient: vertical; | ||||
| } | } | ||||
| .itemTime { | .itemTime { | ||||
| position: absolute; | position: absolute; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| bottom: 22px; | bottom: 22px; | ||||
| left: 20px; | left: 20px; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| .itemIcon { | .itemIcon { | ||||
| position: absolute; | position: absolute; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| right: 20px; | right: 20px; | ||||
| bottom: 22px; | bottom: 22px; | ||||
| display: flex; | |||||
| align-items: center; | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| } | } | ||||
| .dataItem:hover{ | |||||
| border-color: #1664FF; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.2) | |||||
| .dataItem:hover { | |||||
| border-color: #1664ff; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| } | } | ||||
| .dataItem:hover .itemText{ | |||||
| color: #1664FF; | |||||
| .dataItem:hover .itemText { | |||||
| color: #1664ff; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -290,9 +282,9 @@ | |||||
| width: 825px; | width: 825px; | ||||
| padding: 20px 67px; | padding: 20px 67px; | ||||
| background-image: url(/assets/images/modal-back.png); | background-image: url(/assets/images/modal-back.png); | ||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | background-position: top center; | ||||
| background-size: 100%; | |||||
| border-radius: 21px; | border-radius: 21px; | ||||
| } | } | ||||
| .ant-modal-header { | .ant-modal-header { | ||||
| @@ -1,4 +1,5 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { | import { | ||||
| addModelsVersionDetail, | addModelsVersionDetail, | ||||
| deleteModelVersion, | deleteModelVersion, | ||||
| @@ -7,12 +8,7 @@ import { | |||||
| getModelVersionsById, | getModelVersionsById, | ||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { | |||||
| DeleteOutlined, | |||||
| DownloadOutlined, | |||||
| PlusCircleOutlined, | |||||
| UploadOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; | ||||
| import moment from 'moment'; | import moment from 'moment'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| @@ -201,7 +197,7 @@ const Dataset = () => { | |||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="download" | key="download" | ||||
| icon={<DownloadOutlined />} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| onClick={(e) => downloadAlone(e, record)} | onClick={(e) => downloadAlone(e, record)} | ||||
| > | > | ||||
| 下载 | 下载 | ||||
| @@ -225,9 +221,9 @@ const Dataset = () => { | |||||
| <div className={Styles.datasetIntroTopBox}> | <div className={Styles.datasetIntroTopBox}> | ||||
| <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> | ||||
| <div className={Styles.smallTagBox}> | <div className={Styles.smallTagBox}> | ||||
| <div className={Styles.tagItem}>模型 id:{datasetDetailObj.id}</div> | |||||
| <div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> | <div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> | ||||
| <div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> | <div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> | ||||
| {/* <div className={Styles.tagItem}>English</div> */} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className={Styles.datasetIntroCneterBox}> | <div className={Styles.datasetIntroCneterBox}> | ||||
| @@ -255,10 +251,10 @@ const Dataset = () => { | |||||
| options={versionList} | options={versionList} | ||||
| /> | /> | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| onClick={showModal} | onClick={showModal} | ||||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | > | ||||
| 创建新版本 | 创建新版本 | ||||
| </Button> | </Button> | ||||
| @@ -267,21 +263,21 @@ const Dataset = () => { | |||||
| style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} | ||||
| > | > | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| style={{ margin: '0 20px 0 0' }} | style={{ margin: '0 20px 0 0' }} | ||||
| onClick={deleteDataset} | onClick={deleteDataset} | ||||
| icon={<DeleteOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | > | ||||
| 删除 | 删除 | ||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| type="default" | |||||
| className={Styles.plusButton} | className={Styles.plusButton} | ||||
| disabled={!version} | disabled={!version} | ||||
| style={{ margin: '0 20px 0 0' }} | style={{ margin: '0 20px 0 0' }} | ||||
| onClick={handleExport} | onClick={handleExport} | ||||
| icon={<DownloadOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| > | > | ||||
| 下载 | 下载 | ||||
| </Button> | </Button> | ||||
| @@ -379,7 +375,7 @@ const Dataset = () => { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz"> | |||||
| <Button | <Button | ||||
| style={{ | style={{ | ||||
| fontSize: '14px', | fontSize: '14px', | ||||
| @@ -1,8 +1,9 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import clock from '@/assets/img/clock.png'; | import clock from '@/assets/img/clock.png'; | ||||
| import creatByImg from '@/assets/img/creatBy.png'; | import creatByImg from '@/assets/img/creatBy.png'; | ||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { addModel, getAssetIcon, getModelList } from '@/services/dataset/index.js'; | import { addModel, getAssetIcon, getModelList } from '@/services/dataset/index.js'; | ||||
| import { PlusCircleOutlined, UploadOutlined } from '@ant-design/icons'; | |||||
| import { UploadOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd'; | import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd'; | ||||
| import moment from 'moment'; | import moment from 'moment'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| @@ -264,10 +265,10 @@ const PublicData = () => { | |||||
| }} | }} | ||||
| /> | /> | ||||
| <Button | <Button | ||||
| type="primary" | |||||
| className={Styles.plusButton} | |||||
| type="default" | |||||
| style={{ marginLeft: '20px' }} | |||||
| onClick={showModal} | onClick={showModal} | ||||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | > | ||||
| 模型注册 | 模型注册 | ||||
| </Button> | </Button> | ||||
| @@ -413,7 +414,7 @@ const PublicData = () => { | |||||
| <Select allowClear placeholder="请选择模型标签" options={[]} /> | <Select allowClear placeholder="请选择模型标签" options={[]} /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="模型文件" name="models_version_vos"> | <Form.Item label="模型文件" name="models_version_vos"> | ||||
| <Upload {...props} data={{ uuid: uuid }}> | |||||
| <Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz"> | |||||
| <Button | <Button | ||||
| style={{ | style={{ | ||||
| fontSize: '14px', | fontSize: '14px', | ||||
| @@ -58,15 +58,11 @@ | |||||
| overflow-y: auto; | overflow-y: auto; | ||||
| &__file { | &__file { | ||||
| height: 24px; | |||||
| margin-bottom: 10px; | margin-bottom: 10px; | ||||
| padding-left: 10px; | |||||
| overflow: hidden; | |||||
| color: #575757; | |||||
| padding: 3px 10px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 13px; | font-size: 13px; | ||||
| line-height: 24px; | |||||
| white-space: nowrap; | |||||
| text-overflow: ellipsis; | |||||
| word-break: break-all; | |||||
| background: @background-color-gray; | background: @background-color-gray; | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| } | } | ||||
| @@ -5,9 +5,10 @@ | |||||
| */ | */ | ||||
| import datasetImg from '@/assets/img/modal-select-dataset.png'; | import datasetImg from '@/assets/img/modal-select-dataset.png'; | ||||
| import mirrorImg from '@/assets/img/modal-select-mirror.png'; | |||||
| import modelImg from '@/assets/img/modal-select-model.png'; | import modelImg from '@/assets/img/modal-select-model.png'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { CommonTabKeys, MirrorVersionStatus } from '@/enums'; | |||||
| import { | import { | ||||
| getDatasetList, | getDatasetList, | ||||
| getDatasetVersionIdList, | getDatasetVersionIdList, | ||||
| @@ -16,6 +17,7 @@ import { | |||||
| getModelVersionIdList, | getModelVersionIdList, | ||||
| getModelVersionsById, | getModelVersionsById, | ||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Icon } from '@umijs/max'; | import { Icon } from '@umijs/max'; | ||||
| import type { GetRef, ModalProps, TabsProps, TreeDataNode, TreeProps } from 'antd'; | import type { GetRef, ModalProps, TabsProps, TreeDataNode, TreeProps } from 'antd'; | ||||
| @@ -26,42 +28,51 @@ import styles from './index.less'; | |||||
| export enum ResourceSelectorType { | export enum ResourceSelectorType { | ||||
| Model = 'Model', // 模型 | Model = 'Model', // 模型 | ||||
| Dataset = 'Dataset', // 数据集 | Dataset = 'Dataset', // 数据集 | ||||
| Mirror = 'Mirror', //镜像 | |||||
| } | } | ||||
| type ResourceSelectorTypeKeys = keyof typeof ResourceSelectorType; | type ResourceSelectorTypeKeys = keyof typeof ResourceSelectorType; | ||||
| type ResourceSelectorTypeValues = (typeof ResourceSelectorType)[ResourceSelectorTypeKeys]; | 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 = { | export type SelectorTypeInfo = { | ||||
| getList: (params: { page: number; size: number; available_range: string }) => Promise<any>; | |||||
| getVersions: (params: number) => Promise<any>; | |||||
| getFiles: (params: GetFilesReqParam) => Promise<any>; | |||||
| getList: (params: any) => Promise<any>; | |||||
| getVersions: (params: any) => Promise<any>; | |||||
| getFiles: (params: any) => Promise<any>; | |||||
| handleVersionResponse: (res: any) => any[]; | |||||
| modalIcon: string; | modalIcon: string; | ||||
| buttonIcon: string; | buttonIcon: string; | ||||
| name: string; | name: string; | ||||
| litReqParamKey: 'available_range' | 'image_type'; | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; | fileReqParamKey: 'models_id' | 'dataset_id'; | ||||
| tabItems: TabsProps['items']; | tabItems: TabsProps['items']; | ||||
| }; | }; | ||||
| // 获取镜像列表,为了兼容之前的结构 | |||||
| const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => { | |||||
| const index = version.indexOf('-'); | |||||
| const url = version.slice(index + 1); | |||||
| return Promise.resolve({ | |||||
| data: { | |||||
| content: [ | |||||
| { | |||||
| id: `${id}-${version}`, | |||||
| file_name: `${url}`, | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeInfo> = { | export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeInfo> = { | ||||
| Model: { | |||||
| [ResourceSelectorType.Model]: { | |||||
| getList: getModelList, | getList: getModelList, | ||||
| getVersions: getModelVersionsById, | getVersions: getModelVersionsById, | ||||
| getFiles: getModelVersionIdList, | getFiles: getModelVersionIdList, | ||||
| handleVersionResponse: (res) => res.data || [], | |||||
| name: '模型', | name: '模型', | ||||
| modalIcon: modelImg, | modalIcon: modelImg, | ||||
| buttonIcon: 'local:model-select-button', | buttonIcon: 'local:model-select-button', | ||||
| litReqParamKey: 'available_range', | |||||
| fileReqParamKey: 'models_id', | fileReqParamKey: 'models_id', | ||||
| tabItems: [ | tabItems: [ | ||||
| { | { | ||||
| @@ -74,13 +85,15 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| Dataset: { | |||||
| [ResourceSelectorType.Dataset]: { | |||||
| getList: getDatasetList, | getList: getDatasetList, | ||||
| getVersions: getDatasetVersionsById, | getVersions: getDatasetVersionsById, | ||||
| getFiles: getDatasetVersionIdList, | getFiles: getDatasetVersionIdList, | ||||
| handleVersionResponse: (res) => res.data || [], | |||||
| name: '数据集', | name: '数据集', | ||||
| modalIcon: datasetImg, | modalIcon: datasetImg, | ||||
| buttonIcon: 'local:dataset-select-button', | buttonIcon: 'local:dataset-select-button', | ||||
| litReqParamKey: 'available_range', | |||||
| fileReqParamKey: 'dataset_id', | fileReqParamKey: 'dataset_id', | ||||
| tabItems: [ | tabItems: [ | ||||
| { | { | ||||
| @@ -93,6 +106,29 @@ export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeIn | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| [ResourceSelectorType.Mirror]: { | |||||
| getList: getMirrorListReq, | |||||
| getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }), | |||||
| getFiles: getMirrorFilesReq, | |||||
| handleVersionResponse: (res) => | |||||
| res.data?.content?.filter((v: MirrorVersion) => v.status === MirrorVersionStatus.Available) || | |||||
| [], | |||||
| name: '镜像', | |||||
| modalIcon: mirrorImg, | |||||
| buttonIcon: 'local:mirror-select-button', | |||||
| litReqParamKey: 'image_type', | |||||
| fileReqParamKey: 'dataset_id', | |||||
| tabItems: [ | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '我的镜像', | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '公开镜像', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| }; | }; | ||||
| type ResourceSelectorResponse = { | type ResourceSelectorResponse = { | ||||
| @@ -108,7 +144,7 @@ interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| defaultExpandedKeys: React.Key[]; | defaultExpandedKeys: React.Key[]; | ||||
| defaultCheckedKeys: React.Key[]; | defaultCheckedKeys: React.Key[]; | ||||
| defaultActiveTab: CommonTabKeys; | defaultActiveTab: CommonTabKeys; | ||||
| onOk?: (params: ResourceSelectorResponse | null) => void; | |||||
| onOk?: (params: ResourceSelectorResponse | string | null) => void; | |||||
| } | } | ||||
| type ResourceGroup = { | type ResourceGroup = { | ||||
| @@ -116,6 +152,13 @@ type ResourceGroup = { | |||||
| name: string; // 数据集或者模型 id | name: string; // 数据集或者模型 id | ||||
| }; | }; | ||||
| type MirrorVersion = { | |||||
| id: number; // 镜像版本id | |||||
| status: MirrorVersionStatus; // 镜像版本状态 | |||||
| tag_name: string; // 镜像版本 | |||||
| url: string; // 镜像版本路径 | |||||
| }; | |||||
| type ResourceFile = { | type ResourceFile = { | ||||
| id: number; // 文件 id | id: number; // 文件 id | ||||
| file_name: string; // 文件 name | file_name: string; // 文件 name | ||||
| @@ -133,6 +176,27 @@ const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { | |||||
| })); | })); | ||||
| }; | }; | ||||
| // 版本转成 treeData | |||||
| const convertVersionToTreeData = (parentId: number) => { | |||||
| return (item: string | MirrorVersion): TreeDataNode => { | |||||
| if (typeof item === 'string') { | |||||
| return { | |||||
| title: item, | |||||
| key: `${parentId}-${item}`, | |||||
| isLeaf: true, | |||||
| checkable: true, | |||||
| }; | |||||
| } else { | |||||
| return { | |||||
| title: item.tag_name, | |||||
| key: `${parentId}-${item.id}-${item.url}`, | |||||
| isLeaf: true, | |||||
| checkable: true, | |||||
| }; | |||||
| } | |||||
| }; | |||||
| }; | |||||
| // 更新树形结构的 children | // 更新树形结构的 children | ||||
| const updateChildren = (parentId: number, children: TreeDataNode[]) => { | const updateChildren = (parentId: number, children: TreeDataNode[]) => { | ||||
| return (node: TreeDataNode) => { | return (node: TreeDataNode) => { | ||||
| @@ -197,11 +261,11 @@ function ResourceSelectorModal({ | |||||
| // 获取数据集或模型列表 | // 获取数据集或模型列表 | ||||
| const getTreeData = async () => { | const getTreeData = async () => { | ||||
| const available_range = activeTab === CommonTabKeys.Private ? '0' : '1'; | |||||
| const available_range = activeTab === CommonTabKeys.Private ? 0 : 1; | |||||
| const params = { | const params = { | ||||
| page: 0, | page: 0, | ||||
| size: 200, | size: 200, | ||||
| available_range: available_range, | |||||
| [selectorTypeData[type].litReqParamKey]: available_range, | |||||
| }; | }; | ||||
| const getListReq = selectorTypeData[type].getList; | const getListReq = selectorTypeData[type].getList; | ||||
| const [res] = await to(getListReq(params)); | const [res] = await to(getListReq(params)); | ||||
| @@ -222,13 +286,8 @@ function ResourceSelectorModal({ | |||||
| const getVersionsReq = selectorTypeData[type].getVersions; | const getVersionsReq = selectorTypeData[type].getVersions; | ||||
| const [res, error] = await to(getVersionsReq(parentId)); | const [res, error] = await to(getVersionsReq(parentId)); | ||||
| if (res) { | if (res) { | ||||
| const list = res.data || []; | |||||
| const children = list.map((v: string) => ({ | |||||
| title: v, | |||||
| key: `${parentId}-${v}`, | |||||
| isLeaf: true, | |||||
| checkable: true, | |||||
| })); | |||||
| const list = selectorTypeData[type].handleVersionResponse(res); | |||||
| const children = list.map(convertVersionToTreeData(parentId)); | |||||
| // 更新 treeData children | // 更新 treeData children | ||||
| setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); | setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); | ||||
| // 缓存 loadedKeys | // 缓存 loadedKeys | ||||
| @@ -248,7 +307,7 @@ function ResourceSelectorModal({ | |||||
| const getFiles = async (id: number, version: string) => { | const getFiles = async (id: number, version: string) => { | ||||
| const getFilesReq = selectorTypeData[type].getFiles; | const getFilesReq = selectorTypeData[type].getFiles; | ||||
| const paramsKey = selectorTypeData[type].fileReqParamKey; | const paramsKey = selectorTypeData[type].fileReqParamKey; | ||||
| const params = { version: version, [paramsKey]: id } as GetFilesReqParam; | |||||
| const params = { version: version, [paramsKey]: id }; | |||||
| const [res] = await to(getFilesReq(params)); | const [res] = await to(getFilesReq(params)); | ||||
| if (res) { | if (res) { | ||||
| setVersionPath(res.data?.path || ''); | setVersionPath(res.data?.path || ''); | ||||
| @@ -329,17 +388,21 @@ function ResourceSelectorModal({ | |||||
| // 提交 | // 提交 | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| if (checkedKeys.length > 0) { | 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 CommonTabKeys, | |||||
| }; | |||||
| onOk?.(res); | |||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| onOk?.(files[0].file_name); | |||||
| } else { | |||||
| 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 CommonTabKeys, | |||||
| }; | |||||
| onOk?.(res); | |||||
| } | |||||
| } else { | } else { | ||||
| onOk?.(null); | onOk?.(null); | ||||
| } | } | ||||
| @@ -347,7 +410,10 @@ function ResourceSelectorModal({ | |||||
| const title = `选择${selectorTypeData[type].name}`; | const title = `选择${selectorTypeData[type].name}`; | ||||
| const palceholder = `请输入${selectorTypeData[type].name}名称`; | const palceholder = `请输入${selectorTypeData[type].name}名称`; | ||||
| const fileTitle = `已选${selectorTypeData[type].name}文件(${files.length})`; | |||||
| const fileTitle = | |||||
| type === ResourceSelectorType.Mirror | |||||
| ? '已选镜像' | |||||
| : `已选${selectorTypeData[type].name}文件(${files.length})`; | |||||
| const tabItems = selectorTypeData[type].tabItems; | const tabItems = selectorTypeData[type].tabItems; | ||||
| const titleImg = selectorTypeData[type].modalIcon; | const titleImg = selectorTypeData[type].modalIcon; | ||||
| @@ -4,8 +4,9 @@ import { | |||||
| } from '@/pages/Experiment/experimentText/addExperimentModal'; | } from '@/pages/Experiment/experimentText/addExperimentModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; | import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; | ||||
| import { Button, Drawer, Form, Input, Radio } from 'antd'; | |||||
| import { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd'; | |||||
| import { NamePath } from 'antd/es/form/interface'; | import { NamePath } from 'antd/es/form/interface'; | ||||
| import { forwardRef, useImperativeHandle } from 'react'; | import { forwardRef, useImperativeHandle } from 'react'; | ||||
| import styles from './globalParamsDrawer.less'; | import styles from './globalParamsDrawer.less'; | ||||
| @@ -22,9 +23,8 @@ const GlobalParamsDrawer = forwardRef( | |||||
| useImperativeHandle(ref, () => ({ | useImperativeHandle(ref, () => ({ | ||||
| getFieldsValue: async () => { | getFieldsValue: async () => { | ||||
| const [res, error] = await to(form.validateFields()); | |||||
| if (res && !error) { | |||||
| const values = form.getFieldsValue(); | |||||
| const [values, error] = await to(form.validateFields()); | |||||
| if (!error && values) { | |||||
| return values; | return values; | ||||
| } else { | } else { | ||||
| return Promise.reject(error); | return Promise.reject(error); | ||||
| @@ -32,10 +32,20 @@ const GlobalParamsDrawer = forwardRef( | |||||
| }, | }, | ||||
| })); | })); | ||||
| // 处理参数类型变化 | |||||
| const handleTypeChange = (name: NamePath) => { | const handleTypeChange = (name: NamePath) => { | ||||
| form.setFieldValue(name, null); | form.setFieldValue(name, null); | ||||
| }; | }; | ||||
| const removeParameter = (name: number, remove: (param: number) => void) => { | |||||
| modalConfirm({ | |||||
| title: '确认删除该参数吗?', | |||||
| onOk: () => { | |||||
| remove(name); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| return ( | return ( | ||||
| <Drawer | <Drawer | ||||
| rootStyle={{ marginTop: '45px' }} | rootStyle={{ marginTop: '45px' }} | ||||
| @@ -128,12 +138,14 @@ const GlobalParamsDrawer = forwardRef( | |||||
| <Radio value={0}>否</Radio> | <Radio value={0}>否</Radio> | ||||
| </Radio.Group> | </Radio.Group> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Button | |||||
| className={styles.delete_button} | |||||
| type="link" | |||||
| onClick={() => remove(name)} | |||||
| icon={<DeleteOutlined />} | |||||
| ></Button> | |||||
| <Tooltip title="删除参数"> | |||||
| <Button | |||||
| className={styles.delete_button} | |||||
| type="link" | |||||
| onClick={() => removeParameter(name, remove)} | |||||
| icon={<DeleteOutlined />} | |||||
| ></Button> | |||||
| </Tooltip> | |||||
| </div> | </div> | ||||
| ))} | ))} | ||||
| <Form.Item className={styles.add_button_form_item}> | <Form.Item className={styles.add_button_form_item}> | ||||
| @@ -150,6 +162,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| )} | )} | ||||
| </Form.List> | </Form.List> | ||||
| </Form> | </Form> | ||||
| {/* //{contextHolder} */} | |||||
| </Drawer> | </Drawer> | ||||
| ); | ); | ||||
| }, | }, | ||||
| @@ -1,9 +1,7 @@ | |||||
| import { ReactComponent as ParameterIcon } from '@/assets/svg/parameter.svg'; | |||||
| import { ReactComponent as SaveAndReturn } from '@/assets/svg/save--return.svg'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { useVisible } from '@/hooks'; | import { useVisible } 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'; | ||||
| import { SaveOutlined } from '@ant-design/icons'; | |||||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | import { useEmotionCss } from '@ant-design/use-emotion-css'; | ||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { Button, message } from 'antd'; | import { Button, message } from 'antd'; | ||||
| @@ -716,7 +714,7 @@ const EditPipeline = () => { | |||||
| <div className={Styles.buttonList}> | <div className={Styles.buttonList}> | ||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| icon={<ParameterIcon style={{ verticalAlign: '-2px' }} />} | |||||
| icon={<KFIcon type="icon-quanjucanshu" />} | |||||
| style={{ marginRight: '20px' }} | style={{ marginRight: '20px' }} | ||||
| onClick={openParamsDrawer} | onClick={openParamsDrawer} | ||||
| > | > | ||||
| @@ -724,7 +722,7 @@ const EditPipeline = () => { | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| type="primary" | type="primary" | ||||
| icon={<SaveOutlined />} | |||||
| icon={<KFIcon type="icon-baocun" />} | |||||
| style={{ marginRight: '20px' }} | style={{ marginRight: '20px' }} | ||||
| onClick={() => { | onClick={() => { | ||||
| savePipeline(false); | savePipeline(false); | ||||
| @@ -740,7 +738,7 @@ const EditPipeline = () => { | |||||
| background: '#fff', | background: '#fff', | ||||
| color: '#1664ff', | color: '#1664ff', | ||||
| }} | }} | ||||
| icon={<SaveAndReturn />} | |||||
| icon={<KFIcon type="icon-baocunbingfanhui" />} | |||||
| onClick={() => { | onClick={() => { | ||||
| savePipeline(true); | savePipeline(true); | ||||
| }} | }} | ||||
| @@ -1,9 +1,10 @@ | |||||
| import { pick } from '@/utils/index'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Icon } from '@umijs/max'; | |||||
| import { Button, Drawer, Form, Input } from 'antd'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||||
| import { Button, Drawer, Form, Input, Select } from 'antd'; | |||||
| import { pick } from 'lodash'; | |||||
| import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | |||||
| import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | ||||
| import Styles from './editPipeline.less'; | import Styles from './editPipeline.less'; | ||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| @@ -14,6 +15,23 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [selectedModel, setSelectedModel] = useState(undefined); | const [selectedModel, setSelectedModel] = useState(undefined); | ||||
| const [selectedDataset, setSelectedDataset] = useState(undefined); | const [selectedDataset, setSelectedDataset] = useState(undefined); | ||||
| const [resourceStandardList, setResourceStandardList] = useState([]); | |||||
| useEffect(() => { | |||||
| getComputingResource(); | |||||
| }, []); | |||||
| const getComputingResource = async () => { | |||||
| const params = { | |||||
| page: 0, | |||||
| size: 1000, | |||||
| resource_type: '', | |||||
| }; | |||||
| const [res] = await to(getComputingResourceReq(params)); | |||||
| if (res && res.data && res.data.content) { | |||||
| setResourceStandardList(res.data.content); | |||||
| } | |||||
| }; | |||||
| const afterOpenChange = () => { | const afterOpenChange = () => { | ||||
| if (!open) { | if (!open) { | ||||
| @@ -109,9 +127,21 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| // 选择数据集、模型 | // 选择数据集、模型 | ||||
| const selectResource = (name, item) => { | const selectResource = (name, item) => { | ||||
| const type = | |||||
| item.item_type === 'dataset' ? ResourceSelectorType.Dataset : ResourceSelectorType.Model; | |||||
| const resource = type === ResourceSelectorType.Dataset ? selectedDataset : selectedModel; | |||||
| let type; | |||||
| let resource = undefined; | |||||
| switch (item.item_type) { | |||||
| case 'dataset': | |||||
| type = ResourceSelectorType.Dataset; | |||||
| resource = selectedDataset; | |||||
| break; | |||||
| case 'model': | |||||
| type = ResourceSelectorType.Model; | |||||
| resource = selectedModel; | |||||
| break; | |||||
| default: | |||||
| type = ResourceSelectorType.Mirror; | |||||
| break; | |||||
| } | |||||
| const { close } = openAntdModal( | const { close } = openAntdModal( | ||||
| ResourceSelectorModal, | ResourceSelectorModal, | ||||
| { | { | ||||
| @@ -121,18 +151,23 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| defaultActiveTab: resource?.activeTab, | defaultActiveTab: resource?.activeTab, | ||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| const jsonObj = pick(res, ['id', 'version', 'path']); | |||||
| const value = JSON.stringify(jsonObj); | |||||
| form.setFieldValue(name, value); | |||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| form.setFieldValue(name, res); | |||||
| } else { | |||||
| const jsonObj = pick(res, ['id', 'version', 'path']); | |||||
| const value = JSON.stringify(jsonObj); | |||||
| form.setFieldValue(name, value); | |||||
| } | |||||
| if (type === ResourceSelectorType.Dataset) { | if (type === ResourceSelectorType.Dataset) { | ||||
| setSelectedDataset(res); | setSelectedDataset(res); | ||||
| } else { | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(res); | setSelectedModel(res); | ||||
| } | } | ||||
| } else { | } else { | ||||
| if (type === ResourceSelectorType.Dataset) { | if (type === ResourceSelectorType.Dataset) { | ||||
| setSelectedDataset(null); | setSelectedDataset(null); | ||||
| } else { | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(null); | setSelectedModel(null); | ||||
| } | } | ||||
| form.setFieldValue(name, ''); | form.setFieldValue(name, ''); | ||||
| @@ -148,14 +183,18 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| const getSelectBtnIcon = (item) => { | const getSelectBtnIcon = (item) => { | ||||
| const type = item.item_type; | const type = item.item_type; | ||||
| if (type === 'dataset') { | if (type === 'dataset') { | ||||
| return <Icon icon="local:dataset-select-button" className="umi-local-svg" />; | |||||
| return <KFIcon type="icon-xuanzeshujuji" />; | |||||
| } else if (type === 'model') { | } else if (type === 'model') { | ||||
| return <Icon icon="local:model-select-button" className="umi-local-svg" />; | |||||
| return <KFIcon type="icon-xuanzemoxing" />; | |||||
| } else { | } else { | ||||
| return <Icon icon="local:mirror-select-button" className="umi-local-svg" />; | |||||
| return <KFIcon type="icon-xuanzejingxiang" />; | |||||
| } | } | ||||
| }; | }; | ||||
| const filterResourceStandard = (input, { computing_resource = '' }) => { | |||||
| return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase()); | |||||
| }; | |||||
| // 控制策略 | // 控制策略 | ||||
| const controlStrategy = stagingItem.control_strategy; | const controlStrategy = stagingItem.control_strategy; | ||||
| // 输入参数 | // 输入参数 | ||||
| @@ -237,17 +276,22 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| /> | /> | ||||
| 任务信息 | 任务信息 | ||||
| </div> | </div> | ||||
| <Form.Item | |||||
| label="镜像" | |||||
| name="image" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input /> | |||||
| <Form.Item label="镜像" required> | |||||
| <div className={Styles['ref-row']}> | |||||
| <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | |||||
| <Input placeholder="请输入镜像" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| icon={getSelectBtnIcon({ item_type: 'image' })} | |||||
| onClick={() => selectResource('image', { item_type: 'image' })} | |||||
| className={Styles['select-button']} | |||||
| > | |||||
| 选择镜像 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </div> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="工作目录" name="working_directory"> | <Form.Item label="工作目录" name="working_directory"> | ||||
| <Input /> | <Input /> | ||||
| @@ -262,11 +306,20 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入资源规格', | |||||
| message: '请选择资源规格', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input /> | |||||
| <Select | |||||
| showSearch | |||||
| placeholder="请选择资源规格" | |||||
| filterOption={filterResourceStandard} | |||||
| options={resourceStandardList} | |||||
| fieldNames={{ | |||||
| label: 'description', | |||||
| value: 'standard', | |||||
| }} | |||||
| /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="挂载路径" name="mount_path"> | <Form.Item label="挂载路径" name="mount_path"> | ||||
| <Input /> | <Input /> | ||||
| @@ -1,3 +1,4 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { | import { | ||||
| addWorkflow, | addWorkflow, | ||||
| cloneWorkflow, | cloneWorkflow, | ||||
| @@ -6,14 +7,15 @@ import { | |||||
| getWorkflowById, | getWorkflowById, | ||||
| removeWorkflow, | removeWorkflow, | ||||
| } from '@/services/pipeline/index.js'; | } from '@/services/pipeline/index.js'; | ||||
| import { CopyOutlined, DeleteOutlined, EditOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||||
| import { Button, Form, Input, Modal, Space, Table, message } from 'antd'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { Button, ConfigProvider, Form, Input, Modal, Space, Table, message } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import momnet from 'moment'; | import momnet from 'moment'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| import Styles from './index.less'; | import Styles from './index.less'; | ||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| const Pipeline = () => { | const Pipeline = () => { | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| @@ -26,7 +28,7 @@ const Pipeline = () => { | |||||
| const editTable = (e, record) => { | const editTable = (e, record) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| getWorkflowById(record.id).then((ret) => { | getWorkflowById(record.id).then((ret) => { | ||||
| if (ret.code == 200) { | |||||
| if (ret.code === 200) { | |||||
| form.resetFields(); | form.resetFields(); | ||||
| form.setFieldsValue({ ...ret.data }); | form.setFieldsValue({ ...ret.data }); | ||||
| setFormId(ret.data.id); | setFormId(ret.data.id); | ||||
| @@ -72,6 +74,7 @@ const Pipeline = () => { | |||||
| }; | }; | ||||
| const pageOption = useRef({ page: 1, size: 10 }); | const pageOption = useRef({ page: 1, size: 10 }); | ||||
| const paginationProps = { | const paginationProps = { | ||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | showQuickJumper: true, | ||||
| showTotal: () => `共${total}条`, | showTotal: () => `共${total}条`, | ||||
| total: total, | total: total, | ||||
| @@ -111,7 +114,7 @@ const Pipeline = () => { | |||||
| title: '序号', | title: '序号', | ||||
| dataIndex: 'index', | dataIndex: 'index', | ||||
| key: 'index', | key: 'index', | ||||
| width: 140, | |||||
| width: 120, | |||||
| align: 'center', | align: 'center', | ||||
| render(text, record, index) { | render(text, record, index) { | ||||
| return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>; | return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>; | ||||
| @@ -152,7 +155,7 @@ const Pipeline = () => { | |||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="edit" | key="edit" | ||||
| icon={<EditOutlined />} | |||||
| icon={<KFIcon type="icon-bianji" />} | |||||
| onClick={(e) => { | onClick={(e) => { | ||||
| editTable(e, record); | editTable(e, record); | ||||
| }} | }} | ||||
| @@ -163,7 +166,7 @@ const Pipeline = () => { | |||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="clone" | key="clone" | ||||
| icon={<CopyOutlined />} | |||||
| icon={<KFIcon type="icon-fuzhi" />} | |||||
| onClick={async () => { | onClick={async () => { | ||||
| Modal.confirm({ | Modal.confirm({ | ||||
| title: '复制', | title: '复制', | ||||
| @@ -173,7 +176,7 @@ const Pipeline = () => { | |||||
| onOk: () => { | onOk: () => { | ||||
| console.log(record); | console.log(record); | ||||
| cloneWorkflow(record.id).then((ret) => { | cloneWorkflow(record.id).then((ret) => { | ||||
| if (ret.code == 200) { | |||||
| if (ret.code === 200) { | |||||
| message.success('复制成功'); | message.success('复制成功'); | ||||
| getList(); | getList(); | ||||
| } else { | } else { | ||||
| @@ -192,71 +195,65 @@ const Pipeline = () => { | |||||
| > | > | ||||
| 复制 | 复制 | ||||
| </Button> | </Button> | ||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| danger | |||||
| style={{ color: '#f98e1b' }} | |||||
| key="batchRemove" | |||||
| icon={<DeleteOutlined />} | |||||
| onClick={async () => { | |||||
| Modal.confirm({ | |||||
| title: ( | |||||
| <div> | |||||
| <img | |||||
| src="/assets/images/delete-icon.png" | |||||
| style={{ width: '120px', marginBottom: '24px' }} | |||||
| alt="" | |||||
| /> | |||||
| <div style={{ color: '#1d1d20', fontSize: '16px' }}> | |||||
| 删除后,该流水线将不可恢复 | |||||
| </div> | |||||
| </div> | |||||
| ), | |||||
| content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>, | |||||
| closable: true, | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| console.log(record); | |||||
| removeWorkflow(record.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getList(); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| // if (success) { | |||||
| // if (actionRef.current) { | |||||
| // actionRef.current.reload(); | |||||
| // } | |||||
| // } | |||||
| }, | |||||
| }); | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | |||||
| }} | }} | ||||
| > | > | ||||
| 删除 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="batchRemove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该流水线将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| console.log(record); | |||||
| removeWorkflow(record.id).then((ret) => { | |||||
| if (ret.code === 200) { | |||||
| message.success('删除成功'); | |||||
| getList(); | |||||
| } else { | |||||
| message.error(ret.msg); | |||||
| } | |||||
| }); | |||||
| // if (success) { | |||||
| // if (actionRef.current) { | |||||
| // actionRef.current.reload(); | |||||
| // } | |||||
| // } | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| </Space> | </Space> | ||||
| ), | ), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| return ( | return ( | ||||
| <div> | |||||
| <div className={Styles.PipelineBox}> | |||||
| <div className={Styles.pipelineTopBox}> | <div className={Styles.pipelineTopBox}> | ||||
| <Button | |||||
| type="primary" | |||||
| className={Styles.plusButton} | |||||
| onClick={showModal} | |||||
| icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />} | |||||
| > | |||||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 新建流水线 | 新建流水线 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| <Table columns={columns} dataSource={pipeList} pagination={paginationProps} rowKey="id" /> | |||||
| <div className={classNames('vertical-scroll-table', Styles.PipelineTable)}> | |||||
| <Table | |||||
| columns={columns} | |||||
| dataSource={pipeList} | |||||
| pagination={paginationProps} | |||||
| rowKey="id" | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| /> | |||||
| </div> | |||||
| <Modal | <Modal | ||||
| title={ | title={ | ||||
| <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | <div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}> | ||||
| @@ -309,7 +306,12 @@ const Pipeline = () => { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入流水线描述" showCount maxLength={128} /> | |||||
| <TextArea | |||||
| autoSize={{ minRows: 2, maxRows: 5 }} | |||||
| placeholder="请输入流水线描述" | |||||
| showCount | |||||
| maxLength={128} | |||||
| /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Form> | </Form> | ||||
| </Modal> | </Modal> | ||||
| @@ -8,31 +8,18 @@ | |||||
| padding-right: 30px; | padding-right: 30px; | ||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| } | } | ||||
| .plusButton { | |||||
| color: #1d1d20; | |||||
| font-size: 14px; | |||||
| font-family: 'Alibaba'; | |||||
| background: rgba(22, 100, 255, 0.06); | |||||
| border: 1px solid; | |||||
| border-color: rgba(22, 100, 255, 0.11); | |||||
| border-radius: 4px; | |||||
| } | |||||
| .plusButton:hover { | |||||
| color: #1d1d20 !important; | |||||
| background: rgba(22, 100, 255, 0.06) !important; | |||||
| border: 1px solid !important; | |||||
| border-color: rgba(22, 100, 255, 0.11) !important; | |||||
| } | |||||
| .modal { | .modal { | ||||
| :global { | :global { | ||||
| .ant-modal-content { | .ant-modal-content { | ||||
| width: 825px; | width: 825px; | ||||
| padding: 20px 67px; | padding: 20px 67px; | ||||
| background-image: url(/assets/images/modal-back.png); | background-image: url(/assets/images/modal-back.png); | ||||
| background-repeat:no-repeat; | |||||
| background-size:100%; | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | background-position: top center; | ||||
| background-size: 100%; | |||||
| border-radius: 21px; | border-radius: 21px; | ||||
| } | } | ||||
| .ant-modal-header { | .ant-modal-header { | ||||
| @@ -62,6 +49,19 @@ | |||||
| .ant-btn-primary { | .ant-btn-primary { | ||||
| background: #1664ff; | background: #1664ff; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| .PipelineBox{ | |||||
| height: calc(100% - 20px); | |||||
| .PipelineTable{ | |||||
| height: calc(100% - 60px); | |||||
| :global{ | |||||
| .ant-table-wrapper .ant-table{ | |||||
| // overflow-y: auto; | |||||
| height: calc(100% - 48px); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -28,18 +28,24 @@ export function getMirrorVersionListReq(params: any) { | |||||
| }); | }); | ||||
| } | } | ||||
| // 创建公网镜像 | |||||
| export function createPublicMirrorReq(data: any) { | |||||
| return request(`/api/mmp/image/net`, { | |||||
| // 创建镜像 | |||||
| export function createMirrorReq(data: any) { | |||||
| return request(`/api/mmp/image/addImageAndVersion`, { | |||||
| method: 'POST', | method: 'POST', | ||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 创建本地镜像 | |||||
| export function createPrivateMirrorReq(data: any) { | |||||
| return request(`/api/mmp/image/local`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| // 删除镜像 | |||||
| export function deleteMirrorReq(id: number) { | |||||
| return request(`/api/mmp/image/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||
| // 删除镜像 | |||||
| export function deleteMirrorVersionReq(id: number) { | |||||
| return request(`/api/mmp/imageVersion/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -1,3 +1,8 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-03-25 13:52:54 | |||||
| * @Description: | |||||
| */ | |||||
| import { request } from '@umijs/max'; | import { request } from '@umijs/max'; | ||||
| // 查询流水线列表 | // 查询流水线列表 | ||||
| export function getWorkflow(params) { | export function getWorkflow(params) { | ||||
| @@ -63,3 +68,11 @@ export function getWorkflowById(id) { | |||||
| method: 'GET', | method: 'GET', | ||||
| }); | }); | ||||
| } | } | ||||
| // 获取资源规格 | |||||
| export function getComputingResourceReq(params) { | |||||
| return request(`/api/mmp/computingResource`, { | |||||
| method: 'GET', | |||||
| params | |||||
| }); | |||||
| } | |||||
| @@ -8,9 +8,7 @@ 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; | ||||
| } | } | ||||
| @@ -1,17 +1,32 @@ | |||||
| // 全局颜色变量 | // 全局颜色变量 | ||||
| // FIXME: 不能设置 @primary-color 不起作用,感觉是哪里被重置了 | |||||
| @kf-primary-color: #1664ff; // 主色调 | |||||
| @primary-color-hover: #4086ff; | |||||
| @primary-color: #1664ff; // 主色调 | |||||
| @primary-color-hover: #69b1ff; | |||||
| @background-color: #f9fafb; // 页面背景颜色 | @background-color: #f9fafb; // 页面背景颜色 | ||||
| @text-color: #1d1d20; | @text-color: #1d1d20; | ||||
| @text-color-second: #575757; | |||||
| @font-size: 15px; | |||||
| @text-color-secondary: #575757; | |||||
| @success-color: #1ace62; | |||||
| @error-color: #c73131; | |||||
| @warning-color: #f98e1b; | |||||
| @border-color: rgba(22, 100, 255, 0.3); | @border-color: rgba(22, 100, 255, 0.3); | ||||
| @border-color-second: rgba(22, 100, 255, 0.1); | @border-color-second: rgba(22, 100, 255, 0.1); | ||||
| @background-color-primay: rgba(22, 100, 255, 0.03); | @background-color-primay: rgba(22, 100, 255, 0.03); | ||||
| @background-color-gray: rgba(4, 3, 3, 0.06); | @background-color-gray: rgba(4, 3, 3, 0.06); | ||||
| @heading-color: rgba(0, 0, 0, 0.85); | |||||
| @input-icon-hover-color: rgba(0, 0, 0, 0.85); | |||||
| @border-color-base: #d9d9d9; | |||||
| @link-hover-color: #69b1ff; | |||||
| // 字体大小 | |||||
| @font-size: 15px; | |||||
| // 导出变量 | // 导出变量 | ||||
| :export { | :export { | ||||
| primaryColor: @kf-primary-color; | |||||
| primaryColor: @primary-color; | |||||
| successColor: @success-color; | |||||
| errorColor: @error-color; | |||||
| warningColor: @warning-color; | |||||
| textColor: @text-color; | |||||
| fontSize: @font-size; | |||||
| } | } | ||||
| @@ -14,37 +14,3 @@ export function getNameByCode(list: any[], code: any) { | |||||
| }); | }); | ||||
| return name; | 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>; | |||||
| } | |||||
| @@ -3,7 +3,8 @@ | |||||
| * @Date: 2024-04-13 10:08:35 | * @Date: 2024-04-13 10:08:35 | ||||
| * @Description: | * @Description: | ||||
| */ | */ | ||||
| import { type ModalProps } from 'antd'; | |||||
| import { ConfigProvider, type ModalProps } from 'antd'; | |||||
| import { globalConfig } from 'antd/es/config-provider'; | |||||
| import React, { useState } from 'react'; | import React, { useState } from 'react'; | ||||
| import { createRoot } from 'react-dom/client'; | import { createRoot } from 'react-dom/client'; | ||||
| @@ -19,19 +20,20 @@ export const openAntdModal = <T extends ModalProps>( | |||||
| modalProps: T, | modalProps: T, | ||||
| ) => { | ) => { | ||||
| const CustomModel = modal; | const CustomModel = modal; | ||||
| const element = document.createElement('div'); | |||||
| element.id = 'modal-container'; | |||||
| document.body.appendChild(element); | |||||
| const root = createRoot(element); | |||||
| const container = document.createDocumentFragment(); | |||||
| const root = createRoot(container); | |||||
| const { afterClose, onCancel } = modalProps; | const { afterClose, onCancel } = modalProps; | ||||
| const global = globalConfig(); | |||||
| let timeoutId: ReturnType<typeof setTimeout>; | |||||
| function destroy() { | function destroy() { | ||||
| root.unmount(); | root.unmount(); | ||||
| document.body.removeChild(element); | |||||
| } | } | ||||
| function handleAfterClose() { | function handleAfterClose() { | ||||
| afterClose?.(); | afterClose?.(); | ||||
| // Warning: Attempted to synchronously unmount a root while React was already rendering. | |||||
| // React cannot finish unmounting the root until the current render has completed, which may lead to a race condition. | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| destroy(); | destroy(); | ||||
| }, 0); | }, 0); | ||||
| @@ -46,11 +48,26 @@ export const openAntdModal = <T extends ModalProps>( | |||||
| } | } | ||||
| function render(props: T) { | function render(props: T) { | ||||
| root.render(<CustomModel {...props} onCancel={handleCancel}></CustomModel>); | |||||
| clearTimeout(timeoutId); | |||||
| timeoutId = setTimeout(() => { | |||||
| const rootPrefixCls = global.getPrefixCls(); | |||||
| const iconPrefixCls = global.getIconPrefixCls(); | |||||
| const theme = global.getTheme(); | |||||
| const dom = ( | |||||
| <CustomModel {...props} onCancel={handleCancel} afterClose={handleAfterClose}></CustomModel> | |||||
| ); | |||||
| root.render( | |||||
| <ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls} theme={theme}> | |||||
| {global.holderRender ? global.holderRender(dom) : dom} | |||||
| </ConfigProvider>, | |||||
| ); | |||||
| }); | |||||
| } | } | ||||
| function close() { | function close() { | ||||
| render({ ...modalProps, open: false, afterClose: handleAfterClose }); | |||||
| render({ ...modalProps, open: false }); | |||||
| } | } | ||||
| render({ ...modalProps, open: true }); | render({ ...modalProps, open: true }); | ||||
| @@ -0,0 +1,2 @@ | |||||
| // 用于新建镜像 | |||||
| export const mirrorNameKey = 'mirror-name'; | |||||
| @@ -0,0 +1,45 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-19 14:42:51 | |||||
| * @Description: UI 公共方法 | |||||
| */ | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { Modal, type ModalFuncProps, type UploadFile } from 'antd'; | |||||
| // 自定义 Confirm 弹框 | |||||
| export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) { | |||||
| Modal.confirm({ | |||||
| ...rest, | |||||
| title: ( | |||||
| <div> | |||||
| <img | |||||
| src="/assets/images/delete-icon.png" | |||||
| style={{ width: '120px', marginBottom: '24px' }} | |||||
| alt="" | |||||
| /> | |||||
| <div style={{ color: themes.textColor, fontSize: '16px', fontWeight: 500 }}>{title}</div> | |||||
| </div> | |||||
| ), | |||||
| content: content && <div style={{ color: themes.textColor, fontSize: '15px' }}>{content}</div>, | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: onOk, | |||||
| }); | |||||
| } | |||||
| // 从事件中获取上传文件列表,用于 Upload + Form 中 | |||||
| export const getFileListFromEvent = (e: any) => { | |||||
| const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || []; | |||||
| return fileList.map((item) => { | |||||
| if (item.status === 'done') { | |||||
| const { response } = item; | |||||
| if (response?.code !== 200) { | |||||
| return { | |||||
| ...item, | |||||
| status: 'error', | |||||
| }; | |||||
| } | |||||
| } | |||||
| return item; | |||||
| }); | |||||
| }; | |||||
| @@ -4,6 +4,7 @@ import com.ruoyi.common.core.web.controller.BaseController; | |||||
| import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | import com.ruoyi.common.core.web.domain.GenericsAjaxResult; | ||||
| import com.ruoyi.platform.domain.DatasetVersion; | import com.ruoyi.platform.domain.DatasetVersion; | ||||
| import com.ruoyi.platform.service.DatasetVersionService; | import com.ruoyi.platform.service.DatasetVersionService; | ||||
| import com.ruoyi.platform.vo.LabelDatasetVersion; | |||||
| import io.swagger.annotations.ApiOperation; | import io.swagger.annotations.ApiOperation; | ||||
| import org.springframework.data.domain.Page; | import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.PageRequest; | import org.springframework.data.domain.PageRequest; | ||||
| @@ -135,5 +136,10 @@ public class DatasetVersionController extends BaseController { | |||||
| return genericsSuccess(this.datasetVersionService.deleteDatasetVersion(datasetId, version)); | return genericsSuccess(this.datasetVersionService.deleteDatasetVersion(datasetId, version)); | ||||
| } | } | ||||
| @PostMapping("/addDatasetVersionsFromLabel") | |||||
| @ApiOperation("从数据标注添加数据集版本") | |||||
| public GenericsAjaxResult<Boolean> addDatasetVersionsFromLabel(@RequestBody LabelDatasetVersion labelDatasetVersion) throws Exception { | |||||
| return genericsSuccess(true); | |||||
| } | |||||
| } | } | ||||
| @@ -116,7 +116,7 @@ public class ImageController extends BaseController { | |||||
| * @return 删除是否成功 | * @return 删除是否成功 | ||||
| */ | */ | ||||
| @DeleteMapping("{id}") | @DeleteMapping("{id}") | ||||
| public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) { | |||||
| public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) throws Exception { | |||||
| return genericsSuccess(this.imageService.removeById(id)); | return genericsSuccess(this.imageService.removeById(id)); | ||||
| } | } | ||||
| @@ -80,5 +80,7 @@ public interface ImageDao { | |||||
| int deleteById(Integer id); | int deleteById(Integer id); | ||||
| List<Image> queryByName(String name); | List<Image> queryByName(String name); | ||||
| Image getByName(String name); | |||||
| } | } | ||||
| @@ -5,6 +5,7 @@ package com.ruoyi.platform.service; | |||||
| import com.ruoyi.platform.domain.Dataset; | import com.ruoyi.platform.domain.Dataset; | ||||
| import com.ruoyi.platform.domain.DatasetVersion; | import com.ruoyi.platform.domain.DatasetVersion; | ||||
| import com.ruoyi.platform.vo.LabelDatasetVersion; | |||||
| import org.springframework.data.domain.Page; | import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.PageRequest; | import org.springframework.data.domain.PageRequest; | ||||
| @@ -73,4 +74,6 @@ public interface DatasetVersionService { | |||||
| void checkDeclaredVersion(DatasetVersion insert) throws Exception; | void checkDeclaredVersion(DatasetVersion insert) throws Exception; | ||||
| String addDatasetVersions(List<DatasetVersion> datasetVersions) throws Exception; | String addDatasetVersions(List<DatasetVersion> datasetVersions) throws Exception; | ||||
| void addDatasetVersionsFromLabel(LabelDatasetVersion labelDatasetVersion) throws Exception; | |||||
| } | } | ||||
| @@ -26,6 +26,20 @@ public interface ImageService { | |||||
| */ | */ | ||||
| Image queryById(Integer id); | Image queryById(Integer id); | ||||
| Page<Image> queryByName(String name); | |||||
| /** | |||||
| * 通过名字精确查询单条数据 | |||||
| * | |||||
| * @param name 名字 | |||||
| * @return 实例对象 | |||||
| */ | |||||
| Image getByName(String name); | |||||
| /** | /** | ||||
| * 分页查询 | * 分页查询 | ||||
| * | * | ||||
| @@ -59,9 +73,11 @@ public interface ImageService { | |||||
| */ | */ | ||||
| boolean deleteById(Integer id); | boolean deleteById(Integer id); | ||||
| String removeById(Integer id); | |||||
| String removeById(Integer id) throws Exception; | |||||
| Page<Image> queryByName(String name); | |||||
| String insertImageAndVersion(ImageVo imageVo) throws Exception; | String insertImageAndVersion(ImageVo imageVo) throws Exception; | ||||
| @@ -77,4 +93,6 @@ public interface ImageService { | |||||
| Map<String, String> createImageFromNet(String imageName, String imageTag, String NetPath) throws Exception; | Map<String, String> createImageFromNet(String imageName, String imageTag, String NetPath) throws Exception; | ||||
| Map<String, String> uploadImageFiles(MultipartFile file) throws Exception; | Map<String, String> uploadImageFiles(MultipartFile file) throws Exception; | ||||
| } | } | ||||
| @@ -8,6 +8,8 @@ import com.ruoyi.platform.domain.ModelsVersion; | |||||
| import com.ruoyi.platform.domain.Workflow; | import com.ruoyi.platform.domain.Workflow; | ||||
| import com.ruoyi.platform.mapper.DatasetVersionDao; | import com.ruoyi.platform.mapper.DatasetVersionDao; | ||||
| import com.ruoyi.platform.service.DatasetVersionService; | import com.ruoyi.platform.service.DatasetVersionService; | ||||
| import com.ruoyi.platform.utils.HttpUtils; | |||||
| import com.ruoyi.platform.vo.LabelDatasetVersion; | |||||
| import com.ruoyi.system.api.model.LoginUser; | import com.ruoyi.system.api.model.LoginUser; | ||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.data.domain.Page; | import org.springframework.data.domain.Page; | ||||
| @@ -16,6 +18,7 @@ import org.springframework.data.domain.PageRequest; | |||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||
| import java.io.InputStream; | |||||
| import java.lang.reflect.Field; | import java.lang.reflect.Field; | ||||
| import java.util.Date; | import java.util.Date; | ||||
| import java.util.HashMap; | import java.util.HashMap; | ||||
| @@ -32,7 +35,6 @@ import java.util.Map; | |||||
| public class DatasetVersionServiceImpl implements DatasetVersionService { | public class DatasetVersionServiceImpl implements DatasetVersionService { | ||||
| @Resource | @Resource | ||||
| private DatasetVersionDao datasetVersionDao; | private DatasetVersionDao datasetVersionDao; | ||||
| // 固定存储桶名 | // 固定存储桶名 | ||||
| private final String bucketName = "platform-data"; | private final String bucketName = "platform-data"; | ||||
| @@ -193,8 +195,15 @@ public class DatasetVersionServiceImpl implements DatasetVersionService { | |||||
| throw new Exception("新增数据集版本失败: " + e.getMessage()); | throw new Exception("新增数据集版本失败: " + e.getMessage()); | ||||
| } | } | ||||
| } | |||||
| @Override | |||||
| public void addDatasetVersionsFromLabel(LabelDatasetVersion labelDatasetVersion) throws Exception{ | |||||
| // 获取label-studio数据流 | |||||
| InputStream inputStream = HttpUtils.getInputStream("http://127.0.0.1:8080/api/projects/"+labelDatasetVersion.getProject_id()+"/export?exportType="+labelDatasetVersion.getExportType(), labelDatasetVersion.getToken()); | |||||
| // 上传镜像至minio | |||||
| //保存DatasetVersion | |||||
| } | } | ||||
| private void insertPrepare(DatasetVersion datasetVersion) throws Exception { | private void insertPrepare(DatasetVersion datasetVersion) throws Exception { | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ruoyi.platform.service.impl; | package com.ruoyi.platform.service.impl; | ||||
| import com.alibaba.fastjson2.util.DateUtils; | |||||
| import com.ruoyi.common.security.utils.SecurityUtils; | import com.ruoyi.common.security.utils.SecurityUtils; | ||||
| import com.ruoyi.platform.domain.Image; | import com.ruoyi.platform.domain.Image; | ||||
| import com.ruoyi.platform.domain.ImageVersion; | import com.ruoyi.platform.domain.ImageVersion; | ||||
| @@ -12,6 +13,7 @@ import com.ruoyi.platform.utils.FileUtil; | |||||
| import com.ruoyi.platform.utils.K8sClientUtil; | import com.ruoyi.platform.utils.K8sClientUtil; | ||||
| import com.ruoyi.platform.vo.ImageVo; | import com.ruoyi.platform.vo.ImageVo; | ||||
| import com.ruoyi.system.api.model.LoginUser; | import com.ruoyi.system.api.model.LoginUser; | ||||
| import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim; | |||||
| import io.kubernetes.client.openapi.models.V1Pod; | import io.kubernetes.client.openapi.models.V1Pod; | ||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.beans.factory.annotation.Value; | ||||
| @@ -64,6 +66,15 @@ public class ImageServiceImpl implements ImageService { | |||||
| private String deploymentName; | private String deploymentName; | ||||
| @Value("${harbor.serviceNS}") | @Value("${harbor.serviceNS}") | ||||
| private String serviceNS; | private String serviceNS; | ||||
| @Value("${dockerpush.image}") | |||||
| private String image; | |||||
| @Value("${dockerpush.mountPath}") | |||||
| private String mountPath; | |||||
| @Value("${dockerpush.proxyUrl}") | |||||
| private String proxyUrl; | |||||
| @Value("${minio.pvcName}") | |||||
| private String pvcName; | |||||
| /** | /** | ||||
| * 通过ID查询单条数据 | * 通过ID查询单条数据 | ||||
| * | * | ||||
| @@ -88,6 +99,8 @@ public class ImageServiceImpl implements ImageService { | |||||
| return new PageImpl<>(this.imageDao.queryAllByLimit(image, pageRequest), pageRequest, total); | return new PageImpl<>(this.imageDao.queryAllByLimit(image, pageRequest), pageRequest, total); | ||||
| } | } | ||||
| /** | /** | ||||
| * 新增数据 | * 新增数据 | ||||
| * | * | ||||
| @@ -138,10 +151,10 @@ public class ImageServiceImpl implements ImageService { | |||||
| } | } | ||||
| @Override | @Override | ||||
| public String removeById(Integer id) { | |||||
| public String removeById(Integer id) throws Exception { | |||||
| Image image = this.imageDao.queryById(id); | Image image = this.imageDao.queryById(id); | ||||
| if (image == null){ | if (image == null){ | ||||
| return "镜像不存在"; | |||||
| throw new Exception("镜像不存在"); | |||||
| } | } | ||||
| //判断权限,只有admin和创建者本身可以删除该数据集 | //判断权限,只有admin和创建者本身可以删除该数据集 | ||||
| @@ -150,11 +163,11 @@ public class ImageServiceImpl implements ImageService { | |||||
| String createdBy = image.getCreateBy(); | String createdBy = image.getCreateBy(); | ||||
| if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){ | |||||
| return "无权限删除该镜像"; | |||||
| if (!(StringUtils.equals(username,"admin") || !StringUtils.equals(username,createdBy))){ | |||||
| throw new Exception("无权限删除该镜像"); | |||||
| } | } | ||||
| if (!imageVersionService.queryByImageId(id).isEmpty()){ | if (!imageVersionService.queryByImageId(id).isEmpty()){ | ||||
| return "请先删除该镜像下的版本文件"; | |||||
| throw new Exception("请先删除该镜像下的版本文件"); | |||||
| } | } | ||||
| image.setState(0); | image.setState(0); | ||||
| return this.imageDao.update(image)>0?"删除成功":"删除失败"; | return this.imageDao.update(image)>0?"删除成功":"删除失败"; | ||||
| @@ -167,27 +180,46 @@ public class ImageServiceImpl implements ImageService { | |||||
| return new PageImpl<>(this.imageDao.queryByName(name)); | return new PageImpl<>(this.imageDao.queryByName(name)); | ||||
| } | } | ||||
| /** | |||||
| * 通过名字精确查询镜像 | |||||
| * | |||||
| * @param name 名字 | |||||
| * @return 镜像 | |||||
| */ | |||||
| @Override | |||||
| public Image getByName(String name) { | |||||
| return this.imageDao.getByName(name); | |||||
| } | |||||
| @Override | @Override | ||||
| @Transactional | @Transactional | ||||
| public String insertImageAndVersion(ImageVo imageVo) throws Exception { | public String insertImageAndVersion(ImageVo imageVo) throws Exception { | ||||
| Image image = new Image(); | |||||
| image.setName(imageVo.getName()); | |||||
| image.setDescription(imageVo.getDescription()); | |||||
| image.setImageType(imageVo.getImageType()); | |||||
| Image imageInsert = this.insert(image); | |||||
| if (imageInsert == null){ | |||||
| throw new Exception("新增镜像失败"); | |||||
| Image existingImage = getByName(imageVo.getName()); | |||||
| Image imageToUse; | |||||
| if(existingImage == null) { | |||||
| // 如果不存在相同名称的镜像,则创建新的镜像记录 | |||||
| Image newImage = new Image(); | |||||
| newImage.setName(imageVo.getName()); | |||||
| newImage.setDescription(imageVo.getDescription()); | |||||
| newImage.setImageType(imageVo.getImageType()); | |||||
| imageToUse = this.insert(newImage); | |||||
| if (imageToUse == null) { | |||||
| throw new Exception("新增镜像失败"); | |||||
| } | |||||
| }else{ | |||||
| // 如果已存在相同名称的镜像,使用已存在的镜像 | |||||
| imageToUse = existingImage; | |||||
| } | } | ||||
| ImageVersion imageVersion = new ImageVersion(); | ImageVersion imageVersion = new ImageVersion(); | ||||
| imageVersion.setImageId(imageInsert.getId()); | |||||
| imageVersion.setImageId(imageToUse.getId()); | |||||
| imageVersion.setVersion(imageVo.getVersion()); | imageVersion.setVersion(imageVo.getVersion()); | ||||
| imageVersion.setUrl(imageVo.getUrl()); | |||||
| imageVersion.setTagName(imageVo.getTagName()); | imageVersion.setTagName(imageVo.getTagName()); | ||||
| imageVersion.setFileSize(imageVo.getFileSize()); | imageVersion.setFileSize(imageVo.getFileSize()); | ||||
| imageVersion.setStatus("building"); | imageVersion.setStatus("building"); | ||||
| ImageVersion imageVersionInsert = this.imageVersionService.insert(imageVersion); | ImageVersion imageVersionInsert = this.imageVersionService.insert(imageVersion); | ||||
| if (imageVersionInsert == null) { | if (imageVersionInsert == null) { | ||||
| throw new Exception("新增镜像失败"); | |||||
| throw new Exception("新增镜像版本失败"); | |||||
| } | } | ||||
| // 使用CompletableFuture异步执行不同的镜像构建逻辑 | // 使用CompletableFuture异步执行不同的镜像构建逻辑 | ||||
| CompletableFuture.supplyAsync(() -> { | CompletableFuture.supplyAsync(() -> { | ||||
| @@ -207,7 +239,7 @@ public class ImageServiceImpl implements ImageService { | |||||
| }).thenAccept(resultMap ->{ | }).thenAccept(resultMap ->{ | ||||
| try { | try { | ||||
| String imageUrl = resultMap.get("url"); | String imageUrl = resultMap.get("url"); | ||||
| String fileSize = resultMap.get("filesize"); | |||||
| String fileSize = resultMap.get("fileSize"); | |||||
| imageVersion.setUrl(imageUrl); | imageVersion.setUrl(imageUrl); | ||||
| imageVersion.setFileSize(fileSize); | imageVersion.setFileSize(fileSize); | ||||
| imageVersion.setStatus("available"); | imageVersion.setStatus("available"); | ||||
| @@ -230,7 +262,8 @@ public class ImageServiceImpl implements ImageService { | |||||
| // 得到容器 | // 得到容器 | ||||
| V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName); | V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName); | ||||
| if (pod == null) { | if (pod == null) { | ||||
| throw new Exception("镜像推送服务不存在"); | |||||
| String podName = deploymentName+"-"+ DateUtils.formatYMD10(new Date()); | |||||
| pod = k8sClientUtil.createPodWithEnv(podName,serviceNS,proxyUrl,mountPath,pvcName,image); | |||||
| } | } | ||||
| String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl; | String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl; | ||||
| // 执行命令 docker login -u admin -p Harbor12345 172.20.32.187 | // 执行命令 docker login -u admin -p Harbor12345 172.20.32.187 | ||||
| @@ -241,18 +274,18 @@ public class ImageServiceImpl implements ImageService { | |||||
| String logs2 = k8sClientUtil.executeCommand(pod,"docker pull "+ netPath); | String logs2 = k8sClientUtil.executeCommand(pod,"docker pull "+ netPath); | ||||
| // 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag | // 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag | ||||
| if (StringUtils.isNoneBlank(logs2)){ | if (StringUtils.isNoneBlank(logs2)){ | ||||
| String substring = logs2.substring(logs2.indexOf(harborUrl), logs2.length()); | |||||
| String cleanedString = substring.replaceAll("(\\r|\\n)", ""); | |||||
| String cmd1 = "docker tag " + cleanedString+ " " + harborUrl+"/"+repository+"/"+username+"/" + imageName + imageTag; | |||||
| String imageUrl = harborUrl+"/"+repository+"/"+username+"/" + imageName + imageTag; | |||||
| String cmd2 = "docker push " + imageUrl; | |||||
| String cmd3 = "docker inspect --format='{{.Size}}' " + imageUrl; | |||||
| String s = k8sClientUtil.executeCommand(pod, cmd1); | |||||
| if (StringUtils.isNotEmpty(k8sClientUtil.executeCommand(pod, cmd2))){ | |||||
| String[] lines = logs2.split("\n"); | |||||
| String lastLine = lines[lines.length - 1].trim(); | |||||
| String tagCmd = "docker tag " + lastLine + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | |||||
| String imageUrl = harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | |||||
| String pushCmd = "docker push " + imageUrl; | |||||
| String sizeCmd = "docker inspect --format='{{.Size}}' " + imageUrl; | |||||
| String s = k8sClientUtil.executeCommand(pod, tagCmd); | |||||
| if (StringUtils.isNotEmpty(k8sClientUtil.executeCommand(pod, pushCmd))){ | |||||
| resultMap.put("url", imageUrl); | resultMap.put("url", imageUrl); | ||||
| //得到镜像文件大小 | //得到镜像文件大小 | ||||
| long sizeInBytes = Long.parseLong(k8sClientUtil.executeCommand(pod, cmd3)); | |||||
| String imageSizeStr = k8sClientUtil.executeCommand(pod, sizeCmd); | |||||
| long sizeInBytes = Long.parseLong(imageSizeStr.trim()); | |||||
| String formattedImageSize = FileUtil.formatFileSize(sizeInBytes); // 格式化镜像文件大小 | String formattedImageSize = FileUtil.formatFileSize(sizeInBytes); // 格式化镜像文件大小 | ||||
| resultMap.put("fileSize", formattedImageSize); | resultMap.put("fileSize", formattedImageSize); | ||||
| return resultMap; | return resultMap; | ||||
| @@ -271,7 +304,8 @@ public class ImageServiceImpl implements ImageService { | |||||
| // 得到容器 | // 得到容器 | ||||
| V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName); | V1Pod pod = k8sClientUtil.getNSPodList(serviceNS, deploymentName); | ||||
| if (pod == null) { | if (pod == null) { | ||||
| throw new Exception("镜像推送服务不存在"); | |||||
| String podName = deploymentName+"-"+ DateUtils.formatYMD10(new Date()); | |||||
| pod = k8sClientUtil.createPodWithEnv(podName,serviceNS,proxyUrl,mountPath,pvcName,image); | |||||
| } | } | ||||
| String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl; | String loginCmd = "docker login -u " + harborUser +" -p "+harborpassword+" "+harborUrl; | ||||
| // 执行命令 docker login -u admin -p Harbor12345 172.20.32.187 | // 执行命令 docker login -u admin -p Harbor12345 172.20.32.187 | ||||
| @@ -283,17 +317,17 @@ public class ImageServiceImpl implements ImageService { | |||||
| String logs2 = k8sClientUtil.executeCommand(pod,"docker load -i "+filePath); | String logs2 = k8sClientUtil.executeCommand(pod,"docker load -i "+filePath); | ||||
| // 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag | // 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag | ||||
| if (StringUtils.isNoneBlank(logs2)){ | if (StringUtils.isNoneBlank(logs2)){ | ||||
| String substring = logs2.substring(logs2.indexOf(harborUrl), logs2.length()); | |||||
| String cleanedString = substring.replaceAll("(\\r|\\n)", ""); | |||||
| String cmd1 = "docker tag " + cleanedString+ " " + harborUrl+"/"+repository+"/"+username+"/" + imageName + imageTag; | |||||
| String imageUrl = harborUrl+"/"+repository+"/"+username+"/" + imageName + imageTag; | |||||
| String cmd2 = "docker push " + imageUrl; | |||||
| String cmd3 = "docker inspect --format='{{.Size}}' " + imageUrl; | |||||
| String s = k8sClientUtil.executeCommand(pod, cmd1); | |||||
| if (StringUtils.isNotEmpty(k8sClientUtil.executeCommand(pod, cmd2))){ | |||||
| String substring = logs2.substring(logs2.indexOf(":")+1).trim(); | |||||
| String tagCmd = "docker tag " + substring + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | |||||
| String imageUrl = harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; | |||||
| String pushCmd = "docker push " + imageUrl; | |||||
| String sizeCmd = "docker inspect --format='{{.Size}}' " + imageUrl; | |||||
| String s = k8sClientUtil.executeCommand(pod, tagCmd); | |||||
| if (StringUtils.isNotEmpty(k8sClientUtil.executeCommand(pod, pushCmd))){ | |||||
| resultMap.put("url", imageUrl); | resultMap.put("url", imageUrl); | ||||
| //得到镜像文件大小 | //得到镜像文件大小 | ||||
| long sizeInBytes = Long.parseLong(k8sClientUtil.executeCommand(pod, cmd3)); | |||||
| String imageSizeStr = k8sClientUtil.executeCommand(pod, sizeCmd); | |||||
| long sizeInBytes = Long.parseLong(imageSizeStr.trim()); | |||||
| String formattedImageSize = FileUtil.formatFileSize(sizeInBytes); // 格式化镜像文件大小 | String formattedImageSize = FileUtil.formatFileSize(sizeInBytes); // 格式化镜像文件大小 | ||||
| resultMap.put("fileSize", formattedImageSize); | resultMap.put("fileSize", formattedImageSize); | ||||
| return resultMap; | return resultMap; | ||||
| @@ -374,6 +374,29 @@ public class HttpUtils { | |||||
| return httpClient; | return httpClient; | ||||
| } | } | ||||
| /** | |||||
| * 发送 HTTP 请求并返回二进制数据流(InputStream)。 | |||||
| * | |||||
| * @param url 请求的 URL 地址。 | |||||
| * @param token 要携带的 Token。 | |||||
| * @return 服务器响应的二进制数据流(InputStream)。 | |||||
| * @throws IOException 如果请求失败或发生其他 I/O 错误。 | |||||
| */ | |||||
| public static InputStream getInputStream(String url, String token) throws IOException { | |||||
| URL requestUrl = new URL(url); | |||||
| HttpURLConnection connection = (HttpURLConnection) requestUrl.openConnection(); | |||||
| connection.setRequestMethod("GET"); | |||||
| connection.setRequestProperty("Authorization", "Bearer " + token); // 添加 Authorization 头部,携带 Token | |||||
| int responseCode = connection.getResponseCode(); | |||||
| if (responseCode == HttpURLConnection.HTTP_OK) { | |||||
| return connection.getInputStream(); // 获取响应的输入流 | |||||
| } else { | |||||
| throw new IOException("HTTP 请求失败,状态码:" + responseCode); | |||||
| } | |||||
| } | |||||
| private static class TrustAnyTrustManager implements X509TrustManager { | private static class TrustAnyTrustManager implements X509TrustManager { | ||||
| @Override | @Override | ||||
| public void checkClientTrusted(X509Certificate[] chain, String authType) { | public void checkClientTrusted(X509Certificate[] chain, String authType) { | ||||
| @@ -378,7 +378,7 @@ public class K8sClientUtil { | |||||
| // invokes the CoreV1Api client | // invokes the CoreV1Api client | ||||
| for (V1Pod item : v1PodList.getItems()) { | for (V1Pod item : v1PodList.getItems()) { | ||||
| String generateName = item.getMetadata().getGenerateName(); | |||||
| String generateName = item.getMetadata().getName(); | |||||
| if (StringUtils.isNotEmpty(generateName) && generateName.startsWith(deploymentName)) { | if (StringUtils.isNotEmpty(generateName) && generateName.startsWith(deploymentName)) { | ||||
| // 找到匹配的Pod,获取其名称 | // 找到匹配的Pod,获取其名称 | ||||
| return item; | return item; | ||||
| @@ -402,10 +402,16 @@ public class K8sClientUtil { | |||||
| builder.append(line); | builder.append(line); | ||||
| builder.append(System.getProperty("line.separator")); | builder.append(System.getProperty("line.separator")); | ||||
| } | } | ||||
| // 等待进程结束,并获取退出值 | |||||
| int exitValue = proc.waitFor(); | |||||
| if (exitValue != 0) { | |||||
| // 如果进程的退出值不为0,表示命令执行失败 | |||||
| throw new RuntimeException("容器中命令执行失败,退出值:" + exitValue); | |||||
| } | |||||
| return builder.toString(); | return builder.toString(); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| log.error("执行命令异常", e); | |||||
| throw new RuntimeException("执行命令异常"); | |||||
| log.error("容器执行命令异常", e); | |||||
| throw new RuntimeException("容器执行命令异常"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -430,4 +436,46 @@ public class K8sClientUtil { | |||||
| } | } | ||||
| } | } | ||||
| public V1Pod createPodWithEnv(String podName,String namespace,String proxyUrl ,String mountPath,String pvcName, String image){ | |||||
| CoreV1Api api = new CoreV1Api(apiClient); | |||||
| V1PodList v1PodList = null; | |||||
| V1Pod pod = new V1PodBuilder() | |||||
| .withNewMetadata() | |||||
| .withName(podName) | |||||
| .endMetadata() | |||||
| .withNewSpec() | |||||
| .addNewContainer() | |||||
| .withName(podName) | |||||
| .withImage(image) // 替换为您实际要使用的镜像名称 | |||||
| .withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath)) | |||||
| .withNewSecurityContext().withNewPrivileged(true).endSecurityContext() | |||||
| .addNewEnv() | |||||
| .withName("HTTP_PROXY") | |||||
| .withValue(proxyUrl) | |||||
| .endEnv() | |||||
| .addNewEnv() | |||||
| .withName("HTTPS_PROXY") | |||||
| .withValue(proxyUrl) | |||||
| .endEnv() | |||||
| .addNewEnv() | |||||
| .withName("NO_PROXY") | |||||
| .withValue("localhost,kubernetes.default.svc") | |||||
| .endEnv() | |||||
| .endContainer() | |||||
| .addNewVolume() | |||||
| .withName("workspace").withPersistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvcName)) | |||||
| .endVolume() | |||||
| .endSpec() | |||||
| .build(); | |||||
| try { | |||||
| pod = api.createNamespacedPod(namespace, pod, null, null, null); | |||||
| } catch (ApiException e) { | |||||
| log.error("创建pod异常:" + e.getResponseBody(), e); | |||||
| } catch (Exception e) { | |||||
| log.error("创建pod系统异常:", e); | |||||
| } | |||||
| return pod; | |||||
| } | |||||
| } | } | ||||
| @@ -33,11 +33,13 @@ public class ImageVo implements Serializable { | |||||
| */ | */ | ||||
| @ApiModelProperty(name = "version") | @ApiModelProperty(name = "version") | ||||
| private String version; | private String version; | ||||
| /** | /** | ||||
| * 镜像推送地址 | * 镜像推送地址 | ||||
| */ | */ | ||||
| @ApiModelProperty(name = "url") | |||||
| private String url; | |||||
| // @ApiModelProperty(name = "url") | |||||
| // private String url; | |||||
| /** | /** | ||||
| * 镜像tag名称 | * 镜像tag名称 | ||||
| */ | */ | ||||
| @@ -102,13 +104,13 @@ public class ImageVo implements Serializable { | |||||
| this.version = version; | this.version = version; | ||||
| } | } | ||||
| public String getUrl() { | |||||
| return url; | |||||
| } | |||||
| public void setUrl(String url) { | |||||
| this.url = url; | |||||
| } | |||||
| // public String getUrl() { | |||||
| // return url; | |||||
| // } | |||||
| // | |||||
| // public void setUrl(String url) { | |||||
| // this.url = url; | |||||
| // } | |||||
| public String getTagName() { | public String getTagName() { | ||||
| return tagName; | return tagName; | ||||