基于Uni-app实现当前位置关键词范围搜索功能

一、场景需求

通过高德地图Web服务API实现:

  1. 获取用户实时位置
  2. 搜索半径5公里内的医疗机构
  3. 展示医院详细信息及距离

二、实现步骤

   一、 注册开发者账号

  1. 访问高德开放平台
  2. 点击右上角"控制台" -> 选择"立即注册"
  3. 完成企业/个人实名认证(需身份证/营业执照)

   

  二、创建新应用

  1. 登录控制台后进入「应用管理」
  2. 点击「创建新应用」按钮
  3. 填写应用信息:
    • 应用名称:医院导航系统(示例)
    • 应用类型:根据需要选择类型「其他」
    • 添加key:创建完点击右上角添加key,名称自己定义一个,服务平台这里选的Web服务,后面如果报错USERKEY_PLAT_NOMATCH就是这个服务平台不对,可以继续在添加符合服务的key就可以了

三、获取密钥

  1. 创建成功后可在「应用列表」查看
  2. Key格式示例:2cd4564422e1221bced008196b4eac311
  3. 使用限制:
    • 每日限额:默认5万次/日(可申请提升)
    • QPS限制:50次/秒

四、设置小程序权限

  1. manifest.json中配置权限
//这里的权限都要添加,要不然会报错
  "app-plus": {
    "ios": {
      "infoAdditions": {
        "NSLocationAlwaysUsageDescription": "需要定位权限以显示附近医院",
        "NSLocationWhenInUseUsageDescription": "需要定位权限以显示附近医院"
      }
    },
    "distribute": {
      "android": {
        "permissions": [
           //这里是获取网络和位置权限
          "<uses-permission android:name=\"android.permission.INTERNET\"/>",
          "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
          "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
  },


 "mp-weixin": {
    "permission": {
      "scope.userLocation": {
        "desc": "需要定位权限以显示附近医院"
      }
    },
    //这个也要注意,我这里只是获取位置只添加了这一个,如果需要模糊位置等还要添加别的,可以自行搜索一下["onLocationChange","startLocationUpdateBackground", "chooseAddress"]
    "requiredPrivateInfos": ["getLocation"],
  },

五、封装第三方通用网络请求工具(非必要,可以使用自己的,不需要直接跳过)

  • 统一请求管理:封装了 uni.request 方法,提供标准化的 HTTP 请求功能,支持 GET/POST 等方法。
  • 参数自动处理:自动拼接 GET 请求的 URL 参数,并处理参数编码(如特殊字符转义)。
  • 统一错误处理:拦截网络错误和非 2xx 状态码的响应,抛出标准化的错误信息。
  • 请求头管理:默认设置 Content-Type: application/json,允许自定义请求头。
  • 避免重复代码:项目中多个模块需要调用不同 API,直接使用 uni.request 会导致重复编写参数拼接、错误处理等逻辑。
  • 跨平台兼容:Uni-app 需兼容微信小程序、H5 等平台,统一封装可隐藏平台差异。
  • 维护性提升:修改请求逻辑(如添加日志、重试机制)时,只需修改此文件,无需改动业务代码。
// utils/third-party-request.ts
type AnyObject = Record<string, any>

interface ThirdPartyConfig {
  url: string
  method?: UniApp.RequestOptions['method']
  data?: AnyObject
  params?: AnyObject
  headers?: AnyObject
}

export const thirdPartyRequest = <T>(config: ThirdPartyConfig): Promise<T> => {
  return new Promise((resolve, reject) => {
    // 处理GET参数(兼容小程序环境)
    let finalUrl = config.url
    if (config.method === 'GET' && config.params) {
      const queryParts: string[] = []
      for (const key in config.params) {
        if (Object.prototype.hasOwnProperty.call(config.params, key)) {
          queryParts.push(
            `${encodeURIComponent(key)}=${encodeURIComponent(config.params[key])}`
          )
        }
      }
      finalUrl += `?${queryParts.join('&')}`
    }

    uni.request({
      url: finalUrl,
      method: config.method || 'GET',
      data: config.data,
      header: {
        'Content-Type': 'application/json',
        ...config.headers
      },
      success: (res) => {
        if (res.statusCode >= 200 && res.statusCode < 300) {
          resolve(res.data as T)
        } else {
          reject(new Error(`[${res.statusCode}] 请求失败: ${res.data}`))
        }
      },
      fail: (err) => {
        reject(new Error(`网络错误: ${err.errMsg}`))
      }
    })
  })
}
  • 高德 API 对接:专门调用高德地图的「周边搜索」接口(/place/around),实现医院位置搜索。
  • 数据格式转换:将高德返回的原始数据转换为前端易用的结构(如转换距离单位、拆分经纬度)。
  • 密钥与配置管理:集中管理 API 密钥、基础 URL 等敏感信息。
  • 业务错误处理:检查高德 API 返回的状态码(如 status !== '1'),抛出业务相关错误。
  • 职责分离:将地图相关逻辑与其他业务(如用户管理、支付)隔离,符合模块化设计原则。
  • 复用性:其他页面或组件需要位置服务时(如导航、附近药店),可直接调用此模块。
  • 安全隔离:敏感操作(如密钥使用、坐标加密)集中在此处,降低泄露风险。
  • 维护便捷:高德 API 升级或更换地图服务商时,只需修改此模块,无需全局搜索替换。
// services/amap-service.ts
import { thirdPartyRequest } from '@/utils/third-party-request'

const AMAP_BASE_URL = 'https://restapi.amap.com/v3'//这里还有一个v5根据需要更换

interface AmapPoiSearchParams {
  keywords: string
  location: string  // 格式:"经度,纬度"
  radius?: number   // 单位:米
  city?: string
}

interface AmapResponse {
  status: '0' | '1'
  info: string
  count: string
  pois: Array<{
    id: string
    name: string
    type: string
    location: string  // 格式:"经度,纬度"
    address: string
    distance: string  // 单位:米
  }>
}

export const amapService = {
  async searchPoi(params: AmapPoiSearchParams): Promise<AmapResponse> {
    try {
      const data = await thirdPartyRequest<AmapResponse>({
        url: `${AMAP_BASE_URL}/place/around`,//这里可以看高德高级 API 文档上的路径根据需要切换,这里是周边搜索的地址
        method: 'GET',
        params: {
          ...params,
          key: '你的key', //不建议这样,建议从后端获取,这里只是测试直接写在这里
          output: 'json'
        }
      })

      if (data.status !== '1') {
        throw new Error(`高德API错误: ${data.info}`)
      }
      return data
    } catch (err) {
      console.error('高德服务请求失败:', err)
      throw new Error('位置服务暂时不可用')
    }
  }
}
附上:高德高级 API 文档地址:搜索POI-高级 API 文档-开发指南-Web服务 API | 高德地图API(里面参数和返回数据的详细介绍)

六、页面实现

     先附上完整代码
 <template>
  <view class="container">
   <!-- 医院列表 -->
    <view class="hospital-section">
      <view class="section-header">
        <text class="section-title">附近医院</text>
        <up-tag v-if="hospitals.length" size="mini">{{ hospitals.length }}所医院</up-tag>
        <up-loading-icon v-else-if="loading" mode="circle"></up-loading-icon>
        <text v-else-if="error" class="error-text">{{ error }}</text>
      </view>

      <view v-if="hospitals.length" class="hospital-list">
        <block v-for="(item, index) in hospitals" :key="index">
          <up-card :title="item.name" :sub-title="item.level" margin="15rpx 0" :border="false"
            :head-border-bottom="false">
            <template #body>
              <view class="card-body">
                <view class="image-wrapper">
                  <image v-if="item.image" :src="item.image" mode="aspectFill" class="hospital-image" />
                  <up-empty v-else mode="data" icon="http://cdn.uviewui.com/uview/empty/data.png" :text="item.name" />
                </view>

                <view class="info-wrapper">
                  <view class="info-item">
                    <up-icon name="map" size="16" color="#666" />
                    <text class="info-text">{{ item.address }}</text>
                  </view>

                  <view class="info-item">
                    <up-icon name="clock" size="16" color="#666" />
                    <text class="info-text">{{ item.distance }}</text>
                  </view>

                  <view class="action-buttons">
                    <up-button type="primary" size="mini" shape="circle" @click="navToDetail(item)">
                      查看详情
                    </up-button>
                    <up-button type="success" size="mini" shape="circle" @click="startNavigation(item)">
                      一键导航
                    </up-button>
                  </view>
                </view>
              </view>
            </template>
          </up-card>
        </block>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { amapService } from '@/api/app/amap-service'

// 医院数据
const hospitals = ref([]);
const loading = ref(true); // 加载状态
const error = ref(null);   // 错误信息

// 页面加载时触发
onMounted(() => {
  fetchHospitals();
});


// 获取当前位置
const fetchHospitals = async () => {
  try {
    // 先检查是否已授权
    const settingRes = await uni.getSetting({})
    if (!settingRes.authSetting['scope.userLocation']) {
      await uni.authorize({ scope: 'scope.userLocation' })
    }

    // 获取位置时捕获 iOS/Android 差异
    const locationRes = await new Promise((resolve, reject) => {
      uni.getLocation({ //这个地方可能报错
        type: 'gcj02', //gcj02 兼容国内地图服务
        success: resolve,
        fail: (err) => {
          uni.showModal({
            title: '定位失败',
            content: '请前往设置开启定位权限',
            success: () => uni.openSetting()
          })
          reject(err)
        }
      })
    })
    const location = `${locationRes.longitude},${locationRes.latitude}`;



    // 调用高德服务
    const response = await amapService.searchPoi({
      keywords: '医院',
      location: location,
      radius: 5000
    })
    console.log(response)
    // 处理数据
    hospitals.value = response.pois.map(poi => ({
      id: poi.id,
      name: poi.name,
      level: poi.type || '综合医院',
      address: poi.address,
      distance: (parseFloat(poi.distance) / 1000).toFixed(1) + '公里',
      image: poi.photos?.[0]?.url || "",//这里可能没有图,自己设置个默认的
      lat: poi.location.split(',')[1],
      lng: poi.location.split(',')[0]
    }))

  } catch (err) {
    error.value = err.message || '获取医院数据失败'
    uni.showToast({ title: error.value, icon: 'none' })
  } finally {
    loading.value = false
  }
};

</script>

<style scoped>
.hospital-section {
  padding: 25rpx;
}

.section-header {
  display: flex;
  align-items: center;
  margin-bottom: 30rpx;
}

.section-title {
  font-size: 34rpx;
  font-weight: 600;
  margin-right: 20rpx;
}

.card-body {
  display: flex;
  padding: 20rpx 0;
}

.image-wrapper {
  width: 240rpx;
  height: 200rpx;
  border-radius: 12rpx;
  overflow: hidden;
  flex-shrink: 0;
  margin-right: 25rpx;
  background: #f5f5f5;
}

.hospital-image {
  width: 100%;
  height: 100%;
}

.info-wrapper {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.info-item {
  display: flex;
  align-items: center;
  margin-bottom: 15rpx;
}

.info-text {
  font-size: 28rpx;
  color: #666;
  margin-left: 10rpx;
}

.action-buttons {
  display: flex;
  gap: 20rpx;
  margin-top: 25rpx;
}
</style>
效果图

七、可能报错

1、 API请求失败: {errMsg: "getLocation:fail the api need to be declared in the requiredPrivateInfos field in app.json/ext.json"}
这个就是manifest.json中要配置(可能改了之后还是报这个错,建议重启服务,清理缓存,重新编译)

  "mp-weixin": {
    "permission": {
      "scope.userLocation": {
        "desc": "需要定位权限以显示附近医院"
      }
    },
    "requiredPrivateInfos": ["getLocation"],//这个很重要根据需要添加["onLocationChange","startLocationUpdateBackground", "chooseAddress"]
}

2、报错USERKEY_PLAT_NOMATCH表示高德API Key与当前使用平台不匹配,可以到高德开放平台控制台重新添加key选择正确的服务平台,(或添加小程序域名设置白名单)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值