vue使用js渲染组件案例(公用打印组件动态渲染)及静默打印实现

目前的业务是:对接三个平台,每个平台都会推送订单,有新订单时会通过socket推送给前端,前端需要根据门店配置实现订单的自动静默打印.

问题: 封装好了顾客联和商家联,需要根据不同的订单渲染组件然后获取html和style传给已经实现的静默打印函数。其中商家联组件中还包含了其他组件,例如: 封装的条形码组件

功能截图

组件封装

为了方便只列举一个

js 复制代码
<template>
  <div class="print-box" id="print-box" ref="printBoxRef">
    <h3>商家联</h3>
    <h3 class="plat-number">#{{ form.orderNum }} {{ form.orderPrintName ? form.orderPrintName :
      getChannelName(form.channelCode, channelType) }}</h3>
    <div class="flex plat-name">
      <span class="dot"></span>
      <a>{{ form.storeName }}</a>
      <span class="dot"></span>
    </div>
    <div class="flex span-column">
      <!-- <a>期望送达时间: 立即送达</a> -->
      <a>下单时间: {{ form.placeTime }}</a>
      <a>订单编号: {{ form.orderNumber }}</a>
      <a>客户备注: {{ form.buyerRemark ? form.buyerRemark : '无' }}</a>
    </div>
    <span class="line"></span>
    <div :key="index" v-for="(item, index) in form.orderProductList">
      <h3>{{ item.name }}</h3>
      <div>
        <span v-if="item.spec">规格: {{ item.spec }}</span>
        <span>({{ item.originalPrice }}-优惠承担费用)✖️{{ item.count }}={{ item.totalPrice }}</span>
      </div>
    </div>
    <span class="line"></span>
    <div class="flex span-column" style="margin-top: 15px;">
      <div class="flex span-between">
        <a>商品总额: {{ form.orderPrice ? form.orderPrice.productTotalMoney : 0 }}</a>
        <a>配送费: {{ form.freightMoney }}</a>
      </div>
      <a>门店承担优惠: -{{ form.orderPrice ? (form.orderPrice.activityExpenditures ? form.orderPrice.activityExpenditures : 0)
        : 0 }}</a>
      <!-- <a>平台承担优惠: -{{ form.orderPrice ? (form.orderPrice.activityExpenditures ? form.orderPrice.activityExpenditures : 0) : 0 }}</a> -->
    </div>
    <span class="line"></span>
    <div class="flex span-between" style="margin-top: 15px;">
      <a>实付: {{ form.orderPrice ? (form.orderPrice.payMoney ? form.orderPrice.payMoney : 0) : 0 }}</a>
      <a>门店预计收入: {{ form.orderPrice ? (form.orderPrice.estimatedRevenue ? form.orderPrice.estimatedRevenue : 0) : 0
      }}</a>
    </div>
    <span class="line"></span>
    <div class="flex span-column" style="margin-top: 15px;">
      <a>顾客: {{ form.consigneeName }}</a>
      <a>顾客联系电话: {{ form.consigneePhone }}</a>
      <a>顾客真实号后四位: {{ form.consigneePhoneLastFour }}</a>
      <a>地址: {{ form.consigneeAddress }}</a>
      <Barcode style="margin:  0 auto;" :text="form.id" />
      <div class="flex span-bottom">
        <span>************</span>
        <a>订单打印次数: {{ form.stampCount ? form.stampCount : 1 }}</a>
        <span>************</span>
      </div>
    </div>
  </div>
</template>

<script>
import Barcode from '@/components/Barcode/index.vue'
export default {
  components: {
    Barcode
  },
  // dicts: ['channel_type'],
  props: {
    form: {
      type: Object,
      default: () => ({})
    },
    channelType: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
    };
  },
  watch: {},
  computed: {
  },
  methods: {
    getChannelName(val, list) {
      let res = val
      if (list && list.length > 0) {
        let obj = list.find(item => item.value == val)
        if (obj && obj.label) {
          res = obj.label
        }
      }
      return res;
    },
    getHtml() {
      return this.$refs.printBoxRef.innerHTML
    }
  },
  created() { },
  mounted() { }
};
</script>
<style scoped lang="scss">
@import "../../../assets/print/print.css";
</style>

