feat
parent
d1ca41a530
commit
f8f241d544
|
|
@ -12,8 +12,8 @@ module.exports = {
|
||||||
// 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095
|
// 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095
|
||||||
//API_HOST: "https://gbs-gateway.qhdsafety.com",
|
//API_HOST: "https://gbs-gateway.qhdsafety.com",
|
||||||
|
|
||||||
API_HOST: "http://192.168.0.152",
|
//API_HOST: "http://192.168.0.152",
|
||||||
//API_HOST: "http://192.168.0.150", //太浅
|
API_HOST: "http://192.168.0.150", //太浅
|
||||||
//API_HOST: "http://192.168.0.152",
|
//API_HOST: "http://192.168.0.152",
|
||||||
//API_HOST: "http://192.168.0.103", //huwei
|
//API_HOST: "http://192.168.0.103", //huwei
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
|
|
||||||
|
|
||||||
export const identifyPartList = declareRequest(
|
|
||||||
"coursewareLoading",
|
|
||||||
"Post > @/risk/busIdentifyPart/list",
|
|
||||||
);
|
|
||||||
|
|
@ -78,3 +78,23 @@ export const evalProjectDocSave = declareRequest(
|
||||||
"evalProjectDocSaveLoading",
|
"evalProjectDocSaveLoading",
|
||||||
"Post > @/safetyEval/institution/eval-project-doc/save",
|
"Post > @/safetyEval/institution/eval-project-doc/save",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const evalProjectProgressStat = declareRequest(
|
||||||
|
"evalProjectProgressLoading",
|
||||||
|
"Get > /safetyEval/institution/eval-project/progress-stat",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalProjectProgressPage = declareRequest(
|
||||||
|
"evalProjectProgressPageLoading",
|
||||||
|
"Get > /safetyEval/institution/eval-project/progress-page",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalProjectRiskWarningStat = declareRequest(
|
||||||
|
"riskWarningStatLoading",
|
||||||
|
"Get > /safetyEval/institution/eval-project/risk-warning-stat",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalProjectRiskWarningPage = declareRequest(
|
||||||
|
"riskWarningPageLoading",
|
||||||
|
"Get > /safetyEval/institution/eval-project/risk-warning-page",
|
||||||
|
);
|
||||||
|
|
@ -332,3 +332,23 @@ export const MATERIAL_TYPE_MAP = MATERIAL_TYPE_OPTIONS.reduce((acc, cur) => {
|
||||||
acc[cur.value] = cur.label;
|
acc[cur.value] = cur.label;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
/** 风险预警 — 风险等级映射 */
|
||||||
|
export const RISK_LEVEL_MAP = {
|
||||||
|
HIGH: { color: "error", text: "高风险" },
|
||||||
|
MIDDLE: { color: "warning", text: "中风险" },
|
||||||
|
LOW: { color: "default", text: "低风险" },
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 风险预警 — 处理状态映射 */
|
||||||
|
export const RISK_STATUS_MAP = {
|
||||||
|
UNHANDLED: { color: "error", text: "未处理" },
|
||||||
|
PROCESSING: { color: "warning", text: "处理中" },
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 项目进度 — 进度状态映射 */
|
||||||
|
export const PROGRESS_STATUS_MAP = {
|
||||||
|
NORMAL: { color: "success", text: "正常" },
|
||||||
|
NEAR: { color: "warning", text: "项目临期" },
|
||||||
|
DELAY: { color: "error", text: "项目延期" },
|
||||||
|
};
|
||||||
|
|
@ -212,10 +212,20 @@ const menuItems = [
|
||||||
icon: <FileTextOutlined />,
|
icon: <FileTextOutlined />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "/safetyEval/container/SafetyEvalBusiness/EvalProject/ProjectDocLibrary",
|
key: "/safetyEval/container/SafetyEvalBusiness/ProjectDocLibrary",
|
||||||
label: "项目资料管理",
|
label: "项目资料管理",
|
||||||
icon: <FileTextOutlined />,
|
icon: <FileTextOutlined />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "/safetyEval/container/SafetyEvalBusiness/Progress",
|
||||||
|
label: "项目进度管理",
|
||||||
|
icon: <BarChartOutlined />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "/safetyEval/container/SafetyEvalBusiness/RiskWarning",
|
||||||
|
label: "风险预警管理",
|
||||||
|
icon: <ExperimentOutlined />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "/safetyEval/container/SafetyEvalBusiness/CustomerManage",
|
key: "/safetyEval/container/SafetyEvalBusiness/CustomerManage",
|
||||||
label: "安评客户管理",
|
label: "安评客户管理",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,326 @@
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Table,
|
||||||
|
Button,
|
||||||
|
Tag,
|
||||||
|
Modal,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Card,
|
||||||
|
Statistic,
|
||||||
|
Descriptions,
|
||||||
|
Steps,
|
||||||
|
Tooltip,
|
||||||
|
} from "antd";
|
||||||
|
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/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 { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||||
|
import { PROGRESS_STATUS_MAP } from "~/enumerate/constant";
|
||||||
|
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||||
|
import { tools } from "@cqsjjb/jjb-common-lib";
|
||||||
|
import "./index.less";
|
||||||
|
|
||||||
|
const { router } = tools;
|
||||||
|
|
||||||
|
const ProjectProgress = (props) => {
|
||||||
|
const [searchForm] = Form.useForm();
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [statData, setStatData] = useState({});
|
||||||
|
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
||||||
|
const [currentRecord, setCurrentRecord] = useState(null);
|
||||||
|
|
||||||
|
const { evalProjectProgressPageLoading } = props.safetyEvalBusiness;
|
||||||
|
|
||||||
|
const getStat = async () => {
|
||||||
|
const res = await props.evalProjectProgressStat();
|
||||||
|
if (res?.success !== false) {
|
||||||
|
setStatData(res?.data || {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const params = {
|
||||||
|
...router.query,
|
||||||
|
current: router.query.current || 1,
|
||||||
|
size: router.query.size || 10,
|
||||||
|
};
|
||||||
|
const res = await props.evalProjectProgressPage(params);
|
||||||
|
if (res?.success !== false) {
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setTotal(res?.total || 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
searchForm.setFieldsValue(router.query);
|
||||||
|
getStat();
|
||||||
|
getData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSearch = (values) => {
|
||||||
|
router.query = { ...router.query, ...values, current: 1, size: 10 };
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = (values) => {
|
||||||
|
searchForm.resetFields();
|
||||||
|
router.query = { ...values, current: 1, size: 10 };
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (pagination) => {
|
||||||
|
router.query = {
|
||||||
|
...router.query,
|
||||||
|
current: pagination.current,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleView = (record) => {
|
||||||
|
setCurrentRecord(record);
|
||||||
|
setDetailModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderProgressDots = (nodes) => {
|
||||||
|
if (!nodes || nodes.length === 0) return <span className="pp-progress-empty">暂无进度</span>;
|
||||||
|
const total = nodes.length;
|
||||||
|
const done = nodes.filter((n) => n.statusCode === 3).length;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="pp-progress-dots">
|
||||||
|
{nodes.map((node, idx) => {
|
||||||
|
const dotClass =
|
||||||
|
node.statusCode === 3 ? "pp-progress-dot-done" :
|
||||||
|
node.statusCode === 2 ? "pp-progress-dot-doing" :
|
||||||
|
node.statusCode === 4 ? "pp-progress-dot-error" :
|
||||||
|
"pp-progress-dot-wait";
|
||||||
|
return (
|
||||||
|
<Tooltip key={node.nodeCode || idx} title={`${node.nodeName}: ${node.statusName}`}>
|
||||||
|
<span className={`pp-progress-dot ${dotClass}`} />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="pp-progress-text">
|
||||||
|
{done}/{total} · {Math.round((done / total) * 100)}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: "项目编号", dataIndex: "projectNo", width: 100 },
|
||||||
|
{
|
||||||
|
title: "客户/项目",
|
||||||
|
width: 180,
|
||||||
|
render: (_, record) => (
|
||||||
|
<div>
|
||||||
|
<div className="pp-project-name">{record.projectName}</div>
|
||||||
|
<div className="pp-customer-name">{record.customerName}</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ title: "评价类别", dataIndex: "evalTypeName", width: 110 },
|
||||||
|
{ title: "负责人", dataIndex: "projectLeaderName", width: 100, ellipsis: true },
|
||||||
|
{
|
||||||
|
title: "项目进度",
|
||||||
|
width: 240,
|
||||||
|
render: (_, record) => renderProgressDots(record.projectNode),
|
||||||
|
},
|
||||||
|
{ title: "项目结束日期", dataIndex: "planEndDate", width: 120 },
|
||||||
|
{
|
||||||
|
title: "项目状态",
|
||||||
|
dataIndex: "progressStatusCode",
|
||||||
|
width: 130,
|
||||||
|
render: (code, record) => {
|
||||||
|
const cfg = PROGRESS_STATUS_MAP[code];
|
||||||
|
const daysText =
|
||||||
|
record.remainingDays >= 0
|
||||||
|
? `剩余${record.remainingDays}天`
|
||||||
|
: `超期${Math.abs(record.remainingDays)}天`;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>{cfg ? <Tag color={cfg.color}>{cfg.text}-{daysText}</Tag> : <Tag>{code}</Tag>}</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
width: 80,
|
||||||
|
fixed: "right",
|
||||||
|
render: (_, record) => (
|
||||||
|
<Button type="link" size="small" onClick={() => handleView(record)}>
|
||||||
|
查看
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout title="项目进度管理">
|
||||||
|
|
||||||
|
<Row gutter={16} className="pp-stat-row">
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic
|
||||||
|
title="项目总数"
|
||||||
|
value={statData.totalCount ?? "-"}
|
||||||
|
suffix={<span className="pp-stat-suffix">当前机构名下评价项目</span>}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic
|
||||||
|
title="正常项目"
|
||||||
|
value={statData.normalCount ?? "-"}
|
||||||
|
valueStyle={{ color: "#52c41a" }}
|
||||||
|
suffix={<span className="pp-stat-suffix">距项目结束日期超过3天</span>}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic
|
||||||
|
title="项目临期"
|
||||||
|
value={statData.nearExpiryCount ?? "-"}
|
||||||
|
valueStyle={{ color: "#faad14" }}
|
||||||
|
suffix={<span className="pp-stat-suffix">项目结束前3天自动提醒</span>}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic
|
||||||
|
title="项目延期"
|
||||||
|
value={statData.delayedCount ?? "-"}
|
||||||
|
valueStyle={{ color: "#ff4d4f" }}
|
||||||
|
suffix={<span className="pp-stat-suffix">已超过项目结束日期</span>}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<SearchForm
|
||||||
|
form={searchForm}
|
||||||
|
loading={false}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
formLine={[
|
||||||
|
<Form.Item key="keyword" name="keyword">
|
||||||
|
<ControlWrapper.Input
|
||||||
|
label="项目名称/客户名称"
|
||||||
|
placeholder="项目名称/客户名称"
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>,
|
||||||
|
<Form.Item key="progressStatus" name="progressStatus">
|
||||||
|
<ControlWrapper.Select
|
||||||
|
label="项目状态"
|
||||||
|
placeholder="全部"
|
||||||
|
allowClear
|
||||||
|
options={[
|
||||||
|
{ label: "正常", value: "NORMAL" },
|
||||||
|
{ label: "项目临期", value: "NEAR" },
|
||||||
|
{ label: "项目延期", value: "DELAY" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>,
|
||||||
|
]}
|
||||||
|
onFinish={handleSearch}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSource}
|
||||||
|
loading={evalProjectProgressPageLoading}
|
||||||
|
scroll={{ y: props.scrollY, x: 1100 }}
|
||||||
|
pagination={{
|
||||||
|
total,
|
||||||
|
current: router.query.current || 1,
|
||||||
|
pageSize: router.query.size || 10,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
|
}}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="项目进度详情"
|
||||||
|
open={detailModalVisible}
|
||||||
|
onCancel={() => setDetailModalVisible(false)}
|
||||||
|
footer={null}
|
||||||
|
width={680}
|
||||||
|
destroyOnHide={true}
|
||||||
|
>
|
||||||
|
{currentRecord && (
|
||||||
|
<>
|
||||||
|
<Descriptions column={2} bordered size="small" className="pp-detail-desc">
|
||||||
|
<Descriptions.Item label="项目编号">{currentRecord.projectNo}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="项目名称">{currentRecord.projectName}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="客户名称">{currentRecord.customerName}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="客户负责人">{currentRecord.customerContactName}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="评价类型">{currentRecord.evalTypeName}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="项目负责人">{currentRecord.projectLeaderName}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="项目结束日期">{currentRecord.planEndDate}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="剩余天数">
|
||||||
|
<span
|
||||||
|
className={`pp-remaining-days ${
|
||||||
|
currentRecord.remainingDays < 0 ? "pp-remaining-danger" :
|
||||||
|
currentRecord.remainingDays <= 3 ? "pp-remaining-warn" :
|
||||||
|
"pp-remaining-normal"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{currentRecord.remainingDays >= 0 ? `${currentRecord.remainingDays}天` : `已超期${Math.abs(currentRecord.remainingDays)}天`}
|
||||||
|
</span>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="项目状态" span={2}>
|
||||||
|
<Tag color={PROGRESS_STATUS_MAP[currentRecord.progressStatusCode]?.color}>
|
||||||
|
{currentRecord.progressStatusName}
|
||||||
|
</Tag>
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
|
||||||
|
<div className="pp-node-title">项目节点进度</div>
|
||||||
|
<Steps
|
||||||
|
direction="vertical"
|
||||||
|
current={-1}
|
||||||
|
items={
|
||||||
|
currentRecord.projectNode?.map((node) => ({
|
||||||
|
title: node.nodeName,
|
||||||
|
status:
|
||||||
|
node.statusCode === 3
|
||||||
|
? "finish"
|
||||||
|
: node.statusCode === 2
|
||||||
|
? "process"
|
||||||
|
: node.statusCode === 4
|
||||||
|
? "error"
|
||||||
|
: "wait",
|
||||||
|
description: node.statusName,
|
||||||
|
})) || []
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Connect(
|
||||||
|
[NS_SAFETY_EVAL_BUSINESS],
|
||||||
|
true,
|
||||||
|
)(AntdTableFuncControl(ProjectProgress));
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
.pp {
|
||||||
|
// 统计卡片区域
|
||||||
|
&-stat-row {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-stat-suffix {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进度圆点容器
|
||||||
|
&-progress-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-progress-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&-done { background: #52c41a; }
|
||||||
|
&-doing { background: #1677ff; }
|
||||||
|
&-error { background: #ff4d4f; }
|
||||||
|
&-wait { background: #e8e8e8; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&-progress-text {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-progress-empty {
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户/项目列
|
||||||
|
&-project-name {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-customer-name {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 详情弹窗 - Descriptions 间距
|
||||||
|
&-detail-desc {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 剩余天数
|
||||||
|
&-remaining-days {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
&-remaining-normal { color: #52c41a; }
|
||||||
|
&-remaining-warn { color: #faad14; }
|
||||||
|
&-remaining-danger { color: #ff4d4f; }
|
||||||
|
|
||||||
|
// 节点进度标题
|
||||||
|
&-node-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,336 @@
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Table,
|
||||||
|
Button,
|
||||||
|
Tag,
|
||||||
|
Modal,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Card,
|
||||||
|
Statistic,
|
||||||
|
Space,
|
||||||
|
message,
|
||||||
|
} from "antd";
|
||||||
|
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/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 { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||||
|
import { RISK_LEVEL_MAP, RISK_STATUS_MAP } from "~/enumerate/constant";
|
||||||
|
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||||
|
import { tools } from "@cqsjjb/jjb-common-lib";
|
||||||
|
import "./index.less";
|
||||||
|
|
||||||
|
const { router } = tools;
|
||||||
|
|
||||||
|
const RiskWarning = (props) => {
|
||||||
|
const [searchForm] = Form.useForm();
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [statData, setStatData] = useState({});
|
||||||
|
const [handleModalVisible, setHandleModalVisible] = useState(false);
|
||||||
|
const [currentRecord, setCurrentRecord] = useState(null);
|
||||||
|
|
||||||
|
const { riskWarningPageLoading } = props.safetyEvalBusiness;
|
||||||
|
|
||||||
|
const getStat = async () => {
|
||||||
|
const res = await props.evalProjectRiskWarningStat();
|
||||||
|
if (res?.success !== false) {
|
||||||
|
setStatData(res?.data || {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const params = {
|
||||||
|
...router.query,
|
||||||
|
current: router.query.current || 1,
|
||||||
|
size: router.query.size || 10,
|
||||||
|
};
|
||||||
|
const res = await props.evalProjectRiskWarningPage(params);
|
||||||
|
if (res?.success !== false) {
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setTotal(res?.total || 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
searchForm.setFieldsValue(router.query);
|
||||||
|
getStat();
|
||||||
|
getData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSearch = (values) => {
|
||||||
|
router.query = { ...router.query, ...values, current: 1, size: 10 };
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = (values) => {
|
||||||
|
searchForm.resetFields();
|
||||||
|
router.query = { ...values, current: 1, size: 10 };
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (pagination) => {
|
||||||
|
router.query = {
|
||||||
|
...router.query,
|
||||||
|
current: pagination.current,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProcess = (record) => {
|
||||||
|
setCurrentRecord(record);
|
||||||
|
setHandleModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseRisk = async (record) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: "确认闭环",
|
||||||
|
content: `确定将"${record.projectName}"的风险标记为闭环吗?`,
|
||||||
|
onOk: async () => {
|
||||||
|
// TODO: 调用闭环接口
|
||||||
|
message.success("已闭环");
|
||||||
|
getData();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: "项目编号", dataIndex: "projectNo", width: 130 },
|
||||||
|
{
|
||||||
|
title: "项目/客户",
|
||||||
|
width: 240,
|
||||||
|
render: (_, record) => (
|
||||||
|
<div>
|
||||||
|
<div className="rw-project-name">{record.projectName}</div>
|
||||||
|
<div className="rw-customer-name">{record.customerName}</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "风险等级",
|
||||||
|
dataIndex: "riskLevelCode",
|
||||||
|
width: 100,
|
||||||
|
render: (code) => {
|
||||||
|
const cfg = RISK_LEVEL_MAP[code];
|
||||||
|
return cfg ? <Tag color={cfg.color}>{cfg.text}</Tag> : <Tag>{code}</Tag>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: "预警类型", dataIndex: "warningTypeName", width: 100 },
|
||||||
|
{
|
||||||
|
title: "触发条件",
|
||||||
|
dataIndex: "triggerCondition",
|
||||||
|
ellipsis: true,
|
||||||
|
width: 260,
|
||||||
|
},
|
||||||
|
{ title: "责任人", dataIndex: "responsiblePerson", width: 90 },
|
||||||
|
{ title: "预警时间", dataIndex: "warningTime", width: 110 },
|
||||||
|
{
|
||||||
|
title: "状态",
|
||||||
|
dataIndex: "statusCode",
|
||||||
|
width: 90,
|
||||||
|
render: (code) => {
|
||||||
|
const cfg = RISK_STATUS_MAP[code];
|
||||||
|
return cfg ? <Tag color={cfg.color}>{cfg.text}</Tag> : <Tag>{code}</Tag>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
width: 130,
|
||||||
|
fixed: "right",
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space>
|
||||||
|
<Button type="link" size="small" onClick={() => handleProcess(record)}>
|
||||||
|
处理
|
||||||
|
</Button>
|
||||||
|
<Button type="link" size="small" onClick={() => handleCloseRisk(record)}>
|
||||||
|
闭环
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout title="风险预警管理">
|
||||||
|
<Row gutter={16} className="rw-stat-row">
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic
|
||||||
|
title="预警总数"
|
||||||
|
value={statData.totalCount ?? "-"}
|
||||||
|
suffix={<span className="rw-stat-suffix">来自项目过程控制与周期规则</span>}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic
|
||||||
|
title="高风险"
|
||||||
|
value={statData.highRiskCount ?? "-"}
|
||||||
|
valueStyle={{ color: "#ff4d4f" }}
|
||||||
|
suffix={<span className="rw-stat-suffix">延期、逾期、重大整改</span>}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic
|
||||||
|
title="处理中"
|
||||||
|
value={statData.processingCount ?? "-"}
|
||||||
|
valueStyle={{ color: "#faad14" }}
|
||||||
|
suffix={<span className="rw-stat-suffix">已分派但未闭环</span>}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic
|
||||||
|
title="本周新增"
|
||||||
|
value={statData.weekNewCount ?? "-"}
|
||||||
|
valueStyle={{ color: "#52c41a" }}
|
||||||
|
suffix={<span className="rw-stat-suffix">需在周例会确认</span>}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<SearchForm
|
||||||
|
form={searchForm}
|
||||||
|
loading={false}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
formLine={[
|
||||||
|
<Form.Item key="projectOrCustomer" name="projectOrCustomer">
|
||||||
|
<ControlWrapper.Input
|
||||||
|
label="项目/客户"
|
||||||
|
placeholder="项目或客户名称"
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>,
|
||||||
|
<Form.Item key="riskLevel" name="riskLevel">
|
||||||
|
<ControlWrapper.Select
|
||||||
|
label="风险等级"
|
||||||
|
placeholder="全部"
|
||||||
|
allowClear
|
||||||
|
options={[
|
||||||
|
{ label: "高风险", value: "HIGH" },
|
||||||
|
{ label: "中风险", value: "MIDDLE" },
|
||||||
|
{ label: "低风险", value: "LOW" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>,
|
||||||
|
<Form.Item key="warningType" name="warningType">
|
||||||
|
<ControlWrapper.Select
|
||||||
|
label="预警类型"
|
||||||
|
placeholder="全部"
|
||||||
|
allowClear
|
||||||
|
options={[
|
||||||
|
{ label: "进度风险", value: "PROGRESS" },
|
||||||
|
{ label: "周期风险", value: "CYCLE" },
|
||||||
|
{ label: "质量风险", value: "QUALITY" },
|
||||||
|
{ label: "人员风险", value: "PERSONNEL" },
|
||||||
|
{ label: "归档风险", value: "ARCHIVE" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>,
|
||||||
|
]}
|
||||||
|
onFinish={handleSearch}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: 13, color: "#999" }}>
|
||||||
|
处理按钮会打开处置面板;标记闭环会直接更新行状态。
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSource}
|
||||||
|
loading={riskWarningPageLoading}
|
||||||
|
scroll={{ y: props.scrollY, x: 1300 }}
|
||||||
|
pagination={{
|
||||||
|
total,
|
||||||
|
current: router.query.current || 1,
|
||||||
|
pageSize: router.query.size || 10,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
|
}}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="风险处置"
|
||||||
|
open={handleModalVisible}
|
||||||
|
onCancel={() => setHandleModalVisible(false)}
|
||||||
|
footer={null}
|
||||||
|
width={600}
|
||||||
|
destroyOnHide={true}
|
||||||
|
>
|
||||||
|
{currentRecord && (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<strong>项目:</strong>
|
||||||
|
{currentRecord.projectName}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>风险等级:</strong>
|
||||||
|
<Tag color={RISK_LEVEL_MAP[currentRecord.riskLevelCode]?.color}>
|
||||||
|
{currentRecord.riskLevelName}
|
||||||
|
</Tag>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>预警类型:</strong>
|
||||||
|
{currentRecord.warningTypeName}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>触发条件:</strong>
|
||||||
|
{currentRecord.triggerCondition}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>责任人:</strong>
|
||||||
|
{currentRecord.responsiblePerson}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>预警时间:</strong>
|
||||||
|
{currentRecord.warningTime}
|
||||||
|
</p>
|
||||||
|
<div style={{ textAlign: "right", marginTop: 24 }}>
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => setHandleModalVisible(false)}>取消</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
message.success("处理完成");
|
||||||
|
setHandleModalVisible(false);
|
||||||
|
getData();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
确认处理
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Connect(
|
||||||
|
[NS_SAFETY_EVAL_BUSINESS],
|
||||||
|
true,
|
||||||
|
)(AntdTableFuncControl(RiskWarning));
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
.rw {
|
||||||
|
&-stat-row {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-stat-suffix {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-note {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #f6f8fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #555;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-project-name {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-customer-name {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue