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 }) =>
|
||||
http.get<{ records: any[]; total: number }>('/v1/teacher/task-templates', { params })
|
||||
http.get<{ list: any[]; total: number }>('/v1/teacher/task-templates', { params })
|
||||
.then(res => ({
|
||||
items: res.records || [],
|
||||
items: res.list || [],
|
||||
total: res.total || 0,
|
||||
}));
|
||||
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
<div class="task-list-view">
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<h2><CheckSquareOutlined /> 阅读任务</h2>
|
||||
<h2>
|
||||
<CheckSquareOutlined /> 阅读任务
|
||||
</h2>
|
||||
<p class="page-desc">管理班级阅读任务,跟踪学生完成情况</p>
|
||||
</div>
|
||||
<a-button type="primary" @click="openCreateModal">
|
||||
@ -44,35 +46,20 @@
|
||||
<!-- 筛选区域 -->
|
||||
<div class="filter-section">
|
||||
<a-space :size="16">
|
||||
<a-select
|
||||
v-model:value="filters.status"
|
||||
placeholder="任务状态"
|
||||
style="width: 120px;"
|
||||
allowClear
|
||||
@change="loadTasks"
|
||||
>
|
||||
<a-select v-model:value="filters.status" placeholder="任务状态" style="width: 120px;" allowClear
|
||||
@change="loadTasks">
|
||||
<a-select-option value="PUBLISHED">进行中</a-select-option>
|
||||
<a-select-option value="DRAFT">草稿</a-select-option>
|
||||
<a-select-option value="ARCHIVED">已归档</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-model:value="filters.taskType"
|
||||
placeholder="任务类型"
|
||||
style="width: 120px;"
|
||||
allowClear
|
||||
@change="loadTasks"
|
||||
>
|
||||
<a-select v-model:value="filters.taskType" placeholder="任务类型" style="width: 120px;" allowClear
|
||||
@change="loadTasks">
|
||||
<a-select-option value="READING">阅读</a-select-option>
|
||||
<a-select-option value="ACTIVITY">活动</a-select-option>
|
||||
<a-select-option value="HOMEWORK">作业</a-select-option>
|
||||
</a-select>
|
||||
<a-input-search
|
||||
v-model:value="filters.keyword"
|
||||
placeholder="搜索任务标题"
|
||||
style="width: 200px;"
|
||||
@search="loadTasks"
|
||||
allow-clear
|
||||
/>
|
||||
<a-input-search v-model:value="filters.keyword" placeholder="搜索任务标题" style="width: 200px;" @search="loadTasks"
|
||||
allow-clear />
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
@ -105,12 +92,8 @@
|
||||
|
||||
<div class="card-footer">
|
||||
<div class="progress-info">
|
||||
<a-progress
|
||||
:percent="getCompletionRate(task)"
|
||||
:stroke-color="{ '0%': '#52c41a', '100%': '#73d13d' }"
|
||||
size="small"
|
||||
style="width: 150px;"
|
||||
/>
|
||||
<a-progress :percent="getCompletionRate(task)" :stroke-color="{ '0%': '#52c41a', '100%': '#73d13d' }"
|
||||
size="small" style="width: 150px;" />
|
||||
<span class="progress-text">{{ getCompletedCount(task) }}/{{ task.targetCount || 0 }} 人完成</span>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
@ -154,34 +137,18 @@
|
||||
</a-spin>
|
||||
|
||||
<div class="pagination-section" v-if="total > pageSize">
|
||||
<a-pagination
|
||||
v-model:current="currentPage"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
@change="onPageChange"
|
||||
show-quick-jumper
|
||||
:show-total="(total: number) => `共 ${total} 条`"
|
||||
/>
|
||||
<a-pagination v-model:current="currentPage" :total="total" :page-size="pageSize" @change="onPageChange"
|
||||
show-quick-jumper :show-total="(total: number) => `共 ${total} 条`" />
|
||||
</div>
|
||||
|
||||
<!-- 创建/编辑任务弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="createModalVisible"
|
||||
:title="isEdit ? '编辑任务' : '新建阅读任务'"
|
||||
@ok="handleCreate"
|
||||
:confirm-loading="creating"
|
||||
width="600px"
|
||||
>
|
||||
<a-modal v-model:open="createModalVisible" :title="isEdit ? '编辑任务' : '新建阅读任务'" @ok="handleCreate"
|
||||
:confirm-loading="creating" width="600px">
|
||||
<a-form :model="createForm" layout="vertical">
|
||||
<!-- 模板选择(仅新建时显示) -->
|
||||
<a-form-item label="使用模板" v-if="!isEdit">
|
||||
<a-select
|
||||
v-model:value="selectedTemplateId"
|
||||
placeholder="选择模板快速填充(可选)"
|
||||
style="width: 100%;"
|
||||
allowClear
|
||||
@change="onTemplateSelect as any"
|
||||
>
|
||||
<a-select v-model:value="selectedTemplateId" placeholder="选择模板快速填充(可选)" style="width: 100%;" allowClear
|
||||
@change="onTemplateSelect as any">
|
||||
<a-select-option v-for="tpl in templates" :key="tpl.id" :value="tpl.id">
|
||||
{{ tpl.name }}
|
||||
<a-tag size="small" :color="getTaskTypeColor(tpl.taskType)" style="margin-left: 8px;">
|
||||
@ -216,77 +183,44 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="选择目标" required v-if="createForm.targetType === 'CLASS'">
|
||||
<a-select
|
||||
v-model:value="createForm.targetIds"
|
||||
mode="multiple"
|
||||
placeholder="请选择班级"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<a-select 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">
|
||||
{{ cls.name }} ({{ cls.grade }})
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="选择学生" required v-if="createForm.targetType === 'STUDENT'">
|
||||
<a-select
|
||||
v-model:value="createForm.targetIds"
|
||||
mode="multiple"
|
||||
placeholder="请选择学生"
|
||||
style="width: 100%;"
|
||||
:filter-option="filterStudentOption"
|
||||
show-search
|
||||
>
|
||||
<a-select v-model:value="createForm.targetIds" 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">
|
||||
{{ student.name }} - {{ student.class?.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="关联课程">
|
||||
<a-select
|
||||
v-model:value="createForm.relatedCourseId"
|
||||
placeholder="可选,关联课程包"
|
||||
style="width: 100%;"
|
||||
allowClear
|
||||
show-search
|
||||
:filter-option="filterCourseOption"
|
||||
>
|
||||
<a-select v-model:value="createForm.relatedCourseId" placeholder="可选,关联课程包" style="width: 100%;" allowClear
|
||||
show-search :filter-option="filterCourseOption">
|
||||
<a-select-option v-for="course in courses" :key="course.id" :value="course.id">
|
||||
{{ course.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="关联绘本">
|
||||
<a-input
|
||||
v-model:value="createForm.relatedBookName"
|
||||
placeholder="请输入关联的绘本名称(可选)"
|
||||
allow-clear
|
||||
/>
|
||||
<a-input v-model:value="createForm.relatedBookName" placeholder="请输入关联的绘本名称(可选)" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item label="任务时间" required>
|
||||
<a-range-picker
|
||||
v-model:value="createForm.dateRange as any"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
<a-range-picker v-model:value="createForm.dateRange as any" style="width: 100%;" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 完成情况详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="completionModalVisible"
|
||||
:title="`完成情况 - ${selectedTask?.title || ''}`"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
>
|
||||
<a-modal v-model:open="completionModalVisible" :title="`完成情况 - ${selectedTask?.title || ''}`" width="800px"
|
||||
:footer="null">
|
||||
<div class="completion-header">
|
||||
<a-space>
|
||||
<a-select
|
||||
v-model:value="completionFilter.status"
|
||||
placeholder="筛选状态"
|
||||
style="width: 120px;"
|
||||
allowClear
|
||||
@change="loadCompletions"
|
||||
>
|
||||
<a-select v-model:value="completionFilter.status" placeholder="筛选状态" style="width: 120px;" allowClear
|
||||
@change="loadCompletions">
|
||||
<a-select-option value="PENDING">待提交</a-select-option>
|
||||
<a-select-option value="SUBMITTED">已提交</a-select-option>
|
||||
<a-select-option value="REVIEWED">已评价</a-select-option>
|
||||
@ -308,7 +242,9 @@
|
||||
</a-avatar>
|
||||
<div class="student-detail">
|
||||
<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 class="completion-status">
|
||||
@ -331,7 +267,10 @@
|
||||
<div v-if="completion.parentFeedback || completion.feedback?.comment" class="parent-feedback">
|
||||
<MessageOutlined class="feedback-icon" />
|
||||
<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>
|
||||
</div>
|
||||
<span v-else class="no-feedback">暂无反馈</span>
|
||||
@ -351,38 +290,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="empty-state" v-else style="padding: 40px 0;">
|
||||
<InboxOutlined style="font-size: 48px; color: #d9d9d9;" />
|
||||
<div class="completion-empty-state" v-else>
|
||||
<InboxOutlined class="empty-icon" />
|
||||
<p>暂无完成记录</p>
|
||||
</div>
|
||||
</a-spin>
|
||||
|
||||
<div class="pagination-section" v-if="completionTotal > completionPageSize" style="margin-top: 16px;">
|
||||
<a-pagination
|
||||
v-model:current="completionPage"
|
||||
:total="completionTotal"
|
||||
:page-size="completionPageSize"
|
||||
size="small"
|
||||
@change="onCompletionPageChange"
|
||||
/>
|
||||
<a-pagination v-model:current="completionPage" :total="completionTotal" :page-size="completionPageSize"
|
||||
size="small" @change="onCompletionPageChange" />
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- 评价弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="feedbackModalVisible"
|
||||
<a-modal v-model:open="feedbackModalVisible"
|
||||
:title="selectedCompletionForFeedback ? `评价 - ${selectedCompletionForFeedback.student?.name || ''}` : '评价'"
|
||||
width="600px"
|
||||
:confirm-loading="submittingFeedback"
|
||||
@ok="submitFeedback"
|
||||
okText="提交评价"
|
||||
cancelText="取消"
|
||||
>
|
||||
width="600px" :confirm-loading="submittingFeedback" @ok="submitFeedback" okText="提交评价" cancelText="取消">
|
||||
<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>
|
||||
<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;" />
|
||||
<span>{{ selectedCompletionForFeedback.photos.length }} 张照片</span>
|
||||
</div>
|
||||
@ -420,13 +350,8 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="评语">
|
||||
<a-textarea
|
||||
v-model:value="feedbackForm.comment"
|
||||
placeholder="请输入评语(选填,最多500字)"
|
||||
:rows="4"
|
||||
:maxlength="500"
|
||||
show-count
|
||||
/>
|
||||
<a-textarea v-model:value="feedbackForm.comment" placeholder="请输入评语(选填,最多500字)" :rows="4" :maxlength="500"
|
||||
show-count />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
@ -461,6 +386,7 @@ import {
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
getTeacherTasks,
|
||||
getTeacherTask,
|
||||
createTeacherTask,
|
||||
updateTeacherTask,
|
||||
deleteTeacherTask,
|
||||
@ -694,21 +620,32 @@ const getTaskTypeText = (type: string) => {
|
||||
return texts[type] || type;
|
||||
};
|
||||
|
||||
// 打开编辑弹窗
|
||||
const openEditModal = (task: TeacherTask) => {
|
||||
// 打开编辑弹窗:通过详情接口获取完整数据,回显与新增表单一致
|
||||
const openEditModal = async (task: TeacherTask) => {
|
||||
isEdit.value = true;
|
||||
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;
|
||||
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 {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
|
||||
@ -51,8 +51,9 @@ public class TeacherTaskController {
|
||||
@Operation(summary = "根据 ID 获取任务")
|
||||
@GetMapping("/{id}")
|
||||
public Result<TaskResponse> getTask(@PathVariable Long id) {
|
||||
Task task = taskService.getTaskById(id);
|
||||
return Result.success(taskMapper.toVO(task));
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
TaskResponse detail = taskService.getTaskDetailWithTargets(id, tenantId);
|
||||
return Result.success(detail);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取任务分页列表")
|
||||
@ -62,9 +63,11 @@ public class TeacherTaskController {
|
||||
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String type,
|
||||
@RequestParam(required = false) String taskType,
|
||||
@RequestParam(required = false) String status) {
|
||||
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());
|
||||
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) String type,
|
||||
@RequestParam(required = false) String keyword) {
|
||||
// 教师查询其关联学校(tenant)下的所有模板,包括学校端建立的阅读模版
|
||||
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());
|
||||
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
|
||||
}
|
||||
|
||||
@ -21,18 +21,27 @@ public class TaskCreateRequest {
|
||||
@Schema(description = "任务类型:reading-阅读,homework-作业,activity-活动")
|
||||
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 = "开始日期")
|
||||
private LocalDate startDate;
|
||||
|
||||
@Schema(description = "截止日期")
|
||||
private LocalDate dueDate;
|
||||
|
||||
@Schema(description = "截止日期(前端兼容 endDate)")
|
||||
private LocalDate endDate;
|
||||
|
||||
@Schema(description = "附件(JSON 数组)")
|
||||
private String attachments;
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "任务更新请求")
|
||||
@ -18,6 +19,18 @@ public class TaskUpdateRequest {
|
||||
@Schema(description = "任务类型")
|
||||
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 = "开始日期")
|
||||
private LocalDate startDate;
|
||||
|
||||
@ -30,4 +43,10 @@ public class TaskUpdateRequest {
|
||||
@Schema(description = "附件(JSON 数组)")
|
||||
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 = "附件")
|
||||
private String attachments;
|
||||
|
||||
@Schema(description = "目标类型:CLASS-班级,STUDENT-学生")
|
||||
private String targetType;
|
||||
|
||||
@Schema(description = "目标 IDs(班级或学生)")
|
||||
private java.util.List<Long> targetIds;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
|
||||
@ -71,4 +71,9 @@ public interface TaskService extends com.baomidou.mybatisplus.extension.service.
|
||||
*/
|
||||
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.util.StringUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -51,13 +52,16 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
|
||||
task.setTenantId(tenantId);
|
||||
task.setTitle(request.getTitle());
|
||||
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.setCourseId(request.getCourseId());
|
||||
Long courseId = request.getCourseId() != null ? request.getCourseId() : request.getRelatedCourseId();
|
||||
task.setCourseId(courseId);
|
||||
task.setCreatorId(creatorId);
|
||||
task.setCreatorRole(creatorRole);
|
||||
task.setStartDate(request.getStartDate());
|
||||
task.setDueDate(request.getDueDate());
|
||||
LocalDate dueDate = request.getDueDate() != null ? request.getDueDate() : request.getEndDate();
|
||||
task.setDueDate(dueDate);
|
||||
task.setStatus("pending");
|
||||
task.setAttachments(request.getAttachments());
|
||||
|
||||
@ -89,8 +93,16 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
|
||||
if (request.getDescription() != null) {
|
||||
task.setDescription(request.getDescription());
|
||||
}
|
||||
if (request.getType() != null) {
|
||||
task.setType(request.getType());
|
||||
String taskType = request.getType() != null ? request.getType() : request.getTaskType();
|
||||
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) {
|
||||
task.setStartDate(request.getStartDate());
|
||||
@ -106,6 +118,22 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, 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);
|
||||
return task;
|
||||
}
|
||||
@ -518,10 +546,22 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
|
||||
|
||||
@Override
|
||||
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);
|
||||
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()
|
||||
.id(task.getId())
|
||||
@ -537,6 +577,8 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
|
||||
.dueDate(task.getDueDate())
|
||||
.status(task.getStatus())
|
||||
.attachments(task.getAttachments())
|
||||
.targetType(targetType)
|
||||
.targetIds(targetIds)
|
||||
.createdAt(task.getCreatedAt())
|
||||
.updatedAt(task.getUpdatedAt())
|
||||
.build();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user