new feature: capture

This commit is contained in:
KUN1007 2023-06-15 22:11:55 +08:00
parent b380d37d8f
commit 48a613233f
8 changed files with 268 additions and 19 deletions

View file

@ -1,5 +1,9 @@
<!-- App -->
<script setup lang="ts">
//
import Alert from '@/components/KUNGalgameAlert/Alert.vue'
import Info from '@/components/KUNGalgameAlert/Info.vue'
import { onBeforeMount } from 'vue'
// store
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
@ -21,6 +25,11 @@ onBeforeMount(() => {
</script>
<template>
<!-- 全局警告组件 -->
<Alert />
<!-- 全局通知组件 -->
<Info />
<RouterView />
</template>

View file

@ -2,15 +2,12 @@
import { useRouter } from 'vue-router'
const router = useRouter()
// Vue template '<' msg
const msg = `< `
</script>
<template>
<!-- 返回主页 -->
<div class="return" @click="router.back()">
<span>{{ msg + $t('back.back') }}</span>
<span>{{ `< ${$t('back.back')}` }}</span>
</div>
</template>
@ -23,7 +20,8 @@ const msg = `< `
color: var(--kungalgame-font-color-0);
cursor: pointer;
}
.return:hover {
color: var(--kungalgame-blue-4)
color: var(--kungalgame-blue-4);
}
</style>

View file

@ -0,0 +1,193 @@
<template>
<Teleport to="body" :disabled="props.isShowValidate">
<Transition name="capture">
<div class="mask" v-if="props.isShowValidate">
<div class="validate">
<div class="text">
<div
class="character"
v-for="(char, index) in characters"
:key="index"
:style="char.style"
@click="handleCharacterClick(char)"
>
<text>{{ char.value }}</text>
</div>
</div>
<div class="msg">
<div v-if="isVerified" class="pass">验证通过</div>
<div v-else-if="errorCount > 0" class="error">
点击错误请重新点击
</div>
<div class="hint" v-if="!isVerified">
请按顺序点击以下字符:
{{ characters.map((char) => char.value).join(' ') }}
</div>
<div class="refresh" @click="resetCharacters">点击刷新</div>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const containerRef = ref<HTMLDivElement | null>(null)
const characters = ref(generateRandomCharacters())
const clickedCharacters = ref('')
const isVerified = ref(false)
const errorCount = ref(0)
const props = defineProps(['isShowValidate'])
const emits = defineEmits(['validate'])
interface Character {
value: string
style: {
top: string
left: string
}
}
emits('validate', false)
// 26
function generateRandomCharacters(): Character[] {
const chars = '小只可爱软萌鲲'
const randomChars: Character[] = []
// Set
const charSet = new Set<string>()
while (charSet.size < 3) {
const randomIndex = Math.floor(Math.random() * chars.length)
const char = chars[randomIndex]
//
if (!charSet.has(char)) {
charSet.add(char)
const character: Character = {
value: char,
style: {
top: `${getRandomCoordinate()}px`,
left: `${getRandomCoordinate()}px`,
},
}
randomChars.push(character)
}
}
return randomChars
}
function getRandomCoordinate(): number {
const minCoordinate = 50
const maxCoordinate = 150
return (
Math.floor(Math.random() * (maxCoordinate - minCoordinate + 1)) +
minCoordinate
)
}
const handleCharacterClick = (char: Character): Promise<boolean> => {
return new Promise<boolean>((resolve, reject) => {
clickedCharacters.value += char.value
if (
clickedCharacters.value.length === 3 &&
clickedCharacters.value === characters.value.map((c) => c.value).join('')
) {
isVerified.value = true
emits('validate', true)
} else if (clickedCharacters.value.length >= 3) {
errorCount.value += 1
resetCharacters()
emits('validate', false)
}
})
}
function resetCharacters() {
characters.value = generateRandomCharacters()
clickedCharacters.value = ''
isVerified.value = false
}
onMounted(() => {
const container = containerRef.value
if (container) {
container.style.width = '300px'
container.style.height = '300px'
}
})
</script>
<style lang="scss" scoped>
.mask {
position: fixed;
z-index: 9999;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
transition: opacity 0.3s ease;
color: var(--kungalgame-font-color-3);
}
.validate {
height: 100%;
width: 300px;
height: 300px;
margin: auto;
padding: 20px 30px;
background-color: var(--kungalgame-trans-white-2);
border: 1px solid var(--kungalgame-blue-1);
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
transition: all 0.3s ease;
display: flex;
}
.text {
cursor: pointer;
position: absolute;
font-size: 30px;
transform: translateX(-30px);
}
.character {
position: relative;
}
.refresh {
width: 64px;
color: var(--kungalgame-blue-4);
margin-top: 20px;
cursor: pointer;
border-bottom: 2px solid var(--kungalgame-trans-white-9);
&:hover {
border-bottom: 2px solid var(--kungalgame-blue-4);
}
}
.msg {
position: absolute;
}
.capture-enter-from {
opacity: 0;
}
.capture-leave-to {
opacity: 0;
}
.capture-enter-from .validate,
.capture-leave-to .validate {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
</style>

View file

@ -1,8 +1,5 @@
<!-- 先放一个 Layout 在这里后面应该用得到 -->
<script setup lang="ts">
//
import Alert from '@/components/KUNGalgameAlert/Alert.vue'
import Info from '@/components/KUNGalgameAlert/Info.vue'
//
import 'animate.css'
@ -12,12 +9,6 @@ import { currBackground } from '@/hooks/useBackgroundPicture'
<template>
<!-- #default v-slot 的简写route 就是路由Component 是一个 v-node -->
<div class="app" :style="{ backgroundImage: currBackground }">
<!-- 全局警告组件 -->
<Alert />
<!-- 全局通知组件 -->
<Info />
<!-- <RouterView /> -->
<RouterView #default="{ route, Component }">
<transition

View file

@ -23,8 +23,6 @@ export const useKUNGalgamerStore = defineStore({
this.token = token
},
login(loginData: LoginData): Promise<LoginResponseData> {
console.log(1)
return new Promise((resolve, reject) => {
postLoginDataApi({
username: loginData.username,

30
src/utils/validate.ts Normal file
View file

@ -0,0 +1,30 @@
// 正则表达式匹配合法 URL
export const isValidURL = (url: string) => {
const regex =
/^(https?|http):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return regex.test(url)
}
// 正则表达式匹配合法邮箱
export const validateEmail = (email: string) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return regex.test(email)
}
// 匹配 1 到 17 位数 中文、英文、数字、下划线、~ 的用户名
export const isValidName = (name: string) => {
const regex = /^[\u4e00-\u9fa5a-zA-Z0-9_~]{1,17}$/
return regex.test(name)
}
// 正则表达式匹配 6 到 17 位数的密码,必须包含至少一个英文字符和一个数字,可以选择性的包含 \w!@#$%^&*()-+= 特殊字符
export const isValidPassword = (pwd: string) => {
const regex = /^(?=.*[a-zA-Z])(?=.*[0-9])[\w!@#$%^&*()-+=]{6,17}$/
return regex.test(pwd)
}
// 正则表达式匹配 7 位英文或数字,邮箱验证码
export const isValidMailConfirmCode = (code: string) => {
const regex = /^[a-zA-Z0-9]{7}$/
return regex.test(code)
}

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ref, reactive, onMounted, watch } from 'vue'
import KUNGalgameFooter from '@/components/KUNGalgameFooter.vue'
@ -144,7 +144,6 @@ const handleClickRegister = () => {
top: 0;
transition: transform 0.6s ease-in-out;
width: 50%;
z-index: 100;
}
.overlay {

View file

@ -5,6 +5,9 @@ import { useRouter } from 'vue-router'
// 使
import { useKUNGalgameMessageStore } from '@/store/modules/message'
//
import Capture from '@/components/capture/Capture.vue'
const router = useRouter()
const info = useKUNGalgameMessageStore()
@ -24,11 +27,32 @@ const handleLogin = () => {
}
})
}
//
const isFirstLoad = ref(false)
//
const isShowValidate = ref(false)
const handleVerify = async (result: boolean) => {
//
if (isFirstLoad.value) {
//
isShowValidate.value = false
info.info('验证通过')
} else {
isFirstLoad.value = true
}
}
const capture = () => {
isShowValidate.value = true
}
</script>
<template>
<!-- 登陆 -->
<div class="login">
<Capture @validate="handleVerify" :isShowValidate="isShowValidate" />
<form class="form" @submit.prevent="handleLogin">
<h2 class="title">登陆</h2>
<input
@ -44,6 +68,7 @@ const handleLogin = () => {
class="input"
/>
<span class="forget">忘记密码? 点击发送重置邮件</span>
<span @click="capture" class="capture">点击进行人机身份验证</span>
<button class="btn" type="submit">登陆</button>
</form>
</div>
@ -103,6 +128,12 @@ const handleLogin = () => {
margin: 20px 0;
}
.capture {
color: var(--kungalgame-font-color-2);
cursor: pointer;
font-size: 15px;
}
.btn {
position: absolute;
bottom: 7%;