Merge remote-tracking branch 'origin/dev' into dev
commit
149cc8811f
|
|
@ -12,9 +12,9 @@ module.exports = {
|
|||
// 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095
|
||||
//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: {
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
|
||||
|
||||
export const identifyPartList = declareRequest(
|
||||
"coursewareLoading",
|
||||
"Post > @/risk/busIdentifyPart/list",
|
||||
);
|
||||
|
|
@ -122,4 +122,9 @@ export const queryOrgSelectList = declareRequest(
|
|||
"orgSelectLoading",
|
||||
"Get > /safetyEval/org-info/select/list",
|
||||
'orgSelectList: [] | res.data || []',
|
||||
);
|
||||
|
||||
export const fetchQualOnSiteReviewResultDetail = declareRequest(
|
||||
"siteReviewResultLoading",
|
||||
"Get > /safetyEval/qual-on-site-review/result-detail/{id}",
|
||||
);
|
||||
|
|
@ -77,4 +77,24 @@ export const evalProjectDocPage = declareRequest(
|
|||
export const evalProjectDocSave = declareRequest(
|
||||
"evalProjectDocSaveLoading",
|
||||
"Post > @/safetyEval/institution/eval-project-doc/save",
|
||||
);
|
||||
|
||||
export const evalProjectProgressStat = declareRequest(
|
||||
"evalProjectProgressLoading",
|
||||
"Get > /safetyEval/institution/eval-project/progress-stat",
|
||||
);
|
||||
|
||||
export const evalProjectProgressPage = declareRequest(
|
||||
"evalProjectProgressPageLoading",
|
||||
"Get > /safetyEval/institution/eval-project/progress-page",
|
||||
);
|
||||
|
||||
export const evalProjectRiskWarningStat = declareRequest(
|
||||
"riskWarningStatLoading",
|
||||
"Get > /safetyEval/institution/eval-project/risk-warning-stat",
|
||||
);
|
||||
|
||||
export const evalProjectRiskWarningPage = declareRequest(
|
||||
"riskWarningPageLoading",
|
||||
"Get > /safetyEval/institution/eval-project/risk-warning-page",
|
||||
);
|
||||
|
|
@ -331,4 +331,24 @@ export const MATERIAL_TYPE_OPTIONS = [
|
|||
export const MATERIAL_TYPE_MAP = MATERIAL_TYPE_OPTIONS.reduce((acc, cur) => {
|
||||
acc[cur.value] = cur.label;
|
||||
return acc;
|
||||
}, {});
|
||||
}, {});
|
||||
|
||||
/** 风险预警 — 风险等级映射 */
|
||||
export const RISK_LEVEL_MAP = {
|
||||
HIGH: { color: "error", text: "高风险" },
|
||||
MIDDLE: { color: "warning", text: "中风险" },
|
||||
LOW: { color: "default", text: "低风险" },
|
||||
};
|
||||
|
||||
/** 风险预警 — 处理状态映射 */
|
||||
export const RISK_STATUS_MAP = {
|
||||
UNHANDLED: { color: "error", text: "未处理" },
|
||||
PROCESSING: { color: "warning", text: "处理中" },
|
||||
};
|
||||
|
||||
/** 项目进度 — 进度状态映射 */
|
||||
export const PROGRESS_STATUS_MAP = {
|
||||
NORMAL: { color: "success", text: "正常" },
|
||||
NEAR: { color: "warning", text: "项目临期" },
|
||||
DELAY: { color: "error", text: "项目延期" },
|
||||
};
|
||||
|
|
@ -212,10 +212,20 @@ const menuItems = [
|
|||
icon: <FileTextOutlined />,
|
||||
},
|
||||
{
|
||||
key: "/safetyEval/container/SafetyEvalBusiness/EvalProject/ProjectDocLibrary",
|
||||
key: "/safetyEval/container/SafetyEvalBusiness/ProjectDocLibrary",
|
||||
label: "项目资料管理",
|
||||
icon: <FileTextOutlined />,
|
||||
},
|
||||
{
|
||||
key: "/safetyEval/container/SafetyEvalBusiness/Progress",
|
||||
label: "项目进度管理",
|
||||
icon: <BarChartOutlined />,
|
||||
},
|
||||
{
|
||||
key: "/safetyEval/container/SafetyEvalBusiness/RiskWarning",
|
||||
label: "风险预警管理",
|
||||
icon: <ExperimentOutlined />,
|
||||
},
|
||||
{
|
||||
key: "/safetyEval/container/SafetyEvalBusiness/CustomerManage",
|
||||
label: "安评客户管理",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const SiteReviewNotice = (props) => {
|
|||
updateQualOnSiteReview,
|
||||
uploadQualOnSiteReviewResult,
|
||||
fetchQualOnSiteReviewDetail,
|
||||
fetchQualOnSiteReviewResultDetail,
|
||||
queryOrgSelectList,
|
||||
resetModelState,
|
||||
} = props;
|
||||
|
|
@ -145,8 +146,16 @@ const SiteReviewNotice = (props) => {
|
|||
resultForm.setFieldsValue({
|
||||
institution: record.unitName,
|
||||
reviewDateStart: record.reviewDateStart,
|
||||
reviewLeader: record.reviewLeader || "",
|
||||
reviewOpinion: record.reviewOpinion || "",
|
||||
});
|
||||
fetchQualOnSiteReviewResultDetail({ id: record.id }).then((res) => {
|
||||
if (res?.success !== false && res?.data) {
|
||||
const detail = res.data;
|
||||
resultForm.setFieldsValue({
|
||||
reviewLeader: detail.reviewLeader || "",
|
||||
reviewResult: detail.reviewResult,
|
||||
reviewOpinion: detail.reviewOpinion || "",
|
||||
});
|
||||
}
|
||||
});
|
||||
setResultModalOpen(true);
|
||||
};
|
||||
|
|
@ -210,9 +219,7 @@ const SiteReviewNotice = (props) => {
|
|||
</Button>
|
||||
}
|
||||
>
|
||||
<div style={{ fontSize: 13, color: "#666", marginBottom: 16 }}>
|
||||
向安全评价机构下发现场审查时间、地点和审查组安排,并接收审查结果文件。
|
||||
</div>
|
||||
|
||||
|
||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||||
<Col span={8}>
|
||||
|
|
|
|||
|
|
@ -125,23 +125,28 @@ const CustomerManage = (props) => {
|
|||
title: "确认删除",
|
||||
content: "确定要删除这个客户吗?",
|
||||
onOk: async () => {
|
||||
await props.customerDelete({ data: record.id });
|
||||
message.success("删除成功");
|
||||
getData();
|
||||
const res = await props.customerDelete({ data: record.id });
|
||||
if (res?.success !== false) {
|
||||
message.success("删除成功");
|
||||
getData();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateSubmit = () => {
|
||||
modalForm.validateFields().then(async (values) => {
|
||||
let res;
|
||||
if (editingId) {
|
||||
await props.customerModify({ ...values, id: editingId });
|
||||
res = await props.customerModify({ ...values, id: editingId });
|
||||
} else {
|
||||
await props.customerSave(values);
|
||||
res = await props.customerSave(values);
|
||||
}
|
||||
if (res.success) {
|
||||
message.success(editingId ? "修改成功" : "创建成功");
|
||||
setModalOpen(false);
|
||||
getData();
|
||||
}
|
||||
message.success(editingId ? "修改成功" : "创建成功");
|
||||
setModalOpen(false);
|
||||
getData();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -184,7 +189,7 @@ const CustomerManage = (props) => {
|
|||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 260,
|
||||
width: 220,
|
||||
fixed: "right",
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
|
|
@ -335,8 +340,15 @@ const CustomerManage = (props) => {
|
|||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="industryCode" label="所属行业">
|
||||
<Select placeholder="请选择" allowClear>
|
||||
<Form.Item
|
||||
name="industryCode"
|
||||
label="所属行业"
|
||||
getValueFromEvent={(value) => value.join(",")}
|
||||
getValueProps={(value) => ({
|
||||
value: value ? value.split(",").filter(Boolean) : [],
|
||||
})}
|
||||
>
|
||||
<Select mode="multiple" placeholder="请选择" allowClear>
|
||||
{QUALIFICATION_INDUSTRY_OPTIONS.map((opt) => (
|
||||
<Select.Option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ import {
|
|||
Tag,
|
||||
} from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import {tools} from "@cqsjjb/jjb-common-lib";
|
||||
import { tools } from "@cqsjjb/jjb-common-lib";
|
||||
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
||||
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||
import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions";
|
||||
import BaiduMapPicker from "~/components/BaiduMapPicker";
|
||||
import { district } from "~/enumerate/constant";
|
||||
import { phoneRule } from "~/utils/validators";
|
||||
|
|
@ -39,7 +40,11 @@ const EvalProjectCreate = (props) => {
|
|||
const projectId = router.query?.id;
|
||||
const isView = projectId;
|
||||
|
||||
const { evalProjectSaveLoading, evalProjectDetailLoading, customerPage: customerList } = props.safetyEvalBusiness;
|
||||
const {
|
||||
evalProjectSaveLoading,
|
||||
evalProjectDetailLoading,
|
||||
customerPage: customerList,
|
||||
} = props.safetyEvalBusiness;
|
||||
|
||||
useEffect(() => {
|
||||
props.customerPage({ current: 1, size: 999 });
|
||||
|
|
@ -55,12 +60,18 @@ const EvalProjectCreate = (props) => {
|
|||
const data = res.data;
|
||||
form.setFieldsValue({
|
||||
...data,
|
||||
planStartDate: data.planStartDate ? dayjs(data.planStartDate) : undefined,
|
||||
planStartDate: data.planStartDate
|
||||
? dayjs(data.planStartDate)
|
||||
: undefined,
|
||||
planEndDate: data.planEndDate ? dayjs(data.planEndDate) : undefined,
|
||||
});
|
||||
if (data.participants?.length) {
|
||||
setSelectedParticipants(
|
||||
data.participants.map((p) => ({ id: p.id, userName: p.name, ...p })),
|
||||
data.participants.map((p) => ({
|
||||
id: p.id,
|
||||
userName: p.name,
|
||||
...p,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -94,13 +105,12 @@ const EvalProjectCreate = (props) => {
|
|||
};
|
||||
if (isView) {
|
||||
params.id = projectId;
|
||||
await props.evalProjectSave(params);
|
||||
message.success("修改成功");
|
||||
} else {
|
||||
await props.evalProjectSave(params);
|
||||
message.success("创建成功");
|
||||
}
|
||||
props.history.goBack();
|
||||
const res = await props.evalProjectSave(params);
|
||||
if (res?.success) {
|
||||
message.success(isView ? "修改成功" : "创建成功");
|
||||
props.history.goBack();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -115,16 +125,18 @@ const EvalProjectCreate = (props) => {
|
|||
history={props.history}
|
||||
previous
|
||||
footer={
|
||||
!isView && <Flex gap={24}>
|
||||
<Button onClick={() => props.history.goBack()}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleSubmit}
|
||||
loading={detailLoading || evalProjectSaveLoading}
|
||||
>
|
||||
提交并返回项目列表
|
||||
</Button>
|
||||
</Flex>
|
||||
!isView && (
|
||||
<Flex gap={24}>
|
||||
<Button onClick={() => props.history.goBack()}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleSubmit}
|
||||
loading={detailLoading || evalProjectSaveLoading}
|
||||
>
|
||||
提交并返回项目列表
|
||||
</Button>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
loading={detailLoading}
|
||||
>
|
||||
|
|
@ -174,7 +186,9 @@ const EvalProjectCreate = (props) => {
|
|||
showSearch
|
||||
optionFilterProp="label"
|
||||
onChange={(value, option) => {
|
||||
const customer = customerList?.find((item) => item.id === value);
|
||||
const customer = customerList?.find(
|
||||
(item) => item.id === value,
|
||||
);
|
||||
console.log(customer);
|
||||
form.setFieldsValue({
|
||||
customerContactName: customer?.principalName || "",
|
||||
|
|
@ -226,7 +240,7 @@ const EvalProjectCreate = (props) => {
|
|||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="projectLeaderName" label="项目负责人">
|
||||
<Input placeholder="请输入" maxLength={50} />
|
||||
<Input placeholder="请输入" maxLength={30} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -241,6 +255,21 @@ const EvalProjectCreate = (props) => {
|
|||
<DatePicker style={{ width: "100%" }} placeholder="请选择" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="industryCode"
|
||||
label="所属行业"
|
||||
|
||||
>
|
||||
<Select placeholder="请选择" allowClear>
|
||||
{QUALIFICATION_INDUSTRY_OPTIONS.map((opt) => (
|
||||
<Select.Option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@ const EvalProject = (props) => {
|
|||
title: "确认删除",
|
||||
content: "确定要删除这个项目吗?",
|
||||
onOk: async () => {
|
||||
await props.evalProjectDelete({ data: [record.id] });
|
||||
message.success("删除成功");
|
||||
getData();
|
||||
const res = await props.evalProjectDelete({ data: [record.id] });
|
||||
if (res?.success !== false) {
|
||||
message.success("删除成功");
|
||||
getData();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -107,7 +109,7 @@ const EvalProject = (props) => {
|
|||
width: 100,
|
||||
render: (phase) => {
|
||||
const color = phase === "延期" ? "error" : phase === "正常" ? "success" : undefined;
|
||||
return <Tag color={color}>{phase || "-"}</Tag>;
|
||||
return phase ? <Tag color={color}>{phase}</Tag> : "";
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,326 @@
|
|||
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";
|
||||
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
|
||||
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
|
||||
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||
import { PROGRESS_STATUS_MAP } from "~/enumerate/constant";
|
||||
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) => {
|
||||
if (!nodes || nodes.length === 0) return <span className="pp-progress-empty">暂无进度</span>;
|
||||
const total = nodes.length;
|
||||
const done = nodes.filter((n) => n.statusCode === 3).length;
|
||||
return (
|
||||
<div>
|
||||
<div className="pp-progress-dots">
|
||||
{nodes.map((node, idx) => {
|
||||
const dotClass =
|
||||
node.statusCode === 3 ? "pp-progress-dot-done" :
|
||||
node.statusCode === 2 ? "pp-progress-dot-doing" :
|
||||
node.statusCode === 4 ? "pp-progress-dot-error" :
|
||||
"pp-progress-dot-wait";
|
||||
return (
|
||||
<Tooltip key={node.nodeCode || idx} title={`${node.nodeName}: ${node.statusName}`}>
|
||||
<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>
|
||||
),
|
||||
},
|
||||
{ title: "评价类别", dataIndex: "evalTypeName", width: 110 },
|
||||
{ title: "负责人", dataIndex: "projectLeaderName", width: 100, ellipsis: true },
|
||||
{
|
||||
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>
|
||||
<div>{cfg ? <Tag color={cfg.color}>{cfg.text}-{daysText}</Tag> : <Tag>{code}</Tag>}</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 80,
|
||||
fixed: "right",
|
||||
render: (_, record) => (
|
||||
<Button type="link" size="small" onClick={() => handleView(record)}>
|
||||
查看
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageLayout title="项目进度管理">
|
||||
|
||||
<Row gutter={16} className="pp-stat-row">
|
||||
<Col span={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title="项目总数"
|
||||
value={statData.totalCount ?? "-"}
|
||||
suffix={<span className="pp-stat-suffix">当前机构名下评价项目</span>}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title="正常项目"
|
||||
value={statData.normalCount ?? "-"}
|
||||
valueStyle={{ color: "#52c41a" }}
|
||||
suffix={<span className="pp-stat-suffix">距项目结束日期超过3天</span>}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title="项目临期"
|
||||
value={statData.nearExpiryCount ?? "-"}
|
||||
valueStyle={{ color: "#faad14" }}
|
||||
suffix={<span className="pp-stat-suffix">项目结束前3天自动提醒</span>}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title="项目延期"
|
||||
value={statData.delayedCount ?? "-"}
|
||||
valueStyle={{ color: "#ff4d4f" }}
|
||||
suffix={<span className="pp-stat-suffix">已超过项目结束日期</span>}
|
||||
/>
|
||||
</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 && (
|
||||
<>
|
||||
<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>
|
||||
<Descriptions.Item label="剩余天数">
|
||||
<span
|
||||
className={`pp-remaining-days ${
|
||||
currentRecord.remainingDays < 0 ? "pp-remaining-danger" :
|
||||
currentRecord.remainingDays <= 3 ? "pp-remaining-warn" :
|
||||
"pp-remaining-normal"
|
||||
}`}
|
||||
>
|
||||
{currentRecord.remainingDays >= 0 ? `${currentRecord.remainingDays}天` : `已超期${Math.abs(currentRecord.remainingDays)}天`}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="项目状态" span={2}>
|
||||
<Tag color={PROGRESS_STATUS_MAP[currentRecord.progressStatusCode]?.color}>
|
||||
{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,
|
||||
)(AntdTableFuncControl(ProjectProgress));
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
.pp {
|
||||
// 统计卡片区域
|
||||
&-stat-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&-stat-suffix {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// 进度圆点容器
|
||||
&-progress-dots {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-progress-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
|
||||
&-done { background: #52c41a; }
|
||||
&-doing { background: #1677ff; }
|
||||
&-error { background: #ff4d4f; }
|
||||
&-wait { background: #e8e8e8; }
|
||||
}
|
||||
|
||||
&-progress-text {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
&-progress-empty {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// 客户/项目列
|
||||
&-project-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-customer-name {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// 详情弹窗 - Descriptions 间距
|
||||
&-detail-desc {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
// 剩余天数
|
||||
&-remaining-days {
|
||||
font-weight: 600;
|
||||
}
|
||||
&-remaining-normal { color: #52c41a; }
|
||||
&-remaining-warn { color: #faad14; }
|
||||
&-remaining-danger { color: #ff4d4f; }
|
||||
|
||||
// 节点进度标题
|
||||
&-node-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ const ProjectDocLibrary = (props) => {
|
|||
|
||||
const handleUploadSubmit = async () => {
|
||||
const values = await uploadForm.validateFields();
|
||||
await props.evalProjectDocSave({
|
||||
const res = await props.evalProjectDocSave({
|
||||
projectId: values.projectId,
|
||||
projectDocUrl: values.file.map((item) => ({
|
||||
project_doc_url: item.url,
|
||||
|
|
@ -98,9 +98,11 @@ const ProjectDocLibrary = (props) => {
|
|||
materialType: values.materialType,
|
||||
})),
|
||||
});
|
||||
message.success("上传成功");
|
||||
setUploadModalOpen(false);
|
||||
getData();
|
||||
if (res?.success !== false) {
|
||||
message.success("上传成功");
|
||||
setUploadModalOpen(false);
|
||||
getData();
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Form,
|
||||
Table,
|
||||
Button,
|
||||
Tag,
|
||||
Modal,
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
Statistic,
|
||||
Space,
|
||||
message,
|
||||
} from "antd";
|
||||
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/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 { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||
import { RISK_LEVEL_MAP, RISK_STATUS_MAP } from "~/enumerate/constant";
|
||||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||
import { tools } from "@cqsjjb/jjb-common-lib";
|
||||
import "./index.less";
|
||||
|
||||
const { router } = tools;
|
||||
|
||||
const RiskWarning = (props) => {
|
||||
const [searchForm] = Form.useForm();
|
||||
const [dataSource, setDataSource] = useState([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [statData, setStatData] = useState({});
|
||||
const [handleModalVisible, setHandleModalVisible] = useState(false);
|
||||
const [currentRecord, setCurrentRecord] = useState(null);
|
||||
|
||||
const { riskWarningPageLoading } = props.safetyEvalBusiness;
|
||||
|
||||
const getStat = async () => {
|
||||
const res = await props.evalProjectRiskWarningStat();
|
||||
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.evalProjectRiskWarningPage(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 handleProcess = (record) => {
|
||||
setCurrentRecord(record);
|
||||
setHandleModalVisible(true);
|
||||
};
|
||||
|
||||
const handleCloseRisk = async (record) => {
|
||||
Modal.confirm({
|
||||
title: "确认闭环",
|
||||
content: `确定将"${record.projectName}"的风险标记为闭环吗?`,
|
||||
onOk: async () => {
|
||||
// TODO: 调用闭环接口
|
||||
message.success("已闭环");
|
||||
getData();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ title: "项目编号", dataIndex: "projectNo", width: 130 },
|
||||
{
|
||||
title: "项目/客户",
|
||||
width: 240,
|
||||
render: (_, record) => (
|
||||
<div>
|
||||
<div className="rw-project-name">{record.projectName}</div>
|
||||
<div className="rw-customer-name">{record.customerName}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "风险等级",
|
||||
dataIndex: "riskLevelCode",
|
||||
width: 100,
|
||||
render: (code) => {
|
||||
const cfg = RISK_LEVEL_MAP[code];
|
||||
return cfg ? <Tag color={cfg.color}>{cfg.text}</Tag> : <Tag>{code}</Tag>;
|
||||
},
|
||||
},
|
||||
{ title: "预警类型", dataIndex: "warningTypeName", width: 100 },
|
||||
{
|
||||
title: "触发条件",
|
||||
dataIndex: "triggerCondition",
|
||||
ellipsis: true,
|
||||
width: 260,
|
||||
},
|
||||
{ title: "责任人", dataIndex: "responsiblePerson", width: 90 },
|
||||
{ title: "预警时间", dataIndex: "warningTime", width: 110 },
|
||||
{
|
||||
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>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 130,
|
||||
fixed: "right",
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button type="link" size="small" onClick={() => handleProcess(record)}>
|
||||
处理
|
||||
</Button>
|
||||
<Button type="link" size="small" onClick={() => handleCloseRisk(record)}>
|
||||
闭环
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageLayout title="风险预警管理">
|
||||
<Row gutter={16} className="rw-stat-row">
|
||||
<Col span={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title="预警总数"
|
||||
value={statData.totalCount ?? "-"}
|
||||
suffix={<span className="rw-stat-suffix">来自项目过程控制与周期规则</span>}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title="高风险"
|
||||
value={statData.highRiskCount ?? "-"}
|
||||
valueStyle={{ color: "#ff4d4f" }}
|
||||
suffix={<span className="rw-stat-suffix">延期、逾期、重大整改</span>}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title="处理中"
|
||||
value={statData.processingCount ?? "-"}
|
||||
valueStyle={{ color: "#faad14" }}
|
||||
suffix={<span className="rw-stat-suffix">已分派但未闭环</span>}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title="本周新增"
|
||||
value={statData.weekNewCount ?? "-"}
|
||||
valueStyle={{ color: "#52c41a" }}
|
||||
suffix={<span className="rw-stat-suffix">需在周例会确认</span>}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
|
||||
<SearchForm
|
||||
form={searchForm}
|
||||
loading={false}
|
||||
style={{ marginBottom: 16 }}
|
||||
formLine={[
|
||||
<Form.Item key="projectOrCustomer" name="projectOrCustomer">
|
||||
<ControlWrapper.Input
|
||||
label="项目/客户"
|
||||
placeholder="项目或客户名称"
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>,
|
||||
<Form.Item key="riskLevel" name="riskLevel">
|
||||
<ControlWrapper.Select
|
||||
label="风险等级"
|
||||
placeholder="全部"
|
||||
allowClear
|
||||
options={[
|
||||
{ label: "高风险", value: "HIGH" },
|
||||
{ label: "中风险", value: "MIDDLE" },
|
||||
{ label: "低风险", value: "LOW" },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>,
|
||||
<Form.Item key="warningType" name="warningType">
|
||||
<ControlWrapper.Select
|
||||
label="预警类型"
|
||||
placeholder="全部"
|
||||
allowClear
|
||||
options={[
|
||||
{ label: "进度风险", value: "PROGRESS" },
|
||||
{ label: "周期风险", value: "CYCLE" },
|
||||
{ label: "质量风险", value: "QUALITY" },
|
||||
{ label: "人员风险", value: "PERSONNEL" },
|
||||
{ label: "归档风险", value: "ARCHIVE" },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>,
|
||||
]}
|
||||
onFinish={handleSearch}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 12,
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 13, color: "#999" }}>
|
||||
处理按钮会打开处置面板;标记闭环会直接更新行状态。
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
loading={riskWarningPageLoading}
|
||||
scroll={{ y: props.scrollY, x: 1300 }}
|
||||
pagination={{
|
||||
total,
|
||||
current: router.query.current || 1,
|
||||
pageSize: router.query.size || 10,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
}}
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title="风险处置"
|
||||
open={handleModalVisible}
|
||||
onCancel={() => setHandleModalVisible(false)}
|
||||
footer={null}
|
||||
width={600}
|
||||
destroyOnHide={true}
|
||||
>
|
||||
{currentRecord && (
|
||||
<div>
|
||||
<p>
|
||||
<strong>项目:</strong>
|
||||
{currentRecord.projectName}
|
||||
</p>
|
||||
<p>
|
||||
<strong>风险等级:</strong>
|
||||
<Tag color={RISK_LEVEL_MAP[currentRecord.riskLevelCode]?.color}>
|
||||
{currentRecord.riskLevelName}
|
||||
</Tag>
|
||||
</p>
|
||||
<p>
|
||||
<strong>预警类型:</strong>
|
||||
{currentRecord.warningTypeName}
|
||||
</p>
|
||||
<p>
|
||||
<strong>触发条件:</strong>
|
||||
{currentRecord.triggerCondition}
|
||||
</p>
|
||||
<p>
|
||||
<strong>责任人:</strong>
|
||||
{currentRecord.responsiblePerson}
|
||||
</p>
|
||||
<p>
|
||||
<strong>预警时间:</strong>
|
||||
{currentRecord.warningTime}
|
||||
</p>
|
||||
<div style={{ textAlign: "right", marginTop: 24 }}>
|
||||
<Space>
|
||||
<Button onClick={() => setHandleModalVisible(false)}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
message.success("处理完成");
|
||||
setHandleModalVisible(false);
|
||||
getData();
|
||||
}}
|
||||
>
|
||||
确认处理
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Connect(
|
||||
[NS_SAFETY_EVAL_BUSINESS],
|
||||
true,
|
||||
)(AntdTableFuncControl(RiskWarning));
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
.rw {
|
||||
&-stat-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&-stat-suffix {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&-note {
|
||||
padding: 8px 12px;
|
||||
background: #f6f8fa;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
&-project-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-customer-name {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue