简介:面向需要在uni-app框架下快速实现登录功能的前端开发者,这份压缩包提供了一套可直接参考的完整实例。实例基于HBuilderX创建与预览项目,围绕用户名密码输入、按钮交互、服务端请求验证、结果反馈与登录状态持久化等关键环节展开,并兼顾传输加密与本地哈希等安全处理。包内共83个文件,以逻辑代码、页面结构、配置信息、样式表等为主,辅以静态图片与构建产物,整体约280KB,结构清晰,便于按需查找或改造复用。当前已有20413人学习下载,热度较高;适合uni-app初学者熟悉跨平台开发流程,也可供中级开发者快速搭建登录模块时参考。除常规页面与公共组件外,工程还提供了网络请求封装、状态管理与构建输出目录等层次,能够帮助读者理解uni-app的工程组织方式,并从具体代码中提炼出异常提示、密码加密和会话保持等实用思路。
1. uni-app登录:一套代码里最容易翻车的模块
uni-app登录几乎是每个跨端项目绕不开的第一道坎。业务上它是最基础的功能,技术上它又是平台差异最集中的地方:H5要处理跨域和token有效期,微信小程序要面对code换取openid的异步链路,App端要兼容第三方登录SDK的初始化时序。很多团队在排期时把登录估成“两天搞定”的活,实际做下去才发现表单校验、状态持久化、自动登录、退出清理这些细节串起来,比预想中要复杂得多。
本文围绕“uni-app登录实例的实现”这条主线,从方案选型讲到前后端交互,再落到一套可直接改着用的用户名密码登录实现,以及微信小程序登录的差异化处理。最后给出登录模块自测和线上问题排查的具体方法。内容面向两类读者:刚接触uni-app、想把登录跑通的新手,以及被登录问题反复折磨、想看清边界和参数细节的熟手。
2. 登录方案选型:先定token体系,再写业务代码
2.1 三种主流登录方式怎么选
uni-app登录实例在动手前,第一个要拍板的问题不是“怎么写”,而是“用哪种登录方式”。常见的选择有三个维度:业务场景、开发成本、平台覆盖范围。方式不同,后端接口设计和前端状态管理的复杂度完全不同。
第一种是传统用户名密码登录。前端收集账号密码,后端校验通过后返回一个token,前端把token存起来,后续请求带上。这种方案的好处是兼容性最强,H5、小程序、App全平台通用,不依赖任何三方SDK,后端只需要一个校验接口和一个token生成逻辑。缺点是用户需要记住账号密码,移动端输入体验一般。
第二种是手机号验证码登录。用户在界面输入手机号,获取短信验证码,后端校验验证码后直接登录。这种方案跳过了密码,用户操作成本低,在移动端转化率通常比密码登录高。但它的前提是有短信服务商,且国内短信通道存在延迟和到达率波动,测试阶段容易因为验证码收不到而影响进度。
第三种是第三方授权登录,比如微信、QQ、Apple登录。这种方案用户无感度最高,但实现复杂度也最高。以微信小程序为例,前端需要调用uni.login获取code,后端拿code去微信服务器换openid,整个链路依赖微信的接口服务和AppID配置,联调时经常被各种平台配置卡住。本项目演示登录时,通常先用用户名密码把登录闭环跑通,再扩展第三方登录。
2.2 token放哪里:storage存取与安全边界
用户登录成功后拿到的token需要存储到客户端,uni-app里贯穿三端的存储方案是uni.setStorageSync和uni.getStorageSync。这两个API是同步写法,底层各端实现不同,H5落到localStorage,小程序落到storage,App落到本地文件。用起来体验一致,但容量和安全边界需要分清。
token的存储位置直接决定了一个登录实例能不能算“完整”。我在实际项目中会单独建一个auth.js模块,把token的存取、清理、读取统一封装。这样业务页面只调函数不碰storage细节,后续如果要从同步改成异步存储,也只需要改一个文件。
// utils/auth.js const TOKEN_KEY = 'uni_app_token' const USER_KEY = 'uni_app_user' export function setAuth(token, userInfo) { uni.setStorageSync(TOKEN_KEY, token) uni.setStorageSync(USER_KEY, userInfo) } export function getToken() { return uni.getStorageSync(TOKEN_KEY) || '' } export function getUser() { return uni.getStorageSync(USER_KEY) || null } export function clearAuth() { uni.removeStorageSync(TOKEN_KEY) uni.removeStorageSync(USER_KEY) }逻辑上这段代码做了三件事:登录成功后写入token和用户信息,业务页面读取token拼到请求头里,退出登录时清空所有登录态。参数上TOKEN_KEY和USER_KEY直接用字符串常量,实际项目中建议加上版本前缀,比如v1_user_info,避免以后业务升级时旧数据干扰判断。
2.3 登录态过期与自动登录的边界
token不会永不过期。实际项目中后端通常会给token设置有效期,比如7天或30天。过期之后前端需要重新走登录流程,这里就有一个关键设计:过期后是强制跳登录页,还是静默重新拉取token,还是允许用户在登录页看到之前的账号信息。
我推荐的做法是把过期处理统一放在请求拦截层。每个接口返回后先看HTTP状态码和业务状态码,如果后端约定401表示token失效,就在拦截器里统一清登录态并跳转登录页。业务代码里不要散落判断token过期,否则后期每个接口都要处理一遍,容易漏。
自动登录是另一个边界情况。很多团队在登录实例里加“记住我”需求,用本地存的账号密码自动登录,但我一般不建议这么做。把密码明文存在本地,安全风险很高。常见的替代方案是存token并设置较长有效期,后端在token快过期时通过刷新接口换新token。这样既避免重新输入密码,又不把密码落盘。
3. 登录前后端接口约定:不先谈这个,代码写再多也白搭
3.1 接口路径、请求方法与参数命名
登录实例能不能顺利落地,很大程度上取决于前端和后端在接口约定上是否对齐。启动开发前我会先和后端敲定三个字段:接口路径、请求方法、参数命名。
常见的登录接口设计是POST /api/login,请求体用JSON格式,字段名是username和password。用户名和密码的命名看起来是小事,但如果后端用的是account,前端用的是username,联调时就会浪费时间。建议前端团队维护一份接口文档,路径、参数、返回结构、错误码全部写清楚,测试时按文档造数据。
{ "username": "demo_user", "password": "123456" }密码的传输方式也需要提前约定。如果项目跑在HTTPS环境下,明文传输可以接受;如果还没上HTTPS,最好采用加密后再传输的方式。常见做法是前端用RSA公钥加密密码,后端用私钥解密。但这套方案依赖密钥管理,登录实例演示阶段可以先用明文,上线前再补加密。
3.2 统一响应结构与业务状态码
接口返回结构的约定比接口路径更影响开发效率。一个清晰的响应结构应该包含三部分:业务状态码、提示信息、业务数据。状态码用来判断登录是否成功,提示信息直接弹给用户看,业务数据里放token和用户信息。
我常用的登录接口响应结构如下:
{ "code": 0, "message": "登录成功", "data": { "token": "eyJhbGciOiJIUzI1NiIs...", "userInfo": { "id": 10001, "nickname": "某开发者", "avatar": "https://example.com/avatar.png" } } }这里有几个细节值得注意。第一,code: 0是成功,非0是失败,不要用HTTP 200表示一切正常,因为HTTP 200只说明请求到达了服务器,不代表业务成功。第二,message字段要给用户可读的提示,比如“用户名或密码错误”,后端不要只返回英文错误码。第三,data.token建议用JWT,JWT本身自带过期时间,后端校验不用查库,前端解析也能拿到过期节点。
3.3 统一请求封装与token自动携带
接口约定好之后,前端需要写一个统一的请求工具函数。uni-app自带的uni.request可以满足基础需求,但每次登录后手动加token、手动处理错误码,代码会很重复。所以我一般在项目里再包一层request.js,把token注入、错误拦截、loading统一处理。
// utils/request.js import { getToken, clearAuth } from './auth' const BASE_URL = 'https://api.example.com' export function request(options) { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + getToken() }, success: (res) => { if (res.data.code === 0) { resolve(res.data.data) } else if (res.data.code === 401) { clearAuth() uni.reLaunch({ url: '/pages/login/login' }) reject(res.data) } else { uni.showToast({ title: res.data.message, icon: 'none' }) reject(res.data) } }, fail: (err) => { uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' }) reject(err) } }) }) }这段封装的逻辑点有三个:不管业务调哪个接口,请求头都自动带token;token失效统一清登录态并返回登录页;非成功状态码统一弹提示。参数上BASE_URL建议放在单独的config文件里,测试环境、生产环境切起来方便。登录接口本身不需要带token,可以在登录页直接调用uni.request,也可以让request支持一个skipAuth参数,这点按团队习惯来。
4. 用户名密码登录的最小闭环:从表单到首页跳转
4.1 登录页表单与前端校验
做登录实例时,第一步是把登录页的UI和表单校验搭好。uni-app里表单组件用input配合button就够,不需要额外引入UI库。页面结构如下:
<template> <view class="login-page"> <view class="form-item"> <input v-model="username" placeholder="请输入用户名" class="input" /> </view> <view class="form-item"> <input v-model="password" type="safe-password" placeholder="请输入密码" class="input" /> </view> <button class="login-btn" @tap="handleLogin">登 录</button> </view> </template>表单校验是登录页必不可少的一环。uni-app有uni.forms组件可以做表单校验,但在登录这种简单场景下,手写校验更直接。校验逻辑就两条:用户名不能为空,密码不能为空且长度至少6位。校验不通过时用toast提示用户,比在按钮上写loading更直观。
export default { data() { return { username: '', password: '' } }, methods: { validate() { if (!this.username.trim()) { uni.showToast({ title: '请输入用户名', icon: 'none' }) return false } if (!this.password || this.password.length < 6) { uni.showToast({ title: '密码长度不能少于6位', icon: 'none' }) return false } return true }, handleLogin() { if (!this.validate()) return // 后续调用登录接口 } } }这里type="safe-password"需要单独说明,在uni-app中,input组件的password类型在App端有特殊处理,safe-password是App端的密码安全输入模式,可以避免系统输入法缓存密码。不过它只对App端生效,H5和小程序端要用password属性。如果项目只需要跑H5,直接用password更简单。
4.2 调用登录接口与登录态写入
表单校验通过后,下一步是调登录接口。这里要用3.3节封装的request方法。登录成功后的逻辑包含两步:把token和用户信息写入storage,然后跳转到首页。跳转推荐用uni.switchTab或uni.reLaunch,不要用uni.navigateTo。
为什么不能用navigateTo?因为navigateTo会保留当前页,用户按返回键会回到登录页,体验很差。reLaunch会关闭所有页面并打开新页面,登录场景下这是标准语义。
import { request } from '@/utils/request' import { setAuth } from '@/utils/auth' async handleLogin() { if (!this.validate()) return try { const data = await request({ url: '/login', method: 'POST', data: { username: this.username.trim(), password: this.password } }) setAuth(data.token, data.userInfo) uni.reLaunch({ url: '/pages/index/index' }) } catch (err) { console.error('登录失败', err) } }这段代码的登录链路是:校验表单、POST用户名密码、拿到token写入本地、跳到首页。参数上要注意用户名做了trim,密码没有做,因为密码两端一般不会有空格,如果用户真的输入了空格,trim反而会改变密码内容。另外这里用了async/await,比回调嵌套可读性好很多,遇到报错直接看catch里的日志。
4.3 登录按钮防重复提交
登录按钮的防重复提交是很多新手会忽略的细节。用户在弱网环境下点击登录,接口还没返回,又点了一次,就会发出两个重复请求,后端可能为同一个用户生成两个token,前端storage被后返回的覆盖,逻辑上不会出大问题,但容易造成状态混乱。
常见做法是登录期间给按钮加loading状态,同时用一个布尔值锁住请求入口。
data() { return { username: '', password: '', logging: false } }, async handleLogin() { if (this.logging) return if (!this.validate()) return this.logging = true try { // 登录请求逻辑 } catch (err) { // 错误处理 } finally { this.logging = false } }这里的关键是finally里必须重置logging为false,否则登录失败后按钮会一直锁死,用户再也点不了。另外button组件在loading态下建议加disabled属性,双保险防止重复提交。这一点在登录实例里虽然不起眼,但实际联调中很容易被测试提bug。
5. 微信小程序登录:uni.login到code换token的完整链路
5.1 微信登录与用户名密码登录的本质区别
如果项目需要覆盖微信小程序端,登录就不能再走单纯的用户名密码了。微信小程序登录的核心是uni.login拿到临时code,然后把这个code传给后端,后端拿code加上小程序的AppID和AppSecret去微信服务器换openid。整个登录态以微信的openid为准,相当于用微信身份做了免密登录。
这套机制有几个特点需要理解清楚。一方面,code是一次性的,有效期大概5分钟,且只能使用一次,后端换完openid后code就失效了。另一方面,openid是小程序维度下用户的唯一标识,同一个用户在不同小程序下的openid是不同的。后端通常会把openid和业务用户表做关联,第一次登录自动创建用户,后面登录直接返回token。
H5端的微信登录是另一个分支,走的是微信公众平台的OAuth授权,需要用户点击跳转授权页面,拿code再走同样的后端换token流程。这个分支依赖网页授权域名配置,联调时经常因为域名没配好而拿不到code。
5.2 小程序端登录实现代码
小程序端的登录页面不需要用户输入任何东西,进入登录页后直接触发登录逻辑即可,这也是“静默登录”的典型场景。
// pages/login/login.vue import { request } from '@/utils/request' uni.login({ provider: 'weixin', success: async (loginRes) => { const code = loginRes.code if (!code) { uni.showToast({ title: '获取登录凭证失败', icon: 'none' }) return } try { const data = await request({ url: '/login/wechat', method: 'POST', data: { code } }) setAuth(data.token, data.userInfo) uni.reLaunch({ url: '/pages/index/index' }) } catch (err) { console.error('微信登录失败', err) } }, fail: (err) => { console.error('uni.login调用失败', err) } })这条链路的逻辑是:调用uni.login拿到code、把code传给后端、后端换openid后返回token、前端存token后跳首页。这里有一个隐藏细节:uni.login在用户拒绝授权时不会失败,它返回的code依然有效,真正可能失败的是后端拿code换openid那一步。所以前端拿到code后一定要发给后端,不要在前端做任何openid层面的解析。
5.3 微信登录的边界场景:未注册用户怎么处理
微信登录和用户名密码登录最大的业务差异,在于新用户怎么处理。用微信登录的用户,后端第一次拿到openid时,这个用户还没有绑定手机号、没有昵称头像。如果直接放行登录,后续业务页面会发现用户资料不完整。如果拦截下来要求绑定手机号,又会在用户第一次打开小程序时产生额外操作成本。
我见过多个项目的处理方式,大致分成三类:
第一种是彻底静默登录。后端第一次遇到openid就自动创建用户,昵称默认“微信用户”,头像给默认图。用户后续再完善资料。这种方案用户路径最顺,但业务方如果前期需要精准用户信息,就接不住。
第二种是首次登录强制补全手机号。后端返回一个特殊状态码,比如code: 1001,前端拿到这个状态码后跳转到手机号绑定页,绑定完成后再发一次登录请求。这种方案信息完整性好,但会牺牲首次体验。
第三种是折中方案:openid登录返回token的同时,标记isNewUser: true,前端拿到这个标记后弹一个非强制的引导页面,用户可跳过。我一般推荐这个方案,既能收集信息又不卡用户路径。
6. 登录模块自测三板斧:接口联调、token时序与多端差异
6.1 用Mock数据把登录流程先跑通
登录实例开发过程中,前后端联调往往不是同步的。后端接口还没写好时,前端可以先Mock一份登录接口数据,把整个登录链路在本地跑通,这样后端接口一就绪,前端就能接上,不浪费时间。
// 模拟接口响应 function mockLogin(username, password) { return new Promise((resolve) => { setTimeout(() => { if (username === 'demo' && password === '123456') { resolve({ code: 0, message: '登录成功', data: { token: 'mock_token_' + Date.now(), userInfo: { id: 10001, nickname: '某开发者', avatar: '' } } }) } else { resolve({ code: 1001, message: '用户名或密码错误', data: null }) } }, 500) }) }Mock逻辑的关键在于返回结构和真实接口完全一致,错误情况也要模拟。联调时把mockLogin替换成request调用,页面代码一行都不用改。
常见的方式是在开发环境用一个api.js文件做接口层,页面不直接调request,而是调api.js里暴露的函数。这样改Mock还是改真实接口,只动api.js,不动页面。
6.2 接口联调的checklist:字段、状态码、登录态
登录模块联调时,很容易出现“前端改了数据结构,后端没同步”的错位问题。我在每次联调前会过一遍下面几个检查点,能省掉不少来回沟通的时间:
第一,字段名对齐。前端传的字段和后端接口文档里定义的名字是否一致,大小写对不对,这个检查最基础也最容易被忽略。
第二,状态码语义。后端返回的是code: 0还是code: 200表示成功,前端封装的request逻辑是否匹配,如果后端临时改了成功码,前端会一直走进错误分支。
第三,token有效时长。登录成功后拿到的token有没有过期时间,过期时间是多长,这个要看后端返回的JWT payload,调试时可以手动改系统时间测试过期拦截是否生效。
第四,重复登录策略。同一账号在两端同时登录时,后端的策略是互踢还是允许多端共存。如果互踢,前端需要处理被踢下线的提示,不能只静默跳登录页。
这份清单每次联调前过一遍,花不到五分钟,但能把大部分联调问题提前暴露掉。
6.3 微信开发者工具里的三个玄学问题
微信小程序登录联调时,最常遇到的几个问题都有套路可解。
第一个是uni.login在开发者工具里正常,但在真机上一直fail。原因多半是AppID没有配置或者没在微信公众平台的小程序后台配置request合法域名。开发者工具默认不校验合法域名,可以正常请求,真机严格校验,就会报错。解决方法是把后端接口域名加到小程序后台的request合法域名列表里。
第二个是获取code之后传给后端,后端返回“code无效”错误。这种情况先确认code是不是用了两次,如果前端和后端各消费了一次code,第二次就会报无效。还有可能是后端拿code换openid用的AppSecret和AppID不匹配,检查一下配置。
第三个是手机上能正常登录,但H5调试登录不了。这种通常是小程序登录和H5登录走了不同接口,H5的OAuth授权域名没配置,或者H5请求跨域被浏览器拦了。检查一下H5的跨域配置和授权域名,确认前后端都用的是H5对应的登录接口。
6.4 测试点:不光是“能登录”
登录模块的自测不要只覆盖“正确账号能登录成功”这一条路径。我在每次提交测试前,会把登录相关的测试场景写成一张核对表,至少包含下面几个场景:
登录成功的完整链路要测:输入正确账号密码、登录成功、token写入、跳转首页、刷新页面后登录态保留。这个场景是最基础的,但很多项目在“刷新页面后登录态保留”这一段会翻车,比如token存了但首页没从storage读出来。
登录失败的提示要测:账号不存在、密码错误、密码为空、用户名带空格。每种失败场景的提示文案是否准确,弹出来用户能不能看懂。
token过期场景一定要测:把token有效期改成极短时间,等待过期后再调业务接口,观察系统是否跳转登录页,本地登录态是否清空。如果过期后不跳登录页,用户会看到一个永远加载不出来的页面,这种bug上线后影响面很大。
退出登录也要测:退出后调用原业务接口,是否还能拿到数据。如果退出后token没清干净,接口请求头还带旧token,后端还能认出这个用户,那退出就是个摆设。常见做法是后端把token加入黑名单或标记过期,前端也要同步清空本地storage。
多端并行测试:同一套代码分别运行到H5、微信小程序、App,登录页UI和登录流程是否一致,uni-app的API在这三端有没有平台差异。这个测试要趁早做,不要等到上线前才发现App端登录按钮被键盘遮挡。
最后补一个我自己的习惯:登录相关的代码里,console.log在联调时可以多打,但上线前要清理掉。尤其是登录接口的请求参数里不能打明文密码,一旦线上日志被人看到,就是安全事故。调试时可以临时打,提交代码前删干净。希望帮到你。
本文还有配套的精品资源,点击获取