feat(passport): 优化登录页账号/手机号切换和指示器动画
- 调整默认登录类型为账号登录,支持账号和手机号登录切换 - 添加底部蓝色下划线指示器,动态显示当前选中标签位置和宽度 - 实现指示器收缩滑动和展开的两阶段动画效果,提升交互体验 - 增加窗口尺寸变化时自动调整指示器位置和宽度 - 修改扫码登录切换逻辑,避免意外切换至手机号登录 - 在账号登录和手机号登录页底部添加提示“还没有账号?联系管理员开通”
This commit is contained in:
@@ -32,18 +32,21 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<h4
|
||||
class="title-btn"
|
||||
:class="loginType === 'sms' ? 'active' : ''"
|
||||
@click="onLoginType('sms')"
|
||||
>手机号登录</h4
|
||||
>
|
||||
<a-divider type="vertical" style="height: 20px" />
|
||||
<h4
|
||||
ref="accountTabRef"
|
||||
class="title-btn"
|
||||
:class="loginType === 'account' ? 'active' : ''"
|
||||
@click="onLoginType('account')"
|
||||
>账号登录</h4
|
||||
>
|
||||
<a-divider type="vertical" style="height: 20px" />
|
||||
<h4
|
||||
ref="smsTabRef"
|
||||
class="title-btn"
|
||||
:class="loginType === 'sms' ? 'active' : ''"
|
||||
@click="onLoginType('sms')"
|
||||
>手机号登录</h4
|
||||
>
|
||||
<div class="tab-indicator" :style="indicatorStyle"></div>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
@@ -59,7 +62,7 @@
|
||||
<div
|
||||
class="absolute top-2 right-2 text-lg text-white font-bold cursor-pointer"
|
||||
>
|
||||
<QrcodeOutlined v-if="loginType === 'sms'" />
|
||||
<QrcodeOutlined v-if="loginType === 'scan'" />
|
||||
<MobileOutlined v-else />
|
||||
</div>
|
||||
<!-- <span class="absolute top-3 right-1.5 text-sm text-white font-bold cursor-pointer">{{ '登录' }}</span>-->
|
||||
@@ -141,6 +144,9 @@
|
||||
>
|
||||
{{ loading ? t('login.loading') : t('login.login') }}
|
||||
</a-button>
|
||||
<div class="text-center pt-5 text-gray-500"
|
||||
>还没有账号?联系管理员开通</div
|
||||
>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="loginType === 'sms'">
|
||||
@@ -185,6 +191,9 @@
|
||||
>
|
||||
{{ loading ? t('login.loading') : t('login.login') }}
|
||||
</a-button>
|
||||
<div class="text-center pt-5 text-gray-500"
|
||||
>还没有账号?联系管理员开通</div
|
||||
>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="loginType == 'scan'">
|
||||
@@ -265,7 +274,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, unref, watch } from 'vue';
|
||||
import { ref, reactive, unref, watch, nextTick, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
@@ -313,8 +322,62 @@
|
||||
const direction = ref(0);
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
// 是否显示tenantId填写输入框
|
||||
const loginType = ref('sms');
|
||||
// 是否显示tenantId填写输入框,默认进入账号登录/手机号登录切换模式
|
||||
const loginType = ref('account');
|
||||
// 标签下划线指示器
|
||||
const accountTabRef = ref<HTMLElement>();
|
||||
const smsTabRef = ref<HTMLElement>();
|
||||
const indicatorStyle = ref({ left: '0px', width: '0px', transition: 'none' });
|
||||
// 收缩/展开两阶段动画时长(ms),模拟阿里云"先收一下再展开"的弹性效果
|
||||
const SHRINK_MS = 150;
|
||||
const EXPAND_MS = 220;
|
||||
let indicatorTimer: number | null = null;
|
||||
const updateIndicator = (animate = true) => {
|
||||
if (indicatorTimer != null) {
|
||||
clearTimeout(indicatorTimer);
|
||||
indicatorTimer = null;
|
||||
}
|
||||
nextTick(() => {
|
||||
const activeEl =
|
||||
loginType.value === 'account' ? accountTabRef.value : smsTabRef.value;
|
||||
if (!activeEl) {
|
||||
return;
|
||||
}
|
||||
const targetLeft = activeEl.offsetLeft;
|
||||
const targetWidth = activeEl.offsetWidth;
|
||||
// 非动画:直接定位(首次渲染 / 窗口缩放)
|
||||
if (!animate) {
|
||||
indicatorStyle.value = {
|
||||
left: `${targetLeft}px`,
|
||||
width: `${targetWidth}px`,
|
||||
transition: 'none'
|
||||
};
|
||||
return;
|
||||
}
|
||||
// 阶段一:收缩成一条短线,并滑向目标中心
|
||||
const targetCenter = targetLeft + targetWidth / 2;
|
||||
const smallWidth = 24;
|
||||
indicatorStyle.value = {
|
||||
left: `${targetCenter - smallWidth / 2}px`,
|
||||
width: `${smallWidth}px`,
|
||||
transition: `left ${SHRINK_MS}ms ease-in, width ${SHRINK_MS}ms ease-in`
|
||||
};
|
||||
// 阶段二:展开到目标标签的宽度
|
||||
indicatorTimer = window.setTimeout(() => {
|
||||
indicatorStyle.value = {
|
||||
left: `${targetLeft}px`,
|
||||
width: `${targetWidth}px`,
|
||||
transition: `left ${EXPAND_MS}ms ease-out, width ${EXPAND_MS}ms ease-out`
|
||||
};
|
||||
indicatorTimer = null;
|
||||
}, SHRINK_MS);
|
||||
});
|
||||
};
|
||||
watch(loginType, () => updateIndicator(true), { immediate: true });
|
||||
onMounted(() => {
|
||||
updateIndicator(false);
|
||||
window.addEventListener('resize', () => updateIndicator(false));
|
||||
});
|
||||
|
||||
// 默认配置
|
||||
const defaultConfig: Config = {
|
||||
@@ -588,7 +651,7 @@
|
||||
|
||||
const onScan = () => {
|
||||
if (loginType.value == 'scan') {
|
||||
loginType.value = 'sms';
|
||||
loginType.value = 'account';
|
||||
} else {
|
||||
loginType.value = 'scan';
|
||||
}
|
||||
@@ -713,9 +776,11 @@
|
||||
|
||||
.title-btn {
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
line-height: 50px;
|
||||
@@ -723,6 +788,14 @@
|
||||
.active {
|
||||
color: #007dff;
|
||||
}
|
||||
|
||||
.tab-indicator {
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
height: 3px;
|
||||
border-radius: 2px;
|
||||
background-color: #007dff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user