dev_1.2
tangjie 2026-07-28 17:27:14 +08:00
parent 3eaa364f29
commit 286cbb496c
5 changed files with 160 additions and 59 deletions

View File

@ -125,6 +125,18 @@ export const QUAL_MONITOR_STATUS_OPTIONS = [
{ value: "person_rate", label: "人员比例预警" }, { value: "person_rate", label: "人员比例预警" },
]; ];
/** 资质保持监控-业务范围专业能力配备标准 */
export const QUAL_MONITOR_STANDARD_ROWS = [
{ scope: "煤炭开采业", standard: "安全、机械、电气、采矿、通风、地质、矿建各 1 名及以上" },
{ scope: "金属、非金属矿及其他矿采选业", standard: "安全、机械、电气、采矿、通风、地质、水工结构各 1 名及以上" },
{ scope: "陆地石油和天然气开采业", standard: "安全、机械、电气、采油、储运各 1 名及以上" },
{ scope: "陆上油气管道运输业", standard: "油气储运 2 名及以上,设备、仪表、电气、防腐、安全各 1 名及以上" },
{ scope: "石油加工业,化学原料、化学品及医药制造业", standard: "化工工艺、化工机械、仪表自动化、电气、安全各 2 名及以上" },
{ scope: "烟花爆竹制造业和仓储业", standard: "火炸药(爆炸技术)、机械、电气、安全各 1 名及以上" },
{ scope: "金属冶炼", standard: "安全、机械、电气、冶金、有色金属各 1 名及以上" },
{ scope: "民用爆炸物品行业", standard: "火炸药爆炸技术10 名,机械、电气、安全各 2 名及以上" },
];
/** 证书类型选项 */ /** 证书类型选项 */
export const CERT_TYPE_OPTIONS = [ export const CERT_TYPE_OPTIONS = [
{ value: "registered_safety_engineer", label: "注册安全工程师" }, { value: "registered_safety_engineer", label: "注册安全工程师" },

View File

@ -16,6 +16,7 @@ import {
QUAL_MONITOR_STATUS_MAP, QUAL_MONITOR_STATUS_MAP,
QUAL_MONITOR_CONDITION_ITEMS, QUAL_MONITOR_CONDITION_ITEMS,
QUAL_MONITOR_STATUS_OPTIONS, QUAL_MONITOR_STATUS_OPTIONS,
QUAL_MONITOR_STANDARD_ROWS,
} from "~/enumerate/constant"; } from "~/enumerate/constant";
import "./index.less"; import "./index.less";
@ -112,6 +113,7 @@ const QualMonitor = (props) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [detailVisible, setDetailVisible] = useState(false); const [detailVisible, setDetailVisible] = useState(false);
const [detailRecord, setDetailRecord] = useState(null); const [detailRecord, setDetailRecord] = useState(null);
const [standardVisible, setStandardVisible] = useState(false);
const [data, setData] = useState([]); const [data, setData] = useState([]);
const { qualReview, queryQualMonitorPage, queryQualMonitorCondition, queryQualMonitoringPageStatistics } = props; const { qualReview, queryQualMonitorPage, queryQualMonitorCondition, queryQualMonitoringPageStatistics } = props;
@ -326,11 +328,7 @@ const QualMonitor = (props) => {
))} ))}
</div> </div>
{/* 资质条件来源说明 */}
<div className="source-note">
<strong>页面定位</strong>
资质保持监控是监管信息看板展示机构具备哪些资质资质条件是否保持每个结论来自哪些字段系统命中异常后自动生成风险消息发函整改机构反馈复核闭环等办理流程全部在风险预警中心完成
</div>
{/* 搜索表单 */} {/* 搜索表单 */}
<SearchForm <SearchForm
@ -371,6 +369,15 @@ const QualMonitor = (props) => {
onFinish={onFinish} onFinish={onFinish}
/> />
<div className="qm-toolbar">
<span className="qm-toolbar-hint">
机构列表先看机构具备哪些资质/业务范围再看各保持条件和来源字段
</span>
<Button size="small" onClick={() => setStandardVisible(true)}>
检查口径
</Button>
</div>
{/* 数据表格 */} {/* 数据表格 */}
<Table <Table
rowKey="id" rowKey="id"
@ -447,6 +454,32 @@ const QualMonitor = (props) => {
</div> </div>
)} )}
</Modal> </Modal>
{/* 检查口径弹窗 */}
<Modal
title="资质条件检查口径"
open={standardVisible}
onCancel={() => setStandardVisible(false)}
width={760}
footer={
<Button onClick={() => setStandardVisible(false)}>关闭</Button>
}
>
<div className="check-note">
<strong>资质保持条件</strong>
监管端按独立法人资产场所人员数量及比例负责人制度承诺书网站失信记录属地要求和专业能力标准建立检查项
</div>
<Table
rowKey="scope"
size="small"
pagination={false}
columns={[
{ title: "业务范围", dataIndex: "scope", width: 200 },
{ title: "专业能力配备标准", dataIndex: "standard" },
]}
dataSource={QUAL_MONITOR_STANDARD_ROWS}
/>
</Modal>
</div> </div>
</PageLayout> </PageLayout>
); );

