inputArea.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <template>
  2. <div class="box-ft">
  3. <transition name="msgbox-fade">
  4. <Emoji v-show="emojiShow" @addEmoji="addEmoji"></Emoji>
  5. </transition>
  6. <chat-at
  7. ref="chatAt"
  8. v-if="atShow"
  9. @atperson="atPerson"
  10. :curInd="atInd"
  11. :filterList="filterMembers"
  12. ></chat-at>
  13. <div class="toolbar">
  14. <i class="look-icon" @click="emojiShow = !emojiShow"></i>
  15. <div class="file-icon">
  16. <input type="file" ref="inputFile" name="res" @change="handleFile">
  17. </div>
  18. <!-- <i class="icon-packet" @click="$packetGet"></i> -->
  19. </div>
  20. <div class="send-content">
  21. <form class="input-wrap" @submit="handleSend">
  22. <textarea
  23. @keydown.up.prevent="handleUp"
  24. @keydown.down.prevent="handleDown"
  25. cols="1"
  26. ref="chatInput"
  27. rows="1"
  28. @keydown.enter="handleKeyDown"
  29. placeholder
  30. v-focus
  31. v-model="inputMsg"
  32. @blur="handleBlur"
  33. />
  34. </form>
  35. </div>
  36. <div class="send-action">Enter发送,Ctrl + Enter 换行
  37. <el-button @click="handleSend">发送</el-button>
  38. </div>
  39. </div>
  40. </template>
  41. <script>
  42. import Emoji from '@/components/emoji'
  43. import chatAt from '@/components/chatAt'
  44. import { Message } from 'element-ui'
  45. import { mapState, mapMutations, mapActions } from 'vuex'
  46. import { chatAtMixin } from '@/mixins'
  47. import ImageMin from '@/util/imageMin.js'
  48. // import editArea from './editArea'
  49. export default {
  50. name: 'inputArea',
  51. mixins: [chatAtMixin],
  52. components: {
  53. Emoji,
  54. chatAt
  55. },
  56. computed: {
  57. ...mapState(['group', 'userId']),
  58. ...mapState({
  59. chatInputFocus: state => state.group.chatInputFocus,
  60. blockList: state => state.group.blockList
  61. }),
  62. filterMembers () {
  63. let val = this.inputMsg.replace('@', '')
  64. let members = this.group.members
  65. let resArr = []
  66. for (let k in this.group.members) {
  67. if (members[k].nick_name.indexOf(val) > -1) {
  68. resArr.push(members[k])
  69. }
  70. }
  71. return resArr
  72. },
  73. atShow () {
  74. return this.inputMsg.match(/^@/) && this.filterMembers.length
  75. }
  76. },
  77. data () {
  78. return {
  79. emojiShow: false,
  80. inputMsg: '',
  81. atInd: 0
  82. }
  83. },
  84. mounted () {
  85. document.addEventListener('paste', this.initPaste)
  86. document.addEventListener('drop', this.initDrop)
  87. document.addEventListener('dragover', this.initDragOver)
  88. },
  89. beforeDestroy () {
  90. document.removeEventListener('paste', this.initPaste)
  91. document.removeEventListener('drop', this.initDrop)
  92. document.removeEventListener('dragover', this.initDragOver)
  93. },
  94. methods: {
  95. ...mapMutations(['updateChatInputFocus', 'addChatItem']),
  96. ...mapActions(['doSendMsg', 'doSendFile']),
  97. addEmoji (val) {
  98. this.inputMsg += val
  99. this.emojiShow = false
  100. this.$refs.chatInput.focus()
  101. },
  102. handleKeyDown (event) {
  103. if (this.atShow) {
  104. event.preventDefault()
  105. let item = this.filterMembers[this.atInd]
  106. this.atPerson(item.nick_name)
  107. return
  108. }
  109. if (event.altKey || event.ctrlKey || event.metaKey) {
  110. // 单纯换行
  111. this.inputMsg = this.inputMsg + '\n'
  112. } else {
  113. event.returnValue = false
  114. this.handleSend(event)
  115. }
  116. return false
  117. },
  118. /**
  119. * @des 处理消息发送
  120. */
  121. handleSend (e) {
  122. // 判断是否被禁言
  123. if (this.blockList.some(id => id == this.userId)) {
  124. Message({
  125. message: '您已被禁言',
  126. type: 'error'
  127. })
  128. return
  129. }
  130. let text = this.inputMsg.trim()
  131. if (text.length === 0) {
  132. Message({
  133. message: '聊天内容不能为空',
  134. type: 'warning'
  135. })
  136. return
  137. }
  138. let opt = {
  139. type: 0,
  140. msg: text
  141. }
  142. // 用户不是第一次发言
  143. if (this.group.members[this.userId]) {
  144. let createTime = Date.now()
  145. this.addChatItem({
  146. from: this.userId,
  147. content: text,
  148. hash: `${createTime}`,
  149. timestamp: createTime,
  150. createTime,
  151. msg_type: '0',
  152. loading: true
  153. })
  154. opt.createTime = createTime
  155. }
  156. this.doSendMsg(opt)
  157. // 滚到底部
  158. this.$nextTick(function () {
  159. this.inputMsg = ''
  160. this.$emit('toBottom')
  161. })
  162. e.preventDefault()
  163. return false
  164. },
  165. /**
  166. * 文件预处理
  167. * @return {Object} data 预处理文件信息
  168. * @param {Number} data.type
  169. * @param {File} data.res
  170. */
  171. async preHandleFile (file) {
  172. let type = file.type
  173. let size = file.size
  174. if (type.match('video')) {
  175. return size > 3 * 1024 * 1024
  176. ? Promise.reject(new Error(file))
  177. : Promise.resolve({
  178. type: 2,
  179. res: file
  180. })
  181. } else if (type.match('audio')) {
  182. return size > 2 * 1024 * 1024
  183. ? Promise.reject(new Error(file))
  184. : Promise.resolve({
  185. type: 3,
  186. res: file
  187. })
  188. } else if (type.match('image')) {
  189. let image = await new ImageMin({
  190. file: file,
  191. maxSize: 1024 * 1024
  192. })
  193. return {
  194. type: 1,
  195. preview: image.base64,
  196. res: image.res
  197. }
  198. }
  199. },
  200. /**
  201. * @des 处理文件发送
  202. */
  203. async handleFile (e) {
  204. let inputfile
  205. if (e.constructor === File) {
  206. inputfile = e
  207. } else {
  208. inputfile = e.target.files[0]
  209. }
  210. try {
  211. let fileInfo = await this.preHandleFile(inputfile)
  212. let opt = { res: fileInfo.res }
  213. if (this.group.members[this.userId]) {
  214. let createTime = Date.now()
  215. this.addChatItem({
  216. content: fileInfo.preview || '',
  217. from: this.userId,
  218. hash: `${createTime}`,
  219. msg_type: fileInfo.type,
  220. timestamp: createTime,
  221. res: fileInfo.res,
  222. loading: true,
  223. createTime
  224. })
  225. opt.createTime = createTime
  226. }
  227. this.doSendFile(opt)
  228. setTimeout(() => {
  229. this.$refs.inputFile.value = null
  230. this.$emit('toBottom')
  231. }, 100)
  232. } catch (error) {
  233. Message({
  234. message: '上传文件大小限制:音频2M以内,视频3M以内',
  235. type: 'warning'
  236. })
  237. }
  238. },
  239. handleBlur () {
  240. this.updateChatInputFocus(false)
  241. },
  242. initDrop (e) {
  243. e.preventDefault()
  244. let files = Array.from(e.dataTransfer.files)
  245. files.forEach(file => this.handleFile(file))
  246. },
  247. initDragOver (e) {
  248. e.preventDefault()
  249. },
  250. initPaste (event) {
  251. var items = (event.clipboardData || window.clipboardData).items
  252. if (items && items.length) {
  253. Array.from(items).forEach(item => {
  254. let file = item.getAsFile()
  255. if (file) {
  256. this.handleFile(file)
  257. }
  258. })
  259. }
  260. }
  261. }
  262. }
  263. </script>
  264. <style lang="scss" scoped>
  265. .box-ft {
  266. height: 180px;
  267. border-top: 1px solid #d6d6d6;
  268. padding-right: 16px;
  269. position: relative;
  270. }
  271. .toolbar {
  272. padding: 10px 20px;
  273. font-size: 0;
  274. i {
  275. color: #333333;
  276. font-size: 18px;
  277. margin-right: 15px;
  278. vertical-align: middle;
  279. cursor: pointer;
  280. }
  281. }
  282. .send-action {
  283. text-align: right;
  284. }
  285. .look-icon {
  286. background: url("../../assets/icon-face.png") no-repeat;
  287. background-size: 100%;
  288. width: 20px;
  289. height: 20px;
  290. display: inline-block;
  291. vertical-align: middle;
  292. cursor: pointer;
  293. }
  294. .file-icon {
  295. background: url("../../assets/icon-file.png") no-repeat;
  296. background-size: 100%;
  297. width: 19px;
  298. height: 18px;
  299. display: inline-block;
  300. vertical-align: middle;
  301. position: relative;
  302. input[type="file"] {
  303. cursor: pointer;
  304. opacity: 0;
  305. position: absolute;
  306. top: 0;
  307. left: 0;
  308. z-index: 1;
  309. width: 100%;
  310. height: 100%;
  311. }
  312. }
  313. .emoji-list {
  314. position: absolute;
  315. border-radius: 4px;
  316. border: 1px solid #d6d6d6;
  317. left: 10px;
  318. height: 226px;
  319. top: -236px;
  320. overflow: auto;
  321. background-color: #ffffff;
  322. width: 232px;
  323. }
  324. .send-action {
  325. color: #bababa;
  326. font-size: 12px;
  327. button {
  328. margin-left: 10px;
  329. }
  330. }
  331. .input-wrap {
  332. textarea {
  333. display: block;
  334. background-color: #eee;
  335. width: 100%;
  336. box-sizing: border-box;
  337. resize: none;
  338. height: 90px;
  339. line-height: 1.4;
  340. padding-left: 20px;
  341. outline: none;
  342. border: 0;
  343. font-size: 14px;
  344. margin-bottom: 10px;
  345. }
  346. }
  347. .icon-packet{
  348. display: inline-block;
  349. margin-left: 20px;
  350. vertical-align: middle;
  351. background: url('../../assets/icon-packet.png') center center no-repeat;
  352. background-size: 100%;
  353. width: 21px;
  354. height: 21px;
  355. }
  356. </style>