Browse Source

feat: 统一icon的使用方式

pull/30/head
cp3hnu 1 year ago
parent
commit
4ee94e2960
72 changed files with 713 additions and 446 deletions
  1. +11
    -2
      react-ui/config/config.ts
  2. +5
    -0
      react-ui/config/proxy.ts
  3. +0
    -11
      react-ui/jsconfig.json
  4. BIN
      react-ui/public/assets/images/backup/component-icon-7-Failed.png
  5. BIN
      react-ui/public/assets/images/backup/component-icon-7-Omitted.png
  6. BIN
      react-ui/public/assets/images/backup/component-icon-7-Pending.png
  7. BIN
      react-ui/public/assets/images/backup/component-icon-7-Running.png
  8. BIN
      react-ui/public/assets/images/backup/component-icon-7-Skipped.png
  9. BIN
      react-ui/public/assets/images/backup/component-icon-7-Succeeded.png
  10. BIN
      react-ui/public/assets/images/backup/component-icon-7.png
  11. BIN
      react-ui/public/assets/images/component-icon-7-Failed.png
  12. BIN
      react-ui/public/assets/images/component-icon-7-Omitted.png
  13. BIN
      react-ui/public/assets/images/component-icon-7-Pending.png
  14. BIN
      react-ui/public/assets/images/component-icon-7-Running.png
  15. BIN
      react-ui/public/assets/images/component-icon-7-Skipped.png
  16. BIN
      react-ui/public/assets/images/component-icon-7-Succeeded.png
  17. BIN
      react-ui/public/assets/images/component-icon-7.png
  18. BIN
      react-ui/public/assets/images/component-icon-8-Failed.png
  19. BIN
      react-ui/public/assets/images/component-icon-8-Omitted.png
  20. BIN
      react-ui/public/assets/images/component-icon-8-Pending.png
  21. BIN
      react-ui/public/assets/images/component-icon-8-Running.png
  22. BIN
      react-ui/public/assets/images/component-icon-8-Skipped.png
  23. BIN
      react-ui/public/assets/images/component-icon-8-Succeeded.png
  24. BIN
      react-ui/public/assets/images/component-icon-8.png
  25. +33
    -5
      react-ui/src/app.tsx
  26. BIN
      react-ui/src/assets/img/modal-select-mirror.png
  27. +0
    -1
      react-ui/src/assets/svg/modal-close.svg
  28. +0
    -1
      react-ui/src/assets/svg/parameter.svg
  29. +0
    -1
      react-ui/src/assets/svg/save--return.svg
  30. +0
    -1
      react-ui/src/assets/svg/view-param.svg
  31. +4
    -4
      react-ui/src/components/KFRadio/index.less
  32. +1
    -1
      react-ui/src/components/ModalTitle/index.less
  33. +7
    -0
      react-ui/src/enums/index.ts
  34. +41
    -60
      react-ui/src/global.less
  35. +40
    -20
      react-ui/src/hooks/index.ts
  36. +1
    -1
      react-ui/src/iconfont/iconfont.js
  37. +0
    -1
      react-ui/src/icons/add.svg
  38. +0
    -1
      react-ui/src/icons/dataset-select-button.svg
  39. +0
    -1
      react-ui/src/icons/doc-not-inventory.svg
  40. +0
    -1
      react-ui/src/icons/mirror-select-button.svg
  41. +0
    -1
      react-ui/src/icons/modal-close.svg
  42. +0
    -1
      react-ui/src/icons/model-select-button.svg
  43. +0
    -1
      react-ui/src/icons/parameter.svg
  44. +0
    -1
      react-ui/src/icons/private-mirror-tab.svg
  45. +0
    -1
      react-ui/src/icons/public-mirror-tab.svg
  46. +0
    -1
      react-ui/src/icons/refresh.svg
  47. +0
    -1
      react-ui/src/icons/remove.svg
  48. +0
    -1
      react-ui/src/icons/view-detail.svg
  49. +0
    -1
      react-ui/src/icons/view-param.svg
  50. +0
    -1
      react-ui/src/icons/下载.svg
  51. +0
    -1
      react-ui/src/icons/查看详情.svg
  52. +10
    -0
      react-ui/src/overrides.less
  53. +1
    -6
      react-ui/src/pages/Experiment/experimentText/index.jsx
  54. +68
    -85
      react-ui/src/pages/Experiment/index.jsx
  55. +4
    -1
      react-ui/src/pages/Experiment/status.ts
  56. +13
    -0
      react-ui/src/pages/Mirror/components/MirrorStatusCell/index.less
  57. +40
    -0
      react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx
  58. +4
    -1
      react-ui/src/pages/Mirror/create.tsx
  59. +1
    -1
      react-ui/src/pages/Mirror/info.less
  60. +37
    -39
      react-ui/src/pages/Mirror/info.tsx
  61. +0
    -2
      react-ui/src/pages/Mirror/list.less
  62. +42
    -30
      react-ui/src/pages/Mirror/list.tsx
  63. +3
    -7
      react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less
  64. +107
    -41
      react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx
  65. +23
    -10
      react-ui/src/pages/Pipeline/editPipeline/globalParamsDrawer.tsx
  66. +4
    -6
      react-ui/src/pages/Pipeline/editPipeline/index.jsx
  67. +80
    -27
      react-ui/src/pages/Pipeline/editPipeline/props.jsx
  68. +46
    -53
      react-ui/src/pages/Pipeline/index.jsx
  69. +13
    -0
      react-ui/src/services/pipeline/index.js
  70. +21
    -6
      react-ui/src/styles/theme.less
  71. +25
    -8
      react-ui/src/utils/modal.tsx
  72. +28
    -0
      react-ui/src/utils/ui.tsx

