本文将主要讲解一下如果通过微信小程序来实现封装一个富文本编辑器 Editor,可拿来即用。
一、官方文档:
editor(富文本编辑器,可以对图片、文字进行编辑):
https://developers.weixin.qq.com/miniprogram/dev/component/editor.html
rich-text(富文本):
https://developers.weixin.qq.com/miniprogram/dev/component/rich-text.html
二、效果图:
三、代码详解:
1. 编辑器界面设计
richTest.wxml
<view class="whole" id="richText">
<view style="height:{{textTool?'200':'100'}}rpx;"></view>
<view class="page-body">
<view class='wrapper'>
<editor
id="editor"
class="ql-container"
placeholder="{{placeholder}}"
showImgSize
showImgToolbar
showImgResize
bindstatuschange="onStatusChange"
read-only="{{readOnly}}"
bindready="onEditorReady"
bindfocus='bindfocus'
bindblur='bindblur'
bindinput='bindinput'
>
</editor>
</view>
</view>
<view class="editor-toolbar" bindtap="format">
<view class="toolbar-2">
<view class="tool-item-cell">
<view class="tool-item-box">
<view class="cell-rg-shadow"></view>
<scroll-view
scroll-x
class="flex-sb"
style="height:70rpx;white-space: nowrap;"
>
<view class="tool-item">
<i class="iconfont icon-charutupian" data-tool_name='insertImage' bindtap="toolEvent"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-font" data-tool_name='showTextTool' bindtap="toolEvent"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-format-header-1 {{formats.header === 1 ? 'ql-active' : ''}}" data-tool_name='text_H1' data-name="header" data-value="{{1}}" bindtap="toolEvent"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-date" data-tool_name='insertDate' bindtap="toolEvent"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-undo" data-tool_name='undo' bindtap="toolEvent"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-redo" data-tool_name='redo' bindtap="toolEvent"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-shanchu" data-tool_name='clear' bindtap="toolEvent"></i>
</view>
</scroll-view>
</view>
</view>
<lable
class='save-icon'
style='background:{{appColorConfig.check_color}}'
bindtap="getEditorContent"
>
{{buttonTxt}}
</lable>
</view>
<view class="toolbar-1" wx:if="{{textTool}}">
<scroll-view
scroll-x
style="height:70rpx;white-space: nowrap;"
>
<view class="tool-item">
<i class="iconfont icon-zitijiacu {{formats.bold ? 'ql-active' : ''}}" data-name="bold"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-zitixieti {{formats.italic ? 'ql-active' : ''}}" data-name="italic"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-zitixiahuaxian {{formats.underline ? 'ql-active' : ''}}" data-name="underline"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-fengexian" bindtap='insertDivider'></i>
</view>
<view class="tool-item">
<i class="iconfont icon-zuoduiqi {{formats.align === 'left' ? 'ql-active' : ''}}" data-name="align"
data-value="left"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-juzhongduiqi {{formats.align === 'center' ? 'ql-active' : ''}}" data-name="align"
data-value="center"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-youduiqi {{formats.align === 'right' ? 'ql-active' : ''}}" data-name="align"
data-value="right"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-zuoyouduiqi {{formats.align === 'justify' ? 'ql-active' : ''}}" data-name="align" data-value="justify"></i>
</view>
<view class="tool-item">
<i class="iconfont icon--checklist" data-name="list" data-value="check"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-youxupailie {{formats.list === 'ordered' ? 'ql-active' : ''}}" data-name="list" data-value="ordered"></i>
</view>
<view class="tool-item">
<i class="iconfont icon-wuxupailie {{formats.list === 'bullet' ? 'ql-active' : ''}}" data-name="list"
data-value="bullet"></i>
</view>
</scroll-view>
</view>
</view>
</view>
2. 编辑器界面样式
richTest.wxss
@import "./assets/iconfont.wxss";
page {
background: #f8f8f8;
}
.page-body{
padding-bottom: 100rpx;
}
.editor-toolbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 9999;
}
.editor-toolbar i {
display: flex;
align-items: center;
justify-content: center;
}
.toolbar-1 {
padding: 5rpx 0;
background: #e4e4e4;
}
.editor-toolbar .tool-item {
display: inline-block;
}
.toolbar-2 {
padding: 5rpx 20px 5rpx 10px;
background: #f4f4f4;
display: flex;
align-items: center;
justify-content:space-between;
position: relative;
}
.toolbar-2 .tool-item-cell{
max-width: 80%;
}
.toolbar-2 .tool-item-box{
position: relative;
}
.toolbar-2 .cell-rg-shadow{
position: absolute;
right: 0;
top: 0;
width: 1px;
height: 100%;
z-index: 999;
background:#dddddd;
}
.iconfont {
display: inline-block;
padding: 8px 8px;
width: 24px;
height: 24px;
cursor: pointer;
font-size: 20px;
}
.toolbar {
box-sizing: border-box;
border-bottom: 0;
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
}
.ql-container {
box-sizing: border-box;
padding: 12px 15px;
width: 100%;
min-height: 30vh;
height: auto;
background: #fff;
font-size: 16px;
line-height: 1.5;
}
.ql-active {
color: #06c;
}
.save-icon {
padding: 15rpx 30rpx;
font-size: 20rpx;
background: #bf98d2;
color: #fff;
}
.flex{
display: flex;
}
.flex-cc{
display: flex;
align-items: center;
-ms-flex-item-align: center;
justify-content: center;
}
.flex-sb{
display: flex;
align-items: center;
-ms-flex-item-align: center;
justify-content: space-between;
}
.flex-sa{
display: flex;
align-items: center;
-ms-flex-item-align: center;
justify-content: space-around;
}
3. 编辑器业务逻辑
richTest.js
-
富文本工具栏点击事件
toolEvent(res) { let { tool_name } = res.currentTarget.dataset; switch (tool_name) { case 'insertImage': // 插入图片 this.insertImageEvent(); break; case 'showTextTool': // 展示文字编辑工具 this.showTextTool(); break; case 'insertDate': // 插入日期 this.insertDate(); break; case 'undo': // 撤退(向前) this.undo(); break; case 'redo': // 撤退(向后) this.restore(); break; case 'clear': // 清除 this.clearBeforeEvent(); break; } },
-
编辑器初始化完成时触发
onEditorReady() { console.log('编辑器初始化完成时触发') this.triggerEvent('onEditorReady'); // 返回一个 SelectorQuery 对象实例。在自定义组件或包含自定义组件的页面中,应使用this.createSelectorQuery()来代替。 // 对应API:https://developers.weixin.qq.com/miniprogram/dev/api/wxml/wx.createSelectorQuery.html this.createSelectorQuery().select('#editor').context(res => { console.log('createSelectorQuery=>', res) this.editorCtx = res.context; let rtTxt = ''; this.setContents(rtTxt); // 设置富文本内容 }).exec(); },
-
事件方法
// 设置富文本内容 setContents(rechtext) { this.editorCtx.setContents({ html: rechtext, success: res => { console.log('[setContents success]', res) } }) }, // 撤销 undo() { this.editorCtx.undo(); this.triggerEvent('undo'); }, // 恢复 restore() { this.editorCtx.redo(); this.triggerEvent('restore'); }, // 清空编辑器内容 clear() { this.editorCtx.clear({ success: res => { this.triggerEvent('clearSuccess'); } }) }, //清空编辑器内容前的事件 clearBeforeEvent() { this.triggerEvent('clearBeforeEvent'); }, //清除当前选区的样式 removeFormat() { this.editorCtx.removeFormat(); }, //插入图片事件 insertImageEvent() { //触发父组件选择图片方法 this.triggerEvent('insertImageEvent', {}); }, //插入日期 insertDate() { if (supportDateFormat.indexOf(this.data.formatDate) < 0) { console.error(`Format Date ${this.data.formatDate} error \n It should be one of them [${supportDateFormat}]`) return; } let formatDate = this.getThisDate(this.data.formatDate); this.editorCtx.insertText({ text: formatDate }) }, //show展示文本工具栏 showTextTool() { this.setData({ textTool: !this.data.textTool }) }, //保存按钮事件,获取编辑器内容 getEditorContent() { this.editorCtx.getContents({ success: res => { // console.log('[getContents rich text success]', res) this.triggerEvent('getEditorContent', { value: res, }); } }) },
-
编辑器事件
//编辑器聚焦时触发 bindfocus(res) { this.triggerEvent('bindfocus', { value: res, }); }, //编辑器失去焦点时触发 bindblur(res) { this.triggerEvent('bindblur', { value: res, }); }, //编辑器输入中时触发 bindinput(res) { this.triggerEvent('bindinput', { value: res, }); },
-
插入图片方法
/** * 插入图片方法 * @param {String} path 图片地址,仅支持 http(s)、base64、云图片(2.8.0)、临时文件(2.8.3) */ insertImageMethod(path) { return new Promise((resolve, reject) => { this.editorCtx.insertImage({ src: path, data: { id: 'imgage', }, success: res => { resolve(res); }, fail: res => { reject(res); } }) }) },
-
返回当前日期的方法
/** * 返回当前日期 * @format {String} 需要返回的日期格式 */ getThisDate(format) { let date = new Date(), year = date.getFullYear(), month = date.getMonth() + 1, day = date.getDate(), h = date.getHours(), m = date.getMinutes(); //数值补0方法 const zero = (value) => { if (value < 10) return '0' + value; return value; } switch (format) { case 'YY-MM': return year + '-' + zero(month); case 'YY.MM.DD': return year + '.' + zero(month) + '.' + zero(day); case 'YY-MM-DD': return year + '-' + zero(month) + '-' + zero(day); case 'YY.MM.DD HH:MM': return year + '.' + zero(month) + '.' + zero(day) + ' ' + zero(h) + ':' + zero(m); case 'YY/MM/DD HH:MM': return year + '/' + zero(month) + '/' + zero(day) + ' ' + zero(h) + ':' + zero(m); case 'YY-MM-DD HH:MM': return year + '-' + zero(month) + '-' + zero(day) + ' ' + zero(h) + ':' + zero(m); default: return year + '/' + zero(month) + '/' + zero(day); } }
4. 接受富文本编辑器相关事件
index.js
-
编辑器初始化完成时触发,可以获取组件实例
onEditorReady() { console.log('[onEditorReady callback]') richText = this.selectComponent('#richText'); //获取组件实例 },
-
功能点实现
//设置富文本内容 setContents(rechtext) { this.editorCtx.setContents({ html: rechtext, success: res => { console.log('[setContents success]', res) } }) }, //插入图片 insertImageEvent() { wx.chooseImage({ count: 1, success: res => { let path = res.tempFilePaths[0]; //调用子组件方法,图片应先上传再插入,不然预览时无法查看图片。 richText.insertImageMethod(path).then(res => { console.log('[insert image success callback]=>', res) }).catch(res => { console.log('[insert image fail callback]=>', res) }); } }) }, //保存,获取编辑器内容 getEditorContent(res) { let { value } = res.detail; wx.showToast({ title: '获取编辑器内容成功', icon: 'none', }) console.log('[getEditorContent callback]=>', value) },
5. 使用富文本编辑器组件
index.wxml
<richText
id='richText'
readOnly='{{readOnly}}'
placeholder='{{placeholder}}'
formatDate='YY/MM/DD'
buttonTxt='保存'
bind:clearBeforeEvent='clearBeforeEvent'
bind:clearSuccess='clearSuccess'
bind:undo='undo'
bind:restore='restore'
bind:onEditorReady='onEditorReady'
bind:bindfocus='bindfocus'
bind:bindblur='bindblur'
bind:bindinput='bindinput'
bind:insertImageEvent='insertImageEvent'
bind:getEditorContent='getEditorContent'></richText>
<view class="tip">备注:
<view>1.改变图片大小,按住节点一小会儿再拖动。</view>
<view>2.预览内容中,图片仅支持网络url。</view>
</view>
<view class="preview" bindtap="preview">预览</view>
四、demo源码:
欢迎 Star GitHub: https://github.com/jxh1997/Editor 。
所以源代码均在 Github 上,下载即可使用。
1. 下载源码
git clone https://github.com/jxh1997/Editor.git
2. 使用说明
-
在下载源码中找到组件目录:
components/richText
,将 richText 整个文件夹复制到你的项目中。 -
在
page.json
中引入组件"usingComponents": { "richText":"../../components/richText/richText" },
-
在
page.wxml
中使用组件<richText id='richText' readOnly='{{readOnly}}' placeholder='{{placeholder}}' formatDate='YY/MM/DD' buttonTxt='保存' bind:clearBeforeEvent='clearBeforeEvent' bind:clearSuccess='clearSuccess' bind:undo='undo' bind:restore='restore' bind:onEditorReady='onEditorReady' bind:bindfocus='bindfocus' bind:bindblur='bindblur' bind:bindinput='bindinput' bind:insertImageEvent='insertImageEvent' bind:getEditorContent='getEditorContent' > </richText>
-
在
page.js
中,接受富文本编辑器相关事件。具体执行代码如上。