一环境准备
二vite 项目初始化
按照 🍃Vite 官方文档 - 搭建第一个 Vite 项目 说明,执行以下命令完成 vue
、typescirpt
模板项目的初始化
npm init vite@latest vue3-element-admin --template vue-ts
-
vue3-element-admin
: 自定义的项目名称 -
vue-ts
:vue
+typescript
模板的标识,查看 create-vite 以获取每个模板的更多细节:vue,vue-ts,react,react-ts
初始化完成项目位于 D:\project\demo\vue3-element-admin
, 使用 VSCode 导入,执行以下命令启动:
npm install
npm run dev
浏览器访问 localhost:5173 预览
三 src 路径别名配置
相对路径别名配置,使用 @ 代替 src
npm install @types/node --save-dev
import { fileURLToPath, URL } from "node:url"
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url))
}
},
路径别名使用
// src/App.vue
import HelloWorld from '/src/components/HelloWorld.vue'
↓
import HelloWorld from '@/components/HelloWorld.vue'
四unplugin 自动导入
Element Plus 官方文档中推荐
按需自动导入
的方式,而此需要使用额外的插件unplugin-auto-import
和unplugin-vue-components
来导入要使用的组件。所以在整合Element Plus
之前先了解下自动导入
的概念和作用
安装插件依赖
npm install -D unplugin-auto-import unplugin-vue-components
vite.config.ts - 自动导入配置
import Components from "unplugin-vue-components/vite"; // 自动导入 Vue 组件
import AutoImport from "unplugin-auto-import/vite"; // 自动导入 Vue 相关 API
// 自动导入组件(如 Element Plus 组件)
Components({
// 默认只针对src/components目录实现自动导入
dirs: ["src/components", "src/layout"], // 后面布局组件也有相关的组件期望自动导入
dts: "./components.d.ts",
}),
// 自动导入常用的 Vue API,比如 'ref' 和 'vue-router'
AutoImport({
imports: ["vue", "vue-router"], // 自动导入 Vue 和 Vue Router 的 API
dts: "./auto-imports.d.ts", // 生成的 TypeScript 声明文件路径 // 生成的全局变量放到此目录下
eslintrc: {
enabled: true, // 启用 ESLint 配置生成
filepath: "./.eslintrc-auto-import.json" // 生成的 ESLint 配置文件路径
}
}),
自动导入效果
运行项目 npm run dev
自动生成文件
使用: 不用导入也可以
使用
<script setup lang="ts">
const a = ref(0);
console.log("a11111111", a.value);
</script>
五、整合 Element Plus
5.1安装
npm install element-plus --save
5.2按需导入(自动导入)
npm install -D unplugin-vue-components unplugin-auto-import
5.3vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
5.4 使用
5.5自动引入element-plus的样式 (不需要了!!)
在编写我们组件库的组件时,需要使用按需加载的方式引入element-plus
组件,如:
<template>
<el-input />
</template>
<script setup lang="ts">
import { ElInput } from 'element-plus'
import 'element-plus/theme-chalk/src/base.scss'
import 'element-plus/theme-chalk/src/input.scss'
</script>
可以看到我们不仅要引入组件,还需要引入基础样式和组件样式,这个需要的element-plus
组件变多的话,非常麻烦。
我们需要使用unplugin-element-plus
帮助我们自动引入样式
安装unplugin-element-plus
到组件库的包下
pnpm i unplugin-element-plus -D
在vite
配置文件里添加下面配置
// /packages/components/vite.config.ts
import ElementPlus from 'unplugin-element-plus/vite'
export default defineConfig(() => {
return {
plugins: [
// ...
ElementPlus({
// 导入scss而不是css
useSource: true
}),
]
}
})
配置好后,编写组件时只用向下面这样就行
<template>
<el-input />
</template>
<script setup lang="ts">
import { ElInput } from 'element-plus'
</script>
六 使用element-plus 的图标
npm install @element-plus/icons-vue
使用
<el-input
:prefix-icon="Search"
style="width: 240px"
placeholder="Please input"
/>
<script setup lang="ts">
import { Search } from "@element-plus/icons-vue";
</script>
七整合 SVG 图标
安装
npm install vite-plugin-svg-icons --save
npm install fast-glob --save
在vite.config.js配置
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import path from "path";
// svgIcon
createSvgIconsPlugin({
// Specify the icon folder to be cached
iconDirs: [path.resolve(process.cwd(), "src/assets/svg")],
// Specify symbolId format
symbolId: "icon-[dir]-[name]",
}),
定义一个svgicon组件
<template>
<svg aria-hidden="true" class="svg-icon">
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script setup lang="ts">
import { computed } from "vue"
const props = defineProps({
prefix: {
type: String,
default: "icon"
},
iconClass: {
type: String,
required: false,
default: ""
},
color: {
type: String,
default: ""
}
})
const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`)
</script>
<style scoped>
.svg-icon {
display: inline-block;
width: 1em;
height: 1em;
overflow: hidden;
vertical-align: -0.15em; /* 因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果 */
outline: none;
fill: currentcolor; /* 定义元素的颜色,currentColor是一个变量,这个变量的值就表示当前元素的color值,如果当前元素未设置color值,则从父元素继承 */
}
</style>
在main.js中
import "virtual:svg-icons-register"
使用
<svg-icon icon-class="anchorsNavigation" />
如果想要修改svg图片颜色,需要修改svg图片里面的fill属性改为currentColor,如下:
八整合 SCSS
安装
npm i -D sass
创建 themeVar.scss 变量文件,添加变量 $bg-color
定义,注意规范变量以 $
开头
// src/styles/themeVar.scss
$bg-color:#242424;
Vite
配置导入 SCSS
全局变量文件
// vite.config.ts
css: {
preprocessorOptions: {
scss: {
// 按需导入自定义主题
additionalData: `@use "@/styles/themeVar.scss" as *;`,
},
},
},
上面导入的 SCSS
全局变量在 TypeScript
不生效的,需要创建一个以 .module.scss
结尾的文件
// src/styles/variables.module.scss
// 导出 themeVar.scss 文件的变量
:export{
bgColor:$bg-color
}
TypeScript
使用 SCSS
全局变量
<!-- src/components/HelloWorld.vue -->
<script setup lang="ts">
import variables from "@/styles/variables.module.scss";
console.log(variables.bgColor)
</script>
<template>
<div style="width:100px;height:100px" :style="{ 'background-color': variables.bgColor }" />
</template>
九Element Plus 主题
/* 自定义 element-plus 主题 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'white': #ffffff,
'black': #000000,
'primary': (
'base': #f59a23,
),
'success': (
'base': #3abb5f,
),
'warning': (
'base': #f59a23,
),
'danger': (
'base': #f56c6c,
),
'error': (
'base': #ec4c40,
),
'info': (
'base': #909399,
),
),
$table: (
'header-bg-color': #F2F3F5,
'header-text-color': #1D2129,
'text-color': #303133,
'row-hover-bg-color': #fff,
)
);
/** 全局SCSS变量 */
:root {
--menu-background: linear-gradient(180deg, #1f2935 0%, #425365 100%);
--menu-text: #fff;
--menu-active-text: #fff;
--menu-hover: #161d26;
// 修复表格 fixed 列被选中后由于透明色导致叠字的 bug
.el-table {
--el-table-current-row-bg-color: rgb(235 243 250);
};
}
$menu-background: var(--menu-background); // 菜单背景色
$menu-text: var(--menu-text); // 菜单文字颜色
$menu-active-text: var(--menu-active-text); // 菜单激活文字颜色
$menu-hover: var(--menu-hover); // 菜单悬停背景色
$sidebar-width: 256px; // 侧边栏宽度
$sidebar-width-collapsed: 64px; // 侧边栏收缩宽度
$navbar-height: 64px; // 导航栏高度
$bg-color:red
2.在vite.config.js配置中导入scss文件类型
plugins: [
vue(),
viteMockServe({
mockPath: "mock",
localEnabled: true,
}),
// element-plus配置
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
// 1.在element-plus配置中导入sass文件类型
resolvers: [ElementPlusResolver({ importStyle: "sass" })],
}),
]
查看:
primary type类型成功从蓝色替换成黄色了
十vue3+ts+vite解决低版本火狐报错空白屏
下载依赖
npm install @vitejs/plugin-legacy
2、在vite.config.ts中引入
import legacy from '@vitejs/plugin-legacy'
plugins: [
// 提供对旧版浏览器的支持,将现代 JavaScript 编译为旧版浏览器兼容的代码
legacy({
targets: ["defaults", "not IE 11"], // 目标是现代浏览器,排除 IE 11
}),
]
十一Vite:性能优化-gzip压缩
https://juejin.cn/post/7473152293983453203
plugins: [
// 为生产环境的构建启用 gzip 压缩,减小文件体积
viteCompression({
threshold: 100000, // 文件大于 100Kb 开启压缩
algorithm: "gzip", // 使用 gzip 算法进行压缩
ext: ".gz" // 压缩后的文件扩展名
}),
]
十二 环境变量
env配置文件
项目根目录新建 .env.development
、.env.production
-
开发环境变量配置:.env.development
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
# 应用端口
VITE_APP_PORT=3000
# 代理前缀
VITE_APP_BASE_API=/dev-api
# 接口地址
VITE_APP_API_URL=https://api.youlai.tech # 线上
# VITE_APP_API_URL=http://localhost:8989 # 本地
# WebSocket 端点(不配置则关闭),线上 ws://api.youlai.tech/ws ,本地 ws://localhost:8989/ws
VITE_APP_WS_ENDPOINT=
# 启用 Mock 服务
VITE_MOCK_DEV_SERVER=false
- 生产环境变量配置:.env.production
# 应用端口
VITE_APP_PORT=3000
# 代理前缀
VITE_APP_BASE_API=/dev-api
# 接口地址
VITE_APP_API_URL=https://api.youlai.tech # 线上
# VITE_APP_API_URL=http://localhost:8989 # 本地
# WebSocket 端点(不配置则关闭),线上 ws://api.youlai.tech/ws ,本地 ws://localhost:8989/ws
VITE_APP_WS_ENDPOINT=
# 启用 Mock 服务
VITE_MOCK_DEV_SERVER=false
默认情况下,开发服务器 (dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production (生产) 模式,也就是在 package.json 里面的命令:
这意味着当执行 vite build 时,它会自动加载 .env.production 中可能存在的环境变量:
# .env.production
VITE_APP_TITLE=My App
在某些情况下,若想在 vite build
时运行不同的模式来渲染不同的标题,你可以通过传递 --mode
选项标志来覆盖命令使用的默认模式。例如,如果你想在 staging (预发布)模式下构建应用:
vite build --mode staging
还需要新建一个 .env.staging
文件:
# .env.staging
VITE_APP_TITLE=My App (staging)
由于 vite build
默认运行生产模式构建,你也可以通过使用不同的模式和对应的 .env
文件配置来改变它,用以运行开发模式的构建:
# .env.testing
NODE_ENV=development
配置模式
所以我们可以在项目根目录添加一个.env.testing
文件,然后配置一条 test 命令,用于将打包模式改为 testing 模式,这样就可以在执行 test 命令的时候,使用.env.testing
文件中的环境变量:
这样,你的 Vite 项目就可以通过生产不同环境的代码进行打包,适合不同环境的 API 接口。
项目中:
十三反向代理解决跨域
跨域原理
浏览器同源策略: 协议、域名和端口都相同是同源,浏览器会限制非同源请求读取响应结果。
本地开发环境通过 Vite
配置反向代理解决浏览器跨域问题,生产环境则是通过 nginx
配置反向代理 。
vite.config.ts
配置代理
import { defineConfig, loadEnv } from "vite";
// https://vite.dev/config/
export default defineConfig((mode): any => {
const env = loadEnv(mode.mode, process.cwd());
return {
server: {
host: "0.0.0.0",
open: true,
port: 8088,
proxy: {
/** 代理前缀为 /dev-api 的请求 */
[env.VITE_APP_BASE_API]: {
changeOrigin: true,
// 接口地址
target: "https://vue.youlai.tech",//<你的目标接口地址>
rewrite: (path: any) =>
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), "/prod-api"),// 重写路径,去掉/api前缀
},
},
},
};
});
表面肉眼看到的请求地址: http://localhost:3000/dev-api/api/v1/users/me
真实访问的代理目标地址: http://vapi.youlai.tech/api/v1/users/me
总配置
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import { fileURLToPath, URL } from "node:url";
import Components from "unplugin-vue-components/vite"; // 自动导入 Vue 组件
import AutoImport from "unplugin-auto-import/vite"; // 自动导入 Vue 相关 API
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import path from "path";
export default defineConfig((mode): any => {
const env = loadEnv(mode.mode, process.cwd());
return {
server: {
host: "0.0.0.0",
open: true,
port: 8088,
proxy: {
/** 代理前缀为 /dev-api 的请求 */
[env.VITE_APP_BASE_API]: {
changeOrigin: true,
// 接口地址
target: "https://vue.youlai.tech", //<你的目标接口地址>
rewrite: (path: any) =>
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), "/prod-api"), // 重写路径,去掉/api前缀
},
},
},
plugins: [
vue(),
// 自动导入组件(如 Element Plus 组件)
Components({
// 默认只针对src/components目录实现自动导入
dirs: ["src/components", "src/layout"], // 后面布局组件也有相关的组件期望自动导入
dts: "./components.d.ts",
resolvers: [ElementPlusResolver({ importStyle: "sass" })],
}),
// 自动导入常用的 Vue API,比如 'ref' 和 'vue-router'
AutoImport({
imports: ["vue", "vue-router"], // 自动导入 Vue 和 Vue Router 的 API
dts: "./auto-imports.d.ts", // 生成的 TypeScript 声明文件路径 // 生成的全局变量放到此目录下
resolvers: [ElementPlusResolver()],
eslintrc: {
enabled: true, // 启用 ESLint 配置生成
filepath: "./.eslintrc-auto-import.json", // 生成的 ESLint 配置文件路径
},
}),
// svgIcon
createSvgIconsPlugin({
// Specify the icon folder to be cached
iconDirs: [path.resolve(process.cwd(), "src/assets/svg")],
// Specify symbolId format
symbolId: "icon-[dir]-[name]",
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
// vite.config.ts
css: {
preprocessorOptions: {
scss: {
// 按需导入自定义主题
additionalData: `@use "@/styles/themeVar.scss" as *;`,
},
},
},
};
});
查看目标地址的方式:
server: {
host: "0.0.0.0",
port: +env.VITE_APP_PORT,
open: true,
proxy: {
// 代理 /dev-api 的请求
[env.VITE_APP_BASE_API]: {
changeOrigin: true,
// 代理目标地址:https://api.youlai.tech
target: env.VITE_APP_API_URL,
rewrite: (path) =>
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), "/prod-api"),
logLevel: "debug",
bypass(req, res: any, options: any) {
const realUrl =
options.target +
(options.rewrite ? options.rewrite(req.url) : "");
console.log(realUrl); // 在终端显示
res.setHeader("A-Real-Url", realUrl); // 添加响应标头(A-Real-Url为自定义命名),在浏览器中显示
},
},
},
},
十四TypeScript 编译器的配置文件
【项目配置文件】TypeScript 编译器的配置文件_tsconfig.app.json-CSDN博客
tsconfig.app.json
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
tsconfig.json
{
"references": [],
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": true,
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": false,
"types": ["vue", "node", "vite/client", "vite-plugin-pwa/client"],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
},
"jsx": "preserve", // 确保支持 JSX
},
"include": [
"src/**/*.ts",
"src/**/*.vue",
"./src/**/*.d.ts",
"auto-imports.d.ts",
"src/**/*.tsx",
"src/**/*.d.ts",
"vite.config.ts",
"vite/**/*.ts",
"globals.d.ts"
]
}
tsconfig.node.json
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}
十四整合 Axios
安装依赖
npm install axios
Axios 工具类封装
src/utils/request.ts
// src/utils/request.ts
import axios, { InternalAxiosRequestConfig, AxiosResponse } from "axios";
import { ElMessage, ElMessageBox } from "element-plus";
// 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000,
headers: { "Content-Type": "application/json;charset=utf-8" },
});
// 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// const userStore = useUserStoreHook();
// if (userStore.token) {
// config.headers.Authorization = userStore.token;
// }
config.headers.Authorization =
"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImRlcHRJZCI6MSwiZGF0YVNjb3BlIjoxLCJleHAiOjE3NDcwMjY5NjgsInVzZXJJZCI6MiwiaWF0IjoxNzQ3MDE5NzY4LCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6ImQxYmJlMjNkNzA4ZjQ2MzQ5ZGQ5MzI3YTZkYzMyNmI1In0.VH1SS3Y3Rxq8_9cBSLDhfksinDBLkgYp5mPN03bn5To";
return config;
},
(error: any) => {
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const { code, msg } = response.data;
// 登录成功
if (code === "00000") {
return response.data;
}
ElMessage.error(msg || "系统出错");
return Promise.reject(new Error(msg || "Error"));
},
(error: any) => {
if (error.response.data) {
const { code, msg } = error.response.data;
// token 过期,跳转登录页
if (code === "A0230") {
ElMessageBox.confirm("当前页面已失效,请重新登录", "提示", {
confirmButtonText: "确定",
type: "warning",
}).then(() => {
localStorage.clear(); // @vueuse/core 自动导入
window.location.href = "/";
});
} else {
ElMessage.error(msg || "系统出错");
}
}
return Promise.reject(error.message);
}
);
// 导出 axios 实例
export default service;
使用
src/api/auth/index.ts
import request from "@/utils/request";
/**
* 登录API
*
* @param data {LoginData}
* @returns
*/
export function getDeptOptions() {
return request({
url: "/api/v1/dept/options",
method: "get",
});
}
十五 vue-router
1、下载vue-router:
npm install vue-router
2、在项目的src目录下新建文件夹router,router文件夹内新建index.js:
import { createRouter, createWebHistory } from "vue-router";
//导入组件的方式1,先导入,下面引用(在路由这里需要明确导入组件)
import Login from "@/views//login/index.vue";
//定义路由规则
const routes = [
{
path: "/",
redirect: "/login",
},
{
path: "/login", //路由的路径(首页访问路径)
name: "Login", //路由名称,会显示到侧边栏
component: Login, //引入视图组件,其实就是引入vue文件(对应组件)
},
];
//2.创建路由实例并传递上面路由对象routes
const router = createRouter({
//路由的一种前端展现方式,通常使用这个就行了
history: createWebHistory(),
routes,
});
//暴露出去
export default router;
3、在main.js或main.ts中全局挂载:
import router from '@/router'//导入router.js
app.use(router)//全局挂载使用
4、router-view
在根组件中放一个路由的占位符:
通过路由匹配到的组件都会渲染到 router-view当中进行展示
启动:会跳转到登录页面
参考:vue3学习笔记:vue-router(上)_vue3 createwebhistory-CSDN博客
十六 piana
下载
npm install pinia
main.ts
引入 pinia
// src/main.ts
import { createPinia } from "pinia";
import App from "./App.vue";
createApp(App).use(createPinia()).mount("#app");
定义 Store
根据 Pinia 官方文档-核心概念 描述 ,Store 定义分为选项式
和组合式
, 先比较下两种写法的区别:
新建文件 src/store/counter.ts
// src/store/counter.ts
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", () => {
// ref变量 → state 属性
const count = ref(0);
// computed计算属性 → getters
const double = computed(() => {
return count.value * 2;
});
// function函数 → actions
function increment() {
count.value++;
}
return { count, double, increment };
});
父组件
<script setup lang="ts">
import { useCounterStore } from "@/store/counter";
const counterStore = useCounterStore();
</script>
<template>
<h1 class="text-3xl">vue3-element-admin-父组件</h1>
<el-button type="primary" @click="counterStore.increment">count++</el-button>
<span>{{ counterStore.count }}</span>
</template>
promise
function login(loginData: LoginData) {
return new Promise<void>((resolve, reject) => {
logoutClearStorage()
AuthAPI.login(loginData)
.then(async (data: any) => {
try {
const tokenString = data?.data?.token || "TOKEN_KEY"
localStorage.setItem(TOKEN_KEY, tokenString) // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx
// 保存租户列表
const tenantStaffVos = data?.data?.tenantStaffVos || []
setBindTenantLength(tenantStaffVos?.length)
setTenantList(tenantStaffVos)
resolve(data)
} catch (err: any) {
err?.msg && ElMessage.error(err.msg)
}
})
.catch((error) => {
reject(error)
})
})
}
16.2 持久化
在 Vue 3 + Pinia 的开发过程中,我们经常会遇到 页面刷新后 Pinia 状态丢失 的问题。这是因为 Pinia 默认是 内存存储,数据不会自动持久化。一旦刷新页面,所有的状态都会被重置。
为了解决这个问题,我们可以使用 Pinia 持久化存储插件(pinia-plugin-persistedstate),让状态在 localStorage 或 sessionStorage 中持久化存储。
# 安装 Pinia Persist 插件
npm install pinia-plugin-persistedstate
# 注册 persist
插件,让所有 Pinia 的 Store 都支持持久化
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate); // 数据持久化
export * from "./modules/counter";
export default pinia;
# main.ts 引入store
import store from "@/store";
app.use(store);
# 使用 counter.ts
// src/store/counter.ts
import { defineStore } from "pinia";
import { computed, ref } from "vue";
export const useCounterStore = defineStore(
"counter",
() => {
// ref变量 → state 属性
const count = ref(0);
// computed计算属性 → getters
const double = computed(() => {
return count.value * 2;
});
// function函数 → actions
function increment() {
count.value++;
}
return { count, double, increment };
},
{
persist: {
storage: localStorage, // 可以是 localStorage, sessionStorage 或 window.localStorage
pick: ["count"], // 数据持久化数组
key: "useCounterStore", // 状态的键名
},
},
);
# 使用
login/index.vue
<template>
<div>
登录
<h1 class="text-3xl">vue3-element-admin-父组件</h1>
<el-button type="primary" @click="counterStore.increment"
>count++</el-button
>
<span>{{ counterStore.count }}</span>
</div>
</template>
<script setup lang="ts">
import { useCounterStore } from "@/store/modules/counter.ts";
const counterStore = useCounterStore();
</script>
# 验证
刷新 数字仍然不变不会初始化到0,且 localstorage 中有缓存