feat(student-plan): 实现学生学案查询功能

- 新增FindStudentPlansReqVO和FindStudentPlansRspVO定义请求和响应数据结构
- 新增LessonPlanItem用于描述单个学案项
- StudentLessonPlansDO模型新增isFinished属性
- 扩展StudentLessonPlansDOMapper,添加分页及按姓名查询学生学案列表方法及统计总数方法
- 扩展LessonPlansDOMapper,新增按学案ID列表批量查询方法
- 实现StudentLessonPlansService及LessonPlansService接口对应查询方法
- 新增StudentLessonPlansController,提供学生学案分页查询接口
- 在前端LearningPlan.vue添加学案查询界面及分页、搜索功能
- 封装studentLessonPlans接口axios方法,支持分页按姓名查询学生学案数据
- 添加单元测试更新验证数据库查询正确性
This commit is contained in:
lbw
2025-12-17 15:23:40 +08:00
parent 49cd146bc3
commit dbe7312633
16 changed files with 335 additions and 6 deletions

View File

@@ -0,0 +1,77 @@
package com.yinlihupo.enlish.service.controller;
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO;
import com.yinlihupo.enlish.service.model.bo.StudentDetail;
import com.yinlihupo.enlish.service.model.vo.plan.FindStudentPlansReqVO;
import com.yinlihupo.enlish.service.model.vo.plan.FindStudentPlansRspVO;
import com.yinlihupo.enlish.service.model.vo.plan.LessonPlanItem;
import com.yinlihupo.enlish.service.service.LessonPlansService;
import com.yinlihupo.enlish.service.service.StudentLessonPlansService;
import com.yinlihupo.enlish.service.service.StudentService;
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
import com.yinlihupo.framework.common.response.PageResponse;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RequestMapping("/studentLessonPlans/")
@RestController
public class StudentLessonPlansController {
@Resource
private StudentLessonPlansService studentLessonPlansService;
@Resource
private StudentService studentService;
@Resource
private LessonPlansService lessonPlansService;
@PostMapping("/list")
@ApiOperationLog(description = "查询学生学案")
public PageResponse<FindStudentPlansRspVO> findStudentPlans(@RequestBody FindStudentPlansReqVO findStudentPlansReqVO) {
Integer studentLessonPlanTotal = studentLessonPlansService.findStudentLessonPlanTotal();
String name = findStudentPlansReqVO.getName();
Integer page = findStudentPlansReqVO.getPage();
Integer size = findStudentPlansReqVO.getSize();
List<StudentLessonPlansDO> studentLessonPlansDOListPageSize = studentLessonPlansService.findStudentLessonPlansDOList(page, size, name);
Map<Integer, List<StudentLessonPlansDO>> studentId2StudentLessonPlansDOListMap = studentLessonPlansDOListPageSize.stream().collect(
Collectors.groupingBy(StudentLessonPlansDO::getStudentId)
);
List<Integer> planIds = studentLessonPlansDOListPageSize.stream().map(StudentLessonPlansDO::getPlanId).toList();
List<LessonPlansDO> lessonPlans = lessonPlansService.findLessonPlans(planIds);
Map<Integer, LessonPlansDO> id2LessonPlansDO = lessonPlans.stream().collect(Collectors.toMap(
LessonPlansDO::getId,
lessonPlansDO -> lessonPlansDO
));
List<StudentDetail> studentDetailList = studentService.getStudentDetailList(new ArrayList<>(studentId2StudentLessonPlansDOListMap.keySet()));
List<FindStudentPlansRspVO> findStudentPlansRspVOList = studentDetailList.stream().map(studentDetail -> {
List<StudentLessonPlansDO> studentLessonPlansDOList = studentId2StudentLessonPlansDOListMap.get(studentDetail.getId());
return FindStudentPlansRspVO.builder()
.name(studentDetail.getName())
.id(studentDetail.getId())
.classId(studentDetail.getClassId())
.gradeId(studentDetail.getGradeId())
.gradeName(studentDetail.getGradeName())
.className(studentDetail.getClassName())
.plans(studentLessonPlansDOList.stream().map(studentLessonPlansDO ->
LessonPlanItem.builder().title(id2LessonPlansDO.get(studentLessonPlansDO.getPlanId()).getTitle()).id(studentLessonPlansDO.getPlanId()).isFinished(studentLessonPlansDO.getIsFinished()).build()
).toList())
.build();
}).toList();
return PageResponse.success(findStudentPlansRspVOList, page, studentLessonPlanTotal, size);
}
}

View File

