vue+ts在线文档编辑(类腾讯文档)多人在线编辑-自定义页眉和分页打印(三)


前言

随着在线办公场景越来越多,同时需要各式各样办公软件,在开发时就用得到在线文档来内容指定某人填写、审批、传阅文件,文件导出、打印成纸盖章等。所以需要功能较为强大、复杂的文档编辑器,前两篇文章我们已经教大家如何制作分页的文档编辑器,这篇文章我们把功能补全,教大家做好的文档怎么添加自定义页眉、打印。

老样子在教程开始前先上demo,有图有真相,在线demo让你看了是否是您想要的东西,不用浪费您半天时间,看了一推无用东西。

一、自定义页眉内容

vue+ts在线文档编辑(腾讯文档)多人在线编辑-实现假分页(二)  基础上加入页眉内页。

首先添加组件 pageHeader.vue 文件,代码如下:

<template>
    <div class="editor-headerbox" >
        <div class="left">
            <table class="headertable">
                <tr>
                <td>单位</td>
                <td>天文台检查站</td>
                <td>监管单位</td>
                <td>中国</td>
                </tr>
                <tr>
                <td>文件编号</td>
                <td>488 </td>
                <td>页码</td>
                <td>
                    <div style="display: flex; justify-content: center; align-items: center;">
                        第{{page}}页 / 共{{total}}页
                    </div>  
                </td>
                </tr>
            </table>
        </div>
        <div class="right">
            <img src="@/assets/logo.jpg" />
        </div>
    </div>
</template>
<script lang="ts">
import { defineComponent, ref, computed, unref } from 'vue';
  export default defineComponent({
    name: 'pageHeader',
    components: { 
    },
    props:{
       page:{
        type: Number,
        default:1
       },
       total:{
        type: Number,
        default:1
       },
    },
    emits: ['success'],
    setup() {
        return { 
        };
    },
  });
</script>
<style lang="scss" scoped>
 //页眉
 .editor-headerbox{
        display: flex;
        padding: 0px 40px;
        height: 100px;
        user-select: none;
        .left{
            flex:1;
            text-align: left;
            display: flex;
            align-items: center;
            .headertable{
                border-collapse: separate;
                border-spacing: 0;
                border-top: 1px solid #000000;
                border-left: 1px solid #000000;
                td{
                    padding: 5px 10px;
                    border: 1px solid #000000;
                    border-top: none;
                    border-left: none;
                }
            }
        }
        .right{
            text-align: right;
            img{
            height: 100%;
            cursor: pointer;
            pointer-events: auto;
           }
        }
    }
</style>

在编辑器直接引入:

import pageHeader from './component/pageHeader.vue'

二、分页文档打印

1.加入分页符

由于文档按word的A4纸尺寸分页,在打印的时候也是按照A4大小分页打印,在预览的页符加打印分页属性, 下图处的分隔符添加css属性(page-break-after:always;)

 完整代码 如下:

<div class="editor-page-list" >
  <template v-for="item in pageCount">
      <div class="editor-logic-page logc-page-box" >
          <!--页眉-->
          <div class="header-page page_wrapper" >
              <pageHeader :page="item" :total="pageCount"></pageHeader>
          </div>
          <!--页脚-->
          <div class="footer-page page_wrapper">
              <div class="pagetext">-第{{item}}页-</div>
          </div>
      </div>
      <div class="melo-page-gap" v-if="item!=pageCount" style="page-break-after:always; width: 100%; height: 23px; pointer-events: auto;"></div>
  </template>
</div>

2.打印方法

打印直接选用浏览器内置的打印方法(建议使用google和微软Microsoft Edge浏览器),代码如下:

const doPrint=()=>{
  window.print(); //打印刚才新建的网页
  return false;
}

3.打印模块完整代码 

vue 、ts代码如下:

