chat.js 12 KB

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