Vue 3.3 + Vite 4.3 + TypeScript 5+ Element-Plus:从零到一构建企业级后台管理系统(前后端开源)(二)

简介: Vue 3.3 + Vite 4.3 + TypeScript 5+ Element-Plus:从零到一构建企业级后台管理系统(前后端开源)(二)

整合 SVG 图标

通过 vite-plugin-svg-icons 插件整合 Iconfont 第三方图标库实现本地图标


参考: vite-plugin-svg-icons 安装文档


安装依赖


npm install -D fast-glob@3.2.11

npm install -D vite-plugin-svg-icons@2.0.1

1

2

创建 src/assets/icons 目录 , 放入从 Iconfont 复制的 svg 图标


微信图片_20230706152107.png


main.ts 引入注册脚本


// src/main.ts

import 'virtual:svg-icons-register';

1

2

vite.config.ts 配置插件


// vite.config.ts

import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';


export default ({command, mode}: ConfigEnv): UserConfig => {

return (

    {

        plugins: [

            createSvgIconsPlugin({

                // 指定需要缓存的图标文件夹

                iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],

                // 指定symbolId格式

                symbolId: 'icon-[dir]-[name]',

            })

        ]

    }

)



SVG 组件封装




const props = defineProps({

prefix: {

type: String,

default: "icon",

},

iconClass: {

type: String,

required: false,

},

color: {

type: String,

},

size: {

type: String,

default: "1em",

},

});


const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);






.svg-icon {

display: inline-block;

outline: none;

width: 1em;

height: 1em;

vertical-align: -0.15em; /* 因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果 */

fill: currentColor; /* 定义元素的颜色,currentColor是一个变量,这个变量的值就表示当前元素的color值,如果当前元素未设置color值,则从父元素继承 */

overflow: hidden;

}



组件使用





微信图片_20230706152122.png


整合 SCSS

一款CSS预处理语言,SCSS 是 Sass 3 引入新的语法,其语法完全兼容 CSS3,并且继承了 Sass 的强大功能。


安装依赖


npm i -D sass

1

创建 variables.scss 变量文件,添加变量 bg−color定义,注意规范变量以bg-color 定义,注意规范变量以  开头


// src/styles/variables.scss

$bg-color:#242424;

1

2

Vite 配置导入 SCSS 全局变量文件


// vite.config.ts

css: {

   // CSS 预处理器

   preprocessorOptions: {

       //define global scss variable

       scss: {

           javascriptEnabled: true,

           additionalData: `@use "@/styles/variables.scss" as *;`

       }

   }

}

style 标签使用SCSS全局变量






.box {

 width: 100px;

 height: 100px;

 background-color: $bg-color;

}



上面导入的 SCSS 全局变量在 TypeScript 不生效的,需要创建一个以 .module.scss 结尾的文件


// src/styles/variables.module.scss


// 导出 variables.scss 文件的变量

:export{

   bgColor:$bg-color

}


TypeScript 使用 SCSS 全局变量




 import variables from "@/styles/variables.module.scss";

 console.log(variables.bgColor)  





整合 UnoCSS

UnoCSS 是一个具有高性能且极具灵活性的即时原子化 CSS 引擎 。


参考:Vite 安装 UnoCSS 官方文档


安装依赖


npm install -D unocss

1

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'

1

2

VSCode 安装 UnoCSS 插件

微信图片_20230706152202.png



再看/下具体使用方式和实际效果:

微信图片_20230706152220.png

代码 效果


如果UnoCSS 插件智能提示不生效,请参考:VSCode插件UnoCSS智能提示不生效解决 。


整合 Pinia

Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。


参考:Pinia 官方文档


安装依赖


npm install pinia

1

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

微信图片_20230706152240.png组合式 Setup Store

微信图片_20230706152244.png

至于如何选择,官方给出的建议 :选择你觉得最舒服的那一个就好 。


这里选择组合式,新建文件 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 };

});


