地图搜索看起来随机排序,其实没读相关性信息
2026/8/22 12:20:38 网站建设 项目流程

我第一次把地图文字搜索接到页面上时,做完最显眼的部分就停了:输入固定关键词,点按钮,拿到一串地点名称,按接口返回的先后渲染出来。列表出现的那一刻,我还挺满意。可同事看了一眼就问:“第一条为什么排在前面?”

我答不上来。

更准确地说,我当时并没有证据说明它为什么在前面。我只看到了一个数组,顺手把数组下标写成了#1#2#3。页面看起来像有排序,用户却只能猜排序规则。查询词固定为“华为”,位置固定在北京中心点,半径是 50000 米。哪怕这些条件都摆在那里,列表依然可能让人感觉像一把纸条被风吹过,落下来后恰好排成了现在的样子。

这篇只复盘一件小事:当搜索服务返回结果时,怎样把结果项里已有的相关性线索留在页面上;当服务没有返回时,又怎样老老实实停在状态和错误上。它不讨论地点是不是“正确答案”,也不讨论地图服务是否已经稳定可用。当前工程里,真实服务仍取决于认证与运行环境,不能拿一张列表的设计稿替它下结论。

现象很简单,误会也很容易发生

最初页面只展示名称和地址时,我会把第一个元素自然叫作“首条命中”。这句话听起来没有问题,实际上悄悄多走了一步:从“接口数组第一个元素”走到了“最值得信任的地点”。如果用户拿它去做门店跳转、地址回填,后半句就不该由我替服务说出口。

回到现有页面,SearchEvidenceItem明确留了ranksiteIdnameaddress和可选的reliability。这组字段很克制。rank只说明该项在这次展示数组中的位置;siteId让同名地点不至于混为一谈;address提供人工辨认的文字;reliability则是服务返回时可以显示的一条相关性线索。它们拼起来,才让“为什么先看到这条”有了可以继续检查的入口。

interface SearchEvidenceItem { rank: number; siteId: string; name: string; address: string; reliability?: number; } @State totalCount: string = '尚未返回'; @State searchItems: Array<SearchEvidenceItem> = [];

我一开始的错误猜测是,排序看着别扭,多半是ForEach渲染时把元素弄乱了。这个怀疑并非完全荒唐,列表 key 写得不好时确实会有视觉错位。但看回代码后,展示用的是${item.rank}-${item.siteId},而rank又是在sites.forEach中按index + 1生成的。页面没有另做排序,也没有悄悄按名称、距离重新排列。它只是把服务返回的数组逐条装进searchItems,再把这个数组显示出来。

因此,真正的空缺不是“排序代码错了”,而是我没有把返回项里的reliability读出来,也没有向使用者说明它只是线索。

先把调用路径摆在桌面上

runSearch()开始时先把页面写成“第 N 轮搜索中”,再清空上一次的搜索错误。随后组装固定参数,调用site.searchByText。返回成功时,代码遍历response.sites,而不是凭空生成样本;每一项的item.reliability被原样放进SearchEvidenceItem。如果该字段没有提供,页面通过reliabilityText显示“未提供”,没有用 0、100 或所谓默认分去填空。

点击执行真实 searchByText

searchState 写入第 N 轮搜索中

组装固定 query location radius

调用 site.searchByText

是否返回 response

读取 response.sites

逐项保存 rank siteId name address reliability

写入 searchItems 与 totalCount

列表显示位置和相关性线索

清空 searchItems

显示失败状态与真实错误

这张图里最容易被忽略的是totalCount。我原本只关心页面展示了几条,后来才发现这会制造另一个假象:接口报告的总数与本页展示的五条并不是同一个概念。参数固定pageIndex: 1pageSize: 5,因此列表项数最多代表这一页被展示出来的数量;totalCount是响应中单独返回的文字化记录。两者需要并排出现,不能拿“页面只有五条”推断“地图只找到五个地方”,也不能拿一个总数去替每一项解释先后次序。

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 }); }); this.totalCount = `${response.totalCount}`; this.searchItems = items;

当我把这段顺着读完,第二个猜测也被排除了:不是页面在用某个神秘规则洗牌。页面既不计算相关性,也不把它解释为距离,更不承诺数值越大就必然更适合业务。它做的事是保留服务给出的字段,并让结果列表不至于只有“第几条”这一种信息。

