Nuxt3 优雅地在一个项目中集成 PC 端、移动端多套页面

7,058 阅读6分钟

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

本文章由 Loui 原创,如需转载,请先私信或评论。

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

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

背景

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

screenshots.gif

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

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

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

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

最终效果

screenshots.gif

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

image.png

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

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

实现步骤

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

image.png

在 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'>('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/aaapages/pc/aaa.vue
/pc/bbbpages/pc/bbb.vue
/mobile/pages/mobile/index.vue
/mobile/aaapages/mobile/aaa.vue
/mobile/bbbpages/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'

// 导出路由配置项
export default <RouterConfig> {
  routes: (_routes) => {
    // 思路是这样的:
    // 如果是移动端访问,则给移动端页面删除路由前缀 /mobile ,给PC端页面添加路由前缀 /pc
    // 如果是 PC 端访问,则给 PC 端页面删除路由前缀 /pc ,给移动端页面添加路由前缀 /mobile
    
    // 当前设备的类型
    const targetType = useDeviceType().value
    // 不是当前设备类型的另一个类型
    const notTargetType = targetType === 'mobile' ? 'pc' : 'mobile'

    // 找到匹配当前设备的所有页面路由
    let targetRoutes = _routes.filter(item => (item.name as string).startsWith(targetType))
    targetRoutes = targetRoutes.map((item) => {
      // 将路由前缀删除
      item.path = item.path.replace(`/${targetType}`, '') === '' ? '/' : item.path.replace(`/${targetType}`, '')
      // 如果 PC 端、移动端 分别使用的是两套 layout,可以使用下面这段代码去指定布局
      // if (!item.meta?.layout) {
      //   item.meta = {
      //     ...item.meta,
      //     layout: `/${targetType}` === '/mobile' ? '移动端布局名' : 'PC 端布局名',
      //   }
      // }
      return item
    })

    // 找到不匹配当前设备的所有页面路由
    let notTargetRoutes = _routes.filter(item => (item.name as string).startsWith(notTargetType))
    notTargetRoutes = notTargetRoutes.map((item) => {
      // 将路由前缀添加上
      if (!item.path.startsWith(`/${notTargetType}`))
        item.path = `/${notTargetType}${item.path}`
      return item
    })

    return [...targetRoutes, ...notTargetRoutes]
  },
}

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

image.png

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

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

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

总结

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

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

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

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

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

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