1

image

在学习成长的过程中,常常会遇到一些自己从未接触的事物,这就好比是打怪升级,每次打倒一只怪,都会获得经验,让自己进步强大。特别是我们这些做技术的,逆水行舟不进则退。下面分享下小程序开发中的打怪升级经历~

效果图

先来看下实际效果图,小程序开发中有时会要做一些的功能复杂的组件,比如评论回复和发帖功能等,这次主要讲的是关于评论模块的一些思路和实战中的经验,希望能抛砖引玉,给大家一些启发,一同成长~

>>(最下面有实战demo的地址,可以直接浏览器打开添加至IDE工具中) <<

评论组件流程图

根据这个demo.gif,本人做了一个简单的流程图,帮助大家理解。下面罗列一些开发中需要“打的怪”:

1、组件目录结构

├─components      ---小程序自定义组件
│  ├─plugins      --- (重点)可独立运行的大型模块,可以打包成plugins
│  │  ├─comment         ---评论模块
│  │  │  │  index.js
│  │  │  │  index.json
│  │  │  │  index.wxml
│  │  │  │  index.wxss
│  │  │  │  services.js    ---(重点)用来处理和清洗数据的service.js,配套模板和插件
         │      
         └─submit    ---评论模块子模块:提交评论
                 index.js
                 index.json
                 index.wxml
                 index.wxss

为什么要单独做个评论页面页面(submit)
因为如果是当前页面最下面input输入的形式,会出现一些兼容问题,比如:

  • 不同手机的虚拟键盘高度不同,不好绝对定位和完全适配
  • 弹窗输入框过小输入不方便,如果是大的textare时,容易误触下面评论的交。

注:目录结构,仅供参考。

2、NODE端API接口返回结构和页面结构

//node:API接口返回
{
    "data": {
        "commentTotal": 40,
        "comments": [
            {
                "contentText": "喜欢就关注我",   //评论内容
                "createTime": 1560158823647,    //评论时间
                "displayName": "智酷方程式",       //用户名
                "headPortrait": "https://blz.nosdn.127.net/1/weixin/zxts.jpg",  //用户头像
                "id": "46e0fb0066666666",  //评论ID  用于回复和举报
                "likeTotal": 2,    //点赞数
                "replyContents": [   //回复评论
                    {
                        "contentText": "@智酷方程式  喜欢就回复我",   //回复评论内容
                        "createTime": 1560158986524,   //回复时间
                        "displayName": "神秘的前端开发",   //回复的用户名
                        "headPortrait": "https://blz.nosdn.127.net/1/2018cosplay/fourth/tesss.jpg",  //回复的用户头像
                        "id": "46e0fb00111111111",   //回复评论的ID
                        "likeTotal": 2,    //回复评论的点赞数
                        "replyContents": [],   //回复的回复 盖楼
                        "replyId": "46e0fb001ec222222222",   //回复评论的独立ID,用于统计
                    },
                    {
                        "contentText": "@智酷方程式: 威武,学习学习",
                        "createTime": 1560407232814,
                        "displayName": "神秘的前端开发",
                        "headPortrait": "https://blz.nosdn.127.net/1/2018cosplay/fourth/tesss.jpg",
                        "id": "46e0fb00111111111",
                        "likeTotal": 0,
                        "replyContents": [],
                        "replyId": "46e0fb001ec222222222",
                    }
                ],
                "replyId": "",
                "topicId": "46e0fb001ec3333333",
            }
        ],
        "curPage": 1,  //当前页面
        //通过ID 判断  当前用户点赞了 哪些评论
        "likes": [
            "46e0fb00111111111",    
            "46e0fb001ec222222222",
            "46e0fb0066666666",
        ],
        "nextPage": null, //下一页
        "pageSize": 20,  //一页总共多少评论
        "total": 7,   //总共多少页面
    },
    "msg": "success",
    "status": "success"
}
<!-- HTML 部分 -->
<block wx:if="{{commentList.length>0}}">
    <!-- 评论模块 -->
    <block wx:for="{{commentList}}" wx:for-item="item" wx:for-index="index" wx:key="idx">
        <view class="commentItem" catchtap="_goToReply" data-contentid="{{item.id}}" data-replyid="{{item.id}}"
            data-battle-tag="{{item.displayName}}">
            <view class="titleWrap">
                <image class="logo" src="{{item.headPortrait||'默认图'}}"></image>
                <view class="authorWrap">
                    <view class="author">{{item.displayName}}</view>
                    <view class="time">{{item.createTime}}</view>
                </view>
                <view class="starWrap" catchtap="_clickLike" data-index="{{index}}" data-like="{{item.like}}"
                    data-contentid="{{item.id}}" data-topicid="{{item.topicId}}">
                    <text class="count">{{item.likeTotal||""}}</text>
                    <view class="workSprite icon {{item.like?'starIconHasClick':'starIcon'}}"></view>
                </view>
            </view>
            <view class="text">
                {{item.contentText}}
            </view>
        </view>
        <!-- 评论的评论 -->
        <block wx:for="{{item.replyContents}}" wx:for-item="itemReply" wx:for-index="indexReply" wx:key="idxReply">
            <view class="commentItem commentItemReply" catchtap="_goToReply" data-contentid="{{itemReply.id}}"
                data-replyid="{{item.id}}" data-battle-tag="{{itemReply.displayName}}">
                ... 和上面类似
            </view>
        </block>
    </block>
    <!-- 加载更多loading -->
    <block wx:if="{{isOver}}">
        <view class="more">评论加载完成</view>
    </block>
