Merge remote-tracking branch 'origin/feature_民主管理、人员管理' into release_20261030问题整改

# Conflicts:
#	src/main/resources/views/platform/zhgh/activity/culture/common/applyActivity.js
This commit is contained in:
2026-11-02 11:06:34 +08:00
6 changed files with 99 additions and 13 deletions
@@ -57,6 +57,8 @@ public class SysV4ServController {
$condition
""");
Cnd cnd = Cnd.NEW();
// 先取每个流程的最新定义,再判断是否启用,避免最新版本停用后回退展示旧版本。
cnd.and("t.state", "=", 1);
if (StrUtil.isNotBlank(keyword)) {
cnd.where().andLike("t.disPlayName", keyword);
}
@@ -620,6 +620,27 @@ public class ActivitySportsApplyUserServiceImpl extends BaseServiceImpl<Activity
continue;
}
// 以系统人员档案为准校验男女分组,不能用 Excel 性别绕过限制;校验失败时保留已有报名。
Integer isMenWomen = activityEvent.getIsMenWomen();
if (Integer.valueOf(1).equals(isMenWomen) || Integer.valueOf(2).equals(isMenWomen)) {
String sex = normalizeText(sysUser.getSex());
boolean male = "男".equals(sex) || "男性".equals(sex);
boolean female = "女".equals(sex) || "女性".equals(sex);
String errorMessage = null;
if (!male && !female) {
errorMessage = "系统人员档案性别为空或无法识别,请维护后重新导入";
} else if (Integer.valueOf(1).equals(isMenWomen) && !male) {
errorMessage = "该项目为男子组,系统人员档案性别为女,不能报名";
} else if (Integer.valueOf(2).equals(isMenWomen) && !female) {
errorMessage = "该项目为女子组,系统人员档案性别为男,不能报名";
}
if (errorMessage != null) {
importTemp.setNotes(errorMessage);
errorList.add(importTemp);
continue;
}
}
activitySchoolApply.setEventId(schoolEvent.getEventId());
activitySchoolApply.setAwardsMode(activityEvent.getProjectType());
if ("2".equals(activityEvent.getProjectType())) {
@@ -1,6 +1,5 @@
package com.budwk.app.zhgh.welfare.service.impl;
import cn.afterturn.easypoi.entity.ImageEntity;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
@@ -23,8 +22,12 @@ import com.budwk.app.zhgh.welfare.model.WelfareProjectSubjectOption;
import com.budwk.app.zhgh.welfare.param.WelfareSelectionSituationPageForm;
import com.budwk.app.zhgh.welfare.service.WelfareSelectionSituationService;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
@@ -246,7 +249,8 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
byte[] userSignBytes = SysFileMinIoUtil.getFileBytes(file.getBucket(), file.getStoragePath());
map.put("userSign", userSignBytes);
}catch (Exception e){
e.printStackTrace();
// 下载失败时不把原文件地址写入签字单元格。
map.put("userSign", null);
log.error("下载图片失败", e);
}
}
@@ -268,21 +272,66 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
optionList.forEach(option -> {
entities.add(new ExcelExportEntity(option.getOptionName(), option.getOptionName(), 20));
});
// 签字列先按空白文本导出,图片另行写入,避开 EasyPOI 对 Map 图片字段的反射。
ExcelExportEntity userSignEntity = new ExcelExportEntity("签字", "userSign", 20);
userSignEntity.setType(2);
userSignEntity.setExportImageType(2);
entities.add(userSignEntity);
// 导出
Workbook workbook = new HSSFWorkbook();
Workbook workbook = new XSSFWorkbook();
listMap.forEach((k, v) -> {
List<byte[]> signatures = new ArrayList<>();
v.forEach(row -> {
Object signature = row.remove("userSign");
signatures.add(signature instanceof byte[] ? (byte[]) signature : null);
});
ExcelExportService service = new ExcelExportService();
ExportParams exportParams = new ExportParams();
exportParams.setSheetName(k);
exportParams.setType(ExcelType.HSSF);
exportParams.setType(ExcelType.XSSF);
int firstSheet = workbook.getNumberOfSheets();
service.createSheetForMap(workbook, exportParams, entities, v);
// 无标题、单层列头的数据从第二行开始;按新增 Sheet 顺序兼容 EasyPOI 自动拆表。
int signatureIndex = 0;
for (int sheetIndex = firstSheet; sheetIndex < workbook.getNumberOfSheets(); sheetIndex++) {
Sheet sheet = workbook.getSheetAt(sheetIndex);
Drawing<?> drawing = sheet.createDrawingPatriarch();
for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum() && signatureIndex < signatures.size(); rowIndex++) {
byte[] signature = signatures.get(signatureIndex++);
if (signature != null && signature.length > 0) {
insertReceiveSignature(workbook, sheet, drawing, rowIndex, entities.size() - 1, signature);
}
}
}
});
CommonDownloadUtil.download("领取表.xls", workbook, response);
CommonDownloadUtil.download("领取表.xlsx", workbook, response);
}
/**
* 将签字字节数组写入领取表指定单元格。行列索引从 0 开始,图片统一转为 PNG;
* 无法解码的图片仅记录日志并保留空白签字格,不中断其他人员的导出。
*/
private void insertReceiveSignature(Workbook workbook, Sheet sheet, Drawing<?> drawing,
int rowIndex, int columnIndex, byte[] signature) {
try (ByteArrayInputStream input = new ByteArrayInputStream(signature);
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
BufferedImage image = ImageIO.read(input);
if (image == null) {
log.warn("领取表签字图片无法识别,Sheet={},行={}", sheet.getSheetName(), rowIndex + 1);
return;
}
ImageIO.write(image, "png", output);
Row row = sheet.getRow(rowIndex);
row.setHeightInPoints(Math.max(row.getHeightInPoints(), 50));
ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
anchor.setCol1(columnIndex);
anchor.setCol2(columnIndex + 1);
anchor.setRow1(rowIndex);
anchor.setRow2(rowIndex + 1);
anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
drawing.createPicture(anchor, workbook.addPicture(output.toByteArray(), Workbook.PICTURE_TYPE_PNG));
} catch (Exception e) {
log.error("领取表签字图片写入失败,Sheet={},行={}", sheet.getSheetName(), rowIndex + 1, e);
}
}
}
@@ -124,7 +124,10 @@
#v4-home-app .todo-center .el-tabs__nav { display: flex; width: 100%; border-color: #e9eff6; border-radius: 6px 6px 0 0; }
#v4-home-app .todo-center .el-tabs__item { flex: 1; min-width: 0; padding: 0 7px; font-size: 12px; height: 36px; line-height: 36px; text-align: center; }
#v4-home-app .todo-center .el-tabs__item.is-active { border-bottom: 2px solid #1687f5; color: #1687f5; }
#v4-home-app .portal-task-list { display: flex; flex-direction: column; justify-content: space-evenly; flex: 1; min-height: 180px; }
/* 任务从标签栏下方依次排列,避免单条记录被剩余高度推到中间。 */
#v4-home-app .portal-task-list { display: flex; flex-direction: column; justify-content: flex-start; flex: 1; min-height: 180px; }
/* 空数据提示独立占满列表区域,保留居中展示。 */
#v4-home-app .portal-task-list > .portal-empty { flex: 1; }
#v4-home-app .portal-task-row { display: flex; gap: 10px; align-items: center; padding: 8px 6px; border-bottom: 1px solid #eef3f8; }
#v4-home-app .portal-task-row:last-child { border-bottom: 0; }
#v4-home-app .portal-task-row:hover { background: #f7fbff; }
@@ -181,7 +181,7 @@ layout("/layouts/v4/baseLayout.html"){
</style>
<link rel="stylesheet" href="${base!}/assets/platform/css/home-portal.css?v=20261030-1" />
<link rel="stylesheet" href="${base!}/assets/platform/css/home-portal.css?v=20261102-1" />
<!-- 登录资料仅在 HTML 属性中输出并转义;放在 Vue 挂载区域外,避免名称被当作模板表达式解析。 -->
<div id="v4-home-profile-data" hidden
@@ -82,6 +82,7 @@ layout("/layouts/platform.html"){
</el-popover>
</table-tool>
<el-table
ref="memberTableRef"
:data="tableData"
style="width: 100%"
stripe
@@ -102,12 +103,12 @@ layout("/layouts/platform.html"){
<el-table-column
align="center"
header-align="center"
v-for="column in tableColumns"
v-for="column in showColumns"
:key="column.prop"
show-overflow-tooltip
v-if="checkedFields.includes(column.prop)"
:label="column.label"
:prop="column.prop"
:width="column.width"
:min-width="column.width || 120"
:fixed="column.fixed"
:sortable="column.sortable"
>
@@ -268,6 +269,16 @@ layout("/layouts/platform.html"){
this.checkedFields = all.filter(p => !this.checkedFields.includes(p));
}
},
watch: {
checkedFields() {
// 可见列更新后重新计算列宽,保证右侧固定操作列与表头、数据行对齐。
this.$nextTick(() => {
if (this.$refs.memberTableRef) {
this.$refs.memberTableRef.doLayout()
}
})
}
},
computed: {
showColumns() {
return this.tableColumns.filter(c => this.checkedFields.includes(c.prop));