feat(components): 添加图片验证码

This commit is contained in:
Soybean
2021-10-18 16:45:35 +08:00
parent 9097fa3866
commit 336c7766f9
17 changed files with 336 additions and 270 deletions

View File

@ -6,23 +6,23 @@
/>
<path
d="M158.86.3H2C0,.31.27,0,.27,2q0,78.42,0,156.85c-.07-.05-.25.12-.24-.12s0-.64,0-1Q0,79.46,0,1.14C0,.24.2,0,1.1,0l156.68,0C158.13.08,158.59-.2,158.86.3Z"
style="fill: #fefefe"
:style="{ fill: foreground }"
/>
<path
d="M93.65,51.52a68.65,68.65,0,0,1-6.47,28.81,1.72,1.72,0,0,0,.19,2c6.08,8.28,13.58,14.79,23.19,18.69a46.22,46.22,0,0,0,17.15,3.39,28.87,28.87,0,0,0,3.34-.25,6.2,6.2,0,0,1,7,5.12,6.07,6.07,0,0,1-5.15,7.14,50.39,50.39,0,0,1-18.06-1c-15.85-3.66-28-12.75-37.44-25.7a2.15,2.15,0,0,0-2.23-1.09C61.17,90,49,95.06,39.67,105.84a38.47,38.47,0,0,0-6.23,9.74A6.21,6.21,0,0,1,25.27,119,6.14,6.14,0,0,1,22,110.8a49.31,49.31,0,0,1,9.63-14.62c10.56-11.44,23.8-17.54,39.09-19.54a13.93,13.93,0,0,1,2.84-.34c1.61.14,2.18-.73,2.73-2A54.38,54.38,0,0,0,81.12,51a44,44,0,0,0-8-25,6.11,6.11,0,0,1-.65-6.46A6,6,0,0,1,77.75,16a6.34,6.34,0,0,1,5.66,3,53.61,53.61,0,0,1,7.17,14.28A59.33,59.33,0,0,1,93.65,51.52Z"
style="fill: #fefefe"
:style="{ fill: foreground }"
/>
<path
d="M46.92,118.63a6,6,0,0,1,1.35-3.88,37.89,37.89,0,0,1,22.5-14,6.08,6.08,0,0,1,6.65,2.47,6.18,6.18,0,0,1-3.84,9.63,26.09,26.09,0,0,0-15.71,9.77,6.2,6.2,0,0,1-10.95-4Z"
style="fill: #fefefe"
:style="{ fill: foreground }"
/>
<path
d="M124.3,92.8a34.66,34.66,0,0,1-9.82-2.48A35.46,35.46,0,0,1,99.83,79.87a6.19,6.19,0,0,1,2.84-9.93,5.79,5.79,0,0,1,6.44,1.73,26.79,26.79,0,0,0,16.51,8.85,6,6,0,0,1,5,5.54,6.21,6.21,0,0,1-4.29,6.46A6.55,6.55,0,0,1,124.3,92.8Z"
style="fill: #fefefe"
:style="{ fill: foreground }"
/>
<path
d="M69.32,53.27a33.46,33.46,0,0,1-2.27,12.52,6.21,6.21,0,0,1-10.94,1,6.09,6.09,0,0,1-.65-5.4,26,26,0,0,0-.53-18.25,6.21,6.21,0,0,1,11.49-4.72A40.24,40.24,0,0,1,69.32,53.27Z"
style="fill: #fefefe"
:style="{ fill: foreground }"
/>
</svg>
</template>
@ -32,6 +32,10 @@ defineProps({
color: {
type: String,
default: '#409EFF'
},
foreground: {
type: String,
default: '#fefefe00'
}
});
</script>

View File

