知识点

WebSocket介绍

WebSocket 是一种在 Web 应用中实现实时通信的方法,它可以在客户端和服务器端之间建立长连接,实现实时消息传递。

SockJS

SockJS 是一个 JavaScript 库,用于在浏览器和 Web 服务器之间建立实时通信连接。它提供了一个 WebSocket 的备选方案,并兼容多种浏览器和 Web 服务器。SockJS 会自动检测浏览器是否支持 WebSocket,如果不支持,则会自动降级为其他协议(如 long polling、iframe、JSONP 等)

STOMP

STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。
简单地说,stomp是一个用于client之间进行异步消息传输的简单文本协议

开发环境

"sockjs-client": "^1.5.1",
"stompjs": "^2.3.3",
"vue": "^2.6.10",
"vuex": "^3.6.2"
// 本地Node版本号
node v14.7.0

功能实现

安装应用

npm install sockjs-client --save
npm install stompjs --save

在vuex中创建

const call = {
  state: {
    websocketUrl: 'https://app.lzzgh.org.cn/lz72hourApi',
    stompClient: '', // websocket
    sendContent: "",//发送的数据
    userPhone: "",//给对方发送消息对方的userPhone
    chatType: "",//聊天的方式 1:单聊 2:群聊
    activitySign:false,//是否参加活动
    groupContent:"",//群聊发送数据
	groupCPContent:"", // cp小屋的
    clears:false,//清空消息
  },
  getters: {
    getWebsocketUrl: state => {
      return state.websocketUrl
    },
    getWebsocket: state => {
      return state.stompClient
    },
    getSendContent: state => {
      return state.sendContent
    },
    getGroupContent: state => {
      return state.groupContent
    },
		getGroupCPContent: state => {
		  return state.groupCPContent
		},
    getUserPhone: state => {
      return state.userPhone
    },
    getChatType: state => {
      return state.chatType
    },
    getActivitySign:state =>{
      return state.activitySign
    },
    getClears:state =>{
      return state.clears
    }
  },
  mutations: {
    setMyWebsocket(state, stompClient) {
      state.stompClient = stompClient
    },
    setMySendContent(state, sendContent) {
      state.sendContent = sendContent
    },
    setMyGroupContent(state, groupContent) {
      state.groupContent = groupContent
    },
		setMyGroupCPContent(state, groupCPContent) {
		  state.groupCPContent = groupCPContent
		},
    setMyUserPhone(state, userPhone) {
      state.userPhone = userPhone
    },
    setMyChatType(state, chatType) {
      state.chatType = chatType
    },
    setMyActivitySign(state){
      state.activitySign = !state.activitySign
    },
    setMyClears(state){
      state.clears = !state.clears
    }
  },
  actions: {
    setWebsocket({ commit }, stompClient) {
      commit('setMyWebsocket', { stompClient })
    },
    setSendContent({ commit }, sendContent) {
      commit('setMySendContent', { sendContent })
    },
    setGroupContent({ commit }, groupContent) {
      commit('setMyGroupContent', { groupContent })
    },
		setGroupCPContent({ commit }, groupCPContent) {
		  commit('setMyGroupCPContent', { groupCPContent })
		},
    setUserPhone({ commit }, userPhone) {
      commit('setMyUserPhone', { userPhone })
    },
    setChatType({ commit }, chatType) {
      commit('setMyChatType', { chatType })
    }
  }
}
export default call

vue中的引入、监听、实例化与收发、订阅消息

引入组件

import { mapGetters } from "vuex";
import Video from "@/components/video"; //播放
import { imgPreview } from "@/utils/comperssImage.js"; //压缩图片
import { judgeTime } from "@/utils/base.js"; //时间显示
import { Picker } from "emoji-mart-vue"; //引入表情组件
import SockJS from  'sockjs-client';
import  Stomp from 'stompjs';

实例化与订阅

init(){ 
      axios.defaults.baseURL=this.websocketUrl
      axios.defaults.headers={ "X-Access-Token": Vue.ls.get(ACCESS_TOKEN)}
      if (typeof WebSocket === "undefined") {
        alert("您的浏览器不支持socket");
      } else {
        console.log(this.websocketUrl)
        // 实例化
        let socket = new SockJS(this.websocketUrl + "/jeecg-boot/websocket",undefined, { timeout: 10000 });
        this.stompClient = Stomp.over(socket);
        this.$store.commit("setMyWebsocket", this.stompClient);
        let that = this;
        console.log(Vue.ls.get(ACCESS_TOKEN))
        // 订阅
        this.stompClient.connect(
          { "X-Access-Token": Vue.ls.get(ACCESS_TOKEN) }, 
          function (frame) {
            that.isConnent = true;
            that.stompClient.subscribe(
              "/topic/" + that.chatRoomId,
              function (response) {
                let data = response.body;
                that.$store.commit("setMyGroupCPContent", data);
                console.log("小屋" + response.body);
              }
            );
          }
        )
      }
    },

计算属性

computed: {
    ...mapGetters({
      userInfo: "getUserInfo", //获取用户信息
      websocketUrl: "getWebsocketUrl",
      // stompClient: "getWebsocket", //获取websocket
      // sendContent: "getSendContent", //获取实时聊天信息
      sendContent: "getGroupCPContent", //获取实时聊天信息
    }),
  },

监听收到消息

watch: {
    sendContent() {
      debugger
      Toast.clear();
      let datas = JSON.parse(this.sendContent);
      if (datas.code == 500) {
        Dialog.alert({
          message: datas.message,
        }).then(() => {
          // on close
          this.$router.go(-1);
        });
        return;
      }
      this.message = "";
      this.quoteText = "";
      this.quoteValue = null;
      this.smileShow = false;
      let mesItem = datas.result;
      mesItem.isOprate = false;
      if (mesItem.messageType == "6") {
        mesItem.poster = this.getImgView(mesItem.messageValue);
      }
      if (mesItem.quoteValue) {
        mesItem.quoteValue = JSON.parse(mesItem.quoteValue);
      }
      if (mesItem.delFlag == "1") {
        if (
          mesItem.memberId == this.myInfo.id &&
          mesItem.chatRoomId == this.chatRoomId
        ) {
          Dialog.alert({
            message: "您已被踢出群聊,暂时不能参与会话!",
            confirmButtonText: "返回到首页",
          }).then(() => {
            // on close
            this.$router.replace("/index");
          });
        }
      } else {
        let time = mesItem.createTime;
        if (mesItem.chatRoomId == this.chatRoomId) {
          if (
            this.talkArr.length > 0 &&
            new Date(time.replace(/\-/g, "/")).getTime() -
              new Date(
                this.talkArr[this.talkArr.length - 1].createTime.replace(
                  /\-/g,
                  "/"
                )
              ).getTime() <=
              5 * 60 * 1000
          ) {
            this.talkArr[this.talkArr.length - 1].mesList.push(mesItem);
          } else {
            this.talkArr.push({
              createTime: time,
              time: judgeTime(time),
              mesList: [mesItem],
            });
          }
          console.log(this.talkArr)
          debugger
          this.seeMsg();
          if (!this.isLoading) {
            this.scrollBottom();
          }
        }
      }
    },
  },

