Browse Source

💄 Layout组件 路由

晓晓晓晓丶vv 4 years ago
parent
commit
8837f4cfd8

+ 0 - 4
src/App.vue

@@ -1,8 +1,4 @@
 <template>
-  <div id="nav">
-    <router-link to="/">Home</router-link> |
-    <router-link to="/about">About</router-link>
-  </div>
   <a-config-provider :locale="locale">
     <router-view />
   </a-config-provider>

+ 0 - 30
src/helper/cache.ts

@@ -1,30 +0,0 @@
-export default class CacheMap {
-  private maxLength: number;
-  private map: Map<string, any>;
-
-  constructor(max: number) {
-    this.maxLength = max || 50;
-    this.map = new Map();
-  }
-
-  get<T>(key: string): T | null {
-    if (this.map.has(key)) {
-      let mapVal = this.map.get(key);
-      this.map.delete(key);
-      this.map.set(key, mapVal);
-      return mapVal;
-    } else return null;
-  }
-
-  put<T>(key: string, val: T) {
-    if (this.map.has(key)) this.map.delete(key);
-    else {
-      // 达到最大长度时删除不常用的key
-      if (this.map.size === this.maxLength) {
-        let notCommandMap = this.map.keys().next().value;
-        this.map.delete(notCommandMap);
-      }
-    }
-    this.map.set(key, val);
-  }
-}

+ 27 - 0
src/helper/index.ts

@@ -1,3 +1,11 @@
+const path = require("path");
+
+/**
+ * 事件监听
+ * @param el 监听dom
+ * @param cb 回调
+ * @param type 监听事件 默认click
+ */
 export const listener = (
   el: HTMLElement | Element,
   cb: Function,
@@ -5,3 +13,22 @@ export const listener = (
 ) => {
   el.addEventListener(type, (e) => cb(e), false);
 };
+
+/**
+ * 判断url
+ * @param url
+ */
+export const validateURL = (url: string) => {
+  return /^(https?:|mailto:|tel:)/.test(url);
+};
+
+/**
+ * url处理
+ * @param paths
+ * @param base
+ */
+export const pathResolve = (paths: string, base: string = "/") => {
+  if (validateURL(paths)) return paths;
+  if (validateURL(base)) return base;
+  return path.resolve(base, paths);
+};

+ 64 - 0
src/helper/permission.ts

@@ -0,0 +1,64 @@
+const path = require("path");
+import { RouteConfig } from "@/types/route";
+
+/**
+ * 通过用户角色过滤有效路由
+ * @param routes
+ * @param roles
+ */
+export const filterRoutesByRoles = (
+  routes: Array<RouteConfig>,
+  roles: Array<string>
+): Array<RouteConfig> => {
+  const result: Array<RouteConfig> = [];
+  routes.forEach((route) => {
+    const temp = { ...route };
+    if (checkRoutePermission(roles, temp)) {
+      if (temp.children)
+        temp.children = filterRoutesByRoles(temp.children, roles);
+      result.push(temp);
+    }
+  });
+
+  return result;
+};
+
+/**
+ * 判断目标路由是否符合当前用户权限
+ * @param roles
+ * @param route
+ */
+export const checkRoutePermission = (
+  roles: Array<string>,
+  route: RouteConfig
+): boolean => {
+  if (route.meta?.roles)
+    return roles.some((role) => route.meta?.roles?.includes(role));
+  return true;
+};
+
+/**
+ * 格式化菜单路由
+ * @param routes
+ */
+export const formatNavRoutes = (routes: RouteConfig[]) => {
+  const validRoutes = routes.filter((route) => !route.hidden);
+  const resultRoutes: RouteConfig[] = [];
+  validRoutes.forEach((route) => {
+    let tempRoute = { ...route };
+    if (tempRoute.children) {
+      if (tempRoute.children.length === 1) {
+        // 如果路由长度只有1 则提取出来当根路由
+        const children = tempRoute.children[0];
+        tempRoute = {
+          ...route,
+          meta: children.meta,
+          path: path.resolve(tempRoute.path, children.path),
+        };
+        delete tempRoute.children;
+      } else tempRoute.children = formatNavRoutes(tempRoute.children);
+    }
+    resultRoutes.push(tempRoute);
+  });
+  return resultRoutes;
+};

+ 12 - 0
src/hooks/useApp.ts

@@ -0,0 +1,12 @@
+import useStore from "./useStore";
+import { useRoute, useRouter } from "vue-router";
+
+const useApp = () => {
+  const store = useStore();
+  const route = useRoute();
+  const router = useRouter();
+
+  return { store, route, router };
+};
+
+export default useApp;

+ 1 - 1
src/hooks/useClipboard.ts

@@ -3,7 +3,7 @@ import { inject } from "vue";
 
 const useClipboard = () => {
   const { $copyText } = inject(clipboardKey)!;
-  return $copyText;
+  return { $copyText };
 };
 
 export default useClipboard;

+ 42 - 0
src/layout/components/AppHeader.vue

@@ -0,0 +1,42 @@
+<template>
+  <a-layout-header class="web-header">
+    <span style="color: #fff">头部</span>
+    <!-- logo -->
+    <!-- <router-link to="/"
+                 custom
+                 v-slot="{navigate}">
+      <div class="cursor logo-wrap"
+           @click="navigate">
+        <img src="@/assets/logo.png"
+             alt="">
+      </div>
+    </router-link> -->
+    <!-- 站点搜索/添加 -->
+    <!-- <app-header-channel v-model:channel="current_site" /> -->
+    <!-- 用户操作 -->
+    <!-- <app-header-user /> -->
+  </a-layout-header>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref } from "vue";
+
+import AppHeaderChannel from "./AppHeaderChannel.vue";
+import AppHeaderUser from "./AppHeaderUser.vue";
+
+const AppHeader = defineComponent({
+  components: {
+    AppHeaderChannel,
+    AppHeaderUser,
+  },
+  setup() {
+    let current_site = ref("jack");
+
+    return {
+      current_site,
+    };
+  },
+});
+
+export default AppHeader;
+</script>

