企业项目实施顾问视角:本文从现场服务点定位的实际困境出发,讲解如何在 HarmonyOS 6.1.1 中正确处理地图搜索结果不足或可信度低的情况,同时保留完整的搜索证据和人工地址交接的审计链路,确保现场工作的连续性和后续追溯的完整性。


一、企业场景与挑战

在这里插入图片描述

1.1 现场服务点定位的能力边界问题

在实际部署中,基于地图搜索的服务点定位往往面临以下困境:

  • 搜索结果不足:某些偏远地区、新建区域的兴趣点数据不完整
  • 可信度低:搜索返回的结果与现场实际位置偏差过大(如地名有误、坐标陈旧)
  • 结果混乱:同一名称多个记录,无法准确判断哪个是目标服务点
  • 联系信息缺失:搜索到的地点缺少电话、营业时间等关键业务信息

核心挑战:不能因为搜索结果不理想就导致派单流程中断。必须在以下两点间找到平衡:

  1. 搜索的真实性:记录下搜索的真实结果(共几条、可信度如何、返回了哪些信息)
  2. 流程的可用性:为现场人员提供人工确认或手动输入地址的替代方案

1.2 为什么简单的"提示补充地址"不够

许多实现采用简化方案:

搜索服务点 → 结果为空或不可信 → 提示用户"请手动输入地址" → 结束

这种方式的根本缺陷:

  1. 搜索证据丢失:系统无法证明"搜索确实返回了什么"或"确实没有可信候选"
  2. 流程层级混乱:无法区分"主动选择人工输入"和"被迫降级"
  3. 后续无法恢复:如果后续发现搜索结果有误,无法回溯原始搜索条件和返回值
  4. 交接单不可执行:现场人员凭记忆手输的地址往往存在笔误或歧义,后续派单人无法验证
  5. 审计链断裂:无法追溯"为什么这条服务点信息最终确定为这个地址"

1.3 企业实施的真实需求

现场服务点定位和交接必须满足以下要求:

  1. 搜索结果的完整记录:清晰地记下搜索条件、返回总数、前N条结果的详细信息
  2. 可信度的量化评估:不是"有结果"就认为成功,而是基于 reliability 等指标判断是否足够可信
  3. 人工介入的正当性:不是因为系统故障而降级,而是基于搜索结果的真实评估后做出的业务决策
  4. 地址交接单的可执行性:包含服务点名称、坐标、搜索来源、可信度、人工确认时间、操作员信息等关键字段
  5. 异常恢复的可能性:搜索条件改进后(如修正关键词)能否重新启动搜索流程
  6. 审计链的连续性:从搜索条件 → 搜索结果 → 结果评估 → 人工确认 → 地址交接,完整可追溯

二、核心技术概念与设计思路

在这里插入图片描述

2.1 地图搜索能力的关键指标

HarmonyOS 6.1.1 的 MapKit 中,服务点搜索涉及以下关键参数和返回值:

维度API / 字段含义示例值
搜索条件query搜索关键词"华为"
搜索位置location搜索中心点{lat: 39.9042, lng: 116.4074}
搜索范围radius搜索半径(米)50000
语言language返回结果的语言"zh-CN"
分页pageIndex, pageSize分页信息pageIndex: 1, pageSize: 5
返回总数totalCount服务点总数123
单条结果Site 对象包含名称、坐标、可信度等见下表

Site 对象的关键字段

字段类型含义可用性
siteIdstring地点的唯一标识总是有
namestring地点名称可能为空
formatAddressstring格式化地址可能为空
locationLatLng坐标可能为空
reliabilitynumber可信度分数可能未提供
其他字段电话、营业时间等可能缺失

关键发现reliability 字段不总是被返回,某些地区或类型的搜索结果可能没有这个值。

2.2 搜索结果的可信度评估模型

在这里插入图片描述

搜索返回结果后,不能直接当作"可用"使用,需要多维度评估:

interface SearchResultEvaluation {
  // 数量维度
  hasAnyResult: boolean;           // 是否有任何结果
  totalCount: number;              // 搜索返回的总数
  displayCount: number;            // 当前页显示的条数
  
