Uni-app + Vue3+editor富文本编辑器完整实现指南
前言
本文将完整展示如何在Uni-app框架下使用Vue3实现一个功能完善的富文本编辑器。我们将从组件结构、功能实现到样式设计,全面介绍这个编辑器的开发过程。
<template><view class="editor_box"><!-- 操作栏 --><view class="tooltar" v-if="showToolbar"><view v-for="(item, index) in ToolBarList" @tap="item.fnc()" :key="index"><image :src="item.icon" class="img" /></view></view><!-- 编辑器主体 --><editor:id="`${id}`":placeholder="'请输入内容'"@ready="editorReady"@input="handleEditorChange"style="color: black; padding: 12rpx"class="ql-container"/></view>
</template><script setup lang="ts">
import { ref, nextTick, getCurrentInstance } from "vue";// 组件属性定义
interface EditorProps {showToolbar?: boolean; // 是否显示工具栏defaultContent?: string; // 默认内容id?: string; // 编辑器ID
}// 格式化类型定义
type formatType = "bold" | "italic" | "header" | "align" | "list";
type formatVal = "h1" | "h2" | "h3" | "left" | "right" | "ordered" | "bullet";// 设置默认属性
const props = withDefaults(defineProps<EditorProps>(), {showToolbar: true,
});// 工具栏按钮配置
const ToolBarList = [{ fnc: () => insertImage(), icon: '图片路径' },{ fnc: () => formatStyle("italic"), icon: '图片路径' },{ fnc: () => formatStyle("bold"), icon: '图片路径' },{ fnc: () => formatStyle("header", "h1"), icon: '图片路径' },{ fnc: () => formatStyle("header", "h2"), icon: '图片路径' },{ fnc: () => formatStyle("header", "h3"), icon: '图片路径' },{ fnc: () => formatStyle("align", "left"), icon: '图片路径' },{ fnc: () => formatStyle("align", "right"), icon: '图片路径' },{ fnc: () => formatStyle("list", "ordered"), icon: '图片路径' },{ fnc: () => formatStyle("list", "bullet"), icon: '图片路径' },{ fnc: () => undo(), icon: '图片路径' },
];// 定义事件
const emits = defineEmits(["editorChange"]);// 响应式变量
const editorCtx = ref<any>(null); // 编辑器上下文
const isEditorReady = ref(false); // 编辑器是否准备就绪
const instance = getCurrentInstance() as any;// 编辑器准备就绪回调
const editorReady = () => {nextTick(() => {const query = uni.createSelectorQuery().in(instance?.proxy);query.select(`#${props?.id}`).context((res: any) => {if (res?.context) {editorCtx.value = res?.context;isEditorReady.value = true;if (props.defaultContent) {setEditorContent(props.defaultContent);}} else {console.error("获取编辑器上下文失败", res);}}).exec();});
};// 设置编辑器内容
const setEditorContent = (html: string) => {return new Promise((resolve) => {if (!editorCtx.value) return resolve(false);editorCtx.value.setContents({html,success: () => {console.log("内容回显成功");resolve(true);// 初始化,向父组件赋值setTimeout(() => {handleEditorChange();}, 500);},fail: (err: any) => {console.error("内容回显失败:", err);resolve(false);},});});
};// 撤销操作
const undo = () => {if (editorCtx.value) {editorCtx.value.undo();}
};// 格式化文本样式
const formatStyle = (type: formatType, val?: formatVal) => {editorCtx.value.format(type, val);
};// 插入图片
const insertImage = () => {uni.chooseImage({count: 1,sizeType: ["compressed"],sourceType: ["album", "camera"],success: (res) => {const filePath = res.tempFilePaths[0];// 方案1:直接使用本地路径(简单测试)if (editorCtx.value) {editorCtx.value.insertImage({src: filePath,width: "100%",success: () => {uni.showToast({ title: "插入成功", icon: "none" });},});}},fail: (err) => {console.log("选择图片失败", err);},});
};// 编辑器内容变化处理
const handleEditorChange = (e?: any) => {// 获取编辑器内容getEditorContent().then((content) => {// 向父组件传递内容emits("editorChange", content);});
};// 获取编辑器内容
const getEditorContent = () => {return new Promise((resolve) => {if (editorCtx.value) {editorCtx.value.getContents({success: (res: any) => {resolve({html: res?.html,text: res?.text,});},});}});
};
</script><style scoped lang="scss">
.editor_box {width: 100%;height: auto;box-sizing: border-box;background: #f7f7f7;border-radius: 12rpx;.ql-container {box-sizing: border-box;padding: 24rpx 32rpx;width: 100%;min-height: 30vh;max-height: 70vh;height: 100%;margin-top: 20rpx;overflow-y: scroll;line-height: 1.5;}
}.tooltar {display: flex;align-items: center;justify-content: space-between;box-sizing: border-box;padding: 10rpx 12rpx;border-bottom: 1rpx solid #ebebeb;view {.img {width: 24rpx;height: 24rpx;}}
}
</style>
关键功能解析
1. 编辑器初始化流程
组件挂载后触发
editorReady
事件使用
uni.createSelectorQuery()
获取编辑器实例//注意事项这是vue2写法 在vue3中不能哪来使用uni.createSelectorQuery().select('#editor').context((res) => { this.editorCtx = res.context }).exec()//因为是组件化封装,为了确保编译到微信小程序可以查询出来实例必须采用下方写法 const instance = getCurrentInstance() as any; const query = uni.createSelectorQuery().in(instance?.proxy); query.select(`#${props?.id}`).context((res: any) => { console.log("🚀 ~ editorReady ~ res:", res); if (res?.context) { editorCtx.value = res?.context; } else { console.error("获取编辑器上下文失败", res); } }).exec(); });
将编辑器上下文保存到
editorCtx
变量中如果提供了默认内容,调用
setEditorContent
进行内容回显
2. 工具栏功能实现
工具栏通过ToolBarList
数组配置,每个按钮包含:
图标资源
点击事件处理函数
支持的功能包括:
文本加粗/斜体
设置标题级别(H1/H2/H3)
文本对齐(左/右)
列表样式(有序/无序)
插入图片
撤销操作
3. 内容同步机制
编辑器内容变化时:
触发
@input
事件调用handleEditorChange
通过
getEditorContent
获取当前内容使用
emits
将内容传递给父组件
4. 图片插入实现
图片插入流程:
调用
uni.chooseImage
选择图片获取图片临时路径
使用
editorCtx.value.insertImage
插入图片设置图片宽度为100%自适应
使用说明
组件引入
<template><Editor :showToolbar="true" :defaultContent="content" @editorChange="onEditorChange"/>
</template><script setup>
import Editor from '@/components/Editor.vue';const content = ref('<p>正文内容</p>');const onEditorChange = (content) => {console.log('编辑器内容变化:', content);
};
</script>
总结
本文完整展示了基于Uni-app和Vue3的富文本编辑器实现方案。该编辑器具有以下特点:
功能完善:支持常见的文本编辑操作
响应式设计:适配不同尺寸屏幕
易于扩展:可以方便地添加新的工具栏功能
良好的交互体验:提供内容变化实时反馈
兼容h5和小程序平台(通过uni.createSelectorQuery().in(instance?.proxy)解决vue3版本组件化封装内部拿不到实例)
开发者可以根据实际需求,进一步扩展功能或调整样式,如添加更多格式化选项、实现图片上传到服务器等功能。