1.6 收藏、评论、点赞、计数功能准备工作

1、收藏、评论、点赞、计数功能准备工作

编写收藏、评论、点赞的功能按钮。计数功能是一个被动功能,无须用户有意识的主动触发。

1.1 编写3个功能的功能按钮

post-detail.wxml
<view class='tool'>
  <view class='tool-item' catchtap='onUpTap' data-post-id="{{post.postId}}">
    <image src='/images/icon/wx_app_like.png'></image>
    <text>{{post.upNum}}</text>
  </view>

  <view class='tool-item comment' catchtap='onCommentTap' data-post-id="{{post.postId}}">
    <image src='/images/icon/wx_app_message.png'></image>
    <text>{{post.commentNum}}</text>
  </view>

  <view class='tool-item' catchtap='onCollectionTap' data-post-id="{{post.postId}}">
    <image src='/images/icon/wx_app_collect.png'></image>
    <text>{{post.collectionNum}}</text>
  </view>
</view>

1.2 编写3个功能的功能样式

post-detail.wxss
.tool{
height: 64rpx;
text-align: center;
line-height: 64rpx;
margin: 20rpx 28rpx 20rpx 0;
}

.tool-item{
 display: inline-block; 
 vertical-align: top; 
margin-right: 30rpx;
}

.tool-item image{
  height: 30rpx;
  width: 30rpx;
  vertical-align: -3px;
  margin-right: 10rpx;
}

.comment image{
  transform: scale(0.85);
}
image.png
  • 属性注意点

1)transform: scale()属性


image.png
image.png

2)display: inline-block;属性


image.png
image.png

2、文章收藏功能

文章收藏功能需要记录两个变量值:
1)根据是否已经收藏更换照片、
2)收藏数量的变化

2.1 条件渲染:wx:if 与 wx:else

小程序提供了wx:if 与 wx:else来实现条件渲染。当变量为true时,执行wx:if,否则将执行wx:else

2.1.1 条件渲染:wx:if 与 wx:else
post-detail.wxml

  <view class='tool-item' catchtap='onCollectionTap' data-post-id="{{post.postId}}">
    <image wx:if="{{post.collectionStatus}}" src='/images/icon/wx_app_collected.png'></image>
    <image wx:else src='/images/icon/wx_app_collected.png'></image>
    <text>{{post.collectionNum}}</text>
  </view>
image.png
2.1.2 多级别 if else
<view wx:if="{{length > 5}}">1</view>
<view wx:elif="{{length > 2}}">2</view>
<view wx:else>3</view>

2.2 实现收藏点击功能

2.2.1 添加处理文章收藏的方法

首先完善数据库工具类的操作

DBPost.js

  //收藏文章
  collect() {
    return this.updatePostData("collect");
  }

  //更新本地的点赞、评论信息、收藏、阅读量
  updatePostData(category) {
    var itemData = this.getPostItemById(),
      postData = itemData.data,
      allPostData = this.getAllPostData();

    switch (category) {
      case 'collect':
        //处理收藏
        if (!postData.collectionStatus) {
          //如果当前状态是未收藏
          postData.collectionNum++;
          postData.collectionStatus = true;
        } else {
          //如果当前状态是收藏
          postData.collectionNum--;
          postData.collectionStatus = false;
        }
        break;
      default:
        break;
    }

2.2.2 编写onCollectionTap方法

首先完善数据库工具类的操作

post-detail.js
  onCollectionTap:function(event){
    var newData = this.dbPost.collect();

    //重新绑定数据
    //应该选择更新部分数据
    this.setData({
      'post.collectionStatus':newData.collectionStatus,
      'post.collectionNum': newData.collectionNum
    });
  }

2.3 交互反馈wx:showToast

小程序提供了一些交互反馈API来帮助开发者处理交互相关的问题,目前主要有如下几个:


image.png
2.3.1 文章收藏功能的交互反馈
post-detail.js
    wx.showToast({
      title: newData.collectionStatus? '收藏成功':'取消成功',
      duration:1000,
      icon:'success',
      mask:true
    })

-参数说明


image.png
image.png

2.3 文章点赞功能

这个功能跟之前文章收藏功能基本写法是一样的

2.3.1 添加处理点赞操作的方法
DBPost.js

  //点赞或者取消点赞
  up() {
    var data = this.updatePostData('up');
    return data;
  }

  //更新本地的点赞、评论信息、收藏、阅读量
  updatePostData(category) {
    var itemData = this.getPostItemById(),
      postData = itemData.data,
      allPostData = this.getAllPostData();

    switch (category) {
      case 'collect':
        //处理收藏
        if (!postData.collectionStatus) {
          //如果当前状态是未收藏
          postData.collectionNum++;
          postData.collectionStatus = true;
        } else {
          //如果当前状态是收藏
          postData.collectionNum--;
          postData.collectionStatus = false;
        }
        break;
      case 'up':
        if (!postData.upStatus) {
          postData.upNum++;
          postData.upStatus = true;
        } else {
          postData.upNum--;
          postData.upStatus = false;
        }
        break;
      default:
        break;
    }

    //更新缓存数据库
    allPostData[itemData.index] = postData;
    this.execSetStorageSync(allPostData);
    return postData;
  }

2.3.2 编写onUpTap方法
post-detail.js
  onUpTap: function (event){
    var newData = this.dbPost.up();

    this.setData({
      'post.upStatus': newData.upStatus,
      'post.upNum': newData.upNum
    });
    wx.showToast({
      title: newData.collectionStatus ? '点赞成功' : '取消成功',
      duration: 1000,
      icon: 'success',
      mask: false
    })
  },

2.3.3 点赞功能的条件渲染
post-detail.wxml
<view class='tool'>
  <view class='tool-item' catchtap='onUpTap' data-post-id="{{post.postId}}">
    <image wx:if="{{post.upStatus}}" src='/images/icon/wx_app_liked.png'></image>
     <image wx:else src='/images/icon/wx_app_like.png'></image>
    <text>{{post.upNum}}</text>
  </view>
image.png

2.4 本地缓存的重要性及应用举例

提供本地的key&value缓存机制是小程序的一大特点,善用本地缓存可以极大地改善客户端的体验与服务器的性能。

在一个高性能的产品中,缓存的重要性是不言而喻的,建议开发者将本地缓存视作为一个本地的key&value数据库,并封装一些类和公用方法,提供给项目中的各个调用方,最好不要让getStorage 、setStorage等方法充斥在项目的每一个角落

2.5 支持文字、图片、拍照、语音上传的文章评论

2.5.1 注册post-comment页面
app.json
image.png
2.5.2 onCommentTap方法
post-detail.js
  onCommentTap: function (event){
    var id = event.currentTarget.dataset.postId;
    wx.navigateTo({
      url: '../post-comment/post-comment?id='+id
    })
  },

2.6 文章评论页面的实现步骤与思路

2.6.1 思路

1)加载并显示当前文章已存在的评论
2)添加新评论的功能

