diff --git a/jjb.config.js b/jjb.config.js index d3739cf..5336dea 100644 --- a/jjb.config.js +++ b/jjb.config.js @@ -12,9 +12,9 @@ module.exports = { // 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095 //API_HOST: "http://localhost:80", - API_HOST: "http://192.168.0.134", + // API_HOST: "http://192.168.0.134", // API_HOST: "http://192.168.0.150", //太浅 - // API_HOST: "https://gbs-gateway.qhdsafety.com", + API_HOST: "https://gbs-gateway.qhdsafety.com", // API_HOST: "http://192.168.0.103", //huwei }, production: { diff --git a/public/从业告知书模板.docx b/public/从业告知书模板.docx new file mode 100644 index 0000000..84ffa2e --- /dev/null +++ b/public/从业告知书模板.docx @@ -0,0 +1,58 @@ + 安全评价检测检验机构从业告知书 + : +机构名称 +{account} +机构资质证书编号 +{qualificationCertNo} +机构信息 +公开网址 +{infoDisclosureUrl} +办公地址 +{businessAddress} +邮政 +编码 + +法定代表人 +{legalRepresentative} +联系人 +{contactName} +联系 +电话 +{contactPhone} +项目名称 +{projectName} +项目地址 +{projectAddress} +项目所属行业 +{industryText} +项目组组长 +{projectLeader} +联系 +电话 + +技术服务期限 + +计划现场勘验 +(检测检验)时间 + +项目组成员、专业及工作任务(安全评价机构填写) +姓 名 +专 业 +工作任务 +{#members}{name} +{major} +{task}{/members} + + + +现场检测检验人员(安全生产检测检验机构填写) +姓 名 + 检测检验项目 + + + + + 我单位承接了 □安全评价/□安全生产检测检验项目,拟于近期开展技术服务活动,现按照规定将有关信息告知如下。 + 机构(盖章) + 年 月 + diff --git a/src/api/safetyEvalBusiness/index.js b/src/api/safetyEvalBusiness/index.js index be8c2e2..4bd8446 100644 --- a/src/api/safetyEvalBusiness/index.js +++ b/src/api/safetyEvalBusiness/index.js @@ -179,6 +179,16 @@ export const evalProjectMemberPage = declareRequest( "Get > /safetyEval/institution/eval-project-member/page", ); +export const evalProjectEstablishedAttachSave = declareRequest( + "projectEstablishedAttachSaveLoading", + "Post > /safetyEval/institution/eval-project-member/saveProjectEstablishedAttach", +); + +export const evalProjectEstablishedAttachGet = declareRequest( + "projectEstablishedAttachGetLoading", + "Get > /safetyEval/institution/eval-project-member/getProjectEstablishedAttach", +); + export const evalProjectMemberSave = declareRequest( "memberSaveLoading", "Post > @/safetyEval/institution/eval-project-member/save", diff --git a/src/components/AttachmentUpload/index.js b/src/components/AttachmentUpload/index.js index 397d81c..990d6a5 100644 --- a/src/components/AttachmentUpload/index.js +++ b/src/components/AttachmentUpload/index.js @@ -1,6 +1,6 @@ -import { Form, Image, Upload, message } from "antd"; +import { Button, Form, Image, Upload, message } from "antd"; import { useEffect, useRef, useState } from "react"; -import { PlusOutlined } from "@ant-design/icons"; +import { PlusOutlined, UploadOutlined } from "@ant-design/icons"; /** 批量上传接口:一次请求上传 1~N 个文件,表单字段名为 files */ const BATCH_UPLOAD_ACTION = `${window.process.env.app.API_HOST}/safetyEval/file/upload/batch`; @@ -8,7 +8,23 @@ const BATCH_UPLOAD_ACTION = `${window.process.env.app.API_HOST}/safetyEval/file/ const isImage = (url) => /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || ""); -export default function AttachmentUpload({ name, label, disabled, maxCount, accept, extra ,rules, style, keepOriginFile }) { +/** + * @param {string} [listType] - 上传列表样式,默认 picture-card;传 text 时为按钮触发形态 + * @param {string} [uploadText] - 触发区文案,默认“上传附件” + */ +export default function AttachmentUpload({ + name, + label, + disabled, + maxCount, + accept, + extra, + rules, + style, + keepOriginFile, + listType = "picture-card", + uploadText = "上传附件", +}) { const [previewImage, setPreviewImage] = useState(""); // 同一次选择的文件先入队,等微任务结束后合并为一个批量请求 const queueRef = useRef([]); @@ -104,7 +120,7 @@ export default function AttachmentUpload({ name, label, disabled, maxCount, acce > 1} customRequest={customRequest} @@ -136,10 +152,16 @@ export default function AttachmentUpload({ name, label, disabled, maxCount, acce } }} > - + {listType === "text" ? ( + + ) : ( + + )} {previewImage && { > 删除 + ), }, diff --git a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/ControlContent.js b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/ControlContent.js index 81d5ed8..247aac1 100644 --- a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/ControlContent.js +++ b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/ControlContent.js @@ -235,44 +235,7 @@ const ControlContent = ({ readOnly, ...props }) => { } - {!readOnly&& -
  • 合同签订前是否进行项目风险分析。
  • -
  • 是否签订技术服务合同。
  • -
  • 是否下达项目任务通知单。
  • -
  • 是否有项目组成立和项目负责人任命记录。
  • -
  • 是否编制项目实施计划。
  • -
  • 现场检查记录是否完整、有效。
  • -
  • 是否有书面整改通知单并由被评价单位盖章或签字确认。
  • -
  • - 是否进行现场复查,复查单是否双方签名或盖章并注明日期。 -
  • -
  • 是否有现场勘查、整改复查影像资料。
  • -
  • 是否完成内审、技审并保留审核记录、签字和日期。
  • -
  • 委托方提供资料是否齐全并由被评价单位盖章。
  • -
  • - 委托方资料、评价过程资料和评价报告存档是否符合要求。 -
  • -
  • 存档资料是否完整且具有可追溯性。
  • - - ), - }, - ]} - />} + )} diff --git a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/SurveyContent.js b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/SurveyContent.js index 10745ff..f241702 100644 --- a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/SurveyContent.js +++ b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/SurveyContent.js @@ -1,12 +1,14 @@ import React, { useState, useEffect } from "react"; import { Form, Button, Flex, Typography, Steps, Tag, Card, Table, message, Popover, Image, Checkbox } from "antd"; -import { DownloadOutlined, QuestionOutlined, PictureOutlined } from "@ant-design/icons"; +import { DownloadOutlined, QuestionOutlined, PictureOutlined } from "@ant-design/icons"; import { Connect } from "@cqsjjb/jjb-dva-runtime"; import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace"; import { tools } from "@cqsjjb/jjb-common-lib"; import AttachmentUpload from "~/components/AttachmentUpload"; -import { ROLE_DEFINITIONS_MAP } from '~/enumerate/constant' +import { ROLE_DEFINITIONS_MAP, ROLE_DEFINITIONS } from '~/enumerate/constant' +import { QUALIFICATION_INDUSTRY_OPTIONS_MAP } from "~/enumerate/enterpriseOptions"; import LayoutFooterPortal from "~/components/LayoutFooterPortal"; +import { fillDocxTemplate } from "~/utils/fillDocxTemplate"; import "./index.less"; import dayjs from "dayjs"; @@ -36,22 +38,9 @@ const attachRequiredRules = (label) => [ }, ]; -const downloadTemplate = async () => { - try { - const res = await fetch("https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com/jjb/6a6bff3ee4b021a75404adfb.docx"); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = "从业告知书模板.docx"; - a.click(); - URL.revokeObjectURL(url); - } catch { - message.error("下载失败,请重试"); - } -}; -const NoticePanel = ({ readOnly }) => ( + +const NoticePanel = ({ readOnly, downloadTemplate }) => (
    @@ -138,8 +127,8 @@ const InspectionPanel = ({ dataSource = [], evalProjectDetailData = {}, readOnly { title: "项目角色", dataIndex: "role", render: (v) => Array.isArray(v) ? v.map(item => ROLE_DEFINITIONS_MAP[item]).join('、') : v, ellipsis: true }, { title: "人脸识别", dataIndex: "faceState", render: (v) => {v === 1 ? "通过" : "未通过"} }, { title: "GPS定位", dataIndex: "gpsState", render: (v) => {v === 1 ? "正常" : "异常"} }, - { title: "签到时间", dataIndex: "singInTime" , render: (time, record)=>{return renderTimeWithFace(time, record, 0);}}, - { title: "签退时间", dataIndex: "signOutTime" , render: (time, record)=>{return renderTimeWithFace(time, record, 1);}}, + { title: "签到时间", dataIndex: "singInTime", render: (time, record) => { return renderTimeWithFace(time, record, 0); } }, + { title: "签退时间", dataIndex: "signOutTime", render: (time, record) => { return renderTimeWithFace(time, record, 1); } }, { title: "在场时长", render: (_, r) => calcDuration(r.singInTime, r.signOutTime) }, { title: "现场照片", dataIndex: "sceneUrl", render: (v) => { @@ -256,7 +245,7 @@ const SurveyContent = ({ readOnly, ...props }) => { const [activeStage, setActiveStage] = useState("PRACTICE_NOTIFICATION"); const [attachMap, setAttachMap] = useState({}); const [surveyPersonnel, setSurveyPersonnel] = useState([]); - const { evalSurveyListLoading, evalSurveySaveAttachLoading, evalProjectDetailData } = props.safetyEvalBusiness; + const { evalSurveySaveAttachLoading, evalProjectDetailData } = props.safetyEvalBusiness; const projectId = router.query?.id; const currentIdx = STAGES.findIndex((s) => s.key === activeStage); const isFirst = currentIdx === 0; @@ -295,6 +284,57 @@ const SurveyContent = ({ readOnly, ...props }) => { } }; + const downloadTemplate = async () => { + const orgInfo = JSON.parse(sessionStorage.getItem('orgInfo')); + const industryText = String(evalProjectDetailData.industryCode || "") + .split(",") + .map((code) => QUALIFICATION_INDUSTRY_OPTIONS_MAP[code]) + .join("、"); + // 项目组成员(同项目组 tab 页数据):姓名/专业/工作任务 + const memberRes = await props.evalProjectMemberPage({ projectId }); + const members = (memberRes?.data || []).map((item) => ({ + name: item.personnelName, + major: item.capacity || "", + task: + item.responsibility || + (item.role || []) + .map((r) => ROLE_DEFINITIONS.find((d) => d.value === r)?.summary) + .filter(Boolean) + .join("、"), + isLeader: (item.role || []).includes("PROJECT_LEAD"), + })); + // 项目组组长取自项目组成员中的 PROJECT_LEAD,展示同 namesByRole:如"张三(化工)" + const projectLeader = members + .filter((m) => m.isLeader) + .map((m) => `${m.name}${m.major ? `(${m.major})` : ""}`) + .join("、"); + const data = { + account: orgInfo.account, + qualificationCertNo: orgInfo.qualificationCertNo, + infoDisclosureUrl: orgInfo.infoDisclosureUrl, + businessAddress: orgInfo.businessAddress, + legalRepresentative: orgInfo.legalRepresentative, + contactName: orgInfo.contactName, + contactPhone: orgInfo.contactPhone, + projectName: evalProjectDetailData.projectName, + projectAddress: evalProjectDetailData.customerAddress || "", + industryText, + projectLeader, + members, + }; + try { + await fillDocxTemplate( + 'https://gbs-cqaqpj.oss-cn-beijing.aliyuncs.com/jjb/6a913235e4b0b3767a78622d.docx', + data, + `安全评价检测检验机构从业告知书.docx`, + { download: true }, + ); + message.success("导出成功"); + } catch { + message.error("导出失败,请重试"); + } + }; + useEffect(() => { loadAttachments(); }, []); @@ -388,14 +428,14 @@ const SurveyContent = ({ readOnly, ...props }) => { ); return { title: s.label, - ...(uploaded ? { status: "finish" } : {status: "wait"}), + ...(uploaded ? { status: "finish" } : { status: "wait" }), }; })} />
    - +
    diff --git a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js index 6969f57..bd286af 100644 --- a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js +++ b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { Tag, Button, @@ -7,6 +7,7 @@ import { Modal, Radio, Checkbox, + Form, message, Spin, Card, @@ -18,6 +19,7 @@ import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace"; import { tools } from "@cqsjjb/jjb-common-lib"; import { ROLE_DEFINITIONS, EVAL_TYPE_MAP } from "~/enumerate/constant"; import StatCardGroup from "~/components/StatCardGroup"; +import AttachmentUpload from "~/components/AttachmentUpload"; import LayoutFooterPortal from "~/components/LayoutFooterPortal"; import { fillDocxTemplate } from "~/utils/fillDocxTemplate"; @@ -33,6 +35,8 @@ const TeamContent = ({ readOnly, ...props }) => { evalProjectMemberParticipantSave, evalProjectMemberParticipantList, evalSignTaskSave, + evalProjectEstablishedAttachSave, + evalProjectEstablishedAttachGet, } = props; const { evalProjectDetailData, @@ -49,6 +53,9 @@ const TeamContent = ({ readOnly, ...props }) => { const [selectedRole, setSelectedRole] = useState(null); const [selectedPersons, setSelectedPersons] = useState([]); const [teamMembers, setTeamMembers] = useState([]); + // 人员任命书附件(项目成立附件 PROJECT_ESTABLISHED):局部表单承载上传,保存记录主键供更新 + const [attachForm] = Form.useForm(); + const establishedAttachIdRef = useRef(null); // 参与风险分析人员签字 const [participantModalOpen, setParticipantModalOpen] = useState(false); const [selectedParticipantIds, setSelectedParticipantIds] = useState([]); @@ -77,6 +84,39 @@ const TeamContent = ({ readOnly, ...props }) => { if (signRes?.success !== false) { setSigners(signRes?.data || []); } + // 人员任命书附件回显 + const attachRes = await evalProjectEstablishedAttachGet({ projectId }); + if (attachRes?.data) { + establishedAttachIdRef.current = attachRes.data.id || null; + let files = []; + try { + files = JSON.parse(attachRes.data.fileUrl) || []; + } catch {} + attachForm.setFieldsValue({ + appointmentFiles: Array.isArray(files) ? files : [], + }); + } + }; + + // 人员任命书上传/删除后自动保存;接口不回传主键,保存后重查同步 id 供下次更新 + const handleAttachChange = async (_, { appointmentFiles: files = [] }) => { + if (readOnly) return; + if (files.some((f) => f.status === "uploading")) return; + if (files.length && files.some((f) => !(f.status === "done" && f.url))) return; + if (!files.length && !establishedAttachIdRef.current) return; + const res = await evalProjectEstablishedAttachSave({ + ...(establishedAttachIdRef.current + ? { id: establishedAttachIdRef.current } + : {}), + projectId, + attachTypeCode: "PROJECT_ESTABLISHED", + fileUrl: JSON.stringify(files), + }); + if (res?.success !== false) { + message.success("人员任命书已保存"); + const getRes = await evalProjectEstablishedAttachGet({ projectId }); + if (getRes?.data?.id) establishedAttachIdRef.current = getRes.data.id; + } }; useEffect(() => { @@ -520,20 +560,33 @@ const TeamContent = ({ readOnly, ...props }) => { pagination={false} scroll={{ x: 800 }} /> - {!readOnly && ( - - )} + + {!readOnly && ( + + )} + + + + {!readOnly &&