Files
shop-platform/docs/common/CLodop.md

19 KiB
Raw Permalink Blame History

好的!我来详细介绍一下Web打印服务CLodop,这是一个非常强大的浏览器打印解决方案。

🖨️ CLodop - 专业Web打印服务详解

1. CLodop 是什么?

CLodopCloud LodopC-Lodop 云打印服务的简称,是 Lodop 技术的一个轻量级版本,专门为 Web 应用提供强大的打印功能。

核心特点

  • 跨浏览器兼容 - 支持所有主流浏览器
  • 无需安装插件 - 基于云服务架构
  • 精确打印控制 - 像素级打印精度
  • 批量打印支持 - 高效处理大量打印任务
  • 多种打印方式 - 直接打印、预览打印、设计打印

2. CLodop 与 Lodop 的区别

特性 CLodop云打印 Lodop传统打印
架构 云服务模式,无需安装 需要安装本地插件
部署 服务端部署,客户端零配置 每台客户端都需要安装
更新 服务端统一更新 每台客户端单独更新
兼容性 支持所有现代浏览器 对高版本浏览器支持有限
适用场景 Web应用、移动端 企业内部系统、局域网

3. CLodop 核心架构

系统架构图

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Web应用    │ -> │  CLodop服务  │ -> │   打印机     │
│   (浏览器)   │    │  (服务端)    │    │   (本地/网络) │
└─────────────┘    └─────────────┘    └─────────────┘
       │                    │                    │
    JavaScript          打印服务             打印输出
       API             任务队列

4. 完整安装和配置

4.1 服务端安装Windows

# 下载CLodop服务端
# 官方下载地址http://www.c-lodop.com/download.html

# 安装步骤:
1. 下载 CLodop_Setup.exe
2. 以管理员身份运行安装
3. 默认安装目录C:\Program Files (x86)\CLodop
4. 服务自动启动监听端口8000、18000

4.2 服务端配置文件

// CLodop 服务配置 (config.json)
{
  "server": {
    "port": 8000,
    "sslPort": 8443,
    "host": "0.0.0.0"
  },
  "print": {
    "defaultPaper": "A4",
    "dpi": 300,
    "timeout": 30000
  },
  "security": {
    "allowedOrigins": ["http://localhost:3000", "https://yourdomain.com"],
    "authRequired": false
  }
}

4.3 验证安装

# 检查服务状态
netstat -ano | findstr :8000

# 访问测试页面
http://localhost:8000

5. 前端集成完整代码

5.1 基础集成方案

<!DOCTYPE html>
<html>
<head>
    <title>CLodop 打印示例</title>
    <!-- 引入CLodop JS文件 -->
    <script src="http://localhost:8000/CLodopfuncs.js"></script>
    <script>
        // 检查CLodop服务状态
        function checkCLodop() {
            try {
                if (getCLodop()) {
                    console.log('CLodop服务已就绪');
                    return true;
                } else {
                    console.warn('CLodop服务未启动');
                    return false;
                }
            } catch (e) {
                console.error('CLodop检查失败:', e);
                return false;
            }
        }

        // 获取CLodop对象
        function getCLodop() {
            if (window.getCLodop) {
                return window.getLodop();
            }
            return null;
        }
    </script>
</head>
<body onload="checkCLodop()">
    <!-- 打印内容 -->
    <div id="printContent">
        <h1>可打印内容</h1>
        <table border="1" style="width:100%">
            <tr><th>姓名</th><th>年龄</th><th>职位</th></tr>
            <tr><td>张三</td><td>30</td><td>工程师</td></tr>
            <tr><td>李四</td><td>25</td><td>设计师</td></tr>
        </table>
    </div>

    <button onclick="printDirect()">直接打印</button>
    <button onclick="printPreview()">打印预览</button>
    <button onclick="printDesign()">打印设计</button>
</body>
</html>

5.2 高级打印控制器

// clodop-manager.js
class CLodopManager {
    constructor() {
        this.lodop = null;
        this.isReady = false;
        this.init();
    }

    // 初始化CLodop
    init() {
        if (typeof window.getCLodop === 'undefined') {
            console.error('CLodop未加载请检查脚本引入');
            return;
        }

        this.lodop = window.getLodop();
        if (this.lodop) {
            this.isReady = true;
            console.log('CLodop初始化成功');
        } else {
            console.error('CLodop初始化失败');
        }
    }

    // 检查服务状态
    checkStatus() {
        if (!this.lodop) return false;
        
        try {
            const status = this.lodop.PRINT_STATUS;
            return status === 'READY';
        } catch (e) {
            return false;
        }
    }

    // 创建打印任务
    createPrintJob(title = '打印文档') {
        if (!this.isReady) {
            throw new Error('CLodop未就绪');
        }

        this.lodop.PRINT_INIT(title);
        return this;
    }

    // 设置打印内容
    setContent(htmlContent, options = {}) {
        const config = {
            top: options.top || '10mm',
            left: options.left || '10mm',
            width: options.width || '190mm',
            height: options.height || '277mm',
            ...options
        };

        this.lodop.ADD_PRINT_HTML(
            config.top, config.left, config.width, config.height,
            htmlContent
        );

        return this;
    }

    // 设置打印机
    setPrinter(printerName = '') {
        if (printerName) {
            this.lodop.SET_PRINTER_INDEX(printerName);
        }
        return this;
    }

    // 设置纸张大小
    setPaperSize(paperName = 'A4') {
        this.lodop.SET_PRINT_PAGESIZE(1, 0, 0, paperName);
        return this;
    }

    // 设置打印份数
    setCopies(copies = 1) {
        this.lodop.SET_PRINT_COPIES(copies);
        return this;
    }

    // 直接打印
    printDirect() {
        if (!this.isReady) return false;

        try {
            this.lodop.PRINT();
            return true;
        } catch (e) {
            console.error('打印失败:', e);
            return false;
        }
    }

    // 打印预览
    printPreview() {
        if (!this.isReady) return false;

        try {
            this.lodop.PREVIEW();
            return true;
        } catch (e) {
            console.error('预览失败:', e);
            return false;
        }
    }

    // 打印设计
    printDesign() {
        if (!this.isReady) return false;

        try {
            this.lodop.PRINT_DESIGN();
            return true;
        } catch (e) {
            console.error('设计失败:', e);
            return false;
        }
    }

    // 批量打印
    batchPrint(documents = []) {
        if (!this.isReady) return false;

        try {
            documents.forEach((doc, index) => {
                this.lodop.PRINT_INIT(`文档${index + 1}`);
                this.lodop.ADD_PRINT_HTML("10mm", "10mm", "190mm", "277mm", doc);
                
                if (index < documents.length - 1) {
                    this.lodop.NEWPAGE(); // 分页
                }
            });

            this.lodop.PREVIEW();
            return true;
        } catch (e) {
            console.error('批量打印失败:', e);
            return false;
        }
    }
}

// 全局实例
window.clodopManager = new CLodopManager();

6. 实际应用示例

6.1 票据打印

// receipt-print.js
function printReceipt(orderData) {
    const manager = window.clodopManager;
    if (!manager.isReady) {
        alert('打印服务未就绪');
        return;
    }

    const receiptHTML = `
        <div style="width:80mm;font-family:'宋体';font-size:12px;">
            <h3 style="text-align:center;">销售小票</h3>
            <hr>
            <p><strong>订单号:</strong>${orderData.orderNo}</p>
            <p><strong>时间:</strong>${new Date().toLocaleString()}</p>
            <hr>
            <table style="width:100%;">
                ${orderData.items.map(item => `
                    <tr>
                        <td>${item.name}</td>
                        <td>×${item.quantity}</td>
                        <td>¥${item.price}</td>
                    </tr>
                `).join('')}
            </table>
            <hr>
            <p style="text-align:right;"><strong>总计:¥${orderData.total}</strong></p>
        </div>
    `;

    manager.createPrintJob('销售小票')
        .setPaperSize(1) // 1=80mm 小票
        .setContent(receiptHTML, {
            width: '80mm',
            height: 'auto'
        })
        .printDirect();
}