2.6.2 步骤

1)在post-comment.js中获取并绑定文章评论数据
2)在post-comment 页面的wxml和wcss显示文章评论数据
3)编写添加新评论的功能

2.7 获取并绑定文章评论数据

2.7.1 获取评论数据
post-comment.js
import{DBPost} from '../../../db/DBPost.js'

Page({

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

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    var postId = options.id;
    this.dbPost = new DBPost(postId);
    var comments = this.dbPost.getCommentData();

    console.log('comments==' + comments)

    //绑定评论数据
    this.setData({
      comments:comments
    })
  },

 
})
2.7.2 编写获取文章评论的方法
DBPost.js
  //编写获取文章评论的方法
  getCommentData() {
    var itemData = this.getPostItemById().data;
    //按时间减序排列评论
    itemData.comments.sort(this.compareWithTime);
    var len = itemData.comments.length,
      comment;

    for (var i = 0; i < len; i++) {
      comment = itemData.comments[i];
      comment.create_time = util.getDiffTime(comment.create_time, true);
    }
    return itemData.comments;
  }
2.7.3 compareWithTime方法
DBPost.js
 compareWithTime(value1, value2) {
    var flag = parseFloat(value1.create_time) - parseFloat(value2.create_time);

    if (flag < 0) {
      return 1;
    } else if (flag > 0) {
      return -1;
    } else {
      return 0;
    }
  }
2.7.4 getDiffTime方法
util.js
/*
 *根据客户端的时间信息得到发表评论的时间格式
 *多少分钟前,多少小时前,然后是昨天,然后再是月日
 * Para :
 * recordTime - {float} 时间戳
 * yearsFlag -{bool} 是否要年份
 */
function getDiffTime(recordTime, yearsFlag) {
  if (recordTime) {
    recordTime = new Date(parseFloat(recordTime) * 1000);
    var minute = 1000 * 60,
      hour = minute * 60,
      day = hour * 24,
      now = new Date(),
      diff = now - recordTime;
    var result = '';
    if (diff < 0) {
      return result;
    }
    var weekR = diff / (7 * day);
    var dayC = diff / day;
    var hourC = diff / hour;
    var minC = diff / minute;
    if (weekR >= 1) {
      var formate = 'MM-dd hh:mm';
      if (yearsFlag) {
        formate = 'yyyy-MM-dd hh:mm';
      }
      return recordTime.format(formate);
    }
    else if (dayC == 1 || (hourC < 24 && recordTime.getDate() != now.getDate())) {
      result = '昨天' + recordTime.format('hh:mm');
      return result;
    }
    else if (dayC > 1) {
      var formate = 'MM-dd hh:mm';
      if (yearsFlag) {
        formate = 'yyyy-MM-dd hh:mm';
      }
      return recordTime.format(formate);
    }
    else if (hourC >= 1) {
      result = parseInt(hourC) + '小时前';
      return result;
    }
    else if (minC >= 1) {
      result = parseInt(minC) + '分钟前';
      return result;
    } else {
      result = '刚刚';
      return result;
    }
  }
  return '';
}

