人类行为验证处理方案 —— 脱离UI组件库实现登录、注册+表单校验

news2024/10/18 15:37:51

目录

01: 构建登录模块基础UI结构

02: 表单校验实现原理与方案分析

表单校验的实现原理 

自定义表单校验方案分析 

文章中的方案实现

03: 基于 vee-validate 实现普适的表单校验         

04: 什么是人类行为验证?它的目的、实现原理、构建方案分别是什么? 

什么是人类行为验证 

目的 

它的实现原理是什么? 

我们如何在项目中使用它

05: 构建人类行为验证模块

06: 用户登录行为处理 

07: 用户信息获取行为

08: 退出登录操作 

09: token 超时处理 

10: 注册页面基本样式处理 

11: 处理注册行为 

12: 总结


 

01: 构建登录模块基础UI结构

- src/views
- - login-register
- - - components
- - - - header.vue
- - - login
- - - - index.vue
// src/views/login-register/components/header.vue

<template>
  <!-- 头部图标:PC端 -->
  <div class="hidden pt-5 h-8 xl:block">
    <img
      v-lazy
      class="m-auto"
      src="https://res.lgdsunday.club/signlogo.png"
      alt=""
    />
  </div>
  <!-- 头部图标:移动端 -->
  <div class="h-[111px] xl:hidden">
    <img
      v-lazy
      class="dark:hidden"
      src="https://res.lgdsunday.club/login-bg.png"
      alt=""
    />
    <img
      v-lazy
      class="h-5 absolute top-[5%] left-[50%] translate-x-[-50%]"
      src="https://m.imooc.com/static/wap/static/common/img/logo-small@2x.png"
      alt=""
      srcset=""
    />
  </div>
</template>
// src/views/login-register/login/index.vue

<template>
  <div
    class="relative h-screen bg-white dark:bg-zinc-800 text-center xl:bg-zinc-200"
  >
    <!-- 头部图标:PC端 -->
    <header-vue></header-vue>
    <!-- 表单区 -->
    <div
      class="block px-3 mt-4 dark:bg-zinc-800 xl:bg-white xl:w-[388px] xl:dark:bg-zinc-900 xl:m-auto xl:mt-8 xl:py-4 xl:rounded-sm xl:shadow-lg"
    >
      <h3
        class="mb-2 font-semibold text-base text-main dark:text-zinc-300 hidden xl:block"
      >
        账号登录
      </h3>
      <!-- 表单 -->
      <vee-form @submit="onLoginHandler">
        <vee-field
          class="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b-[1px] w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:dark:bg-zinc-900"
          name="username"
          :rules="validateUsername"
          type="text"
          placeholder="用户名"
          autocomplete="on"
          v-model="loginForm.username"
        />
        <vee-error-message
          class="text-sm text-red-600 block mt-0.5 text-left"
          name="username"
        >
        </vee-error-message>
        <vee-field
          class="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b-[1px] w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:dark:bg-zinc-900"
          name="password"
          :rules="validatePassword"
          type="password"
          placeholder="密码"
          autocomplete="on"
          v-model="loginForm.password"
        />
        <vee-error-message
          class="text-sm text-red-600 block mt-0.5 text-left"
          name="password"
        >
        </vee-error-message>

        <div class="pt-1 pb-3 leading-[0px] text-right">
          <a
            class="inline-block p-1 text-zinc-400 text-right dark:text-zinc-600 hover:text-zinc-600 dark:hover:text-zinc-400 text-sm duration-400 cursor-pointer"
            @click="onToRegister"
          >
            去注册
          </a>
        </div>
        <m-button
          class="w-full dark:bg-zinc-900 xl:dark:bg-zinc-800"
          :loading="loading"
          :isActiveAnim="false"
        >
          登录
        </m-button>
      </vee-form>

      <div class="flex justify-around mt-4">
        <!-- QQ -->
        <qq-login-vue></qq-login-vue>
        <!-- 微信 -->
        <wx-login-vue></wx-login-vue>
      </div>
    </div>
    <!-- 人类行为验证模块 -->
    <slider-captcha-vue
      v-if="isSliderCaptchaVisible"
      @close="isSliderCaptchaVisible = false"
      @success="onCaptchaSuccess"
    ></slider-captcha-vue>
  </div>
</template>

<script>
export default {
  name: 'login'
}
</script>

<script setup>
import headerVue from '../components/header.vue'
import sliderCaptchaVue from './slider-captcha.vue'
import {
  Form as VeeForm,
  Field as VeeField,
  ErrorMessage as VeeErrorMessage
} from 'vee-validate'
import { validateUsername, validatePassword } from '../validate'
import { ref } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { LOGIN_TYPE_USERNAME } from '@/constants'
import qqLoginVue from './qq-login.vue'
import wxLoginVue from './weixin-login.vue'

const store = useStore()
const router = useRouter()

// 控制 sliderCaptcha 展示
const isSliderCaptchaVisible = ref(false)

/**
 * 登录触发
 */
const onLoginHandler = () => {
  isSliderCaptchaVisible.value = true
}

/**
 * 人类行为验证通过
 */
const onCaptchaSuccess = async () => {
  isSliderCaptchaVisible.value = false
  // 登录操作
  onLogin()
}

// 登录时的 loading
const loading = ref(false)
// 用户输入的用户名和密码
const loginForm = ref({
  username: '',
  password: ''
})
/**
 * 用户登录行为
 */
const onLogin = async () => {
  loading.value = true
  // 执行登录操作
  try {
    await store.dispatch('user/login', {
      ...loginForm.value,
      loginType: LOGIN_TYPE_USERNAME
    })
  } finally {
    loading.value = false
  }
  router.push('/')
}

/**
 * 进入注册页面
 */
const onToRegister = () => {
  // 配置跳转方式
  store.commit('app/changeRouterType', 'push')
  router.push('/register')
}
</script>

<style lang="scss" scoped></style>

02: 表单校验实现原理与方案分析

        在绝大多数的情况下,我们进行登录时,都会通过 UI 组件库 实现表单校验功能。但是在没有 UI 组件库 的情况下,我们应该如何进行表单校验呢?

想要搞明白这一点,我们首先就需要搞明白表单校验的 实现原理。 

表单校验的实现原理 

我们知道,所谓表单校验,指的是:

        1. 在某一个时机下(失去焦点、内容变化)

        2. 检查表单元素中的 value 是否符合某个条件(校验条件)

        3. 如果不符合,则给出对应的提示

根据以上描述,我们所需要关注的,其实就是三点内容:

        1. 监听表单元素的对应时机

        2. 检查内容是否匹配校验条件

        3. 根据检查结果,展示对应提示

自定义表单校验方案分析 

根据以上原理描述,如果我们想要自定义一套表单校验的功能逻辑,是不是就比较简单了:

        1. 创建对应的 field 输入框组件

        2. 该组件中,包含两个元素:

                1. input 输入框

                2. span 表示错误提示

3. 监听 input 输入框的 blur 失去焦点 事件

4. 根据 input 的 value 判断是否满足一个或多个指定的条件(比如:是否为空)

5. 如果不满足,则展示 span 标签,表示错误提示消息

文章中的方案实现

        根据以上描述,我们确实可以实现一个基础的表单校验。但是这样的表单校验组件,很难具有 普适 性,因为实际开发中,表单校验的场景多种多样。比如:国际化处理。 

        把它抽离成一个 通用组件 意义并不大。咱们在文章中,就不会专门去实现这样的一个组件。而是会采用一种更加普适的方式。

        这种方式就是:vee-validate

        vee-validate 是一个 vue 中专门做表单校验的库,该库更加具有 普适 性,也更加适合大家在实际开发中的使用。

03: 基于 vee-validate 实现普适的表单校验         

- src/views/login-register
- - validate.js
// 关键代码

import {
  Form as VeeForm,
  Field as VeeField,
  ErrorMessage as VeeErrorMessage
} from 'vee-validate'

// 三个组件的使用
// src/views/login-register/validate.js

/**
 * 用户名的表单校验
 */
export const validateUsername = (value) => {
  if (!value) {
    return '用户名为必填的'
  }

  if (value.length < 3 || value.length > 12) {
    return '用户名应该在 3-12 位之间'
  }
  return true
}

/**
 * 密码的表单校验
 */
export const validatePassword = (value) => {
  if (!value) {
    return '密码为必填的'
  }

  if (value.length < 6 || value.length > 12) {
    return '密码应该在 6-12 位之间'
  }
  return true
}

/**
 * 确认密码的表单校验
 */
export const validateConfirmPassword = (value, password) => {
  if (value !== password[0]) {
    return '两次密码输入必须一致'
  }
  return true
}

04: 什么是人类行为验证?它的目的、实现原理、构建方案分别是什么? 

当表单校验完成之后,接下来我们就来处理 人类行为验证 模块。

想要搞清楚 人类行为验证,就需要搞明白三点内容:

        1. 什么是人类行为验证。

        2. 它的目的是什么。

        3. 它的实现原理是什么。

        4. 我们应该如何在项目中使用它。

什么是人类行为验证 

在我们日常使用的应用中,人类行为验证其实已经无处不在了。

比如大家应该都见过如下场景:

以上场景,均属于人类行为验证模块。

目的 

为什么需要有这样的一个东西呢?这样的一个东西对用户而言是非常讨厌的一个操作。

想要搞明白这个问题,大家就需要先搞清楚现在的应用面临的一个问题。

        假如在一个博客系统中,它会根据博客的访问量进行首页排名。假设有一个人,写了一段脚本代码,构建出巨量的 IP 来不断地访问一个指定的博客。这个博客就会被顶到非常靠前的访问位置中。 

        又假如:在某些投票或者砍价的应用中,也有人利用一段脚本代码,伪造出巨量的用户去进行投票或者砍价的行为,这样的投票或者砍价是不是也就失去了原本的意义。

        针对以上这种场景,我们应该如何防止呢?如何能够判断出,当前进行“投票”的操作是 进行的,而不是 机器 进行的呢?

        想要解决这个问题,就需要使用到 人类行为验证 了。

        简单来说,人类行为验证的目的就是:明确当前的操作是人完成的,而非机器。 

它的实现原理是什么? 

想要完成这样的判断,并且让判断准确,其实是非常复杂的:

        人机验证通过对用户的行为数据、设备特征与网络数据构建多维度数据分析,采用完整的可信前端安全方案,保证数据采集的真实性、有效性。 比如以下几个方面(包括但不仅限于):

        1. 浏览器特征检查:所有浏览器都有差异,可以通过各种前端相关手段检查浏览器环境的真实性。

        2. 鼠标事件(click、move、hover、leave)。

        3. 页面窗口(size、scroll、坐标)。

        4. cookie。

通过收集到的多维度数据,分析并建立人类行为模型,以此来判断用户是否是一个机器人。

以这样的滚动为例:

        人进行的拖动拼图和机器进行的拖动拼图,两者的 鼠标行为轨迹 是不同的。这个不同就是区分人和机器的关键。 

我们如何在项目中使用它

目前人类行为验证的实现方案,主要分为两种:

1. 收费平台,年费在几万到几十万不等,有专门的技术人员帮助对接:

        极验

        网易易盾

2. 免费开源,验证的精准度需要看服务端的能力:

        gitee 开源的 SliderCaptcha

我们这里主要是使用这个开源的 SliderCaptcha 实现。

大家在实际项目中可以根据实际情况进行处理。 

05: 构建人类行为验证模块

- src/views/login-register/login
- - slider-captcha.vue
// src/views/login-register/login/slider-captcha.vue

<template>
  <div
    class="fixed top-[20%] left-[50%] translate-x-[-50%] w-[340px] h-[270px] text-sm bg-white dark:bg-zinc-800 rounded border border-zinc-200 dark:border-zinc-900 shadow-3xl"
  >
    <div class="flex items-center h-5 text-left px-1 mb-1">
      <span class="flex-grow dark:text-zinc-200">请完成安全验证</span>
      <m-svg-icon
        name="refresh"
        fillClass="fill-zinc-900 dark:fill-zinc-200"
        class="w-3 h-3 p-0.5 rounded-sm duration-300 cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-900"
        @click="onReset"
      ></m-svg-icon>
      <m-svg-icon
        name="close"
        fillClass="fill-zinc-900 dark:fill-zinc-200"
        class="ml-2 w-3 h-3 p-0.5 rounded-sm duration-300 cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-900"
        @click="onClose"
      ></m-svg-icon>
    </div>
    <div id="captcha"></div>
  </div>
