From e3b993dd271a8389bca34219b0b45f2ff0e16003 Mon Sep 17 00:00:00 2001 From: lbw <1192299468@qq.com> Date: Mon, 15 Dec 2025 14:26:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(exam):=20=E5=AE=9E=E7=8E=B0=E6=8C=89?= =?UTF-8?q?=E5=AD=A6=E7=94=9F=E6=89=B9=E9=87=8F=E7=94=9F=E6=88=90=E5=B9=B6?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E8=AF=95=E9=A2=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加学生多选功能和生成试题按钮,支持批量操作 - 新增ExamGenerateDialog组件,提供选择年级和难度界面 - 设计后端接口支持多个学生ID,生成对应的试题文档 - 在后端实现批量生成Word文档并压缩打包下载 - 新增StudentDetail业务对象,完善学生信息展示 - 优化了Mapper接口及XML,支持批量查询学生和班级数据 - 提供前端API封装用于调用试题生成和下载服务 - 实现下载失败时的错误处理与提示机制 --- .../service/controller/ClassController.java | 2 +- .../controller/ExamWordsController.java | 61 +++------ .../service/domain/mapper/ClassDOMapper.java | 2 + .../domain/mapper/StudentDOMapper.java | 2 + .../service/model/bo/StudentDetail.java | 19 +++ .../enlish/service/service/ClassService.java | 2 +- .../service/service/StudentService.java | 3 + .../service/classs/ClassServiceImpl.java | 2 +- .../service/exam/ExamWordsServiceImpl.java | 2 +- .../service/student/StudentServiceImpl.java | 36 ++++++ .../enlish/service/utils/WordExportUtil.java | 121 ++++++++++++++++++ .../main/resources/mapper/ClassDOMapper.xml | 9 ++ .../main/resources/mapper/StudentDOMapper.xml | 9 ++ enlish-vue/src/api/exam.js | 62 +++++++++ .../layouts/components/ExamGenerateDialog.vue | 94 ++++++++++++++ enlish-vue/src/pages/class.vue | 27 +++- 16 files changed, 406 insertions(+), 47 deletions(-) create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/model/bo/StudentDetail.java create mode 100644 enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/WordExportUtil.java create mode 100644 enlish-vue/src/layouts/components/ExamGenerateDialog.vue diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/ClassController.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/ClassController.java index 8a7b852..80b5f31 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/ClassController.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/ClassController.java @@ -31,7 +31,7 @@ public class ClassController { int total = classService.getClassCount(); List classList = classService.findClassList(page, pageSize); - Map classId2GradeMap = classService.findClassId2GradeMap(classList.stream().map(ClassDO::getId).toList()); + Map classId2GradeMap = classService.findClassIds2GradeMap(classList.stream().map(ClassDO::getId).toList()); List findClassRspVOS = classList.stream().map(classDO -> FindClassRspVO.builder() .id(classDO.getId()) .title(classDO.getTitle()) diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/ExamWordsController.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/ExamWordsController.java index e417b13..4f95347 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/ExamWordsController.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/ExamWordsController.java @@ -1,17 +1,17 @@ package com.yinlihupo.enlish.service.controller; -import com.deepoove.poi.XWPFTemplate; -import com.deepoove.poi.config.Configure; -import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsDO; import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsJudgeResultDO; import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO; +import com.yinlihupo.enlish.service.model.bo.StudentDetail; import com.yinlihupo.enlish.service.model.bo.Word; import com.yinlihupo.enlish.service.model.vo.exam.*; import com.yinlihupo.enlish.service.service.ExamWordsJudgeService; import com.yinlihupo.enlish.service.service.ExamWordsService; +import com.yinlihupo.enlish.service.service.StudentService; import com.yinlihupo.enlish.service.service.VocabularyService; +import com.yinlihupo.enlish.service.utils.WordExportUtil; import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog; import com.yinlihupo.framework.common.response.PageResponse; import com.yinlihupo.framework.common.response.Response; @@ -22,14 +22,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @RequestMapping("/exam/words/") @RestController @@ -44,8 +37,10 @@ public class ExamWordsController { private String templateWordPath; @Resource private ExamWordsJudgeService examWordsJudgeService; + @Resource + private StudentService studentService; - @PostMapping("genexam") + @PostMapping("generate") public void generateFeltExamWords(@RequestBody GenerateExamWordsReqVO generateExamWordsReqVO, HttpServletResponse response) { Integer gradeId = generateExamWordsReqVO.getGradeId(); Integer level = generateExamWordsReqVO.getLevel(); @@ -56,43 +51,25 @@ public class ExamWordsController { try { ExamWordsDO examWordsDO = examWordsService.generateExamWords(gradeId, level, studentIds); List vocabularyBankDOS = vocabularyService.findVocabularyBankDOListById(examWordsDO.getWordIds()); - List assessmentWords = vocabularyBankDOS.stream().map(vocabularyBankDO -> Word.builder() .id(vocabularyBankDO.getId()) .title(vocabularyBankDO.getWord()) .definition(vocabularyBankDO.getDefinition()) .build()).toList(); - // 配置 POI-TL - LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(); - Configure config = Configure.builder() - .bind("words", policy) - .bind("answer", policy) - .build(); + List studentDetailList = studentService.getStudentDetailList(studentIds); + List> maps = studentDetailList.stream().map(studentDetail -> { + Map data = new HashMap<>(); + data.put("examId", examWordsDO.getId()); + data.put("studentId", studentDetail.getId()); + data.put("studentStr", studentDetail.getName() + studentDetail.getClassName() + studentDetail.getGradeName()); + data.put("examStr", examWordsDO.getTitle()); + data.put("words", assessmentWords); + data.put("answer", assessmentWords); + return data; + }).toList(); - Map data = new HashMap<>(); - data.put("examId", examWordsDO.getId()); - data.put("studentId", studentIds.get(0)); - data.put("studentStr","小明三班一年级"); - data.put("examStr", examWordsDO.getTitle()); - data.put("words", assessmentWords); - data.put("answer", assessmentWords); - - // 2. 设置文件名编码 - String fileName = URLEncoder.encode(examWordsDO.getId() + "摸底测试.docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20"); - - // 3. 设置响应头 - response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); - response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName); - - try (InputStream inputStream = new FileInputStream(this.templateWordPath); - XWPFTemplate template = XWPFTemplate.compile(inputStream, config); - OutputStream out = response.getOutputStream()) { - - template.render(data); - template.write(out); - out.flush(); - } + WordExportUtil.generateExamWords(maps, response, templateWordPath); } catch (Exception e) { diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/ClassDOMapper.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/ClassDOMapper.java index fcdb936..f921018 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/ClassDOMapper.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/ClassDOMapper.java @@ -12,4 +12,6 @@ public interface ClassDOMapper { List selectClassDOList(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize); Integer selectClassDOCount(); + + List selectClassDOListByIds(@Param("classIds") List classIds); } \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/StudentDOMapper.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/StudentDOMapper.java index 829d3ef..87ac5d6 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/StudentDOMapper.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/StudentDOMapper.java @@ -12,4 +12,6 @@ public interface StudentDOMapper { int selectStudentCount(); StudentDO selectStudentById(Integer id); + + List selectStudentDOListByIds(@Param("ids") List ids); } \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/bo/StudentDetail.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/bo/StudentDetail.java new file mode 100644 index 0000000..0c9e90c --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/bo/StudentDetail.java @@ -0,0 +1,19 @@ +package com.yinlihupo.enlish.service.model.bo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class StudentDetail { + private Integer id; + private String name; + private Integer classId; + private String className; + private Integer gradeId; + private String gradeName; +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/ClassService.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/ClassService.java index 51e73de..0cc0e2d 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/ClassService.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/ClassService.java @@ -14,5 +14,5 @@ public interface ClassService { int getClassCount(); - Map findClassId2GradeMap(List classIds); + Map findClassIds2GradeMap(List classIds); } diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/StudentService.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/StudentService.java index 34e7575..b384111 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/StudentService.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/StudentService.java @@ -2,6 +2,7 @@ package com.yinlihupo.enlish.service.service; import com.yinlihupo.enlish.service.domain.dataobject.StudentDO; +import com.yinlihupo.enlish.service.model.bo.StudentDetail; import java.util.List; @@ -12,4 +13,6 @@ public interface StudentService { int getAllStudents(); StudentDO getStudentById(Integer studentId); + + List getStudentDetailList(List studentIdList); } diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/classs/ClassServiceImpl.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/classs/ClassServiceImpl.java index 4994b4e..7220f38 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/classs/ClassServiceImpl.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/classs/ClassServiceImpl.java @@ -41,7 +41,7 @@ public class ClassServiceImpl implements ClassService { } @Override - public Map findClassId2GradeMap(List classIds) { + public Map findClassIds2GradeMap(List classIds) { List gradeClassDOS = gradeClassDOMapper.selectByClassIds(classIds); List gradeDOS = gradeDOMapper.selectByGradeIds(gradeClassDOS.stream().map(GradeClassDO::getGradeId).toList()); diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/exam/ExamWordsServiceImpl.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/exam/ExamWordsServiceImpl.java index 513ec8d..c7d667d 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/exam/ExamWordsServiceImpl.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/exam/ExamWordsServiceImpl.java @@ -56,7 +56,7 @@ public class ExamWordsServiceImpl implements ExamWordsService { ExamWordsDO examWordsDO = ExamWordsDO.builder() .gradeId(gradeId) .level(level) - .title("测试") + .title(LocalDateTime.now() + "测试") .createdAt(LocalDateTime.now()) .wordIds(vocabularyBankDOS.stream().map(VocabularyBankDO::getId).toList()) .build(); diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/student/StudentServiceImpl.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/student/StudentServiceImpl.java index def873a..bd03c52 100644 --- a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/student/StudentServiceImpl.java +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/student/StudentServiceImpl.java @@ -1,19 +1,30 @@ package com.yinlihupo.enlish.service.service.student; +import com.yinlihupo.enlish.service.domain.dataobject.ClassDO; +import com.yinlihupo.enlish.service.domain.dataobject.GradeDO; import com.yinlihupo.enlish.service.domain.dataobject.StudentDO; +import com.yinlihupo.enlish.service.domain.mapper.ClassDOMapper; +import com.yinlihupo.enlish.service.domain.mapper.GradeDOMapper; import com.yinlihupo.enlish.service.domain.mapper.StudentDOMapper; +import com.yinlihupo.enlish.service.model.bo.StudentDetail; import com.yinlihupo.enlish.service.service.StudentService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Service public class StudentServiceImpl implements StudentService { @Resource private StudentDOMapper studentDOMapper; + @Resource + private GradeDOMapper gradeDOMapper; + @Resource + private ClassDOMapper classDOMapper; @Override public List getStudentsByClassIdAndGradeId(Integer classId, Integer gradeId, String name, Integer pageNo, Integer pageSize) { @@ -30,4 +41,29 @@ public class StudentServiceImpl implements StudentService { public StudentDO getStudentById(Integer studentId) { return studentDOMapper.selectStudentById(studentId); } + + @Override + public List getStudentDetailList(List studentIdList) { + + List studentDOS = studentDOMapper.selectStudentDOListByIds(studentIdList); + Map gradeId2GradeDO = gradeDOMapper.selectByGradeIds(studentDOS.stream().map(StudentDO::getGradeId).toList()).stream().collect(Collectors.toMap( + GradeDO::getId, + gradeDO -> gradeDO + )); + Map classId2ClassDO = classDOMapper.selectClassDOListByIds(studentDOS.stream().map(StudentDO::getClassId).toList()).stream().collect(Collectors.toMap( + ClassDO::getId, + classDO -> classDO + )); + + return studentDOS.stream().map(studentDO -> StudentDetail.builder() + .id(studentDO.getId()) + .name(studentDO.getName()) + .classId(studentDO.getClassId()) + .className(classId2ClassDO.get(studentDO.getClassId()) != null ? classId2ClassDO.get(studentDO.getClassId()).getTitle() : "") + .gradeId(studentDO.getGradeId()) + .gradeName(gradeId2GradeDO.get(studentDO.getGradeId()) != null ? gradeId2GradeDO.get(studentDO.getGradeId()).getTitle() : "") + .build() + ).toList(); + + } } diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/WordExportUtil.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/WordExportUtil.java new file mode 100644 index 0000000..e1e3005 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/utils/WordExportUtil.java @@ -0,0 +1,121 @@ +package com.yinlihupo.enlish.service.utils; + +import com.deepoove.poi.XWPFTemplate; +import com.deepoove.poi.config.Configure; +import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class WordExportUtil { + + private static final Configure config; + + static { + LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(); + config = Configure.builder() + .bind("words", policy) + .bind("answer", policy) + .build(); + } + + /** + * 公共入口:根据数据量决定是导出单文件还是压缩包 + */ + public static void generateExamWords(List> data, HttpServletResponse response, String templateWordPath) { + if (data == null || data.isEmpty()) { + return; + } + + try { + if (data.size() == 1) { + // 如果只有一份数据,直接导出 docx,用户体验更好 + generateExamWordsDocx(data.get(0), response, templateWordPath); + } else { + // 如果有多份数据,打包导出 + generateExamWordsZip(data, response, templateWordPath); + } + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("导出文档失败", e); + } + } + + /** + * 核心补充:批量渲染并打包为 ZIP + */ + private static void generateExamWordsZip(List> data, HttpServletResponse response, String templateWordPath) throws IOException { + // 1. 设置响应头为 ZIP + String zipName = URLEncoder.encode("批量导出_摸底测试.zip", StandardCharsets.UTF_8).replaceAll("\\+", "%20"); + response.setContentType("application/zip"); + response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + zipName); + + // 2. 创建 ZipOutputStream 包装 Response 输出流 + // 使用 try-with-resources 自动关闭 zipOut + try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) { + + int index = 1; + for (Map itemData : data) { + // 3. 确定压缩包内的文件名 + // 优先从 map 中获取 'fileName' 字段,否则使用默认编号 + String entryName = (String) itemData.getOrDefault("fileName", "摸底测试_" + index); + // 确保文件名后缀正确 + if (!entryName.endsWith(".docx")) { + entryName += ".docx"; + } + + // 4. 创建 ZIP 条目 + ZipEntry zipEntry = new ZipEntry(entryName); + zipOut.putNextEntry(zipEntry); + + // 5. 加载模板并渲染 + // 注意:每次循环都需要重新读取模板流,因为 poi-tl 渲染会修改文档结构 + try (InputStream templateInputStream = new FileInputStream(templateWordPath)) { + XWPFTemplate template = XWPFTemplate.compile(templateInputStream, config); + template.render(itemData); + + // 6. 将渲染后的文档直接写入 ZipOutputStream + // template.write 默认不关闭流,所以这里是安全的 + template.write(zipOut); + + // 显式关闭 template (释放 POI 资源) + template.close(); + } + + // 7. 关闭当前条目 + zipOut.closeEntry(); + index++; + } + + // 8. 循环结束,zipOut 会在 try 块结束时自动 finish 和 close + response.flushBuffer(); + } + } + + /** + * 现有的单文件导出逻辑 + */ + private static void generateExamWordsDocx(Map data, HttpServletResponse response, String templateWordPath) throws IOException { + + String fileName = URLEncoder.encode("摸底测试" + ".docx", StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20"); + + // 3. 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName); + + try (InputStream inputStream = new FileInputStream(templateWordPath)) { + XWPFTemplate template = XWPFTemplate.compile(inputStream, config); + OutputStream out = response.getOutputStream(); + template.render(data); + template.write(out); + template.close(); // 最好关闭 template + out.flush(); + } + } +} diff --git a/enlish-service/src/main/resources/mapper/ClassDOMapper.xml b/enlish-service/src/main/resources/mapper/ClassDOMapper.xml index 1f797d7..d8ae0e5 100644 --- a/enlish-service/src/main/resources/mapper/ClassDOMapper.xml +++ b/enlish-service/src/main/resources/mapper/ClassDOMapper.xml @@ -23,5 +23,14 @@ from `class` + + \ No newline at end of file diff --git a/enlish-service/src/main/resources/mapper/StudentDOMapper.xml b/enlish-service/src/main/resources/mapper/StudentDOMapper.xml index 7cda7a5..4cddcf9 100644 --- a/enlish-service/src/main/resources/mapper/StudentDOMapper.xml +++ b/enlish-service/src/main/resources/mapper/StudentDOMapper.xml @@ -38,4 +38,13 @@ from student where id = #{id} + + \ No newline at end of file diff --git a/enlish-vue/src/api/exam.js b/enlish-vue/src/api/exam.js index c0d9ff1..376590d 100644 --- a/enlish-vue/src/api/exam.js +++ b/enlish-vue/src/api/exam.js @@ -1,4 +1,5 @@ import axios from "@/axios"; +import { showMessage } from "../composables/util"; export function uploadExamWordsPng(data) { return axios.post('/exam/words/submit', data) @@ -16,3 +17,64 @@ export function getExamWordsDetailResult(id) { id: id }) } + +export function generateExamWords(data) { + return axios.post('/exam/words/generate', data, { + // 1. 重要:必须指定响应类型为 blob,否则下载的文件会损坏(乱码) + responseType: 'blob', + headers: { + 'Content-Type': 'application/json; application/octet-stream' // 根据需要调整 + } + }).then(response => { + // 2. 提取文件名 (处理后端设置的 Content-Disposition) + // 后端示例: header("Content-Disposition", "attachment; filename*=UTF-8''" + fileName) + let fileName = 'download.zip'; // 默认兜底文件名 + + const contentDisposition = response.headers['content-disposition']; + if (contentDisposition) { + // 正则提取 filename*=utf-8''xxx.zip 或 filename="xxx.zip" + const fileNameMatch = contentDisposition.match(/filename\*?=['"]?(?:UTF-\d['"]*)?([^;\r\n"']*)['"]?;?/); + if (fileNameMatch && fileNameMatch[1]) { + // 后端如果用了 URLEncoder,这里需要 decode + fileName = decodeURIComponent(fileNameMatch[1]); + } + } + + // 3. 开始下载流程 + resolveBlob(response.data, fileName); + }).catch(error => { + console.error('下载失败', error); + showMessage('下载失败' + error, 'error'); + // 注意:如果后端报错返回 JSON,因为 responseType 是 blob, + // 这里看到的 error.response.data 也是 blob,需要转回文本才能看到错误信息 + const reader = new FileReader(); + reader.readAsText(error.response.data); + reader.onload = () => { + console.log(JSON.parse(reader.result)); // 打印后端实际报错 + } + }); +} + +const resolveBlob = (res, fileName) => { + // 创建 Blob 对象,可以指定 type,也可以让浏览器自动推断 + const blob = new Blob([res], { type: 'application/octet-stream' }); + + // 兼容 IE/Edge (虽然现在很少用了) + if (window.navigator.msSaveOrOpenBlob) { + navigator.msSaveBlob(blob, fileName); + } else { + // 创建一个临时的 URL 指向 Blob + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = fileName; + + // 触发点击 + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + + // 清理资源 + document.body.removeChild(link); + window.URL.revokeObjectURL(link.href); + } +}; diff --git a/enlish-vue/src/layouts/components/ExamGenerateDialog.vue b/enlish-vue/src/layouts/components/ExamGenerateDialog.vue new file mode 100644 index 0000000..2433cf3 --- /dev/null +++ b/enlish-vue/src/layouts/components/ExamGenerateDialog.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/enlish-vue/src/pages/class.vue b/enlish-vue/src/pages/class.vue index 6b7a0ba..2bd8ef1 100644 --- a/enlish-vue/src/pages/class.vue +++ b/enlish-vue/src/pages/class.vue @@ -35,8 +35,19 @@ selectedGradeId }}) 查询 重置 + + 生成试题 + - + + @@ -47,6 +58,11 @@ :total="studentTotalCount" :page-size="studentPageSize" :current-page="studentPageNo" @current-change="handleStudentPageChange" @size-change="handleStudentSizeChange" /> +
@@ -76,6 +92,7 @@ import { ref, onMounted } from 'vue' import { getClassList } from '@/api/class' import { getGradeList } from '@/api/grade' import { getStudentList } from '@/api/student' +import ExamGenerateDialog from '@/layouts/components/ExamGenerateDialog.vue' const classes = ref([]) const pageNo = ref(1) @@ -85,6 +102,7 @@ const loading = ref(false) const classTableRef = ref(null) const selectedClassId = ref(null) const selectedClassTitle = ref('') + const grades = ref([]) const gradePageNo = ref(1) const gradePageSize = ref(10) @@ -93,12 +111,16 @@ const gradeLoading = ref(false) const gradeTableRef = ref(null) const selectedGradeId = ref(null) const selectedGradeTitle = ref('') + const students = ref([]) const studentPageNo = ref(1) const studentPageSize = ref(10) const studentTotalCount = ref(0) const studentLoading = ref(false) const studentName = ref('') +const studentTableRef = ref(null) +const selectedStudentIds = ref([]) +const showGenerateDialog = ref(false) async function fetchClasses() { loading.value = true @@ -178,6 +200,9 @@ function handleStudentSizeChange(s) { studentPageNo.value = 1 fetchStudents() } +function onStudentSelectionChange(rows) { + selectedStudentIds.value = rows.map(r => r.id) +} function onClassRowClick(row) { selectedClassId.value = row.id selectedClassTitle.value = row.title