[Vue3 + Vite + setup syntactic sugar + Pinia + VueRouter + Element Plus Part 5] [The end of the article with source code]

In the fourth article, we learned about the use of mixinpublic method encapsulation VueRouterand the configuration of permission routing. In this issue, we will describe Piniaand use Piniaand Axiosinterface data to complete 登录functions and permissions路由改造

The knowledge you need to master in this issue is as follows:

  • Piniause in the project and持久化
  • Use Piniaand Axiosto complete 登录the function
  • Use Piniato complete the权限路由改造

1. Pinia installation and use

$ npm install piniaInstall pinia
$ npm i pinia-plugin-persist --saveInstall the pinia persistence plugin

main.jsintroduce pinia


import {
    
     createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'

// pinia 部分
import {
    
     createPinia } from 'pinia';
import piniaPersist from 'pinia-plugin-persist'
const pinia = createPinia()
pinia.use(piniaPersist)

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'

import 'nprogress/nprogress.css'

import axios from 'axios'
import VueAxios from 'vue-axios'


createApp(App).use(router).use(pinia).use(ElementPlus, {
    
     locale: zhCn }).use(VueAxios, axios).mount('#app')

scrCreate a new directory under the directory store, and create a new directory under this directoryindex.js

insert image description here


import {
    
     defineStore } from "pinia"
/**
 * 这个 第一个参数main,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。 
 * 将返回的函数命名为use...(更好的语义化) 是跨可组合项的约定,以使其符合你的使用习惯。
*/
export const useStore = defineStore('user', {
    
    

  state() {
    
    
    return {
    
    
      current_userInfo: {
    
    
        id: '',
        username: '',
        password: '',
        isOpen: '',
        apiToken: ''
      },
      animationRoute: [],
    }
  },
  /**
 * 用来封装计算属性 有缓存功能  类似于computed
   */
  getters: {
    
    
    getNum(state) {
    
    
      return `我是一个计数器${
      
      state.count}`
    }

    //或者不使用state传递参数直接使用this
    //getNum(){
    
    
    //    return `我是一个计数器${this.count}`
    // }

  },
  /**
 * 编辑业务逻辑  类似于methods
   */
  actions: {
    
    
    SAVE_USER_MESSAGE(val) {
    
    
      this.current_userInfo = val
    },
    SAVE_ANIMATION_ROUTER(val) {
    
    
      this.animationRoute = val
    }
  },
  persist: {
    
    
    enabled: true, // 开启数据缓存
    strategies: [
      {
    
    
        key: 'user',//存储key值
        storage: localStorage, // 默认是sessionStorage
      }
    ],
  }
})

Pinia explains:

  • stateequivalent vuextodata
  • gettersequivalent vuextogetters
  • actionsis equivalent to the sum of vuexinmethods、actions
  • persistPersistent data configuration to prevent Pinia刷新后数据丢失
  • In Pinia it is possible to use thisthe keyword

2. Realization of login function

1. Create a user JSON file and a dynamic routing file

insert image description here·

// user.json
{
    
    
  "data": [
    {
    
    
      "id": 1,
      "username": "admin",
      "password": "123456",
      "isOpen": "true",
      "apiToken": "token_assets"
    }
  ]
}
// router.json
{
    
    
  "data": [
    {
    
    
      "name": "admin",
      "path": "/admin",
      "hidden": false,
      "meta": {
    
    
        "title": "首页"
      },
      "children": [
        {
    
    
          "name": "admin/user",
          "path": "user",
          "hidden": false,
          "meta": {
    
    
            "title": "用户管理"
          }
        },
        {
    
    
          "name": "admin/role",
          "path": "role",
          "hidden": false,
          "meta": {
    
    
            "title": "角色管理"
          }
        }
      ]
    }
  ],
  "code": 200
}

2. Create an api request interface

insert image description here

// login.js
import request from '@/utils/request'
export const handleLogin = () => {
    
    
  return request({
    
    
    url: 'user.json',
    method: 'GET'
  })
}
// common.js
import request from '@/utils/request'

// 获取动态路由接口
export const animationRoute = () =>{
    
    
  return request({
    
    
    url:'router.json',
    method:'GET'
  })
}

3. Page construction and interface request

目录结构如下

  • index.vueas the parent component
  • componentsThe sub-components in the directory loginForm.vueinclude Formthe form

The reason for this is:

  1. Reduce the code of each page to make the code more concise and convenient for later maintenance
  2. review组件传值

insert image description here

index.vue the code

<template>
  <div class="login">
    <loginForm @LoginEmit="LoginEmit" />
  </div>
</template>
import {
    
     defineComponent, onMounted, ref } from 'vue'
import {
    
     useRouter } from 'vue-router'
import {
    
     ElMessage } from 'element-plus'
// 引入 pinia 方法
import {
    
     useStore } from '@/store/index.js'
// 引入 登录 api
import {
    
     handleLogin } from '@/api/login.js'
// 引入 权限路由 api
import {
    
     animationRoute } from '@/api/common.js'
// 引入 登录表单子组件
import loginForm from './components/loginForm.vue'

// 注册组件
const Component = defineComponent({
    
    
  loginForm
})


/**
 * @type data
 * @description 所有数据都在此体现
 * **/

const Router = useRouter()
const store = useStore()

/**
* @type methods
* @description 所有方法、事件都在此层中体现
* **/

// 登录按钮方法 由子组件触发并携带参数
const LoginEmit = async form => {
    
    
  const {
    
     username, password } = form
  let res = await handleLogin()
  // 以下三行代码是判断登录账号密码是否正确
  let currentUser = res.data.filter(item => {
    
     return item.username === username })
  if (currentUser.length < 1) return ElMessage.error('用户不存在')
  if (currentUser[0].password !== password) return ElMessage.error('密码错误')
  // 确保登录成功后 调用 Pinia 方法存储用户信息
  store.SAVE_USER_MESSAGE(currentUser[0])
  // 获取 权限路由
  let routerList = await animationRoute(username)
  // 调用 Pinia 存储权限路由方法 将路由信息存储到 store中
  store.SAVE_ANIMATION_ROUTER([...routerList.data])
  // 跳转到 首页
  Router.push('/home')
}

form component

<template>
  <div class="login-form">
    <div class="title">用户登录</div>
    <div class="login_content">
      <el-form ref="ruleLogin" :model="loginForm" :rules="rules">
        <el-form-item label="账号:" prop="username">
          <el-input v-model="loginForm.username" autocomplete="off" placeholder="请输入账号" />
        </el-form-item>
        <el-form-item label="密码:" prop="password">
          <el-input v-model="loginForm.password" type="password" autocomplete="off" placeholder="请输入密码" />
        </el-form-item>
      </el-form>
      <el-button type="primary" @click="handleLogin">登录</el-button>
    </div>
  </div>
</template>
import {
    
     ref } from 'vue'

/**
 * @type data
 * @description 所有数据都在此体现
 * **/

const loginForm = ref({
    
    
  username: '',
  password: ''
})
const ruleLogin = ref(null)
const rules = {
    
    
  username: {
    
     required: true, message: "账号不可为空", trigger: "blur" },
  password: {
    
     required: true, message: "密码不可为空", trigger: "blur" },
}


/**
* @type methods
* @description 所有方法、事件都在此层中体现
* **/

const emit = defineEmits(['LoginEmit'])

// 登录按钮点击事件
const handleLogin = async () => {
    
    
  try {
    
    
  	// 先判断表单校验是否通过,再去触发 父组件方法并传参
    await ruleLogin.value.validate()
    // 触发父组件登录方法
    emit('LoginEmit', loginForm.value)
  } catch (err) {
    
    
  }
}

4. Permission routing transformation

import {
    
     createRouter, createWebHistory } from 'vue-router'
import {
    
     homeRoutes } from './modules/home'
import {
    
     loginRoutes } from './modules/login'
import {
    
     getlocalstroage, clearSession, clearLocal, showNprogress, hideNprogress } from '@/mixin'
import {
    
     useStore } from '@/store'
import {
    
     storeToRefs } from "pinia"

// 首先把你需要动态路由的组件地址全部获取
let modules = import.meta.glob('../views/**/*.vue')
let flag = true

const router = createRouter({
    
    
  history: createWebHistory(),
  routes: [...loginRoutes, ...homeRoutes]
})

router.beforeEach((to, from, next) => {
    
    
  showNprogress()
  if (to.meta.title) {
    
    
    document.title = to.meta.title
  }

  const {
    
     animationRoute } = storeToRefs(useStore())
  addDynamicRoute(animationRoute.value)
  if (animationRoute.value.length > 0) {
    
    
    if (flag) {
    
    
      const NotFound = {
    
     path: '/:pathMatch(.*)*', redirect: '/404' }
      router.addRoute(NotFound)
      next({
    
     ...to, replace: true })
      router.options.routes.push(JSON.parse(JSON.stringify(...animationRoute.value),NotFound))
      flag = false
    }
  }

  if (to.path != '/login') {
    
    
    let apiToken = getlocalstroage('user') ? JSON.parse(getlocalstroage('user'))?.current_userInfo?.apiToken : null
    if (!apiToken) {
    
    
      clearSession()
      clearLocal()
      next('/login')
    }
  }
  next()
})

router.afterEach(() => {
    
    
  hideNprogress()
})

// 添加动态路由,parent默认为home是首页最外层的路由name名
const addDynamicRoute = (useroute, parent) => {
    
    
  for (let i = 0; i < useroute.length; i++) {
    
    
    if (useroute[i].children && useroute[i].children.length > 0) {
    
    
      router.addRoute({
    
     name: useroute[i].name, path: useroute[i].path, component: modules[`../views/${
      
      useroute[i].name}/index.vue`], meta: {
    
     title: useroute[i].meta.title }, hidden: useroute[i].hidden })
      // 递归添加动态路由
      addDynamicRoute(useroute[i].children, useroute[i].name);
    } else {
    
    
      router.addRoute(parent, {
    
     path: useroute[i].path, component: modules[`../views/${
      
      useroute[i].name}/index.vue`], meta: {
    
     title: useroute[i].meta.title }, hidden: useroute[i].hidden })
    }
  }
};

export default router

Project source address

Guess you like

Origin blog.csdn.net/Web_chicken/article/details/128628781