  // 可信度维度
  hasReliabilityField: boolean;    // reliability 字段是否提供
  reliabilityDistribution: {       // 可信度分布
    high: number;      // reliability >= 80 的条数
    medium: number;    // 60 <= reliability < 80 的条数
    low: number;       // reliability < 60 的条数
    undefined: number; // 未提供 reliability 的条数
  };
  averageReliability: number;      // 平均可信度(如果有)
  firstReliability: number | null; // 第一条结果的可信度
  
  // 信息完整性维度
  hasAddressField: boolean;        // 地址字段是否完整
  hasPhoneField: boolean;          // 电话字段是否完整
  hasHoursField: boolean;          // 营业时间字段是否完整
  
  // 评估结论
  trustLevel: 'high' | 'medium' | 'low' | 'unavailable';
  recommendation: 'auto_use' | 'manual_confirm' | 'manual_input';
}

评估逻辑

IF 没有搜索结果 → trustLevel = 'unavailable' → recommendation = 'manual_input'

IF 有搜索结果:
  IF totalCount > 100 AND 第一条 reliability >= 80 AND 前3条都有 reliability
    → trustLevel = 'high' → recommendation = 'auto_use'
  
  ELSE IF totalCount > 10 AND 前3条都有 reliability >= 60
    → trustLevel = 'medium' → recommendation = 'manual_confirm'
  
  ELSE IF totalCount > 0 AND 前3条中至少有1条有完整信息
    → trustLevel = 'low' → recommendation = 'manual_confirm'
  
  ELSE
    → trustLevel = 'unavailable' → recommendation = 'manual_input'

2.3 长按Marker与POI的业务含义

地图上的交互操作对应不同的业务场景:

长按 Marker(示意点):

用户在地图上长按由系统添加的标记点
  ↓
触发 onMarkerLongClick 回调
  ↓
记录用户的交互时机和位置
  ↓
用途:确认"用户看到了这个点的位置,已进行人工视觉验证"

长按 POI(底图兴趣点):

用户在地图上长按底图上预置的兴趣点
  ↓
触发 onPoiLongClick 回调
  ↓
捕获用户长按的 POI 对象的详细信息
  ↓
用途:用户主动选择了一个地点作为备选服务点

关键差异

维度Marker 长按POI 长按
触发对象系统添加的标记底图预置的兴趣点
信息来源搜索结果之一地图底图数据
用户意图确认该点可见选择该点作为备选
后续流程可使用首选搜索结果需要验证该POI的详细信息
可信度与搜索结果一致可能与搜索结果不一致

2.4 人工地址交接单的完整结构

人工地址交接单不仅是"地址字符串",而是包含多个层次信息的完整记录:

interface ManualAddressHandoverRecord {
  // ========== 搜索阶段的证据 ==========
  searchEvidence: {
    query: string;                    // 搜索关键词
    location: { lat: number; lng: number }; // 搜索中心
    radius: number;                   // 搜索半径
    searchTime: string;               // 搜索执行时间
    totalCount: number;               // 搜索返回总数
    displayCount: number;             // 本页显示条数
    firstResult?: Site;               // 搜索的第一条结果(完整对象)
  };

  // ========== 评估阶段的结论 ==========
  evaluation: {
    trustLevel: 'high' | 'medium' | 'low' | 'unavailable';
    hasReliability: boolean;
    averageReliability?: number;
    recommendation: 'auto_use' | 'manual_confirm' | 'manual_input';
    evaluationTime: string;
  };

  // ========== 人工介入的决策 ==========
  manualDecision: {
    decision: 'confirmed_from_search' | 'selected_from_poi' | 'manual_input';
    selectedSite?: Site;              // 如果从搜索结果或POI中选择
    manualAddress?: string;           // 如果是人工输入
    confirmationTime: string;
    operatorId: string;               // 操作员ID
    operatorName: string;             // 操作员姓名
  };

  // ========== 最终交接信息 ==========
  handoverInfo: {
    serviceName: string;              // 最终确定的服务点名称
    address: string;                  // 最终确定的地址
    latitude: number;                 // 最终确定的纬度
    longitude: number;                // 最终确定的经度
    reliability?: number;             // 可信度(如果有)
    phoneNumber?: string;             // 联系电话(如果有)
    operatingHours?: string;          // 营业时间(如果有)
    handoverTime: string;             // 交接时间戳
  };

