App.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  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. // 消息加载标志
  112. if (msg.type == enums.MESSAGE_TYPE.LOADING) {
  113. this.chatStore.setLoadingPrivateMsg(JSON.parse(msg.content))
  114. return;
  115. }
  116. // 消息已读处理,清空已读数量
  117. if (msg.type == enums.MESSAGE_TYPE.READED) {
  118. this.chatStore.resetUnreadCount( {
  119. type: 'PRIVATE',
  120. targetId: msg.recvId
  121. })
  122. return;
  123. }
  124. // 消息回执处理,改消息状态为已读
  125. if (msg.type == enums.MESSAGE_TYPE.RECEIPT) {
  126. this.chatStore.readedMessage( {
  127. friendId: msg.sendId
  128. })
  129. return;
  130. }
  131. // 标记这条消息是不是自己发的
  132. msg.selfSend = msg.sendId == this.userStore.userInfo.id;
  133. // 好友id
  134. let friendId = msg.selfSend ? msg.recvId : msg.sendId;
  135. this.loadFriendInfo(friendId).then((friend) => {
  136. this.insertPrivateMessage(friend, msg);
  137. })
  138. },
  139. insertPrivateMessage(friend, msg) {
  140. // 单人视频信令
  141. if (msgType.isRtcPrivate(msg.type)) {
  142. // #ifdef MP-WEIXIN
  143. // 小程序不支持音视频
  144. return;
  145. // #endif
  146. // 被呼叫,弹出视频页面
  147. let delayTime = 100;
  148. if (msg.type == enums.MESSAGE_TYPE.RTC_CALL_VOICE ||
  149. msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO) {
  150. let mode = msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO ? "video" : "voice";
  151. let pages = getCurrentPages();
  152. let curPage = pages[pages.length - 1].route;
  153. if (curPage != "pages/chat/chat-private-video") {
  154. const friendInfo = encodeURIComponent(JSON.stringify(friend));
  155. uni.navigateTo({
  156. url: `/pages/chat/chat-private-video?mode=${mode}&friend=${friendInfo}&isHost=false`
  157. })
  158. delayTime = 500;
  159. }
  160. }
  161. setTimeout(() => {
  162. uni.$emit('WS_RTC_PRIVATE', msg);
  163. }, delayTime)
  164. return;
  165. }
  166. let chatInfo = {
  167. type: 'PRIVATE',
  168. targetId: friend.id,
  169. showName: friend.nickName,
  170. headImage: friend.headImage
  171. };
  172. // 打开会话
  173. this.chatStore.openChat(chatInfo);
  174. // 插入消息
  175. this.chatStore.insertMessage(msg);
  176. // 播放提示音
  177. this.playAudioTip();
  178. },
  179. handleGroupMessage(msg) {
  180. // 消息加载标志
  181. if (msg.type == enums.MESSAGE_TYPE.LOADING) {
  182. this.chatStore.setLoadingGroupMsg(JSON.parse(msg.content))
  183. return;
  184. }
  185. // 消息已读处理
  186. if (msg.type == enums.MESSAGE_TYPE.READED) {
  187. // 我已读对方的消息,清空已读数量
  188. let chatInfo = {
  189. type: 'GROUP',
  190. targetId: msg.groupId
  191. }
  192. this.chatStore.resetUnreadCount(chatInfo)
  193. return;
  194. }
  195. // 消息回执处理
  196. if (msg.type == enums.MESSAGE_TYPE.RECEIPT) {
  197. // 更新消息已读人数
  198. let msgInfo = {
  199. id: msg.id,
  200. groupId: msg.groupId,
  201. readedCount: msg.readedCount,
  202. receiptOk: msg.receiptOk
  203. };
  204. this.chatStore.updateMessage(msgInfo)
  205. return;
  206. }
  207. // 标记这条消息是不是自己发的
  208. msg.selfSend = msg.sendId == this.userStore.userInfo.id;
  209. this.loadGroupInfo(msg.groupId).then((group) => {
  210. // 插入群聊消息
  211. this.insertGroupMessage(group, msg);
  212. })
  213. },
  214. handleSystemMessage(msg) {
  215. if (msg.type == enums.MESSAGE_TYPE.USER_BANNED) {
  216. // 用户被封禁
  217. wsApi.close(3099);
  218. uni.showModal({
  219. content: '您的账号已被管理员封禁,原因:' + msg.content,
  220. showCancel: false,
  221. })
  222. this.exit();
  223. }
  224. },
  225. insertGroupMessage(group, msg) {
  226. // 群视频信令
  227. if (msgType.isRtcGroup(msg.type)) {
  228. // #ifdef MP-WEIXIN
  229. // 小程序不支持音视频
  230. return;
  231. // #endif
  232. // 被呼叫,弹出视频页面
  233. let delayTime = 100;
  234. if (msg.type == enums.MESSAGE_TYPE.RTC_GROUP_SETUP) {
  235. let pages = getCurrentPages();
  236. let curPage = pages[pages.length - 1].route;
  237. if (curPage != "pages/chat/chat-group-video") {
  238. const userInfos = encodeURIComponent(msg.content);
  239. const inviterId = msg.sendId;
  240. const groupId = msg.groupId
  241. uni.navigateTo({
  242. url: `/pages/chat/chat-group-video?groupId=${groupId}&isHost=false
  243. &inviterId=${inviterId}&userInfos=${userInfos}`
  244. })
  245. delayTime = 500;
  246. }
  247. }
  248. // 消息转发到chat-group-video页面进行处理
  249. setTimeout(() => {
  250. uni.$emit('WS_RTC_GROUP', msg);
  251. }, delayTime)
  252. return;
  253. }
  254. let chatInfo = {
  255. type: 'GROUP',
  256. targetId: group.id,
  257. showName: group.showGroupName,
  258. headImage: group.headImageThumb
  259. };
  260. // 打开会话
  261. this.chatStore.openChat(chatInfo);
  262. // 插入消息
  263. this.chatStore.insertMessage( msg);
  264. // 播放提示音
  265. this.playAudioTip();
  266. },
  267. loadFriendInfo(id) {
  268. return new Promise((resolve, reject) => {
  269. let friend = this.friendStore.findFriend(id);
  270. if (friend) {
  271. resolve(friend);
  272. } else {
  273. http({
  274. url: `/friend/find/${id}`,
  275. method: 'GET'
  276. }).then((friend) => {
  277. this.friendStore.addFriend(friend);
  278. resolve(friend)
  279. })
  280. }
  281. });
  282. },
  283. loadGroupInfo(id) {
  284. return new Promise((resolve, reject) => {
  285. let group = this.groupStore.groups.find((g) => g.id == id);
  286. if (group) {
  287. resolve(group);
  288. } else {
  289. http({
  290. url: `/group/find/${id}`,
  291. method: 'GET'
  292. }).then((group) => {
  293. this.groupStore.addGroup(group);
  294. resolve(group)
  295. })
  296. }
  297. });
  298. },
  299. exit() {
  300. console.log("exit");
  301. this.isExit = true;
  302. wsApi.close(3099);
  303. uni.removeStorageSync("loginInfo");
  304. uni.reLaunch({
  305. url: "/pages/login/login"
  306. })
  307. this.unloadStore();
  308. },
  309. playAudioTip() {
  310. // 音频播放无法成功
  311. // this.audioTip = uni.createInnerAudioContext();
  312. // this.audioTip.src = "/static/audio/tip.wav";
  313. // this.audioTip.play();
  314. },
  315. isExpired(loginInfo) {
  316. if (!loginInfo || !loginInfo.expireTime) {
  317. return true;
  318. }
  319. return loginInfo.expireTime < new Date().getTime();
  320. },
  321. reconnectWs() {
  322. // 已退出则不再重连
  323. if (this.isExit) {
  324. return;
  325. }
  326. // 记录标志
  327. this.reconnecting = true;
  328. // 重新加载一次个人信息,目的是为了保证网络已经正常且token有效
  329. this.reloadUserInfo().then((userInfo) => {
  330. uni.showToast({
  331. title: '连接已断开,尝试重新连接...',
  332. icon: 'none',
  333. })
  334. this.userStore.setUserInfo(userInfo);
  335. // 重新连接
  336. let loginInfo = uni.getStorageSync("loginInfo")
  337. wsApi.reconnect(UNI_APP.WS_URL, loginInfo.accessToken);
  338. }).catch(() => {
  339. // 5s后重试
  340. setTimeout(() => {
  341. this.reconnectWs();
  342. }, 5000)
  343. })
  344. },
  345. reloadUserInfo() {
  346. return http({
  347. url: '/user/self',
  348. method: 'GET'
  349. })
  350. }
  351. },
  352. onLaunch() {
  353. // 登录状态校验
  354. let loginInfo = uni.getStorageSync("loginInfo")
  355. if (!this.isExpired(loginInfo)) {
  356. console.log("初始化")
  357. // 初始化
  358. this.init();
  359. // 跳转到聊天页面
  360. uni.switchTab({
  361. url: "/pages/chat/chat"
  362. })
  363. } else {
  364. // 跳转到登录页
  365. // #ifdef H5
  366. uni.navigateTo({
  367. url: "/pages/login/login"
  368. })
  369. // #endif
  370. }
  371. }
  372. }
  373. </script>
  374. <style lang="scss">
  375. @import "@/uni_modules/uview-plus/index.scss";
  376. @import url('./static/icon/iconfont.css');
  377. // #ifdef H5
  378. uni-page-head {
  379. display: none; // h5浏览器本身就有标题
  380. }
  381. // #endif
  382. .tab-page {
  383. // #ifdef H5
  384. height: calc(100vh - 50px); // h5平台100vh是包含了底部高度,需要减去
  385. // #endif
  386. // #ifndef H5
  387. height: calc(100vh);
  388. // #endif
  389. background-color: #f8f8f8;
  390. }
  391. .page {
  392. height: calc(100vh);
  393. background-color: #f8f8f8;
  394. }
  395. </style>