父组件




import HelloWorld from "@/components/HelloWorld.vue";


import { useCounterStore } from "@/store/counter";

const counterStore = useCounterStore();





子组件




import { useCounterStore } from "@/store/counter";

const counterStore = useCounterStore();





效果预览


微信图片_20230706152309.gif


环境变量

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'

1

2

3

4

生产环境变量配置:.env.production


VITE_APP_TITLE = 'vue3-element-admin'

VITE_APP_PORT = 3000

VITE_APP_BASE_API = '/prod-api'

1

2

3

环境变量智能提示


新建 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的配置。


微信图片_20230706152329.png


跨域处理

跨域原理


浏览器同源策略: 协议、域名和端口都相同是同源,浏览器会限制非同源请求读取响应结果。


本地开发环境通过 Vite 配置反向代理解决浏览器跨域问题,生产环境则是通过 nginx 配置反向代理 。


vite.config.ts 配置代理

微信图片_20230706152346.png



表面肉眼看到的请求地址: http://localhost:3000/dev-api/api/v1/users/me


真实访问的代理目标地址: http://vapi.youlai.tech/api/v1/users/me

微信图片_20230706152401.png



整合 Axios

Axios 基于promise可以用于浏览器和node.js的网络请求库


参考: Axios 官方文档


安装依赖


npm install axios

1

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 在线接口文档, 查看登录接口请求参数和响应数据类型

微信图片_20230706152417.png



点击 生成代码 获取登录响应数据 TypeScript 类型定义


微信图片_20230706152433.png


将类型定义复制到 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 {

 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((resolve, reject) => {

   loginApi(loginData)

     .then(response => {

       const { tokenType, accessToken } = response.data;

       token.value = tokenType + ' ' + accessToken; // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx

       resolve();

     })

     .catch(error => {

       reject(error);

     });

 });

}


动态路由

安装 vue-router


npm install vue-router@next

1

路由实例


创建路由实例,顺带初始化静态路由,而动态路由需要用户登录,根据用户拥有的角色进行权限校验后进行初始化


// src/router/index.ts

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';


export const Layout = () => import('@/layout/index.vue');


// 静态路由

export const constantRoutes: RouteRecordRaw[] = [

 {

   path: '/redirect',

   component: Layout,

   meta: { hidden: true },

   children: [

     {

       path: '/redirect/:path(.*)',

       component: () => import('@/views/redirect/index.vue')

     }

   ]

 },


 {

   path: '/login',

   component: () => import('@/views/login/index.vue'),

   meta: { hidden: true }

 },


 {

   path: '/',

   component: Layout,

   redirect: '/dashboard',

   children: [

     {

       path: 'dashboard',

       component: () => import('@/views/dashboard/index.vue'),

       name: 'Dashboard',

       meta: { title: 'dashboard', icon: 'homepage', affix: true }

     }

   ]

 }

];


/**

* 创建路由

*/

const router = createRouter({

 history: createWebHashHistory(),

 routes: constantRoutes as RouteRecordRaw[],

 // 刷新时,滚动条位置还原

 scrollBehavior: () => ({ left: 0, top: 0 })

});


/**

* 重置路由

*/

export function resetRouter() {

 router.replace({ path: '/login' });

 location.reload();

}


export default router;


全局注册路由实例


// main.ts

import router from "@/router";


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


动态权限路由


路由守卫 src/permission.ts ,获取当前登录用户的角色信息进行动态路由的初始化

微信图片_20230706152449.png



最终调用 permissionStore.generateRoutes(roles) 方法生成动态路由


// src/store/modules/permission.ts

import { listRoutes } from '@/api/menu';


