372 lines
12 KiB
Vue
372 lines
12 KiB
Vue
<template>
|
||
<div
|
||
class="bg-white dark:bg-dark-primary-800 flex items-center justify-center p-4"
|
||
>
|
||
<!-- Container -->
|
||
<div class="w-full max-w-sm space-y-6">
|
||
<!-- Header -->
|
||
<div class="text-center space-y-1">
|
||
<div
|
||
class="w-12 h-12 mx-auto mb-3 dark:bg-primary-800 rounded-full flex items-center justify-center"
|
||
>
|
||
<!-- <span class="text-white text-lg font-bold">پ</span> -->
|
||
<img :src="useSystemTheme.logo.value" alt="" class="h-9 w-9" />
|
||
</div>
|
||
<h1 class="text-xl font-medium text-primary-900 dark:text-white">
|
||
ورود به حساب
|
||
</h1>
|
||
<!-- <p class="text-xs text-primary-400 dark:text-primary-400">
|
||
زیستبوم پژوهشگران
|
||
</p> -->
|
||
</div>
|
||
|
||
<!-- Form -->
|
||
<div class="space-y-3">
|
||
<!-- Username -->
|
||
<div>
|
||
<label
|
||
class="block text-xs font-medium text-primary-700 dark:text-primary-300 mb-1"
|
||
>نام کاربری یا ایمیل</label
|
||
>
|
||
<div class="relative">
|
||
<UIcon
|
||
name="i-heroicons-envelope"
|
||
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-primary-400 dark:text-primary-500 w-4 h-4"
|
||
/>
|
||
<input
|
||
v-model="username"
|
||
type="text"
|
||
placeholder="example@domain.com"
|
||
class="w-full px-3 py-2 pr-10 text-sm border border-primary-300 dark:border-primary-700 rounded focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-600 focus:border-transparent bg-white dark:bg-primary-800 text-primary-900 dark:text-white placeholder-primary-400 dark:placeholder-primary-500"
|
||
dir="rtl"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Password -->
|
||
<div>
|
||
<div class="flex justify-between items-center mb-1">
|
||
<label
|
||
class="text-xs font-medium text-primary-700 dark:text-primary-300"
|
||
>رمز عبور</label
|
||
>
|
||
<a
|
||
href="#"
|
||
@click.prevent="goForgotPassword"
|
||
class="text-xs text-primary-500 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300"
|
||
>
|
||
فراموش کردهاید؟
|
||
</a>
|
||
</div>
|
||
<div class="relative">
|
||
<UIcon
|
||
name="i-heroicons-lock-closed"
|
||
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-primary-400 dark:text-primary-500 w-4 h-4"
|
||
/>
|
||
<input
|
||
v-model="password"
|
||
:type="showPassword ? 'text' : 'password'"
|
||
placeholder="••••••••"
|
||
class="w-full px-3 py-2 pr-10 text-sm border border-primary-300 dark:border-primary-700 rounded focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-600 focus:border-transparent bg-white dark:bg-primary-800 text-primary-900 dark:text-white placeholder-primary-400 dark:placeholder-primary-500"
|
||
dir="rtl"
|
||
/>
|
||
<button
|
||
type="button"
|
||
@click="showPassword = !showPassword"
|
||
class="absolute left-2 top-1/2 transform -translate-y-1/2 text-primary-400 dark:text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
|
||
>
|
||
<UIcon
|
||
:name="
|
||
showPassword ? 'i-heroicons-eye-slash' : 'i-heroicons-eye'
|
||
"
|
||
class="w-4 h-4 mt-1"
|
||
/>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CAPTCHA -->
|
||
<div>
|
||
<label
|
||
class="block text-xs font-medium text-primary-700 dark:text-primary-300 mb-1"
|
||
>کد امنیتی</label
|
||
>
|
||
<div class="flex gap-2">
|
||
<div class="flex-1">
|
||
<input
|
||
v-model="captcha"
|
||
type="text"
|
||
placeholder="کد را وارد کنید"
|
||
class="w-full px-3 py-2 text-sm border border-primary-300 dark:border-primary-700 rounded focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-600 focus:border-transparent bg-white dark:bg-primary-800 text-primary-900 dark:text-white placeholder-primary-400 dark:placeholder-primary-500 text-center"
|
||
dir="rtl"
|
||
/>
|
||
</div>
|
||
<div class="flex items-center gap-1">
|
||
<div
|
||
class="w-24 h-10 border border-primary-300 dark:border-primary-700 rounded overflow-hidden bg-primary-50 dark:bg-primary-800 flex items-center justify-center"
|
||
>
|
||
<img
|
||
:src="captchaImage"
|
||
alt="کد امنیتی"
|
||
class="w-full h-full object-contain"
|
||
/>
|
||
</div>
|
||
<button
|
||
@click="resetCaptcha"
|
||
:disabled="loadingCaptcha"
|
||
class="w-8 h-8 border border-primary-300 dark:border-primary-700 rounded flex items-center justify-center hover:bg-primary-50 dark:hover:bg-primary-800 disabled:opacity-50 text-primary-700 dark:text-primary-300"
|
||
>
|
||
<UIcon
|
||
name="i-heroicons-arrow-path"
|
||
class="w-4 h-4"
|
||
:class="{ 'animate-spin': loadingCaptcha }"
|
||
/>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Login Button -->
|
||
<button
|
||
@click="handleLogin"
|
||
:disabled="loading"
|
||
class="w-full py-2.5 bg-primary dark:bg-primary-800 text-white text-sm font-medium rounded hover:bg-primary-600 dark:hover:bg-primary-700 active:bg-primary-900 dark:active:bg-dark-primary-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||
>
|
||
<span v-if="loading" class="flex items-center justify-center gap-2">
|
||
<UIcon name="i-heroicons-arrow-path" class="w-4 h-4 animate-spin" />
|
||
در حال ورود...
|
||
</span>
|
||
<span v-else> ورود </span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Divider -->
|
||
<div class="relative">
|
||
<div class="absolute inset-0 flex items-center">
|
||
<div
|
||
class="w-full border-t border-primary-200 dark:border-primary-700"
|
||
></div>
|
||
</div>
|
||
<div class="relative flex justify-center text-xs">
|
||
<span
|
||
class="px-2 bg-white dark:bg-primary-900 text-primary-500 dark:text-primary-400"
|
||
>یا</span
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Alternative Options -->
|
||
<div class="space-y-2">
|
||
<button
|
||
@click="goLoginPhonePage(false)"
|
||
class="w-full py-2 border border-primary-300 dark:border-primary-700 text-primary-700 dark:text-primary-300 text-sm font-medium rounded hover:bg-primary-50 dark:hover:bg-primary-800 transition-colors"
|
||
>
|
||
ورود با شماره تلفن
|
||
</button>
|
||
|
||
<button
|
||
@click="loginGuest"
|
||
class="w-full py-2 border border-primary-300 dark:border-primary-700 text-primary-700 dark:text-primary-300 text-sm font-medium rounded hover:bg-primary-50 dark:hover:bg-primary-800 transition-colors"
|
||
>
|
||
ورود به عنوان مهمان
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Footer Note -->
|
||
<p
|
||
class="text-xs text-primary-400 dark:text-primary-500 text-center pt-4"
|
||
>
|
||
با ورود، با
|
||
<a
|
||
href="#"
|
||
class="text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-300"
|
||
>قوانین</a
|
||
>
|
||
موافقت میکنید
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from "vue";
|
||
import { useAuthStore } from "@/stores/authStore";
|
||
import { useNuxtApp } from "#app";
|
||
import { navigateTo } from "#imports";
|
||
import { useToast } from "#imports";
|
||
import { composSystemTheme } from "@/composables/composSystemTheme";
|
||
import { getUserPermission } from "@/stores/permissionStore";
|
||
|
||
const useSystemTheme = composSystemTheme();
|
||
const toast = useToast();
|
||
|
||
// --- State ---
|
||
const username = ref("");
|
||
const password = ref("");
|
||
const captcha = ref("");
|
||
const captchaImage = ref("");
|
||
const loading = ref(false);
|
||
const loadingCaptcha = ref(false);
|
||
const showPassword = ref(false);
|
||
|
||
// --- Nuxt App & Store ---
|
||
const nuxtApp = useNuxtApp();
|
||
const { $http: httpService } = nuxtApp;
|
||
const authStore = useAuthStore();
|
||
|
||
// --- Navigation ---
|
||
const goLoginPhonePage = (isReset) => {
|
||
navigateTo("/loginphone");
|
||
};
|
||
|
||
const goForgotPassword = () => {
|
||
navigateTo("/forget");
|
||
};
|
||
|
||
const loginGuest = () => {
|
||
loading.value = true;
|
||
|
||
httpService
|
||
.getRequest("/login/user/validate", {
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
Authorization: "GuestAccess",
|
||
},
|
||
})
|
||
.then((res) => {
|
||
authStore.setUser(res);
|
||
|
||
const userPermissionStore = getUserPermission();
|
||
userPermissionStore.fetchUserPermissions();
|
||
// console.log("Permissions:", userPermissionStore.permissions);
|
||
toast.add({
|
||
title: res.message || "ورود موفقیتآمیز بود",
|
||
icon: "i-lucide-calendar-days",
|
||
});
|
||
|
||
navigateTo({
|
||
name: "DashboardBasePage",
|
||
});
|
||
})
|
||
.catch((error) => {
|
||
console.log("error ==> ", error);
|
||
|
||
toast.add({
|
||
title: "خطا",
|
||
description: "خطا در ورود",
|
||
color: "red",
|
||
});
|
||
|
||
loadCaptcha();
|
||
})
|
||
.finally(() => {
|
||
loading.value = false;
|
||
});
|
||
|
||
// toast.add({
|
||
// title: "خطا",
|
||
// description: "ورود به عنوان مهمان در حال توسعه است",
|
||
// color: "red",
|
||
// });
|
||
};
|
||
|
||
// --- CAPTCHA ---
|
||
const loadCaptcha = async () => {
|
||
loadingCaptcha.value = true;
|
||
try {
|
||
const string = await httpService.getRequest("/login/captcha/makeimage");
|
||
captchaImage.value = `data:image/jpeg;base64,${string}`;
|
||
} catch {
|
||
captchaImage.value = "/assets/common/img/captcha.png";
|
||
} finally {
|
||
loadingCaptcha.value = false;
|
||
}
|
||
};
|
||
|
||
const resetCaptcha = () => {
|
||
loadCaptcha();
|
||
};
|
||
|
||
// --- Login Logic ---
|
||
const handleLogin = () => {
|
||
loading.value = true;
|
||
|
||
const formData = {
|
||
username: username.value,
|
||
password: password.value,
|
||
captcha: captcha.value,
|
||
};
|
||
|
||
httpService
|
||
.postRequest("/login/user/login", formData)
|
||
.then((res) => {
|
||
authStore.setUser(res);
|
||
|
||
const userPermissionStore = getUserPermission();
|
||
userPermissionStore.fetchUserPermissions();
|
||
// console.log("Permissions:", userPermissionStore.permissions);
|
||
toast.add({
|
||
title: res.message || "ورود موفقیتآمیز بود",
|
||
icon: "i-lucide-calendar-days",
|
||
});
|
||
|
||
navigateTo({
|
||
name: "DashboardBasePage",
|
||
});
|
||
})
|
||
.catch((error) => {
|
||
console.log("error ==> ", error);
|
||
|
||
toast.add({
|
||
title: "خطا",
|
||
description: "خطا در ورود",
|
||
color: "red",
|
||
});
|
||
|
||
loadCaptcha();
|
||
})
|
||
.finally(() => {
|
||
loading.value = false;
|
||
});
|
||
};
|
||
|
||
onMounted(() => {
|
||
loadCaptcha();
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
input:focus {
|
||
outline: none;
|
||
}
|
||
|
||
/* بهبود نمایش در حالت دارک مود */
|
||
/* @media (prefers-color-scheme: dark) {
|
||
.dark\:bg-primary-900 {
|
||
background-color: #111827;
|
||
}
|
||
|
||
.dark\:bg-primary-800 {
|
||
background-color: #1f2937;
|
||
}
|
||
|
||
.dark\:border-primary-700 {
|
||
border-color: #374151;
|
||
}
|
||
|
||
.dark\:text-white {
|
||
color: #f9fafb;
|
||
}
|
||
|
||
.dark\:text-primary-300 {
|
||
color: #d1d5db;
|
||
}
|
||
|
||
.dark\:placeholder-primary-500 {
|
||
&::placeholder {
|
||
color: #6b7280;
|
||
}
|
||
}
|
||
} */
|
||
</style>
|