+ 64 - 0
src/layout/components/AppHeaderChannel.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="channel-operator">
+    <SearchOutlined />
+    <a-select class="channel-filter"
+              show-search
+              :value="channel"
+              @change="onChannelChange">
+      <a-select-option value="jack">
+        Jack
+      </a-select-option>
+      <a-select-option value="lucy">
+        Lucy
+      </a-select-option>
+      <a-select-option value="tom">
+        Tom
+      </a-select-option>
+    </a-select>
+    <a-popconfirm placement="bottomLeft"
+                  @confirm="onConfirmCreate">
+      <template v-slot:title>
+        <span>确定创建<i class="theme">新站点</i>吗?</span>
+      </template>
+      <a-button type="primary"
+                shape="circle">
+        <template v-slot:icon>
+          <PlusOutlined />
+        </template>
+      </a-button>
+    </a-popconfirm>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, watch } from "vue";
+
+import { SearchOutlined, PlusOutlined } from "@ant-design/icons-vue";
+
+const AppHeaderChannel = defineComponent({
+  components: {
+    SearchOutlined,
+    PlusOutlined,
+  },
+  props: {
+    channel: String,
+  },
+  setup(props, { emit }) {
+    const onConfirmCreate = () => {
+      console.log("确认创建新站点");
+    };
+
+    const onChannelChange = (value: string) => {
+      console.log(value);
+      emit("update:channel", value);
+    };
+
+    return {
+      onConfirmCreate,
+      onChannelChange,
+    };
+  },
+});
+
+export default AppHeaderChannel;
+</script>

+ 58 - 0
src/layout/components/AppHeaderUser.vue

@@ -0,0 +1,58 @@
+<template>
+  <a-popover trigger="click">
+    <template v-slot:content>
+      <div class="setting-group">
+        <span>
+          <BlockOutlined />站点转移
+        </span>
+        <span>
+          <LockOutlined />修改密码
+        </span>
+        <span>
+          <SearchOutlined />找回密码
+        </span>
+        <span>
+          <CreditCardOutlined />银行卡管理
+        </span>
+        <span>
+          <PoweroffOutlined />安全退出
+        </span>
+      </div>
+    </template>
+    <div class="user-wrap">
+      <UserOutlined />
+      <p class="user">ID:1234</p>
+      <CaretDownFilled />
+    </div>
+  </a-popover>
+</template>
+
+<script lang="ts">
+import { defineComponent } from "vue";
+import {
+  SearchOutlined,
+  UserOutlined,
+  CaretDownFilled,
+  BlockOutlined,
+  CreditCardOutlined,
+  LockOutlined,
+  PoweroffOutlined,
+} from "@ant-design/icons-vue";
+
+const AppHeaderUser = defineComponent({
+  components: {
+    SearchOutlined,
+    UserOutlined,
+    CaretDownFilled,
+    BlockOutlined,
+    CreditCardOutlined,
+    LockOutlined,
+    PoweroffOutlined,
+  },
+  setup() {
+    return {};
+  },
+});
+
+export default AppHeaderUser;
+</script>

+ 37 - 0
src/layout/components/AppLinks.vue

@@ -0,0 +1,37 @@
+<template>
+  <div>
+    <template v-if="is_href_link">
+      <a :href="url"
+         target="_blank"
+         rel="noopener">
+        <slot />
+      </a>
+    </template>
+    <template v-else>
+      <router-link class="menu-link"
+                   :to="url">
+        <slot />
+      </router-link>
+    </template>
+  </div>
+</template>
+
+<script lang="ts">
+import { validateURL } from "@/helper";
+import { computed, defineComponent } from "vue";
+
+const AppLink = defineComponent({
+  props: {
+    url: String,
+  },
+  setup(props) {
+    const is_href_link = computed(() => validateURL(props.url ?? ""));
+
+    return {
+      is_href_link,
+    };
+  },
+});
+
+export default AppLink;
+</script>

+ 112 - 0
src/layout/index.vue

@@ -0,0 +1,112 @@
+<template>
+  <a-layout class="web-wrapper">
+    <app-header></app-header>
+    <a-layout>
+      <a-layout-sider v-if="roles.length"
+                      class="web-side"
+                      theme="light"
+                      :width="240">
+        <a-menu class="web-menu"
+                mode="inline"
+                theme="dark"
+                v-model:openKeys="openKeys"
+                :selected-keys="menuActive">
+          <template v-for="route in filterRoutes">
+            <template v-if="route.children && route.children.length">
+              <a-sub-menu class="nav-item"
+                          :key="route.path"
+                          :title="route.meta.title">
+                <router-link v-for="child in route.children"
+                             :key="formatRoutes(child.path, route.path)"
+                             :to="formatRoutes(child.path, route.path)"
+                             custom
+                             v-slot={navigate}>
+                  <a-menu-item>
+                    <p @click.stop="navigate">{{child.meta.title}}</p>
+                  </a-menu-item>
+                </router-link>
+              </a-sub-menu>
+            </template>
+            <template v-else>
+              <router-link :key="route.path"
+                           :to="route.path"
+                           custom
+                           v-slot="{navigate}">
+                <a-menu-item>
+                  <p @click.stop="navigate">{{route.meta.title}}</p>
+                </a-menu-item>
+              </router-link>
+            </template>
+          </template>
+        </a-menu>
+      </a-layout-sider>
+      <a-layout-content class="web-container">
+        <router-view v-slot="{ Component }">
+          <transition name="fade-transform"
+                      mode="out-in">
+            <component :is="Component" />
+          </transition>
+        </router-view>
+      </a-layout-content>
+    </a-layout>
+  </a-layout>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, reactive, ref, toRefs } from "vue";
+
+import appHeader from "./components/AppHeader.vue";
+
+import useApp from "@/hooks/useApp";
+import { pathResolve } from "@/helper/index";
+import { formatNavRoutes } from "@/helper/permission";
+
+import { RouteConfig } from "@/types/route";
+
+const LayoutComponent = defineComponent({
+  components: {
+    appHeader,
+  },
+  setup() {
+    const { store, route, router } = useApp();
+
+    // 待优化
+    let openKeys = ref(["/" + route.path.substr(1).split("/")[0]]);
+
+    const data = reactive({
+      navRoutes: computed(() => store.getters.permissionRoutes),
+      roles: computed(() => store.getters.userRoles),
+      menuActive: computed(() => {
+        const {
+          meta: { activeMenu },
+          path,
+          name,
+        } = route;
+        if (activeMenu) return [activeMenu];
+        return [path];
+      }),
+    });
+
+    const filterRoutes = computed(() =>
+      formatNavRoutes(data.navRoutes as RouteConfig[])
+    );
+
+    const formatRoutes = (path: string, base: string = "/") =>
+      pathResolve(path, base);
+
+    const formatIconPath = (icon: string) => {
+      const icon_url = require("@/assets/" + icon);
+      return icon_url;
+    };
+
+    return {
+      ...toRefs(data),
+      openKeys,
+      filterRoutes,
+      formatRoutes,
+    };
+  },
+});
+
+export default LayoutComponent;
+</script>

