Vue3结合vue-plugin-hiprint实现自定义打印模板【模板数据访问后端接口】设计与布局(手把手教你,直接复制粘贴使用)

11,702次阅读
没有评论

共计 12518 个字符,预计需要花费 32 分钟才能阅读完成。

简介

在现代 Web 应用开发中,打印功能是不可或缺的一部分,尤其是在需要输出标准化文档的场景下。本文将详细介绍如何在 Vue3 项目中利用 vue-plugin-hiprint 插件实现一个可定制的打印模板设计器,并通过具体示例来展示其配置与使用方法,注意:下面代码中有个人的 js 和 css 样式代码,根据自己的需求去改即可。

技术栈

本项目基于以下技术栈:

  • Vue3
  • TypeScript (Ts)
  • Element Plus
  • Vite

插件安装

首先通过 npm 安装 vue-plugin-hiprint 插件:

npm install vue-plugin-hiprint

main.ts 引入组件

在项目的入口文件 main.ts 中引入 hiPrintPlugin 并注册到 Vue 应用实例上。同时引入全局 CSS(从 node_modules/vue-plugin-hiprint/dist/ 目录下复制一份 print-lock.css 到静态资源目录下)。

import { createApp } from 'vue';
import App from './App.vue';

import './assets/public/print-lock.css'  
import { hiPrintPlugin } from 'vue-plugin-hiprint';

hiPrintPlugin.disAutoConnect(); 
const app = createApp(App);
app.use(hiPrintPlugin, '$pluginName');

CSS 样式配置

为了确保打印效果符合预期,在原有的 print-lock.css 基础上进行了扩展和调整,以适应不同的浏览器环境,尤其是 Firefox 浏览器中可能出现的打印重叠问题。如下是个人的 iconfont.css,等会主组件要用,其中里面的SVG 字体 样式点击下载:

