Vue中使用froala富文本编辑器制作打印模板 + print.js 打印

Posted lee576

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue中使用froala富文本编辑器制作打印模板 + print.js 打印相关的知识,希望对你有一定的参考价值。

参考上一篇 知识开发的一个功能,制作一个打印模板的管理模块,如下(就是保存froala编辑后的html文本,其中包括Vue的Template,这样我们可以利用Vue的模板的优势来动态绑定一些数据源进行HTML的打印,基本上跟过去水晶报表做一个模板再绑定数据源的方法异曲同工)

 在 main.js 里引用 froala 组件

// Import and use Vue Froala lib.
import VueFroala from 'vue-froala-wysiwyg'

// 引入 Froala Editor js file.
require('froala-editor/js/froala_editor.pkgd.min')
// 引入中文语言包
require('froala-editor/js/languages/zh_cn')
// 引入 Froala Editor css files.
require('froala-editor/css/froala_editor.pkgd.min.css')
require('font-awesome/css/font-awesome.css')
require('froala-editor/css/froala_style.min.css')

// 批量导入 froala-editor 插件文件
function importAll (r) 
  r.keys().forEach(r)

importAll(require.context('froala-editor/js/plugins/', false, /\\.js$/))
importAll(require.context('froala-editor/css/plugins/', false, /\\.css$/))

Vue.use(VueFroala)

以上基本是引用了所有的插件,不然工具栏会有很多的按钮不出来

然后把 froala 文本编辑器封装成一个Vue组件,只暴露我们想要的功能

<template>
  <div class="editor-wrap">
    <froala
      :tag="'textarea'"
      :config="config"
      v-model="body.innerHTML"
    />
  </div>
