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

目录

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 实现表单验证功能

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

结合Django和Vue.js构建现代Web应用

文章目录 1. 创建Django项目2. 配置Django后端3. 创建Vue.js前端4. 连接Django和Vue.js5. 构建和部署 在现代Web开发中&#xff0c;结合后端框架和前端框架是非常常见的&#xff0c;其中Django作为一种流行的Python后端框架&#xff0c;而Vue.js则是一种灵活强大的前端框架。本…

一、Nginx详解和安装

目录 一、简介 1、什么是Nginx 2、Nginx的优点 二、四大应用场景 1、HTTP服务器 2、反向代理 3、负载均衡 4、动静分离 三、Nginx 源码安装 1、安装相关依赖 2、启动nginx 3、安装成系统服务 一、简介 1、什么是Nginx Nginx是一款轻量级的Web服务器&#xff0c;反向…

艾体宝干货 | 用于故障排除的最佳 Wireshark 过滤器

在网络故障排除过程中&#xff0c;Wireshark是一款非常强大的工具&#xff0c;它可以用来分析网络数据包并解决各种问题。本文将介绍一些好用的Wireshark过滤器&#xff0c;以便更有效地进行故障排除。 分析网络行为和排除网络故障就像用漏斗过滤渣滓。因此&#xff0c;网络协…

如何给出好的“文言一心”指令?

一、文言一心是什么&#xff1f; 在现代技术背景下&#xff0c;“文言一心”还是百度公司创建的一款大语言模型。这款模型基于飞桨深度学习平台和文心知识增强大模型&#xff0c;并拥有强大的中文语料库&#xff0c;可以理解和生成富含文化内涵和哲理的文本内容。其核心技术架构…

MahApps.Metro的MVVM模式解析(二) 主题功能

MahApps.Metro的MVVM模式解析&#xff08;二&#xff09; 主题功能 MahApps.Metro是一个开源的WPF框架&#xff0c;旨在为WPF应用程序提供现代和漂亮的用户界面。 在MahApps.Metro中提供了切换主题的功能。经过多日的筛选和分析&#xff0c;在本文来理清它的脉络。 1 主题功…

数据结构-二叉树系统性学习(四万字精讲拿捏)

前言 这里我会从二叉树的概念开始讲解&#xff0c;其次涉及到概念结构&#xff0c;以及堆的实现和堆排序。 目的是&#xff0c;堆比二叉树简单&#xff0c;同时堆本质上是二叉树的其中一种情况&#xff0c;堆属于二叉树顺序结构的实现 最后完善二叉树的讲解&#xff0c;也就是…

认知觉醒:情绪绝对是财富的拦路虎……

认知觉醒 无论是投资还是做生意&#xff0c;跟随大众情绪就一定会亏损&#xff0c;老百姓没有是非认知&#xff0c;只有好恶&#xff0c;所以就很容易被人操控情绪。随便一个社会热点事件&#xff0c;就比如最近的涂磊事件、郭有才事件&#xff0c;打开视频的评论区&#xff0…

浅谈JMeter环境变量设置

JMeter环境变量设置 确保已安装Java Development Kit (JDK) JMeter需要Java运行环境&#xff0c;因此请先安装JDK&#xff0c;并确认JAVA_HOME环境变量已经设置好。可以通过命令提示符输入java -version和javac -version来验证是否安装成功及其版本信息&#xff0c;若没有出现…

U8G2移植到STM32,SSD13XXXOLED(硬件SPI DMA通讯)

文章目录 一、前言1.1 U8g2的特点1.2 U8G2的优势1.3 U8G2的下载地址1.4 U8g2支持的显示控制器 二、STM32Cubexm SPI DMA配置2.1 SPI设置为半双工模式2.2 SPI DMA设置2.3 oled其他引脚配置 三、移植U8G2框架3.1 精简U8G2库文件3.2 去掉csrc文件夹中无用的驱动文件3.3 文件移动到…

PLC无线通讯模块在触摸屏与台达PLC之间的应用教程

