diff --git a/app/Http/Controllers/Api/App/QuizController.php b/app/Http/Controllers/Api/App/QuizController.php index 8f06c0d..fcd9429 100644 --- a/app/Http/Controllers/Api/App/QuizController.php +++ b/app/Http/Controllers/Api/App/QuizController.php @@ -65,15 +65,14 @@ final class QuizController extends Controller $user = $this->currentUser($request); abort_if(! $access->canAccessBank($user, $bank), 403); - return ApiResponse::success( + return ApiResponse::page( QuizAttempt::query() ->where('user_id', $user->id) ->where('question_bank_id', $bank->id) ->whereIn('mode', ['sequence', 'random']) ->where('status', 'submitted') ->latest('submitted_at') - ->limit(30) - ->get(['id', 'mode', 'status', 'total_questions', 'correct_count', 'score', 'started_at', 'submitted_at']), + ->paginate((int) $request->query('per_page', 10), ['id', 'mode', 'status', 'total_questions', 'correct_count', 'score', 'started_at', 'submitted_at']), ); } diff --git a/frontend/dist (2).zip b/frontend/dist (2).zip new file mode 100644 index 0000000..1bb8f2d Binary files /dev/null and b/frontend/dist (2).zip differ diff --git a/frontend/dist.zip b/frontend/dist.zip deleted file mode 100644 index f59439c..0000000 Binary files a/frontend/dist.zip and /dev/null differ diff --git a/frontend/src/api/quiz.ts b/frontend/src/api/quiz.ts index 592410c..81c6df6 100644 --- a/frontend/src/api/quiz.ts +++ b/frontend/src/api/quiz.ts @@ -9,8 +9,8 @@ export function fetchBankTags(bankId: number) { return apiGet>(`/api/app/banks/${bankId}/tags`) } -export function fetchBankAttemptHistory(bankId: number) { - return apiGet(`/api/app/banks/${bankId}/attempts/history`) +export function fetchBankAttemptHistory(bankId: number, params?: Record) { + return apiGet>(`/api/app/banks/${bankId}/attempts/history`, params) } export function startBankAttempt(bankId: number, payload: Record) { diff --git a/frontend/src/views/app/QuizView.vue b/frontend/src/views/app/QuizView.vue index 60cf78f..11d3a1f 100644 --- a/frontend/src/views/app/QuizView.vue +++ b/frontend/src/views/app/QuizView.vue @@ -357,7 +357,7 @@ watch(remainingSeconds, async (value) => {
-
@@ -766,6 +766,13 @@ watch(remainingSeconds, async (value) => { box-shadow: none; } + .footer-inner--wrong-review { + grid-template-columns: 42px minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) 42px; + grid-template-areas: + "home prev mastered next sheet" + "confirm confirm confirm confirm confirm"; + } + .footer-group { display: contents; } @@ -804,6 +811,10 @@ watch(remainingSeconds, async (value) => { padding-inline: 10px; } + .footer-inner--wrong-review .footer-primary { + grid-area: confirm; + } + .footer-finish { grid-area: finish; width: 100%; @@ -811,7 +822,7 @@ watch(remainingSeconds, async (value) => { } .footer-mastered { - grid-area: finish; + grid-area: mastered; width: 100%; height: 42px; } diff --git a/frontend/src/views/app/ResourcesView.vue b/frontend/src/views/app/ResourcesView.vue index ca7b2f8..4a009eb 100644 --- a/frontend/src/views/app/ResourcesView.vue +++ b/frontend/src/views/app/ResourcesView.vue @@ -3,7 +3,7 @@ import { computed, onMounted, reactive, shallowRef } from 'vue' import { useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import { fetchBankAttemptHistory, fetchBankTags, startBankAttempt, startPaperAttempt } from '@/api/quiz' -import type { Paper, QuestionBank, QuizAttemptHistory } from '@/types/api' +import type { PageMeta, Paper, QuestionBank, QuizAttemptHistory } from '@/types/api' import { fetchResources } from '@/api/quiz' const router = useRouter() @@ -15,6 +15,9 @@ const historyDialogVisible = shallowRef(false) const randomBank = shallowRef(null) const historyBank = shallowRef(null) const historyAttempts = shallowRef([]) +const historyMeta = shallowRef({ current_page: 1, per_page: 10, total: 0, last_page: 1 }) +const historyPage = shallowRef(1) +const historyPageSize = shallowRef(10) const historyLoading = shallowRef(false) const tags = shallowRef>([]) const randomForm = reactive({ @@ -76,15 +79,37 @@ async function reviewWrong(bank: QuestionBank) { async function openHistory(bank: QuestionBank) { historyBank.value = bank historyAttempts.value = [] + historyPage.value = 1 historyDialogVisible.value = true + await loadHistory() +} + +async function loadHistory() { + if (!historyBank.value) return historyLoading.value = true try { - historyAttempts.value = (await fetchBankAttemptHistory(bank.id)).data + const response = await fetchBankAttemptHistory(historyBank.value.id, { + page: historyPage.value, + per_page: historyPageSize.value, + }) + historyAttempts.value = response.data.items + historyMeta.value = response.data.meta } finally { historyLoading.value = false } } +async function changeHistoryPage(page: number) { + historyPage.value = page + await loadHistory() +} + +async function changeHistoryPageSize(size: number) { + historyPageSize.value = size + historyPage.value = 1 + await loadHistory() +} + async function reviewAttempt(attempt: QuizAttemptHistory) { if (attempt.status !== 'submitted') { ElMessage.warning('只能回顾已提交的记录') @@ -93,6 +118,16 @@ async function reviewAttempt(attempt: QuizAttemptHistory) { await router.push(`/quiz/${attempt.id}`) } +function formatDateTime(value?: string) { + if (!value) return '-' + const date = new Date(value) + if (Number.isNaN(date.getTime())) return value + + const pad = (number: number) => String(number).padStart(2, '0') + + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}` +} + async function startPaper(paper: Paper) { const response = await startPaperAttempt(paper.id) await router.push(`/quiz/${response.data.id}`) @@ -122,7 +157,8 @@ onMounted(loadResources) 顺序背题 顺序刷题 随机刷题 - 回顾错题 + 错题列表 + 重刷错题 历史回顾 @@ -186,13 +222,24 @@ onMounted(loadResources)
{{ attempt.mode === 'random' ? '随机刷题' : '顺序刷题' }} -

{{ attempt.submitted_at || attempt.started_at || '-' }}

+

{{ formatDateTime(attempt.submitted_at || attempt.started_at) }}

{{ attempt.correct_count }} / {{ attempt.total_questions }} 回顾
+ @@ -304,6 +351,11 @@ onMounted(loadResources) gap: 8px; } +.history-pagination { + margin-top: 14px; + justify-content: flex-end; +} + .resource-actions :deep(.el-button) { width: 100%; justify-content: center; @@ -333,6 +385,12 @@ onMounted(loadResources) justify-content: space-between; } + .history-pagination { + justify-content: flex-start; + overflow-x: auto; + padding-bottom: 4px; + } + :global(.random-dialog) { margin-top: 8vh; }