vue使用tinymce实现富文本编辑器

  1. 安装两个插件tinymce和  @tinymce/tinymce-vue

 npm install [email protected] @tinymce/[email protected] -S 

注意:

  • @tinymce/tinymce-vue 是对tinymce进行vue的包装,主要作用当作vue组件使用
  • -S保存到package.json文件 

2. 把node_modules/tinymce下的目录(iconspluginsskinsthemes)拷贝到public/tinymce里面 

 

 3. 汉化tinymce中的语言,下载 langs/zh_CN.js,[语言包下载地址](https://www.tiny.cloud/get-tiny/language-packages/)

 选择

 4.解压,将langs文件也拷贝到public/tinymce

 5.引入tinymce编辑器

import TinymceRich from "@tinymce/tinymce-vue";

6.引入node_modules里的tinymce相关文件文件

import tinymce from "tinymce/tinymce"; 

7.使用TinymceRich组件 

8.引入编辑器插件

import 'tinymce/themes/silver'
import 'tinymce/plugins/advlist' //高级列表
import 'tinymce/plugins/anchor' //锚点
import 'tinymce/plugins/autolink' //自动链接
import 'tinymce/plugins/autosave'//编辑器高度自适应,注:plugins里引入此插件时,Init里设置的height将失效
import 'tinymce/plugins/code'//编辑源码
import 'tinymce/plugins/codesample'//代码示例
import 'tinymce/plugins/directionality'//文字方向
import 'tinymce/plugins/fullscreen'全屏插件
import 'tinymce/plugins/hr'//水平分割线
import 'tinymce/plugins/insertdatetime'//插入日期时间
import 'tinymce/plugins/link'//链接插件
import 'tinymce/plugins/lists'列表插件
import 'tinymce/plugins/media' //媒体插件
import 'tinymce/plugins/nonbreaking'//插入不间断空格
import 'tinymce/plugins/noneditable'
import 'tinymce/plugins/pagebreak'//插入分页符
import 'tinymce/plugins/paste' //预览
import 'tinymce/plugins/print'//打印
import 'tinymce/plugins/save' //保存
import 'tinymce/plugins/searchreplace'//查找替换
// import 'tinymce/plugins/spellchecker' //拼写检查,未加入汉化,不建议使用
import 'tinymce/plugins/tabfocus'//切入切出,按tab键切出编辑器,切入页面其他输入框中
import 'tinymce/plugins/template'//内容模板
import 'tinymce/plugins/textpattern'//快速排版
import 'tinymce/plugins/visualblocks' //显示元素范围
import 'tinymce/plugins/visualchars' //显示不可见字符
import 'tinymce/plugins/wordcount'//字数统计
import 'tinymce/plugins/table'//表格插件
import "tinymce/plugins/textcolor"; //文字颜色
import "tinymce/plugins/toc"; //目录生成器

9.为RichText组件自定义属性(disabled 禁用状态,plugins 可用插件,toolbar 工具栏, modelValue 用于自定义RichText组件的v-model指令,名字是固定的)

  <div>
        <TinymceRich v-model="contentValue" :init="initOption" :disabled="disabled" />
    </div>

 10.初始化Editor组件,在data()选项中定义,并实现图片上传

let initOption = ref({
    base_url: '/tinymce', // [必要参数] 指定public的目录
    language_url: "/tinymce/langs/zh-Hans.js",//语言类型的路径
    language: 'zh-Hans', // 语言类型(中文),默认会到tinymce/langs/zh_CN.js文件
    skin_url: '/tinymce/skins/ui/oxide', // 皮肤,浅色
    // skin_url: '/tinymce/skins/ui/oxide-dark', // 皮肤,深色
    plugins: props.plugins, // 插件配置
    toolbar: props.toolbar, // 工具栏配置,设置false则为隐藏
    toolbar_mode: 'sliding', // 工具栏移除模式,floating / sliding / scrolling / wrap
    // menubar: 'file edit', // 菜单栏配置,设置为false则隐藏,不配置默认显示全部菜单
    fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px', // 字体大小
    font_formats: '微软雅黑=Microsoft YaHei, Helvetica Neue, PingFang SC, sans-serif; 苹果苹方=PingFang SC, Microsoft YaHei, sans-serif; 宋体=simsun,serif; 仿宋体=FangSong,serif; Impact=impact,chicago', // 字体
    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%; }', // 自定义可编辑区的css样式
    setup: function (editor: any) {
        editor.ui.registry.addIcon(
            "image",
            `<svg t="1664002320321" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4420" width="24" height="24"><path d="M125.9 185h772.2v653.9H125.9z" fill="#1F53CC" p-id="4421"></path><path d="M164.7 217.2h694.6v516.7H164.7z" fill="#FECD44" p-id="4422"></path><path d="M458.9 734l-8.6-43.8-101.5-102.8-135 146.6z" fill="#FC355D" p-id="4423"></path><path d="M306.9 348.7m-66.7 0a66.7 66.7 0 1 0 133.4 0 66.7 66.7 0 1 0-133.4 0Z" fill="#FFFFFF" p-id="4424"></path><path d="M384.6 734h474.7V608.8L687.8 400.1z" fill="#FC355D" p-id="4425"></path><path d="M422.5 662l-37.9 72 52.1-57.5z" fill="#BF2847" p-id="4426"></path><path d="M302.5 778.9h418.9v16.7H302.5z" fill="#00F0D4" p-id="4427"></path></svg>`
        );
        editor.ui.registry.addIcon('preview', '<svg t="1664002320321" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4420" width="24" height="24"><path d="M125.9 185h772.2v653.9H125.9z" fill="#1F53CC" p-id="4421"></path><path d="M164.7 217.2h694.6v516.7H164.7z" fill="#FECD44" p-id="4422"></path><path d="M458.9 734l-8.6-43.8-101.5-102.8-135 146.6z" fill="#FC355D" p-id="4423"></path><path d="M306.9 348.7m-66.7 0a66.7 66.7 0 1 0 133.4 0 66.7 66.7 0 1 0-133.4 0Z" fill="#FFFFFF" p-id="4424"></path><path d="M384.6 734h474.7V608.8L687.8 400.1z" fill="#FC355D" p-id="4425"></path><path d="M422.5 662l-37.9 72 52.1-57.5z" fill="#BF2847" p-id="4426"></path><path d="M302.5 778.9h418.9v16.7H302.5z" fill="#00F0D4" p-id="4427"></path></svg>');
    },
    paste_data_images: true, // 图片是否可以粘贴

    images_upload_handler: (blobInfo: any, success: any, failure: any) => {//自定义图片上传 
        // let img = 'data:image/jpeg;base64,' + blobInfo.base64();
        let formData = new FormData();//创建一个表单对象
        formData.append('file', blobInfo.blob());//通过formData对象封装图片二进制数据
        upload(formData).then((res: any) => {//上传的图片路径
            success(res.data.url);//返回图片的回调地址
        }).catch((err: any) => {
            failure(err.response.data.message);
        })
    }
});

