512 lines
11 KiB
Vue
512 lines
11 KiB
Vue
<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>
|
||
|
||
<!-- 底部信息 -->
|
||
<view class="footer">
|
||
<view class="footer-text">新疆喀什智慧就业平台</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 {
|
||
// 调用手机号登录接口 /app/phoneLogin
|
||
const res = await $api.createRequest('/app/phoneLogin', {
|
||
username: form.username,
|
||
password: form.password
|
||
}, 'post')
|
||
|
||
if (res.token) {
|
||
// 登录成功,存储token并获取用户信息
|
||
await userStore.loginSetToken(res.token)
|
||
uni.showToast({
|
||
title: '登录成功',
|
||
icon: 'success'
|
||
})
|
||
window.location.assign('http://222.80.110.161:11111/mechine-dual-vue/login')
|
||
// // 跳转到首页
|
||
// 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>
|