<template> <div class="flex flex-col session" style="height: 100vh"> <div class="py-2 px-3 text-black text-center shrink-0 head"> <span @click="onBack" class="text-12 back-bt"> <doc-icon type="doc-left2" /> </span> <span>{{ targetId }}</span> </div> <div class="p-3 grow overflow-y-auto content"> <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多消息了" direction="up" :disabled="scrollDisabled" @load="loadHistory" > <div v-for="item in msgs" :key="item.time" :class="['flex msg-row', item.from === accountId ? 'self' : 'target']"> <div class="shrink-0 avatar"> <img src="@/assets/image/nim/avatar-doctor.svg" alt="" v-show="item.from !== accountId"> </div> <div class="msg-cont"> <span v-if="item.type === 'text'">{{ item.text }}</span> <FileView v-else-if="['image', 'file'].includes(item.type)" :file="item.file" @viewImage="viewImage"/> </div> <div class="shrink-0 avatar"> <img src="@/assets/image/nim/avatar-man.png" alt="" v-show="item.from === accountId"> </div> </div> </van-list> <div ref="bottom"></div> </div> <div class="shrink-0 px-3 py-2 footer"> <van-field type="textarea" rows="1" :autosize="{ minHeight: 24, maxHeight: 76 }" maxlength="500" v-model="inputValue" center > <template #left-icon> <div style="color: #555; font-size: .2rem" class="mr-1" @click="() => visible = true"> <doc-icon type="plus-circle"/> </div> </template> <template #button> <van-button size="small" type="primary" @click="sendMsg">发送</van-button> </template> </van-field> </div> <input type="file" :accept="sendType == 'image' ? '.jpg,.jpeg,.png,.gif' : '*'" ref="fileElem" style="display: none;" @change="handleFiles" :key="inputKey"> <van-action-sheet v-model:show="visible" :actions="actionsList" cancel-text="取消" close-on-click-action @select="fileSelect" /> </div> </template> <script> import NIM from '@yxim/nim-web-sdk/dist/SDK/NIM_Web_NIM.js' import { showNotify, showToast, showImagePreview, showSuccessToast, showFailToast } from 'vant' import FileView from './FileView.vue' export default { components: { FileView }, data() { return { msgs: [], nim: null, // 输入的信息 inputValue: '', // 是否已连接 isConnect: false, // 加载聊天记录 loading: false, // 是否加载完成 finished: false, scrollDisabled: true, // image file sendType: 'image', inputKey: 1, // 上传功能面板 visible: false, actionsList: [ { name: '上传图片', type: 'image' }, { name: '上传文件', type: 'file' } ] } }, computed: { accountId() { return this.$route.query.accountId }, // 聊天对象 targetId() { return '2000050903' return this.accountId === '18487350810' ? '18987175004' : '18487350810' } }, created() { this.init() }, methods: { init() { this.isConnect = false this.finished = false this.nim = NIM.getInstance({ debug: true, appKey: '6c51376a55f54b2fa586d7b4c85757f8', account: this.accountId, token: '123456', onconnect: () => { console.log('连接成功 ================>') this.isConnect = true this.getLocalMsgs() this.toBottom('instant') setTimeout(() => { // 开启滚动加载 this.scrollDisabled = false }, 1000); }, onwillreconnect: (obj) => { console.log('即将重连') console.log(obj.retryCount) console.log(obj.duration) }, ondisconnect: (error) => { showNotify({ type: 'warning', message: '连接失败', duration: 0 }) }, onmsg: (msg) => { console.log('收到新消息===========>', msg); this.msgs.push(msg) this.toBottom() } }) }, // 发送信息 sendMsg() { if (!this.inputValue) { showToast('不能发送空消息') return } let msg = this.nim.sendText({ scene: 'p2p', to: this.targetId, text: this.inputValue, done: function sendMsgDone (error, msg) { console.log('sendText ================>', error, msg) } }) this.pushMsg(msg) this.inputValue = '' this.toBottom() }, pushMsg(msg) { this.msgs.push(msg) this.toBottom() }, toBottom(behavior = 'smooth') { const dom = this.$refs.bottom setTimeout(() => { dom && dom.scrollIntoView({block: 'start', behavior}) }, 300) }, // 加载本地消息 getLocalMsgs() { const msg = this.msgs[0] || {} this.loading = true this.nim.getLocalMsgs({ sessionId: `p2p-${this.targetId}`, limit: 40, end: msg.time, done: (error, obj) => { // console.log('获取本地消息' + (!error?'成功':'失败'), error, obj) const msgs = obj.msgs || [] this.loading = false if (!msgs.length) { this.finished = true return } msgs.sort((a, b) => { return a.time - b.time }) this.msgs = msgs.concat(this.msgs) } }) }, loadHistory() { this.getLocalMsgs() }, handleFiles() { let files = this.$refs['fileElem'].files if (files.length <= 0) { this.$message.info('未选中文件,请尝试重新选择') return } if (files[0].size / 1024 / 1024 > 100) { this.$message.info('文件大小不能超过100M') return } this.upload(files[0]) }, upload(file, hash) { // 先上传再发送 const tempMsg = { from: this.accountId, text: '上传中...', time: +new Date(), percentage: 0, type: 'text' } this.pushMsg(tempMsg) this.nim.previewFile({ type: this.sendType, blob: file, // fastPass: { md5: hash }, uploadprogress: function(obj) { console.log('文件总大小: ' + obj.total + 'bytes'); console.log('已经上传的大小: ' + obj.loaded + 'bytes'); console.log('上传进度: ' + obj.percentage); console.log('上传进度文本: ' + obj.percentageText); tempMsg.percentage = obj.percentage }, done: (error, file) => { this.inputKey = Math.random().toString(16).substring(2, 6) if (!error) { const msg = this.nim.sendFile({ scene: 'p2p', to: this.targetId, file: file }) console.log('正在发送p2p image消息, id=' + msg.idClient) this.pushMsg(msg) this.msgs = this.msgs.filter(e => e.time !== tempMsg.time) } else { const msg = this.nim.sendText({ scene: 'p2p', to: this.targetId, text: '文件上传失败!', isLocal: true, custom: { type: 'file', status: 'fail' } }) this.pushMsg(msg) } } }) }, fileSelect(action) { this.sendType = action.type const dom = this.$refs['fileElem'] setTimeout(() => { dom && dom.click() }, 100) }, // 图片查看 viewImage(file) { const currt = file.url const tempList = this.msgs.filter(e => ['file', 'image'].includes(e.type)).map(e => e.file) const imageList = [] tempList.forEach(e => { const array = ['JPG', 'PNG', 'JEPG', 'GIF'] if (array.includes(e.type) || array.includes(e.ext.toLocaleUpperCase())) { imageList.push(e.url) } }) console.log('imageList', imageList) const index = imageList.indexOf(currt) || 0 showImagePreview({ images: imageList, startPosition: index, closeable: true }) }, clearMsg() { this.nim.deleteLocalMsgsBySession({ scene: 'p2p', to: this.targetId, delLastMsg: true, done: (error) => { if (error) { showFailToast('删除失败') } else { showSuccessToast('删除成功') } } }); }, clearAllMsg() { this.nim.deleteAllLocalMsgs({ done: (error) => { if (error) { showFailToast('删除失败') } else { showSuccessToast('删除成功') } } }) }, onBack() { } }, beforeUnmount() { if (this.nim) { this.nim.destroy({ done: function (err) { console.log('nim 断开连接') } }) } } } </script> <style lang="less" scoped> .head { position: relative; border-bottom: 1px solid #3C3C435C; .back-bt { position: absolute; left: .16rem; top: 50%; transform: translateY(-50%); } } .footer { border-top: 1px solid #3C3C431C; background-color: #eeeeee66; .van-cell { padding: 0; background: transparent; } } .content { background-color: #f5f5f5; .msg-row { align-items: flex-start; margin-bottom: 12px; } .msg-cont { display: inline-block; line-height: 1.5; padding: 8px; border-radius: 4px; position: relative; word-break: break-all; &::before { content: ''; display: inline-block; width: 4px; height: 4px; transform: rotate(45deg); position: absolute; right: -2px; top: 16px; } } .avatar { width: 38px; >img { width: 100%; height: 100%; object-fit: cover; } } .self { justify-content: flex-end; .msg-cont { margin-right: 12px; background-color: #91d5ff; &::before { right: -2px; background-color: #91d5ff; } } } .target { justify-content: flex-start; .msg-cont { margin-left: 12px; background-color: #fff; &::before { left: -2px; background-color: #fff; } } } } </style>