首页 > 编程语言 >微信小程序:富文本编辑器组件

微信小程序:富文本编辑器组件

时间:2023-08-03 10:45:15浏览次数:39  
标签:文本编辑 console MM 微信 zero value 编辑器 res 组件

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

地址:https://blog.csdn.net/XH_jing/article/details/115509316

demo源码: https://github.com/jxh1997/Editor ,所以源代码均在 Github 上,下载即可使用。

我个人在demo源码的基础上稍微进行了微调。

官方文档:

editor(富文本编辑器,可以对图片、文字进行编辑):
https://developers.weixin.qq.com/miniprogram/dev/component/editor.html

rich-text(富文本):
https://developers.weixin.qq.com/miniprogram/dev/component/rich-text.html

一、组件定义

组件的目录结构如下:

richText.js

const supportDateFormat = ['YY-MM', 'YY.MM.DD', 'YY-MM-DD', 'YY/MM/DD', 'YY.MM.DD HH:MM', 'YY/MM/DD HH:MM', 'YY-MM-DD HH:MM']; //支持的日期格式
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    //编辑器是否只读
    readOnly: {
      type: Boolean,
      value: false
    },
    //编辑器默认提示语
    placeholder: {
      type: String,
      value: '开始编辑吧...'
    },
    //插入的日期格式
    formatDate: {
      type: String,
      value: 'YY/MM/DD'
    },
    buttonTxt: {
      type: String,
      value: '保存'
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    formats: {}, //样式集合
    textTool: false, //文本工具是否显示,默认隐藏
  },

  /**
   * 组件的方法列表
   */
  methods: {
    //富文本工具点击事件
    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()来代替。
      // 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');
    },

    /**
     * 修改样式,样式item点击事件
     * @param {String} name 样式名称 
     * @param {String} value 样式值
     */
    format(res) {
      let {
        name,
        value
      } = res.target.dataset;
      if (!name) return;
      this.editorCtx.format(name, value);
    },

    // 通过 Context 方法改变编辑器内样式时触发,返回选区已设置的样式
    onStatusChange(res) {
      const formats = res.detail;
      console.log('onStatusChange=>',res)
      this.setData({
        formats
      })
    },

    //在光标位置插入下换线
    insertDivider() {
      this.editorCtx.insertDivider({
        success: res => {
          console.log('[insert divider success]', res)
        }
      })
    },

    //清空编辑器内容
    clear() {
      this.editorCtx.clear({
        success: res => {
          this.triggerEvent('clearSuccess');
        }
      })
    },

    //清空编辑器内容前的事件
    clearBeforeEvent() {
      this.triggerEvent('clearBeforeEvent');
    },

    //清除当前选区的样式
    removeFormat() {
      this.editorCtx.removeFormat();
    },

    //插入日期
    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
      })
    },

    //插入图片事件
    insertImageEvent() {
      //触发父组件选择图片方法
      this.triggerEvent('insertImageEvent', {});
    },

    /**
     * 插入图片方法
     * @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);
          }
        })
      })
    },

    //保存按钮事件,获取编辑器内容
    getEditorContent() {
      this.editorCtx.getContents({
        success: res => {
          // console.log('[getContents rich text success]', res)
          this.triggerEvent('getEditorContent', {
            value: res,
          });
        }
      })
    },

    //show文本工具栏
    showTextTool() {
      this.setData({
        textTool: !this.data.textTool
      })
    },

    //编辑器聚焦时触发
    bindfocus(res) {
      this.triggerEvent('bindfocus', {
        value: res,
      });
    },
    //编辑器失去焦点时触发
    bindblur(res) {
      this.triggerEvent('bindblur', {
        value: res,
      });
    },

    //编辑器输入中时触发
    bindinput(res) {
      this.triggerEvent('bindinput', {
        value: 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);
      }
    }
  },

  /**
   * 组件生命周期函数-在组件布局完成后执行)
   */
  ready() {

  },
})

richText.json

{
    "component": true,
    "usingComponents": {}
  }

richText.wxml

<view class="whole" id="richText">
  <view style="height:{{textTool?'200':'30'}}rpx;"></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 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>

richText.wxss

