feat: Добавил постраничную загрузку отзывов
Frontend CI / build-and-check (push) Failing after 5m10s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 15s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 18s
🚀 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
Frontend CI / build-and-check (push) Failing after 5m10s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 15s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 18s
🚀 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:
@@ -35,6 +35,10 @@ const ratingLabel: Record<string, string> = {
|
||||
|
||||
const reviews = ref<ReviewDto[]>([])
|
||||
const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const totalCount = ref(0)
|
||||
const totalPages = ref(0)
|
||||
const reanalyzingId = ref<number | null>(null)
|
||||
const error = ref('')
|
||||
|
||||
@@ -63,6 +67,13 @@ const rows = computed(() =>
|
||||
})),
|
||||
)
|
||||
|
||||
const pageStart = computed(() =>
|
||||
totalCount.value === 0 ? 0 : (page.value - 1) * pageSize.value + 1,
|
||||
)
|
||||
const pageEnd = computed(() => Math.min(page.value * pageSize.value, totalCount.value))
|
||||
const canGoPrev = computed(() => page.value > 1)
|
||||
const canGoNext = computed(() => page.value < totalPages.value)
|
||||
|
||||
function formatQuality(value: number | null | undefined) {
|
||||
if (value === null || value === undefined) return '—'
|
||||
return Number(value).toFixed(2)
|
||||
@@ -72,7 +83,13 @@ async function fetchReviews() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
reviews.value = await reviewsApi.list({ PageSize: 100 })
|
||||
const result = await reviewsApi.listPage({
|
||||
Page: page.value,
|
||||
PageSize: pageSize.value,
|
||||
})
|
||||
reviews.value = result.items
|
||||
totalCount.value = result.totalCount
|
||||
totalPages.value = result.totalPages
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : 'Не удалось загрузить отзывы.'
|
||||
} finally {
|
||||
@@ -80,6 +97,12 @@ async function fetchReviews() {
|
||||
}
|
||||
}
|
||||
|
||||
async function goToPage(nextPage: number) {
|
||||
if (nextPage < 1 || (totalPages.value && nextPage > totalPages.value)) return
|
||||
page.value = nextPage
|
||||
await fetchReviews()
|
||||
}
|
||||
|
||||
async function reanalyze(id: number) {
|
||||
reanalyzingId.value = id
|
||||
try {
|
||||
@@ -114,70 +137,88 @@ onMounted(fetchReviews)
|
||||
title="Отзывов нет"
|
||||
subtitle="Пока нет ни одного отзыва."
|
||||
/>
|
||||
<DataTable v-else :columns="columns" :rows="rows">
|
||||
<template #text="{ value }">
|
||||
<span class="review-text" :title="value">{{ value }}</span>
|
||||
</template>
|
||||
<template #status="{ value, row }">
|
||||
<StatusBadge :status="value" />
|
||||
<span class="raw-status">{{ row.rawStatus }}</span>
|
||||
</template>
|
||||
<template #sentiment="{ value }">
|
||||
<span
|
||||
class="badge"
|
||||
:class="
|
||||
value === 'Positive'
|
||||
? 'badge-green'
|
||||
: value === 'Negative'
|
||||
? 'badge-red'
|
||||
: 'badge-orange'
|
||||
"
|
||||
>
|
||||
{{ value }}
|
||||
</span>
|
||||
</template>
|
||||
<template #quality="{ value }">
|
||||
<span
|
||||
:class="
|
||||
(value ?? 0) >= 0.7
|
||||
? 'badge badge-green'
|
||||
: (value ?? 0) >= 0.4
|
||||
? 'badge badge-orange'
|
||||
: 'badge badge-red'
|
||||
"
|
||||
>
|
||||
{{ formatQuality(value) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #informativeTags="{ row }">
|
||||
<div class="tags-cell">
|
||||
<div v-else class="table-section">
|
||||
<div class="pagination-bar">
|
||||
<span> Показаны {{ pageStart }}–{{ pageEnd }} из {{ totalCount }} отзывов </span>
|
||||
<div class="pagination-actions">
|
||||
<button class="btn-ghost" :disabled="loading || !canGoPrev" @click="goToPage(page - 1)">
|
||||
Назад
|
||||
</button>
|
||||
<span>Страница {{ page }} из {{ totalPages || 1 }}</span>
|
||||
<button class="btn-ghost" :disabled="loading || !canGoNext" @click="goToPage(page + 1)">
|
||||
Вперёд
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable :columns="columns" :rows="rows">
|
||||
<template #text="{ value }">
|
||||
<span class="review-text" :title="value">{{ value }}</span>
|
||||
</template>
|
||||
<template #status="{ value, row }">
|
||||
<StatusBadge :status="value" />
|
||||
<span class="raw-status">{{ row.rawStatus }}</span>
|
||||
</template>
|
||||
<template #sentiment="{ value }">
|
||||
<span
|
||||
class="badge"
|
||||
:class="
|
||||
row.informative
|
||||
value === 'Positive'
|
||||
? 'badge-green'
|
||||
: row.informative === false
|
||||
: value === 'Negative'
|
||||
? 'badge-red'
|
||||
: 'badge-gray'
|
||||
: 'badge-orange'
|
||||
"
|
||||
>
|
||||
{{
|
||||
row.informative === null || row.informative === undefined
|
||||
? '—'
|
||||
: row.informative
|
||||
? 'Informative'
|
||||
: 'Not informative'
|
||||
}}
|
||||
{{ value }}
|
||||
</span>
|
||||
<span v-for="tag in row.tags" :key="tag" class="badge badge-blue">{{ tag }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<button class="btn-ghost" :disabled="reanalyzingId === row.id" @click="reanalyze(row.id)">
|
||||
{{ reanalyzingId === row.id ? 'Запускаем...' : 'Повторить анализ' }}
|
||||
</button>
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
<template #quality="{ value }">
|
||||
<span
|
||||
:class="
|
||||
(value ?? 0) >= 0.7
|
||||
? 'badge badge-green'
|
||||
: (value ?? 0) >= 0.4
|
||||
? 'badge badge-orange'
|
||||
: 'badge badge-red'
|
||||
"
|
||||
>
|
||||
{{ formatQuality(value) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #informativeTags="{ row }">
|
||||
<div class="tags-cell">
|
||||
<span
|
||||
class="badge"
|
||||
:class="
|
||||
row.informative
|
||||
? 'badge-green'
|
||||
: row.informative === false
|
||||
? 'badge-red'
|
||||
: 'badge-gray'
|
||||
"
|
||||
>
|
||||
{{
|
||||
row.informative === null || row.informative === undefined
|
||||
? '—'
|
||||
: row.informative
|
||||
? 'Informative'
|
||||
: 'Not informative'
|
||||
}}
|
||||
</span>
|
||||
<span v-for="tag in row.tags" :key="tag" class="badge badge-blue">{{ tag }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<button
|
||||
class="btn-ghost"
|
||||
:disabled="reanalyzingId === row.id"
|
||||
@click="reanalyze(row.id)"
|
||||
>
|
||||
{{ reanalyzingId === row.id ? 'Запускаем...' : 'Повторить анализ' }}
|
||||
</button>
|
||||
</template>
|
||||
</DataTable>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</template>
|
||||
@@ -213,6 +254,25 @@ onMounted(fetchReviews)
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 11px;
|
||||
}
|
||||
.table-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.pagination-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
.pagination-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.tags-cell {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user