整理笔记时发现的之前做项目用到的富文本编辑器quillEditor
- 效果
- Index.vue
<template>
<r-t-editor
full-button
:disabled="false"
:show-content="content"
@change="getContent"
></r-t-editor>
</template>
<script>
data() {
return {
content: '回显数据',
}
},
methods: {
getContent({ html, text }) {
console.log(html, text)
}
}
</script>
- RTEditor.vue
<!--
富文本编辑器组件
@author: zhangxt
@date: 2021/07/23
@description: 基于quillEditor封装的富文本编辑器
官方文档:https://www.kancloud.cn/liuwave/quill/1409423
参考:https://blog.youkuaiyun.com/liuqiao0327/article/details/107126784/
全屏:https://www.cnblogs.com/huihuihero/p/14084621.html
-->
<template>
<div class="r-t-editor" id="RTEditor">
<div class="full-screen" v-show="fullButton">
<span :title="isFullscreen ? '退出全屏' : '全屏'" @click="toggleFull()">
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" />
</span>
</div>
<!--手动控制数据同步-->
<quill-editor
ref="quillEditor"
class="editor"
:content="content"
:options="option"
@change="onEditorChange($event)"
/>
<!--双向数据绑定-->
<!-- <quill-editor-->
<!-- ref="quillEditor"-->
<!-- class="editor"-->
<!-- v-model="content"-->
<!-- :options="option"-->
<!-- @ready="onEditorReady($event)"-->
<!-- @focus="onEditorFocus($event)"-->
<!-- @blur="onEditorBlur($event)"-->
<!-- />-->
<form action method="post" enctype="multipart/form-data" id="uploadFormMulti">
<input
style="display: none"
:id="uniqueId"
type="file"
name="file"
multiple
accept="image/jpg, image/jpeg, image/png, image/gif"
@change="uploadImg('uploadFormMulti')"
/>
</form>
</div>
</template>
<script>
import { quillEditor } from 'vue-quill-editor'
import screenfull from 'screenfull'
import { uploadAttach } from '@/api/file'
// 工具栏配置
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线 -----['bold', 'italic', 'underline', 'strike']
['blockquote', 'code-block'], // 引用 代码块-----['blockquote', 'code-block']
[{ header: 1 }, { header: 2 }], // 1、2 级标题-----[{ header: 1 }, { header: 2 }]
[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表-----[{ list: 'ordered' }, { list: 'bullet' }]
[{ script: 'sub' }, { script: 'super' }], // 上标/下标-----[{ script: 'sub' }, { script: 'super' }]
[{ indent: '-1' }, { indent: '+1' }], // 缩进-----[{ indent: '-1' }, { indent: '+1' }]
[{ direction: 'rtl' }], // 文本方向-----[{'direction': 'rtl'}]
[{ size: ['small', false, 'large', 'huge'] }], // 字体大小-----[{ size: ['small', false, 'large', 'huge'] }]
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题-----[{ header: [1, 2, 3, 4, 5, 6, false] }]
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色-----[{ color: [] }, { background: [] }]
[{ font: [] }], // 字体种类-----[{ font: [] }]
[{ align: [] }], // 对齐方式-----[{ align: [] }]
['clean'], // 清除文本格式-----['clean']
['link', 'image'] // 链接、图片、视频-----['link', 'image', 'video']
]
export default {
name: 'RTEditor',
components: { quillEditor },
props: {
// 回显内容
showContent: {
type: String,
default: ''
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 是否显示全屏按钮
fullButton: {
type: Boolean,
default: true
}
},
data() {
return {
isFullscreen: false, //是否全屏显示
content: '',
uniqueId: 'uniqueId', //上传图片节点Id
//富文本编辑器配置,详见: https://www.kancloud.cn/liuwave/quill/1409366#bounds_52
option: {
bounds: 'document.body', //可以设置为DOM元素或代表DOM元素的CSS选择器,用来包含编辑器的UI元素(比如tooltips等等),当前只考虑左右边界。
debug: 'warn', //debug的快捷方式。注意,debug是一个静态方法并且可能影响当前页面内Quill编辑器实例。默认只显示警告和错误信息。
formats: '', //编辑器允许的格式列表白名单。完整的列表,请见Formats。
//需要引入模块的集合及它们各自的选项。详情见See Modules。
modules: {
toolbar: toolbarOptions
},
placeholder: '请输入正文', //当编辑器为空时显示的占位符文字。
readOnly: true, // 是否将编辑器实例设置为只读模式,
// 当默认的'ql-editor'元素已经被自定义CSS改变,可以设置为DOM元素或代表DOM元素的CSS选择器,指定有滚动条(overflow-y: auto)的容器。
// 当Quill被设置成自适应高度,需要修复这个滚动时跳动的bug,从而让另一个祖先容器负责滚动。
scrollingContainer: null,
strict: true, //在semver的严格解释下,一些改进或修改被要求一个大版本的bump。它们通过strict严格标志来防止对Quill版本号进行小的更改。可以在
theme: 'snow' //使用主题的名称。这个内置的选项是"bubble"或"snow"。一个无效或错误的值将加载默认的最小化主题。注意,主题的样式表任需要手动包含。详情,请见主题
}
}
},
computed: {
//当前富文本实例
editor() {
return this.$refs.quillEditor.quill
}
},
mounted() {
this.init()
this.initFull()
},
beforeDestroy() {
this.destroyFull()
},
methods: {
init() {
this.content = this.showContent
if (this.disabled) {
this.editor.enable(false)
}
let _this = this
let imgHandler = async function(image) {
if (image) {
let fileInput = document.getElementById(_this.uniqueId) //隐藏的file文本ID
fileInput.click() //加一个触发事件
}
}
_this.editor.getModule('toolbar').addHandler('image', imgHandler)
},
//失去焦点事件
// onEditorBlur(quill) {
// console.log('editor blur!', quill)
// },
// // 准备富文本编辑器
// onEditorReady(quill) {
// console.log('editor ready!', quill)
// },
// //获得焦点事件
// onEditorFocus(quill) {
// console.log('editor focus!', quill)
// },
//内容改变事件
onEditorChange({ quill, html, text }) {
console.log('editor change!', quill, html, text)
this.content = html
this.$emit('change', { html, text })
},
toggleFull() {
if (!screenfull.enabled) {
this.$message({
message: '你的浏览器不支持该功能!',
type: 'warning'
})
return false
}
const $id = document.getElementById('RTEditor')
screenfull.toggle($id)
},
changeFull() {
this.isFullscreen = screenfull.isFullscreen
},
initFull() {
if (screenfull.enabled) {
screenfull.on('change', this.changeFull)
}
},
destroyFull() {
if (screenfull.enabled) {
screenfull.off('change', this.changeFull)
}
},
async uploadImg() {
let _this = this
// 构造formData对象
let formData = new FormData()
formData.append('file', document.getElementById(_this.uniqueId).files[0])
try {
//调用上传文件接口
let queryParam = {
module: 'editor',
publicRead: true,
type: 1 // 1---图片 、 2---附件
}
uploadAttach(formData, queryParam).then(res => {
//返回上传文件的地址
let url = res.data.url
if (url != null && url.length > 0) {
let range = _this.editor.getSelection()
url = url.indexOf('http') !== -1 ? url : 'http:' + url
//上传文件成功之后在富文本中回显(显示)
_this.editor.insertEmbed(range != null ? range.index : 0, 'image', url)
} else {
_this.$message.warning('图片上传失败')
}
//成功之后,将文件的文本框的value置空
document.getElementById(_this.uniqueId).value = ''
})
} catch ({ message: msg }) {
document.getElementById(_this.uniqueId).value = ''
_this.$message.warning(msg)
}
}
}
}
</script>
<style lang="scss">
.r-t-editor {
width: 100%;
padding: 20px;
&:fullscreen {
background: #ffffff;
overflow: auto;
padding: 0;
.ql-editor {
height: calc(100vh - 80px);
}
}
.full-screen {
width: 100%;
text-align: right;
border: 1px solid #ccc;
border-bottom: 0;
padding: 5px 10px;
color: #444;
}
.editor {
line-height: normal !important;
}
}
.ql-snow .ql-editor {
min-height: 200px;
}
.ql-snow .ql-tooltip {
position: static;
}
.ql-snow .ql-tooltip[data-mode='link']::before {
content: '请输入链接地址:';
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0;
content: '保存';
padding-right: 0;
}
.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: '等宽字体';
}
</style>