400 lines
12 KiB
JavaScript
400 lines
12 KiB
JavaScript
|
|
import React, { useState } from "react";
|
|||
|
|
import { Button, Form, Table, Tag } from "antd";
|
|||
|
|
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
|||
|
|
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
|
|||
|
|
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
|
|||
|
|
import { tools } from "@cqsjjb/jjb-common-lib";
|
|||
|
|
import "./index.less";
|
|||
|
|
|
|||
|
|
const { router } = tools;
|
|||
|
|
|
|||
|
|
/** 预警大类配置 */
|
|||
|
|
const RISK_TYPE_MAP = {
|
|||
|
|
qual: { label: "资质预警", color: "error" },
|
|||
|
|
proj: { label: "项目预警", color: "warning" },
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/** 处理阶段配置 */
|
|||
|
|
const STAGE_MAP = {
|
|||
|
|
untreated: { label: "未处理", color: "error" },
|
|||
|
|
sent: { label: "已发机构", color: "warning" },
|
|||
|
|
review: { label: "待复核", color: "processing" },
|
|||
|
|
closed: { label: "已闭环", color: "success" },
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/** 机构端状态配置 */
|
|||
|
|
const ORG_STATUS_MAP = {
|
|||
|
|
unsent: { label: "未发送", color: "error" },
|
|||
|
|
unread: { label: "机构未读", color: "warning" },
|
|||
|
|
processing: { label: "处理中", color: "processing" },
|
|||
|
|
submitted: { label: "已提交材料", color: "processing" },
|
|||
|
|
done: { label: "整改完成", color: "success" },
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/** 预警子类选项 */
|
|||
|
|
const RISK_SUBTYPE_OPTIONS = [
|
|||
|
|
{ label: "资质条件保持异常", value: "qual_condition" },
|
|||
|
|
{ label: "人员比例异常", value: "qual_person_rate" },
|
|||
|
|
{ label: "属地材料待核验", value: "qual_area" },
|
|||
|
|
{ label: "项目启动超期", value: "proj_start" },
|
|||
|
|
{ label: "关键节点超期", value: "proj_node" },
|
|||
|
|
{ label: "项目人员异常", value: "proj_person" },
|
|||
|
|
{ label: "从业告知异常", value: "proj_notify" },
|
|||
|
|
{ label: "现场勘查异常", value: "proj_site" },
|
|||
|
|
{ label: "过程控制/归档异常", value: "proj_archive" },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/** 项目预警规则提示 */
|
|||
|
|
const PROJ_RULES = [
|
|||
|
|
"项目启动超期",
|
|||
|
|
"关键节点超期",
|
|||
|
|
"项目人员异常",
|
|||
|
|
"从业告知异常",
|
|||
|
|
"现场勘查异常",
|
|||
|
|
"过程控制/归档异常",
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/** mock 数据 */
|
|||
|
|
const MOCK_DATA = [
|
|||
|
|
{
|
|||
|
|
id: "W-QUAL-2026-012",
|
|||
|
|
type: "qual",
|
|||
|
|
subtype: "qual_condition",
|
|||
|
|
target: "重庆渝安风险评估中心",
|
|||
|
|
sources: ["财务材料.固定资产", "场所核验.建筑面积", "人员库.专职人数"],
|
|||
|
|
content: "固定资产、场所面积、档案室面积和专职人员数量不满足保持条件。",
|
|||
|
|
stage: "untreated",
|
|||
|
|
orgStatus: "unsent",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "W-QUAL-2026-011",
|
|||
|
|
type: "qual",
|
|||
|
|
subtype: "qual_person_rate",
|
|||
|
|
target: "重庆恒安安全评价有限公司",
|
|||
|
|
sources: ["人员库.职称等级", "人员库.注册安全工程师"],
|
|||
|
|
content: "高级职称比例 26%,合计比例 59%,低于资质保持条件。",
|
|||
|
|
stage: "sent",
|
|||
|
|
orgStatus: "unread",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "W-QUAL-2026-013",
|
|||
|
|
type: "qual",
|
|||
|
|
subtype: "qual_area",
|
|||
|
|
target: "北京中安评价中心重庆分公司",
|
|||
|
|
sources: ["本市社保.缴纳人数", "场所核验.租赁合同"],
|
|||
|
|
content: "机构已提交本地场所和人员说明,等待监管复核。",
|
|||
|
|
stage: "review",
|
|||
|
|
orgStatus: "submitted",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "W-PROJ-2026-021",
|
|||
|
|
type: "proj",
|
|||
|
|
subtype: "proj_start",
|
|||
|
|
target: "华安化工年度安全现状评价",
|
|||
|
|
sources: ["客户信息.评价到期日", "项目库.新建时间"],
|
|||
|
|
content: "评价周期已到期 14 天,系统内尚未建立新一轮评价项目。",
|
|||
|
|
stage: "untreated",
|
|||
|
|
orgStatus: "unsent",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "W-PROJ-2026-018",
|
|||
|
|
type: "proj",
|
|||
|
|
subtype: "proj_node",
|
|||
|
|
target: "安环检测年度安全现状评价",
|
|||
|
|
sources: ["项目计划.节点期限", "流程记录.当前节点"],
|
|||
|
|
content: "项目停留在过程控制环节 12 天,已超过计划完成时间。",
|
|||
|
|
stage: "sent",
|
|||
|
|
orgStatus: "processing",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "W-PROJ-2026-015",
|
|||
|
|
type: "proj",
|
|||
|
|
subtype: "proj_person",
|
|||
|
|
target: "鼎信建设安全验收评价",
|
|||
|
|
sources: ["项目组.项目负责人", "人员库.在职状态"],
|
|||
|
|
content: "项目负责人已提交离职申请,项目尚未完成负责人变更。",
|
|||
|
|
stage: "review",
|
|||
|
|
orgStatus: "submitted",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "W-PROJ-2026-009",
|
|||
|
|
type: "proj",
|
|||
|
|
subtype: "proj_site",
|
|||
|
|
target: "华安化工园区安全预评价",
|
|||
|
|
sources: ["现场勘查.签到签退", "项目组.参与人员"],
|
|||
|
|
content: "现场勘查仅有签到记录,无签退记录和现场照片。",
|
|||
|
|
stage: "closed",
|
|||
|
|
orgStatus: "done",
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const RiskCenter = () => {
|
|||
|
|
const [form] = Form.useForm();
|
|||
|
|
const [data] = useState(MOCK_DATA);
|
|||
|
|
|
|||
|
|
/** 统计卡片数据 */
|
|||
|
|
const stats = {
|
|||
|
|
qualRisk: data.filter((i) => i.type === "qual").length,
|
|||
|
|
projRisk: data.filter((i) => i.type === "proj").length,
|
|||
|
|
pendingFeedback: data.filter((i) => i.orgStatus === "submitted").length,
|
|||
|
|
todayClosed: data.filter((i) => i.stage === "closed").length,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleSearch = () => {
|
|||
|
|
// 接口搜索
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleReset = () => {
|
|||
|
|
form.resetFields();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const columns = [
|
|||
|
|
{
|
|||
|
|
title: "预警编号",
|
|||
|
|
dataIndex: "id",
|
|||
|
|
width: 160,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: "预警大类",
|
|||
|
|
dataIndex: "type",
|
|||
|
|
width: 100,
|
|||
|
|
render: (type) => {
|
|||
|
|
const cfg = RISK_TYPE_MAP[type];
|
|||
|
|
return cfg ? <Tag color={cfg.color}>{cfg.label}</Tag> : "--";
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: "预警子类",
|
|||
|
|
dataIndex: "subtype",
|
|||
|
|
width: 140,
|
|||
|
|
render: (subtype) => {
|
|||
|
|
const opt = RISK_SUBTYPE_OPTIONS.find((o) => o.value === subtype);
|
|||
|
|
return opt?.label || "--";
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: "风险对象",
|
|||
|
|
dataIndex: "target",
|
|||
|
|
width: 200,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: "来源字段",
|
|||
|
|
dataIndex: "sources",
|
|||
|
|
width: 280,
|
|||
|
|
render: (sources) => (
|
|||
|
|
<div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
|
|||
|
|
{(sources || []).map((s, i) => (
|
|||
|
|
<span key={i} className="source-pill">
|
|||
|
|
{s}
|
|||
|
|
</span>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: "触发内容",
|
|||
|
|
dataIndex: "content",
|
|||
|
|
width: 320,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: "处理阶段",
|
|||
|
|
dataIndex: "stage",
|
|||
|
|
width: 100,
|
|||
|
|
render: (stage) => {
|
|||
|
|
const cfg = STAGE_MAP[stage];
|
|||
|
|
return cfg ? <Tag color={cfg.color}>{cfg.label}</Tag> : "--";
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: "机构端状态",
|
|||
|
|
dataIndex: "orgStatus",
|
|||
|
|
width: 110,
|
|||
|
|
render: (status) => {
|
|||
|
|
const cfg = ORG_STATUS_MAP[status];
|
|||
|
|
return cfg ? <Tag color={cfg.color}>{cfg.label}</Tag> : "--";
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: "监管操作",
|
|||
|
|
width: 100,
|
|||
|
|
fixed: "right",
|
|||
|
|
render: () => (
|
|||
|
|
<Button type="primary" size="small">
|
|||
|
|
处理
|
|||
|
|
</Button>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<PageLayout title="风险预警中心">
|
|||
|
|
<div className="risk-center">
|
|||
|
|
{/* 统计卡片 */}
|
|||
|
|
<div className="stat-grid cols-4">
|
|||
|
|
<div className="stat-card">
|
|||
|
|
<div className="stat-info">
|
|||
|
|
<div className="stat-label">资质预警</div>
|
|||
|
|
<div className="stat-value stat-value--high">
|
|||
|
|
{stats.qualRisk}
|
|||
|
|
</div>
|
|||
|
|
<div className="stat-hint">
|
|||
|
|
条件保持、人员比例、属地材料
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
className="stat-icon"
|
|||
|
|
style={{ background: "#fee2e2", color: "#dc2626" }}
|
|||
|
|
>
|
|||
|
|
资
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="stat-card">
|
|||
|
|
<div className="stat-info">
|
|||
|
|
<div className="stat-label">项目预警</div>
|
|||
|
|
<div className="stat-value stat-value--mid">
|
|||
|
|
{stats.projRisk}
|
|||
|
|
</div>
|
|||
|
|
<div className="stat-hint">
|
|||
|
|
进度、人员、告知、勘查、质控
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
className="stat-icon"
|
|||
|
|
style={{ background: "#fef3c7", color: "#d97706" }}
|
|||
|
|
>
|
|||
|
|
项
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="stat-card">
|
|||
|
|
<div className="stat-info">
|
|||
|
|
<div className="stat-label">待机构反馈</div>
|
|||
|
|
<div className="stat-value stat-value--mid">
|
|||
|
|
{stats.pendingFeedback}
|
|||
|
|
</div>
|
|||
|
|
<div className="stat-hint">整改说明或证明材料</div>
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
className="stat-icon"
|
|||
|
|
style={{ background: "#dbeafe", color: "#2563eb" }}
|
|||
|
|
>
|
|||
|
|
反
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="stat-card">
|
|||
|
|
<div className="stat-info">
|
|||
|
|
<div className="stat-label">今日闭环</div>
|
|||
|
|
<div className="stat-value stat-value--ok">
|
|||
|
|
{stats.todayClosed}
|
|||
|
|
</div>
|
|||
|
|
<div className="stat-hint">监管复核已完成</div>
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
className="stat-icon"
|
|||
|
|
style={{ background: "#d1fae5", color: "#059669" }}
|
|||
|
|
>
|
|||
|
|
结
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 项目预警规则提示 */}
|
|||
|
|
<div className="source-note">
|
|||
|
|
<strong>项目预警规则:</strong>
|
|||
|
|
{PROJ_RULES.map((rule) => (
|
|||
|
|
<span key={rule} className="source-pill">
|
|||
|
|
{rule}
|
|||
|
|
</span>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 搜索表单 */}
|
|||
|
|
<SearchForm
|
|||
|
|
style={{ marginBottom: 12 }}
|
|||
|
|
form={form}
|
|||
|
|
formLine={[
|
|||
|
|
<Form.Item key="keyword" name="keyword">
|
|||
|
|
<ControlWrapper.Input
|
|||
|
|
label="关键词"
|
|||
|
|
placeholder="机构/项目/人员/预警编号"
|
|||
|
|
allowClear
|
|||
|
|
/>
|
|||
|
|
</Form.Item>,
|
|||
|
|
<Form.Item key="riskType" name="riskType">
|
|||
|
|
<ControlWrapper.Select
|
|||
|
|
label="预警大类"
|
|||
|
|
placeholder="全部"
|
|||
|
|
allowClear
|
|||
|
|
style={{ width: "100%" }}
|
|||
|
|
options={[
|
|||
|
|
{ label: "资质预警", value: "qual" },
|
|||
|
|
{ label: "项目预警", value: "proj" },
|
|||
|
|
]}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>,
|
|||
|
|
<Form.Item key="subtype" name="subtype">
|
|||
|
|
<ControlWrapper.Select
|
|||
|
|
label="预警子类"
|
|||
|
|
placeholder="全部"
|
|||
|
|
allowClear
|
|||
|
|
style={{ width: "100%" }}
|
|||
|
|
options={RISK_SUBTYPE_OPTIONS}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>,
|
|||
|
|
<Form.Item key="stage" name="stage">
|
|||
|
|
<ControlWrapper.Select
|
|||
|
|
label="处理阶段"
|
|||
|
|
placeholder="全部"
|
|||
|
|
allowClear
|
|||
|
|
style={{ width: "100%" }}
|
|||
|
|
options={[
|
|||
|
|
{ label: "未处理", value: "untreated" },
|
|||
|
|
{ label: "已发机构", value: "sent" },
|
|||
|
|
{ label: "待复核", value: "review" },
|
|||
|
|
{ label: "已闭环", value: "closed" },
|
|||
|
|
]}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>,
|
|||
|
|
]}
|
|||
|
|
onReset={handleReset}
|
|||
|
|
onFinish={handleSearch}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* 工具条 */}
|
|||
|
|
<div className="risk-toolbar">
|
|||
|
|
<div className="risk-toolbar-left">
|
|||
|
|
<span className="risk-toolbar-desc">
|
|||
|
|
资质预警沿用资质保持监控结果;项目预警由项目计划、人员、从业告知、现场记录和审核归档数据触发。
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="risk-toolbar-right">
|
|||
|
|
<Button className="btn-ghost" size="small">
|
|||
|
|
导出预警
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 数据表格 */}
|
|||
|
|
<Table
|
|||
|
|
rowKey="id"
|
|||
|
|
columns={columns}
|
|||
|
|
dataSource={data}
|
|||
|
|
scroll={{ x: 1600 }}
|
|||
|
|
pagination={{
|
|||
|
|
total: data.length,
|
|||
|
|
showSizeChanger: true,
|
|||
|
|
showQuickJumper: true,
|
|||
|
|
showTotal: (total) => `共 ${total} 条`,
|
|||
|
|
current: router.query.current || 1,
|
|||
|
|
pageSize: router.query.size || 10,
|
|||
|
|
onChange: (page, size) => {
|
|||
|
|
router.query = { ...router.query, current: page, size };
|
|||
|
|
},
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</PageLayout>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default RiskCenter;
|