fix: перенёс уровни в бд и пофиксид их отображение на фронте
Backend CI / build-and-test (push) Successful in 52s
Frontend CI / build-and-check (push) Failing after 5m15s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 16s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m0s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 32s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 13s
Backend CI / build-and-test (push) Successful in 52s
Frontend CI / build-and-check (push) Failing after 5m15s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 16s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m0s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 32s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 13s
This commit is contained in:
@@ -45,6 +45,8 @@ export function mapApiUser(user: UserAuthDto | UserDto, stats?: UserStatsDto): U
|
||||
coins: stats?.coins ?? ('coins' in user ? user.coins : 0),
|
||||
level: stats?.level ?? ('level' in user ? user.level : 1),
|
||||
xp: stats?.xp ?? ('xp' in user ? user.xp : 0),
|
||||
currentLevelXp: stats?.currentLevelXp ?? 0,
|
||||
nextLevelXp: stats?.nextLevelXp,
|
||||
lecturesAttended: stats?.attendedLectures ?? 0,
|
||||
hoursLearned: stats ? Math.round(stats.attendedLectures * 1.5 * 10) / 10 : 0,
|
||||
achievements: stats ? Array.from({ length: stats.achievementsCount }, (_, index) => String(index + 1)) : [],
|
||||
|
||||
@@ -60,6 +60,8 @@ export interface UserStatsDto {
|
||||
coins: number
|
||||
level: number
|
||||
achievementsCount: number
|
||||
currentLevelXp: number
|
||||
nextLevelXp?: number | null
|
||||
}
|
||||
|
||||
export interface LectureDto {
|
||||
|
||||
@@ -4,6 +4,7 @@ defineProps<{
|
||||
max?: number
|
||||
label?: string
|
||||
color?: string
|
||||
text?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -14,12 +15,12 @@ defineProps<{
|
||||
<div
|
||||
class="progress-fill"
|
||||
:style="{
|
||||
width: `${Math.min(100, (value / (max ?? 100)) * 100)}%`,
|
||||
width: `${Math.max(0, Math.min(100, (value / (max && max > 0 ? max : 100)) * 100))}%`,
|
||||
background: color ?? 'var(--gradient-progress-success)'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div class="progress-text">{{ value }} / {{ max ?? 100 }}</div>
|
||||
<div class="progress-text">{{ text ?? `${value} / ${max ?? 100}` }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ export const useUserStore = defineStore('user', () => {
|
||||
coins: stats.coins,
|
||||
level: stats.level,
|
||||
xp: stats.xp,
|
||||
currentLevelXp: stats.currentLevelXp,
|
||||
nextLevelXp: stats.nextLevelXp,
|
||||
lecturesAttended: stats.attendedLectures,
|
||||
hoursLearned: Math.round(stats.attendedLectures * 1.5 * 10) / 10,
|
||||
achievements: Array.from({ length: stats.achievementsCount }, (_, index) => String(index + 1)),
|
||||
|
||||
@@ -14,6 +14,8 @@ export interface User {
|
||||
coins: number
|
||||
level: number
|
||||
xp?: number
|
||||
currentLevelXp?: number
|
||||
nextLevelXp?: number | null
|
||||
lecturesAttended?: number
|
||||
hoursLearned?: number
|
||||
achievements?: string[]
|
||||
|
||||
@@ -33,8 +33,25 @@ const recommended = computed(() =>
|
||||
)
|
||||
const achievements = computed(() => userStore.achievements.filter(a => a.unlocked).slice(0, 3))
|
||||
const reminders = computed(() => userStore.notifications.slice(0, 3))
|
||||
const xpToNext = 200
|
||||
const xpProgress = computed(() => user.value.xp ?? 120)
|
||||
const currentLevelXp = computed(() => user.value.currentLevelXp ?? 0)
|
||||
const nextLevelXp = computed(() => user.value.nextLevelXp)
|
||||
const userXp = computed(() => user.value.xp ?? 0)
|
||||
const hasLevelProgress = computed(() => nextLevelXp.value !== undefined)
|
||||
const hasNextLevel = computed(() => typeof nextLevelXp.value === 'number' && nextLevelXp.value > currentLevelXp.value)
|
||||
const levelProgressMax = computed(() => hasNextLevel.value ? nextLevelXp.value! - currentLevelXp.value : 1)
|
||||
const levelProgress = computed(() => {
|
||||
if (!hasLevelProgress.value) return 0
|
||||
if (!hasNextLevel.value) return 1
|
||||
return Math.min(Math.max(userXp.value - currentLevelXp.value, 0), levelProgressMax.value)
|
||||
})
|
||||
const levelProgressLabel = computed(() =>
|
||||
!hasLevelProgress.value
|
||||
? `Уровень ${user.value.level}`
|
||||
: hasNextLevel.value ? `Прогресс до уровня ${user.value.level + 1}` : 'Максимальный уровень'
|
||||
)
|
||||
const levelProgressText = computed(() =>
|
||||
hasNextLevel.value ? `${levelProgress.value} / ${levelProgressMax.value} XP` : `${userXp.value} XP`
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
@@ -99,10 +116,10 @@ onMounted(async () => {
|
||||
<GlassCard>
|
||||
<div class="xp-section">
|
||||
<div class="xp-header">
|
||||
<span class="xp-label">Прогресс до уровня {{ user.level + 1 }}</span>
|
||||
<span class="xp-val">{{ xpProgress }} / {{ xpToNext }} XP</span>
|
||||
<span class="xp-label">{{ levelProgressLabel }}</span>
|
||||
<span class="xp-val">{{ levelProgressText }}</span>
|
||||
</div>
|
||||
<ProgressBar :value="xpProgress" :max="xpToNext" />
|
||||
<ProgressBar :value="levelProgress" :max="levelProgressMax" :text="levelProgressText" />
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
|
||||
@@ -24,6 +24,25 @@ const userYearLine = computed(() => {
|
||||
const year = user.value.year ?? 0
|
||||
return Number.isFinite(year) && year > 0 ? `${year} курс` : ''
|
||||
})
|
||||
const currentLevelXp = computed(() => user.value.currentLevelXp ?? 0)
|
||||
const nextLevelXp = computed(() => user.value.nextLevelXp)
|
||||
const userXp = computed(() => user.value.xp ?? 0)
|
||||
const hasLevelProgress = computed(() => nextLevelXp.value !== undefined)
|
||||
const hasNextLevel = computed(() => typeof nextLevelXp.value === 'number' && nextLevelXp.value > currentLevelXp.value)
|
||||
const levelProgressMax = computed(() => hasNextLevel.value ? nextLevelXp.value! - currentLevelXp.value : 1)
|
||||
const levelProgress = computed(() => {
|
||||
if (!hasLevelProgress.value) return 0
|
||||
if (!hasNextLevel.value) return 1
|
||||
return Math.min(Math.max(userXp.value - currentLevelXp.value, 0), levelProgressMax.value)
|
||||
})
|
||||
const levelProgressLabel = computed(() =>
|
||||
!hasLevelProgress.value
|
||||
? `Уровень ${user.value.level}`
|
||||
: hasNextLevel.value ? `Уровень ${user.value.level}` : `Уровень ${user.value.level} · максимум`
|
||||
)
|
||||
const levelProgressText = computed(() =>
|
||||
hasNextLevel.value ? `${levelProgress.value} / ${levelProgressMax.value} XP` : `${userXp.value} XP`
|
||||
)
|
||||
const interestTags = ref([
|
||||
{ label: '#ML', active: true },
|
||||
{ label: '#ИИ', active: true },
|
||||
@@ -66,10 +85,10 @@ onMounted(() => {
|
||||
</div>
|
||||
<div class="level">
|
||||
<div class="level-header">
|
||||
<span>Уровень {{ user.level }}</span>
|
||||
<span>{{ user.xp }} / 200 XP</span>
|
||||
<span>{{ levelProgressLabel }}</span>
|
||||
<span>{{ levelProgressText }}</span>
|
||||
</div>
|
||||
<ProgressBar :value="user.xp ?? 0" :max="200" />
|
||||
<ProgressBar :value="levelProgress" :max="levelProgressMax" :text="levelProgressText" />
|
||||
</div>
|
||||
<div class="tags">
|
||||
<div class="section-title">Интересы</div>
|
||||
|
||||
Reference in New Issue
Block a user