1. 程式人生 > >微信小程式聊天功能 WebSocket 實現傳送文字,圖片,語音以及WebSocket 常見問題解決方案

微信小程式聊天功能 WebSocket 實現傳送文字,圖片,語音以及WebSocket 常見問題解決方案

如果對你有幫助,來個關注加好評,謝謝。

小程式 WebSocket 常見問題:本文已解決的

1.自動斷開連結,重連但是隻能存在兩個 WebSocket 的問題。

  ---1相容情況:1.1 正常聊天過一段時間 WebSocket 自動斷開後重新連結,並且儲存之前的聊天記錄

  ---1相容情況:1.2 在使用者黑屏但是沒退出小程式過一段時間時 WebSocket 自動斷開連結後重新連結,並且儲存之前的聊天記錄

  ---1相容情況:1.3 在聊天室的頁面,點選右上角返回按鈕,頁面會自動執行解除安裝,這個時候 WebSocket 是沒有銷燬的,再次進入時會同時存在兩個WebSocket,第三次進入就會報錯了(只能同時存在兩個 WebSocket )。

解決方案:因為需要相容的情況比較多,解決方案可詳見程式碼。具體思路就是,新增一個自動重連的開關。(必須WebSocket 銷燬以後才能新建WebSocket )根據情況判斷是否重連 WebSocket 。

2. 錄音成功,但是傳送給後端接收不到語音檔案。

解決辦法:在已錄製完指定幀大小的事件回撥函式中, 使用 wx.arrayBufferToBase64(res.frameBuffer),把得到的arrayBuffer 轉為 Base64 再傳給後端,同時設定 signType: 'BASE64'。

3.錄音傳輸給後端時,後端BASE64 解碼失敗的問題。

解決方案:與後端確認 錄音檔案的取樣率、編碼位元速率、音訊格式、幀大小是否一致。

推薦設定:

    var recorder = wx.getRecorderManager();
    const options = {
      duration: 10000, //指定錄音的時長,單位 ms
      sampleRate: 16000, //取樣率
      numberOfChannels: 1, //錄音通道數
      encodeBitRate: 24000, //編碼位元速率
      format: 'mp3', //音訊格式,有效值 aac/mp3
      frameSize: 12, //指定幀大小,單位 KB
    }
    recorder.start(options) //開始錄音

4.如何自動把頁面聚焦在最新的聊天資訊

效果圖:

解決方案:在聊天資訊的list 賦值成功後執行一次公共頁面聚焦的方法:

  // 公共聚焦方法,方法比較笨,但是過度效果平滑流暢
  bottom: function() {
    var that = this;
    this.setData({
      scrollTop: 100000
    })
  },

//呼叫示例:
      this.setData({
        allContentList: that.data.allContentList,
      })
      this.bottom();

5.使用者輸入空格併發送,這個時候聊天冒泡框樣式肯定會變形,因為沒有空格行高。

解決方案: css 設定冒泡框最低高度  ----  min-height: 80rpx;(數值根據需求自定義)

6.聊天冒泡框的小三角形怎麼實現?三角形還想要給它邊框,又如何實現?

效果圖:

實現步驟:設定文字冒泡框的樣式-在文字冒泡框內新建一個盒子設定相對定位,裡面放一個em和一個span標籤,設定絕對定位,利用邊框設定透明色,示例程式碼:

<view class='new_txt_ai'>
  <view class='arrow'>
     <em></em>
     <span></span>
  </view>
  <view class='ai_content'>
    121(一百二十一)是 120與122之間的一個自然數。它也是奇數、合數、平方數
  </view>
</view>
.new_txt_ai {
  width: 460rpx;
  border-radius: 7rpx;
  left: 20rpx;
  background-color: #fff;
  position: relative;
  border: 1px solid #d0d0d0;
  float: left;
}

.new_txt_ai .arrow {
  position: relative;
  width: 40rpx;
  left: -30rpx;
}

.new_txt_ai .arrow em {
  position: absolute;
  border-style: solid;
  border-width: 15rpx;
  top: 20rpx;
  border-color: transparent #d0d0d0 transparent transparent;
}

.new_txt_ai .arrow span {
  position: absolute;
  top: 20rpx;
  border-style: solid;
  border-width: 15rpx;
  border-color: transparent #fff transparent transparent;
  left: 2rpx;
}

.ai_content {
  word-break: break-all;
  padding: 17rpx 30rpx 17rpx 30rpx;
}

還有很多常見問題就不多囉嗦了,直接上程式碼吧。因業務原因,傳送圖片和錄音功能的程式碼暫時註釋,註釋開啟可用。

聊天室實現效果圖:

全部相關程式碼,程式碼邏輯比較多但是思路清晰,可塑性較強。提供借鑑參考。

js原始碼

// pages/index/to_news/to_news.js  
var app = getApp();
var util = require("../../utils/util.js");
var socketOpen = false;
var uuid = '',
  time_ = "1";
var recorder = wx.getRecorderManager();
const innerAudioContext = wx.createInnerAudioContext() //獲取播放物件
var frameBuffer_Data, session, SocketTask, string_base64, open_num = 0, submitTo_string,
  onUnload_num = 0,
  autoRestart, onHide_s = false;
