2026-04-28 16:52:44 +08:00
|
|
|
// RiskControl.js - 风险管控页面组件
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import { Table } from 'antd';
|
|
|
|
|
import styles from './../fullinter.less';
|
|
|
|
|
import echarts from 'echarts';
|
|
|
|
|
|
|
|
|
|
class RiskControl extends React.Component {
|
|
|
|
|
constructor(props) {
|
|
|
|
|
super(props);
|
|
|
|
|
this.echartsInstances = {
|
|
|
|
|
stackBarChart: null, // 堆叠柱状图
|
|
|
|
|
typeBarChart: null, // 风险类别柱状图
|
|
|
|
|
};
|
|
|
|
|
this.chartResizeHandlers = {};
|
|
|
|
|
this.isUnmounted = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
waitForElement = (elementId, maxRetries = 10) => {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
let retries = 0;
|
|
|
|
|
const checkInterval = setInterval(() => {
|
|
|
|
|
const element = document.getElementById(elementId);
|
|
|
|
|
if (element || retries >= maxRetries) {
|
|
|
|
|
clearInterval(checkInterval);
|
|
|
|
|
resolve(!!element);
|
|
|
|
|
}
|
|
|
|
|
retries++;
|
|
|
|
|
}, 50);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 堆叠柱状图: 各公司风险统计(与 trainingContent 样式一致)
|
|
|
|
|
renderStackBarChart = async () => {
|
|
|
|
|
if (this.isUnmounted) return;
|
|
|
|
|
const elementExists = await this.waitForElement('stackBarChart');
|
|
|
|
|
if (!elementExists || this.isUnmounted) return;
|
|
|
|
|
|
|
|
|
|
if (this.echartsInstances.stackBarChart) {
|
|
|
|
|
this.echartsInstances.stackBarChart.dispose();
|
|
|
|
|
this.echartsInstances.stackBarChart = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const chartDom = document.getElementById('stackBarChart');
|
|
|
|
|
if (!chartDom) return;
|
|
|
|
|
|
|
|
|
|
this.echartsInstances.stackBarChart = echarts.init(chartDom);
|
|
|
|
|
|
|
|
|
|
const { riskSubData } = this.props;
|
|
|
|
|
const riskList = riskSubData?.riskList || [];
|
|
|
|
|
|
|
|
|
|
if (riskList.length === 0) {
|
|
|
|
|
this.echartsInstances.stackBarChart.setOption({
|
|
|
|
|
title: {
|
|
|
|
|
text: '各家公司的风险统计情况',
|
|
|
|
|
x: 'center',
|
|
|
|
|
y: '25%',
|
|
|
|
|
textStyle: { fontSize: 16, color: '#999' },
|
|
|
|
|
},
|
|
|
|
|
graphic: {
|
|
|
|
|
type: 'text',
|
|
|
|
|
left: 'center',
|
|
|
|
|
top: 'middle',
|
|
|
|
|
style: {
|
|
|
|
|
text: '暂无数据',
|
|
|
|
|
fill: '#999',
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const companyNames = riskList.map((item) => item.companyName);
|
|
|
|
|
const majorCounts = riskList.map((item) => item.majorCount);
|
|
|
|
|
const largerCounts = riskList.map((item) => item.largerCount);
|
|
|
|
|
const generalCounts = riskList.map((item) => item.generalCount);
|
|
|
|
|
const lowCounts = riskList.map((item) => item.lowCount);
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
title: {
|
|
|
|
|
text: '各家公司的风险统计情况',
|
|
|
|
|
x: 'center',
|
|
|
|
|
y: '5%',
|
|
|
|
|
textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' },
|
|
|
|
|
},
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
axisPointer: { type: 'shadow' },
|
|
|
|
|
formatter: function (params) {
|
|
|
|
|
let result = `${params[0].axisValue}<br/>`;
|
|
|
|
|
params.forEach((param) => {
|
|
|
|
|
result += `${param.marker}${param.seriesName}: ${param.value}<br/>`;
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
legend: {
|
|
|
|
|
data: ['重大风险', '较大风险', '一般风险', '低风险'],
|
|
|
|
|
orient: 'vertical',
|
|
|
|
|
right: '3%',
|
|
|
|
|
top: '5%',
|
|
|
|
|
itemGap: 16,
|
|
|
|
|
itemWidth: 18,
|
|
|
|
|
itemHeight: 12,
|
|
|
|
|
textStyle: { color: '#000', fontSize: 14 },
|
|
|
|
|
},
|
|
|
|
|
grid: {
|
|
|
|
|
left: '8%',
|
|
|
|
|
right: '5%',
|
|
|
|
|
top: '18%',
|
|
|
|
|
bottom: '8%',
|
|
|
|
|
containLabel: true,
|
|
|
|
|
},
|
|
|
|
|
xAxis: [
|
|
|
|
|
{
|
|
|
|
|
type: 'category',
|
|
|
|
|
data: companyNames,
|
|
|
|
|
axisLine: { show: false },
|
|
|
|
|
axisTick: { show: false },
|
|
|
|
|
axisLabel: {
|
|
|
|
|
textStyle: { color: '#000' },
|
|
|
|
|
rotate: companyNames.length > 4 ? 15 : 0,
|
|
|
|
|
interval: 0,
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
yAxis: [
|
|
|
|
|
{
|
|
|
|
|
type: 'value',
|
|
|
|
|
show: true,
|
|
|
|
|
axisLine: { show: false },
|
|
|
|
|
axisTick: { show: false },
|
|
|
|
|
axisLabel: {
|
|
|
|
|
show: true,
|
|
|
|
|
textStyle: { color: '#000' },
|
|
|
|
|
},
|
|
|
|
|
splitLine: { show: false },
|
|
|
|
|
name: '风险数量',
|
|
|
|
|
nameTextStyle: { fontSize: 12 },
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
name: '重大风险',
|
|
|
|
|
type: 'bar',
|
|
|
|
|
stack: 'total',
|
|
|
|
|
data: majorCounts,
|
|
|
|
|
itemStyle: { normal: { color: '#c92a2a' } },
|
|
|
|
|
label: {
|
|
|
|
|
show: true,
|
|
|
|
|
position: 'inside',
|
|
|
|
|
textStyle: { color: '#fff', fontSize: 11 },
|
|
|
|
|
formatter: (params) => (params.value > 0 ? params.value : ''),
|
|
|
|
|
},
|
|
|
|
|
barWidth: '35%',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: '较大风险',
|
|
|
|
|
type: 'bar',
|
|
|
|
|
stack: 'total',
|
|
|
|
|
data: largerCounts,
|
|
|
|
|
itemStyle: { normal: { color: '#ffa94d' } },
|
|
|
|
|
label: {
|
|
|
|
|
show: true,
|
|
|
|
|
position: 'inside',
|
|
|
|
|
textStyle: { color: '#fff', fontSize: 11 },
|
|
|
|
|
formatter: (params) => (params.value > 0 ? params.value : ''),
|
|
|
|
|
},
|
|
|
|
|
barWidth: '35%',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: '一般风险',
|
|
|
|
|
type: 'bar',
|
|
|
|
|
stack: 'total',
|
|
|
|
|
data: generalCounts,
|
|
|
|
|
itemStyle: { normal: { color: '#ffe066' } },
|
|
|
|
|
label: {
|
|
|
|
|
show: true,
|
|
|
|
|
position: 'inside',
|
|
|
|
|
textStyle: { color: '#fff', fontSize: 11 },
|
|
|
|
|
formatter: (params) => (params.value > 0 ? params.value : ''),
|
|
|
|
|
},
|
|
|
|
|
barWidth: '35%',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: '低风险',
|
|
|
|
|
type: 'bar',
|
|
|
|
|
stack: 'total',
|
|
|
|
|
data: lowCounts,
|
|
|
|
|
itemStyle: { normal: { color: '#4285F4' } },
|
|
|
|
|
label: {
|
|
|
|
|
show: true,
|
|
|
|
|
position: 'inside',
|
|
|
|
|
textStyle: { color: '#fff', fontSize: 11 },
|
|
|
|
|
formatter: (params) => (params.value > 0 ? params.value : ''),
|
|
|
|
|
},
|
|
|
|
|
barWidth: '35%',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.echartsInstances.stackBarChart.setOption(option);
|
|
|
|
|
this.setupResizeHandler('stackBarChart', this.renderStackBarChart);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 风险类别柱状图
|
|
|
|
|
renderTypeBarChart = async () => {
|
|
|
|
|
if (this.isUnmounted) return;
|
|
|
|
|
const elementExists = await this.waitForElement('typeBarChart');
|
|
|
|
|
if (!elementExists || this.isUnmounted) return;
|
|
|
|
|
|
|
|
|
|
if (this.echartsInstances.typeBarChart) {
|
|
|
|
|
this.echartsInstances.typeBarChart.dispose();
|
|
|
|
|
this.echartsInstances.typeBarChart = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const chartDom = document.getElementById('typeBarChart');
|
|
|
|
|
if (!chartDom) return;
|
|
|
|
|
|
|
|
|
|
this.echartsInstances.typeBarChart = echarts.init(chartDom);
|
|
|
|
|
|
|
|
|
|
const { riskSubData } = this.props;
|
|
|
|
|
const riskTypeList = riskSubData?.riskTypeList || [];
|
|
|
|
|
|
|
|
|
|
if (riskTypeList.length === 0) {
|
|
|
|
|
this.echartsInstances.typeBarChart.setOption({
|
|
|
|
|
title: {
|
|
|
|
|
text: '风险类别统计情况',
|
|
|
|
|
x: 'center',
|
|
|
|
|
y: '25%',
|
|
|
|
|
textStyle: { fontSize: 16, color: '#999' },
|
|
|
|
|
},
|
|
|
|
|
graphic: {
|
|
|
|
|
type: 'text',
|
|
|
|
|
left: 'center',
|
|
|
|
|
top: 'middle',
|
|
|
|
|
style: {
|
|
|
|
|
text: '暂无数据',
|
|
|
|
|
fill: '#999',
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const typeNames = riskTypeList.map((item) => item.typeName);
|
|
|
|
|
const quantities = riskTypeList.map((item) => item.qty);
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
title: {
|
|
|
|
|
text: '风险类别统计情况',
|
|
|
|
|
x: 'center',
|
|
|
|
|
y: '5%',
|
|
|
|
|
textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' },
|
|
|
|
|
},
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
axisPointer: { type: 'shadow' },
|
|
|
|
|
formatter: function (params) {
|
|
|
|
|
let result = `${params[0].axisValue}<br/>`;
|
|
|
|
|
params.forEach((param) => {
|
|
|
|
|
result += `${param.marker}${param.seriesName}: ${param.value}<br/>`;
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
grid: {
|
|
|
|
|
left: '8%',
|
|
|
|
|
right: '5%',
|
|
|
|
|
top: '18%',
|
|
|
|
|
bottom: '8%',
|
|
|
|
|
containLabel: true,
|
|
|
|
|
},
|
|
|
|
|
xAxis: [
|
|
|
|
|
{
|
|
|
|
|
type: 'category',
|
|
|
|
|
data: typeNames,
|
|
|
|
|
axisLine: { show: false },
|
|
|
|
|
axisTick: { show: false },
|
|
|
|
|
axisLabel: {
|
|
|
|
|
textStyle: { color: '#000' },
|
|
|
|
|
// rotate: typeNames.length > 4 ? 15 : 0,
|
|
|
|
|
interval: 0,
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
yAxis: [
|
|
|
|
|
{
|
|
|
|
|
type: 'value',
|
|
|
|
|
show: true,
|
|
|
|
|
axisLine: { show: false },
|
|
|
|
|
axisTick: { show: false },
|
|
|
|
|
axisLabel: {
|
|
|
|
|
show: true,
|
|
|
|
|
textStyle: { color: '#000' },
|
|
|
|
|
},
|
|
|
|
|
splitLine: { show: false },
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
name: '风险数量',
|
|
|
|
|
type: 'bar',
|
|
|
|
|
data: quantities,
|
|
|
|
|
itemStyle: {
|
|
|
|
|
normal: {
|
|
|
|
|
color: '#4285F4', // 与 trainingContent 一致的蓝色
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
label: {
|
|
|
|
|
show: true,
|
|
|
|
|
position: 'top',
|
|
|
|
|
textStyle: { color: '#4285F4', fontSize: 12 },
|
|
|
|
|
formatter: (params) => `${params.value}`,
|
|
|
|
|
},
|
|
|
|
|
barWidth: '50%',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.echartsInstances.typeBarChart.setOption(option);
|
|
|
|
|
this.setupResizeHandler('typeBarChart', this.renderTypeBarChart);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 表格: 各公司风险统计明细表
|
|
|
|
|
renderRiskTable = () => {
|
|
|
|
|
const { riskSubData } = this.props;
|
|
|
|
|
const riskList = riskSubData?.riskList || [];
|
|
|
|
|
|
|
|
|
|
if (riskList.length === 0) {
|
|
|
|
|
return <div style={{ textAlign: 'center', padding: '50px', color: '#999' }}>暂无风险数据</div>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 表格列配置(与 trainingContent 样式保持一致)
|
|
|
|
|
const columns = [
|
|
|
|
|
{
|
|
|
|
|
title: '公司名称',
|
|
|
|
|
dataIndex: 'companyName',
|
|
|
|
|
key: 'companyName',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 120,
|
|
|
|
|
render: (text) => <strong>{text}</strong>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '重大风险',
|
|
|
|
|
dataIndex: 'majorCount',
|
|
|
|
|
key: 'majorCount',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 100,
|
|
|
|
|
render: (text) => <span style={{ color: '#c92a2a', fontWeight: 'bold' }}>{text}</span>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '较大风险',
|
|
|
|
|
dataIndex: 'largerCount',
|
|
|
|
|
key: 'largerCount',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 100,
|
|
|
|
|
render: (text) => <span style={{ color: '#ffa94d', fontWeight: 'bold' }}>{text}</span>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '一般风险',
|
|
|
|
|
dataIndex: 'generalCount',
|
|
|
|
|
key: 'generalCount',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 100,
|
|
|
|
|
render: (text) => <span style={{ color: '#ffe066', fontWeight: 'bold' }}>{text}</span>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '低风险',
|
|
|
|
|
dataIndex: 'lowCount',
|
|
|
|
|
key: 'lowCount',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 100,
|
|
|
|
|
render: (text) => <span style={{ color: '#4285F4', fontWeight: 'bold' }}>{text}</span>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '小计',
|
|
|
|
|
dataIndex: 'totalCount',
|
|
|
|
|
key: 'totalCount',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 100,
|
|
|
|
|
render: (text) => <strong style={{ color: '#000', fontSize: '14px' }}>{text}</strong>,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const tableData = riskList.map((item, index) => ({
|
|
|
|
|
key: index,
|
|
|
|
|
companyName: item.companyName,
|
|
|
|
|
majorCount: item.majorCount,
|
|
|
|
|
largerCount: item.largerCount,
|
|
|
|
|
generalCount: item.generalCount,
|
|
|
|
|
lowCount: item.lowCount,
|
|
|
|
|
totalCount: item.totalCount,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// 合计行
|
|
|
|
|
const summary = {
|
|
|
|
|
majorTotal: riskList.reduce((sum, item) => sum + (item.majorCount || 0), 0),
|
|
|
|
|
largerTotal: riskList.reduce((sum, item) => sum + (item.largerCount || 0), 0),
|
|
|
|
|
generalTotal: riskList.reduce((sum, item) => sum + (item.generalCount || 0), 0),
|
|
|
|
|
lowTotal: riskList.reduce((sum, item) => sum + (item.lowCount || 0), 0),
|
|
|
|
|
totalAll: riskList.reduce((sum, item) => sum + (item.totalCount || 0), 0),
|
|
|
|
|
};
|
2026-05-07 16:37:24 +08:00
|
|
|
const scrollConfig = columns.length > 10 ? { x: columns.length * 100, y: 360 } : { y: 360 };
|
2026-04-28 16:52:44 +08:00
|
|
|
return (
|
|
|
|
|
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
padding: '20px 0 0 0',
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
fontSize: '17px',
|
|
|
|
|
color: '#000',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
各家公司的风险统计情况
|
|
|
|
|
</div>
|
2026-05-07 16:37:24 +08:00
|
|
|
<div style={{ flex: 1, padding: '10px' }}>
|
2026-04-28 16:52:44 +08:00
|
|
|
<Table
|
|
|
|
|
columns={columns}
|
|
|
|
|
dataSource={tableData}
|
|
|
|
|
pagination={false}
|
|
|
|
|
scroll={scrollConfig}
|
|
|
|
|
size="small"
|
|
|
|
|
bordered
|
|
|
|
|
className={styles.certificateTable}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setupResizeHandler = (chartName, renderMethod) => {
|
|
|
|
|
const resizeHandler = () => {
|
|
|
|
|
if (this.echartsInstances[chartName] && !this.isUnmounted) {
|
|
|
|
|
this.echartsInstances[chartName].resize();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.chartResizeHandlers[chartName] = resizeHandler;
|
|
|
|
|
window.addEventListener('resize', resizeHandler);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
initAllCharts = () => {
|
|
|
|
|
if (this.isUnmounted) return;
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (this.isUnmounted) return;
|
|
|
|
|
this.renderStackBarChart();
|
|
|
|
|
this.renderTypeBarChart();
|
|
|
|
|
}, 100);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
disposeAllCharts = () => {
|
|
|
|
|
Object.keys(this.echartsInstances).forEach((key) => {
|
|
|
|
|
if (this.echartsInstances[key]) {
|
|
|
|
|
try {
|
|
|
|
|
this.echartsInstances[key].dispose();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn(`Dispose chart ${key} error:`, e);
|
|
|
|
|
}
|
|
|
|
|
this.echartsInstances[key] = null;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Object.keys(this.chartResizeHandlers).forEach((key) => {
|
|
|
|
|
if (this.chartResizeHandlers[key]) {
|
|
|
|
|
window.removeEventListener('resize', this.chartResizeHandlers[key]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.chartResizeHandlers = {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
|
this.isUnmounted = false;
|
|
|
|
|
this.initAllCharts();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps) {
|
|
|
|
|
if (prevProps.riskSubData !== this.props.riskSubData) {
|
|
|
|
|
this.renderStackBarChart();
|
|
|
|
|
this.renderTypeBarChart();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
|
this.isUnmounted = true;
|
|
|
|
|
this.disposeAllCharts();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
return (
|
|
|
|
|
<div className={styles.trainingContentWrapper}>
|
|
|
|
|
<div className={styles.trainingGrid}>
|
|
|
|
|
{/* 第一行 - 表格 + 堆叠柱状图 */}
|
|
|
|
|
<div className={styles.trainingRow}>
|
|
|
|
|
<div className={styles.trainingCard}>{this.renderRiskTable()}</div>
|
|
|
|
|
<div className={styles.trainingCard}>
|
|
|
|
|
<div id="stackBarChart" className={styles.trainingChartContainer}></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/* 第二行 - 风险类别柱状图 */}
|
|
|
|
|
<div className={styles.trainingRow}>
|
|
|
|
|
<div className={styles.trainingCard}>
|
|
|
|
|
<div id="typeBarChart" className={styles.trainingChartContainer}></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default RiskControl;
|