hookehuyr

feat(收藏): 实现课程收藏功能并优化相关页面

- 新增 `favorite.js` API 文件,提供收藏列表、新增和取消收藏的接口
- 在 `CourseDetailPage.vue` 中实现收藏和取消收藏的逻辑,并添加交互反馈
- 在 `MyFavoritesPage.vue` 中替换模拟数据为真实接口调用,优化收藏课程加载逻辑
- 调整 `jsconfig.json` 配置文件,包含新增的 API 文件路径
...@@ -24,6 +24,6 @@ ...@@ -24,6 +24,6 @@
24 "esModuleInterop": true, 24 "esModuleInterop": true,
25 "skipLibCheck": true 25 "skipLibCheck": true
26 }, 26 },
27 - "include": ["src/**/*", "build/**/*", "vite.config.js"], 27 + "include": ["src/**/*", "build/**/*", "vite.config.js", "src/api/.js"],
28 "exclude": ["node_modules", "dist"] 28 "exclude": ["node_modules", "dist"]
29 } 29 }
......
1 +/*
2 + * @Date: 2025-04-16 16:21:37
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-04-17 10:04:30
5 + * @FilePath: /mlaj/src/api/favorite.js
6 + * @Description: 收藏相关接口
7 + */
8 +import { fn, fetch } from './fn'
9 +
10 +const Api = {
11 + GROUP_FAVORITE_LIST: '/srv/?a=group_favorite_list',
12 + FAVORITE_ADD: '/srv/?a=group_favorite',
13 + FAVORITE_CANCEL: '/srv/?a=group_unfavorite',
14 +}
15 +
16 +/**
17 + * @description: 获取课程收藏列表
18 + * @param: page 页码
19 + * @param: limit 每页数量
20 + * @return: data: { id: 收藏ID, title: 课程名称, price: 优惠价格, original_price: 原价, feature: 课程特色, highlights: 课程亮点, learning_goal: 学习目标, count: 课程章节数, cover: 封面图 }
21 + */
22 +export const getGroupFavoriteListAPI = (params) => fn(fetch.get(Api.GROUP_FAVORITE_LIST, params))
23 +
24 +/**
25 + * @description: 新增收藏
26 + * @param: group_id 课程ID
27 + * @return: data: { }
28 + */
29 +export const addFavoriteAPI = (params) => fn(fetch.post(Api.FAVORITE_ADD, params))
30 +
31 +/**
32 + * @description: 取消收藏
33 + * @param: group_id 课程ID
34 + * @return: data: { }
35 + */
36 +export const cancelFavoriteAPI = (params) => fn(fetch.post(Api.FAVORITE_CANCEL, params))
This diff is collapsed. Click to expand it.
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 <div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20"> 2 <div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20">
3 <!-- 分类切换 --> 3 <!-- 分类切换 -->
4 <div class="px-4 py-3"> 4 <div class="px-4 py-3">
5 - <van-tabs v-model:active="activeTab" sticky swipeable title-active-color="#4caf50" color="#4caf50"> 5 + <van-tabs v-model:active="activeTab" @click-tab="onClickTab" sticky swipeable title-active-color="#4caf50" color="#4caf50">
6 <van-tab title="课程" name="courses"> 6 <van-tab title="课程" name="courses">
7 <van-list 7 <van-list
8 v-model:loading="coursesLoading" 8 v-model:loading="coursesLoading"
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
24 </van-tab> 24 </van-tab>
25 25
26 <van-tab title="活动" name="activities"> 26 <van-tab title="活动" name="activities">
27 - <van-list 27 + <!--<van-list
28 v-model:loading="activitiesLoading" 28 v-model:loading="activitiesLoading"
29 :finished="activitiesFinished" 29 :finished="activitiesFinished"
30 finished-text="没有更多了" 30 finished-text="没有更多了"
...@@ -34,13 +34,13 @@ ...@@ -34,13 +34,13 @@
34 <ActivityCard v-for="activity in favoriteActivities" :key="activity.id" :activity="activity" /> 34 <ActivityCard v-for="activity in favoriteActivities" :key="activity.id" :activity="activity" />
35 </van-list> 35 </van-list>
36 36
37 - <!-- 无数据提示 --> 37 + <!~~ 无数据提示 ~~>
38 <div v-if="!activitiesLoading && favoriteActivities.length === 0" class="flex flex-col items-center justify-center py-12"> 38 <div v-if="!activitiesLoading && favoriteActivities.length === 0" class="flex flex-col items-center justify-center py-12">
39 <svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 39 <svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
40 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" /> 40 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
41 </svg> 41 </svg>
42 <p class="mt-4 text-gray-500">暂无收藏活动</p> 42 <p class="mt-4 text-gray-500">暂无收藏活动</p>
43 - </div> 43 + </div>-->
44 </van-tab> 44 </van-tab>
45 </van-tabs> 45 </van-tabs>
46 </div> 46 </div>
...@@ -55,6 +55,9 @@ import ActivityCard from '@/components/ui/ActivityCard.vue'; ...@@ -55,6 +55,9 @@ import ActivityCard from '@/components/ui/ActivityCard.vue';
55 import { courses as mockCourses, activities as mockActivities } from '@/utils/mockData'; 55 import { courses as mockCourses, activities as mockActivities } from '@/utils/mockData';
56 import { useTitle } from '@vueuse/core'; 56 import { useTitle } from '@vueuse/core';
57 57
58 +// 导入接口
59 +import { getGroupFavoriteListAPI } from '@/api/favorite';
60 +
58 const $route = useRoute(); 61 const $route = useRoute();
59 const $router = useRouter(); 62 const $router = useRouter();
60 useTitle($route.meta.title); 63 useTitle($route.meta.title);
...@@ -67,8 +70,8 @@ const favoriteActivities = ref([]); ...@@ -67,8 +70,8 @@ const favoriteActivities = ref([]);
67 // 课程列表加载状态 70 // 课程列表加载状态
68 const coursesLoading = ref(false); 71 const coursesLoading = ref(false);
69 const coursesFinished = ref(false); 72 const coursesFinished = ref(false);
70 -const coursePage = ref(1); 73 +const coursePage = ref(0);
71 -const coursePageSize = 10; 74 +const courseLimit = ref(5);
72 75
73 // 活动列表加载状态 76 // 活动列表加载状态
74 const activitiesLoading = ref(false); 77 const activitiesLoading = ref(false);
...@@ -77,42 +80,41 @@ const activitiesPage = ref(1); ...@@ -77,42 +80,41 @@ const activitiesPage = ref(1);
77 const activitiesPageSize = 10; 80 const activitiesPageSize = 10;
78 81
79 // 加载收藏课程 82 // 加载收藏课程
80 -const onCoursesLoad = () => { 83 +const onCoursesLoad = async () => {
81 - coursesLoading.value = true; 84 + const nextPage = coursePage.value;
82 - // 模拟异步加载 85 + const res = await getGroupFavoriteListAPI({ limit: courseLimit.value, page: nextPage });
83 - setTimeout(() => { 86 + if (res.code) {
84 - const start = (coursePage.value - 1) * coursePageSize; 87 + favoriteCourses.value = [...favoriteCourses.value, ...res.data];
85 - const end = start + coursePageSize; 88 + coursesFinished.value = res.data.length < courseLimit.value;
86 - const newCourses = mockCourses.slice(start, end); 89 + coursePage.value = nextPage + 1;
87 - 90 + }
88 - favoriteCourses.value.push(...newCourses); 91 + coursesLoading.value = false;
89 - coursesLoading.value = false;
90 -
91 - if (newCourses.length < coursePageSize) {
92 - coursesFinished.value = true;
93 - } else {
94 - coursePage.value += 1;
95 - }
96 - }, 1000);
97 }; 92 };
98 93
99 // 加载收藏活动 94 // 加载收藏活动
100 -const onActivitiesLoad = () => { 95 +// const onActivitiesLoad = () => {
101 - activitiesLoading.value = true; 96 +// activitiesLoading.value = true;
102 - // 模拟异步加载 97 +// // 模拟异步加载
103 - setTimeout(() => { 98 +// setTimeout(() => {
104 - const start = (activitiesPage.value - 1) * activitiesPageSize; 99 +// const start = (activitiesPage.value - 1) * activitiesPageSize;
105 - const end = start + activitiesPageSize; 100 +// const end = start + activitiesPageSize;
106 - const newActivities = mockActivities.slice(start, end); 101 +// const newActivities = mockActivities.slice(start, end);
102 +
103 +// favoriteActivities.value.push(...newActivities);
104 +// activitiesLoading.value = false;
107 105
108 - favoriteActivities.value.push(...newActivities); 106 +// if (newActivities.length < activitiesPageSize) {
109 - activitiesLoading.value = false; 107 +// activitiesFinished.value = true;
108 +// } else {
109 +// activitiesPage.value += 1;
110 +// }
111 +// }, 1000);
112 +// };
110 113
111 - if (newActivities.length < activitiesPageSize) { 114 +// 切换标签页
112 - activitiesFinished.value = true; 115 +const onClickTab = ({ name, title }) => {
113 - } else { 116 + if (name === 'activities') {
114 - activitiesPage.value += 1; 117 + location.href = 'http://www.baidu.com'
115 - } 118 + }
116 - }, 1000);
117 }; 119 };
118 </script> 120 </script>
......