沐游虞笔记
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 必看导言

    • 就业的核心问题

      • 课件
    • 表现力的训练

      • 课件
  • 简历制作

    • 课件
    • 简历准备课堂笔记
    • 个人信息课堂笔记
    • 求职意向课堂笔记
    • 技术栈课堂笔记
    • 教育经历课堂笔记
    • 个人优势与评价课堂笔记
    • 工作经历课堂笔记
    • 项目
    • 项目亮难点问题

    • 项目经历

  • 项目准备

    • 课件
    • 课件
  • 技术重点

    • 划重点

    • 非技术环节
  • 简历投递和面试准备

    • 简历投递
    • 面试准备
  • 笔面试环节知识讲解
  • 项目准备
  • 难点攻关
  • Vite相关优化
luzhichang
2024-09-18
目录

13-技术讲解

# Vite相关性能优化

# 技术讲解

# 什么问题

性能优化基本是简历中必定会出现的内容,这本身算简历中的一个亮点难点,但是问题是基本会写简历的人都会写这个内容,所以,想更加的优秀,那么我们还需要在亮点中再高亮,所以一定要表达出下面几点:

1、对Vite整体的认知程度

2、为什么要性能优化,优化的指标是什么?

3、怎么进行性能优化

4、优化的结果是什么?

5、能突出表达你在性能优化中的高亮点

Vite 在开发环境性能已经非常不错了,因为Vite在开发环境使用了esbuild进行了依赖预构建。一般情况下很舒服,但是也会有一些坑点。

生产环境应该如何优化呢? Vite 打包时用的是 Rollup,但是基本的优化处理,其实和 Webpack 差不多,但是无论如何优化问题我们还是要进行处理的。

# 解决思路

开发环境的依赖预构建没什么可多说的,但是会存在一个问题,如果我们使用了按需引入的插件,类似于element-plus (opens new window)这一种,特别是存在切换路由的情况的时候,可能会导致一种情况,我们需要连续点两次,才会生效,而且界面还可能存在闪动的情况。仔细查看会发现点击路由的时候,Vite还进行了依赖预构建的处理:

[vite] ✨ new dependencies optimized: ......
[vite] ✨ optimized dependencies changed. reloading

为了防止在启动时占用大量编译时间,vite只会处理一些常用的组件和依赖,特别是在按需加载的时候,加上vite本身就会忽略node_modules中的内容,这就导致一些按需的依赖会在开发进入到对应页面时才会处理,从而导致一直在处理依赖优化和reloading。

通过分析,类似于element-plus,vant等等这种可以按需加载的组件,Vite的优化触发是在style样式加载,那么我们就让他开发模式把所有组件的样式全优化了

optimizeDeps: {
  include: [
    "element-plus/es/components/**/style/css",
  ]
},

生产环境的优化,大部分都是和打包优化相关的

  • 分包策略
  • 构建分析
  • 摇树优化
  • 代码压缩
  • GZIP压缩
  • 图片压缩
  • CDN加速

这些基本都是常用的手段,就不在一一描述了,只不过在一些优化上,我们可能需要vite插件配合,甚至需要自定义插件

# 解决细节

# 1、Vite的基本认知

image-20240703103246387

# 2、量化指标

# 使用量化指标的原因

性能优化并不是一句口号,所以要优化,首先就需要确立指标。

领导突然来一句,项目需要优化,是的,你知道优化,也有念头从你脑子里蹦出来,但是从何入手?,如何制定计划?没有量化指标,就没有指导方向,也不会有我们优化之后的基线和目标

量化指标大家嘴上都在说,比如FCP,LCP,TTI,CLS,FID,TBT等等

# FCP (First Contentful Paint)

首次内容绘制,浏览器首次绘制来自DOM的内容的时间,内容必须包括文本,图片,非白色的canvas或svg,也包括带有正在加载中的web字体文本。这是用户第一次看到的内容。

FCP时间(秒) 颜色编码 FPC分数
0 - 1.8 绿色(快) 75 - 100
1.8 - 3 橙色(中等) 50 - 74
超过3 红色(慢) 0 - 49

