HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

skyline worklet 纯 setup 写法分享 ts支持 全场景支持

仅在微信小程序测试,多端请自行添加条件编译

首先是一个 worklet.ts 的工具

declare const wx: {  
  worklet: {  
    timing: (value: number, options?: { duration?: number, easing?: any }, callback?: Function) => AnimationObject  
    runOnJS: any  
    shared: <T>(val: T) => SharedValue<T>  
    derived: <T>(val: () => T) => SharedValue<T>  
    Easing: {  
      in: any  
      out: any  
      inOut: any  
      ease: any  
    }  
  }  
}  

export interface SharedValue<T = any> { value: T }  
export type AnimationObject = any  

export const worklet = wx.worklet  

export function getMpInstance() {  
  return (getCurrentInstance()?.proxy as any).$scope  
}  

export interface MpInstance {  
  applyAnimatedStyle: (selector: string, callback: () => Record<string, any>) => void  
}  

export function workletValue<T>(val: T) {  
  return worklet.shared(val)  
}  

export function workletDerived<T>(val: () => T) {  
  return worklet.derived(val)  
}  

export function workletMethods<T extends { [key: string]: Function }>(methods: T) {  
  const mpInstance = getMpInstance()  

  for (const key in methods)  
    mpInstance[key] = methods[key]  

  return methods  
}  

export function useApplyAnimatedStyle() {  
  const mpInstance = getMpInstance()  
  return (selector: string, callback: () => Record<string, any>) => {  
    mpInstance.applyAnimatedStyle(selector, callback)  
  }  
}  

export const GestureState = {  
  POSSIBLE: 0, // 0 此时手势未识别,如 panDown等  
  BEGIN: 1, // 1 手势已识别  
  ACTIVE: 2, // 2 连续手势活跃状态  
  END: 3, // 3 手势终止  
  CANCELLED: 4, // 4 手势取消,  
}  

export const ScrollState = {  
  scrollStart: 0,  
  scrollUpdate: 1,  
  scrollEnd: 2,  
}

使用案例

<script lang="ts">  
export default {  
  options: {  
    virtualHost: true,  
  },  
}  
</script>  

<script setup lang="ts">  
const props = defineProps<{  
  open: boolean  
}>()  

const _height = workletValue(0)  

const applyAnimatedStyle = useApplyAnimatedStyle()  
const { getBoundingClientRect } = useSelectorQuery()  

onMounted(() => {  
  applyAnimatedStyle('.collapse-item', () => {  
    'worklet'  

    return {  
      height: _height.value < 0 ? 'auto' : `${_height.value}px`,  
    }  
  })  
})  

watch(() => props.open, () => {  
  getBoundingClientRect('.collapse-content')  
    .then(rect => rect.height ?? 0)  
    .then((height) => {  
      if (!props.open) {  
        if (_height.value === -1)  
          _height.value = height  
        _height.value = worklet.timing(0, { duration: 300, easing: worklet.Easing.inOut(worklet.Easing.ease) })  
      }  
      else {  
        _height.value = worklet.timing(height, { duration: 300, easing: worklet.Easing.inOut(worklet.Easing.ease) }, () => {  
          'worklet'  
          if (_height.value === height)  
            _height.value = -1  
        })  
      }  
    })  
})  
</script>  

<template>  
  <view class="collapse-item block overflow-hidden">  
    <view class="collapse-content block">  
      <slot />  
    </view>  
  </view>  
</template>  

worklet js 互调 inject popup 示例
popup.ts

const [useProvidePopupStore, usePopupStoreInner] = createInjectionState((close: () => void) => {  
  const _transY = workletValue(1000)  
  const _scrollTop = workletValue(0)  
  const _startPan = workletValue(true)  
  const _popupHeight = workletValue(1000)  

  return { _transY, _scrollTop, _startPan, _popupHeight, close }  
})  

export { useProvidePopupStore }  

export function usePopupStore() {  
  const popupStore = usePopupStoreInner()  
  if (popupStore == null)  
    throw new Error('Please call `useProvidePopupStore` on the appropriate parent component')  
  return popupStore  
}  

export function usePopupWorkletMethods() {  
  const { _popupHeight, _scrollTop, _startPan, _transY, close } = usePopupStore()  

  return workletMethods({  
    openPopup() {  
      'worklet'  
      _transY.value = worklet.timing(0, { duration: 200 })  
    },  
    closePopup() {  
      'worklet'  
      _transY.value = worklet.timing(_popupHeight.value, { duration: 200 })  
      worklet.runOnJS(close)()  
    },  
    // shouldPanResponse 和 shouldScrollViewResponse 用于 pan 手势和 scroll-view 滚动手势的协商  
    shouldPanResponse() {  
      'worklet'  
      return _startPan.value  
    },  
    shouldScrollViewResponse(pointerEvent: any) {  
      'worklet'  
      // transY > 0 说明 pan 手势在移动半屏,此时滚动不应生效  
      if (_transY.value > 0)  
        return false  
      const scrollTop = _scrollTop.value  
      const { deltaY } = pointerEvent  
      // deltaY > 0 是往上滚动,scrollTop <= 0 是滚动到顶部边界,此时 pan 开始生效,滚动不生效  
      const result = scrollTop <= 0 && deltaY > 0  
      _startPan.value = result  
      return !result  
    },  
    handlePan(gestureEvent: any) {  
      'worklet'  
      if (gestureEvent.state === GestureState.ACTIVE) {  
        const curPosition = _transY.value  
        const destination = Math.max(0, curPosition + gestureEvent.deltaY)  
        if (curPosition === destination)  
          return  
        _transY.value = destination  
      }  

      if (  
        gestureEvent.state === GestureState.END  
        || gestureEvent.state === GestureState.CANCELLED  
      ) {  
        if (gestureEvent.velocityY > 500 && _transY.value > 50)  
          this.closePopup()  
        else if (_transY.value > _popupHeight.value / 2)  
          this.closePopup()  
        else  
          this.openPopup()  
      }  
    },  
    adjustDecelerationVelocity(velocity: number) {  
      'worklet'  
      const scrollTop = _scrollTop.value  
      return scrollTop <= 0 ? 0 : velocity  
    },  
    handleScroll(evt: any) {  
      'worklet'  
      _scrollTop.value = evt.detail.scrollTop  
    },  
  })  
}  

popup.vue

<script lang="ts">  
export default {  
  options: {  
    virtualHost: true,  
  },  
}  
</script>  

<script setup lang="ts">  
import { useProvidePopupStore } from './popup'  

const props = withDefaults(defineProps<{  
  open: boolean  
  fullScreen?: boolean  
  rounded?: boolean  
}>(), {  
  fullScreen: false,  
  rounded: true,  
})  

const emit = defineEmits<{  
  (e: 'update:open', val: boolean): void  
  (e: 'scrolltolower'): void  
  (e: 'leave'): void  
  (e: 'afterLeave'): void  
  (e: 'enter'): void  
  (e: 'afterEnter'): void  
}>()  

function onClose() {  
  emit('update:open', false)  
}  

const { _transY, _popupHeight } = useProvidePopupStore(onClose)  

const applyAnimatedStyle = useApplyAnimatedStyle()  

const { getBoundingClientRect } = useSelectorQuery()  

onMounted(() => {  
  getBoundingClientRect('.popup-container').then((res) => {  
    _transY.value = _popupHeight.value = res.height ?? 0  
  })  

  applyAnimatedStyle('.popup-container', () => {  
    'worklet'  
    return {  
      'transform': `translateY(${_transY.value}px)`,  
      'box-shadow': `0px 0px ${_transY.value !== _popupHeight.value ? 10 : 0}px 0px rgba(0, 0, 0, 0.2)`,  
    }  
  })  

  applyAnimatedStyle(`.popup-mask`, () => {  
    'worklet'  
    return {  
      opacity: `${1 - _transY.value / _popupHeight.value}`,  
      display: `${_transY.value !== _popupHeight.value ? 'flex' : 'none'}`,  
    }  
  })  
})  

function onAfterEnter() {  
  emit('afterEnter')  
}  

function onAfterLeave() {  
  emit('afterLeave')  
}  

watch(() => props.open, () => {  
  if (props.open) {  
    emit('enter')  
    _transY.value = worklet.timing(0, { duration: 200 }, () => {  
      'worklet'  

      worklet.runOnJS(onAfterEnter)()  
    })  
  }  
  else {  
    emit('leave')  
    _transY.value = worklet.timing(_popupHeight.value, { duration: 200 }, () => {  
      'worklet'  

      worklet.runOnJS(onAfterLeave)()  
    })  
  }  
})  
</script>  

<template>  
  <root-portal>  
    <view class="popup-mask absolute left-0 top-0 h-screen w-screen" @tap="onClose" />  
    <view class="popup-container absolute bottom-0 w-screen overflow-hidden bg-white" :class="[rounded && 'rounded-t-5', fullScreen ? 'h-screen' : 'h-70vh']">  
      <slot />  
    </view>  
  </root-portal>  
</template>  

<style>  
.popup-container {  
  z-index: 999;  
  transform: translateY(100%);  
  box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);  
}  

.popup-mask {  
  z-index: 998;  
  background-color: rgba(0, 0, 0, 0.5);  
  display: none;  
}  
</style>  

popup-drag-view.vue

<script lang="ts">  
export default {  
  options: {  
    virtualHost: true,  
  },  
  behaviors: [virtualHostClassBehavior],  
}  
</script>  

<script setup lang="ts">  
import { usePopupWorkletMethods } from './popup'  

const { handlePan } = usePopupWorkletMethods()  

const virtualHostClass = useVirtualHostClass()  

const mergedClass = virtualHostClass  
</script>  

<template>  
  <pan-gesture-handler :worklet:ongesture="handlePan.name">  
    <view :class="mergedClass">  
      <slot />  
    </view>  
  </pan-gesture-handler>  
</template>  

popup-scroll-view.vue

<script lang="ts">  
export default {  
  options: {  
    virtualHost: true,  
  },  
  behaviors: [virtualHostClassBehavior],  
}  
</script>  

<script setup lang="ts">  
import { usePopupWorkletMethods } from './popup'  

defineProps<{  
  type: string  
}>()  

defineEmits(['scrolltolower'])  

const { handlePan, shouldPanResponse, shouldScrollViewResponse, adjustDecelerationVelocity, handleScroll } = usePopupWorkletMethods()  

const virtualHostClass = useVirtualHostClass()  

const mergedClass = virtualHostClass  
</script>  

<template>  
  <pan-gesture-handler  
    tag="pan"  
    :worklet:should-response-on-move="shouldPanResponse.name"  
    :simultaneous-handlers="['scroll']"  
    :worklet:ongesture="handlePan.name"  
  >  
    <vertical-drag-gesture-handler  
      tag="scroll"  
      native-view="scroll-view"  
      :worklet:should-response-on-move="shouldScrollViewResponse.name"  
      :simultaneous-handlers="['pan']"  
    >  
      <scroll-view  
        :class="mergedClass"  
        scroll-y  
        :worklet:adjust-deceleration-velocity="adjustDecelerationVelocity.name"  
        :worklet:onscrollupdate="handleScroll.name"  
        type="list"  
        :show-scrollbar="false"  
        @scrolltolower="$emit('scrolltolower')"  
      >  
        <slot />  
      </scroll-view>  
    </vertical-drag-gesture-handler>  
  </pan-gesture-handler>  
</template>  

使用方式

<u-popup v-model:open="open">  
      <u-popup-drag-view v-if="!roomDetail" class="flex-1">  
        <u-loading />  
      </u-popup-drag-view>  
      <u-popup-scroll-view v-else class="flex-1" type="list">  
        <view class="py-4">  
          <view  
            v-for="player in roomDetail?.players"  
            :key="player.steam"  
            class="mb-2 flex-row items-center px-4"  
            @click="onRoomPlayer(player.steam)"  
          >  
            <view>  
              <u-avatar class="h-8 w-8" :src="player.avatar" />  
            </view>  
            <view class="ml-2 flex-1 truncate text-sm">  
              {{ player.name }}  
            </view>  
            <view class="ml-2 text-xs text-neutral-500 font-mono">  
              {{ player.elo }}  
            </view>  
            <view class="ml-2" />  
          </view>  
          <view class="h-[var(--safe-bottom)]" />  
        </view>  
      </u-popup-scroll-view>  
    </u-popup>

至此,便可以细粒度的调整popup的手势协商
在需要用到scroll-into-view的时候 slot中的元素无法被定位
便可以再次复用usePopupWorkletStore来再次实现定制化的popup-scroll-view

workletMethods的实现中,微信小程序会为具名worklet func 添加 _worklet_factory到包含他的object中 (非具名不会处理) 所以需要全部assgin到mpinstance上

继续阅读 »

仅在微信小程序测试,多端请自行添加条件编译

首先是一个 worklet.ts 的工具

declare const wx: {  
  worklet: {  
    timing: (value: number, options?: { duration?: number, easing?: any }, callback?: Function) => AnimationObject  
    runOnJS: any  
    shared: <T>(val: T) => SharedValue<T>  
    derived: <T>(val: () => T) => SharedValue<T>  
    Easing: {  
      in: any  
      out: any  
      inOut: any  
      ease: any  
    }  
  }  
}  

export interface SharedValue<T = any> { value: T }  
export type AnimationObject = any  

export const worklet = wx.worklet  

export function getMpInstance() {  
  return (getCurrentInstance()?.proxy as any).$scope  
}  

export interface MpInstance {  
  applyAnimatedStyle: (selector: string, callback: () => Record<string, any>) => void  
}  

export function workletValue<T>(val: T) {  
  return worklet.shared(val)  
}  

export function workletDerived<T>(val: () => T) {  
  return worklet.derived(val)  
}  

export function workletMethods<T extends { [key: string]: Function }>(methods: T) {  
  const mpInstance = getMpInstance()  

  for (const key in methods)  
    mpInstance[key] = methods[key]  

  return methods  
}  

export function useApplyAnimatedStyle() {  
  const mpInstance = getMpInstance()  
  return (selector: string, callback: () => Record<string, any>) => {  
    mpInstance.applyAnimatedStyle(selector, callback)  
  }  
}  

export const GestureState = {  
  POSSIBLE: 0, // 0 此时手势未识别,如 panDown等  
  BEGIN: 1, // 1 手势已识别  
  ACTIVE: 2, // 2 连续手势活跃状态  
  END: 3, // 3 手势终止  
  CANCELLED: 4, // 4 手势取消,  
}  

export const ScrollState = {  
  scrollStart: 0,  
  scrollUpdate: 1,  
  scrollEnd: 2,  
}

使用案例

<script lang="ts">  
export default {  
  options: {  
    virtualHost: true,  
  },  
}  
</script>  

<script setup lang="ts">  
const props = defineProps<{  
  open: boolean  
}>()  

const _height = workletValue(0)  

const applyAnimatedStyle = useApplyAnimatedStyle()  
const { getBoundingClientRect } = useSelectorQuery()  

onMounted(() => {  
  applyAnimatedStyle('.collapse-item', () => {  
    'worklet'  

    return {  
      height: _height.value < 0 ? 'auto' : `${_height.value}px`,  
    }  
  })  
})  

watch(() => props.open, () => {  
  getBoundingClientRect('.collapse-content')  
    .then(rect => rect.height ?? 0)  
    .then((height) => {  
      if (!props.open) {  
        if (_height.value === -1)  
          _height.value = height  
        _height.value = worklet.timing(0, { duration: 300, easing: worklet.Easing.inOut(worklet.Easing.ease) })  
      }  
      else {  
        _height.value = worklet.timing(height, { duration: 300, easing: worklet.Easing.inOut(worklet.Easing.ease) }, () => {  
          'worklet'  
          if (_height.value === height)  
            _height.value = -1  
        })  
      }  
    })  
})  
</script>  

<template>  
  <view class="collapse-item block overflow-hidden">  
    <view class="collapse-content block">  
      <slot />  
    </view>  
  </view>  
</template>  

worklet js 互调 inject popup 示例
popup.ts

const [useProvidePopupStore, usePopupStoreInner] = createInjectionState((close: () => void) => {  
  const _transY = workletValue(1000)  
  const _scrollTop = workletValue(0)  
  const _startPan = workletValue(true)  
  const _popupHeight = workletValue(1000)  

  return { _transY, _scrollTop, _startPan, _popupHeight, close }  
})  

export { useProvidePopupStore }  

export function usePopupStore() {  
  const popupStore = usePopupStoreInner()  
  if (popupStore == null)  
    throw new Error('Please call `useProvidePopupStore` on the appropriate parent component')  
  return popupStore  
}  

export function usePopupWorkletMethods() {  
  const { _popupHeight, _scrollTop, _startPan, _transY, close } = usePopupStore()  

  return workletMethods({  
    openPopup() {  
      'worklet'  
      _transY.value = worklet.timing(0, { duration: 200 })  
    },  
    closePopup() {  
      'worklet'  
      _transY.value = worklet.timing(_popupHeight.value, { duration: 200 })  
      worklet.runOnJS(close)()  
    },  
    // shouldPanResponse 和 shouldScrollViewResponse 用于 pan 手势和 scroll-view 滚动手势的协商  
    shouldPanResponse() {  
      'worklet'  
      return _startPan.value  
    },  
    shouldScrollViewResponse(pointerEvent: any) {  
      'worklet'  
      // transY > 0 说明 pan 手势在移动半屏,此时滚动不应生效  
      if (_transY.value > 0)  
        return false  
      const scrollTop = _scrollTop.value  
      const { deltaY } = pointerEvent  
      // deltaY > 0 是往上滚动,scrollTop <= 0 是滚动到顶部边界,此时 pan 开始生效,滚动不生效  
      const result = scrollTop <= 0 && deltaY > 0  
      _startPan.value = result  
      return !result  
    },  
    handlePan(gestureEvent: any) {  
      'worklet'  
      if (gestureEvent.state === GestureState.ACTIVE) {  
        const curPosition = _transY.value  
        const destination = Math.max(0, curPosition + gestureEvent.deltaY)  
        if (curPosition === destination)  
          return  
        _transY.value = destination  
      }  

      if (  
        gestureEvent.state === GestureState.END  
        || gestureEvent.state === GestureState.CANCELLED  
      ) {  
        if (gestureEvent.velocityY > 500 && _transY.value > 50)  
          this.closePopup()  
        else if (_transY.value > _popupHeight.value / 2)  
          this.closePopup()  
        else  
          this.openPopup()  
      }  
    },  
    adjustDecelerationVelocity(velocity: number) {  
      'worklet'  
      const scrollTop = _scrollTop.value  
      return scrollTop <= 0 ? 0 : velocity  
    },  
    handleScroll(evt: any) {  
      'worklet'  
      _scrollTop.value = evt.detail.scrollTop  
    },  
  })  
}  

popup.vue

<script lang="ts">  
export default {  
  options: {  
    virtualHost: true,  
  },  
}  
</script>  

<script setup lang="ts">  
import { useProvidePopupStore } from './popup'  

const props = withDefaults(defineProps<{  
  open: boolean  
  fullScreen?: boolean  
  rounded?: boolean  
}>(), {  
  fullScreen: false,  
  rounded: true,  
})  

const emit = defineEmits<{  
  (e: 'update:open', val: boolean): void  
  (e: 'scrolltolower'): void  
  (e: 'leave'): void  
  (e: 'afterLeave'): void  
  (e: 'enter'): void  
  (e: 'afterEnter'): void  
}>()  

function onClose() {  
  emit('update:open', false)  
}  

const { _transY, _popupHeight } = useProvidePopupStore(onClose)  

const applyAnimatedStyle = useApplyAnimatedStyle()  

const { getBoundingClientRect } = useSelectorQuery()  

onMounted(() => {  
  getBoundingClientRect('.popup-container').then((res) => {  
    _transY.value = _popupHeight.value = res.height ?? 0  
  })  

  applyAnimatedStyle('.popup-container', () => {  
    'worklet'  
    return {  
      'transform': `translateY(${_transY.value}px)`,  
      'box-shadow': `0px 0px ${_transY.value !== _popupHeight.value ? 10 : 0}px 0px rgba(0, 0, 0, 0.2)`,  
    }  
  })  

  applyAnimatedStyle(`.popup-mask`, () => {  
    'worklet'  
    return {  
      opacity: `${1 - _transY.value / _popupHeight.value}`,  
      display: `${_transY.value !== _popupHeight.value ? 'flex' : 'none'}`,  
    }  
  })  
})  

function onAfterEnter() {  
  emit('afterEnter')  
}  

function onAfterLeave() {  
  emit('afterLeave')  
}  

watch(() => props.open, () => {  
  if (props.open) {  
    emit('enter')  
    _transY.value = worklet.timing(0, { duration: 200 }, () => {  
      'worklet'  

      worklet.runOnJS(onAfterEnter)()  
    })  
  }  
  else {  
    emit('leave')  
    _transY.value = worklet.timing(_popupHeight.value, { duration: 200 }, () => {  
      'worklet'  

      worklet.runOnJS(onAfterLeave)()  
    })  
  }  
})  
</script>  

<template>  
  <root-portal>  
    <view class="popup-mask absolute left-0 top-0 h-screen w-screen" @tap="onClose" />  
    <view class="popup-container absolute bottom-0 w-screen overflow-hidden bg-white" :class="[rounded && 'rounded-t-5', fullScreen ? 'h-screen' : 'h-70vh']">  
      <slot />  
    </view>  
  </root-portal>  
</template>  

<style>  
.popup-container {  
  z-index: 999;  
  transform: translateY(100%);  
  box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);  
}  

.popup-mask {  
  z-index: 998;  
  background-color: rgba(0, 0, 0, 0.5);  
  display: none;  
}  
</style>  

popup-drag-view.vue

<script lang="ts">  
export default {  
  options: {  
    virtualHost: true,  
  },  
  behaviors: [virtualHostClassBehavior],  
}  
</script>  

<script setup lang="ts">  
import { usePopupWorkletMethods } from './popup'  

const { handlePan } = usePopupWorkletMethods()  

const virtualHostClass = useVirtualHostClass()  

const mergedClass = virtualHostClass  
</script>  

<template>  
  <pan-gesture-handler :worklet:ongesture="handlePan.name">  
    <view :class="mergedClass">  
      <slot />  
    </view>  
  </pan-gesture-handler>  
</template>  

popup-scroll-view.vue

<script lang="ts">  
export default {  
  options: {  
    virtualHost: true,  
  },  
  behaviors: [virtualHostClassBehavior],  
}  
</script>  

<script setup lang="ts">  
import { usePopupWorkletMethods } from './popup'  

defineProps<{  
  type: string  
}>()  

defineEmits(['scrolltolower'])  

const { handlePan, shouldPanResponse, shouldScrollViewResponse, adjustDecelerationVelocity, handleScroll } = usePopupWorkletMethods()  

const virtualHostClass = useVirtualHostClass()  

const mergedClass = virtualHostClass  
</script>  

<template>  
  <pan-gesture-handler  
    tag="pan"  
    :worklet:should-response-on-move="shouldPanResponse.name"  
    :simultaneous-handlers="['scroll']"  
    :worklet:ongesture="handlePan.name"  
  >  
    <vertical-drag-gesture-handler  
      tag="scroll"  
      native-view="scroll-view"  
      :worklet:should-response-on-move="shouldScrollViewResponse.name"  
      :simultaneous-handlers="['pan']"  
    >  
      <scroll-view  
        :class="mergedClass"  
        scroll-y  
        :worklet:adjust-deceleration-velocity="adjustDecelerationVelocity.name"  
        :worklet:onscrollupdate="handleScroll.name"  
        type="list"  
        :show-scrollbar="false"  
        @scrolltolower="$emit('scrolltolower')"  
      >  
        <slot />  
      </scroll-view>  
    </vertical-drag-gesture-handler>  
  </pan-gesture-handler>  
</template>  

使用方式

<u-popup v-model:open="open">  
      <u-popup-drag-view v-if="!roomDetail" class="flex-1">  
        <u-loading />  
      </u-popup-drag-view>  
      <u-popup-scroll-view v-else class="flex-1" type="list">  
        <view class="py-4">  
          <view  
            v-for="player in roomDetail?.players"  
            :key="player.steam"  
            class="mb-2 flex-row items-center px-4"  
            @click="onRoomPlayer(player.steam)"  
          >  
            <view>  
              <u-avatar class="h-8 w-8" :src="player.avatar" />  
            </view>  
            <view class="ml-2 flex-1 truncate text-sm">  
              {{ player.name }}  
            </view>  
            <view class="ml-2 text-xs text-neutral-500 font-mono">  
              {{ player.elo }}  
            </view>  
            <view class="ml-2" />  
          </view>  
          <view class="h-[var(--safe-bottom)]" />  
        </view>  
      </u-popup-scroll-view>  
    </u-popup>

至此,便可以细粒度的调整popup的手势协商
在需要用到scroll-into-view的时候 slot中的元素无法被定位
便可以再次复用usePopupWorkletStore来再次实现定制化的popup-scroll-view

workletMethods的实现中,微信小程序会为具名worklet func 添加 _worklet_factory到包含他的object中 (非具名不会处理) 所以需要全部assgin到mpinstance上

收起阅读 »

关于ios运行白屏问题

iOS 白屏

我这边遇到的情况是图片资源命名错误, 图片从蓝湖导出的是由名字是xxx@2x.png使用时没有改名把@去掉就行了

我这边遇到的情况是图片资源命名错误, 图片从蓝湖导出的是由名字是xxx@2x.png使用时没有改名把@去掉就行了

语雀迁移支付宝云开发:安全与弹性双重升级,开启业务发展新篇章

云开发 支付宝小程序云

4月12日凌晨2点,随着最后一条数据流的平稳迁移,语雀正式完成了至支付宝云开发的切换,翻开了业务发展的新篇章。这次迁移不仅是技术架构的升级,更是语雀在安全性和弹性伸缩能力方面的一次飞跃,为未来迎接更大的挑战做好了充足的准备。

一、迁移前景及背景分析

语雀当前用户达千万级,云函数的日调用量在 1kw 左右。过去,由于传统架构的局限,语雀面临着日益增长的安全及弹性挑战。高昂的维护成本、难以适应流量波动的扩容需求,以及不断升级的安全威胁,成为亟需解决的痛点。迁移至支付宝云开发,代表了对这些挑战的积极回应和预见性规划。

二、调研与规划阶段

在详尽的预期效益分析后,语雀选定了支付宝云开发平台,以期提高安全能力和云资源的弹性伸缩能力,同时降低成本。迁移前的准备工作包括对团队的培训和新环境的搭建,确保迁移过程的顺畅。

三、迁移实施和成果

利用语雀强大的文档管理能力,团队管理了整个迁移计划的文档和进度,确保每一步都有据可循。在搭建支付宝云开发环境后,逐一迁移了云函数,并实施了闭环的数据迁移与同步策略。经过严格的测试和验证,迁移成功切流。迁移完成后的语雀小程序,展现出了显著的改进与提升:

安全升级,护航文档创作

安全是任何线上平台的生命线,对于承载大量知识内容的语雀来说更是如此。

通过迁移至支付宝开发,语雀获得了更强大的安全防护能力。支付宝云开发提供了全面的安全解决方案,包括DDoS防护、WAF、漏洞扫描等,有效保障平台抵御各类安全威胁。同时,安全应急响应机制也得到了显著提升,保障用户数据安全和平台稳定运行。

弹性伸缩,资源高效利用

随着用户量和业务规模的增长,平台需要具备灵活的资源伸缩能力。

支付宝云开发的弹性伸缩功能,完美地解决了这一需求。语雀可以根据业务流量、资源使用率等指标,自动调整计算资源,实现资源的按需分配和高效利用。这种弹性伸缩能力不仅降低了机器成本,也大大缩短了扩缩容时间,保障平台稳定应对流量高峰。

易用性强,开发效率提升

支付宝云开发平台为开发者提供了简洁易用的开发环境和丰富的功能组件。

语雀团队通过使用云函数、云数据库等云开发产品,快速实现了业务逻辑的迁移和功能的迭代。云开发平台的低代码特性,降低了开发门槛,让开发者更专注于业务逻辑的实现,显著提升了开发效率。

优化成本,助力业务发展

语雀迁移至支付宝云开发平台后,不仅获得了更强的安全防护能力和弹性伸缩能力,也实现了成本最优控制的目标。资源的高效利用和开发效率的提升,为语雀节省了大量的运营成本,也为业务的快速发展提供了强有力的支持。

四、支付宝云开发:开发者的首选

语雀迁移支付宝云开发平台的成功案例,充分展现了云开发产品的易用性、稳定性和降本提效的优势。对于开发者而言,云开发是构建现代化应用的理想选择,它可以帮助开发者:

▪️ 专注于业务逻辑: 无需关注底层基础设施,专注于业务逻辑的实现。
▪️ 快速构建应用: 丰富的云端能力和完善的工具链,帮助开发者快速构建应用。
▪️ 降低开发成本: 免去服务器运维,降低开发和运维成本。

如果您正在寻找一个高效、稳定、安全的开发平台,支付宝云开发将是您的不二选择。

继续阅读 »

4月12日凌晨2点,随着最后一条数据流的平稳迁移,语雀正式完成了至支付宝云开发的切换,翻开了业务发展的新篇章。这次迁移不仅是技术架构的升级,更是语雀在安全性和弹性伸缩能力方面的一次飞跃,为未来迎接更大的挑战做好了充足的准备。

一、迁移前景及背景分析

语雀当前用户达千万级,云函数的日调用量在 1kw 左右。过去,由于传统架构的局限,语雀面临着日益增长的安全及弹性挑战。高昂的维护成本、难以适应流量波动的扩容需求,以及不断升级的安全威胁,成为亟需解决的痛点。迁移至支付宝云开发,代表了对这些挑战的积极回应和预见性规划。

二、调研与规划阶段

在详尽的预期效益分析后,语雀选定了支付宝云开发平台,以期提高安全能力和云资源的弹性伸缩能力,同时降低成本。迁移前的准备工作包括对团队的培训和新环境的搭建,确保迁移过程的顺畅。

三、迁移实施和成果

利用语雀强大的文档管理能力,团队管理了整个迁移计划的文档和进度,确保每一步都有据可循。在搭建支付宝云开发环境后,逐一迁移了云函数,并实施了闭环的数据迁移与同步策略。经过严格的测试和验证,迁移成功切流。迁移完成后的语雀小程序,展现出了显著的改进与提升:

安全升级,护航文档创作

安全是任何线上平台的生命线,对于承载大量知识内容的语雀来说更是如此。

通过迁移至支付宝开发,语雀获得了更强大的安全防护能力。支付宝云开发提供了全面的安全解决方案,包括DDoS防护、WAF、漏洞扫描等,有效保障平台抵御各类安全威胁。同时,安全应急响应机制也得到了显著提升,保障用户数据安全和平台稳定运行。

弹性伸缩,资源高效利用

随着用户量和业务规模的增长,平台需要具备灵活的资源伸缩能力。

支付宝云开发的弹性伸缩功能,完美地解决了这一需求。语雀可以根据业务流量、资源使用率等指标,自动调整计算资源,实现资源的按需分配和高效利用。这种弹性伸缩能力不仅降低了机器成本,也大大缩短了扩缩容时间,保障平台稳定应对流量高峰。

易用性强,开发效率提升

支付宝云开发平台为开发者提供了简洁易用的开发环境和丰富的功能组件。

语雀团队通过使用云函数、云数据库等云开发产品,快速实现了业务逻辑的迁移和功能的迭代。云开发平台的低代码特性,降低了开发门槛,让开发者更专注于业务逻辑的实现,显著提升了开发效率。

优化成本,助力业务发展

语雀迁移至支付宝云开发平台后,不仅获得了更强的安全防护能力和弹性伸缩能力,也实现了成本最优控制的目标。资源的高效利用和开发效率的提升,为语雀节省了大量的运营成本,也为业务的快速发展提供了强有力的支持。

四、支付宝云开发:开发者的首选

语雀迁移支付宝云开发平台的成功案例,充分展现了云开发产品的易用性、稳定性和降本提效的优势。对于开发者而言,云开发是构建现代化应用的理想选择,它可以帮助开发者:

▪️ 专注于业务逻辑: 无需关注底层基础设施,专注于业务逻辑的实现。
▪️ 快速构建应用: 丰富的云端能力和完善的工具链,帮助开发者快速构建应用。
▪️ 降低开发成本: 免去服务器运维,降低开发和运维成本。

如果您正在寻找一个高效、稳定、安全的开发平台,支付宝云开发将是您的不二选择。

收起阅读 »

微信小程序、H5、APP百度地图组件的实现(定位、获取定位附近地址列表、移动地图获取地图中心附近地址列表)

百度地图

最新在开发公司用的百度地图实现,发现社区这方向资料参差不齐,很多都有残缺。下面看我的例子来一起做个百度地图的组件吧(仅限在H5、APP、微信小程序中使用)

在微信小程序中用了取巧的方式,使用uni自带map组件(在微信小程序中,map组件为腾讯地图),同时在百度后台不仅仅需要创建一个Web、iOS、Android的AK,同时还需要再创建一个服务器的AK,主要是用在微信小程序中,用来将地图获取的经纬度进行转换、逆地理编码、POI查询等功能。

在微信管理后台需要开启定位权限,具体看uni.getLocation的要求。

下方代码中会指出哪里使用百度地图Web AK,哪里使用百度地图服务器 AK。请详细查看下方代码注释。

1、创建一个定位公共类
起名:location.js
代码如下:

