From 396a534bf51db857a6696b991dfe858a16e3c83e Mon Sep 17 00:00:00 2001 From: tangjie <122778500@qq.com> Date: Thu, 20 Aug 2026 15:03:47 +0800 Subject: [PATCH] feat --- src/api/safetyEvalBusiness/index.js | 10 + .../EvalProjectMonitor/Detail/index.js | 24 +- .../ProjectFlow/TeamContent.js | 283 ++++++++++++++++-- 3 files changed, 291 insertions(+), 26 deletions(-) diff --git a/src/api/safetyEvalBusiness/index.js b/src/api/safetyEvalBusiness/index.js index 91e4093..a259c2e 100644 --- a/src/api/safetyEvalBusiness/index.js +++ b/src/api/safetyEvalBusiness/index.js @@ -163,6 +163,16 @@ export const evalProjectMemberSave = declareRequest( "Post > @/safetyEval/institution/eval-project-member/save", ); +export const evalProjectMemberParticipantSave = declareRequest( + "memberParticipantSaveLoading", + "Post > @/safetyEval/institution/eval-project-member/participant/save", +); + +export const evalProjectMemberParticipantList = declareRequest( + "memberParticipantListLoading", + "Get > /safetyEval/institution/eval-project-member/participant/list", +); + // ===== 项目预警(安评业务提醒) ===== export const projectWaringStat = declareRequest( diff --git a/src/pages/Container/MonitorManage/EvalProjectMonitor/Detail/index.js b/src/pages/Container/MonitorManage/EvalProjectMonitor/Detail/index.js index 88473e3..a96407c 100644 --- a/src/pages/Container/MonitorManage/EvalProjectMonitor/Detail/index.js +++ b/src/pages/Container/MonitorManage/EvalProjectMonitor/Detail/index.js @@ -13,6 +13,7 @@ import { Button, message, } from "antd"; +import { PictureOutlined } from "@ant-design/icons"; import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout"; import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd"; import { Connect } from "@cqsjjb/jjb-dva-runtime"; @@ -231,6 +232,25 @@ const TeamPanel = ({ data, loading }) => { const SurveyPanel = ({ personnel, attachments, loading, detailData }) => { if (loading) return ; + +/** 打卡时间后追加人脸照片图标:faceUrl 逗号分隔,第1张为签到、第2张为签退 */ +const renderTimeWithFace = (time, record, idx) => { + const url = record?.faceUrl?.split(",")[idx]; + if (!url) return time || "-"; + return ( + + {time} + } + title="人脸照片" + trigger="hover" + > + + + + ); +}; + const personnelColumns = [ { title: "人员", dataIndex: "personnelName", width: 100 }, { @@ -263,8 +283,8 @@ const SurveyPanel = ({ personnel, attachments, loading, detailData }) => { ), }, - { title: "签到时间", dataIndex: "singInTime", width: 140 }, - { title: "签退时间", dataIndex: "signOutTime", width: 140 }, + { title: "签到时间", dataIndex: "singInTime" , render: (time, record)=>{return renderTimeWithFace(time, record, 0);}}, + { title: "签退时间", dataIndex: "signOutTime" , render: (time, record)=>{return renderTimeWithFace(time, record, 1);}}, { title: "现场照片", dataIndex: "sceneUrl", diff --git a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js index b83c2b1..d14306f 100644 --- a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js +++ b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js @@ -9,6 +9,9 @@ import { Checkbox, message, Spin, + Card, + Popover, + Image, } from "antd"; import { Connect } from "@cqsjjb/jjb-dva-runtime"; import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace"; @@ -20,10 +23,22 @@ import LayoutFooterPortal from "~/components/LayoutFooterPortal"; const { router } = tools; const TeamContent = ({ readOnly, ...props }) => { - const { safetyEvalBusiness, evalProjectMemberPage, evalProjectMemberSave } = - props; - const { evalProjectDetailData, memberLoading, memberSaveLoading } = - safetyEvalBusiness || {}; + const { + safetyEvalBusiness, + evalProjectMemberPage, + evalProjectMemberSave, + evalProjectMemberParticipantSave, + evalProjectMemberParticipantList, + evalSignTaskSave, + } = props; + const { + evalProjectDetailData, + memberLoading, + memberSaveLoading, + memberParticipantSaveLoading, + memberParticipantListLoading, + evalSignTaskSaveLoading, + } = safetyEvalBusiness || {}; const participants = evalProjectDetailData?.participants || []; const projectId = router.query?.id; @@ -31,6 +46,11 @@ const TeamContent = ({ readOnly, ...props }) => { const [selectedRole, setSelectedRole] = useState(null); const [selectedPersons, setSelectedPersons] = useState([]); const [teamMembers, setTeamMembers] = useState([]); + // 参与风险分析人员签字 + const [participantModalOpen, setParticipantModalOpen] = useState(false); + const [selectedParticipantIds, setSelectedParticipantIds] = useState([]); + + const [signers, setSigners] = useState([]); const fetchData = async () => { if (!projectId) return; @@ -49,6 +69,11 @@ const TeamContent = ({ readOnly, ...props }) => { })), ); } + // 参与签字人员(查询接口:参与人列表) + const signRes = await evalProjectMemberParticipantList({ projectId }); + if (signRes?.success !== false) { + setSigners(signRes?.data || []); + } }; useEffect(() => { @@ -98,16 +123,15 @@ const TeamContent = ({ readOnly, ...props }) => { return; } // 一人只能担任一个单人唯一角色 - const uniqueRoleValues = ROLE_DEFINITIONS.filter( - (r) => r.unique, - ).map((r) => r.value); + const uniqueRoleValues = ROLE_DEFINITIONS.filter((r) => r.unique).map( + (r) => r.value, + ); const conflict = selectedPersons.find((personId) => { const member = teamMembers.find((m) => m.personnelId === personId); return ( member && member.roles.some( - (r) => - uniqueRoleValues.includes(r.role) && r.role !== selectedRole, + (r) => uniqueRoleValues.includes(r.role) && r.role !== selectedRole, ) ); }); @@ -156,14 +180,68 @@ const TeamContent = ({ readOnly, ...props }) => { setTeamMembers((prev) => prev.filter((m) => m.personnelId !== personnelId)); }; - const handleSave = async () => { - const allRoles = teamMembers.flatMap((m) => m.roles.map((r) => r.role)); - if (!allRoles.includes("PROJECT_LEAD")) { - message.warning("请先配置项目负责人(项目组组长)"); + // 发起APP签字 + const handleSignApp = async (index) => { + const participant = signers[index]; + if (!participant?.personnelId) { + message.warning("该人员信息不完整,请先保存后再发起签字"); return; } - if (!allRoles.includes("PROCESS_CONTROL_LEAD")) { - message.warning("请先配置过程控制负责人"); + // 发起签字前先保存签字人员 + const saveRes = await evalProjectMemberParticipantSave( + signers.map((s, idx) => ({ + id: s.id, + projectId, + personnelId: s.personnelId, + personnelName: s.personnelName, + deptName: s.deptName, + signStatusCode: s.signStatusCode, + signFileUrl: s.signFileUrl, + sortOrder: idx + 1, + })), + ); + if (saveRes?.success === false) return; + + try { + const res = await evalSignTaskSave({ + projectId, + nodeCode: "PROJECT_GROUP", + bizRefId: projectId || undefined, + signerPersonnelId: participant.personnelId, + taskTitle: "项目组成立签字", + taskStatusCode: 1, + }); + if (res?.success) { + message.success("已发起APP签字"); + setSigners((prev) => + prev.map((s, i) => (i === index ? { ...s, signStatusCode: 1 } : s)), + ); + } + } finally { + } + }; + + const handleRemoveSigner = (personnelId) => { + setSigners((prev) => prev.filter((s) => s.personnelId !== personnelId)); + }; + + const handleSave = async () => { + const allRoles = teamMembers.flatMap((m) => m.roles.map((r) => r.role)); + const requiredRoles = [ + { value: "REPORT_COMPILER", name: "报告编制人" }, + { value: "INTERNAL_AUDITOR", name: "内审人" }, + { value: "PROJECT_LEAD", name: "项目负责人(项目组组长)" }, + { value: "TECHNICAL_LEAD", name: "技术负责人" }, + { value: "PROCESS_CONTROL_LEAD", name: "过程控制负责人" }, + ]; + const missing = requiredRoles.find((r) => !allRoles.includes(r.value)); + if (missing) { + message.warning(`请先配置${missing.name}(最少1人)`); + return; + } + // 校验签字人员是否已完成签字 + if (signers.length && !signers.every((s) => s.signStatusCode === 2)) { + message.warning("签字人员尚未全部完成签字,请先完成签字后再确认项目组"); return; } const payload = teamMembers.map((m) => ({ @@ -178,6 +256,22 @@ const TeamContent = ({ readOnly, ...props }) => { })); const res = await evalProjectMemberSave(payload); if (res?.success !== false) { + // 保存签字人员(保存项目经理签字人) + if (signers.length) { + const signRes = await evalProjectMemberParticipantSave( + signers.map((s, idx) => ({ + id: s.id, + projectId, + personnelId: s.personnelId, + personnelName: s.personnelName, + deptName: s.deptName, + signStatusCode: s.signStatusCode, + signFileUrl: s.signFileUrl, + sortOrder: idx + 1, + })), + ); + if (signRes?.success === false) return; + } message.success("保存成功"); fetchData(); props.getDetail(); @@ -196,7 +290,8 @@ const TeamContent = ({ readOnly, ...props }) => { render: (roles, record) => (
{roles.map((r) => { - const inOnly = r.role === "PROJECT_LEAD" || r.role === "PROCESS_CONTROL_LEAD"; + const inOnly = + r.role === "PROJECT_LEAD" || r.role === "PROCESS_CONTROL_LEAD"; return ( { }, ]; + // 参与风险分析人员签字(去掉分析意见、只能选择一个人) + const signerColumns = [ + { title: "姓名", dataIndex: "personnelName", ellipsis: true }, + { title: "部门/岗位", dataIndex: "deptName", ellipsis: true }, + { + title: "签字状态", + render: (_, record, index) => { + const value = record.signStatusCode; + const signFileUrl = record.signFileUrl; + let text = "发起APP签字"; + if (value === 1) text = "待APP签字"; + if (value === 2) text = "已手写签字"; + + const btn = ( + + ); + + // 已签字且有签字图片时,悬停展示签字图片(disabled 按钮不触发 hover,需用 span 包裹) + if (value === 2 && signFileUrl) { + return ( + + } + > + {btn} + + ); + } + return btn; + }, + }, + { + title: "操作", + width: 80, + render: (_, record) => ( + + ), + }, + ]; + return ( - +
@@ -270,18 +426,16 @@ const TeamContent = ({ readOnly, ...props }) => { title: "已配置人员", value: `${teamMembers.length} 人`, valueColor: "#1e293b", - + iconBg: "#eff6ff", - }, { key: "roleCoverage", title: "已覆盖角色", value: `${new Set(allRoles).size} / 11`, valueColor: "#1677ff", - + iconBg: "#e6f4ff", - }, { key: "projectLead", @@ -290,7 +444,6 @@ const TeamContent = ({ readOnly, ...props }) => { valueColor: allRoles.includes("PROJECT_LEAD") ? "#52c41a" : "#faad14", - }, { key: "processControlLead", @@ -301,7 +454,6 @@ const TeamContent = ({ readOnly, ...props }) => { valueColor: allRoles.includes("PROCESS_CONTROL_LEAD") ? "#52c41a" : "#faad14", - }, ]} /> @@ -322,6 +474,35 @@ const TeamContent = ({ readOnly, ...props }) => { scroll={{ x: 1600, y: 320 }} /> + + + {!readOnly && ( + + )} + +
查看11类项目角色职责说明 @@ -348,7 +529,7 @@ const TeamContent = ({ readOnly, ...props }) => {
{ + const existing = signers.map((s) => s.personnelId); + return participants.filter((p) => !existing.includes(p.id)); + })()} + rowKey="id" + rowSelection={{ + type: "radio", + selectedRowKeys: selectedParticipantIds, + onChange: (keys) => setSelectedParticipantIds(keys), + }} + columns={[ + { title: "姓名", dataIndex: "name", width: 100 }, + { title: "部门", dataIndex: "deptName", width: 120 }, + { title: "岗位", dataIndex: "posName", width: 120 }, + { title: "能力", dataIndex: "capacity", width: 200 }, + ]} + pagination={false} + size="small" + locale={{ emptyText: "所有人员均已添加" }} + /> + );