// API 服務層 - 支援 mock 和真實 API 切換 export class ApiService { constructor(config = {}) { this.baseUrl = config.baseUrl || 'http://localhost:3000/api' this.useMock = config.useMock !== false // 預設使用 mock this.mockDelay = config.mockDelay || 100 // mock 延遲模擬網路請求 } // 通用請求方法 async request(endpoint, options = {}) { if (this.useMock) { return this.mockRequest(endpoint, options) } return this.realRequest(endpoint, options) } // Mock 請求(使用本地資料) async mockRequest(endpoint, options) { await this.delay(this.mockDelay) const [resource, action] = endpoint.split('/').filter(Boolean) switch (`${resource}/${action}`) { case 'pet/state': return this.getMockPetState() case 'pet/update': return this.updateMockPetState(options.body) case 'events/list': return this.getMockEvents() case 'events/trigger': return this.triggerMockEvent(options.body) case 'buffs/apply': return this.applyMockBuff(options.body) case 'deities/list': return this.getMockDeities() case 'deities/pray': return this.mockPrayToDeity(options.body) case 'fortune/draw': return this.mockDrawFortune() case 'temple/throw-jiaobei': return { localFallback: true } // 使用本地邏輯 case 'items/list': return this.getMockItems() case 'items/use': return this.mockUseItem(options.body) case 'pet/save': return this.saveMockPetState(options.body) case 'pet/delete': return this.deleteMockPetState() case 'achievements/get': return this.getMockAchievements() case 'achievements/save': return this.saveMockAchievements(options.body) case 'inventory/get': return this.getMockInventory() case 'inventory/save': return this.saveMockInventory(options.body) case 'adventure/list': return this.getMockAdventures() case 'adventure/start': return { success: true, message: '冒險開始' } // 實際邏輯在 AdventureSystem case 'adventure/complete': return { success: true, message: '冒險完成' } default: throw new Error(`Unknown endpoint: ${endpoint}`) } } // 真實 API 請求 async realRequest(endpoint, options = {}) { const url = `${this.baseUrl}/${endpoint}` const config = { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...options.headers } } if (options.body) { config.body = JSON.stringify(options.body) } const response = await fetch(url, config) if (!response.ok) { throw new Error(`API Error: ${response.status} ${response.statusText}`) } return response.json() } // 延遲模擬 delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } // ========== API 端點方法 ========== // 寵物狀態相關 async getPetState() { return this.request('pet/state') } async updatePetState(updates) { return this.request('pet/update', { method: 'POST', body: updates }) } async savePetState(state) { return this.request('pet/save', { method: 'POST', body: state }) } async deletePetState() { return this.request('pet/delete', { method: 'DELETE' }) } // 事件相關 async getEvents() { return this.request('events/list') } async triggerEvent(eventId, petState) { return this.request('events/trigger', { method: 'POST', body: { eventId, petState } }) } // Buff 相關 async applyBuff(buffData) { return this.request('buffs/apply', { method: 'POST', body: buffData }) } // 神明相關 async getDeities() { return this.request('deities/list') } async prayToDeity(deityId, petState) { return this.request('deities/pray', { method: 'POST', body: { deityId, petState } }) } // 籤詩相關 async drawFortune() { return this.request('fortune/draw', { method: 'POST' }) } // 擲筊相關 async throwJiaobei(params) { return this.request('temple/throw-jiaobei', { method: 'POST', body: params }) } // 道具相關 async getItems() { return this.request('items/list') } async useItem(itemId, count = 1) { return this.request('items/use', { method: 'POST', body: { itemId, count } }) } // 冒險相關 async getAdventures() { return this.request('adventure/list') } async startAdventure(adventureId) { return this.request('adventure/start', { method: 'POST', body: { adventureId } }) } async completeAdventure(result) { return this.request('adventure/complete', { method: 'POST', body: result }) } // ========== Mock 資料方法 ========== getMockPetState() { // 從 localStorage 或預設值讀取 if (typeof localStorage === 'undefined') { console.warn('[ApiService] localStorage is not available'); return null; } const stored = localStorage.getItem('petState') console.log('[ApiService] getMockPetState:', stored ? 'Found' : 'Not Found'); if (stored) { try { return JSON.parse(stored) } catch (e) { console.error('[ApiService] Failed to parse stored state:', e); return null; } } return null } updateMockPetState(updates) { const current = this.getMockPetState() if (!current) { console.warn('[ApiService] No current state found, cannot update') return { success: false, message: '找不到當前狀態' } } // 使用深度合併,確保不會丟失原有字段 const updated = { ...current, ...updates } localStorage.setItem('petState', JSON.stringify(updated)) return { success: true, data: updated } } saveMockPetState(state) { localStorage.setItem('petState', JSON.stringify(state)) return { success: true, message: '狀態已儲存' } } getMockEvents() { // 動態載入事件配置 return import('../data/events.js').then(m => m.EVENT_CONFIG) } triggerMockEvent({ eventId, petState }) { // 模擬事件觸發邏輯 return { success: true, eventId, effects: [] } } applyMockBuff(buffData) { return { success: true, buff: buffData } } async getMockDeities() { const { DEITIES } = await import('../data/deities.js') return DEITIES } mockPrayToDeity({ deityId, petState }) { return { success: true, favorIncrease: 5, message: '祈福成功' } } mockDrawFortune() { // 模擬抽籤邏輯 const grades = ['上上', '上', '中', '下', '下下'] const weights = [0.05, 0.15, 0.40, 0.30, 0.10] const random = Math.random() let sum = 0 let selectedGrade = '中' for (let i = 0; i < weights.length; i++) { sum += weights[i] if (random <= sum) { selectedGrade = grades[i] break } } return { success: true, lot: { id: Math.floor(Math.random() * 100) + 1, grade: selectedGrade, poem1: '示例籤詩', meaning: '示例解釋' } } } async getMockItems() { const { ITEMS } = await import('../data/items.js') return Object.values(ITEMS) } mockUseItem({ itemId, count }) { return { success: true, itemId, count, effects: {} } } deleteMockPetState() { localStorage.removeItem('petState') return { success: true, message: '寵物已刪除' } } // 成就相關 async getAchievements() { return this.request('achievements/get') } async saveAchievements(data) { return this.request('achievements/save', { method: 'POST', body: data }) } getMockAchievements() { const stored = localStorage.getItem('achievements') if (stored) { return JSON.parse(stored) } return null } saveMockAchievements(data) { localStorage.setItem('achievements', JSON.stringify(data)) return { success: true, message: '成就已儲存' } } // 背包相關 async getInventory() { return this.request('inventory/get') } async saveInventory(data) { return this.request('inventory/save', { method: 'POST', body: data }) } getMockInventory() { const stored = localStorage.getItem('inventory') if (stored) { return JSON.parse(stored) } return { inventory: {}, equipped: { weapon: null, armor: null, hat: null, accessory: null, talisman: null, special: null }, appearance: {} } } saveMockInventory(data) { try { const dataToSave = { inventory: data.inventory || {}, equipped: data.equipped || {}, appearance: data.appearance || {} } localStorage.setItem('inventory', JSON.stringify(dataToSave)) console.log('[ApiService] 背包已儲存到 localStorage:', { inventoryCount: Object.keys(dataToSave.inventory).length, equipped: dataToSave.equipped }) return { success: true, message: '背包已儲存' } } catch (error) { console.error('[ApiService] 儲存背包失敗:', error) return { success: false, message: '儲存失敗: ' + error.message } } } async getMockAdventures() { const { ADVENTURES } = await import('../data/adventures.js') return ADVENTURES } } // 預設實例 export const apiService = new ApiService({ useMock: true, // 開發時使用 mock mockDelay: 100 })