</template>
<script>
import FroalaEditor from 'froala-editor'
export default 
  props: 
    // 显示的工具列表
    placeholder: 
      type: String
    ,
    height: 
      type: Number
    ,
    value: 
      type: String,
      default: null
    ,
    index: 
      type: Number,
      default: 1
    ,
    buttons: 
      type: Array,
      default: () => [
        'undo',
        'redo',
        'clearFormatting',
        'bold',
        'italic',
        'underline',
        'strikeThrough',
        'fontFamily',
        'fontSize',
        'textColor',
        'color',
        'backgroundColor',
        'inlineStyle',
        'paragraphFormat',
        'align',
        'formatOL',
        'formatUL',
        'outdent',
        'indent',
        'quote',
        // 'insertLink',
        'insertImage',
        // 'insertVideo',
        // 'embedly',
        // 'insertFile',
        'insertTable',
        // 'emoticons',
        'specialCharacters',
        'insertHR',
        'selectAll',
        'print',
        'spellChecker',
        'html',
        'help',
        'fullscreen',
        'clear',
        'save'
      ]
    
  ,
  data () 
    const that = this
    return 
      editor: null,
      uploadImage: 
        loading: false,
        previewVisible: false,
        previewImage: '',
        imgFile: ,
        isSize: false,
        isType: false
      ,
      fileList: [],
      body: 
        innerHTML: this.value,
        textLen: 0,
        leftoverLen: 99999999999,
        sumLen: 999999999,
        error_tip: '',
        error_show: false
      ,
      config: 
        codeBeautifierOptions: 
          end_with_newline: true,
          indent_inner_html: true,
          extra_liners: "['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'ul', 'ol', 'table', 'dl']",
          brace_style: 'expand',
          indent_char: ' ',
          indent_size: 4,
          wrap_line_length: 0
        ,
        htmlAllowedAttrs: ['.*'],
        pasteAllowedStyleProps: ['.*'],
        // fullPage: true,
        useClasses: false,
        htmlRemoveTags: [],
        iframe: true,
        toolbarButtons: this.buttons,
        // theme: "dark", //主题
        placeholderText: this.placeholder || '编辑文本',
        language: 'zh_cn', // 国际化
        imageUploadURL: '', // 上传url
        imageUploadParams:  token: '', i: '', ak: '', f: 9 ,
        fileUploadURL: '',
        fileUploadParams:  token: '', i: '', ak: '', f: 9 ,
        videoUploadURL: '',
        videoUploadParams:  token: '', i: '', ak: '', f: 9 ,
        quickInsertButtons: ['image', 'table', 'ul', 'ol', 'hr'], // 快速插入项
        // toolbarVisibleWithoutSelection: true,//是否开启 不选中模式
        // disableRightClick: true,//是否屏蔽右击
        colorsHEXInput: true, // 关闭16进制色值
        toolbarSticky: false, // 操作栏是否自动吸顶,
        // Colors list.
        colorsBackground: [
          '#15E67F',
          '#E3DE8C',
          '#D8A076',
          '#D83762',
          '#76B6D8',
          'REMOVE',
          '#1C7A90',
          '#249CB8',
          '#4ABED9',
          '#FBD75B',
          '#FBE571',
          '#FFFFFF'
        ],
        colorsStep: 6,
        colorsText: [
          '#15E67F',
          '#E3DE8C',
          '#D8A076',
          '#D83762',
          '#76B6D8',
          'REMOVE',
          '#1C7A90',
          '#249CB8',
          '#4ABED9',
          '#FBD75B',
          '#FBE571',
          '#FFFFFF'
        ],
        zIndex: 2501,
        height: this.height || '250',
        // autofocus: true,
        events: 
          initialized: function () 
            that.editor = this
            that.body.innerHTML = that.value
            that.editorChange()
          ,
          blur: () => 
            that.$emit('blur')
          ,
          contentChanged: () => 
            that.editorChange()
          ,
          'image.beforeUpload': function (images) 
            // 自定义上传图片
            that.beforeUpload(images[0])
            return false
          ,
          'file.beforeUpload': function () 
            // Image was uploaded to the server.
            return true
          ,
          'video.beforeUpload': function () 
            // Image was uploaded to the server.
            return true
          
        
      
    
  ,
  watch: 
    value: 
      handler: function (news, old) 
        this.body.innerHTML = news
      ,
      deep: true
    ,
    'body.innerHTML': function (newVal, old) 
      if (old !== newVal) 
        let val = this.getSimpleText(this.editor.html.get(true))
        this.editor.html.set(newVal)
      
    ,
    label: function (newVal, old) 
      if (old !== newVal) 
        this.editor.html.set(newVal)
      
    
  ,
  mounted () 
    // 自定义按钮***********************************************************************
    // 清理
    FroalaEditor.DefineIcon('clear', NAME: 'remove', SVG_KEY: 'remove')
    FroalaEditor.RegisterCommand('clear', 
      title: '清空',
      focus: false,
      undo: true,
      refreshAfterCallback: true,
      callback: function () 
        this.html.set(null)
        this.events.focus()
      
    )
    // 保存
    FroalaEditor.DefineIconTemplate('material_design', '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="floppy-disk" class="svg-inline--fa fa-floppy-disk" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M433.1 129.1l-83.9-83.9C342.3 38.32 327.1 32 316.1 32H64C28.65 32 0 60.65 0 96v320c0 35.35 28.65 64 64 64h320c35.35 0 64-28.65 64-64V163.9C448 152.9 441.7 137.7 433.1 129.1zM224 416c-35.34 0-64-28.66-64-64s28.66-64 64-64s64 28.66 64 64S259.3 416 224 416zM320 208C320 216.8 312.8 224 304 224h-224C71.16 224 64 216.8 64 208v-96C64 103.2 71.16 96 80 96h224C312.8 96 320 103.2 320 112V208z"></path></svg>')
    FroalaEditor.DefineIcon('saveIcon', NAME: 'save', template: 'material_design')
    FroalaEditor.RegisterCommand('save', 
      title: '保存',
      icon: 'saveIcon',
      callback: () => 
        this.$emit('save', this.body.innerHTML)
      
    )
    // **********************************************************************************
    setTimeout(() => 
      this.setIndex(this.index)
    , 200)
  ,
  methods: 
    saveHtml () 
      this.$emit('save', this.body.innerHTML)
    ,
    // 更改富文本层级
    setIndex (val) 
      this.$nextTick(() => 
        let dv = document.getElementsByClassName('fr-box')
        for (let i in dv) 
          if (!dv[i].style) 
            return
          
          dv[i].style.cssText = 'z-index:' + val
        
      )
    ,
    // 富文本中提取纯文本
    getSimpleText: html => 
      var re1 = new RegExp('<p data-f-id="pbf".+?</p>', 'g') // 匹配html标签的正则表达式,"g"是搜索匹配多个符合的内容
      var msg = html.replace(re1, '') // 执行替换成空字符
      return msg
    ,
    editorChange () 
      if (this.editor == null) return
      this.$emit('change', this.body.innerHTML)
    ,
    beforeUpload (file) 
      this.uploadImage.loading = true
      const formData = new FormData()
      formData.append('formFile', file)
      // this.$store
      //   .dispatch('UploadImg', formData)
      //   .then(res => 
      //     this.uploadImage.loading = false
      //     if (res.code === 200) 
      //       this.uploadImage.imgFile = JSON.parse(res.data)
      //       const url = this.uploadImage.imgFile.data
      //       // 插入图片
      //       this.editor.html.insert(
      //         '<img src=' + this.uploadImage.imgFile.data + '>', // HTML
      //         false // 在插入之前是否应清除HTML
      //       ) // 插入图片
      //      else 
      //       this.fileList = []
      //       this.$message.error(res.msg)
      //     
      //   )
      //   .catch(() => 
      //     this.uploadImage.loading = false
      //     this.$message.error('上传失败')
      //   )
    
  