6.2 报表打印

// report-print.js
function printReport(reportData) {
    const manager = window.clodopManager;
    
    const reportHTML = `
        <div style="font-family:'微软雅黑';padding:20px;">
            <h1 style="text-align:center;">${reportData.title}</h1>
            <table border="1" style="width:100%;border-collapse:collapse;">
                <thead>
                    <tr style="background:#f5f5f5;">
                        ${reportData.headers.map(header => 
                            `<th style="padding:8px;">${header}</th>`
                        ).join('')}
                    </tr>
                </thead>
                <tbody>
                    ${reportData.rows.map(row => `
                        <tr>
                            ${row.map(cell => 
                                `<td style="padding:6px;">${cell}</td>`
                            ).join('')}
                        </tr>
                    `).join('')}
                </tbody>
            </table>
            <div style="margin-top:20px;">
                <p>打印时间:${new Date().toLocaleString()}</p>
                <p>打印人:${reportData.printUser}</p>
            </div>
        </div>
    `;

    manager.createPrintJob(reportData.title)
        .setPaperSize('A4')
        .setContent(reportHTML)
        .setPrinter(reportData.printer || '')
        .printPreview();
}

6.3 标签打印

// label-print.js
function printLabels(labelsData) {
    const manager = window.clodopManager;
    
    manager.createPrintJob('产品标签');
    
    labelsData.forEach((label, index) => {
        const labelHTML = `
            <div style="width:50mm;height:30mm;border:1px dotted #ccc;padding:5mm;font-size:10px;">
                <div style="text-align:center;font-weight:bold;">${label.productName}</div>
                <div>规格:${label.spec}</div>
                <div>批次:${label.batchNo}</div>
                <div>有效期:${label.expiryDate}</div>
                <div style="text-align:center;margin-top:2mm;">
                    <img src="${label.barcodeUrl}" style="height:15mm;">
                </div>
            </div>
        `;
        
        manager.setContent(labelHTML, {
            top: `${(index % 5) * 32}mm`,
            left: `${Math.floor(index / 5) * 52}mm`,
            width: '50mm',
            height: '30mm'
        });
        
        if (index < labelsData.length - 1) {
            manager.lodop.NEWPAGE();
        }
    });
    
    manager.printPreview();
}

7. 高级功能实现

7.1 打印状态监控

// print-monitor.js
class PrintMonitor {
    constructor() {
        this.printJobs = new Map();
        this.initEvents();
    }

    initEvents() {
        // 监听打印开始
        window.addEventListener('beforeprint', (e) => {
            console.log('打印开始');
            this.onPrintStart(e);
        });

        // 监听打印结束
        window.addEventListener('afterprint', (e) => {
            console.log('打印结束');
            this.onPrintEnd(e);
        });
    }

    onPrintStart(jobId) {
        this.printJobs.set(jobId, {
            startTime: new Date(),
            status: 'printing'
        });
    }

    onPrintEnd(jobId, success = true) {
        const job = this.printJobs.get(jobId);
        if (job) {
            job.endTime = new Date();
            job.status = success ? 'completed' : 'failed';
            job.duration = job.endTime - job.startTime;
            
            // 发送打印统计
            this.sendPrintStatistics(job);
        }
    }

    sendPrintStatistics(job) {
        // 发送到服务器记录
        fetch('/api/print/log', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(job)
        });
    }

    // 获取打印机列表
    getPrinterList() {
        const lodop = getCLodop();
        if (!lodop) return [];
        
        const printers = [];
        const count = lodop.GET_PRINTER_COUNT();
        
        for (let i = 0; i < count; i++) {
            printers.push({
                name: lodop.GET_PRINTER_NAME(i),
                status: lodop.GET_PRINTER_STATUS(i)
            });
        }
        
        return printers;
    }
}

7.2 错误处理和重试机制

