Vue-Cli 3+tinymce 5 富文本编辑器整合
文章参考自 点击前往
安装插件
// 安装 tinymce-vue
npm install @tinymce/tinymce-vue -S
// 安装 tinymce
npm install tinymce -S
下载中文语言包
tinymce默认英文,中文语言包
使用
- 在
public
目录下新建tinymce
,将上面下载的语言包解压到该目录- 在
node_modules
里面找到tinymce
,将skins
目录复制到public/tinymce
里面- 最终形成以下目录形式:
封装 tinymce
我们将其再封装一下,方便自己在其他组件中使用
创建组件 Tinymce.vue 组件
<template>
<div class="tinymce-editor">
<Editor v-model="myValue"
:init="init"
:disabled="disabled"
@onClick="onClick">
</Editor>
</div>
</template>
<script>
import tinymce from 'tinymce/tinymce'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/themes/silver'
import 'tinymce/plugins/image';
import 'tinymce/plugins/media';
import 'tinymce/plugins/table';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/contextmenu';
import 'tinymce/plugins/wordcount';
import 'tinymce/plugins/colorpicker';
import 'tinymce/plugins/textcolor';
export default {
name: "Tinymce",
components: {
Editor
},
props: {
//传入一个value,使组件支持v-model绑定
value: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
plugins: {
type: [String, Array],
default: 'lists image media table textcolor wordcount contextmenu'
},
toolbar: {
type: [String, Array],
default: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists image media table | removeformat'
}
},
data() {
return {
//初始化配置
init: {
language_url: '/tinymce/langs/zh_CN.js',// 语言包的路径
language: 'zh_CN',//语言
skin_url: '/tinymce/skins/ui/oxide',// skin路径
height: 300,//编辑器高度
plugins: this.plugins,
toolbar: this.toolbar,
branding: false,//是否禁用“Powered by TinyMCE”
menubar: false,//顶部菜单栏显示
//此处为图片上传处理函数,这个直接用了base64的图片形式上传图片,
//如需ajax上传可参考https://www.tiny.cloud/docs/configure/file-image-upload/#images_upload_handler
images_upload_handler: (blobInfo, success, failure) => {
const img = 'data:image/jpeg;base64,' + blobInfo.base64();
success(img)
}
},
myValue: this.value
}
},
mounted() {
tinymce.init({});
},
methods: {
//添加相关的事件,可用的事件参照文档=> https://github.com/tinymce/tinymce-vue => All available events
//需要什么事件可以自己增加
onClick(e) {
this.$emit('onClick', e, tinymce)
},
//可以添加一些自己的自定义事件,如清空内容
clear() {
this.myValue = ''
}
},
watch: {
value(newValue) {
this.myValue = newValue
},
myValue(newValue) {
this.$emit('input', newValue)
}
}
}
</script>
<style scoped>
</style>
使用Tinymce.vue 组件
<template>
<a-form :form="form" @submit="handleSubmit">
<a-tag color="#108ee9">文章编辑</a-tag>
<a-form-item :label-col="labelCol" :wrapper-col="wrapperCol" label="文章分类">
<a-select
style="width: 100%"
v-decorator="['category_id',{initialValue:article.article_id,rules: [{ required: true, message: '请选择文章分类' }]}]"
>
<a-select-option value="0">请选择分类</a-select-option>
<a-select-option v-for="cate in cate_list" :value="cate.category_id">{{ cate.category_name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label-col="labelCol" :wrapper-col="wrapperCol" label="文章名称">
<a-input v-decorator="['article_title',{initialValue:article.article_title,rules: [{ required: true, message: '请填写文章名称' }]}]" />
</a-form-item>
<a-form-item :label-col="labelCol" :wrapper-col="wrapperCol" label="关键词" help="关键词用英文逗号','分隔">
<a-input v-decorator="['keywords',{initialValue:article.keywords,rules: [{ required: true, message: '请填写关键词' }]}]" />
</a-form-item>
<a-form-item :label-col="labelCol" :wrapper-col="wrapperCol" label="文章摘要">
<a-textarea v-decorator="['keywords',{initialValue:article.keywords,rules: [{ required: false, message: '请填写关键词' }]}]" autosize />
</a-form-item>
<a-form-item :label-col="labelCol" :wrapper-col="wrapperCol" label="文章插图">
<a-upload
name="article_pic"
listType="picture-card"
class="avatar-uploader"
:showUploadList="false"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
:beforeUpload="beforeUpload"
@change="handleChange"
v-decorator="['article_pic']"
>
<img v-if="imageUrl" :src="imageUrl" alt="avatar" />
<div v-else>
<a-icon :type="loading ? 'loading' : 'plus'" />
<div class="ant-upload-text">选择头像</div>
</div>
</a-upload>
</a-form-item>
<a-form-item :label-col="labelCol" :wrapper-col="wrapperCol" label="文章内容">
<!--使用 Tinymce.vue 组件-->
<tinymce_editor v-model="msg"
:disabled="disabled"
@onClick="onClick"
ref="editor">
</tinymce_editor>
</a-form-item>
<a-form-item :label-col="labelCol" :wrapper-col="wrapperCol">
<div>
<a-button type="primary" html-type="submit" class="login-form-button" style="margin-right: 5%;">
保存
</a-button>
<a-button type="danger" class="login-form-button" @click="resetFields">
重置
</a-button>
</div>
</a-form-item>
</a-form>
</template>
<script>
import {EditArticle,SaveArticle} from "../../axios/api";
import TinyMce from '../../components/Tinymce';
function getBase64 (img, callback) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img)
}
export default {
name: "MenuEdit",
components:{
tinymce_editor:TinyMce,
},
data: function () {
return {
loading: false,
form: this.$form.createForm(this),
article_id: this.$route.query.article_id,
article:[],
cate_list:[],
imageUrl:"",
head_pic:"",
labelCol: {
xs: {span: 24},
sm: {span: 5},
},
wrapperCol: {
xs: {span: 24},
sm: {span: 12},
},
disabled: false,
msg:"欢迎来到全新编辑器"
};
},
created() {
EditArticle({
article_id:this.article_id
}).then((response)=>{
if (response.data.code == 1){
this.article = response.data.data.article;
this.cate_list = response.data.data.cate_list;
this.imageUrl = response.data.data.article.article_pic;
}else{
this.msg = response.data.msg;
this.openNotification();
}
}).catch((err)=>{
console.log(err);
})
},
methods:{
openNotification () {
this.$notification.open({
message: "提醒框",
description: this.msg,
});
},
handleSubmit (e) {
e.preventDefault();
this.form.validateFields((errors,values)=>{
if (errors){
console.log(errors);
}else{
values.article_id = this.article_id;
values.article_pic = this.article_pic;
this.postSubmit(values);
}
})
},
resetFields(){
this.form.resetFields()
},
postSubmit(param){
SaveArticle(param).then((res)=>{
if (res.data.code == 1){
this.$message.success(res.data.msg, 2);
}else{
this.$message.error(res.data.msg, 2);
}
}).catch((error)=>{
console.log(error);
})
},
handleChange (info) {
if (info.file.status === 'uploading') {
this.loading = true;
return
}
if (info.file.status === 'done') {
this.article_pic = info.file.response.thumbUrl;
// Get this url from response in real world.
getBase64(info.file.originFileObj, (imageUrl) => {
this.imageUrl = imageUrl;
this.loading = false;
})
}else if(info.file.status === 'error'){
this.$message.error("上传失败",2);
}
},
beforeUpload (file) {
const isJPG = file.type === 'image/jpeg'
if (!isJPG) {
this.$message.error('You can only upload JPG file!')
}
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error('Image must smaller than 2MB!')
}
return isJPG && isLt2M
},
//鼠标单击的事件
onClick(e, editor) {
console.log('Element clicked');
console.log(e);
console.log(editor);
},
}
};
</script>
<style scoped>
</style>
成果
本作品采用《CC 协议》,转载必须注明作者和本文链接