With the popularity of Vue3, more and more projects have begun to use Vue3. In order to quickly enter the development state, I recommend a set of enterprise level development scaffolding out of the box. The framework uses: Vue3 + Vite2 + TypeScript + JSX + Pinia(Vuex) + Antd. If you don't talk much nonsense, just start rolling.
The scaffold is divided into two versions, Vuex version and Pinia version, according to the use status library. The following is the relevant code address:
Vuex version,
Pinia version
Preparation for construction
- Vscode : code writing artifact necessary for front-end users
- Chrome : developer friendly browser (standard browser for programmers)
- Nodejs & npm : configure the local development environment. After installing Node, you will find that npm will be installed together (V12 +)
When using npm to install dependency packages, you will find that it is very slow. It is recommended to use cnpm and yarn instead.
Scaffold directory structure
├── src │ ├── App.tsx │ ├── api # Interface management module │ ├── assets # Static resource module │ ├── components # Common component module │ ├── mock # mock interface simulation module │ ├── layouts # Public custom layout │ ├── main.ts # Entry file │ ├── public # Common resource module │ ├── router # route │ ├── store # vuex State Library │ ├── types # Declaration file │ ├── utils # Common method module │ └── views # View module ├── tsconfig.json └── vite.config.js
What is Vite
Next generation front-end development and construction tools
Vite (French means "fast", pronounced / vit /, pronounced the same as "veet") is a new front-end construction tool, which can significantly improve the front-end development experience. It mainly consists of two parts:
- A development server based on Native ES module Provided Rich built-in functions If the speed is amazing Module hot renewal (HMR).
- Use it to build a set of instructions Rollup Package your code, and it is pre configured to output highly optimized static resources for production environments.
Vite is intended to provide out of the box configuration and its Plug in API And JavaScript API It brings a high degree of scalability and complete type support.
You can Why Vite Learn more about the original design intention of the project.
What is Pinia
Pinia.js is a new generation of state manager, which is developed by Vue It is developed by members of the JS team, so it is also considered to be the next generation Vuex, namely Vuex5 x. In vue3 0 is also highly respected for its use in projects
Pinia.js has the following characteristics:
- More complete typescript support than Vuex;
- It is light enough, and the volume after compression is only 1.6kb;
- Remove mutations and only state, getters and actions (support synchronization and asynchrony);
- Compared with Vuex, it is more convenient to use. Each module is independent and has better code segmentation. There is no module nesting, and store s can be used freely
install
npm install pinia --save
Create Store
Create a new src/store directory and create an index Ts and export the store
import { createPinia } from 'pinia' const store = createPinia() export default store
- In main Introduction into TS
import { createApp } from 'vue' import store from './store' const app = createApp(App) app.use(store)
Define State
Create src/store/modules, and add common under modules according to module division ts
import { defineStore } from 'pinia' export const CommonStore = defineStore('common', { // State Library state: () => ({ userInfo: null, //User information }), })
Get State
There are many ways to obtain state, the most commonly used are as follows:
import { CommonStore } from '@/store/modules/common' // Omit defineComponent here setup(){ const commonStore = CommonStore() return ()=>( <div>{commonStore.userInfo}</div> ) }
Get using computed
const userInfo = computed(() => common.userInfo)
Use storeToRefs provided by Pinia
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 = 'Cao Cao'
- Via $patch
commonStore.$patch({ userInfo:'Cao Cao' })
- Modify the store through actions
export const CommonStore = defineStore('common', { // State Library state: () => ({ userInfo: null, //User information }), actions: { setUserInfo(data) { this.userInfo = data }, }, })
import { CommonStore } from '@/store/modules/common' const commonStore = CommonStore() commonStore.setUserInfo('Cao Cao')
Getters
export const CommonStore = defineStore('common', { // State Library state: () => ({ userInfo: null, //User information }), getters: { getUserInfo: (state) => state.userInfo } })
Get using the same State
Actions
Pinia endows Actions with greater functions. Compared with Vuex, Pinia removes Mutations and only relies on Actions to change the Store state. Both synchronous and asynchronous can be placed in Actions.
Synchronous action
export const CommonStore = defineStore('common', { // State Library state: () => ({ userInfo: null, //User information }), actions: { setUserInfo(data) { this.userInfo = data }, }, })
Asynchronous actions
... actions: { async getUserInfo(params) { const data = await api.getUser(params) return data }, }
Internal actions call each other
... 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 }, }
Pinia plugin persist plug-in for 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
Use in corresponding store
export const CommonStore = defineStore('common', { // State Library state: () => ({ userInfo: null, //User information }), // Enable data cache persist: { enabled: true, strategies: [ { storage: localStorage, // The default is stored in sessionStorage paths: ['userInfo'], // Specify the storage state. If it is not written, all data will be stored }, ], }, })
Fetch
In order to better support TypeScript and count Api requests, axios is encapsulated twice here
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 environment variable const BaseUrl = import.meta.env.VITE_API_BASE_URL as string // create an axios instance const service: AxiosInstance = axios.create({ baseURL: BaseUrl, // Formal environment timeout: 60 * 1000, headers: {}, }) /** * Request interception */ service.interceptors.request.use( (config: AxiosRequestConfig) => { config.headers.common.Authorization = getToken() // Request token on header config.headers.common.token = getToken() return config }, (error) => Promise.reject(error), ) /** * Response interception */ 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 error', content: 'token Invalid, please login again!', onOk: () => { sessionStorage.clear() }, }) } else if (code == 200) { if (status) { // Interface request succeeded msg && Message.success(msg) // If msg is returned in the background, msg will be prompted return Promise.resolve(response) // Return success data } // Interface exception msg && Message.warning(msg) // If msg is returned in the background, msg will be prompted return Promise.reject(response) // Return exception data } else { // Interface exception msg && Message.error(msg) return Promise.reject(response) } } return response }, (error) => { if (error.response.status) { switch (error.response.status) { case 500: Notification.error({ message: 'reminder', description: 'Service exception, please restart the server!', }) break case 401: Notification.error({ message: 'reminder', description: 'Service exception, please restart the server!', }) break case 403: Notification.error({ message: 'reminder', description: 'Service exception, please restart the server!', }) break // 404 request does not exist case 404: Notification.error({ message: 'reminder', description: 'Service exception, please restart the server!', }) break default: Notification.error({ message: 'reminder', description: 'Service exception, please restart the server!', }) } } return Promise.reject(error.response) }, ) interface Http { fetch<T>(params: AxiosRequestConfig): Promise<StoreState.ResType<T>> } const http: Http = { // The usage is consistent with axios (including all the built-in request modes of 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 = { // Sign in login: (params) => { return fetch({ method: 'post', url: '/login', data: params, }) } } export default UserApi
type definition
/** * Interface return result Types * -------------------------------------------------------------------------- */ // Login return results export interface ILoginData { token: string userInfo: { address: string username: string } } /** * Interface parameter Types * -------------------------------------------------------------------------- */ // Login parameters export interface ILoginApiParams { username: string // user name password: string // password captcha: string // Verification Code uuid: string // Verification code uuid } /** * Interface definition 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: not shown, false: shown)
- Dynamic routing (permission routing)
// src/router/router.ts router.beforeEach( async ( to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext, ) => { const token: string = getToken() as string if (token) { // The routing list is loaded for the first time, and the project needs dynamic routing if (!isAddDynamicMenuRoutes) { try { //Get dynamic routing table const res: any = await UserApi.getPermissionsList({}) if (res.code == 200) { isAddDynamicMenuRoutes = true const menu = res.data // Generate standard format route through route table const menuRoutes: any = fnAddDynamicMenuRoutes( menu.menuList || [], [], ) mainRoutes.children = [] mainRoutes.children?.unshift(...menuRoutes, ...Routes) // Dynamically add routes router.addRoute(mainRoutes) // Note: this step is very important, otherwise the route cannot be obtained by navigation router.options.routes.unshift(mainRoutes) // Local storage button permission collection 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} Failed to request menu list and permission, jump to login page!!`, '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 layout components
The scaffold provides a variety of typesetting layout, and the directory structure is as follows:
- BlankLayout.tsx: blank layout, only routing distribution
- RouteLayout.tsx: main layout, content display part, including bread crumbs
- LevelBasicLayout.tsx multi-level display layout, applicable to routes above level 2
- SimplifyBasicLayout.tsx simplified multi-level display layout is applicable to routes above level 2
Related reference links
last
The article will be written here for the time being, and the JSX syntax will be added later. If this article is of any help to you, don't forget to move your finger and click like ❤️.
If there are mistakes and shortcomings in this article, you are welcome to point out in the comment area and put forward your valuable opinions!
Finally, share this scaffold address: github address,
gitee address