// 获取位置信息  
const getLocation = () => {  
    return new Promise((resolve, reject) => {  
        uni.showLoading({title: '获取位置中'});  
        // #ifdef APP-PLUS || H5  
        uni.getLocation({  
            /*  
            * 目前国内主要有以下三种坐标系:  
            * WGS84:为一种大地坐标系,也是目前广泛使用的GPS全球卫星定位系统使用的坐标系。  
            * GCJ02:又称火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系。  
            * BD09:为百度坐标系,在GCJ02坐标系基础上再次加密。其中bd09ll表示百度经纬度坐标,bd09mc表示百度墨卡托米制坐标。  
            *   
            * 如果使用的定位为手机自带定位,那么type 使用 GCJ02。  
            * 如果是使用百度地图定位,那么type 使用 BD09。(需要在项目中的manifest.json文件配置 百度地图定位的AK)  
            * 如果是使用高德地图、腾讯地图定位,那么type 使用 GCJ02。(需要在项目中的manifest.json文件配置 高德地图定位的AK)  
            * 如果是使用谷歌地图定位,那么type 使用 WGS84。(需要在项目中的manifest.json文件配置 谷歌地图定位的AK)  
            */  
            type: 'BD09',  
            geocode: true,  
            success: function(data) {  
                resolve(data)  
            },  
            fail: function(error) {  
                reject(error)  
            },  
            complete: function() {  
                uni.hideLoading();  
            }  
        })  
        // #endif  

        // #ifdef MP-WEIXIN  
        uni.getLocation({  
            /*  
            * 目前国内主要有以下三种坐标系:  
            * WGS84:为一种大地坐标系,也是目前广泛使用的GPS全球卫星定位系统使用的坐标系。  
            * GCJ02:又称火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系。  
            * BD09:为百度坐标系,在GCJ02坐标系基础上再次加密。其中bd09ll表示百度经纬度坐标,bd09mc表示百度墨卡托米制坐标。  
            *   
            * 如果使用的定位为手机自带定位,那么type 使用 GCJ02。  
            * 如果是使用百度地图定位,那么type 使用 BD09。(需要在项目中的manifest.json文件配置 百度地图定位的AK)  
            * 如果是使用高德地图、腾讯地图定位,那么type 使用 GCJ02。(需要在项目中的manifest.json文件配置 高德地图定位的AK)  
            * 如果是使用谷歌地图定位,那么type 使用 WGS84。(需要在项目中的manifest.json文件配置 谷歌地图定位的AK)  
            */  
            type: 'GCJ02',  
            geocode: true,  
            success: function(data) {  
                resolve(data)  
            },  
            fail: function(error) {  
                reject(error)  
            },  
            complete: function() {  
                uni.hideLoading();  
            }  
        })  
        // #endif  
    })  
};  

/**  
 * 检测是否开启定位  
*/  
const checkOpenGPSServiceByAndroidIOS = () => {  
    let system = uni.getSystemInfoSync(); // 获取系统信息  
    if (system.platform === 'android') { // 判断平台  
        var context = plus.android.importClass("android.content.Context");  
        var locationManager = plus.android.importClass("android.location.LocationManager");  
        var main = plus.android.runtimeMainActivity();  
        var mainSvr = main.getSystemService(context.LOCATION_SERVICE);  
        if (!mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER)) {  
            uni.showModal({  
                title: '温馨提示',  
                content: '您未开启定位服务,请打开定位服务功能,以便获取您的位置!',  
                showCancel: true,  
                success() {  
                    var Intent = plus.android.importClass('android.content.Intent');  
                    var Settings = plus.android.importClass('android.provider.Settings');  
                    var intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);  
                    main.startActivity(intent); // 打开系统设置GPS服务页面  
                }  
            });  
            return {s: 'android', b: false};  
        } else {  
            return {s: 'android', b: true};  
        }  
    } else if (system.platform === 'ios') {  
        var cllocationManger = plus.ios.import("CLLocationManager");  
        var enable = cllocationManger.locationServicesEnabled();  
        var status = cllocationManger.authorizationStatus();  
        plus.ios.deleteObject(cllocationManger);  
        if (enable && status != 2) {  
            // 手机系统的定位已经打开  
            return {s: 'ios', b: true};  
        } else {  
            // 手机系统的定位没有打开  
            uni.showModal({  
                title: '提示',  
                content: '请前往设置-隐私-定位服务打开定位服务功能',  
                showCancel: true,  
                success() {  
                    var UIApplication = plus.ios.import("UIApplication");  
                    var application2 = UIApplication.sharedApplication();  
                    var NSURL2 = plus.ios.import("NSURL");  
                    var setting2 = NSURL2.URLWithString("app-settings:"); // UIApplicationOpenSettingsURLString  
                    application2.openURL(setting2);  
                    plus.ios.deleteObject(setting2);  
                    plus.ios.deleteObject(NSURL2);  
                    plus.ios.deleteObject(application2);  
                }  
            });  
            return {s: 'ios', b: false};  
        }  
    } else {  
        return {s: 'h5', b: false};  
    }  
}  

const getLocationChange = () => {  
    return new Promise((resolve, reject) => {  
        uni.startLocationUpdate({  
            success: function() {  
                uni.onLocationChange(function (res) {  
                    // 纬度:res.latitude 经度:res.longitude  
                    resolve(res)  
                });  

                uni.onLocationChangeError(function (error) {  
                    reject(error);  
                });  
            },  
            fail: function(error) {  
                reject(error);  
            },  
            complete: function() {  
                // 调用开启小程序接收位置消息 API 完成  
            }  
        });  
    })  
}  

const stopLocationUpdate = () => {  
    uni.stopLocationUpdate(function () {  
        // 关闭监听实时位置变化,前后台都停止消息接收。  
    })  
}  

export default {  
    getLocation: getLocation,  
    getLocationChange: getLocationChange,  
    stopLocationUpdate: stopLocationUpdate,  
    checkOpenGPSServiceByAndroidIOS: checkOpenGPSServiceByAndroidIOS,  
}

2、创建一个名字叫 xn-map.vue 的组件 (不知道uniapp 如何创建组件,自行百度一下)
创建完后,记得进行引入:
import xnMap from '@/components/xn-map/xn-map.vue';
Vue.component('xn-map', xnMap);

以下是 xn-map.vue 代码:
代码中的ak记得换成自己的百度地图web ak 和 百度地图服务器ak

<template>  
    <view class="baidu_map">  
        <view :style="!showSearch ? 'width: 100%; height: 100%; position: relative;' : 'width: 100%; height: 50%; position: relative;'">  
            <!-- #ifdef APP-PLUS || H5 -->  
            <view  
                id="myMap"  
                class="myMap"  
                :prop="dicValue"  
                :change:prop="bmap.changeValue"  
            >  
            </view>  
            <!-- #endif -->  
            <!-- #ifdef MP-WEIXIN -->  
            <map  
                class="myMap"  
                id="myMap"  
                scale="17"  
                enable-rotate="false"  
                show-location="true"  
                :latitude="maplatitude"  
                :longitude="mpalongitude"  
                @regionchange="regionchange"  
            ></map>  
            <!-- #endif -->  
            <view class="location" @click="clickLocation">  
                <u--image :src="sLocationIcon" width="28px" height="28px"></u--image>  
            </view>  
            <view v-if="showSearch" class="marking"></view>  
        </view>  

        <view v-if="showSearch" class="address_list">  
            <view class="search">  
                <u-search :clearabled="true" v-model="sQueryValue" @search="searchQueryValue" @custom="searchQueryValue"></u-search>  
            </view>  
            <view class="list">  
                <view v-for="(item, index) in arrayAddress" :key="index" @click="clickAddressCell(item, index)">  
                    <view class="location_cell">  
                        <view class="centent">  
                            <view class="title">  
                                {{ isEmptyString(item.title) ? '--' : item.title }}  
                            </view>  
                            <view class="sub_title">  
                                {{ item.address }}  
                            </view>  
                        </view>  
                        <view class="right" v-if="item.checked">  
                            <u-icon name="checkbox-mark" color="#2979ff" size="18"></u-icon>  
                        </view>  
                    </view>  
                    <u-line></u-line>  
                </view>  
            </view>  
            <view class="footer" :style="sSearchStyle">  
                <u-button type="primary" text="保存" @click="clickFootButton"></u-button>  
            </view>  
        </view>  
    </view>  
</template>  

<script>  
    import location from '@/tool/location.js';  

    export default {  
        name:"crm-map",  
        props: {  
            showSearch: {  
                type: Boolean,  
                default() {  
                    return false;  
                }  
            },  
        },  
        data() {  
            return {  
                sSearchStyle: "",  

                dicValue: null,  
                sLocationIcon: "/static/map_location.png",  
                sQueryValue: "",  
                arrayAddress: [],  
                iLastIndex: 0,  

                // #ifdef MP-WEIXIN  
                mapObjs: null,  
                needSearch: true,  
                city: "深圳市",  
                maplatitude: 22.685393,  
                maplongitude: 113.798274,  
                // #endif  
            };  
        },  
        mounted() {  
            let res = this.getSystemInfo();  
            this.sSearchStyle = 'margin-bottom: ' + res.safeAreaInsets.bottom + 'px;';  
            // #ifdef APP-PLUS || H5  
            this.dicValue = { type: 'needOtherAddress', needSerach: this.showSearch, guid: uni.$u.guid(), };  
            // #endif  

            // #ifdef MP-WEIXIN  
            if (this.mapObjs === undefined || this.mapObjs === null) {  
                this.mapObjs = uni.createMapContext('myMap', this); // 得到map实例对象  
            }  
            this.clickLocation();  
            // #endif  
        },  
        methods: {  
            /*  
            * 在改变 dicValue 时,或许你会有疑惑,为啥字典中都要带一个自动生成 32 位的guid  
            * 这是因为,如果没有带这个自动生成的guid,在renderjs中,当改变的 dicValue 是同一个值,监听函数是不会被调用的(H5会被调用,APP不会)  
            * 所以在字典中添加一个 自动生成的 guid,有助于renderjs中监听函数changeValue的生效。  
            */  
            // #ifdef APP-PLUS || H5  
            // 显示自身定位位置 回调   
            showMarking() {  
                if (this.showSearch) {  
                    this.dicValue = { type: 'addEventListener', guid: uni.$u.guid(), }  
                }  
                setTimeout(() => {  
                    this.clickLocation();  
                }, 200)  
            },  
            // #endif  

            // #ifdef MP-WEIXIN  
            regionchange(val) {  
                // 在安卓中是 end 事件  
                if (val.type === 'end' && this.needSearch) {  
                    this.getCenterLatLong();  
                    return;  
                }  

                // 在ios中是 regionchange  
                if (val.type ==='regionchange' && this.needSearch) {  
                    this.getCenterLatLong();  
                    return;  
                }  
            },  
            // 获取中心点位置  
            getCenterLatLong() {  
                if (this.mapObjs === undefined || this.mapObjs === null) {  
                    this.mapObjs = uni.createMapContext('myMap', this); // 得到map实例对象  
                }  
                const than = this;  
                this.mapObjs.getCenterLocation({  
                    success: res => {  
                        if (res.errMsg === 'getMapCenterLocation:ok') {  
                            than.circularRegionRetrieval(res.latitude, res.longitude);  
                        } else {  
                            uni.showToast({ icon: 'none', title: res.errMsg || '获取位置出错', duration: 3000 });  
                        }  
                    },  
                    fail: res => {  
                        uni.showToast({ icon: 'none', title: res.errMsg || '获取位置出错', duration: 3000 });  
                    },  
                    complete: res => {}  
                })  
            },  
            // 设置地图中心点  
            setMapCenter(latitude, longitude) {  
                if (this.mapObjs === undefined || this.mapObjs === null) {  
                    this.mapObjs = uni.createMapContext('myMap', this); // 得到map实例对象  
                }  
                this.needSearch = false;  
                const than = this;  
                this.mapObjs.moveToLocation({  
                    longitude: longitude,  
                    latitude: latitude,  
                    success: res => {},  
                    fail: res => {},  
                    complete: res => {   
                        setTimeout(() => {  
                            than.needSearch = true;  
                        }, 500)  
                    }  
                })  
            },  
            // 坐标转换  
            locationConversion(latitude, longitude, sendLocation, sendAddress, dicAddress) {  
                let sUrl = 'https://api.map.baidu.com/geoconv/v2/';  
                let params = {  
                    ak: '百度地图服务器ak',  
                    output: 'json',  
                    model: '1',  
                    coords: `${longitude},${latitude}`  
                }  
                uni.$u.http.get(sUrl, {params: params}).then(res => {  
                    uni.hideLoading();  
                    if (res.status === 0) {  
                        if (sendLocation) {  
                            this.sendLocation({longitude: res.result[0].x, latitude: res.result[0].y})  
                        }  

                        if (sendAddress) {  
                            let dicData = {  
                                title: dicAddress.title,  
                                address: dicAddress.address,  
                                point: {lat: res.result[0].y, lng: res.result[0].x},  
                                checked: true  
                            }  
                            this.$emit("clickSave", dicData);  
                        }  
                    } else {  
                        uni.showToast({ icon: 'none', title: res.message || '获取位置信息出错', duration: 3000 });  
                    }  
                }).catch(err => {  
                    uni.hideLoading();  
                    uni.showToast({ icon: 'none', title: err.errMsg || '获取位置信息出错', duration: 3000 });  
                })  
            },  
            // 获取当前自身坐标点 地理信息  
            getMyAddress(latitude, longitude, type) {  
                let sUrl = 'https://api.map.baidu.com/reverse_geocoding/v3/';  
                let params = {  
                    ak: '百度地图服务器ak',  
                    output: 'json',  
                    coordtype: type,  
                    location: `${latitude},${longitude}`  
                }  
                uni.$u.http.get(sUrl, {params: params}).then(res => {  
                    if (res.status === 0) {  
                        this.city = res.result.addressComponent.city;  
                        this.updateMyAddress({  
                            address: res.result.formatted_address,  
                            txPoint: res.result.location,  
                        })  
                    } else {  
                        uni.showToast({ icon: 'none', title: res.message || '获取位置信息出错', duration: 3000 });  
                    }  
                }).catch(err => {  
                    uni.showToast({ icon: 'none', title: err.errMsg || '获取位置信息出错', duration: 3000 });  
                })  
            },  
            // 圆形区域检索  
            circularRegionRetrieval(latitude, longitude) {  
                let sUrl = 'https://api.map.baidu.com/place/v2/search';  
                let params = {  
                    ak: '百度地图服务器ak',  
                    output: 'json',  
                    query: '公司企业$房地产$美食$酒店$购物$生活服务$休闲娱乐$医疗$交通设施$政府机构',  
                    coord_type: 2,  
                    ret_coordtype: 'gcj02ll',  
                    location: `${latitude},${longitude}`,  
                    radius: 1000,  
                }  
                uni.$u.http.get(sUrl, {params: params}).then(res => {  
                    if (res.status === 0) {  
                        let arrayAddressList = [];  
                        for (let item of res.results) {  
                            arrayAddressList.push({  
                                title: item.name,  
                                address: item.address,  
                                point: item.location,  
                                checked: false,  
                            });  
                        }  
                        this.updateAddressList(arrayAddressList);  
                    } else {  
                        uni.showToast({ icon: 'none', title: res.message || '获取位置信息出错', duration: 3000 });  
                    }  
                }).catch(err => {  
                    uni.showToast({ icon: 'none', title: err.errMsg || '获取位置信息出错', duration: 3000 });  
                })  
            },  
            // 地点输入检索  
            locationInputRetrieval() {  
                let sUrl = 'https://api.map.baidu.com/place/v2/suggestion';  
                let params = {  
                    ak: '百度地图服务器ak',  
                    output: 'json',  
                    ret_coordtype: 'gcj02ll',  
                    query: this.sQueryValue,  
                    region: this.city,  
                }  
                uni.$u.http.get(sUrl, {params: params}).then(res => {  
                    if (res.status === 0) {  
                        let arrayAddressList = [];  
                        let i = 0;  
                        for (let item of res.result) {  
                            arrayAddressList.push({  
                                title: item.name,  
                                address: item.address,  
                                point: item.location,  
                                checked: i === 0 ? true: false  
                            });  
                            i = i + 1;  
                        }  
                        this.updateAddressList(arrayAddressList);  
                    } else {  
                        uni.showToast({ icon: 'none', title: res.message || '获取位置信息出错', duration: 3000 });  
                    }  
                }).catch(err => {  
                    uni.showToast({ icon: 'none', title: err.errMsg || '获取位置信息出错', duration: 3000 });  
                })  
            },  
            // #endif  

            // 更新地址列表  
            updateAddressList(val) {  
                this.iLastIndex = 0;  
                this.arrayAddress = val;  
            },  
            // 向父页面发送当前坐标经纬度  
            sendLocation(val) {  
                this.$emit("sendLocation", val);  
            },  
            // 向父页面发送当前坐标地址信息  
            updateMyAddress(val) {  
                this.$emit("sendMyAddress", val);  
            },  
            // 获取自身当前经纬度  
            clickLocation() {  
                // #ifdef APP-PLUS  
                let dicInfo = location.checkOpenGPSServiceByAndroidIOS();  
                if (dicInfo.s !== 'h5') {  
                    if (dicInfo.b) {  
                        location.getLocation().then((resp) => {  
                            this.dicValue = {  
                                type: 'addMyLocationIcon',  
                                longitude: resp.longitude,  
                                latitude: resp.latitude,  
                                guid: uni.$u.guid(),  
                            }  
                            this.sendLocation({longitude: resp.longitude, latitude: resp.latitude});  
                        }).catch((error) => {  
                            uni.showToast({ icon: 'none', title: error.errMsg || '获取位置出错', duration: 3000 });  
                        });  
                    }  
                } else {  
                    this.dicValue = { type: 'getH5LocationPosition', guid: uni.$u.guid(), }  
                }  
                // #endif  

                // #ifdef H5  
                this.dicValue = { type: 'getH5LocationPosition', guid: uni.$u.guid(), }  
                // #endif  

                // #ifdef MP-WEIXIN  
                location.getLocation().then((resp) => {  
                    if (this.mapObjs === undefined || this.mapObjs === null) {  
                        this.mapObjs = uni.createMapContext('myMap', this); // 得到map实例对象  
                    }  
                    this.maplatitude = resp.latitude;  
                    this.maplongitude = resp.longitude;  
                    this.needSearch = false;  
                    const than = this;  
                    this.mapObjs.moveToLocation({  
                        longitude: resp.longitude,  
                        latitude: resp.latitude,  
                        success: res => {  
                            than.getMyAddress(resp.latitude, resp.longitude, 'gcj02ll');  
                            than.circularRegionRetrieval(resp.latitude, resp.longitude);  
                        },  
                        fail: res => {  
                            uni.showToast({ icon: 'none', title: res.errMsg || '获取位置出错', duration: 3000 });  
                        },  
                        complete: res => {  
                            setTimeout(() => {  
                                than.needSearch = true;  
                            }, 500)  
                        }  
                    })  
                    this.locationConversion(resp.latitude, resp.longitude, true, false, null);  
                }).catch((error) => {  
                    uni.showToast({ icon: 'none', title: error.errMsg || '获取位置出错111', duration: 3000 });  
                });  
                // #endif  
            },  
            // 搜索按钮点击事件  
            searchQueryValue() {  
                uni.hideKeyboard();  
                // #ifdef APP-PLUS || H5  
                this.dicValue = { type: 'searchQueryValue', sSearchValue: this.sQueryValue, guid: uni.$u.guid(), };  
                // #endif  

                // #ifdef MP-WEIXIN  
                this.locationInputRetrieval();  
                // #endif  
            },  
            // 地址信息Cell 点击事件  
            clickAddressCell(item, index) {  
                if (this.arrayAddress[index].checked) {  
                    this.iLastIndex = index;  
                    return;  
                }  
                if (this.iLastIndex === index) {  
                    this.arrayAddress[this.iLastIndex].checked = !this.arrayAddress[this.iLastIndex].checked;  
                } else {  
                    this.arrayAddress[this.iLastIndex].checked = !this.arrayAddress[this.iLastIndex].checked;  
                    this.arrayAddress[index].checked = !this.arrayAddress[index].checked;  
                }  

                this.iLastIndex = index;  
                // #ifdef APP-PLUS || H5  
                this.dicValue = { type: 'moveMapCentre', point: {longitude: item.point.lng, latitude: item.point.lat}, guid: uni.$u.guid(), };  
                // #endif  
                // #ifdef MP-WEIXIN  
                this.setMapCenter(item.point.lat, item.point.lng);  
                // #endif  
            },  
            // 保存按钮 点击事件  
            clickFootButton() {  
                if (this.arrayAddress.length === 0) {  
                    uni.showToast({ icon: 'none', title: '请选择您要保存的地址', duration: 3000 });  
                    return;  
                }  
                // #ifdef APP-PLUS || H5  
                this.$emit("clickSave", this.arrayAddress[this.iLastIndex]);  
                // #endif  

                // #ifdef MP-WEIXIN  
                uni.showLoading({title:"加载中...", mask: true})  
                this.locationConversion(this.arrayAddress[this.iLastIndex].point.lat, this.arrayAddress[this.iLastIndex].point.lng, false, true, this.arrayAddress[this.iLastIndex]);  
                // #endif  
            },  
        }  
    }  