</template>

<script>
const EMITS_CLOSE = 'close'
const EMITS_SUCCESS = 'success'
</script>

<script setup>
import '@/vendor/SliderCaptcha/slidercaptcha.min.css'
import '@/vendor/SliderCaptcha/longbow.slidercaptcha.min.js'
import { getCaptcha } from '@/api/sys'
import { onMounted } from 'vue'

const emits = defineEmits([EMITS_CLOSE, EMITS_SUCCESS])

let captcha = null
onMounted(() => {
  captcha = sliderCaptcha({
    // 渲染位置
    id: 'captcha',
    // 用户拼图成功之后的回调
    async onSuccess(arr) {
      const res = await getCaptcha({
        behavior: arr
      })
      if (res) {
        emits(EMITS_SUCCESS)
      }
    },
    // 用户拼图失败之后的回调
    onFail() {
      console.log('onFail')
    },
    // 默认的验证方法,咱们不在此处进行验证,而是选择在用户拼图成功之后进行验证,所以此处永远返回为 true
    verify() {
      return true
    }
  })
})

/**
 * 重置
 */
const onReset = () => {
  captcha.reset()
}

/**
 * 关闭
 */
const onClose = () => {
  emits(EMITS_CLOSE)
}
</script>
// index.html

<!-- iconfont 在线图标,主要用于 sliderCaptcha -->
  <link rel="stylesheet"
    href="https://at.alicdn.com/t/font_3042963_nv614canpao.css?spm=a313x.7781069.1998910419.47&file=font_3042963_nv614canpao.css" />

使用: 

// src/views/login-register/login/index.vue

<template>
    <!-- 人类行为验证模块 -->
    <slider-captcha-vue
      v-if="isSliderCaptchaVisible"
      @close="isSliderCaptchaVisible = false"
      @success="onCaptchaSuccess"
    ></slider-captcha-vue>
</template>
<script setup>
import sliderCaptchaVue from './slider-captcha.vue'

// 控制 sliderCaptcha 展示
const isSliderCaptchaVisible = ref(false)

/**
 * 登录触发
 */
const onLoginHandler = () => {
  isSliderCaptchaVisible.value = true
}

/**
 * 人类行为验证通过
 */
const onCaptchaSuccess = async () => {
  isSliderCaptchaVisible.value = false
  // 登录操作
  onLogin()
}
</script>

06: 用户登录行为处理 

// src/views/login-register/login/index.vue

const store = useStore()
const router = useRouter()

// 登录时的 loading
const loading = ref(false)
// 用户输入的用户名和密码
const loginForm = ref({
  username: '',
  password: ''
})
/**
 * 用户登录行为
 */
const onLogin = async () => {
  loading.value = true
  // 执行登录操作
  try {
    await store.dispatch('user/login', {
      ...loginForm.value,
      loginType: LOGIN_TYPE_USERNAME
    })
  } finally {
    loading.value = false
  }
  router.push('/')
}

        我们希望把所有登录逻辑都放入 vuex 中。这是一种比较常见的封装方式。token 的处理、用户信息的处理、退出登录的处理、刷新 token,都可以在一块完成。 

- src/store/modules
- - user.js
// src/store/modules/user.js

import { loginUser, getProfile, registerUser } from '@/api/sys'
import md5 from 'md5'
import { message } from '@/libs'
import { LOGIN_TYPE_OAUTH_NO_REGISTER_CODE } from '@/constants'

