With the popularity ofVue3
, more and more projects have begun to use Vue3. In order to quickly enter the development state,Pinia(Vuex)
recommend a set ofout-of-the-
Antd
enterprise-level developmentTypeScript
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
- Vscode : A must-have code writing tool for front-end people
- Chrome : A very developer-friendly browser (programmers come standard with browsers)
- 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:
- A development server based on 's native ES module , which provides 's rich built-in functions , such as the blazingly fast module hot update (HMR) .
- A set of build instructions that use Rollup package your code, and it is preconfigured to output highly optimized static assets for production environments.
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:
- Direct modification (not recommended)
commonStore.userInfo = '曹操'
- via $patch
commonStore.$patch({
userInfo:'曹操'
})
- 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,不写则存储所有
},
],
},
})
Fetch
In order to better support TypeScript and count Api requests, here axios is encapsulated twice
Structure directory:
// 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
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)
- 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:
- 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
- SimplifyBasicLayout.tsx Simplified version of multi-level display layout, suitable for routing above 2 levels
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。