乐于分享
好东西不私藏

没有服务器,没有App,只有一把扫码枪:我在浏览器里搭了个礼宾系统

没有服务器,没有App,只有一把扫码枪:我在浏览器里搭了个礼宾系统

作者:Roggger,文旅行业"野生"产品经理 & 研发

背景:身处酒店业务一线的效率优化者,既懂前场运营的“毛细血管级”痛点,也具备从需求洞察到代码落地的全栈能力。

楔子:

那个揉太阳穴的瞬间

一线酒店数字化提效说是“产品经理”,其实更像一个“翻译”——把一线员工的痛苦翻译成代码,再把代码翻译回他们看得懂的绿勾红叉。站在前台旁边,看他们接电话、办入住、寄存行李、收快递……那些看似琐碎的日常操作里,藏着太多无声的叹息。

有一次,一位前台小姑娘在Excel里翻了半天的快递记录,客人已经在柜台前站了三分钟。她终于找到了,抬头抱歉地说:“不好意思,刚才录的时候少了一位数字……”客人笑了笑说没事,但我看到她转身时悄悄揉了揉太阳穴。

那一刻我突然意识到:不是她们不努力,是工具配不上她们的勤奋。

这篇文章不讲高大上的架构,只讲一个真实的、从零到一的工具诞生记。它或许不完美,但它确确实实让一群人的工作变得轻松了一点。 

一、 那些被

Excel和纸片困住的日子

快递:

一场每天都在发生的“寻宝游戏”

很多酒店礼宾都有一张《快递代收登记表》,通常是Excel文件共享在局域网里。流程看似简单:快递员送来包裹 → 前台打开Excel → 找到最后一行 → 手动输入运单号、收件人、房号、签收时间 → 保存。

但实际执行中,每一步都像踩在泥潭里。

配合度低——高峰时段礼宾忙得脚不沾地,快递员催着签字,客人等着问路,电话响个不停。谁还记得要打开那个Excel?于是随手扯一张便签纸写上运单号,往抽屉里一塞,想着“下班再补”。结果下班时那张纸已经不知道夹在哪本登记簿里了。

容易出错——运单号动辄十几位数字,手输一遍就像在背圆周率。少一位、多一位、顺序颠倒,都是家常便饭。等到客人来取件时,礼宾对着Excel瞪大眼睛找了半天,最后无奈地问:“先生,您还记得是哪家快递吗?”客人一脸茫然。

查找困难——Excel没有搜索功能(除非用Ctrl+F),几百行记录翻起来眼花缭乱。客人报个名字,礼宾要眯着眼睛扫半天,嘴里念念有词:“王……王……哪个王?三横王还是草头黄?”

无法追踪——哪些快递已经取走了?哪些滞留超过三天?Excel里全靠肉眼判断,没人维护。有时候一个包裹放了半个月,礼宾才发现:“咦,这个好像还在?”

行李:

四毛钱的卡片,一万二的账单

行李寄存用的是传统的纸质卡片(一式两联,客人一联,酒店一联)。每张卡片成本约四毛钱,听起来不多,但一家中等规模的酒店一年消耗约三万张,光物料费就一万两千多元。这还不算印刷费、运输费、仓储费。

更让人心疼的不是钱,是时间。

办理寄存时,前台要手写房号、姓名、件数、日期,平均耗时30秒。高峰期排队时,这个时间会被无限放大。客人赶着去开会,前台手忙脚乱,字迹潦草到第二天自己都不认识。

卡片丢失更是家常便饭。客人弄丢卡片的情况每至少发生五六次,每次都要反复核实身份,耗时耗力。有一次,一位客人坚持说自己寄存了三件行李,但卡片上只写着“2件”。双方争执不下,最后调了半小时监控才确认是客人记错了。那位前台事后跟我说:“我当时真想辞职。”

还有环境账——每年三万多张纸卡,相当于砍了两棵树。虽然听起来不大,但每一张卡片从生产到废弃,都留下了碳足迹。

更关键的是,这些数据无法被数字化利用管理人员想统计行李寄存量、平均寄存时长、热门时段分布,根本无从下手。所有的知识都藏在那一摞摞泛黄的卡片里,随着时间腐烂。

为什么

不去买一套系统?

市面上确实有专业的管理系统,价格从几万到十几万不等,还需要部署服务器、培训员工、迁移数据。对于单体酒店来说,这笔投入很难通过效益衡量来批准。IT部门的排期更是遥遥无期——他们说“这个需求已经排到下个季度了”,但下个季度永远在下个季度。

一线员工的真实诉求很简单:一步能搞定,最好别打字。这句话我听了不下二十遍。每一次听到,心里都咯噔一下。因为我们总是习惯于给用户“我们认为好的”方案,却忘了问他们真正想要什么。

