一、CKEditor的使用       

        尝试了很多方法,最后参考了富文本编辑ckeditor5+vue3的使用记录_vue3 ckeditor5 online builder-CSDN博客链接里的2.2.1,直接使用构建文件比较方便,别的方法老是有啥报错,但是能查到的资料又特别少。

        在线构建网址:CKEditor 5 Online Builder | Create your own editor in 5 steps

        建议直接把所有有可能用到的功能都构建上,省的后续还要重新选择构建。记得带premium标签的功能是要另外付费的,不然下载完也是用不了的,报错提示缺少证书。

        另外有的功能可能会相互冲突,也会产生报错,需要注意。

        遇到问题还是的得多看官方文档.

1、安装依赖包

npm install --save @ckeditor/ckeditor5-vue

2、新建组件文件夹,放入构建文件

        构建文件build下的这两个文件ckeditor.js 和 ckeditor.js.map放入组件文件中

3、在该文件夹下添加index.js文件,如上图所示

        index.js文件

    import './ckeditor.js'
    export default window['ClassicEditor']

4、全局挂载

import CKEditor from '@ckeditor/ckeditor5-vue';

app.use( CKEditor );

5、创建组件及使用(组件index.vue文件所有代码)

        如上图目录所示,创建了vue文件,作为组件方便复用。

html

<div class="editor-contain">
  <ckeditor 
    :editor="editor" 
    v-model="editorData" 
    :config="editorConfig"
    @input="handleInput"
    @ready="onEditorReady"></ckeditor>
</div>

        将工具栏设置为props参数,方便按需使用。config中的参数均从构建文件src目录下的ckedtor.ts文件中复制。相关API查看官方文档

js全部代码

import { onMounted, ref } from 'vue';
import ClassicEditor from './ckeditor';
import MyUploadAdapter from './ckeditor/uploadImg';//自定义上传图片方法
import axios from 'axios'
import { getToken } from "@/utils/auth";
import { blobValidate } from '@/utils/panda' // 验证是否为blob格式方法
const downloadImgById = import.meta.env.VITE_APP_BASE_API + "/download"; // 下载的图片服务器地址
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  },
  toolbarItems: {
    type: Array,
    default: () => [
      'heading',
      '|',
      'bold',
      'italic',
      'underline',
      'strikethrough',
      'fontSize',
      'fontFamily',
      'fontColor',
      'fontBackgroundColor',
      'highlight',
      'subscript',
      'superscript',
      'specialCharacters',
      '|',
      'horizontalLine',
      'pageBreak',
      'alignment',
      'bulletedList',
      'numberedList',
      'todoList',
      '|',
      'outdent',
      'indent',
      '|',
      'code',
      'codeBlock',
      '|',
      'link',
      'imageUpload',
      'blockQuote',
      'insertTable',
      'mediaEmbed',
      'findAndReplace',
      '|',
      'undo',
      'redo'
    ]
  },
  isReadonly: {
    type: Boolean,
    default: false
  }
});
const emit = defineEmits();
const editor = ref(ClassicEditor);
const editorData = ref('');
const editorConfig = ref({
  toolbar: {
    items: props.toolbarItems
  },
  image: {
    toolbar: [
      'imageTextAlternative',
      'toggleImageCaption',
      'imageStyle:inline',
      'imageStyle:block',
      'imageStyle:side'
    ]
  },
  table: {
    contentToolbar: [
      'tableColumn',
      'tableRow',
      'mergeTableCells',
      'tableCellProperties'
    ]
  },
});
const imgUrlMap = new Map([]);

const onEditorReady = (editor) => {
  /*
  // 隐藏编辑工具栏
  const toolbarElement = editor.ui.view.toolbar.element;
  if(props.isReadonly){
    toolbarElement.style.display = 'none'
  }else{
    toolbarElement.style.display = 'flex'
  }
  */
  // 是否禁用编辑
  if(props.isReadonly){
    editor.enableReadOnlyMode('my-feature-id');
  }
  else{
    editor.disableReadOnlyMode('my-feature-id');
  }
  //自定义图片上传
  editor.plugins.get('FileRepository').createUploadAdapter = loader => {
    return new MyUploadAdapter(loader)
  }
  // 监听上传完成
  editor.plugins.get( 'ImageUploadEditing').on('uploadComplete', (event, {data}) => {
    // 将blob地址对应的ossId 存储到map中
    imgUrlMap.set(data.default, data.ossId)
  })
}

// 输入框内容变化时 blob地址替换为ossId存储
const handleInput = (value) => {
  // 正则表达式匹配img标签内的blob地址
  const regex = /(<img.*?src="blob)(.*?)(".*?>)/g; //用于匹配<img>标签中的src属性的正则表达式
  const matches = value.match(regex);
  if(matches){
    // 循环匹配到的img标签
    matches.forEach(match => {
      // 获取blob地址
      const blob = match.match(/src="(.*?)"/)[1];
      // 获取blob对应的ossId
      const ossId = imgUrlMap.get(blob);
      // 如果存在ossId,则替换blob地址
      if(ossId){
        // 将blob地址替换为ossId
        value = value.replace(blob, `${ossId}`);
      }
    })
  }
  // 给父组件更新数据
  emit('update:modelValue', value)
}

