本專案小程式端採用Taro技術框架,可將React程式碼編譯為微信小程式、安卓APP、IOS程式、H5頁面等,管理端採用React Hook + TypeScript來進行開發
專案介紹
當代大學生上課缺少積極性,學習缺乏效率。同為大學生的我深有體會。所以特別開發出這樣一款學習類的微信小程式幫助學生進行學習、鞏固知識,同時增加對戰PK模組來加強學生們的學習積極性。這是一個為學生提供線上學習課程、題庫練習、考試答題、做題PK、上課簽到、資料查閱、成績分析等功能的微信小程式!
目前因學業任務比較重,沒有好好的完善,目前小程式端比較完善的只有習題,課程,論壇,聊天室。管理端也開始進行開發了,現在完成了題庫管理,新增題庫,修改題庫以及登入的功能
希望大佬們走過路過可以給個star鼓勵一下~感激不盡~
這個是小程式後臺管理端的介紹文章
後臺管理端介紹文章,使勁戳!
視訊演示
技術選型
前端:Taro + 微信小程式 + Echarts
後端:Node.js + MySql + websocket
其他:七牛雲端儲存
專案功能
小程式端
- 線上學習課程
- 專項題庫練習
- 課程考試答題
- 知識趣味競賽
- 上課簽到系統
- 專業資料查閱
- 學生成績分析
- 活動日程安排
- 學習分享論壇
管理端
- 登入註冊
- 題庫管理
執行截圖
1. 主頁
2. 個人中心
3. 課程詳情
4. 做題練習
5. 學習交流群
6. 聊天室
7. 課程列表
8. 習題列表
9. 排行榜
10. 論壇
11. 活動任務管理
管理端
1. 登入介面
2. 題庫管理
3. 修改題庫
專案分析
專案採用前後端分離的技術,前端採用了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的方法才能獲取到,卑微的我暫時不知道如何解決這個問題......
聊天介面中有一個emoji表情的按鈕,點選就會彈出emoji欄
實現起來比較簡單,首先定義一個變數emojiOpened
來判斷使用者是否點選emoji按鈕,若點選則為輸入欄新增一個類名來控制彈出的樣式
<View className={`chat-input-container ${emojiOpened ? 'emoji-open' : ''}`}>
複製程式碼
同時再scss中設定彈出的樣式
.emoji-open {
transform: translateY(-30vh);
transition: all .2s ease;
}
...
&-input-container {
position: fixed;
left: 0;
bottom: -30vh;
width: 100vw;
height: 40vh;
background: #fff;
z-index: 1;
transition: all .2s ease;
...
}
複製程式碼
因為在下還只是可憐巴巴的大學生,好多大作業有待完成!具體後續請關注一下我的github,將持續更新專案!
猛戳進來star吧!!~
這個是小程式後臺管理端的介紹文章
後臺管理端介紹文章,使勁戳!