safety-eval-service-frontend/src/pages/Container/Supervision/BasicInfo/RegisteredOrg/Detail/index.js

606 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import {
Button,
Input,
Spin,
Table,
Tabs,
Tag,
message,
} from "antd";
import { useEffect, useMemo, useState } from "react";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import {
fetchRegisteredOrgDetail,
fetchRegisteredOrgPersonnelList,
fetchRegisteredOrgQualificationGroups,
} from "~/utils/regulatorOrgInfo";
import { QUALIFICATION_INDUSTRY_OPTIONS_MAP } from "~/enumerate/enterpriseOptions";
import FilePreviewModal from "~/components/FilePreviewModal";
import StaffViewModal from "~/components/StaffViewModal";
const LIST_PATH = "/container/supervision/basicInfo/registeredOrg/list";
const { TextArea } = Input;
/** Mock 开关存储键(与机构备案管理列表共享,默认开启便于与原型对比) */
const ORG_MOCK_STORAGE_KEY = "registeredOrgMockEnabled";
function getOrgMockEnabled() {
try {
const stored = localStorage.getItem(ORG_MOCK_STORAGE_KEY);
if (stored === "1") return true;
if (stored === "0") return false;
} catch {
// ignore
}
return true;
}
/** 机构基础信息详情 mock原型 mod-filing-detail 七项 Tab 数据) */
const ORG_DETAIL_MOCK = {
unitName: "重庆安评技术研究院有限公司",
creditCode: "915001072028699512",
isLocalUnit: 1,
businessScope: ["COAL_MINING", "METAL_NONMETAL_MINING", "PETROCHEMICAL_CHEMICAL_PHARMACEUTICAL", "METAL_SMELTING"],
filingTerritoryName: "重庆市沙坪坝区",
filingUnitTypeName: "本地单位",
registerAddress: "重庆市沙坪坝区大学城东路20号",
businessAddress: "重庆市沙坪坝区大学城东路20号重庆科技学院第39栋西南角实习用房三楼",
infoDisclosureUrl: "https://cqap.example.com",
qualCertNo: "API-2026-001",
legalRepresentative: "郑远平",
legalRepresentativePhone: "023-68705577",
fax: "023-68705578",
contactPhone: "陈芳 / 139****9012",
fixedAssetAmount: 1200,
workplaceArea: 1200,
archiveRoomArea: 80,
fulltimeEvaluatorCount: 25,
registeredEngineerCount: 12,
unitIntro: "重庆安评技术研究院成立于2015年是一家专业从事安全评价、安全咨询、安全技术开发与转让的综合性安全技术服务机构。",
attachmentUrl: [{ name: "营业执照副本.pdf", url: "https://example.com/license.pdf" }],
personnel: [
{ id: 1001, userName: "郑远平", positionName: "法定代表人", titleName: "高级工程师", educationLevelName: "本科", majorName: "安全工程", workYears: "18年" },
{ id: 1002, userName: "张建国", positionName: "技术负责人", titleName: "正高级工程师", educationLevelName: "本科", majorName: "化工工艺", workYears: "16年" },
{ id: 1003, userName: "王丽萍", positionName: "过程控制负责人", titleName: "高级工程师", educationLevelName: "本科", majorName: "安全管理", workYears: "12年" },
{ id: 2001, userName: "李明华", positionName: "专职安全评价师", titleName: "高级工程师", educationLevelName: "硕士", majorName: "采矿工程", workYears: "12年", capabilityName: "非煤矿山安全评价", scopeName: "金属、非金属矿及其他矿采选业" },
{ id: 2002, userName: "陈志强", positionName: "专职安全评价师", titleName: "高级工程师", educationLevelName: "本科", majorName: "化工工艺", workYears: "10年", capabilityName: "化工工艺安全风险评估", scopeName: "石油加工业,化学原料、化学品及医药制造业" },
{ id: 2003, userName: "刘海", positionName: "专职安全评价师", titleName: "工程师", educationLevelName: "本科", majorName: "机械工程", workYears: "8年", capabilityName: "金属冶炼安全评价", scopeName: "金属冶炼" },
],
materials: [
{ id: 1, name: "申请材料目录.pdf", format: "pdf", url: "https://example.com/m1.pdf" },
{ id: 2, name: "安全评价机构资质申请书及材料清单.pdf", format: "pdf", url: "https://example.com/m2.pdf" },
{ id: 3, name: "法人证明.pdf", format: "pdf", url: "https://example.com/m3.pdf" },
{ id: 4, name: "截至申请之日三年内无重大违法失信记录的查询证明.pdf", format: "pdf", url: "https://example.com/m4.pdf" },
{ id: 5, name: "申请单位法定代表人承诺书.pdf", format: "pdf", url: "https://example.com/m5.pdf" },
{ id: 6, name: "固定资产法定证明材料.pdf", format: "pdf", url: "https://example.com/m6.pdf" },
{ id: 7, name: "工作场所及档案室面积证明材料.pdf", format: "pdf", url: "https://example.com/m7.pdf" },
{ id: 8, name: "安全评价师专业能力证明.pdf", format: "pdf", url: "https://example.com/m8.pdf" },
{ id: 9, name: "专职技术负责人和支撑过程控制负责人证明材料.pdf", format: "pdf", url: "https://example.com/m9.pdf" },
{ id: 10, name: "机构内部管理制度(非受控版).pdf", format: "pdf", url: "https://example.com/m10.pdf" },
],
equipment: [
{ id: 301, deviceName: "气相色谱仪", deviceModel: "GC-2018", manufacturer: "岛津", calibration: { label: "已检定", status: "success" } },
{ id: 302, deviceName: "噪声计", deviceModel: "HS-6288", manufacturer: "杭州爱华", calibration: { label: "已检定", status: "success" } },
{ id: 303, deviceName: "粉尘采样器", deviceModel: "FC-5A", manufacturer: "北京劳保所", calibration: { label: "待检定", status: "warning" } },
],
commitment: {
legalRepName: "郑远平",
signDate: "2026-06-25 16:42",
signWay: "APP认证签字",
},
changes: [
{ id: 1, submitTime: "2026-07-18 10:24", submitType: "注册提交", changeFields: "基础信息全量提交", submitter: "机构管理员", statusName: "待审核", statusColor: "warning", url: "https://example.com/c1.pdf" },
{ id: 2, submitTime: "2026-07-20 15:36", submitType: "补正提交", changeFields: "营业执照、办公地址证明", submitter: "机构管理员", statusName: "已通过", statusColor: "success", url: "https://example.com/c2.pdf" },
],
};
const gridStyle = {
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "0.6rem 1rem",
};
const fieldStyle = { display: "flex", flexDirection: "column", gap: "0.3rem" };
const labelStyle = { fontSize: "0.8rem", color: "#64748b", fontWeight: 500 };
const inputStyle = { background: "#f8fafc" };
const fullSpan = { gridColumn: "1 / -1" };
function ReadonlyField({ label, value, span }) {
return (
<div style={span ? { ...fieldStyle, ...span } : fieldStyle}>
<label style={labelStyle}>{label}</label>
<Input readOnly value={value || "-"} style={inputStyle} />
</div>
);
}
function parseQueryId(location) {
const search = location?.search || window.location.search || "";
return new URLSearchParams(search).get("id");
}
function RegisteredOrgDetailPage(props) {
const id = useMemo(
() => parseQueryId(props.location),
[props.location?.search],
);
const [orgLoading, setOrgLoading] = useState(Boolean(id));
const [extrasLoading, setExtrasLoading] = useState(Boolean(id));
const [orgName, setOrgName] = useState("");
const [info, setInfo] = useState({});
const [personnel, setPersonnel] = useState([]);
const [materials, setMaterials] = useState([]);
const [previewFile, setPreviewFile] = useState(null);
const [viewPersonnelId, setViewPersonnelId] = useState("");
const mockEnabled = getOrgMockEnabled();
useEffect(() => {
if (!id) {
setOrgLoading(false);
setExtrasLoading(false);
return;
}
if (mockEnabled) {
// Mock 演示:本地注入原型完整七项 Tab 数据
setInfo(ORG_DETAIL_MOCK);
setPersonnel(ORG_DETAIL_MOCK.personnel);
setMaterials(ORG_DETAIL_MOCK.materials);
setOrgName(ORG_DETAIL_MOCK.unitName);
setOrgLoading(false);
setExtrasLoading(false);
return;
}
let cancelled = false;
setOrgLoading(true);
setExtrasLoading(true);
(async () => {
try {
const [orgRes, groups, staffList] = await Promise.all([
fetchRegisteredOrgDetail(id),
fetchRegisteredOrgQualificationGroups(id).catch((err) => {
console.warn("[RegisteredOrgDetail] qualification load failed:", err);
return { data: {} };
}),
fetchRegisteredOrgPersonnelList(id).catch((err) => {
console.warn("[RegisteredOrgDetail] personnel load failed:", err);
return [];
}),
]);
if (cancelled) return;
const detail = orgRes?.data || {};
setInfo(detail);
setOrgName(detail.unitName || "");
// 申请材料:资质材料组 → 扁平列表(名称/格式/附件地址)
const flatMaterials = [];
Object.values(groups?.data || {}).forEach((list) => {
(Array.isArray(list) ? list : []).forEach((item, idx) => {
const url = item.certImageUrl || item.attachmentUrl || "";
flatMaterials.push({
id: item.id || `${item.licenseTypeName}-${idx}`,
name: item.certName || item.licenseTypeName || "-",
format: /\.(png|jpe?g|gif|webp|bmp)/i.test(String(url)) ? "图片" : "pdf",
url,
});
});
});
setMaterials(flatMaterials);
setPersonnel(Array.isArray(staffList) ? staffList : []);
} catch (err) {
if (!cancelled) {
console.warn("[RegisteredOrgDetail] load failed:", err);
message.error("加载机构详情失败");
}
} finally {
if (!cancelled) {
setOrgLoading(false);
setExtrasLoading(false);
}
}
})();
return () => {
cancelled = true;
};
}, [id]);
const goBack = () => {
if (props.history?.goBack && props.history.length > 1) {
props.history.goBack();
return;
}
if (props.history?.push) {
props.history.push(LIST_PATH);
return;
}
window.location.href = `/certificate${LIST_PATH}`;
};
if (!id) {
return (
<PageLayout
title="机构基础信息详情"
extra={<Button onClick={goBack}>返回</Button>}
>
<p style={{ margin: 0, marginBottom: 24, color: "rgba(0, 0, 0, 0.45)" }}>
缺少机构 ID 参数
</p>
</PageLayout>
);
}
/** 管理人员:负责人岗位优先,无匹配回退全部人员 */
const managerRows = useMemo(() => {
const filtered = personnel.filter((item) =>
/法定代表人|技术负责人|过程控制负责人|负责人/.test(
item.positionName || item.postName || item.position || "",
),
);
return filtered.length ? filtered : personnel;
}, [personnel]);
/** 专职评价师:评价师证书/岗位匹配,无匹配回退全部人员 */
const evaluatorRows = useMemo(() => {
const filtered = personnel.filter((item) =>
item.evaluatorCertNo || /评价师/.test(item.positionName || item.postName || item.position || ""),
);
return filtered.length ? filtered : personnel;
}, [personnel]);
const businessScope = useMemo(() => {
const list = Array.isArray(info.businessScope) ? info.businessScope : [];
return [
{ label: "煤炭开采业", value: "COAL_MINING" },
{ label: "金属、非金属矿及其他矿采选业", value: "METAL_NONMETAL_MINING" },
{ label: "陆地石油和天然气开采业", value: "ONSHORE_OIL_GAS" },
{ label: "陆上油气管道运输业", value: "ONSHORE_OIL_GAS_PIPELINE" },
{ label: "石油加工业,化学原料、化学品及医药制造业", value: "PETROCHEMICAL_CHEMICAL_PHARMACEUTICAL" },
{ label: "烟花爆竹制造业", value: "FIREWORKS_FIRECRACKERS" },
{ label: "金属冶炼", value: "METAL_SMELTING" },
].map((opt) => ({ ...opt, checked: list.includes(opt.value) }));
}, [info.businessScope]);
const equipmentList = info.equipment || [];
const commitment = info.commitment || {};
const changes = info.changes || [];
const tabItems = [
{
key: "info",
label: "基础信息",
children: (
<Spin spinning={orgLoading}>
<div style={gridStyle}>
<div style={{ ...fieldStyle, ...fullSpan }}>
<label style={labelStyle}>拟申请的法定安全评价业务范围</label>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(2, minmax(260px, 1fr))",
gap: "0.3rem 0.75rem",
padding: "0.75rem",
border: "1px solid #e2e8f0",
borderRadius: 8,
background: "#f8fafc",
}}
>
{businessScope.map((opt) => (
<label key={opt.value} style={{ fontWeight: 400, fontSize: "0.82rem" }}>
<input type="checkbox" checked={opt.checked} disabled /> {opt.label}
</label>
))}
</div>
</div>
<ReadonlyField label="备案属地" value={info.filingTerritoryName || info.districtName} />
<ReadonlyField label="备案单位" value={info.unitName} />
<ReadonlyField label="备案单位类型" value={info.filingUnitTypeName || (info.isLocalUnit === 1 ? "本地单位" : "异地单位")} />
<ReadonlyField label="统一社会信用代码" value={info.creditCode} />
<ReadonlyField label="注册地址" value={info.registerAddress} span={fullSpan} />
<ReadonlyField label="办公地址" value={info.businessAddress} span={fullSpan} />
<ReadonlyField label="信息公开网址" value={info.infoDisclosureUrl} />
<ReadonlyField label="资质证书编号" value={info.qualCertNo} />
<ReadonlyField label="法定代表人" value={info.legalRepresentative} />
<ReadonlyField label="法定代表人电话" value={info.legalRepresentativePhone} />
<ReadonlyField label="传真" value={info.fax} />
<ReadonlyField label="联系人及电话" value={info.contactPhone || info.principalPhone} />
<ReadonlyField label="固定资产总值(万元)" value={info.fixedAssetAmount} />
<ReadonlyField label="工作场所建筑面积(㎡)" value={info.workplaceArea} />
<ReadonlyField label="档案室面积(㎡)" value={info.archiveRoomArea} />
<ReadonlyField label="专职安全评价师数量" value={info.fulltimeEvaluatorCount} />
<ReadonlyField label="注册安全工程师数量" value={info.registeredEngineerCount} />
<div style={{ ...fieldStyle, ...fullSpan }}>
<label style={labelStyle}>单位基本情况介绍可附页</label>
<TextArea readOnly rows={4} value={info.unitIntro || ""} style={inputStyle} />
</div>
<div style={{ ...fieldStyle, ...fullSpan }}>
<label style={labelStyle}>营业执照</label>
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
{(info.attachmentUrl || []).length ? (
<>
<Button
size="small"
onClick={() => {
const first = (info.attachmentUrl || [])[0];
setPreviewFile({
name: first.name || "营业执照",
url: first.url || (typeof first === "string" ? first : ""),
});
}}
>
查看营业执照
</Button>
<Tag color="success">{(info.attachmentUrl || [])[0]?.name || "营业执照.pdf"}</Tag>
</>
) : (
<span style={{ color: "#64748b", fontSize: "0.8rem" }}></span>
)}
</div>
</div>
</div>
</Spin>
),
},
{
key: "management",
label: "管理人员",
children: (
<Table
size="small"
bordered
rowKey="id"
pagination={false}
loading={extrasLoading}
dataSource={managerRows}
scroll={{ x: 900 }}
columns={[
{ title: "序号", width: 60, render: (_, __, i) => i + 1 },
{ title: "姓名", dataIndex: "userName", width: 110 },
{ title: "岗位", dataIndex: "positionName", ellipsis: true },
{ title: "职称", dataIndex: "titleName", width: 120, render: (v) => v || "-" },
{
title: "学历/专业",
width: 160,
render: (_, r) =>
[r.educationLevelName, r.majorName || r.educationTypeName].filter(Boolean).join(" / ") || "-",
},
{
title: "从业年限",
width: 100,
render: (_, r) => r.workYears || r.workYear || "-",
},
{
title: "操作",
width: 80,
fixed: "right",
render: (_, record) => (
<Button
type="link"
size="small"
onClick={() => setViewPersonnelId(record.id || record.sourcePersonnelId)}
>
查看
</Button>
),
},
]}
/>
),
},
{
key: "evaluators",
label: "专职评价师",
children: (
<Table
size="small"
bordered
rowKey="id"
pagination={false}
loading={extrasLoading}
dataSource={evaluatorRows}
scroll={{ x: 1100 }}
columns={[
{ title: "序号", width: 60, render: (_, __, i) => i + 1 },
{ title: "人员姓名", dataIndex: "userName", width: 110 },
{ title: "岗位类别", dataIndex: "positionName", width: 140, ellipsis: true },
{
title: "学历/专业",
width: 160,
render: (_, r) =>
[r.educationLevelName, r.majorName || r.educationTypeName].filter(Boolean).join(" / ") || "-",
},
{ title: "职称", dataIndex: "titleName", width: 120, render: (v) => v || "-" },
{
title: "专业能力",
width: 180,
render: (_, r) => r.capabilityName || r.capability || QUALIFICATION_INDUSTRY_OPTIONS_MAP[r.qualScope] || r.qualScope || "-",
},
{
title: "申请业务范围",
width: 200,
ellipsis: true,
render: (_, r) => r.scopeName || r.appliedScopeName || QUALIFICATION_INDUSTRY_OPTIONS_MAP[r.qualScope] || r.qualScope || "-",
},
{
title: "操作",
width: 80,
fixed: "right",
render: (_, record) => (
<Button
type="link"
size="small"
onClick={() => setViewPersonnelId(record.id || record.sourcePersonnelId)}
>
查看
</Button>
),
},
]}
/>
),
},
{
key: "materials",
label: "申请材料",
children: (
<Table
size="small"
bordered
rowKey="id"
pagination={false}
loading={extrasLoading}
dataSource={materials}
columns={[
{ title: "序号", width: 60, render: (_, __, i) => i + 1 },
{ title: "申请材料", dataIndex: "name", ellipsis: true },
{ title: "格式", dataIndex: "format", width: 80 },
{
title: "操作",
width: 80,
render: (_, record) => (
<Button type="link" size="small" onClick={() => setPreviewFile(record)}>
预览
</Button>
),
},
]}
/>
),
},
{
key: "equipment",
label: "设备清单",
children: (
<Table
size="small"
bordered
rowKey="id"
pagination={false}
loading={extrasLoading}
dataSource={equipmentList}
columns={[
{ title: "序号", width: 60, render: (_, __, i) => i + 1 },
{ title: "设备名称", dataIndex: "deviceName", ellipsis: true },
{ title: "规格型号", dataIndex: "deviceModel", ellipsis: true },
{ title: "生产厂家", dataIndex: "manufacturer" },
{
title: "计量检定情况",
width: 120,
render: (_, record) => {
const cal = record.calibration || {};
return cal.label ? <Tag color={cal.status === "success" ? "success" : "warning"}>{cal.label}</Tag> : "-";
},
},
]}
/>
),
},
{
key: "signature",
label: "机构负责人签字",
children: (
<div
style={{
border: "1px solid #e2e8f0",
borderRadius: 8,
padding: "1.25rem 1.5rem",
background: "#fafafa",
fontSize: "0.85rem",
lineHeight: 1.9,
}}
>
<p style={{ textAlign: "center", fontWeight: 600, fontSize: "0.95rem", marginBottom: "0.75rem" }}>
机构负责人签字确认
</p>
<p>机构端已完成资质备案提交确认承诺本次提交的基础信息管理人员专职安全评价师申请材料和设备清单真实准确完整</p>
<div style={gridStyle}>
<ReadonlyField label="签字人" value={commitment.legalRepName} />
<ReadonlyField label="职务" value="法定代表人 / 机构负责人" />
<ReadonlyField label="签字方式" value={commitment.signWay || "APP认证签字"} />
<ReadonlyField label="签字时间" value={commitment.signDate} />
<div style={{ ...fieldStyle, ...fullSpan }}>
<label style={labelStyle}>签字文件</label>
<div>
{commitment.legalRepSignatureUrl ? (
<Button size="small" onClick={() => setPreviewFile({ name: "机构负责人签字件", url: commitment.legalRepSignatureUrl })}>
查看签字件
</Button>
) : (
<span style={{ color: "#64748b", fontSize: "0.8rem" }}></span>
)}
</div>
</div>
</div>
</div>
),
},
{
key: "changes",
label: "变更记录",
children: (
<Table
size="small"
bordered
rowKey="id"
pagination={false}
loading={extrasLoading}
dataSource={changes}
columns={[
{ title: "提交时间", dataIndex: "submitTime", width: 150 },
{ title: "提交类型", dataIndex: "submitType", width: 100 },
{ title: "变更字段", dataIndex: "changeFields", ellipsis: true },
{ title: "提交人", dataIndex: "submitter", width: 100 },
{
title: "审核状态",
width: 100,
render: (_, record) => <Tag color={record.statusColor || "default"}>{record.statusName || "-"}</Tag>,
},
{
title: "操作",
width: 110,
render: (_, record) => (
<Button type="link" size="small" onClick={() => setPreviewFile({ name: "提交件", url: record.url })}>
查看提交件
</Button>
),
},
]}
/>
),
},
];
return (
<PageLayout
title={
<span>
机构基础信息详情{mockEnabled ? <Tag color="warning" style={{ marginLeft: 8 }}>Mock 演示中</Tag> : null}
</span>
}
history={props.history}
previous
>
<p style={{ margin: 0, marginBottom: 24, color: "rgba(0, 0, 0, 0.45)" }}>
{orgName || undefined}
</p>
<Tabs items={tabItems} />
{viewPersonnelId && (
<StaffViewModal
open={!!viewPersonnelId}
currentId={viewPersonnelId}
onCancel={() => setViewPersonnelId("")}
/>
)}
<FilePreviewModal
open={!!previewFile}
title="文件预览"
fileName={previewFile?.name}
url={previewFile?.url}
onCancel={() => setPreviewFile(null)}
/>
</PageLayout>
);
}
export default RegisteredOrgDetailPage;