mirror of
https://github.com/handsomezhuzhu/QQuiz.git
synced 2026-02-20 12:00:14 +00:00
翻页样式完善
This commit is contained in:
87
frontend/src/components/Pagination.jsx
Normal file
87
frontend/src/components/Pagination.jsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { ChevronLeft, ChevronRight, ChevronDown } from 'lucide-react'
|
||||
|
||||
const Pagination = ({
|
||||
currentPage,
|
||||
totalItems,
|
||||
pageSize,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
pageSizeOptions = [10, 20, 50, 100]
|
||||
}) => {
|
||||
const totalPages = Math.ceil(totalItems / pageSize)
|
||||
const [inputPage, setInputPage] = useState(currentPage)
|
||||
|
||||
useEffect(() => {
|
||||
setInputPage(currentPage)
|
||||
}, [currentPage])
|
||||
|
||||
const handlePageSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
let page = parseInt(inputPage)
|
||||
if (isNaN(page)) page = 1
|
||||
if (page < 1) page = 1
|
||||
if (page > totalPages) page = totalPages
|
||||
onPageChange(page)
|
||||
setInputPage(page)
|
||||
}
|
||||
|
||||
if (totalItems === 0) return null
|
||||
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 py-4 border-t border-gray-100 mt-4">
|
||||
{/* Info */}
|
||||
<div className="text-sm text-gray-500">
|
||||
显示 {Math.min((currentPage - 1) * pageSize + 1, totalItems)} - {Math.min(currentPage * pageSize, totalItems)} 共 {totalItems} 条
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 sm:gap-4">
|
||||
{/* Page Size Selector */}
|
||||
<div className="relative group">
|
||||
<select
|
||||
value={pageSize}
|
||||
onChange={(e) => onPageSizeChange(Number(e.target.value))}
|
||||
className="appearance-none bg-white border border-gray-300 text-gray-700 py-2 pl-3 pr-8 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent cursor-pointer hover:border-gray-400 transition-colors"
|
||||
>
|
||||
{pageSizeOptions.map(size => (
|
||||
<option key={size} value={size}>{size} 条/页</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown className="absolute right-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500 pointer-events-none" />
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
|
||||
disabled={currentPage === 1}
|
||||
className="p-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
{/* Manual Input */}
|
||||
<form onSubmit={handlePageSubmit} className="flex items-center">
|
||||
<input
|
||||
type="text"
|
||||
value={inputPage}
|
||||
onChange={(e) => setInputPage(e.target.value)}
|
||||
className="w-12 text-center py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent mx-1"
|
||||
/>
|
||||
<span className="text-gray-500 text-sm mx-1">/ {totalPages}</span>
|
||||
</form>
|
||||
|
||||
<button
|
||||
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
className="p-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Pagination
|
||||
@@ -5,7 +5,8 @@ import React, { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { mistakeAPI } from '../api/client'
|
||||
import Layout from '../components/Layout'
|
||||
import { XCircle, Loader, Trash2, BookOpen, Play, ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import Pagination from '../components/Pagination'
|
||||
import { XCircle, Loader, Trash2, BookOpen, Play } from 'lucide-react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { getQuestionTypeText, formatRelativeTime } from '../utils/helpers'
|
||||
|
||||
@@ -185,48 +186,16 @@ export const MistakeList = () => {
|
||||
})}
|
||||
|
||||
{/* Pagination */}
|
||||
{total > limit && (
|
||||
<div className="flex items-center justify-between pt-4">
|
||||
<div className="text-sm text-gray-600">
|
||||
显示 {Math.min((page - 1) * limit + 1, total)} - {Math.min(page * limit, total)} 共 {total} 条
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setPage(p => Math.max(1, p - 1))}
|
||||
disabled={page === 1}
|
||||
className="p-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</button>
|
||||
<span className="flex items-center px-4 border border-gray-300 rounded-lg bg-white">
|
||||
{page}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setPage(p => (p * limit < total ? p + 1 : p))}
|
||||
disabled={page * limit >= total}
|
||||
className="p-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<ChevronRight className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Limit Selector */}
|
||||
<div className="flex justify-end pt-2">
|
||||
<select
|
||||
value={limit}
|
||||
onChange={(e) => {
|
||||
setLimit(Number(e.target.value))
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalItems={total}
|
||||
pageSize={limit}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={(newLimit) => {
|
||||
setLimit(newLimit)
|
||||
setPage(1)
|
||||
}}
|
||||
className="text-sm border-gray-300 rounded-lg focus:ring-primary-500 focus:border-primary-500"
|
||||
>
|
||||
<option value={10}>10 条/页</option>
|
||||
<option value={20}>20 条/页</option>
|
||||
<option value={50}>50 条/页</option>
|
||||
</select>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { questionAPI } from '../api/client'
|
||||
import Layout from '../components/Layout'
|
||||
import { FileText, Loader, ChevronLeft, ChevronRight, Search } from 'lucide-react'
|
||||
import Pagination from '../components/Pagination'
|
||||
import { FileText, Loader, Search } from 'lucide-react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { getQuestionTypeText, formatRelativeTime } from '../utils/helpers'
|
||||
|
||||
@@ -140,48 +141,16 @@ export const QuestionBank = () => {
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
{total > limit && (
|
||||
<div className="flex items-center justify-between pt-4">
|
||||
<div className="text-sm text-gray-600">
|
||||
显示 {Math.min((page - 1) * limit + 1, total)} - {Math.min(page * limit, total)} 共 {total} 条
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setPage(p => Math.max(1, p - 1))}
|
||||
disabled={page === 1}
|
||||
className="p-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</button>
|
||||
<span className="flex items-center px-4 border border-gray-300 rounded-lg bg-white">
|
||||
{page}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setPage(p => (p * limit < total ? p + 1 : p))}
|
||||
disabled={page * limit >= total}
|
||||
className="p-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<ChevronRight className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Limit Selector */}
|
||||
<div className="flex justify-end pt-2">
|
||||
<select
|
||||
value={limit}
|
||||
onChange={(e) => {
|
||||
setLimit(Number(e.target.value))
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalItems={total}
|
||||
pageSize={limit}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={(newLimit) => {
|
||||
setLimit(newLimit)
|
||||
setPage(1)
|
||||
}}
|
||||
className="text-sm border-gray-300 rounded-lg focus:ring-primary-500 focus:border-primary-500"
|
||||
>
|
||||
<option value={10}>10 条/页</option>
|
||||
<option value={20}>20 条/页</option>
|
||||
<option value={50}>50 条/页</option>
|
||||
</select>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user