11
头图
With the popularity of Vue3 , more and more projects have begun to use Vue3. In order to quickly enter the development state, Pinia(Vuex) recommend a set of out-of-the- Antd enterprise-level development TypeScript Vite2 Vue3 JSX Not much nonsense, just get started.
The scaffolding is divided into two versions: Vuex version and Pinia version according to the different state libraries used. The following is the relevant code address:
Vuex version
Pinia version

Prepare to build

  1. Vscode : A must-have code writing tool for front-end people
  2. Chrome : A very developer-friendly browser (programmers come standard with browsers)
  3. Nodejs & npm : Configure the local development environment. After installing Node, you will find that npm will also be installed together (V12+)
When using npm to install dependent packages, it will be very slow. It is recommended to use cnpm and yarn instead.

Scaffolding Directory Structure

├── src
│   ├── App.tsx
│   ├── api                     # 接口管理模块
│   ├── assets                  # 静态资源模块
│   ├── components              # 公共组件模块
│   ├── mock                    # mock接口模拟模块
│   ├── layouts                 # 公共自定义布局
│   ├── main.ts                 # 入口文件
│   ├── public                  # 公共资源模块
│   ├── router                  # 路由
│   ├── store                   # vuex状态库
│   ├── types                   # 声明文件
│   ├── utils                   # 公共方法模块
│   └── views                   # 视图模块
├── tsconfig.json
└── vite.config.js

What is Vite

Next-generation front-end development and build tools
Vite (French for "fast", pronounced /vit/ , pronounced the same as "veet") is a new front-end building tool that significantly improves the front-end development experience. It mainly consists of two parts:

Vite is intended to provide out-of-the-box configuration, while its plugin API and JavaScript API bring a high degree of extensibility with full type support.

You can learn more about the original design of the project in choose Vite .

What is Pinia

Pinia.js is a new generation of state manager, developed by members of the Vue.js team, so it is also considered to be the next generation of Vuex, namely Vuex5.x, and is also highly respected for use in Vue3.0 projects

Pinia.js has the following features:

  • More complete typescript support than Vuex;
  • Light enough, the compressed volume is only 1.6kb;
  • Remove mutations, only state, getters, actions (support synchronous and asynchronous);
  • Compared with Vuex, it is more convenient to use, each module is independent, better code segmentation, no module nesting, and can be used freely between stores

Install

npm install pinia --save

Create Store

  • Create a new src/store directory and create index.ts under it, and export the store

    import { createPinia } from 'pinia'
    
    const store = createPinia()
    
    export default store
  • Introduced in main.ts
import { createApp } from 'vue'
import store from './store'

const app = createApp(App)

app.use(store)

Define State

In the new src/store/modules, add common.ts under modules according to the module division

import { defineStore } from 'pinia'

export const CommonStore = defineStore('common', {
  // 状态库
  state: () => ({
    userInfo: null, //用户信息
  }),
})

getState

There are many ways to get state, the most commonly used are the following:

import { CommonStore } from '@/store/modules/common'
// 在此省略defineComponent
setup(){
    const commonStore = CommonStore()
    return ()=>(
        <div>{commonStore.userInfo}</div>
    )
}

Use computed to get

const userInfo = computed(() => common.userInfo)

Use 16218625feab20 provided by

import { storeToRefs } from 'pinia'
import { CommonStore } from '@/store/modules/common'

...
const commonStore = CommonStore()
const { userInfo } = storeToRefs(commonStore)

Modify State

There are three ways to modify state:

  1. Direct modification (not recommended)
commonStore.userInfo = '曹操'
  1. via $patch
commonStore.$patch({
    userInfo:'曹操'
})
  1. Modify the store through actions
export const CommonStore = defineStore('common', {
  // 状态库
  state: () => ({
    userInfo: null, //用户信息
  }),
  actions: {
    setUserInfo(data) {
      this.userInfo = data
    },
  },
})
import { CommonStore } from '@/store/modules/common'

const commonStore = CommonStore()
commonStore.setUserInfo('曹操')

Getters

export const CommonStore = defineStore('common', {
  // 状态库
  state: () => ({
    userInfo: null, //用户信息
  }),
  getters: {
    getUserInfo: (state) => state.userInfo
  }
})

Use the same State to get

Actions

Pinia gives Actions a greater function. Compared with Vuex, Pinia removes Mutations and only relies on Actions to change the Store state. Both synchronization and asynchronous can be placed in Actions.

synchronous action

export const CommonStore = defineStore('common', {
  // 状态库
  state: () => ({
    userInfo: null, //用户信息
  }),
  actions: {
    setUserInfo(data) {
      this.userInfo = data
    },
  },
})

asynchronous actions

...
actions: {
   async getUserInfo(params) {
      const data = await api.getUser(params)
      return data
    },
}

Call each other between internal actions

...
actions: {
   async getUserInfo(params) {
      const data = await api.getUser(params)
      this.setUserInfo(data)
      return data
    },
    setUserInfo(data){
       this.userInfo = data
    }
}

Actions between modules call each other

import { UserStore } from './modules/user'

...
actions: {
   async getUserInfo(params) {
      const data = await api.getUser(params)
      const userStore = UserStore()
      userStore.setUserInfo(data)
      return data
    },
}

The pinia-plugin-persist plugin implements data persistence

Install

npm i pinia-plugin-persist --save

use

// src/store/index.ts

import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'

const store = createPinia().use(piniaPluginPersist)

export default store

Corresponding to the use in the store

export const CommonStore = defineStore('common', {
  // 状态库
  state: () => ({
    userInfo: null, //用户信息
  }),
  // 开启数据缓存
  persist: {
    enabled: true,
    strategies: [
      {
        storage: localStorage, // 默认存储在sessionStorage里
        paths: ['userInfo'],  // 指定存储state,不写则存储所有
      },
    ],
  },
})

WX20220224-151530.png

Fetch

In order to better support TypeScript and count Api requests, here axios is encapsulated twice

Structure directory:

WX20220224-155540@2x.png

// src/utils/fetch.ts

import axios, { AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'
import { getToken } from './util'
import { Modal } from 'ant-design-vue'
import { Message, Notification } from '@/utils/resetMessage'

// .env环境变量
const BaseUrl = import.meta.env.VITE_API_BASE_URL as string

// create an axios instance
const service: AxiosInstance = axios.create({
  baseURL: BaseUrl, // 正式环境
  timeout: 60 * 1000,
  headers: {},
})

/**
 * 请求拦截
 */
service.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    config.headers.common.Authorization = getToken() // 请求头带上token
    config.headers.common.token = getToken()
    return config
  },
  (error) => Promise.reject(error),
)

/**
 * 响应拦截
 */
service.interceptors.response.use(
  (response: AxiosResponse) => {
    if (response.status == 201 || response.status == 200) {
      const { code, status, msg } = response.data
      if (code == 401) {
        Modal.warning({
          title: 'token出错',
          content: 'token失效,请重新登录!',
          onOk: () => {
            sessionStorage.clear()
          },
        })
      } else if (code == 200) {
        if (status) {
          // 接口请求成功
          msg && Message.success(msg) // 后台如果返回了msg,则将msg提示出来
          return Promise.resolve(response) // 返回成功数据
        }
        // 接口异常
        msg && Message.warning(msg) // 后台如果返回了msg,则将msg提示出来
        return Promise.reject(response) // 返回异常数据
      } else {
        // 接口异常
        msg && Message.error(msg)
        return Promise.reject(response)
      }
    }
    return response
  },
  (error) => {
    if (error.response.status) {
      switch (error.response.status) {
        case 500:
          Notification.error({
            message: '温馨提示',
            description: '服务异常,请重启服务器!',
          })
          break
        case 401:
          Notification.error({
            message: '温馨提示',
            description: '服务异常,请重启服务器!',
          })
          break
        case 403:
          Notification.error({
            message: '温馨提示',
            description: '服务异常,请重启服务器!',
          })
          break
        // 404请求不存在
        case 404:
          Notification.error({
            message: '温馨提示',
            description: '服务异常,请重启服务器!',
          })
          break
        default:
          Notification.error({
            message: '温馨提示',
            description: '服务异常,请重启服务器!',
          })
      }
    }
    return Promise.reject(error.response)
  },
)

interface Http {
  fetch<T>(params: AxiosRequestConfig): Promise<StoreState.ResType<T>>
}

const http: Http = {
  // 用法与axios一致(包含axios内置所有请求方式)
  fetch(params) {
    return new Promise((resolve, reject) => {
      service(params)
        .then((res) => {
          resolve(res.data)
        })
        .catch((err) => {
          reject(err.data)
        })
    })
  },
}

export default http['fetch']

use

// src/api/user.ts

import qs from 'qs'
import fetch from '@/utils/fetch'
import { IUserApi } from './types/user'

const UserApi: IUserApi = {
  // 登录
  login: (params) => {
    return fetch({
      method: 'post',
      url: '/login',
      data: params,
    })
  }
}

export default UserApi

type definition

/**
 * 接口返回结果Types
 * --------------------------------------------------------------------------
 */
// 登录返回结果
export interface ILoginData {
  token: string
  userInfo: {
    address: string
    username: string
  }
}

/**
 * 接口参数Types
 * --------------------------------------------------------------------------
 */
// 登录参数
export interface ILoginApiParams {
  username: string // 用户名
  password: string // 密码
  captcha: string // 验证码
  uuid: string // 验证码uuid
}

/**
 * 接口定义Types
 * --------------------------------------------------------------------------
 */
export interface IUserApi {
  login: (params: ILoginApiParams) => Promise<StoreState.ResType<ILoginData>>
}

Router4

  1. Basic routing

    // src/router/router.config.ts
    
    const Routes: Array<RouteRecordRaw> = [
      {
     path: '/403',
     name: '403',
     component: () =>
       import(/* webpackChunkName: "403" */ '@/views/exception/403'),
     meta: { title: '403', permission: ['exception'], hidden: true },
      },
      {
     path: '/404',
     name: '404',
     component: () =>
       import(/* webpackChunkName: "404" */ '@/views/exception/404'),
     meta: { title: '404', permission: ['exception'], hidden: true },
      },
      {
     path: '/500',
     name: '500',
     component: () =>
       import(/* webpackChunkName: "500" */ '@/views/exception/500'),
     meta: { title: '500', permission: ['exception'], hidden: true },
      },
      {
     path: '/:pathMatch(.*)',
     name: 'error',
     component: () =>
       import(/* webpackChunkName: "404" */ '@/views/exception/404'),
     meta: { title: '404', hidden: true },
      },
    ]
    title: navigation display text; hidden: whether to hide the route on the navigation (true: do not display false: display)
  2. Dynamic routing (authority routing)
// src/router/router.ts

router.beforeEach(
  async (
    to: RouteLocationNormalized,
    from: RouteLocationNormalized,
    next: NavigationGuardNext,
  ) => {
    const token: string = getToken() as string
    if (token) {
      // 第一次加载路由列表并且该项目需要动态路由
      if (!isAddDynamicMenuRoutes) {
        try {
          //获取动态路由表
          const res: any = await UserApi.getPermissionsList({})
          if (res.code == 200) {
            isAddDynamicMenuRoutes = true
            const menu = res.data
            // 通过路由表生成标准格式路由
            const menuRoutes: any = fnAddDynamicMenuRoutes(
              menu.menuList || [],
              [],
            )
            mainRoutes.children = []
            mainRoutes.children?.unshift(...menuRoutes, ...Routes)
            // 动态添加路由
            router.addRoute(mainRoutes)
            // 注:这步很关键,不然导航获取不到路由
            router.options.routes.unshift(mainRoutes)
            // 本地存储按钮权限集合
            sessionStorage.setItem(
              'permissions',
              JSON.stringify(menu.permissions || '[]'),
            )
            if (to.path == '/' || to.path == '/login') {
              const firstName = menuRoutes.length && menuRoutes[0].name
              next({ name: firstName, replace: true })
            } else {
              next({ path: to.fullPath })
            }
          } else {
            sessionStorage.setItem('menuList', '[]')
            sessionStorage.setItem('permissions', '[]')
            next()
          }
        } catch (error) {
          console.log(
            `%c${error} 请求菜单列表和权限失败,跳转至登录页!!`,
            'color:orange',
          )
        }
      } else {
        if (to.path == '/' || to.path == '/login') {
          next(from)
        } else {
          next()
        }
      }
    } else {
      isAddDynamicMenuRoutes = false
      if (to.name != 'login') {
        next({ name: 'login' })
      }
      next()
    }
  },
)

LayoutsLayout components

Scaffolding provides a variety of typesetting layouts, and the directory structure is as follows:

layout.png

  • BlankLayout.tsx: Blank layout, only for routing distribution
  • RouteLayout.tsx: main layout, content display section, including breadcrumbs
  • LevelBasicLayout.tsx Multi-level display layout, suitable for routing above level 2
    LevelBasicLayout.png
  • SimplifyBasicLayout.tsx Simplified version of multi-level display layout, suitable for routing above 2 levels

SimplifyBasicLayout.png

Related Reference Links

finally

The article is written here for the time being, and the JSX grammar section will be added later. If this article is helpful to you, don’t forget to give it a thumbs up ❤️.
If there are any mistakes or deficiencies in this article, you are welcome to point them out in the comment area and give your valuable opinions!

Finally share this scaffolding address: github address ,
gitee address


阳晨@
47 声望9 粉丝