【Spring AI】基于SpringAI+Vue3+ElementPlus的QA系统实现(前端)

整理不易,请不要吝啬你的赞和收藏。

1. 前言

这篇文章是 Spring AI Q&A 系统的前端实现。这篇文章将介绍如何快速搭建一个基于 vue3 + ElementPlus 的前端项目,vue3 项目的目录结构介绍,如何在前端实现流式响应,如何高亮显示代码等。

效果展示:

2. 前提条件

  • 后端实现:

    【Spring AI】基于SpringAI+Vue3+ElementPlus的Q&A系统实现(后端)-CSDN博客文章浏览阅读762次,点赞26次,收藏24次。这篇文章将介绍如何基于 RAG 技术,使用 SpringAI + Vue3 + ElementPlus 实现一个 Q&A 系统。本文使用 deepseek 的 DeepSeek-V3 作为聊天模型,使用阿里百炼的 text-embedding-v3 作为向量模型,使用 redis 作为向量库。(PS:近期阿里百炼也上架了 DeepSeek-V3 和 DeepSeek-R1 模型供开发者调用,如果觉得 DeepSeek 官方 AP I比较慢的话,可以去试试)。 https://blog.csdn.net/u013176571/article/details/145368559
  • 已安装 18.3 或更高版本的 Node.js ,未安装进入 官网下载 ,LTS 为长期支持版,Current 为最新功能版。

3. 快速搭建 Elemenet-Plus 项目

我这里直接使用官网的快速搭建模板 element-plus-vite-starter ,其它方式参考 Element-Plus 官网 。

3.1 项目下载

# 下载模板
git clone https://github.com/element-plus/element-plus-vite-starter.git
# 进入项目,安装依赖包
npm install

3.2 项目结构介绍

VS Code 中引入项目,项目结构如下:

项目结构介绍:

element-plus-vite-starter/
├── node_modules/            # 存放所有安装的依赖包
├── public/                  # 静态资源文件夹
│   └── favicon.svg          # 默认的站点图标
├── src/                     # 源代码文件夹
│   ├── assets/              # 静态资源(如图片、样式等)
│   ├── components/          # Vue 组件文件夹
│   ├──── layouts/           
│   ├────── BaseHeader.vue   # 顶部导航栏布局
│   ├────── BaseSide.vue     # 侧边导航栏布局
│   ├── composables/         # Vue 3 Composition API 的逻辑复用文件夹,存放 useXXX 命名的函数
│   ├── pages/               # 页面视图文件夹
│   ├── styles/              # 样式文件夹,存放全局样式 css 类
│   ├──── element/           
│   ├────── index.scss       # 全局颜色主题配置文件           
│   ├── App.vue              # 根组件
│   ├── main.js              # 项目入口文件
│   ├── components.d.ts      # 组件的类型声明文件
│   ├── env.d.ts             # 环境变量的类型声明文件
│   ├── typed-router.d.ts    # 路由的类型声明文件
│   └── types.ts             # 全局类型定义文件,用于定义 TypeScript 类型
├── .gitignore               
├── eslint.config.ts         # ESLint 配置文件,用于代码质量检查
├── index.html               # 项目入口 HTML 文件,Vite 会以此文件为模板进行开发和构建
├── babel.config.js          # Babel 配置文件(仅在 Vue CLI 项目中)
├── package-lock.json        # npm 生成的锁定文件,确保依赖版本一致
├── package.json             # 项目依赖和配置信息
├── pnpm-lock.yaml           # pnpm 生成的锁定文件,确保依赖版本一致
├── README.md                # 项目说明文档
├── tsconfig.json            # TypeScript 配置文件,定义 TypeScript 编译选项
├── uno.config.ts            # UnoCSS 配置文件,UnoCSS 是一个用于生成原子 CSS 的工具
└── vite.config.ts           # Vite 配置文件(仅在 Vite 项目中)

3.3 启动项目

执行以下命令启动:

# 启动项目
npm run dev

浏览器访问:http://localhost:5173/

4. 页面开发

这篇文章不会提供所有前端代码,我会在主要的卡点提供相应的代码节选。

4.1 .vue 文件介绍