11.自定义RichText组件v-mode,定义计算属性contentValue,使用get和set巧妙的完成RichText和Editor两个组件之间的数据联动

let props = defineProps({//自定义属性用于设置默认值
    modelValue: {// 用于自定义v-model的value 父组件向子组件传递的值
        type: String,
        default: ''
    },
    disabled: {
        type: Boolean,
        default: false
    },
    plugins: {  //插件
        type: [String, Array],
        default: 'print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap nonbreaking anchor insertdatetime advlist lists wordcount autosave'
    },
    toolbar: { //工具栏
        type: [String, Array],
        default: 'fullscreen undo redo restoredraft | table image media charmap hrpagebreak insertdatetime print preview | cut copy paste pastetext | forcolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter  alignright alignjustify outdent indent | styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | code selectal searchreplace visualblocks indent2em lineheight formatpainter axupimgs'
    }
});


// 计算属性
let contentValue = computed({
    // 富文本框内容
    get() { // 取值 父组件向子组件传值
        return props.modelValue;
    },
    set(value: string) { // 赋值 子组件向父组件传值 ,吧value传递过去,
        emit('update:modelValue', value)
    }
})

 整体代码

 <template>
    <div>
        <TinymceRich v-model="contentValue" :init="initOption" :disabled="disabled" />
    </div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import tinymce from "tinymce/tinymce";
