dev_1.2
tangjie 2026-07-23 17:41:28 +08:00
parent 2d20215e96
commit c96233c0a1
11 changed files with 423 additions and 230 deletions

View File

@ -146,3 +146,23 @@ export const projectWaringRemindPage = declareRequest(
"projectWaringRemindPageLoading",
"Get > /safetyEval/project-waring/remind-page",
);
// ===== 我的项目统计 =====
export const evalProjectStatistics = declareRequest(
"evalProjectStatisticsLoading",
"Get > /safetyEval/institution/eval-project/projectStatistics",
'evalProjectStatisticsData: {} | res.data || {}',
);
// ===== 现场踏勘附件 =====
export const evalSurveyList = declareRequest(
"evalSurveyListLoading",
"Get > /safetyEval/institution/eval-survey/list",
);
export const evalSurveySaveAttach = declareRequest(
"evalSurveySaveAttachLoading",
"Post > @/safetyEval/institution/eval-survey/saveSurveyAttach",
);

View File

@ -299,7 +299,6 @@ export const ENTERPRISE_SCALE_OPTIONS = [
{ label: "微型", value: "MICRO" },
];
/** 评价类型选项 */
export const EVAL_TYPE_OPTIONS = [
{ label: "安全预评价", value: "PRE" },
@ -370,3 +369,14 @@ export const WARING_STATUS_OPTIONS = [
{ label: "已逾期", value: "overdue" },
{ label: "正常", value: "normal" },
];
export const NODE_LABELS = {
RISK_ANALYSIS: "风险分析",
BUSINESS_CONTRACT: "商务合同",
PROJECT_GROUP: "项目组",
SITE_SURVEY: "现场勘查",
REPORT_PREPARATION: "报告编制",
INTERNAL_REVIEW: "内审",
TECHNICAL_REVIEW: "技术审核",
PROCESS_CONTROL: "过程控制",
};

View File

@ -1,24 +1,35 @@
import React, { useState, useEffect } from "react";
import {
Form, Table, Tag, Button, Card, Row, Col, Statistic, Select, Modal, message,
Form, Table, Tag, Button, Card, Row, Col, Statistic, Select, Modal, message, Tooltip,
} from "antd";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { NODE_LABELS } from "~/enumerate/constant";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { tools } from "@cqsjjb/jjb-common-lib";
import "./index.less";
const { router } = tools;
const parseProjectNode = (data) => {
if (Array.isArray(data)) return data;
try {
return JSON.parse(data || '[]');
} catch {
return [];
}
};
const MyProject = (props) => {
const [searchForm] = Form.useForm();
const [dataSource, setDataSource] = useState([]);
const [total, setTotal] = useState(0);
const { evalProjectLoading } = props.safetyEvalBusiness;
const { evalProjectLoading, evalProjectStatisticsData } = props.safetyEvalBusiness;
const getData = async (pagination) => {
const params = {
@ -36,6 +47,7 @@ const MyProject = (props) => {
useEffect(() => {
searchForm.setFieldsValue(router.query);
props.evalProjectStatistics();
getData();
}, []);
@ -88,6 +100,30 @@ const MyProject = (props) => {
return phase ? <Tag color={color}>{phase}</Tag> : "";
},
},
{
title: "已完成节点", width: 200,
render: (_, record) => {
const nodes = parseProjectNode(record.projectNode);
if (nodes.length === 0) return <span className="mp-progress-empty">暂无</span>;
const total = nodes.length;
const done = nodes.filter((n) => n.state === 1).length;
return (
<div>
<div className="mp-progress-dots">
{nodes.map((n, idx) => (
<Tooltip key={n.type || idx} title={NODE_LABELS[n.type] || n.type}>
<span className={`mp-progress-dot ${n.state === 1 ? "mp-progress-dot-done" : "mp-progress-dot-wait"}`} />
</Tooltip>
))}
</div>
<div className="mp-progress-text">
{done}/{total} · {Math.round((done / total) * 100)}%
</div>
</div>
);
},
},
{
title: "操作", width: 140, fixed: "right",
render: (_, record) => (
@ -105,25 +141,25 @@ const MyProject = (props) => {
<Row gutter={16} style={{ marginBottom: 16 }}>
<Col span={6}>
<Card size="small">
<Statistic title="参与项目" value={total} suffix="个" />
<Statistic title="参与项目" value={evalProjectStatisticsData.participatingProjectSum ?? "-"} suffix="个" />
<div style={{ fontSize: 12, color: "#999", marginTop: 4 }}>负责人/编制人/审核人</div>
</Card>
</Col>
<Col span={6}>
<Card size="small">
<Statistic title="待我处理" value={3} valueStyle={{ color: "#faad14" }} suffix="项" />
<Statistic title="待我处理" value={evalProjectStatisticsData.pendingMyHandlingSum ?? "-"} valueStyle={{ color: "#faad14" }} suffix="项" />
<div style={{ fontSize: 12, color: "#999", marginTop: 4 }}>当前角色可操作节点</div>
</Card>
</Col>
<Col span={6}>
<Card size="small">
<Statistic title="现场任务" value={2} valueStyle={{ color: "#52c41a" }} suffix="项" />
<Statistic title="现场任务" value={evalProjectStatisticsData.onSiteTaskSum ?? "-"} valueStyle={{ color: "#52c41a" }} suffix="项" />
<div style={{ fontSize: 12, color: "#999", marginTop: 4 }}>需定位打卡和人脸识别</div>
</Card>
</Col>
<Col span={6}>
<Card size="small">
<Statistic title="延期项目" value={1} valueStyle={{ color: "#ff4d4f" }} suffix="项" />
<Statistic title="延期项目" value={evalProjectStatisticsData.delayedProjectSum ?? "-"} valueStyle={{ color: "#ff4d4f" }} suffix="项" />
<div style={{ fontSize: 12, color: "#999", marginTop: 4 }}>需优先处理</div>
</Card>
</Col>

View File

@ -0,0 +1,28 @@
.mp {
&-progress-dots {
display: flex;
gap: 4px;
align-items: center;
}
&-progress-dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
&-done { background: #52c41a; }
&-wait { background: #e8e8e8; }
}
&-progress-text {
font-size: 11px;
color: #999;
margin-top: 4px;
}
&-progress-empty {
color: #999;
font-size: 12px;
}
}

View File

@ -63,6 +63,7 @@ const ContractContent = (props) => {
if (res?.success !== false && res?.data) {
form.setFieldsValue({
...res.data,
scanFileUrl: res.data.scanFileUrl? JSON.parse(res.data.scanFileUrl) :undefined,
contractStartDate: res.data.contractStartDate
? dayjs(res.data.contractStartDate)
: undefined,
@ -118,6 +119,7 @@ const ContractContent = (props) => {
});
if (res?.success !== false) {
message.success("合同保存成功");
props.getDetail();
}
} finally {
}

View File

@ -99,6 +99,7 @@ const RiskAnalysisContent = (props) => {
);
if (result.success) {
message.success(statusCode === 2 ? "风险分析已完成" : "已暂存");
props.getDetail()
}
}
};

View File

@ -1,65 +1,78 @@
import React, { useState } from "react";
import { Tag, Button, Card, Table, Flex, Typography, Steps } from "antd";
import { CheckCircleFilled } from "@ant-design/icons";
import React, { useState, useEffect } from "react";
import { Form, Button, Flex, Typography, Steps, Tag, Card, Table, message } from "antd";
import { CheckCircleFilled, DownloadOutlined } from "@ant-design/icons";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { tools } from "@cqsjjb/jjb-common-lib";
import AttachmentUpload from "~/components/AttachmentUpload";
import "./index.less";
const { router } = tools;
const STAGES = [
{ key: "notice", index: "01", label: "从业告知",},
{ key: "inspection", index: "02", label: "现场勘查", },
{ key: "rectification", index: "03", label: "整改通知", },
{ key: "enterprise", index: "04", label: "企业整改",},
{ key: "review", index: "05", label: "整改复查", },
{ key: "notice", index: "01", label: "从业告知" },
{ key: "inspection", index: "02", label: "现场勘查" },
{ key: "rectification", index: "03", label: "整改通知" },
{ key: "enterprise", index: "04", label: "企业整改" },
{ key: "review", index: "05", label: "整改复查" },
];
const ATTACH_TYPE = {
notice: "PRACTICE_NOTIFICATION",
inspection: "SITE_INSPECTION_FORM",
rectification: "RECTIFICATION_NOTICE",
enterprise: "ENTERPRISE_RECTIFICATION",
review: "RECTIFICATION_REVIEW",
};
const downloadTemplate = async () => {
try {
const res = await fetch("https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com/jjb/6a608cb30dc53ed98d0b13ae.pdf");
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "从业告知书模板.pdf";
a.click();
URL.revokeObjectURL(url);
} catch {
message.error("下载失败,请重试");
}
};
const NoticePanel = () => (
<div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
<div className="pf-section-header">
<div>
<span style={{ fontSize: 20, fontWeight: 600, color: "#1677ff", marginRight: 8 }}>01</span>
<Typography.Text strong style={{ fontSize: 15 }}>安全评价检测检验机构从业告知书</Typography.Text>
<Typography.Paragraph type="secondary" style={{ margin: 0 }}>支持在线填写后打印阶段试行期间以机构盖章版上传归档作为完成依据</Typography.Paragraph>
</div>
<Tag color="success">盖章件已上传</Tag>
</div>
<div style={{ background: "#f0f5ff", padding: "8px 12px", borderRadius: 6, marginBottom: 16, display: "flex", gap: 16, fontSize: 13 }}>
<strong>办理方式</strong>
<span>在线填写用于生成打印标准告知书</span>
<span>打印盖章后上传扫描件或清晰照片</span>
<Tag color="blue">完成节点必须留存盖章件</Tag>
</div>
<Card type="inner" size="small" title="已上传盖章件" extra={<Tag color="success">已上传</Tag>} style={{ marginBottom: 12 }}>
<Flex justify="space-between" align="center">
<Flex gap={8} align="center">
<span style={{ background: "#1677ff", color: "#fff", padding: "4px 6px", borderRadius: 4, fontSize: 12 }}>PDF</span>
<div className="pf-template-card">
<div className="pf-template-left">
<span className="pf-template-icon">📄</span>
<div>
<Typography.Text strong>安全评价检测检验机构从业告知书盖章版.pdf</Typography.Text>
<Typography.Paragraph type="secondary" style={{ margin: 0, fontSize: 12 }}>2.4MB · 2026-07-07 10:28 上传</Typography.Paragraph>
<div className="pf-template-name">从业告知书模板</div>
<div className="pf-template-desc">PDF 格式可下载后在线填写并打印盖章</div>
</div>
</Flex>
<Flex gap={8}>
<Button size="small">查看</Button>
<Button size="small">重新上传</Button>
</Flex>
</Flex>
</Card>
<div style={{ display: "flex", gap: 16, fontSize: 12 }}>
<div><CheckCircleFilled style={{ color: "#52c41a" }} /> 机构公章清晰</div>
<div><CheckCircleFilled style={{ color: "#52c41a" }} /> 填写内容完整</div>
<div><CheckCircleFilled style={{ color: "#52c41a" }} /> 归档版本一致</div>
</div>
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
<Button>打印告知书</Button>
<Button type="primary">确认从业告知完成</Button>
</Flex>
<Button type="primary" ghost size="small" icon={<DownloadOutlined />} onClick={downloadTemplate}>
下载模板
</Button>
</div>
</div>
);
const InspectionPanel = () => (
<div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
<div className="pf-section-header">
<div>
<span style={{ fontSize: 20, fontWeight: 600, color: "#1677ff", marginRight: 8 }}>02</span>
<Typography.Text strong style={{ fontSize: 15 }}>现场人员打卡与检查表</Typography.Text>
@ -96,132 +109,152 @@ const InspectionPanel = () => (
<span>签到日期2026-07-15</span>
</div>
</Card>
<Card type="inner" size="small" title="现场检查表">
<Flex justify="space-between" align="center">
<Flex gap={8} align="center">
<span style={{ background: "#52c41a", color: "#fff", padding: "4px 6px", borderRadius: 4, fontSize: 12 }}>XLSX</span>
<div>
<Typography.Text strong>华安化工园区现场安全检查表.xlsx</Typography.Text>
<Typography.Paragraph type="secondary" style={{ margin: 0, fontSize: 12 }}>1.8MB · 李明华 · 2026-07-15 17:06 上传</Typography.Paragraph>
</div>
</Flex>
<Flex gap={8} align="center">
<Tag color="success">已归档</Tag>
<Button size="small">查看</Button>
<Button size="small">删除</Button>
</Flex>
</Flex>
</Card>
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
<Button>刷新打卡状态</Button>
<Button>导出打卡记录</Button>
<Button type="primary">确认现场勘查完成</Button>
</Flex>
</div>
);
const SimpleUploadPanel = ({ index, title, desc, icon, fileType, fileName, fileSize, uploader, date }) => (
const SimpleUploadPanel = ({ index, title, desc }) => (
<div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
<div className="pf-section-header">
<div>
<span style={{ fontSize: 20, fontWeight: 600, color: "#1677ff", marginRight: 8 }}>{index}</span>
<Typography.Text strong style={{ fontSize: 15 }}>{title}</Typography.Text>
<Typography.Paragraph type="secondary" style={{ margin: 0 }}>{desc}</Typography.Paragraph>
</div>
<Tag color="success">已完成</Tag>
</div>
<Flex gap={16}>
<Card style={{ flex: 1, textAlign: "center" }}>
<div style={{ fontSize: 32, marginBottom: 8 }}>{icon}</div>
<Typography.Text strong>上传{title}</Typography.Text>
<Typography.Paragraph type="secondary" style={{ fontSize: 12 }}>支持 PDFWordJPGPNG</Typography.Paragraph>
<Button type="primary">选择文件</Button>
<div style={{ marginTop: 8, fontSize: 12, color: "#999" }}>上传后本节点自动标记完成</div>
</Card>
<Card style={{ flex: 1 }}>
<Flex justify="space-between" align="center" style={{ marginBottom: 12 }}>
<Typography.Text strong>已归档文件</Typography.Text>
<Tag color="success">已上传</Tag>
</Flex>
<Flex gap={8} align="center">
<span style={{ background: "#1677ff", color: "#fff", padding: "4px 6px", borderRadius: 4, fontSize: 12 }}>{fileType}</span>
<div>
<Typography.Text strong>{fileName}</Typography.Text>
<Typography.Paragraph type="secondary" style={{ margin: 0, fontSize: 12 }}>{fileSize} · {uploader} · {date} 上传</Typography.Paragraph>
</div>
</Flex>
<Flex justify="flex-end" gap={8} style={{ marginTop: 8 }}>
<Button size="small">查看</Button>
<Button size="small">替换修改</Button>
</Flex>
</Card>
</Flex>
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
<Button>重新上传</Button>
<Button type="primary">确认完成</Button>
</Flex>
</div>
);
const SurveyContent = () => {
const [activeStage, setActiveStage] = useState("notice");
return (
<div>
<div className="pf-permission">
<Tag color="blue" className="pf-tag">项目组专职人员 2</Tag>
<span className="pf-desc">从业告知现场勘查整改与复查全过程留痕</span>
</div>
<Steps
current={STAGES.findIndex((s) => s.key === activeStage)}
onChange={(idx) => setActiveStage(STAGES[idx].key)}
style={{ marginBottom: 16 }}
items={STAGES.map((s) => ({
title: s.label,
status: "process",
}))}
/>
{activeStage === "notice" && <NoticePanel />}
{activeStage === "inspection" && <InspectionPanel />}
{activeStage === "rectification" && (
const STEP_PANELS = {
notice: () => <NoticePanel />,
inspection: () => <InspectionPanel />,
rectification: () => (
<SimpleUploadPanel
index="03" title="整改通知单上传"
desc="本节点不在线编制整改通知,只归档机构已出具的《整改通知单》文件;上传成功即完成,后续可替换修改。"
icon="📄" fileType="PDF"
fileName="华安化工园区建设项目整改通知单.pdf"
fileSize="2.1MB" uploader="张建国" date="2026-07-15 17:20"
/>
)}
{activeStage === "enterprise" && (
),
enterprise: () => (
<SimpleUploadPanel
index="04" title="企业整改报告上传"
desc="企业线下提交《整改报告》后,由机构人员上传归档;本系统不提供企业在线提交和逐项整改流程。"
icon="📄" fileType="PDF"
fileName="华安化工园区企业整改报告.pdf"
fileSize="3.5MB" uploader="李明华" date="2026-07-16 09:15"
/>
)}
{activeStage === "review" && (
),
review: () => (
<SimpleUploadPanel
index="05" title="整改复查单上传"
desc="机构人员现场复查后,将经双方签字确认的《整改复查单》上传归档;上传后本节点自动标记完成。"
icon="📄" fileType="PDF"
fileName="华安化工园区整改复查单(双方签字).pdf"
fileSize="1.6MB" uploader="张建国" date="2026-07-18 11:30"
/>
)}
),
};
const SurveyContent = (props) => {
const [form] = Form.useForm();
const [activeStage, setActiveStage] = useState("notice");
const [attachMap, setAttachMap] = useState({});
const { evalSurveyListLoading, evalSurveySaveAttachLoading } = props.safetyEvalBusiness;
const projectId = router.query?.id;
const currentIdx = STAGES.findIndex((s) => s.key === activeStage);
const isFirst = currentIdx === 0;
const isLast = currentIdx === STAGES.length - 1;
const loadAttachments = async () => {
if (!projectId) return;
const res = await props.evalSurveyList({ projectId });
if (res?.success !== false) {
const map = {};
(res?.data || []).forEach((item) => {
const key = item.attachTypeCode;
try {
const parsed = JSON.parse(item.fileUrl);
map[key] = Array.isArray(parsed) ? parsed : [];
} catch {
map[key] = [];
}
});
setAttachMap(map);
// 回填所有步骤的附件到表单
const values = {};
Object.entries(ATTACH_TYPE).forEach(([, typeCode]) => {
values[typeCode] = map[typeCode] || [];
});
form.setFieldsValue(values);
}
};
useEffect(() => {
loadAttachments();
}, []);
const goPrev = () => {
if (currentIdx > 0) setActiveStage(STAGES[currentIdx - 1].key);
};
const goNext = () => {
if (currentIdx < STAGES.length - 1) setActiveStage(STAGES[currentIdx + 1].key);
};
const handleSave = async () => {
const values = form.getFieldsValue();
const payload = [];
for (const [key, typeCode] of Object.entries(ATTACH_TYPE)) {
const fileList = values[typeCode] || [];
const validFiles = fileList.filter((f) => f.status === "done" && f.url);
if (validFiles.length === 0) {
setActiveStage(key);
message.warning(`请先在"${STAGES.find((s) => s.key === key).label}"步骤上传附件`);
return;
}
payload.push({
projectId,
attachTypeCode: typeCode,
fileUrl: JSON.stringify(fileList),
});
}
const res = await props.evalSurveySaveAttach(payload);
if (res?.success !== false) {
message.success("保存成功");
props.getDetail();
}
};
return (
<div>
<Steps
current={currentIdx}
onChange={(idx) => setActiveStage(STAGES[idx].key)}
style={{ marginBottom: 16 }}
items={STAGES.map((s) => ({ title: s.label }))}
/>
{STEP_PANELS[activeStage]()}
<Form form={form} layout="vertical">
{Object.entries(ATTACH_TYPE).map(([key, typeCode]) => (
<div key={key} style={{ display: key === activeStage ? "block" : "none" }}>
<AttachmentUpload
name={typeCode}
label="上传附件"
maxCount={10}
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png,.xls,.xlsx"
extra="支持 PDF、Word、JPG、PNG、Excel 格式,最多上传 10 个文件"
/>
</div>
))}
</Form>
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
{!isFirst && <Button onClick={goPrev} disabled={evalSurveySaveAttachLoading}>上一步</Button>}
{isLast ? (
<Button type="primary" onClick={handleSave} loading={evalSurveySaveAttachLoading}>保存</Button>
) : (
<Button type="primary" onClick={goNext} disabled={evalSurveySaveAttachLoading}>下一步</Button>
)}
</Flex>
</div>
);
};
export default SurveyContent;
export default Connect(
[NS_SAFETY_EVAL_BUSINESS],
true,
)(SurveyContent);

View File

@ -214,9 +214,9 @@ const TeamContent = (props) => {
</details>
<Flex justify="flex-end" gap={8} className="pf-bottom-actions">
<Button onClick={handleSave} loading={memberSaveLoading}>暂存</Button>
<Button>生成项目组任命文件</Button>
<Button type="primary">确认项目组</Button>
<Button type="primary" onClick={handleSave}>确认项目组</Button>
</Flex>
<Modal title="添加项目人员与角色" open={modalOpen} onCancel={() => setModalOpen(false)} width={700}

View File

@ -21,76 +21,100 @@ const PROCESS_TABS = [
key: "risk",
index: "1",
label: "风险分析",
state: "已完成",
stateType: "success",
role: "市场部经办人 / 市场部经理",
},
{
key: "contract",
index: "2",
label: "业务合同",
state: "已完成",
stateType: "success",
role: "机构管理员 / 项目负责人",
},
{
key: "team",
index: "3",
label: "项目组",
state: "已完成",
stateType: "success",
role: "评价部经理 / 机构主账号",
},
{
key: "survey",
index: "4",
label: "现场踏勘",
state: "已完成",
stateType: "success",
role: "项目组成员",
},
{
key: "report",
index: "5",
label: "报告编制",
state: "编制中",
stateType: "processing",
role: "报告编制人",
},
{
key: "internal",
index: "6",
label: "内部审核",
state: "待办理",
stateType: "default",
role: "内部审核人",
},
{
key: "technical",
index: "7",
label: "技术审核",
state: "待办理",
stateType: "default",
role: "技术负责人",
},
{
key: "control",
index: "8",
label: "过程控制",
state: "前置未完成",
stateType: "warning",
role: "过程控制负责人",
locked: true,
},
{ key: "team", index: "3", label: "项目组", role: "评价部经理 / 机构主账号" },
{ key: "survey", index: "4", label: "现场踏勘", role: "项目组成员" },
{ key: "report", index: "5", label: "报告编制", role: "报告编制人" },
{ key: "internal", index: "6", label: "内部审核", role: "内部审核人" },
{ key: "technical", index: "7", label: "技术审核", role: "技术负责人" },
{ key: "control", index: "8", label: "过程控制", role: "过程控制负责人" },
];
const { router } = tools;
const TYPE_TO_TAB_KEY = {
RISK_ANALYSIS: "risk",
BUSINESS_CONTRACT: "contract",
PROJECT_GROUP: "team",
SITE_SURVEY: "survey",
REPORT_PREPARATION: "report",
INTERNAL_REVIEW: "internal",
TECHNICAL_REVIEW: "technical",
PROCESS_CONTROL: "control",
};
const parseProjectNode = (data) => {
if (Array.isArray(data)) return data;
try {
return JSON.parse(data || "[]");
} catch {
return [];
}
};
const buildProcessTabs = (projectNode) => {
const nodes = parseProjectNode(projectNode);
const nodeMap = {};
nodes.forEach((n) => {
nodeMap[n.type] = n;
});
const allPrevDone = (idx) => {
for (let i = 0; i < idx; i++) {
const key = PROCESS_TABS[i].key;
const type = Object.keys(TYPE_TO_TAB_KEY).find(
(k) => TYPE_TO_TAB_KEY[k] === key,
);
if (!type) continue;
const node = nodeMap[type];
if (!node || node.state !== 1) return false;
}
return true;
};
return PROCESS_TABS.map((item, idx) => {
const type = Object.keys(TYPE_TO_TAB_KEY).find(
(k) => TYPE_TO_TAB_KEY[k] === item.key,
);
const node = type ? nodeMap[type] : null;
if (idx === 7) {
// 过程控制前7个全部完成才解锁
const unlocked = allPrevDone(7);
return {
...item,
state: unlocked ? "待办理" : "前置未完成",
stateType: unlocked ? "default" : "warning",
locked: !unlocked,
};
}
if (node) {
return {
...item,
state: node.state === 1 ? "已完成" : "待办理",
stateType: node.state === 1 ? "success" : "default",
locked: false,
};
}
return { ...item, locked: false };
});
};
const ProjectFlow = (props) => {
const [activeKey, setActiveKey] = useState("risk");
const [visible, setVisible] = useState(false);
const { wpsDoc } = props.safetyEvalBusiness || {};
const { wpsDoc, evalProjectDetailData } = props.safetyEvalBusiness || {};
const projectId = router.query?.id;
const processTabs = buildProcessTabs(evalProjectDetailData?.projectNode);
const handleTabChange = (key) => {
if (key === "report") {
openReportEditor();
@ -103,29 +127,33 @@ const ProjectFlow = (props) => {
setVisible(true);
};
const getDetail = () => {
props.evalProjectDetail({
id: projectId,
});
};
useEffect(() => {
props.queryMyOrCreateDoc({
docxUrl: "",
});
props.evalProjectDetail({
id: projectId,
});
getDetail();
}, []);
const tabItems = PROCESS_TABS.map((item) => {
const tabItems = processTabs.map((item) => {
let children = null;
switch (item.key) {
case "risk":
children = <RiskAnalysisContent />;
children = <RiskAnalysisContent getDetail={getDetail} />;
break;
case "contract":
children = <ContractContent />;
children = <ContractContent getDetail={getDetail} />;
break;
case "team":
children = <TeamContent />;
children = <TeamContent getDetail={getDetail} />;
break;
case "survey":
children = <SurveyContent />;
children = <SurveyContent getDetail={getDetail} />;
break;
case "internal":

View File

@ -1,4 +1,50 @@
.pf {
// 区块头部
&-section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
margin-bottom: 16px;
background: #fafcff;
border: 1px solid #e8edf5;
border-radius: 8px;
border-left: 4px solid #1677ff;
}
// 模板下载卡片
&-template-card {
background: #fafcff;
border: 1px dashed #1677ff;
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
&-template-left {
display: flex;
align-items: center;
gap: 10px;
}
&-template-icon {
font-size: 22px;
}
&-template-name {
font-size: 13px;
font-weight: 500;
color: #172033;
}
&-template-desc {
font-size: 12px;
color: #999;
}
// 权限标签区域
&-permission {
margin-bottom: 12px;

View File

@ -115,7 +115,7 @@ const RiskWarning = (props) => {
width: 100,
render: (code) => {
const cfg = RISK_LEVEL_MAP[code];
return cfg ? <Tag color={cfg.color}>{cfg.text}</Tag> : <Tag>{code}</Tag>;
return cfg ? <Tag color={cfg.color}>{cfg.text}</Tag> : "";
},
},
{ title: "预警类型", dataIndex: "warningTypeName", width: 100 },
@ -126,14 +126,14 @@ const RiskWarning = (props) => {
width: 260,
},
{ title: "责任人", dataIndex: "responsiblePerson", width: 90 },
{ title: "预警时间", dataIndex: "warningTime", width: 110 },
{ title: "预警时间", dataIndex: "warningTime", width: 120 },
{
title: "状态",
dataIndex: "statusCode",
width: 90,
render: (code) => {
const cfg = RISK_STATUS_MAP[code];
return cfg ? <Tag color={cfg.color}>{cfg.text}</Tag> : <Tag>{code}</Tag>;
return cfg ? <Tag color={cfg.color}>{cfg.text}</Tag> : "";
},
},
{
@ -242,18 +242,7 @@ const RiskWarning = (props) => {
onReset={handleReset}
/>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 12,
}}
>
<span style={{ fontSize: 13, color: "#999" }}>
处理按钮会打开处置面板标记闭环会直接更新行状态
</span>
</div>
<Table
rowKey="id"