在 Vue3 中,我们通过创建一个 .vue 格式文件来创建页面,一个 .vue 文件由以下几个部分组成:

  • template:组件的模板部分,用于定义组件的 HTML 结构。Vue 会将模板编译为渲染函数,用于生成最终的 DOM。

  • script:组件的逻辑部分,用于定义组件的数据、方法、生命周期钩子等。

  • style:组件的样式部分,用于定义组件的 CSS 样式,支持 CSS、SCSS、Sass、Less 等。

样例:

<template>
  <div class = "m-container" >
    <h1 class = "m-title" >{{ title }}</h1>
    <button @click = "handleClick" >@click 用来绑定点击事件</button>
  </div>
</template>

// lang="ts" 表示标签中的代码是用 TypeScript 编写的
<script lang="ts" setup>
import { onMounted, reactive, toRefs } from "vue";

// 定义变量
const state = reactive({
  title: "页面1",
});
// 用于将响应式对象中的属性转换为响应式引用(ref)
const { title } = toRefs(state)

/**
 * 页面加载事件
 */
onMounted(() => {
});

/**
 * 绑定事件
 */
const handleClick = () => {
    // 变量赋值
    title.value = "页面2"
}
</script>

// lang="less" 表示使用 less 语法,scoped 用来限制作用域,只对当前组件模板生效
<style lang="less" scoped>
.m-container{
    .m-title{            
    }
}
</style>

4.2 网络请求工具类

在 src 目录下创建一个 utils 文件夹,然后创建一个 api.ts 文件,用于发起网络请求。

4.2.1 代码

import axios from 'axios';
import { ElMessage } from 'element-plus';

// 创建一个 axios 实例
const baseURL = 'http://127.0.0.1:8082/your_service_name';
const apiClient = axios.create({
  baseURL: baseURL,
  timeout: 200000, // 请求超时时间
  headers: {
    'Content-Type': 'application/json',
  },
});

const err = (error) => {
  console.log('error', error)
  if (axios.isCancel(error)) {
    return
  }
  if (!error.response) {
    ElMessage.error({
      message: '请求超时请检查网络链接!',
      offset: 80,
    })
    return Promise.reject(error)
  }

  const data = error.response.data
  if (!data || data.code !== 200) {
    ElMessage.error(data.message || "发生未知错误,请稍后再试!")
  }
  return Promise.reject(error)
}

// 请求拦截器
apiClient.interceptors.request.use(
  (config) => {
    // 在发送请求之前做些什么,例如添加 token
    const token = localStorage.getItem('token'); 
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }
    return config;
  },
  err
);

// 响应拦截器
apiClient.interceptors.response.use(
  (response) => {
    // 对响应数据做点什么
    return response.data;
  },
  err
);

// 定义 API 请求方法
const api = {
  getBaseUrl() {
    return baseURL;
  },
  get(url, params) {
    return apiClient.get(url, { params });
  },
  post(url, params, config) {
    return apiClient.post(url, params, config);
  },
};
export default api;

4.2.2 如何引用?

在需要引用的页面的 script 标签中 或 .ts 文件下键入:

import api from '@/utils/api';

4.2.3 如何调用?

const param = {};
const config = {
    headers: {
        'Content-Type': 'multipart/form-data',
    },
};
api.post("/ai/chat/fileUploadWithRag",param)
    .then((response) => {
      console.log("Response received:", response);
    })
    .catch((error) => {
      // 处理错误
      console.error("请求失败:", error);
    });

4.3 如何接收 SSE 响应式消息

SSE (Server-Sent Events)是一种允许服务器向客户端推送数据的技术,属于 HTML5 的一部分。它支持服务器向客户端的单向通信,客户端通过一次长连接持续接收服务器推送的数据。响应式编程(Reactive Programming)非常适合实现 SSE,因为它允许以非阻塞的方式持续推送数据,不会阻塞服务器资源。在 SpringBoot 中可以使用 Spring WebFlux 框架来实现 SSE。在 Web 端实现 SSE 通常使用 EventSource 对象。

4.3.1 代码

同样在 utils 目录下,创建一个 sse.ts 的工具类。

class SSE {
    private eventSource: EventSource | null = null;
    /**
     * 连接 SSE
     * @param url
     * @param onMessage 
     * @param onError 
     */
    public connect(url: string, onMessage: (event: MessageEvent) => void, onError?: (event: Event) => void): void {
      this.eventSource = new EventSource(url);
      // 监听消息事件
      this.eventSource.onmessage = onMessage;
      // 监听错误事件
      if (onError) {
        this.eventSource.onerror = onError;
      }
    }
    
