Compare commits
1 Commits
c4a1c2cde0
...
d6059f4325
| Author | SHA1 | Date | |
|---|---|---|---|
| d6059f4325 |
25
src/App.vue
25
src/App.vue
@@ -1,16 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<header>
|
<header>
|
||||||
<nav>
|
<nav>
|
||||||
<router-link to="/">Главная</router-link> | <router-link to="/login">Вход</router-link> |
|
<router-link to="/">Главная</router-link> |
|
||||||
|
|
||||||
|
<!-- Показываем, если пользователь НЕ залогинен -->
|
||||||
|
<template v-if="!authStore.isAuthenticated">
|
||||||
|
<router-link to="/login">Вход</router-link> |
|
||||||
<router-link to="/register">Регистрация</router-link>
|
<router-link to="/register">Регистрация</router-link>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Показываем, если пользователь залогинен -->
|
||||||
|
<template v-else>
|
||||||
|
<span>Привет, {{ authStore.user?.username }}!</span> |
|
||||||
|
<a @click="authStore.handleLogout" href="#">Выход</a>
|
||||||
|
</template>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<!-- Здесь будут отображаться компоненты-страницы -->
|
|
||||||
<router-view />
|
<router-view />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
header {
|
header {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
@@ -29,4 +44,10 @@ nav a.router-link-exact-active {
|
|||||||
main {
|
main {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
span {
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import type { RegisterRequest } from "@/types"; // Мы создадим этот тип
|
import type { RegisterRequest } from "@/types";
|
||||||
import apiClient from "./axios";
|
import apiClient from "./axios";
|
||||||
|
|
||||||
export const registerUser = (userData: RegisterRequest) => {
|
export const registerUser = (userData: RegisterRequest) => {
|
||||||
// apiClient уже настроен на /api, поэтому путь относительный
|
|
||||||
return apiClient.post("/register", userData);
|
return apiClient.post("/register", userData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const loginUser = (credentials: LoginRequest) => {
|
||||||
|
return apiClient.post<LoginResponse>("/login", credentials);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,26 +1,53 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { registerUser as apiRegisterUser } from "@/api/auth";
|
import { registerUser as apiRegisterUser } from "@/api/auth";
|
||||||
import type { RegisterRequest } from "@/types";
|
import type { RegisterRequest } from "@/types";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
import apiClient from "@/api/axios";
|
||||||
|
|
||||||
export const useAuthStore = defineStore("auth", () => {
|
export const useAuthStore = defineStore("auth", () => {
|
||||||
// State
|
// State
|
||||||
|
const user = ref<User | null>(null);
|
||||||
|
const token = ref<string | null>(localStorage.getItem("authToken"));
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null);
|
||||||
|
|
||||||
|
// === Getters ===
|
||||||
|
const isAuthenticated = computed(() => !!user.value && !!token.value);
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
function setAuthData(userData: User, authToken: string) {
|
||||||
|
user.value = userData;
|
||||||
|
token.value = authToken;
|
||||||
|
localStorage.setItem("authToken", authToken);
|
||||||
|
apiClient.defaults.headers.common["Authorization"] = `Bearer ${authToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogin(credentials: LoginRequest) {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
try {
|
||||||
|
const response = await apiLoginUser(credentials);
|
||||||
|
setAuthData(response.data.user, response.data.token);
|
||||||
|
await router.push({ name: "home" });
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.response && e.response.data) {
|
||||||
|
error.value = e.response.data;
|
||||||
|
} else {
|
||||||
|
error.value = "Произошла неизвестная ошибка при входе";
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleRegister(userData: RegisterRequest) {
|
async function handleRegister(userData: RegisterRequest) {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
try {
|
try {
|
||||||
await apiRegisterUser(userData);
|
await apiRegisterUser(userData);
|
||||||
// После успешной регистрации перенаправляем на страницу входа
|
|
||||||
await router.push({ name: "login" });
|
await router.push({ name: "login" });
|
||||||
// Можно добавить сообщение об успехе ("тост")
|
|
||||||
// alert('Регистрация прошла успешно! Теперь вы можете войти.');
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// Обрабатываем ошибки от Axios
|
|
||||||
if (e.response && e.response.data) {
|
if (e.response && e.response.data) {
|
||||||
error.value = e.response.data;
|
error.value = e.response.data;
|
||||||
} else {
|
} else {
|
||||||
@@ -30,10 +57,26 @@ export const useAuthStore = defineStore("auth", () => {
|
|||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Функция для выхода
|
||||||
|
function handleLogout() {
|
||||||
|
user.value = null;
|
||||||
|
token.value = null;
|
||||||
|
localStorage.removeItem("authToken");
|
||||||
|
delete apiClient.defaults.headers.common["Authorization"];
|
||||||
|
router.push({ name: "login" });
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// State
|
||||||
|
user,
|
||||||
|
token,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
|
// Getters
|
||||||
|
isAuthenticated,
|
||||||
|
// Actions
|
||||||
|
handleLogin,
|
||||||
handleRegister,
|
handleRegister,
|
||||||
|
handleLogout,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
22
src/types.ts
22
src/types.ts
@@ -4,3 +4,25 @@ export interface RegisterRequest {
|
|||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Тип для запроса на логин
|
||||||
|
export interface LoginRequest {
|
||||||
|
login: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Тип для ответа с бэкенда
|
||||||
|
export interface LoginResponse {
|
||||||
|
token: string;
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Тип для объекта пользователя
|
||||||
|
export interface User {
|
||||||
|
uuid: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
role: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,112 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="login-container">
|
||||||
|
<form @submit.prevent="onSubmit" class="login-form">
|
||||||
<h1>Вход</h1>
|
<h1>Вход</h1>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="login">Имя пользователя или Email</label>
|
||||||
|
<input id="login" v-model="login" type="text" required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Пароль</label>
|
||||||
|
<input id="password" v-model="password" type="password" required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="authStore.error" class="error-message">
|
||||||
|
{{ authStore.error }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" :disabled="authStore.isLoading">
|
||||||
|
{{ authStore.isLoading ? "Вход..." : "Войти" }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onUnmounted } from "vue";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const login = ref("");
|
||||||
|
const password = ref("");
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
if (!login.value || !password.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await authStore.handleLogin({
|
||||||
|
login: login.value,
|
||||||
|
password: password.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
authStore.error = null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Стили можно скопировать из RegisterView.vue или вынести в общий файл */
|
||||||
|
.login-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
|
.login-form {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 2rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.error-message {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: #42b983;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #36a476;
|
||||||
|
}
|
||||||
|
button:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user