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 157 additions and 45 deletions
...@@ -275,83 +275,183 @@ page { ...@@ -275,83 +275,183 @@ page {
.btn { .btn {
width: 100%; width: 100%;
height: 96rpx; height: 96rpx;
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); background: transparent;
border-radius: 12rpx; border-radius: 12rpx;
font-size: 32rpx; font-size: 32rpx;
font-weight: 600; font-weight: 600;
color: #ffffff; color: #ffffff;
margin-top: 40rpx; margin-top: 40rpx;
letter-spacing: 8rpx; letter-spacing: 8rpx;
box-shadow: 0 6rpx 20rpx rgba(37, 99, 235, 0.35); transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
transition: all 0.2s ease;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border: none;
.btn-bg {
position: absolute;
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 { &::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; inset: -2rpx;
left: -100%; background: linear-gradient(45deg, #60a5fa, #3b82f6, #2563eb, #1d4ed8);
width: 100%; background-size: 300% 300%;
height: 100%; border-radius: 12rpx;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.25), transparent); opacity: 0;
transition: left 0.4s ease; transition: opacity 0.4s ease;
animation: gradientShift 3s ease infinite;
z-index: -1;
}
} }
&::after { @keyframes gradientShift {
content: ''; 0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.btn-particles {
position: absolute; position: absolute;
top: 50%; inset: 0;
left: 50%; pointer-events: none;
width: 0; opacity: 0;
height: 0;
.particle {
position: absolute;
width: 8rpx;
height: 8rpx;
background: #ffffff;
border-radius: 50%; 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;
opacity: 0; opacity: 0;
} }
}
&:hover { .btn-content {
box-shadow: 0 8rpx 24rpx rgba(37, 99, 235, 0.45); position: relative;
transform: translateY(-2rpx); z-index: 2;
transition: all 0.3s ease;
} }
&:active { .btn-text {
transform: scale(0.98); transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 3rpx 12rpx rgba(37, 99, 235, 0.25);
} }
&:active::before { .loading-spinner {
left: 100%; 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; }
}
}
@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);
&:active::after { .btn-bg {
width: 600rpx; box-shadow: 0 2rpx 8rpx rgba(37, 99, 235, 0.25);
height: 600rpx; }
.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; opacity: 0;
transform: translate(-50%, -50%) rotate(var(--angle, 0deg)) translateY(-80rpx);
}
} }
&.loading { &.loading {
opacity: 0.85;
pointer-events: none; pointer-events: none;
.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; letter-spacing: 4rpx;
} }
}
.loading-icon { @keyframes loadingPulse {
width: 34rpx; 0%, 100% {
height: 34rpx; transform: scale(1);
border: 3rpx solid rgba(255, 255, 255, 0.3); }
border-top-color: #ffffff; 50% {
border-radius: 50%; transform: scale(1.02);
margin-right: 14rpx;
animation: spin 0.7s linear infinite;
} }
} }
@keyframes spin { &.success {
to { .btn-bg {
transform: rotate(360deg); 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 textBounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6rpx); }
} }
} }
} }
......
...@@ -35,9 +35,17 @@ ...@@ -35,9 +35,17 @@
<input :password="!showPassword" class="uni-input" placeholder="请输入登录密码" v-model="passwd" placeholder-style="color: #6E767A;" /> <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> <text class="iconfont password-toggle" :class="showPassword ? 'icon-yanjing' : 'icon-biyanjing'" @click="togglePassword"></text>
</view> </view>
<button class="btn row rowCenter verCenter" :class="{ loading: isLoading }" :disabled="isLoading" @click="submit()"> <button class="btn row rowCenter verCenter" :class="{ loading: isLoading, success: loginSuccess }" :disabled="isLoading" @click="submit()">
<text class="loading-icon" v-if="isLoading"></text> <view class="btn-bg"></view>
{{ isLoading ? '登录中' : '登录' }} <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> </button>
</view> </view>
<view class="copyright">©{{ currentYear }} 深圳市猎芯供应链有限公司 ALL RIGHTS RESERVED</view> <view class="copyright">©{{ currentYear }} 深圳市猎芯供应链有限公司 ALL RIGHTS RESERVED</view>
...@@ -55,6 +63,7 @@ ...@@ -55,6 +63,7 @@
passwd: '', passwd: '',
showPassword: false, showPassword: false,
isLoading: false, isLoading: false,
loginSuccess: false,
currentYear: new Date().getFullYear(), // 动态获取当前年份 currentYear: new Date().getFullYear(), // 动态获取当前年份
showEmailSuggestions: false, showEmailSuggestions: false,
emailSuggestions: [], emailSuggestions: [],
...@@ -172,10 +181,12 @@ ...@@ -172,10 +181,12 @@
this.isLoading = true; this.isLoading = true;
this.request(API.login, 'POST', { name: this.name, passwd: md5.hex_md5_32(this.passwd) }, true).then(res => { this.request(API.login, 'POST', { name: this.name, passwd: md5.hex_md5_32(this.passwd) }, true).then(res => {
if (res.retcode === 0) { if (res.retcode === 0) {
this.loginSuccess = true;
setTimeout(() => {
uni.setStorageSync('oa_skey', res.data.skey); uni.setStorageSync('oa_skey', res.data.skey);
uni.setStorageSync('oa_user_id', res.data.userId); uni.setStorageSync('oa_user_id', res.data.userId);
uni.switchTab({ uni.switchTab({
url: '/pages/home/index' url: '/pages/home/index'
}); });
...@@ -183,6 +194,7 @@ ...@@ -183,6 +194,7 @@
title: '登录成功', title: '登录成功',
icon: 'success' icon: 'success'
}); });
}, 600);
} else { } else {
this.isLoading = false; this.isLoading = false;
uni.showModal({ 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