Vue3 + Pinia 完整使用教程(企业级)

0 阅读2分钟

本文作者:LIO

前言

这是一套企业级可直接落地的 Vue3 + Pinia 完整方案,包含:封装、异步请求、路由权限、持久化、模块化、TS 类型,复制即用。

一、目录结构(标准企业版)

src/
├── stores/             # Pinia 状态管理
│   ├── modules/        # 按模块拆分
│   │   ├── user.ts     # 用户、登录、权限
│   │   ├── app.ts      # 全局配置、主题、菜单
│   │   └── counter.ts  # 示例
│   └── index.ts        # 统一导出
├── api/                # 接口请求
│   └── user.ts
├── router/             # 路由
│   └── index.ts
├── types/              # TS 类型
└── main.ts

二、统一封装入口:stores/index.ts

import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

export * from './modules/user'
export * from './modules/app'
export * from './modules/counter'

export default pinia

main.ts 引入:

import { createApp } from 'vue'
import pinia from './stores'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(pinia)
app.use(router)
app.mount('#app')

三、最常用模块:用户权限(user.ts)

stores/modules/user.ts

import { defineStore } from 'pinia'
import type { UserInfo, LoginParams } from '@/types/user'
import { loginApi, getUserInfoApi } from '@/api/user'

export const useUserStore = defineStore('user', {
  persist: true,

  state: () => ({
    token: '',
    userInfo: null as UserInfo | null,
    roles: [] as string[],
    permissions: [] as string[],
  }),

  getters: {
    isLogin: (state) => !!state.token,
    isAdmin: (state) => state.roles.includes('admin'),
  },

  actions: {
    // 登录
    async login(loginForm: LoginParams) {
      const res = await loginApi(loginForm)
      this.token = res.token
      return res
    },

    // 获取用户信息 + 权限
    async fetchUserInfo() {
      const res = await getUserInfoApi()
      this.userInfo = res.userInfo
      this.roles = res.roles
      this.permissions = res.permissions
      return res
    },

    // 退出登录
    logout() {
      this.token = ''
      this.userInfo = null
      this.roles = []
      this.permissions = []
    },
  },
})

四、全局配置模块(app.ts)

import { defineStore } from 'pinia'

export const useAppStore = defineStore('app', {
  persist: true,

  state: () => ({
    theme: 'light',
    sidebarCollapse: false,
    language: 'zh-CN',
  }),

  actions: {
    toggleSidebar() {
      this.sidebarCollapse = !this.sidebarCollapse
    },
    switchTheme(theme: string) {
      this.theme = theme
    },
  },
})

五、异步请求封装(api/user.ts)

// 模拟接口
export async function loginApi(data: any) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ token: 'TOKEN_' + Date.now() })
    }, 300)
  })
}

export async function getUserInfoApi() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        userInfo: { id: 1, name: '管理员' },
        roles: ['admin'],
        permissions: ['user:add', 'user:edit', 'user:del'],
      })
    }, 300)
  })
}

六、路由权限控制(企业核心)

router/index.ts

import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores'

const routes = [
  {
    path: '/login',
    component: () => import('@/views/login.vue'),
  },
  {
    path: '/',
    component: () => import('@/layout/index.vue'),
    redirect: '/home',
    children: [
      { path: 'home', component: () => import('@/views/home.vue') },
      { path: 'user', component: () => import('@/views/user.vue') },
    ],
  },
]

const router = createRouter({
  history: createWebHistory(),
  routes,
})

// 路由守卫
router.beforeEach((to, from, next) => {
  const userStore = useUserStore()
  const isLogin = userStore.isLogin

  if (to.path === '/login') {
    next()
    return
  }

  if (!isLogin) {
    next('/login')
    return
  }

  next()
})

export default router

七、组件内使用(完整版)

<template>
  <div>
    <div v-if="userStore.isLogin">
      欢迎:{{ userStore.userInfo?.name }}
      <button @click="userStore.logout">退出</button>
    </div>

    <button @click="handleLogin">登录</button>
  </div>
</template>

<script setup lang="ts">
import { useUserStore } from '@/stores'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()
const { isLogin, userInfo } = storeToRefs(userStore)

const handleLogin = async () => {
  await userStore.login({ username: 'admin', password: '123456' })
  await userStore.fetchUserInfo()
}
</script>

八、Pinia 高频实用技巧

1. 解构保持响应式

import { storeToRefs } from 'pinia'
const { count, doubleCount } = storeToRefs(counterStore)

2. 批量修改 state

userStore.$patch({
  token: 'new-token',
  roles: ['editor'],
})

3. 重置整个 store

userStore.$reset()

4. 监听 state 变化

userStore.$subscribe((mutation, state) => {
  console.log('user 状态变化', state)
})

5. 监听 actions 执行

userStore.$onAction(({ name, after }) => {
  if (name === 'login') {
    after(() => console.log('登录完成'))
  }
})

九、给你一套可直接复制的 JS 简化版

如果你不用 TS,直接用这个:

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  persist: true,
  state: () => ({
    token: '',
    userInfo: null,
    roles: [],
  }),
  actions: {
    async login(form) {
      const res = await loginApi(form)
      this.token = res.token
    },
    logout() {
      this.$reset()
    },
  },
})

——个人观点 · 仅供参考——