</script>  

<!-- #ifdef APP-PLUS || H5 -->   
<script module="bmap" lang="renderjs">  
    export default {  
        data() {  
            return {  
                map: null,  
                locationImg: '/static/location.png', // require("/static/location.png"),  
                innerValue: null,  
                needOtherAddress: false,  
            };  
        },  
        mounted(){  
            // 初始化百度地图  
            this.initBaiDuMap();  
        },  
        methods: {  
            // 动态创建Script标签  
            createScript(url) {  
                return new Promise((resolve,reject) => {  
                    var script = document.createElement('script');  
                    script.type = 'text/javascript';  
                    script.src = url;  
                    script.onload = () => { resolve() };  
                    script.onerror = () => { reject() };  
                    document.head.appendChild(script);  
                })  
            },  
            initBaiDuMap() {  
                const ak = '百度地图web ak';  
                if (typeof window.BMap === 'function') {  
                    this.initMap();  
                } else {  
                    window.init = () => this.initMap();  
                    this.createScript(`https://api.map.baidu.com/api?v=3.0ak=${ak}&callback=init`)  
                }  
            },  
            async initMap() {  
                // myMap 要渲染地图的view的id  
                this.map = new BMap.Map("myMap");  
                this.map.centerAndZoom(this.getPoint(113.804346, 22.691386), 17); // 设置中心点  
                var scaleCtrl = new BMap.ScaleControl(); // 添加比例尺控件  
                this.map.addControl(scaleCtrl);  
                this.$ownerInstance.callMethod("showMarking");  
            },  
            // 转换地图坐标点 经度longitude 纬度latitude  
            getPoint(longitude, latitude) {  
                return new BMap.Point(longitude, latitude);  
            },  
            // 接收Value改变时的数据,用来操作百度地图的API  
            changeValue(val) {  
                if (val === null || val === undefined) {  
                    return  
                }  
                this.innerValue = val;  
                if (this.innerValue.type === 'addMyLocationIcon') {  
                    this.addMyLocationIcon(this.innerValue);  
                } else if (this.innerValue.type === 'getH5LocationPosition') {  
                    this.getH5LocationPosition();  
                } else if (this.innerValue.type === 'addEventListener') {  
                    this.addBaiduEventListener();  
                } else if (this.innerValue.type === 'searchQueryValue') {  
                    this.getSearchKeyAddreeList(this.innerValue.sSearchValue);  
                } else if (this.innerValue.type === 'moveMapCentre') {  
                    this.moveMapCentre(this.innerValue.point);  
                } else if (this.innerValue.type === 'needOtherAddress') {  
                    this.needOtherAddress = this.innerValue.needSerach;  
                } else {  

                }  
                this.innerValue = null;  
            },  
            // 添加自身定位坐标点 并且将地图中心点移动至定位坐标点 经度longitude 纬度latitude  
            addMyLocationIcon(val) {  
                // 清除地图上所有覆盖物  
                this.map.clearOverlays();  

                let point = this.getPoint(val.longitude, val.latitude);  

                // 构造函数: 以给定的图像地址和大小创建图标对象实例  
                var startIcon = new BMap.Icon(  
                    this.locationImg,  
                    new BMap.Size(30, 30)  
                );  
                // 设置图标的大小  
                startIcon.setImageSize(new BMap.Size(30, 30));  
                var overlay = new BMap.Marker(point, {icon: startIcon});  
                // 将覆盖物添加到地图中,一个覆盖物实例只能向地图中添加一次  
                this.map.addOverlay(overlay);  
                // 将地图中心 移动至定位点  
                this.map.panTo(point);  

                this.getLocationMyAddress(point);  
            },  
            // 将地图中心点移动某个经纬度上 经度longitude 纬度latitude  
            moveMapCentre(val) {  
                let point = this.getPoint(val.longitude, val.latitude);  
                // 将地图中心 移动至定位点  
                this.map.panTo(point);  
            },  
            // 调用浏览器H5定位接口进行定位 (优先调用浏览器H5定位接口,如果失败会调用IP定位, IP定位:根据用户IP 返回城市级别的定位结果)  
            getH5LocationPosition() {  
                /* 关于状态码  
                * BMAP_STATUS_SUCCESS   检索成功。对应数值“0”  
                * BMAP_STATUS_CITY_LIST 城市列表。对应数值“1”  
                * BMAP_STATUS_UNKNOWN_LOCATION  位置结果未知。对应数值“2”  
                * BMAP_STATUS_UNKNOWN_ROUTE 导航结果未知。对应数值“3”  
                * BMAP_STATUS_INVALID_KEY   非法密钥。对应数值“4”  
                * BMAP_STATUS_INVALID_REQUEST   非法请求。对应数值“5”  
                * BMAP_STATUS_PERMISSION_DENIED 没有权限。对应数值“6”。(自 1.1 新增)  
                * BMAP_STATUS_SERVICE_UNAVAILABLE   服务不可用。对应数值“7”。(自 1.1 新增)  
                * BMAP_STATUS_TIMEOUT   超时。对应数值“8”。(自 1.1 新增)  
                */  
                const than = this;  
                var geolocation = new BMap.Geolocation();  
                geolocation.getCurrentPosition((r) => {  
                    if(geolocation.getStatus() === BMAP_STATUS_SUCCESS){  
                        than.addMyLocationIcon({longitude: r.point.lng, latitude: r.point.lat});  
                        than.$ownerInstance.callMethod("sendLocation", {longitude: r.point.lng, latitude: r.point.lat});  
                    } else {  
                        switch(geolocation.getStatus()) {  
                            case 2:  
                                alert('位置结果未知,获取位置失败。');  
                            break;  
                            case 3:  
                                alert('导航结果未知,获取位置失败。');  
                            break;  
                            case 4:  
                                alert('非法密钥获取位置失败。');  
                            break;  
                            case 5:  
                                alert('对不起,非法请求位置,获取位置失败。');  
                            break;  
                            case 6:  
                                alert('对不起,当前没有权限,获取位置失败。');  
                            break;  
                            case 7:  
                                alert('对不起,服务不可用,获取位置失败。');  
                            break;  
                            case 8:  
                                alert('对不起,请求超时,获取位置失败。');  
                            break;  
                            default:  
                                alert('定位发生未知错误,请重试!');  
                            break;  
                        }    
                    }  
                },{ enableHighAccuracy: true }); // 指示浏览器获取高精度的位置,默认false   
            },  
            // 添加百度地图拖拽完成事件 回调  
            addBaiduEventListener() {  
                const than = this;  
                this.map.addEventListener('dragend', function (e) {  
                    const center = than.map.getCenter();  
                    than.getLocationAddress(center);  
                });  
            },  
            // 获取经纬度坐标 对应的地址信息  
            getLocationAddress(center) {  
                const than = this;  
                const geoc = new BMap.Geocoder();  
                geoc.getLocation(center, function(rs) {  
                    if (rs !== undefined && rs !== null) {  
                        var arrayAddressList = [];  
                        arrayAddressList.push({  
                            title: rs.business,  
                            address: rs.address,  
                            point: rs.point,  
                            checked: true,  
                        });  
                        for (let item of rs.surroundingPois) {  
                            arrayAddressList.push({  
                                title: item.title,  
                                address: item.address,  
                                point: item.point,  
                                checked: false  
                            });  
                        }  
                        than.$ownerInstance.callMethod("updateAddressList", arrayAddressList);  
                    }  
                });  
            },  
            // 获取自身经纬度坐标 对应的地址信息  
            getLocationMyAddress(center) {  
                const than = this;  
                const geoc = new BMap.Geocoder();  
                geoc.getLocation(center, function(rs) {  
                    if (rs !== undefined && rs !== null) {  
                        var dicAddress = {  
                            address: rs.address,  
                            point: rs.point,  
                        };  
                        than.$ownerInstance.callMethod("updateMyAddress", dicAddress);  
                    }  
                });  
            },  
            // 根据搜索关键字获取地址列表信息  
            getSearchKeyAddreeList(sSearchKey) {  
                const than = this;  
                const locationController = new BMap.LocalSearch(this.map, {  
                    onSearchComplete: function(results) {  
                        var arrayAddressList = [];  
                        var data = [];  
                        for (var i = 0; i < results.getCurrentNumPois(); i ++) {  
                            data.push(results.getPoi(i));  
                        }  
                        var i = 0;  
                        for (let item of data) {  
                            arrayAddressList.push({  
                                title: item.title,  
                                address: item.address,  
                                point: item.point,  
                                checked: i === 0 ? true : false,  
                            });  
                            i = i + 1;  
                        }  
                        than.$ownerInstance.callMethod("updateAddressList", arrayAddressList);  
                        if (arrayAddressList.length > 0) {  
                            than.moveMapCentre({longitude: arrayAddressList[0].point.lng, latitude: arrayAddressList[0].point.lat})  
                        }  
                    },  
                });  
                locationController.setPageCapacity(10);  
                locationController.search(sSearchKey);  
            }  
        },  
    }  
</script>  
<!-- #endif -->  

<style scoped>  
    .baidu_map {  
        width: 100%;  
        height: 100%;  
    }  

    .baidu_map .myMap {  
        width: 100%;  
        height: 100%;  
    }  

    .baidu_map .location {  
        position: absolute;  
        width: 40px;  
        height: 40px;  
        top: 10px;  
        right: 10px;  
        display: flex;  
        justify-content: center;  
        align-items: center;  
    }  

    .baidu_map .marking {  
        background-image: url('/static/location_dw.png');  
        position: absolute;  
        width: 40px;  
        height: 40px;  
        left: 50%;  
        right: 50%;  
        transform: translate(-50%, -40px);  
        top: 50%;  
        /* display: flex;  
        justify-content: center;  
        align-items: center; */  
    }  

    .baidu_map .address_list {  
        width: 100%;  
        height: 50%;  
        overflow: hidden;  
    }  

    .baidu_map .address_list .search {  
        padding: 10px;  
        background-color: #ffffff;  
    }  

    .baidu_map .address_list .list {  
        width: 100%;  
        height: calc(100% - 104px);  
        overflow: scroll;  
    }  

    .baidu_map .address_list .footer {  
        height: 40px;  
        background-color: #f7f7fa;  
        padding: 5px;  
    }  

    .location_cell {  
        display: flex;  
        padding: 5px 10px 0px 10px;  
        overflow: hidden;  
    }  

    .location_cell .centent {  
        flex: 1;  
        overflow: hidden;  
    }  

    .location_cell .right {  
        width: 40px;  
        display: flex;  
        justify-content: center;  
        align-items: center;  
    }  

    .location_cell .centent .title {  
        height: 24px;  
        font-size: 16px;  
        font-weight: 500;  
        line-height: 24px;  
        margin-bottom: 3px;  
        overflow:hidden;  
        white-space:nowrap;  
        text-overflow:ellipsis;  
    }  

    .location_cell .centent .sub_title {  
        font-size: 14px;  
        color: #222222;  
        text-overflow: ellipsis;  
        overflow: hidden;  
        margin-bottom: 5px;  
    }  
</style>

3、在业务页面使用它
代码如下

<template>  
    <view>  
        <u-navbar  
            title="坐标获取"  
            :border="true"  
            :placeholder="true"  
            :pagingEnabled="false"  
            :autoBack="true">  
        </u-navbar>  

        <!-- showSearch 控制显示模式,true为带搜索地址功能,false为纯地图 -->  
        <view :style="sStyle">  
            <xn-map   
                showSearch  
                @clickSave="clickSave"  
                @sendLocation="getLocation"  
            />  
        </view>  
    </view>  
</template>  

<script >      
    export default {  
        data() {  
            return {  
                sStyle: "",  
            }  
        },  
        onLoad: function(option) {},  
        onReady() {  
            let res = uni.getSystemInfoSync();  
            let iHeight = res.windowHeight - (44.5 + res.statusBarHeight + res.safeAreaInsets.bottom);  
            this.sStyle = 'width: 100vw; height: ' + iHeight + 'px;';  
        },  
        methods: {  
            clickSave(val) {  
                console.log('选定了地址');  
                console.log(val);  
            },  
            getLocation(val) {  
                console.log('获取到定位信息');  
                console.log(val);  
            }  
        },  
    }  
</script>  

<style scoped>  

</style>

上面的百度地图有2种模式,一种就是纯地图展示(含定位功能,定位功能需要自己去将项目中的manifest.json,进行配置百度地图的ak),不展示启用搜索选地址功能。
一种就是地图加地址检索和选地址功能。

