chat.js 12 KB

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