通过 vite 搭建一个 Vue3 基础项目
yarn create vite
选择 Vue + TS
配置选好后
- 进入项目目录: cd Evse
- 安装依赖: yarn
- 启动: yarn dev
浏览器打开: http://127.0.0.1:5173/
到此 vite 搭建的 vue-ts 基础项目完成
搭建基础目录结构
在 src 目录下几个文件夹
- apis: 接口请求文件
- directives:自定义指令文件
- layout: 布局文件
- router: 路由文件
- store: 状态管理
- utils: 工具库
- views: 页面组件
vite 配置
默认创建出来的项目, 配置文件 vite.config.ts 如下
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()]
})
配置别名:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
const resolve = (dir: string) => path.join(__dirname, dir)
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve('src'),
'@comps': resolve('src/components'),
'@apis': resolve('src/apis'),
'@views': resolve('src/views'),
'@router': resolve('src/router'),
'@layout': resolve('src/layout'),
}
}
})
这里的别名可根据自己的需求随意配置
此时 TS 可能会报以下这个错误
安装 @types/node 即可 yarn add @types/node
但是在 vue 中的 script ts 中使用别名还是会一下报错
这其实模块已经正常引入了,但是 ts 不认,我们需要在 tsconfig.json 中增加如下配置
配置服务
server: {
// host: '',
// 端口号
port: 8888,
// 设为 true, 若端口被占用则会直接退出, 而不是尝试下一个可用端口
strictPort: false,
// 服务器启动时自动在浏览器中打开应用程序
open: true,
// 自定义代理规则
proxy: {
}
}
集成 Vue Router
yarn add vue-router@4
执行命令安装完成后, 在 src 目录下新建一个 router 目录,里面创建一个 index.ts, 写入以下配置
import type { App } from 'vue'
import { createRouter, createWebHashHistory } from "vue-router";
import { routes } from './routes';
export const router = createRouter({
history: createWebHashHistory(),
routes
})
// config router
export function setupRouter(app: App<Element>) {
app.use(router)
}
同层级新建一个 routes.ts 文件,进行 routes 配置, 用于 router/index.ts 引入使用
const Layout = () => import('@layout/Index.vue')
const Home = () => import('@views/home/Index.vue')
export const routes = [
{
path: '/',
component: Layout,
redirect: '/home',
children: [
{
path: 'home',
name: 'home',
component: Home
}
]
}
]
由于 router 我们采用 hooks 的写法,所以需要改造下 main.ts, 定义一个启动方法 bootstrap 方法,在方法里面进行创建 app 引用, 设置路由, 然后挂载
根组件是 App.vue, 路由匹配的结果会显示在根组件的 标签中,所以在 App.vue 中需要增加该标签;
Home 是 layout 子组件, 子组件也会渲染在父组件的 标签中,所以在 Layout 中也需要增加该标签
-
app.vue
-
layout.vue
页面效果如下