</script>
<style lang="less" scoped>
.editor-wrap 
  width: 100%;
  height: calc(100% - 30px);
  // 去掉底部工具栏logo
  /deep/ .fr-second-toolbar 
    height: 42.19px;
    #fr-logo >span 
      display: none;
    
    #Layer_1 
      display: none;
    
  
  /deep/ .fr-box 
    height: 100% !important;
    .fr-wrapper 
      height: calc(100% - 93px) !important;
    
  
  /deep/ .fr-code 
    height: 100% !important;
  


.fr-wrapper > div[style*="z-index:9999;"],
.fr-wrapper > div[style*="z-index: 9999;"] 
  height: 0;
  overflow: hidden;
  position: absolute;
  top: -1000000px;
  opacity: 0;

.fr-view
  position: absolute;
  top: 0;

.fr-placeholder
  margin-top: 0;

.fr-box .second-toolbar 
  display: none;

.fr-box .second-toolbar #logo 
  width: 0 !important;
  height: 0 !important;
  overflow: hidden;

.fr-box .fr-toolbar 
  border-radius: 4px 4px 0 0;
  border-color: #dcdfe6;

.fr-box .second-toolbar 
  border-radius: 0 0 4px 4px;
  border-color: #dcdfe6;

.fr-box .fr-wrapper 
  border-color: #dcdfe6 !important;

</style>
<style>
.fa-floppy-disk 
  width: 18px;
  height: 18px;

</style>

具体的用法还要参详一下 froala 官方文档,这里不细表了

后端设计一张数据库表用来保存 html 文本

下面使用刚刚上面封装好的组件对html模板进行保存和更新

