在vue3中使用tinymce

背景:在项目中有需要使用富文本编辑器的场景,目前存在的富文本编辑器有很多,本案例中使用的为tinymce富文本编辑器;

解决方案:在vue3中使用的方法有两种,但各有优缺点:

①使用官方提供方法引入,优点:操作简单;缺点:使用 api-key 请求的是云端的js文件,加载速度慢,影响用户体验;

②使用本地文件引入,优点:加载快,可二次封装成editor组件;缺点:样式和插件的引入比较麻烦,某些插件自定义方法容易踩坑;

目录

一. tinymce官方提供方法:

1. 安装 tinymce-vue:

2. 申请 api-key:

 3. 在组件中使用:

二. 使用本地文件引入:

1. 安装 tinymce 和 tinymce-vue

2. 处理文件路径:

 3. 对editor组件进行二次封装,新建 TinymceEditor.vue:

4. 在组件中的使用:

5. 坑:


一. tinymce官方提供方法:

本案例中使用的是tinymce版本5(tinymce-vue版本4)的方法

 官方文档(版本5):Vue integration | Docs | TinyMCE

1. 安装 tinymce-vue:

npm install --save "@tinymce/tinymce-vue@^4"

//或者

yarn add "@tinymce/tinymce-vue@^4"

2. 申请 api-key:

 3. 在组件中使用:

<template>
    <div>
        <Editor :init="myTinyInit" api-key="你申请到的A P I key" v-model="content" />
    </div>
</template>

<script setup>
import {defineExpose, reactive, ref, defineEmits} from 'vue'
import Editor from '@tinymce/tinymce-vue';

const myTinyInit = ref({
    width:'100%', //编辑器宽度,在tinymce版本6中要将toolbar_mode设置为wrap才生效
    height:500, //编辑器高度
    branding: false, //不显示logo
    menubar: false, 
    resize: false, 
    toolbar_mode: 'wrap',
    skeletonScreen: true, //编辑器懒加载,但设置后好像不生效
    placeholder: 'Please enter notification content',
    plugins: ['lists link image table paste help wordcount'], //需要的插件,可参考官方文档引入自己需要的插件
    toolbar: 'undo redo | formatselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image | help', //工具栏显示工具,可参考官方文档引入自己需要的工具
    fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 26px 36px 48px 56px',
    content_style: 'body, p{font-size: 14px;color:#606266}', //编辑器内的文本样式,也可以自定义 content.css 文件后引入
    //自定义图片上传方法(tinymce版本5)
    images_upload_handler:async (blobInfo, success, failure)=> {
        var formData = new FormData();
        formData.append('file', blobInfo.blob(), blobInfo.filename());
        const res = await tinymceImageUpload(formData); //tinymceImageUpload为封装的图片上传方法,可换成自己的方法
        if (res.code === 1) {
            success(`/image_manipulation/${res.data.filePath}`); //成功之后,返回图片路径,此处/image_manipulation/为图片路径前缀,具体情况根据后端返回的结果来
        } else {
            failure('Image upload failed due to a XHR Transport error. Code: ' + res.msg);
        }
    },
})

</script>

二. 使用本地文件引入:

本案例中使用的是tinymce版本6(tinymce-vue版本5)的方法

官方文档:Using the TinyMCE package with the Vue.js framework | TinyMCE Documentation

1. 安装 tinymce 和 tinymce-vue

npm install tinymce
npm install tinymce-vue
//or
yarn add tinymce
yarn add tinymce-vue

2. 处理文件路径:

①在public文件夹(如果是vue2则在static文件夹)下新建tinymce文件夹;

②语言包:默认为英文界面,如果需要改为中文界面,则需要下载一个中文语言包,在tinymce文件夹下新建langs文件夹,将下载的文件放在langs文件夹中;

③skins样式:在public文件夹(如果是vue2则在static文件夹)下新建tinymce文件夹,在node_modules中找到skins,将整个文件夹拷贝到 /public/tinymce/ 中;

 3. 对editor组件进行二次封装,新建 TinymceEditor.vue:

<template>
    <div>
        <Editor v-model="content" :init="myTinyInit"></Editor>
    </div>
</template>

<script setup>
import {computed, defineEmits, defineProps, onMounted, reactive, ref, watch} from 'vue'
import tinymce from "tinymce/tinymce";
import Editor from "@tinymce/tinymce-vue";
import "tinymce/icons/default/icons";
import "tinymce/themes/silver";
import "tinymce/models/dom/model";

//按需引入插件
import "tinymce/plugins/image";
import "tinymce/plugins/table";
import "tinymce/plugins/lists";
import "tinymce/plugins/link";
import "tinymce/plugins/help";
import "tinymce/plugins/wordcount";

import axios from "axios";
import {useStore} from "vuex";
import {ElNotification} from "element-plus";

const props = defineProps({
    modelValue: {
        type: String,
        default: ""
    },
    plugins: {
        type: [String, Array],
        default:'lists link image table help wordcount',
    },
    toolbar: {
        type: [String, Array],
        default: 'undo redo | formatselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image | help',
    }
});
const emit = defineEmits(['input']);

const store = useStore();
const myTinyInit = reactive({
    width: '100%',
    height: 500,
    branding: false,
    menubar: false,
    resize: false,
    skin_url: "/tinymce/skins/ui/oxide", //手动引入
    content_css: '/tinymce/skins/content/default/content.css', //手动引入
    toolbar_mode: "wrap",
    plugins: props.plugins,
    toolbar: props.toolbar,
    //图片上传方法(注意!!:要使用 promise 对象中的 resolve 返回图片路径,否则会报错)
    images_upload_handler: (blobInfo) => new Promise((resolve, reject) => {
        let formData = new FormData();
        formData.append('file', blobInfo.blob(), blobInfo.filename());
        axios.post(`/api/backend/upload`, formData, {
            headers: ({
                'Content-Type': 'multipart/form-data',
                'Authorization': "Bearer " + store.state.user.accessToken
            })
        }).then(res => {
            if (res.data.code === 1) {
                resolve(`/image_manipulation${res.data.data.filePath}`)
            } else {
                ElNotification.warning(res.data.msg)
            }
        }).catch(error => {
            reject(error);
        })
    }),
});


const initContent = computed(() => {
    return props.modelValue
});

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

const content = ref();
watch(initContent, (newVal) => {
    content.value = newVal;
}, {deep: true, immediate: true});

watch(content, (newVal) => {
    emit("input", newVal);
}, {deep: true});
</script>

<style scoped lang="scss">
</style>

4. 在组件中的使用:

<template>
   <div>
      <editor v-model="notificationForm.content" @input="(val)=> {notificationForm.content=val}"></editor>
      //@input方法需要自己定义,不然父组件可能获取不到editor的值
   </div>
</template>

<script setup>
import {reactive} from 'vue'
import Editor from '@/components/backend/TinymceEditor.vue';
const notificationForm = reactive({
    content: '',    
})
</script>

5. 坑:

①文件的引入:除了需要将语言包和skins存在本地外,可能会出现css或plugin文件引入报错的情况,按照报错的情况引入对应的文件即可;

②自定义方法:不能用封装好的axios请求,必须要使用 promise 对象(详情见 images_upload_handler 方法的定义);

猜你喜欢

转载自blog.csdn.net/weixin_57092157/article/details/130722805