import TinymceRich from "@tinymce/tinymce-vue";
import { upload } from "@/utils/http";

// 引入编辑器插件
import 'tinymce/themes/silver'
import 'tinymce/plugins/advlist' //高级列表
import 'tinymce/plugins/anchor' //锚点
import 'tinymce/plugins/autolink' //自动链接
import 'tinymce/plugins/autosave'//编辑器高度自适应,注:plugins里引入此插件时,Init里设置的height将失效
import 'tinymce/plugins/code'//编辑源码
import 'tinymce/plugins/codesample'//代码示例
import 'tinymce/plugins/directionality'//文字方向
import 'tinymce/plugins/fullscreen'全屏插件
import 'tinymce/plugins/hr'//水平分割线
import 'tinymce/plugins/insertdatetime'//插入日期时间
import 'tinymce/plugins/link'//链接插件
import 'tinymce/plugins/lists'列表插件
import 'tinymce/plugins/media' //媒体插件
import 'tinymce/plugins/nonbreaking'//插入不间断空格
import 'tinymce/plugins/noneditable'
import 'tinymce/plugins/pagebreak'//插入分页符
import 'tinymce/plugins/paste' //预览
import 'tinymce/plugins/print'//打印
import 'tinymce/plugins/save' //保存
import 'tinymce/plugins/searchreplace'//查找替换
// import 'tinymce/plugins/spellchecker' //拼写检查,未加入汉化,不建议使用
import 'tinymce/plugins/tabfocus'//切入切出,按tab键切出编辑器,切入页面其他输入框中
import 'tinymce/plugins/template'//内容模板
import 'tinymce/plugins/textpattern'//快速排版
import 'tinymce/plugins/visualblocks' //显示元素范围
import 'tinymce/plugins/visualchars' //显示不可见字符
import 'tinymce/plugins/wordcount'//字数统计
import 'tinymce/plugins/table'//表格插件
import "tinymce/plugins/textcolor"; //文字颜色
import "tinymce/plugins/toc"; //目录生成器

let props = defineProps({//自定义属性用于设置默认值
    modelValue: {// 用于自定义v-model的value 父组件向子组件传递的值
        type: String,
        default: ''
    },
    disabled: {
        type: Boolean,
        default: false
    },
    plugins: {  //插件
        type: [String, Array],
        default: 'print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap nonbreaking anchor insertdatetime advlist lists wordcount autosave'
    },
    toolbar: { //工具栏
        type: [String, Array],
        default: 'fullscreen undo redo restoredraft | table image media charmap hrpagebreak insertdatetime print preview | cut copy paste pastetext | forcolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter  alignright alignjustify outdent indent | styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | code selectal searchreplace visualblocks indent2em lineheight formatpainter axupimgs'
    }
});

let emit = defineEmits(['update:modelValue']);