代码里面都有注释,详细的去看,保证你会了解。

继续阅读 »

最新在开发公司用的百度地图实现,发现社区这方向资料参差不齐,很多都有残缺。下面看我的例子来一起做个百度地图的组件吧(仅限在H5、APP、微信小程序中使用)

在微信小程序中用了取巧的方式,使用uni自带map组件(在微信小程序中,map组件为腾讯地图),同时在百度后台不仅仅需要创建一个Web、iOS、Android的AK,同时还需要再创建一个服务器的AK,主要是用在微信小程序中,用来将地图获取的经纬度进行转换、逆地理编码、POI查询等功能。

在微信管理后台需要开启定位权限,具体看uni.getLocation的要求。

下方代码中会指出哪里使用百度地图Web AK,哪里使用百度地图服务器 AK。请详细查看下方代码注释。

1、创建一个定位公共类
起名:location.js
代码如下:

// 获取位置信息  
const getLocation = () => {  
    return new Promise((resolve, reject) => {  
        uni.showLoading({title: '获取位置中'});  
        // #ifdef APP-PLUS || H5  
        uni.getLocation({  
            /*  
            * 目前国内主要有以下三种坐标系:  
            * WGS84:为一种大地坐标系,也是目前广泛使用的GPS全球卫星定位系统使用的坐标系。  
            * GCJ02:又称火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系。  
            * BD09:为百度坐标系,在GCJ02坐标系基础上再次加密。其中bd09ll表示百度经纬度坐标,bd09mc表示百度墨卡托米制坐标。  
            *   
            * 如果使用的定位为手机自带定位,那么type 使用 GCJ02。  
            * 如果是使用百度地图定位,那么type 使用 BD09。(需要在项目中的manifest.json文件配置 百度地图定位的AK)  
            * 如果是使用高德地图、腾讯地图定位,那么type 使用 GCJ02。(需要在项目中的manifest.json文件配置 高德地图定位的AK)  
            * 如果是使用谷歌地图定位,那么type 使用 WGS84。(需要在项目中的manifest.json文件配置 谷歌地图定位的AK)  
            */  
            type: 'BD09',  
            geocode: true,  
            success: function(data) {  
                resolve(data)  
            },  
            fail: function(error) {  
                reject(error)  
            },  
            complete: function() {  
                uni.hideLoading();  
            }  
        })  
        // #endif  

        // #ifdef MP-WEIXIN  
        uni.getLocation({  
            /*  
            * 目前国内主要有以下三种坐标系:  
            * WGS84:为一种大地坐标系,也是目前广泛使用的GPS全球卫星定位系统使用的坐标系。  
            * GCJ02:又称火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系。  
            * BD09:为百度坐标系,在GCJ02坐标系基础上再次加密。其中bd09ll表示百度经纬度坐标,bd09mc表示百度墨卡托米制坐标。  
            *   
            * 如果使用的定位为手机自带定位,那么type 使用 GCJ02。  
            * 如果是使用百度地图定位,那么type 使用 BD09。(需要在项目中的manifest.json文件配置 百度地图定位的AK)  
            * 如果是使用高德地图、腾讯地图定位,那么type 使用 GCJ02。(需要在项目中的manifest.json文件配置 高德地图定位的AK)  
            * 如果是使用谷歌地图定位,那么type 使用 WGS84。(需要在项目中的manifest.json文件配置 谷歌地图定位的AK)  
            */  
            type: 'GCJ02',  
            geocode: true,  
            success: function(data) {  
                resolve(data)  
            },  
            fail: function(error) {  
                reject(error)  
            },  
            complete: function() {  
                uni.hideLoading();  
            }  
        })  
        // #endif  
    })  
};  

/**  
 * 检测是否开启定位  
*/  
const checkOpenGPSServiceByAndroidIOS = () => {  
    let system = uni.getSystemInfoSync(); // 获取系统信息  
    if (system.platform === 'android') { // 判断平台  
        var context = plus.android.importClass("android.content.Context");  
        var locationManager = plus.android.importClass("android.location.LocationManager");  
        var main = plus.android.runtimeMainActivity();  
        var mainSvr = main.getSystemService(context.LOCATION_SERVICE);  
        if (!mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER)) {  
            uni.showModal({  
                title: '温馨提示',  
                content: '您未开启定位服务,请打开定位服务功能,以便获取您的位置!',  
                showCancel: true,  
                success() {  
                    var Intent = plus.android.importClass('android.content.Intent');  
                    var Settings = plus.android.importClass('android.provider.Settings');  
                    var intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);  
                    main.startActivity(intent); // 打开系统设置GPS服务页面  
                }  
            });  
            return {s: 'android', b: false};  
        } else {  
            return {s: 'android', b: true};  
        }  
    } else if (system.platform === 'ios') {  
        var cllocationManger = plus.ios.import("CLLocationManager");  
        var enable = cllocationManger.locationServicesEnabled();  
        var status = cllocationManger.authorizationStatus();  
        plus.ios.deleteObject(cllocationManger);  
        if (enable && status != 2) {  
            // 手机系统的定位已经打开  
            return {s: 'ios', b: true};  
        } else {  
            // 手机系统的定位没有打开  
            uni.showModal({  
                title: '提示',  
                content: '请前往设置-隐私-定位服务打开定位服务功能',  
                showCancel: true,  
                success() {  
                    var UIApplication = plus.ios.import("UIApplication");  
                    var application2 = UIApplication.sharedApplication();  
                    var NSURL2 = plus.ios.import("NSURL");  
                    var setting2 = NSURL2.URLWithString("app-settings:"); // UIApplicationOpenSettingsURLString  
                    application2.openURL(setting2);  
                    plus.ios.deleteObject(setting2);  
                    plus.ios.deleteObject(NSURL2);  
                    plus.ios.deleteObject(application2);  
                }  
            });  
            return {s: 'ios', b: false};  
        }  
    } else {  
        return {s: 'h5', b: false};  
    }  
}  

const getLocationChange = () => {  
    return new Promise((resolve, reject) => {  
        uni.startLocationUpdate({  
            success: function() {  
                uni.onLocationChange(function (res) {  
                    // 纬度:res.latitude 经度:res.longitude  
                    resolve(res)  
                });  

                uni.onLocationChangeError(function (error) {  
                    reject(error);  
                });  
            },  
            fail: function(error) {  
                reject(error);  
            },  
            complete: function() {  
                // 调用开启小程序接收位置消息 API 完成  
            }  
        });  
    })  
}  

const stopLocationUpdate = () => {  
    uni.stopLocationUpdate(function () {  
        // 关闭监听实时位置变化,前后台都停止消息接收。  
    })  
}  

export default {  
    getLocation: getLocation,  
    getLocationChange: getLocationChange,  
    stopLocationUpdate: stopLocationUpdate,  
    checkOpenGPSServiceByAndroidIOS: checkOpenGPSServiceByAndroidIOS,  
}

2、创建一个名字叫 xn-map.vue 的组件 (不知道uniapp 如何创建组件,自行百度一下)
创建完后,记得进行引入:
import xnMap from '@/components/xn-map/xn-map.vue';
Vue.component('xn-map', xnMap);

以下是 xn-map.vue 代码:
代码中的ak记得换成自己的百度地图web ak 和 百度地图服务器ak

<template>  
    <view class="baidu_map">  
        <view :style="!showSearch ? 'width: 100%; height: 100%; position: relative;' : 'width: 100%; height: 50%; position: relative;'">  
            <!-- #ifdef APP-PLUS || H5 -->  
            <view  
                id="myMap"  
                class="myMap"  
                :prop="dicValue"  
                :change:prop="bmap.changeValue"  
            >  
            </view>  
            <!-- #endif -->  
            <!-- #ifdef MP-WEIXIN -->  
            <map  
                class="myMap"  
                id="myMap"  
                scale="17"  
                enable-rotate="false"  
                show-location="true"  
                :latitude="maplatitude"  
                :longitude="mpalongitude"  
                @regionchange="regionchange"  
            ></map>  
            <!-- #endif -->  
            <view class="location" @click="clickLocation">  
                <u--image :src="sLocationIcon" width="28px" height="28px"></u--image>  
            </view>  
            <view v-if="showSearch" class="marking"></view>  
        </view>  

        <view v-if="showSearch" class="address_list">  
            <view class="search">  
                <u-search :clearabled="true" v-model="sQueryValue" @search="searchQueryValue" @custom="searchQueryValue"></u-search>  
            </view>  
            <view class="list">  
                <view v-for="(item, index) in arrayAddress" :key="index" @click="clickAddressCell(item, index)">  
                    <view class="location_cell">  
                        <view class="centent">  
                            <view class="title">  
                                {{ isEmptyString(item.title) ? '--' : item.title }}  
                            </view>  
                            <view class="sub_title">  
                                {{ item.address }}  
                            </view>  
                        </view>  
                        <view class="right" v-if="item.checked">  
                            <u-icon name="checkbox-mark" color="#2979ff" size="18"></u-icon>  
                        </view>  
                    </view>  
                    <u-line></u-line>  
                </view>  
            </view>  
            <view class="footer" :style="sSearchStyle">  
                <u-button type="primary" text="保存" @click="clickFootButton"></u-button>  
            </view>  
        </view>  
    </view>  
</template>  

<script>  
    import location from '@/tool/location.js';  

    export default {  
        name:"crm-map",  
        props: {  
            showSearch: {  
                type: Boolean,  
                default() {  
                    return false;  
                }  
            },  
        },  
        data() {  
            return {  
                sSearchStyle: "",  

                dicValue: null,  
                sLocationIcon: "/static/map_location.png",  
                sQueryValue: "",  
                arrayAddress: [],  
                iLastIndex: 0,  

                // #ifdef MP-WEIXIN  
                mapObjs: null,  
                needSearch: true,  
                city: "深圳市",  
                maplatitude: 22.685393,  
                maplongitude: 113.798274,  
                // #endif  
            };  
        },  
        mounted() {  
            let res = this.getSystemInfo();  
            this.sSearchStyle = 'margin-bottom: ' + res.safeAreaInsets.bottom + 'px;';  
            // #ifdef APP-PLUS || H5  
            this.dicValue = { type: 'needOtherAddress', needSerach: this.showSearch, guid: uni.$u.guid(), };  
            // #endif  

            // #ifdef MP-WEIXIN  
            if (this.mapObjs === undefined || this.mapObjs === null) {  
                this.mapObjs = uni.createMapContext('myMap', this); // 得到map实例对象  
            }  
            this.clickLocation();  
            // #endif  
        },  
        methods: {  
            /*  
            * 在改变 dicValue 时,或许你会有疑惑,为啥字典中都要带一个自动生成 32 位的guid  
            * 这是因为,如果没有带这个自动生成的guid,在renderjs中,当改变的 dicValue 是同一个值,监听函数是不会被调用的(H5会被调用,APP不会)  
            * 所以在字典中添加一个 自动生成的 guid,有助于renderjs中监听函数changeValue的生效。  
            */  
            // #ifdef APP-PLUS || H5  
            // 显示自身定位位置 回调   
            showMarking() {  
                if (this.showSearch) {  
                    this.dicValue = { type: 'addEventListener', guid: uni.$u.guid(), }  
                }  
                setTimeout(() => {  
                    this.clickLocation();  
                }, 200)  
            },  
            // #endif  

            // #ifdef MP-WEIXIN  
            regionchange(val) {  
                // 在安卓中是 end 事件  
                if (val.type === 'end' && this.needSearch) {  
                    this.getCenterLatLong();  
                    return;  
                }  

                // 在ios中是 regionchange  
                if (val.type ==='regionchange' && this.needSearch) {  
                    this.getCenterLatLong();  
                    return;  
                }  
            },  
            // 获取中心点位置  
            getCenterLatLong() {  
                if (this.mapObjs === undefined || this.mapObjs === null) {  
                    this.mapObjs = uni.createMapContext('myMap', this); // 得到map实例对象  
                }  
                const than = this;  
                this.mapObjs.getCenterLocation({  
                    success: res => {  
                        if (res.errMsg === 'getMapCenterLocation:ok') {  
                            than.circularRegionRetrieval(res.latitude, res.longitude);  
                        } else {  
                            uni.showToast({ icon: 'none', title: res.errMsg || '获取位置出错', duration: 3000 });  
                        }  
                    },  
                    fail: res => {  
                        uni.showToast({ icon: 'none', title: res.errMsg || '获取位置出错', duration: 3000 });  
                    },  
                    complete: res => {}  
                })  
            },  
            // 设置地图中心点  
            setMapCenter(latitude, longitude) {  
                if (this.mapObjs === undefined || this.mapObjs === null) {  
                    this.mapObjs = uni.createMapContext('myMap', this); // 得到map实例对象  
                }  
                this.needSearch = false;  
                const than = this;  
                this.mapObjs.moveToLocation({  
                    longitude: longitude,  
                    latitude: latitude,  
                    success: res => {},  
                    fail: res => {},  
                    complete: res => {   
                        setTimeout(() => {  
                            than.needSearch = true;  
                        }, 500)  
                    }  
                })  
            },  
            // 坐标转换  
            locationConversion(latitude, longitude, sendLocation, sendAddress, dicAddress) {  
                let sUrl = 'https://api.map.baidu.com/geoconv/v2/';  
                let params = {  
                    ak: '百度地图服务器ak',  
                    output: 'json',  
                    model: '1',  
                    coords: `${longitude},${latitude}`  
                }  
                uni.$u.http.get(sUrl, {params: params}).then(res => {  
                    uni.hideLoading();  
                    if (res.status === 0) {  
                        if (sendLocation) {  
                            this.sendLocation({longitude: res.result[0].x, latitude: res.result[0].y})  
                        }  

                        if (sendAddress) {  
                            let dicData = {  
                                title: dicAddress.title,  
                                address: dicAddress.address,  
                                point: {lat: res.result[0].y, lng: res.result[0].x},  
                                checked: true  
                            }  
                            this.$emit("clickSave", dicData);  
                        }  
                    } else {  
                        uni.showToast({ icon: 'none', title: res.message || '获取位置信息出错', duration: 3000 });  
                    }  
                }).catch(err => {  
                    uni.hideLoading();  
                    uni.showToast({ icon: 'none', title: err.errMsg || '获取位置信息出错', duration: 3000 });  
                })  
            },  
            // 获取当前自身坐标点 地理信息  
            getMyAddress(latitude, longitude, type) {  
                let sUrl = 'https://api.map.baidu.com/reverse_geocoding/v3/';  
                let params = {  
                    ak: '百度地图服务器ak',  
                    output: 'json',  
                    coordtype: type,  
                    location: `${latitude},${longitude}`  
                }  
                uni.$u.http.get(sUrl, {params: params}).then(res => {  
                    if (res.status === 0) {  
                        this.city = res.result.addressComponent.city;  
                        this.updateMyAddress({  
                            address: res.result.formatted_address,  
                            txPoint: res.result.location,  
                        })  
                    } else {  
                        uni.showToast({ icon: 'none', title: res.message || '获取位置信息出错', duration: 3000 });  
                    }  
                }).catch(err => {  
                    uni.showToast({ icon: 'none', title: err.errMsg || '获取位置信息出错', duration: 3000 });  
                })  
            },  
            // 圆形区域检索  
            circularRegionRetrieval(latitude, longitude) {  
                let sUrl = 'https://api.map.baidu.com/place/v2/search';  
                let params = {  
                    ak: '百度地图服务器ak',  
                    output: 'json',  
                    query: '公司企业$房地产$美食$酒店$购物$生活服务$休闲娱乐$医疗$交通设施$政府机构',  
                    coord_type: 2,  
                    ret_coordtype: 'gcj02ll',  
                    location: `${latitude},${longitude}`,  
                    radius: 1000,  
                }  
                uni.$u.http.get(sUrl, {params: params}).then(res => {  
                    if (res.status === 0) {  
                        let arrayAddressList = [];  
                        for (let item of res.results) {  
                            arrayAddressList.push({  
                                title: item.name,  
                                address: item.address,  
                                point: item.location,  
                                checked: false,  
                            });  
                        }  
                        this.updateAddressList(arrayAddressList);  
                    } else {  
                        uni.showToast({ icon: 'none', title: res.message || '获取位置信息出错', duration: 3000 });  
                    }  
                }).catch(err => {  
                    uni.showToast({ icon: 'none', title: err.errMsg || '获取位置信息出错', duration: 3000 });  
                })  
            },  
            // 地点输入检索  
            locationInputRetrieval() {  
                let sUrl = 'https://api.map.baidu.com/place/v2/suggestion';  
                let params = {  
                    ak: '百度地图服务器ak',  
                    output: 'json',  
                    ret_coordtype: 'gcj02ll',  
                    query: this.sQueryValue,  
                    region: this.city,  
                }  
                uni.$u.http.get(sUrl, {params: params}).then(res => {  
                    if (res.status === 0) {  
                        let arrayAddressList = [];  
                        let i = 0;  
                        for (let item of res.result) {  
                            arrayAddressList.push({  
                                title: item.name,  
                                address: item.address,  
                                point: item.location,  
                                checked: i === 0 ? true: false  
                            });  
                            i = i + 1;  
                        }  
                        this.updateAddressList(arrayAddressList);  
                    } else {  
                        uni.showToast({ icon: 'none', title: res.message || '获取位置信息出错', duration: 3000 });  
                    }  
                }).catch(err => {  
                    uni.showToast({ icon: 'none', title: err.errMsg || '获取位置信息出错', duration: 3000 });  
                })  
            },  
            // #endif  

            // 更新地址列表  
            updateAddressList(val) {  
                this.iLastIndex = 0;  
                this.arrayAddress = val;  
            },  
            // 向父页面发送当前坐标经纬度  
            sendLocation(val) {  
                this.$emit("sendLocation", val);  
            },  
            // 向父页面发送当前坐标地址信息  
            updateMyAddress(val) {  
                this.$emit("sendMyAddress", val);  
            },  
            // 获取自身当前经纬度  
            clickLocation() {  
                // #ifdef APP-PLUS  
                let dicInfo = location.checkOpenGPSServiceByAndroidIOS();  
                if (dicInfo.s !== 'h5') {  
                    if (dicInfo.b) {  
                        location.getLocation().then((resp) => {  
                            this.dicValue = {  
                                type: 'addMyLocationIcon',  
                                longitude: resp.longitude,  
                                latitude: resp.latitude,  
                                guid: uni.$u.guid(),  
                            }  
                            this.sendLocation({longitude: resp.longitude, latitude: resp.latitude});  
                        }).catch((error) => {  
                            uni.showToast({ icon: 'none', title: error.errMsg || '获取位置出错', duration: 3000 });  
                        });  
                    }  
                } else {  
                    this.dicValue = { type: 'getH5LocationPosition', guid: uni.$u.guid(), }  
                }  
                // #endif  

                // #ifdef H5  
                this.dicValue = { type: 'getH5LocationPosition', guid: uni.$u.guid(), }  
                // #endif  

                // #ifdef MP-WEIXIN  
                location.getLocation().then((resp) => {  
                    if (this.mapObjs === undefined || this.mapObjs === null) {  
                        this.mapObjs = uni.createMapContext('myMap', this); // 得到map实例对象  
                    }  
                    this.maplatitude = resp.latitude;  
                    this.maplongitude = resp.longitude;  
                    this.needSearch = false;  
                    const than = this;  
                    this.mapObjs.moveToLocation({  
                        longitude: resp.longitude,  
                        latitude: resp.latitude,  
                        success: res => {  
                            than.getMyAddress(resp.latitude, resp.longitude, 'gcj02ll');  
                            than.circularRegionRetrieval(resp.latitude, resp.longitude);  
                        },  
                        fail: res => {  
                            uni.showToast({ icon: 'none', title: res.errMsg || '获取位置出错', duration: 3000 });  
                        },  
                        complete: res => {  
                            setTimeout(() => {  
                                than.needSearch = true;  
                            }, 500)  
                        }  
                    })  
                    this.locationConversion(resp.latitude, resp.longitude, true, false, null);  
                }).catch((error) => {  
                    uni.showToast({ icon: 'none', title: error.errMsg || '获取位置出错111', duration: 3000 });  
                });  
                // #endif  
            },  
            // 搜索按钮点击事件  
            searchQueryValue() {  
                uni.hideKeyboard();  
                // #ifdef APP-PLUS || H5  
                this.dicValue = { type: 'searchQueryValue', sSearchValue: this.sQueryValue, guid: uni.$u.guid(), };  
                // #endif  

                // #ifdef MP-WEIXIN  
                this.locationInputRetrieval();  
                // #endif  
            },  
            // 地址信息Cell 点击事件  
            clickAddressCell(item, index) {  
                if (this.arrayAddress[index].checked) {  
                    this.iLastIndex = index;  
                    return;  
                }  
                if (this.iLastIndex === index) {  
                    this.arrayAddress[this.iLastIndex].checked = !this.arrayAddress[this.iLastIndex].checked;  
                } else {  
                    this.arrayAddress[this.iLastIndex].checked = !this.arrayAddress[this.iLastIndex].checked;  
                    this.arrayAddress[index].checked = !this.arrayAddress[index].checked;  
                }  

                this.iLastIndex = index;  
                // #ifdef APP-PLUS || H5  
                this.dicValue = { type: 'moveMapCentre', point: {longitude: item.point.lng, latitude: item.point.lat}, guid: uni.$u.guid(), };  
                // #endif  
                // #ifdef MP-WEIXIN  
                this.setMapCenter(item.point.lat, item.point.lng);  
                // #endif  
            },  
            // 保存按钮 点击事件  
            clickFootButton() {  
                if (this.arrayAddress.length === 0) {  
                    uni.showToast({ icon: 'none', title: '请选择您要保存的地址', duration: 3000 });  
                    return;  
                }  
                // #ifdef APP-PLUS || H5  
                this.$emit("clickSave", this.arrayAddress[this.iLastIndex]);  
                // #endif  

                // #ifdef MP-WEIXIN  
                uni.showLoading({title:"加载中...", mask: true})  
                this.locationConversion(this.arrayAddress[this.iLastIndex].point.lat, this.arrayAddress[this.iLastIndex].point.lng, false, true, this.arrayAddress[this.iLastIndex]);  
                // #endif  
            },  
        }  
    }  