<template>
  <div class="page">
    <SplitPane direction="column" :min="10" :max="90" :triggerLength="10" :paneLengthPercent.sync="paneLengthPercent">
     <template v-slot:one>
      <div style="height:100%">
        <div style="height:calc(100% - 40px)">
          <el-table
            stripe
            element-loading-text="数据正在加载中"
            highlight-current-row
            size="small"
            ref="multipleTable"
            border
            :data="tableData"
            height="100%"
            :cell-style=" 'text-align': 'center' "
            :header-cell-style=" 'text-align': 'center' "
            v-on:select="handleSelectedRow" v-on:row-click="handleSelectedRow"
            @cell-click="cellClick">
            <el-table-column type="index" width="48"></el-table-column>
            <el-table-column label="模板名称" width="350px" prop='template_name'>
              <template slot-scope="scope">
                <el-input v-if="tableData.indexOf(scope.row)  === rowIndex && hearderTitle === '模板名称'"
                  v-model="scope.row.template_name" placeholder="请填写模板名称">
                </el-input>
                <span v-else> scope.row.template_name </span>
              </template>
            </el-table-column>
            <template  v-for="item in headerGroup">
              <el-table-column
                :key="item.label"
                v-if="item.show"
                :label="item.label"
                :prop="item.prop"
                :width="item.width"
              ></el-table-column>
            </template>
            <el-table-column label="操作" width="120" fixed="right">
              <template slot="header">
                <el-popover
                  placement="top-start"
                  width="30"
                  trigger="hover"
                  content="点击添加一行">
                  <el-button size="mini" class='el-buttons' type="primary" icon="el-icon-plus" circle slot="reference" @click="renderAddRow"></el-button>
                </el-popover>
              </template>
              <template slot-scope="scope">
                <el-popover width="160" :ref="`popover-$scope.$index`" placement="top">
                  <p>确定删除该项吗?</p>
                  <div style="text-align: right; margin: 0">
                    <el-button type="text" size="mini" @click="scope._self.$refs[`popover-$scope.$index`].doClose()">
                      取消
                    </el-button>
                    <el-button type="danger" size="mini" @click="deleteRow(scope.$index, scope.row, scope._self.$refs[`popover-$scope.$index`])">确定</el-button>
                  </div>
                  <el-button type="danger" class='el-buttons' style="background-color:#F56C6C" slot="reference" icon="el-icon-delete" size="mini" circle></el-button>
                </el-popover>
                <el-popover
                  placement="top-start"
                  width="80px"
                  trigger="hover"
                  content="点击保存这一行">
                  <el-button size="mini" class='el-buttons' style="background-color:#909399" type="info" icon="el-icon-edit" circle slot="reference" @click="addOrUpdateRow(scope.row)"></el-button>
                </el-popover>
              </template>
            </el-table-column>
          </el-table>
        </div>
        <div style="height:40px;padding:5px;">
          <el-pagination
            background
            layout="total, sizes, prev, pager, next"
            :total="GridPageRequest.PageTotal"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :page-sizes="[20, 30, 50, 100]"
            :page-size="GridPageRequest.PageSize"
            :current-page.sync="GridPageRequest.PageIndex">
          </el-pagination>
        </div>
      </div>
     </template>
     <template v-slot:two>
       <editor v-model="templateHtml" @save="saveTemplate" @change="changeTemplate"></editor>
     </template>
    </SplitPane>
  </div>
