标签: #HarmonyOS #MapKit #SiteKit #元服务 #ArkTS #LBS


🍱 前言:当 LBS 遇上万能卡片

传统的找店流程:解锁手机 -> 找 App -> 点击启动 -> 等广告 -> 搜索“美食” -> 筛选附近。
鸿蒙元服务流程:点亮屏幕 -> 看一眼桌面卡片

这背后的技术链路其实并不复杂:

  1. 定位:获取用户当前经纬度。
  2. 搜索 (Site Kit):根据坐标搜索周边 POI (兴趣点)。
  3. 展示 (Form):将数据渲染到轻量级的卡片上。
  4. 导航 (Map Kit):点击卡片,拉起地图进行导航。

数据流转图 (Mermaid):

1. 刷新卡片/定时更新
2. 获取经纬度
3. 返回坐标
4. 调用周边搜索 API
5. 返回餐厅列表 JSON
6. updateForm (更新数据)
7. 点击某餐厅

用户桌面

FormExtensionAbility

Location Kit

Huawei Site Kit

桌面卡片 UI

应用主页/地图页


🛠️ 一、 准备工作:AGC 配置

在使用华为地图服务前,必须在 AppGallery Connect (AGC) 上开通权限。

  1. 注册开发者账号:登录华为开发者联盟。
  2. 创建项目与应用:获取 client_id
  3. 开启 API:在“我的项目” -> “API管理”中,开启 Map KitSite Kit
  4. 配置工程:在 module.json5 中配置 client_id
"module": {
  "metadata": [
    {
      "name": "client_id",
      "value": "你的_CLIENT_ID" // ⚠️ 别忘了这个!
    }
  ]
}


📡 二、 核心逻辑:获取定位与周边搜索

我们需要在 EntryFormAbility (卡片的生命周期管理类) 或者后台 Service 中处理逻辑。为了演示清晰,我们直接看核心的 API 调用。

首先,申请权限 ohos.permission.LOCATIONohos.permission.APPROXIMATELY_LOCATION

1. 引入模块
import { site } from '@kit.MapKit'; // 鸿蒙 Next 中 Site Kit 已集成在 Map Kit 命名空间下
import { geoLocationManager } from '@kit.LocationKit';

2. 封装搜索函数

我们需要调用 Site Kit 的 nearbySearch 接口。

async function searchNearbyFood(lat: number, lng: number) {
  // 1. 构建搜索请求
  let searchRequest: site.NearbySearchRequest = {
    location: {
      latitude: lat,
      longitude: lng
    },
    query: "美食", // 搜索关键词
    radius: 1000,  // 搜索半径 1公里
    pageSize: 4,   // 卡片位置有限,只拿前4个
    pageIndex: 1,
    poiType: [site.LocationType.RESTAURANT] // 指定类型为餐厅
  };

  try {
    // 2. 调用华为 Site Kit
    let result = await site.nearbySearch(searchRequest);
    
    if (result.sites) {
      // 3. 转换数据格式,用于卡片渲染
      let foodList = result.sites.map(item => {
        return {
          name: item.name,
          address: item.formatAddress,
          distance: item.distance,
          // 如果有图片 url,也可以放这里
        };
      });
      return foodList;
    }
  } catch (err) {
    console.error("Search failed: " + JSON.stringify(err));
  }
  return [];
}


🖼️ 三、 界面实现:ArkTS 卡片 UI

卡片 UI (Widget) 和普通 Page 的写法略有不同,它更轻量。

FoodCard.ets:

@Entry
@Component
struct FoodCard {
  // 接收 FormExtensionAbility 传来的数据
  @LocalStorageProp('foodList') foodList: Array<any> = [];

  build() {
    Column() {
      Text('附近美食推荐')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .padding({ left: 12, top: 10, bottom: 5 })
        .width('100%')

      if (this.foodList.length === 0) {
        Text('正在定位中...').fontSize(12).margin(10)
      } else {
        // 使用 Grid 布局展示 4 个餐厅
        Grid() {
          ForEach(this.foodList, (item: any) => {
            GridItem() {
              Column() {
                // 模拟餐厅图标
                Image($r('app.media.icon_food')) 
                  .width(30)
                  .height(30)
                  .borderRadius(8)
                
                Text(item.name)
                  .fontSize(10)
                  .maxLines(1)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
                  .margin({ top: 4 })
                
                Text(`${item.distance}m`)
                  .fontSize(9)
                  .fontColor(Color.Gray)
              }
              // 点击事件:拉起主应用,并传递参数
              .onClick(() => {
                postCardAction(this, {
                  action: 'router',
                  abilityName: 'EntryAbility',
                  params: {
                    targetPage: 'MapPage',
                    poiName: item.name,
                    lat: item.location.latitude,
                    lng: item.location.longitude
                  }
                });
              })
              .backgroundColor('#F5F5F5')
              .borderRadius(8)
              .padding(8)
              .width('100%')
              .height(80)
            }
          })
        }
        .columnsTemplate('1fr 1fr 1fr 1fr') // 4列
        .columnsGap(8)
        .padding(12)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.White)
  }
}


🗺️ 四、 落地页:Map Kit 导航展示

当用户点击卡片上的餐厅时,跳转到 App 内的 MapPage。这里我们需要使用 Map Component 展示具体位置。

MapPage.ets:

import { MapComponent, mapCommon, map } from '@kit.MapKit';

@Entry
@Component
struct MapPage {
  @State mapController: map.MapComponentController = new map.MapComponentController();
  // 从路由参数中获取的目标坐标
  targetLat: number = 0;
  targetLng: number = 0;
  targetName: string = "";

  aboutToAppear() {
    // 获取路由参数逻辑 (略)
  }

  build() {
    Stack() {
      // 地图组件
      MapComponent({ 
        mapOptions: {
          position: {
            target: {
              latitude: this.targetLat,
              longitude: this.targetLng
            },
            zoom: 15 // 缩放级别
          }
        },
        mapCallback: (err, controller) => {
          if (!err) {
            this.mapController = controller;
            // 地图加载完成后,添加一个标记
            this.addMarker();
          }
        }
      })
      .width('100%')
      .height('100%')
    }
  }

  addMarker() {
    let markerOptions: mapCommon.MarkerOptions = {
      position: {
        latitude: this.targetLat,
        longitude: this.targetLng
      },
      title: this.targetName,
      clickable: true
    };
    this.mapController.addMarker(markerOptions);
  }
}


⚠️ 五、 避坑指南

  1. Client ID 报错:如果你发现地图白屏或者搜索报错 6004/010001,99% 是因为 module.json5 里的 client_id 没配对,或者指纹证书(SHA256)没在 AGC 后台录入。
  2. 定位延迟:在卡片上直接做实时定位(GPS)非常耗电且慢。最佳实践是:App 主程序启动时缓存最后一次位置,或者在 FormAbility 的 onUpdateForm 中使用“上次已知位置”快速刷新,再异步发起精确定位更新。
  3. 模拟器问题:DevEco Studio 的模拟器默认定位可能在海上。记得在模拟器的设置里手动 Mock 一个经纬度(比如深圳坂田)。

🎯 总结

通过 Site Kit 强大的数据能力和 Map Kit 的渲染能力,配合鸿蒙的 元服务卡片,我们用很低的代码成本,就实现了一个原生级的“周边美食猎手”。

这种“服务找人”的体验,正是 HarmonyOS Next 极力推崇的开发范式。

Next Step:
现在的卡片是静态刷新的。尝试引入 ArkTS 卡片动画,当数据加载出来时,给 GridItem 加一个渐入效果,让卡片看起来更灵动!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