mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
переработка админ панели
This commit is contained in:
73
frontend/src/components/AdminFooter.vue
Normal file
73
frontend/src/components/AdminFooter.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<!-- AdminFooter.vue -->
|
||||
<template>
|
||||
<footer class="admin-footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-info">
|
||||
<p>© 2024 IT Support. Все права защищены.</p>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<a href="#" class="footer-link">Помощь</a>
|
||||
<a href="#" class="footer-link">Политика конфиденциальности</a>
|
||||
<a href="#" class="footer-link">Условия использования</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AdminFooter'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-footer {
|
||||
background-color: #1a237e;
|
||||
color: white;
|
||||
padding: 1.5rem 2rem;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-info p {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.footer-link:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.footer-content {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
81
frontend/src/components/AdminHeader.vue
Normal file
81
frontend/src/components/AdminHeader.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<header class="admin-header">
|
||||
<div class="header-content">
|
||||
<div class="logo">
|
||||
<h1>IT Support</h1>
|
||||
</div>
|
||||
<nav class="nav-menu">
|
||||
<router-link to="/admin/dashboard" class="nav-link">Панель управления</router-link>
|
||||
<router-link to="/admin/employees" class="nav-link">Сотрудники</router-link>
|
||||
<router-link to="/admin/requests" class="nav-link">Заявки</router-link>
|
||||
<a @click="logout" class="nav-link logout">Выйти</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AdminHeader',
|
||||
methods: {
|
||||
logout() {
|
||||
localStorage.removeItem('admin_token');
|
||||
this.$router.push('/admin/login');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-header {
|
||||
background-color: #1a237e;
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav-link.router-link-active {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.logout {
|
||||
cursor: pointer;
|
||||
color: #ff5252;
|
||||
}
|
||||
|
||||
.logout:hover {
|
||||
background-color: rgba(255, 82, 82, 0.1);
|
||||
}
|
||||
</style>
|
191
frontend/src/views/admin/AdminDashboardView.vue
Normal file
191
frontend/src/views/admin/AdminDashboardView.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="admin-layout">
|
||||
<AdminHeader />
|
||||
<main class="admin-main">
|
||||
<div class="dashboard-container">
|
||||
<h1 class="dashboard-title">Панель управления</h1>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<h3>Всего заявок</h3>
|
||||
<p class="stat-number">{{ statistics.total_requests || 0 }}</p>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>Новые заявки</h3>
|
||||
<p class="stat-number">{{ statistics.by_status?.new || 0 }}</p>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>В работе</h3>
|
||||
<p class="stat-number">{{ statistics.by_status?.in_progress || 0 }}</p>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>Завершенные</h3>
|
||||
<p class="stat-number">{{ statistics.by_status?.completed || 0 }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-grid">
|
||||
<router-link to="/admin/employees/add" class="action-card">
|
||||
<div class="action-icon">👥</div>
|
||||
<h3>Добавить сотрудника</h3>
|
||||
<p>Регистрация нового сотрудника в системе</p>
|
||||
</router-link>
|
||||
|
||||
<router-link to="/admin/requests" class="action-card">
|
||||
<div class="action-icon">📝</div>
|
||||
<h3>Управление заявками</h3>
|
||||
<p>Просмотр и обработка заявок</p>
|
||||
</router-link>
|
||||
|
||||
<router-link to="/admin/employees" class="action-card">
|
||||
<div class="action-icon">👤</div>
|
||||
<h3>Список сотрудников</h3>
|
||||
<p>Управление учетными записями</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<AdminFooter />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AdminHeader from '@/components/AdminHeader.vue'
|
||||
import AdminFooter from '@/components/AdminFooter.vue'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'AdminDashboardView',
|
||||
components: {
|
||||
AdminHeader,
|
||||
AdminFooter
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statistics: {
|
||||
total_requests: 0,
|
||||
by_status: {},
|
||||
by_priority: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
try {
|
||||
const response = await axios.get('/api/requests/statistics', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('admin_token')}`
|
||||
}
|
||||
});
|
||||
this.statistics = response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching statistics:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-layout {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.admin-main {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
color: #1a237e;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-card h3 {
|
||||
color: #1a237e;
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: #1a237e;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.actions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.action-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.action-card h3 {
|
||||
color: #1a237e;
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.action-card p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.admin-main {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.actions-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,97 +1,173 @@
|
||||
<template>
|
||||
<div class="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6">
|
||||
<h2 class="text-2xl font-semibold text-slate-800 mb-4">Вход в админ-панель</h2>
|
||||
<div class="admin-login">
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<h1>IT Support</h1>
|
||||
<p>Панель администратора</p>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleSubmit" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">
|
||||
Логин
|
||||
</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center">
|
||||
<UserIcon :size="18" class="text-slate-400" />
|
||||
</div>
|
||||
<form @submit.prevent="handleLogin" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Имя пользователя</label>
|
||||
<input
|
||||
v-model="username"
|
||||
type="text"
|
||||
id="username"
|
||||
v-model="username"
|
||||
required
|
||||
class="w-full pl-10 pr-3 py-2 border border-slate-300 rounded-md focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Введите логин"
|
||||
/>
|
||||
class="form-input"
|
||||
placeholder="Введите имя пользователя"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">
|
||||
Пароль
|
||||
</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center">
|
||||
<LockIcon :size="18" class="text-slate-400" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Пароль</label>
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
id="password"
|
||||
v-model="password"
|
||||
required
|
||||
class="w-full pl-10 pr-3 py-2 border border-slate-300 rounded-md focus:ring-2 focus:ring-blue-500"
|
||||
class="form-input"
|
||||
placeholder="Введите пароль"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="isLoading"
|
||||
class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
<component
|
||||
:is="isLoading ? LoaderIcon : LogInIcon"
|
||||
:size="18"
|
||||
:class="{ 'animate-spin': isLoading }"
|
||||
/>
|
||||
{{ isLoading ? 'Вход...' : 'Войти' }}
|
||||
</button>
|
||||
<button type="submit" class="login-button" :disabled="isLoading">
|
||||
{{ isLoading ? 'Вход...' : 'Войти' }}
|
||||
</button>
|
||||
|
||||
<div class="text-center">
|
||||
<router-link
|
||||
to="/"
|
||||
class="text-sm text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Вернуться к входу для сотрудников
|
||||
</router-link>
|
||||
</div>
|
||||
</form>
|
||||
<p v-if="error" class="error-message">{{ error }}</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { UserIcon, LockIcon, LogInIcon, LoaderIcon } from 'lucide-vue-next';
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
const isLoading = ref(false);
|
||||
|
||||
async function handleSubmit() {
|
||||
if (isLoading.value) return;
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const success = await authStore.adminLogin(username.value, password.value);
|
||||
if (success) {
|
||||
router.push('/admin/dashboard');
|
||||
} else {
|
||||
alert('Неверные учетные данные');
|
||||
export default {
|
||||
name: 'AdminLoginView',
|
||||
data() {
|
||||
return {
|
||||
username: '',
|
||||
password: '',
|
||||
error: '',
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleLogin() {
|
||||
this.error = ''
|
||||
this.isLoading = true
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/auth/admin/login', {
|
||||
username: this.username,
|
||||
password: this.password
|
||||
})
|
||||
|
||||
localStorage.setItem('admin_token', response.data.access_token)
|
||||
this.$router.push('/admin/dashboard')
|
||||
} catch (error) {
|
||||
this.error = error.response?.data?.detail || 'Ошибка при входе'
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка авторизации');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-login {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #1a237e 0%, #3949ab 100%);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
background: white;
|
||||
padding: 2.5rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
color: #1a237e;
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
color: #666;
|
||||
margin: 0.5rem 0 0;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
color: #1a237e;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
padding: 0.75rem;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #1a237e;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
background-color: #1a237e;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.login-button:hover:not(:disabled) {
|
||||
background-color: #283593;
|
||||
}
|
||||
|
||||
.login-button:disabled {
|
||||
background-color: #9fa8da;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #d32f2f;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user