feat(exam): 添加按班级、年级、学生姓名筛选考试结果功能

- 在ExamWordsResultReqVO中新增classId、gradeId、studentName字段
- 修改ExamWordsJudgeService接口及实现,支持按筛选条件获取考试结果
- 在ExamWordsJudgeResultDOMapper中添加按学生ID列表分页查询方法
- 扩展StudentDOMapper,新增按班级、年级和姓名查询学生列表方法
- 修改ExamWordsController,支持从请求体接收筛选参数
- 修改前端exam.js,调用接口时传递筛选参数
- 在uploadpng.vue页面新增筛选表单,支持班级、年级、学生姓名输入
- 增加班级和年级选项数据的获取
- 实现筛选查询、重置功能及班级切换自动同步年级
- 在App.vue中配置Element Plus中文语言环境
- 调整配置文件,更新学习计划模板路径
- 修改接口权限配置,关闭plan/word/voice路径的鉴权
This commit is contained in:
lbw
2025-12-31 19:21:19 +08:00
parent 57166af2f4
commit e468be74b7
16 changed files with 159 additions and 12 deletions

View File

@@ -25,9 +25,11 @@ public class SaTokenConfigure implements WebMvcConfigurer {
SaRouter.match("/**") SaRouter.match("/**")
.notMatch("/login/**") .notMatch("/login/**")
.notMatch("plan/word/voice")
.check(r -> StpUtil.checkLogin()); .check(r -> StpUtil.checkLogin());
SaRouter.match("/admin/**") SaRouter.match("/admin/**")
.notMatch("plan/word/voice")
.check(r -> StpUtil.checkRole("root")); .check(r -> StpUtil.checkRole("root"));
})) }))

View File

@@ -107,8 +107,11 @@ public class ExamWordsController {
PageResponse<ExamWordsResultRspVO> getExamWordsResult(@RequestBody ExamWordsResultReqVO examWordsResultReqVO) { PageResponse<ExamWordsResultRspVO> getExamWordsResult(@RequestBody ExamWordsResultReqVO examWordsResultReqVO) {
Integer page = examWordsResultReqVO.getPage(); Integer page = examWordsResultReqVO.getPage();
Integer size = examWordsResultReqVO.getSize(); Integer size = examWordsResultReqVO.getSize();
Integer classId = examWordsResultReqVO.getClassId();
Integer gradeId = examWordsResultReqVO.getGradeId();
String studentName = examWordsResultReqVO.getStudentName();
Integer total = examWordsJudgeService.getExamWordsJudgeResultCount(); Integer total = examWordsJudgeService.getExamWordsJudgeResultCount();
List<ExamWordsJudgeResultDO> examWordsJudgeResult = examWordsJudgeService.getExamWordsJudgeResult(page, size); List<ExamWordsJudgeResultDO> examWordsJudgeResult = examWordsJudgeService.getExamWordsJudgeResult(page, size, classId, gradeId, studentName);
List<ExamWordsResultRspVO> list = examWordsJudgeResult.stream().map(examWordsJudgeResultDO -> ExamWordsResultRspVO List<ExamWordsResultRspVO> list = examWordsJudgeResult.stream().map(examWordsJudgeResultDO -> ExamWordsResultRspVO
.builder() .builder()
.id(examWordsJudgeResultDO.getId()) .id(examWordsJudgeResultDO.getId())

View File

@@ -24,4 +24,6 @@ public interface ExamWordsJudgeResultDOMapper {
List<ExamWordsJudgeResultDO> selectByStudentId(@Param("studentId") Integer studentId); List<ExamWordsJudgeResultDO> selectByStudentId(@Param("studentId") Integer studentId);
List<ExamWordsJudgeResultDO> selectByStudentIdAndLimitTime(@Param("studentId") Integer studentId); List<ExamWordsJudgeResultDO> selectByStudentIdAndLimitTime(@Param("studentId") Integer studentId);
List<ExamWordsJudgeResultDO> selectByPageAndStudentIds(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize, @Param("studentIds") List<Integer> studentIds);
} }

View File

@@ -23,4 +23,10 @@ public interface StudentDOMapper {
int selectStudentCountByClassId(@Param("classId") Integer classId); int selectStudentCountByClassId(@Param("classId") Integer classId);
int updateStudentActualGradeId(@Param("studentId") Integer studentId, @Param("gradeId") Integer gradeId); int updateStudentActualGradeId(@Param("studentId") Integer studentId, @Param("gradeId") Integer gradeId);
List<StudentDO> selectStudentDOListByClassId(@Param("classId") Integer classId);
List<StudentDO> selectStudentDOListByGradeId(@Param("gradeId") Integer gradeId);
List<StudentDO> selectStudentDOListByName(@Param("name") String name);
} }

View File

@@ -13,4 +13,8 @@ public class ExamWordsResultReqVO {
private Integer page; private Integer page;
private Integer size; private Integer size;
private Integer classId;
private Integer gradeId;
private String studentName;
} }

View File

@@ -1,5 +1,6 @@
package com.yinlihupo.enlish.service.model.vo.student; package com.yinlihupo.enlish.service.model.vo.student;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@@ -16,5 +17,6 @@ public class AddStudentReqVO {
private String name; private String name;
private Integer classId; private Integer classId;
private Integer gradeId; private Integer gradeId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime; private LocalDateTime createTime;
} }

View File

@@ -9,7 +9,7 @@ public interface ExamWordsJudgeService {
void judgeExamWords(int count); void judgeExamWords(int count);
List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize); List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize, Integer classId, Integer gradeId, String studentName);
Integer getExamWordsJudgeResultCount(); Integer getExamWordsJudgeResultCount();