@font-face {
  font-family: "iconfont"; 
  src: url("iconfont.woff2?t=1667531544868") format("woff2"),
    url("iconfont.woff?t=1667531544868") format("woff"),
    url("iconfont.ttf?t=1667531544868") format("truetype");
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.sv-edit-data:before {
  content: "e655";
}

.sv-shimmer:before {
  content: "e6d6";
}

.sv-origin:before {
  content: "e6ac";
}

.sv-zIndex:before {
  content: "e603";
}

.sv-structure:before {
  content: "ec6f";
}

.sv-list:before {
  content: "e742";
}

.sv-grid:before {
  content: "e849";
}

.sv-flow:before {
  content: "e611";
}

.sv-switch:before {
  content: "e6f6";
}

.sv-theme:before {
  content: "e644";
}

.sv-element:before {
  content: "e615";
}

.sv-pdf:before {
  content: "e67a";
}

.sv-browser:before {
  content: "e726";
}

.sv-font-big:before {
  content: "eb04";
}

.sv-font-small:before {
  content: "eb05";
}

.sv-font-bold:before {
  content: "ec83";
}

.sv-font-tiny:before {
  content: "e6c1";
}

.sv-options:before {
  content: "e607";
}

.sv-close:before {
  content: "e646";
}

.sv-clone:before {
  content: "ec7a";
}

.sv-cut:before {
  content: "e643";
}

.sv-preview:before {
  content: "e61c";
}

.sv-zoom-in:before {
  content: "e60f";
}

.sv-zoom-out:before {
  content: "e610";
}

.sv-edit:before {
  content: "e6b9";
}

.sv-paste:before {
  content: "e6c0";
}

.sv-copy:before {
  content: "e6c2";
}

.sv-unlock:before {
  content: "e6e7";
}

.sv-lock:before {
  content: "e6e8";
}

.sv-zIndex-plus:before {
  content: "e715";
}

.sv-zIndex-minus:before {
  content: "e716";
}

.sv-zIndex-top:before {
  content: "e71f";
}

.sv-sigh:before {
  content: "e724";
}

.sv-ask:before {
  content: "e725";
}

.sv-dev-code:before {
  content: "e733";
}

.sv-bug:before {
  content: "e73f";
}

.sv-zIndex-bottom:before {
  content: "e71d";
}

.sv-new:before {
  content: "e64d";
}

.sv-clear:before {
  content: "e62d";
}

.sv-base:before {
  content: "e7d0";
}

.sv-export:before {
  content: "eabf";
}

.sv-import:before {
  content: "eac0";
}

.sv-add:before {
  content: "eaf3";
}

.sv-printer:before {
  content: "eabe";
}

.sv-save:before {
  content: "eabd";
}

.sv-more:before {
  content: "e625";
}

.sv-menu:before {
  content: "e628";
}

.sv-nav-right:before {
  content: "e629";
}

.sv-nav-up:before {
  content: "e62a";
}

.sv-nav-left:before {
  content: "e62b";
}

.sv-nav-down:before {
  content: "e62c";
}

.sv-setting:before {
  content: "e62e";
}

.sv-delete:before {
  content: "e630";
}

.sv-undo:before {
  content: "e631";
}

.sv-redo:before {
  content: "e632";
}

.sv-refresh:before {
  content: "e634";
}

.sv-history:before {
  content: "e635";
}

.sv-html:before {
  content: "e633";
}

.sv-longText:before {
  content: "e64c";
}

.sv-table:before {
  content: "ec15";
}

.sv-qrcode:before {
  content: "e642";
}

.sv-image:before {
  content: "e8ba";
}

.sv-barcode:before {
  content: "eb64";
}

.sv-text:before {
  content: "e60b";
}

.sv-vline:before {
  content: "e63a";
}

.sv-oval:before {
  content: "eb99";
}

.sv-rect:before {
  content: "e620";
}

.sv-hline:before {
  content: "e60a";
}

.sv-print-c:before {
  content: "e602";
}

.sv-print:before {
  content: "e601";
}

.sv-c:before {
  content: "e600";
}

.sv-vertical:before {
  content: "e706";
}

.sv-distributeHor:before {
  content: "e707";
}

.sv-right:before {
  content: "e708";
}

.sv-left:before {
  content: "e709";
}

.sv-distributeVer:before {
  content: "e70f";
}

.sv-bottom:before {
  content: "e710";
}

.sv-top:before {
  content: "e711";
}

.sv-horizontal:before {
  content: "e712";
}

.sv-rotate:before {
  content: "e66f";
}

.sv-butongbu:before {
  content: "e636";
}

.sv-synchronization:before {
  content: "e676";
}

.hiprint-headerLine,
.hiprint-footerLine {
  border-color: purple !important;
}

.hiprint-headerLine:hover,
.hiprint-footerLine:hover {
  border-top: 3px dashed purple !important;
}

.hiprint-headerLine:hover:before {
  content: "页眉线";
  left: calc(50% - 18px);
  position: relative;
  background: #ffff;
  top: -14px;
  color: purple;
  font-size: 12px;
}

.hiprint-footerLine:hover:before {
  content: "页脚线";
  left: calc(50% - 18px);
  position: relative;
  color: purple;
  background: #ffff;
  top: -14px;
  font-size: 12px;
}

.left {
  background: white;
  border-radius: 4px;
  border: 1px solid #d9d9d9;
  padding: 10px 0;
  box-shadow: 2px 2px 2px 0px rgb(128 0 128 / 20%);
  overflow: auto;
}
.center {
  margin: 0 10px;
  background: white;
  border-radius: 4px;
  border: 1px solid #d9d9d9;
  padding: 20px;
  box-shadow: 2px 2px 2px 0px rgb(128 0 128 / 20%);
  overflow: auto;
}
.right {
  background: white;
  border-radius: 4px;
  border: 1px solid #d9d9d9;
  padding: 10px 0;
  box-shadow: 2px 2px 2px 0px rgb(128 0 128 / 20%);
  overflow: auto;
}

.title {
  font-size: 16px;
  font-weight: 500;
  width: 100%;
  margin: 10px 0 0 24px;
}
.item {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: white;
  padding: 4px 10px;
  margin: 10px 8px 4px 8px;
  width: 38%;
  min-height: 60px;
  border-radius: 4px;
  box-shadow: 2px 2px 2px 2px rgba(171, 171, 171, 0.2);
}
.item .iconfont {
  font-size: 1.5rem;
}
.item span {
  font-size: 14px;
}


::-webkit-scrollbar {
  height: 4px;
  width: 4px;
}
::-webkit-scrollbar-corner {
  height: 4px;
  width: 4px;
}
::-webkit-scrollbar-thumb {
  background: purple;
  border-radius: 2px;
  background-image: -webkit-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.2) 25%,
    transparent 25%,
    transparent 50%,
    rgba(255, 255, 255, 0.2) 50%,
    rgba(255, 255, 255, 0.2) 75%,
    transparent 75%,
    transparent
  );
}
::-webkit-scrollbar-thumb:hover {
  background: purple;
}


