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 3a7c57a..bc87083 100644
--- a/src/main/resources/views/platform/zhgh/welfare/selectionSituation/index.html
+++ b/src/main/resources/views/platform/zhgh/welfare/selectionSituation/index.html
@@ -92,6 +92,16 @@ layout("/layouts/platform.html"){
导出领取表
+
+
+ 签收单模版导出
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
@@ -161,7 +166,14 @@ layout("/layouts/platform.html"){
>
添加人员
-
+
同步工会