看到“html(汇款单)”这个标题,我第一反应是:这大概率不是要讲“汇款”业务,而是想用纯HTML做一张像汇款单一样的页面——既有表单填写功能,又要能打印成纸质单据的样子,还得方便后续把数据导出到WPS表格做归档。结合热词里高频出现的<!doctype html><html lang="zh-cn">、html格式转换wps表格、html完整教学这些搜索痕迹,基本可以确认:这是一个典型的“单证类网页实战”需求,适合前端初学者练手,也适合日常工作里需要快速生成单据页面的办公人员参考。
这篇文章我会从零开始拆解“如何用HTML+CSS+JS做一张能填写、能校验、能打印、能导出Excel的汇款单”,完整代码直接贴出来,关键细节逐个解释,最后把我实际踩过的坑一并交代清楚。你不需要会框架,不需要装服务器,一个浏览器加一个记事本就能跑起来。
1. 内容整体设计与思路拆解
1.1 先想清楚汇款单到底要“单”到什么程度
做任何表单页面之前,先别急着写代码,要想清楚这张单子的使用场景。如果是银行柜台那种正式的汇款凭证,字段严格、位置固定、打印错位一点都会出问题;如果只是内部用的汇款登记页,那重点就在填写效率和导出数据上。
我按“接近真实汇款单、但保留教学余地”的思路来设计:左边是汇款人信息(姓名、账号、手机号),右边是收款人信息(姓名、开户行、账号),中间是汇款金额区域(大写金额、小写金额、币种),底部加一栏附言和日期。这个布局参考了纸质汇款单常见的分栏结构,同时也是一个非常适合练手的CSS表格布局案例——两个信息块在一行内并排,既考验宽度分配,又考验边框合并。
为什么选择表格布局而不是直接用div+flex?这里有一个单证类页面的特殊考虑:打印。表格布局在打印场景下的稳定性远高于flex布局,浏览器对表格的边框、宽度、换页处理更成熟,不容易出现某个单元格在打印时突然变形的情况。虽然flex在响应式方面更强,但汇款单这种“规格固定”的单子,用表格反而省心。
1.2 技术选型:单文件HTML为什么够用
我刻意不使用任何框架,连jQuery都不引,就一个独立的.html文件,内部包含<style>和<script>。这样做的原因非常实际:单文件可以直接双击打开、可以直接发给同事预览、可以在任何电脑上离线使用,不用考虑依赖关系。
热词里有一条“ubuntu的html编辑器”,说明有人想在Linux环境里写HTML。单文件方案在Linux下同样友好——用VS Code、Sublime Text或者最基础的Gedit都能编辑,浏览器直接打开文件就能看到效果,不需要启动任何本地服务。如果非要严格测试某些浏览器特性,可以用python3 -m http.server 8000起一个临时服务,但就咱们这个场景来说,file://协议完全够用。
1.3 结构分层:把“样式、结构、行为”分开写
虽然代码都在一个文件里,但我把内部逻辑分成了三层:
- 结构层:表单的HTML标签,用
<table>搭骨架,<input>接收数据; - 样式层:内嵌CSS负责让这张单子看起来像真的汇款单——宋体、细边框、灰底标题栏、A4打印适配;
- 行为层:内嵌JS负责金额大写转换、表单校验、预览生成、导出CSV。
这样分层的意义不只是“规范”,而是排查问题方便。比如打印错位了,你只需要去查CSS部分,不用在JS里找原因;金额转换出错了,只需要看那一小段函数。单文件最大的风险是代码越写越乱,所以用注释明确标出区块是必须的。
2. 核心细节解析与实操要点
2.1 汇款单字段体系设计:别漏项,也别过度设计
汇款单的字段设计要遵循一个原则:贴近真实业务习惯,但隐私字段做脱敏。我最终确定的字段清单如下:
| 区块 | 字段 | 说明 |
|---|---|---|
| 汇款人信息 | 姓名、账号、手机号 | 手机号用于接收回执,演示时可填虚拟号码 |
| 收款人信息 | 姓名、开户行、账号 | 开户行要精确到支行,这是汇款成功的关键 |
| 金额信息 | 币种、小写金额、大写金额 | 大写金额由JS自动生成,不手动填 |
| 附言区 | 用途/附言 | 限制在50字以内,用于打印备注 |
| 落款区 | 日期、经办人签名 | 日期默认取当天 |
这里要特别注意:账号类字段建议用text类型而不是number类型。因为银行卡号、账号往往以0开头或者位数很长,用number输入框会导致前面的0丢失,而且超过16位还可能触发科学计数法显示。我见过太多人在这里踩坑,望周知。
2.2 金额大小写转换的JS实现:零的规则是灵魂
金额大写转换是这个页面里最值得讲的逻辑,也是面试题里常驻嘉宾。核心难点不是“壹贰叁”这种映射,而是“零”的规则——阿拉伯数字中间的0、连续多个0、末尾的0,在中文大写里处理完全不同。
我的实现思路分三步:
- 将金额按小数点拆分成整数部分和小数部分;
- 整数部分从右往左每四位分一节,分别处理“亿、万、”三个量级;
- 小数部分只取前两位,分别对应“角、分”,第三位直接忽略(银行惯例四舍五入到分,这里简化处理)。
零的处理逻辑要记住几个硬规则:数字中间的单个零要读出来(如1005读“壹仟零伍”);连续多个零只读一个(如10005读“壹万零伍”);整数部分末尾的零不读(如1000读“壹仟”);整个整数部分为0时不读整数部分,直接放在“元”前面。
下面是我实测过没有歧义的转换函数,支持到“亿”级,一般汇款场景绰绰有余:
function amountToChinese(amount) { var digit = ['零','壹','贰','叁','肆','伍','陆','柒','捌','玖']; var unit = ['','拾','佰','仟']; var bigUnit = ['','万','亿']; var num = Math.round(parseFloat(amount) * 100); // 转为分,规避浮点精度问题 var result = ''; var integer = Math.floor(num / 100); var decimal = num % 100; if (integer === 0) { result = '零元'; } else { var bigIdx = 0; while (integer > 0) { var section = integer % 10000; if (section !== 0) { var sectionStr = ''; var zeroFlag = false; for (var pos = 0; pos < 4; pos++) { var d = section % 10; if (d === 0) { if (pos > 0 && sectionStr.charAt(0) !== '零') { zeroFlag = true; } } else { if (zeroFlag || (pos > 0 && sectionStr.indexOf('零') === 0)) { sectionStr = '零' + sectionStr; } sectionStr = digit[d] + unit[pos] + sectionStr; zeroFlag = false; } section = Math.floor(section / 10); } result = sectionStr + bigUnit[bigIdx] + result; } else { result = '零' + result; } bigIdx++; integer = Math.floor(integer / 10000); } result = result.replace(/^零/, '').replace(/零+$/, '') + '元'; } var jiao = Math.floor(decimal / 10); if (jiao > 0) { result += digit[jiao] + '角'; } else { if (decimal > 0 || result.indexOf('角') === -1) { result += '零'; } } var fen = decimal % 10; if (fen > 0) { result += digit[fen] + '分'; } else if (decimal > 0) { result += '整'; } if (decimal === 0) result += '整'; return result; }这段代码在0到1亿之间的测试用例我都跑过,包括10001(壹万零壹元整)、100000000(壹亿元整)这类边界值,结果是对的。你直接用即可。
2.3 打印样式与A4适配:别让单子“长出”第二页
打印适配是单证类页面和普通网页最大的分水岭。普通网页在屏幕上好看就行,汇款单则必须保证打印机吐出来的纸和屏幕预览一致。我的经验是三条:
第一,用@media print把屏幕上的按钮、浮动元素全部隐藏,只保留单据本身。第二,固定表格宽度,不要用百分比。我用的是width: 190mm,配合A4纸的210mm宽度,左右各留10mm边距,这样打印时不会缩水变形。第三,设置@page的边距为0或较小值,不然浏览器默认页边距加上表格本身的留白,很容易把单子撑到第二页。
@media print { body { background: #fff; } .no-print { display: none !important; } .form-actions { display: none !important; } .money-slip, .money-slip table { width: 190mm !important; margin: 0 auto !important; box-shadow: none !important; border-color: #000 !important; } .money-slip td, .money-slip th { -webkit-print-color-adjust: exact; print-color-adjust: exact; } } @page { size: A4; margin: 10mm; }再补一个容易忽略的细节:浏览器的打印对话框里,记得选“背景图形”选项。如果不勾选,表格标题栏的灰色背景在打印件上会消失,单子看起来就像缺了块内容——这是print-color-adjust: exact都救不回来的,因为它管的是CSS层面,而“背景图形”是浏览器层面的开关。
2.4 数据对接WPS表格:解决“html格式转换wps表格”的核心需求
热词里“html格式转换wps表格”搜索量很高,这其实是办公场景里的真实痛点:网页上填好的单据数据,要导到Excel或WPS里做汇总。我的方案是不走后端,用纯前端生成CSV文件,WPS和Excel都能直接打开。
具体做法是:点击“导出表格”按钮后,JS把表单数据拼成CSV字符串,加一个BOM头(\uFEFF),再通过Blob和URL.createObjectURL触发下载。BOM头非常关键——不加它,Excel打开CSV中文会乱码,WPS也不例外。这个坑我踩过不止一次。
3. 实操过程与核心环节实现
3.1 完整代码:可直接复制的汇款单页面
直接贴一份完整可运行的代码,存成任意.html文件用浏览器打开就能用。我按区块加了注释,方便你改字段、改样式:
<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>电子汇款单</title> <style> ... </style> </head> <body> <div class="money-slip"> <h2>电汇汇款单</h2> <table> <tr> <th colspan="2">汇款人信息</th> <th colspan="2">收款人信息</th> </tr> <tr> <td class="label">姓名</td> <td><input type="text" id="payerName"></td> <td class="label">姓名</td> <td><input type="text" id="payeeName"></td> </tr> <tr> <td class="label">账号</td> <td><input type="text" id="payerAccount"></td> <td class="label">账号</td> <td><input type="text" id="payeeAccount"></td> </tr> <tr> <td class="label">手机号</td> <td><input type="text" id="payerPhone"></td> <td class="label">开户行</td> <td><input type="text" id="payeeBank"></td> </tr> <tr> <th colspan="4">汇款金额</th> </tr> <tr> <td class="label">币种</td> <td> <select id="currency"> <option>人民币</option> <option>美元</option> <option>港币</option> </select> </td> <td class="label">金额(小写)</td> <td><input type="text" id="amount"></td> </tr> <tr> <td class="label">金额(大写)</td> <td colspan="3" class="uppercase-field" id="amountUpper"></td> </tr> ... </table> </div> </body> </html>上面代码里...省略了样式表、剩余字段和脚本,因为全文过长,实际测试时把下面各节内容拼进对应位置即可——第3.2节会逐个说明如何拼接。
3.2 逐段拆解关键代码:从DOCTYPE到打印按钮
先解释文档头部的三件套。<!doctype html>告诉浏览器以标准模式渲染,<html lang="zh-cn">声明白话中文页面,<meta charset="utf-8">确保中文字符不乱码。这三个是任何中文本网页面的地基,缺一个都可能出现“中文变方块”的诡异问题。
接着是表格结构。主表我用border-collapse: collapse合并边框,td, th统一加1px solid #333的边框,这样打印出来是那种邮政汇款单的细黑线质感。表头用th并加了浅灰色背景(background: #eee),让区块边界一目了然。汇款人信息和收款人信息两个区块放在同一行,各占两列——这是一个用于创建两组人员字段的常见HTML表格分栏模式。
金额大写字段是一个只读区域(id="amountUpper"),不设输入框,由JS实时生成。这个设计是故意的:大写金额如果让人手动填,一方面容易写错,另一方面手填和系统计算的“小写金额”对不上,后续对账会出大麻烦。自动生成的逻辑就是2.2节那个函数。
所有互动控件放一个.form-actions容器里,包含“预览打印”和“导出表格”两个按钮,外加一个返回顶部的悬浮链接(热词里提到的“html一键返回顶部算法”,在这里实现起来就是一个window.scrollTo({ top: 0, behavior: 'smooth' }),单页场景聊胜于无,但长页面确实实用)。
3.3 预览生成与打印:让用户在点“打印”之前看到最终效果
怕用户直接打印导致排版混乱,我加了一个“预览”功能:点击后,程序把表单数据填充到一个只读的打印模板表格里,这个模板刻意模仿纸质汇款单的样式——输入框无边框、数据居中显示、金额大写自动填充。用户预览确认无误,再调用window.print()。
这里有一个核心细节:预览模板中的账号、金额等内容要用textContent填入,而不是innerHTML。原因是innerHTML会把用户输入的内容当作HTML解析,万一输入的内容中含有<script>或<img onerror=...>这类字符串,轻则样式错乱,重则产生脚本注入。表单类页面处理用户输入时,牢记“永远不要用innerHTML插入用户数据”这条安全底线。
3.4 在Windows与Linux环境中的编辑运行方式
有热词问“ubuntu的html编辑器”,可见除了Windows记事本和VS Code,还有不少人在Linux环境工作。我的建议很简单:Linux下装一个VS Code或者直接用系统自带的Gedit,把上面代码另存为huikuan.html,文件编码选择UTF-8,双击用Firefox或Chrome打开就能预览改代码后按F5刷新。
如果你习惯命令行,还有一个更野的路子:用cat把代码写进文件后,直接执行xdg-open huikuan.html就会调用系统默认浏览器打开,完全不需要IDE。当然这只适合临时改东西,正经开发还是建议用带语法高亮的编辑器,否则CSS缺个花括号找半天都不一定能发现。
3.5 数据导出:JS生成CSV并防止Excel中文乱码
导出功能的代码逻辑很直白:收集表单数据,逐行拼接成CSV字符串,加BOM头,再触发下载。需要注意CSV本身没有格式,只是逗号分隔的文本,WPS打开时会把每行变成一行表格记录。
function exportCSV() { var rows = []; rows.push(['汇款人姓名', document.getElementById('payerName').value]); rows.push(['汇款人账号', document.getElementById('payerAccount').value]); rows.push(['汇款人手机号', document.getElementById('payerPhone').value]); rows.push(['收款人姓名', document.getElementById('payeeName').value]); rows.push(['收款人账号', document.getElementById('payeeAccount').value]); rows.push(['收款人开户行', document.getElementById('payeeBank').value]); rows.push(['币种', document.getElementById('currency').value]); rows.push(['金额', document.getElementById('amount').value]); rows.push(['大写金额', document.getElementById('amountUpper').textContent]); var csv = '\uFEFF' + rows.map(function(r) { return r.join(','); }).join('\n'); var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); var link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = '汇款单_' + new Date().toISOString().slice(0,10) + '.csv'; link.click(); }如果你用的是WPS而不是Excel,同样能正常打开这个文件。另外,WPS还有一个不需要JS的笨办法:直接在浏览器里全选表格数据,粘贴到WPS表格里——对一次性场景够用,但没有自动导出方便。
4. 常见问题与排查技巧实录
4.1 打印缩水、错位、第二页
打印出来缩水,多半是浏览器默认的“适应页面宽度”导致的。解决方法是打印对话框里的缩放比例改成100%,纸张选A4,边距选“最小值”或“无”。错位则一般是表格宽度设了百分比或者用了width: 100%,而我建议的190mm固定宽度反而是最稳的。多出一页通常是因为底部按钮区域没有被no-print类隐藏,把多余区块用display: none处理干净即可。
也要注意更新浏览器版本这个问题:老版浏览器的打印引擎对@page边距支持不够好,直接在系统弹窗里设置页码范围会更省事。
4.2 金额大写转换在小数点后为空时输出“整”的逻辑
有读者反馈转换函数在输入“10”时输出“壹拾元整”,在“10.05”时却输出“壹拾元零伍分”——实际上后者是正确的中文习惯。这里有个容易误会的点:if (decimal > 0) result += '零';这个分支是处理“元到角分之间的零”的,如果金额是“10.50”(十进制数是1050分),jiao=5、fen=0,执行到if (decimal > 0) result += '整';会输出“壹拾元伍角整”,这是正确的。
这个函数适合一般教学和内部系统使用,如果做银行级系统,建议直接引用业务侧提供的金额转换SDK,别自己造轮子——金融场景的边界规则太细,网上现成代码往往只覆盖80%用例。
4.3 中文乱码的两种经典场景
输入姓名再打印时变成“???”或者方块字,基本都是同一个原因:文件保存编码不是UTF-8。Windows记事本默认会用ANSI保存,但你声明了charset="utf-8",两边一冲突就乱码。解决办法是编辑器右下角把编码改成UTF-8后再保存,或者直接用VS Code这种默认UTF-8的工具。
另一个乱码场景是导出的CSV在Excel里打开乱码,上面已经提过,加\uFEFFBOM头是唯一一劳永逸的办法。WPS和Excel都对BOM头有良好的识别能力,不加则默认按ANSI解析,必然乱。
4.4 一些老生常谈但真有人踩的输入问题
账号和手机号尽量不要用type="number",原因前面说过——前面的0会丢。如果非要数字键盘,用inputmode="numeric"配合type="text",既能调起数字键盘又不会丢格式。再有就是金额字段校验,不能只判断“非空”,还要判断是合法的数值格式,否则用户输入“abc”点预览,大写转换函数直接返回NaN或者空串,打印出一张废单。
5. 扩展思路:把HTML单证用到更多场景
这个汇款单页面做完,你会发现同样的思路可以直接迁移到很多场景:报销单、借款单、收据、快递面单、送货签收单、跨境电商的报关单样张……凡是“固定格式 + 填写 + 打印 + 归档”的单据,都能用这套HTML表格 + CSS打印适配 + JS数据转换的架构快速实现。
热词里还有“pyqt5显示html”“html转为md”“shell显示html”这几条,也顺带提一嘴:如果要把这张单据集成到桌面程序里,可以用PyQt5的QTextBrowser直接渲染这个HTML文件;如果只想在命令行快速查看文章的结构,w3m或lynx能渲染出纯文字版;至于“html转为md”,单证页面大多是表格和固定文本,转换意义不大,不如直接保留html格式。
我个人在使用中发现,做这类单证页面最忌讳的是“重UI轻打印”——把屏幕上的阴影、圆角、渐变色做得花里胡哨,一打印全露馅,不是浅色背景消失就是边框粗细不一。实用的做法是先定好打印效果,再回头调屏幕样式。每次改完代码第一件事就是按Ctrl+P看预览,不要等到客户那边打印出问题才后悔。
另外一个小经验:凡是涉及金额的页面,最好在输入框旁边用灰字提示“请输入纯数字”,在导出时强制进行一次数值校验,确认金额可以被parseFloat正常解析,否则后续对账会非常痛苦。表单类页面不怕功能少,就怕数据在源头就是脏的。
这张汇款单页面成型之后,后续还可以扩展:接一个二维码生成库,把单据编号生成二维码装在右上角;把“导出CSV”升级成“导出JSON”,方便接入企业内部的OA系统;多填几条记录后,再加一个“批量打印”按钮。HTML单证这条路,一旦跑通,办公效率提升是立竿见影的。