毫秒级响应!前端直连打印机实时打印标签码的完整方案(斑马打印机)

0 阅读10分钟

大家好,我是张张!近期深度参与了一个工业自动化领域的落地项目,颇有感触。客户期望在产线末端构建一套自动化的装箱标签生成与管理系统,其核心业务流程如下:

工件经由传送带流转至指定工位后,由工业视觉系统自动完成识别与检测。操作人员依据视觉系统反馈的实时检测结果,将工件分拣入箱。待装箱数量达到预设标准时,系统将自动触发打印机制,生成一枚包含完整装箱信息的二维码/条码标签,以供粘贴于箱体。与此同时,该标签所对应的数据记录会被自动存入后台数据库,后续可通过PDA扫码枪快速检索,精准调取该装箱批次的详细溯源信息。

该方案的核心价值亮点在于:实现了检测结果与物理标签的实时同步转化,打通了从数据采集到物理输出的最后一环,在提升现场作业效率的同时,也为产品全流程追溯提供了可靠的数据支撑。

核心实现讲解

  • 核心技术
  • vue.js 2.x:前端框架
  • Element UI:组件库
  • SSE(Server-Sent Events):服务器推送技术
  • Zebra Browser Print:斑马打印机服务
  • Vuex:状态管理

技术原理图:

ewm.png 1、先封装一个SSE管理器来处理与服务器的连接:

import { getToken } from '@/utils/auth'

class SSEDataParser {
  constructor() {
    this.buffer = '';
    this.currentEvent = {
      event: 'message',
      data: '',
      id: null,
      retry: null
    };
  }

  parse(chunk) {
    this.buffer += chunk;
    const events = [];
    
    const lines = this.buffer.split('\n');
    // 保留最后一行(可能不完整)
    this.buffer = lines.pop() || '';
    
    for (const line of lines) {
      const event = this.parseLine(line.trim());
      if (event) {
        events.push(event);
      }
    }
    
    return events;
  }

  parseLine(line) {
    if (line === '') {
      // 空行表示事件结束
      if (this.currentEvent.data || this.currentEvent.id || this.currentEvent.retry) {
        const completedEvent = { ...this.currentEvent };
        this.resetCurrentEvent();
        return completedEvent;
      }
      return null;
    }
    
    if (line.startsWith('id:')) {
      this.currentEvent.id = line.substring(3).trim();
    } else if (line.startsWith('retry:')) {
      const retryValue = parseInt(line.substring(6).trim());
      if (!isNaN(retryValue)) {
        this.currentEvent.retry = retryValue;
      }
    } else if (line.startsWith('data:')) {
      this.currentEvent.data = line.substring(5).trim();
    } else if (line.startsWith('event:')) {
      this.currentEvent.event = line.substring(6).trim();
    }
    
    return null;
  }

  resetCurrentEvent() {
    this.currentEvent = {
      event: 'message',
      data: '',
      id: null,
      retry: null
    };
  }

  clearBuffer() {
    this.buffer = '';
    this.resetCurrentEvent();
  }
}

class CustomSSEManager {
    constructor() {
        this.controller = null;
        this.listeners = {};
        this.isConnected = false;
        this.parser = new SSEDataParser();
        this.reader = null;
        this._store = null;
        this.messageQueue = [];

        // 延迟获取 store
        this.initStore();
    }
    async initStore() {
        try {
            // 动态导入 store
            const storeModule = await import('@/store');
            this._store = storeModule.default;
            console.log('✅ store 动态导入成功');
            
            // 处理消息队列
            this.processMessageQueue();
        } catch (error) {
            console.error('❌ store 动态导入失败:', error);
            
            // 如果动态导入失败,尝试从 window 获取
            setTimeout(() => {
                this.tryGetStoreFromWindow();
            }, 1000);
        }
    }
     // 从 window 获取 store
    tryGetStoreFromWindow() {
        try {
            // Vue DevTools 中可能可以获取到
            if (window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.store) {
                this._store = window.__VUE_DEVTOOLS_GLOBAL_HOOK__.store;
                console.log('✅ 从 window 获取到 store');
                this.processMessageQueue();
            } else {
                console.warn('⚠️ 无法从 window 获取 store,2秒后重试');
                setTimeout(() => this.tryGetStoreFromWindow(), 2000);
            }
        } catch (e) {
            console.error('从 window 获取 store 失败:', e);
        }
    }
    get store() {
        return this._store;
    }
    /**
     * 连接SSE
     */
    async connect(url, options = {}) {
        try {
        this.close();

        const token = getToken();
        this.controller = new AbortController();

        const response = await fetch(url, {
            method: 'GET',
            headers: {
            'Authorization': `Bearer ${token}`
            },
            signal: this.controller.signal
        });
        if (!response.ok) {
            throw new Error(`SSE连接失败: ${response.status}`);
        }

        this.isConnected = true;
        
        if (options.onOpen) {
            options.onOpen();
        }

        // 处理流数据
        await this.processStream(response.body, options);

        } catch (error) {
        console.error('SSE连接失败:', error);
        if (options.onError) {
            options.onError(error);
        }
        }
    }

    /**
     * 处理服务器推送的数据流
     */
    async processStream(readableStream, options) {
        const reader = readableStream.getReader();
        const decoder = new TextDecoder();

        try {
        while (this.isConnected) {
            const { done, value } = await reader.read();
            
            if (done) {
            console.log('SSE流结束');
            break;
            }

            const chunk = decoder.decode(value, { stream: true });
            this.processChunk(chunk, options);
        }

        } catch (error) {
        // console.error('处理SSE流错误:', error);
        if (options.onError) {
            options.onError(error);
        }
        }
    }     

    /**
     * 处理SSE数据行
     */
    processChunk(chunk, options) {
        const events = this.parser.parse(chunk);
        
        events.forEach(event => {
        this.handleParsedEvent(event, options);
        });
    }

    /**
     * 保存消息到Vuex
     */
    saveToVuex(message) {
        try {
            if (!this.store) {
                console.warn('Vuex store不存在');
                return;
            }
            
            if (!this.store.state?.sse) {
                console.warn('sse模块未注册到Vuex');
                return;
            }
            
            // **重点:使用 commit 保存消息**
            this.store.commit('sse/ADD_MESSAGE', message);
            
            // **如果有打印内容,也保存到 printContent**
            if (message.printContent || message.content) {
                this.store.commit('sse/UPDATE_PRINT_CONTENT', message.printContent || message.content);
            }
            console.log('消息已保存到Vuex:', message);
            
        } catch (error) {
            console.error('保存到Vuex失败:', error);
            // 如果commit失败,尝试使用dispatch
            try {
                this.store.dispatch('sse/addMessage', message);
            } catch (dispatchError) {
                console.error('dispatch也失败:', dispatchError);
            }
        }
    }
     processMessageQueue() {
        if (this.messageQueue.length === 0 || !this._store) return;
        this.messageQueue.forEach(msg => {
            try {
                if (this._store.state?.sse) {
                    this._store.commit('sse/ADD_MESSAGE', msg);
                }
            } catch (e) {
                console.error('处理队列消息失败:', e);
            }
        });
        
        this.messageQueue = [];
    }

    handleParsedEvent(event, options) {
        try {
        const data = event.data ? JSON.parse(event.data) : {};
        
        // 构建完整的消息对象
        const message = {
            ...data,
            _sse: {
            id: event.id, // afdb7713-bd9e-43d0-a908-aa442cf6f231
            retry: event.retry, // 60000
            eventType: event.event, // message
            timestamp: Date.now()
            }
        };

        console.log('处理消息:', message);
        this.saveToVuex(message);
        // 触发通用消息回调
        if (options.onMessage) {
            options.onMessage(message);
        }

        // 根据消息类型触发特定事件
        this.triggerEventListeners('message', message);
        
        // 如果消息有type字段,也触发对应类型的事件
        if (data.type) {
            this.triggerEventListeners(`type_${data.type}`, message);
        }
        } catch (error) {
        console.error('处理SSE事件失败:', error, '原始事件:', event);
        }
    }
  
  addEventListener(eventName, callback) {
    if (!this.listeners[eventName]) {
      this.listeners[eventName] = [];
    }
    this.listeners[eventName].push(callback);
  }

  triggerEventListeners(eventName, data) {
    if (this.listeners[eventName]) {
      this.listeners[eventName].forEach(callback => {
        try {
          callback(data);
        } catch (error) {
          console.error(`事件 ${eventName} 监听器错误:`, error);
        }
      });
    }
  }

