218 lines
9.6 KiB
JavaScript
218 lines
9.6 KiB
JavaScript
|
|
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>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|