<!--填写质量文件-->
<template>
    <!-- <div class=" form-widget-main"> -->
        <div class="form-widget-container" :style="{height:'100%'}">
            <el-button type="primary" class="savebtn" @click="printFile" circle >
                    印
                </el-button>
            <div class="editor-container-box"  :style="{height:pageRaelheight+'px'}">
                <div class="editor-content" id="editor-content" :style="{height:pageRaelheight+'px'}" >
                    <div class="editor-page-list" >
                        <template v-for="item in pageCount">
                            <div class="editor-logic-page logc-page-box" >
                                <!--页眉-->
                                <div class="header-page page_wrapper" >
                                    <pageHeader :page="item" :total="pageCount"></pageHeader>
                                </div>
                                <!--页脚-->
                                <div class="footer-page page_wrapper">
                                    <div class="pagetext">-第{{item}}页-</div>
                                </div>
                            </div>
                            <div class="melo-page-gap" v-if="item!=pageCount" style="page-break-after:always; width: 100%; height: 23px; pointer-events: auto;"></div>
                        </template>
                    </div>
                    <!--编辑器-->
                    <div ref="containerRef" style="padding-top:100px;padding-bottom: 40px;" ></div>
                    <div class="editor-pagebg-list" >
                       <template v-for="item in pageCount">
                            <div class="editor-bg-page" >
                            </div>
                        </template>
                    </div>
                </div>
            </div>
        </div>
    <!-- </div> -->
 </template>
 <script lang="ts"  >
 import { defineComponent, onMounted, reactive,ref,nextTick, toRefs } from 'vue';
 import { getDocValue, getPluginValue,getDemoData} from "../pageEditor/script/index";//数据存储本地
 import { cards, plugins, pluginConfig } from "../FileEditor/script/config";
 //数据
 import { inputItem} from '../FileEditor/script/data';
 //打印
 import { pageHeader } from '../pageEditor/component'
 //编辑器
 import Engine, {
   $,
   EngineInterface,
 } from "@aomao/engine";
 //方法
