共计 7841 个字符,预计需要花费 20 分钟才能阅读完成。
引言
在 Web 开发中,富文本编辑器是一个常见的需求,特别是在内容管理系统(CMS)或博客平台中。本文将详细解析一个基于 Vue 3 和 @wangeditor/editor-for-vue
的富文本编辑器组件的实现,重点介绍其关键功能及其背后的设计思路。通过阅读本文,你将学会如何创建一个支持图片上传、双向数据绑定、异步图片加载的富文本编辑器组件。
组件概述
本文所述的 Vue 组件旨在提供一个可高度自定义的富文本编辑器。这个组件包含以下主要关键点:
- 富文本内容编辑
- 工具栏配置
- 自定义图片上传与插入
- 通过
v-model
实现双向数据绑定 - 异步加载并渲染图片
组件的核心功能实现
1. 管理编辑器实例:为什么选择 shallowRef
?
背景
在 Vue 3 中,ref
和 shallowRef
是用来创建响应式数据的工具。通常情况下,ref
会让它内部的所有对象都响应式地更新,但有时候我们并不需要对所有内部对象进行追踪。这时,shallowRef
就是一个更好的选择。
解释
ref
vs shallowRef
:
ref
:会递归地让内部所有对象都变成响应式,这对于简单数据类型很好用,但如果是复杂对象(例如编辑器实例),它可能会带来性能问题。shallowRef
:只会让最外层的引用是响应式的,内部对象不会变成响应式,这样可以避免不必要的性能开销。
在组件中的应用
在我们的富文本编辑器组件中,我们使用 shallowRef
来管理编辑器实例,这样我们只需要追踪编辑器实例的创建和销毁,而不用关心编辑器内部的复杂数据结构。
代码示例:
const editorRef = shallowRef(); // 只让 editorRef 是响应式的,而不是它内部的所有对象
const handleCreated = (editor) => {editorRef.value = editor; // 当编辑器实例创建时,将其保存到 editorRef};
2. 数据同步:理解 v-model
和 update
事件
背景
在 Vue 3 中,v-model
是用来实现双向数据绑定的工具,它允许父组件和子组件之间轻松地同步数据。我们可以通过自定义事件来更好地控制这个数据同步过程。
解释
v-model
的默认行为:
默认情况下,v-model
绑定的数据是modelValue
,它通过 update:modelValue
事件来同步数据。
自定义事件:
我们可以通过 defineEmits
来声明和触发自定义事件,例如 update:modelText
,用于同步其他类型的数据(如纯文本内容)。
在组件中的应用
在这个组件中,我们通过 v-model
实现了 HTML 内容的同步,同时通过 update:modelText
事件来同步编辑器中的纯文本内容。
代码示例:
const emit = defineEmits(['update:modelValue', 'update:modelText']); // 声明将会触发的事件
const htmlContent = ref('');
watch(() => props.modelValue, (val) => {if (val === htmlContent.value) return;
htmlContent.value = val;
nextTick(() => {emit('update:modelText', editorRef.value.getText().trim()); // 当内容变化时,同步文本内容
});
});
watch(htmlContent, (val) => {emit('update:modelValue', val); // 同步 HTML 内容
emit('update:modelText', editorRef.value.getText().trim()); // 同步纯文本内容
});
3. 处理模态框:如何自定义编辑器中的模态框样式?
背景
在富文本编辑器中,模态框(如图片上传、链接插入等)是常见的交互元素。为了让这些模态框符合我们的 UI 风格,我们通常需要自定义它们的样式和位置。
解释
- 模态框的样式控制:
- 我们可以监听编辑器的
modalOrPanelShow
事件,在模态框显示时,动态设置它的样式和位置,使其在屏幕上居中显示。
- 我们可以监听编辑器的
在组件中的应用
在编辑器实例创建后,我们通过监听 modalOrPanelShow
事件,自定义设置模态框的样式和位置。
代码示例:
const handleCreated = (editor) => {
editorRef.value = editor;
// 当模态框显示时,调整其样式
editorRef.value.on('modalOrPanelShow', (modalOrPanel) => {if (modalOrPanel.type !== 'modal') return;
const {$elem} = modalOrPanel;
const width = $elem.width();
const height = $elem.height();
$elem.css({
left: '50%',
top: '50%',
marginLeft: `-${width / 2}px`,
marginTop: `-${height / 2}px`,
zIndex: 1000,
position: 'fixed',
height: 'fit-content',
});
});
};
4. 图片上传与插入:如何自定义图片上传逻辑?
背景
图片上传是富文本编辑器中的重要功能。通常,我们需要自定义上传逻辑,以便将图片上传到服务器,并将服务器返回的图片信息插入到编辑器中。
解释
自定义图片上传:
MENU_CONF
是一个配置对象,用于自定义富文本编辑器中的菜单行为。这里的配置项['uploadImage']
针对的是图片上传功能,其中customUpload
是编辑器提供的方法,用于自定义图片上传的流程,当用户在编辑器中选择上传图片时,这个方法会被调用。- 这里的
file
参数表示用户选择上传的图片文件。
构建 FormData
对象:
FormData
是用于构建上传文件请求体的标准接口,它能够以键值对的形式存储文件和数据,用于文件上传。formData.append('file', file)
将图片文件添加到FormData
对象中,键名为file
。
异步上传文件:
- 使用
await upload(formData)
将构建好的FormData
对象发送给服务器进行文件上传。 upload
是自定义的一个 API 方法,用于将文件上传到服务器。上传完成后,服务器会返回一个响应数据(res
),其中包含了上传成功后的图片信息,如id
和sign
。
插入图片:
- 上传成功后,通过
insertFn
方法将图片插入到编辑器中。 insertFn
是编辑器提供的一个函数,用于在编辑器中插入图片或其他内容。通过调用insertFn
,你可以控制图片的插入行为。
insertFn
方法的参数含义
insertFn
是编辑器传递给 customUpload
方法的一个回调函数,用于在上传成功后将图片插入到编辑器中。它通常接受三个参数:
insertFn(url, altText, href)
url
(第一个参数):
- 图片的实际 URL 地址。通常情况下,这是图片在服务器上存储的地址,用于在编辑器中渲染图片。
- 在代码中,这个参数被传入一个空字符串
''
,意思是不直接使用 URL 来展示图片,而是通过后面的id
和sign
来渲染。
altText
(第二个参数):
- 图片的替代文本(alt text),当图片无法加载时显示的文字。
- 这里也是传入空字符串
''
,表示不设置替代文本。
href
(第三个参数):
- 图片的链接地址。通常用于点击图片时跳转的目标 URL。
- 在代码中,传入的是一个包含图片
id
和sign
的字符串。这是用于在渲染时通过id
和sign
来向服务器端请求图片资源。
在组件中的应用
在这个组件中,我们通过自定义 uploadImage
配置,来实现图片的上传和插入逻辑。
代码示例:
const editorConfig = {
MENU_CONF: {['uploadImage']: {async customUpload(file, insertFn) {const formData = new FormData();
formData.append('file', file);
const res = await upload(formData); // 上传图片到服务器
insertFn('','', `${res.data.id}&${res.data.sign}`); // 插入图片,将 id 和 sign 传入
},
},
},
};
5. 延迟加载图片:如何确保图片正确显示?
背景
在富文本编辑器中,图片可能不会直接以 URL 的形式插入,而是以某种标识符(如id
和 sign
)的形式存在。为了正确显示这些图片,我们需要在渲染时动态加载它们的实际 URL。
解释
参数检查:
handleImage
接受一个dom
参数,并首先检查这个参数是否为Element
类型。- 如果不是
Element
,则输出警告信息dom is not Element
,提示传入的参数不正确。
查找图片元素:
- 如果
dom
是有效的Element
,函数会调用dom.querySelectorAll('img')
,遍历所有图片(
)元素。 - 这些图片元素通常是从编辑器中插入的,它们可能还没有被正确渲染出来。
处理 data-href
属性:
- 对于每个图片元素,函数检查它的
data-href
属性。这个属性包含图片的标识信息,比如id
和sign
,它们通过&
符号连接。 data-href
属性在图片插入时被设置,用于标识图片的存储位置或其他元数据。
提取 id
和 sign
:
- 如果
data-href
属性存在,并且包含&
符号,函数会将其拆分成id
和sign
两部分。 id
和sign
是服务器识别和验证图片的关键信息。
获取图片 URL:
- 函数调用
getImage({id, sign})
,这是一个异步操作,会向服务器请求,通过id
和sign
获取图片的实际 URL。 getImage
函数返回一个 Promise,成功时会解析出图片的 URL。
设置图片 src
:
- 当 URL 获取成功后,函数将该 URL 设置为图片元素的
src
属性,使图片可以正确显示。
在组件中的应用
handleImage
函数遍历编辑器中的所有图片元素,根据 data-href
属性中的标识符,从服务器获取图片的实际 URL,并将其应用到图片元素上。
代码示例:
const handleImage = (dom) => {if (dom instanceof Element) {dom.querySelectorAll('img').forEach((element) => {const href = element.getAttribute('data-href');
if (href && href.includes('&')) {const [id, sign] = href.split('&');
if (id && sign) {getImage({ id, sign}).then((url) => {element.src = url; // 设置图片的 src 为实际 URL});
}
}
});
} else {console.warn('dom is not Element');
}
};
完整组件代码
最后,让我们把所有的关键点结合起来,看看这个富文本编辑器组件的完整实现:
结语
通过对该 Vue 组件的深入解析,我们看到了它如何利用 Vue 3 的特性和 @wangeditor/editor-for-vue
库实现一个功能丰富的富文本编辑器。组件采用了 shallowRef
来管理编辑器实例,使用 v-model
机制和自定义事件实现了双向数据绑定,并通过异步操作和事件监听实现了图片上传和展示的自定义处理。
这种实现方式不仅满足了基本的文本编辑需求,还提供了高度的可定制性,适用于各种复杂的 Web 应用场景。如果你正在开发一个需要富文本编辑器的项目,可以参考本文的实现思路,结合实际需求进行扩展和优化。
原文地址: Vue 3 富文本编辑器组件详解