+ 11
- 2
react-ui/config/config.ts View File

@@ -1,12 +1,14 @@
// https://umijs.org/config/ // https://umijs.org/config/
import { defineConfig } from '@umijs/max'; import { defineConfig } from '@umijs/max';
import { join } from 'path';
import { join, resolve } from 'path';
import defaultSettings from './defaultSettings'; import defaultSettings from './defaultSettings';
import proxy from './proxy'; import proxy from './proxy';
import routes from './routes'; import routes from './routes';


const { REACT_APP_ENV = 'dev' } = process.env; const { REACT_APP_ENV = 'dev' } = process.env;


console.log('zzz', resolve(__dirname, '../src/styles/theme.less'));

export default defineConfig({ export default defineConfig({
/** /**
* @name 开启 hash 模式 * @name 开启 hash 模式
@@ -39,7 +41,8 @@ export default defineConfig({
theme: { theme: {
// 如果不想要 configProvide 动态设置主题需要把这个设置为 default // 如果不想要 configProvide 动态设置主题需要把这个设置为 default
// 只有设置为 variable, 才能使用 configProvide 动态设置主色调 // 只有设置为 variable, 才能使用 configProvide 动态设置主色调
'root-entry-name': 'variable',
// 'root-entry-name': 'variable',
'kf-success-color': '#ff0000',
}, },
/** /**
* @name moment 的国际化配置 * @name moment 的国际化配置
@@ -157,4 +160,10 @@ export default defineConfig({
}, },
requestRecord: {}, requestRecord: {},
icons: {}, icons: {},
lessLoader: {
modifyVars: {
hack: 'true; @import "@/styles/theme.less";',
},
javascriptEnabled: true,
},
}); });

+ 5
- 0
react-ui/config/proxy.ts View File

@@ -1,3 +1,8 @@
/*
* @Author: 赵伟
* @Date: 2024-04-17 08:48:09
* @Description:
*/
/** /**
* @name 代理的配置 * @name 代理的配置
* @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 * @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置


+ 0
- 11
react-ui/jsconfig.json View File

@@ -1,11 +0,0 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

BIN
react-ui/public/assets/images/backup/component-icon-7-Failed.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.2 kB

BIN
react-ui/public/assets/images/backup/component-icon-7-Omitted.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.1 kB

BIN
react-ui/public/assets/images/backup/component-icon-7-Pending.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.2 kB

BIN
react-ui/public/assets/images/backup/component-icon-7-Running.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.1 kB

BIN
react-ui/public/assets/images/backup/component-icon-7-Skipped.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.1 kB

BIN
react-ui/public/assets/images/backup/component-icon-7-Succeeded.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.3 kB

BIN
react-ui/public/assets/images/backup/component-icon-7.png View File

Before After
Width: 72  |  Height: 72  |  Size: 1.4 kB

BIN
react-ui/public/assets/images/component-icon-7-Failed.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.2 kB Width: 108  |  Height: 108  |  Size: 6.5 kB

BIN
react-ui/public/assets/images/component-icon-7-Omitted.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.1 kB Width: 108  |  Height: 108  |  Size: 6.5 kB

BIN
react-ui/public/assets/images/component-icon-7-Pending.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.2 kB Width: 108  |  Height: 108  |  Size: 6.7 kB

BIN
react-ui/public/assets/images/component-icon-7-Running.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.1 kB Width: 108  |  Height: 108  |  Size: 6.4 kB

BIN
react-ui/public/assets/images/component-icon-7-Skipped.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.1 kB Width: 108  |  Height: 108  |  Size: 6.5 kB

BIN
react-ui/public/assets/images/component-icon-7-Succeeded.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.3 kB Width: 108  |  Height: 108  |  Size: 6.8 kB

BIN
react-ui/public/assets/images/component-icon-7.png View File

Before After
Width: 72  |  Height: 72  |  Size: 1.4 kB Width: 54  |  Height: 54  |  Size: 3.8 kB

BIN
react-ui/public/assets/images/component-icon-8-Failed.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.3 kB

BIN
react-ui/public/assets/images/component-icon-8-Omitted.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.3 kB

BIN
react-ui/public/assets/images/component-icon-8-Pending.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.5 kB

BIN
react-ui/public/assets/images/component-icon-8-Running.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.3 kB

BIN
react-ui/public/assets/images/component-icon-8-Skipped.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.3 kB

BIN
react-ui/public/assets/images/component-icon-8-Succeeded.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.6 kB

BIN
react-ui/public/assets/images/component-icon-8.png View File

Before After
Width: 54  |  Height: 54  |  Size: 1.3 kB

+ 33
- 5
react-ui/src/app.tsx View File

@@ -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,38 @@ 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'],
// fontSize: themes['fontSize'],
}; };
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: themes['fontSize'],
paddingBlock: 4,
paddingInline: 15,
controlHeight: 34,
};
memo.theme.components.Input = {
inputFontSize: themes['fontSize'],
paddingBlock: 4,
paddingInline: 15,
};
memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4,
};
memo.theme.cssVar = true;
// memo.theme.hashed = false; // memo.theme.hashed = false;


// memo.appConfig = { // memo.appConfig = {


BIN
react-ui/src/assets/img/modal-select-mirror.png View File

Before After
Width: 65  |  Height: 52  |  Size: 1.4 kB

+ 0
- 1
react-ui/src/assets/svg/modal-close.svg View File

@@ -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>

+ 0
- 1
react-ui/src/assets/svg/parameter.svg View File

@@ -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>

+ 0
- 1
react-ui/src/assets/svg/save--return.svg View File

@@ -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>

+ 0
- 1
react-ui/src/assets/svg/view-param.svg View File

@@ -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>

+ 4
- 4
react-ui/src/components/KFRadio/index.less View File

@@ -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;


@@ -18,8 +18,8 @@
} }


&--active { &--active {
color: @kf-primary-color;
border: 1px solid @kf-primary-color;
color: @primary-color;
border: 1px solid @primary-color;
} }


& + & { & + & {


+ 1
- 1
react-ui/src/components/ModalTitle/index.less View File

@@ -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;




+ 7
- 0
react-ui/src/enums/index.ts View File

@@ -3,3 +3,10 @@ export enum CommonTabKeys {
Private = 'Private', // 私有 Private = 'Private', // 私有
Public = 'Public', // 公开 Public = 'Public', // 公开
} }

// 镜像状态
export enum MirrorVersionStatus {
Available = 'available', // 可用
Building = 'building', // 构建中
Failed = 'failed', // 构建中
}

+ 41
- 60
react-ui/src/global.less View File

@@ -31,54 +31,42 @@ 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-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 +78,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 {
@@ -119,7 +107,7 @@ font-size:16px;
.ant-pro-layout .ant-pro-layout-container { .ant-pro-layout .ant-pro-layout-container {
height: 98vh; height: 98vh;
} }
.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,36 +118,35 @@ 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 {
@@ -176,13 +163,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,8 +182,6 @@ 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;
border-radius: 6px; border-radius: 6px;
} }
.ant-pagination .ant-pagination-item-active:hover { .ant-pagination .ant-pagination-item-active:hover {
@@ -259,7 +244,3 @@ ol {
} }
} }
} }

.umi-local-svg {
vertical-align: -1px;
}

+ 40
- 20
react-ui/src/hooks/index.ts View File

@@ -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
- 1
react-ui/src/iconfont/iconfont.js
File diff suppressed because it is too large
View File


+ 0
- 1
react-ui/src/icons/add.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/dataset-select-button.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/doc-not-inventory.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/mirror-select-button.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/modal-close.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/model-select-button.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/parameter.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/private-mirror-tab.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/public-mirror-tab.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/refresh.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/remove.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/view-detail.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/view-param.svg View File

@@ -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
- 1
react-ui/src/icons/下载.svg View File

@@ -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>

+ 0
- 1
react-ui/src/icons/查看详情.svg View File

@@ -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>

+ 10
- 0
react-ui/src/overrides.less View File

@@ -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
- 6
react-ui/src/pages/Experiment/experimentText/index.jsx View File

@@ -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>


+ 68
- 85
react-ui/src/pages/Experiment/index.jsx View File

@@ -1,3 +1,4 @@
import KFIcon from '@/components/KFIcon';
import { import {
deleteExperimentById, deleteExperimentById,
deleteQueryByExperimentInsId, deleteQueryByExperimentInsId,
@@ -12,16 +13,11 @@ 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 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';
@@ -323,7 +319,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,61 +330,45 @@ 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>
), ),
}, },
@@ -405,7 +385,7 @@ function Experiment() {
type="primary" type="primary"
className={Styles.plusButton} className={Styles.plusButton}
onClick={createExperiment} onClick={createExperiment}
icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xinjian2" />}
> >
新建实验 新建实验
</Button> </Button>
@@ -485,7 +465,7 @@ function Experiment() {
item.status === 'Failed' || item.status === 'Failed' ||
item.status === 'Terminated' item.status === 'Terminated'
} }
icon={<FieldTimeOutlined />}
icon={<KFIcon type="icon-zhongzhi" />}
onClick={async () => { onClick={async () => {
putQueryByExperimentInsId(item.id).then((ret) => { putQueryByExperimentInsId(item.id).then((ret) => {
if (ret.code === 200) { if (ret.code === 200) {
@@ -499,35 +479,38 @@ function Experiment() {
> >
终止 终止
</Button> </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);
}
});
},
});
<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>
)) ))


+ 4
- 1
react-ui/src/pages/Experiment/status.ts View File

@@ -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',


+ 13
- 0
react-ui/src/pages/Mirror/components/MirrorStatusCell/index.less View File

@@ -0,0 +1,13 @@
//@import '@/styles/theme.less';

.mirror-status-cell {
color: @text-color;

&--success {
color: @success-color;
}

&--error {
color: @error-color;
}
}

+ 40
- 0
react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx View File

@@ -0,0 +1,40 @@
/*
* @Author: 赵伟
* @Date: 2024-04-18 18:35:41
* @Description:
*/
import { MirrorVersionStatus } from '@/enums';
import styles from './index.less';

type MirrorStatusCellProps = {
status: MirrorVersionStatus;
};

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 }: MirrorStatusCellProps) {
return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>;
}

export default MirrorStatusCell;

+ 4
- 1
react-ui/src/pages/Mirror/create.tsx View File

@@ -1,8 +1,9 @@
/* /*
* @Author: 赵伟 * @Author: 赵伟
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 镜像详情
* @Description: 创建镜像
*/ */
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';
@@ -25,10 +26,12 @@ 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" />,
}, },
]; ];




