1
0
mirror of https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git synced 2025-08-14 00:25:46 +02:00

переработка админ панели

This commit is contained in:
MoonTestUse1
2025-01-03 01:56:01 +06:00
parent 47cd588476
commit e3130696f4
4 changed files with 497 additions and 76 deletions

View File

@@ -0,0 +1,73 @@
<!-- AdminFooter.vue -->
<template>
<footer class="admin-footer">
<div class="footer-content">
<div class="footer-info">
<p>&copy; 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>

View 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>

View 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>

View File

@@ -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>
<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 class="admin-login">
<div class="login-container">
<div class="login-header">
<h1>IT Support</h1>
<p>Панель администратора</p>
</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="Введите логин"
/>
</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>
<input
v-model="password"
type="password"
required
class="w-full pl-10 pr-3 py-2 border border-slate-300 rounded-md focus:ring-2 focus:ring-blue-500"
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"
class="form-input"
placeholder="Введите имя пользователя"
>
<component
:is="isLoading ? LoaderIcon : LogInIcon"
:size="18"
:class="{ 'animate-spin': isLoading }"
/>
</div>
<div class="form-group">
<label for="password">Пароль</label>
<input
type="password"
id="password"
v-model="password"
required
class="form-input"
placeholder="Введите пароль"
>
</div>
<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>
<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) {
alert('Ошибка авторизации');
this.error = error.response?.data?.detail || 'Ошибка при входе'
} finally {
isLoading.value = false;
this.isLoading = 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>