diff --git a/jjb.config.js b/jjb.config.js index db76147..a5d9716 100644 --- a/jjb.config.js +++ b/jjb.config.js @@ -10,9 +10,9 @@ module.exports = { javaGitBranch: "dev", // 本地联调 safetyEval-service(context-path: /safetyEval,默认端口 8095) // 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095 - API_HOST: "https://gbs-gateway.qhdsafety.com", + //API_HOST: "https://gbs-gateway.qhdsafety.com", //API_HOST: "http://192.168.0.150", //太浅 - //API_HOST: "http://192.168.0.152", + API_HOST: "http://192.168.0.152", }, production: { // 应用后端分支名称,部署上线需要 diff --git a/src/api/customer/index.js b/src/api/customer/index.js deleted file mode 100644 index b6b5520..0000000 --- a/src/api/customer/index.js +++ /dev/null @@ -1,18 +0,0 @@ -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 ef2c2d0..8b69ffa 100644 --- a/src/api/safetyEvalBusiness/index.js +++ b/src/api/safetyEvalBusiness/index.js @@ -16,4 +16,50 @@ export const createWpsDoc = declareRequest( "createDocLoading", "Post > @/safetyEval-h5/wsp/doc/create", 'wpsDoc: {} | res.data || {}', +); + +export const customerPage = declareRequest( + "customerLoading", + "Get > /safetyEval/institution/eval-customer/page", + 'customerPage: [] | res.data', +); + +export const customerSave = declareRequest( + "customerSaveLoading", + "Post > @/safetyEval/institution/eval-customer/save", +); + +export const customerModify = declareRequest( + "customerModifyLoading", + "Post > @/safetyEval/institution/eval-customer/modify", +); + +export const customerDetail = declareRequest( + "customerDetailLoading", + "Get > /safetyEval/institution/eval-customer/get", +); + +export const customerDelete = declareRequest( + "customerDeleteLoading", + "Post > @/safetyEval/institution/eval-customer/delete", +); + +export const customerProjectPage = declareRequest( + "customerProjectLoading", + "Get > /safetyEval/institution/eval-customer/project/page", +); + +export const evalProjectPage = declareRequest( + "evalProjectLoading", + "Get > /safetyEval/institution/eval-project/page", +); + +export const evalProjectSave = declareRequest( + "evalProjectSaveLoading", + "Post > @/safetyEval/institution/eval-project/save", +); + +export const evalProjectDetail = declareRequest( + "evalProjectDetailLoading", + "Get > /safetyEval/institution/eval-project/get", ); \ No newline at end of file diff --git a/src/components/BaiduMapPicker/index.js b/src/components/BaiduMapPicker/index.js index bf70154..cd30c24 100644 --- a/src/components/BaiduMapPicker/index.js +++ b/src/components/BaiduMapPicker/index.js @@ -8,6 +8,7 @@ const BaiduMapPicker = ({ visible, onCancel, onConfirm }) => { const markerRef = useRef(null); const [loaded, setLoaded] = useState(false); const [selectedPoint, setSelectedPoint] = useState(null); + const [selectedAddress, setSelectedAddress] = useState(''); // 动态加载百度地图脚本 useEffect(() => { @@ -48,12 +49,21 @@ const BaiduMapPicker = ({ visible, onCancel, onConfirm }) => { map.addEventListener('click', (e) => { const point = e.point; setSelectedPoint(point); + setSelectedAddress(''); if (markerRef.current) { map.removeOverlay(markerRef.current); } const marker = new window.BMap.Marker(point); map.addOverlay(marker); markerRef.current = marker; + + // 逆地理编码获取地址 + const geocoder = new window.BMap.Geocoder(); + geocoder.getLocation(point, (result) => { + if (result?.address) { + setSelectedAddress(result.address); + } + }); }); // 搜索自动完成 @@ -69,6 +79,7 @@ const BaiduMapPicker = ({ visible, onCancel, onConfirm }) => { const poi = results.getPoi(0); const point = poi.point; setSelectedPoint(point); + setSelectedAddress(poi.address || poi.title || ''); map.centerAndZoom(point, 15); if (markerRef.current) { map.removeOverlay(markerRef.current); @@ -96,6 +107,7 @@ const BaiduMapPicker = ({ visible, onCancel, onConfirm }) => { if (!visible) { setLoaded(false); setSelectedPoint(null); + setSelectedAddress(''); markerRef.current = null; mapInstanceRef.current = null; } @@ -106,9 +118,11 @@ const BaiduMapPicker = ({ visible, onCancel, onConfirm }) => { message.warning('请在地图上选择位置'); return; } + onConfirm({ lng: selectedPoint.lng, lat: selectedPoint.lat, + address: selectedAddress, }); }; diff --git a/src/enumerate/constant/index.js b/src/enumerate/constant/index.js index 8c96ea7..9757976 100644 --- a/src/enumerate/constant/index.js +++ b/src/enumerate/constant/index.js @@ -28,7 +28,6 @@ export const EXPERT_STATUS_MAP = { pending: { label: "待核验", color: "blue" }, arranging: { label: "待安排", color: "warning" }, passed: { label: "已核验", color: "success" }, - }; /** 公示状态 */ @@ -48,10 +47,9 @@ export const FILING_STATUS_MAP = { export const FILING_STATUS_MAP_CHANGE = { 1: { label: "已备案", color: "success" }, - + 3: { label: "已打回", color: "error" }, 4: { label: "变更审核中", color: "processing" }, - }; /** 变更状态 */ @@ -90,7 +88,7 @@ export const GENDER_MAP = { /** 审核结果 */ export const REVIEW_RESULT_OPTIONS = [ { label: "通过", value: 1 }, - + { label: "不通过", value: 3 }, ]; @@ -143,26 +141,26 @@ export const CERT_TYPE_MAP = { /** 风险预警中心 — 预警大类映射 */ export const RISK_TYPE_MAP = { - "资质预警": { label: "资质预警", color: "error" }, - "项目预警": { label: "项目预警", color: "warning" }, + 资质预警: { label: "资质预警", color: "error" }, + 项目预警: { label: "项目预警", color: "warning" }, }; /** 风险预警中心 — 处理阶段映射 */ export const STAGE_MAP = { - "未处理": { label: "未处理", color: "error" }, - "已发机构": { label: "已发机构", color: "warning" }, - "待复核": { label: "待复核", color: "processing" }, - "已闭环": { label: "已闭环", color: "success" }, + 未处理: { label: "未处理", color: "error" }, + 已发机构: { label: "已发机构", color: "warning" }, + 待复核: { label: "待复核", color: "processing" }, + 已闭环: { label: "已闭环", color: "success" }, }; /** 风险预警中心 — 机构端状态映射 */ export const ORG_STATUS_MAP = { - "未发送": { label: "未发送", color: "error" }, - "机构未读": { label: "机构未读", color: "warning" }, - "已提交材料": { label: "已提交材料", color: "processing" }, - "处理中": { label: "处理中", color: "processing" }, - "已提交说明": { label: "已提交说明", color: "processing" }, - "整改完成": { label: "整改完成", color: "success" }, + 未发送: { label: "未发送", color: "error" }, + 机构未读: { label: "机构未读", color: "warning" }, + 已提交材料: { label: "已提交材料", color: "processing" }, + 处理中: { label: "处理中", color: "processing" }, + 已提交说明: { label: "已提交说明", color: "processing" }, + 整改完成: { label: "整改完成", color: "success" }, }; /** 风险预警中心 — 预警子类选项 */ @@ -176,4 +174,143 @@ export const RISK_SUBTYPE_OPTIONS = [ { label: "从业告知异常", value: "从业告知异常" }, { label: "现场勘查异常", value: "现场勘查异常" }, { label: "过程控制/归档异常", value: "过程控制/归档异常" }, +]; + +export const district = [ + { + code: "500101", + name: "万州区", + }, + { + code: "500102", + name: "涪陵区", + }, + { + code: "500103", + name: "渝中区", + }, + { + code: "500104", + name: "大渡口区", + }, + { + code: "500105", + name: "江北区", + }, + { + code: "500106", + name: "沙坪坝区", + }, + { + code: "500107", + name: "九龙坡区", + }, + { + code: "500108", + name: "南岸区", + }, + { + code: "500109", + name: "北碚区", + }, + { + code: "500110", + name: "綦江区", + }, + { + code: "500111", + name: "大足区", + }, + { + code: "500112", + name: "渝北区", + }, + { + code: "500113", + name: "巴南区", + }, + { + code: "500114", + name: "黔江区", + }, + { + code: "500115", + name: "长寿区", + }, + { + code: "500116", + name: "江津区", + }, + { + code: "500117", + name: "合川区", + }, + { + code: "500118", + name: "永川区", + }, + { + code: "500119", + name: "南川区", + }, + { + code: "500120", + name: "璧山区", + }, + { + code: "500151", + name: "铜梁区", + }, + { + code: "500152", + name: "潼南区", + }, + { + code: "500153", + name: "荣昌区", + }, + { + code: "500154", + name: "开州区", + }, + { + code: "500155", + name: "梁平区", + }, + { + code: "500156", + name: "武隆区", + }, +]; + +/** 企业状态:1正常经营 2停产整改 3停业 4注销 */ +export const ENTERPRISE_STATUS_OPTIONS = [ + { label: "正常经营", value: 1 }, + { label: "停产整改", value: 2 }, + { label: "停业", value: 3 }, + { label: "注销", value: 4 }, +]; + +/** 企业规模:LARGE 大型 MEDIUM 中型 SMALL 小型 MICRO 微型 */ +export const ENTERPRISE_SCALE_OPTIONS = [ + { label: "大型", value: "LARGE" }, + { label: "中型", value: "MEDIUM" }, + { label: "小型", value: "SMALL" }, + { label: "微型", value: "MICRO" }, +]; + + +/** 评价类型选项 */ +export const EVAL_TYPE_OPTIONS = [ + { label: "安全预评价", value: "PRE" }, + { label: "安全设施竣工验收评价", value: "ACCEPT" }, + { label: "安全现状评价", value: "STATUS" }, +]; + +/** 登记半径选项 */ +export const CHECKIN_RADIUS_OPTIONS = [ + { label: "100 米", value: 100 }, + { label: "200 米", value: 200 }, + { label: "300 米", value: 300 }, + { label: "500 米", value: 500 }, ]; \ No newline at end of file diff --git a/src/enumerate/enterpriseOptions.js b/src/enumerate/enterpriseOptions.js index c9525af..abe601b 100644 --- a/src/enumerate/enterpriseOptions.js +++ b/src/enumerate/enterpriseOptions.js @@ -154,6 +154,12 @@ export const PROFESSIONAL_LEVEL_OPTIONS = [ }, ]; +export const PROFESSIONAL_LEVEL_OPTIONS_MAP = + PROFESSIONAL_LEVEL_OPTIONS.reduce( + (acc, cur) => ({ ...acc, [cur.value]: cur.label }), + {}, + ); + /** 学历类型 */ export const EDUCATION_TYPE_OPTIONS = [ { label: "全日制", value: "全日制" }, diff --git a/src/enumerate/namespace/index.js b/src/enumerate/namespace/index.js index 35fabf1..a1b9996 100644 --- a/src/enumerate/namespace/index.js +++ b/src/enumerate/namespace/index.js @@ -23,4 +23,3 @@ 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/Layout/menuConfig.js b/src/pages/Container/Layout/menuConfig.js index ee72eb8..3fd1035 100644 --- a/src/pages/Container/Layout/menuConfig.js +++ b/src/pages/Container/Layout/menuConfig.js @@ -206,6 +206,11 @@ const menuItems = [ label: "我的项目", icon: , }, + { + key: "/safetyEval/container/SafetyEvalBusiness/EvalProject/List", + label: "安评项目管理", + icon: , + }, { key: "/safetyEval/container/SafetyEvalBusiness/CustomerManage", label: "安评客户管理", diff --git a/src/pages/Container/SafetyEvalBusiness/CustomerManage/index.js b/src/pages/Container/SafetyEvalBusiness/CustomerManage/index.js index f90c3b4..ba92b40 100644 --- a/src/pages/Container/SafetyEvalBusiness/CustomerManage/index.js +++ b/src/pages/Container/SafetyEvalBusiness/CustomerManage/index.js @@ -1,150 +1,211 @@ -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 React, { useState, useCallback, useEffect } from "react"; +import { + Form, + Table, + Button, + Modal, + Input, + Select, + Row, + Col, + message, + Descriptions, + Flex, + Space, +} 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 { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace"; import { Connect } from "@cqsjjb/jjb-dva-runtime"; +import BaiduMapPicker from "~/components/BaiduMapPicker"; +import { tools } from "@cqsjjb/jjb-common-lib"; +import { + district, + ENTERPRISE_STATUS_OPTIONS, + ENTERPRISE_SCALE_OPTIONS, +} from "~/enumerate/constant"; +import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions"; +import { phoneRule, creditCodeRule } from "~/utils/validators"; -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 { router } = tools; const CustomerManage = (props) => { const [searchForm] = Form.useForm(); const [modalForm] = Form.useForm(); - const [dataSource, setDataSource] = useState(MOCK_LIST); + const [dataSource, setDataSource] = useState([]); + const [total, setTotal] = useState(0); + const [mapPickerVisible, setMapPickerVisible] = useState(false); 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 [editingId, setEditingId] = useState(null); + const {customerLoading, customerModifyLoading, customerSaveLoading}= props.safetyEvalBusiness; - 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 getData = async (pagination) => { + const params = { + ...router.query, + current: pagination?.current || router.query.current || 1, + size: pagination?.size || router.query.size || 10, + }; + const res = await props.customerPage(params); + if (res?.success !== false) { + setDataSource(res?.data || []); + setTotal(res?.total || 0); + } + }; + + const handleMapConfirm = (location) => { + const { lng, lat, address } = location; + + modalForm.setFieldsValue({ longitude: lng, latitude: lat, businessAddress: address }); + setMapPickerVisible(false); + }; + + useEffect(() => { + searchForm.setFieldsValue(router.query); + getData(); }, []); - const handleReset = useCallback(() => { - searchForm.resetFields(); - setDataSource(MOCK_LIST); - }, [searchForm]); + const handleSearch = (values) => { + router.query = { ...router.query, ...values, current: 1, size: 10 }; + getData(); + }; - const handleOpenCreate = useCallback(() => { - setCreateMode("platform"); - setSelectedPlatform(null); + 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(pagination); + }; + + const handleOpenCreate = () => { + setEditingId(null); + setCurrentDetail(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 handleOpenEdit = async (record) => { + const res = await props.customerDetail({ id: record.id }); + if (res?.success !== false) { + setEditingId(record.id); + setCurrentDetail(res?.data || {}); + modalForm.setFieldsValue(res?.data || {}); + setModalOpen(true); + } + }; - 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 = async (record) => { + const res = await props.customerDetail({ id: record.id }); + if (res?.success !== false) { + setCurrentDetail(res?.data || {}); + setDetailOpen(true); + } + }; - const handleViewDetail = useCallback((record) => { - setCurrentDetail(record); - setDetailOpen(true); - }, []); + const handleDelete = async (record) => { + Modal.confirm({ + title: "确认删除", + content: "确定要删除这个客户吗?", + onOk: async () => { + await props.customerDelete({ data: record.id }); + message.success("删除成功"); + getData(); + }, + }); + }; + + const handleCreateSubmit = () => { + modalForm.validateFields().then(async (values) => { + if (editingId) { + await props.customerModify({ ...values, id: editingId }); + } else { + await props.customerSave(values); + } + message.success(editingId ? "修改成功" : "创建成功"); + setModalOpen(false); + getData(); + }); + }; 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: "customerName", ellipsis: true, width: 220 }, + { title: "统一社会信用代码", dataIndex: "creditCode", width: 220 }, + { title: "客户联系人", dataIndex: "principalName", width: 150 }, + { title: "联系人电话", dataIndex: "principalPhone", width: 160 }, { - title: "服务项目数", dataIndex: "projectCount", width: 110, + title: "属地", + dataIndex: "districtName", + width: 150, + }, + { + title: "企业状态", + dataIndex: "enterpriseStatusCode", + width: 100, + render: (status) => { + const statusOption = ENTERPRISE_STATUS_OPTIONS.find( + (opt) => opt.value === status, + ); + return statusOption?.label || status; + }, + }, + { + title: "服务项目数", + dataIndex: "projectCount", + width: 110, render: (count) => ( - + ), }, { - title: "操作", width: 80, fixed: "right", + title: "操作", + width: 260, + fixed: "right", render: (_, record) => ( - + + + + + ), }, ]; @@ -153,7 +214,11 @@ const CustomerManage = (props) => { } onClick={handleOpenCreate}> + } @@ -164,18 +229,33 @@ const CustomerManage = (props) => { - + + , - - + + , - - - {REGION_OPTIONS.map((r) => ( - {r} + + + {district.map((opt) => ( + + {opt.name} + ))} , @@ -185,86 +265,104 @@ const CustomerManage = (props) => { style={{ marginBottom: 16 }} /> -
- 服务项目数可进入该客户的评价项目列表;操作栏仅查看客户详情。 -
- `共 ${total} 条`, - pageSize: 10, }} + onChange={handlePageChange} /> - {/* 新建客户弹窗 */} + {/* 新建/编辑客户弹窗 */} setModalOpen(false)} onOk={handleCreateSubmit} + confirmLoading={editingId ? customerModifyLoading : customerSaveLoading} width={800} destroyOnHidden > -
- 优先从平台企业库选择;平台没有时可新建客户信息。 -
- - + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - {createMode === "platform" ? ( - - - - ) : ( - - - - )} - - - + + - + @@ -273,78 +371,36 @@ const CustomerManage = (props) => { - - + + - - + + - - + + - - + + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -352,7 +408,7 @@ const CustomerManage = (props) => { {/* 客户详情弹窗 */} setDetailOpen(false)} footer={} @@ -361,48 +417,127 @@ const CustomerManage = (props) => { > {currentDetail && ( <> - - {currentDetail.name} - - {currentDetail.status} + + + {currentDetail.customerName} + + + {currentDetail.creditCode} + + + {(() => { + const statusOption = ENTERPRISE_STATUS_OPTIONS.find( + (opt) => opt.value === currentDetail.enterpriseStatusCode, + ); + return ( + statusOption?.label || currentDetail.enterpriseStatusName + ); + })()} + + + {(() => { + const scaleOption = ENTERPRISE_SCALE_OPTIONS.find( + (opt) => opt.value === currentDetail.enterpriseScaleCode, + ); + return ( + scaleOption?.label || currentDetail.enterpriseScaleName + ); + })()} + + + {currentDetail.industryName || currentDetail.industryCode} + + + {currentDetail.districtName} + + + {currentDetail.principalName} + + + {currentDetail.principalPhone} + + + {currentDetail.principalName} + + + {currentDetail.principalPhone} + + + {currentDetail.legalRepresentative} + + + {currentDetail.legalRepresentativePhone} + + + {currentDetail.businessAddress} + + + {currentDetail.longitude} + + + {currentDetail.latitude} - {currentDetail.creditCode} - 重庆市 - {currentDetail.contact} - {currentDetail.phone} - {currentDetail.address}

安全评价项目列表

{s}, }, ]} pagination={false} - scroll={{ x: 1200,y: props.scrollY }} - + size="small" /> )} + setMapPickerVisible(false)} + onConfirm={handleMapConfirm} + /> ); }; export default Connect( - [NS_CUSTOMER], + [NS_SAFETY_EVAL_BUSINESS], true, -)(AntdTableFuncControl(CustomerManage)); \ No newline at end of file +)(AntdTableFuncControl(CustomerManage)); diff --git a/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/StaffPickerModal.js b/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/StaffPickerModal.js new file mode 100644 index 0000000..ca9d64b --- /dev/null +++ b/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/StaffPickerModal.js @@ -0,0 +1,269 @@ +import React, { useState, useEffect } from "react"; +import { + Modal, + TreeSelect, + Input, + Table, + Button, + Tag, + Row, + Col, + Space, +} from "antd"; +import { CloseOutlined } from "@ant-design/icons"; +import { NS_STAFF_INFO, NS_ORG_DEPARTMENT } from "~/enumerate/namespace"; +import { Connect } from "@cqsjjb/jjb-dva-runtime"; +import { QUALIFICATION_INDUSTRY_OPTIONS_MAP, PROFESSIONAL_LEVEL_OPTIONS_MAP, TITLE_LEVEL_MAP } from "~/enumerate/enterpriseOptions"; + + + +const StaffPickerModal = (props) => { + const { open, onCancel, onConfirm, selectedIds = [] } = props; + const { staffInfoList: dataSource, staffInfoTotal: total, staffInfoLoading: loading } = props.staffInfo || {}; + const { orgDepartmentTreeData } = props.orgDepartment || {}; + + const [deptId, setDeptId] = useState(undefined); + const [keyword, setKeyword] = useState(""); + const [page, setPage] = useState({ current: 1, size: 10 }); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + // 用 Map 存储所有已选行,翻页后不丢失 + const [selectedRowMap, setSelectedRowMap] = useState({}); + + useEffect(() => { + if (open) { + props.orgDepartmentTree(); + // 从父组件恢复已选数据 + const initialIds = selectedIds; + const initialRows = props.selectedParticipants || []; + setSelectedRowKeys(initialIds); + const map = {}; + initialRows.forEach((r) => { map[r.id] = r; }); + setSelectedRowMap(map); + fetchData({ current: 1, size: 10 }); + } + }, [open]); + + const fetchData = (pagination) => { + const params = { + current: pagination?.current || page.current, + size: pagination?.size || page.size, + employmentStatusCode: 1, + }; + if (deptId) params.deptId = deptId; + if (keyword) params.userName = keyword; + props.staffInfoList(params); + setPage(params); + }; + + const handleSearch = () => { + fetchData({ current: 1, size: 10 }); + }; + + const handleTableChange = (pagination) => { + fetchData(pagination); + }; + + const handleSelectChange = (rowKeys, rows) => { + setSelectedRowKeys(rowKeys); + // rows 仅包含当前页的行,用 Map 合并新旧数据 + setSelectedRowMap((prev) => { + const next = { ...prev }; + // 移除取消勾选的项 + Object.keys(next).forEach((key) => { + if (!rowKeys.includes(key)) { + delete next[key]; + } + }); + // 添加/更新当前页勾选的行 + rows.forEach((row) => { + next[row.id] = row; + }); + return next; + }); + }; + + const handleRemoveSelected = (record) => { + const newKeys = selectedRowKeys.filter((id) => id !== record.id); + setSelectedRowKeys(newKeys); + setSelectedRowMap((prev) => { + const next = { ...prev }; + delete next[record.id]; + return next; + }); + }; + + const handleConfirm = () => { + onConfirm(Object.values(selectedRowMap)); + }; + + const selectedRows = Object.values(selectedRowMap); + + const columns = [ + { + title: "用户名称", + dataIndex: "userName", + + width: 200, + render: (text, record) => { + const name = record.userName || "-"; + return ( + + {name} + {record.technicalDirectorFlag && ( + 技术 + )} + {record.processControlLeaderFlag && ( + 过程 + )} + + ); + }, + }, + + { + title: "资质范围", + dataIndex: "qualScope", + ellipsis: true, + render: (code) => { + const status = QUALIFICATION_INDUSTRY_OPTIONS_MAP[code]; + return status || "-"; + }, + }, + { + title: "职业等级", + dataIndex: "professionalLevelCode", + ellipsis: true, + render: (code) => { + const status = PROFESSIONAL_LEVEL_OPTIONS_MAP[code]; + return status || "-"; + }, + }, + { + title: "职称", + dataIndex: "titleCode", + width: 80, + ellipsis: true, + render: (code) => { + const status = TITLE_LEVEL_MAP[code]; + return status || "-"; + }, + }, + ]; + + return ( + + + + + { + setDeptId(value); + setTimeout(() => handleSearch(), 0); + }} + /> + setKeyword(e.target.value)} + onPressEnter={handleSearch} + /> + + +
+ 技术 技术负责人 + 过程 过程控制负责人 +
+
`共 ${t} 条`, + onChange: (cur, size) => handleTableChange({ current: cur, size }), + }} + scroll={{ y: 400, x: 600 }} + /> + + +
+ 已选人员({selectedRows.length}) +
+
+ {selectedRows.length === 0 && ( +
+ 请从左侧勾选人员 +
+ )} + {selectedRows.map((item) => ( +
+ +
{item.userName}
+ + +
+ ))} +
+ + + + ); +}; + +export default Connect( + [NS_STAFF_INFO, NS_ORG_DEPARTMENT], + true, +)(StaffPickerModal); \ No newline at end of file diff --git a/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/index.js b/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/index.js new file mode 100644 index 0000000..c8c23c9 --- /dev/null +++ b/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/index.js @@ -0,0 +1,340 @@ +import React, { useState, useEffect } from "react"; +import { + Form, + Button, + Input, + Select, + Row, + Col, + Card, + message, + DatePicker, + Flex, + Tag, +} from "antd"; +import dayjs from "dayjs"; +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 BaiduMapPicker from "~/components/BaiduMapPicker"; +import { district } from "~/enumerate/constant"; +import { phoneRule } from "~/utils/validators"; +import StaffPickerModal from "./StaffPickerModal"; + +import { + EVAL_TYPE_OPTIONS, + CHECKIN_RADIUS_OPTIONS, +} from "~/enumerate/constant"; + +const { router } = tools; + +const EvalProjectCreate = (props) => { + const [form] = Form.useForm(); + const [mapPickerVisible, setMapPickerVisible] = useState(false); + const [staffPickerVisible, setStaffPickerVisible] = useState(false); + const [selectedParticipants, setSelectedParticipants] = useState([]); + const [detailLoading, setDetailLoading] = useState(false); + + const projectId = router.query?.id; + const isView = projectId; + + const { evalProjectSaveLoading, evalProjectDetailLoading, customerPage: customerList } = props.safetyEvalBusiness; + + useEffect(() => { + props.customerPage({ current: 1, size: 999 }); + }, []); + + useEffect(() => { + if (!projectId) return; + (async () => { + setDetailLoading(true); + try { + const res = await props.evalProjectDetail({ id: projectId }); + if (res?.success !== false && res?.data) { + const data = res.data; + form.setFieldsValue({ + ...data, + 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 })), + ); + } + } + } finally { + setDetailLoading(false); + } + })(); + }, [projectId]); + + const handleMapConfirm = (location) => { + const { lng, lat, address } = location; + form.setFieldsValue({ + longitude: lng, + latitude: lat, + customerAddress: address, + }); + setMapPickerVisible(false); + }; + + const handleSubmit = () => { + form.validateFields().then(async (values) => { + const { planStartDate, planEndDate, ...rest } = values; + const params = { + ...rest, + planStartDate: planStartDate?.format("YYYY-MM-DD"), + planEndDate: planEndDate?.format("YYYY-MM-DD"), + participants: selectedParticipants.map((item) => ({ + id: item.id, + name: item.userName, + })), + }; + if (isView) { + params.id = projectId; + await props.evalProjectSave(params); + message.success("修改成功"); + } else { + await props.evalProjectSave(params); + message.success("创建成功"); + } + props.history.goBack(); + }); + }; + + const handleStaffConfirm = (rows) => { + setSelectedParticipants(rows); + setStaffPickerVisible(false); + }; + + return ( + + + + + } + loading={detailLoading} + > +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setStaffPickerVisible(true)} + style={{ cursor: "pointer", width: 400 }} + /> + {selectedParticipants.length > 0 && ( + + {selectedParticipants.map((item) => ( + + setSelectedParticipants((prev) => + prev.filter((r) => r.id !== item.id), + ) + } + > + {item.userName} + + ))} + + )} + + + + + setStaffPickerVisible(false)} + onConfirm={handleStaffConfirm} + selectedIds={selectedParticipants.map((item) => item.id)} + selectedParticipants={selectedParticipants} + /> + setMapPickerVisible(false)} + onConfirm={handleMapConfirm} + /> + + ); +}; + +export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(EvalProjectCreate); diff --git a/src/pages/Container/SafetyEvalBusiness/EvalProject/List/index.js b/src/pages/Container/SafetyEvalBusiness/EvalProject/List/index.js new file mode 100644 index 0000000..0435cad --- /dev/null +++ b/src/pages/Container/SafetyEvalBusiness/EvalProject/List/index.js @@ -0,0 +1,187 @@ +import React, { useState, useEffect } from "react"; +import { + Form, + Table, + Button, + Input, + Select, + Space, + Tag, +} 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_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace"; +import { Connect } from "@cqsjjb/jjb-dva-runtime"; +import { tools } from "@cqsjjb/jjb-common-lib"; + +const { router } = tools; + +const EvalProject = (props) => { + const [searchForm] = Form.useForm(); + const [dataSource, setDataSource] = useState([]); + const [total, setTotal] = useState(0); + + const { evalProjectLoading } = props.safetyEvalBusiness; + + const getData = async (pagination) => { + const params = { + ...router.query, + current: pagination?.current || router.query.current || 1, + size: pagination?.size || router.query.size || 10, + }; + const res = await props.evalProjectPage(params); + if (res?.success !== false) { + setDataSource(res?.data || []); + setTotal(res?.total || 0); + } + }; + + useEffect(() => { + searchForm.setFieldsValue(router.query); + 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(pagination); + }; + + const columns = [ + { title: "序号", width: 60, render: (_, __, index) => index + 1 }, + { + title: "项目名称", + dataIndex: "projectName", + ellipsis: true, + width: 220, + render: (text, record) => ( +
+
{text}
+
+ {record.projectNo} · {record.evalTypeName} +
+
+ ), + }, + { title: "项目负责人", dataIndex: "projectLeaderName", width: 120 }, + { title: "客户负责人", dataIndex: "customerContactName", width: 120 }, + { title: "项目开始时间", dataIndex: "planStartDate", width: 120 }, + { title: "项目结束时间", dataIndex: "planEndDate", width: 120 }, + { + title: "项目阶段", + dataIndex: "projectPhaseName", + width: 100, + render: (phase) => { + const color = phase === "延期" ? "error" : phase === "正常" ? "success" : undefined; + return {phase || "-"}; + }, + }, + { + title: "操作", + width: 100, + fixed: "right", + render: (_, record) => ( + + + + ), + }, + ]; + + return ( + } + onClick={() => props.history.push("Create")} + > + 新建项目 + + } + > +
+ 管理机构全部安全评价项目;各业务节点可独立维护,不以系统顺序替代机构线下实操。 +
+ + + + , + + + , + + + , + ]} + onFinish={handleSearch} + onReset={handleReset} + style={{ marginBottom: 16 }} + /> + +
`共 ${total} 条`, + }} + onChange={handlePageChange} + /> + + ); +}; + +export default Connect( + [NS_SAFETY_EVAL_BUSINESS], + true, +)(AntdTableFuncControl(EvalProject)); \ No newline at end of file diff --git a/src/pages/Container/SafetyEvalBusiness/EvalProject/index.js b/src/pages/Container/SafetyEvalBusiness/EvalProject/index.js new file mode 100644 index 0000000..151eca2 --- /dev/null +++ b/src/pages/Container/SafetyEvalBusiness/EvalProject/index.js @@ -0,0 +1,9 @@ +function EvalProject(props) { + return ( + + props.children + + ); +} + +export default EvalProject;