feat: 實現詞卡管理頁面完整響應式設計(RWD)
- 例句圖片使用固定像素尺寸,不隨螢幕縮放變形 - 手機版採用垂直堆疊佈局,平板/桌面版水平排列 - 操作按鈕在小螢幕僅顯示圖示,節省空間 - 搜尋控制區域支援垂直/水平佈局切換 - 進階篩選網格響應式調整(1列→2列→4列) - 頁面標題和按鈕區域完整響應式設計 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
913d31100f
commit
b45f2ef4c1
|
|
@ -172,20 +172,20 @@ function FlashcardsContent() {
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
{/* Page Header */}
|
{/* Page Header */}
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-6 gap-4 sm:gap-0">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">我的詞卡</h1>
|
<h1 className="text-2xl sm:text-3xl font-bold text-gray-900">我的詞卡</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 sm:gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowForm(true)}
|
onClick={() => setShowForm(true)}
|
||||||
className="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors"
|
className="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors text-center"
|
||||||
>
|
>
|
||||||
新增詞卡
|
新增詞卡
|
||||||
</button>
|
</button>
|
||||||
<Link
|
<Link
|
||||||
href="/generate"
|
href="/generate"
|
||||||
className="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90 transition-colors"
|
className="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90 transition-colors text-center"
|
||||||
>
|
>
|
||||||
AI 生成詞卡
|
AI 生成詞卡
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -193,7 +193,7 @@ function FlashcardsContent() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className="flex space-x-8 mb-6 border-b border-gray-200">
|
<div className="flex space-x-4 sm:space-x-8 mb-6 border-b border-gray-200 overflow-x-auto">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('all-cards')}
|
onClick={() => setActiveTab('all-cards')}
|
||||||
className={`pb-4 px-1 border-b-2 font-medium text-sm ${
|
className={`pb-4 px-1 border-b-2 font-medium text-sm ${
|
||||||
|
|
@ -301,12 +301,12 @@ interface SearchControlsProps {
|
||||||
function SearchControls({ searchState, searchActions, showAdvancedSearch, setShowAdvancedSearch }: SearchControlsProps) {
|
function SearchControls({ searchState, searchActions, showAdvancedSearch, setShowAdvancedSearch }: SearchControlsProps) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
|
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-4 gap-3 sm:gap-0">
|
||||||
<h2 className="text-lg font-semibold text-gray-900">搜尋詞卡</h2>
|
<h2 className="text-lg font-semibold text-gray-900">搜尋詞卡</h2>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4">
|
||||||
{/* 排序控件 */}
|
{/* 排序控件 */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm text-gray-600">排序:</span>
|
<span className="text-sm text-gray-600 hidden sm:inline">排序:</span>
|
||||||
<select
|
<select
|
||||||
value={searchState.sorting.sortBy}
|
value={searchState.sorting.sortBy}
|
||||||
onChange={(e) => searchActions.updateSorting({ sortBy: e.target.value })}
|
onChange={(e) => searchActions.updateSorting({ sortBy: e.target.value })}
|
||||||
|
|
@ -378,7 +378,7 @@ function SearchControls({ searchState, searchActions, showAdvancedSearch, setSho
|
||||||
{/* 進階篩選面板 */}
|
{/* 進階篩選面板 */}
|
||||||
{showAdvancedSearch && (
|
{showAdvancedSearch && (
|
||||||
<div className="bg-gray-50 rounded-lg p-4 space-y-4">
|
<div className="bg-gray-50 rounded-lg p-4 space-y-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
{/* CEFR等級篩選 */}
|
{/* CEFR等級篩選 */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">CEFR等級</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">CEFR等級</label>
|
||||||
|
|
@ -555,9 +555,9 @@ function FlashcardItem({ card, searchTerm, onEdit, onDelete, onToggleFavorite, g
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4 flex-1">
|
<div className="flex flex-col md:flex-row md:items-center gap-3 md:gap-4 flex-1">
|
||||||
{/* 例句圖片區域 */}
|
{/* 例句圖片區域 - 響應式設計 */}
|
||||||
<div className="w-54 h-36 bg-gray-100 rounded-lg overflow-hidden border border-gray-200 flex items-center justify-center">
|
<div className="w-32 h-20 sm:w-40 sm:h-24 md:w-48 md:h-32 lg:w-54 lg:h-36 bg-gray-100 rounded-lg overflow-hidden border border-gray-200 flex items-center justify-center flex-shrink-0">
|
||||||
{hasExampleImage(card.word) ? (
|
{hasExampleImage(card.word) ? (
|
||||||
// 有例句圖片時顯示圖片
|
// 有例句圖片時顯示圖片
|
||||||
<img
|
<img
|
||||||
|
|
@ -623,12 +623,12 @@ function FlashcardItem({ card, searchTerm, onEdit, onDelete, onToggleFavorite, g
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 操作按鈕 */}
|
{/* 操作按鈕 - 響應式設計 */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-wrap md:flex-nowrap items-center gap-1 md:gap-2 mt-3 md:mt-0">
|
||||||
{/* 收藏按鈕 */}
|
{/* 收藏按鈕 */}
|
||||||
<button
|
<button
|
||||||
onClick={onToggleFavorite}
|
onClick={onToggleFavorite}
|
||||||
className={`px-3 py-2 rounded-lg font-medium transition-colors ${
|
className={`px-2 md:px-3 py-2 rounded-lg font-medium transition-colors ${
|
||||||
card.isFavorite
|
card.isFavorite
|
||||||
? 'bg-yellow-100 text-yellow-700 border border-yellow-300 hover:bg-yellow-200'
|
? 'bg-yellow-100 text-yellow-700 border border-yellow-300 hover:bg-yellow-200'
|
||||||
: 'bg-gray-100 text-gray-600 border border-gray-300 hover:bg-yellow-50 hover:text-yellow-600 hover:border-yellow-300'
|
: 'bg-gray-100 text-gray-600 border border-gray-300 hover:bg-yellow-50 hover:text-yellow-600 hover:border-yellow-300'
|
||||||
|
|
@ -638,7 +638,7 @@ function FlashcardItem({ card, searchTerm, onEdit, onDelete, onToggleFavorite, g
|
||||||
<svg className="w-4 h-4" fill={card.isFavorite ? "currentColor" : "none"} stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill={card.isFavorite ? "currentColor" : "none"} stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-sm">
|
<span className="text-sm hidden sm:inline">
|
||||||
{card.isFavorite ? '已收藏' : '收藏'}
|
{card.isFavorite ? '已收藏' : '收藏'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -647,36 +647,36 @@ function FlashcardItem({ card, searchTerm, onEdit, onDelete, onToggleFavorite, g
|
||||||
{/* 編輯按鈕 */}
|
{/* 編輯按鈕 */}
|
||||||
<button
|
<button
|
||||||
onClick={onEdit}
|
onClick={onEdit}
|
||||||
className="px-3 py-2 bg-blue-100 text-blue-700 border border-blue-300 rounded-lg font-medium hover:bg-blue-200 transition-colors"
|
className="px-2 md:px-3 py-2 bg-blue-100 text-blue-700 border border-blue-300 rounded-lg font-medium hover:bg-blue-200 transition-colors"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-sm">編輯</span>
|
<span className="text-sm hidden sm:inline">編輯</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 刪除按鈕 */}
|
{/* 刪除按鈕 */}
|
||||||
<button
|
<button
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
className="px-3 py-2 bg-red-100 text-red-700 border border-red-300 rounded-lg font-medium hover:bg-red-200 transition-colors"
|
className="px-2 md:px-3 py-2 bg-red-100 text-red-700 border border-red-300 rounded-lg font-medium hover:bg-red-200 transition-colors"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-sm">刪除</span>
|
<span className="text-sm hidden sm:inline">刪除</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 詳細按鈕 */}
|
{/* 詳細按鈕 */}
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push(`/flashcards/${card.id}`)}
|
onClick={() => router.push(`/flashcards/${card.id}`)}
|
||||||
className="px-4 py-2 bg-gray-100 text-gray-700 border border-gray-300 rounded-lg font-medium hover:bg-gray-200 hover:text-gray-900 transition-colors"
|
className="px-2 md:px-4 py-2 bg-gray-100 text-gray-700 border border-gray-300 rounded-lg font-medium hover:bg-gray-200 hover:text-gray-900 transition-colors"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="text-sm">詳細</span>
|
<span className="text-sm hidden md:inline">詳細</span>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue