chat.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. import { mapActions, mapState, mapMutations } from 'vuex'
  2. import { scrollMsgIntoView } from '@/util/util.js'
  3. import { emojiList } from '@/util/emoji'
  4. import { Message } from 'element-ui'
  5. import ImageMin from '@/util/imageMin.js'
  6. // 聊天mixin 用于chatRoom组件
  7. export const chatMixin = {
  8. watch: {
  9. '$route' () {
  10. this.bdHiden = true
  11. // 切换房间
  12. this.groupSet = false
  13. this.lockMore = false
  14. this.lockEnd = false
  15. this.enableScroll = false
  16. this.initRoom()
  17. },
  18. chatList (val) {
  19. let lastVal = val[val.length - 1]
  20. if ((lastVal && lastVal.msg_type == 4) || this.isBottom) {
  21. // 自己发的红包自动滚动到底部
  22. this.$nextTick(this.resizeToBottom)
  23. }
  24. },
  25. isJoinGroup (val) {
  26. if (val == 1) setTimeout(this.resizeToBottom.bind(this), 100)
  27. }
  28. },
  29. data () {
  30. return {
  31. groupSet: false,
  32. lockMore: false,
  33. lockEnd: false,
  34. enableScroll: false, // 记录滚动条是否激活的状态
  35. isBottom: true,
  36. scrollHeight: 100, // 滚动条高度
  37. isScrollToView: false,
  38. isShowGroudMgr: false // 是否显示群管理
  39. }
  40. },
  41. computed: {
  42. ...mapState(['curSession', 'group', 'chat', 'userId', 'userInfo']),
  43. ...mapState({
  44. creator: state => state.group.creator,
  45. isJoin: state => state.group.isJoin,
  46. pinMsg: state => state.group.pinMsg,
  47. atList: state => state.group.atList,
  48. unreadNums: state => state.group.unreadNums,
  49. chatList: state => state.group.chatList,
  50. members: state => state.group.members,
  51. sessionId: state => state.curSession,
  52. sessionInfo: state => state.group.sessionInfo
  53. }),
  54. isExist () {
  55. // 是否存在在会话列表中
  56. let sessionList = this.chat.sessionList
  57. if (sessionList && sessionList.length) {
  58. return sessionList.some(e => {
  59. return e.session_id == this.sessionId
  60. })
  61. } else {
  62. return true
  63. }
  64. },
  65. isAdmin () {
  66. return (this.group.adminList && this.group.adminList.some(id => id == this.userId)) || this.group.creator == this.userId
  67. },
  68. isPrivate () {
  69. return this.$store.getters.isPrivate
  70. },
  71. isCreator () {
  72. return this.userId == this.creator
  73. },
  74. isJoinGroup () {
  75. if (this.group && this.group.groupId) {
  76. return this.isJoin ? 1 : 0
  77. } else {
  78. return 1
  79. }
  80. }
  81. },
  82. mounted () {
  83. this.initRoom()
  84. document.addEventListener('contextmenu', e => e.preventDefault())
  85. },
  86. methods: {
  87. ...mapMutations([
  88. 'initGroup',
  89. 'resetUnreadNums',
  90. 'addChatItem',
  91. 'deleteChatItem',
  92. 'initState',
  93. 'clearAtList',
  94. 'clearHash',
  95. 'setSessionItemUnread',
  96. 'clearChatList',
  97. 'changeSessionId',
  98. 'addPinChatItem'
  99. ]),
  100. ...mapActions([
  101. 'getGroupInfo',
  102. 'getUserInfo',
  103. 'getNewMsgFromDb',
  104. 'getNewMsg',
  105. 'getHistoryMsg',
  106. 'doSendMsg',
  107. 'initPrivateMsg',
  108. 'getPrivateHistoryMsg',
  109. 'doSendPrivateMsg'
  110. ]),
  111. async initRoom () {
  112. if (!this.userInfo) {
  113. await this.getUserInfo()
  114. }
  115. if (!this.userInfo) return
  116. this.changeSessionId(this.$route.params.id)
  117. this.clearHash()
  118. this.clearChatList()
  119. if (this.isPrivate) {
  120. this.initPersonChat()
  121. } else {
  122. this.initGroupChat()
  123. }
  124. if (this.isExist) {
  125. // 把会话列表的消息数设置为0
  126. this.$store.commit('setSessionItemUnread', {
  127. session_id: this.curSession,
  128. unread: 0,
  129. curSession: this.curSession
  130. })
  131. }
  132. },
  133. /**
  134. * @des 私聊初始化处理
  135. */
  136. async initPersonChat () {
  137. let data = await this.initPrivateMsg()
  138. this.$nextTick(() => {
  139. this.resizeToBottom()
  140. this.bdHiden = false
  141. })
  142. setTimeout(() => {
  143. // 不存在会话,则添加会话
  144. if (!this.isExist) {
  145. // 获取对方信息
  146. let otherInfo = null
  147. for (let key in data.data.userMap) {
  148. if (key != this.userId) {
  149. otherInfo = data.data.userMap[key]
  150. }
  151. }
  152. if (otherInfo) {
  153. let obj = {
  154. cover_photo: otherInfo.cover_photo,
  155. is_group: '0',
  156. name: otherInfo.nick_name,
  157. session_id: this.sessionId
  158. }
  159. this.$store.commit('addSessionItem', obj)
  160. }
  161. }
  162. }, 1000)
  163. return data
  164. },
  165. /**
  166. * @des 聊天群初始化处理
  167. */
  168. async initGroupChat () {
  169. this.initGroup({
  170. userId: this.userId,
  171. groupId: this.sessionId,
  172. useCache: false
  173. })
  174. this.isShowGroudMgr = false
  175. this.getGroupInfo()
  176. await this.getNewMsgFromDb()
  177. await this.getNewMsg()
  178. this.$nextTick(() => {
  179. this.resizeToBottom()
  180. this.bdHiden = false
  181. })
  182. setTimeout(() => {
  183. // 不存在会话,则添加会话
  184. if (!this.isExist) {
  185. // 获取对方信息
  186. if (this.group) {
  187. let obj = {
  188. cover_photo: this.group.coverPhoto,
  189. is_group: '1',
  190. name: this.group.groupName,
  191. session_id: this.sessionId
  192. }
  193. this.$store.commit('addSessionItem', obj)
  194. }
  195. }
  196. }, 1000)
  197. },
  198. /**
  199. * @des 滚动事件监听
  200. */
  201. initScrollEvent () {},
  202. /**
  203. * @des 聊天窗体滚动事件处理集
  204. */
  205. async handleScroll (e) {
  206. // 防止切换房间时触发滚动处理
  207. if (!this.group.chatList.length) {
  208. return
  209. }
  210. // 防止滚动到置顶消息触发滚动
  211. if (this.isScrollToView) {
  212. return
  213. }
  214. // 激活滚动条
  215. this.enableScroll = true
  216. let totalHeight = this.$refs.msgWrap.offsetHeight
  217. let scrollTop = e.target.scrollTop
  218. // 差不多滚动到顶部
  219. if (scrollTop === 0 && !this.lockMore) {
  220. if (this.group.endHash !== null) {
  221. this.lockMore = true
  222. let res
  223. if (this.isPrivate) {
  224. res = await this.getPrivateHistoryMsg()
  225. } else {
  226. res = await this.getHistoryMsg()
  227. }
  228. if (res === 'end') {
  229. this.lockEnd = true
  230. } else {
  231. let scrollBottom = totalHeight - scrollTop
  232. this.$nextTick(() => {
  233. e.target.scrollTop =
  234. this.$refs.msgWrap.offsetHeight - scrollBottom
  235. setTimeout(() => {
  236. this.lockMore = false
  237. }, 800)
  238. })
  239. }
  240. }
  241. }
  242. // 滚动到底部清空未读消息状态
  243. if (scrollTop + e.target.offsetHeight > totalHeight) {
  244. this.isBottom = true
  245. if (this.group.unreadNums) {
  246. this.resetUnreadNums()
  247. }
  248. } else {
  249. this.isBottom = false
  250. }
  251. },
  252. /**
  253. * @des 聊天窗体滚动到底部
  254. */
  255. resizeToBottom () {
  256. if (!this.$refs.msgWrap) return
  257. this.$refs.scrollWrap.scrollTop = this.$refs.msgWrap.offsetHeight
  258. this.resetUnreadNums()
  259. this.isBottom = true
  260. },
  261. /**
  262. * @des 点击,查看未读消息
  263. * 直接滚动到聊天列表底部
  264. */
  265. doSetRead () {
  266. this.resizeToBottom()
  267. },
  268. /**
  269. * @des 引用某条消息
  270. */
  271. quoteMsg (msg) {
  272. this.$refs.inputArea.inputMsg = msg
  273. },
  274. /**
  275. * @des 某条消息被删除
  276. */
  277. deleteMsg (hash) {
  278. this.deleteChatItem(hash)
  279. },
  280. pinMsgClose () {
  281. this.pinMsg.visible = false
  282. },
  283. scrollToView () {
  284. if (!this.pinMsg) return
  285. let hash = this.pinMsg.hash
  286. let index = this.group.chatList.findIndex(item => item.hash === hash)
  287. if (index < 0) {
  288. this.addPinChatItem(this.pinMsg)
  289. index = 0
  290. }
  291. let node = this.$refs.msgWrap.getElementsByClassName('msg-item')[index]
  292. let toOffsetTop = index >= 0 ? node.offsetTop - (this.pinMsg ? 40 : 10) : node.offsetTop
  293. scrollMsgIntoView(
  294. this.$refs.scrollWrap, toOffsetTop, node
  295. )
  296. // 防止加载更多
  297. this.isScrollToView = true
  298. setTimeout(() => {
  299. this.isScrollToView = false
  300. }, 2000)
  301. },
  302. scrollToMsg (index) {
  303. let hash = this.atList[index].hash
  304. let eleIndex = this.group.chatList.findIndex(item => item.hash === hash)
  305. if (eleIndex >= 0) {
  306. let node = this.$refs.msgWrap.getElementsByClassName('msg-item').item(eleIndex)
  307. scrollMsgIntoView(
  308. this.$refs.scrollWrap,
  309. node.offsetTop - (this.pinMsg ? 40 : 10),
  310. node
  311. )
  312. }
  313. this.clearAtList()
  314. },
  315. async joinGroup () {
  316. await this.$store.dispatch('joinGroup')
  317. this.$store.dispatch('getGroupInfo')
  318. },
  319. // 群管理
  320. showGroudMgr (flag) {
  321. this.isShowGroudMgr = flag == 1
  322. },
  323. // 关闭表情,文件栏
  324. initEmojiAndTool () {
  325. this.emojiShow = false
  326. this.toolShow = false
  327. }
  328. },
  329. beforeDestroy () {
  330. document.body.removeEventListener('click', this.initEmojiAndTool)
  331. }
  332. }
  333. // 聊天输入框mixin
  334. export const inputMixin = {
  335. computed: {
  336. ...mapState(['group', 'userId']),
  337. ...mapState({
  338. chatInputFocus: state => state.group.chatInputFocus,
  339. blockList: state => state.group.blockList
  340. }),
  341. isPrivate () {
  342. return this.$store.getters.isPrivate
  343. },
  344. emojiMap () {
  345. var emojiMap = {}
  346. for (let i in emojiList) {
  347. let arr = emojiList[i]
  348. arr.forEach(v => {
  349. let names = JSON.stringify(v.names)
  350. let emoji = v.surrogates
  351. emojiMap[names] = emoji
  352. })
  353. }
  354. return emojiMap
  355. }
  356. },
  357. data () {
  358. return {
  359. emojiShow: false,
  360. inputMsg: '',
  361. atInd: 0
  362. }
  363. },
  364. mounted () {
  365. // document.body.addEventListener('click', () => {
  366. // this.emojiShow = false
  367. // })
  368. },
  369. methods: {
  370. ...mapMutations(['updateChatInputFocus', 'addChatItem']),
  371. ...mapActions(['doSendMsg', 'doSendFile', 'doSendPrivateMsg']),
  372. addEmoji (val) {
  373. this.inputMsg += val
  374. this.emojiShow = false
  375. this.$refs.chatInput.focus()
  376. },
  377. /**
  378. * @des 处理消息发送
  379. */
  380. async handleSend (e) {
  381. // 判断是否被禁言
  382. if (this.blockList.some(id => id == this.userId)) {
  383. Message({
  384. message: '您已被禁言',
  385. type: 'error'
  386. })
  387. return
  388. }
  389. // 替换emoji字符串
  390. let _inputMsg = this.inputMsg
  391. let parts = _inputMsg.match(/\["[a-z0-9A-Z_]+"\]/g)
  392. for (let k in parts) {
  393. let emoji = this.emojiMap[parts[k]]
  394. if (emoji) {
  395. _inputMsg = _inputMsg.replace(parts[k], emoji)
  396. }
  397. }
  398. let text = _inputMsg.trim()
  399. if (text.length === 0) {
  400. Message({
  401. message: '聊天内容不能为空',
  402. type: 'warning'
  403. })
  404. return
  405. }
  406. let opt = {
  407. type: 0,
  408. msg: text
  409. }
  410. // 用户不是第一次发言
  411. if (this.group.members[this.userId]) {
  412. let createTime = Date.now()
  413. this.addChatItem({
  414. from: this.userId,
  415. content: text,
  416. hash: `${createTime}`,
  417. timestamp: createTime,
  418. createTime,
  419. msg_type: '0',
  420. loading: true
  421. })
  422. opt.createTime = createTime
  423. }
  424. this.inputMsg = ''
  425. let data = this.isPrivate ? await this.doSendPrivateMsg(opt) : await this.doSendMsg(opt)
  426. // // 发送成功后,才加
  427. this.$store.commit('setSessionItemUnread', {
  428. session_id: this.curSession,
  429. unread: 0,
  430. curSession: this.curSession,
  431. cont: text,
  432. timestamp: data.timestamp
  433. })
  434. // 滚到底部
  435. this.$nextTick(function () {
  436. this.resizeToBottom ? this.resizeToBottom() : this.$emit('toBottom')
  437. })
  438. e.preventDefault()
  439. return false
  440. },
  441. /**
  442. * 文件预处理
  443. * @return {Object} data 预处理文件信息
  444. * @param {Number} data.type
  445. * @param {File} data.res
  446. */
  447. async preHandleFile (file) {
  448. let type = file.type
  449. let size = file.size
  450. if (type.match('video')) {
  451. return size > 3 * 1024 * 1024
  452. ? Promise.reject(new Error(file))
  453. : Promise.resolve({
  454. type: 2,
  455. res: file
  456. })
  457. } else if (type.match('audio')) {
  458. return size > 2 * 1024 * 1024
  459. ? Promise.reject(new Error(file))
  460. : Promise.resolve({
  461. type: 3,
  462. res: file
  463. })
  464. } else if (type.match('image')) {
  465. let image = await new ImageMin({
  466. file: file,
  467. maxSize: 1024 * 1024
  468. })
  469. return {
  470. type: 1,
  471. preview: image.base64,
  472. res: image.res
  473. }
  474. }
  475. },
  476. /**
  477. * @des 处理文件发送
  478. */
  479. async handleFile (e) {
  480. let inputfile
  481. if (e.constructor === File) {
  482. inputfile = e
  483. } else {
  484. inputfile = e.target.files[0]
  485. }
  486. try {
  487. let fileInfo = await this.preHandleFile(inputfile)
  488. let opt = { res: fileInfo.res }
  489. if (this.group.members[this.userId]) {
  490. let createTime = Date.now()
  491. this.addChatItem({
  492. content: fileInfo.preview || '',
  493. from: this.userId,
  494. hash: `${createTime}`,
  495. msg_type: fileInfo.type,
  496. timestamp: createTime,
  497. res: fileInfo.res,
  498. loading: true,
  499. createTime
  500. })
  501. opt.createTime = createTime
  502. }
  503. this.doSendFile(opt)
  504. setTimeout(() => {
  505. if (this.$refs.inputFile) {
  506. this.$refs.inputFile.value = null
  507. }
  508. if (this.$refs.inputFile1) {
  509. this.$refs.inputFile1.value = null
  510. }
  511. if (this.$refs.inputFile2) {
  512. this.$refs.inputFile2.value = null
  513. }
  514. if (this.$refs.inputFile3) {
  515. this.$refs.inputFile3.value = null
  516. }
  517. this.$refs.toolbar && this.$refs.toolbar.resetInput()
  518. this.resizeToBottom ? this.resizeToBottom() : this.$emit('toBottom')
  519. }, 100)
  520. } catch (error) {
  521. Message({
  522. message: '上传文件大小限制:音频2M以内,视频3M以内',
  523. type: 'warning'
  524. })
  525. }
  526. }
  527. }
  528. }