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

423 lines
13 KiB
JavaScript
Raw Normal View History

2026-07-24 11:05:09 +08:00
import React, { useState, useEffect } from "react";
import { Tag, Button, Card, Row, Col, Table, Select, Input, Flex, Typography, message } from "antd";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { tools } from "@cqsjjb/jjb-common-lib";
const { TextArea } = Input;
const { router } = tools;
const CONCLUSION_OPTIONS = [
{ value: "conform_to", label: "符合" },
{ value: "not_conform_to", label: "需修改" },
];
const ControlContent = (props) => {
const projectId = router.query?.id;
const { processControlDetail, processControlFiles } = props.safetyEvalBusiness || {};
const { processControl, controllers, reviewItems } = processControlDetail || {};
// 当前步骤1-过程控制审核 2-机构负责人签发 3-项目归档
const [step, setStep] = useState(1);
// 过程控制审核
const [rows, setRows] = useState([]);
const [opinionContent, setOpinionContent] = useState("");
// 项目归档
const [files, setFiles] = useState([]);
const fetchDetail = () => {
props.evalProcessControlDetail({ projectId });
};
const fetchFiles = () => {
props.evalProcessControlFiles({ projectId });
};
useEffect(() => {
if (projectId) {
fetchDetail();
fetchFiles();
}
}, [projectId]);
useEffect(() => {
if (reviewItems) {
setRows(
reviewItems.map((item) => ({
...item,
_key: item.id || item.reviewDimensionCode,
})),
);
}
if (processControl?.opinionContent) {
setOpinionContent(processControl.opinionContent);
}
}, [processControlDetail]);
useEffect(() => {
if (processControlFiles) {
setFiles(processControlFiles);
}
}, [processControlFiles]);
const updateRow = (index, field, value) => {
const next = [...rows];
next[index] = { ...next[index], [field]: value };
setRows(next);
};
const controllerName = processControl?.controllerName || "";
const isDispatched = processControl?.reviewerSignatureTaskStatus !== "non_dispatched";
const isSignatureCompleted = processControl?.reviewerSignatureTaskStatus === "completed";
const hasHandoverForm = !!processControl?.handoverFormUrl;
const problemCount = rows.filter((r) => r.reviewConclusion === "not_conform_to").length;
const canArchive = isSignatureCompleted && hasHandoverForm;
// 过程控制签字提交confirm
const handleConfirm = async (resultCode) => {
if (!projectId) return;
const payload = {
id: processControl?.id,
projectId,
resultCode,
opinionContent,
reviewItems: rows.map((r) => ({
bizType: "eval_process_control",
bizId: processControl?.id,
reviewDimensionCode: r.reviewDimensionCode,
reviewDimensionName: r.reviewDimensionName,
reviewFocus: r.reviewFocus,
reviewConclusion: r.reviewConclusion,
reviewOpinion: r.reviewOpinion,
})),
};
const res = await props.evalProcessControlConfirm(payload);
if (res?.success !== false) {
message.success(resultCode === "passed" ? "已提交通过审核" : "已提交退回修改");
fetchDetail();
}
};
// 下发签字任务assign
const handleAssign = async () => {
if (!projectId) return;
const res = await props.evalProcessControlAssign({ projectId });
if (res?.success !== false) {
message.success("已下发签字任务");
fetchDetail();
}
};
// 生成归档交接单
const handleGenerateHandover = async () => {
if (!projectId) return;
const res = await props.evalProcessControlGenerateHandover({ projectId });
if (res?.success !== false) {
message.success("归档交接单已生成");
fetchDetail();
}
};
// 归档项目并入库
const handleArchive = async () => {
if (!projectId) return;
const res = await props.evalProcessControlArchive({ projectId });
if (res?.success !== false) {
message.success("项目已归档入库");
fetchDetail();
}
};
const reviewColumns = [
{ title: "审核维度", dataIndex: "reviewDimensionName", width: 120 },
{ title: "审核重点", dataIndex: "reviewFocus", width: 200 },
{
title: "结论",
dataIndex: "reviewConclusion",
width: 120,
render: (val, _, idx) => (
<Select
value={val}
size="small"
style={{ width: 100 }}
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",
render: (val, _, idx) => (
<Input
size="small"
value={val}
onChange={(e) => updateRow(idx, "reviewOpinion", e.target.value)}
/>
),
},
];
const fileColumns = [
{ title: "流程名称", dataIndex: "processName", width: 100 },
{ title: "条目名称", dataIndex: "itemName", width: 120 },
{ title: "文件名称", dataIndex: "materialName", width: 200 },
{
title: "文件地址",
dataIndex: "projectDocUrl",
width: 200,
render: (url) =>
url ? (
<a href={url} target="_blank" rel="noreferrer">
查看文件
</a>
) : (
"-"
),
},
{ title: "资料类型", dataIndex: "materialType", width: 100 },
];
const STEP_ITEMS = [
{ key: 1, label: "过程控制审核" },
{ key: 2, label: "机构负责人签发" },
{ key: 3, label: "项目归档" },
];
return (
<div>
<Flex gap={0} style={{ marginBottom: 16, borderBottom: "1px solid #e8e8e8" }}>
{STEP_ITEMS.map((item) => (
<Button
key={item.key}
type="text"
onClick={() => setStep(item.key)}
style={{
minWidth: 128,
borderBottom: step === item.key ? "3px solid #1677ff" : "3px solid transparent",
borderRadius: 0,
fontWeight: step === item.key ? 700 : 400,
color: step === item.key ? "#1677ff" : undefined,
background: step === item.key ? "#e6f4ff" : "transparent",
}}
>
{item.label}
</Button>
))}
</Flex>
{step === 1 && (
<Card
title="过程控制审核"
style={{ marginBottom: 16 }}
styles={{ body: { paddingBottom: 0 } }}
>
<div className="pf-permission">
<Tag color="blue" className="pf-tag">
过程控制负责人
</Tag>
<span className="pf-desc">过程控制负责人核验前7节点完成及签字记录</span>
</div>
<Row gutter={16} style={{ marginBottom: 16 }}>
<Col span={8}>
<Card size="small">
<Typography.Text type="secondary">过程控制负责人</Typography.Text>
<div>
<strong>{controllerName || "-"}</strong>
</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={reviewColumns}
rowKey="_key"
pagination={false}
size="small"
/>
<div style={{ margin: "12px 0" }}>
<Typography.Text strong>总体过程控制意见</Typography.Text>
<TextArea
rows={2}
value={opinionContent}
onChange={(e) => setOpinionContent(e.target.value)}
style={{ marginTop: 4 }}
/>
</div>
<Flex justify="space-between" align="center" style={{ marginTop: 10, paddingBottom: 16 }}>
<Flex gap={8} align="center">
<Typography.Text type="secondary">过程控制负责人签字</Typography.Text>
<strong>{controllerName}</strong>
<Button
size="small"
onClick={handleAssign}
loading={props.safetyEvalBusiness?.processControlAssignLoading}
disabled={isDispatched}
>
{isDispatched ? "已下发APP签字" : "下发APP签字"}
</Button>
</Flex>
<Flex gap={8}>
<Button onClick={() => handleConfirm("not_passed")}>退回修改</Button>
<Button
type="primary"
onClick={() => handleConfirm("passed")}
loading={props.safetyEvalBusiness?.processControlConfirmLoading}
>
通过
</Button>
</Flex>
</Flex>
</Card>
)}
{step === 2 && (
<Card
title="机构负责人签发"
style={{ marginBottom: 16 }}
styles={{ body: { paddingBottom: 0 } }}
>
<div className="pf-permission">
<Tag color="blue" className="pf-tag">
评价机构负责人
</Tag>
<span className="pf-desc">机构负责人终审并签发确认项目成果</span>
</div>
<Row gutter={16} style={{ marginBottom: 16 }}>
{controllers?.map((c) => (
<Col span={8} key={c.id || c.personnelId}>
<Card size="small">
<Typography.Text type="secondary">机构负责人</Typography.Text>
<div>
<strong>{c.personnelName}</strong>
</div>
{c.deptName && (
<div>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{c.deptName}
</Typography.Text>
</div>
)}
</Card>
</Col>
))}
{(!controllers || controllers.length === 0) && (
<Col span={24}>
<Typography.Text type="secondary">暂无机构负责人信息</Typography.Text>
</Col>
)}
</Row>
<Flex justify="flex-end" style={{ paddingBottom: 16 }}>
<Button
type="primary"
onClick={handleAssign}
loading={props.safetyEvalBusiness?.processControlAssignLoading}
>
终审并签发
</Button>
</Flex>
</Card>
)}
{step === 3 && (
<Card
title="项目归档"
style={{ marginBottom: 16 }}
styles={{ body: { paddingBottom: 0 } }}
>
<div className="pf-permission">
<Tag color="blue" className="pf-tag">
项目归档
</Tag>
<span className="pf-desc">项目过程文件清单确认签字完成后归档入库</span>
</div>
<Table
dataSource={files}
columns={fileColumns}
rowKey={(record) => `${record.processName}-${record.materialName}-${record.itemName}`}
pagination={false}
size="small"
/>
<Flex justify="space-between" align="center" style={{ marginTop: 12, paddingBottom: 16 }}>
<Flex gap={8} align="center">
{!hasHandoverForm && (
<Button
size="small"
onClick={handleGenerateHandover}
loading={props.safetyEvalBusiness?.processControlHandoverLoading}
>
生成归档交接单
</Button>
)}
{hasHandoverForm && (
<Typography.Text type="success">
归档交接单已生成
{processControl?.handoverFormUrl && (
<a
href={processControl.handoverFormUrl}
target="_blank"
rel="noreferrer"
style={{ marginLeft: 8 }}
>
查看
</a>
)}
</Typography.Text>
)}
</Flex>
<Button
type="primary"
onClick={handleArchive}
loading={props.safetyEvalBusiness?.processControlArchiveLoading}
disabled={!canArchive}
title={
!canArchive
? !isSignatureCompleted
? "需签字完成后才可归档"
: "需生成归档交接单后才可归档"
: ""
}
>
双方签字并确认入库
</Button>
</Flex>
</Card>
)}
</div>
);
};
export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(ControlContent);