VUE3工程搭建

本系列博客跟随B站教程一步步搭建vue3+ts项目,以对vue3全家桶知识做系统性的学习和整理


一、技术栈

  • Vue3
  • Vue-Router
  • Axios
  • Pinia
  • Ant Design Vue
  • TypeScript
  • yarn
  • Vite
  • WebStorm

二、初始化项目

  1. 进入cmd运行yarm命令,开始创建项目
yarn create vite
  1. 随后输入项目名,选择vue,typescript创建完成

image-20230215212920107

  1. 删除初始HelloWorld.vue及相关代码

  2. 创建.npmrc文件,用于手动设置代码仓库

registry="https://registry.npmmirror.com"
sass_binary_site="https://npmmirror.com/mirrors/node-sass"
phantomjs_cdnurl="https://npmmirror.com/mirrors/phantomjs"
electron_mirror="https://npmmirror.com/mirrors/electron"
profiler_binary_host_mirror="https://npmmirror.com/mirrors/node-inspector"
chromedriver_cdnurl="https://npmmirror.com/mirrors/chromedriver"

三、设置@别名

  1. 添加node的types依赖,防止ts代码引入mode函数时爆红
yarn add @types/node
  1. 修改vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import {fileURLToPath, URL} from 'url';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
})
  1. 修改tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": [
"ESNext",
"DOM"
],
"skipLibCheck": true,
"noEmit": true,
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

四、集成vue-router

  1. yarn添加依赖
yarn add vue-router
  1. 新建src/router/index.ts
import {createRouter, createWebHashHistory} from 'vue-router';

const routes = [{
path: '/login',
name: 'login',
meta: {
title: '登录',
},
component: () => {
return import('@/views/sys/login/index.vue');
}
}, {
path: '/',
name: 'home',
meta: {
title: '首页',
},
component: () => {
return import('@/views/sys/home/index.vue');
}
}];
export const router = createRouter({
history: createWebHashHistory('/'),
routes
});

  1. 然后对应文件夹新建两个vue文件,同时修改App.vue
<template>
<router-view/>
</template>
  1. 修改mian.ts,引入router
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import {router} from '@/router';

createApp(App).use(router).mount('#app')

五、集成axios

  1. 添加依赖
yarn add axios
  1. 新建文件src/utils/request.ts
import axios, {
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
} from 'axios';

export interface IMAxios {
request<T = any, D = any>(config: AxiosRequestConfig<D>): Promise<T>;

get<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<T>;

delete<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<T>;

head<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<T>;

options<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<T>;

post<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<T>;

put<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<T>;

patch<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<T>;

postForm<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<T>;

putForm<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<T>;

patchForm<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<T>;

upload<T = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<T>;
}


export class MAxios implements IMAxios {
instance: AxiosInstance;

constructor({baseURL, timeout}: { baseURL?: string, timeout?: number }) {
this.instance = axios.create({
baseURL: baseURL,
timeout: timeout,
});
}

upload<T = any, D = any>(url: string, data?: D | undefined, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
url,
data,
...config
});
}

request<T = any, D = any>(config: AxiosRequestConfig<D>): Promise<T> {
return new Promise<T>((resolve, reject) => {
this.instance.request(config).then((response: AxiosResponse) => {
resolve(response.data);
}).catch(e => {
reject(e);
});
});
}

get<T = any, D = any>(url: string, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'get',
url,
...config
});
}

delete<T = any, D = any>(url: string, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'delete',
url,
...config
});
}

head<T = any, D = any>(url: string, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'head',
url,
...config
});
}

options<T = any, D = any>(url: string, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'options',
url,
...config
});
}

post<T = any, D = any>(url: string, data?: D | undefined, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'post',
url,
data,
...config
});
}

put<T = any, D = any>(url: string, data?: D | undefined, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'put',
url,
data,
...config
});
}

patch<T = any, D = any>(url: string, data?: D | undefined, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'patch',
url,
data,
...config
});
}

postForm<T = any, D = any>(url: string, data?: D | undefined, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencode; charset=UTF-8'
},
url,
data,
...config
});
}

putForm<T = any, D = any>(url: string, data?: D | undefined, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'put',
headers: {
'Content-Type': 'application/x-www-form-urlencode; charset=UTF-8'
},
url,
data,
...config
});
}

patchForm<T = any, D = any>(url: string, data?: D | undefined, config?: AxiosRequestConfig<D> | undefined): Promise<T> {
return this.request({
method: 'patch',
headers: {
'Content-Type': 'application/x-www-form-urlencode; charset=UTF-8'
},
url,
data,
...config
});
}
}

const instance = new MAxios({
baseURL: import.meta.env.VITE_APP_API,
timeout: 15000
});
export default instance;

六、集成pinia

  1. 添加依赖
yarn add pinia
yarn add pinia-plugin-persistedstate
  1. 新增文件src/store/modules/user.ts
import {defineStore} from "pinia";

interface UserState {
name: string
age: number
}