封装的发送消息的公共方法

sendFun(messageType, messageValue, quoteValue) {
      // console.log(messageValue)
      Toast.loading({
        duration: 0, // 持续展示 toast
        message: "发送中...",
        forbidClick: true,
      });
      let date = new Date();
      let createTime = date.getTime();
      let prm=JSON.stringify({
          chatRoomId: this.chatRoomId, //群聊id
          messageType: messageType, //1文字,5图片,6视频,7求手机号、8求微信号、9发手机号、10发微信号
          messageValue: messageValue, //发送信息
          // userPhone: this.myInfo.userPhone, //用户手机号
          // fullName: this.myInfo.fullName, //用户名
          // userIcon: this.myInfo.userIcon, //用户头像
          // memberId: this.myInfo.id, //用户id
          memberId: sessionStorage.getItem('clientId'), //用户id
          // createTime: createTime, //创建时间
          // sex: this.myInfo.sex, //性别
          quoteValue: JSON.stringify(quoteValue), //引用的内容
        })
      console.log(prm)
      this.stompClient.send(
        "/app/chatRoom",
        {},
        prm
      );
    },

发送消息

//发送消息
    sendMsg() {
      const that = this;
      if (this.message) {
        this.sendFun("1", this.message, this.quoteValue);
        // that.message = "";
        // that.quoteText = "";
        // that.quoteValue = null;
      } else {
        Toast("内容不能为空!");
      }
    },

完整的代码

聊天的完整代码,包括websocket的应用,(实例化,存vuex,订阅,收发消息),表情,上传图片,视频,历史记录,时间显示,长链接的建立与销毁等

