(一)、准备 vue3 环境,本次复刻环境版本如下:
1、node .js --- 20.18.1
2、 全局安装下 vite
npm install -g vite
3、本次包管理工具都使用 pnpm ,和npm 类似,都是包管理工具 全局安装一下
npm install -g pnpm
(二)、初始化项目
提供了一个初始化的 vue + ts 的创建模板
pnpm create vite my-vue-app --template vue-ts
注意一哈:可能有些 ts 配置文件会报错(缺少这个配置:incremental)
配置文件名字:tsconfig.node.json
ts:
配置完成后保存下,如果还有报错提示,关编辑器重开;
noUncheckedSideEffectImports,如果提示这个报错了,需要升级下 ts, 命令:npm install -D typescript@latest,如果还不行就删除这个配置;
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"incremental": true,
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": false
},
"include": ["vite.config.ts"]
}
(三)、一些管理工具库的安装
1、样式重置插件 normalize.css
pnpm i normalize.css --save
//在 main.ts 文件中 引入
import 'normalize.css/normalize.css'
//验证引入是否成功
打开 f12,查看下样式中是否引入 normalize.css
2、样式框架安装 element-plus
pnpm i element-plus -S
//在 main.ts 文件中 引入
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//验证引入是否成功 在页面中引入一个 element 按钮组件,看看组件是否可以正常显示
<el-button type="primary">Button</el-button>
3、vue-router 路由插件安装
对于vue3 项目,需要指定下 vue-router 路由的版本
(1)、安装路由插件
pnpm i vue-router@4
(2)、在项目 src 目录下新建 路由文件夹 router,在路由文件中新建 index.ts 文件,文件信息如下:
import { createWebHashHistory, createRouter } from 'vue-router'
import HomeView from '../components/HelloWorld.vue'
const routes = [{ path: '/', component: HomeView }]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
(3)、在main.ts 文件下引入路由文件
import router from './router'
(4)、注册插件
app.use(router)
(5)、测试一下路由是否能正常跳转,做一个简单的 demo
1.清理下 App.vue ,清理之后 ,添加一个 RouterView 标签,用于测试一下路由组件是否可以正常展示 App.vue 文件清理之后如下:
<script setup lang="ts">
</script>
<template>
<div>
<RouterView />
</div>
</template>
<style scoped></style>
2.检查下是 是否能正常展示出来 HelloWorld.vue 组件中的内容信息。
4、 pinia 持久状态库安装
(1)、安装插件
pnpm i pinia
(2)、组织 pinia 文件夹目录 在 src 下新建 stroes 文件夹,并新建 index.ts 文件,文件信息如下:
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
(3)、在main.ts 中引入
import { createPinia } from 'pinia'
(4)、在main.ts 中使用插件
app.use(createPinia())
(5)、测试stroes 改造一下 App.vue 文件,改造结果如下,检查界面是否出现了 0 这个数字,如果出现了说明 pinia 引入成功了。
<script setup lang="ts">
import { useCounterStore } from './stores'
const store = useCounterStore()
const { count } = store
</script>
<template>
<div>
<div>{{ count }}</div>
<RouterView />
</div>
</template>
<style scoped></style>
5、axios 请求插件安装
1、安装插件
pnpm install axios -S
(1)封装 axios 请求文件,方便统一使用
src 目录新建 axios 文件夹
1、在axios 目录下创建 config.ts配置文件,配置一些基本的请求参数
export default {
// 基础接口地址(根据环境区分)
baseApi: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5173/api',
// 请求超时时间
timeout: 10000,
// 默认请求头
defaultHeaders: {
'Content-Type': 'application/json'
}
}
(2)在axios文件夹下面创建 axios 封装文件
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios'
import config from './config'
// 定义响应数据结构(后端返回的统一格式)
interface ApiResponse<T = any> {
code: number // 状态码(如 200 成功,500 失败)
message: string // 提示信息
data: T // 响应数据(泛型,动态指定)
success: boolean // 是否成功
}
// 创建 Axios 实例
const service: AxiosInstance = axios.create({
baseURL: config.baseApi,
timeout: config.timeout,
headers: config.defaultHeaders
})
// 请求拦截器
service.interceptors.request.use(
(req: InternalAxiosRequestConfig) => {
if (req.headers) {
// 使用 set 方法设置默认 headers
Object.entries(config.defaultHeaders).forEach(([key, value]) => {
req.headers.set(key, value)
})
}
// 添加 token
const token = localStorage.getItem('token')
if (token && req.headers) {
req.headers.set('Authorization', `Bearer ${token}`)
}
return req
},
(error: AxiosError) => {
return Promise.reject(error)
}
)
// 响应拦截器:统一处理响应、错误
service.interceptors.response.use(
(res: AxiosResponse<ApiResponse>) => {
const responseData = res.data
// 成功:返回响应数据中的 data 字段(简化上层使用)
if (responseData.success || responseData.code === 200) {
return responseData.data
}
// 失败:抛出错误(如业务错误,code 非 200)
return Promise.reject(new Error(responseData.message || '请求失败'))
},
(error: AxiosError<ApiResponse>) => {
// 响应错误处理(网络错误、404、500 等)
const errorMsg = error.response?.data?.message || error.message || '网络错误'
// 示例:根据状态码做特殊处理
if (error.response?.status === 401) {
// Token 过期:跳转登录页
window.location.href = '/login'
}
return Promise.reject(new Error(errorMsg))
}
)
// 定义请求方法类型(支持 GET/POST/PUT/DELETE)
type HttpMethod = 'get' | 'post' | 'put' | 'delete'
interface RequestOptions {
method: HttpMethod
url: string
params?: Record<string, any> // GET/DELETE 参数(拼在 URL 上)
data?: Record<string, any> // POST/PUT 参数(请求体)
config?: AxiosRequestConfig // 额外 Axios 配置(如自定义头)
}
// 封装统一请求函数(泛型 T 为响应数据类型)
const request = async <T = any>(options: RequestOptions): Promise<T> => {
try {
const { method, url, params, data, config } = options // 解构对象参数
const response = await service({
method,
url,
params, // GET/DELETE 用 params
data, // POST/PUT 用 data
...config // 合并额外配置(如自定义头)
})
return response as T
} catch (error) {
// 统一错误提示(可替换为 Element Plus/Naive UI 的 Message 组件)
console.error('请求失败:', (error as Error).message)
throw error // 抛出错误,让上层组件自行处理(可选)
}
}
export default request
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios'
import config from './config'
// 定义响应数据结构(后端返回的统一格式)
interface ApiResponse<T = any> {
code: number // 状态码(如 200 成功,500 失败)
message: string // 提示信息
data: T // 响应数据(泛型,动态指定)
success: boolean // 是否成功
}
// 创建 Axios 实例
const service: AxiosInstance = axios.create({
baseURL: config.baseApi,
timeout: config.timeout,
headers: config.defaultHeaders
})
// 请求拦截器
service.interceptors.request.use(
(req: InternalAxiosRequestConfig) => {
if (req.headers) {
// 使用 set 方法设置默认 headers
Object.entries(config.defaultHeaders).forEach(([key, value]) => {
req.headers.set(key, value)
})
}
// 添加 token
const token = localStorage.getItem('token')
if (token && req.headers) {
req.headers.set('Authorization', `Bearer ${token}`)
}
return req
},
(error: AxiosError) => {
return Promise.reject(error)
}
)
// 响应拦截器:统一处理响应、错误
service.interceptors.response.use(
(res: AxiosResponse<ApiResponse>) => {
const responseData = res.data
// 成功:返回响应数据中的 data 字段(简化上层使用)
if (responseData.success || responseData.code === 200) {
return responseData.data
}
// 失败:抛出错误(如业务错误,code 非 200)
return Promise.reject(new Error(responseData.message || '请求失败'))
},
(error: AxiosError<ApiResponse>) => {
// 响应错误处理(网络错误、404、500 等)
const errorMsg = error.response?.data?.message || error.message || '网络错误'
// 示例:根据状态码做特殊处理
if (error.response?.status === 401) {
// Token 过期:跳转登录页
window.location.href = '/login'
}
return Promise.reject(new Error(errorMsg))
}
)
// 定义请求方法类型(支持 GET/POST/PUT/DELETE)
type HttpMethod = 'get' | 'post' | 'put' | 'delete'
interface RequestOptions {
method: HttpMethod
url: string
params?: Record<string, any> // GET/DELETE 参数(拼在 URL 上)
data?: Record<string, any> // POST/PUT 参数(请求体)
config?: AxiosRequestConfig // 额外 Axios 配置(如自定义头)
}
// 封装统一请求函数(泛型 T 为响应数据类型)
const request = async <T = any>(options: RequestOptions): Promise<T> => {
try {
const { method, url, params, data, config } = options // 解构对象参数
const response = await service({
method,
url,
params, // GET/DELETE 用 params
data, // POST/PUT 用 data
...config // 合并额外配置(如自定义头)
})
return response as T
} catch (error) {
// 统一错误提示(可替换为 Element Plus/Naive UI 的 Message 组件)
console.error('请求失败:', (error as Error).message)
throw error // 抛出错误,让上层组件自行处理(可选)
}
}
export default request
(3)使用示例(方便接下来写接口,以下代码提供一个使用示例)
在 src 文件夹下面创建一个 api 文件夹,用于后续接口统一放置 新建 index.ts 文件
import request from '../axios'
// 定义请求参数类型(分页)
interface userLoginParams {
username: string // 账户名
password: string // 密码
}
// 定义响应类型
interface userInfoResponse {
token: string
userInfo: {
id: number
name: string
avatar: string
role: string
}
}
export const getUserInfo = (data: userLoginParams) => {
return request<userInfoResponse>({
method: 'post',
url: '/auth/login',
data
})
}
6、mock.js 后台模拟接口请求
(1)安装插件
pnpm install --save-dev vite-plugin-mock mockjs
(2) 封装 mockjs 在src 目录下新建 mock 文件夹,并创建 index.ts 文件 (以下为示例文件,mock 接口可按照此方式编写)
import type { MockMethod } from 'vite-plugin-mock' // 导入 Mock 方法类型
import Mock from 'mockjs' // 导入 Mockjs 生成模拟数据
//定义请求体参数类型
interface requestBody {
query: Record<string, any>
body: Record<string, any>
}
// 定义 Mock 接口数组(MockMethod 类型约束)
interface userInfo {
username: string
password: string
userId: string
}
export default [
// 1. 登录接口(POST)
{
url: '/api/auth/login', // 接口地址(需与真实接口路径一致)
method: 'post', // 请求方法
response: (req: requestBody) => {
// body 是前端传递的请求体(如 username、password)
const { username, password } = req.body
// 模拟登录校验(实际开发可根据需求调整)
if (username === 'admin' && password === '123456') {
return {
code: 200,
success: true,
message: '登录成功',
data: {
token: Mock.Random.guid(), // 生成随机 Token
userInfo: {
id: Mock.Random.id(),
name: '管理员',
avatar: Mock.Random.image('100x100', '#888888', '头像'),
role: 'admin'
}
}
}
} else {
return {
code: 400,
success: false,
message: '用户名或密码错误',
data: null
}
}
}
}
] as MockMethod[] // 类型断言,确保接口格式正确
(3) 配置 vite-plugin-mock mockjs
在vite.config.ts 文件中配置一下 mock 插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig({
plugins: [vue(), viteMockServe({ mockPath: './src/mock', logger: true, enable: true })]
})
(4)测试mock 封装的接口能否正常使用
改造一下 App.vue 文件中的代码,分别为引入 api 接口,点击 按钮,查询接口信息并进行展示。 改造后的App.vue 文件如下:
<script setup lang="ts">
import { useCounterStore } from './stores'
import { getUserInfo } from './api'
import { ref } from 'vue'
const store = useCounterStore()
const { count } = store
interface userInfo {
avatar: string
}
const userInfo = ref<userInfo>({
avatar: ''
})
const getGoods = async () => {
const res = await getUserInfo({
username: 'admin',
password: '123456'
})
console.log(res.userInfo)
userInfo.value = res.userInfo
}
</script>
<template>
<div>
<img :src="userInfo.avatar" alt="">
<el-button @click="getGoods()">点击按钮查询接口信息</el-button>
<div>{{ count }}</div>
<RouterView />
</div>
</template>
<style scoped></style>
(四)、 项目搭建初始化结束
到这里,项目的一些基本框架就已经搭建完毕了 vue3 + ts + pinia + vue-router + element-plus + axios + mockjs