diff --git a/src/components/BaiduMapPicker.tsx b/src/components/BaiduMapPicker.tsx index 650a0bd4..0f29e454 100644 --- a/src/components/BaiduMapPicker.tsx +++ b/src/components/BaiduMapPicker.tsx @@ -1,7 +1,289 @@ 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; + sugs: Array; +} + +export class BaiduMapPicker extends React.Component< + MapPickerProps, + MapPickerState +> { + state: MapPickerState = { + inputValue: '', + locs: [], + locIndex: -1, + sugs: [], + }; + + id = uuid(); + mapRef: React.RefObject = 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 = []; + 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 = []; + + 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) { + this.setState({ + inputValue: e.currentTarget.value + }, this.search); + } + + @autobind + handleSelect(e: React.MouseEvent) { + 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) { + 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 { render() { - return

233

; + const {classnames: cx} = this.props; + const {locIndex, locs, inputValue, sugs} = this.state; + const hasSug = Array.isArray(sugs) && sugs.length; + + return ( +
+
+
+ + + + +
+
+ +
+ +
+ {locs.map((item, index) => ( +
+
{item.title}
+
{item.address}
+ {locIndex === index ? : null} +
+ ))} +
+ + {hasSug ? ( +
+ {sugs.map(item => +
{item}
+ )} +
+ ) : null} +
+ ); } } + +export default themeable(BaiduMapPicker); diff --git a/src/components/LocationPicker.tsx b/src/components/LocationPicker.tsx index da756e22..4ad2649e 100644 --- a/src/components/LocationPicker.tsx +++ b/src/components/LocationPicker.tsx @@ -11,6 +11,7 @@ export interface LocationProps { vendor: 'baidu' | 'gaode' | 'tenxun'; placeholder: string; clearable: boolean; + ak: string; value?: { address: string; lat: number; @@ -113,6 +114,17 @@ export class LocationPicker extends React.Component< e.preventDefault(); } + @autobind + handleChange(value: any) { + if (value) { + value = { + ...value, + vendor: this.props.vendor + }; + } + this.props.onChange(value); + } + render() { const { classnames: cx, @@ -122,7 +134,8 @@ export class LocationPicker extends React.Component< placeholder, clearable, popOverContainer, - vendor + vendor, + ak } = this.props; const {isFocused, isOpened} = this.state; @@ -136,7 +149,8 @@ export class LocationPicker extends React.Component< `LocationPicker`, { 'is-disabled': disabled, - 'is-focused': isFocused + 'is-focused': isFocused, + 'is-active': isOpened }, className )} @@ -158,7 +172,7 @@ export class LocationPicker extends React.Component< ) : null} - + {vendor === 'baidu' ? ( - + ) : ({vendor} 地图控件不支持)} diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 65f621f8..81f888b4 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -60,6 +60,9 @@ import MoveIcon from '../icons/move.svg'; // @ts-ignore import InfoIcon from '../icons/info.svg'; +// @ts-ignore +import LocationIcon from '../icons/location.svg'; + // 兼容原来的用法,后续不直接试用。 // @ts-ignore export const closeIcon = ; @@ -121,6 +124,7 @@ registerIcon('search', SearchIcon); registerIcon('back', BackIcon); registerIcon('move', MoveIcon); registerIcon('info', InfoIcon); +registerIcon('location', LocationIcon); export function Icon({ icon, diff --git a/src/icons/location.svg b/src/icons/location.svg new file mode 100644 index 00000000..88b0730c --- /dev/null +++ b/src/icons/location.svg @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/src/renderers/Form/Location.tsx b/src/renderers/Form/Location.tsx index b7eae503..43e6cb0e 100644 --- a/src/renderers/Form/Location.tsx +++ b/src/renderers/Form/Location.tsx @@ -6,6 +6,7 @@ import LocationPicker from '../../components/LocationPicker'; export interface LocationControlProps extends FormControlProps { vendor: 'baidu' | 'gaode' | 'tenxun'; value: any; + ak: string; onChange: (value: any) => void; classnames: ClassNamesFn; classPrefix: string; diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 001437ef..3d0db117 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -1140,3 +1140,24 @@ export function mapObject(value: any, fn: Function): any { } 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); + }); +}