@ -3,7 +3,7 @@
<path
d="M0,158.86Q0,80,0,1.1C0,.2.2,0,1.1,0Q79.44,0,157.78,0c.9,0,1.1.2,1.1,1.1q0,78.35,0,156.68c0,.9-.2,1.1-1.1,1.1Q78.9,158.83,0,158.86Z"
transform="translate(0)"
style="fill: #fefefe"
:style="{ fill: foreground }"
/>
<path
d="M81.28,55.9c-.1-11.67-2.93-22.55-9.37-32.38-1-1.5-2.14-2.86-2.5-4.71a8.1,8.1,0,0,1,4-8.61,7.89,7.89,0,0,1,9.3,1.23,36,36,0,0,1,5.9,8.83,75.18,75.18,0,0,1,8.44,28.58,83.21,83.21,0,0,1-5.23,36.74c-.91,2.47-1.91,4.9-3,7.28a1.2,1.2,0,0,0,0,1.41c9.58,13.3,21.76,23,37.85,27.24a54.35,54.35,0,0,0,19.68,1.57,7.72,7.72,0,0,1,8.36,6.9,7.9,7.9,0,0,1-6.7,9,64.74,64.74,0,0,1-23-1.33,77.68,77.68,0,0,1-36.93-19.88,93.64,93.64,0,0,1-11.91-13.71A2.18,2.18,0,0,0,73.87,103a72.75,72.75,0,0,0-27.38,7.55c-11.6,6-20.67,14.58-26.4,26.45a10.13,10.13,0,0,1-3.7,4.7A8,8,0,0,1,7.2,141a7.86,7.86,0,0,1-2.36-9.28,60.32,60.32,0,0,1,8.72-14.52c12.2-15.43,28.21-24.59,47.32-28.57A85.08,85.08,0,0,1,73.07,87a1.22,1.22,0,0,0,1.18-.8A76.06,76.06,0,0,0,80.78,63.9,57.87,57.87,0,0,0,81.28,55.9Z"
@ -33,6 +33,10 @@ defineProps({
color: {
type: String,
default: '#409EFF'
},
foreground: {
type: String,
default: '#fefefe00'
}
});
</script>

View File