</script>  

<!-- #ifdef APP-PLUS || H5 -->   
<script module="bmap" lang="renderjs">  
    export default {  
        data() {  
            return {  
                map: null,  
                locationImg: '/static/location.png', // require("/static/location.png"),  
                innerValue: null,  
                needOtherAddress: false,  
            };  
        },  
        mounted(){  
            // 初始化百度地图  
            this.initBaiDuMap();  
        },  
        methods: {  
            // 动态创建Script标签  
            createScript(url) {  
                return new Promise((resolve,reject) => {  
                    var script = document.createElement('script');  
                    script.type = 'text/javascript';  
                    script.src = url;  
                    script.onload = () => { resolve() };  
                    script.onerror = () => { reject() };  
                    document.head.appendChild(script);  
                })  
            },  
            initBaiDuMap() {  
                const ak = '百度地图web ak';  
                if (typeof window.BMap === 'function') {  
                    this.initMap();  
                } else {  
                    window.init = () => this.initMap();  
                    this.createScript(`https://api.map.baidu.com/api?v=3.0ak=${ak}&callback=init`)  
                }  
            },  
            async initMap() {  
                // myMap 要渲染地图的view的id  
                this.map = new BMap.Map("myMap");  
                this.map.centerAndZoom(this.getPoint(113.804346, 22.691386), 17); // 设置中心点  
                var scaleCtrl = new BMap.ScaleControl(); // 添加比例尺控件  
                this.map.addControl(scaleCtrl);  
                this.$ownerInstance.callMethod("showMarking");  
            },  
            // 转换地图坐标点 经度longitude 纬度latitude  
            getPoint(longitude, latitude) {  
                return new BMap.Point(longitude, latitude);  
            },  
            // 接收Value改变时的数据,用来操作百度地图的API  
            changeValue(val) {  
                if (val === null || val === undefined) {  
                    return  
                }  
                this.innerValue = val;  
                if (this.innerValue.type === 'addMyLocationIcon') {  
                    this.addMyLocationIcon(this.innerValue);  
                } else if (this.innerValue.type === 'getH5LocationPosition') {  
                    this.getH5LocationPosition();  
                } else if (this.innerValue.type === 'addEventListener') {  
                    this.addBaiduEventListener();  
                } else if (this.innerValue.type === 'searchQueryValue') {  
                    this.getSearchKeyAddreeList(this.innerValue.sSearchValue);  
                } else if (this.innerValue.type === 'moveMapCentre') {  
                    this.moveMapCentre(this.innerValue.point);  
                } else if (this.innerValue.type === 'needOtherAddress') {  
                    this.needOtherAddress = this.innerValue.needSerach;  
                } else {  

                }  
                this.innerValue = null;  
            },  
            // 添加自身定位坐标点 并且将地图中心点移动至定位坐标点 经度longitude 纬度latitude  
            addMyLocationIcon(val) {  
                // 清除地图上所有覆盖物  
                this.map.clearOverlays();  

                let point = this.getPoint(val.longitude, val.latitude);  

                // 构造函数: 以给定的图像地址和大小创建图标对象实例  
                var startIcon = new BMap.Icon(  
                    this.locationImg,  
                    new BMap.Size(30, 30)  
                );  
                // 设置图标的大小  
                startIcon.setImageSize(new BMap.Size(30, 30));  
                var overlay = new BMap.Marker(point, {icon: startIcon});  
                // 将覆盖物添加到地图中,一个覆盖物实例只能向地图中添加一次  
                this.map.addOverlay(overlay);  
                // 将地图中心 移动至定位点  
                this.map.panTo(point);  

                this.getLocationMyAddress(point);  
            },  
            // 将地图中心点移动某个经纬度上 经度longitude 纬度latitude  
            moveMapCentre(val) {  
                let point = this.getPoint(val.longitude, val.latitude);  
                // 将地图中心 移动至定位点  
                this.map.panTo(point);  
            },  
            // 调用浏览器H5定位接口进行定位 (优先调用浏览器H5定位接口,如果失败会调用IP定位, IP定位:根据用户IP 返回城市级别的定位结果)  
            getH5LocationPosition() {  
                /* 关于状态码  
                * BMAP_STATUS_SUCCESS   检索成功。对应数值“0”  
                * BMAP_STATUS_CITY_LIST 城市列表。对应数值“1”  
                * BMAP_STATUS_UNKNOWN_LOCATION  位置结果未知。对应数值“2”  
                * BMAP_STATUS_UNKNOWN_ROUTE 导航结果未知。对应数值“3”  
                * BMAP_STATUS_INVALID_KEY   非法密钥。对应数值“4”  
                * BMAP_STATUS_INVALID_REQUEST   非法请求。对应数值“5”  
                * BMAP_STATUS_PERMISSION_DENIED 没有权限。对应数值“6”。(自 1.1 新增)  
                * BMAP_STATUS_SERVICE_UNAVAILABLE   服务不可用。对应数值“7”。(自 1.1 新增)  
                * BMAP_STATUS_TIMEOUT   超时。对应数值“8”。(自 1.1 新增)  
                */  
                const than = this;  
                var geolocation = new BMap.Geolocation();  
                geolocation.getCurrentPosition((r) => {  
                    if(geolocation.getStatus() === BMAP_STATUS_SUCCESS){  
                        than.addMyLocationIcon({longitude: r.point.lng, latitude: r.point.lat});  
                        than.$ownerInstance.callMethod("sendLocation", {longitude: r.point.lng, latitude: r.point.lat});  
                    } else {  
                        switch(geolocation.getStatus()) {  
                            case 2:  
                                alert('位置结果未知,获取位置失败。');  
                            break;  
                            case 3:  
                                alert('导航结果未知,获取位置失败。');  
                            break;  
                            case 4:  
                                alert('非法密钥获取位置失败。');  
                            break;  
                            case 5:  
                                alert('对不起,非法请求位置,获取位置失败。');  
                            break;  
                            case 6:  
                                alert('对不起,当前没有权限,获取位置失败。');  
                            break;  
                            case 7:  
                                alert('对不起,服务不可用,获取位置失败。');  
                            break;  
                            case 8:  
                                alert('对不起,请求超时,获取位置失败。');  
                            break;  
                            default:  
                                alert('定位发生未知错误,请重试!');  
                            break;  
                        }    
                    }  
                },{ enableHighAccuracy: true }); // 指示浏览器获取高精度的位置,默认false   
            },  
            // 添加百度地图拖拽完成事件 回调  
            addBaiduEventListener() {  
                const than = this;  
                this.map.addEventListener('dragend', function (e) {  
                    const center = than.map.getCenter();  
                    than.getLocationAddress(center);  
                });  
            },  
            // 获取经纬度坐标 对应的地址信息  
            getLocationAddress(center) {  
                const than = this;  
                const geoc = new BMap.Geocoder();  
                geoc.getLocation(center, function(rs) {  
                    if (rs !== undefined && rs !== null) {  
                        var arrayAddressList = [];  
                        arrayAddressList.push({  
                            title: rs.business,  
                            address: rs.address,  
                            point: rs.point,  
                            checked: true,  
                        });  
                        for (let item of rs.surroundingPois) {  
                            arrayAddressList.push({  
                                title: item.title,  
                                address: item.address,  
                                point: item.point,  
                                checked: false  
                            });  
                        }  
                        than.$ownerInstance.callMethod("updateAddressList", arrayAddressList);  
                    }  
                });  
            },  
            // 获取自身经纬度坐标 对应的地址信息  
            getLocationMyAddress(center) {  
                const than = this;  
                const geoc = new BMap.Geocoder();  
                geoc.getLocation(center, function(rs) {  
                    if (rs !== undefined && rs !== null) {  
                        var dicAddress = {  
                            address: rs.address,  
                            point: rs.point,  
                        };  
                        than.$ownerInstance.callMethod("updateMyAddress", dicAddress);  
                    }  
                });  
            },  
            // 根据搜索关键字获取地址列表信息  
            getSearchKeyAddreeList(sSearchKey) {  
                const than = this;  
                const locationController = new BMap.LocalSearch(this.map, {  
                    onSearchComplete: function(results) {  
                        var arrayAddressList = [];  
                        var data = [];  
                        for (var i = 0; i < results.getCurrentNumPois(); i ++) {  
                            data.push(results.getPoi(i));  
                        }  
                        var i = 0;  
                        for (let item of data) {  
                            arrayAddressList.push({  
                                title: item.title,  
                                address: item.address,  
                                point: item.point,  
                                checked: i === 0 ? true : false,  
                            });  
                            i = i + 1;  
                        }  
                        than.$ownerInstance.callMethod("updateAddressList", arrayAddressList);  
                        if (arrayAddressList.length > 0) {  
                            than.moveMapCentre({longitude: arrayAddressList[0].point.lng, latitude: arrayAddressList[0].point.lat})  
                        }  
                    },  
                });  
                locationController.setPageCapacity(10);  
                locationController.search(sSearchKey);  
            }  
        },  
    }  
</script>  
<!-- #endif -->  

<style scoped>  
    .baidu_map {  
        width: 100%;  
        height: 100%;  
    }  

    .baidu_map .myMap {  
        width: 100%;  
        height: 100%;  
    }  

    .baidu_map .location {  
        position: absolute;  
        width: 40px;  
        height: 40px;  
        top: 10px;  
        right: 10px;  
        display: flex;  
        justify-content: center;  
        align-items: center;  
    }  

    .baidu_map .marking {  
        background-image: url('/static/location_dw.png');  
        position: absolute;  
        width: 40px;  
        height: 40px;  
        left: 50%;  
        right: 50%;  
        transform: translate(-50%, -40px);  
        top: 50%;  
        /* display: flex;  
        justify-content: center;  
        align-items: center; */  
    }  

    .baidu_map .address_list {  
        width: 100%;  
        height: 50%;  
        overflow: hidden;  
    }  

    .baidu_map .address_list .search {  
        padding: 10px;  
        background-color: #ffffff;  
    }  

    .baidu_map .address_list .list {  
        width: 100%;  
        height: calc(100% - 104px);  
        overflow: scroll;  
    }  

    .baidu_map .address_list .footer {  
        height: 40px;  
        background-color: #f7f7fa;  
        padding: 5px;  
    }  

    .location_cell {  
        display: flex;  
        padding: 5px 10px 0px 10px;  
        overflow: hidden;  
    }  

    .location_cell .centent {  
        flex: 1;  
        overflow: hidden;  
    }  

    .location_cell .right {  
        width: 40px;  
        display: flex;  
        justify-content: center;  
        align-items: center;  
    }  

    .location_cell .centent .title {  
        height: 24px;  
        font-size: 16px;  
        font-weight: 500;  
        line-height: 24px;  
        margin-bottom: 3px;  
        overflow:hidden;  
        white-space:nowrap;  
        text-overflow:ellipsis;  
    }  

    .location_cell .centent .sub_title {  
        font-size: 14px;  
        color: #222222;  
        text-overflow: ellipsis;  
        overflow: hidden;  
        margin-bottom: 5px;  
    }  
</style>

3、在业务页面使用它
代码如下

<template>  
    <view>  
        <u-navbar  
            title="坐标获取"  
            :border="true"  
            :placeholder="true"  
            :pagingEnabled="false"  
            :autoBack="true">  
        </u-navbar>  

        <!-- showSearch 控制显示模式,true为带搜索地址功能,false为纯地图 -->  
        <view :style="sStyle">  
            <xn-map   
                showSearch  
                @clickSave="clickSave"  
                @sendLocation="getLocation"  
            />  
        </view>  
    </view>  
