mh_jy_safe_web/src/layout/FullOther/RiskControl.js
2026-05-07 16:37:24 +08:00

516 lines
15 KiB
JavaScript

// 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),
};
const scrollConfig = columns.length > 10 ? { x: columns.length * 100, y: 360 } : { y: 360 };
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>
<div style={{ flex: 1, padding: '10px' }}>
<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;