如何使用 Vue Router 在桌面上实现嵌套子级,但在移动设备上实现独立页面?

Pir*_*App 5 master-detail vue.js vue-router vuejs2

在此输入图像描述

我在 Nuxt SSR 中使用 Vue Router,我想实现如上所示的结构。

在桌面上,我想在单个页面上渲染嵌套子路由,其中​​左侧有项目列表,右侧有项目的详细信息,可以像这样完成

export function createRouter() {
  return new Router({
    mode: "history",
    routes: [
      {
        path: "/news/:tag?",
        name: "TaggedNews",
        component: Index,
        children: [
          {
            path: "/news/:tag/:id/:title",
            name: "TaggedNewsItem",
            component: Detail,
            props: true
          }
        ]
      }
    ]
  });
}
Run Code Online (Sandbox Code Playgroud)

在移动设备上,我想要第 1 页上的列表和第 2 页上的详细信息。

路由器需要看起来像这样

export function createRouter() {
  return new Router({
    mode: "history",
    routes: [
      {
        path: "/news/:tag?",
        name: "TaggedNews",
        component: Index
      },
      {
        path: "/news/:tag/:id/:title",
        name: "TaggedNewsItem",
        component: Detail,
        props: true
      }
    ]
  });
}
Run Code Online (Sandbox Code Playgroud)

我如何使用 vue 路由器做到这一点?

这是我的 Codesandbox 正如您在我的沙箱上看到的,它在桌面上完美运行,但在移动设备上,详细信息页面不会显示

桌面

Pir*_*App 3

我终于想出了如何做到这一点。方法不止一种,让我在这里分享每种方法

方法 1 使用编程式路线导航,所有内容均在同一页面上

这种方法的想法是只有一个页面可以处理列表视图和详细信息视图。路由器定义了两条单独的路由,它们都指向同一页面。每当路由发生变化时,我们都会对其进行监控,以便找到新路由的名称,并根据该名称动态加载列表或详细信息组件

路由器.js

import Vue from "vue";
import Router from "vue-router";

import Index from "~/pages/index";

Vue.use(Router);

export function createRouter() {
  return new Router({
    mode: "history",
    routes: [
      {
        path: "/news/:tag?",
        alias: "/",
        name: "NewsList",
        component: Index,
        props: true
      },
      {
        path: "/news/:tag?/:id([a-fA-F\\d]{32})/:title",
        name: "NewsDetail",
        component: Index,
        props: true
      }
    ]
  });
}
Run Code Online (Sandbox Code Playgroud)

索引.vue

<template>
  <div class="news__container">
    <template v-if="isMobile">
      <component :is="current"></component>
    </template>
    <template v-else>
      <div class="left">
        <news-list></news-list>
      </div>
      <div class="right">
        <news-detail></news-detail>
      </div>
    </template>
  </div>
</template>

<script>
import NewsList from "~/components/NewsList";
import NewsDetail from "~/components/NewsDetail";
export default {
  name: "root",

  components: { NewsList, NewsDetail },

  data: () => ({
    isMobile: false,
  }),

  beforeDestroy() {
    if (typeof window !== "undefined") {
      window.removeEventListener("resize", this.onResize, { passive: true });
    }
  },

  computed: {
    current() {
      return this.$route.name === "NewsList" ? NewsList : NewsDetail;
    },
  },

  mounted() {
    this.onResize();
    window.addEventListener("resize", this.onResize, { passive: true });
  },

  watch: {
    $route: {
      immediate: true,
      handler(newRoute) {
        // Set name of the current route inside a variable
        // Use this variable as a computed property to dynamically load the <component> on mobile view
        this.current = newRoute.name;
      },
    },
  },

  methods: {
    onResize() {
      this.isMobile = window.innerWidth < 768;
    },
  },
};
</script>

<style lang="scss" scoped>
.news__container {
  display: flex;
}
.left {
  flex: 1;
}
.right {
  flex: 1;
}
</style>
Run Code Online (Sandbox Code Playgroud)

方法 2 创建列表作为父项,创建详细信息作为在移动设备上隐藏的 nuxt 子项,并创建仅在移动设备上显示的单独详细信息页面(重复)

这里创建的 router.js 使得新闻列表页面是父页面,新闻详细信息页面是子页面。在桌面上,列表和详细信息页面并排显示。在移动设备上,详细信息页面被隐藏,并显示另一个仅移动设备的详细信息页面。因此,在此方法中详细信息页面被复制两次。除了重复之外,这种方法的另一个问题是移动端的 NewsDetail 页面可以在桌面上直接访问

路由器.js

import Vue from "vue";
import Router from "vue-router";

import Index from "~/pages/index";
import Detail from "~/pages/detail";

Vue.use(Router);

export function createRouter() {
  return new Router({
    mode: "history",
    routes: [
      {
        path: "/news/:tag?",
        name: "TaggedNews",
        component: Index,
        alias: "/",
        children: [
          {
            path: "/news/:tag?/:id([a-fA-F\\d]{32})/:title",
            name: "TaggedNewsItemDesktop",
            component: Detail,
            props: true
          }
        ]
      },
      {
        path: "/news/:tag?/:id([a-fA-F\\d]{32})/:title",
        name: "TaggedNewsItemMobile",
        component: Detail,
        props: true
      }
    ]
  });
}
Run Code Online (Sandbox Code Playgroud)

