el-switch
const appStore = useAppStore()
const changeThemeType = (value) => {
appStore.toggleThemeDark(value);
}
<el-switch size="default" v-model="appStore.appThemeDark" @change="changeThemeType"/>
这里夜间开关变动后,会用调用changeThemeType函数并传参最新的appThemeDark值
@change
vue中,@change是一个事件监听器,用于监听表单元素(例如 <input>、<select>、<textarea> 等)发生“值变化并失去焦点”时触发的事件
<input @change="handleChange" />
<!-- 等价于 -->
<input v-on:change="handleChange" />
@change触发时机:
普通输入框: 当输入内容后离开焦点时才会触发, 当输入内容后离开焦点(blur)时才会触发;
复选框:当选中状态改变时立即触发(不需要失焦)
下拉框:当选项改变时立即触发
结合v-model使用
v-model默认会自动监听input事件来更新数据
如果你希望在值确认时再更新,可以结合@change使用:
<template>
<input v-model="text" @change="onConfirm" />
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
function onConfirm() {
console.log('确认输入:', text.value)
}
</script>
appSetting.js
const appThemeDark = ref(dbUtils.get('appThemeDark') === null ? true : dbUtils.get('appThemeDark') === 'light');
const toggleThemeDark = (type) => {
if (!type) {
dbUtils.set('appThemeDark', 'dark')
appThemeDark.value = false
document.documentElement.classList.add("dark");
} else {
dbUtils.set('appThemeDark', 'light')
appThemeDark.value = true
document.documentElement.classList.remove("dark");
}
}
dbUtils
set(key, value) {
if (typeof value === 'object') {
// 如果值是对象或数组,先将其转换为 JSON 字符串再存储
value = JSON.stringify(value);
}
localStorage.setItem(`${t}-${v}-` + key, value)
},
get(key) {
const value = localStorage.getItem(`${t}-${v}-` + key)
console.log(value)
try {
//尝试解析JSON字符串
return JSON.parse(value)
} catch (e) {
return value
}
}
这段代码的作用是:当appThemeDark的值改变时,将localStorage中存储的值也同步更改,并改变页面的css -> 通过在html中加或删除dark类的方式,ElementUI可以一键切换全局模式,也可以自定义:
html.dark {
--main-background: var(--el-bg-color-overlay)
}
这里--el-bg-color-overlay是ElementUI提供的可配置变量颜色,可能由:
- 框架默认值(内置CSS)
- 用户主题定制
- 动态暗黑切换
来共同决定
也就是,可以实时改变的,当你在页面上加上 .dark 类时,CSS 层会生效下面这一条定义:
html.dark {
--el-bg-color-overlay: #1d1e1f;
}
改完后,所有引用 var(--el-bg-color-overlay) 的组件(如弹窗、菜单等)都会立即更新颜色。
这也是为什么 Element Plus 可以实现“实时切换主题”的原因
想要使用--el-bg-color-overlay,需要引入elementUI的相关组件 import 'element-plus/theme-chalk/dark/css-vars.css'
localStorge
localStorage是浏览器提供的一种本地存储机制
用于在浏览器中持久保存少量数据,可以理解成浏览器里的小型数据库,可以保存一些前端需要长期使用的数据
- 位置:保存在用户浏览器本地(不会上传到服务器)
- 生命周期:永久保存,除非主动删除或用户清理浏览器缓存
- 容量:一般每个域名可存 5MB 左右(比 Cookie 大得多)
- 数据类型:只能存字符串(非字符串要序列化)
localStorge和Cookie的区别
Cookie是浏览器保存的一小段数据,主要用于:
- 标识用户身份(例如登录状态)
- 在请求中自动携带数据(例如会话ID,语言偏好)
- 在不同页面间共享信息
它是浏览器和服务器之间的“通信型”存储机制
| 对比项 | localStorage | sessionStorage | Cookie |
|---|---|---|---|
| 存储位置 | 浏览器本地 | 浏览器本地 | 浏览器本地 |
| 生命周期 | 永久保存 | 页面会话结束清除 | 可设置过期时间 |
| 容量 | ~5MB | ~5MB | ~4KB |
| 是否随请求发送 | ❌ 否 | ❌ 否 | ✅ 是 |
| 操作接口 | 简单(API形式) | 简单(API形式) | 复杂(要解析字符串) |
| 典型用途 | 用户设置、缓存数据 | 页面临时数据 | 登录状态、会话信息 |
三种常见存储方式的应用场景
Cookie
Cookie是最早的前端存储方式,最初是为了在HTTP请求间保存用户状态(如登录状态)
- 每次请求同源接口时,浏览器会自动携带 cookie(除非被禁用)。
- 大小一般只有 4KB 左右。
- 可以设置 过期时间。
常见应用场景:
- 登录状态/session 管理(例如服务器根据 cookie 中的
session_id知道你是谁) - 跨请求的用户识别(如跟踪用户行为、广告分析)
- 需要后端读取的状态信息
⚠ 缺点:容易被滥用(追踪用户)、空间小、携带到服务器增加请求负担。
localStorage
- 大约 5MB 容量。
- 永久存储(除非用户主动清理或 JS 调用
removeItem()/clear())。 - 只存在于浏览器端,不会随着 HTTP 请求自动发送到服务器。
- 同源策略:只能在相同的协议 + 域名 + 端口下访问。
常见应用场景
- 保存用户的偏好设置(例如主题、语言、字体大小)
- 缓存不太敏感的数据(例如上次打开的页面、草稿内容)
- 前端状态持久化(比如单页应用保存临时表单内容或上次的滚动位置)
⚠ 缺点:无法在不同浏览器或设备间共享;对隐私敏感内容不安全。
sessionStorage
与 localStorage 相似,但:
- 生命周期更短:只在当前 标签页(tab)会话 中有效;
- 关闭标签页就清空;
- 不会随请求发送;
- 不同标签页之间互不共享。
常见应用场景
- 表单填写过程中的临时数据保存(防止误刷新)
- 单次会话内的数据共享(例如某个页面跳转前后传递信息)
- SPA 应用中短期状态缓存(例如 API 请求结果暂存)
⚠缺点:标签页一关就没了,无法持久化。
初始化函数
如果只有之前的代码,主题颜色只会在颜色按钮改变时改变,那么应该如何通过localStorage内存储的变量实现初始化
export const useAppStore = defineStore("AppSetting", ()=>{
const initThemeDark = () => {
if (!appThemeDark.value) {
dbUtils.set('appThemeDark', 'dark');
document.documentElement.classList.add('dark');
} else {
dbUtils.set('appThemeDark', 'light');
document.documentElement.classList.remove('dark');
}
}
initThemeDark();
}
在这里,initThemeDark() 的执行时机,取决于 Pinia 的 defineStore 是如何创建和使用 store 的
- defineStore本身不会立即执行函数体
export const useAppStore = defineStore("AppSetting", () => {
initThemeDark()
})
这里的defineStore并不会马上执行initThemeDark()
它只是定义了一个工厂函数(也就是 useAppStore),还没有真正创建 store 实例。
Pinia 内部大概相当于做了这样的事:
function defineStore(name, setupFn) {
return function useStore() {
// 当你第一次调用 useAppStore() 时
// 才执行 setupFn()
return setupFn()
}
}
initThemeDark()的执行时机
真正调用的时机是:
const appStore = useAppStore()
就在这行代码执行时:
setupFn(也就是你传给defineStore的回调)会执行;- 也就是
initThemeDark()会在这时运行。
| 阶段 | 发生的事 |
|---|---|
| 加载模块时 | defineStore 定义了一个名为 "AppSetting" 的 store(函数没执行) |
第一次调用 useAppStore() | initThemeDark() 在这里被调用 |
后续调用 useAppStore() | 因为 Pinia 会缓存 store 实例,不会再执行第二次 |
为什么不会执行第二次
export const useAppStore = defineStore("AppSetting", () => {
initThemeDark()
})
Pinia 的defineStore返回一个函数(也就是useAppStore)
当你在组件或代码里调用:
const appStore = useAppStore()
这时候Pinia会:
- 检查这个store名字(这里是"AppSetting")是否已经存在
- 如果没有,就执行一次传进去的函数(也就是
() => { initThemeDark() })来创建 store; - 然后把这个 store 缓存起来;
- 以后再调用
useAppStore()时,Pinia 直接返回缓存好的那个实例,不会再执行initThemeDark()。
设计原因
为了保证:
- 所有组件共享同一个状态(单例store)
- 避免重复初始化逻辑(例如主题初始化、接口请求、localStorage恢复等)
因此initThemeDark()这个初始化方法只会在第一次创建store时执行一次