roomTool.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. <template>
  2. <div class="box-ft">
  3. <chat-at
  4. ref="chatAt"
  5. v-if="atShow"
  6. @atperson="atPerson"
  7. :curInd="atInd"
  8. :filterList="filterMembers">
  9. </chat-at>
  10. <div class="input-ctrl" v-if="showLoginBtn">
  11. <span v-if="showLoginBtn === 'loading'">{{ $t('mini.logining') }}</span>
  12. <span v-else class="enable" @click="handleLogin">{{ $t('mini.login') }}</span>
  13. </div>
  14. <div class="input-con" v-else>
  15. <div class="more-icon" @click.stop="handleMoreClick"></div>
  16. <form class="input-wrap" @submit="handleSend">
  17. <textarea
  18. @keydown.up="handleUp"
  19. @keydown.down="handleDown"
  20. @keydown.left="handleLeft"
  21. @keydown.right="handleRight"
  22. @keydown.delete="handleDel"
  23. @keydown.esc="handleEsc"
  24. cols="1"
  25. ref="chatInput"
  26. rows="1"
  27. @keydown.enter="handleKeyDown"
  28. placeholder="Write a message"
  29. v-model="inputMsg"
  30. @focus="handleFocus"
  31. @blur="handleBlur"
  32. :style="{height:inputHeight}"
  33. />
  34. </form>
  35. <div class="emoji-icon" @click.stop="handleEmojiClick"></div>
  36. <div class="btn-send" @click="handleSend">{{ $t('chat.send') }}</div>
  37. </div>
  38. <toolbar ref="toolbar" :toolShow="toolShow" @handleFile="handleFile"></toolbar>
  39. <emoji @addEmoji="addEmoji" :emojiShow="showEmoji" v-show="showEmoji" @closeEmojiList="closeEmojiList"></emoji>
  40. </div>
  41. </template>
  42. <script>
  43. import emoji from '@/components/emoji'
  44. import chatAt from '@/components/chatAt'
  45. import toolbar from '@/components/chatInput/toolbar'
  46. import { mapActions, mapState, mapMutations } from 'vuex'
  47. import { Message } from 'element-ui'
  48. import ImageMin from '@/util/imageMin.js'
  49. import { chatAtMixin, chatInputMixin, changeLangMixin } from '@/mixins'
  50. import { accountLoginMixin } from '@/mixins/login'
  51. import { emojiList } from '@/util/emoji'
  52. export default {
  53. name: 'chatMini',
  54. mixins: [accountLoginMixin, chatAtMixin, chatInputMixin, changeLangMixin],
  55. components: {
  56. emoji,
  57. chatAt,
  58. toolbar
  59. },
  60. props: {
  61. groupId: [Number, String],
  62. showLoginBtn: [Boolean, String] // 显示登录按钮
  63. },
  64. computed: {
  65. ...mapState([
  66. 'account',
  67. 'group',
  68. 'userId',
  69. 'userInfo'
  70. ]),
  71. ...mapState({
  72. chatInputFocus: state => state.group.chatInputFocus,
  73. blockList: state => state.group.blockList,
  74. pinMsg: state => state.group.pinMsg,
  75. atList: state => state.group.atList,
  76. chatList: state => state.group.chatList,
  77. unreadNums: state => state.group.unreadNums,
  78. sessionList: state => state.chat.sessionList
  79. }),
  80. emojiMap () {
  81. var emojiMap = {}
  82. for (let i in emojiList) {
  83. let arr = emojiList[i]
  84. arr.forEach(v => {
  85. let names = JSON.stringify(v.names)
  86. let emoji = v.surrogates
  87. emojiMap[names] = emoji
  88. })
  89. }
  90. return emojiMap
  91. },
  92. linkToCreator () {
  93. let { creator, userId } = this.group
  94. let sessionId = creator > userId ? `${userId}-${creator}` : `${creator}-${userId}`
  95. return `${location.origin}/#/pm/${sessionId}`
  96. },
  97. isAdmin () {
  98. return (this.group.adminList && this.group.adminList.some(id => id == this.userId)) || this.group.creator == this.userId
  99. }
  100. },
  101. data () {
  102. return {
  103. showEmoji: false, // 显示表情选择框
  104. loading: false,
  105. inputMsg: '', // 用户输入的内容
  106. atInd: 0, // @人索引
  107. inputHeight: 18,
  108. toolShow: false
  109. }
  110. },
  111. watch: {
  112. inputMsg (val, newval) {
  113. let ele = this.$refs.chatInput
  114. this.inputHeight = 'auto'
  115. this.$nextTick(() => {
  116. this.inputHeight = Math.max(18, Math.min(ele.scrollHeight, 75)) + 'px'
  117. })
  118. },
  119. chatInputFocus (val, newval) {
  120. if (this.showLoginBtn) return
  121. let ele = this.$refs.chatInput
  122. if (val) {
  123. if (document.activeElement !== ele) {
  124. this.placeEnd(ele)
  125. ele.focus()
  126. }
  127. } else {
  128. if (document.activeElement === ele) {
  129. ele.blur()
  130. }
  131. }
  132. }
  133. },
  134. async mounted () {
  135. document.body.addEventListener('click', () => {
  136. this.showEmoji = false
  137. this.toolShow = false
  138. })
  139. },
  140. beforeDestroy () {
  141. },
  142. methods: {
  143. ...mapMutations([
  144. 'addChatItem',
  145. 'updateChatInputFocus'
  146. ]),
  147. ...mapActions([
  148. 'doSendMsg',
  149. 'doSendFile'
  150. ]),
  151. handleLogin () {
  152. this.$emit('handleLogin')
  153. },
  154. handleMoreClick () {
  155. this.showEmoji = false
  156. this.toolShow = !this.toolShow
  157. this.checkNeedToBottom()
  158. },
  159. handleEmojiClick () {
  160. this.toolShow = false
  161. this.showEmoji = !this.showEmoji
  162. this.checkNeedToBottom()
  163. },
  164. checkNeedToBottom () {
  165. if (!this.isBottom) return
  166. this.$nextTick(() => {
  167. this.resizeToBottom()
  168. })
  169. },
  170. /**
  171. * 聊天窗体滚动到底部
  172. */
  173. resizeToBottom () {
  174. this.$emit('resizeToBottom')
  175. },
  176. /**
  177. * @des 点击,查看未读消息
  178. * 直接滚动到聊天列表底部
  179. */
  180. doSetRead () {
  181. this.resizeToBottom()
  182. },
  183. /**
  184. * 添加表情
  185. */
  186. addEmoji (value) {
  187. this.inputMsg += value
  188. },
  189. /**
  190. * @des 处理消息发送
  191. */
  192. async handleSend (e) {
  193. // 判断是否被禁言
  194. if (this.blockList.some(id => id == this.userId)) {
  195. Message({
  196. message: this.$t('chat.youAreBan'),
  197. type: 'error'
  198. })
  199. return
  200. }
  201. // 替换emoji字符串
  202. let _inputMsg = this.inputMsg
  203. let parts = _inputMsg.match(/\["[a-z0-9A-Z_]+"\]/g)
  204. for (let k in parts) {
  205. let emoji = this.emojiMap[parts[k]]
  206. if (emoji) {
  207. _inputMsg = _inputMsg.replace(parts[k], emoji)
  208. }
  209. }
  210. let text = _inputMsg.trim()
  211. if (text.length === 0) {
  212. Message({
  213. message: this.$t('chat.cannotBeEmpty'),
  214. type: 'warning'
  215. })
  216. return
  217. }
  218. let opt = {
  219. type: 0,
  220. msg: text
  221. }
  222. // 清空输入框
  223. this.inputMsg = ''
  224. // 用户不是第一次发言
  225. if (this.group.members[this.userId]) {
  226. let createTime = Date.now()
  227. this.addChatItem({
  228. from: this.userId,
  229. content: text,
  230. hash: `${createTime}`,
  231. timestamp: createTime,
  232. createTime,
  233. msg_type: '0',
  234. loading: true
  235. })
  236. opt.createTime = createTime
  237. await this.doSendMsg(opt)
  238. } else {
  239. // 发言后,才滚动底部
  240. await this.doSendMsg(opt)
  241. }
  242. // 滚到底部
  243. this.$nextTick(function () {
  244. this.resizeToBottom()
  245. })
  246. e.preventDefault()
  247. return false
  248. },
  249. placeEnd (el) {
  250. var range = document.createRange()
  251. range.selectNodeContents(el)
  252. range.collapse(false)
  253. var sel = window.getSelection()
  254. sel.removeAllRanges()
  255. sel.addRange(range)
  256. },
  257. /**
  258. * 文件预处理
  259. * @return {Object} data 预处理文件信息
  260. * @param {Number} data.type
  261. * @param {File} data.res
  262. */
  263. async preHandleFile (file) {
  264. let type = file.type
  265. let size = file.size
  266. if (type.match('video')) {
  267. return size > 3 * 1024 * 1024 ? Promise.reject(new Error(file)) : Promise.resolve({
  268. type: 2,
  269. res: file
  270. })
  271. } else if (type.match('audio')) {
  272. return size > 2 * 1024 * 1024 ? Promise.reject(new Error(file)) : Promise.resolve({
  273. type: 3,
  274. res: file
  275. })
  276. } else if (type.match('image')) {
  277. let image = await new ImageMin({
  278. file: file,
  279. maxSize: 1024 * 1024
  280. })
  281. return {
  282. type: 1,
  283. preview: image.base64,
  284. res: image.res
  285. }
  286. }
  287. },
  288. /**
  289. * @des 处理文件发送
  290. */
  291. async handleFile (e) {
  292. let inputfile
  293. if (e.constructor === File) {
  294. inputfile = e
  295. } else {
  296. inputfile = e.target.files[0]
  297. }
  298. try {
  299. let fileInfo = await this.preHandleFile(inputfile)
  300. let opt = { res: fileInfo.res }
  301. if (this.group.members[this.userId]) {
  302. let createTime = Date.now()
  303. this.addChatItem({
  304. content: fileInfo.preview || '',
  305. from: this.userId,
  306. hash: `${createTime}`,
  307. msg_type: fileInfo.type,
  308. timestamp: createTime,
  309. res: fileInfo.res,
  310. loading: true,
  311. createTime
  312. })
  313. opt.createTime = createTime
  314. }
  315. this.doSendFile(opt)
  316. this.toolShow = false
  317. setTimeout(() => {
  318. this.$refs.toolbar.resetInput()
  319. this.resizeToBottom()
  320. }, 100)
  321. } catch (error) {
  322. Message({
  323. message: this.$t('chat.maxUploadTips'),
  324. type: 'warning'
  325. })
  326. }
  327. }
  328. }
  329. }
  330. </script>
  331. <style lang="scss" scoped>
  332. @charset "UTF-8";
  333. $chatContBg: #eeeeee;
  334. .box-ft{
  335. position: relative;
  336. background: $chatContBg;
  337. border-top: 1px solid #d6d6d6;
  338. .input-con{
  339. position: relative;
  340. display: flex;
  341. align-items: flex-end;
  342. background-color: #fafafa;
  343. padding: 6px 0;
  344. }
  345. .input-wrap{
  346. flex: 1;
  347. padding: 10px 0;
  348. background-color: #ffffff;
  349. border-radius: 4px;
  350. }
  351. .more-icon{
  352. width: 38px;
  353. height: 38px;
  354. background: url('../../assets/more-icon.png') center center no-repeat;
  355. background-size: 22px 22px;
  356. cursor: pointer;
  357. &:hover{
  358. opacity: .6;
  359. }
  360. }
  361. .emoji-icon{
  362. width: 38px;
  363. height: 38px;
  364. background: url('../../assets/m-face-icon.png') center center no-repeat;
  365. background-size: 22px 22px;
  366. cursor: pointer;
  367. &:hover{
  368. opacity: .6;
  369. }
  370. }
  371. .btn-send{
  372. margin-right: 4px;
  373. font-size: 12px;
  374. color: #ffffff;
  375. padding: 0 10px;
  376. height: 28px;
  377. line-height: 28px;
  378. margin-bottom: 3px;
  379. background: #2b9ff6;
  380. border-radius: 3px;
  381. cursor: pointer;
  382. &:hover{
  383. opacity: 0.8;
  384. }
  385. }
  386. form{
  387. @include flex(1);
  388. }
  389. textarea {
  390. display: block;
  391. width: 100%;
  392. height: 18px;
  393. max-height: 175px;
  394. font-size: 14px;
  395. color: #000000;
  396. line-height: 16px;
  397. padding: 1px;
  398. padding-left: 8px;
  399. margin: 0;
  400. border: none;
  401. outline: none;
  402. background: none;
  403. box-sizing: border-box;
  404. resize: none;
  405. }
  406. .input-ctrl{
  407. span{
  408. width: 120px;
  409. margin: 4px auto;
  410. height: 30px;
  411. line-height: 30px;
  412. color: #ffffff;
  413. font-size: 12px;
  414. text-align: center;
  415. display: block;
  416. background-image: -webkit-linear-gradient( 90deg, rgb(25,145,235) 0%, rgb(46,161,248) 100%);
  417. border-radius: 3px;
  418. &.enable{
  419. cursor: pointer;
  420. &:hover{
  421. opacity: 0.7;
  422. }
  423. }
  424. }
  425. }
  426. }
  427. </style>