2026-07-20 16:57:49 +08:00
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
|
|
|
|
import { Tabs, Tag, Flex } from "antd";
|
2026-07-20 11:16:32 +08:00
|
|
|
|
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
|
|
|
|
|
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
|
2026-07-20 16:57:49 +08:00
|
|
|
|
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
|
|
|
|
|
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
2026-07-22 16:06:26 +08:00
|
|
|
|
import { tools } from "@cqsjjb/jjb-common-lib";
|
2026-07-20 13:38:09 +08:00
|
|
|
|
import WpsEditor from "~/pages/Container/WpsEditor";
|
2026-07-20 15:03:26 +08:00
|
|
|
|
import RiskAnalysisContent from "./RiskAnalysisContent";
|
|
|
|
|
|
import ContractContent from "./ContractContent";
|
|
|
|
|
|
import TeamContent from "./TeamContent";
|
|
|
|
|
|
import SurveyContent from "./SurveyContent";
|
|
|
|
|
|
|
|
|
|
|
|
import InternalReviewContent from "./InternalReviewContent";
|
|
|
|
|
|
import TechnicalReviewContent from "./TechnicalReviewContent";
|
|
|
|
|
|
import ControlContent from "./ControlContent";
|
2026-07-31 14:08:05 +08:00
|
|
|
|
import ArchiveContent from "./ArchiveContent";
|
2026-07-20 15:03:26 +08:00
|
|
|
|
import "./index.less";
|
2026-07-20 11:16:32 +08:00
|
|
|
|
|
|
|
|
|
|
const PROCESS_TABS = [
|
2026-07-20 16:57:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: "risk",
|
|
|
|
|
|
index: "1",
|
|
|
|
|
|
label: "风险分析",
|
|
|
|
|
|
role: "市场部经办人 / 市场部经理",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: "contract",
|
|
|
|
|
|
index: "2",
|
|
|
|
|
|
label: "业务合同",
|
|
|
|
|
|
role: "机构管理员 / 项目负责人",
|
|
|
|
|
|
},
|
2026-07-23 17:41:28 +08:00
|
|
|
|
{ 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: "过程控制负责人" },
|
2026-07-31 14:08:05 +08:00
|
|
|
|
{ key: "archive", index: "9", label: "项目归档", role: "档案管理员" },
|
2026-07-20 11:16:32 +08:00
|
|
|
|
];
|
2026-07-22 16:06:26 +08:00
|
|
|
|
const { router } = tools;
|
2026-07-20 11:16:32 +08:00
|
|
|
|
|
2026-07-23 17:41:28 +08:00
|
|
|
|
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 [];
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-31 14:08:05 +08:00
|
|
|
|
const buildProcessTabs = (projectNode, archiveFlag) => {
|
2026-07-23 17:41:28 +08:00
|
|
|
|
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;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-12 11:50:26 +08:00
|
|
|
|
const isNodeDone = (tabIdx) => {
|
|
|
|
|
|
const tabKey = PROCESS_TABS[tabIdx].key;
|
|
|
|
|
|
const tabType = Object.keys(TYPE_TO_TAB_KEY).find(
|
|
|
|
|
|
(k) => TYPE_TO_TAB_KEY[k] === tabKey,
|
|
|
|
|
|
);
|
|
|
|
|
|
const n = tabType ? nodeMap[tabType] : null;
|
|
|
|
|
|
return !!n && n.state === 1;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-23 17:41:28 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-08-12 11:50:26 +08:00
|
|
|
|
if (idx === 2) {
|
|
|
|
|
|
// 项目组:业务合同完成后才解锁
|
|
|
|
|
|
const unlocked = isNodeDone(1);
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
state: unlocked ? (node?.state === 1 ? "已完成" : "待办理") : "前置未完成",
|
|
|
|
|
|
stateType: unlocked ? (node?.state === 1 ? "success" : "default") : "warning",
|
|
|
|
|
|
locked: !unlocked,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (idx === 3) {
|
|
|
|
|
|
// 现场踏勘:业务合同、项目组均完成后才解锁(串联)
|
|
|
|
|
|
const unlocked = isNodeDone(1) && isNodeDone(2);
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
state: unlocked ? (node?.state === 1 ? "已完成" : "待办理") : "前置未完成",
|
|
|
|
|
|
stateType: unlocked ? (node?.state === 1 ? "success" : "default") : "warning",
|
|
|
|
|
|
locked: !unlocked,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 17:41:28 +08:00
|
|
|
|
if (idx === 7) {
|
|
|
|
|
|
// 过程控制:前7个全部完成才解锁
|
|
|
|
|
|
const unlocked = allPrevDone(7);
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
2026-07-24 18:26:42 +08:00
|
|
|
|
state: unlocked ? node.state === 1 ? "已完成" : "待办理" : "前置未完成",
|
|
|
|
|
|
stateType: unlocked ? node.state === 1 ? "success" : "default" : "warning",
|
2026-07-23 17:41:28 +08:00
|
|
|
|
locked: !unlocked,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-31 14:08:05 +08:00
|
|
|
|
if (idx === 8) {
|
|
|
|
|
|
// 项目归档:前8个全部完成才解锁
|
|
|
|
|
|
const unlocked = allPrevDone(8);
|
|
|
|
|
|
const archived = archiveFlag === 1;
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
state: unlocked ? (archived ? "已完成" : "待办理") : "前置未完成",
|
|
|
|
|
|
stateType: unlocked ? (archived ? "success" : "default") : "warning",
|
|
|
|
|
|
locked: !unlocked,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 17:41:28 +08:00
|
|
|
|
if (node) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
state: node.state === 1 ? "已完成" : "待办理",
|
|
|
|
|
|
stateType: node.state === 1 ? "success" : "default",
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return { ...item, locked: false };
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-20 13:38:09 +08:00
|
|
|
|
const ProjectFlow = (props) => {
|
|
|
|
|
|
const [activeKey, setActiveKey] = useState("risk");
|
|
|
|
|
|
const [visible, setVisible] = useState(false);
|
2026-07-23 17:41:28 +08:00
|
|
|
|
const { wpsDoc, evalProjectDetailData } = props.safetyEvalBusiness || {};
|
2026-07-22 16:06:26 +08:00
|
|
|
|
const projectId = router.query?.id;
|
2026-07-30 09:59:44 +08:00
|
|
|
|
const readOnly = router.query?.readOnly === '1';
|
2026-07-20 13:38:09 +08:00
|
|
|
|
|
2026-07-31 14:08:05 +08:00
|
|
|
|
const processTabs = buildProcessTabs(
|
|
|
|
|
|
evalProjectDetailData?.projectNode,
|
|
|
|
|
|
evalProjectDetailData?.archiveFlag,
|
|
|
|
|
|
);
|
2026-07-23 17:41:28 +08:00
|
|
|
|
|
2026-07-20 13:38:09 +08:00
|
|
|
|
const handleTabChange = (key) => {
|
|
|
|
|
|
if (key === "report") {
|
2026-07-20 16:57:49 +08:00
|
|
|
|
openReportEditor();
|
2026-07-20 13:38:09 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
setActiveKey(key);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-20 16:57:49 +08:00
|
|
|
|
const openReportEditor = async () => {
|
|
|
|
|
|
setVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-23 17:41:28 +08:00
|
|
|
|
const getDetail = () => {
|
|
|
|
|
|
props.evalProjectDetail({
|
|
|
|
|
|
id: projectId,
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-20 16:57:49 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
props.queryMyOrCreateDoc({
|
2026-07-22 16:06:26 +08:00
|
|
|
|
docxUrl: "",
|
2026-07-24 16:13:41 +08:00
|
|
|
|
projectId,
|
2026-07-22 16:06:26 +08:00
|
|
|
|
});
|
2026-08-05 16:21:20 +08:00
|
|
|
|
|
2026-07-20 16:57:49 +08:00
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-08-05 16:21:20 +08:00
|
|
|
|
useEffect(()=>{
|
|
|
|
|
|
if(!visible){
|
|
|
|
|
|
getDetail();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [visible])
|
|
|
|
|
|
|
2026-07-23 17:41:28 +08:00
|
|
|
|
const tabItems = processTabs.map((item) => {
|
2026-07-20 13:38:09 +08:00
|
|
|
|
let children = null;
|
|
|
|
|
|
switch (item.key) {
|
2026-07-20 16:57:49 +08:00
|
|
|
|
case "risk":
|
2026-07-30 09:59:44 +08:00
|
|
|
|
children = <RiskAnalysisContent getDetail={getDetail} readOnly={readOnly} />;
|
2026-07-20 16:57:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case "contract":
|
2026-07-30 09:59:44 +08:00
|
|
|
|
children = <ContractContent getDetail={getDetail} readOnly={readOnly} />;
|
2026-07-20 16:57:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case "team":
|
2026-07-30 09:59:44 +08:00
|
|
|
|
children = <TeamContent getDetail={getDetail} readOnly={readOnly} />;
|
2026-07-20 16:57:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case "survey":
|
2026-07-30 09:59:44 +08:00
|
|
|
|
children = <SurveyContent getDetail={getDetail} readOnly={readOnly} />;
|
2026-07-20 16:57:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case "internal":
|
2026-07-30 09:59:44 +08:00
|
|
|
|
children = <InternalReviewContent getDetail={getDetail} readOnly={readOnly} />;
|
2026-07-20 16:57:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case "technical":
|
2026-07-30 09:59:44 +08:00
|
|
|
|
children = <TechnicalReviewContent getDetail={getDetail} readOnly={readOnly} />;
|
2026-07-20 16:57:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case "control":
|
2026-07-30 09:59:44 +08:00
|
|
|
|
children = <ControlContent getDetail={getDetail} readOnly={readOnly} />;
|
2026-07-20 16:57:49 +08:00
|
|
|
|
break;
|
2026-07-31 14:08:05 +08:00
|
|
|
|
case "archive":
|
|
|
|
|
|
children = <ArchiveContent getDetail={getDetail} readOnly={readOnly} />;
|
|
|
|
|
|
break;
|
2026-07-20 16:57:49 +08:00
|
|
|
|
default:
|
|
|
|
|
|
children = null;
|
2026-07-20 13:38:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
key: item.key,
|
|
|
|
|
|
disabled: item.locked,
|
|
|
|
|
|
label: (
|
2026-07-20 15:03:26 +08:00
|
|
|
|
<Flex align="center" gap={6}>
|
2026-07-20 13:38:09 +08:00
|
|
|
|
{item.index} {item.label}
|
2026-07-20 16:57:49 +08:00
|
|
|
|
<Tag color={item.stateType} className="pf-tag">
|
|
|
|
|
|
{item.state}
|
|
|
|
|
|
</Tag>
|
2026-07-20 15:03:26 +08:00
|
|
|
|
</Flex>
|
2026-07-20 13:38:09 +08:00
|
|
|
|
),
|
|
|
|
|
|
children,
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
2026-07-20 11:16:32 +08:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<PageLayout
|
2026-07-20 15:03:26 +08:00
|
|
|
|
history={props.history}
|
|
|
|
|
|
previous
|
2026-07-20 11:16:32 +08:00
|
|
|
|
title={
|
2026-07-20 16:57:49 +08:00
|
|
|
|
<div>
|
2026-08-06 17:46:40 +08:00
|
|
|
|
<span>项目工作台</span>
|
2026-07-20 16:57:49 +08:00
|
|
|
|
<div className="pageLayout-extra">
|
2026-08-07 10:24:50 +08:00
|
|
|
|
{evalProjectDetailData?.projectName} |{" "}
|
2026-07-24 15:08:29 +08:00
|
|
|
|
{evalProjectDetailData?.evalTypeName} |{" "}
|
|
|
|
|
|
{evalProjectDetailData?.projectNo}
|
2026-07-20 16:57:49 +08:00
|
|
|
|
</div>
|
2026-07-20 13:38:09 +08:00
|
|
|
|
</div>
|
2026-07-20 11:16:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
>
|
2026-07-24 15:08:29 +08:00
|
|
|
|
<Tabs
|
|
|
|
|
|
activeKey={activeKey}
|
|
|
|
|
|
onChange={handleTabChange}
|
|
|
|
|
|
items={tabItems}
|
2026-08-17 16:55:05 +08:00
|
|
|
|
className="pf-tabs"
|
2026-07-24 15:08:29 +08:00
|
|
|
|
destroyOnHidden
|
|
|
|
|
|
/>
|
2026-07-20 16:57:49 +08:00
|
|
|
|
{visible && (
|
2026-08-07 10:45:25 +08:00
|
|
|
|
<WpsEditor onCancel={() => setVisible(false)} fileId={wpsDoc?.fileId} readOnly={readOnly || evalProjectDetailData?.archiveFlag === 1} />
|
2026-07-20 16:57:49 +08:00
|
|
|
|
)}
|
2026-07-20 11:16:32 +08:00
|
|
|
|
</PageLayout>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-20 16:57:49 +08:00
|
|
|
|
export default Connect(
|
|
|
|
|
|
[NS_SAFETY_EVAL_BUSINESS],
|
|
|
|
|
|
true,
|
|
|
|
|
|
)(AntdTableFuncControl(ProjectFlow));
|