vue移动端实现聊天功能

效果展示

在这里插入图片描述
由于一些其他的原因,只做了聊天的功能,并且助手是写死的,这个再移动端上测试是没有问题的
接下来上代码:

聊天页面的实现代码

这里面了使用了自定义组件封装

主要的实现原理是,在<script>标签中嵌入了方法先捕捉消息列表滚动的对象, 我这个页面捕捉到是<body>标签 所以 我就直接使用了 window.scrollTo(0, this.$refs.list.scrollHeight);来将整个页面进行了滚动,滚动的高度就是聊天内容边框的高度,然后将这个方法设置每次消息发送之后进行滚动,对方消息过来的时候同样执行这个方法进行滚动

this.$nextTick(() => {
  // 一定要用nextTick
  // console.log(this.$refs.list.scrollHeight);
  window.scrollTo(0, this.$refs.list.scrollHeight);
});

注:这里的Title 以及下面消息展示,都是使用了自定的组件,代码我会贴在最下面,仅供大家交流学习。

<template>
  <div class="Serve" :style="{ height: curHeight + 'px' }">
    <!-- 顶部标签栏 -->
    <div class="title_warp">
      <Title title="我的客服" :back="true"></Title>
    </div>
    <div class="serve_main" ref="list">
      <div
        class="message_warp"
        v-for="(item, index) in messageList"
        :key="index"
      >
        <!-- 客服信息 -->
        <other-chat
          v-if="item.user == 'other'"
          :time="item.time"
          :avatar="item.avatar"
          :con="item.con"
        ></other-chat>
        <!-- 我的信息 -->
        <my-chat
          v-else
          :time="item.time"
          :avatar="item.avatar"
          :con="item.con"
        ></my-chat>
      </div>
    </div>
    <div class="inp_warp">
      <input
        class="inp"
        @focus="foucs"
        v-model="sendVal"
        v-on:keyup.enter="send"
        type="text"
        placeholder="请输入内容..."
      />
      <van-icon class="icon_inp" size="24px" color="#9a9a9a" name="smile-o" />
      <van-icon class="icon_inp" size="24px" color="#9a9a9a" name="add-o" />
    </div>
  </div>
</template>

