chatMini.vue 25 KB

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