UniApp实现未登录跳转登录页的完整方案

9 次阅读

一、明确登录状态与页面访问权限

UniApp项目中,“未登录跳转登录页”通常不是单纯判断一个变量这么简单。实际开发时需要同时考虑页面类型、登录状态、Token有效期、跳转方式以及登录后的返回路径。

比较常见的页面可以分成两类:

  • 公开页面:首页、商品列表、文章详情等,未登录也可以访问。

  • 受保护页面:个人中心、订单列表、收藏、支付、账户设置等,必须登录后才能进入。

因此,更合理的做法是建立统一的登录状态判断机制,而不是在每个页面中重复编写 if (!token)。

例如,可以将登录凭证保存到本地:

JavaScript
const token = uni.getStorageSync('token')

if (!token) {
  uni.navigateTo({
    url: '/pages/login/login'
  })
}

这种方式虽然简单,但随着项目规模扩大,很容易出现大量重复代码,也不方便统一维护。


二、使用Token判断用户是否登录

前端项目通常会通过 Token 判断用户当前是否处于登录状态。

登录成功后保存 Token:

JavaScript
uni.setStorageSync('token', res.data.token)

需要访问用户信息时读取:

JavaScript
const token = uni.getStorageSync('token')

if (token) {
  console.log('用户已登录')
} else {
  console.log('用户未登录')
}

退出登录时清除:

JavaScript
uni.removeStorageSync('token')

如果项目同时保存了用户信息,也可以一起清理:

JavaScript
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')

需要注意的是,本地存在 Token 并不意味着 Token 一定有效。例如 Token 可能已经过期,因此生产项目还应该结合接口返回状态码处理登录失效问题。


三、页面级实现未登录跳转

对于某些特定页面,可以直接在页面生命周期中进行判断。

例如个人中心页面:

JavaScript
export default {
  onShow() {
    const token = uni.getStorageSync('token')

    if (!token) {
      uni.navigateTo({
        url: '/pages/login/login'
      })
      return
    }

    this.loadUserInfo()
  },

  methods: {
    loadUserInfo() {
      console.log('加载用户信息')
    }
  }
}

这里使用 onShow 而不是单纯使用 onLoad 有一个实际好处:用户从登录页返回后,页面再次显示时可以重新检查登录状态。

不过,如果项目中存在很多需要登录的页面,每个页面都这样处理,会产生明显的重复代码。


四、通过页面白名单统一控制登录

更推荐的方式是维护一个“不需要登录”的页面白名单。

例如:

JavaScript
const whiteList = [
  '/pages/index/index',
  '/pages/login/login',
  '/pages/register/register',
  '/pages/article/detail'
]

然后创建统一判断方法:

JavaScript
export function checkLogin(url) {
  const token = uni.getStorageSync('token')

  if (whiteList.includes(url)) {
    return true
  }

  if (!token) {
    uni.navigateTo({
      url: `/pages/login/login?redirect=${encodeURIComponent(url)}`
    })

    return false
  }

  return true
}

这种设计将“哪些页面需要登录”和“如何跳转登录页”分离开来,后期新增业务页面时,只需要决定该页面是否加入白名单即可。


五、通过uni.addInterceptor拦截页面跳转

UniApp提供了页面跳转拦截能力,可以针对 navigateTo、redirectTo、reLaunch 等行为进行统一控制。

例如:

JavaScript
const whiteList = [
  '/pages/index/index',
  '/pages/login/login'
]

uni.addInterceptor('navigateTo', {
  invoke(args) {
    const token = uni.getStorageSync('token')

    if (whiteList.includes(args.url.split('?')[0])) {
      return true
    }

    if (!token) {
      uni.navigateTo({
        url: `/pages/login/login?redirect=${encodeURIComponent(args.url)}`
      })

      return false
    }

    return true
  }
})

这样,当用户执行:

JavaScript
uni.navigateTo({
  url: '/pages/order/order'
})

如果没有 Token,就会被拦截并跳转到登录页面。

不过这种方案主要针对程序主动发起的页面跳转。对于底部 TabBar 页面、启动页面以及部分特殊导航场景,还需要结合页面生命周期进行处理,不能简单认为一个拦截器就能覆盖所有登录控制需求。


六、登录后自动返回原页面

用户体验比较好的方案并不是“未登录就跳登录页”,而是:

用户访问受保护页面 → 跳转登录 → 登录成功 → 自动返回原页面。

例如用户原本准备进入:

/pages/order/order

发现没有登录后:

JavaScript
uni.navigateTo({
  url: `/pages/login/login?redirect=${encodeURIComponent('/pages/order/order')}`
})

登录页面获取参数:

JavaScript
export default {
  onLoad(options) {
    this.redirect = options.redirect || ''
  }
}

登录成功后:

JavaScript
methods: {
  loginSuccess(token) {
    uni.setStorageSync('token', token)

    if (this.redirect) {
      uni.redirectTo({
        url: decodeURIComponent(this.redirect)
      })
    } else {
      uni.switchTab({
        url: '/pages/index/index'
      })
    }
  }
}