</template>
<script>
import SplitPane from '@/components/draglayhorizontallayouter/SplitPane.vue'
import Editor from '@/components/print/editor.vue'
export default 
  components:  SplitPane, Editor ,
  data () 
    return 
      templateName: null,
      rowIndex: 0,
      hearderTitle: '',
      paneLengthPercent: 40,
      // 表格
      tableData: [],
      // 表头
      headerGroup: [
         label: '主键ID', prop: 'id', show: false, width: '160px' ,
         label: '创建人', prop: 'create_by_name', show: true, width: '160' ,
         label: '创建人', prop: 'create_by', show: false, width: '160' ,
         label: '创建时间', prop: 'create_time', show: true, width: '160' ,
         label: '修改人', prop: 'update_by_name', show: true, width: '160' ,
         label: '修改人', prop: 'update_by', show: false, width: '160' ,
         label: '修改时间', prop: 'update_time', show: true, width: 'auto' 
      ],
      // 分页
      GridPageRequest: 
        PageSize: 20,
        PageIndex: 0,
        PageTotal: 0
      ,
      selectedRows: [],
      templateHtml: ''
    
  ,
  mounted () 
    this.$nextTick(async () => 
      this.onSearch()
    )
  ,
  methods: 
    // 选择行
    handleSelectedRow (selection, row) 
      this.templateHtml = ''
      this.selectedRows = []
      if (Array.isArray(selection)) 
        for (let i = 0; i < selection.length; i++) 
          this.selectedRows.push(selection[i])
        
       else  this.selectedRows.push(selection) 
      this.templateHtml = this.selectedRows[0].template_html
    ,
    cellClick (row, column, cell, event) 
      this.rowIndex = this.tableData.indexOf(row)
      this.hearderTitle = column.label

      this.templateHtml = ''
      this.selectedRows = []
      if (Array.isArray(row)) 
        for (let i = 0; i < row.length; i++) 
          this.selectedRows.push(row[i])
        
       else  this.selectedRows.push(row) 
      this.templateHtml = this.selectedRows[0].template_html
    ,
    renderAddRow () 
      this.tableData.push( template_name: null )
    ,
    addOrUpdateRow (row) 
      if (!row.id) 
        row.create_by = localStorage.getItem('user')
       else 
        row.update_by = localStorage.getItem('user')
      
      if (!row.template_name || row.template_name.length === 0) 
        this.$message.error('请先填写模板名称!')
        return
      
      this.axios.post('BasePrintTemplate/AddOrUpdate', row)
        .then((response) => 
          this.$message.success('操作成功!')
          this.getTableData()
        )
        .catch((error) => 
          this.$message(
            message: error.response.data.Message,
            type: 'warning'
          )
        )
    ,
    deleteRow (index, row, _self) 
      // 关闭气泡提示
      _self.doClose()
      if (!row.id) 
        this.tableData.splice(this.tableData.indexOf(row), 1)
        this.$message.success('操作成功!')
        return
      
      this.axios.post('BasePrintTemplate/Delete', row)
        .then((response) => 
          if (response.data.Data) 
            this.$message.success('操作成功!')
            this.tableData.splice(this.tableData.indexOf(row), 1)
           else 
            this.$message.error('操作失败!')
          
        )
        .catch((error) => 
          this.$message(
            message: error.response.data.Message,
            type: 'warning'
          )
        )
    ,
    getTableData () 
      this.GridPageRequest.Filter = []
      if (this.templateName != null) 
        this.GridPageRequest.Filter.push(FieldName: 'template_name', ConditionalType: '15', FieldValue: this.templateName)
      
      this.axios
        .post('BasePrintTemplate/QueryTake', this.GridPageRequest)
        .then((response) => 
          this.tableData = response.data.Data
          this.GridPageRequest.PageTotal = response.data.TotalRows
          this.radio = ''
        )
        .catch((error) => 
          this.$message(
            message: error.response.data.Message,
            type: 'warning'
          )
        )
    ,
    onSearch () 
      this.getTableData()
    ,
    handleSizeChange (val) 
      this.GridPageRequest.PageSize = val
      this.onSearch()
    ,
    handleCurrentChange (val) 
      this.GridPageRequest.PageIndex = val
      this.onSearch()
    ,
    changeTemplate (template) 
      this.templateHtml = template
    ,
    saveTemplate () 
      if (this.selectedRows.length === 0) 
        this.$message(
          message: '请选择一行数据再保存!',
          type: 'warning'
        )
        return
      
      this.selectedRows[0].template_html = this.templateHtml
      this.axios.post('BasePrintTemplate/AddOrUpdate', this.selectedRows[0])
        .then((response) => 
          this.$message.success('操作成功!')
          this.getTableData()
        )
        .catch((error) => 
          this.$message(
            message: error.response.data.Message,
            type: 'warning'
          )
        )
    
  


</script>
<style lang="less" scoped>
.page 
  height: 100%;
  /deep/ .el-table .el-table__fixed-right .el-table__fixed-header-wrapper .el-table__cell 
    text-align: right !important;
  

</style>
<style src='@/css/el-buttons.css' scoped>
</style>

 绑定Vue的数据源,

