用vue3写一个AI聊天室

效果图如下:

1、页面布局:

html 复制代码
<template>
  <div class="body" style="background-color: rgb(244, 245, 248); height: 730px">
      <div class="container">
        <div class="right">
          <div class="top">AI问答</div>
          <div class="chat" ref="chatContainer">
            <div
              v-for="(item, i) in msgList"
              :key="i"
              :class="item.type == '1' ? 'rightMsg' : 'leftMsg'"
            >
              <img
                v-if="item.type == '0'"
                src="../assets/images/AI.png"
                alt=""
              />
              <div class="msg">{{ item.content }}</div>
              <img
                v-if="item.type == '1'"
                src="../assets/images/me.png"
                alt=""
              />
            </div>
            <!-- 
            <div v-if="msgList.length >= 10" class="separator">
              -------------- 本AI问答仅显示最近10条对话 --------------
            </div> -->
          </div>
          <div class="bottom">
            <input v-model="value" placeholder="请输入您想提问的内容" />
            <button @click="onSend">
              <img src="../assets/images/send.png" alt="发送" />
            </button>
          </div>
        </div>
      </div>
  </div>
</template>

2、封转函数(用户输入问题和AI回答问题):

javascript 复制代码
const msgList = reactive([]);
//提问
const userQuestion = (question) => {
  var userMsg = {
    content: question,
    type: "1",
    id: Date.now(),
  };
  msgList.push(userMsg);
};
//回答
const AIReplay = (replay) => {
  var autoReplyMsg = {
    content: replay,
    type: "0",
    id: Date.now(),
  };
  msgList.push(autoReplyMsg);
};

3、从后端获取最近的10个对话:

javascript 复制代码
const getMes = () => {
  getQandA({}).then((res) => {
    console.log(res);
    if (res.data.status == 200) {
      // 获取最近五条问答信息
      for (var i = 0; i < 5; i++) {
        userQuestion(res.data.data[i].inputMessage);
        AIReplay(res.data.data[i].aiResult);
      }
      scrollToNew();
    }
  });
};

4、为了使用户发送问题后内容滚动在最底处,写一个函数让其自动滚动,在发送信息和获取信息时调用该函数即可

javascript 复制代码
// 等待DOM更新完成。自动滚动到最新发送的消息处
const scrollToNew = async () => {
  await nextTick();
  const chatContainer = document.querySelector(".chat");
  if (chatContainer) {
    chatContainer.scrollTop = chatContainer.scrollHeight;
  }
};

5、点击发送按钮,发送到后端进行处理:

javascript 复制代码
const onSend = () => {
  // 发送用户输入的消息
  AIQandA({
    inputMessage: value.value,
  }).then((res) => {
    console.log(res);
    if (res.data.status == 200) {
      console.log(6666);
      AIReplay(res.data.data);
      scrollToNew();
    }
  });
  userQuestion(value.value);
  scrollToNew();
  value.value = "";
};

完整代码如下:

html 复制代码
<template>
  <Header></Header>
  <div class="body" style="background-color: rgb(244, 245, 248); height: 730px">
    <header>
      <div class="cover">
        <img
          src="1.png"
          alt=""
          style="width: 100%; height: 100%"
        />
      </div>
    </header>
    <main>
      <div class="container">
        <div class="right">
          <div class="top">AI问答</div>
          <div class="chat" ref="chatContainer">
            <div
              v-for="(item, i) in msgList"
              :key="i"
              :class="item.type == '1' ? 'rightMsg' : 'leftMsg'"
            >
              <img
                v-if="item.type == '0'"
                src="../assets/images/AI.png"
                alt=""
              />
              <div class="msg">{{ item.content }}</div>
              <img
                v-if="item.type == '1'"
                src="../assets/images/me.png"
                alt=""
              />
            </div>
            <!-- 
            <div v-if="msgList.length >= 10" class="separator">
              -------------- 本AI问答仅显示最近10条对话 --------------
            </div> -->
          </div>
          <div class="bottom">
            <input v-model="value" placeholder="请输入您想提问的内容" />
            <button @click="onSend">
              <img src="../assets/images/send.png" alt="发送" />
            </button>
          </div>
        </div>
      </div>
    </main>
  </div>
  <foot></foot>
</template>

