Browse Source

feat: 完成工作空间

pull/43/head
cp3hnu 1 year ago
parent
commit
e17bf0281d
66 changed files with 1422 additions and 126 deletions
  1. +1
    -3
      react-ui/config/config.ts
  2. +19
    -11
      react-ui/config/routes.ts
  3. +2
    -0
      react-ui/package.json
  4. BIN
      react-ui/public/assets/材料科研软件平台使用文档.pdf
  5. +33
    -4
      react-ui/src/app.tsx
  6. BIN
      react-ui/src/assets/img/avatar-default.png
  7. BIN
      react-ui/src/assets/img/blue-triangle.png
  8. BIN
      react-ui/src/assets/img/functional-material.png
  9. BIN
      react-ui/src/assets/img/molecular-material.png
  10. BIN
      react-ui/src/assets/img/user-avatar/1.png
  11. BIN
      react-ui/src/assets/img/user-avatar/2.png
  12. BIN
      react-ui/src/assets/img/user-avatar/3.png
  13. BIN
      react-ui/src/assets/img/user-avatar/4.png
  14. BIN
      react-ui/src/assets/img/user-avatar/5.png
  15. BIN
      react-ui/src/assets/img/user-avatar/6.png
  16. BIN
      react-ui/src/assets/img/user-avatar/7.png
  17. BIN
      react-ui/src/assets/img/user-avatar/8.png
  18. BIN
      react-ui/src/assets/img/workspace-experiment.png
  19. BIN
      react-ui/src/assets/img/workspace-intro-icon.png
  20. BIN
      react-ui/src/assets/img/workspace-intro.png
  21. BIN
      react-ui/src/assets/img/workspace-pipeline.png
  22. BIN
      react-ui/src/assets/img/workspace-quick-start.png
  23. BIN
      react-ui/src/assets/img/workspace-user.png
  24. +6
    -0
      react-ui/src/components/CommonTableCell/index.tsx
  25. +7
    -1
      react-ui/src/components/DateTableCell/index.tsx
  26. +1
    -1
      react-ui/src/components/KFRadio/index.tsx
  27. +6
    -0
      react-ui/src/components/ModalTitle/index.tsx
  28. +9
    -46
      react-ui/src/global.less
  29. +2
    -2
      react-ui/src/hooks/index.ts
  30. +1
    -1
      react-ui/src/iconfont/iconfont.js
  31. +4
    -0
      react-ui/src/overrides.less
  32. +10
    -10
      react-ui/src/pages/Dataset/personalData.jsx
  33. +10
    -10
      react-ui/src/pages/Dataset/publicData.jsx
  34. +0
    -0
      react-ui/src/pages/Docs/index.less
  35. +9
    -0
      react-ui/src/pages/Docs/index.tsx
  36. +9
    -7
      react-ui/src/pages/Experiment/index.less
  37. +1
    -1
      react-ui/src/pages/Experiment/status.ts
  38. +10
    -10
      react-ui/src/pages/Model/personalData.jsx
  39. +10
    -10
      react-ui/src/pages/Model/publicData.jsx
  40. +4
    -4
      react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less
  41. +54
    -0
      react-ui/src/pages/Workspace/components/AssetsManagement/index.less
  42. +81
    -0
      react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx
  43. +7
    -0
      react-ui/src/pages/Workspace/components/ExperimentChart/index.less
  44. +214
    -0
      react-ui/src/pages/Workspace/components/ExperimentChart/index.tsx
  45. +58
    -0
      react-ui/src/pages/Workspace/components/ExperimentTable/index.less
  46. +59
    -0
      react-ui/src/pages/Workspace/components/ExperimentTable/index.tsx
  47. +60
    -0
      react-ui/src/pages/Workspace/components/QuickStart/WorkArrow.tsx
  48. +31
    -0
      react-ui/src/pages/Workspace/components/QuickStart/WorkFlow.tsx
  49. +96
    -0
      react-ui/src/pages/Workspace/components/QuickStart/index.less
  50. +149
    -0
      react-ui/src/pages/Workspace/components/QuickStart/index.tsx
  51. +41
    -0
      react-ui/src/pages/Workspace/components/TotalStatistics/index.less
  52. +25
    -0
      react-ui/src/pages/Workspace/components/TotalStatistics/index.tsx
  53. +65
    -0
      react-ui/src/pages/Workspace/components/UserSpace/index.less
  54. +47
    -0
      react-ui/src/pages/Workspace/components/UserSpace/index.tsx
  55. +43
    -0
      react-ui/src/pages/Workspace/components/WorkspaceIntro/index.less
  56. +43
    -0
      react-ui/src/pages/Workspace/components/WorkspaceIntro/index.tsx
  57. +45
    -0
      react-ui/src/pages/Workspace/index.less
  58. +71
    -0
      react-ui/src/pages/Workspace/index.tsx
  59. +2
    -1
      react-ui/src/requestConfig.ts
  60. +3
    -0
      react-ui/src/services/ant-design-pro/typings.d.ts
  61. +1
    -1
      react-ui/src/services/mirror/index.ts
  62. +21
    -0
      react-ui/src/services/workspace/index.ts
  63. +23
    -2
      react-ui/src/styles/theme.less
  64. +16
    -0
      react-ui/src/types.ts
  65. +12
    -0
      react-ui/src/utils/date.ts
  66. +1
    -1
      react-ui/src/utils/ui.tsx

+ 1
- 3
react-ui/config/config.ts View File