View File

@@ -326,8 +326,24 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
} }
@Override @Override
public List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize) { public List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize, Integer classId, Integer gradeId, String studentName) {
return examWordsJudgeResultDOMapper.selectByPage((page - 1) * pageSize, pageSize);
List<Integer> studentIds = new ArrayList<>();
if (classId != null) {
studentIds.addAll(studentDOMapper.selectStudentDOListByClassId(classId).stream().map(StudentDO::getId).toList());
}
if (gradeId != null) {
studentIds.addAll(studentDOMapper.selectStudentDOListByGradeId(gradeId).stream().map(StudentDO::getId).toList());
}
if (!studentName.isEmpty()) {
studentIds.addAll(studentDOMapper.selectStudentDOListByName(studentName).stream().map(StudentDO::getId).toList());
}
if (studentIds.isEmpty()) {
return examWordsJudgeResultDOMapper.selectByPage((page - 1) * pageSize, pageSize);
} else {
return examWordsJudgeResultDOMapper.selectByPageAndStudentIds((page - 1) * pageSize, page * pageSize, studentIds);
}
} }
@Override @Override

View File

@@ -35,7 +35,7 @@ templates:
count: 100 count: 100
data: C:\project\tess data: C:\project\tess
plan: plan:
weekday: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\tem_study_plan_v5.docx weekday: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\tem_study_plan_v6.docx
weekend: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\study_plan_review_v2.docx weekend: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\study_plan_review_v2.docx
plan_day: 7 plan_day: 7
tmp: tmp:

View File

@@ -83,4 +83,16 @@
and start_date between date_sub(now(), interval 7 day) and now() and start_date between date_sub(now(), interval 7 day) and now()
</select> </select>
<select id="selectByPageAndStudentIds" resultMap="BaseResultMap">
select *
from exam_words_judge_result
where student_id in
<foreach item="item" index="index" collection="studentIds"
open="(" separator="," close=")">
#{item}
</foreach>
order by start_date
limit #{startIndex}, #{pageSize}
</select>
</mapper> </mapper>

View File

