打造专业级ChatGPT风格聊天界面:SpringBoot与Vue实现动态打字机效果,附完整前后端源码
大家好,今天用SpringBoot、vue写了一个仿ChatGPT官网聊天的打字机效果。
所有代码地址:gitee代码地址 ,包含前端和后端,可以直接运行使用本技术实现的项目:aicnn.cn,欢迎大家体验
如果文章知识点有错误的地方,请指正!大家一起学习,一起进步。
本文主要应用的技术有:SpringBoot、Vue、Reactive、WebFlux、EventSource等,学习和练手的好项目。实现效果如下:
准备好了吗,let’s get it!
采用本文技术实现的前后端项目,点击体验使用:aicnn.cn
文章目录
- 前言
- 项目运行
- SSE技术概览
- 什么是Server-Sent Events?
- SSE与WebSockets的区别
- 为什么选择SSE?
- SSE的应用场景
- 在Spring Boot中实现SSE
- 设置SSE
- 处理连接和事件
- 处理异常和断开连接
- 一些实用的提示
- Vue前端对SSE的处理
- 在Vue中接收SSE
- 保证流畅的用户体验
- 异常处理和重连
- 小结
- 实际案例分析:实时通知系统
- 场景描述
- 为什么选择SSE?
- 实现概览
- 体验优化
- 可能的挑战
- 结语
前言
Web开发的世界永远充满惊喜,不是吗?每当我们认为自己掌握了所有的技巧和工具,总会有新的技术出现,挑战我们的知识库。今天,我们要探讨的这项技术可能对一些人来说并不陌生,但对于其他人来说,则像是新发现的宝藏。没错,我在说的是Server-Sent Events(SSE)。
你可能会问:“SSE是什么?”简单来说,SSE是一种让服务器实时向客户端发送更新的技术。但别误会,这不是另一个WebSockets。SSE和WebSockets之间的斗争,有点像是电影《星球大战》中的帝国和反抗军的斗争——两者都有其优势和用武之地,但战场完全不同。SSE是为了解决特定类型的实时通讯问题而生,而不是为了取代WebSockets。
在本文中,我们将一探究竟,看看SSE到底是什么魔法,以及如何在Spring Boot应用程序和VUE中轻松实现它。当然,我们也不会忘记前端的小伙伴们——我们将一起探索如何在Vue应用中接收和处理这些实时数据,并一起实现类似ChatGPT官网的聊天打字机效果。准备好了吗?让我们开始这趟探索之旅吧!
项目运行
所有代码地址:代码地址 ,包含前端和后端,可以直接运行。
springboot安装依赖,并设置对应的sd-key即可:
api key 的获取方式如下:
- 第一步:打开aicnn.cn
- 第二步:进入设置页面
- 第三步:点击创建新的秘钥
- 第四步:复制密钥值,替换上面代码中的
sk-*******
,替换后的代码如下所示:.header("Authorization", "Bearer sk-1234567890123456789")
前端项目采用vue3实现,
在项目中,使用如下命令运行项目,即可运行前端:
yarn install
yarn serve
SSE技术概览
什么是Server-Sent Events?
在深入探索Server-Sent Events(SSE)之前,让我们先搞清楚它到底是什么。简单地说,SSE是一种允许服务器主动向客户端发送信息的技术。与传统的请求-响应模式不同,SSE建立了一个单向通道,使得服务器可以实时发送更新。这听起来有点像WebSockets,但SSE的工作方式和用例却大有不同。
SSE与WebSockets的区别
SSE和WebSockets都是实现实时通信的技术,但它们各有千秋。WebSockets提供了一个全双工的通信渠道,允许数据在客户端和服务器之间双向流动。相比之下,SSE是单向的——仅从服务器到客户端。这意味着如果你需要从客户端向服务器发送数据,SSE可能就不是最佳选择了。SSE的优势在于它的简单性和轻量级,特别适用于那些仅需要服务器单向传送数据的场景,比如实时新闻更新、股票行情等。
为什么选择SSE?
现在你可能在想:“为什么我要使用SSE而不是其他技术?”好问题!首先,SSE在浏览器中有很好的支持,这使得它非常容易实现。其次,由于SSE是基于HTTP的,它可以利用现有的HTTP协议特性,如缓存、认证等。这些特性在WebSockets中可能需要额外的处理。最后,SSE的轻量级特性使其成为一种高效的实时数据传输方式,尤其是当你只需要服务器到客户端的单向数据流时。
SSE的应用场景
SSE最适合的是那些需要服务器定期或不定期推送信息到客户端的场景。例如,如果你正在开发一个需要显示实时消息、股票行情或任何形式实时数据的应用,SSE是一个不错的选择。它的轻量级和易用性使其成为这些类型应用的理想选择。
在Spring Boot中实现SSE
设置SSE
在Spring Boot中实现SSE并不复杂。其核心在于使用Spring框架的webflux
。使用webflux类能够创建一个持久的连接,使服务器能够向客户端发送多个事件,而无需每次都建立新的连接。
先在pom中引入相关依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
然后需要在Controller中创建一个方法来返回Flux
实例。这个方法将被映射到特定的URL,客户端将使用这个URL来接收事件。
package cn.aicnn.chatssespringboot.controller;import cn.aicnn.chatssespringboot.dto.AIAnswerDTO;
import cn.aicnn.chatssespringboot.service.GptServiceImpl;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;import javax.annotation.Resource;@RestController
public class ChatController {//用于流式请求第三方的实现类@ResourceGptServiceImpl gptService;//通过stream返回流式数据@GetMapping(value = "/completions", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<ServerSentEvent<AIAnswerDTO>> getStream(@RequestParam("messages")String messages) {return gptService.doChatGPTStream(messages)//实现类发送消息并获取返回结果.map(aiAnswerDTO -> ServerSentEvent.<AIAnswerDTO>builder()//进行结果的封装,再返回给前端.data(aiAnswerDTO).build()).onErrorResume(e -> Flux.empty());//发生异常时发送空对象}}
处理连接和事件
一旦有客户端连接到这个URL,可以通过调用GptServiceImpl
实例的doChatGPTStream
方法来发送事件。这些事件可以是简单的字符串消息,也可以是更复杂的数据结构,如JSON对象。记住,SSE的设计初衷是轻量级和简单,所以你发送的每个事件都应当是独立的和自包含的。
GptServiceImpl
的实现方式如下,也是springboot后端实现的重点
package cn.aicnn.chatssespringboot.service;import cn.aicnn.chatssespringboot.dto.AIAnswerDTO;
import cn.aicnn.chatssespringboot.dto.ChatRequestDTO;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;import javax.annotation.PostConstruct;
import java.util.*;/*** @author aicnn.cn* @date 2023/2/13* @description: aicnn.cn**/
@Service
public class GptServiceImpl {//webflux的clientprivate WebClient webClient;//用于读取第三方的返回结果private ObjectMapper objectMapper = new ObjectMapper();@PostConstructpublic void postConstruct() {this.webClient = WebClient.builder()//创建webflux的client.baseUrl("https://api.aicnn.cn/v1")//填写对应的api地址.defaultHeader("Content-Type", "application/json")//设置默认请求类型.build();}//请求stream的主题public Flux<AIAnswerDTO> doChatGPTStream(String requestQuestion) {//构建请求对象ChatRequestDTO chatRequestDTO = new ChatRequestDTO();chatRequestDTO.setModel("gpt-3.5-turbo");//设置模型chatRequestDTO.setStream(true);//设置流式返回ChatRequestDTO.ReqMessage message = new ChatRequestDTO.ReqMessage();//设置请求消息,在此可以加入自己的promptmessage.setRole("user");//用户消息message.setContent(requestQuestion);//用户请求内容ArrayList<ChatRequestDTO.ReqMessage> messages = new ArrayList<>();messages.add(message);chatRequestDTO.setMessages(messages);//设置请求消息//构建请求jsonString paramJson = JSONUtil.toJsonStr(chatRequestDTO);;//使用webClient发送消息return this.webClient.post().uri("/chat/completions")//请求uri.header("Authorization", "Bearer sk-**************")//设置成自己的key,获得key的方式可以在下文查看.header(HttpHeaders.ACCEPT, MediaType.TEXT_EVENT_STREAM_VALUE)//设置流式响应.contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(paramJson)).retrieve().bodyToFlux(String.class).flatMap(result -> handleWebClientResponse(result));//接收到消息的处理方法}private Flux<AIAnswerDTO> handleWebClientResponse(String resp) {if (StrUtil.equals("[DONE]",resp)){//[DONE]是消息结束标识return Flux.empty();}try {JsonNode jsonNode = objectMapper.readTree(resp);AIAnswerDTO result = objectMapper.treeToValue(jsonNode, AIAnswerDTO.class);//将获得的结果转成对象if (CollUtil.size(result.getChoices()) > 0 && !Objects.isNull(result.getChoices().get(0)) &&!StrUtil.isBlank(result.getChoices().get(0).delta.getError())){//判断是否有异常throw new RuntimeException(result.getChoices().get(0).delta.getError());}return Flux.just(result);//返回获得的结果} catch (Exception e) {throw new RuntimeException(e.getMessage());}}
}
在这里,我们请求的api地址设置为https://api.aicnn.cn/v1,并且设置从aicnn.cn获取的api key即可。
处理异常和断开连接
在使用SSE时,处理异常和断开连接也非常重要。确保在客户端断开连接或发生异常时,正确地关闭webflux
实例。这有助于避免资源泄露和其他潜在问题。
一些实用的提示
- 超时管理:SSE连接可能因为超时而被关闭。确保妥善处理这种情况。
- 错误处理:适当地处理可能发生的异常,如网络问题或客户端断开连接。
- 资源清理:在连接结束时清理资源,确保应用的健康和性能。
至此,我们就完成了SpringBoot的SSE后端开发。
Vue前端对SSE的处理
在Vue中接收SSE
在Vue应用中接收SSE消息是相对直截了当的。需要做的基本上就是在Vue组件中创建一个新的EventSource
实例,并指向你的Spring Boot应用中设置的SSE URL,本文使用EventSource作为示例,也可以选择axios或@microsoft/fetch-event-source发送post请求的SSE请求,使用另外两种的好处是可以控制header,携带token信息,以便于控制权限。
this.eventSource = new EventSource('http://127.0.0.1:8080/completions?messages='+this.inputText);
一旦建立了连接,就可以定义各种事件监听器来处理从服务器接收到的消息。在Vue中,这通常涉及到更新组件的数据属性,这些属性又通过Vue的响应式系统自动更新UI。
sendSSEMessage() {// 只有当eventSource不存在时才创建新的EventSource连接if (!this.eventSource) {this.messages.push({text: this.inputText, isMine: true});this.messages.push({text: "", isMine: false});// 创建新的EventSource连接this.eventSource = new EventSource('http://127.0.0.1:8080/completions?messages='+this.inputText);// 设置消息接收的回调函数this.eventSource.onmessage = (event) => {const data = JSON.parse(event.data);this.messages[this.messages.length - 1].text += data.choices[0].delta.content;};// 可选:监听错误事件,以便在出现问题时能够重新连接或处理错误this.eventSource.onerror = (event) => {console.error("EventSource failed:", event);this.eventSource.close(); // 关闭出错的连接this.eventSource = null; // 重置eventSource变量,允许重建连接};}}
保证流畅的用户体验
当处理实时数据时,保证一个流畅且不中断的用户体验至关重要。在Vue中,这意味着需要确保UI的更新是平滑和高效的。幸运的是,Vue的响应式系统会处理大部分重活,但你仍需要注意不要进行不必要的大规模DOM操作或数据处理。
异常处理和重连
处理连接中断或其他异常也是至关重要的。你可能需要在失去连接时尝试重新连接,或者至少提醒用户当前的连接状态。这可以通过监听EventSource
的错误事件并采取适当的行动来实现。
小结
将SSE与Vue结合使用,可以为用户提供一个富有动态性和实时性的界面。无论是实时消息、通知,还是实时数据流,SSE都能让Vue应用更加生动和实用。
当然,除了使用SSE进行文字聊天的打字机模式删除,SSE技术还有应用:
实际案例分析:实时通知系统
场景描述
想象一下,你正在为一个大型在线零售平台工作,该平台需要一种方法来实时通知其用户关于订单状态的更新。在这种情况下,使用SSE来推送通知变得非常有意义。这不仅提高了用户体验,还减少了服务器的负载,因为不需要频繁的轮询。
为什么选择SSE?
在这个场景中,SSE提供了一种高效且轻量的方式来实时更新用户的订单状态。由于订单状态更新不需要来自客户端的即时响应,SSE的单向数据流完全符合需求。相比于使用WebSockets,这种方式更加简单且资源消耗更少。
实现概览
在Spring Boot后端,你可以设置一个SSE端点,以便在订单状态发生变化时发送通知。
在Vue前端,可以创建一个监听SSE消息的组件。当接收到新的订单状态更新时,该组件可以相应地更新UI,为用户提供实时反馈。
体验优化
为了优化用户体验,还可以在前端实现一些附加功能,比如当用户离开页面时暂停事件流,或者在网络不稳定导致连接断开时自动重连。
可能的挑战
当然,实现SSE并非没有挑战。例如,你需要确保在高并发场景下后端能够有效地处理大量的连接。此外,处理网络不稳定情况下的重连机制也是需要考虑的。
结语
回顾我们今天的探索,我们可以看到Server-Sent Events(SSE)在现代web开发中的价值和应用潜力。通过在Spring Boot后端轻松实现SSE,并在Vue前端高效处理这些实时事件,我们可以为用户创造出更加动态和互动的体验,并使用SSE从0开始实现了类似ChatGPT聊天的前后端,以及打字机效果的输出。
SSE提供了一种简单、高效的方法来处理单向数据流。它是那些需要服务器向客户端实时推送数据的应用的理想选择。无论是实时通知、实时新闻更新,还是其他任何需要实时数据的场景,SSE都能够提供一种轻量级且易于实现的解决方案。
当然,每种技术都有其适用场景。SSE并不是万能的,它最适合于那些不需要客户端向服务器发送数据的场景。在选择技术方案时,了解你的需求和各种技术的优势及局限性至关重要。
最后,鼓励所有的开发者尝试将SSE集成到自己的项目中。感谢你和我一起探索SSE的奇妙世界。现在,该是你动手实践的时候了!祝你编码愉快!
相关文章:

打造专业级ChatGPT风格聊天界面:SpringBoot与Vue实现动态打字机效果,附完整前后端源码
大家好,今天用SpringBoot、vue写了一个仿ChatGPT官网聊天的打字机效果。 所有代码地址:gitee代码地址 ,包含前端和后端,可以直接运行 使用本技术实现的项目:aicnn.cn,欢迎大家体验 如果文章知识点有错误的地方…...
KMP-重复子字符串
Problem: 459. 重复的子字符串 文章目录 题目思路复杂度Code 题目 给定一个字符串str1, 判断其是否由重复的子串构成。 例子1:输入 str1‘ababab’ ;输出 true 例子2:输入 str1‘ababac’ ;输出 false 思路 重复子字符串组成的字…...
如何使用Markdown生成目录索引
一、Markdown生成目录索引怎么折叠 在Markdown中,可以使用[TOC]语法生成目录索引。 如果想要折叠目录,则需要使用一些插件,如Tocbot、jquery-tocify等。 例如,在使用Tocbot时,可以在Markdown文档中加入以下代码&…...
R语言【taxa】——as_taxon():转换为 taxon 对象
Package taxa version 0.4.2 Description 将其他对象转换为 taxon 向量。taxon 构造器可能将 基础向量转换为 taxon 向量。 Usage as_taxon(x, ...) Arguments 参数【x】:要转换为 taxon 向量的对象。 参数【...】:其余参数。 Examples x <- taxo…...

Android状态栏布局隐藏的方法
1.问题如下,安卓布局很不协调 2.先将ActionBar设置为NoActionBar 先打开styles.xml 3.使用工具类 package com.afison.newfault.utils;import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.graph…...

idea创建公用依赖包项目
创建parent项目 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/…...

设计模式之装饰器模式
面向对象设计原则 接口隔离原则:面向对象设计之接口隔离原则-CSDN博客 设计模式 工厂模式 : 设计模式之工厂模式-CSDN博客 迭代器模式:设计模式之迭代器模式-CSDN博客 适配器模式:设计模式之适配器模式-CSDN博客 过滤器模式&#…...
【Java万花筒】缓存与存储:Java应用中的数据处理利器
激发性能之源:深度剖析Java开发中的五大数据缓存与存储方案 前言 在现代软件开发中,高效地处理和存储数据是至关重要的任务。本文将介绍一系列在Java应用中广泛使用的数据缓存与存储库,涵盖了Ehcache、Redisson、Apache Cassandra、Hazelca…...
解决nodejs报错内存泄漏问题,项目无法运行
解决方法一 一、使用 increase-memory-limit npm install increase-memory-limit //本项目中使用// 或 npm install -g increase-memory-limit //全局安装二、安装 npm install --save cross-env 配置package.json文件 LINMIT大小 81928g "scripts": {"f…...

计算机网络-物理层基本概念(接口特性 相关概念)
文章目录 总览物理层接口特性星火模型给出的相关概念解释(仅供参考) 总览 求极限传输速率:奈氏准则,香农定理(背景环境不一样) 编码:数据变成数字信号 调制:数字信号变成模拟信号 信…...

从规则到神经网络:机器翻译技术的演化之路
文章目录 从规则到神经网络:机器翻译技术的演化之路一、概述1. 机器翻译的历史与发展2. 神经机器翻译的兴起3. 技术对现代社会的影响 二、机器翻译的核心技术1. 规则基础的机器翻译(Rule-Based Machine Translation, RBMT)2. 统计机器翻译&am…...
python 面经
关于自身特点 1. 介绍下自己,讲一下在公司做的项目 2. 说一下熟悉的框架,大致讲下其特点 python 基础 1.可变与不可变类型区别 2.请解释join函数 3.请解释*args和**kwargs的含义,为什么使用* args,** kwargs? 4.解释…...
Ubuntu (Linux) 下创建软链接(即符号链接,相当于windows下的快捷方式)方法
Ubuntu (Linux) 下创建软链接(即符号链接,相当于windows下的快捷方式)方法 使用创建软链接的命令 #命令格式如下。注意:请使用绝对路径,否则链接可能失效 ln -s <源文件或目录的绝对路径> <符号链接文件&am…...

LeetCode.2765. 最长交替子数组
题目 2765. 最长交替子数组 分析 为了得到数组 nums 中的最长交替子数组的长度,需要分别计算以每个下标结尾的最长交替子数组的长度。为了方便处理,计算过程中需要考虑长度等于 1 的最长交替子数组,再返回结果时判断最长交替子数组的长度…...

Springboot日志框架logback与log4j2
目录 Springboot日志使用 Logback日志 日志格式 自定义日志格式 日志文件输出 Springboot启用log4j2日志框架 Springboot日志使用 Springboot底层是使用slf4jlogback的方式进行日志记录 Logback日志 trace:级别最低 debug:调试级别的,…...

浪花 - 用户信息展示+更新
1. 用户登录获取登录凭证 已登录的用户才能获取个人信息发送 Aixos 请求登录 const user ref();onMounted(async () > {const res await myAxios.get(/user/current);if (res.code 0) {console.log("获取用户信息成功");user.value res.data;} else {consol…...

xxe漏洞之scms靶场漏洞
xxe-scms 代码审核 (1)全局搜索simplexml_load_string simplexml_load_string--将XML字符串解释为对象 (2)查看源代码 ID1 $GLOBALS[HTTP_RAW_POST_DATA]就相当于file_get_contents("php://input"); 因此这里就存…...

Unity3d C#实现三维场景中图标根据相机距离动态缩放功能
前言 如题的需求,其实可以通过使用UI替代场景中的图标来实现,不过这样UI的处理稍微麻烦,而且需要在图标上添加粒子特效使用SpriteRender更方便快捷。这里就根据相机离图标的位置来计算图标的缩放大小即可。这样基本保持了图标的大小…...

Linux网络编程(二-套接字)
目录 一、背景知识 1.1 端口号 1.2 网络字节序 1.3 地址转换函数 二、Socket简介 三、套接字相关的函数 3.1 socket() 3.2 bind() 3.3 connect() 3.4 listen() 3.5 accept() 3.6 read()/recv()/recvfrom() 3.7 send()/sendto() 3.8 close() 四、UPD客服/服务端实…...

【DeepLearning-1】 注意力机制(Attention Mechanism)
1.1注意力机制的基本原理: 计算注意力权重: 注意力权重是通过计算输入数据中各个部分之间的相关性来得到的。这些权重表示在给定上下文下,数据的某个部分相对于其他部分的重要性。 加权求和: 使用这些注意力权重对输入数据进行加权…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...