generated from VLADIMIR/template_frontend
add storage
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
import { createEveningDetectiveServerClient, type EveningDetectiveServer } from ".";
|
||||||
|
|
||||||
|
type RequestType = {
|
||||||
|
path: string;
|
||||||
|
method: string;
|
||||||
|
body: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function createHttpHandler(
|
||||||
|
baseURL: string,
|
||||||
|
getToken?: () => string | null,
|
||||||
|
): (
|
||||||
|
request: { path: string, method: string, body: string | null },
|
||||||
|
) => Promise<unknown> {
|
||||||
|
return async (
|
||||||
|
request: { path: string, method: string, body: string | null },
|
||||||
|
) => {
|
||||||
|
// Формируем полный URL
|
||||||
|
const url = `${baseURL}/${request.path}`;
|
||||||
|
|
||||||
|
// Подготавливаем заголовки
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Добавляем токен, если он есть
|
||||||
|
const token = getToken?.();
|
||||||
|
if (token) {
|
||||||
|
headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Делаем запрос
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: request.method,
|
||||||
|
headers,
|
||||||
|
body: request.body, // body уже JSON-строка из генератора
|
||||||
|
});
|
||||||
|
|
||||||
|
// Парсим ответ
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Возвращаем в формате, который ожидает генератор
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getURL(): string {
|
||||||
|
return "http://localhost:8090"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildClient(
|
||||||
|
getToken?: () => string | null,
|
||||||
|
): EveningDetectiveServer {
|
||||||
|
return createEveningDetectiveServerClient(
|
||||||
|
createHttpHandler(
|
||||||
|
getURL(),
|
||||||
|
getToken,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -12,7 +12,14 @@
|
|||||||
<n-input v-model:value="password" type="password" placeholder="********" />
|
<n-input v-model:value="password" type="password" placeholder="********" />
|
||||||
<div class="form-button-wrapper">
|
<div class="form-button-wrapper">
|
||||||
<div class="form-label">
|
<div class="form-label">
|
||||||
<n-button @click="signin" :disabled="email.length == 0 || password.length == 0">Вход</n-button>
|
<n-button @click="signin" :disabled="email.length == 0 || password.length == 0">
|
||||||
|
<span v-if="!authStore.isLoading">
|
||||||
|
Вход
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
Подождите...
|
||||||
|
</span>
|
||||||
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-space>
|
</n-space>
|
||||||
@@ -36,8 +43,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-button-wrapper">
|
<div class="form-button-wrapper">
|
||||||
<div class="form-label">
|
<div class="form-label">
|
||||||
<n-button @click="signup"
|
<n-button @click="signup" :disabled="username.length == 0 || password.length == 0 || !approval">
|
||||||
:disabled="username.length == 0 || password.length == 0 || !approval">Регистрация</n-button>
|
<span v-if="!authStore.isLoading">
|
||||||
|
Регистрация
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
Подождите...
|
||||||
|
</span>
|
||||||
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-space>
|
</n-space>
|
||||||
@@ -50,7 +63,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import { NCard, NTabs, NTabPane, NCheckbox, NButton, NSpace, NInput, NAutoComplete } from 'naive-ui'
|
import { NCard, NTabs, NTabPane, NCheckbox, NButton, NSpace, NInput, NAutoComplete, useMessage } from 'naive-ui'
|
||||||
|
import { useAuthStore } from '@/stores/auth';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
const email = ref('')
|
const email = ref('')
|
||||||
@@ -68,15 +89,27 @@ const options = computed(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function signin() {
|
async function signin() {
|
||||||
console.log(email.value)
|
const res = await authStore.login(email.value, password.value)
|
||||||
console.log(password.value)
|
|
||||||
|
if (!res && authStore.error != null) {
|
||||||
|
message.error(authStore.error)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function signup() {
|
router.push('/office')
|
||||||
console.log(username.value)
|
}
|
||||||
console.log(email.value)
|
|
||||||
console.log(approval.value)
|
async function signup() {
|
||||||
|
if (!approval.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await authStore.signup(username.value, email.value)
|
||||||
|
if (!res && authStore.error != null) {
|
||||||
|
message.error(authStore.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.error("Пароль отправлен на почту")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<header class="header">
|
||||||
|
<p @click="goToLogout" class="btn-login">
|
||||||
|
Выйти
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Доброго времени суток, {{ authStore.username }}.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ authStore.userRoles }}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import router from '@/router';
|
||||||
|
import { useAuthStore } from '@/stores/auth';
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const goToLogout = () => {
|
||||||
|
authStore.logout()
|
||||||
|
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import HomeView from '../views/HomeView.vue'
|
import HomeView from '../views/HomeView.vue'
|
||||||
import LoginView from '../views/LoginView.vue'
|
import LoginView from '../views/LoginView.vue'
|
||||||
|
import OfficeView from '../views/OfficeView.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@@ -15,6 +16,11 @@ const router = createRouter({
|
|||||||
name: 'login',
|
name: 'login',
|
||||||
component: LoginView,
|
component: LoginView,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/office',
|
||||||
|
name: 'office',
|
||||||
|
component: OfficeView,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
// stores/auth.ts
|
||||||
|
import { type User } from '@/api/generated/crabs/evening_detective_server';
|
||||||
|
import { buildClient, getURL } from '@/api/generated/crabs/evening_detective_server/client';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
// ===== STATE =====
|
||||||
|
const accessToken = ref<string | null>(null);
|
||||||
|
const refreshToken = ref<string | null>(null);
|
||||||
|
const user = ref<User | null>(null);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
|
||||||
|
// ===== GETTERS =====
|
||||||
|
const isAuthenticated = computed(() => !!accessToken.value);
|
||||||
|
const username = computed(() => user.value?.username || '');
|
||||||
|
const userRoles = computed(() => user.value?.roles || []);
|
||||||
|
|
||||||
|
// ===== ACTIONS =====
|
||||||
|
|
||||||
|
// Инициализация из localStorage при загрузке
|
||||||
|
function initFromStorage() {
|
||||||
|
const token = localStorage.getItem('accessToken');
|
||||||
|
const refresh = localStorage.getItem('refreshToken');
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
accessToken.value = token;
|
||||||
|
refreshToken.value = refresh;
|
||||||
|
|
||||||
|
// Получаем данные пользователя
|
||||||
|
fetchUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToken(): string | null {
|
||||||
|
return accessToken.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получение данных пользователя
|
||||||
|
async function fetchUser() {
|
||||||
|
if (!accessToken.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = buildClient(getToken);
|
||||||
|
const response = await client.GetMe({});
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
error.value = response.error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.user) {
|
||||||
|
user.value = response.user;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch user:', err);
|
||||||
|
// Если 401 — токен протух
|
||||||
|
if (err instanceof Error && err.message.includes('401')) {
|
||||||
|
await logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Логин
|
||||||
|
async function login(email: string, password: string) {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = buildClient(getToken);
|
||||||
|
const response = await client.Login({ email, password });
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
error.value = response.error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем токены
|
||||||
|
if (response.accessToken) {
|
||||||
|
accessToken.value = response.accessToken;
|
||||||
|
refreshToken.value = response.refreshToken || null;
|
||||||
|
|
||||||
|
// Сохраняем в localStorage
|
||||||
|
localStorage.setItem('accessToken', response.accessToken);
|
||||||
|
if (response.refreshToken) {
|
||||||
|
localStorage.setItem('refreshToken', response.refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем данные пользователя
|
||||||
|
await fetchUser();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : 'Login failed';
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновление токена
|
||||||
|
async function refreshTokenAction() {
|
||||||
|
if (!refreshToken.value) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = buildClient(getToken);
|
||||||
|
const response = await client.Refresh({
|
||||||
|
refreshToken: refreshToken.value
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.error || !response.accessToken) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем токены
|
||||||
|
accessToken.value = response.accessToken;
|
||||||
|
refreshToken.value = response.refreshToken || null;
|
||||||
|
|
||||||
|
localStorage.setItem('accessToken', response.accessToken);
|
||||||
|
if (response.refreshToken) {
|
||||||
|
localStorage.setItem('refreshToken', response.refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Регистрация
|
||||||
|
async function signup(username: string, email: string) {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = buildClient(getToken);
|
||||||
|
const response = await client.Signup({ username, email });
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
error.value = response.error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : 'Signup failed';
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Восстановление пароля
|
||||||
|
async function refreshPassword(email: string) {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = buildClient(getToken);
|
||||||
|
const response = await client.RefreshPassword({ email });
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
error.value = response.error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : 'Password reset failed';
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выход
|
||||||
|
async function logout() {
|
||||||
|
accessToken.value = null;
|
||||||
|
refreshToken.value = null;
|
||||||
|
user.value = null;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
localStorage.removeItem('accessToken');
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка ролей
|
||||||
|
function hasRole(role: string): boolean {
|
||||||
|
return userRoles.value.includes(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasAnyRole(roles: string[]): boolean {
|
||||||
|
return roles.some(role => userRoles.value.includes(role));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== RETURN =====
|
||||||
|
return {
|
||||||
|
// State
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
user,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
isAuthenticated,
|
||||||
|
username,
|
||||||
|
userRoles,
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
initFromStorage,
|
||||||
|
getToken,
|
||||||
|
login,
|
||||||
|
signup,
|
||||||
|
refreshPassword,
|
||||||
|
logout,
|
||||||
|
fetchUser,
|
||||||
|
refreshTokenAction,
|
||||||
|
hasRole,
|
||||||
|
hasAnyRole,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { ref, computed } from 'vue'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
export const useCounterStore = defineStore('counter', () => {
|
|
||||||
const count = ref(0)
|
|
||||||
const doubleCount = computed(() => count.value * 2)
|
|
||||||
function increment() {
|
|
||||||
count.value++
|
|
||||||
}
|
|
||||||
|
|
||||||
return { count, doubleCount, increment }
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import OfficePage from '@/components/OfficePage.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Страница авторизации -->
|
||||||
|
<template>
|
||||||
|
<main>
|
||||||
|
<OfficePage />
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user