基于hiprint的票据定位打印系统开发实践
在日常的Web开发中,我们经常需要实现打印功能,特别是对于票据、标签等需要精确排版的打印需求。今天我将分享一个基于hiprint插件实现的票据定位打印系统,重点介绍如何实现单行打印、批量打印以及金额数据动态绑定等功能。
项目背景
传统的网页打印往往难以控制精确的布局和格式,特别是对于发票、收据等需要固定格式的票据打印。hiprint 是一个专门用于解决这类问题的JavaScript打印插件,它提供了可视化设计工具和丰富的API,可以轻松实现精确定位打印。
核心功能实现
1. 页面结构设计
首先,我们构建了一个包含票据列表和操作按钮的页面结构:
<table class="table table-bordered" style="margin-top: 20px;">
<thead>
<tr>
<th>发票编号</th>
<th>开票日期</th>
<th>客户名称</th>
<th>金额</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>INV-001</td>
<td>2023-10-01</td>
<td>某某公司</td>
<td>¥1085.00</td>
<td>
<a class="btn hiprint-toolbar-item bill-action"
data-action="preview" data-index="1">快速预览</a>
<a class="btn hiprint-toolbar-item bill-action"
data-action="print" data-index="1">打印</a>
</td>
</tr>
<tr>
<td>INV-001</td>
<td>2023-10-01</td>
<td>某某公司</td>
<td>¥985.00</td>
<td>
<a class="btn hiprint-toolbar-item bill-action"
data-action="preview" data-index="2">快速预览</a>
<a class="btn hiprint-toolbar-item bill-action"
data-action="print" data-index="2">打印</a>
</td>
</tr>
</tbody>
</table>
2. 模板外部化管理
为了提高代码的可维护性,我们将打印模板存储在独立的JSON文件中:
下例为简单表格的模板(发票的模板此处不展示,请见hiprint资源)
{
"panels": [
{
"index": 0,
"height": 210,
"width": 297,
"paperHeader": 10,
"paperFooter": 380,
"printElements": [
{
"options": {
"left": 20,
"top": 20,
"width": 250,
"height": 120,
"content": "<table border='1' style='width:100%;border-collapse:collapse;text-align:center;'><tr><th>序号</th><th>姓名</th><th>成绩</th></tr><tr><td>1</td><td>张三</td><td>90</td></tr><tr><td>2</td><td>李四</td><td>85</td></tr><tr><td>3</td><td>王五</td><td>88</td></tr></table>"
},
"printElementType": {
"title": "表格",
"type": "table"
}
}
],
"paperNumberDisabled": true
}
]
}
// 从外部JSON文件加载模板
$.getJSON('custom_test/bill-template.json', function (templateData) {
hiprintTemplate_bill = new hiprint.PrintTemplate({
template: templateData,
settingContainer: '#PrintElementOptionSetting'
});
});
这样做的好处是:
- 模板与代码分离,便于维护
- 可以在多个页面中复用同一模板
- 支持非技术人员通过可视化工具调整模板
3. 单行打印功能
实现单行打印的关键在于从当前行提取金额数据并传递给打印函数:
// 动态绑定事件
$(document).on('click', '.bill-action', function () {
const action = $(this).data('action'); // 获取操作类型
const index = $(this).data('index'); // 获取索引
// 获取当前行的金额
const amountText = $(this).closest('tr').find('td:eq(3)').text();
const amount = parseFloat(amountText.replace(/[¥,]/g, '')); // 去除¥符号和逗号,转换为数字
handleBillAction(action, index, amount);
});
// 处理预览和打印逻辑
function handleBillAction(action, index, amount) {
// 创建当前行的数据对象
const rowData = {
index: index,
totalAmount: amount
};
if (action === 'preview') {
$('#myModal .modal-body .prevViewDiv').html(hiprintTemplate_bill.getHtml(rowData));
$('#myModal').modal('show');
} else if (action === 'print') {
if (hiprintTemplate_bill) {
hiprintTemplate_bill.print([rowData]);
}
}
}
4. 批量打印功能
批量打印功能会遍历表格所有行,提取每行的金额数据:
$('#batchPrint').click(function () {
const printData = [];
$('.table tbody tr').each(function(index) {
const amountText = $(this).find('td:eq(3)').text(); // 获取金额列文本
const amount = parseFloat(amountText.replace(/[¥,]/g, '')); // 去除¥符号和逗号,转换为数字
printData.push({
totalAmount: amount,
index: index + 1
});
});
if (hiprintTemplate_bill) {
hiprintTemplate_bill.print(printData);
}
});
多个发票批量打印:
单个发票打印:
技术要点解析
数据提取与处理
在实现过程中,我们需要注意金额数据的提取和格式化:
const amountText = $(this).closest('tr').find('td:eq(3)').text();
const amount = parseFloat(amountText.replace(/[¥,]/g, ''));
这行代码完成了以下工作:
- 定位到当前行的金额列(第4列,索引为3)
- 提取文本内容
- 使用正则表达式去除货币符号和千位分隔符
- 转换为浮点数便于后续处理
总结
通过这个项目,我们实现了以下功能:
- 可视化模板设计:使用外部JSON文件管理打印模板
- 单行精确打印:每个打印按钮绑定对应行的数据
- 批量处理能力:支持一次性打印所有票据
- 数据动态绑定:自动从表格中提取金额等关键信息
这个系统可以广泛应用于各种票据打印场景,如发票、收据、标签等,为Web应用提供了专业级的打印解决方案。通过合理的设计和实现,我们不仅提高了开发效率,也增强了系统的可维护性和扩展性。
— by agi
附录:
(1)temp.json
{
"panels": [
{
"index": 0,
"height": 148,
"width": 210,
"paperHeader": -1.5,
"paperFooter": 380,
"printElements": [
{
"options": {
"left": 540,
"top": 10.5,
"height": 35,
"width": 33,
"borderColor": "#f20000"
},
"printElementType": {
"title": "椭圆",
"type": "oval"
}
},
{
"options": {
"left": 454.5,
"top": 15,
"height": 18,
"width": 74,
"title": "8888888",
"fontSize": 18,
"fontWeight": "600",
"color": "#2935e3",
"textAlign": "center",
"lineHeight": 16
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 424.5,
"top": 15,
"height": 19,
"width": 24,
"title": "NO",
"fontSize": 18,
"color": "#2935e3",
"textAlign": "center",
"lineHeight": 15
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 190.5,
"top": 15,
"height": 21,
"width": 226,
"title": "上海增值税普通发票",
"fontSize": 18,
"fontWeight": "600",
"letterSpacing": 2.5,
"color": "#cc5a5a",
"textAlign": "center",
"lineHeight": 18
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 244.5,
"top": 19.5,
"height": 51,
"width": 112,
"borderColor": "#eb1111",
"borderWidth": "2"
},
"printElementType": {
"title": "椭圆",
"type": "oval"
}
},
{
"options": {
"left": 90,
"top": 19.5,
"height": 21,
"width": 96,
"title": "8888888",
"fontSize": 19,
"letterSpacing": 1,
"color": "#2935e3",
"textAlign": "center",
"lineHeight": 18
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 19.5,
"top": 19.5,
"height": 61,
"width": 65,
"title": "031001800204",
"textType": "qrcode"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 250.5,
"top": 25.5,
"height": 42,
"width": 104,
"borderColor": "#f00505"
},
"printElementType": {
"title": "椭圆",
"type": "oval"
}
},
{
"options": {
"left": 190.5,
"top": 45,
"height": 10,
"width": 228,
"borderColor": "#b5a8a8"
},
"printElementType": {
"title": "横线",
"type": "hline"
}
},
{
"options": {
"left": 190.5,
"top": 49.5,
"height": 10,
"width": 228,
"borderColor": "#baafaf"
},
"printElementType": {
"title": "横线",
"type": "hline"
}
},
{
"options": {
"left": 244.5,
"top": 55.5,
"height": 22,
"width": 120,
"title": "发票联",
"fontSize": 18,
"fontWeight": "600",
"letterSpacing": 8,
"color": "#cc5a5a",
"textAlign": "center",
"lineHeight": 18
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 510,
"top": 55.5,
"height": 13,
"width": 69,
"title": "2019年05月09日",
"color": "#2935e3"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 445.5,
"top": 55.5,
"height": 15,
"width": 57,
"title": "开票日期:",
"color": "#cc5a5a",
"lineHeight": 13
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 90,
"top": 64.5,
"height": 15,
"width": 141,
"title": "校验码:123456 788942 52344",
"color": "#2935e3",
"textAlign": "center",
"lineHeight": 13
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 400,
"top": 90,
"height": 60,
"width": 10,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "竖线",
"type": "vline"
}
},
{
"options": {
"left": 35,
"top": 90,
"height": 60,
"width": 10,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "竖线",
"type": "vline"
}
},
{
"options": {
"left": 420,
"top": 90,
"height": 61,
"width": 10,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "竖线",
"type": "vline"
}
},
{
"options": {
"left": 10.5,
"top": 90,
"height": 282,
"width": 572,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "矩形",
"type": "rect"
}
},
{
"options": {
"left": 405,
"top": 94.5,
"height": 55,
"width": 13,
"title": "密码区",
"fontSize": 13,
"color": "#cc5a5a",
"lineHeight": 18
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 424.5,
"top": 94.5,
"height": 50,
"width": 152,
"title": "",
"color": "#2935e3"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 15,
"top": 94.5,
"height": 53,
"width": 15,
"title": "购买方",
"fontSize": 13,
"color": "#cc5a5a",
"lineHeight": 18
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 45,
"top": 100.5,
"height": 10,
"width": 348,
"title": "名称:北京地铁税务局有限公司",
"color": "#2935e3"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 45,
"top": 115.5,
"height": 10,
"width": 347,
"title": "纳税人识别号:999999999999999999",
"color": "#2935e3"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 45,
"top": 130.5,
"height": 10,
"width": 347,
"title": "地址、电话:18888888888",
"color": "#2935e3"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 345,
"top": 150,
"height": 190,
"width": 10,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "竖线",
"type": "vline"
}
},
{
"options": {
"left": 409.5,
"top": 150,
"height": 190,
"width": 10,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "竖线",
"type": "vline"
}
},
{
"options": {
"left": 295.5,
"top": 150,
"height": 190,
"width": 10,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "竖线",
"type": "vline"
}
},
{
"options": {
"left": 480,
"top": 150,
"height": 190,
"width": 10,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "竖线",
"type": "vline"
}
},
{
"options": {
"left": 215,
"top": 150,
"height": 224,
"width": 10,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "竖线",
"type": "vline"
}
},
{
"options": {
"left": 520.5,
"top": 150,
"height": 190,
"width": 10,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "竖线",
"type": "vline"
}
},
{
"options": {
"left": 10,
"top": 150,
"height": 10,
"width": 574,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "横线",
"type": "hline"
}
},
{
"options": {
"left": 300,
"top": 160.5,
"height": 10,
"width": 36,
"title": "单位",
"fontSize": 13,
"color": "#cc5a5a",
"textAlign": "center"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 349.5,
"top": 160.5,
"height": 11,
"width": 51,
"title": "数量",
"fontSize": 13,
"color": "#cc5a5a",
"textAlign": "center"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 225,
"top": 160.5,
"height": 10,
"width": 62,
"title": "规格名称",
"fontSize": 13,
"color": "#cc5a5a",
"textAlign": "center"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 420,
"top": 160.5,
"height": 10,
"width": 53,
"title": "单价",
"fontSize": 13,
"color": "#cc5a5a",
"textAlign": "center"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 484.5,
"top": 160.5,
"height": 10,
"width": 32,
"title": "税率",
"fontSize": 13,
"color": "#cc5a5a",
"textAlign": "center"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 525,
"top": 160.5,
"height": 10,
"width": 52,
"title": "税额",
"fontSize": 13,
"color": "#cc5a5a",
"textAlign": "center"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 19.5,
"top": 160.5,
"height": 10,
"width": 184,
"title": "货物或应税劳务、服务名称",
"fontSize": 13,
"color": "#cc5a5a",
"textAlign": "center"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 40.5,
"top": 175.5,
"height": 12,
"width": 120,
"title": "*餐饮服务*餐费",
"color": "#2935e3"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 10.5,
"top": 340.5,
"height": 10,
"width": 574,
"borderColor": "#cc5a5a"
},
"printElementType": {
"title": "横线",
"type": "hline"
}
},
{
"options": {
"left": 225,
"top": 349.5,
"height": 14,
"width": 229,
"title": "壹佰贰拾元整",
"color": "#2935e3"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 460.5,
"top": 349.5,
"height": 13,
"width": 58,
"title": "(小写)",
"fontSize": 13,
"color": "#cc5a5a"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 520.5,
"top": 349.5,
"height": 13,
"width": 48,
"field": "totalAmount",
"color": "#2935e3"
},
"printElementType": {
"title": "¥",
"type": "text"
}
},
{
"options": {
"left": 15,
"top": 349.5,
"height": 14,
"width": 193,
"title": "价税合计(大写)",
"fontSize": 13,
"color": "#cc5a5a",
"textAlign": "center"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 300,
"top": 385.5,
"height": 10,
"width": 39,
"title": "开票人:",
"color": "#cc5a5a"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 190.5,
"top": 385.5,
"height": 10,
"width": 103,
"title": "轩大可",
"color": "#2935e3"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 150,
"top": 385.5,
"height": 10,
"width": 33,
"title": "复核:",
"color": "#cc5a5a"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 345,
"top": 385.5,
"height": 10,
"width": 86,
"title": "张天天",
"color": "#2935e3"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 64.5,
"top": 385.5,
"height": 10,
"width": 78,
"title": "轩天天",
"color": "#2935e3"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 439.5,
"top": 385.5,
"height": 10,
"width": 40,
"title": "销售方:",
"color": "#cc5a5a"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 15,
"top": 385.5,
"height": 10,
"width": 44,
"title": "收款人:",
"color": "#cc5a5a"
},
"printElementType": {
"title": "文本",
"type": "text"
}
}
],
"paperNumberLeft": 565.5,
"paperNumberTop": 394.5
}
]
}
(2)index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link href="css/hiprint.css" rel="stylesheet" />
<link href="css/print-lock.css" rel="stylesheet" />
<link href="content/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.staticfile.net/jquery/1.10.2/jquery.min.js"></script>
<script src="content/bootstrap.min.js"></script>
<style>
.hinnn-layout,
.hinnn-layout * {
box-sizing: border-box;
}
.hinnn-layout {
display: flex;
flex: auto;
flex-direction: column;
}
.hinnn-layout.hinnn-layout-has-sider {
flex-direction: row;
}
.hinnn-layout-sider {
display: flex;
flex-direction: row;
position: relative;
}
.hinnn-layout-content {
flex: auto;
}
.hinnn-header {
position: relative;
z-index: 1030;
display: block;
}
.wrapper {
min-height: 100%;
}
.height-100-per {
height: 100%;
}
</style>
</head>
<body>
<layout class="layout hinnn-layout hinnn-layout-has-sider height-100-per" style="background:#fff;">
<content class="hinnn-layout-content" style="border-left:1px solid #e8e8e8;">
<div class="container-fluid height-100-per print-content">
<div class="row">
<div class="col-sm-12">
<div class="row">
<div class="col-sm-9 col-md-10">
<div class="row">
<div class="col-md-12">
<div class="hinnn-docs-section">
<h1 class="page-header">票据定位打印</h1>
<button type="button" class="btn btn-danger" id="batchPrint">批量打印</button>
<table class="table table-bordered" style="margin-top: 20px;">
<thead>
<tr>
<th>发票编号</th>
<th>开票日期</th>
<th>客户名称</th>
<th>金额</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>INV-001</td>
<td>2023-10-01</td>
<td>某某公司</td>
<td>¥1085.00</td>
<td>
<a class="btn hiprint-toolbar-item bill-action"
data-action="preview" data-index="1">快速预览</a>
<a class="btn hiprint-toolbar-item bill-action"
data-action="print" data-index="1">打印</a>
</td>
</tr>
<tr>
<td>INV-001</td>
<td>2023-10-01</td>
<td>某某公司</td>
<td>¥985.00</td>
<td>
<a class="btn hiprint-toolbar-item bill-action"
data-action="preview" data-index="2">快速预览</a>
<a class="btn hiprint-toolbar-item bill-action"
data-action="print" data-index="2">打印</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</content>
<sider class="hinnn-layout-sider" style="">
<div class="container height-100-per" style="width:250px;">
<div class="row">
<div class="col-sm-12">
<div id="PrintElementOptionSetting" style="margin-top:10px;"></div>
</div>
</div>
</div>
</sider>
</layout>
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-lg" role="document" style="width: 825px;">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">×</span></button>
<h4 class="modal-title" id="">打印预览</h4>
</div>
<div class="modal-body">
<!-- 新增打印按钮 -->
<button type="button" class="btn btn-danger" id="printInModal">打印</button>
<div class="prevViewDiv"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</divmyModalLabel>
</div>
</div>
</div>
</div>
<script src="custom_test/custom-etype-provider.js"></script>
<script src="custom_test/custom-print-json.js"></script>
<script src="custom_test/print-data.js"></script>
<script src="polyfill.min.js"></script>
<script src="plugins/jquery.minicolors.min.js"></script>
<script src="plugins/JsBarcode.all.min.js"></script>
<script src="plugins/qrcode.js"></script>
<script src="hiprint.bundle.js"></script>
<script src="plugins/jquery.hiwprint.js"></script>
<script>
$(document).ready(function () {
var hiprintTemplate_bill;
// 修改: 使用 $.getJSON 方法加载模板文件
$.getJSON('custom_test/bill-template.json', function (templateData) {
hiprintTemplate_bill = new hiprint.PrintTemplate({
template: templateData,
settingContainer: '#PrintElementOptionSetting'
});
});
// 绑定模态框中的打印按钮事件
$('#printInModal').click(function () {
if (hiprintTemplate_bill) {
hiprintTemplate_bill.print(printData);
}
});
// 绑定批量打印按钮事件 - 参考提供的代码进行修改
$('#batchPrint').click(function () {
// 修改: 从表格中获取金额数据
const printData = [];
$('.table tbody tr').each(function(index) {
const amountText = $(this).find('td:eq(3)').text(); // 获取金额列文本
const amount = parseFloat(amountText.replace(/[¥,]/g, '')); // 去除¥符号和逗号,转换为数字
printData.push({
totalAmount: amount,
index: index + 1
});
});
if (hiprintTemplate_bill) {
hiprintTemplate_bill.print(printData);
}
});
// 修改: 统一处理预览和打印逻辑,支持单行金额数据
function handleBillAction(action, index, amount) {
// 创建当前行的数据对象
const rowData = {
index: index,
totalAmount: amount
};
if (action === 'preview') {
$('#myModal .modal-body .prevViewDiv').html(hiprintTemplate_bill.getHtml(rowData));
$('#myModal').modal('show');
} else if (action === 'print') {
if (hiprintTemplate_bill) {
hiprintTemplate_bill.print([rowData]);
}
}
}
// 动态绑定事件
$(document).on('click', '.bill-action', function () {
const action = $(this).data('action'); // 获取操作类型
const index = $(this).data('index'); // 获取索引
// 获取当前行的金额
const amountText = $(this).closest('tr').find('td:eq(3)').text();
const amount = parseFloat(amountText.replace(/[¥,]/g, '')); // 去除¥符号和逗号,转换为数字
handleBillAction(action, index, amount);
});
});
</script>
</body>
</html>