二、为什么是油猴?

——一个“不正经”的技术选型

三根“紧箍咒”

礼宾部的电脑有几道“紧箍咒”,让我不得不放弃所有“正统”的技术路线。

第一道:必须一直开着浏览器。  因为要登录停车管理系统(网页端),浏览器就是他们的工作台。你不能让礼宾关掉浏览器去打开另一个应用程序,那会增加认知负担。

第二道:电脑性能很差。  大多是5-10年前的办公机,开机五分钟,打开三个网页就开始风扇狂转。如果再装一个独立的桌面应用,估计礼宾会拿着键盘来找我拼命。

第三道:网络受限。  只能访问内网和特定外网域名,不允许随意安装软件。IT部门的安全策略严格得像银行金库。

油猴脚本:那个被低估的“瑞士军刀”

油猴脚本(Tampermonkey)就像是为这种环境量身定做的救星:

零安装——浏览器插件,一次配置永久生效甚至感觉不到它的存在,直到扫码枪响起。

轻量级——不占用额外进程,只在页面加载时注入几KB的JavaScript。电脑不会因此变得更卡。

跨域能力——通过GM_xmlhttpRequest可以调用外部API(企微Webhook),绕过了同源策略的限制。

持久化存储——GM_setValue可以把数据存在浏览器本地,不怕刷新丢失。这对于记录核销状态至关重要。

企微Webhook:

那个被我“薅羊毛”的后端

传统做法需要自建后端服务,暴露API供前端调用。但酒店没有运维人员,也不想维护服务器。我曾经想过用云函数,但一想到要教前台配置环境变量,就果断放弃了。

企微智能表格的Webhook功能像一个天降神兵。我第一次读到它的文档时,差点从椅子上跳起来。

免鉴权——每个Webhook自带一个key,不需要access_token,不存在过期问题。你只需要把这个key藏在脚本里,谁也偷不走。

双向操作——支持add_records(新增)和update_records(更新),完全满足我们的核销场景。特别是update_records,它只修改你指定的字段,不会覆盖其他数据,这对多人协作的场景太重要了。

零成本——企微自带功能,无需额外付费。老板听到“免费”两个字,眼睛都亮了。

数据即平台——表格本身就是数据库,管理层可以直接在企微上查看报表,不需要另建后台。我甚至可以用企微的自动化流程给客人发取件提醒。

核心决策就这样定了:用企微做后端,用油猴做前端,前后端分离,成本趋近于零,安全性极高(key只存在于脚本中,不暴露给用户)。

为什么不用access_token? 

很多人问我:“直接用企微API不好吗?为什么要绕一圈用Webhook?”

答案是:我不想给自己找麻烦。

 access_token有7200秒的有效期,过期就要重新获取。这意味着我需要一个定时任务去刷新它,或者在每次调用前检查是否过期。更麻烦的是,获取access_token需要corpid和corpsecret,这两个东西一旦泄露,后果不堪设想。

 而Webhook的key是静态的,永远不会过期。它只能操作绑定的那张表格,权限范围极小。即使泄露了,也只是那一张表的数据可能被篡改,不会影响到整个企业微信。

安全性和易用性之间,我选择了前者。而且,它真的很好用。

三、做减法:

我只保留了这两个功能 

克制,

是因为见过太多“万能废物” 

很多产品经理容易犯的错误是想做大而全。我给自己定了一条铁律:只解决“扫码录入”和“二次核销”这两个核心场景,其他一切功能砍掉。

不做报表统计(企微表格自带视图功能)

不做权限管理(油猴脚本只在指定页面运行,且只有装了插件的电脑能用)

不做离线缓存(网络断了就提示,不搞复杂同步)

为什么要这么克制?因为我见过太多“万能工具”最后变成“没人用的工具”。前台员工每天面对的系统已经够多了——PMS、停车系统、发票系统、考勤系统……再多一个,她们真的会崩溃。

所以我决定,让这个工具像一个隐形的助手:它不改变任何工作流程,只是在扫码枪响起的那一刻,默默地把事情办好。一线员工不需要学习新东西,不需要记住新步骤,只需要像往常一样拿起扫码枪,“嘀”一声,然后继续做手头的事。

字段设计:

每一个“可留空”背后的故事 

企微表格的字段设计非常灵活。我根据实际业务做了精简,每一个字段的选择背后都有故事。

行李表(6个字段):

你可能注意到了,房号和姓名都允许留空。这不是疏忽,而是刻意为之。

因为真实场景中,客人可能只报了手机号没报房号,或者快递员放下包裹就走了。如果强制必填,员工会嫌麻烦而放弃使用。产品的底线是“能用”,然后才是“好用”。

状态字段:

为什么首次不填? 

