generated from VLADIMIR/template_frontend
up
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
NInputNumber,
|
||||
NModal,
|
||||
NQrCode,
|
||||
NRate,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NText,
|
||||
@@ -22,6 +23,7 @@ import { getBinaryClient } from '@/api/binary_client'
|
||||
import type {
|
||||
Application,
|
||||
Game,
|
||||
GameReview,
|
||||
MapItem,
|
||||
Question,
|
||||
ScreenCommand,
|
||||
@@ -519,6 +521,10 @@ async function finishGame() {
|
||||
|
||||
// ===== Ответы команд и оценка организатором =====
|
||||
const showAnswersModal = ref(false)
|
||||
const showReviewsModal = ref(false)
|
||||
const gameReviews = ref<GameReview[]>([])
|
||||
const reviewsAverage = ref(0)
|
||||
const reviewsCount = ref(0)
|
||||
const answersQuestions = ref<Question[]>([])
|
||||
const answersTeams = ref<TeamAnswers[]>([])
|
||||
|
||||
@@ -559,6 +565,27 @@ async function openAnswersModal() {
|
||||
await loadGameAnswers()
|
||||
}
|
||||
|
||||
async function openReviewsModal() {
|
||||
showReviewsModal.value = true
|
||||
const res = await client.GetGameReviewsForModeration({ gameId: Number(gameId) })
|
||||
if (res.error != '') {
|
||||
message.error(res.error!)
|
||||
return
|
||||
}
|
||||
gameReviews.value = res.reviews || []
|
||||
reviewsAverage.value = res.averageRating || 0
|
||||
reviewsCount.value = res.reviewsCount || 0
|
||||
}
|
||||
|
||||
async function moderateReview(review: GameReview, status: string) {
|
||||
const res = await client.ModerateGameReview({ id: review.id, status })
|
||||
if (res.error != '') {
|
||||
message.error(res.error!)
|
||||
return
|
||||
}
|
||||
await openReviewsModal()
|
||||
}
|
||||
|
||||
// Сохраняет баллы всех команд за все вопросы; пустой балл сбрасывает его.
|
||||
async function saveAllScores() {
|
||||
for (const question of answersQuestions.value) {
|
||||
@@ -1192,6 +1219,42 @@ useSimplePolling(() => {
|
||||
</n-card>
|
||||
</n-modal>
|
||||
|
||||
<n-modal v-model:show="showReviewsModal">
|
||||
<n-card
|
||||
style="width: min(94vw, 800px)"
|
||||
title="Отзывы игры"
|
||||
:bordered="false"
|
||||
size="huge"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
:content-style="{ maxHeight: '70vh', overflow: 'auto' }"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-button @click="showReviewsModal = false"> x </n-button>
|
||||
</template>
|
||||
<n-flex align="center" class="reviews-summary">
|
||||
<n-rate :value="reviewsAverage" readonly allow-half />
|
||||
<span>{{ reviewsCount ? `${reviewsAverage.toFixed(1)} · ${reviewsCount}` : 'Пока нет отзывов' }}</span>
|
||||
</n-flex>
|
||||
<div v-if="gameReviews.length == 0" class="answers-empty">Отзывов пока нет.</div>
|
||||
<div v-for="review in gameReviews" :key="review.id" class="review-row">
|
||||
<n-flex justify="space-between" align="center">
|
||||
<strong>{{ review.teamName }}</strong>
|
||||
<n-tag>{{ review.status }}</n-tag>
|
||||
</n-flex>
|
||||
<n-rate :value="review.rating" readonly size="small" />
|
||||
<p>{{ review.text }}</p>
|
||||
<n-flex v-if="review.status == 'pending'" justify="end">
|
||||
<n-button type="success" @click="moderateReview(review, 'approved')">Одобрить</n-button>
|
||||
<n-button type="error" ghost @click="moderateReview(review, 'rejected')">Отклонить</n-button>
|
||||
</n-flex>
|
||||
<n-flex v-else-if="review.status == 'approved'" justify="end">
|
||||
<n-button type="error" ghost @click="moderateReview(review, 'hidden')">Скрыть</n-button>
|
||||
</n-flex>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-modal>
|
||||
|
||||
<HeaderMenu active="games" />
|
||||
</template>
|
||||
|
||||
@@ -1203,6 +1266,19 @@ useSimplePolling(() => {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.reviews-summary {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.review-row {
|
||||
border-top: 1px solid #777;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.review-row p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.team-block-hover:hover {
|
||||
border: 1px solid #63e2b7;
|
||||
color: #63e2b7;
|
||||
|
||||
@@ -101,6 +101,14 @@ const openMenu = ref(false)
|
||||
>
|
||||
Пользователи
|
||||
</div>
|
||||
<div
|
||||
class="menu-item-block"
|
||||
:class="{ active: props.active == 'reviews' }"
|
||||
v-if="hasPermission('reviews_page')"
|
||||
@click="router.push('/reviews')"
|
||||
>
|
||||
Отзывы
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-group">
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NCard, NFlex, NRate, NTag, useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { getAuthClient } from '@/api/auth_client'
|
||||
import type { GameReview } from '@/api/generated/crabs/evening_detective_server'
|
||||
import HeaderMenu from '@/components/HeaderMenu.vue'
|
||||
|
||||
const client = getAuthClient()
|
||||
const message = useMessage()
|
||||
const reviews = ref<GameReview[]>([])
|
||||
|
||||
async function loadReviews() {
|
||||
const res = await client.GetReviewsForModeration({})
|
||||
if (res.error != '') {
|
||||
message.error(res.error!)
|
||||
return
|
||||
}
|
||||
reviews.value = res.reviews || []
|
||||
}
|
||||
|
||||
async function moderate(review: GameReview, status: string) {
|
||||
const res = await client.ModerateGameReview({ id: review.id, status })
|
||||
if (res.error != '') {
|
||||
message.error(res.error!)
|
||||
return
|
||||
}
|
||||
await loadReviews()
|
||||
}
|
||||
|
||||
loadReviews()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="center-block-custom">
|
||||
<div class="width1200">
|
||||
<div class="page-header">
|
||||
<h1>Отзывы</h1>
|
||||
<n-button @click="loadReviews">Обновить</n-button>
|
||||
</div>
|
||||
|
||||
<n-card v-if="reviews.length == 0">Очередь отзывов пуста.</n-card>
|
||||
<n-card v-for="review in reviews" :key="review.id" class="review-card">
|
||||
<n-flex justify="space-between" align="center">
|
||||
<div>
|
||||
<div class="review-game">{{ review.gameName }}</div>
|
||||
<strong>{{ review.teamName }}</strong>
|
||||
</div>
|
||||
<n-tag>{{ review.status }}</n-tag>
|
||||
</n-flex>
|
||||
<n-rate :value="review.rating" readonly />
|
||||
<p class="review-text">{{ review.text }}</p>
|
||||
<n-flex v-if="review.status == 'pending'" justify="end">
|
||||
<n-button type="success" @click="moderate(review, 'approved')">Одобрить</n-button>
|
||||
<n-button type="error" ghost @click="moderate(review, 'rejected')">Отклонить</n-button>
|
||||
</n-flex>
|
||||
<n-flex v-else-if="review.status == 'approved'" justify="end">
|
||||
<n-button type="error" ghost @click="moderate(review, 'hidden')">Скрыть</n-button>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<HeaderMenu active="reviews" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.center-block-custom {
|
||||
height: calc(100dvh - 70px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.review-card {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.review-game {
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.review-text {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
@@ -205,8 +205,8 @@ const paperControlsOverrides = {
|
||||
borderDisabled: '1px dashed black',
|
||||
borderFocus: '1px solid black',
|
||||
borderHover: '1px dashed black',
|
||||
boxShadowFocus: '0 0 0 2px rgb(99 226 183 / 30%)',
|
||||
caretColor: '#63e2b7',
|
||||
boxShadowFocus: '0 0 0 1px black',
|
||||
caretColor: 'black',
|
||||
color: 'transparent',
|
||||
colorDisabled: 'transparent',
|
||||
colorFocus: 'transparent',
|
||||
@@ -218,8 +218,8 @@ const paperControlsOverrides = {
|
||||
},
|
||||
Rate: {
|
||||
itemColor: 'black',
|
||||
itemColorHover: 'black',
|
||||
itemColorActive: 'black',
|
||||
itemColorHover: '#b8860b',
|
||||
itemColorActive: '#b8860b',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ useSimplePolling(() => {
|
||||
<hr class="questions-hr" />
|
||||
<n-config-provider :theme-overrides="paperControlsOverrides">
|
||||
<div class="review-rating font">Оценка</div>
|
||||
<n-rate v-model:value="reviewRating" :count="5" />
|
||||
<n-rate v-model:value="reviewRating" :count="5" class="review-rate" />
|
||||
<n-input
|
||||
v-model:value="reviewText"
|
||||
class="review-input"
|
||||
@@ -405,6 +405,16 @@ useSimplePolling(() => {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.review-rating {
|
||||
margin-bottom: 6px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.review-rate {
|
||||
margin-bottom: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.review-input {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user