From 2113d06d4216deec6bc8401021335f3171a5a5f8 Mon Sep 17 00:00:00 2001 From: tangjie <122778500@qq.com> Date: Mon, 20 Jul 2026 16:57:49 +0800 Subject: [PATCH] feat --- src/api/customer/index.js | 18 + src/api/safetyEvalBusiness/index.js | 7 + src/components/OrgInfoGuard/index.js | 35 ++ src/components/index.js | 2 +- src/enumerate/namespace/index.js | 1 + src/pages/Container/EnterpriseInfo/index.js | 40 +- src/pages/Container/Layout/menuConfig.js | 6 +- src/pages/Container/QualApplication/index.js | 37 +- .../CustomerManage/index.js | 408 ++++++++++++++++++ .../SafetyEvalBusiness/ProjectFlow/index.js | 144 +++++-- .../Container/SafetyEvalBusiness/index.js | 15 +- src/pages/Container/WpsEditor/index.js | 4 +- 12 files changed, 611 insertions(+), 106 deletions(-) create mode 100644 src/api/customer/index.js create mode 100644 src/components/OrgInfoGuard/index.js create mode 100644 src/pages/Container/SafetyEvalBusiness/CustomerManage/index.js diff --git a/src/api/customer/index.js b/src/api/customer/index.js new file mode 100644 index 0000000..b6b5520 --- /dev/null +++ b/src/api/customer/index.js @@ -0,0 +1,18 @@ +import { declareRequest } from "@cqsjjb/jjb-dva-runtime"; + +export const customerPage = declareRequest( + "customerLoading", + "Get > /safetyEval/customer/page", + 'customerList: [] | res.data?.records || [] & customerTotal: 0 | res.data?.total || 0', +); + +export const customerSave = declareRequest( + "customerSaveLoading", + "Post > @/safetyEval/customer/save", +); + +export const customerDetail = declareRequest( + "customerDetailLoading", + "Get > /safetyEval/customer/detail/{id}", + 'customerDetail: {} | res.data || {}', +); \ No newline at end of file diff --git a/src/api/safetyEvalBusiness/index.js b/src/api/safetyEvalBusiness/index.js index e69de29..d736c14 100644 --- a/src/api/safetyEvalBusiness/index.js +++ b/src/api/safetyEvalBusiness/index.js @@ -0,0 +1,7 @@ +import { declareRequest } from "@cqsjjb/jjb-dva-runtime"; + +export const queryMyOrCreateDoc = declareRequest( + "docLoading", + "Get > /safetyEval-h5/wsp/doc/my-or-create", + 'wpsDoc: {} | res.data || {}', +); \ No newline at end of file diff --git a/src/components/OrgInfoGuard/index.js b/src/components/OrgInfoGuard/index.js new file mode 100644 index 0000000..203b66f --- /dev/null +++ b/src/components/OrgInfoGuard/index.js @@ -0,0 +1,35 @@ +import React, { useEffect, useState } from "react"; +import { NS_ORG_INFO } from "~/enumerate/namespace"; +import { Connect } from "@cqsjjb/jjb-dva-runtime"; +import { Skeleton } from "antd"; + +/** 机构信息守卫:确保进入子路由前已有机构信息 */ +function OrgInfoGuard(props) { + const [orgInfoId, setOrgInfoId] = useState( + sessionStorage.getItem("orgInfoId"), + ); + + useEffect(() => { + if (!orgInfoId) { + props.orgInfoGet().then((res) => { + if (res?.data?.id) { + // 下方两行切勿更换顺序 + sessionStorage.setItem("orgInfoId", res?.data?.id); + setOrgInfoId(res?.data?.id); + } else { + props.history.push("/container/EnterpriseInfo/OrgInfo"); + } + }); + } + }, []); + + if (props.skipGuard) return props.children; + + return ( +
+ {orgInfoId ? props.children : } +
+ ); +} + +export default Connect([NS_ORG_INFO], true)(OrgInfoGuard); \ No newline at end of file diff --git a/src/components/index.js b/src/components/index.js index cb0ff5c..1cca838 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1 +1 @@ -export {}; +export { default as OrgInfoGuard } from "./OrgInfoGuard"; \ No newline at end of file diff --git a/src/enumerate/namespace/index.js b/src/enumerate/namespace/index.js index a1b9996..35fabf1 100644 --- a/src/enumerate/namespace/index.js +++ b/src/enumerate/namespace/index.js @@ -23,3 +23,4 @@ export const NS_QUAL_EXPERT = defineNamespace("qualExpert"); export const NS_DRIVER = defineNamespace("driver"); export const NS_RISK_CENTER = defineNamespace("riskCenter"); export const NS_SAFETY_EVAL_BUSINESS = defineNamespace("safetyEvalBusiness"); +export const NS_CUSTOMER = defineNamespace("customer"); diff --git a/src/pages/Container/EnterpriseInfo/index.js b/src/pages/Container/EnterpriseInfo/index.js index 9180068..e66f1ff 100644 --- a/src/pages/Container/EnterpriseInfo/index.js +++ b/src/pages/Container/EnterpriseInfo/index.js @@ -1,37 +1,11 @@ -import React, { useEffect, useState } from "react"; -import { NS_ORG_INFO } from "~/enumerate/namespace"; -import { Connect } from "@cqsjjb/jjb-dva-runtime"; -import { Skeleton } from "antd"; +import OrgInfoGuard from "~/components/OrgInfoGuard"; -/** 资质申请管理模块入口:需先有机构信息 */ -function EnterpriseInfo(props) { - const [orgInfoId, setOrgInfoId] = useState( - sessionStorage.getItem("orgInfoId"), - ); - useEffect(() => { - if (!orgInfoId) { - props.orgInfoGet().then((res) => { - if (res?.data?.id) { - // 下方两行切勿更换顺序 - sessionStorage.setItem("orgInfoId", res?.data?.id); - setOrgInfoId(res?.data?.id); - } - else { - props.history.push("/container/EnterpriseInfo/OrgInfo"); - } - }); - } - }, []); - - if (props.location.pathname.includes("EnterpriseInfo/OrgInfo")) { - return props.children; - } +export default function EnterpriseInfo(props) { + const skipGuard = props.location.pathname.includes("EnterpriseInfo/OrgInfo"); return ( -
- {orgInfoId ? props.children : } -
+ + {props.children} + ); -} - -export default Connect([NS_ORG_INFO], true)(EnterpriseInfo); +} \ No newline at end of file diff --git a/src/pages/Container/Layout/menuConfig.js b/src/pages/Container/Layout/menuConfig.js index 99c3c1a..d57a1be 100644 --- a/src/pages/Container/Layout/menuConfig.js +++ b/src/pages/Container/Layout/menuConfig.js @@ -202,7 +202,11 @@ const menuItems = [ label: "我的项目", icon: , }, - + { + key: "/safetyEval/container/SafetyEvalBusiness/CustomerManage", + label: "安评客户管理", + icon: , + }, ], }, { diff --git a/src/pages/Container/QualApplication/index.js b/src/pages/Container/QualApplication/index.js index a7fcfe0..090bb06 100644 --- a/src/pages/Container/QualApplication/index.js +++ b/src/pages/Container/QualApplication/index.js @@ -1,34 +1,5 @@ -import React, { useEffect, useState } from "react"; -import { NS_ORG_INFO } from "~/enumerate/namespace"; -import { Connect } from "@cqsjjb/jjb-dva-runtime"; -import { Skeleton } from "antd"; +import OrgInfoGuard from "~/components/OrgInfoGuard"; -/** 资质申请管理模块入口:需先有机构信息 */ -function QualApplication(props) { - const [orgInfoId, setOrgInfoId] = useState( - sessionStorage.getItem("orgInfoId"), - ); - useEffect(() => { - if (!orgInfoId) { - props.orgInfoGet().then((res) => { - if (res?.data?.id) { - // 下方两行切勿更换顺序 - sessionStorage.setItem("orgInfoId", res?.data?.id); - setOrgInfoId(res?.data?.id); - - } - else{ - props.history.push("/container/EnterpriseInfo/OrgInfo"); - } - }); - } - }, []); - - return ( -
- {orgInfoId ? props.children : } -
- ); -} - -export default Connect([NS_ORG_INFO], true)(QualApplication); +export default function QualApplication(props) { + return {props.children}; +} \ No newline at end of file diff --git a/src/pages/Container/SafetyEvalBusiness/CustomerManage/index.js b/src/pages/Container/SafetyEvalBusiness/CustomerManage/index.js new file mode 100644 index 0000000..e881010 --- /dev/null +++ b/src/pages/Container/SafetyEvalBusiness/CustomerManage/index.js @@ -0,0 +1,408 @@ +import React, { useState, useCallback } from "react"; +import { Form, Table, Button, Modal, Input, Select, Row, Col, message, Descriptions } 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"; +import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper"; +import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd"; +import { NS_CUSTOMER } from "~/enumerate/namespace"; +import { Connect } from "@cqsjjb/jjb-dva-runtime"; + +const MOCK_LIST = [ + { + id: 1, name: "重庆华安安全科技有限公司", contact: "周经理", phone: "138****5621", + region: "渝北区", projectCount: 6, + creditCode: "91500112MA5UQ001", industry: "化工生产", address: "重庆市渝北区化工园区东路18号", + status: "正常经营", location: "重庆市渝北区 · 东经106.63° / 北纬29.72°", + principal: "周海峰", principalPhone: "138****5621", legalPerson: "周海峰", legalPhone: "138****5621", scale: "中型企业", + }, + { + id: 2, name: "重庆鼎信安全咨询有限公司", contact: "刘总", phone: "139****1120", + region: "南岸区", projectCount: 4, + creditCode: "91500108MA61A1120", industry: "建筑施工", address: "重庆市南岸区茶园新区通江大道96号", + status: "正常经营", location: "重庆市南岸区 · 东经106.66° / 北纬29.48°", + principal: "刘志强", principalPhone: "139****1120", legalPerson: "刘志强", legalPhone: "139****1120", scale: "小型企业", + }, + { + id: 3, name: "重庆安环检测技术有限公司", contact: "陈主任", phone: "136****9088", + region: "江北区", projectCount: 9, + creditCode: "91500105MA60K9088", industry: "其他工贸", address: "重庆市江北区港城工业园A区", + status: "正常经营", location: "重庆市江北区 · 东经106.65° / 北纬29.63°", + principal: "陈海林", principalPhone: "136****9088", legalPerson: "陈海林", legalPhone: "136****9088", scale: "中型企业", + }, + { + id: 4, name: "重庆环宇投资有限公司", contact: "罗经理", phone: "135****8710", + region: "沙坪坝区", projectCount: 3, + creditCode: "91500106MA60K9099", industry: "危险化学品经营", address: "重庆市沙坪坝区三峡广场88号", + status: "正常经营", location: "重庆市沙坪坝区 · 东经106.46° / 北纬29.56°", + principal: "罗建国", principalPhone: "135****8710", legalPerson: "罗建国", legalPhone: "135****8710", scale: "大型企业", + }, +]; + +const PLATFORM_ENTERPRISES = [ + { name: "重庆华安安全科技有限公司", code: "91500112MA5UQ001", region: "渝北区", industry: "化工生产", status: "正常经营", principal: "周海峰", legal: "周海峰" }, + { name: "重庆鼎信安全咨询有限公司", code: "91500108MA61A1120", region: "南岸区", industry: "建筑施工", status: "正常经营", principal: "刘志强", legal: "刘志强" }, + { name: "重庆安环检测技术有限公司", code: "91500105MA60K9088", region: "江北区", industry: "其他工贸", status: "正常经营", principal: "陈海林", legal: "陈海林" }, +]; + +const REGION_OPTIONS = ["渝北区", "南岸区", "江北区", "沙坪坝区", "九龙坡区", "北碚区", "巴南区"]; +const INDUSTRY_OPTIONS = ["化工生产", "危险化学品经营", "金属非金属矿山", "金属冶炼", "建筑施工", "加油站", "其他工贸"]; +const STATUS_OPTIONS = ["正常经营", "停产整改", "停业", "注销"]; +const SCALE_OPTIONS = ["大型企业", "中型企业", "小型企业", "微型企业"]; + +const CustomerManage = (props) => { + const [searchForm] = Form.useForm(); + const [modalForm] = Form.useForm(); + const [dataSource, setDataSource] = useState(MOCK_LIST); + const [modalOpen, setModalOpen] = useState(false); + const [detailOpen, setDetailOpen] = useState(false); + const [currentDetail, setCurrentDetail] = useState(null); + const [createMode, setCreateMode] = useState("platform"); // "platform" | "new" + const [selectedPlatform, setSelectedPlatform] = useState(null); + + const handleSearch = useCallback((values) => { + const { name, contact, region } = values || {}; + const filtered = MOCK_LIST.filter((item) => { + if (name && !item.name.includes(name)) return false; + if (contact && !item.contact.includes(contact)) return false; + if (region && item.region !== region) return false; + return true; + }); + setDataSource(filtered); + }, []); + + const handleReset = useCallback(() => { + searchForm.resetFields(); + setDataSource(MOCK_LIST); + }, [searchForm]); + + const handleOpenCreate = useCallback(() => { + setCreateMode("platform"); + setSelectedPlatform(null); + modalForm.resetFields(); + setModalOpen(true); + }, [modalForm]); + + const handlePlatformSelect = useCallback((name) => { + const ep = PLATFORM_ENTERPRISES.find((e) => e.name === name); + if (ep) { + setSelectedPlatform(ep); + modalForm.setFieldsValue({ + creditCode: ep.code, + region: ep.region, + industry: ep.industry, + status: ep.status, + principal: ep.principal, + legalPerson: ep.legal, + }); + } + }, [modalForm]); + + const handleCreateSubmit = useCallback(() => { + modalForm.validateFields().then((values) => { + const newCustomer = { + id: dataSource.length + 1, + name: createMode === "platform" ? values.platformEnterprise : values.name, + contact: values.contact || "待完善", + phone: values.phone || "—", + region: values.region || "待完善", + projectCount: 0, + creditCode: values.creditCode || "", + industry: values.industry || "", + address: values.address || "", + status: values.status || "正常经营", + location: values.location || "", + principal: values.principal || "", + principalPhone: values.phone || "", + legalPerson: values.legalPerson || "", + legalPhone: values.legalPhone || "", + scale: values.scale || "", + }; + setDataSource((prev) => [newCustomer, ...prev]); + setModalOpen(false); + message.success("安评客户已新建"); + }).catch(() => {}); + }, [modalForm, dataSource.length, createMode]); + + const handleViewDetail = useCallback((record) => { + setCurrentDetail(record); + setDetailOpen(true); + }, []); + + const columns = [ + { title: "序号", width: 60, render: (_, __, index) => index + 1 }, + { title: "客户名称", dataIndex: "name", ellipsis: true }, + { title: "客户联系人", dataIndex: "contact", width: 120 }, + { title: "联系人电话", dataIndex: "phone", width: 130 }, + { title: "属地", dataIndex: "region", width: 100 }, + { + title: "服务项目数", dataIndex: "projectCount", width: 110, + render: (count) => ( + + ), + }, + { + title: "操作", width: 80, fixed: "right", + render: (_, record) => ( + + ), + }, + ]; + + return ( + } onClick={handleOpenCreate}> + 新建客户 + + } + > +
+ 机构管理员维护服务客户,查看客户基本信息及历次安全评价项目。 +
+ + + + , + + + , + + + {REGION_OPTIONS.map((r) => ( + {r} + ))} + + , + ]} + onFinish={handleSearch} + onReset={handleReset} + style={{ marginBottom: 16 }} + /> + +
+ 服务项目数可进入该客户的评价项目列表;操作栏仅查看客户详情。 +
+ + `共 ${total} 条`, + pageSize: 10, + }} + /> + + {/* 新建客户弹窗 */} + setModalOpen(false)} + onOk={handleCreateSubmit} + width={800} + destroyOnClose + > +
+ 优先从平台企业库选择;平台没有时可新建客户信息。 +
+
+ + + + + {createMode === "platform" ? ( + + + + ) : ( + + + + )} + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* 客户详情弹窗 */} + setDetailOpen(false)} + footer={} + width={700} + destroyOnHidden + > + {currentDetail && ( + <> + + {currentDetail.name} + + {currentDetail.status} + + {currentDetail.creditCode} + 重庆市 + {currentDetail.contact} + {currentDetail.phone} + {currentDetail.address} + + +

安全评价项目列表

+
{s}, + }, + ]} + pagination={false} + scroll={{ x: 1200,y: props.scrollY }} + + /> + + )} + + + ); +}; + +export default Connect( + [NS_CUSTOMER], + true, +)(AntdTableFuncControl(CustomerManage)); \ No newline at end of file diff --git a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/index.js b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/index.js index b5fcda2..c9ca098 100644 --- a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/index.js +++ b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/index.js @@ -1,7 +1,9 @@ -import React, { useState } from "react"; -import { Tabs, Tag, Button, Flex } from "antd"; +import React, { useState, useEffect } from "react"; +import { Tabs, Tag, Flex } from "antd"; import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout"; import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd"; +import { Connect } from "@cqsjjb/jjb-dva-runtime"; +import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace"; import WpsEditor from "~/pages/Container/WpsEditor"; import RiskAnalysisContent from "./RiskAnalysisContent"; import ContractContent from "./ContractContent"; @@ -14,40 +16,123 @@ import ControlContent from "./ControlContent"; import "./index.less"; 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: "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, + }, ]; const ProjectFlow = (props) => { const [activeKey, setActiveKey] = useState("risk"); const [visible, setVisible] = useState(false); + const { wpsDoc } = props.safetyEvalBusiness || {}; const handleTabChange = (key) => { if (key === "report") { - setVisible(true); + openReportEditor(); return; } setActiveKey(key); }; + const openReportEditor = async () => { + setVisible(true); + }; + + useEffect(() => { + props.queryMyOrCreateDoc({ + docxUrl: '' + }); + }, []); + const tabItems = PROCESS_TABS.map((item) => { let children = null; switch (item.key) { - case "risk": children = ; break; - case "contract": children = ; break; - case "team": children = ; break; - case "survey": children = ; break; - - case "internal": children = ; break; - case "technical": children = ; break; - case "control": children = ; break; - default: children = null; + case "risk": + children = ; + break; + case "contract": + children = ; + break; + case "team": + children = ; + break; + case "survey": + children = ; + break; + + case "internal": + children = ; + break; + case "technical": + children = ; + break; + case "control": + children = ; + break; + default: + children = null; } return { @@ -56,7 +141,9 @@ const ProjectFlow = (props) => { label: ( {item.index} {item.label} - {item.state} + + {item.state} + ), children, @@ -68,16 +155,23 @@ const ProjectFlow = (props) => { history={props.history} previous title={ -
+
项目法定流程工作台 -
华安化工园区安全预评价 | XP-2026-001
+
+ 华安化工园区安全预评价 | XP-2026-001 +
} > - {visible && setVisible(false)} />} + {visible && ( + setVisible(false)} fileId={wpsDoc?.fileId} /> + )} ); }; -export default AntdTableFuncControl(ProjectFlow); \ No newline at end of file +export default Connect( + [NS_SAFETY_EVAL_BUSINESS], + true, +)(AntdTableFuncControl(ProjectFlow)); diff --git a/src/pages/Container/SafetyEvalBusiness/index.js b/src/pages/Container/SafetyEvalBusiness/index.js index baee37d..2eb2988 100644 --- a/src/pages/Container/SafetyEvalBusiness/index.js +++ b/src/pages/Container/SafetyEvalBusiness/index.js @@ -1,12 +1,5 @@ +import OrgInfoGuard from "~/components/OrgInfoGuard"; -import React from "react"; - -function SafetyEvalBusiness(props) { - return ( - - props.children - - ); -} - -export default SafetyEvalBusiness; +export default function SafetyEvalBusiness(props) { + return {props.children}; +} \ No newline at end of file diff --git a/src/pages/Container/WpsEditor/index.js b/src/pages/Container/WpsEditor/index.js index 57b6529..a3613e6 100644 --- a/src/pages/Container/WpsEditor/index.js +++ b/src/pages/Container/WpsEditor/index.js @@ -16,7 +16,7 @@ import { createPortal } from "react-dom"; import "./index.less"; const App = (props) => { - const { onCancel } = props; + const { onCancel, fileId } = props; const [expandedMap, setExpandedMap] = useState({}); const sdkRef = useRef(null); @@ -40,7 +40,7 @@ const App = (props) => { officeType: WebOfficeSDK.OfficeType.Writer, appId: "SX20260717TSGKEA", mount: document.getElementById("app-sbsb"), - fileId: "123", + fileId: fileId || "123", token: sessionStorage.getItem("token"), }); await instance.ready();