Files
opencatd-open/frontend/src/views/Login.vue
Sakurasan 15f17f4e8d frontend
2025-04-16 18:14:53 +08:00

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>