Yanlin's博客 - Yanlin
type: channel
link: https://blog.yan-lin.cn/author/1/
description: Yanlin
items: 一种利用免费CDN加速视频站点的思路
items: 使用VSCode配置Java Web 开发环境（Servlet）
items: 统一验证系统代码实现：2.初始化数据库
items: 统一验证系统代码实现：1.获取配置文件
items: 记录一下自己的第一个Go项目——统一验证系统后端
items: 《Java编程课程设计》设计报告书——《保卫JAVA》塔防游戏
items: 旧书回收利用系统--后端使用文档
items: Vue学习笔记12-Pinia集中式状态管理
items: Vue学习笔记11-路由
items: Vue学习笔记10-自定义钩子

一种利用免费CDN加速视频站点的思路
type: item
date: 2026-06-27T23:18:00+08:00
description: 现有问题当你在做一个视频站点的时候，你是否遇到下列痛点：服务器带宽不够，人一多就爆炸卡顿？原视频画质高，文件大，CDN拒绝缓存大文件，又不想压缩画质？对资源的访问没有保护，任何人都可以直接观看视频？对于第三点鉴权，一般情况下，我们都是使用token的形式，因为浏览器访问视频资源的时候，是GET请求，如果需要鉴权，那么就需要在GET里带Query，类似：https://example.com/test.mp4?token=xxx 这种鉴权方式本来没有问题，但是一旦我们想要利用CDN的缓存机制来加速的时候，就出事了。对于CDN来讲，当一个用户第一次访问这个资源的时候，他就会把这个资源缓存到边缘节点，当下一次访问的时候，就不会回源，就例如用户刚刚访问里上面那个视频，CDN就会把视频缓存到边缘节点，然后标注，这是路径/test.mp4?token=xxx的资源，下次不用回源。但是如果另一个用户也需要访问这个视频，他的token会和第一个用户不同，他的访问路径类似：https://example.com/test.mp4?token=yyy CDN会检查路径/test.mp4?token=yyy，但是他和/test.mp4?token=xxx是不同的，因为token不同。这样的话，CDN会认为没有命中缓存，继续回源，这就造成了带宽和流量的浪费，尤其是带宽小的情况下尤为严重，会导致其他用户的卡顿。当然，在大部分主流CDN厂商的设置中，我们都可以设置忽略查询字符串来去掉后面的token部分来让第二个用户吃到缓存：但是，这样也让鉴权失去了意义。如果去除掉查询字符串，任何一个人访问/test.mp4都将可以访问到这个视频，因为由于直接访问了，而且服务器端将收不到任何信息，这样就无法审计观看情况了。那有没有什么好的解决方案呢？有的兄弟，有的，请继续往下看。视频分片首先需要解决的是视频太大的问题，这个非常好解决，视频分片技术已经出现很久了，我们通过把一个大视频拆分成一个m3u8文件和多个ts文件，这样就可以让CDN缓存每一个ts文件，完美解决了CDN缓存的文件不能太大的问题。我们可以使用高性能的开源套件FFmpeg来快速、高效率的实现。1.安装FFmpegFFmpeg可以很方便的使用包管理器安装：Ubuntu / Debian 系：sudo apt update sudo apt install ffmpegCentOS / RHEL / Fedora 系：sudo dnf install ffmpeg之后在控制台输入 ffmpeg -version可以看到如下界面，就说明成功了。2.安装显卡驱动（可选）如果你的服务器有GPU，那么你可以利用GPU来进行转码，接下来是英特尔N150的核显+Debian的安装教程，其他品牌的应该类似。执行命令：sudo apt update sudo apt full-upgrade -y sudo apt install -y \ linux-image-amd64 \ firmware-intel-graphics \ mesa-utils \ libgl1-mesa-dri \ mesa-vulkan-drivers \ vainfo \ intel-media-va-driver-non-free \ intel-gpu-tools sudo reboot安装完成后。输入 lsmod | grep i915 （其他显卡需要把i915修改成对应的），显示如下则说明已经成功读取到显卡驱动了。3.转码测试执行命令：ffmpeg \ -i input.mp4 \ -c:v libx264 \ -preset veryfast \ -crf 23 \ -threads 1 \ -c:a aac \ -b:a 128k \ -f hls \ -hls_time 10 \ -hls_list_size 0 \ -hls_segment_filename "output_hls/seg_%03d.ts" \ "output_hls/index.m3u8"输入的mp4文件和输出文件名需要根据真实情况修改，之后你应该在可以看到类似这样的目录结构：output_hls/ ├── index.m3u8 ├── seg_000.ts ├── seg_001.ts ├── seg_002.ts └── ...那么恭喜你，ffmpeg转码正常4.代码实施以下是一个完整的go语言代码，包含GPU加速失败回退、合法性校验等，最终输出一个HLS文件夹。package common import ( "encoding/json" "fmt" "log" "os" "os/exec" "path/filepath" "runtime" "strc…