@ -8,7 +8,7 @@
</template>
<script lang="ts" setup>
import { NDropdown } from 'naive-ui';
import { NDropdown, useDialog } from 'naive-ui';
import { UserAvatar, Logout } from '@vicons/carbon';
import { dynamicIconRender, resetAuthStorage } from '@/utils';
import { HoverContainer } from '@/components';
@ -16,6 +16,8 @@ import avatar from '@/assets/svg/avatar/avatar01.svg';
type DropdownKey = 'user-center' | 'logout';
const dialog = useDialog();
const options = [
{
label: '用户中心',
@ -36,8 +38,16 @@ const options = [
function handleDropdown(optionKey: string) {
const key = optionKey as DropdownKey;
if (key === 'logout') {
resetAuthStorage();
window.location.reload();
dialog.info({
title: '提示',
content: '您确定要退出登录吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
resetAuthStorage();
window.location.reload();
}
});
}
}
</script>

View File

@ -31,7 +31,7 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { ref, computed, watch } from 'vue';
import type { VNodeChild } from 'vue';
import { NScrollbar } from 'naive-ui';
import { useRoute } from 'vue-router';
@ -80,6 +80,13 @@ function handleMouseLeaveMenu() {
activeParentRouteName.value = getActiveRouteName();
hideDrawer();
}
watch(
() => route.name,
() => {
activeParentRouteName.value = getActiveRouteName();
}
);
</script>
<style scoped>
.mix-menu-width {

View File

@ -1,11 +1,11 @@
<template>
<div
class="border-2px rounded-6px cursor-pointer hover:g_border-primary"
:class="[checked ? 'g_border-primary' : 'border-transparent']"
class="nav-type border-2px rounded-6px cursor-pointer"
:class="[checked ? 'border-primary' : 'border-transparent']"
>
<n-tooltip :placement="activeConfig.placement" trigger="hover">
<template #trigger>
<div class="nav-type relative w-56px h-48px bg-[#fff] rounded-4px overflow-hidden">
<div class="nav-type-main relative w-56px h-48px bg-[#fff] rounded-4px overflow-hidden">
<div class="absolute-lt bg-[#273352]" :class="`${activeConfig.menuClass}`"></div>
<div class="absolute-rb bg-[#f0f2f5]" :class="`${activeConfig.mainClass}`"></div>
</div>
@ -31,6 +31,10 @@ const props = defineProps({
checked: {
type: Boolean,
default: false
},
primaryColor: {
type: String,
default: '#409EFF'
}
});
@ -44,7 +48,13 @@ const config = new Map<NavMode, { placement: FollowerPlacement; menuClass: strin
const activeConfig = computed(() => config.get(props.mode)!);
</script>
<style scoped>
.nav-type {
.border-primary {
border-color: v-bind(primaryColor);
}
.nav-type:hover {
border-color: v-bind(primaryColor);
}
.nav-type-main {
box-shadow: 0 1px 2.5px rgba(0, 0, 0, 0.18);
}
</style>

View File

@ -6,6 +6,7 @@
:key="item.mode"
:mode="item.mode"
:checked="theme.navStyle.mode === item.mode"
:primary-color="theme.themeColor"
@click="setNavMode(item.mode)"
/>
</n-space>

View File

@ -3,7 +3,7 @@ import App from './App.vue';
import AppProvider from './AppProvider.vue';
import { setupStore } from './store';
import { setupRouter } from './router';
import { setupWindicssDarkMode } from './plugins';
import { setupWindicssDarkMode, setupMakeitCaptcha } from './plugins';
import 'virtual:windi.css';
import './styles/css/global.css';
@ -11,6 +11,9 @@ async function setupApp() {
const appProvider = createApp(AppProvider);
const app = createApp(App);
// 图片验证码插件
setupMakeitCaptcha(app);
// 挂载全局状态
setupStore(app);

View File

@ -1,3 +1,4 @@
import setupWindicssDarkMode from './dark-mode';
import setupMakeitCaptcha from './makeit-captcha';
export { setupWindicssDarkMode };
export { setupWindicssDarkMode, setupMakeitCaptcha };

View File

@ -0,0 +1,7 @@
import type { App } from 'vue';
import MakeitCaptcha from 'makeit-captcha';
import 'makeit-captcha/dist/captcha.min.css';
export default function setupMakeitCaptcha(app: App) {
app.use(MakeitCaptcha);
}

View File

@ -40,18 +40,23 @@ const themeStore = defineStore({
primaryColor,
primaryColorHover,
primaryColorPressed,
primaryColorSuppl: primaryColor,
infoColor,
infoColorHover,
infoColorPressed,
infoColorSuppl: infoColor,
successColor,
successColorHover,
successColorPressed,
successColorSuppl: infoColor,
warningColor,
warningColorHover,
warningColorPressed,
warningColorSuppl: warningColor,
errorColor,
errorColorHover,
errorColorPressed
errorColorPressed,
errorColorSuppl: errorColor
},
LoadingBar: {
colorLoading

5
src/typings/makeit-captcha.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module 'makeit-captcha' {
import _default from 'makeit-captcha/es/src';
export default _default;
}

View File

@ -7,6 +7,11 @@
<n-form-item path="pwd">
<n-input v-model:value="model.pwd" placeholder="密码" />
</n-form-item>
<n-form-item path="isCaptcha">
<div class="w-full">
<mi-captcha :theme-color="theme.themeColor" :logo="logo" @success="handleCaptcha" />
</div>
</n-form-item>
<n-space :vertical="true" size="large">
<div class="flex-y-center justify-between">
<n-checkbox v-model:checked="rememberMe">记住我</n-checkbox>
@ -31,12 +36,15 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { NForm, NFormItem, NInput, NSpace, NCheckbox, NButton, useNotification } from 'naive-ui';
import type { FormInst } from 'naive-ui';
import type { FormInst, FormRules } from 'naive-ui';
import { EnumLoginModule } from '@/enum';
import { useThemeStore } from '@/store';
import { useRouterChange, useRouteQuery } from '@/hooks';
import { setToken, toLoginRedirectUrl } from '@/utils';
import { OtherLogin } from './components';
import logo from '@/assets/img/common/logo.png';
const theme = useThemeStore();
const { toHome, toCurrentLogin } = useRouterChange();
const { loginRedirectUrl } = useRouteQuery();
const notification = useNotification();
@ -44,9 +52,10 @@ const notification = useNotification();
const formRef = ref<(HTMLElement & FormInst) | null>(null);
const model = reactive({
phone: '15100000000',
pwd: '123456'
pwd: '123456',
isCaptcha: false
});
const rules = {
const rules: FormRules = {
phone: {
required: true,
trigger: ['blur', 'input'],
@ -56,10 +65,21 @@ const rules = {
required: true,
trigger: ['blur', 'input'],
message: '请输入密码'
},
isCaptcha: {
required: true,
type: 'boolean',
trigger: 'change',
message: '请点击按钮进行验证码校验',
validator: (_, value) => value === true
}
};
const rememberMe = ref(false);
function handleCaptcha() {
model.isCaptcha = true;
}
function handleSubmit(e: MouseEvent) {
if (!formRef.value) return;
e.preventDefault();
@ -75,7 +95,8 @@ function handleSubmit(e: MouseEvent) {
notification.success({
title: '登录成功!',
content: '欢迎回来Soybean!'
content: '欢迎回来Soybean!',
duration: 5000
});
}
});