Commit 884e0bbd by liangjianmin

feat(login): enhance button animations with particle effects and loading states

- Replace static gradient background with layered button structure for advanced animations
- Add particle burst effect on button click with staggered animation delays
- Implement gradient shift animation on button background using keyframes
- Add loading state with pulsing animation and spinner dots with wave effect
- Add success state with pop animation and text bounce effect
- Refactor button pseudo-elements into nested components (.btn-bg, .btn-particles, .btn-content)
- Update transition timing to use cubic-bezier easing for smoother motion
- Add loading spinner with three animated dots replacing previous icon approach
- Improve visual feedback with scale transforms on active and loading states
- Enhance accessibility by preventing interactions during loading state
parent cfb012ed
Showing with 172 additions and 60 deletions
......@@ -275,83 +275,183 @@ page {
.btn {
width: 100%;
height: 96rpx;
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
background: transparent;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
margin-top: 40rpx;
letter-spacing: 8rpx;
box-shadow: 0 6rpx 20rpx rgba(37, 99, 235, 0.35);
transition: all 0.2s ease;
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
position: relative;
overflow: hidden;
border: none;
&::before {
content: '';
.btn-bg {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.25), transparent);
transition: left 0.4s ease;
inset: 0;
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
border-radius: 12rpx;
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 6rpx 20rpx rgba(37, 99, 235, 0.35);
&::before {
content: '';
position: absolute;
inset: -2rpx;
background: linear-gradient(45deg, #60a5fa, #3b82f6, #2563eb, #1d4ed8);
background-size: 300% 300%;
border-radius: 12rpx;
opacity: 0;
transition: opacity 0.4s ease;
animation: gradientShift 3s ease infinite;
z-index: -1;
}
}
&::after {
content: '';
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.btn-particles {
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
transform: translate(-50%, -50%);
transition: width 0.6s ease, height 0.6s ease, opacity 0.6s ease;
inset: 0;
pointer-events: none;
opacity: 0;
.particle {
position: absolute;
width: 8rpx;
height: 8rpx;
background: #ffffff;
border-radius: 50%;
opacity: 0;
}
}
&:hover {
box-shadow: 0 8rpx 24rpx rgba(37, 99, 235, 0.45);
transform: translateY(-2rpx);
.btn-content {
position: relative;
z-index: 2;
transition: all 0.3s ease;
}
&:active {
transform: scale(0.98);
box-shadow: 0 3rpx 12rpx rgba(37, 99, 235, 0.25);
.btn-text {
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
&:active::before {
left: 100%;
.loading-spinner {
display: flex;
gap: 8rpx;
margin-right: 16rpx;
.spinner-dot {
width: 10rpx;
height: 10rpx;
background: #ffffff;
border-radius: 50%;
animation: dotWave 1.4s ease-in-out infinite;
&:nth-child(1) { animation-delay: 0s; }
&:nth-child(2) { animation-delay: 0.2s; }
&:nth-child(3) { animation-delay: 0.4s; }
}
}
&:active::after {
width: 600rpx;
height: 600rpx;
opacity: 0;
@keyframes dotWave {
0%, 60%, 100% {
transform: scale(1) translateY(0);
opacity: 0.7;
}
30% {
transform: scale(1.3) translateY(-8rpx);
opacity: 1;
}
}
&:active:not(.loading) {
transform: scale(0.96);
.btn-bg {
box-shadow: 0 2rpx 8rpx rgba(37, 99, 235, 0.25);
}
.btn-particles {
opacity: 1;
.particle {
animation: particleBurst 0.6s ease-out forwards;
@for $i from 1 through 6 {
&:nth-child(#{$i}) {
animation-delay: #{$i * 0.05}s;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(#{$i * 60}deg) translateY(0);
}
}
}
}
}
@keyframes particleBurst {
0% {
opacity: 1;
transform: translate(-50%, -50%) rotate(var(--angle, 0deg)) translateY(0);
}
100% {
opacity: 0;
transform: translate(-50%, -50%) rotate(var(--angle, 0deg)) translateY(-80rpx);
}
}
&.loading {
opacity: 0.85;
pointer-events: none;
letter-spacing: 4rpx;
.btn-bg {
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
box-shadow: 0 4rpx 16rpx rgba(37, 99, 235, 0.25);
animation: loadingPulse 2s ease-in-out infinite;
&::before {
opacity: 1;
}
}
.btn-text {
letter-spacing: 4rpx;
}
}
.loading-icon {
width: 34rpx;
height: 34rpx;
border: 3rpx solid rgba(255, 255, 255, 0.3);
border-top-color: #ffffff;
border-radius: 50%;
margin-right: 14rpx;
animation: spin 0.7s linear infinite;
@keyframes loadingPulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.02);
}
}
&.success {
.btn-bg {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
animation: successPop 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.btn-text {
animation: textBounce 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
}
@keyframes successPop {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
}
@keyframes spin {
to {
transform: rotate(360deg);
@keyframes textBounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6rpx); }
}
}
}
......
......@@ -35,9 +35,17 @@
<input :password="!showPassword" class="uni-input" placeholder="请输入登录密码" v-model="passwd" placeholder-style="color: #6E767A;" />
<text class="iconfont password-toggle" :class="showPassword ? 'icon-yanjing' : 'icon-biyanjing'" @click="togglePassword"></text>
</view>
<button class="btn row rowCenter verCenter" :class="{ loading: isLoading }" :disabled="isLoading" @click="submit()">
<text class="loading-icon" v-if="isLoading"></text>
{{ isLoading ? '登录中' : '登录' }}
<button class="btn row rowCenter verCenter" :class="{ loading: isLoading, success: loginSuccess }" :disabled="isLoading" @click="submit()">
<view class="btn-bg"></view>
<view class="btn-particles">
<view class="particle" v-for="i in 6" :key="i"></view>
</view>
<view class="btn-content row rowCenter verCenter">
<view class="loading-spinner" v-if="isLoading">
<view class="spinner-dot" v-for="i in 3" :key="i"></view>
</view>
<text class="btn-text">{{ isLoading ? '登录中' : '登录' }}</text>
</view>
</button>
</view>
<view class="copyright">©{{ currentYear }} 深圳市猎芯供应链有限公司 ALL RIGHTS RESERVED</view>
......@@ -55,6 +63,7 @@
passwd: '',
showPassword: false,
isLoading: false,
loginSuccess: false,
currentYear: new Date().getFullYear(), // 动态获取当前年份
showEmailSuggestions: false,
emailSuggestions: [],
......@@ -172,17 +181,20 @@
this.isLoading = true;
this.request(API.login, 'POST', { name: this.name, passwd: md5.hex_md5_32(this.passwd) }, true).then(res => {
if (res.retcode === 0) {
uni.setStorageSync('oa_skey', res.data.skey);
uni.setStorageSync('oa_user_id', res.data.userId);
this.loginSuccess = true;
setTimeout(() => {
uni.setStorageSync('oa_skey', res.data.skey);
uni.setStorageSync('oa_user_id', res.data.userId);
uni.switchTab({
url: '/pages/home/index'
});
uni.showToast({
title: '登录成功',
icon: 'success'
});
uni.switchTab({
url: '/pages/home/index'
});
uni.showToast({
title: '登录成功',
icon: 'success'
});
}, 600);
} else {
this.isLoading = false;
uni.showModal({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment