融云群聊/单聊移动端模板(附图) 支持文件上传 图片上传 表情发送 超链接跳转

功能:基础功能群聊 文件上传 图片上传 表情发送 超链接跳转

注:里面涉及到诸多函数依赖,请忽略,只看主要功能即可

 融云群聊/单聊移动端模板(附图) 支持文件上传 图片上传 表情发送 超链接跳转

html

<template>
  <div class="container" :style="{'top':'calc('+marginTop+' + 0.16rem)'}">
    <div class="chart-message  display-flex" :style="{'height':'calc(100vh - '+marginTop+' - '+(KEYBOARD_H+0.16)+'rem)'}">
      <div class="chart-header">
        <h2>{{groupChatInfo.groupName}}({{chatGroupList.length}})</h2>
        <div @click="handleGroupUser" class="more-user display-flex hvc">
          <i class="icon icon-more"></i>
        </div>
      </div>
      <div class="chart-message-cont  flex-1"  @touchstart="handleMessageInputBlur">
        <div class="chart-message-cont-wrap">
          <div class="load-more" v-if="hasMore">
            <span @click="getHistoryMessage({type:'loadMore'})">加载更多</span>
          </div>
          <template v-for="(item,index) in historyMessageList">
            <!--加入者信息-->
            <div v-if="item.messageType == 'RC:InfoNtf'" class="chart-message-item message-time">
              <p>{{item.content.message}}</p>
            </div>
            <!--接收者-->
            <div v-if="item.messageDirection == '2'&&item.messageType != 'RC:InfoNtf'" class="chart-message-item message-receive display-flex">
              <div class="user-avatar" @click="handleAvatar({type:'2',data:item.content.extra})">
                <img v-if="item.content.extra" :src="item.content.extra.avatar" alt="">
                <img v-else src="./../../assets/images/industry/icon-avatar.png" alt="">
              </div>
              <div>
                <p v-if="item.content.extra" style="color: #555;font-size: 0.2rem;line-height: 1.5;">{{item.content.extra.name}}</p>
                <div  @click="handleDownloadFile({type:item.messageType,data:item})"  class="user-msg " :class="{'display-flex':item.messageType == 'RC:FileMsg'}">
                  <template v-if="item.messageType == 'RC:TxtMsg'">
                    <p @contextmenu.stop.prevent="handleCopyText({data:item.content.content,eType:'contextmenu'})"  @click="handleCopyText({data:item.content.content,eType:'click'})"  v-html="chatMessageFormat({data:item.content.content,type:2})"></p>
                  </template>
                  <template v-else-if="item.messageType=='RC:ImgMsg'">
                    <img @click="handleImagePreview({url:item.content.imageUri})" :src="item.content.imageUri" alt="">
                  </template>
                  <template v-else-if="item.messageType == 'RC:FileMsg'">
                    <div class="file-info">
                      <p class="file-name">{{item.content.name}}</p>
                      <p class="file-size">{{item.content.size>1024*1024?(item.content.size/1024/1024).toFixed(2)+'MB':(item.content.size/1024).toFixed(2)+'KB'}}</p>
                    </div>
                    <div class="file-icon">
                      <img src="./../../assets/images/chat/icon-file.png" style="height: 0.68rem; 0.68rem;" alt="">
                    </div>
                  </template>
                </div>
              </div>
            </div>
            <!--发送者-->
            <div  v-if="item.messageDirection == '1'&&item.messageType != 'RC:InfoNtf'" class="chart-message-item message-send display-flex">
              <div>
                <p v-if="item.content.extra" style="text-align: right;color: #555;font-size: 0.2rem;line-height: 1.5;">{{item.content.extra.name}}</p>
                <div  @click="handleDownloadFile({type:item.messageType,data:item})"  class="user-msg" :class="{'user-msg--file display-flex':item.messageType == 'RC:FileMsg'}">
                  <template v-if="item.messageType == 'RC:TxtMsg'">
                    <p  @contextmenu.stop.prevent="handleCopyText({data:item.content.content,eType:'contextmenu'})" @click="handleCopyText({data:item.content.content,eType:'click'})" v-html="chatMessageFormat({data:item.content.content,type:1})"></p>
                  </template>
                  <template v-else-if="item.messageType=='RC:ImgMsg'">
                    <img @click="handleImagePreview({url:item.content.imageUri})" :src="item.content.imageUri" alt="">
                  </template>
                  <template v-else-if="item.messageType == 'RC:FileMsg'">
                    <div class="file-icon">
                      <img src="./../../assets/images/chat/icon-file.png" style="height: 0.68rem; 0.68rem;" alt="">
                    </div>
                    <div class="file-info">
                      <p class="file-name">{{item.content.name}}</p>
                      <p class="file-size">{{item.content.size>1024*1024?(item.content.size/1024/1024).toFixed(2)+'MB':(item.content.size/1024).toFixed(2)+'KB'}}</p>
                    </div>
                  </template>
                </div>
              </div>
              <div @click="handleAvatar({type:'1',data:item.content.extra})" class="user-avatar">
                <img v-if="item.content.extra" :src="item.content.extra.avatar" alt="">
                <img v-else src="./../../assets/images/industry/icon-avatar.png" alt="">
              </div>
            </div>
            <!--时间类型-->
            <div v-if="index < historyMessageList.length-1 &&historyMessageList[index+1].sentTime-historyMessageList[index].sentTime>10*60*1000" class="chart-message-item message-time">
              <p v-html="chatMessageTimeFormat({data:historyMessageList[index+1]})"></p>
            </div>
          </template>
        </div>
      </div>
      <div class="chart-message-footer" v-if="sendMsgState">
        <div class="btn-group display-flex">
          <div class="btn-industry-map display-flex hvc">
            <i  v-if="browser.mobile" @touchstart="handleIndustryMap"  class="icon icon-industryMap"></i>
            <i v-else @click="handleIndustryMap"  class="icon icon-industryMap"></i>
          </div>
          <textarea @blur="messageBlur" ref="messageContent" @focus="messageFocus" @keydown.enter="handleSendMessage({event:$event,sendType:'text'})"  v-model.trim="messageContent" placeholder="请输入内容" :class="messageContent?'message-val':'message-placeholder'" class="message-input flex-1"></textarea>
          <div class="btn-smile display-flex hvc">
            <i class="icon icon-smile"  v-if="browser.mobile" @touchstart="handleTogglePanelEmtion({event:$event})"></i>
            <i class="icon icon-smile" v-else @click="handleTogglePanelEmtion({event:$event})"></i>
          </div>
          <template v-if="messageContent">
            <div class="btn-send  display-flex hvc">
              <span v-if="browser.mobile"  @touchstart="handleSendMessage({event:$event,sendType:'text'})">发送</span>
              <span v-else  @click="handleSendMessage({event:$event,sendType:'text'})">发送</span>
            </div>
          </template>
          <template v-else>
            <div class="btn-add display-flex hvc">
              <i class="icon icon-add" v-if="browser.mobile" @touchstart="handleTogglePanelImage({event:$event})"></i>
              <i class="icon icon-add"  v-else @click="handleTogglePanelImage({event:$event})"></i>
            </div>
          </template>
        </div>
        <div class="multi-fun-panel">
          <div v-show="panelImageState"  class="panel-image">
            <ul>
              <li>
                <label for="uploadImage">
                  <input @change.prevent="handleUploadImg({event: $event})" style="display: none" accept="image/*" type="file" ref="uploadImage" >
                  <img style=" 1.16rem;" src="./../../assets/images/chat/icon-upload-images.png" alt="">
                  <p>照片</p>
                </label>
              </li>
              <li>
                <label for="uploadFile">
                  <input @change.prevent="handleUploadFile({event: $event})" style="display: none"  type="file" ref="uploadFile" >
                  <img style=" 1.16rem;" src="./../../assets/images/chat/icon-upload-file.png" alt="">
                  <p>文件</p>
                </label>
              </li>
            </ul>
          </div>
          <div v-show="panelEmtionState" class="panel-emtion display-flex">
            <template v-if="browser.mobile">
              <Emotion class="emotion-item" v-for="(item, i) in emoticon" :key="i" @touchend.native="handleEmotion({data:item})">{{item}}</Emotion>
            </template>
            <template v-else>
              <Emotion class="emotion-item" v-for="(item, i) in emoticon" :key="i" @click.native="handleEmotion({event:$event,data:item})">{{item}}</Emotion>
            </template>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

