硅谷甄选(三)登录注册

今天跑了步很舒服

一.登录模块

1.1登录路由静态组件

src\views\login\index.vue

<template>
  <div class="login_container">
    <el-row>
      <el-col :span="12" :xs="0"></el-col>
      <el-col :span="12" :xs="24">
        <el-form class="login_form">
          <h1>Hello</h1>
          <h2>欢迎来到硅谷甄选</h2>
          <el-form-item>
            <el-input
              :prefix-icon="User"
              v-model="loginForm.username"
              ></el-input>
          </el-form-item>
          <el-form-item>
            <el-input
              type="password"
              :prefix-icon="Lock"
              v-model="loginForm.password"
              show-password
              ></el-input>
          </el-form-item>
          <el-form-item>
            <el-button class="login_btn" type="primary" size="default">
              登录
            </el-button>
          </el-form-item>
        </el-form>
      </el-col>
    </el-row>
  </div>
</template>
 
<script setup lang="ts">
  import { User, Lock } from '@element-plus/icons-vue'
  import { reactive } from 'vue'
  //收集账号与密码数据
  let loginForm = reactive({ username: 'admin', password: '111111' })
</script>
 
<style lang="scss" scoped>
  .login_container {
    width: 100%;
    height: 100vh;
    background: url('@/assets/images/background.jpg') no-repeat;
    background-size: cover;
    .login_form {
      position: relative;
      width: 80%;
      top: 30vh;
      background: url('@/assets/images/login_form.png') no-repeat;
      background-size: cover;
      padding: 40px;
      h1 {
        color: white;
        font-size: 40px;
      }
      h2 {
        color: white;
        font-size: 20px;
        margin: 20px 0px;
      }
      .login_btn {
        width: 100%;
      }
    }
  }
</style>

注意

el-col是24份的,在此左右分为了12份。我们在右边放置我们的结构。

:xs="0"是为了响应式。

el-form下的element-plus元素都用el-form-item包裹起来。

通过 row (行)和 col (列)组件,并通过 col 组件的 span 属性我们就可以自由地组合布局。

row 行提供 gutter 属性来指定列之间的间距,其默认值为0。

通过制定 col 组件的 offset 属性可以指定分栏偏移的栏数。

参照了 Bootstrap 的 响应式设计,预设了五个响应尺寸:xs、sm、md、lg 和 xl。

我自己改写的静态页面

<template>
  <!-- Element Plus 表单组件 -->
  <el-form>
    <!-- 标题部分,使用条件渲染显示不同文本 -->
    <h2>{{ isLogin ? '欢迎来到硅谷甄选' : '注册新账户' }}</h2>

    <!-- 用户名输入框 -->
    <el-form-item label="用户名">
      <el-input 
        v-model="form.username" 
        :prefix-icon="User"
      ></el-input>
    </el-form-item>

    <!-- 密码输入框 -->
    <el-form-item label="密码">
      <el-input 
        v-model="form.password" 
        :type="passwordVisible ? 'text' : 'password'"
        :prefix-icon="Lock"
      >
        <!-- 使用具名插槽自定义后缀图标(显示/隐藏密码的小眼睛) -->
        <template #suffix>
          <el-icon 
            class="cursor-pointer" 
            @click="togglePasswordVisibility"
          >
            <!-- 动态组件,根据passwordVisible切换显示不同图标 -->
            <component :is="passwordVisible ? View : Hide" />
          </el-icon>
        </template>
      </el-input>
    </el-form-item>

    <!-- 确认密码输入框,仅在注册时显示 -->
    <el-form-item v-if="!isLogin" label="确认密码">
      <el-input 
        v-model="form.confirmPassword" 
        :type="passwordVisible ? 'text' : 'password'"
        :prefix-icon="Lock"
      >
        <template #suffix>
          <el-icon class="cursor-pointer" @click="togglePasswordVisibility">
            <component :is="passwordVisible ? View : Hide" />
          </el-icon>
        </template>
      </el-input>
    </el-form-item>

    <!-- 按钮区域 -->
    <el-form-item>
      <!-- 登录/注册按钮 -->
      <el-button type="primary" @click="submitForm">
        {{ isLogin ? '登录' : '注册' }}
      </el-button>
      <!-- 默认账号按钮 -->
      <el-button type="text" @click="useDefaultAccount">
        使用默认账号
      </el-button>
    </el-form-item>

    <!-- 切换登录/注册的按钮 -->
    <el-form-item>
      <el-button type="text" @click="toggleLoginRegister">
        {{ isLogin ? '没有账户?立即注册' : '已有账户?立即登录' }}
      </el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
