vue3+vite... nodejs+experss... (三)登录和注册逻辑

181 阅读3分钟

构建简单的登录页

新建src/api/login.ts

import request from '@/utils/request'

export default {
  /**
   * 登录
   * @param data {username: string; password: string }
   * @returns
   */
  login: (data: { username: string; password: string }) => request({ url: '/user/login', method: 'post', data }),
}

login页面

<template>
  <div class="login-form">
    <el-form :model="form" ref="loginFormRef" :rules="rules">
      <el-form-item label="" prop="username">
        <el-input v-model="form.username" placeholder="请输入用户名"> </el-input>
      </el-form-item>
      <el-form-item label="" prop="password">
        <el-input type="password" v-model="form.password" placeholder="请输入密码"> </el-input>
      </el-form-item>
      <el-form-item>
        <el-button class="login-form--submit" type="primary" size="large" @click="submitForm" :loading="loading">登录</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import api from '@/api/login'
const form = ref<{
  username: string
  password: string
}>({ username: '', password: '' })
const loading = ref<boolean>(false)
const rules = {
  username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
  password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
}
const loginFormRef = ref(null as any)
const submitForm = async () => {
  const res = await loginFormRef.value.validate()
  if (!res) return
  try {
    loading.value = true
    const res = await api.login({ ...form.value })
    console.log('🚀 ~ submitForm ~ res:', res)
  } catch (error) {
  } finally {
    loading.value = false
  }
}
</script>

<style lang="scss" scoped>
.login-form {
  width: 300px;
  &--submit {
    width: 100%;
  }
}
</style>

Snipaste_2024-03-12_10-30-36.png

这个时候时没有数据 需要我们新增数据

注册功能的实现

注册接口需要用到短信或者邮箱发送短信的功能 这里由于我比较穷就使用邮箱服务就行 这只是一个简陋的注册 只是实现功能需求

在node中

// 安装nodemailer
pnpm i nodemailer

const nodemailer = require('nodemailer') //引入模块
// 获取邮箱验证码
let transporter = nodemailer.createTransport({
  service: 'qq', // 类型qq邮箱
  port: 465,
  secure: true,
  auth: { user: 'xxxx@qq.com', pass: 'xxxxxxx' }
})
function sendMail(email, code, call) {
  // 发送的配置项
  let mailOptions = {
    from: 'xxxx@qq.com', // 发送者
    to: email, // 接受者,可以同时发送多个,以逗号隔开
    subject: 'vue3admin', // 标题
    html: `<h2>欢迎登录:本次的验证码是${code}</h2>`
  }
  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      call(false)
    } else {
      call(true) //因为是异步 所有需要回调函数通知成功结果
    }
  })
}
// 根据邮箱获取code
// 设置一个定时器 定时清除check和邮箱的对应关系
const check = {}
router.get('/code', function (req, res, next) {
  // 获取邮箱
  if (!req.query.email) return res.send({ code: 400, message: '缺少必填参数' })
  let email = req.query.email
  let code = Math.random().toString().slice(2, 6)
  check[email] = code // 邮箱和验证码对应关系
  sendMail(email, code, function (result) {
    if (result) {
      // 获取邮箱成功 开启定时器
      setTimeout(() => {
        delete check[email]
        console.log(`成功清除${email}code的对应关系`)
      }, 60000)
      res.send({ code: 200, message: '验证码发送成功' })
    } else {
      res.send({ code: 400, message: '验证码发送失败' })
    }
  })
})
// 注册用户
router.post('/register', async function (req, res, next) {
  // 必填账号密码邮箱
  const { username, password, email, code } = req.body
  if (!username || !password || !email || !code) return res.send({ code: 400, message: '缺少必填参数' })
  try {
    // 判断用户是否存在
    const user = await User.findOne({ username })
    if (user) return res.send({ code: 400, message: '该账号已存在' })
    // 判断邮箱验证码是否正确 失效时间1分钟
    if (check[email] !== code) return res.send({ code: 400, message: '验证码错误' })
    await User.create({ ...req.body, id: $generateUUID() }) // 创建新用户
    res.send({ code: 200, message: '注册成功' })
  } catch (error) {
    res.send({ code: 500, message: error })
  }
})

