fix(阅读任务): 前后端字段对齐与列表时间展示;教师端时间筛选与 dateRange 修复

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-24 15:56:53 +08:00
parent b551af1355
commit aa44f6262b
8 changed files with 163 additions and 31 deletions

View File

@ -1170,11 +1170,22 @@ export interface SchoolTaskQueryParams {
// ========== 新 API只读接口 ========== // ========== 新 API只读接口 ==========
/** 与后端 TaskResponse 对齐JSON 中 taskType/endDate兼容历史 type/dueDate */
function normalizeSchoolTask(raw: any): SchoolTask {
if (!raw) return raw;
return {
...raw,
taskType: (raw.taskType ?? raw.type) as SchoolTask['taskType'],
endDate: (raw.endDate ?? raw.dueDate ?? '') as string,
startDate: (raw.startDate ?? '') as string,
};
}
// 获取学校端任务列表(只读,多维度筛选) // 获取学校端任务列表(只读,多维度筛选)
export const getReadingTaskList = (params?: SchoolTaskQueryParams) => export const getReadingTaskList = (params?: SchoolTaskQueryParams) =>
http.get<{ list: SchoolTask[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/reading-tasks', { params }) http.get<{ list: SchoolTask[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/reading-tasks', { params })
.then(res => ({ .then(res => ({
items: res.list || [], items: (res.list || []).map(normalizeSchoolTask),
total: res.total || 0, total: res.total || 0,
pageNum: res.pageNum || 1, pageNum: res.pageNum || 1,
pageSize: res.pageSize || 10, pageSize: res.pageSize || 10,
@ -1183,7 +1194,7 @@ export const getReadingTaskList = (params?: SchoolTaskQueryParams) =>
// 获取任务详情(只读) // 获取任务详情(只读)
export const getReadingTaskDetail = (taskId: number) => export const getReadingTaskDetail = (taskId: number) =>
http.get<SchoolTask>(`/v1/school/reading-tasks/${taskId}`); http.get<SchoolTask>(`/v1/school/reading-tasks/${taskId}`).then(normalizeSchoolTask);
// 获取任务完成情况列表 // 获取任务完成情况列表
export const getReadingTaskCompletions = (taskId: number, params?: { export const getReadingTaskCompletions = (taskId: number, params?: {

View File

@ -761,18 +761,38 @@ export interface UpdateTaskCompletionDto {
feedback?: string; feedback?: string;
} }
/** 与后端 TaskResponse 对齐taskType/endDate兼容 type/dueDate */
function normalizeTeacherTask(raw: any): TeacherTask {
if (!raw) return raw;
return {
...raw,
taskType: (raw.taskType ?? raw.type) as TeacherTask['taskType'],
endDate: (raw.endDate ?? raw.dueDate ?? '') as string,
startDate: (raw.startDate ?? '') as string,
};
}
// 获取教师任务列表 // 获取教师任务列表
export const getTeacherTasks = (params?: { pageNum?: number; pageSize?: number; keyword?: string; type?: string; status?: string }) => export const getTeacherTasks = (params?: {
pageNum?: number;
pageSize?: number;
keyword?: string;
type?: string;
taskType?: string;
status?: string;
startDate?: string;
endDate?: string;
}) =>
http.get<{ list: any[]; total: number; pageNum: number; pageSize: number }>('/v1/teacher/tasks', { params }) http.get<{ list: any[]; total: number; pageNum: number; pageSize: number }>('/v1/teacher/tasks', { params })
.then(res => ({ .then(res => ({
items: res.list || [], items: (res.list || []).map(normalizeTeacherTask),
total: res.total || 0, total: res.total || 0,
page: res.pageNum || 1, page: res.pageNum || 1,
pageSize: res.pageSize || 10, pageSize: res.pageSize || 10,
})); }));
export const getTeacherTask = (id: number) => export const getTeacherTask = (id: number) =>
http.get(`/v1/teacher/tasks/${id}`) as any; http.get(`/v1/teacher/tasks/${id}`).then(normalizeTeacherTask) as Promise<TeacherTask>;
// 获取任务完成情况列表(新版,支持分页和状态筛选) // 获取任务完成情况列表(新版,支持分页和状态筛选)
export const getTeacherTaskCompletions = (taskId: number, params?: { export const getTeacherTaskCompletions = (taskId: number, params?: {

View File

@ -132,7 +132,7 @@
</span> </span>
<span> <span>
<CalendarOutlined /> <CalendarOutlined />
{{ formatDate(task.startDate) }} - {{ formatDate(task.endDate) }} {{ formatTaskRange(task) }}
</span> </span>
<span> <span>
<TeamOutlined /> {{ task.targetCount || 0 }} 个目标 <TeamOutlined /> {{ task.targetCount || 0 }} 个目标
@ -581,7 +581,24 @@ const feedbackResultTexts: Record<string, string> = {
const getFeedbackResultColor = (result: string) => feedbackResultColors[result] || 'default'; const getFeedbackResultColor = (result: string) => feedbackResultColors[result] || 'default';
const getFeedbackResultText = (result: string) => feedbackResultTexts[result] || result; const getFeedbackResultText = (result: string) => feedbackResultTexts[result] || result;
const formatDate = (date?: string) => date ? dayjs(date).format('YYYY-MM-DD') : '-'; const formatDate = (date?: string | number[] | null) => {
if (date == null || date === '') return '-';
if (Array.isArray(date) && date.length >= 3) {
const [y, m, d] = date;
return dayjs(`${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`).format('YYYY-MM-DD');
}
return dayjs(date as string).format('YYYY-MM-DD');
};
/** 列表展示:与后端 startDate/endDate 对齐,避免仅一端有值时出现「日期 --」 */
const formatTaskRange = (task: SchoolTask) => {
const s = formatDate(task.startDate as any);
const e = formatDate((task as any).endDate ?? (task as any).dueDate);
if (e === '-') return s;
if (s === '-') return e;
return `${s}${e}`;
};
const formatDateTime = (date?: string) => date ? dayjs(date).format('YYYY-MM-DD HH:mm') : '-'; const formatDateTime = (date?: string) => date ? dayjs(date).format('YYYY-MM-DD HH:mm') : '-';
// //

View File

@ -62,6 +62,13 @@
</a-select> </a-select>
<a-input-search v-model:value="filters.keyword" placeholder="搜索任务标题" style="width: 200px;" @search="loadTasks" <a-input-search v-model:value="filters.keyword" placeholder="搜索任务标题" style="width: 200px;" @search="loadTasks"
allow-clear /> allow-clear />
<a-range-picker
v-model:value="dateRange as any"
style="width: 260px"
:placeholder="['开始日期', '结束日期']"
allow-clear
@change="onDateRangeChange"
/>
</a-space> </a-space>
</div> </div>
@ -84,7 +91,7 @@
</span> </span>
<span> <span>
<CalendarOutlined /> <CalendarOutlined />
{{ formatDate(task.startDate) }} - {{ formatDate(task.endDate) }} {{ formatTaskRange(task) }}
</span> </span>
<span> <span>
<TeamOutlined /> {{ task.targetCount || 0 }} 个目标 <TeamOutlined /> {{ task.targetCount || 0 }} 个目标
@ -439,6 +446,14 @@ const filters = reactive({
keyword: undefined as string | undefined, keyword: undefined as string | undefined,
}); });
/** 列表筛选:任务时间范围(与 createForm.dateRange 区分,勿混用) */
const dateRange = ref<[Dayjs, Dayjs] | null>(null);
const onDateRangeChange = () => {
currentPage.value = 1;
loadTasks();
};
// //
const stats = computed(() => { const stats = computed(() => {
const all = tasks.value; const all = tasks.value;
@ -521,7 +536,22 @@ const getTypeText = (type: string) => typeMap[type]?.text || type;
const getTypeColor = (type: string) => typeMap[type]?.color || 'default'; const getTypeColor = (type: string) => typeMap[type]?.color || 'default';
const getStatusText = (status: string) => statusMap[status]?.text || status; const getStatusText = (status: string) => statusMap[status]?.text || status;
const getStatusColor = (status: string) => statusMap[status]?.color || 'default'; const getStatusColor = (status: string) => statusMap[status]?.color || 'default';
const formatDate = (date?: string) => date ? dayjs(date).format('YYYY-MM-DD') : '-'; const formatDate = (date?: string | number[] | null) => {
if (date == null || date === '') return '-';
if (Array.isArray(date) && date.length >= 3) {
const [y, m, d] = date;
return dayjs(`${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`).format('YYYY-MM-DD');
}
return dayjs(date as string).format('YYYY-MM-DD');
};
const formatTaskRange = (task: TeacherTask) => {
const s = formatDate(task.startDate as any);
const e = formatDate((task as any).endDate ?? (task as any).dueDate);
if (e === '-') return s;
if (s === '-') return e;
return `${s}${e}`;
};
const getCompletionRate = (task: TeacherTask) => { const getCompletionRate = (task: TeacherTask) => {
if (!task.completionCount || !task.targetCount) return 0; if (!task.completionCount || !task.targetCount) return 0;
@ -546,11 +576,16 @@ const filterCourseOption = (input: string, option: any) => {
const loadTasks = async () => { const loadTasks = async () => {
loading.value = true; loading.value = true;
try { try {
const data = await getTeacherTasks({ const params: Parameters<typeof getTeacherTasks>[0] = {
pageNum: currentPage.value, pageNum: currentPage.value,
pageSize: pageSize.value, pageSize: pageSize.value,
...filters, ...filters,
}); };
if (dateRange.value?.[0] && dateRange.value?.[1]) {
params.startDate = dateRange.value[0].format('YYYY-MM-DD');
params.endDate = dateRange.value[1].format('YYYY-MM-DD');
}
const data = await getTeacherTasks(params);
tasks.value = data.items || []; tasks.value = data.items || [];
total.value = data.total || 0; total.value = data.total || 0;
} catch (error: any) { } catch (error: any) {

View File

@ -69,11 +69,13 @@ public class TeacherTaskController {
@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 taskType,
@RequestParam(required = false) String status) { @RequestParam(required = false) String status,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
Long teacherId = SecurityUtils.getCurrentUserId(); Long teacherId = SecurityUtils.getCurrentUserId();
String typeFilter = (type != null && !type.isEmpty()) ? type : taskType; String typeFilter = (type != null && !type.isEmpty()) ? type : taskType;
Page<Task> page = taskService.getTaskPage(tenantId, pageNum, pageSize, keyword, typeFilter, status, teacherId); Page<Task> page = taskService.getTaskPage(tenantId, pageNum, pageSize, keyword, typeFilter, status, teacherId, startDate, endDate);
List<TaskResponse> voList = taskMapper.toVO(page.getRecords()); List<TaskResponse> voList = taskMapper.toVO(page.getRecords());
for (int i = 0; i < voList.size(); i++) { for (int i = 0; i < voList.size(); i++) {
taskService.enrichTaskResponseWithStats(voList.get(i), page.getRecords().get(i).getId()); taskService.enrichTaskResponseWithStats(voList.get(i), page.getRecords().get(i).getId());

View File

@ -1,5 +1,7 @@
package com.reading.platform.dto.response; package com.reading.platform.dto.response;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@ -29,6 +31,8 @@ public class TaskResponse {
private String description; private String description;
@Schema(description = "任务类型") @Schema(description = "任务类型")
@JsonProperty("taskType")
@JsonAlias({"type"})
private String type; private String type;
@Schema(description = "关联绘本名称") @Schema(description = "关联绘本名称")
@ -47,6 +51,8 @@ public class TaskResponse {
private LocalDate startDate; private LocalDate startDate;
@Schema(description = "截止日期") @Schema(description = "截止日期")
@JsonProperty("endDate")
@JsonAlias({"dueDate"})
private LocalDate dueDate; private LocalDate dueDate;
@Schema(description = "状态") @Schema(description = "状态")

View File

@ -35,6 +35,11 @@ public interface TaskService extends com.baomidou.mybatisplus.extension.service.
*/ */
Page<Task> getTaskPage(Long tenantId, Integer pageNum, Integer pageSize, String keyword, String type, String status, Long creatorId); Page<Task> getTaskPage(Long tenantId, Integer pageNum, Integer pageSize, String keyword, String type, String status, Long creatorId);
/**
* 分页查询任务支持按创建人任务时间范围筛选与学校端时间交集逻辑一致
*/
Page<Task> getTaskPage(Long tenantId, Integer pageNum, Integer pageSize, String keyword, String type, String status, Long creatorId, String startDate, String endDate);
Page<Task> getTasksByStudentId(Long studentId, Integer pageNum, Integer pageSize, String status); Page<Task> getTasksByStudentId(Long studentId, Integer pageNum, Integer pageSize, String status);
/** /**

View File

@ -173,11 +173,16 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
@Override @Override
public Page<Task> getTaskPage(Long tenantId, Integer pageNum, Integer pageSize, String keyword, String type, String status) { public Page<Task> getTaskPage(Long tenantId, Integer pageNum, Integer pageSize, String keyword, String type, String status) {
return getTaskPage(tenantId, pageNum, pageSize, keyword, type, status, null); return getTaskPage(tenantId, pageNum, pageSize, keyword, type, status, null, null, null);
} }
@Override @Override
public Page<Task> getTaskPage(Long tenantId, Integer pageNum, Integer pageSize, String keyword, String type, String status, Long creatorId) { public Page<Task> getTaskPage(Long tenantId, Integer pageNum, Integer pageSize, String keyword, String type, String status, Long creatorId) {
return getTaskPage(tenantId, pageNum, pageSize, keyword, type, status, creatorId, null, null);
}
@Override
public Page<Task> getTaskPage(Long tenantId, Integer pageNum, Integer pageSize, String keyword, String type, String status, Long creatorId, String startDate, String endDate) {
Page<Task> page = new Page<>(pageNum, pageSize); Page<Task> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
@ -195,6 +200,33 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
if (StringUtils.hasText(status)) { if (StringUtils.hasText(status)) {
wrapper.eq(Task::getStatus, status); wrapper.eq(Task::getStatus, status);
} }
// 任务时间筛选与学校端 getSchoolTaskList 一致任务区间与查询范围有交集
if (StringUtils.hasText(startDate) && StringUtils.hasText(endDate)) {
try {
LocalDate rangeStart = LocalDate.parse(startDate);
LocalDate rangeEnd = LocalDate.parse(endDate);
wrapper.le(Task::getStartDate, rangeEnd);
wrapper.and(w -> w.ge(Task::getDueDate, rangeStart).or().isNull(Task::getDueDate));
} catch (Exception e) {
log.warn("解析日期参数失败 startDate={}, endDate={}", startDate, endDate, e);
}
} else if (StringUtils.hasText(startDate)) {
try {
LocalDate rangeStart = LocalDate.parse(startDate);
wrapper.ge(Task::getDueDate, rangeStart);
} catch (Exception e) {
log.warn("解析 startDate 参数失败: {}", startDate, e);
}
} else if (StringUtils.hasText(endDate)) {
try {
LocalDate rangeEnd = LocalDate.parse(endDate);
wrapper.le(Task::getStartDate, rangeEnd);
} catch (Exception e) {
log.warn("解析 endDate 参数失败: {}", endDate, e);
}
}
wrapper.orderByDesc(Task::getCreatedAt); wrapper.orderByDesc(Task::getCreatedAt);
return taskMapper.selectPage(page, wrapper); return taskMapper.selectPage(page, wrapper);
@ -758,23 +790,27 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task>
// 转换为响应对象 // 转换为响应对象
// TODO: 使用 TaskMapper 转换 // TODO: 使用 TaskMapper 转换
List<TaskResponse> responses = taskPage.getRecords().stream() List<TaskResponse> responses = taskPage.getRecords().stream()
.map(task -> TaskResponse.builder() .map(task -> {
.id(task.getId()) TaskResponse r = TaskResponse.builder()
.tenantId(task.getTenantId()) .id(task.getId())
.title(task.getTitle()) .tenantId(task.getTenantId())
.description(task.getDescription()) .title(task.getTitle())
.type(task.getType()) .description(task.getDescription())
.relatedBookName(task.getRelatedBookName()) .type(task.getType())
.courseId(task.getCourseId()) .relatedBookName(task.getRelatedBookName())
.creatorId(task.getCreatorId()) .courseId(task.getCourseId())
.creatorRole(task.getCreatorRole()) .creatorId(task.getCreatorId())
.startDate(task.getStartDate()) .creatorRole(task.getCreatorRole())
.dueDate(task.getDueDate()) .startDate(task.getStartDate())
.status(task.getStatus()) .dueDate(task.getDueDate())
.attachments(task.getAttachments()) .status(task.getStatus())
.createdAt(task.getCreatedAt()) .attachments(task.getAttachments())
.updatedAt(task.getUpdatedAt()) .createdAt(task.getCreatedAt())
.build()) .updatedAt(task.getUpdatedAt())
.build();
enrichTaskResponseWithStats(r, task.getId());
return r;
})
.toList(); .toList();
return PageResult.of(responses, taskPage.getTotal(), taskPage.getCurrent(), taskPage.getSize()); return PageResult.of(responses, taskPage.getTotal(), taskPage.getCurrent(), taskPage.getSize());