import { WaterMark } from '../pageEditor/script/commom';
 export default defineComponent({
   name: 'pagePrint',
   // 注册组件
   components: {
    pageHeader,
   },
   setup() {
     const winHeight=ref(document.documentElement.clientHeight-0)
      //获取组件数据
      const plugindata=getPluginValue()||''
      const pluginList=ref<inputItem[]>([])
     //  console.log('获取组件:', plugindata)
      if(plugindata&&plugindata!="undefined"){
         pluginList.value=JSON.parse(plugindata)
      }
     
      // 编辑器容器
      const containerRef = ref<HTMLElement | null>(null);
     // 编辑器引擎
     const engine = ref<EngineInterface | null>(null);
     // 默认设置为当前在加载中
     const loading = ref(true);
     onMounted(() => {
         document.title="质量文件编辑器-分页打印测试"
       // 容器加载后实例化编辑器引擎
       if (containerRef.value) {
                const engineInstance = new Engine(containerRef.value, {
                // 启用的插件
                plugins,
                // 启用的卡片
                cards,
                readonly:true,
                lazyRender:false,
                // 所有的卡片配置
                config: pluginConfig,
                });
                const value = getDocValue() || getDemoData();
                engineInstance.setValue(value, () => {
                  loading.value = false;
                });
             //监听渲染完成
            nextTick(()=>{
             setTimeout(() => {
                countPage()//计算的分页
                 pluginList.value.forEach((item:inputItem,index:number)=>{
                 const pluginDom=<HTMLInputElement>document.getElementById(item.domid)
                 if(pluginDom){
                     if(item.type=="input"){
                        if(pluginDom.parentElement&&pluginDom.parentElement.parentElement)
                        pluginDom.parentElement.parentElement.innerHTML=`<span style="width:${item.style.width}px;min-height: 26px;display: inline-block;border-bottom: 1px solid #d9d9d9;margin-bottom: -6px;"> ${item.value||'<span style="opacity:0;">-</span>'}</span>`
                     }else if(item.type=="textarea"){
                        if(pluginDom.parentElement)
                         pluginDom.parentElement.innerHTML=item.value
                     }else if(item.type=="checkbox"){
                        let htmlstr=""
                        if(item.value&&parseInt(item.value)==1){
                            htmlstr=`<span style="width:${item.style.width}px;height:${item.style.height}px;position: relative;display: inline-block;vertical-align: text-bottom;border: 1px solid #000000;border-radius: 3px;" >
                                <i style=" content: ''; width: ${item.style.height/2}px;height: ${item.style.height/3}px; border-left: 1px solid #000000; border-bottom: 1px solid #000000; position: absolute;  top: 3px;left: 3px;transform: rotate(-45deg);"></i>
                                </span>`
                        }else{
                            htmlstr=`<span style="width:${item.style.width}px;height:${item.style.height}px;position: relative;display: inline-block;vertical-align: text-bottom;border: 1px solid #000000;border-radius: 3px;" ></span>`
                        }
                        if(pluginDom.parentElement&&pluginDom.parentElement.parentElement)
                        pluginDom.parentElement.parentElement.innerHTML=htmlstr
                     }else if(item.type=="image"){
                        if(pluginDom.parentElement)
                         pluginDom.parentElement.innerHTML=`<img id="image_${item.uuid}" style="width:${item.style.width}px;margin-bottom: ${item.style.marginBottom}px;" src="https://tuwen.hulingyun.cn/common/uploadfile/getimage?url=resource/uploads/20230303/4c0fe297b181e4b509cedacf9918aa61.jpg"/>`
                     }else if(item.type=="textsync"){//动态数据
                        if(pluginDom.parentElement&&pluginDom.parentElement.parentElement)
                        pluginDom.parentElement.parentElement.innerText=item.value
                     }
                    //事件
                   if(item.type=="image"){//放大图片
                     $(`#${item.domid}`).on("click",(event)=>{
                        //  alert("放大图片")
                     });
                   } 
                 }
             });
             }, 500);
            })
         }

     });
     //打印预览
     const easyPrint = ref()
     const printFile=()=>{
        doPrint();
        // if(easyPrint.value)
        //  easyPrint.value.print()
     }
     const doPrint=()=>{
            // var head_str = "<html><head><title></title></head><body>"; //先生成头部
            // var foot_str = "</body></html>"; //生成尾部
            // var new_str = document.getElementsByClassName('form-widget-container')[0].innerHTML; //获取指定打印区域
            // document.body.innerHTML = head_str + new_str + foot_str; //构建新网页
            window.print(); //打印刚才新建的网页
            return false;
        }
     //分页
    //计算有多少页面
    const pageRaelheight=ref(1100)
    const pageCount=ref(1)//页面数
    const countPage=()=>{
        let editorheight=containerRef.value?.clientHeight||0
        editorheight=editorheight-40
        let pagenum=Math.ceil(editorheight/1100)
        if(pagenum>1){
           const midileheight=(pagenum-1)*25//中间分割线高之和
           const allheight=editorheight+midileheight
           //重新介绍高度
            pagenum=Math.ceil(allheight/(1100))
            pageRaelheight.value=pagenum*1100+midileheight
        }else{
          pageRaelheight.value=pagenum*1100
        }
        pageCount.value=pagenum
        nextTick(()=>{
          // 添加水印
          var watermark_txt = "<div>仅供GoFly专用 请注意保护隐私</div>" ;//水印内容
          WaterMark({ "watermarl_element": ".editor-bg-page", "watermark_txt": watermark_txt });
        })
    }
     return{
         winHeight,
         containerRef,engine,loading,
         printFile,
         easyPrint,
         //分页
         pageRaelheight,pageCount,
     }
   }
 });
 
 </script>
 <style lang="scss" scoped>
 @import "./style/index.scss";
 </style>

less样式 (样式布局很重要,这直接影响打印效果)如下:

