hookehuyr

feat(打卡): 完善打卡功能逻辑和接口

- 在ProfilePage.vue中重构打卡按钮点击逻辑,添加无任务时的提示
- 在checkin.js中添加删除、点赞和取消点赞的API接口
- 在IndexCheckInPage.vue中实现点赞和删除打卡动态的功能
- 在HomePage.vue中优化打卡任务显示逻辑,添加无任务时的提示
1 /* 1 /*
2 * @Date: 2025-06-06 09:26:16 2 * @Date: 2025-06-06 09:26:16
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-06-06 17:46:06 4 + * @LastEditTime: 2025-06-06 18:12:47
5 * @FilePath: /mlaj/src/api/checkin.js 5 * @FilePath: /mlaj/src/api/checkin.js
6 * @Description: 签到模块相关接口 6 * @Description: 签到模块相关接口
7 */ 7 */
...@@ -15,6 +15,9 @@ const Api = { ...@@ -15,6 +15,9 @@ const Api = {
15 TASK_UPLOAD_LIST: '/srv/?a=checkin&t=upload_list', 15 TASK_UPLOAD_LIST: '/srv/?a=checkin&t=upload_list',
16 TASK_UPLOAD_INFO: '/srv/?a=checkin&t=upload_info', 16 TASK_UPLOAD_INFO: '/srv/?a=checkin&t=upload_info',
17 TASK_UPLOAD_EDIT: '/srv/?a=checkin&t=upload_edit', 17 TASK_UPLOAD_EDIT: '/srv/?a=checkin&t=upload_edit',
18 + TASK_UPLOAD_DEL: '/srv/?a=checkin&t=upload_del',
19 + TASK_UPLOAD_LIKE: '/srv/?a=checkin&t=like',
20 + TASK_UPLOAD_DISLIKE: '/srv/?a=checkin&t=dislike',
18 } 21 }
19 22
20 /** 23 /**
...@@ -78,3 +81,24 @@ export const getUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_IN ...@@ -78,3 +81,24 @@ export const getUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_IN
78 * @returns 81 * @returns
79 */ 82 */
80 export const editUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_EDIT, params)) 83 export const editUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_EDIT, params))
84 +
85 +/**
86 + * @description: 删除打卡动态详情
87 + * @param i 打卡动态ID
88 + * @returns
89 + */
90 +export const delUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_DEL, params))
91 +
92 +/**
93 + * @description: 给打卡点赞
94 + * @param checkin_id 打卡动态ID
95 + * @returns
96 + */
97 +export const likeUploadTaskInfoAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_LIKE, params))
98 +
99 +/**
100 + * @description: 取消点赞
101 + * @param checkin_id 打卡动态ID
102 + * @returns
103 + */
104 +export const dislikeUploadTaskInfoAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_DISLIKE, params))
......
1 <!-- 1 <!--
2 * @Date: 2025-03-20 19:55:21 2 * @Date: 2025-03-20 19:55:21
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-06-06 17:26:39 4 + * @LastEditTime: 2025-06-06 18:22:14
5 * @FilePath: /mlaj/src/views/HomePage.vue 5 * @FilePath: /mlaj/src/views/HomePage.vue
6 * @Description: 美乐爱觉教育首页组件 6 * @Description: 美乐爱觉教育首页组件
7 * 7 *
...@@ -74,62 +74,69 @@ ...@@ -74,62 +74,69 @@
74 </FrostedGlass> 74 </FrostedGlass>
75 75
76 <!-- Daily Check-in --> 76 <!-- Daily Check-in -->
77 - <FrostedGlass v-if="checkInTypes.length" class="p-4 rounded-xl"> 77 + <FrostedGlass class="p-4 rounded-xl">
78 <div class="flex justify-between items-center mb-3"> 78 <div class="flex justify-between items-center mb-3">
79 <h3 class="font-medium">今日打卡</h3> 79 <h3 class="font-medium">今日打卡</h3>
80 <router-link to="/profile" class="text-green-600 text-sm">打卡记录</router-link> 80 <router-link to="/profile" class="text-green-600 text-sm">打卡记录</router-link>
81 </div> 81 </div>
82 82
83 - <div v-if="checkInSuccess" class="bg-green-50 border border-green-200 rounded-lg p-4 text-center"> 83 + <template v-if="checkInTypes.length">
84 - <svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-green-500 mx-auto mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 84 + <div v-if="checkInSuccess" class="bg-green-50 border border-green-200 rounded-lg p-4 text-center">
85 - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> 85 + <svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-green-500 mx-auto mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
86 - </svg> 86 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
87 - <h4 class="text-green-700 font-medium mb-1">打卡成功!</h4> 87 + </svg>
88 - <!-- <p class="text-green-600 text-sm">+5 积分已添加到您的账户</p> --> 88 + <h4 class="text-green-700 font-medium mb-1">打卡成功!</h4>
89 - </div> 89 + <!-- <p class="text-green-600 text-sm">+5 积分已添加到您的账户</p> -->
90 - <template v-else>
91 - <div class="flex space-x-2 py-2">
92 - <button
93 - v-for="checkInType in checkInTypes"
94 - :key="checkInType.id"
95 - :class="[
96 - 'flex-1 flex flex-col items-center p-2 rounded-lg transition-colors',
97 - selectedCheckIn?.id === checkInType.id
98 - ? 'bg-green-100 border border-green-200'
99 - : 'bg-white/70 border border-gray-100 hover:bg-white'
100 - ]"
101 - @click="handleCheckInSelect(checkInType)"
102 - >
103 - <div :class="[
104 - 'w-12 h-12 rounded-full flex items-center justify-center mb-1 transition-colors',
105 - selectedCheckIn?.id === checkInType.id
106 - ? 'bg-green-500 text-white'
107 - : 'bg-gray-100 text-gray-500'
108 - ]">
109 - <van-icon v-if="checkInType.task_type === 'checkin'" name="edit" size="1.5rem" />
110 - <van-icon v-if="checkInType.task_type === 'upload'" name="tosend" size="1.5rem" />
111 - </div>
112 - <span class="text-xs">{{ checkInType.name }}</span>
113 - </button>
114 </div> 90 </div>
91 + <template v-else>
92 + <div class="flex space-x-2 py-2">
93 + <button
94 + v-for="checkInType in checkInTypes"
95 + :key="checkInType.id"
96 + :class="[
97 + 'flex-1 flex flex-col items-center p-2 rounded-lg transition-colors',
98 + selectedCheckIn?.id === checkInType.id
99 + ? 'bg-green-100 border border-green-200'
100 + : 'bg-white/70 border border-gray-100 hover:bg-white'
101 + ]"
102 + @click="handleCheckInSelect(checkInType)"
103 + >
104 + <div :class="[
105 + 'w-12 h-12 rounded-full flex items-center justify-center mb-1 transition-colors',
106 + selectedCheckIn?.id === checkInType.id
107 + ? 'bg-green-500 text-white'
108 + : 'bg-gray-100 text-gray-500'
109 + ]">
110 + <van-icon v-if="checkInType.task_type === 'checkin'" name="edit" size="1.5rem" />
111 + <van-icon v-if="checkInType.task_type === 'upload'" name="tosend" size="1.5rem" />
112 + </div>
113 + <span class="text-xs">{{ checkInType.name }}</span>
114 + </button>
115 + </div>
115 116
116 - <div v-if="selectedCheckIn" class="mt-3"> 117 + <div v-if="selectedCheckIn" class="mt-3">
117 - <!-- <textarea 118 + <!-- <textarea
118 - :placeholder="`请输入${selectedCheckIn.name}内容...`" 119 + :placeholder="`请输入${selectedCheckIn.name}内容...`"
119 - v-model="checkInContent" 120 + v-model="checkInContent"
120 - class="w-full p-3 border border-gray-200 rounded-lg text-sm resize-none h-24" 121 + class="w-full p-3 border border-gray-200 rounded-lg text-sm resize-none h-24"
121 - /> --> 122 + /> -->
122 - <button 123 + <button
123 - class="mt-2 w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-2 rounded-lg flex items-center justify-center" 124 + class="mt-2 w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-2 rounded-lg flex items-center justify-center"
124 - @click="handleCheckInSubmit" 125 + @click="handleCheckInSubmit"
125 - :disabled="isCheckingIn" 126 + :disabled="isCheckingIn"
126 - > 127 + >
127 - <template v-if="isCheckingIn"> 128 + <template v-if="isCheckingIn">
128 - <div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div> 129 + <div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>
129 - 提交中... 130 + 提交中...
130 - </template> 131 + </template>
131 - <template v-else>提交打卡</template> 132 + <template v-else>提交打卡</template>
132 - </button> 133 + </button>
134 + </div>
135 + </template>
136 + </template>
137 + <template v-else>
138 + <div class="text-center">
139 + <p class="text-gray-500">暂无打卡任务</p>
133 </div> 140 </div>
134 </template> 141 </template>
135 </FrostedGlass> 142 </FrostedGlass>
......
1 <!-- 1 <!--
2 * @Date: 2025-05-29 15:34:17 2 * @Date: 2025-05-29 15:34:17
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-06-06 17:40:39 4 + * @LastEditTime: 2025-06-06 18:27:24
5 * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue 5 * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -47,8 +47,7 @@ ...@@ -47,8 +47,7 @@
47 </div> 47 </div>
48 </div> 48 </div>
49 49
50 - <!-- <div v-if="!taskDetail.is_gray" class="text-wrapper"> --> 50 + <div v-if="!taskDetail.is_finish" class="text-wrapper">
51 - <div v-if="taskDetail.is_gray" class="text-wrapper">
52 <div class="text-header">打卡类型</div> 51 <div class="text-header">打卡类型</div>
53 <div class="upload-wrapper"> 52 <div class="upload-wrapper">
54 <div @click="goToCheckinImagePage" class="upload-boxer"> 53 <div @click="goToCheckinImagePage" class="upload-boxer">
...@@ -164,7 +163,7 @@ import AudioPlayer from "@/components/ui/AudioPlayer.vue"; ...@@ -164,7 +163,7 @@ import AudioPlayer from "@/components/ui/AudioPlayer.vue";
164 import { useTitle } from '@vueuse/core'; 163 import { useTitle } from '@vueuse/core';
165 import dayjs from 'dayjs'; 164 import dayjs from 'dayjs';
166 165
167 -import { getTaskDetailAPI, getUploadTaskListAPI } from "@/api/checkin"; 166 +import { getTaskDetailAPI, getUploadTaskListAPI, delUploadTaskInfoAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI } from "@/api/checkin";
168 167
169 const route = useRoute() 168 const route = useRoute()
170 const router = useRouter() 169 const router = useRouter()
...@@ -449,7 +448,8 @@ const mockPosts = ref([ ...@@ -449,7 +448,8 @@ const mockPosts = ref([
449 ]); 448 ]);
450 449
451 const themeVars = { 450 const themeVars = {
452 - calendarSelectedDayBackground: '#4caf50' 451 + calendarSelectedDayBackground: '#4caf50',
452 + calendarHeaderShadow: 'rgba(0, 0, 0, 0.1)',
453 } 453 }
454 454
455 const progress1 = ref(0); 455 const progress1 = ref(0);
...@@ -531,9 +531,22 @@ const goToCheckinAudioPage = (type) => { ...@@ -531,9 +531,22 @@ const goToCheckinAudioPage = (type) => {
531 }) 531 })
532 } 532 }
533 533
534 -const handLike = (post) => { 534 +const handLike = async (post) => {
535 - post.is_liked = !post.is_liked; 535 + if (!post.is_liked) {
536 - // TODO: 调用接口 536 + const { code, data } = await likeUploadTaskInfoAPI({ checkin_id: post.id, })
537 + if (code) {
538 + showSuccessToast('点赞成功')
539 + post.likes++;
540 + post.is_liked = true;
541 + }
542 + } else {
543 + const { code, data } = await dislikeUploadTaskInfoAPI({ checkin_id: post.id, })
544 + if (code) {
545 + showSuccessToast('取消点赞成功')
546 + post.likes--;
547 + post.is_liked = false;
548 + }
549 + }
537 } 550 }
538 551
539 const editCheckin = (post) => { 552 const editCheckin = (post) => {
...@@ -574,12 +587,16 @@ const delCheckin = (post) => { ...@@ -574,12 +587,16 @@ const delCheckin = (post) => {
574 message: '您是否确定要删除该动态?', 587 message: '您是否确定要删除该动态?',
575 confirmButtonColor: '#4caf50', 588 confirmButtonColor: '#4caf50',
576 }) 589 })
577 - .then(() => { 590 + .then(async () => {
578 - // on confirm 591 + // 调用接口
579 - // TODO: 调用接口 592 + const { code, data } = await delUploadTaskInfoAPI({ i: post.id });
580 - // 删除成功后,刷新页面 593 + if (code) {
581 - showSuccessToast('成功文案'); 594 + // 删除成功后,刷新页面
582 - // showFailToast('失败文案'); 595 + showSuccessToast('删除成功');
596 + router.go(0);
597 + } else {
598 + showErrorToast('删除失败');
599 + }
583 }) 600 })
584 .catch(() => { 601 .catch(() => {
585 // on cancel 602 // on cancel
......
...@@ -55,8 +55,8 @@ ...@@ -55,8 +55,8 @@
55 </div> 55 </div>
56 <div class="text-xs text-gray-500 mt-1">最长连续</div> 56 <div class="text-xs text-gray-500 mt-1">最长连续</div>
57 </div> 57 </div>
58 - <div v-if="checkinData.length"> 58 + <div>
59 - <div @click="showCheckInDialog = true" class="cursor-pointer"> 59 + <div @click="handleCheckin" class="cursor-pointer">
60 <button 60 <button
61 class="bg-gradient-to-r from-green-500 to-green-600 text-white py-2 px-6 rounded-full text-sm shadow-sm" 61 class="bg-gradient-to-r from-green-500 to-green-600 text-white py-2 px-6 rounded-full text-sm shadow-sm"
62 > 62 >
...@@ -274,4 +274,12 @@ const menuItems3 = [ ...@@ -274,4 +274,12 @@ const menuItems3 = [
274 path: "/profile/settings", 274 path: "/profile/settings",
275 }, 275 },
276 ]; 276 ];
277 +
278 +const handleCheckin = () => {
279 + if (checkinData.value.length) {
280 + showCheckInDialog.value = true;
281 + } else {
282 + showToast('暂无打卡任务')
283 + }
284 +}
277 </script> 285 </script>
......