Files
ks-app-employment-service/pages/login/h5-login.vue
2025-11-27 17:07:04 +08:00

512 lines
11 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-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>