Backend: - Goal model: title, description, status (active/paused/completed/abandoned), progress (auto-computed from milestones), target_date, category, color, icon - GoalStep model: unified phase/milestone with parent nesting - GoalReview model: periodic reflection with rating - goal_tasks M2M: link existing tasks to goals - /api/goals CRUD + steps CRUD + reviews + task linking + status toggle - Progress auto-calculated from milestone completion ratio Frontend: - GoalPage: card grid with progress bars, status filter - GoalDetailPage: step tree (phases > milestones), reviews, linked tasks - GoalDialog: create/edit form with color/icon picker - Goal navigation in AppHeader - useGoalStore: full Pinia store for all goal operations Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
212 lines
6.0 KiB
TypeScript
212 lines
6.0 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
import type { Goal, GoalDetail, GoalStep, GoalReview, GoalFormData, GoalStepFormData, GoalReviewFormData } from '@/api/types'
|
|
import * as goalApi from '@/api/goals'
|
|
|
|
export const useGoalStore = defineStore('goal', () => {
|
|
const goals = ref<Goal[]>([])
|
|
const currentGoal = ref<GoalDetail | null>(null)
|
|
const loading = ref(false)
|
|
const error = ref('')
|
|
|
|
const activeGoals = computed(() => goals.value.filter(g => g.status === 'active'))
|
|
const pausedGoals = computed(() => goals.value.filter(g => g.status === 'paused'))
|
|
const completedGoals = computed(() => goals.value.filter(g => g.status === 'completed'))
|
|
|
|
async function fetchGoals(status?: string) {
|
|
loading.value = true
|
|
error.value = ''
|
|
try {
|
|
goals.value = await goalApi.getGoals(status)
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '获取目标列表失败'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function fetchGoal(id: number) {
|
|
loading.value = true
|
|
error.value = ''
|
|
try {
|
|
currentGoal.value = await goalApi.getGoal(id)
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '获取目标详情失败'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function createGoal(data: GoalFormData): Promise<GoalDetail | null> {
|
|
loading.value = true
|
|
error.value = ''
|
|
try {
|
|
const goal = await goalApi.createGoal(data)
|
|
await fetchGoals()
|
|
return goal
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '创建目标失败'
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function updateGoal(id: number, data: Partial<GoalFormData>): Promise<GoalDetail | null> {
|
|
loading.value = true
|
|
error.value = ''
|
|
try {
|
|
const goal = await goalApi.updateGoal(id, data)
|
|
if (currentGoal.value?.id === id) {
|
|
currentGoal.value = { ...currentGoal.value, ...goal }
|
|
}
|
|
await fetchGoals()
|
|
return goal
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '更新目标失败'
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function deleteGoal(id: number): Promise<boolean> {
|
|
loading.value = true
|
|
error.value = ''
|
|
try {
|
|
await goalApi.deleteGoal(id)
|
|
if (currentGoal.value?.id === id) {
|
|
currentGoal.value = null
|
|
}
|
|
await fetchGoals()
|
|
return true
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '删除目标失败'
|
|
return false
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function updateGoalStatus(id: number, status: string) {
|
|
try {
|
|
const goal = await goalApi.updateGoalStatus(id, status)
|
|
if (currentGoal.value?.id === id) {
|
|
currentGoal.value = { ...currentGoal.value, ...goal }
|
|
}
|
|
await fetchGoals()
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '更新状态失败'
|
|
}
|
|
}
|
|
|
|
// ============ Steps ============
|
|
|
|
async function createStep(goalId: number, data: GoalStepFormData): Promise<GoalStep | null> {
|
|
try {
|
|
const step = await goalApi.createStep(goalId, data)
|
|
if (currentGoal.value?.id === goalId) {
|
|
await fetchGoal(goalId)
|
|
}
|
|
await fetchGoals()
|
|
return step
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '添加步骤失败'
|
|
return null
|
|
}
|
|
}
|
|
|
|
async function updateStep(goalId: number, stepId: number, data: Partial<GoalStepFormData>) {
|
|
try {
|
|
await goalApi.updateStep(goalId, stepId, data)
|
|
if (currentGoal.value?.id === goalId) {
|
|
await fetchGoal(goalId)
|
|
}
|
|
await fetchGoals()
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '更新步骤失败'
|
|
}
|
|
}
|
|
|
|
async function deleteStep(goalId: number, stepId: number) {
|
|
try {
|
|
await goalApi.deleteStep(goalId, stepId)
|
|
if (currentGoal.value?.id === goalId) {
|
|
await fetchGoal(goalId)
|
|
}
|
|
await fetchGoals()
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '删除步骤失败'
|
|
}
|
|
}
|
|
|
|
async function toggleStep(goalId: number, stepId: number) {
|
|
try {
|
|
await goalApi.toggleStep(goalId, stepId)
|
|
if (currentGoal.value?.id === goalId) {
|
|
await fetchGoal(goalId)
|
|
}
|
|
await fetchGoals()
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '切换步骤状态失败'
|
|
}
|
|
}
|
|
|
|
// ============ Reviews ============
|
|
|
|
async function createReview(goalId: number, data: GoalReviewFormData) {
|
|
try {
|
|
await goalApi.createReview(goalId, data)
|
|
if (currentGoal.value?.id === goalId) {
|
|
await fetchGoal(goalId)
|
|
}
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '创建复盘失败'
|
|
}
|
|
}
|
|
|
|
async function deleteReview(goalId: number, reviewId: number) {
|
|
try {
|
|
await goalApi.deleteReview(goalId, reviewId)
|
|
if (currentGoal.value?.id === goalId) {
|
|
await fetchGoal(goalId)
|
|
}
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '删除复盘失败'
|
|
}
|
|
}
|
|
|
|
// ============ Task Linking ============
|
|
|
|
async function linkTask(goalId: number, taskId: number) {
|
|
try {
|
|
await goalApi.linkTask(goalId, taskId)
|
|
if (currentGoal.value?.id === goalId) {
|
|
await fetchGoal(goalId)
|
|
}
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '关联任务失败'
|
|
}
|
|
}
|
|
|
|
async function unlinkTask(goalId: number, taskId: number) {
|
|
try {
|
|
await goalApi.unlinkTask(goalId, taskId)
|
|
if (currentGoal.value?.id === goalId) {
|
|
await fetchGoal(goalId)
|
|
}
|
|
} catch (e: any) {
|
|
error.value = e?.response?.data?.detail || '取消关联失败'
|
|
}
|
|
}
|
|
|
|
return {
|
|
goals, currentGoal, loading, error,
|
|
activeGoals, pausedGoals, completedGoals,
|
|
fetchGoals, fetchGoal, createGoal, updateGoal, deleteGoal, updateGoalStatus,
|
|
createStep, updateStep, deleteStep, toggleStep,
|
|
createReview, deleteReview,
|
|
linkTask, unlinkTask,
|
|
}
|
|
})
|