182 lines
8.1 KiB
Vue
182 lines
8.1 KiB
Vue
<template>
|
|
<div class="min-h-screen flex items-center justify-center p-4">
|
|
<div class="card w-full max-w-md bg-base-100 shadow-xl">
|
|
<div class="card-body p-4 sm:p-6">
|
|
<img src="../assets/openteam.webp" alt="Company Logo" class="h-32 w-auto mx-auto mb-0 pb-0 select-none hover:cursor-pointer"
|
|
@click="$router.push('/')" />
|
|
|
|
<h2 class="card-title text-md sm:text-xl mb-6 justify-center flex">
|
|
Log in to Dashboard
|
|
</h2>
|
|
|
|
<form @submit.prevent="handleLogin">
|
|
<div class="form-control mb-4">
|
|
<label class="label" for="username">
|
|
<span class="label-text">Account</span>
|
|
</label>
|
|
<input type="text" id="username" placeholder="username/email" class="input input-bordered w-full input-sm"
|
|
v-model="user.username" required />
|
|
</div>
|
|
|
|
<div class="form-control mb-4">
|
|
<label class="label" for="password">
|
|
<span class="label-text">Password</span>
|
|
<a href="#" class="label-text-alt link link-hover link-primary text-sm">
|
|
Forgot password?
|
|
</a>
|
|
</label>
|
|
<input type="password" id="password" placeholder="••••••••" class="input input-bordered w-full input-sm"
|
|
v-model="user.password" minlength="4" required />
|
|
</div>
|
|
|
|
<div class="form-control mb-6">
|
|
<label class="label cursor-pointer justify-start gap-2">
|
|
<input type="checkbox" v-model="user.rember" class="checkbox checkbox-primary checkbox-sm" />
|
|
<span class="label-text text-sm">Remember me</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-control mb-4">
|
|
<button type="submit" class="btn btn-outline w-full btn-sm">
|
|
Log In
|
|
</button>
|
|
</div>
|
|
<div v-if="error" role="alert" class="alert shadow-lg bg-rose-100">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none"
|
|
viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<div>
|
|
<span>{{ error }}</span>
|
|
</div>
|
|
<button class="btn btn-sm" @click="error = null">X</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="divider my-2 text-sm">OR</div>
|
|
|
|
<div class="form-control mb-6">
|
|
<button @click="handlePasskeyLogin" class="btn btn-outline w-full btn-sm" :disabled="!supportWebAuth">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"
|
|
viewBox="0 0 24 24">
|
|
<path fill="currentColor"
|
|
d="M7 13.23q-.517 0-.874-.356T5.769 12t.357-.874t.874-.357t.874.357t.357.874t-.357.874t-.874.357M7 17q-2.077 0-3.538-1.461T2 12t1.462-3.538T7 7q1.54 0 2.778.835q1.238.834 1.807 2.165h9.204l2 2l-3.193 3.154l-1.712-1.288l-1.807 1.326L14.298 14h-2.713q-.57 1.312-1.807 2.156T7 17m0-1q1.477 0 2.52-.889T10.856 13h3.76l1.43.967l1.858-1.333l1.621 1.222L21.381 12l-1-1h-9.525q-.292-1.223-1.336-2.111T7 8Q5.35 8 4.175 9.175T3 12t1.175 2.825T7 16" />
|
|
</svg>
|
|
Sign in with Passkey
|
|
</button>
|
|
</div>
|
|
<div class="hidden flex flex-col sm:flex-row gap-3 w-full">
|
|
<button @click="handleGithubLogin" class="btn btn-outline flex-1 btn-sm">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-github mr-1"
|
|
viewBox="0 0 16 16">
|
|
<path
|
|
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8" />
|
|
</svg>
|
|
|
|
</button>
|
|
<button @click="handleGoogleLogin" class="btn btn-outline flex-1 btn-sm">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-google mr-1"
|
|
viewBox="0 0 16 16">
|
|
<path
|
|
d="M15.545 6.558a9.4 9.4 0 0 1 .139 1.626c0 2.434-.87 4.492-2.384 5.885h.002C11.978 15.292 10.158 16 8 16A8 8 0 1 1 8 0a7.7 7.7 0 0 1 5.352 2.082l-2.284 2.284A4.35 4.35 0 0 0 8 3.166c-2.087 0-3.86 1.408-4.492 3.304a4.8 4.8 0 0 0 0 3.063h.003c.635 1.896 2.405 3.301 4.492 3.301 1.078 0 2.004-.276 2.722-.764h-.003a3.7 3.7 0 0 0 1.599-2.431H8v-3.08z" />
|
|
</svg>
|
|
|
|
</button>
|
|
<button @click="handleTelegramLogin" class="btn btn-outline flex-1 btn-sm">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
|
class="bi bi-telegram mr-1" viewBox="0 0 16 16">
|
|
<path
|
|
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.287 5.906q-1.168.486-4.666 2.01-.567.225-.595.442c-.03.243.275.339.69.47l.175.055c.408.133.958.288 1.243.294q.39.01.868-.32 3.269-2.206 3.374-2.23c.05-.012.12-.026.166.016s.042.12.037.141c-.03.129-1.227 1.241-1.846 1.817-.193.18-.33.307-.358.336a8 8 0 0 1-.188.186c-.38.366-.664.64.015 1.088.327.216.589.393.85.571.284.194.568.387.936.629q.14.092.27.187c.331.236.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.4 1.4 0 0 0-.013-.315.34.34 0 0 0-.114-.217.53.53 0 0 0-.31-.093c-.3.005-.763.166-2.984 1.09" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="text-center mt-6 text-sm">
|
|
Don't have an account?
|
|
<router-link to="/signup" class="link link-primary link-hover">
|
|
Sign up
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, inject, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useAuthStore } from '@/stores/auth';
|
|
import { useWebAuthStore } from '@/stores/webauth';
|
|
// import request from '@/utils/request';
|
|
|
|
const router = useRouter()
|
|
const authStore = useAuthStore();
|
|
const webauthStore = useWebAuthStore();
|
|
const { setToast } = inject('toast');
|
|
|
|
const error = ref(null)
|
|
const user = reactive({
|
|
username: localStorage.getItem('account') || '',
|
|
password: localStorage.getItem('password') || '',
|
|
rember: localStorage.getItem('rember') || false,
|
|
})
|
|
|
|
const supportWebAuth = ref(false);
|
|
onMounted(() => {
|
|
if (localStorage.getItem('token')) {
|
|
router.push('/dashboard')
|
|
}
|
|
supportWebAuth.value = !!window.PublicKeyCredential;
|
|
})
|
|
|
|
|
|
const handleLogin = async () => {
|
|
error.value = null;
|
|
try {
|
|
const response = await authStore.login({ username: user.username, password: user.password });
|
|
if (response.status === 200) {
|
|
if (user.rember) {
|
|
localStorage.setItem('account', user.username);
|
|
localStorage.setItem('password', user.password);
|
|
localStorage.setItem('rember', user.rember);
|
|
} else {
|
|
localStorage.removeItem('account');
|
|
localStorage.removeItem('password');
|
|
localStorage.removeItem('rember');
|
|
}
|
|
setToast('登录成功', 'success');
|
|
router.push('/dashboard');
|
|
}
|
|
} catch (err) {
|
|
console.error('Login error:', err);
|
|
error.value = err
|
|
}
|
|
}
|
|
|
|
const handlePasskeyLogin = async () => {
|
|
error.value = null;
|
|
try {
|
|
const res = await webauthStore.loginPasskey();
|
|
if (!!res.code && res.code === 200) {
|
|
setToast('登录成功', 'success');
|
|
router.push('/dashboard');
|
|
}
|
|
} catch (err) {
|
|
console.error('Passkey login error:', err);
|
|
error.value = err
|
|
}
|
|
};
|
|
|
|
const handleGithubLogin = () => {
|
|
alert('GitHub login is not yet implemented.');
|
|
};
|
|
|
|
const handleGoogleLogin = () => {
|
|
alert('Google login is not yet implemented.');
|
|
};
|
|
|
|
const handleTelegramLogin = () => {
|
|
alert('Telegram login is not yet implemented.');
|
|
};
|
|
</script> |