<template>
  <top-middle-bottom>
    <template v-slot:header>
      <el-row :gutter="20">
        <el-col :span="6">
          <el-button
            size="medium"
            icon="el-icon-circle-plus-outline"
            type="success"
            v-on:click="onOpenAdd()"
          >
            新 增
          </el-button>
          <el-button
            icon="el-icon-bottom"
            type="info"
            size="medium"
            v-on:click="onImport()"
          >
            批量导入
          </el-button>
          <el-button
            icon="el-icon-printer"
            size="medium"
            v-on:click="onShowTemplate()"
          >
            打印
          </el-button>
        </el-col>
        <el-col :span="18">
          <template>
            <el-radio v-model="search.status" :label="1000">全部</el-radio>
            <el-radio v-model="search.status" :label="1001">启用</el-radio>
            <el-radio v-model="search.status" :label="1002">禁用</el-radio>
          </template>
          <el-input
            size="medium"
            v-model="search.name"
            placeholder="请输入单位名称"
            style="width: 300px; margin-right: 5px"
            clearable
            v-on:clear="clearSearchValue"
          >
          </el-input>
          <el-button
            type="primary"
            v-on:click="onSearch"
            icon="el-icon-search"
            size="medium"
          >
            搜索
          </el-button>
        </el-col>
      </el-row>
    </template>
    <template v-slot:body>
      <el-table
        element-loading-text="数据正在加载中"
        highlight-current-row
        ref="multipleTable"
        border
        stripe
        :data="table.data"
        :cell-style=" 'text-align': 'center' "
        :header-cell-style=" 'text-align': 'center' "
        height="100%"
      >
        <el-table-column type="index" label="序号" width="50"></el-table-column>
        <el-table-column
          prop="supplier_name"
          show-overflow-tooltip
          label="单位名称"
          width="220px"
        >
        </el-table-column>
        <el-table-column
          prop="supplier_code"
          show-overflow-tooltip
          label="单位代码"
          width="220px"
        >
        </el-table-column>
        <el-table-column prop="supplier_type_text" label="类别" width="100px">
        </el-table-column>
        <el-table-column label="状态" width="65">
          <template slot-scope="scope">
            <el-switch
              v-model="scope.row.bill_status"
              active-color="#13ce66"
              inactive-color="#ff4949"
              :active-value="1001"
              :inactive-value="1002"
              @change="onUpdateStatus(scope.row)"
            >
            </el-switch>
          </template>
        </el-table-column>
        <el-table-column prop="create_time" label="创建时间" width="180px">
        </el-table-column>
        <el-table-column prop="context" label="联系人" width="100px">
        </el-table-column>
        <el-table-column prop="phone" label="电话" width="180px">
        </el-table-column>
        <el-table-column prop="email" label="邮箱" width="300px">
        </el-table-column>
        <el-table-column
          prop="address"
          show-overflow-tooltip
          label="地址"
          width="auto"
        >
        </el-table-column>
        <el-table-column fixed="right" width="180" label="操作" align="left">
          <template slot-scope="scope">
            <el-button
              type="text"
              size="small"
              icon="el-icon-edit"
              v-on:click="onOpenUpdate(scope.row)"
            >
              修改
            </el-button>
            <el-button
              type="text"
              size="small"
              icon="el-icon-delete"
              style="color: #f56c6c"
              v-on:click="onDelete(scope.row)"
            >
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </template>
    <template v-slot:footer>
      <el-pagination
        ref="pagination"
        v-on:size-change="onPageSizeChange"
        v-on:current-change="onPageCurrentChange"
        :current-page.sync="GridPageRequest.PageIndex"
        :page-sizes="[20, 50, 100, 200]"
        :page-size="GridPageRequest.PageSize"
        layout="sizes, prev, pager, next, total"
        :total="table.count"
      >
      </el-pagination>
    </template>
    <template v-slot:dialog>
      <el-dialog title="打印" append-to-body :close-on-press-escape="false"
        :visible.sync = "showPrint"
        :close-on-click-modal="false"
        width="50%">
        <div id="myPrint" v-html="templateHtml"></div>
      </el-dialog>
      <!-- 弹窗 新增 / 修改 -->
      <el-dialog
        :title="form.id ? '修改' : '新增'"
        :visible.sync="form.show"
        :destroy-on-close="true"
        :close-on-press-escape="false"
        :close-on-click-modal="false"
        width="50%"
      >
        <el-form ref="form" :model="form" label-width="100px" :rules="rules">
          <el-row>
            <el-col :span="12">
              <el-form-item label="单位名称" prop="supplier_name">
                <el-input
                  v-model="form.supplier_name"
                  style="width: 300px"
                ></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="单位代码" prop="supplier_code">
                <el-input
                  v-model="form.supplier_code"
                  style="width: 300px"
                ></el-input>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row>
            <el-form-item label="地址">
              <el-input v-model="form.address"></el-input>
            </el-form-item>
          </el-row>
          <el-row>
            <el-col :span="12">
              <el-form-item label="联系人">
                <el-input v-model="form.context"></el-input> </el-form-item
            ></el-col>
            <el-col :span="12">
              <el-form-item label="电话">
                <el-input v-model="form.phone"></el-input> </el-form-item
            ></el-col>
          </el-row>
          <el-row>
            <el-col :span="12">
              <el-form-item label="邮箱">
                <el-input v-model="form.email"></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="类别">
                <el-radio v-model="form.supplier_type" :label="1001">
                  供应商
                </el-radio>
                <el-radio v-model="form.supplier_type" :label="1002">
                  客户
                </el-radio>
                <el-radio v-model="form.supplier_type" :label="1003">
                  制造商
                </el-radio>
                <el-radio v-model="form.supplier_type" :label="1004">
                  维保外委
                </el-radio>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row>
            <el-form-item label="备注">
              <el-input
                type="textarea"
                :rows="3"
                placeholder="请输入备注内容"
                v-model="form.remarks"
              >
              </el-input>
            </el-form-item>
          </el-row>
        </el-form>
        <div slot="footer" class="dialog-footer">
          <el-button size="medium" v-on:click="dialogClose()">取 消</el-button>
          <el-button size="medium" type="primary" v-on:click="onFormSubmit()">
            确 定
          </el-button>
        </div>
      </el-dialog>
      <!-- 导出组件 -->
      <import-excel
        :visible="showExcelImport"
        :upload-path="uploadPath"
        :tempfile-url="tempfileUrl"
        tempfile-name="往来单位模板"
        v-on:close="closeExcelImport"
      ></import-excel>
    </template>
  </top-middle-bottom>
