diff --git a/jjb.config.js b/jjb.config.js index d20c44b..f2b5901 100644 --- a/jjb.config.js +++ b/jjb.config.js @@ -10,8 +10,8 @@ module.exports = { javaGitBranch: "dev", // 本地联调 safetyEval-service(context-path: /safetyEval,默认端口 8095) // 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095 - //API_HOST: "https://gbs-gateway.qhdsafety.com", - API_HOST: "http://192.168.0.150", + API_HOST: "https://gbs-gateway.qhdsafety.com", + //API_HOST: "http://192.168.0.150", }, production: { // 应用后端分支名称,部署上线需要 diff --git a/src/components/StaffViewModal/index.js b/src/components/StaffViewModal/index.js index 30c9fe0..ce7231e 100644 --- a/src/components/StaffViewModal/index.js +++ b/src/components/StaffViewModal/index.js @@ -9,7 +9,7 @@ import { fetchStaffCertListByPersonnelId, } from "~/api/staffCertificate"; import { fetchOrgPersonnelDetail } from "~/utils/qualFiling/personnelHelper"; -import { TITLE_LEVEL_MAP } from "~/enumerate/enterpriseOptions"; +import { QUALIFICATION_INDUSTRY_OPTIONS_MAP, TITLE_LEVEL_MAP } from "~/enumerate/enterpriseOptions"; const GENDER_MAP = { 1: "男", 2: "女" }; const REGISTER_ENGINEER_MAP = { 1: "是", 2: "否" }; @@ -138,7 +138,7 @@ export default function StaffViewModal({ {info.personTypeName || "基础人员"} - {info.qualScope || "-"} + {QUALIFICATION_INDUSTRY_OPTIONS_MAP[info.qualScope] || info.qualScope || "-"} {info.professionalLevelName || "-"} @@ -307,13 +307,18 @@ export default function StaffViewModal({ {certPreviewInfo.certAttachmentUrl && - certPreviewInfo.certAttachmentUrl.split(",").map((item) => ( -
- window.open(item)}> - {item} - -
- ))} + certPreviewInfo.certAttachmentUrl.split(",").map((item) => { + const isImage = /\.(png|jpe?g|gif|bmp|webp|svg)(\?|#|$)/i.test(item); + return ( +
+ {isImage ? ( + + ) : ( + window.open(item)}>{item} + )} +
+ ); + })}
diff --git a/src/components/UploadButton/index.js b/src/components/UploadButton/index.js index bb3c375..302ec02 100644 --- a/src/components/UploadButton/index.js +++ b/src/components/UploadButton/index.js @@ -1,4 +1,4 @@ -import { Button, Upload } from "antd"; +import { Button, Upload, message } from "antd"; import { useState } from "react"; /** @@ -19,6 +19,21 @@ export default function UploadButton({ }) { const [uploading, setUploading] = useState(false); + const allowedExts = uploadProps?.accept + ? uploadProps.accept.split(",").map((e) => e.trim().toLowerCase()) + : []; + + const beforeUpload = (file) => { + if (allowedExts.length > 0) { + const ext = "." + file.name.split(".").pop().toLowerCase(); + if (!allowedExts.includes(ext)) { + message.error(`不支持 ${ext} 格式文件,仅支持 ${uploadProps.accept}`); + return Upload.LIST_IGNORE; + } + } + return true; + }; + return ( { if (info.file.status === "uploading") { setUploading(true); diff --git a/src/pages/Container/QualApplication/FilingForm/components/EquipmentStep.jsx b/src/pages/Container/QualApplication/FilingForm/components/EquipmentStep.jsx index 6dfbede..c872dca 100644 --- a/src/pages/Container/QualApplication/FilingForm/components/EquipmentStep.jsx +++ b/src/pages/Container/QualApplication/FilingForm/components/EquipmentStep.jsx @@ -13,27 +13,67 @@ export default function EquipmentStep({ }) { const [selectOpen, setSelectOpen] = useState(false); const [previewImage, setPreviewImage] = useState(""); - const existingIds = equipmentList.map((item) => String(item.sourceEquipmentId || "")); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const existingIds = equipmentList.map((item) => + String(item.sourceEquipmentId || ""), + ); - const isImage = (url) => /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || ""); + const isImage = (url) => + /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || ""); + + const handleBatchRemove = () => { + const records = selectedRowKeys + .map((key) => + equipmentList.find( + (item) => String(item.sourceEquipmentId || item.id) === key, + ), + ) + .filter(Boolean); + if (records.length > 0) { + onRemove?.(records); + } + }; return ( <> -
+
申请单位装备清单 {!disabled && ( - + + + {selectedRowKeys.length > 0 && ( + + )} + )}
String(record.sourceEquipmentId || record.id)} pagination={false} dataSource={equipmentList || []} + rowSelection={ + !disabled + ? { + selectedRowKeys, + onChange: setSelectedRowKeys, + } + : undefined + } columns={[ { title: "序号", width: 60, render: (_, __, index) => index + 1 }, { title: "装备名称", dataIndex: "deviceName", ellipsis: true }, - { title: "规格型号", dataIndex: "deviceModel",ellipsis: true }, + { title: "规格型号", dataIndex: "deviceModel", ellipsis: true }, { title: "生产厂家", dataIndex: "manufacturer" }, { title: "计量检定情况", @@ -57,6 +97,10 @@ export default function EquipmentStep({ ) : ( !disabled && ( onUploadCalibration?.(record, data)} buttonProps={{ children: "上传报告" }} /> @@ -68,11 +112,17 @@ export default function EquipmentStep({ { title: "操作", width: 100, - render: (_, record) => ( + render: (_, record) => !disabled && ( - - ) - ), + + ), }, ]} /> @@ -85,16 +135,18 @@ export default function EquipmentStep({ setSelectOpen(false); }} /> - {previewImage&& { - if (!visible) setPreviewImage(""); - }, - }} - />} + {previewImage && ( + { + if (!visible) setPreviewImage(""); + }, + }} + /> + )} ); } diff --git a/src/pages/Container/QualApplication/FilingForm/components/MaterialStep.jsx b/src/pages/Container/QualApplication/FilingForm/components/MaterialStep.jsx index 728ad86..8e2befc 100644 --- a/src/pages/Container/QualApplication/FilingForm/components/MaterialStep.jsx +++ b/src/pages/Container/QualApplication/FilingForm/components/MaterialStep.jsx @@ -62,6 +62,7 @@ export default function MaterialStep({ {!disabled && ( onUpload?.(record, data)} + uploadProps={{ accept: ".pdf,.doc,.docx,.xls,.xlsx,.png,.jpg,.jpeg,.gif,.bmp,.webp" }} > {record.attachmentUrl ? "重新上传" : "上传"} diff --git a/src/pages/Container/QualApplication/FilingForm/components/PersonnelStep.jsx b/src/pages/Container/QualApplication/FilingForm/components/PersonnelStep.jsx index 72c3ca7..8060a68 100644 --- a/src/pages/Container/QualApplication/FilingForm/components/PersonnelStep.jsx +++ b/src/pages/Container/QualApplication/FilingForm/components/PersonnelStep.jsx @@ -12,9 +12,22 @@ const PersonnelStep=({ })=> { const [selectOpen, setSelectOpen] = useState(false); const [viewId, setViewId] = useState(""); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); const existingIds = personnelList.map((item) => String(item.sourcePersonnelId || item.id || "")); + const handleBatchRemove = () => { + const records = selectedRowKeys + .map((key) => personnelList.find( + (item) => String(item.sourcePersonnelId || item.id) === key, + )) + .filter(Boolean); + if (records.length > 0) { + onRemove?.(records); + } + + }; + return ( <>
@@ -25,15 +38,24 @@ const PersonnelStep=({ {!disabled && ( - + + + {selectedRowKeys.length > 0 && ( + + )} + )}
String(record.sourcePersonnelId || record.id)} pagination={false} scroll={{ y: 400 }} dataSource={personnelList} + rowSelection={!disabled ? { + selectedRowKeys, + onChange: setSelectedRowKeys, + } : undefined} columns={[ { title: "序号", width: 60, render: (_, __, index) => index + 1 }, { title: "人员姓名", dataIndex: "userName", render: (_, record) => record.userName || record.personName }, diff --git a/src/pages/Container/QualApplication/FilingForm/index.js b/src/pages/Container/QualApplication/FilingForm/index.js index e8ff942..ef70c42 100644 --- a/src/pages/Container/QualApplication/FilingForm/index.js +++ b/src/pages/Container/QualApplication/FilingForm/index.js @@ -264,15 +264,18 @@ function FilingFormPage(props) { message.success(`已添加至列表,${saveActionHint}`); }; - const handlePersonnelRemove = (record) => { + const handlePersonnelRemove = (records) => { + const list = Array.isArray(records) ? records : [records]; + const names = list.map((r) => r.userName || r.personName).join("、"); Modal.confirm({ title: "提示", - content: `确认删除人员「${record.userName || record.personName}」?`, + content: `确认删除人员「${names}」?`, onOk: () => { + const ids = new Set(list.map((r) => r.id)); setDetail((prev) => ({ ...prev, personnelList: (prev.personnelList || []).filter( - (item) => item.id !== record.id, + (item) => !ids.has(item.id), ), })); }, @@ -305,15 +308,18 @@ function FilingFormPage(props) { message.success(`已添加至列表,${saveActionHint}`); }; - const handleEquipmentRemove = (record) => { + const handleEquipmentRemove = (records) => { + const list = Array.isArray(records) ? records : [records]; + const names = list.map((r) => r.deviceName).join("、"); Modal.confirm({ title: "提示", - content: `确认移除装备「${record.deviceName}」?`, + content: `确认移除装备「${names}」?`, onOk: () => { + const ids = new Set(list.map((r) => r.id)); setDetail((prev) => ({ ...prev, equipmentList: (prev.equipmentList || []).filter( - (item) => item.id !== record.id, + (item) => !ids.has(item.id), ), })); }, diff --git a/src/pages/Container/QualApplication/filingVerify.js b/src/pages/Container/QualApplication/filingVerify.js index 3087d63..4c22ba5 100644 --- a/src/pages/Container/QualApplication/filingVerify.js +++ b/src/pages/Container/QualApplication/filingVerify.js @@ -72,14 +72,24 @@ export function verifyFilingPrerequisites(detail = {}) { return !isNaN(joinDate.getTime()) && joinDate <= twoYearsAgo; }).length; - + const noExperienceNames = personnel + .filter((p) => { + const joinDate = new Date(p.joinWorkDate); + return isNaN(joinDate.getTime()) || joinDate > twoYearsAgo; + }) + .map((p) => p.userName || p.personName || ""); + + const personnelExpPassed = total >= 25 && ratio(withExperience, total) >= 1; items.push({ key: "personnelExp", label: "人员资历", - desc: "专职技术人员在本行业领域工作二年以上", + desc: `专职技术人员在本行业领域工作二年以上${ + !SKIP_PERSONNEL_VERIFY && !personnelExpPassed && noExperienceNames.length > 0 + ? `(不满足:${noExperienceNames.join("、")})` + : "" + }`, passed: - SKIP_PERSONNEL_VERIFY || - (total >= 25 && ratio(withExperience, total) >= 1), + SKIP_PERSONNEL_VERIFY || personnelExpPassed, }); const keyOk = KEY_POSITIONS.every((pos) => diff --git a/src/pages/Container/Supervision/BasicInfo/RegisteredOrg/Detail/index.js b/src/pages/Container/Supervision/BasicInfo/RegisteredOrg/Detail/index.js index 2638fb2..510811f 100644 --- a/src/pages/Container/Supervision/BasicInfo/RegisteredOrg/Detail/index.js +++ b/src/pages/Container/Supervision/BasicInfo/RegisteredOrg/Detail/index.js @@ -1,6 +1,22 @@ -import { Button, Card, Form, Spin, Table, Tabs, Tag, message } from "antd"; +import { + Button, + Card, + Col, + DatePicker, + Form, + Input, + InputNumber, + Row, + Select, + Spin, + Table, + Tabs, + Tag, + Upload, + message, +} from "antd"; +import dayjs from "dayjs"; import { useEffect, useMemo, useState } from "react"; -import FormBuilder from "zy-react-library/components/FormBuilder"; import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout"; import FilePreviewModal from "~/components/FilePreviewModal"; import { @@ -8,11 +24,18 @@ import { fetchRegisteredOrgPersonnelList, fetchRegisteredOrgQualificationGroups, } from "~/utils/regulatorOrgInfo"; -import { buildOrgInfoFormOptions } from "../../../../EnterpriseInfo/OrgInfo/formOptions"; +import { + CHONGQING_DISTRICTS, + ENTERPRISE_SCALE_OPTIONS, + ENTERPRISE_STATUS_OPTIONS, + FILING_RECORD_STATUS_OPTIONS, + FILING_TYPE_OPTIONS, + QUALIFICATION_INDUSTRY_OPTIONS_MAP, +} from "~/enumerate/enterpriseOptions"; +import AttachmentUpload from "~/components/AttachmentUpload"; import StaffViewModal from "~/components/StaffViewModal"; const LIST_PATH = "/container/supervision/basicInfo/registeredOrg/list"; -const ORG_INFO_FORM_OPTIONS = buildOrgInfoFormOptions(); const GENDER_MAP = { 1: "男", 2: "女" }; /** 后端字段名 → 前端表单字段名映射 */ @@ -47,7 +70,7 @@ const BACKEND_TO_FORM_FIELD = { enterpriseScaleName: "enterpriseScale", filingTypeName: "filingType", filingRecordStatusName: "filingRecordStatus", - attachments: "attachments", + attachmentUrls: "attachmentUrls", }; function toFormFields(backendData) { @@ -55,7 +78,11 @@ function toFormFields(backendData) { const result = {}; Object.entries(BACKEND_TO_FORM_FIELD).forEach(([backendKey, formKey]) => { if (backendData[backendKey] !== undefined) { - result[formKey] = backendData[backendKey]; + if (formKey === "productionDate" && backendData[backendKey]) { + result[formKey] = dayjs(backendData[backendKey]); + } else { + result[formKey] = backendData[backendKey]; + } } }); return result; @@ -75,54 +102,64 @@ function resolvePreviewUrl(raw) { } function isImageUrl(url) { - return /\.(jpe?g|png|gif|webp|bmp)(\?|#|$)/i.test(String(url || "").toLowerCase()); + return /\.(jpe?g|png|gif|webp|bmp)(\?|#|$)/i.test( + String(url || "").toLowerCase(), + ); } function MaterialsTab({ materialGroups, loading, onPreview }) { if (loading) { return ; } - 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) + 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, - }; - }), - }; - }); + ? (() => { + 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, + }; + }), + }; + }, + ); return (
@@ -136,15 +173,20 @@ function MaterialsTab({ materialGroups, loading, onPreview }) { size="small" style={{ marginBottom: 16 }} title={`业务范围:${group.scopeName}`} - extra={( + extra={ 证书有效期: - + {group.expireDate} {group.expireWarning ? "(即将到期)" : ""} - )} + } >
( - + ), }, ]} @@ -170,12 +218,11 @@ function MaterialsTab({ materialGroups, loading, onPreview }) { } function PersonnelTab({ personnel, loading, onView }) { - if (loading) { - return ; - } + return (
index + 1 }, { title: "姓名", dataIndex: "userName", width: 90 }, - { title: "性别", width: 60, render: (_, record) => GENDER_MAP[record.genderCode] || "-" }, + { + title: "性别", + width: 60, + render: (_, record) => GENDER_MAP[record.genderCode] || "-", + }, { title: "岗位", dataIndex: "postName", width: 110 }, - { title: "资质范围", dataIndex: "qualScope", width: 90 }, + { + title: "资质范围", + width: 90, + render: (_, record) => + QUALIFICATION_INDUSTRY_OPTIONS_MAP[record.qualScope] || + record.qualScope || + "-", + }, { title: "学历", dataIndex: "educationName", width: 80 }, { title: "注册安全工程师", width: 120, render: (_, record) => ( - + {record.registerEngineerFlag === 1 ? "是" : "否"} ), @@ -200,7 +260,9 @@ function PersonnelTab({ personnel, loading, onView }) { title: "操作", width: 80, render: (_, record) => ( - + ), }, ]} @@ -209,10 +271,17 @@ function PersonnelTab({ personnel, loading, onView }) { } function RegisteredOrgDetailPage(props) { - const id = useMemo(() => parseQueryId(props.location), [props.location?.search]); + 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 [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([]); @@ -235,13 +304,16 @@ function RegisteredOrgDetailPage(props) { const [orgRes, groups, staffList] = await Promise.all([ fetchRegisteredOrgDetail(id), fetchRegisteredOrgQualificationGroups(id).catch((err) => { - console.warn("[RegisteredOrgDetail] qualification load failed:", err); - + console.warn( + "[RegisteredOrgDetail] qualification load failed:", + err, + ); + return { data: {} }; }), fetchRegisteredOrgPersonnelList(id).catch((err) => { console.warn("[RegisteredOrgDetail] personnel load failed:", err); - + return []; }), ]); @@ -254,14 +326,12 @@ function RegisteredOrgDetailPage(props) { } setMaterialGroups(groups?.data || {}); setPersonnel(staffList || []); - } - catch (err) { + } catch (err) { if (!cancelled) { console.warn("[RegisteredOrgDetail] load failed:", err); message.error("加载机构详情失败"); } - } - finally { + } finally { if (!cancelled) { setOrgLoading(false); setExtrasLoading(false); @@ -288,8 +358,13 @@ function RegisteredOrgDetailPage(props) { if (!id) { return ( - 返回}> -

+ 返回} + > +

缺少机构 ID 参数

@@ -302,15 +377,214 @@ function RegisteredOrgDetailPage(props) { label: "备案信息", children: ( - +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ), }, @@ -350,7 +624,7 @@ function RegisteredOrgDetailPage(props) { ]; return ( - +

{orgName || undefined}

@@ -368,7 +642,6 @@ function RegisteredOrgDetailPage(props) { setViewPersonnelId("")} /> )}