在vue3中

注册对应的路由和页面 /register

  /**
   * 注册
   * @param data {username: string; password: string; code: string; email: string }
   * @returns
   */
  register: (data: { username: string; password: string; code: string; email: string }) => request({ url: '/user/register', method: 'post', data }),

/login/register.vue

<template>
  <el-form :model="form" ref="registerFormRef" :rules="rules" class="register-form">
    <el-form-item label="" prop="username">
      <el-input v-model="form.username" placeholder="请输入用户名"> </el-input>
    </el-form-item>
    <el-form-item label="" prop="password">
      <el-input type="password" v-model="form.password" placeholder="请输入密码" autocomplete="new-password" show-password> </el-input>
    </el-form-item>
    <!-- 邮箱 -->
    <el-form-item label="" prop="email">
      <el-input v-model="form.email" placeholder="请输入邮箱"> </el-input>
    </el-form-item>
    <!-- 获取邮箱code 点击发送 -->
    <el-form-item label="" prop="code">
      <div class="sendEmailCode">
        <el-input v-model="form.code" placeholder="请输入邮箱验证码"> </el-input>
        <el-button type="primary" @click="sendEmailCode" :disabled="!disabled">{{ isCountDown ? '发送验证码' : `请等待${count}秒重试` }}</el-button>
      </div>
    </el-form-item>
    <el-form-item label="" prop="emailCode">
      <!-- 注册 -->
      <el-button type="primary" @click="register" size="large" style="width: 100%">注册</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import api from '@/api/login'

const form = ref({ username: '', password: '', email: '', code: '' })
const rules = {
  username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
  password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
  email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
  code: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
}
const isEmail = (str: string) => {
  const emailRegular = new RegExp(/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$/)
  return emailRegular.test(str)
}
// 判断邮箱是否为空且格式正确
const disabled = computed(() => {
  return form.value.email && isEmail(form.value.email) && isCountDown.value
})
// 显示倒数计时
const isCountDown = ref(true)
const count = ref(60)
const sendEmailCode = async () => {
  isCountDown.value = false
  countDown()
  // 发送邮箱验证码
  await api.getEmailCode({ email: form.value.email })
}
// 倒计时函数
const countDown = () => {
  if (count.value > 0) {
    count.value--
    setTimeout(() => {
      countDown()
    }, 1000)
  } else {
    isCountDown.value = true
    count.value = 60
  }
}
// 注册
const registerFormRef = ref(null as any)

const register = async () => {
  const res = await registerFormRef.value.validate()
  if (!res) return
  try {
    await api.register(form.value)
  } catch (error) {}
}
</script>
<style lang="scss" scoped>
.sendEmailCode {
  display: flex;
  justify-content: space-between;
  gap: 10px;
}
</style>

这样就可以成功的注册了 我们去登录就可以看

Snipaste_2024-03-12_11-18-03.png

完善登录页面 登录成功后我们需要保存token到pinia中 在后面的登录中带上token请求

store/modules/main.ts

import { defineStore } from 'pinia'
import api from '@/api/login'

export const useMainStore = defineStore('main', {
  state: () => {
    return {
      token: '',
      userInfo: {},
    }
  },
  getters: {
    tokenGetter: state => state.token,
  },
  actions: {
    // 设置token
    async setToken(data: any) {
      if (!data.token) return
      this.token = data.token
      const res = await api.getUserDetail(data.id)
      this.userInfo = res.data || {}
    },
  },
  persist: true`,`
})

store/index.ts

这样方便统一导出

import { createPinia } from 'pinia'
const pinia = createPinia()
// pinia-plugin-persistedstate 插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
pinia.use(piniaPluginPersistedstate)

export { useMainStore } from './modules/main.ts'

export default pinia
// 登录页
import { useMainStore } from '@/store'
mainStore.setToken(res.data)

// aioxs封装
// 在请求发送之前可以做一些处理,比如添加请求头等
const storeMain = useMainStore()
config.headers!.token = storeMain.tokenGetter

这样的话完整的登录和注册就可以实现

最后

欢迎 点赞 收藏 有问题欢迎留言评论!!