+ 3 - 0
src/main.ts

@@ -1,3 +1,6 @@
+import "@/scss/index.scss";
+import "@/router/permission";
+
 import { createApp } from "vue";
 import App from "./App.vue";
 import router from "./router";

+ 6 - 2
src/plugins/install.ts

@@ -1,5 +1,5 @@
 import { App } from "vue";
-import { Button, ConfigProvider, Modal } from "ant-design-vue";
+import { Button, ConfigProvider, Layout, Menu, Modal } from "ant-design-vue";
 
 import { ModalConfirmKey } from "./injectionKey";
 
@@ -7,7 +7,11 @@ const install = (app: App<Element>) => {
   app.provide(ModalConfirmKey, Modal.confirm);
   app.config.globalProperties.$confirm = Modal.confirm;
 
-  return app.use(ConfigProvider).use(Button);
+  return app
+    .use(ConfigProvider)
+    .use(Layout)
+    .use(Menu)
+    .use(Button);
 };
 
 export default install;

+ 65 - 0
src/router/async.ts

@@ -0,0 +1,65 @@
+import { RouteConfig } from "@/types/route";
+
+export const Page1: RouteConfig = {
+  name: "Page1",
+  path: "/page1",
+  meta: {
+    title: "Page 1",
+    roles: ["admin"],
+  },
+  component: () => import("@/views/Page1.vue"),
+};
+
+export const Page2: RouteConfig = {
+  name: "Page2",
+  path: "/page2",
+  meta: {
+    title: "Page 2",
+    roles: ["admin"],
+  },
+  children: [
+    {
+      name: "Page3",
+      path: "page3",
+      meta: {
+        title: "Page 3",
+        roles: ["admin"],
+      },
+      component: () => import("@/views/Page3.vue"),
+    },
+    {
+      name: "Page4",
+      path: "page4",
+      meta: {
+        title: "Page 4",
+        roles: ["admin"],
+      },
+      component: () => import("@/views/Page4.vue"),
+    },
+  ],
+  component: () => import("@/views/Page2.vue"),
+};
+
+export const Page3: RouteConfig = {
+  name: "Page3",
+  path: "/page3",
+  meta: {
+    title: "Page 3",
+    roles: ["admin"],
+  },
+  component: () => import("@/views/Page3.vue"),
+};
+
+export const Page4: RouteConfig = {
+  name: "Page4",
+  path: "/page4",
+  meta: {
+    title: "Page 4",
+    roles: ["admin"],
+  },
+  component: () => import("@/views/Page4.vue"),
+};
+
+const asyncRoutes: RouteConfig[] = [Page1, Page2];
+
+export default asyncRoutes;

+ 6 - 20
src/router/index.ts

@@ -1,25 +1,11 @@
-import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
-import Home from '../views/Home.vue'
+import { createRouter, createWebHistory } from "vue-router";
+import constantRoutes from "./modules/constant";
 
-const routes: Array<RouteRecordRaw> = [
-  {
-    path: '/',
-    name: 'Home',
-    component: Home
-  },
-  {
-    path: '/about',
-    name: 'About',
-    // route level code-splitting
-    // this generates a separate chunk (about.[hash].js) for this route
-    // which is lazy-loaded when the route is visited.
-    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
-  }
-]
+const routes = constantRoutes;
 
 const router = createRouter({
   history: createWebHistory(process.env.BASE_URL),
-  routes
-})
+  routes,
+});
 
