Vue3后台管理系统的搭建之路——从零开始,打造高颜值前端框架(二)

发表时间: 2024-01-20 16:16


上一篇我们已经把基础的工程化搭建了,下面就是集成项目所需要的插件,比如elementplus、unocss、mock之类的,因为我们使用的是vite,所以这些插件需要集成在里面。

vite配置

//vite.config.tsimport { ConfigEnv, defineConfig, loadEnv } from 'vite';import { setupVitePlugins } from './build';import pkg from './package.json';import { resolve } from 'path';import dayjs from 'dayjs';const { dependencies, devDependencies, name, version } = pkg;const __APP_INFO__ = {  pkg: { dependencies, devDependencies, name, version },  lastBuildTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')};export default defineConfig((configEnv: ConfigEnv) => {  const viteEnv = loadEnv(configEnv.mode, process.cwd()) as ImportMetaEnv;  return {    base: viteEnv.VITE_BASE_URL,    resolve: {      alias: {        '@': resolve(__dirname, './src'),        'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'      }    },    server: {      host: '0.0.0.0',      port: 8848,      open: true,      https: false,      // 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy      proxy: {}    },    plugins: setupVitePlugins(viteEnv),    build: {      sourcemap: false,      chunkSizeWarningLimit: 2000,      rollupOptions: {        output: {          chunkFileNames: 'static/js/[name]-[hash].js',          entryFileNames: 'static/js/[name]-[hash].js',          assetFileNames: 'static/[ext]/[name]-[hash].[ext]'        }      }    },    define: { __APP_INFO__: JSON.stringify(__APP_INFO__) }  };});


很简单的一个vite配置页,我们项目中采用将插件抽离到根目录下的build/plugins中,通过函数加载想要的插件。

下面说下这些子文件的内容

  • compress --->压缩插件
  • index --->入口插件
  • mock --->mock插件
  • unplugin --->其它
  • visualizer --->打包分析

build/index

import vue from '@vitejs/plugin-vue';import vueJsx from '@vitejs/plugin-vue-jsx';import unocss from '@unocss/vite';import unplugin from './unplugin'; //UI&Iconimport mock from './mock'; //mock(默认开启)import compress from './compress'; //压缩工具import visualizer from './visualizer'; //打包分析import progress from 'vite-plugin-progress'; //打包进度显示import VueDevtools from 'vite-plugin-vue-devtools'; //开发工具/** * vite插件 * @param viteEnv - 环境变量配置 */export function setupVitePlugins(viteEnv: ImportMetaEnv): (PluginOption | PluginOption[])[] {  const plugins = [    vue(),    vueJsx(),    VueDevtools(),    ...unplugin(viteEnv),    mock(viteEnv),    unocss(),    progress()  ];  if (viteEnv.VITE_COMPRESS === 'Y') {    plugins.push(compress(viteEnv));  }  if (viteEnv.VITE_VISUALIZER === 'Y') {    plugins.push(visualizer);  }  return plugins;}


这里index文件的viteEnv.xxx这个变量,是在env里的配置项

对于elementplus,我们这里采用自动加载引用的插件unplugin-auto-import,对应在unplugin里,这个文件里我们只对elementplus进行自动加载,关于vue、vue-router等插件采用自己手动引用的方式,这里看个人习惯你也可以都自动引入

配置router

下面我们需要创建路由文件结构

下面说下这些子文件的内容

  • guard --->路由拦截
  • helpers --->帮助文件,主要是一些过滤路由的方法
  • modules --->静态路由
  • routes --->常量路由
  • index --->入口

从routes引入常量路由,比如登录、未匹配的页面等。我们首先透过import.meta.env里取出配置文件里的路由选择变量,根据变量判断使用hash还是history

//index.tsimport { App } from 'vue';import { createRouter, createWebHashHistory, createWebHistory, RouteRecordRaw } from 'vue-router';import { LoginRoute, RootRoute, PathMatchRoute } from './routes';import { createRouterGuard } from './guard';/** 静态路由 */export const constantRoutes: RouteRecordRaw[] = [LoginRoute, RootRoute, PathMatchRoute];const { VITE_ROUTE_HASH = 'Y' } = import.meta.env;const router = createRouter({  history: VITE_ROUTE_HASH === 'Y' ? createWebHashHistory() : createWebHistory(),  routes: constantRoutes,  scrollBehavior: () => ({ left: 0, top: 0 })});export async function setupRouter(app: App) {  app.use(router);  createRouterGuard(router);  await router.isReady();}export default router;