Page({
  data: {
    listCustmerServiceBanner: [],
    indicatorDots: false,
    autoplay: false,
    interval: 5000,
    duration: 1000,
    user_input_text: '', //使用者輸入文字
    inputValue: '',
    time: '',
    returnValue: '',
    if_send: false,
    add: true,
    cross: false,
    // is_my: true, text: '12432'
    allContentList: [{}, {
      is_ai: []
    }],
    num: 0
  },
  // 頁面載入
  onLoad: function(e) {
    autoRestart = true; //是否重啟
    console.log('onLoad')
    if (e && e.ofOperatorType) {
      this.setData({
        ofOperatorType: e.ofOperatorType
      })
    } else {
      this.setData({
        ofOperatorType: 2
      })
    }
    // if (onUnload_num < 1) {
    this.webSocket_open()
    // }
  },
  onShow: function(e) {
    onHide_s = false
  },

  onHide: function() {
    autoRestart = false;
    onHide_s = true
    console.log('onHide')
  },
  onUnload: function() {
    onUnload_num++;
    autoRestart = false;
    console.log('onUnload')
    this.close();
  },
  // 頁面載入完成
  onReady: function() {
    var that = this;
    this.on_recorder();
    this.bottom()
  },
  // 建立websocket
  webSocket_open: function () {
    var that = this;
    console.log('開始建立')
    // 建立Socket
    SocketTask = wx.connectSocket({
      url: app.webS_url,
      header: {
        'content-type': 'application/json'
      },
      method: 'post',
      success: function(res) {
        console.log('WebSocket連線建立', res)
      },
      fail: function(err) {
        wx.showToast({
          title: '網路異常!',
        })
        console.log(err)
      },
    })
    that.initSocket();
  },

  // 提交文字
  submitTo: function(e) {
    submitTo_string =false
    console.log('提交文字')
    console.log("SocketTask", SocketTask)
    let that = this;
    if (that.data.inputValue == "") {
      return;
    }
    var data = {
      cmd: 1,
      type: 1,
      signType: 'BASE64',
      session: session,
      body: that.data.inputValue,
    }
    console.log('提交文字data:', socketOpen, data)
    if (socketOpen) {
      // 如果打開了socket就傳送資料給伺服器
      sendSocketMessage(data)
      if (session != undefined && session != null) {
        this.data.allContentList.push({
          is_my: true,
          text: this.data.inputValue
        });
        this.setData({
          allContentList: this.data.allContentList,
          if_send: false,
          inputValue: ''
        })

      }
      that.bottom()
    } else {
      submitTo_string=true;
      this.webSocket_open()
    }
  },

  // socket監聽事件
  initSocket: function () {
    var that = this;
    console.log("aaa", SocketTask)
    SocketTask.onOpen(res => {
      socketOpen = true;
      open_num++
      if (session == undefined || session == null) {
        // repositoryType = 1 聯通 2 移動 3 電信
        // ofType	int		進入客服小程式型別 1, 小程式跳轉 2,搜尋
        //  ofOperatorType	int	否	運營商型別1,移動 2,聯通 3,電信
        //  wy_appid	String	否	小程式appid
        if (app.appid) {
          var data = {
            cmd: 2,
            ofType: 1,
            wy_appid: app.appid
          }
        } else {
          var data = {
            cmd: 2,
            ofType: 2,
            ofOperatorType: that.data.ofOperatorType
            // ofOperatorType: 1
          }
        }
        sendSocketMessage(data)
      }
      console.log('監聽 WebSocket 連線開啟事件。', res)
    })
    SocketTask.onClose(onClose => {
      console.log('監聽 WebSocket 連線關閉事件。', onClose)
      session = null;
      SocketTask = false;
      socketOpen = false;
      // if (!autoRestart && onHide_s) {
      //   this.webSocket_open()
      // }
      // if (autoRestart) {
      //   this.webSocket_open()
      // }
    })
    SocketTask.onError(onError => {
      console.log('監聽 WebSocket 錯誤。錯誤資訊', onError)
      session = null;
    })
    SocketTask.onMessage(onMessage => {
      var onMessage_data = JSON.parse(onMessage.data);
      console.log("onMessage:", onMessage_data)
      // if (onMessage_data == 'session為空') {
      //   if (submitTo_string) {
      //     console.log('submitTo_string2222222222')
      //     that.submitTo()
      //   }
      //   return;
      // }
      if (onMessage_data.minipTitle) {
        wx.setTopBarText({
          text: onMessage_data.minipTitle,
        })
      }
      let is_ai_arr = onMessage_data.body;
      // 登入。預設傳送一條訊息給使用者展示,不展示已解決未解決
      if (onMessage_data.cmd == 3) {
        that.session_pro = new Promise(function (resolve) {
          session = onMessage_data.session;
          if (submitTo_string) {
            console.log('submitTo_string11111111')
            that.submitTo()
          }
          resolve(session)
        })
        var messageTime = util.formatTime(onMessage_data.messageTime);
        // if (open_num < 2){

        if (is_ai_arr.length == 1) {
          that.data.allContentList.push({
            is_ai: is_ai_arr,
            solve_show: false,
            show_answer: true,
            messageTime: messageTime
          });
        } else {
            console.log('is_ai_arr:', is_ai_arr)
            that.data.allContentList.push({
              is_ai: is_ai_arr,
              show_answer: false,
              solve_show: false,
              messageTime: messageTime
            });
          }
        // }
        this.setData({
          listCustmerServiceBanner: onMessage_data.listCustmerServiceBanner,
          staffServicePhone: onMessage_data.staffServicePhone,
          allContentList: that.data.allContentList
        })
      } else {
        // 正常接收訊息
        uuid = onMessage_data.messageRecordUuid;
        var messageTime;
        time_ = onMessage_data.messageTime;
        if (time_ + 1000 * 60 * 10 > onMessage_data.messageTime) {
          messageTime = 0;
        } else {
          messageTime = util.formatTime(onMessage_data.messageTime);
        }
        let arr_list = that.data.allContentList
        if (is_ai_arr.length == 1) {
          arr_list.push({
            show_answer: true,
            is_ai: is_ai_arr,
            messageTime: messageTime,
            solve_show: true,
            no_problem: false,
            yse_problem: false
          });
        } else {
          arr_list.push({
            show_answer: false,
            is_ai: is_ai_arr,
            messageTime: messageTime,
            solve_show: true,
            no_problem: false,
            yse_problem: false
          });
        }
        that.setData({
          allContentList: arr_list
        })
      }
      that.bottom();
    })
  },
  // 點選輪播圖
  swiper_item_click: function (e) {
    var id = e.target.id
    console.log(id);
    var item_banners = this.data.listCustmerServiceBanner[id];
    var page = item_banners.page;
    // 型別1、自己小程式、2、其它小程式 3、H5
    switch (item_banners.type) {
      case 1:
        wx.navigateTo({
          url: page,
        })
        break;
      case 2:
        wx.navigateToMiniProgram({
          appId: item_banners.appid,
          path: page,
          extraData: {},
          envVersion: 'release',
          success(res) {
            // 開啟成功
          }
        })
        break;
      case 3:
        wx.navigateTo({
          url: web + '?url=' + page,
        })
        break;
    }
  },
  // 關閉
  close: function (e) {
    if (SocketTask) {
      SocketTask.close(function (close) {
        console.log('關閉 WebSocket 連線。', close)
      })
    }
  },
  // 解決問題
  is_problem: function(e) {
    console.log('e.target.id', e.currentTarget.dataset.id)
    console.log('item', e.currentTarget.dataset.item)
    var id = e.currentTarget.dataset.id;
    var item = e.currentTarget.dataset.item;
    // id=1 已解決  0 未解決
    var yse_problem = this.data.allContentList[item].yse_problem;
    var no_problem = this.data.allContentList[item].no_problem;
    if (yse_problem || no_problem) {
      console.log(12)
      return
    } else {
      if (id == 1) {
        this.setData({
          ['allContentList[' + item + '].yse_problem']: true
        })
      } else if (id == 0) {
        this.setData({
          ['allContentList[' + item + '].no_problem']: true
        })
      }
      console.log(this.data.allContentList[item].yse_problem, this.data.allContentList[item].no_problem)
      this.bottom();
    }
    var url = app.httpUrl + '/v1/userFeedbackResult.do'
    var data = {
      'session': app.http_session,
      'type': id,
      'uuid': uuid
    }
    console.log('userFeedbackResult提交的資料:', data)
    util.request(url, 'POST', data, '', function(res) {
      console.log('userFeedbackResult返回的資料:', res.data)

    }, function(err) {
      console.log(err)
    })
  },
  // 跳轉小程
  minip: function(e) {
    console.log(e)
    wx.navigateToMiniProgram({
      appId: e.target.dataset.appid,
      path: e.target.dataset.path,
      extraData: {},
      envVersion: 'develop',
      success(res) {
        // 開啟成功
      }
    })
  },
  // 跳轉WEB
  link: function(e) {
    console.log(e.target.id)
    wx.navigateTo({
      url: '../web/web?link=' + e.target.id,
    })
  },
  // 點選加號
  add_icon_click: function(e) {
    console.log(e.target.id)
    // e.target.id == 1 點選加號   ==2  點選 X
    if (e.target.id == 2) {
      this.setData({
        add: true,
        cross: false,
        input_bottom: 0
      })
    } else if (e.target.id == 1) {
      this.setData({
        add: false,
        cross: true,
        input_bottom: 240
      })
    }
  },
  // 自動新增問題答案
  add_question: function(e) {
    var that = this;
    let answer = e.currentTarget.dataset.answer;
    let messageTime = e.currentTarget.dataset.messagetime;
    let question = e.currentTarget.dataset.question;
    console.log('question:', question, 'answer:', answer, 'messageTime', messageTime);
    this.data.allContentList.push({
      is_my: true,
      text: question
    });
    this.setData({
      allContentList: this.data.allContentList,
      if_send: false,
      inputValue: ''
    })
    that.bottom();
    setTimeout(function() {
      that.data.allContentList.push({
        is_ai: [{
          answer: answer,
          type: 1
        }],
        solve_show: true,
        show_answer: true,
        messageTime: false,
        text: question
      });
      that.setData({
        allContentList: that.data.allContentList,
      })
      that.bottom();
    }, 1000)
  },
  // 撥打電話
  phone_click: function() {
    var that = this;
    wx.showModal({
      title: '',
      content: '是否撥打' + that.data.staffServicePhone + '人工客服電話',
      success: function(res) {
        if (res.confirm) {
          wx.makePhoneCall({
            phoneNumber: that.data.staffServicePhone //僅為示例,並非真實的電話號碼  
          })
        } else if (res.cancel) {
          console.log('使用者點選取消')
        }
      }
    })
  },
  // 輸入框
  bindKeyInput: function(e) {
    console.log(e.detail.value)
    if (e.detail.value == "") {
      this.setData({
        if_send: false,
        inputValue: e.detail.value
      })
    } else {
      this.setData({
        if_send: true,
        inputValue: e.detail.value
      })
    }
  },
  // 獲取到焦點
  focus: function(e) {
    var that = this;
    console.log(e.detail.height)
    this.setData({
      focus: true,
      add: true,
      cross: false,
      input_bottom: e.detail.height
    })
  },
  // 失去焦點
  no_focus: function(e) {
    if (this.data.cross) {
      this.setData({
        focus: false,
        input_bottom: 240,
      })
    } else {
      this.setData({
        focus: false,
        input_bottom: 0
      })
    }
  },
  // 獲取hei的id節點然後螢幕焦點調轉到這個節點  
  bottom: function() {
    var that = this;
    this.setData({
      scrollTop: 100000
    })
  },
  hide_bg: function() {
    this.setData({
      block: false
    })
  },
  // 點選錄音事件
  my_audio_click: function(e) {
    console.log('my_audio_click執行了', e)
    var index = e.currentTarget.dataset.id;
    console.log('url地址', this.data.allContentList[index].audio);
    innerAudioContext.src = this.data.allContentList[index].audio
    innerAudioContext.seek(0);
    innerAudioContext.play();
  },
  // 手指點選錄音
  voice_ing_start: function() {
    var that = this;
    this.setData({
      voice_ing_start_date: new Date().getTime(), //記錄開始點選的時間
    })
    const options = {
      duration: 10000, //指定錄音的時長,單位 ms
      sampleRate: 16000, //取樣率
      numberOfChannels: 1, //錄音通道數
      encodeBitRate: 24000, //編碼位元速率
      format: 'mp3', //音訊格式,有效值 aac/mp3
      frameSize: 12, //指定幀大小,單位 KB
    }
    recorder.start(options) //開始錄音

    this.animation = wx.createAnimation({
      duration: 1200,
    }) //播放按鈕動畫
    that.animation.scale(0.8, 0.8); //還原
    that.setData({

      spreakingAnimation: that.animation.export()
    })
  },
  // 錄音監聽事件
  on_recorder: function() {
    var that = this;
    recorder.onStart((res) => {
      console.log('開始錄音');
    })
    recorder.onStop((res) => {
      console.log('停止錄音,臨時路徑', res.tempFilePath);
      // _tempFilePath = res.tempFilePath;
      var x = new Date().getTime() - this.data.voice_ing_start_date
      if (x > 1000) {
        that.data.allContentList.push({
          is_my: true,
          audio: res.tempFilePath,
          length: x / 1000 * 30
        });
        that.setData({
          allContentList: that.data.allContentList
        })
      }
    })
    recorder.onFrameRecorded((res) => {
      var x = new Date().getTime() - this.data.voice_ing_start_date
      if (x > 1000) {
        console.log('onFrameRecorded  res.frameBuffer', res.frameBuffer);
        string_base64 = wx.arrayBufferToBase64(res.frameBuffer)

        // console.log('string_base64--', wx.arrayBufferToBase64(string_base64))
        if (res.isLastFrame) {
          that.session_pro.then(function(session) {
            var data = {
              audioType: 3,
              cmd: 1,
              type: 2,
              signType: 'BASE64',
              session: session,
              body: string_base64,
            }
            console.log('that.data.allContentList', that.data.allContentList)
            sendSocketMessage(data)
          })
          // 進行下一步操作
        } else {
          that.session_pro.then(function(session) {
            var data = {
              cmd: 1,
              audioType: 2,
              type: 2,
              signType: 'BASE64',
              session: session,
              body: string_base64
            }
            console.log('錄音上傳的data:', data)
            sendSocketMessage(data)
          })
        }
      }
    })
  },
  // 手指鬆開錄音
  voice_ing_end: function() {
    var that = this;
    that.setData({
      voice_icon_click: false,
      animationData: {}
    })
    this.animation = "";
    var x = new Date().getTime() - this.data.voice_ing_start_date
    if (x < 1000) {
      console.log('錄音停止,說話小於1秒!')
      wx.showModal({
        title: '提示',
        content: '說話要大於1秒!',
      })
      recorder.stop();
    } else {
      // 錄音停止,開始上傳
      recorder.stop();
    }
  },
  // 點選語音圖片
  voice_icon_click: function() {
    this.setData({
      voice_icon_click: !this.data.voice_icon_click
    })
  },
})
//通過 WebSocket 連線傳送資料,需要先 wx.connectSocket,並在 wx.onSocketOpen 回撥之後才能傳送。
function sendSocketMessage(msg) {
  var that = this;
  if (app.http_session != "") {
    msg.http_session = app.http_session
    console.log('通過 WebSocket 連線傳送資料', JSON.stringify(msg))
    SocketTask.send({
      data: JSON.stringify(msg)
    }, function(res) {
      console.log('已傳送', res)
    })
  } else {
    app.promise.then(function(http_session) {
      msg.http_session = http_session;
      console.log('通過 WebSocket 連線傳送資料', JSON.stringify(msg));
      SocketTask.send({
        data: JSON.stringify(msg)
      }, function(res) {
        console.log('已傳送', res);
      })

    })

  }

}