export const usePermissionStore = defineStore('permission', () => {

 const routes = ref([]);


 function setRoutes(newRoutes: RouteRecordRaw[]) {

   routes.value = constantRoutes.concat(newRoutes);

 }

 /**

  * 生成动态路由

  *

  * @param roles 用户角色集合

  * @returns

  */

 function generateRoutes(roles: string[]) {

   return new Promise((resolve, reject) => {

     // 接口获取所有路由

     listRoutes()

       .then(({ data: asyncRoutes }) => {

         // 根据角色获取有访问权限的路由

         const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);

         setRoutes(accessedRoutes);

         resolve(accessedRoutes);

       })

       .catch(error => {

         reject(error);

       });

   });

 }

 // 导出 store 的动态路由数据 routes

 return { routes, setRoutes, generateRoutes };

});


接口获取得到的路由数据

微信图片_20230706152540.png



根据路由数据 (routes)生成菜单的关键代码

微信图片_20230706152554.png

src/layout/componets/Sidebar/index.vue src/layout/componets/Sidebar/SidebarItem.vue

相关文章
|
4月前
|
JavaScript 前端开发 IDE
[译] 用 Typescript + Composition API 重构 Vue 3 组件
[译] 用 Typescript + Composition API 重构 Vue 3 组件
[译] 用 Typescript + Composition API 重构 Vue 3 组件
|
3月前
|
JavaScript
在vue3中(vite)引入unocss,安装配置unocss
在vue3中(vite)引入unocss,安装配置unocss
|
1月前
|
前端开发 JavaScript 容器
在 vite+vue 中使用@originjs/vite-plugin-federation 模块联邦
【10月更文挑战第25天】模块联邦是一种强大的技术,它允许将不同的微前端模块组合在一起,形成一个统一的应用。在 vite+vue 项目中,使用@originjs/vite-plugin-federation 模块联邦可以实现高效的模块共享和组合。通过本文的介绍,相信你已经了解了如何在 vite+vue 项目中使用@originjs/vite-plugin-federation 模块联邦,包括安装、配置和使用等方面。在实际开发中,你可以根据自己的需求和项目的特点,灵活地使用模块联邦,提高项目的可维护性和扩展性。
|
2月前
|
资源调度 JavaScript 前端开发
vue3第一章基础:创建Vue3.0工程,包括使用vue-cli 创建、使用 vite 创建
vue3第一章基础:创建Vue3.0工程,包括使用vue-cli 创建、使用 vite 创建
27 5
|
2月前
|
存储 前端开发 中间件
vue3之vite配置vite-plugin-mock使用mock轻松创建模拟数据提高开发效率
vue3之vite配置vite-plugin-mock使用mock轻松创建模拟数据提高开发效率
429 0
|
2月前
|
JavaScript 安全 开发工具
在 Vue 3 中使用 TypeScript
【10月更文挑战第3天】
|
3月前
|
JavaScript 开发工具
vite如何打包vue3插件为JSSDK
【9月更文挑战第10天】以下是使用 Vite 打包 Vue 3 插件为 JS SDK 的步骤:首先通过 `npm init vite-plugin-sdk --template vue` 创建 Vue 3 项目并进入项目目录 `cd vite-plugin-sdk`。接着,在 `src` 目录下创建插件文件(如 `myPlugin.js`),并在 `main.js` 中引入和使用该插件。然后,修改 `vite.config.js` 文件以配置打包选项。最后,运行 `npm run build` 进行打包,生成的 `my-plugin-sdk.js` 即为 JS SDK,可在其他项目中引入使用。
142 6
|
2月前
|
存储 资源调度 JavaScript
Vite是什么?怎样使用Vite创建Vue3项目?
Vite是什么?怎样使用Vite创建Vue3项目?
118 0
|
4月前
|
JavaScript API
如何使用Vue 3和Type Script进行组件化设计
【8月更文挑战第16天】如何使用Vue 3和Type Script进行组件化设计
49 3
|
4月前
|
JavaScript API
如何使用Vue 3和Type Script进行组件化设计
【8月更文挑战第16天】如何使用Vue 3和Type Script进行组件化设计
76 1