1.修复谷歌验证报错

2.修复商城配置为小数点后3位
This commit is contained in:
2026-08-06 11:16:30 +08:00
parent 239f5f4444
commit 545b8d9a1c
8 changed files with 95 additions and 30 deletions

View File

@@ -53,13 +53,13 @@ class AdminTotp
return self::tfa()->getQRCodeImageAsDataUri($label, $secret); return self::tfa()->getQRCodeImageAsDataUri($label, $secret);
} }
public static function verifyCode(string $plainSecret, string $code): bool public static function verifyCode(string $plainSecret, string $code, int $discrepancy = 2): bool
{ {
$code = trim($code); $code = trim($code);
if (!preg_match('/^\d{6}$/', $code)) { if (!preg_match('/^\d{6}$/', $code)) {
return false; return false;
} }
return self::tfa()->verifyCode($plainSecret, $code); return self::tfa()->verifyCode($plainSecret, $code, $discrepancy);
} }
public static function encryptSecret(string $plainSecret): string public static function encryptSecret(string $plainSecret): string

View File

@@ -27,19 +27,29 @@ export function login(method: 'get' | 'post', params: object = {}, axiosConfig:
} }
export function totpVerify(params: object = {}) { export function totpVerify(params: object = {}) {
return createAxios({ return createAxios(
{
url: url + 'totpVerify', url: url + 'totpVerify',
data: params, data: params,
method: 'post', method: 'post',
}) },
{
cancelDuplicateRequest: false,
}
)
} }
export function totpBindInit(params: object = {}) { export function totpBindInit(params: object = {}) {
return createAxios({ return createAxios(
{
url: url + 'totpBindInit', url: url + 'totpBindInit',
data: params, data: params,
method: 'post', method: 'post',
}) },
{
cancelDuplicateRequest: false,
}
)
} }
export function totpBindConfirm(params: object = {}) { export function totpBindConfirm(params: object = {}) {
@@ -50,6 +60,7 @@ export function totpBindConfirm(params: object = {}) {
method: 'post', method: 'post',
}, },
{ {
cancelDuplicateRequest: false,
showSuccessMessage: true, showSuccessMessage: true,
} }
) )

View File

@@ -13,4 +13,5 @@ export default {
'Back to login': 'Back to login', 'Back to login': 'Back to login',
'Binding...': 'Binding...', 'Binding...': 'Binding...',
'Verifying...': 'Verifying...', 'Verifying...': 'Verifying...',
'Login failed, please try again': 'Sign-in could not be completed. Please try again or contact support.',
} }

View File

@@ -13,4 +13,5 @@ export default {
'Back to login': '返回登录', 'Back to login': '返回登录',
'Binding...': '绑定中...', 'Binding...': '绑定中...',
'Verifying...': '验证中...', 'Verifying...': '验证中...',
'Login failed, please try again': '登录未完成,请重试或联系管理员',
} }

View File

@@ -91,6 +91,8 @@ router.afterEach(() => {
ensureStandardHashUrl() ensureStandardHashUrl()
if (window.existLoading) { if (window.existLoading) {
loading.hide() loading.hide()
} else {
window.existLoading = false
} }
NProgress.done() NProgress.done()
}) })

View File

@@ -26,9 +26,11 @@ export const loading = {
hide: () => { hide: () => {
nextTick(() => { nextTick(() => {
setTimeout(() => { setTimeout(() => {
const el = document.querySelector('.block-loading') document.querySelectorAll('.block-loading').forEach((el) => {
el && el.parentNode?.removeChild(el) el.parentNode?.removeChild(el)
}, 1000) })
window.existLoading = false
}, 100)
}) })
}, },
} }

View File

