本文最后更新于:3 天前
框架构建 安装 | Vue CLI (vuejs.org)
Arco Design Vue
1 2 3 4 5 6 7 8 9 10 11 12 13 <template> <BasicLayout /> </template> <style></style> <script> import BasicLayout from "@/layouts/BasicLayout"; export default { components: { BasicLayout }, }; </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template> <GlobalHeader /> </template> <script> import GlobalHeader from "@/components/GlobalHeader"; export default { name: "BasicLayout", components: { GlobalHeader }, }; </script> <style scoped></style>
1 2 3 4 5 6 7 8 9 10 11 12 <template> <div></div> </template> <script> export default { name: "GlobalHeader", }; </script> <style scoped></style>
路由跳转 router 下的 index.ts:
1 2 3 4 5 6 7 8 9 10 import { createRouter, createWebHistory } from "vue-router" ;import { routes } from "@/router/routes" ;const router = createRouter ({ history : createWebHistory (process.env .BASE_URL ), routes, });export default router;
router 下的 router.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { RouteRecordRaw } from "vue-router" ;import HomeView from "@/views/HomeView.vue" ;export const routes : Array <RouteRecordRaw > = [ { path : "/" , name : "首页" , component : HomeView , }, { path : "/about" , name : "关于" , component : () => import ( "../views/AboutView.vue" ), }, ];
动态导航栏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <a-menu mode="horizontal" :selected-keys="selectedKeys" @menu-item-click="doMenuClick" > <a-menu-item key="0" :style="{ padding: 0, marginRight: '38px' }" disabled > <div class="title-bar"> <img class="logo" src="../assets/oj-logo.svg" /> <div class="title">OJ</div> </div> </a-menu-item> <a-menu-item v-for="item in routes" :key="item.path"> {{ item.name }} </a-menu-item> </a-menu>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { ref } from "vue" ;import { useRouter } from "vue-router" ;import { routes } from "@/router/routes" ;const router = useRouter ();const selectedKeys = ref (["/" ]); router.afterEach ((to, from , failure ) => { selectedKeys.value = [to.path ]; });const doMenuClick = (key: string ) => { router.push ({ path : key, }); };
效果如下:
全局状态管理 vuex/examples/classic/shopping-cart/store/index.js at main · vuejs/vuex (github.com)
开始 | Vuex (vuejs.org)
store 下的 user.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { StoreOptions } from "vuex" ;export default { namespaced : true , state : () => ({ loginUser : { userName : "未登录" , }, }), actions : { async getLoginUser ({ commit, state }, payload ) { commit ("updateUser" , { userName : "memory" }); }, }, mutations : { updateUser (state, payload ) { state.loginUser = payload; }, }, } as StoreOptions <any >;
index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 import { createStore } from "vuex" ;import user from "@/store/user" ;export default createStore ({ state : {}, getters : {}, mutations : {}, actions : {}, modules : { user, }, });
main.ts
1 2 3 4 import store from "./store" ;createApp (App ).use (ArcoVue ).use (store).use (router).mount ("#app" );
store 原理(执行流程)
1 2 3 4 5 6 setTimeout (() => { store.dispatch ("user/getLoginUser" , { userName : "回忆如初" , }); }, 3000 );
这样引入 store,执行 dispatch方法,根据 actions 下的路径,提供参数,执行 mutations 下的方法。
1 2 3 4 5 actions : { async getLoginUser ({ commit, state }, payload ) { commit ("updateUser" , payload); }, },
1 2 3 4 5 mutations : { updateUser (state, payload ) { state.loginUser = payload; }, },
mutations 改变了 state 的值,根据传入的参数改变了。
我们尝试在页面获取 store 值,并展示:
1 2 3 4 5 <a-col flex="100px"> <div> {{ store.state.user?.loginUser?.userName ?? "未登录" }} </div> </a-col>
效果如下:
另外,action 这里可以写死,不接受参数:
1 2 3 4 5 actions : { async getLoginUser ({ commit, state }, payload ) { commit ("updateUser" , { userName : "memory" }); }, },
全局权限管理 关键在于这段逻辑,App.vue 下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import router from "@/router" ;import store from "@/store" ; router.beforeEach ((to, from , next ) => { if (to.meta ?.access === "canAdmin" ) { if (store.state .user ?.loginUser ?.role !== "admin" ) { next ("/noAuth" ); return ; } } next (); });
设置路由访问权限,必须为管理员可见:
1 2 3 4 5 6 7 8 { path : "/auth" , name : "管理员可见" , component : AuthView , meta : { access : "canAdmin" , }, },
默认用户权限,测试用:
1 2 3 4 5 6 state : () => ({ loginUser : { userName : "未登录" , role : "admin" , }, }),
我们发现能正常跳转页面,但这样做:
1 2 3 4 5 6 7 setTimeout (() => { store.dispatch ("user/getLoginUser" , { userName : "回忆如初" , role : "noAdmin" , }); }, 3000 );
三秒过后,该用户不是管理员权限,访问一个管理员可见的页面,直接重定向:
隐藏菜单 构造通用的导航栏组件,根据配置控制菜单栏的显隐
1 2 3 4 5 6 7 8 { path : "/hide" , name : "隐藏页面" , component : noAuthView, meta : { hideInMenu : true , }, },
过滤,仅展示显示在菜单上的路由数组
1 2 3 <a-menu-item v-for="item in visibleRoutes" :key="item.path"> {{ item.name }} </a-menu-item>
1 2 3 4 5 6 import { routes } from "@/router/routes" ;const visibleRoutes = routes.filter ((item, index ) => { return !item.meta ?.hideInmenu ; });
除了 根据配置权限隐藏菜单 ,还需要根据用户权限,只有具有相关权限的用户,才能看到该菜单。
检测用户权限:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import ACCESS_ENUM from "@/access/accessEnum" ;const checkAccess = (loginUser: any , needAccess = ACCESS_ENUM.NOT_LOGIN ) => { const loginUserAccess = loginUser?.userRole ?? ACCESS_ENUM .NOT_LOGIN ; if (needAccess === ACCESS_ENUM .NOT_LOGIN ) { return true ; } if (needAccess === ACCESS_ENUM .USER ) { if (loginUserAccess === ACCESS_ENUM .NOT_LOGIN ) { return false ; } } if (needAccess === ACCESS_ENUM .ADMIN ) { if (loginUserAccess !== ACCESS_ENUM .ADMIN ) { return false ; } } return true ; };export default checkAccess;
使用计算属性,使得用户信息发生变更时,触发菜单栏的重新渲染,。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const visibleRoutes = computed (() => { return routes.filter ((item, index ) => { if (item.meta ?.hideInmenu ) { return false ; } if ( !checkAccess (store.state .user ?.loginUser , item.meta ?.access as string ) ) { return false ; } return true ; }); });
全局项目入口 1 2 3 4 5 6 7 const doInit = ( ) => { console .log ("项目全局入口" ); };onMounted (() => { doInit (); });
根据后端生成接口 后端接口文档:
我们根据后端接口文档,一键生成前端 HTTP 请求接口:
官方文档:
ferdikoomen/openapi-typescript-codegen: NodeJS library that generates Typescript or Javascript clients based on the OpenAPI specification (github.com)
安装:
1 npm install openapi-typescript-codegen --save-dev
执行命令生成代码:
1 openapi --input http://localhost:8121/api/v2/api-docs?group=memory-oj --output ./generated --client axios
如上,执行成功,成功生成 HTTP 请求接口
MarkDown 编辑器 代码编辑器