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";
|
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("");
|
|
|
|
|
|
|
|
|
|
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]);
|
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 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)}
|
|
|
|
|
/>
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
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>
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<Table
|
|
|
|
|
dataSource={rows}
|
|
|
|
|
columns={columns}
|
|
|
|
|
rowKey="_key"
|
|
|
|
|
pagination={false}
|
|
|
|
|
size="small"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<div style={{ marginTop: 12 }}>
|
|
|
|
|
<Typography.Text strong>技术审核意见</Typography.Text>
|
|
|
|
|
<TextArea
|
|
|
|
|
rows={2}
|
|
|
|
|
value={overallOpinion}
|
|
|
|
|
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>
|
|
|
|
|
<strong>{reviewerName}</strong>
|
|
|
|
|
<Button
|
|
|
|
|
size="small"
|
|
|
|
|
onClick={handleAssign}
|
|
|
|
|
loading={props.safetyEvalBusiness?.techReviewAssignLoading}
|
|
|
|
|
disabled={isDispatched}
|
|
|
|
|
>
|
|
|
|
|
{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 }}>
|
|
|
|
|
<Button onClick={() => handleConfirm("2")}>保存意见</Button>
|
|
|
|
|
<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 11:05:09 +08:00
|
|
|
export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(TechnicalReviewContent);
|