</template>  

<script >      
    export default {  
        data() {  
            return {  
                sStyle: "",  
            }  
        },  
        onLoad: function(option) {},  
        onReady() {  
            let res = uni.getSystemInfoSync();  
            let iHeight = res.windowHeight - (44.5 + res.statusBarHeight + res.safeAreaInsets.bottom);  
            this.sStyle = 'width: 100vw; height: ' + iHeight + 'px;';  
        },  
        methods: {  
            clickSave(val) {  
                console.log('选定了地址');  
                console.log(val);  
            },  
            getLocation(val) {  
                console.log('获取到定位信息');  
                console.log(val);  
            }  
        },  
    }  
</script>  

<style scoped>  

</style>

上面的百度地图有2种模式,一种就是纯地图展示(含定位功能,定位功能需要自己去将项目中的manifest.json,进行配置百度地图的ak),不展示启用搜索选地址功能。
一种就是地图加地址检索和选地址功能。

代码里面都有注释,详细的去看,保证你会了解。

收起阅读 »

uni-app h5、app、微信小程序 全局水印组件

参考了:https://ask.dcloud.net.cn/article/35955 内容,对代码进行了修改,添加了优化

1、使用者需要在自己想要开始使用全局水印的地方开始写入,一般来说都是在首页index.vue执行的,在App.vue无法实现。本示例举例在 首页的 index.vue进行。

<!-- 水印组件.vue -->  

<template>  
    <!-- #ifdef APP-PLUS -->  
    <view class="watermark_back">  
        <canvas class="watermark" canvas-id="watermarkCanvas" id="watermarkCanvas"></canvas>  
    </view>  
    <!-- #endif -->  

    <!-- #ifdef H5 -->  
    <view>  

    </view>  
    <!-- #endif -->  

    <!-- #ifdef MP-WEIXIN -->  
    <view class="wx_watermark">  
        <view v-for="(item, index) in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]" :key="index">  
            <view class="wx_watermark-row">  
                <span class="wx_watermark-col" v-for="(item1, index1) in ['a1','a2','a3','a4','a5','a6']" :key="index1">{{title}}</span>  
            </view>  
        </view>  
    </view>  
    <!-- #endif -->  
</template>  

<script>  
    export default {  
        name:"crm-watermark",  
        props: {  
            title: {  
                type: String,  
                default() {  
                    return '';  
                }  
            },  
        },  
        data() {  
            return {  

            };  
        },  
        mounted() {  
            uni.$on('initCrmWatermark', this.initCrmWatermark);  
            uni.$on('removeCrmWatermark', this.removeCrmWatermark);  
        },  
        destroyed() {  
            uni.$off('initCrmWatermark', this.initCrmWatermark)  
            uni.$off('removeCrmWatermark', this.removeCrmWatermark)  
        },  
        methods: {  
            initCrmWatermark(msg) {  
            // #ifdef APP-PLUS  
                let id = 'crm_watermark_20240423';  
                if (plus.nativeObj.View.getViewById(id) !== null) {  
                    plus.nativeObj.View.getViewById(id).close();  
                }  
                let context = uni.createCanvasContext('watermarkCanvas');  
                context.rotate(-45 * Math.PI / 180);  
                context.setFontSize(15);  
                context.setFillStyle('rgba(200, 200, 200, 0.30)');  
                context.setTextAlign('left');  
                context.setTextBaseline('middle');  
                context.fillText(msg, -83, 86);  

                context.draw(false, function() {  
                    uni.canvasToTempFilePath({  
                        canvasId: "watermarkCanvas",  
                        width: 126,  // 与下方 tarArr position width 保持一致  
                        height: 126, // 与下方 tarArr position height 保持一致  
                        success: function(res) {  
                            let path = res.tempFilePath;  
                            uni.getSystemInfo({  
                                success: function (res) {  
                                    // 水印排列行数  
                                    let row = Math.floor(res.windowHeight / 126);  
                                    let tarArr = [];  
                                    for (let i = 0; i < row + 1; i++) {  
                                        for (let j = 0; j < row + 1; j++){  
                                            tarArr.push({  
                                                tag: 'img',  
                                                src: path,  
                                                position: {  
                                                    top: (126 * i) + 'px',  
                                                    left: (126 * j) + 'px',  
                                                    width: '126px',  
                                                    height: '126px',  
                                                }  
                                            });  
                                        }  
                                    }  
                                    var watermarkView = new plus.nativeObj.View(  
                                        id,  
                                        {top:'0px', left:'0px', right: '0px', bottom: '0px'},  
                                        tarArr  
                                    );  
                                    // 拦截View控件的触屏事件,将事件穿透给下一层view  
                                    watermarkView.interceptTouchEvent(false);  
                                    watermarkView.show();  
                                    context.clearRect(0, 0, 126, 126);  
                                    context.draw();  
                                }  
                            });  
                        }  
                    });  
                });  
            // #endif  
            // #ifdef H5  
                let id = 'crm_watermark_20240423';  
                if (document.getElementById(id) !== null) {  
                    document.body.removeChild(document.getElementById(id));  
                }  

                let canvas = document.createElement('canvas');  
                canvas.width = 126;  
                canvas.height = 126;  

                let canvas2 = canvas.getContext('2d');  

                canvas2.rotate(-45 * Math.PI / 180);  
                canvas2.font = '15px Vedana';  
                canvas2.fillStyle = 'rgba(200, 200, 200, 0.3)';  
                canvas2.textAlign = 'left';  
                canvas2.textBaseline = 'middle';  
                canvas2.fillText(msg, -83, 86);  

                let div = document.createElement('div');  
                div.id = id;  
                div.style.pointerEvents = 'none';  
                div.style.top = '0px';  
                div.style.left = '0px';  
                div.style.bottom = '0px';  
                div.style.right = '0px';  
                div.style.position = 'fixed';  
                div.style.zIndex = '100000';  
                div.style.background = 'url(' + canvas.toDataURL('image/png') + ') left top repeat';  
                document.body.appendChild(div);  
                return id;  
            // #endif  
            },  
            // 删除水印(仅APP与H5有效)  
            removeCrmWatermark() {  
                // #ifdef APP-PLUS  
                    if (plus.nativeObj.View.getViewById('crm_watermark_20240423') !== null) {  
                        plus.nativeObj.View.getViewById('crm_watermark_20240423').close();  
                    }  
                // #endif  
                // #ifdef H5  
                    if (document.getElementById('crm_watermark_20240423') !== null) {  
                        document.body.removeChild(document.getElementById('crm_watermark_20240423'));  
                    }  
                // #endif  
            },  
        },  
    }  
</script>  

<style scoped>  
    /* #ifdef APP-PLUS */  
    .watermark_back {  
        position: relative;  
        width: 126px;  
        height: 126px;  
        z-index: -1;  
    }  

    .watermark {  
        position: absolute;  
        left: -126px;  
        top: -126px;  
    }  
    /* #endif */  

    /* #ifdef MP-WEIXIN */  
    .wx_watermark {  
      position: fixed;  
      width: 200vw;  
      height: 150vh;  
      top: -20vw;   
      left: -50vw;   
      color: rgba(200, 200, 200, 0.3);  
      font-size: 15px;  
      opacity: 1;  
      z-index: 1000000; // 放在顶层  
      pointer-events: none; // 点击穿透,不影响页面交互  
      transform: rotate(-45deg); // 水印倾斜角度  
    }  

    .wx_watermark-col {  
      display: inline-block;  
      padding: 50rpx 40rpx;  
    }  

    .wx_watermark-row {  
      white-space: nowrap;  
    }  

    .wx_watermark-row:nth-child(2n+1) {  
      transform: translateX(10%); // 奇偶数行水印错开  
    }  

    /* #endif */  

</style>

业务代码

<template>  
    <view>  
        <!-- 其他业务代码 -->  

        <!-- 添加水印 app与H5 只需要调用一次,即可配置全局水印 微信小程序需要每个页面都引入 -->  
        <!-- #ifdef MP-WEIXIN || APP-PLUS -->  
        <crm-watermark :title="sWatermark"></crm-watermark>  
        <!-- #endif -->  
    </view>  
</template>  

<script>  
    data() {  
        return {  
            // 微信小程序需要每个页面都引入,并且设置水印  
            // #ifdef MP-WEIXIN || APP-PLUS  
            sWatermark: "",  
            // #endif  
        }  
    },  
    methods: {  
        showWatermark() {  
            // app与H5 只需要调用一次,即可配置全局水印  
            // #ifdef APP-PLUS || H5  
            uni.$emit('initCrmWatermark', sTitle);  
            // #endif  

            // 微信小程序需要每个页面都引入,并且设置水印  
            // #ifdef MP-WEIXIN || APP-PLUS  
            this.sWatermark = sTitle;  
            // #endif  
        },  
    }

如果渲染出来 在APP水印页面发现多了个 格格不入的水印,请进行 APP-PLUS 中代码的微调,这是正常的。下面让我来解释一下 (H5不会出现这个问题)

在 APP-PLUS 模式下,先是使用 uni.createCanvasContext('watermarkCanvas'),找到页面中的 canvas。并且对 canvas进行设置(如字体、旋转角度、字体颜色、文字对齐方式、文字水平对齐方式、及填充文字,并且进行 X、Y轴设置)

接着 使用 context.draw,进行渲染,这个时候,其实页面渲染成功就会出现一个 水印了,但后续调用 uni.canvasToTempFilePath(把当前画布指定区域的内容导出生成指定大小的图片) 在其中做了 for循环,将画布内容又生产了N个,并且通过plus.nativeObj.View方式加入到了页面中。这个时候页面就会多出一个看起来格格不入的水印,这个水印就是前面提到的 context.draw 的时候页面已经出来一个了。

那么如何解决这个格格不入的水印问题呢,我的方式是覆盖后将其删除。将一个通过context.draw渲染出来的水印,通过大小调整为与 uni.canvasToTempFilePath for 循环出来的 水印大小一致,将其盖在上面,这样虽然第一个水印看起来颜色深了一个点,但其他并无异常。在uni.canvasToTempFilePath for 循环成功后加入 context.clearRect(0, 0, 126, 126); context.draw(); 将其原来绘制的水印进行删除。

具体看我代码注释:下面是关键代码
width: 126, // 与下方 tarArr position width 保持一致
height: 126, // 与下方 tarArr position height 保持一致
context.clearRect(0, 0, 126, 126);
context.draw();

微信小程序由于无法实现全局水印,只能在每个页面都加入该组件,并且使用条件编译
<!-- #ifdef MP-WEIXIN-->
<crm-watermark :title="sWatermark"></crm-watermark>
<!-- #endif -->

// #ifdef MP-WEIXIN
sWatermark: "",
// #endif

// #ifdef MP-WEIXIN
this.sWatermark = sTitle;
// #endif

继续阅读 »

参考了:https://ask.dcloud.net.cn/article/35955 内容,对代码进行了修改,添加了优化

1、使用者需要在自己想要开始使用全局水印的地方开始写入,一般来说都是在首页index.vue执行的,在App.vue无法实现。本示例举例在 首页的 index.vue进行。

<!-- 水印组件.vue -->  

<template>  
    <!-- #ifdef APP-PLUS -->  
    <view class="watermark_back">  
        <canvas class="watermark" canvas-id="watermarkCanvas" id="watermarkCanvas"></canvas>  
    </view>  
    <!-- #endif -->  

    <!-- #ifdef H5 -->  
    <view>  

    </view>  
    <!-- #endif -->  

    <!-- #ifdef MP-WEIXIN -->  
    <view class="wx_watermark">  
        <view v-for="(item, index) in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]" :key="index">  
            <view class="wx_watermark-row">  
                <span class="wx_watermark-col" v-for="(item1, index1) in ['a1','a2','a3','a4','a5','a6']" :key="index1">{{title}}</span>  
            </view>  
        </view>  
    </view>  
    <!-- #endif -->  
</template>  

<script>  
    export default {  
        name:"crm-watermark",  
        props: {  
            title: {  
                type: String,  
                default() {  
                    return '';  
                }  
            },  
        },  
        data() {  
            return {  

            };  
        },  
        mounted() {  
            uni.$on('initCrmWatermark', this.initCrmWatermark);  
            uni.$on('removeCrmWatermark', this.removeCrmWatermark);  
        },  
        destroyed() {  
            uni.$off('initCrmWatermark', this.initCrmWatermark)  
            uni.$off('removeCrmWatermark', this.removeCrmWatermark)  
        },  
        methods: {  
            initCrmWatermark(msg) {  
            // #ifdef APP-PLUS  
                let id = 'crm_watermark_20240423';  
                if (plus.nativeObj.View.getViewById(id) !== null) {  
                    plus.nativeObj.View.getViewById(id).close();  
                }  
                let context = uni.createCanvasContext('watermarkCanvas');  
                context.rotate(-45 * Math.PI / 180);  
                context.setFontSize(15);  
                context.setFillStyle('rgba(200, 200, 200, 0.30)');  
                context.setTextAlign('left');  
                context.setTextBaseline('middle');  
                context.fillText(msg, -83, 86);  

                context.draw(false, function() {  
                    uni.canvasToTempFilePath({  
                        canvasId: "watermarkCanvas",  
                        width: 126,  // 与下方 tarArr position width 保持一致  
                        height: 126, // 与下方 tarArr position height 保持一致  
                        success: function(res) {  
                            let path = res.tempFilePath;  
                            uni.getSystemInfo({  
                                success: function (res) {  
                                    // 水印排列行数  
                                    let row = Math.floor(res.windowHeight / 126);  
                                    let tarArr = [];  
                                    for (let i = 0; i < row + 1; i++) {  
                                        for (let j = 0; j < row + 1; j++){  
                                            tarArr.push({  
                                                tag: 'img',  
                                                src: path,  
                                                position: {  
                                                    top: (126 * i) + 'px',  
                                                    left: (126 * j) + 'px',  
                                                    width: '126px',  
                                                    height: '126px',  
                                                }  
                                            });  
                                        }  
                                    }  
                                    var watermarkView = new plus.nativeObj.View(  
                                        id,  
                                        {top:'0px', left:'0px', right: '0px', bottom: '0px'},  
                                        tarArr  
                                    );  
                                    // 拦截View控件的触屏事件,将事件穿透给下一层view  
                                    watermarkView.interceptTouchEvent(false);  
                                    watermarkView.show();  
                                    context.clearRect(0, 0, 126, 126);  
                                    context.draw();  
                                }  
                            });  
                        }  
                    });  
                });  
            // #endif  
            // #ifdef H5  
                let id = 'crm_watermark_20240423';  
                if (document.getElementById(id) !== null) {  
                    document.body.removeChild(document.getElementById(id));  
                }  

                let canvas = document.createElement('canvas');  
                canvas.width = 126;  
                canvas.height = 126;  

                let canvas2 = canvas.getContext('2d');  

                canvas2.rotate(-45 * Math.PI / 180);  
                canvas2.font = '15px Vedana';  
                canvas2.fillStyle = 'rgba(200, 200, 200, 0.3)';  
                canvas2.textAlign = 'left';  
                canvas2.textBaseline = 'middle';  
                canvas2.fillText(msg, -83, 86);  

                let div = document.createElement('div');  
                div.id = id;  
                div.style.pointerEvents = 'none';  
                div.style.top = '0px';  
                div.style.left = '0px';  
                div.style.bottom = '0px';  
                div.style.right = '0px';  
                div.style.position = 'fixed';  
                div.style.zIndex = '100000';  
                div.style.background = 'url(' + canvas.toDataURL('image/png') + ') left top repeat';  
                document.body.appendChild(div);  
                return id;  
            // #endif  
            },  
            // 删除水印(仅APP与H5有效)  
            removeCrmWatermark() {  
                // #ifdef APP-PLUS  
                    if (plus.nativeObj.View.getViewById('crm_watermark_20240423') !== null) {  
                        plus.nativeObj.View.getViewById('crm_watermark_20240423').close();  
                    }  
                // #endif  
                // #ifdef H5  
                    if (document.getElementById('crm_watermark_20240423') !== null) {  
                        document.body.removeChild(document.getElementById('crm_watermark_20240423'));  
                    }  
                // #endif  
            },  
        },  
    }  
</script>  

<style scoped>  
    /* #ifdef APP-PLUS */  
    .watermark_back {  
        position: relative;  
        width: 126px;  
        height: 126px;  
        z-index: -1;  
    }  

    .watermark {  
        position: absolute;  
        left: -126px;  
        top: -126px;  
    }  
    /* #endif */  

    /* #ifdef MP-WEIXIN */  
    .wx_watermark {  
      position: fixed;  
      width: 200vw;  
      height: 150vh;  
      top: -20vw;   
      left: -50vw;   
      color: rgba(200, 200, 200, 0.3);  
      font-size: 15px;  
      opacity: 1;  
      z-index: 1000000; // 放在顶层  
      pointer-events: none; // 点击穿透,不影响页面交互  
      transform: rotate(-45deg); // 水印倾斜角度  
    }  

    .wx_watermark-col {  
      display: inline-block;  
      padding: 50rpx 40rpx;  
    }  

    .wx_watermark-row {  
      white-space: nowrap;  
    }  

    .wx_watermark-row:nth-child(2n+1) {  
      transform: translateX(10%); // 奇偶数行水印错开  
    }  

    /* #endif */  

