补充地图选择器

This commit is contained in:
liaoxuezhi 2020-03-29 21:29:45 +08:00
parent 2ad93f1c10
commit 42645138a6
6 changed files with 337 additions and 6 deletions

View File

@ -1,7 +1,289 @@
import React from 'react'; import React from 'react';
import {ClassNamesFn, themeable} from '../theme';
import {Icon} from '..';
import {loadScript, autobind, uuid} from '../utils/helper';
import { threadId } from 'worker_threads';
import debounce from 'lodash/debounce';
declare const BMap: any;
interface MapPickerProps {
ak: string;
classnames: ClassNamesFn;
classPrefix: string;
value?: {
address: string;
lat: number;
lng: number;
city?: string;
};
onChange: (value: any) => void;
}
interface LocationItem {
title?: string;
address: string;
lat: number;
lng: number;
city?: string;
}
interface MapPickerState {
inputValue: string;
locIndex?: number;
locs: Array<LocationItem>;
sugs: Array<string>;
}
export class BaiduMapPicker extends React.Component<
MapPickerProps,
MapPickerState
> {
state: MapPickerState = {
inputValue: '',
locs: [],
locIndex: -1,
sugs: [],
};
id = uuid();
mapRef: React.RefObject<HTMLDivElement> = React.createRef();
placeholderInput: HTMLInputElement;
map: any;
ac: any;
search = debounce(() => {
if (this.state.inputValue) {
this.ac?.search(this.state.inputValue);
} else {
this.setState({
sugs: []
})
}
}, 250, {
trailing: true,
leading: false
});
componentDidMount() {
if ((window as any).BMap) {
this.initMap();
} else {
loadScript(
`http://api.map.baidu.com/api?v=2.0&ak=${
this.props.ak
}&callback={{callback}}`
).then(this.initMap);
}
}
componentWillUnmount() {
this.ac?.dispose();
document.body.removeChild(this.placeholderInput);
delete this.placeholderInput;
delete this.map;
}
@autobind
async initMap() {
const map = new BMap.Map(this.mapRef.current, {
enableMapClick: false
});
this.map = map;
const value = this.props.value;
let point = value ? new BMap.Point(value.lng, value.lat) : new BMap.Point(116.404, 39.915);
map.centerAndZoom(point, 15);
const geolocationControl = new BMap.GeolocationControl();
geolocationControl.addEventListener('locationSuccess', (e: any) => {
this.getLocations(e.point);
});
map.addControl(geolocationControl);
map.addEventListener('click', (e: any) => {
this.getLocations(e.point, true);
});
const input = document.createElement('input');
input.className = 'invisible';
this.placeholderInput = input;
document.body.appendChild(input);
this.ac = new BMap.Autocomplete({
input,
location: map,
onSearchComplete: (e:any) => {
// 说明已经销毁了。
if (!this.map) {
return;
}
const sugs:Array<string> = [];
if (Array.isArray(e.Ir)) {
e.Ir.forEach((item:any) => {
sugs.push([item.province, item.city, item.district, item.street, item.business].filter(item => item).join(' '))
});
this.setState({
sugs
});
}
}
});
value ? this.getLocations(point) : geolocationControl.location();
}
getLocations(point: any, select?: boolean) {
const map = this.map;
map.clearOverlays();
const mk = new BMap.Marker(point);
map.addOverlay(mk);
map.panTo(point);
var geoc = new BMap.Geocoder();
geoc.getLocation(point, (rs: any) => {
// 说明已经销毁了。
if (!this.map) {
return;
}
const index = 0;
const locs: Array<LocationItem> = [];
locs.push({
title: '当前位置',
address: rs.address,
city: rs.addressComponents.city,
lat: rs.point.lat,
lng: rs.point.lng
});
if (Array.isArray(rs.surroundingPois)) {
rs.surroundingPois.forEach((item: any) => {
locs.push({
title: item.title,
address: item.address,
city: item.city,
lat: item.point.lat,
lng: item.point.lng
});
});
}
this.setState({
locIndex: index,
locs
}, () => {
if (!select) {
return;
}
this.props?.onChange({
address: locs[0].address,
lat: locs[0].lat,
lng: locs[0].lng,
city: locs[0].city
})
});
});
}
@autobind
handleChange(e: React.ChangeEvent<HTMLInputElement>) {
this.setState({
inputValue: e.currentTarget.value
}, this.search);
}
@autobind
handleSelect(e: React.MouseEvent<HTMLElement>) {
const index= parseInt(e.currentTarget.getAttribute('data-index')!, 10);
const loc = this.state.locs[index];
this.setState({
locIndex: index
}, () => {
const point = new BMap.Point(loc.lng, loc.lat);
this.map.clearOverlays();
const mk = new BMap.Marker(point);
this.map.addOverlay(mk);
this.map.panTo(point);
this.props?.onChange({
address: loc.address.trim() || loc.title,
lat: loc.lat,
lng: loc.lng,
city: loc.city
})
})
}
@autobind
handleSugSelect(e: React.MouseEvent<HTMLDivElement>) {
const value = e.currentTarget.innerText;
this.setState({
inputValue: value
});
var local = new BMap.LocalSearch(this.map, { //智能搜索
onSearchComplete: () => {
const results = local.getResults();
const poi = results.getPoi(0);
this.setState({
inputValue: poi.title,
sugs: []
});
this.getLocations(poi.point, true)
}
});
local.search(value);
}
export default class BaiduMapPicker extends React.Component<any> {
render() { render() {
return <p>233</p>; const {classnames: cx} = this.props;
const {locIndex, locs, inputValue, sugs} = this.state;
const hasSug = Array.isArray(sugs) && sugs.length;
return (
<div className={cx('MapPicker')}>
<div className={cx('MapPicker-search TextControl-control')}>
<div className={cx('TextControl-input')}>
<input onChange={this.handleChange} value={inputValue} placeholder="搜索地点" />
<span>
<Icon icon="search" />
</span>
</div>
</div>
<div ref={this.mapRef} className={cx('MapPicker-map', {
invisible: hasSug
})} />
<div className={cx('MapPicker-result', {
invisible: hasSug
})}>
{locs.map((item, index) => (
<div onClick={this.handleSelect} key={index} data-index={index} className={cx('MapPicker-item')}>
<div className={cx('MapPicker-itemTitle')}>{item.title}</div>
<div className={cx('MapPicker-itemDesc')}>{item.address}</div>
{locIndex === index ? <Icon icon="success" /> : null}
</div>
))}
</div>
{hasSug ? (
<div className={cx('MapPicker-sug')}>
{sugs.map(item =>
<div onClick={this.handleSugSelect} className={cx('MapPicker-sugItem')} key={item}>{item}</div>
)}
</div>
) : null}
</div>
);
} }
} }
export default themeable(BaiduMapPicker);

