fix: небольшие фиксы фронта
This commit is contained in:
@@ -105,7 +105,9 @@
|
||||
--color-error-a40: rgba(239,68,68,0.4);
|
||||
|
||||
--color-aqua-a15: rgba(6,182,212,0.15);
|
||||
--color-aqua-a25: rgba(6,182,212,0.25);
|
||||
--color-aqua-a30: rgba(6,182,212,0.3);
|
||||
--color-aqua-a40: rgba(6,182,212,0.4);
|
||||
--color-orange-a15: rgba(251,146,60,0.15);
|
||||
--color-orange-a30: rgba(251,146,60,0.3);
|
||||
--color-purple-a12: rgba(139,92,246,0.12);
|
||||
@@ -141,8 +143,11 @@
|
||||
--gradient-stats-aqua: linear-gradient(90deg, var(--color-aqua), var(--color-aqua-light));
|
||||
--gradient-stats-orange: linear-gradient(90deg, var(--color-orange), var(--color-yellow));
|
||||
--gradient-stats-purple: linear-gradient(90deg, var(--color-purple), var(--color-purple-light));
|
||||
--gradient-coin-chip: linear-gradient(135deg, var(--color-star-a20), var(--color-warning-a15));
|
||||
--gradient-coin-chip-hover: linear-gradient(135deg, var(--color-star-a30), var(--color-warning-a25));
|
||||
--gradient-coin-chip: linear-gradient(135deg, var(--color-primary-a25) 0%, var(--color-aqua-a25) 100%);
|
||||
--gradient-coin-chip-hover: linear-gradient(135deg, var(--color-primary-a40) 0%, var(--color-aqua-a40) 100%);
|
||||
--color-coin-chip-border: var(--color-primary-a45);
|
||||
--color-coin-chip-text: var(--color-primary-border);
|
||||
--color-coin-chip-label: var(--color-aqua-dark);
|
||||
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
|
||||
@@ -14,7 +14,6 @@ const navItems: NavItem[] = [
|
||||
{ label: 'Каталог', icon: 'books', to: '/catalog', roles: ['student'] },
|
||||
{ label: 'Мои записи', icon: 'clipboard-list', to: '/my-lectures', roles: ['student'] },
|
||||
{ label: 'Достижения', icon: 'trophy', to: '/achievements', roles: ['student'] },
|
||||
{ label: 'Уведомления', icon: 'bell', to: '/notifications', roles: ['student'] },
|
||||
{ label: 'Профиль', icon: 'user', to: '/profile', roles: ['student'] },
|
||||
// Teacher
|
||||
{ label: 'Дашборд', icon: 'chart-bar', to: '/teacher', roles: ['teacher'] },
|
||||
|
||||
@@ -18,7 +18,7 @@ defineProps<{ amount: number }>()
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
background: var(--gradient-coin-chip);
|
||||
border: 1px solid var(--color-warning-a40);
|
||||
border: 1px solid var(--color-coin-chip-border);
|
||||
border-radius: 20px;
|
||||
padding: 5px 12px;
|
||||
cursor: default;
|
||||
@@ -28,7 +28,7 @@ defineProps<{ amount: number }>()
|
||||
.coin-chip:hover {
|
||||
background: var(--gradient-coin-chip-hover);
|
||||
}
|
||||
.coin-icon { color: var(--color-brown-dark); }
|
||||
.coin-amount { font-weight: 800; font-size: 14px; color: var(--color-brown-dark); }
|
||||
.coin-label { font-size: 12px; color: var(--color-warning-text); }
|
||||
.coin-icon { color: var(--color-coin-chip-text); }
|
||||
.coin-amount { font-weight: 800; font-size: 14px; color: var(--color-coin-chip-text); }
|
||||
.coin-label { font-size: 12px; color: var(--color-coin-chip-label); }
|
||||
</style>
|
||||
|
||||
@@ -7,12 +7,6 @@ import StatusBadge from '@/components/ui/StatusBadge.vue'
|
||||
import { lecturesApi, reviewsApi, syncApi, usersApi } from '@/api'
|
||||
import type { LectureDto, ReviewDto, SyncStatusDto, UserDto } from '@/api/types'
|
||||
|
||||
const disciplines = [
|
||||
{ name: 'Информатика и ИИ', value: 80 },
|
||||
{ name: 'Экономика и маркетинг', value: 55 },
|
||||
{ name: 'Философия и этика', value: 42 },
|
||||
{ name: 'Право и политика', value: 36 },
|
||||
]
|
||||
const users = ref<UserDto[]>([])
|
||||
const lectures = ref<LectureDto[]>([])
|
||||
const reviews = ref<ReviewDto[]>([])
|
||||
@@ -50,34 +44,6 @@ onMounted(async () => {
|
||||
<StatsWidget label="Отзывов в LLM" :value="reviews.length" icon="message-circle" color="purple" />
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<GlassCard>
|
||||
<div class="section-title">Популярные дисциплины</div>
|
||||
<div class="bars">
|
||||
<div class="bar-row" v-for="d in disciplines" :key="d.name">
|
||||
<span>{{ d.name }}</span>
|
||||
<div class="bar">
|
||||
<div class="bar-fill" :style="{ width: `${d.value}%` }"></div>
|
||||
</div>
|
||||
<span class="percent">{{ d.value }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard>
|
||||
<div class="section-title">Межфакультетская вовлеченность</div>
|
||||
<div class="metric">46% студентов посещают лекции вне своего института</div>
|
||||
<ProgressBar :value="46" :max="100" />
|
||||
<div class="section-title">Активность студентов</div>
|
||||
<div class="activity">
|
||||
<div class="day" v-for="n in 7" :key="n">
|
||||
<div class="day-bar" :style="{ height: `${40 + n * 6}px` }"></div>
|
||||
<span>Д{{ n }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<GlassCard>
|
||||
<div class="section-title">Состояние синхронизации расписания</div>
|
||||
@@ -99,15 +65,6 @@ onMounted(async () => {
|
||||
.admin-dashboard { display: flex; flex-direction: column; gap: 18px; }
|
||||
.stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; }
|
||||
.bars { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
|
||||
.bar-row { display: grid; grid-template-columns: 1fr 2fr auto; align-items: center; gap: 8px; font-size: 13px; }
|
||||
.bar { background: var(--color-black-a08); border-radius: 6px; height: 8px; overflow: hidden; }
|
||||
.bar-fill { background: var(--gradient-progress-success); height: 100%; }
|
||||
.percent { color: var(--color-text-secondary); font-size: 12px; }
|
||||
.metric { margin-bottom: 10px; color: var(--color-text-secondary); }
|
||||
.activity { display: flex; gap: 10px; margin-top: 12px; align-items: flex-end; }
|
||||
.day { display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; color: var(--color-text-secondary); }
|
||||
.day-bar { width: 16px; background: var(--gradient-bar-neutral-vertical); border-radius: 6px 6px 0 0; }
|
||||
.sync-meta { font-size: 12px; color: var(--color-text-secondary); margin-top: 6px; }
|
||||
.sync-error { font-size: 12px; color: var(--color-error); margin-top: 8px; }
|
||||
.queue-meta { font-size: 12px; color: var(--color-text-secondary); margin-bottom: 8px; }
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import GlassCard from '@/components/ui/GlassCard.vue'
|
||||
import AchievementBadge from '@/components/ui/AchievementBadge.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
|
||||
@@ -11,13 +10,6 @@ const auth = useAuthStore()
|
||||
const unlocked = computed(() => userStore.achievements.filter(a => a.unlocked))
|
||||
const locked = computed(() => userStore.achievements.filter(a => !a.unlocked))
|
||||
|
||||
const rewards = [
|
||||
{ id: 'r1', title: 'Стикерпак UniVerse', price: 80, available: true },
|
||||
{ id: 'r2', title: 'Термокружка ЮФУ', price: 150, available: true },
|
||||
{ id: 'r3', title: 'Доп. консультация преподавателя', price: 220, available: false },
|
||||
{ id: 'r4', title: 'Цифровой бейдж «Research Explorer»', price: 60, available: true },
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (auth.user) void userStore.fetchStudentData(auth.user.id)
|
||||
})
|
||||
@@ -25,7 +17,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="achievements page-content">
|
||||
<h1 class="page-title">Достижения и магазин наград</h1>
|
||||
<h1 class="page-title">Достижения</h1>
|
||||
|
||||
<section>
|
||||
<h2 class="section-title">Полученные достижения</h2>
|
||||
@@ -66,27 +58,10 @@ onMounted(() => {
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="section-title">Магазин наград</h2>
|
||||
<div class="rewards">
|
||||
<GlassCard v-for="r in rewards" :key="r.id" class="reward-card">
|
||||
<div class="reward-title">{{ r.title }}</div>
|
||||
<div class="reward-price">{{ r.price }} монет</div>
|
||||
<button class="btn-primary" :disabled="!r.available">
|
||||
{{ r.available ? 'Купить' : 'Недоступно' }}
|
||||
</button>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.achievements { display: flex; flex-direction: column; gap: 20px; }
|
||||
.list { display: flex; flex-direction: column; gap: 12px; }
|
||||
.rewards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
|
||||
.reward-card { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
|
||||
.reward-title { font-weight: 700; }
|
||||
.reward-price { color: var(--color-text-secondary); font-size: 13px; }
|
||||
</style>
|
||||
|
||||
@@ -70,6 +70,7 @@ const typeIcon: Record<string, string> = {
|
||||
min-height: calc(100vh - var(--topbar-height) - 28px - 80px);
|
||||
}
|
||||
.header { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.header .page-title { margin-bottom: 0; }
|
||||
.notification-groups { display: flex; flex-direction: column; gap: 14px; }
|
||||
.group-title { font-weight: 700; margin-bottom: 10px; }
|
||||
.items { display: flex; flex-direction: column; gap: 10px; }
|
||||
|
||||
@@ -3,10 +3,8 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import GlassCard from '@/components/ui/GlassCard.vue'
|
||||
import CoinChip from '@/components/ui/CoinChip.vue'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import AchievementBadge from '@/components/ui/AchievementBadge.vue'
|
||||
import DataTable from '@/components/ui/DataTable.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
|
||||
const auth = useAuthStore()
|
||||
@@ -54,12 +52,6 @@ const interestTags = ref([
|
||||
|
||||
const notificationSettings = ref({ email: true })
|
||||
|
||||
const historyColumns = [
|
||||
{ key: 'date', label: 'Дата' },
|
||||
{ key: 'description', label: 'Описание' },
|
||||
{ key: 'amount', label: 'Монеты', align: 'right' },
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
void userStore.fetchStudentData(user.value.id)
|
||||
})
|
||||
@@ -69,7 +61,6 @@ onMounted(() => {
|
||||
<div class="profile page-content">
|
||||
<div class="header">
|
||||
<h1 class="page-title">Профиль пользователя</h1>
|
||||
<CoinChip :amount="user.coins" />
|
||||
</div>
|
||||
|
||||
<div class="profile-grid">
|
||||
@@ -118,7 +109,7 @@ onMounted(() => {
|
||||
<EmptyState
|
||||
v-if="!userStore.achievements.length"
|
||||
title="Достижений пока нет"
|
||||
subtitle="Они появятся после посещений, отзывов и начислений."
|
||||
subtitle="Они появятся после посещений и отзывов."
|
||||
/>
|
||||
<div v-else class="achievements">
|
||||
<AchievementBadge
|
||||
@@ -129,25 +120,10 @@ onMounted(() => {
|
||||
:description="a.description"
|
||||
:unlocked="a.unlocked"
|
||||
:unlockedAt="a.unlockedAt"
|
||||
:coins="a.coins"
|
||||
/>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
<GlassCard>
|
||||
<div class="section-title">История начисления монет</div>
|
||||
<EmptyState
|
||||
v-if="!userStore.coinHistory.length"
|
||||
title="История монет пуста"
|
||||
subtitle="Начисления появятся после активностей на платформе."
|
||||
/>
|
||||
<DataTable :columns="historyColumns" :rows="userStore.coinHistory">
|
||||
<template #amount="{ value }">
|
||||
<span :class="value > 0 ? 'positive' : 'negative'">{{ value > 0 ? `+${value}` : value }}</span>
|
||||
</template>
|
||||
</DataTable>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -165,6 +141,4 @@ onMounted(() => {
|
||||
.settings { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
|
||||
.setting { font-size: 13px; color: var(--color-text-secondary); display: flex; gap: 8px; align-items: center; }
|
||||
.achievements { display: flex; flex-direction: column; gap: 12px; margin-top: 10px; }
|
||||
.positive { color: #166534; font-weight: 600; }
|
||||
.negative { color: #991B1B; font-weight: 600; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user