hookehuyr

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

- 在CheckInList组件中添加计数型打卡图标和跳转逻辑
- 在多个页面中将固定文本替换为动态子任务标题显示
- 完善打卡详情页的子任务选择和编辑功能
- 更新API文档说明并优化相关接口参数传递
/*
* @Date: 2025-06-06 09:26:16
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-12-12 18:06:49
* @LastEditTime: 2025-12-12 21:15:25
* @FilePath: /mlaj/src/api/checkin.js
* @Description: 签到模块相关接口
*/
......@@ -84,9 +84,11 @@ export const addUploadTaskAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_ADD,
export const getUploadTaskListAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_LIST, params))
/**
* @description: 获取打卡动态详情
* @description: 上传打卡详情
* @param i 打卡动态ID
* @returns
* @returns data: {id 打卡动态ID, subtask_id 小作业ID, status 审批状态 3=待审批,5=审批通过,7=审批不通过, created_by 打卡人ID, username 打卡人昵称
* avatar 打卡人头像, created_time 打卡时间, created_time_desc 打卡时间描述, note 打卡内容, files[{meta_id,name,value,extension}] 附件列表,
* file_type 上传附件的类型 image=上传图片,video=视频,audio=音频, like_count 点赞数, is_my 是不是我的打卡, is_like 我是否已经点赞, is_makeup 是否补卡}
*/
export const getUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_INFO, params))
......
......@@ -13,6 +13,7 @@
>
<van-icon v-if="item.task_type === 'checkin'" name="edit" size="1.5rem" :color="item.is_gray ? 'gray' : ''" />
<van-icon v-if="item.task_type === 'upload'" name="tosend" size="1.5rem" :color="item.is_gray ? 'gray' : ''" />
<van-icon v-if="item.task_type === 'count'" name="point-gift-o" size="1.5rem" :color="item.is_gray ? 'gray' : ''" />
</div>
<span :class="['text-xs', item.is_gray ? 'text-gray-500' : '']">{{ item.name }}</span>
</button>
......@@ -185,6 +186,14 @@ const handle_click = (item) => {
})
return
}
// 打卡项:计数型跳转
if (item.task_type === 'count') {
router.push({
path: '/checkin/index',
query: { id: item.id },
})
return
}
// 打卡项:选中后展示提交按钮
selected_item.value = item
}
......
......@@ -21,7 +21,9 @@ export function useCheckin() {
const message = ref('')
const fileList = ref([])
const activeType = ref('') // 当前选中的打卡类型
const taskId = ref('') // 当前选中的任务ID
const subTaskId = ref('') // 当前选中的任务ID
const selectedTaskText = ref('') // 选中的任务文本
const selectedTaskValue = ref([]) // 选中的任务值(Picker使用)
const maxCount = ref(5)
// 打卡类型
......@@ -306,6 +308,7 @@ export function useCheckin() {
// 编辑打卡
result = await editUploadTaskInfoAPI({
i: route.query.post_id,
subtask_id: route.query.subtask_id,
note: submitData.note,
meta_id: submitData.meta_id,
file_type: submitData.file_type,
......@@ -363,16 +366,26 @@ export function useCheckin() {
/**
* 初始化编辑数据
* @param {Array} taskOptions - 任务选项列表
*/
const initEditData = async () => {
const initEditData = async (taskOptions = []) => {
if (route.query.status === 'edit') {
try {
const { code, data } = await getUploadTaskInfoAPI({ i: route.query.post_id })
const { code, data } = await getUploadTaskInfoAPI({ i: route.query.post_id, subtask_id: route.query.subtask_id })
if (code) {
message.value = data.note || ''
activeType.value = data.file_type || 'text'
// TODO: 假数据等待真实数据
taskId.value = 'task1'
// 小作业ID
subTaskId.value = route.query.subtask_id
// 更新选中的任务显示
if (subTaskId.value) {
selectedTaskValue.value = [subTaskId.value]
if (taskOptions && taskOptions.length > 0) {
const option = taskOptions.find(o => o.value === subTaskId.value)
selectedTaskText.value = option ? option.text : ''
}
}
// 如果有文件数据,初始化文件列表 - 使用data.files而不是data.meta
if (data.files && data.files.length > 0) {
......@@ -416,7 +429,9 @@ export function useCheckin() {
message,
fileList,
activeType,
taskId,
subTaskId,
selectedTaskText,
selectedTaskValue,
maxCount,
canSubmit,
......
......@@ -23,12 +23,12 @@
<van-field v-model="selectedTaskText" is-link readonly label="选择作业" placeholder="请选择本次打卡的作业"
@click="showTaskPicker = true" class="rounded-lg border border-gray-100" />
<van-popup v-model:show="showTaskPicker" round position="bottom">
<van-picker :columns="taskOptions" @cancel="showTaskPicker = false"
<van-picker v-model="selectedTaskValue" :columns="taskOptions" @cancel="showTaskPicker = false"
@confirm="onConfirmTask" />
</van-popup>
</div>
<!-- 计数对象 -->
<div v-if="taskType === 'count' && selectedTaskValue" class="mb-4">
<div v-if="taskType === 'count' && selectedTaskValue && selectedTaskValue.length > 0" class="mb-4">
<div class="flex justify-between items-center mb-2 mx-2">
<div class="text-sm font-bold text-gray-700">{{ dynamicFieldText }}对象</div>
<van-button size="small" type="primary" plain icon="plus"
......@@ -205,7 +205,9 @@ const {
message,
fileList,
activeType,
taskId,
subTaskId,
selectedTaskText,
selectedTaskValue,
maxCount,
canSubmit,
beforeRead,
......@@ -228,8 +230,6 @@ const taskType = computed(() => route.query.task_type)
// 作业选择相关
const showTaskPicker = ref(false)
const selectedTaskText = ref('')
const selectedTaskValue = ref('')
const taskOptions = ref([])
// TODO: 模拟任务选项数据
......@@ -247,10 +247,10 @@ const mockData = {
]
}
const fetchTargetList = async (taskId) => {
const fetchTargetList = async (subTaskId) => {
// 模拟接口调用延迟
setTimeout(() => {
targetList.value = mockData[taskId] || []
targetList.value = mockData[subTaskId] || []
showLoadingToast({
message: '加载成功',
type: 'success',
......@@ -262,7 +262,7 @@ const fetchTargetList = async (taskId) => {
const onConfirmTask = ({ selectedOptions }) => {
const option = selectedOptions[0]
selectedTaskText.value = option.text
selectedTaskValue.value = option.value
selectedTaskValue.value = [option.value]
showTaskPicker.value = false
// 如果是计数打卡,根据选中的作业ID查询计数对象
......@@ -273,8 +273,8 @@ const onConfirmTask = ({ selectedOptions }) => {
// 监听作业选择变化
watch(selectedTaskValue, (newVal) => {
if (taskType.value === 'count' && newVal) {
fetchTargetList(newVal)
if (taskType.value === 'count' && newVal && newVal.length > 0) {
fetchTargetList(newVal[0])
}
})
......@@ -325,7 +325,7 @@ const confirmAddTarget = (formData) => {
*/
const isSubmitDisabled = computed(() => {
// 1. 校验作业选择
if (!selectedTaskValue.value) return true
if (!selectedTaskValue.value || selectedTaskValue.value.length === 0) return true
// 2. 计数打卡特定校验
if (taskType.value === 'count') {
......@@ -348,13 +348,13 @@ const isSubmitDisabled = computed(() => {
const handleSubmit = async () => {
// 1. 校验作业选择
if (!selectedTaskValue.value) {
if (!selectedTaskValue.value || selectedTaskValue.value.length === 0) {
showToast('请选择作业')
return
}
const extraData = {
subtask_id: selectedTaskValue.value // 小作业ID
subtask_id: selectedTaskValue.value[0] // 小作业ID
}
// 2. 计数打卡特定校验
......@@ -475,7 +475,7 @@ const getTaskDetail = async (month) => {
// 处理编辑模式下的类型合并
if (isEditMode.value && Array.isArray(data.attachment_type)) {
const info = await getUploadTaskInfoAPI({ i: route.query.post_id });
const info = await getUploadTaskInfoAPI({ i: route.query.post_id, subtask_id: route.query.subtask_id });
if (info.code) {
data.attachment_type = [...new Set([...data.attachment_type, info.data.file_type])];
}
......@@ -796,6 +796,9 @@ onMounted(async () => {
getTaskDetail(dayjs().format('YYYY-MM'));
}
// 初始化选中的子任务ID
selectedTaskValue.value = [route.query.subtask_id]
// TODO: 获取小作业列表
const subtask_list = await getSubtaskListAPI({ task_id: route.query.id })
if (subtask_list.code) {
......@@ -811,15 +814,18 @@ onMounted(async () => {
{ text: '作业二:进阶挑战', value: 'task2' },
{ text: '作业三:综合应用', value: 'task3' }]
// 如果有默认选中值,初始化显示文本
if (selectedTaskValue.value.length > 0) {
const option = taskOptions.value.find(o => o.value === selectedTaskValue.value[0])
if (option) {
selectedTaskText.value = option.text
}
}
// 初始化编辑数据
await initEditData()
await initEditData(taskOptions.value)
// TODO: 临时处理,待后端返回真实数据
if (taskId.value) {
selectedTaskText.value = taskOptions.value.find(option => option.value === taskId.value)?.text || ''
selectedTaskValue.value = taskId.value
console.warn('当前选中的任务ID:', taskId.value);
}
console.warn(selectedTaskValue.value);
})
</script>
......
<!--
* @Date: 2025-05-29 15:34:17
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-12-12 17:27:43
* @LastEditTime: 2025-12-12 20:49:55
* @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue
* @Description: 文件描述
-->
......@@ -84,7 +84,7 @@
@audio-play="handleAudioPlay"
>
<template #content-top>
<div class="text-gray-500 font-bold text-sm mb-4">阅读与写作</div>
<div class="text-gray-500 font-bold text-sm mb-4">{{ post.subtask_title }}</div>
</template>
</CheckinCard>
</van-list>
......@@ -392,10 +392,10 @@ const goToCheckinDetailPage = () => {
path: '/checkin/detail',
query: {
id: route.query.id,
subtask_id: selectedSubtaskId.value,
date: current_date,
is_patch: isPatchCheckin.value ? '1' : '0',
task_type: taskDetail.value.task_type,
// type: 'count',
}
})
}
......@@ -463,6 +463,7 @@ const editCheckin = (post) => {
path: '/checkin/detail',
query: {
post_id: post.id,
subtask_id: post.subtask_id,
type: post.file_type,
task_type: taskDetail.value.task_type,
status: 'edit',
......
......@@ -100,7 +100,7 @@
@audio-play="handleAudioPlay"
>
<template #content-top>
<div class="text-gray-500 font-bold text-sm mb-4">阅读与写作</div>
<div class="text-gray-500 font-bold text-sm mb-4">{{ post.subtask_title }}</div>
</template>
<template #footer-right>
<div class="flex items-center cursor-pointer" @click="openAuditDialog(post)">
......
......@@ -198,7 +198,7 @@
@audio-play="handleAudioPlay"
>
<template #content-top>
<div class="text-gray-500 font-bold text-sm mb-4">阅读与写作</div>
<div class="text-gray-500 font-bold text-sm mb-4">{{ post.subtask_title }}</div>
</template>
<template #footer-right>
<div class="flex items-center cursor-pointer" @click="openCommentPopup(post)">
......
<!--
* @Date: 2025-11-19 22:05:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-12-12 18:09:33
* @LastEditTime: 2025-12-12 21:26:17
* @FilePath: /mlaj/src/views/teacher/studentRecordPage.vue
* @Description: 学生作业记录页面(仅作业记录与点评功能),固定 user_id 与 group_id
-->
......@@ -20,7 +20,7 @@
@audio-play="handleAudioPlay"
>
<template #content-top>
<div class="text-gray-500 font-bold text-sm mb-4">阅读与写作</div>
<div class="text-gray-500 font-bold text-sm mb-4">{{ post.subtask_title }}</div>
</template>
<template #footer-right>
<div class="flex items-center cursor-pointer" @click="openCommentPopup(post)">
......