<script setup lang="ts">
|
import type { ApiError } from '@/api/core/handlers'
|
import { fetchMiniMe, loginByWx } from '@/api/auth'
|
import { useMiniAuthStore } from '@/store/auth'
|
import { normalizeLoginProfile, normalizeMeProfile, normalizeProfileDisplayName } from '@/types/auth'
|
|
definePage({
|
name: 'login',
|
layout: 'default',
|
style: {
|
navigationBarTitleText: '登录',
|
navigationBarBackgroundColor: '#FFFFFF',
|
navigationBarTextStyle: 'black',
|
},
|
})
|
|
const router = useRouter()
|
const authStore = useMiniAuthStore()
|
const submitting = ref(false)
|
const needPhoneAuthorize = ref(false)
|
const EMPLOYEE_SUBSCRIBE_TEMPLATE_IDS = [
|
'QlNwG99WXWztRDgtlLcE0fHxfY2VwaX8PRMrwGpEync',
|
'Wip0ODJgybZpeFpcvev_0GK1wQXEqZw-LHOjS-UyDLw',
|
]
|
const MINI_PHONE_AUTH_REQUIRED = 10008
|
|
function getApiErrorCode(error: unknown) {
|
return typeof error === 'object' && error != null && 'code' in error
|
? Number((error as ApiError).code)
|
: NaN
|
}
|
|
async function getWxLoginCode() {
|
// #ifdef MP-WEIXIN
|
return await new Promise<string>((resolve, reject) => {
|
uni.login({
|
provider: 'weixin',
|
success: (res) => {
|
if (res.code) {
|
resolve(res.code)
|
return
|
}
|
reject(new Error('获取微信登录凭证失败'))
|
},
|
fail: () => reject(new Error('获取微信登录凭证失败')),
|
})
|
})
|
// #endif
|
// #ifndef MP-WEIXIN
|
throw new Error('当前环境暂不支持微信授权登录')
|
// #endif
|
}
|
|
async function finishLogin(loginData: Awaited<ReturnType<typeof loginByWx>>) {
|
authStore.setSession({
|
token: loginData.token,
|
profile: normalizeLoginProfile(loginData),
|
})
|
|
try {
|
const profile = await fetchMiniMe(loginData.token)
|
authStore.setProfile(normalizeMeProfile(profile))
|
}
|
catch (error) {
|
console.warn('load mini profile failed after login', error)
|
}
|
|
await requestEmployeeSubscribeAuthorization()
|
}
|
|
function shouldForceEditProfile() {
|
return !normalizeProfileDisplayName(authStore.profile?.displayName)
|
}
|
|
async function openProfileEditorIfNeeded() {
|
if (!shouldForceEditProfile()) {
|
return false
|
}
|
uni.showToast({
|
title: '请先完善个人资料',
|
icon: 'none',
|
})
|
router.replaceAll({ name: 'profile' })
|
return true
|
}
|
|
async function handleLogin() {
|
if (submitting.value) {
|
return
|
}
|
|
submitting.value = true
|
try {
|
const wxCode = await getWxLoginCode()
|
const loginData = await loginByWx({ wxCode })
|
needPhoneAuthorize.value = false
|
await finishLogin(loginData)
|
if (await openProfileEditorIfNeeded()) {
|
return
|
}
|
uni.showToast({
|
title: '登录成功',
|
icon: 'success',
|
})
|
router.replaceAll({ name: 'home' })
|
}
|
catch (error) {
|
if (getApiErrorCode(error) === MINI_PHONE_AUTH_REQUIRED) {
|
needPhoneAuthorize.value = true
|
uni.showToast({
|
title: '请授权手机号完成登录',
|
icon: 'none',
|
})
|
}
|
else {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '登录失败',
|
icon: 'none',
|
})
|
}
|
}
|
finally {
|
submitting.value = false
|
}
|
}
|
|
async function handlePhoneAuthorize(event: any) {
|
if (submitting.value) {
|
return
|
}
|
const phoneCode = event?.detail?.code
|
if (!phoneCode) {
|
uni.showToast({
|
title: '未获取到手机号授权',
|
icon: 'none',
|
})
|
return
|
}
|
submitting.value = true
|
try {
|
const wxCode = await getWxLoginCode()
|
const loginData = await loginByWx({ wxCode, phoneCode })
|
needPhoneAuthorize.value = false
|
await finishLogin(loginData)
|
if (await openProfileEditorIfNeeded()) {
|
return
|
}
|
uni.showToast({
|
title: '登录成功',
|
icon: 'success',
|
})
|
router.replaceAll({ name: 'home' })
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '登录失败',
|
icon: 'none',
|
})
|
}
|
finally {
|
submitting.value = false
|
}
|
}
|
|
async function requestEmployeeSubscribeAuthorization() {
|
if (authStore.profile?.userType !== 'EMPLOYEE') {
|
return
|
}
|
// #ifdef MP-WEIXIN
|
try {
|
await new Promise<void>((resolve) => {
|
uni.requestSubscribeMessage({
|
tmplIds: EMPLOYEE_SUBSCRIBE_TEMPLATE_IDS,
|
complete: () => resolve(),
|
})
|
})
|
}
|
catch (error) {
|
console.warn('request employee subscribe message failed', error)
|
}
|
// #endif
|
}
|
</script>
|
|
<template>
|
<view class="login-page">
|
<view class="login-card">
|
<text class="login-title">
|
预约小程序
|
</text>
|
<text class="login-subtitle">
|
{{ needPhoneAuthorize ? '首次登录需授权手机号完成身份识别' : '点击下方按钮使用微信授权登录' }}
|
</text>
|
|
<button class="login-button" :disabled="submitting" @click="handleLogin">
|
{{ submitting ? '登录中...' : '微信登录' }}
|
</button>
|
|
<!-- #ifdef MP-WEIXIN -->
|
<button
|
v-if="needPhoneAuthorize"
|
class="login-button login-button-secondary"
|
:disabled="submitting"
|
open-type="getPhoneNumber"
|
@getphonenumber="handlePhoneAuthorize"
|
>
|
{{ submitting ? '授权中...' : '授权手机号登录' }}
|
</button>
|
<!-- #endif -->
|
</view>
|
</view>
|
</template>
|
|
<style scoped>
|
.login-page {
|
min-height: 100vh;
|
padding: 120rpx 32rpx 32rpx;
|
box-sizing: border-box;
|
background: linear-gradient(180deg, #e9f2ff 0%, #f7fbff 40%, #ffffff 100%);
|
}
|
|
.login-card {
|
padding: 56rpx 40rpx;
|
border-radius: 32rpx;
|
background: #ffffff;
|
box-shadow: 0 16rpx 48rpx rgba(6, 47, 108, 0.12);
|
}
|
|
.login-title {
|
display: block;
|
font-size: 44rpx;
|
font-weight: 600;
|
color: #1a1a1a;
|
}
|
|
.login-subtitle {
|
display: block;
|
margin-top: 20rpx;
|
font-size: 28rpx;
|
line-height: 1.6;
|
color: #666666;
|
}
|
|
.login-button {
|
margin-top: 56rpx;
|
height: 88rpx;
|
line-height: 88rpx;
|
border: none;
|
border-radius: 44rpx;
|
background: #0c6fff;
|
color: #ffffff;
|
font-size: 32rpx;
|
}
|
|
.login-button[disabled] {
|
opacity: 0.7;
|
}
|
|
.login-button-secondary {
|
margin-top: 24rpx;
|
background: #19be6b;
|
}
|
</style>
|