Browse Source

feat: 完成新的工作空间

dev-new-workspace
zhaowei 10 months ago
parent
commit
6567f4c1c0
47 changed files with 1489 additions and 275 deletions
  1. +2
    -1
      react-ui/package.json
  2. BIN
      react-ui/src/assets/img/logo-ccc.png
  3. BIN
      react-ui/src/assets/img/logo.png
  4. BIN
      react-ui/src/assets/img/workspace/app.png
  5. BIN
      react-ui/src/assets/img/workspace/banner1.png
  6. BIN
      react-ui/src/assets/img/workspace/banner2.png
  7. BIN
      react-ui/src/assets/img/workspace/block-title-bg.png
  8. BIN
      react-ui/src/assets/img/workspace/code.png
  9. BIN
      react-ui/src/assets/img/workspace/dataset.png
  10. BIN
      react-ui/src/assets/img/workspace/echart-bg.png
  11. BIN
      react-ui/src/assets/img/workspace/experiment.png
  12. BIN
      react-ui/src/assets/img/workspace/hot-icon.png
  13. BIN
      react-ui/src/assets/img/workspace/image.png
  14. BIN
      react-ui/src/assets/img/workspace/model.png
  15. BIN
      react-ui/src/assets/img/workspace/notebook-icon.png
  16. BIN
      react-ui/src/assets/img/workspace/paper.png
  17. BIN
      react-ui/src/assets/img/workspace/points-bg.png
  18. BIN
      react-ui/src/assets/img/workspace/prompt-pilot.png
  19. BIN
      react-ui/src/assets/img/workspace/search-icon.png
  20. BIN
      react-ui/src/assets/img/workspace/search-star.gif
  21. BIN
      react-ui/src/assets/img/workspace/statistics-bg.png
  22. BIN
      react-ui/src/assets/img/workspace/title-icon.png
  23. BIN
      react-ui/src/assets/img/workspace/train.png
  24. BIN
      react-ui/src/assets/img/workspace/workspace-bg.png
  25. +29
    -0
      react-ui/src/hooks/useRootFontSize.ts
  26. +1
    -1
      react-ui/src/iconfont/iconfont.js
  27. +35
    -44
      react-ui/src/pages/Workspace/components/AssetsManagement/index.less
  28. +32
    -34
      react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx
  29. +27
    -0
      react-ui/src/pages/Workspace/components/BlockTitle/index.less
  30. +28
    -0
      react-ui/src/pages/Workspace/components/BlockTitle/index.tsx
  31. +285
    -0
      react-ui/src/pages/Workspace/components/ExperimentChart/charts.js
  32. +12
    -6
      react-ui/src/pages/Workspace/components/ExperimentChart/index.less
  33. +29
    -16
      react-ui/src/pages/Workspace/components/ExperimentChart/index.tsx
  34. +125
    -0
      react-ui/src/pages/Workspace/components/ExperimentChart/test.tsx
  35. +84
    -0
      react-ui/src/pages/Workspace/components/ExperimentChart/threePieChart.jsx
  36. +61
    -0
      react-ui/src/pages/Workspace/components/Experiments/index.less
  37. +82
    -0
      react-ui/src/pages/Workspace/components/Experiments/index.tsx
  38. +62
    -0
      react-ui/src/pages/Workspace/components/Messages/index.less
  39. +106
    -0
      react-ui/src/pages/Workspace/components/Messages/index.tsx
  40. +120
    -0
      react-ui/src/pages/Workspace/components/Notebooks/index.less
  41. +95
    -0
      react-ui/src/pages/Workspace/components/Notebooks/index.tsx
  42. +111
    -0
      react-ui/src/pages/Workspace/components/Search/index.less
  43. +80
    -0
      react-ui/src/pages/Workspace/components/Search/index.tsx
  44. +27
    -33
      react-ui/src/pages/Workspace/components/UserPoints/index.less
  45. +27
    -15
      react-ui/src/pages/Workspace/components/UserPoints/index.tsx
  46. +6
    -62
      react-ui/src/pages/Workspace/index.less
  47. +23
    -63
      react-ui/src/pages/Workspace/index.tsx

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

@@ -8,7 +8,7 @@
"build": "max build",
"deploy": "npm run build && npm run gh-pages",
"dev": "npm run start:dev",
"dev-no-sso": "cross-env NO_SSO=true npm run start:mock",
"dev-no-sso": "cross-env NO_SSO=true PORT=8001 npm run start:mock",
"docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./",
"docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build",
"docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up",
@@ -75,6 +75,7 @@
"classnames": "^2.3.2",
"crypto-js": "^4.2.0",
"echarts": "^5.5.0",
"echarts-gl": "~2.0.9",
"fabric": "^5.3.0",
"highlight.js": "^11.7.0",
"lodash": "^4.17.21",


BIN
react-ui/src/assets/img/logo-ccc.png View File

Before After
Width: 112  |  Height: 112  |  Size: 5.3 kB

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

Before After
Width: 112  |  Height: 112  |  Size: 5.3 kB Width: 93  |  Height: 93  |  Size: 8.0 kB

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

Before After
Width: 94  |  Height: 94  |  Size: 7.7 kB

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

Before After
Width: 1752  |  Height: 490  |  Size: 147 kB

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

Before After
Width: 1752  |  Height: 490  |  Size: 247 kB

BIN
react-ui/src/assets/img/workspace/block-title-bg.png View File

Before After
Width: 2704  |  Height: 126  |  Size: 73 kB

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

Before After
Width: 94  |  Height: 94  |  Size: 7.8 kB

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

Before After
Width: 94  |  Height: 94  |  Size: 7.6 kB

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

Before After
Width: 705  |  Height: 1023  |  Size: 159 kB

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

Before After
Width: 94  |  Height: 94  |  Size: 7.7 kB

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

Before After
Width: 38  |  Height: 38  |  Size: 1.1 kB

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

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

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

Before After
Width: 94  |  Height: 94  |  Size: 7.7 kB

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

Before After
Width: 56  |  Height: 56  |  Size: 7.2 kB

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

Before After
Width: 94  |  Height: 94  |  Size: 7.8 kB

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

Before After
Width: 1203  |  Height: 2046  |  Size: 606 kB

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

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

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

Before After
Width: 52  |  Height: 56  |  Size: 5.5 kB

BIN
react-ui/src/assets/img/workspace/search-star.gif View File

Before After
Width: 702  |  Height: 702  |  Size: 482 kB

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

Before After
Width: 4056  |  Height: 522  |  Size: 1.3 MB

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

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

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

