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

相关推荐
Coffeeee11 小时前
一个kotlin的Smart cast导致的编译问题
android·前端·kotlin
CodeSheep12 小时前
胡彦斌都开始苦修Vibe Coding,还上架App Store,都卷到编程来了吗?
前端·后端·程序员
触底反弹12 小时前
从数据结构到 Prompt 设计:前端工程师的 AI 时代进阶指南
javascript·人工智能·python
薄荷椰果抹茶12 小时前
前端技术之---打字机效果与流式输出
前端
Mintopia12 小时前
Tanstack为什么会火
前端
DongWook12 小时前
关于Harness Engineering的一次实践
前端·后端
风骏时光牛马12 小时前
Kotlin开发高频疑难问题汇总梳理
前端
暗冰ཏོ12 小时前
ECharts 前端图表开发全攻略:参数配置、项目实战与高级可视化资源整理
前端·vue.js·echarts·visual studio code
PILIPALAPENG12 小时前
gh:终端里的GitHub总控台,AI时代的开发者神器
前端·人工智能·后端
橘猫走江湖12 小时前
前端项目如何做 vibe coding
javascript·vue.js·架构