Compare commits

...
11 Commits
Author SHA1 Message Date
c-zhangr1 37a9c579cd 1、拉取编制类别数据
2、更新人员报错
3、日志级别更换
2026-09-21 15:35:10 +08:00
c-zhangr1 c5a8c39bec 1、定时拉取人员数据
2、过滤掉部分人员不更新同步
2026-09-21 13:49:17 +08:00
c-zhangr1 3aa5c3406a change 2026-09-03 13:54:58 +08:00
c-hongqw1 d5d804df2f Merge remote-tracking branch 'origin/main' 2026-09-01 13:57:38 +08:00
c-hongqw1 0fb6ae65e7 commit 2026-09-01 13:57:18 +08:00
c-zhangr1 4bc9d9f073 change 2026-07-03 15:48:04 +08:00
c-zhangr1 0083cfeed4 change 2026-07-02 16:56:29 +08:00
c-zhangr1 9c31bf8153 change 2026-07-01 18:30:50 +08:00
c-zhangr1 3304b9113f change 2026-07-01 18:17:27 +08:00
= d72c86cc14 commit 2026-04-20 17:22:28 +08:00
= 97def4472b commit 2026-04-20 16:53:26 +08:00
29 changed files with 1724 additions and 80 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.budwk</groupId> <groupId>com.budwk</groupId>
<artifactId>zhgh_jshvc</artifactId> <artifactId>mini</artifactId>
<version>5.6.0-plus</version> <version>5.6.0-plus</version>
<build> <build>
<resources> <resources>
+1 -1
View File
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging> <packaging>jar</packaging>
<groupId>com.budwk</groupId> <groupId>com.budwk</groupId>
<artifactId>zhgh_jshvc</artifactId> <artifactId>mini</artifactId>
<version>5.6.0-plus</version> <version>5.6.0-plus</version>
<properties> <properties>
<nutzboot.version>2.6.0-SNAPSHOT</nutzboot.version> <nutzboot.version>2.6.0-SNAPSHOT</nutzboot.version>
@@ -140,7 +140,8 @@ public class FlowTodoCenterController {
ins.createdAt, ins.createdAt,
ins.variable ->> '$.instanceName' AS instanceName, ins.variable ->> '$.instanceName' AS instanceName,
GROUP_CONCAT(DISTINCT t.displayName) as taskName, GROUP_CONCAT(DISTINCT t.displayName) as taskName,
cat.`name` categoryName cat.`name` categoryName,
def.instanceViewUrl formKey
FROM FROM
wf_process_instance ins wf_process_instance ins
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id and t.taskState = 10 LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id and t.taskState = 10
@@ -197,6 +197,7 @@ public class Sys_user extends BaseModel implements Serializable {
@Column @Column
@Comment("编制类别码") @Comment("编制类别码")
@ColDefine(type = ColType.VARCHAR, width = 30) @ColDefine(type = ColType.VARCHAR, width = 30)
@DataCenterColumn(name = "编制类别码", key = "BZLXM")
private String preparedBy; private String preparedBy;
@Column @Column
@@ -380,4 +381,9 @@ public class Sys_user extends BaseModel implements Serializable {
@ColDefine(type = ColType.VARCHAR, width = 50) @ColDefine(type = ColType.VARCHAR, width = 50)
@DataCenterColumn(name = "党政职务", key = "DZZW") @DataCenterColumn(name = "党政职务", key = "DZZW")
private String position; private String position;
@Column
@Comment("是否同步")
@ColDefine(type = ColType.CHAR, width = 1)
private String isSyn;
} }
@@ -92,6 +92,6 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implem
} }
log.info("本次拉取单位数据,新增{}条,更新{}条", insertList.size(), updateList.size()); log.info("本次拉取单位数据,新增{}条,更新{}条", insertList.size(), updateList.size());
dao().insert(insertList); dao().insert(insertList);
dao().update(updateList); dao().updateIgnoreNull(updateList);
} }
} }
@@ -121,22 +121,25 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
*/ */
private boolean checkMembershipEligibility(String userState, String preparedBy, Date postDoctoralJoinDate) { private boolean checkMembershipEligibility(String userState, String preparedBy, Date postDoctoralJoinDate) {
// 博士后单独判断:只要进站时间在两年内就是会员 // 博士后单独判断:只要进站时间在两年内就是会员
if ("博士后".equals(preparedBy)) { // if ("博士后".equals(preparedBy)) {
if (postDoctoralJoinDate != null) { // if (postDoctoralJoinDate != null) {
Date twoYearsAgo = DateUtil.offset(DateUtil.date(), DateField.YEAR, -2).toJdkDate(); // Date twoYearsAgo = DateUtil.offset(DateUtil.date(), DateField.YEAR, -2).toJdkDate();
return postDoctoralJoinDate.after(twoYearsAgo); // return postDoctoralJoinDate.after(twoYearsAgo);
} // }
return false; // return false;
} // }
// 其他人员需要判断在岗状态和聘用方式 // if (!"在岗".equals(userState) && !"在岗-里仁学院".equals(userState)) {
if (!"在岗".equals(userState)) { // return false;
return false; // }
} //
// // 判断聘用方式
// Set<String> memberPreparedByTypes = new HashSet<>(Arrays.asList("新人事代理", "校聘合同制", "事业编制"));
// return memberPreparedByTypes.contains(preparedBy);
// 判断聘用方式
Set<String> memberPreparedByTypes = new HashSet<>(Arrays.asList("新人事代理", "校聘合同制", "事业编制")); // 其他人员只需要判断在岗状态
return memberPreparedByTypes.contains(preparedBy); return "在岗".equals(userState) || "在岗-里仁学院".equals(userState);
} }
/** /**
@@ -185,6 +188,9 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
for (Sys_user_source source : sources) { for (Sys_user_source source : sources) {
Sys_user user = userMap.get(source.getLoginname()); Sys_user user = userMap.get(source.getLoginname());
if (ObjectUtil.isNotEmpty(user) && ObjectUtil.isNotEmpty(user.getIsSyn()) && user.getIsSyn().equals("0")) {
continue;
}
// 创建用户对象 // 创建用户对象
Sys_user u = new Sys_user(); Sys_user u = new Sys_user();
BeanUtils.copyProperties(source, u); BeanUtils.copyProperties(source, u);
@@ -195,20 +201,31 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
u.setSalt(salt); u.setSalt(salt);
u.setPassword(PwdUtil.getPassword(PwdUtil.generate(12), salt)); u.setPassword(PwdUtil.getPassword(PwdUtil.generate(12), salt));
if (u.getMember()) { // 判断是否为会员
boolean isMember = checkMembershipEligibility(u.getUserState(), u.getPreparedBy(), u.getPostDoctoralJoinDate());
u.setMember(isMember);
if (isMember) {
addMemberUserIds.add(u.getId()); addMemberUserIds.add(u.getId());
} }
needInitUserList.add(u); needInitUserList.add(u);
} else { } else {
// 修改现有用户 // 修改现有用户
u.setId(user.getId()); u.setId(user.getId());
// 更新会员状态 // 更新会员状态
boolean currentIsMember = user.getMember() != null && user.getMember(); boolean currentIsMember = user.getMember() != null && user.getMember();
boolean shouldBeMember = checkMembershipEligibility(u.getUserState(), u.getPreparedBy(), u.getPostDoctoralJoinDate());
if (!currentIsMember) { // 设置新的会员状态
u.setMember(shouldBeMember);
if (shouldBeMember && !currentIsMember) {
// 应该是会员但当前不是,需要添加
addMemberUserIds.add(user.getId()); addMemberUserIds.add(user.getId());
} else{ } else if (!shouldBeMember && currentIsMember) {
// 不应该是会员但当前是,需要移除
removeMemberUserIds.add(user.getId()); removeMemberUserIds.add(user.getId());
} }
@@ -70,31 +70,36 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic
// 检查是否符合会员条件 // 检查是否符合会员条件
// 1. 用户状态为"在岗" // 1. 用户状态为"在岗"
if ("在岗".equals(sysUser.getUserState())) { if ("在岗".equals(sysUser.getUserState()) || "在岗-里仁学院".equals(sysUser.getUserState())) {
// 2. 检查聘用方式条件 sysUser.setMember(true);
List<String> membershipQualifyingPreparedBy = Arrays.asList("新人事代理", "校聘合同制", "事业编制");
// 第一种情况:聘用方式在指定列表中
boolean isQualifiedByPreparedBy = membershipQualifyingPreparedBy.contains(sysUser.getPreparedBy());
// 第二种情况:聘用方式是博士后,且进站时间不超过两年
boolean isQualifiedPostdoc = false;
if ("博士后".equals(sysUser.getPreparedBy()) && sysUser.getPostDoctoralJoinDate() != null) {
Calendar twoYearsAgo = Calendar.getInstance();
twoYearsAgo.add(Calendar.YEAR, -2);
Date twoYearsAgoDate = twoYearsAgo.getTime();
isQualifiedPostdoc = sysUser.getPostDoctoralJoinDate().after(twoYearsAgoDate);
}
// 如果满足任一条件,设置为会员
if (isQualifiedByPreparedBy || isQualifiedPostdoc) {
sysUser.setMember(true);
} else {
sysUser.setMember(false);
}
} else { } else {
sysUser.setMember(false); sysUser.setMember(false);
} }
// if ("在岗".equals(sysUser.getUserState()) || "在岗-里仁学院".equals(sysUser.getUserState())) {
// // 2. 检查聘用方式条件
// List<String> membershipQualifyingPreparedBy = Arrays.asList("新人事代理", "校聘合同制", "事业编制");
//
// // 第一种情况:聘用方式在指定列表中
// boolean isQualifiedByPreparedBy = membershipQualifyingPreparedBy.contains(sysUser.getPreparedBy());
//
// // 第二种情况:聘用方式是博士后,且进站时间不超过两年
// boolean isQualifiedPostdoc = false;
//// if ("博士后".equals(sysUser.getPreparedBy()) && sysUser.getPostDoctoralJoinDate() != null) {
//// Calendar twoYearsAgo = Calendar.getInstance();
//// twoYearsAgo.add(Calendar.YEAR, -2);
//// Date twoYearsAgoDate = twoYearsAgo.getTime();
//// isQualifiedPostdoc = sysUser.getPostDoctoralJoinDate().after(twoYearsAgoDate);
//// }
//
// // 如果满足任一条件,设置为会员
// if (isQualifiedByPreparedBy || isQualifiedPostdoc) {
// sysUser.setMember(true);
// } else {
// sysUser.setMember(false);
// }
// } else {
// sysUser.setMember(false);
// }
} }
List<List<Sys_user>> splitSysUsers = ListUtil.split(sysUsers, 500); List<List<Sys_user>> splitSysUsers = ListUtil.split(sysUsers, 500);
@@ -57,6 +57,6 @@ public class SysUserAllRenewJob implements Job {
param.setUpdateMode(SysDataUpdateMode.ALL.name()); param.setUpdateMode(SysDataUpdateMode.ALL.name());
param.setConditionGroup(conditionGroup); param.setConditionGroup(conditionGroup);
// sysDataUserUpdateService.update(param); sysDataUserUpdateService.update(param);
} }
} }
@@ -30,12 +30,17 @@ import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap; import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.AdaptBy;
import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok; 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 javax.servlet.http.HttpServletResponse;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@@ -179,6 +184,7 @@ public class AssetManageController {
@At @At
@ApiOperation("按条件导出资产台账") @ApiOperation("按条件导出资产台账")
@SaCheckPermission("asset.manage") @SaCheckPermission("asset.manage")
@Ok("void")
@SLog(type = "assetManage", tag = "资产台账", msg = "按条件导出资产台账") @SLog(type = "assetManage", tag = "资产台账", msg = "按条件导出资产台账")
public void doExport(AssetPageForm pageForm, HttpServletResponse response) { public void doExport(AssetPageForm pageForm, HttpServletResponse response) {
Sql sql = assetService.getSql(pageForm); Sql sql = assetService.getSql(pageForm);
@@ -213,4 +219,53 @@ public class AssetManageController {
e.printStackTrace(); 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<ExcelExportEntity> 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);
}
} }
@@ -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;
}
@@ -1,10 +1,13 @@
package com.budwk.app.zhgh.dayofficework.asset.service; package com.budwk.app.zhgh.dayofficework.asset.service;
import com.budwk.app.base.service.BaseService; 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.model.Asset;
import com.budwk.app.zhgh.dayofficework.asset.param.AssetPageForm; import com.budwk.app.zhgh.dayofficework.asset.param.AssetPageForm;
import org.nutz.dao.sql.Sql; import org.nutz.dao.sql.Sql;
import org.nutz.lang.util.NutMap; import org.nutz.lang.util.NutMap;
import org.nutz.mvc.upload.TempFile;
public interface AssetService extends BaseService<Asset> { public interface AssetService extends BaseService<Asset> {
@@ -12,4 +15,12 @@ public interface AssetService extends BaseService<Asset> {
Sql getSql(AssetPageForm pageForm); Sql getSql(AssetPageForm pageForm);
NutMap findOne(String id); NutMap findOne(String id);
/**
* 导入资产台账数据,负责 Excel 解析、字段转换、预计报废时间计算和折旧记录初始化。
*
* @param file 页面上传的资产 Excel 文件
* @return 导入结果,包含成功数和失败明细
*/
ExcelImportRes<AssetImportExcelMode> importAssets(TempFile file);
} }
@@ -1,21 +1,39 @@
package com.budwk.app.zhgh.dayofficework.asset.service.impl; 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 cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant; 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.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.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; 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.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.model.AssetStocktaking;
import com.budwk.app.zhgh.dayofficework.asset.param.AssetPageForm; 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 com.budwk.app.zhgh.dayofficework.asset.service.AssetService;
import org.nutz.dao.Cnd; import org.nutz.dao.Cnd;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
import org.nutz.dao.Sqls; import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql; import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup; import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap; 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 * @author zhf
@@ -28,6 +46,12 @@ public class AssetServiceImpl extends BaseServiceImpl<Asset> implements AssetSer
super(dao); super(dao);
} }
@Inject
private AssetDepreciationRecordService assetDepreciationRecordService;
@Inject
private SysDictService sysDictService;
@Override @Override
public Sql getSql(AssetPageForm pageForm) { public Sql getSql(AssetPageForm pageForm) {
Sql sql = Sqls.create(""" Sql sql = Sqls.create("""
@@ -51,6 +75,7 @@ public class AssetServiceImpl extends BaseServiceImpl<Asset> implements AssetSer
} }
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
cnd.and("t1.assetUseUnionId", "=", SecurityUtil.getUnionId()); cnd.and("t1.assetUseUnionId", "=", SecurityUtil.getUnionId());
cnd.and("t1.assetTypeCode", "=", "2");
} }
cnd.andEX("t1.assetUseUnionId", "=", pageForm.getAssetUseUnionId()); cnd.andEX("t1.assetUseUnionId", "=", pageForm.getAssetUseUnionId());
cnd.andEX("t1.assetCategoryId", "=", pageForm.getAssetCategoryId()); cnd.andEX("t1.assetCategoryId", "=", pageForm.getAssetCategoryId());
@@ -83,4 +108,260 @@ public class AssetServiceImpl extends BaseServiceImpl<Asset> implements AssetSer
Cnd.where("assetId", "=", id))); Cnd.where("assetId", "=", id)));
return nutMap; return nutMap;
} }
/**
* 导入资产台账数据,逐行完成字段校验和新增,失败行不影响其他行入库。
*
* @param file 页面上传的资产 Excel 文件
* @return 导入统计结果和失败明细
*/
@Override
public ExcelImportRes<AssetImportExcelMode> importAssets(TempFile file) {
List<AssetImportExcelMode> importRows = ExcelImportUtil.importExcel(file.getFile(), AssetImportExcelMode.class, new ImportParams());
ExcelImportRes<AssetImportExcelMode> importRes = new ExcelImportRes<>();
importRes.setTotalRecords(importRows.size());
for (int i = 0; i < importRows.size(); i++) {
AssetImportExcelMode row = importRows.get(i);
List<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> options, List<String> 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<String> errors) {
String text = StrUtil.trim(value);
if (StrUtil.isBlank(text)) {
errors.add(fieldName + "不能为空");
}
return text;
}
} }
@@ -65,10 +65,10 @@ public class MemberInfoGroupController {
public Result pageData(MemberInfoPageForm pageForm) { public Result pageData(MemberInfoPageForm pageForm) {
Sql sql = memberInfoService.getSql(pageForm); Sql sql = memberInfoService.getSql(pageForm);
Pagination<NutMap> pagination = memberInfoService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); Pagination<NutMap> pagination = memberInfoService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
for (NutMap row : pagination.getList()) { // for (NutMap row : pagination.getList()) {
row.put("mobile",DesensitizedUtil.mobilePhone(row.getString("mobile"))); // row.put("mobile",DesensitizedUtil.mobilePhone(row.getString("mobile")));
row.put("idCard",DesensitizedUtil.idCardNum(row.getString("idCard"), 3, 4)); // row.put("idCard",DesensitizedUtil.idCardNum(row.getString("idCard"), 3, 4));
} // }
return Result.success(pagination); return Result.success(pagination);
} }
@@ -0,0 +1,128 @@
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 javax.servlet.http.HttpServletResponse;
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<NutMap> pagination = memberInfoService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
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);
}
/**
* 查询个人信息维护详情,返回只读会员信息和可编辑子女信息。
*
* @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, String marriage, @Param("children") String childrenJson) {
try {
// 保存前复用详情权限校验,防止绕过列表入口修改无权限会员的子女信息。
memberInfoService.getPersonalMaintainInfo(userId);
List<MemberChild> children = new ArrayList<>();
if (Strings.isNotBlank(childrenJson)) {
children = Json.fromJsonAsList(MemberChild.class, childrenJson);
}
memberInfoService.savePersonalMaintainMarriage(userId, marriage);
memberChildService.saveUserChildren(userId, children);
return Result.success();
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
}
}
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.staffmanage.member.controller.statistics;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.result.Result; import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.zhgh.staffmanage.member.service.MemberStatisticsService; import com.budwk.app.zhgh.staffmanage.member.service.MemberStatisticsService;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.ioc.loader.annotation.Inject; 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, public void doExport(@Param("queryId") String queryId, @Param("queryType") String queryType,
@Param("currentYear") Integer currentYear, HttpServletResponse response) { @Param("currentYear") Integer currentYear, HttpServletResponse response) {
List<NutMap> list = new ArrayList<>(); List<NutMap> list = new ArrayList<>();
if ("union".equals(queryType)) { boolean unionQuery = isUnionQuery(queryType);
if (unionQuery) {
list = memberStatisticsService.getUnionStatisticsData(queryId, currentYear); list = memberStatisticsService.getUnionStatisticsData(queryId, currentYear);
} else { } else {
list = memberStatisticsService.getUnitStatisticsData(queryId, currentYear); list = memberStatisticsService.getUnitStatisticsData(queryId, currentYear);
} }
// 导出数据与页面查询保持一致,并追加合计行方便 Excel 查看总量。
NutMap map = NutMap.NEW(); NutMap map = NutMap.NEW();
map.put("union".equals(queryType) ? "unionName" : "unitName", "合计"); map.put(unionQuery ? "unionName" : "unitName", "合计");
map.put("totalNumber", list.stream().mapToInt(v -> v.getInt("TotalNumber")).sum()); map.put("totalNumber", sumInt(list, "totalNumber"));
map.put("maleMember", list.stream().mapToInt(v -> v.getInt("maleMember")).sum()); map.put("maleMember", sumInt(list, "maleMember"));
map.put("femaleMember", list.stream().mapToInt(v -> v.getInt("femaleMember")).sum()); map.put("femaleMember", sumInt(list, "femaleMember"));
map.put("smallForty", list.stream().mapToInt(v -> v.getInt("smallForty")).sum()); map.put("smallForty", sumInt(list, "smallForty"));
map.put("smallFifty", list.stream().mapToInt(v -> v.getInt("smallFifty")).sum()); map.put("smallFifty", sumInt(list, "smallFifty"));
map.put("smallFiftyFive", list.stream().mapToInt(v -> v.getInt("smallFiftyFive")).sum()); map.put("smallFiftyFive", sumInt(list, "smallFiftyFive"));
map.put("smallFiftyFive2", list.stream().mapToInt(v -> v.getInt("smallFiftyFive2")).sum()); map.put("smallFiftyFive2", sumInt(list, "smallFiftyFive2"));
list.add(map);
try { try {
response.setContentType("application/octet-stream"); Workbook workbook = memberStatisticsService.exportStatisticsExcel(list, unionQuery ? "union" : "unit");
response.setHeader("Content-Disposition", "attachment;filename=" + new String(("会员性别统计.xlsx").getBytes("utf-8"), "ISO8859-1")); CommonDownloadUtil.download("会员统计.xlsx", workbook, response);
Workbook workbook = memberStatisticsService.exportStatisticsExcel(list, queryType);
workbook.write(response.getOutputStream());
workbook.close();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); 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<NutMap> list, String key) {
return list.stream().mapToInt(item -> item.getInt(key, 0)).sum();
}
} }
@@ -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;
}
@@ -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<MemberChild> {
/**
* 根据会员用户ID查询子女信息列表。
*
* @param userId 会员用户ID
* @return 子女信息列表
*/
List<MemberChild> listByUserId(String userId);
/**
* 保存会员子女信息,页面按整表提交,服务层负责覆盖当前会员的子女资料。
*
* @param userId 会员用户ID
* @param children 页面提交的子女信息列表
*/
void saveUserChildren(String userId, List<MemberChild> children);
}
@@ -27,6 +27,38 @@ public interface MemberInfoService extends BaseService<Sys_user> {
*/ */
Sql getSql(MemberInfoPageForm pageForm); Sql getSql(MemberInfoPageForm pageForm);
/**
* 获取个人信息维护列表查询SQL,系统管理员查看全部,其他人员仅查看本人。
*
* @param pageForm 会员档案查询参数
* @return 个人信息维护列表查询SQL
*/
Sql getPersonalMaintainSql(MemberInfoPageForm pageForm);
/**
* 获取个人信息维护详情,复用会员档案查询权限展示会员基础信息。
*
* @param userId 用户ID
* @return 会员基础信息
*/
NutMap getPersonalMaintainInfo(String userId);
/**
* 保存个人信息维护中允许编辑的婚姻状况字段,其他会员原始字段不在此方法中更新。
*
* @param userId 用户ID
* @param marriage 婚姻状况字典值
*/
void savePersonalMaintainMarriage(String userId, String marriage);
/**
* 导出个人信息维护列表数据,查询条件与页面列表保持一致,并汇总导出家庭主要成员信息。
*
* @param pageForm 页面查询和显示列参数
* @param response 文件下载响应
*/
void exportPersonalMaintain(MemberInfoPageForm pageForm, HttpServletResponse response);
/** /**
* 获取会员历史 * 获取会员历史
* @param pageForm * @param pageForm
@@ -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<MemberChild> implements MemberChildService {
public MemberChildServiceImpl(Dao dao) {
super(dao);
}
/**
* 根据会员用户ID查询子女信息列表,并按创建时间保持页面展示顺序。
*
* @param userId 会员用户ID
* @return 子女信息列表
*/
@Override
public List<MemberChild> 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<MemberChild> 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);
});
}
}
@@ -15,15 +15,29 @@ import com.budwk.app.base.utils.easyexcel.EasyExcelUtil;
import com.budwk.app.sys.models.Sys_role; import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_unit; import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.sys.models.Sys_user_role; 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.web.commons.auth.utils.AuthUtil;
import com.budwk.app.sys.models.Sys_user; import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; 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.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.param.pageform.MemberInfoPageForm;
import com.budwk.app.zhgh.staffmanage.member.service.MemberInfoService; import com.budwk.app.zhgh.staffmanage.member.service.MemberInfoService;
import com.budwk.app.zhgh.staffmanage.member.vo.MemberImportVo; 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.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.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.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain; import org.nutz.dao.Chain;
import org.nutz.dao.Cnd; import org.nutz.dao.Cnd;
@@ -40,9 +54,12 @@ import org.nutz.lang.util.NutMap;
import org.nutz.mvc.upload.TempFile; import org.nutz.mvc.upload.TempFile;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -113,6 +130,295 @@ public class MemberInfoServiceImpl extends BaseServiceImpl<Sys_user> implements
return sql; 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());
}
// 会员查询条件统一作用于列表和导出:1 表示会员,0 表示非会员。
if (pageForm.getMemberStatus() != null) {
cnd.and("u.member", "=", pageForm.getMemberStatus() == 1);
}
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);
}
/**
* 保存个人信息维护开放编辑的婚姻状况字段,权限校验复用详情查询口径。
*
* @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));
}
/**
* 导出个人信息维护列表,人员数据复用页面查询 SQL,家庭主要成员按同一人员汇总到一行。
*
* @param pageForm 页面查询和显示列参数
* @param response 文件下载响应
*/
@Override
public void exportPersonalMaintain(MemberInfoPageForm pageForm, HttpServletResponse response) {
Sql sql = getPersonalMaintainSql(pageForm);
List<NutMap> list = listMap(sql);
fillChildExportData(list);
List<Map<String, String>> personColumns = Lang.isNotEmpty(pageForm.getColumns())
? pageForm.getColumns()
: buildDefaultPersonalMaintainColumns();
Workbook workbook = buildPersonalMaintainWorkbook(personColumns, list);
CommonDownloadUtil.download("人员信息维护.xlsx", workbook, response);
}
/**
* 为查询结果追加子女信息汇总字段,多个子女之间使用“ | ”分隔。
*
* @param list 页面查询出的人员数据
*/
private void fillChildExportData(List<NutMap> list) {
if (Lang.isEmpty(list)) {
return;
}
List<String> 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<String, List<MemberChild>> 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<MemberChild> 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<MemberChild> children, Function<MemberChild, String> 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<Map<String, String>> buildDefaultPersonalMaintainColumns() {
List<Map<String, String>> 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<Map<String, String>> columns, String label, String prop) {
Map<String, String> 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<Map<String, String>> personColumns, List<NutMap> 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<Map<String, String>> 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<String, String> column : personColumns) {
createCell(headerRow, colIndex++, column.get("label"), headerStyle);
}
int childStartIndex = colIndex;
createMergedCell(sheet, groupRow, childStartIndex, childStartIndex + childColumns.size() - 1, "子女信息", childGroupStyle);
for (Map<String, String> 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<String, String> column : personColumns) {
createCell(dataRow, dataCol++, data.getString(column.get("prop"), ""), dataStyle);
}
for (Map<String, String> 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 @Override
public Sql getMemberHistorySql(MemberInfoPageForm pageForm) { public Sql getMemberHistorySql(MemberInfoPageForm pageForm) {
@@ -243,10 +549,10 @@ public class MemberInfoServiceImpl extends BaseServiceImpl<Sys_user> implements
public void exportMember(MemberInfoPageForm pageForm, HttpServletResponse response) { public void exportMember(MemberInfoPageForm pageForm, HttpServletResponse response) {
Sql sql = getSql(pageForm); Sql sql = getSql(pageForm);
List<NutMap> list = listMap(sql); List<NutMap> list = listMap(sql);
for (NutMap row : list) { // for (NutMap row : list) {
row.put("mobile", DesensitizedUtil.mobilePhone(row.getString("mobile"))); // row.put("mobile", DesensitizedUtil.mobilePhone(row.getString("mobile")));
row.put("idCard",DesensitizedUtil.idCardNum(row.getString("idCard"), 3, 4)); // row.put("idCard",DesensitizedUtil.idCardNum(row.getString("idCard"), 3, 4));
} // }
Workbook workbook = null; Workbook workbook = null;
if (Lang.isNotEmpty(pageForm.getColumns())) { if (Lang.isNotEmpty(pageForm.getColumns())) {
+89 -9
View File
@@ -1,17 +1,85 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="false" scanPeriod="60000" debug="false"> <configuration scan="false" scanPeriod="60000" debug="false">
<!-- 定义日志文件的存储路径 --> <!-- 日志存储路径 -->
<property name="LOG_HOME" value="./logs"/> <property name="LOG_HOME" value="./logs"/>
<!-- 控制台输出配置 --> <!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
<!-- INFO级别日志文件配置 -->
<!--
#################################################################################
# #
# 全文输出日志(一个文件存储)start #
# #
#################################################################################
-->
<!-- 所有级别日志写入同一个文件 -->
<appender name="ALL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/app.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/app-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>300</maxHistory>
</rollingPolicy>
</appender>
<!-- 框架日志级别(按需调整) -->
<logger name="org.eclipse.jetty" level="INFO"/>
<logger name="org.quartz" level="INFO"/>
<logger name="org.nutz" level="DEBUG"/>
<!-- root logger:DEBUG 级别,输出到控制台和全量文件 -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ALL_FILE"/>
</root>
<!--
#################################################################################
# #
# 全文输出日志(一个文件存储)end #
# #
#################################################################################
-->
<!--
#################################################################################
# #
# debug、info、error分文件输出 start #
# #
#################################################################################
-->
<!-- DEBUG级别日志文件配置 -->
<!--<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/debug.log</file>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/debug-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>300</maxHistory>
</rollingPolicy>
</appender>
&lt;!&ndash; INFO级别日志文件配置 &ndash;&gt;
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/info.log</file> <file>${LOG_HOME}/info.log</file>
<encoder> <encoder>
@@ -24,11 +92,11 @@
</filter> </filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/info-%d{yyyy-MM-dd}.log</fileNamePattern> <fileNamePattern>${LOG_HOME}/info-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory> <maxHistory>300</maxHistory>
</rollingPolicy> </rollingPolicy>
</appender> </appender>
<!-- ERROR级别日志文件配置 --> &lt;!&ndash; ERROR级别日志文件配置 &ndash;&gt;
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/error.log</file> <file>${LOG_HOME}/error.log</file>
<encoder> <encoder>
@@ -41,19 +109,31 @@
</filter> </filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/error-%d{yyyy-MM-dd}.log</fileNamePattern> <fileNamePattern>${LOG_HOME}/error-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory> <maxHistory>300</maxHistory>
</rollingPolicy> </rollingPolicy>
</appender> </appender>
<logger name="java" additivity="false" /> &lt;!&ndash; 框架日志级别 &ndash;&gt;
&lt;!&ndash; <logger name="java" additivity="false" />&ndash;&gt;
<logger name="org.eclipse.jetty" level="INFO"/> <logger name="org.eclipse.jetty" level="INFO"/>
<logger name="org.quartz" level="INFO"/> <logger name="org.quartz" level="INFO"/>
<logger name="org.nutz" level="DEBUG"/> <logger name="org.nutz" level="DEBUG"/>
<!-- 日志级别和appender的关联 --> &lt;!&ndash; 日志级别和appender的关联 &ndash;&gt;
<root level="DEBUG"> <root level="DEBUG">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/> <appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/> <appender-ref ref="ERROR_FILE"/>
</root> </root>-->
<!--
#################################################################################
# #
# debug、info、error分文件输出 end #
# #
#################################################################################
-->
</configuration> </configuration>
@@ -83,7 +83,7 @@ layout("/layouts/platform.html"){
<el-table-column prop="position" label="干部职务" sortable width="120" show-overflow-tooltip></el-table-column> <el-table-column prop="position" label="干部职务" sortable width="120" show-overflow-tooltip></el-table-column>
<el-table-column prop="education" label="学历" show-overflow-tooltip sortable width="120"></el-table-column> <el-table-column prop="education" label="学历" show-overflow-tooltip sortable width="120"></el-table-column>
<el-table-column prop="academicDegree" label="学位" show-overflow-tooltip sortable width="120"></el-table-column> <el-table-column prop="academicDegree" label="学位" show-overflow-tooltip sortable width="120"></el-table-column>
<el-table-column prop="unitName" label="单位" show-overflow-tooltip sortable width="120"></el-table-column> <el-table-column prop="unit_name" label="单位" show-overflow-tooltip sortable width="120"></el-table-column>
<el-table-column prop="unitId" label="单位编码" sortable width="120"></el-table-column> <el-table-column prop="unitId" label="单位编码" sortable width="120"></el-table-column>
<el-table-column prop="political" label="政治面貌" sortable></el-table-column> <el-table-column prop="political" label="政治面貌" sortable></el-table-column>
<el-table-column prop="nation" label="民族" sortable></el-table-column> <el-table-column prop="nation" label="民族" sortable></el-table-column>
@@ -53,6 +53,9 @@ layout("/layouts/platform.html"){
<el-button @click="openAdd" size="small" type="primary"> <el-button @click="openAdd" size="small" type="primary">
新增资产 新增资产
</el-button> </el-button>
<el-button @click="showImportDialog = true" size="small" type="primary">
导入资产
</el-button>
<el-button @click="doExport" size="small" type="primary"> <el-button @click="doExport" size="small" type="primary">
导出资产 导出资产
</el-button> </el-button>
@@ -110,6 +113,16 @@ layout("/layouts/platform.html"){
<template #edit> <template #edit>
<asset-form ref="assetForm" @save_success="saveSuccess"></asset-form> <asset-form ref="assetForm" @save_success="saveSuccess"></asset-form>
</template> </template>
<excel-import
ref="excelImportRef"
url="/platform/asset/manage/doImport"
template_url="/platform/asset/manage/downloadTemplate"
:visible.sync="showImportDialog"
title="导入资产"
width="700px"
@import-success="doSearch"
:extra_params="{}"
></excel-import>
</guava> </guava>
</div> </div>
@@ -132,7 +145,8 @@ layout("/layouts/platform.html"){
{prop: 'assetUsageStateName', label: '使用状况'}, {prop: 'assetUsageStateName', label: '使用状况'},
], ],
assetCategoryOption: [], assetCategoryOption: [],
unionList: [] unionList: [],
showImportDialog: false
} }
}, },
components: { components: {
@@ -83,9 +83,35 @@
<el-descriptions-item label="岗级">{{ viewData.positionLevel }}</el-descriptions-item> <el-descriptions-item label="岗级">{{ viewData.positionLevel }}</el-descriptions-item>
<el-descriptions-item label="行政岗级">{{ viewData.administrativePositionLevel }}</el-descriptions-item> <el-descriptions-item label="行政岗级">{{ viewData.administrativePositionLevel }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<!-- 子女信息由个人信息维护详情接口返回,其他查看入口未返回 children 时不展示该区块 -->
<template v-if="viewData.children">
<div class="process-title">子女信息</div>
<el-table ref="childrenTable" v-if="viewData.children.length" :data="viewData.children" size="mini" border :header-cell-style="{background:'#eff3f6'}" style="width: 100%">
<el-table-column prop="relation" label="关系" align="center" min-width="160"></el-table-column>
<el-table-column prop="name" label="姓名" align="center" min-width="160"></el-table-column>
<el-table-column prop="sex" label="性别" align="center" min-width="120"></el-table-column>
<el-table-column label="出生日期" align="center" min-width="160">
<template slot-scope="scope">
{{ scope.row.birthday ? $moment(scope.row.birthday).format('YYYY-MM-DD') : '' }}
</template>
</el-table-column>
</el-table>
<el-empty v-else :image-size="50" description="暂无子女信息"></el-empty>
</template>
</div> </div>
`, `,
store, store,
props: {
detailUrl: {
type: String,
default: "/platform/member/apply/common/findOne"
},
idParam: {
type: String,
default: "id"
}
},
data() { data() {
return { return {
viewData: {} viewData: {}
@@ -93,9 +119,19 @@
}, },
methods: { methods: {
onOpen(id) { 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) { if (res.code === 0) {
this.viewData = res.data this.viewData = res.data
this.$nextTick(() => {
// 查看页在隐藏容器中打开时,Element 表格需要重新计算列宽,避免子女信息列头纵向错位。
if (this.$refs.childrenTable) {
this.$refs.childrenTable.doLayout()
}
})
} }
}) })
} }
@@ -45,12 +45,22 @@ const COMMON_QUERY = {
<dict-select v-model="pageForm.preparedBys" placeholder="请选择编制类别" @change="doSearch" <dict-select v-model="pageForm.preparedBys" placeholder="请选择编制类别" @change="doSearch"
code="USER_PREPARED_BY_TYPE" multiple collapse-tags></dict-select> code="USER_PREPARED_BY_TYPE" multiple collapse-tags></dict-select>
</search-item> </search-item>
<search-item label="会员查询" v-if="show_member_query">
<el-select v-model="pageForm.memberStatus" clearable placeholder="请选择会员状态" @change="doSearch" style="width: 100%">
<el-option label="会员" :value="1"></el-option>
<el-option label="非会员" :value="0"></el-option>
</el-select>
</search-item>
</search> </search>
`, `,
props: { props: {
query_disable: { query_disable: {
type: Boolean, type: Boolean,
default: false default: false
},
show_member_query: {
type: Boolean,
default: false
} }
}, },
mixins: [initTableMixins], mixins: [initTableMixins],
@@ -67,6 +77,8 @@ const COMMON_QUERY = {
userStates: [], userStates: [],
personTypes: [], personTypes: [],
preparedBys: [], preparedBys: [],
// 会员查询仅在指定页面开启,空值表示不限制会员状态。
memberStatus: ''
} }
} }
}, },
@@ -81,6 +93,8 @@ const COMMON_QUERY = {
pageForm.userStates = JSON.stringify(this.pageForm.userStates) pageForm.userStates = JSON.stringify(this.pageForm.userStates)
pageForm.personTypes = JSON.stringify(this.pageForm.personTypes) pageForm.personTypes = JSON.stringify(this.pageForm.personTypes)
pageForm.preparedBys = JSON.stringify(this.pageForm.preparedBys) pageForm.preparedBys = JSON.stringify(this.pageForm.preparedBys)
// 会员查询清空时传 null,避免后台 Integer 参数收到空字符串。
pageForm.memberStatus = this.pageForm.memberStatus === '' ? null : this.pageForm.memberStatus
this.$emit('search', pageForm) this.$emit('search', pageForm)
}, },
async initData(){ async initData(){
@@ -125,6 +125,7 @@ layout("/layouts/platform.html"){
{ prop: "nation", label: "民族", width: 120, sortable: true}, { prop: "nation", label: "民族", width: 120, sortable: true},
{ prop: "mobile", label: "联系电话", width: 120 }, { prop: "mobile", label: "联系电话", width: 120 },
{ prop: "political", label: "政治面貌", width: 120, sortable: true}, { prop: "political", label: "政治面貌", width: 120, sortable: true},
{ prop: "education", label: "学历", width: 120, sortable: true, checked: 0 },
{ prop: "userState", label: "在职状态", width: 120, sortable: true }, { prop: "userState", label: "在职状态", width: 120, sortable: true },
{ prop: "personType", label: "教职工类别", width: 120, sortable: true }, { prop: "personType", label: "教职工类别", width: 120, sortable: true },
{ prop: "preparedBy", label: "编制类别", width: 120, sortable: true }, { prop: "preparedBy", label: "编制类别", width: 120, sortable: true },
@@ -0,0 +1,227 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<div slot="header" class="clearfix">
<span>个人信息维护</span>
</div>
<el-form :model="formData" ref="formRef" label-width="0" label-suffix=":" class="flow-task-form">
<el-descriptions :column="3" border>
<el-descriptions-item label="工号">
<el-input v-model="formData.loginname" readonly size="small"></el-input>
</el-descriptions-item>
<el-descriptions-item label="姓名">
<el-input v-model="formData.username" readonly size="small"></el-input>
</el-descriptions-item>
<el-descriptions-item label="性别">
<el-radio-group disabled v-model="formData.sex" size="small">
<el-radio border label="男">男</el-radio>
<el-radio border label="女">女</el-radio>
</el-radio-group>
</el-descriptions-item>
<el-descriptions-item label="民族">
<dict-select style="width: 100%" v-model="formData.nation" disabled code="USER_NATION"></dict-select>
</el-descriptions-item>
<el-descriptions-item label="出生日期">
<el-date-picker v-model="formData.birthday" type="date" disabled format="yyyy-MM-dd" value-format="yyyy-MM-dd" style="width: 100%"></el-date-picker>
</el-descriptions-item>
<el-descriptions-item label="政治面貌">
<dict-select style="width: 100%" v-model="formData.political" disabled code="USER_POLITICAL"></dict-select>
</el-descriptions-item>
<el-descriptions-item label="学历">
<dict-select style="width: 100%" v-model="formData.education" disabled code="USER_EDUCATION"></dict-select>
</el-descriptions-item>
<el-descriptions-item label="学位">
<dict-select style="width: 100%" v-model="formData.academicDegree" disabled code="USER_ACADEMIC_DEGREE"></dict-select>
</el-descriptions-item>
<el-descriptions-item label="党政职务">
<el-input v-model="formData.position" disabled maxlength="50"></el-input>
</el-descriptions-item>
<el-descriptions-item label="工作单位">
<el-input v-model="formData.unitName" disabled></el-input>
</el-descriptions-item>
<el-descriptions-item label="所属工会">
<el-input v-model="formData.unionName" disabled></el-input>
</el-descriptions-item>
<el-descriptions-item label="所属校区">
<dict-select style="width: 100%" v-model="formData.campus" disabled code="USER_CAMPUS"></dict-select>
</el-descriptions-item>
<el-descriptions-item label="身份证号码">
<el-input v-model="formData.idCard" disabled maxlength="18"></el-input>
</el-descriptions-item>
<el-descriptions-item label="联系电话">
<el-input v-model="formData.mobile" disabled maxlength="15"></el-input>
</el-descriptions-item>
<el-descriptions-item label="电子邮箱">
<el-input v-model="formData.email" disabled maxlength="50"></el-input>
</el-descriptions-item>
<el-descriptions-item label="在职状态">
<dict-select v-model="formData.userState" code="USER_STATE" disabled style="width: 100%"></dict-select>
</el-descriptions-item>
<el-descriptions-item label="教职工类别">
<dict-select v-model="formData.personType" code="USER_PERSON_TYPE" disabled style="width: 100%"></dict-select>
</el-descriptions-item>
<el-descriptions-item label="编制类别">
<dict-select v-model="formData.preparedBy" code="USER_PREPARED_BY_TYPE" disabled style="width: 100%"></dict-select>
</el-descriptions-item>
<el-descriptions-item label="会员状态">
<el-radio-group v-model="formData.member" disabled size="small">
<el-radio border :label="true">会员</el-radio>
<el-radio border :label="false">非会员</el-radio>
</el-radio-group>
</el-descriptions-item>
<el-descriptions-item label="福利会员状态">
<el-radio-group v-model="formData.welfareMember" disabled size="small">
<el-radio border :label="true">福利会员</el-radio>
<el-radio border :label="false">非福利会员</el-radio>
</el-radio-group>
</el-descriptions-item>
<el-descriptions-item label="婚姻状况">
<dict-select style="width: 100%" v-model="formData.marriage" code="USER_MARRIAGE"></dict-select>
</el-descriptions-item>
<el-descriptions-item label="家庭主要成员" :span="3">
<el-table :data="formData.families" border size="small">
<el-table-column label="关系" prop="relation" align="center"></el-table-column>
<el-table-column label="姓名" prop="name" align="center"></el-table-column>
<el-table-column label="工作单位" prop="unit" align="center"></el-table-column>
<el-table-column label="备注" prop="remark" align="center"></el-table-column>
</el-table>
</el-descriptions-item>
<el-descriptions-item label="子女信息" :span="3">
<el-table :data="children" border size="small">
<el-table-column label="关系" prop="relation">
<template slot-scope="{row}">
<el-select v-model="row.relation" placeholder="请选择关系" style="width: 100%">
<el-option label="父子" value="父子"></el-option>
<el-option label="父女" value="父女"></el-option>
<el-option label="母子" value="母子"></el-option>
<el-option label="母女" value="母女"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="姓名" prop="name">
<template slot-scope="{row}">
<el-input v-model="row.name" maxlength="50" placeholder="请输入姓名"></el-input>
</template>
</el-table-column>
<el-table-column label="性别" prop="sex">
<template slot-scope="{row}">
<el-select v-model="row.sex" placeholder="请选择性别" style="width: 100%">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="出生日期" prop="birthday">
<template slot-scope="{row}">
<el-date-picker v-model="row.birthday" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd" placeholder="请选择出生日期" style="width: 100%"></el-date-picker>
</template>
</el-table-column>
<el-table-column width="100px">
<template slot="header" slot-scope="scope">
<el-button type="primary" size="mini" @click="children.push({})">添加</el-button>
</template>
<template slot-scope="scope">
<el-button type="danger" icon="el-icon-delete" size="mini" @click="children.splice(scope.$index, 1)"></el-button>
</template>
</el-table-column>
</el-table>
</el-descriptions-item>
</el-descriptions>
</el-form>
<el-row type="flex" justify="end" class="mt20">
<el-button type="primary" plain @click="goBack">返回</el-button>
<el-button type="primary" @click="saveChildren">保存</el-button>
</el-row>
</el-card>
</div>
<script>
new Vue({
el: "#app",
store,
data() {
return {
userId: GetQueryString("userId"),
formData: {
families: []
},
children: []
}
},
methods: {
initData() {
this.$axios.post("/platform/member/info/maintain/detail", { userId: this.userId }).then((res) => {
if (res.code === 0) {
this.formData = res.data
this.formData.member = this.formData.member === true || this.formData.member === 1
this.formData.welfareMember = this.formData.welfareMember === true || this.formData.welfareMember === 1
this.formData.families = this.formData.families || []
this.children = res.data.children || []
} else {
this.$message.error(res.msg)
}
})
},
saveChildren() {
const cleanedChildren = this.cleanChildren()
if (!this.validateChildren(cleanedChildren)) {
return
}
this.$confirm("确定保存子女信息吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).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) {
this.$message.success("保存成功")
this.goBack()
} else {
this.$message.error(res.msg)
}
})
})
},
cleanChildren() {
// 保存前过滤完全空白的子女行,避免添加后未填写的空行进入查看页。
return this.children.filter((child) => {
return child.relation || child.name || child.sex || child.birthday
})
},
validateChildren(children) {
// 子女信息按整行保存,只要填写了一项,就要求关系、姓名、性别、出生日期完整。
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (!child.relation || !child.name || !child.sex || !child.birthday) {
this.$message.warning("请完整填写第" + (i + 1) + "行子女信息")
return false
}
}
return true
},
goBack() {
commonUtil.pjaxPush("/platform/member/info/maintain?refresh=" + new Date().getTime())
}
},
created() {
this.initData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,192 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<template>
<el-card shadow="never">
<common-query ref="commonQueryRef" @search="search" show_member_query></common-query>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="个人信息维护">
<el-popover placement="bottom" trigger="click" width="200">
<div style="padding:4px 0;border-bottom:1px solid #eee;">
<el-button size="mini" @click="checkAll">全选</el-button>
<el-button size="mini" @click="invertCheck">反选</el-button>
</div>
<div style="max-height:40vh;overflow-y:auto;padding:6px 0;">
<el-checkbox-group v-model="checkedFields">
<el-checkbox
v-for="f in tableColumns"
:key="f.prop"
:label="f.prop"
style="display:block;margin:6px 0;">
{{ f.label }}
</el-checkbox>
</el-checkbox-group>
</div>
<el-button
slot="reference"
type="text"
icon="el-icon-s-operation"
size="small"
style="margin-left:12px;">
列设置
</el-button>
</el-popover>
<el-button type="primary" size="small" icon="el-icon-download" @click="doExport">导出</el-button>
</table-tool>
<el-table :data="tableData" style="width: 100%" row-key="id" @sort-change="pageOrder" :size="tableSize" class="vi-table">
<el-table-column
align="center"
header-align="center"
type="index"
fixed="left"
:index="indexMethod"
label="序号"
width="80px"
></el-table-column>
<el-table-column
show-overflow-tooltip
align="center"
header-align="center"
v-for="column in tableColumns"
v-if="checkedFields.includes(column.prop)"
:label="column.label"
:fixed="column.fixed"
:prop="column.prop"
:sortable="column.sortable"
:width="column.width"
min-width="50"
></el-table-column>
<el-table-column prop="userOnline" align="center" header-align="center" label="操作" fixed="right" width="160px">
<template scope="scope">
<el-button size="mini" type="primary" @click="openView(scope.row.id)">查看</el-button>
<el-button size="mini" type="primary" @click="openEdit(scope.row.id)">编辑</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</template>
<template #view>
<member-info ref="infoRef" detail-url="/platform/member/info/maintain/detail" id-param="userId"></member-info>
</template>
</guava>
</div>
<script>
<!--#include("../common/commonQuery.js"){}#-->
<!--#include("../../common/info/memberInfo.js"){}#-->
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
pageForm: {},
tableColumns: [
{ prop: "loginname", label: "工号", width: 120, fixed: "left"},
{ prop: "username", label: "姓名", width: 120, fixed: "left" },
{ prop: "sex", label: "性别", width: 120},
{ prop: "birthday", label: "出生年月", width: 120, sortable: true },
{ prop: "nationality", label: "国籍", width: 120, sortable: true, checked: 0 },
{ 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 },
{ prop: "postDoctoralJoinDate", label: "进站时间", sortable: true, checked: 0 },
{ prop: "unionName", label: "所属工会", width: 120, sortable: true },
{ prop: "unitName", label: "所属单位", width: 120, sortable: true },
{ prop: "idCardType", label: "证件类型", width: 120, sortable: true, checked: 0 },
{ prop: "idCard", label: "证件号码", width: 120, sortable: true, checked: 0 },
{ prop: "marriage", label: "婚姻状况", width: 120, sortable: true, checked: 0 },
{ prop: "jobCategory", label: "岗位类别", width: 120, sortable: true, checked: 0 },
{ prop: "professionalTitle", label: "职称", width: 120, sortable: true, checked: 0 },
{ prop: "professionalLevel", label: "职级", width: 120, sortable: true, checked: 0 },
{ prop: "employeeSource", label: "职工来源", width: 120, sortable: true, checked: 0 },
{ prop: "administrativeLevel", label: "行政级别(管理岗位)", width: 120, sortable: true, checked: 0 },
{ prop: "positionSeries", label: "岗位系列", width: 120, sortable: true, checked: 0 },
{ prop: "positionLevel", label: "岗级", width: 120, sortable: true, checked: 0 },
{ prop: "administrativePositionLevel", label: "行政岗级", width: 120, sortable: true, checked: 0 },
{ prop: "arrivalAtSchoolDate", label: "来校时间", width: 120, sortable: true, checked: 0 },
{ prop: "teachingTime", label: "从教年月", width: 120, sortable: true, checked: 0 },
{ prop: "expectedLeaveSchoolDate", label: "预计/最后离校时间", width: 120, sortable: true, checked: 0 }
],
checkedFields: []
}
},
components: {
"member-info": MEMBER_INFO,
"common-query": COMMON_QUERY
},
methods: {
openView(userId) {
this.$refs.guava.view(() => {
this.$refs.infoRef.onOpen(userId)
})
},
openEdit(userId) {
commonUtil.pjaxPush("/platform/member/info/maintain/edit?userId=" + userId)
},
search(pageForm) {
this.pageForm = Object.assign({}, this.pageForm, pageForm)
this.pageData()
},
pageData() {
this.tableLoading = true
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
this.$axios.post(loc() + "/pageData", pageForm)
.then((data) => {
if (data.code === 0) {
this.tableData = data.data.list
this.pageForm.totalCount = data.data.totalCount
} else {
this.$message.error(data.msg)
}
})
.finally(() => {
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)
},
invertCheck() {
const all = this.tableColumns.map(c => c.prop)
this.checkedFields = all.filter(p => !this.checkedFields.includes(p))
}
},
created() {
this.checkedFields = this.tableColumns.filter(c => c.checked !== 0).map(c => c.prop)
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -42,7 +42,9 @@ layout("/layouts/platform.html"){
</el-card> </el-card>
<el-card shadow="never" class="mt10"> <el-card shadow="never" class="mt10">
<table-tool label="会员统计"></table-tool> <table-tool label="会员统计">
<el-button type="primary" size="small" icon="el-icon-download" @click="doExport">导出</el-button>
</table-tool>
<el-table :data="tableData" show-summary> <el-table :data="tableData" show-summary>
<el-table-column <el-table-column
align="center" align="center"
@@ -90,7 +92,9 @@ layout("/layouts/platform.html"){
} }
}, },
methods: { methods: {
doExport() {}, doExport() {
this.$downLoad(loc() + "/doExport", this.pageForm)
},
initData() { initData() {
if (this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])) { if (this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])) {
this.$businessTool.listUnion().then((data) => { this.$businessTool.listUnion().then((data) => {