<template>
  <div class="groupChatInf">
    <!-- <NavBar :title="title"></NavBar> -->
    <div class="chatInf">
      <div class="chatBox" id="chat">
        <van-pull-refresh v-model="isLoading" @refresh="onRefresh">
          <div class="chatOne" v-for="(item, i) in talkArr" :key="i">
            <div class="date">{{ item.time }}</div>
            <div v-for="(items, is) in item.mesList" :key="is">
              <div
                class="welcome"
                v-html="welcome"
                v-if="welcome && items.messageType == '4'"
              ></div>
              <div v-if="items.messageType != '4'">
                <div
                  class="chatMine"
                  v-if="items.userPhone == myInfo.userPhone"
                >
                  <div
                    class="chatImg"
                    v-if="
                      items.messageType != '12' && items.messageType != '15'
                    "
                  >
                    <van-image
                      :src="items.userIcon"
                      fit="cover"
                      v-if="items.userIcon"
                      ><template v-slot:loading>
                        <van-loading type="spinner" size="20" />
                      </template>
                      <template v-slot:error>加载失败</template></van-image
                    >
                    <img
                      src="~@/assets/img/redGirl.png"
                      alt=""
                      v-else-if="items.memberId == 9999999"
                      class="imgs"
                    />
                    <img
                      src="~@/assets/img/girlHeadImg.png"
                      alt=""
                      v-else-if="items.sex == 'F'"
                      class="imgs"
                    />
                    <img
                      src="~@/assets/img/boyHeadImg.png"
                      alt=""
                      class="imgs"
                      v-else
                    />
                  </div>
                  <div
                    class="chatName"
                    v-if="
                      items.messageType != '12' && items.messageType != '15'
                    "
                  ><i v-if="items.isAdmin == '1'"
                      ><img src="~@/assets/img/manage2.png"
                    /></i>
                    <i v-if="items.isAdmin == '2'"
                      ><img src="~@/assets/img/manage1.png"
                    /></i>
                  </div>
                  <div class="chatvalueDiv" v-if="items.messageType == '1'">
                    <div
                      class="chatText"
                      @touchstart="touchstart(items)"
                      @touchend="touchend"
                    >
                      {{ items.messageValue }}
                    </div>
                    <div
                      class="chatOprateMask"
                      v-if="items.isOprate"
                      @click="closeMask(items)"
                    ></div>
                    <div class="chatOprate" v-if="items.isOprate">
                      <span @click="quoteChat(items)">引用</span>
                      <span @click="delChat(items)">删除</span>
                      <span @click="copyChat(items)">复制</span>
                      <span @click="recallChat(items)">撤回</span>
                    </div>
                  </div>
                  <div class="chatvalueDiv" v-if="items.messageType == '5'">
                    <div
                      class="chatImage"
                      @click="imgPreview(items.messageValue)"
                    >
                      <img :src="items.messageValue" alt="" />
                    </div>
                    <div
                      class="chatOprateMask"
                      v-if="items.isOprate"
                      @click="closeMask(items)"
                    ></div>
                    <div class="chatOprate" v-if="items.isOprate">
                      <span @click="quoteChat(items)">引用</span>
                      <span @click="delChat(items)">删除</span>
                      <span @click="copyChat(items)">复制</span>
                      <span @click="recallChat(items)">撤回</span>
                    </div>
                  </div>
                  <div class="chatvalueDiv" v-if="items.messageType == '6'">
                    <div class="chatVideo">
                      <video
                        :src="items.messageValue"
                        :poster="items.poster"
                      ></video>
                      <div
                        class="chatmask"
                        @click="videoPlay(items.messageValue, items.poster)"
                      >
                        <img src="~@/assets/bofang.png" alt="" />
                      </div>
                    </div>
                    <div
                      class="chatOprateMask"
                      v-if="items.isOprate"
                      @click="closeMask(items)"
                    ></div>
                    <div class="chatOprate" v-if="items.isOprate">
                      <span @click="quoteChat(items)">引用</span>
                      <span @click="delChat(items)">删除</span>
                      <span @click="copyChat(items)">复制</span>
                      <span @click="recallChat(items)">撤回</span>
                    </div>
                  </div>
                  <div
                    class="chatvalueDiv"
                    v-if="items.quoteValue && items.messageType != '12'"
                  >
                    <div
                      class="quoteValue"
                      v-if="items.quoteValue.messageType == '1'"
                    >
                      @{{
                        items.quoteValue.fullName +
                        items.quoteValue.messageValue
                      }}
                    </div>
                    <div
                      class="quoteImg"
                      v-else-if="items.quoteValue.messageType == '5'"
                    >
                      <p>@{{ items.quoteValue.fullName }}</p>
                      <img
                        :src="items.quoteValue.messageValue"
                        alt=""
                        class="qimgs"
                        @click="imgPreview(items.quoteValue.messageValue)"
                      />
                    </div>
                    <div
                      class="quoteImg"
                      v-else-if="items.quoteValue.messageType == '6'"
                    >
                      <p>@{{ items.quoteValue.fullName }}</p>
                      <div class="quoteVideo">
                        <video
                          :src="items.quoteValue.messageValue"
                          :poster="items.quoteValue.poster"
                        ></video>
                        <div
                          class="chatmask"
                          @click="
                            videoPlay(
                              items.quoteValue.messageValue,
                              items.quoteValue.poster
                            )
                          "
                        >
                          <img src="~@/assets/bofang.png" alt="" />
                        </div>
                      </div>
                    </div>
                  </div>
                  <div class="chatRecall" v-if="items.messageType == '12'">
                    消息已撤回 <a @click="reEdit(items)">重新编辑</a>
                  </div>
                </div>
                <div class="chatOther" v-else>
                  <div
                    class="chatImg"
                    @click="toPerson(items.memberId)"
                    v-if="
                      items.messageType != '12' && items.messageType != '15'
                    "
                  >
                    <van-image
                      :src="items.userIcon"
                      fit="cover"
                      v-if="items.userIcon"
                      ><template v-slot:loading>
                        <van-loading type="spinner" size="20" />
                      </template>
                      <template v-slot:error>加载失败</template></van-image
                    >
                    <img
                      src="~@/assets/img/girlHeadImg.png"
                      alt=""
                      v-else-if="items.sex == 'F'"
                      class="imgs"
                    />
                    <img
                      src="~@/assets/img/boyHeadImg.png"
                      alt=""
                      class="imgs"
                      v-else
                    />
                    <img
                      src="~@/assets/img/woman.png"
                      alt=""
                      class="sex"
                      v-if="items.sex == 'F'"
                    />
                    <img
                      src="~@/assets/img/man.png"
                      alt=""
                      class="sex"
                      v-if="items.sex == 'M'"
                    />
                  </div>
                  <div
                    class="chatName"
                    v-if="
                      items.messageType != '12' && items.messageType != '15'
                    "
                  >
                    {{ items.fullName
                    }}<i v-if="items.isAdmin == '1'"
                      ><img src="~@/assets/img/manage2.png"
                    /></i>
                    <i v-if="items.isAdmin == '2'"
                      ><img src="~@/assets/img/manage1.png"
                    /></i>
                  </div>
                  <div class="chatvalueDiv" v-if="items.messageType == '1'">
                    <div
                      class="chatText"
                      @touchstart="touchstart(items)"
                      @touchend="touchend"
                    >
                      {{ items.messageValue }}
                    </div>
                    <div
                      class="chatOprateMask"
                      v-if="items.isOprate"
                      @click="closeMask(items)"
                    ></div>
                    <div class="chatOprate" v-if="items.isOprate">
                      <span @click="quoteChat(items)">引用</span>
                      <span @click="delChat(items)">删除</span>
                      <span @click="copyChat(items)">复制</span>
                    </div>
                  </div>
                  <div class="chatvalueDiv" v-if="items.messageType == '5'">
                    <div
                      class="chatImage"
                      @click="imgPreview(items.messageValue)"
                    >
                      <img :src="items.messageValue" alt="" />
                    </div>
                    <div
                      class="chatOprateMask"
                      v-if="items.isOprate"
                      @click="closeMask(items)"
                    ></div>
                    <div class="chatOprate" v-if="items.isOprate">
                      <span @click="quoteChat(items)">引用</span>
                      <span @click="delChat(items)">删除</span>
                      <span @click="copyChat(items)">复制</span>
                    </div>
                  </div>
                  <div class="chatvalueDiv" v-if="items.messageType == '6'">
                    <div class="chatVideo">
                      <video
                        :src="items.messageValue"
                        :poster="items.poster"
                      ></video>
                      <div
                        class="chatmask"
                        @click="videoPlay(items.messageValue, items.poster)"
                      >
                        <img src="~@/assets/bofang.png" alt="" />
                      </div>
                    </div>
                    <div
                      class="chatOprateMask"
                      v-if="items.isOprate"
                      @click="closeMask(items)"
                    ></div>
                    <div class="chatOprate" v-if="items.isOprate">
                      <span @click="quoteChat(items)">引用</span>
                      <span @click="delChat(items)">删除</span>
                      <span @click="copyChat(items)">复制</span>
                    </div>
                  </div>
                  <div
                    class="chatvalueDiv"
                    v-if="items.quoteValue && items.messageType != '12'"
                  >
                    <div
                      class="quoteValue"
                      v-if="items.quoteValue.messageType == '1'"
                    >
                      @{{
                        items.quoteValue.fullName +
                        items.quoteValue.messageValue
                      }}
                    </div>
                    <div
                      class="quoteImg"
                      v-else-if="items.quoteValue.messageType == '5'"
                    >
                      <p>@{{ items.quoteValue.fullName }}</p>
                      <img
                        :src="items.quoteValue.messageValue"
                        alt=""
                        class="qimgs"
                        @click="imgPreview(items.quoteValue.messageValue)"
                      />
                    </div>
                    <div
                      class="quoteImg"
                      v-else-if="items.quoteValue.messageType == '6'"
                    >
                      <p>@{{ items.quoteValue.fullName }}</p>
                      <div class="quoteVideo">
                        <video
                          :src="items.quoteValue.messageValue"
                          :poster="items.quoteValue.poster"
                        ></video>
                        <div
                          class="chatmask"
                          @click="
                            videoPlay(
                              items.quoteValue.messageValue,
                              items.quoteValue.poster
                            )
                          "
                        >
                          <img src="~@/assets/bofang.png" alt="" />
                        </div>
                      </div>
                    </div>
                  </div>
                  <div class="chatRecall" v-if="items.messageType == '12'">
                    {{ items.fullName }}撤回了一条消息
                  </div>
                  <div class="chatRecall" v-if="items.messageType == '15'">
                    <a>{{ items.createBy }}</a
                    >加入了群聊
                  </div>
                </div>
              </div>
            </div>
          </div>
        </van-pull-refresh>
      </div>
    </div>
    <div class="sendBox">
      <div class="sendBoxTop">
        <form>
          <!-- @click="toMember" -->
          <span class="sendMember" >
            <img src="~@/assets/img/member.png" alt="" />
          </span>
          <van-field
            class="sendInf"
            v-model="message"
            :border="false"
            style="border: 0px;"
            @keyup.enter.native="sendMsg"
          />
          <div class="quoteMessage" v-if="quoteText">
            <p class="quoteText">{{ quoteText }}</p>
            <img
              src="~@/assets/img/mclose.png"
              alt=""
              class="quoteClose"
              @click="quoteClose"
            />
          </div>
          <span class="sendExpression" @click="smile">
            <img src="~@/assets/img/smile.png" alt="" />
          </span>
          <span class="sendBtn" @click="sendMsg"
            ><img src="~@/assets/img/sendBtn.png" alt=""
          /></span>
        </form>
      </div>
      <div class="emojiBox" @select="addEmoji" v-if="smileShow">
        <div class="emojiDiv">
          <span
            v-for="(item, index) in emojiList"
            :key="'e-' + index"
            @click="chooseEmoji(item)"
            >{{ item.emoji }}</span
          >
        </div>
      </div>
      <!-- <picker
        :include="['people', 'Smileys']"
        :showSearch="false"
        :showPreview="false"
        :showCategories="false"
        @select="addEmoji"
        v-if="smileShow"
      /> -->
      <div class="sendBoxBtm" v-if="!smileShow">
        <div class="sendOprate">
          <img src="~@/assets/img/micon1.png" alt="" />
          <van-uploader
            :after-read="afterRead"
            upload-icon="plus"
            :before-read="beforeRead"
            accept="image/png,image/jpg,image/jpeg,video/mp4,video/mov"
            :max-count="1"
          />
        </div>
        <div class="sendOprate">
          <img src="~@/assets/img/micon2.png" alt="" />
          <!--是苹果手机直接调用摄像头-->
          <van-uploader
            :after-read="afterReadVideo"
            upload-icon="plus"
            :before-read="beforeReadVideo"
            accept="image/png,image/jpg,image/jpeg,video/mp4,video/mov"
            :max-count="1"
            capture="camera"
            v-if="isIos"
          />
          <!--不是苹果手机先调起相册-->
          <van-uploader
            :after-read="afterReadVideo"
            upload-icon="plus"
            :before-read="beforeReadVideo"
            accept="image/png,image/jpg,image/jpeg,video/mp4,video/mov"
            :max-count="1"
            v-if="!isIos"
          />
        </div>
        <div class="sendOprate">
          <img src="~@/assets/img/nowx.png" alt="" />
        </div>
        <div class="sendOprate">
          <img src="~@/assets/img/nophone.png" alt="" />
        </div>
      </div>
    </div>
    <Video
      :videoPath="videoPath"
      v-if="videoShow"
      @videoFun="videoFun"
      :posterPath="posterPath"
    ></Video>
  </div>