wxml原始碼

<!-- <button bindtap='close'>關閉</button>
<button bindtap='open'>開啟</button> -->
<!-- <swiper indicator-dots="{{indicatorDots}}" autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}">
  <block wx:for="{{listCustmerServiceBanner}}" wx:key=''>
    <swiper-item>
      <image src="{{item.picUrl}}" bindtap='swiper_item_click' id='{{index}}' class="slide-image" />
    </swiper-item>
  </block>
</swiper> -->
<view class='page_bg' wx:if='{{block}}' bindtap='hide_bg' />
<view class='btn_bg' wx:if='{{block}}'>
  <view wx:for="{{link_list}}" wx:key='index'>
    <button class="sp_tit" id='{{index}}' bindtap='list_item'>檢視詳情 {{item}} </button>
  </view>
</view>
<scroll-view class="history" scroll-y="true" scroll-with-animation scroll-top="{{scrollTop}}">

  <block wx:key="{{index}}" wx:for="{{allContentList}}">
    <block wx:if="{{item.is_my}}">
      <view class='my_right new_txt'>
        <view class='time' wx:if='{{item.messageTime&&item.messageTime!=0}}'>
          {{item.messageTime}}
        </view>
        <view class='p_r page_r' style='margin-right: 25rpx;' wx:if='{{item.text}}'>
          <view class='new_txt'>
            <view class='new_txt_my'>
              <view class='arrow'>
                <em></em>
                <span></span>
              </view>
              <text decode="true">{{item.text}}</text>
            </view>
          </view>
          <open-data class='new_img' type="userAvatarUrl"></open-data>
        </view>
        <view class='p_r page_r' style='margin-right: 25rpx;' wx:if='{{item.audio}}' bindtap='my_audio_click' data-id='{{index}}'>
          <view class='new_txt_my_2' style=' width:{{item.length}}px'>
            <image class='my_audio' src='/images/yuyin_icon.png'></image>
          </view>
          <span class='_span'></span>
          <open-data class='new_img' type="userAvatarUrl"></open-data>
        </view>
      </view>
    </block>
    <!-- <view class='you_left' id='id_{{allContentList.length}}'> -->
    <block wx:if="{{item.is_ai&&item.is_ai!=''}}">
      <view class='you_left' style='width:100%;' id='id_{{allContentList.length}}' wx:key="{{index}}">
        <view class='time' wx:if='{{item.messageTime}}'>
          {{item.messageTime}}
        </view>
        <view class='p_r' style='margin-left: 20rpx;'>
          <image class='new_img' src='/images/top_img.png'></image>
          <view class='new_txt'>
            <view class='new_txt_ai'>
              <view class='arrow'>
                <em></em>
                <span></span>
              </view>
              <!-- {{item.text}} -->
              <view class='ai_content'>
                <block wx:for='{{item.is_ai}}' wx:for-item='itt' wx:for-index='indexi'  wx:key=''>
                  <text wx:if='{{itt.type=="1"&&item.show_answer}}' decode="true" >{{itt.answer}}</text>
                  <block wx:if='{{itt.type=="1"&&!item.show_answer}}'>
                  <text decode="true" wx:if='{{indexi==0}}'>{{itt.answer}}</text>
                  <view  decode="true" style='color:#0000EE' bindtap='add_question' data-messagetime='{{itt.messageTime}}' data-question='{{itt.question}}' data-answer='{{itt.answer}}'>· {{itt.question}}?</view>
                  </block>
                  <text wx:if='{{item.type=="2"}}' decode="true" style='color:#0000EE' bindtap='link' id='{{item.link}}'>{{item.text}}</text>
                  <image wx:if='{{item.type=="3"}}' style='width:{{item.w}}rpx;height:{{item.h}}rpx;' src='{{item.src}}'></image>
                  <text wx:if='{{item.type=="10"}}' decode="true" data-path='{{item.path}}' data-appid='{{item.appId}}' bindtap='minip'>{{item.text}}</text>
                </block>
              </view>
              <!-- <view class='is_ai_btn' wx:if='{{item.solve_show&&item.is_ai[0].answer!="我不明白"}}'>
                <view bindtap='is_problem' data-id='1' data-item='{{index}}' style=' {{item.yse_problem?"color: red;":""}}'>

                  <image src='{{item.yse_problem?"/images/in_zan.png":"/images/zan.png"}}' /> 已解決
                </view>
                <view bindtap='is_problem' data-id='0' data-item='{{index}}' class='two' style=' {{item.no_problem?"color: #00B1FF;":""}}'>
                  <image src='{{item.no_problem?"/images/in_zan_no.png":"/images/zan_no.png"}}' /> 未解決
                </view>
              </view> -->
              <view class='yes_problem_log' wx:if="{{item.yse_problem&&item.solve_show}}" style=''>感謝您的反饋,我們會再接再厲!</view>
              <view class='yes_problem_log' style='color:#32CF3C' wx:if="{{item.no_problem&&item.solve_show}}" bindtap='phone_click'>撥打人工客服</view>

            </view>
          </view>
        </view>
      </view>
    </block>
  </block>
