本次專案, 前端(Vue) 主要透過 API 與後端互動,來顯示🥤飲品菜單、🤖 AI 智能推薦助手、⭐ 收藏喜愛飲品等內容,並根據回傳資料更新畫面。以下以🤖 AI 智能推薦功能為例,說明如何設計前端與後端互動流程。
// 使用者資訊
const username = ref('TestUser') // 實際會從 AWS Cognito 取得
// API BASE URL
const API_BASE_URL = 'http://localhost:8080/api'
// 生命週期
onMounted(() => {
// 載入服務狀態
fetchServiceStatus()
})
// 取得服務狀態
const serviceStatus = ref(null)
const fetchServiceStatus = async () => {
try {
const response = await fetch(`${API_BASE_URL}/recommendations/service-status`)
if (response.ok) {
const data = await response.json()
serviceStatus.value = data
console.log('服務狀態:', data)
}
} catch (error) {
console.error('獲取服務狀態失敗:', error)
}
}
fetch
發送 POST 請求到後端 API(/recommendations/generate
),並給予 userInput
和 username
參數,讓後端回傳 JSON,前端根據 status
判斷成功或失敗,並將結果(推薦、心情、AI 回應等)推送到 messages
。在沒有推薦結果或 API 錯誤的情況下,前端會顯示提示或錯誤訊息。整個流程以 isLoading
控制狀態,避免重複請求並優化使用者體驗。// 前端與後端互動流程(以AI 智能推薦為例)
const messages = ref([])
const isLoading = ref(false)
let messageId = 0
const userInput = ref('')
const sendMessage = async () => {
if (!userInput.value.trim() || isLoading.value) return
const messageContent = userInput.value.trim()
userInput.value = ''
// 顯示使用者資訊
messages.value.push({
id: messageId++,
type: 'user',
content: messageContent,
timestamp: new Date(),
})
await scrollToBottom()
// 發送 POST 請求到後端生成推薦內容
isLoading.value = true
try {
const response = await fetch(`${API_BASE_URL}/recommendations/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
userInput: messageContent,
username: username.value,
}),
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
if (data.status === 'error') {
// 錯誤
messages.value.push({
id: messageId++,
type: 'ai',
error: data.message || '發生未知錯誤',
timestamp: new Date(),
})
} else if (data.status === 'success') {
// 成功 - 檢查必要欄位
const moodDetected = data.moodDetected || data.mood || '未檢測到心情'
const aiResponse = data.aiResponse || data.response || data.message || 'AI 正在為您分析...'
const aiReason = data.aiReason || 'AI 為您精心挑選的飲品推薦'
const recommendations = Array.isArray(data.recommendations) ? data.recommendations : []
// 如果沒有推薦結果
if (recommendations.length === 0) {
console.warn('沒有收到推薦結果')
}
messages.value.push({
id: messageId++,
type: 'ai',
moodDetected: moodDetected,
aiResponse: aiResponse,
aiReason: aiReason,
recommendations: recommendations,
serviceType: data.serviceType || '未知服務',
isBedrockAvailable: data.isBedrockAvailable || false,
timestamp: new Date(),
})
console.log('處理後的 AI 回應:', {
moodDetected: moodDetected,
aiResponse: aiResponse,
aiReason: aiReason,
recommendations: recommendations,
serviceType: data.serviceType,
isBedrockAvailable: data.isBedrockAvailable,
})
} else {
// 未知
console.error('未知的格式:', data)
messages.value.push({
id: messageId++,
type: 'ai',
error: '回應格式異常,請稍後再試',
timestamp: new Date(),
})
}
} catch (error) {
console.error('API 請求失敗:', error)
messages.value.push({
id: messageId++,
type: 'ai',
error: '網路連線錯誤,請稍後再試',
timestamp: new Date(),
})
} finally {
isLoading.value = false
await scrollToBottom()
}
}
<div v-else class="message-content">
<div
v-if="message.moodDetected && message.moodDetected !== '未檢測到心情'"
class="mood-info"
>
<span class="mood-label">🔍 檢測心情:</span>
<span class="mood-value">{{ message.moodDetected }}</span>
</div>
<div v-if="message.serviceType" class="service-info">
<span class="service-label">🤖 推薦引擎:</span>
<span
class="service-value"
:class="{ bedrock: message.isBedrockAvailable, local: !message.isBedrockAvailable }"
>
{{ message.serviceType }}
</span>
</div>
<div
v-if="message.aiResponse && message.aiResponse !== 'AI 正在為您分析...'"
class="ai-response"
>
{{ message.aiReason }}
</div>
<!-- 當沒有有效的 AI 回應時顯示預設資訊 -->
<div v-else-if="!message.error" class="ai-response">
<div class="analyzing-info">
<span class="analyzing-icon">🔍</span>
<span>正在分析您的需求,請稍候...</span>
</div>
</div>
<div
v-if="message.recommendations && message.recommendations.length > 0"
class="recommendations"
>
<h4>🍹 為您推薦:</h4>
<div class="recommendation-list">
<div
v-for="rec in message.recommendations"
:key="rec.name"
class="recommendation-item"
>
<div class="rec-header">
<span class="rec-name">{{ rec.name }}</span>
<span class="rec-category">{{ rec.category }}</span>
<span v-if="rec.price" class="rec-price">${{ rec.price }}</span>
</div>
<div class="rec-description">{{ rec.description }}</div>
<div class="rec-reason">{{ rec.reason }}</div>
<div class="rec-actions">
<button @click="addToFavorites(rec)" class="fav-btn" :disabled="isAddingFavorite">
<span v-if="isAddingFavorite">⏳</span>
<span v-else>⭐ 收藏</span>
</button>
<span class="match-score">匹配度: {{ Math.round(rec.matchScore * 100) }}%</span>
</div>
</div>
</div>
</div>
<!-- 當沒有推薦結果時預設資訊 -->
<div v-else-if="!message.error && message.serviceType" class="no-recommendations">
<div class="no-rec-info">
<span class="no-rec-icon">💭</span>
<p>目前沒有找到合適的推薦,請嘗試描述更具體的需求或心情。</p>
<div class="suggestion-tips">
<strong>💡 建議:</strong>
<ul>
<li>描述您的心情狀態(如:開心、疲憊、放鬆)</li>
<li>提及偏好的口味(如:甜的、酸的、清爽的)</li>
<li>說明場合或時間(如:上班時、運動後、睡前)</li>
</ul>
</div>
</div>
</div>
<div v-if="message.error" class="error-message">❌ {{ message.error }}</div>
</div>
當然,其他的互動功能(例如 ⭐ 收藏喜愛飲品、💾 推薦歷史記錄等)同樣以 API 請求方式與後端溝通,並根據回應更新前端狀態。
這樣的設計能使前端與後端的資料流更加清晰,讓每個互動功能都能獨立維護。前端只需根據 API 回應動態更新狀態,即可快速響應使用者的操作,透過維持資料格式的一致性,同時兼顧使用者體驗。