Nuxt3 在同一路由下,根据设备类型动态切换显示对应的页面,实现在一个项目中优雅地集成 PC 端、移动端多套页面

在网上找了很久都没有找到解决类似问题的文章,所以就自己实现了一种的解决方案,发出来供大家参考批评。

这篇文章的方案适用于 PC 端和移动端的是分开的两套页面的项目。如果你的项目中 PC 端和移动端使用的是同一套响应式的页面,那么这篇文章可能对你的帮助不大。

背景

在很多网站中,用移动端浏览器和 PC 端浏览器访问同一个路由时,展示的页面是不一样的。比如说下面的中国大学 MOOC:

Nuxt3 在同一路由下,根据设备类型动态切换显示对应的页面,实现在一个项目中优雅地集成 PC 端、移动端多套页面

在 Nuxt 中,实现这一功能常见的做法是这样的:

  1. 将 PC 端、移动端 两套页面内容抽离出来,分别放到 components 文件夹下的两个组件内
  2. 在 pages 文件夹对应的页面文件中,使用 <component> 组件,根据设备类型动态引入相应的组件,如下所示:
<component :is="isMobile ? MobileHomeComponent : PcHomeComponent">

这样做很简单,但会让目录结构不太优雅,随着项目复杂度的增加,维护难度会非常大。

为了避免这些问题,在一个项目中优雅地集成 PC 端、移动端多套页面,我实现了另外一种解决的方法。

最终效果

Nuxt3 在同一路由下,根据设备类型动态切换显示对应的页面,实现在一个项目中优雅地集成 PC 端、移动端多套页面

在 pages 文件夹下新建了 mobile、pc 两个目录。使用移动端访问时,应用会自动直接访问 mobile 目录下的页面;使用 PC 端访问时,应用会自动直接访问 pc 目录下的页面。同时 pc 和 mobile 这两个字符串不会出现在路由中

Nuxt3 在同一路由下,根据设备类型动态切换显示对应的页面,实现在一个项目中优雅地集成 PC 端、移动端多套页面

在最终效果中,上图中的目录结构对应的路由如下:

路由 移动端显示的页面 PC端显示的页面
/ pages/mobile/index.vue pages/pc/index.vue
/aaa pages/mobile/aaa.vue pages/pc/aaa.vue
/bbb pages/mobile/bbb.vue pages/pc/bbb.vue

实现步骤

第一步:创建一个获取设备类型的组合函数

Nuxt3 在同一路由下,根据设备类型动态切换显示对应的页面,实现在一个项目中优雅地集成 PC 端、移动端多套页面

在 composables 文件夹下,创建 useDeviceType.ts 文件,在其中通过 UA 来判断设备类型。代码如下:

export const useDeviceType = () => {
  let UA: string
  if (process.client)
    // 如果是在客户端执行,则通过 navigator 获取 user-agent
    UA = navigator.userAgent
  else
    // 如果是在服务端执行,则通过请求头获取 user-agent
    UA = useRequestHeader('user-agent') as string

  const type = ref<'mobile' | 'pc'>()

  console.log(UA)

  // 通过 UA 来判断设备类型是 pc 还是 mobile
  if (/(Android|webOS|iPhone|iPod|tablet|BlackBerry|Mobile)/i.test(UA))
    type.value = 'mobile'
  else
    type.value = 'pc'

  return type
}

由于 Nuxt 应用会在服务端和客户端两个环境中执行,所以获取 UA 时需要根据所在环境分别获取

第二步:通过 router options 动态修改路由

一般情况下,Nuxt 使用的是基于文件的路由系统,Nuxt 默认会扫描 pages 目录下的所有文件,并根据目录结构生成路由。如果直接按照最终效果所示的方式,新建了 pc、mobile 两个文件夹,那么 pc、mobile 这两个字符串势必会分别出现在这两个文件夹下所有页面的路由中,路由会变成这样子:

路由 显示的页面
/pc/ pages/pc/index.vue
/pc/aaa pages/pc/aaa.vue
/pc/bbb pages/pc/bbb.vue
/mobile/ pages/mobile/index.vue
/mobile/aaa pages/mobile/aaa.vue
/mobile/bbb pages/mobile/bbb.vue

在这样的路由中,两端的页面分别需要通过 /pc 和 /mobile 前缀才能访问到,这显然不是我们想要的,我们需要对这些路由进行修改。

通过仔细阅读 官方文档 可以知道,Nuxt3 中,修改路由一共有三种方式:

修改方式 可行性 原因
修改 router options 可行✅ 实例化 Router 时的配置项,可以获取设备类型并动态修改路由
使用 pages:extend 钩子 不可行❌ 该钩子只在构建时被调用,无法获取设备类型
使用 Nuxt Module 不可行❌ 经测试,无法使用 useDeviceType 组合函数获取设备类型

三种方式中只有修改 router options 的方式才能获取设备类型并动态修改路由,下面是具体的操作方法:

新建 app/router.options.ts 文件,在其中根据设备类型动态导出路由配置项,代码如下:

