chatMiniHandle.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. import msgItem from '@/components/msgItem'
  2. import emoji from '@/components/emoji'
  3. import chatAt from '@/components/chatAt'
  4. import atMe from '@/components/chatAt/atme'
  5. import chatPin from '@/components/chatPin'
  6. import toolbar from '@/components/chatInput/toolbar'
  7. import roomTool from './roomTool'
  8. import { mapActions, mapState, mapMutations } from 'vuex'
  9. import { getMiniWsUrl } from '@/util/contract.js'
  10. import { isMobile, scrollMsgIntoView, lazyloadImage } from '@/util/util.js'
  11. import WsManager from '@/util/wsManager.js'
  12. import PostMessager from '@/util/postMessager.js'
  13. import { Message } from 'element-ui'
  14. import { chatAtMixin, chatInputMixin, changeLangMixin } from '@/mixins'
  15. import { accountLoginMixin } from '@/mixins/login'
  16. import { emojiList } from '@/util/emoji'
  17. import api from '@/api'
  18. import loginBox from '@/components/login/loginBox'
  19. export default {
  20. name: 'chatMini',
  21. mixins: [accountLoginMixin, chatAtMixin, chatInputMixin, changeLangMixin],
  22. components: {
  23. msgItem,
  24. emoji,
  25. chatAt,
  26. chatPin,
  27. atMe,
  28. toolbar,
  29. loginBox,
  30. roomTool
  31. },
  32. props: {
  33. width: {
  34. type: Number,
  35. default: 274
  36. },
  37. height: {
  38. type: Number,
  39. default: 390
  40. },
  41. show: {
  42. type: Boolean,
  43. default: false
  44. },
  45. groupId: [Number, String]
  46. },
  47. computed: {
  48. ...mapState([
  49. 'account',
  50. 'group',
  51. 'userId',
  52. 'userInfo'
  53. ]),
  54. ...mapState({
  55. chatInputFocus: state => state.group.chatInputFocus,
  56. blockList: state => state.group.blockList,
  57. pinMsg: state => state.group.pinMsg,
  58. atList: state => state.group.atList,
  59. chatList: state => state.group.chatList,
  60. unreadNums: state => state.group.unreadNums,
  61. sessionList: state => state.chat.sessionList
  62. }),
  63. emojiMap () {
  64. var emojiMap = {}
  65. for (let i in emojiList) {
  66. let arr = emojiList[i]
  67. arr.forEach(v => {
  68. let names = JSON.stringify(v.names)
  69. let emoji = v.surrogates
  70. emojiMap[names] = emoji
  71. })
  72. }
  73. return emojiMap
  74. },
  75. linkToCreator () {
  76. let { creator, userId } = this.group
  77. let sessionId = creator > userId ? `${userId}-${creator}` : `${creator}-${userId}`
  78. return `${location.origin}/#/pm/${sessionId}`
  79. },
  80. isAdmin () {
  81. return (this.group.adminList && this.group.adminList.some(id => id == this.userId)) || this.group.creator == this.userId
  82. }
  83. },
  84. data () {
  85. return {
  86. loginBoxVisible: false,
  87. isLoadingRoom: true,
  88. isMobile: isMobile(),
  89. showChat: !!this.show, // 显示聊天窗
  90. showEmoji: false, // 显示表情选择框
  91. showMenuExtra: false, // 显示左上角菜单
  92. showLoginBtn: true, // 显示登录按钮
  93. lockMore: false,
  94. isScrollToView: false,
  95. lockEnd: false,
  96. loading: false,
  97. unreadCounts: 0, // 未读消息数
  98. atInd: 0, // @人索引
  99. inputHeight: 18,
  100. enableScroll: false, // 记录滚动条是否激活的状态
  101. isBottom: true,
  102. toolShow: false,
  103. chatImageArrSel: null,
  104. serverUnRead: 0, // 客服未读
  105. personUnRead: 0 // 私聊未读
  106. }
  107. },
  108. watch: {
  109. chatList (val) {
  110. let lastVal = val[val.length - 1]
  111. if ((lastVal && lastVal.msg_type == 4) || this.isBottom) {
  112. // 自己发的红包自动滚动到底部
  113. this.$nextTick(this.resizeToBottom)
  114. }
  115. }
  116. },
  117. async mounted () {
  118. // 初始化postMessage消息管理器
  119. let callback = (event) => {
  120. if (event.action === 'meechat:setShow') {
  121. this.handleToggleChat(event.show)
  122. } else if (event.action === 'meechat:logout') {
  123. this.handleLogout2()
  124. }
  125. }
  126. this.postMessager = new PostMessager('*', { callback })
  127. window.postMessager = this.postMessager
  128. // 设置groupId
  129. this.initGroup({
  130. userId: this.userId,
  131. groupId: this.groupId,
  132. useCache: false
  133. })
  134. // 检查登录态
  135. let isLogin = await this.checkLocalLogin()
  136. let account = localStorage.getItem('accounts')
  137. if (isLogin) {
  138. await this.loginEosParent()
  139. this.setAccount(JSON.parse(account))
  140. this.showLoginBtn = false
  141. await this.getUserInfo()
  142. }
  143. this.$nextTick(this.initChat)
  144. this.$nextTick(this.initMiniSocket)
  145. document.getElementById('app').addEventListener('contextmenu', e => e.preventDefault())
  146. document.addEventListener('paste', this.initPaste)
  147. document.addEventListener('drop', this.initDrop)
  148. document.addEventListener('dragover', this.initDragOver)
  149. document.body.addEventListener('click', () => {
  150. this.showEmoji = false
  151. this.showMenuExtra = false
  152. this.toolShow = false
  153. })
  154. },
  155. beforeDestroy () {
  156. document.removeEventListener('paste', this.initPaste)
  157. document.removeEventListener('drop', this.initDrop)
  158. document.removeEventListener('dragover', this.initDragOver)
  159. },
  160. methods: {
  161. ...mapMutations([
  162. 'initGroup',
  163. 'setUserId',
  164. 'setToken',
  165. 'addChatItem',
  166. 'deleteChatItem',
  167. 'updateChatInputFocus',
  168. 'updateGroupBlockList',
  169. 'updateMembers',
  170. 'updateGroupPinMsg',
  171. 'repealChatItem',
  172. 'removeAtListLast',
  173. 'clearAtList',
  174. 'initState',
  175. 'addPacketItem',
  176. 'addPacketTip',
  177. 'addUnreadNums',
  178. 'resetUnreadNums',
  179. 'addPinChatItem'
  180. ]),
  181. ...mapActions([
  182. 'getUserInfo',
  183. 'setAccount',
  184. 'doGameLogin',
  185. 'doScatterLogout',
  186. 'getGroupInfo',
  187. 'getNewMsgFromDb',
  188. 'getNewMsg',
  189. 'getHistoryMsg',
  190. 'doSendMsg',
  191. 'doSendFile',
  192. 'doContractLogin',
  193. 'updateSessionLastmsg'
  194. ]),
  195. handleMoreClick () {
  196. this.showEmoji = false
  197. this.toolShow = !this.toolShow
  198. this.checkNeedToBottom()
  199. },
  200. handleEmojiClick () {
  201. this.toolShow = false
  202. this.showEmoji = !this.showEmoji
  203. this.checkNeedToBottom()
  204. },
  205. checkNeedToBottom () {
  206. if (!this.isBottom) return
  207. this.$nextTick(() => {
  208. this.resizeToBottom()
  209. })
  210. },
  211. async initMiniLoginCallback () {
  212. await this.initMiniSocket()
  213. this.showLoginBtn = false
  214. this.initGroup({
  215. userId: this.userId,
  216. groupId: this.groupId,
  217. useCache: false
  218. })
  219. await this.getUserInfo()
  220. this.loginBoxVisible = false
  221. },
  222. // 连接socket
  223. initMiniSocket () {
  224. if (!window.WebSocket) {
  225. console.log('Error: WebSocket is not supported .')
  226. return
  227. }
  228. let host = getMiniWsUrl() + `?group_id=${this.groupId}`
  229. if (this.socket) {
  230. this.socket.destroy()
  231. this.socket = null
  232. }
  233. this.socket = new WsManager(host, {
  234. autoConnect: true, // 自动连接
  235. reconnection: true, // 断开自动重连
  236. reconnectionDelay: 2000 // 重连间隔时间,单位秒
  237. })
  238. this.socket.on('open', res => {})
  239. this.socket.on('message', (data) => {
  240. data = JSON.parse(data)
  241. if (data.channel.match('chat:group')) {
  242. if (data.data.type === 'msg') {
  243. this.getNewMsg({ newMsg: true })
  244. if (data.data.from != this.userId) {
  245. // 未读消息数+1
  246. if (!this.showChat) {
  247. if (this.unreadCounts === 0) {
  248. this.postResize(130, 50)
  249. }
  250. this.unreadCounts++
  251. } else {
  252. this.addUnreadNums()
  253. }
  254. }
  255. }
  256. if (data.data.type === 'repeal') {
  257. this.repealChatItem(data.data)
  258. }
  259. if (data.data.type === 'block') {
  260. this.updateGroupBlockList({
  261. type: 'add',
  262. id: data.data.to
  263. })
  264. }
  265. if (data.data.type === 'unblock') {
  266. this.updateGroupBlockList({
  267. type: 'delete',
  268. id: data.data.to
  269. })
  270. }
  271. if (data.data.type === 'join') {
  272. this.updateMembers(data.data.user_info)
  273. }
  274. if (data.data.type === 'pin_msg') {
  275. this.updateGroupPinMsg(data.data.pinMsg)
  276. }
  277. if (data.data.type === 'unpin_msg') {
  278. this.updateGroupPinMsg(null)
  279. }
  280. if (data.data.type === 'new_redpack') {
  281. this.addPacketItem(data.data)
  282. if (data.data.from == this.userId) {
  283. this.$nextTick(this.resizeToBottom)
  284. }
  285. }
  286. if (data.data.type === 'grab_redpack') {
  287. if (data.data.from == this.userId || data.data.to == this.userId) {
  288. this.addPacketTip(data.data)
  289. }
  290. }
  291. }
  292. })
  293. },
  294. /**
  295. * 聊天群初始化处理
  296. * 先后调用 group/info, group/msg
  297. */
  298. async initChat () {
  299. this.handleToggleChat(this.show)
  300. this.isLoadingRoom = true
  301. await this.getGroupInfo()
  302. await this.getNewMsgFromDb()
  303. await this.getNewMsg()
  304. this.isLoadingRoom = false
  305. // 有登录态才要请求
  306. if (!this.showLoginBtn) {
  307. await this.getPmUnRead()
  308. }
  309. if (this.show) {
  310. this.$nextTick(this.resizeToBottom)
  311. }
  312. this.chatImageArrSel = this.$refs.scrollWrap.getElementsByTagName('img')
  313. },
  314. getPmUnRead () {
  315. api.session.getMiniUnRead().then(({ data }) => {
  316. this.personUnRead = data.data['0']
  317. this.serverUnRead = data.data[this.groupId] || 0
  318. })
  319. },
  320. /**
  321. * 清空私聊未读
  322. * @param {type} 1.客服2.私聊
  323. **/
  324. clearPmUnread (type) {
  325. if (type == 1) this.serverUnRead = 0
  326. else this.personUnRead = 0
  327. },
  328. async handleLogout () {
  329. this.doScatterLogout()
  330. this.showLoginBtn = true
  331. if (self !== top) {
  332. localStorage.removeItem('account')
  333. this.postMessager.send({
  334. action: 'meechat:logout'
  335. })
  336. }
  337. // 初始vuex数据
  338. this.$store.commit('setUserInfo', null)
  339. // this.$store.commit('initChatData')
  340. // this.$store.commit('initGroupData')
  341. // 注销后,刷新页面
  342. location.replace(location.href.replace('show=false', 'show=true'))
  343. },
  344. async handleLogout2 () {
  345. this.doScatterLogout()
  346. this.showLoginBtn = true
  347. },
  348. /**
  349. * 登录处理
  350. */
  351. async handleLogin () {
  352. // 设置登录按钮状态
  353. this.showLoginBtn = 'loading'
  354. // if (self !== top) {
  355. // this.handleParentLogin()
  356. // } else {
  357. // // 连接scatter
  358. // this.scatterConnect()
  359. // }
  360. // this.$login()
  361. this.loginBoxVisible = true
  362. },
  363. async scatterConnect () {
  364. await this.loginEos()
  365. },
  366. /**
  367. * @des 调用父级页面getIdentity
  368. */
  369. async getParentIdentity () {
  370. if (self !== top) {
  371. try {
  372. let response = await this.postMessager.send({ action: 'meechat:getIdentity' })
  373. let account = response.accounts.find(x => x.blockchain === 'eos')
  374. localStorage.setItem('account', JSON.stringify(account))
  375. this.setAccount(account)
  376. } catch (error) {
  377. this.showLoginBtn = true
  378. }
  379. }
  380. },
  381. /**
  382. * @des 调用父级页面登录
  383. */
  384. async handleParentLogin () {
  385. try {
  386. // scatter登录
  387. await this.getParentIdentity()
  388. if (!this.account) return
  389. // 合约登录
  390. await this.doContractLogin()
  391. location.replace(location.href.replace('show=false', 'show=true'))
  392. } catch (msg) {
  393. if (msg.type) {
  394. // scatter报错的情况
  395. } else {
  396. let json = JSON.parse(msg)
  397. let details = json.error.details
  398. Message({
  399. message: details[0].message,
  400. type: 'error'
  401. })
  402. }
  403. this.showLoginBtn = true
  404. }
  405. },
  406. /**
  407. * 聊天窗体滚动到底部
  408. */
  409. resizeToBottom () {
  410. this.$refs.scrollWrap.scrollTop = this.$refs.msgWrap.offsetHeight
  411. this.resetUnreadNums()
  412. this.isBottom = true
  413. lazyloadImage({
  414. wrap: this.$refs.scrollWrap,
  415. imageArr: this.chatImageArrSel,
  416. derection: 'up'
  417. })
  418. },
  419. /**
  420. * @des 点击,查看未读消息
  421. * 直接滚动到聊天列表底部
  422. */
  423. doSetRead () {
  424. this.resizeToBottom()
  425. },
  426. /**
  427. * 添加表情
  428. */
  429. addEmoji (value) {
  430. this.inputMsg += value
  431. },
  432. /**
  433. * @des 聊天窗体滚动事件处理集
  434. */
  435. handleScroll (e) {
  436. this.enableScroll = true
  437. e.target.focus()
  438. // 防止切换房间时触发滚动处理
  439. if (!this.group.chatList.length) {
  440. return
  441. }
  442. // 防止滚动到置顶消息触发滚动
  443. if (this.isScrollToView) {
  444. return
  445. }
  446. let msgWrap = this.$refs.msgWrap
  447. let totalHeight = msgWrap.offsetHeight
  448. let scrollTop = e.target.scrollTop
  449. if (scrollTop === 0 && !this.lockMore) {
  450. if (this.group.endHash !== null) {
  451. this.lockMore = true
  452. this.getHistoryMsg().then((res) => {
  453. if (res === 'end') {
  454. this.lockEnd = true
  455. } else {
  456. let scrollBottom = totalHeight - scrollTop
  457. this.$nextTick(() => {
  458. e.target.scrollTop = msgWrap.offsetHeight - scrollBottom
  459. this.ps && this.ps.update()
  460. setTimeout(() => {
  461. this.lockMore = false
  462. }, 800)
  463. })
  464. }
  465. })
  466. }
  467. }
  468. // 滚动到底部清空未读消息状态
  469. if (scrollTop + e.target.offsetHeight > totalHeight) {
  470. this.isBottom = true
  471. if (this.unreadNums) {
  472. this.resetUnreadNums()
  473. }
  474. } else {
  475. this.isBottom = false
  476. }
  477. lazyloadImage({
  478. wrap: this.$refs.scrollWrap,
  479. imageArr: this.chatImageArrSel,
  480. derection: 'up'
  481. })
  482. },
  483. /**
  484. * @des 处理消息发送
  485. */
  486. async handleSend (e) {
  487. // 判断是否被禁言
  488. if (this.blockList.some(id => id == this.userId)) {
  489. Message({
  490. message: this.$t('chat.youAreBan'),
  491. type: 'error'
  492. })
  493. return
  494. }
  495. // 替换emoji字符串
  496. let _inputMsg = this.inputMsg
  497. let parts = _inputMsg.match(/\["[a-z0-9A-Z_]+"\]/g)
  498. for (let k in parts) {
  499. let emoji = this.emojiMap[parts[k]]
  500. if (emoji) {
  501. _inputMsg = _inputMsg.replace(parts[k], emoji)
  502. }
  503. }
  504. let text = _inputMsg.trim()
  505. if (text.length === 0) {
  506. Message({
  507. message: this.$t('chat.cannotBeEmpty'),
  508. type: 'warning'
  509. })
  510. return
  511. }
  512. let opt = {
  513. type: 0,
  514. msg: text
  515. }
  516. // 清空输入框
  517. this.inputMsg = ''
  518. // 用户不是第一次发言
  519. if (this.group.members[this.userId]) {
  520. let createTime = Date.now()
  521. this.addChatItem({
  522. from: this.userId,
  523. content: text,
  524. hash: `${createTime}`,
  525. timestamp: createTime,
  526. createTime,
  527. msg_type: '0',
  528. loading: true
  529. })
  530. opt.createTime = createTime
  531. await this.doSendMsg(opt)
  532. } else {
  533. // 发言后,才滚动底部
  534. await this.doSendMsg(opt)
  535. }
  536. // 滚到底部
  537. this.$nextTick(function () {
  538. this.resizeToBottom()
  539. })
  540. e.preventDefault()
  541. return false
  542. },
  543. placeEnd (el) {
  544. var range = document.createRange()
  545. range.selectNodeContents(el)
  546. range.collapse(false)
  547. var sel = window.getSelection()
  548. sel.removeAllRanges()
  549. sel.addRange(range)
  550. },
  551. initDrop (e) {
  552. e.preventDefault()
  553. let files = Array.from(e.dataTransfer.files)
  554. files.forEach(file => this.handleFile(file))
  555. },
  556. initDragOver (e) {
  557. e.preventDefault()
  558. },
  559. initPaste (event) {
  560. var items = (event.clipboardData || window.clipboardData).items
  561. if (items && items.length) {
  562. Array.from(items).forEach(item => {
  563. let file = item.getAsFile()
  564. if (file) {
  565. this.handleFile(file)
  566. }
  567. })
  568. }
  569. },
  570. /**
  571. * @des 控制聊天窗口开关
  572. */
  573. handleToggleChat (flag) {
  574. if (flag) {
  575. this.showChat = true
  576. this.unreadCounts = 0
  577. this.$nextTick(() => {
  578. this.postResize(this.width, this.height + 16)
  579. this.resizeToBottom()
  580. })
  581. } else {
  582. this.showChat = false
  583. this.$nextTick(() => {
  584. this.postResize(56, 50)
  585. })
  586. }
  587. },
  588. postResize (width, height) {
  589. let request = {
  590. action: 'meechat:resize',
  591. data: {
  592. ch: height,
  593. cw: width
  594. }
  595. }
  596. return window.parent.postMessage(request, '*')
  597. },
  598. /**
  599. * @des 引用某条消息
  600. */
  601. quoteMsg (msg) {
  602. this.inputMsg = msg
  603. },
  604. /**
  605. * @des 某条消息被删除
  606. */
  607. deleteMsg (hash) {
  608. this.deleteChatItem(hash)
  609. },
  610. pinMsgClose () {
  611. this.pinMsg.visible = false
  612. },
  613. scrollToView () {
  614. if (!this.pinMsg) return
  615. let hash = this.pinMsg.hash
  616. let index = this.group.chatList.findIndex(item => item.hash === hash)
  617. if (index < 0) {
  618. this.addPinChatItem(this.pinMsg)
  619. index = 0
  620. }
  621. let node = this.$refs.msgWrap.getElementsByClassName('msg-item')[index]
  622. let toOffsetTop = index >= 0 ? node.offsetTop - (this.pinMsg ? 40 : 10) : node.offsetTop
  623. scrollMsgIntoView(
  624. this.$refs.scrollWrap, toOffsetTop, node
  625. )
  626. // 防止加载更多
  627. this.isScrollToView = true
  628. setTimeout(() => {
  629. this.isScrollToView = false
  630. lazyloadImage({
  631. wrap: this.$refs.scrollWrap,
  632. imageArr: this.chatImageArrSel,
  633. derection: 'up'
  634. })
  635. }, 2000)
  636. },
  637. scrollToMsg (index) {
  638. let hash = this.atList[index].hash
  639. let eleIndex = this.group.chatList.findIndex(item => item.hash === hash)
  640. if (eleIndex >= 0) {
  641. let node = this.$refs.msgWrap.querySelectorAll('.msg-item').item(eleIndex)
  642. scrollMsgIntoView(this.$refs.scrollWrap, node.offsetTop - (this.pinMsg ? 40 : 10), node)
  643. }
  644. this.removeAtListLast()
  645. }
  646. }
  647. }