dev_1.2
tangjie 2026-07-28 16:45:46 +08:00
parent 6156138a55
commit c755e472bc
7 changed files with 351 additions and 195 deletions

View File

@ -12,8 +12,8 @@ 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
},

View File

@ -0,0 +1,154 @@
import { useState, useEffect } from "react";
import { Modal, Button, Descriptions, Table, Tag } from "antd";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import {
ENTERPRISE_STATUS_OPTIONS,
ENTERPRISE_SCALE_OPTIONS,
EVAL_TYPE_MAP,
} from "~/enumerate/constant";
import { QUALIFICATION_INDUSTRY_OPTIONS_MAP } from "~/enumerate/enterpriseOptions";
const CustomerDetailModal = ({ open, customerId, onCancel, ...props }) => {
const [detail, setDetail] = useState(null);
const { customerDetailLoading } = props.safetyEvalBusiness || {};
useEffect(() => {
if (!open || !customerId) {
setDetail(null);
return;
}
props.customerDetail({ id: customerId }).then((res) => {
if (res?.success !== false) {
setDetail(res?.data || null);
}
});
}, [open, customerId]);
return (
<Modal
title={`客户详情 - ${detail?.customerName || ""}`}
open={open}
loading={customerDetailLoading}
onCancel={onCancel}
footer={<Button onClick={onCancel}>关闭</Button>}
width={700}
destroyOnHidden
>
{detail && (
<>
<Descriptions
column={2}
bordered
size="small"
style={{ marginBottom: 16 }}
>
<Descriptions.Item label="客户名称">
{detail.customerName}
</Descriptions.Item>
<Descriptions.Item label="统一社会信用代码">
{detail.creditCode}
</Descriptions.Item>
<Descriptions.Item label="企业状态">
{(() => {
const statusOption = ENTERPRISE_STATUS_OPTIONS.find(
(opt) => opt.value === detail.enterpriseStatusCode,
);
return statusOption?.label || detail.enterpriseStatusName;
})()}
</Descriptions.Item>
<Descriptions.Item label="企业规模">
{(() => {
const scaleOption = ENTERPRISE_SCALE_OPTIONS.find(
(opt) => opt.value === detail.enterpriseScaleCode,
);
return scaleOption?.label || detail.enterpriseScaleName;
})()}
</Descriptions.Item>
<Descriptions.Item label="所属行业">
{detail.industryCode
?.split(",")
.map(
(code) =>
QUALIFICATION_INDUSTRY_OPTIONS_MAP[code.trim()] ||
code.trim(),
)
.join("、")}
</Descriptions.Item>
<Descriptions.Item label="属地">
{detail.districtName}
</Descriptions.Item>
<Descriptions.Item label="联系人">
{detail.principalName}
</Descriptions.Item>
<Descriptions.Item label="联系电话">
{detail.principalPhone}
</Descriptions.Item>
<Descriptions.Item label="主要负责人">
{detail.principalName}
</Descriptions.Item>
<Descriptions.Item label="负责人电话">
{detail.principalPhone}
</Descriptions.Item>
<Descriptions.Item label="法定代表人">
{detail.legalRepresentative}
</Descriptions.Item>
<Descriptions.Item label="法人电话">
{detail.legalRepresentativePhone}
</Descriptions.Item>
<Descriptions.Item label="经营地址" span={2}>
{detail.businessAddress}
</Descriptions.Item>
<Descriptions.Item label="经度">
{detail.longitude}
</Descriptions.Item>
<Descriptions.Item label="纬度">
{detail.latitude}
</Descriptions.Item>
</Descriptions>
<h4 style={{ margin: "12px 0 8px" }}>安全评价项目列表</h4>
<Table
rowKey="id"
dataSource={detail.projects || []}
columns={[
{ title: "项目名称", dataIndex: "projectName" },
{
title: "评价类型",
dataIndex: "evalTypeCode",
width: 120,
render: (s) => EVAL_TYPE_MAP[s] || s || "",
},
{ title: "报告完成日期", dataIndex: "planEndDate", width: 130 },
{
title: "项目状态",
dataIndex: "status",
width: 100,
fixed: "right",
render: (phase, record) => {
if (record.archiveFlag === 1) {
return <Tag color="success">已归档</Tag>;
}
if (record.projectPhaseCode === "NORMAL") {
return <Tag color="success">正常</Tag>;
}
if (record.projectPhaseCode === "DELAY") {
return <Tag color="error">已延期</Tag>;
}
return record.projectPhaseName;
},
},
]}
pagination={false}
size="small"
/>
</>
)}
</Modal>
);
};
export default Connect(
[NS_SAFETY_EVAL_BUSINESS],
true,
)(CustomerDetailModal);

