safety-eval-service-frontend/src/pages/Container/Supervision/Dashboard/index.js

218 lines
9.6 KiB
JavaScript
Raw Normal View History

import React, { useEffect, useMemo, useRef } from 'react';
import * as echarts from 'echarts';
import {
AuditOutlined,
CheckCircleOutlined,
ClockCircleOutlined,
FileDoneOutlined,
FileSearchOutlined,
FormOutlined,
FundProjectionScreenOutlined,
SoundOutlined,
ReconciliationOutlined,
SafetyCertificateOutlined,
ScheduleOutlined,
SnippetsOutlined,
} from '@ant-design/icons';
import './index.css';
const overviewGroups = [
{
title: '备案整体情况',
items: [
{ label: '已备案机构数', value: 985, yoy: '23%', qoq: '5%', yoyUp: true, qoqUp: false },
{ label: '开发评价企业数', value: 5846, yoy: '3%', qoq: '1%', yoyUp: true, qoqUp: false },
{ label: '开展业务机构数', value: 89512, yoy: '15%', qoq: '2%', yoyUp: true, qoqUp: false },
{ label: '备案评价师数', value: 10005, yoy: '10%', qoq: '5%', yoyUp: false, qoqUp: false },
],
},
{
title: '本年数据统计',
items: [
{ label: '开展评价企业数', value: 523, yoy: '23%', qoq: '5%', yoyUp: true, qoqUp: false },
{ label: '评价项目数', value: 5600, yoy: '3%', qoq: '1%', yoyUp: true, qoqUp: false },
{ label: '评价服务机构数', value: 1200, yoy: '15%', qoq: '2%', yoyUp: true, qoqUp: false },
{ label: '评价服务项目数', value: 2500, yoy: '10%', qoq: '5%', yoyUp: false, qoqUp: false },
],
},
];
const todoItems = [
{ label: '待审核', value: 956, color: '#f1f3ff' },
{ label: '待审核', value: 548, color: '#d9f6fb' },
{ label: '待审核', value: 1548, color: '#faf4dc' },
];
const filingCards = [
{ group: '本地备案机构数', label: '平台备案数', value: 2654, icon: SafetyCertificateOutlined, color: '#ffb739', bg: '#fffbe8' },
{ group: '本地备案机构数', label: '完全备案确认', value: 4561, icon: CheckCircleOutlined, color: '#3aaef6', bg: '#eefaff' },
{ group: '异地备案机构数', label: '平台备案数', value: 126, icon: SafetyCertificateOutlined, color: '#60ddc7', bg: '#ecfbf8' },
{ group: '异地备案机构数', label: '完全备案确认', value: 254, icon: CheckCircleOutlined, color: '#928cf1', bg: '#f3f3ff' },
];
const flowNodes = [
{ label: '项目合同签订', value: 5632, icon: FileDoneOutlined, color: '#409eff' },
{ label: '待风险分析', value: 951, icon: FundProjectionScreenOutlined, color: '#8d7cf6' },
{ label: '待成立项目组', value: 5632, icon: SnippetsOutlined, color: '#43d2c5' },
{ label: '待制定工作计划', value: 5632, icon: ScheduleOutlined, color: '#ff9f42' },
{ label: '待初勘', value: 5632, icon: AuditOutlined, color: '#8b7df3' },
{ label: '归档', value: 651, icon: ReconciliationOutlined, color: '#08c7ad' },
{ label: '过程管控', value: 951, icon: ClockCircleOutlined, color: '#3ca6f5' },
{ label: '待现场勘查', value: 5632, icon: FileSearchOutlined, color: '#8b7df3' },
{ label: '待从业告知', value: 5632, icon: SoundOutlined, color: '#ffb327' },
{ label: '待编制检查表', value: 456, icon: FormOutlined, color: '#8b7df3' },
];
function EChart({ option, className }) {
const ref = useRef(null);
const chartRef = useRef(null);
useEffect(() => {
if (!ref.current) return undefined;
chartRef.current = echarts.init(ref.current, null, { renderer: 'svg' });
const resize = () => chartRef.current && chartRef.current.resize();
const observer = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
if (observer) observer.observe(ref.current);
window.addEventListener('resize', resize);
return () => {
window.removeEventListener('resize', resize);
if (observer) observer.disconnect();
if (chartRef.current) chartRef.current.dispose();
chartRef.current = null;
};
}, []);
useEffect(() => {
if (chartRef.current) chartRef.current.setOption(option, true);
}, [option]);
return <div className={className} ref={ref} />;
}
function Card({ title, children, className = '' }) {
return (
<section className={`supervision-home-card ${className}`}>
<h3>{title}</h3>
{children}
</section>
);
}
function Trend({ label, value, up }) {
return <span>{label} <i className={up ? 'up' : 'down'}>{up ? '▲' : '▼'}</i> {value}</span>;
}
function Overview() {
return (
<Card title="备案整体情况" className="overview-card">
{overviewGroups.map(group => (
<div className="overview-group" key={group.title}>
{group.title !== '备案整体情况' && <h3>{group.title}</h3>}
<div className="overview-grid">
{group.items.map(item => (
<div className="overview-item" key={item.label}>
<p>{item.label}</p>
<strong>{item.value}</strong>
<div><Trend label="同比" value={item.yoy} up={item.yoyUp} /><Trend label="环比" value={item.qoq} up={item.qoqUp} /></div>
</div>
))}
</div>
</div>
))}
</Card>
);
}
function Todo() {
return (
<Card title="待办事项" className="todo-home-card">
<div className="todo-home-grid">
{todoItems.map((item, index) => <div className="todo-home-item" style={{ background: item.color }} key={`${item.label}-${index}`}><p>{item.label}</p><strong>{item.value}</strong></div>)}
</div>
</Card>
);
}
function FilingGroup({ title, cards }) {
return (
<Card title={title} className="filing-card">
<div className="filing-grid">
{cards.map(item => {
const Icon = item.icon;
return <div className="filing-item" style={{ background: item.bg }} key={item.label}><span style={{ background: item.color }}><Icon /></span><div><p>{item.label}</p><strong style={{ color: item.color }}>{item.value}</strong></div></div>;
})}
</div>
</Card>
);
}
function FlowStatus() {
return (
<Card title="当前评价状态状态统计" className="flow-card">
<div className="region-select">请选择县区 <span></span></div>
<div className="flow-summary"><span>评价项目合计228</span><span>185</span><span>21</span><span>22</span></div>
<div className="flow-grid">
{flowNodes.map((item, index) => {
const Icon = item.icon;
return <div className={`flow-node flow-node-${index + 1}`} key={item.label}><span style={{ background: item.color }}><Icon /></span><div><p>{item.label}</p><strong>{item.value}</strong></div>{index < 4 && <i className="arrow-right" />}{index === 4 && <i className="arrow-down" />}{index > 5 && <i className="arrow-left" />}</div>;
})}
</div>
</Card>
);
}
function LineChart() {
const option = useMemo(() => ({
color: ['#409eff', '#ff9f43'],
grid: { left: 38, right: 16, top: 34, bottom: 28 },
legend: { right: 8, top: 0, itemWidth: 14, itemHeight: 6, textStyle: { color: '#555', fontSize: 12 } },
tooltip: { trigger: 'axis', backgroundColor: '#fff', borderColor: '#edf0f5', textStyle: { color: '#333' } },
xAxis: { type: 'category', boundaryGap: false, data: ['定期检测', '现状评价', '控制效果评价', '预评价', '设计专篇', '委托检测'], axisLabel: { color: '#555', fontSize: 11 }, axisTick: { show: false }, axisLine: { lineStyle: { color: '#e6eaf0' } } },
yAxis: { type: 'value', max: 200, splitNumber: 4, axisLabel: { color: '#777' }, splitLine: { lineStyle: { color: '#eef1f5', type: 'dashed' } } },
series: [
{ name: '企业数', type: 'line', smooth: true, symbolSize: 6, data: [5, 38, 52, 33, 92, 78, 120, 116, 132], areaStyle: { color: 'rgba(64,158,255,.08)' } },
{ name: '评价数', type: 'line', smooth: true, symbolSize: 6, data: [4, 47, 31, 42, 72, 75, 64, 140, 121] },
],
}), []);
return <Card title="评价类型分类" className="chart-card line-home-card"><EChart className="home-line-chart" option={option} /></Card>;
}
function BarChart() {
const option = useMemo(() => ({
color: ['#5b9bff', '#ffc75b'],
grid: { left: 38, right: 18, top: 38, bottom: 36 },
legend: { right: 8, top: 0, itemWidth: 12, itemHeight: 12, textStyle: { color: '#555', fontSize: 12 } },
tooltip: { trigger: 'axis', backgroundColor: '#fff', borderColor: '#edf0f5', textStyle: { color: '#333' } },
xAxis: { type: 'category', data: ['矿山开采业', '危险化学品行业', '烟花爆竹行业', '金属冶炼与加工', '建筑施工', '交通运输业'], axisLabel: { color: '#555', fontSize: 11, interval: 0 }, axisTick: { show: false }, axisLine: { lineStyle: { color: '#e6eaf0' } } },
yAxis: { type: 'value', max: 200, splitNumber: 4, axisLabel: { color: '#777' }, splitLine: { lineStyle: { color: '#eef1f5' } } },
series: [
{ name: '企业数', type: 'bar', barWidth: 12, data: [158, 76, 112, 108, 158, 143] },
{ name: '评价数', type: 'bar', barWidth: 12, data: [92, 136, 57, 136, 123, 121] },
],
}), []);
return <Card title="企业类型评价统计" className="chart-card bar-home-card"><EChart className="home-bar-chart" option={option} /></Card>;
}
export default function SupervisionDashboard() {
return (
<div className="supervision-dashboard-page">
<div className="supervision-dashboard-grid">
<div className="dashboard-left">
<Overview />
<div className="filing-row">
<FilingGroup title="本地备案机构数" cards={filingCards.slice(0, 2)} />
<FilingGroup title="异地备案机构数" cards={filingCards.slice(2)} />
</div>
<FlowStatus />
</div>
<div className="dashboard-right">
<Todo />
<LineChart />
<BarChart />
</div>
</div>
</div>
);
}