UniApp 实战:集成手机号、第三方微信与QQ登录功能及退出登录功能

UniApp 实战:集成手机号、微信与QQ登录功能

UniApp 实战:集成手机号、第三方微信与QQ登录功能及退出登录功能

前言

在当今移动互联网迅速发展的时代,用户对于应用的便捷性和安全性提出了更高的要求。为了满足这些需求,开发者们不断探索更加友好和高效的用户认证方式。传统的用户名密码登录虽然简单直接,但随着人们对隐私保护意识的增强以及对操作简便性的追求,越来越多的应用开始转向使用手机号验证码登录及第三方平台(如微信QQ)登录等更为安全快捷的方式。

对于移动应用开发而言,UniApp框架因其一次开发多端运行的特点而广受青睐。它不仅能够显著减少开发成本和周期,还能保证不同平台上的一致性体验。因此,在UniApp中实现多种登录方式的支持变得尤为重要。这不仅可以提升用户体验,还可以为应用程序吸引更多的潜在用户群体。

本篇博客将基于UniApp框架,深入探讨如何实现手机号验证码登录、微信登录以及QQ登录的功能集成。我们将从环境搭建、接口调用到代码实现等多个方面进行详细介绍,并分享一些实用技巧和注意事项。无论你是刚刚接触UniApp的新手,还是希望优化现有项目的资深开发者,相信都能从中获得有价值的参考信息。

实现

第一步、实现登录页面样式

UniApp 实战:集成手机号、微信与QQ登录功能
代码:

<template>
	<view class="app">
		<!-- mixin.js 中的 navBack(),不能少了括号,不然会传递event对象 -->
		<text class="back-btn iconfont icon-close" @click="navBack()"></text>
		<view class="left-top-sign">LOGIN</view>
		<view class="welcome">欢迎回来!</view>
		<!-- 手机号登录 -->
		<view class="input-content">
			<view class="input-item">
				<text class="tit">手机号码</text>
				<view class="row">
					<input v-model="mobile" type="number" maxlength="11" placeholder="请输入手机号码"
						placeholder-style="color: #909399" />
				</view>
			</view>
			<view class="input-item">
				<text class="tit">验证码</text>
				<view class="row">
					<input v-model="code" type="number" maxlength="6" placeholder="请输入手机验证码" placeholder-style="color: #909399" />
					<wyk-code :mobile="mobile" templateCode="SMS_19999999"></wyk-code>
				</view>
			</view>
			<button type="primary" :loading="loading" @click="login">登录</button>
		</view>
		<!-- #ifndef H5 -->
		<view class="other-wrapper">
			<view class="line center"><text class="tit">社交账号登录</text>
			</view>
			<view class="row">
				<image @click="toProviderLogin('weixin')" class="icon" src="/static/share/weixin.png"></image>
				<!-- #ifdef APP-PLUS -->
				<image @click="toProviderLogin('qq')" class="icon" src="/static/share/qq.png"></image>
				<!-- #endif -->
			</view>
		</view>
		<!-- #endif -->

		<!-- #ifdef APP-PLUS || MP-WEIXIN -->
		<!-- #endif -->
		<!-- 协议 -->
		<view class="agreement center">
			<text class="iconfont icon-roundcheckfill" :class="{active: agreement}" @click="checkAgreement"></text>
			<text @click="checkAgreement">请认真阅读并同意</text>
			<text class="tit" @click="navTo('/pages/public/xieyi')">《用户服务协议》</text>
			<text class="tit" @click="navTo('/pages/public/yinsi')">《隐私权政策》</text>
		</view>
	</view>
</template>
<script>
	import api from '@/api/system.js'
	export default {
		data() {
			return {
				mobile: '', // 用户名
				code: '', // 验证码
				agreement: false, // 是否同意协议
				loading: false, // 登录中
			}
		},
		methods: {
			//同意协议
			checkAgreement() { this.agreement = !this.agreement},
			// 手机号登录
			async login() {},
			// 获取应用内的认证授权信息
			async getServiceUserInfo(reqData) {}
		}
	}
</script>
<style lang="scss">
	.app {
		padding-top: 200rpx;
	}

	/* 关闭按钮 */
	.back-btn {
		position: absolute;
		left: 20rpx;
		top: calc(var(--status-bar-height) + 20rpx);
		z-index: 90;
		padding: 20rpx;
		font-size: 39rpx;
		color: #606266;
	}

	.left-top-sign {
		font-size: 120rpx;
		color: #f8f8f8;
	}

	.welcome {
		position: relative;
		top: -90rpx;
		padding-left: 60rpx;
		font-size: 46rpx;
		color: #555;
		text-shadow: 1px 0px 1px rgba(0, 0, 0, .3);
	}

	.input-content {
		padding: 0 60rpx;
	}

	.input-item {
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: flex-start;
		padding-left: 30rpx;
		background: #f8f6fc;
		height: 120rpx;
		border-radius: 4px;
		margin-bottom: 50rpx;

		&:last-child {
			margin-bottom: 0;
		}

		.row {
			width: 100%;
		}

		.tit {
			height: 50rpx;
			line-height: 56rpx;
			font-size: 26rpx;
			color: #606266;
		}

		input {
			flex: 1;
			height: 60rpx;
			font-size: 30rpx;
			color: #303133;
			width: 100%;
		}
	}

	button[type=primary] {
		background-color: $mxg-color-primary !important;
	}

	/* 其他登录方式 */
	.other-wrapper {
		display: flex;
		flex-direction: column;
		align-items: center;
		padding-top: 20rpx;
		margin-top: 80rpx;

		.weixin {
			border: 0;
		}

		.line {
			margin-bottom: 40rpx;

			.tit {
				margin: 0 32rpx;
				font-size: 24rpx;
				color: #606266;
			}

			&:before,
			&:after {
				content: '';
				width: 160rpx;
				height: 0;
				border-top: 1px solid #e0e0e0;
			}
		}

		.icon {
			width: 80rpx;
			height: 80rpx;
			margin: 0 50rpx;
		}
	}

	.agreement {
		position: absolute;
		left: 0;
		bottom: 10rpx;
		width: 750rpx;
		height: 90rpx;
		font-size: 24rpx;
		color: #999;

		.iconfont {
			font-size: 36rpx;
			color: #ccc;
			margin-right: 8rpx;

			&.active {
				color: $mxg-color-primary;
			}
		}

		.tit {
			color: #40a2ff;
		}
	}
</style>

第二步、创建 获取验证码按钮子组件

创建@/components/mxg-code/wyk-code.vue,并添加相应的逻辑:

<template>
	<view class="mxg-code center">
		<text class="code-btn" @click="sendSmsCode">
			{{codeDuration ? codeDuration + 's' : '获取验证码' }}
		</text>
	</view>
</template>
<script>
	let interval = null
	import api from '@/api/system.js'
	export default {
		props: {
			mobile: { // 手机号
				type: String,
				default: '18888888888'
			},
			templateCode: { // 短信模板代码
				type: String,
				default: ''
			}
		},
		data() {
			return {
				codeDuration: null, // 倒计时时长
			}
		},
		methods: {
			// 发送短信验证码
			async sendSmsCode() {
				// 有倒计时,则已发送
				if (this.codeDuration) {
					uni.showModal({
						content: `请在${this.codeDuration}秒后重试`,
						showCancel: false
					})
					return
				}
				// 校验手机号
				if (!this.$util.checkStr(this.mobile, 'mobile')) {
					this.$util.msg('手机号码格式不正确')
					return
				}
				// 更换手机号与原手机号一致,不允许发送
				if (this.$store && this.mobile === this.$store.state.userInfo.mobile) {
					this.$util.msg('新手机号与当前绑定的手机号不能相同')
					return
				}
				try {
					// 发送短信验证码
					uni.showLoading({
						mask: true
					})
					const data = {
						mobile: this.mobile,
						templateCode: this.templateCode
					}
					const res = await api.sendSmsCode(data)
					uni.hideLoading()
					// 响应结果
					this.$util.msg(res.message)
					if (res.code !== 20000){
						return
					}
					// 倒计时
					this.codeDuration = 60
					interval = setInterval(() => {
						this.codeDuration--
						if (this.codeDuration === 0) {
							if (interval) {
								clearInterval(interval)
								interval = null
							}
						}
					}, 1000)
				} catch (e) {
					uni.hideLoading()
					this.$util.msg('验证码发送失败')
					console.log('验证码发送失败', e)
				}
			},
		}
	}
</script>
<style lang="scss">
	.mxg-code {
		width: 160rpx;
		background-color: #345dc2;
		border-radius: 10rpx;
	}

	.code-btn {
		padding: 10rpx 0;
		font-size: 25rpx;
		color: #FFFFFF;
	}
</style>

第三步、实现手机号登录成功后使用Vuex进行用户状态管理

// 手机号登录
async login() {
	// 协议
	if (!this.agreement) {
		this.$util.msg("请阅读并统一服务及隐私协议");
		return
	}
	// 手机号验证码判断
	const {
		mobile,
		code
	} = this
	if (this.$util.checkStr(mobile, 'mobile')) {
		this.$util.msg("请输入有效手机号码")
	}
	if (this.$util.checkStr(code, 'mobileCode')) {
		this.$util.msg("请输入有效验证码")
	}
	// 调用登录接口
	this.loading = true
	uni.showLoading({
		title: '登陆中',
		mask: true
	})
	const res = await api.login({
		mobile,
		code
	})
	this.loading = false
	uni.hideLoading()
	if (res.code === 20000) {
		this.loginSuccessCallBack(res.data)
	} else {
		this.$util.msg(res.message)
	}

},
// 登录成功的回调
loginSuccessCallBack(data) {
	this.$util.msg("登录成功")
	// 状态管理保存用户信息
	this.$store.commit('setToken', data)
	setTimeout(() => {
		this.navBack()
	}, 500)
},

第四步、创建并使用Vuex 管理登录状态

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
	// 声明状态变量
	state: {
		userInfo: {}, // 用户信息
		accessToken: "" //访问令牌"
	},

	// store的计算属性
	getters: {
		hasLogin(state) {
			return !!state.accessToken
		}
	},
	mutations: {
		setState(state, data) {
			for (let key in data) {
				// 每个对象的key作为状态名, value作为状态值
				state[key] = data[key]
			}
		},
		// 更新用户登录状态
		setToken(state, data) {
			// 解构属性,
			const {
				userInfo,
				access_token
			} = data
			// 状态赋值保存
			if (userInfo) {
				state.userInfo = userInfo
				uni.setStorageSync('userInfo', userInfo)
			}
			if (access_token) {
				state.accessToken = access_token
				uni.setStorageSync('mxgEducationToken', access_token)
			}
		},
		// 退出登录
		logout(state) {
			// 状态清空
			state.userInfo = {}
			state.accessToken = ''
			// 移除本地数据
			uni.removeStorageSync('userInfo')
			uni.removeStorageSync('mxgEducationToken')
		}
	}
})

export default store

main.js中引入vuex全局状态管理

// 状态管理文件
import store from './store'
// 挂载Vue实例上,this.$store 获取
Vue.prototype.$store = store;
const app = new Vue({
		store,
	...App
})

第五步、使用vuex对用户信息进行渲染

<template>
	<view>
		<!-- #ifndef MP -->
		<!-- 头部空出的距离 -->
		<view class="status_bar"></view>
		<!-- #endif -->
		<!-- 状况用户信息 -->
		<view class="my-header">
			<view class="header-content space-between center" @click="hasLogin? navTo('/pages/my/user', {login: true}): navTo('/pages/auth/login')">
				<view class="left row center">
					<image :src="userInfo.imageUrl||'../../static/logo.png'" class="header-image" mode=""></image>
					<view class="header-info column" v-if="hasLogin">
						<text class="nickname">{{userInfo.nickName}}</text>
						<text class="username">用户名:{{userInfo.username}}</text>
					</view>
					<view class="header-info" v-else>
						<text class="nickname">请登录</text>
					</view>
				</view>
				<text class="iconfont icon-right"></text>
			</view>
		</view>
		<!-- 功能列表 -->
		<wyk-list :list="list"></wyk-list>
	</view>
</template>
<script>
import {
	mapState,
	mapGetters
} from 'vuex'
import list from '@/config/my-list-bar.js'
export default {
	computed: {
		// 结构状态作为计算属性
		...mapState(['userInfo']),
		...mapGetters(['hasLogin'])
	},
	data() {
		return {
			list: list() // 功能列表数据
		}
	},
	methods: {

	}
}
</script>

第七步、启动应用初始化登录状态

重新打开应用时,在 App.vue 中获取本地保存的 accessTokenuserInfo 信息 ,如果有则认为是登录状态,没有则为未登录状态,得到用户信息后需要同步改变 vuex 的状态,使所有页面都能共享登陆状态与用户信息。

<script>
export default {
	onLaunch: function() {
		this.initLogin()
	},
	onShow: function() {
		console.log('App Show')
	},
	onHide: function() {
		console.log('App Hide')
	},
	methods: {
		initLogin() {
			const userInfo = uni.getStorageSync('userInfo');
			const accessToken = uni.getStorageSync("mxgEducationToken")
			if (userInfo && accessToken) {
				this.$store.commit('setState', {
					userInfo,
					accessToken
				})
			}
		}
	}
}
</script>

关键点解释:

  • onLaunch:这是应用首次启动时触发的生命周期钩子。在这个例子中,当应用启动时会调用initLogin方法来检查是否有保存的用户信息和访问令牌,并根据情况初始化用户的登录状态。
  • onShowonHide:这两个是应用可见性变化时触发的钩子。onShow在应用从前台被切到后台后再次回到前台时触发;onHide则是在应用将要退到后台时触发。它们可以用来执行清理工作或恢复某些资源。
  • methods:这部分定义了组件内的方法。initLogin方法用于尝试自动登录用户,如果之前已经登录过(即本地存储中有用户信息和有效的访问令牌),那么就直接设置这些信息到Vuex的状态管理器中,以便其他页面可以直接使用。
  • uni.getStorageSync:这是一个同步版本的API,用来获取本地缓存的数据。这里的'userInfo''mxgEducationToken'是存储在本地缓存中的键名,用来存放用户信息和访问令牌。
  • this.$store.commit:这是Vuex的状态变更方式,通过提交mutation来改变store中的状态。这里假设有一个名为setState的mutation,它接受一个对象参数,包含userInfoaccessToken,用于更新全局状态。

第八步、在 mxin.js 中的 navTo 方法里,增加判断未登录跳转到登录页

UniApp 实战:集成手机号、微信与QQ登录功能

第九步、微信QQ第三方授权登录【参考

调用 uni.login 实现第三方授权登录,授权登录后,通过第三方响应的授权信息,再进行请求后台进行应用内认证授权,获取应用内认证信息。
实现
login.vuetoProviderLogin 方法中进行调用 uni.login 实现第三方授权登录


// 微信、QQ提供商登录
toProviderLogin(provider) {
	// 协议
	if (!this.agreement) {
		this.$util.msg("请阅读并统一服务及隐私协议");
		return
	}
	uni.showLoading({
			mask: true
		}),
		uni.login({
			provider,
			// #ifdef MP-ALIPAY
			scopes: 'auth_user', // 支付宝小程序要设置主动授权
			// #endif
			success: (res) => {
				console.log("授权成功", res)
				// #ifdef APP-PLUS
				const data = {
					userInfo: res.authResult
				}
				// #endif
				// #ifdef MP-WEIXIN
				const data = {
					code: res.code
				}
				// #endif
				// 2. 授权成功后,请求自己的后台认证接口,来完成自己平台的认证
				this.getServiceUserInfo(data)
				uni.hideLoading()
			},
			fail: (err) => {
				this.$util.msg("授权登陆失败")
				uni.hideLoading()
			}
		})
},

.授权登录后,通过第三方响应的授权信息,再进行请求后台进行应用内认证授权,获取应用认证信息token等信息

// 获取应用内的认证授权信息
async getServiceUserInfo(reqData) {
	// 1. 调用后台服务接口应用内登录,获取用户信息和token
	const {
		code,
		message,
		data
	} = await api.loginByProvider(reqData)
	uni.hideLoading()
	if (code !== 20000) {
		this.$util.msg(message)
		return
	}
	// 2. 判断是否绑定了手机号
	if (data.userInfo.mobile && data.access_token) {
		// 2.1 已绑定:更新 store 中的登录状态为已登录
		this.loginSuccessCallBack(data)
	} else {
		this.$util.msg('授权成功,请绑定手机号')
		// 2.2 未绑定:则跳转手机号绑定页 /pages/auth/bindMobile
		this.navTo('/pages/auth/bind-mobile?data=' + JSON.stringify(data))
	}
}

第十步、退出登录功能实现

点击个人资料页面的 退出登录 按钮,触发 logout 方法

// 退出登录
async logout() {
	uni.showModal({
		title: '确定退出登录?',
		content: '退出后不会删除任何历史数据',
		success: async (res) => {
			if (res.confirm) {
				const {
					code,
					message
				} = await authApi.logout(this.$store.state.accessToken)
				if (code === 20000) {
					this.$util.msg('成功退出登录')
					this.$store.commit('logout')
					setTimeout(() => {
						this.navBack()
					}, 300)
				} else {
					this.$util.msg(message)
				}
			}
		}
	})
},

效果:
UniApp 实战:集成手机号、微信与QQ登录功能

第十一步、完善请求头带上令牌和路由拦截

登录成功后,后台会响应访问令牌 accessToken ,如果当前有访问令牌,则在请求头带上访问令牌,这样针后台会认证访问令牌是否有效,如果令牌有效,才可以访问需要登录后的接口,不然就访问失败。找到 request.js 文件,添加代码:
UniApp 实战:集成手机号、第三方微信与QQ登录功能及退出登录功能
代码:

import {
	msg
} from './util.js'
// 导入成功后,后台会相应访问令牌accessToken,如果当前有访问令牌,则在请求头上面带上令牌
import store from '@/store'
// 基础URL
// #ifndef H5
// 非h5端,
let BASE_URL = 'https://mock.mengxuegu.com/mock/63fcbc2d7c016026ff2b8cd8/education-app'
// #endif

// #ifdef H5
// h5, 进行代理转发
let BASE_URL = '/api' // 'http://39.108.187.100:6001'
// #endif

const request = (options = {}) => {
	// 2.判断请求头带上访问令牌
	const accessToken = store.state.accessToken
	if(accessToken){
		options.header = {'Authorization':`Bearer${accessToken}`}
	}
	// resolve 正常响应,reject异常响应
	return new Promise((resolve, reject) => {
		uni.request({
			url: BASE_URL + options.url,
			method: options.method || 'GET',
			data: options.data || {},
			timeout: 8000, // 8秒超时时间,单位ms
			// 3. 添加请求头
			header:options.header,
			success: (res) => {
				// console.log('res', res.data)
				resolve(res.data)
			},
			fail: (err) => {
				// console.log('err', err)
				msg('请求接口失败')
				reject(err)
			}
		})
	})
}
// 导出
export default request

效果图

APP端口第三方授权QQ登录效果

UniApp 实战:集成手机号、微信与QQ登录功能

APP端口第三方授权微信登录效果

UniApp 实战:集成手机号、微信与QQ登录功能

APP端口第三方手机号验证码登录效果

UniApp 实战:集成手机号、微信与QQ登录功能

微信小程序端手机号登录效果

UniApp 实战:集成手机号、微信与QQ登录功能

微信小程序端微信登录效果

UniApp 实战:集成手机号、微信与QQ登录功能

退出登录功能

UniApp 实战:集成手机号、微信与QQ登录功能及退出登录功能

完结

通过上述步骤,我们已经能够在UniApp项目中集成手机号登录、微信登录(包括微信小程序)、QQ登录以及安全退出登录的功能。希望这篇博客能帮助你更好地理解和实现这些特性。如果你有任何疑问或者遇到了困难,请随时查阅官方文档或寻求社区的帮助。祝你开发顺利!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值