# LCP (Largest Contentful Paint)

最大内容绘制,可视区域中最大的内容元素呈现到屏幕上的时间,用以估算页面的主要内容对用户的可见时间。img图片,video元素的封面,通过url加载到的北京,文本节点等,为了提供更好的用户体验,网站应该在2.5s以内或者更短的时间最大内容绘制。

LCP时间(秒) 颜色编码
0 - 2.5 绿色(快)
2.5 - 4 橙色(中等)
超过4 红色(慢)

# FID (First Input Delay)

首次输入延迟,从用户第一次与页面进行交互到浏览器实际能够响应该交互的时间,输入延迟是因为浏览器的主线程正忙于做其他事情,所以不能响应用户,发生这种情况的一个常见原因是浏览器正忙于解析和执行应用程序加载的大量计算的JavaScript。

FID时间(毫秒) 颜色编码
0 - 100 绿色(快)
100 - 300 橙色(中等)
超过300 红色(慢)

# TTI (Time to Interactive)

网页第一次完全达到可交互状态的时间点,浏览器已经可以持续的响应用户的输入,完全达到可交互的状态的时间是在最后一个长任务完成的时间,并且在随后的5s内网络和主线程是空闲的。从定义上来看,中文名称叫持续可交互时间或可流畅交互时间更合适。

TTI时间(秒) 颜色编码
0 - 3.8 绿色(快)
3.9 - 7.3 橙色(中等)
超过7.3 红色(慢)

# TBT (Total Block Time)

总阻塞时间,度量了FCP和TTI之间的总时间,在该时间范围内,主线程被阻塞足够长的时间以防止输入响应。只要存在长任务,该主线程就会被视为阻塞,该任务在主线程上运行超过50毫秒。

线程阻塞是因为浏览器无法中断正在进行的任务,因此如果用户确实在较长的任务中间与页面进行交互,则浏览器必须等待任务完成才能响应。

TBT时间(毫秒) 颜色编码
0 - 300 绿色(快)
300 - 600 橙色(中等)
超过600 红色(慢)

# CLS (Cumulative Layout Shift)

累计布局位移,CLS会测量在页面整个生命周期中发生的每个意外的布局移位的所有单独布局移位分数的总和,他是一种保证页面的视觉稳定性从而提升用户体验的指标方案。

用人话来说就是当点击页面中的某个元素的时候,突然布局变了,手指点到了其它位置。比如想点击页面的链接,突然出现了一个banner。这种情况可能是因为尺寸未知的图像或者视频。

CLS时间(毫秒) 颜色编码
0 - 0.1 绿色(快)
0.1 - 0.25 橙色(中等)
超过0.25 红色(慢)

# 性能测试

指标有了,怎么样进行测试?自行测试可以使用浏览器工具,比如Chrome DevTools,Lighthouse,Performance,Network网络分析,Memory 面板等等,这些数据拿到的结果都是实验室数据。

要准确的用户数据就必须上相关的性能监控埋点。

但是说这些指标,这些工具手段的前提是,你要知道为什么?

# 3、具体方案

# 1、分包策略

在生产环境下 Vite 完全利用 Rollup 进行构建,因此拆包也是基于 Rollup 来完成的

# Vite 默认拆包策略

只不过Rollup专注于JS库的打包,对应用构建的能力还有待提升,Vite 正好是补足了 Rollup 应用构建的能力,在拆包能力这一块的扩展就是很好的体现

项目的基本结构:

.
├── public
│   ├── vite.svg
├── src
│   ├── routes
│   │			└── index.ts				//路由配置
│   ├── views
│   │			├── AboutView.vue 	// 懒加载路由页面
│   │			├── HomeView.vue		// 直接加载路由页面
│   │			├── PhotoView.vue   // 懒加载路由页面
│   │			├── StudentView.vue // 懒加载路由页面
│   │			└── UserView.vue		// 懒加载路由页面
│   ├── App.vue
│   └── main.ts
└── index.html                

执行打包:

vite v4.4.9 building for production...
✓ 34 modules transformed.
dist/index.html                        0.45 kB │ gzip:  0.29 kB
dist/assets/AboutView-786d9592.css     0.04 kB │ gzip:  0.06 kB
dist/assets/index-d1e7a483.css         0.27 kB │ gzip:  0.17 kB
dist/assets/UserView-18eefd25.js       0.54 kB │ gzip:  0.35 kB
dist/assets/StudentView-85c73246.js    0.55 kB │ gzip:  0.35 kB
dist/assets/AboutView-355cc1df.js      0.74 kB │ gzip:  0.42 kB
dist/assets/index-c0c63fdb.js        216.25 kB │ gzip: 51.48 kB
✓ built in 552ms

打包之后的产物:

.
├── assets
│   ├── AboutView-355cc1df.js   // Async Chunk
│   ├── AboutView-786d9592.css 	// Async Chunk (CSS)
│   ├── index-c0c63fdb.js      	// Initial Chunk
│   ├── index-d1e7a483.css     	// Initial Chunk (CSS)
│   ├── StudentView-85c73246.js // Async Chunk
│   └── UserView-18eefd25.js		// Async Chunk
├── index.html                 	// 入口 HTML
└── vite.svg										// 静态资源

这是没有任何配置的vite打包之后的产物,自动对懒加载的路由和与其对应的css进行的处理

# 自定义拆包策略

针对更细粒度的拆包,Vite还是基于的Rollup处理。

manualChunks 主要有两种配置的形式,可以配置为一个对象或者一个函数。

# 对象方式
build: {
  ...
  rollupOptions: {
    output: {
      ...
      manualChunks:{
        'vue-vendor': ['vue', 'vue-router']
      }
    }
	}
}

在对象格式的配置中,key代表 chunk 的名称,value为一个字符串数组,每一项为第三方包的包名

vite v4.4.9 building for production...
✓ 34 modules transformed.
dist/index.html                        0.54 kB │ gzip:  0.33 kB
dist/assets/AboutView-786d9592.css     0.04 kB │ gzip:  0.06 kB
dist/assets/index-d1e7a483.css         0.27 kB │ gzip:  0.17 kB
dist/assets/UserView-9010cdee.js       0.55 kB │ gzip:  0.35 kB
dist/assets/StudentView-eff7d69a.js    0.55 kB │ gzip:  0.35 kB
dist/assets/AboutView-74a57329.js      0.78 kB │ gzip:  0.43 kB
dist/assets/index-4fb9786f.js          5.87 kB │ gzip:  2.06 kB
dist/assets/vue-vendor-99c39a4b.js   210.95 kB │ gzip: 49.67 kB
✓ built in 553ms

可以看到之前很大的index.js文件被拆分了出来。

# 函数方式
manualChunks(id) {
  if (id.includes('node_modules/vue')) {
    return 'vue';
  }
  if (id.includes('element-plus')) {
    return 'element-plus';
  }
  if (id.includes('lodash-es')) {
    return 'lodash';
  }
}

不过需要注意的是,这种方式在Vite4并不安全,因为element-plus这种库也依赖了Vue,这里单独拆分Vue的话,导致循环依赖的错误问题。建议还是使用对象方式

# 2、treeshaking

这个没什么可多说的,不需要额外进行配置。但有一个条件,必须是 ES6 module 模块才行,而且我们自己在引入的时候,注意只需要引入具体的函数(对象),不要直接导入全对象

# 3、代码压缩

Vite默认使用esbuild进行代码压缩,速度快,如果要追求压缩效果。

minify:esbuild

也可以使用terser,Vite也默认集成了terser,只是需要手动导入terser插件

minify: 'terser',
terserOptions: {
  compress: {
      drop_console: true,
      drop_debugger: true,
  }
},

# 4、GZIP压缩

gzip 是一种使用非常普遍的压缩格式。使用 gzip 压缩可以大幅减小代码体积,提升网络性能。当然gzip只是其中一种,brotliCompress也是一样,一般简称br

服务器就可以直接帮我们处理,不过需要耗费服务器性能,所以,一般前端也可能直接把打包之后的文件进行压缩处理,直接使用插件即可vite-plugin-compression2 (opens new window),

