提交 972aef68 编写于 作者: Q qiang

feat(h5): map

上级 c6cef44e
此差异已折叠。
此差异已折叠。
......@@ -12,3 +12,37 @@ export function isInWindows(vm: ComponentPublicInstance) {
}
return false
}
interface Options {
success?: (res: any) => void
fail?: (res: any) => void
complete?: (res: any) => void
}
export function callback(options: Options, errMsg: string): void
export function callback(
options: Options,
data: { [key: string]: any; errMsg: string }
): void
export function callback(
options: Options,
data: { [key: string]: any; errMsg: string } | string
): void {
options = options || {}
if (typeof data === 'string') {
data = {
errMsg: data,
}
}
if (/:ok$/.test(data.errMsg)) {
if (typeof options.success === 'function') {
options.success(data)
}
} else {
if (typeof options.fail === 'function') {
options.fail(data)
}
}
if (typeof options.complete === 'function') {
options.complete(data)
}
}
import Video from './video/index'
import WebView from './web-view/index'
import Map from './map/index'
export { Video, WebView }
export { Video, WebView, Map }
import { defineComponent, inject, onUnmounted, watch } from 'vue'
import { useCustomEvent } from '@dcloudio/uni-components'
import { Map, Circle } from './qqMap/types'
import { QQMapsExt } from './qqMap'
const props = {
latitude: { type: [Number, String], require: true },
longitude: { type: [Number, String], require: true },
color: { type: String, default: '' },
fillColor: { type: String, default: '' },
radius: { type: [Number, String], require: true },
strokeWidth: { type: [Number, String], default: '' },
level: { type: String, default: '' },
}
export type Props = Partial<Record<keyof typeof props, any>>
type CustomEventTrigger = ReturnType<typeof useCustomEvent>
type OnMapReadyCallback = (
map: Map,
maps: QQMapsExt,
trigger: CustomEventTrigger
) => void
type OnMapReady = (callback: OnMapReadyCallback) => void
export default /*#__PURE__*/ defineComponent({
name: 'MapCircle',
props,
setup(props) {
const onMapReady: OnMapReady = inject('onMapReady') as OnMapReady
let circle: Circle
function removeCircle() {
if (circle) {
circle.setMap(null)
}
}
onMapReady((map, maps) => {
function updateCircle(option: Props) {
removeCircle()
addCircle(option)
}
function addCircle(option: Props) {
const center = new maps.LatLng(option.latitude, option.longitude)
function getColor(color: string) {
const c = color.match(/#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/)
if (c && c.length) {
return maps.Color.fromHex(c[0], Number('0x' + c[1] || 255) / 255)
} else {
return undefined
}
}
circle = new maps.Circle({
map,
center,
clickable: false,
radius: option.radius,
strokeWeight: Number(option.strokeWidth) || 1,
fillColor: getColor(option.fillColor) || getColor('#00000001'),
strokeColor: getColor(option.color) || '#000000',
strokeDashStyle: 'solid',
})
}
addCircle(props as Props)
watch(props, updateCircle)
})
onUnmounted(removeCircle)
return () => {
return null
}
},
})
import { defineComponent, inject, onUnmounted, watch, PropType } from 'vue'
import { getRealPath } from '@dcloudio/uni-platform'
import { useCustomEvent } from '@dcloudio/uni-components'
import { Map } from './qqMap/types'
import { QQMapsExt } from './qqMap'
interface Position {
left: number | string
top: number | string
width: number | string
height: number | string
}
const props = {
id: { type: [Number, String], default: '' },
position: { type: Object as PropType<Position>, require: true },
iconPath: { type: String, require: true },
clickable: { type: [Boolean, String], default: '' },
}
export type Props = Partial<Record<keyof typeof props, any>>
type CustomEventTrigger = ReturnType<typeof useCustomEvent>
type OnMapReadyCallback = (
map: Map,
maps: QQMapsExt,
trigger: CustomEventTrigger
) => void
type OnMapReady = (callback: OnMapReadyCallback) => void
export default /*#__PURE__*/ defineComponent({
name: 'MapControl',
props,
setup(props) {
const onMapReady: OnMapReady = inject('onMapReady') as OnMapReady
let control: HTMLDivElement
function removeControl() {
if (control) {
control.remove()
}
}
onMapReady((map, maps, trigger) => {
function updateControl(option: Props) {
removeControl()
addControl(option)
}
function addControl(option: Props) {
const position = option.position || {}
control = document.createElement('div')
const img = new Image()
control.appendChild(img)
const style = control.style
style.position = 'absolute'
style.width = '0'
style.height = '0'
img.onload = () => {
if (option.position.width) {
img.width = option.position.width
}
if (option.position.height) {
img.height = option.position.height
}
const style = img.style
style.position = 'absolute'
style.left = (position.left || 0) + 'px'
style.top = (position.top || 0) + 'px'
style.maxWidth = 'initial'
}
img.src = getRealPath(option.iconPath)
img.onclick = function ($event) {
if (option.clickable) {
trigger('controltap', $event, {
controlId: option.id,
})
}
}
map.controls[maps.ControlPosition.TOP_LEFT].push(control)
}
addControl(props as Props)
watch(props, updateControl)
})
onUnmounted(removeControl)
return () => {
return null
}
},
})
import { defineComponent, inject, onUnmounted, reactive } from 'vue'
import { useCustomEvent } from '@dcloudio/uni-components'
import {
onCompassChange,
offCompassChange,
getLocation,
} from '../../../service/api'
import { Map } from './qqMap/types'
import { QQMapsExt } from './qqMap'
import MapMarker from './MapMarker'
type CustomEventTrigger = ReturnType<typeof useCustomEvent>
type OnMapReadyCallback = (
map: Map,
maps: QQMapsExt,
trigger: CustomEventTrigger
) => void
type OnMapReady = (callback: OnMapReadyCallback) => void
interface State {
latitude: number
longitude: number
rotate: number
}
export interface MoveToLocationOptions {
latitude?: number
longitude?: number
}
export const CONTEXT_ID = 'MAP_LOCATION'
export interface Context {
id: string
state: State
}
type AddMapChidlContext = (context: Context) => void
type RemoveMapChidlContext = (context: Context) => void
const ICON_PATH =
'data:image/png;base64,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'
export default /*#__PURE__*/ defineComponent({
name: 'MapLocation',
setup() {
const onMapReady: OnMapReady = inject('onMapReady') as OnMapReady
const state: State = reactive({
latitude: 0,
longitude: 0,
rotate: 0,
})
let timer: number
function compassChangeHandler(res: { direction: number }) {
state.rotate = res.direction
}
function updateLocation() {
getLocation({
type: 'gcj02',
success: (res) => {
state.latitude = res.latitude
state.longitude = res.longitude
},
complete: () => {
timer = setTimeout(updateLocation, 30000)
},
})
}
function removeLocation() {
if (timer) {
clearTimeout(timer)
}
offCompassChange(compassChangeHandler)
}
onCompassChange(compassChangeHandler)
onMapReady(updateLocation)
onUnmounted(removeLocation)
const addMapChidlContext: AddMapChidlContext = inject(
'addMapChidlContext'
) as AddMapChidlContext
const removeMapChidlContext: RemoveMapChidlContext = inject(
'removeMapChidlContext'
) as RemoveMapChidlContext
const context: Context = {
id: CONTEXT_ID,
state,
}
addMapChidlContext(context)
onUnmounted(() => removeMapChidlContext(context))
return () => {
return state.latitude ? (
<MapMarker
anchor={{ x: 0.5, y: 0.5 }}
width="44"
height="44"
iconPath={ICON_PATH}
{...state}
/>
) : null
}
},
})
此差异已折叠。
import { defineComponent, inject, PropType, onUnmounted, watch } from 'vue'
import { useCustomEvent } from '@dcloudio/uni-components'
import { Map, LatLng, Polyline } from './qqMap/types'
import { QQMapsExt } from './qqMap'
interface Point {
latitude: number
longitude: number
}
const props = {
points: { type: Array as PropType<Point[]>, require: true },
color: { type: String, default: '#000000' },
width: { type: [Number, String], default: '' },
dottedLine: { type: [Boolean, String], default: false },
arrowLine: { type: [Boolean, String], default: false },
arrowIconPath: { type: String, default: '' },
borderColor: { type: String, default: '#000000' },
borderWidth: { type: [Number, String], default: '' },
colorList: {
type: Array,
default() {
return []
},
},
level: { type: String, default: '' },
}
export type Props = Partial<Record<keyof typeof props, any>>
type CustomEventTrigger = ReturnType<typeof useCustomEvent>
type OnMapReadyCallback = (
map: Map,
maps: QQMapsExt,
trigger: CustomEventTrigger
) => void
type OnMapReady = (callback: OnMapReadyCallback) => void
export default /*#__PURE__*/ defineComponent({
name: 'MapPolyline',
props,
setup(props) {
const onMapReady: OnMapReady = inject('onMapReady') as OnMapReady
let polyline: Polyline
let polylineBorder: Polyline
function removePolyline() {
if (polyline) {
polyline.setMap(null)
}
if (polylineBorder) {
polylineBorder.setMap(null)
}
}
onMapReady((map, maps) => {
function updatePolyline(option: Props) {
removePolyline()
addPolyline(option)
}
function addPolyline(option: Props) {
const path: LatLng[] = []
option.points.forEach((point: Point) => {
path.push(new maps.LatLng(point.latitude, point.longitude))
})
const strokeWeight = Number(option.width) || 1
polyline = new maps.Polyline({
map,
clickable: false,
path,
strokeWeight,
strokeColor: option.color || undefined,
strokeDashStyle: option.dottedLine ? 'dash' : 'solid',
})
const borderWidth = Number(option.borderWidth) || 0
if (borderWidth) {
polylineBorder = new maps.Polyline({
map,
clickable: false,
path,
strokeWeight: strokeWeight + borderWidth * 2,
strokeColor: option.borderColor || undefined,
strokeDashStyle: option.dottedLine ? 'dash' : 'solid',
})
}
}
addPolyline(props as Props)
watch(props, updatePolyline)
})
onUnmounted(removePolyline)
return () => {
return null
}
},
})
此差异已折叠。
import { LatLng, Overlay, QQMaps } from './types'
export interface CalloutOptions {
map?: any
position?: LatLng
display?: 'ALWAYS'
boxShadow?: string
content?: string
fontSize?: number
padding?: number
color?: string
borderRadius?: number
bgColor?: string
top?: number
}
export function createCallout(maps: QQMaps) {
const overlay = new maps.Overlay()
class Callout implements Overlay {
option: CalloutOptions
position?: LatLng
index?: number
visible?: boolean
alwaysVisible?: boolean
div: HTMLDivElement
triangle: HTMLDivElement
setMap = overlay.setMap
getMap = overlay.getMap
getPanes = overlay.getPanes
getProjection = overlay.getProjection
map_changed = overlay.map_changed
set = overlay.set
get = overlay.get
setOptions = overlay.setOptions
bindTo = overlay.bindTo
bindsTo = overlay.bindsTo
notify = overlay.notify
setValues = overlay.setValues
unbind = overlay.unbind
unbindAll = overlay.unbindAll
set onclick(callback: any) {
this.div.onclick = callback
}
get onclick(): any {
return this.div.onclick
}
constructor(option: CalloutOptions = {}) {
this.option = option || {}
const map = option.map
this.position = option.position
this.index = 1
const visible = (this.visible = this.alwaysVisible =
option.display === 'ALWAYS')
const div = (this.div = document.createElement('div'))
const divStyle = div.style
divStyle.position = 'absolute'
divStyle.whiteSpace = 'nowrap'
divStyle.transform = 'translateX(-50%) translateY(-100%)'
divStyle.zIndex = '1'
divStyle.boxShadow = option.boxShadow || 'none'
divStyle.display = visible ? 'block' : 'none'
const triangle = (this.triangle = document.createElement('div'))
triangle.setAttribute(
'style',
'position: absolute;white-space: nowrap;border-width: 4px;border-style: solid;border-color: #fff transparent transparent;border-image: initial;font-size: 12px;padding: 0px;background-color: transparent;width: 0px;height: 0px;transform: translate(-50%, 100%);left: 50%;bottom: 0;'
)
this.setStyle(option)
div.appendChild(triangle)
if (map) {
this.setMap(map)
}
}
construct() {
const div = this.div
const panes = this.getPanes()
panes.floatPane.appendChild(div)
}
setOption(option: CalloutOptions) {
this.option = option
this.setPosition(option.position)
if (option.display === 'ALWAYS') {
this.alwaysVisible = this.visible = true
} else {
this.alwaysVisible = false
}
this.setStyle(option)
}
setStyle(option: CalloutOptions) {
const div = this.div
const divStyle = div.style
div.innerText = option.content || ''
divStyle.lineHeight = (option.fontSize || 14) + 'px'
divStyle.fontSize = (option.fontSize || 14) + 'px'
divStyle.padding = (option.padding || 8) + 'px'
divStyle.color = option.color || '#000'
divStyle.borderRadius = (option.borderRadius || 0) + 'px'
divStyle.backgroundColor = option.bgColor || '#fff'
divStyle.marginTop = '-' + ((option.top || 0) + 5) + 'px'
this.triangle.style.borderColor = `${
option.bgColor || '#fff'
} transparent transparent`
}
setPosition(position?: LatLng) {
this.position = position
this.draw()
}
draw() {
const overlayProjection = this.getProjection()
if (!this.position || !this.div || !overlayProjection) {
return
}
const pixel = overlayProjection.fromLatLngToDivPixel(this.position)
const divStyle = this.div.style
divStyle.left = pixel.x + 'px'
divStyle.top = pixel.y + 'px'
}
changed() {
const divStyle = this.div.style
divStyle.display = this.visible ? 'block' : 'none'
}
destroy() {
const parentNode = this.div.parentNode
if (parentNode) {
parentNode.removeChild(this.div)
}
}
}
return Callout
}
import { QQMaps } from './types'
import { createCallout } from './Callout'
export { CalloutOptions } from './Callout'
export type Callout = ReturnType<typeof createCallout>
export interface QQMapsExt extends QQMaps {
Callout: Callout
}
let maps: QQMapsExt
const callbacks: Array<(maps: QQMaps) => void> = []
const QQ_MAP_CALLBACKNAME = '__qq_map_callback__'
interface WindowExt extends Window {
[key: string]: any
}
export function loadMaps(callback: (maps: QQMaps) => void) {
if (maps) {
callback(maps)
} else if (window.qq && window.qq.maps) {
maps = window.qq.maps
callback(maps)
} else if (callbacks.length) {
callbacks.push(callback)
} else {
callbacks.push(callback)
const key = __uniConfig.qqMapKey
const globalExt = window as WindowExt
globalExt[QQ_MAP_CALLBACKNAME] = function () {
delete globalExt[QQ_MAP_CALLBACKNAME]
maps = window.qq.maps
maps.Callout = createCallout(maps)
callbacks.forEach((callback) => callback(maps))
callbacks.length = 0
}
const script = document.createElement('script')
script.src = `https://map.qq.com/api/js?v=2.exp&key=${key}&callback=${QQ_MAP_CALLBACKNAME}&libraries=geometry`
document.body.appendChild(script)
}
}
uni-map {
position: relative;
width: 300px;
height: 150px;
display: block;
}
uni-map[hidden] {
display: none;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册