一、基础说明
- 使用技术:
Vite3+Vue3+TypeScript+Ant Design+axios+Vue-Router
1.2 参考文档
- Vue3
- Vue Router (Vue.js 的官方路由)
- Ant Design (组件框架)
- Vite (前端构建工具)
- Pinia (Vue 的存储库)
- Axios (基于 promise 网络请求库)
- TypeScript
二、 项目结构
practice-vue-project
.
├── index.html # 页面入口
├── public # 该文件下的目录打包时将直接拷贝只根目录
├── src # 源码文件
│ ├── assets # 静态资源
│ │ ├── base.css # 全局的css变量
│ ├── constants # 全局常量定义
│ ├── components # 全局组件
│ ├── hooks # 封装的常用hooks
│ ├── layouts # 公共布局
│ ├── pages # 页面组件(下面只列举主要文件)
│ ├── router # 路由配置文件
│ ├── service # 接口请求相关
│ │ ├── index.ts # 整合接口请求,统一导出
│ ├── typings # 全局类型声明
│ ├── App.vue
│ ├── main.ts # 创建APP挂载
│ └── utils # 工具函数
│ └── request.ts # 基于axios封装的请求类
├── .env.dev # 开发环境配置文件
├── .env.production # 生产环境配置文件
├── .gitignore # git跟踪忽略配置
├── env.d.ts # 环境变量类型定义
├── index.html # 模板:包含网页标题图标
├── tsconfig.json # TS配置文件
└── vite.config.js # vite 配置文件
三、使用脚手架构建
- 使用脚手架创建新项目:
npm create vue@latest - 根据需要选择需要的功能
- 安装依赖:
cd <your-project-name> npm install - 启动项目:
npm run dev
四、配置vite.config.js
自动解析项目根目录下 vite.config.js 的配置文件
只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码
获取环境变量的方法:import.meta.env.VITE_xxx
需要在 package.json 中修改运行的命令,保证 vite 获取环境变量
-
如下为示例的配置,详情参考官方文档
// vite.config.js import { fileURLToPath, URL } from 'node:url' import { defineConfig, loadEnv } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' import Components from 'unplugin-vue-components/vite' import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' // mode: dev用于开发,production用于构建,可以使用命令行--mode重写 // 项目配置项:默认不加载 .env 文件 export default defineConfig(({ mode }: { mode: string }) => { // 根据当前工作目录中的 `mode` 加载 .env 文件 // 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。 const env = loadEnv(mode, process.cwd(), '') return { base: env.VITE_APP_BASE || '/', // 需要用到的插件数组。 plugins: [ vue(), vueJsx(), Components({ resolvers: [ AntDesignVueResolver({ importStyle: false // css in js }) ] }) ], // 路径解析 resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), comps: fileURLToPath(new URL('./src/components', import.meta.url)), views: fileURLToPath(new URL('./src/views', import.meta.url)) } }, // 服务启动的端口等 server: { https: false, port: 5173, host: '0.0.0.0', // 开发服务器启动时,自动在浏览器中打开应用程序 open: false, // 跨域 cors: false } } }) -
修改默认的启动程序
// package.json "scripts": { "dev": "vite --mode dev", //.... } 增加环境变量的文件
.env.dev和.env.production
五、使用 store
-
配置
// main.ts import { createPinia } from 'pinia' app.use(createPinia()) // 避免刷新以后,store丢失,进行数据的持久化 // 👉 持久化pinia const store = useStore() // 页面进入:合并状态 const localState = localStorage.getItem(AiAppStorageKey) if (localState) { console.log('[温馨提示]:合并Store...') store.$state = JSON.parse(localState) } // 页面刷新:存储状态 window.addEventListener('beforeunload', () => { console.log('[温馨提示]:缓存Store...') localStorage.setItem(AiAppStorageKey, JSON.stringify(store.$state)) }) -
定义
// store/index.ts import { defineStore } from 'pinia' import type { UserInfoType } from '@/typings' interface StoreProps { token: string userInfo: UserInfoType | any } interface ActionProps { setToken: (token: string) => void resetToken: () => void } // 用于保存token export const useStore = defineStore<string, StoreProps, any, ActionProps> ('appStore', { state: () => ({ token: '', userInfo: {} }), actions: { async setToken(token) { this.token = token }, async resetToken() { this.token = '' } } }) -
使用
const store = useStore() store.setToken("测试")
六、使用 Ant Design
-
安装依赖
# 安装AntDesign: npm install ant-design-vue@4.x --save # 安装antDesign Icon npm install --save @ant-design/icons-vue -
引入
// 文件:src\main.ts import Antd from 'ant-design-vue' import 'ant-design-vue/dist/reset.css' // 1. 创建app const app = createApp(App) // 2.使用插件Antd app.use(Antd) // 3. 挂在在id=app的根 app.mount('#app')
6.1 使用时的问题
6.1.1 按照官方文档说明使用 message 出现不显示的问题处理
- 使用网上各种方法后仍然不显示
- 指明挂载的节点
// 文件:src\main.ts import { message } from 'ant-design-vue' message.config({ maxCount: 1, getContainer: () => document.getElementById('message_modal') || document.body }) - 在#app 的节点同级增加 id=message_modal 的节点
6.1.2 动态加载 Icon 图标
-
注册 Icon 组件
// 文件:main.js import * as antIcons from '@ant-design/icons-vue' Object.keys(antIcons).forEach((key) => { app.component(key, antIcons[key as keyof typeof antIcons]) }) app.config.globalProperties.$antIcons = antIcons -
使用
<script setup lang="ts"> import { ref } from 'vue'; const data = ref([{ "name": "a", "icon": "SettingOutlined" }]) </script> <template> <template v-for="item in data"> <component :is="item.icon">{{ item.name }}</component> </template> </template>
七、HTTP 请求的封装
// src\utils\request.ts
import axios from 'axios'
import type { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
import { useStore } from '@/store'
/********************
** 基础类型
********************/
export interface BaseResponse<T = any> {
success: boolean
errCode: number
data: T
errMessage: string
}
/********************
** 创建axios实例
********************/
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_APP_HOST,
timeout: 500000
})
/********************
** 请求拦截器
********************/
service.interceptors.request.use(
(config: AxiosRequestConfig): any => {
const store = useStore()
// -- 配置请求头
const token = store.token
config.headers = {
'Content-Type': 'application/json',
Authorization: token ? `Bearer ${token}` : ''
}
return config
},
(error: AxiosError) => Promise.reject(error)
)
/********************
** 响应拦截器
********************/
service.interceptors.response.use(
(response: AxiosResponse): any => {
const { status } = response
console.log(response)
if (status === 200) {
return response.data
} else {
return Promise.reject(new Error(response?.data?.errMessage ?? '未知错误'))
}
},
(error: AxiosError) => {
// 处理 HTTP 错误
if (!error.response) {
return Promise.reject({ code: 503, message: `网络连接失败` })
} else {
const store = useStore()
let errorMsg = {
code: error.response?.status,
message: (error.response?.data as BaseResponse).errMessage,
response: error.response
}
if (errorMsg?.code === 401) {
store.resetStore()
errorMsg.message = '权限过期,请重新登录'
}
return Promise.reject(errorMsg)
}
}
)
/********************
** 导出请求方法(重点)
********************/
export const http = {
get<T = any>(
url: string,
params?: object,
config?: AxiosRequestConfig
): Promise<BaseResponse<T>> {
const conf = { params, ...config }
return service.get(url, conf)
},
post<T = any>(url: string, data?: object, config?: AxiosRequestConfig): Promise<BaseResponse<T>> {
return service.post(url, data, config)
},
put<T = any>(url: string, data?: object, config?: AxiosRequestConfig): Promise<BaseResponse<T>> {
return service.put(url, data, config)
},
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<BaseResponse<T>> {
return service.delete(url, config)
}
}
- 使用
// src\service\users.ts
import { http } from '@/utils/request'
// 登录
export function login(data: USERAPI.LoginParamType) {
return http.post<string>('/ai-api/ai-auth/auth/login', data)
}
// 退出登录
export function logout() {
return http.post<string>('/ai-api/ai-auth/auth/logout')
}
// 获取用户信息
export function getUserInfo() {
return http.get<USERAPI.UserInfo>('/ai-api/ai-auth/auth/info')
}
其他
- 布局和完成的代码,见下一章