feat(打卡): 完善打卡功能逻辑和接口
- 在ProfilePage.vue中重构打卡按钮点击逻辑,添加无任务时的提示 - 在checkin.js中添加删除、点赞和取消点赞的API接口 - 在IndexCheckInPage.vue中实现点赞和删除打卡动态的功能 - 在HomePage.vue中优化打卡任务显示逻辑,添加无任务时的提示
Showing
4 changed files
with
123 additions
and
67 deletions
| 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> | ... | ... |
-
Please register or login to post a comment