相关推荐recommended
Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源)
作者:mmseoamin日期:2024-02-06

vue3-element-admin 是基于 vue-element-admin 升级的 Vue3 + Element Plus 版本的后台管理前端解决方案,技术栈为 Vue3 + Vite4 + TypeScript + Element Plus + Pinia + Vue Router 等当前主流框架。

相较于其他管理前端框架,vue3-element-admin 的优势在于一有一无 (有配套后端、无复杂封装):

  • 配套完整 Java 后端 权限管理系统,开箱即用,提供 OpenAPI 文档 搭配 Apifox 生成 Node、Python、Go等其他服务端代码;

  • 完全基于 vue-element-admin 升级的 Vue3 版本,没有对框架(Element Plus)的组件再封装,上手成本低和扩展性高。

前言

本篇是 vue3-element-admin v2.x 版本从 0 到 1,相较于 v1.x 版本 主要增加了对原子CSS(UnoCSS)、按需自动导入、暗黑模式的支持。

项目预览

在线预览

https://vue3.youlai.tech/

首页控制台

Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),image-20230326161901446,第1张
Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),暗黑模式,第2张

接口文档

Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),接口文档,第3张

权限管理系统

Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),用户管理,第4张Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),角色管理,第5张
Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),菜单管理,第6张Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),字典管理,第7张

扩展生态

youlai-mall 有来开源商城:Spring Cloud微服务+ vue3-element-admin+uni-app

youlai-mall 商品管理mall-app 移动端
Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第8张Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第9张

项目指南

功能清单

Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第10张

技术栈&官网

技术栈描述官网
Vue3渐进式 JavaScript 框架https://cn.vuejs.org/
Element Plus基于 Vue 3,面向设计师和开发者的组件库https://element-plus.gitee.io/zh-CN/
Vite前端开发与构建工具https://cn.vitejs.dev/
TypeScript微软新推出的一种语言,是 JavaScript 的超集https://www.tslang.cn/
Pinia新一代状态管理工具https://pinia.vuejs.org/
Vue RouterVue.js 的官方路由https://router.vuejs.org/zh/
wangEditorTypescript 开发的 Web 富文本编辑器https://www.wangeditor.com/
Echarts一个基于 JavaScript 的开源可视化图表库https://echarts.apache.org/zh/
vue-i18nVue 国际化多语言插件https://vue-i18n.intlify.dev/
VueUse基于Vue组合式API的实用工具集(类比HuTool工具)http://www.vueusejs.com/

前/后端源码

GiteeGithub
前端vue3-element-adminvue3-element-admin
后端youlai-bootyoulai-boot

