chat.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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. let parts = _inputMsg.match(/\["[a-z_]+"\]/g)
  334. for (let k in parts) {
  335. let emoji = this.emojiMap[parts[k]]
  336. if (emoji) {
  337. _inputMsg = _inputMsg.replace(parts[k], emoji)
  338. }
  339. }
  340. let text = _inputMsg.trim()
  341. if (text.length === 0) {
  342. Message({
  343. message: '聊天内容不能为空',
  344. type: 'warning'
  345. })
  346. return
  347. }
  348. let opt = {
  349. type: 0,
  350. msg: text
  351. }
  352. // 用户不是第一次发言
  353. if (this.group.members[this.userId]) {
  354. let createTime = Date.now()
  355. this.addChatItem({
  356. from: this.userId,
  357. content: text,
  358. hash: `${createTime}`,
  359. timestamp: createTime,
  360. createTime,
  361. msg_type: '0',
  362. loading: true
  363. })
  364. opt.createTime = createTime
  365. }
  366. this.$store.commit('setSessionItemUnread', {
  367. session_id: this.curSession,
  368. unread: 0,
  369. curSession: this.curSession,
  370. cont: this.inputMsg,
  371. timestamp: Date.now()
  372. })
  373. this.isPrivate ? this.doSendPrivateMsg(opt) : this.doSendMsg(opt)
  374. // 滚到底部
  375. this.$nextTick(function () {
  376. this.inputMsg = ''
  377. this.resizeToBottom ? this.resizeToBottom() : this.$emit('toBottom')
  378. })
  379. e.preventDefault()
  380. return false
  381. },
  382. /**
  383. * 文件预处理
  384. * @return {Object} data 预处理文件信息
  385. * @param {Number} data.type
  386. * @param {File} data.res
  387. */
  388. async preHandleFile (file) {
  389. let type = file.type
  390. let size = file.size
  391. if (type.match('video')) {
  392. return size > 3 * 1024 * 1024
  393. ? Promise.reject(new Error(file))
  394. : Promise.resolve({
  395. type: 2,
  396. res: file
  397. })
  398. } else if (type.match('audio')) {
  399. return size > 2 * 1024 * 1024
  400. ? Promise.reject(new Error(file))
  401. : Promise.resolve({
  402. type: 3,
  403. res: file
  404. })
  405. } else if (type.match('image')) {
  406. let image = await new ImageMin({
  407. file: file,
  408. maxSize: 1024 * 1024
  409. })
  410. return {
  411. type: 1,
  412. preview: image.base64,
  413. res: image.res
  414. }
  415. }
  416. },
  417. /**
  418. * @des 处理文件发送
  419. */
  420. async handleFile (e) {
  421. let inputfile
  422. if (e.constructor === File) {
  423. inputfile = e
  424. } else {
  425. inputfile = e.target.files[0]
  426. }
  427. try {
  428. let fileInfo = await this.preHandleFile(inputfile)
  429. let opt = { res: fileInfo.res }
  430. if (this.group.members[this.userId]) {
  431. let createTime = Date.now()
  432. this.addChatItem({
  433. content: fileInfo.preview || '',
  434. from: this.userId,
  435. hash: `${createTime}`,
  436. msg_type: fileInfo.type,
  437. timestamp: createTime,
  438. res: fileInfo.res,
  439. loading: true,
  440. createTime
  441. })
  442. opt.createTime = createTime
  443. }
  444. this.doSendFile(opt)
  445. setTimeout(() => {
  446. if (this.$refs.inputFile) {
  447. this.$refs.inputFile.value = null
  448. }
  449. if (this.$refs.inputFile1) {
  450. this.$refs.inputFile1.value = null
  451. }
  452. if (this.$refs.inputFile2) {
  453. this.$refs.inputFile2.value = null
  454. }
  455. if (this.$refs.inputFile3) {
  456. this.$refs.inputFile3.value = null
  457. }
  458. this.resizeToBottom ? this.resizeToBottom() : this.$emit('toBottom')
  459. }, 100)
  460. } catch (error) {
  461. Message({
  462. message: '上传文件大小限制:音频2M以内,视频3M以内',
  463. type: 'warning'
  464. })
  465. }
  466. }
  467. }
  468. }