123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- <template>
- <view class="im-v">
- <view @click="hideDrawer">
- <mescroll-body ref="mescrollRef" bottom="50%" @init="mescrollInit" :down="downOption" @down="downCallback"
- :up="upOption">
- <!-- 无更多消息 -->
- <view v-if="isEnd && !isMsgList" class="msg-end">没有更多消息了</view>
- <view v-if="isMsgList" class="notData-box u-flex-col">
- <view class="u-flex-col notData-inner">
- <image :src="icon" class="iconImg"></image>
- <text class="notData-inner-text">暂无聊天记录</text>
- </view>
- </view>
- <view class="msg-list">
- <!-- 消息列表 (必须配置id,以便定位) -->
- <view class="msg-list-item" v-for="(msg,index) in msgList" :key="index" :id="'msg'+msg.id"
- :class="userId === msg.sendUserId ? 'msg-list-item-r' : 'msg-list-item-l'">
- <view class="avatar" v-if="userId === msg.sendUserId">
- <u-avatar :src="baseURL+userInfoHeadIcon" size="80"></u-avatar>
- </view>
- <view class="avatar" v-else>
- <u-avatar :src="baseURL+headIcon" size="80"></u-avatar>
- </view>
- <!-- 文字,表情 -->
- <view class="msg-text" v-if="msg.contentType==='text'">
- <view v-for="(item,i) in msg.msgContent" :key="i">
- <text class="msg-text-txt" v-if="item.type=='text'">{{item.content}}</text>
- <image class="msg-text-emoji" :src="item.content" v-if="item.type=='emjio'" />
- </view>
- </view>
- <!-- 图片消息 -->
- <view v-if="msg.contentType=='image'" class="msg-img" @click="showPic(msg.msgContent.path)">
- <image lazy-load="true" :src="msg.msgContent.path"
- :style="{'width': msg.msgContent.width+'px','height': msg.msgContent.height+'px'}">
- </image>
- </view>
- <!-- 语言消息 -->
- <view v-if="msg.contentType==='voice'" class="msg-text msg-voice" @click="playVoice(msg)"
- :class="playMsgid == msg.id?'play':''">
- <view class="length">{{msg.msgContent.length}}</view>
- <view class="icon my-voice"></view>
- </view>
- </view>
- </view>
- </mescroll-body>
- </view>
- <!-- 抽屉栏 -->
- <view class="input-box" :class="popupLayerClass" @touchmove.stop.prevent="discard">
- <view class="input-box-icon icon biaoqing" @click="chooseEmoji"></view>
- <!-- #ifndef H5 || APP-HARMONY -->
- <view class="input-box-icon icon" :class="isVoice?'jianpan':'yuyin'" @click="switchVoice"></view>
- <!-- #endif -->
- <view class="voice-mode" :class="[isVoice?'':'hidden',recording?'recording':'']" @touchstart="voiceBegin"
- @touchmove.stop.prevent="voiceIng" @touchend="voiceEnd" @touchcancel="voiceCancel">{{voiceTis}}</view>
- <view class="text-mode" v-if="!isVoice">
- <view class="input-area">
- <textarea auto-height :cursor-spacing="8" maxlength="500" v-model="textMsg" @focus="textareaFocus"
- :focus="textFocus" />
- </view>
- </view>
- <view class="input-box-icon icon add" @click="openMore"></view>
- <view class="send-btn" @click="sendText" v-if="!isVoice">发送</view>
- </view>
- <view class="popup-layer u-border-top" :class="popupLayerClass" @touchmove.stop.prevent="discard">
- <swiper class="emoji-swiper" indicator-dots="true" duration="150" v-show="showEmoji">
- <swiper-item v-for="(page,pid) in emojiTree" :key="pid">
- <view v-for="(em,eid) in page" :key="eid" @click="addEmoji(em)" class="emoji-item">
- <image mode="widthFix" :src="getEmojiUrl(em.url)" class="emoji-item-img"></image>
- </view>
- </swiper-item>
- </swiper>
- <view class="more-layer" v-show="showMore">
- <view class="list">
- <view class="box" @click="chooseImage('album')">
- <text class="icon tupian2"></text>
- </view>
- <!-- #ifndef H5 -->
- <view class="box" @click="chooseImage('camera')">
- <text class="icon paizhao"></text>
- </view>
- <!-- #endif -->
- </view>
- </view>
- </view>
- <!-- 录音UI效果 -->
- <view class="record" :class="recording?'':'hidden'">
- <view class="ing" :class="willStop?'hidden':''">
- <view class="icon luyin2"></view>
- </view>
- <view class="cancel" :class="willStop?'':'hidden'">
- <view class="icon chehui"></view>
- </view>
- <view class="tis" :class="willStop?'change':''">{{recordTis}}</view>
- </view>
- </view>
- </template>
- <script>
- import chat from '@/libs/chat.js'
- import {
- emojiList,
- emojiTree,
- imagesMap
- } from './emoji.js'
- import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
- import {
- mapGetters
- } from 'vuex'
- import resources from '@/libs/resources.js'
- import {
- useChatStore
- } from '@/store/modules/chat'
- const chatStore = useChatStore()
- //播放语音相关参数
- const AUDIO = uni.createInnerAudioContext()
- export default {
- mixins: [MescrollMixin],
- name: 'im',
- data() {
- return {
- icon: resources.message.nodata,
- formUserId: '',
- headIcon: '',
- name: '',
- downOption: {
- auto: true,
- },
- upOption: {
- use: false,
- toTop: {
- src: ''
- }
- },
- currentPage: 1,
- pageSize: 30,
- //录音相关参数
- // #ifndef H5
- //H5不能录音
- RECORDER: uni.getRecorderManager(),
- // #endif
- playMsgid: null,
- popupLayerClass: '',
- textFocus: false,
- showMore: false,
- showEmoji: false,
- emojiList,
- emojiTree,
- msgList: [],
- isEnd: false,
- isMsgList: true,
- isVoice: false,
- voiceTis: '按住 说话',
- recordTis: "手指上滑 取消发送",
- recording: false,
- willStop: false,
- initPoint: {
- identifier: 0,
- Y: 0
- },
- recordTimer: null,
- recordLength: 0,
- textMsg: '',
- msgImageList: [],
- userId: '',
- userInfoHeadIcon: ''
- }
- },
- computed: {
- baseURL() {
- return this.define.baseURL
- }
- },
- watch: {},
- onLoad(option) {
- let userInfo = uni.getStorageSync('userInfo')
- this.userId = userInfo.userId
- this.userInfoHeadIcon = userInfo.headIcon
- this.formUserId = option.formUserId;
- this.headIcon = option.headIcon;
- this.name = option.name;
- uni.$on('getMessageList', data => {
- this.getMessageList(data)
- })
- uni.$on('addMsg', data => {
- this.addMsg(data)
- })
- chatStore.setFormUserId(this.formUserId)
- const updateReadMessage = {
- method: "UpdateReadMessage",
- formUserId: this.formUserId,
- token: uni.getStorageSync('token')
- }
- chat.sendMsg(JSON.stringify(updateReadMessage))
- uni.setNavigationBarTitle({
- title: option.name
- });
- //语音自然播放结束
- // #ifndef MP-ALIPAY
- AUDIO.onEnded((res) => {
- this.playMsgid = null;
- });
- // #endif
- // #ifndef H5 || MP-ALIPAY
- //录音开始事件
- this.RECORDER.onStart((e) => {
- this.recordBegin(e);
- });
- //录音结束事件
- this.RECORDER.onStop((e) => {
- this.recordEnd(e);
- });
- // #endif
- },
- onUnload() {
- uni.$off('getMessageList')
- uni.$off('addMsg')
- chatStore.setFormUserId('')
- // #ifndef MP-ALIPAY
- AUDIO.stop();
- // #endif
- },
- methods: {
- getMessageList(data) {
- let msgImageList = []
- const list = data.list.map(o => {
- if (o.contentType === 'image') {
- if (o.content) {
- let content = {}
- if (typeof(o.content) === 'string') {
- content = JSON.parse(o.content)
- } else {
- content = o.content
- }
- msgImageList.push(this.baseURL + content.path)
- }
- }
- return this.dealMsg(o)
- })
- this.msgImageList = [...msgImageList, ...this.msgImageList]
- let topMsg = this.msgList[0]
- this.msgList = [...list, ...this.msgList]
- if (this.msgList.length) this.isMsgList = false
- if (data.list.length < data.pagination.pageSize) {
- this.mescroll.lockDownScroll(true)
- this.isEnd = true
- }
- this.$nextTick(() => {
- if (this.currentPage <= 2) {
- this.mescroll.scrollTo(99999, 0)
- } else if (topMsg) {
- let view = uni.createSelectorQuery().select('#msg' + topMsg.id);
- view.boundingClientRect(v => {
- this.mescroll.scrollTo(v.top - 100, 0)
- }).exec();
- }
- })
- },
- downCallback() {
- const messageList = {
- method: "MessageList",
- toUserId: this.formUserId,
- formUserId: this.userId,
- token: uni.getStorageSync('token'),
- currentPage: this.currentPage,
- pageSize: this.pageSize,
- sord: "desc"
- }
- chat.sendMsg(JSON.stringify(messageList))
- this.currentPage++;
- this.mescroll.endSuccess();
- },
- discard() {
- return;
- },
- switchVoice() {
- this.hideDrawer();
- this.isVoice = !this.isVoice;
- },
- openMore() {
- if (this.showMore) return this.hideDrawer()
- this.showMore = true;
- this.showEmoji = false;
- this.openDrawer();
- },
- openDrawer() {
- this.isVoice = false;
- this.popupLayerClass = 'showLayer';
- },
- hideDrawer() {
- this.popupLayerClass = '';
- setTimeout(() => {
- this.showMore = false;
- this.showEmoji = false;
- }, 150);
- },
- textareaFocus() {
- this.hideDrawer();
- },
- chooseEmoji() {
- if (this.showEmoji) return this.hideDrawer()
- this.showMore = false;
- this.showEmoji = true;
- this.openDrawer();
- },
- addEmoji(em) {
- this.textMsg += em.alt;
- },
- getEmojiUrl(url) {
- return imagesMap[url.replace('.', '')]
- },
- chooseImage(type) {
- uni.chooseImage({
- // #ifdef H5
- count: 1,
- // #endif
- sourceType: [type], //从相册选择
- success: (res) => {
- this.hideDrawer();
- if (res.tempFilePaths.length) res.tempFilePaths.map(o => (this.uploadFile(o)))
- }
- });
- },
- /* 上传图片 */
- uploadFile(files) {
- uni.uploadFile({
- url: this.define.comUploadUrl + 'IM',
- filePath: files,
- name: 'file',
- header: {
- Authorization: uni.getStorageSync('token') || ''
- },
- success: (uploadFileRes) => {
- const response = uploadFileRes.data ? JSON.parse(uploadFileRes
- .data) : {}
- if (uploadFileRes.statusCode !== 200) return this.$u.toast(
- response.msg)
- if (!response.data || !response.data.name) return
- const name = response.data.name
- this.getImageInfo(files, name)
- }
- })
- },
- /* 获取图片信息 */
- getImageInfo(files, name) {
- uni.getImageInfo({
- src: files,
- success: (image) => {
- let msg = {
- name,
- width: image.width,
- height: image.height,
- };
- this.sendMessage(msg, 'image');
- }
- })
- },
- addMsg(data) {
- if (data.method === 'receiveMessage') {
- const updateReadMessage = {
- method: "UpdateReadMessage",
- formUserId: this.formUserId,
- token: uni.getStorageSync('token')
- }
- chat.sendMsg(JSON.stringify(updateReadMessage))
- }
- data.id = this.$u.guid()
- if (data.contentType === "text") {
- data.msgContent = this.replaceEmoji(data.content)
- }
- if (data.contentType === "image") {
- this.msgImageList.push(this.baseURL + data.content.path)
- data.msgContent = this.setPicSize(data.content)
- data.msgContent.path = this.baseURL + data.content.path
- }
- if (data.contentType === "voice") {
- data.msgContent = data.content
- }
- this.msgList.push(data)
- this.$nextTick(() => {
- this.mescroll.scrollTo(99999, 0)
- })
- },
- dealMsg(item) {
- if (item.contentType === "text") {
- item.msgContent = this.replaceEmoji(item.content)
- }
- if (item.contentType === "image") {
- item.msgContent = this.setPicSize(JSON.parse(item.content))
- item.msgContent.path = this.baseURL + item.msgContent.path
- }
- if (item.contentType === "voice") {
- item.msgContent = JSON.parse(item.content)
- }
- return item
- },
- sendText() {
- if (!this.textMsg) return
- this.hideDrawer()
- this.sendMessage(this.textMsg, 'text')
- this.textMsg = ''
- },
- sendMessage(content, type) {
- const messageObj = {
- method: "SendMessage",
- token: uni.getStorageSync('token'),
- toUserId: this.formUserId,
- messageType: type,
- messageContent: content
- }
- chat.sendMsg(JSON.stringify(messageObj))
- this.isMsgList = false
- },
- voiceBegin(e) { // 录音开始
- this.RECORDER.stop();
- if (e.touches.length > 1) {
- return;
- }
- this.initPoint.Y = e.touches[0].clientY;
- this.initPoint.identifier = e.touches[0].identifier;
- // #ifdef APP-HARMONY
- this.RECORDER.start(); //录音开始,
- // #endif
- // #ifndef APP-HARMONY
- this.RECORDER.start({
- format: "mp3"
- }); //录音开始,
- // #endif
- },
- recordBegin(e) { //录音开始UI效果
- this.recording = true;
- this.voiceTis = '松开 结束';
- this.recordLength = 0;
- this.recordTimer = setInterval(() => {
- this.recordLength++;
- }, 1000)
- },
- voiceCancel() { // 录音被打断
- this.recording = false;
- this.voiceTis = '按住 说话';
- this.recordTis = '手指上滑 取消发送'
- this.willStop = true; //不发送录音
- this.RECORDER.stop(); //录音结束
- },
- voiceIng(e) { // 录音中(判断是否触发上滑取消发送)
- if (!this.recording) return
- let touche = e.touches[0];
- // #ifndef APP-HARMONY
- let voice = uni.upx2px(100)
- // #endif
- // #ifdef APP-HARMONY
- let voice = 100 / 2
- // #endif
- //上滑一个导航栏的高度触发上滑取消发送
- if (this.initPoint.Y - touche.clientY >= voice) {
- this.willStop = true;
- this.recordTis = '松开手指 取消发送'
- } else {
- this.willStop = false;
- this.recordTis = '手指上滑 取消发送'
- }
- },
- voiceEnd(e) { // 结束录音
- if (!this.recording) return
- this.recording = false;
- this.voiceTis = '按住 说话';
- this.recordTis = '手指上滑 取消发送'
- this.RECORDER.stop(); //录音结束
- },
- recordEnd(e) { //录音结束(回调文件)
- if (!this.willStop) {
- let min = parseInt(this.recordLength / 60);
- let sec = this.recordLength % 60;
- min = min < 10 ? '0' + min : min;
- sec = sec < 10 ? '0' + sec : sec;
- if (sec < '01') {
- this.willStop = true;
- this.$u.toast('说话时间太短');
- return
- }
- uni.uploadFile({
- url: this.define.comUploadUrl + 'IM',
- filePath: e.tempFilePath,
- name: 'file',
- header: {
- Authorization: uni.getStorageSync('token') || ''
- },
- success: (uploadFileRes) => {
- const handleUploadResponse = (uploadFileRes, min, sec) => {
- const response = (uploadFileRes.data && JSON.parse(uploadFileRes.data)) ||
- {};
- if (uploadFileRes.statusCode !== 200) {
- this.$u.toast(response.msg || '上传失败,未知错误');
- return;
- }
- const {
- data = {}
- } = response;
- if (!data.name) {
- this.$u.toast('上传的文件信息不完整');
- return;
- }
- const msg = {
- name: data.name,
- length: `${min}:${sec}`
- };
- this.sendMessage(msg, 'voice');
- };
- handleUploadResponse(uploadFileRes, min, sec);
- }
- })
- } else {
- // console.log('取消发送录音');
- }
- this.willStop = false;
- },
- setPicSize(content) { //处理图片尺寸,如果不处理宽高,新进入页面加载图片时候会闪
- // 让图片最长边等于设置的最大长度,短边等比例缩小,图片控件真实改变,区别于aspectFit方式。
- // #ifndef APP-HARMONY
- let maxW = uni.upx2px(350); //350是定义消息图片最大宽度
- let maxH = uni.upx2px(350); //350是定义消息图片最大高度
- // #endif
- // #ifdef APP-HARMONY
- let maxW = 350 / 2; //350是定义消息图片最大宽度
- let maxH = 350 / 2; //350是定义消息图片最大高度
- // #endif
- if (content.width > maxW || content.height > maxH) {
- let scale = content.width / content.height;
- content.width = scale > 1 ? maxW : maxH * scale;
- content.height = scale > 1 ? maxW / scale : maxH;
- }
- return content;
- },
- replaceEmoji(str) { //替换表情符号为图片
- let replacedStr = str.replace(/\[([^(\]|\[)]*)\]/g, item => 'jnpfjnpf' + item + 'jnpfjnpf');
- let strArr = replacedStr.split(/jnpfjnpfjnpfjnpf|jnpfjnpf/g)
- strArr = strArr.filter(o => o)
- let contentList = []
- for (let i = 0; i < strArr.length; i++) {
- let item = {
- content: strArr[i],
- type: 'emjio'
- }
- if (/\[([^(\]|\[)]*)\]/.test(strArr[i])) {
- let content = ''
- for (let j = 0; j < this.emojiList.length; j++) {
- let row = this.emojiList[j];
- if (row.alt == strArr[i]) {
- content = this.getEmojiUrl(row.url)
- break
- }
- }
- item = {
- content: content,
- type: 'emjio'
- }
- } else {
- item = {
- content: strArr[i],
- type: 'text'
- }
- }
- contentList.push(item)
- }
- return contentList
- },
- showPic(path) { // 预览图片
- uni.previewImage({
- indicator: "none",
- current: path,
- urls: this.msgImageList
- });
- },
- playVoice(msg) { // 播放语音
- AUDIO.stop();
- AUDIO.src = this.baseURL + msg.msgContent.path;
- if (this.playMsgid != null && this.playMsgid == msg.id) {
- this.$nextTick(() => {
- AUDIO.stop();
- });
- this.playMsgid = null;
- } else {
- this.$nextTick(() => {
- AUDIO.play();
- });
- this.playMsgid = msg.id;
- }
- },
- }
- }
- </script>
- <style lang="scss">
- @import "./index.scss";
- </style>
|