  // ========== 审计链 ==========
  auditTrail: {
    workOrderId: string;              // 工单编号
    batchId: string;                  // 批次ID
    retryCount: number;               // 搜索重试次数
    searchVariations: string[];       // 搜索的不同关键词列表
    finalRecommendation: string;      // 后续处理建议(如"需人工电话验证")
  };
}

三、完整的搜索与评估流程

在这里插入图片描述

3.1 搜索执行与结果处理

private async runSearch(): Promise<void> {
  // 第一步:更新状态
  const nextRound = this.searchRound + 1;
  this.searchState = `${nextRound} 轮搜索中`;
  this.searchErrorMessage = '暂无错误';
  this.markOperation(`开始第 ${nextRound} 轮固定条件搜索`);

  // 第二步:构建搜索参数
  const params: site.SearchByTextParams = {
    query: this.evidenceState.query,        // 固定查询词,如"华为"
    location: this.center,                  // 固定位置:北京中心 39.9042,116.4074
    radius: 50000,                          // 固定半径:50km
    language: 'zh-CN',                      // 固定语言:中文
    pageIndex: 1,                           // 固定分页:第一页
    pageSize: 5                             // 固定每页:5条结果
  };

  try {
    // 第三步:执行搜索
    const response = await site.searchByText(getContext(this), params);
    const sites = response.sites ?? [];
    
    // 第四步:结果转化
    const items: Array<SearchEvidenceItem> = [];
    sites.forEach((item: site.Site, index: number) => {
      items.push({
        rank: index + 1,
        siteId: item.siteId,
        name: item.name ?? '未提供名称',
        address: item.formatAddress ?? '未提供地址',
        reliability: item.reliability
      });
    });

    // 第五步:评估可信度
    const first = items.length > 0 ? items[0] : undefined;
    const trustLevel = this.evaluateTrustLevel(items, response.totalCount);

    // 第六步:状态更新
    this.searchRound = nextRound;
    this.totalCount = `${response.totalCount}`;
    this.searchItems = items;
    this.searchState = items.length > 0
      ? `${nextRound} 轮返回 ${items.length} 条展示结果`
      : `${nextRound} 轮调用成功,但 sites 为空`;

    // 第七步:更新证据
    this.evidenceState = {
      query: this.evidenceState.query,
      resultName: first?.name ?? '未返回结果',
      reliability: first?.reliability,
      markerLongClickCount: this.evidenceState.markerLongClickCount,
      poiLongClickCount: this.evidenceState.poiLongClickCount,
      lastEvent: `${nextRound} 轮搜索完成,首条 reliability=${this.reliabilityText(first?.reliability)}`
    };
    this.lastTriggerTime = timestamp();
  } catch (error) {
    // 异常处理路径
    this.searchRound = nextRound;
    this.searchItems = [];
    this.totalCount = '调用失败,未返回';
    this.searchState = `${nextRound} 轮搜索失败`;
    this.searchErrorMessage = formatRuntimeError(error as Error);
    this.evidenceState = {
      query: this.evidenceState.query,
      resultName: '调用失败,未返回',
      reliability: undefined,
      markerLongClickCount: this.evidenceState.markerLongClickCount,
      poiLongClickCount: this.evidenceState.poiLongClickCount,
      lastEvent: `${nextRound} 轮搜索失败,已保留真实错误`
    };
    this.lastTriggerTime = timestamp();
  }
}

// 可信度评估函数
private evaluateTrustLevel(items: SearchEvidenceItem[], totalCount: number): string {
  if (items.length === 0) return 'unavailable';
  
  const hasReliability = items.some(item => item.reliability !== undefined);
  if (!hasReliability) return 'low';
  
  const reliabilitiesPresent = items.filter(item => item.reliability !== undefined).map(item => item.reliability!);
  const averageReliability = reliabilitiesPresent.reduce((a, b) => a + b, 0) / reliabilitiesPresent.length;
  
  if (totalCount > 100 && averageReliability >= 80) return 'high';
  if (totalCount > 10 && averageReliability >= 60) return 'medium';
  return 'low';
}

// 格式化reliability为字符串
private reliabilityText(value?: number): string {
  return value === undefined ? '未提供' : `${value}`;
}

3.2 Marker 长按的捕获与记录

场景:用户在地图上长按系统添加的搜索结果标记点

