diff --git a/jjb.config.js b/jjb.config.js
index 68c8e2d..f261548 100644
--- a/jjb.config.js
+++ b/jjb.config.js
@@ -12,8 +12,8 @@ module.exports = {
// 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095
//API_HOST: "https://gbs-gateway.qhdsafety.com",
- API_HOST: "http://192.168.0.152",
- //API_HOST: "http://192.168.0.150", //太浅
+ //API_HOST: "http://192.168.0.152",
+ API_HOST: "http://192.168.0.150", //太浅
//API_HOST: "http://192.168.0.152",
//API_HOST: "http://192.168.0.103", //huwei
},
diff --git a/src/api/courseware/index.js b/src/api/courseware/index.js
deleted file mode 100644
index f6b7114..0000000
--- a/src/api/courseware/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
-
-export const identifyPartList = declareRequest(
- "coursewareLoading",
- "Post > @/risk/busIdentifyPart/list",
-);
diff --git a/src/api/safetyEvalBusiness/index.js b/src/api/safetyEvalBusiness/index.js
index 0132085..66659a7 100644
--- a/src/api/safetyEvalBusiness/index.js
+++ b/src/api/safetyEvalBusiness/index.js
@@ -77,4 +77,24 @@ export const evalProjectDocPage = declareRequest(
export const evalProjectDocSave = declareRequest(
"evalProjectDocSaveLoading",
"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",
);
\ No newline at end of file
diff --git a/src/enumerate/constant/index.js b/src/enumerate/constant/index.js
index 9c9fb4f..8e59e2e 100644
--- a/src/enumerate/constant/index.js
+++ b/src/enumerate/constant/index.js
@@ -331,4 +331,24 @@ export const MATERIAL_TYPE_OPTIONS = [
export const MATERIAL_TYPE_MAP = MATERIAL_TYPE_OPTIONS.reduce((acc, cur) => {
acc[cur.value] = cur.label;
return acc;
-}, {});
\ No newline at end of file
+}, {});
+
+/** 风险预警 — 风险等级映射 */
+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: "项目延期" },
+};
\ No newline at end of file
diff --git a/src/pages/Container/Layout/menuConfig.js b/src/pages/Container/Layout/menuConfig.js
index ffcc75a..3aed2f8 100644
--- a/src/pages/Container/Layout/menuConfig.js
+++ b/src/pages/Container/Layout/menuConfig.js
@@ -212,10 +212,20 @@ const menuItems = [
icon: ,
},
{
- key: "/safetyEval/container/SafetyEvalBusiness/EvalProject/ProjectDocLibrary",
+ key: "/safetyEval/container/SafetyEvalBusiness/ProjectDocLibrary",
label: "项目资料管理",
icon: ,
},
+ {
+ key: "/safetyEval/container/SafetyEvalBusiness/Progress",
+ label: "项目进度管理",
+ icon: ,
+ },
+ {
+ key: "/safetyEval/container/SafetyEvalBusiness/RiskWarning",
+ label: "风险预警管理",
+ icon: ,
+ },
{
key: "/safetyEval/container/SafetyEvalBusiness/CustomerManage",
label: "安评客户管理",
diff --git a/src/pages/Container/SafetyEvalBusiness/Progress/index.js b/src/pages/Container/SafetyEvalBusiness/Progress/index.js
new file mode 100644
index 0000000..2ecfb3a
--- /dev/null
+++ b/src/pages/Container/SafetyEvalBusiness/Progress/index.js
@@ -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 暂无进度;
+ const total = nodes.length;
+ const done = nodes.filter((n) => n.statusCode === 3).length;
+ return (
+
+
+ {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 (
+
+
+
+ );
+ })}
+
+
+ {done}/{total} · {Math.round((done / total) * 100)}%
+
+
+ );
+ };
+
+ const columns = [
+ { title: "项目编号", dataIndex: "projectNo", width: 100 },
+ {
+ title: "客户/项目",
+ width: 180,
+ render: (_, record) => (
+
+
{record.projectName}
+
{record.customerName}
+
+ ),
+ },
+ { 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 (
+
+
{cfg ? {cfg.text}-{daysText} : {code}}
+
+
+ );
+ },
+ },
+ {
+ title: "操作",
+ width: 80,
+ fixed: "right",
+ render: (_, record) => (
+
+ ),
+ },
+ ];
+
+ return (
+
+
+
+
+
+ 当前机构名下评价项目}
+ />
+
+
+
+
+ 距项目结束日期超过3天}
+ />
+
+
+
+
+ 项目结束前3天自动提醒}
+ />
+
+
+
+
+ 已超过项目结束日期}
+ />
+
+
+
+
+
+
+
+
+ ,
+
+
+ ,
+ ]}
+ onFinish={handleSearch}
+ onReset={handleReset}
+ />
+
+
+
+ `共 ${total} 条`,
+ }}
+ onChange={handlePageChange}
+ />
+
+ setDetailModalVisible(false)}
+ footer={null}
+ width={680}
+ destroyOnHide={true}
+ >
+ {currentRecord && (
+ <>
+
+ {currentRecord.projectNo}
+ {currentRecord.projectName}
+ {currentRecord.customerName}
+ {currentRecord.customerContactName}
+ {currentRecord.evalTypeName}
+ {currentRecord.projectLeaderName}
+ {currentRecord.planEndDate}
+
+
+ {currentRecord.remainingDays >= 0 ? `${currentRecord.remainingDays}天` : `已超期${Math.abs(currentRecord.remainingDays)}天`}
+
+
+
+
+ {currentRecord.progressStatusName}
+
+
+
+
+ 项目节点进度
+ ({
+ title: node.nodeName,
+ status:
+ node.statusCode === 3
+ ? "finish"
+ : node.statusCode === 2
+ ? "process"
+ : node.statusCode === 4
+ ? "error"
+ : "wait",
+ description: node.statusName,
+ })) || []
+ }
+ />
+ >
+ )}
+
+
+ );
+};
+
+export default Connect(
+ [NS_SAFETY_EVAL_BUSINESS],
+ true,
+)(AntdTableFuncControl(ProjectProgress));
\ No newline at end of file
diff --git a/src/pages/Container/SafetyEvalBusiness/Progress/index.less b/src/pages/Container/SafetyEvalBusiness/Progress/index.less
new file mode 100644
index 0000000..9c56de4
--- /dev/null
+++ b/src/pages/Container/SafetyEvalBusiness/Progress/index.less
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/pages/Container/SafetyEvalBusiness/EvalProject/ProjectDocLibrary/index.js b/src/pages/Container/SafetyEvalBusiness/ProjectDocLibrary/index.js
similarity index 100%
rename from src/pages/Container/SafetyEvalBusiness/EvalProject/ProjectDocLibrary/index.js
rename to src/pages/Container/SafetyEvalBusiness/ProjectDocLibrary/index.js
diff --git a/src/pages/Container/SafetyEvalBusiness/RiskWarning/index.js b/src/pages/Container/SafetyEvalBusiness/RiskWarning/index.js
new file mode 100644
index 0000000..0aceb0c
--- /dev/null
+++ b/src/pages/Container/SafetyEvalBusiness/RiskWarning/index.js
@@ -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) => (
+
+
{record.projectName}
+
{record.customerName}
+
+ ),
+ },
+ {
+ title: "风险等级",
+ dataIndex: "riskLevelCode",
+ width: 100,
+ render: (code) => {
+ const cfg = RISK_LEVEL_MAP[code];
+ return cfg ? {cfg.text} : {code};
+ },
+ },
+ { 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 ? {cfg.text} : {code};
+ },
+ },
+ {
+ title: "操作",
+ width: 130,
+ fixed: "right",
+ render: (_, record) => (
+
+
+
+
+ ),
+ },
+ ];
+
+ return (
+
+
+
+
+ 来自项目过程控制与周期规则}
+ />
+
+
+
+
+ 延期、逾期、重大整改}
+ />
+
+
+
+
+ 已分派但未闭环}
+ />
+
+
+
+
+ 需在周例会确认}
+ />
+
+
+
+
+
+
+
+
+ ,
+
+
+ ,
+
+
+ ,
+ ]}
+ onFinish={handleSearch}
+ onReset={handleReset}
+ />
+
+
+
+ 处理按钮会打开处置面板;标记闭环会直接更新行状态。
+
+
+
+ `共 ${total} 条`,
+ }}
+ onChange={handlePageChange}
+ />
+
+ setHandleModalVisible(false)}
+ footer={null}
+ width={600}
+ destroyOnHide={true}
+ >
+ {currentRecord && (
+
+
+ 项目:
+ {currentRecord.projectName}
+
+
+ 风险等级:
+
+ {currentRecord.riskLevelName}
+
+
+
+ 预警类型:
+ {currentRecord.warningTypeName}
+
+
+ 触发条件:
+ {currentRecord.triggerCondition}
+
+
+ 责任人:
+ {currentRecord.responsiblePerson}
+
+
+ 预警时间:
+ {currentRecord.warningTime}
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default Connect(
+ [NS_SAFETY_EVAL_BUSINESS],
+ true,
+)(AntdTableFuncControl(RiskWarning));
\ No newline at end of file
diff --git a/src/pages/Container/SafetyEvalBusiness/RiskWarning/index.less b/src/pages/Container/SafetyEvalBusiness/RiskWarning/index.less
new file mode 100644
index 0000000..6da334f
--- /dev/null
+++ b/src/pages/Container/SafetyEvalBusiness/RiskWarning/index.less
@@ -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;
+ }
+}
\ No newline at end of file