js

<script>
  import {ImagePreview} from 'vant'
  import {formatDate,browser,getOpenId,emotionParams ,addEvent,regularExpression,copyText,globalStatistics} from "../../../static/js/help";
  import {getByToken,groupBoardChat,setMessageRead,groupChatInfo,getWechatInfoByUnionId ,attachUpload,chatAutoQuote,groupChatMembers} from "../../../static/js/api";
  import {mapGetters} from 'vuex';
  import Emotion from './../../components/chat/emotion'
  const avatar = require('./../../assets/images/industry/icon-avatar.png');
  import {Dialog} from 'vant'
  window.vueThis = null;
  export default {
    name: "chatMessage",
    components:{
      Emotion
    },
    data() {
      return {
        userInfo:{
          name:'',
          icon:'',
          appUserId:''
        },//自己的用户信息
        receiveTargetId:'',//接送消息的userId
        messageContent:'',//消息内容
        historyMessageList:[],//历史消息
        scrollDirection:'down',//down 向下,up向上
        timestrap:new Date().getTime(),//历史消息limit
        hasMore:false,//是否展示加载更多
        beforeScrollHeight:0,//滚动之前高度
        sentTime:localStorage.sentTime,//记录已读消息时间
        groupChatInfo:{},//群聊信息
        wxUserInfo:{},//微信用户信息
        emoticon:emotionParams.list,
        panelImageState:false,//图片上传面板
        panelEmtionState:false,//表情面板
        sendImage:'',//图片发送
        chatGroupList:[],
        chatFilter:process.env.CHAT_FILTER,//聊天用户过滤列表
        browser:browser.versions,
        KEYBOARD_H:0,//软盘高度
      }
    },
    watch:{
      'historyMessageList'(){
        this.$nextTick(()=>{
          this.scrollView();
        })
      },
      'im'(){
        this.creatRongIMConnect();
      },
    },
    computed:{
      ...mapGetters ([
        'im',
        'sysUserInfo'
      ]),
      deviceInfo() {
        return this.$store.getters.deviceInfo;
      },
      marginTop() {
        if (this.deviceInfo.grade == '1.3' || this.deviceInfo.grade == '1.4') {//App
          return '0rem'
        } else if (this.deviceInfo.grade == '2') {//PC
          return '1.78rem'
        } else {//手机微信
          return '0.9rem'
        }
      },
      //发送框状态
      sendMsgState(){
        let state = true;
        for(let i  = 0;i<this.chatFilter.length;i++){
          if(this.chatFilter[i].id == this.receiveTargetId){
            return false
            continue
          }
        }
        return state
      }
    },
    methods: {
      //让input失去焦点
      handleMessageInputBlur(){
        this.$refs.messageContent.blur();
      },
      //点击头像
      handleAvatar({type,data} = {}){
        if(type == '1'){
          this.$router.push({
            name:'industrySetting'
          })
        }else if(type == '2'){
          this.$router.push({
            name:'industryContacts',
            query:{
              clientId:data.clientId
            }
          })
        }
      },
      //图片面板切换
      handleTogglePanelImage({event} = {}){
        this.panelImageState = !this.panelImageState;
        this.panelEmtionState = false;
        if(this.panelImageState){
          this.$refs.messageContent.blur();
        }else {
          this.$refs.messageContent.focus();
        }
        event.preventDefault();
      },
      //表情面板切换
      handleTogglePanelEmtion({event} = {}){
        this.panelEmtionState = !this.panelEmtionState;
        this.panelImageState = false;
        if(this.panelEmtionState){
          this.$refs.messageContent.blur();
        }else {
          this.$refs.messageContent.focus();
        }
        event.preventDefault();
      },
      //图片预览
      handleImagePreview({url} = {}){
        ImagePreview({
          images: [url],
          closeable: true,
        });
      },
      //图片上传
      handleUploadImg({event} = {}){
        let file = event.target.files[0];
        if(file.size>10*1024*1024){
          this.$_toast('图片大小需小于1OMB')
          this.$refs.uploadImage.value = '';
          return
        }
        attachUpload({
          multipartFile:file
        }).then((res)=>{
          if(res.data.state == 'SUCCESS'){
            let url = res.data.url;
            this.sendImage = url;
            this.handleSendMessage({event:event,sendType:'image'});
            this.$refs.uploadImage.value = '';
          }
        })
        event.preventDefault();
      },
      //文件上传
      handleUploadFile({event} = {}){
        let file = event.target.files[0];
        let fileInfo = {
          name: file.name,
          size: file.size,
          type: file.type,
        }
        if(file.size>10*1024*1024){
          this.$_toast('文件大小需小于1OMB')
          this.$refs.uploadFile.value = '';
          return
        }
        attachUpload({
          multipartFile:file
        }).then((res)=>{
          if(res.data.state == 'SUCCESS'){
            let url = res.data.url;
            this.sendImage = url;
            fileInfo.fileUrl = url;
            this.handleSendMessage({event:event,sendType:'file',fileInfo});
            this.$refs.uploadFile.value = '';
          }
        })
        event.preventDefault();
      },
      //文件下载
      handleDownloadFile({type,data} = {}){
        if(type == 'RC:FileMsg'){
          let {fileUrl,name,size} = data.content;
          this.$router.push({
            name:'downloadFile',
            query:{
              params:JSON.stringify({
                fileUrl,
                name,
                size,
              })
            }
          })
        }
      },
      //聊天消息格式化
      chatMessageFormat({data,type} = {}){
        if(type == 1){
          var style = "color:#232323;text-decoration: underline"
        }else if(type == 2){
          var style = "color:#333;text-decoration: underline"
        }
        let _html = data.replace(regularExpression.http, (res)=> {
          let url = res;
          if(res.indexOf('http')=='-1'){
            url ='http://'+url;
          }
          return  `<a style="${style}"  onclick="handleCopyTextByMiniProgarm({event:event,url:'${url}'})"    href="javascript:;">${res}</a>`;
          return this.deviceInfo.terminalType == 'MINIPROGRAM'?`<a style="${style}"  onclick="handleCopyTextByMiniProgarm(event,'${res}')"    href="javascript:;">${res}</a>`:`<a onclick="handleCopyTextByMiniProgarm(event,'${res}')" style="${style}" target="_blank" href="${url}">${res}</a>`
        })
         _html = _html.replace(/#[u4E00-u9FA5]{1,3};/gi, this.emotionFormat)
        return _html
      },
      //表情格式化
      emotionFormat (res) {
        let word = res.replace(/#|;/gi,'')
        const list = emotionParams.list
        let index = list.indexOf(word)
        return `<img style=" 0.343rem;height: 0.343rem;display: inline-block;vertical-align: -4px" src="${emotionParams.url}${index}.gif" align="middle">`
      },
      //选择表情
      handleEmotion({event,data} ={}){
        let emotion = `#${data};`;
        this.messageContent +=emotion;
        let h = this.$refs.messageContent.scrollHeight;
        let fontSize = 100/parseFloat($('html').css('font-size'));//px换算rem
        this.$refs.messageContent.scrollTop =h*fontSize*2;
        //event.preventDefault();
      },
      //切换到地图
      handleIndustryMap(){
        this.handleMessageInputBlur();
        this.$parent.type = 1;
        localStorage.industryType = 1;
        globalStatistics({
          eventType:'E_033',
          clickType:'C_032_0002'
        })
      },
      //聊天用户
      getGroupChatMembers(){
        groupChatMembers({
          groupId:this.$route.query.groupId
        }).then((res)=>{
          if(res.data.code == '0000'){
            let data = res.data.data;
            this.chatGroupList = data;
          }
        })
      },
      //获取微信用户信息
      getWechatInfoByUnionId(){
        getWechatInfoByUnionId({
          unionId:getOpenId()
        }).then((res)=>{
          if(res.data.code == '0000'){
            let data = res.data.data;
            this.wxUserInfo = data;
          }
        })
      },
      //input失去焦点滚动到可视区域 https://cdn.ronghub.com/RongIMLib-3.0.6-dev.min.js
      handleScrolltoView() {
        document.querySelector(".chart-message").scrollIntoView(true);
      },
      //与建立融云连接
      creatRongIMConnect(){
        //只有获取到了targetId才能做其他操作
        this.getGroupChatInfo().then(()=>{
          this.watchRongIMLib();//监测
          this.getHistoryMessage();//历史消息
          this.clearnUnReadConvert() //清除未读
        })
      },
      //设置监听
      watchRongIMLib(){
        this.im.watch({
          conversation: ()=>{
            console.log('消息列表更新');
          },
          message: (event)=>{
            if(this.$route.name !='industry') return
            var message = event.message;
            let {messageUId,sentTime,type} = message;
            if(message.messageType !='RC:ReadNtf'){
              if(message.type == 3){
                message.receivedStatus = 0;
                this.historyMessageList.push(message);
                this.setMessageRead({
                  messageUId,
                  lastMessageSendTime:sentTime,
                  type});
                this.clearnUnReadConvert() //清除未读
              }
              console.log('我已接收到新消息');
            }else {
              this.sentTime  = localStorage.sentTime = message.sentTime;
              this.SetServerMessageRead();//服务器已读
              console.log('我已接收到已读通知')
            }
          }
        });
      },
      //message失去焦点
      messageBlur(){
        this.handleScrolltoView();
      },
      //message获取焦点
      messageFocus(){
        this.panelImageState = false;
        this.panelEmtionState = false;
      },
      //消息发送
      handleSendMessage({event,sendType,fileInfo} = {}){
        //手机回车不发消息
        var conversation = this.im.Conversation.get({
          targetId: this.receiveTargetId,//接收方USERID
          type: RongIMLib.CONVERSATION_TYPE.GROUP
        });
        //发送文本
        if(sendType == 'text'){
          if(this.browser.mobile&&event.keyCode == '13'){
            return
          }
          if(!this.messageContent) {
            this.$_toast('请输入内容');
            return
          }
          //发送文本
          var sendParams = {
            messageType: RongIMLib.MESSAGE_TYPE.TEXT, // 填写开发者定义的 messageType
            content: { // 填写开发者定义的消息内容
              content:this.messageContent,
              extra:{
                avatar:this.wxUserInfo.headImgUrl,
                name:this.sysUserInfo.name,
                clientId:this.sysUserInfo.client_id
              },
            },
            isPersited: true,// 是否存储在服务端,默认为 true
            isCounted: true,  // 是否计数. 计数消息接收端接收后未读数加 1,默认为 true
            pushContent:'user 发送了一条消息',  // Push 显示内容
            pushData: 'Push 通知时附加信息',  // Push 通知时附加信息, 可不填
            isStatusMessage: false,  // 设置为 true 后 isPersited 和 isCounted 属性失效
            disableNotification: false, // 设置为 true 后移动端不会收到 Push 信息和本地通知提醒
          }
        }else if(sendType == 'image'){
          //发送图片
          var sendParams = {
            messageType: RongIMLib.MESSAGE_TYPE.IMAGE, // 填写开发者定义的 messageType
            content: { // 填写开发者定义的消息内容
              content:'',//base64图片地址
              imageUri:this.sendImage,//图片url
              extra:{
                avatar:this.wxUserInfo.headImgUrl,
                name:this.sysUserInfo.name,
                clientId:this.sysUserInfo.client_id
              },
            },
            isPersited: true,// 是否存储在服务端,默认为 true
            isCounted: true,  // 是否计数. 计数消息接收端接收后未读数加 1,默认为 true
            pushContent:'user 发送了一条消息',  // Push 显示内容
            pushData: 'Push 通知时附加信息',  // Push 通知时附加信息, 可不填
            isStatusMessage: false,  // 设置为 true 后 isPersited 和 isCounted 属性失效
            disableNotification: false, // 设置为 true 后移动端不会收到 Push 信息和本地通知提醒
          }
        }else if(sendType == 'file'){
          //发文件
          var sendParams = {
            messageType: RongIMLib.MESSAGE_TYPE.FILE, // 填写开发者定义的 messageType
            content: {
              ...fileInfo,
              extra:{
                avatar:this.wxUserInfo.headImgUrl,
                name:this.sysUserInfo.name,
                clientId:this.sysUserInfo.client_id
              }
            },
            isPersited: true,// 是否存储在服务端,默认为 true
            isCounted: true,  // 是否计数. 计数消息接收端接收后未读数加 1,默认为 true
            pushContent:'user 发送了一条消息',  // Push 显示内容
            pushData: 'Push 通知时附加信息',  // Push 通知时附加信息, 可不填
            isStatusMessage: false,  // 设置为 true 后 isPersited 和 isCounted 属性失效
            disableNotification: false, // 设置为 true 后移动端不会收到 Push 信息和本地通知提醒
          }
        }
        conversation.send(sendParams).then((message)=>{
          this.scrollDirection = 'down';
          this.historyMessageList.push(message);
          if(sendType == 'text'){
            //自动报单
            chatAutoQuote({
              wechatNickName:this.wxUserInfo.nickname,
              content:this.messageContent
            })
          }
          this.messageContent = '';
        });

        groupBoardChat({
          targetToken:this.receiveTargetId,
          content:this.messageContent
        })
        event.preventDefault();
      },
      //告诉对方,该消息已读过了
      setMessageRead({messageUId,lastMessageSendTime,type}){
        var conversation = this.im.Conversation.get({
          targetId:this.receiveTargetId,
          type: RongIMLib.CONVERSATION_TYPE.GROUP
        });
        // 以上 3 个属性在会话的最后一条消息中可以获得
        conversation.send({
          messageType: 'RC:ReadNtf',
          content: {
            messageUId: messageUId,
            lastMessageSendTime: lastMessageSendTime,
            type: type
          }
        }).then((message)=>{
          console.log('消息我已经读了,发了通知给你');
        });
      },
      //获取历史消息
      getHistoryMessage({type}={}){
        if(type == 'loadMore'){
          this.scrollDirection = 'up'
        }else {
          this.scrollDirection = 'down'
        }

        var conversation = this.im.Conversation.get({
          targetId: this.receiveTargetId,//接收方的 userId
          type: RongIMLib.CONVERSATION_TYPE.GROUP
        });
        conversation.getMessages({
          timestrap:this.timestrap,
          count: 20
        }).then((result)=>{
          console.log(result.list)
          this.scrollDirection = 'up';
          this.beforeScrollHeight = $('.chart-message-cont-wrap').height();
          this.historyMessageList = [...result.list,...this.historyMessageList]; // 历史消息列表
          this.hasMore = result.hasMore;
          if(result.list.length){
            this.timestrap = result.list[0].sentTime;
          }
        });

      },

      //滚动到可视区域
      scrollView({animate}={animate:true}){
        let scrollHeight = $('.chart-message-cont-wrap').height();
        if(this.scrollDirection == 'down'){
          if(animate){
            $('.chart-message-cont').animate({scrollTop: scrollHeight+'px'}, 500);
          }else {
            $('.chart-message-cont').scrollTop(scrollHeight)
          }
        }else if(this.scrollDirection == 'up'){
          $('.chart-message-cont').animate({scrollTop: (scrollHeight- this.beforeScrollHeight)+'px'}, 0);
        }

      },
      //时间格式化
      chatMessageTimeFormat({data}={}){
        let currentDate = formatDate({date:data.sentTime});
        let nowDate = formatDate();
        if(currentDate == nowDate){
          return formatDate({
            date:data.sentTime,
            fmt:'hh:mm',
          })
        }else {
          return formatDate({
            date:data.sentTime,
            fmt:'yyyy-MM-dd hh:mm'
          })
        }
      },
      //清除未读数
      clearnUnReadConvert(){
        this.im.Conversation.get({
          targetId:this.receiveTargetId,
          type: RongIMLib.CONVERSATION_TYPE.GROUP
        }).read().then(()=>{
          console.log('清除未读数成功'); // im.watch conversation 将被触发
        });
      },
      getUserInfo(){
        getByToken({
          targetId:this.receiveTargetId
        }).then((res)=>{
          if(res.data.code == '0000'){
            let data = res.data.data;
            if(data){
              this.userInfo = data;
            }
          }
        })
      },
      //服务器已读
      SetServerMessageRead(){
        setMessageRead({
          targetToken:this.receiveTargetId
        })
      },
      //获取群聊信息
      getGroupChatInfo(){
        return new Promise((resolve,reject)=>{
          groupChatInfo({}).then((res)=>{
            if(res.data.code == '0000'){
              let data = res.data.data;
              this.groupChatInfo = data;
              this.receiveTargetId = data.groupId;
              document.title = data.groupName
              resolve();
            }
          })
        })

      },
      //查看群组用户
      handleGroupUser(){
        this.$router.push({
          name:'chatGroupUser',
          query:{
            groupId:this.receiveTargetId
          }
        })
      },
      //复制
      handleCopyText({data,eType} ={}){
        if(!this.browser.mobile&&eType =='click') return
        if(this.deviceInfo.grade == '2.1') {
          this.$_toast('请使用ctrl+c复制')
          return;
        }
        copyText({text:data})
      },
      stopEvent(event) { //阻止冒泡事件
        //取消事件冒泡
        var e =  event; //若省略此句,下面的e改为event,IE运行可以,但是其他浏览器就不兼容
        if (e && e.stopPropagation) {
          // this code is for Mozilla and Opera
          e.stopPropagation();
        } else if (window.event) {
          // this code is for IE
          window.event.cancelBubble = true;
        }
      }
    },
    created(){
      this.getGroupChatMembers();
      window.vueThis = this;
    },
    activated(){
      this.scrollView();
      this.getGroupChatMembers();
      if(this.im){
        this.clearnUnReadConvert() //清除未读
      }
    },
    mounted() {
      this.SetServerMessageRead();
      this.getWechatInfoByUnionId();
      if(this.im){
        this.creatRongIMConnect();
      }
      addEvent({ele:'.panel-emtion'})
    }

  }
  window.handleCopyTextByMiniProgarm = function(data){
    if(window.vueThis.deviceInfo.terminalType == 'MINIPROGRAM'){
      copyText({text:data.url,isTip:false})
      Dialog.alert({
        message: '小程序不支持打开链接,链接已复制请在浏览器中打开',
      })
    }else {
      window.open(data.url,'_blank')
    }
    window.vueThis.stopEvent(event);
  }
</script>

css

<style lang="scss" scoped>

  .container{ 7.5rem;margin: auto;position: relative;
    -moz-user-select:text;
    -webkit-user-select:text;
  }
  .chart-header{height: 0.93rem; 7.5rem;background: #fff;
    h2{font-size: 0.3rem;color: #2C2C2C;text-align: center;line-height: 0.93rem;font-weight: 700}
    .more-user{
       0.93rem;
      height: 0.93rem;
      position: absolute;right: 0;top: 0;
      .icon-more{ 0.36rem;height: 0.09rem;background: url("././../../assets/images/chat/icon-more.png") no-repeat;background-size: contain;}
    }

  }
  .chart-message{flex-direction: column;height: 100vh;
    .load-more{text-align: center;padding: 0.3rem 0;cursor: pointer;
      span{color: #555;}
    }
    .chart-message{
      &-item{margin: 0.7rem 0;
        .user-avatar{ 0.88rem;
          img{ 0.88rem;height: 0.88rem;background: #fff;border-radius: 0.88rem;overflow: hidden;border: 0.01rem solid #eee;}
        }
      }
      &-cont{padding: 0 0.18rem;overflow: scroll;background: #f5f5f5;
        .message-time{color: #888888;font-size: 0.26rem;text-align: center;margin: 0.2rem 0;
          p{display: inline-block;
            background: rgba(0,0,0,0.2);
            padding: 2px 5px;
            color: #fff;
            border-radius: 2px;}
        }
        .message-receive{margin-right: 1.2rem;
          .user-avatar{margin-right: 0.2rem;}
          .user-msg {background: #fff;padding: 0.25rem 0.3rem;border: 0.01rem solid #DDDDDD;border-radius: 0.04rem 0.2rem 0.2rem 0.2rem;
            img{ auto;height: auto;max- 1.8rem;}
            .href{color: #333;text-decoration: underline}
          }

          .file-info{margin-right: 0.6rem;}
          .file-size{color: #999;font-size: 0.24rem;}
          p{font-size:0.28rem;color: #333;line-height: 0.45rem;word-break: break-all;}

          }
        .message-send{margin-left: 1.2rem;justify-content: flex-end;
          .user-avatar{margin-left: 0.2rem;}
          .user-msg {background: #9eea6a;padding: 0.25rem 0.3rem;border: 0.01rem solid #9eea6a;border-radius: 0.2rem  0.04rem 0.2rem 0.2rem;position: relative;
            img{ auto;height: auto;max- 1.8rem;}
          }
          .user-msg.user-msg--file{background: #fff;border: 0.01rem solid #DDDDDD;}
          .file-info{margin-left: 0.6rem;}
          .file-size{color: #232323;font-size: 0.24rem;}
          .href{color: #232323;text-decoration: underline;border: 1px solid red;}
          .read-state{position: absolute;font-size: 0.24rem; 0.6rem;text-align: center;right: 0;bottom: -0.4rem;background: rgba(0,0,0,0.1);border-radius: 0.05rem;color: #555;}
          p{font-size:0.28rem;color: #232323;line-height: 0.45rem;word-break: break-all;}
        }
      }
      &-footer{
        .btn-group{
          background: #fff;padding: 0.15rem 0.25rem;box-sizing: border-box;padding-bottom: 0.4rem;
        }
        .btn-industry-map{ 0.68rem;margin-right: 0.34rem;
          .icon-industryMap{ 0.68rem;height: 0.68rem;display: inline-block;background: url("./../../assets/images/industry/icon-industryMap.png") no-repeat;background-size: contain;cursor: pointer}
        }
        .btn-smile{ 0.56rem;margin-right: 0.24rem;margin-left: 0.24rem;
          .icon-smile{ 0.56rem;height: 0.56rem;display: inline-block;background: url("./../../assets/images/industry/icon-smile.png") no-repeat;background-size: contain;cursor: pointer}
        }
        .btn-add{ 0.56rem;
          .icon-add{ 0.56rem;height: 0.56rem;display: inline-block;background: url("./../../assets/images/industry/icon-add.png") no-repeat;background-size: contain;;cursor: pointer}
        }
        .btn-send{ 0.56rem;
          span{color: #5C86F7;font-size: 0.3rem;background: transparent;border: none;text-align: center;font-weight: bold;min- 1rem;white-space: nowrap;cursor: pointer}
        }
        .message-input{padding: 0;margin: 0;border: none;height: 0.8rem;border-radius: 0.12rem;background:#F1F3FA;box-sizing: border-box;font-size: 0.28rem;color: #555;
          &::-webkit-scrollbar {
             0.05rem;
            height: 0.1rem
          }
          &::-webkit-scrollbar-thumb {
            min-height: 0.05rem;
            background: #c4c6cc;
            border-radius: 0.02rem;
          }
          &::-webkit-scrollbar-track-piece {
            background: #fff
          }
          &::placeholder{color: #999;}
        }
        .message-placeholder{
          padding: 0.2rem  0.1rem;
          line-height: 0.4rem;
        }
        .message-val{
          padding: 0.1rem  0.1rem;
          line-height: 0.35rem;
        }
      }

    }
    .multi-fun-panel{background: #F7F7F7;
      .panel-image{
        ul{
          li{text-align: center;display: inline-block;margin:0.3rem 0.33rem;
            p{text-align: center;color: #4C4C4C;font-size: 0.24rem;margin-top: 0.1rem;}
          }
        }
      }
    }
    .panel-emtion{flex-direction: row;flex-wrap: wrap;max-height: 3rem;overflow: scroll;padding: 0.1rem ;box-sizing: border-box;}
  }

</style>