export default {
  namespaced: true,
  state: () => ({
    // 登录之后的 token
    token: '',
    // 获取用户信息
    userInfo: {}
  }),
  mutations: {
    /**
     * 保存 token
     */
    setToken(state, newToken) {
      state.token = newToken
    },
    /**
     * 保存用户信息
     */
    setUserInfo(state, newInfo) {
      state.userInfo = newInfo
    }
  },
  actions: {
    /**
     * 注册
     */
    async register(context, payload) {
      const { password } = payload
      // 注册
      return await registerUser({
        ...payload,
        password: password ? md5(password) : ''
      })
    },
    /**
     * 登录
     */
    async login(context, payload) {
      const { password } = payload
      const data = await loginUser({
        ...payload,
        password: password ? md5(password) : ''
      })
      // QQ 扫码登录,用户未注册
      if (data.code === LOGIN_TYPE_OAUTH_NO_REGISTER_CODE) {
        return data.code
      }
      context.commit('setToken', data.token)
      context.dispatch('profile')
    },
    /**
     * 获取用户信息
     */
    async profile(context) {
      const data = await getProfile()
      context.commit('setUserInfo', data)
      // 欢迎
      message(
        'success',
        `欢迎您 ${
          data.vipLevel
            ? '尊贵的 VIP' + data.vipLevel + ' 用户 ' + data.nickname
            : data.nickname
        } `,
        6000
      )
    },
    /**
     * 退出登录
     */
    logout(context) {
      context.commit('setToken', '')
      context.commit('setUserInfo', {})
      // 退出登录之后,重新刷新下页面,
      // 因为对于前台项目而言,用户是否登录(是否为 VIP)看到的数据可能不同
      location.reload()
    }
  }
}
// 注意:在 src/store/index.js 中进行注册
npm i md5

07: 用户信息获取行为

企业级项目中常见的传递 token 方式:在 axios 请求头中

// src/utils/request.js

const service = axios.create({
  baseURL: import.meta.env.VITE_BASE_API,
  timeout: 5000
})

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // config.headers.icode = '你需要在这里填入你的 icode'
    if (store.getters.token) {
      // 如果token存在 注入token
      config.headers.Authorization = `Bearer ${store.getters.token}`
    }
    return config // 必须返回配置
  },
  (error) => {
    return Promise.reject(error)
  }
)
// 代码在上一小节 src/store/modules/user.js 中。

08: 退出登录操作 

// 代码在上一小节 src/store/modules/user.js 中。
// src/views/layout/components/header/header-my.vue

<script setup>
import { confirm } from '@/libs'

/**
 * menu Item 点击事件,也可以根据其他的 key 作为判定,比如 name
 */
const onItemClick = (path) => {
  // 有路径则进行路径跳转
  if (path) {
    // 配置跳转方式
    store.commit('app/changeRouterType', 'push')
    router.push(path)
    return
  }
  // 无路径则为退出登录
  confirm('您确定要退出登录吗?').then(() => {
    // 退出登录不存在跳转路径
    store.dispatch('user/logout')
  })
}
</script>

09: token 超时处理 

通常情况下 token 均具备时效性。在本文章中,token 失效后,服务端会返回 401.

当服务端返回 401 时,表示 token 超时,则需要重新登录。

对应的操作可以在 axios 的响应式拦截器中进行

// src/utils/request.js

import axios from 'axios'
import store from '@/store'
import { message as $message } from '@/libs'

const service = axios.create({
  baseURL: import.meta.env.VITE_BASE_API,
  timeout: 5000
})

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // config.headers.icode = '你需要在这里填入你的 icode'
    if (store.getters.token) {
      // 如果token存在 注入token
      config.headers.Authorization = `Bearer ${store.getters.token}`
    }
    return config // 必须返回配置
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    const { success, message, data } = response.data
    //   要根据success的成功与否决定下面的操作
    if (success) {
      return data
    } else {
      $message('warn', message)
      // TODO:业务错误
      return Promise.reject(new Error(message))
    }
  },
  // code 非 200 时,回调函数。
  (error) => {
    // 处理 token 超时问题
    if (
      error.response &&
      error.response.data &&
      error.response.data.code === 401
    ) {
      // TODO: token超时
      store.dispatch('user/logout')
    }
    $message('error', error.response.data.message)
    // TODO: 提示错误消息
    return Promise.reject(error)
  }
)

export default service

10: 注册页面基本样式处理 