.form-widget-container{
    background: transparent;
    overflow: auto;
    width: 100%;
    height: 100%;
    display: inline-flex;
    background: #f3f5f7;
    .savebtn{
        position:fixed;
        top: 60px;
        right: 120px;
        z-index: 99;
    }
}
//编辑器
.editor-container-box {
    width: 793.667px;
    margin: 0 auto;
    margin: 12px auto;
    .editor-content {
        overflow: hidden;
        position: relative;
        background: #fff;
        .editor-page-list{
            position: absolute;
            top: 0;
            z-index: 33;
            pointer-events: none;
            .editor-logic-page{
                width: 793.667px;
                height: 1100px;
                display: block;
                position: relative;
                .header-page{
                    width: 100%;
                    position: absolute;
                    top: 0px;
                    height: 100px;
                    background: #fff;
                }
                .footer-page{
                    width: 100%;
                    position: absolute;
                    bottom: 0px;
                    height: 40px;
                    background: #fff;
                    overflow: hidden;
                    .pagetext{
                        text-align: center;
                        padding-top: 10px;
                        height: 100%;
                    }
                }
            }
            .melo-page-gap {
                background: #f3f5f7;
                z-index:99999999;
                cursor: url("data:image/svg+xml,%3c%3fxml version='1.0' encoding='UTF-8'%3f%3e %3csvg width='24px' height='24px' viewBox='0 0 24 24' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3e %3ctitle%3eICON/bottombar/paging%3c/title%3e %3cg id='ICON/bottombar/paging' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3e %3cg id='Word-%e5%ba%95%e9%83%a8%e6%a0%8f-%e5%88%86%e9%a1%b5'%3e %3cg id='Group%e5%a4%8d%e5%88%b6-3' opacity='0' fill='black' fill-rule='nonzero'%3e %3crect id='Rectangle' opacity='0.0599999987' x='0' y='0' width='24' height='24'%3e%3c/rect%3e %3crect id='Rectangle' opacity='0.100000001' x='3' y='3' width='18' height='18'%3e%3c/rect%3e %3crect id='Rectangle' opacity='0.119999997' x='4' y='4' width='16' height='16'%3e%3c/rect%3e %3c/g%3e %3cpath d='M6.25%2c14 L6.25%2c16 L6.25%2c17.75 L17.75%2c17.75 L17.75%2c16 L17.75%2c14 L19%2c14 L19%2c18.5 C19%2c18.7761424 18.7761424%2c19 18.5%2c19 L5.5%2c19 C5.22385763%2c19 5%2c18.7761424 5%2c18.5 L5%2c14 L6.25%2c14 Z' id='%e5%bd%a2%e7%8a%b6%e7%bb%93%e5%90%88%e5%a4%8d%e5%88%b6' fill='%23464D5A' transform='translate(12.000000%2c 16.500000) scale(-1%2c 1) rotate(-180.000000) translate(-12.000000%2c -16.500000) '%3e%3c/path%3e %3crect id='%e7%9f%a9%e5%bd%a2' fill='%23464D5A' x='3' y='11.25' width='18' height='1.25'%3e%3c/rect%3e %3cpath d='M6.25%2c5 L6.25%2c7 L6.25%2c8.75 L17.75%2c8.75 L17.75%2c7 L17.75%2c5 L19%2c5 L19%2c9.5 C19%2c9.77614237 18.7761424%2c10 18.5%2c10 L5.5%2c10 C5.22385763%2c10 5%2c9.77614237 5%2c9.5 L5%2c5 L6.25%2c5 Z' id='%e5%bd%a2%e7%8a%b6%e7%bb%93%e5%90%88' fill='%23464D5A'%3e%3c/path%3e %3c/g%3e %3c/g%3e %3c/svg%3e") 15 15,auto;
            }
        }
        .am-engine-view{
            position: absolute;
            top: 0;
            width: 793.667px;
            background: transparent;
            padding: 0px 40px 0px 40px;
            z-index: 19;
        }
        .am-engine{
            position: absolute;
            top: 0;
            width: 793.667px;
            background: transparent;
            padding: 0px 40px 0px 40px;
            z-index: 19;
        }
         //下一层 加水印
         .editor-pagebg-list{
            position: absolute;
            top: 0;
            z-index: 2;
            .editor-bg-page{
                width: 794px;
                height: 1120px;
                position: relative;
                overflow: hidden;
                background: transparent;
                pointer-events: none;
            }
        }
    }
    :deep(.data-table){
        background-color: transparent;
        max-width: 100%;
     }
    :deep(.data-table-reader .data-table tr td){
        border: 1px solid #000000;
        padding: 4px 4px;
     }
     //修改线颜色
     :deep(.table-wrapper .table-main-bg .table-main-border-top){
        border-top: 1px solid #000000;
     }
     :deep(.table-wrapper .table-main-bg .table-main-border-right){
        border-right: 1px solid #000000;
     }
     :deep(.table-wrapper .table-main-bg .table-main-border-bottom){
        border-bottom: 1px solid #000000;
     }
     :deep(.table-wrapper .table-main-bg .table-main-border-left){
        border-left: 1px solid #000000;
     }
}
@media print {
    @page{
        size:auto;
        margin: 0;
        width: 793.667px;
        height: 1100px;
    }
  .melo-page-gap{
    background: #fff;
  }
 .savebtn{
    display: none;
  }
  .editor-container-box{
    margin: 0px;
  }
}

 4.实际效果如下:在线预览打印效果  建议用谷歌浏览器打开


