一、场景需求
通过高德地图Web服务API实现:
- 获取用户实时位置
- 搜索半径5公里内的医疗机构
- 展示医院详细信息及距离
二、实现步骤
一、 注册开发者账号
- 访问高德开放平台
- 点击右上角"控制台" -> 选择"立即注册"
- 完成企业/个人实名认证(需身份证/营业执照)
二、创建新应用
- 登录控制台后进入「应用管理」
- 点击「创建新应用」按钮
- 填写应用信息:
- 应用名称:
医院导航系统
(示例) - 应用类型:根据需要选择类型「其他」
- 添加key:创建完点击右上角添加key,名称自己定义一个,服务平台这里选的Web服务,后面如果报错USERKEY_PLAT_NOMATCH就是这个服务平台不对,可以继续在添加符合服务的key就可以了
- 应用名称:
三、获取密钥
- 创建成功后可在「应用列表」查看
- Key格式示例:
2cd4564422e1221bced008196b4eac311
- 使用限制:
- 每日限额:默认5万次/日(可申请提升)
- QPS限制:50次/秒
四、设置小程序权限
- 在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选择正确的服务平台,(或添加小程序域名设置白名单)