</scroll-view>
<!-- 遮罩 -->
<view class='zezhao' wx:if='{{cross}}' bindtap='add_icon_click' id='2'></view>
<!-- 輸入框 -->
<view class='{{cross?"in_voice_icon":""}}'>
  <view class="sendmessage" wx:if='{{!cross}}' style='bottom:{{input_bottom}}px'>
    <input type="text" style='{{focus?"border-bottom: 1px solid #88DD4B;":""}}' adjust-position='{{false}}' cursor-spacing='5' bindinput="bindKeyInput" value='{{inputValue}}' focus='{{focus}}' bindblur='no_focus' bindfocus="focus" confirm-type="done" placeholder="請輸入您要諮詢的問題"/>
    <button wx:if='{{if_send&&inputValue!=""}}' bindtap="submitTo" class='user_input_text'>傳送</button>
    <image class='add_icon' bindtap='add_icon_click' id='1' wx:if='{{add&&!if_send&&inputValue==""}}' src='/images/jia_img.png'></image>
    <image class='add_icon' bindtap='add_icon_click' id='2' wx:if='{{cross}}' src='/images/audio/cross37.png'></image>

  </view>
  <view wx:if='{{cross}}' class='item' bindtap='phone_click'>
    <image class='img' src='/images/yuyin_icon.png'></image>
    <view class='text'>人工客服</view>
  </view>
