feat
parent
0a0d207409
commit
9c8e386773
|
|
@ -7,6 +7,7 @@
|
||||||
/prototype
|
/prototype
|
||||||
/dist
|
/dist
|
||||||
/demo
|
/demo
|
||||||
|
/doc
|
||||||
/docs
|
/docs
|
||||||
/.trae/
|
/.trae/
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
|
|
||||||
|
|
@ -61,4 +61,18 @@ export const modifyFilingMaterialChange = declareRequest(
|
||||||
export const modifyFilingMaterial = declareRequest(
|
export const modifyFilingMaterial = declareRequest(
|
||||||
"qualFilingMaterialLoading",
|
"qualFilingMaterialLoading",
|
||||||
"Post > @/safetyEval/qual-filing-material/modify",
|
"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 || {}',
|
||||||
);
|
);
|
||||||
|
|
@ -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 (
|
||||||
|
<Modal
|
||||||
|
title="选择坐标"
|
||||||
|
open={visible}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onOk={handleConfirm}
|
||||||
|
width={800}
|
||||||
|
okText="确认"
|
||||||
|
cancelText="取消"
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="baiduSearchInput"
|
||||||
|
className="baidu-map-search"
|
||||||
|
placeholder="输入地名搜索"
|
||||||
|
/>
|
||||||
|
<div ref={mapRef} className="baidu-map-container" />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BaiduMapPicker;
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -61,6 +61,14 @@ export const CHANGE_STATUS_MAP = {
|
||||||
approved: { label: "已审核", color: "success" },
|
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 = [
|
export const GENDER_OPTIONS = [
|
||||||
{ label: "男", value: 1 },
|
{ label: "男", value: 1 },
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ import {
|
||||||
Row,
|
Row,
|
||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
|
Flex
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import AttachmentUpload from "~/components/AttachmentUpload";
|
import AttachmentUpload from "~/components/AttachmentUpload";
|
||||||
|
import BaiduMapPicker from "~/components/BaiduMapPicker";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
||||||
|
|
@ -44,6 +45,7 @@ function OrgInfoPage(props) {
|
||||||
const [editing, setEditing] = useState(true);
|
const [editing, setEditing] = useState(true);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [detail, setDetail] = useState({});
|
const [detail, setDetail] = useState({});
|
||||||
|
const [mapPickerVisible, setMapPickerVisible] = useState(false);
|
||||||
/** 是否已存在机构数据(有 id 视为已入库,只能修改) */
|
/** 是否已存在机构数据(有 id 视为已入库,只能修改) */
|
||||||
const [hasExistingData, setHasExistingData] = useState(false);
|
const [hasExistingData, setHasExistingData] = useState(false);
|
||||||
|
|
||||||
|
|
@ -128,6 +130,10 @@ function OrgInfoPage(props) {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handleMapConfirm = ({ lng, lat }) => {
|
||||||
|
form.setFieldsValue({ longitude: lng, latitude: lat });
|
||||||
|
setMapPickerVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout
|
<PageLayout
|
||||||
|
|
@ -160,7 +166,11 @@ function OrgInfoPage(props) {
|
||||||
label="生产经营单位名称"
|
label="生产经营单位名称"
|
||||||
rules={[{ required: true, message: "请输入生产经营单位名称" }]}
|
rules={[{ required: true, message: "请输入生产经营单位名称" }]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入生产经营单位名称" allowClear maxLength={200} />
|
<Input
|
||||||
|
placeholder="请输入生产经营单位名称"
|
||||||
|
allowClear
|
||||||
|
maxLength={200}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -169,7 +179,11 @@ function OrgInfoPage(props) {
|
||||||
label="统一社会信用代码"
|
label="统一社会信用代码"
|
||||||
rules={[creditCodeRule(true)]}
|
rules={[creditCodeRule(true)]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入统一社会信用代码" allowClear maxLength={18} />
|
<Input
|
||||||
|
placeholder="请输入统一社会信用代码"
|
||||||
|
allowClear
|
||||||
|
maxLength={18}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -180,7 +194,11 @@ function OrgInfoPage(props) {
|
||||||
{ required: true, message: "请输入安全生产监管行业类别" },
|
{ required: true, message: "请输入安全生产监管行业类别" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入安全生产监管行业类别" allowClear maxLength={100} />
|
<Input
|
||||||
|
placeholder="请输入安全生产监管行业类别"
|
||||||
|
allowClear
|
||||||
|
maxLength={100}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -206,31 +224,62 @@ function OrgInfoPage(props) {
|
||||||
label="所属镇、街道"
|
label="所属镇、街道"
|
||||||
rules={[{ required: true, message: "请输入所属镇街道" }]}
|
rules={[{ required: true, message: "请输入所属镇街道" }]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入所属镇街道" allowClear maxLength={100} />
|
<Input
|
||||||
|
placeholder="请输入所属镇街道"
|
||||||
|
allowClear
|
||||||
|
maxLength={100}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item name="villageCommunity" label="属村(社区)">
|
<Form.Item name="villageCommunity" label="属村(社区)">
|
||||||
<Input placeholder="请输入属村(社区)" allowClear maxLength={100} />
|
<Input
|
||||||
|
placeholder="请输入属村(社区)"
|
||||||
|
allowClear
|
||||||
|
maxLength={100}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item
|
<Form.Item label="所在地坐标经度">
|
||||||
name="longitude"
|
<Flex>
|
||||||
label="所在地坐标经度"
|
<Form.Item
|
||||||
dependencies={["latitude"]}
|
name="longitude"
|
||||||
rules={[
|
noStyle
|
||||||
longitudeRule(false),
|
dependencies={["latitude"]}
|
||||||
coordinatePairRule("latitude", "经度", "纬度"),
|
rules={[
|
||||||
]}
|
{
|
||||||
>
|
validator(_, value) {
|
||||||
<InputNumber
|
const latitude = form.getFieldValue("latitude");
|
||||||
style={{ width: "100%" }}
|
if (
|
||||||
min={-180}
|
value !== undefined &&
|
||||||
max={180}
|
value !== null &&
|
||||||
precision={6}
|
value !== "" &&
|
||||||
placeholder="请输入经度"
|
(latitude === undefined ||
|
||||||
/>
|
latitude === null ||
|
||||||
|
latitude === "")
|
||||||
|
) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error("填写经度后,纬度必填"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
min={-180}
|
||||||
|
max={180}
|
||||||
|
precision={6}
|
||||||
|
placeholder="请输入经度"
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Button onClick={() => setMapPickerVisible(true)}>选择</Button>
|
||||||
|
</Flex>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -258,7 +307,11 @@ function OrgInfoPage(props) {
|
||||||
label="注册地址"
|
label="注册地址"
|
||||||
rules={[{ required: true, message: "请输入注册地址" }]}
|
rules={[{ required: true, message: "请输入注册地址" }]}
|
||||||
>
|
>
|
||||||
<Input.TextArea rows={3} placeholder="请输入注册地址" maxLength={200} />
|
<Input.TextArea
|
||||||
|
rows={3}
|
||||||
|
placeholder="请输入注册地址"
|
||||||
|
maxLength={200}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -267,7 +320,11 @@ function OrgInfoPage(props) {
|
||||||
label="经营地址"
|
label="经营地址"
|
||||||
rules={[{ required: true, message: "请输入经营地址" }]}
|
rules={[{ required: true, message: "请输入经营地址" }]}
|
||||||
>
|
>
|
||||||
<Input.TextArea rows={3} placeholder="请输入经营地址" maxLength={200} />
|
<Input.TextArea
|
||||||
|
rows={3}
|
||||||
|
placeholder="请输入经营地址"
|
||||||
|
maxLength={200}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -280,7 +337,11 @@ function OrgInfoPage(props) {
|
||||||
name="economyIndustryCode"
|
name="economyIndustryCode"
|
||||||
label="国民经济行业分类(GB/T4754-2017)"
|
label="国民经济行业分类(GB/T4754-2017)"
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入国民经济行业分类" allowClear maxLength={50} />
|
<Input
|
||||||
|
placeholder="请输入国民经济行业分类"
|
||||||
|
allowClear
|
||||||
|
maxLength={50}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -301,7 +362,11 @@ function OrgInfoPage(props) {
|
||||||
typeof value === "string" ? value.trim() : value
|
typeof value === "string" ? value.trim() : value
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入法定代表人联系电话" allowClear maxLength={11} />
|
<Input
|
||||||
|
placeholder="请输入法定代表人联系电话"
|
||||||
|
allowClear
|
||||||
|
maxLength={11}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -322,12 +387,20 @@ function OrgInfoPage(props) {
|
||||||
typeof value === "string" ? value.trim() : value
|
typeof value === "string" ? value.trim() : value
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入主要负责人联系电话" allowClear maxLength={11} />
|
<Input
|
||||||
|
placeholder="请输入主要负责人联系电话"
|
||||||
|
allowClear
|
||||||
|
maxLength={11}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item name="safetyDeptManager" label="安全管理部门负责人">
|
<Form.Item name="safetyDeptManager" label="安全管理部门负责人">
|
||||||
<Input placeholder="请输入安全管理部门负责人" allowClear maxLength={50} />
|
<Input
|
||||||
|
placeholder="请输入安全管理部门负责人"
|
||||||
|
allowClear
|
||||||
|
maxLength={50}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -356,7 +429,11 @@ function OrgInfoPage(props) {
|
||||||
typeof value === "string" ? value.trim() : value
|
typeof value === "string" ? value.trim() : value
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入主管安全副总联系电话" allowClear maxLength={11} />
|
<Input
|
||||||
|
placeholder="请输入主管安全副总联系电话"
|
||||||
|
allowClear
|
||||||
|
maxLength={11}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -369,7 +446,11 @@ function OrgInfoPage(props) {
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item name="businessStatusName" label="企业经营状态">
|
<Form.Item name="businessStatusName" label="企业经营状态">
|
||||||
<Input placeholder="请输入企业经营状态" allowClear maxLength={50} />
|
<Input
|
||||||
|
placeholder="请输入企业经营状态"
|
||||||
|
allowClear
|
||||||
|
maxLength={50}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -381,7 +462,11 @@ function OrgInfoPage(props) {
|
||||||
typeof value === "string" ? value.trim() : value
|
typeof value === "string" ? value.trim() : value
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入信息公开网址" allowClear maxLength={200} />
|
<Input
|
||||||
|
placeholder="请输入信息公开网址"
|
||||||
|
allowClear
|
||||||
|
maxLength={200}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|
@ -393,6 +478,7 @@ function OrgInfoPage(props) {
|
||||||
<InputNumber
|
<InputNumber
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
min={0}
|
min={0}
|
||||||
|
max={99999}
|
||||||
precision={2}
|
precision={2}
|
||||||
placeholder="请输入工作场所建筑面积"
|
placeholder="请输入工作场所建筑面积"
|
||||||
/>
|
/>
|
||||||
|
|
@ -407,6 +493,7 @@ function OrgInfoPage(props) {
|
||||||
<InputNumber
|
<InputNumber
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
min={0}
|
min={0}
|
||||||
|
max={99999}
|
||||||
precision={2}
|
precision={2}
|
||||||
placeholder="请输入档案室面积"
|
placeholder="请输入档案室面积"
|
||||||
/>
|
/>
|
||||||
|
|
@ -421,6 +508,7 @@ function OrgInfoPage(props) {
|
||||||
<InputNumber
|
<InputNumber
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
min={0}
|
min={0}
|
||||||
|
max={99999}
|
||||||
precision={0}
|
precision={0}
|
||||||
placeholder="请输入专职安全评价师数量"
|
placeholder="请输入专职安全评价师数量"
|
||||||
/>
|
/>
|
||||||
|
|
@ -435,6 +523,7 @@ function OrgInfoPage(props) {
|
||||||
<InputNumber
|
<InputNumber
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
min={0}
|
min={0}
|
||||||
|
max={99999}
|
||||||
precision={0}
|
precision={0}
|
||||||
placeholder="请输入注册安全工程师数量"
|
placeholder="请输入注册安全工程师数量"
|
||||||
/>
|
/>
|
||||||
|
|
@ -503,10 +592,21 @@ function OrgInfoPage(props) {
|
||||||
<Form.Item name="filingRecordStatusName" noStyle />
|
<Form.Item name="filingRecordStatusName" noStyle />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<AttachmentUpload name="attachmentUrls" label="上传附件" />
|
<AttachmentUpload
|
||||||
|
name="attachmentUrls"
|
||||||
|
label="上传附件"
|
||||||
|
extra={"最多上传10个PDF、DOC、DOCX格式文件"}
|
||||||
|
maxCount={10}
|
||||||
|
accept=".pdf,.doc,.docx"
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Form>
|
</Form>
|
||||||
|
<BaiduMapPicker
|
||||||
|
visible={mapPickerVisible}
|
||||||
|
onCancel={() => setMapPickerVisible(false)}
|
||||||
|
onConfirm={handleMapConfirm}
|
||||||
|
/>
|
||||||
{editing && (
|
{editing && (
|
||||||
<div style={{ marginTop: 24, textAlign: "center" }}>
|
<div style={{ marginTop: 24, textAlign: "center" }}>
|
||||||
<Space>
|
<Space>
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,11 @@ const menuItems = [
|
||||||
label: "备案变更管理",
|
label: "备案变更管理",
|
||||||
icon: <SwapOutlined />,
|
icon: <SwapOutlined />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "/safetyEval/container/QualificationReview/QualMonitor",
|
||||||
|
label: "资质保持监控",
|
||||||
|
icon: <BarChartOutlined />,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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 <Tag color="success">通过</Tag>;
|
||||||
|
if (result === "预警" || result === "需核验") return <Tag color="warning">{result}</Tag>;
|
||||||
|
if (result === "不通过" || result === "部分异常") return <Tag color="error">{result}</Tag>;
|
||||||
|
return <Tag>{result}</Tag>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "机构信息",
|
||||||
|
dataIndex: "orgName",
|
||||||
|
width: 240,
|
||||||
|
fixed: "left",
|
||||||
|
render: (name, record) => (
|
||||||
|
<div className="org-info">
|
||||||
|
<div className="org-name">{name}</div>
|
||||||
|
<div className="org-meta">
|
||||||
|
统一社会信用代码:{record.creditCode} | 注册地:{record.registerArea}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "具备资质/业务范围",
|
||||||
|
dataIndex: "bizScopes",
|
||||||
|
width: 260,
|
||||||
|
render: (scopes, record) => (
|
||||||
|
<div className="biz-scope-wrap">
|
||||||
|
<div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
|
||||||
|
{scopes.map((s, i) => (
|
||||||
|
<Tag key={i} color="blue">{s}</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="biz-scope-hint">
|
||||||
|
业务范围数:{record.bizScopeCount};最低专职人员要求:{record.minStaffRequire} 人
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "状态",
|
||||||
|
dataIndex: "status",
|
||||||
|
width: 110,
|
||||||
|
render: (status) => {
|
||||||
|
const config = QUAL_MONITOR_STATUS_MAP[status];
|
||||||
|
return config ? <Tag color={config.color}>{config.label}</Tag> : "--";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "固定资产/场所",
|
||||||
|
dataIndex: "fixedAsset",
|
||||||
|
width: 260,
|
||||||
|
render: (_, record) => (
|
||||||
|
<div>
|
||||||
|
<div>固定资产 {record.fixedAsset};工作场所 {record.workplace};档案室 {record.archiveRoom}</div>
|
||||||
|
<div className={`place-status ${record.assetOk && record.workplaceOk ? "place-ok" : "place-bad"}`}>
|
||||||
|
{record.assetOk && record.workplaceOk ? "均满足资质条件" : "三项基础条件低于要求"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "人员与负责人",
|
||||||
|
dataIndex: "staffCount",
|
||||||
|
width: 300,
|
||||||
|
render: (_, record) => (
|
||||||
|
<div>
|
||||||
|
专职技术人员 {record.staffCount} 人;中级及以上注册安全工程师 {record.midSafeEngineer} 人/{record.midSafeRatio};
|
||||||
|
高级职称 {record.seniorTitle} 人/{record.seniorRatio};合计 {record.totalRatio};
|
||||||
|
技术负责人 {record.techLeader},过程控制负责人 {record.processLeader}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "制度/网站/信用",
|
||||||
|
dataIndex: "systemStatus",
|
||||||
|
width: 220,
|
||||||
|
render: (_, record) => (
|
||||||
|
<div>
|
||||||
|
{record.systemStatus};{record.promiseStatus};{record.websiteStatus};{record.creditStatus}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "来源字段",
|
||||||
|
dataIndex: "sourceFields",
|
||||||
|
width: 200,
|
||||||
|
render: (fields) => (
|
||||||
|
<div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
|
||||||
|
{fields.map((f, i) => (
|
||||||
|
<span key={i} className="source-pill">{f}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
width: 120,
|
||||||
|
fixed: "right",
|
||||||
|
render: (_, record) => (
|
||||||
|
<TableAction>
|
||||||
|
<Button type="primary" size="small" onClick={() => openDetail(record)}>
|
||||||
|
条件详情
|
||||||
|
</Button>
|
||||||
|
</TableAction>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout title="资质保持监控">
|
||||||
|
<div className="qual-monitor">
|
||||||
|
|
||||||
|
|
||||||
|
{/* 统计卡片 */}
|
||||||
|
<div className="stat-grid">
|
||||||
|
{[
|
||||||
|
{ 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) => (
|
||||||
|
<div key={idx} className="stat-card">
|
||||||
|
<div className="stat-info">
|
||||||
|
<div className="stat-label">{card.label}</div>
|
||||||
|
<div className="stat-value" style={{ color: card.color }}>{card.value}</div>
|
||||||
|
<div className="stat-hint">{card.hint}</div>
|
||||||
|
</div>
|
||||||
|
<div className="stat-icon" style={{ background: card.iconBg, color: card.iconColor }}>
|
||||||
|
{card.iconText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 资质条件来源说明 */}
|
||||||
|
<div className="source-note">
|
||||||
|
<strong>资质条件来源:</strong>
|
||||||
|
依据安全评价机构资质条件,将机构备案信息拆为固定资产不少于 800 万元、工作场所不少于 1000 平方米、档案室不少于 100 平方米、
|
||||||
|
专职技术人员数量及比例、技术负责人/过程控制负责人、内部制度、法定代表人承诺、公开网站、三年重大违法失信记录、注册地属地满足情况等字段。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 搜索表单 */}
|
||||||
|
<SearchForm
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
|
form={form}
|
||||||
|
loading={qualReviewLoading}
|
||||||
|
formLine={[
|
||||||
|
<Form.Item key="keyword" name="keyword">
|
||||||
|
<ControlWrapper.Input
|
||||||
|
label="机构名称/信用代码"
|
||||||
|
placeholder="输入机构名称或统一社会信用代码"
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>,
|
||||||
|
<Form.Item key="scope" name="scope">
|
||||||
|
<ControlWrapper.Select
|
||||||
|
label="业务范围"
|
||||||
|
placeholder="请选择"
|
||||||
|
allowClear
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
options={SCOPE_OPTIONS.map((o) => ({ label: o.label, value: o.value }))}
|
||||||
|
/>
|
||||||
|
</Form.Item>,
|
||||||
|
<Form.Item key="status" name="status">
|
||||||
|
<ControlWrapper.Select
|
||||||
|
label="资质状态"
|
||||||
|
placeholder="请选择"
|
||||||
|
allowClear
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
options={STATUS_OPTIONS.map((o) => ({ label: o.label, value: o.value }))}
|
||||||
|
/>
|
||||||
|
</Form.Item>,
|
||||||
|
]}
|
||||||
|
onReset={handleReset}
|
||||||
|
onFinish={onFinish}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
{/* 数据表格 */}
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data}
|
||||||
|
scroll={{ x: 1600, y: props.scrollY }}
|
||||||
|
loading={qualReviewLoading}
|
||||||
|
pagination={{
|
||||||
|
total: data.length,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
|
current: router.query.current || 1,
|
||||||
|
pageSize: router.query.size || 10,
|
||||||
|
onChange: (page, size) => {
|
||||||
|
router.query = { ...router.query, current: page, size };
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 条件详情弹窗 */}
|
||||||
|
<Modal
|
||||||
|
title={detailRecord ? `资质保持条件详情 - ${detailRecord.orgName}` : ""}
|
||||||
|
open={detailVisible}
|
||||||
|
onCancel={() => setDetailVisible(false)}
|
||||||
|
width={960}
|
||||||
|
footer={
|
||||||
|
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
|
||||||
|
<Button onClick={() => setDetailVisible(false)}>关闭</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{detailRecord && (
|
||||||
|
<div>
|
||||||
|
<div className="check-note">
|
||||||
|
<strong>机构核查口径:</strong>以机构为主线,将资质条件拆为十项保持条件和属地要求;
|
||||||
|
每项展示监管字段、当前值、结论和来源。
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
rowKey="key"
|
||||||
|
size="small"
|
||||||
|
pagination={false}
|
||||||
|
columns={[
|
||||||
|
{ title: "检查项", dataIndex: "label", width: 120 },
|
||||||
|
{ title: "资质要求", dataIndex: "require", width: 280 },
|
||||||
|
{ title: "当前值", dataIndex: "value", width: 260 },
|
||||||
|
{ title: "结论", dataIndex: "result", width: 80, render: resultTag },
|
||||||
|
{ title: "来源字段", dataIndex: "source", render: (text) => <span className="cell-source">{text}</span> },
|
||||||
|
]}
|
||||||
|
dataSource={CONDITION_ITEMS.map((item) => {
|
||||||
|
const detail = detailRecord.conditionDetails[item.key];
|
||||||
|
return detail ? { key: item.key, label: item.label, ...detail } : null;
|
||||||
|
}).filter(Boolean)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Connect([NS_QUAL_REVIEW], true)(AntdTableFuncControl(QualMonitor));
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue