Vue3+ Vite + Element-Plus + TypeScript 从0到1搭建

一环境准备

二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-importunplugin-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 中有缓存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值