/* components/richText/richText.wxss */
@import "./assets/iconfont.wxss";
page {
  background: #f8f8f8;
}
.page-body{
  padding-bottom: 10rpx;
}
.editor-toolbar {
  /* position: fixed;
  top: 0;
  left: 0; */
  width: 100%;
  z-index: 9999;
  margin-bottom: 30rpx;
}

.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;
}

二、组件使用

index.js

const app = getApp();
let richText = null;  //富文本编辑器实例
Page({

  /**
   * 页面的初始数据
   */
  data: {
    readOnly: false, //编辑器是否只读
    placeholder: '开始编辑吧...',
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onl oad(options) {
    
  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  // 编辑器初始化完成时触发,可以获取组件实例
  onEditorReady() {
    console.log('[onEditorReady callback]')
    richText = this.selectComponent('#richText'); //获取组件实例
  },

  //设置富文本内容
  setContents(rechtext) {
    this.editorCtx.setContents({
      html: rechtext,
      success: res => {
        console.log('[setContents success]', res)
      }
    })
  },

  //撤销
  undo() {
    console.log('[undo callback]')
  },

  //恢复
  restore() {
    console.log('[restore callback]')
  },

  //清空编辑器内容
  clear() {
    this.editorCtx.clear({
      success: res => {
        console.log("[clear success]", res)
      }
    })
  },

  //清空编辑器事件
  clearBeforeEvent(){
    console.log('[clearBeforeEvent callback]')
    wx.showModal({
      cancelText: '取消',
      confirmText: '确认',
      content: '确认清空编辑器内容吗?',
      success: (result) => {
        if(result.confirm){
          richText.clear();
        }
      },
      fail: (res) => {},
    })
  },

  //清空编辑器成功回调
  clearSuccess(){
    console.log('[clearSuccess callback]')
  },

  //清除当前选区的样式
  removeFormat() {
    this.editorCtx.removeFormat();
  },

  //插入图片
  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)
  },

  //show文本工具栏
  showTextTool() {
    this.setData({
      textTool: !this.data.textTool
    })
  },

  //编辑器聚焦时触发
  bindfocus(res) {
    let {
      value
    } = res.detail;
    // console.log('[bindfocus callback]=>', value)
  },

  //编辑器失去焦点时触发
  bindblur(res) {
    let {
      value
    } = res.detail;
    // console.log('[bindblur callback]=>', value)
  },

  //编辑器输入中时触发
  bindinput(res) {
    let {
      value
    } = res.detail;
    // console.log('[bindinput callback]=>', value)
    app.data.richTextContents = value.detail.html;
  },

  //预览富文本
  preview(){
    wx.navigateTo({
      url: `../preview/preview`,
    })
  }
})

index.wxml

<view class="row3" style="height: 700rpx">
                <richText 
                class="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="preview" bindtap="preview">预览</view>
            </view>

index.json

{
  "usingComponents": {
    "richText":"../../components/richText/richText"
  }
}

index.wxss

.preview{
    width: 20%;
    height: 80rpx;
    line-height: 80rpx;
    margin: 0 auto;
    margin-bottom: 50rpx;
    text-align: center;
    font-size:26rpx;
    color: #ffffff;
    background-color: #bf98d2;
    float: right;
  }

效果如下:

添加一张图片

 

三、预览页面

preview.js

