hookehuyr

feat(打卡): 添加计数型打卡功能并优化子任务显示

- 在CheckInList组件中添加计数型打卡图标和跳转逻辑
- 在多个页面中将固定文本替换为动态子任务标题显示
- 完善打卡详情页的子任务选择和编辑功能
- 更新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-12-12 18:06:49 4 + * @LastEditTime: 2025-12-12 21:15:25
5 * @FilePath: /mlaj/src/api/checkin.js 5 * @FilePath: /mlaj/src/api/checkin.js
6 * @Description: 签到模块相关接口 6 * @Description: 签到模块相关接口
7 */ 7 */
...@@ -84,9 +84,11 @@ export const addUploadTaskAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_ADD, ...@@ -84,9 +84,11 @@ export const addUploadTaskAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_ADD,
84 export const getUploadTaskListAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_LIST, params)) 84 export const getUploadTaskListAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_LIST, params))
85 85
86 /** 86 /**
87 - * @description: 获取打卡动态详情 87 + * @description: 上传打卡详情
88 * @param i 打卡动态ID 88 * @param i 打卡动态ID
89 - * @returns 89 + * @returns data: {id 打卡动态ID, subtask_id 小作业ID, status 审批状态 3=待审批,5=审批通过,7=审批不通过, created_by 打卡人ID, username 打卡人昵称
90 + * avatar 打卡人头像, created_time 打卡时间, created_time_desc 打卡时间描述, note 打卡内容, files[{meta_id,name,value,extension}] 附件列表,
91 + * file_type 上传附件的类型 image=上传图片,video=视频,audio=音频, like_count 点赞数, is_my 是不是我的打卡, is_like 我是否已经点赞, is_makeup 是否补卡}
90 */ 92 */
91 export const getUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_INFO, params)) 93 export const getUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_INFO, params))
92 94
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
13 > 13 >
14 <van-icon v-if="item.task_type === 'checkin'" name="edit" size="1.5rem" :color="item.is_gray ? 'gray' : ''" /> 14 <van-icon v-if="item.task_type === 'checkin'" name="edit" size="1.5rem" :color="item.is_gray ? 'gray' : ''" />
15 <van-icon v-if="item.task_type === 'upload'" name="tosend" size="1.5rem" :color="item.is_gray ? 'gray' : ''" /> 15 <van-icon v-if="item.task_type === 'upload'" name="tosend" size="1.5rem" :color="item.is_gray ? 'gray' : ''" />
16 + <van-icon v-if="item.task_type === 'count'" name="point-gift-o" size="1.5rem" :color="item.is_gray ? 'gray' : ''" />
16 </div> 17 </div>
17 <span :class="['text-xs', item.is_gray ? 'text-gray-500' : '']">{{ item.name }}</span> 18 <span :class="['text-xs', item.is_gray ? 'text-gray-500' : '']">{{ item.name }}</span>
18 </button> 19 </button>
...@@ -185,6 +186,14 @@ const handle_click = (item) => { ...@@ -185,6 +186,14 @@ const handle_click = (item) => {
185 }) 186 })
186 return 187 return
187 } 188 }
189 + // 打卡项:计数型跳转
190 + if (item.task_type === 'count') {
191 + router.push({
192 + path: '/checkin/index',
193 + query: { id: item.id },
194 + })
195 + return
196 + }
188 // 打卡项:选中后展示提交按钮 197 // 打卡项:选中后展示提交按钮
189 selected_item.value = item 198 selected_item.value = item
190 } 199 }
......
...@@ -21,7 +21,9 @@ export function useCheckin() { ...@@ -21,7 +21,9 @@ export function useCheckin() {
21 const message = ref('') 21 const message = ref('')
22 const fileList = ref([]) 22 const fileList = ref([])
23 const activeType = ref('') // 当前选中的打卡类型 23 const activeType = ref('') // 当前选中的打卡类型
24 - const taskId = ref('') // 当前选中的任务ID 24 + const subTaskId = ref('') // 当前选中的任务ID
25 + const selectedTaskText = ref('') // 选中的任务文本
26 + const selectedTaskValue = ref([]) // 选中的任务值(Picker使用)
25 const maxCount = ref(5) 27 const maxCount = ref(5)
26 28
27 // 打卡类型 29 // 打卡类型
...@@ -306,6 +308,7 @@ export function useCheckin() { ...@@ -306,6 +308,7 @@ export function useCheckin() {
306 // 编辑打卡 308 // 编辑打卡
307 result = await editUploadTaskInfoAPI({ 309 result = await editUploadTaskInfoAPI({
308 i: route.query.post_id, 310 i: route.query.post_id,
311 + subtask_id: route.query.subtask_id,
309 note: submitData.note, 312 note: submitData.note,
310 meta_id: submitData.meta_id, 313 meta_id: submitData.meta_id,
311 file_type: submitData.file_type, 314 file_type: submitData.file_type,
...@@ -363,16 +366,26 @@ export function useCheckin() { ...@@ -363,16 +366,26 @@ export function useCheckin() {
363 366
364 /** 367 /**
365 * 初始化编辑数据 368 * 初始化编辑数据
369 + * @param {Array} taskOptions - 任务选项列表
366 */ 370 */
367 - const initEditData = async () => { 371 + const initEditData = async (taskOptions = []) => {
368 if (route.query.status === 'edit') { 372 if (route.query.status === 'edit') {
369 try { 373 try {
370 - const { code, data } = await getUploadTaskInfoAPI({ i: route.query.post_id }) 374 + const { code, data } = await getUploadTaskInfoAPI({ i: route.query.post_id, subtask_id: route.query.subtask_id })
371 if (code) { 375 if (code) {
372 message.value = data.note || '' 376 message.value = data.note || ''
373 activeType.value = data.file_type || 'text' 377 activeType.value = data.file_type || 'text'
374 - // TODO: 假数据等待真实数据 378 + // 小作业ID
375 - taskId.value = 'task1' 379 + subTaskId.value = route.query.subtask_id
380 +
381 + // 更新选中的任务显示
382 + if (subTaskId.value) {
383 + selectedTaskValue.value = [subTaskId.value]
384 + if (taskOptions && taskOptions.length > 0) {
385 + const option = taskOptions.find(o => o.value === subTaskId.value)
386 + selectedTaskText.value = option ? option.text : ''
387 + }
388 + }
376 389
377 // 如果有文件数据,初始化文件列表 - 使用data.files而不是data.meta 390 // 如果有文件数据,初始化文件列表 - 使用data.files而不是data.meta
378 if (data.files && data.files.length > 0) { 391 if (data.files && data.files.length > 0) {
...@@ -416,7 +429,9 @@ export function useCheckin() { ...@@ -416,7 +429,9 @@ export function useCheckin() {
416 message, 429 message,
417 fileList, 430 fileList,
418 activeType, 431 activeType,
419 - taskId, 432 + subTaskId,
433 + selectedTaskText,
434 + selectedTaskValue,
420 maxCount, 435 maxCount,
421 canSubmit, 436 canSubmit,
422 437
......
...@@ -23,12 +23,12 @@ ...@@ -23,12 +23,12 @@
23 <van-field v-model="selectedTaskText" is-link readonly label="选择作业" placeholder="请选择本次打卡的作业" 23 <van-field v-model="selectedTaskText" is-link readonly label="选择作业" placeholder="请选择本次打卡的作业"
24 @click="showTaskPicker = true" class="rounded-lg border border-gray-100" /> 24 @click="showTaskPicker = true" class="rounded-lg border border-gray-100" />
25 <van-popup v-model:show="showTaskPicker" round position="bottom"> 25 <van-popup v-model:show="showTaskPicker" round position="bottom">
26 - <van-picker :columns="taskOptions" @cancel="showTaskPicker = false" 26 + <van-picker v-model="selectedTaskValue" :columns="taskOptions" @cancel="showTaskPicker = false"
27 - @confirm="onConfirmTask" /> 27 + @confirm="onConfirmTask" />
28 </van-popup> 28 </van-popup>
29 </div> 29 </div>
30 <!-- 计数对象 --> 30 <!-- 计数对象 -->
31 - <div v-if="taskType === 'count' && selectedTaskValue" class="mb-4"> 31 + <div v-if="taskType === 'count' && selectedTaskValue && selectedTaskValue.length > 0" class="mb-4">
32 <div class="flex justify-between items-center mb-2 mx-2"> 32 <div class="flex justify-between items-center mb-2 mx-2">
33 <div class="text-sm font-bold text-gray-700">{{ dynamicFieldText }}对象</div> 33 <div class="text-sm font-bold text-gray-700">{{ dynamicFieldText }}对象</div>
34 <van-button size="small" type="primary" plain icon="plus" 34 <van-button size="small" type="primary" plain icon="plus"
...@@ -205,7 +205,9 @@ const { ...@@ -205,7 +205,9 @@ const {
205 message, 205 message,
206 fileList, 206 fileList,
207 activeType, 207 activeType,
208 - taskId, 208 + subTaskId,
209 + selectedTaskText,
210 + selectedTaskValue,
209 maxCount, 211 maxCount,
210 canSubmit, 212 canSubmit,
211 beforeRead, 213 beforeRead,
...@@ -228,8 +230,6 @@ const taskType = computed(() => route.query.task_type) ...@@ -228,8 +230,6 @@ const taskType = computed(() => route.query.task_type)
228 230
229 // 作业选择相关 231 // 作业选择相关
230 const showTaskPicker = ref(false) 232 const showTaskPicker = ref(false)
231 -const selectedTaskText = ref('')
232 -const selectedTaskValue = ref('')
233 const taskOptions = ref([]) 233 const taskOptions = ref([])
234 234
235 // TODO: 模拟任务选项数据 235 // TODO: 模拟任务选项数据
...@@ -247,10 +247,10 @@ const mockData = { ...@@ -247,10 +247,10 @@ const mockData = {
247 ] 247 ]
248 } 248 }
249 249
250 -const fetchTargetList = async (taskId) => { 250 +const fetchTargetList = async (subTaskId) => {
251 // 模拟接口调用延迟 251 // 模拟接口调用延迟
252 setTimeout(() => { 252 setTimeout(() => {
253 - targetList.value = mockData[taskId] || [] 253 + targetList.value = mockData[subTaskId] || []
254 showLoadingToast({ 254 showLoadingToast({
255 message: '加载成功', 255 message: '加载成功',
256 type: 'success', 256 type: 'success',
...@@ -262,7 +262,7 @@ const fetchTargetList = async (taskId) => { ...@@ -262,7 +262,7 @@ const fetchTargetList = async (taskId) => {
262 const onConfirmTask = ({ selectedOptions }) => { 262 const onConfirmTask = ({ selectedOptions }) => {
263 const option = selectedOptions[0] 263 const option = selectedOptions[0]
264 selectedTaskText.value = option.text 264 selectedTaskText.value = option.text
265 - selectedTaskValue.value = option.value 265 + selectedTaskValue.value = [option.value]
266 showTaskPicker.value = false 266 showTaskPicker.value = false
267 267
268 // 如果是计数打卡,根据选中的作业ID查询计数对象 268 // 如果是计数打卡,根据选中的作业ID查询计数对象
...@@ -273,8 +273,8 @@ const onConfirmTask = ({ selectedOptions }) => { ...@@ -273,8 +273,8 @@ const onConfirmTask = ({ selectedOptions }) => {
273 273
274 // 监听作业选择变化 274 // 监听作业选择变化
275 watch(selectedTaskValue, (newVal) => { 275 watch(selectedTaskValue, (newVal) => {
276 - if (taskType.value === 'count' && newVal) { 276 + if (taskType.value === 'count' && newVal && newVal.length > 0) {
277 - fetchTargetList(newVal) 277 + fetchTargetList(newVal[0])
278 } 278 }
279 }) 279 })
280 280
...@@ -325,7 +325,7 @@ const confirmAddTarget = (formData) => { ...@@ -325,7 +325,7 @@ const confirmAddTarget = (formData) => {
325 */ 325 */
326 const isSubmitDisabled = computed(() => { 326 const isSubmitDisabled = computed(() => {
327 // 1. 校验作业选择 327 // 1. 校验作业选择
328 - if (!selectedTaskValue.value) return true 328 + if (!selectedTaskValue.value || selectedTaskValue.value.length === 0) return true
329 329
330 // 2. 计数打卡特定校验 330 // 2. 计数打卡特定校验
331 if (taskType.value === 'count') { 331 if (taskType.value === 'count') {
...@@ -348,13 +348,13 @@ const isSubmitDisabled = computed(() => { ...@@ -348,13 +348,13 @@ const isSubmitDisabled = computed(() => {
348 348
349 const handleSubmit = async () => { 349 const handleSubmit = async () => {
350 // 1. 校验作业选择 350 // 1. 校验作业选择
351 - if (!selectedTaskValue.value) { 351 + if (!selectedTaskValue.value || selectedTaskValue.value.length === 0) {
352 showToast('请选择作业') 352 showToast('请选择作业')
353 return 353 return
354 } 354 }
355 355
356 const extraData = { 356 const extraData = {
357 - subtask_id: selectedTaskValue.value // 小作业ID 357 + subtask_id: selectedTaskValue.value[0] // 小作业ID
358 } 358 }
359 359
360 // 2. 计数打卡特定校验 360 // 2. 计数打卡特定校验
...@@ -475,7 +475,7 @@ const getTaskDetail = async (month) => { ...@@ -475,7 +475,7 @@ const getTaskDetail = async (month) => {
475 475
476 // 处理编辑模式下的类型合并 476 // 处理编辑模式下的类型合并
477 if (isEditMode.value && Array.isArray(data.attachment_type)) { 477 if (isEditMode.value && Array.isArray(data.attachment_type)) {
478 - const info = await getUploadTaskInfoAPI({ i: route.query.post_id }); 478 + const info = await getUploadTaskInfoAPI({ i: route.query.post_id, subtask_id: route.query.subtask_id });
479 if (info.code) { 479 if (info.code) {
480 data.attachment_type = [...new Set([...data.attachment_type, info.data.file_type])]; 480 data.attachment_type = [...new Set([...data.attachment_type, info.data.file_type])];
481 } 481 }
...@@ -796,6 +796,9 @@ onMounted(async () => { ...@@ -796,6 +796,9 @@ onMounted(async () => {
796 getTaskDetail(dayjs().format('YYYY-MM')); 796 getTaskDetail(dayjs().format('YYYY-MM'));
797 } 797 }
798 798
799 + // 初始化选中的子任务ID
800 + selectedTaskValue.value = [route.query.subtask_id]
801 +
799 // TODO: 获取小作业列表 802 // TODO: 获取小作业列表
800 const subtask_list = await getSubtaskListAPI({ task_id: route.query.id }) 803 const subtask_list = await getSubtaskListAPI({ task_id: route.query.id })
801 if (subtask_list.code) { 804 if (subtask_list.code) {
...@@ -811,15 +814,18 @@ onMounted(async () => { ...@@ -811,15 +814,18 @@ onMounted(async () => {
811 { text: '作业二:进阶挑战', value: 'task2' }, 814 { text: '作业二:进阶挑战', value: 'task2' },
812 { text: '作业三:综合应用', value: 'task3' }] 815 { text: '作业三:综合应用', value: 'task3' }]
813 816
817 + // 如果有默认选中值,初始化显示文本
818 + if (selectedTaskValue.value.length > 0) {
819 + const option = taskOptions.value.find(o => o.value === selectedTaskValue.value[0])
820 + if (option) {
821 + selectedTaskText.value = option.text
822 + }
823 + }
824 +
814 // 初始化编辑数据 825 // 初始化编辑数据
815 - await initEditData() 826 + await initEditData(taskOptions.value)
816 827
817 - // TODO: 临时处理,待后端返回真实数据 828 + console.warn(selectedTaskValue.value);
818 - if (taskId.value) {
819 - selectedTaskText.value = taskOptions.value.find(option => option.value === taskId.value)?.text || ''
820 - selectedTaskValue.value = taskId.value
821 - console.warn('当前选中的任务ID:', taskId.value);
822 - }
823 }) 829 })
824 </script> 830 </script>
825 831
......
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-12-12 17:27:43 4 + * @LastEditTime: 2025-12-12 20:49:55
5 * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue 5 * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
84 @audio-play="handleAudioPlay" 84 @audio-play="handleAudioPlay"
85 > 85 >
86 <template #content-top> 86 <template #content-top>
87 - <div class="text-gray-500 font-bold text-sm mb-4">阅读与写作</div> 87 + <div class="text-gray-500 font-bold text-sm mb-4">{{ post.subtask_title }}</div>
88 </template> 88 </template>
89 </CheckinCard> 89 </CheckinCard>
90 </van-list> 90 </van-list>
...@@ -392,10 +392,10 @@ const goToCheckinDetailPage = () => { ...@@ -392,10 +392,10 @@ const goToCheckinDetailPage = () => {
392 path: '/checkin/detail', 392 path: '/checkin/detail',
393 query: { 393 query: {
394 id: route.query.id, 394 id: route.query.id,
395 + subtask_id: selectedSubtaskId.value,
395 date: current_date, 396 date: current_date,
396 is_patch: isPatchCheckin.value ? '1' : '0', 397 is_patch: isPatchCheckin.value ? '1' : '0',
397 task_type: taskDetail.value.task_type, 398 task_type: taskDetail.value.task_type,
398 - // type: 'count',
399 } 399 }
400 }) 400 })
401 } 401 }
...@@ -463,6 +463,7 @@ const editCheckin = (post) => { ...@@ -463,6 +463,7 @@ const editCheckin = (post) => {
463 path: '/checkin/detail', 463 path: '/checkin/detail',
464 query: { 464 query: {
465 post_id: post.id, 465 post_id: post.id,
466 + subtask_id: post.subtask_id,
466 type: post.file_type, 467 type: post.file_type,
467 task_type: taskDetail.value.task_type, 468 task_type: taskDetail.value.task_type,
468 status: 'edit', 469 status: 'edit',
......
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
100 @audio-play="handleAudioPlay" 100 @audio-play="handleAudioPlay"
101 > 101 >
102 <template #content-top> 102 <template #content-top>
103 - <div class="text-gray-500 font-bold text-sm mb-4">阅读与写作</div> 103 + <div class="text-gray-500 font-bold text-sm mb-4">{{ post.subtask_title }}</div>
104 </template> 104 </template>
105 <template #footer-right> 105 <template #footer-right>
106 <div class="flex items-center cursor-pointer" @click="openAuditDialog(post)"> 106 <div class="flex items-center cursor-pointer" @click="openAuditDialog(post)">
......
...@@ -198,7 +198,7 @@ ...@@ -198,7 +198,7 @@
198 @audio-play="handleAudioPlay" 198 @audio-play="handleAudioPlay"
199 > 199 >
200 <template #content-top> 200 <template #content-top>
201 - <div class="text-gray-500 font-bold text-sm mb-4">阅读与写作</div> 201 + <div class="text-gray-500 font-bold text-sm mb-4">{{ post.subtask_title }}</div>
202 </template> 202 </template>
203 <template #footer-right> 203 <template #footer-right>
204 <div class="flex items-center cursor-pointer" @click="openCommentPopup(post)"> 204 <div class="flex items-center cursor-pointer" @click="openCommentPopup(post)">
......
1 <!-- 1 <!--
2 * @Date: 2025-11-19 22:05:00 2 * @Date: 2025-11-19 22:05:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-12 18:09:33 4 + * @LastEditTime: 2025-12-12 21:26:17
5 * @FilePath: /mlaj/src/views/teacher/studentRecordPage.vue 5 * @FilePath: /mlaj/src/views/teacher/studentRecordPage.vue
6 * @Description: 学生作业记录页面(仅作业记录与点评功能),固定 user_id 与 group_id 6 * @Description: 学生作业记录页面(仅作业记录与点评功能),固定 user_id 与 group_id
7 --> 7 -->
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
20 @audio-play="handleAudioPlay" 20 @audio-play="handleAudioPlay"
21 > 21 >
22 <template #content-top> 22 <template #content-top>
23 - <div class="text-gray-500 font-bold text-sm mb-4">阅读与写作</div> 23 + <div class="text-gray-500 font-bold text-sm mb-4">{{ post.subtask_title }}</div>
24 </template> 24 </template>
25 <template #footer-right> 25 <template #footer-right>
26 <div class="flex items-center cursor-pointer" @click="openCommentPopup(post)"> 26 <div class="flex items-center cursor-pointer" @click="openCommentPopup(post)">
......