###### tags: `mynah-admin`、`mynah-client-web`、`chatroom`
# mynah-admin
## 後台
### 群組觀念
| Name | TypeCode | 說明 |
| -------- | -------- | -------- |
| 商號 | company | 公司 |
| 站點 | site | 商號下面的站點 |
| 聊天群 | group | 站點下面的聊天群組 |
==(聊天室流程都會圍繞這三個元素,是一個樹狀結構,root 通常是商號)==
### 角色分類
| Name | TypeCode | 說明 |
| -------- | -------- | -------- |
| 客服人員 | service | 待補上 |
| 訪客 | guest | 待補上 |
| 機器人 | bot | 待補上 |
### 開發
```
git clone repoUrl
cd repoName
npm i
npm run dev
```
### 路由權限驗證
```javascript=
path: 'list',
name: 'CsrList',
component: () =>import('@/views/setting/csr/list.vue'),
meta: {
title: '客服列表',
icon: 'list-of-three-elements-on-black-background',
noCache: true,
id: 'MPCustomerList' // super 為不驗證
}
```
### 流程
```flow
st=>start: Start
e=>end: End
get_site=>operation: 取得站點列表
get_group=>operation: 取得各站群組
/cs/<site>/group
connect=>operation: ws連線
/cs/ws
click_chat=>operation: 點擊聊天室
get_members=>operation: 取得成員
/cs/<group>/member
get_history=>operation: 取得歷史訊息
/cs/<group>/history
show_new_msg=>operation: 從ws接收並顯示新訊息
disconnect=>operation: ws斷線
------------------
st->get_site
get_site->connect
connect->get_group
get_group->click_chat
click_chat->get_members
get_members->get_history
get_history->show_new_msg
show_new_msg->disconnect
disconnect(left)->connect
```
### 數據結構
> **chat** : 存放所有的聊天室資料
```javascript
chat: {
// 開啟大頭貼
noAvatarMode: window.localStorage.getItem('noAvatarMode') === 'true',
// 送出訊息模式
sendMode: window.localStorage.getItem('sendMode') || 'auto',
// 會話板多視窗或單視窗
singleMode: window.localStorage.getItem('chatWindowMode') === 'true',
alertAudio: {
// 新訊息提示音
normal: setAlertNormalAudio(),
// 來電音效
ring: setAlertRingAudio()
},
// 當前選中站點
activeSite: '',
// 判斷站點資訊是否被初始化,用來防止 refresh 時送出兩次 getChatSiteInfo request
isSitesInit: false,
// 存放所有站點資料
chatSiteData: {...},
// 存放所有聊天室資訊
chatMessagePool: {...},
// 撥接清單
ringList: {...},
// 傳送已讀請求的倒數
readTimer: null
}
```
---
> **chatSiteData** : 存放所有站點資料
> **siteInfo** : 站點資訊
```javascript
// 格式是用站點 ID 的 Map
chatSiteData: {
siteId1: siteData,
siteId2: siteData,
other site ...
}
// 站點資訊格式
siteData: {
code: siteId, // 站點ID
logo: url, // 站點圖標
name: siteName, // 站點名稱
activeChatKey: groupId, // 當前選中聊天室群組ID
lastMsgHint: 1590544156538, // 站點最後訊息時間
readHint: 1590544156538, // 站點最後讀取時間
}
```
---
> **chatMessagePool** : 存放所有聊天室資訊
```javascript
// 格式 chatMessagePool => 站點ID => 群組ID => 群組資訊
chatMessagePool: {
siteId1: {
groupId1: groupData,
groupId2: groupData,
groupId3: groupData,
...
}
}
// 群組資訊格式
groupData: {
// 是否結束聊天
isEnd: false,
// 錯誤判斷:通常用在歷史訊息取得失敗
isError: false,
// 聊天室ID
groupId: group.id,
// 聊天室圖案
groupAvatar: group.avatar_url !== '' ? group.avatar_url : guestDefaultAvatar,
// 聊天室名稱
groupName: group.name,
// 聊天室成員清單
groupMember: {...},
// 創建時間
createTime: new Date(group.created_at).getTime(),
// 未讀訊息計算,會先使用 server 回傳的數量,當前群組被 actived 的話不會被計算
count: group.unread || 0,
// 新訊息計算: 主要只用在當前群組被 actived 做 count++,到某的數量後發送已讀取的請求
readPollingCount: 0,
// 其他使用者正在打字的訊息提醒
typing: '',
// 草稿區:會使用 localStorage 做暫存
msgInput: window.localStorage.getItem(`draft_${group.site}_${group.id}`) || '',
uploadList: {...},
// 最後一則訊息內容:使用在右邊群組 sidebar 的顯示資料
lastMsgContent: {
type: group.last_msg.msg_type || 'text',
value: group.last_msg.text || group.last_msg.url || '对话已开启',
timestamp: group.last_msg.timestamp || moment(group.created_at).valueOf()
},
lastRead: now,
// 聊天訊息池
messages: {...},
// 是否有拿過歷史訊息,有拿過之後的更新都是靠 ws 的回傳
hasHistory: false
}
```
---
> **groupMember** : 聊天室成員清單,位置是放在 `chatMessagePool` 裡面
```javascript
// 格式 groupMember => 成員ID => 成員資訊
groupMember: {
memberId1: memberInfo,
memberId2: memberInfo,
memberId3: memberInfo
}
// 成員資訊格式
memberInfo: {
id: user.id,
status: 1, // 0 是下線, 1 是上線
nickname: user.nickname,
username: user.username,
avatar_url: user.avatar_url,
type: user.type // guest: 訪客 || bot: 機器人 || service: 客服人員
}
```
---
> **messages** : 聊天訊息池,位置是放在 `chatMessagePool` 裡面
```javascript
// 格式 messages => 訊息ID => 訊息資訊
messages: {
messageId1: meesageInfo,
messageId2: meesageInfo,
messageId3: meesageInfo
}
meesageInfo: {
// 發送訊息者的 id
authorId: "GU44ynzLicpA9",
// 客戶端自己產生的 id,在沒有 server id 時使用
cid: "69f55670-1a64-11eb-9809-ffb19b1556aa",
// server 端的 id
id: "budovk7jc49dirmi8d10",
// 判斷自己傳送的訊息有沒有成功
isUpdated: true,
timestamp: 1604030416565,
// 目前總類有: text | event | image
type: "image",
// 圖片的連結
url: "http://demo/CM44ZcnwRaphL/hahaha.png",
// 文字訊息的值
value: 'hahaha'
}
```
> **ringList** : 撥接清單
```javascript
// 格式 ringList => 站點ID => 群組ID => 撥接資訊
ringList: {
siteId1: {
groupId1: ringInfo,
groupId2: ringInfo,
groupId3: ringInfo
},
siteId2: {...}
}
// 撥接資訊
ringInfo: {
isRinging: true,
isReceived: false,
groupId: group.id,
groupAvatar: group.avatar_url,
groupName: group.name,
authorId: chatUser.id
}
```
---
## 前台
### 群組觀念
只有一個聊天群組,通常就一個訪客對應一個客服人員。
### 角色分類
1. 訪客
2. 第三方登入會員(產線)
### 開發
```
git clone repoUrl
cd repoName
git submodule init //安裝submodule
git submodule update --recursive //更新submodule
npm i
npm run dev
```
### 使用
> - 前台聊天室:/chatroom?company=**company**&site=**id**
(*參數請查看 /api/siteinfo*)

