1

效果

TIM截图20191225171838.png

初始化滚动条高度
var keyHeight = 0;
  • 数据格式
const CHAT_DATA=[
  {
    type:0,//0客服1用户
    content:'欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎欢迎',
    headImg:'../../assets/common/images/headHortrait.jpeg',//头像
    creatTime:'2019-01-01',//创建时间
    contentType:'text'
  }, {
    type: 0,//0客服1用户
    content: '1111111',
    headImg: '../../assets/common/images/headHortrait.jpeg',//头像
    creatTime: '2019-01-01',//创建时间
    contentType: 'text'
  }, {
    type: 1,//0客服1用户
    content: '222222',
    headImg: '../../assets/common/images/headHortrait.jpeg',//头像
    creatTime: '2019-01-01',//创建时间
    contentType: 'text'
  }, {
    type: 0,//0客服1用户
    content: '333333',
    headImg: '../../assets/common/images/headHortrait.jpeg',//头像
    creatTime: '2019-01-01',//创建时间
    contentType: 'text'
  }, {
    type: 1,//0客服1用户
    content: '4444444',
    headImg: '../../assets/common/images/headHortrait.jpeg',//头像
    creatTime: '2019-01-01',//创建时间
    contentType: 'text',
  }, {
    type: 0,//0客服1用户
    content: 'http://tmp/wxc79c66d8b0ed19a8.o6zAJs6QE8L8FKq645ts4e3LoKzI.pGakaVHKmbQ3160aa57e2bf33cb576fbabf691cd890b.durationTime=3706.aac',
    headImg: '../../assets/common/images/headHortrait.jpeg',//头像
    creatTime: '2019-01-01',//创建时间
    contentType: 'voice',
    duration: '3706',
  },{
    type: 1,//0客服1用户
    content: 'http://tmp/wxc79c66d8b0ed19a8.o6zAJs6QE8L8FKq645ts4e3LoKzI.pGakaVHKmbQ3160aa57e2bf33cb576fbabf691cd890b.durationTime=3706.aac',
    headImg: '../../assets/common/images/headHortrait.jpeg',//头像
    creatTime: '2019-01-01',//创建时间
    contentType: 'voice',
    duration:'3706'
  },
  {
    type: 1,//0客服1用户
    content: 'https://img.yzcdn.cn/vant/cat.jpeg',
    headImg: '../../assets/common/images/headHortrait.jpeg',//头像
    creatTime: '2019-01-01',//创建时间
    contentType: 'img'
  }, {
    type: 1,//0客服1用户
    content: 'https://img.yzcdn.cn/vant/cat.jpeg',
    headImg: '../../assets/common/images/headHortrait.jpeg',//头像
    creatTime: '2019-01-01',//创建时间
    contentType: 'img'
  }
];
  • wxml对话框