    /**
     * 关闭 SSE 连接
     */
    public close(): void {
      if (this.eventSource) {
        this.eventSource.close();
        this.eventSource = null;
      }
    }
  }
  export default SSE;

4.3.2 如何引用?

import SSE from "@/utils/sse";

4.3.3 如何调用

需要注意的是,SSE 仅支持 GET 调用。

const state = reactive({
  sse: new SSE(),
});
const { sse } = toRefs(state);

// 接收消息的回调函数
const handleMessage = (event: MessageEvent) => {
  eventMessage = eventMessage.concat(event.data);
};
// 错误处理的回调函数
const handleError = (event: Event) => {
  stopGenerate();
  console.log('SSE 连接错误:', event);
};
const url = "接口地址?param1=param1&param2=param2"
sse.value.connect(url, handleMessage, handleError);

4.4 对话组件

template 中主要代码节选,通过 message.sender 的值加载用户和AI消息的样式。

<el-scrollbar ref="scrollbar" class="message-list" always @scroll="handleScroll">
    <div v-for="message in messages" :key="message.id" class="message-wrapper" :class="{
      'user-message': message.sender === 'user',
      'bot-message': message.sender === 'bot',
    }">
      <div class="message-bubble">
        <div class="message-content" v-html="message.content"></div>

      </div>
    </div>
</el-scrollbar>

script 中主要代码节选:

// 定义一个 Message 接口
interface Message {
  id: number;
  content: string;
  sender: "user" | "bot";
  timestamp: number;
}

// 定义变量
const state = reactive({
  messages: [] as Message[],
});
const { messages } = toRefs(state);

4.5 如何加载 Markdown 内容

由于大模型返回的流一般为 Markdown 格式,我们使用第三方库 markdown-it 来加载内容。

4.5.1 安装 markdown-it

由于我需要支持代码高亮、数学公式、流程图等,所以我安装了额外的插件来扩展 MarkdownIt 的功能。

npm install markdown-it highlight.js katex mermaid markdown-it-sub markdown-it-sup markdown-it-emoji markdown-it-task-lists markdown-it-footnote markdown-it-deflist markdown-it-abbr markdown-it-ins markdown-it-mark

4.5.2 如何使用

template 中代码节选:

<template>
    <div class="message-content" v-html="message.content"></div>
</template>

script 中代码节选:

// 引入 markdown-it
import MarkdownIt from "markdown-it";
// 定义MarkdownIt对象
const md = new MarkdownIt();

// SSE 接收消息的回调函数
const handleMessage = (event: MessageEvent) => {
  eventMessage = eventMessage.concat(event.data);
  botMessage.content = computed(() => {
    return md.value.render(eventMessage);
  });
  // 设置
  nextTick(() => {
    scrollToBottom();
  });
};

4.6 其它

4.6.1 上传组件

使用 ElementPlus 的 upload 组件。

4.6.2 使用 Alt + Enter 键换行

Input 默认的换行快捷键为 Shift + Enter,不符合我们平时的使用习惯。

const handleKeyDown = (event: KeyboardEvent) => {
  if (event.key === "Enter") {
    // 按下 Alt + Enter 键时,插入换行符
    if (event.altKey) {
      const cursorPosition = event.target.selectionStart;
      const textBeforeCursor = inputMessage.value.slice(0, cursorPosition);
      const textAfterCursor = inputMessage.value.slice(cursorPosition);
      inputMessage.value = textBeforeCursor + '\n' + textAfterCursor;
      event.target.selectionStart = cursorPosition + 1;
      event.target.selectionEnd = cursorPosition + 1;
      return;
    }
    event.preventDefault();
    sendMessage();
  }
};

4.6.3 消息自动滚动到最下方

代码节选:

 
 // 定义变量
const state = reactive({
  autoScroll: true,
  scrollbar: {} as any,
});
const { autoScroll, scrollbar } = toRefs(state);
 
/**
 * 消息滚动事件监听
 */
const handleScroll = ({ scrollTop }: { scrollTop: number }) => {
  const scrollHeight = scrollbar.value.wrapRef?.scrollHeight || 0;
  const clientHeight = scrollbar.value.wrapRef?.clientHeight || 0;
  autoScroll.value = scrollTop + clientHeight >= scrollHeight - 10;
};

