上一篇我们已经把基础的工程化搭建了,下面就是集成项目所需要的插件,比如elementplus、unocss、mock之类的,因为我们使用的是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中,通过函数加载想要的插件。
下面说下这些子文件的内容
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等插件采用自己手动引用的方式,这里看个人习惯你也可以都自动引入
下面我们需要创建路由文件结构
下面说下这些子文件的内容
从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的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