验证码
parent
306f658fbf
commit
4df4b5cdb8
|
|
@ -0,0 +1,138 @@
|
||||||
|
<template>
|
||||||
|
<div class="s-canvas">
|
||||||
|
<canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SIdentify',
|
||||||
|
props: {
|
||||||
|
identifyCode: {
|
||||||
|
type: String,
|
||||||
|
default: '1234',
|
||||||
|
},
|
||||||
|
fontSizeMin: {
|
||||||
|
type: Number,
|
||||||
|
default: 16,
|
||||||
|
},
|
||||||
|
fontSizeMax: {
|
||||||
|
type: Number,
|
||||||
|
default: 40,
|
||||||
|
},
|
||||||
|
backgroundColorMin: {
|
||||||
|
type: Number,
|
||||||
|
default: 180,
|
||||||
|
},
|
||||||
|
backgroundColorMax: {
|
||||||
|
type: Number,
|
||||||
|
default: 240,
|
||||||
|
},
|
||||||
|
colorMin: {
|
||||||
|
type: Number,
|
||||||
|
default: 50,
|
||||||
|
},
|
||||||
|
colorMax: {
|
||||||
|
type: Number,
|
||||||
|
default: 160,
|
||||||
|
},
|
||||||
|
lineColorMin: {
|
||||||
|
type: Number,
|
||||||
|
default: 40,
|
||||||
|
},
|
||||||
|
lineColorMax: {
|
||||||
|
type: Number,
|
||||||
|
default: 180,
|
||||||
|
},
|
||||||
|
dotColorMin: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
dotColorMax: {
|
||||||
|
type: Number,
|
||||||
|
default: 255,
|
||||||
|
},
|
||||||
|
contentWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 112,
|
||||||
|
},
|
||||||
|
contentHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 38,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
identifyCode() {
|
||||||
|
this.drawPic()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.drawPic()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 生成一个随机数
|
||||||
|
randomNum(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min) + min)
|
||||||
|
},
|
||||||
|
// 生成一个随机的颜色
|
||||||
|
randomColor(min, max) {
|
||||||
|
let r = this.randomNum(min, max)
|
||||||
|
let g = this.randomNum(min, max)
|
||||||
|
let b = this.randomNum(min, max)
|
||||||
|
return 'rgb(' + r + ',' + g + ',' + b + ')'
|
||||||
|
},
|
||||||
|
drawPic() {
|
||||||
|
let canvas = document.getElementById('s-canvas')
|
||||||
|
let ctx = canvas.getContext('2d')
|
||||||
|
ctx.textBaseline = 'bottom'
|
||||||
|
// 绘制背景
|
||||||
|
ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)
|
||||||
|
ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)
|
||||||
|
// 绘制文字
|
||||||
|
for (let i = 0; i < this.identifyCode.length; i++) {
|
||||||
|
this.drawText(ctx, this.identifyCode[i], i)
|
||||||
|
}
|
||||||
|
this.drawLine(ctx)
|
||||||
|
this.drawDot(ctx)
|
||||||
|
},
|
||||||
|
drawText(ctx, txt, i) {
|
||||||
|
ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)
|
||||||
|
ctx.font = this.randomNum(this.fontSizeMin, this.fontSizeMax) + 'px SimHei'
|
||||||
|
let x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1))
|
||||||
|
let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)
|
||||||
|
var deg = this.randomNum(-45, 45)
|
||||||
|
// 修改坐标原点和旋转角度
|
||||||
|
ctx.translate(x, y)
|
||||||
|
ctx.rotate((deg * Math.PI) / 180)
|
||||||
|
ctx.fillText(txt, 0, 0)
|
||||||
|
// 恢复坐标原点和旋转角度
|
||||||
|
ctx.rotate((-deg * Math.PI) / 180)
|
||||||
|
ctx.translate(-x, -y)
|
||||||
|
},
|
||||||
|
drawLine(ctx) {
|
||||||
|
// 绘制干扰线
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
|
||||||
|
ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
|
||||||
|
ctx.stroke()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drawDot(ctx) {
|
||||||
|
// 绘制干扰点
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
ctx.fillStyle = this.randomColor(0, 255)
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(
|
||||||
|
this.randomNum(0, this.contentWidth),
|
||||||
|
this.randomNum(0, this.contentHeight),
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2 * Math.PI,
|
||||||
|
)
|
||||||
|
ctx.fill()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -25,6 +25,23 @@
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem name="identifyCode" class="enter-x">
|
||||||
|
<div class="flex items-center w-full">
|
||||||
|
<div class="flex-1">
|
||||||
|
<Input
|
||||||
|
size="large"
|
||||||
|
v-model:value="formData.identifyCode"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
class="!w-full !min-w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div @click="refreshCode">
|
||||||
|
<Security class="ml-1 flex-none" :identifyCode="identifyCode" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
<!-- <ARow class="enter-x">
|
<!-- <ARow class="enter-x">
|
||||||
<ACol :span="12">
|
<ACol :span="12">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
|
|
@ -90,6 +107,7 @@
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Security from '/@/components/Security/index.vue'
|
||||||
import { reactive, ref, unref, computed } from 'vue'
|
import { reactive, ref, unref, computed } from 'vue'
|
||||||
import md5 from 'js-md5'
|
import md5 from 'js-md5'
|
||||||
import { Checkbox, Form, Input, Row, Col, Button, Divider } from 'ant-design-vue'
|
import { Checkbox, Form, Input, Row, Col, Button, Divider } from 'ant-design-vue'
|
||||||
|
|
@ -125,10 +143,11 @@
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const rememberMe = ref(false)
|
const rememberMe = ref(false)
|
||||||
|
const identifyCode = ref('')
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
account: '',
|
account: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
identifyCode: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { validForm } = useFormValid(formRef)
|
const { validForm } = useFormValid(formRef)
|
||||||
|
|
@ -136,10 +155,38 @@
|
||||||
//onKeyStroke('Enter', handleLogin);
|
//onKeyStroke('Enter', handleLogin);
|
||||||
|
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||||
|
function randomNum(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min) + min)
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshCode() {
|
||||||
|
formData.identifyCode = ''
|
||||||
|
makeCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
makeCode()
|
||||||
|
|
||||||
|
function makeCode(l = 4) {
|
||||||
|
const identifyCodes = '1234567890abcdef'
|
||||||
|
let code = ''
|
||||||
|
|
||||||
|
for (let i = 0; i < l; i++) {
|
||||||
|
code += identifyCodes[randomNum(0, identifyCodes.length)]
|
||||||
|
}
|
||||||
|
identifyCode.value = code
|
||||||
|
}
|
||||||
|
|
||||||
async function handleLogin(e) {
|
async function handleLogin(e) {
|
||||||
const data = await validForm()
|
const data = await validForm()
|
||||||
if (!data) return
|
if (!data) return
|
||||||
|
if (data.identifyCode.toLowerCase() !== identifyCode.value.toLowerCase()) {
|
||||||
|
createErrorModal({
|
||||||
|
title: '错误提示',
|
||||||
|
content: '验证码不正确',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const userInfo = await userStore.login({
|
const userInfo = await userStore.login({
|
||||||
|
|
|
||||||
|
|
@ -66,39 +66,40 @@
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref, unref, computed } from 'vue';
|
import { reactive, ref, unref, computed } from 'vue'
|
||||||
import LoginFormTitle from './LoginFormTitle.vue';
|
import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
import { Form, Input, Button, Checkbox } from 'ant-design-vue';
|
import { Form, Input, Button, Checkbox } from 'ant-design-vue'
|
||||||
import { StrengthMeter } from '/@/components/StrengthMeter';
|
import { StrengthMeter } from '/@/components/StrengthMeter'
|
||||||
import { CountdownInput } from '/@/components/CountDown';
|
import { CountdownInput } from '/@/components/CountDown'
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '/@/hooks/web/useI18n'
|
||||||
import { useLoginState, useFormRules, useFormValid, LoginStateEnum } from './useLogin';
|
import { useLoginState, useFormRules, useFormValid, LoginStateEnum } from './useLogin'
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item
|
||||||
const InputPassword = Input.Password;
|
const InputPassword = Input.Password
|
||||||
const { t } = useI18n();
|
const { t } = useI18n()
|
||||||
const { handleBackLogin, getLoginState } = useLoginState();
|
const { handleBackLogin, getLoginState } = useLoginState()
|
||||||
|
|
||||||
const formRef = ref();
|
const formRef = ref()
|
||||||
const loading = ref(false);
|
const loading = ref(false)
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
account: '',
|
account: '',
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
|
identifyCode: '',
|
||||||
mobile: '',
|
mobile: '',
|
||||||
sms: '',
|
sms: '',
|
||||||
policy: false,
|
policy: false,
|
||||||
});
|
})
|
||||||
|
|
||||||
const { getFormRules } = useFormRules(formData);
|
const { getFormRules } = useFormRules(formData)
|
||||||
const { validForm } = useFormValid(formRef);
|
const { validForm } = useFormValid(formRef)
|
||||||
|
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER);
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
|
||||||
|
|
||||||
async function handleRegister() {
|
async function handleRegister() {
|
||||||
const data = await validForm();
|
const data = await validForm()
|
||||||
if (!data) return;
|
if (!data) return
|
||||||
console.log(data);
|
console.log(data)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
|
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'
|
||||||
import type { RuleObject } from 'ant-design-vue/lib/form/interface';
|
import type { RuleObject } from 'ant-design-vue/lib/form/interface'
|
||||||
import { ref, computed, unref, Ref } from 'vue';
|
import { ref, computed, unref, Ref } from 'vue'
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '/@/hooks/web/useI18n'
|
||||||
|
|
||||||
export enum LoginStateEnum {
|
export enum LoginStateEnum {
|
||||||
LOGIN,
|
LOGIN,
|
||||||
|
|
@ -11,67 +11,84 @@ export enum LoginStateEnum {
|
||||||
QR_CODE,
|
QR_CODE,
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentState = ref(LoginStateEnum.LOGIN);
|
const currentState = ref(LoginStateEnum.LOGIN)
|
||||||
|
|
||||||
export function useLoginState() {
|
export function useLoginState() {
|
||||||
function setLoginState(state: LoginStateEnum) {
|
function setLoginState(state: LoginStateEnum) {
|
||||||
currentState.value = state;
|
currentState.value = state
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLoginState = computed(() => currentState.value);
|
const getLoginState = computed(() => currentState.value)
|
||||||
|
|
||||||
function handleBackLogin() {
|
function handleBackLogin() {
|
||||||
setLoginState(LoginStateEnum.LOGIN);
|
setLoginState(LoginStateEnum.LOGIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { setLoginState, getLoginState, handleBackLogin };
|
return { setLoginState, getLoginState, handleBackLogin }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFormValid<T extends Object = any>(formRef: Ref<any>) {
|
export function useFormValid<T extends Object = any>(formRef: Ref<any>) {
|
||||||
async function validForm() {
|
async function validForm() {
|
||||||
const form = unref(formRef);
|
const form = unref(formRef)
|
||||||
if (!form) return;
|
if (!form) return
|
||||||
const data = await form.validate();
|
const data = await form.validate()
|
||||||
return data as T;
|
return data as T
|
||||||
}
|
}
|
||||||
|
|
||||||
return { validForm };
|
return { validForm }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFormRules(formData?: Recordable) {
|
export function useFormRules(formData?: Recordable) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n()
|
||||||
|
|
||||||
const getAccountFormRule = computed(() => createRule(t('sys.login.accountPlaceholder')));
|
const getAccountFormRule = computed(() => createRule(t('sys.login.accountPlaceholder')))
|
||||||
const getPasswordFormRule = computed(() => createRule(t('sys.login.passwordPlaceholder')));
|
const getPasswordFormRule = computed(() => createRule(t('sys.login.passwordPlaceholder')))
|
||||||
const getSmsFormRule = computed(() => createRule(t('sys.login.smsPlaceholder')));
|
const getIdentifyCodeFormRule = computed(() => createRule('请输入验证码'))
|
||||||
const getMobileFormRule = computed(() => createRule(t('sys.login.mobilePlaceholder')));
|
const getSmsFormRule = computed(() => createRule(t('sys.login.smsPlaceholder')))
|
||||||
|
const getMobileFormRule = computed(() => createRule(t('sys.login.mobilePlaceholder')))
|
||||||
|
|
||||||
const validatePolicy = async (_: RuleObject, value: boolean) => {
|
const validatePolicy = async (_: RuleObject, value: boolean) => {
|
||||||
return !value ? Promise.reject(t('sys.login.policyPlaceholder')) : Promise.resolve();
|
return !value ? Promise.reject(t('sys.login.policyPlaceholder')) : Promise.resolve()
|
||||||
};
|
}
|
||||||
|
|
||||||
const validateConfirmPassword = (password: string) => {
|
const validateConfirmPassword = (password: string) => {
|
||||||
return async (_: RuleObject, value: string) => {
|
return async (_: RuleObject, value: string) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return Promise.reject(t('sys.login.passwordPlaceholder'));
|
return Promise.reject(t('sys.login.passwordPlaceholder'))
|
||||||
}
|
}
|
||||||
if (value !== password) {
|
if (value !== password) {
|
||||||
return Promise.reject(t('sys.login.diffPwd'));
|
return Promise.reject(t('sys.login.diffPwd'))
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve()
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
//校验验证码
|
||||||
|
const validateIdentifyCode = (validate: string) => {
|
||||||
|
return async (_: RuleObject, value: string) => {
|
||||||
|
console.log(validate)
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入验证码')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !== validate) {
|
||||||
|
return Promise.reject('验证码不正确')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getFormRules = computed((): { [k: string]: ValidationRule | ValidationRule[] } => {
|
const getFormRules = computed((): { [k: string]: ValidationRule | ValidationRule[] } => {
|
||||||
const accountFormRule = unref(getAccountFormRule);
|
const accountFormRule = unref(getAccountFormRule)
|
||||||
const passwordFormRule = unref(getPasswordFormRule);
|
const passwordFormRule = unref(getPasswordFormRule)
|
||||||
const smsFormRule = unref(getSmsFormRule);
|
const identifyFormRule = unref(getIdentifyCodeFormRule)
|
||||||
const mobileFormRule = unref(getMobileFormRule);
|
const smsFormRule = unref(getSmsFormRule)
|
||||||
|
const mobileFormRule = unref(getMobileFormRule)
|
||||||
|
|
||||||
const mobileRule = {
|
const mobileRule = {
|
||||||
sms: smsFormRule,
|
sms: smsFormRule,
|
||||||
mobile: mobileFormRule,
|
mobile: mobileFormRule,
|
||||||
};
|
}
|
||||||
switch (unref(currentState)) {
|
switch (unref(currentState)) {
|
||||||
// register form rules
|
// register form rules
|
||||||
case LoginStateEnum.REGISTER:
|
case LoginStateEnum.REGISTER:
|
||||||
|
|
@ -83,28 +100,29 @@ export function useFormRules(formData?: Recordable) {
|
||||||
],
|
],
|
||||||
policy: [{ validator: validatePolicy, trigger: 'change' }],
|
policy: [{ validator: validatePolicy, trigger: 'change' }],
|
||||||
...mobileRule,
|
...mobileRule,
|
||||||
};
|
}
|
||||||
|
|
||||||
// reset password form rules
|
// reset password form rules
|
||||||
case LoginStateEnum.RESET_PASSWORD:
|
case LoginStateEnum.RESET_PASSWORD:
|
||||||
return {
|
return {
|
||||||
account: accountFormRule,
|
account: accountFormRule,
|
||||||
...mobileRule,
|
...mobileRule,
|
||||||
};
|
}
|
||||||
|
|
||||||
// mobile form rules
|
// mobile form rules
|
||||||
case LoginStateEnum.MOBILE:
|
case LoginStateEnum.MOBILE:
|
||||||
return mobileRule;
|
return mobileRule
|
||||||
|
|
||||||
// login form rules
|
// login form rules
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
account: accountFormRule,
|
account: accountFormRule,
|
||||||
password: passwordFormRule,
|
password: passwordFormRule,
|
||||||
};
|
identifyCode: identifyFormRule,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
return { getFormRules };
|
return { getFormRules }
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRule(message: string) {
|
function createRule(message: string) {
|
||||||
|
|
@ -114,5 +132,5 @@ function createRule(message: string) {
|
||||||
message,
|
message,
|
||||||
trigger: 'change',
|
trigger: 'change',
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue