当前位置:   article > 正文

搭建 Vite + Vue3 + Pinia + Element Plus 项目。_创建vue3+vite

创建vue3+vite

一、基础项目搭建:

  • 技术栈
技术栈描述官网
Vue3渐进式 JavaScript 框架https://cn.vuejs.org/
Element Plus基于 Vue 3,面向设计师和开发者的组件库https://element-plus.gitee.io/zh-CN/
Vite前端开发与构建工具https://cn.vitejs.dev/guide/
TypeScript开发语言,是 JavaScript 的超集
Vue RouterVue.js 的官方路由https://router.vuejs.org/zh/
wangEditorTypescript 开发的 Web 富文本编辑器www.wangeditor.com/
Echarts开源可视化图表库https://echarts.apache.org/zh/
vue-i18nVue 国际化多语言插件https://vue-i18n.intlify.dev/
VueUse基于Vue组合式API的实用工具集(类比HuTool工具)http://www.vueusejs.com/guide/
  • 环境准备
环境名称
运行环境Node 16+
开发工具VSCode
VSCode插件Vue Language Features (Volar) TypeScript Vue Plugin (Volar),禁用Vetur

推荐安装nvm控制node版本

1. 项目初始化

按照 vite 官网搭建第一个 Vite 项目,执行以下命令完成 vue 、typescirpt 模板项目的初始化

 npm init vite@latest vue3-element-admin --template vue-ts
  • 1

在这里插入图片描述

2. 使用Vscode启动项目

npm install
npm run dev
  • 1
  • 2

在这里插入图片描述

3. src 路径别名配置

相对路径别名配置,使用 @ 代替 src

配置 vite.config.ts