这种方式可以明显减少用户重复操作。

例如用户点击“我的订单”,系统发现未登录,进入登录页。登录成功后直接进入订单页面,而不是重新让用户寻找刚才的功能。


七、处理TabBar页面的特殊跳转

UniApp中的 TabBar 页面不能简单使用 navigateTo 跳转。

例如:

JavaScript
uni.switchTab({
  url: '/pages/user/user'
})

如果登录页需要返回的是 TabBar 页面,就应该根据目标页面类型选择对应的 API。

常见跳转方式可以这样区分:

JavaScript
uni.navigateTo({
  url: '/pages/order/order'
})

适用于普通页面。

JavaScript
uni.redirectTo({
  url: '/pages/order/order'
})

适用于关闭当前页面后进入目标页面。

JavaScript
uni.reLaunch({
  url: '/pages/index/index'
})

适用于重新启动页面栈。

JavaScript
uni.switchTab({
  url: '/pages/user/user'
})

适用于 TabBar 页面。

因此,登录成功后的返回逻辑最好不要简单写死成一个 API,而应该根据目标页面类型进行处理。


八、封装统一的登录检查方法

实际项目中,可以创建一个 auth.js 文件。

JavaScript
const whiteList = [
  '/pages/index/index',
  '/pages/login/login',
  '/pages/register/register'
]

export function isLogin() {
  return !!uni.getStorageSync('token')
}

export function requireLogin(redirect = '') {
  if (isLogin()) {
    return true
  }

  const target = redirect || '/pages/index/index'

  uni.navigateTo({
    url: `/pages/login/login?redirect=${encodeURIComponent(target)}`
  })

  return false
}

export function logout() {
  uni.removeStorageSync('token')
  uni.removeStorageSync('userInfo')
}

业务页面只需要调用:

JavaScript
import { requireLogin } from '@/utils/auth.js'

export default {
  onShow() {
    if (!requireLogin('/pages/order/order')) {
      return
    }

    this.getOrderList()
  }
}

这样可以将登录逻辑集中管理。


九、登录状态失效时自动跳转

只检查本地 Token 还不够。

例如用户登录后保存了 Token:

token = abc123

几天后服务器已经将该 Token 判定为过期,但浏览器本地仍然保存着 abc123。

此时:

JavaScript
uni.getStorageSync('token')

仍然能够获取到值,因此单纯判断 Token 是否存在会产生误判。

更可靠的方式是在网络请求封装层统一处理,例如:

JavaScript
uni.request({
  url: baseUrl + '/api/order/list',
  method: 'GET',
  header: {
    Authorization: `Bearer ${uni.getStorageSync('token')}`
  },
  success(res) {
    if (res.statusCode === 401) {
      uni.removeStorageSync('token')

      uni.navigateTo({
        url: '/pages/login/login'
      })

      return
    }

    // 正常处理业务数据
  }
})

实际项目一般会进一步封装 request 方法,让所有接口统一处理 401、Token过期和登录失效。


十、避免重复跳转登录页

一个常见问题是:多个接口同时返回未登录状态,可能连续执行多次:

JavaScript
uni.navigateTo({
  url: '/pages/login/login'
})

最终可能出现多个登录页面堆叠。

可以增加一个跳转锁:

JavaScript
let isRedirecting = false

export function goLogin() {
  if (isRedirecting) {
    return
  }

  isRedirecting = true

  uni.navigateTo({
    url: '/pages/login/login',
    complete() {
      setTimeout(() => {
        isRedirecting = false
      }, 500)
    }
  })
}

之后统一使用:

JavaScript
goLogin()

而不是在各个业务模块中直接调用 navigateTo。


十一、推荐的完整实现结构

一个相对清晰的 UniApp 登录控制结构可以设计为:

项目
├── pages
│   ├── index
│   ├── login
│   ├── order
│   └── user
├── utils
│   ├── auth.js
│   └── request.js
├── store
│   └── user.js
└── App.vue

其中:

auth.js

负责登录状态、退出登录和登录跳转。

request.js

负责 Token 注入、接口请求以及 401 登录失效处理。

user.js

负责集中维护用户信息和登录状态。

页面生命周期

负责页面进入时的权限检查。

这种职责划分比把所有代码塞进 App.vue 更容易维护。


十二、Vue 3 Composition API中的实现方式

如果使用 UniApp + Vue 3,可以进一步采用组合式函数封装。

例如:

JavaScript
// composables/useAuth.js

import { computed } from 'vue'

export function useAuth() {
  const token = computed(() => uni.getStorageSync('token'))

  const isLogin = computed(() => !!token.value)

  const login = (tokenValue) => {
    uni.setStorageSync('token', tokenValue)
  }

  const logout = () => {
    uni.removeStorageSync('token')
    uni.removeStorageSync('userInfo')
  }

  const checkLogin = (redirect) => {
    if (isLogin.value) {
      return true
    }

    uni.navigateTo({
      url: `/pages/login/login?redirect=${encodeURIComponent(redirect)}`
    })

    return false
  }

  return {
    token,
    isLogin,
    login,
    logout,
    checkLogin
  }
}