</template>

<script>
import {
  Image as vanImage,
  Toast,
  PullRefresh,
  Field,
  Loading,
  Dialog,
  ImagePreview,
  Uploader,
} from "vant";
import { mapGetters } from "vuex";
import Video from "@/components/video"; //播放
import { imgPreview } from "@/utils/comperssImage.js"; //压缩图片
import { judgeTime } from "@/utils/base.js"; //时间显示
import { Picker } from "emoji-mart-vue"; //引入表情组件
import SockJS from  'sockjs-client';
import  Stomp from 'stompjs';
import Vue from 'vue'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import axios from 'axios'

export default {
  name: "GroupChat",
  components: {
    [vanImage.name]: vanImage,
    [Field.name]: Field,
    [PullRefresh.name]: PullRefresh,
    [Loading.name]: Loading,
    [Uploader.name]: Uploader,
    Video,
    Picker,
  },
  props:{
    records:{
      type: Object,
      default: ()=>({})
    }
  },
  data() {
    return {
      stompClient:"",
      message: "", //发送内容
      talkArr: [], //聊天信息
      title: "聊天", //标题
      isToday: false, //是否是今天
      isLoading: false, //是否在加载中
      params: {
        chatRoomId: "", //群聊id
        pageNo: 1, //页数
        pageSize: 50, //每页数据数量
        oldMesId: "", //最后一条消息id
      }, //群聊查询历史数据参数
      chatRoomId: "", //群聊id
      allPage: 0, //历史消息总页数
      myInfo: {}, //个人信息
      welcome: "", //欢迎信息
      isFirst: true, //是否首次进入
      quoteText: "", //引用的文字
      videoShow: false, //播放器插件是否出现
      videoPath: "", //视频路径
      posterPath: "", //默认图路径
      touch: null, //长按方法定时器
      reMessage: "", //要重新编辑的信息
      fileList: [], //上传图片视频返回值
      videoList: [], //拍摄返回值
      isIos: false, //是否是苹果手机
      quoteValue: null, //引用的详细信息
      isClick: true, //操作按钮是否可以点击
      smileShow: false, //
      imgUrl:'',
    };
  },
  computed: {
    ...mapGetters({
      userInfo: "getUserInfo", //获取用户信息
      websocketUrl: "getWebsocketUrl",
      // stompClient: "getWebsocket", //获取websocket
      // sendContent: "getSendContent", //获取实时聊天信息
      sendContent: "getGroupCPContent", //获取实时聊天信息
    }),
  },
  watch: {
    sendContent() {
      debugger
      Toast.clear();
      let datas = JSON.parse(this.sendContent);
      if (datas.code == 500) {
        Dialog.alert({
          message: datas.message,
        }).then(() => {
          // on close
          this.$router.go(-1);
        });
        return;
      }
      this.message = "";
      this.quoteText = "";
      this.quoteValue = null;
      this.smileShow = false;
      let mesItem = datas.result;
      mesItem.isOprate = false;
      if (mesItem.messageType == "6") {
        mesItem.poster = this.getImgView(mesItem.messageValue);
      }
      if (mesItem.quoteValue) {
        mesItem.quoteValue = JSON.parse(mesItem.quoteValue);
      }
      if (mesItem.delFlag == "1") {
        if (
          mesItem.memberId == this.myInfo.id &&
          mesItem.chatRoomId == this.chatRoomId
        ) {
          Dialog.alert({
            message: "您已被踢出群聊,暂时不能参与会话!",
            confirmButtonText: "返回到首页",
          }).then(() => {
            // on close
            this.$router.replace("/index");
          });
        }
      } else {
        let time = mesItem.createTime;
        if (mesItem.chatRoomId == this.chatRoomId) {
          if (
            this.talkArr.length > 0 &&
            new Date(time.replace(/\-/g, "/")).getTime() -
              new Date(
                this.talkArr[this.talkArr.length - 1].createTime.replace(
                  /\-/g,
                  "/"
                )
              ).getTime() <=
              5 * 60 * 1000
          ) {
            this.talkArr[this.talkArr.length - 1].mesList.push(mesItem);
          } else {
            this.talkArr.push({
              createTime: time,
              time: judgeTime(time),
              mesList: [mesItem],
            });
          }
          console.log(this.talkArr)
          debugger
          this.seeMsg();
          if (!this.isLoading) {
            this.scrollBottom();
          }
        }
      }
    },
  },
  created() {
    this.imgUrl=this.websocketUrl+'/jeecg-boot/sys/common/static'
    const that = this;
    const UA = navigator.userAgent;
    const isIpad = /(iPad).*OS\s([\d_]+)/.test(UA);
    const isIpod = /(iPod)(.*OS\s([\d_]+))?/.test(UA);
    const isIphone = !isIpad && /(iPhone\sOS)\s([\d_]+)/.test(UA);
    this.isIos = isIpad || isIpod || isIphone;
    if (this.$route.query.activityName) {
      this.title = this.$route.query.activityName;
    }
    this.type = this.$route.query.type;
    if (this.$route.query.activityId) {
      this.chatRoomId = this.$route.query.activityId;
      this.params.chatRoomId = this.$route.query.activityId;
    }
    console.log(this.records)
    this.chatRoomId = this.records.chatId;
    this.params.chatRoomId = this.records.chatId;
    this.init()
    // this.getpersonfo();
    //查看未读消息
    this.seeMsg();
    //获取历史消息
    this.getHistory();
    this.getEmoji();
  },
  // mounted() {},
  destroyed() {
    // 取消订阅
    this.stompClient.unsubscribe('/topic/'+this.chatRoomId)
    // 销毁连接
    this.stompClient.disconnect()
  }, 
  methods: {
    // 初始化
    init(){
      axios.defaults.baseURL=this.websocketUrl
      axios.defaults.headers={ "X-Access-Token": Vue.ls.get(ACCESS_TOKEN)}
      if (typeof WebSocket === "undefined") {
        alert("您的浏览器不支持socket");
      } else {
        console.log(this.websocketUrl)
        let socket = new SockJS(this.websocketUrl + "/jeecg-boot/websocket",undefined, { timeout: 10000 });
        this.stompClient = Stomp.over(socket);
        this.$store.commit("setMyWebsocket", this.stompClient);
        let that = this;
        console.log(Vue.ls.get(ACCESS_TOKEN))
        this.stompClient.connect(
          { "X-Access-Token": Vue.ls.get(ACCESS_TOKEN) }, 
          function (frame) {
            that.isConnent = true;
            that.stompClient.subscribe(
              "/topic/" + that.chatRoomId,
              function (response) {
                let data = response.body;
                that.$store.commit("setMyGroupCPContent", data);
                console.log("小屋" + response.body);
              }
            );
          }
        )
      }
    },
    //获取表情包
    getEmoji() {
      axios.get("/jeecg-boot/api/emoji/emojiList")
        .then((res) => {
          if (res.code == 200) {
            this.emojiList = res.result;
          }
        })
        .catch(() => {});
    },
    //获取个人资料
    getpersonfo() {
      axios.get("/jeecg-boot/api/member/memberDetails")
        .then((res) => {
          if (res.code == 200) {
            this.myInfo = res.result;
          }
        })
        .catch(() => {});
    },
    //发送消息
    sendMsg() {
      const that = this;
      if (this.message) {
        this.sendFun("1", this.message, this.quoteValue);
        // that.message = "";
        // that.quoteText = "";
        // that.quoteValue = null;
      } else {
        Toast("内容不能为空!");
      }
    },
    //查看未读消息
    seeMsg() {
      axios.get("/jeecg-boot/api/releaseLog/seePcReleaseLog", {params:{
        type: 2,
        chatRoomId: this.chatRoomId,
      }})
        .then((res) => {})
        .catch(() => {});
    },
    //刷新历史消息
    onRefresh() {
      this.getHistory();
      // if (this.params.pageNo <= this.allPage) {
      //   this.getHistory();
      // } else {
      //   this.isLoading = false;
      // }
    },
    //查看历史消息
    getHistory() {
      const that = this;
      axios.get("/jeecg-boot/api/memberMessage/historyCpMsgList", {params:this.params} )
        .then((res) => {
          // console.log(res);
          res=res.data
          if (res.result.records.length == 0) {
            if (!this.isFirst) {
              Toast("没有更多数据了");
            }
          }
          res.result.records.forEach((item) => {
            let mesItem = item;
            let time = mesItem.createTime;
            if (mesItem.messageType == "6") {
              mesItem.poster = that.getImgView(mesItem.messageValue);
            }
            mesItem.isOprate = false;
            if (mesItem.quoteValue) {
              mesItem.quoteValue = JSON.parse(mesItem.quoteValue);
            }
            if (mesItem.chatRoomId == this.chatRoomId) {
              if (mesItem.messageType == "4") {
                this.welcome = mesItem.messageValue.replace(/\r\n/g, "<br>");
                this.welcome = this.welcome.replace(/\n/g, "<br>");
              }
              if (this.talkArr.length > 0) {
                let time1 = new Date(time.replace(/\-/g, "/")).getTime();
                let time2 = new Date(
                  that.talkArr[0].createTime.replace(/\-/g, "/")
                ).getTime();
                let diff = 0;
                if (parseInt(time1) >= parseInt(time2)) {
                  diff = parseInt(time1) - parseInt(time2);
                } else {
                  diff = parseInt(time2) - parseInt(time1);
                }
                if (diff <= 5 * 60 * 1000) {
                  that.talkArr[0].mesList.unshift(mesItem);
                } else {
                  that.talkArr.unshift({
                    createTime: time,
                    time: judgeTime(time),
                    mesList: [mesItem],
                  });
                }
              } else {
                that.talkArr.unshift({
                  createTime: time,
                  time: judgeTime(time),
                  mesList: [mesItem],
                });
              }
            }
          });
          // Toast("刷新成功");
          this.isLoading = false;
          this.allPage = res.result.pages;
          this.params.oldMesId =
            res.result.records[res.result.records.length - 1].id;
          if (this.isFirst) {
            that.isFirst = false;
            that.scrollBottom();
          }
        })
        .catch(() => {});
    },
    //去个人主页
    toPerson(id) {
      if (id) {
        this.$router.push({ path: "/person", query: { id: id } });
      }
    },
    //去聊天室信息页面
    toMember() {
      this.$router.push({
        path: "/groupMember",
        query: { activityId: this.chatRoomId },
      });
    },
    scrollBottom() {
      this.$nextTick((res) => {
        let div = document.getElementById("chat");
        div.scrollTop = div.scrollHeight;
      });
    },

    //选择表情
    smile() {
      this.smileShow = !this.smileShow;
    },
    //查看大图
    imgPreview(src) {
      // ImagePreview([src]);
    },
    //视频播放
    videoPlay(path, poster) {
      this.videoPath = path;
      this.posterPath = poster;
      this.videoShow = true;
    },
    //
    videoFun(visible) {
      this.videoShow = visible;
      this.posterPath = "";
      this.videoPath = "";
    },
    /* 视频取第一秒截图 */
    getImgView(text) {
      return text + "?x-oss-process=video/snapshot,t_1000";
    },
    // 在屏幕上时触发
    touchstart(items) {
      clearTimeout(this.touch); //再次清空定时器,防止重复注册定时器
      this.touch = setTimeout(() => {
        items.isOprate = true;
        this.$forceUpdate();
      }, 800);
    },
    //离开屏幕时触发
    touchend() {
      clearTimeout(this.touch); //再次清空定时器,防止重复注册定时器
    },
    //点击任意地方关闭操作
    closeMask(items) {
      items.isOprate = false;
      this.$forceUpdate();
    },
    //重新编辑
    reEdit() {
      this.message = this.reMessage;
    },
    //引用
    quoteChat(items) {
      if (items.messageType == "1") {
        this.quoteText = "@" + items.fullName + " " + items.messageValue;
      } else if (items.messageType == "5") {
        this.quoteText = "@" + items.fullName + " [图片]";
      } else if (items.messageType == "6") {
        this.quoteText = "@" + items.fullName + " [视频]";
      }
      this.quoteValue = items;
      items.isOprate = false;
      this.$forceUpdate();
    },
    //清楚引用
    quoteClose() {
      this.quoteText = "";
      this.quoteValue = null;
    },
    //删除
    delChat(items) {
      if (this.isClick) {
        this.isClick = false;
        this.$fetch("/jeecg-boot/api/memberMessage/delMessageLog", {
          messageLogId: items.id,
        })
          .then((res) => {
            if (res.code == 200) {
              Toast("消息删除成功");
              items.isOprate = false;
              items.delFlag = "1";
              this.$forceUpdate();
            } else {
              Toast(res.message);
            }
            this.isClick = true;
          })
          .catch(() => {
            this.isClick = true;
          });
      }
    },
    //复制
    copyChat(items) {
      this.copeText(items);
    },
    //撤回
    recallChat(items) {
      if (this.isClick) {
        this.isClick = false;
        this.$fetch("/jeecg-boot/api/memberMessage/delMessageLog", {
          messageLogId: items.id,
          messageType: 12,
        })
          .then((res) => {
            if (res.code == 200) {
              Toast("消息撤回成功");
              items.isOprate = false;
              items.messageType = "12";
              this.$forceUpdate();
            } else {
              Toast(res.message);
            }
            this.isClick = true;
          })
          .catch(() => {
            this.isClick = true;
          });
      }
    },
    //复制消息
    copeText(items) {
      let that = this;
      // 数字没有 .length 不能执行selectText 需要转化成字符串
      const textString = items.messageValue.toString();
      let input = document.querySelector("#copy-input");
      if (!input) {
        input = document.createElement("input");
        input.id = "copy-input";
        input.readOnly = "readOnly"; // 防止ios聚焦触发键盘事件
        input.style.position = "absolute";
        input.style.left = "-1000px";
        input.style.zIndex = "-1000";
        document.body.appendChild(input);
      }
      input.value = textString;
      // ios必须先选中文字且不支持 input.select();
      this.selectText(input, 0, textString.length);
      if (document.execCommand("copy")) {
        document.execCommand("copy");
        console.log("复制成功");
        Toast("复制成功!");
        items.isOprate = false;
        that.$forceUpdate();
      } else {
        console.log("不兼容");
      }
      input.blur();
    },
    // input自带的select()方法在苹果端无法进行选择,所以需要自己去写一个类似的方法
    // 选择文本。createTextRange(setSelectionRange)是input方法
    selectText(textbox, startIndex, stopIndex) {
      if (textbox.createTextRange) {
        // ie
        const range = textbox.createTextRange();
        range.collapse(true);
        range.moveStart("character", startIndex); // 起始光标
        range.moveEnd("character", stopIndex - startIndex); // 结束光标
        range.select(); // 不兼容苹果
      } else {
        // firefox/chrome
        textbox.setSelectionRange(startIndex, stopIndex);
        textbox.focus();
      }
    },
    // 上传图片
    async afterRead(fileList) {
      Toast.loading({
        duration: 0, // 持续展示 toast
        message: "发送中...",
        forbidClick: true,
      });
      let that = this;
      let formData = new FormData(); //构造一个 FormData,把后台需要发送的参数添加
      if (fileList.file.type.indexOf("video") == 0) {
        formData.append("file", fileList.file); //接口需要传的参数
        formData.append("biz", fileList.file.name.split(".")[0]);
        await axios.post("/jeecg-boot/sys/common/uploadCp", formData)
          .then((res) => {
            Toast.clear();
            if (res.data.code == 0) {
              let imgsrc = res.data.message;
              that.sendFun("6", imgsrc);
            } else {
              Toast("发送失败");
            }
          });
      } else {
        imgPreview(fileList.file, async (files) => {
          // console.log(files)
          formData.append("file", files); //接口需要传的参数
          formData.append("biz", files.name.split(".")[0]);
          await axios.post("/jeecg-boot/sys/common/uploadCp", formData)
            .then((res) => {
              Toast.clear();
              if (res.data.code == 0) {
                let imgsrc = this.imgUrl+res.data.message;
                that.sendFun("5", imgsrc);
              } else {
                Toast("发送失败");
              }
            });
        });
      }
    },
    beforeRead(fileList) {
      if (
        fileList.type.indexOf("image") != 0 &&
        fileList.type.indexOf("video") != 0
      ) {
        Toast("只能上传图片(jpg,jpeg,png)、视频(mp4,mov)");
        return false;
      }
      let fileListLength = fileList.length ? fileList.length : 1;
      if (fileListLength + this.fileList.length > 1) {
        Toast("最多同时上传一张图片或视频");
        return false;
      }
      return true;
    },
    // 拍摄
    async afterReadVideo(fileList) {
      Toast.loading({
        duration: 0, // 持续展示 toast
        message: "发送中...",
        forbidClick: true,
      });
      let that = this;
      let formData = new FormData(); //构造一个 FormData,把后台需要发送的参数添加
      if (fileList.file.type.indexOf("video") == 0) {
        formData.append("file", fileList.file); //接口需要传的参数
        formData.append("biz", fileList.file.name.split(".")[0]);
        await axios.post("/jeecg-boot/sys/common/uploadCp", formData)
          .then((res) => {
            Toast.clear();
            if (res.data.code == 0) {
              let imgsrc = res.data.message;
              that.sendFun("6", imgsrc);
            } else {
              Toast("发送失败");
            }
          });
      } else {
        imgPreview(fileList.file, async (files) => {
          // console.log(files)
          formData.append("file", files); //接口需要传的参数
          formData.append("biz", files.name.split(".")[0]);
          await axios.post("/jeecg-boot/sys/common/uploadCp", formData)
            .then((res) => {
              Toast.clear();
              if (res.data.code == 0) {
                let imgsrc =this.imgUrl+ res.data.message;
                that.sendFun("5", imgsrc);
              } else {
                Toast("发送失败");
              }
            });
        });
      }
    },
    beforeReadVideo(fileList) {
      if (
        fileList.type.indexOf("image") != 0 &&
        fileList.type.indexOf("video") != 0
      ) {
        Toast("只能上传图片(jpg,jpeg,png)、视频(mp4,mov)");
        return false;
      }
      let fileListLength = fileList.length ? fileList.length : 1;
      if (fileListLength + this.fileList.length > 1) {
        Toast("最多同时上传一张图片或视频");
        return false;
      }
      return true;
    },
    //发送方法
    sendFun(messageType, messageValue, quoteValue) {
      // console.log(messageValue)
      Toast.loading({
        duration: 0, // 持续展示 toast
        message: "发送中...",
        forbidClick: true,
      });
      let date = new Date();
      let createTime = date.getTime();
      let prm=JSON.stringify({
          chatRoomId: this.chatRoomId, //群聊id
          messageType: messageType, //1文字,5图片,6视频,7求手机号、8求微信号、9发手机号、10发微信号
          messageValue: messageValue, //发送信息
          // userPhone: this.myInfo.userPhone, //用户手机号
          // fullName: this.myInfo.fullName, //用户名
          // userIcon: this.myInfo.userIcon, //用户头像
          // memberId: this.myInfo.id, //用户id
          memberId: sessionStorage.getItem('clientId'), //用户id
          // createTime: createTime, //创建时间
          // sex: this.myInfo.sex, //性别
          quoteValue: JSON.stringify(quoteValue), //引用的内容
        })
      console.log(prm)
      this.stompClient.send(
        "/app/chatRoom",
        {},
        prm
      );
    },
    //添加表情
    addEmoji(e) {
      this.message += e.native;
    },
    //选择表情
    chooseEmoji(item) {
      this.message += item.emoji;
    },
  },
  beforeDestroy() {
    Toast.clear();
  },
  updated() {},
};
</script>