上面我们只是加载了常态路由,对于其它路由我们采用router.beforeEach通过routeStore里的initRoute函数加载路由,关于路由拦截下面的注释写的还算清楚了

// guard.tsimport type { Router } from 'vue-router';import NProgress from '@/utils/nprogress';import { useUserStore, useRouteStore } from '@/store';const whiteList = ['/login'];/** * 路由守卫函数 * @param router - 路由实例 */export function createRouterGuard(router: Router) {  const userStore = useUserStore();  const routeStore = useRouteStore();  router.beforeEach(async (to, from, next) => {    NProgress.start();    // 访问登录页,有token不做跳转,没有跳转登录页    if (to.path === '/login') {      if (userStore.isLogin) return next(from.fullPath);      return next();    }    // 白名单放行    if (whiteList.includes(to.path)) return next();    // token不存在,跳登录页    if (!userStore.isLogin) return next({ path: '/login', query: { redirect: to.fullPath } });    // 未初始化路由,等待执行    if (!routeStore.isInitRoute) {      routeStore.initRoute();      return next({ path: to.fullPath, replace: true, query: to.query, hash: to.hash });    }    // 匹配到未知路径,跳404    if (to.name === 'not-found') return next({ path: '/error' });    // 默认放行    next();  });  router.afterEach(() => {    NProgress.done();  });}


上面的文件里使用到了userStore和routeStore,那么就需要配置pinia了,当然你也可以不用pinia,对于vue3来说你可以使用一个响应式充当全局的变量,在本项目中我们需要使用pinia

pinia

我们需要在根目录下新建这样的结构,将模块化归于一个文件中统一暴露出来

pinia的store有两种配置方式,我们这里采用下面的这种方式,结构清晰。initStaticRoute里通过一个filterRoutesByRole函数根据角色来过滤出对应的路由,过滤出来的路由通过遍历使用addRoute追加,无论是静态的路由方式还是动态的路由方式最后其实都是通过addRoute最加进去的

import router from '@/router';import { useUserStore } from './user';import { asyncRouter } from '@/router/modules';import { filterRoutesByRole, filterRoutesToMenus } from '@/router/helpers';interface IRouteState {  /** 权限路由的模式(static|dynamic) */  routeMode: ImportMetaEnv['VITE_ROUTE_MODE'];  /** 是否初始化权限路由的生成 */  isInitRoute: boolean;  /** 菜单渲染数据 */  menus: App.Menu[];  /** 缓存的路由 */  cacheList: string[];}export const useRouteStore = defineStore({  id: 'route',  state: (): IRouteState => ({    routeMode: import.meta.env.VITE_ROUTE_MODE,    isInitRoute: false,    menus: [],    cacheList: []  }),  getters: {},  actions: {    /** 初始化权限路由 */    initRoute() {      if (this.routeMode === 'static') {        this.initStaticRoute();      } else {        this.initDynamicRoute();      }    },    /** 静态权限路由 */    initStaticRoute() {      const userStore = useUserStore();      const routes = filterRoutesByRole(asyncRouter, userStore.userInfo.role);      routes.forEach((route) => {        route.children?.length ? router.addRoute(route) : router.addRoute('root', route);      });      const menus = filterRoutesToMenus(routes);      this.setMenus(menus);      this.isInitRoute = true;    },    /** 动态权限路由 */    initDynamicRoute() {},    /** 设置菜单 */    setMenus(menus: App.Menu[]) {      this.menus = menus;    }  }});


汇总集成

上面我们举例了vite配置、store配置、router配置,但我们还没有把他们集成到项目上去。将build/index的setupVitePlugins、store/index的setupStore、router/index的setupRouter,将三个页面的暴露函数引入到main.ts里

import { createApp } from 'vue';import App from './App.vue';import install from './plugins';import { setupRouter } from './router';import { setupStore } from './store';async function setupApp() {  const app = createApp(App);  // 插件注册&资源引入  install(app);  // 引入pinia  setupStore(app);  // 引入vue-router  await setupRouter(app);  app.mount('#app');}void setupApp();


这样看起来,是不是整个页面都简单化了许多,最近事情有点多,又在忙着考试没有更新,下一篇就开始介绍本项目的布局结构


作者:小心海一拳打爆提瓦特
链接:
https://juejin.cn/post/7292416512332972086