代码改变世界

自己弄个对话框

  WEB前端小菜鸟  阅读(4)  评论(0编辑  收藏  举报

 

 

 

遇到的问题:

 

1.输入框一输入,然后清空,然后列表显示输入的内容,发送消息后接口返回后 对话框如何自动滚动到最新的消息位置

2.回答问题的时候如何让文字一个字一个字的输出 而不是一下子就输出完毕【定时器 100ms执行一次 每次都执行
scrollToLatestMessage 这个定位函数定到最下面
 
<template>
  <!-- 看板管理-->
  <div class="my_chatai_box">
    <div class="up">
      <div class="up_btn">
        <img src="@/assets/images/drive/aiimg.png" alt="" />
        <div class="txt">Chat Ai</div>
      </div>
    </div>
    <div class="down">
      <div class="down_up commonBox_left_mini" ref="messagesRef">
        <div class="down_up_start">
          <div v-if="currentObj.repeatTalksList.length == 0">
            <chat-ai-robot :isTitle="true"></chat-ai-robot>
            <!-- 默认初始状态 -->
            <div class="tax">请把你的问题交给我吧</div>
          </div>

          <!-- 对话状态 -->
          <div v-else>
            <div
              v-for="(item, index) in currentObj.repeatTalksList"
              :key="index"
            >
              <div
                class="ask"
                style="
                  width: 100%;
                  box-sizing: border-box;
                  display: flex;
                  flex-direction: column;

                  align-items: flex-end;
                  font-size: 14px;
                  color: #7a7687;
                "
              >
                <div style="padding-right: 10px">{{ currentObj.name }}</div>
                <div
                  style="
                    max-width: 80%;
                    text-align: right;
                    background: #fbfbfb;
                    padding: 10px;
                    margin-bottom: 10px;
                    border-radius: 5px;
                  "
                >
                  {{ item.ask }}
                </div>
              </div>
              <div
                class="anser"
                style="
                  width: 100%;
                  box-sizing: border-box;
                  display: flex;
                  flex-direction: column;
                  align-items: flex-start;
                  font-size: 14px;
                  color: #7a7687;
                "
              >
                <chat-ai-robot
                  :isTitle="false"
                  :isLoading="item.isLoading"
                ></chat-ai-robot>
                <div
                  style="
                    max-width: 80%;
                    background: #fbfbfb;
                    padding: 10px;
                    margin-bottom: 10px;
                    border-radius: 5px;
                  "
                >
                  {{ item.anser }}
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="down_input">
        <el-input
          class="input-box"
          v-model="currentObj.inputTalks"
          placeholder="给AI发送消息"
          suffix-icon="el-icon-arrow-right"
          @keyup.enter="sendMessage"
        >
        </el-input>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, reactive, ref, nextTick } from "vue";
