怎么用 这是格式与深度的参考,不是让你照抄的模板。数字全部是示例,直接抄进简历一问就穿——必须换成你自己的真实数据,而且要能说清是怎么测出来的。下面每个模块都有「数字是怎么测的」和「面试追问」两段,写不出那两段的模块就不要往简历上写

面试题库(电商 / 短视频 / 社区 / 本地生活 / 旅游 / 企业服务 多业务场景)

实习级这一档是干什么的 实时比价与库存刷新、复杂打包产品的选购流、地图导览这些核心台面,实习生大概率碰不到。这一档收的是出行客户端里实习生真的会被分派、但仍然有明确技术含量的活。下面三个模块彼此独立——只做过一个就只写一个。
怎么讲才不显得 low 「我做了个多语言切换,加了个语言包」和「英文文案普遍比中文长,按中文排版做的固定宽度按钮换成英文直接撑破;而且日期不能硬拼「X年X月X日」,切到英文要用当地格式;图片里的文字根本翻不了,得改成文字叠加」——同一件事,后者面试官会顺着追问。这一档的破解办法是认清「用户在旅途中的处境和在家里完全不同」:可能在境外没网、可能在另一个时区、可能看不懂中文、手机可能快没电,所以离线、时区、语言这些平时的边角问题在这里是主线。
三条自检 一、能说出不这么做会怎样(文案长度不做弹性布局会撑破、凭证不能离线看境外用户打不开、提醒时间按手机时区算会提前或延后几小时);二、能说出你踩过的具体坑;三、能说出量级(几种语言多少条文案、语言包多大、凭证缓存多久)。三条都有就能写。
项目背景设定 在线旅游平台的用户端(uni-app,微信小程序 + iOS/Android),Vue 3 组合式 API。我负责的是多语言与多币种、行程凭证的离线查看、出行前提醒这三块「不在选购下单主链路上、但直接决定用户在旅途中好不好用」的功能。
为什么这三块值得写 它们的技术含量来自用户在旅途中的真实处境:在境外可能没有网络、在另一个时区、看不懂中文、手机电量紧张。这些条件在开发机上一个都不成立,所以按常规思路做出来的功能在真实旅途中会失效——凭证打不开、提醒时间错了几小时、界面被长文案撑破。三块共同回答的是「怎么让功能在使用者的处境下仍然可用」。

