This commit is contained in:
parent
be35ecb62a
commit
595d1d0932
@ -1,15 +1,40 @@
|
||||
<template>
|
||||
<form @submit.prevent="handleLogin" class="flex flex-col gap-6 text-base text-black md:text-2xl">
|
||||
<div class="overflow-hidden rounded-[20px] bg-[#78788029] px-4">
|
||||
<div class="rounded-[20px] bg-[#78788029] px-4">
|
||||
<div class="relative">
|
||||
<Input
|
||||
v-model="email"
|
||||
type="email"
|
||||
name="email"
|
||||
autocomplete="email"
|
||||
type="text"
|
||||
name="user_email_identity"
|
||||
autocomplete="new-password"
|
||||
placeholder="Email"
|
||||
class="h-[50px] border-none bg-transparent text-base focus-visible:ring-0 md:text-2xl"
|
||||
@focus="isFocused = true"
|
||||
@blur="onBlur"
|
||||
@keydown="handleKeyDown"
|
||||
/>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="isFocused && suggestList.length > 0"
|
||||
class="absolute top-[55px] left-0 z-[100] w-full rounded-xl border border-white/20 bg-white/95 p-1 shadow-2xl backdrop-blur-xl dark:bg-black/90"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in suggestList"
|
||||
:key="item.full"
|
||||
@mousedown="selectSuggest(item.full)"
|
||||
:class="[
|
||||
'flex cursor-pointer items-center rounded-lg px-3 py-2 text-sm transition-colors md:text-xl',
|
||||
activeIndex === index ? 'bg-[#A8FF53]/10' : '',
|
||||
]"
|
||||
>
|
||||
<span class="font-medium text-black/80">{{ prefix }}</span>
|
||||
|
||||
<span class="font-medium text-black">{{ item.userInputPart }}</span>
|
||||
|
||||
<span class="text-black opacity-30">{{ item.suggestPart }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<div class="absolute bottom-0 left-0 h-[1px] w-full bg-gray-400/30"></div>
|
||||
</div>
|
||||
|
||||
@ -56,7 +81,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { toast } from 'vue-sonner'
|
||||
@ -68,21 +93,90 @@ const CodeSentTipRef = ref<InstanceType<typeof CodeSentTip> | null>(null)
|
||||
const email = ref('')
|
||||
const code = ref('')
|
||||
const countdown = ref(0)
|
||||
const isFocused = ref(false)
|
||||
const commonSuffixes = ['@gmail.com', '@outlook.com', '@qq.com', '@163.com']
|
||||
const activeIndex = ref(-1) // 记录当前键盘选中的索引
|
||||
|
||||
// 1. 逻辑:提取 @ 前的字符
|
||||
const prefix = computed(() => {
|
||||
const atIndex = email.value.indexOf('@')
|
||||
return atIndex > -1 ? email.value.slice(0, atIndex) : email.value
|
||||
})
|
||||
|
||||
// 输入变化时重置索引
|
||||
watch(email, () => {
|
||||
activeIndex.value = -1
|
||||
})
|
||||
// 键盘控制逻辑
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (!suggestList.value.length) return
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault()
|
||||
activeIndex.value = (activeIndex.value + 1) % suggestList.value.length
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault()
|
||||
activeIndex.value =
|
||||
(activeIndex.value - 1 + suggestList.value.length) % suggestList.value.length
|
||||
} else if (e.key === 'Enter' && activeIndex.value !== -1) {
|
||||
e.preventDefault()
|
||||
selectSuggest(suggestList.value[activeIndex.value])
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 逻辑:生成建议列表
|
||||
const suggestList = computed(() => {
|
||||
const val = email.value.trim()
|
||||
if (!val || val.length < 1) return []
|
||||
|
||||
const atIndex = val.indexOf('@')
|
||||
// 用户目前输入的后缀部分(例如输入了 "abc@gm",则 domainPart 为 "@gm")
|
||||
const domainPart = atIndex > -1 ? val.slice(atIndex) : ''
|
||||
|
||||
// 过滤匹配的后缀
|
||||
const matches = commonSuffixes.filter((s) => s.startsWith(domainPart) && s !== domainPart)
|
||||
|
||||
return matches.map((full) => {
|
||||
return {
|
||||
full: full, // 完整后缀,用于点击填入
|
||||
userInputPart: domainPart, // 用户已经输入的后缀部分(标色)
|
||||
suggestPart: full.replace(domainPart, ''), // 还没输入的推荐部分(置灰)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 4. 处理选中
|
||||
const selectSuggest = (fullSuffix: string) => {
|
||||
email.value = prefix.value + fullSuffix
|
||||
isFocused.value = false
|
||||
}
|
||||
|
||||
const onBlur = () => {
|
||||
// 必须延迟,否则点击列表时会先触发 blur 导致列表消失无法选中
|
||||
setTimeout(() => (isFocused.value = false), 200)
|
||||
}
|
||||
|
||||
// 3. 校验逻辑
|
||||
const validateEmail = (str: string) => {
|
||||
return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(str)
|
||||
}
|
||||
const emit = defineEmits(['close'])
|
||||
const handleGetCode = () => {
|
||||
if (!email.value) {
|
||||
toast('请输入邮箱')
|
||||
return
|
||||
}
|
||||
CodeSentTipRef.value?.show()
|
||||
|
||||
// 发送前强制校验格式
|
||||
if (!validateEmail(email.value)) {
|
||||
return toast.error('邮箱格式无效,请检查')
|
||||
}
|
||||
|
||||
request
|
||||
.get<{ exist: boolean }>('/api/v1/auth/check', {
|
||||
email: email.value,
|
||||
})
|
||||
.then(({ exist }) => {
|
||||
console.log(exist)
|
||||
request
|
||||
.post('/api/v1/common/send_code', {
|
||||
// 1=登录, 2=注册,
|
||||
@ -90,6 +184,7 @@ const handleGetCode = () => {
|
||||
type: exist ? 2 : 1,
|
||||
})
|
||||
.then(() => {
|
||||
CodeSentTipRef.value?.show()
|
||||
countdown.value = 60
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--
|
||||
@ -100,6 +195,13 @@ const handleGetCode = () => {
|
||||
}
|
||||
const router = useRouter()
|
||||
const handleLogin = () => {
|
||||
if (!code.value) {
|
||||
toast('请输入验证码')
|
||||
return
|
||||
}
|
||||
if (!validateEmail(email.value)) {
|
||||
return toast.error('邮箱格式无效,请检查')
|
||||
}
|
||||
request
|
||||
.post<any, { token: string }>('/api/v1/auth/login/email', {
|
||||
email: email.value,
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<button
|
||||
v-else
|
||||
@click="openLoginModal"
|
||||
class="flex h-[30px] cursor-pointer items-center justify-center rounded-full bg-[#78788029] px-6 text-sm font-bold backdrop-blur-md transition hover:brightness-110 md:h-[60px] md:w-[220px] md:text-2xl"
|
||||
class="flex h-[30px] cursor-pointer items-center justify-center rounded-full bg-[#78788029] px-6 text-sm font-bold backdrop-blur-md transition hover:brightness-110 md:h-[40px] md:w-[220px] md:text-xl"
|
||||
>
|
||||
登录 / 注册
|
||||
</button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user