---
# System prepended metadata

title: mynah-admin
tags: [mynah-admin, chatroom, mynah-client-web]

---

###### 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,
  // 聊天室ＩＤ
  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*)
![](https://i.imgur.com/bUHoBIP.png)

### 更新模式
> 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
![](https://i.imgur.com/V7Covao.png)
1. 處理站點訊息通知
2. 處理選中聊天站點群
#### chatroom
![](https://i.imgur.com/8WRYi60.png)
1. 處理聊天室列表
2. 處理響鈴列表
#### chatArea
![](https://i.imgur.com/yx3SSgi.png)
1. 處理聊天室發送訊息
2. 處理聊天室輔助功能展示(drawer, tool)
#### chatPool
![](https://i.imgur.com/d9gxzH6.png)
1. 處理訊息列表相關功能
#### chatMsg
![](https://i.imgur.com/m52dFM3.png)
1. 處理訊息內部功能
