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 需要用到

相关推荐
Hilaku7 小时前
不要在简历上写精通 Vue3?来自面试官的真实劝退
前端·javascript·vue.js
三小河7 小时前
前端视角详解 Agent Skill
前端·javascript·后端
Aniugel8 小时前
单点登录(SSO)系统
前端
颜酱8 小时前
二叉树遍历思维实战
javascript·后端·算法
鹏多多8 小时前
移动端H5项目,还需要react-fastclick解决300ms点击延迟吗?
前端·javascript·react.js
serioyaoyao8 小时前
上万级文件一起可视化,怎么办?答案是基于 ParaView 的远程可视化
前端
万少8 小时前
端云一体 一天开发的元服务-奇趣故事匣经验分享
前端·ai编程·harmonyos
WindrunnerMax8 小时前
从零实现富文本编辑器#11-Immutable状态维护与增量渲染
前端·架构·前端框架
不想秃头的程序员8 小时前
Vue3 封装 Axios 实战:从基础到生产级,新手也能秒上手
前端·javascript·面试
数研小生8 小时前
亚马逊商品列表API详解
前端·数据库·python·pandas