// 在 onMapReady 回调中注册 Marker 长按监听
this.mapEventManager.onMarkerLongClick((marker: map.Marker) => {
  const position = marker.getPosition();
  
  // 更新状态:记录用户长按了Marker
  this.evidenceState = {
    query: this.evidenceState.query,
    resultName: this.evidenceState.resultName,
    reliability: this.evidenceState.reliability,
    markerLongClickCount: this.evidenceState.markerLongClickCount + 1,  // 计数加1
    poiLongClickCount: this.evidenceState.poiLongClickCount,
    lastEvent: `Marker 长按:${marker.getTitle()} @ ${position.latitude},${position.longitude}`
  };
  
  this.lastTriggerTime = timestamp();
  this.operationSequence += 1;
  this.lastOperationId = `MAP-${this.operationSequence}`;
});

业务含义:用户确认了"这个搜索返回的结果在地图上的位置是可见的",这是人工视觉验证的证据。

3.3 POI 长按的捕获与决策

场景:用户在地图上长按底图预置的兴趣点

// 在 onMapReady 回调中注册 POI 长按监听
this.mapEventManager.onPoiLongClick((poi: mapCommon.Poi) => {
  // 用户选择了一个底图上的POI
  this.evidenceState = {
    query: this.evidenceState.query,
    resultName: this.evidenceState.resultName,
    reliability: this.evidenceState.reliability,
    markerLongClickCount: this.evidenceState.markerLongClickCount,
    poiLongClickCount: this.evidenceState.poiLongClickCount + 1,  // 计数加1
    lastEvent: `POI 长按:${poi.name} (${poi.id}) @ ${poi.position.latitude},${poi.position.longitude}`
  };
  
  this.lastTriggerTime = timestamp();
  this.operationSequence += 1;
  this.lastOperationId = `MAP-${this.operationSequence}`;
  
  // 后续流程:判断该POI是否应作为人工地址交接的候选
  this.handlePoiSelection(poi);
});

private handlePoiSelection(poi: mapCommon.Poi): void {
  // 判断:POI是否与搜索结果匹配
  const matchedInSearchResults = this.searchItems.some(item => 
    item.name === poi.name || this.distanceBetweenCoordinates(
      { lat: poi.position.latitude, lng: poi.position.longitude },
      this.center
    ) < 1000  // 距离小于1km认为可能是同一地点
  );
  
  if (matchedInSearchResults) {
    // POI在搜索结果中也出现过,可信度较高
    this.statusMessage = `用户确认POI"${poi.name}"与搜索结果匹配,可纳入地址交接`;
  } else {
    // POI是底图上的新发现,需要额外验证
    this.statusMessage = `用户选择了底图上的POI"${poi.name}"作为备选,建议人工验证`;
  }
}

业务含义:用户可能发现了与搜索结果不同的地点,或者确认了搜索结果在底图上的具体位置。这种情况需要额外的人工验证步骤。

3.4 人工地址交接的决策树

搜索执行
  ↓
[评估可信度]
  ├─ trustLevel = 'high' (自动搜索可用)
  │   ├─ 用户可直接确认首选结果
  │   └─ 形成自动交接单(最小人工介入)
  │
  ├─ trustLevel = 'medium' (需要人工确认)
  │   ├─ 展示搜索结果列表
  │   ├─ 允许用户:
  │   │   ├─ 从搜索结果中选择 → 形成"确认搜索结果"交接单
  │   │   ├─ 长按地图上的POI → 形成"POI替换"交接单
  │   │   └─ 手动输入新地址 → 形成"人工输入"交接单
  │   └─ 记录用户选择的原因
  │
  ├─ trustLevel = 'low' (结果不可信)
  │   ├─ 提示用户"搜索结果可信度低"
  │   ├─ 允许用户:
  │   │   ├─ 修改搜索关键词重新搜索
  │   │   ├─ 长按地图上的POI → 形成"底图POI"交接单
  │   │   └─ 手动输入地址 → 形成"人工输入"交接单
  │   └─ 记录原始搜索条件供后续回溯
  │
  └─ trustLevel = 'unavailable' (无搜索结果)
      ├─ 提示用户"未搜索到结果"
      ├─ 建议用户:
      │   ├─ 修改关键词重新搜索
      │   ├─ 扩大搜索范围重新搜索
      │   ├─ 长按地图上的POI → 形成"底图POI"交接单
      │   └─ 手动输入地址 → 形成"人工输入"交接单
      └─ 记录搜索失败的原因

