vue使用Tinymce富文本模拟在线word文档

20,741 阅读5分钟

先上图,看最终效果:

image.png

项目需求:

产品期望实现【公文管理】其中发文拟文一块内容,就是用户撰写正文,再选择不同套红模板,最后拼接为一个对外发文格式,导出pdf文档。

综上所述,根据需求分析首先得有在线编辑功能富文本,并且该编辑器应该具有以下功能:

  1. 富文本编辑功能:用户可以使用富文本编辑器轻松地编辑和排版文本,最好编辑功能上得和word类似,包括插入图片、添加表格、超链接、字体样式等操作。
  2. 模板应用功能:用户可以选择不同的套红模板来应用于文档正文,以更好地适应不同的发文需求。
  3. 导出pdf功能:用户可以将文档导出为pdf格式,以便于打印、共享和存档。

技术选型:

  1. CKEditor: 一个功能强大的富文本编辑器,支持文本、图像、视频、表格等元素的编辑和排版。它可以集成各种插件,包括模板应用和pdf导出插件。
  2. TinyMCE: 一个受欢迎的富文本编辑器,可用于创建富文本编辑器和内容管理系统等。它提供了广泛的样式、格式和布局选项,也可以应用模板和导出pdf。
  3. Froala Editor: 一个现代化的文本编辑器,具有直观的界面和易于使用的编辑工具。它还提供了模板应用和pdf导出功能,支持HTML、Markdown和文本格式。
  4. Wangeditor: 具有丰富的编辑和排版选项,易于集成和定制。它还具有模板应用、插入图片、导出pdf等功能。

最终确定使用TinyMCE,它的优势主要体现在以下几个方面:

  1. 丰富的插件和扩展:TinyMCE 拥有大量的官方第三方插件和扩展,可以轻松地扩展和定制编辑器的功能,如图像上传、拓展工具栏、嵌入视频等。
  2. 可靠的稳定性: TinyMCE 是一款稳定成的富文本编辑器,它可以与各种浏览器和操作系统无缝兼容。并且,它的代码结构简单,易于维护和升级。
  3. 高度可定制:TinyMCE 可以根据不同的需求进行个性化的配置和定制,例如可以设置字体、大小、颜色、行间距等,支持多语言、多平台。
  4. 简单易用的接口和文档:TinyMCE 的官方文档非常详细,提供大量的示例和代码库,使开发者可以快速上手和集成。小巧简单的API使得任何人都可以轻松使用。

其他几款编辑器(如CKEditor、Froala Editor、Quill等)也具有自身的优势,但总体来说,TinyMCE 是一个成熟、稳定、灵活、易用的选择,适用于各种web应用的开发。

代码实现:

这里讲的实现方式是vue2使用Tinymce,vue3有些不一样,具体的可以网上搜搜看。

准备工作:

在开发之前得做前期学习准备工作:

tinymce中文文档:TinyMCE中文文档中文手册

tinymce官方文档:Documentation | Docs | TinyMCE

tinymce官方githubgitcode.net/mirrors/tin…

使用方式

下载依赖
npm i tinymce@5.10.3 @tinymce/tinymce-vue@3.2.8 -S

将依赖文件node_modules中的tinymce拷贝一份在public文件夹下面并创建tinymce,注意不需要拷贝所有文件,只需要拷贝下图标记的文件即可。

image.png

image.png

封装组件

在components创建对应组件imcoder-tinymce,相关路径: src\components\Tinymce\imcoder-tinymce.vue 文件创建完成后将以下内容填写到该文件内:

<!-- 富文本 -->
<template>
    <div class="tinymce-class">
        <editor api-key="去官网申请key" v-model="content" :init="init" :disabled="disabled"></editor>
    </div>
</template>
 
<script>
import Editor from "@tinymce/tinymce-vue";
import Tinymce from '@/components/Tinymce'
import { demo1,demo2 } from "../../views/overRed/templateCode";