打印css封装

css 复制代码
/* 修改后的标准 CSS */
.print-box {
  border: 1px solid #aaaaaa;
  width: auto;
  padding: 10px;
  padding-top: 0px;
  box-sizing: border-box;
}

.plat-number {
  width: 100%;
  text-align: center;
}

.plat-name {
  display: flex; /* 添加 display: flex */
  justify-content: center;
  align-items: center;
  gap: 10px;
}

.dot {
  width: 6px;
  height: 6px;
  background: #000;
  border-radius: 5px;
}

.span-column {
  margin-top: 10px;
  display: flex; /* 添加 display: flex */
  flex-direction: column;
  gap: 10px;
}

.span-between {
  display: flex; /* 添加 display: flex */
  justify-content: space-between;
}

.line {
  height: 1px;
  width: 100%;
  display: block;
  background: #000;
  margin-top: 15px;
}

.span-bottom {
  display: flex; /* 添加 display: flex */
  justify-content: space-around;
}
@media print {
  @page {
   size: 80mm auto; /* 或 58mm auto */
    margin: 0;
    padding: 0;
  }
}

css样式处理

  1. 安装 "raw-loader": "^4.0.2",
js 复制代码
npm install raw-loader
  1. 在vue.config.js(chainWebpack方法)中进行配置
js 复制代码
    config.module
      .rule('raw-css')
      .test(/\.css$/)
      .include
      // 自己存放print.css样式的路径
      .add(path.resolve(__dirname, 'src/assets/print'))  // 只包含 src 目录
      .end()
      .exclude
      .add(/node_modules/)  // 排除 node_modules
      .end()
      .use('raw-loader')
      .loader('raw-loader')
      .end();

渲染

我使用的是监听器进行触发打印函数

js 复制代码
import Vue from 'vue';
// 处理css样式文本
import cssContent from '!raw-loader!@/assets/print/print.css';
import MerchantPrientTemplate from '@/views/plugins/modules/merchantPrientTemplate.vue';
import CustomerPrientTemplate from '@/views/plugins/modules/customerPrientTemplate.vue';
import { printKey } from '@/utils/printConstant'
export default {
  name: "App",
  components: {  },
  data() {
    return {
      printCSS: cssContent,
    }
  },
  created() {

  },
  methods: {
    async onmessageWSCreateOrder(data) {
      console.log('新订单监听器 ==>', data)
      let detail = (data.detail || {}).data
      let msg = "您有新的订单,请及时处理"
      // 语音播报
      this.speak(msg)
      // 消息通知
      this.$notify({
        title: '新订单提醒',
        dangerouslyUseHTMLString: true,
        message: msg + '<br />' + (detail || {}).sendTime, //detail.content,
        duration: 0
      });
      // todo: 验证是否自动接单,如果自动接单,需要再去获取配置查看是优先打印顾客还是商家,方法可以跟接单操作封装到一个hook中
      try {
        const { isAutoTake, autoPrintEmit } = await useAutoPrint((detail || {}).storeId)
        // 自动接单
        if (isAutoTake) {
          let orderId = (detail || {}).data
          // 自动接单成功后,获取订单详情
          const data = await this.takeOrder(orderId)
          // const { data } = await orderInfo(orderId)
          // 下面就是js渲染组件实现
          let merchantHtml = null;
          let customerHtml = null
          const merchantVue = Vue.extend(MerchantPrientTemplate)
          const merchantVm = new merchantVue({
            propsData: {
              form: data,
              channelType: this.channelType //this.dict.type.channel_type
            }
          })
          // 手动挂载Barcode组件
          merchantVm.$options.components.Barcode = (await import('@/components/Barcode/index.vue')).default;
          merchantVm.$mount();

          // 顾客
          const customerVue = Vue.extend(CustomerPrientTemplate)
          const customerVm = new customerVue({
            propsData: {
              form: data,
              channelType: this.channelType //this.dict.type.channel_type
            }
          })
          customerVm.$options.components.Barcode = (await import('@/components/Barcode/index.vue')).default;
          customerVm.$mount();

          // 等待渲染
          await this.$nextTick();
          // 获取需要打印的html
          merchantHtml = "<body>" + merchantVm.$el.innerHTML + "</body>" + "<style>" + this.printCSS + "</style>";
          merchantVm.$destroy()
          customerHtml = "<body>" + customerVm.$el.innerHTML + "</body>" + "<style>" + this.printCSS + "</style>";
          customerVm.$destroy()
          // 下面console就是最终渲染完后的完整的html+style
          console.log('merchantHtml', merchantHtml)
          console.log('customerHtml', customerHtml)
          // 执行自动打印---此处换成自己实现的静默打印函数
          autoPrintEmit(merchantHtml, customerHtml, printKey.PRINGT_ORDER + orderId)
        } else {
          console.log('不自动接单, 不做处理')
        }
      } catch (e) {
        console.error('新订单 useAutoPrint 异常', e)
      }
    },
  },
  beforeDestroy() {
    console.log('app 销毁监听器')
  },
}
</script>