集成 Pinia 状态管理库
安装 yarn add pinia
配置 src/store/index.ts 文件,
import type { App } from 'vue'
import { createPinia } from 'pinia'
const store = createPinia()
export function setupStore(app: App<Element>) {
app.use(store)
}
export { store }
在 main.js 中引入 setupStore 然后进行初始化
声明一个仓库有多种写法,如下图所示
我们采用第三种方式 在store 文件夹中定义一个 app.ts 声明一个 app 仓库,
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useAppStore = defineStore('app', () => {
const count = ref(100)
function getDoubleCount() {
return count.value * 2
}
function setCount(val: number) {
count.value = val
}
return { count, getDoubleCount, setCount }
})
在 Home 组件中使用 pinia
<template>
<h3>这个是Home组件</h3>
<p>count: {{ count }}</p>
<p>doubleCount: {{ appStore.getDoubleCount() }}</p>
<button @click="changCount">修改 count</button>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useAppStore } from '@store/app'
const appStore = useAppStore()
const { count } = storeToRefs(appStore)
function changCount() {
appStore.setCount(count.value + 2)
}
</script>
集成 Ant design UI 框架
安装 ant-design-vue
main.ts 中进行全局引入
Home 组件直接使用组件
效果如下
集成 Axios
安装 yarn add axios
封装 Axios
需求:想要通过如下方式进行调用接口
-
单独一个文件维护接口地址, 内部通过不同模块名进行划分, 页面通过 $api.moduleName.url 进行引入对应的接口
-
所有接口方法都可以通过 $http.methods名 进行调用,常用的 $get 和 $post 可通过 $get, $post 直接调用
-
可直接通过 { data, err } 进行接口数据的接受,data, err 分别代表成功与失败的信息
const { data, err } = await $get($api.moduleName.url)
const { data, err } = await $post($api.moduleName.url, [params, config])
const { data, err } = await $http.get($api.moduleName.url)
const { data, err } = await $http.post($api.moduleName.url, [params, config])
const { data, err } = await $http.delete($api.moduleName.url, [params, config])
const { data, err } = await $http.put($api.moduleName.url, [params, config])
接口单独维护
api/api.ts
// api 集中式管理
export default {
sysLogin: '/xxx/xxx/sysLogin3', // 登陆接口
station: {
list: '/xxx/xxx/getList',
add: 'xxx',
del: 'xxx',
update: 'xxx'
},
evse: {
list: 'xxx',
add: 'xxx',
del: 'xxx',
update: 'xxx'
}
}
Axios 全局配置
api/axios.ts
// 此处封装主要是 实现请求拦截,实现响应拦截,常见错误信息处理,请求头设置
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from "axios"
import config from '../../config'
import { handleRequestHeader, handleAuth } from './requestHandle' // 请求拦截回调
import { handleNetworkError } from './responseHandle' // 响应拦截回调
import { message } from 'ant-design-vue'
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
const MODE = import.meta.env.MODE
// axios 实例全局配置
export const service: AxiosInstance = axios.create({
baseURL: config[MODE].baseUrl,
withCredentials: true,
timeout: 15000,
headers: {
}
})
// 请求拦截器
service.interceptors.request.use((config: AxiosRequestConfig) => {
//这里会最先拿到你的请求配置
config = handleRequestHeader(config)
config = handleAuth(config)
return config
}, (error: AxiosError) => {
// 这里极少情况会进来,暂时没有找到主动触发的方法,估计只有浏览器不兼容时才会触发,欢迎后面同学补充
// 看了几个GitHub的issue,有人甚至提出了这个方法是不必要的(因为没有触发的场景),不过还是建议大家按照官方的写法,避免不必要的错误
// 进来之后没法发起请求
return Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use((response: AxiosResponse) => {
// 这里会最先拿到你的response
// 只有返回的 http 状态码是2xx,都会进来这里
const res = response.data
if (res.code !== 200) { // 业务状态码判断
handleNetworkError(res.code)
return null
}
return response.data
}, (error: AxiosError) => {
// 目前发现三种情况会进入这里:
// 1. http状态码非2开头的都会进来这里,如404,500等
// 2. 取消请求也会进入这里,CancelToken,可以用axios.isCancel(err)来判断是取消的请求
// 3. 请求运行有异常也会进入这里,如故意将headers写错:axios.defaults.headers = '123',或者在request中有语法或解析错误也会进入这里
// 进入这里意味着请求失败,axios会进入catch分支
message.error(error.message)
return Promise.reject(error)
})
便捷调用方式
api/http.ts
import { service as axios } from './axios'
import qs from "qs"
function get(url: string, config?: object) {
return new Promise((resolve, reject) => {
axios
.get(url, config)
.then(res => {
resolve({ data: res.data, err: null })
})
.catch(err => {
resolve({data: null, err})
})
})
}
function post(url: string, params?: object, config?: object) {
return new Promise((resolve, reject) => {
axios
.post(url, qs.stringify(params), config)
.then(res => {
resolve({ data: res.data, err: null })
})
.catch(err => {
resolve({data: null, err})
})
})
}
export const http = {
get,
post
}
全局注册
// main.ts
//引入
import { registerGlobVariable } from '@/utils/registerGlobVariable'
// bootstrap() 中注册
// 注册全局变量
registerGlobVariable(app)
// 注册全局组件
registerGlobComp(app)
// utils/registerGlobVariable.ts
import type { App } from 'vue'
import { http } from '@/api/http'
import api from '@/api/api'
export function registerGlobVariable(app: App) {
app.config.globalProperties.$api = api
app.config.globalProperties.$http = http
app.config.globalProperties.$get = http.get
app.config.globalProperties.$post = http.post
}
页面使用
// home.vue
import { getCurrentInstance } from 'vue'
// axios 测试
const { proxy, proxy: { $http, $get, $post, $api } } = getCurrentInstance() as any
async function axiosTest() {
const { data, err } = await $get($api.sysLogin)
console.log(data, err)
}
其他
axios.ts 文件中将请求拦截回调和响应拦截回调提出到了一个单独的文件, 还有 config.ts 用于区分不同的打包环境
// requestHandle.ts
// 通过判断是否存在 token 判断用户登陆情况
// 即使 token 存在, 也有可能 token 过期,所需需要在每次的请求头中携带 token, 让后端根据 token 判断用户登陆情况,并返回我们对应的状态码
export const handleRequestHeader = (config: any) => {
config['xxxx'] = 'xxx'
return config
}
export const handleAuth = (config: any) => {
config.header['token'] = localStorage.getItem('token') || ''
return config
}
// responseHandle.ts
import { message } from 'ant-design-vue'
export const handleNetworkError = (errStatus?: any) => {
let errMessage = '未知错误'
if (errStatus) {
switch (errStatus) {
case 400:
errMessage = '错误的请求'
break
case 401:
errMessage = '未授权,请重新登录'
break
case 403:
errMessage = '拒绝访问'
break
case 404:
errMessage = '请求错误,未找到该资源'
break
case 405:
errMessage = '请求方法未允许'
break
case 408:
errMessage = '请求超时'
break
case 500:
errMessage = '服务器端出错'
break
case 501:
errMessage = '网络未实现'
break
case 502:
errMessage = '网络错误'
break
case 503:
errMessage = '服务不可用'
break
case 504:
errMessage = '网络超时'
break
case 505:
errMessage = 'http版本不支持该请求'
break
default:
errMessage = `其他连接错误 --${errStatus}`
}
} else {
errMessage = '无法连接到服务器!'
}
message.error(errMessage)
}