View File

@ -373,20 +373,22 @@ export const PROGRESS_STATUS_MAP = {
/** 安评业务提醒 — 提醒等级映射 */
export const WARING_STATUS_MAP = {
first: { color: "success", text: "首次预警" },
second: { color: "warning", text: "二次提醒" },
urgent: { color: "error", text: "紧急预警" },
overdue: { color: "default", text: "已逾期" },
normal: { color: "default", text: "正常" },
first_waring: { color: "success", text: "首次预警" },
second_waring: { color: "warning", text: "二次预警" },
emergency_waring: { color: "error", text: "紧急预警" },
overdue_warning: { color: "default", text: "逾期预警" },
end_warning: { color: "default", text: "结束预警" },
non_warn: { color: "default", text: "暂时不预警" },
};
/** 安评业务提醒 — 提醒等级筛选选项 */
export const WARING_STATUS_OPTIONS = [
{ label: "首次预警6个月", value: "first" },
{ label: "二次提醒3个月", value: "second" },
{ label: "紧急预警1个月", value: "urgent" },
{ label: "已逾期", value: "overdue" },
{ label: "正常", value: "normal" },
{ label: "暂时不预警", value: "non_warn" },
{ label: "首次预警", value: "first_waring" },
{ label: "二次预警", value: "second_waring" },
{ label: "紧急预警", value: "emergency_waring" },
{ label: "逾期预警", value: "overdue_warning" },
{ label: "结束预警", value: "end_warning" },
];
export const NODE_LABELS = {

View File

@ -137,11 +137,6 @@ const menuItems = [
},
],
},
{
key: "/safetyEval/container/MonitorManage/RiskCenter",
label: "风险预警中心",
icon: <ExperimentOutlined />,
},
{
key: "/safetyEval/container/Supervision/InspectionNotice",
label: "监督检查告知",

View File

@ -16,24 +16,24 @@ 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 { WARING_STATUS_MAP, WARING_STATUS_OPTIONS } from "~/enumerate/constant";
import {
QUALIFICATION_INDUSTRY_OPTIONS,
QUALIFICATION_INDUSTRY_OPTIONS_MAP,
} from "~/enumerate/enterpriseOptions";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { tools } from "@cqsjjb/jjb-common-lib";
import CustomerDetailModal from "~/components/CustomerDetailModal";
import "./index.less";
const { router } = tools;
const INDUSTRY_OPTIONS = [
{ label: "危险化学品", value: "DANGEROUS_CHEMICAL" },
{ label: "非煤矿山", value: "NON_COAL_MINE" },
{ label: "城镇燃气", value: "URBAN_GAS" },
{ label: "一般工贸", value: "GENERAL_TRADE" },
];
const BusinessReminder = (props) => {
const [searchForm] = Form.useForm();
const [dataSource, setDataSource] = useState([]);
const [total, setTotal] = useState(0);
const [statData, setStatData] = useState({});
const [customerDetailOpen, setCustomerDetailOpen] = useState(false);
const [customerId, setCustomerId] = useState(null);
const { projectWaringRemindPageLoading } = props.safetyEvalBusiness;
@ -84,7 +84,8 @@ const BusinessReminder = (props) => {
};
const handleViewCustomer = (record) => {
// TODO: 查看企业资料
setCustomerId(record.customerId);
setCustomerDetailOpen(true);
};
const formatCycle = (days) => {
@ -99,19 +100,22 @@ const BusinessReminder = (props) => {
const renderWaringStatus = (status) => {
const cfg = WARING_STATUS_MAP[status];
if (!cfg) return <Tag>{status}</Tag>;
const labelMap = {
first: "首次预警 · 6个月",
second: "二次提醒 · 3个月",
urgent: "紧急预警 · 1个月",
overdue: "已逾期",
normal: "正常",
};
return <Tag color={cfg.color}>{labelMap[status] || cfg.text}</Tag>;
return <Tag color={cfg.color}>{cfg.text}</Tag>;
};
const columns = [
{ title: "客户名称", dataIndex: "customerName", width: 180, ellipsis: true },
{ title: "行业 / 场景", dataIndex: "industryCode", width: 160, ellipsis: true },
{
title: "业务范围",
dataIndex: "industryCode",
width: 160,
ellipsis: true,
render: (val) =>
val
?.split(",")
.map((code) => QUALIFICATION_INDUSTRY_OPTIONS_MAP[code])
.join("、"),
},
{ title: "最近报告完成日", dataIndex: "archiveDate", width: 140 },
{
title: "周期",
@ -203,16 +207,16 @@ const BusinessReminder = (props) => {
</Form.Item>,
<Form.Item key="industryCode" name="industryCode">
<ControlWrapper.Select
label="行业 / 场景"
placeholder="全部"
label="业务范围"
placeholder="请选择业务范围"
allowClear
options={INDUSTRY_OPTIONS}
options={QUALIFICATION_INDUSTRY_OPTIONS}
/>
</Form.Item>,
<Form.Item key="waringStatus" name="waringStatus">
<ControlWrapper.Select
label="提醒等级"
placeholder="全部"
placeholder="请选择提醒等级"
allowClear
options={WARING_STATUS_OPTIONS}
/>
@ -227,8 +231,8 @@ const BusinessReminder = (props) => {
下一次应开展日期 = 最近一次正式报告完成日期 + 对应行业评价周期
</span>
<Space>
<Button>导出清单</Button>
<Button type="primary">批量提醒</Button>
{/* <Button></Button>
<Button type="primary">批量提醒</Button> */}
</Space>
</div>
@ -248,6 +252,12 @@ const BusinessReminder = (props) => {
}}
onChange={handlePageChange}
/>
<CustomerDetailModal
open={customerDetailOpen}
customerId={customerId}
onCancel={() => setCustomerDetailOpen(false)}
/>
</PageLayout>
);
};

View File

@ -9,11 +9,12 @@ import {
Row,
Col,
message,
Descriptions,
Flex,
Tag,
Space,
Tooltip,
} from "antd";
import { PlusOutlined } from "@ant-design/icons";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
@ -22,12 +23,14 @@ import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import BaiduMapPicker from "~/components/BaiduMapPicker";
import AttachmentUpload from "~/components/AttachmentUpload";
import CustomerDetailModal from "~/components/CustomerDetailModal";
import { tools } from "@cqsjjb/jjb-common-lib";
import {
district,
ENTERPRISE_STATUS_OPTIONS,
ENTERPRISE_SCALE_OPTIONS,
EVAL_TYPE_MAP,
NODE_LABELS
} from "~/enumerate/constant";
import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions";
import { phoneRule, creditCodeRule } from "~/utils/validators";
@ -42,10 +45,16 @@ const CustomerManage = (props) => {
const [mapPickerVisible, setMapPickerVisible] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [detailOpen, setDetailOpen] = useState(false);
const [detailCustomerId, setDetailCustomerId] = useState(null);
const [projectModalOpen, setProjectModalOpen] = useState(false);
const [currentDetail, setCurrentDetail] = useState(null);
const [editingId, setEditingId] = useState(null);
const { customerLoading, customerModifyLoading, customerSaveLoading } =
props.safetyEvalBusiness;
const {
customerLoading,
customerModifyLoading,
customerSaveLoading,
customerDetailLoading,
} = props.safetyEvalBusiness;
const getData = async (pagination) => {
const params = {
@ -103,22 +112,32 @@ const CustomerManage = (props) => {
setModalOpen(true);
};
const handleOpenEdit = async (record) => {
const res = await props.customerDetail({ id: record.id });
if (res?.success !== false) {
const handleOpenEdit = (record) => {
setEditingId(record.id);
setCurrentDetail(null);
modalForm.resetFields();
setModalOpen(true);
props.customerDetail({ id: record.id }).then((res) => {
if (res?.success !== false) {
setCurrentDetail(res?.data || {});
modalForm.setFieldsValue(res?.data || {});
setModalOpen(true);
}
});
};
const handleViewDetail = async (record) => {
const res = await props.customerDetail({ id: record.id });
const handleViewDetail = (record) => {
setDetailCustomerId(record.id);
setDetailOpen(true);
};
const handleViewProjects = (record) => {
setCurrentDetail(null);
setProjectModalOpen(true);
props.customerDetail({ id: record.id }).then((res) => {
if (res?.success !== false) {
setCurrentDetail(res?.data || {});
setDetailOpen(true);
}
});
};
const handleDelete = async (record) => {
@ -182,8 +201,13 @@ const CustomerManage = (props) => {
title: "服务项目数",
dataIndex: "projectCount",
width: 110,
render: (count) => (
<Button type="link" size="small" style={{ padding: 0 }}>
render: (count, record) => (
<Button
type="link"
size="small"
onClick={() => handleViewProjects(record)}
>
{count || 0}
</Button>
),
@ -197,7 +221,6 @@ const CustomerManage = (props) => {
<Button
type="link"
size="small"
onClick={() => handleViewDetail(record)}
>
查看
@ -223,6 +246,14 @@ const CustomerManage = (props) => {
),
},
];
const parseProjectNode = (data) => {
if (Array.isArray(data)) return data;
try {
return JSON.parse(data || "[]");
} catch {
return [];
}
};
return (
<PageLayout
@ -300,6 +331,7 @@ const CustomerManage = (props) => {
<Modal
title={editingId ? "编辑客户" : "新建客户"}
open={modalOpen}
loading={editingId ? customerDetailLoading : false}
onCancel={() => setModalOpen(false)}
onOk={handleCreateSubmit}
confirmLoading={editingId ? customerModifyLoading : customerSaveLoading}
@ -440,123 +472,107 @@ const CustomerManage = (props) => {
</Modal>
{/* 客户详情弹窗 */}
<Modal
title={`客户详情 - ${currentDetail?.customerName || ""}`}
<CustomerDetailModal
open={detailOpen}
customerId={detailCustomerId}
onCancel={() => setDetailOpen(false)}
footer={<Button onClick={() => setDetailOpen(false)}>关闭</Button>}
width={700}
destroyOnHidden
>
{currentDetail && (
<>
<Descriptions
column={2}
bordered
size="small"
style={{ marginBottom: 16 }}
>
<Descriptions.Item label="客户名称">
{currentDetail.customerName}
</Descriptions.Item>
<Descriptions.Item label="统一社会信用代码">
{currentDetail.creditCode}
</Descriptions.Item>
<Descriptions.Item label="企业状态">
{(() => {
const statusOption = ENTERPRISE_STATUS_OPTIONS.find(
(opt) => opt.value === currentDetail.enterpriseStatusCode,
);
return (
statusOption?.label || currentDetail.enterpriseStatusName
);
})()}
</Descriptions.Item>
<Descriptions.Item label="企业规模">
{(() => {
const scaleOption = ENTERPRISE_SCALE_OPTIONS.find(
(opt) => opt.value === currentDetail.enterpriseScaleCode,
);
return (
scaleOption?.label || currentDetail.enterpriseScaleName
);
})()}
</Descriptions.Item>
<Descriptions.Item label="所属行业">
{currentDetail.industryName || currentDetail.industryCode}
</Descriptions.Item>
<Descriptions.Item label="属地">
{currentDetail.districtName}
</Descriptions.Item>
<Descriptions.Item label="联系人">
{currentDetail.principalName}
</Descriptions.Item>
<Descriptions.Item label="联系电话">
{currentDetail.principalPhone}
</Descriptions.Item>
<Descriptions.Item label="主要负责人">
{currentDetail.principalName}
</Descriptions.Item>
<Descriptions.Item label="负责人电话">
{currentDetail.principalPhone}
</Descriptions.Item>
<Descriptions.Item label="法定代表人">
{currentDetail.legalRepresentative}
</Descriptions.Item>
<Descriptions.Item label="法人电话">
{currentDetail.legalRepresentativePhone}
</Descriptions.Item>
<Descriptions.Item label="经营地址" span={2}>
{currentDetail.businessAddress}
</Descriptions.Item>
<Descriptions.Item label="经度">
{currentDetail.longitude}
</Descriptions.Item>
<Descriptions.Item label="纬度">
{currentDetail.latitude}
</Descriptions.Item>
</Descriptions>
<h4 style={{ margin: "12px 0 8px" }}>安全评价项目列表</h4>
<Table
rowKey="id"
dataSource={currentDetail.projects || []}
columns={[
{ title: "项目名称", dataIndex: "projectName" },
{
title: "评价类型",
dataIndex: "evalTypeCode",
width: 120,
render: (s) => EVAL_TYPE_MAP[s] || s || "",
},
{ title: "报告完成日期", dataIndex: "planEndDate", width: 130 },
{
title: "项目状态",
dataIndex: "status",
width: 100,
fixed: "right",
render: (phase) => {
const color =
phase === "延期"
? "error"
: phase === "正常"
? "success"
: undefined;
return <Tag color={color}>{phase || "-"}</Tag>;
},
},
]}
pagination={false}
size="small"
/>
</>
)}
</Modal>
<BaiduMapPicker
visible={mapPickerVisible}
onCancel={() => setMapPickerVisible(false)}
onConfirm={handleMapConfirm}
/>
{/* 服务项目列表弹窗 */}
<Modal
title={`服务项目列表 - ${currentDetail?.customerName || ""}`}
open={projectModalOpen}
loading={customerDetailLoading}
onCancel={() => setProjectModalOpen(false)}
footer={
<Button onClick={() => setProjectModalOpen(false)}>关闭</Button>
}
width={800}
destroyOnHidden
>
<Table
rowKey="id"
dataSource={currentDetail?.projects || []}
columns={[
{ title: "项目名称", dataIndex: "projectName" },
{
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: "负责人", dataIndex: "projectLeaderName", width: 130 },
{
title: "项目状态",
dataIndex: "status",
width: 100,
fixed: "right",
render: (phase, record) => {
if (record.archiveFlag === 1) {
return <Tag color="success">已归档</Tag>;
}
if (record.projectPhaseCode === "NORMAL") {
return <Tag color="success">正常</Tag>;
}
if (record.projectPhaseCode === "DELAY") {
return <Tag color="error">已延期</Tag>;
}
return record.projectPhaseName;
},
},
{
title: "操作",
width: 100,
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => {
props.history.push(`/container/safetyEvalBusiness/ProjectFlow?id=${record.id}`)
}}
>
查看
</Button>
</Space>
),
}
]}
pagination={false}
size="small"
/>
</Modal>
</PageLayout>
);
};

View File

@ -79,17 +79,7 @@ const RiskWarning = (props) => {
getData();
};
const handleCloseRisk = async (record) => {
Modal.confirm({
title: "确认闭环",
content: `确定将"${record.projectName}"的风险标记为闭环吗?`,
onOk: async () => {
// TODO: 调用闭环接口
message.success("已闭环");
getData();
},
});
};
const columns = [
{ title: "项目编号", dataIndex: "projectNo", width: 130 },
@ -130,18 +120,7 @@ const RiskWarning = (props) => {
return cfg ? <Tag color={cfg.color}>{cfg.text}</Tag> : "";
},
},
{
title: "操作",
width: 130,
fixed: "right",
render: (_, record) => (
<Space>
<Button type="link" size="small" onClick={() => handleCloseRisk(record)}>
闭环
</Button>
</Space>
),
},
];
return (