就是它同一个url对应了两种不同的页面。上面这个是从列表页点开一个文章的时候,浏览器的路由变了,但是页面没有发生跳转,而是以一个弹窗的模式显示文章,底下我们还能看到列表。
import { RouteRecordRaw } from "vue-router"; export const routes: RouteRecordRaw[] = [ { path: "/", redirect: '/home' }, { path: "/home", name: "Home", component: () => import("./Home.vue"), children: [ { path: ':id', name: "Detail", component: () => import('./Detail.vue'), } ] }, ]router.ts
import {createRouter, createWebHistory} from "vue-router"; import { routes } from './routes.ts' export const router = createRouter({ history: createWebHistory(), routes, })文件结构:
<template> <div> <div class="text-red-700">Home</div> <div class="w-full flex flex-wrap gap-3"> <router-link v-for="item in dataList" :to="`/home/${item.id}`"> <img :src="item.url" alt=""> </router-link> </div> <el-dialog title="Detail" v-model="dialogVisible"> <router-view></router-view> </el-dialog> </div> </template> <script setup lang="ts"> import {computed, ref} from "vue"; import {useRoute, useRouter} from "vue-router"; import axios from "axios"; import {randomSize} from "../utils/randomSize.ts"; // 堆代码 duidaima.com const route = useRoute() const router = useRouter() const lastRoute = computed(() => route.matched[route.matched.length - 1]) const dialogVisible = computed({ get() { return lastRoute.value.name == 'Detail' }, set(val) { if (!val) { router.go(-1) } }, }) const dataList = ref([]) const loading = ref(false) function getList() { loading.value = true const data = localStorage.getItem('imageData') if (!data) { axios.get('https://picsum.photos/v2/list') .then(({data}) => setDataList(data)) .then(data => localStorage.setItem('imageData', JSON.stringify(data))) .finally(() => { loading.value = false }) } else { setDataList(JSON.parse(data)) } } getList() function setDataList(data) { dataList.value = data.map(item => ({ id: item.url.split('/').pop(), url: randomSize(item.download_url) })) return data } </script>这里重点看两个地方:
2.为了控制弹窗的显隐,我定义了一个dialogVisible计算对象,他的get来自router.matched列表中最后一个路由(最终命中的路由)是否为Detail,如果为Detail,就true,否则为false;它的set我们只需要处理false的情况,当false的时候,路由回退1。(其实是用push/replace还是用go我是有点纠结的,但是我看到小红书这里是用的回退,所以我也就用回退了,虽然回退在这种使用场景中存在一定的隐患)
// route.ts export const routes = [ ... { path: '/home/:id', name: "DetailId", component: () => import('./Detail.vue') } ]这个路由跟Home是同级的,使用了绝对路径来标记path(这就是上面detail采用相对路径的原因),同时为了避免name冲突,我换了一个name,component还是使用Detail.vue(这里我后来发现其实也可以使用其他的组件,其实真正起作用的是path,而不是component)。
// router.ts router.beforeEach((to, from) => { if (to.name === 'Detail') { if (from.name === 'Home') { return true } else { return { name: 'DetailId', params: to.params } } } })这个守卫的作用是,当发生路由跳转时,如果to为Detail,则判断from是否为Home,如果from为Home,则可以正常跳转,如果from不为Home,则说明是刷新或者链接打开,这时跳转至DetailId页面,并且params保持不变。短短十行代码,就解决了问题。可以看到,正常从列表显示详情还是会正常从弹窗中显示,而如果此时刷新页面,就会直接进入到详情页面。