356 lines
8.2 KiB
JavaScript
356 lines
8.2 KiB
JavaScript
// 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)
|
|
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 }
|
|
})
|
|
}
|
|
|
|
// ========== Mock 資料方法 ==========
|
|
|
|
getMockPetState() {
|
|
// 從 localStorage 或預設值讀取
|
|
const stored = localStorage.getItem('petState')
|
|
if (stored) {
|
|
return JSON.parse(stored)
|
|
}
|
|
return null
|
|
}
|
|
|
|
updateMockPetState(updates) {
|
|
const current = this.getMockPetState()
|
|
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 }
|
|
}
|
|
}
|
|
}
|
|
|
|
// 預設實例
|
|
export const apiService = new ApiService({
|
|
useMock: true, // 開發時使用 mock
|
|
mockDelay: 100
|
|
})
|
|
|