@@ -194,6 +194,28 @@ const unwrapLoginApiData = (res: anyObj | undefined): anyObj => {
return res as anyObj return res as anyObj
} }
/** 从 login / totpVerify / totpBindConfirm 响应中解析 userInfo */
const extractLoginUserInfo = (res: anyObj | undefined): anyObj | undefined => {
const data = unwrapLoginApiData(res)
if (data.userInfo && typeof data.userInfo === 'object' && data.userInfo.token) {
return data.userInfo as anyObj
}
if (data.token) {
return data as anyObj
}
return undefined
}
const finishLoginWithApiResponse = (res: anyObj | undefined) => {
const userInfo = extractLoginUserInfo(res)
if (userInfo) {
navigateAfterLogin(userInfo)
return true
}
handleLoginApiResult(res)
return false
}
const handleLoginApiResult = (res: anyObj | undefined) => { const handleLoginApiResult = (res: anyObj | undefined) => {
if (!res || typeof res !== 'object') { if (!res || typeof res !== 'object') {
return return
@@ -211,7 +233,6 @@ const handleLoginApiResult = (res: anyObj | undefined) => {
navigateAfterLogin(data.userInfo) navigateAfterLogin(data.userInfo)
return return
} }
const tempToken = data.tempToken || data.temp_token const tempToken = data.tempToken || data.temp_token
if (data.type === 'need_totp' && tempToken) { if (data.type === 'need_totp' && tempToken) {
state.step = 'totp' state.step = 'totp'
@@ -311,13 +332,16 @@ const resetToLogin = () => {
const navigateAfterLogin = (userInfo: anyObj) => { const navigateAfterLogin = (userInfo: anyObj) => {
if (!userInfo?.token) { if (!userInfo?.token) {
ElMessage.error(t('login.Login failed, please try again'))
return return
} }
abortLoginPrefetch() abortLoginPrefetch()
adminInfo.dataFill(userInfo, false) adminInfo.setToken(userInfo.token, 'auth')
nextTick(() => { if (userInfo.refresh_token) {
adminInfo.setToken(userInfo.refresh_token, 'refresh')
}
adminInfo.dataFill(userInfo, ['token', 'refresh_token'])
router.replace({ path: adminBaseRoutePath }) router.replace({ path: adminBaseRoutePath })
})
} }
onMounted(() => { onMounted(() => {
@@ -365,13 +389,17 @@ const onSubmit = (captchaInfo = '') => {
form.captchaInfo = captchaInfo form.captchaInfo = captchaInfo
login('post', form) login('post', form)
.then((res) => { .then((res) => {
handleLoginApiResult(res) finishLoginWithApiResponse(res)
}) })
.catch((err) => { .catch((err) => {
if (err?.code === 'ERR_CANCELED' || err?.message === 'canceled') { if (err?.code === 'ERR_CANCELED' || err?.message === 'canceled') {
return return
} }
if (err && typeof err === 'object' && 'code' in err) {
handleLoginApiResult(err as anyObj) handleLoginApiResult(err as anyObj)
return
}
console.log(err)
}) })
.finally(() => { .finally(() => {
state.submitLoading = false state.submitLoading = false
@@ -400,7 +428,17 @@ const onTotpVerify = () => {
keep: form.keep, keep: form.keep,
}) })
.then((res) => { .then((res) => {
navigateAfterLogin(res?.data?.userInfo) if (!finishLoginWithApiResponse(res)) {
const code = res?.code
if (code === 1) {
ElMessage.error(t('login.Login failed, please try again'))
}
}
})
.catch((err) => {
if (err && typeof err === 'object' && 'msg' in err && err.msg) {
ElMessage.error(String(err.msg))
}
}) })
.finally(() => { .finally(() => {
state.submitLoading = false state.submitLoading = false
@@ -419,7 +457,17 @@ const onBindConfirm = () => {
keep: form.keep, keep: form.keep,
}) })
.then((res) => { .then((res) => {
navigateAfterLogin(res?.data?.userInfo) if (!finishLoginWithApiResponse(res)) {
const code = res?.code
if (code === 1) {
ElMessage.error(t('login.Login failed, please try again'))
}
}
})
.catch((err) => {
if (err && typeof err === 'object' && 'msg' in err && err.msg) {
ElMessage.error(String(err.msg))
}
}) })
.finally(() => { .finally(() => {
state.submitLoading = false state.submitLoading = false

View File

@@ -21,8 +21,8 @@
v-model="form.unlock_ratio" v-model="form.unlock_ratio"
:min="0" :min="0"
:max="100" :max="100"
:step="0.01" :step="0.001"
:precision="2" :precision="3"
controls-position="right" controls-position="right"
class="w100" class="w100"
/> />
@@ -82,8 +82,8 @@
v-model="form.return_ratio" v-model="form.return_ratio"
:min="0" :min="0"
:max="100" :max="100"
:step="0.01" :step="0.001"
:precision="2" :precision="3"
controls-position="right" controls-position="right"
class="w100" class="w100"
:disabled="!form.daily_points_enabled" :disabled="!form.daily_points_enabled"
@@ -101,8 +101,8 @@
v-model="form.lifetime_points_ratio" v-model="form.lifetime_points_ratio"
:min="0" :min="0"
:max="100" :max="100"
:step="0.01" :step="0.001"
:precision="2" :precision="3"
controls-position="right" controls-position="right"
class="w100" class="w100"
:disabled="!form.lifetime_points_enabled" :disabled="!form.lifetime_points_enabled"