  /**
   * 关闭连接
   */
  close() {
    this.isConnected = false;
    if (this.controller) {
      this.controller.abort();
      this.controller = null;
    }
   if (this.reader) {
      this.reader.cancel().catch(error => {
        // 忽略取消读取器的错误,包括 AbortError
        if (error.name !== 'AbortError') {
          console.warn('取消读取器时出现错误:', error);
        }
      });
      this.reader = null;
    }
    if (this.parser && this.parser.clearBuffer) {
      this.parser.clearBuffer();
    }
    // 修复:直接赋值为空对象,而不是调用 clear()
    this.listeners = {};
    
    console.log('SSE连接已关闭');
  }
}
const customSSEManager = new CustomSSEManager();
export default customSSEManager;

export const printWithIframe = (data) => {
    return customSSEManager.printWithIframe(data);
};

export const autoPrint = (message) => {
    return customSSEManager.autoPrint(message);
};

2、使用vueX管理SSE消息状态,实现跨组件数据共享:


const state = {
  sseMessages: [], // 所有SSE消息
  latestMessage: null, // 最新消息
  ngMessages: [], // 类型为2的NG消息
  okMessages: [], // 类型为1的OK消息
  printContent: null
}

const mutations = {
  ADD_MESSAGE: (state, message) => {
    state.sseMessages.unshift(message);
    state.latestMessage = message;
    // 根据类型分类存储
    if (message.type === '2') {
      state.ngMessages.unshift(message);
    }
    
    // 如果消息包含打印内容,保存到printContent
   if (message.printContent) {
      try {
        // 如果printContent是字符串,尝试解析
        if (typeof message.printContent === 'string') {
          const parsedContent = JSON.parse(message.printContent);
          state.printContent = parsedContent; // 存储解析后的对象
          console.log('printContent已解析:', parsedContent);
        } else {
          state.printContent = message.printContent;
        }
      } catch (e) {
        state.printContent = message.printContent;
      }
    } else if (message.content) {
      state.printContent = message.content;
    }
  },
  
  // 更新打印内容
  UPDATE_PRINT_CONTENT: (state, content) => {
    state.printContent = content;
    console.log('打印内容已更新:', content);
  },
  
  // 清空所有消息
  CLEAR_MESSAGES: (state) => {
    state.sseMessages = [];
    state.latestMessage = null;
    state.ngMessages = [];
    state.okMessages = [];
    state.printContent = null;
    console.log('所有SSE消息已清空');
  },
  
  // 清空NG消息
  CLEAR_NG_MESSAGES: (state) => {
    state.ngMessages = [];
  },
  
  // 清空OK消息
  CLEAR_OK_MESSAGES: (state) => {
    state.okMessages = [];
  },
const actions = {
  // 添加消息
  addMessage({ commit }, message) {
    commit('ADD_MESSAGE', message);
    return message;
  },
  // 更新打印内容
  updatePrintContent({ commit }, content) {
    commit('UPDATE_PRINT_CONTENT', content);
    return content;
  },
  // 获取最新的N条消息
  getRecentMessages({ state }, count = 10) {
    return state.sseMessages.slice(0, count);
  },
  
  // 根据类型获取消息
  getMessagesByType({ state }, type) {
    if (type === '2') return state.ngMessages;
    if (type === '1') return state.okMessages;
    return state.sseMessages;
  }
}

export default {
  namespaced: true,
  mutations,
  state,
  actions
}

3、打印机组件封装

<template>
  <div class="zebra-print-page">
    <!-- 打印机状态显示 -->
    <div class="printer-status">
      <span>打印机状态:</span>
      <span :class="{'connected': selectedDevice, 'disconnected': !selectedDevice}">
        {{ selectedDevice ? '已连接' : '未连接' }}
      </span>
      <span v-if="selectedDevice" class="printer-uid">({{ selectedDeviceUid }})</span>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ZD888Print',
  data() {
    return {
      selectedDevice: null, // 当前选中的打印机实例
      devices: [], // 所有打印机列表
      selectedDeviceUid: '', // 选中打印机的UID
      alerts: [], // 存储所有消息的数组
      refreshing: false // 刷新状态
    };
  },
  mounted() {
    this.setup();
  },
  methods: {
    showAlert(message, type = 'info', title = '提示') {
      this.alerts.push({
        title: title,
        type: type,
        description: message,
        closable: true
      });

      setTimeout(() => {
        if (this.alerts.length > 0) {
          this.alerts.shift();
        }
      }, 5000);
    },
    removeAlert(index) {
      this.alerts.splice(index, 1);
    },
    setup() {
      if (!window.BrowserPrint) {
        this.showAlert('BrowserPrint未加载,请检查脚本引入!', 'error', '错误');
        return;
      }

      BrowserPrint.getDefaultDevice("printer", (device) => {
        if (device) {
          this.selectedDevice = device;
          this.selectedDeviceUid = device.uid;
          this.showAlert('打印机连接成功!', 'success', '成功');
        }
      }, (error) => {
        this.showAlert(error, 'error', '错误');
      });
    }
  }
};
</script>