<script setup>
import { ref, reactive, nextTick, onMounted } from "vue";
import Header from "../components/header.vue";
import foot from "../components/foot.vue";
import { AIQandA, getQandA } from "../api/AIApi";
const value = ref("");
const msgList = reactive([]);
onMounted(() => {
  getMes();
});
// 等待DOM更新完成。自动滚动到最新发送的消息处
const scrollToNew = async () => {
  await nextTick();
  const chatContainer = document.querySelector(".chat");
  if (chatContainer) {
    chatContainer.scrollTop = chatContainer.scrollHeight;
  }
};
const userQuestion = (question) => {
  var userMsg = {
    content: question,
    type: "1",
    id: Date.now(),
  };
  msgList.push(userMsg);
};
const AIReplay = (replay) => {
  var autoReplyMsg = {
    content: replay,
    type: "0",
    id: Date.now(),
  };
  msgList.push(autoReplyMsg);
};
const getMes = () => {
  getQandA({}).then((res) => {
    console.log(res);
    if (res.data.status == 200) {
      // 获取最近五条问答信息
      for (var i = 0; i < 5; i++) {
        userQuestion(res.data.data[i].inputMessage);
        AIReplay(res.data.data[i].aiResult);
      }
      scrollToNew();
    }
  });
};

const onSend = () => {
  // 发送用户输入的消息
  AIQandA({
    inputMessage: value.value,
  }).then((res) => {
    console.log(res);
    if (res.data.status == 200) {
      console.log(6666);
      AIReplay(res.data.data);
      scrollToNew();
    }
  });
  userQuestion(value.value);
  scrollToNew();
  value.value = "";
};
</script>

<style scoped lang="scss">
.body {
  color: #fff;
  font-weight: 900;
  letter-spacing: 2px;
  width: 100%;
  height: 100%;
  background-size: 50%;
  display: flex;
  align-items: center;
  position: relative;
}
main {
  /* border: 1px solid red; */
  width: 1400px;
  height: 600px;
  margin: 100px auto;
  display: flex;
}
.cover {
  position: absolute;
  top: 0px;
  z-index: 0;
  height: 180px;
  width: 1483px;
  left: 50%;
  margin-left: -754px;
  overflow: hidden;
}
.body {
  :deep(.slick-slide) {
    text-align: center;
    height: 100%;
    line-height: 100%;
    background: #364d79;
    overflow: hidden;
  }

  :deep(.slick-arrow.custom-slick-arrow) {
    width: 25px;
    height: 25px;
    font-size: 25px;
    color: #fff;
    background-color: rgba(31, 45, 61, 0.11);
    transition: ease all 0.3s;
    opacity: 0.3;
    z-index: 1;
  }

  :deep(.slick-arrow.custom-slick-arrow:before) {
    display: none;
  }

  :deep(.slick-arrow.custom-slick-arrow:hover) {
    color: #fff;
    opacity: 0.5;
  }

  :deep(.slick-slide h3) {
    color: #fff;
  }
}

.container {
  z-index: 1;
  // border: solid 1px #bebebe;
  width: 85%;
  height: 100%;
  margin: -6px auto;
  display: flex;
  justify-content: center;

  .right {
    flex: 1;
    // border-radius: 10px;
    background-color: white;
    display: flex;
    flex-direction: column;
    height: 600px;
    .top {
      height: 70px;
      background-color: rgba(147, 213, 255, 0.764);
      width: 100%;
      font-size: 22px;
      text-align: center;
      line-height: 70px;
    }

    .chat {
      flex: 1;
      max-height: 580px;
      overflow-y: auto;
      padding: 10px;
      .leftMsg,
      .rightMsg {
        display: flex;
        flex-direction: row;
        justify-content: start;
        align-items: center;
        margin: 10px;

        img {
          width: 40px;
          height: 40px;
          border-radius: 20px;
          overflow: hidden;
          object-fit: cover;
          margin: 0 10px;
        }

        .msg {
          display: inline-block;
          padding: 10px;
          word-wrap: anywhere;
          max-width: 600px;
          background-color: #364d79;
          border-radius: 10px;
        }
      }

      .rightMsg {
        justify-content: end;

        .msg {
          color: black;
          background-color: #dfdfdf;
        }
      }
    }

    .bottom {
      height: 45px;
      display: flex;
      align-items: center;
      width: 80%;
      margin: 10px auto;

      input {
        width: 90%;
        border: 1px solid rgb(171, 171, 171);
        border-right: none;
        height: 40px;
        color: black;
        text-indent: 2px;
        line-height: 40px;
        border-radius: 10px 0 0 10px;
      }

      button {
        cursor: pointer;
        width: 10%;
        border: none;
        outline: none;
        height: 45px;
        border-radius: 0 10px 10px 0;
        background: linear-gradient(
          to right,
          rgb(146, 197, 255),
          rgb(200, 134, 200)
        );
      }
      img {
        width: 20px;
        height: 20px;
      }
    }
  }
}
.separator {
  color: rgb(133, 132, 132);
  text-align: center;
  font-size: 15px;
  font-weight: normal;
}
</style>
相关推荐
轻口味44 分钟前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王1 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发1 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀2 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪2 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef4 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6414 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻5 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云5 小时前
npm淘宝镜像
前端·npm·node.js