const app = getApp();
Page({

  /**
   * 页面的初始数据
   */
  data: {
   
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onl oad: function (options) {
    
  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  onEditorReady() {
    wx.createSelectorQuery().select('#editor').context(res => {
      this.editorCtx = res.context;
      this.editorCtx.setContents({
        html: app.globalData.richTextContents,
        success: res => {
          console.log('[setContents success]')
        }
      })
    }).exec()
  }
})

preview.json

{
    "usingComponents": {},
    "navigationBarTitleText": "预览富文本"
  }

preview.wxml

<view style="padding:20rpx;">
    <editor id="editor" style="height: 1200rpx;" read-only bindready="onEditorReady"></editor>
</view>

preview.wxss

.editor {
    box-sizing: border-box;
    padding: 12px 15px;
    width: 100%;
    min-height: 10vh;
    height: auto;
    background: #fff;
    font-size: 24rpx;
    line-height: 1.5;
  }

预览效果如下:

 

标签:文本编辑,console,MM,微信,zero,value,编辑器,res,组件
From: https://www.cnblogs.com/zwh0910/p/17602635.html

相关文章

  • less中更改iview组件默认样式
    1.先声明变量'deep'@deep:~">>>";2.使用该变量去修改ivew的样式@{deep}.ivu-modal-body//与类之间要有空格完整代码:@deep:~">>>";#qualityControlIssueList{height:100%;.textRight{text-align:right}.searchForm{......
  • 前端vue uni-app自定义精美海报生成组件
    在当前技术飞速发展的时代,软件开发的复杂度也在不断提高。传统的开发方式往往将一个系统做成整块应用,一个小的改动或者一个小功能的增加都可能引起整体逻辑的修改,从而造成牵一发而动全身的情况。为了解决这个问题,组件化开发逐渐成为了一种趋势。通过组件化开发,我们可以实现单独开......
  • 客服如何通过微信接收消息通知-唯一客服文档中心
    当我们在自己网站上嵌入对接了客服代码,我们想要通过微信接收访客的消息提醒通知,可以通过扫描客服后台的微信二维码,即时收消息通知提醒。我们网站地址:gofly.v1kf.com客服后台后台主页面板,就展示了一个微信二维码,扫码关注公众号,就能将客服账号与微信公众号进行绑定,通过微信公众号......
  • 11-微服务技术组件
    一、使用Nacos实现集中式配置管理(一)配置中心模型​在微服务架构中,存在着多环境、多服务、多实例(集群化)的情况,那么就需要将一些配置信息集中的放在一个地方做统一管理,这就是配置中心的原型。​对于配置中心来说,要保证其隔离性、一致性、安全性和易管理性,隔离性......
  • 微信小程序学习笔记(完结)
    本笔记是小程序学习笔记,主要记录小程序の基础知识说明本笔记为观看慕课网微信小程序入门与实战-全新版、尚硅谷2021版微信小程序开发(零基础小程序开发入门到精通)这两个教学视频、并参照官方的微信开放文档记录整理而成两个教学视频主要就是是面向初学者......
  • JavaScript中介者模式:解耦组件之间的依赖关系
    JavaScript中介者模式在前端开发中,组件之间的依赖关系往往会导致代码的复杂性和可维护性降低。为了解决这个问题,我们可以使用中介者模式来解耦组件之间的依赖关系。本文将介绍JavaScript中介者模式的概念和使用方法,并通过一个实例来说明其应用。什么是中介者模式?中介者模式是一......
  • 微信小程序6 常用标签之 input,基础样式
    inputinput标签不做任何设置的时候,就是个输入框,需要注意的是默认没有样式,这跟html不同。<input></input> 我输入了内容,但是可以看到没有边框样式。 type属性1.text,就是默认的type属性值,输入框;2.password,密码框;3.number,只能输入数字,但是要在移动端才能看......
  • 微信支付回调
    在微信支付的回调中,常用参数列举如下:$resultArray['out_trade_no']:商户订单号。$resultArray['transaction_id']:订单号。$resultArray['amount']['total']:订单金额。$resultArray['mch_id']:商户号,即微信支付分配的商户号。$resultArray['appid']:公众账号ID,......
  • uniapp微信小程序实现记录退出程序功能
    uniapp微信小程序实现记录退出程序功能获取用户在申请过程中退出小程序时的路径,进而知道客户在哪个时间哪个结点离开的程序。我可以可以分析用户退出原因,或者告知用户在申请过程中还差几步就能结束了。记录地方在uniapp的app.vue文件。在此的onhide生命周期中,通过getCurrentPages()......
  • TOMCAT功能及组件简介
      一、功能     servlet是ORACLE公司为了让WEB应用程序与WEB服务器程序之间进行交互协作而制定的一个接口。协作示意图如下:  这个接口规定:WEB应用中需要被WEB服务器动态调用的程序位于Servlet接口的实现类中;WEB服务器可以访问一个WEB应用中所有实现了Servlet接口......