</block>

通过node提供一个API接口,通过用户的openId来判断是否点赞,这里提供一个参考的JSON结构。
JSON尽量做成array循环的结构方便渲染,根据ID来BAN人和管理。底部加上加载更多的效果,同时,记得做一些兼容,比如默认头像等。

3、评论中的一些微信原生交互

这里建议很多交互如果不是必须要特别定制,可以才用微信原生的组件,效果和兼容性都有保障,而且方便简单。

对评论进行回复/举报

<!-- HTML部分 通过绑定事件:_goToReply 进行交互-->
<view class="commentItem" catchtap="_goToReply" data-contentid="{{item.id}}" data-replyid="{{item.id}}"
    data-battle-tag="{{item.displayName}}">
    ... 内部省略
</view>
//JS部分  微信原生wx.showActionSheet 显示操作菜单交互
_goToReply(e) {
    //  上面的各种授权判断省略...
    let self = this;
    wx.showActionSheet({
        itemList: ['回复', '举报'],
        success: function (res) {
            if (!res.cancel) {
                console.log(res.tapIndex);
                //前往评论
                if (res.tapIndex == 0) {
                    //判断是否是 评论的评论
                    self._goToComment(replyid);
                }
                //举报按钮
                if (res.tapIndex == 1) {
                    //弹出框
                    self.setComplain(contentid);
                }
            } else { //取消选择
                
            }
        },
        fail(res) {
            console.log(res.errMsg)
        }
    });
}
//当选择“举报”的时候,二次调用 wx.showActionSheet 方法
setComplain(contentid){
    let complainJson = ["敏感信息", "色情淫秽", "垃圾广告", "语言辱骂", "其它"];
    wx.showActionSheet({
        itemList: complainJson,
        success: async res => {
            if (!res.cancel) {
                //选择好后,提交举报
                try {
                    let complainResult = await request.postComplainReport(complainJson[index], openid, contentid);
                    if (complainResult.msg == "success") {  //提交成功后反馈

                    } else {

                    }
                } catch (e) {
                    console.log(e)
                }
            }
        }
    });
}

显示操作菜单 wx.showActionSheet 方法说明

属性 类型 说明
itemList Array.<string> 按钮的文字数组,数组长度最大为 6
itemColor string 按钮的文字颜色
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

使用这个方法,即是主流的做法,也能很好的兼容不同机型,同时给予用户“习惯性体验”。

原生评论排序切换

