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

292 lines
8.3 KiB
JavaScript
Raw Normal View History

2026-07-24 11:05:09 +08:00
import React, { useState, useEffect } from "react";
2026-07-24 15:08:29 +08:00
import {
Tag,
Button,
Card,
Row,
Col,
Table,
Select,
Input,
Flex,
Typography,
message,
Tooltip,
} from "antd";
2026-07-24 11:05:09 +08:00
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-24 11:05:09 +08:00
const TechnicalReviewContent = (props) => {
const projectId = router.query?.id;
const { techReviewDetail } = props.safetyEvalBusiness || {};
const { techReview, reviewers, reviewItems } = techReviewDetail || {};
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.evalTechReviewDetail({ projectId }).then((res) => {
if (res?.success) {
const co = res.data?.signTaskPersonCo;
if (co?.signerPersonnelId) {
setSelectedReviewer(co.signerPersonnelId);
}
}
});
2026-07-24 11:05:09 +08:00
};
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]);
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 15:08:29 +08:00
const isDispatched = techReview?.reviewerSignatureTaskStatus === "dispatched";
2026-07-24 11:05:09 +08:00
const staffMatch = techReview?.staffMatchCode === 1 ? "满足" : "不满足";
2026-07-24 15:08:29 +08:00
const inBizScope =
techReview?.inBizScopeFlag === 1 ? "在范围内" : "不在范围内";
2026-07-24 11:05:09 +08:00
const handleAssign = async () => {
if (!projectId) return;
2026-07-28 13:58:58 +08:00
await handleConfirm("2");
2026-07-24 15:08:29 +08:00
const res = await props.evalTechReviewAssign({
projectId,
signTaskPersonCmd: {
signerPersonnelId: selectedReviewer,
signerName:
reviewers?.find((r) => r.personnelId === selectedReviewer)
?.personnelName || "",
},
});
2026-07-24 11:05:09 +08:00
if (res?.success !== false) {
message.success("已下发签字任务");
}
};
const handleConfirm = async (resultCode) => {
if (!projectId) return;
2026-07-28 13:58:58 +08:00
return new Promise(async (resolve, reject) => {
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) {
resolve(res);
message.success(
resultCode === "1" ? "已确认技审并提交过程控制" : "已保存",
);
if (resultCode === "1") {
props.getDetail?.();
}
} else {
reject(res);
}
});
2026-07-24 11:05:09 +08:00
};
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"
2026-07-28 13:58:58 +08:00
style={{ width: 130 }}
allowClear
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",
render: (val, _, idx) => (
<Input
size="small"
placeholder="请输入技术意见"
maxLength={80}
value={val}
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-24 15:08:29 +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>
</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>
2026-07-24 15:08:29 +08:00
<Typography.Text
strong
type={techReview?.staffMatchCode === 1 ? "success" : "warning"}
>
2026-07-24 11:05:09 +08:00
{staffMatch}
</Typography.Text>
</div>
</Card>
</Col>
<Col span={8}>
<Card size="small">
<Typography.Text type="secondary">资质范围</Typography.Text>
<div>
2026-07-24 15:08:29 +08:00
<Typography.Text
strong
type={techReview?.inBizScopeFlag === 1 ? "success" : "warning"}
>
2026-07-24 11:05:09 +08:00
{inBizScope}
</Typography.Text>
</div>
</Card>
</Col>
</Row>
<Table
dataSource={rows}
columns={columns}
rowKey="_key"
pagination={false}
2026-07-24 15:08:29 +08:00
loading={props.safetyEvalBusiness?.techReviewDetailLoading}
2026-07-24 11:05:09 +08:00
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
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: 160 }}
value={selectedReviewer}
onChange={setSelectedReviewer}
options={(reviewers || []).map((r) => ({
value: r.personnelId,
label: r.personnelName,
}))}
/>
2026-07-24 11:05:09 +08:00
<Button
size="small"
onClick={handleAssign}
loading={props.safetyEvalBusiness?.techReviewAssignLoading}
2026-07-24 15:08:29 +08:00
disabled={isDispatched || !selectedReviewer}
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-20 15:40:12 +08:00
2026-07-24 11:05:09 +08:00
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
2026-07-28 13:58:58 +08:00
<Button
loading={props.safetyEvalBusiness?.techReviewConfirmLoading}
onClick={() => handleConfirm("2")}
>
保存意见
</Button>
2026-07-24 11:05:09 +08:00
<Button
type="primary"
onClick={() => handleConfirm("1")}
loading={props.safetyEvalBusiness?.techReviewConfirmLoading}
>
确认技审并提交过程控制
</Button>
</Flex>
2026-07-20 15:40:12 +08:00
</div>
);
};
2026-07-20 15:03:26 +08:00
2026-07-24 15:08:29 +08:00
export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(TechnicalReviewContent);