小程序富文本编辑器组件

 效果图只是功能的一部分

话不多说,先上代码

html部分:

<page-container show="{{showPage}}" z-index="9999999" round="{{round}}" overlay="{{overlay}}" duration="{{duration}}" position="{{position}}" close-on-slide-down="{{false}}" bindbeforeenter="onBeforeEnter" bindenter="onEnter" bindafterenter="onAfterEnter" bindbeforeleave="onBeforeLeave" bindleave="onLeave" bindafterleave="onAfterLeave" bindclickoverlay="onClickOverlay" custom-style="{{customStyle}}" overlay-style="{{overlayStyle}}">


    <view class="detail-page">
        <mp-navigation-bar back="{{false}}" title="文本编辑" ext-class="navigation" background="{{background}}">
            <view slot="left">
                <view bindtap="exit" style="width: 100rpx;height:60rpx;display: flex;justify-content: left;align-items: center;">
                    <van-icon name="arrow-left'" color="#333" size="40rpx" />
                </view>
            </view>

        </mp-navigation-bar>
        <view class="editor_toolbox">
            <i class="iconfont icon-undo" data-method="undo" bindtap="edit" />
            <i class="iconfont icon-redo" data-method="redo" bindtap="edit" />
            <i class="iconfont icon-img" data-method="insertImg" bindtap="edit" />
            <i class="iconfont icon-video" data-method="insertVideo" bindtap="edit" />
            <i class="iconfont icon-link" data-method="insertLink" bindtap="edit" />
            <i class="iconfont icon-text" data-method="insertText" bindtap="edit" />
            <!-- <i class="iconfont icon-text" data-method="insertTable" bindtap="edit" /> -->
            <i class="iconfont icon-clear" bindtap="clear" />
            <i class="iconfont icon-save" bindtap="save" />
        </view>

        <view>
            <mp-html id="article" container-style="padding:20px" tag-style="{{tagStyle}}" content="{{html}}" domain="https://mp-html.oss-cn-hangzhou.aliyuncs.com" editable="{{editable}}" bindremove="remove"></mp-html>
        </view>

        <block wx:if="{{modal}}">
            <view class="mask" />
            <view class="modal">
                <view class="modal_title">{{modal.title}}</view>
                <input class="modal_input" value="{{modal.value}}" maxlength="-1" auto-focus bindinput="modalInput" />
                <view class="modal_foot">
                    <view class="modal_button" bindtap="modalCancel">取消</view>
                    <view class="modal_button" style="color:#576b95;border-left:1px solid rgba(0,0,0,.1)" bindtap="modalConfirm">确定</view>
                </view>
            </view>
        </block>
    </view>

    <van-dialog use-slot title="插入表格" show="{{ show }}" show-cancel-button bind:close="onClose" bind:confirm="getUserInfo">

        <view style="display: flex;justify-content: center;flex-direction: column;align-items: center;">
            <view class="between" style="margin-top: 24rpx;">
                行:{{ row }}
                <van-stepper model:value="{{ row }}" mark:type="row" bind:change="onChange" />
            </view>
            <view class="between" style="margin-top: 24rpx;">
                列:{{ column }}
                <van-stepper model:value="{{ column }}" mark:type="column" bind:change="onChange" />
            </view>
        </view>
    </van-dialog>
</page-container>

css部分

.editor_toolbox {
  background-color: #ededed;
  display: flex;
  padding: 5px;
  box-sizing: border-box;
  line-height: 1.6;
}

.detail-page {
  width: 100%;
  height: 100%;
  position: fixed;
  z-index: 999999999999;
}

@font-face {
  font-family: "iconfont";
  src: url("data:application/x-font-woff2;charset=utf-8;base64,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")
    format("woff2");
}

