2026-07-22 10:46:56 +08:00
|
|
|
import React, { useState, useEffect } from "react";
|
|
|
|
|
import {
|
|
|
|
|
Form,
|
|
|
|
|
Table,
|
|
|
|
|
Button,
|
|
|
|
|
Tag,
|
|
|
|
|
Modal,
|
|
|
|
|
Row,
|
|
|
|
|
Col,
|
|
|
|
|
Card,
|
|
|
|
|
Statistic,
|
|
|
|
|
Descriptions,
|
|
|
|
|
Steps,
|
|
|
|
|
Tooltip,
|
|
|
|
|
} from "antd";
|
|
|
|
|
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
|
|
|
|
|
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
2026-08-13 10:26:12 +08:00
|
|
|
import SearchForm from "~/components/SearchForm";
|
2026-07-22 10:46:56 +08:00
|
|
|
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
|
|
|
|
|
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
2026-08-05 15:30:43 +08:00
|
|
|
import {
|
|
|
|
|
PROGRESS_STATUS_MAP,
|
|
|
|
|
NODE_LABELS,
|
|
|
|
|
EVAL_TYPE_MAP,
|
|
|
|
|
} from "~/enumerate/constant";
|
2026-07-22 10:46:56 +08:00
|
|
|
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
|
|
|
|
import { tools } from "@cqsjjb/jjb-common-lib";
|
|
|
|
|
import "./index.less";
|
|
|
|
|
|
|
|
|
|
const { router } = tools;
|
|
|
|
|
|
|
|
|
|
const ProjectProgress = (props) => {
|
|
|
|
|
const [searchForm] = Form.useForm();
|
|
|
|
|
const [dataSource, setDataSource] = useState([]);
|
|
|
|
|
const [total, setTotal] = useState(0);
|
|
|
|
|
const [statData, setStatData] = useState({});
|
|
|
|
|
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
|
|
|
|
const [currentRecord, setCurrentRecord] = useState(null);
|
|
|
|
|
|
|
|
|
|
const { evalProjectProgressPageLoading } = props.safetyEvalBusiness;
|
|
|
|
|
|
|
|
|
|
const getStat = async () => {
|
|
|
|
|
const res = await props.evalProjectProgressStat();
|
|
|
|
|
if (res?.success !== false) {
|
|
|
|
|
setStatData(res?.data || {});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getData = async () => {
|
|
|
|
|
const params = {
|
|
|
|
|
...router.query,
|
|
|
|
|
current: router.query.current || 1,
|
|
|
|
|
size: router.query.size || 10,
|
|
|
|
|
};
|
|
|
|
|
const res = await props.evalProjectProgressPage(params);
|
|
|
|
|
if (res?.success !== false) {
|
|
|
|
|
setDataSource(res?.data || []);
|
|
|
|
|
setTotal(res?.total || 0);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
searchForm.setFieldsValue(router.query);
|
|
|
|
|
getStat();
|
|
|
|
|
getData();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleSearch = (values) => {
|
|
|
|
|
router.query = { ...router.query, ...values, current: 1, size: 10 };
|
|
|
|
|
getData();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleReset = (values) => {
|
|
|
|
|
searchForm.resetFields();
|
|
|
|
|
router.query = { ...values, current: 1, size: 10 };
|
|
|
|
|
getData();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handlePageChange = (pagination) => {
|
|
|
|
|
router.query = {
|
|
|
|
|
...router.query,
|
|
|
|
|
current: pagination.current,
|
|
|
|
|
size: pagination.pageSize,
|
|
|
|
|
};
|
|
|
|
|
getData();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleView = (record) => {
|
|
|
|
|
setCurrentRecord(record);
|
|
|
|
|
setDetailModalVisible(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const renderProgressDots = (nodes) => {
|
2026-08-05 15:30:43 +08:00
|
|
|
if (!nodes || nodes.length === 0)
|
|
|
|
|
return <span className="pp-progress-empty">暂无进度</span>;
|
2026-07-22 10:46:56 +08:00
|
|
|
const total = nodes.length;
|
2026-07-24 16:16:18 +08:00
|
|
|
const done = nodes.filter((n) => n.state === 1).length;
|
2026-07-22 10:46:56 +08:00
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<div className="pp-progress-dots">
|
|
|
|
|
{nodes.map((node, idx) => {
|
2026-08-05 15:30:43 +08:00
|
|
|
const dotClass =
|
|
|
|
|
node.state === 1
|
|
|
|
|
? "pp-progress-dot-done"
|
|
|
|
|
: "pp-progress-dot-wait";
|
2026-07-24 16:16:18 +08:00
|
|
|
const name = NODE_LABELS[node.type] || node.type;
|
2026-07-22 10:46:56 +08:00
|
|
|
return (
|
2026-08-05 15:30:43 +08:00
|
|
|
<Tooltip
|
|
|
|
|
key={node.type || idx}
|
|
|
|
|
title={`${name}: ${node.state === 1 ? "已完成" : "未完成"}`}
|
|
|
|
|
>
|
2026-07-22 10:46:56 +08:00
|
|
|
<span className={`pp-progress-dot ${dotClass}`} />
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="pp-progress-text">
|
|
|
|
|
{done}/{total} · {Math.round((done / total) * 100)}%
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const columns = [
|
|
|
|
|
{ title: "项目编号", dataIndex: "projectNo", width: 100 },
|
|
|
|
|
{
|
|
|
|
|
title: "客户/项目",
|
|
|
|
|
width: 180,
|
|
|
|
|
render: (_, record) => (
|
|
|
|
|
<div>
|
|
|
|
|
<div className="pp-project-name">{record.projectName}</div>
|
|
|
|
|
<div className="pp-customer-name">{record.customerName}</div>
|
|
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
},
|
2026-08-05 15:30:43 +08:00
|
|
|
{
|
|
|
|
|
title: "评价类别",
|
|
|
|
|
dataIndex: "evalTypeCode",
|
|
|
|
|
width: 110,
|
|
|
|
|
render: (_, record) => {
|
|
|
|
|
return EVAL_TYPE_MAP[record.evalTypeCode];
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: "负责人",
|
|
|
|
|
dataIndex: "projectLeaderName",
|
|
|
|
|
width: 100,
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
},
|
2026-07-22 10:46:56 +08:00
|
|
|
{
|
|
|
|
|
title: "项目进度",
|
|
|
|
|
width: 240,
|
|
|
|
|
render: (_, record) => renderProgressDots(record.projectNode),
|
|
|
|
|
},
|
|
|
|
|
{ title: "项目结束日期", dataIndex: "planEndDate", width: 120 },
|
|
|
|
|
{
|
|
|
|
|
title: "项目状态",
|
|
|
|
|
dataIndex: "progressStatusCode",
|
|
|
|
|
width: 130,
|
|
|
|
|
render: (code, record) => {
|
|
|
|
|
const cfg = PROGRESS_STATUS_MAP[code];
|
|
|
|
|
const daysText =
|
|
|
|
|
record.remainingDays >= 0
|
|
|
|
|
? `剩余${record.remainingDays}天`
|
|
|
|
|
: `超期${Math.abs(record.remainingDays)}天`;
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
2026-08-05 15:30:43 +08:00
|
|
|
<div>
|
|
|
|
|
{cfg ? (
|
|
|
|
|
<Tag color={cfg.color}>
|
|
|
|
|
{cfg.text}-{daysText}
|
|
|
|
|
</Tag>
|
|
|
|
|
) : (
|
|
|
|
|
<Tag>{code}</Tag>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-07-22 10:46:56 +08:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-07-27 18:06:06 +08:00
|
|
|
// {
|
|
|
|
|
// title: "操作",
|
|
|
|
|
// width: 80,
|
|
|
|
|
// fixed: "right",
|
|
|
|
|
// render: (_, record) => (
|
|
|
|
|
// <Button type="link" size="small" onClick={() => handleView(record)}>
|
|
|
|
|
// 查看
|
|
|
|
|
// </Button>
|
|
|
|
|
// ),
|
|
|
|
|
// },
|
2026-07-22 10:46:56 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<PageLayout title="项目进度管理">
|
|
|
|
|
<Row gutter={16} className="pp-stat-row">
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
<Card size="small">
|
|
|
|
|
<Statistic
|
|
|
|
|
title="项目总数"
|
|
|
|
|
value={statData.totalCount ?? "-"}
|
2026-08-05 15:30:43 +08:00
|
|
|
suffix={
|
|
|
|
|
<span className="pp-stat-suffix">当前机构名下评价项目</span>
|
|
|
|
|
}
|
2026-07-22 10:46:56 +08:00
|
|
|
/>
|
|
|
|
|
</Card>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
<Card size="small">
|
|
|
|
|
<Statistic
|
|
|
|
|
title="正常项目"
|
|
|
|
|
value={statData.normalCount ?? "-"}
|
|
|
|
|
valueStyle={{ color: "#52c41a" }}
|
2026-08-05 15:30:43 +08:00
|
|
|
suffix={
|
|
|
|
|
<span className="pp-stat-suffix">距项目结束日期超过3天</span>
|
|
|
|
|
}
|
2026-07-22 10:46:56 +08:00
|
|
|
/>
|
|
|
|
|
</Card>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
<Card size="small">
|
|
|
|
|
<Statistic
|
|
|
|
|
title="项目临期"
|
|
|
|
|
value={statData.nearExpiryCount ?? "-"}
|
|
|
|
|
valueStyle={{ color: "#faad14" }}
|
2026-08-05 15:30:43 +08:00
|
|
|
suffix={
|
|
|
|
|
<span className="pp-stat-suffix">项目结束前3天自动提醒</span>
|
|
|
|
|
}
|
2026-07-22 10:46:56 +08:00
|
|
|
/>
|
|
|
|
|
</Card>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
<Card size="small">
|
|
|
|
|
<Statistic
|
|
|
|
|
title="项目延期"
|
|
|
|
|
value={statData.delayedCount ?? "-"}
|
|
|
|
|
valueStyle={{ color: "#ff4d4f" }}
|
2026-08-05 15:30:43 +08:00
|
|
|
suffix={
|
|
|
|
|
<span className="pp-stat-suffix">已超过项目结束日期</span>
|
|
|
|
|
}
|
2026-07-22 10:46:56 +08:00
|
|
|
/>
|
|
|
|
|
</Card>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
<SearchForm
|
|
|
|
|
form={searchForm}
|
|
|
|
|
loading={false}
|
|
|
|
|
style={{ marginBottom: 16 }}
|
|
|
|
|
formLine={[
|
|
|
|
|
<Form.Item key="keyword" name="keyword">
|
|
|
|
|
<ControlWrapper.Input
|
|
|
|
|
label="项目名称/客户名称"
|
|
|
|
|
placeholder="项目名称/客户名称"
|
|
|
|
|
allowClear
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>,
|
|
|
|
|
<Form.Item key="progressStatus" name="progressStatus">
|
|
|
|
|
<ControlWrapper.Select
|
|
|
|
|
label="项目状态"
|
|
|
|
|
placeholder="全部"
|
|
|
|
|
allowClear
|
|
|
|
|
options={[
|
|
|
|
|
{ label: "正常", value: "NORMAL" },
|
|
|
|
|
{ label: "项目临期", value: "NEAR" },
|
|
|
|
|
{ label: "项目延期", value: "DELAY" },
|
|
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>,
|
|
|
|
|
]}
|
|
|
|
|
onFinish={handleSearch}
|
|
|
|
|
onReset={handleReset}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Table
|
|
|
|
|
rowKey="id"
|
|
|
|
|
columns={columns}
|
|
|
|
|
dataSource={dataSource}
|
|
|
|
|
loading={evalProjectProgressPageLoading}
|
|
|
|
|
scroll={{ y: props.scrollY, x: 1100 }}
|
|
|
|
|
pagination={{
|
|
|
|
|
total,
|
|
|
|
|
current: router.query.current || 1,
|
|
|
|
|
pageSize: router.query.size || 10,
|
|
|
|
|
showSizeChanger: true,
|
|
|
|
|
showQuickJumper: true,
|
|
|
|
|
showTotal: (total) => `共 ${total} 条`,
|
|
|
|
|
}}
|
|
|
|
|
onChange={handlePageChange}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Modal
|
|
|
|
|
title="项目进度详情"
|
|
|
|
|
open={detailModalVisible}
|
|
|
|
|
onCancel={() => setDetailModalVisible(false)}
|
|
|
|
|
footer={null}
|
|
|
|
|
width={680}
|
|
|
|
|
destroyOnHide={true}
|
|
|
|
|
>
|
|
|
|
|
{currentRecord && (
|
|
|
|
|
<>
|
2026-08-05 15:30:43 +08:00
|
|
|
<Descriptions
|
|
|
|
|
column={2}
|
|
|
|
|
bordered
|
|
|
|
|
size="small"
|
|
|
|
|
className="pp-detail-desc"
|
|
|
|
|
>
|
|
|
|
|
<Descriptions.Item label="项目编号">
|
|
|
|
|
{currentRecord.projectNo}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="项目名称">
|
|
|
|
|
{currentRecord.projectName}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="客户名称">
|
|
|
|
|
{currentRecord.customerName}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="客户负责人">
|
|
|
|
|
{currentRecord.customerContactName}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="评价类型">
|
|
|
|
|
{currentRecord.evalTypeName}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="项目负责人">
|
|
|
|
|
{currentRecord.projectLeaderName}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="项目结束日期">
|
|
|
|
|
{currentRecord.planEndDate}
|
|
|
|
|
</Descriptions.Item>
|
2026-07-22 10:46:56 +08:00
|
|
|
<Descriptions.Item label="剩余天数">
|
|
|
|
|
<span
|
|
|
|
|
className={`pp-remaining-days ${
|
2026-08-05 15:30:43 +08:00
|
|
|
currentRecord.remainingDays < 0
|
|
|
|
|
? "pp-remaining-danger"
|
|
|
|
|
: currentRecord.remainingDays <= 3
|
|
|
|
|
? "pp-remaining-warn"
|
|
|
|
|
: "pp-remaining-normal"
|
2026-07-22 10:46:56 +08:00
|
|
|
}`}
|
|
|
|
|
>
|
2026-08-05 15:30:43 +08:00
|
|
|
{currentRecord.remainingDays >= 0
|
|
|
|
|
? `${currentRecord.remainingDays}天`
|
|
|
|
|
: `已超期${Math.abs(currentRecord.remainingDays)}天`}
|
2026-07-22 10:46:56 +08:00
|
|
|
</span>
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="项目状态" span={2}>
|
2026-08-05 15:30:43 +08:00
|
|
|
<Tag
|
|
|
|
|
color={
|
|
|
|
|
PROGRESS_STATUS_MAP[currentRecord.progressStatusCode]?.color
|
|
|
|
|
}
|
|
|
|
|
>
|
2026-07-22 10:46:56 +08:00
|
|
|
{currentRecord.progressStatusName}
|
|
|
|
|
</Tag>
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
</Descriptions>
|
|
|
|
|
|
|
|
|
|
<div className="pp-node-title">项目节点进度</div>
|
|
|
|
|
<Steps
|
|
|
|
|
direction="vertical"
|
|
|
|
|
current={-1}
|
|
|
|
|
items={
|
|
|
|
|
currentRecord.projectNode?.map((node) => ({
|
|
|
|
|
title: node.nodeName,
|
|
|
|
|
status:
|
|
|
|
|
node.statusCode === 3
|
|
|
|
|
? "finish"
|
|
|
|
|
: node.statusCode === 2
|
|
|
|
|
? "process"
|
|
|
|
|
: node.statusCode === 4
|
|
|
|
|
? "error"
|
|
|
|
|
: "wait",
|
|
|
|
|
description: node.statusName,
|
|
|
|
|
})) || []
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Modal>
|
|
|
|
|
</PageLayout>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default Connect(
|
|
|
|
|
[NS_SAFETY_EVAL_BUSINESS],
|
|
|
|
|
true,
|
2026-08-05 15:30:43 +08:00
|
|
|
)(AntdTableFuncControl(ProjectProgress));
|