一直想要一个神奇多功能的富文本编辑器,经同事介绍这个 WangEditor 富文本编辑器,发现挺好用的。
传送门:wangEditor
于是用Vue2.X语法自行封装一下,这样在复用组件时,可以少写一些代码,直接组件引用即可。另外,Vue3.X项目也可直接使用,因为Vue3.X是兼容Vue2.X语法的。
注意:如果反复创建与销毁该组件,记得用上v-if
<WangEditor
ref="wangEditorRef"
v-if="isShowWangEditor"
:disabled="editorParams.isDisabled"
:editorParams="editorParams">
</WangEditor>
导入依赖包,注意Vue2.X和Vue3.X项目导入的依赖包版本会有所不同
// 查看 @wangeditor/editor 版本列表
npm view @wangeditor/editor versions --json
// 导入 @wangeditor/editor 依赖包
npm i --save @wangeditor/editor@5.1.15
// 查看 @wangeditor/editor-for-vue 版本列表
npm view @wangeditor/editor-for-vue versions --json
// 导入 @wangeditor/editor-for-vue 依赖包
npm i --save @wangeditor/editor-for-vue@5.1.12
父组件:index.vue
<template>
<div style="padding: 100px">
<WangEditor
ref="wangEditorRef1"
:disabled="editorParams1.isDisabled"
:editorParams="editorParams1">
</WangEditor>
<br/>
<WangEditor
ref="wangEditorRef2"
:disabled="editorParams2.isDisabled"
:editorParams="editorParams2">
</WangEditor>
<br/>
<p align="center">
<el-button type="primary" @click="handleSubmitClick">完成</el-button>
</p>
</div>
</template>
<script>
import WangEditor from './components/wangEditor'
export default {
components: {
WangEditor
},
data: () => ({
// editorParams1 必填非空的富文本参数
editorParams1: {
content: '', // 富文本内容
placeholder: '请填写内容', // 富文本占位内容
uploadImageUrl: 'http://IP:Port/api/uploadFileAction', // 富文本上传图片的地址
height: '300px', // 富文本最小高度
isDisabled: false // 富文本是否禁用
},
// editorParams2 必填非空的富文本参数
editorParams2: {
content: 'HelloWorld!', // 富文本内容
placeholder: '请填写内容', // 富文本占位内容
uploadImageUrl: 'http://IP:Port/api/uploadFileAction', // 富文本上传图片的地址
height: '300px', // 富文本最小高度
isDisabled: true // 富文本是否禁用
}
}),
methods: {
/**
* 获取富文本内容
*/
async handleSubmitClick () {
let refs = await this.$refs
let wangEditorRef1 = refs.wangEditorRef1
if (wangEditorRef1 != null) {
console.log(wangEditorRef1.getEditorHtml())
console.log(wangEditorRef1.getEditorText())
}
let wangEditorRef2 = refs.wangEditorRef2
if (wangEditorRef2 != null) {
console.log(wangEditorRef2.getEditorHtml())
console.log(wangEditorRef2.getEditorText())
}
}
}
}
</script>
子组件:wangEditor.vue
<template>
<div>
<div class="w-e-for-vue">
<!-- 工具栏 -->
<Toolbar
class="w-e-for-vue-toolbar"
:editor="editor"
:defaultConfig="toolbarConfig">
</Toolbar>
<!-- 编辑器 -->
<Editor
class="w-e-for-vue-editor"
:style="'height: ' + height"
:disabled="isDisabled"
:defaultConfig="editorConfig"
v-model="content"
@onChange="onChange"
@onCreated="onCreated">
</Editor>
</div>
</div>
</template>
<script>
import {
Editor, Toolbar } from '@wangeditor/editor-for-vue'
export default {
name: 'wangEditor',
components: {
Editor, Toolbar },
props: [
'editorParams'
],
data () {
return {
editor: null, // 富文本编辑器对象
content: null, // 富文本内容
placeholder: null, // 富文本占位内容
uploadImageUrl: null, // 富文本上传图片的地址
height: '300px', // 富文本最小高度
isDisabled: false, // 富文本是否禁用
// 工具栏配置
toolbarConfig: {
// toolbarKeys: [], // 显示指定的菜单项
// excludeKeys: [], // 隐藏指定的菜单项
},
// 编辑器配置
editorConfig: {
placeholder: '请输入内容......',
MENU_CONF: ['uploadImage']
}
}
},
watch: {
/**
* 深度监听富文本参数
*/
'editorParams': {
handler: function (newVal, oldVal) {
if (newVal != null) {
this.content = newVal.content
this.editorConfig.placeholder = this.placeholder
this.uploadImageUrl = newVal.uploadImageUrl
this.setUploadImageConfig()
this.height = newVal.height
this.isDisabled = newVal.isDisabled
}
},
immediate: true,
deep: true
}
},
methods: {
/**
* 实例化富文本编辑器
* 文档链接:https://www.wangeditor.com/
*/
onCreated (editor) {
this.editor = Object.seal(editor)
this.setIsDisabled()
},
/**
* 监听富文本编辑器
*/
onChange (editor) {
// console.log('onChange =>', editor.getHtml())
},
/**
* this.editor.getConfig().spellcheck = false
* 由于在配置中关闭拼写检查,值虽然设置成功,但是依然显示红色波浪线
* 因此在富文本编辑器组件挂载完成后,操作 Dom 元素设置拼写检查 spellcheck 为假
*/
async setSpellCheckFasle () {
let doms = await document.getElementsByClassName('w-e-scroll')
for (let vo of doms) {
if (vo) {
if (vo.children.length > 0) {
vo.children[0].setAttribute('spellcheck', 'false')
}
}
}
},
/**
* 设置富文本是否禁用
*/
async setIsDisabled () {
if (this.editor) {
this.isDisabled ? (this.editor.disable()) : (this.editor.enable())
}
},
/**
* 上传图片配置
*/
setUploadImageConfig () {
this.editorConfig.placeholder = this.placeholder
this.editorConfig.MENU_CONF['uploadImage'] = {
fieldName: 'files', // 文件字段名, 默认值 'wangeditor-uploaded-image'
maxFileSize: 1 * 1024 * 1024, // 单个文件的最大体积限制,默认为 2M,此次设置为 1M
maxNumberOfFiles: 10, // 最多可上传几个文件,默认为 100
allowedFileTypes: ['image/*'], // 选择文件时的类型限制,默认为 ['image/*'] ,若不想限制,则设置为 []
meta: {// 自定义上传参数,例如传递验证的 token 等,参数会被添加到 formData 中,一起上传到服务端
token: 'xxx',
otherKey: 'yyy'
},
metaWithUrl: false, // 将 meta 拼接到 URL 参数中,默认 false
headers: {// 设置 HTTP 请求头信息
// ...
},
server: this.uploadImageUrl, // 上传图片接口地址
withCredentials: false, // 跨域是否传递 cookie ,默认为 false
timeout: 5 * 1000, // 超时时间,默认为 10 秒,此次设置为 5 秒
// 上传之前触发
onBeforeUpload (file) {
return file
},
// 上传进度的回调函数
onProgress (progress) {
console.log('progress', progress)
},
// 单个文件上传成功之后
onSuccess (file, res) {
console.log(`${file.name} 上传成功`, res)
},
// 单个文件上传失败
onFailed (file, res) {
console.log(`${file.name} 上传失败`, res)
},
// 上传错误,或者触发 timeout 超时
onError (file, err, res) {
console.log(`${file.name} 上传出错`, err, res)
}
}
},
/**
* 获取编辑器文本内容
*/
getEditorText () {
const editor = this.editor
if (editor != null) {
return editor.getText()
}
},
/**
* 获取编辑器Html内容
*/
getEditorHtml () {
const editor = this.editor
if (editor != null) {
return editor.getHtml()
}
}
},
created () {
},
mounted () {
this.setSpellCheckFasle() // 设置拼写检查 spellcheck 为假
document.activeElement.blur() // 取消富文本自动聚焦且禁止虚拟键盘弹出
},
/**
* 销毁富文本编辑器
*/
beforeDestroy () {
const editor = this.editor
if (editor != null) {
editor.destroy()
}
}
}
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>
<style lang="less" scoped>
.w-e-full-screen-container {
z-index: 99;
}
.w-e-for-vue {
margin: 0;
border: 1px solid #ccc;
.w-e-for-vue-toolbar {
border-bottom: 1px solid #ccc;
}
.w-e-for-vue-editor {
height: auto;
/deep/ .w-e-text-container {
.w-e-text-placeholder {
top: 6px;
color: #666;
}
pre {
code {
text-shadow: unset;
}
}
p {
margin: 5px 0;
font-size: 14px; // 设置编辑器的默认字体大小为14px
}
}
}
}
</style>
服务端 IndexController.java 的上传图片方法
@Value("${system.upload-file-path}")
private String uploadFilePath;
@ResponseBody
@RequestMapping("uploadImageAction")
@CrossOrigin
public HashMap<String, Object> uploadImageAction(@RequestParam List<MultipartFile> files) {
HashMap<String, Object> responseMap = new HashMap<>();
String newsFilePath = uploadFilePath;
try {
List<HashMap<String, String>> newFileList = new ArrayList<>();
for (MultipartFile file : files) {
String uuidStr = UUID.randomUUID().toString();
String uuid = uuidStr.substring(0, 8) + uuidStr.substring(9, 13) + uuidStr.substring(14, 18) + uuidStr.substring(19, 23) + uuidStr.substring(24);
String OldFileName = file.getOriginalFilename();// 原文件名,如:hello.pdf
int beginIndex = OldFileName.lastIndexOf(".");// 从后匹配"."
String newFileName = uuid + OldFileName.substring(beginIndex);// 新文件名,如uuid.pdf
String destFileName = newsFilePath + File.separator + newFileName;// 存储路径 + 新文件名
// 复制文件到指定目录
File destFile = new File(destFileName);
destFile.getParentFile().mkdirs();
file.transferTo(destFile);
String url = "http://IP:Port/uploadFilePath/" + newFileName;
HashMap<String, String> map = new HashMap<>();
map.put("url", url);
map.put("alt", "图片描述,非必须");
map.put("href", "图片的链接,非必须");
newFileList.add(map);
}
responseMap.put("data", newFileList);
responseMap.put("errno", 0);// 上传成功:值是数字0,不能是字符串
} catch (FileNotFoundException e) {
e.printStackTrace();
responseMap.put("errno", 1);// 上传失败:值不是数字0
responseMap.put("message", "文件无法找到");
} catch (IOException e) {
e.printStackTrace();
responseMap.put("errno", 1);// 上传失败:值不是数字0
responseMap.put("message", "文件上传失败");// 上传失败:值不是数字0
}
return responseMap;
}
服务端 StaticResourceConfig.java 静态资源配置类
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class StaticResourceConfig extends WebMvcConfigurationSupport {
@Value("${system.upload-file-path}")
private String uploadFilePath;
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/uploadFilePath/**").addResourceLocations("file:" + uploadFilePath);
}
}
服务端 application.yml 配置文件
server:
port: 8091
system:
upload-file-path: "D:/uploadFilePath/"
最终效果: