实现思路与核心逻辑
1. 组件设计思路
我们基于微信小程序原生editor
组件进行封装,主要实现以下功能:
-
工具栏配置:通过图标按钮提供格式控制
-
双向数据绑定:支持内容获取与设置
-
图片上传:集成图片选择与上传功能
-
格式控制:封装常用文本格式方法
2. 关键技术点
-
编辑器实例管理 :通过
createSelectorQuery
获取编辑器上下文 -
内容同步机制 :使用
getContents
和setContents
实现内容双向绑定 -
图片处理流程 :整合
chooseImage
和uploadFile
API实现图片上传 -
格式控制方法 :统一封装
format
方法处理各种文本格式 -
事件处理:正确处理小程序特有的事件命名规则(首字母大写)
完整实现代码
核心代码
TypeScript<!-- RichTextEditor.vue --> <template> <view :class="styles.editor_box"> <!-- 工具栏 --> <view :class="styles.tooltar" v-if="showToolbar"> <view v-for="(item, index) in ToolBarList" @tap="item.fnc()" :key="index"> <image :src="item.icon" :class="styles.img" /> </view> </view> <!-- 编辑器主体 --> <editor id="editor" :placeholder="placeholder" @ready="editorReady" @Input="handleEditorChange" :style="editorStyle" /> </view> </template> <script setup lang="ts"> import Taro from '@tarojs/taro'; import { ref, withDefaults } from 'vue'; import styles from './index.module.scss'; // 类型定义 type FormatType = 'bold' | 'italic' | 'header' | 'align' | 'list'; type FormatValue = 'h1' | 'h2' | 'h3' | 'left' | 'right' | 'ordered' | 'bullet'; interface EditorProps { showToolbar?: boolean; defaultContent?: string; placeholder?: string; } // 组件属性 const props = withDefaults(defineProps<EditorProps>(), { showToolbar: true, placeholder: '请输入内容', defaultContent: '' }); const emits = defineEmits(['editorChange']); // 编辑器实例与状态 const editorCtx = ref<any>(null); const isEditorReady = ref(false); const editorStyle = ref('color: black; padding: 12rpx'); // 工具栏配置 const ToolBarList = [ { fnc: insertImage, icon: '实际图标链接', name: '插入图片' }, { fnc: () => formatText('italic'), icon: '实际图标链接', name: '斜体' }, { fnc: () => formatText('bold'), icon: '实际图标链接', name: '加粗' }, { fnc: () => formatText('header', 'h1'), icon: '实际图标链接', name: '标题1' }, { fnc: () => formatText('header', 'h2'), icon: '实际图标链接', name: '标题2' }, { fnc: () => formatText('header', 'h3'), icon: '实际图标链接', name: '标题3' }, { fnc: () => formatText('align', 'left'), icon: '实际图标链接', name: '左对齐' }, { fnc: () => formatText('align', 'right'), icon: '实际图标链接', name: '右对齐' }, { fnc: () => formatText('list', 'ordered'), icon: '实际图标链接', name: '有序列表' }, { fnc: () => formatText('list', 'bullet'), icon: '实际图标链接', name: '无序列表' }, { fnc: undo, icon: '实际图标链接', name: '撤销' } ]; // 请求头配置 const header = { 'Content-Type': 'multipart/form-data', WEAPP_TOKEN_HEADER: xxxxxx, }; // 初始化编辑器 const editorReady = () => { Taro.createSelectorQuery() .select('#editor') .context((res) => { editorCtx.value = res.context; isEditorReady.value = true; if (props.defaultContent) { setEditorContent(props.defaultContent); } }) .exec(); }; // 设置编辑器内容 const setEditorContent = async (html: string) => { if (!editorCtx.value) return false; try { await editorCtx.value.setContents({ html }); handleEditorChange(); return true; } catch (err) { console.error('内容设置失败:', err); return false; } }; // 获取编辑器内容 const getEditorContent = () => { return new Promise((resolve) => { if (!editorCtx.value) return resolve(null); editorCtx.value.getContents({ success: (res) => resolve({ html: res.html, text: res.text }), fail: (err) => { console.error('内容获取失败:', err); resolve(null); } }); }); }; // 内容变化回调 const handleEditorChange = async () => { const content = await getEditorContent(); if (content) emits('editorChange', content); }; // 文本格式控制 const formatText = (type: FormatType, value?: FormatValue) => { if (editorCtx.value) editorCtx.value.format(type, value); }; // 撤销操作 const undo = () => { if (editorCtx.value) editorCtx.value.undo(); }; // 插入图片 const insertImage = () => { Taro.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: async (res) => { Taro.showLoading({ title: '上传中...' }); const filePath = res.tempFilePaths[0]; try { const uploadRes = await Taro.uploadFile({ url: `实际后台服务器地址`, filePath, header, name: 'file' }); const data = JSON.parse(uploadRes.data); if (data?.data?.error) { throw new Error(data.data.error); } if (editorCtx.value) { await editorCtx.value.insertImage({ src: data.data, width: '100%' }); Taro.showToast({ title: '插入成功', icon: 'success' }); } } catch (error) { Taro.showToast({ title: '上传失败', icon: 'error' }); console.error('图片上传失败:', error); } finally { Taro.hideLoading(); } } }); }; </script>
index.module.scss 样式文件
html.editor_box { width: 100%; height: auto; box-sizing: border-box; background: #F7F7F7; border-radius: 12rpx; } .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; } } }
2. 方法说明
方法名 说明 参数 setEditorContent
设置编辑器内容 html: string getEditorContent
获取编辑器内容 无 formatText
格式化文本 type: FormatType, value?: FormatValue insertImage
插入图片 无 undo
撤销操作 无 3. 注意事项
-
图片上传需要配置正确的服务器地址和认证信息
-
编辑器样式在小程序中只能使用内联样式
-
内容变化事件会频繁触发,建议在父组件中添加防抖处理
-
需要提前准备好工具栏所需的图标资源