feat: 将登录页面的表单输入、验证和复选框替换为 Naive UI 组件并进行样式调整

This commit is contained in:
Wei_佳 2025-11-21 12:51:46 +08:00
parent 8b17d74359
commit ba2bc6c53e

View File

@ -11,58 +11,60 @@
referrerpolicy="no-referrer"
:src="loginTitleImg"
/>
<div class="block_1 flex-row">
<div class="image-text_1 flex-row">
<img
class="thumbnail_1"
referrerpolicy="no-referrer"
:src="iconUserImg"
/>
<input
v-model="loginInfo.phone"
class="input-reset text-group_1"
placeholder="请输入账号"
type="text"
@keypress.enter="handleLogin"
/>
</div>
</div>
<div class="block_2 flex-row justify-between">
<div class="section_4 flex-row">
<div class="image-text_2 flex-row">
<img
class="thumbnail_2"
referrerpolicy="no-referrer"
:src="iconCodeImg"
/>
<input
v-model="loginInfo.verifyCode"
class="input-reset text-group_2"
placeholder="请输入验证码"
type="text"
@keypress.enter="handleLogin"
/>
<n-form ref="formRef" :model="loginInfo" :rules="rules" :show-label="false">
<n-form-item path="phone">
<div class="block_1 flex-row">
<div class="image-text_1 flex-row">
<img
class="thumbnail_1"
referrerpolicy="no-referrer"
:src="iconUserImg"
/>
<n-input
v-model:value="loginInfo.phone"
class="input-reset text-group_1 custom-n-input"
placeholder="请输入账号"
type="text"
:bordered="false"
@keypress.enter="handleLogin"
/>
</div>
</div>
</n-form-item>
<div class="block_2 flex-row justify-between">
<n-form-item path="verifyCode" class="verify-code-item">
<div class="section_4 flex-row">
<div class="image-text_2 flex-row">
<img
class="thumbnail_2"
referrerpolicy="no-referrer"
:src="iconCodeImg"
/>
<n-input
v-model:value="loginInfo.verifyCode"
class="input-reset text-group_2 custom-n-input"
placeholder="请输入验证码"
type="text"
:bordered="false"
@keypress.enter="handleLogin"
/>
</div>
</div>
</n-form-item>
<div
class="text-wrapper_1 flex-col cursor-pointer"
@click="handleSendCode"
:class="{ 'disabled': countdown > 0 }"
>
<span class="text_1">{{ countdown > 0 ? `${countdown}秒后重新获取` : '获取验证码' }}</span>
</div>
</div>
<div
class="text-wrapper_1 flex-col cursor-pointer"
@click="handleSendCode"
:class="{ 'disabled': countdown > 0 }"
>
<span class="text_1">{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}</span>
</div>
</div>
<div class="block_3 flex-row justify-between">
<div class="image-text_3 flex-row cursor-pointer" @click="isAgreed = !isAgreed">
<img
class="thumbnail_3"
referrerpolicy="no-referrer"
:src="iconCheckboxImg"
:style="{ opacity: isAgreed ? 1 : 0.5, filter: isAgreed ? 'none' : 'grayscale(100%)' }"
/>
<span class="text-group_3" style="margin-left: 5px;">阅读并同意</span>
</div>
<span class="text_2 cursor-pointer">用户协议</span>
</n-form>
<div class="block_3 flex-row">
<n-checkbox v-model:checked="isAgreed" class="agreement-checkbox">
<span class="text-group_3">阅读并同意</span>
<span class="text_2">用户协议</span>
</n-checkbox>
</div>
<div
class="text-wrapper_2 flex-col cursor-pointer"
@ -110,6 +112,17 @@ if(localStorage.getItem('phone')){
loginInfo.value.phone = localStorage.getItem('phone')
}
const formRef = ref(null)
const rules = {
phone: [
{ required: true, message: '请输入手机号', trigger: ['input', 'blur'] },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: ['input', 'blur'] }
],
verifyCode: [
{ required: true, message: '请输入验证码', trigger: ['input', 'blur'] }
]
}
// Validate phone
function validatePhone(phone) {
const phoneReg = /^1[3-9]\d{9}$/
@ -153,50 +166,38 @@ async function handleSendCode() {
// Login
async function handleLogin() {
const { phone, verifyCode } = loginInfo.value
if (!phone) {
$message.warning('请输入手机号')
return
}
if (!validatePhone(phone)) {
$message.warning('请输入正确的手机号')
return
}
if (!verifyCode) {
$message.warning('请输入验证码')
return
}
if (!isAgreed.value) {
$message.warning('请阅读并同意用户协议')
return
}
loading.value = true
try {
const res = await api.loginWithVerifyCode({ phone, code: verifyCode })
if (res.data?.access_token) {
setToken(res.data.access_token)
localStorage.setItem('phone', phone)
if (query.redirect) {
const path = query.redirect
Reflect.deleteProperty(query, 'redirect')
router.push({ path, query })
} else {
router.push('/home')
formRef.value?.validate(async (errors) => {
if (!errors) {
loading.value = true
try {
const res = await api.loginWithVerifyCode({ phone, code: verifyCode })
if (res.data?.access_token) {
setToken(res.data.access_token)
localStorage.setItem('phone', phone)
if (query.redirect) {
const path = query.redirect
Reflect.deleteProperty(query, 'redirect')
router.push({ path, query })
} else {
router.push('/home')
}
} else {
$message.error('登录失败未获取到token')
}
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
} else {
$message.error('登录失败未获取到token')
}
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
})
}
onBeforeUnmount(() => {
@ -411,35 +412,19 @@ onBeforeUnmount(() => {
.block_3 {
width: 100%;
height: 14px;
margin-top: 36px;
align-items: center;
}
.image-text_3 {
display: flex;
align-items: center;
transition: all 0.3s ease;
}
.image-text_3:hover {
opacity: 0.8;
}
.thumbnail_3 {
width: 14px;
height: 14px;
}
.text-group_3 {
color: rgba(136, 136, 136, 1);
font-size: 12px;
letter-spacing: 0.32px;
font-family: Alibaba-PuHuiTi-R, sans-serif;
font-weight: normal;
text-align: center;
white-space: nowrap;
line-height: 12px;
margin-right: 4px;
}
.text_2 {
@ -448,11 +433,10 @@ onBeforeUnmount(() => {
letter-spacing: 0.32px;
font-family: Alibaba-PuHuiTi-M, sans-serif;
font-weight: normal;
text-align: left;
white-space: nowrap;
line-height: 12px;
transition: all 0.3s ease;
position: relative;
cursor: pointer;
}
.text_2:hover {
@ -495,4 +479,117 @@ onBeforeUnmount(() => {
.text-wrapper_2:hover .text_3 {
letter-spacing: 1px;
}
/* Custom Naive UI Overrides */
.custom-n-input {
background-color: transparent !important;
font-size: 14px;
line-height: 14px;
}
.custom-n-input :deep(.n-input-wrapper) {
padding: 0 !important;
}
.custom-n-input :deep(.n-input__input-el) {
height: 16px !important;
line-height: 16px !important;
color: rgba(48, 49, 51, 1);
font-family: Alibaba-PuHuiTi-R, sans-serif;
}
.custom-n-input :deep(.n-input__placeholder) {
color: rgba(189, 189, 189, 1) !important;
}
.custom-n-input :deep(.n-input__state-border),
.custom-n-input :deep(.n-input__border) {
display: none !important;
}
/* Fix Form Item Layout to prevent shifts */
:deep(.n-form-item-feedback-wrapper) {
position: absolute;
top: 100%;
left: 0;
padding-top: 2px;
line-height: 1;
}
:deep(.n-form-item) {
display: block;
margin-bottom: 0 !important;
}
/* Ensure the code input item doesn't break flex layout */
.verify-code-item {
flex: 1;
/* The section_4 inside has fixed width, so we just let it be */
max-width: 191px;
}
/* Input Container Interactions */
.block_1,
.section_4 {
transition: all 0.3s ease;
}
.block_1:hover,
.section_4:hover {
border-color: rgba(163, 1, 19, 0.5);
}
.block_1:focus-within,
.section_4:focus-within {
border-color: rgba(163, 1, 19, 1);
box-shadow: 0 0 0 2px rgba(163, 1, 19, 0.1);
}
/* Error State Styling */
:deep(.n-form-item.n-form-item--error) .block_1,
:deep(.n-form-item.n-form-item--error) .section_4 {
border-color: #d03050;
}
:deep(.n-form-item.n-form-item--error) .block_1:focus-within,
:deep(.n-form-item.n-form-item--error) .section_4:focus-within {
border-color: #d03050;
box-shadow: 0 0 0 2px rgba(208, 48, 80, 0.1);
}
/* Checkbox Customization */
:deep(.agreement-checkbox .n-checkbox-box) {
border-radius: 2px;
transition: all 0.3s ease;
}
/* Checked State - Solid Red */
:deep(.agreement-checkbox.n-checkbox--checked .n-checkbox-box) {
background-color: rgba(163, 1, 19, 1) !important;
border: 1px solid rgba(163, 1, 19, 1) !important;
}
/* Hide the separate border element when checked to prevent color overlap/double borders */
:deep(.agreement-checkbox.n-checkbox--checked .n-checkbox-box .n-checkbox-box__border) {
display: none !important;
}
/* Hover State - Red Border */
:deep(.agreement-checkbox:hover .n-checkbox-box .n-checkbox-box__border) {
border-color: rgba(163, 1, 19, 1) !important;
}
/* Focus State - Red Border & Shadow */
:deep(.agreement-checkbox.n-checkbox--focus .n-checkbox-box .n-checkbox-box__border) {
border-color: rgba(163, 1, 19, 1) !important;
}
:deep(.agreement-checkbox.n-checkbox--focus .n-checkbox-box) {
box-shadow: 0 0 0 2px rgba(163, 1, 19, 0.2) !important;
}
/* Ensure checkmark is white */
:deep(.agreement-checkbox .n-checkbox-icon) {
color: #fff !important;
}
</style>