调整登录组织校验和图形验证码交互

This commit is contained in:
andy
2026-07-01 11:33:26 +08:00
parent 4919a311c3
commit 9e67339d3c
4 changed files with 76 additions and 15 deletions

View File

@@ -5,6 +5,7 @@ import type { TokenResponse } from '@/types/api'
import { joinUrl } from '@/utils/url' import { joinUrl } from '@/utils/url'
export interface OrganizationMemberInfo { export interface OrganizationMemberInfo {
userId?: string
memberId?: string memberId?: string
organizationId?: string organizationId?: string
memberName?: string memberName?: string
@@ -28,7 +29,7 @@ export const getImageCodeUrl = (imageRandomStr: string) => {
randomStr: imageRandomStr, randomStr: imageRandomStr,
t: String(Date.now()) t: String(Date.now())
}) })
return `${joinUrl(env.authBase, 'code/image')}?${params.toString()}` return `${joinUrl(env.apiBase, env.authBase, 'code/image')}?${params.toString()}`
} }
export const sendMobileCode = async (payload: SendMobileCodePayload) => { export const sendMobileCode = async (payload: SendMobileCodePayload) => {
@@ -72,3 +73,10 @@ export const bindUserInfoAndGetUserMemberInfoByPhone = async (phone: string) =>
{ userPhone: phone } { userPhone: phone }
) as unknown as OrganizationMemberInfo ) as unknown as OrganizationMemberInfo
} }
export const getBindStaffInfo = async () => {
if (env.useMock) return mockApi.getBindStaffInfo()
return await http.post<OrganizationMemberInfo>(
joinUrl(env.staffBase, 'user/getBindStaffInfo')
) as unknown as OrganizationMemberInfo
}

View File