<script>
function findScroller(element) {
  element.onscroll = function () {
    // console.log(element);
  };
  Array.from(element.children).forEach(findScroller);
}
findScroller(document.body);
import Title from "../../components/Title";
import OtherChat from "../../components/OtherChat";
import MyChat from "../../components/MyChat";
import { mapState } from "vuex";
import { formatDate } from "../../utils/date";
export default {
  name: "Serve",
  components: {
    Title,
    OtherChat,
    MyChat,
  },
  data() {
    return {
      // 屏幕高度
      curHeight: 0,
      // 信息体
      messageList: [
        {
          avatar: "https://bucket-ans.oss-cn-hangzhou.aliyuncs.com/2.jpg",
          time: formatDate(new Date(), "yyyy.mm.dd hh:mi"),
          con: "您好,欢迎来带我的客服中心,您的专属客服小浴为您服务!",
          user: "other",
        },
        {
          avatar: "https://bucket-ans.oss-cn-hangzhou.aliyuncs.com/2.jpg",
          time: formatDate(new Date(), "yyyy.mm.dd hh:mi"),
          con: "我们致力于打造一个为老人服务至上的平台!",
          user: "other",
        },
        {
          avatar: "https://bucket-ans.oss-cn-hangzhou.aliyuncs.com/2.jpg",
          time: formatDate(new Date(), "yyyy.mm.dd hh:mi"),
          con: "如有疑问请致电 109-3301-4401!",
          user: "other",
        },
        {
          avatar: "https://bucket-ans.oss-cn-hangzhou.aliyuncs.com/2.jpg",
          time: formatDate(new Date(), "yyyy.mm.dd hh:mi"),
          con: "小浴随时在线为您解答疑惑,请问您有什么需要帮助的嘛?",
          user: "other",
        },
        {
          avatar: "https://bucket-ans.oss-cn-hangzhou.aliyuncs.com/2.jpg",
          time: formatDate(new Date(), "yyyy.mm.dd hh:mi"),
          con: "小浴随时在线为您解答疑惑,举报电话请联系:109-4401-3301",
          user: "other",
        },
      ],
      // 输入框
      sendVal: "",
    };
  },
  computed: {
    ...mapState(["userInfo"]),
  },
  created() {
    this.initMessage();
    this.beforeMount(0);
  },
  methods: {
    initMessage() {
      var other = {
        avatar: "https://bucket-ans.oss-cn-hangzhou.aliyuncs.com/2.jpg",
        time: formatDate(new Date(), "yyyy.mm.dd hh:mi"),
        con: "您好,欢迎来带我的客服中心,您的专属客服小浴为您服务,如有疑问请致电官方:109-4431-2231,小浴可能还不够聪明,如果有什么冒犯的地方请您理解!",
        user: "other",
      };
      setTimeout(() => {
        this.messageList.push(other);
      }, 1000);
    },
    // 发送消息
    send() {
      var item = {
        avatar: this.userInfo.avatar,
        time: formatDate(new Date(), "yyyy.mm.dd hh:mi"),
        con: this.sendVal,
        user: "my",
      };
      this.messageList.push(item);
      this.$nextTick(() => {
        // 一定要用nextTick
        // console.log(this.$refs.list.scrollHeight);
        window.scrollTo(0, this.$refs.list.scrollHeight);
      });
      this.sendVal = "";
      var other = {
        avatar: "https://bucket-ans.oss-cn-hangzhou.aliyuncs.com/2.jpg",
        time: formatDate(new Date(), "yyyy.mm.dd hh:mi"),
        con: "很抱歉,我没听懂您在说什么",
        user: "other",
      };
      setTimeout(() => {
        this.messageList.push(other);
        this.$nextTick(() => {
          // 一定要用nextTick
          // console.log(this.$refs.list.scrollHeight);
          window.scrollTo(0, this.$refs.list.scrollHeight);
        });
      }, 2000);
    },
    // 获取屏幕高度
    beforeMount(height) {
      var h =
        document.documentElement.clientHeight || document.body.clientHeight;
      this.curHeight = h - height; //减去页面上固定高度height
    },
  },
};
</script>

<style scoped>
.title_warp {
  position: fixed;
  top: 0px;
  left: 0px;
  width: 100%;
  z-index: 99;
}
.ser_img {
  width: 100%;
}
.serve_main {
  padding: 180px 20px 120px;
}
.inp_warp {
  box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.1);
}
.inp_warp {
  padding: 20px 0;
  position: fixed;
  bottom: 0px;
  background-color: #fff;
  width: 100%;
  left: 0px;
  display: flex;
  justify-content: flex-start;
  align-items: center;
}
.icon_inp {
  margin: 0px 20px;
}
.inp {
  border: none;
  margin-left: 20px;
  background-color: #ededed;
  padding: 22px 20px;
  border-radius: 10px;
  width: 66%;
}
</style>

自定义Ttile标签:

这个没有什么技术含量

<template>
  <div class="Title">
    <!-- 头部组件编写 -->
    <div class="top">
      <!-- 顶部图标区 -->
      <div class="top_icon">
        <div class="top_icon_left">
          <van-icon
            v-if="back"
            @click="backHandle"
            size="24px"
            color="#fff"
            name="arrow-left"
          />
        </div>
        <div class="top_icon_right">
          <van-icon
            v-if="add"
            @click="openAdd"
            size="24px"
            color="#fff"
            name="plus"
            class="btn_icon"
          />
          <van-icon
            v-if="search"
            @click="openSearch"
            size="24px"
            color="#fff"
            name="search"
            class="btn_icon"
          />
        </div>
      </div>
      <div class="top_title">{{ title }}</div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Title",
  props: {
    add: {
      type: Boolean,
      default: false,
    },
    back: {
      type: Boolean,
      default: true,
    },
    title: {
      type: String,
      default: "登陆",
    },
    search: {
      type: Boolean,
      default: false,
    },
  },
  methods: {
    // 跳转到搜索页面
    openAdd() {
      this.$router.push("/article");
    },
    // 跳转到搜索页面
    openSearch() {
      this.$router.push("/search");
    },
    // 返回事件函数
    backHandle() {
      this.$router.back();
    },
  },
};
</script>

<style scoped>
.top {
  padding: 20px;
  background-color: #3c86df;
  border-bottom: 1px solid #ececec;
}
.top_title {
  margin: 10px;
  font-size: 50px;
  color: #fff;
}
.top_icon {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.btn_icon {
  margin: 0px 20px;
}
</style>

我发送消息之后的展示(MyChat组件)

<template>
  <div class="MyChat">
    <div class="my_user">
      <div class="time">{{ time }}</div>
      <div class="other_main">
        <div class="other_message">
          {{ con }}
        </div>
        <div class="triangle_right"></div>
        <div class="avatar_warp">
          <van-image round width="46" height="46" :src="avatar" />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "MyChat",
  props: {
    time: {
      type: String,
      default: "",
    },
    avatar: {
      type: String,
      default: "",
    },
    con: {
      type: String,
      default: "",
    },
  },
};
</script>

<style scoped>
.time {
  text-align: center;
  color: #9a9a9a;
  padding-bottom: 20px;
}
/* 我的信息 */
.my_user {
  padding-bottom: 30px;
}
.other_main {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}
.other_message {
  padding: 30px 25px;
  border-radius: 20px;
  color: #000;
  line-height: 34px;
  background-color: #ededed;
  max-width: 60%;
  word-wrap: break-word;
  word-break: break-all;
  overflow: hidden;
}
.triangle_right {
  width: 0;
  height: 0;
  border-top: 16px solid transparent;
  margin-right: 20px;
  border-left: 20px solid #ededed;
  border-bottom: 16px solid transparent;
}
</style>

对方回消息之后的展示(OtherChat)

<template>
  <div class="OtherChat">
    <div class="other_user">
      <div class="time">{{ time }}</div>
      <div class="user_main">
        <div class="avatar_warp">
          <van-image round width="46" height="46" :src="avatar" />
        </div>
        <div class="triangle_left"></div>
        <div class="user_message">
          {{ con }}
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "OtherChat",
  props: {
    time: {
      type: String,
      default: "",
    },
    avatar: {
      type: String,
      default: "",
    },
    con: {
      type: String,
      default: "",
    },
  },
};
</script>

<style scoped>
.time {
  text-align: center;
  color: #9a9a9a;
  padding-bottom: 20px;
}
.other_user {
  padding-bottom: 30px;
}
.user_main {
  display: flex;
  justify-content: flex-start;
  align-items: center;
}
.user_message {
  /* display: inline-block; */
  padding: 30px 25px;
  border-radius: 20px;
  color: #000;
  background-color: #ededed;
  max-width: 60%;
  line-height: 34px;
  word-wrap: break-word;
  word-break: break-all;
  overflow: hidden;
}
.triangle_left {
  width: 0;
  height: 0;
  border-top: 16px solid transparent;
  margin-left: 20px;
  border-right: 20px solid #ededed;
  border-bottom: 16px solid transparent;
}
</style>

这个是兼容,键盘弹起的,移动端好像会出现一开始输入消息,屏幕异常跳动的问题,那个需要设置一下聊天内容页面高度,量一下MyChat和OtherChat组件的高度进行类型操作,就可以处理这个问题,有更好的建议,欢迎打扰,哈哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值