From 3304b9113f03d96d0cd45f1abe0ac6a660e6d559 Mon Sep 17 00:00:00 2001 From: zhangrui Date: Wed, 1 Jul 2026 18:17:27 +0800 Subject: [PATCH 1/4] change --- .../controller/SysDataUserPullController.java | 2 +- .../controller/AssetManageController.java | 55 ++++ .../asset/mode/AssetImportExcelMode.java | 65 ++++ .../asset/service/AssetService.java | 11 + .../asset/service/impl/AssetServiceImpl.java | 280 ++++++++++++++++++ .../info/MemberInfoGroupController.java | 8 +- .../MemberPersonalInfoMaintainController.java | 113 +++++++ .../statistics/MemberSummaryController.java | 50 +++- .../member/models/MemberChild.java | 59 ++++ .../member/service/MemberChildService.java | 28 ++ .../member/service/MemberInfoService.java | 16 + .../service/impl/MemberChildServiceImpl.java | 60 ++++ .../service/impl/MemberInfoServiceImpl.java | 82 +++++ .../dayofficework/asset/manage/index.html | 16 +- .../member/common/info/memberInfo.js | 38 ++- .../staffmanage/member/info/group/index.html | 1 + .../member/info/maintain/edit.html | 226 ++++++++++++++ .../member/info/maintain/index.html | 176 +++++++++++ .../member/statistics/summary/index.html | 8 +- 19 files changed, 1271 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/asset/mode/AssetImportExcelMode.java create mode 100644 src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java create mode 100644 src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberChild.java create mode 100644 src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberChildService.java create mode 100644 src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberChildServiceImpl.java create mode 100644 src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html create mode 100644 src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html diff --git a/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java b/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java index 1bb61fd..d931a97 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java @@ -42,7 +42,7 @@ public class SysDataUserPullController { @ApiOperation("分页数据") @Ok("json:{locked:'password|idCard'}") public Result pageData(@Valid SysDataUserPullPageForm pageForm) { - Sql sql = Sqls.create("select us.*,su.`name` AS unit_name from sys_user_source us left join sys_unit su ON su.id = us.unitId $condition"); + Sql sql = Sqls.create("select us.*,su.`name` AS unitName from sys_user_source us left join sys_unit su ON su.id = us.unitId $condition"); Cnd cnd = Cnd.NEW(); cnd.andEX(Sys_user_source::getPullTime, "=", pageForm.getPullTime()); cnd.and(Cnd.likeEX(Sys_user_source::getUsername, pageForm.getUserName())); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java index a9346b0..161d4fc 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java @@ -30,12 +30,17 @@ import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.AdaptBy; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; +import org.nutz.mvc.upload.TempFile; +import org.nutz.mvc.upload.UploadAdaptor; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.List; /** @@ -179,6 +184,7 @@ public class AssetManageController { @At @ApiOperation("按条件导出资产台账") @SaCheckPermission("asset.manage") + @Ok("void") @SLog(type = "assetManage", tag = "资产台账", msg = "按条件导出资产台账") public void doExport(AssetPageForm pageForm, HttpServletResponse response) { Sql sql = assetService.getSql(pageForm); @@ -213,4 +219,53 @@ public class AssetManageController { e.printStackTrace(); } } + + /** + * 导入资产台账,controller 只负责接收文件,具体解析和业务转换放在 service 层。 + * + * @param file 页面上传的资产导入 Excel + * @return 导入结果,包含成功数量和失败明细 + */ + @At + @ApiOperation("导入资产台账") + @Aop(TransAop.READ_COMMITTED) + @SLog(type = "assetManage", tag = "资产台账", msg = "导入资产台账") + @SaCheckPermission("asset.manage") + @AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"}) + public Result doImport(@Param("file") TempFile file) { + return Result.success(assetService.importAssets(file)); + } + + /** + * 下载资产导入模板,字段与导入解析 VO 保持一致,不包含预计报废时间。 + * + * @param response 文件下载响应 + */ + @At + @Ok("void") + @ApiOperation("下载资产导入模板") + @SaCheckPermission("asset.manage") + public void downloadTemplate(HttpServletResponse response) { + List entities = new ArrayList<>(); + entities.add(new ExcelExportEntity("资产编号", "assetNumber", 20)); + entities.add(new ExcelExportEntity("资产名称", "assetName", 20)); + entities.add(new ExcelExportEntity("资产类别", "assetCategoryName", 20)); + entities.add(new ExcelExportEntity("资产规格型号", "assetSpecs", 20)); + entities.add(new ExcelExportEntity("供应商名称", "assetSupplierName", 20)); + entities.add(new ExcelExportEntity("采购方式", "assetFundingSubjectName", 20)); + entities.add(new ExcelExportEntity("用途", "assetPurposeName", 20)); + entities.add(new ExcelExportEntity("单价", "assetUnitPrice", 20)); + entities.add(new ExcelExportEntity("数量", "assetQuantity", 20)); + entities.add(new ExcelExportEntity("资产类型", "assetTypeName", 20)); + entities.add(new ExcelExportEntity("开始使用日期", "assetUsedDate", 20)); + entities.add(new ExcelExportEntity("负责人", "assetUseUserName", 20)); + entities.add(new ExcelExportEntity("使用/管理部门", "assetUseUnionName", 20)); + entities.add(new ExcelExportEntity("存放地点", "assetStorageLocation", 20)); + entities.add(new ExcelExportEntity("使用状况", "assetUsageStateName", 20)); + entities.add(new ExcelExportEntity("备注", "assetNotes", 30)); + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, Collections.emptyList()); + CommonDownloadUtil.download("资产导入模板.xlsx", workbook, response); + } } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/mode/AssetImportExcelMode.java b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/mode/AssetImportExcelMode.java new file mode 100644 index 0000000..044f86d --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/mode/AssetImportExcelMode.java @@ -0,0 +1,65 @@ +package com.budwk.app.zhgh.dayofficework.asset.mode; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.budwk.app.base.model.ExcelImportError; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 资产台账导入模板行数据,字段与资产新增页面红框内可录入字段保持一致。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AssetImportExcelMode extends ExcelImportError { + + @Excel(name = "资产编号", width = 20) + private String assetNumber; + + @Excel(name = "资产名称", width = 20) + private String assetName; + + @Excel(name = "资产类别", width = 20) + private String assetCategoryName; + + @Excel(name = "资产规格型号", width = 20) + private String assetSpecs; + + @Excel(name = "供应商名称", width = 20) + private String assetSupplierName; + + @Excel(name = "采购方式", width = 20) + private String assetFundingSubjectName; + + @Excel(name = "用途", width = 20) + private String assetPurposeName; + + @Excel(name = "单价", width = 20) + private BigDecimal assetUnitPrice; + + @Excel(name = "数量", width = 20) + private Integer assetQuantity; + + @Excel(name = "资产类型", width = 20) + private String assetTypeName; + + @Excel(name = "开始使用日期", width = 20, format = "yyyy-MM-dd") + private Date assetUsedDate; + + @Excel(name = "负责人", width = 20) + private String assetUseUserName; + + @Excel(name = "使用/管理部门", width = 20) + private String assetUseUnionName; + + @Excel(name = "存放地点", width = 20) + private String assetStorageLocation; + + @Excel(name = "使用状况", width = 20) + private String assetUsageStateName; + + @Excel(name = "备注", width = 30) + private String assetNotes; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/AssetService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/AssetService.java index 49bb5b6..8e78442 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/AssetService.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/AssetService.java @@ -1,10 +1,13 @@ package com.budwk.app.zhgh.dayofficework.asset.service; import com.budwk.app.base.service.BaseService; +import com.budwk.app.base.model.ExcelImportRes; +import com.budwk.app.zhgh.dayofficework.asset.mode.AssetImportExcelMode; import com.budwk.app.zhgh.dayofficework.asset.model.Asset; import com.budwk.app.zhgh.dayofficework.asset.param.AssetPageForm; import org.nutz.dao.sql.Sql; import org.nutz.lang.util.NutMap; +import org.nutz.mvc.upload.TempFile; public interface AssetService extends BaseService { @@ -12,4 +15,12 @@ public interface AssetService extends BaseService { Sql getSql(AssetPageForm pageForm); NutMap findOne(String id); + + /** + * 导入资产台账数据,负责 Excel 解析、字段转换、预计报废时间计算和折旧记录初始化。 + * + * @param file 页面上传的资产 Excel 文件 + * @return 导入结果,包含成功数和失败明细 + */ + ExcelImportRes importAssets(TempFile file); } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/impl/AssetServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/impl/AssetServiceImpl.java index 566d3da..5dd4b77 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/impl/AssetServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/impl/AssetServiceImpl.java @@ -1,21 +1,39 @@ package com.budwk.app.zhgh.dayofficework.asset.service.impl; +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ImportParams; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.model.ExcelImportRes; import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.sys.models.Sys_dict; +import com.budwk.app.sys.models.Sys_union; +import com.budwk.app.sys.services.SysDictService; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.dayofficework.asset.mode.AssetImportExcelMode; import com.budwk.app.zhgh.dayofficework.asset.model.Asset; +import com.budwk.app.zhgh.dayofficework.asset.model.AssetCategory; +import com.budwk.app.zhgh.dayofficework.asset.model.AssetDepreciationRecord; import com.budwk.app.zhgh.dayofficework.asset.model.AssetStocktaking; import com.budwk.app.zhgh.dayofficework.asset.param.AssetPageForm; +import com.budwk.app.zhgh.dayofficework.asset.service.AssetDepreciationRecordService; import com.budwk.app.zhgh.dayofficework.asset.service.AssetService; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; import org.nutz.dao.util.cri.SqlExpressionGroup; +import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.util.NutMap; +import org.nutz.mvc.upload.TempFile; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; /** * @author zhf @@ -28,6 +46,12 @@ public class AssetServiceImpl extends BaseServiceImpl implements AssetSer super(dao); } + @Inject + private AssetDepreciationRecordService assetDepreciationRecordService; + + @Inject + private SysDictService sysDictService; + @Override public Sql getSql(AssetPageForm pageForm) { Sql sql = Sqls.create(""" @@ -83,4 +107,260 @@ public class AssetServiceImpl extends BaseServiceImpl implements AssetSer Cnd.where("assetId", "=", id))); return nutMap; } + + /** + * 导入资产台账数据,逐行完成字段校验和新增,失败行不影响其他行入库。 + * + * @param file 页面上传的资产 Excel 文件 + * @return 导入统计结果和失败明细 + */ + @Override + public ExcelImportRes importAssets(TempFile file) { + List importRows = ExcelImportUtil.importExcel(file.getFile(), AssetImportExcelMode.class, new ImportParams()); + ExcelImportRes importRes = new ExcelImportRes<>(); + importRes.setTotalRecords(importRows.size()); + for (int i = 0; i < importRows.size(); i++) { + AssetImportExcelMode row = importRows.get(i); + List errors = new ArrayList<>(); + Asset asset = buildImportAsset(row, errors); + if (!errors.isEmpty()) { + row.setErrInfo(String.join(";", errors), i + 2); + continue; + } + insertAssetWithDepreciation(asset); + } + importRes.setErrorDetails(importRows.stream().filter(row -> StrUtil.isNotBlank(row.getErrMsg())).collect(Collectors.toList())); + importRes.setFailedCount(importRes.getErrorDetails().size()); + importRes.setSuccessCount(Math.max(importRows.size() - importRes.getFailedCount(), 0)); + return importRes; + } + + /** + * 将 Excel 行转换为资产实体,包含字典值、单选值、负责人和部门等页面字段转换。 + * + * @param row Excel 导入行 + * @param errors 当前行错误收集器 + * @return 可入库的资产实体 + */ + private Asset buildImportAsset(AssetImportExcelMode row, List errors) { + Asset asset = new Asset(); + asset.setAssetNumber(StrUtil.trim(row.getAssetNumber())); + asset.setAssetName(requiredText(row.getAssetName(), "资产名称", errors)); + AssetCategory category = resolveAssetCategory(row.getAssetCategoryName(), errors); + if (category != null) { + asset.setAssetCategoryId(category.getId()); + } + asset.setAssetSpecs(requiredText(row.getAssetSpecs(), "资产规格型号", errors)); + asset.setAssetSupplierName(requiredText(row.getAssetSupplierName(), "供应商名称", errors)); + asset.setAssetFundingSubjectName(resolveFixedOption(row.getAssetFundingSubjectName(), "采购方式", Set.of("网购", "线下", "采购"), errors)); + asset.setAssetPurposeName(resolveFixedOption(row.getAssetPurposeName(), "用途", Set.of("职工之家建设", "日常办公", "其他"), errors)); + asset.setAssetUnitPrice(row.getAssetUnitPrice()); + asset.setAssetQuantity(row.getAssetQuantity()); + if (asset.getAssetUnitPrice() == null) { + errors.add("单价不能为空"); + } + if (asset.getAssetQuantity() == null || asset.getAssetQuantity() <= 0) { + errors.add("数量不能为空且必须大于0"); + } + asset.setAssetTypeCode(resolveAssetType(row.getAssetTypeName(), errors)); + asset.setAssetUsedDate(row.getAssetUsedDate()); + if (asset.getAssetUsedDate() == null) { + errors.add("开始使用日期不能为空"); + } + NutMap user = resolveAssetUseUser(row.getAssetUseUserName(), errors); + if (user != null) { + asset.setAssetUseUserId(user.getString("id")); + } + Sys_union union = resolveAssetUseUnion(row.getAssetUseUnionName(), asset.getAssetTypeCode(), user, errors); + if (union != null) { + asset.setAssetUseUnionId(union.getId()); + asset.setAssetUseUnionName(union.getName()); + } + asset.setAssetStorageLocation(requiredText(row.getAssetStorageLocation(), "存放地点", errors)); + asset.setAssetUsageStateName(resolveDictName(row.getAssetUsageStateName(), "ASSET_USAGE_STATE", "使用状况", errors)); + asset.setAssetNotes(StrUtil.trim(row.getAssetNotes())); + if (category != null && asset.getAssetUsedDate() != null) { + asset.setAssetRetiredAssetsDate(calculateRetiredAssetsDate(asset.getAssetUsedDate(), category.getDepreciationYear())); + } + return asset; + } + + /** + * 新增资产并按现有新增逻辑初始化折旧记录。 + * + * @param asset 已完成转换和校验的资产 + */ + private void insertAssetWithDepreciation(Asset asset) { + if (StrUtil.isBlank(asset.getAssetNumber())) { + asset.setAssetNumber(nextAssetNumber()); + } + asset.setApplyUserId(SecurityUtil.getUserId()); + insert(asset); + AssetDepreciationRecord record = assetDepreciationRecordService.initAssetDepreciationRecord(asset.getAssetUnitPrice(), asset.getAssetQuantity(), asset.getAssetUsedDate(), asset.getAssetRetiredAssetsDate()); + if (record != null) { + record.setAssetId(asset.getId()); + assetDepreciationRecordService.insert(record); + } + } + + /** + * 按新增资产页面逻辑计算预计报废时间:开始使用日期加资产类别折旧年限。 + * + * @param assetUsedDate 开始使用日期 + * @param depreciationYear 资产类别折旧年限 + * @return 预计报废时间 + */ + private java.util.Date calculateRetiredAssetsDate(java.util.Date assetUsedDate, int depreciationYear) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(assetUsedDate); + calendar.add(Calendar.YEAR, depreciationYear); + return calendar.getTime(); + } + + /** + * 生成与新增资产一致的资产编号:当前年份 + 4 位流水。 + * + * @return 新资产编号 + */ + private String nextAssetNumber() { + int year = Calendar.getInstance().get(Calendar.YEAR); + Sql sql = Sqls.create(""" + SELECT count(1) FROM `asset` where LEFT(assetNumber,4) = @year and LENGTH(assetNumber) = 8 + """).setParam("year", year); + int count = count(sql); + return year + String.format("%04d", count + 1); + } + + /** + * 按资产类别名称、编码或 ID 匹配资产类别。 + */ + private AssetCategory resolveAssetCategory(String value, List errors) { + String text = requiredText(value, "资产类别", errors); + if (StrUtil.isBlank(text)) { + return null; + } + AssetCategory category = dao().fetch(AssetCategory.class, Cnd.where("categoryName", "=", text).or("categoryCode", "=", text).or("id", "=", text)); + if (category == null) { + errors.add("资产类别不存在:" + text); + } + return category; + } + + /** + * 将资产类型单选文本转换为保存值。 + */ + private Integer resolveAssetType(String value, List errors) { + String text = requiredText(value, "资产类型", errors); + if (StrUtil.isBlank(text)) { + return null; + } + if ("校工会资产".equals(text) || "1".equals(text)) { + return 1; + } + if ("分工会资产".equals(text) || "2".equals(text)) { + return 2; + } + errors.add("资产类型只能填写校工会资产或分工会资产"); + return null; + } + + /** + * 使用/管理部门:校工会资产固定为校工会;分工会资产按导入值或负责人所属工会转换。 + */ + private Sys_union resolveAssetUseUnion(String value, Integer assetTypeCode, NutMap user, List errors) { + if (assetTypeCode != null && assetTypeCode == 1) { + Sys_union union = new Sys_union(); + union.setId(user == null ? null : user.getString("unionId")); + union.setName("校工会"); + return union; + } + String text = StrUtil.trim(value); + if (StrUtil.isBlank(text) && user != null) { + text = user.getString("unionName"); + } + if (StrUtil.isBlank(text)) { + errors.add("使用/管理部门不能为空"); + return null; + } + Sys_union union = dao().fetch(Sys_union.class, Cnd.where("name", "=", text).or("unionCode", "=", text).or("id", "=", text)); + if (union == null) { + errors.add("使用/管理部门不存在:" + text); + } + return union; + } + + /** + * 负责人支持工号、姓名、用户 ID,以及页面下拉常见的“姓名-工号”格式。 + */ + private NutMap resolveAssetUseUser(String value, List errors) { + String text = requiredText(value, "负责人", errors); + if (StrUtil.isBlank(text)) { + return null; + } + String keyword = text.contains("-") ? StrUtil.subAfter(text, "-", true) : text; + Sql sql = Sqls.create(""" + SELECT + id, + loginname, + username, + unionName, + unionId + FROM + `vw_user` + WHERE + id = @keyword + OR loginname = @keyword + OR username = @keyword + """).setParam("keyword", keyword); + NutMap user = (NutMap) dao().execute(sql.setCallback(Sqls.callback.map())).getResult(); + if (user == null) { + errors.add("负责人不存在:" + text); + } + return user; + } + + /** + * 将数据字典文本或编码转换为当前系统保存的字典名称。 + */ + private String resolveDictName(String value, String dictCode, String fieldName, List errors) { + String text = requiredText(value, fieldName, errors); + if (StrUtil.isBlank(text)) { + return null; + } + Sys_dict dict = sysDictService.getSubListByCode(dictCode).stream() + .filter(item -> text.equals(item.getName()) || text.equals(item.getCode())) + .findFirst() + .orElse(null); + if (dict == null) { + errors.add(fieldName + "字典值不存在:" + text); + return null; + } + return dict.getName(); + } + + /** + * 校验固定选项字段,导入值按新增页面下拉选项保存为中文名称。 + */ + private String resolveFixedOption(String value, String fieldName, Set options, List errors) { + String text = requiredText(value, fieldName, errors); + if (StrUtil.isBlank(text)) { + return null; + } + if (!options.contains(text)) { + errors.add(fieldName + "只能填写:" + String.join("、", options)); + return null; + } + return text; + } + + /** + * 必填文本统一去空格,便于导入错误提示保持一致。 + */ + private String requiredText(String value, String fieldName, List errors) { + String text = StrUtil.trim(value); + if (StrUtil.isBlank(text)) { + errors.add(fieldName + "不能为空"); + } + return text; + } } diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberInfoGroupController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberInfoGroupController.java index 3ff2d15..4d49a4e 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberInfoGroupController.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberInfoGroupController.java @@ -65,10 +65,10 @@ public class MemberInfoGroupController { public Result pageData(MemberInfoPageForm pageForm) { Sql sql = memberInfoService.getSql(pageForm); Pagination pagination = memberInfoService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); - for (NutMap row : pagination.getList()) { - row.put("mobile",DesensitizedUtil.mobilePhone(row.getString("mobile"))); - row.put("idCard",DesensitizedUtil.idCardNum(row.getString("idCard"), 3, 4)); - } +// for (NutMap row : pagination.getList()) { +// row.put("mobile",DesensitizedUtil.mobilePhone(row.getString("mobile"))); +// row.put("idCard",DesensitizedUtil.idCardNum(row.getString("idCard"), 3, 4)); +// } return Result.success(pagination); } diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java new file mode 100644 index 0000000..c61a4da --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java @@ -0,0 +1,113 @@ +package com.budwk.app.zhgh.staffmanage.member.controller.info; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.staffmanage.member.models.MemberChild; +import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberInfoPageForm; +import com.budwk.app.zhgh.staffmanage.member.service.MemberChildService; +import com.budwk.app.zhgh.staffmanage.member.service.MemberInfoService; +import org.nutz.dao.sql.Sql; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.json.Json; +import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import java.util.ArrayList; +import java.util.List; + +/** + * 个人信息维护控制器,提供会员基础信息只读展示和子女信息维护入口。 + */ +@IocBean +@Ok("json:full") +@At("/platform/member/info/maintain") +public class MemberPersonalInfoMaintainController { + + @Inject + private MemberInfoService memberInfoService; + + @Inject + private MemberChildService memberChildService; + + /** + * 个人信息维护列表页,查询条件和列表字段复用会员档案管理页面。 + */ + @At("") + @Ok("beetl:/platform/zhgh/staffmanage/member/info/maintain/index.html") + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public void index() { + } + + /** + * 个人信息维护编辑页,会员原有字段只读,子女信息允许维护。 + */ + @At("/edit") + @Ok("beetl:/platform/zhgh/staffmanage/member/info/maintain/edit.html") + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public void edit() { + } + + /** + * 查询会员列表,沿用会员档案管理的查询条件和权限过滤。 + * + * @param pageForm 会员档案查询参数 + * @return 分页会员列表 + */ + @At + @Ok("json:{dateFormat:'yyyy-MM-dd'}") + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public Result pageData(MemberInfoPageForm pageForm) { + Sql sql = memberInfoService.getPersonalMaintainSql(pageForm); + Pagination pagination = memberInfoService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + return Result.success(pagination); + } + + /** + * 查询个人信息维护详情,返回只读会员信息和可编辑子女信息。 + * + * @param userId 用户ID + * @return 会员信息和子女信息 + */ + @At + @Ok("json:{dateFormat:'yyyy-MM-dd'}") + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public Result detail(String userId) { + try { + NutMap data = memberInfoService.getPersonalMaintainInfo(userId); + data.put("children", memberChildService.listByUserId(userId)); + return Result.success(data); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } + + /** + * 保存子女信息,原有会员字段不接收保存,仅覆盖当前会员的子女资料。 + * + * @param userId 用户ID + * @param childrenJson 子女信息JSON字符串 + * @return 保存结果 + */ + @At + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public Result saveChildren(String userId, @Param("children") String childrenJson) { + try { + // 保存前复用详情权限校验,防止绕过列表入口修改无权限会员的子女信息。 + memberInfoService.getPersonalMaintainInfo(userId); + List children = new ArrayList<>(); + if (Strings.isNotBlank(childrenJson)) { + children = Json.fromJsonAsList(MemberChild.class, childrenJson); + } + memberChildService.saveUserChildren(userId, children); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberSummaryController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberSummaryController.java index 78a579b..22fe8f6 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberSummaryController.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberSummaryController.java @@ -2,6 +2,7 @@ package com.budwk.app.zhgh.staffmanage.member.controller.statistics; import cn.dev33.satoken.annotation.SaCheckPermission; import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.zhgh.staffmanage.member.service.MemberStatisticsService; import org.apache.poi.ss.usermodel.Workbook; import org.nutz.ioc.loader.annotation.Inject; @@ -56,28 +57,49 @@ public class MemberSummaryController { public void doExport(@Param("queryId") String queryId, @Param("queryType") String queryType, @Param("currentYear") Integer currentYear, HttpServletResponse response) { List list = new ArrayList<>(); - if ("union".equals(queryType)) { + boolean unionQuery = isUnionQuery(queryType); + if (unionQuery) { list = memberStatisticsService.getUnionStatisticsData(queryId, currentYear); } else { list = memberStatisticsService.getUnitStatisticsData(queryId, currentYear); } + // 导出数据与页面查询保持一致,并追加合计行方便 Excel 查看总量。 NutMap map = NutMap.NEW(); - map.put("union".equals(queryType) ? "unionName" : "unitName", "合计"); - map.put("totalNumber", list.stream().mapToInt(v -> v.getInt("TotalNumber")).sum()); - map.put("maleMember", list.stream().mapToInt(v -> v.getInt("maleMember")).sum()); - map.put("femaleMember", list.stream().mapToInt(v -> v.getInt("femaleMember")).sum()); - map.put("smallForty", list.stream().mapToInt(v -> v.getInt("smallForty")).sum()); - map.put("smallFifty", list.stream().mapToInt(v -> v.getInt("smallFifty")).sum()); - map.put("smallFiftyFive", list.stream().mapToInt(v -> v.getInt("smallFiftyFive")).sum()); - map.put("smallFiftyFive2", list.stream().mapToInt(v -> v.getInt("smallFiftyFive2")).sum()); + map.put(unionQuery ? "unionName" : "unitName", "合计"); + map.put("totalNumber", sumInt(list, "totalNumber")); + map.put("maleMember", sumInt(list, "maleMember")); + map.put("femaleMember", sumInt(list, "femaleMember")); + map.put("smallForty", sumInt(list, "smallForty")); + map.put("smallFifty", sumInt(list, "smallFifty")); + map.put("smallFiftyFive", sumInt(list, "smallFiftyFive")); + map.put("smallFiftyFive2", sumInt(list, "smallFiftyFive2")); + list.add(map); try { - response.setContentType("application/octet-stream"); - response.setHeader("Content-Disposition", "attachment;filename=" + new String(("会员性别统计.xlsx").getBytes("utf-8"), "ISO8859-1")); - Workbook workbook = memberStatisticsService.exportStatisticsExcel(list, queryType); - workbook.write(response.getOutputStream()); - workbook.close(); + Workbook workbook = memberStatisticsService.exportStatisticsExcel(list, unionQuery ? "union" : "unit"); + CommonDownloadUtil.download("会员统计.xlsx", workbook, response); } catch (Exception e) { e.printStackTrace(); } } + + /** + * 判断统计维度,兼容页面传入的 fgh 和服务层导出使用的 union。 + * + * @param queryType 统计维度 + * @return 是否按分工会统计 + */ + private boolean isUnionQuery(String queryType) { + return "fgh".equals(queryType) || "union".equals(queryType); + } + + /** + * 汇总 NutMap 数值字段,避免空值导致导出合计失败。 + * + * @param list 统计列表 + * @param key 汇总字段名 + * @return 汇总值 + */ + private int sumInt(List list, String key) { + return list.stream().mapToInt(item -> item.getInt(key, 0)).sum(); + } } diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberChild.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberChild.java new file mode 100644 index 0000000..96b3124 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberChild.java @@ -0,0 +1,59 @@ +package com.budwk.app.zhgh.staffmanage.member.models; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.entity.annotation.TableMeta; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +import java.util.Date; + +/** + * 会员子女信息表,维护个人信息维护页面可录入的子女资料。 + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@Table("member_child") +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("会员子女信息") +public class MemberChild extends BaseModel { + + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("用户ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String userId; + + @Column + @Comment("关系") + @ColDefine(type = ColType.VARCHAR, width = 20) + private String relation; + + @Column + @Comment("姓名") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String name; + + @Column + @Comment("性别") + @ColDefine(type = ColType.VARCHAR, width = 10) + private String sex; + + @Column + @Comment("出生日期") + @ColDefine(type = ColType.DATE) + private Date birthday; +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberChildService.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberChildService.java new file mode 100644 index 0000000..7e9ce68 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberChildService.java @@ -0,0 +1,28 @@ +package com.budwk.app.zhgh.staffmanage.member.service; + +import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.staffmanage.member.models.MemberChild; + +import java.util.List; + +/** + * 会员子女信息业务服务,集中处理子女信息查询和保存逻辑。 + */ +public interface MemberChildService extends BaseService { + + /** + * 根据会员用户ID查询子女信息列表。 + * + * @param userId 会员用户ID + * @return 子女信息列表 + */ + List listByUserId(String userId); + + /** + * 保存会员子女信息,页面按整表提交,服务层负责覆盖当前会员的子女资料。 + * + * @param userId 会员用户ID + * @param children 页面提交的子女信息列表 + */ + void saveUserChildren(String userId, List children); +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java index 9b76e76..8420f6d 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java @@ -27,6 +27,22 @@ public interface MemberInfoService extends BaseService { */ Sql getSql(MemberInfoPageForm pageForm); + /** + * 获取个人信息维护列表查询SQL,系统管理员查看全部,其他人员仅查看本人。 + * + * @param pageForm 会员档案查询参数 + * @return 个人信息维护列表查询SQL + */ + Sql getPersonalMaintainSql(MemberInfoPageForm pageForm); + + /** + * 获取个人信息维护详情,复用会员档案查询权限展示会员基础信息。 + * + * @param userId 用户ID + * @return 会员基础信息 + */ + NutMap getPersonalMaintainInfo(String userId); + /** * 获取会员历史 * @param pageForm diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberChildServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberChildServiceImpl.java new file mode 100644 index 0000000..04e8474 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberChildServiceImpl.java @@ -0,0 +1,60 @@ +package com.budwk.app.zhgh.staffmanage.member.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.zhgh.staffmanage.member.models.MemberChild; +import com.budwk.app.zhgh.staffmanage.member.service.MemberChildService; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Lang; + +import java.util.List; + +/** + * 会员子女信息业务实现,负责个人信息维护页面的子女资料持久化。 + */ +@IocBean(args = {"refer:dao"}) +public class MemberChildServiceImpl extends BaseServiceImpl implements MemberChildService { + + public MemberChildServiceImpl(Dao dao) { + super(dao); + } + + /** + * 根据会员用户ID查询子女信息列表,并按创建时间保持页面展示顺序。 + * + * @param userId 会员用户ID + * @return 子女信息列表 + */ + @Override + public List listByUserId(String userId) { + return query(Cnd.where("userId", "=", userId).asc("createdAt")); + } + + /** + * 覆盖保存指定会员的子女信息,避免前端逐条新增删除导致数据残留。 + * + * @param userId 会员用户ID + * @param children 页面提交的子女信息列表 + */ + @Override + @Aop(TransAop.READ_COMMITTED) + public void saveUserChildren(String userId, List children) { + clear(Cnd.where("userId", "=", userId)); + if (Lang.isEmpty(children)) { + return; + } + children.stream() + .filter(child -> StrUtil.isNotBlank(child.getRelation()) || StrUtil.isNotBlank(child.getName()) + || StrUtil.isNotBlank(child.getSex()) || child.getBirthday() != null) + .forEach(child -> { + // 子女信息归属以后端传入的会员ID为准,防止前端篡改归属用户。 + child.setId(null); + child.setUserId(userId); + insert(child); + }); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java index a5d227e..31d67c4 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java @@ -15,6 +15,7 @@ import com.budwk.app.base.utils.easyexcel.EasyExcelUtil; import com.budwk.app.sys.models.Sys_role; import com.budwk.app.sys.models.Sys_unit; import com.budwk.app.sys.models.Sys_user_role; +import com.budwk.app.sys.views.View_user; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.sys.models.Sys_user; import com.budwk.app.web.commons.auth.utils.SecurityUtil; @@ -113,6 +114,87 @@ public class MemberInfoServiceImpl extends BaseServiceImpl implements return sql; } + /** + * 获取个人信息维护详情,非学校管理员只能查看本分工会或未分配分工会的会员。 + * + * @param userId 用户ID + * @return 会员基础信息 + */ + /** + * 获取个人信息维护列表SQL,系统管理员按页面条件查询全部,其他人员强制查询本人数据。 + * + * @param pageForm 会员档案查询参数 + * @return 个人信息维护列表查询SQL + */ + @Override + public Sql getPersonalMaintainSql(MemberInfoPageForm pageForm) { + Sql sql = Sqls.create(""" + SELECT + u.id, + u.loginname, + u.username, + u.sex, + DATE_FORMAT(u.birthday, '%Y-%m-%d') AS birthday, + u.political, + DATE_FORMAT(u.joinPartyDate, '%Y-%m-%d') AS joinPartyDate, + u.nation, + u.nativePlace, + u.nationality, + u.idCardType, + u.mobile, + u.idCard, + u.marriage, + u.education, + u.academicDegree, + u.position, + u.jobCategory, + u.professionalTitle, + u.professionalLevel, + u.employeeSource, + u.administrativeLevel, + u.positionSeries, + u.positionLevel, + u.administrativePositionLevel, + u.arrivalAtSchoolDate, + u.personType, + u.preparedBy, + u.identityType, + u.userState, + u.unionName, + u.unitName, + DATE_FORMAT(u.teachingTime, '%Y-%m-%d') AS teachingTime, + DATE_FORMAT(u.expectedLeaveSchoolDate, '%Y-%m-%d') AS expectedLeaveSchoolDate, + u.member, + DATE_FORMAT(u.postDoctoralJoinDate, '%Y-%m-%d') AS postDoctoralJoinDate + FROM + vw_user u + $condition + """); + Cnd cnd = Cnd.NEW(); + if (AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) { + pageForm.buildSearch(cnd, "u."); + } else { + // 个人信息维护只允许非系统管理员查看本人,避免通过查询条件越权查看其他人员。 + cnd.and("u.id", "=", SecurityUtil.getUserId()); + } + sql.setCondition(cnd); + return sql; + } + + @Override + public NutMap getPersonalMaintainInfo(String userId) { + Cnd cnd = Cnd.where("id", "=", userId); + if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) { + // 非系统管理员只能维护本人信息,保存子女信息前也复用此校验。 + cnd.and("id", "=", SecurityUtil.getUserId()); + } + View_user user = dao().fetch(View_user.class, cnd); + if (user == null) { + throw new RuntimeException("用户信息不存在或无权限查看"); + } + return Lang.obj2nutmap(user); + } + @Override public Sql getMemberHistorySql(MemberInfoPageForm pageForm) { diff --git a/src/main/resources/views/platform/zhgh/dayofficework/asset/manage/index.html b/src/main/resources/views/platform/zhgh/dayofficework/asset/manage/index.html index 81e3466..b93c2cf 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/asset/manage/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/asset/manage/index.html @@ -53,6 +53,9 @@ layout("/layouts/platform.html"){ 新增资产 + + 导入资产 + 导出资产 @@ -110,6 +113,16 @@ layout("/layouts/platform.html"){ + @@ -132,7 +145,8 @@ layout("/layouts/platform.html"){ {prop: 'assetUsageStateName', label: '使用状况'}, ], assetCategoryOption: [], - unionList: [] + unionList: [], + showImportDialog: false } }, components: { diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/common/info/memberInfo.js b/src/main/resources/views/platform/zhgh/staffmanage/member/common/info/memberInfo.js index 98edb76..293d839 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/member/common/info/memberInfo.js +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/common/info/memberInfo.js @@ -83,9 +83,35 @@ {{ viewData.positionLevel }} {{ viewData.administrativePositionLevel }} + + + `, store, + props: { + detailUrl: { + type: String, + default: "/platform/member/apply/common/findOne" + }, + idParam: { + type: String, + default: "id" + } + }, data() { return { viewData: {} @@ -93,9 +119,19 @@ }, methods: { onOpen(id) { - this.$axios.post("/platform/member/apply/common/findOne", { id }).then((res) => { + // 详情接口和主键参数由调用页面决定,保证公共查看组件可复用不同权限口径的数据源。 + const params = {} + params[this.idParam] = id + this.viewData = {} + this.$axios.post(this.detailUrl, params).then((res) => { if (res.code === 0) { this.viewData = res.data + this.$nextTick(() => { + // 查看页在隐藏容器中打开时,Element 表格需要重新计算列宽,避免子女信息列头纵向错位。 + if (this.$refs.childrenTable) { + this.$refs.childrenTable.doLayout() + } + }) } }) } diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/info/group/index.html b/src/main/resources/views/platform/zhgh/staffmanage/member/info/group/index.html index d6ab691..ec01b93 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/member/info/group/index.html +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/info/group/index.html @@ -125,6 +125,7 @@ layout("/layouts/platform.html"){ { prop: "nation", label: "民族", width: 120, sortable: true}, { prop: "mobile", label: "联系电话", width: 120 }, { prop: "political", label: "政治面貌", width: 120, sortable: true}, + { prop: "education", label: "学历", width: 120, sortable: true, checked: 0 }, { prop: "userState", label: "在职状态", width: 120, sortable: true }, { prop: "personType", label: "教职工类别", width: 120, sortable: true }, { prop: "preparedBy", label: "编制类别", width: 120, sortable: true }, diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html new file mode 100644 index 0000000..d6135a6 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html @@ -0,0 +1,226 @@ + +
+ +
+ 个人信息维护 +
+ + + + + + + + + + + 男 + 女 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 会员 + 非会员 + + + + + 福利会员 + 非福利会员 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 返回 + 保存 + +
+
+ + diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html new file mode 100644 index 0000000..0680c70 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html @@ -0,0 +1,176 @@ + +
+ + + + + +
+ + diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/statistics/summary/index.html b/src/main/resources/views/platform/zhgh/staffmanage/member/statistics/summary/index.html index 82589b4..9b895c9 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/member/statistics/summary/index.html +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/statistics/summary/index.html @@ -42,7 +42,9 @@ layout("/layouts/platform.html"){ - + + 导出 + { From 9c31bf8153880e7c973330e9c60368637dc015ad Mon Sep 17 00:00:00 2001 From: zhangrui Date: Wed, 1 Jul 2026 18:30:50 +0800 Subject: [PATCH 2/4] change --- .../info/MemberPersonalInfoMaintainController.java | 3 ++- .../member/service/MemberInfoService.java | 8 ++++++++ .../member/service/impl/MemberInfoServiceImpl.java | 13 +++++++++++++ .../zhgh/staffmanage/member/info/maintain/edit.html | 3 ++- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java index c61a4da..88f1ecc 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java @@ -96,7 +96,7 @@ public class MemberPersonalInfoMaintainController { */ @At @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) - public Result saveChildren(String userId, @Param("children") String childrenJson) { + public Result saveChildren(String userId, String marriage, @Param("children") String childrenJson) { try { // 保存前复用详情权限校验,防止绕过列表入口修改无权限会员的子女信息。 memberInfoService.getPersonalMaintainInfo(userId); @@ -104,6 +104,7 @@ public class MemberPersonalInfoMaintainController { if (Strings.isNotBlank(childrenJson)) { children = Json.fromJsonAsList(MemberChild.class, childrenJson); } + memberInfoService.savePersonalMaintainMarriage(userId, marriage); memberChildService.saveUserChildren(userId, children); return Result.success(); } catch (RuntimeException e) { diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java index 8420f6d..a929b27 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java @@ -43,6 +43,14 @@ public interface MemberInfoService extends BaseService { */ NutMap getPersonalMaintainInfo(String userId); + /** + * 保存个人信息维护中允许编辑的婚姻状况字段,其他会员原始字段不在此方法中更新。 + * + * @param userId 用户ID + * @param marriage 婚姻状况字典值 + */ + void savePersonalMaintainMarriage(String userId, String marriage); + /** * 获取会员历史 * @param pageForm diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java index 31d67c4..fd1e750 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java @@ -195,6 +195,19 @@ public class MemberInfoServiceImpl extends BaseServiceImpl implements return Lang.obj2nutmap(user); } + /** + * 保存个人信息维护开放编辑的婚姻状况字段,权限校验复用详情查询口径。 + * + * @param userId 用户ID + * @param marriage 婚姻状况字典值 + */ + @Override + public void savePersonalMaintainMarriage(String userId, String marriage) { + // 先执行详情权限校验,确保非系统管理员不能通过保存接口修改其他人员婚姻状况。 + getPersonalMaintainInfo(userId); + dao().update(Sys_user.class, Chain.make("marriage", marriage), Cnd.where("id", "=", userId)); + } + @Override public Sql getMemberHistorySql(MemberInfoPageForm pageForm) { diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html index d6135a6..1f00f9d 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html @@ -84,7 +84,7 @@ layout("/layouts/platform.html"){ - + @@ -184,6 +184,7 @@ layout("/layouts/platform.html"){ }).then(() => { this.$axios.post("/platform/member/info/maintain/saveChildren", { userId: this.userId, + marriage: this.formData.marriage, children: JSON.stringify(cleanedChildren) }).then((res) => { if (res.code === 0) { From 0083cfeed4bf27999bfee2e9edc26ce3919337e6 Mon Sep 17 00:00:00 2001 From: zhangrui Date: Thu, 2 Jul 2026 16:56:29 +0800 Subject: [PATCH 3/4] change --- .../com/budwk/app/sys/controller/SysDataUserPullController.java | 2 +- src/main/resources/views/platform/sys/data/user/pull.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java b/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java index d931a97..1bb61fd 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java @@ -42,7 +42,7 @@ public class SysDataUserPullController { @ApiOperation("分页数据") @Ok("json:{locked:'password|idCard'}") public Result pageData(@Valid SysDataUserPullPageForm pageForm) { - Sql sql = Sqls.create("select us.*,su.`name` AS unitName from sys_user_source us left join sys_unit su ON su.id = us.unitId $condition"); + Sql sql = Sqls.create("select us.*,su.`name` AS unit_name from sys_user_source us left join sys_unit su ON su.id = us.unitId $condition"); Cnd cnd = Cnd.NEW(); cnd.andEX(Sys_user_source::getPullTime, "=", pageForm.getPullTime()); cnd.and(Cnd.likeEX(Sys_user_source::getUsername, pageForm.getUserName())); diff --git a/src/main/resources/views/platform/sys/data/user/pull.html b/src/main/resources/views/platform/sys/data/user/pull.html index bf9f69f..f1047b5 100644 --- a/src/main/resources/views/platform/sys/data/user/pull.html +++ b/src/main/resources/views/platform/sys/data/user/pull.html @@ -83,7 +83,7 @@ layout("/layouts/platform.html"){ - + From 4bc9d9f073544f72a76780cbd9581b71e16ecf14 Mon Sep 17 00:00:00 2001 From: zhangrui Date: Fri, 3 Jul 2026 15:48:04 +0800 Subject: [PATCH 4/4] change --- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- .../MemberPersonalInfoMaintainController.java | 14 ++ .../member/service/MemberInfoService.java | 8 + .../service/impl/MemberInfoServiceImpl.java | 207 ++++++++++++++++++ .../member/info/maintain/index.html | 16 ++ 6 files changed, 247 insertions(+), 2 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 81cb4a3..0984c76 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.budwk - zhgh_jshvc + mini 5.6.0-plus diff --git a/pom.xml b/pom.xml index 6e1fd03..18f4bae 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 jar com.budwk - zhgh_jshvc + mini 5.6.0-plus 2.6.0-SNAPSHOT diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java index 88f1ecc..05736c7 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java @@ -18,6 +18,7 @@ import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; +import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; @@ -68,6 +69,19 @@ public class MemberPersonalInfoMaintainController { return Result.success(pagination); } + /** + * 导出个人信息维护列表数据,导出条件与页面查询保持一致,具体 Excel 生成逻辑放在 service 层。 + * + * @param pageForm 页面查询和显示列参数 + * @param response 文件下载响应 + */ + @At + @Ok("void") + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public void doExport(MemberInfoPageForm pageForm, HttpServletResponse response) { + memberInfoService.exportPersonalMaintain(pageForm, response); + } + /** * 查询个人信息维护详情,返回只读会员信息和可编辑子女信息。 * diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java index a929b27..5f83f6e 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java @@ -51,6 +51,14 @@ public interface MemberInfoService extends BaseService { */ void savePersonalMaintainMarriage(String userId, String marriage); + /** + * 导出个人信息维护列表数据,查询条件与页面列表保持一致,并汇总导出家庭主要成员信息。 + * + * @param pageForm 页面查询和显示列参数 + * @param response 文件下载响应 + */ + void exportPersonalMaintain(MemberInfoPageForm pageForm, HttpServletResponse response); + /** * 获取会员历史 * @param pageForm diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java index fd1e750..208bcab 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java @@ -20,11 +20,24 @@ import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.sys.models.Sys_user; import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.zhgh.staffmanage.member.models.MemberHistory; +import com.budwk.app.zhgh.staffmanage.member.models.MemberChild; import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberInfoPageForm; import com.budwk.app.zhgh.staffmanage.member.service.MemberInfoService; import com.budwk.app.zhgh.staffmanage.member.vo.MemberImportVo; +import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.formula.functions.T; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Chain; import org.nutz.dao.Cnd; @@ -41,9 +54,12 @@ import org.nutz.lang.util.NutMap; import org.nutz.mvc.upload.TempFile; import javax.servlet.http.HttpServletResponse; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -208,6 +224,197 @@ public class MemberInfoServiceImpl extends BaseServiceImpl implements dao().update(Sys_user.class, Chain.make("marriage", marriage), Cnd.where("id", "=", userId)); } + /** + * 导出个人信息维护列表,人员数据复用页面查询 SQL,家庭主要成员按同一人员汇总到一行。 + * + * @param pageForm 页面查询和显示列参数 + * @param response 文件下载响应 + */ + @Override + public void exportPersonalMaintain(MemberInfoPageForm pageForm, HttpServletResponse response) { + Sql sql = getPersonalMaintainSql(pageForm); + List list = listMap(sql); + fillChildExportData(list); + List> personColumns = Lang.isNotEmpty(pageForm.getColumns()) + ? pageForm.getColumns() + : buildDefaultPersonalMaintainColumns(); + Workbook workbook = buildPersonalMaintainWorkbook(personColumns, list); + CommonDownloadUtil.download("人员信息维护.xlsx", workbook, response); + } + + /** + * 为查询结果追加子女信息汇总字段,多个子女之间使用“ | ”分隔。 + * + * @param list 页面查询出的人员数据 + */ + private void fillChildExportData(List list) { + if (Lang.isEmpty(list)) { + return; + } + List userIds = list.stream().map(item -> item.getString("id")).filter(StrUtil::isNotBlank).toList(); + if (Lang.isEmpty(userIds)) { + return; + } + // Query child records in batch so exported child data follows the same people returned by the page query. + Map> childMap = dao().query(MemberChild.class, Cnd.where("userId", "in", userIds).asc("createdAt")) + .stream().collect(Collectors.groupingBy(MemberChild::getUserId)); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + for (NutMap item : list) { + List children = childMap.get(item.getString("id")); + item.put("childRelation", joinChildField(children, MemberChild::getRelation)); + item.put("childName", joinChildField(children, MemberChild::getName)); + item.put("childSex", joinChildField(children, MemberChild::getSex)); + item.put("childBirthday", joinChildField(children, child -> child.getBirthday() == null ? "" : dateFormat.format(child.getBirthday()))); + } + } + + /** + * 聚合子女信息单个字段,保持每个子女字段在同一导出行内的顺序一致。 + * + * @param children 子女信息列表 + * @param getter 字段取值函数 + * @return 使用“ | ”分隔的字段内容 + */ + private String joinChildField(List children, Function getter) { + if (Lang.isEmpty(children)) { + return ""; + } + // Keep child field order consistent across all exported child columns. + return children.stream() + .map(child -> StrUtil.nullToEmpty(getter.apply(child))) + .collect(Collectors.joining(" | ")); + } + + /** + * 页面未传显示列时,使用个人信息维护列表当前默认显示列作为导出字段。 + * + * @return 默认人员信息导出列 + */ + private List> buildDefaultPersonalMaintainColumns() { + List> columns = new ArrayList<>(); + addExportColumn(columns, "工号", "loginname"); + addExportColumn(columns, "姓名", "username"); + addExportColumn(columns, "性别", "sex"); + addExportColumn(columns, "出生年月", "birthday"); + addExportColumn(columns, "民族", "nation"); + addExportColumn(columns, "联系电话", "mobile"); + addExportColumn(columns, "政治面貌", "political"); + addExportColumn(columns, "在职状态", "userState"); + addExportColumn(columns, "教职工类别", "personType"); + addExportColumn(columns, "编制类别", "preparedBy"); + addExportColumn(columns, "所属工会", "unionName"); + addExportColumn(columns, "所属单位", "unitName"); + return columns; + } + + /** + * 添加导出列配置,使用有序 Map 保持 Excel 列顺序。 + */ + private void addExportColumn(List> columns, String label, String prop) { + Map column = new LinkedHashMap<>(); + column.put("label", label); + column.put("prop", prop); + columns.add(column); + } + + /** + * 构建带“人员信息 / 子女信息”分组表头的 Excel。 + * + * @param personColumns 人员信息列 + * @param list 导出数据 + * @return Excel 工作簿 + */ + private Workbook buildPersonalMaintainWorkbook(List> personColumns, List list) { + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = new XSSFWorkbook(); + Sheet sheet = workbook.createSheet("人员信息维护"); + CellStyle groupStyle = createExportCellStyle(workbook, true, IndexedColors.PALE_BLUE.getIndex()); + CellStyle childGroupStyle = createExportCellStyle(workbook, true, IndexedColors.LIGHT_GREEN.getIndex()); + CellStyle headerStyle = createExportCellStyle(workbook, true, IndexedColors.GREY_25_PERCENT.getIndex()); + CellStyle dataStyle = createExportCellStyle(workbook, false, IndexedColors.WHITE.getIndex()); + + List> childColumns = new ArrayList<>(); + addExportColumn(childColumns, "关系", "childRelation"); + addExportColumn(childColumns, "姓名", "childName"); + addExportColumn(childColumns, "性别", "childSex"); + addExportColumn(childColumns, "出生日期", "childBirthday"); + + Row groupRow = sheet.createRow(0); + Row headerRow = sheet.createRow(1); + int colIndex = 0; + createMergedCell(sheet, groupRow, 0, Math.max(personColumns.size() - 1, 0), "人员信息", groupStyle); + for (Map column : personColumns) { + createCell(headerRow, colIndex++, column.get("label"), headerStyle); + } + int childStartIndex = colIndex; + createMergedCell(sheet, groupRow, childStartIndex, childStartIndex + childColumns.size() - 1, "子女信息", childGroupStyle); + for (Map column : childColumns) { + createCell(headerRow, colIndex++, column.get("label"), headerStyle); + } + for (int i = 0; i < list.size(); i++) { + Row dataRow = sheet.createRow(i + 2); + int dataCol = 0; + NutMap data = list.get(i); + for (Map column : personColumns) { + createCell(dataRow, dataCol++, data.getString(column.get("prop"), ""), dataStyle); + } + for (Map column : childColumns) { + createCell(dataRow, dataCol++, data.getString(column.get("prop"), ""), dataStyle); + } + } + for (int i = 0; i < colIndex; i++) { + sheet.setColumnWidth(i, 20 * 256); + } + return workbook; + } + + /** + * 创建导出单元格样式,统一表头和数据区的边框、对齐、换行。 + */ + private CellStyle createExportCellStyle(Workbook workbook, boolean bold, short fillColor) { + CellStyle style = workbook.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setWrapText(true); + style.setFillForegroundColor(fillColor); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.BLACK.getIndex()); + style.setBottomBorderColor(IndexedColors.BLACK.getIndex()); + style.setLeftBorderColor(IndexedColors.BLACK.getIndex()); + style.setRightBorderColor(IndexedColors.BLACK.getIndex()); + Font font = workbook.createFont(); + font.setBold(bold); + style.setFont(font); + return style; + } + + /** + * 创建普通单元格并写入文本值。 + */ + private void createCell(Row row, int colIndex, String value, CellStyle style) { + Cell cell = row.createCell(colIndex); + cell.setCellValue(value == null ? "" : value); + cell.setCellStyle(style); + } + + /** + * 创建横向合并单元格,作为 Excel 分组表头。 + */ + private void createMergedCell(Sheet sheet, Row row, int firstCol, int lastCol, String value, CellStyle style) { + createCell(row, firstCol, value, style); + if (lastCol > firstCol) { + sheet.addMergedRegion(new CellRangeAddress(row.getRowNum(), row.getRowNum(), firstCol, lastCol)); + for (int i = firstCol + 1; i <= lastCol; i++) { + createCell(row, i, "", style); + } + } + } + @Override public Sql getMemberHistorySql(MemberInfoPageForm pageForm) { diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html index 0680c70..f08ea37 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html @@ -35,6 +35,7 @@ layout("/layouts/platform.html"){ 列设置 + 导出 @@ -157,6 +158,21 @@ layout("/layouts/platform.html"){ this.tableLoading = false }) }, + doExport() { + const pageForm = clone(this.pageForm) + pageForm.userStates = Array.isArray(this.pageForm.userStates) ? JSON.stringify(this.pageForm.userStates) : this.pageForm.userStates + pageForm.personTypes = Array.isArray(this.pageForm.personTypes) ? JSON.stringify(this.pageForm.personTypes) : this.pageForm.personTypes + pageForm.preparedBys = Array.isArray(this.pageForm.preparedBys) ? JSON.stringify(this.pageForm.preparedBys) : this.pageForm.preparedBys + pageForm.columns = JSON.stringify(this.tableColumns.filter((column) => { + return this.checkedFields.includes(column.prop) + }).map((column) => { + return { + label: column.label, + prop: column.prop + } + })) + this.$downLoad(loc() + "/doExport", pageForm) + }, checkAll() { this.checkedFields = this.tableColumns.map(c => c.prop) },