export const useUserStore = defineStore('app-user', {
state: (): UserState => {
return {
name: '张三',
age: 20
}
},
actions: {
setName(name: string) {
this.name = name
},
setAge(age: number) {
this.age = age
}
},
getters: {
getName: (state): string => {
return state.name
},
getAge: (state): number => {
return state.age
}
},
persist: {
storage: localStorage,
key: "app-user"
}
})
  1. 新增文件src/store/index.ts
import {useUserStore} from "@/store/modules/user";

const useStore = () => ({
useUserStore: useUserStore()
})

export default useStore
  1. 修改main.ts
import {createApp} from 'vue'
import './style.css'
import App from './App.vue'
import {router} from '@/router';
import {createPinia} from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

const app = createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')

七、集成Ant Design Vue

  1. 添加依赖
yarn add ant-design-vue
yarn add @ant-design/icons-vue
yarn add -D unplugin-vue-components
  1. 修改vite.config.ts,设置自动引入
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import Components from 'unplugin-vue-components/vite';
import {AntDesignVueResolver} from 'unplugin-vue-components/resolvers';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [AntDesignVueResolver()],
}),
],
resolve: {
alias:
{
'@': path.resolve(__dirname, 'src')
}
},
})

八、集成mock

  1. 添加依赖
yarn add -D mockjs vite-plugin-mock @types/mockjs
  1. 新建/mock/mockUtil.ts
export function resultSuccess<T = any>(result: T, {message = 'success'} = {}) {
return {
code: 200,
result,
message
};
}

export function resultPageSuccess<T = any>(
page: number,
pageSize: number,
list: T[],
{message = 'success'} = {},
) {
const pageData = pagination(page, pageSize, list);

return {
...resultSuccess({
items: pageData,
total: list.length,
}),
message,
};
}

export function resultError(
message = 'Request failed',
{code = 500, result = null} = {},
) {
return {
code,
result,
message,
type: 'error',
};
}

export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]): T[] {
const offset = (pageNo - 1) * Number(pageSize);
return offset + Number(pageSize) >= array.length
? array.slice(offset, array.length)
: array.slice(offset, offset + Number(pageSize));
}

export interface requestParams {
method: string;
body: any;
headers?: { authorization?: string };
query: any;
}

/**
* @description 本函数用于从request数据中获取token,请根据项目的实际情况修改
*
*/
export function getRequestToken({headers}: requestParams): string | undefined {
return headers?.authorization;
}
  1. 新建/mock/mockProdServer.ts
import {createProdMockServer} from 'vite-plugin-mock/es/createProdMockServer';
import testMock from './test/testMock';

const mockModules: any[] = [];
mockModules.push(testMock)

export function setupProdMockServer() {
createProdMockServer(mockModules);
}
  1. 新建/mock/test/testMock.ts
import {MockMethod} from 'vite-plugin-mock';
import {resultSuccess} from '../mockUtil';

const userInfo = {
userName: '张三',
userId: '1',
email: 'test@qq.com'
};

export default [
{
url: '/api/test',
method: 'get',
response: () => {
return resultSuccess(userInfo);
}
}
] as MockMethod[];
  1. 修改vite.config.ts
import {defineConfig} from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import {AntDesignVueResolver} from 'unplugin-vue-components/resolvers';
import {viteMockServe} from 'vite-plugin-mock';
import {fileURLToPath, URL} from 'url';

// https://vitejs.dev/config/
export default ({mode, command}) => {
return defineConfig({
plugins: [
vue(),
Components({
resolvers: [AntDesignVueResolver()],
}),
viteMockServe({
mockPath: 'mock',
localEnabled: command === 'serve',
prodEnabled: false,
injectCode: `
import { setupProdMockServer } from '../mock/mockProdServer';
setupProdMockServer();
`,
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
});
}
  1. 修改tsconfig.json
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"mock/**/*.ts",
"vite.config.ts"
],

九、验证各项配置

目前项目结构如图:

image-20230216224016031

修改src/views/sys/home/index.vue

<template>
<a-button type="primary" @click='testStore'>测试store</a-button>
<br>
<br>
<a-button type="primary" @click='testAxios'>测试axios</a-button>
</template>

<script setup lang="ts">
import useStore from '@/store';
import request from '@/utils/request';

const {useUserStore} = useStore();
const {getName} = useUserStore;

const testStore = () => {
alert(getName);
};
const testAxios = async () => {
const result = await request.get('/api/test');
alert(JSON.stringify(result));
};
</script>

<style scoped>

</style>

运行如图:

image-20230216224203448

点击测试store按钮

image-20230216224236818

点击测试axios按钮

image-20230216224314950

说明各项配置成功,可以开始根据需求填充项目内容

总结

按照B站视频和自己理解,新建了vue3工程模板,可以作为后续新建项目模板使用,但同时存在不足,待以后前端知识储备足够再做优化

  1. mock文件夹下使用import.meta.glob自动导入下属文件夹所有ts,运行时会报警告import_meta.glob is not a function(但是可以正常运行使用),暂时无法解决,改为手动import
  2. 未集成eslint、prettier、husky等各类规范性插件