</view>

<!-- <view class='zezhao' wx:if='{{add_icon_click}}' bindtap='add_icon_click'></view> -->
<!-- <view class='in_voice_icon'>
  <view class="sendmessage_2">
    <input type="text" bindinput="bindKeyInput" adjust-position='{{false}}' value='{{inputValue}}' focus='{{focus}}' bindfocus="focus" confirm-type="done" placeholder="" />
    <image class='add_icon' bindtap='add_icon_click' src='/images/audio/cross37.png'></image>
  </view>
  <view class='item' bindtap='phone_click'>
    <image class='img' src='/images/yuyin_icon.png'></image>
    <view class='text'>人工客服</view>
  </view>
</view> -->

wxss原始碼

page {
  background-color: #f2f2f2;
  height: 100%;
  padding: 0 auto;
  margin: 0 auto;
}

swiper {
  height: 180rpx;
}

swiper swiper-item .slide-image {
  width: 100%;
  height: 180rpx;
}

.jia_img {
  height: 80rpx;
  width: 90rpx;
}

.time {
  text-align: center;
  padding: 5rpx 20rpx 5rpx 20rpx;
  border-radius: 10rpx;
  display: block;
  height: 38rpx;
  line-height: 38rpx;
  position: relative;
  margin: 0 auto;
  margin-bottom: 20rpx;
  width: 90rpx;
  color: white;
  font-size: 26rpx;
  background-color: #dedede;
}