- src/views/login-register
- - register
- - - index.vue
<template>
  <div
    class="relative h-screen bg-white dark:bg-zinc-800 text-center xl:bg-zinc-200"
  >
    <!-- 头部图标 -->
    <header-vue></header-vue>
    <!-- 表单区 -->
    <div
      class="block px-3 mt-4 dark:bg-zinc-800 xl:bg-white xl:w-[388px] xl:dark:bg-zinc-900 xl:m-auto xl:mt-8 xl:py-4 xl:rounded-sm xl:shadow-lg"
    >
      <h3
        class="mb-2 font-semibold text-base text-main dark:text-zinc-300 hidden xl:block"
      >
        注册账号
      </h3>
      <!-- 表单 -->
      <vee-form @submit="onRegister">
        <!-- 用户名 -->
        <vee-field
          class="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b-[1px] w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:dark:bg-zinc-900"
          name="username"
          type="text"
          placeholder="用户名"
          autocomplete="on"
          :rules="validateUsername"
          v-model="regForm.username"
        />
        <vee-error-message
          class="text-sm text-red-600 block mt-0.5 text-left"
          name="username"
        >
        </vee-error-message>
        <!-- 密码 -->
        <vee-field
          class="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b-[1px] w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:dark:bg-zinc-900"
          name="password"
          type="password"
          placeholder="密码"
          autocomplete="on"
          :rules="validatePassword"
          v-model="regForm.password"
        />
        <vee-error-message
          class="text-sm text-red-600 block mt-0.5 text-left"
          name="password"
        >
        </vee-error-message>
        <!-- 确认密码 -->
        <vee-field
          class="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b-[1px] w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:dark:bg-zinc-900"
          name="confirmPassword"
          type="password"
          placeholder="确认密码"
          autocomplete="on"
          rules="validateConfirmPassword:@password"
          v-model="regForm.confirmPassword"
        />
        <vee-error-message
          class="text-sm text-red-600 block mt-0.5 text-left"
          name="confirmPassword"
        >
        </vee-error-message>

        <div class="pt-1 pb-3 leading-[0px] text-right">
          <div class="mb-2">
            <a
              class="inline-block p-1 text-zinc-400 text-right dark:text-zinc-600 hover:text-zinc-600 dark:hover:text-zinc-400 text-sm duration-400 cursor-pointer"
              target="__black"
              @click="onToLogin"
            >
              去登录
            </a>
          </div>
          <div class="text-center">
            <a
              class="text-zinc-400 dark:text-zinc-600 hover:text-zinc-600 dark:hover:text-zinc-400 text-sm duration-400"
              href="https://m.imooc.com/newfaq?id=89"
              target="__black"
            >
              注册即同意《慕课网注册协议》
            </a>
          </div>
        </div>

        <m-button
          class="w-full dark:bg-zinc-900 xl:dark:bg-zinc-800"
          :isActiveAnim="false"
          :loading="loading"
        >
          立即注册
        </m-button>
      </vee-form>
    </div>
  </div>
</template>

<script setup>
import headerVue from '../components/header.vue'
import {
  Form as VeeForm,
  Field as VeeField,
  ErrorMessage as VeeErrorMessage,
  defineRule
} from 'vee-validate'
import {
  validateUsername,
  validatePassword,
  validateConfirmPassword
} from '../validate'
import { LOGIN_TYPE_USERNAME } from '@/constants'
import { ref } from 'vue'
import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router'

const store = useStore()
const router = useRouter()
const route = useRoute()

/**
 * 插入规则
 */
defineRule('validateConfirmPassword', validateConfirmPassword)

/**
 * 进入登录页面
 */
const onToLogin = () => {
  // 配置跳转方式
  store.commit('app/changeRouterType', 'push')
  router.push('/login')
}

// 数据源
const regForm = ref({
  username: '',
  password: '',
  confirmPassword: ''
})
// loading
const loading = ref(false)
console.log(route)
/**
 * 触发注册
 */
const onRegister = async () => {
  loading.value = true
  try {
    const payload = {
      username: regForm.value.username,
      password: regForm.value.password
    }
    // 触发注册,携带第三方数据
    await store.dispatch('user/register', {
      ...payload,
      ...route.query
    })
    // 注册成功,触发登录
    await store.dispatch('user/login', {
      ...payload,
      loginType: LOGIN_TYPE_USERNAME
    })
  } finally {
    loading.value = false
  }
  router.push('/')
}
</script>

<style lang="scss" scoped></style>