使用VSCode配置Java Web 开发环境（Servlet）
type: item
date: 2025-09-23T23:35:00+08:00
description: 0.配置基本的Java环境详见以前的推文：VSCode配置java开发环境 - Yanlin's博客需要安装JDK17及以上的版本，推荐最新的JDK21。当配置成功后，在命令提示符中输入java -version应该会显示类似以下的内容：1.安装Mavenmaven对于Servlet的开发并不是一个必须安装的模块，但是maven可以在管理依赖、编译的时候提供便利，同时也为后续大项目做准备。所以还是推荐先安装上。注：以下所有安装均推荐在虚拟网卡加速下进行，否则下载速度较慢。1.1下载Maven打开Maven官网下载：Download Apache Maven – Maven 找到Windows版本并下载。下载后是一个zip格式的压缩包，将其解压到一个合适的位置。如果你想完全跟随本教程，那么你需要将其解压到C盘根目录。解压后应该显示如下：1.2配置环境变量接下来，我们需要在环境变量中加入Maven的路径来方便我们后续在VScode中的调用。打开设置-搜索栏搜索“环境”-编辑账户的环境变量。双击“Path”点击右上角的“新建”，之后将Maven的安装目录添加到环境变量中。如果你前面完全按照教程来操作，那么你应该添加：C:\apache-maven-3.9.11\bin。之后点击“确认”关闭所有窗口。1.3测试安装打开命令提示符并输入：mvn -version 如果出现类似下面的信息，那么恭喜你，安装成功！2.安装Tomcat2.1下载Tomcat打开Tomcat官网：Apache Tomcat® - Apache Tomcat 10 Software Downloads 即可下载GH老师推荐的Tomcat10。这里推荐下载exe格式的方便安装。2.2安装Tomcat下载后打开安装包，连续点击下一步直到这个界面：这里点击Full，为了防止某些功能之后会用到。下一步可以配置端口，无特殊要求默认8080即可。在这里你需要配置你的jdk路径，注意必须在17及以上的版本。接下来点击next安装即可。2.3测试首先打开Monitor Tomcat。接下来在右下角的状态栏里应该可以看到Monitor的图标。双击即可打开配置页面。如果Service Status状态不为Started，那么你需要点击Start来启动。之后就可以打开网页：http://localhost:端口打开Tomcat。如果你是默认的设置，那么就应该是 http://localhost:8080如果你打开显示下面类似的网页，那么恭喜你，配置成功！3.配置VScode完成了以上的安装后，我们就可以在vscode中配置了。3.1安装插件搜索Extension Pack for Java来一次性安装相应的Java插件。为了方便与Tomcat通信，还需要下载Community Server Connectors插件3.2创建新项目将一个方便的文件夹目录添加到VScode中。右键空白处——Maven——从Maven原型创建新项目。这里选择maven-archetypr-webapp正常情况下选择1.4版本即可在这里你可以自定义你的包名字，使用.隔开，当然你也可以直接使用默认的com.example在这里是你的项目名称，这个不能和你以后的其他项目名称重复。之后会弹出设置文件夹的窗口，我们直接在这个目录下即可。当提示输入版本是直接回车默认即可。这里输入y确认配置。出现绿色的BUILD SUCCESS 就代表配置成功了。3.3配置Servlet配置完成后，右侧会出现新文件夹，打开里面的pom.xml文件。将这里的1.7版本修改为21（或你安装的版本）之后再上面的搜索栏输入：>reload并选择Java 重新加载项目即可加载。接下来再上面的输入框中搜索：>maven并选择添加依赖选择对应的项目搜索：servlet选择第一个：servlet-api之后等待Java插件自动下载后就为安装完成。之后需要和上面一样重载java配置。3.3测试开发3.3.1测试Servlet在src\main下创建新的java文件夹，之后再次创建符合前面Maven里配置的包路径相应的文件夹。创建Test.java粘贴代码：package 你的包路径; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServl…