条码组件

js 复制代码
<template>
  <div>
    <img ref="barcodeRef" />
  </div>
</template>

<script>
import JsBarcode from "jsbarcode";
export default {
  components: {},
  props: {
    fontSize: {
      type: Number,
      default: 10
    },
    height: {
      type: Number,
      default: 60
    },
    width: {
      type: Number,
      default: 5
    },
    text: {
      type: String,
      default: '编码内容'
    }
  },
  data() {
    return {
    };
  },
  watch: {
    text: {
      handler(val) {
        this.$nextTick(() => {
          this.getBarcode(val)
        })
      },
      deep: true,
      immediate: true
    }
  },
  computed: {},
  methods: {
    getBarcode(text) {
      let options = {
        text: text,//等同于JsBarcode第二个参数
        fontSize: this.fontSize,//条形码下方文字的大小
        height: this.height,//条形码的高度
        width: this.width,//条形码 条的宽度不是总体的宽度 (宽度过小 扫描枪会扫描不出来 )
        displayValue: false,//隐藏条形码下方文本
      };
      // JsBarcode第一个参数是容器ID名 第二个参数是条形码扫描后的内容(默认条形码的内容会在条形码下方展示) 第三个参数是方法的配置项(具体配置查看官网)
      // JsBarcode("#barcode", '5', options);
      JsBarcode(this.$refs.barcodeRef, '5', options);
      //实现批量生成的功能
      // prinList.value.forEach((item, index) => {
      // prinList.value.forEach((item, index) => {
      //   options.text = item.order_number;
      //   options.order_number = item.order_number;
      //   JsBarcode("#barcode-" + index, '5', options);  挂载条形码方法 注意批量生成 容器必须是唯一的 且同时都要存在 思路容器加上循环的index即可
      // });
    }
  },
  created() { },
  mounted() {
    // this.getBarcode()
  }
};
</script>
<style scoped></style>

题外话

静默打印

electron-hiprint + socket.io-client.js

前端socket封装

js 复制代码
import { io } from "socket.io-client";
import modal from '../../plugins/modal'
/**
print: {
  description: 描述,
  displayName: 显示名称,
  isDefault: 是否默认,
  name: 名称,
  description: 描述,
  status: 状态
}

html: HTML 字符串。
templateId: 用于标识成功或失败回调的 ID。
printer: 打印机名称。
pageSize: 打印纸张大小

this.socket.emit("news", { html, templateId, printer, pageSize });
// socket.emit("getClientInfo");
// 刷新打印列表
// socket.emit("refreshPrinterList");
 */
