富文本
这里使用的是vue-quill-editor
组件作为富文本功能。
在VUE中使用vue-quill-editor
非常方便,引入组件就可以马上使用,但是通常情况下,我们都需要进行一些自定义的功能,所以需要自己进行二次封装。
初始化
<template>
<quillEditor
class="ql-toolbar"
v-model="content"
ref="quillEditor"
>
</quillEditor>
</template>
<script>
import { quillEditor } from 'vue-quill-editor';
export default {
components:[quillEditor],
props:{
value:{
type:String.
default:''
}
},
data() {
return {
content: '',
};
},
watch:{
value(v) {
this.content = v;
},
}
}
</script>
富文本初始化
封装了一个方法和配置,在页面初始化的时候调用即可。
const titleConfig = {
'ql-bold': '加粗',
'ql-color': '颜色',
'ql-font': '字体',
'ql-code': '插入代码',
'ql-italic': '斜体',
'ql-link': '添加链接',
'ql-background': '背景颜色',
'ql-size': '字体大小',
'ql-strike': '删除线',
'ql-script': '上标/下标',
'ql-underline': '下划线',
'ql-blockquote': '引用',
'ql-header': '标题',
'ql-indent': '缩进',
'ql-list': '列表',
'ql-align': '文本对齐',
'ql-direction': '文本方向',
'ql-code-block': '代码块',
'ql-formula': '公式',
'ql-image': '图片',
'ql-clean': '清除字体样式',
};
function addQuillTitle() {
const oToolBar = document.querySelector('.ql-toolbar');
const aButton = oToolBar.querySelectorAll('button');
const aSelect = oToolBar.querySelectorAll('select');
aButton.forEach((item) => {
if (item.className === 'ql-script') {
item.value === 'sub' ? (item.title = '下标') : (item.title = '上标');
} else if (item.className === 'ql-indent') {
item.value === '+1' ? (item.title = '向右缩进') : (item.title = '向左缩进');
} else {
item.title = titleConfig[item.classList[0]];
}
});
aSelect.forEach((item) => {
item.parentNode.title = titleConfig[item.classList[0]];
});
// 关闭单词检测提示
document.querySelector('.ql-editor').setAttribute('spellcheck', false);
return oToolBar;
}
<template>
<quillEditor
class="ql-toolbar"
v-model="content"
:options="editorOption"
ref="quillEditor"
>
</quillEditor>
</template>
// 使用computed
export default {
computed:{
editorOption() {
const editorOption = {
placeholder: this.placeholder,
};
editorOption.modules = !this.readOnly
? {
toolbar: [
'bold',
'underline',
'strike',
'blockquote',
'code-block',
'italic',
{ color: [] },
{ background: [] },
{ list: 'ordered' },
{ list: 'bullet' },
{ script: 'sub' },
{ script: 'super' },
{ indent: '-1' },
{ indent: '+1' },
'link',
'image',
'clean',
],
}
: {
toolbar: [],
};
return editorOption;
},
}
}
增加只读样式
为了方便后续仅展示的情况,我们需要增加一个只读样式
- 编辑模式
- 只读模式
代码预览:
<template>
<quillEditor
class="ql-toolbar"
:class="{
'hide-toolbar': readOnly,
}"
v-model="content"
:options="editorOption"
ref="quillEditor"
>
</quillEditor>
</template>
export default {
props: {
readOnly: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: '',
},
value: {
type: String,
defualt: '',
},
},
computed: {
editorOption() {
//..........//
},
quill() {
// 获取富文本的vue节点
return this.$refs.quillEditor.quill;
},
},
watch: {
value(v) {
/* */
},
readOnly(v) {
// 设置为不可编辑
this.quill.enable(!v);
},
},
}
// css 样式
<style lang="less">
.hide-toolbar {
.ql-toolbar {
height: 0;
padding: 0 !important;
border-top-color: transparent !important;
border: none !important;
}
.ql-container.ql-snow {
border: none;
}
.ql-editor {
background-color: #ffffff;
}
}
</style>
支持el-form-item的校验
我们从源码可以只读,el-inpt
这些表单元素在改变值的时候都会向上发送一个$EMIT
事件,为了兼容el-form-item
的校验。我们也需要加入这样的传播事件。
/// 广播事件
methods:{
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.componentName;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
console.log(parent);
if (parent) {
parent.$emit(...[eventName].concat(params));
}
},
}
我们添加change和bulr事件
中添加广播事件
watch: {
value(v) {
this.content = v;
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.change', [v]);
}
},
},
methods:{
handleBlur() {
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
}
},
}
错误样式提示
只需要增加一个.el-form-item.is-error
父类的选择器即可。
.el-form-item.is-error .ql-container,
.el-form-item.is-error .ql-toolbar {
border-color: #f56c6c;
}
完整代码
<template>
<quillEditor
class="ql-toolbar"
:class="{
'hide-toolbar': readOnly,
}"
v-model="content"
:options="editorOption"
ref="quillEditor"
@blur="handleBlur"
>
</quillEditor>
</template>
<script>
/* eslint-disable indent */
import { quillEditor } from 'vue-quill-editor';
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';
const titleConfig = {
'ql-bold': '加粗',
'ql-color': '颜色',
'ql-font': '字体',
'ql-code': '插入代码',
'ql-italic': '斜体',
'ql-link': '添加链接',
'ql-background': '背景颜色',
'ql-size': '字体大小',
'ql-strike': '删除线',
'ql-script': '上标/下标',
'ql-underline': '下划线',
'ql-blockquote': '引用',
'ql-header': '标题',
'ql-indent': '缩进',
'ql-list': '列表',
'ql-align': '文本对齐',
'ql-direction': '文本方向',
'ql-code-block': '代码块',
'ql-formula': '公式',
'ql-image': '图片',
'ql-clean': '清除字体样式',
};
function addQuillTitle() {
const oToolBar = document.querySelector('.ql-toolbar');
const aButton = oToolBar.querySelectorAll('button');
const aSelect = oToolBar.querySelectorAll('select');
aButton.forEach((item) => {
if (item.className === 'ql-script') {
item.value === 'sub' ? (item.title = '下标') : (item.title = '上标');
} else if (item.className === 'ql-indent') {
item.value === '+1' ? (item.title = '向右缩进') : (item.title = '向左缩进');
} else {
item.title = titleConfig[item.classList[0]];
}
});
aSelect.forEach((item) => {
item.parentNode.title = titleConfig[item.classList[0]];
});
// 关闭单词检测提示
document.querySelector('.ql-editor').setAttribute('spellcheck', false);
return oToolBar;
}
export default {
props: {
readOnly: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: '',
},
value: {
type: String,
defualt: '',
},
validateEvent: {
type: Boolean,
default: true,
},
},
components: {
quillEditor,
},
data() {
return {
content: '',
};
},
computed: {
editorOption() {
const editorOption = {
placeholder: this.placeholder,
};
editorOption.modules = !this.readOnly
? {
toolbar: [
'bold',
'underline',
'strike',
'blockquote',
'code-block',
'italic',
{ color: [] },
{ background: [] },
{ list: 'ordered' },
{ list: 'bullet' },
{ script: 'sub' },
{ script: 'super' },
{ indent: '-1' },
{ indent: '+1' },
'link',
'image',
'clean',
],
}
: {
toolbar: [],
};
return editorOption;
},
quill() {
return this.$refs.quillEditor.quill;
},
},
watch: {
value(v) {
this.content = v;
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.change', [v]);
}
},
readOnly(v) {
this.quill.enable(!v);
},
},
mounted() {
addQuillTitle();
this.initQuillEvent();
this.content = this.value;
},
methods: {
handleBlur() {
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
}
},
// 广播事件
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.componentName;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
console.log(parent);
if (parent) {
parent.$emit(...[eventName].concat(params));
}
},
initQuillEvent() {
this.quill.enable(!this.readOnly);
this.quill.on('text-change', (delta, oldDelta, source) => {
if (source === 'api') {
// console.log('An API call triggered this change.', delta);
} else if (source === 'user') {
// console.log('A user action triggered this change.', delta);
}
this.$emit('input', this.content);
});
},
},
};
</script>
<style lang="less">
.ql-container {
height: calc(~'100% - 40px');
// height: 100%;
}
.hide-toolbar {
.ql-toolbar {
height: 0;
padding: 0 !important;
border-top-color: transparent !important;
border: none !important;
}
.ql-container.ql-snow {
border: none;
}
.ql-editor {
background-color: #ffffff;
}
}
</style>