这个页面的起点是客服的一句抱怨:「查一个用户的订单我要开五个页面」。因为机票、酒店、门票、用车各有一套管理页面,而用户打电话来问的时候只会说「我上个月订的那个」。四个问题。
一是订单类型异构,没法放在一个列表里。机票有航班号、舱位、乘机人;酒店有入住退房日期、房型、间夜数;门票有使用日期、张数。字段完全不同,状态定义也不同(机票有「已出票」,酒店有「已确认」,语义相近但不是一回事)。我最初的想法是「那就分几个 tab 吧」——但那和开五个页面没有本质区别。
二是按证件号查不了。证件号在后端是加密存储的。客服常用的查询方式恰恰是「用户报证件号」(因为他可能不记得订单号、手机号也换了)。我最初想在前端先做点处理再传给后端,后来才理解这个查询必须完全由服务端用哈希索引完成——前端只负责收集输入和格式预校验。
三是只显示当前状态,客服答不上「进行到哪一步了」。用户问「我的退款什么时候到」,页面上只有一个「退款中」。客服不知道是刚提交、还是供应商已经确认、还是在等银行到账。他只能说「请再等等」,而用户已经等了一周。
四是同一个订单在不同查询条件下会重复出现。我把几个来源的结果直接拼在一起,没有去重,客服看到两条一样的订单不知道点哪个。
所以四个改动:加归一化适配层把异构订单映射为统一摘要模型、证件号查询由服务端按哈希索引匹配、前端只做格式预校验、详情用状态时间线呈现关键节点、查询结果去重并按优先级排序。
这个模块最想说的一句话是:「不同类型的数据放不进一个列表」通常不是数据的问题,是没有找到正确的抽象层次。机票和酒店的字段确实完全不同,但客服在这个页面上关心的只有五件事:什么时候出行、谁出行、多少钱、现在什么状态、我能做什么操作。找到这五个共同维度之后,异构就不再是障碍了——差异下沉到详情里去展示。
选「归一化到统一摘要模型」而不是「分 tab 展示」。分 tab 实现最简单,但它和「开五个页面」没有本质区别——客服还是要一个个点过去看。归一化的代价是要维护适配层、而且摘要模型的维度选择需要拿准(选多了退化成大杂烬、选少了信息不够用)。判据是「使用者的真实动作」:客服接到电话时要在几秒内看到这个用户所有相关的订单,那就必须在一个列表里。
状态语义归一但保留原始文案,而不是二选一。只保留原始文案的话列表无法统一筛选;只保留归一状态的话客服说不出供应商侧的准确状态(用户问「航空公司那边确认了吗」,答不上来)。两者都留的代价是多一个字段、界面上要合理安排位置(列表用归一状态,详情显示原始文案)。这类「抽象之后丢失细节」的问题,通用解法就是抽象和原始并存,而不是用抽象替换原始。
可执行操作由后端返回而不是前端判断。前端判断响应更快、少一次数据依赖,但退改规则、供应商能力、订单状态这些判断条件都在后端,前端复刻一份必然会不同步。而不同步的表现特别糟:客服看到「可退」按钮,跟用户说了可以退,点下去报错。判据是「判断依据在哪一侧」——依据在后端,判断就该在后端。
踩过的坑一:最初想做成几个 tab,被客服直接否了。他说「那我还是要点五次」。这件事让我意识到我在解决「怎么把它们放在一起显示」这个技术问题,而客服要解决的是「怎么快速看全一个用户的订单」这个使用问题。两者的区别在于:技术问题的答案可能是「分 tab」,使用问题的答案只能是「一个列表」。后来我先去问了「你拿到这些订单之后要看什么」,才找到那五个共同维度。
踩过的坑二:查询失败被显示成「没有查到订单」。客服照着页面告诉用户「查不到您的订单」,用户当场就急了(他明明订了)。事后才发现是那次查询接口超时。教训是:空态必须区分「没有数据」和「拿不到数据」——尤其在客服工作台上,因为客服会把页面上的信息当成事实直接转述给用户。这个场景比 C 端更严重。
踩过的坑三:前端自己判断「是否可退」,规则调整后没同步。客服看到可退按钮,跟用户承诺了,点下去接口返回「该订单不可退」。用户认为我们反悔。教训是:任何「能不能做某件事」的判断,都应该由持有完整规则的那一侧给出,前端只负责渲染。前端复刻业务规则是一个很容易犯的错误,因为它在当时看起来能省一次请求。
没做的部分:没做跨用户的关联查询(比如查同一个证件号在不同账号下的订单)。它对识别异常行为有用,但涉及跨账号的数据可见性,需要专门的权限设计,而且容易被滥用。取舍依据是「这个能力的滥用风险高于当前的业务收益」,应该由风控侧以受控的方式提供。
核心指标是「客服处理一次咨询的平均查询次数」或「打开的页面数」。这个指标直接对应「开五个页面」这个原始问题。报法是埋点统计改造前后的对比,并说明样本量与统计区间。
归一化的可扩展性可以报一个很具体的数:「新增一种订单类型时,需要改动的文件数 / 新增的代码量」。改造前要动列表、筛选、详情多处,改造后只需加一个适配器——这个对比比抽象的「可扩展性提升」有说服力。
适配器的正确性用断言型用例:每种订单类型各准备真实结构的样例数据,断言映射后的摘要模型字段正确、缺字段显示为「—」而不是 undefined。
时间线的正确性:断言节点顺序、时间格式、操作方标注正确;断言异常节点被突出显示。要覆盖「退款中」这类进行中的状态。
查询去重:构造一个订单能被多个条件命中的场景,断言列表只出现一条。
空态区分:让查询接口返回空结果与让它失败,断言两种情况展示不同的文案,失败态有重试按钮。这条对客服场景尤其重要。
操作按钮的一致性:断言按钮完全由后端返回的可执行操作列表渲染,前端没有额外的判断逻辑。这条可以用代码检查而不是运行时测试。
不要报什么:不要报「客服效率提升 N%」——效率受话务量、问题类型、客服熟练度影响。该报的是「单次咨询的查询次数下降」「新增订单类型的改动量对比」「各类型适配器的映射断言通过」「查询失败与无结果展示不同」这几件可核对的事。
退改处理台第一版的界面很简洁:显示订单信息、一个「应退金额」数字、一个「确认退款」按钮,另外有一个「特批全退」的选项。简洁的代价是四个问题。
一是客服解释不了这个金额,于是直接特批。页面上只有「应退 320 元」,没有说明订单金额多少、扣了多少、按哪一档扣的。用户问「为什么扣我 180」,客服答不上来。他的解决办法是——点「特批全退」。因为那样用户就不投诉了。结果是特批率异常高,而这是实实在在的损失。
二是特批没有额度分级和原因记录。任何客服都能点特批、金额多大都能批、不用填任何原因。事后完全无法分析「为什么特批这么多」——是规则不合理?是话术不够?是个别客服图省事?数据上看不出来。
三是误操作。金额输入框可以手填(用于部分退款),有一次客服多打了一个零。提交后直接发起了退款。页面上没有任何二次确认。
四是客服说的和用户收到的不一样。客服跟用户说「三到五天到账」,但系统给用户发的通知写的是「七个工作日」。用户第六天来投诉客服骗他。而客服并不知道系统发了什么文案。
所以四个改动:把规则快照、判定依据、试算明细并排展示出来、特批按额度分级并强制填原因分类、提交前做金额二次确认、处理完成后展示用户可见的通知文案。
这个模块最想说的一句话是:当操作者无法解释系统给出的结果时,他会选择那个「不需要解释」的操作。客服不是想乱特批,是因为「特批全退」是他唯一不需要向用户解释的选项。所以降低特批率的正确办法不是限制特批权限,而是让他能解释那个金额——把依据摊开给他看,他才有底气说「按您下单时的政策,出行前 12 小时退改扣 30%」。
降特批率的办法选「让客服能解释」而不是「收紧特批权限」。收紧权限是最直接的做法,但它解决不了根因——客服之所以特批,是因为他无法向用户解释那个金额,而「特批全退」是唯一不需要解释的选项。如果只收紧权限,他的应对会是把用户转给有权限的人(问题只是转移)、或者在通话里含糊过去(投诉照样来)。所以我先做的是把依据摊开给他看,再做额度分级——顺序很重要:先给他解释的能力,再限制他偷懒的出口。判据是「这个人为什么做出了我们不希望的选择」,而不是「怎么阻止他这么选」。
特批做「额度分级 + 审批流」而不是「一律需要审批」。一律审批会让小额补偿也卡住,而小额补偿的即时性恰恰是它的价值(用户在通话中就得到解决)。分级的代价是要设计额度阈值和审批链路。判据是「误批的金额代价 vs 卡流程的体验代价」:小额时前者小于后者,大额时反过来。
二次确认要求「手动动作」而不是只点确定。只点确定的确认层会被无脑点过——而它要拦的恰恰是「注意力不集中导致的手滑」,无脑点过等于没拦。要求重新输入关键金额或逐项勾选,代价是多几秒。判据是「这个操作错了能不能撤回」:退款一旦发起很难撤回,那就值得多这几秒。
试算完全依赖后端接口,前端不做任何计算。前端算能减少一次请求、响应更快。但退改金额的计算依赖规则快照、时区判定、已使用部分等一堆后端才有的信息,前端复刻一份必然算出不同结果——而「客服看到的金额和实际扣的不一样」比不显示明细严重得多。这一条和「可执行操作由后端返回」是同一个原则。
踩过的坑一:只显示一个应退金额,客服解释不了就直接特批。特批率异常高,而我们最初以为是「客服服务意识过强」,还讨论过要不要考核特批率。后来是听了几通录音才明白:用户问「为什么扣我 180」,客服真的答不上来——页面上没有任何依据。这件事对我影响很大:我们差点用「考核」去解决一个「信息缺失」的问题,那只会让客服更痛苦而问题依旧。
踩过的坑二:客服说「三到五天到账」,系统通知写「七个工作日」。用户第六天来投诉客服骗他。而客服完全不知道系统发了什么文案——他说的是自己的经验值。教训是:只要有两个渠道向用户传递同一件事,就必须让它们看到同一份口径。我的做法是在处理结果页直接展示「系统将发给用户的通知文案」,让客服照着复述。这个改动成本极低但直接消掉了一类投诉。
踩过的坑三:金额输入框没有二次确认,客服多打了一个零。提交后直接发起了退款。教训是:涉及资金且难以撤回的操作,确认环节必须要求一个「动作」而不是一次「点击」——因为手滑的人往往会连着手滑第二次(他的注意力本来就不在这上面)。
没做的部分:没做批量退改处理。客服提过(航班取消时有一批订单要退),但每单的规则快照、时区、已使用部分都不同,批量意味着用同一套判断处理不同情况,风险很高。我的替代方案是「按航班筛选出这批订单,逐单处理但保留上一单的原因填写」——减少重复输入而不是跳过判断。大批量的场景应该由运营侧走专门的批量退改流程(带审批),不该由客服在处理台上完成。
特批率是这个模块最核心的指标,而且要按原因分类拆开报。只报总体下降说明不了问题。报法是「特批率从 X 降到 Y,其中『规则争议』类占比从 A 降到 B」——因为这一类正是「客服解释不了」造成的,它的下降才能归因到依据展示这个改动。
特批金额也要报,不能只报次数。次数下降但金额上升说明结构变了,要分开看。
金额争议类工单数下降,需说明工单分类口径与同期其他变更。
二次确认的拦截次数是一个很有意思的指标:报「因二次确认而被取消的操作数」。它直接证明这个环节在起作用;如果长期为零,反而要警惕它是否被无脑点过。
试算一致性用断言型用例:断言页面展示的试算明细与提交后实际扣费完全一致。这条要覆盖档位分界点,因为不一致最容易出现在边界上。
额度分级:断言小额可当场批、中额触发审批流、超额被拒绝并提示需要更高权限。要用不同角色的账号各测一遍。
口径一致:断言处理结果页展示的通知文案与实际发给用户的文案相同。这条容易被忽略但正是踩坑的回归。
留档完整性:断言每次操作都留下了规则快照、试算结果、表单取值、原因、操作人;特批出现在独立的审计列表中。
不要报什么:不要报「客服满意度提升」——受太多因素影响。该报的是「特批率按原因分类的下降」「试算明细与实际扣费在分界点上一致」「额度分级按角色生效」「结果页文案与实际通知一致」这几件可核对的事。
出行人表单看起来是最普通的表单:姓名、证件类型、证件号、出生日期、有效期。但它的错误代价在所有表单里几乎是最高的——填错了不是页面报错,是用户到了机场上不了飞机。四个问题。
一是校验太弱,明显错误的信息能提交成功。我最初只做了「非空」和「长度」校验。结果是身份证号打错一位也能提交(校验位算不上)、护照号填成了身份证号也能提交。这些错误在出票时被供应商拒绝,或者更糟——出票成功了但和证件对不上,到机场才发现。
二是字段之间不做交叉校验。身份证号里本身包含出生日期和性别,而用户手填的出生日期和性别可能和证件号对不上(打错、或者从别的地方复制过来的)。两处都填了、格式都对,但互相矛盾——我完全没校验。
三是有效期和年龄按下单日期算,这是最严重的一个。护照有效期我只校验了「晚于今天」。但很多目的地要求护照剩余有效期覆盖出行后一段时间——出行日期在三个月后的行程,护照还有四个月有效期,按我的校验能过,实际上入境可能被拒。年龄的问题同理而且更隐蔽:一个孩子下单时未满岁数、但出行日期已经超过了生日,按儿童票买了,出票后被拒,用户要重新买成人票还要付差价。
四是错误提示只说「格式不正确」。用户不知道哪里不对、不知道为什么要这样填。护照有效期的提示如果只说「有效期不符合要求」,他会以为是系统有问题,反复修改再提交;而如果告诉他「护照有效期需在出行日期后至少半年,否则可能无法入境」,他会去换护照或者改行程。
所以四个改动:校验规则按证件类型配置化并实现各自的算法、补充字段间交叉校验、有效期与年龄按出行日期判定、错误提示说明原因与后果并改为失焦逐项校验。
这个模块最想说的一句话是:判断一个校验该不该做、该做多严,标准是「这个错误会在什么时候被发现、那时的补救成本有多高」。普通表单填错了,用户刷新重填就行;而出行人信息填错,用户是在机场发现的——那时候的补救成本是改签费、是行程取消、是一次旅行没了。所以这个表单值得做得比一般表单严格得多,甚至值得为了拦住错误而牺牲一点填写流畅度。
把校验做得比一般表单严格得多,代价是填写流畅度下降。产品担心校验太严会影响转化。我的依据是「这个错误会在什么时候被发现、那时的补救成本有多高」:普通表单填错了刷新重填就行;而出行人信息填错,用户是在机场发现的——那时的补救成本是改签费、是行程取消、是一次旅行没了。所以这里值得为了拦住错误而牺牲一点流畅度,甚至值得多一次确认。但我没有把所有校验都做成硬阻断——见下一条。
提示分「硬性阻断」和「风险提示」两级,而不是一律阻断。一律阻断最安全,但护照有效期的要求因目的地而异(有的要求半年、有的三个月、有的只要覆盖行程),我们不一定掌握全部目的地的准确规则。按最严标准硬拦会挡住本来没问题的用户。所以做法是:明确违反格式规则的硬阻断(比如校验位算不上),规则可能因目的地而异的给风险提示并要求确认。判据是「我们的规则数据是否足够确定」——不确定的地方,提示比阻断合适。
校验时机选「失焦逐项 + 提交时全量」而不是「输入时实时」。输入时实时校验的问题是用户还没填完就开始报错(输入身份证第三位就说格式错误),干扰很大。失焦时校验既及时又不打断。提交时再全量跑一遍是为了覆盖交叉校验(依赖多个字段都填完)。
规则做成配置而不是代码分支。散落的条件判断的问题是新增证件类型要改多处、而且必然漏改某处——而漏改的那处就是错误的来源。配置化的代价是要设计配置结构(校验项、正则、算法引用、提示文案)。判据是「这类规则会不会持续增加」:会的话就必须配置化。
踩过的坑一:年龄按下单日期算。一个孩子下单时未满岁数、出行日期已经过了生日,按儿童票买,出票后被供应商拒绝。用户要重新买成人票并付差价,而责任在我们,最后是我们承担了差价。教训是:任何和「时间」有关的资格判定,都要先确认「基准时间点是哪一个」——下单时间、出行时间、支付时间在这里是三个不同的答案,而我当时用了最顺手的那个(当前时间)。这类错误在测试时看不出来,因为测试数据的两个日期通常离得很近。
踩过的坑二:护照有效期只校验「晚于今天」。出行在三个月后、护照还剩四个月的用户能顺利下单,入境时被拒。教训和上一条同源:判定基准错了。而且这一条更容易被忽略,因为「有效期要晚于今天」听起来完全合理。
踩过的坑三:错误提示只写「格式不正确」,用户反复提交然后打客服电话。客服也说不清具体要求。教训是:校验提示的作用不只是「阻止提交」,还要「告诉用户怎么做才对」——尤其当正确做法需要他去做线下的事(换护照、改行程)时,不说清后果他不会去做,只会以为是系统问题。
没做的部分:没做证件照片的自动识别录入。它能显著减少手工输入错误(而手工输入是错误的主要来源),但需要接识别服务、处理识别错误的确认流程、还涉及证件照片的存储与合规。我把它作为后续建议提了,并用「因证件信息错误导致的改签数」作为价值依据。
核心指标是「因证件信息错误导致的改签或拒登反馈数」。它直接对应这个模块要解决的问题。报法要说明数据来源(客服工单分类还是订单异常标记)和统计区间,并说明同期是否有其他变更。
出票被供应商拒绝的数量也是一个有效指标(证件信息不合格会在出票时被拒)。这个数字比客服工单更客观。
校验算法的正确性用断言型用例,这是最基础也最重要的一组:准备各类证件的合法与非法样例(非法样例要包含「只错一位」这种),断言校验结果正确。身份证校验位算法要专门测——它是最容易实现错的。
交叉校验:构造「身份证号内含的出生日期与所填出生日期不一致」的用例,断言被拦下;性别同理。
基准日期的用例是这个模块最值得写的:构造「下单时未满岁数、出行时已满」的用例,断言按成人票要求;构造「护照晚于今天但不满足出行日期后的有效期要求」的用例,断言给出提示。这两条是踩坑的直接回归,而且测试数据必须刻意把两个日期拉开。
提示文案的验证:断言每一条校验失败都给出了「原因 + 后果」而不是「格式不正确」。这条可以做成清单逐项核对。
常用旅客的复校验:用一条保存时有效、现在已过期的护照数据,断言选择后仍被校验拦下。
粘贴清理:粘贴带换行、全角字符、不可见字符的内容,断言被自动清理后校验通过。
不要报什么:不要报「校验准确率」——这个说法没有明确的分母。该报的是「因证件错误导致的改签或拒登反馈数下降」「出票被拒数下降」「各证件类型的合法与非法样例断言通过」「跨基准日期的年龄与有效期用例通过」这几件可核对的事。
没有匹配的内容,换个关键词试试。
项目拆解 · 订单客服工作台(Vue · 实习级)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据