ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

微信小程序登入界面实战:从布局到token管理的完整指南

微信小程序登入界面实战:从布局到token管理的完整指南 简介本资源是一份面向微信小程序初学者与前端开发者的登录界面实战项目聚焦用户授权、头像昵称获取与界面渲染等核心功能实现。适用于课程设计、毕业项目或快速搭建小程序首屏场景帮助开发者掌握微信登录流程的关键技术点。压缩包共10个文件7KB包含4个JSON配置文件如app.json、project.config.json、2个JS逻辑文件处理wx.login与getUserInfo、2个WXSS样式文件定义登录页布局与视觉、1个WXML模板文件构建UI结构及1张示例图片目录结构简洁规范便于理解小程序基础页面构成。已有1595人学习下载资源提供完整可运行的登录界面代码涵盖授权弹窗触发、数据绑定展示、生命周期调用时机及基础样式适配开箱即用适合边学边练、快速验证授权逻辑与UI渲染效果。 做微信小程序登入界面看起来是个入门级需求但真正动手做过的人都知道这里面坑不少。从账号密码登录到手机号快捷登录从token管理到会话过期从rpx适配到安全区避让每一步都藏着细节。这篇内容我就把做登入界面时踩过的坑、总结的经验、还能直接抄的代码一起聊透适合刚接触微信小程序开发的新手也适合那些想把自己登入模块做得更规范的朋友。1. 登入界面的核心先想清楚要解决什么问题1.1 登入界面不只是“放两个输入框”很多第一次做微信小程序登入界面的人第一反应是照着App登录页抄一个——上面一个logo中间手机号密码输入框下面一个登录按钮完事。真上线了才发现问题一个接一个有的用户收不到验证码有的用户点了登录没反应有的用户登录成功后下次打开又变成未登录状态。登入界面的本质是在做三件事收集用户身份信息、建立会话凭证、管理登录状态。用户输入手机号和密码只是第一步后续的验证码发送、登录态写入、token缓存、过期续期哪一个环节断了登入界面都算失败。所以做之前先别急着写WXML先把整个登录链路在脑子里过一遍用户怎么进来的、信息怎么传的、服务端怎么验的、状态怎么存的、失效怎么处理的。在微信小程序里登入还有一个特殊背景微信官方推荐使用wx.login换取 code再由后端拿着 code 去微信接口换取 openid 和 session_key用它来建立你自己的账号体系。但实际项目里很多业务是基于手机号绑定用户的所以常见的做法是“微信授权 手机号绑定”双轨并行。我在做的时候习惯于这样设计新用户直接用手机号登录登录成功后自动创建账号老用户直接密码登录或者短信验证码登录。这样既绕开了微信生态账号绑定的复杂度又保证了用户在多端小程序、App、H5使用的统一性。1.2 需求侧拆解登入界面该有哪些功能模块把登入界面拆开看至少需要这几个模块账号输入区手机号/用户名、密码输入区或验证码输入区、登入按钮、协议勾选、其他登录方式。如果做手机号验证码登录还涉及验证码获取按钮和倒计时逻辑。这里容易被忽略的是协议勾选——现在各大平台审核都会看这个如果你不勾选就提交登录应该被拦截并提示。宁可做的时候多写几行校验也别让审核打回来再补。另外还有一个容易被忽略的点错误提示。用户输错手机号、密码格式不对、验证码过期、账号被锁定每种情况都要有明确的提示文案。很多人直接用微信默认的wx.showToast但toast几秒就消失用户来不及看。我的习惯是格式错误用输入框下方的红字提示请求失败用toast提示这些交互细节直接影响登入界面的体验评分。2. 界面布局与样式设计从设计稿到小程序落地2.1 初始化项目结构与页面搭建新建一个微信小程序页面最常规的操作是在app.json里注册页面路径然后在pages目录下创建对应的文件夹。登入界面我一般命名为pages/login/login因为后续其他页面跳转时语义最清晰。{ pages: [ pages/login/login, pages/index/index ] }然后在login.json里做导航栏配置。登入界面通常希望用户沉浸式操作所以我会设置成自定义导航或者浅色导航去掉默认的黑色标题栏。{ navigationBarTitleText: 登入, navigationBarBackgroundColor: #ffffff, navigationBarTextStyle: black }如果你想要那种顶部完全沉浸式效果可以在app.json里设置navigationStyle: custom但这时候wx.getSystemInfoSync()里的状态栏高度就需要你自己处理了不然内容会被刘海屏和状态栏遮住。这块我后面会专门讲。2.2 WXML骨架搭建登入表单结构登入页面的 WXML 结构相对固定常见的排列是顶部品牌区、中间表单区、底部登录按钮和第三方登录区。下面是我经常用的一个模板兼容密码登录和验证码登录两种模式view classcontainer view classheader image classlogo src/assets/logo.png modeaspectFit / text classapp-name某某应用/text text classslogan欢迎登入/text /view view classform view classinput-group input classinput typenumber placeholder请输入手机号 maxlength11 value{{phone}} bindinputonPhoneInput / /view view classinput-group wx:if{{loginMode password}} input classinput password placeholder请输入密码 value{{password}} bindinputonPasswordInput confirm-typedone bindconfirmonLogin / /view view classinput-group wx:else input classinput typenumber placeholder请输入验证码 maxlength6 value{{code}} bindinputonCodeInput / button classcode-btn bindtapsendCode{{countdown 0 ? countdown s后重发 : 获取验证码}}/button /view view classswitch-mode bindtapswitchLoginMode {{loginMode password ? 验证码登入 : 密码登入}} /view button classlogin-btn bindtaponLogin loading{{loading}}登 入/button view classagreement radio classradio checked{{agreed}} bindtaptoggleAgree / text我已阅读并同意/text text classlink bindtapopenAgreement《用户协议》/text text和/text text classlink bindtapopenPrivacy《隐私政策》/text /view /view /view这里有几个值得注意的细节。输入框的typenumber在手机号输入时会调起数字键盘这个体验更好。maxlength一定要限制防止用户多输。密码框要用password属性而不是typepassword这是小程序和Web的差异点写错的话在部分安卓机上会有兼容问题。协议勾选这里我用了radio组件的样式但更简单的做法是自定义一个勾选状态的图标因为 radio 默认样式在不同机型上表现不一致。我实际项目里是放了两张图片选中态和未选中态用wx:if切换这样样式最稳定。2.3 WXSS样式Flex布局、rpx适配与安全区避让微信小程序的样式单位推荐用rpx它可以根据屏幕宽度自动适配。750rpx等于屏幕宽度所以你在设计稿上量到的像素值直接乘以2就是rpx的值以iPhone 6的750px设计稿为基准。这个换算逻辑是rpx px * (750 / 设计稿宽度)如果设计稿宽度是750那就1:1写即可。表单区域通常用flex纵向排列。居中是最常用的布局方式这里直接给一段我用得顺手的样式.container { min-height: 100vh; background: linear-gradient(180deg, #f8f9fc 0%, #ffffff 40%); display: flex; flex-direction: column; align-items: center; padding: 120rpx 64rpx 0; box-sizing: border-box; } .header { display: flex; flex-direction: column; align-items: center; margin-bottom: 96rpx; } .logo { width: 160rpx; height: 160rpx; border-radius: 32rpx; } .app-name { margin-top: 24rpx; font-size: 44rpx; font-weight: 600; color: #1a1a1a; } .form { width: 100%; } .input-group { display: flex; align-items: center; background: #f5f6f8; border-radius: 16rpx; padding: 28rpx 32rpx; margin-bottom: 32rpx; } .input { flex: 1; font-size: 30rpx; height: 48rpx; } .login-btn { margin-top: 48rpx; width: 100%; height: 96rpx; line-height: 96rpx; background: #3b7cff; color: #ffffff; font-size: 34rpx; border-radius: 48rpx; border: none; } .login-btn[disabled] { background: #a8c6ff; color: #ffffff; }背景色加了渐变比较符合现在主流App登入页的设计风格。输入框底色用浅灰和页面背景形成区分用户视觉聚焦点更清晰。login-btn用胶囊样式圆角很大这是小程序登录按钮的主流审美。安全区适配是很多新手容易忽略的点。iPhone X之后的机型底部有Home Indicator横条如果你的页面上有底部固定按钮不做安全区适配就会出现按钮被遮挡的尴尬。解决办法是用env(safe-area-inset-bottom)这个属性对应的是微信小程序内置的CSS变量.safe-bottom { padding-bottom: constant(safe-area-inset-bottom); /* iOS 11.0-11.2 */ padding-bottom: env(safe-area-inset-bottom); /* iOS 11.2 */ }如果你用了自定义导航栏还需要处理顶部状态栏高度。在app.js的onLaunch里获取系统信息App({ onLaunch() { const systemInfo wx.getSystemInfoSync(); this.globalData.statusBarHeight systemInfo.statusBarHeight; this.globalData.navigationBarHeight systemInfo.statusBarHeight 44; } })然后在页面的onLoad里读取并设置占位view的高度。44是微信导航栏的标准高度单位是px这里不能用rpx。3. 登入逻辑与后端交互不止是按钮跳转3.1 微信小程序登录态原理wx.login到底做了什么先聊清楚一个基础概念微信小程序的wx.login不是用来做用户登录的它是用来获取登录凭证code的这个code有效期只有5分钟并且只能使用一次。它的作用在于让后端拿到code后调用微信的code2Session接口换取openid用户在小程序里的唯一标识和session_key会话密钥。所以标准流程是前端调用wx.login()获取code前端把code发给自己的后端后端拿着code请求微信接口https://api.weixin.qq.com/sns/jscode2session微信返回 openid、session_key、unionid后端在自己数据库里查找或创建该用户后端生成自己的登录凭证token返回给前端前端把token存到wx.setStorageSync里后续请求带上这个过程openid 永远不会暴露给前端。因为code2Session接口必须用小程序AppSecret调用而AppSecret绝对不能放在小程序前端代码里否则任何人都能通过反编译拿到你的密钥后果就是别人可以任意操作你的用户数据。我自己在项目里是这么封装的先封装一个login.js作为登录模块的核心function wxLogin() { return new Promise((resolve, reject) { wx.login({ success: (res) { if (res.code) { resolve(res.code); } else { reject(new Error(wx.login 获取code失败)); } }, fail: reject }); }); } function loginWithCode(code) { return request({ url: /api/auth/login, method: POST, data: { code } }); }实际调用时我先wxLogin拿code再传给后端换取业务token。这里有个经验wx.login在用户登录过的前提下会直接用本地缓存的session不会每次都走网络所以速度很快。但如果你希望强制刷新可以传false参数比如wx.login({ force: true })。3.2 request封装与token管理登入成功之后怎么办登入成功之后token的存储和管理是整个登入模块的骨架。很多新手把token直接存在本地存储里然后在每个页面里手动取这是最原始的做法代码里到处是wx.getStorageSync(token)一旦接口报401还得在每个请求里判断项目大了就乱成一团。我推荐的做法是封装一个全局的request方法统一处理token注入、过期判断、错误码拦截const request (options) { const token wx.getStorageSync(token); return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${options.url}, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: token ? Bearer ${token} : }, success: (res) { if (res.statusCode 200 res.data.code 0) { resolve(res.data.data); } else if (res.statusCode 401) { // token过期清理登录态跳转登入页 handleTokenExpired(); reject(new Error(登录过期)); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(new Error(res.data.msg || 请求失败)); } }, fail: reject }); }); };这里有个关键设计后端返回的code要和HTTP状态码区分开。我用的约定是HTTP 200保证请求通body里的code0表示业务成功401表示token失效。这样前端就知道HTTP层的错误和业务层的错误是两回事。3.3 表单校验与协议勾选该挡的地方必须挡登入界面的校验逻辑比很多人想象中要多。手机号格式、密码长度、验证码位数、协议是否勾选每一项都要在提交之前检查。我见过的项目里很多人只在后端校验前端不校验结果用户输了个不完整的手机号也要点一下登录然后等接口返回错误体感很差。我一般会在data里维护一个表单状态对象然后通过bindinput事件实时更新Page({ data: { phone: , password: , code: , agreed: false, loginMode: password, countdown: 0, loading: false }, onPhoneInput(e) { this.setData({ phone: e.detail.value }); }, validate() { const { phone, password, code, loginMode, agreed } this.data; if (!phone || phone.length ! 11) { wx.showToast({ title: 请输入正确的手机号, icon: none }); return false; } if (!agreed) { wx.showToast({ title: 请先阅读并同意用户协议, icon: none }); return false; } if (loginMode password) { if (!password || password.length 6) { wx.showToast({ title: 密码至少6位, icon: none }); return false; } } else { if (!code || code.length ! 6) { wx.showToast({ title: 请输入6位验证码, icon: none }); return false; } } return true; }, async onLogin() { if (!this.validate()) return; this.setData({ loading: true }); try { const { phone, password, code, loginMode } this.data; if (loginMode password) { await this.passwordLogin(phone, password); } else { await this.codeLogin(phone, code); } wx.showToast({ title: 登入成功, icon: success }); setTimeout(() { wx.switchTab({ url: /pages/index/index }); }, 500); } catch (error) { console.error(login error:, error); } finally { this.setData({ loading: false }); } } });这里校验用了短路逻辑任何一个条件不满足就直接失败不继续往下执行。密码校验的最小长度建议6位如果业务上没特殊要求别搞太复杂。太复杂的密码策略会让用户懒得注册。协议勾选这里我见过有些人只在按钮上做了拦截但是用户完全不知道要先去勾选协议所以登入界面应该把“我已阅读并同意”做成一行明显的提示常驻在按钮上方而不是放在某个折叠区域内。3.4 手机号快捷登入微信生态专属能力微信小程序里有一个特别的登入方式——button的open-typegetPhoneNumber。用户点击后微信会弹窗让用户授权手机号授权成功后返回加密的手机号数据由后端解密获取真实手机号。button open-typegetPhoneNumber bindgetphonenumberonGetPhoneNumber 微信手机号快捷登入 /buttonasync onGetPhoneNumber(e) { if (e.detail.errMsg ! getPhoneNumber:ok) { wx.showToast({ title: 已取消授权, icon: none }); return; } const { code, encryptedData, iv } e.detail; // 将code、encryptedData、iv传给后端 // 后端用session_key解密获取手机号 const res await request({ url: /api/auth/phone-login, method: POST, data: { code, encryptedData, iv } }); // 存储token完成登录 wx.setStorageSync(token, res.token); }这个code和wx.login拿到的code不同它是一次性的手机号授权凭证。注意这个能力有调用频率限制而且从2023年开始微信要求手机号快捷登入组件必须在小程序后台申请开通个人主体的小程序无法使用此接口。如果是个人开发者的项目就只能走“手机号验证码”的方案这个要提前确认清楚免得开发完才发现用不了。4. 登入体验细节把用户留下的小心思4.1 记住账号与自动登入减少重复输入登入界面上最实用的小功能就是“记住账号”。用户下次打开还在登入页结果手机号已经帮填好了体验直接上一个档次。实现成本很低就是把手机号存到本地存储里下次onLoad时读出来回填。onLoad() { const savedPhone wx.getStorageSync(savedPhone); if (savedPhone) { this.setData({ phone: savedPhone }); } }至于“自动登入”这个功能要慎重。通常的做法是冷启动时如果有token先不急着跳登入页而是调一个“获取用户信息”的接口验证token是否有效。有效就直接跳首页无效才展示登入页。如果每次都先闪一下登入页再跳首页就有明显的白屏闪烁感这也是很多原生小程序tab页面切换白屏一瞬间的原因之一。解决办法是在app.json里把启动页设置为一个独立的splash页面在splash页里通过wx.checkSession判断登录态再决定跳登入页还是首页这样视觉上就顺滑很多。4.2 键盘遮挡与滚动处理被忽略的致命伤输入框在页面底部时弹出的键盘会遮挡输入内容这个问题在登入页尤其常见。解决的思路有两个一是把输入框位置保持在页面上半部分二是监听键盘高度并动态调整页面位置。第一种方案最省事登入页的输入框一般不会放得太靠下只要布局合理基本不会踩坑。但如果你用了自定义导航栏或者页面内容较多输入框被顶到屏幕下方就需要第二种方案了onLoad() { wx.onKeyboardHeightChange((res) { if (res.height 0) { // 键盘弹出将页面整体上移 this.setData({ keyboardHeight: res.height }); } else { this.setData({ keyboardHeight: 0 }); } }); }然后在WXML里给最外层容器动态绑定样式view classcontainer stylepadding-bottom: {{keyboardHeight}}px;这种方案的原理是键盘弹出时给页面底部增加一个等于键盘高度的padding让被遮挡的内容可以被滚动到可视区域。不过要注意wx.onKeyboardHeightChange需要在页面卸载时调用wx.offKeyboardHeightChange取消监听否则会在页面隐藏后仍然触发导致奇怪的页面跳动问题。4.3 密码可见切换一个提升好感的小交互密码输入框默认用password属性隐藏输入但用户经常输错密码所以提供一个“眼睛”图标切换可见性非常有必要。实现方式就是用password属性的布尔值切换view classinput-group input classinput password{{!showPassword}} placeholder请输入密码 value{{password}} bindinputonPasswordInput / text classeye-icon bindtaptogglePassword {{showPassword ? 隐藏 : 显示}} /text /viewdata: { showPassword: false }, togglePassword() { this.setData({ showPassword: !this.data.showPassword }); }这个交互看似小但在用户输错密码的挫败感高峰期一个能切换明文密码的按钮能有效减少用户输入错误率。什么你说直接用图片图标更美观对正式项目建议用两张眼睛图片切换我这里用文字是为了演示逻辑方便。5. 常见问题与排查技巧实录5.1 真机白屏与开发者工具表现不一致做微信小程序登入界面最让人抓狂的就是“在开发者工具里好好的一上真机就白屏”。这个问题我自己排查过很多次最常见的几个原因如下第一WXML里使用了不兼容的CSS属性。比如部分安卓WebView不支持position: sticky的某些用法或者backdrop-filter毛玻璃属性在低版本微信内核中直接失效。这不一定会白屏但常见的表现是样式错乱甚至某些版本的transform动画会导致整个页面渲染失败。第二接口请求被拦截。真机上如果你的域名没有配置在request合法域名里请求会直接失败而你的登入逻辑又是await等待接口返回的失败后没有任何兜底页面就停留在“加载中”状态看起来像白屏。这个在开发者工具里可以勾选“不校验合法域名”来规避但真机上没有这个选项。第三ES6语法兼容问题。老版本基础库不支持某些语法比如?.、??这些新特性。微信开发者工具默认开启ES6转ES5但转译后的代码在部分安卓机型上依然可能出错。我的建议是在项目里明确最低基础库版本并在app.json里设置libVersion大于等于你依赖特性的最低版本。提示如果白屏只发生在iOS或者只发生在安卓可以先看看控制台有没有报错日志。微信开发工具的“真机调试”功能可以直接调试真机页面配合wx.getSystemInfoSync()查看基础库版本。多数白屏问题都能在控制台里定位到具体报错别盲猜。5.2 request请求失败与401处理登入接口请求失败排在第一位的原因是域名配置问题。微信小程序要求所有请求域名必须为HTTPS并且在mp.weixin.qq.com后台配置到“request合法域名”列表里。开发阶段可以在开发者工具里勾选“不校验合法域名”临时绕过但发布前必须配置好不然线上用户的登入请求直接失败。还有非443端口的坑。小程序request只支持443端口如果后端接口跑在8080或者3000端口本地联调没问题但真机和线上全打不开。这个要在后端部署阶段就提前规避别等到联调才发现。401过期处理和用户重新登入的衔接也很重要。用户在使用过程中token过期如果直接弹“请重新登入”然后跳登入页用户之前填了一半的表单就丢了体验非常差。我的方案是在拦截到401时不清空用户当前页面的表单数据而是弹窗提示“登录已过期请重新登入”用户点击确认才清空token并跳转登录页。这样至少用户还有机会复制当前输入的内容。5.3 验证码按钮的倒计时与重复点击验证码获取按钮如果不做处理用户连点十次后端就会收到十条短信不仅骚扰用户还浪费短信费用。所以需要在前端严格限制点击一次后按钮进入倒计时状态在倒计时结束前不可重复点击。倒计时逻辑用setInterval实现sendCode() { if (this.data.countdown 0) return; const { phone } this.data; if (!/^1\d{10}$/.test(phone)) { wx.showToast({ title: 请输入正确的手机号, icon: none }); return; } // 先请求后端接口发送验证码 request({ url: /api/auth/send-code, method: POST, data: { phone } }).then(() { this.setData({ countdown: 60 }); this.timer setInterval(() { if (this.data.countdown 1) { clearInterval(this.timer); this.setData({ countdown: 0 }); return; } this.setData({ countdown: this.data.countdown - 1 }); }, 1000); }); }这里要注意的是倒计时中断时一定要清理定时器否则页面销毁后定时器还在跑不仅浪费资源还可能在页面onShow时出现界面数据错乱。页面卸载时加上onUnload() { if (this.timer) { clearInterval(this.timer); } }还有一种更优雅的处理把倒计时的状态放到服务端控制前端每次点击先请求后端接口判断是否在倒计时内。不过这个方案会增加接口开发量一般项目用前端倒计时就够了。5.4 常见问题速查表问题原因解决方案登入按钮点击无效loading状态未处理按钮被disabled在onLogin开头加if(this.data.loading) return输入框无法聚焦cover-view或者canvas遮挡检查页面层级输入框不要放在canvas或map同层获取验证码后收不到短信后端模板未审核或手机号绑定被限制检查微信后台短信模板状态测试时注意频率限制登入成功后跳转回首页又变成未登录token未持久化或请求头未带上token确保wx.setStorageSync成功request里统一加Authorization自定义导航栏上内容被状态栏遮挡没有预留statusBarHeight在onLoad获取系统信息动态设置顶部占位高度iOS上按钮边框和背景色异常CSSborder和background冲突使用按钮的plain属性或全局重置button样式输入框自动拉起键盘又收起页面在onShow时设置了focus检查代码中是否存在bindfocus或focus的误用6. 登入界面的扩展思考做一个有扩展性的登入模块6.1 账号体系的多端打通小程序登入不可避的一个问题是一个用户可能在微信小程序、App、H5端都使用你的产品。如果这三个端的用户体系各自独立用户在App上开通了会员到小程序上发现自己是新用户这种情况很致命。所以设计登入模块时我强烈建议用一个统一的用户中心服务各端注册登录都走同一套接口以手机号作为唯一用户标识微信openid只是作为绑定关系存在。架构上可以这样设计用户服务提供注册、密码登录、验证码登录、微信登录接口前端根据当前端类型传入登录方式参数小程序端先wx.login拿code后端用code换来openid后优先通过openid查找用户找不到再引导用户绑定手机号。这样用户在小程序完成了手机号绑定后在App端直接用手机号登录就是同一个账号。6.2 登入模块的工程化封装登入功能做得多了以后你会发现不同项目的登入逻辑80%是一样的。所以我自己会维护一个登入模块的基础代码包含request封装、登录页模板、token管理、登出逻辑。新项目里直接拷贝然后改一下接口地址、logo和主题色就能用。这样做的好处是多年踩坑的成果得到了沉淀新项目不容易再踩同样的坑。另外还建议把登入模块做成一个独立的service层页面只管调用不直接操作存储。比如提供三个方法login()、logout()、getToken()。页面里只调用这些方法不直接写wx.getStorageSync。这样以后如果你要换存储方式或者加token刷新逻辑只需要改service层页面完全不用动。6.3 防滥用与安全建议登入接口是攻击者重点关注的对象。手机号验证码接口如果不做频率限制很容易被刷短信一天能刷掉几千块。我在实际项目中做了三层防护前端按钮倒计时、后端按手机号限制每分钟一次、按IP限制每天同一手机号最多10次。另外所有输入框的内容都必须经过后端校验前端校验只是体验层的后端校验才是安全底线。比如说手机号格式、密码长度限制后端都要再校验一遍。密码在传输过程中必须加密推荐使用HTTPS传输密码存储必须加盐哈希绝不能明文存储。这些是老生常谈的安全要求但在登入模块里是硬性指标上线前一定要检查到位。做登入界面做到后面你会发现真正的难点不在于画一个漂亮的界面而在于把整个登录链路想清楚把各种边界情况处理好。我个人的体会是宁可在这个页面上多花一些时间把基础打牢也别急着上业务功能否则后面返工的成本只会更高。等你把登入模块沉淀成一套可复用的方案后面再做更多页面的时候会感觉无比轻松。本文还有配套的精品资源点击获取
返回列表