фикс дизайна админ панели

This commit is contained in:
Professional 2025-05-26 00:11:44 +07:00
parent a5a01c02fa
commit 26fd7ab77c
2 changed files with 490 additions and 15 deletions

View File

@ -1,39 +1,118 @@
<template> <template>
<div class="admin-dashboard"> <div class="admin-dashboard">
<div class="admin-header"> <div class="admin-header">
<h1>Административная панель</h1> <div class="admin-header-left">
<button v-if="isMobile" @click="toggleSidebar" class="menu-toggle-btn">
<i class="bi-list"></i>
</button>
<h1>Административная панель</h1>
</div>
<div class="admin-user-info"> <div class="admin-user-info">
<span>{{ user?.name || 'Администратор' }}</span> <span class="user-name">{{ user?.name || 'Администратор' }}</span>
<button @click="logout" class="logout-btn">Выйти</button> <button @click="logout" class="logout-btn">
<span class="logout-text">Выйти</span>
<i class="bi-box-arrow-right"></i>
</button>
</div> </div>
</div> </div>
<div class="admin-content"> <div class="admin-content">
<div class="admin-sidebar"> <!-- Десктопная боковая панель -->
<div v-if="!isMobile" class="admin-sidebar">
<nav> <nav>
<router-link :to="{ name: 'AdminUsers' }" active-class="active">Пользователи</router-link> <router-link :to="{ name: 'AdminUsers' }" active-class="active">
<router-link :to="{ name: 'AdminConversations' }" active-class="active">Диалоги</router-link> <i class="bi-people"></i> Пользователи
<router-link :to="{ name: 'AdminStatistics' }" active-class="active">Статистика</router-link> </router-link>
<router-link :to="{ name: 'AdminConversations' }" active-class="active">
<i class="bi-chat-dots"></i> Диалоги
</router-link>
<router-link :to="{ name: 'AdminStatistics' }" active-class="active">
<i class="bi-graph-up"></i> Статистика
</router-link>
</nav> </nav>
</div> </div>
<!-- Мобильное боковое меню -->
<div v-if="isMobile" class="admin-sidebar-mobile" :class="{ 'sidebar-open': sidebarOpen }">
<div class="sidebar-overlay" @click="toggleSidebar"></div>
<div class="sidebar-content">
<div class="sidebar-header">
<span>Меню</span>
<button @click="toggleSidebar" class="close-sidebar-btn">
<i class="bi-x-lg"></i>
</button>
</div>
<nav>
<router-link :to="{ name: 'AdminUsers' }" active-class="active" @click="sidebarOpen = false">
<i class="bi-people"></i> Пользователи
</router-link>
<router-link :to="{ name: 'AdminConversations' }" active-class="active" @click="sidebarOpen = false">
<i class="bi-chat-dots"></i> Диалоги
</router-link>
<router-link :to="{ name: 'AdminStatistics' }" active-class="active" @click="sidebarOpen = false">
<i class="bi-graph-up"></i> Статистика
</router-link>
<div class="mobile-logout-container">
<button @click="logout" class="mobile-logout-btn">
<i class="bi-box-arrow-right"></i> Выйти
</button>
</div>
</nav>
</div>
</div>
<div class="admin-main-content"> <div class="admin-main-content">
<router-view></router-view> <router-view></router-view>
</div> </div>
</div> </div>
<!-- Мобильная нижняя навигация -->
<nav v-if="isMobile" class="admin-mobile-nav">
<router-link :to="{ name: 'AdminUsers' }" class="nav-item" active-class="active">
<div class="nav-icon">
<i class="bi-people"></i>
</div>
<span class="nav-text">Пользователи</span>
</router-link>
<router-link :to="{ name: 'AdminConversations' }" class="nav-item" active-class="active">
<div class="nav-icon">
<i class="bi-chat-dots"></i>
</div>
<span class="nav-text">Диалоги</span>
</router-link>
<router-link :to="{ name: 'AdminStatistics' }" class="nav-item" active-class="active">
<div class="nav-icon">
<i class="bi-graph-up"></i>
</div>
<span class="nav-text">Статистика</span>
</router-link>
</nav>
</div> </div>
</template> </template>
<script> <script>
import { useAuth } from '../../auth'; import { useAuth } from '../../auth';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { computed } from 'vue'; import { computed, ref, onMounted, onBeforeUnmount, watch } from 'vue';
export default { export default {
name: 'AdminDashboard', name: 'AdminDashboard',
setup() { setup() {
const { user, logout: authLogout } = useAuth(); const { user, logout: authLogout } = useAuth();
const router = useRouter(); const router = useRouter();
const isMobile = ref(window.innerWidth < 768);
const sidebarOpen = ref(false);
const handleResize = () => {
isMobile.value = window.innerWidth < 768;
if (!isMobile.value) {
sidebarOpen.value = false;
}
};
const toggleSidebar = () => {
sidebarOpen.value = !sidebarOpen.value;
};
const logout = async () => { const logout = async () => {
try { try {
@ -44,9 +123,27 @@ export default {
} }
}; };
onMounted(() => {
window.addEventListener('resize', handleResize);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize);
});
// Закрываем боковое меню при изменении маршрута
watch(() => router.currentRoute.value.path, () => {
if (sidebarOpen.value) {
sidebarOpen.value = false;
}
});
return { return {
user: computed(() => user.value), user: computed(() => user.value),
logout logout,
isMobile,
sidebarOpen,
toggleSidebar
}; };
} }
} }
@ -68,6 +165,13 @@ export default {
background-color: #ff3e68; background-color: #ff3e68;
color: white; color: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 100;
}
.admin-header-left {
display: flex;
align-items: center;
gap: 0.75rem;
} }
.admin-header h1 { .admin-header h1 {
@ -75,6 +179,18 @@ export default {
margin: 0; margin: 0;
} }
.menu-toggle-btn {
background: none;
border: none;
color: white;
font-size: 1.5rem;
cursor: pointer;
padding: 0.25rem;
display: flex;
align-items: center;
justify-content: center;
}
.admin-user-info { .admin-user-info {
display: flex; display: flex;
align-items: center; align-items: center;
@ -89,6 +205,9 @@ export default {
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 500;
display: flex;
align-items: center;
gap: 0.5rem;
} }
.admin-content { .admin-content {
@ -97,11 +216,13 @@ export default {
overflow: hidden; overflow: hidden;
} }
/* Стили для десктопной боковой панели */
.admin-sidebar { .admin-sidebar {
width: 200px; width: 200px;
background-color: white; background-color: white;
box-shadow: 1px 0 3px rgba(0,0,0,0.1); box-shadow: 1px 0 3px rgba(0,0,0,0.1);
overflow-y: auto; overflow-y: auto;
flex-shrink: 0;
} }
.admin-sidebar nav { .admin-sidebar nav {
@ -115,6 +236,13 @@ export default {
color: #333; color: #333;
text-decoration: none; text-decoration: none;
transition: background-color 0.3s; transition: background-color 0.3s;
display: flex;
align-items: center;
gap: 0.75rem;
}
.admin-sidebar a i {
font-size: 1.2rem;
} }
.admin-sidebar a:hover { .admin-sidebar a:hover {
@ -126,9 +254,243 @@ export default {
color: white; color: white;
} }
/* Стили для мобильной боковой панели */
.admin-sidebar-mobile {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: none;
}
.admin-sidebar-mobile.sidebar-open {
display: block;
}
.sidebar-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 1;
}
.sidebar-content {
position: absolute;
top: 0;
left: 0;
width: 80%;
max-width: 300px;
height: 100%;
background-color: white;
z-index: 2;
box-shadow: 2px 0 10px rgba(0,0,0,0.2);
display: flex;
flex-direction: column;
animation: slideIn 0.25s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
background-color: #ff3e68;
color: white;
}
.close-sidebar-btn {
background: none;
border: none;
color: white;
font-size: 1.2rem;
cursor: pointer;
}
.admin-sidebar-mobile nav {
display: flex;
flex-direction: column;
flex: 1;
padding: 1rem 0;
}
.admin-sidebar-mobile a {
padding: 1rem 1.5rem;
color: #333;
text-decoration: none;
display: flex;
align-items: center;
gap: 0.75rem;
}
.admin-sidebar-mobile a i {
font-size: 1.2rem;
}
.admin-sidebar-mobile a.active {
background-color: rgba(255, 62, 104, 0.1);
color: #ff3e68;
font-weight: 500;
}
.mobile-logout-container {
margin-top: auto;
padding: 1rem 1.5rem;
border-top: 1px solid #eee;
}
.mobile-logout-btn {
display: flex;
align-items: center;
gap: 0.75rem;
width: 100%;
padding: 0.75rem;
background-color: #f8f9fa;
color: #dc3545;
border: 1px solid #dee2e6;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
text-align: left;
}
.admin-main-content { .admin-main-content {
flex: 1; flex: 1;
padding: 1.5rem; padding: 1.5rem;
overflow-y: auto; overflow-y: auto;
height: calc(100vh - var(--header-height) - var(--nav-height, 0px));
}
/* Мобильная навигация */
.admin-mobile-nav {
display: none;
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: var(--nav-height, 60px);
background: white;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
padding-bottom: env(safe-area-inset-bottom, 0);
}
/* На мобильных устройствах */
@media (max-width: 767px) {
.admin-header h1 {
font-size: 1.25rem;
}
.user-name {
display: none;
}
.logout-text {
display: none;
}
.logout-btn {
padding: 0.4rem;
}
.logout-btn i {
font-size: 1.2rem;
margin: 0;
}
.admin-main-content {
padding: 1rem;
height: calc(100vh - 56px - 56px - env(safe-area-inset-bottom, 0px)); /* header + nav + safe area */
padding-bottom: 1rem;
}
.admin-mobile-nav {
display: flex;
--nav-height: 56px;
}
}
/* CSS для мобильной навигации в админ-панели */
.admin-mobile-nav .nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-decoration: none;
color: #6c757d;
transition: all 0.2s ease;
padding: 0;
position: relative;
}
.admin-mobile-nav .nav-icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 3px;
height: 28px;
}
.admin-mobile-nav .nav-text {
font-size: 0.7rem;
font-weight: 500;
transform: translateY(-10px);
display: block;
}
.admin-mobile-nav .nav-item.active {
color: #ff3e68;
}
.admin-mobile-nav .nav-item.active .nav-icon i {
color: #ff3e68;
transform: translateY(-2px);
}
:root {
--header-height: 56px;
}
/* Поддержка устройств с вырезом (iPhone X и новее) */
@supports (padding: env(safe-area-inset-bottom)) {
.admin-mobile-nav {
height: calc(var(--nav-height) + env(safe-area-inset-bottom, 0px));
padding-bottom: env(safe-area-inset-bottom, 0px);
}
}
/* Ландшафтная ориентация на мобильных */
@media (max-height: 450px) and (orientation: landscape) {
.admin-mobile-nav {
--nav-height: 50px;
}
.admin-mobile-nav .nav-item {
flex-direction: row;
gap: 5px;
}
.admin-mobile-nav .nav-icon {
margin-bottom: 0;
}
.admin-main-content {
height: calc(100vh - 56px - 50px);
}
} }
</style> </style>

View File

@ -40,7 +40,7 @@
<th>ID</th> <th>ID</th>
<th>Имя</th> <th>Имя</th>
<th>Email</th> <th>Email</th>
<th>Дата регистрации</th> <th class="hide-sm">Дата регистрации</th>
<th>Статус</th> <th>Статус</th>
<th>Действия</th> <th>Действия</th>
</tr> </tr>
@ -49,8 +49,8 @@
<tr v-for="user in users" :key="user._id"> <tr v-for="user in users" :key="user._id">
<td>{{ shortenId(user._id) }}</td> <td>{{ shortenId(user._id) }}</td>
<td>{{ user.name }}</td> <td>{{ user.name }}</td>
<td>{{ user.email }}</td> <td class="email-cell">{{ user.email }}</td>
<td>{{ formatDate(user.createdAt) }}</td> <td class="hide-sm">{{ formatDate(user.createdAt) }}</td>
<td> <td>
<span class="status-badge" :class="{ 'active': user.isActive, 'blocked': !user.isActive }"> <span class="status-badge" :class="{ 'active': user.isActive, 'blocked': !user.isActive }">
{{ user.isActive ? 'Активен' : 'Заблокирован' }} {{ user.isActive ? 'Активен' : 'Заблокирован' }}
@ -58,14 +58,16 @@
</td> </td>
<td class="actions"> <td class="actions">
<button @click="viewUser(user._id)" class="btn view-btn"> <button @click="viewUser(user._id)" class="btn view-btn">
Просмотр <span class="btn-text">Просмотр</span>
<i class="bi-eye"></i>
</button> </button>
<button <button
@click="toggleUserStatus(user._id, user.isActive)" @click="toggleUserStatus(user._id, user.isActive)"
class="btn" class="btn"
:class="user.isActive ? 'block-btn' : 'unblock-btn'" :class="user.isActive ? 'block-btn' : 'unblock-btn'"
> >
{{ user.isActive ? 'Заблокировать' : 'Разблокировать' }} <span class="btn-text">{{ user.isActive ? 'Заблокировать' : 'Разблокировать' }}</span>
<i :class="user.isActive ? 'bi-lock-fill' : 'bi-unlock-fill'"></i>
</button> </button>
</td> </td>
</tr> </tr>
@ -263,6 +265,7 @@ export default {
<style scoped> <style scoped>
.admin-users { .admin-users {
padding-bottom: 2rem; padding-bottom: 2rem;
max-width: 100%;
} }
h2 { h2 {
@ -287,7 +290,8 @@ h2 {
.filter-options { .filter-options {
display: flex; display: flex;
gap: 1.5rem; flex-wrap: wrap;
gap: 1rem;
} }
.filter-options label { .filter-options label {
@ -313,12 +317,15 @@ h2 {
.users-table-container { .users-table-container {
overflow-x: auto; overflow-x: auto;
width: 100%;
margin-bottom: env(safe-area-inset-bottom, 0px);
} }
.users-table { .users-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin-bottom: 1rem; margin-bottom: 1rem;
min-width: 600px; /* Минимальная ширина таблицы */
} }
.users-table th, .users-table td { .users-table th, .users-table td {
@ -331,12 +338,14 @@ h2 {
background-color: #f5f5f5; background-color: #f5f5f5;
font-weight: 500; font-weight: 500;
color: #444; color: #444;
white-space: nowrap;
} }
.status-badge { .status-badge {
padding: 0.3rem 0.6rem; padding: 0.3rem 0.6rem;
border-radius: 20px; border-radius: 20px;
font-size: 0.875rem; font-size: 0.875rem;
white-space: nowrap;
} }
.status-badge.active { .status-badge.active {
@ -352,6 +361,7 @@ h2 {
.actions { .actions {
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
white-space: nowrap;
} }
.btn { .btn {
@ -361,6 +371,9 @@ h2 {
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 500;
font-size: 0.875rem; font-size: 0.875rem;
display: flex;
align-items: center;
gap: 0.5rem;
} }
.view-btn { .view-btn {
@ -390,6 +403,7 @@ h2 {
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
margin-top: 1.5rem; margin-top: 1.5rem;
flex-wrap: wrap;
} }
.pagination-btn { .pagination-btn {
@ -408,4 +422,103 @@ h2 {
.pagination-info { .pagination-info {
color: #666; color: #666;
} }
/* Адаптивное отображение */
@media (max-width: 991px) {
.users-table {
font-size: 0.9rem;
}
.users-table th, .users-table td {
padding: 0.6rem;
}
.btn {
padding: 0.3rem 0.6rem;
}
}
/* Планшеты и маленькие экраны */
@media (max-width: 767px) {
h2 {
font-size: 1.5rem;
}
/* Скрываем менее важные данные на мобильных */
.hide-sm {
display: none;
}
.email-cell {
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.filter-options {
justify-content: space-between;
width: 100%;
}
.users-table {
min-width: auto;
font-size: 0.85rem;
}
.status-badge {
font-size: 0.75rem;
padding: 0.2rem 0.5rem;
}
.btn-text {
display: none;
}
.btn {
padding: 0.4rem;
display: inline-flex;
align-items: center;
justify-content: center;
}
.btn i {
margin: 0;
font-size: 1.1rem;
}
/* На мобильных добавляем отступ снизу для нижней навигации */
.admin-users {
padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px));
}
}
/* Мелкие мобильные устройства */
@media (max-width: 420px) {
.users-table th, .users-table td {
padding: 0.5rem 0.4rem;
}
.email-cell {
max-width: 80px;
}
}
/* Устройства с вырезом (notch) */
@supports (padding: env(safe-area-inset-bottom)) {
.admin-users {
padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px));
}
}
/* Ландшафтная ориентация на мобильных */
@media (max-height: 450px) and (orientation: landscape) {
.users-table {
font-size: 0.8rem;
}
.admin-users {
padding-bottom: calc(50px + env(safe-area-inset-bottom, 0px));
}
}
</style> </style>