Before After
Width: 94  |  Height: 94  |  Size: 7.8 kB

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

Before After
Width: 3648  |  Height: 3028  |  Size: 2.3 MB

+ 29
- 0
react-ui/src/hooks/useRootFontSize.ts View File

@@ -0,0 +1,29 @@
import { useEffect, useState } from 'react';

/**
* 获取 root font size
*
* @param initialValue - 状态的初始值
* @return 包含状态值、状态设置函数和可变引用对象的数组
*/
export function useRootFontSize() {
const fontSizeStr = document.documentElement.style.fontSize;
const fontSize = parseFloat(fontSizeStr);
const [size, setSize] = useState(fontSize);

useEffect(() => {
const setFontSize = () => {
setTimeout(() => {
const fontSizeStr = document.documentElement.style.fontSize;
const fontSize = parseFloat(fontSizeStr);
setSize(fontSize);
}, 300);
};
window.addEventListener('resize', setFontSize);
return () => {
window.removeEventListener('resize', setFontSize);
};
}, []);

return size;
}

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


+ 35
- 44
react-ui/src/pages/Workspace/components/AssetsManagement/index.less View File

@@ -1,65 +1,56 @@
.assets-management {
flex: 1;
display: flex;
flex-direction: column;
width: 100%;
padding: 20px 20px 0;
background-color: white;
border-radius: 4px;
height: 10.875rem;
color: white;
.backgroundFullImage(url(@/assets/img/workspace/statistics-bg.png));

: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 {
flex: none;
padding: 1.25rem;
font-weight: 700;
font-size: 1rem;
}

&__title {
color: @text-color;
font-weight: 500;
font-size: @font-size-title;
&__line {
flex: none;
width: 64.9375rem;
height: 1px;
padding-left: 1.25rem;
background-image: linear-gradient(
135.57deg,
rgba(255, 255, 255, 0.3) 0%,
rgba(255, 255, 255, 0) 100%
);
}

&__increase {
display: inline-block;
margin-top: 12px;
margin-bottom: 30px;
padding: 2px 7px;
color: @primary-color;
font-size: 13px;
background-color: rgba(187, 210, 255, 0.29);
border-radius: 2px;
&__content {
display: flex;
flex: 1;
align-items: center;
padding: 0 1.25rem;
}

&__summary {
display: flex;
flex-direction: column;
width: 40%;
text-align: left;
align-items: center;
width: 8.125rem;
border-right: 1px solid rgba(255, 255, 255, 0.2);

&:nth-child(3n + 2) {
width: 30%;
text-align: center;
&:last-child {
border-right: none;
}

&:nth-child(3n) {
width: 30%;
text-align: right;
&__value {
margin-bottom: 4px;
font-weight: 700;
font-size: 1.25rem;
}

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

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

+ 32
- 34
react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx View File

@@ -1,12 +1,15 @@
import { CommonTabKeys } from '@/enums';
import { getWorkspaceAssetCountReq } from '@/services/workspace';
import { to } from '@/utils/promise';
import { Flex, Select } from 'antd';
import { Flex, Typography } 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 }[]>([]);
const [assetCounts, setAssetCounts] = useState<{ title: string; value: number; icon: string }[]>(
[],
);

useEffect(() => {
// 获取工作空间资产数量
@@ -21,34 +24,37 @@ function AssetsManagement() {
{
title: '数据集',
value: dataset,
icon: 'dataset.png',
},
{
title: '模型',
value: model,
icon: 'model.png',
},
{
title: '代码配置',
value: 0,
icon: 'code.png',
},
{
title: '镜像',
value: image,
icon: 'image.png',
},
// {
// title: '组件',
// value: component,
// },
// {
// title: '代码配置',
// value: 0,
// },
{
title: '流水线模版',
value: workflow,
title: 'APP',
value: service,
icon: 'app.png',
},
{
title: '代码配置',
value: codeConfig,
title: '流水线模板',
value: workflow,
icon: 'experiment.png',
},
{
title: '服务',
value: service,
title: 'NOTEBOOKS',
value: codeConfig,
icon: 'train.png',
},
];
setAssetCounts(items);
@@ -60,26 +66,18 @@ function AssetsManagement() {

return (
<div className={styles['assets-management']}>
<Flex justify="space-between">
<div className={styles['assets-management__title']}>多形态资源库</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 gap="22px 0" wrap="wrap" style={{ marginTop: '40px', padding: '0 8px' }}>
<div className={styles['assets-management__title']}>个人资源库统计</div>
<div className={styles['assets-management__line']}></div>
<Flex className={styles['assets-management__content']}>
{assetCounts.map((item, index) => (
<div className={styles['assets-management__summary']} key={index}>
<div key={index} className={styles['assets-management__summary']}>
<Typography.Text
className={styles['assets-management__summary__value']}
ellipsis={{ tooltip: item.value }}
>
{item.value ?? '-'}
</Typography.Text>
<div className={styles['assets-management__summary__title']}>{item.title}</div>
<div className={styles['assets-management__summary__value']}>{item.value ?? '-'}</div>
</div>
))}
</Flex>


+ 27
- 0
react-ui/src/pages/Workspace/components/BlockTitle/index.less View File

@@ -0,0 +1,27 @@
.block-title {
display: flex;
align-items: center;
padding: 0.9375rem 1.25rem;
color: @text-color;
font-weight: 700;
font-size: 1rem;
.backgroundFullImage(url(@/assets/img/workspace/block-title-bg.png));

&__icon {
width: 1.875rem;
height: 1.875rem;
margin-right: 0.9375rem;
}

&__right {
margin-right: 0;
margin-left: auto;
color: @text-color-secondary;
font-weight: normal;
font-size: 0.875rem;

&:hover {
color: @primary-color;
}
}
}

+ 28
- 0
react-ui/src/pages/Workspace/components/BlockTitle/index.tsx View File

@@ -0,0 +1,28 @@
import KFIcon from '@/components/KFIcon';
import { Flex } from 'antd';
import styles from './index.less';

export type BlockTitleProps = {
title: string;
viewMore?: () => void;
};

function BlockTitle({ title, viewMore }: BlockTitleProps) {
return (
<div className={styles['block-title']}>
<img
src={require('@/assets/img/workspace/title-icon.png')}
className={styles['block-title__icon']}
></img>
<span>{title}</span>
{viewMore && (
<Flex className={styles['block-title__right']} onClick={viewMore}>
<span>查看全部</span>
<KFIcon type="icon-a-zu14930" font={12} style={{ marginLeft: 8 }} />
</Flex>
)}
</div>
);
}

export default BlockTitle;

+ 285
- 0
react-ui/src/pages/Workspace/components/ExperimentChart/charts.js View File

@@ -0,0 +1,285 @@
/**
* 绘制3d图
* @param pieData 总数据
* @param internalDiameterRatio:透明的空心占比
* @param distance 视角到主体的距离
* @param alpha 旋转角度
* @param pieHeight 立体的高度
* @param opacity 饼或者环的透明度
*/

const setSeriesItem = (item, k) => {
const seriesItem = {
name:
typeof item.name === 'undefined'
? `series${item}`
: item.name,
type: 'surface',
parametric: true,
wireframe: {
show: false
},
pieData: item,
pieStatus: {
selected: false,
hovered: false,
k: k
},
center: ['10%', '50%']
}
if ( item.color) {
seriesItem.itemStyle = { color: item.color }
}
return seriesItem
}

export const getPie3D = (pieData, internalDiameterRatio, distance, alpha, pieHeight, opacity = 1) => {
const series = []
let sumValue = 0
let startValue = 0
let endValue = 0
let legendData = []
let legendBfb = []
const k = 1 - internalDiameterRatio

pieData.sort((a, b) => {
return b.value - a.value
})
// 为每一个饼图数据,生成一个 series-surface 配置
for (let i = 0; i < pieData.length; i++) {
sumValue += pieData[i].value
const seriesItem = setSeriesItem(pieData[i], k)
// if (typeof pieData[i].itemStyle !== 'undefined') {
// const itemStyle = {}
// itemStyle.color =
// typeof pieData[i].itemStyle.color !== 'undefined'
// ? pieData[i].itemStyle.color
// : opacity
// itemStyle.opacity =
// typeof pieData[i].itemStyle.opacity !== 'undefined'
// ? pieData[i].itemStyle.opacity
// : opacity
// seriesItem.itemStyle = itemStyle
// }
series.push(seriesItem);
}

if (sumValue === 0) {
series.length = 0
series.push(setSeriesItem({ value: 100, color: '#d3d3d3', name: '暂无数据' }))
}

// 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
// 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
legendData = []
legendBfb = []
for (let i = 0; i < series.length; i++) {
endValue = startValue + series[i].pieData.value
series[i].pieData.startRatio = startValue / sumValue
series[i].pieData.endRatio = endValue / sumValue
series[i].parametricEquation = getParametricEquation(
series[i].pieData.startRatio,
series[i].pieData.endRatio,
false,
false,
k,
series[i].pieData.value
)
startValue = endValue
const bfb = fomatFloat(series[i].pieData.value / sumValue, 4)
legendData.push({
name: series[i].name,
value: bfb
})
legendBfb.push({
name: series[i].name,
value: bfb
})
}
const boxHeight = getHeight3D(series, pieHeight) // 通过pieHeight设定3d饼/环的高度,单位是px
// 准备待返回的配置项,把准备好的 legendData、series 传入。
const option = {
legend: {
show: false,
data: legendData,
orient: 'vertical',
left: 10,
top: 10,
itemGap: 10,
textStyle: {
color: '#A1E2FF'
},
icon: 'rect',
formatter: function (param) {
const item = legendBfb.filter(item => item.name === param)[0]
const bfs = fomatFloat(item.value * 100, 2) + '%'
return `${item.name}`
}
},
tooltip: {
backgroundColor: '#033b77',
borderColor: '#21f2c4',
textStyle: {
color: '#fff',
fontSize: 13
},
formatter: params => {
if (
params.seriesName !== 'mouseoutSeries' &&
params.seriesName !== 'pie2d'
) {
const bfb = (
(option.series[params.seriesIndex].pieData.endRatio -
option.series[params.seriesIndex].pieData.startRatio) *
100
).toFixed(2)
if (bfb === 'NaN') {
return (
`${params.seriesName}`
)
} else {
return (
`${params.seriesName}<br/>` +
`<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>` +
`${bfb}%`
)
}
}
}
},
xAxis3D: {
min: -1,
max: 1
},
yAxis3D: {
min: -1,
max: 1
},
zAxis3D: {
min: -1,
max: 1
},
grid3D: {
top: -20,
show: false,
boxHeight: 8, // 圆环的高度
viewControl: {
// 3d效果可以放大、旋转等,请自己去查看官方配置
alpha, // 角度
distance, // 调整视角到主体的距离,类似调整zoom
rotateSensitivity: 0, // 设置为0无法旋转
zoomSensitivity: 0, // 设置为0无法缩放
panSensitivity: 0, // 设置为0无法平移
autoRotate: false // 自动旋转
}
},
series:series
}
return option
}
/**
* 生成扇形的曲面参数方程,用于 series-surface.parametricEquation
*/
export const getParametricEquation = (startRatio, endRatio, isSelected, isHovered, k, h) => {
// 计算
const midRatio = (startRatio + endRatio) / 2
const startRadian = startRatio * Math.PI * 2
const endRadian = endRatio * Math.PI * 2
const midRadian = midRatio * Math.PI * 2
// 如果只有一个扇形,则不实现选中效果。
if (startRatio === 0 && endRatio === 1) {
isSelected = false
}
// 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
k = typeof k !== 'undefined' ? k : 1 / 3
// 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
const offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0
const offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0
// 计算高亮效果的放大比例(未高亮,则比例为 1)
const hoverRate = isHovered ? 1.05 : 1
// 返回曲面参数方程
return {
u: {
min: -Math.PI,
max: Math.PI * 3,
step: Math.PI / 32
},
v: {
min: 0,
max: Math.PI * 2,
step: Math.PI / 20
},
x: function (u, v) {
if (u < startRadian) {
return (
offsetX +
Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
)
}
if (u > endRadian) {
return (
offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
)
}
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate
},
y: function (u, v) {
if (u < startRadian) {
return (
offsetY +
Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
)
}
if (u > endRadian) {
return (
offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
)
}
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate
},
z: function (u, v) {
if (u < -Math.PI * 0.5) {
return Math.sin(u)
}
if (u > Math.PI * 2.5) {
return Math.sin(u) * 1
}
return Math.sin(v) > 0 ? 1 : -1
}
}
}
/**
* 获取3d丙图的最高扇区的高度
*/
const getHeight3D = (series, height) => {
series.sort((a, b) => {
return b.pieData.value - a.pieData.value
})
return (height * 25) / series[0].pieData.value
}
/**
* 格式化浮点数
*/
const fomatFloat = (num, n) => {
let f = parseFloat(num)
if (isNaN(f)) {
return false
}
f = Math.round(num * Math.pow(10, n)) / Math.pow(10, n) // n 幂
let s = f.toString()
let rs = s.indexOf('.')
// 判定如果是整数,增加小数点再补0
if (rs < 0) {
rs = s.length
s += '.'
}
while (s.length <= rs + n) {
s += '0'
}
return s
}

+ 12
- 6
react-ui/src/pages/Workspace/components/ExperimentChart/index.less View File

@@ -1,8 +1,14 @@
.experiment-chart {
flex: none;
width: 295px;
min-width: 295px;
height: 140px;
background: @workspace-background;
border-radius: 4px;
display: flex;
flex-direction: column;
width: 100%;
height: 15rem;
margin-top: 1.25rem;

&__title {
margin-bottom: 1.25rem;
color: @text-color;
font-weight: 700;
font-size: 1rem;
}
}

+ 29
- 16
react-ui/src/pages/Workspace/components/ExperimentChart/index.tsx View File

@@ -1,3 +1,4 @@
import { useRootFontSize } from '@/hooks/useRootFontSize';
import themes from '@/styles/theme.less';
import * as echarts from 'echarts';
import React, { useEffect, useMemo, useRef } from 'react';
@@ -87,6 +88,8 @@ type ExperimentChartProps = {
};

function ExperimentChart({ chartData, style }: ExperimentChartProps) {
const fontSize = useRootFontSize();

const chartRef = useRef<HTMLDivElement>(null);
const total =
chartData.Failed +
@@ -99,21 +102,21 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) {
() => ({
title: {
show: true,
left: '29%',
top: 'center',
left: '50%',
top: 3.5 * fontSize,
textAlign: 'center',
text: [`{a|${total}}`, '{b|实验状态}'].join('\n'),
textStyle: {
rich: {
a: {
color: themes['textColor'],
fontSize: 20,
fontSize: 1.25 * fontSize,
fontWeight: 700,
lineHeight: 28,
lineHeight: 1.75 * fontSize,
},
b: {
color: themes['textColorSecondary'],
fontSize: 10,
fontSize: 0.625 * fontSize,
fontWeight: 'normal',
},
},
@@ -123,20 +126,22 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) {
trigger: 'item',
},
legend: {
top: 'center',
right: '5%',
orient: 'vertical',
bottom: '0',
left: 'center',
orient: 'horizontal',
icon: 'circle',
itemWidth: 6,
itemGap: 20,
height: 100,
itemWidth: 0.75 * fontSize,
itemGap: 1.25 * fontSize,
textStyle: {
fontSize: 0.75 * fontSize,
},
},
color: [color1, color2, color3, color4, color5],
series: [
{
type: 'pie',
radius: ['70%', '80%'],
center: ['30%', '50%'],
center: ['50%', '40%'],
avoidLabelOverlap: false,
padAngle: 3,
itemStyle: {
@@ -165,7 +170,7 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) {
{
type: 'pie',
radius: '60%',
center: ['30%', '50%'],
center: ['50%', '40%'],
avoidLabelOverlap: false,
label: {
show: false,
@@ -192,7 +197,7 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) {
},
],
}),
[chartData, total],
[chartData, total, fontSize],
);

useEffect(() => {
@@ -202,16 +207,24 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) {
// 设置图表实例的配置项和数据
chart.setOption(options);

// 改变大小
const chartResize = () => {
chart.resize();
};

window.addEventListener('resize', chartResize);

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

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


+ 125
- 0
react-ui/src/pages/Workspace/components/ExperimentChart/test.tsx View File

@@ -0,0 +1,125 @@
import * as echarts from 'echarts';
import React from 'react';
import styles from './index.less';
import ThreePieChart from './threePieChart';

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) {
// useEffect(() => {
// // 创建一个echarts实例,返回echarts实例
// const chart = echarts.init(chartRef.current);

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

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

const pieData = [
{ name: '销售业务', value: 77.56, color: '#f00' },
{ name: '技术服务', value: 18.21, color: '#0f0' },
{ name: '配送业务', value: 3.67, color: '#00f' },
{ name: '其他业务', value: 0.17, color: '#ff0' },
];

return (
<div className={styles['experiment-chart']} style={style}>
<div className={styles['experiment-chart__title']}>实验统计</div>
<div style={{ width: '100%', height: '100%' }}>
<ThreePieChart
id="test"
data={pieData}
style={{ width: '100%', height: '200px' }}
></ThreePieChart>
</div>
</div>
);
}

export default ExperimentChart;

+ 84
- 0
react-ui/src/pages/Workspace/components/ExperimentChart/threePieChart.jsx View File

@@ -0,0 +1,84 @@
import * as echarts from 'echarts';
import { useEffect } from 'react';
import { getPie3D } from './charts';
function PieEchart({ id, data, style }) {
useEffect(() => {
if (data && data.length > 0) {
init(setLabel(data));
}
}, [data]);

const init = (data) => {
let huan_val = document.getElementById(id);
let myEcharts = echarts.init(huan_val);

let options = getOption(data);
console.log(options);
options.color = ['#4154f1', '#ffa900', '#00aa82', '#a296fd', '#40acfc'];
options.legend = {
orient: 'horizontal',
right: 'center',
bottom: 40,
width: '100%',
itemWidth: 11,
itemHeight: 11,
};
options.grid3D.top = -50;
options.series.map((item) => {
item.radius = [30, 120];
item.height = 20;
});
console.log(options);
myEcharts.setOption(options);
};
const getOption = (data) => {
console.log(data);
let option = getPie3D(data, 0.86, 150, 20, 20, 1);
return option;
};
const setLabel = (data) => {
console.log(data, 'data');

data.forEach((item, index) => {
item.itemStyle = {
color: item.color,
};
item.label = {
normal: {
show: true,
color: item.color,
rich: {
b: {
color: '#fff',
lineHeight: 25,
align: 'left',
},
c: {
fontSize: 22,
color: '#fff',
textShadowColor: '#1c90a6',
textShadowOffsetX: 0,
textShadowOffsetY: 2,
textShadowBlur: 5,
},
d: {
color: item.color,
align: 'left',
},
},
},
};
item.labelLine = {
normal: {
lineStyle: {
width: 1,
color: 'rgba(255,255,255,0.7)',
},
},
};
});
return data;
};
return <div id={id} style={style}></div>;
}
export default PieEchart;

+ 61
- 0
react-ui/src/pages/Workspace/components/Experiments/index.less View File

@@ -0,0 +1,61 @@
.experiments {
width: calc(40% - 1.25rem);
margin-left: 1.25rem;
background-color: white;
border-radius: 10px;
box-shadow: 0rem 0.1875rem 0.75rem rgba(151, 150, 180, 0.08);

&__content {
width: 100%;
padding: 1.25rem;
}

&__item {
margin-bottom: 0.875rem;
padding: 1rem 1.25rem;
background-color: #f7f9fd;
border: 1px solid transparent;
border-radius: 0.5rem;
cursor: pointer;

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

&:hover {
background-image: linear-gradient(173.59deg, #e7e8ff 0%, #f7f9fd 20.63%, #f7f9fd 100%);
border: 1px solid @primary-color;
}

&__title {
flex: 0 1 fit-content;
color: @text-color;
font-size: 1rem;
}

&__status {
margin-left: 1rem;
padding: 0.125rem 0.25rem;
color: #1664ff;
font-size: 0.75rem;
background-color: rgba(22, 100, 255, 0.07);
border-radius: 4px;
}

&__time {
margin-right: 0;
margin-left: auto;
color: @text-color-secondary;
font-size: 0.8125rem;
}

&:hover &__title {
color: @primary-color;
}

&__desc {
margin-top: 0.875rem;
font-size: 0.875rem;
}
}
}

+ 82
- 0
react-ui/src/pages/Workspace/components/Experiments/index.tsx View File

@@ -0,0 +1,82 @@
import { ExperimentInstance } from '@/types';
import { formatDate } from '@/utils/date';
import { useNavigate } from '@umijs/max';
import { Flex, Typography } from 'antd';
import BlockTitle from '../BlockTitle';
import styles from './index.less';

type ExperimentsProps = {
tableData: ExperimentInstance[];
};

const testData = [
{
name: '基于pytorch的XXXX开发',
description: '基于pytorch的XXXX开发基于pytorch的XXXX开发',
status: 'Running',
date: new Date(),
},
{
name: '基于pytorch的XXXX开发',
description: '基于pytorch的XXXX开发基于pytorch的XXXX开发',
status: 'Running',
date: new Date(),
},
{
name: '基于pytorch的XXXX开发',
description: '基于pytorch的XXXX开发基于pytorch的XXXX开发',
status: 'Running',
date: new Date(),
},
{
name: '基于pytorch的XXXX开发',
description: '基于pytorch的XXXX开发基于pytorch的XXXX开发',
status: 'Running',
date: new Date(),
},
];

function Experiments({ tableData }: ExperimentsProps) {
const navigate = useNavigate();
const gotoExperiment = (record: ExperimentInstance) => {
navigate(`/pipeline/experiment/instance/${record.workflow_id}/${record.id}`);
};

return (
<div className={styles.experiments}>
<BlockTitle title="正在运行的训练" viewMore={() => {}}></BlockTitle>
<div className={styles['experiments__content']}>
{testData.map((item) => {
return (
<div
className={styles['experiments__item']}
key={item.id}
onClick={() => gotoExperiment(item)}
>
<Flex align="center">
<Typography.Text
className={styles['experiments__item__title']}
ellipsis={{ tooltip: item.name }}
>
{item.name}
</Typography.Text>
<div className={styles['experiments__item__status']}>运行中</div>
<div className={styles['experiments__item__time']}>
{formatDate(item.date, 'YYYY-MM-DD')}
</div>
</Flex>
<Typography.Text
className={styles['experiments__item__desc']}
ellipsis={{ tooltip: item.description }}
>
{item.description}
</Typography.Text>
</div>
);
})}
</div>
</div>
);
}

export default Experiments;

+ 62
- 0
react-ui/src/pages/Workspace/components/Messages/index.less View File

@@ -0,0 +1,62 @@
.messages {
flex: 1;
margin-top: 1.25rem;

&__title {
color: @text-color;
font-weight: 700;
font-size: 1rem;
}

&__view-more {
margin-right: 0;
margin-left: auto;
color: @text-color-secondary;
font-weight: normal;
font-size: 0.875rem;

&:hover {
color: @primary-color;
}
}

&__content {
width: 100%;
margin-top: 1.25rem;
padding: 1rem 1.25rem;
overflow-y: auto;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0px 3px 6px rgba(151, 150, 180, 0.08);
}

&__item {
display: flex;
align-self: center;
padding: 0.625rem 0;
border-bottom: 1px dashed rgba(130, 132, 164, 0.18);

&__title {
flex: 1;
margin-right: 0.625rem;
color: @text-color-secondary;
font-size: 0.875rem;

&:hover {
color: @primary-color;
}
}

&__time {
flex: none;
margin-right: 0;
margin-left: auto;
color: @text-color-secondary;
font-size: 0.8125rem;
}

&:hover &__title {
color: @primary-color;
}
}
}

+ 106
- 0
react-ui/src/pages/Workspace/components/Messages/index.tsx View File

@@ -0,0 +1,106 @@
import KFIcon from '@/components/KFIcon';
import { MessageStatus, MessageType } from '@/enums';
import { Message, MessageResponse } from '@/pages/Message';
import { getMessageListReq, readMessagesReq } from '@/services/message';
import { ago } from '@/utils/date';
import { to } from '@/utils/promise';
import { useModel, useNavigate } from '@umijs/max';
import { Empty, Flex, Typography } from 'antd';
import { useCallback, useEffect, useState } from 'react';
import styles from './index.less';

function Messages() {
const { initialState } = useModel('@@initialState');
const { currentUser } = initialState || {};
const { userId } = currentUser || {};
const navigate = useNavigate();
const [messages, setMessages] = useState<Message[]>([]);

const getMessages = useCallback(async () => {
if (!userId) return;

const params: Record<string, any> = {
receiver: userId,
status: MessageStatus.All,
type: MessageType.System,
page: 1,
size: 10,
};
const [res] = await to(getMessageListReq(params));
if (res && res.data) {
const { records } = res.data as MessageResponse;
setMessages(records);
}
}, [userId]);

useEffect(() => {
getMessages();
}, [getMessages]);

// 标记为已读
const readMessages = async (message: Message) => {
const params: Record<string, any> = {
notificationIds: message.id,
status: MessageStatus.Readed,
receiver: message.receiver,
type: message.type,
};
readMessagesReq(params, true);
};

// 点击消息中心
const gotoMessageCenter = () => {
navigate(`/workspace/message`);
};

// 点击消息
const hanldeMessageClick = (message: Message) => {
if (message.status === MessageStatus.UnRead) {
readMessages(message);
}

if (message.notification_url) {
navigate(message.notification_url);
}
};

return (
<div className={styles.messages}>
<Flex align="center" justify="space-between">
<div className={styles['messages__title']}>消息通知</div>
<Flex align="center" className={styles['messages__view-more']} onClick={gotoMessageCenter}>
<span>消息中心</span>
<KFIcon type="icon-a-zu14930" font={12} style={{ marginLeft: 8 }} />
</Flex>
</Flex>
<div className={styles['messages__content']}>
{messages.length > 0 ? (
messages.map((item) => {
return (
<div
className={styles['messages__item']}
key={item.id}
onClick={() => hanldeMessageClick(item)}
>
<Typography.Text
className={styles['messages__item__content']}
ellipsis={{ tooltip: item.content }}
>
{item.content}
</Typography.Text>
<div className={styles['messages__item__time']}>{ago(item.created_at)}</div>
</div>
);
})
) : (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
// imageStyle={{ height: '40px', marginTop: '-10px' }}
/>
)}
</div>
</div>
);
}

export default Messages;

+ 120
- 0
react-ui/src/pages/Workspace/components/Notebooks/index.less View File

@@ -0,0 +1,120 @@
.notebooks {
width: 60%;
background-color: white;
border-radius: 10px;
box-shadow: 0rem 0.1875rem 0.75rem rgba(151, 150, 180, 0.08);

&__content {
display: flex;
flex-wrap: wrap;
gap: 1.25rem;
width: 100%;
padding: 1.25rem;
}

&__item {
width: calc((100% - 1.25rem) / 2);
padding: 1.25rem;
background-image: linear-gradient(173.59deg, #e7e8ff 0%, #f7f9fd 20.63%, #f7f9fd 100%);
border: 1px solid transparent;
border-radius: 0.5rem;
cursor: pointer;

&:hover {
background-image: linear-gradient(203.57deg, #e7e8ff 0%, #f7f9fd 20.63%, #f7f9fd 100%);
border: 1px solid @primary-color;
outline: 4px solid .addAlpha(@primary-color, 0.1) [];
}

&__title-icon {
flex: none;
width: 1.75rem;
height: 1.75rem;
margin-right: 0.75rem;
}

&__title {
flex: 0 1 fit-content;
color: @text-color;
font-size: 1rem;
}

&:hover &__title {
color: @primary-color;
}

&__arrow {
position: relative;
top: 0.125rem;
display: none;
width: 0.75rem;
height: 0.75rem;
margin-left: 0.5rem;
color: @primary-color;
}

&:hover &__arrow {
display: block;
}

&__desc {
margin-top: 0.625rem;
margin-bottom: 1.25rem;
font-size: 0.875rem;
}

&__user {
color: @text-color;
font-size: 0.875rem;
}

&__user-divider {
flex: none;
height: 0.625rem;
margin-right: 0.75rem;
margin-left: 0.75rem;
background-color: @home-divider-color;
}

&__timestamp {
flex: none;
color: @text-color-secondary;
font-size: 0.8125rem;
}

&:hover &__user-divider {
background-color: #d7d9e1;
}

&__category {
padding: 0.25rem 0.625rem;
color: #646a73;
font-size: 0.875rem;
background-color: .addAlpha(@primary-color, 0.07) [];
border-radius: 0.25rem;
}

&:hover &__category {
color: @primary-color;
}

&__info-btn {
display: none;
width: 100%;
height: 2rem;
}

&:hover &__info-btn {
display: block;
}

&__user-container {
display: flex;
height: 2rem;
}

&:hover &__user-container {
display: none;
}
}
}

+ 95
- 0
react-ui/src/pages/Workspace/components/Notebooks/index.tsx View File

@@ -0,0 +1,95 @@
import KFIcon from '@/components/KFIcon';
import { ModelData } from '@/pages/Dataset/config';
import { getPublicModelsReq } from '@/services/home';
import { to } from '@/utils/promise';
import { gotoPageIfLogin } from '@/utils/ui';
import { Button, Divider, Flex, Typography } from 'antd';
import { useEffect, useState } from 'react';
import BlockTitle from '../BlockTitle';
import styles from './index.less';

function NotebookComponent() {
const [modelData, setModelData] = useState<ModelData[]>([]);

useEffect(() => {
const getPublicModels = async () => {
const [res] = await to(getPublicModelsReq());
if (res && res.data) {
const { content = [] } = res.data;
setModelData(content.slice(0, 4));
}
};

getPublicModels();
}, []);

return (
<div className={styles.notebooks}>
<BlockTitle title="最近使用notebooks" viewMore={() => {}}></BlockTitle>
<div className={styles['notebooks__content']}>
{modelData.map((item) => {
return (
<div
className={styles['notebooks__item']}
key={item.id}
onClick={() => {
gotoPageIfLogin(
`/dataset/model/info/${item.id}?name=${item.name}&owner=${item.owner}&identifier=${item.identifier}&is_public=${item.is_public}`,
);
}}
>
<Flex align="center">
<img
src={require('@/assets/img/workspace/notebook-icon.png')}
className={styles['notebooks__item__title-icon']}
></img>
<Typography.Text
className={styles['notebooks__item__title']}
ellipsis={{ tooltip: item.name }}
>
{item.name}
</Typography.Text>
<KFIcon
type="icon-a-zu14930"
font={12}
className={styles['notebooks__item__arrow']}
/>
</Flex>
<Typography.Text
className={styles['notebooks__item__desc']}
ellipsis={{ tooltip: item.description }}
>
{item.description}
</Typography.Text>

<Flex align="center" gap={'0 0.625rem'} style={{ marginBottom: '1rem' }}>
<div className={styles['notebooks__item__category']}>电池开发</div>
<div className={styles['notebooks__item__category']}>材料研发</div>
</Flex>

<Button type="primary" className={styles['notebooks__item__info-btn']}>
查看详情
</Button>

<Flex align="center" className={styles['notebooks__item__user-container']}>
<Typography.Text
className={styles['notebooks__item__user']}
ellipsis={{ tooltip: item.create_by }}
>
{item.create_by}
</Typography.Text>
<Divider
type="vertical"
className={styles['notebooks__item__user-divider']}
></Divider>
<div className={styles['notebooks__item__timestamp']}>{item.time_ago}更新</div>
</Flex>
</div>
);
})}
</div>
</div>
);
}

export default NotebookComponent;

+ 111
- 0
react-ui/src/pages/Workspace/components/Search/index.less View File

@@ -0,0 +1,111 @@
.workspace-search {
padding: 0 170px;
color: @text-color;
font-size: 18px;

&__icon {
width: 36px;
height: 36px;
margin-right: 12px;
padding: 4px 5px;
background-color: white;
border-radius: 6px;
}

&__star {
position: relative;
top: -10px;
width: 36px;
height: 36px;
}

&__textarea {
position: relative;
margin-top: 15px;
margin-bottom: 20px;

:global {
.ant-input-affix-wrapper {
border: 2px solid rgba(22, 100, 255, 0.1);
border-radius: 20px;
box-shadow: 0px 0px 12px rgba(33, 73, 212, 0.04);

&:hover {
border: 2px solid @primary-color;
outline: 4px solid .addAlpha(@primary-color, 0.1) [];
}
}

.ant-input {
padding: 20px;
}

.ant-btn {
position: absolute;
right: 20px;
bottom: 20px;
z-index: 100;
padding: 6px 13px;
font-size: 14px;
background: @text-color !important;

&:hover {
outline: 4px solid .addAlpha(@text-color, 0.15) [];
}

&:active {
opacity: 0.8;
}
}
}
}

&__question {
display: flex;
align-items: center;
height: 40px;
padding: 0 14px;
color: @text-color-secondary;
font-size: 14px;
border: 1px solid .addAlpha(@text-color-secondary, 0.2) [];
border-radius: 15px;
box-shadow: 0px 0px 12px rgba(33, 73, 212, 0.04);

&:hover {
color: @primary-color;
background-color: rgba(81, 76, 249, 0.05);
border: 1px solid .addAlpha(@primary-color, 0.24) [];
box-shadow: 0px 0px 12px rgba(33, 73, 212, 0.04);
}

&__icon {
width: 19px;
height: 19px;
margin-right: 8px;
}
}

&__try {
margin-top: 24px;
margin-bottom: 34px;
color: @text-color-secondary;
font-size: 14px;

:global {
.ant-btn {
display: flex;
align-items: center;
height: 28px;
margin-left: 10px;
padding: 0 15px;
color: @text-color-secondary;
font-size: 14px;
background: white !important;

&:hover {
color: @primary-color !important;
}
}
}
}
}

+ 80
- 0
react-ui/src/pages/Workspace/components/Search/index.tsx View File

@@ -0,0 +1,80 @@
import KFIcon from '@/components/KFIcon';
import { Button, Flex, Input } from 'antd';
import styles from './index.less';

const hotQuestions = [
{
icon: '',
title: '高分子材料有哪些?',
},
{
icon: '',
title: '高分子材料有哪些?',
},
{
icon: '',
title: '高分子材料有哪些?',
},
{
icon: '',
title: '高分子材料有哪些?',
},
];

function WorkspaceSearch() {
return (
<div className={styles['workspace-search']}>
<Flex align="center">
<img
src={require('@/assets/img/workspace/search-icon.png')}
className={styles['workspace-search__icon']}
></img>
<span>今天你想问一些什么科学的问题</span>
<img
src={require('@/assets/img/workspace/search-star.gif')}
className={styles['workspace-search__star']}
></img>
</Flex>
<div className={styles['workspace-search__textarea']}>
<Input.TextArea
autoSize={{ minRows: 5, maxRows: 5 }}
placeholder="请输入科学问题进行搜索"
allowClear
/>
<Button
icon={<KFIcon type="icon-a-lianhe173" />}
color="default"
variant="solid"
shape="round"
>
开始搜索
</Button>
</div>
<Flex align="center" gap={'20px'} wrap>
{hotQuestions.map((item, index) => (
<div key={index} className={styles['workspace-search__question']}>
<img
src={require('@/assets/img/workspace/hot-icon.png')}
className={styles['workspace-search__question__icon']}
></img>
<span>{item.title}</span>
</div>
))}
</Flex>
<Flex className={styles['workspace-search__try']} align="center" justify="center">
<div>试一试主题:梅洛 · 庞蒂的“身体现象学”</div>
<Button
icon={<KFIcon type="icon-shuaxin" font={12} />}
color="default"
variant="solid"
shape="round"
size="middle"
>
换一换
</Button>
</Flex>
</div>
);
}

export default WorkspaceSearch;

+ 27
- 33
react-ui/src/pages/Workspace/components/UserPoints/index.less View File

@@ -1,40 +1,34 @@
.user-points {
display: flex;
flex: none;
flex-direction: column;
align-items: center;
width: 326px;
height: 228px;
padding: 0 20px;
.backgroundFullImage(url(@/assets/img/user-points-bg.png));
margin-bottom: 16px;

&__label {
margin-top: 60px;
&__title {
margin-bottom: 1.25rem;
color: @text-color;
font-size: @font-size-title;
font-family: DingTalk-JinBuTi;
font-weight: 700;
font-size: 1rem;
}

&__value {
width: 100%;
margin-top: 8px;
margin-bottom: 12px;
color: @primary-color;
font-size: 36px;
font-family: DingTalk-JinBuTi;
line-height: 43px;
text-align: center;
.singleLine();
}
&__content {
display: flex;
gap: 0 1.25rem;
padding: 1.25rem;
background-color: white;
border-radius: 0.625rem;

&__title {
margin-bottom: 0.625rem;
color: @text-color-secondary;
font-size: 0.875rem;
}

&__value {
color: @text-color;
font-weight: 700;
font-size: 1.25rem;
}

&__button {
padding: 8px 20px;
color: @primary-color;
font-size: @font-size-content;
background: rgba(255, 255, 255, 0.3);
border: 1px solid #ffffff;
border-radius: 8px;
cursor: pointer;
&__unit {
position: relative;
top: -0.25rem;
margin-left: 0.25rem;
}
}
}

+ 27
- 15
react-ui/src/pages/Workspace/components/UserPoints/index.tsx View File

@@ -3,7 +3,7 @@ import { getPointsStatisticsReq } from '@/services/points';
import { formatNumber } from '@/utils/format';
import { to } from '@/utils/promise';
import { useNavigate } from '@umijs/max';
import { Typography } from 'antd';
import { Flex, Typography } from 'antd';
import { useEffect, useState } from 'react';
import styles from './index.less';

@@ -27,20 +27,32 @@ function UserPoints() {

return (
<div className={styles['user-points']}>
<div className={styles['user-points__label']}>当前可用算力积分</div>
<Typography.Paragraph
className={styles['user-points__value']}
ellipsis={{ tooltip: userCredit }}
>
{userCredit}
</Typography.Paragraph>
<div
className={styles['user-points__button']}
onClick={() => {
navigate('/points');
}}
>
查看详情
<div className={styles['user-points__title']}>算力积分明细</div>
<div className={styles['user-points__content']}>
<div style={{ width: '50%' }}>
<div className={styles['user-points__content__title']}>当前可以积分</div>
<Flex align="flex-end">
<Typography.Text
className={styles['user-points__content__value']}
ellipsis={{ tooltip: userCredit }}
>
{userCredit}
</Typography.Text>
<span className={styles['user-points__content__unit']}> 分</span>
</Flex>
</div>
<div style={{ width: '50%' }}>
<div className={styles['user-points__content__title']}>总消耗积分</div>
<Flex align="flex-end">
<Typography.Text
className={styles['user-points__content__value']}
ellipsis={{ tooltip: userCredit }}
>
{userCredit}
</Typography.Text>
<span className={styles['user-points__content__unit']}> 分</span>
</Flex>
</div>
</div>
</div>
);


+ 6
- 62
react-ui/src/pages/Workspace/index.less View File

@@ -1,71 +1,15 @@
.workspace {
position: relative;
height: 100%;
padding: 20px 30px 10px;
padding: 80px 24px 70px;
overflow-y: auto;
background: linear-gradient(#ecf2fe, #f9fafb);
.backgroundFullImage(url(@/assets/img/workspace/workspace-bg.png));

&__overview {
flex: 1;
gap: 15px;
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;
flex-wrap: wrap;
gap: 15px;
align-items: center;

&__statistics {
flex: none;
// min-width: 500px;
background: linear-gradient(
123.08deg,
rgba(138, 138, 138, 0.06) 1.32%,
rgba(22, 100, 255, 0.02) 58.35%
);
border-radius: 4px;

// // 媒体查询
// @media screen and (max-width: 1600px) {
// flex: 1;
// }
}
}
}

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

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

&__robot-img {
position: fixed;
right: 20px;
bottom: 90px;
z-index: 99;
width: 56px;
height: 56px;
cursor: pointer;
width: calc(25% - 1.25rem);
padding: 1.25rem;
.backgroundFullImage(url(@/assets/img/workspace/points-bg.png));
}
}

+ 23
- 63
react-ui/src/pages/Workspace/index.tsx View File

@@ -2,17 +2,18 @@
import { getWorkspaceOverviewReq } from '@/services/workspace';
import { ExperimentInstance } from '@/types';
import { to } from '@/utils/promise';
import { Divider, Flex } from 'antd';
import { useEffect, useState } from 'react';
// import Draggable from 'react-draggable';
import AssetsManagement from './components/AssetsManagement';
import ExperimentChart, { type ExperimentStatistics } from './components/ExperimentChart';
import ExperitableTable from './components/ExperimentTable';
import QuickStart from './components/QuickStart';
import { type ExperimentStatistics } from './components/ExperimentChart';
// import RobotFrame from './components/RobotFrame';
import TotalStatistics from './components/TotalStatistics';
import { Flex } from 'antd';
import AssetsManagement from './components/AssetsManagement';
import ExperimentChart from './components/ExperimentChart';
import Experiments from './components/Experiments';
import Messages from './components/Messages';
import Notebooks from './components/Notebooks';
import WorkspaceSearch from './components/Search';
import UserPoints from './components/UserPoints';
import WorkspaceIntro from './components/WorkspaceIntro';
import styles from './index.less';

type OverviewData = {
@@ -24,11 +25,6 @@ type OverviewData = {

function Workspace() {
const [overviewData, setOverviewData] = useState<OverviewData>();
// const [robotFrameVisible, setRobotFrameVisible] = useState(false);
// const { handleStart, handleStop, handleDrag, handleClick } = useDraggable(() =>
// setRobotFrameVisible((prev) => !prev),
// );
const users: number[] = new Array(8).fill(0);

useEffect(() => {
getWorkspaceOverview();
@@ -44,59 +40,23 @@ function Workspace() {

return (
<div className={styles.workspace}>
<WorkspaceIntro></WorkspaceIntro>
<Flex gap={'15px'} wrap>
<div className={styles['workspace__overview']}>
<div className={styles['workspace__overview__title']}>运行概览</div>
<div className={styles['workspace__overview__content']}>
<Flex align="center" className={styles['workspace__overview__content__statistics']}>
<TotalStatistics
icon={require('@/assets/img/workspace-pipeline.png')}
title="流水线总数"
count={overviewData?.workflowCount}
/>
<Divider
type="vertical"
dashed
style={{ color: '1px dashed rgba(96, 107, 122, 0.19)', height: 68, margin: 0 }}
/>
<TotalStatistics
icon={require('@/assets/img/workspace-experiment.png')}
title="正在运行实例总数"
count={overviewData?.runningExperimentInsCount}
/>
</Flex>
<ExperitableTable
tableData={overviewData?.latestExperimentInsList || []}
></ExperitableTable>
{overviewData?.experimentInsStatus && (
<ExperimentChart chartData={overviewData?.experimentInsStatus}></ExperimentChart>
)}
</div>
</div>
</Flex>
<div className={styles['workspace__quick-start']}>
<QuickStart></QuickStart>
<div className={styles['workspace__user']}>
<UserPoints />
{/* <UserSpace users={users}></UserSpace> */}
<WorkspaceSearch></WorkspaceSearch>
<Flex gap={'0 1.25rem'} justify="stretch">
<div style={{ width: '75%' }}>
<AssetsManagement></AssetsManagement>
<Flex style={{ marginTop: 24 }} justify="stretch">
<Notebooks></Notebooks>
<Experiments></Experiments>
</Flex>
</div>
</div>
{/* <Draggable onStart={handleStart} onStop={handleStop} onDrag={handleDrag} bounds="body">
<img
className={styles['workspace__robot-img']}
src={require('@/assets/img/robot.png')}
onClick={handleClick}
draggable={false}
alt=""
></img>
</Draggable>

<RobotFrame
visible={robotFrameVisible}
onClose={() => setRobotFrameVisible(false)}
></RobotFrame> */}
<div className={styles['workspace__right']}>
<UserPoints></UserPoints>
{overviewData?.experimentInsStatus && (
<ExperimentChart chartData={overviewData?.experimentInsStatus}></ExperimentChart>
)}
<Messages></Messages>
</div>
</Flex>
</div>
);
}


Loading…
Cancel
Save