我终于想出了如何做到这一点。方法不止一种,我在这里分享每一种方法
方法 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>