dev_1.2
tangjie 2026-07-24 11:05:09 +08:00
parent c96233c0a1
commit 6ad6475911
7 changed files with 926 additions and 157 deletions

View File

@ -13,8 +13,8 @@ module.exports = {
//API_HOST: "https://gbs-gateway.qhdsafety.com",
//API_HOST: "http://192.168.0.152",
//API_HOST: "http://192.168.0.150", //太浅
API_HOST: "http://192.168.0.152",
API_HOST: "http://192.168.0.150", //太浅
//API_HOST: "http://192.168.0.152",
//API_HOST: "http://192.168.0.103", //huwei
},
production: {

View File

@ -165,4 +165,74 @@ export const evalSurveyList = declareRequest(
export const evalSurveySaveAttach = declareRequest(
"evalSurveySaveAttachLoading",
"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 || []",
);

View File

@ -1,26 +1,423 @@
import React from "react";
import { Card, Empty, Table } from "antd";
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 ControlContent = () => (
<Card title="过程控制审核与归档">
<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>
);
const { TextArea } = Input;
const { router } = tools;
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);

View File

@ -1,20 +1,63 @@
import React, { useState } from "react";
import { Tag, Button, Card, Table, Select, Input, Flex, Typography, Row, Col } from "antd";
import React, { useState, useEffect } from "react";
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 { router } = tools;
const INITIAL_ROWS = [
{ dim: "评价依据", focus: "充分、有效", result: "符合", opinion: "补充地方标准有效版本", feedback: "已验证", feedbackType: "success" },
{ dim: "危险辨识", focus: "全面、无遗漏", result: "需修改", opinion: "补充液氨装卸泄漏场景", feedback: "待反馈", feedbackType: "warning" },
{ 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 CONCLUSION_OPTIONS = [
{ value: "conform_to", label: "符合" },
{ value: "not_conform_to", label: "需修改" },
];
const InternalReviewContent = () => {
const [rows, setRows] = useState(INITIAL_ROWS);
const InternalReviewContent = (props) => {
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 next = [...rows];
@ -22,74 +65,179 @@ const InternalReviewContent = () => {
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 (
<div>
<div className="pf-permission">
<Tag color="blue" className="pf-tag">内部审核人</Tag>
<Tag color="blue" className="pf-tag">
内部审核人
</Tag>
<span className="pf-desc">审核修改反馈验证闭环</span>
</div>
<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={6}><Card size="small"><Typography.Text type="secondary">内审人</Typography.Text><div><strong></strong></div></Card></Col>
<Col span={6}><Card size="small"><Typography.Text type="secondary">审核项</Typography.Text><div><strong>7</strong></div></Card></Col>
<Col span={6}><Card size="small"><Typography.Text type="secondary">问题</Typography.Text><div><Typography.Text strong type="warning">2</Typography.Text></div></Card></Col>
</Row>
<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>
<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={[
{ title: "审核维度", dataIndex: "dim", width: 100 },
{ title: "审核重点", dataIndex: "focus", width: 160 },
{
title: "结论", dataIndex: "result", width: 120,
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", width: 180,
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"
<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}
onChange={(e) => setOverallOpinion(e.target.value)}
style={{ marginTop: 4 }}
/>
</div>
<div style={{ marginTop: 12 }}>
<Typography.Text strong>总体审核意见</Typography.Text>
<TextArea rows={2} defaultValue="完成危险辨识修改后通过内部审核。" style={{ marginTop: 4 }} />
</div>
<Flex justify="space-between" align="center" style={{ marginTop: 10 }}>
<Flex gap={8} align="center">
<Typography.Text type="secondary">内审人独立签字</Typography.Text>
<strong>王丽萍</strong>
<Button size="small">下发APP签字</Button>
</Flex>
<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={internalReviewAssignLoading}
disabled={isDispatched}
>
{isDispatched ? "已下发APP签字" : "下发APP签字"}
</Button>
</Flex>
</Flex>
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
<Button>保存意见</Button>
<Button type="primary">确认修改并提交技审</Button>
</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>
);
};
export default InternalReviewContent;
export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(InternalReviewContent);

View File

@ -80,6 +80,10 @@ const RiskAnalysisContent = (props) => {
const values = await form.validateFields();
const { analysisDate, ...rest } = values;
if (!rest.participants?.length) {
message.warning("请选择风险分析人员");
return;
}
const res = await evalRiskAnalysisSave({
...rest,
projectId,

View File

@ -74,6 +74,14 @@ const TeamContent = (props) => {
if (selectedPersons.length === 0) { message.warning("请至少选择1名人员"); return; }
const role = ROLE_DEFINITIONS.find((r) => r.value === selectedRole);
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 p = participants.find((item) => item.id === personId);
@ -110,6 +118,15 @@ const TeamContent = (props) => {
};
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) => ({
id: m.id,
projectId,
@ -124,6 +141,7 @@ const TeamContent = (props) => {
if (res?.success !== false) {
message.success("保存成功");
fetchData();
props.getDetail();
}
};
@ -134,10 +152,16 @@ const TeamContent = (props) => {
{ title: "部门/岗位", dataIndex: "deptName", width: 200, ellipsis: true },
{
title: "项目角色", dataIndex: "roles", width: 350,
render: (roles) => (
render: (roles, record) => (
<div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
{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}
</Tag>
))}
@ -216,7 +240,7 @@ const TeamContent = (props) => {
<Flex justify="flex-end" gap={8} className="pf-bottom-actions">
<Button>生成项目组任命文件</Button>
<Button type="primary" onClick={handleSave}>确认项目组</Button>
<Button type="primary" loading={memberSaveLoading} onClick={handleSave}>确认项目组</Button>
</Flex>
<Modal title="添加项目人员与角色" open={modalOpen} onCancel={() => setModalOpen(false)} width={700}

View File

@ -1,18 +1,46 @@
import React, { useState } from "react";
import { Tag, Button, Card, Row, Col, Table, Select, Input, Flex, Typography } from "antd";
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 INITIAL_ROWS = [
{ dim: "资料有效性", focus: "现场资料和检测报告齐全有效", result: "符合", opinion: "资料有效" },
{ dim: "危险辨识", focus: "辨识充分、规律准确", result: "符合", opinion: "辨识充分" },
{ dim: "评价方法", focus: "选型合理、计算正确", result: "需修改", opinion: "补充事故模拟参数来源" },
{ dim: "对策措施", focus: "针对性和可行性", result: "符合", opinion: "措施可行" },
{ dim: "评价结论", focus: "结论正确、反映风险", result: "符合", opinion: "结论正确" },
const CONCLUSION_OPTIONS = [
{ value: "conform_to", label: "符合" },
{ value: "not_conform_to", label: "需修改" },
];
const TechnicalReviewContent = () => {
const [rows, setRows] = useState(INITIAL_ROWS);
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]);
const updateRow = (index, field, value) => {
const next = [...rows];
@ -20,68 +48,166 @@ const TechnicalReviewContent = () => {
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 (
<div>
<div className="pf-permission">
<Tag color="blue" className="pf-tag">技术负责人</Tag>
<Tag color="blue" className="pf-tag">
技术负责人
</Tag>
<span className="pf-desc">技术负责人独立审核技术路线和结论</span>
</div>
<Row gutter={16} style={{ marginBottom: 16 }}>
<Col span={6}><Card size="small"><Typography.Text type="secondary">审核版本</Typography.Text><div><strong>V1.4</strong></div></Card></Col>
<Col span={6}><Card size="small"><Typography.Text type="secondary">技术负责人</Typography.Text><div><strong></strong></div></Card></Col>
<Col span={6}><Card size="small"><Typography.Text type="secondary">专业匹配</Typography.Text><div><Typography.Text strong type="success"></Typography.Text></div></Card></Col>
<Col span={6}><Card size="small"><Typography.Text type="secondary">备案校验</Typography.Text><div><Typography.Text strong type="success"></Typography.Text></div></Card></Col>
</Row>
<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={[
{ title: "审核维度", dataIndex: "dim", width: 120 },
{ title: "技术审核重点", dataIndex: "focus", width: 200 },
{
title: "结论", dataIndex: "result", width: 120,
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"
<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 }}
/>
</div>
<div style={{ marginTop: 12 }}>
<Typography.Text strong>技术审核意见</Typography.Text>
<TextArea rows={2} defaultValue="补充定量评价参数来源后通过技术审核。" style={{ marginTop: 4 }} />
</div>
<Flex justify="space-between" align="center" style={{ marginTop: 10 }}>
<Flex gap={8} align="center">
<Typography.Text type="secondary">技术负责人签字</Typography.Text>
<strong>陈志强</strong>
<Button size="small">下发APP签字</Button>
</Flex>
<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>
</Flex>
</Flex>
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
<Button>保存意见</Button>
<Button>查看修改反馈</Button>
<Button type="primary">确认技审并提交过程控制</Button>
</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>
);
};
export default TechnicalReviewContent;
export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(TechnicalReviewContent);