本方案可以作为信捷触摸屏与台达PLC之间在台达专用协议下将有线通讯改为无线通讯的应用实例。此方案中信捷TG765触摸屏作为主站&#xff0c;台达DVP-32EH PLC作为从站&#xff0c;采用西安达泰电子日系PLC无线通讯终端——DTD435MA-V384&#xff0c;作为实现无线通讯的硬件设备…

【面试必看】系统设计

系统设计 1. 认证与授权 Authentication&#xff08;认证&#xff09; 是验证您的身份的凭据&#xff08;例如用户名/用户 ID 和密码&#xff09;&#xff0c;通过这个凭据&#xff0c;系统得以知道你就是你&#xff0c;也就是说系统存在你这个用户&#xff0c;也叫做身份/用户…

所有平台均可发布,矩阵操作+工具+素材,自动混剪8090后怀旧视频

“怀旧”这个词对于80、90后来说&#xff0c;总能勾起一阵阵心中的涟漪。无论是那些留存在记忆深处的动画经典&#xff0c;还是代代相传的游戏主题曲&#xff0c;亦或是那个时代特有的玩具&#xff0c;都构成了他们共同的美好回忆。就像乘坐一艘穿梭机&#xff0c;怀旧视频能够…

无人机行业所需企业服务等级证书详解

无人机行业中的民用无人机企业服务等级证书是对企业在无人机服务领域的能力、技术、管理和服务质量等方面的一种全面认可和评定。这种证书的存在&#xff0c;不仅有助于企业了解自身服务能力和水平&#xff0c;提升服务质量&#xff0c;增强市场竞争力&#xff0c;促进规范化管…

【有效的数独】python

目录 很好&#xff0c;超级暴力做法&#xff0c;能过就行&#xff0c;优雅个锤子啊 优雅的做法 &#xff0c;绅士&#xff0c;噢听说叫模拟 很好&#xff0c;超级暴力做法&#xff0c;能过就行&#xff0c;优雅个锤子啊 我直接11个2层for循环暴力秒杀 class Solution:def is…

使用 SwanLab 进行可视化 MNIST 手写体识别训练

使用 SwanLab 进行可视化 MNIST 手写体识别训练 在线演示demo 本案例主要&#xff1a; 使用pytorch进行CNN&#xff08;卷积神经网络&#xff09;的构建、模型训练与评估使用swanlab跟踪超参数、记录指标和可视化监控整个训练周期 一、相关简介 SwanLab SwanLab是一款开源…

Vue基础(数据绑定、export使用)

1、简介 在使用vue开发的过程中&#xff0c;经常会遇到一些容易混淆的问题&#xff0c;因此&#xff0c;在本文中进行汇总操作&#xff0c;只有通过不断总结学习&#xff0c;才能更好掌握vue的使用&#xff08;每天进步一点&#xff09;。 2、数据绑定 在js中定义数据&#xf…

三分钟一条AI小和尚视频 ,日引300+创业粉。单日变现四位数 全套工具

经过六个月的不懈努力和无数次的尝试错误&#xff0c;我终于找到了一个高效引流和积累粉丝的新策略&#xff0c;并愿意与大家无私分享。这一次&#xff0c;我将详尽地介绍这个方法&#xff0c;建议朋友们多次观看以彻底掌握其精髓。 简而言之&#xff0c;该策略主要依托于AI绘…

Spring 原理详解

1. Bean的作用域 Bean在Spring中表示的是Spring管理的对象&#xff0c;Bean的作用域是只Bean在Spring框架中的某种行为模式。 在Spring中&#xff0c;支持6中作用域&#xff1a; singleton&#xff1a;单例作用域&#xff0c;在整个 Spring IoC 容器中&#xff0c;只创建一个…

Json差异比较

json差异比较 如何比较两个json的差异 代码实现 导入依赖 <dependency><groupId>cn.xiaoandcai</groupId><artifactId>json-diff</artifactId><!-- 旧版本可能存在某些缺陷。版本请以maven仓库最版为准。 --><version>4.1.3-RC1-R…

没想到,一个小妙招让桌面运维效率翻倍

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 我的网工朋友大家好。 咱们都知道&#xff0c;电脑用久了&#xff0c;总会出些小毛病&#xff0c;比如桌面图标不显示了&#xff0c;C盘又满了&a…