Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源)(1):
https://developer.aliyun.com/article/1395742
style 标签使用SCSS全局变量
<!-- src/components/HelloWorld.vue --> <template> <div class="box" /> </template> <style lang="scss" scoped> .box { width: 100px; height: 100px; background-color: $bg-color; } </style>
上面导入的 SCSS 全局变量在 TypeScript 不生效的,需要创建一个以 .module.scss 结尾的文件
// src/styles/variables.module.scss // 导出 variables.scss 文件的变量 :export{ bgColor:$bg-color }
TypeScript 使用 SCSS 全局变量
<!-- src/components/HelloWorld.vue --> <script setup lang="ts"> import variables from "@/styles/variables.module.scss"; console.log(variables.bgColor) </script> <template> <div style="width:100px;height:100px" :style="{ 'background-color': variables.bgColor }" /> </template>
整合 UnoCSS
UnoCSS 是一个具有高性能且极具灵活性的即时原子化 CSS 引擎 。
安装依赖
npm install -D unocss
vite.config.ts 配置
// vite.config.ts import UnoCSS from 'unocss/vite' export default { plugins: [ UnoCSS({ /* options */ }), ], }
main.ts 引入 uno.css
// src/main.ts import 'uno.css'
VSCode 安装 UnoCSS 插件
再看下具体使用方式和实际效果:
如果UnoCSS 插件智能提示不生效,请参考:VSCode插件UnoCSS智能提示不生效解决 。
整合 Pinia
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。
参考:Pinia 官方文档
安装依赖
npm install pinia
main.ts 引入 pinia
// src/main.ts import { createPinia } from "pinia"; import App from "./App.vue"; createApp(App).use(createPinia()).mount("#app");
定义 Store
根据 Pinia 官方文档-核心概念 描述 ,Store 定义分为选项式
和组合式
, 先比较下两种写法的区别:
选项式 Option Store
组合式 Setup Store
至于如何选择,官方给出的建议 :选择你觉得最舒服的那一个就好 。
这里选择组合式,新建文件 src/store/counter.ts
// src/store/counter.ts import { defineStore } from "pinia"; export const useCounterStore = defineStore("counter", () => { // ref变量 → state 属性 const count = ref(0); // computed计算属性 → getters const double = computed(() => { return count.value * 2; }); // function函数 → actions function increment() { count.value++; } return { count, double, increment }; });
父组件
<!-- src/App.vue --> <script setup lang="ts"> import HelloWorld from "@/components/HelloWorld.vue"; import { useCounterStore } from "@/store/counter"; const counterStore = useCounterStore(); </script> <template> <h1 class="text-3xl">vue3-element-admin-父组件</h1> <el-button type="primary" @click="counterStore.increment">count++</el-button> <HelloWorld /> </template>
子组件
<!-- src/components/HelloWorld.vue --> <script setup lang="ts"> import { useCounterStore } from "@/store/counter"; const counterStore = useCounterStore(); </script> <template> <el-card class="text-left text-white border-white border-1 border-solid mt-10 bg-[#242424]" > <template #header> 子组件 HelloWorld.vue</template> <el-form> <el-form-item label="数字:"> {{ counterStore.count }}</el-form-item> <el-form-item label="加倍:"> {{ counterStore.double }}</el-form-item> </el-form> </el-card> </template>
效果预览
环境变量
Vite 环境变量主要是为了区分开发、测试、生产等环境的变量
参考: Vite 环境变量配置官方文档
env配置文件
项目根目录新建 .env.development 、.env.production
开发环境变量配置:.env.development
# 变量必须以 VITE_ 为前缀才能暴露给外部读取 VITE_APP_TITLE = 'vue3-element-admin' VITE_APP_PORT = 3000 VITE_APP_BASE_API = '/dev-api'
生产环境变量配置:.env.production
VITE_APP_TITLE = 'vue3-element-admin' VITE_APP_PORT = 3000 VITE_APP_BASE_API = '/prod-api'
环境变量智能提示
新建 src/types/env.d.ts文件存放环境变量TS类型声明
// src/types/env.d.ts interface ImportMetaEnv { /** * 应用标题 */ VITE_APP_TITLE: string; /** * 应用端口 */ VITE_APP_PORT: number; /** * API基础路径(反向代理) */ VITE_APP_BASE_API: string; } interface ImportMeta { readonly env: ImportMetaEnv; }
使用自定义环境变量就会有智能提示,环境变量的读取和使用请看下一节的跨域处理中的 vite.config.ts的配置
跨域处理
跨域原理
浏览器同源策略: 协议、域名和端口都相同是同源,浏览器会限制非同源请求读取响应结果。
本地开发环境通过 Vite 配置反向代理解决浏览器跨域问题,生产环境则是通过 nginx 配置反向代理 。
vite.config.ts 配置代理
表面肉眼看到的请求地址: http://localhost:3000/dev-api/api/v1/users/me
真实访问的代理目标地址: http://vapi.youlai.tech/api/v1/users/me
整合 Axios
Axios 基于promise可以用于浏览器和node.js的网络请求库
参考: Axios 官方文档
安装依赖
npm install axios
Axios 工具类封装
// src/utils/request.ts import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import { useUserStoreHook } from '@/store/modules/user'; // 创建 axios 实例 const service = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, timeout: 50000, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); // 请求拦截器 service.interceptors.request.use( (config: InternalAxiosRequestConfig) => { const userStore = useUserStoreHook(); if (userStore.token) { config.headers.Authorization = userStore.token; } return config; }, (error: any) => { return Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( (response: AxiosResponse) => { const { code, msg } = response.data; // 登录成功 if (code === '00000') { return response.data; } ElMessage.error(msg || '系统出错'); return Promise.reject(new Error(msg || 'Error')); }, (error: any) => { if (error.response.data) { const { code, msg } = error.response.data; // token 过期,跳转登录页 if (code === 'A0230') { ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', { confirmButtonText: '确定', type: 'warning' }).then(() => { localStorage.clear(); // @vueuse/core 自动导入 window.location.href = '/'; }); }else{ ElMessage.error(msg || '系统出错'); } } return Promise.reject(error.message); } ); // 导出 axios 实例 export default service;
登录接口实战
访问 vue3-element-admin 在线接口文档, 查看登录接口请求参数和响应数据类型
点击 生成代码 获取登录响应数据 TypeScript 类型定义
将类型定义复制到 src/api/auth/types.ts 文件中
/** * 登录请求参数 */ export interface LoginData { /** * 用户名 */ username: string; /** * 密码 */ password: string; } /** * 登录响应 */ export interface LoginResult { /** * 访问token */ accessToken?: string; /** * 过期时间(单位:毫秒) */ expires?: number; /** * 刷新token */ refreshToken?: string; /** * token 类型 */ tokenType?: string; }
登录 API 定义
// src/api/auth/index.ts import request from '@/utils/request'; import { AxiosPromise } from 'axios'; import { LoginData, LoginResult } from './types'; /** * 登录API * * @param data {LoginData} * @returns */ export function loginApi(data: LoginData): AxiosPromise<LoginResult> { return request({ url: '/api/v1/auth/login', method: 'post', params: data }); }
登录 API 调用
// src/store/modules/user.ts import { loginApi } from '@/api/auth'; import { LoginData } from '@/api/auth/types'; /** * 登录调用 * * @param {LoginData} * @returns */ function login(loginData: LoginData) { return new Promise<void>((resolve, reject) => { loginApi(loginData) .then(response => { const { tokenType, accessToken } = response.data; token.value = tokenType + ' ' + accessToken; // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx resolve(); }) .catch(error => { reject(error); }); }); }
Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源)(3):