let initOption = ref({
    base_url: '/tinymce', // [必要参数] 指定public的目录
    language_url: "/tinymce/langs/zh-Hans.js",//语言类型的路径
    language: 'zh-Hans', // 语言类型(中文),默认会到tinymce/langs/zh_CN.js文件
    skin_url: '/tinymce/skins/ui/oxide', // 皮肤,浅色
    // skin_url: '/tinymce/skins/ui/oxide-dark', // 皮肤,深色
    plugins: props.plugins, // 插件配置
    toolbar: props.toolbar, // 工具栏配置,设置false则为隐藏
    toolbar_mode: 'sliding', // 工具栏移除模式,floating / sliding / scrolling / wrap
    // menubar: 'file edit', // 菜单栏配置,设置为false则隐藏,不配置默认显示全部菜单
    fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px', // 字体大小
    font_formats: '微软雅黑=Microsoft YaHei, Helvetica Neue, PingFang SC, sans-serif; 苹果苹方=PingFang SC, Microsoft YaHei, sans-serif; 宋体=simsun,serif; 仿宋体=FangSong,serif; Impact=impact,chicago', // 字体
    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%; }', // 自定义可编辑区的css样式
    setup: function (editor: any) {
        editor.ui.registry.addIcon(
            "image",
            `<svg t="1664002320321" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4420" width="24" height="24"><path d="M125.9 185h772.2v653.9H125.9z" fill="#1F53CC" p-id="4421"></path><path d="M164.7 217.2h694.6v516.7H164.7z" fill="#FECD44" p-id="4422"></path><path d="M458.9 734l-8.6-43.8-101.5-102.8-135 146.6z" fill="#FC355D" p-id="4423"></path><path d="M306.9 348.7m-66.7 0a66.7 66.7 0 1 0 133.4 0 66.7 66.7 0 1 0-133.4 0Z" fill="#FFFFFF" p-id="4424"></path><path d="M384.6 734h474.7V608.8L687.8 400.1z" fill="#FC355D" p-id="4425"></path><path d="M422.5 662l-37.9 72 52.1-57.5z" fill="#BF2847" p-id="4426"></path><path d="M302.5 778.9h418.9v16.7H302.5z" fill="#00F0D4" p-id="4427"></path></svg>`
        );
        editor.ui.registry.addIcon('preview', '<svg t="1664002320321" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4420" width="24" height="24"><path d="M125.9 185h772.2v653.9H125.9z" fill="#1F53CC" p-id="4421"></path><path d="M164.7 217.2h694.6v516.7H164.7z" fill="#FECD44" p-id="4422"></path><path d="M458.9 734l-8.6-43.8-101.5-102.8-135 146.6z" fill="#FC355D" p-id="4423"></path><path d="M306.9 348.7m-66.7 0a66.7 66.7 0 1 0 133.4 0 66.7 66.7 0 1 0-133.4 0Z" fill="#FFFFFF" p-id="4424"></path><path d="M384.6 734h474.7V608.8L687.8 400.1z" fill="#FC355D" p-id="4425"></path><path d="M422.5 662l-37.9 72 52.1-57.5z" fill="#BF2847" p-id="4426"></path><path d="M302.5 778.9h418.9v16.7H302.5z" fill="#00F0D4" p-id="4427"></path></svg>');
    },
    paste_data_images: true, // 图片是否可以粘贴

    images_upload_handler: (blobInfo: any, success: any, failure: any) => {//自定义图片上传 
        // let img = 'data:image/jpeg;base64,' + blobInfo.base64();
        let formData = new FormData();//创建一个表单对象
        formData.append('file', blobInfo.blob());//通过formData对象封装图片二进制数据
        upload(formData).then((res: any) => {//上传的图片路径
            success(res.data.url);//返回图片的回调地址
        }).catch((err: any) => {
            failure(err.response.data.message);
        })
    }
});
// 计算属性
let contentValue = computed({
    // 富文本框内容
    get() { // 取值 父组件向子组件传值
        return props.modelValue;
    },
    set(value: string) { // 赋值 子组件向父组件传值 ,吧value传递过去,
        emit('update:modelValue', value)
    }
})
</script>

效果图:

猜你喜欢

转载自blog.csdn.net/m0_71933813/article/details/129867828