用戶
 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

掃一掃,登錄網站

小程序社區 首頁 教程 查看內容

使用Taro小程序框架開發一個學習做題聊天交流的微信小程序

Rolan 2019-11-19 00:41

項目介紹當代大學生上課缺少積極性,學習缺乏效率。同為大學生的我深有體會。所以特別開發出這樣一款學習類的微信小程序幫助學生進行學習、鞏固知識,同時增加對戰PK模塊來加強學生們的學習積極性。這是一個為學生提 ...

項目介紹

當代大學生上課缺少積極性,學習缺乏效率。同為大學生的我深有體會。所以特別開發出這樣一款學習類的微信小程序幫助學生進行學習、鞏固知識,同時增加對戰PK模塊來加強學生們的學習積極性。這是一個為學生提供在線學習課程、題庫練習、考試答題、做題PK、上課簽到、資料查閱、成績分析等功能的微信小程序

希望大佬們走過路過給個star~

技術選型

前端:Taro + 微信小程序 + Echarts

后端:Node.js + MySql + websocket

其他:七牛云存儲

項目功能

  1. 在線學習課程
  2. 專項題庫練習
  3. 課程考試答題
  4. 知識趣味競賽
  5. 上課簽到系統
  6. 專業資料查閱
  7. 學生成績分析

運行截圖

1. 主頁

2. 個人中心

3. 課程詳情

4. 做題練習

5. 學習交流群

6. 聊天室

7. 課程列表

8. 習題列表

9. 排行榜

10. 論壇

項目分析

項目采用前后端分離的技術,前端采用了Taro微信小程序框架,因為本人比較喜歡React,所以采用了Taro這款類React語法的框架,后端則采用了Node.js,koa2框架。聊天室頁面采用websocket來進行連接

今天,我們首先來聊一聊聊天室使用的小技巧(并不)

首先我們的后端數據庫采用的是mysql,我們建了一個聊天記錄的表(萌新勿噴~)

1. 后端部分

  • 數據庫部分

我們將所有的聊天記錄存放到一張表上方便管理,因為我們有多個聊天群組,我們該如何區分這些不同的聊天群組呢?答案是,通過room_name來區分,獲取聊天記錄的時候就直接查詢這個群組名即可,這樣就不用開很多的表,將不同的群聊記錄存放到不同的表中啦!

同時因為我們的聊天記錄內需要存儲emoji等信息,所以,我們需要將數據庫的字符集調整為 utf8mb4 -- UTF-8 Unicode ,排序規則選擇 utf8mb4_unicode_ci ,這個可以通過自行百度,或者navicat中設置。

然后我們將數據表以及字段類型也設置為 utf8mb4 ,便于存儲emoji信息

  • 后端處理聊天記錄的方法。
router.get('/chatlog/:to', async (ctx) => {
  const to = ctx.params.to
  const response = []
  const res = await query(`SELECT * FROM chatlog WHERE room_name = '${to}' ORDER BY current_time DESC`);
  res.map((item, index) => {
    const { room_name, user_name, user_avatar, current_time, message } = item
    response[index] = {
      to: room_name,
      userName: user_name,
      userAvatar: user_avatar,
      currentTime: formatTime(current_time),
      message,
      messageId: `msg${current_time}${Math.ceil(Math.random() * 100)}`
    }
  })
  ctx.response.body = parse(response)
})

這是獲取指定群聊的后端接口,to代表的是群組名,使用get的方法即可獲取到指定群聊的聊天記錄啦!

繼續聊聊我們如何為所有連接到聊天室的網友們發送信息,這里我們采用的是廣播的方式,不同于socket.io內已經封裝好廣播的方法,小程序規定只能使用websocket,所以我粗略的封裝了一下廣播(十分丑陋的代碼)

let onlineUserSocket = {}
let onlineUserInfo = {}

const handleLogin = (ws, socketMessage) => {
  const { socketId, userName, userAvatar } = socketMessage
  onlineUserSocket[socketId] = ws
  onlineUserInfo[socketId] = { userName, userAvatar }
  ws.socketId = socketId
}

// 廣播消息
const broadcast = (message) => {
  const { from, userName } = message
  Object.values(onlineUserSocket).forEach((socket) => {
    socket.send(JSON.stringify({
      ...message,
      isMyself: userName === onlineUserInfo[socket.socketId].userName
    }))
  })
}

我們再登錄的時候,就將前端傳來的消息存入對象中,以及他的socket對象,然后廣播的時候就可以遍歷所有的socket對象,為所有在線用戶廣播消息,其中的 isMyself 代表的是否為本人,例如我發的消息,自己的socket對象接受廣播的時候就是 true 。別人的就是 false ,這樣做是為了方便區分,自己的聊天消息和被人的聊天消息

2. 前端部分

接下來聊聊前端的聊天室部分

