随着Vue3的遍及,曾经有越来越多的我的项目开始应用Vue3。为了疾速进入开发状态,在这里向大家举荐一套开箱即用的企业级开发脚手架,框架应用:Vue3 + Vite2 + TypeScript + JSX + Pinia(Vuex) + Antd。废话不多话,间接上手开撸。
该脚手架依据应用状态库的不同分为两个版本Vuex版、Pinia版,上面是相干代码地址:
Vuex版、
Pinia版

搭建需筹备

  1. Vscode : 前端人必备写码神器
  2. Chrome :对开发者十分敌对的浏览器(程序员标配浏览器)
  3. Nodejs & npm :配置本地开发环境,装置 Node 后你会发现 npm 也会一起装置下来 (V12+)
应用npm装置依赖包时会发现十分慢,在这里举荐应用cnpm、yarn代替。

脚手架目录构造

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

什么是Vite

下一代前端开发与构建工具
Vite(法语意为 "疾速的",发音 /vit/,发音同 "veet")是一种新型前端构建工具,可能显著晋升前端开发体验。它次要由两局部组成:
  • 一个开发服务器,它基于 原生 ES 模块 提供了 丰盛的内建性能,如速度快到惊人的 模块热更新(HMR)。
  • 一套构建指令,它应用 Rollup 打包你的代码,并且它是预配置的,可输入用于生产环境的高度优化过的动态资源。

Vite 意在提供开箱即用的配置,同时它的 插件 API 和 JavaScript API 带来了高度的可扩展性,并有残缺的类型反对。

你能够在 为什么选 Vite 中理解更多对于我的项目的设计初衷。

什么是Pinia

Pinia.js 是新一代的状态管理器,由 Vue.js团队中成员所开发的,因而也被认为是下一代的 Vuex,即 Vuex5.x,在 Vue3.0 的我的项目中应用也是备受推崇

Pinia.js 有如下特点:

  • 相比Vuex更加残缺的 typescript 的反对;
  • 足够轻量,压缩后的体积只有1.6kb;
  • 去除 mutations,只有 state,getters,actions(反对同步和异步);
  • 应用相比Vuex更加不便,每个模块独立,更好的代码宰割,没有模块嵌套,store之间能够自在应用

装置

npm install pinia --save

创立Store

  • 新建 src/store 目录并在其上面创立 index.ts,并导出store

    import { createPinia } from 'pinia'const store = createPinia()export default store
  • 在main.ts中引入
import { createApp } from 'vue'import store from './store'const app = createApp(App)app.use(store)

定义State

在新建src/store/modules,依据模块划分在modules下新增common.ts

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

获取State

获取state有多种形式,最罕用一下几种:

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

应用computed获取

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

应用Pinia提供的storeToRefs

import { storeToRefs } from 'pinia'import { CommonStore } from '@/store/modules/common'...const commonStore = CommonStore()const { userInfo } = storeToRefs(commonStore)

批改State

批改state的三种形式:

  1. 间接批改(不举荐)
commonStore.userInfo = '曹操'
  1. 通过$patch
commonStore.$patch({    userInfo:'曹操'})
  1. 通过actions批改store
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  }})

应用同State获取

Actions

Pinia赋予了Actions更大的职能,相较于Vuex,Pinia去除了Mutations,仅依附Actions来更改Store状态,同步异步都能够放在Actions中。

同步action

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

异步actions

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

外部actions间互相调用

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

modules间actions互相调用

import { UserStore } from './modules/user'...actions: {   async getUserInfo(params) {      const data = await api.getUser(params)      const userStore = UserStore()      userStore.setUserInfo(data)      return data    },}

pinia-plugin-persist 插件实现数据长久化

装置

npm i pinia-plugin-persist --save

应用

// src/store/index.tsimport { createPinia } from 'pinia'import piniaPluginPersist from 'pinia-plugin-persist'const store = createPinia().use(piniaPluginPersist)export default store

对应store中的应用

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

Fetch

为了更好的反对TypeScript,统计Api申请,这里将axios进行二次封装

构造目录:

// src/utils/fetch.tsimport 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 instanceconst 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']

应用

// src/api/user.tsimport 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

类型定义

/** * 接口返回后果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. 根底路由

    // src/router/router.config.tsconst 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: 导航显示文字;hidden: 导航上是否暗藏该路由 (true: 不显示 false:显示)
  2. 动静路由(权限路由)
// src/router/router.tsrouter.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()    }  },)

Layouts布局组件

脚手架提供多种排版布局,目录构造如下:

  • BlankLayout.tsx: 空白布局,只做路由散发
  • RouteLayout.tsx: 主体布局,内容显示局部,蕴含面包屑
  • LevelBasicLayout.tsx 多级展现布局,实用于2级以上路由
  • SimplifyBasicLayout.tsx 简化版多级展现布局,实用于2级以上路由

相干参考链接

  • Pinia官网
  • Vue3官网
  • Vite
  • Antd Design Vue

最初

文章临时就写到这,后续会减少JSX语法局部,如果本文对您有什么帮忙,别忘了动动手指导个赞❤️。
本文如果有谬误和不足之处,欢送大家在评论区指出,多多提出您贵重的意见!

最初分享本脚手架地址:github地址、
gitee地址