export default {
    components: {
        Editor
    },
    props: {
        value: {
            type: String,
            default: ""
        },
        disabled: {
            type: Boolean,
            default: false
        },
        plugins: {
            type: [String, Array],
            default:
                "template print preview searchreplace autolink directionality visualblocks visualchars  image link media  code codesample table charmap hr nonbreaking insertdatetime advlist lists wordcount imagetools textpattern autosave autoresize"
        },
        toolbar: {
            type: [String, Array],
            default:
                "undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link codesample | alignleft aligncenter alignright alignjustify outdent indent formatpainter | \
    styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
    table image media charmap hr pagebreak insertdatetime | print preview template code| "
        },
    },
    data() {
        return {
            //初始化配置
            init: {},
            content: this.value
        };
    },
    created() {
        let that = this
        this.init = {
            //selector: '#mytextarea',
            //menubar: true, // 菜单栏显隐
            statusbar:false,
            language_url: "/zh_CN.js",
            language: "zh_CN",
            skin_url: "/tinymce/skins/ui/oxide",
            //skin_url: '../../static/tinymce/skins/ui/oxide', // vue-cli2.x
            // content_css: '../../../public/tinymce/skins/content/default/content.css',// vue-cli2.x
            // content_css:['']
            height: '1500%',
            // min_height: '620',
            // max_height: '620',
            toolbar_mode: "wrap",//是否折叠工具栏
            plugins: this.plugins,
            toolbar: this.toolbar,
            content_style: "p {margin: 5px 0;}",
            fontsize_formats: "12px 14px 16px 18px 24px 36px 48px 56px 72px 88px",
            font_formats:
                "微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;",
            branding: false,
            resize: false,//禁止拖拽改变大小
            // 对话框支持拖动
            draggable_modal: true,
            // 开启拖入功能,true:禁止拖入
            paste_block_drop: true,
            block_unsupported_drop: false,
            // 允许粘贴图片
            paste_data_images: true,
            images_file_types: '*.*',
            
            templates: [
                {"title": "模板1", "description": "模板1", "content":demo1},
                {"title": "模板2", "description": "模板2", "content": demo2}
                // {"title": "模板3", "description": "S模板22", "url": "development.html"}
            ],
            // // 是否开启自动保存,退出页面或刷新时提示
            // autosave_ask_before_unload: true,
            // // 自动保存时间间隔 秒
            // autosave_interval: '30s',
            // // 本地保存数据的有效期 分
            // autosave_retention: "5m",
            // 图片上传
            images_upload_handler: (blobInfo, success, failure) => {
                // const img = 'data:image/jpeg;base64,' + blobInfo.base64()
                // success(img)
 
                const formData = new FormData()
                formData.append('file', blobInfo.blob())
                reserveTableFoodDescribe(formData).then(res => {
                    if (res.code === '10000') {
                        const file = res.data
                        success(file.url)
                        return
                    }
                    failure('上传失败')
                }).catch(() => {
                    failure('上传出错')
                })
            },
            init_instance_callback: function (editor) {
                //5.0版本所有组件事件,均在此添加(keyup\keydown\Change……)
                editor.on('Change', function (e) {
                })
            },
        }
    },
    mounted() {
    },
    methods: {
 
    },
    watch: {
        value(newValue) {
            this.content = newValue;
        },
        content(newValue) {
            this.$emit("input", newValue);
        }
    }
};
</script>
<!-- <style scoped lang="scss"> -->
<style>
/*后面的这些css为了调整样式更像word编辑器而设置的最开始可以不需要 */
/*
.tinymce-class{
  margin:0px;
  padding:0px;
  width: calc(100% - 0px);
  height:100%;
  left: 0px;
  top: 56px;
  bottom:0px;
}

.tinymce-class :global(.tox-tinymce) {
    height: 100% !important;
}
.tinymce-class :global( hr){
    border-color: red !important; 
    border-style: solid;
    border-width: 6px 0 0 0 !important; 
}
 */
</style>

