779b6aba77
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 8s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 54s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 27s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 6s
115 lines
4.8 KiB
Vue
115 lines
4.8 KiB
Vue
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { useRoute } from 'vue-router'
|
||
import GlassCard from '@/components/ui/GlassCard.vue'
|
||
import { reviewsApi } from '@/api'
|
||
|
||
const route = useRoute()
|
||
const rating = ref<'positive' | 'neutral' | 'negative'>('positive')
|
||
const text = ref('Лекция была хорошо структурирована, особенно понравились практические примеры и разбор кейсов.')
|
||
const submitted = ref(false)
|
||
const editing = ref(false)
|
||
const loading = ref(false)
|
||
const error = ref('')
|
||
|
||
const ratingMap = {
|
||
positive: 'Like',
|
||
neutral: 'Neutral',
|
||
negative: 'Dislike',
|
||
} as const
|
||
|
||
async function submit() {
|
||
loading.value = true
|
||
error.value = ''
|
||
try {
|
||
await reviewsApi.create(String(route.params.id), ratingMap[rating.value], text.value)
|
||
submitted.value = true
|
||
editing.value = false
|
||
} catch (err) {
|
||
error.value = err instanceof Error ? err.message : 'Не удалось отправить отзыв.'
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="review page-content">
|
||
<div class="header">
|
||
<div>
|
||
<h1 class="page-title">Отзыв о лекции #{{ route.params.id }}</h1>
|
||
<p class="text-secondary">Ваш отзыв помогает улучшать качество лекций и приносит бонусные монеты.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<GlassCard>
|
||
<div v-if="submitted && !editing" class="success-state">
|
||
<div class="success-icon">✅</div>
|
||
<div class="success-title">Отзыв отправлен и будет обработан</div>
|
||
<div class="success-sub">
|
||
Спасибо! Оценка полезности отзыва рассчитывается автоматически. Студентам не показывается техническая оценка LLM.
|
||
</div>
|
||
<button class="btn-secondary" @click="editing = true">Редактировать отзыв</button>
|
||
</div>
|
||
|
||
<form v-else @submit.prevent="submit" class="form">
|
||
<label class="field-label">Ваш отзыв о лекции</label>
|
||
<textarea v-model="text" rows="6" placeholder="Опишите, что было полезно, а что можно улучшить"></textarea>
|
||
|
||
<label class="field-label">Оценка впечатлений</label>
|
||
<div class="rating-options">
|
||
<button type="button" :class="{ active: rating === 'positive' }" @click="rating = 'positive'">👍 Положительный</button>
|
||
<button type="button" :class="{ active: rating === 'neutral' }" @click="rating = 'neutral'">😐 Нейтральный</button>
|
||
<button type="button" :class="{ active: rating === 'negative' }" @click="rating = 'negative'">👎 Отрицательный</button>
|
||
</div>
|
||
|
||
<div class="hint">
|
||
💡 Постарайтесь быть конкретными: что понравилось, где было сложно, какие темы хотите раскрыть глубже.
|
||
</div>
|
||
<div class="error" v-if="error">{{ error }}</div>
|
||
|
||
<div class="form-actions">
|
||
<button class="btn-primary" type="submit" :disabled="loading">{{ loading ? 'Отправляем...' : 'Отправить отзыв' }}</button>
|
||
<button class="btn-secondary" type="button" :disabled="submitted">Сохранить черновик</button>
|
||
</div>
|
||
</form>
|
||
</GlassCard>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.review { display: flex; flex-direction: column; gap: 16px; }
|
||
.form { display: flex; flex-direction: column; gap: 12px; }
|
||
.field-label { font-weight: 600; font-size: 13px; color: var(--color-text-secondary); }
|
||
textarea {
|
||
padding: 12px;
|
||
border-radius: var(--radius-sm);
|
||
border: 1px solid var(--color-border-glass);
|
||
background: rgba(255,255,255,0.8);
|
||
font-size: 14px;
|
||
resize: vertical;
|
||
}
|
||
.rating-options { display: flex; gap: 10px; flex-wrap: wrap; }
|
||
.rating-options button {
|
||
padding: 8px 12px;
|
||
border-radius: var(--radius-sm);
|
||
border: 1px solid var(--color-border-glass);
|
||
background: rgba(255,255,255,0.6);
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
}
|
||
.rating-options button.active {
|
||
border-color: var(--color-primary);
|
||
background: rgba(34,197,94,0.15);
|
||
color: var(--color-primary-dark);
|
||
font-weight: 600;
|
||
}
|
||
.hint { font-size: 12px; color: var(--color-text-secondary); background: rgba(255,255,255,0.6); padding: 8px 12px; border-radius: var(--radius-sm); }
|
||
.form-actions { display: flex; gap: 10px; }
|
||
.success-state { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
|
||
.success-icon { font-size: 28px; }
|
||
.success-title { font-size: 16px; font-weight: 700; }
|
||
.success-sub { font-size: 13px; color: var(--color-text-secondary); }
|
||
.error { color: var(--color-error); font-size: 13px; }
|
||
</style>
|