import type { RouterConfig } from '@nuxt/schema'
import type { RouteRecordRaw } from 'vue-router'
// 删除路由 path 中指定前缀
function deletePrefixInPath(prefix: '/mobile' | '/pc', routes: RouteRecordRaw[]) {
const newRoutes: RouteRecordRaw[] = []
for (let i = 0; i < routes.length; i++) {
routes[i].path = routes[i].path.replace(prefix, '') === '' ? '/' : routes[i].path.replace(prefix, '')
// 如果 PC 端、移动端 分别使用的是两套 layout,可以使用下面这段注释掉的代码去指定布局
// if (!routes[i].meta?.layout) {
//   routes[i].meta = {
//     ...routes[i].meta,
//     layout: prefix === '/mobile' ? '移动端布局名' : 'PC 端布局名',
//   }
// }
}
return newRoutes
}
// 重点!
// 导出路由配置项
export default <RouterConfig> {
routes: (_routes) => {
// 获取设备类型
const deviceType = useDeviceType()
const targetRoutes = ref<RouteRecordRaw[]>([]) // 存储匹配当前设备类型的页面路由
const notTargetRoutes = ref<RouteRecordRaw[]>([]) // 存储不匹配当前设备类型的页面路由
// 如果是移动端访问,则给移动端页面删除路由前缀 /mobile ,给PC端页面添加路由前缀 /pc
if (deviceType.value === 'mobile') {
targetRoutes.value = _routes.filter(item => item.path.startsWith('/mobile')).length ? _routes.filter(item => item.path.startsWith('/mobile')) : _routes.filter(item => !item.path.startsWith('/pc'))
targetRoutes.value = deletePrefixInPath('/mobile', targetRoutes.value)
notTargetRoutes.value = _routes.filter(item => item.path.startsWith('/pc'))
notTargetRoutes.value = notTargetRoutes.value.map((item) => {
if (!item.path.startsWith('/pc'))
item.path = `/pc${item.path}`
return item
})
// console.log([...targetRoutes.value, ...notTargetRoutes.value])
return [...targetRoutes.value, ...notTargetRoutes.value]
}
// 如果是PC端访问,则给PC端页面删除路由前缀 /pc ,给移动端页面添加路由前缀 /mobile
else if (deviceType.value === 'pc') {
targetRoutes.value = _routes.filter(item => item.path.startsWith('/pc')).length ? _routes.filter(item => item.path.startsWith('/pc')) : _routes.filter(item => !item.path.startsWith('/mobile'))
targetRoutes.value = deletePrefixInPath('/pc', targetRoutes.value)
notTargetRoutes.value = _routes.filter(item => item.path.startsWith('/mobile'))
notTargetRoutes.value = notTargetRoutes.value.map((item) => {
if (!item.path.startsWith('/mobile'))
item.path = `/mobile${item.path}`
return item
})
// console.log([...targetRoutes.value, ...notTargetRoutes.value])
return [...targetRoutes.value, ...notTargetRoutes.value]
}
},
}

实现的动态修改效果如下图:

Nuxt3 在同一路由下,根据设备类型动态切换显示对应的页面,实现在一个项目中优雅地集成 PC 端、移动端多套页面

第三步:优雅地分别为 PC 端、移动端 写页面

所有的移动端页面都放到 pages/mobile 文件夹中;所有的 PC 端页面都放到 pages/pc 文件夹中。

需要注意的是,如果 PC 端和 移动端 的页面数量不完全一致,比如说 PC 端有一个 pages/pc/abc.vue 页面文件,而移动端没有这个文件,那么使用移动端访问 /abc 时就会出现 404 的错误,而 PC 端可以正常显示。这一点应该很好理解。

总结

整个实现过程的核心其实只有两步:

  1. 创建获取设备类型的组合函数
  2. 通过 app/router.options.ts 文件实现动态修改路由

同时,标题的表述其实不太恰当,准确地说应该是实现了,根据设备类型的不同,使用 pages 目录下不同的文件夹作为路由的 “根目录”,因此也就可以实现在同一路由下,根据设备类型,动态切换显示对应的页面。

此外值得一提的是, 由于我们是通过实例化 Router 时的配置项来动态修改路由,所以只有在初次或以刷新的方式进入页面时,路由的动态修改才会生效,也就是说:如果在中途通过开发者工具修改了设备类型,这种情况是不会动态切换到相匹配的页面的,必须要刷新页面,路由才会动态修改。

以上就是全部内容了,如果你有其他更好的实现方法或者思路,欢迎在评论区补充。如果你发现了什么 Bug 或者有其它问题,也可以在评论区提出来。

如果这篇文章有帮到你,还请点一个不要钱的赞,感谢支持 ❤️

原文链接:https://juejin.cn/post/7326268915100794906 作者:Loui

(0)
上一篇 2024年1月21日 下午4:16
下一篇 2024年1月21日 下午4:26

相关推荐

发表回复

登录后才能评论