上一篇我们已经把基础的工程化搭建了,下面就是集成项目所需要的插件,比如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();
});
}
上面的文件里使用到了userStore和routeStore,那么就需要配置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();
这样看起来,是不是整个页面都简单化了许多,最近事情有点多,又在忙着考试没有更新,下一篇就开始介绍本项目的布局结构