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