侧边栏壁纸
博主头像
云BLOG 博主等级

行动起来,活在当下

  • 累计撰写 318 篇文章
  • 累计创建 6 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

零售小票打印系统(浏览器打印方案)

Administrator
2025-10-22 / 0 评论 / 0 点赞 / 44 阅读 / 0 字

完整的零售小票打印系统,使用纯前端技术实现,无需安装任何插件,通过浏览器原生打印功能实现小票打印。

实现思路

  1. 使用CSS媒体查询和打印样式控制小票格式

  2. 通过JavaScript动态生成小票内容

  3. 利用浏览器内置的打印功能

  4. 使用58mm纸张规格的CSS设置

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>零售小票打印系统</title>
    <link href="https://cdn.jsdelivr.net/npm/layui@2.9.6/dist/css/layui.min.css" rel="stylesheet">
    <style>
        body {
            font-family: "Microsoft YaHei", sans-serif;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 800px;
            margin: 0 auto;
            background: white;
            padding: 20px;
            border-radius: 5px;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
        }
        .header {
            text-align: center;
            margin-bottom: 20px;
            padding-bottom: 15px;
            border-bottom: 1px solid #eee;
        }
        .form-section {
            margin-bottom: 20px;
        }
        .preview-section {
            margin-top: 30px;
            border-top: 1px solid #eee;
            padding-top: 20px;
        }
        .preview-box {
            border: 1px dashed #ccc;
            padding: 15px;
            background: #f9f9f9;
            min-height: 200px;
        }
        .receipt {
            width: 58mm; /* POS58打印机标准宽度 */
            margin: 0 auto;
            font-family: "Courier New", monospace;
            font-size: 12px;
            line-height: 1.2;
            padding: 10px;
            background: white;
            box-shadow: 0 0 5px rgba(0,0,0,0.1);
        }
        .receipt-header {
            text-align: center;
            margin-bottom: 10px;
            padding-bottom: 5px;
            border-bottom: 1px dashed #000;
        }
        .receipt-title {
            font-weight: bold;
            font-size: 14px;
            margin-bottom: 5px;
        }
        .receipt-info {
            font-size: 10px;
            margin-bottom: 5px;
        }
        .receipt-items {
            width: 100%;
            margin: 10px 0;
        }
        .receipt-item {
            display: flex;
            justify-content: space-between;
            margin-bottom: 3px;
        }
        .item-name {
            flex: 3;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .item-qty, .item-price, .item-total {
            flex: 1;
            text-align: right;
        }
        .receipt-divider {
            border-top: 1px dashed #000;
            margin: 5px 0;
        }
        .receipt-totals {
            margin-top: 10px;
        }
        .receipt-total-line {
            display: flex;
            justify-content: space-between;
            margin-bottom: 3px;
        }
        .receipt-footer {
            text-align: center;
            margin-top: 15px;
            font-size: 10px;
        }
        .barcode {
            text-align: center;
            margin: 10px 0;
            font-family: "Libre Barcode 39", monospace;
            font-size: 24px;
        }
        
        /* 打印样式 */
        @media print {
            body * {
                visibility: hidden;
            }
            .receipt, .receipt * {
                visibility: visible;
            }
            .receipt {
                position: absolute;
                left: 0;
                top: 0;
                width: 58mm;
                box-shadow: none;
                margin: 0;
                padding: 5mm;
            }
            .no-print {
                display: none !important;
            }
        }
        
        /* 小票纸张模拟 */
        .receipt-paper {
            background: white;
            min-height: 200px;
            width: 58mm;
            margin: 0 auto;
            position: relative;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
        }
        .receipt-paper::before {
            content: "";
            position: absolute;
            left: 0;
            right: 0;
            top: 0;
            height: 100%;
            background: repeating-linear-gradient(
                transparent,
                transparent 19px,
                #f0f0f0 19px,
                #f0f0f0 20px
            );
            pointer-events: none;
            opacity: 0.3;
        }
    </style>
</head>
<body>
    <div class="container no-print">
        <div class="header">
            <h1>零售小票打印系统</h1>
            <p>基于浏览器打印功能,兼容POS58热敏打印机</p>
        </div>
        
        <div class="form-section">
            <form class="layui-form">
                <div class="layui-form-item">
                    <label class="layui-form-label">店铺名称</label>
                    <div class="layui-input-block">
                        <input type="text" id="storeName" class="layui-input" value="零售小店" placeholder="请输入店铺名称">
                    </div>
                </div>
                
                <div class="layui-form-item">
                    <label class="layui-form-label">联系电话</label>
                    <div class="layui-input-block">
                        <input type="text" id="storePhone" class="layui-input" value="138-XXXX-XXXX" placeholder="请输入联系电话">
                    </div>
                </div>
                
                <div class="layui-form-item">
                    <label class="layui-form-label">地址</label>
                    <div class="layui-input-block">
                        <input type="text" id="storeAddress" class="layui-input" value="XX市XX区XX路XX号" placeholder="请输入店铺地址">
                    </div>
                </div>
                
                <div class="layui-form-item">
                    <label class="layui-form-label">订单号</label>
                    <div class="layui-input-block">
                        <input type="text" id="orderNo" class="layui-input" value="DD20230520001" placeholder="请输入订单号">
                    </div>
                </div>
                
                <div class="layui-form-item">
                    <label class="layui-form-label">收银员</label>
                    <div class="layui-input-block">
                        <input type="text" id="cashier" class="layui-input" value="张三" placeholder="请输入收银员姓名">
                    </div>
                </div>
                
                <div class="layui-form-item">
                    <label class="layui-form-label">商品列表</label>
                    <div class="layui-input-block">
                        <table class="layui-table" id="itemTable">
                            <thead>
                                <tr>
                                    <th>商品名称</th>
                                    <th width="100">数量</th>
                                    <th width="100">单价(元)</th>
                                    <th width="100">操作</th>
                                </tr>
                            </thead>
                            <tbody id="itemList">
                                <tr>
                                    <td><input type="text" class="layui-input item-name" value="苹果"></td>
                                    <td><input type="number" class="layui-input item-qty" value="2" min="1"></td>
                                    <td><input type="number" class="layui-input item-price" value="5.00" min="0" step="0.01"></td>
                                    <td><button type="button" class="layui-btn layui-btn-sm layui-btn-danger remove-item">删除</button></td>
                                </tr>
                                <tr>
                                    <td><input type="text" class="layui-input item-name" value="面包"></td>
                                    <td><input type="number" class="layui-input item-qty" value="1" min="1"></td>
                                    <td><input type="number" class="layui-input item-price" value="10.00" min="0" step="0.01"></td>
                                    <td><button type="button" class="layui-btn layui-btn-sm layui-btn-danger remove-item">删除</button></td>
                                </tr>
                            </tbody>
                        </table>
                        <button type="button" class="layui-btn layui-btn-sm" id="addItem">
                            <i class="layui-icon">&#xe654;</i> 添加商品
                        </button>
                    </div>
                </div>
                
                <div class="layui-form-item">
                    <div class="layui-input-block">
                        <button type="button" class="layui-btn layui-btn-normal" id="updatePreview">更新预览</button>
                        <button type="button" class="layui-btn" id="printReceipt">打印小票</button>
                    </div>
                </div>
            </form>
        </div>
        
        <div class="preview-section no-print">
            <h3>小票预览</h3>
            <div class="preview-box">
                <div class="receipt-paper">
                    <div class="receipt" id="receiptPreview">
                        <!-- 小票内容将通过JavaScript动态生成 -->
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 小票打印模板 -->
    <div id="receiptTemplate" style="display: none;">
        <div class="receipt">
            <div class="receipt-header">
                <div class="receipt-title" id="r-storeName">零售小店</div>
                <div class="receipt-info" id="r-storePhone">电话: 138-XXXX-XXXX</div>
                <div class="receipt-info" id="r-storeAddress">地址: XX市XX区XX路XX号</div>
            </div>
            
            <div class="receipt-info">
                订单号: <span id="r-orderNo">DD20230520001</span>
            </div>
            <div class="receipt-info">
                收银员: <span id="r-cashier">张三</span>
            </div>
            <div class="receipt-info">
                日期: <span id="r-date">2023-05-20 14:30:25</span>
            </div>
            
            <div class="receipt-divider"></div>
            
            <div class="receipt-items">
                <div class="receipt-item">
                    <div class="item-name"><strong>商品</strong></div>
                    <div class="item-qty"><strong>数量</strong></div>
                    <div class="item-price"><strong>单价</strong></div>
                    <div class="item-total"><strong>金额</strong></div>
                </div>
                <div class="receipt-divider"></div>
                <!-- 商品列表将通过JavaScript动态生成 -->
                <div id="r-items">
                    <div class="receipt-item">
                        <div class="item-name">苹果</div>
                        <div class="item-qty">2</div>
                        <div class="item-price">5.00</div>
                        <div class="item-total">10.00</div>
                    </div>
                    <div class="receipt-item">
                        <div class="item-name">面包</div>
                        <div class="item-qty">1</div>
                        <div class="item-price">10.00</div>
                        <div class="item-total">10.00</div>
                    </div>
                </div>
                <div class="receipt-divider"></div>
            </div>
            
            <div class="receipt-totals">
                <div class="receipt-total-line">
                    <div>合计:</div>
                    <div>¥<span id="r-subtotal">20.00</span></div>
                </div>
                <div class="receipt-total-line">
                    <div>优惠:</div>
                    <div>-¥<span id="r-discount">0.00</span></div>
                </div>
                <div class="receipt-total-line">
                    <div><strong>应收:</strong></div>
                    <div><strong>¥<span id="r-total">20.00</span></strong></div>
                </div>
            </div>
            
            <div class="receipt-divider"></div>
            
            <div class="barcode">
                *<span id="r-barcode">DD20230520001</span>*
            </div>
            
            <div class="receipt-footer">
                <div>谢谢惠顾,欢迎再次光临!</div>
                <div>本小票可作为购物凭证</div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/layui@2.9.6/dist/layui.min.js"></script>
    <script>
        layui.use(['form', 'layer'], function(){
            var form = layui.form;
            var layer = layui.layer;
            
            // 初始化
            document.addEventListener('DOMContentLoaded', function() {
                updateReceiptPreview();
                
                // 添加商品行
                document.getElementById('addItem').addEventListener('click', function() {
                    var tbody = document.getElementById('itemList');
                    var newRow = document.createElement('tr');
                    newRow.innerHTML = `
                        <td><input type="text" class="layui-input item-name" value="新商品"></td>
                        <td><input type="number" class="layui-input item-qty" value="1" min="1"></td>
                        <td><input type="number" class="layui-input item-price" value="0.00" min="0" step="0.01"></td>
                        <td><button type="button" class="layui-btn layui-btn-sm layui-btn-danger remove-item">删除</button></td>
                    `;
                    tbody.appendChild(newRow);
                    
                    // 绑定删除事件
                    newRow.querySelector('.remove-item').addEventListener('click', function() {
                        tbody.removeChild(newRow);
                        updateReceiptPreview();
                    });
                    
                    // 绑定输入事件
                    var inputs = newRow.querySelectorAll('input');
                    inputs.forEach(function(input) {
                        input.addEventListener('input', updateReceiptPreview);
                    });
                });
                
                // 绑定现有删除按钮事件
                var removeButtons = document.querySelectorAll('.remove-item');
                removeButtons.forEach(function(button) {
                    button.addEventListener('click', function() {
                        var row = this.closest('tr');
                        row.parentNode.removeChild(row);
                        updateReceiptPreview();
                    });
                });
                
                // 绑定输入事件
                var inputs = document.querySelectorAll('#storeName, #storePhone, #storeAddress, #orderNo, #cashier, .item-name, .item-qty, .item-price');
                inputs.forEach(function(input) {
                    input.addEventListener('input', updateReceiptPreview);
                });
                
                // 更新预览按钮
                document.getElementById('updatePreview').addEventListener('click', updateReceiptPreview);
                
                // 打印按钮
                document.getElementById('printReceipt').addEventListener('click', function() {
                    // 创建打印内容
                    var printContent = document.getElementById('receiptTemplate').innerHTML;
                    var originalContent = document.body.innerHTML;
                    
                    // 替换body内容为小票
                    document.body.innerHTML = printContent;
                    
                    // 调用打印
                    window.print();
                    
                    // 恢复原始内容
                    document.body.innerHTML = originalContent;
                    
                    // 重新绑定事件
                    window.location.reload();
                });
            });
            
            // 更新小票预览
            function updateReceiptPreview() {
                // 获取表单数据
                var storeName = document.getElementById('storeName').value;
                var storePhone = document.getElementById('storePhone').value;
                var storeAddress = document.getElementById('storeAddress').value;
                var orderNo = document.getElementById('orderNo').value;
                var cashier = document.getElementById('cashier').value;
                
                // 获取当前时间
                var now = new Date();
                var dateStr = now.getFullYear() + '-' + 
                             String(now.getMonth() + 1).padStart(2, '0') + '-' + 
                             String(now.getDate()).padStart(2, '0') + ' ' +
                             String(now.getHours()).padStart(2, '0') + ':' + 
                             String(now.getMinutes()).padStart(2, '0') + ':' + 
                             String(now.getSeconds()).padStart(2, '0');
                
                // 计算商品总额
                var subtotal = 0;
                var itemsHtml = '';
                var itemRows = document.querySelectorAll('#itemList tr');
                
                itemRows.forEach(function(row) {
                    var name = row.querySelector('.item-name').value;
                    var qty = parseFloat(row.querySelector('.item-qty').value) || 0;
                    var price = parseFloat(row.querySelector('.item-price').value) || 0;
                    var total = qty * price;
                    subtotal += total;
                    
                    itemsHtml += `
                        <div class="receipt-item">
                            <div class="item-name">${name}</div>
                            <div class="item-qty">${qty}</div>
                            <div class="item-price">${price.toFixed(2)}</div>
                            <div class="item-total">${total.toFixed(2)}</div>
                        </div>
                    `;
                });
                
                // 更新预览
                var preview = document.getElementById('receiptPreview');
                preview.innerHTML = `
                    <div class="receipt-header">
                        <div class="receipt-title">${storeName}</div>
                        <div class="receipt-info">电话: ${storePhone}</div>
                        <div class="receipt-info">地址: ${storeAddress}</div>
                    </div>
                    
                    <div class="receipt-info">
                        订单号: ${orderNo}
                    </div>
                    <div class="receipt-info">
                        收银员: ${cashier}
                    </div>
                    <div class="receipt-info">
                        日期: ${dateStr}
                    </div>
                    
                    <div class="receipt-divider"></div>
                    
                    <div class="receipt-items">
                        <div class="receipt-item">
                            <div class="item-name"><strong>商品</strong></div>
                            <div class="item-qty"><strong>数量</strong></div>
                            <div class="item-price"><strong>单价</strong></div>
                            <div class="item-total"><strong>金额</strong></div>
                        </div>
                        <div class="receipt-divider"></div>
                        ${itemsHtml}
                        <div class="receipt-divider"></div>
                    </div>
                    
                    <div class="receipt-totals">
                        <div class="receipt-total-line">
                            <div>合计:</div>
                            <div>¥${subtotal.toFixed(2)}</div>
                        </div>
                        <div class="receipt-total-line">
                            <div>优惠:</div>
                            <div>-¥0.00</div>
                        </div>
                        <div class="receipt-total-line">
                            <div><strong>应收:</strong></div>
                            <div><strong>¥${subtotal.toFixed(2)}</strong></div>
                        </div>
                    </div>
                    
                    <div class="receipt-divider"></div>
                    
                    <div class="barcode">
                        *${orderNo}*
                    </div>
                    
                    <div class="receipt-footer">
                        <div>谢谢惠顾,欢迎再次光临!</div>
                        <div>本小票可作为购物凭证</div>
                    </div>
                `;
                
                // 同时更新打印模板
                document.getElementById('r-storeName').textContent = storeName;
                document.getElementById('r-storePhone').textContent = '电话: ' + storePhone;
                document.getElementById('r-storeAddress').textContent = '地址: ' + storeAddress;
                document.getElementById('r-orderNo').textContent = orderNo;
                document.getElementById('r-cashier').textContent = cashier;
                document.getElementById('r-date').textContent = dateStr;
                document.getElementById('r-items').innerHTML = itemsHtml;
                document.getElementById('r-subtotal').textContent = subtotal.toFixed(2);
                document.getElementById('r-total').textContent = subtotal.toFixed(2);
                document.getElementById('r-barcode').textContent = orderNo;
            }
        });
    </script>
</body>
</html>

系统特点

  1. 响应式设计:适配POS58打印机纸张宽度(58mm)

  2. 实时预览:输入内容时实时更新小票预览

  3. 动态商品管理:可添加、删除商品行

  4. 自动计算:自动计算商品总价

  5. 打印优化:使用专门的打印样式,隐藏不相关元素

  6. 模拟纸张:预览区域模拟热敏纸效果

使用说明

  1. 在表单中填写店铺信息、订单信息和商品信息

  2. 查看右侧的实时预览效果

  3. 点击"打印小票"按钮调用浏览器打印功能

  4. 在打印对话框中选择POS58打印机并设置纸张类型为58mm宽度

  5. 打印前建议先使用"打印到PDF"功能测试效果

注意事项

  1. 确保POS58打印机已正确安装驱动

  2. 在打印设置中选择正确的纸张大小(58mm宽度)

  3. 部分浏览器可能需要允许弹出窗口以进行打印

  4. 实际打印效果可能因打印机型号和驱动略有差异

这个方案完全基于浏览器原生功能,无需安装任何插件,适合零售行业快速部署使用。

0

评论区