onMounted(() => {
  // 初始化数据 默认值
  if(props.modelValue){
    // 对内容进行初始化处理 原本存储的ossId改为blob地址
    editorData.value = props.modelValue;
    // 正则表达式匹配img标签内的src
    const regex = /(<img.*?src=")(.*?)(".*?>)/g; //用于匹配<img>标签中的src属性的正则表达式
    const matches = props.modelValue.match(regex);
    if (matches){
      // 循环匹配到的img标签
      matches.forEach(async (match) => {
        const ossId = match.match(/src="(.*?)"/)[1];
        // 获取src地址 且不是blob地址
        if(ossId && !(/blob/.test(ossId))){
          // 通过ossId请求接口获取图片文件流
          await axios({
            method: 'get',
            url: downloadImgById  + '/' + ossId,
            responseType: 'blob',
            headers: { Authorization: "Bearer " + getToken() }
          }).then((downloadRes) => {
            const isBlob = blobValidate(downloadRes.data);
            if (isBlob && downloadRes.status==200) {
              // 将文件流转为blob地址
              const blobUrl = URL.createObjectURL(downloadRes.data);
              // 将blob地址和ossId的对应关系保存到map中
              imgUrlMap.set(blobUrl,ossId);
              // 替换掉ossId
              editorData.value = editorData.value.replace(ossId, blobUrl);
            }
          }).catch((r) => {
              console.error(r)
          })
        }
      });
    }
  }
})

二、关键代码详情

1、查看时不可编辑

        enableReadOnlyMode方法可以禁用大部分功能,工具栏呈现灰色,文本不可编辑,本质是修改contentEditable属性。如下图所示。

但是部分功能仍然可以,比如查询 等等。如果要彻底无法使用,可以直接隐藏工具栏,如下方代码。在编辑器ready钩子中写入。

2、图片上传

       Custom image upload adapter | CKEditor 5 Framework Documentation官网自定义图片上传参考。

        同样在ready钩子中写入。MyUploadAdapter是引入的自定义类

         按照上面目录创建uploadImg.js文件,代码如下:

import axios from 'axios'
import { getToken } from "@/utils/auth";
import { blobValidate } from '@/utils/panda'
const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/upload"; // 上传的图片服务器地址
const downloadImgById = import.meta.env.VITE_APP_BASE_API + "/download"; // 下载的图片服务器地址
export default class MyUploadAdapter {
    constructor(loader) {
      // Save Loader instance to update upload progress.
      this.loader = loader
    }
  
    async upload() {
      let formData = new FormData()
      formData.append('file', await this.loader.file)
      let config = {
        headers: {
          'Content-Type': 'multipart/form-data',
          'Authorization': "Bearer " + getToken()
        },
      }
  
      return new Promise(async (resolve, reject) => {
        axios({
          url: uploadUrl,
          method: 'post',
          data: formData,
          headers: config.headers
        }).then(async res => {
          // 上传图片成功后获取图片的ossId
          if(res.data.code == 200){
            const {fileName, ossId, url} = res.data.data
            // 下载图片回显
            axios({
              method: 'get',
              url: downloadImgById  + '/' + ossId,
              responseType: 'blob',
              headers: config.headers
            }).then((downloadRes) => {
              const isBlob = blobValidate(downloadRes.data);
              if (isBlob && downloadRes.status==200) {
                // 将文件流转为blob地址
                const blobUrl = URL.createObjectURL(downloadRes.data)
                //注意这里的resolve的内容一定要是一个对象,而且一定要有一个default设置为图片上传后的url,这是ckeditor的规定格式。
                resolve({ default: blobUrl , ossId})
              }
            }).catch((r) => {
              console.error(r)
            })
          }
        }).catch(err => {
          reject(err)
        })
      })
    }

    // Aborts the upload process.
    abort() {
        // Reject the promise returned from the upload() method.
        server.abortUpload();
    }
}

        这里包含图片上传后回显。图片上传至oss,后端要求显示的话调用download方法获取文件流,再转换成blob地址进行显示。

3、图片回显

        这里会出现保存时,把图片也保存成blob格式,再次打开编辑时无法显示图片的问题。所以resolve的时候将blob格式的地址和图片id都返回在组件做一个Map,最后保存的时候再替换回去。替换时使用编辑器的input事件(change事件)。

        resolve返回的数据将在uploadComplete事件中得到,在ready钩子中进行一个监听,并存入map中。

        注意这里value值中的图片已经是blob地址,我们将它转化为id传递给父组件,这里不会影响编辑器的内容,只是传给父组件的值修改了。

        onMounted中则是初始化编辑状态传过来的数据,将图片id转为blob地址显示,不再赘述。

GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