refactor: 使用antd组件替换密码登录表单

- 废弃原有的shadcn-ui验证码组件,改用antd实现
- 更新登录表单中的验证码输入组件为新的antd实现
- 调整表单布局和样式
This commit is contained in:
dap 2026-01-08 11:47:37 +08:00
parent dbe8beb7f9
commit 1383f63361
6 changed files with 115 additions and 13 deletions

View File

@ -2,6 +2,10 @@
dev中 未发布
**REFACTOR**
- 使用antd组件替换密码登录表单
**FEATURES**
- 合并官方更新 将Radix(v1)替换为Reka UI(v2)

View File

@ -0,0 +1,87 @@
<script setup lang="ts">
import { Input } from 'ant-design-vue';
interface Props {
captcha?: string;
label?: string;
loading?: boolean;
placeholder?: string;
}
withDefaults(defineProps<Props>(), {
captcha: '',
label: '验证码',
loading: false,
placeholder: '请输入验证码',
});
defineEmits<{ captchaClick: [] }>();
const modelValue = defineModel<string>({ default: '' });
</script>
<!-- 图片验证码 -->
<template>
<div class="flex w-full">
<div class="flex-1">
<Input
size="large"
id="code"
name="code"
type="text"
autocomplete="off"
required
v-model:value="modelValue"
:class="$attrs?.class ?? {}"
:label="label"
:placeholder="placeholder"
/>
</div>
<div class="captcha-image--container relative">
<img
:src="captcha"
class="h-[40px] w-[115px] cursor-pointer rounded-r-lg"
:class="{ 'pointer-events-none': loading }"
@click="$emit('captchaClick')"
/>
<div
v-if="loading"
class="absolute inset-0 flex cursor-not-allowed items-center justify-center rounded-r-lg bg-black/30"
>
<span class="captcha-loading"></span>
</div>
</div>
</div>
</template>
<style lang="scss">
@keyframes loading-rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.captcha-loading {
box-sizing: border-box;
display: inline-block;
width: 18px;
height: 18px;
border: 2px solid #fff;
border-bottom-color: transparent;
border-radius: 50%;
animation: loading-rotation 1s linear infinite;
}
/**
验证码输入框样式
去除右边的圆角
*/
input[id='code'] {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
</style>

View File

@ -4,12 +4,13 @@ import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
import type { TenantResp } from '#/api';
import type { CaptchaResponse } from '#/api/core/captcha';
import { computed, onMounted, ref, useTemplateRef } from 'vue';
import { computed, markRaw, onMounted, ref, useTemplateRef } from 'vue';
import { AuthenticationLogin, z } from '@vben/common-ui';
import { DEFAULT_TENANT_ID } from '@vben/constants';
import { $t } from '@vben/locales';
import { Input, Select } from 'ant-design-vue';
import { omit } from 'lodash-es';
import { tenantList } from '#/api';
@ -17,6 +18,7 @@ import { captchaImage } from '#/api/core/captcha';
import { useAuthStore } from '#/store';
import { useLoginTenantId } from '../oauth-common';
import InputCaptcha from './input-captcha.vue';
import OAuthLogin from './oauth-login.vue';
defineOptions({ name: 'Login' });
@ -73,15 +75,18 @@ const { loginTenantId } = useLoginTenantId();
const formSchema = computed((): VbenFormSchema[] => {
return [
{
component: 'VbenSelect',
component: markRaw(Select),
modelPropName: 'value',
componentProps: {
class: 'bg-background h-[40px] focus:border-primary',
contentClass: 'max-h-[256px] overflow-y-auto',
class: 'w-full',
size: 'large',
showSearch: true,
optionFilterProp: 'label',
options: tenantInfo.value.voList?.map((item) => ({
label: item.companyName,
value: item.tenantId,
})),
placeholder: $t('authentication.selectAccount'),
placeholder: $t('ui.formRules.selectRequired'),
},
defaultValue: DEFAULT_TENANT_ID,
dependencies: {
@ -98,10 +103,12 @@ const formSchema = computed((): VbenFormSchema[] => {
rules: z.string().min(1, { message: $t('authentication.selectAccount') }),
},
{
component: 'VbenInput',
component: markRaw(Input),
modelPropName: 'value',
componentProps: {
class: 'focus:border-primary',
size: 'large',
placeholder: $t('authentication.usernameTip'),
allowClear: true,
},
defaultValue: 'admin',
fieldName: 'username',
@ -109,10 +116,11 @@ const formSchema = computed((): VbenFormSchema[] => {
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
},
{
component: 'VbenInputPassword',
component: markRaw(Input.Password),
modelPropName: 'value',
componentProps: {
class: 'focus:border-primary',
placeholder: $t('authentication.password'),
size: 'large',
placeholder: $t('authentication.passwordTip'),
},
defaultValue: 'admin123',
fieldName: 'password',
@ -120,7 +128,7 @@ const formSchema = computed((): VbenFormSchema[] => {
rules: z.string().min(5, { message: $t('authentication.passwordTip') }),
},
{
component: 'VbenInputCaptcha',
component: markRaw(InputCaptcha),
componentProps: {
captcha: captchaInfo.value.img,
class: 'focus:border-primary',

View File

@ -1 +1,4 @@
/**
* @deprecated 使antd组件实现来代替
*/
export { default as VbenInputCaptcha } from './input-captcha.vue';

View File

@ -109,7 +109,7 @@ defineExpose({
</Title>
</slot>
<Form />
<Form class="mb-2" />
<div
v-if="showRememberMe || showForgetPassword"

View File

@ -20,7 +20,7 @@ defineProps<{
<component
:is="Component"
:key="route.fullPath"
class="side-content mt-6 w-full sm:mx-auto md:max-w-md"
class="side-content mt-6 w-full sm:mx-auto md:max-w-md lg:max-w-[400px]"
:data-side="dataSide"
/>
</KeepAlive>