-export default router
+export default router;

+ 25 - 0
src/router/modules/constant.ts

@@ -0,0 +1,25 @@
+import { RouteConfig } from "@/types/route";
+
+export const Home: RouteConfig = {
+  name: "Home",
+  path: "/home",
+  hidden: true,
+  meta: {
+    title: "首页",
+  },
+  component: () => import("@/views/Home.vue"),
+};
+
+export const About: RouteConfig = {
+  name: "About",
+  path: "/about",
+  hidden: true,
+  meta: {
+    title: "关于我",
+  },
+  component: () => import("@/views/About.vue"),
+};
+
+const constantRoutes: RouteConfig[] = [Home, About];
+
+export default constantRoutes;

+ 15 - 0
src/router/modules/index.ts

@@ -0,0 +1,15 @@
+import { RouteRecordRaw } from "vue-router";
+
+// 获取所有路由模块
+const routes = require.context(".", false, /\.ts$/);
+
+let configRoutes: RouteRecordRaw[] = [];
+
+// 合并所有模块
+routes.keys().forEach((key) => {
+  // 过滤掉主入口文件
+  if (key === "./index.ts") return;
+  configRoutes = configRoutes.concat(routes(key).default);
+});
+
+export default configRoutes;

+ 97 - 0
src/router/permission.ts

@@ -0,0 +1,97 @@
+import { NavigationGuardNext, RouteLocationNormalized } from "vue-router";
+
+import router from "./index";
+import store from "@/store";
+
+import Layout from "@/layout/index.vue";
+
+import { RouteConfig } from "@/types/route";
+import { ActionType } from "@/store/modules/app/actions";
+
+const WHITE_URL_LIST: string[] = ["/login", "/forget"];
+
+/**
+ * 权限动态路由合并
+ * TODO 外部封装next函数有问题
+ * @param to
+ * @param next
+ */
+const generatorRoutes = async (
+  to: RouteLocationNormalized,
+  next: NavigationGuardNext
+) => {
+  const roles = store.getters["system/userRoles"];
+  if (roles.length) next();
+  else {
+    const userRoles: string[] = await store.dispatch("system/getUserRoles");
+    const webRoutes: RouteConfig[] = await store.dispatch(
+      "system/generatorRoutes",
+      userRoles
+    );
+    const AppRoute: RouteConfig = {
+      name: "home",
+      path: "/",
+      redirect: webRoutes[0].path,
+      component: Layout,
+      children: webRoutes,
+    };
+    router.addRoute(AppRoute);
+    next({ ...to, replace: true, name: to.name! });
+  }
+};
+
+/**
+ * 未登录的情况下 路由的处理
+ * @param to
+ * @param next
+ */
+const redirectRoutes = (
+  to: RouteLocationNormalized,
+  next: NavigationGuardNext
+) => {
+  if (!!~WHITE_URL_LIST.indexOf(to.path)) next();
+  else
+    next({
+      path: "/login",
+      query: {
+        redirect: encodeURIComponent(to.path),
+      },
+    });
+};
+
+router.beforeEach(
+  async (
+    to: RouteLocationNormalized,
+    _: RouteLocationNormalized,
+    next: NavigationGuardNext
+  ) => {
+    if (to.path === "/login") next({ path: "/", replace: true });
+    else {
+      const roles = store.state.app.roles;
+      if (roles.length) next();
+      else {
+        const userRoles: string[] = await store.dispatch(
+          ActionType.GET_USER_ROLES
+        );
+        const webRoutes: RouteConfig[] = await store.dispatch(
+          ActionType.INIT_ROUTES,
+          userRoles
+        );
+        const AppRoute: RouteConfig = {
+          name: "home",
+          path: "",
+          redirect: webRoutes[0].path,
+          component: Layout,
+          children: webRoutes,
+        };
+        router.addRoute(AppRoute);
+        next({ ...to, replace: true, name: to.name! });
+      }
+    }
+    //  redirectRoutes(to, next);
+  }
+);
+
+router.afterEach(() => {
+  // NProgress.done();
+});