2.7.5 在Date原型链上新增format方法
util.js
/*
 *拓展Date方法。得到格式化的日期形式
 *date.format('yyyy-MM-dd'),date.format('yyyy/MM/dd'),date.format('yyyy.MM.dd')
 *date.format('dd.MM.yy'), date.format('yyyy.dd.MM'), date.format('yyyy-MM-dd HH:mm')
 *使用方法 如下:
 *                       var date = new Date();
 *                       var todayFormat = date.format('yyyy-MM-dd'); //结果为2015-2-3
 *Parameters:
 *format - {string} 目标格式 类似('yyyy-MM-dd')
 *Returns - {string} 格式化后的日期 2015-2-3
 *
 */
(function initTimeFormat() {
  Date.prototype.format = function (format) {
    var o = {
      "M+": this.getMonth() + 1, //month
      "d+": this.getDate(), //day
      "h+": this.getHours(), //hour
      "m+": this.getMinutes(), //minute
      "s+": this.getSeconds(), //second
      "q+": Math.floor((this.getMonth() + 3) / 3), //quarter
      "S": this.getMilliseconds() //millisecond
    }
    if (/(y+)/.test(format)) format = format.replace(RegExp.$1,
      (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    for (var k in o) if (new RegExp("(" + k + ")").test(format))
      format = format.replace(RegExp.$1,
        RegExp.$1.length == 1 ? o[k] :
          ("00" + o[k]).substr(("" + o[k]).length));
    return format;
  };
})()

2.7.6 添加module.exports
util.js
module.exports = {
  getDiffTime: getDiffTime
}
2.7.7 引用util模块
DBPost.js
var util = require('../util/util.js')
image.png

2.8 显示文章评论数据

2.8.1 获取评论数据wxml代码
post-comment.wxml
<!--pages/post/post-comment/post-comment.wxml-->
<view class='comment-detail-box'>
  <view classs='comment-main-box'>
    <view class='comment-title'>评论......(共{{comments.length}}条)</view>
    <block wx:for="{{comments}}" wx:for-item="item" wx:for-index="idx">
      <view class='comment-item'>
        <view class='comment-item-header'>
          <view class='left-img'>
            <image src='{{item.avatar}}'></image>
          </view>
          <view class='right-user'>
            <text class='user-name'>{{item.username}}</text>
          </view>
        </view>
        <view class='comment-body'>
          <view class='comment-text' wx:if="{{item.content.text}}">
            <text>{{item.content.txt}}</text>
          </view>
          <view class='comment-voice' wx:if="{{item.content.audio && item.content.audio.url}}">
          <view data-url='{{item.content.audio.url}}' class='comment-voice-item' catchtap="playAudio">
          <image src='/images/icon/wx_app_voice.png' class='voice-play'></image>
          <text>{{item.content.audio.timeLen}}</text>
          </view>
          </view>
          <view class='comment-img' wx:if="{{item.content.img.length!=0}}">
          <block wx:for="{{item.content.img}}" wx:for-item="img">
          <image src='{{img}}' mode='aspectFill'></image>
          </block>
          </view>
        </view>
        <view class='comment-time'>{{item.create_time}}</view>
      </view>
    </block>
  </view>
</view>
2.8.2 添加评论列表的样式
post-comment.wxss
/* pages/post/post-comment/post-comment.wxss */

.comment-detail-box {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
   overflow-y: hidden; 
}

.comment-main-box {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 100rpx;
  right: 0;
  overflow-y: auto;
}

.comment-title {
  height: 60rpx;
  line-height: 60rpx;
  font-size: 28rpx;
  color: #212121;
  border-bottom: 1px solid #ccc;
  margin-left: 24rpx;
  padding: 8rpx 0;
  font-family: Microsoft YaHei;
}

.comment-item {
  margin: 20rpx 0 20rpx 24rpx;
  padding: 24rpx 24rpx 24rpx 0;
  border-bottom: 1rpx solid #f2e7e1;
}

.comment-item:last-child {
  border-bottom: none;
}

.comment-item-header {
  display: flex;
  flex-direction: row;
  align-items: center;
}

.comment-item-header .left-img image {
  height: 80rpx;
  width: 80rpx;
}

.comment-item-header .right-user {
  margin-left: 30rpx;
  line-height: 80rpx;
}

.comment-item-header .right-user text {
  font-size: 26rpx;
  color: #212121;
}

.comment-body {
  font-size: 26rpx;
  line-height: 26rpx;
  color: #666;
  padding: 10rpx 0;
}

.comment-text text {
  line-height: 50rpx;
}

.comment-voice-item {
  display: flex;
  flex-direction: row;
  align-items: center;
  width: 200rpx;
  height: 64rpx;
  border: 1px solid #ccc;
  background-color: #fff;
  border-radius: 6rpx;
}

.comment-voice-item .voice-play {
  height: 64rpx;
  width: 64rpx;
}

.comment-voice-item text {
  margin-left: 60rpx;
  color: #212121;
  font-size: 22rpx;
}

.comment-img {
  margin: 10rpx 0;
}

.comment-img image {
  max-width: 32%;
  margin-right: 10rpx;
  width: 220rpx;
  height: 22orpx;
}

.comment-time {
  margin-top: 10rpx;
  color: #ccc;
  font-size: 24rpx;
}

image.png
  • 样式注意点:
    1)overflow-y: hidden


    image.png
image.png

2).comment-item:last-child