4、页面调用

<template>
    <div class="app-container">
        <ZD888Print ref="zd888print"></ZD888Print>
    </div>
</template>

<script>
    import ZD888Print from '@/components/ZD888Print'
    import { mapState } from 'vuex'

    export default{
        components:{ ZD888Print },
        data(){
            return{
                refreshTimer: null,
                printing: false
            }
        },
        computed: {
            ...mapState('sse', ['printContent']),
        },
        watch: {
            // 监听printContent变化,收到消息直接打印
            printContent: {
                async handler(newVal) {
                    if (!newVal || this.printing) return;
                    await this.convertAndPrint(newVal);
                },
                immediate: true
            }
        },
        methods:{
            // 转换内容为ZPL并打印
            async convertAndPrint(content) {
                if (!content) {
                    this.$message.warning('打印内容为空');
                    return;
                }
                this.printing = true;
                try {
                    // 检查打印机是否连接
                    if (!this.$refs.zd888print || !this.$refs.zd888print.selectedDevice) {
                        this.$message.error('打印机未连接,请检查打印机状态');
                        return;
                    }
                    // 根据内容类型生成不同的ZPL指令
                    let zplCommand = this.generateZPL(content);
                    // 发送到打印机
                    await this.sendToPrinter(zplCommand);
                    this.$message.success('打印成功');
                } catch (error) {
                    console.error('打印失败:', error);
                    this.$message.error('打印失败:' + error.message);
                } finally {
                    this.printing = false;
                }
            },
            
            // 生成ZPL指令
            generateZPL(data) {
                // 如果传入的是字符串,尝试解析JSON
                let printData = data;
                if (typeof data === 'string') {
                    try {
                        printData = JSON.parse(data);
                    } catch (e) {
                        // 如果不是JSON,当作普通文本处理
                        printData = { text: data };
                    }
                }
                
                // 根据数据类型生成不同的ZPL模板
                if (printData.boxCode || printData.productCode) {
                    // 装箱码/产品码打印模板
                    return this.generateBoxLabelZPL(printData);
                } else if (printData.text) {
                    // 普通文本打印模板
                    return this.generateTextZPL(printData.text);
                } else {
                    // 默认模板
                    return this.generateDefaultZPL(printData);
                }
            },
            // 生成装箱标签ZPL
            generateBoxLabelZPL(data, options = {}) {
                const boxCode = data.boxCode || data.productCode || '未知';
                const lineName = data.lineName || '1#线';
                const productModel = data.productModel || 'LHR117A';
                const productQty = data.productQty || data.count || 0;
                const qualifiedQty = data.qualifiedQty || data.count || 0;
                
                // 二维码内容(可以自定义)
                const qrCodeContent = data.qrContent || boxCode;
                
                // 布局参数
                const labelWidth = options.labelWidth || 830;      // 标签宽度
                const labelHeight = options.labelHeight || 650;    // 标签高度
                const leftMargin = options.leftMargin || 85;       // 左边距
                const topMargin = options.topMargin || 45;         // 上边距
                const rowHeight = options.rowHeight || 65;         // 行高
                const titleFontSize = options.titleFontSize || 22;  // 标题字体大小
                const valueFontSize = options.valueFontSize || 24;  // 值字体大小
                const qrSize = options.qrSize || 6;                 // 二维码大小
                
                // 计算文字垂直居中偏移量
                const titleVerticalOffset = Math.floor((rowHeight - titleFontSize) / 2);
                const valueVerticalOffset = Math.floor((rowHeight - valueFontSize) / 2);
                
                // 计算表格尺寸
                const col1Width = 140;  // 第一列宽度(标题列)
                const col2Width = 290;  // 第二列宽度(值列)- 保持不变
                const col3Width = 230;  // 第三列宽度(二维码列)- 保持不变
                const tableWidth = col1Width + col2Width + col3Width; // 660
                
                // 确保表格宽度不超过标签宽度
                const maxTableWidth = labelWidth - leftMargin * 2; // 830 - 170 = 660
                const adjustedTableWidth = Math.min(tableWidth, maxTableWidth); // 660
                
                const tableHeight = rowHeight * 5; // 325
                
                // 计算各列X坐标
                const col1X = leftMargin;
                const col2X = leftMargin + col1Width;
                const col3X = leftMargin + col1Width + col2Width;
                
                // 计算右侧边框位置(二维码区域右侧竖线)
                const rightBorderX = leftMargin + adjustedTableWidth;
                
                // 估算二维码宽度和高度
                const estimatedQrSize = qrSize * 22; // 132
                
                // 计算二维码Y坐标(居中显示)
                const qrY = topMargin + (tableHeight - estimatedQrSize) / 2;
                
                // 计算二维码在第三列内的居中位置
                // 可用空间 = 第三列宽度 - 二维码宽度
                const availableSpace = col3Width - estimatedQrSize; // 230 - 132 = 98
                // 左右边距相等,使二维码完全居中
                const sideMargin = Math.floor(availableSpace / 2); // 49
                
                // 二维码X坐标(在第三列内完全居中)
                const qrX = col3X + sideMargin;
                
                // 线条粗细
                const borderThickness = 3;
                
                return `^XA
                ^SEE:GB18030.DAT^FS
                ^CWZ,E:SIMSUN.FNT
                ^CI28
                ^JMA
                ^LL${labelHeight}
                ^PW${labelWidth}
                ^MD10
                ^PR2
                ^PON
                ^LRN
                ^LH0,0

                ^FO${leftMargin},${topMargin}
                ^GB${adjustedTableWidth},0,${borderThickness}^FS

                ^FO${leftMargin},${topMargin + tableHeight}
                ^GB${adjustedTableWidth},0,${borderThickness}^FS

                ^FO${leftMargin},${topMargin}
                ^GB0,${tableHeight},${borderThickness}^FS

                ^FO${col2X},${topMargin}
                ^GB0,${tableHeight},${borderThickness}^FS

                ^FO${col3X},${topMargin}
                ^GB0,${tableHeight},${borderThickness}^FS

                ^FO${rightBorderX},${topMargin}
                ^GB0,${tableHeight},${borderThickness}^FS

                ^FO${leftMargin},${topMargin + rowHeight}
                ^GB${col1Width + col2Width},0,${borderThickness}^FS
                ^FO${leftMargin},${topMargin + rowHeight * 2}
                ^GB${col1Width + col2Width},0,${borderThickness}^FS
                ^FO${leftMargin},${topMargin + rowHeight * 3}
                ^GB${col1Width + col2Width},0,${borderThickness}^FS
                ^FO${leftMargin},${topMargin + rowHeight * 4}
                ^GB${col1Width + col2Width},0,${borderThickness}^FS

                ^FO${col1X + 10},${topMargin + titleVerticalOffset}
                ^AZN,${titleFontSize},${titleFontSize}
                ^FD装箱编码^FS
                ^FO${col2X + 10},${topMargin + valueVerticalOffset}
                ^AZN,${valueFontSize},${valueFontSize}
                ^FD${boxCode}^FS

                ^FO${col1X + 10},${topMargin + rowHeight + titleVerticalOffset}
                ^AZN,${titleFontSize},${titleFontSize}
                ^FD装配线^FS
                ^FO${col2X + 10},${topMargin + rowHeight + valueVerticalOffset}
                ^AZN,${valueFontSize},${valueFontSize}
                ^FD${lineName}^FS

                ^FO${col1X + 10},${topMargin + rowHeight * 2 + titleVerticalOffset}
                ^AZN,${titleFontSize},${titleFontSize}
                ^FD产品型号^FS
                ^FO${col2X + 10},${topMargin + rowHeight * 2 + valueVerticalOffset}
                ^AZN,${valueFontSize},${valueFontSize}
                ^FD${productModel}^FS

                ^FO${col1X + 10},${topMargin + rowHeight * 3 + titleVerticalOffset}
                ^AZN,${titleFontSize},${titleFontSize}
                ^FD装箱数量^FS
                ^FO${col2X + 10},${topMargin + rowHeight * 3 + valueVerticalOffset}
                ^AZN,${valueFontSize},${valueFontSize}
                ^FD${productQty}^FS

                ^FO${col1X + 10},${topMargin + rowHeight * 4 + titleVerticalOffset}
                ^AZN,${titleFontSize},${titleFontSize}
                ^FD合格数量^FS
                ^FO${col2X + 10},${topMargin + rowHeight * 4 + valueVerticalOffset}
                ^AZN,${valueFontSize},${valueFontSize}
                ^FD${qualifiedQty}^FS

                ^FO${qrX},${qrY}
                ^BQN,2,${qrSize}
                ^FDQA,${qrCodeContent}^FS

                ^XZ`;
            },
            // 生成普通文本ZPL
            generateTextZPL(text) {
                return `^XA
                ^SEE:GB18030.DAT^FS
                ^CWZ,E:SIMSUN.FNT
                ^CI28
                ^JMA
                ^LL200
                ^PW700
                ^MD10
                ^PR2
                ^PON
                ^LRN
                ^LH0,0
                ^FO50,50
                ^AZN,36,36
                ^FD${text}^FS
                ^XZ`;
            },
            
            // 生成默认ZPL
            generateDefaultZPL(data) {
                // 将对象转换为字符串显示
                const text = typeof data === 'object' ? JSON.stringify(data) : String(data);
                return this.generateTextZPL(text.substring(0, 50)); // 限制长度
            },
            
            // 发送到打印机
            sendToPrinter(zplCommand) {
                return new Promise((resolve, reject) => {
                    if (!this.$refs.zd888print || !this.$refs.zd888print.selectedDevice) {
                        reject(new Error('打印机未连接'));
                        return;
                    }
                    // 使用ZD888Print组件的发送方法
                    this.$refs.zd888print.selectedDevice.send(zplCommand, 
                        () => {
                            resolve();
                        }, 
                        (error) => {
                            reject(error);
                        }
                    );
                });
            },
            
            // 格式化时间
            formatTime(date) {
                const year = date.getFullYear();
                const month = String(date.getMonth() + 1).padStart(2, '0');
                const day = String(date.getDate()).padStart(2, '0');
                const hours = String(date.getHours()).padStart(2, '0');
                const minutes = String(date.getMinutes()).padStart(2, '0');
                return `${year}-${month}-${day} ${hours}:${minutes}`;
            },
            
            // 快速打印所有数据
            async handleQuickPrint(){
                getBoxPrintingData().then(res =>{
                    this.convertAndPrint(res.data);
                })
            },
            // 延迟函数
            delay(ms) {
                return new Promise(resolve => setTimeout(resolve, ms));
            }
        }
    }
