使用vue3和ts和elementplus搭建的简单聊天的页面

搭建的页面比价简单废话不多说先看效果图(页面功能包括了进入页面会首先请求后端热点问题显示,输入时提示可能会问的问题,点击超粘接会直接向机器人发送点击的问题,点击图片放大展示功能)

  

就不做其他讲解,直接上代码了

<template>
  <div class="common-layout">
    <el-container>
      <el-header :style="{ height: '50px', width: '1000px' ,backgroundColor: '#87CEEB'}">
        <p class="centered-text">机器人</p></el-header>
      <el-main :style="{ height: '600px', width: '1000px' ,border: '2px solid #ccc' }">
        <div class="message-container" v-for="(message, index) in messages" :key="index"
             :class="getMessageClass(message.isSent)">
          <div v-if="message.isSent" class="message-container">
            <div class="bubble">
              <div class="message" v-html="message.content"></div>
            </div>
            <div class="avatar">
              <img src="./my.jpg" alt="Avatar" class="avatar-image"/>
            </div>
          </div>
          <div v-if="!message.isSent" class="message-container">
            <div class="avatar">
              <img src="./a.jpg" alt="Avatar" class="avatar-image"/>
            </div>
            <div class="bubble">
              <div class="message" v-html="message.content"
                   @click.prevent="handleMessageClick($event)"></div>
            </div>
          </div>
        </div>
      </el-main>
      <el-row :style="{ width: '1000px' }">
        <AutomaticPrompt @keydown.enter="handleButtonClick" @updateState="getState"
                         ref="automaticPromptRef"></AutomaticPrompt>
        <el-button type="primary" plain style="width: 50px;" @click="handleButtonClick">发送</el-button>
      </el-row>
    </el-container>
  </div>
</template>

<style>

.centered-text {
  text-align: center;
  color: black;
}

.underline-link {
  text-decoration: underline;
}

.message-container {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
}

.avatar {
  margin-left: 10px; /* 修改这里将头像放在消息框的右边 */
}

.avatar-image {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
}

.bubble {
  background-color: #e8e8e8;
  color: #000;
  padding: 10px;
  border-radius: 5px;
}

.message {
  text-align: left;
  margin: 0;
}

.message-container-right {
  justify-content: flex-end;
}

.message-container-left {
  justify-content: flex-start;
}
</style>

<script lang="ts" setup>
import {ref, onMounted} from 'vue';
import AutomaticPrompt from './AutomaticPrompt.vue'
import axios from 'axios';
import ImageViewer from "@luohc92/vue3-image-viewer";
import '@luohc92/vue3-image-viewer/dist/style.css';


const automaticPromptRef = ref('');

let msg: string = '';
const messages = ref([]);
//获取子组件中state的值,这个好像是写多余了,可以直接使用automaticPromptRef.value.setState('');获取state值
const getState = (v) => {
  msg = v;
};