View File

@ -11,6 +11,7 @@ export interface LocationProps {
vendor: 'baidu' | 'gaode' | 'tenxun'; vendor: 'baidu' | 'gaode' | 'tenxun';
placeholder: string; placeholder: string;
clearable: boolean; clearable: boolean;
ak: string;
value?: { value?: {
address: string; address: string;
lat: number; lat: number;
@ -113,6 +114,17 @@ export class LocationPicker extends React.Component<
e.preventDefault(); e.preventDefault();
} }
@autobind
handleChange(value: any) {
if (value) {
value = {
...value,
vendor: this.props.vendor
};
}
this.props.onChange(value);
}
render() { render() {
const { const {
classnames: cx, classnames: cx,
@ -122,7 +134,8 @@ export class LocationPicker extends React.Component<
placeholder, placeholder,
clearable, clearable,
popOverContainer, popOverContainer,
vendor vendor,
ak
} = this.props; } = this.props;
const {isFocused, isOpened} = this.state; const {isFocused, isOpened} = this.state;
@ -136,7 +149,8 @@ export class LocationPicker extends React.Component<
`LocationPicker`, `LocationPicker`,
{ {
'is-disabled': disabled, 'is-disabled': disabled,
'is-focused': isFocused 'is-focused': isFocused,
'is-active': isOpened
}, },
className className
)} )}
@ -158,7 +172,7 @@ export class LocationPicker extends React.Component<
) : null} ) : null}
<a className={cx('LocationPicker-toggler')}> <a className={cx('LocationPicker-toggler')}>
<Icon icon="search" /> <Icon icon="location" />
</a> </a>
<Overlay <Overlay
@ -174,7 +188,7 @@ export class LocationPicker extends React.Component<
onClick={this.handlePopOverClick} onClick={this.handlePopOverClick}
> >
{vendor === 'baidu' ? ( {vendor === 'baidu' ? (
<BaiduMapPicker /> <BaiduMapPicker ak={ak} value={value} onChange={this.handleChange} />
) : (<Alert2>{vendor} </Alert2>)} ) : (<Alert2>{vendor} </Alert2>)}
</PopOver> </PopOver>
</Overlay> </Overlay>

