124 lines
3.3 KiB
Vue
124 lines
3.3 KiB
Vue
<script setup>
|
|
import { ref, watch, computed, useTemplateRef } from 'vue';
|
|
import CAPTCHA from '../CAPTCHA.vue';
|
|
|
|
const emit = defineEmits(['login-submit']);
|
|
|
|
const username = ref('');
|
|
const password = ref('');
|
|
|
|
const captcha = useTemplateRef('captcha');
|
|
const captchaResponse = ref(null);
|
|
const captchaVerified = computed(() => {
|
|
return captchaResponse.value !== null;
|
|
});
|
|
|
|
const usernameError = ref('');
|
|
const passwordError = ref('');
|
|
|
|
function validateUsername() {
|
|
if (!username.value) {
|
|
usernameError.value = '使用者名稱為必填。';
|
|
} else if (username.value.length < 3) {
|
|
usernameError.value = '使用者名稱長度必須至少三個字元。';
|
|
} else {
|
|
usernameError.value = '';
|
|
}
|
|
}
|
|
|
|
function validatePassword() {
|
|
if (!password.value) {
|
|
passwordError.value = '密碼為必填。';
|
|
} else if (password.value.length < 8) {
|
|
passwordError.value = '密碼長度必須至少八個字元。';
|
|
} else {
|
|
passwordError.value = '';
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => username.value,
|
|
() => {
|
|
validateUsername();
|
|
}
|
|
);
|
|
|
|
watch(
|
|
() => password.value,
|
|
() => {
|
|
validatePassword();
|
|
}
|
|
);
|
|
|
|
const handleCaptchaVerified = (response) => {
|
|
captchaResponse.value = response;
|
|
};
|
|
|
|
const submit = () => {
|
|
validateUsername();
|
|
validatePassword();
|
|
|
|
if (usernameError.value || passwordError.value || !captchaVerified) {
|
|
return;
|
|
}
|
|
|
|
emit('login-submit', { username: username.value, password: password.value, captchaResponse: captchaResponse.value });
|
|
captcha.value.reset();
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<form @submit.prevent="submit">
|
|
<div class="ts-box ts-content">
|
|
<div class="ts-header is-large is-center-aligned has-bottom-spaced">登入</div>
|
|
<div class="ts-wrap is-vertical">
|
|
<div class="ts-control is-wide">
|
|
<div class="label">使用者名稱</div>
|
|
<div class="content is-fluid">
|
|
<div class="ts-input" :class="{'is-negative': usernameError}">
|
|
<input name="username" type="text" placeholder="使用者名稱" v-model="username" />
|
|
</div>
|
|
<div class="ts-text is-small is-negative" v-if="usernameError">{{ usernameError }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="ts-control is-wide">
|
|
<div class="label">密碼</div>
|
|
<div class="content is-fluid">
|
|
<div class="ts-input" :class="{'is-negative': passwordError}">
|
|
<input name="password" type="password" placeholder="密碼" v-model="password" />
|
|
</div>
|
|
<div class="ts-text is-small is-negative" v-if="passwordError">{{ passwordError }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<CAPTCHA
|
|
ref="captcha"
|
|
:hcaptchaSitekey="$hcaptchaSitekey"
|
|
:recaptchaSitekey="$recaptchaSitekey"
|
|
:turnstileSitekey="$turnstileSitekey"
|
|
@captchaVerified="handleCaptchaVerified"
|
|
/>
|
|
<div class="ts-wrap has-top-spaced is-end-aligned">
|
|
<button class="ts-button is-fluid" type="submit" :class="{
|
|
'is-disabled': username === '' || password === '' || usernameError !== '' || passwordError !== '' || !captchaVerified
|
|
}">登入</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.ts-error {
|
|
color: red;
|
|
font-size: 0.8em;
|
|
}
|
|
|
|
.ts-control {
|
|
--label-width: 100px;
|
|
}
|
|
|
|
.ts-control .content.is-fluid {
|
|
max-width: 100%;
|
|
}
|
|
</style>
|