+ 1
- 1
react-ui/src/pages/Mirror/info.less View File

@@ -12,7 +12,7 @@


.label { .label {
width: 80px; width: 80px;
color: @text-color-second;
color: @text-color-secondary;
} }


.value { .value {


+ 37
- 39
react-ui/src/pages/Mirror/info.tsx View File

@@ -3,16 +3,20 @@
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 镜像详情 * @Description: 镜像详情
*/ */
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle'; import SubAreaTitle from '@/components/SubAreaTitle';
import { MirrorVersionStatus } from '@/enums';
import { useDomSize } from '@/hooks'; import { useDomSize } from '@/hooks';
import { getMirrorInfoReq, getMirrorVersionListReq } from '@/services/mirror'; import { 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 { useParams, useSearchParams } from '@umijs/max';
import { Button, Col, Row, Table, TablePaginationConfig, TableProps } from 'antd';
import { Button, Col, ConfigProvider, Row, Table, TablePaginationConfig, 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 = {
@@ -36,18 +40,20 @@ function MirrorInfo() {
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 = seachParams.get('isPublic') === 'true';
useEffect(() => { useEffect(() => {
getMirrorInfo(); getMirrorInfo();
getMirrorVersionList();
}, []); }, []);
useEffect(() => {
getMirrorVersionList();
}, [pagination]);


// 获取镜像详情 // 获取镜像详情
const getMirrorInfo = async () => { const getMirrorInfo = async () => {
@@ -77,10 +83,7 @@ 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);
} }
}; };


@@ -88,9 +91,7 @@ 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 downloadVersion = (record: MirrorVersionData) => {};
@@ -99,64 +100,61 @@ function MirrorInfo() {
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%',
}, },
{ {
title: '镜像地址', title: '镜像地址',
dataIndex: 'url', dataIndex: 'url',
key: 'url', key: 'url',
width: '26%',
}, },
{ {
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: '7%',
width: 150,
render: (text: string) => <MirrorStatusCell status={text as MirrorVersionStatus} />,
}, },
{ {
title: '镜像大小', title: '镜像大小',
dataIndex: 'file_size', dataIndex: 'file_size',
key: 'file_size', key: 'file_size',
width: '7%',
width: 150,
}, },
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'create_time', dataIndex: 'create_time',
key: 'create_time', key: 'create_time',
width: '20%',
width: 200,
render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>, render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
width: '20%',
width: 150,
key: 'operation', key: 'operation',
hidden: isPublic,
render: (_: any, record: any) => ( render: (_: any, record: any) => (
<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={() => removeVersion(record)}
>
删除
</Button>
</ConfigProvider>
)} )}
</div> </div>
), ),
@@ -184,7 +182,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>
@@ -218,7 +216,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"
/> />