统一验证系统代码实现：2.初始化数据库
type: item
date: 2025-08-09T17:28:53+08:00
description: gorm是一个很方便的数据库连接器，使用这个包：gorm.io/gorm连接数据库首先我们需要根据配置文件组件数据库链接，如下：dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", AppConfig.DBUser, AppConfig.DBPassword, AppConfig.DBHost, AppConfig.DBPort, AppConfig.DBName)Sprintf与c语言的类似，相当于把print出来的内容赋值给一个变量。接下来就可以使用gorm.Open()连接数据库了db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logLevel), }) if err != nil { log.Fatalf("连接数据库失败: %v", err) }迁移数据库表如果连接成功，就可以开始迁移表了。gorm的迁移功能十分强大，它可以检测数据库中表的类型和对应的数据结构类型是否一致，如果不一致就会自动修改，并且不会影响到原有的数据。如果数据库中没有这个表，它也会按照数据结构来创建。例如InviteCode就会创建invitecodes表，即把大写字母去除并在最后加上一个s可以使用db.AutoMigrate(&InviteCode{})来完成迁移，这里的InviteCode是一个数据结构，如下：type InviteCode struct { gorm.Model Code string `gorm:"type:varchar(100);uniqueIndex" json:"code"` Count int `gorm:"type:int;default:0" json:"count"` MaxCount int `gorm:"type:int;default:1" json:"max_count"` CreatedBy uint `gorm:"type:int" json:"created_by"` ExpTime *time.Time `gorm:"type:datetime;default:null" json:"exp_time"` IsAvailable bool `gorm:"type:boolean;default:true" json:"is_available"` BindGroup uint `gorm:"type:int" json:"bind_group"` }gorm.Model会自动帮我们创建id，updatetime，createtime，deletetime等基础元素。值得注意的是，gorm会自动帮我们自动转换列名称，转换逻辑是大写字母全部转为小写，若是中间的大写字母则在大写字母前添加下划线。如上面的邀请码数据结构就会生成如下的表：创建默认管理员账户迁移完成后，很重要的一步是需要判断是否需要创建默认管理员账户。当用户第一次启动时，应该创建一个管理员账户。当用户设置了DEFAULT_ADMIN_USERNAME和DEFAULT_ADMIN_PASSWORD时会根据设置创建对应的账号，如果没有设置，那么就会使用默认密码：admin/admin123来创建。判断是否要创建的方法也很简单，只需要判断用户表是否为空即可。首先判断用户数量：var userCount int64 if err := db.Model(&User{}).Count(&userCount).Error; err != nil { log.Printf("检查用户数量失败: %v", err) return }接下来完成判断并写入数据库：if userCount == 0 { defaultUsername := os.Getenv("DEFAULT_ADMIN_USERNAME") if defaultUsername == "" { defaultUsername = "admin" } defaultPassword := os.Getenv("DEFAULT_ADMIN_PASSWORD") if defaultPassword == "" { defaultPassword = "admin123" } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(defaultPassword), bcrypt.DefaultCost) if err != nil { log.Printf("生成管理员密码哈希失败: %v", err) re…

统一验证系统代码实现：1.获取配置文件
type: item
date: 2025-08-09T17:01:37+08:00
description: 为了方便部署，我将数据库连接、jwt key和配置开发/部署数据结构首先我设计了Config的数据结构，用于读取环境配置文件。这里我使用了github.com/joho/godotenv包来进行导入。type Config struct { JWTSecret string DBHost string DBPort int DBUser string DBPassword string DBName string Port string Environment string CORSOrigins []string }读取配置文件之后试图读取配置文件，如果没有的话就使用环境变量，还没有的话就使用开发时的默认值。err := godotenv.Load() if err != nil { log.Println("警告: 无法加载 .env 文件，使用默认配置或环境变量") }godotenv.Load()指令可以把目录下的.env文件存储到环境变量中，这样后续的os.Getenv()就可以读取到文件中设置的环境变量。为了防止文件读取错误导致的报错，设计了一个函数来使未读取到文件时使用默认值。func getEnv(key, defaultValue string) string { value := os.Getenv(key) if value == "" { return defaultValue } return value }之后就可以把文件写入配置文件了AppConfig = &Config{ JWTSecret: getEnv("JWT_SECRET", "default-secret-key"), DBHost: getEnv("DB_HOST", "localhost"), DBPort: dbPort, DBUser: getEnv("DB_USER", "root"), DBPassword: getEnv("DB_PASSWORD", ""), DBName: getEnv("DB_NAME", "fastlogin"), Port: getEnv("PORT", "3000"), Environment: getEnv("ENV", "development"), CORSOrigins: corsOrigins, }写入后，就可以使用AppConfig.项目来获取到设置，十分方便。