+ 31 - 0
src/scss/antd.scss

@@ -8,3 +8,34 @@
     margin-bottom: 0;
   }
 }
+
+.ant-menu-vertical > .ant-menu-item,
+.ant-menu-vertical-left > .ant-menu-item,
+.ant-menu-vertical-right > .ant-menu-item,
+.ant-menu-inline > .ant-menu-item,
+.ant-menu-vertical > .ant-menu-submenu > .ant-menu-submenu-title,
+.ant-menu-vertical-left > .ant-menu-submenu > .ant-menu-submenu-title,
+.ant-menu-vertical-right > .ant-menu-submenu > .ant-menu-submenu-title,
+.ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title {
+  height: 60px !important;
+  line-height: 60px !important;
+}
+
+.ant-menu-vertical .ant-menu-item:not(:last-child),
+.ant-menu-vertical-left .ant-menu-item:not(:last-child),
+.ant-menu-vertical-right .ant-menu-item:not(:last-child),
+.ant-menu-inline .ant-menu-item:not(:last-child) {
+  margin-bottom: 0 !important;
+}
+
+.ant-menu-vertical .ant-menu-item,
+.ant-menu-vertical-left .ant-menu-item,
+.ant-menu-vertical-right .ant-menu-item,
+.ant-menu-inline .ant-menu-item,
+.ant-menu-vertical .ant-menu-submenu-title,
+.ant-menu-vertical-left .ant-menu-submenu-title,
+.ant-menu-vertical-right .ant-menu-submenu-title,
+.ant-menu-inline .ant-menu-submenu-title {
+  margin-top: 0 !important;
+  margin-bottom: 0 !important;
+}

+ 4 - 0
src/scss/index.scss

@@ -4,6 +4,10 @@
 // @import "./components/index.scss";
 // @import "./views/index.scss";
 