编辑器editor的api-key需要去官网申请key,因为采用从tinymce云上调用组件,如果没有的会提示错误,如下图 image.png

获取api-key可以参考这个大佬帖子blog.csdn.net/snans/artic…

调用组件
<template>
    <div style="width: 100%;height: 100%;">
        <Editor v-model="html" height="calc(100% - 20px)" class="wordCode"></Editor>
    </div>
</template>
<script>
import Editor from "@/components/Tinymce/imcoder-tinymce.vue";
export default {
    components: { Editor },
    data() {
        return {
            html: '<p>呀哈哈</p>'
        }
    },
    created() {

        //   tox.style = 
    },
    mounted() {
    },
    computed: {},
    methods: {
        onclicktemp() {
            console.log(this.html);
        },
    }
}
</script>

<style scoped>
/*后面的这些css为了调整样式更像word编辑器而设置的最开始可以不需要 */
/*
#top-container {
    border-bottom: 1px solid #e8e8e8;
    padding-left: 30px;
}

.wordCode :global(.tox .tox-edit-area) {
    justify-content: center;
    background-color: #f5f5f5;
    height: 100%;
    overflow-y: scroll;
}
.wordCode :global(.tox .tox-edit-area__iframe) {
    width: 900px !important;
    margin: 30px auto 150px auto;
    background-color: #fff;
    padding: 20px 50px 50px 50px;
    border: 1px solid #e8e8e8;
    box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
    overflow-y: scroll;

}
.wordCode :global(.tox .tox-edit-area__iframe html) {
    overflow-y: scroll;
} 
*/
</style>

引入组件后即可在网页上看到效果

image.png

调整样式

在这个时候可以看出Tinymce富文本默认样式,与想要的在线文档样式差距还是比较大的,刚好在这个时候看到了另外一个富文本wangEditor 5官方模拟腾讯文档的demo,并查看下对应github源码:

demo地址:www.wangeditor.com/demo/like-q…

源码地址:github.com/wangeditor-…

wangEditor 5官方模拟腾讯文档效果图:

image.png

参考wangEditor思路,通过修改css样式调整编辑器和菜单栏的界面显示

把style中样式删除掉注释符

最后效果:

image.png

文档套红

兼容说明

再次说明为何采用这个TinyMCE:支持从 Microsoft 文档中复制和粘贴内容,并可以其转换为 HTML 格式。这使得从 Word 打印到 TinyMCE 编辑器中的过程更为顺畅,而且可以确保复制的内容与源文档一致。

除此之外,TinyMCE 还支持导入和导出 Microsoft Word 文档。导入功能将 Microsoft Word 文档转换为 HTML 格式,并将其放入TinyMCE 编辑器,方便编辑和修改。而导出功能则可以将 TinyMCE 编辑器中的内容导出为 Word 文档,以便在微软 Office 等其他应用程序中使用。

由于相关的兼容性我们可以复制一段word文档到编辑器看下效果,重点是保留文档格式和字体、字号等。

image.png

制作红头

当我们点击这个查看源码可以看到刚才输入的内容是html片段,并且可以修改任何html内容,基于此,我们可以根据自己想要的参考正式文件添加文档开头 image.png

image.png

将红色分割线部分前面内容保存一份用个单独templateCode.js文件做红头记录。

image.png

通过封装进行调用

import { demo1,demo2 } from "../../views/overRed/templateCode";
制作模板

TinyMCE有一个template模板属性,对应在界面上是【查看源码】旁边的按钮 image.png

image.png 详细使用参看官方文档:tinymce.ax-z.cn/plugins/tem… 实现效果,点击保存则插入到文档中:

image.png

也可以通过打印保存pdf文档

image.png

后续:

非常快乐找产品沟通这个需求讲述这个功能预演情况,然而产品重新讲解想法,期望用户通过上传word正文然后拼接套红,导出一个完整文档,你不能让客户在word上写一份又复制在平台上,这个方法不行pass掉,重新下解决方案。

image.png