鸿蒙OS&UniApp 实现精美的用户登录和注册页面#三方框架 #Uniapp

发布于:2025-05-14 ⋅ 阅读:(14) ⋅ 点赞:(0)

UniApp 实现精美的用户登录和注册页面

前言

在开发鸿蒙APP时,登录注册页面作为用户与应用交互的第一道门槛,其体验与质量往往决定了用户的第一印象。去年我接手了一个电商小程序项目,产品经理特别强调要做一个既美观又实用的登录注册页面。经过一番摸索和实践,我用UniApp实现了一套完整的登录注册流程,今天就把这个过程分享给大家。

设计思路

一个好的登录注册页面应该具备以下特点:

  1. 简洁美观 - 视觉上吸引用户
  2. 交互流畅 - 包括动画过渡、表单验证反馈等
  3. 功能完整 - 常规登录、手机验证码登录、第三方登录等
  4. 兼容适配 - 适应不同设备尺寸

基于这些考虑,我采用了卡片式设计、渐变色背景,并加入适当的动效,让整个页面既现代又不失实用性。

页面结构

我们的登录注册系统包含三个主要页面:

  1. 登录页面 - 支持账号密码登录和手机验证码登录
  2. 注册页面 - 新用户注册表单
  3. 忘记密码页面 - 用于密码找回

下面我们逐一实现这些页面。

登录页面实现

首先来看登录页面的结构代码:

<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) {
  // 保存用户信息和token
  uni.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'
    });
  }
}

总结与心得

通过这次实践,我总结了几点关于实现好的登录注册页面的经验:

  1. 设计先行 - 在编码前先做好设计稿,确保视觉效果和交互流程
  2. 拆分组件 - 将复杂页面拆分为多个可复用组件,便于维护
  3. 验证健壮 - 表单验证要全面且给出友好提示
  4. 安全考虑 - 密码加密传输,防止中间人攻击
  5. 适配兼容 - 适配不同尺寸的设备,兼容不同平台

UniApp提供了很多实用组件和API,极大简化了我们的开发过程。通过合理利用这些功能,我们能够快速实现一个既美观又实用的登录注册系统。

希望这篇文章对你在UniApp中实现登录注册页面有所帮助。如果有任何问题或建议,欢迎在评论区交流!

参考资料

  1. UniApp官方文档:https://uniapp.dcloud.io/
  2. uni-ui组件库:https://ext.dcloud.net.cn/plugin?id=55

网站公告

今日签到

点亮在社区的每一天
去签到