</template>
<script>
import axios from 'axios'
import ImportExcel from '@/components/importexcel.vue'
import TopMiddleBottom from '@/components/uilayout/topmiddlebottom.vue'
import Vue from 'vue'
import printJS from 'print-js'
export default 
  components:  ImportExcel, TopMiddleBottom ,
  data () 
    return 
      condition: [],
      templateHtml: null,
      tempfileUrl: process.env.ROOT + '/TemplateFiles/单位模板.xls',
      uploadPath: process.env.ROOT + '/api/Supplier/Import',
      showPrint: false,
      showExcelImport: false,
      // 搜索
      search: 
        name: '',
        status: 1000
      ,
      // 表格
      table: 
        data: [],
        count: 0
      ,
      // 分页
      GridPageRequest: 
        PageSize: 20,
        PageIndex: 0,
        PageTotal: 0
      ,
      rules: 
        supplier_name: [
           required: true, message: '请输入单位名称', trigger: 'blur' 
        ],
        supplier_code: [
           required: true, message: '请输入单位代码', trigger: 'blur' 
        ],
      form: 
        show: false,
        id: '',
        supplier_name: '',
        supplier_code: '',
        address: '',
        context: '',
        phone: '',
        supplier_type: 1001,
        bill_status: 1001,
        email: '',
        remarks: ''
      ,
      styleNode: ''
    
  ,
  mounted () 
    this.$nextTick(() => 
      this.getTableData(this.search)
    )
  ,
  methods: 
    // 搜索
    onSearch () 
      const search = this.search
      this.getTableData(search)
    ,
    // 请求表格数据
    getTableData (data) 
      axios
        .get('Supplier/Query', 
          params: 
            name: data.name,
            status: data.status,
            pageIndex: this.GridPageRequest.PageIndex,
            pageSize: this.GridPageRequest.PageSize
          
        )
        .then((response) => 
          if (response.data.Success === true) 
            this.table.data = response.data.Data
            this.table.count = response.data.TotalRows
           else 
            // 请求失败
            this.$message.error(response.data.Msg)
          
        )
        .catch(function (error) 
          this.$message.error(error)
        )
    ,
    // 修改分页大小
    onPageSizeChange (val) 
      this.GridPageRequest.PageSize = val
      this.getTableData(this.search)
    ,
    // 修改分页页数
    onPageCurrentChange (val) 
      this.GridPageRequest.PageIndex = val
      this.getTableData(this.search)
    ,
    // 打开新增
    onOpenAdd () 
      this.dialogClose()
      this.form.show = true
    ,
    // 打开修改
    onOpenUpdate (row) 
      // 赋值form数据
      this.form.id = row.id
      this.form.supplier_name = row.supplier_name
      this.form.supplier_code = row.supplier_code
      this.form.address = row.address
      this.form.context = row.context
      this.form.phone = row.phone
      this.form.supplier_type = row.supplier_type
      this.form.email = row.email
      this.form.remarks = row.remarks
      this.form.bill_status = row.bill_status
      this.form.show = true
    ,
    // 删除
    onDelete (row) 
      const supplierId = row.id
      this.$confirm('此操作将删除该条记录, 是否继续?', '提示', 
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      )
        .then(() => 
          axios
            .post(`Supplier/IsDelete?supplierId=$supplierId`)
            .then((response) => 
              if (response.data.Success === true) 
                this.$message(
                  type: 'success',
                  message: '删除成功!'
                )
                this.getTableData(this.search)
               else 
                this.$message(
                  type: 'warning',
                  message: `$response.data.Msg`
                )
              
            )
            .catch((error) => 
              this.$message.error(error)
            )
        )
        .catch(() => )
    ,
    // 清除搜索条件
    clearSearchValue () 
      this.search = 
        name: '',
        status: 1000
      
    ,
    // 启用/禁用
    onUpdateStatus (row) 
      // 保存点击之后v-modeld的值
      const flag = row.bill_status
      // 保持switch点击前的状态
      row.bill_status = row.bill_status === 1001 ? 1002 : 1001
      this.$confirm('是否确认此操作?', '提示', 
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      )
        .then(() => 
          const supplierId = row.id
          const status = flag
          axios
            .post(
              `Supplier/UpdateState?supplierId=$supplierId&status=$status`
            )
            .then((response) => 
              this.getTableData(this.search)
            )
            .catch((error) => 
              this.$message.error(error)
            )
        )
        .catch(() => 

        )
    ,
    // 表单提交确认
    onFormSubmit () 
      const form =  ...this.form 
      const userId = localStorage.getItem('user')
      axios
        .post('Supplier/AddOrEdit', 
          UserId: userId,
          Data: 
            id: form.id,
            supplier_name: form.supplier_name,
            supplier_code: form.supplier_code,
            address: form.address,
            context: form.context,
            phone: form.phone,
            supplier_type: form.supplier_type,
            bill_status: form.bill_status,
            email: form.email,
            remarks: form.remarks
          
        )
        .then((response) => 
          if (response.data.Success === true) 
            this.dialogClose()
            this.getTableData(this.search)
           else 
            this.$message.error(response.data.Msg)
          
        )
        .catch((error) => 
          console.log(error)
        )
    ,
    // 导入
    onImport () 
      this.showExcelImport = true
    ,
    // 对话框关闭
    dialogClose () 
      // 初始化form数据
      this.form = 
        show: false,
        id: '',
        supplier_name: '',
        supplier_code: '',
        address: '',
        context: '',
        phone: '',
        supplier_type: 1001,
        bill_status: 1001,
        email: '',
        remarks: ''
      
    ,
    closeExcelImport () 
      this.showExcelImport = false
    ,
    async onShowTemplate () 
      let findTemplate = null
      this.condition = []
      // 查询模板
      this.condition.push(FieldName: 'template_name', ConditionalType: '0', FieldValue: '供应商与客户')
      await this.axios.post('BasePrintTemplate/QueryCondition', this.condition)
        .then((response) => 
          if (Array.isArray(response.data.Data)) 
            findTemplate = response.data.Data[0].template_html
          
        )
        .catch((error) => 
          this.$message(
            message: error.response.Message,
            type: 'warning'
          )
        )
      // 绑定模板
      if (findTemplate) 
        const printData = this.table.data
        const Component = Vue.extend(
          template: `<div>$findTemplate</div>`,
          data () 
            return 
              printItems: printData
            
          ,
          methods: 
          
        )
        const component = new Component().$mount()
        this.templateHtml = component.$el.innerHTML
        this.showPrint = true
        // 等待渲染完成后再调用打印方法
        this.$nextTick(() => 
          printJS(
            printable: 'myPrint',
            type: 'html',
            scanStyles: false,
            targetStyles: ['*'],
            // @media print 用来控制打印的样式,边距,横向/纵向,纸张大小等
            style: '@media print @page margin: 10px;size: portrait; body font-size: 10px;margin: 0px;'
          )
          this.showPrint = false
        )
      
    
  

</script>

打印预览效果如下

以上是关于Vue中使用froala富文本编辑器制作打印模板 + print.js 打印的主要内容,如果未能解决你的问题,请参考以下文章

vue使用froala-editor富文本编辑器

vue-froala-wysiwyg 富文本编辑器

VUE使用富文本自定义打印模板

tinymce-vue5富文本的实现

tinymce-vue5富文本的实现

froala富文本编辑器与golangbeego,脱离ueditor苦海