handleSocketMessage(): void {
    const { socketTask } = this
    socketTask.onMessage(async ({ data }) => {
      const messageInfo: ReceiveMessageInfo = JSON.parse(data)
      const { to, messageId, isMyself, userName, userAvatar, currentTime, message } = messageInfo
      const time: string = formatTime(currentTime)

      this.messageList[to].push({
        ...messageInfo,
        currentTime: time
      })
      /* 設置群組最新消息 */
      this.contactsList.filter(contacts => contacts.contactsId === to)[0].latestMessage = {
        userName, message, currentTime: time
      }
      this.scrollViewId = isMyself ? messageId : ''
      await Taro.request({
        url: 'http://localhost:3000/chatlog',
        method: 'PUT',
        data: {
          to,
          userName,
          userAvatar,
          currentTime,
          message,
        }
      })
    })
  }

我們先接受消息,然后先更新指定群組名的聊天群組的聊天記錄,然后再使用 PUT 的方式訪問接口添加聊天記錄到數據庫中。

可以看到我們的聊天記錄是分為左邊以及右邊的,自己發的消息即為右邊,我們可以通過簡單的flex布局來實現

// 這里是覆蓋默認樣式,顯示自己消息的樣式
.myself {
  justify-content: flex-end;

  .avatar {
    order: 1;
  }

  .info {
    display: flex;
    flex-direction: column;
    align-items: flex-end;

    .header {
      justify-content: flex-end;

      .username {
        order: 1;
        margin-right: 0 !important;
        margin-left: .5em;
      }
    }

    .content {
      color: #333 !important;
      border: #e7e7e7 1px solid;
      background: #fff !important;
      box-shadow: 0 8px 20px -8px #d7d7d7;
    }
  }
}

// 以下是默認樣式,就是左邊的樣式
.message-wrap {
  display: flex;
  margin: 20px 0;

  .avatar {
    width: 14vw;
    height: 14vw;
    margin: 10px;
    border-radius: 50%;
    background-image: linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%);
  }

  .info {

    .header {
      display: flex;
      align-items: center;
      max-width: 40vw;
      padding: 10px 0;
      color: #666;
      font-size: .8em;

      .username {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        max-width: 40vw;
        margin-right: .5em;
        color: #555;
        font-size: 1.2em;
        font-weight: bold;
      }
    }

    .content {
      display: inline-block;
      max-width: 60vw;
      padding: 10px 20px;
      color: #fff;
      word-break: break-all;
      border-radius: 20px;
      background: #66a6ff;
    }
  }
}

最后我們聊一下websocket的斷線重連

handleSocketClose(): void {
    const { socketTask } = this
    socketTask.onClose((msg) => {
      this.socketTask = null
      this.socketReconnect()
      console.log('onClose: ', msg)
    })
  }

  handleSocketError(): void {
    const { socketTask } = this
    socketTask.onError(() => {
      this.socketTask = null
      this.socketReconnect()
      console.log('Error!')
    })
  }

我們這里先監聽一下websocket關閉或者異常的情況,調用重連方法,以及清空socketTask的對象,接下來是重連的方法

socketConnect() {
    // 生成隨機特有的socketId
    this.generateSocketId()

    /* 使用then的方法才能正確觸發onOpen的方法,暫時不知道原因 */
    Taro.connectSocket({
      url: 'ws://localhost:3000',
    }).then(task => {
      this.socketTask = task
      this.handleSocketOpen()
      this.handleSocketMessage()
      this.handleSocketClose()
      this.handleSocketError()
    })
  }

  socketReconnect(): void {
    this.isReconnected = true
    clearTimeout(this.timer)

    /* 3s延遲重連,減輕壓力 */
    this.timer = setTimeout(() => {
      this.socketConnect()
    }, 3000)
  }

我們每三秒調用一遍socket連接的方法,重新再設置好socketId,以及socketTask,重新監聽各種方法。這里有一個奇特的地方,就是Taro的connectSocket方法,不能使用 async/await 的方法來獲取socketTask,也就是說不能這樣 const socketTask = await Taro.connectSocket({...}) 來獲取socketTask,只能通過then的方法才能獲取到,卑微的我暫時不知道如何解決這個問題......

具體后續請關注一下我的github,將持續更新項目!

猛戳~

鮮花
鮮花
雞蛋
雞蛋
分享至 : QQ空間
收藏
原作者: zhcxk1998 來自: segmentfault
必中分分彩在线计划 甘肃11选5前3选号技巧 股票指数理论 华东15选5开奖30期结果 个人做期货配资合法吗 旺旺论坛一肖免费资料 重庆彩计划app下载 幸运赛车2019开奖结果 江西多乐彩玩法 河南22选5开奖视频 沪深股票代码一览表 陕西快乐十分钟开奖结果 全国最知名的股票配资平台 tianjin快乐10分开奖 股票期货入门基础知 青海11选5开奖一定牛 海南环岛自行车赛直播