<block wx:key wx:for='{{chatData}}' wx:for-index="index">

      <!-- 单个消息1 客服发出(左) -->
      <view wx:if='{{item.type==0}}' id='msg-{{index}}'  class="contentLeft" style=''>
        <view class="head">
           <image class="headImg" src='{{item.headImg}}' mode='widthFix'></image>
        </view>
        <view class='leftMsg' wx:if="{{item.contentType==='text'}}">{{item.content}}</view>
        <view class='leftMsg' wx:if="{{item.contentType==='voice'}}" data-duration="{{item.content}}">{{item.duration}}s</view>
         
        <view class='leftMsg img'  wx:if="{{item.contentType==='img'}}"><image src="{{item.content}}"  data-src="{{item.content}}" mode='widthFix' bindtap="onPreview"/></view>
      </view>

      <!-- 单个消息2 用户发出(右) -->
      <view wx:else id='msg-{{index}}'  class="contentRight">
        <view class='rightMsg'  wx:if="{{item.contentType==='voice'}}" data-duration="{{item.duration}}"  bindtap="playRecord" style="width:{{30+item.duration*5}}px;justify-content: flex-end;display:flex;color:#000;">{{item.duration}}"<van-icon name="../../../assets/common/icon/voice-r.png" style="padding:0 0 0 5px;" /></view>
        <view class='rightMsg'  wx:if="{{item.contentType==='text'}}">{{item.content}}</view>
        
        <view class='rightMsg img'  wx:if="{{item.contentType==='img'}}"><image src="{{item.content}}"  data-src="{{item.content}}" mode='widthFix' bindtap="onPreview"/></view>
         <view>
           <image class="headImg" src='{{item.headImg}}' mode='widthFix'></image>
        </view>
      </view>

    </block>
  • wxml底部输入框
 <view class='inputRoom' style='bottom: {{inputBottom}};height: {{bottomHeight}}'>
    <van-row class="bottomRow">
      <van-col span="2"wx:if="{{show}}"> <van-icon bindtap="startRecord" class="iconfont icon" class-prefix='icon' size="40rpx" name="yuyin" ></van-icon></van-col>
       <van-col span="2" wx:if="{{!show}}"> <van-icon bindtap="startRecord" class="iconfont icon" class-prefix='icon' size="40rpx"  name="fabiaowenzhang" ></van-icon></van-col>
      <van-col span="18" wx:if="{{show}}"> <input bindconfirm='sendClick'bind:input="inputValue" adjust-position='{{false}}' value='{{inputVal}}' confirm-type='send' bindfocus='focus' bindblur='blur'></input></van-col>
      <van-col span="18" wx:if="{{!show}}"> <view class="holdTape" bind:touchstart="startTalk" bind:touchend='stopRecord'>按住请说话</view></van-col>
      <van-col span="2"><van-icon class="iconfont icon" class-prefix='icon'  size="40rpx" name='biaoqing' bindtap="getEmoji"></van-icon></van-col>
      <van-col span="2"><van-uploader use-slot accept='image' bind:after-read="uploadeImg"> <van-icon class="iconfont icon" class-prefix='icon' size="40rpx" name='icon02' ></van-icon></van-uploader></van-col>
    </van-row> 
     <view wx:if="{{showEmoji}}" class="emoji">
      <emoji bind:clickEmoji="clickEmoji" data-key="inputVal" value="{{inputVal}}" />
    </view>
  </view>
   
</view>
<view class="recordDailog" wx:if="{{showDailog}}"   >
<view class="show">
  <image src="../../assets/common/images/record.png"></image>
  <text>{{toastTitle}}</text>
</view>
  • css
#page{
  height: 90%;
overflow-y: auto;

}
.content{
  background: white;

}

.inputRoom {
  width: 100vw;
  /* height: 16vw; */
  border-top: 1px solid #cdcdcd;
  position: fixed;
  bottom: 0;
  display: flex;
  align-items: center;
  z-index: 20;
  background: white;
  flex-direction: column;

}
.bottomRow{
  width: 100%;
  height: 16vw;
display: flex;
align-items: center;
flex-direction: row
}
.bottomRow .van-row{
  width: 100%;

}
.emoji{
  height: 30vw;
}
input {
width: 90%;
height: 9.33vw;
background-color: #EEF4FA;
border-radius: 6rpx;
font-size: 28rpx;
color: #444;
padding: 0 3%;
margin-left: 2%;
}

.leftMsg {
  font-size: 26rpx;
  color: #333333;
line-height: 6vw;
padding: 2vw 2.5vw;
background-color: #EEF4FA;
  border-radius: 10rpx;
  z-index: 10;
}

.rightMsg {
  font-size: 26rpx;
  color: white;
  line-height: 6vw;
  padding: 2vw 2.5vw;
  background-color: #496DFF;
  border-radius: 10rpx;
  z-index: 10;
}



.chatFrame{
  background: white;
  height: 100%
} 
.icon{
  line-height: 8vw;

}
.head{
  display: flex;
  align-items: center
}
.headImg{
  border-radius: 50%; width: 60rpx;height: 60rpx;
}
.holdTape{
  width: 90%;
height: 9.33vw;
background-color: #EEF4FA;
border-radius: 6rpx;
padding: 0 3%;
margin-left: 2%;
display: flex;
align-items: center;
justify-content: center;

}

.recordDailog{
  -webkit-transition-duration: 300ms;
transition-duration: 300ms;
z-index: 1000;
position: fixed;
top: 50%;
left: 50%;
width: -webkit-fit-content;
width: fit-content;
-webkit-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
max-width: var(--toast-max-width,70%);

}

.show{
width: var(--toast-default-width,90px);
min-height: var(--toast-default-min-height,90px);
padding: var(--toast-default-padding,16px);
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
box-sizing: initial;
color: var(--toast-text-color,#fff);
font-size: var(--toast-font-size,14px);
line-height: var(--toast-line-height,20px);
white-space: pre-wrap;
word-wrap: break-word;
background-color: var(--toast-background-color,rgba(50,50,51,.88));
border-radius: var(--toast-border-radius,4px);

}
.show image{
  width: 24px;
  height: 24px
}
image{
  /* max-width: 88vw;
max-height: 400rpx; */
width: 100%
}
.img{
background: none;
width: 90%
}
.contentRight{
  display: flex; justify-content: flex-end; padding: 2vw 2vw 2vw 11vw;width: 86%;
}
.contentLeft{
  display: flex; padding: 2vw 11vw 2vw 2vw;width: 86%;
}
  • js
// pages/contact/contact.js
const {
  pageFunc
} = require('../../utils/util.js');
const app = getApp();
var windowWidth = wx.getSystemInfoSync().windowWidth;
var windowHeight = wx.getSystemInfoSync().windowHeight;
var keyHeight = 0;
const { CHAT_DATA}=require("../../data/customerService.js");
import Toast from '../../components/vant/toast/toast';
const recorderManager = wx.getRecorderManager();
const innerAudioContext = wx.createInnerAudioContext();
const db = wx.cloud.database();
/**
 * 初始化数据
 */
/**
 * 计算msg总高度
 */
function calScrollHeight(that, keyHeight) {
  var query = wx.createSelectorQuery();
  query.select('.scrollMsg').boundingClientRect(function(rect) {
  }).exec();
}
Page({

  /**
   * 页面的初始数据
   */
  data: {
    scrollHeight: '100vh',
    inputVal:"",
    inputBottom: 0,
    chatData:[],
    show:true,
    showDailog:false,
    bottomHeight:"18vw",
    sendData:{},
    pagination: {
      pageSize: 5,
      currentPage: 1,
      total: 0,
    },
    showEmoji:false,
    toastTitle:"录音中...."
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    // this.setData({
    //   cusHeadIcon: app.globalData.userInfo.avatarUrl,
    // });
    const {
      pagination
    } = this.data
    this.getData({ param: CHAT_DATA, pagination });
      wx.pageScrollTo({
        scrollTop: 1000
      })
  },
  getData(params) {
    const { chatData } = this.data;
    const {
      param,
      pagination: {
        pageSize = 10,
        currentPage = 1
      },
    } = params;
    this.setData({
      pagination: { pageSize, currentPage }
    });
    const {
      data,
      pagination
    } = pageFunc(param, currentPage, pageSize);
    data.forEach((item) => {
      if (item.duration) {
        item.duration = Math.ceil(item.duration / 1000)
      }
    });
      this.setData({
        'chatData': data.concat(chatData)
      });
  },
  startRecord(){//开始录音
  const {show}=this.data
    if (show){
      this.setData({
        show: false,
      })
    }else{
      this.setData({
        show: true,
      })
    }
   
    
  },
  startTalk(e){//开始说话
    this.setData({
      showDailog:true,
    })
    const options = {
      duration: 60000,
      sampleRate: 44100,
      numberOfChannels: 1,
      encodeBitRate: 192000,
      format: 'aac',
      frameSize: 50
    }
    recorderManager.start(options)
    recorderManager.onStart((res) => {
    })
  },
  stopRecord(){//停止说话
    const that=this
    this.setData({
      showDailog:false,
    })
    recorderManager.stop();
    recorderManager.onStop((res) => {
      const { sendData, chatData } = that.data;
      let { tempFilePath, duration, fileSize} = res
      sendData.tempFilePathData=res
      duration = Math.ceil(duration / 1000)
      const data = { content: tempFilePath, duration , fileSize, contentType: 'voice', type: 1};
      chatData.push(data);
      wx.createSelectorQuery().select('.content').boundingClientRect(function (rect) {
        // 使页面滚动到底部
        wx.pageScrollTo({
          scrollTop: rect.bottom + 5000
        })
      }).exec();
      that.setData({
        'tempFilePath': tempFilePath,
        sendData,
        chatData,
        scrollHeight: (windowHeight - 0) + 'px',
        toView: 'msg-' + (chatData.length - 1),
        inputBottom: '0px'
      })
    })
  },

  playRecord(e){//播放语音
    const { currentTarget: { dataset: { duration } }}=e;
    const { tempFilePath} = this.data
    innerAudioContext.autoplay = true;
    innerAudioContext.src = tempFilePath ,
      innerAudioContext.onPlay(() => {
        this.setData({
          toastTitle: "播放中....",
          showDailog: true,
        })
      })
    innerAudioContext.onEnded((res) => {
      this.setData({
        toastTitle: "录音中....",
        showDailog: false,
      })
    })
    innerAudioContext.onError((res) => {
    });
    innerAudioContext.play()
  },

  uploadeImg(e){
    const { file: { path,size:fileSize} } = e.detail;
    const { chatData } = this.data;
    const data = { content: path, fileSize, contentType: 'img', type: 1 };
    chatData.push(data);
    wx.createSelectorQuery().select('.content').boundingClientRect(function (rect) {
      // 使页面滚动到底部
      wx.pageScrollTo({
        scrollTop: rect.bottom + 5000
      })
    }).exec()
    this.setData({
      chatData,
      scrollHeight: (windowHeight - 0) + 'px',
      toView: 'msg-' + (chatData.length - 1),
      inputBottom:'0px'
    })
  },
  getEmoji(){//获取表情包
    wx.createSelectorQuery().select('.content').boundingClientRect(function (rect) {
      // 使页面滚动到底部
      wx.pageScrollTo({
        scrollTop: rect.bottom + 5000
      })
    }).exec()
  this.setData({
    showEmoji:true,
    bottomHeight:"48vw"
  })
  },

  clickEmoji: function (e) {//选择表情包
    const {
      detail: {
        value
      },
      currentTarget: {
        dataset: {
          key
        }
      }
    } = e;
    this.setData({
      [key]: value
    })
  },

  onPreview(e){
    const {
      currentTarget: {
        dataset: {
          src
        }
      }
    } = e;
    const urls = [src]
    wx.previewImage({
      current: src,
      urls
    })
  },
  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {
    let { pagination: { currentPage } } = this.data
    this.getData({
      param: CHAT_DATA,
      pagination: {
        pageSize: 5,
        currentPage: currentPage + 1,
      }
    });
  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 获取聚焦
   */
  inputValue(e){
    const {detail:{value}}=e
    this.setData({
      inputVal:value
    })
  },
  focus: function (e) {
    const { chatData}=this.data
    keyHeight = e.detail.height;
    wx.pageScrollTo({
      scrollTop: windowHeight - keyHeight
    })
    this.setData({
      toView: 'msg-' + (chatData.length - 1),
      inputBottom: keyHeight + 'px',
      scrollHeight: (windowHeight - keyHeight) + 'px',
      showEmoji:false,
      bottomHeight: "18vw"
    })
    //计算msg高度
    // calScrollHeight(this, keyHeight);

  },

  //失去聚焦(软键盘消失)
  blur: function (e) {
    const { chatData } = this.data
    this.setData({
      scrollHeight: '100vh',
      inputBottom: 0
    })
    this.setData({
      toView: 'msg-' + (chatData.length - 1)
    })

  },

  /**
   * 发送点击监听
   */
  sendClick: function (e) {
    const { chatData, scrollHeight}=this.data;
    wx.createSelectorQuery().select('.content').boundingClientRect(function (rect) {
      // 使页面滚动到底部
      wx.pageScrollTo({
        scrollTop: rect.bottom + 5000
      })
    }).exec()
  
    chatData.push({
      type: 1,
      contentType: 'text',
      content: e.detail.value,
      headImg: '../../assets/common/images/headHortrait.jpeg',
    })
    this.setData({
      chatData,
      inputVal:''
    });


  },

  /**
   * 退回上一页
   */
  toBackClick: function () {
    wx.navigateBack({})
  }

})// pages/contact/contact.js
const {
  pageFunc
} = require('../../utils/util.js');
const app = getApp();
var windowWidth = wx.getSystemInfoSync().windowWidth;
var windowHeight = wx.getSystemInfoSync().windowHeight;
var keyHeight = 0;
const { CHAT_DATA}=require("../../data/customerService.js");
import Toast from '../../components/vant/toast/toast';
const recorderManager = wx.getRecorderManager();
const innerAudioContext = wx.createInnerAudioContext();
const db = wx.cloud.database();
/**
 * 初始化数据
 */
/**
 * 计算msg总高度
 */
function calScrollHeight(that, keyHeight) {
  var query = wx.createSelectorQuery();
  query.select('.scrollMsg').boundingClientRect(function(rect) {
  }).exec();
}
Page({

  /**
   * 页面的初始数据
   */
  data: {
    scrollHeight: '100vh',
    inputVal:"",
    inputBottom: 0,
    chatData:[],
    show:true,
    showDailog:false,
    bottomHeight:"18vw",
    sendData:{},
    pagination: {
      pageSize: 5,
      currentPage: 1,
      total: 0,
    },
    showEmoji:false,
    toastTitle:"录音中...."
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    // this.setData({
    //   cusHeadIcon: app.globalData.userInfo.avatarUrl,
    // });
    const {
      pagination
    } = this.data
    this.getData({ param: CHAT_DATA, pagination });
      wx.pageScrollTo({
        scrollTop: 1000
      })
  },
  getData(params) {
    const { chatData } = this.data;
    const {
      param,
      pagination: {
        pageSize = 10,
        currentPage = 1
      },
    } = params;
    this.setData({
      pagination: { pageSize, currentPage }
    });
    const {
      data,
      pagination
    } = pageFunc(param, currentPage, pageSize);
    data.forEach((item) => {
      if (item.duration) {
        item.duration = Math.ceil(item.duration / 1000)
      }
    });
      this.setData({
        'chatData': data.concat(chatData)
      });
  },
  startRecord(){//开始录音
  const {show}=this.data
    if (show){
      this.setData({
        show: false,
      })
    }else{
      this.setData({
        show: true,
      })
    }
   
    
  },
  startTalk(e){//开始说话
    this.setData({
      showDailog:true,
    })
    const options = {
      duration: 60000,
      sampleRate: 44100,
      numberOfChannels: 1,
      encodeBitRate: 192000,
      format: 'aac',
      frameSize: 50
    }
    recorderManager.start(options)
    recorderManager.onStart((res) => {
    })
  },
  stopRecord(){//停止说话
    const that=this
    this.setData({
      showDailog:false,
    })
    recorderManager.stop();
    recorderManager.onStop((res) => {
      const { sendData, chatData } = that.data;
      let { tempFilePath, duration, fileSize} = res
      sendData.tempFilePathData=res
      duration = Math.ceil(duration / 1000)
      const data = { content: tempFilePath, duration , fileSize, contentType: 'voice', type: 1};
      chatData.push(data);
      wx.createSelectorQuery().select('.content').boundingClientRect(function (rect) {
        // 使页面滚动到底部
        wx.pageScrollTo({
          scrollTop: rect.bottom + 5000
        })
      }).exec();
      that.setData({
        'tempFilePath': tempFilePath,
        sendData,
        chatData,
        scrollHeight: (windowHeight - 0) + 'px',
        toView: 'msg-' + (chatData.length - 1),
        inputBottom: '0px'
      })
    })
  },

  playRecord(e){//播放语音
    const { currentTarget: { dataset: { duration } }}=e;
    const { tempFilePath} = this.data
    innerAudioContext.autoplay = true;
    innerAudioContext.src = tempFilePath ,
      innerAudioContext.onPlay(() => {
        this.setData({
          toastTitle: "播放中....",
          showDailog: true,
        })
      })
    innerAudioContext.onEnded((res) => {
      this.setData({
        toastTitle: "录音中....",
        showDailog: false,
      })
    })
    innerAudioContext.onError((res) => {
    });
    innerAudioContext.play()
  },

  uploadeImg(e){
    const { file: { path,size:fileSize} } = e.detail;
    const { chatData } = this.data;
    const data = { content: path, fileSize, contentType: 'img', type: 1 };
    chatData.push(data);
    wx.createSelectorQuery().select('.content').boundingClientRect(function (rect) {
      // 使页面滚动到底部
      wx.pageScrollTo({
        scrollTop: rect.bottom + 5000
      })
    }).exec()
    this.setData({
      chatData,
      scrollHeight: (windowHeight - 0) + 'px',
      toView: 'msg-' + (chatData.length - 1),
      inputBottom:'0px'
    })
  },
  getEmoji(){//获取表情包
    wx.createSelectorQuery().select('.content').boundingClientRect(function (rect) {
      // 使页面滚动到底部
      wx.pageScrollTo({
        scrollTop: rect.bottom + 5000
      })
    }).exec()
  this.setData({
    showEmoji:true,
    bottomHeight:"48vw"
  })
  },

  clickEmoji: function (e) {//选择表情包
    const {
      detail: {
        value
      },
      currentTarget: {
        dataset: {
          key
        }
      }
    } = e;
    this.setData({
      [key]: value
    })
  },

  onPreview(e){
    const {
      currentTarget: {
        dataset: {
          src
        }
      }
    } = e;
    const urls = [src]
    wx.previewImage({
      current: src,
      urls
    })
  },
  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {
    let { pagination: { currentPage } } = this.data
    this.getData({
      param: CHAT_DATA,
      pagination: {
        pageSize: 5,
        currentPage: currentPage + 1,
      }
    });
  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 获取聚焦
   */
  inputValue(e){
    const {detail:{value}}=e
    this.setData({
      inputVal:value
    })
  },
  focus: function (e) {
    const { chatData}=this.data
    keyHeight = e.detail.height;
    wx.pageScrollTo({
      scrollTop: windowHeight - keyHeight
    })
    this.setData({
      toView: 'msg-' + (chatData.length - 1),
      inputBottom: keyHeight + 'px',
      scrollHeight: (windowHeight - keyHeight) + 'px',
      showEmoji:false,
      bottomHeight: "18vw"
    })
    //计算msg高度
    // calScrollHeight(this, keyHeight);

  },

  //失去聚焦(软键盘消失)
  blur: function (e) {
    const { chatData } = this.data
    this.setData({
      scrollHeight: '100vh',
      inputBottom: 0
    })
    this.setData({
      toView: 'msg-' + (chatData.length - 1)
    })

  },

  /**
   * 发送点击监听
   */
  sendClick: function (e) {
    const { chatData, scrollHeight}=this.data;
    wx.createSelectorQuery().select('.content').boundingClientRect(function (rect) {
      // 使页面滚动到底部
      wx.pageScrollTo({
        scrollTop: rect.bottom + 5000
      })
    }).exec()
  
    chatData.push({
      type: 1,
      contentType: 'text',
      content: e.detail.value,
      headImg: '../../assets/common/images/headHortrait.jpeg',
    })
    this.setData({
      chatData,
      inputVal:''
    });


  },

  /**
   * 退回上一页
   */
  toBackClick: function () {
    wx.navigateBack({})
  }

})

lulu
23 声望2 粉丝

« 上一篇
FastClick用法