相关性不是装饰性的紫色文字

列表里那行reliability:${this.reliabilityText(item.reliability)},看上去只是辅助信息。可在排查时,它决定了我会不会把不确定的结果说得太满。

假如某次真实响应给出了若干项,我能记录:固定查询词是什么、当前是第几轮、页面本轮展示多少项、响应声明的totalCount是多少,以及每个地点是否携带reliability。这已经足够支持“排序有返回字段可供检查”这句话。它不支持“第一条一定离我最近”“第一条一定是用户要找的公司”“分数等于某个固定阈值就可以自动下单”。那些都需要额外业务规则、地点事实与运行材料,当前页面没有写。

反过来,若字段是undefinedreliabilityText返回“未提供”。我以前总觉得这种文案不够漂亮,想补一个 0 让卡片整齐。后来想明白了:0 是一个数值结论,未提供是信息没有到达。把后者改成前者,会让排查者误以为服务明确给了最低分,实际上代码只知道字段缺席。地图搜索里最危险的不是空白,而是把空白涂成一个很像答案的数字。

服务没有回来时,别把页面做成会讲故事的人

当前代码的失败分支很直白:searchItems = []totalCount = '调用失败,未返回'searchState写第 N 轮搜索失败,searchErrorMessage保存格式化后的真实错误。页面还把首条名称改为“调用失败,未返回”,并把相关性设回undefined。这是一种比“给你一组演示地点”更有用的失败表达,因为它告诉我:此刻没有结果可以解释。

} 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} 轮搜索失败,已保留真实错误` }; }

我会把这里的页面复测分成两段。第一段不依赖外部结果,只观察动作后的状态转换:点击后是否先出现搜索中,失败后是否清掉旧列表,错误文本有没有留在searchErrorMessage。第二段需要认证、网络和真实 Map 服务响应,才可以记录sitestotalCountreliability的实际值。第一段通过,不能冒充第二段已经完成;第二段没有材料,也不该反过来否定失败分支的页面处理。

重置为尚未搜索

确认 totalCount 为尚未返回

点击搜索

searchState 是否进入搜索中

检查 runSearch 是否被按钮调用

服务是否有真实响应

检查 searchItems 为空

检查 totalCount 和 searchErrorMessage

记录为等待或错误状态

核对展示数与 totalCount 分开记录

核对每项 reliability 为实际值或未提供

仅记录排序线索 不把首条写成地点事实

这个流程看起来比“有列表就截图”麻烦一点,却把问题拆开了。若searchState没进“搜索中”,先查按钮和函数;若进了搜索中又失败,查错误文本与环境;若有返回,再查字段是否被保留。每一步都有落点,至少不会因为最后空白就胡乱改样式。

复测后我留下的三条判断

第一,列表顺序本身是一个现象,不是理由。rank只记录这次展示的先后,不替服务解释业务含义。

第二,reliability有值时可以作为检查线索,未提供时就显示未提供。无论哪一种,都不能从页面字段直接推导“这个地点是真的”或“这个地点一定适合用户”。

第三,searchItems的长度和totalCount是两种不同记录。前者是当前页实际装入并展示的项目,后者是响应返回的总结果数。把它们混成一个数字,后面无论做分页还是人工核对都会很别扭。

我现在再看到“地图搜索看起来乱”的反馈,不会马上讨论算法,也不会急着给第一条加一个“推荐”角标。我会先问,页面有没有显示能解释结果的原始线索;再问,这一轮到底有没有真实响应。若答案还是没有,那就把状态和错误留在页面上,等环境补齐。地图搜索最朴素的诚实,是承认我们此刻只看到了什么,也承认还没有看到什么。

我还排除了三个看似合理的解释

第一个是“固定北京中心点,所以肯定按距离排”。页面确实构造了location: this.center,也设置了radius: 50000,但当前代码没有读取距离字段,也没有在客户端比较坐标,更没有sort。位置和半径是请求条件,不能被我偷换成已证实的排序规则。即使用户感觉某一项“应该更近”,那也只能成为进一步核对的理由,不是页面已经解释了顺序的证据。

第二个是“名称相同,第一条自然就是目标”。恰恰相反,同名是我更不敢直接采纳的原因。页面保留siteIdformatAddress,说明名称不足以独立承担辨认任务。把名称、地址和 ID 同时摆出来,目的不是把卡片做复杂,而是让人工在返回真的存在时能区分两个名称相近的候选。这里没有地址确认按钮,也没有业务表单回填,因此我不能把任何一个siteId写成最终地点。

第三个是“反正本页只展示五条,相关性不显示也无所谓”。这会让诊断失去方向。五条是pageSize: 5的展示上限,而非质量结论。没有reliability时,我只能说这一页按返回数组显示了五项;有该字段时,我可以额外记录服务给出的线索。两种情况都比给用户一个没有解释的序号更清楚,但谁也不能代替服务文档或实际运行材料解释分值算法。

这些排除有个共同点:不从请求参数、显示序号或单个字段跳到“排序已经被证明”。工程里常见的误会不是完全没有数据,而是刚有一点数据就把它解释得比它本身更多。回到代码的好处,是它会把我拉回事实边界:当前页保存了哪些字段,又明确没有保存哪些字段。

状态区为什么不能被结果卡片遮住

我还做过一次很容易误导人的操作:先尝试搜索,得到一个错误;接着改了页面样式,只留下结果卡片区域。这样一来,空列表看上去和“没有符合条件的地点”一模一样。直到我把searchStatesearchErrorMessage恢复到状态区,才知道二者根本不是同一件事。

searchState负责描述这轮请求处在什么阶段,searchErrorMessage负责保留失败的原因文本。前者可能是“调用成功,但 sites 为空”,后者仍是“暂无错误”;也可能前者是“搜索失败”,后者有格式化的运行错误。若只保留一个“暂无结果”,就会把成功空结果和调用失败混在一起。排查人会去修改关键词或半径,真正的问题却可能是认证或网络。

this.searchState = `第 ${nextRound} 轮搜索中`; this.searchErrorMessage = '暂无错误'; this.markOperation(`开始第 ${nextRound} 轮固定条件搜索`); // 成功后写入返回结果;失败时清空列表并保留格式化错误。

我现在复测时会先截状态区,再看结果区。因为结果区只能告诉我“现在展示了什么”,状态区才告诉我“为什么会是这个样子”。对于真实服务不可用的环境,这个顺序尤其重要:没有地点卡片不是文章的遗憾,而是页面应当留下的真实状态。

一次可复用的人工核对记录

如果以后环境拿到了真实响应,我不会只保存“首条名称”。我会在同一份记录里写下查询词、中心点、半径、语言、页码和 pageSize,再写searchRoundtotalCount、本页searchItems.length。每一项则记录 rank、name、siteId、address、reliability 是否提供。这样做不是为了制造一张漂亮表格,而是避免下次有人换了参数后,仍拿旧截图讨论排序。

这里还要把时间维度放稳。当前页记录了lastTriggerTime,它可帮助定位最近一次页面动作,但并没有为每个结果项生成独立时间戳。若需要比较多轮实际响应,应在外部测试记录中按轮次保存,而不是让页面当前这一份状态替我保存历史。页面展示的是现场仪表盘,不是历史仓库;不承认这点,后面最容易把上一轮结果错挂到下一轮。

所以我的复测结论很窄:页面在成功响应时能将数组位置、总数和reliability的提供情况显示出来;响应失败时能清空项目并显示错误。至于排序是否符合某个业务预期、地点是否真实、分数具体意味着什么,必须由合法服务响应、文档和人工判断共同补齐。把结论收窄不是退缩,而是让下一次真正拿到数据时,知道应该补哪一块。

再往前走一步,我会检查同一轮的页面文案是否自洽:有项目时不应仍显示“尚未返回”,失败时不应保留旧项目,字段未提供时不应用一个数值冒充。这些不是排序算法检查,却是排序解释进入页面之前最基本的卫生条件。状态干净,后续的真实响应才有被正确阅读的可能。

还有一点很容易漏:response.sites ?? []把未提供的数组收成空数组,并不自动说明“服务找到零个地点”。只有结合searchState、错误文本和实际响应,空列表才有可解释的语境。把空数组当零命中,是我过去最轻率的一种快捷判断。

这也要求我在截图时保留状态摘要,而不只截取列表区域;否则一张空白图会把不同原因压成同一种沉默。

本文依据当前MapSearchLongClickPage中的runSearchsearchItemstotalCount状态撰写。真实搜索排序、实际相关性值与地点结果,仍需在合法认证、网络可用的运行环境中单独复核。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询