/** * 問卷和偏好探索功能 * 處理註冊後的問卷填寫和偏好探索流程 */ class QuestionnaireManager { constructor() { this.questionnaireData = {}; this.preferenceData = []; this.currentRound = 1; this.totalRounds = 16; this.questions = []; this.dogImages = []; this.comparisonPairs = []; this.dogLetterMap = {}; // 狗狗ID到字母的映射 this.nextLetter = 0; // 下一個要分配的字母索引 this.preloadedImages = new Map(); // 預載的圖片快取 this.init(); } init() { this.bindEvents(); this.loadQuestions(); } async loadQuestions() { try { const response = await fetch('/api/questionnaire/questions'); const result = await response.json(); if (result.success && result.data) { this.questions = result.data; this.renderQuestions(); } else { console.error('載入問卷問題失敗:', result.message); this.showError('載入問卷失敗,請重新整理頁面'); } } catch (error) { console.error('載入問卷問題時發生錯誤:', error); this.showError('載入問卷時發生錯誤,請檢查網路連線'); } } renderQuestions() { const contentDiv = document.getElementById('questionnaireContent'); if (!contentDiv) { console.error('找不到問卷內容容器'); return; } if (this.questions.length === 0) { contentDiv.innerHTML = `

目前沒有可用的問卷問題

`; return; } let html = ''; this.questions.forEach((question, index) => { const questionNumber = index + 1; html += `
${questionNumber}. ${question.text}
${question.description ? `

${question.description}

` : ''} ${this.renderQuestionOptions(question, questionNumber)}
`; }); contentDiv.innerHTML = html; // 重新綁定事件 this.bindQuestionEvents(); this.checkFormCompletion(); } renderQuestionOptions(question, questionNumber) { let html = ''; question.options.forEach((option, optionIndex) => { const optionId = `q${questionNumber}_${optionIndex}`; // 使用 A, B, C, D... 作為選項標籤 const optionLabel = String.fromCharCode(65 + optionIndex); // A, B, C, D... // 使用選項的 ID 或索引作為表單值 const optionValue = option.id || optionLabel; html += `
`; }); return html; } bindQuestionEvents() { // 綁定單選按鈕變化事件 const radioInputs = document.querySelectorAll('#questionnaireForm input[type="radio"]'); radioInputs.forEach(input => { input.addEventListener('change', () => { this.checkFormCompletion(); }); }); } showError(message) { const contentDiv = document.getElementById('questionnaireContent'); if (contentDiv) { contentDiv.innerHTML = ` `; } } bindEvents() { // 問卷表單事件 if (typeof $ !== 'undefined') { $('#questionnaireForm input[type="radio"]').on('change', () => { this.checkFormCompletion(); }); // 提交問卷 $('#submitQuestionnaireBtn').on('click', () => { this.submitQuestionnaire(); }); // 關閉問卷按鈕 - 允許關閉 $('#closeQuestionnaireBtn').on('click', () => { const questionnaireModal = document.getElementById('questionnaireModal'); if (questionnaireModal) { const bsModal = bootstrap.Modal.getInstance(questionnaireModal); if (bsModal) { bsModal.hide(); } } }); // 關閉2選1按鈕 - 允許關閉 $('#closePreferenceBtn').on('click', () => { const preferenceModal = document.getElementById('preferenceModal'); if (preferenceModal) { const bsModal = bootstrap.Modal.getInstance(preferenceModal); if (bsModal) { bsModal.hide(); } } }); // 偏好探索事件 $(document).on('click', '.dog-frame-container', (e) => { const dogId = String($(e.currentTarget).data('dog-id')); this.selectPreference(dogId); }); // 完成頁面事件 $('#viewRecommendations').on('click', () => { this.viewRecommendations(); }); $('#restartProcess').on('click', () => { this.restartProcess(); }); } else { // 使用原生JavaScript const form = document.getElementById('questionnaireForm'); if (form) { form.addEventListener('change', (e) => { if (e.target.type === 'radio') { this.checkFormCompletion(); } }); } const submitBtn = document.getElementById('submitQuestionnaireBtn'); if (submitBtn) { submitBtn.addEventListener('click', () => { this.submitQuestionnaire(); }); } const closeQuestionnaireBtn = document.getElementById('closeQuestionnaireBtn'); if (closeQuestionnaireBtn) { closeQuestionnaireBtn.addEventListener('click', () => { const questionnaireModal = document.getElementById('questionnaireModal'); if (questionnaireModal) { const bsModal = bootstrap.Modal.getInstance(questionnaireModal); if (bsModal) { bsModal.hide(); } } }); } const closePreferenceBtn = document.getElementById('closePreferenceBtn'); if (closePreferenceBtn) { closePreferenceBtn.addEventListener('click', () => { // 確認是否要關閉 if (this.preferenceData.length > 0) { const confirmed = confirm('偏好探索尚未完成,確定要關閉嗎?'); if (!confirmed) { return; } } this.closePreferenceExploration(); }); } document.addEventListener('click', (e) => { if (e.target.closest('.dog-frame-container')) { const dogId = String(e.target.closest('.dog-frame-container').dataset.dogId); this.selectPreference(dogId); } }); const confirmBtn = document.getElementById('confirmComplete'); if (confirmBtn) { confirmBtn.addEventListener('click', () => { this.confirmComplete(); }); } } } checkFormCompletion() { // 如果問題尚未載入,直接返回 if (!this.questions || this.questions.length === 0) { return; } const requiredQuestions = this.questions.length; let answeredQuestions = 0; for (let i = 1; i <= requiredQuestions; i++) { const checked = document.querySelector(`input[name="question_${i}"]:checked`); if (checked) { answeredQuestions++; } } const submitBtn = document.getElementById('submitQuestionnaireBtn'); if (submitBtn) { if (answeredQuestions === requiredQuestions && requiredQuestions > 0) { submitBtn.disabled = false; submitBtn.classList.remove('btn-secondary'); submitBtn.classList.add('btn-primary'); } else { submitBtn.disabled = true; submitBtn.classList.remove('btn-primary'); submitBtn.classList.add('btn-secondary'); } } } async submitQuestionnaire() { // 收集問卷數據 this.questionnaireData = {}; const answeredLog = []; for (let i = 1; i <= this.questions.length; i++) { const selected = document.querySelector(`input[name="question_${i}"]:checked`); if (selected) { this.questionnaireData[`question_${i}`] = selected.value; // 找到對應題目與選項 const q = this.questions[i-1]; const opt = (q && q.options || []).find(o => String(o.id) === String(selected.value)); answeredLog.push({ questionText: q ? q.text : '', optionText: opt ? opt.label : '', traits: opt && opt.traits ? opt.traits : [], }); } } // 先儲存問券填寫結果,等待完成 let saveSuccess = false; try { const saveResponse = await fetch('/front_header_footer/survey_history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ survey_type: 'questionnaire', content: answeredLog }) }); const saveResult = await saveResponse.json(); if (saveResult.success) { saveSuccess = true; } else { console.error('問卷儲存失敗:', saveResult.message); alert('問卷儲存失敗,請稍後再試'); return; } } catch(e) { console.error('問卷儲存時發生錯誤:', e); alert('問卷儲存時發生錯誤,請稍後再試'); return; } // 只有在儲存成功後才關閉問卷模態框 if (saveSuccess) { const questionnaireModal = document.getElementById('questionnaireModal'); if (questionnaireModal) { const bsModal = bootstrap.Modal.getInstance(questionnaireModal); if (bsModal) { bsModal.hide(); } } } // 清除註冊後自動開啟偏好探索的標記(如果有的話) try { if (localStorage.getItem('autoStartPreferenceAfterQuestionnaire') === 'true') { localStorage.removeItem('autoStartPreferenceAfterQuestionnaire'); } } catch(e) {} // 問卷完成後,顯示完成提示並跳轉到首頁 showCompletionModal('已完成問卷填寫,感謝您的回覆!'); // 為確認按鈕綁定跳轉到首頁的邏輯 setTimeout(() => { const completionModal = document.getElementById('completionModal'); if (completionModal) { const confirmBtn = completionModal.querySelector('#confirmComplete'); if (confirmBtn) { const newConfirmHandler = () => { const instance = bootstrap.Modal.getInstance(completionModal); if (instance) instance.hide(); // 跳轉到首頁 window.location.href = '/'; }; confirmBtn.onclick = newConfirmHandler; } } }, 100); } closeQuestionnaire() { // 關閉問卷模態框 const questionnaireModal = document.getElementById('questionnaireModal'); if (questionnaireModal) { const bsModal = bootstrap.Modal.getInstance(questionnaireModal); if (bsModal) { bsModal.hide(); } else { // 如果 modal 實例不存在,創建新的實例並隱藏 const newBsModal = new bootstrap.Modal(questionnaireModal); newBsModal.hide(); } } } closePreferenceExploration() { // 關閉偏好探索模態框 const preferenceModal = document.getElementById('preferenceModal'); if (preferenceModal) { const bsModal = bootstrap.Modal.getInstance(preferenceModal); if (bsModal) { bsModal.hide(); } else { // 如果 modal 實例不存在,創建新的實例並隱藏 const newBsModal = new bootstrap.Modal(preferenceModal); newBsModal.hide(); } } } // 二選一圖片載入 async fetchDogExploreItems() { try { const response = await fetch('/dog_explore/items'); const result = await response.json(); if (result.success && result.data) { // 只保留title、image_filename與id this.dogImages = result.data.filter(i => i.is_visible).map(i => { const originalImage = '/static/uploads/dog_explore/' + (i.image_filename || 'default.jpg'); // 使用縮圖(400px 寬度適合二選一展示) const thumbnailImage = typeof getThumbnailUrl === 'function' ? getThumbnailUrl(originalImage, { width: 400 }) : originalImage; return { id: i.id, name: i.title, image: thumbnailImage }; }); } else { this.dogImages = []; } } catch (err) { this.dogImages = []; } } // 獲取或分配狗狗的字母標籤 getDogLetter(dogId) { // 統一轉為字符串作為鍵 const dogIdStr = String(dogId); if (!this.dogLetterMap[dogIdStr]) { // 如果這隻狗還沒有分配字母,分配一個新的 this.dogLetterMap[dogIdStr] = String.fromCharCode(65 + this.nextLetter); // 65 是 'A' 的 ASCII 碼 this.nextLetter++; } return this.dogLetterMap[dogIdStr]; } // 生成二選一淘汰賽邏輯 initiateEliminationTournament() { // 初始待選陣列為所有項目 this.eliminationRounds = [ [...this.dogImages] ]; this.currentEliminationRound = 0; this.currentPairs = []; this.dogLetterMap = {}; // 重置字母映射 this.nextLetter = 0; // 重置字母索引 this._generateNextPairs(); this.champWinner = null; } _generateNextPairs() { // 本輪內容配對(偶數才夠一次對兩兩) const round = this.eliminationRounds[this.currentEliminationRound]; // 去重檢查:確保本輪沒有重複的項目 const seenIds = new Set(); const uniqueRound = []; for (const item of round) { const itemId = String(item.id); if (!seenIds.has(itemId)) { seenIds.add(itemId); uniqueRound.push(item); } else { console.warn('偏好探索配對:跳過重複的項目', item.name, '(ID:', itemId + ')'); } } // 使用去重後的陣列進行配對 this.currentPairs = []; for(let i=0;i { const originalImage = '/static/uploads/dog_explore/' + (i.image_filename || 'default.jpg'); // 使用縮圖(400px 寬度適合二選一展示) const thumbnailImage = typeof getThumbnailUrl === 'function' ? getThumbnailUrl(originalImage, { width: 400 }) : originalImage; // 確保 traits 是有效的陣列 const traits = Array.isArray(i.traits) ? i.traits : []; // 驗證 traits 資料的完整性 const validTraits = traits.filter(t => { if (!t || typeof t !== 'object') return false; if (!t.category_id || !t.score) { console.warn(`⚠️ 項目 ${i.title} (ID: ${i.id}) 的 trait 資料不完整:`, t); return false; } return true; }); if (validTraits.length !== traits.length) { console.warn(`⚠️ 項目 ${i.title} 有 ${traits.length - validTraits.length} 個無效的 trait 被過濾掉`); } return { id: i.id, name: i.title, image: thumbnailImage, image_filename: i.image_filename, traits: validTraits }; }); // 去重:使用 Map 來確保不會有重複的項目(基於 id) const uniqueItemsMap = new Map(); for (const item of mappedItems) { // 使用字符串 id 作為鍵,確保不會有重複的項目 const itemId = String(item.id); if (!uniqueItemsMap.has(itemId)) { uniqueItemsMap.set(itemId, item); } else { console.warn('偏好探索:跳過重複的項目 ID', itemId); } } // 進一步去重:檢查是否有相同的圖片檔案名稱 // 標準化檔案名稱:移除副檔名並轉小寫,因為縮圖 API 會自動嘗試多種副檔名 const normalizeFilename = (filename) => { if (!filename) return null; // 移除副檔名和路徑,只保留檔案名稱本身 const nameWithoutPath = filename.split('/').pop().split('\\').pop(); const nameWithoutExt = nameWithoutPath.split('.')[0]; return nameWithoutExt.toLowerCase(); }; const imageFilenameSet = new Set(); this.dogImages = []; for (const item of uniqueItemsMap.values()) { const normalized = normalizeFilename(item.image_filename); if (normalized && !imageFilenameSet.has(normalized)) { imageFilenameSet.add(normalized); // 移除 image_filename 屬性(不需要在最終數據中) const { image_filename, ...itemWithoutFilename } = item; this.dogImages.push(itemWithoutFilename); } else { console.warn('偏好探索:跳過重複的圖片', item.image_filename, '(標準化為', normalized + ')'); } } // 檢查去重後是否還有足夠的項目 if (this.dogImages.length < 2) { alert('偏好探索項目不足(去重後少於 2 個),請聯絡管理員檢查探索圖片!'); return; } // 最終驗證:確保有足夠且無重複的項目 this.preferenceData = []; this.champWinner = null; this.eliminationRounds = [ [...this.dogImages] ]; this.currentEliminationRound = 0; this.currentPairs = []; this._generateNextPairs(); // 計算輪數(n個項目需要 n-1 場比賽) this.totalRounds = this.dogImages.length - 1; // 預載所有圖片以加速顯示 await this.preloadImages(); this.showCurrentRound(); } // 預載圖片方法 async preloadImages() { let successCount = 0; let failCount = 0; const preloadPromises = this.dogImages.map(dog => { return new Promise((resolve) => { if (this.preloadedImages.has(dog.image)) { successCount++; resolve(); return; } const img = new Image(); img.onload = () => { this.preloadedImages.set(dog.image, img); successCount++; resolve(); }; img.onerror = () => { console.warn(`✗ 圖片載入失敗: ${dog.name} (${dog.image})`); failCount++; // 即使失敗也繼續,使用預設圖片 resolve(); }; img.src = dog.image; // 設置超時(5秒) setTimeout(() => { if (!this.preloadedImages.has(dog.image)) { console.warn(`⏱️ 圖片載入超時: ${dog.name}`); failCount++; resolve(); } }, 5000); }); }); await Promise.all(preloadPromises); } // 取代原有showCurrentRound,只顯示一對 showCurrentRound() { // 若單一冠軍產生 if(this.champWinner){ this.completePreferenceExploration(); return; } if(!this.currentPairs.length && !this.byeItem){ this.completePreferenceExploration(); return; } const pair = this.currentPairs[this.currentPairIndex]; if(!pair){ let winners = this.preferenceData.filter(x=>x.roundIndex===this.currentEliminationRound).map(x=>x.selected); if(this.byeItem) winners.push(this.byeItem); // 去重:確保 winners 中沒有重複的項目(基於 id) const uniqueWinners = []; const seenIds = new Set(); for (const winner of winners) { const winnerId = String(winner.id); // 統一轉為字符串比較 if (!seenIds.has(winnerId)) { seenIds.add(winnerId); uniqueWinners.push(winner); } else { console.warn('偏好探索:跳過重複的勝者', winner.name, '(ID:', winnerId + ')'); } } winners = uniqueWinners; if(winners.length===1){ this.champWinner = winners[0]; this.completePreferenceExploration(); return; } this.eliminationRounds.push([...winners]); this.currentEliminationRound++; this._generateNextPairs(); this.showCurrentRound(); return; } // 渲染配對 const imageComparison = document.getElementById('imageComparison'); if(imageComparison) { // 確保配對中的兩個項目都存在且有效 if (!pair[0] || !pair[1]) { console.error('偏好探索:配對資料不完整', pair); this.completePreferenceExploration(); return; } // 再次確認兩個項目不相同 if (String(pair[0].id) === String(pair[1].id)) { console.error('偏好探索:配對中的兩個項目相同!跳過此配對'); this.currentPairIndex++; this.showCurrentRound(); return; } const letter1 = this.getDogLetter(pair[0].id); const letter2 = this.getDogLetter(pair[1].id); // 優化:只使用一次圖片載入(移除 background-image),並添加 loading 屬性 imageComparison.innerHTML = `
${pair[0].name || '毛寶貝'}
${letter1}
VS
${pair[1].name || '毛寶貝'}
${letter2}
`; } // 依已完成選擇數更新輪數與進度 const finishedSelections = this.preferenceData.filter(p => p.selected).length; const currentRoundNumber = Math.min(finishedSelections + 1, this.totalRounds); const currentRoundEl = document.getElementById('currentRound'); const totalRoundsEl = document.getElementById('totalRounds'); if(currentRoundEl) currentRoundEl.textContent = this.totalRounds > 0 ? currentRoundNumber : 0; if(totalRoundsEl) totalRoundsEl.textContent = this.totalRounds; const progressBar = document.getElementById('progressBar'); if(progressBar){ const percent = this.totalRounds > 0 ? (finishedSelections / this.totalRounds) * 100 : 0; progressBar.style.width = `${percent}%`; } } // 取代selectPreference,修正記錄方式,勝方晉級 selectPreference(dogId) { const pair = this.currentPairs[this.currentPairIndex]; if (!pair) { console.error('偏好探索:當前沒有配對可選擇'); return; } // 統一轉為字符串比較,避免類型不一致 const dogIdStr = String(dogId); const selectedDog = pair.find(x => String(x.id) === dogIdStr); if (!selectedDog) { console.error('偏好探索:找不到選中的項目', dogId, '配對:', pair); return; } // 記錄當前輪晉級者,包含配對資訊 this.preferenceData.push({ roundIndex: this.currentEliminationRound, pairIndex: this.currentPairIndex, orderIds: pair.map(x => String(x.id)), orderNames: pair.map(x => x.name), selected: selectedDog, selectedTraits: selectedDog.traits || [], timestamp: new Date() }); // 動畫 const selectedCard = document.querySelector(`.dog-frame-container[data-dog-id="${dogIdStr}"]`); if (selectedCard) selectedCard.classList.add('selected'); setTimeout(() => { this.currentPairIndex++; this.showCurrentRound(); }, 800); } // 跳過當前配對(淘汰賽邏輯) skipRound() { const pair = this.currentPairs[this.currentPairIndex]; if (!pair) { console.warn('偏好探索:當前沒有配對可跳過'); return; } // 隨機選擇一個作為勝者(因為用戶跳過) const randomIndex = Math.floor(Math.random() * 2); const selectedDog = pair[randomIndex]; // 記錄跳過但仍需晉級者 this.preferenceData.push({ roundIndex: this.currentEliminationRound, pairIndex: this.currentPairIndex, orderIds: pair.map(x => String(x.id)), orderNames: pair.map(x => x.name), selected: selectedDog, selectedTraits: selectedDog.traits || [], skipped: true, // 標記為跳過 timestamp: new Date() }); setTimeout(() => { this.currentPairIndex++; this.showCurrentRound(); }, 300); } // 此函數已不再使用(保留以避免破壞現有引用) nextRound() { console.warn('nextRound() 已棄用,請使用淘汰賽邏輯'); this.showCurrentRound(); } // 處理偏好探索完成後的導向邏輯 handlePreferenceExplorationCompletion() { const isOnMatchingPage = window.location.pathname.includes('/start_match'); // 顯示完成提示 showCompletionModal('已完成 偏好探索!'); // 為確認按鈕綁定額外邏輯 setTimeout(() => { const completionModal = document.getElementById('completionModal'); if (completionModal) { const confirmBtn = completionModal.querySelector('#confirmComplete'); if (confirmBtn) { const newConfirmHandler = () => { const instance = bootstrap.Modal.getInstance(completionModal); if (instance) instance.hide(); if (isOnMatchingPage) { // 如果在配對頁面,重新整理以更新配對狀態 window.location.reload(); } else { // 如果不在配對頁面,導向到開始配對頁面 window.location.href = '/start_match'; } }; confirmBtn.onclick = newConfirmHandler; } } }, 100); } // 取代completePreferenceExploration,使最後champWinner成為冠軍 completePreferenceExploration() { // 詳細紀錄:每輪的順序與選擇及特質 const selectedCount = this.preferenceData.filter(p => p.selected).length; const skippedCount = this.preferenceData.filter(p => p.skipped).length; const resultHtml = `
偏好分析

您完成了 ${selectedCount} 輪偏好選擇

${skippedCount > 0 ? `

跳過了 ${skippedCount} 輪

` : ''}

問卷和偏好探索已完成,系統將根據您的選擇進行配對分析

`; const preferenceResults = document.getElementById('preferenceResults'); if(preferenceResults) preferenceResults.innerHTML = resultHtml; // modal 控制 const preferenceModal = document.getElementById('preferenceModal'); if (preferenceModal) { const bsModal = bootstrap.Modal.getInstance(preferenceModal); if(bsModal) bsModal.hide(); } setTimeout(() => { // 儲存偏好探索填寫結果 try { const cleaned = this.preferenceData.map(x => ({ round: x.roundIndex + 1, pairIndex: x.pairIndex !== undefined ? x.pairIndex + 1 : null, orderIds: x.orderIds || [], orderNames: x.orderNames || [], chosenId: x.selected ? String(x.selected.id) : null, chosenName: x.selected ? x.selected.name : null, skipped: x.skipped || false, traits: (x.selectedTraits || []).map(t => ({ category_id: t.category_id, category_code: t.category_code, category_group: t.category_group, category_name: t.category_name, score: t.score })) })); fetch('/front_header_footer/survey_history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ survey_type: 'pairwise', content: cleaned }) }).then(response => response.json()) .then(result => { // 偏好探索完成後,處理導向邏輯 this.handlePreferenceExplorationCompletion(); }).catch(err => { console.error('✗ 偏好探索資料儲存錯誤:', err); // 即使儲存失敗,也處理導向邏輯 this.handlePreferenceExplorationCompletion(); }); } catch(e) { console.error('✗ 偏好探索資料處理錯誤:', e); // 如果發生錯誤,也處理導向邏輯 this.handlePreferenceExplorationCompletion(); } }, 300); } showResults() { // 簡單的結果展示 const selectedCount = this.preferenceData.filter(p => p.selected).length; const resultsHtml = `
偏好分析

您完成了 ${selectedCount} 輪偏好選擇

問卷和偏好探索已完成,系統將根據您的選擇進行配對分析

`; const preferenceResults = document.getElementById('preferenceResults'); if (preferenceResults) { preferenceResults.innerHTML = resultsHtml; } } confirmComplete() { // 關閉完成模態框 const completionModal = document.getElementById('completionModal'); if (completionModal) { const bsModal = bootstrap.Modal.getInstance(completionModal); if (bsModal) { bsModal.hide(); } } } } // 初始化函數 function initQuestionnaire() { // 初始化問卷管理器 window.questionnaireManager = new QuestionnaireManager(); // 檢查是否需要顯示問卷(例如:新註冊用戶) const shouldShowQuestionnaire = localStorage.getItem('showQuestionnaire') === 'true'; if (shouldShowQuestionnaire) { // 清除標記 localStorage.removeItem('showQuestionnaire'); // 顯示問卷 setTimeout(() => { if (typeof $ !== 'undefined') { $('#questionnaireModal').modal('show'); } else { // 如果jQuery未載入,使用原生JavaScript const modal = document.getElementById('questionnaireModal'); if (modal) { const bsModal = new bootstrap.Modal(modal); bsModal.show(); } } }, 1000); } } // 當頁面載入完成時初始化 if (typeof $ !== 'undefined') { $(document).ready(initQuestionnaire); } else { // 如果jQuery未載入,等待DOM載入完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initQuestionnaire); } else { initQuestionnaire(); } } // 模擬註冊完成後觸發問卷 function triggerQuestionnaire() { localStorage.setItem('showQuestionnaire', 'true'); location.reload(); } // 註冊完成後觸發問卷的函數 function showQuestionnaireAfterRegistration() { $('#questionnaireModal').modal('show'); } // 添加CSS樣式 const style = document.createElement('style'); style.textContent = ` .dog-frame-container { position: relative; cursor: pointer; transition: all 0.3s ease; } .dog-frame-container:hover { transform: translateY(-5px); } .dog-photo-frame { background: linear-gradient(145deg, #f0f0f0, #ffffff); padding: 20px; border-radius: 8px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); transition: all 0.3s ease; } .dog-frame-container:hover .dog-photo-frame { box-shadow: 0 15px 40px rgba(0, 0, 0, 0.25); } .frame-inner { position: relative; background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%); border-radius: 4px; box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.1); overflow: hidden; height: 310px; display: flex; align-items: center; justify-content: center; } .frame-image { position: relative; max-width: 100%; max-height: 100%; height: auto; object-fit: contain; display: block; border-radius: 2px; z-index: 2; transition: opacity 0.3s ease; } /* 圖片載入動畫 */ .frame-image[src=""] { opacity: 0; } .frame-inner::after { content: ''; position: absolute; top: 50%; left: 50%; width: 40px; height: 40px; margin: -20px 0 0 -20px; border: 4px solid rgba(0, 71, 103, 0.2); border-top-color: #004767; border-radius: 50%; animation: spin 1s linear infinite; z-index: 1; } .frame-image[src]:not([src=""]) ~ .frame-inner::after { display: none; } @keyframes spin { to { transform: rotate(360deg); } } .dog-label { position: absolute; bottom: -30px; left: 50%; transform: translateX(-50%); background: #2c2c2c; color: white; width: 50px; height: 50px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; font-weight: bold; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); } .dog-frame-container.selected .dog-photo-frame { box-shadow: 0 0 0 4px #28a745, 0 15px 40px rgba(40, 167, 69, 0.3); } .dog-frame-container.selected { transform: scale(1.05); } .vs-badge { background: linear-gradient(135deg, #ff9800, #ff6b00); color: white; width: 80px; height: 80px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.8rem; font-weight: bold; box-shadow: 0 6px 20px rgba(255, 107, 0, 0.4); animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } .cursor-pointer { cursor: pointer; } .traits .badge { font-size: 0.8rem; } `; document.head.appendChild(style); // 以完成頁面樣式呈現簡訊息 function showCompletionModal(message) { try { const completionModal = document.getElementById('completionModal'); if (!completionModal) return; // 更新文字 const body = completionModal.querySelector('.modal-body'); if (body) { body.innerHTML = ` `; } const bsModal = new bootstrap.Modal(completionModal); bsModal.show(); // 綁定關閉 const confirmBtn = completionModal.querySelector('#confirmComplete'); if (confirmBtn) { confirmBtn.onclick = () => { const instance = bootstrap.Modal.getInstance(completionModal); if (instance) instance.hide(); }; } } catch (e) {} } // window上掛reopenPreferenceExploration以及reopenQuestionnaire window.reopenPreferenceExploration = function() { if(window.questionnaireManager){ questionnaireManager.preferenceData = []; questionnaireManager.champWinner = null; questionnaireManager.eliminationRounds = []; questionnaireManager.currentEliminationRound = 0; questionnaireManager.currentPairs = []; questionnaireManager.currentPairIndex = 0; const preferenceModal = document.getElementById('preferenceModal'); if(preferenceModal) { const bsModal = new bootstrap.Modal(preferenceModal); bsModal.show(); } questionnaireManager.startPreferenceExploration(); } }; window.reopenQuestionnaire = function() { if(window.questionnaireManager){ questionnaireManager.questionnaireData={}; questionnaireManager.preferenceData=[]; questionnaireManager.currentRound=1; // 強制刷新問題 questionnaireManager.loadQuestions(); const questionnaireModal = document.getElementById('questionnaireModal'); if(questionnaireModal){ const bsModal = new bootstrap.Modal(questionnaireModal); bsModal.show(); } } };