image.png

2.9 实现图片预览

image.png
2.9.1 实现图片预览
post-comment.wxml
          <view class='comment-img' wx:if="{{item.content.img.length!=0}}">
          <block wx:for="{{item.content.img}}" wx:for-item="img" wx:for-index="imgIdx">
          <image src='{{img}}' mode='aspectFill' catchtap='previewimg' data-comment-idx='{{idx}}' data-img-idx='{{imgIdx}}'></image>
          </block>
          </view>
image.png
image.png
image.png

2.10 实现提交评论的界面

2.10.1 评论框的骨架
post-comment.wxml
 <view class="input-box">
    <view class="send-msg-box">
      <view hidden="{{useKeyboardFlag}}" class="input-item">
        <image src="/images/icon/wx_app_keyboard.png" class="comment-icon keyboard-icon" catchtap="switchInputType"></image>
        <input class="input speak-input {{recodingClass}}" value="按住 说话" disabled="disabled" catchtouchstart="recordStart" catchtouchend="recordEnd" />
      </view>
      <view hidden="{{!useKeyboardFlag}}" class="input-item">
        <image class="comment-icon speak-icon" src="/images/icon/wx_app_speak.png" catchtap="switchInputType"></image>
        <input class="input keyboard-input"  value="{{keyboardInputValue}}" bindconfirm="submitComment" bindinput="bindCommentInput" placeholder="说点什么吧……" />
      </view>
      <image class="comment-icon add-icon" src="/images/icon/wx_app_add.png" catchtap="sendMoreMsg"></image>
      <view class="submit-btn" catchtap="submitComment">发送</view>
    </view>
 </view>
2.10.2 评论框的样式
post-comment.wxss

/*******************评论框**********************/
.input-box{
    position: absolute;
    bottom: 0;
    left:0;
    right: 0;
    background-color: #EAE8E8;
    border-top:1rpx solid #D5D5D5;
    min-height: 100rpx;
    z-index: 1000;
}
.input-box .send-msg-box{
    width: 100%;
    height: 100%;
    display: flex;
    padding: 20rpx 0;
}
.input-box .send-more-box{
    margin: 20rpx 35rpx 35rpx 35rpx;
}
.input-box .input-item{
    margin:0 5rpx;
    flex:1;
    width: 0%;
    position: relative;
}
.input-box .input-item .comment-icon{
    position: absolute;
    left:5rpx;
    top:6rpx;
}

.input-box .input-item .input{
    border: 1rpx solid #D5D5D5;
    background-color: #fff;
    border-radius: 3px;
    line-height: 65rpx;
    margin:5rpx 0 5rpx 75rpx ;
    font-size: 24rpx;
    color: #838383;
    padding: 0 2%;
}
.input-box .input-item .keyboard-input{
    width: auto;
    max-height: 500rpx;
    height: 65rpx;
    word-break:break-all;
    overflow:auto;
}
.input-box .input-item .speak-input{
    text-align: center;
    color: #212121;
    height: 65rpx;
}

.input-box .input-item .recoding{
    background-color: #ccc;
}

.input-box .input-item .comment-icon.speak-icon{
    height: 62rpx;
    width: 62rpx;
}
.input-box .input-item .comment-icon.keyboard-icon{
    height: 60rpx;
    width: 60rpx;
    left:6rpx;
}
.input-box .add-icon{
    margin:0 5rpx;
    height: 65rpx;
    width: 65rpx;
    transform: scale(0.9);
    margin-top: 2px;
}
.input-box .submit-btn{
    font-size: 24rpx;
    margin-top: 5rpx;
    margin-right: 8rpx;
    line-height: 60rpx;
    width: 120rpx;
    height: 60rpx;
    background-color: #4A6141;
    border-radius:5rpx;
    color: #fff;
    text-align: center;
    font-family:Microsoft Yahei;
}


.send-more-box .more-btn-item{
    display: inline-block;
    width: 110rpx;
    height: 145rpx;
    margin-right: 35rpx;
    text-align: center;
}

.more-btn-main{
    width: 100%;
    height:60rpx;
    text-align: center;
    border:1px solid #D5D5D5;
    border-radius: 10rpx;
    background-color: #fbfbfc;
    margin: 0 auto;
    padding:25rpx 0
}
.more-btn-main image{
    width: 60rpx;
    height: 60rpx;
}
.send-more-box .more-btn-item .btn-txt{
    color: #888888;
    font-size: 24rpx;
    margin:10rpx 0;
}

.send-more-result-main{
    margin-top: 30rpx;
}
.send-more-result-main .file-box{
    margin-right: 14rpx;
    height: 160rpx;
    width: 160rpx;
    position: relative;
    display: inline-block;
}

.send-more-result-main .file-box.deleting{
    animation:deleting 0.5s ease;
    animation-fill-mode: forwards;
}

@keyframes deleting {
    0%{
        transform: scale(1);
    }
    100%{
        transform: scale(0);
    }
}

.send-more-result-main image{
    height: 100%;
    width: 100%;
}
.send-more-result-main .remove-icon{
    position: absolute;
    right: 5rpx;
    top: 5rpx;
}

.send-more-result-main .file-box .img-box {
    height: 100%;
    width: 100%;
image.png

2.11 wx:if 与 hidden控制元素显示和隐藏

在小程序中,隐藏UI元素的方法有俩种:
1)wx:if
2)hidden


image.png

它们都是通过一个状态变量来控制元素的显示和隐藏

  • wx:if 和 hidden 有什么异同?
    1)wx:if的切换和渲染机制较为复杂,当wx:if进行切换时,MINA框架有一个局部渲染的过程,它会确保条件块在切换时销毁或者重新渲染。
    2)wx:if是惰性的,如果初始渲染条件为false,那么MINA框架什么也不做,在条件第一次变成真的时候才开始局部渲染。
    3)hidden就比较简单,组件始终会被渲染,只是简单地控制显示与隐藏
    4)一般来说,wx:if有更高的切换消耗,而hidden有更高的初始化渲染消耗。因此,在需要频繁切换的情境下用hidden更好,在运行时条件不太可能改变时用wx:if较好。

2.12 实现文字评论框和语音评论框的切换

2.12.1 初始化useKeyboardFlag
post-comment.js
image.png
2.12.1 切换useKeyboardFlag
post-comment.js
image.png
image.png
image.png

2.13 input组件

属于小程序中最为重要的数据输入组件

2.13.1 属性介绍
image.png

image.png
2.13.2 事件介绍
image.png

由MINA框架直接指定的,属于非冒泡事件,不需要在事件名称前面再添加catch和bind

  • bindinput事件较为特殊,具有如下几个特点:
    1)当用户输入字符时触发
    2)每当用户输入或者删除一个字符时,bindinput事件都会触发一次
    3)可以在事件响应函数中使用return返回一个字符或者字符串,该字符串将替换input输入框的显示文本
    4)比较适合用来做“即时搜索”的功能

input输入值都是在事件对应的响应函数中使用 event.detail.value来获取

2.14 bindinput事件

2.14.1 响应bindinput事件
post-comment.js
  //获取用户输入
  bindCommentInput:function(event){
    var val = event.detail.value;
    console.log(val);
    this.data.keyboardInputValue = val;
  }
image.png

2.15 屏蔽评论关键字

bindinput还有一个有意思的特性,就是在事件响应函数中可以return一个值来代替当前的输入值,并显示在input中

2.15.1 bindinput的return值
post-comment.js

