项目背景:
最近搞了一个若依的请后端分离项目,需要进行富文本编辑,发现CKEditor5 看上去挺不错的。
首先安装组件
npm install --save
@ckeditor/ckeditor5-vue2
@ckeditor/ckeditor5-dev-webpack-plugin
@ckeditor/ckeditor5-dev-utils
postcss-loader@3 raw-loader@0.5.1
先新建一个组件
<template>
<div id="ck-editer">
<ckeditor id="editor"
:editor="editor"
@ready="onReady"
@input="onChange"
v-model="editorData"
:config="editorConfig" ></ckeditor>
</div>
</template>
<script>
import CKEditor from '@ckeditor/ckeditor5-vue2'
import '@ckeditor/ckeditor5-build-decoupled-document/build/translations/zh-cn'
import DecoupledEditor from '@ckeditor/ckeditor5-build-decoupled-document'
// import EssentialsPlugin from '@ckeditor/ckeditor5-essentials/src/essentials'
import request from '@/utils/request' // 若依自带的请求
export default {
props:{
value : String
},
components: {
ckeditor: CKEditor.component //声明组件名称
},
data () {
return {
editor: DecoupledEditor,
editorData: undefined,
textData : undefined,
// placeholder:,
editorConfig: { //定义组件插件
placeholder: '请输入详细内容',
toolbar: ['heading', 'fontSize',
'bold',
'italic',
'underline',
'strikethrough',
'|',
'highlight',
'highlight:yellowMarker',
'highlight:greenMarker',
'highlight:pinkMarker',
'highlight:blueMarker',
'fontFamily',
'alignment',
'imageStyle:full', 'imageStyle:alignLeft', 'imageStyle:alignRight',
'|',
'alignment',
'|',
'numberedList',
'bulletedList',
'|',
'indent',
'outdent',
'|',
'codeBlock',
'link',
'blockquote',
'imageUpload',
'insertTable',
'mediaEmbed',
'undo', 'redo'],
fontSize: {
options: [8, 9, 10, 11, 12, 'default', 14, 16, 18, 20, 22, 24, 26, 28, 36, 44, 48, 72],
},
language: 'zh-cn',
// extraPlugins : 'uploadimage',
// uploadUrl : process.env.VUE_APP_BASE_API + "/common/upload",
}
}
},
watch:{
editorData(e){
// console.log("编辑器内容"+e);
// console.log(this.textData);
if (e && e !== this.textData) { //这个地方数据和页面上的数据进行联动
this.textData =e;
// 编辑器内容发生变化时,告知外部,实现 v-model 双向监听效果
this.$emit("input", e);
}
}
},
methods: {
// 富文本初始方法
onReady (editor) {
//这个地方是导入文件上传的 适配器 所以需要下面
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
return new MyUploadAdapter( loader );
};
// editor.execute( 'codeBlock', { language: 'css' } );
editor.ui
.getEditableElement()
.parentElement.insertBefore(
editor.ui.view.toolbar.element,
editor.ui.getEditableElement()
)
},
//内容改变方式
onChange(editor){
// console.log(document.getElementById('editor').childNodes[0]);
// console.log(this.editorData)
// this.value = document.getElementById('editor').childNodes[0]
}
}
}
//文件上传适配器
class MyUploadAdapter {
constructor( loader ) {
// Save Loader instance to update upload progress.
this.loader = loader;
}
async upload() {
const data = new FormData();
data.append('typeOption', 'upload_image');
data.append('file', await this.loader.file);
return new Promise((resolve, reject) => {
return request({
url: '/minio/upload',
method: 'post',
data: data
})
});
}
async abort() {
// Reject the promise returned from the upload() method.
server.abortUpload();
}
}
</script>
组件的调用 ,删除了其他不需要展示的代码
<template>
<div class="app-container">
<!-- 添加或修改食谱对话框 -->
<el-dialog :title="title" :visible.sync="true" fullscreen>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="菜名" prop="name">
<el-input v-model="form.name" placeholder="请输入菜名" />
</el-form-item>
<el-form-item label="展示图" prop="img">
<single-upload v-model="form.img" style="width:300px;display:line-block;margin-left:10px;"/>
</el-form-item>
<el-form-item label="菜系">
<el-select v-model="form.cuisines" placeholder="请选择菜系">
<el-option
v-for="dict in cuisinesOptions"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="类别">
<el-select v-model="form.type" placeholder="请选择类别">
<el-option
v-for="dict in typeOptions"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="食材" prop="ingredientsIds">
<el-select v-model="form.ingredientsIds" multiple filterable remote reserve-keyword :loading="loading" placeholder="请输入食材" >
<el-option v-for="item in ingredientsOptions"
:key="item.id"
:label="item.name"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item>
<!-- 调用了editor 组件 -->
<el-form-item label="详细步骤" prop="manufacturing">
<editor v-model="form.manufacturing" ref="editor" @onChange="onChange" :width="680" :height="360" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listFood, getFood, delFood, addFood, updateFood, exportFood } from "@/api/food/food";
import {listIngredients} from "@/api/food/ingredients";
// import editor from '@/components/Editor'
import singleUpload from '@/components/Upload/singleUpload'
import editor from '@/components/CKEditor'
export default {
components :{singleUpload ,editor},
dicts: ['food_type','cuisines'],
data() {
return {
// 遮罩层
loading: true,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
name: undefined,
img: undefined,
type: undefined,
ingredientsIds: undefined,
manufacturing: undefined,
},
// 表单参数
form: {},
// 表单校验
rules: {
} //文件上传
};
},
created() {
this.getDicts("food_type").then(response => {
this.typeOptions = response.data;
});
this.getDicts("cuisines").then(response =>{
this.cuisinesOptions = response.data;
})
},
methods: {
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
id: undefined,
name: undefined,
img: undefined,
type: undefined,
ingredientsIds: undefined,
manufacturing: undefined,
createTime: undefined,
createBy: undefined
};
this.resetForm("form");
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加食谱";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
let that = this;
getFood(id).then(response => {
this.form = response.data;
this.form.ingredientsIds = response.ingredients
this.form.type = response.data.type.toString();
this.open = true;
this.title = "修改食谱";
this.$nextTick(() => {
//这个地方对于初始化加载数据的时候进行双向渲染, 但是需要提前加载好,所以用 this.$nextTick()
that.$refs.editor.editorData = that.form.manufacturing ;
// that.$refs.editor.textData = that.form.manufacturing ;
})
});
},
/** 提交按钮 */
submitForm: function() {
let that = this;
this.$refs["form"].validate(valid => {
if (valid) {
let ids =this.form.ingredientsIds;
console.log(this.form);
this.form.ingredientsIds = ids.toString();
if (this.form.id != undefined) {
// 修改form id
updateFood(this.form).then(response => {
if (response.code === 200) {
that.$refs.editor.editorData = '' ;
that.$modal.msgSuccess("修改成功");
// alert("修改成功")
that.open = false;
that.getList();
} else {
that.msgError(response.msg);
}
});
} else {
addFood(this.form).then(response => {
if (response.code === 200) {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
} else {
this.msgError(response.msg);
}
});
}
}
});
},
onChange(editor){
// debugger
console.log("aa"+editor);
var that = this;
that.form.manufacturing = editor;
}
}
};
</script>