📝 业务背景:被微信规则折磨的开发者们

做过微信小程序的开发者都知道,小程序的登录授权规则可谓是“一年一小改,三年一大改”。 现在的最新规则是:

  1. 不再支持一键获取头像昵称,必须使用 <button open-type="chooseAvatar">type="nickname" 引导用户手动填写。

  2. 手机号授权不再返回明文,而是返回一个动态 code,必须交由后端去微信服务器解密。

除此之外,在真实业务场景中,我们还会面临一个极其棘手的“页面路由与状态恢复”问题: 用户在【业务页】(如商品详情)点击购买,由于未登录被拦截到【登录页】,登录后又发现没绑手机号,被强制跳转到【补充资料页】。当资料填完后,如何优雅地连退两层回到业务页,并自动继续刚才的购买动作

本文将结合 UniApp 实战,给出一套大厂级的小程序登录授权+多级路由回跳的终极解决方案


💡 核心架构:化繁为简的“三步走”策略

为了保证数据的一致性和极佳的用户体验,我们将登录切分为以下三个阶段:

  1. 静默登录:调用 wx.login 获取 code 换取 Token。

  2. 资料补充(三合一):前端收集“头像临时路径 + 昵称 + 手机号授权 Code”,整合成一个接口发给后端,开启事务统一下库,坚决不产生脏数据。

  3. 路由接力通信:废弃粗暴的 delta: 2 跨级后退,采用 EventChannel 实现“退1层交接棒”的安全通信。


🛠️ 实战代码揭秘

环节一:补充资料页(头像 + 昵称 + 手机号一次性搞定)

这里最大的坑在于:如何处理手机号的动态 Code? 很多新手会让前端调两次接口(先绑手机,再更资料),这极易导致网络波动下的数据不同步。标准的做法是:前端只存 code,最后和资料一起打包发给后端 updateUser

<template>
  <view class="form-wrap">
    <button open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
      <image :src="formData.avatar" mode="aspectFill"></image>
    </button>
    
    <input type="nickname" v-model="formData.nickname" @blur="onNicknameBlur" />
    
    <button open-type="getPhoneNumber" @getphonenumber="onGetPhone">获取手机号</button>
    
    <button @click="submitProfile">确定提交</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      formData: { avatar: '', nickname: '' },
      phoneCode: '' // 核心:只存 code,不强求明文
    }
  },
  methods: {
    onGetPhone(e) {
      if (e.detail.errMsg === 'getPhoneNumber:ok') {
        this.phoneCode = e.detail.code; // 拿到动态授权码
      }
    },
    async submitProfile() {
      // 1. 上传头像拿到真实 URL (略)
      // 2. 🌟 终极合并调用:把头像、昵称、手机号Code 一起发给后端!
      await api.updateUser({
        headImage: this.formData.avatar,
        nickName: this.formData.nickname,
        phoneCode: this.phoneCode // 后端拿这个 code 去解密手机号存库
      });
      
      uni.showToast({ title: '资料完善成功' });

      // 3. 🌟 核心路由技巧:绝不使用 delta: 2 直接跨级杀页面!
      // 而是退 1 层,把成功信号交给 Login 页去传递。
      setTimeout(() => {
        uni.navigateBack({ delta: 1 });
      }, 500);
    }
  },
  onUnload() {
    // 页面销毁前,向上一级 (Login页) 发送完成信号
    const eventChannel = this.getOpenerEventChannel();
    if (eventChannel && eventChannel.emit) {
      eventChannel.emit('ProfileFlowDone');
    }
  }
}
</script>

环节二:Login 页的“路由接力棒”(极其重要)

当前的页面栈是:[业务页] -> [登录页] -> [资料页]。 如果我们在资料页直接执行 delta: 2 退两层,登录页会被微信引擎瞬间销毁,它挂载的事件监听器会全部失效,导致业务页永远收不到“登录成功”的信号,API 请求彻底卡死。

终极解法:让 Login 页做“中间人”,接力传信号!

<script>
export default {
  data() {
    return {
      isLoginFlowSuccess: false // 标记整个登录流是否成功
    }
  },
  methods: {
    goToProfile() {
      uni.navigateTo({
        url: '/pages/profile-supplement',
        events: {
          // 监听从资料页发来的信号
          ProfileFlowDone: () => {
             this.isLoginFlowSuccess = true;
             // 拿到信号后,登录页主动自杀,退回业务页
             uni.navigateBack({ delta: 1 });
          }
        }
      });
    }
  },
  // 🌟 神级生命周期控制
  onUnload() {
    const eventChannel = this.getOpenerEventChannel();
    if (this.isLoginFlowSuccess) {
      // 如果登录+资料全搞定了,通知业务页重发请求!
      eventChannel.emit('LoginSuccess');
    } else {
      // 如果用户中途点左上角退出了,通知业务页取消 Loading
      eventChannel.emit('LoginCancel');
    }
  }
}
</script>

环节三:业务页(触发者)无缝恢复状态

在底层 request.js 拦截到 401(Token失效)时,或者用户点击“立即购买”时,拉起登录页并监听结果:

// 业务页触发登录
uni.navigateTo({
  url: '/pages/login/login',
  events: {
    LoginSuccess: () => {
      console.log('用户已完美完成登录与资料填写!');
      // 这里可以立刻重发刚才被拦截的接口,或者继续执行购买逻辑
      this.fetchData(); 
    },
    LoginCancel: () => {
      console.log('用户中途放弃了登录');
      uni.showToast({ title: '需登录后才能操作' });
    }
  }
});

🏆 总结与避坑指南

  1. 别再执念于前端获取明文手机号:微信新规下,前端只能拿到 phoneCode。直接把 code 扔给后端,让后端去解密并开启数据库事务一并更新用户信息,这才是最安全、数据最一致的做法。

  2. 警惕 uni.navigateBack({ delta: N }) 的跨级谋杀:在涉及到跨页面的 EventChannel 通信时,永远不要用跨级后退去“暗杀”中间页面。老老实实退 1 层接力传递,或者改用全局 uni.$emit,才能保证回调函数被安全执行。

  3. 延迟回退抵消动画:在执行 MapsBack 前加上 500mssetTimeout,不仅能让成功提示框(Toast)被用户看清,还能避免页面切换过快导致的动画重影 Bug。

希望这套经过无数次生产环境毒打的登录路由方案,能帮你少走弯路!欢迎在评论区交流你在小程序登录中遇到的坑!👇

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