feat: подготовил дизайн (изменения из другого репозитория)
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 5s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 8s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 3s

This commit is contained in:
2026-05-08 01:06:22 +03:00
parent 655ab1b5c5
commit 047611fd24
54 changed files with 4497 additions and 28 deletions
@@ -0,0 +1,176 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useUserStore } from '@/stores/user'
import CoinChip from '@/components/ui/CoinChip.vue'
import SearchInput from '@/components/ui/SearchInput.vue'
const auth = useAuthStore()
const userStore = useUserStore()
const router = useRouter()
const searchQuery = ref('')
const roleLabels: Record<string, string> = {
student: 'Студент',
teacher: 'Преподаватель',
admin: 'Администратор',
}
const unreadCount = computed(() => userStore.unreadCount())
function switchRole() {
auth.switchRole()
if (auth.user?.role === 'teacher') router.push('/teacher')
else if (auth.user?.role === 'admin') router.push('/admin')
else router.push('/')
}
function openProfile() {
router.push('/profile')
}
</script>
<template>
<header class="topbar">
<div class="topbar-brand">
<span class="brand-icon">🌍</span>
<span class="brand-name">UniVerse</span>
</div>
<div class="topbar-center">
<SearchInput v-model="searchQuery" placeholder="Поиск лекций, преподавателей, тегов" />
</div>
<div class="topbar-right">
<CoinChip v-if="auth.user" :amount="auth.user.coins" />
<button class="role-btn" @click="switchRole" v-if="auth.user">
{{ roleLabels[auth.user.role] }}
</button>
<button class="notif-btn" @click="$router.push('/notifications')">
🔔
<span class="notif-dot" v-if="auth.user && unreadCount > 0">
{{ unreadCount }}
</span>
</button>
<div
class="avatar"
role="button"
tabindex="0"
@click="openProfile"
@keydown.enter.prevent="openProfile"
@keydown.space.prevent="openProfile"
>
<span class="avatar-icon">👤</span>
<span class="avatar-name" v-if="auth.user">{{ auth.user.name.split(' ')[0] }}</span>
</div>
</div>
</header>
</template>
<style scoped>
.topbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--topbar-height);
background: rgba(255,255,255,0.8);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-bottom: 1px solid var(--color-border-glass);
box-shadow: 0 2px 20px rgba(0,0,0,0.06);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
z-index: 100;
gap: 12px;
}
.topbar-brand {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
cursor: pointer;
}
.brand-icon { font-size: 24px; }
.brand-name {
font-size: 20px;
font-weight: 800;
background: var(--gradient-brand);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.topbar-center { flex: 1; max-width: 400px; }
.topbar-right {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.role-btn {
background: rgba(34,197,94,0.12);
border: 1px solid rgba(34,197,94,0.3);
border-radius: var(--radius-sm);
padding: 5px 10px;
font-size: 12px;
font-weight: 600;
color: var(--color-primary-dark);
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.role-btn:hover { background: rgba(34,197,94,0.2); }
.notif-btn {
position: relative;
background: none;
border: none;
font-size: 20px;
cursor: pointer;
padding: 4px;
border-radius: 50%;
transition: background 0.2s;
}
.notif-btn:hover { background: rgba(0,0,0,0.05); }
.notif-dot {
position: absolute;
top: -2px;
right: -2px;
background: var(--color-error);
color: #fff;
font-size: 9px;
font-weight: 700;
width: 16px;
height: 16px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.avatar {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
padding: 5px 10px;
border-radius: 20px;
border: 1px solid var(--color-border-glass);
background: rgba(255,255,255,0.5);
transition: all 0.2s;
}
.avatar:hover { background: rgba(255,255,255,0.8); }
.avatar-icon { font-size: 18px; }
.avatar-name { font-size: 13px; font-weight: 600; color: var(--color-text); }
@media (max-width: 640px) {
.topbar-center { display: none; }
.brand-name { display: none; }
.avatar-name { display: none; }
.role-btn { display: none; }
}
</style>