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="********" />
|
||||
<div class="form-button-wrapper">
|
||||
<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>
|
||||
</n-space>
|
||||
@@ -36,8 +43,14 @@
|
||||
</div>
|
||||
<div class="form-button-wrapper">
|
||||
<div class="form-label">
|
||||
<n-button @click="signup"
|
||||
:disabled="username.length == 0 || password.length == 0 || !approval">Регистрация</n-button>
|
||||
<n-button @click="signup" :disabled="username.length == 0 || password.length == 0 || !approval">
|
||||
<span v-if="!authStore.isLoading">
|
||||
Регистрация
|
||||
</span>
|
||||
<span v-else>
|
||||
Подождите...
|
||||
</span>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-space>
|
||||
@@ -50,7 +63,15 @@
|
||||
<script setup lang="ts">
|
||||
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 email = ref('')
|
||||
@@ -68,15 +89,27 @@ const options = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
function signin() {
|
||||
console.log(email.value)
|
||||
console.log(password.value)
|
||||
async function signin() {
|
||||
const res = await authStore.login(email.value, password.value)
|
||||
|
||||
if (!res && authStore.error != null) {
|
||||
message.error(authStore.error)
|
||||
return
|
||||
}
|
||||
|
||||
router.push('/office')
|
||||
}
|
||||
|
||||
function signup() {
|
||||
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>
|
||||
|
||||
|
||||
@@ -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 HomeView from '../views/HomeView.vue'
|
||||
import LoginView from '../views/LoginView.vue'
|
||||
import OfficeView from '../views/OfficeView.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@@ -15,6 +16,11 @@ const router = createRouter({
|
||||
name: 'login',
|
||||
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