App.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <script>
  2. import App from './App'
  3. import http from './common/request';
  4. import * as msgType from './common/messageType';
  5. import * as enums from './common/enums';
  6. import * as wsApi from './common/wssocket';
  7. import UNI_APP from '@/.env.js'
  8. export default {
  9. data() {
  10. return {
  11. chatStore: this.useChatStore(),
  12. friendStore: this.useFriendStore(),
  13. groupStore: this.useGroupStore(),
  14. configStore: this.useConfigStore(),
  15. userStore: this.useUserStore(),
  16. isExit: false, // 是否已退出
  17. audioTip: null,
  18. reconnecting: false // 正在重连标志
  19. }
  20. },
  21. methods: {
  22. init() {
  23. this.isExit = false;
  24. // 加载数据
  25. this.loadStore().then(() => {
  26. // 初始化websocket
  27. this.initWebSocket();
  28. }).catch((e) => {
  29. console.log(e);
  30. this.exit();
  31. })
  32. },
  33. initWebSocket() {
  34. let loginInfo = uni.getStorageSync("loginInfo")
  35. wsApi.init();
  36. wsApi.connect(UNI_APP.WS_URL, loginInfo.accessToken);
  37. wsApi.onConnect(() => {
  38. // 重连成功提示
  39. if (this.reconnecting) {
  40. this.reconnecting = false;
  41. uni.showToast({
  42. title: "已重新连接",
  43. icon: 'none'
  44. })
  45. }
  46. // 加载离线消息
  47. this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId);
  48. this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId);
  49. });
  50. wsApi.onMessage((cmd, msgInfo) => {
  51. if (cmd == 2) {
  52. // 异地登录,强制下线
  53. uni.showModal({
  54. content: '您已在其他地方登陆,将被强制下线',
  55. showCancel: false,
  56. })
  57. this.exit();
  58. } else if (cmd == 3) {
  59. // 私聊消息
  60. this.handlePrivateMessage(msgInfo);
  61. } else if (cmd == 4) {
  62. // 群聊消息
  63. this.handleGroupMessage(msgInfo);
  64. } else if (cmd == 5) {
  65. // 系统消息
  66. this.handleSystemMessage(msgInfo);
  67. }
  68. });
  69. wsApi.onClose((res) => {
  70. console.log("ws断开", res);
  71. // 重新连接
  72. this.reconnectWs();
  73. })
  74. },
  75. loadStore(){
  76. return this.userStore.loadUser().then(() => {
  77. const promises = [];
  78. promises.push(this.friendStore.loadFriend());
  79. promises.push(this.groupStore.loadGroup());
  80. promises.push(this.chatStore.loadChat());
  81. promises.push(this.configStore.loadConfig());
  82. return Promise.all(promises);
  83. })
  84. },
  85. unloadStore(){
  86. this.friendStore.clear();
  87. this.groupStore.clear();
  88. this.chatStore.clear();
  89. this.configStore.clear();
  90. this.userStore.clear();
  91. },
  92. pullPrivateOfflineMessage(minId) {
  93. this.chatStore.setLoadingPrivateMsg(true)
  94. http({
  95. url: "/message/private/pullOfflineMessage?minId=" + minId,
  96. method: 'GET'
  97. }).catch(() => {
  98. this.chatStore.setLoadingPrivateMsg(false)
  99. })
  100. },
  101. pullGroupOfflineMessage(minId) {
  102. this.chatStore.setLoadingGroupMsg(true)
  103. http({
  104. url: "/message/group/pullOfflineMessage?minId=" + minId,
  105. method: 'GET'
  106. }).catch(() => {
  107. this.chatStore.setLoadingGroupMsg(false)
  108. })
  109. },
  110. handlePrivateMessage(msg) {
  111. console.log("handlePrivateMessage")
  112. // 消息加载标志
  113. if (msg.type == enums.MESSAGE_TYPE.LOADING) {
  114. this.chatStore.setLoadingPrivateMsg(JSON.parse(msg.content))
  115. return;
  116. }
  117. // 消息已读处理,清空已读数量
  118. if (msg.type == enums.MESSAGE_TYPE.READED) {
  119. this.chatStore.resetUnreadCount( {
  120. type: 'PRIVATE',
  121. targetId: msg.recvId
  122. })
  123. return;
  124. }
  125. // 消息回执处理,改消息状态为已读
  126. if (msg.type == enums.MESSAGE_TYPE.RECEIPT) {
  127. this.chatStore.readedMessage( {
  128. friendId: msg.sendId
  129. })
  130. return;
  131. }
  132. // 标记这条消息是不是自己发的
  133. msg.selfSend = msg.sendId == this.userStore.userInfo.id;
  134. // 好友id
  135. let friendId = msg.selfSend ? msg.recvId : msg.sendId;
  136. this.loadFriendInfo(friendId).then((friend) => {
  137. this.insertPrivateMessage(friend, msg);
  138. })
  139. },
  140. insertPrivateMessage(friend, msg) {
  141. // 单人视频信令
  142. if (msgType.isRtcPrivate(msg.type)) {
  143. // #ifdef MP-WEIXIN
  144. // 小程序不支持音视频
  145. return;
  146. // #endif
  147. // 被呼叫,弹出视频页面
  148. let delayTime = 100;
  149. if (msg.type == enums.MESSAGE_TYPE.RTC_CALL_VOICE ||
  150. msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO) {
  151. let mode = msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO ? "video" : "voice";
  152. let pages = getCurrentPages();
  153. let curPage = pages[pages.length - 1].route;
  154. if (curPage != "pages/chat/chat-private-video") {
  155. const friendInfo = encodeURIComponent(JSON.stringify(friend));
  156. uni.navigateTo({
  157. url: `/pages/chat/chat-private-video?mode=${mode}&friend=${friendInfo}&isHost=false`
  158. })
  159. delayTime = 500;
  160. }
  161. }
  162. setTimeout(() => {
  163. uni.$emit('WS_RTC_PRIVATE', msg);
  164. }, delayTime)
  165. return;
  166. }
  167. let chatInfo = {
  168. type: 'PRIVATE',
  169. targetId: friend.id,
  170. showName: friend.nickName,
  171. headImage: friend.headImage
  172. };
  173. // 打开会话
  174. this.chatStore.openChat(chatInfo);
  175. // 插入消息
  176. this.chatStore.insertMessage(msg);
  177. // 播放提示音
  178. this.playAudioTip();
  179. },
  180. handleGroupMessage(msg) {
  181. // 消息加载标志
  182. if (msg.type == enums.MESSAGE_TYPE.LOADING) {
  183. this.chatStore.setLoadingGroupMsg(JSON.parse(msg.content))
  184. return;
  185. }
  186. // 消息已读处理
  187. if (msg.type == enums.MESSAGE_TYPE.READED) {
  188. // 我已读对方的消息,清空已读数量
  189. let chatInfo = {
  190. type: 'GROUP',
  191. targetId: msg.groupId
  192. }
  193. this.chatStore.resetUnreadCount(chatInfo)
  194. return;
  195. }
  196. // 消息回执处理
  197. if (msg.type == enums.MESSAGE_TYPE.RECEIPT) {
  198. // 更新消息已读人数
  199. let msgInfo = {
  200. id: msg.id,
  201. groupId: msg.groupId,
  202. readedCount: msg.readedCount,
  203. receiptOk: msg.receiptOk
  204. };
  205. this.chatStore.updateMessage(msgInfo)
  206. return;
  207. }
  208. // 标记这条消息是不是自己发的
  209. msg.selfSend = msg.sendId == this.userStore.userInfo.id;
  210. this.loadGroupInfo(msg.groupId).then((group) => {
  211. // 插入群聊消息
  212. this.insertGroupMessage(group, msg);
  213. })
  214. },
  215. handleSystemMessage(msg) {
  216. if (msg.type == enums.MESSAGE_TYPE.USER_BANNED) {
  217. // 用户被封禁
  218. wsApi.close(3099);
  219. uni.showModal({
  220. content: '您的账号已被管理员封禁,原因:' + msg.content,
  221. showCancel: false,
  222. })
  223. this.exit();
  224. }
  225. },
  226. insertGroupMessage(group, msg) {
  227. // 群视频信令
  228. if (msgType.isRtcGroup(msg.type)) {
  229. // #ifdef MP-WEIXIN
  230. // 小程序不支持音视频
  231. return;
  232. // #endif
  233. // 被呼叫,弹出视频页面
  234. let delayTime = 100;
  235. if (msg.type == enums.MESSAGE_TYPE.RTC_GROUP_SETUP) {
  236. let pages = getCurrentPages();
  237. let curPage = pages[pages.length - 1].route;
  238. if (curPage != "pages/chat/chat-group-video") {
  239. const userInfos = encodeURIComponent(msg.content);
  240. const inviterId = msg.sendId;
  241. const groupId = msg.groupId
  242. uni.navigateTo({
  243. url: `/pages/chat/chat-group-video?groupId=${groupId}&isHost=false
  244. &inviterId=${inviterId}&userInfos=${userInfos}`
  245. })
  246. delayTime = 500;
  247. }
  248. }
  249. // 消息转发到chat-group-video页面进行处理
  250. setTimeout(() => {
  251. uni.$emit('WS_RTC_GROUP', msg);
  252. }, delayTime)
  253. return;
  254. }
  255. let chatInfo = {
  256. type: 'GROUP',
  257. targetId: group.id,
  258. showName: group.showGroupName,
  259. headImage: group.headImageThumb
  260. };
  261. // 打开会话
  262. this.chatStore.openChat(chatInfo);
  263. // 插入消息
  264. this.chatStore.insertMessage( msg);
  265. // 播放提示音
  266. this.playAudioTip();
  267. },
  268. loadFriendInfo(id) {
  269. return new Promise((resolve, reject) => {
  270. let friend = this.friendStore.findFriend(id);
  271. if (friend) {
  272. resolve(friend);
  273. } else {
  274. http({
  275. url: `/friend/find/${id}`,
  276. method: 'GET'
  277. }).then((friend) => {
  278. this.friendStore.addFriend(friend);
  279. resolve(friend)
  280. })
  281. }
  282. });
  283. },
  284. loadGroupInfo(id) {
  285. return new Promise((resolve, reject) => {
  286. let group = this.groupStore.groups.find((g) => g.id == id);
  287. if (group) {
  288. resolve(group);
  289. } else {
  290. http({
  291. url: `/group/find/${id}`,
  292. method: 'GET'
  293. }).then((group) => {
  294. this.groupStore.addGroup(group);
  295. resolve(group)
  296. })
  297. }
  298. });
  299. },
  300. exit() {
  301. console.log("exit");
  302. this.isExit = true;
  303. wsApi.close(3099);
  304. uni.removeStorageSync("loginInfo");
  305. uni.reLaunch({
  306. url: "/pages/login/login"
  307. })
  308. this.unloadStore();
  309. },
  310. playAudioTip() {
  311. // 音频播放无法成功
  312. // this.audioTip = uni.createInnerAudioContext();
  313. // this.audioTip.src = "/static/audio/tip.wav";
  314. // this.audioTip.play();
  315. },
  316. isExpired(loginInfo) {
  317. if (!loginInfo || !loginInfo.expireTime) {
  318. return true;
  319. }
  320. return loginInfo.expireTime < new Date().getTime();
  321. },
  322. reconnectWs() {
  323. // 已退出则不再重连
  324. if (this.isExit) {
  325. return;
  326. }
  327. // 记录标志
  328. this.reconnecting = true;
  329. // 重新加载一次个人信息,目的是为了保证网络已经正常且token有效
  330. this.reloadUserInfo().then((userInfo) => {
  331. uni.showToast({
  332. title: '连接已断开,尝试重新连接...',
  333. icon: 'none',
  334. })
  335. this.userStore.setUserInfo(userInfo);
  336. // 重新连接
  337. let loginInfo = uni.getStorageSync("loginInfo")
  338. wsApi.reconnect(UNI_APP.WS_URL, loginInfo.accessToken);
  339. }).catch(() => {
  340. // 5s后重试
  341. setTimeout(() => {
  342. this.reconnectWs();
  343. }, 5000)
  344. })
  345. },
  346. reloadUserInfo() {
  347. return http({
  348. url: '/user/self',
  349. method: 'GET'
  350. })
  351. }
  352. },
  353. onLaunch() {
  354. // 登录状态校验
  355. let loginInfo = uni.getStorageSync("loginInfo")
  356. if (!this.isExpired(loginInfo)) {
  357. console.log("初始化")
  358. // 初始化
  359. this.init();
  360. // 跳转到聊天页面
  361. uni.switchTab({
  362. url: "/pages/chat/chat"
  363. })
  364. } else {
  365. // 跳转到登录页
  366. // #ifdef H5
  367. uni.navigateTo({
  368. url: "/pages/login/login"
  369. })
  370. // #endif
  371. }
  372. }
  373. }
  374. </script>
  375. <style lang="scss">
  376. @import "@/uni_modules/uview-plus/index.scss";
  377. @import url('./static/icon/iconfont.css');
  378. // #ifdef H5
  379. uni-page-head {
  380. display: none; // h5浏览器本身就有标题
  381. }
  382. // #endif
  383. .tab-page {
  384. // #ifdef H5
  385. height: calc(100vh - 50px); // h5平台100vh是包含了底部高度,需要减去
  386. // #endif
  387. // #ifndef H5
  388. height: calc(100vh);
  389. // #endif
  390. background-color: #f8f8f8;
  391. }
  392. .page {
  393. height: calc(100vh);
  394. background-color: #f8f8f8;
  395. }
  396. </style>