2026-06-26 17:28:06 +08:00
|
|
|
|
import { Button, Card, Form, Spin, Table, Tabs, Tag, message } from "antd";
|
|
|
|
|
|
import { useEffect, useMemo, useState } from "react";
|
|
|
|
|
|
import FormBuilder from "zy-react-library/components/FormBuilder";
|
2026-06-29 16:45:58 +08:00
|
|
|
|
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
2026-06-26 17:28:06 +08:00
|
|
|
|
import FilePreviewModal from "~/components/FilePreviewModal";
|
|
|
|
|
|
import {
|
|
|
|
|
|
fetchRegisteredOrgDetail,
|
|
|
|
|
|
fetchRegisteredOrgPersonnelCertDetail,
|
|
|
|
|
|
fetchRegisteredOrgPersonnelCertList,
|
|
|
|
|
|
fetchRegisteredOrgPersonnelDetail,
|
|
|
|
|
|
fetchRegisteredOrgPersonnelList,
|
|
|
|
|
|
fetchRegisteredOrgQualificationGroups,
|
|
|
|
|
|
} from "~/api/regulatorOrgInfo";
|
|
|
|
|
|
import { buildOrgInfoFormOptions } from "../../../../EnterpriseInfo/OrgInfo/formOptions";
|
|
|
|
|
|
import StaffViewModal from "../../../../EnterpriseInfo/PersonnelInfo/StaffViewModal";
|
|
|
|
|
|
|
|
|
|
|
|
const LIST_PATH = "/container/supervision/basicInfo/registeredOrg/list";
|
|
|
|
|
|
const ORG_INFO_FORM_OPTIONS = buildOrgInfoFormOptions();
|
|
|
|
|
|
|
|
|
|
|
|
function parseQueryId(location) {
|
|
|
|
|
|
const search = location?.search || window.location.search || "";
|
|
|
|
|
|
return new URLSearchParams(search).get("id");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-07 15:58:05 +08:00
|
|
|
|
function resolvePreviewUrl(raw) {
|
|
|
|
|
|
if (!raw) return "";
|
|
|
|
|
|
const u = String(raw);
|
|
|
|
|
|
if (/^https?:\/\//i.test(u)) return u;
|
|
|
|
|
|
const base = window.fileUrl || "";
|
|
|
|
|
|
return base ? `${base}${u}` : u;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isImageUrl(url) {
|
|
|
|
|
|
return /\.(jpe?g|png|gif|webp|bmp)(\?|#|$)/i.test(String(url || "").toLowerCase());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-26 17:28:06 +08:00
|
|
|
|
function MaterialsTab({ materialGroups, loading, onPreview }) {
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
|
return <Spin />;
|
|
|
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div style={{ marginBottom: 12, color: "rgba(0,0,0,0.45)" }}>
|
|
|
|
|
|
该机构共申请
|
|
|
|
|
|
<strong>{materialGroups.length}</strong>
|
|
|
|
|
|
个业务范围资质,以下为各范围对应的申请材料清单
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{materialGroups.map((group) => (
|
|
|
|
|
|
<Card
|
|
|
|
|
|
key={group.id}
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
style={{ marginBottom: 16 }}
|
|
|
|
|
|
title={`业务范围:${group.scopeName}`}
|
|
|
|
|
|
extra={(
|
|
|
|
|
|
<span style={{ fontSize: 12 }}>
|
|
|
|
|
|
证书有效期:
|
|
|
|
|
|
<span style={{ color: group.expireWarning ? "#faad14" : "#52c41a", fontWeight: 500 }}>
|
|
|
|
|
|
{group.expireDate}
|
|
|
|
|
|
{group.expireWarning ? "(即将到期)" : ""}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Table
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
bordered
|
|
|
|
|
|
rowKey="id"
|
|
|
|
|
|
pagination={false}
|
|
|
|
|
|
dataSource={group.materials}
|
|
|
|
|
|
columns={[
|
|
|
|
|
|
{ title: "材料名称", dataIndex: "name" },
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "操作",
|
|
|
|
|
|
width: 80,
|
|
|
|
|
|
render: (_, record) => (
|
|
|
|
|
|
<Button type="link" size="small" onClick={() => onPreview(record)}>预览</Button>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function PersonnelTab({ personnel, loading, onView }) {
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
|
return <Spin />;
|
|
|
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Table
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
bordered
|
|
|
|
|
|
rowKey="id"
|
|
|
|
|
|
pagination={false}
|
|
|
|
|
|
dataSource={personnel}
|
|
|
|
|
|
columns={[
|
|
|
|
|
|
{ title: "序号", width: 60, render: (_, __, index) => index + 1 },
|
|
|
|
|
|
{ title: "姓名", dataIndex: "name", width: 90 },
|
|
|
|
|
|
{ title: "性别", dataIndex: "gender", width: 60 },
|
|
|
|
|
|
{ title: "岗位", dataIndex: "position", width: 110 },
|
|
|
|
|
|
{ title: "资质范围", dataIndex: "qualScope", width: 90 },
|
|
|
|
|
|
{ title: "学历", dataIndex: "education", width: 80 },
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "注册安全工程师",
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
render: (_, record) => (
|
|
|
|
|
|
<Tag color={record.registerEngineer === 1 ? "success" : "error"}>
|
|
|
|
|
|
{record.registerEngineer === 1 ? "是" : "否"}
|
|
|
|
|
|
</Tag>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "操作",
|
|
|
|
|
|
width: 80,
|
|
|
|
|
|
render: (_, record) => (
|
|
|
|
|
|
<Button type="link" size="small" onClick={() => onView(record)}>查看</Button>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function RegisteredOrgDetailPage(props) {
|
|
|
|
|
|
const id = useMemo(() => parseQueryId(props.location), [props.location?.search]);
|
|
|
|
|
|
const [form] = Form.useForm();
|
|
|
|
|
|
const [orgLoading, setOrgLoading] = useState(Boolean(parseQueryId(props.location)));
|
|
|
|
|
|
const [extrasLoading, setExtrasLoading] = useState(Boolean(parseQueryId(props.location)));
|
|
|
|
|
|
const [orgName, setOrgName] = useState("");
|
|
|
|
|
|
const [materialGroups, setMaterialGroups] = useState([]);
|
|
|
|
|
|
const [personnel, setPersonnel] = useState([]);
|
|
|
|
|
|
const [filePreview, setFilePreview] = useState(null);
|
|
|
|
|
|
const [viewPersonnelId, setViewPersonnelId] = useState("");
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!id) {
|
|
|
|
|
|
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);
|
|
|
|
|
|
message.error(err?.message || "加载申请材料失败");
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}),
|
|
|
|
|
|
fetchRegisteredOrgPersonnelList(id).catch((err) => {
|
|
|
|
|
|
console.warn("[RegisteredOrgDetail] personnel load failed:", err);
|
|
|
|
|
|
message.error(err?.message || "加载人员信息失败");
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}),
|
|
|
|
|
|
]);
|
|
|
|
|
|
if (cancelled) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (orgRes?.data) {
|
|
|
|
|
|
form.setFieldsValue(orgRes.data);
|
|
|
|
|
|
setOrgName(orgRes.data.orgName || "");
|
|
|
|
|
|
}
|
|
|
|
|
|
setMaterialGroups(groups || []);
|
|
|
|
|
|
setPersonnel(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 (
|
2026-06-29 16:45:58 +08:00
|
|
|
|
<PageLayout title="备案信息详情" extra={<Button onClick={goBack}>返回</Button>}>
|
|
|
|
|
|
<p style={{ margin: 0, marginBottom: 24, color: "rgba(0, 0, 0, 0.45)" }}>
|
|
|
|
|
|
缺少机构 ID 参数
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</PageLayout>
|
2026-06-26 17:28:06 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const tabItems = [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: "info",
|
|
|
|
|
|
label: "备案信息",
|
|
|
|
|
|
children: (
|
|
|
|
|
|
<Spin spinning={orgLoading}>
|
|
|
|
|
|
<FormBuilder
|
|
|
|
|
|
form={form}
|
|
|
|
|
|
span={12}
|
|
|
|
|
|
disabled
|
|
|
|
|
|
useAutoGenerateRequired={false}
|
|
|
|
|
|
options={ORG_INFO_FORM_OPTIONS}
|
|
|
|
|
|
labelCol={{ span: 10 }}
|
|
|
|
|
|
showActionButtons={false}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Spin>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: "materials",
|
|
|
|
|
|
label: "申请清单材料",
|
|
|
|
|
|
children: (
|
|
|
|
|
|
<MaterialsTab
|
|
|
|
|
|
materialGroups={materialGroups}
|
|
|
|
|
|
loading={extrasLoading}
|
2026-07-07 15:58:05 +08:00
|
|
|
|
onPreview={(record) => {
|
|
|
|
|
|
const url = resolvePreviewUrl(record?.previewUrl);
|
|
|
|
|
|
if (!url) {
|
|
|
|
|
|
message.warning("该材料暂无可预览的附件");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (isImageUrl(url)) {
|
|
|
|
|
|
setFilePreview(record);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.open(url, "_blank");
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
2026-06-26 17:28:06 +08:00
|
|
|
|
/>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: "personnel",
|
|
|
|
|
|
label: "人员信息",
|
|
|
|
|
|
children: (
|
|
|
|
|
|
<PersonnelTab
|
|
|
|
|
|
personnel={personnel}
|
|
|
|
|
|
loading={extrasLoading}
|
|
|
|
|
|
onView={(record) => setViewPersonnelId(record.id)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-06-29 16:45:58 +08:00
|
|
|
|
<PageLayout title="备案信息详情" extra={<Button onClick={goBack}>← 返回</Button>}>
|
|
|
|
|
|
<p style={{ margin: 0, marginBottom: 24, color: "rgba(0, 0, 0, 0.45)" }}>
|
|
|
|
|
|
{orgName || undefined}
|
|
|
|
|
|
</p>
|
2026-06-26 17:28:06 +08:00
|
|
|
|
<Tabs items={tabItems} />
|
|
|
|
|
|
|
|
|
|
|
|
<FilePreviewModal
|
|
|
|
|
|
open={!!filePreview}
|
|
|
|
|
|
title="文件预览"
|
|
|
|
|
|
fileName={filePreview?.name}
|
2026-07-07 15:58:05 +08:00
|
|
|
|
url={resolvePreviewUrl(filePreview?.previewUrl)}
|
2026-06-26 17:28:06 +08:00
|
|
|
|
onCancel={() => setFilePreview(null)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
{viewPersonnelId && (
|
|
|
|
|
|
<StaffViewModal
|
|
|
|
|
|
open={!!viewPersonnelId}
|
|
|
|
|
|
currentId={viewPersonnelId}
|
|
|
|
|
|
requestDetails={fetchRegisteredOrgPersonnelDetail}
|
|
|
|
|
|
requestCertList={fetchRegisteredOrgPersonnelCertList}
|
|
|
|
|
|
requestCertDetails={fetchRegisteredOrgPersonnelCertDetail}
|
|
|
|
|
|
onCancel={() => setViewPersonnelId("")}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2026-06-29 16:45:58 +08:00
|
|
|
|
</PageLayout>
|
2026-06-26 17:28:06 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default RegisteredOrgDetailPage;
|