目录
复杂而且不好口述,直接上代码,后面出视频
搜索区域代码
// ------------------外部资源
import React, { useEffect } from 'react'
import { observer } from 'mobx-react-lite'
import { Form, Button, Input, Select } from 'antd'
const { Option } = Select
// ------------------内部公共
import DataSelect from "@liepin/react-dataselect-pc";
// ------------------内部私有
import store from '../../store'
export default observer(() => {
const [form] = Form.useForm()
const onEmployeeChange = () => {
form.setFieldsValue({ customerId: undefined })
store.setStore({ customerId: '' })
onSearchParamsChange()
}
// 搜索客户
const onSearchParamsChange = () => {
const data = form.getFieldsValue(true)
data.area = undefined
data.range = undefined
if (data.employeeId) {
store.setStore({ employeeId: data.employeeId })
store.getCustomerLocationList(data)
} else {
store.setStore({
employeeId: '',
customerId: '',
visitInfo: {},
customerLocationList: []
})
}
}
// 客户
const onCustomerChange = (value) => {
store.setStore({ customerId: value })
}
// 重置
const handleClear = () => {
const employeeId = form.getFieldsValue(true).employeeId
form.resetFields()
form.setFieldsValue({ employeeId })
store.setStore({ customerId: null, range: undefined, area: '' })
employeeId && store.getCustomerLocationList({ employeeId })
}
useEffect(() => {
if (store.useInfo?.isSale) {
form.setFieldsValue({
employeeId: store.useInfo?.employeeId
})
store.getCustomerLocationList({ employeeId: store.useInfo?.employeeId })
}
}, [store.useInfo])
useEffect(() => {
form.setFieldsValue({ range: store.range, area: store.area })
}, [store.range, store.area])
return (
<>
<div className="search-block">
<Form
layout="inline"
form={form}
>
<Form.Item label="销售" name="employeeId">
<DataSelect
selectType="selectEmployee"
width={200}
onChange={onEmployeeChange}
/>
</Form.Item>
<Form.Item label="合作状态" name="customerSignType">
<Select placeholder="请选择" allowClear onChange={onSearchParamsChange} style={
{ width: 100 }}>
<Option value={0}>未合作</Option>
<Option value={1}>合作中</Option>
<Option value={2}>合作结束</Option>
</Select>
</Form.Item>
<Form.Item label="客户" name="customerId">
<DataSelect
value={store.customerId}
selectType="selectEmployee"
width={200}
url="/customermap/search-customer.json"
ajaxData={
{
initCondition: 'Init_LptCustomer',
queryField: 'name',
queryType: 'select_customer',
isNeedPerssion: true,
extendCondition: JSON.stringify([{"queryField":"creatorId","queryOption":"=","queryValue": store.employeeId}])
}}
onChange={onCustomerChange}
/>
</Form.Item>
<Form.Item name="area">
<Input placeholder="区域/地点" allowClear
onChange={(e) => store.setStore({ area: e.target.value })} />
</Form.Item>
<Form.Item name="range">
<Select placeholder="周边" allowClear style={
{ width: 100 }}
onChange={(value) => store.setStore({ range: value })}
>
<Option value={1}>1公里以内</Option>
<Option value={2}>2公里以内</Option>
<Option value={3}>3公里以内</Option>
<Option value={4}>4公里以内</Option>
<Option value={5}>5公里以内</Option>
</Select>
</Form.Item>
<Form.Item>
<Button onClick={handleClear}>清除</Button>
</Form.Item>
</Form>
</div>
<style jsx>{`
.search-block {
padding: 10px 20px;
}
`}</style>
</>
)
})
地图区域代码
// ------------------外部资源
import React, { useState, useEffect, useRef } from 'react';
import { observer } from 'mobx-react-lite'
import { toJS } from "mobx";
import {Modal, Spin } from 'antd';
import { Map, Markers, Circle } from 'react-amap';
import { useDebounceFn } from "ahooks";
// ------------------内部公共
import EditVisitPlan from "@components/EditVisitPlan";
import mark_gray from '@static/images/mark_gray.png'
import mark_green from '@static/images/mark_green.png'
// ------------------内部私有
import store from '../../store'
import './index.less'
let currentMarkerData = {}
let markerMap = []
let MapsOption = {}
// 自定义菜单
const CustomContextMenu = ({ coverLocations, mapsOption, showRange, addVisit, setCurrentMarkerData }) => {
return (
<ul className="custom-amap-menu"
style={
{
left: mapsOption.pixel.x + 5,
top: mapsOption.pixel.y
}}>
{
coverLocations.map(item => {
const row = JSON.parse(item)
return <li key={row.customerId}>
<span>{row.customerName}</span>
<ul className="customer-menus">
<li onClick={() => {
setCurrentMarkerData?.(row)
showRange?.()
}}>显示周边范围</li>
<li onClick={() => {
setCurrentMarkerData?.(row)
addVisit?.()
}}>增加拜访计划</li>
<li><a href={`/customer/todetail/?customerId=${row.customerId}&isinfoEdit=1`} target="_blank">重新维护地址</a></li>
<li><a href={`/customervisitplan/listall/?customer_id=${row.customerId}`} target="_blank">查看拜访记录</a></li>
<li><a href={`/customer/todetail/?customerId=${row.customerId}`} target="_blank">查看客户详情</a></li>
</ul>
</li>
})
}
</ul>
)
}
export default observer(() => {
const mapInstance = useRef(null); // created map 记录map实例instance
const contextMenu = useRef(null); // 菜单
const [zoom, setZoom] = useState(11) // 缩放
const [coordinates, setCoordinates] = useState([]) // 当前坐标点
const [radius, setRadius] = useState(0) // 半径范围range
const [markers, setMarkers] = useState([]) // Markers 数据
const [visible, setVisible] = useState(false) // Circle 显示
const [editVisitPlanVisible, setEditVisitPlanVisible] = useState(false) // 增加拜访计划显示
const [customContextMenuVisible, setCustomContextMenuVisible] = useState(false) // 自定义菜单显示
// 监听客户位置列表变化,控制地图显示
useEffect(() => {
getMarkers()
store?.visitInfo?.city && debounceSetAddrLocation.run(store?.visitInfo?.city)
}, [store.customerLocationList])
// 监听客户变化
useEffect(() => {
if (markers.length && (store.customerId || store.customerId === 0)) {
const customer = markers.find(item => {
return item?.extData?.customerId === Number(store.customerId)
})
console.log('customer', customer);
if (customer) {
setZoom(16)
setCoordinates(customer.position)
store.setStore({ range: 3, customer })
setVisible(true)
setIconRed(customer, 500)
} else {
store.setStore({ customer: {} })
Modal.warning({
content: (<div>当前所选客户还没有坐标,<a href={`/customer/todetail/?customerId=${store.customerId}&isinfoEdit=1`} target="_blank">现在去维护</a></div>),
okText: '取消'
});
}
} else {
setZoom(11)
setCoordinates([116.397428, 39.90923])
store.setStore({ range: undefined, customer: {} })
setVisible(false)
}
}, [store.customerId])
// 标红点
const setIconRed = (customer, timeout) => {
if (!Object.keys(store.customer).length) return
setTimeout(() => {
// 标红
for(const item of markerMap) {
if (customer?.extData?.isCover) {
if (item?.w?.extData?.position.join() === customer?.position?.join()) {
item.render(() => {
return <img src='//webapi.amap.com/theme/v1.3/markers/n/mark_r.png' />
})
}
} else {
if (item?.w?.extData?.extData.customerId === customer?.extData?.customerId) {
item.render(() => {
return <img src='//webapi.amap.com/theme/v1.3/markers/n/mark_r.png' />
})
}
}
}
}, timeout)
}
// 监听区域、地点变化,获取对应经纬点
useEffect(() => {
store?.area && debounceSetAddrLocation.run(store?.area);
}, [store.area])
// 监听周边变化,设置Circle⭕️半径
useEffect(() => {
setRadius(store.range * 1000)
}, [store.range])
// 监听经纬坐标、区域变化,控制Circle⭕️显示
useEffect(() => {
if (coordinates && store.area) {
setZoom(16);
setVisible(true)
} else {
setVisible(false)
}
}, [store.area])
// 获取地图 Markers
const getMarkers = () => {
const list = []
for(const item of toJS(store.customerLocationList)) {
const position = item.location?.split(',')
if (position.length === 2) {
list.push({
position,
extData: item
})
}
}
setMarkers(list)
store.setStore({ area: '' })
store.setStore({ range: undefined })
}
// render动态修改标记的外观icon
const renderMarkerFn = (extData) => {
let icon = '//webapi.amap.com/theme/v1.3/markers/n/mark_bs.png'
if (extData?.extData?.visitPlanStatus === 1) {
icon = mark_gray
} else if (extData?.extData?.visitPlanStatus === 2) {
icon = mark_green
}
return <img src={icon} />
};
// 根据城市名做地理/逆地理编码 获取经纬坐标
const setAddrLocation = (area) => {
window.AMap.plugin('AMap.Geocoder', () => {
let geocoder = new window.AMap.Geocoder({
// city 指定进行编码查询的城市,支持传入城市名、adcode 和 citycode
city: store?.visitInfo?.city
});
// 使用geocoder做地理/逆地理编码
geocoder.getLocation(area, (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const location = result.geocodes[0].location;
setCoordinates([location.lng, location.lat])
if (store.area && result.info === 'OK') store.setStore({ range: 3 })
}
});
});
};
const debounceSetAddrLocation = useDebounceFn(setAddrLocation);
// 显示周边范围
const showRange = () => {
setZoom(16);
setCoordinates(currentMarkerData.location.split(','));
store.setStore({ range: 3, customerId: currentMarkerData.customerId })
setVisible(true)
}
// 增加拜访计划
const addVisit = () => {
setEditVisitPlanVisible(true)
}
// Markers 事件
const events = {
created: (markers) => {
for (const item of markers) {
markerMap.push(item)
}
},
click: (mapsOption, marker) => {
currentMarkerData = marker.getExtData().extData
setCustomContextMenuVisible(false)
if (currentMarkerData.isCover === 1) {
const customAmapMenu = document.getElementsByClassName('custom-amap-menu')
if (customAmapMenu.length === 0) {
MapsOption = mapsOption
setCustomContextMenuVisible(true)
}
} else {
contextMenu.current.open(mapInstance.current, mapsOption.lnglat);
}
},
mouseover: (mapsOption, marker) => {
marker.setLabel({
offset: new window.AMap.Pixel(20, 20), // 修改label相对于maker的位置
content: marker.w.extData.extData.showName
})
marker.setTop(true);
},
mouseout: (mapsOption, marker) => {
marker.setLabel({
content: null
});
marker.setTop(false);
}
}
// 地图事件
const mapEvents = {
created: (instance) => {
mapInstance.current = instance;
contextMenu.current = new window.AMap.ContextMenu();
contextMenu.current.addItem("显示周边范围", showRange, 0);
contextMenu.current.addItem("增加拜访计划", addVisit, 1);
contextMenu.current.addItem("重新维护地址", function() {
window.open(`/customer/todetail/?customerId=${currentMarkerData.customerId}&isinfoEdit=1`);
}, 2);
contextMenu.current.addItem("查看拜访记录", function() {
window.open(`/customervisitplan/listall/?customer_id=${currentMarkerData.customerId}`);
}, 3);
contextMenu.current.addItem("查看客户详情", function() {
window.open(`/customer/todetail/?customerId=${currentMarkerData.customerId}`);
}, 4);
},
click: () => setCustomContextMenuVisible(false),
mapmove: () => setCustomContextMenuVisible(false),
zoomchange: () => {
const list = [...markers]
console.log('mapInstance.current.getZoom()', mapInstance.current.getZoom());
setZoom(mapInstance.current.getZoom());
if (mapInstance.current.getZoom() > 12) {
for(const item of list) {
item.label = {
content: item.extData.showName,
offset: new window.AMap.Pixel(20, 20)
}
}
} else {
for(const item of list) {
item.label = undefined
}
}
setMarkers(list)
console.log('keys', );
setIconRed(store.customer)
setCustomContextMenuVisible(false)
},
};
return (
<Spin spinning={store.loading} >
<div id="customer-maps" style={
{width: '100%', height: 'calc(100vh - 184px)'}}>
<div style={
{ display: store.customerLocationList.length ? 'block' : 'none', height: '100%', width: '100%' }}>
<Map
amapkey="0fe0e92d23dc183b6384d51515676b81"
version="1.4.0"
plugins={['Scale', 'ToolBar']}
center={coordinates}
isHotspot={true}
zoom={zoom}
events={mapEvents}
>
<Markers
markers={markers}
events={events}
render={renderMarkerFn}
/>
<Circle
center={ coordinates }
radius={ radius }
visible={ visible }
style={
{
strokeColor: "#F33", // 线颜色
strokeOpacity: 1, // 线透明度
strokeWeight: 3, // 线粗细度
fillColor: "#ee2200", // 填充颜色
fillOpacity: 0.15 // 填充透明度
}}
draggable={ false }
bubble
/>
</Map>
</div>
{/* 增加拜访计划 */}
{
editVisitPlanVisible &&
<EditVisitPlan
visible={editVisitPlanVisible}
params={
{
customerId: currentMarkerData.customerId,
customerName: currentMarkerData.customerName,
title: '增加拜访计划',
isEdit: false,
isPhone: true,
onSave: () => setEditVisitPlanVisible(false),
onCancel: () => setEditVisitPlanVisible(false)
}}
/>
}
{/* 重复坐标点的自定义菜单 */}
{
customContextMenuVisible &&
<CustomContextMenu
mapsOption={MapsOption}
coverLocations={ currentMarkerData.coverLocations }
showRange={showRange}
addVisit={addVisit}
setCurrentMarkerData={(data) => currentMarkerData = data}
/>
}
</div>
</Spin>
)
})
自定义菜单样式
#customer-maps {
position: relative;
.custom-amap-menu {
position: absolute;
top: 100px;
left: 300px;
border: 1px solid #ccc;
background: #fff;
z-index: 111;
padding: 0;
ul, li {
padding: 0;
margin: 0;
list-style:none
}
li {
height: 30px;
line-height: 30px;
padding: 0 10px;
position: relative;
}
li:hover {
background: #eee;
}
li:hover ul {
display: block;
}
ul {
width: 100px;
position: absolute;
top: 0;
right: -100px;
display: none;
background: #fff;
border: 1px solid #ccc;
z-index: 111;
}
ul li {
height: 30px;
line-height: 30px;
padding: 0 10px;
text-align: center;
cursor: pointer;
}
ul li:hover {
background: #eee;
}
a {
color: #5f6d80;
}
a:hover {
text-decoration: none;
}
ul li a {
display: block;
}
}
}