tangjie 2026-07-14 10:08:50 +08:00
parent 7b86d6e620
commit d781275317
4 changed files with 362 additions and 70 deletions

View File

@ -1,10 +1,10 @@
import { Form, Image, Upload } from "antd"; import { Form, Image, Upload, message } from "antd";
import { useState } from "react"; import { useState } from "react";
import { PlusOutlined } from "@ant-design/icons"; import { PlusOutlined } from "@ant-design/icons";
const isImage = (url) => const isImage = (url) =>
/\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || ""); /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || "");
export default function AttachmentUpload({ name, label, disabled = false, maxCount, accept }) { export default function AttachmentUpload({ name, label, disabled = false, maxCount, accept, extra }) {
const [previewImage, setPreviewImage] = useState(""); const [previewImage, setPreviewImage] = useState("");
return ( return (
@ -12,25 +12,31 @@ export default function AttachmentUpload({ name, label, disabled = false, maxCou
<Form.Item <Form.Item
name={name} name={name}
label={label} label={label}
extra={extra}
valuePropName="fileList" valuePropName="fileList"
getValueProps={(value) => { getValueProps={(value) => {
if (Array.isArray(value)) { if (Array.isArray(value)) {
return { fileList: value }; return { fileList: value };
} }
return { if (typeof value === "string" && value) {
fileList: value try {
? value.split(",").map((item) => ({ const parsed = JSON.parse(value);
url: item, if (Array.isArray(parsed)) {
uid: item, return { fileList: parsed };
status: "done", }
name: "附件", } catch {}
})) return {
: [], fileList: value.split(",").map((item) => ({
}; url: item,
uid: item,
status: "done",
name: "附件",
})),
};
}
return { fileList: [] };
}} }}
getValueFromEvent={({ fileList }) => { getValueFromEvent={({ fileList }) => {
return ( return (
fileList?.map((file) => ({ fileList?.map((file) => ({
url: file.response?.data?.url || file.url, url: file.response?.data?.url || file.url,
@ -43,11 +49,32 @@ export default function AttachmentUpload({ name, label, disabled = false, maxCou
}} }}
> >
<Upload <Upload
disabled={disabled} //disabled={disabled}
listType="picture-card" listType="picture-card"
headers={{
token: sessionStorage.getItem('token')
}}
maxCount={maxCount} maxCount={maxCount}
accept={accept} accept={accept}
action={`${window.process.env.app.API_HOST}/safetyEval/images/upload`} action={`${window.process.env.app.API_HOST}/safetyEval/file/upload`}
beforeUpload={(file) => {
if (!accept) return true;
const rules = accept.split(",").map((e) => e.trim().toLowerCase());
const ext = "." + file.name.split(".").pop().toLowerCase();
const matchExt = rules.includes(ext);
const matchMime = rules.some((r) => {
if (r.endsWith("/*")) {
const prefix = r.slice(0, -1);
return file.type && file.type.toLowerCase().startsWith(prefix);
}
return false;
});
if (!matchExt && !matchMime) {
message.error(`仅支持 ${accept} 格式文件`);
return Upload.LIST_IGNORE;
}
return true;
}}
onPreview={(file) => { onPreview={(file) => {
if (isImage(file.url || file.thumbUrl)) { if (isImage(file.url || file.thumbUrl)) {
setPreviewImage(file.url || file.thumbUrl); setPreviewImage(file.url || file.thumbUrl);

View File

@ -0,0 +1,136 @@
import React, { useEffect, useRef, useState } from 'react';
import { Modal, Input, message } from 'antd';
import './index.less';
const BaiduMapPicker = ({ visible, onCancel, onConfirm }) => {
const mapRef = useRef(null);
const mapInstanceRef = useRef(null);
const markerRef = useRef(null);
const [loaded, setLoaded] = useState(false);
const [selectedPoint, setSelectedPoint] = useState(null);
// 动态加载百度地图脚本
useEffect(() => {
if (!visible) return;
if (window.BMap) {
// 如果已有 BMap等 DOM 渲染后再初始化
setTimeout(() => setLoaded(true), 100);
return;
}
const scriptId = 'baiduMapScript';
if (document.getElementById(scriptId)) return;
window.__onBMapLoaded = () => {
setLoaded(true);
};
const script = document.createElement('script');
script.id = scriptId;
script.src = `https://api.map.baidu.com/api?v=3.0&ak=6lkWDSnHTCcvUenZhovQPER9nkhFfT9A&callback=__onBMapLoaded`;
script.async = true;
document.body.appendChild(script);
}, [visible]);
// 初始化地图
useEffect(() => {
if (!loaded || !visible || !mapRef.current) return;
// 避免重复初始化
if (mapInstanceRef.current) return;
const map = new window.BMap.Map(mapRef.current);
// 默认以重庆市中心为中心
map.centerAndZoom(new window.BMap.Point(106.55, 29.57), 12);
map.enableScrollWheelZoom(true);
mapInstanceRef.current = map;
// 点击地图获取坐标
map.addEventListener('click', (e) => {
const point = e.point;
setSelectedPoint(point);
if (markerRef.current) {
map.removeOverlay(markerRef.current);
}
const marker = new window.BMap.Marker(point);
map.addOverlay(marker);
markerRef.current = marker;
});
// 搜索自动完成
const ac = new window.BMap.Autocomplete({
input: 'baiduSearchInput',
location: map,
});
ac.addEventListener('onconfirm', (e) => {
const value = e.item.value;
const local = new window.BMap.LocalSearch(map, {
onSearchComplete: (results) => {
if (results.getCurrentNumPois() > 0) {
const poi = results.getPoi(0);
const point = poi.point;
setSelectedPoint(point);
map.centerAndZoom(point, 15);
if (markerRef.current) {
map.removeOverlay(markerRef.current);
}
const marker = new window.BMap.Marker(point);
map.addOverlay(marker);
markerRef.current = marker;
}
},
});
local.search(
[value.province, value.city, value.district, value.street, value.business]
.filter(Boolean)
.join(''),
);
});
return () => {
mapInstanceRef.current = null;
};
}, [loaded, visible]);
// 弹窗关闭时重置
useEffect(() => {
if (!visible) {
setLoaded(false);
setSelectedPoint(null);
markerRef.current = null;
mapInstanceRef.current = null;
}
}, [visible]);
const handleConfirm = () => {
if (!selectedPoint) {
message.warning('请在地图上选择位置');
return;
}
onConfirm({
lng: selectedPoint.lng,
lat: selectedPoint.lat,
});
};
return (
<Modal
title="选择坐标"
open={visible}
onCancel={onCancel}
onOk={handleConfirm}
width={800}
okText="确认"
cancelText="取消"
destroyOnClose
>
<input
id="baiduSearchInput"
className="baidu-map-search"
placeholder="输入地名搜索"
/>
<div ref={mapRef} className="baidu-map-container" />
</Modal>
);
};
export default BaiduMapPicker;

View File

@ -0,0 +1,33 @@
.baidu-map-search {
width: 100%;
height: 36px;
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 6px;
outline: none;
box-sizing: border-box;
margin-bottom: 12px;
font-size: 14px;
transition: border-color 0.2s;
&:focus {
border-color: #4096ff;
box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1);
}
&::placeholder {
color: #bfbfbf;
}
}
.baidu-map-container {
width: 100%;
height: 480px;
border-radius: 6px;
overflow: hidden;
}
/* 百度地图 Autocomplete 联想面板默认 z-index 过低,被 Modal 遮挡 */
.tangram-suggestion-main {
z-index: 10000 !important;
}

View File

@ -15,7 +15,6 @@ import {
InputNumber, InputNumber,
DatePicker, DatePicker,
Space, Space,
Modal,
} from "antd"; } from "antd";
import { NS_REGISTER } from "~/enumerate/namespace"; import { NS_REGISTER } from "~/enumerate/namespace";
import AttachmentUpload from "~/components/AttachmentUpload"; import AttachmentUpload from "~/components/AttachmentUpload";
@ -40,6 +39,7 @@ import {
REGISTERED_ORG_FILING_TYPE_SEARCH_OPTIONS, REGISTERED_ORG_FILING_TYPE_SEARCH_OPTIONS,
REGISTERED_ORG_FILING_RECORD_STATUS_OPTIONS, REGISTERED_ORG_FILING_RECORD_STATUS_OPTIONS,
} from "~/enumerate/enterpriseOptions"; } from "~/enumerate/enterpriseOptions";
import BaiduMapPicker from "~/components/BaiduMapPicker";
import "./index.less"; import "./index.less";
const { router } = tools; const { router } = tools;
@ -47,6 +47,7 @@ const { router } = tools;
const RegisterMore = (props) => { const RegisterMore = (props) => {
const { orgInfoSave, register, syncUserToGBS } = props; const { orgInfoSave, register, syncUserToGBS } = props;
const [current, setCurrent] = useState(0); const [current, setCurrent] = useState(0);
const [mapPickerVisible, setMapPickerVisible] = useState(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
const { registerLoading, syncUserToGBSLoading } = register; const { registerLoading, syncUserToGBSLoading } = register;
const hasRegisterContext = Boolean( const hasRegisterContext = Boolean(
@ -109,6 +110,11 @@ const RegisterMore = (props) => {
} }
}; };
const handleMapConfirm = ({ lng, lat }) => {
form.setFieldsValue({ longitude: lng, latitude: lat });
setMapPickerVisible(false);
};
const handleLogin = () => { const handleLogin = () => {
message.success("即将跳转到登录页面,请稍候..."); message.success("即将跳转到登录页面,请稍候...");
setTimeout(() => { setTimeout(() => {
@ -173,11 +179,11 @@ const RegisterMore = (props) => {
<Form <Form
form={form} form={form}
labelCol={{ span: 10 }} labelCol={{ span: 10 }}
disabled={!hasRegisterContext} //disabled={!hasRegisterContext}
> >
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={12}> <Col span={12}>
<Form.Item noStyle name='id' /> <Form.Item noStyle name="id" />
<Form.Item <Form.Item
name="unitName" name="unitName"
label="生产经营单位名称" label="生产经营单位名称"
@ -185,7 +191,11 @@ const RegisterMore = (props) => {
{ required: true, message: "请输入生产经营单位名称" }, { required: true, message: "请输入生产经营单位名称" },
]} ]}
> >
<Input placeholder="请输入生产经营单位名称" allowClear maxLength={200} /> <Input
placeholder="请输入生产经营单位名称"
allowClear
maxLength={200}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -194,7 +204,11 @@ const RegisterMore = (props) => {
label="统一社会信用代码" label="统一社会信用代码"
rules={[creditCodeRule(true)]} rules={[creditCodeRule(true)]}
> >
<Input placeholder="请输入统一社会信用代码" allowClear maxLength={18} /> <Input
placeholder="请输入统一社会信用代码"
allowClear
maxLength={18}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -235,47 +249,64 @@ const RegisterMore = (props) => {
label="所属镇、街道" label="所属镇、街道"
rules={[{ required: true, message: "请输入所属镇街道" }]} rules={[{ required: true, message: "请输入所属镇街道" }]}
> >
<Input placeholder="请输入所属镇街道" allowClear maxLength={100} /> <Input
placeholder="请输入所属镇街道"
allowClear
maxLength={100}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item name="villageCommunity" label="属村(社区)"> <Form.Item name="villageCommunity" label="属村(社区)">
<Input placeholder="请输入属村(社区)" allowClear maxLength={100} /> <Input
placeholder="请输入属村(社区)"
allowClear
maxLength={100}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item <Form.Item label="所在地坐标经度">
name="longitude" <Flex>
label="所在地坐标经度" <Form.Item
dependencies={["latitude"]} name="longitude"
rules={[ noStyle
{ dependencies={["latitude"]}
validator(_, value) { rules={[
const latitude = form.getFieldValue("latitude"); {
if ( validator(_, value) {
value !== undefined && const latitude = form.getFieldValue("latitude");
value !== null && if (
value !== "" && value !== undefined &&
(latitude === undefined || value !== null &&
latitude === null || value !== "" &&
latitude === "") (latitude === undefined ||
) { latitude === null ||
return Promise.reject( latitude === "")
new Error("填写经度后,纬度必填"), ) {
); return Promise.reject(
} new Error("填写经度后,纬度必填"),
return Promise.resolve(); );
}, }
}, return Promise.resolve();
]} },
> },
<InputNumber ]}
style={{ width: "100%" }} >
min={-180} <InputNumber
max={180} style={{ width: "100%" }}
precision={6} min={-180}
placeholder="请输入经度" max={180}
/> precision={6}
placeholder="请输入经度"
allowClear
/>
</Form.Item>
<Button onClick={() => setMapPickerVisible(true)}>
选择
</Button>
</Flex>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -319,7 +350,11 @@ const RegisterMore = (props) => {
label="注册地址" label="注册地址"
rules={[{ required: true, message: "请输入注册地址" }]} rules={[{ required: true, message: "请输入注册地址" }]}
> >
<Input.TextArea rows={3} placeholder="请输入注册地址" maxLength={200} /> <Input.TextArea
rows={3}
placeholder="请输入注册地址"
maxLength={200}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -328,12 +363,20 @@ const RegisterMore = (props) => {
label="经营地址" label="经营地址"
rules={[{ required: true, message: "请输入经营地址" }]} rules={[{ required: true, message: "请输入经营地址" }]}
> >
<Input.TextArea rows={3} placeholder="请输入经营地址" maxLength={200} /> <Input.TextArea
rows={3}
placeholder="请输入经营地址"
maxLength={200}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item name="ownershipTypeName" label="归属类型"> <Form.Item name="ownershipTypeName" label="归属类型">
<Input placeholder="请输入归属类型" allowClear maxLength={50} /> <Input
placeholder="请输入归属类型"
allowClear
maxLength={50}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -341,7 +384,11 @@ const RegisterMore = (props) => {
name="economyIndustryCode" name="economyIndustryCode"
label="国民经济行业分类(GB/T4754-2017)" label="国民经济行业分类(GB/T4754-2017)"
> >
<Input placeholder="请输入国民经济行业分类" allowClear maxLength={50} /> <Input
placeholder="请输入国民经济行业分类"
allowClear
maxLength={50}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -350,7 +397,11 @@ const RegisterMore = (props) => {
label="法定代表人" label="法定代表人"
rules={[{ required: true, message: "请输入法定代表人" }]} rules={[{ required: true, message: "请输入法定代表人" }]}
> >
<Input placeholder="请输入法定代表人" allowClear maxLength={50} /> <Input
placeholder="请输入法定代表人"
allowClear
maxLength={50}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -359,7 +410,11 @@ const RegisterMore = (props) => {
label="法定代表人联系电话" label="法定代表人联系电话"
rules={[phoneRule("法定代表人联系电话", false)]} rules={[phoneRule("法定代表人联系电话", false)]}
> >
<Input placeholder="请输入法定代表人联系电话" allowClear maxLength={11} /> <Input
placeholder="请输入法定代表人联系电话"
allowClear
maxLength={11}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -368,7 +423,11 @@ const RegisterMore = (props) => {
label="主要负责人" label="主要负责人"
rules={[{ required: true, message: "请输入主要负责人" }]} rules={[{ required: true, message: "请输入主要负责人" }]}
> >
<Input placeholder="请输入主要负责人" allowClear maxLength={50} /> <Input
placeholder="请输入主要负责人"
allowClear
maxLength={50}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -377,7 +436,11 @@ const RegisterMore = (props) => {
label="主要负责人联系电话" label="主要负责人联系电话"
rules={[phoneRule("主要负责人联系电话", true)]} rules={[phoneRule("主要负责人联系电话", true)]}
> >
<Input placeholder="请输入主要负责人联系电话" allowClear maxLength={11} /> <Input
placeholder="请输入主要负责人联系电话"
allowClear
maxLength={11}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -385,7 +448,11 @@ const RegisterMore = (props) => {
name="safetyDeptManager" name="safetyDeptManager"
label="安全管理部门负责人" label="安全管理部门负责人"
> >
<Input placeholder="请输入安全管理部门负责人" allowClear maxLength={50} /> <Input
placeholder="请输入安全管理部门负责人"
allowClear
maxLength={50}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -401,7 +468,7 @@ const RegisterMore = (props) => {
/> />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item <Form.Item
name="safetyDeputyPhone" name="safetyDeputyPhone"
@ -425,7 +492,11 @@ const RegisterMore = (props) => {
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item name="businessStatusName" label="企业经营状态"> <Form.Item name="businessStatusName" label="企业经营状态">
<Input placeholder="请输入企业经营状态" allowClear maxLength={50} /> <Input
placeholder="请输入企业经营状态"
allowClear
maxLength={50}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -434,7 +505,11 @@ const RegisterMore = (props) => {
label="信息公开网址" label="信息公开网址"
rules={[urlRule("信息公开网址", false)]} rules={[urlRule("信息公开网址", false)]}
> >
<Input placeholder="请输入信息公开网址" allowClear maxLength={200} /> <Input
placeholder="请输入信息公开网址"
allowClear
maxLength={200}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -446,6 +521,7 @@ const RegisterMore = (props) => {
<InputNumber <InputNumber
style={{ width: "100%" }} style={{ width: "100%" }}
min={0} min={0}
max={99999}
precision={2} precision={2}
placeholder="请输入工作场所建筑面积" placeholder="请输入工作场所建筑面积"
/> />
@ -460,6 +536,7 @@ const RegisterMore = (props) => {
<InputNumber <InputNumber
style={{ width: "100%" }} style={{ width: "100%" }}
min={0} min={0}
max={99999}
precision={2} precision={2}
placeholder="请输入档案室面积" placeholder="请输入档案室面积"
/> />
@ -476,6 +553,7 @@ const RegisterMore = (props) => {
<InputNumber <InputNumber
style={{ width: "100%" }} style={{ width: "100%" }}
min={0} min={0}
max={99999}
precision={0} precision={0}
placeholder="请输入专职安全评价师数量" placeholder="请输入专职安全评价师数量"
/> />
@ -492,6 +570,7 @@ const RegisterMore = (props) => {
<InputNumber <InputNumber
style={{ width: "100%" }} style={{ width: "100%" }}
min={0} min={0}
max={99999}
precision={0} precision={0}
placeholder="请输入注册安全工程师数量" placeholder="请输入注册安全工程师数量"
/> />
@ -504,7 +583,10 @@ const RegisterMore = (props) => {
placeholder="请选择企业状态" placeholder="请选择企业状态"
allowClear allowClear
onChange={(value, option) => { onChange={(value, option) => {
form.setFieldValue("enterpriseStatusName", option?.label || ""); form.setFieldValue(
"enterpriseStatusName",
option?.label || "",
);
}} }}
/> />
</Form.Item> </Form.Item>
@ -517,7 +599,10 @@ const RegisterMore = (props) => {
placeholder="请选择企业规模" placeholder="请选择企业规模"
allowClear allowClear
onChange={(value, option) => { onChange={(value, option) => {
form.setFieldValue("enterpriseScaleName", option?.label || ""); form.setFieldValue(
"enterpriseScaleName",
option?.label || "",
);
}} }}
/> />
</Form.Item> </Form.Item>
@ -553,10 +638,21 @@ const RegisterMore = (props) => {
<Form.Item name="filingRecordStatusName" noStyle /> <Form.Item name="filingRecordStatusName" noStyle />
</Col> </Col>
<Col span={12}> <Col span={12}>
<AttachmentUpload name="attachmentUrls" label="上传附件" /> <AttachmentUpload
name="attachmentUrls"
label="上传附件"
extra={"最多上传10个PDF、DOC、DOCX格式文件"}
maxCount={10}
accept=".pdf,.doc,.docx"
/>
</Col> </Col>
</Row> </Row>
</Form> </Form>
<BaiduMapPicker
visible={mapPickerVisible}
onCancel={() => setMapPickerVisible(false)}
onConfirm={handleMapConfirm}
/>
<Flex justify="center"> <Flex justify="center">
<Space size={16}> <Space size={16}>
{/* <Button {/* <Button