四、异常与边界处理

4.1 搜索条件改变时的处理

场景:用户想要改变搜索关键词或搜索位置

private updateSearchQuery(newQuery: string): void {
  // 检查是否需要清理前次搜索结果
  if (this.evidenceState.query !== newQuery) {
    // 关键词改变,需要清理前次搜索的所有状态
    this.searchItems = [];
    this.totalCount = '尚未返回';
    this.searchState = '搜索条件已改变,等待新搜索';
    this.searchErrorMessage = '暂无错误';
    
    // 但保留前次搜索的审计记录(用于回溯)
    this.previousSearchRecords.push({
      query: this.evidenceState.query,
      round: this.searchRound,
      totalCount: this.totalCount,
      timestamp: this.lastTriggerTime,
      results: this.searchItems
    });
  }
  
  this.evidenceState.query = newQuery;
  this.searchRound = 0;
}

private previousSearchRecords: Array<SearchRecord> = [];

关键原则

  1. 改变搜索条件后,前次搜索结果被清空,但审计记录被保留
  2. 保留完整的搜索历史,支持后续分析"为什么最终选择了这个地址"
  3. 如果用户多次搜索后才确定地址,所有搜索尝试都被记录在交接单中

4.2 搜索超时与网络异常

场景:搜索API调用超时或返回错误

private async runSearch(): Promise<void> {
  try {
    const response = await site.searchByText(getContext(this), params);
    // ... 成功处理
  } catch (error) {
    // 网络异常、超时、无权限等
    this.searchRound = nextRound;
    this.searchItems = [];
    this.totalCount = '调用失败,未返回';
    this.searchState = `${nextRound} 轮搜索失败`;
    this.searchErrorMessage = formatRuntimeError(error as Error);
    
    // 错误信息保留在状态中,用户可以查看
    this.statusMessage = `搜索失败:${this.searchErrorMessage}。请检查网络连接后重试。`;
    
    // 交接单中应包含"搜索异常"的标记
  }
}

恢复策略

  1. 清晰地展示搜索失败的原因
  2. 允许用户重新尝试搜索
  3. 如果搜索确实无法进行,允许用户手动输入地址
  4. 在交接单中记录"搜索异常原因",供后续跟进

4.3 Marker/POI 回调未到达

场景:用户在地图上长按,但回调迟到或未到达

// 当前实现:以时间戳记录每次操作
private markOperation(event: string): void {
  this.operationSequence += 1;
  this.lastOperationId = `MAP-${this.operationSequence}`;
  this.lastTriggerTime = timestamp();
  this.evidenceState = {
    // ... 更新状态
    lastEvent: event
  };
}

// 如果需要更严格的回调超时检测,可以添加:
private checkCallbackTimeout(): void {
  const now = new Date().getTime();
  const lastEventTime = new Date(this.lastTriggerTime).getTime();
  const elapsed = now - lastEventTime;
  
  if (elapsed > 5000) {  // 5秒无新事件
    this.statusMessage = '回调等待时间过长,建议补充人工地址输入';
  }
}

处理原则

  1. 不依赖于"是否收到回调"来判断操作成功
  2. 基于最后一次记录的操作状态来判断
  3. 允许用户跳过地图交互,直接进入手动输入流程

五、地址交接单的生成与验收

5.1 从多源信息生成交接单

交接单的内容应该来自多个源头,每个源头都有明确的优先级:

private generateHandoverRecord(): HandoverRecord {
  return {
    // ===== 搜索阶段证据 =====
    searchEvidence: {
      query: this.evidenceState.query,
      location: this.center,
      radius: 50000,
      searchTime: this.lastTriggerTime,
      totalCount: parseInt(this.totalCount) || 0,
      displayCount: this.searchItems.length,
      firstResult: this.searchItems.length > 0 ? this.searchItems[0] : undefined
    },

    // ===== 可信度评估 =====
    evaluation: {
      trustLevel: this.searchItems.length === 0 ? 'unavailable' : 'low',
      hasReliability: this.searchItems.some(item => item.reliability !== undefined),
      averageReliability: this.calculateAverageReliability(),
      recommendation: this.searchItems.length === 0 ? 'manual_input' : 'manual_confirm'
    },

    // ===== 人工决策 =====
    manualDecision: {
      decision: 'manual_input',  // 或其他类型
      manualAddress: '',         // 用户输入的地址
      confirmationTime: timestamp(),
      operatorId: this.operatorId,
      operatorName: this.operatorName
    },

    // ===== 最终交接信息 =====
    handoverInfo: {
      serviceName: '',
      address: '',
      latitude: 0,
      longitude: 0,
      handoverTime: timestamp()
    },

    // ===== 审计链 =====
    auditTrail: {
      workOrderId: this.workOrderId,
      batchId: this.batchId,
      retryCount: this.searchRound,
      searchVariations: this.searchVariations,
      finalRecommendation: '需人工电话验证'
    }
  };
}

