fix
parent
de95e1620e
commit
9f4fc71d90
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
安全评价检测检验机构从业告知书
|
||||
:
|
||||
机构名称
|
||||
{account}
|
||||
机构资质证书编号
|
||||
{qualificationCertNo}
|
||||
机构信息
|
||||
公开网址
|
||||
{infoDisclosureUrl}
|
||||
办公地址
|
||||
{businessAddress}
|
||||
邮政
|
||||
编码
|
||||
|
||||
法定代表人
|
||||
{legalRepresentative}
|
||||
联系人
|
||||
{contactName}
|
||||
联系
|
||||
电话
|
||||
{contactPhone}
|
||||
项目名称
|
||||
{projectName}
|
||||
项目地址
|
||||
{projectAddress}
|
||||
项目所属行业
|
||||
{industryText}
|
||||
项目组组长
|
||||
{projectLeader}
|
||||
联系
|
||||
电话
|
||||
|
||||
技术服务期限
|
||||
|
||||
计划现场勘验
|
||||
(检测检验)时间
|
||||
|
||||
项目组成员、专业及工作任务(安全评价机构填写)
|
||||
姓 名
|
||||
专 业
|
||||
工作任务
|
||||
{#members}{name}
|
||||
{major}
|
||||
{task}{/members}
|
||||
|
||||
|
||||
|
||||
现场检测检验人员(安全生产检测检验机构填写)
|
||||
姓 名
|
||||
检测检验项目
|
||||
|
||||
|
||||
|
||||
|
||||
我单位承接了 □安全评价/□安全生产检测检验项目,拟于近期开展技术服务活动,现按照规定将有关信息告知如下。
|
||||
机构(盖章)
|
||||
年 月
|
||||
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
|||
>
|
||||
<Upload
|
||||
disabled={disabled}
|
||||
listType="picture-card"
|
||||
listType={listType}
|
||||
// maxCount 为 1 时仍单选,其余场景允许一次选择多个文件
|
||||
multiple={!maxCount || maxCount > 1}
|
||||
customRequest={customRequest}
|
||||
|
|
@ -136,10 +152,16 @@ export default function AttachmentUpload({ name, label, disabled, maxCount, acce
|
|||
}
|
||||
}}
|
||||
>
|
||||
<button style={{ border: 0, background: "none" }} type="button">
|
||||
<PlusOutlined />
|
||||
<div style={{ marginTop: 8 }}>上传附件</div>
|
||||
</button>
|
||||
{listType === "text" ? (
|
||||
<Button type="dashed" size="small" icon={<UploadOutlined />}>
|
||||
{uploadText}
|
||||
</Button>
|
||||
) : (
|
||||
<button style={{ border: 0, background: "none" }} type="button">
|
||||
<PlusOutlined />
|
||||
<div style={{ marginTop: 8 }}>{uploadText}</div>
|
||||
</button>
|
||||
)}
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
{previewImage && <Image
|
||||
|
|
|
|||
|
|
@ -143,6 +143,10 @@ const EvalProject = (props) => {
|
|||
>
|
||||
删除
|
||||
</Button>
|
||||
<Button type="link" size="small" onClick={() =>
|
||||
props.history.push(`/container/SafetyEvalBusiness/ProjectFlow?id=${record.id}&readOnly=1`)}>
|
||||
项目节点
|
||||
</Button>
|
||||
</TableAction>
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -235,44 +235,7 @@ const ControlContent = ({ readOnly, ...props }) => {
|
|||
</div>
|
||||
<Image src={processControl?.reviewerSignatureUrl} width={120} height={80} />
|
||||
</div>}
|
||||
{!readOnly&&<Collapse
|
||||
style={{ marginTop: 12 }}
|
||||
items={[
|
||||
{
|
||||
key: "checklist",
|
||||
label: "过程控制完整核查清单(13项)",
|
||||
children: (
|
||||
<ol
|
||||
style={{
|
||||
margin: 0,
|
||||
paddingLeft: 20,
|
||||
fontSize: 14,
|
||||
color: "#475569",
|
||||
lineHeight: 1.8,
|
||||
}}
|
||||
>
|
||||
<li>合同签订前是否进行项目风险分析。</li>
|
||||
<li>是否签订技术服务合同。</li>
|
||||
<li>是否下达项目任务通知单。</li>
|
||||
<li>是否有项目组成立和项目负责人任命记录。</li>
|
||||
<li>是否编制项目实施计划。</li>
|
||||
<li>现场检查记录是否完整、有效。</li>
|
||||
<li>是否有书面整改通知单并由被评价单位盖章或签字确认。</li>
|
||||
<li>
|
||||
是否进行现场复查,复查单是否双方签名或盖章并注明日期。
|
||||
</li>
|
||||
<li>是否有现场勘查、整改复查影像资料。</li>
|
||||
<li>是否完成内审、技审并保留审核记录、签字和日期。</li>
|
||||
<li>委托方提供资料是否齐全并由被评价单位盖章。</li>
|
||||
<li>
|
||||
委托方资料、评价过程资料和评价报告存档是否符合要求。
|
||||
</li>
|
||||
<li>存档资料是否完整且具有可追溯性。</li>
|
||||
</ol>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>}
|
||||
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }) => (
|
||||
<div>
|
||||
<div className="pf-section-header">
|
||||
<div>
|
||||
|
|
@ -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) => <Tag color={v === 1 ? "success" : "error"}>{v === 1 ? "通过" : "未通过"}</Tag> },
|
||||
{ title: "GPS定位", dataIndex: "gpsState", render: (v) => <Tag color={v === 1 ? "success" : "error"}>{v === 1 ? "正常" : "异常"}</Tag> },
|
||||
{ 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" }),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
|
||||
<Form form={form} layout="vertical" disabled={readOnly} scrollToFirstError>
|
||||
<div style={{ display: activeStage === "PRACTICE_NOTIFICATION" ? "block" : "none" }}>
|
||||
<NoticePanel readOnly={readOnly} />
|
||||
<NoticePanel readOnly={readOnly} downloadTemplate={downloadTemplate} />
|
||||
</div>
|
||||
<div style={{ display: activeStage === "SITE_INSPECTION_FORM" ? "block" : "none" }}>
|
||||
<InspectionPanel dataSource={surveyPersonnel} evalProjectDetailData={evalProjectDetailData} readOnly={readOnly} />
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<Button
|
||||
type="dashed"
|
||||
size="small"
|
||||
style={{ marginTop: 8 }}
|
||||
disabled={signers.length >= 1}
|
||||
onClick={() => {
|
||||
setSelectedParticipantIds([]);
|
||||
setParticipantModalOpen(true);
|
||||
}}
|
||||
>
|
||||
+ 添加参与人员
|
||||
</Button>
|
||||
)}
|
||||
<Flex justify="space-between" align="flex-start" style={{ marginTop: 8 }}>
|
||||
{!readOnly && (
|
||||
<Button
|
||||
type="dashed"
|
||||
size="small"
|
||||
disabled={signers.length >= 1}
|
||||
onClick={() => {
|
||||
setSelectedParticipantIds([]);
|
||||
setParticipantModalOpen(true);
|
||||
}}
|
||||
>
|
||||
+ 添加参与人员
|
||||
</Button>
|
||||
)}
|
||||
<Form form={attachForm} disabled={readOnly} onValuesChange={handleAttachChange}>
|
||||
<AttachmentUpload
|
||||
name="appointmentFiles"
|
||||
label="人员任命书"
|
||||
rules={[{ required: true, message: "请上传人员任命书" }]}
|
||||
maxCount={10}
|
||||
accept=".pdf,.doc,.docx,.png,.jpg,.jpeg"
|
||||
listType="text"
|
||||
uploadText="上传人员任命书"
|
||||
extra="支持 PDF、Word、PNG、JPG 格式,最多上传 10 个文件,上传完成自动保存"
|
||||
/>
|
||||
</Form>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
||||
{!readOnly &&<details className="pf-role-guide">
|
||||
|
|
|
|||
Loading…
Reference in New Issue