2025-07-22 14:39:45 +08:00
|
|
|
|
import { defineStore } from 'pinia'
|
|
|
|
|
import { ref, computed } from 'vue'
|
2025-07-28 09:51:21 +08:00
|
|
|
|
import { CourseApi } from '@/api/modules/course'
|
2025-07-22 14:39:45 +08:00
|
|
|
|
|
|
|
|
|
export interface Course {
|
|
|
|
|
id: number
|
|
|
|
|
title: string
|
|
|
|
|
description: string
|
|
|
|
|
instructor: string
|
|
|
|
|
instructorAvatar?: string
|
|
|
|
|
thumbnail: string
|
|
|
|
|
price: number
|
|
|
|
|
originalPrice?: number
|
|
|
|
|
rating: number
|
|
|
|
|
studentsCount: number
|
|
|
|
|
duration: string
|
|
|
|
|
level: 'beginner' | 'intermediate' | 'advanced'
|
|
|
|
|
category: string
|
|
|
|
|
tags: string[]
|
|
|
|
|
createdAt: string
|
|
|
|
|
updatedAt: string
|
|
|
|
|
isEnrolled?: boolean
|
|
|
|
|
progress?: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Lesson {
|
|
|
|
|
id: number
|
|
|
|
|
courseId: number
|
|
|
|
|
title: string
|
|
|
|
|
description: string
|
|
|
|
|
videoUrl?: string
|
|
|
|
|
duration: string
|
|
|
|
|
order: number
|
|
|
|
|
isCompleted?: boolean
|
|
|
|
|
isFree?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const useCourseStore = defineStore('course', () => {
|
|
|
|
|
// 状态
|
|
|
|
|
const courses = ref<Course[]>([])
|
|
|
|
|
const currentCourse = ref<Course | null>(null)
|
|
|
|
|
const lessons = ref<Lesson[]>([])
|
|
|
|
|
const enrolledCourses = ref<Course[]>([])
|
|
|
|
|
const isLoading = ref(false)
|
|
|
|
|
const searchQuery = ref('')
|
|
|
|
|
const selectedCategory = ref('')
|
|
|
|
|
const selectedLevel = ref('')
|
|
|
|
|
|
|
|
|
|
// 计算属性
|
|
|
|
|
const filteredCourses = computed(() => {
|
|
|
|
|
let filtered = courses.value
|
|
|
|
|
|
|
|
|
|
if (searchQuery.value) {
|
|
|
|
|
filtered = filtered.filter(course =>
|
|
|
|
|
course.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
|
|
|
|
course.description.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
|
|
|
|
course.instructor.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selectedCategory.value) {
|
|
|
|
|
filtered = filtered.filter(course => course.category === selectedCategory.value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selectedLevel.value) {
|
|
|
|
|
filtered = filtered.filter(course => course.level === selectedLevel.value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return filtered
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const categories = computed(() => {
|
|
|
|
|
const cats = courses.value.map(course => course.category)
|
|
|
|
|
return [...new Set(cats)]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 方法
|
|
|
|
|
const fetchCourses = async () => {
|
|
|
|
|
isLoading.value = true
|
|
|
|
|
try {
|
2025-07-28 09:51:21 +08:00
|
|
|
|
console.log('尝试从API获取课程数据...')
|
|
|
|
|
const response = await CourseApi.getCourses()
|
|
|
|
|
console.log('API响应:', response)
|
|
|
|
|
courses.value = response.data.list
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('API调用失败,使用模拟数据:', error)
|
|
|
|
|
// 如果API调用失败,使用模拟数据作为后备
|
2025-07-22 14:39:45 +08:00
|
|
|
|
const mockCourses: Course[] = [
|
|
|
|
|
{
|
|
|
|
|
id: 1,
|
|
|
|
|
title: 'Vue.js 3 完整教程',
|
|
|
|
|
description: '从零开始学习Vue.js 3,包括Composition API、TypeScript集成等现代开发技术',
|
|
|
|
|
instructor: '李老师',
|
|
|
|
|
instructorAvatar: 'https://via.placeholder.com/50',
|
|
|
|
|
thumbnail: 'https://via.placeholder.com/300x200',
|
|
|
|
|
price: 299,
|
|
|
|
|
originalPrice: 399,
|
|
|
|
|
rating: 4.8,
|
|
|
|
|
studentsCount: 1234,
|
|
|
|
|
duration: '12小时',
|
|
|
|
|
level: 'intermediate',
|
|
|
|
|
category: '前端开发',
|
|
|
|
|
tags: ['Vue.js', 'JavaScript', 'TypeScript'],
|
|
|
|
|
createdAt: '2024-01-01',
|
|
|
|
|
updatedAt: '2024-01-15'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 2,
|
|
|
|
|
title: 'React 18 实战开发',
|
|
|
|
|
description: '掌握React 18的新特性,包括并发渲染、Suspense等高级功能',
|
|
|
|
|
instructor: '王老师',
|
|
|
|
|
instructorAvatar: 'https://via.placeholder.com/50',
|
|
|
|
|
thumbnail: 'https://via.placeholder.com/300x200',
|
|
|
|
|
price: 399,
|
|
|
|
|
rating: 4.9,
|
|
|
|
|
studentsCount: 2156,
|
|
|
|
|
duration: '15小时',
|
|
|
|
|
level: 'advanced',
|
|
|
|
|
category: '前端开发',
|
|
|
|
|
tags: ['React', 'JavaScript', 'Hooks'],
|
|
|
|
|
createdAt: '2024-01-05',
|
|
|
|
|
updatedAt: '2024-01-20'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 3,
|
|
|
|
|
title: 'Node.js 后端开发',
|
|
|
|
|
description: '学习Node.js后端开发,包括Express、数据库操作、API设计等',
|
|
|
|
|
instructor: '张老师',
|
|
|
|
|
instructorAvatar: 'https://via.placeholder.com/50',
|
|
|
|
|
thumbnail: 'https://via.placeholder.com/300x200',
|
|
|
|
|
price: 349,
|
|
|
|
|
rating: 4.7,
|
|
|
|
|
studentsCount: 987,
|
|
|
|
|
duration: '18小时',
|
|
|
|
|
level: 'intermediate',
|
|
|
|
|
category: '后端开发',
|
|
|
|
|
tags: ['Node.js', 'Express', 'MongoDB'],
|
|
|
|
|
createdAt: '2024-01-10',
|
|
|
|
|
updatedAt: '2024-01-25'
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
courses.value = mockCourses
|
|
|
|
|
} finally {
|
|
|
|
|
isLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchCourseById = async (id: number) => {
|
|
|
|
|
isLoading.value = true
|
|
|
|
|
try {
|
2025-07-28 09:51:21 +08:00
|
|
|
|
const response = await CourseApi.getCourseById(id)
|
|
|
|
|
currentCourse.value = response.data
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to fetch course:', error)
|
|
|
|
|
// 如果API调用失败,从本地数据中查找
|
2025-07-22 14:39:45 +08:00
|
|
|
|
const course = courses.value.find(c => c.id === id)
|
|
|
|
|
if (course) {
|
|
|
|
|
currentCourse.value = course
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
isLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchLessons = async (courseId: number) => {
|
|
|
|
|
isLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
// 模拟API调用
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500))
|
|
|
|
|
|
|
|
|
|
// 模拟课程章节数据
|
|
|
|
|
const mockLessons: Lesson[] = [
|
|
|
|
|
{
|
|
|
|
|
id: 1,
|
|
|
|
|
courseId,
|
|
|
|
|
title: '课程介绍',
|
|
|
|
|
description: '了解课程内容和学习目标',
|
|
|
|
|
duration: '10分钟',
|
|
|
|
|
order: 1,
|
|
|
|
|
isFree: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 2,
|
|
|
|
|
courseId,
|
|
|
|
|
title: '环境搭建',
|
|
|
|
|
description: '配置开发环境和工具',
|
|
|
|
|
duration: '20分钟',
|
|
|
|
|
order: 2,
|
|
|
|
|
isFree: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 3,
|
|
|
|
|
courseId,
|
|
|
|
|
title: '基础语法',
|
|
|
|
|
description: '学习基础语法和概念',
|
|
|
|
|
duration: '45分钟',
|
|
|
|
|
order: 3
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
lessons.value = mockLessons
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to fetch lessons:', error)
|
|
|
|
|
} finally {
|
|
|
|
|
isLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const enrollCourse = async (courseId: number) => {
|
|
|
|
|
isLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
// 模拟API调用
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
|
|
|
|
|
|
|
|
const course = courses.value.find(c => c.id === courseId)
|
|
|
|
|
if (course) {
|
|
|
|
|
course.isEnrolled = true
|
|
|
|
|
course.progress = 0
|
|
|
|
|
enrolledCourses.value.push(course)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { success: true, message: '报名成功' }
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return { success: false, message: '报名失败' }
|
|
|
|
|
} finally {
|
|
|
|
|
isLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updateProgress = async (courseId: number, progress: number) => {
|
|
|
|
|
const course = enrolledCourses.value.find(c => c.id === courseId)
|
|
|
|
|
if (course) {
|
|
|
|
|
course.progress = progress
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
// 状态
|
|
|
|
|
courses,
|
|
|
|
|
currentCourse,
|
|
|
|
|
lessons,
|
|
|
|
|
enrolledCourses,
|
|
|
|
|
isLoading,
|
|
|
|
|
searchQuery,
|
|
|
|
|
selectedCategory,
|
|
|
|
|
selectedLevel,
|
|
|
|
|
// 计算属性
|
|
|
|
|
filteredCourses,
|
|
|
|
|
categories,
|
|
|
|
|
// 方法
|
|
|
|
|
fetchCourses,
|
|
|
|
|
fetchCourseById,
|
|
|
|
|
fetchLessons,
|
|
|
|
|
enrollCourse,
|
|
|
|
|
updateProgress
|
|
|
|
|
}
|
|
|
|
|
})
|