.tab {
  bottom: 120rpx;
}

.tab_1 {
  position: fixed;
  bottom: 50rpx;
  width: 200rpx;
  font-size: 26rpx;
  left: 50%;
  margin-left: -45rpx;
  height: 100rpx;
}

.tab_2 {
  right: 30rpx;
  position: fixed;
}

/* 聊天 */

.my_right {
  float: right;
  margin-top: 30rpx;
  position: relative;
}

.my_audio {
  height: 60rpx;
  width: 60rpx;
  z-index: 2;
  position: relative;
  top: 10rpx;
  left: 20rpx;
}

.you_left {
  margin-top: 30rpx;
  float: left;
  position: relative;
  padding-left: 5rpx;
}

.new_img {
  width: 85rpx;
  height: 85rpx;
  overflow: hidden;
}

.page_r {
  float: right;
}

.new_txt {
  min-width: 380rpx;
  width: 460rpx;
  word-break: break-all;
}

.new_txt_my {
  border-radius: 7rpx;
  background: #9fe75a;
  position: relative;
  right: 30rpx;
  min-height: 50rpx;
  padding: 17rpx 30rpx 17rpx 30rpx;
  float: right;
  border: 1px solid #d0d0d0;
}

.new_txt_my .arrow {
  position: absolute;
  z-index: 2;
  width: 40rpx;
  right: -38rpx;
}