记录一下自己的第一个Go项目——统一验证系统后端
type: item
date: 2025-08-09T16:55:47+08:00
description: 首先祝贺一下网址备案成功啦！也是用上国内EdgeOne了。为什么会想用Go呢？24年寒假的时候用Flask写了一个图书管理系统，上线以后发现即使使用了多线程但是反应速度还是奇慢无比，人多的时候直接卡死。本来是想优化的，但是最后觉得一不做二不休，不如直接换一个高效一点的语言。本来看上的是java springboot，但是据说系统占用会很大。作为我们这种没米的大学生肯定不是首选。毕竟也租不起这么好的服务器。了解一圈以后觉得go语言简洁高效、运行速度快、可以直接编译成二进制文件，也有丰富的社区资源。所以最后选了go语言。在重写图书管理系统之前，我觉得先写一个统一验证系统练练手。因为如果每个系统都配备独立的鉴权系统的话，写的费力不说，还会浪费很多系统资源，既然要重写了，那还不如接入验证系统。系统的设计一开始看上了其他现有的统一验证系统，但是他们都太过于臃肿了，我们只需要一个可以鉴权、修改密码、登入登出的系统即可，为了遵循轻量化原则，我觉得我的所有系统都使用JWT鉴权。各个系统统一向验证系统鉴权，token存储在用户的浏览器中，这样可以实现同一个浏览器可以一次登录所有系统，避免了繁琐的登录过程。目前是各个系统主动发送登录请求，后面计划做成跳转至统一验证系统登录后，将token存储到当前网站的local storage中。今后会陆续更新有关统一验证系统的具体代码实现