确认密码 要关联到 密码,这样的关联操作 需要进行一个单独的注册。

// src/views/login-register/validate.js

/**
 * 确认密码的表单校验
 */
export const validateConfirmPassword = (value, password) => {
  if (value !== password[0]) {
    return '两次密码输入必须一致'
  }
  return true
}


// register/index.vue 中使用代码

import { defineRule } from 'vee-validate'
import { validateConfirmPassword } from '../validate'

/**
 * 插入规则
 */
defineRule('validateConfirmPassword', validateConfirmPassword)

<vee-field      
    placeholder="确认密码"
    autocomplete="on"
    rules="validateConfirmPassword:@password"
/>

11: 处理注册行为 

// src/store/modules/user.js

export default {
    ……
    actions: {
        ……

        /**
         * 注册
         */
        async register(context, payload) {
          const { password } = payload
          // 注册
          return await registerUser({
            ...payload,
            password: password ? md5(password) : ''
          })
        },
    }
}
// src/views/login-register/register/index.vue
// 代码在上一小节中

12: 总结

在本篇文章中,我们主要处理了两块内容:

1. 人类行为验证

        1. 是什么

        2. 目的

        3. 实现原理

        4. 构建方案

2. 表单验证原理 以及在实际开发中 通过 vee-validate 实现表单验证功能

登录处理完成之后,接下来我们就需要处理用户的信息展示和修改了。

在用户信息展示和修改中,我们将接触到新的通用组件和图片裁剪、上传的概念。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1703650.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

three.js官方案例webgl_loader_fbx.html学习

目录 1.1 添加库引入 1.2 添加必要的组件scene,camera,webrenderer等 1.3 模型加载 1.4 半球光 1.5 动画 1.6 换个自己的fbx模型 1.7 fbx模型和fbx动画关联 1.7 html脚本全部如下 1.8 fbx.js全部脚本如下 1.1 添加库引入 import * as THREE from three; import Stats …

AI重塑了我的工作流

阅读内容 Inhai: Agentic Workflow&#xff1a;AI 重塑了我的工作流 4 种主要的 Agentic Workflow 设计模式 Reflection&#xff08;反思&#xff09;&#xff1a;让 Agent 审视和修正自己生成的输出。 举例&#xff1a;如果有两个 Agent&#xff1a;一个负责 Coding&#…

【加密与解密(第四版)】第十二章笔记

第十二章 注入技术 12.1 DLL注入方法 在通常情况下&#xff0c;程序加载 DLL的时机主要有以下3个&#xff1a;一是在进程创建阶段加载输入表中的DLL&#xff0c;即俗称的“静态输人”;二是通过调用 LoadLibrary(Ex)主动加载&#xff0c;称为“动态加载”&#xff1b;三是由于系…

实现UI显示在最上面的功能

同学们肯定遇到过UI被遮挡的情况&#xff0c;那如何让UI显示在最前面呢&#xff0c;先看效果 原理:UI的排序方式是和unityHierarchy窗口的层级顺序有关的&#xff0c;排序在下就越后显示&#xff0c;所以按照这个理论&#xff0c;当我们鼠标指到UI的时候把层级设置到最下层就好…

小猪APP分发:一站式免费应用推广的理想平台

在日益拥挤的移动应用市场中&#xff0c;对于独立开发者和新兴应用而言&#xff0c;找到一个高效且成本效益高的分发渠道至关重要。这正是小猪APP分发平台www.appzhu.cn脱颖而出的原因&#xff0c;它不仅提供了一个全面的解决方案&#xff0c;帮助开发者免费推广他们的应用程序…

宠物空气净化器终极PK,霍尼韦尔、小米、希喂哪款去浮毛最厉害?

宠物毛发和浮毛问题几乎困扰着每一个热爱动物的家庭。宠物的毛发不仅会附着在家具、地毯和衣物上&#xff0c;还可能引起过敏反应&#xff0c;特别是对于那些对宠物毛发敏感的人来说。此外&#xff0c;宠物毛发还可能携带细菌和尘螨&#xff0c;进一步影响家庭环境的清洁和健康…

【讲解下Web前端三大主流的框架】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

STM32系列(HAL库)——F103C8T6通过HC-SR04超声波模块实现测距