+ 0
- 2
react-ui/src/pages/Mirror/list.less View File

@@ -1,5 +1,3 @@
@import '@/styles/theme.less';

.mirror-list { .mirror-list {
height: 100%; height: 100%;
&__tabs-container { &__tabs-container {


+ 42
- 30
react-ui/src/pages/Mirror/list.tsx View File

@@ -6,9 +6,18 @@
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums'; import { CommonTabKeys } from '@/enums';
import { getMirrorListReq } from '@/services/mirror'; import { 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 { useNavigate } from '@umijs/max';
import {
Button,
ConfigProvider,
Input,
Table,
TablePaginationConfig,
TableProps,
Tabs,
} 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';
@@ -18,12 +27,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" />,
}, },
]; ];


@@ -39,16 +48,16 @@ function MirrorList() {
const [activeTab, setActiveTab] = useState('Public'); const [activeTab, setActiveTab] = useState('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]);


// 获取镜像列表 // 获取镜像列表
const getMirrorList = async () => { const getMirrorList = async () => {
@@ -62,10 +71,7 @@ function MirrorList() {
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);
} }
}; };


@@ -82,6 +88,9 @@ function MirrorList() {
}); });
}; };


// 删除镜像
const deleteMirror = (record: MirrorData) => {};

// 创建镜像 // 创建镜像
const createMirror = () => { const createMirror = () => {
navgite({ pathname: `/dataset/mirror/create?isPublic=${activeTab === CommonTabKeys.Public}` }); navgite({ pathname: `/dataset/mirror/create?isPublic=${activeTab === CommonTabKeys.Public}` });
@@ -91,7 +100,6 @@ function MirrorList() {
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);
}; };
@@ -101,32 +109,31 @@ function MirrorList() {
title: '镜像名称', title: '镜像名称',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width: '10%',
width: '30%',
}, },
{ {
title: '版本数据', title: '版本数据',
dataIndex: 'version_count', dataIndex: 'version_count',
key: 'version_count', key: 'version_count',
width: '10%',
width: 100,
}, },
{ {
title: '镜像描述', title: '镜像描述',
dataIndex: 'description', dataIndex: 'description',
key: 'description', key: 'description',
width: '50%',
//ellipsis: true, //ellipsis: true,
}, },
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'create_time', dataIndex: 'create_time',
key: 'create_time', key: 'create_time',
width: '20%',
width: 200,
render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>, render: (text: string) => <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
}, },
{ {
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: any) => (
<div> <div>
@@ -134,24 +141,29 @@ function MirrorList() {
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={() => deleteMirror(record)}
>
删除
</Button>
</ConfigProvider>
)} )}
</div> </div>
), ),
@@ -183,7 +195,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 +204,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 +214,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"
/> />