将这个函数的代码临时改一下,用来测试

    bindCommentInput: function (event) {
    var val = event.detail.value;
    return val + "#";
image.png
2.15.2 屏蔽关键字“qq”
post-comment.js
 bindCommentInput: function(event) {
    var value = event.detail.value;
    var pos = event.detail.cursor;
    console.log("pos======" + pos);
    if (pos != -1) {
      //光标在中间
      var left = event.detail.value.slice(0, pos);
      console.log(left);
      //计算光标的位置
      pos = left.replace(/qq/g, "*").length;
    }
    //直接返回对象,可以对输入进行过滤处理,同时可以控制光标的位置
    return {
      value: value.replace(/qq/g, "*"),
      cursor: pos
    };
  }
image.png
2.15.3 简化版屏蔽关键字“qq”
post-comment.js
 bindCommentInput: function(event) {
    var value = event.detail.value;
    return value.replace(/qq/g, "*");
  }

image.png

2.16 实现自定义发送按钮

2.16.1 实现 submitComment 方法
post-comment.js
//提交用户评论
  submitComment: function(event) {
    var newData = {
      username: "青石",
      avatar: "/images/avatar/avatar-3.png",
      //评论时间
      create_time: new Date().getTime() / 1000,
      //评论内容
      content: {
        text: this.data.keyboardInputValue
      },
    };

    if (!newData.content.text) {
      //如果没有评论内容,就不执行任何操作
      return;
    }

    //保存新评论到缓存数据库中
    this.dbPost.newComment(newData);
    //显示操作结果
    this.showCommitSuccessToast();
    //重新渲染并绑定所有评论
    this.bindCommentData();
    //恢复初始状态
    this.resetAllDefaultStatus();
  },
2.16.2 编写newComment方法
DBPost.js
 //发表评论
  newComment(newComment) {
    this.updatePostData("comment", newComment);
  }

2.16.3 在updatePostData中处理新评论
DBPost.js
 //更新本地的点赞、评论信息、收藏、阅读量
  updatePostData(category, newComment) {
    var itemData = this.getPostItemById(),
      postData = itemData.data,
      allPostData = this.getAllPostData();

    switch (category) {
      case 'collect':
        //处理收藏
        if (!postData.collectionStatus) {
          //如果当前状态是未收藏
          postData.collectionNum++;
          postData.collectionStatus = true;
        } else {
          //如果当前状态是收藏
          postData.collectionNum--;
          postData.collectionStatus = false;
        }
        break;
      case 'up':
        if (!postData.upStatus) {
          postData.upNum++;
          postData.upStatus = true;
        } else {
          postData.upNum--;
          postData.upStatus = false;
        }
        break;

      case "comment":
        postData.comments.push(newComment);
        postData.commentNum++;
        break;
      default:
        break;
    }
2.16.4 编写新增评论成功的提示方法
post-comment.js
  //评论成功
  showCommitSuccessToast: function() {
    wx.showToast({
      title: '评论成功',
      duration: 1000,
      icon: "success"
    })
  },
2.16.5 重新绑定评论数据
post-comment.js
bindCommentData: function() {
    var comments = this.dbPost.getCommentData();
    //绑定评论数据
    this.setData({
      comments: comments
    })
  },
2.16.6 重置input组件的输入值
post-comment.js
  //将所有相关的按钮状态、输入状态都回复到初始化状态
  resetAllDefaultStatus: function() {
    //清空评论框
    this.setData({
      keyboardInputValue: ""
    })
  }
image.png

2.17 同时支持模拟器回车、真机点击“完成”发送评论

如果要想在模拟器中实现回车发送评论信息的功能,可以使用如下几个input事件:

  • bindchange(早期版本存在,新版本说明文档不在了,不过依然有效,只是不建议用)
  • bindblur:可以触发回车的原理是:点击回车后,input组件将失去焦点,从而触发bindblur事件
  • bindconfirm:可以在真机上响应键盘的“完成”点击事件,同时也可以在模拟器中响应键盘的“回车”事件
2.17.1 添加bindcomfirm事件
post-comment.wxml
image.png

2.18 图片与拍照评论的界面实现

2.18.1 选择图片与拍照面板代码
post-comment.wxml
    <view class="send-msg-box">
      <view hidden="{{useKeyboardFlag}}" class="input-item">
        <image src="/images/icon/wx_app_keyboard.png" class="comment-icon keyboard-icon" catchtap="switchInputType"></image>
        <input class="input speak-input {{recodingClass}}" value="按住 说话" disabled="disabled" catchtouchstart="recordStart" catchtouchend="recordEnd" />
      </view>
      <view hidden="{{!useKeyboardFlag}}" class="input-item">
        <image class="comment-icon speak-icon" src="/images/icon/wx_app_speak.png" catchtap="switchInputType"></image>
        <input class="input keyboard-input"  value="{{keyboardInputValue}}" bindconfirm="submitComment" bindinput="bindCommentInput" placeholder="说点什么吧……" />
      </view>
      <image class="comment-icon add-icon" src="/images/icon/wx_app_add.png" catchtap="sendMoreMsg"></image>
      <view class="submit-btn" catchtap="submitComment">发送</view>
    </view>

     <view class="send-more-box" hidden="{{!sendMoreMsgFlag}}">

      <!--选择图片和拍照的按钮-->
      <view class="send-more-btns-main">
        <view class="more-btn-item" catchtap="chooseImage" data-category="album">
          <view class="more-btn-main">
            <image src="/images/icon/wx_app_upload_image.png"></image>
          </view>
          <text>照片</text>
        </view>
        <view class="more-btn-item" catchtap="chooseImage" data-category="camera">
          <view class="more-btn-main">
            <image src="/images/icon/wx_app_camera.png"></image>
          </view>
          <text>拍照</text>
        </view>
      </view>

      <!--显示选择的图片-->
      <view class="send-more-result-main" hidden="{{chooseFiles.length==0}}">
        <block wx:for="{{chooseFiles}}" wx:for-index="idx">

          <!--如果删除其中一个,则对其添加deleting 样式;-->
          <view class="file-box {{deleteIndex==idx?'deleting':''}}">
            <view class="img-box">
              <image src="{{item}}" mode="aspectFill"></image>
              <icon class="remove-icon" type="cancel" size="23" color="#B2B2B2" catchtap="deleteImage" data-idx="{{idx}}" />
            </view>
          </view>
        </block>
      </view>
    </view>
 </view>
</view>

2.18.2 编写sendMoreMsg方法
image.png
post-comment.js
image.png
sendMoreMsg:function(){
    this.setData({
      sendMoreMsgFlag: !this.data.sendMoreMsgFlag
    })
  }
image.png

2.19 实现从相册选择照片与拍照

2.19.1 新增chooseFiles变量
post-comment.js
  /**
   * 页面的初始数据
   */
  data: {
    //控制使用键盘还是发送语音
    useKeyboardFlag: true,
    //控制input组件的初始值
    keyboardInputValue:"",
    //控制是否显示图片选择面板
    sendMoreMsgFlag:false,
    //保存已经选择的图片
    chooseFiles:[]
  },
2.19.2 实现选择图片与拍照功能
post-comment.js
//选择本地照片与拍照
  chooseImage: function(event) {
    //已选择图片数组
    var imgArr = this.data.chooseFiles;
    //只能上传3张照片,包括拍照
    var leftCount = 3 - imgArr.length;
    if (leftCount <= 0) {
      return;
    }
    var sourceType = [event.currentTarget.dataset.category],
      that = this;
    console.log("sourceType==============" + sourceType);
    console.log("that==============" + that);
    wx.chooseImage({
      count: leftCount,
      sourceType: sourceType,
      success: function(res) {
        that.setData({
          chooseFiles: imgArr.concat(res.tempFilePaths)
        })
      },
    })
  }
image.png
image.png

注意,sourceType的传值过程

image.png

2.20 icon图片

image.png

image.png

2.21 删除已选择的图片

2.21.1 deleteImage方法
post-comment.js
  //删除已经选择的图片
  deleteImage: function(event) {
    var index = event.currentTarget.dataset.idx;
    var that = this;
    that.data.chooseFiles.splice(index, 1);
    that.setData({
      chooseFiles: that.data.chooseFiles
    })
  }

获取当前删除图片的序号,并且将该图片的URL从 this.data.chooseFiles数组中删除,重新绑定chooseFiles变量即可

2.22 在小程序中使用CSS 3动画

image.png

如果delete的值等于当前图片的序号,就说明该图片是要被删除的,需要添加一个deleteing动画


image.png
2.22.1 修改deleteImage方法
post-comment.js
image.png
 //删除已经选择的图片
  deleteImage: function(event) {
    var index = event.currentTarget.dataset.idx;
    var that = this;
    that.setData({
      deleteIndex:index
    });
    that.data.chooseFiles.splice(index, 1);
    setTimeout(function(){
      that.setData({
        deleteIndex:-1,
        chooseFiles: that.data.chooseFiles
      })
    },1000)
  }
image.png

2.23 实现图片评论的发送

实现原理:把当前的this.data.chooseFiles所保存的图片地址存入数据库缓存中,并且重新渲染评论列表即可。

修改如下的方法

2.23.1 修改submitComment方法
post-comment.js
//提交用户评论
  submitComment: function(event) {
    var imgs = this.data.chooseFiles;
    var newData = {
      username: "青石",
      avatar: "/images/avatar/avatar-3.png",
      //评论时间
      create_time: new Date().getTime() / 1000,
      //评论内容
      content: {
        text: this.data.keyboardInputValue,
        img:imgs
      },
    };

    if (!newData.content.text && imgs.length == 0) {
      //如果没有评论内容,就不执行任何操作
      return;
    }

    //保存新评论到缓存数据库中
    this.dbPost.newComment(newData);
    //显示操作结果
    this.showCommitSuccessToast();
    //重新渲染并绑定所有评论
    this.bindCommentData();
    //恢复初始状态
    this.resetAllDefaultStatus();
  },
2.23.2 修改submitComment方法
post-comment.js
  //将所有相关的按钮状态、输入状态都回复到初始化状态
  resetAllDefaultStatus: function() {
    //清空评论框
    this.setData({
      keyboardInputValue: "",
      chooseFiles:[],
      sendMoreMsgFlag:false
    })
  },
image.png

2.24 实现语音信息的发送

  • 发送语音评论的操作过程:
    1)切换到语音面板
    2)长按“按住说话”这个按钮
    3)说话
    4)松开“按住说话”,语音信息自动发送