@@ -78,7 +78,7 @@ export default defineConfig({
*/
title: '智能软件开发平台',
layout: {
locale: true,
locale: false,
...defaultSettings,
},
// keepalive: [/./],
@@ -97,10 +97,8 @@ export default defineConfig({
* @doc https://umijs.org/docs/max/i18n
*/
locale: {
// default zh-CN
default: 'zh-CN',
antd: true,
// default true, when it is true, will use `navigator.language` overwrite default
baseNavigator: true,
},
/**


+ 19
- 11
react-ui/config/routes.ts View File

@@ -13,7 +13,7 @@
export default [
{
path: '/',
redirect: '/account/center',
redirect: '/workspace',
},
{
path: '*',
@@ -88,13 +88,6 @@ export default [
},
],
},
{
name: 'experiment',
path: '/experiment',
routes: [
],
},
{
name: 'developmentEnvironment',
path: '/developmentEnvironment',
@@ -169,7 +162,7 @@ export default [
},
],
},
{
name: 'workspace',
path: '/workspace',
@@ -177,7 +170,8 @@ export default [
{
name: '工作空间',
path: '',
component: './missingPage.jsx',
key: 'workspace',
component: './Workspace/index',
},
],
},
@@ -188,11 +182,11 @@ export default [
{
name: '模型部署',
path: '',
key: 'modelDseployment',
component: './missingPage.jsx',
},
],
},
{
name: 'appsDeployment',
path: '/appsDeployment',
@@ -200,6 +194,7 @@ export default [
{
name: '应用开发',
path: '',
key: 'appsDeployment',
component: './missingPage.jsx',
},
],
@@ -211,6 +206,7 @@ export default [
{
name: '监控运维',
path: '',
key: 'see',
component: './missingPage.jsx',
},
],
@@ -242,4 +238,16 @@ export default [
},
],
},
{
name: 'docs',
path: '/docs',
routes: [
{
name: '使用指南',
path: '',
key: 'docs',
component: './Docs/index',
},
],
},
];

+ 2
- 0
react-ui/package.json View File

@@ -60,6 +60,7 @@
"@umijs/route-utils": "^4.0.1",
"antd": "^5.4.4",
"classnames": "^2.3.2",
"echarts": "^5.5.0",
"fabric": "^5.3.0",
"highlight.js": "^11.7.0",
"lodash": "^4.17.21",
@@ -70,6 +71,7 @@
"rc-menu": "^9.8.4",
"rc-util": "^5.30.0",
"react": "^18.2.0",
"react-activation": "^0.12.4",
"react-cropper": "^2.3.3",
"react-dev-inspector": "^1.8.1",
"react-dom": "^18.2.0",


BIN
react-ui/public/assets/材料科研软件平台使用文档.pdf View File


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

@@ -35,13 +35,12 @@ export async function getInitialState(): Promise<{
const response = await getUserInfo({
skipErrorHandler: true,
});
response.user.avatar =
response.user.avatar ||
'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png';
return {
...response.user,
avatar: response.user.avatar || require('@/assets/img/avatar-default.png'),
permissions: response.permissions,
roles: response.roles,
roleNames: response.user.roles,
} as API.CurrentUser;
} catch (error) {
console.log(error);
@@ -128,7 +127,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
// </Link>,
// ]
// : [],
menuHeaderRender: undefined,
menuHeaderRender: false,
// 自定义 403 页面
// unAccessible: <div>unAccessible</div>,
// 增加一个 loading 的状态
@@ -138,10 +137,40 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
},
menuProps: {
onClick: () => {
// 点击菜单项,删除所有的页面 state 缓存
removeAllPageCacheState();
},
// onSelect: (e) => {
// console.log(e);
// },
},
...initialState?.settings,
token: {
sider: {
colorTextMenu: themes['textColor'],
colorTextMenuSelected: themes['primaryColor'],
colorTextMenuActive: themes['primaryColor'],
colorBgMenuItemSelected: 'rgba(197, 232, 255, 0.8)',
colorMenuBackground: themes['siderBGColor'],
},
},
// menuItemRender: (itemProps, defaultDom, props) => {
// console.log('menuItemProps', itemProps);
// console.log('defaultDom', defaultDom);
// console.log('props', props);

// const { pathname } = window.location;
// const isSelected = pathname === itemProps.path;

// // 根据菜单项的状态动态显示不同的 icon
// const icon = isSelected ? 'icon-developmentEnvironment-icon' : 'icon-kaifahuanjing';
// return (
// <Link to={itemProps.path || ''} target={itemProps.target}>
// <KFIcon type={icon} />
// {itemProps.name}
// </Link>
// );
// },
};
};



BIN
react-ui/src/assets/img/avatar-default.png View File

Before After
Width: 109  |  Height: 109  |  Size: 9.7 kB

BIN
react-ui/src/assets/img/blue-triangle.png View File

Before After
Width: 17  |  Height: 20  |  Size: 376 B

BIN
react-ui/src/assets/img/functional-material.png View File

Before After
Width: 57  |  Height: 57  |  Size: 8.0 kB

BIN
react-ui/src/assets/img/molecular-material.png View File

Before After
Width: 57  |  Height: 57  |  Size: 8.1 kB

BIN
react-ui/src/assets/img/user-avatar/1.png View File

Before After
Width: 73  |  Height: 73  |  Size: 10 kB

BIN
react-ui/src/assets/img/user-avatar/2.png View File

Before After
Width: 73  |  Height: 73  |  Size: 11 kB

BIN
react-ui/src/assets/img/user-avatar/3.png View File

Before After
Width: 73  |  Height: 73  |  Size: 11 kB

BIN
react-ui/src/assets/img/user-avatar/4.png View File

Before After
Width: 73  |  Height: 73  |  Size: 11 kB

BIN
react-ui/src/assets/img/user-avatar/5.png View File

Before After
Width: 73  |  Height: 73  |  Size: 9.9 kB

BIN
react-ui/src/assets/img/user-avatar/6.png View File

Before After
Width: 73  |  Height: 73  |  Size: 12 kB

BIN
react-ui/src/assets/img/user-avatar/7.png View File

Before After
Width: 73  |  Height: 73  |  Size: 10 kB

BIN
react-ui/src/assets/img/user-avatar/8.png View File

Before After
Width: 73  |  Height: 73  |  Size: 10 kB

BIN
react-ui/src/assets/img/workspace-experiment.png View File

Before After
Width: 147  |  Height: 148  |  Size: 17 kB

BIN
react-ui/src/assets/img/workspace-intro-icon.png View File

Before After
Width: 363  |  Height: 216  |  Size: 71 kB

BIN
react-ui/src/assets/img/workspace-intro.png View File

Before After
Width: 1626  |  Height: 235  |  Size: 159 kB

BIN
react-ui/src/assets/img/workspace-pipeline.png View File

Before After
Width: 169  |  Height: 159  |  Size: 21 kB

BIN
react-ui/src/assets/img/workspace-quick-start.png View File

Before After
Width: 3669  |  Height: 1848  |  Size: 429 kB

BIN
react-ui/src/assets/img/workspace-user.png View File

Before After
Width: 654  |  Height: 214  |  Size: 99 kB

+ 6
- 0
react-ui/src/components/CommonTableCell/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2024-04-28 14:18:11
* @Description: 自定义 Table 单元格,没有数据时展示 --
*/

function CommonTableCell(text?: string | null) {
return <span>{text ?? '--'}</span>;
}


+ 7
- 1
react-ui/src/components/DateTableCell/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2024-04-28 14:18:11
* @Description: 自定义 Table 日期类单元格
*/

import dayjs from 'dayjs';

function DateTableCell(text?: string | null) {
@@ -5,7 +11,7 @@ function DateTableCell(text?: string | null) {
return <span>--</span>;
}
if (!dayjs(text).isValid()) {
return <span>日期无效</span>;
return <span>无效的日期</span>;
}
return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
}


+ 1
- 1
react-ui/src/components/KFRadio/index.tsx View File

@@ -1,7 +1,7 @@
/*
* @Author: 赵伟
* @Date: 2024-04-17 16:59:42
* @Description: 自定义Radio
* @Description: 自定义 Radio
*/

import classNames from 'classnames';


+ 6
- 0
react-ui/src/components/ModalTitle/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2024-04-28 14:18:11
* @Description: 自定义 Modal Title
*/

import classNames from 'classnames';
import React from 'react';
import styles from './index.less';


+ 9
- 46
react-ui/src/global.less View File

@@ -1,6 +1,7 @@
html,
body,
#root {
min-width: 1440px;
height: 100%;
margin: 0;
padding: 0;
@@ -20,9 +21,6 @@ body,
.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
left: unset;
}
.ant-layout-sider-children {
margin-top: 60px !important;
}
canvas {
display: block;
}
@@ -33,58 +31,29 @@ body {
-moz-osx-font-smoothing: grayscale;
}
.ant-pro-layout .ant-pro-layout-content {
padding: 10px;
padding: 0 10px 10px;
background-color: transparent;
}
.ant-pro-layout .ant-pro-layout-bg-list {
background: #f9fafb;
background: @background-color;
}

.ant-menu-light .ant-menu-item-selected {
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-pro-layout .ant-pro-sider .ant-layout-sider-children {
background: #f2f5f7;
}
.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{
// color:#1664ff;
// }

.ant-pro-layout .ant-pro-sider-menu {
padding-top: 40px;
}
.ant-table-wrapper .ant-table-container table > thead > tr:first-child > *:first-child,
.ant-table-wrapper .ant-table-tbody>tr>td:first-child {
padding: 0 30px;
}


.ant-pro-global-header-logo-mix {
width: 257px;
height: 75px;
margin-left: -16px;
padding-left: 28px;
background: #f2f5f7;
border-bottom: 1px solid rgba(233, 237, 240, 1);
border-top-right-radius: 20px;
padding-left: 12px;
}
.ant-pro-layout .ant-pro-sider .ant-layout-sider-children {
border-right: unset;
border-bottom-right-radius: 20px;
}
.ant-pro-base-menu-inline {
// height: 87vh;
background: #f2f5f7;
border-radius: 0px 20px 20px 0px;
}
.ant-pro-layout .ant-pro-layout-content {
background-color: transparent;
}
.ant-drawer .ant-drawer-body {
padding: 0;
}
@@ -102,15 +71,12 @@ body {
padding: 20px 16px;
background-color: #fff;
}
// .ant-table-wrapper .ant-table {
// height: 81vh;
// // overflow-y: auto;
// }
.ant-pro-global-header-logo img {
height: 21px;
}
.ant-pro-layout .ant-layout-sider.ant-pro-sider {
height: 94vh;
height: 100vh;
padding-top: 56px;
}
.ant-pro-layout .ant-pro-layout-container {
height: 100vh;
@@ -131,7 +97,7 @@ body {
width: 110px;
height: 40px;
margin-right: 10px;
// color: #1d1d20;
// color: @text-color;
font-size: 18px;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
@@ -161,9 +127,6 @@ body {
.ant-modal .ant-select-single .ant-select-selector .ant-select-selection-placeholder {
line-height: 46px;
}
.ant-menu-light.ant-menu-inline .ant-menu-item {
color: #575757;
}
.ant-modal .ant-modal-close-x {
width: 26px;
height: 26px;


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

@@ -41,7 +41,7 @@ export function useVisible(initialValue: boolean) {
setVisible(false);
}, []);

return [visible, open, close];
return [visible, open, close] as const;
}

type Callback<T> = (state: T) => void;
@@ -92,7 +92,7 @@ export function useDomSize<T extends HTMLElement>(
setWidth(domRef.current.offsetWidth);
}
};
const debounceFunc = debounce(setDomHeight, 200);
const debounceFunc = debounce(setDomHeight, 100);

setDomHeight();
window.addEventListener('resize', debounceFunc);


+ 1
- 1
react-ui/src/iconfont/iconfont.js
File diff suppressed because it is too large
View File


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

@@ -52,3 +52,7 @@
.ant-table-wrapper .ant-table-thead > tr > td {
background-color: #fff;
}

.ant-pro-page-container {
overflow-y: auto;
}

+ 10
- 10
react-ui/src/pages/Dataset/personalData.jsx View File

@@ -117,14 +117,14 @@ const PublicData = (React.FC = () => {
};
const chooseDatasetType = (val, item) => {
console.log(val, item);
if (item.path == queryFlow.data_type) {
if (item.id == queryFlow.data_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, data_type: null });
getDatasetlist({ ...queryFlow, data_type: null });
} else {
setActiveType(item.path);
setQueryFlow({ ...queryFlow, data_type: item.path });
getDatasetlist({ ...queryFlow, data_type: item.path });
setActiveType(item.id);
setQueryFlow({ ...queryFlow, data_type: item.id });
getDatasetlist({ ...queryFlow, data_type: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
@@ -132,14 +132,14 @@ const PublicData = (React.FC = () => {
};
const chooseDatasetTag = (val, item) => {
console.log(val, item);
if (item.path == queryFlow.data_tag) {
if (item.id == queryFlow.data_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, data_tag: null });
getDatasetlist({ ...queryFlow, data_tag: null });
} else {
setActiveTag(item.path);
setQueryFlow({ ...queryFlow, data_tag: item.path });
getDatasetlist({ ...queryFlow, data_tag: item.path });
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, data_tag: item.id });
getDatasetlist({ ...queryFlow, data_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
@@ -196,7 +196,7 @@ const PublicData = (React.FC = () => {
<div
className={[
Styles.messageBox,
item.path === activeType ? Styles.active : null,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetType(e, item);
@@ -230,7 +230,7 @@ const PublicData = (React.FC = () => {
<div
className={[
Styles.messageBox,
item.path === activeTag ? Styles.active : null,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetTag(e, item);


+ 10
- 10
react-ui/src/pages/Dataset/publicData.jsx View File

@@ -76,14 +76,14 @@ const PublicData = () => {
};
const chooseDatasetType = (val, item) => {
console.log(val, item);
if (item.path == queryFlow.data_type) {
if (item.id == queryFlow.data_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, data_type: null });
getDatasetlist({ ...queryFlow, data_type: null });
} else {
setActiveType(item.path);
setQueryFlow({ ...queryFlow, data_type: item.path });
getDatasetlist({ ...queryFlow, data_type: item.path });
setActiveType(item.id);
setQueryFlow({ ...queryFlow, data_type: item.id });
getDatasetlist({ ...queryFlow, data_type: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
@@ -91,14 +91,14 @@ const PublicData = () => {
};
const chooseDatasetTag = (val, item) => {
console.log(val, item);
if (item.path == queryFlow.data_tag) {
if (item.id == queryFlow.data_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, data_tag: null });
getDatasetlist({ ...queryFlow, data_tag: null });
} else {
setActiveTag(item.path);
setQueryFlow({ ...queryFlow, data_tag: item.path });
getDatasetlist({ ...queryFlow, data_tag: item.path });
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, data_tag: item.id });
getDatasetlist({ ...queryFlow, data_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
@@ -146,7 +146,7 @@ const PublicData = () => {
<div
className={[
Styles.messageBox,
item.path === activeType ? Styles.active : null,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetType(e, item);
@@ -180,7 +180,7 @@ const PublicData = () => {
<div
className={[
Styles.messageBox,
item.path === activeTag ? Styles.active : null,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetTag(e, item);


+ 0
- 0
react-ui/src/pages/Docs/index.less View File


+ 9
- 0
react-ui/src/pages/Docs/index.tsx View File

@@ -0,0 +1,9 @@
const Docs = () => {
return (
<iframe
style={{ width: '100%', height: '100%', border: 0 }}
src={'/assets/材料科研软件平台使用文档.pdf'}
></iframe>
);
};
export default Docs;

+ 9
- 7
react-ui/src/pages/Experiment/index.less View File

@@ -24,7 +24,7 @@
align-items: center;
width: 100%;
padding: 0 0 0 33px;
color: #1d1d20;
color: @text-color;
font-size: 15px;

& > div {
@@ -76,16 +76,18 @@
.statusBox:hover .statusIcon {
visibility: visible;
}
.experimentBox{
.experimentBox {
height: calc(100% - 20px);
.experimentTable{
.experimentTable {
height: calc(100% - 60px);
:global{
.ant-table-wrapper .ant-table{
:global {
.ant-table-wrapper .ant-table {
// overflow-y: auto;
height: calc(100% - 48px);
}
.ant-table-row-expand-icon-cell {
padding: 0 30px;
}
}
}

}
}

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

@@ -16,7 +16,7 @@ export enum ExperimentStatus {
}

type ExperimentStatusKeys = keyof typeof ExperimentStatus;
type ExperimentStatusValues = (typeof ExperimentStatus)[ExperimentStatusKeys];
export type ExperimentStatusValues = (typeof ExperimentStatus)[ExperimentStatusKeys];

export const experimentStatusInfo: Record<ExperimentStatusValues, StatusInfo | undefined> = {
Running: {


+ 10
- 10
react-ui/src/pages/Model/personalData.jsx View File

@@ -121,14 +121,14 @@ const PublicData = () => {

const chooseModelType = (val, item) => {
console.log(val, item);
if (item.path == queryFlow.model_type) {
if (item.id == queryFlow.model_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, model_type: null });
getModelLists({ ...queryFlow, model_type: null });
} else {
setActiveType(item.path);
setQueryFlow({ ...queryFlow, model_type: item.path });
getModelLists({ ...queryFlow, model_type: item.path });
setActiveType(item.id);
setQueryFlow({ ...queryFlow, model_type: item.id });
getModelLists({ ...queryFlow, model_type: item.id });
}

// setQueryFlow({...queryFlow,data_type:item.path},()=>{
@@ -136,14 +136,14 @@ const PublicData = () => {
// })
};
const chooseModelTag = (val, item) => {
if (item.path == queryFlow.model_tag) {
if (item.id == queryFlow.model_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, model_tag: null });
getModelLists({ ...queryFlow, model_tag: null });
} else {
setActiveTag(item.path);
setQueryFlow({ ...queryFlow, model_tag: item.path });
getModelLists({ ...queryFlow, model_tag: item.path });
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, model_tag: item.id });
getModelLists({ ...queryFlow, model_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
@@ -190,7 +190,7 @@ const PublicData = () => {
<div
className={[
Styles.messageBox,
item.path === activeType ? Styles.active : null,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelType(e, item);
@@ -231,7 +231,7 @@ const PublicData = () => {
<div
className={[
Styles.messageBox,
item.path === activeTag ? Styles.active : null,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelTag(e, item);


+ 10
- 10
react-ui/src/pages/Model/publicData.jsx View File

@@ -77,14 +77,14 @@ const PublicData = () => {
};
const chooseModelType = (val, item) => {
console.log(val, item);
if (item.path == queryFlow.model_type) {
if (item.id == queryFlow.model_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, model_type: null });
getModelLists({ ...queryFlow, model_type: null });
} else {
setActiveType(item.path);
setQueryFlow({ ...queryFlow, model_type: item.path });
getModelLists({ ...queryFlow, model_type: item.path });
setActiveType(item.id);
setQueryFlow({ ...queryFlow, model_type: item.id });
getModelLists({ ...queryFlow, model_type: item.id });
}

// setQueryFlow({...queryFlow,data_type:item.path},()=>{
@@ -92,14 +92,14 @@ const PublicData = () => {
// })
};
const chooseModelTag = (val, item) => {
if (item.path == queryFlow.model_tag) {
if (item.id == queryFlow.model_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, model_tag: null });
getModelLists({ ...queryFlow, model_tag: null });
} else {
setActiveTag(item.path);
setQueryFlow({ ...queryFlow, model_tag: item.path });
getModelLists({ ...queryFlow, model_tag: item.path });
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, model_tag: item.id });
getModelLists({ ...queryFlow, model_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
@@ -147,7 +147,7 @@ const PublicData = () => {
<div
className={[
Styles.messageBox,
item.path === activeType ? Styles.active : null,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelType(e, item);
@@ -181,7 +181,7 @@ const PublicData = () => {
<div
className={[
Styles.messageBox,
item.path === activeTag ? Styles.active : null,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelTag(e, item);


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

@@ -22,7 +22,7 @@
height: 398px;
margin-right: 15px;
padding: 15px;
background-color: @background-color-primay;
background-color: @background-color-primary;
border: 1px solid @border-color;
border-radius: 8px;

@@ -31,7 +31,7 @@
padding-left: 0;
background-color: transparent;
border-width: 0;
border-bottom: 1px solid @border-color-second;
border-bottom: 1px solid @border-color-secondary;
border-radius: 0;
}
}
@@ -40,7 +40,7 @@
width: calc(100% - 488px - 15px);
height: 398px;
padding: 15px;
background-color: @background-color-primay;
background-color: @background-color-primary;
border: 1px solid @border-color;
border-radius: 8px;

@@ -49,7 +49,7 @@
padding: 3px 0 6px;
color: @text-color;
font-size: @font-size;
border-bottom: 1px solid @border-color-second;
border-bottom: 1px solid @border-color-secondary;
}
&__files {
height: calc(100% - 75px);


+ 54
- 0
react-ui/src/pages/Workspace/components/AssetsManagement/index.less View File

@@ -0,0 +1,54 @@
.assets-management {
flex: 1;
width: 100%;
padding: 20px 20px 0;
background-color: white;
border-radius: 4px;

:global {
.ant-select-filled {
background-color: rgba(138, 138, 138, 0.12);
border-radius: 2px;

.ant-select-selection-item {
color: @text-color-secondary !important;
font-size: 13px;
}
}
}

&__title {
color: @text-color;
font-weight: 500;
font-size: @font-size-title;
}

&__increase {
display: inline-block;
margin-top: 12px;
margin-bottom: 30px;
padding: 2px 7px;
color: @primary-color-secondary;
font-size: 13px;
background-color: rgba(187, 210, 255, 0.29);
border-radius: 2px;
}

&__summary {
display: flex;
flex-direction: column;
width: 33.33%;

&__title {
margin-bottom: 12px;
color: @text-color-secondary;
font-size: @font-size;
}

&__value {
color: @text-color;
font-weight: 500;
font-size: 22px;
}
}
}

+ 81
- 0
react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx View File

@@ -0,0 +1,81 @@
import { CommonTabKeys } from '@/enums';
import { getWorkspaceAssetCountReq } from '@/services/workspace';
import { to } from '@/utils/promise';
import { Flex, Select } from 'antd';
import { useEffect, useState } from 'react';
import styles from './index.less';
function AssetsManagement() {
const [type, setType] = useState(CommonTabKeys.Public);
const [assetCounts, setAssetCounts] = useState<{ title: string; value: number }[]>([]);
useEffect(() => {
getWorkspacAssetCount();
}, [type]);
// 获取工作空间资产数量
const getWorkspacAssetCount = async () => {
const params = {
isPublic: type === CommonTabKeys.Public,
};
const [res] = await to(getWorkspaceAssetCountReq(params));
if (res && res.data) {
const { component, dataset, image, model, workflow } = res.data;
const items = [
{
title: '数据集',
value: dataset,
},
{
title: '模型',
value: model,
},
{
title: '镜像',
value: image,
},
{
title: '组件',
value: component,
},
{
title: '代码配置',
value: 0,
},
{
title: '流水线模版',
value: workflow,
},
];
setAssetCounts(items);
}
};

return (
<div className={styles['assets-management']}>
<Flex justify="space-between">
<div className={styles['assets-management__title']}>AI资产</div>
<Select
size="small"
value={type}
style={{ width: 70 }}
onChange={setType}
variant="filled"
options={[
{ value: CommonTabKeys.Public, label: '公开' },
{ value: CommonTabKeys.Private, label: '个人' },
]}
/>
</Flex>

<div className={styles['assets-management__increase']}>今日新增数量:5</div>
<Flex justify="space-between" gap="22px 0" wrap="wrap">
{assetCounts.map((item, index) => (
<div className={styles['assets-management__summary']} key={index}>
<div className={styles['assets-management__summary__title']}>{item.title}</div>
<div className={styles['assets-management__summary__value']}>{item.value}</div>
</div>
))}
</Flex>
</div>
);
}

export default AssetsManagement;

+ 7
- 0
react-ui/src/pages/Workspace/components/ExperimentChart/index.less View File

@@ -0,0 +1,7 @@
.experiment-chart {
width: 295px;
min-width: 295px;
height: 140px;
background-color: @workspace-background;
border-radius: 4px;
}

+ 214
- 0
react-ui/src/pages/Workspace/components/ExperimentChart/index.tsx View File

@@ -0,0 +1,214 @@
import themes from '@/styles/theme.less';
import * as echarts from 'echarts';
import React, { useEffect, useRef } from 'react';
import styles from './index.less';

const color1 = new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{ offset: 0, color: '#c73131' },
{ offset: 1, color: '#ff7e96' },
],
false,
);

const color2 = new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{ offset: 0, color: '#6ac21d' },
{ offset: 1, color: '#96e850' },
],
false,
);
const color3 = new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{ offset: 0, color: '#8c8c8c' },
{ offset: 1, color: '#c8c6c6' },
],
false,
);
const color4 = new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{ offset: 0, color: '#ecb934' },
{ offset: 1, color: '#f0864d' },
],
false,
);

const color5 = new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{ offset: 0, color: '#7ea9fe' },
{ offset: 1, color: '#1664ff' },
],
false,
);

const color6 = new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{ offset: 0, color: 'rgba(255, 255, 255, 0.62)' },
{ offset: 1, color: '#ebf2ff ' },
],
false,
);

export type ExperimentStatistics = {
Failed: number;
Pending: number;
Running: number;
Succeeded: number;
Terminated: number;
};

type ExperimentChartProps = {
style?: React.CSSProperties;
chartData: ExperimentStatistics;
};

function ExperimentChart({ chartData, style }: ExperimentChartProps) {
const chartRef = useRef<HTMLDivElement>(null);
const total =
chartData.Failed +
chartData.Pending +
chartData.Running +
chartData.Succeeded +
chartData.Terminated;
const options: echarts.EChartsOption = {
title: {
show: true,
left: '29%',
top: 'center',
textAlign: 'center',
text: [`{a|${total}}`, '{b|实验状态}'].join('\n'),
textStyle: {
rich: {
a: {
color: themes['textColor'],
fontSize: 20,
fontWeight: 700,
lineHeight: 28,
},
b: {
color: themes['textColorSecondary'],
fontSize: 10,
fontWeight: 'normal',
},
},
},
},
tooltip: {
trigger: 'item',
},
legend: {
top: 'center',
right: '5%',
orient: 'vertical',
icon: 'circle',
itemWidth: 6,
itemGap: 20,
height: 100,
},
color: [color1, color2, color3, color4, color5],
series: [
{
type: 'pie',
radius: ['70%', '80%'],
center: ['30%', '50%'],
avoidLabelOverlap: false,
padAngle: 3,
itemStyle: {
borderRadius: 3,
},
label: {
show: false,
},
emphasis: {
label: {
show: false,
},
},
labelLine: {
show: false,
},
data: [
{ value: chartData.Failed, name: '失败' },
{ value: chartData.Succeeded, name: '成功' },
{ value: chartData.Terminated, name: '中止' },
{ value: chartData.Pending, name: '等待' },
{ value: chartData.Running, name: '运行中' },
],
},
{
type: 'pie',
radius: '60%',
center: ['30%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
},
tooltip: {
show: false,
},
emphasis: {
label: {
show: false,
},
disabled: true,
},
animation: false,
labelLine: {
show: false,
},
data: [
{
value: 100,
itemStyle: { color: color6, borderColor: 'rgba(22, 100, 255, 0.08)', borderWidth: 1 },
},
],
},
],
};

useEffect(() => {
// 创建一个echarts实例,返回echarts实例
const chart = echarts.init(chartRef.current);

// 设置图表实例的配置项和数据
chart.setOption(options);

// 组件卸载
return () => {
// myChart.dispose() 销毁实例
chart.dispose();
};
}, []);

return (
<div className={styles['experiment-chart']} style={style}>
<div style={{ width: '100%', height: '100%' }} ref={chartRef}></div>
</div>
);
}

export default ExperimentChart;

+ 58
- 0
react-ui/src/pages/Workspace/components/ExperimentTable/index.less View File

@@ -0,0 +1,58 @@
.experiment-table {
flex: 1;
min-width: 500px;
height: 140px;
padding: 12px 24px;
background-color: @workspace-background;
border-radius: 4px;

&__header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 4px;
color: @text-color;
font-size: @font-size;
line-height: 20px;
}

&__content {
display: flex;
align-items: center;
justify-content: center;
padding: 6px 0;
color: .addAlpha(@text-color, 0.75) [];
font-size: 14px;
line-height: 20px;
border-bottom: 1px solid rgba(234, 234, 234, 0.8);

&:last-child {
border-bottom: 0;
}
}

&__status {
width: 20%;
}

&__duration {
width: 25%;
}

&__date {
width: 35%;
}

&__operation {
width: 20%;

:global {
.ant-btn-link {
height: 20px;
padding: 0;
line-height: 20px;
border: 0;
}
}
}
}

+ 59
- 0
react-ui/src/pages/Workspace/components/ExperimentTable/index.tsx View File

@@ -0,0 +1,59 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatusValues, experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { useNavigate } from '@umijs/max';
import { Button } from 'antd';
import styles from './index.less';
type ExperimentTableProps = {
tableData: ExperimentInstance[];
style?: React.CSSProperties;
};

function ExperimentTable({ tableData = [], style }: ExperimentTableProps) {
const navgite = useNavigate();
const gotoExperiment = (record: ExperimentInstance) => {
navgite(`/pipeline/experimentPytorchtext/${record.workflow_id}/${record.id}`);
};

return (
<div className={styles['experiment-table']} style={style}>
<div className={styles['experiment-table__header']}>
<div className={styles['experiment-table__status']}>状态</div>
<div className={styles['experiment-table__duration']}>运行时长</div>
<div className={styles['experiment-table__date']}>开始时间</div>
<div className={styles['experiment-table__operation']}>操作</div>
</div>
{tableData?.map((item) => (
<div className={styles['experiment-table__content']} key={item.id}>
<div className={styles['experiment-table__status']} style={{ paddingLeft: '6.5px' }}>
<img
src={experimentStatusInfo[item.status as ExperimentStatusValues]?.icon}
width={17}
height={17}
/>
</div>
<div className={styles['experiment-table__duration']}>
{elapsedTime(
new Date(item.create_time),
item.finish_time ? new Date(item.finish_time) : new Date(),
)}
</div>
<div className={styles['experiment-table__date']}>{formatDate(item.create_time)}</div>
<div className={styles['experiment-table__operation']}>
<Button
size="small"
type="link"
icon={<KFIcon type="icon-xiangqing2" font={14} />}
onClick={() => gotoExperiment(item)}
>
详情
</Button>
</div>
</div>
))}
</div>
);
}

export default ExperimentTable;

+ 60
- 0
react-ui/src/pages/Workspace/components/QuickStart/WorkArrow.tsx View File

@@ -0,0 +1,60 @@
import styles from './index.less';

type WorkArrowProps = {
width?: number;
height?: number;
x: number;
y: number;
arrowLeft: number;
arrorwTop: number;
borderLeft?: number;
borderTop?: number;
borderRight?: number;
borderBottom?: number;
arrrowAngle?: number;
};

function WorkArrow({
width = 1,
height = 1,
x,
y,
arrowLeft,
arrorwTop,
borderLeft = 0,
borderTop = 0,
borderRight = 0,
borderBottom = 0,
arrrowAngle = 0,
}: WorkArrowProps) {
return (
<div
className={styles['work-arrow']}
style={{
left: `${x}px`,
top: `${y}px`,
width: `${width}px`,
height: `${height}px`,
borderLeftWidth: `${borderLeft}px`,
borderTopWidth: `${borderTop}px`,
borderRightWidth: `${borderRight}px`,
borderBottomWidth: `${borderBottom}px`,
}}
>
<img
className={styles['work-arrow__img']}
src={require('@/assets/img/blue-triangle.png')}
alt=""
width={10}
height={9}
style={{
left: `${arrowLeft}px`,
top: `${arrorwTop}px`,
transform: `rotate(${arrrowAngle}deg)`,
}}
/>
</div>
);
}

export default WorkArrow;

+ 31
- 0
react-ui/src/pages/Workspace/components/QuickStart/WorkFlow.tsx View File

@@ -0,0 +1,31 @@
import { Button } from 'antd';
import styles from './index.less';

type WorkFlowProps = {
content: string;
buttonText: string;
tips?: string;
onClick?: () => void;
buttonTop?: number;
x: number;
y: number;
};

function WorkFlow({ content, buttonText, tips, buttonTop = 20, x, y, onClick }: WorkFlowProps) {
return (
<div className={styles['work-flow']} style={{ left: `${x}px`, top: `${y}px` }}>
{tips && <div className={styles['work-flow__tips']}>{tips}</div>}
<div className={styles['work-flow__content']}>{content}</div>
<Button
className={styles['work-flow__button']}
type="primary"
style={{ marginTop: `${buttonTop}px` }}
onClick={onClick}
>
{buttonText}
</Button>
</div>
);
}

export default WorkFlow;

+ 96
- 0
react-ui/src/pages/Workspace/components/QuickStart/index.less View File

@@ -0,0 +1,96 @@
.quick-start {
width: calc(100% - 326px);
padding: 20px 30px;
background-color: white;
border-radius: 4px;

&__title {
margin-bottom: 20px;
color: @text-color;
font-weight: 500;
font-size: @font-size-title;
}

&__content {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 610px;
background-image: url(@/assets/img/workspace-quick-start.png);
background-repeat: no-repeat;
background-position: top left;
background-size: 100% 100%;

&__canvas {
position: relative;
width: 1223px;
height: 610px;
transform-origin: center left;

&__model {
position: absolute;
top: 358px;
left: 920px;
display: flex;
flex-direction: column;
align-items: center;
color: @primary-color;
font-size: @font-size;
}

&__task {
position: absolute;
top: 110px;
left: 603px;
display: flex;
align-items: center;
justify-content: center;
width: 131px;
height: 41px;
color: @primary-color;
font-size: @font-size;
background-color: rgba(22, 100, 255, 0.05);
border: 1px dashed @primary-color;
border-radius: 4px;
box-shadow: 0px 0px 6px rgba(22, 100, 255, 0.07);
}
}
}
}

.work-flow {
position: absolute;
width: 192px;
padding: 15px;
background-color: white;
border: 1px solid;
border-color: rgba(22, 100, 255, 0.08);
border-radius: 0px 8px 0px 0px;
box-shadow: 0px 0px 10px rgba(22, 100, 255, 0.06);

&__content {
color: @text-color-secondary;
font-size: @font-size;
}

&__tips {
position: absolute;
top: -16px;
left: 0;
padding: 4px 10px;
color: white;
font-size: @font-size;
background-color: #333333;
}
}

.work-arrow {
position: absolute;
border: 0 dashed @primary-color;

&__img {
position: absolute;
}
}

+ 149
- 0
react-ui/src/pages/Workspace/components/QuickStart/index.tsx View File

@@ -0,0 +1,149 @@
import KFIcon from '@/components/KFIcon';
import { useNavigate } from '@umijs/max';
import { debounce } from 'lodash';
import { useEffect, useState } from 'react';
import WorkArrow from './WorkArrow';
import WorkFlow from './WorkFlow';
import styles from './index.less';

function QuickStart() {
const navgite = useNavigate();
const [scale, setScale] = useState(1);

useEffect(() => {
const changeScale = () => {
const width = document.body.offsetWidth - 256 - 80 - 60 - 326 - 15 - 8;
const ratio = width >= 1223 ? 1 : width / 1223;
setScale(ratio);
};

const debounceFunc = debounce(changeScale, 16);
window.addEventListener('resize', debounceFunc);

return () => {
window.removeEventListener('resize', debounceFunc);
};
}, []);

return (
<div className={styles['quick-start']}>
<div className={styles['quick-start__title']}>快速开始</div>
<div className={styles['quick-start__content']}>
<div
className={styles['quick-start__content__canvas']}
style={{ transform: `scale(${scale})` }}
>
<WorkFlow
content="为开发者提供数据智能标注与数据回流服务"
buttonText="数据准备"
buttonTop={40}
x={20}
y={309}
onClick={() => navgite('/datasetPreparation/datasetAnnotation')}
/>
<WorkFlow
content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用"
buttonText="开发环境"
buttonTop={20}
x={248}
y={301}
onClick={() => navgite('/developmentEnvironment')}
/>
<WorkFlow
content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用"
tips="可视化建模Designer"
buttonText="流水线"
buttonTop={20}
x={476}
y={276}
onClick={() => navgite('/pipeline/pipelineText')}
/>
<WorkFlow
content="开发者可以在这里运行流水线模板,产生实验实例,对比实验训练过程与产生的实验训练数据"
buttonText="实验"
buttonTop={40}
x={699}
y={295}
onClick={() => navgite('/pipeline/experimentText')}
/>
<WorkFlow
content="支持异构硬件(CPU/GPU)的模型加载,高吞吐,低延迟;支持大规模复杂模型的一键部署,实时弹性扩缩容;提供完整的运维监控体系。"
tips="模型在线服务"
buttonText="模型在线部署"
buttonTop={20}
x={1010}
y={263}
onClick={() => navgite('/modelDseployment')}
/>
<div className={styles['quick-start__content__canvas__model']}>
<KFIcon type="icon-moxingguanli" font={38} />
<span>模型管理</span>
</div>
<div className={styles['quick-start__content__canvas__task']}>
<KFIcon type="icon-tiaoduguanli" font={13} style={{ marginRight: '5px' }} />
<span>任务自动调度</span>
</div>
<WorkArrow
x={213}
y={378}
width={22}
height={1}
arrowLeft={22}
arrorwTop={-4}
borderBottom={1}
/>
<WorkArrow
x={441}
y={378}
width={22}
height={1}
arrowLeft={22}
arrorwTop={-4}
borderBottom={1}
/>
<WorkArrow
x={893}
y={378}
width={22}
height={1}
arrowLeft={22}
arrorwTop={-4}
borderBottom={1}
/>
<WorkArrow
x={974}
y={378}
width={22}
height={1}
arrowLeft={22}
arrorwTop={-4}
borderBottom={1}
/>
<WorkArrow
x={532}
y={139}
width={54}
height={125}
arrowLeft={54}
arrorwTop={-4}
borderLeft={1}
borderTop={1}
/>
<WorkArrow
x={740}
y={127}
width={49}
height={156}
arrowLeft={44}
arrorwTop={156}
arrrowAngle={90}
borderRight={1}
borderTop={1}
/>
</div>
</div>
</div>
);
}

export default QuickStart;

+ 41
- 0
react-ui/src/pages/Workspace/components/TotalStatistics/index.less View File

@@ -0,0 +1,41 @@
.total-statistics {
display: flex;
align-items: center;
justify-content: center;
width: 400px;
height: 140px;
background-color: @workspace-background;
border-radius: 4px;

&__icon {
width: 85px;
height: 80px;
margin-right: 40px;
}

&__title {
position: relative;
margin-bottom: 6px;
color: @text-color-secondary;
font-size: @font-size-content;
}

&__title-shadow {
position: absolute;
bottom: 6px;
left: 0;
width: 79px;
height: 6px;
background-color: linear-gradient(
87.07deg,
rgba(22, 100, 255, 0.6) 0%,
rgba(22, 100, 255, 0) 100%
);
}

&__count {
color: @text-color;
font-weight: 700;
font-size: 25px;
}
}

+ 25
- 0
react-ui/src/pages/Workspace/components/TotalStatistics/index.tsx View File

@@ -0,0 +1,25 @@
import styles from './index.less';

type TotalStatisticsProps = {
icon: string;
title: string;
count?: string | number;
style?: React.CSSProperties;
};

function TotalStatistics({ icon = '', title = '', count = 0, style }: TotalStatisticsProps) {
return (
<div className={styles['total-statistics']} style={style}>
<img className={styles['total-statistics__icon']} src={icon} />
<div>
<div className={styles['total-statistics__title']}>
<span>{title}</span>
<div className={styles['total-statistics__title-shadow']}></div>
</div>
<div className={styles['total-statistics__count']}>{count ?? '--'}</div>
</div>
</div>
);
}

export default TotalStatistics;

+ 65
- 0
react-ui/src/pages/Workspace/components/UserSpace/index.less View File

@@ -0,0 +1,65 @@
.user-space {
margin-bottom: 16px;
padding-bottom: 20px;
background-color: white;
border-radius: 4px;
&__title {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 107px;
color: @text-color;
font-size: @font-size-title;
background-image: url(@/assets/img/workspace-user.png);
background-repeat: no-repeat;
background-position: top left;
background-size: 100% 100%;
}

&__avatar {
position: relative;
top: -28px;
width: 56px;
height: 56px;
}

&__name {
margin-top: -20px;
margin-bottom: 8px;
color: @text-color;
font-size: @font-size-content;
}

&__role {
display: inline-block;
padding: 1px 7px;
color: @primary-color-secondary;
font-size: 13px;
background-color: rgba(187, 210, 255, 0.29);
border-radius: 2px;
}

&__participant {
&__title {
color: @text-color-secondary;
font-size: @font-size-content;
}

&__count {
width: 24px;
height: 24px;
color: #8a8a8a;
font-size: 12px;
line-height: 24px;
text-align: center;
background-color: rgba(153, 153, 153, 0.13);
border-radius: 50%;
}

&__user {
width: 36px;
height: 36px;
}
}
}

+ 47
- 0
react-ui/src/pages/Workspace/components/UserSpace/index.tsx View File

@@ -0,0 +1,47 @@
import { useModel } from '@umijs/max';
import { Divider, Flex, Space } from 'antd';
import styles from './index.less';

type UserSpaceProps = {
users: any[];
};

function UserSpace({ users = [] }: UserSpaceProps) {
const { initialState } = useModel('@@initialState');
const { currentUser } = initialState || {};

return (
<div className={styles['user-space']}>
<div className={styles['user-space__title']}>工作空间管理</div>
<div style={{ padding: '0 20px' }}>
<img className={styles['user-space__avatar']} src={currentUser?.avatar} alt="" />
<div className={styles['user-space__name']}>{currentUser?.nickName}</div>
<div className={styles['user-space__role']}>{currentUser?.roleNames?.[0]?.roleName}</div>
<Divider
dashed
style={{ borderColor: 'rgba(22, 100, 255, 0.19)', margin: '20px 0' }}
></Divider>
<div className={styles['user-space__participant']}>
<Space align="center" size={10} style={{ marginBottom: '20px' }}>
<div className={styles['user-space__participant__title']}>参与者</div>
<div className={styles['user-space__participant__count']}>8</div>
</Space>
<Flex align="center" gap={12} wrap="wrap">
{users?.map((item, index) => {
return (
<img
className={styles['user-space__participant__user']}
key={index}
src={require(`@/assets/img/user-avatar/${index + 1}.png`)}
alt=""
/>
);
})}
</Flex>
</div>
</div>
</div>
);
}

export default UserSpace;

+ 43
- 0
react-ui/src/pages/Workspace/components/WorkspaceIntro/index.less View File

@@ -0,0 +1,43 @@
.workspace-intro {
display: flex;
align-items: center;
margin-bottom: 16px;
padding: 0 30px;
background-image: url(@/assets/img/workspace-intro.png);
background-repeat: no-repeat;
background-position: top right;
background-size: 100% 100%;
border-radius: 4px;

&__left {
padding: 30px 0 34px;
}

&__right {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
}

&__title {
margin-bottom: 20px;
color: @text-color;
font-weight: 500;
font-size: 20px;
}

&__content {
max-width: 980px;
margin-bottom: 20px;
color: @text-color-secondary;
font-size: @font-size-title;
line-height: 1.8;
letter-spacing: 1px;
}

&__icon {
width: 363px;
height: 216px;
}
}

+ 43
- 0
react-ui/src/pages/Workspace/components/WorkspaceIntro/index.tsx View File

@@ -0,0 +1,43 @@
import { Button } from 'antd';
import styles from './index.less';

function WorkspaceIntro() {
return (
<div className={styles['workspace-intro']}>
<div className={styles['workspace-intro__left']}>
<div className={styles['workspace-intro__title']}>自主实验平台</div>
<div className={styles['workspace-intro__content']}>
材料领域的自主实验系统是一种用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能,
以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作
</div>
<div className={styles['workspace-intro__buttons']}>
<Button
type="primary"
style={{ marginRight: '20px' }}
icon={
<img src={require('@/assets/img/functional-material.png')} width={19} height={19} />
}
>
功能材料自主实验系统
</Button>
<Button
type="default"
icon={
<img src={require('@/assets/img/molecular-material.png')} width={19} height={19} />
}
>
分子材料自主实验系统
</Button>
</div>
</div>
<div className={styles['workspace-intro__right']}>
<img
className={styles['workspace-intro__icon']}
src={require('@/assets/img/workspace-intro-icon.png')}
/>
</div>
</div>
);
}

export default WorkspaceIntro;

+ 45
- 0
react-ui/src/pages/Workspace/index.less View File

@@ -0,0 +1,45 @@
.workspace {
height: 100%;
padding: 20px 30px 10px;
overflow-y: auto;
background-color: linear-gradient(#ecf2fe, #f9fafb);

&__overview {
margin-bottom: 16px;
padding: 20px 30px;
background-color: white;
border-radius: 4px;

&__title {
margin-bottom: 20px;
color: @text-color;
font-weight: 500;
font-size: @font-size-title;
}

&__content {
display: flex;
gap: 15px;
align-items: center;

@media screen and (max-width: 1800px) {
flex-wrap: wrap;
}
}
}

&__quick-start {
display: flex;
gap: 15px;
align-items: flex-start;
width: 100%;
}

&__user {
display: flex;
flex-direction: column;
width: 326px;
min-width: 326px;
height: 700px;
}
}

+ 71
- 0
react-ui/src/pages/Workspace/index.tsx View File

@@ -0,0 +1,71 @@
import { getWorkspaceOverviewReq } from '@/services/workspace';
import { ExperimentInstance } from '@/types';
import { to } from '@/utils/promise';
import { useEffect, useState } from 'react';
import AssetsManagement from './components/AssetsManagement';
import ExperimentChart, { type ExperimentStatistics } from './components/ExperimentChart';
import ExperitableTable from './components/ExperimentTable';
import QuickStart from './components/QuickStart';
import TotalStatistics from './components/TotalStatistics';
import UserSpace from './components/UserSpace';
import WorkspaceIntro from './components/WorkspaceIntro';
import styles from './index.less';

type OverviewData = {
workflowCount: number;
runningExperimentInsCount: number;
experimentInsStatus: ExperimentStatistics;
latestExperimentInsList: ExperimentInstance[];
};

function Workspace() {
const [overviewData, setOverviewData] = useState<OverviewData>();
const users: number[] = new Array(8).fill(0);
useEffect(() => {
getWorkspaceOverview();
}, []);

// 获取工作空间概况
const getWorkspaceOverview = async () => {
const [res] = await to(getWorkspaceOverviewReq());
if (res && res.data) {
setOverviewData(res.data);
}
};

return (
<div className={styles.workspace}>
<WorkspaceIntro></WorkspaceIntro>
<div className={styles['workspace__overview']}>
<div className={styles['workspace__overview__title']}>运行概览</div>
<div className={styles['workspace__overview__content']}>
<TotalStatistics
icon={require('@/assets/img/workspace-pipeline.png')}
title="流水线总数"
count={overviewData?.workflowCount}
/>
<TotalStatistics
icon={require('@/assets/img/workspace-experiment.png')}
title="正在运行实例总数"
count={overviewData?.runningExperimentInsCount}
/>
<ExperitableTable
tableData={overviewData?.latestExperimentInsList || []}
></ExperitableTable>
{overviewData?.experimentInsStatus && (
<ExperimentChart chartData={overviewData?.experimentInsStatus}></ExperimentChart>
)}
</div>
</div>
<div className={styles['workspace__quick-start']}>
<QuickStart></QuickStart>
<div className={styles['workspace__user']}>
<UserSpace users={users}></UserSpace>
<AssetsManagement></AssetsManagement>
</div>
</div>
</div>
);
}

export default Workspace;

+ 2
- 1
react-ui/src/requestConfig.ts View File

@@ -46,7 +46,8 @@ export const requestConfig: RequestConfig = {
],
responseInterceptors: [
(response: any) => {
const { status, data } = response;
const { status, data } = response || {};
console.log('response2', response);
if (status >= 200 && status < 300) {
if (data && (data instanceof Blob || data.code === 200)) {
return response;


+ 3
- 0
react-ui/src/services/ant-design-pro/typings.d.ts View File

@@ -14,6 +14,9 @@ declare namespace API {
};
address?: string;
phone?: string;
roleNames?: {
roleName?: string;
}[];
};

type ErrorResponse = {


+ 1
- 1
react-ui/src/services/mirror/index.ts View File

@@ -1,7 +1,7 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 14:29:44
* @Description:
* @Description: 镜像管理接口
*/
import { request } from '@umijs/max';



+ 21
- 0
react-ui/src/services/workspace/index.ts View File

@@ -0,0 +1,21 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 14:29:44
* @Description: 工作空间接口
*/
import { request } from '@umijs/max';

// 获取工作空间概况
export function getWorkspaceOverviewReq() {
return request(`/api/mmp/workspace/overview`, {
method: 'GET',
});
}

// 获取工作空间概况
export function getWorkspaceAssetCountReq(params: any) {
return request(`/api/mmp/workspace/assetCount`, {
method: 'GET',
params,
});
}

+ 23
- 2
react-ui/src/styles/theme.less View File

@@ -6,6 +6,7 @@

// 颜色
@primary-color: #1664ff; // 主色调
@primary-color-secondary: #4e89ff;
@primary-color-hover: #69b1ff;
@background-color: #f9fafb; // 页面背景颜色
@text-color: #1d1d20;
@@ -15,17 +16,35 @@
@warning-color: #f98e1b;

@border-color: rgba(22, 100, 255, 0.3);
@border-color-second: rgba(22, 100, 255, 0.1);
@background-color-primay: rgba(22, 100, 255, 0.03);
@border-color-secondary: rgba(22, 100, 255, 0.1);
@background-color-primary: rgba(22, 100, 255, 0.03);
@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;
@sider-background-color: #f2f5f7;

@workspace-background: linear-gradient(
179.03deg,
rgba(138, 138, 138, 0.06) 0%,
rgba(22, 100, 255, 0.02) 100%
);

// 字体大小
@font-size: 15px;
@font-size-title: 18px;
@font-size-content: 16px;

// 函数
.addAlpha(@color, @alpha) {
@red: red(@color);
@green: green(@color);
@blue: blue(@color);

@result: rgba(@red, @green, @blue, @alpha);
}

// 导出变量
:export {
@@ -34,5 +53,7 @@
errorColor: @error-color;
warningColor: @warning-color;
textColor: @text-color;
textColorSecondary: @text-color-secondary;
fontSize: @font-size;
siderBGColor: @sider-background-color;
}

+ 16
- 0
react-ui/src/types.ts View File

@@ -12,3 +12,19 @@ export type PipelineGlobalParam = {
param_value: number | string | boolean;
is_sensitive: number;
};

// 实验实例
export type ExperimentInstance = {
id: number;
experiment_id: number;
workflow_id: number;
create_time: string;
finish_time: string;
update_time: string;
status: string;
argo_ins_name: string;
argo_ins_ns: string;
nodes_result: string;
nodes_status: string;
global_param: PipelineGlobalParam[];
};

+ 12
- 0
react-ui/src/utils/date.ts View File

@@ -36,3 +36,15 @@ export const isValidDate = (date: Date): boolean => {
}
return false;
};

// 格式化日期
export const formatDate = (text: Date | string, format = 'YYYY-MM-DD HH:mm:ss'): string => {
if (text === undefined || text === null || text === '') {
return '--';
}
if (!dayjs(text).isValid()) {
return '--';
}

return dayjs(text).format(format);
};

+ 1
- 1
react-ui/src/utils/ui.tsx View File

@@ -51,7 +51,7 @@ export const gotoLoginPage = (toHome: boolean = true) => {
const { pathname, search } = window.location;
const urlParams = new URLSearchParams();
urlParams.append('redirect', pathname + search);
const newSearch = toHome ? '' : urlParams.toString();
const newSearch = toHome && pathname && pathname !== PageEnum.LOGIN ? '' : urlParams.toString();
if (window.location.pathname !== PageEnum.LOGIN) {
history.replace({
pathname: PageEnum.LOGIN,


Loading…
Cancel
Save