Files
martial-mini/pages/login/login.vue
2025-12-12 17:29:38 +08:00

502 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="login-page">
<!-- 背景装饰 -->
<view class="bg-decoration">
<view class="circle circle-1"></view>
<view class="circle circle-2"></view>
<view class="circle circle-3"></view>
</view>
<!-- 顶部Logo区域 -->
<view class="login-header">
<view class="logo-container">
<image class="logo" src="/static/logo.png" mode="aspectFit"></image>
</view>
<text class="title">武术赛事管理系统</text>
<text class="subtitle">MARTIAL ARTS COMPETITION</text>
</view>
<!-- 登录表单 -->
<view class="login-form">
<view class="form-title">账号登录</view>
<view class="form-item">
<view class="input-wrapper">
<view class="input-icon">📱</view>
<input
class="form-input"
v-model="form.username"
placeholder="请输入手机号或用户名"
placeholder-class="placeholder"
/>
</view>
</view>
<view class="form-item">
<view class="input-wrapper">
<view class="input-icon">🔒</view>
<input
class="form-input"
v-model="form.password"
:password="!showPassword"
placeholder="请输入密码"
placeholder-class="placeholder"
/>
<view class="eye-icon" @click="showPassword = !showPassword">
<text>{{ showPassword ? '👁️' : '👁️‍🗨️' }}</text>
</view>
</view>
</view>
<view class="form-options">
<label class="checkbox-label" @click="rememberPassword = !rememberPassword">
<view :class="['checkbox', rememberPassword ? 'checked' : '']">
<text v-if="rememberPassword" class="check-icon"></text>
</view>
<text class="checkbox-text">记住密码</text>
</label>
<text class="forgot-password" @click="handleForgotPassword">忘记密码</text>
</view>
<button class="login-btn" @click="handleLogin" :loading="loading" :disabled="loading">
<text class="btn-text">{{ loading ? '登录中...' : '立即登录' }}</text>
</button>
<view class="register-link">
还没有账号<text class="link-text" @click="goToRegister">立即注册</text>
</view>
</view>
<!-- 底部装饰 -->
<view class="footer-decoration">
<view class="wave"></view>
</view>
</view>
</template>
<script>
import userAPI from '@/api/user.js'
import { setToken, setRefreshToken, setUserInfo } from '@/utils/auth.js'
export default {
data() {
return {
form: {
username: '',
password: ''
},
showPassword: false,
rememberPassword: false,
loading: false
}
},
onLoad() {
// 读取记住的密码
const savedUsername = uni.getStorageSync('saved_username')
const savedPassword = uni.getStorageSync('saved_password')
if (savedUsername && savedPassword) {
this.form.username = savedUsername
this.form.password = savedPassword
this.rememberPassword = true
}
},
methods: {
async handleLogin() {
// 表单验证
if (!this.form.username) {
uni.showToast({
title: '请输入手机号或用户名',
icon: 'none'
})
return
}
if (!this.form.password) {
uni.showToast({
title: '请输入密码',
icon: 'none'
})
return
}
this.loading = true
try {
// 调用登录接口
const res = await userAPI.login(this.form)
// 保存Token
setToken(res.access_token)
setRefreshToken(res.refresh_token)
// 保存用户信息
const userInfo = {
userId: res.user_id,
account: res.account,
userName: res.user_name,
avatar: res.avatar,
tenantId: res.tenant_id
}
setUserInfo(userInfo)
// 记住密码
if (this.rememberPassword) {
uni.setStorageSync('saved_username', this.form.username)
uni.setStorageSync('saved_password', this.form.password)
} else {
uni.removeStorageSync('saved_username')
uni.removeStorageSync('saved_password')
}
uni.showToast({
title: '登录成功',
icon: 'success'
})
// 跳转到首页
setTimeout(() => {
uni.switchTab({
url: '/pages/home/home'
})
}, 1500)
} catch (error) {
console.error('登录失败:', error)
uni.showToast({
title: error.message || '登录失败',
icon: 'none'
})
} finally {
this.loading = false
}
},
handleForgotPassword() {
uni.showToast({
title: '请联系管理员重置密码',
icon: 'none'
})
},
goToRegister() {
uni.navigateTo({
url: '/pages/register/register'
})
}
}
}
</script>
<style lang="scss" scoped>
.login-page {
min-height: 100vh;
background: linear-gradient(135deg, #C93639 0%, #A82E31 50%, #8B1F22 100%);
padding: 0;
position: relative;
overflow: hidden;
}
/* 背景装饰圆圈 */
.bg-decoration {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
overflow: hidden;
z-index: 0;
}
.circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
animation: float 6s ease-in-out infinite;
}
.circle-1 {
width: 300rpx;
height: 300rpx;
top: -100rpx;
right: -50rpx;
animation-delay: 0s;
}
.circle-2 {
width: 200rpx;
height: 200rpx;
bottom: 100rpx;
left: -50rpx;
animation-delay: 2s;
}
.circle-3 {
width: 150rpx;
height: 150rpx;
top: 40%;
right: 50rpx;
animation-delay: 4s;
}
@keyframes float {
0%, 100% {
transform: translateY(0) scale(1);
}
50% {
transform: translateY(-30rpx) scale(1.05);
}
}
/* 顶部Logo区域 */
.login-header {
text-align: center;
padding: 120rpx 60rpx 80rpx;
position: relative;
z-index: 1;
}
.logo-container {
width: 180rpx;
height: 180rpx;
margin: 0 auto 40rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(10rpx);
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
}
.logo {
width: 140rpx;
height: 140rpx;
}
.title {
font-size: 48rpx;
font-weight: bold;
color: #fff;
margin-bottom: 16rpx;
letter-spacing: 4rpx;
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
}
.subtitle {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.85);
letter-spacing: 2rpx;
font-weight: 300;
}
/* 登录表单 */
.login-form {
background: #fff;
border-radius: 40rpx 40rpx 0 0;
padding: 60rpx 50rpx 80rpx;
position: relative;
z-index: 1;
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
min-height: calc(100vh - 480rpx);
}
.form-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 50rpx;
text-align: center;
}
.form-item {
margin-bottom: 32rpx;
}
.input-wrapper {
position: relative;
display: flex;
align-items: center;
background: #F7F8FA;
border-radius: 16rpx;
border: 2rpx solid transparent;
transition: all 0.3s ease;
overflow: hidden;
}
.input-wrapper:focus-within {
background: #fff;
border-color: #C93639;
box-shadow: 0 4rpx 16rpx rgba(201, 54, 57, 0.1);
}
.input-icon {
width: 80rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
flex-shrink: 0;
}
.form-input {
flex: 1;
height: 96rpx;
font-size: 28rpx;
color: #333;
background: transparent;
border: none;
padding-right: 30rpx;
}
.placeholder {
color: #999;
font-size: 28rpx;
}
.eye-icon {
width: 80rpx;
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
flex-shrink: 0;
cursor: pointer;
transition: opacity 0.3s;
}
.eye-icon:active {
opacity: 0.6;
}
/* 表单选项 */
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin: 40rpx 0 50rpx;
}
.checkbox-label {
display: flex;
align-items: center;
cursor: pointer;
}
.checkbox {
width: 36rpx;
height: 36rpx;
border: 2rpx solid #ddd;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
transition: all 0.3s ease;
}
.checkbox.checked {
background: #C93639;
border-color: #C93639;
}
.check-icon {
color: #fff;
font-size: 24rpx;
font-weight: bold;
}
.checkbox-text {
font-size: 26rpx;
color: #666;
}
.forgot-password {
font-size: 26rpx;
color: #C93639;
cursor: pointer;
transition: opacity 0.3s;
}
.forgot-password:active {
opacity: 0.7;
}
/* 登录按钮 */
.login-btn {
width: 100%;
height: 96rpx;
background: linear-gradient(135deg, #C93639 0%, #A82E31 100%);
border-radius: 16rpx;
border: none;
box-shadow: 0 8rpx 24rpx rgba(201, 54, 57, 0.3);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.login-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.login-btn:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(201, 54, 57, 0.2);
}
.login-btn:active::before {
left: 100%;
}
.btn-text {
font-size: 32rpx;
font-weight: bold;
color: #fff;
letter-spacing: 2rpx;
}
/* 注册链接 */
.register-link {
text-align: center;
margin-top: 50rpx;
font-size: 28rpx;
color: #666;
}
.link-text {
color: #C93639;
font-weight: bold;
margin-left: 8rpx;
cursor: pointer;
transition: opacity 0.3s;
}
.link-text:active {
opacity: 0.7;
}
/* 底部装饰 */
.footer-decoration {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 100rpx;
z-index: 0;
pointer-events: none;
}
.wave {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg, transparent 0%, rgba(255, 255, 255, 0.05) 100%);
}
</style>