ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用HTML+CSS+JS打造银行汇款单:从表单到打印实战

用HTML+CSS+JS打造银行汇款单:从表单到打印实战 很多人第一眼看到“用HTML做银行汇款单”这个需求第一反应是用Word排版不就行了或者干脆用Excel画个格子再不然用Python的ReportLab生成个PDF确实常规的道具各有各的顺手之处但HTML做到的是“一次性开发、随处可用”——打开浏览器就能填填完能直接打印还能顺手加各种校验和自动计算逻辑。我在给一个做行政的朋友帮忙时他们公司每天需要手工填写大量内部调拨汇款凭证后来我用一套纯HTMLCSSJS的页面把这活儿完全接住了从录入到打印一张A4纸搞定效率提升非常明显。这类需求其实很适合作为前端初学者的“练手鱼塘”。它不像电商首页那样追求花哨的视觉也不需要复杂框架但对手写HTML语义、表单控件、CSS表格线框、打印样式适配、JavaScript基础交互都有实打实的要求。换句话说一张汇款单页面如果做得好说明你对前端基础体系的掌握已经过关了。这篇就把我做这套“HTML银行汇款单”的完整过程拆开讲从需求分析、页面结构、样式还原到打印适配、常见坑点一步不落。文末的代码可以直接复制去改基础部分我会解释清楚不怕你跟不上。1. 需求拆解与方案选型做东西之前先把需求聊透这是少走弯路的关键。银行汇款单这类东西本质上是“填单”和普通网页表单最大的区别是它必须长得像一张正式的单据、打印出来纸面上不能乱、并且所有字段在视觉上要对齐得整整齐齐。这里先花点篇幅说说方案为什么是HTML而非其他工具。1.1 为什么选择HTML而不是Word或ExcelWord做表格确实快选个模板、填个表就完事了。但Word是“每张单子一个文件”你没法做到“打开页面自动生成今天的日期、自动把小写金额转成大写、漏填关键字段时弹提示”。一旦单量多起来人力成本就上去了。Excel倒是能加公式但打印控制尤其是边框粗细、固定A4尺寸、页边距很折磨人而且颜值和复用性都差。HTMLCSSJS的方案好处有三点实时渲染改完一个表单字段页面马上联动更新大写金额、合计、日期全部自动算好。精确打印CSS有media print规则可以为打印单独定制样式做到“屏幕上有按钮大标题打印出来只剩单据”。零成本复用扔到公司内网共享文件夹双击就是一张空单子扔到服务器就是一个在线填单系统。让新手用这个项目练手还有一个额外好处你写一遍下来table、form、CSS盒模型、伪元素、事件监听、正则表达式基本全过了一遍。1.2 汇款单字段清单与分析我参考了常见银行汇款凭证的样式把字段拆成了四大块区块字段输入控件建议单据头单据编号、汇款日期、汇款币种编号用input生成日期用input typedate币种用select汇款人信息户名、账号、开户行文本框账号加maxlength和数字限制收款人信息户名、账号、开户行同上金额与用途小写金额、大写金额、汇款用途小写金额用input typetext配合正则校验大写金额由JS自动生成用途用select带自定义选项划重点不要把“小写金额”做成typenumber。number输入框自带上下箭头打印倒是无所谓但屏幕上看就很丑而且它不支持输入“1,230.50”这种带千分位分隔符的写法用text加正则更舒服。2. DOM结构与HTML骨架搭建结构是第一位的。我习惯先写一个干净的HTML骨架无视CSS和JS只保证语义完整——这样就算后面样式崩了浏览器默认样式下页面信息依然可读。2.1 单据容器的设计整体用一个div classremittance-slip包住全部内容给它设定一个A4纸的宽度比例width: 210mm打印时正好一页。单据内部再划分header、form-body、footer三块header放标题“银行汇款凭证”、单据编号、日期。form-body分左右两栏左栏汇款人右栏收款人中间或底部放金额区。footer放用途、备注、签名区域。结构上不要全用table撑场。表单布局用CSS Flex/Grid更方便控制表格线框只用于“金额明细区”这种纯粹的矩形数据区域。我的经验是整张汇款单更像“一张信息卡”不是Excel网格。DOM结构一上来就规划好后面调整样式会轻松很多。2.2 完整的HTML骨架代码下面是核心结构的简化版字段做了裁剪但框架完全可用。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title银行汇款单/title link relstylesheet hrefstyle.css /head body div classremittance-slip !-- 单据头 -- div classslip-header h1银行汇款凭证/h1 div classheader-meta label单据编号input typetext idbillNo valueDH2025 readonly/label label汇款日期input typedate idbillDate/label label币种select idcurrency option valueCNY selected人民币/option option valueUSD美元/option /select/label /div /div !-- 主体信息 -- div classslip-body !-- 汇款人 -- div classparty-block remitter h2汇款人/h2 div classfield-row label户名/label input typetext idremitterName placeholder请输入汇款人户名 maxlength30 /div div classfield-row label账号/label input typetext idremitterAccount placeholder请输入汇款人账号 maxlength20 inputmodenumeric /div div classfield-row label开户行/label input typetext idremitterBank placeholder请输入开户行名称 maxlength40 /div /div !-- 收款人 -- div classparty-block payee h2收款人/h2 div classfield-row label户名/label input typetext idpayeeName placeholder请输入收款人户名 maxlength30 /div div classfield-row label账号/label input typetext idpayeeAccount placeholder请输入收款人账号 maxlength20 inputmodenumeric /div div classfield-row label开户行/label input typetext idpayeeBank placeholder请输入收款行名称 maxlength40 /div /div /div !-- 金额区 -- div classamount-section div classfield-row amount-row label汇款金额小写/label input typetext idamountLower placeholder0.00 inputmodedecimal /div div classfield-row amount-row label汇款金额大写/label input typetext idamountUpper readonly placeholder零元整 /div /div !-- 用途与签名 -- div classslip-footer div classfield-row label汇款用途/label select idpurpose option value请选择/option option value货款货款/option option value稿费稿费/option option value服务费服务费/option option value其他其他/option /select /div div classsign-area span汇款人签字/span span classsign-line/span span经办人签字/span span classsign-line/span /div /div !-- 操作按钮区打印时隐藏 -- div classaction-bar no-print button typebutton onclickgenerateBillNo()生成新单号/button button typebutton onclickresetForm()重置/button button typebutton onclickwindow.print()打印汇款单/button /div /div script srcapp.js/script /body /html这段结构里有个容易忽略的点操作按钮一定要加上no-print类否则打印出来的单据上会混进“打印汇款单”这种按钮非常不专业。后面打印样式里会对它做隐藏处理。3. CSS还原纸质汇款单质感结构搭完最难啃的部分来了怎么让一个网页看起来像一张正经的银行单据。我的思路是“屏幕上是设计稿打印出来是纸质单据”所以CSS分成两部分屏幕样式 打印样式。屏幕样式要营造纸张感打印样式则要保证黑白色调清晰、尺寸精确。3.1 纸张尺寸与基础边框一张A4纸是210mm x 297mm。我用CSS给.remittance-slip设成width: 210mm但min-height不写死因为内容多少会影响高度写着写着容易溢出没必要自找麻烦。外层加个大背景模拟桌面内层单据主体用浅米色调#fffef7模拟纸张本色。边框用双线.remittance-slip { width: 210mm; margin: 20px auto; background: #fffef7; border: 2px solid #333; padding: 12mm 14mm; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); font-family: SimSun, 宋体, serif; }这里用宋体是有讲究的。银行单据、红头文件这类正式纸质文档默认字体就是宋体。宋体在低分辨率显示下虽然不够圆润但打印出来反而最接近印刷效果。系统没有宋体时备选serif兜底不会让页面崩掉。内边框我用outline实现“先border画外框再用outline加一道内线间隔差点靠outline-offset控制”。这样不用嵌套div就能做出双线效果.remittance-slip { border: 2px solid #333; outline: 1px solid #333; outline-offset: 3px; }3.2 表格线框与信息区块布局汇款人与收款人两块信息我用Flexbox左右排列。.slip-body { display: flex; justify-content: space-between; gap: 8mm; margin: 6mm 0; } .party-block { flex: 1; border: 1px solid #666; padding: 4mm 5mm; } .field-row { display: flex; align-items: center; margin-bottom: 3mm; border-bottom: 1px dashed #999; padding-bottom: 2mm; } .field-row label { width: 25mm; font-weight: bold; font-size: 13pt; } .field-row input, .field-row select { flex: 1; border: none; background: transparent; font-size: 12pt; font-family: inherit; border-bottom: 1px solid #333; padding: 1mm 2mm; }重点解释一下field-row下边的虚线这是模仿纸质单据填写区域的分隔线让用户视觉上知道“这是一行一行的填写位”。输入框本身的下划线样式则是为了贴合“手填单据”的手感。还有一个小细节field-row的border-bottom: 1px dashed #999是“行与行之间的分隔虚线”而输入框内的border-bottom: 1px solid #333是“填写位置的实线”二者不是一个元素别合并合并了后面调整起来会很痛苦。3.3 金额区与大写展示金额区是全单的核心视觉焦点我单独给了它一个醒目的边框和更粗的线。小写金额输入框字体加大加粗数字用等宽处理font-variant-numeric: tabular-nums小数点前后对齐。.amount-section { border: 2px solid #333; padding: 4mm 5mm; margin: 6mm 0; background: #fafaf0; } .amount-row input#amountLower { font-size: 16pt; font-weight: bold; letter-spacing: 1px; } .amount-row input#amountUpper { font-size: 14pt; letter-spacing: 2px; background: #f5f0e0; border-bottom: 2px solid #333; }大写金额的底色我特意做成了浅黄褐色#f5f0e0这是模仿真实单据上的“衬底色”告诉用户这个字段是自动生成的、不需要手填。3.4 打印样式与A4输出打印是整个项目最考验耐心的环节。浏览器默认打印会带上页眉页脚、地址栏信息必须通过CSS压制。media print { body { background: none; margin: 0; padding: 0; } .remittance-slip { width: 100%; margin: 0; box-shadow: none; border: 2px solid #000; padding: 8mm; } .no-print { display: none !important; } }这里关键指令有三个background: none去掉屏幕上的桌面底纹防止打印时整页涂黑或浪费墨粉。box-shadow: none阴影打出来就是一坨黑乎乎的东西必须去掉。.no-print { display: none !important; }操作按钮全部隐藏。另外纸张尺寸上的一个细节是不要过度依赖page设置size: A4因为不同浏览器的默认页边距不一致。我在实际项目中会配合页面设置——边距——无来用或者用page { margin: 0; }加上自己控制的padding。两种方式效果略有差异推荐自己试一版选更顺手那个。4. JavaScript交互从填单到出单结构样式都定了接下来是让页面“活”起来的JS部分。核心交互有三个初始化日期和单号、小写金额转大写、表单校验。4.1 初始化日期和单据编号日期用input typedate默认值直接给今天。单据编号我用时间戳生成一个12位编号格式类似“DH 年月日 三位随机数”。function generateBillNo() { const now new Date(); const y now.getFullYear().toString(); const m String(now.getMonth() 1).padStart(2, 0); const d String(now.getDate()).padStart(2, 0); const rand String(Math.floor(Math.random() * 900) 100); const billNo DH${y}${m}${d}${rand}; document.getElementById(billNo).value billNo; } function initDate() { const now new Date(); const y now.getFullYear(); const m String(now.getMonth() 1).padStart(2, 0); const d String(now.getDate()).padStart(2, 0); document.getElementById(billDate).value ${y}-${m}-${d}; } window.onload function () { initDate(); generateBillNo(); };padStart是ES6的字符串方法用来补零。如果你还在用很旧的浏览器需要换成长度判断拼接不过现在常规环境都不用管了。4.2 小写金额自动转大写这是整套代码里最有含金量的部分。阿拉伯数字转中文大写有一套固定规则个十百千万、零整角分。处理不好就是“壹佰零壹元”变成“壹佰壹元”这种细节错误。我写了一个完整可用的转换函数function amountToChinese(numStr) { const digits [零, 壹, 贰, 叁, 肆, 伍, 陆, 柒, 捌, 玖]; const units [, 拾, 佰, 仟]; const bigUnits [, 万, 亿, 万亿]; let num parseFloat(numStr); if (isNaN(num) || num 0) return 无效金额; if (num 0) return 零元整; num num.toFixed(2); const parts num.split(.); let integerPart parseInt(parts[0], 10); const decimalPart parts[1] || 00; const jiao parseInt(decimalPart[0], 10); const fen parseInt(decimalPart[1], 10); let integerStr integerPart 0 ? : ; // 处理整数部分每四位分一段 let segmentCount 0; while (integerPart 0) { let segment integerPart % 10000; integerPart Math.floor(integerPart / 10000); let segStr ; let zeroFlag false; let pos 0; while (segment 0) { let digit segment % 10; if (digit 0) { if (segStr ! !zeroFlag) { segStr 零 segStr; } zeroFlag true; } else { segStr digits[digit] units[pos] segStr; zeroFlag false; } segment Math.floor(segment / 10); pos; } if (segStr ! ) { integerStr segStr bigUnits[segmentCount] integerStr; } else if (integerStr ! ) { integerStr 零 integerStr; } segmentCount; } let result integerStr ? 零 : integerStr; result 元; if (jiao 0 fen 0) { result 整; } else if (jiao 0) { result 零${digits[fen]}分; } else if (fen 0) { result ${digits[jiao]}角整; } else { result ${digits[jiao]}角${digits[fen]}分; } return result; }这个函数用“万/亿分段法”处理超大金额逻辑靠谱。挂上监听事件每次小写金额输入框内容一变化就刷新大写字段document.getElementById(amountLower).addEventListener(input, function () { const val this.value.trim(); if (val ! ) { const upper amountToChinese(val); document.getElementById(amountUpper).value upper; } else { document.getElementById(amountUpper).value ; } });这里有个细节监听input事件而不是change事件。input只要值变就触发用户每敲一个字符大写金额都会实时刷新change要等输入框失焦才触发体验差很多。4.3 表单校验与提交汇款单最怕漏填关键项。提交校验我写了一个通用函数把必填字段统一验证function validateForm() { const requiredFields [ { id: remitterName, msg: 汇款人户名不能为空 }, { id: remitterAccount, msg: 汇款人账号不能为空 }, { id: payeeName, msg: 收款人户名不能为空 }, { id: payeeAccount, msg: 收款人账号不能为空 }, { id: amountLower, msg: 汇款金额不能为空 } ]; for (const field of requiredFields) { const el document.getElementById(field.id); if (el.value.trim() ) { alert(field.msg); el.focus(); return false; } } const accountReg /^\d{6,20}$/; if (!accountReg.test(document.getElementById(remitterAccount).value.trim())) { alert(汇款人账号应为6-20位数字); return false; } if (!accountReg.test(document.getElementById(payeeAccount).value.trim())) { alert(收款人账号应为6-20位数字); return false; } return true; }银行的账号实际有字符和数字组合但这里作为单页练习用“6-20位数字”的规则演示已经足够了。校验通过后正常就应该调用后端接口保存数据。作为纯前端实现我直接触发打印document.querySelector(button[onclickwindow.print()]).addEventListener(click, function (e) { if (validateForm()) { window.print(); } });5. 实操踩坑记录与排查清单做这个页面遇到的事比写代码本身更能帮你省时间。我梳理了几个高频问题直接给排查方向。5.1 字体不一致导致对齐错乱不同电脑上宋体缺失或者浏览器渲染差异会导致输入框文字高低不齐。解决办法是CSS里统一字体栈.remittance-slip, .remittance-slip input, .remittance-slip select { font-family: SimSun, 宋体, NSimSun, serif; }input和select必须单独写上font-family因为它们默认不继承body字体。这是新手特别容易忽略的一点。5.2 打印出来只有一半内容或者出现空白页这种问题绝大多数是height和页边距冲突导致的。我遇到过表单主体高度超过一页、第二页又只有一行字的情况非常尴尬。对策是给.slip-body、.amount-section这些块元素添加page-break-inside: avoid;.slip-body, .amount-section, .slip-footer { page-break-inside: avoid; }同时纸张内侧的padding不要设太大我用的是8mm再配浏览器“默认边距”能正好落在安全区内。5.3 打印时背景色/背景图消失主流浏览器默认不打印背景颜色和图片。所以我的大写金额区域的底色#f5f0e0、纸张底色#fffef7在打印样式里都设了-webkit-print-color-adjust: exact; print-color-adjust: exact;。media print { .remittance-slip, .amount-section, .amount-row input#amountUpper { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }如果你不加这一句打印出来就是一个白底单子倒也不影响读取但档次感会差很多。5.4 手机端打开布局错乱严格来讲汇款单是桌面端工具但难免有人拿手机临时查看。我用两个手段兼容设置meta nameviewport contentwidthdevice-width, initial-scale1.0给.remittance-slip加一个min-width: 700px手机上横向滚动也能看。移动端真正要把表单填完是很痛苦的所以这里做到“能看清、不崩版”就够了不需要做响应式重排。5.5 账号输入框出现科学计数法当输入框偏窄、数字过长时某些浏览器会显示成类似“1.23457E10”的样子非常吓人。解决方式是限制maxlength20同时CSS里加input { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }设置了maxlength能在源头上控制输入长度显示层面再加省略号不会溢出到边框外。最后说点实在的这个页面前前后后我调整了差不多一周才达到“打印出来能直接当凭证”的程度最耗时间的不是逻辑恰恰是那些零碎的对齐和打印细节。我的经验是每改一次样式不要只在浏览器里看直接CtrlP输出成PDF再检查一遍很多屏幕上看不出来的问题在PDF里会原形毕露。这个单子的后续方向也不少可以接后端把单据数据存进数据库、生成PDF归档也可以加一个“批量导入”功能把Excel里的汇款明细一次拉进来。但对初学者来说先把这一张单子做到完美你收获的远不止几张纸的代码量更是对HTML/CSS/JS关系的一次彻底理顺。
返回列表