Index.vue 页面使用窗口调整大小侦听器来设置变量 isMobile,该变量对于宽度 < 768 为 true

索引.vue

<template>
  <div class="news__container">
    <div class="left">
      <news-list :is-mobile="isMobile" />
    </div>

    <div v-if="!isMobile" class="right">
      <nuxt-child></nuxt-child>
    </div>
  </div>
</template>

<script>
import NewsList from "~/components/NewsList";
export default {
  name: "root",
  components: { NewsList },
  data: () => ({
    isMobile: false,
  }),

  beforeDestroy() {
    if (typeof window !== "undefined") {
      window.removeEventListener("resize", this.onResize, { passive: true });
    }
  },

  mounted() {
    this.onResize();
    window.addEventListener("resize", this.onResize, { passive: true });
  },

  methods: {
    onResize() {
      this.isMobile = window.innerWidth < 768;
    },
  },
};
</script>

<style lang="scss" scoped>
.news__container {
  display: flex;
  height: 100%;
}
.left {
  flex: 1;
}
.right {
  flex: 1;
}
</style>
Run Code Online (Sandbox Code Playgroud)

新闻列表始终以这种方式显示。详细信息仅显示在桌面上。手机版NewsDetail页面如下图

新闻详情.vue

<template>
  <news-detail :tag="tag" :id="id" :title="title" />
</template>

<script>
import NewsDetail from "~/components/NewsDetail";

export default {
  components: { NewsDetail },
  props: {
    tag: {
      type: String,
      required: true,
      default: "",
    },
    id: {
      type: String,
      required: true,
      default: "",
    },
    title: {
      type: String,
      required: true,
      default: "",
    },
  },
};
</script>
Run Code Online (Sandbox Code Playgroud)

方法 3 作为子项创建列表和详细信息

创建 router.js,其中新闻页面将 NewsList 和 NewsDetail 页面作为子页面

路由器.js

import Vue from "vue";
import Router from "vue-router";

import Index from "~/pages/index";
import NewsList from "~/pages/NewsList";
import NewsDetail from "~/pages/NewsDetail";

Vue.use(Router);

export function createRouter() {
  return new Router({
    mode: "history",
    routes: [
      {
        path: "/news/:tag?",
        alias: "/",
        component: Index,
        children: [
          {
            path: "",
            name: "NewsList",
            component: NewsList,
            props: true
          },
          {
            path: "/news/:tag?/:id([a-fA-F\\d]{32})/:title",
            name: "NewsDetail",
            component: NewsDetail,
            props: true
          }
        ]
      }
    ]
  });
}
Run Code Online (Sandbox Code Playgroud)

Index.vue 文件添加了一个窗口调整大小侦听器,每次屏幕尺寸发生变化时都会调用 onResize 方法。在此方法中,如果屏幕宽度 < 768,我们将变量 isMobile 设置为 true。现在,只要 isMobile 为 true,我们就会根据路由显示适当的 nuxt 子项,并在桌面上并排显示列表和详细信息组件,而不使用 nuxt 子项。简单的想法是,页面 NewsList 和 NewsDetail 根据移动视图上的路线显示,而桌面视图并排加载这两个组件

索引.vue

<template>
  <div class="news__container">
    <template v-if="isMobile">
      <div class="left">
        <nuxt-child></nuxt-child>
      </div>
    </template>
    <template v-else>
      <div class="left">
        <app-news-list />
      </div>
      <div class="right">
        <app-news-detail
          :tag="$route.params.tag"
          :id="$route.params.id"
          :title="$route.params.title"
        ></app-news-detail>
      </div>
    </template>
  </div>
</template>

<script>
import AppNewsList from "~/components/AppNewsList";
import AppNewsDetail from "~/components/AppNewsDetail";
export default {
  name: "root",

  components: { AppNewsList, AppNewsDetail },

  data: () => ({
    isMobile: false,
  }),

  beforeDestroy() {
    if (typeof window !== "undefined") {
      window.removeEventListener("resize", this.onResize, { passive: true });
    }
  },

  computed: {
    current() {
      return this.$route.name === "Index" || this.$route.name === "AppNewsList"
        ? AppNewsList
        : AppNewsDetail;
    },
  },

  mounted() {
    this.onResize();
    window.addEventListener("resize", this.onResize, { passive: true });
  },

  methods: {
    onResize() {
      this.isMobile = window.innerWidth < 768;
    },
  },
};
</script>

<style lang="scss" scoped>
.news__container {
  display: flex;
}
.left {
  flex: 1;
}
.right {
  flex: 1;
}
</style>
Run Code Online (Sandbox Code Playgroud)

NewsList 页面仅加载显示新闻项列表的组件,而 NewsDetail 页面仅加载详细信息组件

新闻列表.vue

<template>
  <app-news-list />
</template>

<script>
import AppNewsList from "~/components/AppNewsList";
export default {
  name: "NewsList",
  components: { AppNewsList },
};
</script>
Run Code Online (Sandbox Code Playgroud)

新闻详情.vue

<template>
  <app-news-detail :tag="tag" :id="id" :title="title" />
</template>

<script>
import AppNewsDetail from "~/components/AppNewsDetail";
export default {
  name: "NewsDetail",
  components: { AppNewsDetail },
  props: {
    tag: { type: String, required: true, default: "" },
    id: { type: String, required: true, default: "" },
    title: { type: String, required: true, default: "" },
  },
};
</script>
Run Code Online (Sandbox Code Playgroud)