feat: 阅读任务模块 - 关联绘本、任务模板与教师端优化
Made-with: Cursor
This commit is contained in:
parent
de742d9acf
commit
463c3d9922
@ -819,9 +819,9 @@ export interface CreateTaskFromTemplateDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getTaskTemplates = (params?: { pageNum?: number; pageSize?: number }) =>
|
export const getTaskTemplates = (params?: { pageNum?: number; pageSize?: number }) =>
|
||||||
http.get<{ records: any[]; total: number }>('/v1/teacher/task-templates', { params })
|
http.get<{ list: any[]; total: number }>('/v1/teacher/task-templates', { params })
|
||||||
.then(res => ({
|
.then(res => ({
|
||||||
items: res.records || [],
|
items: res.list || [],
|
||||||
total: res.total || 0,
|
total: res.total || 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
<div class="task-list-view">
|
<div class="task-list-view">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<h2><CheckSquareOutlined /> 阅读任务</h2>
|
<h2>
|
||||||
|
<CheckSquareOutlined /> 阅读任务
|
||||||
|
</h2>
|
||||||
<p class="page-desc">管理班级阅读任务,跟踪学生完成情况</p>
|
<p class="page-desc">管理班级阅读任务,跟踪学生完成情况</p>
|
||||||
</div>
|
</div>
|
||||||
<a-button type="primary" @click="openCreateModal">
|
<a-button type="primary" @click="openCreateModal">
|
||||||
@ -44,35 +46,20 @@
|
|||||||
<!-- 筛选区域 -->
|
<!-- 筛选区域 -->
|
||||||
<div class="filter-section">
|
<div class="filter-section">
|
||||||
<a-space :size="16">
|
<a-space :size="16">
|
||||||
<a-select
|
<a-select v-model:value="filters.status" placeholder="任务状态" style="width: 120px;" allowClear
|
||||||
v-model:value="filters.status"
|
@change="loadTasks">
|
||||||
placeholder="任务状态"
|
|
||||||
style="width: 120px;"
|
|
||||||
allowClear
|
|
||||||
@change="loadTasks"
|
|
||||||
>
|
|
||||||
<a-select-option value="PUBLISHED">进行中</a-select-option>
|
<a-select-option value="PUBLISHED">进行中</a-select-option>
|
||||||
<a-select-option value="DRAFT">草稿</a-select-option>
|
<a-select-option value="DRAFT">草稿</a-select-option>
|
||||||
<a-select-option value="ARCHIVED">已归档</a-select-option>
|
<a-select-option value="ARCHIVED">已归档</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-select
|
<a-select v-model:value="filters.taskType" placeholder="任务类型" style="width: 120px;" allowClear
|
||||||
v-model:value="filters.taskType"
|
@change="loadTasks">
|
||||||
placeholder="任务类型"
|
|
||||||
style="width: 120px;"
|
|
||||||
allowClear
|
|
||||||
@change="loadTasks"
|
|
||||||
>
|
|
||||||
<a-select-option value="READING">阅读</a-select-option>
|
<a-select-option value="READING">阅读</a-select-option>
|
||||||
<a-select-option value="ACTIVITY">活动</a-select-option>
|
<a-select-option value="ACTIVITY">活动</a-select-option>
|
||||||
<a-select-option value="HOMEWORK">作业</a-select-option>
|
<a-select-option value="HOMEWORK">作业</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-input-search
|
<a-input-search v-model:value="filters.keyword" placeholder="搜索任务标题" style="width: 200px;" @search="loadTasks"
|
||||||
v-model:value="filters.keyword"
|
allow-clear />
|
||||||
placeholder="搜索任务标题"
|
|
||||||
style="width: 200px;"
|
|
||||||
@search="loadTasks"
|
|
||||||
allow-clear
|
|
||||||
/>
|
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -105,12 +92,8 @@
|
|||||||
|
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="progress-info">
|
<div class="progress-info">
|
||||||
<a-progress
|
<a-progress :percent="getCompletionRate(task)" :stroke-color="{ '0%': '#52c41a', '100%': '#73d13d' }"
|
||||||
:percent="getCompletionRate(task)"
|
size="small" style="width: 150px;" />
|
||||||
:stroke-color="{ '0%': '#52c41a', '100%': '#73d13d' }"
|
|
||||||
size="small"
|
|
||||||
style="width: 150px;"
|
|
||||||
/>
|
|
||||||
<span class="progress-text">{{ getCompletedCount(task) }}/{{ task.targetCount || 0 }} 人完成</span>
|
<span class="progress-text">{{ getCompletedCount(task) }}/{{ task.targetCount || 0 }} 人完成</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
@ -154,34 +137,18 @@
|
|||||||
</a-spin>
|
</a-spin>
|
||||||
|
|
||||||
<div class="pagination-section" v-if="total > pageSize">
|
<div class="pagination-section" v-if="total > pageSize">
|
||||||
<a-pagination
|
<a-pagination v-model:current="currentPage" :total="total" :page-size="pageSize" @change="onPageChange"
|
||||||
v-model:current="currentPage"
|
show-quick-jumper :show-total="(total: number) => `共 ${total} 条`" />
|
||||||
:total="total"
|
|
||||||
:page-size="pageSize"
|
|
||||||
@change="onPageChange"
|
|
||||||
show-quick-jumper
|
|
||||||
:show-total="(total: number) => `共 ${total} 条`"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 创建/编辑任务弹窗 -->
|
<!-- 创建/编辑任务弹窗 -->
|
||||||
<a-modal
|
<a-modal v-model:open="createModalVisible" :title="isEdit ? '编辑任务' : '新建阅读任务'" @ok="handleCreate"
|
||||||
v-model:open="createModalVisible"
|
:confirm-loading="creating" width="600px">
|
||||||
:title="isEdit ? '编辑任务' : '新建阅读任务'"
|
|
||||||
@ok="handleCreate"
|
|
||||||
:confirm-loading="creating"
|
|
||||||
width="600px"
|
|
||||||
>
|
|
||||||
<a-form :model="createForm" layout="vertical">
|
<a-form :model="createForm" layout="vertical">
|
||||||
<!-- 模板选择(仅新建时显示) -->
|
<!-- 模板选择(仅新建时显示) -->
|
||||||
<a-form-item label="使用模板" v-if="!isEdit">
|
<a-form-item label="使用模板" v-if="!isEdit">
|
||||||
<a-select
|
<a-select v-model:value="selectedTemplateId" placeholder="选择模板快速填充(可选)" style="width: 100%;" allowClear
|
||||||
v-model:value="selectedTemplateId"
|
@change="onTemplateSelect as any">
|
||||||
placeholder="选择模板快速填充(可选)"
|
|
||||||
style="width: 100%;"
|
|
||||||
allowClear
|
|
||||||
@change="onTemplateSelect as any"
|
|
||||||
>
|
|
||||||
<a-select-option v-for="tpl in templates" :key="tpl.id" :value="tpl.id">
|
<a-select-option v-for="tpl in templates" :key="tpl.id" :value="tpl.id">
|
||||||
{{ tpl.name }}
|
{{ tpl.name }}
|
||||||
<a-tag size="small" :color="getTaskTypeColor(tpl.taskType)" style="margin-left: 8px;">
|
<a-tag size="small" :color="getTaskTypeColor(tpl.taskType)" style="margin-left: 8px;">
|
||||||
@ -216,77 +183,44 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-form-item label="选择目标" required v-if="createForm.targetType === 'CLASS'">
|
<a-form-item label="选择目标" required v-if="createForm.targetType === 'CLASS'">
|
||||||
<a-select
|
<a-select v-model:value="createForm.targetIds" mode="multiple" placeholder="请选择班级" style="width: 100%;">
|
||||||
v-model:value="createForm.targetIds"
|
|
||||||
mode="multiple"
|
|
||||||
placeholder="请选择班级"
|
|
||||||
style="width: 100%;"
|
|
||||||
>
|
|
||||||
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
|
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
|
||||||
{{ cls.name }} ({{ cls.grade }})
|
{{ cls.name }} ({{ cls.grade }})
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="选择学生" required v-if="createForm.targetType === 'STUDENT'">
|
<a-form-item label="选择学生" required v-if="createForm.targetType === 'STUDENT'">
|
||||||
<a-select
|
<a-select v-model:value="createForm.targetIds" mode="multiple" placeholder="请选择学生" style="width: 100%;"
|
||||||
v-model:value="createForm.targetIds"
|
:filter-option="filterStudentOption" show-search>
|
||||||
mode="multiple"
|
|
||||||
placeholder="请选择学生"
|
|
||||||
style="width: 100%;"
|
|
||||||
:filter-option="filterStudentOption"
|
|
||||||
show-search
|
|
||||||
>
|
|
||||||
<a-select-option v-for="student in students" :key="student.id" :value="student.id">
|
<a-select-option v-for="student in students" :key="student.id" :value="student.id">
|
||||||
{{ student.name }} - {{ student.class?.name }}
|
{{ student.name }} - {{ student.class?.name }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="关联课程">
|
<a-form-item label="关联课程">
|
||||||
<a-select
|
<a-select v-model:value="createForm.relatedCourseId" placeholder="可选,关联课程包" style="width: 100%;" allowClear
|
||||||
v-model:value="createForm.relatedCourseId"
|
show-search :filter-option="filterCourseOption">
|
||||||
placeholder="可选,关联课程包"
|
|
||||||
style="width: 100%;"
|
|
||||||
allowClear
|
|
||||||
show-search
|
|
||||||
:filter-option="filterCourseOption"
|
|
||||||
>
|
|
||||||
<a-select-option v-for="course in courses" :key="course.id" :value="course.id">
|
<a-select-option v-for="course in courses" :key="course.id" :value="course.id">
|
||||||
{{ course.name }}
|
{{ course.name }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="关联绘本">
|
<a-form-item label="关联绘本">
|
||||||
<a-input
|
<a-input v-model:value="createForm.relatedBookName" placeholder="请输入关联的绘本名称(可选)" allow-clear />
|
||||||
v-model:value="createForm.relatedBookName"
|
|
||||||
placeholder="请输入关联的绘本名称(可选)"
|
|
||||||
allow-clear
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="任务时间" required>
|
<a-form-item label="任务时间" required>
|
||||||
<a-range-picker
|
<a-range-picker v-model:value="createForm.dateRange as any" style="width: 100%;" />
|
||||||
v-model:value="createForm.dateRange as any"
|
|
||||||
style="width: 100%;"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<!-- 完成情况详情弹窗 -->
|
<!-- 完成情况详情弹窗 -->
|
||||||
<a-modal
|
<a-modal v-model:open="completionModalVisible" :title="`完成情况 - ${selectedTask?.title || ''}`" width="800px"
|
||||||
v-model:open="completionModalVisible"
|
:footer="null">
|
||||||
:title="`完成情况 - ${selectedTask?.title || ''}`"
|
|
||||||
width="800px"
|
|
||||||
:footer="null"
|
|
||||||
>
|
|
||||||
<div class="completion-header">
|
<div class="completion-header">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-select
|
<a-select v-model:value="completionFilter.status" placeholder="筛选状态" style="width: 120px;" allowClear
|
||||||
v-model:value="completionFilter.status"
|
@change="loadCompletions">
|
||||||
placeholder="筛选状态"
|
|
||||||
style="width: 120px;"
|
|
||||||
allowClear
|
|
||||||
@change="loadCompletions"
|
|
||||||
>
|
|
||||||
<a-select-option value="PENDING">待提交</a-select-option>
|
<a-select-option value="PENDING">待提交</a-select-option>
|
||||||
<a-select-option value="SUBMITTED">已提交</a-select-option>
|
<a-select-option value="SUBMITTED">已提交</a-select-option>
|
||||||
<a-select-option value="REVIEWED">已评价</a-select-option>
|
<a-select-option value="REVIEWED">已评价</a-select-option>
|
||||||
@ -308,7 +242,9 @@
|
|||||||
</a-avatar>
|
</a-avatar>
|
||||||
<div class="student-detail">
|
<div class="student-detail">
|
||||||
<div class="student-name">{{ completion.student.name }}</div>
|
<div class="student-name">{{ completion.student.name }}</div>
|
||||||
<div class="student-class">{{ completion.student.class?.name || completion.student.classInfo?.name || '-' }}</div>
|
<div class="student-class">{{ completion.student.class?.name || completion.student.classInfo?.name ||
|
||||||
|
'-' }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="completion-status">
|
<div class="completion-status">
|
||||||
@ -331,7 +267,10 @@
|
|||||||
<div v-if="completion.parentFeedback || completion.feedback?.comment" class="parent-feedback">
|
<div v-if="completion.parentFeedback || completion.feedback?.comment" class="parent-feedback">
|
||||||
<MessageOutlined class="feedback-icon" />
|
<MessageOutlined class="feedback-icon" />
|
||||||
<a-tooltip :title="completion.parentFeedback || completion.feedback?.comment">
|
<a-tooltip :title="completion.parentFeedback || completion.feedback?.comment">
|
||||||
<span class="feedback-text">{{ (completion.parentFeedback || completion.feedback?.comment || '').substring(0, 30) }}{{ (completion.parentFeedback || completion.feedback?.comment || '').length > 30 ? '...' : '' }}</span>
|
<span class="feedback-text">{{ (completion.parentFeedback || completion.feedback?.comment ||
|
||||||
|
'').substring(0, 30) }}{{ (completion.parentFeedback || completion.feedback?.comment || '').length >
|
||||||
|
30
|
||||||
|
? '...' : '' }}</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<span v-else class="no-feedback">暂无反馈</span>
|
<span v-else class="no-feedback">暂无反馈</span>
|
||||||
@ -351,38 +290,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="empty-state" v-else style="padding: 40px 0;">
|
<div class="completion-empty-state" v-else>
|
||||||
<InboxOutlined style="font-size: 48px; color: #d9d9d9;" />
|
<InboxOutlined class="empty-icon" />
|
||||||
<p>暂无完成记录</p>
|
<p>暂无完成记录</p>
|
||||||
</div>
|
</div>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
|
|
||||||
<div class="pagination-section" v-if="completionTotal > completionPageSize" style="margin-top: 16px;">
|
<div class="pagination-section" v-if="completionTotal > completionPageSize" style="margin-top: 16px;">
|
||||||
<a-pagination
|
<a-pagination v-model:current="completionPage" :total="completionTotal" :page-size="completionPageSize"
|
||||||
v-model:current="completionPage"
|
size="small" @change="onCompletionPageChange" />
|
||||||
:total="completionTotal"
|
|
||||||
:page-size="completionPageSize"
|
|
||||||
size="small"
|
|
||||||
@change="onCompletionPageChange"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<!-- 评价弹窗 -->
|
<!-- 评价弹窗 -->
|
||||||
<a-modal
|
<a-modal v-model:open="feedbackModalVisible"
|
||||||
v-model:open="feedbackModalVisible"
|
|
||||||
:title="selectedCompletionForFeedback ? `评价 - ${selectedCompletionForFeedback.student?.name || ''}` : '评价'"
|
:title="selectedCompletionForFeedback ? `评价 - ${selectedCompletionForFeedback.student?.name || ''}` : '评价'"
|
||||||
width="600px"
|
width="600px" :confirm-loading="submittingFeedback" @ok="submitFeedback" okText="提交评价" cancelText="取消">
|
||||||
:confirm-loading="submittingFeedback"
|
|
||||||
@ok="submitFeedback"
|
|
||||||
okText="提交评价"
|
|
||||||
cancelText="取消"
|
|
||||||
>
|
|
||||||
<a-form layout="vertical" v-if="selectedCompletionForFeedback">
|
<a-form layout="vertical" v-if="selectedCompletionForFeedback">
|
||||||
<!-- 提交内容预览 -->
|
<!-- 提交内容预览 -->
|
||||||
<div class="submission-preview" style="margin-bottom: 24px; padding: 16px; background: #fafafa; border-radius: 8px;">
|
<div class="submission-preview"
|
||||||
|
style="margin-bottom: 24px; padding: 16px; background: #fafafa; border-radius: 8px;">
|
||||||
<h4 style="margin-bottom: 12px;">提交内容</h4>
|
<h4 style="margin-bottom: 12px;">提交内容</h4>
|
||||||
<div v-if="selectedCompletionForFeedback.photos && selectedCompletionForFeedback.photos.length > 0" style="margin-bottom: 12px;">
|
<div v-if="selectedCompletionForFeedback.photos && selectedCompletionForFeedback.photos.length > 0"
|
||||||
|
style="margin-bottom: 12px;">
|
||||||
<PictureOutlined style="margin-right: 8px;" />
|
<PictureOutlined style="margin-right: 8px;" />
|
||||||
<span>{{ selectedCompletionForFeedback.photos.length }} 张照片</span>
|
<span>{{ selectedCompletionForFeedback.photos.length }} 张照片</span>
|
||||||
</div>
|
</div>
|
||||||
@ -420,13 +350,8 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="评语">
|
<a-form-item label="评语">
|
||||||
<a-textarea
|
<a-textarea v-model:value="feedbackForm.comment" placeholder="请输入评语(选填,最多500字)" :rows="4" :maxlength="500"
|
||||||
v-model:value="feedbackForm.comment"
|
show-count />
|
||||||
placeholder="请输入评语(选填,最多500字)"
|
|
||||||
:rows="4"
|
|
||||||
:maxlength="500"
|
|
||||||
show-count
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@ -461,6 +386,7 @@ import {
|
|||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import {
|
import {
|
||||||
getTeacherTasks,
|
getTeacherTasks,
|
||||||
|
getTeacherTask,
|
||||||
createTeacherTask,
|
createTeacherTask,
|
||||||
updateTeacherTask,
|
updateTeacherTask,
|
||||||
deleteTeacherTask,
|
deleteTeacherTask,
|
||||||
@ -694,21 +620,32 @@ const getTaskTypeText = (type: string) => {
|
|||||||
return texts[type] || type;
|
return texts[type] || type;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开编辑弹窗
|
// 打开编辑弹窗:通过详情接口获取完整数据,回显与新增表单一致
|
||||||
const openEditModal = (task: TeacherTask) => {
|
const openEditModal = async (task: TeacherTask) => {
|
||||||
isEdit.value = true;
|
isEdit.value = true;
|
||||||
editTaskId.value = task.id;
|
editTaskId.value = task.id;
|
||||||
createForm.title = task.title;
|
|
||||||
createForm.description = task.description || '';
|
|
||||||
createForm.taskType = task.taskType;
|
|
||||||
createForm.targetType = task.targetType;
|
|
||||||
createForm.relatedCourseId = task.relatedCourseId;
|
|
||||||
createForm.relatedBookName = task.relatedBookName || ''; // 添加绘本名称
|
|
||||||
createForm.dateRange = [
|
|
||||||
dayjs(task.startDate),
|
|
||||||
dayjs(task.endDate),
|
|
||||||
];
|
|
||||||
createModalVisible.value = true;
|
createModalVisible.value = true;
|
||||||
|
try {
|
||||||
|
await loadOptions(); // 确保班级、学生、课程选项已加载
|
||||||
|
const detail = await getTeacherTask(task.id);
|
||||||
|
// 详情接口返回字段映射到表单(与新增一致)
|
||||||
|
createForm.title = detail.title || '';
|
||||||
|
createForm.description = detail.description || '';
|
||||||
|
createForm.taskType = (detail.type || detail.taskType || 'READING') as 'READING' | 'ACTIVITY' | 'HOMEWORK';
|
||||||
|
createForm.targetType = (detail.targetType?.toUpperCase?.() || 'CLASS') as 'CLASS' | 'STUDENT';
|
||||||
|
createForm.targetIds = detail.targetIds || [];
|
||||||
|
// 关联课程:统一为 number,避免 API 返回 string 导致 a-select 无法匹配
|
||||||
|
const rawCourseId = detail.courseId ?? detail.relatedCourseId;
|
||||||
|
createForm.relatedCourseId = rawCourseId;
|
||||||
|
createForm.relatedBookName = detail.relatedBookName || '';
|
||||||
|
// 任务时间:dueDate 为空时用 startDate 作为结束日期,保证能回显
|
||||||
|
createForm.dateRange = detail.startDate
|
||||||
|
? [dayjs(detail.startDate), dayjs(detail.dueDate || detail.startDate)]
|
||||||
|
: null;
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message || '加载任务详情失败');
|
||||||
|
createModalVisible.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建/更新任务
|
// 创建/更新任务
|
||||||
@ -1104,6 +1041,26 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.completion-empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #d9d9d9;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.completion-list {
|
.completion-list {
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
@ -51,8 +51,9 @@ public class TeacherTaskController {
|
|||||||
@Operation(summary = "根据 ID 获取任务")
|
@Operation(summary = "根据 ID 获取任务")
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public Result<TaskResponse> getTask(@PathVariable Long id) {
|
public Result<TaskResponse> getTask(@PathVariable Long id) {
|
||||||
Task task = taskService.getTaskById(id);
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
return Result.success(taskMapper.toVO(task));
|
TaskResponse detail = taskService.getTaskDetailWithTargets(id, tenantId);
|
||||||
|
return Result.success(detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "获取任务分页列表")
|
@Operation(summary = "获取任务分页列表")
|
||||||
@ -62,9 +63,11 @@ public class TeacherTaskController {
|
|||||||
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
|
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
|
||||||
@RequestParam(required = false) String keyword,
|
@RequestParam(required = false) String keyword,
|
||||||
@RequestParam(required = false) String type,
|
@RequestParam(required = false) String type,
|
||||||
|
@RequestParam(required = false) String taskType,
|
||||||
@RequestParam(required = false) String status) {
|
@RequestParam(required = false) String status) {
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
Page<Task> page = taskService.getTaskPage(tenantId, pageNum, pageSize, keyword, type, status);
|
String typeFilter = (type != null && !type.isEmpty()) ? type : taskType;
|
||||||
|
Page<Task> page = taskService.getTaskPage(tenantId, pageNum, pageSize, keyword, typeFilter, status);
|
||||||
List<TaskResponse> voList = taskMapper.toVO(page.getRecords());
|
List<TaskResponse> voList = taskMapper.toVO(page.getRecords());
|
||||||
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
|
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,8 +41,9 @@ public class TeacherTaskTemplateController {
|
|||||||
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
|
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
|
||||||
@RequestParam(required = false) String type,
|
@RequestParam(required = false) String type,
|
||||||
@RequestParam(required = false) String keyword) {
|
@RequestParam(required = false) String keyword) {
|
||||||
|
// 教师查询其关联学校(tenant)下的所有模板,包括学校端建立的阅读模版
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
Page<TaskTemplate> page = taskTemplateService.getTemplates(tenantId, pageNum, pageSize, type, keyword, true);
|
Page<TaskTemplate> page = taskTemplateService.getTemplates(tenantId, pageNum, pageSize, type, keyword, false);
|
||||||
List<TaskTemplateResponse> voList = taskTemplateMapper.toVO(page.getRecords());
|
List<TaskTemplateResponse> voList = taskTemplateMapper.toVO(page.getRecords());
|
||||||
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
|
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,18 +21,27 @@ public class TaskCreateRequest {
|
|||||||
@Schema(description = "任务类型:reading-阅读,homework-作业,activity-活动")
|
@Schema(description = "任务类型:reading-阅读,homework-作业,activity-活动")
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "任务类型(前端兼容 taskType)")
|
||||||
|
private String taskType;
|
||||||
|
|
||||||
@Schema(description = "关联绘本名称(手动填写)")
|
@Schema(description = "关联绘本名称(手动填写)")
|
||||||
private String relatedBookName;
|
private String relatedBookName;
|
||||||
|
|
||||||
@Schema(description = "课程 ID")
|
@Schema(description = "课程 ID")
|
||||||
private Long courseId;
|
private Long courseId;
|
||||||
|
|
||||||
|
@Schema(description = "课程 ID(前端兼容 relatedCourseId)")
|
||||||
|
private Long relatedCourseId;
|
||||||
|
|
||||||
@Schema(description = "开始日期")
|
@Schema(description = "开始日期")
|
||||||
private LocalDate startDate;
|
private LocalDate startDate;
|
||||||
|
|
||||||
@Schema(description = "截止日期")
|
@Schema(description = "截止日期")
|
||||||
private LocalDate dueDate;
|
private LocalDate dueDate;
|
||||||
|
|
||||||
|
@Schema(description = "截止日期(前端兼容 endDate)")
|
||||||
|
private LocalDate endDate;
|
||||||
|
|
||||||
@Schema(description = "附件(JSON 数组)")
|
@Schema(description = "附件(JSON 数组)")
|
||||||
private String attachments;
|
private String attachments;
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "任务更新请求")
|
@Schema(description = "任务更新请求")
|
||||||
@ -18,6 +19,18 @@ public class TaskUpdateRequest {
|
|||||||
@Schema(description = "任务类型")
|
@Schema(description = "任务类型")
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "任务类型(前端兼容 taskType)")
|
||||||
|
private String taskType;
|
||||||
|
|
||||||
|
@Schema(description = "关联绘本名称")
|
||||||
|
private String relatedBookName;
|
||||||
|
|
||||||
|
@Schema(description = "课程 ID")
|
||||||
|
private Long courseId;
|
||||||
|
|
||||||
|
@Schema(description = "课程 ID(前端兼容 relatedCourseId)")
|
||||||
|
private Long relatedCourseId;
|
||||||
|
|
||||||
@Schema(description = "开始日期")
|
@Schema(description = "开始日期")
|
||||||
private LocalDate startDate;
|
private LocalDate startDate;
|
||||||
|
|
||||||
@ -30,4 +43,10 @@ public class TaskUpdateRequest {
|
|||||||
@Schema(description = "附件(JSON 数组)")
|
@Schema(description = "附件(JSON 数组)")
|
||||||
private String attachments;
|
private String attachments;
|
||||||
|
|
||||||
|
@Schema(description = "目标类型:CLASS-班级,STUDENT-学生")
|
||||||
|
private String targetType;
|
||||||
|
|
||||||
|
@Schema(description = "目标 IDs")
|
||||||
|
private List<Long> targetIds;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,6 +55,12 @@ public class TaskResponse {
|
|||||||
@Schema(description = "附件")
|
@Schema(description = "附件")
|
||||||
private String attachments;
|
private String attachments;
|
||||||
|
|
||||||
|
@Schema(description = "目标类型:CLASS-班级,STUDENT-学生")
|
||||||
|
private String targetType;
|
||||||
|
|
||||||
|
@Schema(description = "目标 IDs(班级或学生)")
|
||||||
|
private java.util.List<Long> targetIds;
|
||||||
|
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
|||||||
@ -71,4 +71,9 @@ public interface TaskService extends com.baomidou.mybatisplus.extension.service.
|
|||||||
*/
|
*/
|
||||||
TaskResponse getTaskDetailForSchool(Long taskId, Long tenantId);
|
TaskResponse getTaskDetailForSchool(Long taskId, Long tenantId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取任务详情(含目标类型、目标列表),用于编辑回显
|
||||||
|
*/
|
||||||
|
TaskResponse getTaskDetailWithTargets(Long taskId, Long tenantId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -51,13 +52,16 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
|
|||||||
task.setTenantId(tenantId);
|
task.setTenantId(tenantId);
|
||||||
task.setTitle(request.getTitle());
|
task.setTitle(request.getTitle());
|
||||||
task.setDescription(request.getDescription());
|
task.setDescription(request.getDescription());
|
||||||
task.setType(request.getType() != null ? request.getType() : "homework");
|
String taskType = request.getType() != null ? request.getType() : request.getTaskType();
|
||||||
|
task.setType(taskType != null ? taskType : "homework");
|
||||||
task.setRelatedBookName(request.getRelatedBookName()); // 添加关联绘本名称
|
task.setRelatedBookName(request.getRelatedBookName()); // 添加关联绘本名称
|
||||||
task.setCourseId(request.getCourseId());
|
Long courseId = request.getCourseId() != null ? request.getCourseId() : request.getRelatedCourseId();
|
||||||
|
task.setCourseId(courseId);
|
||||||
task.setCreatorId(creatorId);
|
task.setCreatorId(creatorId);
|
||||||
task.setCreatorRole(creatorRole);
|
task.setCreatorRole(creatorRole);
|
||||||
task.setStartDate(request.getStartDate());
|
task.setStartDate(request.getStartDate());
|
||||||
task.setDueDate(request.getDueDate());
|
LocalDate dueDate = request.getDueDate() != null ? request.getDueDate() : request.getEndDate();
|
||||||
|
task.setDueDate(dueDate);
|
||||||
task.setStatus("pending");
|
task.setStatus("pending");
|
||||||
task.setAttachments(request.getAttachments());
|
task.setAttachments(request.getAttachments());
|
||||||
|
|
||||||
@ -89,8 +93,16 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
|
|||||||
if (request.getDescription() != null) {
|
if (request.getDescription() != null) {
|
||||||
task.setDescription(request.getDescription());
|
task.setDescription(request.getDescription());
|
||||||
}
|
}
|
||||||
if (request.getType() != null) {
|
String taskType = request.getType() != null ? request.getType() : request.getTaskType();
|
||||||
task.setType(request.getType());
|
if (taskType != null) {
|
||||||
|
task.setType(taskType);
|
||||||
|
}
|
||||||
|
if (request.getRelatedBookName() != null) {
|
||||||
|
task.setRelatedBookName(request.getRelatedBookName());
|
||||||
|
}
|
||||||
|
Long courseId = request.getCourseId() != null ? request.getCourseId() : request.getRelatedCourseId();
|
||||||
|
if (courseId != null) {
|
||||||
|
task.setCourseId(courseId);
|
||||||
}
|
}
|
||||||
if (request.getStartDate() != null) {
|
if (request.getStartDate() != null) {
|
||||||
task.setStartDate(request.getStartDate());
|
task.setStartDate(request.getStartDate());
|
||||||
@ -106,6 +118,22 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
|
|||||||
}
|
}
|
||||||
|
|
||||||
taskMapper.updateById(task);
|
taskMapper.updateById(task);
|
||||||
|
|
||||||
|
// 更新任务目标
|
||||||
|
if (request.getTargetIds() != null) {
|
||||||
|
taskTargetMapper.delete(new LambdaQueryWrapper<TaskTarget>().eq(TaskTarget::getTaskId, id));
|
||||||
|
if (!request.getTargetIds().isEmpty()) {
|
||||||
|
String targetType = StringUtils.hasText(request.getTargetType()) ? request.getTargetType() : "CLASS";
|
||||||
|
for (Long targetId : request.getTargetIds()) {
|
||||||
|
TaskTarget target = new TaskTarget();
|
||||||
|
target.setTaskId(id);
|
||||||
|
target.setTargetType(targetType);
|
||||||
|
target.setTargetId(targetId);
|
||||||
|
taskTargetMapper.insert(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.info("任务更新成功:id={}", id);
|
log.info("任务更新成功:id={}", id);
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
@ -518,10 +546,22 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TaskResponse getTaskDetailForSchool(Long taskId, Long tenantId) {
|
public TaskResponse getTaskDetailForSchool(Long taskId, Long tenantId) {
|
||||||
log.info("获取学校端任务详情:taskId={}, tenantId={}", taskId, tenantId);
|
return buildTaskDetailResponse(taskId, tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
// 验证任务存在且属于该租户
|
/**
|
||||||
|
* 获取任务详情(含目标类型、目标列表),用于编辑回显
|
||||||
|
*/
|
||||||
|
public TaskResponse getTaskDetailWithTargets(Long taskId, Long tenantId) {
|
||||||
|
return buildTaskDetailResponse(taskId, tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskResponse buildTaskDetailResponse(Long taskId, Long tenantId) {
|
||||||
Task task = getTaskByIdWithTenantCheck(taskId, tenantId);
|
Task task = getTaskByIdWithTenantCheck(taskId, tenantId);
|
||||||
|
List<TaskTarget> targets = taskTargetMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<TaskTarget>().eq(TaskTarget::getTaskId, taskId));
|
||||||
|
String targetType = targets.isEmpty() ? null : targets.get(0).getTargetType();
|
||||||
|
List<Long> targetIds = targets.stream().map(TaskTarget::getTargetId).toList();
|
||||||
|
|
||||||
return TaskResponse.builder()
|
return TaskResponse.builder()
|
||||||
.id(task.getId())
|
.id(task.getId())
|
||||||
@ -537,6 +577,8 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
|
|||||||
.dueDate(task.getDueDate())
|
.dueDate(task.getDueDate())
|
||||||
.status(task.getStatus())
|
.status(task.getStatus())
|
||||||
.attachments(task.getAttachments())
|
.attachments(task.getAttachments())
|
||||||
|
.targetType(targetType)
|
||||||
|
.targetIds(targetIds)
|
||||||
.createdAt(task.getCreatedAt())
|
.createdAt(task.getCreatedAt())
|
||||||
.updatedAt(task.getUpdatedAt())
|
.updatedAt(task.getUpdatedAt())
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user