模块一:多语言与多币种展示

  1. 多语言与多币种展示(文案长度差异下的弹性布局 + 日期数字与货币的本地化格式 + 语言包按需加载与缺失降级 + 服务端内容的语言协商)★★★
    简历这样写 客户端多语言与多币种(uni-app + Vue 3 组合式 API + 语言包按需加载 + 本地化格式化):接入多语言时发现按中文排版的固定宽度控件在其他语种下被文案长度撑破,逐页改为弹性布局与文本溢出策略并建立「以最长语种验收」的自查流程;日期、时间、数字、货币原为字符串硬拼(如年月日拼接),改为统一格式化入口按语言输出;语言包按需加载并做缺失键降级(回退默认语种而非显示键名),主包体积不随语种数量线性增长;语言选择持久化并随请求带给服务端,解决界面已切换但服务端返回内容仍为中文的问题;币种展示标注换算说明与实际结算币种,避免用户按展示币种理解最终扣款。同时把散落各页的文案收敛为统一键值结构,新增语种不再需要逐页改动。
    展开完整拆解
    为什么要这么设计

    接多语言这个需求下来的时候,我以为就是「把中文抽成键值、加几份语言包、切换的时候换一份」。做到一半发现问题远不止这些,四类。

    一是布局被撑破。我们的界面是按中文排版的,中文很紧凑,四个字的按钮换成英文可能是十几个字符。结果是按钮文字换行、导航栏挤在一起、卡片上的标题溢出遮住旁边的价格。这不是几个页面的问题,是几乎每一页都要检查。

    二是日期和数字是字符串硬拼的。代码里到处是把年月日拼成中文格式的写法。切到英文之后还是中文格式——而且这些拼接散落在几十个地方,没法统一改。

    三是界面切了语言,服务端返回的内容还是中文。产品名称、政策说明、退改规则这些内容都来自服务端。我只做了前端文案的切换,请求里没有带语言标识,所以用户看到的是「英文的界面框架 + 中文的产品信息」,比全中文更糟(他会以为翻译坏了)。

    四是币种切换让用户误解了扣款金额。我做了个币种切换,把价格按汇率换算展示。但没有说明「这是参考换算、实际按某个币种结算」。有用户按展示的币种理解最终扣款,实际扣款因为汇率和结算币种不同而有差异,来投诉。

    所以四个改动:逐页改弹性布局并建立以最长语种验收的流程日期数字货币收敛到统一格式化入口语言选择持久化并随请求带给服务端币种展示标注换算说明与实际结算币种,另外把散落的文案收敛成统一键值结构并做按需加载与缺失降级

    这个模块最想说的一句话是:多语言的工作量大头不在翻译,在「原来的实现里隐含了多少对中文的假设」。固定宽度隐含了「文案就这么长」、字符串拼接隐含了「日期就是这个格式」、不传语言参数隐含了「服务端内容只有一种语言」。这些假设在只有中文的时候完全看不出来,加第二种语言时全部暴露——所以它的难点是「找出全部隐含假设」,而不是「写一个切换函数」。

    整体链路
    文案组织 │ ├─ 散落的中文字符串收敛为统一键值结构(按模块分组) │ 散落的后果:新增语种要逐页改,必然漏 │ ├─ 键名要语义化并带模块前缀,避免不同模块的键冲突 │ ├─ 带变量的文案用占位符而不是字符串拼接 │ 拼接的问题:不同语言的语序不同,拼出来的句子不通 │ 例:数量在前还是在后,各语言不一样 │ └─ 复数与量词差异要交给格式化处理,不要写死 中文没有复数变化,其他语言有 —— 这是最容易漏的一类 语言包加载 │ ├─ 默认语种打进主包,其他语种按需加载 │ 全部打进主包的后果:语种越多包越大(小程序有体积限制) │ ├─ 加载失败 → 回退默认语种并提示,不要白屏 │ └─ 缺失键降级:回退默认语种的文案,不要显示键名 显示键名的后果:用户看到一串英文标识,像是程序出错 格式化(收敛到统一入口) │ ├─ 日期时间:按语言输出对应格式,不要字符串拼接 │ 硬拼的后果:切了语言还是中文格式,且散落几十处无法统一改 │ ├─ 数字:千分位与小数点符号在不同语言里不同 │ ├─ 货币:符号位置与格式按语言与币种共同决定 │ └─ 时长与相对时间(还有 3 天)同样要走格式化 这一类最容易被漏,因为它看起来像业务文案 布局适配(工作量最大的一块) │ ├─ 去掉按中文长度定的固定宽度,改为弹性布局 │ 固定宽度的后果:英文按钮文字换行 / 标题溢出遮住价格 │ ├─ 长文案的溢出策略:换行 · 截断加省略 · 缩小字号(按场景选) │ ├─ 图片里的文字无法翻译 → 改为图片加文字叠加 │ 这一条容易被忽略,但运营图上常有文字 │ └─ 验收流程:以「最长语种」过一遍全部页面 按中文验收的问题:中文最短,什么都看不出来 服务端内容的语言协商 │ ├─ 语言选择持久化到本地 │ ├─ 每次请求带上语言标识,服务端返回对应语言的内容 │ 不带的后果:英文界面 + 中文产品信息,比全中文更糟 │ ├─ 服务端无对应语言的内容 → 返回默认语言并标识 │ 前端据此可提示「该内容暂无当前语言版本」 │ └─ 切换语言不应丢失当前页面状态(不要整体重启) 币种展示 ├─ 明确标注:换算仅供参考 · 实际结算币种 · 汇率参考时间 ├─ 不标注的后果:用户按展示币种理解扣款,实际有差异会投诉 └─ 支付环节必须展示实际结算币种与金额(不能只显示换算值)
    分步拆解
    1. 先把散落的中文字符串收敛为统一键值结构。不收敛的话新增语种要逐页改,必然漏掉某处——而漏掉的那处会以中文的形式出现在英文界面里。
    2. 带变量的文案要用占位符,不要字符串拼接。不同语言的语序不同,拼出来的句子不通(数量在前还是在后各语言不一样)。
    3. 复数与量词差异要交给格式化处理。中文没有复数变化,其他语言有——这是中文母语开发者最容易漏的一类。
    4. 默认语种打进主包,其他按需加载。全部打进主包的话语种越多包越大,而小程序有体积限制。
    5. 语言包加载失败要回退默认语种并提示,不要白屏。网络问题导致整个界面没有文案是最糟的失败方式。
    6. 缺失键要降级到默认语种的文案,不要显示键名。显示键名的后果是用户看到一串英文标识,像是程序出错。
    7. 日期时间数字货币必须收敛到统一格式化入口。硬拼的后果是切了语言还是中文格式,而且散落几十处无法统一改——这是我踩的最费时间的一个坑。
    8. 时长与相对时间(「还有 3 天」)同样要走格式化。这一类最容易漏,因为它看起来像业务文案而不是格式化。
    9. 去掉按中文长度定的固定宽度,改弹性布局。这是工作量最大的一块——几乎每一页都要检查。
    10. 长文案要按场景选溢出策略(换行、截断省略、缩小字号)。没有一个策略适用所有场景:按钮适合缩小字号、列表标题适合截断、正文适合换行。
    11. 图片里的文字无法翻译,要改成图片加文字叠加。这一条容易被忽略,但运营图上常有文字。
    12. 验收必须以「最长语种」过一遍全部页面。按中文验收的问题是中文最紧凑,什么问题都看不出来——这个流程比逐个修 bug 更重要。
    13. 语言选择要持久化并随每次请求带给服务端。不带的后果是英文界面加中文产品信息,比全中文更糟(用户会以为翻译坏了)。
    14. 服务端无对应语言内容时要返回默认语言并标识。前端据此提示「该内容暂无当前语言版本」,比默默给中文要好。
    15. 切换语言不应丢失当前页面状态。整体重启应用最简单,但用户正在填的表单、正在看的位置都会丢。
    16. 币种换算必须标注「仅供参考、实际结算币种、汇率参考时间」。不标注的后果是用户按展示币种理解扣款,实际有差异会投诉。
    17. 支付环节必须展示实际结算币种与金额。只显示换算值是不可接受的——那是用户真正要付的钱。
    关键决策与取舍

    语言包按需加载而不是全部打进主包。全部打进最简单、切换零延迟,但小程序有包体积限制,语种数量一多就顶到上限。按需加载的代价是切换时有一次加载(要处理加载态和失败回退)。判据是「语种数量会不会继续增加」——会的话就必须按需,否则每加一个语种都要重新评估包体积。

    缺失键降级到默认语种,而不是显示键名或者空白。显示键名对开发调试友好(一眼看出哪个键漏了),但对用户是灾难(看到一串标识以为程序坏了)我的处理是:正式环境降级到默认语种文案,开发环境显示键名并在控制台告警——两个目标都满足。这类「开发友好」和「用户友好」冲突的地方,按环境区分是最常见的正确解法。

    切换语言选择「局部刷新文案」而不是「重启应用」。重启最简单可靠(所有地方都会重新读语言包),但用户正在填的表单、正在看的列表位置都会丢——而切换语言这个动作本身不该产生副作用。局部刷新的代价是要保证所有文案都是响应式读取的,有硬编码的地方就不会跟着变(这也反过来帮我们找出了漏抽的文案)。

    币种做「展示换算 + 明确标注」而不是「按用户选择的币种结算」。后者体验更完整,但涉及汇率锁定、结算通道、退款币种一致性,远超前端范围而只做展示换算却不标注,就是在制造误解——所以标注是这个功能能上线的前提。判据是「用户会不会因为这个展示做出错误的判断」:会的话就必须把不确定性说清楚。

    踩过的坑一:日期时间是字符串硬拼的,散落几十处。切到英文之后还是中文格式,而我要一处一处找出来改这个坑花的时间比我预估的多得多,而且很难确认改全了(有些是在条件分支里)。教训是:任何「格式化」的动作都应该从一开始就走统一入口,即使当时只有一种格式——因为「只有一种格式」这个假设迟早会被打破,而到那时改造成本是几十倍。

    踩过的坑二:只做了前端文案,没给服务端传语言。用户看到英文界面加中文产品名,反馈是「你们的翻译坏了」教训是:多语言不是前端一个人的事——界面上的内容有一部分来自服务端,不做语言协商就只完成了一半。我当时的思路完全停留在「前端文案替换」上。

    踩过的坑三:按中文验收,上线后英文版大量布局问题。我自己测的时候界面都是正常的(因为我看的是中文)。教训是:验收标准要按「最坏情况」定——多语言场景的最坏情况是最长的那个语种。后来我把「以最长语种过一遍全部页面」固化成了流程,这比逐个修 bug 有效得多。

    没做的部分:没做从右向左书写语言的支持。它需要整套布局的镜像(图标方向、滑动方向、对齐方式),工作量远大于加一个语言包,而当前的目标市场不涉及。我在文档里标注了「当前布局假设从左向右」,以便将来评估。把假设写下来,比默默留一个坑要好。

    数字是怎么测的

    先报规模:几种语言、多少条文案键、语言包多大、主包体积变化。主包体积变化这一项最能说明「按需加载」的价值——报「新增 N 个语种,主包体积基本不变」。

    布局问题的发现要报流程而不只是数量:「以最长语种逐页检查,发现并修复 N 处布局问题」。要说明检查覆盖了多少个页面——覆盖面比修了多少个更能说明可靠性。

    格式化收敛的验证:断言切换语言后日期、时间、数字、货币、相对时间全部按目标语言呈现。要做成清单逐项核对,因为漏掉一处就会出现中英混杂。

    缺失键降级:故意删除某个键,断言正式环境回退默认语种文案、开发环境显示键名并告警

    语言协商:断言切换语言后请求携带了语言标识、服务端返回内容随之变化;服务端无对应语言时返回默认语言且前端给出提示。后一条容易漏。

    切换不丢状态:在表单填一半时切换语言,断言已填内容与滚动位置保留。

    币种标注:断言换算展示处都有「仅供参考 + 实际结算币种 + 汇率时间」,支付页展示的是实际结算币种与金额。这条是合规性质的,必须逐处核对。

    不要报什么:不要报「多语言覆盖率 100%」——服务端内容的翻译完备度不由前端决定。该报的是「语种数量与主包体积基本不变」「以最长语种逐页检查的覆盖页数与修复数」「格式化项逐项核对通过」「缺失键按环境分别降级」这几件可核对的事。

    面试追问
    Q:多语言不就是把文案抽成键值、加几份语言包吗?工作量在哪? A:我接需求时也是这么想的,实际做下来发现工作量大头不在翻译,在「原来的实现里隐含了多少对中文的假设」。四类假设。第一,固定宽度隐含了「文案就这么长」:我们的界面按中文排版,中文很紧凑,四个字的按钮换成英文可能是十几个字符,结果是按钮文字换行、导航栏挤在一起、卡片标题溢出遮住价格——几乎每一页都要检查改成弹性布局。第二,字符串拼接隐含了「日期就是这个格式」:代码里到处是把年月日拼成中文格式的写法,散落几十处,切了语言还是中文格式,而且很难确认改全了(有些在条件分支里)。第三,不传语言参数隐含了「服务端内容只有一种语言」:产品名称、政策说明都来自服务端,我只做了前端文案切换,用户看到英文界面加中文产品名,反馈是「你们的翻译坏了」——比全中文更糟第四,复数和语序:中文没有复数变化、带变量的文案用拼接在其他语言里语序不对。这些假设在只有中文的时候完全看不出来,加第二种语言时全部暴露。所以我的结论是:多语言的难点是「找出全部隐含假设」,而不是「写一个切换函数」。而且我从中得到一条更一般的教训:任何「格式化」的动作都应该从一开始就走统一入口,即使当时只有一种格式——因为「只有一种」这个假设迟早会被打破,到那时改造成本是几十倍。
    Q:布局被撑破这类问题,你怎么保证改全了? A:关键是把验收标准从「按中文看一遍」改成「以最长语种过一遍全部页面」,并把它固化成流程。我踩的坑就是按中文验收——我自己测的时候界面都正常,因为我看的是中文,而中文是最紧凑的,上线后英文版大量布局问题。所以我做的第一件事是确定「最坏情况」是哪个语种(文案平均长度最长的那个),然后逐页用它检查,并记录覆盖了多少个页面、发现并修复了多少处——我报数据时会强调覆盖面而不是修复数,因为覆盖面才说明可靠性(修了 20 处但只看了 5 个页面,说明不了什么)。技术上的处理是三类:去掉按中文长度定的固定宽度改弹性布局;长文案按场景选不同的溢出策略——按钮适合缩小字号、列表标题适合截断加省略、正文适合换行,没有一个策略适用所有场景;还有一类容易被忽略的是图片里的文字根本无法翻译,运营图上经常有文字,要改成图片加文字叠加。另外有一个意外的收获:我选择「切换语言时局部刷新文案」而不是「重启应用」(因为重启会丢掉用户正在填的表单和滚动位置),而局部刷新要求所有文案都是响应式读取的——那些硬编码没抽出来的文案切换后不会变,这反过来帮我找出了一批漏抽的文案让错误以可见的方式暴露,比靠人工检查更可靠。

模块二:行程凭证离线查看

  1. 行程凭证离线查看(本地缓存并标注数据时间 + 二维码本地渲染不依赖网络 + 出行地时区显式标注不做自动换算 + 扫码场景的亮度提升与降级出示)★★★
    简历这样写 行程凭证离线查看(uni-app + 本地缓存 + 二维码本地生成 + 时区处理):凭证原先每次进入都请求接口且二维码为服务端图片,境外或弱网下页面空白、码图加载不出来,改为凭证数据本地缓存 + 二维码由本地库根据缓存内容渲染,无网络时仍可出示;缓存页显著标注数据获取时间与「可能非最新」提示,并在有网络时静默更新,避免用户依据过期凭证行动;出行时间的展示修正为标注出行地时区且不按设备时区自动换算(原实现按设备时区显示,用户跨时区后看到的登机时间偏移数小时);扫码出示时临时提升屏幕亮度并提供核销码文本与订单号作为降级出示方式(部分场景扫码失败需人工录入);凭证入口从多层级页面提升到首页近期行程卡片直达。上线后境外场景下凭证打不开的反馈明显下降。
    展开完整拆解
    为什么要这么设计

    凭证页第一版是最常规的写法:进页面请求接口拿凭证数据,二维码是服务端生成的图片,前端直接展示。时间按设备时区格式化。四个问题,全部只在真实出行场景下暴露。

    一是境外或弱网下页面空白。用户下了飞机、没有当地网络、漫游也没开,打开 App 想看凭证——请求失败,页面空白而这正是他最需要凭证的时刻。更糟的是二维码是服务端图片,即使凭证文字信息有缓存,码图也加载不出来。

    二是跨时区后时间显示错了。我按设备时区格式化出行时间。用户从国内飞到另一个时区,手机自动切换了时区,他看到的登机时间就偏移了好几个小时——而航班时间本来就是当地时间,根本不该做换算。有用户因此差点误机。

    三是扫码经常扫不上,而且没有替代方案。屏幕亮度低(用户为了省电调暗了)、屏幕有反光,核销设备扫不上而我的页面上只有一个二维码,没有可供人工录入的编号,用户只能站在那反复调整角度。

    四是凭证入口太深。要从「我的订单」进去,找到那个订单,点进详情,再点「查看凭证」。四层。而用户在核销口前面,后面排着队。

    所以四个改动:凭证数据本地缓存加二维码本地渲染出行时间标注出行地时区且不做自动换算扫码时临时提升亮度并提供文本降级出示凭证入口提升到首页近期行程卡片直达

    这个模块最想说的一句话是:这个功能的使用场景是「用户站在核销口、可能没网、可能手忙脚乱、后面还排着队」,而我是在办公室有网的电脑上开发和测试的。四个问题没有一个是代码写错了,全都是因为我按自己的处境设计了一个别人用不了的功能后来我养成的习惯是先把使用场景的约束一条条写下来(有没有网、什么时区、有多少时间、手上还拿着什么),再开始设计。

    整体链路
    数据获取与缓存 │ ├─ 首次获取成功后写入本地缓存(含完整凭证内容与核销码文本) │ 只缓存图片不行 —— 要缓存能重新渲染二维码的原始内容 │ ├─ 进入页面:先渲染缓存 → 再后台请求更新 │ 先请求后渲染的后果:无网络时页面空白,而那正是最需要的时刻 │ ├─ 更新成功 → 静默替换并刷新数据时间 │ ├─ 更新失败 → 保留缓存展示,顶部提示「当前为离线数据」 │ 不提示的后果:用户依据过期凭证行动(可能已被改签作废) │ └─ 缓存要显著标注获取时间,并给「刷新」入口 时间标注让用户自己能判断这份数据可不可信 二维码本地渲染(关键改动) │ ├─ 服务端返回码内容(文本),前端用本地库渲染成图 │ 服务端返回图片的后果:无网络时码图加载不出来 │ 而凭证的核心就是这个码 —— 文字信息看得到但码没有,等于没用 │ ├─ 渲染失败要有兜底:直接展示码内容文本 │ └─ 码内容本身不要含敏感信息(它可能被拍照转发) 时区呈现(我踩过的最危险的坑) │ ├─ 出行时间是「出行地当地时间」,不做设备时区换算 │ 按设备时区显示的后果 │ 用户跨时区后手机自动切时区 → 登机时间偏移数小时 │ 而航班时间本来就是当地时间,换算本身就是错的 │ ├─ 显式标注时区(例:当地时间 · 出发地时区) │ 不标注的后果:用户不确定这是哪里的时间,自己去换算又算错 │ ├─ 跨天航班要标注「次日」「+1 天」 │ └─ 如果确实要展示两种时间,必须并列标注各自的时区 只给一个时间又不说时区,是最容易误解的做法 出示场景(用户站在核销口) │ ├─ 进入凭证页临时提升屏幕亮度,离开恢复 │ 用户为省电调暗屏幕 → 扫码设备扫不上 │ ├─ 二维码区域留足白边并避免深色主题下的低对比 │ 深色主题下反色的码有些设备扫不出来 │ ├─ 降级出示:核销码文本 + 订单号 + 出行人姓名 │ 扫码失败时可人工录入 —— 没有这个用户只能干等 │ ├─ 支持保存到相册(作为最后的兜底,连 App 都打不开时) │ └─ 页面上同屏显示关键信息:时间 · 地点 · 出行人 · 码 需要滑动才能看到的信息在核销口等于没有 入口可达性 ├─ 首页展示「近期行程」卡片,一键直达凭证 ├─ 原路径要从我的订单进去点四层 —— 排队时来不及 └─ 出行当天可进一步前置(顶部横幅提示) 失效与作废 ├─ 已核销 / 已作废的凭证要明确标识,不能只是普通展示 ├─ 离线数据可能不知道已作废 → 这也是必须标注数据时间的原因 └─ 有网络时优先同步作废状态(比其他字段优先级更高)
    分步拆解
    1. 进入页面要先渲染缓存再后台更新,不能先请求后渲染。先请求的后果是无网络时页面空白,而那正是用户最需要凭证的时刻。
    2. 缓存要存能重新渲染二维码的原始内容,不是只存图片。只存图片的话无网络时码图加载不出来——而凭证的核心就是这个码,文字信息看得到但码没有等于没用。
    3. 二维码必须由前端本地库渲染。服务端返回码内容文本,前端画图。这是让凭证真正离线可用的关键一步。
    4. 二维码渲染失败要兜底展示码内容文本。让工作人员能手动录入。
    5. 码内容本身不要包含敏感信息。它可能被拍照转发,应该只是一个查询用的标识。
    6. 缓存要显著标注获取时间并给刷新入口。时间标注让用户自己能判断这份数据可不可信,不标注的话他会依据可能已作废的凭证行动。
    7. 更新失败时要保留缓存并提示「当前为离线数据」。直接报错或清空缓存都比保留更糟。
    8. 出行时间不要按设备时区换算,直接展示出行地当地时间。按设备时区显示的后果是用户跨时区后手机自动切换,登机时间偏移数小时——而航班时间本来就是当地时间,换算本身就是错的。
    9. 时区必须显式标注。不标注的话用户不确定这是哪里的时间,自己去换算又会算错。
    10. 跨天航班要标注「次日」。只显示时间不标日期偏移很容易误解。
    11. 如果确实要展示两种时间,必须并列且各自标注时区。只给一个时间又不说时区是最容易误解的做法。
    12. 进入凭证页要临时提升屏幕亮度,离开时恢复。用户为省电调暗屏幕,核销设备就扫不上。
    13. 二维码区域要留足白边,并避免深色主题下的低对比。深色主题下反色的码有些设备扫不出来——这一条我是在真机上试出来的。
    14. 必须提供降级出示方式:核销码文本、订单号、出行人姓名。扫码失败时可人工录入,没有这个用户只能站在那干等。
    15. 支持保存到相册作为最后的兜底。连 App 都打不开时(手机没电换了设备、账号登录不上)还有一张图。
    16. 关键信息要同屏显示,不要需要滑动。在核销口需要滑动才能看到的信息等于没有。
    17. 凭证入口要提升到首页近期行程卡片直达。原路径要点四层,排队时来不及。
    18. 已核销与已作废的凭证要明确标识。而且有网络时作废状态的同步优先级要高于其他字段——出示一张已作废的凭证是最糟的情况。
    关键决策与取舍

    二维码本地渲染而不是服务端出图,代价是引入一个本地库(包体积增加)并要保证渲染结果被核销设备认可。服务端出图的好处是渲染一致、前端简单。但它有一个致命问题:无网络时码图加载不出来,而凭证的核心就是这个码——文字信息有缓存但码没有,等于凭证不可用。判据是「这个功能在没有网络时还要不要能用」:要,那么任何依赖网络的环节都必须去掉。包体积的增加我实测确认在可接受范围,而且这是必要成本。

    缓存优先展示而不是「必须拿到最新数据才展示」。后者更严谨(避免展示过期凭证),但在境外无网络时它等于什么都不给用户我的取舍是「先给他能用的,同时把不确定性标注清楚」:显著显示数据获取时间、提示「当前为离线数据」、有网络时优先同步作废状态。这一条的核心判断是:在用户最需要的时刻给一份可能过期的数据加一个明确的警示,好过给一个空白页面。

    时区选择「不做换算,显式标注」而不是「换算成设备时区」。换算听起来更贴心(用户看到的是自己手机上的时间),但它在这里是错的——航班和酒店的时间本来就是当地时间,换算之后反而不是用户需要的那个数字而且换算引入了一个新风险:设备时区可能不准(手动设置过、自动切换有延迟)。判据是「原始数据的语义是什么」:语义就是「出行地的当地时间」,那就原样展示并标注。

    提升屏幕亮度这类系统级操作要「进入时提升、离开时恢复」而不是一直保持。一直保持会耗电,而用户在旅途中电量往往紧张。这是一个很小的细节,但它体现了「知道使用者的处境」

    踩过的坑一:境外用户打开凭证页是空白的。是客服转过来的反馈——用户在国外没有网络,站在核销口打不开凭证我当时的第一反应是「加个缓存」,但只缓存了接口数据,二维码还是服务端图片,所以缓存了也用不了教训是:做离线支持要检查「这条链路上还有哪个环节依赖网络」,只缓存主数据是不够的——一个依赖网络的子资源就能让整个功能失效。

    踩过的坑二:按设备时区显示出行时间,用户跨时区后看到偏移数小时的登机时间。有用户因此差点误机,这是我做过的最危险的一个 bug而它的成因是一个「看起来正确」的做法——把时间戳按用户设备时区格式化,这在大多数业务里是对的。教训是:格式化时间之前必须先问「这个时间的语义是哪个时区的」,而不是无条件按设备时区展示。这类错误在国内测试永远不会暴露。

    踩过的坑三:只有二维码,没有可人工录入的编号。扫码失败时用户只能反复调整角度,后面还排着队教训是:任何依赖硬件识别的交互都要准备人工降级路径——识别失败是常态不是异常(光线、反光、设备差异、屏幕划痕)。

    没做的部分:没做把凭证写入系统级的卡包(钱包类应用)。它的离线可用性最好(不用打开我们的 App),但各平台的接入方式差异大、审核要求高,超出了这次改动的范围。我做的是「支持保存到相册」这个最基础的兜底,并把卡包接入作为后续建议提出。

    数字是怎么测的

    离线可用性必须真的断网测:开飞行模式,断言凭证页能正常展示、二维码能渲染出来、顶部有「离线数据」提示与数据时间这是这个模块最核心的一条验证,而且必须在真机上做(开发工具的网络模拟不完全一致)。

    二维码可用性要用真实的核销设备验证,并报测试条件:「在若干种亮度与屏幕状态下用核销设备扫描,成功率如何」。要说明测了哪些条件(深色主题、低亮度、有反光)——只报一个成功率不说条件没有参考价值。

    时区正确性用断言型用例:把设备时区改成不同时区,断言出行时间的显示值不变、且时区标注正确「显示值不变」是这条用例的关键点——它正好是踩坑的反面。

    缓存与更新:断言先渲染缓存再后台更新;更新失败时保留缓存并提示;更新成功后数据时间刷新

    作废同步:凭证在服务端作废后,断言有网络时前端优先同步到作废状态并明确标识。

    亮度处理:断言进入页面亮度提升、离开后恢复原值。「恢复」这一步容易漏,而不恢复会持续耗电。

    入口可达性可以报操作步数:从打开 App 到看到凭证的点击次数,从四次降到一次。这个数字很具体。

    不要报什么:不要报「扫码成功率 100%」——硬件识别受太多物理条件影响。该报的是「飞行模式下凭证与二维码可正常展示」「设备时区变化时显示值不变且标注正确」「多种屏幕条件下的扫码结果与测试条件」「入口点击次数从四次降到一次」这几件可核对的事。

    面试追问
    Q:凭证页加个缓存不就能离线看了吗? A:我第一反应也是「加个缓存」,而且真的加了,但没解决问题——因为我只缓存了接口数据,二维码还是服务端返回的图片。结果是境外用户打开凭证页,文字信息(航班、时间、出行人)都能看到,但二维码那块是空的——而凭证的核心就是那个码,码没有等于凭证不可用教训是:做离线支持要把整条链路上「还有哪个环节依赖网络」都检查一遍,只缓存主数据是不够的——一个依赖网络的子资源就能让整个功能失效。真正的解法是服务端返回码的内容文本,前端用本地库渲染成图,这样缓存了内容就能离线重新渲染。代价是引入一个本地库(包体积增加,我实测确认在可接受范围)、并且要用真实的核销设备验证渲染出来的码能被认可另外围绕「离线」还有两个配套决策。一是先渲染缓存再后台更新,而不是先请求后渲染——后者在无网络时页面空白,而那正是用户最需要凭证的时刻。二是必须显著标注数据获取时间并提示「当前为离线数据」,因为离线数据可能已经被改签作废了。我的判断是:在用户最需要的时刻给一份可能过期的数据加一个明确的警示,好过给一个空白页面;同时有网络时把「作废状态」的同步优先级排在其他字段之前——出示一张已作废的凭证是最糟的情况。还做了一个降级:提供核销码文本和订单号供人工录入,因为扫码失败是常态不是异常(光线、反光、屏幕划痕、设备差异)。
    Q:时区那个问题,把时间按用户设备时区显示不是更贴心吗? A:这个「贴心」的做法在这里是错的,而且它是我做过的最危险的一个 bug——有用户因此差点误机。我按设备时区格式化出行时间,用户从国内飞到另一个时区,手机自动切换时区,他看到的登机时间就偏移了好几个小时问题的根源是我搞错了这个时间的语义:航班时间本来就是「出行地的当地时间」,它不是一个需要被换算的时间戳——换算之后得到的那个数字反而不是用户需要的。用户到了机场看登机口的显示屏、听广播,那些都是当地时间;我们的 App 显示一个换算过的时间,只会造成混乱。而且换算还引入了一个新风险:设备时区本身可能不准(用户手动设置过、或者自动切换有延迟)。所以正确做法是不做换算,直接展示当地时间,并且显式标注时区(「当地时间」「出发地时区」)——不标注的话用户不确定这是哪里的时间,会自己去换算,又会算错。跨天航班还要标注「次日」。如果确实要展示两种时间,必须并列且各自标注时区,绝不能只给一个时间又不说时区。我从这件事得到的教训是:格式化时间之前必须先问「这个时间的语义是哪个时区的」,而不是无条件按设备时区展示。「按用户时区显示」在大多数业务里是对的(比如消息时间、订单创建时间),但在「某地发生的事件的当地时间」这个语义上它是错的这类错误在国内测试永远不会暴露——所以我后来专门把「改设备时区后断言显示值不变」写成了回归用例。