//对机器人回复的【link】标签进行渲染(替换字符串)
const formatString = (str) => {
  str = str.replace(/(\[link submit="faqvote.*?\])/g, '<a class="underline-link" href="">');
  const replacedStr1 = str.replace(/(\[link.*?\])/g, '<br><a class="underline-link" href="">');
  const replacedStr2 = replacedStr1.replace(/\[\/link\]/g, `</a>`)
  const replacedStr3 = replacedStr2.replace(/\\r\\n/g, `<br>`)
  const replacedStr4 = replacedStr3.replace(/\\\\r\\\\n/g, ``)
  return replacedStr4;
}
//发送按钮
const handleButtonClick = () => {
  messages.value.push({content: msg, isSent: true});
  sendMsg(msg);
  automaticPromptRef.value.setState('');
};
//向后端发送请求逻辑
const sendMsg = async (msg: string) => {
  let responseMsg = '';
  try {
    //请求后端问题答案,并对问题答案进行封装,这里需要各位对各自的后端返回格式进行解析
    const response = await axios.get(`http://localhost:8080/question/` + msg);
    //正常情况的文本处理
    if (response.data.content !== undefined) {
      responseMsg = formatString(response.data.content);
      console.log(responseMsg)
    }
    //图片处理
    let mark = false;
    let imageTxt = '';
    let imageUrl = '';
    for (let i = 0; i < response.data.commands.length; i++) {
      if (response.data.commands[i].name === 'imgmsg' || response.data.commands[i].name === 'txtimage') {
        mark = true;
        for (let j = 0; j < response.data.commands[i].args.length; j++) {
          if (response.data.commands[i].args[j].includes('http://')) {
            imageUrl += response.data.commands[i].args[j];
          } else {
            imageTxt += response.data.commands[i].args[j] + '<br>'
          }
        }
      }
    }
    if (mark) {
      responseMsg = responseMsg + '<br>' + imageTxt + '<img src="' + imageUrl + '" alt="Image" style="width: 200px; height: auto;">'
    }
    if (response.data.relatedQuestions !== undefined && response.data.relatedQuestions.length !== 0) {
      responseMsg = responseMsg + '<br>您可能想问:'
      for (let i = 0; i < response.data.relatedQuestions.length; i++) {
        let responseIndex = i + 1
        responseMsg = responseMsg + '<br>' + responseIndex + ':' + '<a class="underline-link" href="">' + response.data.relatedQuestions[i] + '</a>'
      }
    }
  } catch (error) {
    console.error(error);
    responseMsg = '网络异常';
  }
  messages.value.push({content: responseMsg, isSent: false});
}

const handleMessageClick = (event) => {
  const target = event.target;
  if (target.tagName === 'A') {
    // 点击的是超链接
    // 执行相应的操作
    if (target.innerHTML === '解决') {
      alert('感谢您的使用')
    } else if (target.innerHTML === '未解决') {
      alert('很抱歉未能解决你的问题')
    } else {
      handleLinkClick(target.innerHTML);
    }
  } else if (target.tagName === 'IMG') {
    // 点击的图片进行放大操作
    ImageViewer({
        //切记额images这个参数是数组,我的target.valueof().src值是一个http的图片地址
      images: [target.valueOf().src],
      curIndex: 0,
      zIndex: 2000,
      showDownload: true,
      showThumbnail: true,
      handlePosition: "bottom",
      maskBgColor: "rgba(0,0,0,0.7)",
      onClose: () => {
        console.log("close");
      },
    });

  } else {

  }
}

const handleLinkClick = (msg) => {
  messages.value.push({content: msg, isSent: true})
  sendMsg(msg);
}
//消息框样式动态选择
const getMessageClass = (isSent) => {
  return isSent ? 'message-container-right' : 'message-container-left';
};
//进入页面直接发送请求从后端获取热点数据
onMounted(async () => {
  let responseMsg = '';
  try {
    const response = await axios.get(`http://localhost:8080/getHotAsk`);
    responseMsg = responseMsg + '<br>您可能想问:'
    for (let i = 0; i < response.data.length; i++) {
      let responseIndex = i + 1
      responseMsg = responseMsg + '<br>' + responseIndex + ':' + '<a class="underline-link" href="">' + response.data[i] + '</a>'
    }
  } catch (error) {
    console.error(error);
    responseMsg = '网络异常,暂时无法加载出热点问题';
  }
  messages.value.push({content: responseMsg, isSent: false})
})
</script>

上面代码中使用到了vue3-image-viewer,请自行下载,运行命令npm install --save @luohc92/vue3-image-viewer,同时代码中automaticPromptRef.value.setState('');的setState会爆红不影响使用,可以正常的去将子组件的值清除。下面是父组件中引用的子组件代码

<template>
  <el-autocomplete :style="{ width: '950px' }" v-model="state" :fetch-suggestions="querySearchAsync"
                   placeholder="请输入问题" @select="handleSelect" ref="automaticPromptRef"/>
</template>

<script lang="ts" setup>
import { ref, watch, defineEmits ,defineExpose} from 'vue';
import axios from 'axios';
const state = ref('');

interface LinkItem {
  value: string;
  link: string;
}

const links = ref<LinkItem[]>([]);

const loadFromBackend = async (value: string) => {
  try {
//输入时候请求后端根据输入值得到提示。    后端返回集合,集合里面对象属性为value和link都是string类型
    const response = await axios.get(`http://localhost:8080/getAutoMsg/${value}`);
    links.value = response.data;
  } catch (error) {
    console.error(error);
  }
};

const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
  const results = queryString ? links.value.filter(createFilter(queryString)) : links.value;
  cb(results);
};

const createFilter = (queryString: string) => {
  return (link: LinkItem) => {
    return link.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0;
  };
};

const handleSelect = (value: string) => {

};

const emit = defineEmits(['updateState']);

watch(state, async (newValue) => {
  emit('updateState',newValue)
  if (newValue) {
    await loadFromBackend(newValue);
  } else {
    links.value = [];
  }
});

defineExpose({
  setState(res){
    state.value = res
  },
  getState(){
    return state.value
  }
})
</script>

对参数的解析需要各位去对照自己的去修改,后端就不展示给大家了,后端基本就几行代码都是调用公司的api。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用Vue3 + TypeScript + Vite + Element Plus + Router + Axios搭建前端项目框架的步骤: 1. 首先,确保你已经安装了Node.js和npm。你可以在命令行中运行以下命令来检查它们的版本: ```shell node -v npm -v ``` 2. 创建一个新的项目文件夹,并在该文件夹中打开命令行。 3. 在命令行中运行以下命令来初始化一个新的Vite项目: ```shell npm init vite ``` 在初始化过程中,你需要选择Vue作为模板,选择TypeScript作为语言,并填写项目名称。 4. 进入项目文件夹,并安装所需的依赖: ```shell cd your-project-name npm install ``` 5. 安装Vue Router、Vuex和Axios: ```shell npm install vue-router@next vuex@next axios ``` 6. 在项目文件夹中创建一个新的文件夹,用于存放页面组件和路由配置文件。 7. 在src文件夹中创建一个新的文件夹,用于存放页面组件。 8. 在src文件夹中创建一个新的文件夹,用于存放路由配置文件。 9. 在src/router文件夹中创建一个新的文件,命名为index.ts,并在其中编写路由配置: ```typescript import { createRouter, createWebHistory } from 'vue-router'; import Home from '../views/Home.vue'; const routes = [ { path: '/', name: 'Home', component: Home, }, // 添加其他页面的路由配置 ]; const router = createRouter({ history: createWebHistory(), routes, }); export default router; ``` 10. 在src/main.ts文件中导入并使用Vue Router: ```typescript import { createApp } from 'vue'; import App from './App.vue'; import router from './router'; createApp(App).use(router).mount('#app'); ``` 11. 在src/views文件夹中创建一个新的文件,命名为Home.vue,并在其中编写一个简单页面组件: ```vue <template> <div> <h1>Welcome to Home Page</h1> </div> </template> <script> export default { name: 'Home', }; </script> ``` 12.src/App.vue文件中添加一个路由出口,用于显示组件: ```vue <template> <div id="app"> <router-view></router-view> </div> </template> <script> export default { name: 'App', }; </script> ``` 13. 在src/main.ts文件中导入并使用Element Plus: ```typescript import { createApp } from 'vue'; import App from './App.vue'; import router from './router'; import ElementPlus from 'element-plus'; import 'element-plus/lib/theme-chalk/index.css'; createApp(App).use(router).use(ElementPlus).mount('#app'); ``` 14. 运行以下命令来启动开发服务器: ```shell npm run dev ``` 15. 打开浏览器,并访问http://localhost:3000,你将看到一个简单页面,其中包含"Welcome to Home Page"的文本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值