/**
 * 滚动到最下方
 */
const scrollToBottom = () => {
  if (autoScroll.value) {
    nextTick(() => {
      scrollbar.value?.setScrollTop(scrollbar.value.wrapRef?.scrollHeight);
    });
  }
};

5. 参考文档

  • Vue3 文档
  • Element Plus 文档
  • markdown-it 文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/969975.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中望CAD c#二次开发 ——VS环境配置

新建类库项目&#xff1a;下一步 下一步 下一步&#xff1a; 或直接&#xff1a; 改为&#xff1a; <Project Sdk"Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>NET48</TargetFramework> <LangVersion>pr…

Java—File

Flie对象就表示一个路径&#xff0c;可以是文件的路径、也可以是文件夹的路径这个路径可以是存在的&#xff0c;也允许是不存在的 file类常用的构造方法&#xff1a; 代码案列&#xff1a; 小结&#xff1a; file的常见成员方法 判断获取相关方法&#xff1a; 代码案例&#…

HTML的入门

一、HTML HTML&#xff08;HyperText Markup Language&#xff0c;超文本标记语言&#xff09;是一种用来告知浏览器如何组织页面的标记语言。 超文本&#xff1a;就是超越了文本&#xff1b;HTML不仅仅可以用来显示文本(字符串、数字之类)&#xff0c;还可以显示视频、音频等…

辛格迪客户案例 | 钥准医药科技GMP文件管理(DMS)项目

01 创新药企&#xff0c;崛起于启东 在我国医药行业蓬勃发展的浪潮中&#xff0c;钥准医药科技&#xff08;启东&#xff09;有限公司&#xff08;以下简称“钥准医药”&#xff09;犹如一颗冉冉升起的新星&#xff0c;闪耀着创新与活力的光芒。成立于2015年&#xff0c;钥准医…

DeepSeek本地化部署【window下安装】【linux下安装】

一、window 本地安装指导 1.1、下载window安装包 https://ollama.com/download/OllamaSetup.exe 1.2、点击下载好的安装包进行安装 检测安装是否成功&#xff1a; C:\Users\admin>ollama -v ollama version is 0.5.7有上面的输出&#xff0c;则证明已经安装成功。 配置…

【第1章:深度学习概览——1.4 深度学习的核心组件与概念解析之激活函数的作用与类型】

嘿,各位技术小伙伴们,今天咱们来聊聊深度学习中的一个超级重要的概念——激活函数。这可是深度学习模型中的“调味剂”,让模型变得更加灵活和强大。准备好了吗?咱们这就开讲! 一、激活函数是什么? 激活函数,简单来说,就是神经网络中的一层“魔法调料”。它给神经网络…

智慧升级,赋能未来——开启安全高效与绿色低碳新篇章

在数字化转型与“双碳”目标的驱动下&#xff0c;古河云科技携手全球领先的AI企业DeepSeek&#xff0c;以“AI数字孪生”为核心&#xff0c;推出全新一代智能运维与能碳管理解决方案&#xff0c;助力企业实现安全管控、设备效能优化、绿色节能与高效管理四大维度的全面升级&…

SpringCloud - Seata 分布式事务

前言 该博客为Sentinel学习笔记&#xff0c;主要目的是为了帮助后期快速复习使用 学习视频&#xff1a;7小快速通关SpringCloud 辅助文档&#xff1a;SpringCloud快速通关 源码地址&#xff1a;cloud-demo 一、简介 官网&#xff1a;https://seata.apache.org/zh-cn/ Seata …

Java面试宝典:说下Spring Bean的生命周期?

Java面试宝典专栏范围&#xff1a;JAVA基础&#xff0c;面向对象编程&#xff08;OOP&#xff09;&#xff0c;异常处理&#xff0c;集合框架&#xff0c;Java I/O&#xff0c;多线程编程&#xff0c;设计模式&#xff0c;网络编程&#xff0c;框架和工具等全方位面试题详解 每…

基于Swift实现仿IOS闹钟

