fix: 修正圖片載入失敗 + 清理未使用的 CSS 檔案
## 修正圖片載入問題 - ✅ 移除 flashcardUtils.ts 中的硬編碼端口 - ✅ 使用統一的 API_CONFIG.BASE_URL 配置 - ✅ 圖片 URL 現在自動跟隨後端配置 - ✅ 添加圖片載入失敗的錯誤處理 ## 代碼清理 - 🗑️ 移除未使用的 review-simple/globals.css - 🗑️ 移除對應的 CSS import - ✅ 所有組件使用 Tailwind CSS,保持一致性 ## 技術改進 - 🔧 消除硬編碼,提升維護性 - ⚙️ 統一配置管理,環境變數驅動 - 🛡️ 更好的錯誤處理和用戶體驗 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
fde7d1209b
commit
2a2c47da48
|
|
@ -1,107 +0,0 @@
|
||||||
/* 極簡MVP專用CSS - 復用現有的精美設計 */
|
|
||||||
|
|
||||||
/* 高級3D翻卡動畫 (來自原FlipMemoryTest設計) */
|
|
||||||
.flip-card-container {
|
|
||||||
perspective: 1000px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flip-card {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
transform-style: preserve-3d;
|
|
||||||
transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flip-card.flipped {
|
|
||||||
transform: rotateY(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.flip-card-front,
|
|
||||||
.flip-card-back {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
backface-visibility: hidden;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.flip-card-back {
|
|
||||||
transform: rotateY(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.flip-card:hover .flip-card-front,
|
|
||||||
.flip-card:hover .flip-card-back {
|
|
||||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 信心度按鈕 (復用ConfidenceButtons設計) */
|
|
||||||
.confidence-button {
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
border: 2px solid;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confidence-button:hover:not(:disabled) {
|
|
||||||
transform: scale(1.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
.confidence-button.selected {
|
|
||||||
transform: scale(1.05);
|
|
||||||
ring: 2px;
|
|
||||||
ring-color: rgba(59, 130, 246, 0.75);
|
|
||||||
ring-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confidence-button:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 進度條平滑動畫 */
|
|
||||||
.progress-bar {
|
|
||||||
transition: width 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 響應式設計 (復用原有邏輯) */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.flip-card-container {
|
|
||||||
min-height: 300px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 481px) and (max-width: 768px) {
|
|
||||||
.flip-card-container {
|
|
||||||
min-height: 350px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 769px) {
|
|
||||||
.flip-card-container {
|
|
||||||
min-height: 400px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 內容區塊樣式 (復用原有設計) */
|
|
||||||
.content-block {
|
|
||||||
background-color: #f9fafb;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-block h3 {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #111827;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-block p {
|
|
||||||
color: #374151;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Navigation } from '@/components/shared/Navigation'
|
import { Navigation } from '@/components/shared/Navigation'
|
||||||
import './globals.css'
|
|
||||||
import { FlipMemory } from '@/components/review/quiz/FlipMemory'
|
import { FlipMemory } from '@/components/review/quiz/FlipMemory'
|
||||||
import { VocabChoiceQuiz } from '@/components/review/quiz/VocabChoiceQuiz'
|
import { VocabChoiceQuiz } from '@/components/review/quiz/VocabChoiceQuiz'
|
||||||
import { QuizProgress } from '@/components/review/ui/QuizProgress'
|
import { QuizProgress } from '@/components/review/ui/QuizProgress'
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,25 @@ export const FlashcardContentBlocks: React.FC<FlashcardContentBlocksProps> = ({
|
||||||
alt={`${flashcard.word} example`}
|
alt={`${flashcard.word} example`}
|
||||||
className="w-full aspect-square object-cover rounded-lg border border-blue-300"
|
className="w-full aspect-square object-cover rounded-lg border border-blue-300"
|
||||||
style={{ imageRendering: 'auto' }}
|
style={{ imageRendering: 'auto' }}
|
||||||
|
onError={(e) => {
|
||||||
|
console.error('圖片載入失敗:', getFlashcardImageUrl(flashcard))
|
||||||
|
// 隱藏破損的圖片,顯示無圖片狀態
|
||||||
|
e.currentTarget.style.display = 'none'
|
||||||
|
const parent = e.currentTarget.parentElement?.parentElement
|
||||||
|
if (parent) {
|
||||||
|
parent.innerHTML = `
|
||||||
|
<div class="w-full max-w-md mx-auto h-48 bg-gray-100 rounded-lg border-2 border-dashed border-gray-300 flex items-center justify-center">
|
||||||
|
<div class="text-center text-gray-500">
|
||||||
|
<svg class="w-12 h-12 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
<p class="text-sm">圖片載入失敗</p>
|
||||||
|
<p class="text-xs text-red-500 mt-1">請檢查後端服務是否正常運行</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
* 統一管理詞卡相關的顯示和處理邏輯
|
* 統一管理詞卡相關的顯示和處理邏輯
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { API_CONFIG } from '@/lib/config/api'
|
||||||
|
|
||||||
// 詞性簡寫轉換
|
// 詞性簡寫轉換
|
||||||
export const getPartOfSpeechDisplay = (partOfSpeech: string): string => {
|
export const getPartOfSpeechDisplay = (partOfSpeech: string): string => {
|
||||||
const shortMap: {[key: string]: string} = {
|
const shortMap: {[key: string]: string} = {
|
||||||
|
|
@ -77,7 +79,7 @@ export const getFlashcardImageUrl = (flashcard: any): string | null => {
|
||||||
if (flashcard.primaryImageUrl) {
|
if (flashcard.primaryImageUrl) {
|
||||||
// 如果是相對路徑,加上後端基礎 URL
|
// 如果是相對路徑,加上後端基礎 URL
|
||||||
if (flashcard.primaryImageUrl.startsWith('/')) {
|
if (flashcard.primaryImageUrl.startsWith('/')) {
|
||||||
return `http://localhost:5008${flashcard.primaryImageUrl}`
|
return `${API_CONFIG.BASE_URL}${flashcard.primaryImageUrl}`
|
||||||
}
|
}
|
||||||
return flashcard.primaryImageUrl
|
return flashcard.primaryImageUrl
|
||||||
}
|
}
|
||||||
|
|
@ -87,10 +89,10 @@ export const getFlashcardImageUrl = (flashcard: any): string | null => {
|
||||||
const primaryImage = flashcard.exampleImages.find((img: any) => img.isPrimary)
|
const primaryImage = flashcard.exampleImages.find((img: any) => img.isPrimary)
|
||||||
if (primaryImage) {
|
if (primaryImage) {
|
||||||
const imageUrl = primaryImage.imageUrl
|
const imageUrl = primaryImage.imageUrl
|
||||||
return imageUrl?.startsWith('/') ? `http://localhost:5008${imageUrl}` : imageUrl
|
return imageUrl?.startsWith('/') ? `${API_CONFIG.BASE_URL}${imageUrl}` : imageUrl
|
||||||
}
|
}
|
||||||
const firstImageUrl = flashcard.exampleImages[0].imageUrl
|
const firstImageUrl = flashcard.exampleImages[0].imageUrl
|
||||||
return firstImageUrl?.startsWith('/') ? `http://localhost:5008${firstImageUrl}` : firstImageUrl
|
return firstImageUrl?.startsWith('/') ? `${API_CONFIG.BASE_URL}${firstImageUrl}` : firstImageUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue