feat(微信登录和授权登录功能):1.新增登录功能,2.新增用户切换功能。
This commit is contained in:
parent
6bc1907d7a
commit
c5f6e635ad
@ -41,6 +41,17 @@ export interface LoginResult {
|
||||
isNewUser: boolean
|
||||
}
|
||||
|
||||
export interface SwitchRoleParams {
|
||||
userType: string
|
||||
tenantId?: number
|
||||
}
|
||||
|
||||
export interface SwitchRoleResult {
|
||||
token: string
|
||||
userType: string
|
||||
tenantId?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信静默登录
|
||||
* @param params 登录参数
|
||||
@ -72,6 +83,14 @@ export function updateUserInfo(data: Partial<UserInfo>) {
|
||||
return put<any>('/api/auth/user-info', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换角色
|
||||
* @param params 角色切换参数
|
||||
*/
|
||||
export function switchRole(params: SwitchRoleParams) {
|
||||
return post<SwitchRoleResult>('/api/auth/switch-role', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
|
||||
@ -13,9 +13,19 @@
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<!-- 手机号授权登录按钮 -->
|
||||
<button class="login-btn" @click="handlePhoneLogin">
|
||||
<text>📱 手机号一键登录</text>
|
||||
<!-- 微信一键登录按钮 -->
|
||||
<button class="login-btn" @click="handleWxLogin">
|
||||
<text>🔐 微信一键登录</text>
|
||||
</button>
|
||||
|
||||
<!-- 手机号授权登录按钮(需要绑定手机号时显示) -->
|
||||
<button
|
||||
v-if="needBindPhone"
|
||||
class="phone-btn"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="handleGetPhoneNumber"
|
||||
>
|
||||
<text>📱 授权手机号完成注册</text>
|
||||
</button>
|
||||
|
||||
<!-- 角色选择说明 -->
|
||||
@ -70,6 +80,8 @@ const roleStore = useRoleStore()
|
||||
|
||||
const agreed = ref(false)
|
||||
const redirectUrl = ref('')
|
||||
const needBindPhone = ref(false)
|
||||
const tempOpenid = ref('')
|
||||
|
||||
onLoad((options: any) => {
|
||||
redirectUrl.value = options?.redirect || roleStore.homePagePath
|
||||
@ -80,8 +92,8 @@ const onAgreeChange = (e: any) => {
|
||||
agreed.value = e.detail.value.length > 0
|
||||
}
|
||||
|
||||
// 手机号授权登录(模拟)
|
||||
const handlePhoneLogin = async () => {
|
||||
// 微信一键登录
|
||||
const handleWxLogin = async () => {
|
||||
if (!agreed.value) {
|
||||
uni.showToast({
|
||||
title: '请先阅读并同意协议',
|
||||
@ -93,16 +105,103 @@ const handlePhoneLogin = async () => {
|
||||
try {
|
||||
uni.showLoading({ title: '登录中...' })
|
||||
|
||||
// 模拟微信登录
|
||||
const code = 'mock_code_' + Date.now()
|
||||
// 调用微信登录获取code
|
||||
const loginRes = await new Promise<UniApp.LoginRes>((resolve, reject) => {
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: resolve,
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
|
||||
// 调用登录接口
|
||||
await userStore.wxLogin(code)
|
||||
if (!loginRes.code) {
|
||||
throw new Error('获取登录凭证失败')
|
||||
}
|
||||
|
||||
// 获取用户信息(头像、昵称)
|
||||
let nickname = ''
|
||||
let avatar = ''
|
||||
try {
|
||||
const userProfile = await new Promise<any>((resolve, reject) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.getUserProfile({
|
||||
desc: '用于完善用户资料',
|
||||
success: resolve,
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
resolve({ userInfo: {} })
|
||||
// #endif
|
||||
})
|
||||
nickname = userProfile.userInfo?.nickName || ''
|
||||
avatar = userProfile.userInfo?.avatarUrl || ''
|
||||
} catch (e) {
|
||||
// 用户拒绝授权,继续登录
|
||||
}
|
||||
|
||||
// 调用后端登录接口
|
||||
const result = await userStore.wxLogin(loginRes.code, nickname, avatar)
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (result.needBindPhone) {
|
||||
// 需要绑定手机号
|
||||
needBindPhone.value = true
|
||||
tempOpenid.value = result.openid
|
||||
uni.showToast({
|
||||
title: '请授权手机号完成注册',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
// 登录成功
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 跳转到角色选择页
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({
|
||||
url: '/pages/auth/role-switch?redirect=' + encodeURIComponent(redirectUrl.value)
|
||||
})
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
console.error('登录失败:', error)
|
||||
uni.showToast({
|
||||
title: error.message || '登录失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取手机号授权
|
||||
const handleGetPhoneNumber = async (e: any) => {
|
||||
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||||
uni.showToast({
|
||||
title: '需要授权手机号才能完成注册',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
uni.showLoading({ title: '注册中...' })
|
||||
|
||||
// 调用手机号登录接口
|
||||
const result = await userStore.phoneLogin({
|
||||
openid: tempOpenid.value,
|
||||
encryptedData: e.detail.encryptedData,
|
||||
iv: e.detail.iv
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
title: '注册成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
@ -115,8 +214,9 @@ const handlePhoneLogin = async () => {
|
||||
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
console.error('注册失败:', error)
|
||||
uni.showToast({
|
||||
title: error.message || '登录失败',
|
||||
title: error.message || '注册失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
@ -195,6 +295,21 @@ const viewAgreement = (type: string) => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.phone-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
border-radius: 48rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@ -34,8 +34,8 @@
|
||||
</view>
|
||||
|
||||
<view class="action-btns">
|
||||
<button class="confirm-btn" @click="confirmRole">
|
||||
<text>确认进入</text>
|
||||
<button class="confirm-btn" :disabled="loading" @click="confirmRole">
|
||||
<text>{{ loading ? '切换中...' : '确认进入' }}</text>
|
||||
</button>
|
||||
<button class="switch-tip">
|
||||
<text>您可以随时在个人中心切换角色</text>
|
||||
@ -49,11 +49,14 @@ import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import type { UserType } from '@/types'
|
||||
import { useRoleStore } from '@/store/modules/role'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
const roleStore = useRoleStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const currentRole = ref<UserType>('customer')
|
||||
const redirectUrl = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
const roles = [
|
||||
{
|
||||
@ -81,7 +84,8 @@ const roles = [
|
||||
|
||||
onLoad((options: any) => {
|
||||
redirectUrl.value = options?.redirect || ''
|
||||
currentRole.value = roleStore.currentRole
|
||||
// 从用户信息或本地存储初始化当前角色
|
||||
currentRole.value = userStore.userInfo?.userType || roleStore.currentRole || 'customer'
|
||||
})
|
||||
|
||||
const selectRole = (role: UserType) => {
|
||||
@ -89,17 +93,20 @@ const selectRole = (role: UserType) => {
|
||||
}
|
||||
|
||||
const confirmRole = async () => {
|
||||
if (loading.value) return
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
uni.showLoading({ title: '切换中...' })
|
||||
|
||||
// 切换角色
|
||||
// 切换角色(会调用后端API并跳转)
|
||||
await roleStore.switchRole(currentRole.value)
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
// 跳转会在switchRole中自动完成
|
||||
// switchRole 内部会自动跳转到对应首页
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
loading.value = false
|
||||
uni.showToast({
|
||||
title: error.message || '切换失败',
|
||||
icon: 'none'
|
||||
@ -228,6 +235,10 @@ const confirmRole = async () => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.switch-tip {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { UserType } from '@/types'
|
||||
import { useUserStore } from './user'
|
||||
import { mockUsers } from '@/mock'
|
||||
import { switchRole as switchRoleApi } from '@/api/auth'
|
||||
|
||||
interface RoleState {
|
||||
currentRole: UserType
|
||||
@ -14,7 +14,7 @@ interface RoleState {
|
||||
|
||||
export const useRoleStore = defineStore('role', {
|
||||
state: (): RoleState => ({
|
||||
currentRole: (uni.getStorageSync('mock_user_type') || 'customer') as UserType,
|
||||
currentRole: (uni.getStorageSync('currentRole') || 'customer') as UserType,
|
||||
availableRoles: ['customer', 'merchant', 'player']
|
||||
}),
|
||||
|
||||
@ -52,28 +52,61 @@ export const useRoleStore = defineStore('role', {
|
||||
actions: {
|
||||
/**
|
||||
* 切换角色
|
||||
* @param role 目标角色
|
||||
* @param tenantId 租户ID(商家和代练需要)
|
||||
*/
|
||||
async switchRole(role: UserType): Promise<void> {
|
||||
this.currentRole = role
|
||||
|
||||
// 保存到本地存储
|
||||
uni.setStorageSync('mock_user_type', role)
|
||||
|
||||
// 更新用户信息
|
||||
async switchRole(role: UserType, tenantId?: number): Promise<void> {
|
||||
const userStore = useUserStore()
|
||||
const user = mockUsers.find(u => u.userType === role)
|
||||
|
||||
if (user) {
|
||||
userStore.userInfo = user
|
||||
uni.setStorageSync('userInfo', user)
|
||||
// 如果已登录,调用后端API切换角色
|
||||
if (userStore.isLoggedIn) {
|
||||
try {
|
||||
const result = await switchRoleApi({
|
||||
userType: role,
|
||||
tenantId
|
||||
})
|
||||
|
||||
// 更新token
|
||||
if (result.token) {
|
||||
userStore.token = result.token
|
||||
uni.setStorageSync('token', result.token)
|
||||
}
|
||||
|
||||
// 更新用户信息中的角色
|
||||
if (userStore.userInfo) {
|
||||
userStore.userInfo.userType = role
|
||||
if (tenantId) {
|
||||
userStore.userInfo.tenantId = tenantId
|
||||
}
|
||||
uni.setStorageSync('userInfo', userStore.userInfo)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换角色失败:', error)
|
||||
// 即使API失败,也允许本地切换(用于演示)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新本地角色状态
|
||||
this.currentRole = role
|
||||
uni.setStorageSync('currentRole', role)
|
||||
|
||||
// 跳转到对应首页
|
||||
uni.reLaunch({
|
||||
url: this.homePagePath
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化角色(从用户信息中获取)
|
||||
*/
|
||||
initRole() {
|
||||
const userStore = useUserStore()
|
||||
if (userStore.userInfo?.userType) {
|
||||
this.currentRole = userStore.userInfo.userType
|
||||
uni.setStorageSync('currentRole', this.currentRole)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查角色权限
|
||||
*/
|
||||
|
||||
@ -4,21 +4,23 @@
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import type { User, UserProfile, LoginResult } from '@/types'
|
||||
import { getCurrentUser, getUserProfile, mockApiResponse, mockDelay } from '@/mock'
|
||||
import { wxLogin as wxLoginApi, phoneLogin as phoneLoginApi, getUserInfo as getUserInfoApi, updateUserInfo as updateUserInfoApi, logout as logoutApi } from '@/api/auth'
|
||||
|
||||
interface UserState {
|
||||
token: string
|
||||
userInfo: User | null
|
||||
userProfile: UserProfile | null
|
||||
needBindPhone: boolean
|
||||
openid: string
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: (): UserState => ({
|
||||
token: uni.getStorageSync('token') || '',
|
||||
userInfo: null,
|
||||
userInfo: uni.getStorageSync('userInfo') || null,
|
||||
userProfile: null,
|
||||
needBindPhone: false
|
||||
needBindPhone: false,
|
||||
openid: uni.getStorageSync('openid') || ''
|
||||
}),
|
||||
|
||||
getters: {
|
||||
@ -43,37 +45,43 @@ export const useUserStore = defineStore('user', {
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 微信登录(模拟)
|
||||
* 微信登录
|
||||
* @param code 微信登录code
|
||||
* @param nickname 可选昵称
|
||||
* @param avatar 可选头像
|
||||
*/
|
||||
async wxLogin(code: string): Promise<LoginResult> {
|
||||
await mockDelay(1000)
|
||||
|
||||
const user = getCurrentUser()
|
||||
|
||||
const result: LoginResult = {
|
||||
token: 'mock_token_' + Date.now(),
|
||||
userId: user.id,
|
||||
openid: user.openid,
|
||||
phone: user.phone,
|
||||
userType: user.userType,
|
||||
needBindPhone: false,
|
||||
isNewUser: false,
|
||||
tenantId: user.tenantId
|
||||
}
|
||||
async wxLogin(code: string, nickname?: string, avatar?: string): Promise<LoginResult> {
|
||||
const result = await wxLoginApi({
|
||||
code,
|
||||
nickname,
|
||||
avatar
|
||||
})
|
||||
|
||||
this.token = result.token
|
||||
this.userInfo = user
|
||||
this.openid = result.openid
|
||||
this.needBindPhone = result.needBindPhone
|
||||
|
||||
// 构建用户信息
|
||||
this.userInfo = {
|
||||
id: result.userId,
|
||||
openid: result.openid,
|
||||
phone: result.phone,
|
||||
nickname: nickname || '',
|
||||
avatar: avatar || '',
|
||||
userType: result.userType as any,
|
||||
tenantId: 0
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
uni.setStorageSync('token', result.token)
|
||||
uni.setStorageSync('userInfo', user)
|
||||
uni.setStorageSync('openid', result.openid)
|
||||
uni.setStorageSync('userInfo', this.userInfo)
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
/**
|
||||
* 手机号授权登录(模拟)
|
||||
* 手机号授权登录
|
||||
*/
|
||||
async phoneLogin(data: {
|
||||
openid: string
|
||||
@ -82,28 +90,27 @@ export const useUserStore = defineStore('user', {
|
||||
nickname?: string
|
||||
avatar?: string
|
||||
}): Promise<LoginResult> {
|
||||
await mockDelay(1000)
|
||||
|
||||
const user = getCurrentUser()
|
||||
|
||||
const result: LoginResult = {
|
||||
token: 'mock_token_' + Date.now(),
|
||||
userId: user.id,
|
||||
openid: user.openid,
|
||||
phone: user.phone,
|
||||
userType: user.userType,
|
||||
needBindPhone: false,
|
||||
isNewUser: false,
|
||||
tenantId: user.tenantId
|
||||
}
|
||||
const result = await phoneLoginApi(data)
|
||||
|
||||
this.token = result.token
|
||||
this.userInfo = user
|
||||
this.openid = result.openid
|
||||
this.needBindPhone = false
|
||||
|
||||
// 构建用户信息
|
||||
this.userInfo = {
|
||||
id: result.userId,
|
||||
openid: result.openid,
|
||||
phone: result.phone,
|
||||
nickname: data.nickname || '',
|
||||
avatar: data.avatar || '',
|
||||
userType: result.userType as any,
|
||||
tenantId: 0
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
uni.setStorageSync('token', result.token)
|
||||
uni.setStorageSync('userInfo', user)
|
||||
uni.setStorageSync('openid', result.openid)
|
||||
uni.setStorageSync('userInfo', this.userInfo)
|
||||
|
||||
return result
|
||||
},
|
||||
@ -112,22 +119,28 @@ export const useUserStore = defineStore('user', {
|
||||
* 获取用户信息
|
||||
*/
|
||||
async getUserInfo(): Promise<User> {
|
||||
const user = getCurrentUser()
|
||||
this.userInfo = user
|
||||
const userInfo = await getUserInfoApi()
|
||||
|
||||
const profile = getUserProfile(user.id)
|
||||
if (profile) {
|
||||
this.userProfile = profile
|
||||
this.userInfo = {
|
||||
id: userInfo.userId,
|
||||
openid: userInfo.openid,
|
||||
phone: userInfo.phone,
|
||||
nickname: userInfo.nickname,
|
||||
avatar: userInfo.avatar,
|
||||
userType: userInfo.userType as any,
|
||||
tenantId: userInfo.tenantId || 0
|
||||
}
|
||||
|
||||
return user
|
||||
uni.setStorageSync('userInfo', this.userInfo)
|
||||
|
||||
return this.userInfo
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
async updateUserInfo(data: Partial<User>): Promise<boolean> {
|
||||
await mockDelay(500)
|
||||
await updateUserInfoApi(data)
|
||||
|
||||
if (this.userInfo) {
|
||||
this.userInfo = { ...this.userInfo, ...data }
|
||||
@ -141,8 +154,6 @@ export const useUserStore = defineStore('user', {
|
||||
* 更新用户扩展信息
|
||||
*/
|
||||
async updateUserProfile(data: Partial<UserProfile>): Promise<boolean> {
|
||||
await mockDelay(500)
|
||||
|
||||
if (this.userProfile) {
|
||||
this.userProfile = { ...this.userProfile, ...data }
|
||||
}
|
||||
@ -153,13 +164,21 @@ export const useUserStore = defineStore('user', {
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
logout() {
|
||||
async logout() {
|
||||
try {
|
||||
await logoutApi()
|
||||
} catch (e) {
|
||||
// 忽略退出登录接口错误
|
||||
}
|
||||
|
||||
this.token = ''
|
||||
this.userInfo = null
|
||||
this.userProfile = null
|
||||
this.needBindPhone = false
|
||||
this.openid = ''
|
||||
|
||||
uni.removeStorageSync('token')
|
||||
uni.removeStorageSync('openid')
|
||||
uni.removeStorageSync('userInfo')
|
||||
uni.removeStorageSync('mock_user_type')
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user