.new_txt_my .arrow em {
  position: absolute;
  border-style: solid;
  border-width: 15rpx;
  border-color: transparent transparent transparent #d0d0d0;
  top: 1rpx;
}

.new_txt_my .arrow span {
  position: absolute;
  top: 5rpx;
  border-style: solid;
  border-width: 15rpx;
  border-color: transparent transparent transparent #9fe75a;
}

.new_txt_my_2 {
  word-break: break-all;
  border-radius: 7rpx;
  background: #9fe75a;
  min-width: 330rpx;
  max-width: 530rpx;
  padding: 17rpx 30rpx 17rpx 30rpx;
  float: right;
}

.new_txt_ai {
  width: 460rpx;
  border-radius: 7rpx;
  left: 20rpx;
  background-color: #fff;
  position: relative;
  border: 1px solid #d0d0d0;
  float: left;
}

.new_txt_ai .arrow {
  position: relative;
  width: 40rpx;
  left: -30rpx;
}

.new_txt_ai .arrow em {
  position: absolute;
  border-style: solid;
  border-width: 15rpx;
  top: 20rpx;
  border-color: transparent #d0d0d0 transparent transparent;
}

.new_txt_ai .arrow span {
  position: absolute;
  top: 20rpx;
  border-style: solid;
  border-width: 15rpx;
  border-color: transparent #fff transparent transparent;
  left: 2rpx;
}

.ai_content {
  word-break: break-all;
  padding: 17rpx 30rpx 17rpx 30rpx;
}

.sanjiao {
  top: 25rpx;
  position: relative;
  width: 0px;
  height: 0px;
  border-width: 15rpx;
  border-style: solid;
}

.my {
  border-color: transparent transparent transparent #9fe75a;
}

.you {
  border-color: transparent #fff transparent transparent;
}

._span {
  border-color: #fff transparent transparent;
  top: -17px;
}

.is_ai_btn {
  border-radius: 0 0 7px 7px;
  border-top: 1px solid #d0d0d0;
  background: white;
  position: relative;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 80rpx;
  line-height: 80rpx;
  display: flex;
  flex-direction: row;
  text-align: center;
}

.is_ai_btn view {
  width: 50%;
}

.is_ai_btn image {
  width: 32rpx;
  position: relative;
  top: 4rpx;
  height: 32rpx;
}

.is_ai_btn .two {
  border-left: 1px solid #d0d0d0;
}

.yes_problem_log {
  border-top: 1px solid #d0d0d0;
  height: 80rpx;
  text-align: center;
  line-height: 80rpx;
}

.voice_icon {
  width: 60rpx;
  height: 60rpx;
  margin: 0 auto;
  padding: 10rpx 10rpx 10rpx 10rpx;
}

.add_icon {
  width: 70rpx;
  height: 70rpx;
  margin: 0 auto;
  padding: 20rpx 10rpx 10rpx 15rpx;
}

.voice_ing {
  width: 90%;
  height: 75rpx;
  line-height: 85rpx;
  text-align: center;
  border-radius: 15rpx;
  border: 1px solid #d0d0d0;
}

.zezhao {
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  width: 100%;
  background: rgba(0, 0, 0, 0.5);
}

.in_voice_icon {
  z-index: 3;
  left: 0;
  bottom: 0;
  width: 100%;
  position: absolute;
  height: 500rpx;
  background: #f8f8f8;
}

.in_voice_icon .item {
  position: relative;
  left: 50%;
  margin-left: -60rpx;
  margin-top: 180rpx;
  text-align: center;
  width: 120rpx;
}

.in_voice_icon .img {
  width: 120rpx;
  height: 120rpx;
  border-radius: 15rpx;
}

.in_voice_icon .text {
  font-size: 32rpx;
  margin-top: 20rpx;
  background: white;
  width: 200rpx;
  margin-left: -40rpx;
  border-radius: 15rpx;
  height: 80rpx;
  line-height: 80rpx;
}

.sendmessage {
  width: 100%;
  z-index: 2;
  display: flex;
  position: fixed;
  bottom: 0px;
  background-color: #f8f8f8;
  flex-direction: row;
  height: 100rpx;
}

.sendmessage input {
  width: 78%;
  height: 80rpx;
  line-height: 80rpx;
  font-size: 28rpx;
  margin-top: 10rpx;
  margin-left: 20rpx;
  border-bottom: 1px solid #d0d0d0;
  padding-left: 20rpx;
}

.sendmessage button {
  border: 1px solid white;
  width: 18%;
  height: 80rpx;
  background: #0c0;
  color: white;
  line-height: 80rpx;
  margin-top: 10rpx;
  font-size: 28rpx;
}

.hei {
  height: 20rpx;
}

.history {
  /* height: 73%; */
  height: 88%;
  display: flex;
  font-size: 14px;
  line-height: 50rpx;
  position: relative;
  top: 20rpx;
}