const printSocketData = {
  state: {
    socket: null,
    printList: [],
    // todo: url一般为localhost本地固定,只支持同个网段,如果不在同一个网段需要配置中转做云打印,暂时先不做云打印
    url: 'http://127.0.0.1:17521',
    // 链接token,用户动态配置跟插件保持一致
    token: '123456',
    isConnect: false
  },

  mutations: {
    SET_SOCKET(state, socket) {
      state.socket = socket;
      state.isConnect = true
    },
  },

  actions: {
    // 初始化打印机socket
    initPrintSocket({ state, commit, dispatch }) {
      console.log('初始化本地打印socket')
      const socket = io(state.url, {
        transports: ["websocket"],
        auth: {
          token: state.token
        }
      })
      socket.on('connect', () => {
        console.log('打印插件socket连接成功, ', new Date().toLocaleString('af'))
        commit('SET_SOCKET', socket);
      })
      socket.on('connect_error', () => {
        console.log('打印插件连接失败')
        // modal.msgError('打印插件socket连接失败')
      })
      socket.on('error', (e) => {
        console.log('打印插件异常', e)
        modal.msgError('打印插件异常: ' + e)
      })
      socket.on('clientInfo', (clientInfo) => {
        console.log('clientInfo ==>', clientInfo)
      })
      socket.on('printerList', (printerList) => {
        console.log('打印机列表', printerList)
        state.printList = printerList
      })
      socket.on('success', (res) => {
        console.log('打印插件打印成功', res)
        // todo: 打印成功后,执行事件,方便其他操作:比如回调后端增加订单打印次数
        window.dispatchEvent(new CustomEvent('onPrintSuccess', {
          detail: {
            res
          }
        }));
      })

    },
    // 刷新打印机列表
    refreshPrintList({ state }) {
      console.log('state.socket', state.socket)
      console.log('state', state)
      state.socket.emit('refreshPrinterList')
    },
    // 打印机是否存在
    hasPrint({ state }, printName) {
      return new Promise((resolve, reject) => {
        console.log('hasPrint printName', printName)
        if (state.printList && state.printList.length > 0) {
          let obj = state.printList.find(item => item.name === printName)
          console.log('hasPrint find obj ==>', obj)
          if (obj) {
            resolve(true);
          }
        }
        resolve(false);
      })
    },
    /**
     * templateId 可以穿订单id等标识信息
     * @param {*} param0
     * @param {*} param1
     * @returns
     */
    printEmit({ state, dispatch }, { eventName, html, templateId, printer, pageSize = null, printBackground = true }) {
      if (!eventName) {
        modal.msgError('事件名称不能为空')
        return;
      }
      console.log('执行打印')
      if (state.isConnect != true) {
        console.error('当前打印机插件socket未连接或未初始化')
        modal.msgError('当前打印机插件socket未连接或未初始化')
        return;
      }
      dispatch('hasPrint', printer).then(res => {
        console.log('printEmit res', res)
        if (res) {
          console.log('111', { html, templateId, printer, pageSize, printBackground })
          state.socket.emit(eventName, { html, templateId, printer, pageSize, printBackground });
        } else {
          console.error('当前打印机 ' + printer + ',不存在')
          modal.msgError('当前打印机 ' + printer + ',不存在')
        }
      })

    }
  }
};

export default printSocketData;

electron-hiprint

看官网文档> 传送门

安装完后:

如果找不到自己系统的应用,就下载源码自己手动编译即可

相关推荐
wyhwust2 小时前
学技术找工作经验分享--前端
前端
BD_Marathon2 小时前
【JavaWeb】JS_BOM编程_window对象的常见属性
前端
IT_陈寒2 小时前
Redis性能提升50%的7个关键配置:从慢查询优化到内存碎片整理实战指南
前端·人工智能·后端
还不秃顶的计科生2 小时前
wps“文件路径与可点击的超链接“之间的相互转换
前端
黛色正浓2 小时前
【React】极客园案例实践-编辑文章模块和项目打包
前端·react.js·前端框架
徐同保2 小时前
n8n项目编译时取消类型检测,提交代码时取消校验
开发语言·前端·javascript
不会kao代码的小王2 小时前
openEuler上Docker部署Kafka消息队列实战
前端·云原生·stable diffusion·eureka
Lenyiin2 小时前
makefile
java·大数据·前端
汝生淮南吾在北2 小时前
SpringBoot+Vue非遗文化宣传网站
java·前端·vue.js·spring boot·后端·毕业设计·课程设计