微信小程序之实现封装一个富文本编辑器 Editor 的完整流程【附demo源码】欢迎点赞收藏

本文将主要讲解一下如果通过微信小程序来实现封装一个富文本编辑器 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

  1. 富文本工具栏点击事件

    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;
      }
    },
    
  2. 编辑器初始化完成时触发

    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();
    },
    
  3. 事件方法

    // 设置富文本内容
    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,
          });
        }
      })
    },
    
  4. 编辑器事件

    //编辑器聚焦时触发
    bindfocus(res) {
      this.triggerEvent('bindfocus', {
        value: res,
      });
    },
    
    //编辑器失去焦点时触发
    bindblur(res) {
      this.triggerEvent('bindblur', {
        value: res,
      });
    },
    
    //编辑器输入中时触发
    bindinput(res) {
      this.triggerEvent('bindinput', {
        value: res,
      });
    },
    
  5. 插入图片方法

     /**
     * 插入图片方法
     * @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);
          }
        })
      })
    },
    
  6. 返回当前日期的方法

    /**
     * 返回当前日期
     * @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

  1. 编辑器初始化完成时触发,可以获取组件实例

      onEditorReady() {
        console.log('[onEditorReady callback]')
        richText = this.selectComponent('#richText'); //获取组件实例
      },
    
  2. 功能点实现

    //设置富文本内容
    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. 使用说明
  1. 在下载源码中找到组件目录:components/richText,将 richText 整个文件夹复制到你的项目中。

  2. page.json 中引入组件

    "usingComponents": {
        "richText":"../../components/richText/richText"
    },
    
  3. 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>
    
  4. page.js 中,接受富文本编辑器相关事件。具体执行代码如上。


如果以上内容对你的学习工作有帮助,欢迎点赞收藏,如果觉得 Github 上的代码还不错,欢迎前往 Star

  • 5
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值