最近接到一个小需求,要求对原项目的富文本编辑器进行统一化。由于之前没有具体的规范约束,该PC端项目中在不同模块中分别引入了3种不一样的富文本编辑器,一来不便于维护,二来样式不统一,用户使用也可能存在疑惑,于是我需要考虑重新封装一个富文本编辑器组件,再将旧代码中引入的富文本编辑器组件统一替换为新的。以下记录整个任务实现的大致思路。
首先,分析代码结构,发现项目中不同的模块分别使用了两种不同的form组件,element-UI和iView。
因此,需要对富文本编辑器组件最外层的form-item组件进行判断处理,这里使用了Vue内置的component动态组件,绑定is属性动态渲染对应的form-item组件。
<template>
<!-- 根据页面使用form是elementUI还是iView,外层使用对应的formItem组件(兼容旧代码有的用iView有的用elementUI的情况) -->
<!-- 初始化时先默认不渲染,等获取到dom节点后再渲染,解决控制台在created阶段报错-->
<component :is="wrapForm" v-if="isMounted" :rulesList="rulesList" :label="label" :lableWidth="labelWidth"
:required="required" :prop="prop">
<div class="fullEdit">
...
</div>
</component>
</template>
而判断条件为父组件中的dom元素是否包含类名“el-form”,是则渲染el-form-item组件,否则渲染iview的form-item组件。
created() {
this.$nextTick(()=>{
this.isElForm = document.querySelector('.mainCont').querySelector('.el-form') !== null
this.isMounted = true
...
})
},
computed: {
...
//渲染哪种外层form-item组件,elementUI或iView的form-item
wrapForm() {
return this.isElForm ? 'elFormItem' : 'FormItem'
},
},
富文本编辑器使用了wangEditor,具体参数参考官方文档配置:编辑器 API | wangEditor
<template>
<!-- 根据页面使用form是elementUI还是iView,外层使用对应的formItem组件(兼容旧代码有的用iView有的用elementUI的情况) -->
<!-- 初始化时先默认不渲染,等mounted后再渲染,解决控制台在created阶段报错-->
<component :is="wrapForm" v-if="isMounted" :rulesList="rulesList" :label="label" :labelWidth="labelWidth"
:required="required" :prop="prop || name">
<div class="fullEdit">
<div style="border: 1px solid #ccc;">
<Toolbar style="border-bottom: 1px solid #ccc"
:editor="pcEditor"
:defaultConfig="pcToolbarConfig"
:mode="mode"
/>
<Editor :ref="name"
v-model="formData[name]"
style="height: 400px; overflow-y: hidden;padding: 10px;"
:defaultConfig="pcEditorConfig"
:mode="mode"
@submit="submit"
@onChange="onEditorChange($event)"
@onCreated="onCreatedPC"
@onMaxLength="onMaxLength"
@onBlur="onBlur"
/>
</div>
<div v-if="isShowError" class="error-tip">{{ errorText }}</div>
</div>
</component>
</template>
<script>
import editorMixin from '@/mixins/editorMixin'
import {Editor, Toolbar} from '@wangeditor/editor-for-vue'
export default {
name: 'RichTextEditor',
components: {
Editor,
Toolbar
},
mixins: [editorMixin],
props: [
//表单数据对象
'formData',
//富文本编辑器内容在表单数据对象中的名称
'name',
//用于校验的prop,不填则默认为与name相同
'prop',
//标题标签宽度
'labelWidth',
//是否必填,是则需要执行校验
'required',
//标题标签名
'label',
//指定文件大小
'fileSize',
//父组件form校验规则,需修改父组件中该对象内富文本的校验方法
'rulesList',
//最大可输入字数
'length',
//错误提示,默认为"请填写xxx"
'errorMsg',
],
data() {
return {
//是否显示错误提示
isShowError: false,
//错误提示内容
errorText: '',
//父组件传入的整个form数据的校验规则
rules: {},
//判断外层form是否使用了elementUI的form组件
isElForm: false,
//是否完成挂载(用于判断外层使用的form组件并渲染本组件)
isMounted: false,
//初始化状态,此时不校验富文本框是否为空,直至父组件提交表单后再触发
isInitialized: true
}
},
computed: {
//最大文件大小,默认为10M
maxSize() {
return (this.fileSize || 10) * 1024 * 1024
},
//渲染哪种外层form-item组件,elementUI或iView的form-item
wrapForm() {
return this.isElForm ? 'elFormItem' : 'FormItem'
},
},
created() {
//nextTick中通过获取外层dom元素是否包含el-form类名判断本组件最外层渲染哪一种form-item组件
this.$nextTick(() => {
this.isElForm = document.querySelector('.mainCont').querySelector('.el-form') !== null
this.isMounted = true
//更新父组件rules
this.getNewRules()
})
},
methods: {
//更新父组件的rules(覆盖富文本校验的方法)
getNewRules() {
this.rules = this.rulesList
//如果父组件rules已设置富文本校验validator,则无需赋值
if (this.rulesList && this.name in this.rulesList && typeof this.rulesList[this.name].validator === 'function') {
this.isInitialized = false
return
}
if (!this.rulesList) this.rules = {}
this.rules[this.name] = {validator: this.judgeEditor, message: this.errorText}
this.$emit('update:rulesList', this.rules)
},
onEditorChange(e) {
if (this.required) {
if (e.isEmpty() && !this.isInitialized) {
this.isShowError = true
this.errorText = this.errorMsg || '请填写' + this.label.replace(/[::]/g, '')
} else {
this.isShowError = false
}
}
return this.isShowError
},
judgeEditor(rules, val, callback) {
//触发校验后再将isInitialized设置为false
this.isInitialized = false
// 富文本校验
const flag = this.onEditorChange(this.pcEditor)
if (flag) {
// callback(new Error())
} else {
callback()
}
},
submit(){
console.log('submit!')
}
}
}
</script>
<style lang="less" scoped>
.error-tip {
color: #ed4014;
font-size: 13px;
padding-top: 6px;
}
.wrapper{
border: 1px solid #dcdee2;
z-index: 9;
}
.warning {
border-color: #ed4014;
}
</style>
为了方便组件复用,减轻传递过多参数造成的心智负担,这里考虑将富文本内容的校验方法以缺省值的形式写在组件内部(judgeEditor方法),但由于需要配合父组件中form组件下的rules属性校验才能生效,就需要修改外部父组件中的值。
这里通过$emit(update:xxx) 和更新父组件绑定值的语法糖修饰符.sync的结合,实现对父组件绑定的校验规则对象rulesList的修改,使表单在submit前的校验时使用更新后的校验规则加入对富文本编辑器内容的校验。
methods: {
//更新父组件的rules(覆盖富文本校验的方法)
getNewRules() {
this.rules = this.rulesList
//如果父组件rules已设置富文本校验validator,则无需赋值
if (this.rulesList && this.name in this.rulesList && typeof this.rulesList[this.name].validator === 'function') {
this.isInitialized = false
return
}
if (!this.rulesList) this.rules = {}
this.rules[this.name] = {validator: this.judgeEditor, message: this.errorText}
this.$emit('update:rulesList', this.rules)
},
onEditorChange(e) {
if (this.required) {
if (e.isEmpty() && !this.isInitialized) {
this.isShowError = true
this.errorText = this.errorMsg || '请填写' + this.label.replace(/[::]/g, '')
} else {
this.isShowError = false
}
}
return this.isShowError
},
judgeEditor(rules, val, callback) {
//触发校验后再将isInitialized设置为false
this.isInitialized = false
// 富文本校验
const flag = this.onEditorChange(this.pcEditor)
if (flag) {
// callback(new Error())
} else {
callback()
}
}
}
<RichTextEditor :rulesList.sync="ruleValidate" :formData="orgIntroduceData" name="content" label="简介内容:" :length="6000" :required="true" />
最终效果