chat.js 12 KB

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