vue3-10

动态路由与菜单
路由文件

a6router.ts

TypeScript 复制代码
import { createRouter, createWebHashHistory } from "vue-router";
import { useStorage } from "@vueuse/core";
import { Route, Menu } from "../model/Model8080";
const clientRoutes = [
  {
    path: "/login",
    name: "login",
    component: () => import("../views/A6Login.vue"),
  },
  {
    path: "/404",
    name: "404",
    component: () => import("../views/A6NotFound.vue"),
  },
  {
    path: "/",
    name: "main",
    component: () => import("../views/A6Main.vue"),
  },
  {
    path: "/:pathMatcher(.*)*",
    name: "remaining",
    redirect: "/404",
  },
];

const router = createRouter({
  history: createWebHashHistory(),
  routes: clientRoutes,
});

export const serverMenus = useStorage<Menu[]>("serverMenus", []);
const serverRoutes = useStorage<Route[]>("serverRoutes", []);
addServerRoutes(serverRoutes.value);

export function addServerRoutes(routeList: Route[]) {
  for (const r of routeList) {
    if (r.parentName) {
      router.addRoute(r.parentName, {
        path: r.path,
        component: () => import(r.component),
        name: r.name,
      });
    }
  }
  serverRoutes.value = routeList;
}

export function resetRoutes() {
  for (const r of clientRoutes) {
    router.addRoute(r);
  }
  serverRoutes.value = null;
  serverMenus.value = null;
}

export default router;
TypeScript 复制代码
export interface Route {
  path: string;
  component: string;
  name: string;
  parentName: string;
}

export interface Menu {
  id: number;
  pid: number;
  title: string;
  icon?: string;
  routePath?: string;
  routeComponent?: string;
  routeName?: string;
  routeParentName?: string;
  children?: Menu[];
}

本文件重要的函数及变量

  • addServerRoutes 函数向路由表中添加由服务器提供的路由,路由分成两部分

    • clientRoutes 这是客户端固定的路由

    • serverRoutes 这是服务器变化的路由,存储于 localStorage

  • resetRoutes 函数用来将路由重置为 clientRoutes

    • vue-router@4 中的 addRoute 方法会【覆盖】同名路由,这是这种实现的关键

    • 因此,服务器返回的路由最好是 main 的子路由,这样重置时就会比较简单,用之前的 main 一覆盖就完事了

  • serverMenus 变量记录服务器变化的菜单,存储于 localStorage

登录组件

动态路由应当在登录时生成,A6Login.vue

TypeScript 复制代码
<template>
  <div class="login">
    <a-form :label-col="{ span: 6 }" autocomplete="off">
      <a-form-item label="用户名" v-bind="validateInfos.username">
        <a-input v-model:value="dto.username" />
      </a-form-item>
      <a-form-item label="密码" v-bind="validateInfos.password">
        <a-input-password v-model:value="dto.password" />
      </a-form-item>
      <a-form-item :wrapper-col="{ offset: 6, span: 16 }">
        <a-button type="primary" @click="onClick">Submit</a-button>
      </a-form-item>
    </a-form>
  </div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Form } from 'ant-design-vue'
import { useRouter } from 'vue-router'
import axios from '../api/request'
import { useRequest } from 'vue-request'
import { AxiosRespToken, LoginDto, AxiosRespMenuAndRoute } from '../model/Model8080'
import { resetRoutes, addServerRoutes, serverMenus } from '../router/a6router'
const dto = ref({username:'', password:''})
const rules = ref({
  username: [
    {required: true, message:'用户名必填'}
  ],
  password:[
    {required: true, message:'密码必填'}
  ]
})
const { validateInfos, validate } = Form.useForm(dto, rules)
const router = useRouter()
const { runAsync:login } = useRequest<AxiosRespToken, LoginDto[]>((dto)=> axios.post('/api/loginJwt', dto), {manual:true})
const { runAsync:menu } = useRequest<AxiosRespMenuAndRoute, string[]>((username)=> axios.get(`/api/menu/${username}`), {manual:true})
async function onClick() {
  try {
    await validate()
    const loginResp = await login(dto.value
    if(loginResp.data.code === 200) { // 登录成功
      const token = loginResp.data.data.token
      const menuResp = await menu(dto.value.username)
      const routeList = menuResp.data.data.routeList
      addServerRoutes(routeList)
      serverMenus.value = menuResp.data.data.menuTree
      router.push('/')
    })
  } catch (e) {
    console.error(e)
  }
}
onMounted(()=>{
  resetRoutes()
})
</script>
<style scoped>
.login {
  margin: 200px auto;
  width: 25%;
  padding: 20px;
  height: 180px;
  background-color: antiquewhite;
}
</style>
  • 登录成功后去请求 /api/menu/{username} 获取该用户的菜单和路由

  • router.push 方法用来以编程方式跳转至主页路由

