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

218 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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>
);
}