.flex-row {
  display: flex;
}
.flex-col {
  display: flex;
  flex-direction: column;
}
.flex-wrap {
  flex-wrap: wrap;
}
.align-center {
  align-items: center;
}
.justify-center {
  justify-content: center;
}

.flex-1 {
  flex: 1;
}
.flex-2 {
  flex: 2;
}
.flex-3 {
  flex: 3;
}
.flex-4 {
  flex: 4;
}
.flex-5 {
  flex: 5;
}

.ml-10 {
  margin-left: 10px;
}
.mr-10 {
  margin-right: 10px;
}
.mt-10 {
  margin-top: 10px;
}
.mb-10 {
  margin-bottom: 10px;
}

button:hover {
  opacity: 1;
}
button i {
  font-size: 16px !important;
}
.circle,
.circle-4 {
  border-radius: 4px !important;
}
.circle-10 {
  border-radius: 10px !important;
}


.modal {
  padding: 0;
  margin: 0;
}
.modal .mask {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1000;
  height: 100%;
  background-color: #00000073;
}
.modal .wrap {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1000;
  overflow: auto;
  background-color: #00000073;
  outline: 0;
}
.modal .wrap .box {
  position: relative;
  margin: 10% auto;
  width: 40%;
  background: #fff;
  border-radius: 4px;
  z-index: 1001;
  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
  transition: all 0.3s ease;
}
.modal-box__header {
  padding: 10px 14px;
  border-bottom: 1px solid #e9e9e9;
}
.modal-box__footer {
  text-align: end;
}
.modal-box__footer button {
  min-width: 100px;
}
.modal-box__footer button:not(:last-child) {
  margin-right: 10px;
}

拖拽组件

为了提供更加丰富的设计体验,可以通过自定义拖拽组件来增强打印模板设计器的功能性。例如,这里 customProvider.js:可以添加 个人业务 (追溯业务) 相关的元素,如文本、条形码、二维码等。

import { hiprint } from "vue-plugin-hiprint";
export const iCustomProvider = function (options) {
    console.log(options.moduleList.value);
    var addElementTypes = function (context) {
        context.removePrintElementTypes("providerModule1");
        context.addPrintElementTypes("providerModule1", [
            new hiprint.PrintElementTypeGroup("追溯业务", [
                
                ...options.moduleList.value.map(item => ({
                    tid: item.defaultModule,
                    title: item.title,
                    data: item.title,
                    type: item.type,
                    options: {
                        field: item.field,
                        height: 14,
                        testData: "默认",
                        fontSize: 12,
                        fontWeight: '500',
                        textAlign: 'center',
                        textContentVerticalAlign: 'middle',
                        ...(item.textType ? { textType: item.textType } : {}),
                    },
                })),
                {
                    tid: "providerModule1.text",
                    title: "文本",
                    data: "文本",
                    type: "text",
                    options: {
                        field: "customText",
                        testData: "文本",
                        height: 14,
                        fontSize: 12,
                        fontWeight: "500",
                        textAlign: "left",
                        textContentVerticalAlign: "middle",
                    },
                },
                {
                    tid: "providerModule1.barcode",
                    title: "条形码",
                    data: "XS888888888",
                    type: "text",
                    options: {
                        field: "barcode",
                        testData: "XS888888888",
                        height: 32,
                        fontSize: 12,
                        lineHeight: 18,
                        textAlign: "left",
                        textType: "barcode",
                    },
                },
                {
                    tid: "providerModule1.qrcode",
                    title: "二维码",
                    data: "XS888888888",
                    type: "qrcode",
                    options: {
                        field: "qrcode",
                        testData: "XS888888888",
                        height: 32,
                        fontSize: 12,
                        lineHeight: 18,
                        textType: "qrcode",
                    },
                },
            ]),
        ]);
    };
    return {
        addElementTypes: addElementTypes,
    };
};

注意 :如上代码中的moduleList 是在主组件调用时,访问后台接口的数据传递过来的,根据自己的需求返回格式即可。需要注意的是,field是组件字段名,title是组件的标题或标签,type是组件的类型和对应的基础模块或组件实现defaultModule,了解更多 type 和对应的 defaultModule 可以点击这里,如下是个人接口返回的数据格式:

{
	"code": 200,
	"msg": "操作成功",
	"data": {
		"style": [{
				"field": "wlName",
				"title": "物料名称",
				"type": "text",
				"defaultModule": "defaultModule.wlName"
			},
			{
				"field": "specification",
				"title": "物料规格",
				"type": "text",
				"defaultModule": "defaultModule.specification"
			},
			{
				"field": "printQrCodeUrl",
				"textType": "qrcode",
				"title": "追溯码",
				"type": "qrcode",
				"defaultModule": "defaultModule.qrCode"
			},
			...
		]
	}
}

主组件调用

在主组件中,我们使用 el-drawer 来显示打印模板的设计界面,引入上述我们创建好的 customProvider.jsiconfont.css。此界面包括纸张尺寸的选择或自定义输入,以及打印、导出模板为 JSON 格式的功能按钮。

el-drawer v-model="drawer" title="创建模板" size="100%" @opened="buildDesigner">
  
  el-button-group>
    el-button 
      v-for="(value, type) in paperTypes" 
      :key="type" 
      :type="getButtonType(type)" 
      @click="setPaper(type, value)">
      {{type}}
    el-button>
    
    el-popover 
      v-model="paperPopVisible" 
      title="设置纸张宽高(mm)" 
      :width="240" 
      trigger="click">
      
      el-input 
        type="number" 
        v-model="paperWidth" 
        placeholder="宽(mm)">
      el-input>
      el-input 
        type="number" 
        v-model="paperHeight" 
        placeholder="高(mm)">
      el-input>
      el-button type="primary" @click="otherPaper">确定el-button>
    el-popover>
  el-button-group>

  
  button @click.stop="print">i class="iconfont sv-printer" />浏览器打印button>
  button @click.stop="exportJson">i class="iconfont sv-export" />保存模板button>
  el-popconfirm
         title="是否确认清空?"
         @confirm="clearPaper"
       >
         template #reference>
           el-button type="danger" style="margin-left: 10px;">
             清空
             template #icon>
               el-icon>Close />el-icon>
             template>
           el-button>
         template>
       el-popconfirm>
     div>
     div class="flex-row" style="height: 87vh">
       div class="flex-2  flex-wrap">
         
         div id="provider-container1" class="container rect-printElement-types">div>
       div>
       div class="flex-5 center">
         
         div id="hiprint-printTemplate">div>
       div>
       div class="flex-2 right">
         
         div id="PrintElementOptionSetting">div>
       div>
     div>
   div>
el-drawer>
script setup name="Template" lang="ts">
import { hiprint } from 'vue-plugin-hiprint';
import { iCustomProvider } from './customProvider'


const curPaper = ref({
      type: 'other',
      width: 80,
      height: 60
    });

