safety-eval-service-frontend/src/pages/Container/SafetyEvalBusiness/ProjectFlow/InternalReviewContent.js

337 lines
10 KiB
JavaScript
Raw Normal View History

2026-07-24 11:05:09 +08:00
import React, { useState, useEffect } from "react";
import {
Tag,
Button,
Card,
Table,
Select,
Input,
Flex,
Typography,
Row,
Col,
message,
2026-07-24 15:08:29 +08:00
Tooltip,
2026-07-28 18:12:06 +08:00
Collapse,
2026-07-24 11:05:09 +08:00
} from "antd";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { tools } from "@cqsjjb/jjb-common-lib";
2026-07-20 15:40:12 +08:00
const { TextArea } = Input;
2026-07-24 11:05:09 +08:00
const { router } = tools;
2026-07-20 15:40:12 +08:00
2026-07-24 11:05:09 +08:00
const CONCLUSION_OPTIONS = [
{ value: "conform_to", label: "符合" },
{ value: "not_conform_to", label: "需修改" },
2026-07-20 15:40:12 +08:00
];
2026-07-30 09:59:44 +08:00
const InternalReviewContent = ({ readOnly, ...props }) => {
2026-07-24 11:05:09 +08:00
const projectId = router.query?.id;
const {
internalReviewDetail,
internalReviewAssignLoading,
internalReviewConfirmLoading,
internalReviewDetailLoading,
} = props.safetyEvalBusiness || {};
const { internalReview, reviewers, reviewItems } = internalReviewDetail || {};
const [rows, setRows] = useState([]);
const [overallOpinion, setOverallOpinion] = useState("");
2026-07-24 15:08:29 +08:00
const [selectedReviewer, setSelectedReviewer] = useState(undefined);
2026-07-24 11:05:09 +08:00
const fetchDetail = () => {
2026-07-24 15:08:29 +08:00
props.evalInternalReviewDetail({ projectId }).then((res) => {
if (res?.success && res.data.signTaskPersonCo) {
setSelectedReviewer(res.data.signTaskPersonCo?.signerPersonnelId);
}
});
2026-07-24 11:05:09 +08:00
};
useEffect(() => {
if (projectId) fetchDetail();
}, [projectId]);
useEffect(() => {
if (reviewItems) {
setRows(
reviewItems.map((item) => ({
...item,
2026-08-03 15:45:58 +08:00
reviewConclusion: item.reviewConclusion || "conform_to",
2026-07-24 11:05:09 +08:00
_key: item.id || item.reviewDimensionCode,
})),
);
}
if (internalReview?.opinionContent) {
setOverallOpinion(internalReview.opinionContent);
}
}, [internalReviewDetail]);
2026-07-20 15:40:12 +08:00
const updateRow = (index, field, value) => {
const next = [...rows];
next[index] = { ...next[index], [field]: value };
setRows(next);
};
2026-07-24 11:05:09 +08:00
const isDispatched =
internalReview?.reviewerSignatureTaskStatus === "dispatched";
const problemCount = rows.filter(
(r) => r.reviewConclusion === "not_conform_to",
).length;
const handleAssign = async () => {
if (!projectId) return;
2026-07-28 18:12:06 +08:00
const result = await handleConfirm("not_passed");
if (!result) return;
2026-07-28 13:58:58 +08:00
const res = await props.evalInternalReviewAssign({
projectId,
signTaskPersonCmd: {
signerPersonnelId: selectedReviewer,
signerName: reviewers.find(
(item) => item.personnelId === selectedReviewer,
)?.personnelName,
},
});
2026-07-24 11:05:09 +08:00
if (res?.success !== false) {
message.success("已下发签字任务");
2026-07-28 18:12:06 +08:00
fetchDetail();
2026-07-24 11:05:09 +08:00
}
};
const handleConfirm = async (resultCode) => {
2026-07-28 18:12:06 +08:00
if (!rows.every((r) => r.reviewConclusion)) {
2026-07-28 15:10:56 +08:00
message.error("请填写审核结论");
return;
}
2026-07-28 13:58:58 +08:00
return new Promise(async (resolve, reject) => {
if (!projectId) return;
const payload = {
id: internalReview?.id,
projectId,
resultCode,
opinionContent: overallOpinion,
reviewItems: rows.map((r) => ({
bizType: "eval_internal_review",
bizId: internalReview?.id,
reviewDimensionCode: r.reviewDimensionCode,
reviewDimensionName: r.reviewDimensionName,
reviewFocus: r.reviewFocus,
reviewConclusion: r.reviewConclusion,
reviewOpinion: r.reviewOpinion,
})),
};
const res = await props.evalInternalReviewConfirm(payload);
if (res?.success !== false) {
message.success(resultCode === "passed" ? "已提交" : "已保存");
resolve(res);
if (resultCode === "passed") {
props.getDetail?.();
}
2026-07-28 18:12:06 +08:00
} else {
2026-07-28 15:10:56 +08:00
reject();
2026-07-28 13:58:58 +08:00
}
});
2026-07-24 11:05:09 +08:00
};
const columns = [
{ title: "审核维度", dataIndex: "reviewDimensionName", width: 100 },
{ title: "审核重点", dataIndex: "reviewFocus", width: 160 },
{
title: "结论",
dataIndex: "reviewConclusion",
width: 120,
render: (val, _, idx) => (
<Select
2026-08-03 15:45:58 +08:00
value={val}
2026-07-24 11:05:09 +08:00
size="small"
2026-07-28 13:58:58 +08:00
style={{ width: 130 }}
allowClear
2026-07-30 09:59:44 +08:00
disabled={readOnly}
2026-07-28 13:58:58 +08:00
placeholder="请选择结论"
2026-07-24 11:05:09 +08:00
onChange={(v) => updateRow(idx, "reviewConclusion", v)}
>
{CONCLUSION_OPTIONS.map((opt) => (
<Select.Option key={opt.value} value={opt.value}>
{opt.label}
</Select.Option>
))}
</Select>
),
},
{
title: "审核意见",
dataIndex: "reviewOpinion",
width: 180,
render: (val, _, idx) => (
<Input
size="small"
value={val}
allowClear
2026-07-30 09:59:44 +08:00
disabled={readOnly}
2026-07-24 11:05:09 +08:00
placeholder="请输入审核意见"
maxLength={80}
onChange={(e) => updateRow(idx, "reviewOpinion", e.target.value)}
/>
),
},
];
2026-07-20 15:40:12 +08:00
return (
<div>
<div className="pf-permission">
2026-07-24 11:05:09 +08:00
<Tag color="blue" className="pf-tag">
内部审核人
</Tag>
2026-07-20 15:40:12 +08:00
<span className="pf-desc">审核修改反馈验证闭环</span>
</div>
2026-07-24 11:05:09 +08:00
<Row gutter={16} style={{ marginBottom: 16 }}>
<Col span={8}>
<Card size="small">
<Typography.Text type="secondary">内审人</Typography.Text>
2026-07-28 13:58:58 +08:00
<div
style={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
<Tooltip
title={(reviewers || []).map((r) => r.personnelName).join("、")}
>
<strong>
{(reviewers || []).map((r) => r.personnelName).join("、") ||
"-"}
</strong>
2026-07-24 15:08:29 +08:00
</Tooltip>
2026-07-24 11:05:09 +08:00
</div>
</Card>
</Col>
<Col span={8}>
<Card size="small">
<Typography.Text type="secondary">审核项</Typography.Text>
<div>
<strong>{rows.length}</strong>
</div>
</Card>
</Col>
<Col span={8}>
<Card size="small">
<Typography.Text type="secondary">问题</Typography.Text>
<div>
<Typography.Text
strong
type={problemCount > 0 ? "warning" : undefined}
>
{problemCount}
</Typography.Text>
</div>
</Card>
</Col>
</Row>
<Table
dataSource={rows}
columns={columns}
rowKey="_key"
pagination={false}
loading={internalReviewDetailLoading}
size="small"
/>
<div style={{ marginTop: 12 }}>
<Typography.Text strong>总体审核意见</Typography.Text>
<TextArea
rows={2}
value={overallOpinion}
2026-07-28 13:58:58 +08:00
allowClear
2026-07-30 09:59:44 +08:00
disabled={readOnly}
2026-07-28 13:58:58 +08:00
placeholder="请输入总体审核意见"
maxLength={120}
2026-07-24 11:05:09 +08:00
onChange={(e) => setOverallOpinion(e.target.value)}
style={{ marginTop: 4 }}
2026-07-20 15:40:12 +08:00
/>
2026-07-24 11:05:09 +08:00
</div>
2026-07-20 15:40:12 +08:00
2026-07-24 11:05:09 +08:00
<Flex justify="space-between" align="center" style={{ marginTop: 10 }}>
<Flex gap={8} align="center">
<Typography.Text type="secondary">内审人独立签字</Typography.Text>
2026-07-24 15:08:29 +08:00
<Select
placeholder="请选择内审人"
size="small"
style={{ width: 140 }}
value={selectedReviewer}
onChange={setSelectedReviewer}
2026-07-30 09:59:44 +08:00
disabled={readOnly}
2026-07-24 15:08:29 +08:00
options={(reviewers || []).map((r) => ({
value: r.personnelId,
label: r.personnelName,
}))}
/>
2026-07-24 11:05:09 +08:00
<Button
size="small"
onClick={handleAssign}
2026-07-28 18:12:06 +08:00
loading={internalReviewAssignLoading || props.safetyEvalBusiness?.internalReviewConfirmLoading}
2026-07-30 09:59:44 +08:00
disabled={isDispatched || !selectedReviewer || readOnly}
2026-07-24 11:05:09 +08:00
>
{isDispatched ? "已下发APP签字" : "下发APP签字"}
</Button>
2026-07-20 15:40:12 +08:00
</Flex>
2026-07-24 11:05:09 +08:00
</Flex>
2026-07-28 18:12:06 +08:00
<Collapse
style={{marginTop: '12px'}}
items={[
{
key: "checklist",
label: "内部审核完整核查清单13项",
children: (
<ol
style={{
margin: 0,
paddingLeft: 20,
fontSize: 13,
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>
),
},
]}
/>
2026-07-30 09:59:44 +08:00
{!readOnly && (
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
<Button
loading={props.safetyEvalBusiness?.internalReviewConfirmLoading}
onClick={() => handleConfirm("not_passed")}
>
保存意见
</Button>
<Button
type="primary"
onClick={() => handleConfirm("passed")}
loading={props.safetyEvalBusiness?.internalReviewConfirmLoading}
>
确认修改并提交技审
</Button>
</Flex>
)}
2026-07-20 15:40:12 +08:00
</div>
);
};
2026-07-20 15:03:26 +08:00
2026-07-24 11:05:09 +08:00
export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(InternalReviewContent);