当前位置:   article > 正文

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

vue3 聊天

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

  

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

  1. <template>
  2. <div class="common-layout">
  3. <el-container>
  4. <el-header :style="{ height: '50px', width: '1000px' ,backgroundColor: '#87CEEB'}">
  5. <p class="centered-text">机器人</p></el-header>
  6. <el-main :style="{ height: '600px', width: '1000px' ,border: '2px solid #ccc' }">
  7. <div class="message-container" v-for="(message, index) in messages" :key="index"
  8. :class="getMessageClass(message.isSent)">
  9. <div v-if="message.isSent" class="message-container">
  10. <div class="bubble">
  11. <div class="message" v-html="message.content"></div>
  12. </div>
  13. <div class="avatar">
  14. <img src="./my.jpg" alt="Avatar" class="avatar-image"/>
  15. </div>
  16. </div>
  17. <div v-if="!message.isSent" class="message-container">
  18. <div class="avatar">
  19. <img src="./a.jpg" alt="Avatar" class="avatar-image"/>
  20. </div>
  21. <div class="bubble">
  22. <div class="message" v-html="message.content"
  23. @click.prevent="handleMessageClick($event)"></div>
  24. </div>
  25. </div>
  26. </div>
  27. </el-main>
  28. <el-row :style="{ width: '1000px' }">
  29. <AutomaticPrompt @keydown.enter="handleButtonClick" @updateState="getState"
  30. ref="automaticPromptRef"></AutomaticPrompt>
  31. <el-button type="primary" plain style="width: 50px;" @click="handleButtonClick">发送</el-button>
  32. </el-row>
  33. </el-container>
  34. </div>
  35. </template>
  36. <style>
  37. .centered-text {
  38. text-align: center;
  39. color: black;
  40. }
  41. .underline-link {
  42. text-decoration: underline;
  43. }
  44. .message-container {
  45. display: flex;
  46. align-items: center;
  47. margin-bottom: 10px;
  48. }
  49. .avatar {
  50. margin-left: 10px; /* 修改这里将头像放在消息框的右边 */
  51. }
  52. .avatar-image {
  53. width: 40px;
  54. height: 40px;
  55. border-radius: 50%;
  56. object-fit: cover;
  57. }
  58. .bubble {
  59. background-color: #e8e8e8;
  60. color: #000;
  61. padding: 10px;
  62. border-radius: 5px;
  63. }
  64. .message {
  65. text-align: left;
  66. margin: 0;
  67. }
  68. .message-container-right {
  69. justify-content: flex-end;
  70. }
  71. .message-container-left {
  72. justify-content: flex-start;
  73. }
  74. </style>
  75. <script lang="ts" setup>
  76. import {ref, onMounted} from 'vue';
  77. import AutomaticPrompt from './AutomaticPrompt.vue'
  78. import axios from 'axios';
  79. import ImageViewer from "@luohc92/vue3-image-viewer";
  80. import '@luohc92/vue3-image-viewer/dist/style.css';
  81. const automaticPromptRef = ref('');
  82. let msg: string = '';
  83. const messages = ref([]);
  84. //获取子组件中state的值,这个好像是写多余了,可以直接使用automaticPromptRef.value.setState('');获取state值
  85. const getState = (v) => {
  86. msg = v;
  87. };
  88. //对机器人回复的【link】标签进行渲染(替换字符串)
  89. const formatString = (str) => {
  90. str = str.replace(/(\[link submit="faqvote.*?\])/g, '<a class="underline-link" href="">');
  91. const replacedStr1 = str.replace(/(\[link.*?\])/g, '<br><a class="underline-link" href="">');
  92. const replacedStr2 = replacedStr1.replace(/\[\/link\]/g, `</a>`)
  93. const replacedStr3 = replacedStr2.replace(/\\r\\n/g, `<br>`)
  94. const replacedStr4 = replacedStr3.replace(/\\\\r\\\\n/g, ``)
  95. return replacedStr4;
  96. }
  97. //发送按钮
  98. const handleButtonClick = () => {
  99. messages.value.push({content: msg, isSent: true});
  100. sendMsg(msg);
  101. automaticPromptRef.value.setState('');
  102. };
  103. //向后端发送请求逻辑
  104. const sendMsg = async (msg: string) => {
  105. let responseMsg = '';
  106. try {
  107. //请求后端问题答案,并对问题答案进行封装,这里需要各位对各自的后端返回格式进行解析
  108. const response = await axios.get(`http://localhost:8080/question/` + msg);
  109. //正常情况的文本处理
  110. if (response.data.content !== undefined) {
  111. responseMsg = formatString(response.data.content);
  112. console.log(responseMsg)
  113. }
  114. //图片处理
  115. let mark = false;
  116. let imageTxt = '';
  117. let imageUrl = '';
  118. for (let i = 0; i < response.data.commands.length; i++) {
  119. if (response.data.commands[i].name === 'imgmsg' || response.data.commands[i].name === 'txtimage') {
  120. mark = true;
  121. for (let j = 0; j < response.data.commands[i].args.length; j++) {
  122. if (response.data.commands[i].args[j].includes('http://')) {
  123. imageUrl += response.data.commands[i].args[j];
  124. } else {
  125. imageTxt += response.data.commands[i].args[j] + '<br>'
  126. }
  127. }
  128. }
  129. }
  130. if (mark) {
  131. responseMsg = responseMsg + '<br>' + imageTxt + '<img src="' + imageUrl + '" alt="Image" style="width: 200px; height: auto;">'
  132. }
  133. if (response.data.relatedQuestions !== undefined && response.data.relatedQuestions.length !== 0) {
  134. responseMsg = responseMsg + '<br>您可能想问:'
  135. for (let i = 0; i < response.data.relatedQuestions.length; i++) {
  136. let responseIndex = i + 1
  137. responseMsg = responseMsg + '<br>' + responseIndex + ':' + '<a class="underline-link" href="">' + response.data.relatedQuestions[i] + '</a>'
  138. }
  139. }
  140. } catch (error) {
  141. console.error(error);
  142. responseMsg = '网络异常';
  143. }
  144. messages.value.push({content: responseMsg, isSent: false});
  145. }
  146. const handleMessageClick = (event) => {
  147. const target = event.target;
  148. if (target.tagName === 'A') {
  149. // 点击的是超链接
  150. // 执行相应的操作
  151. if (target.innerHTML === '解决') {
  152. alert('感谢您的使用')
  153. } else if (target.innerHTML === '未解决') {
  154. alert('很抱歉未能解决你的问题')
  155. } else {
  156. handleLinkClick(target.innerHTML);
  157. }
  158. } else if (target.tagName === 'IMG') {
  159. // 点击的图片进行放大操作
  160. ImageViewer({
  161. //切记额images这个参数是数组,我的target.valueof().src值是一个http的图片地址
  162. images: [target.valueOf().src],
  163. curIndex: 0,
  164. zIndex: 2000,
  165. showDownload: true,
  166. showThumbnail: true,
  167. handlePosition: "bottom",
  168. maskBgColor: "rgba(0,0,0,0.7)",
  169. onClose: () => {
  170. console.log("close");
  171. },
  172. });
  173. } else {
  174. }
  175. }
  176. const handleLinkClick = (msg) => {
  177. messages.value.push({content: msg, isSent: true})
  178. sendMsg(msg);
  179. }
  180. //消息框样式动态选择
  181. const getMessageClass = (isSent) => {
  182. return isSent ? 'message-container-right' : 'message-container-left';
  183. };
  184. //进入页面直接发送请求从后端获取热点数据
  185. onMounted(async () => {
  186. let responseMsg = '';
  187. try {
  188. const response = await axios.get(`http://localhost:8080/getHotAsk`);
  189. responseMsg = responseMsg + '<br>您可能想问:'
  190. for (let i = 0; i < response.data.length; i++) {
  191. let responseIndex = i + 1
  192. responseMsg = responseMsg + '<br>' + responseIndex + ':' + '<a class="underline-link" href="">' + response.data[i] + '</a>'
  193. }
  194. } catch (error) {
  195. console.error(error);
  196. responseMsg = '网络异常,暂时无法加载出热点问题';
  197. }
  198. messages.value.push({content: responseMsg, isSent: false})
  199. })
  200. </script>

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

  1. <template>
  2. <el-autocomplete :style="{ width: '950px' }" v-model="state" :fetch-suggestions="querySearchAsync"
  3. placeholder="请输入问题" @select="handleSelect" ref="automaticPromptRef"/>
  4. </template>
  5. <script lang="ts" setup>
  6. import { ref, watch, defineEmits ,defineExpose} from 'vue';
  7. import axios from 'axios';
  8. const state = ref('');
  9. interface LinkItem {
  10. value: string;
  11. link: string;
  12. }
  13. const links = ref<LinkItem[]>([]);
  14. const loadFromBackend = async (value: string) => {
  15. try {
  16. //输入时候请求后端根据输入值得到提示。 后端返回集合,集合里面对象属性为value和link都是string类型
  17. const response = await axios.get(`http://localhost:8080/getAutoMsg/${value}`);
  18. links.value = response.data;
  19. } catch (error) {
  20. console.error(error);
  21. }
  22. };
  23. const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
  24. const results = queryString ? links.value.filter(createFilter(queryString)) : links.value;
  25. cb(results);
  26. };
  27. const createFilter = (queryString: string) => {
  28. return (link: LinkItem) => {
  29. return link.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0;
  30. };
  31. };
  32. const handleSelect = (value: string) => {
  33. };
  34. const emit = defineEmits(['updateState']);
  35. watch(state, async (newValue) => {
  36. emit('updateState',newValue)
  37. if (newValue) {
  38. await loadFromBackend(newValue);
  39. } else {
  40. links.value = [];
  41. }
  42. });
  43. defineExpose({
  44. setState(res){
  45. state.value = res
  46. },
  47. getState(){
  48. return state.value
  49. }
  50. })
  51. </script>

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/761318
推荐阅读
相关标签
  

闽ICP备14008679号