博客头像

    Suim

    状态

    • 网站出生日期
      2026-06-26
    • 文章总数
      2
    • 评论数量
      0

    Md测试

    Suim
    |
    发布于 06/27/2026 13:45:47
    |
    编辑于 06/27/2026 13:45:47
    |
    0 条评论

    Vue Router 路由拦截与鉴权

    路由守卫是 Vue Router 提供的钩子函数,用于在导航过程中执行逻辑检查。可以把它理解为路由系统的"安检门",在页面跳转前进行条件判定。

    全局守卫分类

    Vue Router 提供了三种层级的守卫:

    类型 触发时机 典型用途
    全局前置 导航开始前 登录校验、权限验证
    全局解析 导航确认前 数据预加载
    全局后置 导航完成后 页面标题更新、埋点上报

    beforeEach 基础写法

    import { createRouter, createWebHashHistory } from 'vue-router'
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes: [...]
    })
    
    router.beforeEach((to, from) => {
      // to 表示目标路由对象
      // from 表示来源路由对象
      // 返回 false 则取消导航
      console.log(`从 ${from.path} 跳转到 ${to.path}`)
    })

    next 函数的替代方案

    Vue Router 4.x 推荐使用返回值控制导航,但 next 函数仍兼容:

    // 方式一:返回 false 取消导航
    router.beforeEach(() => {
      return false;
    });
    
    // 方式二:返回路径字符串重定向
    router.beforeEach(() => {
      return "/login";
    });
    
    // 方式三:返回路由对象
    router.beforeEach(() => {
      return { name: "Login", query: { redirect: to.fullPath } };
    });

    基于角色的访问控制

    const routes = [
      {
        path: "/admin",
        component: AdminDashboard,
        meta: { permission: "admin", needLogin: true },
      },
      {
        path: "/editor",
        component: EditorPanel,
        meta: { permission: "editor", needLogin: true },
      },
      {
        path: "/profile",
        component: UserProfile,
        meta: { needLogin: true },
      },
    ];
    
    router.beforeEach((to) => {
      const isAuthenticated = sessionStorage.getItem("sessionId");
      const userRole = sessionStorage.getItem("role") || "guest";
    
      if (to.meta.needLogin && !isAuthenticated) {
        return { name: "SignIn", query: { back: to.fullPath } };
      }
    
      if (to.meta.permission && to.meta.permission !== userRole) {
        return { name: "Forbidden" };
      }
    });

    登录后的回跳处理

    <script setup>
    import { useRoute, useRouter } from "vue-router";
    
    const route = useRoute();
    const router = useRouter();
    
    async function doLogin(form) {
      const res = await api.auth.signIn(form);
      sessionStorage.setItem("sessionId", res.token);
    
      const fallback = route.query.back || "/dashboard";
      router.replace(fallback);
    }
    </script>

    课堂练习

    练习一:计数器按钮

    <script setup>
    import { ref } from "vue";
    
    const counter = ref(0);
    function increment() {
      counter.value++;
      console.log(`当前计数: ${counter.value}`);
    }
    </script>
    
    <template>
      <button @click="increment">点我 +1(已点 {{ counter }} 次)</button>
    </template>

    练习二:双向绑定演示

    <script setup>
    import { ref } from "vue";
    const keyword = ref("");
    </script>
    
    <template>
      <input v-model="keyword" placeholder="输入搜索词" />
      <p>您正在搜索:{{ keyword || "(暂无内容)" }}</p>
    </template>

    练习三:表单提交

    <script setup>
    import { ref } from "vue";
    const email = ref("");
    
    function onSubmit() {
      alert(`提交邮箱: ${email.value}`);
    }
    </script>
    
    <template>
      <form @submit.prevent="onSubmit">
        <input v-model="email" type="email" placeholder="请输入邮箱" />
        <button type="submit">提交</button>
      </form>
    </template>

    练习四:事件修饰符

    <script setup>
    function parentHandler() {
      console.log("父级 div 被触发");
    }
    function childHandler() {
      console.log("子级按钮被触发");
    }
    function onEnter() {
      console.log("按下了回车键");
    }
    </script>
    
    <template>
      <div @click="parentHandler">
        <button @click.stop="childHandler">点击我(阻止冒泡)</button>
      </div>
      <input @keyup.enter="onEnter" placeholder="按回车触发" />
    </template>

    练习五:动态样式绑定

    <script setup>
    import { ref } from "vue";
    const active = ref(false);
    </script>
    
    <template>
      <div
        :class="{ 'box-active': active }"
        :style="{ backgroundColor: active ? '#42b983' : '#ccc' }"
        @mouseenter="active = true"
        @mouseleave="active = false"
      >
        {{ active ? "鼠标已进入" : "鼠标已离开" }}
      </div>
    </template>
    
    <style scoped>
    .box-active {
      transform: scale(1.05);
      transition: all 0.3s;
    }
    </style>

    练习六:插槽封装

    <!-- CardBox.vue -->
    <template>
      <div class="card">
        <div class="card-head">
          <slot name="head"></slot>
        </div>
        <div class="card-body">
          <slot></slot>
        </div>
      </div>
    </template>
    
    <style scoped>
    .card {
      border: 1px solid #e0e0e0;
      border-radius: 6px;
      padding: 16px;
    }
    .card-head {
      font-weight: bold;
      margin-bottom: 12px;
    }
    </style>

    练习七:v-model 修饰符

    <script setup>
    import { ref } from "vue";
    const age = ref(null);
    const nickname = ref("");
    </script>
    
    <template>
      <input v-model.number="age" placeholder="年龄(自动转数字)" />
      <input v-model.trim="nickname" placeholder="昵称(自动去空格)" />
      <p>年龄类型:{{ typeof age }},昵称:「{{ nickname }}」</p>
    </template>

    练习八:综合注册表单

    <script setup>
    import { reactive, computed } from "vue";
    
    const form = reactive({
      account: "",
      pwd: "",
      confirmPwd: "",
      gender: "",
      hobbies: [],
      city: "",
      agreement: false,
    });
    
    const cities = [
      { label: "杭州", val: "hz" },
      { label: "成都", val: "cd" },
      { label: "武汉", val: "wh" },
    ];
    
    const isValid = computed(() => {
      return (
        form.account.length >= 4 &&
        form.pwd.length >= 6 &&
        form.pwd === form.confirmPwd &&
        form.agreement
      );
    });
    
    function register() {
      if (!isValid.value) return;
      console.log("注册信息:", form);
    }
    </script>
    
    <template>
      <div class="register-box">
        <h3>账号注册</h3>
        <input v-model="form.account" placeholder="账号(至少4位)" />
        <input v-model="form.pwd" type="password" placeholder="密码(至少6位)" />
        <input v-model="form.confirmPwd" type="password" placeholder="确认密码" />
    
        <div>
          <label><input type="radio" v-model="form.gender" value="M" /> 男</label>
          <label><input type="radio" v-model="form.gender" value="F" /> 女</label>
        </div>
    
        <div>
          <label
            ><input type="checkbox" v-model="form.hobbies" value="code" />
            编程</label
          >
          <label
            ><input type="checkbox" v-model="form.hobbies" value="game" />
            游戏</label
          >
          <label
            ><input type="checkbox" v-model="form.hobbies" value="music" />
            音乐</label
          >
        </div>
    
        <select v-model="form.city">
          <option value="">选择城市</option>
          <option v-for="c in cities" :key="c.val" :value="c.val">
            {{ c.label }}
          </option>
        </select>
    
        <label>
          <input type="checkbox" v-model="form.agreement" />
          同意用户协议
        </label>
    
        <button @click="register" :disabled="!isValid">立即注册</button>
      </div>
    </template>

    评论区