chatMini.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. <template>
  2. <div class="mini-wrap">
  3. <div class="mini-body" :class="{'moblie-wrap': isMobile}" v-show="showChat">
  4. <div class="box">
  5. <div class="box-hd">
  6. <div class="btn-menu" :class="[{'active':personUnRead>0}]" @click.stop="showMenuExtra = !showMenuExtra"></div>
  7. <div class="box-title" @click.stop="showMenuExtra = !showMenuExtra">{{group.groupName}} ({{group.membersNum}})</div>
  8. <div class="btn-close" @click="handleToggleChat(false)"></div>
  9. <p class="user-tips" v-if="showLoginBtn" @click="handleLogin"><em>{{ $t('mini.server') }}</em></p>
  10. <a class="user-tips" v-else :class="[{'active':serverUnRead>0}]" :href="linkToCreator" target="meechatpc" @click="clearPmUnread(1)">
  11. <em>{{ $t('mini.server') }}</em>
  12. </a>
  13. <ul class="menu-extra" v-show="showMenuExtra">
  14. <div class="info clearfix" v-if="account && userInfo">
  15. <img class="avatar" :src="userInfo.cover_photo" v-if="userInfo.cover_photo">
  16. <span class="avatar avatar-bg" v-else>{{userInfo.nick_name.slice(0,2).toUpperCase() || userInfo.user_name.slice(0,2).toUpperCase()}}</span>
  17. <span class="account-info">
  18. <em>{{userInfo.nick_name || userInfo.user_name}}</em>
  19. <i class="login-out" href="javascript:void(0);" @click="handleLogout">{{ $t('mini.logout') }}</i>
  20. </span>
  21. </div>
  22. <li v-if="account && userInfo" @click="$showUserInfo"><a href="#">{{ $t('mini.accountSetting') }}</a></li>
  23. <li v-if="group.groupId"><a :href="`/#/group/${group.groupId}`" target="meechatpc" @click="clearPmUnread(2)">{{ $t('mini.openWeb') }}<em v-show="personUnRead>0">{{personUnRead}}</em></a></li>
  24. <li @click="changeLang"><a href="javascript:;">{{curLang=="en"?'中文':'English'}}</a></li>
  25. <li><a href="https://www.mee.chat/" target="_blank">
  26. Powered by <span>MeeChat</span>
  27. </a></li>
  28. </ul>
  29. </div>
  30. <chat-pin v-bind="pinMsg" @pinMsgClose="pinMsgClose" @scrollToView="scrollToView"></chat-pin>
  31. <div class="box-bd" ref="msgBox">
  32. <div ref="scrollWrap"
  33. @scroll.prevent="handleScroll"
  34. class="scroller"
  35. >
  36. <div ref="msgWrap" class="msg-wrap">
  37. <div class="msg-top-more" v-if="lockEnd">
  38. <em>{{ $t('chat.noMore') }}</em>
  39. </div>
  40. <div class="msg-top-load" v-if="lockMore && !lockEnd">
  41. <i class="msg-loading-icon"></i>
  42. </div>
  43. <msg-item v-for="item in chatList"
  44. :key="item.hash"
  45. v-bind="item"
  46. :msgItem="item"
  47. :isMobile="isMobile"
  48. :isAdmin="isAdmin"
  49. @quoteMsg="quoteMsg"
  50. @deleteMsg="deleteMsg"
  51. ></msg-item>
  52. </div>
  53. </div>
  54. <at-me :atList="atList" class="mini" @scrollToMsg="scrollToMsg"></at-me>
  55. <div class="msg-unread"
  56. @click="doSetRead"
  57. v-if="unreadNums > 0 && enableScroll && !isBottom">
  58. <em><i class="el-icon-d-arrow-right"></i>{{unreadNums}}{{ $t('chat.unreadMsg') }}</em>
  59. </div>
  60. </div>
  61. <div class="box-ft">
  62. <chat-at
  63. ref="chatAt"
  64. v-if="atShow"
  65. @atperson="atPerson"
  66. :curInd="atInd"
  67. :filterList="filterMembers">
  68. </chat-at>
  69. <div class="input-ctrl" v-if="showLoginBtn">
  70. <span v-if="showLoginBtn === 'loading'">{{ $t('mini.logining') }}</span>
  71. <span v-else class="enable" @click="handleLogin">{{ $t('mini.login') }}</span>
  72. </div>
  73. <div class="input-con" v-else>
  74. <div class="more-icon" @click.stop="handleMoreClick"></div>
  75. <form class="input-wrap" @submit="handleSend">
  76. <textarea
  77. @keydown.up="handleUp"
  78. @keydown.down="handleDown"
  79. @keydown.left="handleLeft"
  80. @keydown.right="handleRight"
  81. @keydown.delete="handleDel"
  82. @keydown.esc="handleEsc"
  83. cols="1"
  84. ref="chatInput"
  85. rows="1"
  86. @keydown.enter="handleKeyDown"
  87. placeholder="Write a message"
  88. v-model="inputMsg"
  89. @focus="handleFocus"
  90. @blur="handleBlur"
  91. :style="{height:inputHeight}"
  92. />
  93. </form>
  94. <div class="emoji-icon" @click.stop="handleEmojiClick"></div>
  95. <div class="btn-send" @click="handleSend">{{ $t('chat.send') }}</div>
  96. </div>
  97. <toolbar ref="toolbar" :toolShow="toolShow" @handleFile="handleFile"></toolbar>
  98. <emoji @addEmoji="addEmoji" :emojiShow="showEmoji" v-show="showEmoji" @closeEmojiList="closeEmojiList"></emoji>
  99. </div>
  100. </div>
  101. </div>
  102. <div @click="handleToggleChat(true)" class="mini-control" v-show="!showChat">
  103. <i class="at-tip" v-show="atList.length">@</i>
  104. <ul v-if="unreadCounts > 0">
  105. <li class="msg-tips">
  106. <em>+{{unreadCounts}}</em>
  107. </li>
  108. <li class="user-tips">
  109. <em>{{group.membersNum}}</em>
  110. </li>
  111. </ul>
  112. <div class="meechat-icon" v-else>
  113. <i class="user-tips">
  114. <em>{{group.membersNum}}</em>
  115. </i>
  116. </div>
  117. </div>
  118. </div>
  119. </template>
  120. <script>
  121. import msgItem from '@/components/msgItem'
  122. import emoji from '@/components/emoji'
  123. import chatAt from '@/components/chatAt'
  124. import atMe from '@/components/chatAt/atme'
  125. import chatPin from '@/components/chatPin'
  126. import toolbar from '@/components/chatInput/toolbar'
  127. import { mapActions, mapState, mapMutations } from 'vuex'
  128. import { getMiniWsUrl } from '@/util/contract.js'
  129. import { isMobile, scrollMsgIntoView } from '@/util/util.js'
  130. import WsManager from '@/util/wsManager.js'
  131. import PostMessager from '@/util/postMessager.js'
  132. import { Message } from 'element-ui'
  133. import ImageMin from '@/util/imageMin.js'
  134. import { accountLoginMixin, chatAtMixin, chatInputMixin, changeLangMixin } from '@/mixins'
  135. import { emojiList } from '@/util/emoji'
  136. import api from '@/api'
  137. export default {
  138. name: 'chatMini',
  139. mixins: [accountLoginMixin, chatAtMixin, chatInputMixin, changeLangMixin],
  140. components: {
  141. msgItem,
  142. emoji,
  143. chatAt,
  144. chatPin,
  145. atMe,
  146. toolbar
  147. },
  148. props: {
  149. width: {
  150. type: Number,
  151. default: 274
  152. },
  153. height: {
  154. type: Number,
  155. default: 390
  156. },
  157. show: {
  158. type: Boolean,
  159. default: false
  160. },
  161. groupId: [Number, String]
  162. },
  163. computed: {
  164. ...mapState([
  165. 'account',
  166. 'group',
  167. 'userId',
  168. 'userInfo'
  169. ]),
  170. ...mapState({
  171. chatInputFocus: state => state.group.chatInputFocus,
  172. blockList: state => state.group.blockList,
  173. pinMsg: state => state.group.pinMsg,
  174. atList: state => state.group.atList,
  175. chatList: state => state.group.chatList,
  176. unreadNums: state => state.group.unreadNums,
  177. sessionList: state => state.chat.sessionList
  178. }),
  179. emojiMap () {
  180. var emojiMap = {}
  181. for (let i in emojiList) {
  182. let arr = emojiList[i]
  183. arr.forEach(v => {
  184. let names = JSON.stringify(v.names)
  185. let emoji = v.surrogates
  186. emojiMap[names] = emoji
  187. })
  188. }
  189. return emojiMap
  190. },
  191. linkToCreator () {
  192. let { creator, userId } = this.group
  193. let sessionId = creator > userId ? `${userId}-${creator}` : `${creator}-${userId}`
  194. return `${location.origin}/#/pm/${sessionId}`
  195. },
  196. isAdmin () {
  197. return (this.group.adminList && this.group.adminList.some(id => id == this.userId)) || this.group.creator == this.userId
  198. }
  199. },
  200. data () {
  201. return {
  202. isMobile: isMobile(),
  203. showChat: !!this.show, // 显示聊天窗
  204. showEmoji: false, // 显示表情选择框
  205. showMenuExtra: false, // 显示左上角菜单
  206. showLoginBtn: true, // 显示登录按钮
  207. lockMore: false,
  208. isScrollToView: false,
  209. lockEnd: false,
  210. loading: false,
  211. unreadCounts: 0, // 未读消息数
  212. inputMsg: '', // 用户输入的内容
  213. atInd: 0, // @人索引
  214. inputHeight: 18,
  215. enableScroll: false, // 记录滚动条是否激活的状态
  216. isBottom: true,
  217. toolShow: false,
  218. serverUnRead: 0, // 客服未读
  219. personUnRead: 0 // 私聊未读
  220. }
  221. },
  222. watch: {
  223. inputMsg (val, newval) {
  224. let ele = this.$refs.chatInput
  225. this.inputHeight = 'auto'
  226. this.$nextTick(() => {
  227. this.inputHeight = Math.max(18, Math.min(ele.scrollHeight, 75)) + 'px'
  228. })
  229. },
  230. chatList (val) {
  231. let lastVal = val[val.length - 1]
  232. if ((lastVal && lastVal.msg_type == 4) || this.isBottom) {
  233. // 自己发的红包自动滚动到底部
  234. this.$nextTick(this.resizeToBottom)
  235. }
  236. },
  237. chatInputFocus (val, newval) {
  238. if (this.showLoginBtn) return
  239. let ele = this.$refs.chatInput
  240. if (val) {
  241. if (document.activeElement !== ele) {
  242. this.placeEnd(ele)
  243. ele.focus()
  244. }
  245. } else {
  246. if (document.activeElement === ele) {
  247. ele.blur()
  248. }
  249. }
  250. }
  251. },
  252. async mounted () {
  253. // 初始化postMessage消息管理器
  254. let callback = (event) => {
  255. if (event.action === 'meechat:setShow') {
  256. this.handleToggleChat(event.show)
  257. } else if (event.action === 'meechat:logout') {
  258. this.handleLogout2()
  259. }
  260. }
  261. this.postMessager = new PostMessager('*', { callback })
  262. window.postMessager = this.postMessager
  263. // 设置groupId
  264. this.initGroup({
  265. userId: this.userId,
  266. groupId: this.groupId,
  267. useCache: false
  268. })
  269. // 检查登录态
  270. let userId = localStorage.getItem('user_id')
  271. let token = localStorage.getItem('token')
  272. let account = localStorage.getItem('account')
  273. if (userId && token && account) {
  274. this.setUserId(userId)
  275. this.setToken(token)
  276. this.setAccount(JSON.parse(account))
  277. this.showLoginBtn = false
  278. if (self === top) {
  279. await this.scatterConnect()
  280. }
  281. await this.getUserInfo()
  282. }
  283. this.$nextTick(this.initChat)
  284. this.$nextTick(this.initMiniSocket)
  285. document.addEventListener('contextmenu', e => e.preventDefault())
  286. document.addEventListener('paste', this.initPaste)
  287. document.addEventListener('drop', this.initDrop)
  288. document.addEventListener('dragover', this.initDragOver)
  289. document.body.addEventListener('click', () => {
  290. this.showEmoji = false
  291. this.showMenuExtra = false
  292. this.toolShow = false
  293. })
  294. },
  295. beforeDestroy () {
  296. document.removeEventListener('paste', this.initPaste)
  297. document.removeEventListener('drop', this.initDrop)
  298. document.removeEventListener('dragover', this.initDragOver)
  299. },
  300. methods: {
  301. ...mapMutations([
  302. 'initGroup',
  303. 'setUserId',
  304. 'setToken',
  305. 'addChatItem',
  306. 'deleteChatItem',
  307. 'updateChatInputFocus',
  308. 'updateGroupBlockList',
  309. 'updateMembers',
  310. 'updateGroupPinMsg',
  311. 'repealChatItem',
  312. 'removeAtListLast',
  313. 'clearAtList',
  314. 'initState',
  315. 'addPacketItem',
  316. 'addPacketTip',
  317. 'addUnreadNums',
  318. 'resetUnreadNums'
  319. ]),
  320. ...mapActions([
  321. 'getUserInfo',
  322. 'setAccount',
  323. 'doGameLogin',
  324. 'doScatterLogout',
  325. 'getGroupInfo',
  326. 'getNewMsgFromDb',
  327. 'getNewMsg',
  328. 'getHistoryMsg',
  329. 'doSendMsg',
  330. 'doSendFile',
  331. 'doContractLogin',
  332. 'updateSessionLastmsg'
  333. ]),
  334. handleMoreClick () {
  335. this.showEmoji = false
  336. this.toolShow = !this.toolShow
  337. this.checkNeedToBottom()
  338. },
  339. handleEmojiClick () {
  340. this.toolShow = false
  341. this.showEmoji = !this.showEmoji
  342. this.checkNeedToBottom()
  343. },
  344. checkNeedToBottom () {
  345. if (!this.isBottom) return
  346. this.$nextTick(() => {
  347. this.resizeToBottom()
  348. })
  349. },
  350. // 连接socket
  351. initMiniSocket () {
  352. if (!window.WebSocket) {
  353. console.log('Error: WebSocket is not supported .')
  354. return
  355. }
  356. let host = getMiniWsUrl() + `?group_id=${this.groupId}`
  357. if (this.socket) {
  358. this.socket.destroy()
  359. this.socket = null
  360. }
  361. this.socket = new WsManager(host, {
  362. autoConnect: true, // 自动连接
  363. reconnection: true, // 断开自动重连
  364. reconnectionDelay: 2000 // 重连间隔时间,单位秒
  365. })
  366. this.socket.on('open', res => {})
  367. this.socket.on('message', (data) => {
  368. data = JSON.parse(data)
  369. if (data.channel.match('chat:group')) {
  370. if (data.data.type === 'msg') {
  371. this.getNewMsg({ newMsg: true })
  372. if (data.data.from != this.userId) {
  373. // 未读消息数+1
  374. if (!this.showChat) {
  375. if (this.unreadCounts === 0) {
  376. this.postResize(130, 50)
  377. }
  378. this.unreadCounts++
  379. } else {
  380. this.addUnreadNums()
  381. }
  382. }
  383. }
  384. if (data.data.type === 'repeal') {
  385. this.repealChatItem(data.data)
  386. }
  387. if (data.data.type === 'block') {
  388. this.updateGroupBlockList({
  389. type: 'add',
  390. id: data.data.to
  391. })
  392. }
  393. if (data.data.type === 'unblock') {
  394. this.updateGroupBlockList({
  395. type: 'delete',
  396. id: data.data.to
  397. })
  398. }
  399. if (data.data.type === 'join') {
  400. this.updateMembers(data.data.user_info)
  401. }
  402. if (data.data.type === 'pin_msg') {
  403. this.updateGroupPinMsg(data.data.pinMsg)
  404. }
  405. if (data.data.type === 'unpin_msg') {
  406. this.updateGroupPinMsg(null)
  407. }
  408. if (data.data.type === 'new_redpack') {
  409. this.addPacketItem(data.data)
  410. if (data.data.from == this.userId) {
  411. this.$nextTick(this.resizeToBottom)
  412. }
  413. }
  414. if (data.data.type === 'grab_redpack') {
  415. if (data.data.from == this.userId || data.data.to == this.userId) {
  416. this.addPacketTip(data.data)
  417. }
  418. }
  419. }
  420. })
  421. },
  422. /**
  423. * 聊天群初始化处理
  424. * 先后调用 group/info, group/msg
  425. */
  426. async initChat () {
  427. this.handleToggleChat(this.show)
  428. await this.getGroupInfo()
  429. await this.getNewMsgFromDb()
  430. await this.getNewMsg()
  431. // 有登录态才要请求
  432. if (!this.showLoginBtn) {
  433. await this.getPmUnRead()
  434. }
  435. if (this.show) {
  436. this.$nextTick(this.resizeToBottom)
  437. }
  438. },
  439. getPmUnRead () {
  440. api.session.getMiniUnRead().then(({ data }) => {
  441. this.personUnRead = data.data['0']
  442. this.serverUnRead = data.data[this.groupId] || 0
  443. })
  444. },
  445. /**
  446. * 清空私聊未读
  447. * @param {type} 1.客服2.私聊
  448. **/
  449. clearPmUnread (type) {
  450. if (type == 1) this.serverUnRead = 0
  451. else this.personUnRead = 0
  452. },
  453. async handleLogout () {
  454. this.doScatterLogout()
  455. this.showLoginBtn = true
  456. if (self !== top) {
  457. localStorage.removeItem('account')
  458. this.postMessager.send({
  459. action: 'meechat:logout'
  460. })
  461. }
  462. // 初始vuex数据
  463. this.$store.commit('setUserInfo', null)
  464. this.$store.commit('initChatData')
  465. this.$store.commit('initGroupData')
  466. // 注销后,刷新页面
  467. location.replace(location.href.replace('show=false', 'show=true'))
  468. },
  469. async handleLogout2 () {
  470. this.doScatterLogout()
  471. this.showLoginBtn = true
  472. },
  473. /**
  474. * 登录处理
  475. */
  476. async handleLogin () {
  477. // 设置登录按钮状态
  478. this.showLoginBtn = 'loading'
  479. if (self !== top) {
  480. this.handleParentLogin()
  481. } else {
  482. // 连接scatter
  483. this.scatterConnect()
  484. }
  485. },
  486. async scatterConnect () {
  487. await this.loginEos()
  488. },
  489. /**
  490. * @des 调用父级页面getIdentity
  491. */
  492. async getParentIdentity () {
  493. if (self !== top) {
  494. try {
  495. let response = await this.postMessager.send({ action: 'meechat:getIdentity' })
  496. let account = response.accounts.find(x => x.blockchain === 'eos')
  497. localStorage.setItem('account', JSON.stringify(account))
  498. this.setAccount(account)
  499. } catch (error) {
  500. this.showLoginBtn = true
  501. }
  502. }
  503. },
  504. /**
  505. * @des 调用父级页面登录
  506. */
  507. async handleParentLogin () {
  508. try {
  509. // scatter登录
  510. await this.getParentIdentity()
  511. if (!this.account) return
  512. // 合约登录
  513. await this.doContractLogin()
  514. location.replace(location.href.replace('show=false', 'show=true'))
  515. } catch (msg) {
  516. if (msg.type) {
  517. // scatter报错的情况
  518. } else {
  519. let json = JSON.parse(msg)
  520. let details = json.error.details
  521. Message({
  522. message: details[0].message,
  523. type: 'error'
  524. })
  525. }
  526. this.showLoginBtn = true
  527. }
  528. },
  529. /**
  530. * 聊天窗体滚动到底部
  531. */
  532. resizeToBottom () {
  533. this.$refs.scrollWrap.scrollTop = this.$refs.msgWrap.offsetHeight
  534. this.resetUnreadNums()
  535. this.isBottom = true
  536. },
  537. /**
  538. * @des 点击,查看未读消息
  539. * 直接滚动到聊天列表底部
  540. */
  541. doSetRead () {
  542. this.resizeToBottom()
  543. },
  544. /**
  545. * 添加表情
  546. */
  547. addEmoji (value) {
  548. this.inputMsg += value
  549. },
  550. /**
  551. * @des 聊天窗体滚动事件处理集
  552. */
  553. handleScroll (e) {
  554. this.enableScroll = true
  555. e.target.focus()
  556. // 防止切换房间时触发滚动处理
  557. if (!this.group.chatList.length) {
  558. return
  559. }
  560. // 防止滚动到置顶消息触发滚动
  561. if (this.isScrollToView) {
  562. return
  563. }
  564. let msgWrap = this.$refs.msgWrap
  565. let totalHeight = msgWrap.offsetHeight
  566. let scrollTop = e.target.scrollTop
  567. if (scrollTop === 0 && !this.lockMore) {
  568. if (this.group.endHash !== null) {
  569. this.lockMore = true
  570. this.getHistoryMsg().then((res) => {
  571. if (res === 'end') {
  572. this.lockEnd = true
  573. } else {
  574. let scrollBottom = totalHeight - scrollTop
  575. this.$nextTick(() => {
  576. e.target.scrollTop = msgWrap.offsetHeight - scrollBottom
  577. this.ps && this.ps.update()
  578. setTimeout(() => {
  579. this.lockMore = false
  580. }, 800)
  581. })
  582. }
  583. })
  584. }
  585. }
  586. // 滚动到底部清空未读消息状态
  587. if (scrollTop + e.target.offsetHeight > totalHeight) {
  588. this.isBottom = true
  589. if (this.unreadNums) {
  590. this.resetUnreadNums()
  591. }
  592. } else {
  593. this.isBottom = false
  594. }
  595. },
  596. /**
  597. * @des 处理消息发送
  598. */
  599. async handleSend (e) {
  600. // 判断是否被禁言
  601. if (this.blockList.some(id => id == this.userId)) {
  602. Message({
  603. message: this.$t('chat.youAreBan'),
  604. type: 'error'
  605. })
  606. return
  607. }
  608. // 替换emoji字符串
  609. let _inputMsg = this.inputMsg
  610. let parts = _inputMsg.match(/\["[a-z0-9A-Z_]+"\]/g)
  611. for (let k in parts) {
  612. let emoji = this.emojiMap[parts[k]]
  613. if (emoji) {
  614. _inputMsg = _inputMsg.replace(parts[k], emoji)
  615. }
  616. }
  617. let text = _inputMsg.trim()
  618. if (text.length === 0) {
  619. Message({
  620. message: this.$t('chat.cannotBeEmpty'),
  621. type: 'warning'
  622. })
  623. return
  624. }
  625. let opt = {
  626. type: 0,
  627. msg: text
  628. }
  629. // 清空输入框
  630. this.inputMsg = ''
  631. // 用户不是第一次发言
  632. if (this.group.members[this.userId]) {
  633. let createTime = Date.now()
  634. this.addChatItem({
  635. from: this.userId,
  636. content: text,
  637. hash: `${createTime}`,
  638. timestamp: createTime,
  639. createTime,
  640. msg_type: '0',
  641. loading: true
  642. })
  643. opt.createTime = createTime
  644. await this.doSendMsg(opt)
  645. } else {
  646. // 发言后,才滚动底部
  647. await this.doSendMsg(opt)
  648. }
  649. // 滚到底部
  650. this.$nextTick(function () {
  651. this.resizeToBottom()
  652. })
  653. e.preventDefault()
  654. return false
  655. },
  656. placeEnd (el) {
  657. var range = document.createRange()
  658. range.selectNodeContents(el)
  659. range.collapse(false)
  660. var sel = window.getSelection()
  661. sel.removeAllRanges()
  662. sel.addRange(range)
  663. },
  664. initDrop (e) {
  665. e.preventDefault()
  666. let files = Array.from(e.dataTransfer.files)
  667. files.forEach(file => this.handleFile(file))
  668. },
  669. initDragOver (e) {
  670. e.preventDefault()
  671. },
  672. initPaste (event) {
  673. var items = (event.clipboardData || window.clipboardData).items
  674. if (items && items.length) {
  675. Array.from(items).forEach(item => {
  676. let file = item.getAsFile()
  677. if (file) {
  678. this.handleFile(file)
  679. }
  680. })
  681. }
  682. },
  683. /**
  684. * 文件预处理
  685. * @return {Object} data 预处理文件信息
  686. * @param {Number} data.type
  687. * @param {File} data.res
  688. */
  689. async preHandleFile (file) {
  690. let type = file.type
  691. let size = file.size
  692. if (type.match('video')) {
  693. return size > 3 * 1024 * 1024 ? Promise.reject(new Error(file)) : Promise.resolve({
  694. type: 2,
  695. res: file
  696. })
  697. } else if (type.match('audio')) {
  698. return size > 2 * 1024 * 1024 ? Promise.reject(new Error(file)) : Promise.resolve({
  699. type: 3,
  700. res: file
  701. })
  702. } else if (type.match('image')) {
  703. let image = await new ImageMin({
  704. file: file,
  705. maxSize: 1024 * 1024
  706. })
  707. return {
  708. type: 1,
  709. preview: image.base64,
  710. res: image.res
  711. }
  712. }
  713. },
  714. /**
  715. * @des 处理文件发送
  716. */
  717. async handleFile (e) {
  718. let inputfile
  719. if (e.constructor === File) {
  720. inputfile = e
  721. } else {
  722. inputfile = e.target.files[0]
  723. }
  724. try {
  725. let fileInfo = await this.preHandleFile(inputfile)
  726. let opt = { res: fileInfo.res }
  727. if (this.group.members[this.userId]) {
  728. let createTime = Date.now()
  729. this.addChatItem({
  730. content: fileInfo.preview || '',
  731. from: this.userId,
  732. hash: `${createTime}`,
  733. msg_type: fileInfo.type,
  734. timestamp: createTime,
  735. res: fileInfo.res,
  736. loading: true,
  737. createTime
  738. })
  739. opt.createTime = createTime
  740. }
  741. this.doSendFile(opt)
  742. this.toolShow = false
  743. setTimeout(() => {
  744. this.$refs.toolbar.resetInput()
  745. this.resizeToBottom()
  746. }, 100)
  747. } catch (error) {
  748. Message({
  749. message: this.$t('chat.maxUploadTips'),
  750. type: 'warning'
  751. })
  752. }
  753. },
  754. /**
  755. * @des 控制聊天窗口开关
  756. */
  757. handleToggleChat (flag) {
  758. if (flag) {
  759. this.showChat = true
  760. this.unreadCounts = 0
  761. this.$nextTick(() => {
  762. this.postResize(this.width, this.height + 16)
  763. this.resizeToBottom()
  764. })
  765. } else {
  766. this.showChat = false
  767. this.$nextTick(() => {
  768. this.postResize(56, 50)
  769. })
  770. }
  771. },
  772. postResize (width, height) {
  773. let request = {
  774. action: 'meechat:resize',
  775. data: {
  776. ch: height,
  777. cw: width
  778. }
  779. }
  780. return window.parent.postMessage(request, '*')
  781. },
  782. /**
  783. * @des 引用某条消息
  784. */
  785. quoteMsg (msg) {
  786. this.inputMsg = msg
  787. },
  788. /**
  789. * @des 某条消息被删除
  790. */
  791. deleteMsg (hash) {
  792. this.deleteChatItem(hash)
  793. },
  794. pinMsgClose () {
  795. this.pinMsg.visible = false
  796. },
  797. scrollToView () {
  798. if (!this.pinMsg) return
  799. let hash = this.pinMsg.hash
  800. let index = this.group.chatList.findIndex(item => item.hash === hash)
  801. if (index < 0) {
  802. this.addPinChatItem(this.pinMsg)
  803. index = 0
  804. }
  805. let node = this.$refs.msgWrap.getElementsByClassName('msg-item')[index]
  806. let toOffsetTop = index >= 0 ? node.offsetTop - (this.pinMsg ? 40 : 10) : node.offsetTop
  807. scrollMsgIntoView(
  808. this.$refs.scrollWrap, toOffsetTop, node
  809. )
  810. // 防止加载更多
  811. this.isScrollToView = true
  812. setTimeout(() => {
  813. this.isScrollToView = false
  814. }, 2000)
  815. },
  816. scrollToMsg (index) {
  817. let hash = this.atList[index].hash
  818. let eleIndex = this.group.chatList.findIndex(item => item.hash === hash)
  819. if (eleIndex >= 0) {
  820. let node = this.$refs.msgWrap.querySelectorAll('.msg-item').item(eleIndex)
  821. scrollMsgIntoView(this.$refs.scrollWrap, node.offsetTop - (this.pinMsg ? 40 : 10), node)
  822. }
  823. this.removeAtListLast()
  824. }
  825. }
  826. }
  827. </script>
  828. <style lang="scss" scoped>
  829. @import "./chatMini.scss";
  830. </style>