.iconfont {
  flex: 1;
  text-align: center;
  font-family: "iconfont" !important;
  font-size: 22px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-undo:before {
  content: "\e607";
}

.icon-redo:before {
  content: "\e606";
}

.icon-img:before {
  content: "\e6e2";
}

.icon-video:before {
  content: "\e798";
}

.icon-link:before {
  content: "\e60d";
}

.icon-text:before {
  content: "\e6ce";
}

.icon-clear:before {
  content: "\e637";
}

.icon-save:before {
  content: "\e501";
}

/* 模态框 */
.modal {
  position: fixed;
  top: 50%;
  left: 16px;
  right: 16px;
  background-color: #fff;
  border-radius: 12px;
  transform: translateY(-50%);
}

.modal_title {
  padding: 32px 24px 16px;
  font-size: 17px;
  font-weight: 700;
  text-align: center;
}

.modal_input {
  display: block;
  padding: 5px;
  margin: 0 24px 32px 24px;
  font-size: 14px;
  border: 1px solid #dfe2e5;
}

.modal_foot {
  display: flex;
  line-height: 56px;
  font-weight: 700;
  border-top: 1px solid rgba(0, 0, 0, 0.1);
}

.modal_button {
  flex: 1;
  text-align: center;
}

/* 蒙版 */
.mask {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: black;
  opacity: 0.5;
}

js 部分

const content = require('./content')
const Shop = require("../../libs/api/shop");
// 上传图片方法
function upload(src, type) {
    return new Promise((resolve, reject) => {
        console.log('上传', type === 'img' ? '图片' : '视频', ':', src)

        // 实际使用时,上传到服务器
        wx.uploadFile({
            url: 'https://zlhb.wifimsl.cn/group/upload/image', // 接口地址
            filePath: src,
            name: 'image',
            success(res) {
                const { data } = JSON.parse(res.data)
                resolve(data) // 返回线上地址
            },
            fail: reject
        })

    })
}
// 删除图片方法
function remove(src) {
    console.log('删除图片:', src)
    // 实际使用时,删除线上资源
}
Component({
    /**
 * 组件的属性列表
 */
    properties: {
        title: String,
        showPage: {
            type: Boolean,
            value: false,
        },
        html: {
            type: String,
            value: "",
        },
        naigation: {
            type: String,
            value: "文本编辑",
        },
    },
    data: {
        duration: 300,
        position: 'right',
        round: false,
        overlay: true,
        customStyle: '',
        overlayStyle: '',
        show: false,
        tagStyle: { img: 'width:100% !important;' },

        html: '',
        content,
        editable: true,
        column: 1,
        row: 1,
    },
    ready() {

        this.ctx = this.selectComponent('#article')
        /**
         * @description 设置获取链接的方法
         * @param {String} type 链接的类型(img/video/audio/link)
         * @param {String} value 修改链接时,这里会传入旧值
         * @returns {Promise} 返回线上地址(2.2.0 版本起设置了 domain 属性时,可以缺省主域名)
         *   type 为 audio/video 时,可以返回一个源地址数组
         *   2.1.3 版本起 type 为 audio 时,可以返回一个 object,包含 src、name、author、poster 等字段
         *   2.2.0 版本起 type 为 img 时,可以返回一个源地址数组,表示插入多张图片(修改链接时仅限一张)
         */
        this.ctx.getSrc = (type, value) => {
            return new Promise((resolve, reject) => {
                if (type === 'img' || type === 'video') {
                    wx.showActionSheet({
                        itemList: ['本地选取', '远程链接'],
                        success: res => {
                            if (res.tapIndex == 0) {
                                // 本地选取
                                if (type === 'img') {
                                    wx.chooseImage({
                                        count: value === undefined ? 9 : 1, // 2.2.0 版本起插入图片时支持多张(修改图片链接时仅限一张)
                                        success: res => {
                                            if (res.tempFilePaths.length == 1 && wx.editImage) {
                                                // 单张图片时进行编辑
                                                wx.editImage({
                                                    src: res.tempFilePaths[0],
                                                    complete: res2 => {
                                                        wx.showLoading({
                                                            title: '上传中'
                                                        })
                                                        upload(res2.tempFilePath || res.tempFilePaths[0], type).then(res => {
                                                            wx.hideLoading()
                                                            resolve(res)
                                                        })
                                                    }
                                                })
                                            } else {
                                                // 否则批量上传
                                                wx.showLoading({
                                                    title: '上传中'
                                                });
                                                (async () => {
                                                    const arr = []
                                                    for (let item of res.tempFilePaths) {
                                                        // 依次上传
                                                        const src = await upload(item, type)
                                                        arr.push(src)
                                                    }
                                                    return arr
                                                })().then(res => {
                                                    wx.hideLoading()
                                                    resolve(res)
                                                })
                                            }
                                        },
                                        fail: reject
                                    })
                                } else {
                                    wx.chooseVideo({
                                        success: res => {
                                            wx.showLoading({
                                                title: '上传中'
                                            })
                                            upload(res.tempFilePath, type).then(res => {
                                                wx.hideLoading()
                                                resolve(res)
                                            })
                                        },
                                        fail: reject
                                    })
                                }
                            } else {
                                // 远程链接
                                this.callback = {
                                    resolve,
                                    reject
                                }
                                this.setData({
                                    modal: {
                                        title: (type === 'img' ? '图片' : '视频') + '链接',
                                        value
                                    }
                                })
                            }
                        }
                    })
                } else {
                    this.callback = {
                        resolve,
                        reject
                    }
                    let title
                    if (type === 'audio') {
                        title = '音频链接'
                    } else if (type === 'link') {
                        title = '链接地址'
                    }
                    this.setData({
                        modal: {
                            title,
                            value
                        }
                    })
                }
            })
        }



    },
    /**
 * 组件的方法列表
 */
    methods: {
        getUserInfo(event) {
            console.log(event);
            const text = 'insertTable'
            const { row, column } = this.data
            this.ctx[text](row, column)
        },
        onChange(e) {
            const { type } = e.mark
            this.setData({
                [type]: e.detail
            })
        },

        onClose() {
            this.setData({ show: false });
        },
        // 删除图片/视频/音频标签事件
        remove(e) {
            // 删除线上资源
            remove(e.detail.src)
        },
        // 处理模态框
        modalInput(e) {
            this.value = e.detail.value
        },
        modalConfirm() {
            this.callback.resolve(this.value || this.data.modal.value || '')
            this.setData({
                modal: null
            })
        },
        modalCancel() {
            this.callback.reject()
            this.setData({
                modal: null
            })
        },
        // 调用编辑器接口
        edit(e) {
            const text = e.currentTarget.dataset.method
            if (text == 'insertTable') {
                this.setData({ show: true });
                // wx.showModal({
                //     title: '',
                //     content: `<view>测试</ view>`,
                //     complete: (res) => {
                //         if (res.cancel) {

                //         }

                //         if (res.confirm) {
                //             this.ctx[text]()
                //         }
                //     }
                // })

            } else {
                this.ctx[text]()
            }

            console.log(this.ctx);
        },
        // 清空编辑器内容
        clear() {
            wx.showModal({
                title: '确认',
                content: '确定清空内容吗?',
                success: res => {
                    if (res.confirm)
                        this.ctx.clear()
                }
            })
        },
        // 保存编辑器内容
        save() {
            const that = this
            // 避免无法获取到正在编辑的文本内容
            setTimeout(() => {
                var content = this.ctx.getContent()
                wx.showModal({
                    title: '保存',
                    content,
                    confirmText: '完成',
                    success: res => {
                        if (res.confirm) {
                            // 实际使用时,这里需要上传到服务器
                            this.setData({ html: content, showPage: false })
                            this.triggerEvent('getHtml', this.ctx.getContent())
                            // that.update()
                            // 复制到剪贴板
                            // wx.setClipboardData({
                            //     data: content,
                            // })
                            // 结束编辑
                            // this.setData({
                            //     editable: false
                            // })
                        }
                    }
                })
            }, 50)
        },

        exit() {
            wx.showModal({
                title: '提示',
                content: '离开前请保存编辑内容!',
                complete: (res) => {
                    if (res.cancel) {

                    }

                    if (res.confirm) {
                        this.setData({ showPage: false })
                    }
                }
            })



            return
            wx.enableAlertBeforeUnload({ //开启页面退出时的对话框

                message: "离开前请保存编辑内容!",

                success: function (res) {

                    // console.log("成功:", res);

                    return true

                },

                fail: function (err) {

                    // console.log("失败:", err);

                    return false

                },

            });

            // wx.navigateBack()
        },
        back() {

        },

        goTo(e) {
            wx.navigateTo({ url: `../shareElement/index` })
        },

        onBeforeEnter(res) {

        },
        onEnter(res) {

        },
        onAfterEnter(res) {

        },
        onBeforeLeave(res) {

        },
        onLeave(res) {

        },
        onAfterLeave(res) {

        },

        onClickOverlay(res) {

        }
    },

})

json部分

{
    "usingComponents": {
        "mp-html": "/components/mp-html/index",
        "van-stepper": "/components/vant/stepper/index",
        "van-dialog": "/components/vant/dialog/index",
        "van-nav-bar": "/components/vant/nav-bar/index"
    },
    "navigationBarTitleText": "文本编辑",
    "navigationBarBackgroundColor": "#ffffff"
}

以上是组件的所有代码部分,下边是使用

<editor-jj showPage="{{showPage}}" html="{{html}}" bind:getHtml="getHtml"></editor-jj>
    getHtml(e) {
        console.log('测试', e.detail);
        // 这里是编辑后返回的结果


        //参数介绍
        // showPage 是组件的显示隐藏
        // html 是组件的初始化内容,并不是双向绑定的,仅在初始化的时候使用
    },

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值