feat
parent
c96233c0a1
commit
6ad6475911
|
|
@ -13,8 +13,8 @@ module.exports = {
|
||||||
//API_HOST: "https://gbs-gateway.qhdsafety.com",
|
//API_HOST: "https://gbs-gateway.qhdsafety.com",
|
||||||
|
|
||||||
//API_HOST: "http://192.168.0.152",
|
//API_HOST: "http://192.168.0.152",
|
||||||
//API_HOST: "http://192.168.0.150", //太浅
|
API_HOST: "http://192.168.0.150", //太浅
|
||||||
API_HOST: "http://192.168.0.152",
|
//API_HOST: "http://192.168.0.152",
|
||||||
//API_HOST: "http://192.168.0.103", //huwei
|
//API_HOST: "http://192.168.0.103", //huwei
|
||||||
},
|
},
|
||||||
production: {
|
production: {
|
||||||
|
|
|
||||||
|
|
@ -166,3 +166,73 @@ export const evalSurveySaveAttach = declareRequest(
|
||||||
"evalSurveySaveAttachLoading",
|
"evalSurveySaveAttachLoading",
|
||||||
"Post > @/safetyEval/institution/eval-survey/saveSurveyAttach",
|
"Post > @/safetyEval/institution/eval-survey/saveSurveyAttach",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ===== 内部审核 =====
|
||||||
|
|
||||||
|
export const evalInternalReviewDetail = declareRequest(
|
||||||
|
"internalReviewDetailLoading",
|
||||||
|
"Get > /safetyEval/institution/eval-internal-review/detail",
|
||||||
|
"internalReviewDetail: {} | res.data || {}",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalInternalReviewConfirm = declareRequest(
|
||||||
|
"internalReviewConfirmLoading",
|
||||||
|
"Post > @/safetyEval/institution/eval-internal-review/confirm",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalInternalReviewAssign = declareRequest(
|
||||||
|
"internalReviewAssignLoading",
|
||||||
|
"Post > @/safetyEval/institution/eval-internal-review/assign",
|
||||||
|
);
|
||||||
|
|
||||||
|
// ===== 技术审核 =====
|
||||||
|
|
||||||
|
export const evalTechReviewDetail = declareRequest(
|
||||||
|
"techReviewDetailLoading",
|
||||||
|
"Get > /safetyEval/institution/eval-tech-review/detail",
|
||||||
|
"techReviewDetail: {} | res.data || {}",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalTechReviewConfirm = declareRequest(
|
||||||
|
"techReviewConfirmLoading",
|
||||||
|
"Post > @/safetyEval/institution/eval-tech-review/confirm",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalTechReviewAssign = declareRequest(
|
||||||
|
"techReviewAssignLoading",
|
||||||
|
"Post > @/safetyEval/institution/eval-tech-review/assign",
|
||||||
|
);
|
||||||
|
|
||||||
|
// ===== 过程控制 =====
|
||||||
|
|
||||||
|
export const evalProcessControlDetail = declareRequest(
|
||||||
|
"processControlDetailLoading",
|
||||||
|
"Get > /safetyEval/institution/eval-process-control/detail",
|
||||||
|
"processControlDetail: {} | res.data || {}",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalProcessControlConfirm = declareRequest(
|
||||||
|
"processControlConfirmLoading",
|
||||||
|
"Post > @/safetyEval/institution/eval-process-control/confirm",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalProcessControlAssign = declareRequest(
|
||||||
|
"processControlAssignLoading",
|
||||||
|
"Post > @/safetyEval/institution/eval-process-control/assign",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalProcessControlGenerateHandover = declareRequest(
|
||||||
|
"processControlHandoverLoading",
|
||||||
|
"Post > @/safetyEval/institution/eval-process-control/generate-handover-form",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalProcessControlArchive = declareRequest(
|
||||||
|
"processControlArchiveLoading",
|
||||||
|
"Post > @/safetyEval/institution/eval-process-control/archive",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const evalProcessControlFiles = declareRequest(
|
||||||
|
"processControlFilesLoading",
|
||||||
|
"Get > /safetyEval/institution/eval-process-control/process-files",
|
||||||
|
"processControlFiles: [] | res.data || []",
|
||||||
|
);
|
||||||
|
|
@ -1,26 +1,423 @@
|
||||||
import React from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Card, Empty, Table } from "antd";
|
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 ControlContent = () => (
|
const { TextArea } = Input;
|
||||||
<Card title="过程控制审核与归档">
|
const { router } = tools;
|
||||||
<Empty description="过程控制审核须校验前7个节点全部完成并形成有效签字记录后才可办理" />
|
|
||||||
<Table
|
|
||||||
dataSource={[
|
|
||||||
{ id: 1, item: "风险分析", content: "合同前完成并审批签字", result: "完整" },
|
|
||||||
{ id: 2, item: "项目实施计划", content: "人员、进度、设备和分工", result: "完整" },
|
|
||||||
{ id: 3, item: "从业告知与现场", content: "告知盖章件、打卡、检查表、整改复查", result: "完整" },
|
|
||||||
{ id: 4, item: "审核签字", content: "内审、技审修改闭环及独立签字", result: "待技审" },
|
|
||||||
]}
|
|
||||||
columns={[
|
|
||||||
{ title: "程序审核项", dataIndex: "item" },
|
|
||||||
{ title: "核查内容", dataIndex: "content" },
|
|
||||||
{ title: "结果", dataIndex: "result" },
|
|
||||||
]}
|
|
||||||
rowKey="id"
|
|
||||||
pagination={false}
|
|
||||||
className="pf-table-mt"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ControlContent;
|
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);
|
||||||
|
|
@ -1,20 +1,63 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Tag, Button, Card, Table, Select, Input, Flex, Typography, Row, Col } from "antd";
|
import {
|
||||||
|
Tag,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Table,
|
||||||
|
Select,
|
||||||
|
Input,
|
||||||
|
Flex,
|
||||||
|
Typography,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
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 { TextArea } = Input;
|
||||||
|
const { router } = tools;
|
||||||
|
|
||||||
const INITIAL_ROWS = [
|
const CONCLUSION_OPTIONS = [
|
||||||
{ dim: "评价依据", focus: "充分、有效", result: "符合", opinion: "补充地方标准有效版本", feedback: "已验证", feedbackType: "success" },
|
{ value: "conform_to", label: "符合" },
|
||||||
{ dim: "危险辨识", focus: "全面、无遗漏", result: "需修改", opinion: "补充液氨装卸泄漏场景", feedback: "待反馈", feedbackType: "warning" },
|
{ value: "not_conform_to", label: "需修改" },
|
||||||
{ dim: "单元划分", focus: "科学合理", result: "符合", opinion: "划分合理", feedback: "—", feedbackType: "default" },
|
|
||||||
{ dim: "评价方法", focus: "适用、过程正确", result: "符合", opinion: "方法适用", feedback: "—", feedbackType: "default" },
|
|
||||||
{ dim: "对策措施", focus: "针对性、可操作性", result: "需修改", opinion: "细化报警联锁措施", feedback: "已验证", feedbackType: "success" },
|
|
||||||
{ dim: "评价结论", focus: "客观、正确", result: "符合", opinion: "结论客观", feedback: "—", feedbackType: "default" },
|
|
||||||
{ dim: "报告规范", focus: "格式、文字、数据逻辑", result: "符合", opinion: "统一图表编号", feedback: "—", feedbackType: "default" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const InternalReviewContent = () => {
|
const InternalReviewContent = (props) => {
|
||||||
const [rows, setRows] = useState(INITIAL_ROWS);
|
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("");
|
||||||
|
|
||||||
|
const fetchDetail = () => {
|
||||||
|
props.evalInternalReviewDetail({ projectId });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectId) fetchDetail();
|
||||||
|
}, [projectId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (reviewItems) {
|
||||||
|
setRows(
|
||||||
|
reviewItems.map((item) => ({
|
||||||
|
...item,
|
||||||
|
_key: item.id || item.reviewDimensionCode,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (internalReview?.opinionContent) {
|
||||||
|
setOverallOpinion(internalReview.opinionContent);
|
||||||
|
}
|
||||||
|
}, [internalReviewDetail]);
|
||||||
|
|
||||||
const updateRow = (index, field, value) => {
|
const updateRow = (index, field, value) => {
|
||||||
const next = [...rows];
|
const next = [...rows];
|
||||||
|
|
@ -22,74 +65,179 @@ const InternalReviewContent = () => {
|
||||||
setRows(next);
|
setRows(next);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const reviewerName = internalReview?.reviewerName || "";
|
||||||
|
const isDispatched =
|
||||||
|
internalReview?.reviewerSignatureTaskStatus === "dispatched";
|
||||||
|
const problemCount = rows.filter(
|
||||||
|
(r) => r.reviewConclusion === "not_conform_to",
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const handleAssign = async () => {
|
||||||
|
if (!projectId) return;
|
||||||
|
const res = await props.evalInternalReviewAssign({ projectId });
|
||||||
|
if (res?.success !== false) {
|
||||||
|
message.success("已下发签字任务");
|
||||||
|
fetchDetail();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = async (resultCode) => {
|
||||||
|
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" ? "已提交" : "已保存",
|
||||||
|
);
|
||||||
|
fetchDetail();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: "审核维度", dataIndex: "reviewDimensionName", width: 100 },
|
||||||
|
{ title: "审核重点", dataIndex: "reviewFocus", width: 160 },
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
width: 180,
|
||||||
|
render: (val, _, idx) => (
|
||||||
|
<Input
|
||||||
|
size="small"
|
||||||
|
value={val}
|
||||||
|
allowClear
|
||||||
|
placeholder="请输入审核意见"
|
||||||
|
maxLength={80}
|
||||||
|
onChange={(e) => updateRow(idx, "reviewOpinion", e.target.value)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="pf-permission">
|
<div className="pf-permission">
|
||||||
<Tag color="blue" className="pf-tag">内部审核人</Tag>
|
<Tag color="blue" className="pf-tag">
|
||||||
|
内部审核人
|
||||||
|
</Tag>
|
||||||
<span className="pf-desc">审核、修改、反馈、验证闭环</span>
|
<span className="pf-desc">审核、修改、反馈、验证闭环</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||||||
<Col span={6}><Card size="small"><Typography.Text type="secondary">审核版本</Typography.Text><div><strong>V1.3</strong></div></Card></Col>
|
<Col span={8}>
|
||||||
<Col span={6}><Card size="small"><Typography.Text type="secondary">内审人</Typography.Text><div><strong>王丽萍</strong></div></Card></Col>
|
<Card size="small">
|
||||||
<Col span={6}><Card size="small"><Typography.Text type="secondary">审核项</Typography.Text><div><strong>7项</strong></div></Card></Col>
|
<Typography.Text type="secondary">内审人</Typography.Text>
|
||||||
<Col span={6}><Card size="small"><Typography.Text type="secondary">问题</Typography.Text><div><Typography.Text strong type="warning">2项</Typography.Text></div></Card></Col>
|
<div>
|
||||||
</Row>
|
<strong>{reviewerName || "-"}</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
|
<Table
|
||||||
dataSource={rows}
|
dataSource={rows}
|
||||||
columns={[
|
columns={columns}
|
||||||
{ title: "审核维度", dataIndex: "dim", width: 100 },
|
rowKey="_key"
|
||||||
{ title: "审核重点", dataIndex: "focus", width: 160 },
|
pagination={false}
|
||||||
{
|
loading={internalReviewDetailLoading}
|
||||||
title: "结论", dataIndex: "result", width: 120,
|
size="small"
|
||||||
render: (val, _, idx) => (
|
/>
|
||||||
<Select value={val} size="small" style={{ width: 100 }} onChange={(v) => updateRow(idx, "result", v)}>
|
|
||||||
<Select.Option value="符合">符合</Select.Option>
|
<div style={{ marginTop: 12 }}>
|
||||||
<Select.Option value="需修改">需修改</Select.Option>
|
<Typography.Text strong>总体审核意见</Typography.Text>
|
||||||
</Select>
|
<TextArea
|
||||||
),
|
rows={2}
|
||||||
},
|
value={overallOpinion}
|
||||||
{
|
onChange={(e) => setOverallOpinion(e.target.value)}
|
||||||
title: "审核意见", dataIndex: "opinion", width: 180,
|
style={{ marginTop: 4 }}
|
||||||
render: (val, _, idx) => (
|
|
||||||
<Input size="small" value={val} onChange={(e) => updateRow(idx, "opinion", e.target.value)} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "修改反馈", dataIndex: "feedback", width: 120,
|
|
||||||
render: (val, record) => {
|
|
||||||
if (record.feedbackType === "success") return <Tag color="success">已验证</Tag>;
|
|
||||||
if (record.feedbackType === "warning") return <Tag color="warning">待反馈</Tag>;
|
|
||||||
return <span style={{ color: "#999" }}>—</span>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
rowKey="dim"
|
|
||||||
pagination={false}
|
|
||||||
size="small"
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ marginTop: 12 }}>
|
<Flex justify="space-between" align="center" style={{ marginTop: 10 }}>
|
||||||
<Typography.Text strong>总体审核意见</Typography.Text>
|
<Flex gap={8} align="center">
|
||||||
<TextArea rows={2} defaultValue="完成危险辨识修改后通过内部审核。" style={{ marginTop: 4 }} />
|
<Typography.Text type="secondary">内审人独立签字</Typography.Text>
|
||||||
</div>
|
<strong>{reviewerName}</strong>
|
||||||
|
<Button
|
||||||
<Flex justify="space-between" align="center" style={{ marginTop: 10 }}>
|
size="small"
|
||||||
<Flex gap={8} align="center">
|
onClick={handleAssign}
|
||||||
<Typography.Text type="secondary">内审人独立签字</Typography.Text>
|
loading={internalReviewAssignLoading}
|
||||||
<strong>王丽萍</strong>
|
disabled={isDispatched}
|
||||||
<Button size="small">下发APP签字</Button>
|
>
|
||||||
</Flex>
|
{isDispatched ? "已下发APP签字" : "下发APP签字"}
|
||||||
</Flex>
|
</Button>
|
||||||
|
|
||||||
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
|
|
||||||
<Button>保存意见</Button>
|
|
||||||
<Button type="primary">确认修改并提交技审</Button>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InternalReviewContent;
|
export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(InternalReviewContent);
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,10 @@ const RiskAnalysisContent = (props) => {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
|
|
||||||
const { analysisDate, ...rest } = values;
|
const { analysisDate, ...rest } = values;
|
||||||
|
if (!rest.participants?.length) {
|
||||||
|
message.warning("请选择风险分析人员");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const res = await evalRiskAnalysisSave({
|
const res = await evalRiskAnalysisSave({
|
||||||
...rest,
|
...rest,
|
||||||
projectId,
|
projectId,
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,14 @@ const TeamContent = (props) => {
|
||||||
if (selectedPersons.length === 0) { message.warning("请至少选择1名人员"); return; }
|
if (selectedPersons.length === 0) { message.warning("请至少选择1名人员"); return; }
|
||||||
const role = ROLE_DEFINITIONS.find((r) => r.value === selectedRole);
|
const role = ROLE_DEFINITIONS.find((r) => r.value === selectedRole);
|
||||||
if (role?.unique && selectedPersons.length > 1) { message.warning("该角色为单人唯一角色,只能选择1名人员"); return; }
|
if (role?.unique && selectedPersons.length > 1) { message.warning("该角色为单人唯一角色,只能选择1名人员"); return; }
|
||||||
|
// 唯一角色校验:检查是否已有人担任该角色
|
||||||
|
if (role?.unique) {
|
||||||
|
const existing = teamMembers.some((m) => m.roles.some((r) => r.role === selectedRole));
|
||||||
|
if (existing) {
|
||||||
|
message.warning(`"${role.name}"已配置,不可重复添加`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newEntries = selectedPersons.map((personId) => {
|
const newEntries = selectedPersons.map((personId) => {
|
||||||
const p = participants.find((item) => item.id === personId);
|
const p = participants.find((item) => item.id === personId);
|
||||||
|
|
@ -110,6 +118,15 @@ const TeamContent = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
|
const allRoles = teamMembers.flatMap((m) => m.roles.map((r) => r.role));
|
||||||
|
if (!allRoles.includes("PROJECT_LEAD")) {
|
||||||
|
message.warning("请先配置项目负责人(项目组组长)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!allRoles.includes("PROCESS_CONTROL_LEAD")) {
|
||||||
|
message.warning("请先配置过程控制负责人");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const payload = teamMembers.map((m) => ({
|
const payload = teamMembers.map((m) => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
projectId,
|
projectId,
|
||||||
|
|
@ -124,6 +141,7 @@ const TeamContent = (props) => {
|
||||||
if (res?.success !== false) {
|
if (res?.success !== false) {
|
||||||
message.success("保存成功");
|
message.success("保存成功");
|
||||||
fetchData();
|
fetchData();
|
||||||
|
props.getDetail();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -134,10 +152,16 @@ const TeamContent = (props) => {
|
||||||
{ title: "部门/岗位", dataIndex: "deptName", width: 200, ellipsis: true },
|
{ title: "部门/岗位", dataIndex: "deptName", width: 200, ellipsis: true },
|
||||||
{
|
{
|
||||||
title: "项目角色", dataIndex: "roles", width: 350,
|
title: "项目角色", dataIndex: "roles", width: 350,
|
||||||
render: (roles) => (
|
render: (roles, record) => (
|
||||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
|
<div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
|
||||||
{roles.map((r) => (
|
{roles.map((r) => (
|
||||||
<Tag key={r.role} color="blue">
|
<Tag key={r.role} color="blue" closable onClose={() => {
|
||||||
|
setTeamMembers((prev) => prev.map((m) => {
|
||||||
|
if (m.personnelId !== record.personnelId) return m;
|
||||||
|
const updated = m.roles.filter((role) => role.role !== r.role);
|
||||||
|
return updated.length > 0 ? { ...m, roles: updated } : null;
|
||||||
|
}).filter(Boolean));
|
||||||
|
}}>
|
||||||
{ROLE_DEFINITIONS.find((d) => d.value === r.role)?.name || r.role}
|
{ROLE_DEFINITIONS.find((d) => d.value === r.role)?.name || r.role}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
|
|
@ -216,7 +240,7 @@ const TeamContent = (props) => {
|
||||||
<Flex justify="flex-end" gap={8} className="pf-bottom-actions">
|
<Flex justify="flex-end" gap={8} className="pf-bottom-actions">
|
||||||
|
|
||||||
<Button>生成项目组任命文件</Button>
|
<Button>生成项目组任命文件</Button>
|
||||||
<Button type="primary" onClick={handleSave}>确认项目组</Button>
|
<Button type="primary" loading={memberSaveLoading} onClick={handleSave}>确认项目组</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Modal title="添加项目人员与角色" open={modalOpen} onCancel={() => setModalOpen(false)} width={700}
|
<Modal title="添加项目人员与角色" open={modalOpen} onCancel={() => setModalOpen(false)} width={700}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,46 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Tag, Button, Card, Row, Col, Table, Select, Input, Flex, Typography } from "antd";
|
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 { TextArea } = Input;
|
||||||
|
const { router } = tools;
|
||||||
|
|
||||||
const INITIAL_ROWS = [
|
const CONCLUSION_OPTIONS = [
|
||||||
{ dim: "资料有效性", focus: "现场资料和检测报告齐全有效", result: "符合", opinion: "资料有效" },
|
{ value: "conform_to", label: "符合" },
|
||||||
{ dim: "危险辨识", focus: "辨识充分、规律准确", result: "符合", opinion: "辨识充分" },
|
{ value: "not_conform_to", label: "需修改" },
|
||||||
{ dim: "评价方法", focus: "选型合理、计算正确", result: "需修改", opinion: "补充事故模拟参数来源" },
|
|
||||||
{ dim: "对策措施", focus: "针对性和可行性", result: "符合", opinion: "措施可行" },
|
|
||||||
{ dim: "评价结论", focus: "结论正确、反映风险", result: "符合", opinion: "结论正确" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const TechnicalReviewContent = () => {
|
const TechnicalReviewContent = (props) => {
|
||||||
const [rows, setRows] = useState(INITIAL_ROWS);
|
const projectId = router.query?.id;
|
||||||
|
const { techReviewDetail } = props.safetyEvalBusiness || {};
|
||||||
|
const { techReview, reviewers, reviewItems } = techReviewDetail || {};
|
||||||
|
|
||||||
|
const [rows, setRows] = useState([]);
|
||||||
|
const [overallOpinion, setOverallOpinion] = useState("");
|
||||||
|
|
||||||
|
const fetchDetail = () => {
|
||||||
|
props.evalTechReviewDetail({ projectId });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectId) fetchDetail();
|
||||||
|
}, [projectId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (reviewItems) {
|
||||||
|
setRows(
|
||||||
|
reviewItems.map((item) => ({
|
||||||
|
...item,
|
||||||
|
_key: item.id || item.reviewDimensionCode,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (techReview?.opinionContent) {
|
||||||
|
setOverallOpinion(techReview.opinionContent);
|
||||||
|
}
|
||||||
|
}, [techReviewDetail]);
|
||||||
|
|
||||||
const updateRow = (index, field, value) => {
|
const updateRow = (index, field, value) => {
|
||||||
const next = [...rows];
|
const next = [...rows];
|
||||||
|
|
@ -20,68 +48,166 @@ const TechnicalReviewContent = () => {
|
||||||
setRows(next);
|
setRows(next);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const reviewerName = techReview?.reviewerName || "";
|
||||||
|
const isDispatched = techReview?.reviewerSignatureTaskStatus !== "non_dispatched";
|
||||||
|
const staffMatch = techReview?.staffMatchCode === 1 ? "满足" : "不满足";
|
||||||
|
const inBizScope = techReview?.inBizScopeFlag === 1 ? "在范围内" : "不在范围内";
|
||||||
|
|
||||||
|
const handleAssign = async () => {
|
||||||
|
if (!projectId) return;
|
||||||
|
const res = await props.evalTechReviewAssign({ projectId });
|
||||||
|
if (res?.success !== false) {
|
||||||
|
message.success("已下发签字任务");
|
||||||
|
fetchDetail();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = async (resultCode) => {
|
||||||
|
if (!projectId) return;
|
||||||
|
const payload = {
|
||||||
|
id: techReview?.id,
|
||||||
|
projectId,
|
||||||
|
resultCode,
|
||||||
|
opinionContent: overallOpinion,
|
||||||
|
reviewItems: rows.map((r) => ({
|
||||||
|
bizType: "eval_tech_review",
|
||||||
|
bizId: techReview?.id,
|
||||||
|
reviewDimensionCode: r.reviewDimensionCode,
|
||||||
|
reviewDimensionName: r.reviewDimensionName,
|
||||||
|
reviewFocus: r.reviewFocus,
|
||||||
|
reviewConclusion: r.reviewConclusion,
|
||||||
|
reviewOpinion: r.reviewOpinion,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
const res = await props.evalTechReviewConfirm(payload);
|
||||||
|
if (res?.success !== false) {
|
||||||
|
message.success(resultCode === "1" ? "已确认技审并提交过程控制" : "已保存");
|
||||||
|
fetchDetail();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ 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"
|
||||||
|
placeholder="请输入技术意见"
|
||||||
|
maxLength={80}
|
||||||
|
value={val}
|
||||||
|
onChange={(e) => updateRow(idx, "reviewOpinion", e.target.value)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="pf-permission">
|
<div className="pf-permission">
|
||||||
<Tag color="blue" className="pf-tag">技术负责人</Tag>
|
<Tag color="blue" className="pf-tag">
|
||||||
|
技术负责人
|
||||||
|
</Tag>
|
||||||
<span className="pf-desc">技术负责人独立审核技术路线和结论</span>
|
<span className="pf-desc">技术负责人独立审核技术路线和结论</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||||||
|
<Col span={8}>
|
||||||
|
<Card size="small">
|
||||||
|
<Typography.Text type="secondary">技术负责人</Typography.Text>
|
||||||
|
<div>
|
||||||
|
<strong>{reviewerName || "-"}</strong>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Card size="small">
|
||||||
|
<Typography.Text type="secondary">专业匹配</Typography.Text>
|
||||||
|
<div>
|
||||||
|
<Typography.Text strong type={techReview?.staffMatchCode === 1 ? "success" : "warning"}>
|
||||||
|
{staffMatch}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Card size="small">
|
||||||
|
<Typography.Text type="secondary">资质范围</Typography.Text>
|
||||||
|
<div>
|
||||||
|
<Typography.Text strong type={techReview?.inBizScopeFlag === 1 ? "success" : "warning"}>
|
||||||
|
{inBizScope}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
<Table
|
||||||
<Col span={6}><Card size="small"><Typography.Text type="secondary">审核版本</Typography.Text><div><strong>V1.4</strong></div></Card></Col>
|
dataSource={rows}
|
||||||
<Col span={6}><Card size="small"><Typography.Text type="secondary">技术负责人</Typography.Text><div><strong>陈志强</strong></div></Card></Col>
|
columns={columns}
|
||||||
<Col span={6}><Card size="small"><Typography.Text type="secondary">专业匹配</Typography.Text><div><Typography.Text strong type="success">化工安全</Typography.Text></div></Card></Col>
|
rowKey="_key"
|
||||||
<Col span={6}><Card size="small"><Typography.Text type="secondary">备案校验</Typography.Text><div><Typography.Text strong type="success">有效</Typography.Text></div></Card></Col>
|
pagination={false}
|
||||||
</Row>
|
size="small"
|
||||||
|
/>
|
||||||
|
|
||||||
<Table
|
<div style={{ marginTop: 12 }}>
|
||||||
dataSource={rows}
|
<Typography.Text strong>技术审核意见</Typography.Text>
|
||||||
columns={[
|
<TextArea
|
||||||
{ title: "审核维度", dataIndex: "dim", width: 120 },
|
rows={2}
|
||||||
{ title: "技术审核重点", dataIndex: "focus", width: 200 },
|
value={overallOpinion}
|
||||||
{
|
onChange={(e) => setOverallOpinion(e.target.value)}
|
||||||
title: "结论", dataIndex: "result", width: 120,
|
style={{ marginTop: 4 }}
|
||||||
render: (val, _, idx) => (
|
|
||||||
<Select value={val} size="small" style={{ width: 100 }} onChange={(v) => updateRow(idx, "result", v)}>
|
|
||||||
<Select.Option value="符合">符合</Select.Option>
|
|
||||||
<Select.Option value="需修改">需修改</Select.Option>
|
|
||||||
</Select>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "技术意见", dataIndex: "opinion",
|
|
||||||
render: (val, _, idx) => (
|
|
||||||
<Input size="small" value={val} onChange={(e) => updateRow(idx, "opinion", e.target.value)} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
rowKey="dim"
|
|
||||||
pagination={false}
|
|
||||||
size="small"
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ marginTop: 12 }}>
|
<Flex justify="space-between" align="center" style={{ marginTop: 10 }}>
|
||||||
<Typography.Text strong>技术审核意见</Typography.Text>
|
<Flex gap={8} align="center">
|
||||||
<TextArea rows={2} defaultValue="补充定量评价参数来源后通过技术审核。" style={{ marginTop: 4 }} />
|
<Typography.Text type="secondary">技术负责人签字</Typography.Text>
|
||||||
</div>
|
<strong>{reviewerName}</strong>
|
||||||
|
<Button
|
||||||
<Flex justify="space-between" align="center" style={{ marginTop: 10 }}>
|
size="small"
|
||||||
<Flex gap={8} align="center">
|
onClick={handleAssign}
|
||||||
<Typography.Text type="secondary">技术负责人签字</Typography.Text>
|
loading={props.safetyEvalBusiness?.techReviewAssignLoading}
|
||||||
<strong>陈志强</strong>
|
disabled={isDispatched}
|
||||||
<Button size="small">下发APP签字</Button>
|
>
|
||||||
</Flex>
|
{isDispatched ? "已下发APP签字" : "下发APP签字"}
|
||||||
</Flex>
|
</Button>
|
||||||
|
|
||||||
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
|
|
||||||
<Button>保存意见</Button>
|
|
||||||
<Button>查看修改反馈</Button>
|
|
||||||
<Button type="primary">确认技审并提交过程控制</Button>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
|
||||||
|
<Button onClick={() => handleConfirm("2")}>保存意见</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => handleConfirm("1")}
|
||||||
|
loading={props.safetyEvalBusiness?.techReviewConfirmLoading}
|
||||||
|
>
|
||||||
|
确认技审并提交过程控制
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TechnicalReviewContent;
|
export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(TechnicalReviewContent);
|
||||||
Loading…
Reference in New Issue