wangEditor编辑器链接:(使用更简单些~~)
https://blog.csdn.net/ziyu_nuannuan/article/details/122837550
1.安装
npm install quill ^2.0.0-dev.3 版本需要大于2.0版本 npm install
quill-better-table 处理表格
2.创建组件页面 quill-editor.vue
<template>
<div>
<el-upload
ref="upload"
class="avatar-uploader"
:action="serverUrl"
name="file"
:headers="headers"
:show-file-list="false"
:on-success="uploadSuccess"
:on-error="uploadError">
</el-upload>
<div class="editor" style="height: calc(100% - 100px)"></div>
</div>
</template>
<script>
import Quill from 'quill'
import 'quill/dist/quill.snow.css'
import QuillBetterTable from 'quill-better-table'
import 'quill-better-table/dist/quill-better-table.css'
Quill.register({
'modules/better-table': QuillBetterTable
}, true)
export default {
name: 'editor',
props: ['value'],
data() {
return {
quill: null,
content:'',
options: {
theme: 'snow',
modules: {
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'],
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
[{ 'script': 'super' }],
[{ 'indent': '-1' }, { 'indent': '+1' }],
[{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }],
[{ 'align': [] }],
['link', 'image'],
[
{ 'table': 'TD' },
],
],
handlers: {
'table': function () {//默认插入的表格行列数
this.quill.getModule('better-table').insertTable(3, 3)
},
},
},
table: false,
'better-table': {//表格设置
operationMenu: {
items: {//鼠标右键菜单设置,如将某一项设置false则右键菜单不会显示 如insertColumnRight: false
insertColumnRight: {
text: '右边插入一列'
},
insertColumnLeft: {
text: '左边插入一列'
},
insertRowUp: {
text: '上边插入一行'
},
insertRowDown: {
text: '下边插入一行'
},
mergeCells: {
text: '合并单元格'
},
unmergeCells: {
text: '拆分单元格'
},
deleteColumn: {
text: '删除列'
},
deleteRow: {
text: '删除行'
},
deleteTable: {
text: '删除表格'
},
},
background: {
color: '#333'
},
color: {
colors: ['green', 'red', 'yellow', 'blue', 'white'],
text: '背景颜色:'
}
}
},
keyboard: {
bindings: QuillBetterTable.keyboardBindings
}
},
placeholder: '请输入内容 ...'
},
// 图片上传请求头
headers: {
token: localStorage.getItem('token')
},
// 图片上传调用的后端地址url
serverUrl:'/api/upload',
}
},
mounted() {
let dom = this.$el.querySelector('.editor')
this.quill = new Quill(dom, this.options);
this.quill.setContents(this.value)
this.quill.on('text-change', () => {
this.$emit('inputChange', this.quill.getContents())
});
},
methods:{
uploadSuccess(res, file) {
let quill = this.$refs.myQuillEditor.quill
// 上传成功
if (res.code === 0) {
// 获取光标所在位置
let length = quill.getSelection.index;
// 插入图片
quill.insertEmbed(length, 'image', res.data);
// 调整光标到最后
quill.setSelection(length + 1);
} else {
this.$message.error("插入图片失败");
}
},
uploadError() {
this.$message.error("图片上传失败");
},
},
}
</script>
<style scoped>
/deep/ .ql-snow.ql-toolbar button, .ql-snow .ql-toolbar button {
background: ivory;
}
//图标背景颜色
/deep/ .ql-toolbar.ql-snow .ql-picker-label {
border: 1px solid transparent;
background: ivory;
}
//富文本框大小
/deep/ .ql-editor{
height: 500px;
padding: 0 10px;
}
//图标间距设置
/deep/ .ql-toolbar.ql-snow .ql-formats {
margin-right: 15px;
margin-bottom: 5px;
}
//表格边框颜色设置
/deep/ table.quill-better-table td{
border: 1px solid #fff;
}
//table宽度铺满
/deep/ table.quill-better-table{
width: 100% !important;
}
</style>
3.父组件引用
<Editor :ref="'myQuillEditor'+id"
v-model="details"
@inputChange="onEditorChange($event)">
</Editor>
<script>
import Editor from '@/components/quill-editor'
export default {
name: "index",
components: {
Editor
},
data() {
return {
details:'',
id:''
};
},
created() {
this.getInfo()
},
methods:{
//将已有的数据回显到富文本框中
getInfo(){
//假设调用接口返回值为res.data
//解析成json格式
this.details = JSON.parse(res.data.details)
//如果页面中调用多个富文本框,则ref动态
let r = 'myQuillEditor'+id
if (this.$refs[r]) {
this.$refs[r].quill.setContents(this.details)
}
//如是单个页面调用方式如下
//this.$refs.myQuillEditor.quill.setContents(this.details)
},
onEditorChange(val){
//富文本框返回的数据格式为
//{"ops":[{"insert":"SSSSSSSSSSSSSSSSSSSSSSSSS\n"}]}
this.details = val
},
//保存需要处理数据
save(){
//处理成string格式存储到数据库
let details = JSON.stringify(this.details)
...
}
}
};
</script>
4.后端接口
@Slf4j
@RestController
public class FileUploadController {
/**
* yml中配置的存储路径
*/
@Value("${file.uploadFolder}")
private String rootPath;
/**
* 文件上传
* @param file
* @param request
* @return
*/
@RequestMapping("/upload")
@ResponseBody
public JsonResult uploadFile(@RequestParam(value = "file", required = false)MultipartFile file, HttpServletRequest request) {
String path = rootPath;
String fileName = file.getOriginalFilename();
// 获取文件名后缀
String suffix = fileName.substring(file.getOriginalFilename().lastIndexOf("."));
suffix = suffix.toLowerCase();
fileName = UUID.randomUUID().toString() + suffix;
File targetFile = new File(path, fileName);
// 判断父级目录是否存在
if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
}
// 保存本地
try {
file.transferTo(targetFile);
} catch (IOException e) {
e.printStackTrace();
return JsonResult.error("上传失败!");
}
// 项目url
String fileUrl = request.getContextPath() + "/api/showImg?imgUrl=";
// 文件获取路径
fileUrl += "/"+ fileName;
return JsonResult.success(fileUrl);
}
/**
* 文件回显
* @param imgUrl
* @param response
* @throws IOException
*/
@GetMapping("/showImg")
public void ioReadImage(String imgUrl, HttpServletResponse response) throws IOException {
ServletOutputStream out = null;
FileInputStream ips = null;
try {
//获取图片存放路径
String imgPath = rootPath + imgUrl;
ips = new FileInputStream(new File(imgPath));
String type = imgUrl.substring(imgUrl.indexOf(".") + 1);
if ("png".equals(type)) {
response.setContentType("image/png");
}
if ("jpeg".equals(type)) {
response.setContentType("image/jpeg");
}
out = response.getOutputStream();
//读取文件流
int len = 0;
byte[] buffer = new byte[1024 * 10];
while ((len = ips.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
out.close();
ips.close();
}
}
}