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 或者有其它问题,也可以在评论区提出来。

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

相关推荐
JXDN23 天前
Nuxt.js代码风格配置
vue.js·nuxt.js
Amd79423 天前
Nuxt.js 应用中的 modules:done 事件钩子详解
生命周期·前端开发·nuxt.js·钩子函数·代码示例·应用初始化·modules:done
Amd79424 天前
Nuxt.js 应用中的 modules:before 事件钩子详解
生命周期·配置·模块·nuxt.js·初始化·钩子·环境设置
熊猫在哪25 天前
nuxt3脚手架安装报错解决方法
前端·javascript·nuxt.js
Amd79425 天前
Nuxt.js 应用中的 restart 事件钩子详解
nuxt.js·实例方法·开发技巧·普通重启·硬重启·应用重启·重启方法
Amd7941 个月前
Nuxt.js 应用中的 close 事件钩子详解
生命周期·nuxt.js·日志记录·状态保存·资源清理·应用关闭·close钩子
Amd7941 个月前
Nuxt.js 应用中的 ready 事件钩子详解
生命周期·前端开发·nuxt.js·请求处理·应用初始化·nuxt实例·ready钩子
Amd7941 个月前
Nuxt.js 应用中的 kit:compatibility 事件钩子详解
浏览器·开发·应用·nuxt.js·插件·兼容性·钩子
Amd7941 个月前
Nuxt.js 应用中的 page:transition:finish 钩子详解
前端开发·nuxt.js·状态管理·钩子函数·ui更新·页面动画·页面过渡
Amd7941 个月前
Nuxt.js 应用中的 page:finish 钩子详解
nuxt.js·性能分析·用户体验·钩子·suspense·状态更新·page:finish