Files
property-manager-side/wuye-manager-uniapp/pageSubPack/loginSub/login.vue
2026-09-04 10:44:07 +08:00

632 lines
14 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="status_bar"></view>
<!-- 登录标题 -->
<view class="login-title">{{ mode === 'sms' ? '手机验证码登录' : '账号密码登录' }}</view>
<!-- ========== 验证码登录表单 ========== -->
<template v-if="mode === 'sms'">
<view class="input-item">
<view class="input-icon">
<image
src="https://wuye.ckdzkj.com/images/login/phone.png"
mode="aspectFit"
style="height: 100%; width: 100%"
></image>
</view>
<uni-easyinput
class="input-content"
type="number"
v-model="phoneNumber"
placeholder="请输入手机号"
:inputBorder="false"
maxlength="11"
:placeholderStyle="placeholderStyle"
>
</uni-easyinput>
</view>
<view class="input-item verify-item">
<view class="input-icon">
<image
src="https://wuye.ckdzkj.com/images/login/code.png"
mode="aspectFit"
style="height: 100%; width: 100%"
>
</image>
</view>
<uni-easyinput
class="input-content verify-input"
type="number"
v-model="verifyCode"
placeholder="请输入验证码"
:inputBorder="false"
maxlength="6"
:placeholderStyle="placeholderStyle"
>
</uni-easyinput>
<button
class="get-verify-btn"
:disabled="!canGetVerify || countDown > 0"
@click="getVerifyCode"
>
{{ countDown > 0 ? `${countDown}s后重新获取` : '获取验证码' }}
</button>
</view>
</template>
<!-- ========== 账号密码登录表单 ========== -->
<template v-else>
<view class="input-item">
<view class="input-icon">
<image
src="https://wuye.ckdzkj.com/images/login/phone.png"
mode=""
style="height: 100%; width: 100%"
></image>
</view>
<uni-easyinput
class="input-content"
type="text"
v-model="account"
placeholder="请输入帐号"
:inputBorder="false"
:placeholderStyle="placeholderStyle"
>
</uni-easyinput>
</view>
<view class="input-item">
<view class="input-icon">
<image
src="https://wuye.ckdzkj.com/images/login/password.png"
mode=""
style="height: 100%; width: 100%"
></image>
</view>
<uni-easyinput
class="input-content"
type="password"
v-model="password"
placeholder="请输入6-16位密码"
:inputBorder="false"
:placeholderStyle="placeholderStyle"
>
</uni-easyinput>
</view>
</template>
<!-- 协议勾选 -->
<view class="agreement-wrap">
<view class="agreement-item">
<view @click="toggleAgreement">
<uni-icons
type="checkbox-filled"
size="24"
color="#4D5DFA"
v-if="agreeAgreement"
></uni-icons>
<uni-icons type="checkbox" size="24" color="#999" v-else></uni-icons>
</view>
<text class="agreement-text"
>我已阅读并同意
<text class="agreePrivacy" @click.stop="goToUserAgreement">《用户协议》</text>和
<text class="agreePrivacy" @click.stop="goToPrivacy">《隐私政策》</text>
</text>
</view>
<text class="forget-pwd" v-if="mode === 'pwd'" @click="goToForgetPwd">忘记密码?</text>
</view>
<!-- 登录按钮 -->
<button class="login-btn" @click="handleLogin">登录</button>
<!-- 底部切换链接 -->
<view class="bottom-link">
<text class="link-item" @click="toggleMode">
{{ mode === 'sms' ? '账号密码登录' : '验证码登录' }}
</text>
</view>
<!-- 隐私协议弹窗 -->
<privacy-popup
v-model:show="showPrivacyPopup"
@agree="agreePrivacy"
@disagree="disagreePrivacy"
@close="showPrivacyPopup = false"
/>
</view>
</template>
<script setup>
import { ref, computed, onUnmounted } from 'vue'
import { onLoad, onReady } from '@dcloudio/uni-app'
import { getLoginBySms, getSendCode, getLoginPassword } from '../../api/api.js'
import { clearAccountStorage } from '../../api/request.js'
import PrivacyPopup from '@/components/privacy-popup/privacy-popup.vue'
import { usePermStore } from '@/stores/usePermStore'
const permStore = usePermStore()
// ========== 登录方式 ==========
// 'sms' 验证码登录 | 'pwd' 账号密码登录
const mode = ref('sms')
// ========== 响应式数据 ==========
// 验证码登录
const phoneNumber = ref('')
const verifyCode = ref('')
// 账号密码登录
const account = ref('')
const password = ref('')
const placeholderStyle = ref('font-size:24rpx')
// 是否同意协议
const agreeAgreement = ref(false)
const showPrivacyPopup = ref(false)
// 倒计时(秒)
const countDown = ref(0)
// 定时器
let timer = null
// ========== 计算属性 ==========
// 是否可以获取验证码(手机号格式正确)
const canGetVerify = computed(() => {
const phoneReg = /^1[3-9]\d{9}$/
return phoneReg.test(phoneNumber.value)
})
// ========== 方法 ==========
// 切换登录方式(底部链接点击)
const toggleMode = () => {
mode.value = mode.value === 'sms' ? 'pwd' : 'sms'
}
// 切换协议勾选状态
const toggleAgreement = () => {
agreeAgreement.value = !agreeAgreement.value
}
// 打开用户协议
const goToUserAgreement = () => {
uni.navigateTo({
url: '/pageSubPack/loginSub/user-agreement'
})
}
// 打开隐私政策
const goToPrivacy = () => {
uni.navigateTo({
url: '/pageSubPack/loginSub/privacy'
})
}
// 防止重复点击导致页面堆叠
let isNavigating = false
const navigateOnce = (url) => {
if (isNavigating) return
isNavigating = true
uni.navigateTo({
url,
complete: () => {
isNavigating = false
}
})
}
const goToForgetPwd = () => navigateOnce('/pageSubPack/loginSub/forget-pwd')
// 不同意隐私协议
const disagreePrivacy = () => {
agreeAgreement.value = false
showPrivacyPopup.value = false
}
// 获取验证码
const getVerifyCode = () => {
if (!canGetVerify.value) return
const params = {
phone: phoneNumber.value,
scene: 'butler_login'
}
uni.showLoading({
title: '发送中...',
mask: true
})
getSendCode(params)
.then(res => {
if (res.code === 200) {
uni.hideLoading()
uni.showToast({
title: '验证码已发送',
icon: 'success'
})
// 开始60秒倒计时
countDown.value = 60
timer = setInterval(() => {
countDown.value--
if (countDown.value <= 0) {
clearInterval(timer)
timer = null
}
}, 1000)
} else {
uni.showToast({
title: '验证码发送失败',
icon: 'none'
})
}
})
.catch(err => {
uni.showToast({
title: err.msg,
icon: 'none'
})
})
}
// 登录成功后的公共处理
const loginSuccess = () => {
uni.hideLoading()
// 标记引导页已看过,下次启动跳过引导
uni.setStorageSync('hasShowGuide', true)
uni.showToast({
title: '登录成功',
icon: 'success'
})
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
})
}, 1500)
}
// 验证码登录
const handleSmsLogin = () => {
if (!phoneNumber.value) {
uni.showToast({ title: '请输入手机号', icon: 'none' })
return
}
if (!/^1[3-9]\d{9}$/.test(phoneNumber.value)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
return
}
if (!verifyCode.value) {
uni.showToast({ title: '请输入验证码', icon: 'none' })
return
}
if (!/^\d{6}$/.test(verifyCode.value)) {
uni.showToast({ title: '验证码为 6 位数字', icon: 'none' })
return
}
if (!agreeAgreement.value) {
showPrivacyPopup.value = true
return
}
uni.showLoading({
title: '登录中...',
mask: true
})
const push_cid = uni.getStorageSync('push_cid')
const params = {
phone: phoneNumber.value,
smsCode: verifyCode.value,
scene: 'butler_login',
cid: push_cid
}
permStore.clearPerm()
// 清除上个账号的所有缓存,防止新账号显示旧数据
clearAccountStorage()
getLoginBySms(params)
.then(res => {
if (res.code === 200) {
if (res.data) {
uni.setStorageSync('token', res.data.token)
permStore.setPerm(res.data.user.permissions)
// 存用户ID
if (res.data.user.userId) {
uni.setStorageSync('userId', res.data.user.userId)
}
// 存当前绑定的小区ID,0 表示未绑定
const currentVillageId = res.data.currentVillageId || 0
uni.setStorageSync('currentVillageId', currentVillageId)
loginSuccess()
} else {
uni.showToast({
title: '登录失败',
icon: 'none'
})
}
} else {
uni.showToast({
title: '登录失败',
icon: 'none'
})
}
})
.catch(err => {
uni.showToast({
title: err.msg,
icon: 'none'
})
})
}
// 账号密码登录
const handlePwdLogin = () => {
if (!account.value) {
uni.showToast({ title: '请输入账号', icon: 'none' })
return
}
if (account.value.length < 4 || account.value.length > 16) {
uni.showToast({ title: '账号长度需为 4-16 位', icon: 'none' })
return
}
if (!password.value) {
uni.showToast({ title: '请输入密码', icon: 'none' })
return
}
if (password.value.length < 6) {
uni.showToast({ title: '密码至少输入 6 位', icon: 'none' })
return
}
if (password.value.length > 16) {
uni.showToast({ title: '密码不能超过 16 位', icon: 'none' })
return
}
if (!agreeAgreement.value) {
showPrivacyPopup.value = true
return
}
uni.showLoading({ title: '登录中...', mask: true })
const push_cid = uni.getStorageSync('push_cid')
const params = {
username: account.value,
password: password.value,
cid: push_cid
}
permStore.clearPerm()
// 清除上个账号的所有缓存,防止新账号显示旧数据
clearAccountStorage()
getLoginPassword(params)
.then(res => {
if (res.code === 200) {
uni.setStorageSync('token', res.data.token)
uni.setStorage({
key: 'userPermission',
data: res.data.user.permissions
})
permStore.setPerm(res.data.user.permissions)
loginSuccess()
} else {
uni.showToast({ title: res.msg, icon: 'none' })
}
})
.catch(err => {
uni.hideLoading()
uni.showToast({
title: err.msg,
icon: 'none'
})
})
}
// 登录入口(按当前模式分发)
const handleLogin = () => {
if (mode.value === 'pwd') {
handlePwdLogin()
} else {
handleSmsLogin()
}
}
// 同意隐私协议(弹窗中点击同意 → 直接登录)
const agreePrivacy = () => {
agreeAgreement.value = true
showPrivacyPopup.value = false
handleLogin()
}
// ========== 生命周期 ==========
onLoad((options) => {
// 外部跳转时可通过 ?mode=pwd 直接进入账号密码登录
if (options && options.mode === 'pwd') {
mode.value = 'pwd'
}
})
onReady(() => {
// 登录页渲染完成后关闭启动屏
// #ifdef APP-PLUS
plus.navigator.closeSplashscreen()
// #endif
})
// 页面卸载时清除定时器
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<style scoped>
/* 页面容器 */
.login-page {
padding: 80rpx 40rpx 40rpx;
min-height: 100vh;
background: repeating-linear-gradient(to bottom, #e2f0ff, #f6faff);
}
.status_bar {
height: 160px;
width: 100%;
}
/* 登录标题 */
.login-title {
font-size: 52rpx;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 142rpx;
}
/* 输入框项 */
.input-item {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 40rpx;
padding: 0 30rpx;
height: 88rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
/* 验证码输入项(特殊布局) */
.verify-item {
position: relative;
padding-right: 220rpx;
}
/* 输入框图标 */
.input-icon {
width: 50rpx;
height: 50rpx;
margin-right: 20rpx;
color: #999;
}
/* 输入框内容 */
.input-content {
flex: 1;
font-size: 32rpx;
color: #333;
display: flex;
align-items: center;
}
/* 清除 uni-easyinput 内部默认边距 */
:deep(.uni-easyinput__content) {
margin: 0 !important;
padding: 0 !important;
min-height: auto !important;
background-color: transparent !important;
}
:deep(.uni-easyinput__content-input) {
height: 60rpx !important;
min-height: 60rpx !important;
line-height: 60rpx !important;
padding: 0 !important;
margin: 0 !important;
font-size: 32rpx;
}
/* 验证码输入框 */
.verify-input {
padding-right: 20rpx;
}
/* 获取验证码按钮 */
.get-verify-btn {
position: absolute;
right: 10rpx;
top: 50%;
transform: translateY(-50%);
width: 200rpx;
height: 70rpx;
line-height: 70rpx;
background-color: #4d5dfa;
color: #fff;
border-radius: 35rpx;
font-size: 26rpx;
padding: 0;
margin: 0;
}
/* 获取验证码按钮禁用状态 */
.get-verify-btn:disabled {
background-color: #ccc;
color: #fff;
border-radius: 50px;
}
.get-verify-btn:after {
border-radius: 50px;
}
/* 协议勾选行 */
.agreement-wrap {
display: flex;
justify-content: space-between;
align-items: center;
margin: 40rpx 0 28rpx 0;
padding: 0 10rpx;
}
.agreement-item {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
/* 协议文本 */
.agreement-text {
font-size: 24rpx;
color: #666;
margin-left: 10rpx;
line-height: 1.5;
}
.agreePrivacy {
color: #4d5dfa;
}
/* 忘记密码 */
.forget-pwd {
font-size: 24rpx;
color: #666;
text-decoration: none;
flex-shrink: 0;
margin-left: 16rpx;
line-height: 1.5;
}
/* 登录按钮 */
.login-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #4d5dfa;
color: #fff;
border-radius: 8rpx;
font-size: 28rpx;
margin-bottom: 40rpx;
}
/* 登录按钮禁用状态 */
.login-btn:disabled {
background-color: #99c8ff;
color: #fff;
}
/* 禁用按钮边框 */
button:after {
border-radius: 8px;
}
/* 底部切换链接 */
.bottom-link {
display: flex;
justify-content: center;
padding: 0 20rpx;
}
.link-item {
font-size: 28rpx;
color: #4d5dfa;
text-decoration: none;
}
</style>