import request from "@/API/request";
import serviceModule from "@/API/httpUrl";
import { ElMessage } from "element-plus";
import chatAiRobot from "./chatAiRobot.vue";
const currentObj = reactive({
  inputTalks: "",
  inputTalksCopy: "",
  repeatTalksList: [],
  inputTalksList: [],
  name: "",
  // 当前显示到的字符索引
  currentIndex: 0,
  // 定时器 ID
  timer: null,
});
const getbasicUserInfo = async () => {
  const { data, code, msg } = await request(serviceModule.basicUserInfo);
  if (code === "0000") {
    currentObj.name = data;
  }
};
// 模拟发送消息的函数
const sendMessage = () => {
  if (currentObj.inputTalks.trim() !== "") {
    console.log("发送的消息:", currentObj.inputTalks);
    currentObj.inputTalksList.push(currentObj.inputTalks);
    currentObj.inputTalksCopy = currentObj.inputTalks;
    currentObj.inputTalks = "";

    currentObj.repeatTalksList.push({
      ask: currentObj.inputTalksCopy,
      anser: "",
      anserFullText: "",
      isLoading: true,
    });
    // 这里可以添加与后端交互发送消息给AI的逻辑
    getAiChart(currentObj.inputTalksList);
  }
};
const getAiChart = async (contents) => {
  let params = {
    contents,
  };
  const { data, code, msg } = await request(serviceModule.aiChart, params);
  if (code === "0000") {
    // return data;
    // currentObj.repeatTalksList.push({
    //   ask: currentObj.inputTalksCopy,
    //   anser: data,
    // });
    currentObj.repeatTalksList[
      currentObj.repeatTalksList.length - 1
    ].anserFullText = data;
    currentObj.repeatTalksList[
      currentObj.repeatTalksList.length - 1
    ].isLoading = false;
    // console.log(currentObj.repeatTalksList, "对话内容");
    startTyping(); //一个一个字的输出
    scrollToLatestMessage();
  } else {
    ElMessage.error(msg);
  }
};
const messagesRef = ref(null);
const startTyping = () => {
  // 清除可能存在的旧定时器
  clearInterval(currentObj.timer);
  // 重置显示文本和索引
  currentObj.repeatTalksList[currentObj.repeatTalksList.length - 1].anser = "";
  currentObj.currentIndex = 0;

  // 设置新的定时器
  currentObj.timer = setInterval(() => {
    if (
      currentObj.currentIndex <
      currentObj.repeatTalksList[currentObj.repeatTalksList.length - 1]
        .anserFullText.length
    ) {
      // 逐个添加字符到显示文本中
      currentObj.repeatTalksList[currentObj.repeatTalksList.length - 1].anser +=
        currentObj.repeatTalksList[
          currentObj.repeatTalksList.length - 1
        ].anserFullText[currentObj.currentIndex];
      currentObj.currentIndex++;
      scrollToLatestMessage(); //这里执行一次  不然输入后对话框不会再最新的对话的位置
    } else {
      // 显示完所有字符后清除定时器
      clearInterval(currentObj.timer);
    }
  }, 100); // 每个字符显示的间隔时间,单位为毫秒
};
// const scrollToLatestMessage = () => {
//   if (messagesRef.value) {
//     messagesRef.value.scrollTop = messagesRef.value.scrollHeight;
//   }
// };
const scrollToLatestMessage = async () => {
  // 等待DOM更新完成,确保列表已经渲染完毕
  await nextTick();
  if (messagesRef.value) {
    const childElements = messagesRef.value.children;
    if (childElements.length > 0) {
      const lastElement = childElements[childElements.length - 1];
      // 让列表容器滚动到最后一个元素的位置
      messagesRef.value.scrollTop =
        lastElement.offsetTop +
        lastElement.offsetHeight -
        messagesRef.value.offsetHeight;
    }
  }
};
onMounted(() => {
  getbasicUserInfo();
});
</script>
<style scoped lang="less">
.my_chatai_box {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  background-color: #fff;
  border-radius: 6px 6px 6px 6px;
  .up {
    box-sizing: border-box;
    height: 64px;
    background: #ffffff;
    border-bottom: 1px solid #e5e4e7;
    display: flex;
    align-items: center;

    .up_btn {
      width: 92px;
      height: 32px;
      margin-left: 20px;
      border-radius: 6px;
      border: 1px solid;
      border-image: linear-gradient(
          94deg,
          rgba(255, 129, 129, 1),
          rgba(205, 111, 255, 1),
          rgba(205, 111, 255, 1),
          rgba(122, 146, 255, 1)
        )
        1 1;
      display: flex;
      align-items: center;
      justify-content: center;
      img {
        width: 20px;
        height: 20px;
        margin-right: 3px;
      }
      .txt {
        font-family: PingFang SC, PingFang SC;
        font-size: 14px;
        color: #7a7687;
      }
    }
  }

  .down {
    height: calc(100% - 64px);
    box-sizing: border-box;
    background: #fff;
    padding: 20px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;

    .down_up {
      height: 0;
      flex: 1;
      box-sizing: border-box;
      margin-bottom: 10px;
      .down_up_start {
        .tax {
          width: 100%;
          height: 60px;
          box-sizing: border-box;
          line-height: 60px;
          background: #fbfbfb;
          box-shadow: 0px 0px 2px 0px rgba(219, 199, 234, 0.25);
          border-radius: 12px 12px 12px 12px;
          border: 1px solid #f4f0ff;
          padding-left: 20px;
          font-size: 14px;
          color: #7a7687;
        }
      }
      .down_up_talking {
      }
    }
    .down_input {
      height: 80px;
      //   border: 1px solid #f4f0ff;
      .input-box {
        height: 100%;
        background: #fff;
        border-radius: 12px;

        box-sizing: border-box;
        // padding: 0 20px;
        border: none;
        box-shadow: none; /* 去掉默认的聚焦阴影 */
        outline: none; /* 去掉聚焦时的轮廓线 */
      }
    }
    .down_input :deep(.el-input__inner) {
      border: none;
      box-shadow: none; /* 去掉默认的聚焦阴影 */
      outline: none; /* 去掉聚焦时的轮廓线 */
    }
  }
}
</style>

  

 
import chatAiRobot from "./chatAiRobot.vue";
 代码在下面
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
  <div class="default" :class="isTitle ? 'title' : 'ainame'">
    <img src="@/assets/images/drive/aiimg.png" alt="" />
    <div class="txt">我的AI</div>
    <div v-if="!isTitle">
      <div class="think" v-if="isLoading">思考中...</div>
      <div class="think" v-else>思考完毕</div>
    </div>
  </div>
</template>
<script lang="ts" setup>
import { defineProps } from "vue";
const props = defineProps({
  isTitle: { type: Boolean, default: false },
  isLoading: { type: Boolean, default: false },
});
</script>
 
<style scoped lang="less">
.default {
  display: flex;
  align-items: center;
  justify-content: center;
  img {
    width: 20px;
    height: 20px;
    margin-right: 3px;
  }
  .txt {
    font-family: PingFang SC, PingFang SC;
    font-size: 14px;
    color: #7a7687;
    margin-right: 5px;
  }
  .think {
    border: 1px solid #f4f0ff;
    padding: 5px 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 6px;
  }
}
.title {
  margin-bottom: 30px;
}
.ainame {
  margin-bottom: 0px;
  padding-left: 10px;
}
</style>

  

 
 

 

相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示