// error-handler.js
class PrintErrorHandler {
    static async printWithRetry(printFunction, maxRetries = 3) {
        for (let attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                const result = await printFunction();
                return result;
            } catch (error) {
                console.error(`打印尝试 ${attempt} 失败:`, error);
                
                if (attempt === maxRetries) {
                    throw new Error(`打印失败,已重试 ${maxRetries} 次`);
                }
                
                // 等待后重试
                await this.delay(1000 * attempt);
            }
        }
    }

    static delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    static handleCommonErrors(error) {
        const errorMap = {
            'PRINTER_NOT_FOUND': '打印机未找到,请检查打印机连接',
            'OUT_OF_PAPER': '打印机缺纸,请添加纸张',
            'SERVICE_UNAVAILABLE': '打印服务不可用请检查CLodop服务',
            'TIMEOUT': '打印超时,请重试'
        };

        const message = errorMap[error.code] || `打印错误: ${error.message}`;
        this.showErrorDialog(message);
    }

    static showErrorDialog(message) {
        // 可以替换为更友好的UI提示
        alert(`打印错误: ${message}`);
    }
}

8. 部署和配置最佳实践

8.1 Docker 部署

# Dockerfile
FROM windows/servercore:ltsc2019

# 安装CLodop服务
COPY CLodop_Setup.exe /setup/
RUN /setup/CLodop_Setup.exe /S

# 暴露端口
EXPOSE 8000 18000

# 启动服务
CMD ["C:\\Program Files (x86)\\CLodop\\CLodop.exe", "-start"]

8.2 Nginx 反向代理配置

# nginx.conf
server {
    listen 80;
    server_name print.yourdomain.com;
    
    location /CLodop/ {
        proxy_pass http://localhost:8000/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # WebSocket 支持
    location /CLodop/ws {
        proxy_pass http://localhost:8000/ws;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

9. 安全配置

9.1 访问控制

// security-middleware.js
const express = require('express');
const app = express();

// IP白名单
const allowedIPs = ['192.168.1.0/24', '10.0.0.0/8'];

app.use('/print', (req, res, next) => {
    const clientIP = req.ip || req.connection.remoteAddress;
    
    if (!isIPAllowed(clientIP, allowedIPs)) {
        return res.status(403).json({ error: '访问被拒绝' });
    }
    
    next();
});

function isIPAllowed(ip, allowedRanges) {
    // IP地址验证逻辑
    return allowedRanges.some(range => {
        // 简单的IP范围检查实现
        return ip.startsWith(range.split('.')[0]);
    });
}

10. 性能优化

10.1 打印任务队列

// print-queue.js
class PrintQueue {
    constructor() {
        this.queue = [];
        this.isProcessing = false;
        this.maxConcurrent = 1; // 串行打印
    }

    async addJob(printJob) {
        return new Promise((resolve, reject) => {
            this.queue.push({ printJob, resolve, reject });
            this.processQueue();
        });
    }

    async processQueue() {
        if (this.isProcessing || this.queue.length === 0) {
            return;
        }

        this.isProcessing = true;
        
        while (this.queue.length > 0) {
            const { printJob, resolve, reject } = this.queue.shift();
            
            try {
                const result = await printJob();
                resolve(result);
            } catch (error) {
                reject(error);
            }
            
            // 延迟一下,避免打印机过载
            await this.delay(500);
        }
        
        this.isProcessing = false;
    }

    delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

💡 总结

CLodop 核心优势

  1. 零客户端安装 - 基于云服务架构
  2. 完美浏览器兼容 - 支持所有现代浏览器
  3. 企业级功能 - 批量打印、精确控制、多种纸张支持
  4. 易于集成 - 简单的 JavaScript API

适用场景

  • 企业ERP系统 - 报表、单据打印
  • 电商平台 - 订单、发货单打印
  • 医疗系统 - 处方、报告打印
  • 政府办公 - 公文、证件打印
  • 教育系统 - 成绩单、证书打印

部署建议

  1. 开发环境本地安装CLodop服务
  2. 测试环境:内网服务器部署
  3. 生产环境:高可用集群部署,配合负载均衡

CLodop 是解决Web打印难题的优秀方案特别适合需要精确控制打印格式的企业级应用