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