接口文档

  • 接口调用地址:https://vapi.youlai.tech
  • 接口文档地址:在线接口文档
  • OpenAPI 3.0 文档地址:http://vapi.youlai.tech/v3/api-docs

    环境准备

    名称备注
    开发工具VSCode 下载-
    运行环境Node 16+ 下载Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),image-20230224222640120,第11张
    VSCode插件(必装)插件市场搜索 Vue Language Features (Volar) 和 TypeScript Vue Plugin (Volar) 安装,且禁用 VeturVue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),image-20230224222541797,第12张

    项目初始化

    按照 🍃Vite 官方文档 - 搭建第一个 Vite 项目 说明,执行以下命令完成 vue 、typescirpt 模板项目的初始化

     npm init vite@latest vue3-element-admin --template vue-ts
    
    • **vue3-element-admin **: 自定义的项目名称

    • vue-ts : vue + typescript 模板的标识,查看 create-vite 以获取每个模板的更多细节:vue,vue-ts,react,react-ts

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第13张

      初始化完成项目位于 D:\project\demo\vue3-element-admin , 使用 VSCode 导入,执行以下命令启动:

      npm install
      npm run dev
      

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第14张

      浏览器访问 localhost:5173 预览

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第15张

      路径别名配置

      相对路径别名配置,使用 @ 代替 src

      Vite 配置

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第16张

      TypeScirpt 编译器配置

      // tsconfig.json
      "compilerOptions": {
          ...
          "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
          "paths": { // 路径映射,相对于baseUrl
          	"@/*": ["src/*"] 
          }
      }
      

      路径别名使用

      // src/App.vue
      import HelloWorld from '/src/components/HelloWorld.vue'
      						↓
      import HelloWorld from '@/components/HelloWorld.vue'
      

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第17张

      安装自动导入

      Element Plus 官方文档中推荐 按需自动导入 的方式,而此需要使用额外的插件 unplugin-auto-import 和 unplugin-vue-components 来导入要使用的组件。所以在整合 Element Plus 之前先了解下自动导入的概念和作用

      概念

      为了避免在多个页面重复引入 API 或 组件,由此而产生的自动导入插件来节省重复代码和提高开发效率。

      插件概念自动导入对象
      unplugin-auto-import按需自动导入APIref,reactive,watch,computed 等API
      unplugin-vue-components按需自动导入组件Element Plus 等三方库和指定目录下的自定义组件

      看下自动导入插件未使用和使用的区别:

      插件名未使用自动导入使用自动导入
      unplugin-auto-importVue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第18张Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第19张
      unplugin-vue-componentsVue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第20张Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第21张

      安装插件依赖

      npm install -D unplugin-auto-import unplugin-vue-components 
      

      vite.config.ts - 自动导入配置

      新建 /src/types 目录用于存放自动导入函数和组件的TS类型声明文件

      import AutoImport from "unplugin-auto-import/vite";
      import Components from "unplugin-vue-components/vite";
      plugins: [
        AutoImport({
          // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
          imports: ["vue"],
          eslintrc: {
            enabled: true, // 是否自动生成 eslint 规则,建议生成之后设置 false 
            filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件
          },
          dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 指定自动导入函数TS类型声明文件路径
        }),
        Components({
          dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径
        }),
      ]
      

      .eslintrc.cjs - 自动导入函数 eslint 规则引入

      "extends": [
          "./.eslintrc-auto-import.json"
      ],
      

      tsconfig.json - 自动导入TS类型声明文件引入

      {
        "include": ["src/**/*.d.ts"]
      }
      

      自动导入效果

      运行项目 npm run dev 自动

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),image-20230227234439410,第22张

      整合 Element Plus

      参考: element plus 按需自动导入

      需要完成上面一节的 自动导入 的安装和配置

      安装 Element Plus

      npm install element-plus
      

      安装自动导入 Icon 依赖

      npm i -D unplugin-icons
      

      vite.config.ts 配置

      参考: element-plus-best-practices - vite.config.ts

      // vite.config.ts
      import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
      import Icons from "unplugin-icons/vite";
      import IconsResolver from "unplugin-icons/resolver";
      export default ({ mode }: ConfigEnv): UserConfig => {
        return {
          plugins: [
            // ...
            AutoImport({
              // ...  
              resolvers: [
                // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
                ElementPlusResolver(),
                // 自动导入图标组件
                IconsResolver({}),
              ]
              vueTemplate: true, // 是否在 vue 模板中自动导入
              dts: path.resolve(pathSrc, 'types', 'auto-imports.d.ts') // 自动导入组件类型声明文件位置,默认根目录
                
            }),
            Components({ 
              resolvers: [
                // 自动导入 Element Plus 组件
                ElementPlusResolver(),
                // 自动注册图标组件
                IconsResolver({
                  enabledCollections: ["ep"] // element-plus图标库,其他图标库 https://icon-sets.iconify.design/
                }),
              ],
              dts: path.resolve(pathSrc, "types", "components.d.ts"), //  自动导入组件类型声明文件位置,默认根目录
            }),
            Icons({
              // 自动安装图标库
              autoInstall: true,
            }),
          ],
        };
      };
      

      示例代码

      
      
      Success Info Warning Danger

      效果预览

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第23张

      整合 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 
      

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

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第24张

      main.ts 引入注册脚本

      // src/main.ts
      import 'virtual:svg-icons-register';
      

      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 组件封装

      
      
      
      

      组件使用

      
      
      

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第25张

      整合 SCSS

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

      安装依赖

      npm i -D sass 
      

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

      // src/styles/variables.scss
      $bg-color:#242424;
      

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

      // vite.config.ts
      css: {
          // CSS 预处理器
          preprocessorOptions: {
              //define global scss variable
              scss: {
                  javascriptEnabled: true,
                  additionalData: `@use "@/styles/variables.scss" as *;`
              }
          }
      }
      

      style 标签使用SCSS全局变量

      
      
      
      

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

      // src/styles/variables.module.scss
      // 导出 variables.scss 文件的变量
      :export{
          bgColor:$bg-color
      }
      

      TypeScript 使用 SCSS 全局变量

      
      
      

      整合 UnoCSS

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

      参考:Vite 安装 UnoCSS 官方文档

      安装依赖

      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 插件

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第26张

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

      代码效果
      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第27张Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),image-20230222220856251,第28张

      如果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
      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第29张Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第30张

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

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

      父组件

      
      
      

      子组件

      
      
      

      效果预览

      Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第31张

      环境变量

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

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第32张

        跨域处理

        跨域原理

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

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

        vite.config.ts 配置代理

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第33张

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

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

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第34张

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

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第35张

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

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第36张

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

        路由实例

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

        // 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 ,获取当前登录用户的角色信息进行动态路由的初始化

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第37张

        最终调用 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 };
        });
        

        接口获取得到的路由数据

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第38张

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

        src/layout/componets/Sidebar/index.vuesrc/layout/componets/Sidebar/SidebarItem.vue
        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第39张Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),image-20230326145836872,第40张

        按钮权限

        除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives),以下就通过自定义指令的方式实现按钮权限控制。

        参考:Vue 官方文档-自定义指令

        **自定义指令 **

        // src/directive/permission/index.ts
        import { useUserStoreHook } from '@/store/modules/user';
        import { Directive, DirectiveBinding } from 'vue';
        /**
         * 按钮权限
         */
        export const hasPerm: Directive = {
          mounted(el: HTMLElement, binding: DirectiveBinding) {
            // 「超级管理员」拥有所有的按钮权限
            const { roles, perms } = useUserStoreHook();
            if (roles.includes('ROOT')) {
              return true;
            }
            // 「其他角色」按钮权限校验
            const { value } = binding;
            if (value) {
              const requiredPerms = value; // DOM绑定需要的按钮权限标识
              const hasPerm = perms?.some(perm => {
                return requiredPerms.includes(perm);
              });
              if (!hasPerm) {
                el.parentNode && el.parentNode.removeChild(el);
              }
            } else {
              throw new Error(
                "need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\""
              );
            }
          }
        };
        

        全局注册自定义指令

        // src/directive/index.ts
        import type { App } from 'vue';
        import { hasPerm } from './permission';
        // 全局注册 directive 方法
        export function setupDirective(app: App) {
          // 使 v-hasPerm 在所有组件中都可用
          app.directive('hasPerm', hasPerm);
        }
        
        // src/main.ts
        import { setupDirective } from '@/directive';
        const app = createApp(App);
        // 全局注册 自定义指令(directive)
        setupDirective(app);
        

        组件使用自定义指令

        // src/views/system/user/index.vue
        新增
        删除
        

        国际化

        国际化分为两个部分,Element Plus 框架国际化(官方提供了国际化方式)和自定义国际化(通过 vue-i18n 国际化插件)

        Element Plus 国际化

        简单的使用方式请参考 Element Plus 官方文档-国际化示例,以下介绍 vue3-element-admin 整合 pinia 实现国际化语言切换。

        Element Plus 提供了一个 Vue 组件 ConfigProvider 用于全局配置国际化的设置。

        
        
        

        定义 store

        // src/store/modules/app.ts
        import { defineStore } from 'pinia';
        import { useStorage } from '@vueuse/core';
        import defaultSettings from '@/settings';
        // 导入 Element Plus 中英文语言包
        import zhCn from 'element-plus/es/locale/lang/zh-cn';
        import en from 'element-plus/es/locale/lang/en';
        // setup
        export const useAppStore = defineStore('app', () => {
            
          const language = useStorage('language', defaultSettings.language);
            
          /**
           * 根据语言标识读取对应的语言包
           */
          const locale = computed(() => {
            if (language?.value == 'en') {
              return en;
            } else {
              return zhCn;
            }
          });
          /**
           * 切换语言
           */
          function changeLanguage(val: string) {
            language.value = val;
          }
          return {
            language,
            locale,
            changeLanguage
          };
        });
        

        切换语言组件调用

        
        
        

        从 Element Plus 分页组件看下国际化的效果

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第41张

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第42张

        vue-i18n 自定义国际化

        i18n 英文全拼 internationalization ,国际化的意思,英文 i 和 n 中间18个英文字母

        参考:vue-i18n 官方文档 - installation

        安装 vue-i18n

        npm install vue-i18n@9
        

        自定义语言包

        创建 src/lang/package 语言包目录,存放自定义的语言文件

        中文语言包 zh-cn.ts英文语言包 en.ts
        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第43张Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第44张

        创建 i18n 实例

        // src/lang/index.ts
        import { createI18n } from 'vue-i18n';
        import { useAppStore } from '@/store/modules/app';
        const appStore = useAppStore();
        // 本地语言包
        import enLocale from './package/en';
        import zhCnLocale from './package/zh-cn';
        const messages = {
          'zh-cn': {
            ...zhCnLocale
          },
          en: {
            ...enLocale
          }
        };
        // 创建 i18n 实例
        const i18n = createI18n({
          legacy: false,
          locale: appStore.language,
          messages: messages
        });
        // 导出 i18n 实例
        export default i18n;
        

        i18n 全局注册

        // main.ts
        // 国际化
        import i18n from '@/lang/index';
        app.use(i18n).mount('#app');
        

        登录页面国际化使用

        $t 是 i18n 提供的根据 key 从语言包翻译对应的 value 方法

        {{ $t("login.title") }}
        

        在登录页面 src/view/login/index.vue 查看如何使用

        效果预览

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第45张

        暗黑模式

        Element Plus 2.2.0 版本开始支持暗黑模式,启用方式参考 Element Plus 官方文档 - 暗黑模式, 官方也提供了示例 element-plus-vite-starter 模版 。

        这里根据官方文档和示例讲述 vue3-element-admin 是如何使用 VueUse 的 useDark 方法实现暗黑模式的动态切换。

        导入 Element Plus 暗黑模式变量

        // src/main.ts
        import 'element-plus/theme-chalk/dark/css-vars.css'
        

        切换暗黑模式设置

        
        
        

        自定义变量

        除了 Element Plus 组件样式之外,应用中还有很多自定义的组件和样式,像这样的:

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第46张

        应对自定义组件样式实现暗黑模式步骤如下:

        新建 src/styles/dark.scss

        html.dark {
          /* 修改自定义元素的样式 */   
          .navbar {
            background-color: #141414;
          }
        }
        

        在 Element Plus 的样式之后导入它

        // main.ts
        import 'element-plus/theme-chalk/dark/css-vars.css'
        import '@/styles/dark.scss';
        

        效果预览

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第47张

        组件封装

        wangEditor 富文本

        参考: wangEditor 官方文档

        安装 wangEditor

        npm install @wangeditor/editor @wangeditor/editor-for-vue@next 
        

        wangEditor 组件封装

        
        
        
        

        使用案例

        
        
        

        效果预览

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第48张

        Echarts 图表

        参考:📊 Echarts 官方示例

        安装 Echarts

        npm install echarts
        

        组件封装

         
        
        

        组件使用

        
        

        效果预览

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第49张

        图标选择器

        组件封装

        
        
        

        组件使用

        
        
        

        效果预览

        Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第50张

        规范配置

        代码统一规范

        【vue3-element-admin】ESLint+Prettier+Stylelint+EditorConfig 约束和统一前端代码规范

        Git 提交规范

        【vue3-element-admin】Husky + Lint-staged + Commitlint + Commitizen + cz-git 配置 Git 提交规范

        启动部署

        项目启动

        # 安装 pnpm
        npm install pnpm -g
        # 安装依赖
        pnpm install
        # 项目运行
        pnpm run dev
        

        项目部署

        # 项目打包
        pnpm run build:prod
        

        生成的静态文件在工程根目录 dist 文件夹

        FAQ

        1: defineProps is not defined

        • 问题描述

          ‘defineProps’ is not defined.eslint no-undef

          Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第51张

        • 解决方案

          根据 Eslint 官方解决方案描述,解析器使用 vue-eslint-parser v9.0.0 + 版本

          Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第52张

          安装 vue-eslint-parser 解析器

          npm install -D vue-eslint-parser
          

          Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第53张

          .eslintrc.js 关键配置( v9.0.0 及以上版本无需配置编译宏 vue/setup-compiler-macros)如下 :

            parser: 'vue-eslint-parser',
            extends: [
              'eslint:recommended',
          	// ...		
            ],
          

          重启 VSCode 已无报错提示

          Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第54张

          2: Vite 首屏加载慢(白屏久)

          • 问题描述

            Vite 项目启动很快,但首次打开界面加载慢?

            参考文章:为什么有人说 vite 快,有人却说 vite 慢

            vite 启动时,并不像 webpack 那样做一个全量的打包构建,所以启动速度非常快。启动以后,浏览器发起请求时, Dev Server 要把请求需要的资源发送给浏览器,中间需要经历预构建、对请求文件做路径解析、加载源文件、对源文件做转换,然后才能把内容返回给浏览器,这个时间耗时蛮久的,导致白屏时间较长。

            解决方案升级 vite 4.3 版本

            https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md

            Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源),第55张

            结语

            本篇从项目介绍、环境准备、VSCode 的代码规范配置 、整合各种框架 、再到最后的启动部署,完整讲述如何基于 Vue3 + Vite4 + TypeScript + Element Plus 等主流技术栈从 0 到 1构建一个企业应用级管理前端框架。

            项目有问题建议 issue 或者可以通过项目 关于我们 加入交流群反馈。