在vue3.0+中使用tinymce及实现多图上传,文件上传,公式编辑等功能

vue2版本移步至 https://www.cnblogs.com/huihuihero/p/13877589.html

vue3版本中配置tinymce,相较于vue2版本区别不大,主要注意需要变更版本号

【vue2.0+tinymce】我采用的是

"@tinymce/tinymce-vue": "^3.2.4",
"tinymce": "^5.6.1"


【vue3.0+tinymce】我采用的是
"@tinymce/tinymce-vue": "^4.0.5",
"tinymce": "^5.10.2"

配置步骤

【1】安装相关包依赖

yarn add tinymce@5.10.2  ||  npm install tinymce@5.10.2 -S

yarn add @tinymce/tinymce-vue@4.0.5  ||  npm install @tinymce/tinymce-vue@4.0.5 -S

【2】配置编辑器源文件:下载压缩包并解压到public文件夹下(本人已配置好,需要可前往 https://files.cnblogs.com/files/huihuihero/public.zip 自取)

【3】封装编辑器组件:在components下新建TEditor/TEditor.vue写入以下代码

<template>
    <div class="tinymce-box">
        <Editor v-model="contentValue" :init="init" :disabled="disabled" @onClick="onClick" />
    </div>
</template>

<script>
import { onMounted, reactive, toRefs, watch } from "vue";
// import { useStore } from "vuex";

import { uploadApi, editorUrl } from "@/services/apis/editor.js";
import axios from "axios";

//引入tinymce编辑器
import Editor from "@tinymce/tinymce-vue";

//引入方式引入node_modules里的tinymce相关文件文件
import tinymce from "tinymce/tinymce"; //tinymce默认hidden,不引入则不显示编辑器
import "tinymce/themes/silver"; //编辑器主题,不引入则报错
import "tinymce/icons/default"; //引入编辑器图标icon,不引入则不显示对应图标

// 引入编辑器插件
import "tinymce/plugins/advlist"; //高级列表
import "tinymce/plugins/anchor"; //锚点
import "tinymce/plugins/autolink"; //自动链接
import "tinymce/plugins/autoresize"; //编辑器高度自适应,注:plugins里引入此插件时,Init里设置的height将失效
// import 'tinymce/plugins/autosave'  //自动存稿
import "tinymce/plugins/charmap"; //特殊字符
import "tinymce/plugins/code"; //编辑源码
import "tinymce/plugins/codesample"; //代码示例
import "tinymce/plugins/directionality"; //文字方向
import "tinymce/plugins/emoticons"; //表情
import "tinymce/plugins/fullpage"; //文档属性
import "tinymce/plugins/fullscreen"; //全屏
import "tinymce/plugins/help"; //帮助
import "tinymce/plugins/hr"; //水平分割线
import "tinymce/plugins/image"; //插入编辑图片
import "tinymce/plugins/importcss"; //引入css
import "tinymce/plugins/insertdatetime"; //插入日期时间
import "tinymce/plugins/link"; //超链接
import "tinymce/plugins/lists"; //列表插件
import "tinymce/plugins/media"; //插入编辑媒体
import "tinymce/plugins/nonbreaking"; //插入不间断空格
import "tinymce/plugins/pagebreak"; //插入分页符
import "tinymce/plugins/contextmenu";
import "tinymce/plugins/paste"; //粘贴插件
import "tinymce/plugins/preview"; //预览
import "tinymce/plugins/print"; //打印
import "tinymce/plugins/quickbars"; //快速工具栏
import "tinymce/plugins/save"; //保存
import "tinymce/plugins/searchreplace"; //查找替换
// import 'tinymce/plugins/spellchecker'  //拼写检查,暂未加入汉化,不建议使用
import "tinymce/plugins/tabfocus"; //切入切出,按tab键切出编辑器,切入页面其他输入框中
import "tinymce/plugins/table"; //表格
import "tinymce/plugins/template"; //内容模板
import "tinymce/plugins/textcolor"; //文字颜色
import "tinymce/plugins/textpattern"; //快速排版
import "tinymce/plugins/toc"; //目录生成器
import "tinymce/plugins/visualblocks"; //显示元素范围
import "tinymce/plugins/visualchars"; //显示不可见字符
import "tinymce/plugins/wordcount"; //字数统计
import "../../../../public/tinymce/indent2em"; //首行缩进
import "../../../../public/tinymce/axupimgs"; //多图上传
import "../../../../public/tinymce/formulas"; //公式编辑
import "../../../../public/tinymce/formatpainter"; //格式刷

export default {
    name: "TEditor",
    components: {
        Editor,
    },
    props: {
        value: {
            type: String,
            default: "",
        },
        disabled: {
            type: Boolean,
            default: false,
        },
        plugins: {
            type: [String, Array],
            default: "print preview contextmenu searchreplace autolink directionality emoticons formulas indent2em visualblocks visualchars fullscreen image axupimgs link media template code codesample table charmap hr pagebreak nonbreaking anchor insertdatetime advlist lists wordcount textpattern formatpainter",
        },

        toolbar: {
            type: [String, Array],
            default: "fullscreen undo redo restoredraft | forecolor backcolor bold italic underline strikethrough link anchor formatpainter | alignleft aligncenter alignright alignjustify | outdent indent indent2em | \
                styleselect formatselect fontselect fontsizeselect lineheight | table image axupimgs media | bullist numlist | blockquote subscript superscript removeformat charmap emoticons | \
                 hr pagebreak insertdatetime | selectall cut copy paste pastetext | print preview searchreplace visualblocks code formulas",
        },
    },
    setup(props, { emit }) {
        const state = reactive({
            init: {
                language_url: `${editorUrl}tinymce/langs/zh_CN.js`, //引入语言包文件
                language: "zh_CN", //语言类型

                skin_url: `${editorUrl}tinymce/skins/ui/oxide`, //皮肤:浅色
                // skin_url: `${editorUrl}tinymce/skins/ui/oxide-dark`,//皮肤:暗色

                emoticons_database_url: `${editorUrl}tinymce/emoticons/js/emojis.js`, //更改表情插件文件路径引入
                contextmenu: false,
                plugins: props.plugins, //插件配置
                toolbar: props.toolbar, //工具栏配置,设为false则隐藏
                // menubar: 'file edit',  //菜单栏配置,设为false则隐藏,不配置则默认显示全部菜单,也可自定义配置--查看 http://tinymce.ax-z.cn/configure/editor-appearance.php --搜索“自定义菜单”

                fontsize_formats: "12px 14px 16px 18px 20px 22px 24px 26px 28px 30px 32px 36px 40px 44px 48px 56px 64px 72px", //字体大小
                font_formats: "宋体=SimSun;黑体=SimHei;楷体=KaiTi;仿宋=FangSong;Times New Roman=Times New Roman;微软雅黑=Microsoft Yahei;苹方=PingFang SC;隶书=LiSu;",
                lineheight_formats: "0.5 0.8 1 1.2 1.5 1.75 2 2.5 3 4 5", //行高配置,也可配置成"12px 14px 16px 18px"这种形式

                height: 400, //注:引入autoresize插件时,此属性失效
                placeholder: "在这里输入文字",
                branding: false, //tiny技术支持信息是否显示
                resize: false, //编辑器宽高是否可变,false-否,true-高可变,'both'-宽高均可,注意引号
                // statusbar: false,  //最下方的元素路径和字数统计那一栏是否显示
                elementpath: false, //元素路径是否显示

                // content_style: "img {max-width:100%!important;}",  //直接自定义可编辑区域的css样式
                content_css: `${editorUrl}tinymce/tinycontent.css`, //以css文件方式自定义可编辑区域的css样式,css文件需自己创建并引入

                // images_upload_url: '/apib/api-upload/uploadimg',  //后端处理程序的url
                // images_upload_base_path: '/demo',  //相对基本路径--关于图片上传建议查看--http://tinymce.ax-z.cn/general/upload-images.php
                paste_data_images: true, //图片是否可粘贴
                images_upload_handler: (blobInfo, success, failure) => {
                    if (blobInfo.blob().size / 1024 / 1024 > 2) {
                        failure("上传失败,图片大小请控制在 2M 以内");
                    } else {
                        let params = new FormData();
                        params.append("file", blobInfo.blob());
                        let config = {
                            headers: {
                                "Content-Type": "multipart/form-data",
                            },
                        };
                        axios.post(`${uploadApi}/uploadimg`, params, config).then((res) => {
                            if (res.data.code == 200) {
                                success(res.data.msg); //上传成功,在成功函数里填入图片路径
                            } else {
                                if (res.data.msg) {
                                    failure(res.data.msg);
                                } else {
                                    failure("上传失败");
                                }
                            }
                        }).catch(() => {
                            failure("上传出错,服务器开小差了呢");
                        });
                    }
                },
                file_picker_types: "file image media", //分别对应三个类型文件的上传:link插件,image和axupimgs插件,media插件。想屏蔽某个插件的上传就去掉对应的参数
                file_picker_callback: (callback, value, meta) => {
                    let filetype = ".pdf, .txt, .zip, .rar, .7z, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .mp3, .mp4, .jpg, .png, .gif";
                    let inputElem = document.createElement("input");
                    inputElem.setAttribute("type", "file");
                    inputElem.setAttribute("accept", filetype);
                    inputElem.click();
                    inputElem.onchange = () => {
                        let upurl = "";
                        let isVideo = false;
                        let file = inputElem.files[0];
                        if (file.type.slice(0, 5) == "video") {
                            upurl = `${uploadApi}/uploadTxVideo`;
                            isVideo = true;
                            alert("暂不支持视频上传,您可上传音频");
                        } else {
                            upurl = `${uploadApi}/upload`;
                            isVideo = false;
                        }
                        if (!isVideo) {
                            //暂时去除视频上传功能
                            if (file.type.slice(0, 5) == "image" && file.size / 1024 / 1024 > 2) {
                                alert("上传失败,图片大小请控制在 2M 以内");
                            } else if (file.size / 1024 / 1024 > 10) {
                                alert("上传失败,文件大小请控制在 10M 以内");
                            } else {
                                let params = new FormData();
                                params.append("file", file);
                                let config = {
                                    headers: {
                                        "Content-Type": "multipart/form-data",
                                    },
                                };
                                axios.post(upurl, params, config).then((res) => {
                                    if (res.data.code == 200) {
                                        if (isVideo) {
                                            callback(res.data.data); //上传成功,在回调函数里填入文件路径
                                        } else {
                                            callback(res.data.msg);
                                        }
                                    } else {
                                        if (res.data.msg) {
                                            alert(res.data.msg);
                                        } else {
                                            alert("上传失败");
                                        }
                                    }
                                })
                                .catch(() => {
                                    alert("上传出错,服务器开小差了呢");
                                });
                            }
                        }
                    };
                },
            },
            contentValue: props.value, //富文本内容--双向绑定的值
        });

        // const $store = useStore()
        // //初始化编辑器前选择编辑器主题色
        // function editorThemeInit() {
        //     let mode = $store.state.setting.theme.mode;
        //     if (mode == "night") {
        //         state.init.skin_url = `${editorUrl}tinymce/skins/ui/oxide-dark`;
        //     } else {
        //         state.init.skin_url = `${editorUrl}tinymce/skins/ui/oxide`;
        //     }
        // }
        // editorThemeInit();

        // 添加相关的事件,可用的事件参照文档=> https://github.com/tinymce/tinymce-vue => All available events
        function onClick(e) {
            emit("onClick", e, tinymce);
        }
        //清空内容
        function clear() {
            state.contentValue = "";
        }

        // 监听调用此富文本的父组件的数据改变
        watch(() => props.value,(newValue, oldValue) => {
            if (newValue != state.contentValue) {
                state.contentValue = newValue;
            }
        });
        // 监听富文本内容改变
        watch(() => state.contentValue,(newValue, oldValue) => {
            emit("update:value", newValue);
        });

        onMounted(() => {
            tinymce.init({});
        });

        return {
            onClick,
            ...toRefs(state),
        };
    },
};
</script>

<style lang="less">
.tinymce-box {
}
</style>

【3】关于组件中用到的 uploadApi, editorUrl。

根据自己的项目文件结构创建editor.js文件
我的目录是在src/services/apis/editor.js

代码如下
export const editorUrl = process.env.VUE_APP_PUBLIC_PATH; //编辑器接口地址
export const uploadApi = `${process.env.VUE_APP_BASE_API}/api-upload` //上传接口

【4】使用

<TEditor ref="editor" v-model:value="richTextValue" />
posted @ 2022-12-09 17:26  huihuihero  阅读(1985)  评论(0编辑  收藏  举报