写在前面
作者简书地址
在我们前端开发中有时候需要对文章或者商品详情之类的进行编辑,需要图文并茂,文字排版.......普通的form表单已经不能担此重任。
本文呢?讲的如何在vue项目中如何封装富文本编辑器 Tinymce 成组件 如何上传图片 文件
公司采用的是阿里云oss上传 所以以这个为例
简书首发网址
第三步将整个tinymce封装的代码贴出
第四步对代码进行解析,描述一下为什么这么做
第五步将贴出再其它组件调用tinymce的注意事项与使用方法
具体步骤
一 、下载tinymce 原数据包 并拷贝到 static文件夹下
版本锁定4.8.2
npm i tinymce -S
1、从node_modules中找到tinymce数据包
2、拷贝tinymce数据到static文件夹下
二、在componts文件夹下建个Tinymce组件
三、编写tinymce组件
<template>
<div>
<input type="file" id="photoFileUpload" style="display: none" />
<textarea :id="Id"></textarea>
</div>
</template>
<script>
import { ossUpload, uploadImg } from '@/api/public'
import '../../../static/tinymce/tinymce'
export default {
name: 'mceeditor',
props: {
value: {
default: '',
type: String
},
config: {
type: Object,
default: () => {
return {
theme: 'modern',
height: 600
}
}
},
url: {
default: '',
type: String
},
accept: {
default: 'image/jpeg, image/png',
type: String
},
maxSize: {
default: 2097152,
type: Number
}
},
data() {
const Id = Date.now()
return {
Id: Id,
myEditor: null,
DefaultConfig: {
// GLOBAL
language: 'zh_CN', //汉化
height: 500, //默认高度
theme: 'modern', //默认主题
menubar: true,
toolbar: `styleselect | fontselect | formatselect | fontsizeselect | forecolor backcolor | bold italic underline strikethrough | insertfile link image | table | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | preview removeformat hr | paste code | undo redo | fullscreen `,//需要的工具栏
plugins: `
paste
importcss
image
code
table
advlist
fullscreen
link
lists
textcolor
colorpicker
hr
preview
`,
// CONFIG
forced_root_block: 'p',
force_p_newlines: true,
importcss_append: true,
// CONFIG: ContentStyle 这块很重要, 在最后呈现的页面也要写入这个基本样式保证前后一致, `table`和`img`的问题基本就靠这个来填坑了
content_style: `
* { padding:0; margin:0; }
html, body { height:100%; }
img { max-width:100%; display:block;height:auto; }
a { text-decoration: none; }
iframe { width: 100%; }
p { line-height:1.6; margin: 0px; }
table { word-wrap:break-word; word-break:break-all; max-width:100%; border:none; border-color:#999; }
.mce-object-iframe { width:100%; box-sizing:border-box; margin:0; padding:0; }
ul,ol { list-style-position:inside; }
`,
insert_button_items: 'image link | inserttable',
// CONFIG: Paste
paste_retain_style_properties: 'all',
paste_word_valid_elements: '*[*]', // word需要它
paste_data_images: true, // 粘贴的同时能把内容里的图片自动上传,非常强力的功能
paste_convert_word_fake_lists: false, // 插入word文档需要该属性
paste_webkit_styles: 'all',
paste_merge_formats: true,
nonbreaking_force_tab: false,
paste_auto_cleanup_on_paste: false,
// CONFIG: Font
fontsize_formats: '10px 11px 12px 14px 16px 18px 20px 24px',
// CONFIG: StyleSelect
style_formats: [
{
title: '首行缩进',
block: 'p',
styles: { 'text-indent': '2em' }
},
{
title: '行高',
items: [
{ title: '1', styles: { 'line-height': '1' }, inline: 'span' },
{ title: '1.5', styles: { 'line-height': '1.5' }, inline: 'span' },
{ title: '2', styles: { 'line-height': '2' }, inline: 'span' },
{ title: '2.5', styles: { 'line-height': '2.5' }, inline: 'span' },
{ title: '3', styles: { 'line-height': '3' }, inline: 'span' }
]
}
],
// FontSelect
font_formats: `
微软雅黑=微软雅黑;
宋体=宋体;
黑体=黑体;
仿宋=仿宋;
楷体=楷体;
隶书=隶书;
幼圆=幼圆;
Andale Mono=andale mono,times;
Arial=arial, helvetica,
sans-serif;
Arial Black=arial black, avant garde;
Book Antiqua=book antiqua,palatino;
Comic Sans MS=comic sans ms,sans-serif;
Courier New=courier new,courier;
Georgia=georgia,palatino;
Helvetica=helvetica;
Impact=impact,chicago;
Symbol=symbol;
Tahoma=tahoma,arial,helvetica,sans-serif;
Terminal=terminal,monaco;
Times New Roman=times new roman,times;
Trebuchet MS=trebuchet ms,geneva;
Verdana=verdana,geneva;
Webdings=webdings;
Wingdings=wingdings,zapf dingbats`,
// Tab
tabfocus_elements: ':prev,:next',
object_resizing: true,
// Image
imagetools_toolbar: 'rotateleft rotateright | flipv fliph | editimage imageoptions'
}
}
},
methods: {
setContent(content) {
this.myEditor.setContent(content)
},
getContent() {
return this.myEditor.getContent()
},
init() {
const self = this
window.tinymce.init({
// 默认配置
...this.DefaultConfig,
// 挂载的DOM对象
selector: `#${this.Id}`,
file_picker_types: 'file',
//上传文件
file_picker_callback: function (callback, value, meta) {
let fileUploadControl = document.getElementById("photoFileUpload")
fileUploadControl.click()
fileUploadControl.onchange = function () {
if (fileUploadControl.files.length > 0) {
let localFile = fileUploadControl.files[0]
ossUpload({ type: localFile.type }).then(res => {
uploadImg(res.data, localFile).then(res => {
if (res.code == 0) {
callback(res.data.name, { text: localFile.name, })
self.$emit('on-upload-complete', res) // 抛出 'on-upload-complete' 钩子
} else {
callback()
self.$emit('on-upload-complete', res) // 抛出 'on-upload-complete' 钩子
}
})
})
} else {
alert('请选择文件上传')
}
}
},
// 图片上传
images_upload_handler: function (blobInfo, success, failure) {
if (blobInfo.blob().size > self.maxSize) {
failure('文件体积过大')
}
if (self.accept.indexOf(blobInfo.blob().type) >= 0) {
uploadPic()
} else {
failure('图片格式错误')
}
function uploadPic() {
ossUpload({ type: "image/png" }).then(res => {
uploadImg(res.data, blobInfo.blob()).then(res => {
if (res.code == 0) {
success(res.data.name)
self.$emit('on-upload-complete', res) // 抛出 'on-upload-complete' 钩子
} else {
failure('上传失败: ')
self.$emit('on-upload-complete', res) // 抛出 'on-upload-complete' 钩子
}
})
})
}
},
// prop内传入的的config
...this.config,
setup: (editor) => {
self.myEditor = editor
editor.on(
'init', () => {
self.loading = true
self.$emit('on-ready') // 抛出 'on-ready' 事件钩子
editor.setContent(self.value)
self.loading = false
}
)
// 抛出 'input' 事件钩子,同步value数据
editor.on(
'input change undo redo', () => {
self.$emit('input', editor.getContent())
}
)
}
})
}
},
mounted() {
this.init()
},
beforeDestroy() {
// 销毁tinymce
this.$emit('on-destroy')
window.tinymce.remove(`#${this.Id}`)
},
}
</script>
四、解析代码 择重点讲
1.dom结构 主要有两个标签,input textrea
input: 用来做模拟文件上传的,上传后文件的地址 以a标签存在。
官网的demo中文件(附件)上传需要有php 和 .net环境支持 需要后台配合,效果会更好,貌似还需要收费。自己可以招后端小伙伴研究
tinymce 官网附件地址:https://www.moxiemanager.com/demos/tinymce.php
使用input标签的方法是我绞尽脑汁想出来的唯一替代方法,也是我封装出来与其他人不同的地方,这里所需要的参数和配置在以下两个参数中:
file_picker_types: 'file', 参数
file_picker_callback ,上传文件的钩子函数 在里面配置的上传文件函数即可
textarea:用来初始化tinymce的容器
2.import 引入 static文件下的 tinymce.js
3.prop: 组件调用时可传入的配置参数。
- url:是留出来给上传函数封装普通ajax的路径,因为我是oss上传 所以直接改造了images_upload_handler钩子
- accept:是点击上传图片 可选择图片类型
4.data部分
* DefaultConfig 注意配置tinymce的默认项 可以根据自己的需求增减
* myEditor 用来获取tinymce的setContent 和 getContent 两个实用api,观看代码它在 tinymce的setup阶段出现
5.methods
注意暴露出 两个实用api 再将tinymce初始化
需要自己动手的地方 ⚠️
* file_picker_types
* file_picker_callback
* images_upload_handler
这三个地方就是最重要的 文件上传以及图片上传,根据自己的业务需求改造上传函数即可
6.抛出了三个钩子函数给tinymce的父组件调用
- on-ready
- on-upload-complete
- on-destroy
五、组件调用
<template>
<div id="home" class="pd20">
tinyMce的使用
<tinymce ref="richText" v-model="content" @on-upload-complete="onEditorUploadComplete"></tinymce>
<div class="mt20">
<el-button type="primary" class="w100" @click="get">保存</el-button>
<el-button type="primary" class="w100" @click="set">设置</el-button>
</div>
</div>
</template>
<script>
import tinymce from '@/components/Tinymce'
window.tinymce.baseURL = '/static/tinymce' //需要调用tinymce的组件中得加入这,不然会报错
//this.$refs.richText.setContent//getContent 两个方法 获取与设置
export default {
name: 'Dashboard',
components: {
tinymce
},
data() {
return {
content: '欢迎来到首页'
}
},
watch: {
},
computed: {
},
methods: {
onEditorUploadComplete(res) {
if (res.code == 0) {
this.$message({
type: 'success',
message: '上传成功'
})
} else {
this.$message({
type: 'error',
message: res.msg
})
}
},
set() {
this.$refs.richText.setContent('设置内容')
},
get() {
console.log(this.$refs.richText.getContent())
},
},
created() {
},
mounted() {
}
}
</script>
<style lang="less" scoped>
</style>
补个知识点:
vue 中的ref 相当于 dom节点中id的作用 显然是更强大,this.$refs.ref,是可以拿到这个这个名为ref的实例,可以调用这个ref组件的methods方法(俗称 :父组件调用子组件的方法)
写在后面
总结:本文也是参考了许多其他优秀的前端开发写的博客,路数也差不多,都是写到图片上传就点到为止了,所以相似度很。但是我极少看见其他人会把tinymce文件上传的这部分写出来,我也算是另辟蹊径(歪门邪道),把这个共享给大家。希望大家会喜欢。觉得有用的小伙伴可以给个心,给个关注,有不懂的地方可以评论我和私信我,有空会一一解答。也可以加扣(214395856)
目前本人因要踏入node开发,学习egg.js 所以前端的文章会更新的较少了。如果有想到好的题材也会分享出来。
简书首发网址