本贴会涉及以下几个技术点:
- Vue+Element-UI实现富文本编辑框,以及文本编辑框中事件拦截、插入图片
- Element-UI限制上传图片后,隐藏上传按钮:官网上是没有这个方法的,可以通过上传到指定张数后隐藏上传按钮来实现
- form表单验证
首先安装一下插件vue-quill-editor,npm嫌慢可以用cnpm
官网地址:https://www.npmjs.com/package/vue-quill-editor
npm install vue-quill-editor --save
引用:
import { quillEditor } from "vue-quill-editor";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
//这个是插件优化的样式,在下面贴出来
import "@/config/quill/quill.scss";
下面是HTML部分:
<template>
<div class="page-content">
<el-form ref="quillForm" :rules="quillRules" :model="quillForm" label-width="65px">
<el-form-item label="内容:" prop="content">
<!-- 图片上传组件辅助 -->
<el-upload
class="avatar-uploader"
:action="editorOption.serverUrl"
name="file"
:show-file-list="false"
:on-success="uploadSuccess"
:on-error="uploadError"
:before-upload="beforeUpload"
style="display: none;">
</el-upload>
<quill-editor
class="editor"
ref="myQuillEditor"
v-model.lazy.trim="quillForm.content"
:options="editorOption"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@ready="onEditorReady($event)"
@change="onEditorChange($event)"/>
</el-form-item>
<el-form-item label="封面:" prop="coverImg">
<el-upload
name="coverImg"
action="#"
:auto-upload="false"
:file-list="fileList"
:class="{disabled: uploadDisabled}"
accept="image/*"
list-type="picture-card"
:limit="1"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:on-change="getFile">
<i slot="default" class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible" append-to-body>
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
</el-form-item>
</el-form>
<el-button @click="submit('quillForm')" type="primary">提交</el-button>
</div>
</template>
当前页面css:
<style lang="scss" scoped>
.page-content {
width: 100%;
height: 100%;
/deep/ .editor {
margin-top: 15px;
.ql-editor {
min-height: 300px;
}
}
.header {
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
/deep/ .el-form {
.el-form-item {
max-width: 700px;
}
}
/* 此处是隐藏上传按钮的样式 */
/deep/ .disabled .el-upload--picture-card {
display: none;
}
}
</style>
接下来是重点了,JavaScript它来了它来了,它迈着步伐走来了
<script>
//定义编辑框功能区,具体的可到官网上查看
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
[{'header': 1}, {'header': 2}], // custom button values
[{'list': 'ordered'}, {'list': 'bullet'}],
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
[{'direction': 'rtl'}], // text direction
[{'size': ['small', false, 'large', 'huge']}], // custom dropdown
[{'header': [1, 2, 3, 4, 5, 6, false]}],
[{'color': []}, {'background': []}], // dropdown with defaults from theme
[{'font': []}],
[{'align': []}],
['link', 'image'],
['clean']
]
//提交数据的方法,自己定义吧
import { shelfVehicle } from "@/api/disassemble"
//从这儿往下5行是引入编辑框相关事宜,上面也有
import { quillEditor } from "vue-quill-editor";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
import "@/config/quill/quill.scss";
export default {
//注册编辑框组件
components: {
quillEditor
},
data () {
return {
//加载动画
loading: "",
//form表单
quillForm: {
content: ""
},
//form表单校验
quillRules: {
content: [
{required: true, message: '请输入内容', trigger: 'blur'},
],
},
//富文本配置
editorOption: {
placeholder: "请输入相关内容",
modules: {
toolbar: {
container: toolbarOptions,
handlers: {
'image': function (value) {
if (value) {
document.querySelector('.avatar-uploader input').click()
} else {
this.quill.format('image', false);
}
}
}
}
},
// 此项配置是选择图片之后的上传路径
serverUrl: "xxx"
},
//上传图片
fileList: [],
dialogImageUrl: "",
dialogVisible: false,
postFile: "",
}
},
computed: {
//计算属性:计算fileList的长度,我的项目要求是上传一张图片,具体的可以自己改
uploadDisabled() {
return this.fileList.length > 0
},
},
methods: {
//监听富文本编辑框动向,敌不动我不动敌若动我乱动,还有个上传图片
onEditorChange({editor, html, text}) {
// 此处三项可以自己在控制台打印
this.quillForm.content = html
},
//图片上传前,显示loading动画,element官网上的,可以自行查找
beforeUpload() {
this.loading = this.$loading({
lock: true,
text: '图片加载中,请稍后!',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
},
// 上传成功
uploadSuccess(res, file) {
// 获取富文本组件实例
let quill = this.$refs.myQuillEditor.quill
if (res.code == 200 ) {
// 获取光标所在位置
let length = quill.getSelection().index;
// 获取光标所在位置// 获取光标所在位置
quill.insertEmbed(length, 'image', res.message)
// 调整光标到最后
quill.setSelection(length + 1)
} else {
this.$message.error(res.message)
}
this.loading.close();
},
// 上传失败来到这里
uploadError() {
this.loading.close();
this.$message.error(res.message)
},
//监听编辑器事件,此处三个事件可以自己根据业务定义
onEditorBlur(quill) {},
onEditorFocus(quill) {},
onEditorReady(quill) {},
/* 下面是上传图片 */
//拿到图片对象
getFile(file, fileList) {
this.postFile = file.raw
this.fileList = fileList
},
//查看图片预览
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
//删除图片
handleRemove(file, fileList) {
this.dialogImageUrl = ""
this.fileList = []
this.dialogVisible = false
},
//最后的提交数据,此处有表单验证
submit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
//将数据组成FormData传递给后台
let formData = new FormData();
formData.append("VehiclePartsStore", JSON.stringify(this.quillForm));
this.fileList.forEach( function (file) {
formData.append('file', file.raw, file.raw.name);
} );
//与后台交互,得到返回结果
shelfVehicle(formData).then(res => {
console.log(res);
})
} else {
return false;
}
});
}
},
}
</script>
后端上传图片的方法,提交数据的方法不贴了(懒惰一下)自己根据业务实现吧
@PostMapping("haha")
public String haha (@RequestParam("file") MultipartFile file) {
//此处不出意外的话你已经拿到了文件对象,具体业务逻辑就可以自己实现了
return "xxx";
}
实现效果图,编辑前
编辑后:
刚刚学会,有不对出烦请指出~
差点儿忘了,还有个优化css
.ql-editor {
min-height: 300px;
}
.ql-toolbar.ql-snow {
border: 1px solid #DCDFE6;
border-radius: 3px 3px 0 0;
}
.ql-container.ql-snow {
border: 1px solid #DCDFE6;
border-radius: 0 0 3px 3px;
}
.editor {
line-height: normal !important;
.ql-snow .ql-tooltip[data-mode='link']::before {
content: '请输入链接地址:';
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: '保存';
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode='video']::before {
content: '请输入视频地址:';
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: '14px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
content: '10px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
content: '18px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
content: '32px';
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: '文本';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
content: '标题1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
content: '标题2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
content: '标题3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
content: '标题4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
content: '标题5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
content: '标题6';
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: '标准字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
content: '衬线字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
content: '等宽字体';
}
}