// 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}
`; params.forEach((param) => { result += `${param.marker}${param.seriesName}: ${param.value}
`; }); 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}
`; params.forEach((param) => { result += `${param.marker}${param.seriesName}: ${param.value}
`; }); 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
暂无风险数据
; } // 表格列配置(与 trainingContent 样式保持一致) const columns = [ { title: '公司名称', dataIndex: 'companyName', key: 'companyName', align: 'center', width: 120, render: (text) => {text}, }, { title: '重大风险', dataIndex: 'majorCount', key: 'majorCount', align: 'center', width: 100, render: (text) => {text}, }, { title: '较大风险', dataIndex: 'largerCount', key: 'largerCount', align: 'center', width: 100, render: (text) => {text}, }, { title: '一般风险', dataIndex: 'generalCount', key: 'generalCount', align: 'center', width: 100, render: (text) => {text}, }, { title: '低风险', dataIndex: 'lowCount', key: 'lowCount', align: 'center', width: 100, render: (text) => {text}, }, { title: '小计', dataIndex: 'totalCount', key: 'totalCount', align: 'center', width: 100, render: (text) => {text}, }, ]; 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 (
各家公司的风险统计情况
); }; 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 (
{/* 第一行 - 表格 + 堆叠柱状图 */}
{this.renderRiskTable()}
{/* 第二行 - 风险类别柱状图 */}
); } } export default RiskControl;