有视频
@@ -323,9 +324,11 @@
style="margin-bottom: 24px; padding: 16px; background: #fafafa; border-radius: 8px;">
提交内容
-
-
{{ selectedCompletionForFeedback.photos.length }} 张照片
+ class="review-photos" style="margin-bottom: 12px;">
+
+
![照片]()
+
@@ -366,6 +369,12 @@
+
+
+
@@ -900,6 +909,14 @@ const onCompletionPageChange = (page: number) => {
loadCompletions();
};
+// 图片预览
+const imagePreviewVisible = ref(false);
+const previewImageUrl = ref('');
+const previewImage = (url: string) => {
+ previewImageUrl.value = url;
+ imagePreviewVisible.value = true;
+};
+
onMounted(() => {
loadTasks();
loadOptions();
@@ -1189,5 +1206,46 @@ onMounted(() => {
color: #999;
text-align: right;
}
+
+ .completion-content {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 8px;
+
+ .content-photos {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+
+ .photo-thumb {
+ width: 48px;
+ height: 48px;
+ object-fit: cover;
+ border-radius: 6px;
+ cursor: pointer;
+ }
+ }
+
+ .content-preview {
+ font-size: 13px;
+ color: #666;
+ }
+ }
+}
+
+// 评价弹窗 - 提交内容照片网格
+.review-photos .photos-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+
+ img {
+ width: 100px;
+ height: 100px;
+ object-fit: cover;
+ border-radius: 8px;
+ cursor: pointer;
+ }
}
diff --git a/reading-platform-java/src/main/java/com/reading/platform/mapper/TaskFeedbackMapper.java b/reading-platform-java/src/main/java/com/reading/platform/mapper/TaskFeedbackMapper.java
index 9449296..168ca2d 100644
--- a/reading-platform-java/src/main/java/com/reading/platform/mapper/TaskFeedbackMapper.java
+++ b/reading-platform-java/src/main/java/com/reading/platform/mapper/TaskFeedbackMapper.java
@@ -17,7 +17,7 @@ public interface TaskFeedbackMapper extends BaseMapper
{
/**
* 根据完成记录ID查询评价
*/
- @Select("SELECT * FROM task_feedback WHERE completion_id = #{completionId} AND deleted_at IS NULL")
+ @Select("SELECT * FROM task_feedback WHERE completion_id = #{completionId} AND deleted = 0")
Optional findByCompletionId(@Param("completionId") Long completionId);
/**
@@ -25,7 +25,7 @@ public interface TaskFeedbackMapper extends BaseMapper {
*/
@Select("SELECT tf.* FROM task_feedback tf " +
"INNER JOIN task_completion tc ON tf.completion_id = tc.id " +
- "WHERE tf.task_id = #{taskId} AND tc.student_id = #{studentId} AND tf.deleted_at IS NULL")
+ "WHERE tf.task_id = #{taskId} AND tc.student_id = #{studentId} AND tf.deleted = 0")
Optional findByTaskIdAndStudentId(@Param("taskId") Long taskId, @Param("studentId") Long studentId);
}
diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/TaskServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/TaskServiceImpl.java
index 989b62a..7f76378 100644
--- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/TaskServiceImpl.java
+++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/TaskServiceImpl.java
@@ -17,6 +17,7 @@ import com.reading.platform.dto.response.TaskResponse;
import com.reading.platform.dto.response.TaskWithCompletionResponse;
import com.reading.platform.entity.*;
import com.reading.platform.mapper.CoursePackageMapper;
+import com.reading.platform.mapper.ParentStudentMapper;
import com.reading.platform.mapper.TaskCompletionMapper;
import com.reading.platform.mapper.TaskMapper;
import com.reading.platform.mapper.TaskTargetMapper;
@@ -45,6 +46,7 @@ public class TaskServiceImpl extends ServiceImpl
private final TaskTargetMapper taskTargetMapper;
private final TaskCompletionMapper taskCompletionMapper;
private final CoursePackageMapper coursePackageMapper;
+ private final ParentStudentMapper parentStudentMapper;
private final StudentService studentService;
private final ClassService classService;
private final TaskFeedbackService taskFeedbackService;
@@ -392,6 +394,11 @@ public class TaskServiceImpl extends ServiceImpl
// 验证任务存在且属于该租户
Task task = getTaskByIdWithTenantCheck(taskId, tenantId);
+ // status=PENDING 待提交:返回参与任务且与家长关联、尚未提交的学生(无 task_completion 记录)
+ if ("PENDING".equalsIgnoreCase(status)) {
+ return getPendingCompletions(taskId, tenantId, pageNum, pageSize, task);
+ }
+
Page page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(TaskCompletion::getTaskId, taskId);
@@ -410,6 +417,105 @@ public class TaskServiceImpl extends ServiceImpl
return PageResult.of(responses, completionPage.getTotal(), completionPage.getCurrent(), completionPage.getSize());
}
+ /**
+ * 获取待提交学生列表:参与任务(班级或指定学生)且与家长关联、尚未提交的学生
+ */
+ private PageResult getPendingCompletions(Long taskId, Long tenantId, Integer pageNum, Integer pageSize, Task task) {
+ List targetStudentIds = getTargetStudentIdsForTask(taskId);
+ if (targetStudentIds.isEmpty()) {
+ return PageResult.empty();
+ }
+
+ // 仅保留与家长关联的学生(parent_student 有记录)
+ List studentIdsWithParent = parentStudentMapper.selectList(
+ new LambdaQueryWrapper()
+ .in(ParentStudent::getStudentId, targetStudentIds)
+ ).stream().map(ParentStudent::getStudentId).distinct().toList();
+
+ if (studentIdsWithParent.isEmpty()) {
+ return PageResult.empty();
+ }
+
+ // 排除已提交或已评价的学生(已有 task_completion 且 status 非 PENDING)
+ List existing = taskCompletionMapper.selectList(
+ new LambdaQueryWrapper()
+ .eq(TaskCompletion::getTaskId, taskId)
+ .in(TaskCompletion::getStudentId, studentIdsWithParent)
+ .in(TaskCompletion::getStatus, "SUBMITTED", "REVIEWED", "submitted", "reviewed")
+ );
+ List submittedStudentIds = existing.stream().map(TaskCompletion::getStudentId).distinct().toList();
+ List pendingStudentIds = studentIdsWithParent.stream()
+ .filter(id -> !submittedStudentIds.contains(id))
+ .toList();
+
+ // 分页
+ int from = (pageNum - 1) * pageSize;
+ int to = Math.min(from + pageSize, pendingStudentIds.size());
+ List pagedIds = from < pendingStudentIds.size() ? pendingStudentIds.subList(from, to) : List.of();
+
+ List responses = pagedIds.stream()
+ .map(studentId -> buildPendingCompletionResponse(taskId, task.getTitle(), studentId))
+ .toList();
+
+ return PageResult.of(responses, (long) pendingStudentIds.size(), (long) pageNum, (long) pageSize);
+ }
+
+ /**
+ * 获取任务目标学生 ID 列表(班级学生或指定学生)
+ */
+ private List getTargetStudentIdsForTask(Long taskId) {
+ List targets = taskTargetMapper.selectList(
+ new LambdaQueryWrapper().eq(TaskTarget::getTaskId, taskId)
+ );
+ if (targets.isEmpty()) {
+ return List.of();
+ }
+
+ List studentIds = new ArrayList<>();
+ for (TaskTarget t : targets) {
+ String tt = t.getTargetType() != null ? t.getTargetType().toUpperCase() : "";
+ if ("CLASS".equals(tt) || "class".equals(t.getTargetType())) {
+ studentIds.addAll(studentService.getStudentListByClassId(t.getTargetId()).stream()
+ .map(Student::getId).toList());
+ } else if ("STUDENT".equals(tt) || "student".equals(t.getTargetType())) {
+ studentIds.add(t.getTargetId());
+ }
+ }
+ return studentIds.stream().distinct().toList();
+ }
+
+ /**
+ * 构建待提交的完成详情(无 task_completion 记录)
+ */
+ private TaskCompletionDetailResponse buildPendingCompletionResponse(Long taskId, String taskTitle, Long studentId) {
+ Student student = studentService.getById(studentId);
+ if (student == null) {
+ return null;
+ }
+ TaskCompletionDetailResponse.StudentInfo studentInfo = TaskCompletionDetailResponse.StudentInfo.builder()
+ .id(student.getId())
+ .name(student.getName())
+ .avatar(student.getAvatarUrl())
+ .gender(student.getGender())
+ .build();
+ Clazz clazz = classService.getPrimaryClassByStudentId(student.getId());
+ if (clazz != null) {
+ studentInfo.setClassInfo(TaskCompletionDetailResponse.ClassInfo.builder()
+ .id(clazz.getId())
+ .name(clazz.getName())
+ .grade(clazz.getGrade())
+ .build());
+ }
+ return TaskCompletionDetailResponse.builder()
+ .id(null)
+ .taskId(taskId)
+ .taskTitle(taskTitle)
+ .student(studentInfo)
+ .status("PENDING")
+ .statusText("待提交")
+ .build();
+ }
+
@Override
public TaskCompletionDetailResponse getCompletionDetail(Long completionId, Long tenantId) {
TaskCompletion completion = taskCompletionMapper.selectById(completionId);
diff --git a/reading-platform-java/src/main/resources/db/migration/V47__add_task_feedback_audit_columns.sql b/reading-platform-java/src/main/resources/db/migration/V47__add_task_feedback_audit_columns.sql
new file mode 100644
index 0000000..4138b1e
--- /dev/null
+++ b/reading-platform-java/src/main/resources/db/migration/V47__add_task_feedback_audit_columns.sql
@@ -0,0 +1,21 @@
+-- =====================================================
+-- 修复 task_feedback 表与 BaseEntity 字段对齐
+-- 版本: V47
+-- 日期: 2026-03-20
+-- 说明: 添加 create_by、update_by,将 deleted_at 改为 deleted
+-- 解决 POST /api/v1/teacher/tasks/completions/{id}/feedback 报错
+-- "Unknown column 'create_by' in 'field list'"
+-- =====================================================
+
+-- 1. 添加 create_by、update_by 审计字段
+ALTER TABLE task_feedback ADD COLUMN create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人';
+ALTER TABLE task_feedback ADD COLUMN update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人';
+
+-- 2. 添加 deleted 字段(与 BaseEntity 一致)
+ALTER TABLE task_feedback ADD COLUMN deleted TINYINT DEFAULT 0 COMMENT '删除标识(0-未删除,1-已删除)';
+
+-- 3. 迁移 deleted_at 数据到 deleted
+UPDATE task_feedback SET deleted = 1 WHERE deleted_at IS NOT NULL;
+
+-- 4. 删除 deleted_at 列
+ALTER TABLE task_feedback DROP COLUMN deleted_at;