《Java编程课程设计》设计报告书——《保卫JAVA》塔防游戏
type: item
date: 2025-06-10T22:39:00+08:00
description: 题目 ：《保卫JAVA》塔防游戏程序设计一、基本信息设计时间2025.5.9-2025.6.10设计题目《保卫JAVA》塔防游戏程序设计Github开源地址https://github.com/LittleYanlin/Protect-Java团队协作方式Git+Github主要用到的库JFrame、Timer、Image、Toolkit等二、需求分析2.1 课题设计问题分析塔防游戏旨在让玩家通过布置防御塔，抵御一波又一波敌人的进攻，保护核心目标不被敌人摧毁。游戏需实现以下核心功能：地图与场景：创建可容纳防御塔和敌人移动的游戏地图，显示游戏场景。 防御塔系统：玩家可在地图指定位置建造、升级、出售防御塔，防御塔具备攻击范围、攻击力、等属性。 敌人系统：敌人按照预设路线移动，拥有生命值、移动速度、攻击目标等属性，成功抵达终点会对核心目标造成伤害。游戏进程控制：控制敌人的刷新波次，随着波次推进，敌人数量和强度增加，设定游戏胜利和失败条件。 用户交互：提供界面让玩家进行建造、升级、出售、使用道具操作，显示游戏状态信息，如金币数量、剩余生命值等。技能设置 ：玩家可以合理使用金钱，在适当的时候发动技能来帮助赢得游戏，增加了游戏的趣味性2.2 问题分解地图模块：负责地图的创建、显示，以及防御塔可建造区域的判定。 防御塔模块：管理防御塔的建造、升级、出售逻辑，计算防御塔的攻击行为。 敌人模块：控制敌人的生成、移动、攻击和死亡逻辑。 子弹模块：控制子弹的位置、移动、伤害。技能模块：设置技能的释放、伤害、范围。游戏控制模块：协调各模块工作，控制游戏波次，判定游戏胜负。 用户界面模块：实现用户交互功能，展示游戏相关信息。 图片设计模块： 负责地图样式，敌人，子弹，特效等方面的制作，展现游戏特色。三、概要设计3.1 类的设计Panel 类：是所有面板的超类，继承自JPanel类，实现定时器（每秒刷新62.5次界面），添加了鼠标点击、移动监听器以及组件监听器（通过面板的显示与否来开始或停止计时器）其子类有：GamePanel（游戏面板类）、MenuPanel（菜单面板类）、StartPanel（开始面板类）、IntroductionPanel（游戏介绍面板类）App类：是程序的主入口，负责管理卡片布局、切换卡片、删除卡片等操作。Tower类：是所有防御塔的超类，拥有攻击力、攻击范围、攻击速度、价格等属性，以及攻击敌人、升级、发射子弹等方法。 其子类有：ArrayTower（箭塔类）、MagicTower（法师塔类）Enemy类：表示敌人，包含生命值、移动速度、移动路线、攻击力等属性，以及移动、受到攻击、死亡等方法。 其子类有：PaoChe（炮车类）、XiaoBing（小兵类）、Boss（Boss类）Player类：存储玩家的金钱和生命值、有扣钱、加钱、扣血量等方法ImageGather类：存储所有图片的集合，供其他类调用Bullet类：表示子弹，包含子弹速度、移动路线、攻击力等属性，以及受到攻击等方法。3.2 类图（UML 图）图 1 程序UML图3.3 程序流程图与模块层次关系程序流程图图 2 主要流程图模块层次关系图 3 模块层次关系图四、详细设计4.1 数据类型具体实现GamePanel为主要逻辑类类 GamePanel 继承 Panel:属性:​ player: Player​ towers: Tower[]​ enemies: List​ bullets: List​ 关卡地图、敌人波次、金钱、状态等构造方法(cardSwitcher):​ 初始化各种游戏数据​ 设置定时器，每帧调用 updategame() 和 repaint()方法 paint(g):​ 绘制背景、塔、敌人、子弹、按钮、金钱、血量等方法 updategame():​ 如果游戏未开始，直接返回​ 处理敌人生成（按波次、计时器）​ 检查金钱是否足够发动技能​ 处理技能效果（灭霸、冰冻等）​ 敌人移动，若到达终点则扣血​ 塔自动攻击范围内敌人​ 子弹移动并处理命中​ 敌人死亡奖励金钱​ 判断胜利或失败，弹窗提示并结束本关方法 handleMouseClicked(e):​ 判断是否点击返回按钮，切换到菜单​ 判断是否点击塔位，进入建造/升级状态​ 判断是否点击建造/升级按钮，扣钱并建造/升级​ 判断是否点击开始按钮，开始新一波敌人​ 判断是否点击技能按钮，发动技能方法 handleMouseMoved(e):​ 处理鼠标悬停高亮、按钮状态等方法 enemiesNotSpawnSum():返回当前未生成敌人总数4.2 核心算法敌人移动算法图 4 敌人移动算法示意图防御塔攻击算法图 5 防御塔攻击算法示意图子弹追踪算法图 6 子弹追踪算法示意图五、调试分析5.1 问题解决与回顾讨论…

