前端框架搭建——从零到一搭建一个高颜值Vue3后台管理系统(二)

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

vite配置

js 复制代码
//vite.config.ts
import { 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 复制代码
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import unocss from '@unocss/vite';
import unplugin from './unplugin'; //UI&Icon
import 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

javascript 复制代码
//index.ts
import { 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函数加载路由,关于路由拦截下面的注释写的还算清楚了

php 复制代码
// guard.ts
import 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();
  });
}

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

pinia

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

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

import 复制代码
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

javascript 复制代码
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();

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

相关推荐
mldong3 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排4 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60614 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角5 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!5 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰6 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi7 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen8 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马8 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒9 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端