Files
property-resident-side/property-uniapp-project/pageSubPack/loginSub/register-new-user.vue
2026-08-24 17:04:18 +08:00

548 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="register-page">
<view class="status_bar"></view>
<!-- 注册标题 -->
<view class="register-title">注册用户</view>
<!-- 手机号输入框 -->
<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>
<input class="input-content native-input" :type="inputType" v-model="phoneNumber" placeholder="请输入手机号"
placeholder-style="font-size:24rpx" maxlength="11"/>
</view>
<!-- 验证码输入 + 获取验证码按钮 -->
<view class="input-item verify-item">
<view class="input-icon">
<image src="https://wuye.ckdzkj.com/images/login/code.png" mode="" style="height: 100%;width: 100%;">
</image>
</view>
<input class="input-content verify-input native-input" :type="inputType" v-model="verifyCode" placeholder="请输入验证码"
maxlength="6" placeholder-style="font-size:24rpx"/>
<button class="get-verify-btn" :disabled="!canGetVerify || countDown > 0" @click="getVerifyCode">
{{ countDown > 0 ? `${countDown}s后重新获取` : '获取验证码' }}
</button>
</view>
<!-- 账号输入框 -->
<view class="input-item-wrap">
<view class="input-item">
<view class="input-icon">
<image src="https://wuye.ckdzkj.com/images/login/account.png" mode=""
style="height: 100%;width: 100%;"></image>
</view>
<uni-easyinput class="input-content" type="text" v-model="account" @blur="validateAccount" @focus="clearAccountError" placeholder="请输入账号4-20位" :inputBorder="false"
maxlength="20" :placeholderStyle="placeholderStyle" />
</view>
<text class="input-error" v-if="accountError">{{ accountError }}</text>
</view>
<!-- 密码输入框 -->
<view class="input-item-wrap">
<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" @blur="validatePassword" @focus="clearPasswordError" placeholder="请输入密码6-16位"
:inputBorder="false" maxlength="16" :placeholderStyle="placeholderStyle" />
</view>
<text class="input-error" v-if="passwordError">{{ passwordError }}</text>
</view>
<!-- 协议勾选 -->
<view class="agreement-item">
<view @click="toggleAgreement">
<uni-icons type="checkbox-filled" size="24" color="#007aff" 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>
<!-- 立即注册按钮 -->
<button class="register-btn" @click="handleRegister">
立即注册
</button>
<!-- 跳转登录链接 -->
<view class="login-link">
<navigator class="link-item" url="/pageSubPack/loginSub/login">已有账号去登录</navigator>
</view>
<!-- 隐私协议弹窗 -->
<privacy-popup v-model:show="showPrivacyPopup" @agree="agreePrivacy" @disagree="disagreePrivacy" @close="showPrivacyPopup = false" />
</view>
</template>
<script setup>
import {
ref,
computed,
watch,
onUnmounted
} from 'vue';
import {
getRegisterUser,
getSendCode
} from '../api/api.js'
import PrivacyPopup from '@/components/privacy-popup/privacy-popup.vue'
// 输入框类型:统一用 text避免 number 在真机上把 -/. 过滤掉导致手机号被误判合法;非法手机号由 canGetVerify 禁用获取验证码按钮
const inputType = 'text'
// ========== 响应式数据 ==========
const phoneNumber = ref('');
const verifyCode = ref('');
const account = ref('');
const accountError = ref('');
const password = ref('');
const passwordError = ref('');
const placeholderStyle = ref('font-size:24rpx')
const agreeAgreement = ref(false);
const showPrivacyPopup = ref(false)
const pendingAction = ref('')
const countDown = ref(0);
let timer = null;
// ========== 计算属性 ==========
const canGetVerify = computed(() => {
const phoneReg = /^1[3-9]\d{9}$/;
return phoneReg.test(phoneNumber.value);
});
const canRegister = computed(() => {
const phoneReg = /^1[3-9]\d{9}$/;
const verifyReg = /^\d{6}$/;
const accountReg = /^.{4,20}$/;
const pwdReg = /^.{6,16}$/;
return phoneReg.test(phoneNumber.value) &&
verifyReg.test(verifyCode.value) &&
accountReg.test(account.value) &&
pwdReg.test(password.value) &&
agreeAgreement.value;
});
// 实时校验密码格式(禁止颜文字/emoji/特殊符号,只允许中文、字母、数字)
watch(password, (val) => {
if (!val) { passwordError.value = ''; return }
if (val.length < 6 || val.length > 16) {
passwordError.value = '密码需 6-16 位'
} else if (/[^一-龥a-zA-Z0-9]/.test(val)) {
passwordError.value = '密码不能包含特殊符号或表情'
} else {
passwordError.value = ''
}
})
// ========== 方法 ==========
const goBack = () => {
uni.navigateBack();
};
const toggleAgreement = () => {
agreeAgreement.value = !agreeAgreement.value;
};
// 账号失焦校验(禁止颜文字/emoji/特殊符号,只允许中文、字母、数字)
const validateAccount = () => {
const val = account.value
if (!val) { accountError.value = ''; return }
if (/\s/.test(val)) {
accountError.value = '账号不能包含空格'
} else if (val.length < 4 || val.length > 20) {
accountError.value = '账号需 4-20 位'
} else if (/[^一-龥a-zA-Z0-9]/.test(val)) {
accountError.value = '账号不能包含特殊符号或表情'
} else {
accountError.value = ''
}
};
// 账号聚焦清除错误提示
const clearAccountError = () => {
accountError.value = ''
};
// 密码失焦校验(禁止颜文字/emoji/特殊符号,只允许中文、字母、数字)
const validatePassword = () => {
const val = password.value
if (!val) { passwordError.value = ''; return }
if (val.length < 6 || val.length > 16) {
passwordError.value = '密码需 6-16 位'
} else if (/[^一-龥a-zA-Z0-9]/.test(val)) {
passwordError.value = '密码不能包含特殊符号或表情'
} else {
passwordError.value = ''
}
};
// 密码聚焦清除错误提示
const clearPasswordError = () => {
passwordError.value = ''
};
const goToUserAgreement = () => {
uni.navigateTo({ url: '/pageSubPack/loginSub/user-agreement' })
}
const goToPrivacy = () => {
uni.navigateTo({ url: '/pageSubPack/loginSub/privacy' })
}
const checkForm = () => {};
const openPrivacyPopup = (action) => {
pendingAction.value = action || ''
showPrivacyPopup.value = true
}
const agreePrivacy = () => {
showPrivacyPopup.value = false
agreeAgreement.value = true
if (pendingAction.value === 'register') {
doRegister()
}
pendingAction.value = ''
}
const disagreePrivacy = () => {
showPrivacyPopup.value = false
pendingAction.value = ''
}
// 获取验证码
const getVerifyCode = () => {
if (!canGetVerify.value) return;
let params = {
phone: phoneNumber.value,
scene: 'register'
}
getSendCode(params).then(res => {
if (res.code === 200) {
uni.showToast({
title: '验证码已发送',
icon: 'success'
});
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 handleRegister = () => {
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 (!account.value) {
uni.showToast({ title: '请输入账号', icon: 'none' });
return;
}
if (account.value.length < 4 || account.value.length > 20) {
uni.showToast({ title: '账号长度需为 4-20 位', icon: 'none' });
return;
}
if (/\s/.test(account.value)) {
uni.showToast({ title: '账号不能包含空格', icon: 'none' });
return;
}
if (/[^一-龥a-zA-Z0-9]/.test(account.value)) {
uni.showToast({ title: '账号不能包含特殊符号或表情', 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 (/[^一-龥a-zA-Z0-9]/.test(password.value)) {
uni.showToast({ title: '密码不能包含特殊符号或表情', icon: 'none' });
return;
}
if (!agreeAgreement.value) {
openPrivacyPopup('register')
return;
}
doRegister()
};
const doRegister = () => {
uni.showLoading({
title: '注册中...',
mask: true
});
let params = {
phone: phoneNumber.value,
smsCode: verifyCode.value,
username: account.value,
password: password.value
}
getRegisterUser(params).then(res => {
if (res.code === 200) {
uni.hideLoading();
uni.showToast({
title: '注册成功',
icon: 'success'
});
setTimeout(() => {
uni.navigateTo({
url: '/pageSubPack/loginSub/login?mode=pwd'
});
}, 1500);
} else {
uni.showToast({
title: '注册失败',
icon: 'none'
});
}
}).catch((err) => {
uni.hideLoading();
uni.showToast({
title: err.msg || '网络异常',
icon: 'none'
});
})
};
// ========== 生命周期 ==========
onUnmounted(() => {
if (timer) {
clearInterval(timer);
}
});
</script>
<style scoped>
/* 页面容器 */
.register-page {
padding: 20rpx 40rpx 40rpx;
height: 100vh;
overflow: hidden;
box-sizing: border-box;
background: repeating-linear-gradient(180deg, #E2F0FF, #F6FAFF);
}
.status_bar {
height: 46px;
width: 100%;
}
.nav-bar {
display: flex;
align-items: center;
margin-bottom: 70rpx;
}
.nav-title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 500;
color: #333;
margin-right: 100rpx;
}
.register-title {
font-size: 40rpx;
font-weight: 600;
color: #333;
margin-bottom: 60rpx;
}
/* 输入框外层包装(用于承载错误提示) */
.input-item-wrap {
margin-bottom: 30rpx;
}
.input-item-wrap .input-item {
margin-bottom: 0;
}
/* 输入框通用样式 */
.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 {
margin-right: 20rpx;
color: #999;
width: 50rpx;
height: 50rpx;
}
/* 输入框内容 */
.input-content {
flex: 1;
font-size: 32rpx;
color: #333;
height: 100%;
}
/* 原生 input 垂直居中 */
.native-input {
line-height: 88rpx;
}
/* 深度覆盖 uni-easyinput 内部默认样式 */
.input-content :deep(.uni-easyinput__content) {
height: 100% !important;
min-height: unset !important;
padding: 0 !important;
background-color: transparent !important;
}
.input-content :deep(.uni-easyinput__content-input) {
height: 100% !important;
min-height: unset !important;
padding: 0 !important;
line-height: 88rpx;
}
.verify-input {
padding-right: 20rpx;
}
/* 输入框错误提示(在输入框下方) */
.input-error {
color: #e74c3c;
font-size: 22rpx;
line-height: 1.4;
padding: 10rpx 30rpx 0;
}
/* 获取验证码按钮 */
.get-verify-btn {
position: absolute;
right: 10rpx;
top: 50%;
transform: translateY(-50%);
width: 200rpx;
height: 70rpx;
line-height: 70rpx;
background-color: #007aff;
color: #fff;
border-radius: 35rpx;
font-size: 26rpx;
padding: 0;
margin: 0;
}
.get-verify-btn:disabled {
background-color: #ccc;
color: #fff;
}
.get-verify-btn::after {
border-radius: 50px;
}
/* 协议勾选项 */
.agreement-item {
display: flex;
align-items: center;
margin: 70rpx 0 28rpx 0;
padding-left: 10rpx;
}
.agreement-text {
font-size: 24rpx;
color: #666;
margin-left: 10rpx;
}
.agreePrivacy {
color: #2F77FD;
}
/* 立即注册按钮 */
.register-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #007aff;
color: #fff;
border-radius: 8rpx;
font-size: 28rpx;
margin-bottom: 40rpx;
}
button:after {
border-radius: 8px;
}
.register-btn:disabled {
background-color: #99c8ff;
color: #fff;
}
/* 跳转登录链接 */
.login-link {
text-align: center;
}
.link-item {
font-size: 28rpx;
color: #007aff;
text-decoration: none;
}
</style>