Enhance MessageEnter component by adding a map selection feature. Introduced a new button for map visibility and integrated SelectMap component for improved user interaction.
This commit is contained in:
@@ -1,147 +0,0 @@
|
|||||||
.chatFooter {
|
|
||||||
background: #f7f7f7;
|
|
||||||
border-top: 1px solid #e1e1e1;
|
|
||||||
padding: 0;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputContainer {
|
|
||||||
padding: 8px 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputToolbar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftTool {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbarButton {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 16px;
|
|
||||||
transition: all 0.15s;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #e6e6e6;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: #d9d9d9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputArea {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputWrapper {
|
|
||||||
border: 1px solid #d1d1d1;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #fff;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&:focus-within {
|
|
||||||
border-color: #07c160;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageInput {
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
resize: none;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.4;
|
|
||||||
padding: 8px 10px;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: #b3b3b3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sendButtonArea {
|
|
||||||
padding: 8px 10px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sendButton {
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-weight: normal;
|
|
||||||
min-width: 60px;
|
|
||||||
font-size: 13px;
|
|
||||||
background: #07c160;
|
|
||||||
border-color: #07c160;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #06ad56;
|
|
||||||
border-color: #06ad56;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: #059748;
|
|
||||||
border-color: #059748;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
background: #b3b3b3;
|
|
||||||
border-color: #b3b3b3;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hintButton {
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: #666;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputHint {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #999;
|
|
||||||
text-align: right;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.inputToolbar {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sendButtonArea {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
.stepContent {
|
|
||||||
.stepHeader {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1a1a1a;
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.step3Content {
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
.leftColumn {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightColumn {
|
|
||||||
width: 400px;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messagePreview {
|
|
||||||
border: 2px dashed #52c41a;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f6ffed;
|
|
||||||
|
|
||||||
.previewTitle {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #52c41a;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageBubble {
|
|
||||||
min-height: 60px;
|
|
||||||
padding: 12px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 6px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.6;
|
|
||||||
|
|
||||||
.currentEditingLabel {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageText {
|
|
||||||
color: #333;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.savedScriptGroups {
|
|
||||||
.scriptGroupTitle {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scriptGroupItem {
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
.scriptGroupHeader {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.scriptGroupLeft {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
:global(.ant-radio) {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scriptGroupName {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageCount {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scriptGroupActions {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
.actionButton {
|
|
||||||
padding: 4px;
|
|
||||||
color: #666;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #1890ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scriptGroupContent {
|
|
||||||
margin-top: 8px;
|
|
||||||
padding-top: 8px;
|
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageInputArea {
|
|
||||||
.messageInput {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachmentButtons {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aiRewriteSection {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageHint {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.settingsPanel {
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fafafa;
|
|
||||||
|
|
||||||
.settingsTitle {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1a1a1a;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settingItem {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settingLabel {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1a1a1a;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settingControl {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tagSection {
|
|
||||||
.settingLabel {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1a1a1a;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pushPreview {
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f0f7ff;
|
|
||||||
|
|
||||||
.previewTitle {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1a1a1a;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
li {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.step3Content {
|
|
||||||
.rightColumn {
|
|
||||||
width: 350px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.step3Content {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.leftColumn {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightColumn {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import ContentSelection from "@/components/ContentSelection";
|
|
||||||
import { ContentItem } from "@/components/ContentSelection/data";
|
|
||||||
import InputMessage from "./InputMessage/InputMessage";
|
|
||||||
import styles from "./index.module.scss";
|
|
||||||
|
|
||||||
interface StepSendMessageProps {
|
|
||||||
@@ -0,0 +1,900 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
AutoComplete,
|
||||||
|
Input as AntInput,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Spin,
|
||||||
|
Button,
|
||||||
|
} from "antd";
|
||||||
|
import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data";
|
||||||
|
import { useWebSocketStore } from "@/store/module/websocket/websocket";
|
||||||
|
|
||||||
|
declare const AMap: any;
|
||||||
|
|
||||||
|
interface SelectMapProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
contract: ContractData | weChatGroup;
|
||||||
|
addMessage: (message: ChatRecord) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectMap: React.FC<SelectMapProps> = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
contract,
|
||||||
|
addMessage,
|
||||||
|
}) => {
|
||||||
|
const [selectedLocation, setSelectedLocation] = useState(null);
|
||||||
|
const mapRef = useRef(null);
|
||||||
|
const markersRef = useRef<any[]>([]); // 使用数组保存所有标记点
|
||||||
|
const geocoderRef = useRef<any>(null); // 保存 Geocoder 实例
|
||||||
|
const placeSearchRef = useRef<any>(null); // 保存 PlaceSearch 实例
|
||||||
|
const geolocationRef = useRef<any>(null); // 保存 Geolocation 实例
|
||||||
|
const pendingClickRef = useRef<{
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
lnglat: any;
|
||||||
|
} | null>(null); // 保存待处理的点击坐标
|
||||||
|
const [options, setOptions] = useState([]);
|
||||||
|
const [mapLoading, setMapLoading] = useState(true);
|
||||||
|
|
||||||
|
const sendCommand = useWebSocketStore(state => state.sendCommand);
|
||||||
|
|
||||||
|
// 清除所有标记点
|
||||||
|
const clearAllMarkers = useCallback(() => {
|
||||||
|
if (markersRef.current && markersRef.current.length > 0) {
|
||||||
|
console.log(`清除 ${markersRef.current.length} 个标记点`);
|
||||||
|
markersRef.current.forEach(marker => {
|
||||||
|
if (marker) {
|
||||||
|
marker.setMap(null);
|
||||||
|
marker = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
markersRef.current = [];
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const addMarker = useCallback(
|
||||||
|
(lnglat: any) => {
|
||||||
|
console.log("addMarker 调用,坐标:", lnglat);
|
||||||
|
|
||||||
|
// 先清除所有现有的标记点
|
||||||
|
clearAllMarkers();
|
||||||
|
|
||||||
|
// 创建红色图标,保持默认倒水滴形状
|
||||||
|
const redIcon = new AMap.Icon({
|
||||||
|
size: new AMap.Size(25, 34), // 默认标记点尺寸
|
||||||
|
image: "https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png", // 红色标记点图片
|
||||||
|
imageOffset: new AMap.Pixel(0, 0),
|
||||||
|
imageSize: new AMap.Size(25, 34),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建新的标记点
|
||||||
|
const newMarker = new AMap.Marker({
|
||||||
|
position: lnglat,
|
||||||
|
map: mapRef.current,
|
||||||
|
icon: redIcon,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将新标记点添加到数组中
|
||||||
|
markersRef.current.push(newMarker);
|
||||||
|
|
||||||
|
mapRef.current.setCenter(lnglat);
|
||||||
|
mapRef.current.setZoom(16); // 确保缩放到合适级别
|
||||||
|
console.log("新 marker 已添加并居中");
|
||||||
|
},
|
||||||
|
[clearAllMarkers],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 通用的地址获取函数
|
||||||
|
const getAddressForLocation = useCallback(
|
||||||
|
(lat: number, lng: number, lnglat: any) => {
|
||||||
|
console.log("=== getAddressForLocation 调用 ===");
|
||||||
|
console.log("坐标:", { lat, lng });
|
||||||
|
console.log(
|
||||||
|
"Geocoder ref 状态:",
|
||||||
|
geocoderRef.current ? "存在" : "不存在",
|
||||||
|
);
|
||||||
|
|
||||||
|
// 检查 Geocoder 是否已初始化
|
||||||
|
if (!geocoderRef.current) {
|
||||||
|
console.warn("Geocoder 未初始化,无法获取地址");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用更精确的坐标进行查询
|
||||||
|
// 高德地图 getAddress 支持 LngLat 对象或 [lng, lat] 数组
|
||||||
|
let queryLnglat: any;
|
||||||
|
if (lnglat && typeof lnglat.getLat === "function") {
|
||||||
|
// 如果传入的是 LngLat 对象,直接使用
|
||||||
|
queryLnglat = lnglat;
|
||||||
|
} else {
|
||||||
|
// 否则创建新的 LngLat 对象或使用数组格式
|
||||||
|
try {
|
||||||
|
queryLnglat = new AMap.LngLat(lng, lat);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("创建 LngLat 对象失败,使用数组格式:", error);
|
||||||
|
queryLnglat = [lng, lat];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("调用 geocoder.getAddress,坐标:", queryLnglat);
|
||||||
|
|
||||||
|
try {
|
||||||
|
geocoderRef.current.getAddress(
|
||||||
|
queryLnglat,
|
||||||
|
(status: string, result: any) => {
|
||||||
|
console.log("=== Geocoder 回调触发(通用函数) ===");
|
||||||
|
console.log("Status:", status);
|
||||||
|
console.log(
|
||||||
|
"Result:",
|
||||||
|
result ? JSON.stringify(result, null, 2) : "null",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
status === "complete" &&
|
||||||
|
result &&
|
||||||
|
result.info === "OK" &&
|
||||||
|
result.regeocode
|
||||||
|
) {
|
||||||
|
const regeocode = result.regeocode;
|
||||||
|
const formattedAddress = regeocode.formattedAddress || "";
|
||||||
|
const addressComponent = regeocode.addressComponent || {};
|
||||||
|
|
||||||
|
// 构建详细地址信息
|
||||||
|
let addressLabel = formattedAddress;
|
||||||
|
let poiName = "点击位置";
|
||||||
|
|
||||||
|
// 优先级1: 如果有POI信息,优先使用POI名称
|
||||||
|
if (regeocode.pois && regeocode.pois.length > 0) {
|
||||||
|
const poi = regeocode.pois[0];
|
||||||
|
poiName = poi.name || poiName;
|
||||||
|
const poiAddress = poi.address || "";
|
||||||
|
addressLabel = poiAddress
|
||||||
|
? `${poiName} ${poiAddress}`
|
||||||
|
: `${poiName} ${formattedAddress}`;
|
||||||
|
}
|
||||||
|
// 优先级2: 如果有建筑物信息
|
||||||
|
else if (regeocode.buildings && regeocode.buildings.length > 0) {
|
||||||
|
const building = regeocode.buildings[0];
|
||||||
|
poiName = building.name || addressComponent.building || poiName;
|
||||||
|
addressLabel = `${poiName} ${formattedAddress}`;
|
||||||
|
}
|
||||||
|
// 优先级3: 如果有AOI(兴趣区域)信息
|
||||||
|
else if (regeocode.aois && regeocode.aois.length > 0) {
|
||||||
|
const aoi = regeocode.aois[0];
|
||||||
|
poiName = aoi.name || poiName;
|
||||||
|
addressLabel = `${poiName} ${formattedAddress}`;
|
||||||
|
}
|
||||||
|
// 优先级4: 使用地址组件构建详细地址
|
||||||
|
else if (addressComponent.building) {
|
||||||
|
poiName = addressComponent.building;
|
||||||
|
addressLabel = `${poiName} ${formattedAddress}`;
|
||||||
|
}
|
||||||
|
// 优先级5: 组合地址组件
|
||||||
|
else {
|
||||||
|
const parts = [];
|
||||||
|
if (addressComponent.province)
|
||||||
|
parts.push(addressComponent.province);
|
||||||
|
if (addressComponent.city) parts.push(addressComponent.city);
|
||||||
|
if (addressComponent.district)
|
||||||
|
parts.push(addressComponent.district);
|
||||||
|
if (addressComponent.township)
|
||||||
|
parts.push(addressComponent.township);
|
||||||
|
if (addressComponent.street)
|
||||||
|
parts.push(addressComponent.street);
|
||||||
|
if (addressComponent.streetNumber)
|
||||||
|
parts.push(addressComponent.streetNumber);
|
||||||
|
|
||||||
|
if (parts.length > 0) {
|
||||||
|
const fullAddress = parts.join("");
|
||||||
|
poiName =
|
||||||
|
addressComponent.street ||
|
||||||
|
addressComponent.district ||
|
||||||
|
"点击位置";
|
||||||
|
addressLabel = fullAddress || formattedAddress;
|
||||||
|
} else {
|
||||||
|
addressLabel =
|
||||||
|
formattedAddress || `纬度: ${lat}, 经度: ${lng}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: addressLabel,
|
||||||
|
poiname: poiName,
|
||||||
|
});
|
||||||
|
|
||||||
|
message.success("地址信息获取成功");
|
||||||
|
} else {
|
||||||
|
console.warn("=== Geocoder 返回异常,尝试 PlaceSearch ===");
|
||||||
|
console.warn("Status:", status);
|
||||||
|
console.warn("Result:", result);
|
||||||
|
// Geocoder 失败,使用 PlaceSearch 作为备用
|
||||||
|
if (placeSearchRef.current) {
|
||||||
|
try {
|
||||||
|
const searchLnglat = lnglat || [lng, lat];
|
||||||
|
placeSearchRef.current.searchNearBy(
|
||||||
|
"",
|
||||||
|
searchLnglat,
|
||||||
|
1000,
|
||||||
|
(searchStatus: string, searchResult: any) => {
|
||||||
|
if (
|
||||||
|
searchStatus === "complete" &&
|
||||||
|
searchResult &&
|
||||||
|
searchResult.info === "OK" &&
|
||||||
|
searchResult.poiList?.pois?.length > 0
|
||||||
|
) {
|
||||||
|
const poi = searchResult.poiList.pois[0];
|
||||||
|
const poiLabel = poi.address
|
||||||
|
? `${poi.name} ${poi.address}`
|
||||||
|
: poi.name;
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: poiLabel,
|
||||||
|
poiname: poi.name,
|
||||||
|
});
|
||||||
|
message.success("通过附近搜索获取到地址信息");
|
||||||
|
} else {
|
||||||
|
console.warn("PlaceSearch 返回异常:", {
|
||||||
|
status: searchStatus,
|
||||||
|
result: searchResult,
|
||||||
|
});
|
||||||
|
const coordLabel = `纬度: ${lat}, 经度: ${lng}`;
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: coordLabel,
|
||||||
|
poiname: "点击位置",
|
||||||
|
});
|
||||||
|
message.warning("无法获取详细地址信息,但坐标已记录");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (placeSearchError) {
|
||||||
|
console.error(
|
||||||
|
"PlaceSearch.searchNearBy 调用异常:",
|
||||||
|
placeSearchError,
|
||||||
|
);
|
||||||
|
const coordLabel = `纬度: ${lat}, 经度: ${lng}`;
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: coordLabel,
|
||||||
|
poiname: "点击位置",
|
||||||
|
});
|
||||||
|
message.warning("无法获取详细地址信息,但坐标已记录");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const coordLabel = `纬度: ${lat}, 经度: ${lng}`;
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: coordLabel,
|
||||||
|
poiname: "点击位置",
|
||||||
|
});
|
||||||
|
message.warning("无法获取详细地址信息,但坐标已记录");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("=== Geocoder.getAddress 调用异常 ===", error);
|
||||||
|
// 如果 geocoder 调用失败,尝试使用 PlaceSearch
|
||||||
|
if (placeSearchRef.current) {
|
||||||
|
console.log("尝试使用 PlaceSearch 作为备用方案");
|
||||||
|
try {
|
||||||
|
placeSearchRef.current.searchNearBy(
|
||||||
|
"",
|
||||||
|
lnglat || [lng, lat],
|
||||||
|
1000,
|
||||||
|
(status: string, result: any) => {
|
||||||
|
if (
|
||||||
|
status === "complete" &&
|
||||||
|
result &&
|
||||||
|
result.info === "OK" &&
|
||||||
|
result.poiList?.pois?.length > 0
|
||||||
|
) {
|
||||||
|
const poi = result.poiList.pois[0];
|
||||||
|
const poiLabel = poi.address
|
||||||
|
? `${poi.name} ${poi.address}`
|
||||||
|
: poi.name;
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: poiLabel,
|
||||||
|
poiname: poi.name,
|
||||||
|
});
|
||||||
|
message.success("通过附近搜索获取到地址信息");
|
||||||
|
} else {
|
||||||
|
const coordLabel = `纬度: ${lat}, 经度: ${lng}`;
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: coordLabel,
|
||||||
|
poiname: "点击位置",
|
||||||
|
});
|
||||||
|
message.warning("无法获取详细地址信息,但坐标已记录");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (placeSearchError) {
|
||||||
|
console.error("PlaceSearch 调用也失败:", placeSearchError);
|
||||||
|
const coordLabel = `纬度: ${lat}, 经度: ${lng}`;
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: coordLabel,
|
||||||
|
poiname: "点击位置",
|
||||||
|
});
|
||||||
|
message.warning("无法获取详细地址信息,但坐标已记录");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const coordLabel = `纬度: ${lat}, 经度: ${lng}`;
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: coordLabel,
|
||||||
|
poiname: "点击位置",
|
||||||
|
});
|
||||||
|
message.warning("无法获取详细地址信息,但坐标已记录");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const initMap = useCallback(() => {
|
||||||
|
console.log("initMap 执行中");
|
||||||
|
setMapLoading(true);
|
||||||
|
|
||||||
|
// 确保容器存在
|
||||||
|
const container = document.getElementById("amap-container");
|
||||||
|
if (!container) {
|
||||||
|
console.error("地图容器不存在");
|
||||||
|
setMapLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保容器样式正确
|
||||||
|
container.style.pointerEvents = "auto";
|
||||||
|
container.style.cursor = "crosshair";
|
||||||
|
container.style.position = "relative";
|
||||||
|
container.style.zIndex = "1";
|
||||||
|
|
||||||
|
const map = new AMap.Map("amap-container", {
|
||||||
|
zoom: 16,
|
||||||
|
center: [118.113653, 24.470164], // 默认中心
|
||||||
|
viewMode: "2D", // 明确指定视图模式
|
||||||
|
});
|
||||||
|
mapRef.current = map;
|
||||||
|
|
||||||
|
// 添加超时机制,防止 loading 一直显示
|
||||||
|
const loadingTimeout = setTimeout(() => {
|
||||||
|
console.warn("地图加载超时,强制关闭 loading");
|
||||||
|
setMapLoading(false);
|
||||||
|
}, 10000); // 10秒超时
|
||||||
|
|
||||||
|
// 立即加载插件,不等待地图 complete 事件
|
||||||
|
console.log("=== 立即开始加载 AMap 插件(地图创建后) ===");
|
||||||
|
AMap.plugin(
|
||||||
|
[
|
||||||
|
"AMap.AutoComplete",
|
||||||
|
"AMap.PlaceSearch",
|
||||||
|
"AMap.Geocoder",
|
||||||
|
"AMap.Geolocation",
|
||||||
|
],
|
||||||
|
error => {
|
||||||
|
if (error) {
|
||||||
|
console.error("=== AMap 插件加载失败 ===", error);
|
||||||
|
message.error("地图插件加载失败,部分功能可能不可用");
|
||||||
|
clearTimeout(loadingTimeout);
|
||||||
|
setMapLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("=== AMap 插件加载成功 ===");
|
||||||
|
|
||||||
|
// 立即创建 PlaceSearch 和 Geocoder 实例
|
||||||
|
const placeSearch = new AMap.PlaceSearch({
|
||||||
|
city: "全国",
|
||||||
|
map: map,
|
||||||
|
});
|
||||||
|
placeSearchRef.current = placeSearch;
|
||||||
|
console.log("PlaceSearch 实例已创建");
|
||||||
|
|
||||||
|
const geocoder = new AMap.Geocoder({
|
||||||
|
city: "全国",
|
||||||
|
radius: 1000, // 搜索半径,单位米
|
||||||
|
extensions: "all", // 返回详细信息,包括POI、建筑物等
|
||||||
|
});
|
||||||
|
geocoderRef.current = geocoder;
|
||||||
|
console.log("Geocoder 实例已创建并保存到 ref,现在可以立即使用");
|
||||||
|
|
||||||
|
// 创建 Geolocation 实例用于获取当前位置
|
||||||
|
const geolocation = new AMap.Geolocation({
|
||||||
|
enableHighAccuracy: true, // 是否使用高精度定位,默认:true
|
||||||
|
timeout: 10000, // 超过10秒后停止定位,默认:无穷大
|
||||||
|
maximumAge: 0, // 定位结果缓存0毫秒,默认:0
|
||||||
|
convert: true, // 自动偏移坐标,偏移后的坐标为高德坐标,默认:true
|
||||||
|
showButton: false, // 显示定位按钮,默认:true
|
||||||
|
buttonPosition: "RB", // 定位按钮停靠位置,默认:'LB',左下角
|
||||||
|
showMarker: false, // 定位成功后在定位到的位置显示点标记,默认:true
|
||||||
|
showCircle: false, // 定位成功后用圆圈表示定位精度范围,默认:true
|
||||||
|
panToLocation: false, // 定位成功后将定位到的位置作为地图中心点,默认:true
|
||||||
|
zoomToAccuracy: false, // 定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false
|
||||||
|
});
|
||||||
|
geolocationRef.current = geolocation;
|
||||||
|
|
||||||
|
// 获取当前位置
|
||||||
|
console.log("=== 开始获取当前位置 ===");
|
||||||
|
geolocation.getCurrentPosition((status: string, result: any) => {
|
||||||
|
console.log("=== 定位回调触发 ===");
|
||||||
|
console.log("定位状态:", status);
|
||||||
|
console.log("定位结果:", result);
|
||||||
|
|
||||||
|
if (status === "complete") {
|
||||||
|
const { position, formattedAddress, addressComponent } = result;
|
||||||
|
const lat = position.lat;
|
||||||
|
const lng = position.lng;
|
||||||
|
const lnglat = new AMap.LngLat(lng, lat);
|
||||||
|
|
||||||
|
console.log("定位成功,当前位置:", { lat, lng });
|
||||||
|
console.log("定位地址:", formattedAddress);
|
||||||
|
|
||||||
|
// 将地图中心设置为当前位置
|
||||||
|
map.setCenter(lnglat);
|
||||||
|
map.setZoom(16);
|
||||||
|
|
||||||
|
// 添加当前位置标记
|
||||||
|
addMarker(lnglat);
|
||||||
|
|
||||||
|
// 设置选中位置信息
|
||||||
|
let addressLabel = formattedAddress || `纬度: ${lat}, 经度: ${lng}`;
|
||||||
|
let poiName = "当前位置";
|
||||||
|
|
||||||
|
// 尝试从地址组件中获取更详细的信息
|
||||||
|
if (addressComponent) {
|
||||||
|
const parts = [];
|
||||||
|
if (addressComponent.province)
|
||||||
|
parts.push(addressComponent.province);
|
||||||
|
if (addressComponent.city) parts.push(addressComponent.city);
|
||||||
|
if (addressComponent.district)
|
||||||
|
parts.push(addressComponent.district);
|
||||||
|
if (addressComponent.street) parts.push(addressComponent.street);
|
||||||
|
if (addressComponent.streetNumber)
|
||||||
|
parts.push(addressComponent.streetNumber);
|
||||||
|
|
||||||
|
if (parts.length > 0) {
|
||||||
|
addressLabel = parts.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
poiName =
|
||||||
|
addressComponent.street ||
|
||||||
|
addressComponent.district ||
|
||||||
|
"当前位置";
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: addressLabel,
|
||||||
|
poiname: poiName,
|
||||||
|
});
|
||||||
|
|
||||||
|
message.success("已获取当前位置");
|
||||||
|
} else {
|
||||||
|
console.warn("定位失败:", result);
|
||||||
|
message.warning(
|
||||||
|
"无法获取当前位置,请手动点击地图选择位置。原因: " +
|
||||||
|
(result.message || "定位服务不可用"),
|
||||||
|
);
|
||||||
|
// 定位失败时,使用默认中心点
|
||||||
|
console.log("使用默认中心点");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果有待处理的点击坐标,立即处理它
|
||||||
|
if (pendingClickRef.current) {
|
||||||
|
console.log("=== 检测到待处理的点击坐标,立即处理 ===");
|
||||||
|
const { lat, lng, lnglat } = pendingClickRef.current;
|
||||||
|
console.log("待处理坐标:", { lat, lng });
|
||||||
|
setTimeout(() => {
|
||||||
|
const success = getAddressForLocation(lat, lng, lnglat);
|
||||||
|
if (success) {
|
||||||
|
console.log("✓ 待处理的坐标已成功获取地址");
|
||||||
|
pendingClickRef.current = null; // 清除待处理坐标
|
||||||
|
} else {
|
||||||
|
console.warn("待处理的坐标获取地址失败");
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 立即绑定点击事件,插件可能已加载或正在加载
|
||||||
|
console.log("立即绑定点击事件(插件可能已初始化)");
|
||||||
|
map.on("click", (e: any) => {
|
||||||
|
console.log("=== 地图点击事件触发 ===");
|
||||||
|
console.log("点击事件对象:", e);
|
||||||
|
console.log("点击位置对象:", e.lnglat);
|
||||||
|
console.log(
|
||||||
|
"Geocoder ref 状态:",
|
||||||
|
geocoderRef.current ? "已初始化" : "未初始化",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!e || !e.lnglat) {
|
||||||
|
console.error("点击事件无效,缺少 lnglat");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lnglat = e.lnglat;
|
||||||
|
const lat = lnglat.getLat();
|
||||||
|
const lng = lnglat.getLng();
|
||||||
|
console.log(`点击坐标 - 纬度: ${lat}, 经度: ${lng}`);
|
||||||
|
|
||||||
|
// 立即添加标记和居中
|
||||||
|
addMarker(lnglat);
|
||||||
|
|
||||||
|
// 设置基本 selectedLocation(至少有坐标)
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: "正在获取地址信息...",
|
||||||
|
poiname: "点击位置",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果 Geocoder 已初始化,立即使用它
|
||||||
|
if (geocoderRef.current) {
|
||||||
|
console.log("Geocoder 已初始化,立即获取地址");
|
||||||
|
getAddressForLocation(lat, lng, lnglat);
|
||||||
|
} else {
|
||||||
|
console.log("Geocoder 未初始化,保存坐标待插件加载完成后处理");
|
||||||
|
// 保存待处理的坐标
|
||||||
|
pendingClickRef.current = { lat, lng, lnglat };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 等待地图完全加载后关闭 loading
|
||||||
|
map.on("complete", () => {
|
||||||
|
console.log("地图加载完成");
|
||||||
|
// 清除超时定时器
|
||||||
|
clearTimeout(loadingTimeout);
|
||||||
|
// 关闭 loading
|
||||||
|
setMapLoading(false);
|
||||||
|
console.log("地图加载完成,loading 已关闭");
|
||||||
|
|
||||||
|
// 确保地图容器可点击
|
||||||
|
const mapContainer = map.getContainer();
|
||||||
|
if (mapContainer) {
|
||||||
|
mapContainer.style.pointerEvents = "auto";
|
||||||
|
mapContainer.style.cursor = "crosshair";
|
||||||
|
console.log("地图容器指针事件已启用");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果地图加载失败,也设置加载完成
|
||||||
|
map.on("error", (error: any) => {
|
||||||
|
console.error("地图加载错误:", error);
|
||||||
|
clearTimeout(loadingTimeout);
|
||||||
|
setMapLoading(false);
|
||||||
|
message.error("地图加载失败");
|
||||||
|
});
|
||||||
|
}, [addMarker, getAddressForLocation]);
|
||||||
|
|
||||||
|
const handleSearch = value => {
|
||||||
|
if (value) {
|
||||||
|
AMap.plugin("AMap.AutoComplete", () => {
|
||||||
|
const auto = new AMap.AutoComplete({ city: "全国" });
|
||||||
|
auto.search(value, (status, result) => {
|
||||||
|
if (status === "complete") {
|
||||||
|
setOptions(
|
||||||
|
result.tips.map(tip => ({
|
||||||
|
value: tip.name,
|
||||||
|
data: tip,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setOptions([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelect = (value, option) => {
|
||||||
|
const { district, address, name, location } = option.data;
|
||||||
|
const lnglat = location;
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lnglat.lat,
|
||||||
|
lng: lnglat.lng,
|
||||||
|
label: `${name} ${address || district}`,
|
||||||
|
poiname: name,
|
||||||
|
});
|
||||||
|
addMarker(lnglat);
|
||||||
|
mapRef.current.setCenter(lnglat);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalChange = useCallback(
|
||||||
|
(visible: boolean) => {
|
||||||
|
if (visible) {
|
||||||
|
console.log("模态打开:开始加载地图脚本");
|
||||||
|
setMapLoading(true);
|
||||||
|
setSelectedLocation(null);
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src =
|
||||||
|
"https://webapi.amap.com/maps?v=1.4.15&key=79370028f5763e46742125ed2e900c76&plugin=AMap.PlaceSearch,AMap.AutoComplete,AMap.Geocoder,AMap.Geolocation";
|
||||||
|
script.async = true;
|
||||||
|
script.onload = () => {
|
||||||
|
console.log("脚本加载成功:开始初始化地图");
|
||||||
|
setTimeout(() => initMap(), 100); // 添加延迟确保 DOM 就绪
|
||||||
|
};
|
||||||
|
script.onerror = () => {
|
||||||
|
console.error("脚本加载失败");
|
||||||
|
message.error("地图加载失败,请检查网络或API密钥");
|
||||||
|
setMapLoading(false);
|
||||||
|
};
|
||||||
|
document.body.appendChild(script);
|
||||||
|
} else {
|
||||||
|
console.log("模态关闭:清理地图和脚本");
|
||||||
|
// 清除所有标记点
|
||||||
|
clearAllMarkers();
|
||||||
|
// 重置 ref
|
||||||
|
geocoderRef.current = null;
|
||||||
|
placeSearchRef.current = null;
|
||||||
|
geolocationRef.current = null;
|
||||||
|
// Cleanup on close
|
||||||
|
const scripts = document.querySelectorAll(
|
||||||
|
'script[src*="webapi.amap.com"]',
|
||||||
|
);
|
||||||
|
scripts.forEach(s => {
|
||||||
|
if (document.body.contains(s)) {
|
||||||
|
document.body.removeChild(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (mapRef.current) {
|
||||||
|
mapRef.current.destroy();
|
||||||
|
mapRef.current = null;
|
||||||
|
}
|
||||||
|
setMapLoading(false);
|
||||||
|
setOptions([]); // 重置搜索选项
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[initMap, clearAllMarkers],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 手动获取当前位置
|
||||||
|
const handleGetCurrentLocation = useCallback(() => {
|
||||||
|
if (!geolocationRef.current) {
|
||||||
|
message.warning("定位服务未初始化,请稍候再试");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("=== 手动触发获取当前位置 ===");
|
||||||
|
message.loading({ content: "正在获取当前位置...", key: "location" });
|
||||||
|
|
||||||
|
geolocationRef.current.getCurrentPosition((status: string, result: any) => {
|
||||||
|
message.destroy("location");
|
||||||
|
console.log("=== 手动定位回调触发 ===");
|
||||||
|
console.log("定位状态:", status);
|
||||||
|
console.log("定位结果:", result);
|
||||||
|
|
||||||
|
if (status === "complete" && result && result.position) {
|
||||||
|
const { position, formattedAddress, addressComponent } = result;
|
||||||
|
const lat = position.lat;
|
||||||
|
const lng = position.lng;
|
||||||
|
const lnglat = new AMap.LngLat(lng, lat);
|
||||||
|
|
||||||
|
console.log("定位成功,当前位置:", { lat, lng });
|
||||||
|
|
||||||
|
// 将地图中心设置为当前位置
|
||||||
|
if (mapRef.current) {
|
||||||
|
mapRef.current.setCenter(lnglat);
|
||||||
|
mapRef.current.setZoom(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加当前位置标记
|
||||||
|
addMarker(lnglat);
|
||||||
|
|
||||||
|
// 设置选中位置信息
|
||||||
|
let addressLabel = formattedAddress || `纬度: ${lat}, 经度: ${lng}`;
|
||||||
|
let poiName = "当前位置";
|
||||||
|
|
||||||
|
// 尝试从地址组件中获取更详细的信息
|
||||||
|
if (addressComponent) {
|
||||||
|
const parts = [];
|
||||||
|
if (addressComponent.province) parts.push(addressComponent.province);
|
||||||
|
if (addressComponent.city) parts.push(addressComponent.city);
|
||||||
|
if (addressComponent.district) parts.push(addressComponent.district);
|
||||||
|
if (addressComponent.street) parts.push(addressComponent.street);
|
||||||
|
if (addressComponent.streetNumber)
|
||||||
|
parts.push(addressComponent.streetNumber);
|
||||||
|
|
||||||
|
if (parts.length > 0) {
|
||||||
|
addressLabel = parts.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
poiName =
|
||||||
|
addressComponent.street || addressComponent.district || "当前位置";
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedLocation({
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
label: addressLabel,
|
||||||
|
poiname: poiName,
|
||||||
|
});
|
||||||
|
|
||||||
|
message.success("已获取当前位置");
|
||||||
|
} else {
|
||||||
|
console.warn("定位失败:", result);
|
||||||
|
message.error(
|
||||||
|
"获取当前位置失败: " + (result?.message || "定位服务不可用"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [addMarker]);
|
||||||
|
|
||||||
|
const handleSendLocation = () => {
|
||||||
|
if (!selectedLocation || !selectedLocation.lat || !selectedLocation.lng) {
|
||||||
|
message.warning("请选择有效位置");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { lat, lng, label, poiname } = selectedLocation;
|
||||||
|
const content = `<msg><location x="${lat}" y="${lng}" scale="16" label="${label}" poiname="${poiname}" infourl="" maptype="0" poiid="" /></msg>`;
|
||||||
|
|
||||||
|
const messageId = +Date.now();
|
||||||
|
const params = {
|
||||||
|
wechatAccountId: contract.wechatAccountId,
|
||||||
|
wechatChatroomId: contract?.chatroomId ? contract.id : 0,
|
||||||
|
wechatFriendId: contract?.chatroomId ? 0 : contract.id,
|
||||||
|
msgSubType: 0,
|
||||||
|
msgType: 48,
|
||||||
|
content: content,
|
||||||
|
seq: messageId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 构造本地消息
|
||||||
|
const localMessage: ChatRecord = {
|
||||||
|
id: messageId,
|
||||||
|
wechatAccountId: contract.wechatAccountId,
|
||||||
|
wechatFriendId: contract?.chatroomId ? 0 : contract.id,
|
||||||
|
wechatChatroomId: contract?.chatroomId ? contract.id : 0,
|
||||||
|
tenantId: 0,
|
||||||
|
accountId: 0,
|
||||||
|
synergyAccountId: 0,
|
||||||
|
content: content,
|
||||||
|
msgType: 48,
|
||||||
|
msgSubType: 0,
|
||||||
|
msgSvrId: "",
|
||||||
|
isSend: true,
|
||||||
|
createTime: new Date().toISOString(),
|
||||||
|
isDeleted: false,
|
||||||
|
deleteTime: "",
|
||||||
|
sendStatus: 1,
|
||||||
|
wechatTime: Date.now(),
|
||||||
|
origin: 0,
|
||||||
|
msgId: 0,
|
||||||
|
recalled: false,
|
||||||
|
seq: messageId,
|
||||||
|
};
|
||||||
|
addMessage(localMessage);
|
||||||
|
|
||||||
|
sendCommand("CmdSendMessage", params);
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
setSelectedLocation(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
handleModalChange(true);
|
||||||
|
// 确保容器在模态框打开后可以接收事件
|
||||||
|
setTimeout(() => {
|
||||||
|
const container = document.getElementById("amap-container");
|
||||||
|
if (container) {
|
||||||
|
container.style.pointerEvents = "auto";
|
||||||
|
container.style.cursor = "crosshair";
|
||||||
|
console.log(
|
||||||
|
"容器样式已设置,pointerEvents:",
|
||||||
|
container.style.pointerEvents,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
} else {
|
||||||
|
handleModalChange(false);
|
||||||
|
}
|
||||||
|
}, [visible, handleModalChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={visible}
|
||||||
|
title="选择位置"
|
||||||
|
onCancel={onClose}
|
||||||
|
onOk={handleSendLocation}
|
||||||
|
width={800}
|
||||||
|
afterOpenChange={handleModalChange}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", gap: 10, marginBottom: 10 }}>
|
||||||
|
<AutoComplete
|
||||||
|
options={options}
|
||||||
|
onSelect={onSelect}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<AntInput placeholder="搜索位置" />
|
||||||
|
</AutoComplete>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleGetCurrentLocation}
|
||||||
|
disabled={!geolocationRef.current}
|
||||||
|
>
|
||||||
|
定位当前位置
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 显示点击位置的信息 */}
|
||||||
|
{selectedLocation && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: 10,
|
||||||
|
padding: 10,
|
||||||
|
background: "#f5f5f5",
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ marginBottom: 5, color: "#333", fontWeight: 500 }}>
|
||||||
|
位置信息:
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: 5, color: "#666" }}>
|
||||||
|
<strong>地址:</strong> {selectedLocation.label || "加载中..."}
|
||||||
|
</div>
|
||||||
|
<div style={{ color: "#666", fontSize: 12 }}>
|
||||||
|
<strong>坐标:</strong> {selectedLocation.lat},{" "}
|
||||||
|
{selectedLocation.lng}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ height: "400px", position: "relative" }}>
|
||||||
|
{mapLoading && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
zIndex: 1000,
|
||||||
|
pointerEvents: "none", // 确保加载层不阻止点击
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spin tip="加载地图中..." />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
id="amap-container"
|
||||||
|
style={{
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
position: "relative",
|
||||||
|
pointerEvents: "auto", // 确保可以接收点击事件
|
||||||
|
cursor: "crosshair", // 显示十字光标提示可以点击
|
||||||
|
zIndex: 1,
|
||||||
|
touchAction: "none", // 防止触摸事件干扰
|
||||||
|
}}
|
||||||
|
onClick={e => {
|
||||||
|
// 添加原生点击事件监听作为备用
|
||||||
|
console.log("容器原生点击事件触发", e);
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectMap;
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import { Layout, Input, Button, Modal, message, Tooltip } from "antd";
|
import {
|
||||||
|
Layout,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
message,
|
||||||
|
Tooltip,
|
||||||
|
AutoComplete,
|
||||||
|
Input as AntInput,
|
||||||
|
Spin,
|
||||||
|
} from "antd";
|
||||||
import {
|
import {
|
||||||
SendOutlined,
|
SendOutlined,
|
||||||
FolderOutlined,
|
FolderOutlined,
|
||||||
@@ -8,6 +18,7 @@ import {
|
|||||||
CloseOutlined,
|
CloseOutlined,
|
||||||
MessageOutlined,
|
MessageOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
|
EnvironmentOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data";
|
import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data";
|
||||||
import { useWebSocketStore } from "@/store/module/websocket/websocket";
|
import { useWebSocketStore } from "@/store/module/websocket/websocket";
|
||||||
@@ -23,6 +34,7 @@ import {
|
|||||||
manualTriggerAi,
|
manualTriggerAi,
|
||||||
} from "@/store/module/weChat/weChat";
|
} from "@/store/module/weChat/weChat";
|
||||||
import { useContactStore } from "@/store/module/weChat/contacts";
|
import { useContactStore } from "@/store/module/weChat/contacts";
|
||||||
|
import SelectMap from "./components/selectMap";
|
||||||
const { Footer } = Layout;
|
const { Footer } = Layout;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
@@ -326,6 +338,8 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
|
|||||||
updateShowChatRecordModel(!showChatRecordModel);
|
updateShowChatRecordModel(!showChatRecordModel);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [mapVisible, setMapVisible] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* 聊天输入 */}
|
{/* 聊天输入 */}
|
||||||
@@ -423,6 +437,12 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
|
|||||||
}
|
}
|
||||||
className={styles.toolbarButton}
|
className={styles.toolbarButton}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
className={styles.toolbarButton}
|
||||||
|
type="text"
|
||||||
|
icon={<EnvironmentOutlined />}
|
||||||
|
onClick={() => setMapVisible(true)}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* AI模式下显示重新生成按钮 */}
|
{/* AI模式下显示重新生成按钮 */}
|
||||||
{(isAiAssist || isAiTakeover) && (
|
{(isAiAssist || isAiTakeover) && (
|
||||||
@@ -502,7 +522,12 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Footer>
|
</Footer>
|
||||||
、
|
<SelectMap
|
||||||
|
visible={mapVisible}
|
||||||
|
onClose={() => setMapVisible(false)}
|
||||||
|
contract={contract}
|
||||||
|
addMessage={addMessage}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user