仿 iOS 系统闹钟 添加闹钟效果图 收到通知效果图 更新日志 2018.09.12 由于 iOS 系统限制了注册本地推送的数量&#xff0c;最大的注册量为 64 条&#xff0c;且一旦超出 64 条&#xff0c;所有的推送都将失效&#xff0c;故而在添加推送的时候做了一个判断&#xff0c;超过…

如何使用 DeepSeek R1 构建开源 ChatGPT Operator 替代方案

开源大型语言模型&#xff08;LLMs&#xff09;的兴起使得创建 AI 驱动的工具比以往任何时候都更容易&#xff0c;这些工具可以与 OpenAI 的 ChatGPT Operator 等专有解决方案相媲美。在这些开源模型中&#xff0c;DeepSeek R1 以其强大的推理能力、自由的可访问性和适应性而脱…

力反馈设备在工厂生产中遥操作机器人的应用优势

工业自动化与智能化已经成为现代工厂提升生产效率、保障人员安全的关键手段。在这一背景下&#xff0c;Haption Virtuose力反馈设备凭借其卓越的性能和广泛的应用前景&#xff0c;在机器人遥操作领域脱颖而出&#xff0c;尤其在工厂生产中展现出了显著的应用优势。本文将深入探…

【STM32】输入捕获实现超声波测距

1.超声波测距原理 &#xff08;超声波发出到 遇到障碍物反弹回来的时间&#xff09;*声速/2就是到障碍物的距离 操作过程&#xff1a; 单片机给TRIG引脚输出一个脉冲&#xff0c;然后超声波模块会将ECHO电平拉高&#xff0c;当超声波遇到障碍物回来时&#xff0c;ECHO电平就会…

phpipam1.7安装部署

0软件说明 phpipam是一个开源Web IP地址管理应用程序&#xff08;IPAM&#xff09; phpipam官网&#xff1a;https://www.phpipam.net/ 1安装环境 操作系统&#xff1a;Rocky Linux9.5x86_64 phpipam版本&#xff1a;1.7 php版本&#xff1a;8.0.30 数据库版本&#xff1a…

【C语言】C语言 好声音比赛管理系统(含源码+数据文件)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;专__注&#x1f448;&#xff1a;专注主流机器人、人工智能等相关领域的开发、测试技术。 【C语言】C语言 好声音比赛管理系统&#xff08;含…

WPF进阶 | 深入 WPF 依赖项属性:理解其强大功能与应用场景

WPF进阶 | 深入 WPF 依赖项属性&#xff1a;理解其强大功能与应用场景 前言一、依赖项属性基础概念1.1 什么是依赖项属性1.2 依赖项属性与 CLR 属性的区别1.3 依赖项属性的定义与注册 二、依赖项属性的原理深入剖析2.1 依赖项属性系统的工作机制2.2 元数据&#xff08;Metadata…

QML使用ChartView绘制饼状图

一、工程配置 首先修改CMakeLists.txt&#xff0c;按下图修改&#xff1a; find_package(Qt6 6.4 REQUIRED COMPONENTS Quick Widgets) PRIVATEtarget_link_libraries(appuntitledPRIVATE Qt6::QuickPRIVATE Qt6::Widgets )其次修改main.cpp&#xff0c;按下图修改&#xff…

单片机上SPI和IIC的区别

SPI&#xff08;Serial Peripheral Interface&#xff09;和IC&#xff08;Inter-Integrated Circuit&#xff09;是两种常用的嵌入式外设通信协议&#xff0c;它们各有优缺点&#xff0c;适用于不同的场景。以下是它们的详细对比&#xff1a; — 1. 基本概念 SPI&#xff0…

2025年02月12日Github流行趋势

项目名称&#xff1a;data-formulator 项目地址url&#xff1a;https://github.com/microsoft/data-formulator 项目语言&#xff1a;TypeScript 历史star数&#xff1a;4427 今日star数&#xff1a;729 项目维护者&#xff1a;danmarshall, Chenglong-MS, apps/dependabot, mi…

LeetCode《算法通关手册》 1.2 数组排序

Python强推&#xff1a;算法通关手册&#xff08;LeetCode&#xff09; | 算法通关手册&#xff08;LeetCode&#xff09; (itcharge.cn) 目录 文章目录 1.2 数组排序1.2.1 选择排序1.2.2 冒泡排序[283. 移动零 - 力扣&#xff08;LeetCode&#xff09;](https://leetcode.cn/p…