import { compression } from 'vite-plugin-compression2'
......

plugins: [
  ......
  compression({
    //压缩算法,默认gzip
    algorithm: 'brotliCompress',
    //匹配文件
    include: [/\.(js)$/, /\.(css)$/,],
    //压缩超过此大小的文件,以字节为单位
    // threshold: 10240,
    //是否删除源文件,只保留压缩文件
    // deleteOriginalAssets: true
  }),
  ]

# 5、图片压缩

图片压缩肯定可以减少网络传输,不过需要看项目对图片清晰度的需求,一般情况下,如果美工能直接处理最好,或者我们也可以使用外部工具进行处理,甚至是网上就可以直接处理,比如:tinypng (opens new window)

如果前端处理的话,可以使用插件vite-plugin-imagemin (opens new window)

import viteImagemin from 'vite-plugin-imagemin'
......
plugins: [
  ...... 
  viteImagemin({
     gifsicle: {
       optimizationLevel: 7,
       interlaced: false,
     },
     optipng: {
       optimizationLevel: 7,
     },
     mozjpeg: {
       quality: 20,
     },
     pngquant: {
       quality: [0.8, 0.9],
       speed: 4,
     },
     svgo: {
       plugins: [
         {
           name: 'removeViewBox',
         },
         {
           name: 'removeEmptyAttrs',
           active: false,
         },
       ],
     },
   }),
]

# 6、产物构建分析

为了能可视化地感知到产物的体积情况,推荐大家用rollup-plugin-visualizer (opens new window)来进行产物分析

import { visualizer } from "rollup-plugin-visualizer";
......
plugins: [
  visualizer({
    // 是否打包完成之后直接打开
    open: true,
  }),
],

# 7、CDN加速

使用CDN,可以让用户从最近的服务器请求资源,能够大大的提高资源的请求获取的速度。特别是一些第三方资源库,放在CDN上不但可以提高资源请求速度,而且也能大大降低我们打包体积和打包速度。

如果要把项目中的一些第三方资源放入到CDN,但是项目中我们又使用了这些资源,需要在项目中进行处理。一般比较常用的处理方式是直接使用第三方插件。比如:rollup-plugin-external-globals (opens new window)

import externalGlobals from "rollup-plugin-external-globals";
......
rollupOptions: {
  external:['vue','vue-router','element-plus','vue-echarts','echarts','@vueuse/core'],
  plugins: [
    externalGlobals({
      "vue": "Vue",
      "vue-router": "VueRouter",
      "element-plus": 'ElementPlus',
      "@vueuse/core": "VueUse",
      "echarts": "echarts",
      "vue-echarts": "VueECharts",
    })
  ],
}

# 8、打包处理

如果希望设置打包之后的文件位置,可以通过相关rollup设置处理

build:{
  rollupOptions: {
    output: {
      chunkFileNames: 'assets/[name]-[hash].js',
      entryFileNames: 'assets/[name].[hash].js',
      assetFileNames: 'assets/xxx/[ext]/[name]-[hash].[ext]',
    }
}

# 9、自定义插件

Vite 插件扩展了设计出色的 Rollup 接口,整个Rollup插件结构体系比较庞杂,简单来说,分成了build和output两大工作流的不同阶段进行分类。这个还需要单独去学习。就Vite来说他有可以和rollup结合的通用钩子 (opens new window),也有自己的独有钩子 (opens new window)

Vite独有钩子:

钩子名称 释义
config 在解析 Vite 配置前调用。钩子接收原始用户配置(命令行选项指定的会与配置文件合并)和一个描述配置环境的变量,包含正在使用的 mode 和 command。
configResolved 在解析 Vite 配置后调用。使用这个钩子读取和存储最终解析的配置。
configureServer 是用于配置开发服务器的钩子。
configurePreviewServer 与 configureServer (opens new window) 相同但是作为预览服务器。
transformIndexHtml 转换 index.html 的专用钩子。钩子接收当前的 HTML 字符串和转换上下文。
handleHotUpdate 执行自定义 HMR 更新处理。
Theme by Vdoing | Copyright © 2021-2024 蜀ICP备2024068710号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式