Browse Source

feat: 完成工作空间echart

dev-new-workspace
zhaowei 10 months ago
parent
commit
8299b814fb
16 changed files with 257 additions and 413 deletions
  1. +1
    -1
      react-ui/package.json
  2. BIN
      react-ui/src/assets/img/workspace/echart-bg.png
  3. +0
    -2
      react-ui/src/hooks/useRootFontSize.ts
  4. +0
    -164
      react-ui/src/pages/Welcome.tsx
  5. +29
    -28
      react-ui/src/pages/Workspace/components/ExperimentChart/charts.js
  6. +6
    -2
      react-ui/src/pages/Workspace/components/ExperimentChart/index.less
  7. +167
    -0
      react-ui/src/pages/Workspace/components/ExperimentChart/new.tsx
  8. +0
    -125
      react-ui/src/pages/Workspace/components/ExperimentChart/test.tsx
  9. +0
    -84
      react-ui/src/pages/Workspace/components/ExperimentChart/threePieChart.jsx
  10. +4
    -0
      react-ui/src/pages/Workspace/components/Experiments/index.tsx
  11. +4
    -0
      react-ui/src/pages/Workspace/components/Messages/index.less
  12. +1
    -1
      react-ui/src/pages/Workspace/components/Messages/index.tsx
  13. +9
    -0
      react-ui/src/pages/Workspace/components/Search/index.tsx
  14. +1
    -0
      react-ui/src/pages/Workspace/components/UserPoints/index.less
  15. +1
    -0
      react-ui/src/pages/Workspace/index.less
  16. +34
    -6
      react-ui/src/pages/Workspace/index.tsx

+ 1
- 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 PORT=8001 npm run start:mock",
"dev-no-sso": "cross-env NO_SSO=true 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",


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

Before After
Width: 705  |  Height: 1023  |  Size: 159 kB Width: 682  |  Height: 470  |  Size: 61 kB

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