@@ -171,6 +171,17 @@ export const mockApi = {
} }
}, },
async getBindStaffInfo() {
await wait(180)
return {
userId: 'USER10001',
memberId: 'MEMBER10001',
organizationId: 'ORG10001',
memberName: '当前员工',
memberRoleId: 'ROLE_STAFF'
}
},
async orderList(query: CommodityOrderSearch) { async orderList(query: CommodityOrderSearch) {
await wait() await wait()
const keyword = query.orderId || query.contactPhone || '' const keyword = query.orderId || query.contactPhone || ''

View File

@@ -1,7 +1,7 @@
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { import {
bindUserInfoAndGetUserMemberInfoByPhone, getBindStaffInfo,
loginByMobile, loginByMobile,
type OrganizationMemberInfo type OrganizationMemberInfo
} from '@/api/auth' } from '@/api/auth'
@@ -66,7 +66,7 @@ export const useAuthStore = defineStore('auth', () => {
const payload = await loginByMobile(phone, code) const payload = await loginByMobile(phone, code)
persistToken(payload, phone) persistToken(payload, phone)
try { try {
const member = await bindUserInfoAndGetUserMemberInfoByPhone(phone) const member = await getBindStaffInfo()
if (!member?.organizationId || !member?.memberId) { if (!member?.organizationId || !member?.memberId) {
throw new Error('未绑定组织,请联系管理员') throw new Error('未绑定组织,请联系管理员')
} }

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue' import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { showToast } from 'vant' import { showToast } from 'vant'
import { BadgeCheck, Phone, ShieldCheck } from 'lucide-vue-next' import { BadgeCheck, Phone, ShieldCheck } from 'lucide-vue-next'
@@ -36,6 +36,7 @@ const refreshImageCode = () => {
} }
const canSend = computed(() => /^1\d{10}$/.test(phone.value) && seconds.value === 0) const canSend = computed(() => /^1\d{10}$/.test(phone.value) && seconds.value === 0)
const captchaLocked = computed(() => seconds.value > 0 || sending.value)
const startCountdown = () => { const startCountdown = () => {
seconds.value = 60 seconds.value = 60
@@ -43,7 +44,9 @@ const startCountdown = () => {
timer = window.setInterval(() => { timer = window.setInterval(() => {
seconds.value -= 1 seconds.value -= 1
if (seconds.value <= 0) { if (seconds.value <= 0) {
seconds.value = 0
window.clearInterval(timer) window.clearInterval(timer)
refreshImageCode()
} }
}, 1000) }, 1000)
} }
@@ -74,7 +77,6 @@ const handleSend = async () => {
showToast('验证码已发送') showToast('验证码已发送')
} }
startCountdown() startCountdown()
refreshImageCode()
} catch (error) { } catch (error) {
showToast(error instanceof Error ? error.message : '验证码发送失败') showToast(error instanceof Error ? error.message : '验证码发送失败')
refreshImageCode() refreshImageCode()
@@ -105,6 +107,7 @@ const handleLogin = async () => {
} }
onMounted(refreshImageCode) onMounted(refreshImageCode)
onBeforeUnmount(() => window.clearInterval(timer))
</script> </script>
<template> <template>
@@ -137,6 +140,7 @@ onMounted(refreshImageCode)
maxlength="8" maxlength="8"
placeholder="请输入图形验证码" placeholder="请输入图形验证码"
autocomplete="off" autocomplete="off"
:disabled="captchaLocked"
clearable clearable
> >
<template #left-icon><ShieldCheck :size="18" /></template> <template #left-icon><ShieldCheck :size="18" /></template>
@@ -145,6 +149,7 @@ onMounted(refreshImageCode)
class="captcha-image-button" class="captcha-image-button"
type="button" type="button"
aria-label="刷新图形验证码" aria-label="刷新图形验证码"
:disabled="captchaLocked"
@click="refreshImageCode" @click="refreshImageCode"
> >
<img v-if="imageCodeUrl" :src="imageCodeUrl" alt="图形验证码" /> <img v-if="imageCodeUrl" :src="imageCodeUrl" alt="图形验证码" />
@@ -200,6 +205,7 @@ onMounted(refreshImageCode)
overflow-x: hidden; overflow-x: hidden;
place-items: center; place-items: center;
background: background:
radial-gradient(circle at 18% 4%, rgba(19, 144, 111, 0.18), transparent 220px),
linear-gradient(180deg, #dff4ec 0, rgba(246, 248, 247, 0) 44%), linear-gradient(180deg, #dff4ec 0, rgba(246, 248, 247, 0) 44%),
var(--app-bg); var(--app-bg);
} }
@@ -210,10 +216,11 @@ onMounted(refreshImageCode)
min-width: 0; min-width: 0;
padding: 22px 16px 16px; padding: 22px 16px 16px;
overflow: hidden; overflow: hidden;
border: 1px solid var(--line); border: 1px solid rgba(202, 214, 208, 0.86);
border-radius: var(--radius); border-radius: var(--panel-radius);
background: var(--surface); background: rgba(255, 255, 255, 0.9);
box-shadow: var(--shadow); box-shadow: var(--shadow);
backdrop-filter: blur(12px);
} }
.login-brand { .login-brand {
@@ -233,7 +240,8 @@ onMounted(refreshImageCode)
width: 54px; width: 54px;
height: 54px; height: 54px;
flex: 0 0 54px; flex: 0 0 54px;
border-radius: var(--radius); border: 1px solid rgba(19, 144, 111, 0.16);
border-radius: var(--panel-radius);
background: var(--primary-soft); background: var(--primary-soft);
color: var(--primary-deep); color: var(--primary-deep);
place-items: center; place-items: center;
@@ -250,6 +258,7 @@ onMounted(refreshImageCode)
margin: 0; margin: 0;
color: var(--text-strong); color: var(--text-strong);
font-size: 25px; font-size: 25px;
font-weight: 750;
line-height: 1.2; line-height: 1.2;
} }
@@ -261,10 +270,18 @@ onMounted(refreshImageCode)
.login-form :deep(.van-cell) { .login-form :deep(.van-cell) {
width: 100%; width: 100%;
height: 50px;
min-width: 0; min-width: 0;
min-height: 50px; padding: 0 12px;
align-items: center;
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: var(--radius); border-radius: var(--radius);
background: rgba(255, 255, 255, 0.86);
}
.login-form :deep(.van-cell:focus-within) {
border-color: var(--primary);
box-shadow: var(--focus-ring);
} }
.login-form :deep(.van-field__value), .login-form :deep(.van-field__value),
@@ -273,8 +290,16 @@ onMounted(refreshImageCode)
min-width: 0; min-width: 0;
} }
.login-form :deep(.van-field__value),
.login-form :deep(.van-field__body) {
height: 100%;
align-items: center;
}
.login-form :deep(.van-field__button) { .login-form :deep(.van-field__button) {
display: inline-flex;
flex: 0 0 auto; flex: 0 0 auto;
align-items: center;
padding-left: 8px; padding-left: 8px;
} }
@@ -282,10 +307,17 @@ onMounted(refreshImageCode)
max-width: 100%; max-width: 100%;
} }
.login-form :deep(.van-field__button .van-button) {
min-width: 86px;
height: 32px;
padding: 0 10px;
font-size: 12px;
}
.captcha-image-button { .captcha-image-button {
display: inline-flex; display: inline-flex;
width: 100px; width: 96px;
height: 40px; height: 32px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden; overflow: hidden;
@@ -294,13 +326,23 @@ onMounted(refreshImageCode)
border-radius: var(--radius); border-radius: var(--radius);
background: var(--surface-soft); background: var(--surface-soft);
color: var(--primary-deep); color: var(--primary-deep);
transition: transform var(--duration-fast) var(--ease-out);
}
.captcha-image-button:active {
transform: scale(0.98);
}
.captcha-image-button:disabled {
cursor: not-allowed;
opacity: 0.66;
} }
.captcha-image-button img { .captcha-image-button img {
display: block; display: block;
width: 100px; width: 96px;
height: 40px; height: 32px;
object-fit: cover; object-fit: contain;
} }
.login-note { .login-note {