.icno_kf {
  position: fixed;
  bottom: 160rpx;
  margin: 0 auto;
  text-align: center;
  left: 50%;
  margin-left: -40rpx;
  width: 100rpx;
  height: 100rpx;
  border-radius: 50%;
}

引用的util檔案原始碼:


// 手機號碼驗證
function isUnicoms(mobileNo) {
    //移動:134(0 - 8) 、135、136、137、138、139、147、150、151、152、157、158、159、178、182、183、184、187、188、198 
    //聯通:130、131、132、145、155、156、175、176、185、186、166
    //電信:133、153、173、177、180、181、189、199 
  // 1,移動 2,聯通 3,電信
  var move = /^((134)|(135)|(136)|(137)|(138)|(139)|(147)|(150)|(151)|(152)|(157)|(158)|(159)|(178)|(182)|(183)|(184)|(187)|(188)|(198))\d{8}$/g;
  var link = /^((130)|(131)|(132)|(155)|(156)|(145)|(185)|(186)|(176)|(175)|(170)|(171)|(166))\d{8}$/g;
  var telecom = /^((133)|(153)|(173)|(177)|(180)|(181)|(189)|(199))\d{8}$/g;
  if (move.test(mobileNo)) {
    return '1';
  } else if (link.test(mobileNo)) {
    return '2';
  } else if (telecom.test(mobileNo)) {
    return '3';
  } else {
    return '非三網號段';
  }
}
// 網路請求
function request(url, method, data, message, _success, _fail) {
  wx.showNavigationBarLoading()
  if (message != "") {
    wx.showLoading({
      title: message
    })
  }
  wx.request({
    url: url,
    data: data,
    header: {
      'content-type': 'application/x-www-form-urlencoded'
    },
    method: method,
    success: function (res) {
      _success(res)
      wx.hideNavigationBarLoading()
      if (message != "") {
        wx.hideLoading()
      }
    },
    fail: function (err) {
      if (err) {
        _fail(err)
      }
      wx.hideNavigationBarLoading()
      if (message != "") {
        wx.hideLoading()
      }
    },
  })
}

//上傳語音
function up_audio(url, audioSrc,name, data, _succ, _fail) {
  const uploadTask = wx.uploadFile({
      url: url, //僅為示例,非真實的介面地址
      filePath: audioSrc,
      name: name,
      formData: data,
      header: {
        "content-type": "multipart/form-data"
      },
      success: function (res) {
        _succ(res)
      },fail:function(err){
        _fail(err)
      }
    })
    uploadTask.onProgressUpdate((res) => {
      console.log('audio上傳進度', res.progress)
      console.log('audio已經上傳的資料長度', res.totalBytesSent)
      console.log('audio預期需要上傳的資料總長度', res.totalBytesExpectedToSend)
    })
}

function formatTime(unixtime) {
  var dateTime = new Date(parseInt(unixtime))
  var year = dateTime.getFullYear();
  var month = dateTime.getMonth() + 1;
  var day = dateTime.getDate();
  var hour = dateTime.getHours();
  var minute = dateTime.getMinutes();
  var second = dateTime.getSeconds();
  var now = new Date();
  var now_new = Date.parse(now.toDateString());  //typescript轉換寫法
  var milliseconds = now_new - dateTime;
  var timeSpanStr =  hour + ':' + minute;
  // var timeSpanStr = year + '-' + month + '-' + day + ' ' + hour + ':' + minute;
  return timeSpanStr;
}
module.exports = {
  request: request,
  isUnicoms: isUnicoms,
  formatTime:formatTime,
  up_audio: up_audio
}

引用的app.js

//app.js
var util = require('utils/util.js');
App({
  onLaunch: function () {
    var that = this;
    that.http_session = '';
    return that.promise = new Promise(function (resolve) {
      // that.webS_url = 'ws://192.168.199.147:7041';//填你請求的地址
      // that.httpUrl = 'http://192.168.199.147:7051'//填你請求的測試
      wx.login({
        success: function (res) {
          var data = {
            code: res.code
          }
          if (res.code) {
            //發起網路請求
            var url = that.httpUrl + '/v1/user/login.do';
            util.request(url, 'POST', data, '', function (res) {
              console.log(res);
              that.http_session = res.data.body;
              resolve(that.http_session);
            }, function (err) {
              console.log(err);
            })
          } else {
            console.log('登入失敗!' + res.errMsg)
          }
        }
      });
    })
  },
  // 提交formid
  form_id_bg: function (formId) {
    console.log('form_id_bg執行了')
    let url = this.httpUrl + '/v1/formid/saveFormid.do';
    this.promise.then(function (http_session) {
      let data = {
        session: http_session,
        // minipid: '10000',
        formId: formId
      }
      util.request(url, 'post', data, '', function (res) {
      })
    })
  },
  onShow: function (even) {
    var e;
    // if (even.referrerInfo.extraData && even.referrerInfo.extraData.foo) {
    //   e = even.referrerInfo.extraData.foo
    // }
    if (e && e.appid) {
      this.appid = e.appid;
    }
  }
})