状态字段的设计是整个方案的灵魂。首次扫码时,我不传状态字段,让它保持空白。二次扫码时,我才通过update_records将状态改为“已取”。

为什么不在首次就写入“待取”?因为我想让表格的视觉更干净——空白意味着“待处理”,绿色标签意味着“已完成”。前台看一眼就知道哪些还没处理完。

而且,Webhook的update_records只修改指定字段,不会覆盖其他数据。这意味着即使后来有人手动改了表格里的其他列(比如备注),我们的核销也不会影响它们。这在多人协作的场景下非常重要。

四、只有一个输入框

——我故意的 

符合直觉的设计 

整个工具的核心交互就是一个输入框 + 一个提交按钮。没有下拉菜单,没有选项卡,没有复杂的配置页面。员工只需要记住一件事:扫完码按回车

为什么这么设计?  因为礼宾部的工作节奏非常快,员工常常一只手接电话,另一只手操作电脑。如果界面元素太多,他们在忙乱中很容易点错。一个输入框,一个按钮,大脑不需要任何决策成本。

我记得第一次给前台演示时,她们的反应是:“就这?”我说:“对,就这。你们试试。”然后我拿起扫码枪,对着一个行李码“嘀”了一声,结果区立刻显示“✅ 上报成功”。她们瞪大了眼睛:“就这么简单?”

是的,就这么简单。

自动识别,

无需选择 

输入框背后做了两件事:

判断类型:如果输入以LUG#开头,走行李流程;否则走快递流程。

快递公司自动识别:通过正则匹配运单号前缀,自动选中快递公司。匹配不到时,才展开一个简单的下拉选择表单。

// 快递公司识别规则(部分)constCOURIER_RULES=[{ name:'顺丰速运', patterns:[/^SF\d{12}$/i,/^1188\d{10}$/]},{ name:'圆通速递', patterns:[/^YT\d{10,12}$/i]},{ name:'中通快递', patterns:[/^73\d{10}$/,/^75\d{10}$/]},// ... 共10+家];

这些规则准确率超过95%。剩下的5%让员工手动选择,成本几乎为零。

有一次,一个快递员送来一个包裹,运单号以“SF”开头。礼宾扫了一下,系统自动识别为“顺丰速运”,只需要填个收件人姓名就完成了。惊讶地说:“哇,它居然知道是哪家快递!”那种语气让我觉得,这一切都值了。

结果反馈:

大字+颜色 

结果区域用18px加粗字体,配合左侧4px彩色边条:

绿色:成功(上报成功/已核销)

红色:失败(网络错误/格式错误)

橙色:警告(已核销过的记录再次扫码)

蓝色:提示(等待操作)

为什么放大字号?  员工往往是一边接电话一边扫二维码,眼睛不能长时间盯着屏幕。大字号让他们余光一扫就知道结果,不用凑近看。

我还特意加了一个“本次扫入原始内容”的区域,把扫码枪读到的原始字符串原样显示出来。为什么?因为有时候扫码枪会漏读或误读,员工需要确认“扫进去的是什么”。有了这个区域,他们一眼就能发现异常,比如“咦,怎么少了一位数?”然后重新扫一次。

五、核销闭环:

那些让代码“靠谱”的关键设计 

Webhook的Payload结构 

企微智能表格的Webhook要求严格的字段格式。文本字段必须包装成[{type:'text', text:'值'}],数字字段直接传数值,单选字段传[{text:'选项名'}]。

首次新增(行李)