</style>

业务代码

<template>  
    <view>  
        <!-- 其他业务代码 -->  

        <!-- 添加水印 app与H5 只需要调用一次,即可配置全局水印 微信小程序需要每个页面都引入 -->  
        <!-- #ifdef MP-WEIXIN || APP-PLUS -->  
        <crm-watermark :title="sWatermark"></crm-watermark>  
        <!-- #endif -->  
    </view>  
</template>  

<script>  
    data() {  
        return {  
            // 微信小程序需要每个页面都引入,并且设置水印  
            // #ifdef MP-WEIXIN || APP-PLUS  
            sWatermark: "",  
            // #endif  
        }  
    },  
    methods: {  
        showWatermark() {  
            // app与H5 只需要调用一次,即可配置全局水印  
            // #ifdef APP-PLUS || H5  
            uni.$emit('initCrmWatermark', sTitle);  
            // #endif  

            // 微信小程序需要每个页面都引入,并且设置水印  
            // #ifdef MP-WEIXIN || APP-PLUS  
            this.sWatermark = sTitle;  
            // #endif  
        },  
    }

如果渲染出来 在APP水印页面发现多了个 格格不入的水印,请进行 APP-PLUS 中代码的微调,这是正常的。下面让我来解释一下 (H5不会出现这个问题)

在 APP-PLUS 模式下,先是使用 uni.createCanvasContext('watermarkCanvas'),找到页面中的 canvas。并且对 canvas进行设置(如字体、旋转角度、字体颜色、文字对齐方式、文字水平对齐方式、及填充文字,并且进行 X、Y轴设置)

接着 使用 context.draw,进行渲染,这个时候,其实页面渲染成功就会出现一个 水印了,但后续调用 uni.canvasToTempFilePath(把当前画布指定区域的内容导出生成指定大小的图片) 在其中做了 for循环,将画布内容又生产了N个,并且通过plus.nativeObj.View方式加入到了页面中。这个时候页面就会多出一个看起来格格不入的水印,这个水印就是前面提到的 context.draw 的时候页面已经出来一个了。

那么如何解决这个格格不入的水印问题呢,我的方式是覆盖后将其删除。将一个通过context.draw渲染出来的水印,通过大小调整为与 uni.canvasToTempFilePath for 循环出来的 水印大小一致,将其盖在上面,这样虽然第一个水印看起来颜色深了一个点,但其他并无异常。在uni.canvasToTempFilePath for 循环成功后加入 context.clearRect(0, 0, 126, 126); context.draw(); 将其原来绘制的水印进行删除。

具体看我代码注释:下面是关键代码
width: 126, // 与下方 tarArr position width 保持一致
height: 126, // 与下方 tarArr position height 保持一致
context.clearRect(0, 0, 126, 126);
context.draw();

微信小程序由于无法实现全局水印,只能在每个页面都加入该组件,并且使用条件编译
<!-- #ifdef MP-WEIXIN-->
<crm-watermark :title="sWatermark"></crm-watermark>
<!-- #endif -->

// #ifdef MP-WEIXIN
sWatermark: "",
// #endif

// #ifdef MP-WEIXIN
this.sWatermark = sTitle;
// #endif

收起阅读 »

全栈开发在线接单,可接急单大单

外包 外包接单

本人全栈,因现在本职工作是产品了,担心久了不写代码,丢了吃饭的家伙,所以想接点写代码的单子。
主要是应用开发,小程序、微信网页应用,或是AI应用、区块链应用都做过,大佬们有需求可以聊聊,vx:laifeilim。
不急的单我可以周末自己抽空写,成本低;
急单,我得通过单位来接,这样我上班的时候也可以写。
大一点的单,单位这边应该也可以接下来的:团队百人+,目前CMMI3,年内CMMI5,注册资本3亿。

我自己从2021年就在插件市场发布插件,前端插件、云端一体页面模板、Admin插件、SDK都有:https://ext.dcloud.net.cn/publisher?id=743749
挑战过不到3天开发上线一个在线报名选课缴费应用,也在各种技术分享会安利unicloud:https://www.bilibili.com/video/BV1nW421N7XC/?spm_id_from=333.999.0.0

继续阅读 »

本人全栈,因现在本职工作是产品了,担心久了不写代码,丢了吃饭的家伙,所以想接点写代码的单子。
主要是应用开发,小程序、微信网页应用,或是AI应用、区块链应用都做过,大佬们有需求可以聊聊,vx:laifeilim。
不急的单我可以周末自己抽空写,成本低;
急单,我得通过单位来接,这样我上班的时候也可以写。
大一点的单,单位这边应该也可以接下来的:团队百人+,目前CMMI3,年内CMMI5,注册资本3亿。

我自己从2021年就在插件市场发布插件,前端插件、云端一体页面模板、Admin插件、SDK都有:https://ext.dcloud.net.cn/publisher?id=743749
挑战过不到3天开发上线一个在线报名选课缴费应用,也在各种技术分享会安利unicloud:https://www.bilibili.com/video/BV1nW421N7XC/?spm_id_from=333.999.0.0

收起阅读 »

免费帮忙开发 安卓 ios 原生插件,刚学完练练手,有需要的留言(太难的就算了)

uniapp原生插件

免费开发安卓 ios 原生插件,刚学完练练手,有需要的留言(太难的就算了)

免费开发安卓 ios 原生插件,刚学完练练手,有需要的留言(太难的就算了)

uni-app排坑指南-uni-push获取cid异常问题的处理

uni-push2配置完成后,根据官方文档使用uni.getPushClientId方法获取cid,
可能会报错getPushClientId:fail uniPush is not enabled,此时重新打自定义调试基座也没有作用。

app端可以使用

let pinf = plus.push.getClientInfo();  
let cid = pinf && pinf.clientid || ''; //客户端标识  
uni.setStorageSync('unipush_id', cid)  
plus.push.getClientInfoAsync((info) => {  
    cid = info.clientid;  
    uni.setStorageSync('unipush_id', cid)  
    console.log(cid)  
}, err => {});

使用plus方法可以稳定获取到cid,但是这个方法有局限性,只能在APP-PLUS中使用。

如果想在h5中使用,就需要在app.vue文件中引入一下uni-push模块

// #ifdef H5  
require('@dcloudio/vue-cli-plugin-uni/packages/uni-push/dist/uni-push.es.js')  
// #endif

使用这个方法获取到的cid后台会直接检测为小程序,不管是否勾选离线推送都只能在线推送。
根据自己编译环境具体看使用什么方法

继续阅读 »

uni-push2配置完成后,根据官方文档使用uni.getPushClientId方法获取cid,
可能会报错getPushClientId:fail uniPush is not enabled,此时重新打自定义调试基座也没有作用。

app端可以使用

let pinf = plus.push.getClientInfo();  
let cid = pinf && pinf.clientid || ''; //客户端标识  
uni.setStorageSync('unipush_id', cid)  
plus.push.getClientInfoAsync((info) => {  
    cid = info.clientid;  
    uni.setStorageSync('unipush_id', cid)  
    console.log(cid)  
}, err => {});

使用plus方法可以稳定获取到cid,但是这个方法有局限性,只能在APP-PLUS中使用。

如果想在h5中使用,就需要在app.vue文件中引入一下uni-push模块

// #ifdef H5  
require('@dcloudio/vue-cli-plugin-uni/packages/uni-push/dist/uni-push.es.js')  
// #endif

使用这个方法获取到的cid后台会直接检测为小程序,不管是否勾选离线推送都只能在线推送。
根据自己编译环境具体看使用什么方法

收起阅读 »

uni-app的app和web平台定位异常;uni-app已支持鸿蒙next;uni-app x 3端发布;uniCloud阿里云版Nodejs8终止支持;oppo商店误报广告

鸿蒙 uniCloud

uni-app的app和web平台定位异常

该问题已修复,请升级到uni-app 4.24。原因分析见下:

uni-app x 3端正式发布

随着4.14正式版发布,uni-app x的Web、Android、iOS均已上线。详见

欢迎阅读评测报告,了解uni-app x为何是性能最好的跨平台框架。详见

uni-app 已支持鸿蒙next

update@20240706:uni-app的鸿蒙版已灰度发布,若你想抢鲜体验,可下载最新的HBuilderX Alpha版本,手动打开鸿蒙入口,详见uni-app 开发鸿蒙应用

关联阅读,参考:星河璀璨,uni-app 亮相华为 HDC2024 开发者大会

2023底举办的插件大赛圆满结束

本次插件大赛在数千名候选插件中,评选出了82个优秀插件。这些插件为uni-app x的生态做出重要的贡献。
至此,大部分常见插件在uni-app x中均已提供。

获奖名单详见:https://ask.dcloud.net.cn/article/40812

关于 App Store 提交的隐私更新

Appstore要求2024年5月1日以后提交的包,都必须包含隐私清单。
Apple公告另见:关于 App Store 提交的隐私更新

使用 Transporter 工具上传到 AppStore 会提示以下警告:

ITMS-91053: Missing API declaration - Your app’s code in the “HBuilder” file references one or more APIs that require reasons, including the following API categories: NSPrivacyAccessedAPICategoryFileTimestamp. While no action is required at this time, starting May 1, 2024, when you upload a new app or app update, you must include a NSPrivacyAccessedAPITypes array in your app’s privacy manifest to provide approved reasons for these APIs used by your app’s code. For more details about this policy, including a list of required reason APIs and approved reasons for usage, visit: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api.

此警告只是提示信息,在2024年5月1日前仍可继续提交 AppStore。

2024年4月24号起,云打包支持隐私清单配置。

  • uni-app、5+App项目请更新到HBuilderX4.08及以上版本,重新提交云端打包
  • uni-app x项目需更新到HBuilderX4.13及以上版本重新提交云端打包

配置方法教程:详见

离线打包和uni小程序sdk也见上面的链接。

uniCloud阿里云版Nodejs8终止支持

uniCloud阿里云版服务空间云函数运行时目前支持Nodejs8、Nodejs12、Nodejs14、Nodejs16四个版本。接阿里云通知,其中Nodejs8即将终止支持,计划如下:

版本 终止支持阶段一:禁止新建 终止支持阶段二:禁止新建和更新
Node.js8 2024年06月01日 2024年09月01日

终止支持仅影响运行时为Nodejs8的云函数更新,不影响已有云函数继续运行,建议您及时升级到更高的版本。

所有已部署到服务空间的云函数(并非公共模块)均需开发者自行升级,升级方式:修改云函数package.jsoncloudfunction-config节点下的runtime,然后重新上传云函数。如果云函数的package.json没有cloudfunction-config相关配置,可参考文档进行添加,点此查看相关文档

uni一键登录SDK升级

接到联通运营商通知,旧版联通一键认证SDK将于2024年5月31日正式下线。
之前我们已经发布过升级公告,另见:uni一键登录SDK升级公告
注意:
云端打包:如果您已经使用HBuilderX3.99及以上版本重新打包并发布了一键登录的App,请忽略此公告。
离线打包:请更新Android、IOS离线SDK至最新版本

vivo商店提示无合理使用场景频繁采集oaid的问题

详见:https://ask.dcloud.net.cn/article/41143

oppo商店误报有广告的问题

推荐使用uni加固解决,详见:https://uniapp.dcloud.net.cn/tutorial/app-security.html
后续官方会升级代码,减少误检测误报。

继续阅读 »

uni-app的app和web平台定位异常

该问题已修复,请升级到uni-app 4.24。原因分析见下:

uni-app x 3端正式发布

随着4.14正式版发布,uni-app x的Web、Android、iOS均已上线。详见

欢迎阅读评测报告,了解uni-app x为何是性能最好的跨平台框架。详见

uni-app 已支持鸿蒙next

update@20240706:uni-app的鸿蒙版已灰度发布,若你想抢鲜体验,可下载最新的HBuilderX Alpha版本,手动打开鸿蒙入口,详见uni-app 开发鸿蒙应用

关联阅读,参考:星河璀璨,uni-app 亮相华为 HDC2024 开发者大会

2023底举办的插件大赛圆满结束

本次插件大赛在数千名候选插件中,评选出了82个优秀插件。这些插件为uni-app x的生态做出重要的贡献。
至此,大部分常见插件在uni-app x中均已提供。

获奖名单详见:https://ask.dcloud.net.cn/article/40812

关于 App Store 提交的隐私更新

Appstore要求2024年5月1日以后提交的包,都必须包含隐私清单。
Apple公告另见:关于 App Store 提交的隐私更新

使用 Transporter 工具上传到 AppStore 会提示以下警告:

ITMS-91053: Missing API declaration - Your app’s code in the “HBuilder” file references one or more APIs that require reasons, including the following API categories: NSPrivacyAccessedAPICategoryFileTimestamp. While no action is required at this time, starting May 1, 2024, when you upload a new app or app update, you must include a NSPrivacyAccessedAPITypes array in your app’s privacy manifest to provide approved reasons for these APIs used by your app’s code. For more details about this policy, including a list of required reason APIs and approved reasons for usage, visit: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api.

此警告只是提示信息,在2024年5月1日前仍可继续提交 AppStore。

2024年4月24号起,云打包支持隐私清单配置。

  • uni-app、5+App项目请更新到HBuilderX4.08及以上版本,重新提交云端打包
  • uni-app x项目需更新到HBuilderX4.13及以上版本重新提交云端打包

配置方法教程:详见

离线打包和uni小程序sdk也见上面的链接。

uniCloud阿里云版Nodejs8终止支持

uniCloud阿里云版服务空间云函数运行时目前支持Nodejs8、Nodejs12、Nodejs14、Nodejs16四个版本。接阿里云通知,其中Nodejs8即将终止支持,计划如下:

版本 终止支持阶段一:禁止新建 终止支持阶段二:禁止新建和更新
Node.js8 2024年06月01日 2024年09月01日

终止支持仅影响运行时为Nodejs8的云函数更新,不影响已有云函数继续运行,建议您及时升级到更高的版本。

所有已部署到服务空间的云函数(并非公共模块)均需开发者自行升级,升级方式:修改云函数package.jsoncloudfunction-config节点下的runtime,然后重新上传云函数。如果云函数的package.json没有cloudfunction-config相关配置,可参考文档进行添加,点此查看相关文档

uni一键登录SDK升级

接到联通运营商通知,旧版联通一键认证SDK将于2024年5月31日正式下线。
之前我们已经发布过升级公告,另见:uni一键登录SDK升级公告
注意:
云端打包:如果您已经使用HBuilderX3.99及以上版本重新打包并发布了一键登录的App,请忽略此公告。
离线打包:请更新Android、IOS离线SDK至最新版本

vivo商店提示无合理使用场景频繁采集oaid的问题

详见:https://ask.dcloud.net.cn/article/41143

oppo商店误报有广告的问题

推荐使用uni加固解决,详见:https://uniapp.dcloud.net.cn/tutorial/app-security.html
后续官方会升级代码,减少误检测误报。

收起阅读 »

【建议】建议压缩包添加一个脚本文件创建桌面快捷方式

@echo off  
echo Set oWS = WScript.CreateObject("WScript.Shell") > CreateShortcut.vbs  
echo sLinkFile = "%USERPROFILE%\Desktop\HBuilder X.lnk" >> CreateShortcut.vbs  
echo Set oLink = oWS.CreateShortcut(sLinkFile) >> CreateShortcut.vbs  
echo oLink.TargetPath = "%CD%\HBuilderX.exe" >> CreateShortcut.vbs  
echo oLink.WorkingDirectory = "%CD%\" >> CreateShortcut.vbs  
echo oLink.Save >> CreateShortcut.vbs  
cscript CreateShortcut.vbs  
del CreateShortcut.vbs
继续阅读 »
@echo off  
echo Set oWS = WScript.CreateObject("WScript.Shell") > CreateShortcut.vbs  
echo sLinkFile = "%USERPROFILE%\Desktop\HBuilder X.lnk" >> CreateShortcut.vbs  
echo Set oLink = oWS.CreateShortcut(sLinkFile) >> CreateShortcut.vbs  
echo oLink.TargetPath = "%CD%\HBuilderX.exe" >> CreateShortcut.vbs  
echo oLink.WorkingDirectory = "%CD%\" >> CreateShortcut.vbs  
echo oLink.Save >> CreateShortcut.vbs  
cscript CreateShortcut.vbs  
del CreateShortcut.vbs
收起阅读 »