const paperTypes = ref({
    'A3': {
      width: 420,
      height: 296.6
    },
    'A4': {
      width: 210,
      height: 296.6
    },
    'A5': {
      width: 210,
      height: 147.6
    },
    'B3': {
      width: 500,
      height: 352.6
    },
    'B4': {
      width: 250,
      height: 352.6
    },
    'B5': {
      width: 250,
      height: 175.6
    }
  });

const paperPopVisible = ref(false);
const paperWidth = ref('80');
const paperHeight = ref('60');

const getButtonType = (type) => {
  return curPaper.value.type === type ? 'primary' : 'default';
};
const drawer = ref(false);

const buildLeftElement = () => {
  
  

  $("#provider-container1").empty(); 
  
  hiprint.PrintElementTypeManager.build($("#provider-container1"), "providerModule1");
};

let hiprintTemplate;
const buildDesigner = () => {
  
  buildLeftElement();
  
  $("#hiprint-printTemplate").empty(); 
  hiprintTemplate = new hiprint.PrintTemplate({
    settingContainer: "#PrintElementOptionSetting", 
  });
  hiprintTemplate.design("#hiprint-printTemplate");
  if (data.form.templateJson) {
    mergeTemplate(data.form.templateJson);
  }
};


const print = () => {
  
  let printData = { name: "CcSimple" };
  
  let options = { leftOffset: -1, topOffset: -1 };
  
  let ext = {
    callback: () => {
      console.log("浏览器打印窗口已打开");
    },
    styleHandler: () => {
      
      return "";
    },
  };
  
  hiprintTemplate.print(printData, options, ext);
};

const exportJson = () => {
  const jsonIns = hiprintTemplate.getJson();
  data.form.templateJson = JSON.stringify(jsonIns);
  drawer.value = false;
  validateTemplateJson();
};

const exportJsonTid = () => {
  const jsonIns = hiprintTemplate.getJsonTid();
  console.log(jsonIns);
  alert("导出成功! 请查看控制台输出");
};

const mergeTemplate = (jsonIn: string) => {
  if (hiprintTemplate) {
    try {
      hiprintTemplate.update(JSON.parse(jsonIn))
    } catch (e) {
      ElMessage.error(`更新失败: ${e}`)
    }
  }
};

const setPaper = (type, value) => {
  try {
    
    curPaper.value = {type: type, width: value.width, height: value.height};
    
    hiprintTemplate.setPaper(value.width, value.height);
  } catch (error) {
    ElMessage.error(`操作失败: ${error}`);
  }
};
  

const validateTemplateJson = () => {
  if (templateFormRef.value) {
    templateFormRef.value.validateField('templateJson').catch(() => { });
  }
};

const getList = async () => {
  loading.value = true;
  const res = await listTemplate(queryParams.value);
  templateList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}

let moduleList = ref([]);
const templateModules = async () => {
  try {
    const res = await getTemplateModule();
    moduleList.value = res.data.style;
  } catch (error) {
    console.error('Failed to fetch template module:', error);
  }
};
    
onMounted(async () => {
  getList();
  await templateModules();
  hiprint.init({
    providers: [iCustomProvider({ moduleList: moduleList })]
  });
  buildDesigner();
});
script>
style>
@import '@/assets/css/iconfont.css';
style>

展示效果

 在这里插入图片描述

注意事项

  1. 纸张尺寸自定义:在设置纸张尺寸时,需确保输入的数值符合实际打印设备支持的范围。
  2. 打印样式兼容性:不同浏览器对打印样式的处理可能略有差异,特别是在处理页面布局时,需要对特定浏览器进行针对性优化。
  3. 自定义元素类型:当添加自定义元素类型时,需确保每个元素的属性值正确无误,特别是对于条形码和二维码这样的复杂元素。

原文地址: Vue3 结合 vue-plugin-hiprint 实现自定义打印模板【模板数据访问后端接口】设计与布局(手把手教你,直接复制粘贴使用)

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-11-07发表,共计12518字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)