[图片上传中...(image.png-a8f196-1529334605160-0)]

2.24.1 实现recodrdStart
post-comment.js
//开始录音
  recordStart: function() {
    var that = this;
    this.setData({
      recodingClass: "recoding"
    });
    //记录录音开始时间
    this.startTime = new Date();
    console.log("this.startTime============" + this.startTime);
    wx.startRecord({
      success: function(res) {
        //计算录音时长
        var diff = (that.endTime - that.startTime) / 1000;
        console.log("diff============" + diff);
        diff = Math.ceil(diff);
        console.log("diff======ceil======" + diff);

        //发送录音
        that.submitVoiceComment({
          url: res.tempFilePath,
          timeLen: diff
        });
      },

      fail: function(res) {
        console.log("fail============" + res);
      },
      complete: function(res) {
        console.log("complete============" + res);
      }
    })
  },
2.24.2 结束录音
post-comment.js
 //结束录音
  recordEnd: function() {
    this.setData({
      recodingClass: ""
    });
    this.endTime = new Date();
    wx.stopRecord();
  },
2.24.3 发送语音评论
post-comment.js
//提交录音
  submitVoiceComment: function (audio){
    var newData = {
      username: "青石",
      avatar: "/images/avatar/avatar-3.png",
      //评论时间
      create_time: new Date().getTime() / 1000,
      //评论内容
      content: {
        text: "",
        img: [],
        audio:audio
      },
    };
    console.log("submitVoiceComment====create_time=========" + newData.create_time);

    //保存新评论到缓存数据中
    this.dbPost.newComment(newData);

    //显示操作结果
    this.showCommitSuccessToast();

    //重新渲染并且绑定所有评论
    this.bindCommentData();
  }