### 更新模式
> 1. Websocket更新數據模式 (store.stat.chat.chatUpdateMode)
> 2. Pooling更新數據模式
### 重連
> 重連機制有兩個情境
> 每一次重新連線都會確認是否要填詢前表單以確認對話狀態(有可能被客服主動掛斷)
> 如果被掛斷視同重新對話與重新創群組因此需要重新填寫表單
> 1. websocketUrl不只一個時
> 會嘗試連線每一個wsUrl後, 皆失敗後進行pooling模式
> 2. websocketUrl只有一個時
> 會嘗試連線三次wsUrl, 失敗後進行pooling模式
### 流程
```flow
cond1=>condition: 是否要填寫詢前表單
cond2=>condition: 是否要填寫評價表單
opChatEnd=>operation: 聊天結束
opReview=>operation: 填評價表單
opGroupEnd=>operation: 對話結束
end=>end: 填詢前表單
op2=>operation: 聊天開始
cond1(yes)->end->op2->opChatEnd->cond2(yes)->opReview->opGroupEnd
cond1(yes)->end->op2->opChatEnd->cond2(no)->opGroupEnd
cond1(no)->op2
```
### 數據結構
> 因前台只會有一個聊天群組, 因此群組資訊分開存取
``` javascript
// store.stat.chat.chatGroupInfo
chatGroupInfo: {
"isComplete": true, // 聊天資料是否取得完成的狀態
"isEnd": false, // 對話是否結束
"connectMessage": { // 連線時展示的url 先是連線中, 再來就是客服接入中
"shouldShow": true, // 要不要展示連線狀態訊息區
"isConnecting": false, // 連線是否已經建立的狀態
"hint": "正在等待客服接入", // 提示 title
"message": "推荐收藏 https://3355100.com 拉斯维加斯线 路检测中心"
},
"groupId": "CM42NcgvEXyjm", // 對話群組id
"lastRead": 1600152111244, // 最後讀的時間
"chatMate": [ // 對話群組成員
{
"id": "BO3SXyVTvmLPs", // 成員id
"type": "bot", // 成員類別 service, guest, bot
"username": "bot", // 成員名稱
"nickname": "客服人员", // 成員暱稱
"status": 0, // 成員是否在對話群中
"reject": false, // 沒用...
"company": "FM3SDuUDSiGX3", // 商號id 沒用...
"site": [
"WS3SXyGDtjQmq" // 站點id 沒用...
],
"avatar_url": "http://366cdn.com/mynah/avatar/cs_avatar.png" // 成員頭像
},
{
"id": "CS3SSB1tjHrv3",
"type": "service",
"username": "csr_aqua002",
"nickname": "阿克雅002",
"status": 1,
"reject": false,
"company": "FM3SDuUDSiGX3",
"site": null,
"avatar_url": "http://squirrel-dev.paradise-soft.com.tw/5b0c90b21.png"
}
]
}
// store.state.chat.messagePool
messagePool: {
"auto_btg5ve44fgj8fr1voidg": {
"authorId": "BO3SXyVTvmLPs", // 訊息作者id
"timestamp": 1600151480283, // 訊息時間 (server time)
"isUpdated": true, // 訊息是否從server side 通知更新
"id": "auto_btg5ve44fgj8fr1voidg", // server產生的 msg_id
"cid": "", // client產生的 msg_id
"type": "text", msg_type
"uploadError": false, // msg_type為image時會上傳是否失敗的狀態
"uploadProgress": 100, // msg_type為image上傳進度條
"url": "", // msg_type為image時 訊息資料為url
"value": "今天中午吃拉麵~" // msg_type為text 使用text, image為url
},
"auto_btg5ve44fgj8fr1voie0": {
"authorId": "BO3SXyVTvmLPs",
"timestamp": 1600151480285,
"isUpdated": true,
"id": "auto_btg5ve44fgj8fr1voie0",
"cid": "",
"type": "text",
"uploadError": false,
"uploadProgress": 100,
"url": "",
"value": "第二則信息"
},
"auto_btg5ve44fgj8fr1voieg": {
"authorId": "BO3SXyVTvmLPs",
"timestamp": 1600151480287,
"isUpdated": true,
"id": "auto_btg5ve44fgj8fr1voieg",
"cid": "",
"type": "text",
"uploadError": false,
"uploadProgress": 100,
"url": "",
"value": "B哥好帥"
},
"18b5eeb0-f71d-11ea-9c76-ed588078d85a": {
"authorId": "GU3ZsHw286TBf",
"timestamp": 1600151493346,
"isUpdated": true,
"id": "btg5vhc4fgj8fr1voif0",
"cid": "18b5eeb0-f71d-11ea-9c76-ed588078d85a",
"type": "text",
"uploadError": false,
"uploadProgress": 100,
"value": "123"
}
}
```
---
## Components
### 前台(Client)
#### ChatContent
1. 聊天訊息的容器
2. 處理訊息與訊息間狀態展示
3. 處理連線提示狀態展示
#### ChatBubble
1. 聊天訊息組件
2. 處理聊天訊息parser和聊天訊息各種功能
#### ChatFooter
1. 訊息發送用組件
2. 處理發送邏輯和input
### 後台(Admin)
#### board

1. 處理站點訊息通知
2. 處理選中聊天站點群
#### chatroom

1. 處理聊天室列表
2. 處理響鈴列表
#### chatArea

1. 處理聊天室發送訊息
2. 處理聊天室輔助功能展示(drawer, tool)
#### chatPool

1. 處理訊息列表相關功能
#### chatMsg

1. 處理訊息內部功能