评论排序切换示例

<!-- picker组件  html部分-->
<picker bindchange="bindPickerChange" value="{{index}}" range="{{array}}">
    <view class="picker">
        当前选择:{{array[index]}}
    </view>
</picker>
// js部分
Page({
    data:{
        //查看评论类型切换
        array: ["最佳", "最新", "只看自己"],
        //选择数组中的第几个显示
        index:0
    },
    bindPickerChange(e) {
        console.log('picker发送选择改变,携带值为', e.detail.value)
        this.setData({
            index: e.detail.value
        })
    }
})

picker组件是一个从底部弹起的滚动选择器,这里我们用它来切换不同评论的排序。每次切换都可以通过 bindchange获得对应的变化,通过 e.detail.value获取用户选择的索引值。
官方文档:
https://developers.weixin.qq....

4、传参跳转写评论页

let uriData = {
    logo: "xxx.jpg",
    type: "commentReply",
    title: "文章:小程序评论,动态发帖开发指北\n 作者:智酷方程式",
    openId:"xxxxxxxxxxx",
    replyId:"aaaaaa"   //用户回复的是哪个评论的ID
};
wx.navigateTo({ url: `/components/plugins/comment/submit/index?uriData=${encodeURIComponent(JSON.stringify(uriData))}` });

这个可以用encodeURIComponent的方式处理下参数中的中文,避免跳转发布评论页接收数据时出现乱码。

5、发表评论页

发表评论页

显示和控制评论的字数
<!-- html部分  关于textarea 的配置 -->
<view class='feedback-cont'>
    <textarea auto-focus="true" value="{{replyName}}" maxlength="200" bindinput="textareaCtrl"
        placeholder-style="color:#999;" placeholder="留下评论,共同学习,一起进步" />
    <view class='fontNum'>{{content.length}}/200</view>
</view>
<view class='feedback-btn' bindtap='commentSubmit'>提交</view>
// js部分
Page({
    data: {
        //初始化评论内容,如果是回复则通过传参变成 @xxxx的形式
        content: "@xxxx",
    },
    textareaCtrl: function (e) {
        if (e.detail.value) {
            this.setData({
                content: e.detail.value
            })
        } else {
            this.setData({
                content: ""
            })
        }
    }
})

textarea 在小程序中改动不大,这个标签原有的一些属性都可以继续使用,通过配置maxlength来控制字数,同时,设置auto-focus="true"可以让用户进到这个发表评论页面时自动弹出虚拟键盘和光标定位在输入的区域。

当然,也可以将发表评论评论展示区域做在一起,这个就要考虑到要么通过“小程序API”获取键盘高度,要么将“发布评论”置顶区域显示,也是可以做的,只是相对考虑的点会多些。当时开发评论组件的时候,考虑开发时间短和用户体验,权衡后,最终决定以上方案,希望能给到大家一些参考和借鉴,在其他组件开发中触类旁通。

[代码片段]评论回复组件实战demo

demo的微信路径:https://developers.weixin.qq....

demo的ID:oHs5cMma7N9W

如果你装了IDE工具,可以直接访问上面的demo路径

通过代码片段将demo的ID输入进去也可添加:

浏览器打开路径
打开代码片段

总结,“组件化思想”对于无论做小程序、react/VUE还是其他项目来说,减少重复开发,提高复用性都是一个非常重要的点。评论功能其实只要理清楚整体思路,做起来难度并不大,通过一些原生组件,可以大大提高开发效率,同时保证良好的兼容性。
后面一期还将分享下功能点较多的发帖组件开发。

往期回顾:
[[[打怪升级]小程序评论回复和发贴功能实战(二)](https://segmentfault.com/a/11...
[[填坑手册]小程序Canvas生成海报(一)](https://segmentfault.com/a/11...
[[拆弹时刻]小程序Canvas生成海报(二)](https://segmentfault.com/a/11...
[[填坑手册]小程序目录结构和component组件使用心得](https://segmentfault.com/a/11...


智酷方程式
21 声望4 粉丝