feat(打卡): 实现打卡功能完整流程
- 新增获取作业详情和打卡API接口 - 重构打卡弹窗组件,移除文本输入改为直接提交 - 更新文件上传组件,支持根据类型过滤文件 - 实现打卡日历页面,展示作业进度和团队头像 - 添加打卡类型选择页面,区分图文/视频/音频打卡 - 完善打卡成功后的状态处理和页面跳转
Showing
6 changed files
with
167 additions
and
82 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 09:31:00 | 4 | + * @LastEditTime: 2025-06-06 15:05:19 |
| 5 | * @FilePath: /mlaj/src/api/checkin.js | 5 | * @FilePath: /mlaj/src/api/checkin.js |
| 6 | * @Description: 签到模块相关接口 | 6 | * @Description: 签到模块相关接口 |
| 7 | */ | 7 | */ |
| 8 | import { fn, fetch } from './fn' | 8 | import { fn, fetch } from './fn' |
| 9 | 9 | ||
| 10 | const Api = { | 10 | const Api = { |
| 11 | - GET_TASK_LIST: '/srv/?a=task&t=my_list' | 11 | + GET_TASK_LIST: '/srv/?a=task&t=my_list', |
| 12 | + GET_TASK_DETAIL: '/srv/?a=task&t=detail', | ||
| 13 | + TASK_CHECKIN: '/srv/?a=checkin&t=checkin', | ||
| 12 | } | 14 | } |
| 13 | 15 | ||
| 14 | /** | 16 | /** |
| ... | @@ -18,3 +20,18 @@ const Api = { | ... | @@ -18,3 +20,18 @@ const Api = { |
| 18 | */ | 20 | */ |
| 19 | 21 | ||
| 20 | export const getTaskListAPI = (params) => fn(fetch.get(Api.GET_TASK_LIST, params)) | 22 | export const getTaskListAPI = (params) => fn(fetch.get(Api.GET_TASK_LIST, params)) |
| 23 | + | ||
| 24 | +/** | ||
| 25 | + * @description: 获取作业详情 | ||
| 26 | + * @param: i 作业id | ||
| 27 | + * @param: month 月份 | ||
| 28 | + * @returns data: { id 作业id, title 作业名称, frequency 交作业的频次, begin_date 开始时间, end_date 结束时间, task_type 任务类型 [checkin=签到 | file=上传附件], is_gray 作业是否应该置灰, my_checkin_dates 我在日历中打过卡的日期, target_number 打卡的目标数量, checkin_number 已经打卡的数量, checkin_avatars 最后打卡的10个人的头像 } | ||
| 29 | + */ | ||
| 30 | +export const getTaskDetailAPI = (params) => fn(fetch.get(Api.GET_TASK_DETAIL, params)) | ||
| 31 | + | ||
| 32 | +/** | ||
| 33 | + * @description: 签到打卡 | ||
| 34 | + * @param task_id 签到作业ID | ||
| 35 | + * @returns | ||
| 36 | + */ | ||
| 37 | +export const checkinTaskAPI = (params) => fn(fetch.post(Api.TASK_CHECKIN, params)) | ... | ... |
| ... | @@ -17,7 +17,7 @@ | ... | @@ -17,7 +17,7 @@ |
| 17 | <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" /> | 17 | <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" /> |
| 18 | </svg> | 18 | </svg> |
| 19 | <h4 class="text-green-700 font-medium mb-1">打卡成功!</h4> | 19 | <h4 class="text-green-700 font-medium mb-1">打卡成功!</h4> |
| 20 | - <p class="text-green-600 text-sm">+5 积分已添加到您的账户</p> | 20 | + <!-- <p class="text-green-600 text-sm">+5 积分已添加到您的账户</p> --> |
| 21 | </div> | 21 | </div> |
| 22 | <template v-else> | 22 | <template v-else> |
| 23 | <div class="flex space-x-2 py-2"> | 23 | <div class="flex space-x-2 py-2"> |
| ... | @@ -39,18 +39,18 @@ | ... | @@ -39,18 +39,18 @@ |
| 39 | : 'bg-gray-100 text-gray-500' | 39 | : 'bg-gray-100 text-gray-500' |
| 40 | ]"> | 40 | ]"> |
| 41 | <van-icon v-if="checkInType.task_type === 'checkin'" name="edit" size="1.5rem" /> | 41 | <van-icon v-if="checkInType.task_type === 'checkin'" name="edit" size="1.5rem" /> |
| 42 | - <van-icon v-if="checkInType.task_type === 'file'" name="tosend" size="1.5rem" /> | 42 | + <van-icon v-if="checkInType.task_type === 'upload'" name="tosend" size="1.5rem" /> |
| 43 | </div> | 43 | </div> |
| 44 | <span class="text-xs">{{ checkInType.name }}</span> | 44 | <span class="text-xs">{{ checkInType.name }}</span> |
| 45 | </button> | 45 | </button> |
| 46 | </div> | 46 | </div> |
| 47 | 47 | ||
| 48 | <div v-if="selectedCheckIn" class="mt-3"> | 48 | <div v-if="selectedCheckIn" class="mt-3"> |
| 49 | - <textarea | 49 | + <!-- <textarea |
| 50 | :placeholder="`请输入${selectedCheckIn.name}内容...`" | 50 | :placeholder="`请输入${selectedCheckIn.name}内容...`" |
| 51 | v-model="checkInContent" | 51 | v-model="checkInContent" |
| 52 | class="w-full p-3 border border-gray-200 rounded-lg text-sm resize-none h-24" | 52 | class="w-full p-3 border border-gray-200 rounded-lg text-sm resize-none h-24" |
| 53 | - /> | 53 | + /> --> |
| 54 | <button | 54 | <button |
| 55 | 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" | 55 | 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" |
| 56 | @click="handleCheckInSubmit" | 56 | @click="handleCheckInSubmit" |
| ... | @@ -72,7 +72,7 @@ | ... | @@ -72,7 +72,7 @@ |
| 72 | import { ref } from 'vue' | 72 | import { ref } from 'vue' |
| 73 | import { showToast } from 'vant' | 73 | import { showToast } from 'vant' |
| 74 | import { useRoute, useRouter } from 'vue-router' | 74 | import { useRoute, useRouter } from 'vue-router' |
| 75 | -import { getTaskListAPI } from "@/api/checkin"; | 75 | +import { getTaskListAPI, checkinTaskAPI } from "@/api/checkin"; |
| 76 | 76 | ||
| 77 | // 签到列表 | 77 | // 签到列表 |
| 78 | const checkInTypes = ref([]); | 78 | const checkInTypes = ref([]); |
| ... | @@ -96,9 +96,16 @@ const isCheckingIn = ref(false) | ... | @@ -96,9 +96,16 @@ const isCheckingIn = ref(false) |
| 96 | const checkInSuccess = ref(false) | 96 | const checkInSuccess = ref(false) |
| 97 | 97 | ||
| 98 | const handleCheckInSelect = (type) => { | 98 | const handleCheckInSelect = (type) => { |
| 99 | - if (type.task_type === 'file') { | 99 | + if (type.is_gray) { |
| 100 | + showToast('您已经完成了今天的打卡') | ||
| 101 | + return | ||
| 102 | + } | ||
| 103 | + if (type.task_type === 'upload') { | ||
| 100 | router.push({ | 104 | router.push({ |
| 101 | path: '/checkin/index', | 105 | path: '/checkin/index', |
| 106 | + query: { | ||
| 107 | + id: type.id | ||
| 108 | + } | ||
| 102 | }) | 109 | }) |
| 103 | } | 110 | } |
| 104 | selectedCheckIn.value = type; | 111 | selectedCheckIn.value = type; |
| ... | @@ -109,25 +116,26 @@ const handleCheckInSubmit = async () => { | ... | @@ -109,25 +116,26 @@ const handleCheckInSubmit = async () => { |
| 109 | showToast('请选择打卡项目') | 116 | showToast('请选择打卡项目') |
| 110 | return | 117 | return |
| 111 | } | 118 | } |
| 112 | - if (!checkInContent.value.trim()) { | 119 | + // if (!checkInContent.value.trim()) { |
| 113 | - showToast('请输入打卡内容') | 120 | + // showToast('请输入打卡内容') |
| 114 | - return | 121 | + // return |
| 115 | - } | 122 | + // } |
| 116 | 123 | ||
| 117 | isCheckingIn.value = true | 124 | isCheckingIn.value = true |
| 118 | try { | 125 | try { |
| 119 | - // 模拟API调用 | 126 | + // API调用 |
| 120 | - await new Promise(resolve => setTimeout(resolve, 1000)) | 127 | + const { code, data } = await checkinTaskAPI({ task_id: selectedCheckIn.value.id }); |
| 121 | - checkInSuccess.value = true | 128 | + if (code) { |
| 122 | - emit('check-in-success') | 129 | + checkInSuccess.value = true |
| 123 | - | 130 | + emit('check-in-success') |
| 124 | - // 重置表单 | 131 | + // 重置表单 |
| 125 | - setTimeout(() => { | 132 | + setTimeout(() => { |
| 126 | - checkInSuccess.value = false | 133 | + checkInSuccess.value = false |
| 127 | - selectedCheckIn.value = null | 134 | + selectedCheckIn.value = null |
| 128 | - checkInContent.value = '' | 135 | + checkInContent.value = '' |
| 129 | - emit('update:show', false) | 136 | + emit('update:show', false) |
| 130 | - }, 1500) | 137 | + }, 1500) |
| 138 | + } | ||
| 131 | } catch (error) { | 139 | } catch (error) { |
| 132 | showToast('打卡失败,请重试') | 140 | showToast('打卡失败,请重试') |
| 133 | } finally { | 141 | } finally { |
| ... | @@ -151,6 +159,7 @@ onMounted(async () => { | ... | @@ -151,6 +159,7 @@ onMounted(async () => { |
| 151 | id: item.id, | 159 | id: item.id, |
| 152 | name: item.title, | 160 | name: item.title, |
| 153 | task_type: item.task_type, | 161 | task_type: item.task_type, |
| 162 | + is_gray: item.is_gray | ||
| 154 | }) | 163 | }) |
| 155 | }) | 164 | }) |
| 156 | } | 165 | } | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2025-03-21 13:28:30 | 2 | * @Date: 2025-03-21 13:28:30 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-06-03 18:36:49 | 4 | + * @LastEditTime: 2025-06-06 14:35:56 |
| 5 | * @FilePath: /mlaj/src/router/checkin.js | 5 | * @FilePath: /mlaj/src/router/checkin.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| ... | @@ -65,7 +65,7 @@ export default [ | ... | @@ -65,7 +65,7 @@ export default [ |
| 65 | name: 'FileCheckIn', | 65 | name: 'FileCheckIn', |
| 66 | component: () => import('@/views/checkin/upload/file.vue'), | 66 | component: () => import('@/views/checkin/upload/file.vue'), |
| 67 | meta: { | 67 | meta: { |
| 68 | - title: '打卡视频音频', | 68 | + title: '打卡视频/音频', |
| 69 | requiresAuth: true | 69 | requiresAuth: true |
| 70 | } | 70 | } |
| 71 | }, | 71 | }, | ... | ... |
| 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 11:10:02 | 4 | + * @LastEditTime: 2025-06-06 15:16:49 |
| 5 | * @FilePath: /mlaj/src/views/HomePage.vue | 5 | * @FilePath: /mlaj/src/views/HomePage.vue |
| 6 | * @Description: 美乐爱觉教育首页组件 | 6 | * @Description: 美乐爱觉教育首页组件 |
| 7 | * | 7 | * |
| ... | @@ -85,7 +85,7 @@ | ... | @@ -85,7 +85,7 @@ |
| 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 | <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" /> |
| 86 | </svg> | 86 | </svg> |
| 87 | <h4 class="text-green-700 font-medium mb-1">打卡成功!</h4> | 87 | <h4 class="text-green-700 font-medium mb-1">打卡成功!</h4> |
| 88 | - <p class="text-green-600 text-sm">+5 积分已添加到您的账户</p> | 88 | + <!-- <p class="text-green-600 text-sm">+5 积分已添加到您的账户</p> --> |
| 89 | </div> | 89 | </div> |
| 90 | <template v-else> | 90 | <template v-else> |
| 91 | <div class="flex space-x-2 py-2"> | 91 | <div class="flex space-x-2 py-2"> |
| ... | @@ -107,18 +107,18 @@ | ... | @@ -107,18 +107,18 @@ |
| 107 | : 'bg-gray-100 text-gray-500' | 107 | : 'bg-gray-100 text-gray-500' |
| 108 | ]"> | 108 | ]"> |
| 109 | <van-icon v-if="checkInType.task_type === 'checkin'" name="edit" size="1.5rem" /> | 109 | <van-icon v-if="checkInType.task_type === 'checkin'" name="edit" size="1.5rem" /> |
| 110 | - <van-icon v-if="checkInType.task_type === 'file'" name="tosend" size="1.5rem" /> | 110 | + <van-icon v-if="checkInType.task_type === 'upload'" name="tosend" size="1.5rem" /> |
| 111 | </div> | 111 | </div> |
| 112 | <span class="text-xs">{{ checkInType.name }}</span> | 112 | <span class="text-xs">{{ checkInType.name }}</span> |
| 113 | </button> | 113 | </button> |
| 114 | </div> | 114 | </div> |
| 115 | 115 | ||
| 116 | <div v-if="selectedCheckIn" class="mt-3"> | 116 | <div v-if="selectedCheckIn" class="mt-3"> |
| 117 | - <textarea | 117 | + <!-- <textarea |
| 118 | :placeholder="`请输入${selectedCheckIn.name}内容...`" | 118 | :placeholder="`请输入${selectedCheckIn.name}内容...`" |
| 119 | v-model="checkInContent" | 119 | v-model="checkInContent" |
| 120 | class="w-full p-3 border border-gray-200 rounded-lg text-sm resize-none h-24" | 120 | class="w-full p-3 border border-gray-200 rounded-lg text-sm resize-none h-24" |
| 121 | - /> | 121 | + /> --> |
| 122 | <button | 122 | <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" | 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 | @click="handleCheckInSubmit" | 124 | @click="handleCheckInSubmit" |
| ... | @@ -523,7 +523,7 @@ import { showToast } from 'vant' | ... | @@ -523,7 +523,7 @@ import { showToast } from 'vant' |
| 523 | 523 | ||
| 524 | // 导入接口 | 524 | // 导入接口 |
| 525 | import { getCourseListAPI } from "@/api/course"; | 525 | import { getCourseListAPI } from "@/api/course"; |
| 526 | -import { getTaskListAPI } from "@/api/checkin"; | 526 | +import { getTaskListAPI, checkinTaskAPI } from "@/api/checkin"; |
| 527 | 527 | ||
| 528 | // 视频播放状态管理 | 528 | // 视频播放状态管理 |
| 529 | const activeVideoIndex = ref(null); // 当前播放的视频索引 | 529 | const activeVideoIndex = ref(null); // 当前播放的视频索引 |
| ... | @@ -602,8 +602,9 @@ onMounted(async () => { | ... | @@ -602,8 +602,9 @@ onMounted(async () => { |
| 602 | id: item.id, | 602 | id: item.id, |
| 603 | name: item.title, | 603 | name: item.title, |
| 604 | task_type: item.task_type, | 604 | task_type: item.task_type, |
| 605 | + is_gray: item.is_gray | ||
| 605 | }) | 606 | }) |
| 606 | - }) | 607 | + }); |
| 607 | } | 608 | } |
| 608 | } | 609 | } |
| 609 | }) | 610 | }) |
| ... | @@ -667,9 +668,16 @@ const scrollToSlide = (index) => { | ... | @@ -667,9 +668,16 @@ const scrollToSlide = (index) => { |
| 667 | 668 | ||
| 668 | // 打卡功能:处理打卡类型选择 | 669 | // 打卡功能:处理打卡类型选择 |
| 669 | const handleCheckInSelect = (checkInType) => { | 670 | const handleCheckInSelect = (checkInType) => { |
| 670 | - if (checkInType.task_type === 'file') { | 671 | + if (checkInType.is_gray) { |
| 672 | + showToast('您已经完成了今天的打卡') | ||
| 673 | + return | ||
| 674 | + } | ||
| 675 | + if (checkInType.task_type === 'upload') { | ||
| 671 | $router.push({ | 676 | $router.push({ |
| 672 | path: '/checkin/index', | 677 | path: '/checkin/index', |
| 678 | + query: { | ||
| 679 | + id: checkInType.id, | ||
| 680 | + }, | ||
| 673 | }) | 681 | }) |
| 674 | } | 682 | } |
| 675 | selectedCheckIn.value = checkInType // 更新选中的打卡类型 | 683 | selectedCheckIn.value = checkInType // 更新选中的打卡类型 |
| ... | @@ -677,31 +685,30 @@ const handleCheckInSelect = (checkInType) => { | ... | @@ -677,31 +685,30 @@ const handleCheckInSelect = (checkInType) => { |
| 677 | } | 685 | } |
| 678 | 686 | ||
| 679 | // 打卡功能:处理打卡提交 | 687 | // 打卡功能:处理打卡提交 |
| 680 | -const handleCheckInSubmit = () => { | 688 | +const handleCheckInSubmit = async () => { |
| 681 | // 表单验证 | 689 | // 表单验证 |
| 682 | if (!selectedCheckIn.value) { | 690 | if (!selectedCheckIn.value) { |
| 683 | showToast('请选择打卡项目') | 691 | showToast('请选择打卡项目') |
| 684 | return | 692 | return |
| 685 | } | 693 | } |
| 686 | - if (!checkInContent.value.trim()) { | 694 | + // if (!checkInContent.value.trim()) { |
| 687 | - showToast('请输入打卡内容') | 695 | + // showToast('请输入打卡内容') |
| 688 | - return | 696 | + // return |
| 689 | - } | 697 | + // } |
| 690 | 698 | ||
| 691 | isCheckingIn.value = true | 699 | isCheckingIn.value = true |
| 692 | 700 | ||
| 693 | - // 模拟API调用 | 701 | + // API调用 |
| 694 | - setTimeout(() => { | 702 | + isCheckingIn.value = false |
| 695 | - isCheckingIn.value = false | 703 | + checkInSuccess.value = true |
| 696 | - checkInSuccess.value = true | 704 | + checkInContent.value = '' |
| 697 | - selectedCheckIn.value = null | 705 | + const { code, data } = await checkinTaskAPI({ task_id: selectedCheckIn.value.id }); |
| 698 | - checkInContent.value = '' | 706 | + if (code) { |
| 699 | - | ||
| 700 | - // 3秒后重置成功提示 | ||
| 701 | setTimeout(() => { | 707 | setTimeout(() => { |
| 708 | + selectedCheckIn.value = null | ||
| 702 | checkInSuccess.value = false | 709 | checkInSuccess.value = false |
| 703 | - }, 3000) | 710 | + }, 1500); |
| 704 | - }, 1500) | 711 | + } |
| 705 | } | 712 | } |
| 706 | 713 | ||
| 707 | const contentRef = ref(null) // 内容区域的ref引用 | 714 | const contentRef = ref(null) // 内容区域的ref引用 | ... | ... |
| 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-03 16:07:54 | 4 | + * @LastEditTime: 2025-06-06 14:33:04 |
| 5 | * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue | 5 | * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | --> | 7 | --> |
| 8 | <template> | 8 | <template> |
| 9 | <AppLayout :hasTitle="false"> | 9 | <AppLayout :hasTitle="false"> |
| 10 | <van-config-provider :theme-vars="themeVars"> | 10 | <van-config-provider :theme-vars="themeVars"> |
| 11 | - <van-calendar title="每日打卡" :poppable="false" :show-confirm="false" :style="{ height: '24rem' }" | 11 | + <van-calendar :title="taskDetail.title" :poppable="false" :show-confirm="false" :style="{ height: '24rem' }" |
| 12 | switch-mode="year-month" color="#4caf50" :formatter="formatter" row-height="42" :show-mark="false" | 12 | switch-mode="year-month" color="#4caf50" :formatter="formatter" row-height="42" :show-mark="false" |
| 13 | @select="onSelectDay" | 13 | @select="onSelectDay" |
| 14 | @click-subtitle="onClickSubtitle"> | 14 | @click-subtitle="onClickSubtitle"> |
| ... | @@ -20,7 +20,7 @@ | ... | @@ -20,7 +20,7 @@ |
| 20 | <div class="grade-percentage-main"> | 20 | <div class="grade-percentage-main"> |
| 21 | <van-row justify="space-between" style="margin: 0.5rem 0; font-size: 0.9rem;"> | 21 | <van-row justify="space-between" style="margin: 0.5rem 0; font-size: 0.9rem;"> |
| 22 | <van-col span="12"> | 22 | <van-col span="12"> |
| 23 | - <span>年级目标</span> | 23 | + <span>作业目标</span> |
| 24 | </van-col> | 24 | </van-col> |
| 25 | <van-col span="12" style="text-align: right;"> | 25 | <van-col span="12" style="text-align: right;"> |
| 26 | <span style="font-weight: bold;">{{ progress1 }}%</span> | 26 | <span style="font-weight: bold;">{{ progress1 }}%</span> |
| ... | @@ -28,7 +28,7 @@ | ... | @@ -28,7 +28,7 @@ |
| 28 | </van-row> | 28 | </van-row> |
| 29 | <van-progress :percentage="progress1" color="#4caf50" :show-pivot="false" /> | 29 | <van-progress :percentage="progress1" color="#4caf50" :show-pivot="false" /> |
| 30 | </div> | 30 | </div> |
| 31 | - <div class="class-percentage-main"> | 31 | + <!-- <div class="class-percentage-main"> |
| 32 | <van-row justify="space-between" style="margin: 0.5rem 0; font-size: 0.9rem;"> | 32 | <van-row justify="space-between" style="margin: 0.5rem 0; font-size: 0.9rem;"> |
| 33 | <van-col span="12"> | 33 | <van-col span="12"> |
| 34 | <span>班级目标</span> | 34 | <span>班级目标</span> |
| ... | @@ -38,7 +38,7 @@ | ... | @@ -38,7 +38,7 @@ |
| 38 | </van-col> | 38 | </van-col> |
| 39 | </van-row> | 39 | </van-row> |
| 40 | <van-progress :percentage="progress2" color="#4caf50" :show-pivot="false" /> | 40 | <van-progress :percentage="progress2" color="#4caf50" :show-pivot="false" /> |
| 41 | - </div> | 41 | + </div> --> |
| 42 | <div style="padding: 0.75rem 1rem;"> | 42 | <div style="padding: 0.75rem 1rem;"> |
| 43 | <van-image round width="2.8rem" height="2.8rem" src="https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg" | 43 | <van-image round width="2.8rem" height="2.8rem" src="https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg" |
| 44 | v-for="(item, index) in teamAvatars" :key="index" | 44 | v-for="(item, index) in teamAvatars" :key="index" |
| ... | @@ -47,16 +47,20 @@ | ... | @@ -47,16 +47,20 @@ |
| 47 | </div> | 47 | </div> |
| 48 | </div> | 48 | </div> |
| 49 | 49 | ||
| 50 | - <div class="text-wrapper"> | 50 | + <div v-if="!taskDetail.is_gray" class="text-wrapper"> |
| 51 | - <div class="text-header">上传附件</div> | 51 | + <div class="text-header">打卡类型</div> |
| 52 | <div class="upload-wrapper"> | 52 | <div class="upload-wrapper"> |
| 53 | <div @click="goToCheckinImagePage" class="upload-boxer"> | 53 | <div @click="goToCheckinImagePage" class="upload-boxer"> |
| 54 | <div><van-icon name="photo" size="2.5rem" /></div> | 54 | <div><van-icon name="photo" size="2.5rem" /></div> |
| 55 | - <div style="font-size: 0.85rem;">图文上传</div> | 55 | + <div style="font-size: 0.85rem;">图文打卡</div> |
| 56 | </div> | 56 | </div> |
| 57 | - <div @click="goToCheckinFilePage" class="upload-boxer"> | 57 | + <div @click="goToCheckinFilePage('video')" class="upload-boxer"> |
| 58 | <div><van-icon name="video" size="2.5rem" /></div> | 58 | <div><van-icon name="video" size="2.5rem" /></div> |
| 59 | - <div style="font-size: 0.85rem;">视频/语音</div> | 59 | + <div style="font-size: 0.85rem;">视频打卡</div> |
| 60 | + </div> | ||
| 61 | + <div @click="goToCheckinFilePage('audio')" class="upload-boxer"> | ||
| 62 | + <div><van-icon name="music" size="2.5rem" /></div> | ||
| 63 | + <div style="font-size: 0.85rem;">音频打卡</div> | ||
| 60 | </div> | 64 | </div> |
| 61 | </div> | 65 | </div> |
| 62 | </div> | 66 | </div> |
| ... | @@ -66,15 +70,21 @@ | ... | @@ -66,15 +70,21 @@ |
| 66 | <div class="post-card" v-for="post in mockPosts" :key="post.id"> | 70 | <div class="post-card" v-for="post in mockPosts" :key="post.id"> |
| 67 | <div class="post-header"> | 71 | <div class="post-header"> |
| 68 | <van-row> | 72 | <van-row> |
| 69 | - <van-col span="3"> | 73 | + <van-col span="4"> |
| 70 | <van-image round width="2.5rem" height="2.5rem" :src="post.user.avatar" /> | 74 | <van-image round width="2.5rem" height="2.5rem" :src="post.user.avatar" /> |
| 71 | </van-col> | 75 | </van-col> |
| 72 | - <van-col span="20"> | 76 | + <van-col span="17"> |
| 73 | <div class="user-info"> | 77 | <div class="user-info"> |
| 74 | <div class="username">{{ post.user.name }}</div> | 78 | <div class="username">{{ post.user.name }}</div> |
| 75 | <div class="post-time">{{ post.user.time }}</div> | 79 | <div class="post-time">{{ post.user.time }}</div> |
| 76 | </div> | 80 | </div> |
| 77 | </van-col> | 81 | </van-col> |
| 82 | + <van-col span="3"> | ||
| 83 | + <div class="post-menu"> | ||
| 84 | + <van-icon name="edit" @click="editCheckin()" /> | ||
| 85 | + <van-icon name="delete-o" @click="delCheckin()" /> | ||
| 86 | + </div> | ||
| 87 | + </van-col> | ||
| 78 | </van-row> | 88 | </van-row> |
| 79 | </div> | 89 | </div> |
| 80 | <div class="post-content"> | 90 | <div class="post-content"> |
| ... | @@ -129,7 +139,7 @@ | ... | @@ -129,7 +139,7 @@ |
| 129 | </div> | 139 | </div> |
| 130 | </div> | 140 | </div> |
| 131 | <div class="post-footer" @click="handLike(post)"> | 141 | <div class="post-footer" @click="handLike(post)"> |
| 132 | - <van-icon name="like" class="like-icon" :color="post.is_liked ? 'red' : ''" /> | 142 | + <van-icon name="good-job" class="like-icon" :color="post.is_liked ? 'red' : ''" /> |
| 133 | <span class="like-count">{{ post.likes }}</span> | 143 | <span class="like-count">{{ post.likes }}</span> |
| 134 | </div> | 144 | </div> |
| 135 | </div> | 145 | </div> |
| ... | @@ -137,12 +147,15 @@ | ... | @@ -137,12 +147,15 @@ |
| 137 | 147 | ||
| 138 | <div style="height: 5rem;"></div> | 148 | <div style="height: 5rem;"></div> |
| 139 | </van-config-provider> | 149 | </van-config-provider> |
| 150 | + | ||
| 151 | + <van-dialog v-model:show="dialog_show" title="标题" show-cancel-button></van-dialog> | ||
| 140 | </AppLayout> | 152 | </AppLayout> |
| 141 | </template> | 153 | </template> |
| 142 | 154 | ||
| 143 | <script setup> | 155 | <script setup> |
| 144 | import { ref, onBeforeUnmount } from 'vue' | 156 | import { ref, onBeforeUnmount } from 'vue' |
| 145 | import { useRoute, useRouter } from 'vue-router' | 157 | import { useRoute, useRouter } from 'vue-router' |
| 158 | +import { showConfirmDialog, showSuccessToast, showFailToast } from 'vant'; | ||
| 146 | import AppLayout from "@/components/layout/AppLayout.vue"; | 159 | import AppLayout from "@/components/layout/AppLayout.vue"; |
| 147 | import FrostedGlass from "@/components/ui/FrostedGlass.vue"; | 160 | import FrostedGlass from "@/components/ui/FrostedGlass.vue"; |
| 148 | import VideoPlayer from "@/components/ui/VideoPlayer.vue"; | 161 | import VideoPlayer from "@/components/ui/VideoPlayer.vue"; |
| ... | @@ -150,6 +163,8 @@ import AudioPlayer from "@/components/ui/AudioPlayer.vue"; | ... | @@ -150,6 +163,8 @@ import AudioPlayer from "@/components/ui/AudioPlayer.vue"; |
| 150 | import { useTitle } from '@vueuse/core'; | 163 | import { useTitle } from '@vueuse/core'; |
| 151 | import dayjs from 'dayjs'; | 164 | import dayjs from 'dayjs'; |
| 152 | 165 | ||
| 166 | +import { getTaskDetailAPI } from "@/api/checkin"; | ||
| 167 | + | ||
| 153 | const route = useRoute() | 168 | const route = useRoute() |
| 154 | const router = useRouter() | 169 | const router = useRouter() |
| 155 | useTitle(route.meta.title); | 170 | useTitle(route.meta.title); |
| ... | @@ -445,15 +460,10 @@ const themeVars = { | ... | @@ -445,15 +460,10 @@ const themeVars = { |
| 445 | calendarSelectedDayBackground: '#4caf50' | 460 | calendarSelectedDayBackground: '#4caf50' |
| 446 | } | 461 | } |
| 447 | 462 | ||
| 448 | -const progress1 = ref(50); | 463 | +const progress1 = ref(0); |
| 449 | const progress2 = ref(76); | 464 | const progress2 = ref(76); |
| 450 | 465 | ||
| 451 | -const teamAvatars = ref([ | 466 | +const teamAvatars = ref([]) |
| 452 | - 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg', | ||
| 453 | - 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg', | ||
| 454 | - 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg', | ||
| 455 | - 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg' | ||
| 456 | -]) | ||
| 457 | 467 | ||
| 458 | // 图片预览相关 | 468 | // 图片预览相关 |
| 459 | const showImagePreview = ref(false); | 469 | const showImagePreview = ref(false); |
| ... | @@ -503,14 +513,57 @@ const onClickSubtitle = (evt) => { | ... | @@ -503,14 +513,57 @@ const onClickSubtitle = (evt) => { |
| 503 | const goToCheckinImagePage = () => { | 513 | const goToCheckinImagePage = () => { |
| 504 | router.push('/checkin/image'); | 514 | router.push('/checkin/image'); |
| 505 | } | 515 | } |
| 506 | -const goToCheckinFilePage = () => { | 516 | +const goToCheckinFilePage = (type) => { |
| 507 | - router.push('/checkin/file'); | 517 | + router.push('/checkin/file?type=' + type); |
| 508 | } | 518 | } |
| 509 | 519 | ||
| 510 | const handLike = (post) => { | 520 | const handLike = (post) => { |
| 511 | post.is_liked = !post.is_liked; | 521 | post.is_liked = !post.is_liked; |
| 512 | // TODO: 调用接口 | 522 | // TODO: 调用接口 |
| 513 | } | 523 | } |
| 524 | + | ||
| 525 | +const editCheckin = () => { | ||
| 526 | + let type = 'image'; | ||
| 527 | + if (type === 'image') { | ||
| 528 | + router.push({ | ||
| 529 | + path: '/checkin/image', | ||
| 530 | + }) | ||
| 531 | + } else { | ||
| 532 | + router.push({ | ||
| 533 | + path: '/checkin/file', | ||
| 534 | + }) | ||
| 535 | + } | ||
| 536 | +} | ||
| 537 | + | ||
| 538 | +const delCheckin = () => { | ||
| 539 | + showConfirmDialog({ | ||
| 540 | + title: '温馨提示', | ||
| 541 | + message: '您是否确定要删除该动态?', | ||
| 542 | + confirmButtonColor: '#4caf50', | ||
| 543 | + }) | ||
| 544 | + .then(() => { | ||
| 545 | + // on confirm | ||
| 546 | + // TODO: 调用接口 | ||
| 547 | + // 删除成功后,刷新页面 | ||
| 548 | + showSuccessToast('成功文案'); | ||
| 549 | + // showFailToast('失败文案'); | ||
| 550 | + }) | ||
| 551 | + .catch(() => { | ||
| 552 | + // on cancel | ||
| 553 | + }); | ||
| 554 | +} | ||
| 555 | + | ||
| 556 | +const taskDetail = ref({}); | ||
| 557 | + | ||
| 558 | +onMounted(async () => { | ||
| 559 | + const { code, data } = await getTaskDetailAPI({ id: route.query.id, month: dayjs().format('YYYY-MM') }); | ||
| 560 | + if (code) { | ||
| 561 | + console.warn(data); | ||
| 562 | + taskDetail.value = data; | ||
| 563 | + progress1.value = (data.checkin_number/data.target_number)*100 ; | ||
| 564 | + teamAvatars.value = data.checkin_avatars; | ||
| 565 | + } | ||
| 566 | +}) | ||
| 514 | </script> | 567 | </script> |
| 515 | 568 | ||
| 516 | <style lang="less"> | 569 | <style lang="less"> |
| ... | @@ -574,6 +627,13 @@ const handLike = (post) => { | ... | @@ -574,6 +627,13 @@ const handLike = (post) => { |
| 574 | } | 627 | } |
| 575 | } | 628 | } |
| 576 | 629 | ||
| 630 | + .post-menu { | ||
| 631 | + display: flex; | ||
| 632 | + justify-content: space-between; | ||
| 633 | + align-items: center; | ||
| 634 | + margin-bottom: 1rem; | ||
| 635 | + } | ||
| 636 | + | ||
| 577 | .post-content { | 637 | .post-content { |
| 578 | .post-text { | 638 | .post-text { |
| 579 | margin-bottom: 1rem; | 639 | margin-bottom: 1rem; | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2025-06-03 09:41:41 | 2 | * @Date: 2025-06-03 09:41:41 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-06-03 17:11:53 | 4 | + * @LastEditTime: 2025-06-06 14:38:20 |
| 5 | * @FilePath: /mlaj/src/views/checkin/upload/file.vue | 5 | * @FilePath: /mlaj/src/views/checkin/upload/file.vue |
| 6 | * @Description: 音视频文件上传组件 | 6 | * @Description: 音视频文件上传组件 |
| 7 | --> | 7 | --> |
| ... | @@ -17,7 +17,7 @@ | ... | @@ -17,7 +17,7 @@ |
| 17 | :after-read="afterRead" | 17 | :after-read="afterRead" |
| 18 | @delete="onDelete" | 18 | @delete="onDelete" |
| 19 | multiple | 19 | multiple |
| 20 | - accept="audio/*,video/*" | 20 | + :accept="route.query.type === 'video' ? 'video/*' : 'audio/*'" |
| 21 | result-type="file" | 21 | result-type="file" |
| 22 | upload-icon="plus" | 22 | upload-icon="plus" |
| 23 | > | 23 | > |
| ... | @@ -29,7 +29,7 @@ | ... | @@ -29,7 +29,7 @@ |
| 29 | </template> | 29 | </template> |
| 30 | </van-uploader> | 30 | </van-uploader> |
| 31 | <div class="mt-2 text-xs text-gray-500">最多上传{{ max_count }}个文件,每个不超过20M</div> | 31 | <div class="mt-2 text-xs text-gray-500">最多上传{{ max_count }}个文件,每个不超过20M</div> |
| 32 | - <div class="mt-2 text-xs text-gray-500">上传类型: {{ type_text }}</div> | 32 | + <div class="mt-2 text-xs text-gray-500">上传类型: {{ route.query.type === 'video' ? "视频文件" : '音频文件' }}</div> |
| 33 | </div> | 33 | </div> |
| 34 | 34 | ||
| 35 | <!-- 文字留言区域 --> | 35 | <!-- 文字留言区域 --> |
| ... | @@ -96,14 +96,6 @@ const canSubmit = computed(() => { | ... | @@ -96,14 +96,6 @@ const canSubmit = computed(() => { |
| 96 | return fileList.value.length > 0 && message.value.trim() !== '' | 96 | return fileList.value.length > 0 && message.value.trim() !== '' |
| 97 | }) | 97 | }) |
| 98 | 98 | ||
| 99 | -// 固定类型限制 | ||
| 100 | -const fileTypes = "audio/video"; | ||
| 101 | - | ||
| 102 | -// 文件类型中文页面显示 | ||
| 103 | -const type_text = computed(() => { | ||
| 104 | - return "音频/视频文件"; | ||
| 105 | -}); | ||
| 106 | - | ||
| 107 | // 文件校验 | 99 | // 文件校验 |
| 108 | const beforeRead = (file) => { | 100 | const beforeRead = (file) => { |
| 109 | let flag = true | 101 | let flag = true | ... | ... |
-
Please register or login to post a comment