пофиксил страницу админа

This commit is contained in:
Мельников Данил 2025-05-31 09:41:40 +05:00
parent dfac532216
commit 7002337a9b
14 changed files with 446 additions and 978 deletions

View File

@ -6,10 +6,13 @@ const loginUser = async (loginData) => {
const response = await axios.post(`${CONFIG.BASE_URL}/auth/`, loginData, {
withCredentials: true,
});
return response.data.access_token;
const { access_token, user_id } = response.data;
return { access_token, user_id };
} catch (error) {
if (error.status === 401) {
throw new Error("Неверное имя пользователя или пароль")
if (error.response?.status === 401) {
throw new Error("Неверное имя пользователя или пароль");
}
throw new Error(error.message);

View File

@ -0,0 +1,18 @@
import axios from "axios";
import CONFIG from "@/core/config.js";
const fetchContests = async () => {
try {
const response = await axios.get(`${CONFIG.BASE_URL}/contests`, {
withCredentials: true,
});
return response.data;
} catch (error) {
if (error.response?.status === 401) {
throw new Error("Нет доступа к конкурсам (401)");
}
throw new Error(error.message);
}
};
export default fetchContests;

View File

@ -0,0 +1,17 @@
import axios from 'axios'
import CONFIG from '../../core/config.js'
const getUserProfile = async (user_id, token) => {
try {
const response = await axios.get(`${CONFIG.BASE_URL}/profiles/${user_id}/`, {
headers: {
Authorization: `Bearer ${token}`
}
})
return response.data
} catch (error) {
throw new Error(error.response?.data?.detail || 'Ошибка получения профиля')
}
}
export default getUserProfile

View File

@ -0,0 +1,18 @@
import axios from "axios";
import CONFIG from "@/core/config.js";
const fetchProjects = async () => {
try {
const response = await axios.get(`${CONFIG.BASE_URL}/projects`, {
withCredentials: true,
});
return response.data;
} catch (error) {
if (error.response?.status === 401) {
throw new Error("Нет доступа к проектам (401)");
}
throw new Error(error.message);
}
};
export default fetchProjects;

View File

@ -0,0 +1,24 @@
import axios from "axios";
import CONFIG from "@/core/config.js";
const fetchTeams = async () => {
try {
const token = localStorage.getItem("access_token");
const response = await axios.get(`${CONFIG.BASE_URL}/teams`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
} catch (error) {
if (error.response?.status === 401) {
throw new Error("Нет доступа к командам (401)");
} else if (error.response?.status === 403) {
throw new Error("Доступ запрещён (403)");
}
throw new Error(error.message);
}
};
export default fetchTeams;

View File

@ -0,0 +1,17 @@
import axios from 'axios'
import CONFIG from '@/core/config.js'
const updateTeam = async (team) => {
try {
const response = await axios.put(
`${CONFIG.BASE_URL}/teams/${team.id}`,
team,
{ withCredentials: true }
)
return response.data
} catch (error) {
throw new Error(error.response?.data?.detail || error.message)
}
}
export default updateTeam

View File

@ -0,0 +1,18 @@
import axios from "axios";
import CONFIG from "@/core/config.js";
const fetchUsers = async () => {
try {
const response = await axios.get(`${CONFIG.BASE_URL}/users`, {
withCredentials: true,
});
return response.data;
} catch (error) {
if (error.response?.status === 401) {
throw new Error("Нет доступа к пользователям (401)");
}
throw new Error(error.message);
}
};
export default fetchUsers;

View File

@ -1,8 +1,33 @@
import { createApp } from 'vue'
import {createApp} from 'vue'
import App from './App.vue'
import { Quasar, Notify, Dialog, Loading, QInput, QBtn, QForm, QCard, QCardSection } from 'quasar'
import router from './router'
import {
Quasar,
Notify,
Dialog,
Loading,
QInput,
QBtn,
QForm,
QCard,
QCardSection,
QLayout,
QPageContainer,
QPage,
QTabs,
QTab,
QTabPanels,
QTabPanel,
QHeader,
QTable,
QSeparator,
QCardActions,
QDialog,
QIcon
} from 'quasar'
import '@quasar/extras/material-icons/material-icons.css'
@ -11,8 +36,13 @@ import 'quasar/src/css/index.sass'
const app = createApp(App)
app.use(Quasar, {
plugins: { Notify, Dialog, Loading }, // уведомления, диалоги, загрузка
components: { QInput, QBtn, QForm, QCard, QCardSection } // обязательно указать используемые компоненты
plugins: {Notify, Dialog, Loading},
components: {
QInput, QBtn, QForm, QCard, QCardSection,
QLayout, QPageContainer, QPage,
QTabs, QTab, QTabPanels, QTabPanel, QHeader,QTable,
QSeparator, QCardActions, QDialog, QIcon
}
})
app.use(router)

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,15 @@
<script setup>
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { isAuthenticated as checkAuth, isAdmin as checkAdmin } from '../utils/auth.js'
const router = useRouter()
const isAuthenticated = computed(() => checkAuth())
const isAdmin = computed(() => checkAdmin())
// Реактивное вычисление статуса авторизации
const isAuthenticated = computed(() => !!localStorage.getItem('access_token'))
const goToLogin = () => router.push('/login')
const goToAdmin = () => router.push('/admin')
const logout = () => {
localStorage.removeItem('access_token')
router.go(0) // Принудительное обновление страницы для обновления состояния
@ -24,15 +22,6 @@ const logout = () => {
<div v-if="isAuthenticated" class="q-gutter-sm">
<q-btn label="Редактировать профиль" color="secondary" />
<q-btn
v-if="isAdmin"
label="Управление данными"
color="accent"
@click="goToAdmin"
icon="admin_panel_settings"
/>
<q-btn label="Выход" color="negative" @click="logout" />
</div>

View File

@ -2,7 +2,11 @@
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import { Notify } from 'quasar'
import loginUser from "../api/auth/loginRequest.js"
import axios from 'axios'
import CONFIG from '../core/config.js'
import loginUser from '../api/auth/loginRequest.js'
const router = useRouter()
const login = ref('')
@ -11,14 +15,12 @@ const loading = ref(false)
const marqueeTrack = ref(null)
const marqueeText = ref(null)
const animationDuration = ref(7) // секунд
const animationDuration = ref(7)
// Динамически рассчитываем длительность анимации в зависимости от ширины текста
const updateAnimation = () => {
if (marqueeTrack.value && marqueeText.value) {
const textWidth = marqueeText.value.offsetWidth
const containerWidth = marqueeTrack.value.offsetWidth / 2
// Скорость: 100px/sec (можно подстроить)
const speed = 100
animationDuration.value = (textWidth + containerWidth) / speed
marqueeTrack.value.style.setProperty('--marquee-width', `${textWidth}px`)
@ -27,7 +29,7 @@ const updateAnimation = () => {
}
onMounted(() => {
setTimeout(updateAnimation, 100) // Дать DOM отрисоваться
setTimeout(updateAnimation, 100)
window.addEventListener('resize', updateAnimation)
})
onBeforeUnmount(() => {
@ -37,12 +39,21 @@ onBeforeUnmount(() => {
const authorisation = async () => {
loading.value = true
try {
const accessToken = await loginUser({
const { access_token, user_id } = await loginUser({
login: login.value,
password: password.value
})
localStorage.setItem('access_token', accessToken)
localStorage.setItem('access_token', access_token)
localStorage.setItem('user_id', user_id)
const profileResponse = await axios.get(`${CONFIG.BASE_URL}/profiles/user/${user_id}/`, {
headers: { Authorization: `Bearer ${access_token}` }
})
const roleId = profileResponse.data.role_id
Notify.create({
type: 'positive',
@ -50,7 +61,21 @@ const authorisation = async () => {
icon: 'check_circle'
})
router.push('/')
console.log('Role ID:', roleId)
if (roleId === 1) {
console.log('Переход на /admin')
router.push('/admin')
} else {
console.log('Переход на /')
router.push('/')
}
if (roleId === 1) {
router.push('/admin')
} else {
router.push('/')
}
} catch (error) {
Notify.create({
type: 'negative',
@ -63,6 +88,7 @@ const authorisation = async () => {
}
</script>
<template>
<div class="fullscreen flex flex-center bg-violet-strong">
<q-card class="q-pa-xl shadow-violet card-animate violet-card" style="width: 370px; max-width: 92vw;">

View File

@ -1,23 +1,12 @@
import {createRouter, createWebHistory} from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
import LoginPage from "../pages/LoginPage.vue"
import HomePage from "../pages/HomePage.vue"
import AdminPage from "../pages/AdminPage.vue"
import { isAuthenticated, getUserRole } from "../utils/auth.js"
const routes = [
{
path: '/',
component: HomePage
},
{
path: '/login',
component: LoginPage
},
{
path: '/admin',
component: AdminPage,
meta: { requiresAuth: true, requiresAdmin: true }
}
{ path: '/', component: HomePage },
{ path: '/login', component: LoginPage },
{ path: '/admin', component: AdminPage }
]
const router = createRouter({
@ -26,30 +15,20 @@ const router = createRouter({
})
router.beforeEach((to, from, next) => {
const authenticated = isAuthenticated()
const isAuthenticated = !!localStorage.getItem('access_token')
const userId = localStorage.getItem('user_id')
// Если переходим на страницу логина и уже авторизованы
if (to.path === '/login' && authenticated) {
if (to.path === '/login' && isAuthenticated) {
next('/')
return
}
// Проверка доступа к админ-панели
if (to.meta.requiresAdmin) {
if (!authenticated) {
next('/login')
return
}
const userRole = getUserRole()
if (userRole !== 'admin') {
// Редирект на главную, если не админ
} else if (to.path === '/admin') {
if (isAuthenticated && userId === '1') {
next()
} else {
next('/')
return
}
} else {
next()
}
next()
})
export default router
export default router

View File

@ -1,35 +0,0 @@
export const decodeToken = (token) => {
try {
const base64Url = token.split('.')[1]
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
const jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
)
return JSON.parse(jsonPayload)
} catch (error) {
console.error('Ошибка декодирования токена:', error)
return null
}
}
export const getUserRole = () => {
const token = localStorage.getItem('access_token')
if (!token) return null
const decoded = decodeToken(token)
return decoded?.role || null
}
export const isAuthenticated = () => {
return !!localStorage.getItem('access_token')
}
export const isAdmin = () => {
return getUserRole() === 'admin'
}

View File

@ -1,7 +1,12 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
})
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})