<style lang="scss" scoped>
.groupChatInf {
  position: relative;
  .chatBox {
    // position: relative;
    height: calc(100vh - 320px);
    overflow-y: auto;
  }
  /deep/.van-pull-refresh {
    // min-height: 300px;
    min-height: calc(100vh - 320px);
    .van-pull-refresh__track {
      padding: 0px 10px;
    }
  }
  // ::v-deep .van-pull-refresh {
  //   min-height: 300px;
  // }
  .welcome {
    text-align: center;
    font-size: 14px;
    color: #666;
    padding: 6px 10px;
  }
  .chatInf {
    width: 100%;
    padding: 0px 0px 70px 0px;
    .chatOne {
      .date {
        text-align: center;
        line-height: 30px;
        font-size: 12px;
        color: #999;
      }
      .chatMine,
      .chatOther {
        position: relative;
        margin-bottom: 10px;
        padding: 0px 50px;
        min-height: 40px;
        position: relative;
        .chatImg {
          width: 38px;
          height: 38px;
          position: absolute;
          top: 0px;
          .imgs {
            width: 100%;
            height: 100%;
            border-radius: 50%;
            overflow: hidden;
          }
          .van-image {
            width: 100%;
            height: 100%;
            border-radius: 50%;
            overflow: hidden;
          }
          .sex {
            width: 15px;
            position: absolute;
            right: -4px;
            bottom: 0px;
          }
        }
        .chatText {
          padding: 8px 13px;
          background: #d8d8d8;
          font-size: 14px;
          color: #333;
          line-height: 1.7;
          border-radius: 6px;
          position: relative;
        }
        .chatName {
          font-size: 12px;
          color: #666;
          line-height: 20px;
          img {
            width: 11px;
            position: relative;
            top: -1px;
            margin-right: 3px;
          }
        }
        .chatImage,
        .chatVideo {
          width: 124px;
          height: 124px;
          overflow: hidden;
        }
        .chatImage {
          img {
            width: 100%;
            height: 100%;
            object-fit: cover;
          }
        }
        .chatVideo {
          position: relative;
          video {
            width: 100%;
            height: 100%;
          }
          .chatmask {
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            position: absolute;
            top: 0;
            left: 0;
            img {
              width: 40px;
              height: 40px;
              position: absolute;
              top: 50%;
              left: 50%;
              transform: translate(-50%, -50%);
            }
          }
        }
        .chatvalueDiv {
          position: relative;
        }
        .chatvalueDiv:after {
          content: "";
          display: block;
          clear: both;
        }
        .quoteValue {
          padding: 5px 10px;
          font-size: 10px;
          color: #8e8e93;
          border-radius: 4px;
          background: #eeeeef;
          margin-top: 5px;
          text-overflow: -o-ellipsis-lastline;
          overflow: hidden;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-line-clamp: 2;
          line-clamp: 2;
          -webkit-box-orient: vertical;
        }
        .chatOprate {
          width: auto;
          height: 28px;
          background: #fff;
          position: absolute;
          bottom: -34px;
          z-index: 120;
          border-radius: 4px;
          display: flex;
          span {
            font-size: 13px;
            color: #333;
            line-height: 28px;
            width: 40px;
            text-align: center;
            margin: 0 10px;
          }
        }
        .chatOprateMask {
          width: 100%;
          height: 100%;
          position: fixed;
          top: 0;
          left: 0;
          background: rgba(255, 255, 255, 0);
          z-index: 111;
        }
        .quoteImg {
          padding: 5px 10px;
          border-radius: 4px;
          background: #eeeeef;
          margin-top: 5px;
          p {
            font-size: 10px;
            color: #8e8e93;
          }
          .qimgs {
            width: 90px;
            height: 60px;
            object-fit: cover;
            margin-left: 5px;
          }
          .quoteVideo {
            width: 90px;
            height: 60px;
            position: relative;
            margin-left: 5px;
            video {
              width: 100%;
              height: 100%;
            }
            .chatmask {
              width: 100%;
              height: 100%;
              background: rgba(0, 0, 0, 0.5);
              position: absolute;
              top: 0;
              left: 0;
              img {
                width: 30px;
                height: 30px;
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
              }
            }
          }
        }
      }
      .chatMine {
        .chatImg {
          right: 0px;
        }
        .chatText {
          float: right;
          background: #ff4e7b;
          border: 1px #ff4e7b solid;
          color: #fff;
        }
        .chatName {
          text-align: right;
        }
        .quoteValue {
          float: right;
        }
        .chatImage,
        .chatVideo {
          border-radius: 6px 0 6px 6px;
          float: right;
        }
        .chatOprate {
          right: 0;
        }
        .chatOprate:after {
          content: "";
          position: absolute;
          width: 0px;
          height: 0px;
          line-height: 0px; /*为了防止ie下出现题型*/
          border-bottom: 5px solid #fff;
          border-left: 5px solid transparent;
          border-right: 5px solid transparent;
          right: 10px;
          top: -5px;
        }
        .quoteImg {
          float: right;
          p {
            float: left;
          }
          .qimgs,
          .quoteVideo {
            float: right;
          }
        }
      }
      .chatOther {
        .chatImg {
          left: 0px;
        }
        .chatText {
          float: left;
          background: #fff;
          // border: 1px #eee solid;
        }
        .chatImage,
        .chatVideo {
          border-radius: 0 6px 6px 6px;
          float: left;
        }
        .quoteValue {
          float: left;
        }
        .chatOprate {
          left: 0;
        }
        .chatOprate:after {
          content: "";
          position: absolute;
          width: 0px;
          height: 0px;
          line-height: 0px; /*为了防止ie下出现题型*/
          border-bottom: 5px solid #fff;
          border-left: 5px solid transparent;
          border-right: 5px solid transparent;
          left: 10px;
          top: -5px;
        }
        .quoteImg {
          float: left;
          p {
            float: left;
          }
          .qimgs,
          .quoteVideo {
            float: right;
          }
        }
      }
      .chatMine:after,
      .chatOther:after {
        content: "";
        display: block;
        clear: both;
      }
    }
  }
  .sendBox {
    // width: 400px;
    width: 100%;
    background: #fff;
    position: absolute;
    z-index: 99;
    left: 0px;
    margin: 0 auto;
    bottom: -20px;
    border-top: 1px #eee solid;
    .sendBoxTop {
      min-height: 50px;
      width: 100%;
      padding: 6px 60px 6px 60px;
      .sendMember {
        width: 40px;
        height: 40px;
        position: absolute;
        top: 5px;
        left: 10px;
        img {
          width: 24px;
          height: 24px;
          position: absolute;
          top: 8px;
          left: 8px;
        }
      }
      .sendInf {
        width: 100%;
        height: 40px;
        background: #f5f5f5;
        border-radius: 20px;
        padding-left: 10px;
      }
      .sendBtn {
        width: 60px;
        height: 50px;
        padding: 5px 10px;
        position: absolute;
        top: 0px;
        right: 0px;
        img {
          width: 40px;
        }
      }
    }
    .sendBoxBtm {
      width: 100%;
      height: 42px;
      display: flex;
      .sendOprate {
        flex: 1;
        height: 100%;
        align-items: center;
        justify-content: center;
        position: relative;
        img {
          padding: 5px;
          width: 34px;
          height: 34px;
          display: block;
          margin: 4px auto;
        }
        ::v-deep .van-uploader {
          width: 34px;
          height: 34px;
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
          .van-uploader__upload {
            width: 34px;
            height: 34px;
            margin: 0;
            opacity: 0;
          }
        }
      }
    }
    .sendExpression {
      width: 30px;
      height: 30px;
      position: absolute;
      top: 12px;
      right: 65px;
      padding: 3px;
      img {
        width: 24px;
      }
    }
    .quoteMessage {
      display: inline-block;
      background: #f4f4f4;
      border-radius: 6px;
      padding: 5px 30px 5px 8px;
      position: relative;
      margin-top: 6px;
      .quoteText {
        font-size: 10px;
        color: #8e8e93;
        line-height: 1.5;
        text-overflow: -o-ellipsis-lastline;
        overflow: hidden;
        text-overflow: ellipsis;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        line-clamp: 2;
        -webkit-box-orient: vertical;
      }
      .quoteClose {
        width: 24px;
        height: 24px;
        padding: 5px;
        position: absolute;
        right: 5px;
        top: 50%;
        transform: translateY(-50%);
      }
    }
  }
  .chatRecall {
    text-align: center;
    font-size: 12px;
    color: #8e8e93;
    padding: 6px 0px;
    line-height: 20px;
    a {
      color: #ff0021;
    }
  }
  .rejectCol {
    color: #ee6262;
  }
  ::v-deep .emoji-mart {
    width: 100% !important;
    height: 160px;
    border: none;
    .emoji-mart-category-label {
      display: none;
    }
  }
  .emojiBox {
    width: 100%;
    height: 160px;
    overflow: auto;
    .emojiDiv {
      display: flex;
      flex-wrap: wrap;
      padding: 0 10px;
      span {
        padding: 3px;
        width:32px;
        height:32px;
        text-align:center;
        line-height:32px;
      }
    }
  }
}
::v-deep .van-field__control{
  margin-top: 8px;
  border: 0px;
  width: calc(100% - 40px);
  background-color: transparent;
}
::v-deep .van-field__control:focus{
  outline: none !important;
}
</style>
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