5.2 验收标准检查

private validateHandoverRecord(record: HandoverRecord): ValidationResult {
  const errors: string[] = [];
  
  // 检查:搜索证据是否完整
  if (!record.searchEvidence.query) {
    errors.push('缺少搜索关键词');
  }
  if (record.searchEvidence.totalCount === undefined) {
    errors.push('缺少搜索总数');
  }
  
  // 检查:评估结论是否存在
  if (!record.evaluation.trustLevel) {
    errors.push('缺少信任级别评估');
  }
  
  // 检查:人工决策是否明确
  if (!record.manualDecision.decision) {
    errors.push('缺少人工决策类型');
  }
  if (record.manualDecision.decision === 'manual_input' && !record.manualDecision.manualAddress) {
    errors.push('人工输入决策但缺少地址内容');
  }
  
  // 检查:最终交接信息是否完整
  if (!record.handoverInfo.serviceName) {
    errors.push('缺少服务点名称');
  }
  if (!record.handoverInfo.address) {
    errors.push('缺少最终地址');
  }
  if (record.handoverInfo.latitude === 0 && record.handoverInfo.longitude === 0) {
    errors.push('坐标未设置或为默认值');
  }
  
  // 检查:审计链是否完整
  if (!record.auditTrail.workOrderId) {
    errors.push('缺少工单编号');
  }
  
  return {
    isValid: errors.length === 0,
    errors,
    warnings: this.generateWarnings(record)
  };
}

private generateWarnings(record: HandoverRecord): string[] {
  const warnings: string[] = [];
  
  if (record.evaluation.trustLevel === 'low' && record.manualDecision.decision === 'manual_input') {
    warnings.push('原搜索结果可信度低且用户选择了手动输入,建议双重验证');
  }
  
  if (record.auditTrail.retryCount > 3) {
    warnings.push('搜索重试次数过多,建议人工审核搜索条件');
  }
  
  return warnings;
}

六、用户界面与交互流程

6.1 搜索与结果展示界面

左侧:搜索参数与控制

固定查询词:华为
搜索轮次:3
总结果数:123

[执行真实 searchByText] 按钮

中央:搜索结果列表

┌─ 搜索结果 ──────────────────────────┐
│ 未返回时不生成演示分数。           │
│                                    │
│ #1 华为北京总部                   │
│   reliability:95                  │
│   siteId:site-12345              │
│   北京市朝阳区深美路1号           │
│                                    │
│ #2 华为云服务中心                 │
│   reliability:87                  │
│   siteId:site-12346              │
│   北京市海淀区中关村大街          │
│                                    │
│ #3 华为体验店                     │
│   reliability:未提供              │
│   siteId:site-12347              │
│   北京市东城区王府井大街          │
└────────────────────────────────────┘

右侧:地图与长按交互

┌─ 真实地图与测试 Marker ──────────────┐
│ ┌──────────────────────────────────┐ │
│ │   [真实地图预览,北京中心]       │ │
│ │   [添加的Marker标记点]           │ │
│ │   [用户可长按Marker或POI]        │ │
│ └──────────────────────────────────┘ │
│                                      │
│ 请在地图中真实长按 Marker,         │
│ 并选择一个底图 POI 长按。           │
│                                      │
│ Marker 长按:5 次                    │
│ POI 长按:2 次                       │
│                                      │
│ 最后事件:POI 长按:华为研究院     │
│ 触发时间:2026-08-15 14:30:45      │
└──────────────────────────────────────┘

6.2 地址交接的决策界面

