29 手機登錄+退出登錄+第三方登錄
一 賬號郵箱手機登錄
效果圖:
思路:
1 我的頁面根據loginStatus 選擇是否顯示登錄選項。
2 大前提:沒有明顯的注冊邏輯,直接手機號獲取驗證碼可以登錄,相當於直接注冊了。
3 button的登錄的時候要配置lodding 顯示加載效果以及無法點擊button按鈕
4 兩種登錄方式比較像可以考慮直接進行代碼整合(見代碼)
5 app.vue 第一次加載的時候 初始化用戶的登錄狀態(就是去緩存里取出來用戶信息和loginStatus設置為true)。
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state:{
loginStatus:false,
user:{
// "id": 400,
// "username": "17621868045",
// "userpic": null,
// "password": false,
// "phone": "17621868045",
// "email": null,
// "status": 1,
// "create_time": 1587717384,
// "logintype": "phone",
// "token": "3e80c968d8b5610b9fda2341fd0ba43b39c59a40",
// "userinfo": {
// "id": 390,
// "user_id": 400,
// "age": 0,
// "sex": 2,
// "qg": 0,
// "job": null,
// "path": null,
// "birthday": null
// }
}
},
mutations:{
// 登錄
login(state,user){
// 更改state中的變量要在這里更改。
state.loginStatus = true
state.user = user
// 永久存儲
uni.setStorageSync('user',JSON.stringify(user));
},
// 初始化用戶登錄狀態
initUser(state){
let user = uni.getStorageSync('user');
if (user){
state.user = JSON.parse(user)
state.loginStatus = true
}
}
}
}
})
My.vue
<template>
<view >
<!-- 為了測試login頁面 -->
<!--
</navigator> -->
<template v-if="!loginStatus">
<view class="flex align-center justify-center font font-weight-bold my-2">
登錄社區,體驗更多功能
</view>
<outer-login></outer-login>
<view class="flex align-center justify-center font text-secondary my-2"
hover-class="text-main"
@click="openLogin">
賬號/郵箱/手機登錄 <text class="ml-1 iconfont icon-jinru"></text>
</view>
</template>
<view v-else class="flex px-2 py-3 align-center">
<image :src="avator" class="rounded-circle mr-2" style="height: 100rpx;width: 100rpx;" mode=""></image>
<view class="flex-1 flex flex-column ">
<text class="font-lg font-weight-bold">{{user.username}}</text>
<view class="font text-muted">
<text class="mr-3 ">總帖子 1</text>
<text>今日發帖 0</text>
</view>
</view>
<text class="iconfont icon-jinru font-lg"></text>
</view>
<view class="flex align-center px-2">
<view class="flex-1 flex flex-column align-center justify-center"
v-for="(item,index) in myData" :key="index">
<text class="font-lg font-weight-bold">{{item.num}}</text>
<text class="font text-muted">{{item.name}}</text>
</view>
</view>
<!-- 廣告欄 -->
<view class="px-2 py-3">
<image src="/static/demo/banner2.jpg"
class="w-100 rounded"
style="height: 170rpx;" mode="aspectFill"></image>
</view>
<!-- 三個小列表 -->
<uni-list-item :show-extra-icon="true" title="瀏覽歷史">
<text slot="myicon" class="iconfont icon-liulan"></text>
</uni-list-item>
<uni-list-item :show-extra-icon="true" title="社區認證">
<text slot="myicon" class="iconfont icon-huiyuanvip"></text>
</uni-list-item>
<uni-list-item :show-extra-icon="true" title="審核帖子">
<text slot="myicon" class="iconfont icon-keyboard"></text>
</uni-list-item>
</view>
</template>
<script>
import uniListItem from '@/components/uni-ui/uni-list-item/uni-list-item.vue'
// 導入vuex的助手函數
import outerLogin from '@/components/common/outer-login.vue';
import { mapState } from 'vuex'
export default {
components:{
uniListItem,
outerLogin
},
data() {
return {
myData:[{
name:"帖子",
num:1
},{
name:"動態",
num:1
},{
name:"評論",
num:2
},{
name:"粉絲",
num:0
}]
}
},
// 監聽導航條按鈕
onNavigationBarButtonTap(){
uni.navigateTo({
url:'../user-set/user-set'
})
},
computed:{
// 看這里 看這里 看這里
// 1 放到計算屬性是為了更精准的監聽state里面的變化
// 2 放到mapState里面是為了少些幾行代碼,相當於語法糖吧更加方便管理(當然了直接寫成普通的計算屬性也行)
// 3 為了和其他的計算屬性不沖突需要+...進行打散
...mapState({
// 4 這相當於loginStatus 是vue的那個loginstatus了 ,他地方就可以this.調用
loginStatus:state=>state.loginStatus,
user:state=>state.user
// 5 如果名字一樣也可以 不用對象可以直接寫...mapState(['loginStatus'])
}),
// 6 多說一點改變vuex里面的state里面的數據,一定要用commit的形式,方便vue追蹤數據變化。
avator(){
return this.user.userpic ? this.user.userpic : '/static/mv.png'
}
},
methods: {
// 打開登錄頁
openLogin(){
uni.navigateTo({
url: '../login/login',
});
}
}
}
</script>
<style>
</style>
loggin.vue
<template>
<view>
<!-- 狀態欄占位 -->
<uni-status-bar></uni-status-bar>
<!-- 左上角的叉 -->
<view class="iconfont font-weight-bold icon-guanbi flex align-center justify-center font-lg"
style="width: 100rpx; height: 100rpx;"
hover-class="text-muted"
@click="back"></view>
<!-- 賬號密碼登錄 -->
<template v-if="status">
<view class="font-lgger flex align-center justify-center" style="margin-top:100rpx;">
賬號密碼登錄
</view>
<view class="px-2">
<input class=" border-bottom font-md p-3" type="text"
style="margin-top:100rpx"
value="" placeholder="昵稱/手機號/郵箱"
v-model="username"/>
<!-- align-stretch直接保證了各個元素高度的統一,就不只是劇中對齊了 -->
<view class="flex align-stretch border-bottom ">
<input class="flex-1 font-md p-3" type="text"
value="" placeholder="請輸入密碼"
v-model="password"/>
<view class="text-muted flex align-center justify-center" style="width: 160rpx;">
忘記密碼
</view>
</view>
</view>
</template>
<!-- 短信驗證登錄 -->
<template v-else>
<view class="font-lgger flex align-center justify-center" style="margin-top:100rpx;">
短信驗證碼登錄
</view>
<view class="px-2">
<!-- 手機號 -->
<view class="flex align-center border-bottom" style="margin-top:100rpx">
<view class="flex align-center justify-center font-weight-bold font-md">
+86
</view>
<input class="font-md p-3" type="text"
value="" placeholder="手機號"
v-model="phone"/>
<!-- <view class="flex align-center justify-center font-weight-bold bg-main">
+86
</view> -->
</view>
<!-- 驗證碼 -->
<!-- align-stretch直接保證了各個元素高度的統一,就不只是劇中對齊了 -->
<view class="flex align-center border-bottom ">
<input class="flex-1 font-md p-3" type="text"
value="" placeholder="請輸入驗證碼"
v-model="code"/>
<view class="text-white rounded flex font-md p-1 align-center justify-center bg-main " style="width: 180rpx;"
@click="getCode"
:class="codeTime>0 ?'bg-main-disabled':'bg-main'">
{{codeTime>0 ? codeTime: '獲取驗證碼'}}
</view>
</view>
</view>
</template>
<!-- 登錄按鈕 -->
<view class="px-3 " style="padding-top:60rpx">
<button class=" text-white" style="border-radius: 50rpx;border: 0;" type="primary"
:disabled="disabled"
:class="disabled?'bg-main-disabled':'bg-main'"
@click="submit"
:loading="loading">{{loading?loading:'登錄'}}</button>
<!-- 這個loading bool一旦為true 會顯示旋轉加載狀態並且不可點擊 -->
</view>
<view class="flex align-center justify-center mx-2 my-2">
<view class="text-primary font py-2 mx-2 " @click="changeStatus">{{status?'短信驗證登錄':'賬號密碼登錄'}}</view>
<text>|</text>
<view class="text-primary font py-2 mx-2">登錄遇到問題</view>
</view>
<view class="flex align-center justify-center py-2">
<view class="" style="height: 1rpx;background-color: #CCCCCC;width: 100rpx;"></view>
<view class="text-muted font">
社交賬號登錄
</view>
<view class="" style="height: 1rpx;background-color: #CCCCCC;width: 100rpx;"></view>
</view>
<!-- 其他登錄方式 -->
<other-login></other-login>
<view class="flex align-center justify-center mt-2">
<text class="text-muted font">注冊即代表您同意</text>
<text class="font text-primary">《xxx社區協議》</text>
</view>
<!-- <view class="">
</view> -->
</view>
</template>
<script>
import uniStatusBar from '@/components/uni-ui/uni-status-bar/uni-status-bar.vue'
import otherLogin from '@/components/common/outer-login.vue'
export default {
components:{
uniStatusBar,
otherLogin
},
data() {
return {
status:true,// 賬號密碼驗證
username:"",
password:"",
phone:"",
code:"",
codeTime:0,
loading:false
}
},
computed:{
disabled(){
if((this.username===''||this.password==='')&&(this.phone===''||this.code==='')){
return true
}
return false
}
},
methods: {
// 后退1頁
back(){
uni.navigateBack({
delta:1
})
},
// 初始化表單
initForm(){
this.username = ''
this.password = ''
this.phone = ''
this.code = ''
},
// 切換賬號密碼登錄or短信登錄
changeStatus(){
this.status = !this.status
},
// 獲取驗證碼
getCode(){
// 防止重復獲取
if (this.codeTime>0){
return;
}
// 驗證手機號
if (!this.validate()) return;
// 請求數據
this.$H.post('/user/sendcode',{
phone:this.phone
},{
// 這樣原生數據就會傳輸過來
native:true
}).then(res=>{
// console.log(res)
uni.showToast({
title:res.data.msg,
icon:'none'
})
}).catch(err=>{
// console.log(err)
})
// 倒計時
this.codeTime = 5
// 箭頭函數可以直接拿到外面的this的內容
let timer = setInterval(()=>{
if (this.codeTime >= 1){
this.codeTime--
} else{
clearInterval(timer)
}
},1000)
},
// 表單驗證
validate(){
//手機號正則 只是針對驗證碼的手機號字段,其他並未做兼容。
var mPattern = /^1[34578]\d{9}$/;
if (!mPattern.test(this.phone)) {
uni.showToast({
title: '手機號格式不正確',
icon: 'none'
});
return false
}
// ...更多驗證
return true
},
// 提交
submit(){
this.loading = '登錄中...'
let url = ""
let data = ""
// 表單驗證
if(!this.status){
if (!this.validate()) return;
}
if (this.status){
// 賬號密碼登錄
url = '/user/login'
data = {
username:this.username,
password:this.password
}
}else{
// 手機驗證碼登錄
url = '/user/phonelogin'
data = {
phone:this.phone,
code:this.code
}
}
// 提交后端
this.$H.post(url,data).then(res=>{
// console.log(res)
// 修改vuex的state,持久化存儲
this.$store.commit('login',res)
// 提示和跳轉
uni.navigateBack({
delta:1
})
uni.showToast({
title:'登錄成功',
icon:'none'
})
this.loading = ''
}).catch(
this.loading = ''
)
// 登錄成功處理
}
}
}
</script>
<style>
</style>
App.vue
<script>
export default {
onLaunch: function() {
console.log('App Launch');
// 檢測更新
this.$U.update()
// 網絡更新
this.$U.onNetWork()
// 初始化數據
// 初始化用戶登錄狀態
this.$store.commit('initUser')
},
onShow: function() {
console.log('App Show');
},
onHide: function() {
console.log('App Hide');
}
};
</script>
<style>
/*每個頁面公共css */
/* 官方css庫 */
@import "./common/uni.css";
/* 自定義圖標庫 */
@import "./common/icon.css";
/* 動畫庫 */
@import "./common/animate.css";
/* @import url("./common/ceshi.css"); */
/* 引入自定義的css庫 */
@import "./common/free.css";
/* 引入自定義本項目相關的css庫 */
@import "./common/common.css";
/* 解決頭條小程序組件內引入字體不生效的問題 */
/* #ifdef MP-TOUTIAO */
@font-face {
font-family: uniicons;
src: url('/static/uni.ttf');
}
/* #endif */
</style>
二 退出登錄
效果圖:
點擊確定回到了首頁,再次進到設置里一看只有這兩個功能了。
思路:
1 既然有退出登錄,就要考慮設置里面哪些是登錄了才可以顯示的。
2 退出登錄就是設置loginState的狀態為false、user={}, 清除本地緩存。
User-set.vue
<template>
<view>
<!-- // 登錄可見 -->
<template v-if="loginStatus">
<uni-list-item title="賬號與安全" @click="open('user-password')"></uni-list-item>
<uni-list-item title="綁定郵箱" @click="open('user-email')"></uni-list-item>
<uni-list-item title="資料編輯" @click="open('user-userinfo')"></uni-list-item>
</template>
<!-- // 非登錄可見 -->
<uni-list-item title="清除緩存" @click="clear" class="text-muted"><text slot="right_content">{{currentSize | formatSize}}</text></uni-list-item>
<!-- // 登錄可見 -->
<uni-list-item v-if="loginStatus" title="意見反饋" @click="open('user-feedback')"></uni-list-item>
<!-- // 非登錄可見 -->
<uni-list-item title="關於社區" @click="open('about')"></uni-list-item>
<!-- // 登錄可見 -->
<view v-if="loginStatus" class="py-3 px-2">
<button type="primary"
class="bg-main rounded text-white"
style="border-radius: 50rpx;"
@click="logout">退出登陸</button>
</view>
</view>
</template>
<script>
import uniListItem from '@/components/uni-ui/uni-list-item/uni-list-item.vue'
import {mapState} from 'vuex'
export default {
components:{
uniListItem
},
data() {
return {
currentSize:0
}
},
computed:{
...mapState({
loginStatus:state=>state.loginStatus
})
},
onLoad() {
this.getStorageInfo()
},
filters:{
formatSize(value){
//保留1位小數
return value>1024? (value/1024).toFixed(1)+' MB':value.toFixed(1)+' KB';
}
},
methods: {
open(path){
uni.navigateTo({
url:`../${path}/${path}`
})
},
getStorageInfo(){
// 獲取緩存信息
let res = uni.getStorageInfoSync()
// console.log(res)
this.currentSize = res.currentSize
},
// 清除緩存信息
clear(){
uni.showModal({
title:'提示',
content:'是否要清除所有緩存?',
cancelText:'不清除',
confirmText:'清除',
confirmColor:'#FF4A6A',
success: (res) => {
if(res.confirm){
// 清除所有本地緩存
uni.clearStorage()
// 更新最新的緩存信息
this.getStorageInfo()
// 提示一下
uni.showToast({
title:'清除成功',
icon:'none'
})
}
}
})
},
// 退出登錄
logout(){
// 詢問是否退出登錄?
uni.showModal({
content:'是否要退出登錄',
success:(res) => {
if (res.confirm) {
this.$store.commit('logout')
uni.navigateBack({
delta:1
})
uni.showToast({
title:"退出登錄成功",
icon:'none'
})
}
}
})
}
}
}
</script>
<style>
</style>
Store/index.js(只看退出登錄注釋就行了)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state:{
loginStatus:false,
user:{
// "id": 400,
// "username": "17621868045",
// "userpic": null,
// "password": false,
// "phone": "17621868045",
// "email": null,
// "status": 1,
// "create_time": 1587717384,
// "logintype": "phone",
// "token": "3e80c968d8b5610b9fda2341fd0ba43b39c59a40",
// "userinfo": {
// "id": 390,
// "user_id": 400,
// "age": 0,
// "sex": 2,
// "qg": 0,
// "job": null,
// "path": null,
// "birthday": null
// }
}
},
mutations:{
// 登錄
login(state,user){
// 更改state中的變量要在這里更改。
state.loginStatus = true
state.user = user
// 永久存儲
uni.setStorageSync('user',JSON.stringify(user));
},
// 初始化用戶登錄狀態
initUser(state){
let user = uni.getStorageSync('user');
if (user){
state.user = JSON.parse(user)
state.loginStatus = true
}
},
// 退出登錄
logout(state){
state.loginStatus = false
state.user = {}
uni.removeStorageSync('user')
}
}
})
三 第三方登錄
效果圖:
思路:
以微信第三方登錄為案例:
1 點擊微信圖標
2 像微信后台發送兩次請求拿到openid和頭像等信息。
3 把拿到的信息發送到我們的后台。
4 后台存儲后發送到前台,前台把拿到的后台返回來的信息進行本地存儲。
5 然后看情況提示以及返回上一頁操作
代碼:
<template>
<view>
<view class="flex align-center justify-between" style="padding:20rpx 100rpx">
<view
v-for="(item,index) in providerList" :key="index"
class="iconfont text-white font-lgger rounded-circle flex align-center justify-center"
style="height:100rpx;width:100rpx;"
:class="item.icon+' '+item.bgColor"
@click="login(item)">
<!-- 如果直接寫了函數就會直接調用 :login="login(item)"-->
<!-- 完善login函數 實現第三方登錄。 -->
</view>
</view>
</view>
</template>
<script>
export default {
props:{
back:{
type:Boolean,
default:false
}
},
data() {
return {
providerList: []
}
},
mounted() {
uni.getProvider({
service: 'oauth',
success: (result) => {
// console.log(result.provider)
this.providerList = result.provider.map((value) => {
let providerName = '';
let icon = ''
let bgColor = ''
switch (value) {
case 'weixin':
providerName = '微信登錄'
// 自己在這個位置添加上需要渲染的信息 如 icon bgColor
icon = 'icon-weixin'
bgColor = 'bg-success'
break;
case 'qq':
providerName = 'QQ登錄'
icon = 'icon-QQ'
bgColor = 'bg-primary'
break;
case 'sinaweibo':
providerName = '新浪微博登錄'
icon = 'icon-xinlangweibo'
bgColor = 'bg-warning'
break;
}
// 返回的時候
return {
name: providerName,
id: value,
icon:icon,
bgColor:bgColor
}
});
},
fail: (error) => {
console.log('獲取登錄通道失敗', error);
}
});
},
methods: {
// 登錄
login(item){
console.log('###',item)
// 微信需要先login一下,才能執行里面的uni.getUserInfo
// 這個uni.login的工具函數,可以渲染出來微信登錄的頁面
uni.login({
provider:item.id,
success:res => {
console.log(res)
// 獲取用戶信息
uni.getUserInfo({
provider:item.id,//'weixin'
success:(infoRes) =>{
// console.log('####',infoRes)
// {
// "errMsg": "getUserInfo:ok",
// "userInfo": {
// "openId": "oRrdQtzJovVH1ZbCqvf9rQ",
// "nickName": "不爭",
// "gender": 1,
// "city": "白山",
// "province": "吉林",
// "country": "中國",
// "avatarUrl": "http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJYwJfvYwOp29uZAvfRoPSMvo357ibaGh79IdZ40ja3kQS9Sp7VTWhUHcg/132",
// "unionId": "oU5YytxKvy2K0oUftFxmm4"
// }
// }
let obj = {
provider:item.id, // weixin/weibo/qq
openid :infoRes.userInfo.openId,// 唯一id
expires_in:0,// 過期時間
nickName:infoRes.userInfo.nickName, // 微信的昵稱
avatarUrl:infoRes.userInfo.avatarUrl// 微信頭像地址
}
console.log(obj)
// 從微信/qq拿到的信息提交給后端。
this.loginEvent(obj)
},
})
}
})
},
// 拿到第三方來的登錄信息請求后端。
loginEvent(data){
this.$H.post('/user/otherlogin',data)
.then(res=>{
// 修改vuex的state,持久化存儲
this.$store.commit('login',this.$U.formatUserinfo(res))
// 返回上一頁
if (this.back){
uni.navigateBack({
delta:1
})
}
uni.showToast({
title:'登錄成功',
icon:'none'
})
})
}
}
}
</script>
<style>
</style>