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地址显示,不再赘述。
更多推荐
所有评论(0)