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

380 lines
12 KiB
JavaScript
Raw Normal View History

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,
fetchRegisteredOrgPersonnelList,
fetchRegisteredOrgQualificationGroups,
2026-07-07 17:13:33 +08:00
} from "~/utils/regulatorOrgInfo";
2026-06-26 17:28:06 +08:00
import { buildOrgInfoFormOptions } from "../../../../EnterpriseInfo/OrgInfo/formOptions";
2026-07-09 09:23:22 +08:00
import StaffViewModal from "~/components/StaffViewModal";
2026-06-26 17:28:06 +08:00
const LIST_PATH = "/container/supervision/basicInfo/registeredOrg/list";
const ORG_INFO_FORM_OPTIONS = buildOrgInfoFormOptions();
2026-07-13 11:14:27 +08:00
const GENDER_MAP = { 1: "男", 2: "女" };
/** 后端字段名 → 前端表单字段名映射 */
const BACKEND_TO_FORM_FIELD = {
unitName: "orgName",
creditCode: "creditCode",
safetyIndustryCategoryName: "safetyIndustryCategory",
districtName: "regionCountyName",
townStreet: "regionStreetName",
villageCommunity: "regionCommunityName",
longitude: "longitude",
latitude: "latitude",
registerAddress: "registerAddress",
businessAddress: "businessAddress",
ownershipTypeName: "ownershipType",
economyIndustryCode: "gbIndustryCode",
legalRepresentative: "legalRepresentative",
legalRepresentativePhone: "legalRepPhone",
principalName: "principal",
principalPhone: "principalPhone",
safetyDeptManager: "safetyDeptHead",
safetyDeptManagerPhone: "safetyDeptPhone",
safetyDeputyPhone: "safetyVpPhone",
productionDate: "productionDate",
businessStatusName: "businessStatus",
infoDisclosureUrl: "disclosureUrl",
workplaceArea: "workplaceArea",
archiveRoomArea: "archiveRoomArea",
fulltimeEvaluatorCount: "fullTimeEvaluatorCount",
registeredEngineerCount: "registeredSafetyEngineerCount",
enterpriseStatusName: "enterpriseStatus",
enterpriseScaleName: "enterpriseScale",
filingTypeName: "filingType",
filingRecordStatusName: "filingRecordStatus",
attachments: "attachments",
};
function toFormFields(backendData) {
if (!backendData) return {};
const result = {};
Object.entries(BACKEND_TO_FORM_FIELD).forEach(([backendKey, formKey]) => {
if (backendData[backendKey] !== undefined) {
result[formKey] = backendData[backendKey];
}
});
return result;
}
2026-06-26 17:28:06 +08:00
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 />;
}
2026-07-13 11:14:27 +08:00
const groups = Object.entries(materialGroups || {}).map(([code, list], index) => {
const items = Array.isArray(list) ? list : [];
const scopeName = items[0]?.licenseTypeName || code || `资质${index + 1}`;
const expireDate = items.reduce((latest, item) => {
const d = item.validEndDate;
if (!d) return latest;
return !latest || d > latest ? d : latest;
}, "");
const expireWarning = expireDate
? (() => {
const end = new Date(String(expireDate).replace(/-/g, "/"));
if (Number.isNaN(end.getTime())) return false;
const diff = end.getTime() - Date.now();
return diff > 0 && diff < 90 * 24 * 3600 * 1000;
})()
: false;
return {
id: code || String(index),
scopeName,
expireDate: expireDate || "-",
expireWarning,
materials: items.map((item, idx) => {
const certImgs = item.certImageUrl
? (Array.isArray(item.certImageUrl)
? item.certImageUrl
: String(item.certImageUrl).startsWith("[")
? (() => { try { return JSON.parse(item.certImageUrl); } catch { return [{ url: item.certImageUrl }]; } })()
: [{ url: item.certImageUrl }])
: item.certImgFiles || [];
const previewUrl = certImgs[0]?.url || "";
return {
id: item.id || `${code}-${idx}`,
name: item.certName || item.licenseTypeName || "-",
required: true,
status: previewUrl ? "uploaded" : "pending",
previewUrl,
raw: item,
};
}),
};
});
2026-06-26 17:28:06 +08:00
return (
<div>
<div style={{ marginBottom: 12, color: "rgba(0,0,0,0.45)" }}>
该机构共申请
2026-07-13 11:14:27 +08:00
<strong>{groups.length}</strong>
2026-06-26 17:28:06 +08:00
个业务范围资质以下为各范围对应的申请材料清单
</div>
2026-07-13 11:14:27 +08:00
{groups.map((group) => (
2026-06-26 17:28:06 +08:00
<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 },
2026-07-13 11:14:27 +08:00
{ title: "姓名", dataIndex: "userName", width: 90 },
{ title: "性别", width: 60, render: (_, record) => GENDER_MAP[record.genderCode] || "-" },
{ title: "岗位", dataIndex: "postName", width: 110 },
2026-06-26 17:28:06 +08:00
{ title: "资质范围", dataIndex: "qualScope", width: 90 },
2026-07-13 11:14:27 +08:00
{ title: "学历", dataIndex: "educationName", width: 80 },
2026-06-26 17:28:06 +08:00
{
title: "注册安全工程师",
width: 120,
render: (_, record) => (
2026-07-13 11:14:27 +08:00
<Tag color={record.registerEngineerFlag === 1 ? "success" : "error"}>
{record.registerEngineerFlag === 1 ? "是" : "否"}
2026-06-26 17:28:06 +08:00
</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("");
2026-07-13 11:14:27 +08:00
const [materialGroups, setMaterialGroups] = useState({});
2026-06-26 17:28:06 +08:00
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);
2026-07-08 20:55:23 +08:00
2026-07-13 11:14:27 +08:00
return { data: {} };
2026-06-26 17:28:06 +08:00
}),
fetchRegisteredOrgPersonnelList(id).catch((err) => {
console.warn("[RegisteredOrgDetail] personnel load failed:", err);
2026-07-08 20:55:23 +08:00
2026-06-26 17:28:06 +08:00
return [];
}),
]);
if (cancelled) {
return;
}
if (orgRes?.data) {
2026-07-13 11:14:27 +08:00
form.setFieldsValue(toFormFields(orgRes.data));
setOrgName(orgRes.data.unitName || "");
2026-06-26 17:28:06 +08:00
}
2026-07-13 11:14:27 +08:00
setMaterialGroups(groups?.data || {});
2026-06-26 17:28:06 +08:00
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-07-08 20:55:23 +08:00
<PageLayout title="备案信息详情" history={props.history} previous>
2026-06-29 16:45:58 +08:00
<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}
2026-07-08 22:33:22 +08:00
2026-06-26 17:28:06 +08:00
onCancel={() => setViewPersonnelId("")}
/>
)}
2026-06-29 16:45:58 +08:00
</PageLayout>
2026-06-26 17:28:06 +08:00
);
}
export default RegisteredOrgDetailPage;