hookehuyr

feat(打卡): 实现打卡功能完整流程

- 新增获取作业详情和打卡API接口
- 重构打卡弹窗组件,移除文本输入改为直接提交
- 更新文件上传组件,支持根据类型过滤文件
- 实现打卡日历页面,展示作业进度和团队头像
- 添加打卡类型选择页面,区分图文/视频/音频打卡
- 完善打卡成功后的状态处理和页面跳转
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">上传类型:&nbsp;{{ type_text }}</div> 32 + <div class="mt-2 text-xs text-gray-500">上传类型:&nbsp;{{ 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
......