总结


到此整个文档分页,自定义页眉页脚,文档打印的完整业务流程讲完了,希望看完让你也能直接写出腾讯文档一样的在线文档,需要源码在这里免费下载,如果您还有问题直接到GoFly全栈开发者社区找我讨论,作者也希望更多人分享出自己开发过程遇到难点项目,让更多人一起进步,助力新人成长。

  • 11
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
好的,以下是一个基于SpringBoot、Vue.js和MySQL的在线帮助文档编辑项目的搭建步骤: 1.创建SpringBoot项目 在你的IDE中选择“New Project”,然后选择“Spring Initializr”创建一个新的SpringBoot项目。在“Dependencies”选项中勾选“Spring Web”、“Spring Data JPA”、“MySQL Driver”等必要的依赖项。 2.创建数据库 在MySQL中创建一个名为“helpdoc”的数据库,并创建一个名为“docs”的表,表结构如下: ``` CREATE TABLE docs ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, content text NOT NULL, create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id) ); ``` 3.创建后端API 在SpringBoot项目中创建一个名为“DocController”的RESTful API,处理与文档相关的请求。例如,可以添加以下方法: - GET /docs:获取所有文档列表 - GET /docs/{id}:根据ID获取单个文档 - POST /docs:创建新的文档 - PUT /docs/{id}:更新现有文档 - DELETE /docs/{id}:删除现有文档 4.创建前端界面 使用Vue.js创建一个名为“HelpDoc”的前端项目,并使用axios库来调用后端API。可以创建以下页面: - HelpDocList.vue:显示所有文档列表,包括标题和创建时间 - HelpDocDetail.vue:显示选定文档的详细信息,包括标题和内容,并提供编辑和删除按钮 - HelpDocEdit.vue:允许用户创建或编辑文档,包括标题和内容 5.连接前后端 使用axios库来调用后端API,并在前端界面中显示结果。例如,可以在HelpDocList.vue中使用以下代码来获取文档列表: ``` mounted() { axios.get('/docs').then(response => { this.docs = response.data; }); } ``` 然后,在HelpDocDetail.vue中使用以下代码来获取单个文档的详细信息: ``` mounted() { axios.get('/docs/' + this.$route.params.id).then(response => { this.doc = response.data; }); } ``` 最后,在HelpDocEdit.vue中使用以下代码来保存新的或更新现有的文档: ``` saveDoc() { if (this.doc.id) { axios.put('/docs/' + this.doc.id, this.doc).then(response => { this.$router.push('/docs/' + this.doc.id); }); } else { axios.post('/docs', this.doc).then(response => { this.$router.push('/docs/' + response.data.id); }); } } ``` 以上就是一个基于SpringBoot、Vue.js和MySQL的在线帮助文档编辑项目的搭建步骤。希望能对你有所帮助!
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值