主文件
<template>
<div style="width: 100%; height: 100%;">
<!-- 回显-->
<div style="width: 100%; height: 100%;" class="ql-editor" v-if="disabled" v-html="content">{{ content }}</div>
<!-- 编辑-->
<quill-editor
v-else
v-model="content"
:options="options"
ref="quillEditor"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@ready="onEditorReady($event)"
@change="onEditorChange($event)"
/>
<div style="float:right; margin: 10px 0;" v-if="!disabled">
{{currentLength + ' / ' + max}}
</div>
</div>
</template>
<script>
import ResourcesService from "../../../util/ResourcesService";
const resourcesServer = new ResourcesService()
import {quillEditor} from "vue-quill-editor";
import Quill from "quill";
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
// 引入表格(必须是2.0.0的版本,现在还是dev版没有正式上线)
// 踩了很多坑,最后没显示出来,排查了半天,发现是z-index值太低了,wangEditor好用些
// npm install quill ^2.0.0-dev.4
// npm install quill-better-table
import QuillBetterTable from "quill-better-table"
import "quill-better-table/dist/quill-better-table.css"
Quill.register({'modules/better-table': QuillBetterTable}, true)
// 工具栏提示,参考博客https://www.cnblogs.com/meiyanstar/p/13346355.html
const titleConfig=[
{Choice:'.ql-bold',title:'加粗'},
{Choice:'.ql-italic',title:'斜体'},
{Choice:'.ql-underline',title:'下划线'},
{Choice:'.ql-header',title:'段落格式'},
{Choice:'.ql-strike',title:'删除线'},
{Choice:'.ql-blockquote',title:'块引用'},
{Choice:'.ql-code',title:'插入代码'},
{Choice:'.ql-code-block',title:'插入代码段'},
{Choice:'.ql-font',title:'字体'},
{Choice:'.ql-size',title:'字体大小'},
{Choice:'.ql-list[value="ordered"]',title:'编号列表'},
{Choice:'.ql-list[value="bullet"]',title:'项目列表'},
{Choice:'.ql-direction',title:'文本方向'},
{Choice:'.ql-header[value="1"]',title:'h1'},
{Choice:'.ql-header[value="2"]',title:'h2'},
{Choice:'.ql-align',title:'对齐方式'},
{Choice:'.ql-color',title:'字体颜色'},
{Choice:'.ql-background',title:'背景颜色'},
{Choice:'.ql-image',title:'图像'},
{Choice:'.ql-video',title:'视频'},
{Choice:'.ql-link',title:'添加链接(请先选中文字)'},
{Choice:'.ql-formula',title:'插入公式'},
{Choice:'.ql-clean',title:'清除字体格式'},
{Choice:'.ql-table',title:'表格'},
{Choice:'.ql-script[value="sub"]',title:'下标'},
{Choice:'.ql-script[value="super"]',title:'上标'},
{Choice:'.ql-indent[value="-1"]',title:'向左缩进'},
{Choice:'.ql-indent[value="+1"]',title:'向右缩进'},
{Choice:'.ql-header .ql-picker-label',title:'标题大小'},
{Choice:'.ql-header .ql-picker-item[data-value="1"]',title:'标题一'},
{Choice:'.ql-header .ql-picker-item[data-value="2"]',title:'标题二'},
{Choice:'.ql-header .ql-picker-item[data-value="3"]',title:'标题三'},
{Choice:'.ql-header .ql-picker-item[data-value="4"]',title:'标题四'},
{Choice:'.ql-header .ql-picker-item[data-value="5"]',title:'标题五'},
{Choice:'.ql-header .ql-picker-item[data-value="6"]',title:'标题六'},
{Choice:'.ql-header .ql-picker-item:last-child',title:'标准'},
{Choice:'.ql-size .ql-picker-item[data-value="small"]',title:'小号'},
{Choice:'.ql-size .ql-picker-item[data-value="large"]',title:'大号'},
{Choice:'.ql-size .ql-picker-item[data-value="huge"]',title:'超大号'},
{Choice:'.ql-size .ql-picker-item:nth-child(2)',title:'标准'},
{Choice:'.ql-align .ql-picker-item:first-child',title:'居左对齐'},
{Choice:'.ql-align .ql-picker-item[data-value="center"]',title:'居中对齐'},
{Choice:'.ql-align .ql-picker-item[data-value="right"]',title:'居右对齐'},
{Choice:'.ql-align .ql-picker-item[data-value="justify"]',title:'两端对齐'}
];
// 自定义字体(需要先全局引入css样式)
let fontFamily = ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial', 'pingfang'];
Quill.imports['formats/font'].whitelist = fontFamily;
Quill.register(Quill.imports['formats/font'])
// 自定义字体大小(需要先全局引入css样式)
let fontSize = ['10px', '12px', '14px', '16px', '20px', '24px', '26px', '30px', '36px']
Quill.imports['attributors/style/size'].whitelist = fontSize;
Quill.register(Quill.imports['attributors/style/size']);
// 上传图片的地址
import apiUrl from "../../../api/apiUrl";
const baseURL = apiUrl.baseAddr
/*富文本编辑图片上传配置*/
const uploadConfig = {
action: '/api/files/v1/upEdit', // 必填参数 图片上传地址
methods: 'POST', // 必填参数 图片上传方式
token: sessionStorage.getItem('token'), // 可选参数 如果需要token验证,假设你的token有存放在sessionStorage
name: 'file', // 必填参数 文件的参数名
size: 10240, // 可选参数 图片大小,单位为Kb, 1M = 1024Kb
accept: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon', // 可选 可上传的图片格式
}
// quill方法配置
const handlers = {
// 图片
image: function image() {
var self = this
var fileInput = this.container.querySelector(
'input.ql-image[type=file]'
)
if (fileInput === null) {
fileInput = document.createElement('input')
fileInput.setAttribute('type', 'file')
// 设置图片参数名
if (uploadConfig.name) fileInput.setAttribute('name', uploadConfig.name)
// 可设置上传图片的格式
fileInput.setAttribute('accept', uploadConfig.accept)
fileInput.classList.add('ql-image')
// 监听选择文件
fileInput.addEventListener('change', async function () {
let res = await resourcesServer.uploadFile('med-main-svc', 'C006', 1, fileInput.files[0])
if (res.code === '0000') {
let length = self.quill.getSelection(true).index
//这里很重要,你图片上传成功后,img的src需要在这里添加,res.path就是你服务器返回的图片链接。
const img = res.list[0]
self.quill.insertEmbed(length, 'image', baseURL + '/res-svc/res/v3/download?fileId=' + img.fileId + '&applicationName=med-main-svc&businessTypeCode=C006&free=1')
self.quill.setSelection(length + 1)
fileInput.value = ''
}
})
this.container.appendChild(fileInput)
}
fileInput.click()
},
// 表
table: function () {
this.quill.getModule('better-table').insertTable(3, 4)
},
}
// quill工具栏展示工具配置
const container = [
[{ 'font': fontFamily }], //字体
[{ 'size': fontSize }], // 字体大小
['bold', 'italic', 'underline', 'strike'], //加粗,斜体,下划线,删除线
// ['blockquote', 'code-block'], //引用,代码块
// [{ 'header': 1 }, { 'header': 2 }], // 标题,键值对的形式;1、2表示字体大小
[{ 'list': 'ordered'}, { 'list': 'bullet' }], //列表
[{ 'script': 'sub'}, { 'script': 'super' }], // 上下标
[{ 'indent': '-1'}, { 'indent': '+1' }], // 缩进
// [{ 'direction': 'rtl' }], // 文本方向
[{ 'header': [1, 2, 3, 4, 5, 6, false] }], //几级标题
[{ 'color': [] }, { 'background': [] }], // 字体颜色,字体背景颜色
[{ 'align': [] }], //对齐方式
['clean'], //清除字体样式
['image'], //上传图片、上传视频
['table'] // 表格
]
// better-table配置
const betterTable = {
// 右键菜单
operationMenu: {
items: {
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', '#558ff2', '#595959', '#9fd9c5'],
text: '背景色:'
}
}
}
export default {
// quill-editor二次封装
name: "quillEditorPackage",
props: {
// 禁用
disabled: {
type: Boolean,
default: false
},
// 字数限制
max: {
type: Number | String,
default: 10000
}
},
data() {
return {
content: '',
lastContent: '', // 用于限制字数
currentLength: '0', // 当前字数
// quill配置
options: {
placeholder: '请在这里输入',
modules:{
toolbar: {
container: container,
handlers: handlers,
},
table: false,
'better-table': betterTable,
keyboard: {
bindings: QuillBetterTable.keyboardBindings
}
},
theme:'snow'
}
}
},
components: { quillEditor },
mounted(){
// 调用函数出现工具栏提示(不调用则没有)
this.autotip()
},
methods: {
// 失去焦点
onEditorBlur(e){
// console.log('quill失去焦点', e)
},
// 获得焦点
onEditorFocus(e){
// console.log('quill获得焦点', e)
},
// 准备好
onEditorReady(e){
// console.log('quill准备好', e)
},
// 改变
onEditorChange(e){
const allText = e.text.replace('\n', "")
this.currentLength = allText.length
if(this.currentLength > Number(this.max)) {
this.$nextTick(() => {
this.content = this.lastContent
})
this.$message({
type:'error',
message: '超出最大字数'
})
} else {
this.lastContent = this.content
}
},
// 回显
backShow(content) {
if(this.disabled){
this.content = content
return
}
// 回显编辑 本来直接双向绑定改变this.content就可以了,增加了表格组件以后就报错 this.quill.pasteHTML(newVal) is not a function
const editor = this.$refs.quillEditor.quill
let delta = editor.clipboard.convert({html: content});
editor.setContents(delta);
this.content = content
},
autotip(){
document.getElementsByClassName('ql-editor')[0].dataset.placeholder=''
for(let item of titleConfig){
let tip = document.querySelector('.quill-editor '+ item.Choice)
if (!tip) continue
tip.setAttribute('title',item.title)
}
}
}
}
</script>
<style scoped lang="less">
</style>
css配置 -- QuillFont字体配置
/*
* quillEditor 字体配置
*/
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="SimSun"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="SimSun"]::before {
content: "宋体";
font-family: "SimSun";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="SimHei"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="SimHei"]::before {
content: "黑体";
font-family: "SimHei";
}
.ql-snow
.ql-picker.ql-font
.ql-picker-label[data-value="Microsoft-YaHei"]::before,
.ql-snow
.ql-picker.ql-font
.ql-picker-item[data-value="Microsoft-YaHei"]::before {
content: "微软雅黑";
font-family: "Microsoft YaHei";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="KaiTi"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="KaiTi"]::before {
content: "楷体";
font-family: "KaiTi";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="FangSong"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="FangSong"]::before {
content: "仿宋";
font-family: "FangSong";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="Arial"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="Arial"]::before {
content: "Arial";
font-family: "Arial";
}
.ql-snow
.ql-picker.ql-font
.ql-picker-label[data-value="Times-New-Roman"]::before,
.ql-snow
.ql-picker.ql-font
.ql-picker-item[data-value="Times-New-Roman"]::before {
content: "Times New Roman";
font-family: "Times New Roman";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="sans-serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="sans-serif"]::before {
content: "sans-serif";
font-family: "sans-serif";
}
.ql-font-SimSun {
font-family: "SimSun";
}
.ql-font-SimHei {
font-family: "SimHei";
}
.ql-font-Microsoft-YaHei {
font-family: "Microsoft YaHei";
}
.ql-font-KaiTi {
font-family: "KaiTi";
}
.ql-font-FangSong {
font-family: "FangSong";
}
.ql-font-Arial {
font-family: "Arial";
}
.ql-font-Times-New-Roman {
font-family: "Times New Roman";
}
.ql-font-sans-serif {
font-family: "sans-serif";
}
css配置 -- QuillFontSize字体可选大小配置
/*
* quillEditor 字体大小配置
*/
/* 默认字号 */
.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="14px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="14px"]::before {
content: "14px";
font-size: 14px;
}
.ql-size-14px {
font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="10px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="10px"]::before {
content: "10px";
font-size: 10px;
}
.ql-size-12px {
font-size: 12px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="12px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="12px"]::before {
content: "12px";
font-size: 12px;
}
.ql-size-12px {
font-size: 12px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before {
content: "16px";
font-size: 16px;
}
.ql-size-16px {
font-size: 16px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="18px"]::before {
content: "18px";
font-size: 18px;
}
.ql-size-18px {
font-size: 18px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before {
content: "20px";
font-size: 20px;
}
.ql-size-20px {
font-size: 20px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="22px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="22px"]::before {
content: "22px";
font-size: 22px;
}
.ql-size-22px {
font-size: 22px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="24px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="24px"]::before {
content: "24px";
font-size: 24px;
}
.ql-size-24px {
font-size: 24px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="26px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="26px"]::before {
content: "26px";
font-size: 26px;
}
.ql-size-26px {
font-size: 26px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="28px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="28px"]::before {
content: "28px";
font-size: 28px;
}
.ql-size-28px {
font-size: 28px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="30px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="30px"]::before {
content: "30px";
font-size: 30px;
}
.ql-size-30px {
font-size: 30px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="36px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="36px"]::before {
content: "36px";
font-size: 36px;
}
.ql-size-36px {
font-size: 36px;
}
css配置 -- QuillTitle标题配置
/*
* quillEditor 几级标题设置
*/
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本" !important;
}
.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" !important;
}
.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" !important;
}
.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" !important;
}
.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" !important;
}
.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" !important;
}
.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" !important;
}