+p {
+  margin-bottom: 0 !important;
+}
+
 .clearfix {
   &:after {
     visibility: hidden;

+ 20 - 8
src/store/modules/app/_type.ts

@@ -1,21 +1,33 @@
-import CacheMap from "@/helper/cache";
 import { ActionType } from "./actions";
 import { MutationType } from "./mutations";
 import { AugmentedActionContext } from "./_module";
+import { RouteConfig } from "@/types/route";
+import { RouteRecordRaw } from "vue-router";
 
-type State = {};
+type State = {
+  roles: string[];
+  routes: RouteConfig[];
+  addRoutes: RouteConfig[];
+};
 
 type Mutations<S = State> = {
-  // [MutationType.SET_AGE_VALUE](state: S, age: number): void;
+  [MutationType.SET_ROLES](state: S, roles: string[]): void;
+  [MutationType.SET_ROUTES](state: S, routes: RouteConfig[]): void;
 };
 
 type Actions = {
-  // [ActionType.SET_AGE_VALUE](
-  //   { commit }: AugmentedActionContext,
-  //   age: number
-  // ): void;
+  [ActionType.INIT_ROUTES](
+    { commit }: AugmentedActionContext,
+    roles: string[]
+  ): Promise<RouteConfig[]>;
+  [ActionType.GET_USER_ROLES]({
+    commit,
+  }: AugmentedActionContext): Promise<string[]>;
 };
 
-type Getters = {};
+type Getters = {
+  permissionRoutes(state: State): RouteRecordRaw[];
+  userRoles(state: State): string[];
+};
 
 export { State, Mutations, Actions, Getters };

+ 24 - 15
src/store/modules/app/actions.ts

@@ -1,23 +1,32 @@
 import { ActionTree } from "vuex";
-import { RootState } from "@/store";
-import { AugmentedActionContext } from "./_module";
-import { State } from "./_type";
+import { Actions, State } from "./_type";
 import { MutationType } from "./mutations";
+import { RootState } from "@/store";
+import asyncRoutes from "@/router/async";
+import { filterRoutesByRoles } from "@/helper/permission";
 
-export enum ActionType {}
-// SET_AGE_VALUE = "SET_AGE_VALUE",
-
-export type Actions = {
-  // [ActionType.SET_AGE_VALUE](
-  //   { commit }: AugmentedActionContext,
-  //   age: number
-  // ): void;
-};
+export enum ActionType {
+  INIT_ROUTES = "generatorRoutes",
+  GET_USER_ROLES = "getUserRoles",
+}
 
 const actions: ActionTree<State, RootState> & Actions = {
-  // [ActionType.SET_AGE_VALUE]({ commit }, age: number) {
-  //   commit(MutationType.SET_AGE_VALUE, age);
-  // },
+  [ActionType.INIT_ROUTES]({ commit }, roles) {
+    return new Promise(async (resolve) => {
+      let resultRoutes;
+      if (roles.includes("admin")) resultRoutes = asyncRoutes || [];
+      else resultRoutes = filterRoutesByRoles(asyncRoutes, roles) || [];
+      await commit(MutationType.SET_ROUTES, resultRoutes);
+      resolve(resultRoutes);
+    });
+  },
+  [ActionType.GET_USER_ROLES]({ commit }) {
+    return new Promise(async (resolve) => {
+      const roles = ["admin"];
+      await commit(MutationType.SET_ROLES, roles);
+      resolve(roles);
+    });
+  },
 };
 
 export default actions;

+ 3 - 6
src/store/modules/app/getters.ts

@@ -1,13 +1,10 @@
 import { GetterTree } from "vuex";
 import { RootState } from "@/store";
-import { State } from "./_type";
-
-export type Getters = {
-  // age(state: State): number;
-};
+import { Getters, State } from "./_type";
 
 const getters: GetterTree<State, RootState> & Getters = {
-  // age: (state) => state.age
+  permissionRoutes: (state) => state.routes,
+  userRoles: (state) => state.roles,
 };
 
 export default getters;

+ 11 - 10
src/store/modules/app/mutations.ts

@@ -1,17 +1,18 @@
 import { MutationTree } from "vuex";
-import { State } from "./_type";
+import { State, Mutations } from "./_type";
 
-export enum MutationType {}
-// SET_AGE_VALUE = "SET_AGE_VALUE",
-
-export type Mutations<S = State> = {
-  // [MutationType.SET_AGE_VALUE](state: S, age: number): void;
-};
+export enum MutationType {
+  SET_ROLES = "setRoles",
+  SET_ROUTES = "setRoutes",
+}
 
 const mutations: MutationTree<State> & Mutations = {
-  // [MutationType.SET_AGE_VALUE](state, age: number) {
-  //   state.age = age;
-  // },
+  [MutationType.SET_ROLES](state, roles) {
+    state.roles = roles;
+  },
+  [MutationType.SET_ROUTES](state, routes) {
+    state.routes = routes;
+  },
 };
 
 export default mutations;

+ 3 - 2
src/store/modules/app/state.ts

@@ -1,8 +1,9 @@
 import { State } from "./_type";
 
 const state: State = {
-  age: 12,
-  name: "张三",
+  roles: [],
+  routes: [],
+  addRoutes: [],
 };
 
 export default state;

+ 14 - 0
src/types/route.d.ts

@@ -0,0 +1,14 @@
+import { RouteRecordRaw } from "vue-router";
+
+type RouteMeta = {
+  title: string;
+  icon: string;
+  roles: string[];
+  activeMenu: string;
+};
+
+export type RouteConfig = RouteRecordRaw & {
+  hidden?: boolean;
+  children?: RouteConfig[];
+  meta?: Partial<RouteMeta>;
+};

+ 3 - 0
src/views/Page1.vue

@@ -0,0 +1,3 @@
+<template>
+  <div>page 1</div>
+</template>

+ 6 - 0
src/views/Page2.vue

@@ -0,0 +1,6 @@
+<template>
+  <div>
+    <div>page 2</div>
+    <router-view></router-view>
+  </div>
+</template>

+ 3 - 0
src/views/Page3.vue

@@ -0,0 +1,3 @@
+<template>
+  <div>page 3</div>
+</template>

+ 3 - 0
src/views/Page4.vue

@@ -0,0 +1,3 @@
+<template>
+  <div>page 4</div>
+</template>