旧书回收利用系统--后端使用文档
type: item
date: 2025-02-21T09:13:00+08:00
description: 后端接口文档后端提供各种api接口共同性所有返回值均为json格式返回值说明举例类型备注http code返回token验证情况成功：200 失败：422或其他int必有status表明程序运行是否正确0：有错误，1：正确int必有message输出程序运行结果或错误登录成功！string如果status为0，必有传入值：加入headers传输传入值说明举例类型备注Authorization传入access token，验证身份Bearer +你的tokenstring必须在其他模块里，共同性的东西不在赘述若无特殊说明，均默认string类型，默认使用表单传输刷新令牌URL：/refresh方法：POST用于访问令牌失效了，但是刷新令牌未失效的情况自动获取新的访问令牌加入headers传输：传入值说明举例类型备注Authorization传入refresh tokenBearer +你的refresh tokenstring必须返回值说明举例类型备注access_token返回新的访问令牌无string必有登录URL：/login方法：POST注意：本接口不需要访问令牌用于用户登录传入值说明举例备注role验证登录角色1必须 1为学生，2为收书员，3为管理员username用户名302024334011必须password密码无必须返回值说明举例类型备注access_token访问令牌无stringstatus为1时refresh_token刷新令牌无stringstatus为1时注册URL：/reg方法：POST注意：本接口不需要访问令牌传入值说明举例备注role设置注册角色1必须 1为学生，2为收书员username用户名302024334011必须password密码无必须place校区1必须 校区，1为朝晖，2为屏峰，3为莫干山phone电话无必须shoukuan收款方式支付宝role为1时必须address地址尚6role为1时必须name姓名测试学生role为2时必须返回值说明举例类型备注access_token访问令牌无stringstatus为1时且role为1refresh_token刷新令牌无stringstatus为1时且role为1检验首次登录URL：/check_first_login方法：POST注意：本接口不需要访问令牌用于检查学生是否为第一次登录传入值说明举例备注role验证登录角色1必须 1为学生，2为收书员，3为管理员username用户名302024334011必须password密码无必须返回值说明举例类型备注result返回结果Trueboolstatus为1时获取旧书再利用市场信息URL：/get_uploaded_books方法：POST注：管理员使用本接口会返回所有订单，包括已卖出的传入值说明举例备注action区分市场和本人订单all可选 all代表本校区所有还未卖出的订单，own或无代表本人订单返回值说明举例类型备注result返回结果略jsonstatus为1时result中的子项目说明name书的名称place校区，1为朝晖，2为屏峰，3为莫干山print_edition出版社publisher作者img_url图片链接uploader上传者available是否被卖出 True：未卖出 False：已卖出id订单id号time上传时间显示旧书回收订单URL：/get_user_upload_books方法：POST传入值说明举例备注action区分市场和本人订单all可选 all代表本校区所有还没有被接单的订单，stu_check 为学生查询，booker_check为收书员查询返回值说明举例类型备注result返回结果略jsonstatus为1时result中的子项目说明uploader上传者kg_real收书员上传的重量place校区，1为朝晖，2为屏峰，3为莫干山money_uploader学生获得的奖励kg_upload学生上传的重量img_url图片链接uploader上传者available是否被接单 True：未接单 False：已接单id订单id号time上传时间isend是否完成订单 True：已完成 False：未完成booker收书员学号money_booker收书员获得的奖励我要卖书模块上传URL：/upload_book方法：POST传入值说明举例备注kg_upload上传的大概重量5必须img_url照片的地址无必须收书员完成收书URL：/shoushu方法：POST传入值说明举例备注img_url照片地址无必须kg_real真实的重量5必须id接单订单的id1必须收书员获得订单URL：/get_order方法：POST传入值说明举例备注id…

Vue学习笔记12-Pinia集中式状态管理
type: item
date: 2025-02-07T20:34:00+08:00
description: Pinia是集中式状态管理工具，可以实现多个组件共用数据。引入pinia在main.ts中进行引用import './assets/main.css' import { createApp } from 'vue' import App from './App.vue' import router from './router' import { createPinia } from 'pinia' const app=createApp(App) //创建 const pinia=createPinia() app.use(router) app.use(pinia) app.mount('#app')引入 createPinia 即可存储+读取数据新建一个.ts文件来读取数据。love.tsimport { defineStore } from "pinia"; export const useCountLove=defineStore('love',{ state(){ return{ talkList:[{id:'',content:''}] } } })在vue文件中这样引用： 获取情话 {{ t.content }} <count></count> 这样就可以完成读取和写入数据了actions我们可以把一些常用的函数封装到pinia中。例如上面的代码：love.tsimport { defineStore } from "pinia"; import axios from "axios"; import {nanoid} from 'nanoid' export const useCountLove=defineStore('love',{ actions:{ async getATalk(){ let result=await axios.get('/api') let obj={id:nanoid(),content:result.data.content} this.talkList.unshift(obj) } }, state(){ return{ talkList:[{id:'',content:''}] } } })设置了action后就可以在vue里引用。 获取情话 {{ t.content }} <count></count> 也可以获得相同的效果。storeToRefs与toRefs相似，当我们 const {sum,school.address} = countStore这类代码是会丢失响应式。使用 const {sum,school.address} = storeToRefs(countStore)即可。getters如果我们有数据需要预处理，那么我们可以使用getterslove.tsimport { defineStore } from "pinia"; import axios from "axios"; import {nanoid} from 'nanoid' export const useCountLove=defineStore('love',{ actions:{ async getATalk(){ let result=await axios.get('/api') let obj={id:nanoid(),content:result.data.content} this.talkList.unshift(obj) } }, state(){ return{ talkList:[{id:'',content:''}] } }, getters:{ leng(state){ return state.talkList.length } } })$subscribe$subscribe有点像watch，他会在数据发生变化时执行函数。就可以做成自动保存。love.vue 获取情话 {{ t.content }} <count></count> love.tsimport { defineStore } from "pinia"; import axios from "axios"; import {nanoid} from 'nanoid' export const useCountLove=defineStore('love',{ actions:{ async getATalk(){ let result=await axios.get('/api') let obj={id:nanoid(),content:result.data.content} this.talkList.unshift(obj) } }, state(){ return{ talkList:JS…