@@ -18,8 +18,6 @@ export function useRootFontSize() {
const html = document.documentElement;
const observer = new MutationObserver(() => {
const size = getHtmlFontSize();
console.log('zzz', size);

setFontSize(size);
});
observer.observe(html, {


+ 0
- 164
react-ui/src/pages/Welcome.tsx View File

@@ -1,164 +0,0 @@
import { PageContainer } from '@ant-design/pro-components';
import { useModel } from '@umijs/max';
import { Card, theme } from 'antd';
import React from 'react';

/**
* 每个单独的卡片,为了复用样式抽成了组件
* @param param0
* @returns
*/
const InfoCard: React.FC<{
title: string;
index: number;
desc: string;
href: string;
}> = ({ title, href, index, desc }) => {
const { useToken } = theme;

const { token } = useToken();

return (
<div
style={{
backgroundColor: token.colorBgContainer,
boxShadow: token.boxShadow,
borderRadius: '8px',
fontSize: '14px',
color: token.colorTextSecondary,
lineHeight: '22px',
padding: '16px 19px',
minWidth: '220px',
flex: 1,
}}
>
<div
style={{
display: 'flex',
gap: '4px',
alignItems: 'center',
}}
>
<div
style={{
width: 48,
height: 48,
lineHeight: '22px',
backgroundSize: '100%',
textAlign: 'center',
padding: '8px 16px 16px 12px',
color: '#FFF',
fontWeight: 'bold',
backgroundImage:
"url('https://gw.alipayobjects.com/zos/bmw-prod/daaf8d50-8e6d-4251-905d-676a24ddfa12.svg')",
}}
>
{index}
</div>
<div
style={{
fontSize: '16px',
color: token.colorText,
paddingBottom: 8,
}}
>
{title}
</div>
</div>
<div
style={{
fontSize: '14px',
color: token.colorTextSecondary,
textAlign: 'justify',
lineHeight: '22px',
marginBottom: 8,
}}
>
{desc}
</div>
<a href={href} target="_blank" rel="noreferrer">
了解更多 {'>'}
</a>
</div>
);
};

const Welcome: React.FC = () => {
const { token } = theme.useToken();
const { initialState } = useModel('@@initialState');
return (
<PageContainer>
<Card
style={{
borderRadius: 8,
}}
bodyStyle={{
backgroundImage:
initialState?.settings?.navTheme === 'realDark'
? 'background-image: linear-gradient(75deg, #1A1B1F 0%, #191C1F 100%)'
: 'background-image: linear-gradient(75deg, #FBFDFF 0%, #F5F7FF 100%)',
}}
>
<div
style={{
backgroundPosition: '100% -30%',
backgroundRepeat: 'no-repeat',
backgroundSize: '274px auto',
backgroundImage:
"url('https://gw.alipayobjects.com/mdn/rms_a9745b/afts/img/A*BuFmQqsB2iAAAAAAAAAAAAAAARQnAQ')",
}}
>
<div
style={{
fontSize: '20px',
color: token.colorTextHeading,
}}
>
欢迎使用 Ant Design Pro
</div>
<p
style={{
fontSize: '14px',
color: token.colorTextSecondary,
lineHeight: '22px',
marginTop: 16,
marginBottom: 32,
width: '65%',
}}
>
Ant Design Pro 是一个整合了 umi,Ant Design 和 ProComponents
的脚手架方案。致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。
</p>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
gap: 16,
}}
>
<InfoCard
index={1}
href="https://umijs.org/docs/introduce/introduce"
title="了解 umi"
desc="umi 是一个可扩展的企业级前端应用框架,umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。"
/>
<InfoCard
index={2}
title="了解 ant design"
href="https://ant.design"
desc="antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。"
/>
<InfoCard
index={3}
title="了解 Pro Components"
href="https://procomponents.ant.design"
desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
/>
</div>
</div>
</Card>
</PageContainer>
);
};

export default Welcome;

+ 29
- 28
react-ui/src/pages/Workspace/components/ExperimentChart/charts.js View File

@@ -8,32 +8,6 @@
* @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
@@ -255,7 +229,7 @@ export const getParametricEquation = (startRatio, endRatio, isSelected, isHovere
/**
* 获取3d丙图的最高扇区的高度
*/
const getHeight3D = (series, height) => {
export const getHeight3D = (series, height) => {
series.sort((a, b) => {
return b.pieData.value - a.pieData.value
})
@@ -265,7 +239,7 @@ const getHeight3D = (series, height) => {
/**
* 格式化浮点数
*/
const fomatFloat = (num, n) => {
export const fomatFloat = (num, n) => {
let f = parseFloat(num)
if (isNaN(f)) {
return false
@@ -283,3 +257,30 @@ const fomatFloat = (num, n) => {
}
return s
}

export 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
}


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

@@ -1,6 +1,5 @@
.experiment-chart {
display: flex;
flex-direction: column;
flex: none;
width: 100%;
height: 15rem;
margin-top: 1.25rem;
@@ -11,4 +10,9 @@
font-weight: 700;
font-size: 1rem;
}

&__canvas {
height: calc(15rem - 1.53rem - 1.25rem);
.backgroundFullImage(url(@/assets/img/workspace/echart-bg.png));
}
}

+ 167
- 0
react-ui/src/pages/Workspace/components/ExperimentChart/new.tsx View File

@@ -0,0 +1,167 @@
import { useRootFontSize } from '@/hooks/useRootFontSize';
import * as echarts from 'echarts';
import 'echarts-gl';
import React, { useEffect, useMemo, useRef } from 'react';
import { getParametricEquation, setSeriesItem } from './charts';
import styles from './index.less';

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

type ExperimentChartProps = {
style?: React.CSSProperties;
chartData: { name: string; value: number; color?: string }[];
};

function ExperimentChart({ chartData, style }: ExperimentChartProps) {
const fontSize = useRootFontSize();
const chartRef = useRef<HTMLDivElement>(null);

const series = useMemo(() => {
const series = [];
let startValue = 0;
let endValue = 0;
let legendData = [];
let legendBfb = [];
const k = 1 - 0.86;
const sumValue = chartData.reduce((res, item) => res + item.value, 0);

// 为每一个饼图数据,生成一个 series-surface 配置
for (let i = 0; i < chartData.length; i++) {
const seriesItem = setSeriesItem(chartData[i], k);
series.push(seriesItem);
}

// 使用上一次遍历时,计算出的数据和 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;
//@ts-ignore
series[i].parametricEquation = getParametricEquation(
series[i].pieData.startRatio,
series[i].pieData.endRatio,
false,
false,
k,
series[i].pieData.value,
);
startValue = endValue;
const bfb = series[i].pieData.value / sumValue;
legendData.push({
name: series[i].name,
value: bfb,
});
legendBfb.push({
name: series[i].name,
value: bfb,
});
}

return series;
}, [chartData]);

// @ts-ignore
const options: echarts.EChartsOption = useMemo(
() => ({
tooltip: {
backgroundColor: 'white',
textStyle: {
color: '#494a6f',
fontSize: 14,
},
formatter: (params: any) => {
if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
const value = chartData[params.seriesIndex].value;
return (
`<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>` +
`<span style="margin-right: 16px">${params.seriesName}</span>` +
`${value}`
);
}
},
},
xAxis3D: {
min: -1,
max: 1,
},
yAxis3D: {
min: -1,
max: 1,
},
zAxis3D: {
min: -1,
max: 1,
},
grid3D: {
top: -2 * fontSize,
show: false,
boxHeight: 8, // 圆环的高度
viewControl: {
// 3d效果可以放大、旋转等,请自己去查看官方配置
alpha: 20, // 角度
distance: 150, // 调整视角到主体的距离,类似调整zoom
rotateSensitivity: 0, // 设置为0无法旋转
zoomSensitivity: 0, // 设置为0无法缩放
panSensitivity: 0, // 设置为0无法平移
autoRotate: false, // 自动旋转
},
},
series: series,
legend: {
bottom: fontSize,
left: 'center',
orient: 'horizontal',
icon: 'rect',
itemWidth: 0.75 * fontSize,
itemHeight: 0.75 * fontSize,
itemGap: 1.25 * fontSize,
textStyle: {
fontSize: 0.75 * fontSize,
},
selectedMode: false,
},
}),
[chartData, series, fontSize],
);

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

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

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

window.addEventListener('resize', chartResize);

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

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

export default ExperimentChart;

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

@@ -1,125 +0,0 @@
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;

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

@@ -1,84 +0,0 @@
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;

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

@@ -11,24 +11,28 @@ type ExperimentsProps = {

const testData = [
{
id: 1,
name: '基于pytorch的XXXX开发',
description: '基于pytorch的XXXX开发基于pytorch的XXXX开发',
status: 'Running',
date: new Date(),
},
{
id: 2,
name: '基于pytorch的XXXX开发',
description: '基于pytorch的XXXX开发基于pytorch的XXXX开发',
status: 'Running',
date: new Date(),
},
{
id: 3,
name: '基于pytorch的XXXX开发',
description: '基于pytorch的XXXX开发基于pytorch的XXXX开发',
status: 'Running',
date: new Date(),
},
{
id: 4,
name: '基于pytorch的XXXX开发',
description: '基于pytorch的XXXX开发基于pytorch的XXXX开发',
status: 'Running',


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

@@ -1,5 +1,8 @@
.messages {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
margin-top: 1.25rem;

&__title {
@@ -21,6 +24,7 @@
}

&__content {
flex: 1;
width: 100%;
margin-top: 1.25rem;
padding: 1rem 1.25rem;


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

@@ -66,7 +66,7 @@ function Messages() {

return (
<div className={styles.messages}>
<Flex align="center" justify="space-between">
<Flex align="center" justify="space-between" style={{ flex: 'none' }}>
<div className={styles['messages__title']}>消息通知</div>
<Flex align="center" className={styles['messages__view-more']} onClick={gotoMessageCenter}>
<span>消息中心</span>


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

@@ -1,5 +1,6 @@
import KFIcon from '@/components/KFIcon';
import { Button, Flex, Input } from 'antd';
import { useState } from 'react';
import styles from './index.less';

const hotQuestions = [
@@ -22,6 +23,11 @@ const hotQuestions = [
];

function WorkspaceSearch() {
const [question, setQuestion] = useState('');
const handle = () => {
console.log(question);
};

return (
<div className={styles['workspace-search']}>
<Flex align="center">
@@ -37,6 +43,8 @@ function WorkspaceSearch() {
</Flex>
<div className={styles['workspace-search__textarea']}>
<Input.TextArea
value={question}
onChange={(e) => setQuestion(e.target.value)}
autoSize={{ minRows: 5, maxRows: 5 }}
placeholder="请输入科学问题进行搜索"
allowClear
@@ -46,6 +54,7 @@ function WorkspaceSearch() {
color="default"
variant="solid"
shape="round"
onClick={handle}
>
开始搜索
</Button>


+ 1
- 0
react-ui/src/pages/Workspace/components/UserPoints/index.less View File

@@ -1,4 +1,5 @@
.user-points {
flex: none;
&__title {
margin-bottom: 1.25rem;
color: @text-color;


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

@@ -9,6 +9,7 @@
display: flex;
flex-direction: column;
width: calc(25% - 1.25rem);
height: 44.75rem;
padding: 1.25rem;
.backgroundFullImage(url(@/assets/img/workspace/points-bg.png));
}


+ 34
- 6
react-ui/src/pages/Workspace/index.tsx View File

@@ -8,7 +8,8 @@ import { type ExperimentStatistics } from './components/ExperimentChart';
// import RobotFrame from './components/RobotFrame';
import { Flex } from 'antd';
import AssetsManagement from './components/AssetsManagement';
import ExperimentChart from './components/ExperimentChart';
// import ExperimentChart from './components/ExperimentChart';
import ExperimentChart from '@/pages/Workspace/components/ExperimentChart/new';
import Experiments from './components/Experiments';
import Messages from './components/Messages';
import Notebooks from './components/Notebooks';
@@ -25,6 +26,35 @@ type OverviewData = {

function Workspace() {
const [overviewData, setOverviewData] = useState<OverviewData>();
const chartData = overviewData?.experimentInsStatus
? [
{
value: overviewData.experimentInsStatus.Failed,
name: '失败',
color: 'rgba(217, 71, 71, 1)',
},
{
value: overviewData.experimentInsStatus.Succeeded,
name: '成功',
color: 'rgba(0, 180, 42, 1)',
},
{
value: overviewData.experimentInsStatus.Terminated,
name: '中止',
color: 'rgba(122, 123, 178, 1)',
},
{
value: overviewData.experimentInsStatus.Pending,
name: '等待',
color: 'rgba(253, 174, 83, 1)',
},
{
value: overviewData.experimentInsStatus.Running,
name: '运行中',
color: 'rgba(50, 135, 251, 1)',
},
]
: [];

useEffect(() => {
getWorkspaceOverview();
@@ -41,19 +71,17 @@ function Workspace() {
return (
<div className={styles.workspace}>
<WorkspaceSearch></WorkspaceSearch>
<Flex gap={'0 1.25rem'} justify="stretch">
<Flex gap={'0 1.25rem'}>
<div style={{ width: '75%' }}>
<AssetsManagement></AssetsManagement>
<Flex style={{ marginTop: 24 }} justify="stretch">
<Flex style={{ marginTop: '1.5rem' }} justify="stretch">
<Notebooks></Notebooks>
<Experiments></Experiments>
</Flex>
</div>
<div className={styles['workspace__right']}>
<UserPoints></UserPoints>
{overviewData?.experimentInsStatus && (
<ExperimentChart chartData={overviewData?.experimentInsStatus}></ExperimentChart>
)}
<ExperimentChart chartData={chartData}></ExperimentChart>
<Messages></Messages>
</div>
</Flex>


Loading…
Cancel
Save