┌─ 人工地址交接 ─────────────────────────┐
│                                        │
│ 当前搜索状态:第3轮,123条结果        │
│ 首条结果可信度:95                     │
│                                        │
│ [从搜索结果选择] [从POI选择]           │
│ [手动输入地址]                         │
│                                        │
│ ──── 或 ────                           │
│                                        │
│ 修改搜索条件重新搜索:                 │
│ 搜索关键词:[___________]             │
│ [执行新搜索]                           │
│                                        │
│ ──── 或 ────                           │
│                                        │
│ 使用底图POI:                          │
│ 长按地图上的POI,系统将记录选择       │
│ 上次POI选择:华为研究院(已记录)       │
│ [使用该POI生成交接单]                  │
└────────────────────────────────────────┘

七、实施验证与测试清单

7.1 搜索能力的验收标准

  • 搜索成功返回结果

    • 关键词"华为"在北京中心搜索,返回>10条结果
    • totalCount 显示正确
    • 前3条结果都包含 siteId 和 name
  • 可信度字段处理

    • 当 reliability 有值时,正确显示数字
    • 当 reliability 无值时,显示"未提供"
    • 多条结果混合有/无 reliability 的情况下,正确统计
  • 搜索失败处理

    • 网络异常时,错误信息清晰显示
    • 允许用户重新搜索
    • 异常状态下不生成交接单

7.2 地图交互的验收标准

  • Marker 长按

    • 长按系统添加的Marker,触发 onMarkerLongClick 回调
    • 计数器准确加1
    • 最后事件记录中包含Marker的名称和坐标
  • POI 长按

    • 长按地图底图上的POI,触发 onPoiLongClick 回调
    • 计数器准确加1
    • 最后事件记录中包含POI的名称和ID
  • 多次交互

    • 多次长按同一元素或不同元素,计数器持续累加
    • 最后事件始终记录最新的交互

7.3 地址交接单的验收标准

  • 交接单完整性

    • 包含搜索证据(关键词、总数、前N条结果)
    • 包含评估结论(可信度等级、建议)
    • 包含人工决策(选择来源、时间、操作员)
    • 包含最终交接信息(名称、地址、坐标)
    • 包含审计链(工单号、批次、重试次数)
  • 多源地址支持

    • 从搜索结果中选择的地址能形成"确认搜索"交接单
    • 从POI中选择的地址能形成"POI替换"交接单
    • 手动输入的地址能形成"人工输入"交接单
    • 每种交接单都包含原始来源信息
  • 异常恢复

    • 多次搜索后最终选择的地址,交接单包含所有搜索历史
    • 用户修改关键词搜索,前次结果被保留在审计链中
    • 用户选择POI时,系统记录"为什么选择这个而不是搜索结果"

八、常见问题与应急处理

Q1: reliability 字段什么时候为空?

A: reliability 的返回取决于多个因素:

  1. 地区数据覆盖:某些偏远地区的兴趣点数据可能不完整
  2. 搜索类型:不同类型的POI(如餐厅vs停车场)的reliability填充率不同
  3. 地图版本:不同版本的地图底图数据可能返回字段不一致
  4. 第三方数据源:某些POI来自第三方,该来源可能没有可信度评分

建议

  • 不依赖 reliability 字段的存在
  • 设计"无 reliability"的降级方案
  • 可信度评估应该多维度,不只看单一字段

Q2: 搜索结果可信度为90+,用户还是选择了POI,为什么?

A: 这反映了用户的现场实际情况:

  1. 搜索结果偏差:即使可信度高,坐标可能仍有几百米的偏差
  2. 用户视觉验证:用户在地图上看到的实际位置与搜索结果标记不符
  3. 信息不同步:搜索数据陈旧,POI是用户现场拍照或GPS确认的

处理

  • 记录用户的选择和原因,不要强制使用高可信度结果
  • 交接单中明确标注"用户覆盖搜索结果"的决策
  • 后续可用这类信息反馈地图服务商改进坐标精度

Q3: 如何防止人工输入地址时出现笔误?

A: 多层防护:

  1. 输入时提示:用户输入时,系统调用 searchByText API 返回匹配建议
  2. 交接单验证:交接单生成时,使用地址反向编码验证坐标有效性
  3. 人工复核:交接单生成后,由上级审核人再次核对
  4. 后续反馈:派单执行后,现场反馈该地址是否准确

Q4: 搜索返回总数100+,但本页只展示5条,用户如何了解其他结果?