{"add_records":[{"values":{"fOtkkI":[{"type":"text","text":"6"}],"f9LDHN":[{"type":"text","text":"13800138000"}],"fV7pBU":6,"f2YkN7":[{"type":"text","text":"1001"}],"fMcNVp":[{"type":"text","text":"张三"}],"fdoGZY":"1798089600000"// 状态字段fyYoQP不传,保持为空}}]}

二次核销(只改状态)

{"update_records":[{"record_id":"rGAzx8","values":{"fyYoQP":[{"text":"已取"}]}}]}

注意:update_records只需要传record_id和要修改的字段,其他字段保持不变。这是Webhook的强大之处——不会意外覆盖已有数据。

本地缓存

与核销闭环 

为了支持二次扫码核销,必须把首次新增时返回的record_id存下来。我选择了GM_setValue,因为它不受页面刷新影响,且容量足够。

// 存储结构GM_setValue('hotel_records',JSON.stringify([{        key:'6',// 寄存码或运单号        recordId:'rGAzx8',// 企微表格中的唯一标识        action:'first',// first=已上报,second=已核销        name:'张三',        roomNo:'1001',        timestamp:1798089600000}]));

核销流程

1. 首次扫码 → 调用add_records → 从返回体中提取record_id → 存入本地

2. 二次扫码 → 从本地取出record_id → 调用update_records只改状态 → 更新本地action为second

3. 第三次扫码 → 检测到action === 'second' → 弹出确认框:“该记录已于X月X日核销,是否重新上报?” → 用户确认后删除旧记录,重新走首次流程

这个流程我反复测试了很多遍。最担心的场景是:本地缓存丢了怎么办?比如员工清空了浏览器数据,或者换了电脑。这种情况下,二次扫码就无法核销了。

我最后的解决方案是:在脚本里加一个兜底的反查机制。如果本地找不到record_id,就通过企微的get_records接口(需要access_token)去表格里按寄存码或运单号反查。虽然增加了复杂度,但至少保证了数据不丢失。

不过在实际使用中,这种情况几乎没有发生过。因为前台电脑是固定的,员工也不会闲到去清浏览器数据。

容错处理:

让脚本“皮实”一点 

行李码解析:允许房号和姓名为空,只要至少有4段(寄存码、日期、手机号、件数)即可。空字段直接跳过,不给Webhook传空值。

快递表单:所有字段均可留空。确认录入时不校验任何字段,直接上报。空字符串也会以[{type:'text', text:''}]格式写入,表格中显示为空。

网络错误:如果Webhook调用失败(超时、HTTP错误、返回errcode非0),结果区显示红色错误信息,并给出具体原因(如“Webhook无效(840001)”),方便排查。

我还特意加了一个“重试”按钮——但后来去掉了。因为前台员工遇到错误的第一反应是“再扫一次”,而不是“点重试”。所以我把逻辑改成了:如果失败,输入框不清空,员工可以按回车重新提交。这样更符合直觉。

历史记录管理 

面板上有一个“📋 历史”按钮,点击展开一个可滚动的列表,显示所有本地记录。每条记录包含:

图标(🏨行李/📦快递)

关键信息(寄存码/运单号、姓名/收件人、房号)

时间(精确到秒)

状态徽标(🟠待核销/🟢已核销)

删除按钮(单条删除)

底部有一个红色“🗑️ 清空历史”按钮,点击后弹出确认框,确认后一次性删除所有本地记录。注意:清空只影响本地,不会删除企微表格中的数据。

这个功能其实是被前台逼出来的。有一天,一个前台问我:“我能不能看看今天一共处理了多少个行李?”我愣了一下,然后意识到:对啊,她们也需要成就感。看着列表里一排绿色的“已核销”,那种满足感是无法替代的。

尾声:

那个揉太阳穴的女孩,后来笑了 

这个脚本从构思到上线只用了三天,成本为零。但它解决的问题却是酒店行业长期存在的痛点:手工登记效率低、容易出错、难以追溯

数据说话

行李寄存:从平均45秒缩短到3秒(扫码+自动录入),效率提升15倍

快递代收:从Excel登记平均60秒缩短到10秒(扫码+自动识别+手动补填),效率提升6倍

物料成本:纸质卡片年省一万二千元

员工满意度:100%的一线员工表示“比以前轻松多了”

但对我来说,最重要的不是这些数字,而是一个画面。

那天我回访时,正赶上一位客人来取行李。礼宾小哥拿起扫码枪,对着客人手里的寄件码“嘀”了一声,屏幕上立刻显示“已核销”。客人笑着说:“这么快?”礼宾小哥也笑了:“嗯,我们现在用高科技了。”

说的“高科技”,不过是一个油猴脚本而已。但在那一刻,我看到了眼里的光。

数字化转型不一定非要高大上的系统。一线员工最懂业务痛点,只要给他们合适的工具(哪怕只是一个油猴脚本),他们就能创造出惊人的价值。企微智能表格作为后端,油猴脚本作为前端,这种“轻前端+云表格”的模式,可以复制到酒店行业的很多场景:客房报修、宴会预订、员工排班……

如果你也在酒店行业,或者对类似场景感兴趣,欢迎一起交流改进。

最后,我想对所有做产品的人说一句话:别总想着造火箭,有时候,一把扫码枪和一个脚本,就能改变一群人的一天。

核心架构图

扫码枪输入 → 键盘回车事件 → processInput()                              ├── LUG开头→ 行李处理                              │               ├── 首次:add_records → 存record_id 礼宾员根据寄件码尾数挂上号码牌                              │               └── 二次:update_records(已取) 礼宾员取下号码牌并交回行李                              └── 其他 → 快递处理                                          ├── 首次:弹出表单 → add_records → 存record_id 礼宾员根据快递单信息完善资料                                          └── 二次:update_records(已取) 礼宾员发出快递本地存储:GM_setValue('hotel_records', [...])         key = 寄存码/运单号, value = { recordId, action, timestamp, ... }