主页组件

A6Main.vue

TypeScript 复制代码
<template>
  <div class="a6main">
    <a-layout>
      <a-layout-header> </a-layout-header>
      <a-layout>
        <a-layout-sider>
          <a-menu mode="inline" theme="dark">
            <template v-for="m1 of serverMenus">
              <a-sub-menu v-if="m1.children" :key="m1.id" :title="m1.title">
                <template #icon><a-icon :icon="m1.icon"></a-icon></template>
                <a-menu-item v-for="m2 of m1.children" :key="m2.id">
                  <template #icon><a-icon :icon="m2.icon"></a-icon></template>
                  <router-link v-if="m2.routePath" :to="m2.routePath">{{
                    m2.title
                  }}</router-link>
                  <span v-else>{{ m2.title }}</span>
                </a-menu-item>
              </a-sub-menu>
              <a-menu-item v-else :key="m1.id">
                <template #icon><a-icon :icon="m1.icon"></a-icon></template>
                <router-link v-if="m1.routePath" :to="m1.routePath">{{
                  m1.title
                }}</router-link>
                <span v-else>{{ m1.title }}</span>
              </a-menu-item>
            </template>
          </a-menu>
        </a-layout-sider>
        <a-layout-content>
          <router-view></router-view>
        </a-layout-content>
      </a-layout>
    </a-layout>
  </div>
</template>
<script setup lang="ts">
import AIcon from "../components/AIcon3"; // jsx icon 组件
import { serverMenus } from "../router/a6router";
</script>
<style scoped>
.a6main {
  height: 100%;
  background-color: rgb(220, 225, 255);
  box-sizing: border-box;
}
.ant-layout-header {
  height: 50px;
  background-color: darkseagreen;
}

.ant-layout-sider {
  background-color: lightsalmon;
}

.ant-layout-content {
  background-color: aliceblue;
}

.ant-layout-footer {
  background-color: darkslateblue;
  height: 30px;
}

.ant-layout {
  height: 100%;
}

.ant-layout-has-sider {
  height: calc(100% - 50px);
}
</style>
token 使用
  1. 获取用户信息,例如服务器端可以把用户名、该用户的路由、菜单信息都统一从 token 返回

  2. 前端路由跳转依据,例如跳转前检查 token,如果不存在,表示未登录,就避免跳转至某些路由

  3. 后端 API 访问依据,例如每次发请求携带 token,后端需要身份校验的 API 需要用到

相关推荐
四岁半儿1 小时前
常用css
前端·css
你的人类朋友2 小时前
说说git的变基
前端·git·后端
姑苏洛言2 小时前
网页作品惊艳亮相!这个浪浪山小妖怪网站太治愈了!
前端
字节逆旅2 小时前
nvm 安装pnpm的异常解决
前端·npm
Jerry2 小时前
Compose 从 View 系统迁移
前端
IT码农-爱吃辣条2 小时前
Three.js 初级教程大全
开发语言·javascript·three.js
GIS之路2 小时前
2025年 两院院士 增选有效候选人名单公布
前端
四岁半儿2 小时前
vue,H5车牌弹框定制键盘包括新能源车牌
前端·vue.js
烛阴3 小时前
告别繁琐的类型注解:TypeScript 类型推断完全指南
前端·javascript·typescript
gnip3 小时前
工程项目中.env 文件原理
前端·javascript