diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 8d66637..ee2c34b 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,5 +1,14 @@ \ No newline at end of file diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvSurveyController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvSurveyController.java index 584f252..be78d4a 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvSurveyController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvSurveyController.java @@ -70,7 +70,8 @@ public class QsvSurveyController { @SaCheckPermission("qsv.survey") @ApiOperation("分页查询") public Result pageData(@Valid PageForm pageForm, Integer year) { - Cnd cnd = Cnd.where("category", "in", new String[]{"SURVEY", "VOTE"}); +// Cnd cnd = Cnd.where("category", "in", new String[]{"SURVEY", "VOTE"}); + Cnd cnd = Cnd.NEW(); cnd.andEX("YEAR(startTime)", "=", year); cnd.desc("category"); Pagination pagination = qsvActivityService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvQuizController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvQuizController.java index c2c8e13..1ebd37e 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvQuizController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvQuizController.java @@ -323,6 +323,11 @@ public class H5QsvQuizController { } } + // 非随机答题统一按题目配置顺序展示;随机答题保留抽题记录中的原始随机顺序。 + if (!"RANDOM".equals(displayMode)) { + resultSubjects = qsvQuizService.sortSubjectsByOrder(resultSubjects); + } + dao.fetchLinks(resultSubjects, "options", Cnd.NEW().asc("sortNum")); resultSubjects.forEach(v -> v.setUserSelectOptionIds(new ArrayList<>())); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/QsvQuizService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/QsvQuizService.java index 8532af0..b4a2a11 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/QsvQuizService.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/QsvQuizService.java @@ -4,6 +4,7 @@ package com.budwk.app.zhgh.dayofficework.qsv.service; import com.budwk.app.base.service.BaseService; import com.budwk.app.zhgh.dayofficework.qsv.dto.QsvCheckAnswerResult; +import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject; import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord; import java.util.List; @@ -18,4 +19,12 @@ public interface QsvQuizService extends BaseService { */ QsvCheckAnswerResult calcScore(String subjectId, List userSelectOptions); + /** + * 按题目配置顺序整理非随机答题题目。 + * + * @param subjects 待排序的题目列表,题目顺序字段为 sortNum + * @return 新的题目列表,按 sortNum 升序、空顺序置后,同序时按题目ID稳定排序 + */ + List sortSubjectsByOrder(List subjects); + } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/impl/QsvQuizServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/impl/QsvQuizServiceImpl.java index b6f5b67..fc9fed2 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/impl/QsvQuizServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/impl/QsvQuizServiceImpl.java @@ -10,6 +10,7 @@ import org.nutz.dao.Dao; import org.nutz.ioc.loader.annotation.IocBean; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashSet; import java.util.List; @@ -33,4 +34,20 @@ public class QsvQuizServiceImpl extends BaseServiceImpl imp boolean equals = new HashSet<>(userSelectOptions).equals(new HashSet<>(correctOptionIds)); return QsvCheckAnswerResult.builder().isCorrect(equals).score(equals ? subject.getScore() : 0).build(); } + + /** + * 返回按题目配置顺序排列的新列表,不修改答题记录中保存的题目ID顺序。 + * sortNum 为空时排在最后,同一顺序按题目ID排序以保证多次查询结果稳定。 + * + * @param subjects 待排序的题目列表 + * @return 排序后的新题目列表 + */ + @Override + public List sortSubjectsByOrder(List subjects) { + List sortedSubjects = subjects == null ? new ArrayList<>() : new ArrayList<>(subjects); + sortedSubjects.sort(Comparator + .comparing(QsvSubject::getSortNum, Comparator.nullsLast(Integer::compareTo)) + .thenComparing(QsvSubject::getId, Comparator.nullsLast(String::compareTo))); + return sortedSubjects; + } } diff --git a/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareSelectionSituationController.java b/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareSelectionSituationController.java index 397efcf..bbe27b3 100644 --- a/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareSelectionSituationController.java +++ b/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareSelectionSituationController.java @@ -158,6 +158,24 @@ public class WelfareSelectionSituationController { situationService.receiveXlsx(pageForm, response); } + /** + * 按选择情况页面的查询条件导出签收单模版。 + * + * @param pageForm 页面完整查询条件,包含项目、福利选项、人员信息、组织范围和选择状态 + * @param response HTTP响应,返回XLSX格式的签收单模版文件流 + */ + @At + @SaCheckPermission("welfare.selection.situation") + @Ok("void") + @ApiOperation("导出签收单模版") + public void exportReceiveTemplate(@Valid @Param("pageForm") WelfareSelectionSituationPageForm pageForm, + HttpServletResponse response) { + if (StrUtil.isBlank(pageForm.getProjectId())) { + throw new IllegalArgumentException("请选择福利项目"); + } + situationService.exportReceiveTemplate(pageForm, response); + } + @At @SaCheckLogin public Result getMobileByUserId(String userId) { diff --git a/src/main/java/com/budwk/app/zhgh/welfare/service/WelfareSelectionSituationService.java b/src/main/java/com/budwk/app/zhgh/welfare/service/WelfareSelectionSituationService.java index 3fa93b2..0820f27 100644 --- a/src/main/java/com/budwk/app/zhgh/welfare/service/WelfareSelectionSituationService.java +++ b/src/main/java/com/budwk/app/zhgh/welfare/service/WelfareSelectionSituationService.java @@ -23,4 +23,12 @@ public interface WelfareSelectionSituationService extends BaseService queryPeople = listMap(sql); + // 福利选项关联或历史名单重复时,以人员ID保留首条,避免同一人在签收单中重复出现。 + List people = new ArrayList<>(queryPeople.stream().collect(Collectors.toMap( + item -> StrUtil.blankToDefault(item.getString("userId"), item.getString("id")), + item -> item, + (first, duplicate) -> first, + LinkedHashMap::new + )).values()); + if (people.isEmpty()) { + throw new BaseException("当前查询条件下没有可导出的人员"); + } + + // 同名单位可能属于不同工会,因此使用工会ID语义对应的名称与单位名称共同作为分组键。 + Map> peopleByUnit = people.stream().collect(Collectors.groupingBy( + item -> StrUtil.blankToDefault(item.getString("welfareUnionName"), "未设置工会") + + "\t" + + StrUtil.blankToDefault(item.getString("welfareUnitName"), "未设置单位"), + LinkedHashMap::new, + Collectors.toList() + )); + + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + try (Workbook workbook = exportParams.getType() == ExcelType.XSSF + ? new XSSFWorkbook() + : new HSSFWorkbook()) { + Set sheetNames = new HashSet<>(); + String provideMonth = project.getProvideTimeStart() == null + ? "" + : DateUtil.format(project.getProvideTimeStart(), "yyyy年M月"); + + peopleByUnit.forEach((groupKey, unitPeople) -> { + String[] groupNames = groupKey.split("\t", -1); + String unitName = groupNames[1]; + String sheetName = createUniqueSheetName(unitName, sheetNames); + Sheet sheet = workbook.createSheet(sheetName); + createReceiveTemplateSheet(workbook, sheet, project.getName(), unitName, provideMonth, unitPeople); + }); + + CommonDownloadUtil.download(project.getName() + "签收单模版.xlsx", workbook, response); + } catch (Exception e) { + log.error("导出签收单模版失败", e); + throw new BaseException("导出签收单模版失败"); + } + } + + /** + * 创建一个所属单位的签收单工作表。 + * 人员每十五人占用一组三列,不足十五人的最后一组仍保留十五行签字空间。 + */ + private void createReceiveTemplateSheet(Workbook workbook, Sheet sheet, String projectName, + String unitName, String provideMonth, List people) { + final int peoplePerGroup = 15; + final int columnsPerGroup = 3; + // 默认保留左右两组三列;超过30人后,每增加15人再向右扩展一组三列。 + int groupCount = Math.max(2, (people.size() + peoplePerGroup - 1) / peoplePerGroup); + int totalColumns = groupCount * columnsPerGroup; + + CellStyle titleStyle = createCellStyle(workbook, 18, true, HorizontalAlignment.CENTER, + VerticalAlignment.CENTER, false); + CellStyle infoStyle = createCellStyle(workbook, 11, false, HorizontalAlignment.LEFT, + VerticalAlignment.CENTER, false); + infoStyle.setWrapText(true); + CellStyle headerStyle = createCellStyle(workbook, 12, true, HorizontalAlignment.CENTER, + VerticalAlignment.CENTER, true); + CellStyle bodyStyle = createCellStyle(workbook, 11, false, HorizontalAlignment.CENTER, + VerticalAlignment.CENTER, true); + + Row titleRow = sheet.createRow(0); + titleRow.setHeightInPoints(32); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellValue(StrUtil.blankToDefault(projectName, "福利项目")); + titleCell.setCellStyle(titleStyle); + sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, totalColumns - 1)); + + Row infoRow = sheet.createRow(1); + infoRow.setHeightInPoints(32); + // 第二行按全部列的中点均分,左侧显示工会小组,右侧显示发放日期。 + int infoSplitColumn = totalColumns / 2; + Cell unitCell = infoRow.createCell(0); + unitCell.setCellValue("工会小组(部门):" + unitName); + unitCell.setCellStyle(infoStyle); + if (infoSplitColumn > 1) { + sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, infoSplitColumn - 1)); + } + Cell dateCell = infoRow.createCell(infoSplitColumn); + dateCell.setCellValue("发放日期:" + provideMonth); + dateCell.setCellStyle(infoStyle); + if (infoSplitColumn < totalColumns - 1) { + sheet.addMergedRegion(new CellRangeAddress(1, 1, infoSplitColumn, totalColumns - 1)); + } + + Row headerRow = sheet.createRow(2); + headerRow.setHeightInPoints(28); + for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) { + int startColumn = groupIndex * columnsPerGroup; + createCell(headerRow, startColumn, "序号", headerStyle); + createCell(headerRow, startColumn + 1, "姓名", headerStyle); + createCell(headerRow, startColumn + 2, "签字", headerStyle); + sheet.setColumnWidth(startColumn, 8 * 256); + sheet.setColumnWidth(startColumn + 1, 16 * 256); + sheet.setColumnWidth(startColumn + 2, 24 * 256); + } + + for (int rowIndex = 0; rowIndex < peoplePerGroup; rowIndex++) { + Row dataRow = sheet.createRow(rowIndex + 3); + dataRow.setHeightInPoints(28); + for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) { + int personIndex = groupIndex * peoplePerGroup + rowIndex; + int startColumn = groupIndex * columnsPerGroup; + if (personIndex < people.size()) { + NutMap person = people.get(personIndex); + createCell(dataRow, startColumn, personIndex + 1, bodyStyle); + createCell(dataRow, startColumn + 1, person.getString("userName", ""), bodyStyle); + } else { + createCell(dataRow, startColumn, "", bodyStyle); + createCell(dataRow, startColumn + 1, "", bodyStyle); + } + createCell(dataRow, startColumn + 2, "", bodyStyle); + } + } + + sheet.setFitToPage(true); + sheet.setHorizontallyCenter(true); + PrintSetup printSetup = sheet.getPrintSetup(); + printSetup.setPaperSize(PrintSetup.A4_PAPERSIZE); + printSetup.setLandscape(groupCount > 2); + printSetup.setFitWidth((short) 1); + printSetup.setFitHeight((short) 0); + workbook.setPrintArea(workbook.getSheetIndex(sheet), 0, totalColumns - 1, 0, peoplePerGroup + 2); + } + + /** + * 创建统一的Excel单元格样式,边框参数用于区分标题信息与签收表格区域。 + */ + private CellStyle createCellStyle(Workbook workbook, int fontSize, boolean bold, + HorizontalAlignment horizontalAlignment, + VerticalAlignment verticalAlignment, boolean bordered) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setFontName("宋体"); + font.setFontHeightInPoints((short) fontSize); + font.setBold(bold); + style.setFont(font); + style.setAlignment(horizontalAlignment); + style.setVerticalAlignment(verticalAlignment); + if (bordered) { + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + } + return style; + } + + /** + * 创建并赋值单元格,统一应用指定样式。 + */ + private void createCell(Row row, int columnIndex, Object value, CellStyle style) { + Cell cell = row.createCell(columnIndex); + if (value instanceof Number number) { + cell.setCellValue(number.doubleValue()); + } else { + cell.setCellValue(value == null ? "" : value.toString()); + } + cell.setCellStyle(style); + } + + /** + * 生成合法且不重复的工作表名称,处理Excel限制的特殊字符和31字符上限。 + */ + private String createUniqueSheetName(String rawName, Set usedNames) { + String baseName = StrUtil.blankToDefault(rawName, "未设置单位") + .replaceAll("[\\\\/?*\\[\\]:]", "_"); + baseName = StrUtil.sub(baseName, 0, Math.min(baseName.length(), 31)); + String sheetName = baseName; + int sequence = 2; + while (usedNames.contains(sheetName)) { + String suffix = "(" + sequence++ + ")"; + int maxBaseLength = 31 - suffix.length(); + sheetName = StrUtil.sub(baseName, 0, Math.min(baseName.length(), maxBaseLength)) + suffix; + } + usedNames.add(sheetName); + return sheetName; + } } diff --git a/src/main/resources/views/platform/zhgh/welfare/selectionSituation/index.html b/src/main/resources/views/platform/zhgh/welfare/selectionSituation/index.html index ceb4a20..fc32c75 100644 --- a/src/main/resources/views/platform/zhgh/welfare/selectionSituation/index.html +++ b/src/main/resources/views/platform/zhgh/welfare/selectionSituation/index.html @@ -96,6 +96,16 @@ layout("/layouts/platform.html"){ 导出领取表 + + + 签收单模版导出 + - - - - - + + + + + + + + + + + + + - - - + + + + @@ -166,7 +166,14 @@ layout("/layouts/platform.html"){ > 添加人员 - + 同步工会