@@ -75,4 +75,24 @@
where class_id = #{classId} where class_id = #{classId}
and is_deleted = 0 and is_deleted = 0
</select> </select>
<select id="selectStudentDOListByClassId" resultMap="BaseResultMap">
select *
from student
where class_id = #{classId}
and is_deleted = 0
</select>
<select id="selectStudentDOListByGradeId" resultMap="BaseResultMap">
select *
from student
where grade_id = #{gradeId}
and is_deleted = 0
</select>
<select id="selectStudentDOListByName" resultMap="BaseResultMap">
select *
from student
where name like concat('%', #{name}, '%')
and is_deleted = 0
</select>
</mapper> </mapper>

View File

@@ -56,8 +56,8 @@ public class ExamWordsJudgeServiceTest {
@Test @Test
public void selectExamWordsJudgeResult() { public void selectExamWordsJudgeResult() {
List<ExamWordsJudgeResultDO> examWordsJudgeResult = examWordsJudgeService.getExamWordsJudgeResult(1, 10); // List<ExamWordsJudgeResultDO> examWordsJudgeResult = examWordsJudgeService.getExamWordsJudgeResult(1, 10);
log.info("examWordsJudgeResult:{}", examWordsJudgeResult); // log.info("examWordsJudgeResult:{}", examWordsJudgeResult);
} }
// @Test // @Test

View File

@@ -1,11 +1,14 @@
<template> <template>
<router-view></router-view> <el-config-provider :locale="locale">
<router-view />
</el-config-provider>
</template> </template>
<script setup> <script setup>
import zhCn from 'element-plus/es/locale/lang/zh-cn'
const locale = zhCn
</script> </script>
<style> <style>

View File

@@ -5,10 +5,13 @@ export function uploadExamWordsPng(data) {
return axios.post('/exam/words/submit', data) return axios.post('/exam/words/submit', data)
} }
export function getExamWordsResult(page, size) { export function getExamWordsResult(page, size, classId, gradeId, studentName) {
return axios.post('/exam/words/get', { return axios.post('/exam/words/get', {
page: page, page: page,
size: size size: size,
classId: classId,
gradeId: gradeId,
studentName: studentName
}) })
} }

View File

@@ -19,6 +19,35 @@
</div> </div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-lg font-semibold mb-4">结果集</div> <div class="text-lg font-semibold mb-4">结果集</div>
<el-form :inline="true" class="mb-4">
<el-form-item label="班级">
<el-select v-model="classId" placeholder="选择班级" clearable filterable @change="onClassChange" style="min-width: 220px">
<el-option
v-for="item in classOptions"
:key="item.id"
:label="`${item.title}${item.gradeName ? '' + item.gradeName + '' : ''}`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="年级">
<el-select v-model="gradeId" placeholder="选择年级" clearable filterable style="min-width: 220px">
<el-option
v-for="g in gradeOptions"
:key="g.id"
:label="g.title"
:value="g.id"
/>
</el-select>
</el-form-item>
<el-form-item label="学生姓名">
<el-input v-model="studentName" placeholder="学生姓名" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<el-table <el-table
:data="list" :data="list"
border border
@@ -70,6 +99,8 @@ import Header from '@/layouts/components/Header.vue'
import ExamWordsDetailCard from '@/layouts/components/ExamWordsDetailCard.vue' import ExamWordsDetailCard from '@/layouts/components/ExamWordsDetailCard.vue'
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { uploadExamWordsPng, getExamWordsResult } from '@/api/exam' import { uploadExamWordsPng, getExamWordsResult } from '@/api/exam'
import { getClassList } from '@/api/class'
import { getGradeList } from '@/api/grade'
const list = ref([]) const list = ref([])
const pageNo = ref(1) const pageNo = ref(1)
@@ -78,11 +109,22 @@ const totalCount = ref(0)
const loading = ref(false) const loading = ref(false)
const showDetail = ref(false) const showDetail = ref(false)
const selectedId = ref(null) const selectedId = ref(null)
const classId = ref(null)
const gradeId = ref(null)
const studentName = ref('')
const classOptions = ref([])
const gradeOptions = ref([])
async function fetchList() { async function fetchList() {
loading.value = true loading.value = true
try { try {
const res = await getExamWordsResult(pageNo.value, pageSize.value) const res = await getExamWordsResult(
pageNo.value,
pageSize.value,
classId.value,
gradeId.value,
studentName.value
)
const d = res.data const d = res.data
list.value = Array.isArray(d.data) ? d.data : [] list.value = Array.isArray(d.data) ? d.data : []
totalCount.value = d.totalCount || 0 totalCount.value = d.totalCount || 0
@@ -93,6 +135,18 @@ async function fetchList() {
} }
} }
async function fetchClassOptions() {
const res = await getClassList(1, 200)
const d = res.data
classOptions.value = Array.isArray(d.data) ? d.data : []
}
async function fetchGradeOptions() {
const res = await getGradeList(1, 200)
const d = res.data
gradeOptions.value = Array.isArray(d.data) ? d.data : []
}
function handlePageChange(p) { function handlePageChange(p) {
pageNo.value = p pageNo.value = p
fetchList() fetchList()
@@ -117,8 +171,28 @@ function handleRowClick(row) {
showDetail.value = true showDetail.value = true
} }
function handleSearch() {
pageNo.value = 1
fetchList()
}
function handleReset() {
classId.value = null
gradeId.value = null
studentName.value = ''
pageNo.value = 1
fetchList()
}
function onClassChange(val) {
const item = classOptions.value.find(i => i.id === val)
gradeId.value = item ? item.gradeId : null
}
onMounted(() => { onMounted(() => {
fetchList() fetchList()
fetchClassOptions()
fetchGradeOptions()
}) })
</script> </script>