Vue学习笔记11-路由
type: item
date: 2025-02-04T11:59:00+08:00
description: 基本路由切换步骤：设计好导航区和展示区请来路由器制定路由的基本规则写一个一个的vue/router/index.ts首先要创建一个路由器import { createRouter, createWebHashHistory } from "vue-router"; //引入要呈现的组件 import dog from "@/components/dog.vue"; import news from "@/components/news.vue"; import home from "@/components/home.vue"; //创建一个路由器 const router=createRouter({ history:createWebHashHistory(), routes:[ { name:"Home", path:'/home',//路径 component:home }, { name:"Dog", path:'/dog',//路径 component:dog }, { name:"news", path:'/news',//路径 component:news }, ] }) //暴露出去 export default routermain.ts其次要在main.ts中使用这个路由器import './assets/main.css' import { createApp } from 'vue' import App from './App.vue' import router from './router' const app=createApp(App) //使用路由器 app.use(router) app.mount('#app')app.vue在app.vue中使用router-link来表明更改页面的按钮使用router-view来确定子页面输出的位置 Vue路由测试 <router-link :to="{name:'Home'}">首页</router-link> <router-link :to="{name:'Dog'}">狗</router-link> <router-link :to="{name:'news'}">新闻</router-link> <router-view></router-view> 这时运行后就可以看到实现了基本的切换效果路由器的工作模式history模式写法：history:createWebHistory()优点：URL更加美观，不带有#缺点：后期项目上线需要服务器端进行配置，否则有404Hash模式写法：history:createWebHashHistory()优点：兼容性好，不需要服务端处理路径缺点：URL带有#不美观，SEO较差嵌套路由在index.ts中需要添加嵌套路由的地方添加以下配置{ name:"news", path:'/news',//路径 component:news, children:[{ name:"detail",//嵌套路由路径 path:'deatil', component:Deatil }在news.vue里按照上文一样的写法添加路由即可。 <RouterLink :to="{name:'detail'}">{{ t.title }}</RouterLink> <RouterView></RouterView> 这时就完成了基本的嵌套路由，但是还需要更多配置来使新闻显示生效。传参qerey在进行路由跳转的时候，有时候我们需要传递参数，这个时候我们就需要用到传参。qerey传参比较简单，只需要在to参数中传递即可。<RouterLink :to="{name:'detail',query:{id:t.id,title:t.title,content:t.content}}">{{ t.title }}</RouterLink>detail.vue之后再引用即可 编号：{{ route.query.id }} 标题：{{ route.query.title }} 内容：{{ route.query.content }} paramsparams不是使用？的形式传参，而是使用/要使用params，需要先在路由表里定义变量index.ts{ name:"news", path:'/news',//路径 component:news, children:[{ name:"detail", path:'deatil/:id/:title/:content', component:Deatil }]news.vue<RouterLink :to="{name:'detail',params:{id:t.id,title:t.title,con…

Vue学习笔记10-自定义钩子
type: item
date: 2025-02-03T16:20:00+08:00
description: 我们也可以自定义钩子来让我们的代码更易于维护和调用。test.vue 当前有{{ sum }}张照片 点击获取新图片 useDog.tsimport axios from 'axios'; import { onMounted, reactive, ref } from 'vue'; export default function(){ let sum=ref(0) let dogList = reactive(['']) async function add(){ try{ sum.value+=1 let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random') dogList.push(result.data.message) } catch(error){ alert(error) } } onMounted(()=>{ add() }) return {dogList,add,sum} }在ts文件中，除了import，其他代码都需要包在一个函数里，同时要有返回值，暴露给vue文件。这样就可以做到一个ts被多个vue文件调用，更方便运行示例：