A: 根据可信度分层策略:

  1. 高可信度:只展示前5条,因为前5条通常是最相关的
  2. 需要看更多:提供"加载更多"或"修改搜索条件"的入口
  3. 不强制翻页:因为大多数情况下,用户需要的就在前几条中

Q5: 地址交接单生成后,如何避免被误删除?

A: 交接单应该有生命周期管理:

enum HandoverRecordStatus {
  DRAFT = 'draft',          // 草稿,可编辑删除
  CONFIRMED = 'confirmed', // 已确认,不能删除
  SUBMITTED = 'submitted', // 已提交,只读
  CLOSED = 'closed'       // 已关闭,归档
}
  • 交接单一旦确认(CONFIRMED),不能在页面上直接删除
  • 需要返回上一步重新编辑(生成新的DRAFT)
  • 所有状态变迁都被记录在审计链中

九、扩展建议

9.1 搜索条件的智能建议

// 当搜索结果为空时,自动建议相似关键词
private suggestAlternativeQueries(failedQuery: string): string[] {
  return [
    failedQuery.slice(0, -1),      // 删除末尾字符
    failedQuery + '总部',           // 添加常见后缀
    failedQuery.replace(//, ''),  // 简化地名
    failedQuery + '分公司'          // 添加其他后缀
  ];
}

9.2 POI 与搜索结果的自动匹配

private matchPoiWithSearchResults(poi: mapCommon.Poi): SearchEvidenceItem | null {
  // 基于地理位置、名称相似度等因素匹配
  for (const item of this.searchItems) {
    const nameSimilarity = this.calculateStringSimilarity(poi.name, item.name);
    const distance = this.calculateDistance(
      { lat: poi.position.latitude, lng: poi.position.longitude },
      this.center
    );
    
    if (nameSimilarity > 0.8 && distance < 500) {  // 名称相似+位置接近
      return item;
    }
  }
  return null;
}

9.3 多源地址融合

// 当搜索结果、POI、用户输入都存在时,进行融合
private fuseMultipleSources(
  searchResult?: SearchEvidenceItem,
  poiResult?: mapCommon.Poi,
  manualInput?: string
): FusedAddress {
  // 优先级:搜索结果 > POI > 用户输入
  // 但记录所有来源信息,支持后续回溯
}

总结

HarmonyOS 6.1.1 中的地图搜索与人工地址交接需要遵循以下核心原则:

  1. 完整的搜索证据:记录搜索条件、返回结果、可信度等所有信息
  2. 多维度可信度评估:不仅看结果数量,还看 reliability、信息完整性等
  3. 明确的降级路由:根据可信度决定是自动用、需要确认、还是需要人工输入
  4. 用户的现场判断权:即使搜索结果可信度高,用户通过地图长按选择的POI也应被记录和尊重
  5. 完整的交接单:包含搜索证据、评估结论、人工决策、最终地址、审计链等多个层次的信息
  6. 可追溯性:从搜索条件改变到最终地址确定的完整链路都被记录

通过这套设计,现场服务点定位系统可以优雅地处理搜索能力的不足,同时保持完整的业务可追溯性和流程连续性。


验证状态:✅ 本文对应的代码已集成到项目,所有搜索、结果展示、Marker/POI长按、地址交接流程均在 MapSearchLongClickPage.ets 中完整实现。

后续复拍建议:计划在后续版本中支持搜索条件智能建议、POI与搜索结果自动匹配、多源地址融合等高级功能。

必要条件|模拟器与真机准备对照

条件API 24 模拟器HarmonyOS 6.1.1 真机
SDK/API与构建工具使用 API 24 镜像验证构建和基础页面使用兼容 API 24 的签名包安装
Kit引入先确认编译期 Kit 类型可用再确认设备运行时模块实际可用
模块/页面配置页面路由和 Stage 启动可验证页面路由、签名和设备安装状态均需验证
权限可演练授权弹窗和拒绝分支需重新授权并确认系统设置中的真实状态
系统能力/硬件只能代表模拟器提供的能力Camera、麦克风、地图、视觉识别等以真机能力为准

SDK/API 对照完成后插入 DevEco Studio API 24 与构建配置截图:

在这里插入图片描述

版本和能力对照完成后插入设备/模拟器信息截图:

在这里插入图片描述

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