diff --git a/.gitignore b/.gitignore index 1181c62..e634bf3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /prototype /dist /demo +/doc /docs /.trae/ npm-debug.log* diff --git a/src/api/qualReview/index.js b/src/api/qualReview/index.js index fcf784a..d333ad1 100644 --- a/src/api/qualReview/index.js +++ b/src/api/qualReview/index.js @@ -61,4 +61,18 @@ export const modifyFilingMaterialChange = declareRequest( export const modifyFilingMaterial = declareRequest( "qualFilingMaterialLoading", "Post > @/safetyEval/qual-filing-material/modify", +); + +// ─── 资质保持监控 ─── + +export const queryQualMonitorPage = declareRequest( + "qualReviewLoading", + "Get > /safetyEval/qual-monitor/page", + 'qualMonitorList: [] | res.data || [] & qualMonitorTotal: 0 | res.total || 0', +); + +export const queryQualMonitorCondition = declareRequest( + "qualReviewLoading", + "Get > /safetyEval/qual-monitor/condition-detail", + 'qualMonitorCondition: {} | res.data || {}', ); \ No newline at end of file diff --git a/src/components/BaiduMapPicker/index.js b/src/components/BaiduMapPicker/index.js new file mode 100644 index 0000000..cc01e97 --- /dev/null +++ b/src/components/BaiduMapPicker/index.js @@ -0,0 +1,136 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Modal, Input, message } from 'antd'; +import './index.less'; + +const BaiduMapPicker = ({ visible, onCancel, onConfirm }) => { + const mapRef = useRef(null); + const mapInstanceRef = useRef(null); + const markerRef = useRef(null); + const [loaded, setLoaded] = useState(false); + const [selectedPoint, setSelectedPoint] = useState(null); + + // 动态加载百度地图脚本 + useEffect(() => { + if (!visible) return; + if (window.BMap) { + // 如果已有 BMap,等 DOM 渲染后再初始化 + setTimeout(() => setLoaded(true), 100); + return; + } + + const scriptId = 'baiduMapScript'; + if (document.getElementById(scriptId)) return; + + window.__onBMapLoaded = () => { + setLoaded(true); + }; + + const script = document.createElement('script'); + script.id = scriptId; + script.src = `https://api.map.baidu.com/api?v=3.0&ak=6lkWDSnHTCcvUenZhovQPER9nkhFfT9A&callback=__onBMapLoaded`; + script.async = true; + document.body.appendChild(script); + }, [visible]); + + // 初始化地图 + useEffect(() => { + if (!loaded || !visible || !mapRef.current) return; + // 避免重复初始化 + if (mapInstanceRef.current) return; + + const map = new window.BMap.Map(mapRef.current); + // 默认以重庆市中心为中心 + map.centerAndZoom(new window.BMap.Point(106.55, 29.57), 12); + map.enableScrollWheelZoom(true); + mapInstanceRef.current = map; + + // 点击地图获取坐标 + map.addEventListener('click', (e) => { + const point = e.point; + setSelectedPoint(point); + if (markerRef.current) { + map.removeOverlay(markerRef.current); + } + const marker = new window.BMap.Marker(point); + map.addOverlay(marker); + markerRef.current = marker; + }); + + // 搜索自动完成 + const ac = new window.BMap.Autocomplete({ + input: 'baiduSearchInput', + location: map, + }); + ac.addEventListener('onconfirm', (e) => { + const value = e.item.value; + const local = new window.BMap.LocalSearch(map, { + onSearchComplete: (results) => { + if (results.getCurrentNumPois() > 0) { + const poi = results.getPoi(0); + const point = poi.point; + setSelectedPoint(point); + map.centerAndZoom(point, 15); + if (markerRef.current) { + map.removeOverlay(markerRef.current); + } + const marker = new window.BMap.Marker(point); + map.addOverlay(marker); + markerRef.current = marker; + } + }, + }); + local.search( + [value.province, value.city, value.district, value.street, value.business] + .filter(Boolean) + .join(''), + ); + }); + + return () => { + mapInstanceRef.current = null; + }; + }, [loaded, visible]); + + // 弹窗关闭时重置 + useEffect(() => { + if (!visible) { + setLoaded(false); + setSelectedPoint(null); + markerRef.current = null; + mapInstanceRef.current = null; + } + }, [visible]); + + const handleConfirm = () => { + if (!selectedPoint) { + message.warning('请在地图上选择位置'); + return; + } + onConfirm({ + lng: selectedPoint.lng, + lat: selectedPoint.lat, + }); + }; + + return ( + + +
+ + ); +}; + +export default BaiduMapPicker; diff --git a/src/components/BaiduMapPicker/index.less b/src/components/BaiduMapPicker/index.less new file mode 100644 index 0000000..cc8763f --- /dev/null +++ b/src/components/BaiduMapPicker/index.less @@ -0,0 +1,33 @@ +.baidu-map-search { + width: 100%; + height: 36px; + padding: 4px 11px; + border: 1px solid #d9d9d9; + border-radius: 6px; + outline: none; + box-sizing: border-box; + margin-bottom: 12px; + font-size: 14px; + transition: border-color 0.2s; + + &:focus { + border-color: #4096ff; + box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1); + } + + &::placeholder { + color: #bfbfbf; + } +} + +.baidu-map-container { + width: 100%; + height: 480px; + border-radius: 6px; + overflow: hidden; +} + +/* 百度地图 Autocomplete 联想面板默认 z-index 过低,被 Modal 遮挡 */ +.tangram-suggestion-main { + z-index: 10000 !important; +} diff --git a/src/enumerate/constant/index.js b/src/enumerate/constant/index.js index 2383cbd..4ffa5b8 100644 --- a/src/enumerate/constant/index.js +++ b/src/enumerate/constant/index.js @@ -61,6 +61,14 @@ export const CHANGE_STATUS_MAP = { approved: { label: "已审核", color: "success" }, }; +/** 资质保持监控状态 */ +export const QUAL_MONITOR_STATUS_MAP = { + normal: { label: "保持正常", color: "success" }, + person_warning: { label: "人员比例预警", color: "warning" }, + focus_warning: { label: "重点预警", color: "error" }, + area_pending: { label: "属地待核验", color: "warning" }, +}; + /** 性别 */ export const GENDER_OPTIONS = [ { label: "男", value: 1 }, diff --git a/src/pages/Container/EnterpriseInfo/OrgInfo/index.js b/src/pages/Container/EnterpriseInfo/OrgInfo/index.js index ed3d1d6..a87c268 100644 --- a/src/pages/Container/EnterpriseInfo/OrgInfo/index.js +++ b/src/pages/Container/EnterpriseInfo/OrgInfo/index.js @@ -10,9 +10,10 @@ import { Row, Select, Space, + Flex } from "antd"; import AttachmentUpload from "~/components/AttachmentUpload"; - +import BaiduMapPicker from "~/components/BaiduMapPicker"; import dayjs from "dayjs"; import { useEffect, useState } from "react"; import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout"; @@ -44,6 +45,7 @@ function OrgInfoPage(props) { const [editing, setEditing] = useState(true); const [submitting, setSubmitting] = useState(false); const [detail, setDetail] = useState({}); + const [mapPickerVisible, setMapPickerVisible] = useState(false); /** 是否已存在机构数据(有 id 视为已入库,只能修改) */ const [hasExistingData, setHasExistingData] = useState(false); @@ -128,6 +130,10 @@ function OrgInfoPage(props) { setSubmitting(false); } }; + const handleMapConfirm = ({ lng, lat }) => { + form.setFieldsValue({ longitude: lng, latitude: lat }); + setMapPickerVisible(false); + }; return ( - + @@ -169,7 +179,11 @@ function OrgInfoPage(props) { label="统一社会信用代码" rules={[creditCodeRule(true)]} > - + @@ -180,7 +194,11 @@ function OrgInfoPage(props) { { required: true, message: "请输入安全生产监管行业类别" }, ]} > - + @@ -206,31 +224,62 @@ function OrgInfoPage(props) { label="所属镇、街道" rules={[{ required: true, message: "请输入所属镇街道" }]} > - + - + - - + + + + + + + + @@ -258,7 +307,11 @@ function OrgInfoPage(props) { label="注册地址" rules={[{ required: true, message: "请输入注册地址" }]} > - + @@ -267,7 +320,11 @@ function OrgInfoPage(props) { label="经营地址" rules={[{ required: true, message: "请输入经营地址" }]} > - + @@ -280,7 +337,11 @@ function OrgInfoPage(props) { name="economyIndustryCode" label="国民经济行业分类(GB/T4754-2017)" > - + @@ -301,7 +362,11 @@ function OrgInfoPage(props) { typeof value === "string" ? value.trim() : value } > - + @@ -322,12 +387,20 @@ function OrgInfoPage(props) { typeof value === "string" ? value.trim() : value } > - + - + @@ -356,7 +429,11 @@ function OrgInfoPage(props) { typeof value === "string" ? value.trim() : value } > - + @@ -369,7 +446,11 @@ function OrgInfoPage(props) { - + @@ -381,7 +462,11 @@ function OrgInfoPage(props) { typeof value === "string" ? value.trim() : value } > - + @@ -393,6 +478,7 @@ function OrgInfoPage(props) { @@ -407,6 +493,7 @@ function OrgInfoPage(props) { @@ -421,6 +508,7 @@ function OrgInfoPage(props) { @@ -435,6 +523,7 @@ function OrgInfoPage(props) { @@ -503,10 +592,21 @@ function OrgInfoPage(props) { - + + setMapPickerVisible(false)} + onConfirm={handleMapConfirm} + /> {editing && (
diff --git a/src/pages/Container/Layout/menuConfig.js b/src/pages/Container/Layout/menuConfig.js index d3ed421..c2ebacc 100644 --- a/src/pages/Container/Layout/menuConfig.js +++ b/src/pages/Container/Layout/menuConfig.js @@ -106,6 +106,11 @@ const menuItems = [ label: "备案变更管理", icon: , }, + { + key: "/safetyEval/container/QualificationReview/QualMonitor", + label: "资质保持监控", + icon: , + }, ], }, ] diff --git a/src/pages/Container/QualificationReview/QualMonitor/index.js b/src/pages/Container/QualificationReview/QualMonitor/index.js new file mode 100644 index 0000000..6031efa --- /dev/null +++ b/src/pages/Container/QualificationReview/QualMonitor/index.js @@ -0,0 +1,532 @@ +import React, { useState, useEffect } from "react"; +import { Button, Form, Select, Table, Tag, Modal } from "antd"; +import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction"; +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_QUAL_REVIEW } from "~/enumerate/namespace"; +import { QUAL_MONITOR_STATUS_MAP } from "~/enumerate/constant"; +import "./index.less"; + +const { router } = tools; + +/** 资质条件检查项模板 */ +const CONDITION_ITEMS = [ + { key: "legalPerson", label: "独立法人资格" }, + { key: "fixedAsset", label: "固定资产" }, + { key: "workplace", label: "工作场所" }, + { key: "equipment", label: "设施设备软件" }, + { key: "staffCount", label: "专职技术人员数量" }, + { key: "staffRatio", label: "人员比例" }, + { key: "leader", label: "负责人配备" }, + { key: "system", label: "制度与过程控制体系" }, + { key: "website", label: "承诺书/网站/报告查询" }, + { key: "credit", label: "三年重大违法失信记录" }, + { key: "area", label: "属地满足情况" }, +]; + +/** + * Mock 数据 — 后端接口就绪后删除此数据及下方 MOCK_INSTITUTIONS 引用, + * 改用 props.qualReview.qualMonitorList 即可。 + */ +const MOCK_INSTITUTIONS = [ + { + id: 1, + orgName: "重庆安评技术研究院有限公司", + creditCode: "91500112MA5UQ001", + registerArea: "重庆市渝北区", + bizScopes: ["石油加工业,化学原料、化学品及医药制造业", "金属冶炼"], + bizScopeCount: 2, + minStaffRequire: 30, + status: "normal", + fixedAsset: "1260 万元", + workplace: "1280 ㎡", + archiveRoom: "160 ㎡", + staffCount: 36, + midSafeEngineer: 12, + midSafeRatio: "33%", + seniorTitle: 13, + seniorRatio: "36%", + totalRatio: "69%", + techLeader: "2/2", + processLeader: "1/1", + systemStatus: "制度体系已备案", + promiseStatus: "承诺书已上传", + websiteStatus: "网站可查询", + creditStatus: "近三年无重大违法失信记录", + sourceFields: ["机构备案.固定资产", "场所核验.面积", "人员库.专职人员", "网站备案.报告查询"], + assetOk: true, + workplaceOk: true, + archiveOk: true, + staffOk: true, + conditionDetails: { + legalPerson: { require: "具有独立法人资格", value: "独立法人", result: "通过", source: "机构备案.法人类型、营业执照附件" }, + fixedAsset: { require: "不少于 800 万元", value: "1260 万元", result: "通过", source: "财务材料.固定资产净值、审计报告" }, + workplace: { require: "建筑面积不少于 1000 平方米,档案室不少于 100 平方米", value: "工作场所 1280 平方米,档案室 160 平方米", result: "通过", source: "场所核验.建筑面积、档案室核验.面积" }, + equipment: { require: "具有与业务相适应的技术支撑条件", value: "设备台账、软件授权、检测工具齐全", result: "通过", source: "设备台账.设备清单、软件授权.有效期" }, + staffCount: { require: "单一业务范围不少于 25 人", value: "2 个业务范围,36 人", result: "通过", source: "人员库.专职人员、业务范围.数量" }, + staffRatio: { require: "注安≥30%,高工≥30%,合计≥60%", value: "注安 33%,高工 36%,合计 69%", result: "通过", source: "人员库.注册安全工程师、人员库.职称等级" }, + leader: { require: "每个业务范围配备专职技术负责人", value: "技术负责人 2/2,过程控制负责人 1/1", result: "通过", source: "负责人备案.技术负责人、负责人备案.过程控制负责人" }, + system: { require: "具有健全内部管理制度和安全评价过程控制体系", value: "制度文件已备案,过程控制节点可追溯", result: "通过", source: "制度文件.版本、过程控制.节点记录" }, + website: { require: "法定代表人承诺书已出具;网站正常运行", value: "承诺书已上传;网站可查询机构信息和报告", result: "通过", source: "承诺书.上传状态、网站备案.报告查询状态" }, + credit: { require: "三年内无重大违法失信记录", value: "未命中重大违法失信记录", result: "通过", source: "信用核查.重大违法失信记录" }, + area: { require: "注册地所在行政区域范围内满足资质条件", value: "注册地与资质条件材料一致", result: "通过", source: "异地备案.注册地、本市社保.缴纳人数" }, + }, + }, + { + id: 2, + orgName: "重庆恒安安全评价有限公司", + creditCode: "91500103MA60H002", + registerArea: "重庆市江北区", + bizScopes: ["石油加工业,化学原料、化学品及医药制造业"], + bizScopeCount: 1, + minStaffRequire: 25, + status: "person_warning", + fixedAsset: "920 万元", + workplace: "1040 ㎡", + archiveRoom: "110 ㎡", + staffCount: 27, + midSafeEngineer: 9, + midSafeRatio: "33%", + seniorTitle: 7, + seniorRatio: "26%", + totalRatio: "59%", + techLeader: "1/1", + processLeader: "1/1", + systemStatus: "制度体系已备案", + promiseStatus: "承诺书已上传", + websiteStatus: "网站可查询", + creditStatus: "近三年无重大违法失信记录", + sourceFields: ["人员库.职称等级", "人员库.注册安全工程师", "社保比对.缴纳单位", "业务范围.数量"], + assetOk: true, + workplaceOk: true, + archiveOk: true, + staffOk: true, + conditionDetails: { + legalPerson: { require: "具有独立法人资格", value: "独立法人", result: "通过", source: "机构备案.法人类型" }, + fixedAsset: { require: "不少于 800 万元", value: "920 万元", result: "通过", source: "财务材料.固定资产净值" }, + workplace: { require: "建筑面积不少于 1000 平方米,档案室不少于 100 平方米", value: "工作场所 1040 平方米,档案室 110 平方米", result: "通过", source: "场所核验.建筑面积、档案室核验.面积" }, + equipment: { require: "具有与业务相适应的技术支撑条件", value: "设备台账、软件授权齐全", result: "通过", source: "设备台账.设备清单" }, + staffCount: { require: "单一业务范围不少于 25 人", value: "1 个业务范围,27 人", result: "通过", source: "人员库.专职人员" }, + staffRatio: { require: "注安≥30%,高工≥30%,合计≥60%", value: "注安 33%,高工 26%,合计 59%", result: "预警", source: "人员库.职称等级" }, + leader: { require: "每个业务范围配备专职技术负责人", value: "技术负责人 1/1,过程控制负责人 1/1", result: "通过", source: "负责人备案" }, + system: { require: "具有健全内部管理制度和安全评价过程控制体系", value: "制度文件已备案", result: "通过", source: "制度文件" }, + website: { require: "法定代表人承诺书已出具;网站正常运行", value: "承诺书已上传;网站可查询", result: "通过", source: "网站备案" }, + credit: { require: "三年内无重大违法失信记录", value: "未命中", result: "通过", source: "信用核查" }, + area: { require: "注册地所在行政区域范围内满足资质条件", value: "注册地与资质条件材料一致", result: "通过", source: "本市社保.缴纳人数" }, + }, + }, + { + id: 3, + orgName: "重庆渝安风险评估中心", + creditCode: "91500108MA61Y003", + registerArea: "重庆市南岸区", + bizScopes: ["金属、非金属矿及其他矿采选业"], + bizScopeCount: 1, + minStaffRequire: 25, + status: "focus_warning", + fixedAsset: "760 万元", + workplace: "980 ㎡", + archiveRoom: "90 ㎡", + staffCount: 24, + midSafeEngineer: 7, + midSafeRatio: "29%", + seniorTitle: 8, + seniorRatio: "33%", + totalRatio: "62%", + techLeader: "1/1", + processLeader: "1/1", + systemStatus: "制度体系已备案", + promiseStatus: "承诺书已上传", + websiteStatus: "网站报告查询接口异常", + creditStatus: "近三年无重大违法失信记录", + sourceFields: ["财务材料.固定资产", "场所核验.建筑面积", "档案室核验.面积", "人员库.专职人数", "网站监测.查询状态"], + assetOk: false, + workplaceOk: false, + archiveOk: false, + staffOk: false, + conditionDetails: { + legalPerson: { require: "具有独立法人资格", value: "独立法人", result: "通过", source: "机构备案.法人类型" }, + fixedAsset: { require: "不少于 800 万元", value: "760 万元", result: "不通过", source: "财务材料.固定资产净值" }, + workplace: { require: "建筑面积不少于 1000 平方米,档案室不少于 100 平方米", value: "工作场所 980 平方米,档案室 90 平方米", result: "不通过", source: "场所核验.建筑面积" }, + equipment: { require: "具有与业务相适应的技术支撑条件", value: "设备台账齐全,报告查询接口异常", result: "部分异常", source: "网站监测.查询状态" }, + staffCount: { require: "单一业务范围不少于 25 人", value: "1 个业务范围,24 人", result: "不通过", source: "人员库.专职人数" }, + staffRatio: { require: "注安≥30%,高工≥30%,合计≥60%", value: "注安 29%,高工 33%,合计 62%", result: "预警", source: "人员库.注册安全工程师" }, + leader: { require: "每个业务范围配备专职技术负责人", value: "技术负责人 1/1,过程控制负责人 1/1", result: "通过", source: "负责人备案" }, + system: { require: "具有健全内部管理制度和安全评价过程控制体系", value: "制度文件已备案", result: "通过", source: "制度文件" }, + website: { require: "法定代表人承诺书已出具;网站正常运行", value: "承诺书已上传;网站报告查询接口异常", result: "预警", source: "网站备案.报告查询状态" }, + credit: { require: "三年内无重大违法失信记录", value: "未命中", result: "通过", source: "信用核查" }, + area: { require: "注册地所在行政区域范围内满足资质条件", value: "注册地与资质条件材料一致", result: "通过", source: "本市社保" }, + }, + }, + { + id: 4, + orgName: "北京中安评价中心重庆分公司", + creditCode: "91110108MA62B004", + registerArea: "北京市,重庆备案场所:两江新区", + bizScopes: ["陆上油气管道运输业"], + bizScopeCount: 1, + minStaffRequire: 25, + status: "area_pending", + fixedAsset: "1080 万元", + workplace: "1120 ㎡", + archiveRoom: "120 ㎡", + staffCount: 31, + midSafeEngineer: 10, + midSafeRatio: "32%", + seniorTitle: 9, + seniorRatio: "29%", + totalRatio: "61%", + techLeader: "1/1", + processLeader: "1/1", + systemStatus: "制度体系已备案", + promiseStatus: "承诺书已上传", + websiteStatus: "网站可查询", + creditStatus: "近三年无重大违法失信记录", + sourceFields: ["异地备案.注册地", "本市社保.缴纳人数", "场所核验.租赁合同", "业务范围.油气管道"], + assetOk: true, + workplaceOk: true, + archiveOk: true, + staffOk: true, + conditionDetails: { + legalPerson: { require: "具有独立法人资格", value: "总机构独立法人,重庆为分公司备案", result: "需核验", source: "机构备案.法人类型" }, + fixedAsset: { require: "不少于 800 万元", value: "1080 万元", result: "通过", source: "财务材料.固定资产净值" }, + workplace: { require: "建筑面积不少于 1000 平方米,档案室不少于 100 平方米", value: "重庆场所 1120 平方米,档案室 120 平方米", result: "通过", source: "场所核验.租赁合同" }, + equipment: { require: "具有与业务相适应的技术支撑条件", value: "设备台账齐全", result: "通过", source: "设备台账" }, + staffCount: { require: "单一业务范围不少于 25 人", value: "备案 31 人,本市社保 21 人", result: "不通过", source: "社保比对.缴纳单位" }, + staffRatio: { require: "注安≥30%,高工≥30%,合计≥60%", value: "本市专职人员比例待核验", result: "需核验", source: "人员库.本市社保" }, + leader: { require: "每个业务范围配备专职技术负责人", value: "技术负责人 1/1,过程控制负责人 1/1", result: "通过", source: "负责人备案" }, + system: { require: "具有健全内部管理制度和安全评价过程控制体系", value: "制度文件已备案", result: "通过", source: "制度文件" }, + website: { require: "法定代表人承诺书已出具;网站正常运行", value: "承诺书已上传;网站可查询", result: "通过", source: "网站备案" }, + credit: { require: "三年内无重大违法失信记录", value: "未命中", result: "通过", source: "信用核查" }, + area: { require: "注册地所在行政区域范围内满足资质条件", value: "重庆备案场所达标,本市社保专职人员不足", result: "需核验", source: "异地备案.注册地" }, + }, + }, +]; + +const STATUS_OPTIONS = [ + { value: "", label: "全部" }, + { value: "normal", label: "保持正常" }, + { value: "person_warning", label: "人员比例预警" }, + { value: "focus_warning", label: "重点预警" }, + { value: "area_pending", label: "属地待核验" }, +]; + +const SCOPE_OPTIONS = [ + { value: "", label: "全部" }, + { value: "化工", label: "石油加工业,化学原料、化学品及医药制造业" }, + { value: "金属冶炼", label: "金属冶炼" }, + { value: "矿采选", label: "金属、非金属矿及其他矿采选业" }, + { value: "油气管道", label: "陆上油气管道运输业" }, +]; + +/** 统计卡片数据 */ +function computeStats(list) { + return { + total: list.length, + normal: list.filter((i) => i.status === "normal").length, + warning: list.filter((i) => i.status === "person_warning" || i.status === "area_pending").length, + focus: list.filter((i) => i.status === "focus_warning").length, + }; +} + +const QualMonitor = (props) => { + const [form] = Form.useForm(); + const [detailVisible, setDetailVisible] = useState(false); + const [detailRecord, setDetailRecord] = useState(null); + // 待后端接口就绪后,data 改为使用 props.qualReview.qualMonitorList, + // 并移除 MOCK_INSTITUTIONS 引用 + const [data, setData] = useState(MOCK_INSTITUTIONS); + + const { qualReview, queryQualMonitorPage, queryQualMonitorCondition } = props; + const { qualMonitorList, qualMonitorTotal, qualReviewLoading } = qualReview || {}; + + const stats = computeStats(data); + + /** 搜索 — 当前使用 mock 本地过滤;接接口后改为调用 queryQualMonitorPage */ + const onFinish = (values) => { + const { keyword, scope, status } = values; + let filtered = MOCK_INSTITUTIONS; + if (keyword) { + filtered = filtered.filter( + (i) => i.orgName.includes(keyword) || i.creditCode.includes(keyword), + ); + } + if (scope) { + filtered = filtered.filter((i) => + i.bizScopes.some((s) => s.includes(scope)), + ); + } + if (status) { + filtered = filtered.filter((i) => i.status === status); + } + router.query = { ...router.query, ...values, current: 1, size: 10 }; + setData(filtered); + }; + + /** 接接口后改为 queryQualMonitorPage 调用 */ + const handleSearch = () => { + // queryQualMonitorPage(router.query); + }; + + const handleReset = (values) => { + router.query = { ...router.query, ...values, current: 1, size: 10 }; + handleSearch(); + }; + + useEffect(() => { + form.setFieldsValue(router.query); + // 接接口后打开下行注释 + // handleSearch(); + }, []); + + const openDetail = (record) => { + setDetailRecord(record); + setDetailVisible(true); + }; + + const resultTag = (result) => { + if (result === "通过") return 通过; + if (result === "预警" || result === "需核验") return {result}; + if (result === "不通过" || result === "部分异常") return {result}; + return {result}; + }; + + const columns = [ + { + title: "机构信息", + dataIndex: "orgName", + width: 240, + fixed: "left", + render: (name, record) => ( +
+
{name}
+
+ 统一社会信用代码:{record.creditCode} | 注册地:{record.registerArea} +
+
+ ), + }, + { + title: "具备资质/业务范围", + dataIndex: "bizScopes", + width: 260, + render: (scopes, record) => ( +
+
+ {scopes.map((s, i) => ( + {s} + ))} +
+
+ 业务范围数:{record.bizScopeCount};最低专职人员要求:{record.minStaffRequire} 人 +
+
+ ), + }, + { + title: "状态", + dataIndex: "status", + width: 110, + render: (status) => { + const config = QUAL_MONITOR_STATUS_MAP[status]; + return config ? {config.label} : "--"; + }, + }, + { + title: "固定资产/场所", + dataIndex: "fixedAsset", + width: 260, + render: (_, record) => ( +
+
固定资产 {record.fixedAsset};工作场所 {record.workplace};档案室 {record.archiveRoom}
+
+ {record.assetOk && record.workplaceOk ? "均满足资质条件" : "三项基础条件低于要求"} +
+
+ ), + }, + { + title: "人员与负责人", + dataIndex: "staffCount", + width: 300, + render: (_, record) => ( +
+ 专职技术人员 {record.staffCount} 人;中级及以上注册安全工程师 {record.midSafeEngineer} 人/{record.midSafeRatio}; + 高级职称 {record.seniorTitle} 人/{record.seniorRatio};合计 {record.totalRatio}; + 技术负责人 {record.techLeader},过程控制负责人 {record.processLeader} +
+ ), + }, + { + title: "制度/网站/信用", + dataIndex: "systemStatus", + width: 220, + render: (_, record) => ( +
+ {record.systemStatus};{record.promiseStatus};{record.websiteStatus};{record.creditStatus} +
+ ), + }, + { + title: "来源字段", + dataIndex: "sourceFields", + width: 200, + render: (fields) => ( +
+ {fields.map((f, i) => ( + {f} + ))} +
+ ), + }, + { + title: "操作", + width: 120, + fixed: "right", + render: (_, record) => ( + + + + ), + }, + ]; + + return ( + +
+ + + {/* 统计卡片 */} +
+ {[ + { label: "备案机构总数", value: stats.total, hint: "来源:已备案机构管理.机构状态", color: "#1e293b", iconBg: "#ede9fe", iconColor: "#7c3aed", iconText: "机" }, + { label: "条件保持正常", value: stats.normal, hint: "十项资质条件均通过", color: "#059669", iconBg: "#d1fae5", iconColor: "#059669", iconText: "正" }, + { label: "条件预警机构", value: stats.warning, hint: "人员比例、属地材料待核验", color: "#d97706", iconBg: "#fef3c7", iconColor: "#d97706", iconText: "预" }, + + ].map((card, idx) => ( +
+
+
{card.label}
+
{card.value}
+
{card.hint}
+
+
+ {card.iconText} +
+
+ ))} +
+ + {/* 资质条件来源说明 */} +
+ 资质条件来源: + 依据安全评价机构资质条件,将机构备案信息拆为固定资产不少于 800 万元、工作场所不少于 1000 平方米、档案室不少于 100 平方米、 + 专职技术人员数量及比例、技术负责人/过程控制负责人、内部制度、法定代表人承诺、公开网站、三年重大违法失信记录、注册地属地满足情况等字段。 +
+ + {/* 搜索表单 */} + + + , + + ({ label: o.label, value: o.value }))} + /> + , + + ({ label: o.label, value: o.value }))} + /> + , + ]} + onReset={handleReset} + onFinish={onFinish} + /> + + + {/* 数据表格 */} + `共 ${total} 条`, + current: router.query.current || 1, + pageSize: router.query.size || 10, + onChange: (page, size) => { + router.query = { ...router.query, current: page, size }; + }, + }} + /> + + {/* 条件详情弹窗 */} + setDetailVisible(false)} + width={960} + footer={ +
+ +
+ } + > + {detailRecord && ( +
+
+ 机构核查口径:以机构为主线,将资质条件拆为十项保持条件和属地要求; + 每项展示监管字段、当前值、结论和来源。 +
+
{text} }, + ]} + dataSource={CONDITION_ITEMS.map((item) => { + const detail = detailRecord.conditionDetails[item.key]; + return detail ? { key: item.key, label: item.label, ...detail } : null; + }).filter(Boolean)} + /> + + )} + + + + ); +}; + +export default Connect([NS_QUAL_REVIEW], true)(AntdTableFuncControl(QualMonitor)); diff --git a/src/pages/Container/QualificationReview/QualMonitor/index.less b/src/pages/Container/QualificationReview/QualMonitor/index.less new file mode 100644 index 0000000..18e4cac --- /dev/null +++ b/src/pages/Container/QualificationReview/QualMonitor/index.less @@ -0,0 +1,168 @@ +.qual-monitor { + // 页面描述条 + .page-desc { + font-size: 0.82rem; + color: #64748b; + margin-bottom: 16px; + padding: 8px 12px; + background: #f8fafc; + border-radius: 8px; + border: 1px solid #e2e8f0; + } + + // 统计卡片网格 + .stat-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + margin-bottom: 10px; + } + + // 统计卡片 + .stat-card { + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 8px; + padding: 12px; + display: flex; + justify-content: space-between; + + .stat-info { + .stat-label { + font-size: 0.82rem; + color: #64748b; + } + + .stat-value { + font-size: 1.75rem; + font-weight: 700; + line-height: 1.15; + margin-top: 6px; + } + + .stat-hint { + font-size: 0.75rem; + color: #64748b; + margin-top: 4px; + } + } + + .stat-icon { + width: 38px; + height: 38px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.1rem; + flex-shrink: 0; + } + } + + // 资质条件来源说明 + .source-note { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + padding: 8px 14px; + font-size: 0.82rem; + color: #64748b; + margin-bottom: 10px; + + strong { + color: #1e293b; + } + } + + // 机构信息 + .org-info { + .org-name { + font-weight: 500; + } + + .org-meta { + font-size: 0.72rem; + color: #64748b; + } + } + + // 业务范围标签 + .biz-scope-wrap { + display: flex; + flex-wrap: wrap; + gap: 4px; + + .biz-scope-hint { + font-size: 0.72rem; + color: #64748b; + margin-top: 4px; + } + } + + // 场所状态 + .place-status { + font-size: 0.72rem; + } + + .place-ok { + color: #059669; + } + + .place-bad { + color: #dc2626; + } + + // 来源字段药丸 + .source-pill { + display: inline-flex; + align-items: center; + gap: 2px; + padding: 2px 7px; + border-radius: 999px; + background: #f1f5f9; + color: #475569; + font-size: 0.68rem; + white-space: nowrap; + } + + // 核查口径说明 + .check-note { + margin-bottom: 12px; + font-size: 0.86rem; + color: #64748b; + padding: 12px; + border: 1px solid #e2e8f0; + border-radius: 8px; + background: #f8fafc; + } + + // 条件详情表格 + .detail-table { + width: 100%; + border-collapse: collapse; + font-size: 0.82rem; + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 8px; + + th { + padding: 8px 12px; + border-bottom: 1px solid #e2e8f0; + text-align: left; + background: #f8fafc; + } + + td { + padding: 8px 12px; + border-bottom: 1px solid #f1f5f9; + } + + .cell-source { + font-size: 0.78rem; + } + + .cell-label { + font-weight: 500; + } + } +}