View File

@ -60,6 +60,9 @@ import MoveIcon from '../icons/move.svg';
// @ts-ignore // @ts-ignore
import InfoIcon from '../icons/info.svg'; import InfoIcon from '../icons/info.svg';
// @ts-ignore
import LocationIcon from '../icons/location.svg';
// 兼容原来的用法,后续不直接试用。 // 兼容原来的用法,后续不直接试用。
// @ts-ignore // @ts-ignore
export const closeIcon = <CloseIcon />; export const closeIcon = <CloseIcon />;
@ -121,6 +124,7 @@ registerIcon('search', SearchIcon);
registerIcon('back', BackIcon); registerIcon('back', BackIcon);
registerIcon('move', MoveIcon); registerIcon('move', MoveIcon);
registerIcon('info', InfoIcon); registerIcon('info', InfoIcon);
registerIcon('location', LocationIcon);
export function Icon({ export function Icon({
icon, icon,

9
src/icons/location.svg Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1585402743083" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="2408"
xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<defs>
<style type="text/css"></style>
</defs>
<path d="M511.968 0c-207.84 0-376.96 169.12-376.96 376.992 0 54.208 11.104 105.984 32.96 153.888 94.24 206.24 274.976 424 328.128 485.824 3.968 4.608 9.792 7.296 15.904 7.296s11.904-2.656 15.904-7.296c53.12-61.824 233.856-279.552 328.128-485.824 21.888-47.904 32.96-99.648 32.96-153.888-0.032-207.872-169.152-376.992-376.992-376.992zM511.968 572.8c-107.968 0-195.808-87.84-195.808-195.808s87.84-195.84 195.808-195.84 195.808 87.84 195.808 195.84c0 107.968-87.84 195.808-195.808 195.808z" p-id="2409"></path>
</svg>

After

Width:  |  Height:  |  Size: 917 B

View File

@ -6,6 +6,7 @@ import LocationPicker from '../../components/LocationPicker';
export interface LocationControlProps extends FormControlProps { export interface LocationControlProps extends FormControlProps {
vendor: 'baidu' | 'gaode' | 'tenxun'; vendor: 'baidu' | 'gaode' | 'tenxun';
value: any; value: any;
ak: string;
onChange: (value: any) => void; onChange: (value: any) => void;
classnames: ClassNamesFn; classnames: ClassNamesFn;
classPrefix: string; classPrefix: string;

View File

@ -1140,3 +1140,24 @@ export function mapObject(value: any, fn: Function): any {
} }
return fn(value); return fn(value);
} }
export function loadScript(src: string) {
return new Promise((ok, fail) => {
const script = document.createElement('script');
script.onerror = reason => fail(reason);
if (~src.indexOf('{{callback}}')) {
const callbackFn = `loadscriptcallback_${uuid()}`;
(window as any)[callbackFn] = () => {
ok();
delete (window as any)[callbackFn];
};
src = src.replace('{{callback}}', callbackFn);
} else {
script.onload = () => ok();
}
script.src = src;
document.head.appendChild(script);
});
}