鸿蒙OSUniApp 实现精美的用户登录和注册页面#三方框架 #Uniapp
UniApp 实现精美的用户登录和注册页面
前言
在开发鸿蒙APP时,登录注册页面作为用户与应用交互的第一道门槛,其体验与质量往往决定了用户的第一印象。去年我接手了一个电商小程序项目,产品经理特别强调要做一个既美观又实用的登录注册页面。经过一番摸索和实践,我用UniApp实现了一套完整的登录注册流程,今天就把这个过程分享给大家。
设计思路
一个好的登录注册页面应该具备以下特点:
- 简洁美观 - 视觉上吸引用户
- 交互流畅 - 包括动画过渡、表单验证反馈等
- 功能完整 - 常规登录、手机验证码登录、第三方登录等
- 兼容适配 - 适应不同设备尺寸
基于这些考虑,我采用了卡片式设计、渐变色背景,并加入适当的动效,让整个页面既现代又不失实用性。
页面结构
我们的登录注册系统包含三个主要页面:
- 登录页面 - 支持账号密码登录和手机验证码登录
- 注册页面 - 新用户注册表单
- 忘记密码页面 - 用于密码找回
下面我们逐一实现这些页面。
登录页面实现
首先来看登录页面的结构代码:
<template><view class="login-container"><!-- 背景 --><view class="bg-wrapper"><image class="bg-image" src="/static/images/login-bg.jpg" mode="aspectFill"></image><view class="bg-mask"></view></view><!-- 顶部Logo --><view class="logo-box"><image class="logo" src="/static/images/logo.png" mode="widthFix"></image><text class="slogan">探索移动应用的无限可能</text></view><!-- 登录表单 --><view class="login-form"><view class="tab-header"><view class="tab-item" :class="{ active: loginType === 'password' }"@tap="switchLoginType('password')">账号登录</view><view class="tab-item" :class="{ active: loginType === 'sms' }"@tap="switchLoginType('sms')">短信登录</view><view class="tab-line" :style="tabLineStyle"></view></view><!-- 账号密码登录 --><view v-if="loginType === 'password'" class="form-content"><view class="input-item"><uni-icons type="person" size="20" color="#999"></uni-icons><input class="input" type="text" placeholder="请输入账号/手机号" v-model="account"/></view><view class="input-item"><uni-icons type="locked" size="20" color="#999"></uni-icons><input class="input" :type="showPassword ? 'text' : 'password'" placeholder="请输入密码" v-model="password"/><view class="eye-icon" @tap="togglePasswordVisibility"><uni-icons :type="showPassword ? 'eye' : 'eye-slash'" size="20" color="#999"></uni-icons></view></view></view><!-- 手机验证码登录 --><view v-else class="form-content"><view class="input-item"><uni-icons type="phone" size="20" color="#999"></uni-icons><input class="input" type="number" maxlength="11"placeholder="请输入手机号" v-model="phone"/></view><view class="input-item"><uni-icons type="chat" size="20" color="#999"></uni-icons><input class="input" type="number" maxlength="6"placeholder="请输入验证码" v-model="smsCode"/><view class="code-btn" :class="{ disabled: counting }"@tap="sendSmsCode">{{ codeText }}</view></view></view><!-- 登录按钮 --><view class="action-area"><button class="login-btn" :disabled="!isFormValid" :class="{ disabled: !isFormValid }"@tap="handleLogin">登录</button><view class="additional-links"><navigator url="/pages/auth/register" class="link-item">注册账号</navigator><navigator url="/pages/auth/forgot-password" class="link-item">忘记密码</navigator></view></view><!-- 第三方登录 --><view class="third-party-login"><view class="divider"><view class="line"></view><text class="text">其他登录方式</text><view class="line"></view></view><view class="icons-row"><view class="icon-item" @tap="thirdPartyLogin('wechat')"><image src="/static/images/wechat.png" mode="widthFix"></image></view><view class="icon-item" @tap="thirdPartyLogin('apple')"><image src="/static/images/apple.png" mode="widthFix"></image></view></view></view></view><!-- 底部隐私政策 --><view class="privacy-policy">登录即表示您同意<text class="policy-link" @tap="openPrivacyPolicy">《用户协议》</text>和<text class="policy-link" @tap="openPrivacyPolicy">《隐私政策》</text></view></view>
</template>
上面的模板看起来代码挺多,但结构非常清晰。接下来实现逻辑部分:
<script>
export default {data() {return {loginType: 'password', // 登录类型:password-密码登录,sms-短信登录account: '',password: '',phone: '',smsCode: '',showPassword: false,counting: false, // 是否正在倒计时countDown: 60, // 倒计时秒数codeText: '获取验证码'};},computed: {tabLineStyle() {return {transform: this.loginType === 'password' ? 'translateX(0)' : 'translateX(100%)'};},isFormValid() {if (this.loginType === 'password') {return this.account.trim() && this.password.trim();} else {return this.phone.trim().length === 11 && this.smsCode.trim().length === 6;}}},methods: {// 切换登录方式switchLoginType(type) {this.loginType = type;},// 切换密码可见性togglePasswordVisibility() {this.showPassword = !this.showPassword;},// 发送短信验证码sendSmsCode() {if (this.counting) return;// 验证手机号if (!this.validatePhone()) {return;}// 开始倒计时this.counting = true;this.countDown = 60;this.codeText = `${this.countDown}秒后重发`;const timer = setInterval(() => {this.countDown--;this.codeText = `${this.countDown}秒后重发`;if (this.countDown <= 0) {clearInterval(timer);this.counting = false;this.codeText = '获取验证码';}}, 1000);// 发送验证码请求this.requestSmsCode();},// 验证手机号validatePhone() {if (!/^1[3-9]\d{9}$/.test(this.phone)) {uni.showToast({title: '请输入正确的手机号',icon: 'none'});return false;}return true;},// 请求发送验证码requestSmsCode() {// 实际项目中这里应该调用API发送验证码uni.showLoading({ title: '发送中...' });setTimeout(() => {uni.hideLoading();uni.showToast({title: '验证码已发送',icon: 'success'});}, 1500);},// 处理登录handleLogin() {if (!this.isFormValid) return;uni.showLoading({ title: '登录中...' });// 登录验证逻辑setTimeout(() => {uni.hideLoading();// 模拟登录成功uni.setStorageSync('token', 'demo_token_' + Date.now());uni.setStorageSync('userInfo', {id: 1,nickname: '测试用户',avatar: '/static/images/avatar.png'});uni.showToast({title: '登录成功',icon: 'success',duration: 1500,success: () => {// 延迟跳转,让用户看到成功提示setTimeout(() => {uni.switchTab({url: '/pages/home/home'});}, 1500);}});}, 2000);},// 第三方登录thirdPartyLogin(type) {uni.showToast({title: `正在尝试${type === 'wechat' ? '微信' : '苹果'}登录`,icon: 'none'});// 这里应该调用对应的第三方登录API// 微信登录示例if (type === 'wechat' && uni.getSystemInfoSync().platform !== 'devtools') {uni.login({provider: 'weixin',success: (loginRes) => {console.log('微信登录成功', loginRes);// 获取用户信息uni.getUserInfo({provider: 'weixin',success: (infoRes) => {console.log('获取用户信息成功', infoRes);// 将用户信息传给后端进行登录验证// this.wxLoginToServer(loginRes.code, infoRes.userInfo);}});},fail: (err) => {console.error('微信登录失败', err);}});}},// 打开隐私政策openPrivacyPolicy() {uni.navigateTo({url: '/pages/common/privacy-policy'});}}
};
</script>
最后是样式部分,一个漂亮的UI很大程度上取决于CSS:
<style lang="scss">
.login-container {position: relative;width: 100%;min-height: 100vh;display: flex;flex-direction: column;align-items: center;overflow: hidden;
}// 背景样式
.bg-wrapper {position: absolute;top: 0;left: 0;width: 100%;height: 100%;z-index: -1;.bg-image {width: 100%;height: 100%;}.bg-mask {position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: linear-gradient(to bottom, rgba(29, 36, 52, 0.5), rgba(29, 36, 52, 0.8));}
}// Logo区域
.logo-box {margin-top: 80rpx;display: flex;flex-direction: column;align-items: center;.logo {width: 180rpx;margin-bottom: 20rpx;}.slogan {font-size: 28rpx;color: #ffffff;text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);}
}// 登录表单
.login-form {width: 85%;margin-top: 80rpx;background-color: rgba(255, 255, 255, 0.95);border-radius: 16rpx;padding: 40rpx 30rpx;box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.15);// 标签头部.tab-header {display: flex;position: relative;border-bottom: 1px solid #f2f2f2;margin-bottom: 50rpx;.tab-item {flex: 1;text-align: center;font-size: 32rpx;color: #666;padding-bottom: 20rpx;transition: all 0.3s;&.active {color: #3b7aff;font-weight: 500;}}.tab-line {position: absolute;bottom: 0;left: 0;width: 50%;height: 4rpx;background-color: #3b7aff;border-radius: 4rpx;transition: all 0.3s ease;}}// 表单内容.form-content {margin-bottom: 50rpx;.input-item {display: flex;align-items: center;height: 100rpx;border-bottom: 1rpx solid #eee;margin-bottom: 30rpx;.input {flex: 1;height: 100%;font-size: 30rpx;padding-left: 20rpx;}.eye-icon {padding: 0 10rpx;}.code-btn {width: 200rpx;height: 70rpx;line-height: 70rpx;background: #3b7aff;color: #fff;font-size: 26rpx;text-align: center;border-radius: 35rpx;&.disabled {background: #cccccc;}}}}// 操作区域.action-area {.login-btn {width: 100%;height: 90rpx;line-height: 90rpx;background: linear-gradient(135deg, #4b8eff, #3b7aff);color: #fff;font-size: 32rpx;border-radius: 45rpx;margin-bottom: 30rpx;&.disabled {background: linear-gradient(135deg, #cccccc, #aaaaaa);}}.additional-links {display: flex;justify-content: space-between;font-size: 26rpx;color: #666;margin-bottom: 40rpx;.link-item {padding: 10rpx;}}}// 第三方登录.third-party-login {margin-top: 30rpx;.divider {display: flex;align-items: center;margin-bottom: 40rpx;.line {flex: 1;height: 1rpx;background-color: #eee;}.text {padding: 0 20rpx;font-size: 26rpx;color: #999;}}.icons-row {display: flex;justify-content: center;.icon-item {width: 80rpx;height: 80rpx;margin: 0 40rpx;image {width: 100%;height: 100%;}}}}
}// 底部隐私政策
.privacy-policy {position: absolute;bottom: 40rpx;font-size: 24rpx;color: rgba(255, 255, 255, 0.8);text-align: center;.policy-link {color: #4b8eff;}
}
</style>
注册页面实现
注册页面与登录页面类似,但表单内容有所不同。这里我们简化一下代码,只展示关键部分:
<template><view class="register-container"><!-- 背景和顶部与登录页类似 --><view class="register-form"><view class="form-header"><text class="title">新用户注册</text><text class="subtitle">加入我们,体验更多精彩</text></view><view class="form-content"><view class="input-item"><uni-icons type="phone" size="20" color="#999"></uni-icons><input class="input" type="number" maxlength="11"placeholder="请输入手机号" v-model="form.phone"/></view><view class="input-item"><uni-icons type="chat" size="20" color="#999"></uni-icons><input class="input" type="number" maxlength="6"placeholder="请输入验证码" v-model="form.smsCode"/><view class="code-btn" :class="{ disabled: counting }"@tap="sendSmsCode">{{ codeText }}</view></view><view class="input-item"><uni-icons type="locked" size="20" color="#999"></uni-icons><input class="input" :type="showPassword ? 'text' : 'password'" placeholder="请设置6-20位密码" v-model="form.password"/><view class="eye-icon" @tap="togglePasswordVisibility"><uni-icons :type="showPassword ? 'eye' : 'eye-slash'" size="20" color="#999"></uni-icons></view></view><view class="input-item"><uni-icons type="locked" size="20" color="#999"></uni-icons><input class="input" :type="showPassword ? 'text' : 'password'" placeholder="请确认密码" v-model="form.confirmPassword"/></view></view><view class="action-area"><button class="register-btn" :disabled="!isFormValid" :class="{ disabled: !isFormValid }"@tap="handleRegister">注册</button><view class="login-link">已有账号?<text class="link" @tap="goToLogin">去登录</text></view></view></view></view>
</template><script>
export default {data() {return {form: {phone: '',smsCode: '',password: '',confirmPassword: ''},showPassword: false,counting: false,countDown: 60,codeText: '获取验证码'};},computed: {isFormValid() {return this.form.phone.length === 11 && this.form.smsCode.length === 6 && this.form.password.length >= 6 &&this.form.password === this.form.confirmPassword;}},methods: {// 密码可见性切换togglePasswordVisibility() {this.showPassword = !this.showPassword;},// 发送验证码(与登录页类似)sendSmsCode() {// 实现与登录页类似},// 处理注册handleRegister() {if (!this.isFormValid) return;// 表单验证if (this.form.password !== this.form.confirmPassword) {uni.showToast({title: '两次密码不一致',icon: 'none'});return;}uni.showLoading({ title: '注册中...' });// 模拟注册请求setTimeout(() => {uni.hideLoading();uni.showToast({title: '注册成功',icon: 'success',duration: 1500,success: () => {// 延迟跳转到登录页setTimeout(() => {uni.navigateTo({url: '/pages/auth/login'});}, 1500);}});}, 2000);},// 跳转到登录页goToLogin() {uni.navigateBack();}}
};
</script>
注册页面的样式可以参考登录页面,只需对一些细节进行调整即可。
实际效果与优化方案
在实际项目中,我们实现的登录注册页面已经投入使用,用户反馈非常好。不过在使用过程中也发现了一些问题和优化点:
1. 动画效果优化
为了让登录体验更流畅,我们加入了一些过渡动画:
// 切换标签时添加动画
switchLoginType(type) {// 添加动画类this.animating = true;setTimeout(() => {this.loginType = type;setTimeout(() => {this.animating = false;}, 300);}, 150);
}
对应的CSS:
.form-content {transition: opacity 0.3s ease;opacity: 1;&.animating {opacity: 0;}
}
2. 表单验证优化
在用户输入过程中实时验证并给出友好提示:
<view class="input-item" :class="{ error: phoneError }"><uni-icons type="phone" size="20" color="#999"></uni-icons><input class="input" type="number" maxlength="11"placeholder="请输入手机号" v-model="phone"@input="validatePhoneInput"/><text v-if="phoneError" class="error-tip">{{ phoneErrorMsg }}</text>
</view>
validatePhoneInput() {if (this.phone && !/^1[3-9]\d{9}$/.test(this.phone)) {this.phoneError = true;this.phoneErrorMsg = '请输入正确的手机号';} else {this.phoneError = false;this.phoneErrorMsg = '';}
}
3. 记住登录状态
用户体验优化,记住登录状态避免频繁登录:
// 登录成功后保存状态
handleLoginSuccess(userInfo, token) {// 保存用户信息和tokenuni.setStorageSync('token', token);uni.setStorageSync('userInfo', userInfo);uni.setStorageSync('loginTime', Date.now());// 如果用户勾选了"记住登录状态"if (this.rememberMe) {uni.setStorageSync('rememberMe', true);} else {uni.removeStorageSync('rememberMe');}
}
在App启动时检查登录状态:
// 在App.vue的onLaunch中
checkLoginStatus() {const token = uni.getStorageSync('token');const rememberMe = uni.getStorageSync('rememberMe');const loginTime = uni.getStorageSync('loginTime');const currentTime = Date.now();// 如果有token且选择了记住登录,或者登录时间在7天内if (token && (rememberMe || (currentTime - loginTime < 7 * 24 * 60 * 60 * 1000))) {// token验证this.verifyToken(token);} else {// 清除登录信息,跳转到登录页this.clearLoginInfo();uni.reLaunch({url: '/pages/auth/login'});}
}
总结与心得
通过这次实践,我总结了几点关于实现好的登录注册页面的经验:
- 设计先行 - 在编码前先做好设计稿,确保视觉效果和交互流程
- 拆分组件 - 将复杂页面拆分为多个可复用组件,便于维护
- 验证健壮 - 表单验证要全面且给出友好提示
- 安全考虑 - 密码加密传输,防止中间人攻击
- 适配兼容 - 适配不同尺寸的设备,兼容不同平台
UniApp提供了很多实用组件和API,极大简化了我们的开发过程。通过合理利用这些功能,我们能够快速实现一个既美观又实用的登录注册系统。
希望这篇文章对你在UniApp中实现登录注册页面有所帮助。如果有任何问题或建议,欢迎在评论区交流!
参考资料
- UniApp官方文档:https://uniapp.dcloud.io/
- uni-ui组件库:https://ext.dcloud.net.cn/plugin?id=55