<script setup lang="ts">
|
import { bindOfficialPhone } from '@/api/auth'
|
import { useMiniAuthStore } from '@/store/auth'
|
import { clearOfficialOAuthQuery, finishMiniLogin, handleOfficialOAuthCallback, startOfficialOAuthLogin } from '@/utils/mini-login'
|
import { normalizeProfileDisplayName } from '@/types/auth'
|
|
const emit = defineEmits<{
|
(e: 'login-success'): void
|
}>()
|
|
const authStore = useMiniAuthStore()
|
const router = useRouter()
|
const loginSubmitting = ref(false)
|
const bindToken = ref('')
|
const mobile = ref('')
|
|
const visible = computed(() => authStore.loginPromptVisible)
|
const needPhoneAuthorize = computed(() => authStore.needPhoneAuthorize)
|
const message = computed(() => {
|
if (needPhoneAuthorize.value) {
|
return '首次登录需填写手机号完成身份识别'
|
}
|
return authStore.authPromptMessage || '您还未登录,请先登录'
|
})
|
|
function hideLoginPrompt() {
|
authStore.hideLoginPrompt()
|
}
|
|
function shouldForceEditProfile() {
|
return !normalizeProfileDisplayName(authStore.profile?.displayName)
|
}
|
|
async function openProfileEditorIfNeeded() {
|
if (!shouldForceEditProfile()) {
|
return false
|
}
|
uni.showToast({
|
title: '请先完善个人资料',
|
icon: 'none',
|
})
|
router.push({ name: 'profile' })
|
return true
|
}
|
|
async function handleStartLogin() {
|
if (loginSubmitting.value) {
|
return
|
}
|
|
loginSubmitting.value = true
|
try {
|
await startOfficialOAuthLogin()
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '登录失败',
|
icon: 'none',
|
})
|
}
|
finally {
|
loginSubmitting.value = false
|
}
|
}
|
|
async function completeLogin(loginData: Awaited<ReturnType<typeof bindOfficialPhone>>) {
|
await finishMiniLogin(loginData)
|
hideLoginPrompt()
|
clearOfficialOAuthQuery()
|
uni.$emit('mini-login-success')
|
emit('login-success')
|
if (await openProfileEditorIfNeeded()) {
|
return
|
}
|
uni.showToast({
|
title: '登录成功',
|
icon: 'success',
|
})
|
}
|
|
async function handlePhoneBind() {
|
if (loginSubmitting.value) {
|
return
|
}
|
const trimmedMobile = mobile.value.trim()
|
if (!/^1[3-9]\d{9}$/.test(trimmedMobile)) {
|
uni.showToast({
|
title: '请输入正确手机号',
|
icon: 'none',
|
})
|
return
|
}
|
if (!bindToken.value) {
|
uni.showToast({
|
title: '登录凭证已失效,请重新登录',
|
icon: 'none',
|
})
|
authStore.setNeedPhoneAuthorize(false)
|
return
|
}
|
|
loginSubmitting.value = true
|
try {
|
const loginData = await bindOfficialPhone({ bindToken: bindToken.value, mobile: trimmedMobile })
|
await completeLogin(loginData)
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '登录失败',
|
icon: 'none',
|
})
|
}
|
finally {
|
loginSubmitting.value = false
|
}
|
}
|
|
onMounted(async () => {
|
try {
|
const result = await handleOfficialOAuthCallback()
|
if (!result) {
|
return
|
}
|
if (result.mobileRequired) {
|
bindToken.value = result.bindToken || ''
|
mobile.value = ''
|
clearOfficialOAuthQuery()
|
authStore.showLoginPrompt('首次登录需填写手机号完成身份识别')
|
authStore.setNeedPhoneAuthorize(true)
|
return
|
}
|
if (result.login) {
|
await completeLogin(result.login)
|
}
|
}
|
catch (error) {
|
clearOfficialOAuthQuery()
|
uni.showToast({
|
title: error instanceof Error ? error.message : '登录失败',
|
icon: 'none',
|
})
|
}
|
})
|
</script>
|
|
<template>
|
<view v-if="visible" class="fixed inset-0 z-40 bg-[rgba(0,0,0,0.55)]" @click="!needPhoneAuthorize && hideLoginPrompt()" />
|
|
<view
|
v-if="visible"
|
class="fixed left-[50%] top-[50%] z-50 w-[520rpx] translate-x-[-50%] translate-y-[-50%] rounded-[28rpx] bg-white px-[40rpx] pb-[36rpx] pt-[28rpx]"
|
>
|
<view class="mb-[16rpx] text-center text-[34rpx] text-[#1A1A1A] font-medium">
|
提示
|
</view>
|
<view class="flex flex-col items-center">
|
<image src="/static/home/tips.png" class="mb-[24rpx] h-[144rpx] w-[144rpx]" mode="aspectFit" />
|
<text class="mb-[44rpx] text-[28rpx] text-[#4A4A4A]">
|
{{ message }}
|
</text>
|
<input
|
v-if="needPhoneAuthorize"
|
v-model="mobile"
|
type="number"
|
maxlength="11"
|
placeholder="请输入手机号"
|
placeholder-style="color: #B3B3B3;"
|
class="mb-[32rpx] h-[76rpx] w-full rounded-[38rpx] bg-[#F7F8FA] px-[28rpx] text-[30rpx] text-[#1A1A1A] box-border"
|
/>
|
</view>
|
<view class="flex items-center justify-between gap-[20rpx]">
|
<view
|
class="h-[80rpx] flex flex-1 items-center justify-center rounded-full bg-[#F2F2F2] text-[30rpx] text-[#666666]"
|
@click="hideLoginPrompt"
|
>
|
再想想
|
</view>
|
<button
|
v-if="needPhoneAuthorize"
|
class="m-0 h-[80rpx] flex flex-1 items-center justify-center rounded-full border-none bg-[#0C6FFF] p-0 text-[30rpx] text-white after:border-none"
|
:disabled="loginSubmitting"
|
@click="handlePhoneBind"
|
>
|
{{ loginSubmitting ? '绑定中...' : '绑定手机号' }}
|
</button>
|
<view
|
v-else
|
class="h-[80rpx] flex flex-1 items-center justify-center rounded-full bg-[#0C6FFF] text-[30rpx] text-white"
|
@click="handleStartLogin"
|
>
|
{{ loginSubmitting ? '登录中...' : '去登录' }}
|
</view>
|
</view>
|
</view>
|
</template>
|