一体机登录功能开发
This commit is contained in:
529
pages/login/h5-login.vue
Normal file
529
pages/login/h5-login.vue
Normal file
@@ -0,0 +1,529 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user