页面中可以这样使用:

JavaScript
import { useAuth } from '@/composables/useAuth.js'

const { isLogin, checkLogin } = useAuth()

const openOrder = () => {
  if (!checkLogin('/pages/order/order')) {
    return
  }

  uni.navigateTo({
    url: '/pages/order/order'
  })
}

对于采用 Vue 3 开发的中大型 UniApp 项目,这种方式可以减少业务代码和认证逻辑之间的耦合。


十三、登录跳转方案的选择建议

不同项目可以采用不同方案:

方案适用场景推荐程度
页面内直接判断Token小型项目、少量受保护页面★★★
白名单统一判断中小型项目★★★★
页面跳转拦截有统一路由控制需求的项目★★★★
请求层处理401所有需要Token认证的项目★★★★★
Auth封装 + 请求拦截中大型项目★★★★★

比较稳妥的组合通常是:

页面权限控制 + Auth统一封装 + 请求层401处理 + 登录后返回原页面。

这样既能解决“用户未登录无法进入页面”的问题,也能处理“登录状态后来失效”的情况。


十四、开发时容易踩到的坑

1. 只判断Token是否存在

JavaScript
if (uni.getStorageSync('token')) {
  // 已登录
}

只能说明本地存在 Token,并不能证明服务器端认证仍然有效。

2. 登录页本身被拦截

如果没有把登录页加入白名单,就可能出现:

未登录
 ↓
访问登录页
 ↓
权限检查
 ↓
发现未登录
 ↓
再次跳登录页

最终形成重复跳转。

3. 忽略TabBar页面

switchTab 与 navigateTo 的使用规则不同,登录后的目标页面如果属于 TabBar,就应该使用正确的导航方式。

4. 没有保存redirect

没有保存原始访问地址时,登录成功后只能返回固定首页,用户体验较差。

5. 退出登录后没有清理用户数据

如果只删除 Token,却保留旧的 userInfo,页面可能短时间显示已经退出账号的用户信息。

6. 多处重复实现登录逻辑

项目中同时存在:

JavaScript
if (!token) {}
JavaScript
if (!isLogin()) {}

以及多个不同版本的登录跳转代码,后期非常容易产生行为不一致的问题。


十五、一个更完整的业务流程

实际应用中,可以将整个认证流程设计成:

用户访问页面
      ↓
判断页面是否需要登录
      ↓
   ┌──是──→ 检查Token
   │          ↓
   │      Token有效?
   │       ↙      ↘
   │     是        否
   │     ↓         ↓
   │  正常进入   保存当前页面
   │               ↓
   │             登录页
   │               ↓
   │           登录成功
   │               ↓
   │          保存新Token
   │               ↓
   │          返回原页面
   │
   └──否──→ 直接访问

接口请求则独立处理:

发起接口请求
      ↓
携带Token
      ↓
服务器验证
      ↓
  返回401?
   ↙    ↘
 是      否
 ↓        ↓
清理Token  正常处理
 ↓
跳转登录页

前端页面权限和后端接口权限形成两层保护,可以避免仅依赖前端判断带来的安全问题。


十六、前端登录判断不能代替后端鉴权

需要特别强调,UniApp中的登录拦截主要解决的是用户体验和页面访问控制,不能作为真正的安全边界。

例如:

JavaScript
if (token) {
  uni.navigateTo({
    url: '/pages/admin/admin'
  })
}

只能控制普通用户界面上的页面跳转。

真正的接口权限仍然必须由服务器验证 Token、用户身份以及操作权限。

因此,即使用户通过修改客户端代码绕过页面判断,也不应该能够通过接口直接获取无权限的数据。

一个可靠的系统应该遵循:

前端:控制页面访问体验
      +
后端:验证身份与权限

两者缺一不可。


十七、推荐的最终方案

如果是普通的 UniApp 项目,可以采用以下思路:

  1. 登录成功后保存 Token。

  2. 使用 Auth 模块统一判断登录状态。

  3. 设置公开页面白名单。

  4. 受保护页面进入时检查登录状态。

  5. 未登录时保存当前页面路径。

  6. 跳转到登录页面。

  7. 登录成功后恢复原页面。

  8. 请求层统一携带 Token。

  9. 收到 401 时清理失效 Token。

  10. 避免重复跳转登录页面。

  11. 对 TabBar 页面使用 switchTab。

  12. 后端始终进行真正的身份和权限验证。

对于小型项目,可以直接使用页面生命周期判断;对于中大型项目,则建议将 Auth、路由控制、请求拦截、Token管理和登录回跳 分开设计。这样不仅能够实现 UniApp 未登录跳转登录页,还能形成一套完整、稳定且易于扩展的用户认证机制。