模块三:出行前提醒与日历写入

  1. 出行前提醒与日历写入(提醒时点按出行地时区推算 + 平台通知能力差异下的分端策略 + 权限申请时机与降级路径 + 日历事件去重与行程变更同步)★★
    简历这样写 出行前提醒(uni-app + 分端通知能力 + 日历写入 + 定时任务配合):提醒时点原按设备时区推算,跨时区行程下提醒提前或延后数小时,改为以出行地时区的出行时间为基准推算触发时刻;针对小程序与原生 App 的通知能力差异做分端策略(原生走本地通知可离线触发,小程序走订阅消息且受单次授权限制,需在合适时机引导并合并提醒内容);权限引导从进入应用即申请改为在用户明确表达需要提醒时申请,授权率提升且减少了首次打开就被弹窗的流失;提供写入系统日历作为不依赖推送的兜底,写入做去重与变更同步(改签更新事件、取消删除事件,避免重复事件堆积);提醒内容改为包含关键信息(航班号、出发时间、值机口)而非仅提示「有行程即将开始」。上线后出行前提醒的有效触达明显改善。
    展开完整拆解
    为什么要这么设计

    出行前提醒这个需求看起来简单:出发前几小时给用户发个提醒。第一版我用了最直接的做法——在客户端算出提醒时刻,注册一个本地通知。四个问题。

    一是提醒时间算错了,跨时区行程下偏移数小时。我用设备时区算「出行时间减 3 小时」。但出行时间是出行地的当地时间而用户下单时还在国内结果是提醒在完全不对的时刻触发——有的提前了大半天,有的在他已经登机之后才响。这和凭证页的时区问题同源,但表现更隐蔽(用户不会来报告「提醒时间不对」,他只会觉得这个功能没用)。

    二是小程序和 App 的通知能力完全不同,我用了同一套逻辑。原生 App 可以注册本地通知(不依赖网络、到点就响);小程序没有本地通知,只能用订阅消息,而订阅消息受单次授权限制——用户授权一次只能发一条。我按 App 的思路做了「出发前 24 小时、3 小时、1 小时各提醒一次」,在小程序上只有第一条能发出去。

    三是权限申请时机不对,授权率很低。我在用户第一次打开 App 时就申请通知权限。那时他还不知道我们要通知他什么,大多数人直接拒绝——而一旦拒绝,后面就很难再要到了(要引导他去系统设置里手动开)。

    四是日历事件重复堆积。我做了「添加到日历」,但没有做去重。用户点两次就写入两个事件;改签之后又写一个新的,旧的还在——他的日历里有三个同一趟航班的事件,时间还不一样。

    所以四个改动:提醒时点以出行地时区的出行时间为基准推算按端做通知策略差异(小程序合并提醒内容)权限申请挪到用户明确表达需要提醒的时刻日历写入做去重与变更同步,另外把提醒内容改成包含关键信息

    这个模块最想说的一句话是:提醒类功能的价值完全取决于「在正确的时间、通过用户真的能收到的渠道、送达他真的用得上的信息」这三件事同时成立。我第一版三件都没做到:时间算错了、渠道在小程序上失效了、内容只说「有行程即将开始」(他知道自己有行程,他需要的是航班号和值机口)而这个功能上线后看起来是「正常工作」的——因为通知确实发出去了,只是没有产生任何价值。

    整体链路
    提醒时点计算(第一个必须做对的事) │ ├─ 基准:出行时间(出行地当地时间 + 时区标识) │ ├─ 提醒时刻 = 出行地当地时间往前推 N 小时 → 换算为绝对时刻 │ 用设备时区推算的后果 │ 用户下单时还在国内、出行地在另一个时区 │ 提醒可能提前大半天,或在他已登机后才响 │ ├─ 提醒时刻已过则不注册(补一条「已临近」的即时提示) │ └─ 用户改签 → 重新计算并替换原提醒 不替换的后果:旧提醒照样在原时刻响,用户被误导 分端通知策略(能力差异必须分别设计) │ ├─ 原生 App │ 本地通知:客户端注册,到点触发,不依赖网络 │ 可以注册多个时点(24 小时 / 3 小时 / 1 小时) │ ├─ 小程序 │ 无本地通知,只能用订阅消息 │ 订阅消息受单次授权限制 —— 授权一次只能发一条 │ 策略:不注册多个时点,改为合并成一条信息量最大的提醒 │ 并在用户每次进入行程页时引导再次订阅(不强制) │ ├─ 服务端定时任务兜底 │ 客户端注册的提醒可能因卸载 · 清缓存 · 权限变更失效 │ 服务端按提醒时刻推送一份,两条路都走(客户端做去重展示) │ └─ 关键:不要用同一套逻辑覆盖两端 我最初按 App 思路做了三个时点,小程序上只有第一条发出去 权限申请时机(决定授权率) │ ├─ 不在首次打开时申请 │ 那时用户不知道要通知他什么 → 大多直接拒绝 │ 而一旦拒绝,后面要引导他进系统设置,成本极高 │ ├─ 在用户明确表达需要提醒时申请 │ 例:他点了「出行前提醒我」开关 / 进入行程详情页 │ ├─ 申请前先用一屏说明「会提醒什么、什么时候提醒」 │ └─ 被拒绝后不反复弹,改为在页面上提供其他方式 日历写入就是这里最有用的降级路径 日历写入(不依赖推送的兜底) │ ├─ 写入内容:标题(含航班号)· 起止时间 · 地点 · 备注(凭证入口) │ ├─ 去重:记录已写入的事件标识,重复点击不再新建 │ 不去重的后果:点两次就是两个事件 │ ├─ 变更同步:改签 → 更新事件;取消 → 删除事件 │ 不同步的后果:日历里三个同一航班的事件,时间还都不一样 │ ├─ 日历事件的提醒时间也要按出行地时区设置 │ └─ 写入失败要明确提示(权限 · 系统限制),不要静默失败 提醒内容 ├─ 要包含关键信息:航班号 · 出发时间(标注时区)· 值机口 · 出行人 ├─ 只说「有行程即将开始」等于没有信息 —— 他知道自己有行程 └─ 附直达凭证的入口(点开就是凭证,不用自己找) 可观测 ├─ 提醒的注册数 · 实际触达数 · 点击打开数 ├─ 授权率(按申请时机拆分对比) └─ 日历写入成功率与失败原因分布
    分步拆解
    1. 提醒时刻必须以出行地当地时间为基准推算,再换算成绝对时刻。用设备时区推算的后果是用户下单时还在国内、出行地在另一个时区,提醒可能提前大半天或在他已登机后才响。
    2. 提醒时刻已经过去的不要注册,改为给一条「已临近」的即时提示。注册一个过去的时刻通常不会触发,用户以为设置成功了实际没有。
    3. 改签后要重新计算并替换原提醒。不替换的后果是旧提醒照样在原时刻响,用户被误导——这比不提醒更糟。
    4. 原生 App 用本地通知,可以注册多个时点。本地通知不依赖网络,在境外无网络时也能触发,这是它最大的价值。
    5. 小程序没有本地通知,只能用订阅消息,且受单次授权限制。我最初按 App 思路注册了三个时点,小程序上只有第一条发出去了。
    6. 小程序策略要改成「合并成一条信息量最大的提醒」。既然只能发一条,那就把航班号、时间、值机口都放进去,而不是分三次说三件小事。
    7. 要有服务端定时任务兜底。客户端注册的提醒可能因卸载、清缓存、权限变更而失效,而用户不会知道它失效了。
    8. 两条路都走时客户端要做去重展示。否则用户可能收到两条内容相同的提醒。
    9. 不要在首次打开时申请通知权限。那时用户不知道要通知他什么,大多直接拒绝——而一旦拒绝,后面要引导他进系统设置,成本极高。
    10. 在用户明确表达需要提醒时才申请。他点了「出行前提醒我」开关,这个时刻的授权率完全不同。
    11. 申请前先用一屏说明「会提醒什么、什么时候提醒」。让他知道自己在授权什么,这一步能明显提高授权率。
    12. 被拒绝后不要反复弹窗。反复弹只会让用户更反感,改为在页面上提供其他方式——日历写入是这里最有用的降级路径。
    13. 日历写入要记录已写入的事件标识做去重。不去重的后果是用户点两次就写入两个事件。
    14. 日历事件要跟随行程变更:改签更新、取消删除。不同步的后果是日历里出现三个同一航班的事件、时间还都不一样。
    15. 日历事件自带的提醒时间也要按出行地时区设置。这一条容易漏——写入的时间对了但事件提醒还是错的。
    16. 写入失败要明确提示原因(权限、系统限制),不要静默失败。静默失败的话用户以为加成功了,到时候没有提醒。
    17. 提醒内容必须包含关键信息(航班号、出发时间带时区、值机口、出行人)。只说「有行程即将开始」等于没有信息——他知道自己有行程。
    18. 提醒里要附直达凭证的入口。点开就是凭证,不用他自己去找。
    关键决策与取舍

    分端做不同的通知策略,而不是求一个「两端都能跑」的最小公倍数。取最小公倍数(两端都只发一条)实现最简单,但会浪费原生 App 上「本地通知可以注册多个时点、且离线可触发」这个真实优势——而出行场景恰恰经常没网络。代价是两端的逻辑不同,要分别测判据是「这个平台能力的差异是否影响到功能的核心价值」:影响,那就必须分别设计。把平台差异当成「兼容问题」去抹平,往往会把两端都做成较差的那个。

    客户端注册与服务端定时推送两条路都走,代价是可能重复。只走客户端的问题是它可能因为卸载、清缓存、权限变更而静默失效,而用户不会知道;只走服务端的问题是无网络时收不到——而这正是出行场景的常态。两条都走加客户端去重,是在「可靠性」和「重复风险」之间选了前者判据是「漏掉一次提醒的代价」:误机的代价远大于多收一条通知。

    权限申请挪到「用户明确表达需要」的时刻,代价是有一部分用户永远不会走到那个入口(所以永远不会被申请)。首次打开就申请能覆盖所有用户,但授权率很低,而且拒绝之后基本无法挽回(要引导他进系统设置)。判据是「授权率 × 覆盖率」而不是单看覆盖率——在合适时机申请,实际拿到的授权总数更多。而且被拒绝的用户还有日历写入这条降级路径。

    日历写入作为主要降级路径,而不是「引导用户去系统设置开通知」。后者的转化率很低(步骤多、界面陌生)。而日历写入不需要通知权限,且用户对日历的信任度更高(他本来就用日历安排行程)判据是「哪条降级路径的完成率更高」——不是「哪条更接近原方案」。

    踩过的坑一:提醒时刻按设备时区推算,跨时区行程下完全错乱。有的提前大半天、有的在用户已经登机之后才响。而这个坑最麻烦的地方是它不会被投诉——用户不会来说「你们的提醒时间不对」,他只会觉得这个功能没用,然后关掉它我是在查提醒点击率异常低的时候才发现的。教训是:功能上线后「看起来正常工作」不代表它在产生价值——通知确实发出去了,只是发在了错误的时刻。所以要看的不是「发送成功数」而是「点击打开数」。

    踩过的坑二:按原生 App 的思路做了三个提醒时点,小程序上只有第一条发出去。我以为订阅消息和通知是类似的东西,没有仔细看它的单次授权限制教训是:跨端开发时,「同名的能力」在不同平台可能有完全不同的约束模型——不能假设它们只是 API 形式不同。正确做法是先读清各端的限制,再设计功能形态,而不是设计好了再去适配。

    踩过的坑三:日历事件不去重、不同步变更。用户的日历里出现三个同一航班的事件、时间还都不一样。这个反馈很直接(有用户截图给客服)教训是:任何「写入到用户自己的空间」的操作(日历、相册、通讯录、文件),都必须做去重和生命周期管理——因为这些数据的清理成本在用户身上,而他会因此觉得我们的产品很粗糙。

    没做的部分:没做基于用户实时位置的动态提醒(比如「你离机场还有一小时车程,建议现在出发」)。它价值更高,但需要持续定位(耗电、隐私敏感)和路况数据,超出范围。我做的是「固定提前量 + 提醒内容里给出建议到达时间」,把判断留给用户。

    数字是怎么测的

    核心指标是「提醒的点击打开率」而不是「发送成功率」。这是我踩坑之后最重要的认识——发送成功但时刻错误的提醒,发送成功率是满的,点击率接近零。报法是分端、分提醒时点统计点击率。

    时区正确性用断言型用例:构造不同时区的出行地,断言计算出的提醒绝对时刻等于「出行地当地时间往前推 N 小时」对应的时刻;把设备时区改掉,断言结果不变。后一条是踩坑的直接回归。

    改签同步:改签后断言旧提醒被替换(不会在原时刻触发)、新提醒时刻正确。「旧提醒不再触发」这一条要真的等到原时刻验证,或者用可控时间测。

    分端策略:在小程序端断言只发送合并后的一条且内容包含全部关键信息;在原生端断言多个时点都能触发,且飞行模式下本地通知仍然触发最后这一条是本地通知的核心价值,必须验证。

    授权率要按申请时机拆分对比:报「首次打开时申请 vs 用户主动开启时申请」的授权率。还要报「实际获得授权的用户总数」——因为后者覆盖率低但授权率高,只看比例会得出错误结论。

    日历去重与同步:断言重复点击只产生一个事件;改签后事件被更新而不是新增;取消后事件被删除。第三条容易漏。

    写入失败:拒绝日历权限,断言给出明确提示而不是静默失败。

    不要报什么:不要报「提醒送达率」——它不代表用户看到了,而且时刻错误时这个数字反而很好看。该报的是「分端分时点的点击打开率」「设备时区变化时提醒时刻不变」「飞行模式下本地通知仍触发」「两种申请时机的授权率与实际授权总数」「日历事件去重与变更同步」这几件可核对的事。

    面试追问
    Q:出行前发个提醒,这里面能有什么技术含量? A:我第一版做完,功能「看起来正常工作」——通知确实发出去了,发送成功率是满的。但它没有产生任何价值,三件事都错了。第一,时间算错了:我按设备时区推算「出行时间减 3 小时」,但出行时间是出行地的当地时间,而用户下单时还在国内——结果提醒在完全不对的时刻触发,有的提前大半天、有的在他已经登机之后才响。而这个坑最麻烦的地方是它不会被投诉:用户不会来说「你们的提醒时间不对」,他只会觉得这个功能没用然后关掉。我是在查「提醒点击率异常低」时才发现的。第二,渠道在小程序上失效了:我按原生 App 的思路注册了三个提醒时点(24 小时/3 小时/1 小时),但小程序没有本地通知,只能用订阅消息,而订阅消息受单次授权限制——授权一次只能发一条,所以只有第一条发出去了教训是跨端开发时「同名的能力」在不同平台可能有完全不同的约束模型,不能假设它们只是 API 形式不同。第三,内容没有信息量:提醒只说「您有行程即将开始」——他知道自己有行程,他需要的是航班号、出发时间和值机口所以我总结的是:提醒类功能的价值取决于「正确的时间 × 用户真能收到的渠道 × 他真用得上的信息」三者同时成立,缺一个整个功能就等于没做。而且要看的指标是「点击打开率」而不是「发送成功率」——后者在时刻错误时反而很好看。
    Q:通知权限为什么不在用户第一次打开时就申请?那样覆盖的用户最多。 A:覆盖率最高,但实际拿到的授权最少——这里要看的是「授权率乘覆盖率」而不是单看覆盖率。首次打开时申请的问题是用户还不知道我们要通知他什么,大多数人会直接拒绝而一旦拒绝,后面就很难挽回了——系统不会再弹,只能引导他自己进系统设置里手动开,而这条路径的转化率很低(步骤多、界面陌生)。所以我改成在用户明确表达需要提醒时才申请(他点了「出行前提醒我」这个开关),并且在申请前先用一屏说明「会提醒什么、什么时候提醒」——让他知道自己在授权什么。代价是有一部分用户永远不会走到那个入口,所以永远不会被申请到,覆盖率确实降了。但实际获得授权的用户总数是上升的,所以我在报数据时会同时报「授权率」和「实际获得授权的用户总数」——只看比例会得出错误结论。另外配套做了两件事。一是被拒绝后不反复弹窗(反复弹只会让用户更反感),改为在页面上提供日历写入这条降级路径——它不需要通知权限,而且用户对日历的信任度更高(他本来就用日历安排行程)。我选降级路径的判据是「哪条路径的完成率更高」,而不是「哪条更接近原方案」,所以没选「引导他去系统设置开通知」。二是加了服务端定时推送作为兜底——因为客户端注册的提醒可能因卸载、清缓存、权限变更而静默失效,而用户不会知道它失效了;两条路都走,客户端做去重展示。这里的判据是「漏掉一次提醒的代价」:误机的代价远大于多收一条通知。

没有匹配的内容,换个关键词试试。

项目拆解 · 出行客户端常用页(uni-app · 实习级)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据