image.png
image.png

2.25 实现语音信息的暂停与播放

语音评论的播放需要满足以下几个播放场景。假设有两个信息------A语音和B语音,当点击A语音时:

  • 如果A语音处于未播状态,就开始播放A语音
  • 如果A语音处于暂停状态,就继续播放A语音
    当点击B语音时:
  • B语音的行为同上述A语音
  • 无论A语音处于何种状态,都将立刻被中断;被中断后,再次点击A语音,A语音重新开始播放
2.25.1 发送语音评论
post-comment.js
playAudio: function(event) {
    console.log("playAudio=============");
    var url = event.currentTarget.dataset.url,
      that = this;
    console.log("url=============" + url);
    //暂停当前录音
    if (url == this.data.currentAudio) {
      wx.pauseVoice();
      this.data.currentAudio = ""

      //播放录音
    } else {
      this.data.currentAudio = url;
      wx.playVoice({
        filePath: url,
        complete: function() {
          //只有当录音播放完毕后才会执行
          that.data.currentAudio = ""
        }
      })
    }
  }
2.25.2 定义currentAudio变量
post-comment.js
  data: {
    //控制使用键盘还是发送语音
    useKeyboardFlag: true,
    //控制input组件的初始值
    keyboardInputValue: "",
    //控制是否显示图片选择面板
    sendMoreMsgFlag: false,
    //保存已经选择的图片
    chooseFiles: [],
    //被删除的图片序号
    deleteIndex: -1,
    //保存当前正在播放语音的URL
    currentAudio: ''
  },
image.png

2.26 用户授权

使用某些特定的API,是需要用户主动授权的,比如,调用wx.startRecord接口录音时,需要用户主动授权,如下所示:


image.png

如果用户点击确定后,以后使用该功能的时候都不会提示,如果要让弹出授权框,需要手动清除授权文件,如下所示:


image.png

2.27 解决真机运行时评论页面滑动卡顿的问题

image.png

2.28 文章阅读计算功能

2.28.1 阅读数+1
post-detail.js
//阅读量+1
  addReadingTimes:function(){
    this.dbPost.addReadingTimes();
  }
2.28.2 阅读数+1的缓存操作方法
DBPost.js
  //阅读量+1
  addReadingTimes() {
    this.updatePostData("reading");
  }
2.28.3 updatePostData方法最终代码
DBPost.js
 //更新本地的点赞、评论信息、收藏、阅读量
  updatePostData(category, newComment) {
    var itemData = this.getPostItemById(),
      postData = itemData.data,
      allPostData = this.getAllPostData();

    switch (category) {
      case 'collect':
        //处理收藏
        if (!postData.collectionStatus) {
          //如果当前状态是未收藏
          postData.collectionNum++;
          postData.collectionStatus = true;
        } else {
          //如果当前状态是收藏
          postData.collectionNum--;
          postData.collectionStatus = false;
        }
        break;
      case 'up':
        if (!postData.upStatus) {
          postData.upNum++;
          postData.upStatus = true;
        } else {
          postData.upNum--;
          postData.upStatus = false;
        }
        break;

      case "comment":
        postData.comments.push(newComment);
        postData.commentNum++;
        break;

      case "reading":
        postData.readingNum++;
        break;
      default:
        break;
    }
2.28.4 调用addReadingTimes方法
post-detail.js
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    var postId = options.id;
    this.dbPost = new DBPost(postId);
    this.postData = this.dbPost.getPostItemById().data;
    this.setData({
      post:this.postData
    })

    this.addReadingTimes();
  },

每次进来阅读量都会加1


image.png
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容