@@ -25,6 +25,8 @@ public class StudentLessonPlansDO {
private Integer totalCount; private Integer totalCount;
private Integer isFinished;
private String memorizedWordsJson; private String memorizedWordsJson;
private String unmemorizedWordsJson; private String unmemorizedWordsJson;

View File

@@ -1,10 +1,15 @@
package com.yinlihupo.enlish.service.domain.mapper; package com.yinlihupo.enlish.service.domain.mapper;
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO; import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface LessonPlansDOMapper { public interface LessonPlansDOMapper {
void insert(LessonPlansDO lessonPlansDO); void insert(LessonPlansDO lessonPlansDO);
LessonPlansDO selectById(Integer id); LessonPlansDO selectById(Integer id);
List<LessonPlansDO> findLessonPlansByStudentId(@Param("ids") List<Integer> ids);
} }

View File

@@ -1,8 +1,15 @@
package com.yinlihupo.enlish.service.domain.mapper; package com.yinlihupo.enlish.service.domain.mapper;
import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO; import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface StudentLessonPlansDOMapper { public interface StudentLessonPlansDOMapper {
void insert(StudentLessonPlansDO studentLessonPlansDO); void insert(StudentLessonPlansDO studentLessonPlansDO);
List<StudentLessonPlansDO> selectStudentLessonPlanList(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize, @Param("name") String name);
Integer selectStudentPlanTotal();
} }

View File

@@ -0,0 +1,17 @@
package com.yinlihupo.enlish.service.model.vo.plan;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class FindStudentPlansReqVO {
String name;
Integer page;
Integer size;
}

View File

@@ -0,0 +1,24 @@
package com.yinlihupo.enlish.service.model.vo.plan;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class FindStudentPlansRspVO {
private Integer id;
private String name;
private Integer classId;
private String className;
private Integer gradeId;
private String gradeName;
List<LessonPlanItem> plans;
}

View File

@@ -0,0 +1,19 @@
package com.yinlihupo.enlish.service.model.vo.plan;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class LessonPlanItem {
private Integer id;
private String title;
private Integer isFinished;
}

View File

@@ -1,5 +1,11 @@
package com.yinlihupo.enlish.service.service; package com.yinlihupo.enlish.service.service;
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
import java.util.List;
public interface LessonPlansService { public interface LessonPlansService {
void generateLessonPlans(Integer studentId, Integer unitId); void generateLessonPlans(Integer studentId, Integer unitId);
List<LessonPlansDO> findLessonPlans(List<Integer> ids);
} }

View File

@@ -0,0 +1,12 @@
package com.yinlihupo.enlish.service.service;
import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO;
import java.util.List;
public interface StudentLessonPlansService {
List<StudentLessonPlansDO> findStudentLessonPlansDOList(Integer page, Integer size, String name);
Integer findStudentLessonPlanTotal();
}

View File

@@ -8,7 +8,6 @@ import com.yinlihupo.enlish.service.utils.DifyArticleClient;
import com.yinlihupo.enlish.service.utils.StringToPlanMapUtil; import com.yinlihupo.enlish.service.utils.StringToPlanMapUtil;
import com.yinlihupo.framework.common.util.JsonUtils; import com.yinlihupo.framework.common.util.JsonUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@@ -150,6 +149,10 @@ public class LessonPlansServiceImpl implements LessonPlansService {
} }
} }
@Override
public List<LessonPlansDO> findLessonPlans(List<Integer> ids) {
return lessonPlansDOMapper.findLessonPlansByStudentId(ids);
}
private Map<String, Object> generateWeekendPlans(List<VocabularyBankDO> checkList, private Map<String, Object> generateWeekendPlans(List<VocabularyBankDO> checkList,

View File

@@ -0,0 +1,33 @@
package com.yinlihupo.enlish.service.service.plan;
import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO;
import com.yinlihupo.enlish.service.domain.mapper.StudentLessonPlansDOMapper;
import com.yinlihupo.enlish.service.service.StudentLessonPlansService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@Slf4j
public class StudentLessonPlansServiceImpl implements StudentLessonPlansService {
@Resource
private StudentLessonPlansDOMapper studentLessonPlansDOMapper;
@Override
public List<StudentLessonPlansDO> findStudentLessonPlansDOList(Integer page, Integer size, String name) {
log.info("查询学生学案");
if (name.isEmpty()) {
return studentLessonPlansDOMapper.selectStudentLessonPlanList((page - 1) * size, size, null);
}
return studentLessonPlansDOMapper.selectStudentLessonPlanList((page - 1) * size, size, name);
}
@Override
public Integer findStudentLessonPlanTotal() {
return studentLessonPlansDOMapper.selectStudentPlanTotal();
}
}

View File

@@ -22,4 +22,15 @@
where id = #{id} where id = #{id}
</select> </select>
<select id="findLessonPlansByStudentId" resultMap="BaseResultMap">
select *
from lesson_plans
<if test="ids != null">
where id in
<foreach item="id" collection="ids" separator="," open="(" close=")" index="">
#{id}
</foreach>
</if>
</select>
</mapper> </mapper>

View File

@@ -8,6 +8,7 @@
<result column="start_time" jdbcType="TIMESTAMP" property="startTime" /> <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
<result column="mastery_rat" jdbcType="DECIMAL" property="masteryRat" /> <result column="mastery_rat" jdbcType="DECIMAL" property="masteryRat" />
<result column="total_count" jdbcType="INTEGER" property="totalCount" /> <result column="total_count" jdbcType="INTEGER" property="totalCount" />
<result column="is_finished" jdbcType="INTEGER" property="isFinished" />
</resultMap> </resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO"> <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO">
@@ -22,4 +23,26 @@
) )
</insert> </insert>
<select id="selectStudentLessonPlanList" resultMap="BaseResultMap">
SELECT slp.*
FROM student_lesson_plans slp
LEFT JOIN student s ON slp.student_id = s.id
JOIN (
SELECT DISTINCT slp_in.student_id
FROM student_lesson_plans slp_in
LEFT JOIN student s_in ON slp_in.student_id = s_in.id
<if test="name != null and name != ''">
WHERE s_in.name LIKE CONCAT('%', #{name}, '%')
</if>
ORDER BY slp_in.student_id
LIMIT #{startIndex}, #{pageSize}
) AS temp ON slp.student_id = temp.student_id
ORDER BY slp.id
</select>
<select id="selectStudentPlanTotal">
SELECT count(DISTINCT student_id) AS total
FROM student_lesson_plans;
</select>
</mapper> </mapper>

View File

@@ -17,7 +17,7 @@ public class PlanTest {
@Test @Test
public void test() throws Exception { public void test() throws Exception {
LessonPlansDO lessonPlansDO = lessonPlansDOMapper.selectById(21); LessonPlansDO lessonPlansDO = lessonPlansDOMapper.selectById(58);
Map<String, Object> stringObjectMap = JsonUtils.parseMap(lessonPlansDO.getContentDetails(), String.class, Object.class); Map<String, Object> stringObjectMap = JsonUtils.parseMap(lessonPlansDO.getContentDetails(), String.class, Object.class);
for (Map.Entry<String, Object> entry : stringObjectMap.entrySet()) { for (Map.Entry<String, Object> entry : stringObjectMap.entrySet()) {
System.out.println(entry.getKey()); System.out.println(entry.getKey());

View File

@@ -0,0 +1,9 @@
import axios from "@/axios";
export function findStudentLessonPlans(page, size, name) {
return axios.post('/studentLessonPlans/list', {
page: page,
size: size,
name: name ?? ''
})
}

View File

@@ -6,10 +6,44 @@
</el-header> </el-header>
<el-main class="p-4"> <el-main class="p-4">
<el-tabs v-model="activeTab" type="border-card" class="demo-tabs"> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<el-tab-pane label="学习计划" name="first">习计划</el-tab-pane> <div class="text-lg font-semibold mb-4">案查询</div>
<el-tab-pane label="学习记录" name="second">学习记录</el-tab-pane> <div class="flex flex-wrap items-center gap-3 mb-4">
</el-tabs> <el-input v-model="searchName" placeholder="按姓名查询" clearable style="max-width: 220px" />
<el-button type="primary" @click="onSearch">查询</el-button>
<el-button @click="onReset">重置</el-button>
</div>
<el-table ref="tableRef" :data="rows" border class="w-full" v-loading="loading" row-key="id">
<el-table-column type="expand">
<template #default="{ row }">
<div class="p-3">
<div class="text-sm font-semibold mb-2">学案</div>
<el-table :data="row.plans || []" size="small" border>
<el-table-column prop="id" label="学案ID" width="100" />
<el-table-column prop="title" label="标题" min-width="280" />
<el-table-column label="状态" width="120">
<template #default="{ row: plan }">
<el-tag :type="plan.isFinished === 1 ? 'success' : 'info'"
effect="plain">
{{ plan.isFinished === 1 ? '已完成' : '未完成' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column prop="id" label="学生ID" width="100" />
<el-table-column prop="name" label="姓名" min-width="120" />
<el-table-column prop="className" label="班级" min-width="120" />
<el-table-column prop="gradeName" label="年级" min-width="120" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total" :total="totalCount"
:page-size="pageSize" :current-page="pageNo" @current-change="handlePageChange"
@size-change="handleSizeChange" />
</div>
</div>
</el-main> </el-main>
</el-container> </el-container>
@@ -18,5 +52,52 @@
<script setup> <script setup>
import Header from '@/layouts/components/Header.vue' import Header from '@/layouts/components/Header.vue'
import { ref, onMounted } from 'vue'
import { findStudentLessonPlans } from '@/api/studentLessonPlans'
const rows = ref([])
const loading = ref(false)
const pageNo = ref(1)
const pageSize = ref(10)
const totalCount = ref(0)
const searchName = ref('')
const tableRef = ref(null)
async function fetchLessonPlans() {
loading.value = true
try {
const res = await findStudentLessonPlans(pageNo.value, pageSize.value, searchName.value || '')
const d = res.data
rows.value = Array.isArray(d.data) ? d.data : []
totalCount.value = d.totalCount || 0
pageNo.value = d.pageNo || pageNo.value
pageSize.value = d.pageSize || pageSize.value
} finally {
loading.value = false
}
}
function handlePageChange(p) {
pageNo.value = p
fetchLessonPlans()
}
function handleSizeChange(s) {
pageSize.value = s
pageNo.value = 1
fetchLessonPlans()
}
function onSearch() {
pageNo.value = 1
fetchLessonPlans()
}
function onReset() {
searchName.value = ''
pageNo.value = 1
fetchLessonPlans()
}
onMounted(() => {
fetchLessonPlans()
})
</script> </script>