// 导入Element Plus的图标组件
import { User, Lock, View, Hide } from '@element-plus/icons-vue'
// 导入Vue的响应式API
import { reactive, ref } from 'vue'
// 导入用户状态管理store
import useUserStore from '@/store/modules/user'
// 导入路由实例
import { useRouter } from 'vue-router'
// 导入Element Plus的消息提示组件
import { ElMessage } from 'element-plus'

// 创建store和router实例
const userStore = useUserStore()
const router = useRouter()

// 控制当前是登录还是注册状态
const isLogin = ref(true)
// 控制密码是否可见
const passwordVisible = ref(false)

// 表单数据
const form = reactive({
  username: '',
  password: '',
  confirmPassword: ''
})

// 切换密码显示/隐藏
const togglePasswordVisibility = () => {
  passwordVisible.value = !passwordVisible.value
}

// 切换登录/注册状态
const toggleLoginRegister = () => {
  isLogin.value = !isLogin.value
  // 切换时清空表单
  form.username = ''
  form.password = ''
  form.confirmPassword = ''
}

// 使用默认账号
const useDefaultAccount = () => {
  form.username = '123'
  form.password = '111111'
}

// 提交表单
const submitForm = async () => {
  if (isLogin.value) {
    try {
      const result = await userStore.userLogin(form.username, form.password)
      if (result === 'ok') {
        ElMessage.success('登录成功')
        await router.push('/home')
      }
    } catch (err: any) {
      console.error(err)
      ElMessage.error(err.message || '登录失败')
    }
  } else {
    if (form.password !== form.confirmPassword) {
      ElMessage.error('两次输入的密码不一致')
      return
    }
    console.log('注册', form.username, form.password)
  }
}
</script>

<style scoped lang="scss">
/* 标题样式 */
h2 {
  text-align: center;
  margin-bottom: 20px;
}

/* 鼠标悬停时显示手型指针 */
.cursor-pointer {
  cursor: pointer;
}

/* 按钮间距 */
.el-button {
  margin-right: 10px;
}
</style>

1.2登陆业务实现

登录按钮绑定回调
回调应该做的事情
const login =  () => {
  //点击登录按钮以后干什么
  //通知仓库发起请求
  //请求成功->路由跳转
  //请求失败->弹出登陆失败信息
} 
仓库store初始化

大仓库

安装pinia:pnpm i pinia@2.0.34

//创建用户相关的小仓库
import { defineStore } from 'pinia'
//创建用户小仓库
const useUserStore = defineStore('User', {
  //小仓库存储数据地方
  state: () => {},
  //处理异步|逻辑地方
  actions: {},
  getters: {},
})
//对外暴露小仓库
export default useUserStore
按钮回调
//登录按钮的回调
const login = async () => {
  //按钮加载效果
  loading.value = true
  //点击登录按钮以后干什么
  //通知仓库发起请求
  //请求成功->路由跳转
  //请求失败->弹出登陆失败信息
  try {
    //也可以书写.then语法
    await useStore.userLogin(loginForm)
    //编程式导航跳转到展示数据的首页
    $router.push('/')
    //登录成功的提示信息
    ElNotification({
      type: 'success',
      message: '登录成功!',
    })
    //登录成功,加载效果也消失
    loading.value = false
  } catch (error) {
    //登陆失败加载效果消失
    loading.value = false
    //登录失败的提示信息
    ElNotification({
      type: 'error',
      message: (error as Error).message,
    })
  }
}
用户仓库
//创建用户相关的小仓库
import {defineStore} from "pinia";
// 引入接口
import {reqLogin} from "../../api/user";
//引入数据类型
import type {loginForm} from '@/api/user/type'
//创建用户小仓库
const useUserStore = defineStore('User', {
小仓库存储数据地方
state: () => {
return {
token: localStorage.getItem('TOKEN')//用户的唯一标识
}
},
//处理异步|逻辑地方
actions: {
async userLogin(data: loginForm) {
//登陆的请求
async
userLogin(data
:
loginForm
)
{
//登陆的请求
const result: any = await reqLogin(data)
if (result.code == 200) {
this.token = result.data.token
localStorage.setItem('TOKEN', result.data.token)
return 'ok'
} else {
return Promise.reject(new Error(result.data.message))
}

}
}
},
getters: {},
})
export default useUserStore

