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

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

背景

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

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

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

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

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

最终效果

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

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

路由 移动端显示的页面 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

实现步骤

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

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

ts 复制代码
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 文件,在其中根据设备类型动态导出路由配置项,代码如下:

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, '')
  }
  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]
    }
  },
}

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

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

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

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

总结

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

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

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

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

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

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

相关推荐
Amd79422 天前
Nuxt.js 应用中的 error 事件钩子
前端·nuxt.js·web应用·稳定性·用户体验·错误处理·钩子
Amd7941 个月前
Nuxt.js 应用中的 render:response 事件钩子
nuxt.js·事件·ssr·处理·钩子·修改·响应
Amd7941 个月前
Nuxt.js 应用中的 dev:ssr-logs 事件钩子
开发·监控·日志·nuxt.js·调试·ssr·钩子
Amd7941 个月前
Nuxt.js 应用中的 webpack:done 事件钩子
ui·webpack·编译·nuxt.js·加载·清理·钩子
Amd7941 个月前
Nuxt.js 应用中的 webpack:change 事件钩子
webpack·文件·nuxt.js·用户界面·钩子·修改·重新加载
Amd7941 个月前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
Amd7941 个月前
Nuxt.js 应用中的 webpack:compile 事件钩子
webpack·自定义·编译·nuxt.js·构建·钩子·逻辑
进击的松鼠1 个月前
【Nuxt 实战】02-集成 Pinia、新增导航栏、暗黑模式
前端·全栈·nuxt.js
Amd7941 个月前
Nuxt.js 应用中的 webpack:configResolved事件钩子
webpack·自定义·开发·配置·nuxt.js·构建·钩子
枣把儿1 个月前
Nuxt3全栈开发博客 · 配置篇
前端·后端·nuxt.js