CKEditor5 + vue3自用记录(查看时只读不可编辑;图片上传,解决通过下载图片接口进行图片回显问题,将文件流转为blob格式显示)

一、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地址显示,不再赘述。

  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
首先,你需要安装 `@ckeditor/ckeditor5-vue` 和 `@ckeditor/ckeditor5-build-classic` 两个依赖包。 ``` npm install --save @ckeditor/ckeditor5-vue @ckeditor/ckeditor5-build-classic ``` 然后在你的 Vue 组件中引入 CKEditor 组件: ```vue <template> <div> <ckeditor :editor="editor" v-model="content" :config="editorConfig"></ckeditor> </div> </template> <script> import ClassicEditor from '@ckeditor/ckeditor5-build-classic' import CKEditor from '@ckeditor/ckeditor5-vue' export default { components: { ckeditor: CKEditor.component }, data() { return { content: '', editorConfig: { // 配置项 }, editor: ClassicEditor } } } </script> ``` 在上面的代码中,我们引入了 `ClassicEditor`,它是一个预先配置好的编辑器,包含了常用的插件,如加粗、斜体、链接等。我们也可以自定义 `editorConfig`,来配置编辑器。 下面是一个常用的配置项示例: ```js editorConfig: { toolbar: { items: [ 'bold', 'italic', 'link', '|', 'bulletedList', 'numberedList', '|', 'imageUpload', 'blockQuote', 'insertTable', 'undo', 'redo' ] }, image: { toolbar: [ 'imageTextAlternative', '|', 'imageStyle:full', 'imageStyle:side', '|', 'imageResize', '|', 'imageUpload', 'imageUpload', 'imageUpload', 'imageUpload' ], styles: [ 'full', 'side' ] }, language: 'zh-cn', table: { contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells', 'tableCellProperties', 'tableProperties' ] }, licenseKey: '', simpleUpload: { uploadUrl: '/your/upload/url', headers: { 'X-CSRF-TOKEN': 'CSRF-Token' } } } ``` 上面的配置中,我们开启了图片上传功能,并且添加了表格插入、图片上传、撤销、重做等常用功能。同也设置了语言为中文。 如果需要添加额外的插件,可以使用 `@ckeditor/ckeditor5-*` 的包名,比如添加字数统计插件: ``` npm install --save @ckeditor/ckeditor5-word-count ``` 然后在 `editorConfig` 中添加: ```js import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount'; editorConfig: { plugins: [WordCount], toolbar: [ 'wordCount' ] } ``` 这样就可以在编辑器中添加字数统计功能了。 我们也可以自定义上传图片的方法,需要在配置项中添加 `simpleUpload` 选项: ```js editorConfig: { simpleUpload: { uploadUrl: '/your/upload/url', headers: { 'X-CSRF-TOKEN': 'CSRF-Token' }, // 自定义上传方法 async upload(file) { // 这里写上传逻辑 const formData = new FormData(); formData.append('file', file); const response = await axios.post('/your/upload/url', formData, { headers: { 'Content-Type': 'multipart/form-data', 'X-CSRF-TOKEN': 'CSRF-Token' } }); return { default: response.data.url }; } } } ``` 在上面的代码中,我们使用了 axios 发送了一个 `multipart/form-data` 的请求,然后返回上传的图片地址。 希望以上内容能够帮到你。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值