【问题标题】:How to do nested child on Desktop but separate pages on mobile using Vue Router?如何使用 Vue Router 在桌面上执行嵌套子项,但在移动设备上执行单独的页面?
【发布时间】:2021-09-19 15:02:12
【问题描述】:

我在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
          }
        ]
      }
    ]
  });
}

在手机上,我想要第 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
      }
    ]
  });
}

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

Here is my CODESANDBOX 正如您在我的沙盒上看到的,它在桌面上完美运行,但在移动设备上,详细信息页面不显示

桌面

【问题讨论】:

    标签: vue.js vuejs2 vue-router master-detail


    【解决方案1】:

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

    方法 1 使用程序化路线导航,所有内容都使用同一页面

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

    router.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
          }
        ]
      });
    }
    

    Index.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>
    

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

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

    router.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
          }
        ]
      });
    }
    

    Index.vue 页面使用窗口调整大小监听器来设置一个变量 isMobile,它对宽度

    Index.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>
    

    NewsList 总是以这种方式显示。详细信息仅显示在桌面上。手机版NewsDetail页面如下图

    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>
    

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

    创建 router.js,其中 News 页面同时包含 NewsList 和 NewsDetail 页面作为子页面

    router.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
              }
            ]
          }
        ]
      });
    }
    

    Index.vue 文件添加了一个窗口调整大小侦听器,该侦听器在每次屏幕大小更改时调用 onResize 方法。在此方法中,如果屏幕宽度

    Index.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>
    

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

    NewsList.vue

    <template>
      <app-news-list />
    </template>
    
    <script>
    import AppNewsList from "~/components/AppNewsList";
    export default {
      name: "NewsList",
      components: { AppNewsList },
    };
    </script>
    

    NewsDetail.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>
    

    【讨论】:

      猜你喜欢
      • 2014-04-05
      • 2021-04-20
      • 1970-01-01
      • 1970-01-01
      • 2020-06-02
      • 2021-06-11
      • 1970-01-01
      • 2018-08-03
      • 1970-01-01
      相关资源
      最近更新 更多