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 }) => {
+
+
setParticipantModalOpen(false)}
+ onOk={() => {
+ if (signers.length >= 1) {
+ message.warning("该签字人员最多只能配置1人");
+ setParticipantModalOpen(false);
+ return;
+ }
+ const selected = participants.filter((p) =>
+ selectedParticipantIds.includes(p.id),
+ );
+ selected.forEach((p) => {
+ setSigners((prev) => {
+ if (prev.some((s) => s.personnelId === p.id)) return prev;
+ return [
+ ...prev,
+ {
+ personnelId: p.id,
+ personnelName: p.name,
+ deptName: p.deptName,
+ projectId,
+ },
+ ];
+ });
+ });
+ setParticipantModalOpen(false);
+ }}
+ >
+ {
+ 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: "所有人员均已添加" }}
+ />
+
);