+ 3
- 7
react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less View File

@@ -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;
} }


+ 107
- 41
react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx View File

@@ -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;




+ 23
- 10
react-ui/src/pages/Pipeline/editPipeline/globalParamsDrawer.tsx View File

@@ -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>
); );
}, },


+ 4
- 6
react-ui/src/pages/Pipeline/editPipeline/index.jsx View File

@@ -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);
}} }}


+ 80
- 27
react-ui/src/pages/Pipeline/editPipeline/props.jsx View File

@@ -1,9 +1,10 @@
import KFIcon from '@/components/KFIcon';
import { getComputingResourceReq } from '@/services/pipeline';
import { pick } from '@/utils/index'; import { pick } from '@/utils/index';
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 { 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 />


+ 46
- 53
react-ui/src/pages/Pipeline/index.jsx View File

@@ -1,3 +1,4 @@
import KFIcon from '@/components/KFIcon';
import { import {
addWorkflow, addWorkflow,
cloneWorkflow, cloneWorkflow,
@@ -6,8 +7,9 @@ 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 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';
@@ -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);
@@ -152,7 +154,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 +165,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 +175,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,54 +194,45 @@ 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>
), ),
}, },
@@ -251,7 +244,7 @@ const Pipeline = () => {
type="primary" type="primary"
className={Styles.plusButton} className={Styles.plusButton}
onClick={showModal} onClick={showModal}
icon={<PlusCircleOutlined style={{ color: '#1664ff' }} />}
icon={<KFIcon type="icon-xinjian2" />}
> >
新建流水线 新建流水线
</Button> </Button>


+ 13
- 0
react-ui/src/services/pipeline/index.js View File

@@ -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
});
}

+ 21
- 6
react-ui/src/styles/theme.less View File

@@ -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;
} }

+ 25
- 8
react-ui/src/utils/modal.tsx View File

@@ -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 });


+ 28
- 0
react-ui/src/utils/ui.tsx View File

@@ -0,0 +1,28 @@
/*
* @Author: 赵伟
* @Date: 2024-04-19 14:42:51
* @Description: UI 公共方法
*/
import themes from '@/styles/theme.less';
import { Modal, type ModalFuncProps } 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' }}>{title}</div>
</div>
),
content: content && <div style={{ color: themes.textColor, fontSize: '15px' }}>{content}</div>,
okText: '确认',
cancelText: '取消',
onOk: onOk,
});
}

Loading…
Cancel
Save