dramaling-app/apps/web/src/stores/user.ts

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']
}
})