308 lines
7.6 KiB
TypeScript
308 lines
7.6 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
import type { User, UserProgress, UserPreferences } from '@/types/user'
|
|
|
|
export const useUserStore = defineStore('user', () => {
|
|
// 狀態
|
|
const profile = ref<User | null>(null)
|
|
const progress = ref<UserProgress | null>(null)
|
|
const preferences = ref<UserPreferences>({
|
|
language: 'zh-TW',
|
|
theme: 'light',
|
|
notifications: {
|
|
email: true,
|
|
push: true,
|
|
dailyReminder: true,
|
|
achievementAlert: true
|
|
},
|
|
privacy: {
|
|
profileVisible: false,
|
|
progressVisible: false,
|
|
allowFriendRequests: true
|
|
},
|
|
learning: {
|
|
dailyGoal: 30,
|
|
difficultyLevel: 'intermediate',
|
|
preferredPracticeTime: 'evening',
|
|
voiceEnabled: true,
|
|
subtitlesEnabled: true
|
|
}
|
|
})
|
|
const achievements = ref<any[]>([])
|
|
const friends = ref<any[]>([])
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
|
|
// 計算屬性
|
|
const totalLearningTime = computed(() => {
|
|
return progress.value?.totalLearningTime || 0
|
|
})
|
|
|
|
const currentLevel = computed(() => {
|
|
return progress.value?.currentLevel || 1
|
|
})
|
|
|
|
const experiencePoints = computed(() => {
|
|
return progress.value?.experiencePoints || 0
|
|
})
|
|
|
|
const streakDays = computed(() => {
|
|
return progress.value?.streakDays || 0
|
|
})
|
|
|
|
const completedLessons = computed(() => {
|
|
return progress.value?.completedLessons || 0
|
|
})
|
|
|
|
const unlockedAchievements = computed(() => {
|
|
return achievements.value.filter(achievement => achievement.unlocked)
|
|
})
|
|
|
|
const reviewDueVocabulary = computed(() => {
|
|
// 模擬待複習詞彙數據,實際應該從學習進度中計算
|
|
return []
|
|
})
|
|
|
|
// 動作
|
|
const fetchUserProfile = async () => {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
const response = await fetch('/api/user/profile', {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
}
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('獲取用戶資料失敗')
|
|
}
|
|
|
|
profile.value = await response.json()
|
|
} catch (err) {
|
|
error.value = err instanceof Error ? err.message : '獲取用戶資料失敗'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const fetchUserProgress = async () => {
|
|
isLoading.value = true
|
|
|
|
try {
|
|
const response = await fetch('/api/user/progress', {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
}
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('獲取學習進度失敗')
|
|
}
|
|
|
|
progress.value = await response.json()
|
|
} catch (err) {
|
|
error.value = err instanceof Error ? err.message : '獲取學習進度失敗'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const updatePreferences = async (newPreferences: Partial<UserPreferences>) => {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
const response = await fetch('/api/user/preferences', {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
},
|
|
body: JSON.stringify(newPreferences)
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('更新偏好設定失敗')
|
|
}
|
|
|
|
const updatedPreferences = await response.json()
|
|
preferences.value = { ...preferences.value, ...updatedPreferences }
|
|
|
|
return { success: true }
|
|
} catch (err) {
|
|
error.value = err instanceof Error ? err.message : '更新偏好設定失敗'
|
|
return { success: false, error: error.value }
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const updateDailyGoal = async (goal: number) => {
|
|
const result = await updatePreferences({
|
|
learning: {
|
|
...preferences.value.learning,
|
|
dailyGoal: goal
|
|
}
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
const addExperience = (points: number) => {
|
|
if (progress.value) {
|
|
progress.value.experiencePoints += points
|
|
|
|
// 檢查是否升級
|
|
const newLevel = Math.floor(progress.value.experiencePoints / 1000) + 1
|
|
if (newLevel > progress.value.currentLevel) {
|
|
progress.value.currentLevel = newLevel
|
|
// 觸發升級事件
|
|
return { levelUp: true, newLevel }
|
|
}
|
|
}
|
|
return { levelUp: false }
|
|
}
|
|
|
|
const incrementLearningTime = (minutes: number) => {
|
|
if (progress.value) {
|
|
progress.value.totalLearningTime += minutes
|
|
progress.value.lastLearningDate = new Date().toISOString()
|
|
}
|
|
}
|
|
|
|
const updateStreak = () => {
|
|
if (!progress.value) return
|
|
|
|
const today = new Date().toDateString()
|
|
const lastLearning = progress.value.lastLearningDate ?
|
|
new Date(progress.value.lastLearningDate).toDateString() : null
|
|
|
|
if (lastLearning === today) {
|
|
// 今天已經學習過了,不更新連擊
|
|
return
|
|
}
|
|
|
|
const yesterday = new Date()
|
|
yesterday.setDate(yesterday.getDate() - 1)
|
|
|
|
if (lastLearning === yesterday.toDateString()) {
|
|
// 昨天有學習,增加連擊
|
|
progress.value.streakDays += 1
|
|
} else if (lastLearning !== today) {
|
|
// 中斷連擊,重新開始
|
|
progress.value.streakDays = 1
|
|
}
|
|
|
|
progress.value.lastLearningDate = new Date().toISOString()
|
|
}
|
|
|
|
const fetchAchievements = async () => {
|
|
try {
|
|
const response = await fetch('/api/user/achievements', {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
}
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('獲取成就失敗')
|
|
}
|
|
|
|
achievements.value = await response.json()
|
|
} catch (err) {
|
|
console.error('獲取成就錯誤:', err)
|
|
}
|
|
}
|
|
|
|
const unlockAchievement = async (achievementId: string) => {
|
|
try {
|
|
const response = await fetch(`/api/user/achievements/${achievementId}/unlock`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
}
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('解鎖成就失敗')
|
|
}
|
|
|
|
// 更新本地狀態
|
|
const achievement = achievements.value.find(a => a.id === achievementId)
|
|
if (achievement) {
|
|
achievement.unlocked = true
|
|
achievement.unlockedAt = new Date().toISOString()
|
|
}
|
|
|
|
return { success: true, achievement }
|
|
} catch (err) {
|
|
console.error('解鎖成就錯誤:', err)
|
|
return { success: false }
|
|
}
|
|
}
|
|
|
|
const fetchFriends = async () => {
|
|
try {
|
|
const response = await fetch('/api/user/friends', {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
}
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('獲取朋友列表失敗')
|
|
}
|
|
|
|
friends.value = await response.json()
|
|
} catch (err) {
|
|
console.error('獲取朋友列表錯誤:', err)
|
|
}
|
|
}
|
|
|
|
const clearUserData = () => {
|
|
profile.value = null
|
|
progress.value = null
|
|
achievements.value = []
|
|
friends.value = []
|
|
error.value = null
|
|
}
|
|
|
|
return {
|
|
// 狀態
|
|
profile,
|
|
progress,
|
|
preferences,
|
|
achievements,
|
|
friends,
|
|
isLoading,
|
|
error,
|
|
|
|
// 計算屬性
|
|
totalLearningTime,
|
|
currentLevel,
|
|
experiencePoints,
|
|
streakDays,
|
|
completedLessons,
|
|
unlockedAchievements,
|
|
reviewDueVocabulary,
|
|
|
|
// 動作
|
|
fetchUserProfile,
|
|
fetchUserProgress,
|
|
updatePreferences,
|
|
updateDailyGoal,
|
|
addExperience,
|
|
incrementLearningTime,
|
|
updateStreak,
|
|
fetchAchievements,
|
|
unlockAchievement,
|
|
fetchFriends,
|
|
clearUserData
|
|
}
|
|
}, {
|
|
persist: {
|
|
paths: ['preferences']
|
|
}
|
|
}) |