feat: 登录补充验证码
This commit is contained in:
@@ -67,6 +67,7 @@ export { NsVNode } from './VNode';
|
||||
export { NsMessage } from './message';
|
||||
export { NsModal } from './modal';
|
||||
export { NsDrawer } from './drawer';
|
||||
export { NsVerify } from './verify';
|
||||
|
||||
export { NsForm } from './form/form';
|
||||
export * from './table';
|
||||
|
4
lib/component/verify/index.ts
Normal file
4
lib/component/verify/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import verify from './verify.vue';
|
||||
import { withInstall } from '/nerv-lib/util';
|
||||
|
||||
export const NsVerify = withInstall(verify);
|
127
lib/component/verify/verify.vue
Normal file
127
lib/component/verify/verify.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="img-verify">
|
||||
<canvas ref="verify" :width="state.width" :height="state.height" @click="handleDraw"></canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, ref, toRefs, defineEmits } from 'vue';
|
||||
const verify = ref(null);
|
||||
const emit = defineEmits(['get-code']);
|
||||
defineOptions({
|
||||
name: 'NsVerify',
|
||||
});
|
||||
const state = reactive({
|
||||
pool: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', // 字符串
|
||||
width: 120,
|
||||
height: 48,
|
||||
imgCode: '',
|
||||
});
|
||||
onMounted(() => {
|
||||
// 初始化绘制图片验证码
|
||||
state.imgCode = draw();
|
||||
emit('get-code', state.imgCode);
|
||||
});
|
||||
// 点击图片重新绘制
|
||||
const handleDraw = () => {
|
||||
state.imgCode = draw();
|
||||
emit('get-code', state.imgCode);
|
||||
};
|
||||
|
||||
// 随机数
|
||||
const randomNum = (min, max) => {
|
||||
return parseInt(Math.random() * (max - min) + min);
|
||||
};
|
||||
// 随机颜色
|
||||
const randomColor = (min, max) => {
|
||||
const r = randomNum(min, max);
|
||||
const g = randomNum(min, max);
|
||||
const b = randomNum(min, max);
|
||||
return `rgb(${r},${g},${b})`;
|
||||
};
|
||||
|
||||
// 绘制图片
|
||||
const draw = () => {
|
||||
// 3.填充背景颜色,背景颜色要浅一点
|
||||
const ctx = verify.value?.getContext('2d');
|
||||
// 填充颜色
|
||||
ctx.fillStyle = randomColor(180, 230);
|
||||
// 填充的位置
|
||||
ctx.fillRect(0, 0, state.width, state.height);
|
||||
// 定义paramText
|
||||
let imgCode = '';
|
||||
// 4.随机产生字符串,并且随机旋转
|
||||
for (let i = 0; i < 4; i++) {
|
||||
// 随机的四个字
|
||||
const text = state.pool[randomNum(0, state.pool.length)];
|
||||
imgCode += text;
|
||||
// 随机的字体大小
|
||||
const fontSize = randomNum(Math.floor(state.height / 2), state.height);
|
||||
// 字体随机的旋转角度
|
||||
const deg = randomNum(-30, 30);
|
||||
/*
|
||||
* 绘制文字并让四个文字在不同的位置显示的思路 :
|
||||
* 1、定义字体
|
||||
* 2、定义对齐方式
|
||||
* 3、填充不同的颜色
|
||||
* 4、保存当前的状态(以防止以上的状态受影响)
|
||||
* 5、平移translate()
|
||||
* 6、旋转 rotate()
|
||||
* 7、填充文字
|
||||
* 8、restore出栈
|
||||
* */
|
||||
ctx.font = fontSize + 'px Simhei';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillStyle = randomColor(80, 150);
|
||||
/*
|
||||
* save() 方法把当前状态的一份拷贝压入到一个保存图像状态的栈中。
|
||||
* 这就允许您临时地改变图像状态,
|
||||
* 然后,通过调用 restore() 来恢复以前的值。
|
||||
* save是入栈,restore是出栈。
|
||||
* 用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。 restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
|
||||
*
|
||||
* */
|
||||
ctx.save();
|
||||
ctx.translate(30 * i + 15, 15);
|
||||
ctx.rotate((deg * Math.PI) / 180);
|
||||
// fillText() 方法在画布上绘制填色的文本。文本的默认颜色是黑色。
|
||||
// 请使用 font 属性来定义字体和字号,并使用 fillStyle 属性以另一种颜色/渐变来渲染文本。
|
||||
// fillText(text,x,y,maxWidth);
|
||||
ctx.fillText(text, -15 + 5, -15);
|
||||
ctx.restore();
|
||||
}
|
||||
// 5.随机产生5条干扰线,干扰线的颜色要浅一点
|
||||
for (let i = 0; i < 5; i++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(randomNum(0, state.width), randomNum(0, state.height));
|
||||
ctx.lineTo(randomNum(0, state.width), randomNum(0, state.height));
|
||||
ctx.strokeStyle = randomColor(180, 230);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
// 6.随机产生40个干扰的小点
|
||||
for (let i = 0; i < state.height; i++) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
randomNum(0, state.width),
|
||||
randomNum(0, state.height),
|
||||
randomNum(0, 3), //随机大小
|
||||
0,
|
||||
2 * Math.PI,
|
||||
);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = randomColor(150, 200);
|
||||
ctx.fill();
|
||||
}
|
||||
return imgCode;
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.img-verify {
|
||||
height: 48px;
|
||||
/* margin: 0 0.1rem; */
|
||||
}
|
||||
.img-verify canvas {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
@@ -4,6 +4,7 @@ import { useApi, HttpRequestConfig } from '/nerv-lib/use/use-api';
|
||||
|
||||
interface AppConfig {
|
||||
projectType: string;
|
||||
projectName: string;
|
||||
baseApi: string;
|
||||
timeout: number;
|
||||
pagePermission: boolean;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@primary-color: #37abc4; // 全局主色
|
||||
@primary-color: #2778FF; // 全局主色
|
||||
|
||||
@white: #fff;
|
||||
@black: #000;
|
||||
@@ -25,6 +25,9 @@
|
||||
// Border color
|
||||
@border-color-base: hsv(0, 0, 85%); // base border outline a component
|
||||
|
||||
// border
|
||||
@border-radius-base: 4px;
|
||||
|
||||
//btn
|
||||
//@btn-disable-color: #9b9b9b; //禁用按钮color
|
||||
//@btn-disable-bg: #f5f5f5; //禁用按钮background
|
||||
|
@@ -16,24 +16,31 @@
|
||||
backgroundSize: 'cover',
|
||||
}">
|
||||
<div class="lg_card">
|
||||
<h1 class="lg_card_title">账号登录</h1>
|
||||
<h1 class="lg_card_title">{{ configStore.projectName }}</h1>
|
||||
<p v-show="!errorShow" style="height: 8px"></p>
|
||||
<p v-show="errorShow" class="lg_card_error">{{ errorMsg }}</p>
|
||||
<p class="lg_card_tip">用户名/手机号</p>
|
||||
<a-input
|
||||
placeholder="登录账号"
|
||||
v-model:value="userName"
|
||||
style="height: 48px; margin-bottom: 20px">
|
||||
<!-- <p class="lg_card_tip">用户名/手机号</p> -->
|
||||
<a-input class="loginInfo" placeholder="用户名" v-model:value="userName">
|
||||
<template #prefix>
|
||||
<ns-icon class="loginIcon" name="userName" size="25" style="margin-right: 5px" />
|
||||
<ns-icon class="loginIcon" name="userName" size="19" style="margin-right: 20px" />
|
||||
</template>
|
||||
</a-input>
|
||||
<p class="lg_card_tip">密码</p>
|
||||
<a-input-password placeholder="登录密码" v-model:value="password" style="height: 48px">
|
||||
<!-- <p class="lg_card_tip">密码</p> -->
|
||||
<a-input-password class="loginInfo" placeholder="密码" v-model:value="password">
|
||||
<template #prefix>
|
||||
<ns-icon class="loginIcon" name="passWord" size="25" style="margin-right: 5px" />
|
||||
<ns-icon class="loginIcon" name="passWord" size="19" style="margin-right: 20px" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
<!-- 验证码 -->
|
||||
|
||||
<a-input v-model:value="code" placeholder="验证码" class="loginInfo">
|
||||
<template #prefix>
|
||||
<ns-icon class="loginIcon" name="verifyIcon" size="19" style="margin-right: 20px" />
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<ns-verify @get-code="onGetCode" />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button
|
||||
@click="submit"
|
||||
:loading="loading"
|
||||
@@ -55,7 +62,7 @@
|
||||
import { authorizationService } from '/nerv-base/store/modules/authorization-service';
|
||||
import { Cookies } from '/nerv-lib/util/cookie';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { http } from '/nerv-lib/util';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserLogin',
|
||||
setup() {
|
||||
@@ -69,20 +76,37 @@
|
||||
const router = useRouter();
|
||||
const title = ref<string>('');
|
||||
const initUrl = ref('');
|
||||
const verifyCode = ref('');
|
||||
const code = ref();
|
||||
title.value = '账号登录';
|
||||
// title.value = appConfig.title ? appConfig.title : '账号登录';
|
||||
const loading = ref<boolean>(false);
|
||||
const configStore = appConfigStore();
|
||||
const { getThemeConfig: themeConfig } = storeToRefs(configStore);
|
||||
console.log(configStore);
|
||||
|
||||
const { getThemeConfig: themeConfig, projectName } = storeToRefs(configStore);
|
||||
|
||||
const useAuthorization = authorizationService();
|
||||
const submit = (): void => {
|
||||
if (password.value === '') {
|
||||
errorMsg.value = '请输入密码';
|
||||
errorShow.value = true;
|
||||
return;
|
||||
}
|
||||
if (userName.value === '') {
|
||||
errorMsg.value = '请输入账号';
|
||||
errorShow.value = true;
|
||||
return;
|
||||
}
|
||||
if (!code.value) {
|
||||
errorMsg.value = '请输入验证码';
|
||||
errorShow.value = true;
|
||||
return;
|
||||
}
|
||||
if (code.value.toLocaleLowerCase() !== verifyCode.value.toLocaleLowerCase()) {
|
||||
errorMsg.value = '请输入正确的验证码';
|
||||
errorShow.value = true;
|
||||
return;
|
||||
}
|
||||
if (userName.value !== '' && password.value !== '') {
|
||||
errorShow.value = false;
|
||||
@@ -159,7 +183,13 @@
|
||||
? (url.value = 'src/assetimg/background.jpg')
|
||||
: (url.value = 'src/assetimg/background.png');
|
||||
};
|
||||
const onGetCode = (res) => {
|
||||
verifyCode.value = res;
|
||||
};
|
||||
return {
|
||||
projectName,
|
||||
onGetCode,
|
||||
code,
|
||||
title,
|
||||
themeConfig,
|
||||
url,
|
||||
@@ -173,6 +203,7 @@
|
||||
checkoutLogo,
|
||||
checkoutBg,
|
||||
errorShow,
|
||||
configStore,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -219,12 +250,12 @@
|
||||
// border-radius: 50%;
|
||||
}
|
||||
|
||||
.lg_card .loginIcon {
|
||||
color: @primary-color !important;
|
||||
}
|
||||
// .lg_card .loginIcon {
|
||||
// color: @primary-color !important;
|
||||
// }
|
||||
.ant-layout-header {
|
||||
min-height: 64px;
|
||||
background: #fff !important;
|
||||
// background: #fff !important;
|
||||
}
|
||||
|
||||
.ant-layout-content {
|
||||
@@ -234,7 +265,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-right: 100px;
|
||||
padding-right: 10%;
|
||||
}
|
||||
|
||||
.ant-layout-footer {
|
||||
@@ -250,32 +281,56 @@
|
||||
}
|
||||
|
||||
.lg_card {
|
||||
width: 385px;
|
||||
height: 409px;
|
||||
width: 500px;
|
||||
height: 570px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 2px 0 rgb(0 0 0 / 5%), 0 0 18px 0 rgb(0 0 0 / 8%);
|
||||
border-radius: 6px;
|
||||
margin: 0 30px 0 30px;
|
||||
padding: 35px;
|
||||
padding: 50px;
|
||||
border-radius: 10px;
|
||||
// background: ;
|
||||
|
||||
border: 3px solid rgba(255, 255, 255, 1);
|
||||
|
||||
.lg_card_tip {
|
||||
color: #a7b6c9;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.loginInfo {
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
margin-bottom: 31px;
|
||||
:deep(.ant-input) {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
:deep(.ant-input-group) {
|
||||
height: 100%;
|
||||
.ant-input-affix-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
.ant-input-group-addon {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lg_card .lg_card_title {
|
||||
height: 28px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #172e3d;
|
||||
line-height: 28px;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0px;
|
||||
line-height: 42px;
|
||||
color: @primary-color;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.lg_card_error {
|
||||
text-align: left;
|
||||
color: #e4393c;
|
||||
font-size: 14px;
|
||||
color: #e00e12;
|
||||
font-size: 16px;
|
||||
}
|
||||
.lg_card .loginIcon {
|
||||
color: @primary-color !important;
|
||||
|
Reference in New Issue
Block a user