|
@@ -5,10 +5,6 @@
|
|
|
<div v-if="restoringSession" class="login-content">
|
|
<div v-if="restoringSession" class="login-content">
|
|
|
<van-loading vertical>正在恢复登录状态…</van-loading>
|
|
<van-loading vertical>正在恢复登录状态…</van-loading>
|
|
|
</div>
|
|
</div>
|
|
|
- <div v-else-if="userStore.access_token && formData.pushRecordId" class="login-content">
|
|
|
|
|
- <div class="text">暂时无法恢复开票流程,请重试。</div>
|
|
|
|
|
- <div class="login-btn btn" @click="initializeLogin">重试</div>
|
|
|
|
|
- </div>
|
|
|
|
|
<div v-else class="login-content">
|
|
<div v-else class="login-content">
|
|
|
<div class="text">感谢您参加{{ pubName }}项目,请您登录系统开具发票。</div>
|
|
<div class="text">感谢您参加{{ pubName }}项目,请您登录系统开具发票。</div>
|
|
|
|
|
|
|
@@ -116,7 +112,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive, watch } from 'vue'
|
|
|
|
|
|
|
+import { ref, reactive, watch, onUnmounted } from 'vue'
|
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
|
import { showFailToast, showSuccessToast, showToast, showLoadingToast } from 'vant'
|
|
import { showFailToast, showSuccessToast, showToast, showLoadingToast } from 'vant'
|
|
|
import {
|
|
import {
|
|
@@ -126,11 +122,7 @@ import {
|
|
|
registerUrlApi,
|
|
registerUrlApi,
|
|
|
} from '@/services/modules/login'
|
|
} from '@/services/modules/login'
|
|
|
import { getStatusApi } from '@/services/modules/invoiceInformation'
|
|
import { getStatusApi } from '@/services/modules/invoiceInformation'
|
|
|
-import type {
|
|
|
|
|
- SendTtsSmsRequest,
|
|
|
|
|
- LoginEtssmsRequest,
|
|
|
|
|
- RegisterUrlRequest,
|
|
|
|
|
-} from '@/services/modules/login/type.d'
|
|
|
|
|
|
|
+import type { LoginEtssmsRequest, RegisterUrlRequest } from '@/services/modules/login/type.d'
|
|
|
import { useUserStore } from '@/stores/modules/user'
|
|
import { useUserStore } from '@/stores/modules/user'
|
|
|
import { useDebounceFn } from '@/utils/util'
|
|
import { useDebounceFn } from '@/utils/util'
|
|
|
import { REGISTER_URL_KEY } from '@/constants/storage'
|
|
import { REGISTER_URL_KEY } from '@/constants/storage'
|
|
@@ -140,6 +132,12 @@ const userStore = useUserStore()
|
|
|
const router = useRouter()
|
|
const router = useRouter()
|
|
|
const route = useRoute()
|
|
const route = useRoute()
|
|
|
const restoringSession = ref(false)
|
|
const restoringSession = ref(false)
|
|
|
|
|
+const loggingIn = ref(false)
|
|
|
|
|
+let loginAttemptId = 0
|
|
|
|
|
+
|
|
|
|
|
+// 离开登录页或切换链接后,忽略上一轮请求的结果。
|
|
|
|
|
+const isCurrentLogin = (attemptId: number, pushRecordId: string) =>
|
|
|
|
|
+ attemptId === loginAttemptId && route.name === 'Login' && userStore.pushRecordId === pushRecordId
|
|
|
|
|
|
|
|
const passwordType = ref<'text' | 'password'>('password')
|
|
const passwordType = ref<'text' | 'password'>('password')
|
|
|
const switchPasswordType = () => {
|
|
const switchPasswordType = () => {
|
|
@@ -154,12 +152,6 @@ const formData = reactive<LoginEtssmsRequest>({
|
|
|
password: '',
|
|
password: '',
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const sendEtsSms = reactive<SendTtsSmsRequest>({
|
|
|
|
|
- mobile: '',
|
|
|
|
|
- pushRecordId: '',
|
|
|
|
|
- password: '',
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
/* 限制手机号只能输入数字,最多 30 位 */
|
|
/* 限制手机号只能输入数字,最多 30 位 */
|
|
|
const onInputMobile = (val: string) => {
|
|
const onInputMobile = (val: string) => {
|
|
|
formData.mobile = val.replace(/\D/g, '').slice(0, 30)
|
|
formData.mobile = val.replace(/\D/g, '').slice(0, 30)
|
|
@@ -192,7 +184,11 @@ const changePasswordDCancel = () => {
|
|
|
}
|
|
}
|
|
|
/* 发送验证码 */
|
|
/* 发送验证码 */
|
|
|
const sendCode = async () => {
|
|
const sendCode = async () => {
|
|
|
- if (sending.value) return
|
|
|
|
|
|
|
+ if (sending.value || isCounting.value || loggingIn.value || restoringSession.value) return
|
|
|
|
|
+ if (!formData.pushRecordId) {
|
|
|
|
|
+ showToast('登录链接存在不完整或者输入错误')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
if (!/^1\d{10}$/.test(formData.mobile)) {
|
|
if (!/^1\d{10}$/.test(formData.mobile)) {
|
|
|
showToast('请输入正确的手机号')
|
|
showToast('请输入正确的手机号')
|
|
|
return
|
|
return
|
|
@@ -203,10 +199,11 @@ const sendCode = async () => {
|
|
|
}
|
|
}
|
|
|
sending.value = true
|
|
sending.value = true
|
|
|
const toast = showLoadingToast({ message: '发送中…', forbidClick: true, duration: 3000 })
|
|
const toast = showLoadingToast({ message: '发送中…', forbidClick: true, duration: 3000 })
|
|
|
- sendEtsSms.mobile = formData.mobile
|
|
|
|
|
- sendEtsSms.password = formData.password
|
|
|
|
|
|
|
+ const attemptId = loginAttemptId
|
|
|
|
|
+ const { mobile, password, pushRecordId } = formData
|
|
|
try {
|
|
try {
|
|
|
- const res = await sendEtsSmsApi(sendEtsSms)
|
|
|
|
|
|
|
+ const res = await sendEtsSmsApi({ mobile, password, pushRecordId })
|
|
|
|
|
+ if (!isCurrentLogin(attemptId, pushRecordId)) return
|
|
|
if (res.code === 0 && res.data) {
|
|
if (res.code === 0 && res.data) {
|
|
|
showSuccessToast('发送成功')
|
|
showSuccessToast('发送成功')
|
|
|
startCountDown()
|
|
startCountDown()
|
|
@@ -217,6 +214,7 @@ const sendCode = async () => {
|
|
|
toast.close()
|
|
toast.close()
|
|
|
} catch (err: any) {
|
|
} catch (err: any) {
|
|
|
toast.close()
|
|
toast.close()
|
|
|
|
|
+ if (!isCurrentLogin(attemptId, pushRecordId)) return
|
|
|
|
|
|
|
|
const { code, message } = err
|
|
const { code, message } = err
|
|
|
if (code === 401) {
|
|
if (code === 401) {
|
|
@@ -227,7 +225,7 @@ const sendCode = async () => {
|
|
|
|
|
|
|
|
showFailToast(err?.message || '获取验证码失败')
|
|
showFailToast(err?.message || '获取验证码失败')
|
|
|
} finally {
|
|
} finally {
|
|
|
- sending.value = false
|
|
|
|
|
|
|
+ if (isCurrentLogin(attemptId, pushRecordId)) sending.value = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -255,12 +253,14 @@ const loginConfirm = () => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 首次登录与缓存恢复使用同一套业务状态判断,避免已提交业务重新进入开票页。
|
|
// 首次登录与缓存恢复使用同一套业务状态判断,避免已提交业务重新进入开票页。
|
|
|
-const continueInvoice = async (pushRecordId: string) => {
|
|
|
|
|
|
|
+const continueInvoice = async (pushRecordId: string, attemptId: number) => {
|
|
|
const token = userStore.access_token
|
|
const token = userStore.access_token
|
|
|
|
|
+ const ready = await getPubName(pushRecordId, attemptId)
|
|
|
|
|
+ if (!ready || userStore.access_token !== token) return
|
|
|
|
|
+ if (!userStore.hasValidSession()) return
|
|
|
const statusRes = await getStatusApi({ pushRecordId })
|
|
const statusRes = await getStatusApi({ pushRecordId })
|
|
|
if (
|
|
if (
|
|
|
- route.name !== 'Login' ||
|
|
|
|
|
- userStore.pushRecordId !== pushRecordId ||
|
|
|
|
|
|
|
+ !isCurrentLogin(attemptId, pushRecordId) ||
|
|
|
userStore.access_token !== token ||
|
|
userStore.access_token !== token ||
|
|
|
!userStore.hasValidSession()
|
|
!userStore.hasValidSession()
|
|
|
)
|
|
)
|
|
@@ -286,6 +286,11 @@ const continueInvoice = async (pushRecordId: string) => {
|
|
|
|
|
|
|
|
/* 登录逻辑 */
|
|
/* 登录逻辑 */
|
|
|
const loginBtn = useDebounceFn(async () => {
|
|
const loginBtn = useDebounceFn(async () => {
|
|
|
|
|
+ if (route.name !== 'Login' || loggingIn.value || restoringSession.value || sending.value) return
|
|
|
|
|
+ if (!formData.pushRecordId) {
|
|
|
|
|
+ showFailToast('登录链接存在不完整或者输入错误')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
if (!formData.mobile) {
|
|
if (!formData.mobile) {
|
|
|
showFailToast('请输入手机号')
|
|
showFailToast('请输入手机号')
|
|
|
return
|
|
return
|
|
@@ -302,6 +307,8 @@ const loginBtn = useDebounceFn(async () => {
|
|
|
showFailToast('请勾选阅读并同意')
|
|
showFailToast('请勾选阅读并同意')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ loggingIn.value = true
|
|
|
|
|
+ const attemptId = ++loginAttemptId
|
|
|
const toast = showLoadingToast({
|
|
const toast = showLoadingToast({
|
|
|
message: '登录中…',
|
|
message: '登录中…',
|
|
|
forbidClick: true,
|
|
forbidClick: true,
|
|
@@ -309,24 +316,31 @@ const loginBtn = useDebounceFn(async () => {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const pushRecordId = formData.pushRecordId
|
|
const pushRecordId = formData.pushRecordId
|
|
|
|
|
+ let token = userStore.access_token
|
|
|
try {
|
|
try {
|
|
|
- const res: any = await loginEtssmsApi(formData)
|
|
|
|
|
- if (route.name !== 'Login' || userStore.pushRecordId !== pushRecordId) return
|
|
|
|
|
|
|
+ const res: any = await loginEtssmsApi({ ...formData })
|
|
|
|
|
+ if (!isCurrentLogin(attemptId, pushRecordId)) return
|
|
|
if (res?.access_token) {
|
|
if (res?.access_token) {
|
|
|
userStore.setAccessToken(res.access_token, res.expires_in)
|
|
userStore.setAccessToken(res.access_token, res.expires_in)
|
|
|
|
|
+ token = userStore.access_token
|
|
|
formData.password = ''
|
|
formData.password = ''
|
|
|
formData.code = ''
|
|
formData.code = ''
|
|
|
- sendEtsSms.password = ''
|
|
|
|
|
- if (await getPubName(pushRecordId)) await continueInvoice(pushRecordId)
|
|
|
|
|
|
|
+ resetCountDown()
|
|
|
|
|
+ await continueInvoice(pushRecordId, attemptId)
|
|
|
} else if (res.code == 401) {
|
|
} else if (res.code == 401) {
|
|
|
loginMessage.value = res.msg
|
|
loginMessage.value = res.msg
|
|
|
loginDialog.value = true
|
|
loginDialog.value = true
|
|
|
} else if (res.code == 402) {
|
|
} else if (res.code == 402) {
|
|
|
changePasswordDialog.value = true
|
|
changePasswordDialog.value = true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showFailToast(res.msg || '登录失败,请重试')
|
|
|
}
|
|
}
|
|
|
} catch (err: any) {
|
|
} catch (err: any) {
|
|
|
|
|
+ if (!isCurrentLogin(attemptId, pushRecordId) || userStore.access_token !== token) return
|
|
|
|
|
+ userStore.LogOut()
|
|
|
showFailToast(err?.message || '登录失败,请重试')
|
|
showFailToast(err?.message || '登录失败,请重试')
|
|
|
} finally {
|
|
} finally {
|
|
|
|
|
+ if (attemptId === loginAttemptId) loggingIn.value = false
|
|
|
toast.close()
|
|
toast.close()
|
|
|
}
|
|
}
|
|
|
}, 800)
|
|
}, 800)
|
|
@@ -353,27 +367,17 @@ const registerUrl = useDebounceFn(async () => {
|
|
|
// 项目名称
|
|
// 项目名称
|
|
|
const pubName = ref('')
|
|
const pubName = ref('')
|
|
|
|
|
|
|
|
-const getPubName = async (pushRecordId: string) => {
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await getPubNameApi({
|
|
|
|
|
- pushRecordId,
|
|
|
|
|
- })
|
|
|
|
|
- if (route.name !== 'Login' || userStore.pushRecordId !== pushRecordId) return false
|
|
|
|
|
- if (res.code === 0) {
|
|
|
|
|
- pubName.value = res.data.projectName
|
|
|
|
|
- registerUrlParams.value.areaId = res.data.cityCode
|
|
|
|
|
- userStore.setNeedFaceId(res.data.needFaceId)
|
|
|
|
|
- registerUrlParams.value.pushRecordId = pushRecordId
|
|
|
|
|
- return true
|
|
|
|
|
- } else {
|
|
|
|
|
- codeMessage.value = res.msg
|
|
|
|
|
- showDialog.value = true
|
|
|
|
|
- if (userStore.access_token) showFailToast(res.msg || '获取项目信息失败,请重试')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (err: any) {
|
|
|
|
|
- showFailToast(err?.message || '获取项目信息失败,请重试')
|
|
|
|
|
|
|
+const getPubName = async (pushRecordId: string, attemptId: number) => {
|
|
|
|
|
+ const res = await getPubNameApi({ pushRecordId })
|
|
|
|
|
+ if (!isCurrentLogin(attemptId, pushRecordId)) return false
|
|
|
|
|
+ if (Number(res.code) !== 0 || !res.data) {
|
|
|
|
|
+ throw new Error(res.msg || '获取项目信息失败,请重新登录')
|
|
|
}
|
|
}
|
|
|
- return false
|
|
|
|
|
|
|
+ pubName.value = res.data.projectName
|
|
|
|
|
+ registerUrlParams.value.areaId = res.data.cityCode
|
|
|
|
|
+ userStore.setNeedFaceId(res.data.needFaceId)
|
|
|
|
|
+ registerUrlParams.value.pushRecordId = pushRecordId
|
|
|
|
|
+ return true
|
|
|
}
|
|
}
|
|
|
/* 协议跳转 */
|
|
/* 协议跳转 */
|
|
|
const toAgreement = (type: string) => {
|
|
const toAgreement = (type: string) => {
|
|
@@ -383,29 +387,36 @@ const toAgreement = (type: string) => {
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const resetCountDown = () => {
|
|
|
|
|
+ clearInterval(timer)
|
|
|
|
|
+ timer = undefined
|
|
|
|
|
+ isCounting.value = false
|
|
|
|
|
+ countDown.value = 60
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const resetLoginState = () => {
|
|
const resetLoginState = () => {
|
|
|
formData.mobile = ''
|
|
formData.mobile = ''
|
|
|
formData.password = ''
|
|
formData.password = ''
|
|
|
formData.code = ''
|
|
formData.code = ''
|
|
|
- sendEtsSms.password = ''
|
|
|
|
|
agree.value = false
|
|
agree.value = false
|
|
|
- isCounting.value = false
|
|
|
|
|
- countDown.value = 60
|
|
|
|
|
|
|
+ resetCountDown()
|
|
|
sending.value = false
|
|
sending.value = false
|
|
|
showDialog.value = false
|
|
showDialog.value = false
|
|
|
loginDialog.value = false
|
|
loginDialog.value = false
|
|
|
changePasswordDialog.value = false
|
|
changePasswordDialog.value = false
|
|
|
|
|
|
|
|
- if (timer) {
|
|
|
|
|
- clearInterval(timer)
|
|
|
|
|
- timer = undefined
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ pubName.value = ''
|
|
|
|
|
+ registerUrlParams.value.areaId = ''
|
|
|
|
|
+ registerUrlParams.value.pushRecordId = ''
|
|
|
|
|
+ registerUrlParams.value.returnUrl = ''
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-let initializationId = 0
|
|
|
|
|
const initializeLogin = async () => {
|
|
const initializeLogin = async () => {
|
|
|
|
|
+ const attemptId = ++loginAttemptId
|
|
|
|
|
+ restoringSession.value = false
|
|
|
|
|
+ loggingIn.value = false
|
|
|
|
|
+ sending.value = false
|
|
|
if (route.name !== 'Login') return
|
|
if (route.name !== 'Login') return
|
|
|
- const currentInitialization = ++initializationId
|
|
|
|
|
if (route.query.from === 'reject') {
|
|
if (route.query.from === 'reject') {
|
|
|
resetLoginState()
|
|
resetLoginState()
|
|
|
return router.replace({
|
|
return router.replace({
|
|
@@ -421,27 +432,29 @@ const initializeLogin = async () => {
|
|
|
: typeof route.query.pushRecordId === 'string'
|
|
: typeof route.query.pushRecordId === 'string'
|
|
|
? route.query.pushRecordId
|
|
? route.query.pushRecordId
|
|
|
: ''
|
|
: ''
|
|
|
|
|
+ if (formData.pushRecordId !== pushRecordId) resetLoginState()
|
|
|
|
|
+ userStore.setPushRecordId(pushRecordId)
|
|
|
|
|
+ formData.pushRecordId = pushRecordId
|
|
|
if (!pushRecordId) {
|
|
if (!pushRecordId) {
|
|
|
- formData.pushRecordId = ''
|
|
|
|
|
- sendEtsSms.pushRecordId = ''
|
|
|
|
|
|
|
+ userStore.LogOut()
|
|
|
showToast('登录链接存在不完整或者输入错误')
|
|
showToast('登录链接存在不完整或者输入错误')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- if (formData.pushRecordId !== pushRecordId) resetLoginState()
|
|
|
|
|
- userStore.setPushRecordId(pushRecordId)
|
|
|
|
|
- sendEtsSms.pushRecordId = pushRecordId
|
|
|
|
|
- formData.pushRecordId = pushRecordId
|
|
|
|
|
registerUrlParams.value.returnUrl = `${location.origin}${import.meta.env.BASE_URL}login?pushRecordId=${encodeURIComponent(pushRecordId)}`
|
|
registerUrlParams.value.returnUrl = `${location.origin}${import.meta.env.BASE_URL}login?pushRecordId=${encodeURIComponent(pushRecordId)}`
|
|
|
restoringSession.value = userStore.hasValidSession()
|
|
restoringSession.value = userStore.hasValidSession()
|
|
|
|
|
+ const token = userStore.access_token
|
|
|
try {
|
|
try {
|
|
|
- const ready = await getPubName(pushRecordId)
|
|
|
|
|
- if (currentInitialization !== initializationId) return
|
|
|
|
|
- if (ready && userStore.hasValidSession()) await continueInvoice(pushRecordId)
|
|
|
|
|
|
|
+ if (restoringSession.value) {
|
|
|
|
|
+ await continueInvoice(pushRecordId, attemptId)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await getPubName(pushRecordId, attemptId)
|
|
|
|
|
+ }
|
|
|
} catch (err: any) {
|
|
} catch (err: any) {
|
|
|
- // 网络异常保留 token 供重试;真正的失效由请求拦截器统一清理。
|
|
|
|
|
- showFailToast(err?.message || '恢复登录状态失败,请重试')
|
|
|
|
|
|
|
+ if (!isCurrentLogin(attemptId, pushRecordId) || userStore.access_token !== token) return
|
|
|
|
|
+ userStore.LogOut()
|
|
|
|
|
+ showFailToast(err?.message || '恢复登录状态失败,请重新登录')
|
|
|
} finally {
|
|
} finally {
|
|
|
- if (currentInitialization === initializationId) restoringSession.value = false
|
|
|
|
|
|
|
+ if (attemptId === loginAttemptId) restoringSession.value = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -455,6 +468,11 @@ watch(
|
|
|
},
|
|
},
|
|
|
{ immediate: true },
|
|
{ immediate: true },
|
|
|
)
|
|
)
|
|
|
|
|
+
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ loginAttemptId++
|
|
|
|
|
+ clearInterval(timer)
|
|
|
|
|
+})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|