View File

@ -125,6 +125,19 @@
white-space: nowrap; white-space: nowrap;
} }
// 工具栏
.qm-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
.qm-toolbar-hint {
font-size: 0.85rem;
color: #64748b;
}
}
// 核查口径说明 // 核查口径说明
.check-note { .check-note {
margin-bottom: 12px; margin-bottom: 12px;

View File

@ -323,8 +323,8 @@ const EvalProjectCreate = (props) => {
rules={[{ required: true, message: "请选择是否法定项目" }]} rules={[{ required: true, message: "请选择是否法定项目" }]}
> >
<Radio.Group> <Radio.Group>
<Radio value={true}></Radio> <Radio value={true}>纳入监管端监管</Radio>
<Radio value={false}></Radio> <Radio value={false}>仅机构内部管理</Radio>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
</Col> </Col>

View File

@ -1,5 +1,18 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Tag, Button, Card, Table, Select, Input, Flex, Typography, message, Popover, Image } from "antd"; import {
Tag,
Button,
Card,
Table,
Select,
Input,
Flex,
Typography,
message,
Popover,
Image,
Collapse,
} from "antd";
import { CheckCircleFilled } from "@ant-design/icons"; import { CheckCircleFilled } from "@ant-design/icons";
import { Connect } from "@cqsjjb/jjb-dva-runtime"; import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace"; import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
@ -16,17 +29,15 @@ const ControlContent = (props) => {
processControlDetail || {}; processControlDetail || {};
const [selectPeople, setSelectPeople] = useState(null); const [selectPeople, setSelectPeople] = useState(null);
// 当前步骤1-过程控制审核 2-机构负责人签发 3-项目归档 // 当前步骤1-过程控制审核 2-机构负责人签发 3-项目归档
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
// 过程控制审核 // 过程控制审核
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
// 项目归档 // 项目归档
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
// 机构负责人签发 // 机构负责人签发
const [approvalOpinion, setApprovalOpinion] = useState(""); const [approvalOpinion, setApprovalOpinion] = useState("");
@ -34,7 +45,6 @@ const ControlContent = (props) => {
const fetchDetail = () => { const fetchDetail = () => {
props.evalProcessControlDetail({ projectId }).then((res) => { props.evalProcessControlDetail({ projectId }).then((res) => {
if (res?.success) { if (res?.success) {
if (res?.data?.signTaskPersonCo) { if (res?.data?.signTaskPersonCo) {
setSelectPeople({ setSelectPeople({
value: res?.data?.signTaskPersonCo?.signerPersonnelId, value: res?.data?.signTaskPersonCo?.signerPersonnelId,
@ -65,10 +75,9 @@ const ControlContent = (props) => {
})), })),
); );
} }
if (processControl?.opinionContent) { if (processControl?.opinionContent) {
setApprovalOpinion(processControl.opinionContent); setApprovalOpinion(processControl.opinionContent);
} }
}, [processControlDetail]); }, [processControlDetail]);
@ -89,13 +98,12 @@ const ControlContent = (props) => {
const isSignatureCompleted = const isSignatureCompleted =
processControl?.reviewerSignatureTaskStatus === "completed"; processControl?.reviewerSignatureTaskStatus === "completed";
const canArchive = isSignatureCompleted;
const canArchive = isSignatureCompleted ;
// 过程控制签字提交confirm // 过程控制签字提交confirm
const handleConfirm = async (resultCode) => { const handleConfirm = async (resultCode) => {
if (!projectId) return; if (!projectId) return;
if(!rows.every((r) => r.reviewConclusion)){ if (!rows.every((r) => r.reviewConclusion)) {
message.error("请填写所有核查项的结果"); message.error("请填写所有核查项的结果");
return; return;
} }
@ -119,7 +127,7 @@ const ControlContent = (props) => {
message.success( message.success(
resultCode === "passed" ? "已提交通过审核" : "已提交退回修改", resultCode === "passed" ? "已提交通过审核" : "已提交退回修改",
); );
fetchDetail(); fetchDetail();
} }
}; };
@ -194,63 +202,61 @@ const ControlContent = (props) => {
const fileColumns = [ const fileColumns = [
{ title: "流程名称", dataIndex: "processName", ellipsis: true }, { title: "流程名称", dataIndex: "processName", ellipsis: true },
{ title: "条目名称", dataIndex: "itemName", ellipsis: true }, { title: "条目名称", dataIndex: "itemName", ellipsis: true },
{ {
title: "文件", title: "文件",
dataIndex: "projectDocUrl", dataIndex: "projectDocUrl",
render: (val) => { render: (val) => {
let files = []; let files = [];
if (typeof val === "string") { if (typeof val === "string") {
try { files = JSON.parse(val); } catch { files = []; } try {
files = JSON.parse(val);
} catch {
files = [];
}
} else if (Array.isArray(val)) { } else if (Array.isArray(val)) {
files = val; files = val;
} }
if (!files.length) return "-"; if (!files.length) return "-";
return ( return (
<Flex vertical gap={2}> <Flex vertical gap={2}>
<Popover <Popover
title="附件列表" title="附件列表"
content={ content={
<Flex vertical gap={4} > <Flex vertical gap={4}>
{files.map((f, i) => { {files.map((f, i) => {
const url = f.url || f.response?.url; const url = f.url || f.response?.url;
return isImage(f.name) ? ( return isImage(f.name) ? (
<Flex key={f.uid || i} align="center" gap={4}> <Flex key={f.uid || i} align="center" gap={4}>
{( {<Image src={url} width={80} />}
<Image </Flex>
src={url} ) : (
width={80} <a
/> key={f.uid || i}
)} style={{ cursor: "pointer" }}
</Flex> onClick={() => window.open(url)}
) : ( >
<a
key={f.uid || i}
style={{ cursor: "pointer" }}
onClick={() => window.open(url)}
>
{f.name || "查看文件"} {f.name || "查看文件"}
</a> </a>
); );
})} })}
</Flex> </Flex>
} }
trigger="hover" trigger="hover"
placement="topLeft" placement="topLeft"
>
<a
onClick={(e) => e.preventDefault()}
style={{ cursor: "pointer" }}
> >
<a {files.length}个附件
onClick={(e) => e.preventDefault()} </a>
style={{ cursor: "pointer" }} </Popover>
>
{files.length}个附件
</a>
</Popover>
</Flex> </Flex>
); );
}, },
}, },
]; ];
const STEP_ITEMS = [ const STEP_ITEMS = [
@ -300,7 +306,45 @@ const ControlContent = (props) => {
pagination={false} pagination={false}
size="small" size="small"
/> />
<Collapse
style={{ marginTop: 12 }}
items={[
{
key: "checklist",
label: "过程控制完整核查清单13项",
children: (
<ol
style={{
margin: 0,
paddingLeft: 20,
fontSize: 13,
color: "#475569",
lineHeight: 1.8,
}}
>
<li>合同签订前是否进行项目风险分析</li>
<li>是否签订技术服务合同</li>
<li>是否下达项目任务通知单</li>
<li>是否有项目组成立和项目负责人任命记录</li>
<li>是否编制项目实施计划</li>
<li>现场检查记录是否完整有效</li>
<li>是否有书面整改通知单并由被评价单位盖章或签字确认</li>
<li>
是否进行现场复查复查单是否双方签名或盖章并注明日期
</li>
<li>是否有现场勘查整改复查影像资料</li>
<li>是否完成内审技审并保留审核记录签字和日期</li>
<li>委托方提供资料是否齐全并由被评价单位盖章</li>
<li>
委托方资料评价过程资料和评价报告存档是否符合要求
</li>
<li>存档资料是否完整且具有可追溯性</li>
</ol>
),
},
]}
/>
<Flex justify="flex-end" gap={8} style={{ padding: "12px 0" }}> <Flex justify="flex-end" gap={8} style={{ padding: "12px 0" }}>
<Button <Button
type="primary" type="primary"
@ -435,7 +479,6 @@ const ControlContent = (props) => {
align="center" align="center"
style={{ marginTop: 12, paddingBottom: 16 }} style={{ marginTop: 12, paddingBottom: 16 }}
> >
<Button <Button
type="primary" type="primary"
onClick={handleArchive} onClick={handleArchive}