小结

  1. Element-plus中ElNotification用法(弹窗): 引入:import { ElNotification } from 'element-plus'使用:

    //登录失败的提示信息

    ElNotification({

    type: 'error',

    message: (error as Error).message,

    })

    Element-plus中el-button的loading属性。

    pinia使用actions、state的方式和vuex不同:需要引入函数和创建实例

    $router的使用:也需要引入函数和创建实例

    在actions中使用state的token数据:this.token

    类型定义需要注意。

    promise的使用和vue2现在看来是一样的

模板封装登陆业务

result返回类型封装

 src\api\user\type.ts

interface dataType {
  token?: string
  message?: string
}

//登录接口返回的数据类型
export interface loginResponseData {
  code: number
  data: dataType
}

State仓库类型封装

src\store\modules\types\type.ts

 //定义小仓库数据state类型
export interface UserState {
  token: string | null
}
本地存储封装
 src\utils\token.ts

//封装本地存储存储数据与读取数据方法
export const SET_TOKEN = (token: string) => {
  localStorage.setItem('TOKEN', token)
}

export const GET_TOKEN = () => {
  return localStorage.getItem('TOKEN')
}
登录时间的判断
  1. 封装函数 src\utils\time.ts

    //封装函数:获取当前时间段
    export const getTime = () => {
      let message = ''
      //通过内置构造函数Date
      const hour = new Date().getHours()
      if (hour <= 9) {
        message = '早上'
      } else if (hour <= 14) {
        message = '上午'
      } else if (hour <= 18) {
        message = '下午'
      } else {
        message = '晚上'
      }
      return message
    }
    

    表单校验规则

    表单校验

    表单绑定项

  2. 表单元素绑定项

  3. Form 组件提供了表单验证的功能,只需为 rules 属性传入约定的验证规则,并将 form-Item 的 prop 属性设置为需要验证的特殊键值即可

  4. 使用规则rules

    //定义表单校验需要的配置对象
    const rules = {
      username: [
        //规则对象属性:
        {
          required: true, // required,代表这个字段务必要校验的
          min: 5, //min:文本长度至少多少位
          max: 10, // max:文本长度最多多少位
          message: '长度应为6-10位', // message:错误的提示信息
          trigger: 'change', //trigger:触发校验表单的时机 change->文本发生变化触发校验, blur:失去焦点的时候触发校验规则
        }, 
        
      ],
      password: [
       {
          required: true,
          min: 6,
          max: 10,
          message: '长度应为6-15位',
          trigger: 'change',
        }, 
      ],
    }
    
  5. 校验规则通过后运行

    const login = async () => {
      //保证全部表单项校验通过
      await loginForms.value.validate()
        。。。。。。
    }
    
    自定义表单校验
  6. 修改使用规则rules使用自己编写的函数作为规则校验。

    //定义表单校验需要的配置对象
    const rules = {
    username: [
    //规则对象属性:
    /* {
    required: true, // required,代表这个字段务必要校验的
    min: 5, //min:文本长度至少多少位
    max: 10, // max:文本长度最多多少位
    message: '长度应为6-10位', // message:错误的提示信息
    trigger: 'change', //trigger:触发校验表单的时机 change->文本发生变化触发校验, blur:失去焦点的时候触发校验规则
    }, */
    { trigger: 'change', validator: validatorUserName },
    ],
    password: [
    { trigger: 'change', validator: validatorPassword },
    ],
    }

  7. 自定义校验规则函数

    //自定义校验规则函数
    const validatorUserName = (rule: any, value: any, callback: any) => {
      //rule:校验规则对象
      //value:表单元素文本内容
      //callback:符合条件,callback放行通过,不符合:注入错误提示信息
      if (value.length >= 5) {
        callback()
      } else {
        callback(new Error('账号长度至少5位'))
      }
    }
    
    const validatorPassword = (rule: any, value: any, callback: any) => {
      if (value.length >= 6) {
        callback()
      } else {
        callback(new Error('密码长度至少6位'))
      }
    }
    

    再来总体讲一下(以下是用我自己改写的代码)

    要学习这个登录页面
    建议您按以下顺序学习这些关键文件:

  8. 基础页面结构 src/view/login/index.vue // 登录页面主文件

    src/components/LoginForm.vue // 登录表单组件

    这两个文件包含了:

  9. Vue3 组件的基本结构

  10. Element Plus 组件的使用
    组件拆分和组织方式

  11. 路由配置 src/router/index.ts // 路由配置文件

    src/App.vue // 根组件,包含路由出口

    学习:

  12. 路由的基本配置

  13. 页面跳转的实现

  14. 路由出口的使用

  15. 状态管理 src/store/index.ts // Pinia 主配置

    src/store/modules/user.ts // 用户状态管理模块

    学习:

  16. Pinia 的基本使用

  17. 状态管理的概念

  18. 数据持久化(如 token 的存储)

    1. API 请求

    src/api/user/index.ts // 用户相关 API 接口定义

    src/utils/request.ts // Axios 请求封装

    学习:

  19. API 接口的组织方式

  20. Axios 的基本使用

  21. 请求拦截器和响应拦截器

  22. 配置文件 vite.config.ts // Vite 配置文件

    tsconfig.json // TypeScript 配置

    学习:

  23. 项目基本配置

  24. 路径别名配置

  25. TypeScript 配置

  26. TypeScript 类型声明的基础

    1. 类型声明

    shims-vue.d.ts // Vue 文件的类型声明

    先从 login/index.vue 和 LoginForm.vue 开始,理解基本的页面结构和组件通信

  • 然后学习路由配置,理解页面跳转的实现
  // 1. 引入必要的函数和类型
  import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

  // 2. 定义路由配置
  const routes: Array<RouteRecordRaw> = [
    {
      path: '/',           // 访问路径
      redirect: '/home'    // 重定向到首页
    },
    {
      path: '/login',      // 登录页路径
      name: 'Login',       // 路由名称
      component: Login     // 对应的组件
    }
  ]

  // 3. 创建路由实例
  const router = createRouter({
    history: createWebHistory(),  // 使用HTML5历史模式
    routes
  })
  • 接着学习状态管理,理解数据共享的方式
    Pinia用于管理全局状态。我们以用户状态为例:

    // 1. 创建store
    import { defineStore } from 'pinia'
    
    export const useUserStore = defineStore('user', {
      // 状态(数据)
      state: () => ({
        token: localStorage.getItem('token') || '',
        username: '',
      }),
    
      // 动作(异步操作)
      actions: {
        async userLogin(username: string, password: string) {
          // 处理登录逻辑
          if (username === '123' && password === '111111') {
            this.token = 'default_token'
            this.username = username
            localStorage.setItem('token', this.token)
            return 'ok'
          }
        }
      }
    })
    

    重要概念解释

    路由相关:

  • path: 访问路径

  • component: 对应的组件

  • redirect: 重定向

  • router.push(): 编程式导航

  • <router-view>: 路由出口,显示当前路由对应的组件

  • state: 存储数据的地方

  • actions: 处理业务逻辑,特别是异步操作

  • store: 状态管理的容器

  • defineStore: 创建store的函数

  • Pinia相关:

  • 最后了解 API 请求和项目配置
相关推荐
顾辰呀15 分钟前
css 文字一行没有放满不进行换行
前端·javascript·css·vue.js·css3
q5673152317 分钟前
Python 中的字符串匹配算法
android·java·javascript·python·算法
nixiaoge41 分钟前
Web前端第二次作业
前端·javascript·css3
安冬的码畜日常1 小时前
【玩转 Postman 接口测试与开发2_005】第六章:Postman 测试脚本的创建(上)
javascript·测试工具·单元测试·postman·bdd·chai
Gavin_9151 小时前
【JavaScript】数组-集合-Map-对象-Class用法一览
开发语言·前端·javascript
毕业设计制作和分享1 小时前
ssm公交车信息管理系统+vue
java·vue.js·spring boot·毕业设计·mybatis
琴~~2 小时前
前端根据后端返回的文本流逐个展示文本内容
前端·javascript·vue
zhaocarbon2 小时前
el-scrollbar 动态更新内容 鼠标滚轮无效
前端·javascript·vue.js
一纸忘忧3 小时前
Nuxt 3.14 发布!全新功能与性能提升
前端·javascript·vue.js
少年姜太公3 小时前
【ES6】让你彻底搞懂const ,let和var的区别
javascript