一、模块资料 &#xff08;1&#xff09;模块简介 超声波是振动频率高于20kHz的机械波。它具有频率高、波长短、绕射现象小、方向性好、能够成为射线而定向传播等特点。HC-SRO4是一款尺寸完全兼容老版本&#xff0c;增加UART和IIC功能的开放式超声波测距模块,默认条件下,软件…

【简单介绍下容器是什么?】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

001.数据分析_NumPy

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

如何根据系统的业务场景需求定制自己的线程池?

如何根据系统的业务场景需求定制自己的线程池? 1、背景2、生产中应当如何使用线程池才比较合理呢?2.1、指定线程数量2.2、选择合适的工作队列2.3、自定义线程工厂2.4、选择合适的拒绝策略3、自定义线程池代码案例1、背景 线程池有那么多的参数和类型,在实际的开发中,我们应…

【安装】VMware虚拟机安装windows操作系统,VM的相关操作

目录 引出报错&#xff1a;press any key to boot form cd激活调整显示 在VMware上新建虚拟机&#xff0c;并安装Windows1、新建虚拟机2、装载 ISO 镜像3、安装Windows server 20164、开机初始化 虚拟机操作1、虚拟机基本操作2、虚拟机快照3、虚拟机克隆4、VMware Tools 总结 引…

2024.5.22 关于 SpringCloud —— Nacos 配置管理

目录 Nacos 配置统一管理 Nacos 配置热部署 Nacos 多环境配置共享 配置优先级 Nacos 配置统一管理 实例理解 我们想要利用 Nacos 在 user-service 的 application.yml 配置文件中新增配置项此处我们将新增配置日期格式为 yyyy-MM-dd HH:mm:ss下图为新增 Nacos 配置统一管理…

Latex公式编辑:在矩阵内画横线与竖线

在LaTeX中&#xff0c;要在矩阵内绘制横线和竖线&#xff0c;我们通常使用array或matrix环境&#xff0c;并结合\hline&#xff08;用于横线&#xff09;和|&#xff08;用于竖线&#xff09;来实现。但需要注意的是&#xff0c;\hline通常用于表格环境中。 LaTeX中绘制分块矩阵…

jQuery事件导读+其它方法

jQuery 事件导读一、事件注册二、事件处理1.内容2.例子&#xff0c;微博绑定事件3.off解绑事件4.自动触发事件 三、事件对象 其他方法一、拷贝对象二、多库共存三、插件 事件导读 一、事件注册 单个事件叫注册&#xff0c;多个事件叫处理 二、事件处理 1.内容 2.例子&#…

BUUCTF-misc刷题

被嗅探的流量1 用wireshark打开附件&#xff0c;Ctrlf&#xff0c;然后搜索flag&#xff0c;我们在这么多数据包中搜索带有flag字符的 然后第一个包中上传了一个名叫flag的.jpg文件 然后直接ctrlf&#xff0c;搜索flag{ 得到flag&#xff1a;flag{da73d88936010da1eeeb36e945e…

【简单介绍下线性回归模型】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

51单片机-实机演示(单多个数码管)

仿真链接&#xff1a; http://t.csdnimg.cn/QAPhx 目录 一.引脚位置 二.多个显示 三 扩展 一.引脚位置 注意P00 - >A ; 这个多个的在左边,右边的A到B是控制最右边那个单个的. 接下来上显示单个的代码 #include <reg52.h> #include <intrins.h> #define u…

【MySQL事务(上)】

文章目录 前言一、什么是事务&#xff1f;1.关于事务的特性 二、为什么要有事务三、事务的提交方式测试事务准备工作事务的操作1.启动事务2.对事务进行回滚&#xff08;只有在事务进行期间&#xff09;3.提交事务&#xff08;持久化&#xff09;4.事务的异常情况结论 四、事务的…

漫步者x1穷鬼耳机双耳断连

困扰了我两天&#xff0c;终于有时间解决这个问题了&#xff0c;查看了一堆都是别的型号。怎么没人用这个啥按键都没有的耳机QAQ&#xff0c;幸好给我找到了说明书&#xff0c;啊哈哈&#xff01; 说明书地址