npm install @types/node
  • 1
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path"; //这个path用到了上面安装的@types/node
const pathSrc = path.resolve(__dirname, "src");
// https://vitejs.dev/config/
export default defineConfig({
  // 路径别名
  resolve: {
    alias: {
      "@": pathSrc, // @代替src
    },
  },

  plugins: [vue()],
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
// src/App.vue
import HelloWorld from '../src/components/HelloWorld.vue'
				
import HelloWorld from '@/components/HelloWorld.vue'

  • 1
  • 2
  • 3
  • 4
  • 5

修改tsconfig.json

"compilerOptions": {
    ...
    "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
    "paths": { // 路径映射,相对于baseUrl
    	"@/*": ["src/*"] 
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

二、使用Element Plus组件库

1. 安装

# 选择一个你喜欢的包管理器

# NPM
$ npm install element-plus --save

# Yarn
$ yarn add element-plus

# pnpm
$ pnpm install element-plus
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2. 用法

2.1 完整引入

如果你对打包后的文件大小不是很在乎,那么使用完整导入会更方便。

// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
2.2 按需导入

您需要使用额外的插件来导入要使用的组件。

2.3 自动导入(推荐)

首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件

npm install -D unplugin-vue-components unplugin-auto-import
  • 1

vite.config.ts - 自动导入配置

src目录下,新建 types 目录,用于存放自动导入函数和组件的TS类型声明文件。

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";

import path from "path"; //这个path用到了上面安装的@types/node
const pathSrc = path.resolve(__dirname, "src");
// https://vitejs.dev/config/
export default defineConfig({
  // 路径别名
  resolve: {
    alias: {
      "@": pathSrc, // @代替src
    },
  },

  plugins: [
    vue(),
    AutoImport({
      // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
      imports: ["vue"],
      eslintrc: {
        enabled: true, // 是否自动生成 eslint 规则,建议生成之后设置 false
        filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件
      },
      dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 指定自动导入函数TS类型声明文件路径
    }),
    Components({
      dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径
    }),
  ],
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

.eslintrc.cjs - 自动导入函数 eslint 规则引入

"extends": [
    "./.eslintrc-auto-import.json"
],
  • 1
  • 2
  • 3
2.4 手动导入

Element Plus 提供了基于 ES Module 的开箱即用的 Tree Shaking 功能。

但你需要安装 unplugin-element-plus 来导入样式。 配置文档参考 docs

App.vue

<template>
  <el-button>我是 ElButton</el-button>
</template>
<script>
  import { ElButton } from 'element-plus'
  export default {
    components: { ElButton },
  }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
// vite.config.ts
import { defineConfig } from 'vite'
import ElementPlus from 'unplugin-element-plus/vite'

export default defineConfig({
  // ...
  plugins: [ElementPlus()],
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

三. 使用 Icon

1. 安装导入 Icon 依赖

npm i -D unplugin-icons
  • 1

2. 配置 vite.config.ts

// vite.config.ts
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";

export default ({ mode }: ConfigEnv): UserConfig => {

  return {
    plugins: [
      // ...
      AutoImport({
        // ...  
        resolvers: [
          // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
          ElementPlusResolver(),
          // 自动导入图标组件
          IconsResolver({}),
        ]
        vueTemplate: true, // 是否在 vue 模板中自动导入
        dts: path.resolve(pathSrc, 'types', 'auto-imports.d.ts') // 自动导入组件类型声明文件位置,默认根目录
          
      }),
      Components({ 
        resolvers: [
          // 自动导入 Element Plus 组件
          ElementPlusResolver(),
          // 自动注册图标组件
          IconsResolver({
            enabledCollections: ["ep"] // element-plus图标库,其他图标库 https://icon-sets.iconify.design/
          }),
        ],
        dts: path.resolve(pathSrc, "types", "components.d.ts"), //  自动导入组件类型声明文件位置,默认根目录
      }),
      Icons({
        // 自动安装图标库
        autoInstall: true,
      }),
    ],
  };
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

参考配置:element-plus-best-practices - vite.config.ts

<template>
  <h1>{{ msg }}</h1>
  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
  </div>
  <div class="card">
    <el-button type="success"><i-ep-SuccessFilled />Success</el-button>
    <el-button type="info"><i-ep-InfoFilled />Info</el-button>
    <el-button type="warning"><i-ep-WarningFilled />Warning</el-button>
    <el-button type="danger"><i-ep-WarnTriangleFilled />Danger</el-button>
  </div>
</template>

<style scoped>
.read-the-docs {
  color: #888;
}
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在这里插入图片描述

四、使用 SVG 图标

通过 vite-plugin-svg-icons 插件,Iconfont 第三方图标库实现本地图标。

1. 安装依赖

npm install -D fast-glob@3.2.11 
npm install -D vite-plugin-svg-icons@2.0.1 
  • 1
  • 2

2. 创建 src/assets/icons 目录 , 把svg 图标放入。

在这里插入图片描述

3. main.ts 引入注册脚本

// src/main.ts
import 'virtual:svg-icons-register';
  • 1
  • 2

4. 配置 vite.config.ts

// vite.config.ts
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';

export default ({command, mode}: ConfigEnv): UserConfig => {
 return (
     {
         plugins: [
             createSvgIconsPlugin({
                 // 指定需要缓存的图标文件夹
                 iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
                 // 指定symbolId格式
                 symbolId: 'icon-[dir]-[name]',
             })
         ]
     }
 )
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

5. SVG 组件封装以及使用

  • 组件封装,新建 src/components/SvgIcon/index.vue
<script setup lang="ts">
const props = defineProps({
  prefix: {
    type: String,
    default: "icon",
  },
  name: {
    type: String,
    required: false,
  },
  color: {
    type: String,
  },
  size: {
    type: String,
    default: "1em",
  },
});

const symbolId = computed(() => `#${props.prefix}-${props.name}`);
</script>

<template>
  <svg
    aria-hidden="true"
    class="svg-icon"
    :style="'width:' + size + ';height:' + size"
  >
    <use :xlink:href="symbolId" :fill="color" />
  </svg>
</template>

<style scoped>
.svg-icon {
  display: inline-block;
  outline: none;
  width: 1em;
  height: 1em;
  vertical-align: -0.15em; /* 因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果 */
  fill: currentColor; /* 定义元素的颜色,currentColor是一个变量,这个变量的值就表示当前元素的color值,如果当前元素未设置color值,则从父元素继承 */
  overflow: hidden;
}
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 组件使用
<template>
 <el-button type="info"><svg-icon name="bl"/>本地svg</el-button>
</template>
  • 1
  • 2
  • 3

在这里插入图片描述

参考:vite-plugin-svg-icons 文档

五、使用全局状态管理工具 pinia

pinia 官网

1. pinia介绍

Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。

pinia优点:

  1. 支持Vue2和Vue3,也就是老项目也可以使用Pinia。
  2. 足够轻量,压缩后的体积只有1kb左右。
  3. 完整的TypeScript支持,Vue3版本的一大优势就是对TypeScript的支持,所以Pinia也做到了完整的支持。如果你对Vuex很熟悉的化,一定知道Vuex对TS的语法支持不是完整的。
  4. 代码更加简洁,可以实现很好的代码自动分割。Vue2的时代,写代码需要来回翻滚屏幕屏幕找变量,非常的麻烦,Vue3的Composition api完美了解决这个问题。 可以实现代码自动分割,pinia也同样继承了这个优点。
  5. 去除 mutations,只有 state,getters,actions;actions 支持同步和异步。
  6. 不需要嵌套模块,让代码更加扁平化,只有 store 的概念,store 之间可以自由使用,每一个store都是独立的,符合Vue3的Composition api 。
  7. 无需手动添加 store,store 一旦创建便会自动添加。
1.1 安装
yarn add pinia
# 或者使用 npm
npm install pinia
  • 1
  • 2
  • 3
1.2 引入

创建一个 pinia 实例 (根 store) 并将其传递给应用:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
1.3 使用

src 文件夹下创建 store 文件夹,并添加 counter.js 文件。

2. Pinia中的Store

2.1 定义store
  1. Store (如 Pinia)是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有三个概念,stategetteraction,我们可以假设这些概念相当于组件中的 datacomputed methods
  2. store 是用 defineStore(name, function | options) 定义的,建议其函数返回的值命名为 use…Store 方便理解
  • 参数 name:必填值且唯一,简单点说就可以理解成是一个命名空间。
  • 参数 function | options:可以是对象或函数形式。
    • 对象形式【选项模式】,其中配置 stategettersactions 选项。
    • 函数形式【组合模式,类似组件组合式 API 的书写方式】,定义响应式变量和方法,并且 return 对应的变量和方法;ref() 相当于 state,computed() 相当于 getters,function() 相当于 actions。
2.2 使用store读取和写入 state

下面案例以选项模式为例:

store 文件夹下创建 counter.js 文件,这个文件就是存有关 counter 的一些相关的数据。

import {defineStore} from 'pinia'

/*defineStore 是需要传参数的,其中第一个参数是id,就是一个唯一的值,
简单点说就可以理解成是一个命名空间.
第二个参数就是一个对象,里面有三个模块需要处理,第一个是 state,
第二个是 getters,第三个是 actions。
*/
const useCounter = defineStore("counter",{
    state:() => ({
        count:88,
    }),
    getters: {

  	},
  	actions: {

  	}
})

//暴露useCounter这个模块
export default useCounter
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在页面中使用:

<template>
  <div>
    <el-button>我是 ElButton</el-button>
    <div>store===>counter.js的count值:{{ counterStore.count }}</div>
  </div>
</template>

<script setup>
// 引入创建的store
import useCounter from "./store/counter";
// 调用store里的方法
const counterStore = useCounter();
console.log("counterStore", counterStore.count);
</script>

<style scoped></style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在这里插入图片描述

**注意:**在使用时 ,取值时不用和 vuex 一样还要.state,直接.state里面的count值就行了,写法:counterStore.count。

案例需求,点击按钮加一:

我们分别用两种方法取count值,一个解构,一个不解构

<template>
  <div>
    <h2>Home Word</h2>
    <h2>展示pinia的counter的count值: {{ counterStore.count }}</h2>
    <h2>展示解构出来的pinia的counter的count值: {{ count }}</h2>
    <el-button @click="addCount">function count+1</el-button>
  </div>
</template>

<script setup>
// 引入创建的store
import useCounter from "./store/counter";
// 调用store里的方法
const counterStore = useCounter();
// 结构count值
const { count } = counterStore;
function addCount() {
  //这里可以直接操作count,修改(写入)store,在vuex还要commit在mutaitions修改数据
  counterStore.count++;
}
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在这里插入图片描述

我们发现解构出来的值 失去响应式了

解决方案:

为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs() 。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。

<template>
  <div>
    <h2>Home Word</h2>
    <!-- 使用useCounter的实例获取state中的值 -->
    <h2>展示pinia的counter的count值: {{ counterStore.count }}</h2>
    <h2>展示解构出来的pinia的counter的count值: {{ count }}</h2>
    <el-button @click="addCount">function count+1</el-button>
  </div>
</template>

<script setup>
import { storeToRefs } from "pinia";
// 引入创建的store
import useCounter from "./store/counter";
// 调用store里的方法
const counterStore = useCounter();
// 结构count值
const { count } = storeToRefs(counterStore);
function addCount() {
  //这里可以直接操作count,修改(写入)store,在vuex还要commit在mutaitions修改数据
  counterStore.count++;
}
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在这里插入图片描述

2.3 修改state数据
  • 定义一个关于user的Store
import { defineStore } from 'pinia'

const useUser = defineStore("user", {
  state: () => ({
    name: "why",
    age: 18,
    level: 100
  }),
  actions:{
    setCurrent () {
        this.level++
    }
  }
})

export default useUser
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 三种修改state的方法
<template>
  <div>
    <h2>Home Word</h2>
    <h2>姓名: {{ name }}</h2>
    <h2>年龄: {{ age }}</h2>
    <h2>等级: {{ level }}</h2>
    <el-button @click="updateStore">修改user信息</el-button>
    <el-button @click="updateStoreLevel">修改level</el-button>
  </div>
</template>

<script setup>
import useUser from "./store/user";
import { storeToRefs } from "pinia";
const userStore = useUser();
const { name, age, level } = storeToRefs(userStore);

function updateStore() {
  // 方法一:一个个的修改状态
  // userStore.name = "zimo";
  // userStore.age = 20;

  // 方法二 :一次性修改多个状态
  // userStore.$patch({
  //   name: "zimo",
  //   age: 20,
  // });

  // 方法三:替换state为新的对象
  const oldState = userStore.$state;
  userStore.$state = {
    ...userStore.$state,
    name: "curry",
    level: 200,
  };
  // 下面会返回true
  console.log(oldState === userStore.$state);
}
// 方法四:通过actions修改
const updateStoreLevel = () => {
  userStore.setCurrent();
};
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

在这里插入图片描述在这里插入图片描述

2.4 重置state数据

新增一个重置按钮:

<el-button @click="resetStore">重置user信息</el-button>
  • 1

新增一个重置方法:

function resetStore() {
  userStore.$reset();
}
  • 1
  • 2
  • 3

在这里插入图片描述

3. Pinia中的getters

getters 类似于 vue 里面的计算属性,可以对已有的数据进行修饰。getters中可以定义接受一个state作为参数的函数,不管调用多少次,getters中的函数只会执行一次,且都会缓存。

3.1 定义getters
  • 基本使用
  • 一个getter引入另外一个getter
  • getters也支持返回一个函数
  • getters中用到别的store中的数据
// 定义关于counter的store
import { defineStore } from 'pinia'

const useCounter = defineStore("counter", {
  state: () => ({
    count: 99
  }),
  getters: {
    // 1.基本使用
    doubleCount(state) {
      return state.count * 2
    },
      
    // 2.一个getter引入另外一个getter
    doubleCountAddOne() {
      // this是store实例,可以直接使用另一个getter
      return this.doubleCount + 1
    },
      
    // 3.getters也支持返回一个函数
    getFriendById(state) {
      return function(id) {
        return id
      }
    },
    
    // 4.getters中用到别的store中的数据
    showMessage(state) {
      //获取user信息,拿到useUser模块
      const userStore = useUser()
      //拼接信息
      return `name:${userStore.name}-count:${state.count}`
    }  
  },
})

export default useCounter
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
3.2 访问getters
<template>
  <div>
    <!-- 在模板中使用 -->
    <h2>基本使用:doubleCount: {{ counterStore.doubleCount }}</h2>
    <h2>
      一个getter引入另外一个getter:doubleCountAddOne:
      {{ counterStore.doubleCountAddOne }}
    </h2>
    <h2>函数id-99: {{ counterStore.getFriendById(99) }}</h2>
    <h2>
      getters中获取另一个store中的state/getters数据==>showMessage:{{
        counterStore.showMessage
      }}
    </h2>
  </div>
</template>

<script setup>
import useCounter from "./store/counter";

const counterStore = useCounter();
// 在js文件中使用
const doubleCount = counterStore.doubleCount;
const doubleCountAddOne = counterStore.doubleCountAddOne;
const frend = counterStore.getFriendById(99);
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在这里插入图片描述
**注意:**getters中用别的store中的数据 ,在counter模块中拿user模块的store数据,要引入user模块。

4. Pinia中的actions

actions 相当于组件中的 methods

可以使用 defineStore() 中的 actions 属性定义,并且它们非常适合定义一些业务逻辑。

getters 一样,在 action 中可以通过 this 访问整个 store 实例的所有操作。

4.1 同步
  • 定义关于counter的store
// 定义关于counter的store
import { defineStore } from 'pinia'

const useCounter = defineStore("counter", {
  state: () => ({
    count: 99,
  }),
  // 定义actions
  actions: {
    increment() {
      this.count++
    },
    incrementNum(num) {
      this.count += num
    }
  }
})

export default useCounter
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 同步使用方式
<template>
  <div>
    <h2>Home Word</h2>
    <h2>doubleCount: {{ counterStore.count }}</h2>
    <el-button @click="changeState">修改state</el-button>
  </div>
</template>

<script setup>
import useCounter from "./store/counter";

const counterStore = useCounter();
function changeState() {
  // 可以通过counterStore对象直接使用
  counterStore.increment();
  // counterStore.incrementNum(10);
}
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
4.2 异步

结合async await 修饰

  • 定义store
import { defineStore } from 'pinia'

const Login = () => {
  return new Promise((resolve) => {
      setTimeout(() => {
          resolve({
              name: 'Rlm',
              isLogin: true
          })
      }, 3000)
  })
}

const useUser = defineStore("user", {
  state: () => ({
    user: {},
    name: "123"
  }),
  actions:{
    async getLoginInfo() {
      const result = await Login()
      this.user = result;
  }
  }
})

export default useUser
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 页面使用
<template>
  <div>
    <h2>Home Word</h2>
    <h2>user: {{ userStore.user }}</h2>
    <el-button @click="Login">获取user</el-button>
  </div>
</template>

<script setup>
import useUser from "./store/user";
const userStore = useUser();
const Login = () => {
  userStore.getLoginInfo();
};
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 多个action互相调用getLoginInfo setName
import { defineStore } from 'pinia'

const Login = () => {
  return new Promise((resolve) => {
      setTimeout(() => {
          resolve({
              name: 'Rlm',
              isLogin: true
          })
      }, 3000)
  })
}

const useUser = defineStore("user", {
  state: () => ({
    user: {},
    name: "123"
  }),
  actions:{
    async getLoginInfo() {
      const result = await Login()
      this.user = result;
      this.setName(result.name)
    },
    setName (name) {
      this.name = name;
    }
  
  }
})

export default useUser
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

在这里插入图片描述

5. Pinia的API

5.1 重置 state

$reset() 将会把state所有值 重置回 原始状态。前面2.4 重置state数据有写到。

5.2 订阅 state

类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次 (例如,当使用上面的函数版本时)。

  • 定义user store
import { defineStore } from 'pinia'

const useUser = defineStore("user", {
  state: () => ({
    name: "why",
    age: 18,
    level: 100
  }),
  actions:{
    setCurrent () {
        this.level++
    }
  }
})

export default useUser
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 页面使用
<template>
  <div>
    <h2>Home Word</h2>
    <h2>姓名: {{ name }}</h2>
    <h2>年龄: {{ age }}</h2>
    <h2>等级: {{ level }}</h2>
    <el-button @click="updateStore">修改user信息</el-button>
    <el-button @click="updateStoreLevel">修改level</el-button>
    <el-button @click="resetStore">重置user信息</el-button>
  </div>
</template>

<script setup>
import useUser from "./store/user";
import { storeToRefs } from "pinia";
const userStore = useUser();
const { name, age, level } = storeToRefs(userStore);

userStore.$subscribe((mutation, state) => {
  // mutation.type // type的三种类型 'direct' | 'patch object' | 'patch function'

  // 和 cartStore.$id 一样
  // mutation.storeId // 'user'

  // payload只有 mutation.type === 'patch object'的情况下才可用
  // mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
  console.log(mutation, state);

  // 每当状态发生变化时,将整个 state 持久化到本地存储。
  localStorage.setItem("user", JSON.stringify(state));
});

function updateStore() {
  // 方法一:一个个的修改状态  订阅 state type 为 'direct'
  // userStore.name = "zimo";
  // userStore.age = 20;

  // 方法二 :一次性修改多个状态  订阅 state type 为 'patch object'
  userStore.$patch({
    name: "zimo",
    age: 20,
  });

  // 方法三:替换state为新的对象 订阅 state type 为 'patch function'
  // const oldState = userStore.$state;
  // userStore.$state = {
  //   ...userStore.$state,
  //   name: "curry",
  //   level: 200,
  // };
  // 下面会返回true
  // console.log(oldState === userStore.$state);
}
// 方法四:通过actions修改 订阅 state type 为 'direct'
const updateStoreLevel = () => {
  userStore.setCurrent();
};

// 重置user信息方法
function resetStore() {
  userStore.$reset();
}
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

在这里插入图片描述
在这里插入图片描述
默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离:

userStore.$subscribe(
  (mutation, state) => {
    // mutation.type // 'direct' | 'patch object' | 'patch function'

    // 和 cartStore.$id 一样
    // mutation.storeId // 'user'

    // 只有 mutation.type === 'patch object'的情况下才可用
    // mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
    console.log(mutation, state);

    // 每当状态发生变化时,将整个 state 持久化到本地存储。
    localStorage.setItem("user", JSON.stringify(state));
  },
  {
    detached: true,
  }
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
5.3 订阅 action

你可以通过 store.$onAction() 来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after 表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。同样地,onError 允许你在 action 抛出错误或 reject 时执行一个回调函数。这些函数对于追踪运行时错误非常有用,类似于Vue docs 中的这个提示。

  • 定义store
import { defineStore } from 'pinia'

const Login = () => {
  return new Promise((resolve) => {
      setTimeout(() => {
          resolve({
              name: 'Rlm',
              isLogin: true
          })
      }, 3000)
  })
}

const useUser = defineStore("user", {
  state: () => ({
    user: {},
    name: "123",
    count:1
  }),
  actions:{
    async getLoginInfo() {
      const result = await Login()
      this.user = result;
      this.setName(result.name)
    },
    setName (name) {
      this.name = name;
    },
    testOnAction(id) {
      this.count++
      console.log(this.count)
      return Promise.resolve('这是testOnAction返回的值')
    },
    testOnAction2(...args) {
      console.log(...args)
      return Promise.reject('这是testOnAction222错误返回的值')
    }
  }
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 页面使用
<template>
  <div>
    <h2>Home Word</h2>
    <h2>user: {{ userStore.user }}</h2>
    <h2>name: {{ userStore.name }}</h2>
    <el-button @click="Login">获取user</el-button>
  </div>
</template>

<script setup>
import useUser from "./store/user";
const userStore = useUser();
const Login = () => {
  userStore.getLoginInfo();
};
// 订阅 action
const unsubscribe = userStore.$onAction(
  ({
    name, // action 名称
    store, // store 实例,类似 `someStore`
    args, // 传递给 action 的参数数组
    after, // 在 action 返回或解决后的钩子
    onError, // action 抛出或拒绝的钩子
  }) => {
    // 这将在执行 "store "的 action 之前触发。
    console.log(`Start "${name}" with params [${args.join(", ")}].`);

    if (name === "testOnAction") {
      // 这将在 action 成功并完全运行后触发。
      // 它等待着任何返回的 promise
      after((reject) => {
        //这里可以执行一些操作
        console.log("订阅 action---after===>", reject);
      });
    }

    // 如果 action 抛出或返回一个拒绝的 promise,这将触发
    onError((error) => {
      console.log(error);
    });
  }
);

// 手动删除监听器
// unsubscribe();

userStore.testOnAction(1);
userStore.testOnAction2(1, 2, 3, 45);
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

在这里插入图片描述

6. Pinia 数据持久化

6.1 使用插件
npm i pinia-plugin-persist
  • 1
  • main.js中引入插件
// 引入pinia
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'
const pinia = createPinia()
pinia.use(piniaPluginPersist)
app.use(pinia)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 定义store时,开启persist
import { defineStore } from 'pinia'
 
export default defineStore('commonStore', {
 
  //开启数据存储
  persist: { enabled: true },
 
  state: () => {
    return {
     info: {} 
    }
  },
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • persist配置项
 persist: {
    enabled: true,
    strategies: [
      {
        key: 'all', //存储的key值,默认为store名,
        storage: localStorage, //存储的位置,默认为sessionStorage
        paths: ['info'] //需要存储的state状态,默认为所有
      }
    ]
  },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

六、使用scss

1. 安装依赖

npm i -D sass 
  • 1

2. 创建 src/styles/variables.scss 变量文件

添加变量 $bg-color 定义,注意规范变量以 $ 开头

// src/styles/variables.scss
$bg-color:#FFB03E;
  • 1
  • 2

3. 配置 vite.config.ts

导入 SCSS 全局变量文件

// vite.config.ts
css: {
    // CSS 预处理器
    preprocessorOptions: {
        //define global scss variable
        scss: {
            javascriptEnabled: true,
            additionalData: `@use "@/styles/variables.scss" as *;`
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4. style 使用 SCSS 全局变量

<script setup lang="ts">
import { ref } from "vue";

defineProps<{ msg: string }>();

const count = ref(0);
</script>

<template>
  <h1>{{ msg }}</h1>
  <h1 class="box">hello word</h1>
  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
  </div>
  <div class="card">
    <el-button type="success"><i-ep-SuccessFilled />Success</el-button>
    <el-button type="info"><i-ep-InfoFilled />Info</el-button>
    <el-button type="warning"><i-ep-WarningFilled />Warning</el-button>
    <el-button type="danger"><i-ep-WarnTriangleFilled />Danger</el-button>
    <el-button type="info"><svg-icon name="bl" />本地 svg</el-button>
  </div>
</template>

<style lang="scss" scoped>
.read-the-docs {
  color: #888;
}
.box {
  background-color: $bg-color;
}
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

在这里插入图片描述

5. 在 TypeScript 使用

  • 新建variables.module.scss 文件
// 导出 variables.scss 文件的变量
:export{
    bgColor:$bg-color
}
  • 1
  • 2
  • 3
  • 4
  • 使用变量
<script setup lang="ts">
import { ref } from "vue";
import variables from "@/styles/variables.module.scss";
console.log("scss变量颜色===>", variables.bgColor);
defineProps<{ msg: string }>();

const count = ref(0);
</script>

<template>
  <h1 class="box">hello word</h1>
  <h1 style="height: 50px" :style="{ 'background-color': variables.bgColor }">
    {{ msg }}
  </h1>
  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
  </div>
  <div class="card">
    <el-button type="success"><i-ep-SuccessFilled />Success</el-button>
    <el-button type="info"><i-ep-InfoFilled />Info</el-button>
    <el-button type="warning"><i-ep-WarningFilled />Warning</el-button>
    <el-button type="danger"><i-ep-WarnTriangleFilled />Danger</el-button>
    <el-button type="info"><svg-icon name="bl" />本地 svg</el-button>
  </div>
</template>

<style lang="scss" scoped>
.read-the-docs {
  color: #888;
}
.box {
  background-color: $bg-color;
}
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

在这里插入图片描述

七、使用 UnoCSS

unocss是一个即时的原子CSS引擎,它可以让你用简短的类名来控制元素的样式,而不需要写复杂的CSS代码。

1. 安装依赖

npm install -D unocss
  • 1

2. 配置 vite.config.ts

// vite.config.ts
import UnoCSS from 'unocss/vite'

export default {
  plugins: [
    UnoCSS({ /* options */ }),
  ],
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3. main.ts 引入 uno.css

import 'uno.css'
  • 1

4. VSCode 安装 UnoCSS 插件

在这里插入图片描述
安装启用后,页面上就能看出哪些 class 使用 unocss 提供 (带有虚线),并且能显示类名对应的样式内容。

  • 配置vscode的自动提示

ctrl + shift + p => 输入 open Setting => 选择 首选项:打开用户设置
在这里插入图片描述
添加以下配置

"editor.quickSuggestions": {
    "strings": true,
    "other": true,
    "comments": true,
  },
  • 1
  • 2
  • 3
  • 4
  • 5

配置完成后,在输入类名时会对 unocss 中存在的 class 进行智能提示。

八、使用环境变量

Vite的环境变量主要是为了区分开发、测试、生产等环境的变量。

1. 项目根目录新建 env 配置文件

.env.development.env.production
在这里插入图片描述

  • 开发环境变量配置:.env.development
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
VITE_APP_TITLE = 'my-vue3-element-admin'
VITE_APP_PORT = 9000
VITE_APP_BASE_API = '/dev-api'
  • 1
  • 2
  • 3
  • 4
  • 生产环境变量配置:.env.production
VITE_APP_TITLE = 'my-vue3-element-admin'
VITE_APP_PORT = 9000
VITE_APP_BASE_API = '/prod-api'
  • 1
  • 2
  • 3

2. 环境变量智能提示

新建 src/types/env.d.ts 文件存放环境变量TS类型声明

interface ImportMetaEnv {
  /**
   * 应用标题
   */
  VITE_APP_TITLE: string;
  /**
   * 应用端口
   */
  VITE_APP_PORT: number;
  /**
   * API基础路径(反向代理)
   */
  VITE_APP_BASE_API: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在这里插入图片描述

九、反向代理

本地开发环境通过 Vite 配置中 server 反向代理解决浏览器跨域问题,生产环境则是可以通过 nginx 配置反向代理 。

import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";

import path from "path"; //这个path用到了上面安装的@types/node
const pathSrc = path.resolve(__dirname, "src");

// 引入 icon
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";

// svg
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";

// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const env = loadEnv(mode, process.cwd());
  return {
    // 路径别名
    resolve: {
      alias: {
        "@": pathSrc, // @代替src
      },
    },
    css: {
      // CSS 预处理器
      preprocessorOptions: {
        //define global scss variable
        scss: {
          javascriptEnabled: true,
          additionalData: `@use "@/styles/variables.scss" as *;`,
        },
      },
    },
    server: {
      host: "0.0.0.0",
      port: Number(env.VITE_APP_PORT),
      open: true, // 运行是否自动打开浏览器
      proxy: {
        // 反向代理解决跨域
        [env.VITE_APP_BASE_API]: {
          target: 'http://localhost:8989',  // 本地接口地址
          changeOrigin: true,
          rewrite: (path) =>
            path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), // 替换 /dev-api 为 target 接口地址
        },
      },
    },

    plugins: [
      vue(),
      AutoImport({
        // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
        imports: ["vue"],
        eslintrc: {
          enabled: false, // 是否自动生成 eslint 规则,建议生成之后设置 false
          filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件
        },
        resolvers: [
          // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
          ElementPlusResolver(),
          // 自动导入图标组件
          IconsResolver({ enabledCollections: ["ep"] }),
        ],
        vueTemplate: true, // 是否在 vue 模板中自动导入
        dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 指定自动导入函数TS类型声明文件路径
      }),
      Components({
        resolvers: [
          // 自动导入 Element Plus 组件
          ElementPlusResolver(),
          // 自动注册图标组件
          IconsResolver({
            enabledCollections: ["ep"], // element-plus图标库,其他图标库 https://icon-sets.iconify.design/
          }),
        ],
        dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径
      }),
      Icons({
        // 自动安装图标库
        autoInstall: true,
      }),
      // createSvgIconsPlugin({
      //   // 指定需要缓存的图标文件夹
      //   iconDirs: [path.resolve(process.cwd(), "src/assets/icons")],
      //   // 指定symbolId格式
      //   symbolId: "icon-[dir]-[name]",
      // }),

      createSvgIconsPlugin({
        // 指定需要缓存的图标文件夹
        iconDirs: [path.resolve(process.cwd(), "src/assets/icons")],
        // 指定symbolId格式
        symbolId: "icon-[dir]-[name]",

        /**
         * 自定义插入位置 'body-last' | 'body-first'
         * @default: body-last
         */
        inject: "body-last",

        /**
         * custom dom id
         * @default: __svg__icons__dom__
         */
        customDomId: "__svg__icons__dom__",
      }),
    ],
  };
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/225677
推荐阅读
相关标签
  

闽ICP备14008679号