</script>

结束啦,从视觉识别到标签生成,看似简单的流程闭环,实则解决了产线信息断点的核心痛点。当每一次装箱都能被精准记录、每一件产品都能被追溯溯源,企业构建的不仅是一套打印系统,更是一道质量防线和一张效率网络。这正是工业数字化落地的真实写照。

纵横工业互联网团队是河南863一支专注于工业数字化的团队,是深耕工业数字化转型领域的专业技术与解决方案服务商,聚焦工业企业智能化升级核心需求,打造了全栈式、可落地的工业互联网产品与服务体系。我们构建了自主可控的五大核心产品体系,涵盖面向产业集聚区 / 工业园区的产业集聚区工业互联网管理平台,以及面向工业企业全生产流程的物联网平台、能耗能碳管理平台、设备管理系统、MES 生产制造执行系统。 我们团队累计接入工业设备 10 万余台,覆盖 100 余类设备类型,适配 840 余种工业协议,深耕烟草、高端装备、汽车零部件、新能源电力、煤炭能源、家电制造等数十个核心工业领域,服务 50 余家行业头部企业与产业园区主体,沉淀了海量的项目落地经验与行业 Know-How,具备全链路数据采集、计算、应用与数字化运营能力,可为产业园区、工业企业提供一站式工业互联网解决方案,助力园区产业升级、治理提效,助力企业降本增效、精益管理、绿色转型。

“关于打印标签码的落地实践,欢迎通过我的掘金主页联系交流~”