367 lines
11 KiB
JavaScript
367 lines
11 KiB
JavaScript
import React, { useState, useEffect } 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 { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
|
||
import { tools } from "@cqsjjb/jjb-common-lib";
|
||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||
import { NS_RISK_CENTER } from "~/enumerate/namespace";
|
||
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 CARD_THEMES = [
|
||
{ bg: "#fee2e2", color: "#dc2626" },
|
||
{ bg: "#fef3c7", color: "#d97706" },
|
||
{ bg: "#dbeafe", color: "#2563eb" },
|
||
{ bg: "#d1fae5", color: "#059669" },
|
||
];
|
||
|
||
const RiskCenter = (props) => {
|
||
const [form] = Form.useForm();
|
||
const [data] = useState(MOCK_DATA);
|
||
|
||
const { riskCenter, queryRiskOverviewAction } = props;
|
||
const { overviewStats = [], riskOverviewLoading } = riskCenter || {};
|
||
|
||
useEffect(() => {
|
||
queryRiskOverviewAction();
|
||
}, []);
|
||
|
||
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">
|
||
{overviewStats.map((stat, idx) => {
|
||
const theme = CARD_THEMES[idx % CARD_THEMES.length];
|
||
return (
|
||
<div className="stat-card" key={idx}>
|
||
<div className="stat-info">
|
||
<div className="stat-label">{stat.label}</div>
|
||
<div className="stat-value">{stat.value}</div>
|
||
<div className="stat-hint">{stat.hint}</div>
|
||
</div>
|
||
<div
|
||
className="stat-icon"
|
||
style={{ background: theme.bg, color: theme.color }}
|
||
>
|
||
{stat.label.charAt(0)}
|
||
</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, y: props.scrollY }}
|
||
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 Connect(
|
||
[NS_RISK_CENTER],
|
||
true,
|
||
)(AntdTableFuncControl(RiskCenter));
|