Files
ks-app-employment-service/pages/login/h5-login.vue

530 lines
12 KiB
Vue
Raw Normal View History

2025-11-14 18:10:59 +08:00
<template>
<view class="login-container">
<!-- 背景装饰元素 -->
<view class="bg-decoration">
<view class="bg-circle bg-circle-1"></view>
<view class="bg-circle bg-circle-2"></view>
<view class="bg-circle bg-circle-3"></view>
</view>
<!-- Logo和标题 -->
<view class="login-header">
<view class="logo-wrapper">
<image class="logo" src="@/static/logo3.png" mode="aspectFit"></image>
</view>
<view class="welcome-section">
<view class="welcome-title">欢迎登录</view>
<view class="welcome-subtitle">社保就业服务平台</view>
</view>
</view>
<!-- 登录表单 -->
<view class="login-form">
<!-- 账号输入 -->
<view class="input-group">
<view class="input-label">账号</view>
<view class="input-item">
<uni-icons type="person-filled" size="28" color="#4778EC"></uni-icons>
<input
class="input"
placeholder="请输入手机号码"
placeholder-class="placeholder"
v-model="form.username"
type="text"
/>
</view>
</view>
<!-- 密码输入 -->
<view class="input-group">
<view class="input-label">密码</view>
<view class="input-item">
<uni-icons type="locked-filled" size="28" color="#4778EC"></uni-icons>
<input
class="input"
placeholder="请输入您的密码"
placeholder-class="placeholder"
v-model="form.password"
:type="showPassword ? 'text' : 'password'"
/>
<view class="password-toggle" @click="togglePasswordVisibility">
<uni-icons :type="showPassword ? 'eye-slash-filled' : 'eye-filled'" size="28" color="#999"></uni-icons>
</view>
</view>
<!-- 密码规则提示 -->
<view class="password-rules">
<view class="rule-item" :class="{ 'rule-valid': validateRule('length') }">
<uni-icons :type="validateRule('length') ? 'checkmarkempty' : 'circle'" size="20" :color="validateRule('length') ? '#52c41a' : '#999'"></uni-icons>
<text class="rule-text">长度8位</text>
</view>
<view class="rule-item" :class="{ 'rule-valid': validateRule('letterNumber') }">
<uni-icons :type="validateRule('letterNumber') ? 'checkmarkempty' : 'circle'" size="20" :color="validateRule('letterNumber') ? '#52c41a' : '#999'"></uni-icons>
<text class="rule-text">字母数字组合</text>
</view>
<view class="rule-item" :class="{ 'rule-valid': validateRule('upperLower') }">
<uni-icons :type="validateRule('upperLower') ? 'checkmarkempty' : 'circle'" size="20" :color="validateRule('upperLower') ? '#52c41a' : '#999'"></uni-icons>
<text class="rule-text">大小写字母</text>
</view>
</view>
</view>
<!-- 登录按钮 -->
<button class="login-btn" :class="{ 'login-btn-loading': loading }" @click="handleLogin">
<view class="btn-content">
<view class="btn-text" v-if="!loading">登录</view>
<view class="loading-spinner" v-else></view>
</view>
</button>
<!-- 其他登录方式 -->
<view class="other-login">
<view class="divider">
<view class="divider-line"></view>
<view class="divider-text">其他登录方式</view>
<view class="divider-line"></view>
</view>
<view class="login-options">
<view class="login-option" @click="goToIdCardLogin">
<view class="option-icon">
<uni-icons type="idcard" size="36" color="#4778EC"></uni-icons>
</view>
<view class="option-text">社保卡登录</view>
</view>
</view>
</view>
</view>
<!-- 底部信息 -->
<view class="footer">
<view class="footer-text">© 2024 社保就业服务平台</view>
</view>
</view>
</template>
<script setup>
import { reactive, inject, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import useUserStore from '@/stores/useUserStore'
const { $api } = inject("globalFunction")
const userStore = useUserStore()
const form = reactive({
username: '',
password: ''
})
const loading = ref(false)
const showPassword = ref(false)
// 切换密码可见性
const togglePasswordVisibility = () => {
showPassword.value = !showPassword.value
}
// 验证单个密码规则
const validateRule = (ruleType) => {
const password = form.password
if (!password) return false
switch (ruleType) {
case 'length':
return password.length === 8
case 'letterNumber':
return /[a-zA-Z]/.test(password) && /\d/.test(password)
case 'upperLower':
return /[A-Z]/.test(password) && /[a-z]/.test(password)
default:
return false
}
}
// 验证密码规则
const validatePassword = (password) => {
// 规则1: 必须有字母数字组合
const hasLetterAndNumber = /[a-zA-Z]/.test(password) && /\d/.test(password)
// 规则2: 至少一个大写和一个小写字母
const hasUpperCase = /[A-Z]/.test(password)
const hasLowerCase = /[a-z]/.test(password)
// 规则3: 长度必须是8位
const isLength8 = password.length === 8
if (!hasLetterAndNumber) {
return '密码必须包含字母和数字组合'
}
if (!hasUpperCase) {
return '密码必须包含至少一个大写字母'
}
if (!hasLowerCase) {
return '密码必须包含至少一个小写字母'
}
if (!isLength8) {
return '密码长度必须为8位'
}
return null // 验证通过
}
// 处理登录
const handleLogin = async () => {
if (!form.username) {
uni.showToast({
icon: 'none',
title: '请输入账号'
})
return
}
if (!form.password) {
uni.showToast({
icon: 'none',
title: '请输入密码'
})
return
}
// 验证密码规则
const passwordError = validatePassword(form.password)
if (passwordError) {
uni.showToast({
icon: 'none',
title: passwordError
})
return
}
loading.value = true
try {
// 调用账号密码登录接口
const res = await $api.createRequest('/app/login', {
username: form.username,
password: form.password
}, 'post')
if (res.token) {
// 登录成功存储token并获取用户信息
await userStore.loginSetToken(res.token)
uni.showToast({
title: '登录成功',
icon: 'success'
})
// 跳转到首页
uni.reLaunch({
url: '/pages/index/index'
})
} else {
uni.showToast({
title: '登录失败',
icon: 'none'
})
}
} catch (error) {
console.error('登录失败:', error)
uni.showToast({
title: error.msg || '登录失败,请重试',
icon: 'none'
})
} finally {
loading.value = false
}
}
// 跳转到身份证号码登录页面
const goToIdCardLogin = () => {
uni.navigateTo({
url: '/pages/login/id-card-login'
})
}
onLoad(() => {
// 页面加载时的初始化逻辑
})
</script>
<style lang="stylus" scoped>
.login-container
padding: 0 40rpx
min-height: 100vh
background: linear-gradient(135deg, #4778EC 0%, #256BFA 100%)
position: relative
overflow: hidden
.bg-decoration
position: absolute
top: 0
left: 0
width: 100%
height: 100%
pointer-events: none
z-index: 1
.bg-circle
position: absolute
border-radius: 50%
background: rgba(255, 255, 255, 0.1)
.bg-circle-1
width: 400rpx
height: 400rpx
top: -200rpx
right: -100rpx
.bg-circle-2
width: 300rpx
height: 300rpx
bottom: 100rpx
left: -150rpx
.bg-circle-3
width: 200rpx
height: 200rpx
bottom: -50rpx
right: 100rpx
.login-header
position: relative
z-index: 2
text-align: center
margin-bottom: 80rpx
padding-top: 120rpx
.logo-wrapper
margin-bottom: 40rpx
.logo
width: 441rpx
height: 160rpx
filter: drop-shadow(0 8rpx 20rpx rgba(0, 0, 0, 0.2))
.welcome-section
.welcome-title
font-size: 48rpx
font-weight: 700
color: #FFFFFF
margin-bottom: 16rpx
text-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.2)
.welcome-subtitle
font-size: 32rpx
color: rgba(255, 255, 255, 0.9)
font-weight: 500
.login-form
position: relative
z-index: 2
background: #FFFFFF
border-radius: 32rpx
padding: 60rpx 40rpx
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15)
backdrop-filter: blur(20rpx)
border: 1rpx solid rgba(255, 255, 255, 0.2)
.input-group
margin-bottom: 48rpx
.input-label
font-size: 28rpx
font-weight: 600
color: #333333
margin-bottom: 20rpx
padding-left: 10rpx
.input-item
display: flex
align-items: center
padding: 0 32rpx
height: 100rpx
background: #F8F9FF
border-radius: 25rpx
border: 2rpx solid #E8ECFF
transition: all 0.3s ease
&:focus-within
border-color: #4778EC
background: #FFFFFF
box-shadow: 0 8rpx 24rpx rgba(71, 120, 236, 0.15)
transform: translateY(-2rpx)
.input
flex: 1
height: 100%
margin-left: 24rpx
font-size: 32rpx
color: #333333
background: transparent
font-weight: 500
.password-toggle
display: flex
align-items: center
justify-content: center
width: 60rpx
height: 60rpx
border-radius: 50%
transition: all 0.3s ease
cursor: pointer
&:active
background: rgba(71, 120, 236, 0.1)
transform: scale(0.9)
.placeholder
font-size: 32rpx
color: #999999
font-weight: 400
.password-rules
margin-top: 20rpx
display: flex
flex-wrap: wrap
gap: 20rpx
.rule-item
display: flex
align-items: center
font-size: 24rpx
color: #999999
transition: all 0.3s ease
&.rule-valid
color: #52c41a
.rule-text
margin-left: 8rpx
font-size: 24rpx
.login-btn
width: 100%
height: 100rpx
background: linear-gradient(135deg, #4778EC 0%, #256BFA 100%)
border-radius: 25rpx
color: #FFFFFF
font-size: 34rpx
font-weight: 600
border: none
margin-bottom: 48rpx
box-shadow: 0 12rpx 30rpx rgba(37, 107, 250, 0.4)
transition: all 0.3s ease
position: relative
overflow: hidden
&:active
transform: translateY(2rpx)
box-shadow: 0 6rpx 20rpx rgba(37, 107, 250, 0.3)
&.login-btn-loading
background: linear-gradient(135deg, #4778EC 0%, #256BFA 100%)
opacity: 0.8
.btn-content
display: flex
align-items: center
justify-content: center
height: 100%
.btn-text
font-size: 34rpx
font-weight: 600
.loading-spinner
width: 40rpx
height: 40rpx
border: 4rpx solid rgba(255, 255, 255, 0.3)
border-radius: 50%
border-top: 4rpx solid #FFFFFF
animation: spin 1s linear infinite
.other-login
.divider
display: flex
align-items: center
margin-bottom: 40rpx
.divider-line
flex: 1
height: 1rpx
background: #E5E5E5
.divider-text
padding: 0 24rpx
font-size: 26rpx
color: #999999
font-weight: 500
.login-options
display: flex
justify-content: center
.login-option
display: flex
flex-direction: column
align-items: center
padding: 20rpx 40rpx
border-radius: 20rpx
background: #F8F9FF
border: 2rpx solid #E8ECFF
transition: all 0.3s ease
cursor: pointer
&:active
background: #F0F4FF
transform: scale(0.95)
.option-icon
margin-bottom: 16rpx
.option-text
font-size: 24rpx
color: #4778EC
font-weight: 500
.footer
position: relative
z-index: 2
text-align: center
margin-top: 60rpx
padding-bottom: 40rpx
.footer-text
font-size: 24rpx
color: rgba(255, 255, 255, 0.7)
// 按钮重置样式
button::after
border: none
// 动画定义
@keyframes spin
0%
transform: rotate(0deg)
100%
transform: rotate(360deg)
// 响应式设计
@media (max-width: 750px)
.login-container
padding: 0 32rpx
.login-header
padding-top: 80rpx
margin-bottom: 60rpx
.logo
width: 360rpx
height: 130rpx
.welcome-title
font-size: 40rpx
.welcome-subtitle
font-size: 28rpx
.login-form
padding: 48rpx 32rpx
border-radius: 24rpx
</style>