Vue.js路由在Android Chrome中失效问题排查及优化方案求助
我正在用Vue.js、webkit、Firebase、Vuex和Vuefire开发Web应用,做了Google登录成功后的路由跳转功能——用路由守卫来控制权限。这个功能在桌面端和iPhone上都正常,但在Android设备(比如Galaxy 5 5.1.1及其他版本)里完全失效。
当前实现细节
- 登录触发:在
login.vue里用firebase.auth().signInWithRedirect(provider)发起Google登录 - 登录回调处理:在组件的
created钩子中调用firebase.auth().getRedirectResult()获取用户信息,确认登录成功后执行this.$router.replace('/hello')——这个跳转在Android上没效果,但控制台能拿到用户数据,也没报错 - 路由守卫配置:用
router.beforeEach校验登录状态,判断firebase.auth().currentUser是否存在,如果访问需要授权的页面(比如/hello)且未登录,就重定向到landing页面。同时还出现了错误:Uncaught (in promise) Error: PERMISSION_DENIED: Permission denied
求助点
- 这个问题值得提交Issue吗?应该给Firebase还是Vue?
- 求更通用的登录状态校验及跳转实现方案(比如用mixin);
- 排查Android端路由失效的原因。
相关代码
登录方法代码
googleSignUp: function(user) { firebase.auth().signInWithRedirect(provider).then(function(user, result) { var token = result.credential.accessToken; var user = result.user; }).catch(function(error) { var errorCode = error.code; if (errorCode != null) { toastr.error(errorCode)} var errorMessage = error.message; if (errorMessage != null) { toastr.error(errorMessage)} var errorEmail = error.email; if (errorEmail != null) { toastr.error( errorEmail)} var credential = error.credential; if (credential != null) { toastr.error(credential)} }); },
created钩子代码
created() { firebase.auth().getRedirectResult().then(result => { var user = result.user; // I am getting the user auth data! console.log(user); if (user) { toastr.success("You're in!") // This WORKS! this.$router.replace('/hello') // This DOESN'T WORK ON ANDROIDS but works pretty much everywhere else! } }).catch(error => { var errorCode = error.code; if (errorCode != null) { toastr.error(errorCode); console.log("error in getredirect code"); } var errorMessage = error.message; if (errorMessage != null) {toastr.error(errorMessage); console.log("error in getredirect message");} var errorEmail = error.email; if (errorEmail != null) {toastr.error( errorEmail); console.log("error in getredirect email");} var credential = error.credential; if (credential != null) { toastr.error(credential); console.log("error in getredirect credential");} }); }
路由配置代码
import Vue from 'vue' import Router from 'vue-router' import firebase from '../firebase-config'; import {store} from '../store/store.js'; import toastr from 'toastr' import landing from '@/components/landing' import hello from '@/components/hello' import login from '@/components/login' Vue.use(Router) let router = new Router({ mode: 'history', routes: [ { path: '*', redirect: '/landing' }, { path: '/', redirect: '/landing' }, { path: '/landing', name: 'landing', component: landing }, { path: '/login', name: 'login', component: login }, { path: '/hello', name: 'hello', component: hello, meta: { requiresAuth: true } }, ], }) router.beforeEach((to, from, next) => { let currentUser = firebase.auth().currentUser; let requiresAuth = to.matched.some(record => record.meta.requiresAuth); if (requiresAuth && !currentUser) next('landing') // i think this is where the problem is... else next() }) export default router
错误信息
Uncaught (in promise) Error: PERMISSION_DENIED: Permission denied at Repo.js:510 at Object.t.exceptionGuard (util.js:536) at e.callOnCompleteCallback (Repo.js:501) at Repo.js:314 at PersistentConnection.js:402 at t.onDataMessage_ (PersistentConnection.js:435) at e.onDataMessage_ (Connection.js:262) at e.onPrimaryMessageReceived_ (Connection.js:256) at t.onMessage (Connection.js:157) at t.appendFrame_ (WebSocketConnection.js:197)
1. 是否提交Issue?提交给谁?
必须值得提交!优先找Firebase,原因很实在:
- 你这逻辑在桌面、iPhone都跑的好好的,唯独Android拉胯,而且已经确认能拿到用户数据,说明Vue Router本身没毛病,大概率是Firebase在Android环境下的
getRedirectResult或者currentUser状态同步有延迟/异常; - 那个
PERMISSION_DENIED错误一看就是Firebase数据库(Realtime DB或者Firestore)的权限问题,和路由本身无关,但侧面反映了登录状态同步慢的问题,属于Firebase生态的锅; - 提交前先去Firebase的Issue tracker搜搜类似的「Android + 重定向登录」问题,很多时候这类平台兼容性问题早就有人反馈过了。
2. 通用的登录状态校验方案(Mixin+Vuex组合拳)
你现在用firebase.auth().currentUser做路由守卫判断,最大的问题是这个值是异步更新的,很容易出现“刚登录成功但currentUser还是null”的延迟情况。我推荐用Vuex统一管理登录状态,再用Mixin复用权限校验逻辑,稳得一批:
第一步:用Vuex托管登录状态
// store/store.js import Vue from 'vue' import Vuex from 'vuex' import firebase from '../firebase-config' Vue.use(Vuex) export const store = new Vuex.Store({ state: { currentUser: null }, mutations: { SET_USER(state, user) { state.currentUser = user } }, actions: { initAuth({ commit }) { // 用Firebase的状态监听代替直接取currentUser,这才是正确姿势! firebase.auth().onAuthStateChanged(user => { commit('SET_USER', user) }) } }, getters: { isAuthenticated: state => !!state.currentUser } })
第二步:写一个全局权限校验Mixin
// mixins/auth.js import { mapGetters } from 'vuex' export default { computed: { ...mapGetters(['isAuthenticated']) }, beforeRouteEnter(to, from, next) { // 进入路由前先校验权限 next(vm => { if (to.meta.requiresAuth && !vm.isAuthenticated) { vm.$toastr.error('请先登录哦~') next('/login') } else { next() } }) }, beforeRouteUpdate(to, from, next) { // 路由参数变化时也重新校验(比如从/hello/1到/hello/2) if (to.meta.requiresAuth && !this.isAuthenticated) { this.$toastr.error('请先登录哦~') next('/login') } else { next() } } }
第三步:在Vue实例初始化时启动Auth监听
// main.js import { store } from './store/store' new Vue({ el: '#app', store, router, created() { // 启动Firebase用户状态监听,一有变化就更新Vuex this.$store.dispatch('initAuth') }, render: h => h(App) })
第四步:替换旧的路由守卫,用Mixin搞定权限
把原来的router.beforeEach删掉,然后选一种方式用Mixin:
- 全局注册:所有组件都自动带权限校验(适合大部分页面都需要权限的场景)
// main.js里加这行 import authMixin from './mixins/auth' Vue.mixin(authMixin)
- 局部引入:只在需要权限的组件里用(比如
hello.vue)
// hello.vue import authMixin from '../mixins/auth' export default { mixins: [authMixin], // ...你的组件代码 }
这个方案的好处:
- 用
onAuthStateChanged监听状态,比直接取currentUser靠谱100倍,彻底避免异步延迟; - Vuex统一管理状态,所有组件都能实时拿到最新的登录状态;
- Mixin复用逻辑,不用在路由守卫里写一堆复杂判断,维护起来贼方便。
3. Android端路由失效的排查&修复思路
结合你的代码和现象,我敢打赌大概率是Firebase登录状态同步延迟导致路由守卫把跳转拦截了:
- 你在
created里调用this.$router.replace('/hello')时,Android环境下firebase.auth().currentUser还没更新成登录后的用户,这时候路由守卫一看“需要授权但currentUser是null”,直接给你重定向回landing了; - 那个
PERMISSION_DENIED错误,应该是你的Firebase数据库权限要求用户登录,但此时currentUser还没同步,导致访问数据库被拒,这个错误和路由跳转无关,但暴露了状态同步慢的问题。
具体排查&修复步骤:
- 给路由守卫加日志,抓现行:
在路由守卫里加两行日志,看看跳转/hello时currentUser到底是什么值:
router.beforeEach((to, from, next) => { let currentUser = firebase.auth().currentUser; console.log('路由守卫:当前页面', to.path, 'currentUser:', currentUser); // 加这行 let requiresAuth = to.matched.some(record => record.meta.requiresAuth); if (requiresAuth && !currentUser) { console.log('拦截跳转!因为没检测到登录状态'); // 加这行 next('landing') } else { next() } })
然后用Chrome远程调试连Android设备,看控制台日志,是不是跳转时currentUser还是null,过几秒才变成有值?如果是,那就是状态同步延迟的问题。
- 把跳转逻辑改成监听用户状态变化触发:
getRedirectResult在Android上可能有回调延迟,换成onAuthStateChanged来触发跳转,确保只有登录状态真正更新后才跳转:
// login.vue的created钩子 created() { // 监听用户状态变化,只触发一次就取消监听 const unsubscribe = firebase.auth().onAuthStateChanged(user => { if (user) { toastr.success("You're in!") this.$router.replace('/hello') unsubscribe() // 登录成功后就不用再监听了 } }) }
这样路由守卫就能检测到已经登录,不会拦截跳转了。
检查Android设备的WebView版本:
Galaxy 5 5.1.1的WebView版本特别老,可能不支持某些ES6特性或者Firebase的最新API。你可以试试把Firebase降级到兼容旧WebView的版本,或者在项目里加Babel转译,确保代码能在旧WebView里跑。测试路由本身是否正常:
在login.vue里加个按钮,点击直接调用this.$router.replace('/hello'),看看Android上能不能跳转。如果能跳转,说明路由本身没问题,就是登录状态同步的时机不对。
内容的提问来源于stack exchange,提问作者Joao Alves Marrucho

