Merge branch 'feature_周何峰' into release_20260828

This commit is contained in:
2026-08-28 14:54:48 +08:00
51 changed files with 1743 additions and 896 deletions
@@ -1,7 +1,6 @@
package com.budwk.app.base.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -16,7 +15,6 @@ import java.util.Map;
* @Date 2025/9/15 15:34
* @注释 数据中心配置文件,存放接口地址和参数配置
*/
@Slf4j
@Data
@IocBean(create = "init")
public class DataCenterProperties {
@@ -24,16 +22,14 @@ public class DataCenterProperties {
@Inject
private PropertiesProxy conf;
private String tokenUrl;
private String key;
private String secret;
private String appId;
private String appSecret;
private Map<String, String> urls = new HashMap<>();
public void init() {
Map<String, String> all = conf.toMap();
tokenUrl = all.get("data-center.token-url");
key = all.get("data-center.key");
secret = all.get("data-center.secret");
appId = all.get("data-center.app-id");
appSecret = all.get("data-center.app-secret");
extract(all, "data-center.urls.", urls);
}
@@ -60,6 +60,8 @@ import java.util.Map;
@At("/platform/sys/unit")
public class SysUnitController {
private static final Log log = Logs.get();
/** 浙江交通职业技术学院在 sys_unit 中的单位主键,作为单位树未指定 pid 时的默认根。 */
private static final String DEFAULT_UNIT_ROOT_ID = "4133012036";
@Inject
private SysUnitService sysUnitService;
@Inject
@@ -300,7 +302,7 @@ public class SysUnitController {
@SaCheckLogin
public Object tree(@Param("pid") String pid, HttpServletRequest req) {
try {
String rootId = StrUtil.blankToDefault(pid, "0");
String rootId = StrUtil.blankToDefault(pid, DEFAULT_UNIT_ROOT_ID);
String virtualRootId = "root";
Cnd cnd = Cnd.NEW();
cnd.asc("unitcode");
@@ -1,31 +0,0 @@
package com.budwk.app.sys.enums;
import lombok.Getter;
/**
* @version 1.0
* @Author zzr
* @nameApi2FinanceFiledMap
* @Date 2025/9/16 9:51
* @注释 财务会费字段映射
*/
@Getter
public enum Api2FinanceFiledMap {
// 工号
RYDM("rydm", "loginname"),
// 人员名称
RYMC("rymc", "username"),
// 编制会费
HF("hf", "name"),
// 合同制会费
HF2("hf2", "unitType");
public final String apiField;
public final String dbColumn;
Api2FinanceFiledMap(String apiField, String dbColumn) {
this.apiField = apiField;
this.dbColumn = dbColumn;
}
}
@@ -130,4 +130,9 @@ public class Sys_unit extends BaseModel implements Serializable {
@Comment("部门类别区分(部门类设置 1, 其他设置 0)")
@ColDefine(type = ColType.INT)
private Integer unitTypeCode;
@Column
@Comment("生日祝福排序")
@ColDefine(type = ColType.INT)
private Integer birthdaySortNo;
}
@@ -37,19 +37,19 @@ public class Sys_user extends BaseModel implements Serializable {
@Column
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 120)
@DataCenterColumn(name = "工号", key = "ZGH")
@DataCenterColumn(name = "工号", key = "gh")
private String loginname;
@Column
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
@DataCenterColumn(name = "姓名", key = "XM")
@DataCenterColumn(name = "姓名", key = "xm")
private String username;
@Column
@Comment("性别")
@ColDefine(type = ColType.VARCHAR, width = 10)
@DataCenterColumn(name = "性别", key = "XB", dict = "USER_SEX")
@DataCenterColumn(name = "性别", key = "xb", dict = "USER_SEX")
private String sex;
@Column
@@ -65,12 +65,13 @@ public class Sys_user extends BaseModel implements Serializable {
@Column
@Comment("出生日期")
@ColDefine(type = ColType.DATE)
@DataCenterColumn(name = "出生日期", key = "csrq")
private Date birthday;
@Column
@Comment("政治面貌")
@ColDefine(type = ColType.VARCHAR, width = 100)
@DataCenterColumn(name = "政治面貌", key = "ZZMMM", dict = "USER_POLITICAL")
@DataCenterColumn(name = "政治面貌", key = "zzmm", dict = "USER_POLITICAL")
private String political;
@Column
@@ -81,7 +82,6 @@ public class Sys_user extends BaseModel implements Serializable {
@Column
@Comment("民族")
@ColDefine(type = ColType.VARCHAR, width = 20)
@DataCenterColumn(name = "民族", key = "MZM", dict = "USER_NATION")
private String nation;
@Column
@@ -102,13 +102,12 @@ public class Sys_user extends BaseModel implements Serializable {
@Column
@Comment("证件号码")
@ColDefine(type = ColType.VARCHAR, width = 30)
@DataCenterColumn(name = "证件号码", key = "SFZJH")
private String idCard;
@Column
@Comment("手机号码")
@ColDefine(type = ColType.VARCHAR, width = 32)
@DataCenterColumn(name = "手机号码", key = "YDDH")
@DataCenterColumn(name = "手机号码", key = "sjh")
private String mobile;
@Column
@@ -124,7 +123,6 @@ public class Sys_user extends BaseModel implements Serializable {
@Column
@Comment("学位")
@ColDefine(type = ColType.VARCHAR, width = 50)
// @DataCenterColumn(name = "学位", key = "zgxwmc")
private String academicDegree;
@Column
@@ -145,37 +143,32 @@ public class Sys_user extends BaseModel implements Serializable {
@Column
@Comment("职工来源")
@ColDefine(type = ColType.VARCHAR, width = 50)
// @DataCenterColumn(name = "职工来源", key = "zgly")
private String employeeSource;
@Column
@Comment("行政级别(用于管理岗位)")
@ColDefine(type = ColType.VARCHAR, width = 50)
// @DataCenterColumn(name = "行政级别(用于管理岗位)", key = "xzjb")
private String administrativeLevel;
@Column
@Comment("岗位系列")
@ColDefine(type = ColType.VARCHAR, width = 50)
// @DataCenterColumn(name = "岗位系列", key = "gwxl")
private String positionSeries;
@Column
@Comment("岗级")
@ColDefine(type = ColType.VARCHAR, width = 50)
// @DataCenterColumn(name = "岗级", key = "gj")
private String positionLevel;
@Column
@Comment("行政岗级")
@ColDefine(type = ColType.VARCHAR, width = 50)
// @DataCenterColumn(name = "行政岗级", key = "xzgj")
private String administrativePositionLevel;
@Column
@Comment("来校时间")
@ColDefine(type = ColType.VARCHAR, width = 10)
@DataCenterColumn(name = "来校时间", key = "LXNY")
@DataCenterColumn(name = "来校时间", key = "jxrq")
private String arrivalAtSchoolDate;
@Column
@@ -191,7 +184,7 @@ public class Sys_user extends BaseModel implements Serializable {
@Column
@Comment("在职状态")
@ColDefine(type = ColType.VARCHAR, width = 30)
@DataCenterColumn(name = "当前状态码", key = "DQZT", dict = "USER_STATE")
@DataCenterColumn(name = "当前状态码", key = "zzzt", dict = "USER_STATE")
private String userState;
@Column
@@ -208,7 +201,7 @@ public class Sys_user extends BaseModel implements Serializable {
@Column
@Comment("教职工类别码")
@ColDefine(type = ColType.VARCHAR, width = 30)
@DataCenterColumn(name = "教职工类别码", key = "RYLX", dict = "USER_PERSON_TYPE")
@DataCenterColumn(name = "教职工类别码", key = "rylb", dict = "USER_PERSON_TYPE")
private String personType;
@Column
@@ -230,6 +223,7 @@ public class Sys_user extends BaseModel implements Serializable {
@Column
@Comment("预计离校时间/最后离校时间")
@ColDefine(type = ColType.DATE)
@DataCenterColumn(name = "离校日期", key = "lxrq")
private Date expectedLeaveSchoolDate;
@Column
@@ -269,7 +263,7 @@ public class Sys_user extends BaseModel implements Serializable {
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@DataCenterColumn(name = "单位", key = "SZDWH")
@DataCenterColumn(name = "单位", key = "bmdm")
private String unitId;
@Column
@@ -6,7 +6,6 @@ import org.nutz.lang.util.NutMap;
import java.util.Date;
import java.util.List;
import java.util.Map;
public interface SysDataUserPullService extends BaseService<Sys_user_source> {
@@ -15,11 +14,6 @@ public interface SysDataUserPullService extends BaseService<Sys_user_source> {
*/
Date pull();
/**
* 拉取人员博士后的信息
*/
Map<String, Date> pullPostDoctoral();
/**
* 获取下拉框数据(单位、在职状态、编制类别、教职工类别)
*/
@@ -31,11 +25,4 @@ public interface SysDataUserPullService extends BaseService<Sys_user_source> {
* @return
*/
List<NutMap> pullTimeOptions();
/**
* 获取财务数据
* @return
*/
Map<String, NutMap> pullFinance();
}
@@ -1,10 +1,8 @@
package com.budwk.app.sys.services.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
@@ -21,7 +19,6 @@ import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
@@ -41,8 +38,8 @@ import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implements SysDataUnitPullService {
private static final String DMP_UNIT_KEY = "unit";
private static final int DMP_PAGE_SIZE = 1000;
private static final String CDSP_UNIT_KEY = "unit";
private static final int CDSP_PAGE_SIZE = 1000;
@Inject
private DataCenterProperties dataCenterProperties;
@@ -64,10 +61,9 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implem
@Override
@Aop(TransAop.READ_COMMITTED)
public void updateUnits(List<String> unitIds) {
String accessToken = getDmpAccessToken();
List<JSONObject> rawDataList = distinctDmpUnits(pullDmpUnits(getDmpUnitUrl(), accessToken));
List<JSONObject> rawDataList = distinctCdspUnits(pullCdspUnits());
Set<String> parentCodes = rawDataList.stream()
.map(this::getNormalizedParentCode)
.map(raw -> raw.getStr("sjbmbm"))
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
@@ -79,109 +75,45 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implem
}
/**
* 获取信息中心开放平台访问令牌
* 参数来自 data-center.token-url、data-center.key、data-center.secret
* 返回值为后续单位接口 body 中 access_token 使用的字符串
* 严格按参考项目通过 CDSP V2 单位接口单次拉取数据
* 请求头包含 appId、timestamp、sign,请求体只包含 $top=1000 和 $count=true
* 返回值为响应 value 中的单位 JSON 列表
*/
private String getDmpAccessToken() {
String tokenUrl = dataCenterProperties.getTokenUrl();
if (StrUtil.isBlank(tokenUrl)) {
throw new BaseException("未配置信息中心token地址: data-center.token-url");
}
String key = dataCenterProperties.getKey();
if (StrUtil.isBlank(key)) {
throw new BaseException("未配置信息中心token key: data-center.key");
}
String secret = dataCenterProperties.getSecret();
if (StrUtil.isBlank(secret)) {
throw new BaseException("未配置信息中心token secret: data-center.secret");
}
String params = URLUtil.buildQuery(Map.of("key", key, "secret", secret), StandardCharsets.UTF_8);
String tokenResBody = HttpUtil.createGet(tokenUrl + "?" + params).execute().body();
JSONObject tokenJsonBody = JSONUtil.parseObj(tokenResBody);
String accessToken = tokenJsonBody.getStr("access_token");
if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("result") != null) {
accessToken = tokenJsonBody.getJSONObject("result").getStr("access_token");
}
if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("data") != null) {
accessToken = tokenJsonBody.getJSONObject("data").getStr("access_token");
}
if (StrUtil.isBlank(accessToken)) {
accessToken = tokenJsonBody.getStr("token");
}
if (StrUtil.isBlank(accessToken)) {
throw new BaseException("获取信息中心token失败");
}
return accessToken;
}
/**
* 读取信息中心单位接口地址。
* 配置项为 data-center.urls.unit,返回值为单位全量接口URL。
*/
private String getDmpUnitUrl() {
String url = dataCenterProperties.getUrls().get(DMP_UNIT_KEY);
private List<JSONObject> pullCdspUnits() {
String url = dataCenterProperties.getUrls().get(CDSP_UNIT_KEY);
String appId = dataCenterProperties.getAppId();
String appSecret = dataCenterProperties.getAppSecret();
if (StrUtil.isBlank(url)) {
throw new BaseException("未配置信息中心单位接口地址: data-center.urls." + DMP_UNIT_KEY);
throw new BaseException("未配置信息中心单位接口地址: data-center.urls." + CDSP_UNIT_KEY);
}
return url;
if (StrUtil.isBlank(appId)) {
throw new BaseException("未配置信息中心appId: data-center.app-id");
}
if (StrUtil.isBlank(appSecret)) {
throw new BaseException("未配置信息中心appSecret: data-center.app-secret");
}
long timestamp = System.currentTimeMillis();
String sign = Base64.encode(DigestUtil.md5Hex(appId + appSecret + timestamp));
String resBody = HttpUtil.createPost(url)
.addHeaders(Map.of("appId", appId, "timestamp", String.valueOf(timestamp), "sign", sign))
.body(JSONUtil.toJsonStr(Map.of("$top", CDSP_PAGE_SIZE, "$count", true)))
.execute().body();
JSONObject jsonBody = JSONUtil.parseObj(resBody);
JSONArray data = jsonBody.getJSONArray("value");
if (data == null) {
throw new BaseException("获取信息中心单位数据失败,响应中缺少value数组");
}
return data.stream().map(v -> (JSONObject) v).toList();
}
/**
* 分页拉取单位接口数据
* 入参 url 为单位接口地址,accessToken 为信息中心令牌;
* 请求 body 包含 access_token、per_page、page
* 返回值为 result.data 合并后的单位原始 JSON 列表。
* 按新接口 bmbm(部门编码)去重,避免接口分页或源数据重复写入同一单位
*/
private List<JSONObject> pullDmpUnits(String url, String accessToken) {
List<JSONObject> rawDataList = new ArrayList<>();
int page = 1;
int maxPage = 1;
int total = 0;
do {
HttpRequest httpRequest = HttpUtil.createPost(url);
httpRequest.header(Header.CONTENT_TYPE, "application/json");
httpRequest.body(JSONUtil.toJsonStr(Map.of(
"access_token", accessToken,
"per_page", String.valueOf(DMP_PAGE_SIZE),
"page", String.valueOf(page)
)));
log.info("请求信息中心单位数据,第{}页: {}", page, httpRequest);
String resBody = httpRequest.execute().body();
log.info("请求信息中心单位数据第{}页结果: {}", page, resBody);
JSONObject jsonBody = JSONUtil.parseObj(resBody);
if (jsonBody.getInt("code") != 10000) {
throw new BaseException("获取信息中心单位数据失败,错误码: " + jsonBody.getInt("code") + ";错误原因:" + jsonBody.getStr("message"));
}
JSONObject result = jsonBody.getJSONObject("result");
if (result == null) {
break;
}
total = result.getInt("total", total);
maxPage = result.getInt("max_page", maxPage);
JSONArray data = result.getJSONArray("data");
if (CollUtil.isNotEmpty(data)) {
rawDataList.addAll(data.stream().map(v -> (JSONObject) v).toList());
}
log.info("信息中心单位数据拉取进度: {}/{}", rawDataList.size(), total);
page++;
} while (page <= maxPage);
return rawDataList;
}
/**
* 按单位代码去重,避免接口分页或源数据重复导致同一 DWDM 重复写入。
*/
private List<JSONObject> distinctDmpUnits(List<JSONObject> rawDataList) {
private List<JSONObject> distinctCdspUnits(List<JSONObject> rawDataList) {
Map<String, JSONObject> unitMap = new LinkedHashMap<>();
for (JSONObject raw : rawDataList) {
String unitCode = raw.getStr("DWDM");
String unitCode = raw.getStr("bmbm");
if (StrUtil.isBlank(unitCode)) {
continue;
}
@@ -192,20 +124,21 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implem
/**
* 保存或更新单位。
* DWDM 对应系统单位 id/unitcodeDWMC 对应名称,LSDWH 对应父级单位号,DWCC 对应信息中心原始单位层级。
* bmbm 对应系统单位 id/unitcodebmmc 对应名称,sjbmbm 对应父级单位号,bmdj 对应单位层级。
*/
private void saveOrUpdateUnit(JSONObject raw, Set<String> parentCodes) {
String unitCode = raw.getStr("DWDM");
String unitCode = raw.getStr("bmbm");
if (StrUtil.isBlank(unitCode)) {
return;
}
String parentId = getParentId(unitCode, getNormalizedParentCode(raw));
String parentId = getParentId(unitCode, raw.getStr("sjbmbm"));
Sys_unit unit = fetch(unitCode);
if (unit == null) {
unit = new Sys_unit();
unit.setId(unitCode);
unit.setUnitcode(unitCode);
unit.setAliasName(raw.getStr("bmmc"));
unit.setPath(getUnitPath(parentId));
setUnitValue(unit, raw, parentId, parentCodes);
dao().fastInsert(unit);
@@ -220,39 +153,26 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implem
/**
* 根据接口字段给系统单位赋值。
* DWYXBS 表示单位是否有效,DWCC 表示信息中心原始单位层次;
* 系统内置根单位占用一级层级,因此保存时统一将 DWCC 加一。
* 新接口提供 bmbm、bmmc、bmdj、sjbmbm;未提供的别名、地址、工会和单位类别等本地字段保持原值。
*/
private void setUnitValue(Sys_unit unit, JSONObject raw, String parentId, Set<String> parentCodes) {
String unitCode = raw.getStr("DWDM");
String unitName = raw.getStr("DWMC");
String aliasName = StrUtil.blankToDefault(raw.getStr("DWJC"), unitName);
Integer rawUnitLevel = raw.getInt("DWCC");
Integer unitLevel = getNormalizedUnitLevel(raw);
boolean availableBusinessUnit = "".equals(raw.getStr("DWYXBS")) && Integer.valueOf(2).equals(rawUnitLevel);
String unitCode = raw.getStr("bmbm");
unit.setParentId(parentId);
unit.setName(unitName);
unit.setAliasName(aliasName);
unit.setName(raw.getStr("bmmc"));
unit.setUnitcode(unitCode);
unit.setAddress(raw.getStr("DWDZ"));
unit.setUnitLevel(unitLevel);
unit.setUnitType(raw.getStr("DWLB"));
unit.setUnitTypeCode(availableBusinessUnit ? 1 : 0);
unit.setUnitLevel(raw.getInt("bmdj"));
unit.setHasChildren(parentCodes.contains(unitCode));
}
/**
* 计算父级单位号。
* LSDWH 为空、等于自身或本地不存在该父级时按根级处理;系统根单位 0 允许直接作为父级
* sjbmbm 为空、等于自身或本地不存在该父级时按根级处理;已存在的父单位直接作为 parentId
*/
private String getParentId(String unitCode, String parentCode) {
if (StrUtil.isBlank(parentCode) || unitCode.equals(parentCode)) {
return "";
}
if ("0".equals(parentCode)) {
return parentCode;
}
Sys_unit parentUnit = fetch(parentCode);
return parentUnit == null ? "" : parentCode;
}
@@ -271,28 +191,9 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implem
}
/**
* 信息中心一级单位应挂在系统根单位 0 下,其余单位沿用接口返回的 LSDWH
*/
private String getNormalizedParentCode(JSONObject raw) {
Integer rawUnitLevel = raw.getInt("DWCC");
if (Integer.valueOf(1).equals(rawUnitLevel)) {
return "0";
}
return raw.getStr("LSDWH");
}
/**
* 系统内置根单位占用一级层级,信息中心单位层次入库时统一加一。
*/
private Integer getNormalizedUnitLevel(JSONObject raw) {
Integer rawUnitLevel = raw.getInt("DWCC");
return rawUnitLevel == null ? null : rawUnitLevel + 1;
}
/**
* DWCC 为信息中心单位层次,排序时用于尽量先处理父级单位,便于后续子级找到父节点。
* bmdj 为信息中心单位层级,排序后优先处理父级单位,便于子单位建立 parentId 和 path
*/
private int getUnitLevel(JSONObject raw) {
return raw.getInt("DWCC", 0);
return raw.getInt("bmdj", 0);
}
}
@@ -2,14 +2,8 @@ package com.budwk.app.sys.services.impl;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
@@ -18,18 +12,13 @@ import com.budwk.app.base.config.DataCenterProperties;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.annotation.DataCenterColumn;
import com.budwk.app.sys.enums.Api2FinanceFiledMap;
import com.budwk.app.sys.models.Sys_data_dict;
import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.models.Sys_user_source;
import com.budwk.app.sys.services.SysDataUnitPullService;
import com.budwk.app.sys.services.SysDataUserPullService;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.sys.utils.DataCenterUtil;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.async.Async;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
@@ -39,11 +28,8 @@ 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.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
@@ -51,19 +37,13 @@ import java.util.stream.Collectors;
@Slf4j
public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source> implements SysDataUserPullService {
private static final String DMP_TEACHER_KEY = "teacher";
private static final String DMP_DISPATCH_KEY = "dispatch";
private static final int DMP_PAGE_SIZE = 1000;
private static final String CDSP_USER_KEY = "user";
private static final int CDSP_PAGE_SIZE = 1000;
@Inject
private SysDictService sysDictService;
@Inject
private DataCenterProperties dataCenterProperties;
@Inject
private SysDataUnitPullService sysDataUnitPullService;
@Inject
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
public SysDataUserPullServiceImpl(Dao dao) {
super(dao);
}
@@ -120,22 +100,20 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
// 字典码表Map,key为父级编码,value为子级字典列表
Map<String, List<Sys_data_dict>> dataDictMap = dataDictList.stream().collect(Collectors.groupingBy(Sys_data_dict::getParentCode));
String accessToken = getDmpAccessToken();
List<JSONObject> rawDataList = new ArrayList<>();
rawDataList.addAll(pullDmpUsers(getDmpUrl(DMP_TEACHER_KEY), accessToken, "在岗教职工"));
rawDataList.addAll(pullDmpUsers(getDmpUrl(DMP_DISPATCH_KEY), accessToken, "在岗劳务派遣"));
rawDataList = distinctDmpUsers(rawDataList);
List<JSONObject> rawDataList = distinctCdspUsers(pullCdspUsers());
if (rawDataList.isEmpty()) {
log.warn("当前未获取到人员数据");
}
Date nowDate = new Date();
Map<String, String> unitNameMap = dao().query(Sys_unit.class, Cnd.NEW()).stream()
.filter(unit -> StrUtil.isNotBlank(unit.getId()))
.collect(Collectors.toMap(Sys_unit::getId, Sys_unit::getName, (oldValue, newValue) -> oldValue));
// 赋值
List<Sys_user_source> latestSourceList = rawDataList.stream().map(raw -> {
Sys_user_source sysUser = new Sys_user_source();
// 使用缓存的字段映射
for (FieldMapping mapping : getFieldMappings()) {
try {
@@ -149,17 +127,7 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
.findFirst().orElse(new Sys_data_dict());
mapping.field.set(sysUser, StrUtil.blankToDefault(sysDataDict.getName(), raw.getStr(mapping.key)));
} else {
// 特殊处理 根据身份证号获取性别和出生年月
if ("SFZJH".equals(mapping.key)) {
String idCard = raw.getStr(mapping.key);
mapping.field.set(sysUser, idCard);
// 设置出生年月
try {
sysUser.setBirthday(IdcardUtil.getBirthDate(idCard));
} catch (Exception e) {
sysUser.setBirthday(null);
}
} else if ("LXNY".equals(mapping.key)) {
if ("jxrq".equals(mapping.key)) {
mapping.field.set(sysUser, normalizeArrivalAtSchoolDate(raw.getStr(mapping.key)));
} else {
mapping.field.set(sysUser, raw.getStr(mapping.key));
@@ -173,8 +141,8 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
}
}
sysUser.setUnitName(raw.getStr("SZDW"));
sysUser.setUnitId(resolveSourceUnitId(raw.getStr("SZDWH")));
sysUser.setUnitId(resolveSourceUnitId(raw.getStr("bmdm")));
sysUser.setUnitName(unitNameMap.get(sysUser.getUnitId()));
// 设置拉取时间
sysUser.setPullTime(nowDate);
@@ -207,7 +175,7 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
/**
* 规范化信息中心返回的来校时间。
* 入参 arrivalAtSchoolDate 对应信息中心 LXNY 字段,允许 yyyy-MM-dd 或 yyyy-MM
* 入参 arrivalAtSchoolDate 对应信息中心 jxrq 字段,允许 yyyy-MM-dd 或 yyyy-MM
* 返回值用于 sys_user_source.arrivalAtSchoolDate 和后续 sys_user.arrivalAtSchoolDate,年月格式统一补为当月 01 日。
*/
private String normalizeArrivalAtSchoolDate(String arrivalAtSchoolDate) {
@@ -227,115 +195,68 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
}
/**
* 获取信息中心开放平台访问令牌
* 返回值为接口后续调用使用的 access_token 字符串。
* 从新 CDSP V2 人员接口分页拉取数据
* 签名在本次拉取开始时生成一次,全部分页复用同一个 timestamp 和 sign
* $skip 从 1 开始作为页码,$top 固定为 1000,并根据 @odata.count 计算总页数;
* 返回值为各页 value 数组合并后的人员 JSON 列表。
*/
private String getDmpAccessToken() {
String tokenUrl = dataCenterProperties.getTokenUrl();
if (StrUtil.isBlank(tokenUrl)) {
throw new BaseException("未配置信息中心token地址: data-center.token-url");
}
String key = dataCenterProperties.getKey();
if (StrUtil.isBlank(key)) {
throw new BaseException("未配置信息中心token key: data-center.key");
}
String secret = dataCenterProperties.getSecret();
if (StrUtil.isBlank(secret)) {
throw new BaseException("未配置信息中心token secret: data-center.secret");
}
// 信息中心token接口要求key和secret通过URL参数传入,配置中分开维护,调用时统一组装。
String params = URLUtil.buildQuery(Map.of("key", key, "secret", secret), StandardCharsets.UTF_8);
HttpRequest tokenHttpRequest = HttpUtil.createGet(tokenUrl + "?" + params);
log.info("请求信息中心token: {}", tokenHttpRequest);
String tokenResBody = tokenHttpRequest.execute().body();
log.info("请求信息中心token结果: {}", tokenResBody);
JSONObject tokenJsonBody = JSONUtil.parseObj(tokenResBody);
String accessToken = tokenJsonBody.getStr("access_token");
if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("result") != null) {
accessToken = tokenJsonBody.getJSONObject("result").getStr("access_token");
}
if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("data") != null) {
accessToken = tokenJsonBody.getJSONObject("data").getStr("access_token");
}
if (StrUtil.isBlank(accessToken)) {
accessToken = tokenJsonBody.getStr("token");
}
if (StrUtil.isBlank(accessToken)) {
throw new BaseException("获取信息中心token失败");
}
return accessToken;
}
/**
* 读取信息中心人员接口地址。
* 入参 key 对应 data-center.urls 下的配置项,返回值为具体接口URL。
*/
private String getDmpUrl(String key) {
String url = dataCenterProperties.getUrls().get(key);
private List<JSONObject> pullCdspUsers() {
String url = dataCenterProperties.getUrls().get(CDSP_USER_KEY);
String appId = dataCenterProperties.getAppId();
String appSecret = dataCenterProperties.getAppSecret();
if (StrUtil.isBlank(url)) {
throw new BaseException("未配置信息中心人员接口地址: data-center.urls." + key);
throw new BaseException("未配置信息中心人员接口地址: data-center.urls." + CDSP_USER_KEY);
}
if (StrUtil.isBlank(appId)) {
throw new BaseException("未配置信息中心appId: data-center.app-id");
}
if (StrUtil.isBlank(appSecret)) {
throw new BaseException("未配置信息中心appSecret: data-center.app-secret");
}
return url;
}
/**
* 分页拉取单个人员接口数据。
* 入参 url 为人员接口地址,accessToken 为信息中心令牌,sourceName 用于日志区分接口来源。
* 返回值为该接口所有分页合并后的原始人员 JSON 列表。
*/
private List<JSONObject> pullDmpUsers(String url, String accessToken, String sourceName) {
long timestamp = System.currentTimeMillis();
String sign = Base64.encode(DigestUtil.md5Hex(appId + appSecret + timestamp));
List<JSONObject> rawDataList = new ArrayList<>();
int page = 1;
int maxPage = 1;
int total = 0;
int skip = 1;
boolean hasRecords = true;
do {
// 信息中心接口单页最多返回1000条,按page循环拉完当前接口全部数据。
HttpRequest httpRequest = HttpUtil.createPost(url);
httpRequest.header(Header.CONTENT_TYPE, "application/json");
httpRequest.body(JSONUtil.toJsonStr(Map.of(
"access_token", accessToken,
"per_page", String.valueOf(DMP_PAGE_SIZE),
"page", String.valueOf(page)
)));
log.info("请求{}人员数据,第{}页: {}", sourceName, page, httpRequest);
String resBody = httpRequest.execute().body();
log.info("请求{}人员数据第{}页结果: {}", sourceName, page, resBody);
while (hasRecords) {
String resBody = HttpUtil.createPost(url)
.addHeaders(Map.of("appId", appId, "timestamp", String.valueOf(timestamp), "sign", sign))
.body(JSONUtil.toJsonStr(Map.of(
"$skip", skip,
"$top", CDSP_PAGE_SIZE,
"$count", true
)))
.execute().body();
JSONObject jsonBody = JSONUtil.parseObj(resBody);
if (jsonBody.getInt("code") != 10000) {
throw new BaseException("获取" + sourceName + "人员数据失败,错误码: " + jsonBody.getInt("code") + ";错误原因:" + jsonBody.getStr("message"));
JSONArray data = jsonBody.getJSONArray("value");
if (data == null) {
throw new BaseException("获取信息中心人员数据失败,响应中缺少value数组");
}
JSONObject result = jsonBody.getJSONObject("result");
if (result == null) {
break;
rawDataList.addAll(data.stream().map(v -> (JSONObject) v).toList());
int totalCount = jsonBody.getInt("@odata.count", 0);
int maxPage = (int) Math.ceil((double) totalCount / CDSP_PAGE_SIZE);
log.info("信息中心人员数据拉取进度: {}/{}", rawDataList.size(), totalCount);
skip++;
if (skip > maxPage) {
hasRecords = false;
}
total = result.getInt("total", total);
maxPage = result.getInt("max_page", maxPage);
JSONArray data = result.getJSONArray("data");
if (CollUtil.isNotEmpty(data)) {
rawDataList.addAll(data.stream().map(v -> (JSONObject) v).toList());
}
log.info("{}人员数据拉取进度: {}/{}", sourceName, rawDataList.size(), total);
page++;
} while (page <= maxPage);
}
return rawDataList;
}
/**
* 合并两个接口数据时按职工号去重,避免同一批次出现重复人员。
* 按新接口 gh工号去重,避免分页或源数据重复导致同一批次出现重复人员。
* 返回值保持第一次出现的数据,用于后续统一映射入库。
*/
private List<JSONObject> distinctDmpUsers(List<JSONObject> rawDataList) {
private List<JSONObject> distinctCdspUsers(List<JSONObject> rawDataList) {
Map<String, JSONObject> userMap = new LinkedHashMap<>();
for (JSONObject raw : rawDataList) {
String loginName = raw.getStr("ZGH");
String loginName = raw.getStr("gh");
if (StrUtil.isBlank(loginName)) {
loginName = raw.getStr("ID");
continue;
}
userMap.putIfAbsent(loginName, raw);
}
@@ -344,7 +265,7 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
/**
* 根据接口返回的所在单位号转换数据源用户单位。
* 入参 rawUnitId 对应人员接口 SZDWH:5位表示三级单位,需要写入其二级父单位;其他长度保持接口原值。
* 入参 rawUnitId 对应人员接口 bmdm:5位表示三级单位,需要写入其二级父单位;其他长度保持接口原值。
* 返回值为 sys_user_source.unitId 使用的单位ID,找不到三级单位或父级为空时保留接口原始单位号,避免人员数据丢失。
*/
private String resolveSourceUnitId(String rawUnitId) {
@@ -358,118 +279,6 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
return unit.getParentId();
}
@Async
private void updateDict(List<Sys_user_source> userSources) {
//判断是否要更新在职状态字典
List<String> sourceUserStates = userSources.stream().map(Sys_user::getUserState).filter(StrUtil::isNotBlank).distinct().toList();
//判断是否要更新学历字典
List<String> sourceEducations = userSources.stream().map(Sys_user::getEducation).filter(StrUtil::isNotBlank).distinct().toList();
//判断是否要更新学位字典
List<String> sourceAcademicDegrees = userSources.stream().map(Sys_user::getAcademicDegree).filter(StrUtil::isNotBlank).distinct().toList();
//判断是否要更新教职工类别字典
List<String> personTypes = userSources.stream().map(Sys_user::getPersonType).filter(StrUtil::isNotBlank).distinct().toList();
//判断是否要更新身份类型字典
List<String> sourceIdentityTypes = userSources.stream().map(Sys_user::getIdentityType).filter(StrUtil::isNotBlank).distinct().toList();
//系统在职状态字典
List<Sys_dict> sysUserStates = sysDictService.getSubListByCode("USER_STATE");
//系统学历字典
List<Sys_dict> sysEducations = sysDictService.getSubListByCode("USER_EDUCATION");
//系统学位字典
List<Sys_dict> sysAcademicDegrees = sysDictService.getSubListByCode("USER_ACADEMIC_DEGREE");
//系统教职工类别字典
List<Sys_dict> sysStaffTypes = sysDictService.getSubListByCode("USER_STAFF_TYPE");
//系统身份类型字典
List<Sys_dict> sysIdentityTypes = sysDictService.getSubListByCode("USER_IDENTITY_TYPE");
//系统中不存在的就插入
List<Sys_dict> insertUserStates = sourceUserStates.stream().filter(state -> sysUserStates.stream().noneMatch(dict -> dict.getCode().equals(state))).map(state -> {
Sys_dict dict = new Sys_dict();
dict.setCode(state);
dict.setName(state);
return dict;
}).toList();
List<Sys_dict> insertEducations = sourceEducations.stream().filter(education -> sysEducations.stream().noneMatch(dict -> dict.getCode().equals(education))).map(education -> {
Sys_dict dict = new Sys_dict();
dict.setCode(education);
dict.setName(education);
return dict;
}).toList();
List<Sys_dict> insertAcademicDegrees = sourceAcademicDegrees.stream().filter(academicDegree -> sysAcademicDegrees.stream().noneMatch(dict -> dict.getCode().equals(academicDegree))).map(academicDegree -> {
Sys_dict dict = new Sys_dict();
dict.setCode(academicDegree);
dict.setName(academicDegree);
return dict;
}).toList();
for (Sys_dict dict : insertUserStates) {
sysDictService.saveByParentCode(dict, "USER_STATE");
}
for (Sys_dict dict : insertEducations) {
sysDictService.saveByParentCode(dict, "USER_EDUCATION");
}
for (Sys_dict dict : insertAcademicDegrees) {
sysDictService.saveByParentCode(dict, "USER_ACADEMIC_DEGREE");
}
sysDictService.clearCache();
}
@Override
public Map<String, Date> pullPostDoctoral() {
String appId = "1926887238437818370";
String secret = "32df31fcf4fc43f9a647ee1f47134f36";
List<JSONObject> allUsers = new ArrayList<>();
int skip = 0;
int totalCount = 0;
boolean firstRequest = true;
do {
long ts = System.currentTimeMillis();
String sign = Base64.encode(DigestUtil.md5Hex(appId + secret + ts, CharsetUtil.CHARSET_UTF_8));
HttpRequest httpRequest = HttpUtil.createPost("https://sjzcpt.nnu.edu.cn/cdsp/data-api/v2/DS0040");
httpRequest.header("Content-Type", "application/json");
httpRequest.header("appId", appId);
httpRequest.header("timestamp", String.valueOf(ts));
httpRequest.header("sign", sign);
HashMap<String, Object> reqBody = new HashMap<>();
reqBody.put("$count", true);
reqBody.put("$skip", skip);
reqBody.put("$top", 1000);
httpRequest.body(JSONUtil.toJsonStr(reqBody));
String resBody = httpRequest.execute().body();
JSONObject entries = JSONUtil.parseObj(resBody);
if (firstRequest) {
totalCount = entries.getInt("@odata.count", 0);
firstRequest = false;
}
JSONArray value = entries.getJSONArray("value");
if (!value.isEmpty()) {
List<JSONObject> list = value.stream().map(item -> (JSONObject) item).toList();
allUsers.addAll(list);
skip += list.size();
} else {
break;
}
} while (skip < totalCount);
Map<String, Date> data = allUsers.stream().filter(v -> v.getStr("进站日期") != null).collect(Collectors.toMap(v -> v.getStr("教职工号"), v -> v.getDate("进站日期")));
return data;
}
@Override
public NutMap searchOptions() {
Sql sql = Sqls.create("""
@@ -517,11 +326,4 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
""");
return listMap(sql);
}
@Override
public Map<String, NutMap> pullFinance() {
// 旧数据中心财务接口已停用,避免继续调用旧token/code模式接口。
throw new BaseException("旧数据中心财务拉取接口已停用");
}
}
@@ -1,44 +0,0 @@
package com.budwk.app.sys.utils;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.json.JSONObject;
import java.lang.reflect.Field;
import java.util.Map;
/**
* @version 1.0
* @Author zzr
* @nameDataCenterUtil
* @Date 2025/9/16 9:34
* @注释
*/
public class DataCenterUtil {
/**
* 映射字段
* @param obj
* @param clazz
* @param api2db
* @return
* @param <T>
*/
public static <T> T mapJsonToBean(JSONObject obj, Class<T> clazz, Map<String, String> api2db) {
T bean = ReflectUtil.newInstance(clazz);
api2db.forEach((apiField, dbColumn) -> {
Object val = obj.getByPath(apiField);
if (val == null) {
return;
}
Field field = ClassUtil.getDeclaredField(clazz, dbColumn);
if (field != null) {
Object castVal = Convert.convert(field.getType(), val);
ReflectUtil.setFieldValue(bean, field, castVal);
}
});
return bean;
}
}
@@ -42,6 +42,9 @@ import java.util.Map;
@Ok("json:full")
public class ActivityBasicUnitController {
/** 浙江交通职业技术学院在单位表中的主键,作为活动单位页面的默认根节点。 */
private static final String DEFAULT_UNIT_ROOT_ID = "4133012036";
@Inject
private BaseService baseService;
@Inject
@@ -59,7 +62,7 @@ public class ActivityBasicUnitController {
public Result pageData(@Param(value = "parentId") String parentId, PageForm pageForm) {
Cnd cnd = Cnd.NEW();
if (StrUtil.isBlank(parentId)) {
cnd.and("parentId", "=", "1");
cnd.and("parentId", "=", DEFAULT_UNIT_ROOT_ID);
} else {
cnd.and("parentId", "=", parentId);
}
@@ -77,21 +80,25 @@ public class ActivityBasicUnitController {
@SaCheckPermission("activity.basic.unit")
public Result tree(@Param("pid") String pid) {
try {
String rootId = StrUtil.blankToDefault(pid, DEFAULT_UNIT_ROOT_ID);
String virtualRootId = "root";
Cnd cnd = Cnd.NEW();
cnd.andEX("id", "=", pid);
cnd.asc("unitcode");
List<ActivityBasicUnit> list = baseService.dao().query(ActivityBasicUnit.class, cnd);
List<TreeNode<String>> nodeList = CollUtil.newArrayList();
for (int i = 0; i < list.size(); i++) {
nodeList.add(new TreeNode<>(list.get(i).getId(), list.get(i).getParentId(), list.get(i).getName(), i)
ActivityBasicUnit unit = list.get(i);
// 将当前查询根节点挂到虚拟根下,兼容数据库根节点 parentId 为空的情况。
String parentId = rootId.equals(unit.getId()) ? virtualRootId : unit.getParentId();
nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i)
.setExtra(
Map.of(
"unitTypeCode", list.get(i).getUnitTypeCode()
"unitTypeCode", unit.getUnitTypeCode()
)
));
}
List<Tree<String>> treeList = TreeUtil.build(nodeList, StrUtil.blankToDefault(pid, "0"));
List<Tree<String>> treeList = TreeUtil.build(nodeList, virtualRootId);
return Result.success(treeList);
} catch (Exception e) {
return Result.error();
@@ -11,7 +11,7 @@ import org.nutz.mvc.annotation.Ok;
public class ActivityWorksCollectionNewController {
@At("")
@Ok("beetl:/platform/zhgh/activity/workscollection/manage/index.html")
@Ok("beetl:/platform/zhgh/activity/workscollection/new/index.html")
@SaCheckPermission(value = {"activity.workscollection.manage", "activity.workscollection.new"}, mode = SaMode.OR)
public void index() {
}
@@ -0,0 +1,90 @@
package com.budwk.app.zhgh.staffmanage.birthday.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffmanage.birthday.param.UserBirthdayPageForm;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import javax.servlet.http.HttpServletResponse;
/**
* 生日祝福数据导出。
* Controller 只接收查询参数并调用 Service,名单分组、日期范围和签名表布局均由 Service 处理。
*/
@IocBean
@At("/platform/staffManage/birthday/export")
@Ok("json:full")
@Api(tags = "生日祝福数据导出")
public class UserBirthdayExportController {
@Inject
private UserBirthdayService userBirthdayService;
@At("")
@Ok("beetl:/platform/zhgh/staffmanage/birthday/export/index.html")
@SaCheckPermission("staff.birthday.export")
public void index() {
}
/**
* 分页查询待导出的生日人员。
*
* @param pageForm 分页、姓名/工号、校区、组织、人员状态和生日日期条件;校区使用 cx 或 hz
* @return Result.data 为 Paginationlist 是人员数据,totalCount 是总数
*/
@At
@ApiOperation("分页查询待导出的生日人员")
@SaCheckPermission("staff.birthday.export")
public Result pageData(UserBirthdayPageForm pageForm) {
return Result.success(userBirthdayService.pageData(pageForm));
}
/**
* 按当前查询条件导出生日人员列表。
*
* @param pageForm 与页面列表一致的筛选条件
* @param response 返回 XSSF 格式的 xlsx 文件流
*/
@At
@Ok("void")
@ApiOperation("导出当前生日人员列表")
@SaCheckPermission("staff.birthday.export")
public void doExport(UserBirthdayPageForm pageForm, HttpServletResponse response) {
userBirthdayService.exportXlsx(pageForm, response);
}
/**
* 按当前查询条件分别生成杭州、长兴校区生日名单 Sheet。
*
* @param pageForm 与页面列表一致的筛选条件
* @param response 返回 XSSF 格式的 xlsx 文件流
*/
@At
@Ok("void")
@ApiOperation("按校区导出职工生日名单")
@SaCheckPermission("staff.birthday.export")
public void doExportCampusBirthday(UserBirthdayPageForm pageForm, HttpServletResponse response) {
userBirthdayService.exportCampusBirthdayXlsx(pageForm, response);
}
/**
* 导出生日签名表。
* pageForm.campusName 使用 hz 或 cxstartDate、endDate 传 yyyy-MM-dd,未传时使用默认导出月份。
*
* @param pageForm 校区、生日日期以及其他人员筛选条件
* @param response 返回 XSSF 格式的 xlsx 文件流
*/
@At
@Ok("void")
@ApiOperation("导出生日签名表")
@SaCheckPermission("staff.birthday.export")
public void doExportSignature(UserBirthdayPageForm pageForm, HttpServletResponse response) {
userBirthdayService.exportSignatureXlsx(pageForm, response);
}
}
@@ -46,7 +46,7 @@ public class UserBirthdayManageController {
/**
* 查询生日人员。
*
* @param pageForm 分页、姓名/工号、工会、单位、人员状态及生日日期条件
* @param pageForm 分页、姓名/工号、校区、工会、单位、人员状态及生日日期条件;校区使用 cx 或 hz
* @return Result.data 为 Paginationlist 是人员数据,totalCount 是总数
*/
@At
@@ -0,0 +1,56 @@
package com.budwk.app.zhgh.staffmanage.birthday.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
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;
/**
* 生日祝福人员校区标记。
* campusId 使用 cx 表示长兴校区、hz 表示杭州校区,按工号覆盖人员单位推导出的默认校区。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("user_birthday_campus")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("生日祝福人员校区标记")
public class UserBirthdayCampus extends BaseModel {
@Name
@Comment("ID")
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String loginname;
@Column
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String username;
@Column
@Comment("校区编码,cx为长兴校区、hz为杭州校区")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String campusId;
@Column
@Comment("校区名称")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String campusName;
@Column
@Comment("备注")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String remark;
}
@@ -25,6 +25,36 @@ public class UserBirthdayPageForm extends MemberInfoPageForm {
@ApiModelProperty("生日结束日期,格式 yyyy-MM-dd")
private String endDate;
@ApiModelProperty("所属校区,cx为长兴校区、hz为杭州校区")
private String campusName;
/**
* 将所属校区条件追加到生日人员查询。
* 校区标记表中的 cx、hz 配置优先;未配置时,智能制造学院归入长兴校区,其余归入杭州校区。
*
* @param cnd 已包含人员范围和会员状态的查询条件
* @param prefix 人员视图字段前缀,生日查询使用 u.
*/
public void buildCampusSearch(Cnd cnd, String prefix) {
String campusValue = normalizeCampusValue(campusName);
if (StrUtil.isBlank(campusValue)) {
return;
}
String unitField = StrUtil.blankToDefault(prefix, "") + "unitName";
String campusField = String.format(
"CASE " +
"WHEN birthdayCampus.campusId = 'cx' THEN 'cx' " +
"WHEN birthdayCampus.campusId = 'hz' THEN 'hz' " +
"WHEN TRIM(IFNULL(%s, '')) LIKE '智能制造学院%%' THEN 'cx' " +
"ELSE 'hz' END",
unitField);
if (StrUtil.equals(campusValue, "cx")) {
cnd.and(new Static(String.format("%s = 'cx'", campusField)));
} else if (StrUtil.equals(campusValue, "hz")) {
cnd.and(new Static(String.format("%s = 'hz'", campusField)));
}
}
/**
* 将生日日期条件追加到已有人员查询条件。
* 未传日期时按配置天数查询,配置无效时默认十五天,跨年时自动使用 OR 条件。
@@ -64,4 +94,17 @@ public class UserBirthdayPageForm extends MemberInfoPageForm {
}
return DateUtil.format(DateUtil.parse(value), "MM-dd");
}
private String normalizeCampusValue(String value) {
if (StrUtil.isBlank(value)) {
return "";
}
if (StrUtil.equalsAnyIgnoreCase(value, "cx", "长兴校区")) {
return "cx";
}
if (StrUtil.equalsAnyIgnoreCase(value, "hz", "杭州校区")) {
return "hz";
}
return value;
}
}
@@ -30,6 +30,22 @@ public interface UserBirthdayService extends BaseService<Sys_user> {
*/
void exportXlsx(UserBirthdayPageForm pageForm, HttpServletResponse response);
/**
* 将符合条件的生日会员按杭州、长兴校区拆分为两个 Sheet 导出。
*
* @param pageForm 人员、组织、校区和生日日期查询条件
* @param response XSSF 格式的 Excel 文件响应
*/
void exportCampusBirthdayXlsx(UserBirthdayPageForm pageForm, HttpServletResponse response);
/**
* 导出指定校区的生日签名表;未传生日日期时按业务默认月份计算导出范围。
*
* @param pageForm campusName 使用 cx 或 hzstartDate、endDate 使用 yyyy-MM-dd
* @param response XSSF 格式的 Excel 文件响应
*/
void exportSignatureXlsx(UserBirthdayPageForm pageForm, HttpServletResponse response);
/**
* 按当前查询条件批量发送生日通知。
*
@@ -17,7 +17,17 @@ import com.budwk.app.zhgh.staffmanage.birthday.param.UserBirthdaySendMsgForm;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayMsgLogService;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
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.Cnd;
import org.nutz.dao.Dao;
@@ -30,8 +40,17 @@ import org.nutz.lang.Times;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Slf4j
@IocBean(args = {"refer:dao"})
@@ -60,6 +79,7 @@ public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implement
public void exportXlsx(UserBirthdayPageForm pageForm, HttpServletResponse response) {
try {
List<NutMap> list = listMap(buildBirthdaySql(pageForm));
fillExportUnitName(list);
List<ExcelExportEntity> columns = new ArrayList<>();
columns.add(new ExcelExportEntity("工号", "loginname", 20));
columns.add(new ExcelExportEntity("姓名", "username", 20));
@@ -67,16 +87,16 @@ public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implement
columns.add(new ExcelExportEntity("联系方式", "mobile", 20));
columns.add(new ExcelExportEntity("出生日期", "birthday", 20));
columns.add(new ExcelExportEntity("生日倒计时(天)", "daysUntilBirthday", 20));
columns.add(new ExcelExportEntity("所属校区", "campusName", 20));
columns.add(new ExcelExportEntity("在职状态", "userState", 20));
columns.add(new ExcelExportEntity("人员类型", "personType", 20));
columns.add(new ExcelExportEntity("人员性质", "preparedBy", 20));
columns.add(new ExcelExportEntity("所属工会", "unionName", 25));
columns.add(new ExcelExportEntity("所属单位", "unitName", 25));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
try (Workbook workbook = ExcelExportUtil.exportExcel(exportParams, columns, list)) {
CommonDownloadUtil.download("生日人员名单.xlsx", workbook, response);
CommonDownloadUtil.download("生日祝福人员名单.xlsx", workbook, response);
}
} catch (Exception e) {
log.error("导出生日人员名单失败", e);
@@ -84,6 +104,47 @@ public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implement
}
}
@Override
public void exportCampusBirthdayXlsx(UserBirthdayPageForm pageForm, HttpServletResponse response) {
try {
List<NutMap> list = listMap(buildBirthdaySql(pageForm));
List<NutMap> hangzhouList = filterList(list, this::isHangzhouCampus);
List<NutMap> changxingList = filterList(list, this::isChangxingCampus);
fillExportUnitName(hangzhouList);
fillExportUnitName(changxingList);
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
try (Workbook workbook = createXssfWorkbook(exportParams)) {
createBirthdaySheet(workbook, "杭州校区生日名单", hangzhouList);
createBirthdaySheet(workbook, "长兴校区生日名单", changxingList);
writeWorkbook(response, workbook, "职工生日名单.xlsx");
}
} catch (Exception e) {
log.error("导出职工生日名单失败", e);
throw new RuntimeException("导出职工生日名单失败", e);
}
}
@Override
public void exportSignatureXlsx(UserBirthdayPageForm pageForm, HttpServletResponse response) {
try {
DateRange dateRange = buildSignatureDateRange(pageForm);
String campusDisplayName = getCampusDisplayName(pageForm.getCampusName());
String exportName = dateRange.fileName + StrUtil.blankToDefault(campusDisplayName, "") + "生日签名表";
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
try (Workbook workbook = createXssfWorkbook(exportParams)) {
createSignatureSheets(workbook, exportName, pageForm, dateRange, campusDisplayName);
writeWorkbook(response, workbook, exportName + ".xlsx");
}
} catch (Exception e) {
log.error("导出生日签名表失败", e);
throw new RuntimeException("导出生日签名表失败", e);
}
}
@Override
@Aop(TransAop.READ_COMMITTED)
public int sendByQuery(UserBirthdaySendMsgForm form, String pushBy, String pushByName) {
@@ -162,11 +223,21 @@ public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implement
u.sex AS sex,
u.birthday AS birthday,
u.mobile AS mobile,
u.unitId AS unitId,
u.unitCode AS unitCode,
u.unitName AS unitName,
u.unionName AS unionName,
u.userState AS userState,
u.personType AS personType,
u.preparedBy AS preparedBy,
CASE
WHEN birthdayCampus.campusId = 'cx' THEN '长兴校区'
WHEN birthdayCampus.campusId = 'hz' THEN '杭州校区'
WHEN TRIM(IFNULL(u.unitName, '')) LIKE '智能制造学院%' THEN '长兴校区'
ELSE '杭州校区'
END AS campusName,
birthdayCampus.campusId AS birthdayCampusId,
COALESCE(birthdayUnit.birthdaySortNo, 999999) AS unitSortNo,
DATEDIFF(
CASE
WHEN DATE_FORMAT(u.birthday, '%m-%d') >= DATE_FORMAT(NOW(), '%m-%d')
@@ -176,18 +247,385 @@ public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implement
CURDATE()
) AS daysUntilBirthday
FROM vw_user u
LEFT JOIN sys_unit birthdayUnit ON birthdayUnit.id = u.unitId
LEFT JOIN user_birthday_campus birthdayCampus
ON birthdayCampus.loginname = u.loginname
AND IFNULL(birthdayCampus.delFlag, 0) = 0
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("u.birthday", "is not", null);
cnd.and("u.member", "=", 1);
pageForm.buildSearch(cnd, "u.");
pageForm.buildCampusSearch(cnd, "u.");
pageForm.buildBirthdaySearch(cnd, getConfig().getBirthdayDays());
cnd.asc("unitSortNo");
cnd.asc("u.unitCode");
cnd.asc("daysUntilBirthday");
sql.setCondition(cnd);
return sql;
}
private Workbook createXssfWorkbook(ExportParams exportParams) {
if (!ExcelType.XSSF.equals(exportParams.getType())) {
throw new IllegalArgumentException("生日名单只支持 XSSF 格式");
}
return new XSSFWorkbook();
}
private List<NutMap> filterList(List<NutMap> list, Predicate<NutMap> predicate) {
return list.stream().filter(predicate).collect(Collectors.toList());
}
/**
* 使用单位别名替换导出数据中的原单位名称,未配置别名时保留查询结果。
*/
private void fillExportUnitName(List<NutMap> list) {
if (list == null || list.isEmpty()) {
return;
}
Map<String, String> aliasMap = loadSysUnitAliasMap();
for (NutMap row : list) {
String aliasName = aliasMap.get(getMapString(row, "unitId"));
if (StrUtil.isNotBlank(aliasName)) {
row.setv("unitName", aliasName);
}
}
}
private Map<String, String> loadSysUnitAliasMap() {
Map<String, String> aliasMap = new HashMap<>();
Sql sql = Sqls.create("""
SELECT
id,
aliasName
FROM sys_unit
WHERE IFNULL(aliasName, '') <> ''
""");
sql.setCallback(Sqls.callback.maps());
dao().execute(sql);
for (NutMap item : sql.getList(NutMap.class)) {
aliasMap.put(getMapString(item, "id"), getMapString(item, "aliasName"));
}
return aliasMap;
}
private boolean isHangzhouCampus(NutMap row) {
return !isChangxingCampus(row);
}
/**
* 校区标记表优先;没有标记时,智能制造学院人员归入长兴校区,其余归入杭州校区。
*/
private boolean isChangxingCampus(NutMap row) {
String campusName = getMapString(row, "campusName");
if (StrUtil.equalsAnyIgnoreCase(campusName, "cx", "长兴校区")) {
return true;
}
if (StrUtil.equalsAnyIgnoreCase(campusName, "hz", "杭州校区")) {
return false;
}
String campusId = getMapString(row, "birthdayCampusId");
if (StrUtil.equalsAnyIgnoreCase(campusId, "cx", "长兴校区")) {
return true;
}
if (StrUtil.equalsAnyIgnoreCase(campusId, "hz", "杭州校区")) {
return false;
}
String unitName = StrUtil.trim(getMapString(row, "unitName"));
return StrUtil.isNotBlank(unitName) && unitName.startsWith("智能制造学院");
}
private String getCampusDisplayName(NutMap row) {
return isChangxingCampus(row) ? "长兴校区" : "杭州校区";
}
private String getCampusDisplayName(String campusValue) {
if (StrUtil.equalsAnyIgnoreCase(campusValue, "cx", "长兴校区")) {
return "长兴校区";
}
if (StrUtil.equalsAnyIgnoreCase(campusValue, "hz", "杭州校区")) {
return "杭州校区";
}
return "";
}
/**
* 创建带序号的校区生日名单 Sheet。
*/
private void createBirthdaySheet(Workbook workbook, String sheetName, List<NutMap> list) {
Sheet sheet = workbook.createSheet(sheetName);
String[] titles = {"序号", "工号", "姓名", "性别", "联系方式", "出生年月", "生日倒计时(天)", "所属校区", "所属工会", "所属单位"};
String[] fields = {"loginname", "username", "sex", "mobile", "birthday", "daysUntilBirthday", "campusName", "unionName", "unitName"};
CellStyle headerStyle = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
headerStyle.setFont(font);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
Row header = sheet.createRow(0);
for (int i = 0; i < titles.length; i++) {
Cell cell = header.createCell(i);
cell.setCellValue(titles[i]);
cell.setCellStyle(headerStyle);
sheet.setColumnWidth(i, 18 * 256);
}
for (int i = 0; i < list.size(); i++) {
NutMap item = list.get(i);
Row row = sheet.createRow(i + 1);
row.createCell(0).setCellValue(i + 1);
for (int j = 0; j < fields.length; j++) {
String value = "campusName".equals(fields[j])
? getCampusDisplayName(item)
: getMapString(item, fields[j]);
row.createCell(j + 1).setCellValue(value);
}
}
}
/**
* 签名表按校区和日期范围控制 Sheet:长兴校区保持单 Sheet,杭州校区跨月时按月份拆分。
*/
private void createSignatureSheets(Workbook workbook, String exportName, UserBirthdayPageForm pageForm,
DateRange dateRange, String campusDisplayName) {
if (StrUtil.equals(campusDisplayName, "长兴校区") || isSameMonth(dateRange.start, dateRange.end)) {
createSignatureSheetByRange(workbook, exportName, pageForm, dateRange, campusDisplayName);
return;
}
YearMonth month = YearMonth.from(dateRange.start);
YearMonth endMonth = YearMonth.from(dateRange.end);
while (!month.isAfter(endMonth)) {
LocalDate monthStart = month.equals(YearMonth.from(dateRange.start)) ? dateRange.start : month.atDay(1);
LocalDate monthEnd = month.equals(endMonth) ? dateRange.end : month.atEndOfMonth();
DateRange monthRange = new DateRange(monthStart, monthEnd,
buildSignatureFileName(monthStart, monthEnd), buildSignatureDisplayPeriod(monthStart, monthEnd));
createSignatureSheetByRange(workbook, month.format(DateTimeFormatter.ofPattern("M月")),
pageForm, monthRange, campusDisplayName);
month = month.plusMonths(1);
}
}
/**
* 按指定 MM-dd 生日范围查询人员并创建签名表 Sheet。
*/
private void createSignatureSheetByRange(Workbook workbook, String sheetName, UserBirthdayPageForm pageForm,
DateRange dateRange, String campusDisplayName) {
pageForm.setStartDate(dateRange.start.format(DateTimeFormatter.ISO_LOCAL_DATE));
pageForm.setEndDate(dateRange.end.format(DateTimeFormatter.ISO_LOCAL_DATE));
List<NutMap> list = listMap(buildBirthdaySql(pageForm));
fillExportUnitName(list);
createSignatureSheet(workbook, sheetName, list, dateRange.displayPeriod, campusDisplayName);
}
private void createSignatureSheet(Workbook workbook, String sheetName, List<NutMap> list,
String displayPeriod, String campusDisplayName) {
Sheet sheet = workbook.createSheet(sheetName);
sheet.setColumnWidth(0, 8 * 256);
sheet.setColumnWidth(1, 18 * 256);
sheet.setColumnWidth(2, 12 * 256);
sheet.setColumnWidth(3, 16 * 256);
sheet.setColumnWidth(4, 8 * 256);
sheet.setColumnWidth(5, 18 * 256);
sheet.setColumnWidth(6, 12 * 256);
sheet.setColumnWidth(7, 16 * 256);
CellStyle titleStyle = createCellStyle(workbook, true, (short) 16, false);
CellStyle subtitleStyle = createCellStyle(workbook, false, (short) 12, false);
CellStyle headerStyle = createCellStyle(workbook, true, (short) 11, true);
CellStyle contentStyle = createCellStyle(workbook, false, (short) 11, true);
Row titleRow = sheet.createRow(0);
titleRow.setHeightInPoints(28);
createCell(titleRow, 0, buildSignatureTitle(campusDisplayName), titleStyle);
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 7));
Row subtitleRow = sheet.createRow(1);
subtitleRow.setHeightInPoints(22);
createCell(subtitleRow, 0, buildSignatureSubtitle(displayPeriod), subtitleStyle);
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 7));
String[] headers = {"序号", "部门", "姓名", "签名", "序号", "部门", "姓名", "签名"};
Row headerRow = sheet.createRow(2);
headerRow.setHeightInPoints(22);
for (int i = 0; i < headers.length; i++) {
createCell(headerRow, i, headers[i], headerStyle);
}
int leftSize = (list.size() + 1) / 2;
for (int i = 0; i < leftSize; i++) {
Row row = sheet.createRow(i + 3);
row.setHeightInPoints(22);
fillSignatureRow(row, 0, i, getListItem(list, i), contentStyle);
fillSignatureRow(row, 4, i + leftSize, getListItem(list, i + leftSize), contentStyle);
}
}
private void fillSignatureRow(Row row, int startColumn, int index, NutMap item, CellStyle contentStyle) {
if (item == null) {
for (int i = 0; i < 4; i++) {
createCell(row, startColumn + i, "", contentStyle);
}
return;
}
createCell(row, startColumn, String.valueOf(index + 1), contentStyle);
createCell(row, startColumn + 1, getDepartmentName(item), contentStyle);
createCell(row, startColumn + 2, getMapString(item, "username"), contentStyle);
createCell(row, startColumn + 3, "", contentStyle);
}
private NutMap getListItem(List<NutMap> list, int index) {
if (index < 0 || index >= list.size()) {
return null;
}
return list.get(index);
}
private String getDepartmentName(NutMap item) {
String departmentName = getMapString(item, "unitName");
if (StrUtil.isBlank(departmentName)) {
departmentName = getMapString(item, "unionName");
}
return departmentName;
}
private CellStyle createCellStyle(Workbook workbook, boolean bold, short fontSize, boolean border) {
CellStyle style = workbook.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
if (border) {
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
}
Font font = workbook.createFont();
font.setBold(bold);
font.setFontHeightInPoints(fontSize);
style.setFont(font);
return style;
}
private void createCell(Row row, int columnIndex, String value, CellStyle style) {
Cell cell = row.createCell(columnIndex);
cell.setCellValue(StrUtil.blankToDefault(value, ""));
cell.setCellStyle(style);
}
private boolean isSameMonth(LocalDate start, LocalDate end) {
return start.getYear() == end.getYear() && start.getMonthValue() == end.getMonthValue();
}
/**
* 未指定日期时默认导出下月;下月为一月时覆盖一至二月,下月为六月时覆盖六至八月。
*/
private DateRange buildSignatureDateRange(UserBirthdayPageForm pageForm) {
if (StrUtil.isAllNotBlank(pageForm.getStartDate(), pageForm.getEndDate())) {
LocalDate start = LocalDate.parse(pageForm.getStartDate());
LocalDate end = LocalDate.parse(pageForm.getEndDate());
return new DateRange(start, end, buildSignatureFileName(start, end),
buildSignatureDateDisplayPeriod(start, end));
}
LocalDate now = LocalDate.now();
YearMonth nextMonth = YearMonth.from(now).plusMonths(1);
int month = nextMonth.getMonthValue();
LocalDate start = nextMonth.atDay(1);
LocalDate end;
if (month == 1) {
end = nextMonth.plusMonths(1).atEndOfMonth();
} else if (month == 6) {
end = nextMonth.plusMonths(2).atEndOfMonth();
} else {
end = nextMonth.atEndOfMonth();
}
return new DateRange(start, end, buildSignatureFileName(start, end),
buildSignatureDisplayPeriod(start, end));
}
private String buildSignatureFileName(LocalDate start, LocalDate end) {
if (isSameMonth(start, end)) {
return start.format(DateTimeFormatter.ofPattern("yyyy年M月"));
}
if (start.getYear() == end.getYear()) {
return start.format(DateTimeFormatter.ofPattern("yyyy年M月")) + "-" +
end.format(DateTimeFormatter.ofPattern("M月"));
}
return start.format(DateTimeFormatter.ofPattern("yyyy年M月")) + "-" +
end.format(DateTimeFormatter.ofPattern("yyyy年M月"));
}
private String buildSignatureDisplayPeriod(LocalDate start, LocalDate end) {
if (isSameMonth(start, end)) {
return start.format(DateTimeFormatter.ofPattern("yyyy年M月"));
}
if (start.getYear() == end.getYear()) {
return start.format(DateTimeFormatter.ofPattern("yyyy年M")) + "-" +
end.format(DateTimeFormatter.ofPattern("M月"));
}
return start.format(DateTimeFormatter.ofPattern("yyyy年M月")) + "-" +
end.format(DateTimeFormatter.ofPattern("yyyy年M月"));
}
private String buildSignatureDateDisplayPeriod(LocalDate start, LocalDate end) {
if (start.equals(end)) {
return start.format(DateTimeFormatter.ofPattern("yyyy年M月d日"));
}
if (isSameMonth(start, end)) {
return start.format(DateTimeFormatter.ofPattern("yyyy年M月d日")) + "-" +
end.format(DateTimeFormatter.ofPattern("d日"));
}
if (start.getYear() == end.getYear()) {
return start.format(DateTimeFormatter.ofPattern("yyyy年M月d日")) + "-" +
end.format(DateTimeFormatter.ofPattern("M月d日"));
}
return start.format(DateTimeFormatter.ofPattern("yyyy年M月d日")) + "-" +
end.format(DateTimeFormatter.ofPattern("yyyy年M月d日"));
}
private String buildSignatureTitle(String campusDisplayName) {
if (StrUtil.isBlank(campusDisplayName)) {
return "浙江交通职业技术学院职工生日蛋糕券发放名单";
}
return "浙江交通职业技术学院" + campusDisplayName + "职工生日蛋糕券发放名单";
}
private String buildSignatureSubtitle(String displayPeriod) {
return "(发放时间:" + displayPeriod + "";
}
private void writeWorkbook(HttpServletResponse response, Workbook workbook, String fileName) throws Exception {
String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodeFileName);
workbook.write(response.getOutputStream());
}
private String getMapString(NutMap row, String key) {
String value = row.getString(key);
if (StrUtil.isBlank(value)) {
value = row.getString(key.toLowerCase());
}
return StrUtil.blankToDefault(value, "");
}
private static class DateRange {
private final LocalDate start;
private final LocalDate end;
private final String fileName;
private final String displayPeriod;
private DateRange(LocalDate start, LocalDate end, String fileName, String displayPeriod) {
this.start = start;
this.end = end;
this.fileName = fileName;
this.displayPeriod = displayPeriod;
}
}
/**
* 补齐历史配置缺失的默认值,保证配置页、手动发送和定时任务使用同一套参数。
*/
@@ -732,6 +732,7 @@
</main>
<!-- 页脚 -->
<!--
<footer class="v4-footer" id="v4-footer" style="display: none;">
<div class="v4-footer-bottom">
<div class="v4-footer-copyright">
@@ -744,6 +745,7 @@
</div>
</div>
</footer>
-->
<script nonce="${cspNonce!}">
@@ -153,7 +153,7 @@ layout("/layouts/v4/baseLayout.html"){
<work-template v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN'])"></work-template>
<jcdt :list="websiteNews"></jcdt>
<!-- <jcdt :list="websiteNews"></jcdt> -->
</div>
</div>
@@ -184,7 +184,7 @@ layout("/layouts/v4/baseLayout.html"){
'stats': stats,
},
mounted() {
this.getWebSiteNews()
// this.getWebSiteNews()
},
methods: {
getWebSiteNews(){
@@ -174,10 +174,18 @@ layout("/layouts/platform.html"){
methods: {
getTreeData() {
this.$axios.post(loc() + "/tree").then((res) => {
this.treeData = res.data
if (this.treeData) {
this.currentTreeData = this.treeData[0]
this.defaultExpandedKeys = this.treeData.length > 0 ? [this.treeData[0].id] : []
this.$set(this, "treeData", res.data)
if (this.treeData && this.treeData.length > 0) {
const rootNode = this.treeData[0]
this.$set(this, "currentTreeData", rootNode)
this.$set(this, "defaultExpandedKeys", [rootNode.id])
// 树节点异步渲染完成后选中根节点,保证首次进入页面时显示当前节点高亮。
this.$nextTick(() => {
if (this.$refs.treeRef) {
this.$refs.treeRef.setCurrentKey(rootNode.id)
this.$set(this, "currentTreeNode", this.$refs.treeRef.getNode(rootNode.id))
}
})
}
})
},
@@ -1,31 +1,92 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style scoped>
.custom-tree {
background: transparent;
padding: 8px;
}
/* 统一单位树节点的间距、圆角和交互过渡效果。 */
.custom-tree .el-tree-node {
margin: 2px 0;
border-radius: 6px;
transition: all 0.2s ease;
}
.custom-tree .el-tree-node.is-current > .el-tree-node__content {
background: #e8f4ff;
border-left: 3px solid #409eff;
}
.custom-tree .el-tree-node__content {
height: 36px;
line-height: 36px;
padding: 0 8px;
border-radius: 6px;
transition: all 0.2s ease;
}
.custom-tree-node {
display: flex;
align-items: center;
width: 100%;
gap: 8px;
}
.el-icon-folder {
color: #909399;
}
.el-icon-folder-opened {
color: #409eff;
}
.custom-tree .el-tree-node__expand-icon {
transition: transform 0.3s ease;
}
.custom-tree .el-tree-node__expand-icon.expanded {
transform: rotate(90deg);
}
.node-label {
flex: 1;
font-size: 13px;
color: #303133;
font-weight: 500;
}
.custom-tree .el-tree-node__expand-icon.is-leaf {
color: transparent;
cursor: default;
}
</style>
<div id="app" v-cloak>
<el-row :gutter="10" type="flex" style="height: calc(100vh - 126px)">
<el-col :span="5">
<el-card shadow="never" style="height: 100%" body-style="{ height: '100%' }">
<div style="max-height: calc(100vh - 160px); overflow-y: auto">
<el-card shadow="never">
<el-input placeholder="输入关键字进行查找" v-model="filterText" clearable></el-input>
<div style="max-height: calc(100vh - 200px); overflow-y: auto">
<el-tree
:data="treeData"
:default-expanded-keys="['1']"
:default-expanded-keys="defaultExpandedKeys"
:expand-on-click-node="false"
:props="{
children: 'child',
label: 'name'
}"
:filter-node-method="filterNode"
:props="{children: 'children', label: 'name'}"
@node-click="treeNodeClick"
class="custom-tree"
highlight-current
node-key="id"
ref="treeRef"
>
<template slot-scope="{ node, data }">
<span class="el-tree-node__label">
<i class="el-icon-folder-opened" v-if="node.level===1"></i>
<i class="el-icon-folder" v-else></i>
{{node.label}}
</span>
<span class="custom-tree-node">
<i class="el-icon-folder-opened" v-if="node.level===1"></i>
<i class="el-icon-folder" v-else></i>
<span class="node-label">{{node.label}}</span>
</span>
</template>
</el-tree>
</div>
@@ -97,44 +158,69 @@ layout("/layouts/platform.html"){
pageForm: {
searchName: "name"
},
filterText: null,
defaultExpandedKeys: [],
treeData: [],
currentTreeNode: null,
currentTreeData: null
}
},
components: {},
watch: {
filterText(val) {
this.$refs.treeRef.filter(val)
}
},
methods: {
async doDelete(id) {
const confirm = await this.$confirm("确定要删除该单位吗?", "提示", {
doDelete(id) {
this.$confirm("确定要删除该单位吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
if (confirm === "confirm") {
const resp = await this.$axios.post(loc() + "/doDelete", { id })
if (resp.code === 0) {
this.getTreeData()
this.doSearch()
this.$message.success(resp.msg)
} else {
this.notifyWarning(resp.msg)
}
}
}).then(() => {
this.$set(this, "tableLoading", true)
this.$axios.post(loc() + "/doDelete", {id: id}).then((resp) => {
if (resp.code === 0) {
this.getTreeData()
this.doSearch()
this.$message.success(resp.msg)
} else {
this.notifyWarning(resp.msg)
}
}).finally(() => {
this.$set(this, "tableLoading", false)
})
}).catch(() => {})
},
treeNodeClick(data, node) {
this.currentTreeData = data
this.currentTreeNode = node
this.pageForm.parentId = data.id
this.$set(this, "currentTreeData", data)
this.$set(this, "currentTreeNode", node)
this.$set(this.pageForm, "parentId", data.id)
this.doSearch()
},
getTreeData() {
this.$axios.post(loc() + "/tree").then((res) => {
this.treeData = res.data
if (this.treeData) {
this.currentTreeData = this.treeData[0]
this.$set(this, "treeData", res.data)
if (this.treeData && this.treeData.length > 0) {
const rootNode = this.treeData[0]
this.$set(this, "currentTreeData", rootNode)
this.$set(this, "defaultExpandedKeys", [rootNode.id])
// 树节点异步渲染完成后选中根节点,保证首次进入页面时显示当前节点高亮。
this.$nextTick(() => {
if (this.$refs.treeRef) {
this.$refs.treeRef.setCurrentKey(rootNode.id)
this.$set(this, "currentTreeNode", this.$refs.treeRef.getNode(rootNode.id))
}
})
}
})
},
filterNode(value, data) {
if (!value) {
return true
}
return data.name.indexOf(value) !== -1
},
openUnit() {}
},
created() {
@@ -57,12 +57,6 @@ layout("/layouts/platform.html"){
<el-tab-pane label="活动模板列表" name="template"></el-tab-pane>
</el-tabs>
<table-tool :label="canManageTemplate && activeTab === 'template' ? '活动模板列表' : '活动列表'">
<el-button
size="mini"
type="primary"
icon="el-icon-plus"
@click="openAdd"
v-if="!canManageTemplate || activeTab === 'activity'">创建活动</el-button>
</table-tool>
<el-table :data="tableData" :height="tableHeight" @sort-change="pageOrder" ref="activityTableRef" style="width: 100%" row-key="id">
@@ -444,38 +438,20 @@ layout("/layouts/platform.html"){
},
watch: {
"formData.activityGroupId": {
async handler(newVal, oldVal) {
this.activityGroupList = await this.getActivityGroup()
this.userScopeDialog = false
handler(newVal, oldVal) {
this.getActivityGroup().then((activityGroupList) => {
this.activityGroupList = activityGroupList
this.userScopeDialog = false
})
},
deep: true
}
},
methods: {
isNewPage() {
return window.location.pathname.toLowerCase() === "/platform/activity/workscollection/new"
},
backToManage() {
commonUtil.pjaxPush("/platform/activity/worksCollection/manage")
},
afterSaveSuccess() {
this.formVisible = false
if (this.isNewPage()) {
this.backToManage()
} else {
this.doSearch()
this.$refs.guava.index()
}
},
initNewPage() {
const params = new URLSearchParams(window.location.search)
const mode = params.get("mode")
const id = params.get("id")
if (mode === "edit" && id) {
this.openEdit(id, true)
} else {
this.openAdd()
}
this.doSearch()
this.$refs.guava.index()
},
getActivityGroup() {
return this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup").then((res) => res.data)
@@ -537,9 +513,9 @@ layout("/layouts/platform.html"){
})
})
},
async loadFormOptions() {
loadFormOptions() {
if (this.formOptionsLoaded) {
return
return Promise.resolve()
}
if (this.formOptionsPromise) {
return this.formOptionsPromise
@@ -589,22 +565,6 @@ layout("/layouts/platform.html"){
this.$set(worksType, "allowFileTypes", worksType.allowFileTypes ? worksType.allowFileTypes : [])
},
async openAdd() {
this.copyTemplateMode = false
this.copyTemplateName = ""
await this.loadFormOptions()
this.formVisible = true
this.$refs.guava.edit()
this.$nextTick(() => {
this.formData = {
nbCount: 0,
activityColor: this.colorList[0],
subjectTypes: [{worksTypes: [{}]}],
enable: true
}
this.initData(this.formData.subjectTypes[0].worksTypes[0])
})
},
cleanCopyTemplateData(data) {
if (!data) {
return
@@ -666,37 +626,32 @@ layout("/layouts/platform.html"){
}
return data
},
async openEdit(id, copyTemplateMode = false) {
openEdit(id, copyTemplateMode = false) {
this.copyTemplateMode = !!copyTemplateMode
const [resp] = await Promise.all([
Promise.all([
this.$axios.post("/platform/activity/worksCollection/manage/findOne", {id}),
this.loadFormOptions()
])
this.formVisible = true
this.formData = resp.data
if (this.copyTemplateMode) {
this.copyTemplateName = this.formData.name
this.cleanCopyTemplateData(this.formData)
} else {
this.copyTemplateName = ""
}
this.$set(this.formData, "activityTime", [this.formData.startDateTime, this.formData.endDateTime])
// this.formData.activityTime = [this.formData.startDateTime, this.formData.endDateTime]
if (this.formData.nbStartDateTime && this.formData.nbEndDateTime) {
// this.formData.nbTime = [this.formData.nbStartDateTime, this.formData.nbEndDateTime]
this.$set(this.formData, "nbTime", [this.formData.nbStartDateTime, this.formData.nbEndDateTime])
}
this.$nextTick(() => {
this.$refs.guava.edit()
]).then(([resp]) => {
this.formVisible = true
this.formData = resp.data
if (this.copyTemplateMode) {
this.copyTemplateName = this.formData.name
this.cleanCopyTemplateData(this.formData)
} else {
this.copyTemplateName = ""
}
this.$set(this.formData, "activityTime", [this.formData.startDateTime, this.formData.endDateTime])
if (this.formData.nbStartDateTime && this.formData.nbEndDateTime) {
this.$set(this.formData, "nbTime", [this.formData.nbStartDateTime, this.formData.nbEndDateTime])
}
this.$nextTick(() => {
this.$refs.guava.edit()
})
})
},
close() {
this.formVisible = false
if (this.isNewPage()) {
this.backToManage()
} else {
this.$refs.guava.index()
}
this.$refs.guava.index()
},
doSubmit() {
this.$refs.formRef.validate((valid) => {
@@ -718,6 +673,7 @@ layout("/layouts/platform.html"){
} else {
this.$message.warning(res.msg)
}
}).finally(() => {
loading.close()
})
}
@@ -741,6 +697,7 @@ layout("/layouts/platform.html"){
} else {
this.$message.warning(res.msg)
}
}).finally(() => {
loading.close()
})
},
@@ -823,25 +780,15 @@ layout("/layouts/platform.html"){
return this.$axios.post("/platform/club/examine/apply/getClubsByRole").then((resp) => resp.data)
},
},
async created() {
if (!this.isNewPage() && this.canManageTemplate) {
created() {
if (this.canManageTemplate) {
// 有模板管理角色时默认进入活动列表,只展示未设置模板的活动。
this.$set(this.pageForm, "templateStatus", 0)
}
if (this.isNewPage()) {
await this.loadFormOptions()
this.$nextTick(() => {
this.initNewPage()
})
} else {
this.pageData()
this.loadFormOptions()
}
this.pageData()
this.loadFormOptions()
},
mounted() {
if (this.isNewPage()) {
return
}
const listCardBody = this.$el.querySelector(".works-collection-list-card > .el-card__body")
if (listCardBody && window.ResizeObserver) {
// 查询区换行、页签权限或窗口变化时同步调整表格内部滚动区域。
@@ -0,0 +1,570 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card v-loading="formLoading" shadow="never">
<div slot="header">
<span>{{pageTitle}}</span>
</div>
<div v-if="formVisible">
<el-form :model="formData" ref="formRef" label-width="130px" :rules="formRules">
<el-form-item label="活动主题" prop="name">
<el-input placeholder="请输入活动主题" v-model="formData.name" clearable></el-input>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="活动时间" prop="activityTime">
<el-date-picker
:default-time="['00:00:00']"
end-placeholder="结束时间"
start-placeholder="开始时间"
style="width: 100%"
type="datetimerange"
v-model="formData.activityTime"
value-format="yyyy-MM-dd HH:mm:ss"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="点赞时间" prop="nbTime">
<el-date-picker
:default-time="['00:00:00']"
end-placeholder="结束时间"
start-placeholder="开始时间"
style="width: 100%"
type="datetimerange"
v-model="formData.nbTime"
value-format="yyyy-MM-dd HH:mm:ss"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="每人每日点赞次数" prop="nbCount">
<el-input-number
:min="0"
placeholder="请输入每人每日点赞次数"
style="width: 100%"
v-model="formData.nbCount"
></el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="主题类型" prop="subjectTypes">
<el-row class="pb5" v-for="(type,index) in formData.subjectTypes" :key="type.id">
<el-col :span="20">
<el-input placeholder="请输入名称" v-model="type.typeName"></el-input>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="addType(index)" icon="el-icon-plus" type="primary"></el-button>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button
:disabled="index == 0"
@click="formData.subjectTypes.splice(index,1)"
icon="el-icon-delete"
type="danger"
></el-button>
</el-col>
<el-col class="pt5 pb5">
<el-row class="pb5" v-for="(worksType,worksIndex) in type.worksTypes" :key="worksType.id">
<el-col :span="18">
<el-col :span="2">
<span class="el-form-item__label" v-if="worksIndex === 0">作品类型</span>
<span v-else>&nbsp;</span>
</el-col>
<el-col :span="22">
<el-input placeholder="请输入名称" v-model="worksType.worksTypeName"></el-input>
</el-col>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="openSetting(worksType)" icon="el-icon-s-tools"
type="primary"></el-button>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="addWorksType(type, worksIndex)" icon="el-icon-plus"
type="primary"></el-button>
</el-col>
<el-col :span="2" style="text-align: center">
<el-button @click="type.worksTypes.splice(worksIndex, 1)" icon="el-icon-delete"
type="danger"></el-button>
</el-col>
</el-row>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="面向对象" prop="joinCnd">
<div style="display: flex; justify-content: space-between">
<div style="width: 99%">
<el-select clearable filterable placeholder="请选择参加人员范围" style="width: 99%"
v-model="formData.activityGroupId">
<el-option
:label="item.groupName"
:value="item.groupId"
v-for="item in activityGroupList"
:key="item.groupId"
></el-option>
</el-select>
</div>
<div>
<el-button @click="$refs.drawerUserScope.userScopeDialog = true" type="primary">设置
</el-button>
</div>
</div>
</el-form-item>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item prop="hostUnitIds" label="主办单位">
<el-select v-model="formData.hostUnitIds" clearable filterable multiple
placeholder="请选择主办单位"
style="width: 100%">
<el-option
v-for="item in unitOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="undertakeUnitIds" label="承办单位">
<el-select v-model="formData.undertakeUnitIds" clearable filterable multiple
placeholder="请选择承办单位"
style="width: 100%">
<el-option
v-for="item in unitOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="活动内容" prop="content">
<text-editor v-model="formData.content"></text-editor>
</el-form-item>
<!--<el-form-item label="活动介绍" prop="introduce">
<text-editor v-model="formData.introduce"></text-editor>
</el-form-item>
<el-form-item label="奖励方式" prop="rewardMethod">
<text-editor v-model="formData.rewardMethod"></text-editor>
</el-form-item>-->
<el-form-item label="活动封面">
<file-upload
:upload_number="1"
:value.sync="formData.cover"
accept=".jpg,.jpeg,.png"
complete_result
upload_mode="image"
upload_result_category="interval"
></file-upload>
</el-form-item>
<el-form-item label="配色" prop="activityColor">
<el-color-picker v-model="formData.activityColor" show-alpha></el-color-picker>
</el-form-item>
<el-form-item prop="enable" label="状态">
<el-radio-group v-model="formData.enable" size="small">
<el-radio :label="true" border>开启</el-radio>
<el-radio :label="false" border>关闭</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<el-row class="mt20" justify="end" type="flex">
<el-button @click="close">取消</el-button>
<el-button type="primary" v-if="copyTemplateMode || formData.isSubmit!=true" plain @click="doSave">{{ copyTemplateMode ? "另存为" : "保存" }}</el-button>
<el-button type="primary" @click="doSubmit">{{ copyTemplateMode ? "新活动提交" : "确定" }}</el-button>
</el-row>
</div>
</el-card>
<drawer-user-scope v-if="formVisible" :group_id.sync="formData.activityGroupId" @group_change="getActivityGroup"
ref="drawerUserScope"></drawer-user-scope>
<el-dialog v-if="formVisible" :close-on-click-modal="false" :visible.sync="settingDialogVisible" title="更多设置" top="4%" width="40%">
<el-form :model="currentWorksType" label-width="160px" size="small">
<el-form-item label="作品介绍最多字数">
<el-input-number :max="10000" :min="1" style="width: 100%"
v-model="currentWorksType.maxWordCount"></el-input-number>
</el-form-item>
<el-form-item label="允许上传的文件类型" prop="allowFileTypes">
<el-checkbox-group v-model="currentWorksType.allowFileTypes">
<el-checkbox key="jpg" label="jpg">jpg</el-checkbox>
<el-checkbox key="jpeg" label="jpeg">jpeg</el-checkbox>
<el-checkbox key="png" label="png">png</el-checkbox>
<el-checkbox key="mp3" label="mp3">mp3</el-checkbox>
<el-checkbox key="mp4" label="mp4">mp4</el-checkbox>
<el-checkbox key="docx" label="docx">docx</el-checkbox>
<el-checkbox key="xlsx" label="xlsx">xlsx</el-checkbox>
<el-checkbox key="pdf" label="pdf">pdf</el-checkbox>
<el-checkbox key="zip" label="zip">zip</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="允许上传的文件数量" prop="allowFileNum">
<el-input-number :max="10000" :min="1" style="width: 100%"
v-model="currentWorksType.allowFileNum"></el-input-number>
</el-form-item>
<el-form-item label="单个文件大小(kb)" prop="allowSingleFileSize">
<el-input-number :min="1" style="width: 100%"
v-model="currentWorksType.allowSingleFileSize"></el-input-number>
</el-form-item>
<el-form-item label="总文件大小(kb)" prop="allowTotalFileSize">
<el-input-number :min="1" style="width: 100%"
v-model="currentWorksType.allowTotalFileSize"></el-input-number>
</el-form-item>
<!-- <div class="left-span-label">上传图片设置</div>-->
<!-- <el-form-item label="是否上传图片">-->
<!-- <el-radio :label="true" border v-model="currentWorksType.isUploadPhoto">上传</el-radio>-->
<!-- <el-radio :label="false" border v-model="currentWorksType.isUploadPhoto">不上传</el-radio>-->
<!-- </el-form-item>-->
<!-- <template v-if="currentWorksType.isUploadPhoto === true">-->
<!-- <el-form-item label="上传图片数量">-->
<!-- <el-input-number :max="5" :min="1" style="width: 100%" v-model="currentWorksType.photoNum"></el-input-number>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="上传图片格式">-->
<!-- <el-checkbox-group v-model="currentWorksType.photoType">-->
<!-- <el-checkbox border key="jpg" label="jpg">jpg</el-checkbox>-->
<!-- <el-checkbox border key="png" label="png">png</el-checkbox>-->
<!-- <el-checkbox border key="jpeg" label="jpeg">jpeg</el-checkbox>-->
<!-- </el-checkbox-group>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="每张图片大小">-->
<!-- <el-input placeholder="请填写每张图片大小" type="number" v-model="currentWorksType.photoSize">-->
<!-- <template slot="append">Mb</template>-->
<!-- </el-input>-->
<!-- </el-form-item>-->
<!-- </template>-->
<!-- <div class="left-span-label">上传音/视频、文档设置</div>-->
<!-- <el-form-item label="是否上传音/视频、文档">-->
<!-- <el-radio :label="true" border v-model="currentWorksType.isUploadOther">上传</el-radio>-->
<!-- <el-radio :label="false" border v-model="currentWorksType.isUploadOther">不上传</el-radio>-->
<!-- </el-form-item>-->
<!-- <template v-if="currentWorksType.isUploadOther === true">-->
<!-- <el-form-item label="文件上传数量">-->
<!-- <el-input-number :max="5" :min="1" style="width: 100%" v-model="currentWorksType.otherFileNum"></el-input-number>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="上传文件格式">-->
<!-- <el-checkbox-group v-model="currentWorksType.otherType">-->
<!-- <el-checkbox key="mp3" label="mp3">mp3</el-checkbox>-->
<!-- <el-checkbox key="mp4" label="mp4">mp4</el-checkbox>-->
<!-- <el-checkbox key="docx" label="docx">docx</el-checkbox>-->
<!-- <el-checkbox key="xlsx" label="xlsx">xlsx</el-checkbox>-->
<!-- <el-checkbox key="pdf" label="pdf">pdf</el-checkbox>-->
<!-- </el-checkbox-group>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="每个文件大小">-->
<!-- <el-input placeholder="请填写每个文件大小" type="number" v-model="currentWorksType.otherSize">-->
<!-- <template slot="append">Mb</template>-->
<!-- </el-input>-->
<!-- </el-form-item>-->
</template>
</el-form>
<el-row class="mt20" justify="end" type="flex">
<el-button @click="settingDialogVisible=false">取消</el-button>
<el-button @click="settingDialogVisible = false" type="primary">确定</el-button>
</el-row>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
activityGroupList: [],
formRules: {
name: [{required: true, message: "必填", trigger: ["change", "blur"]}],
activityTime: [{required: true, message: "必填", trigger: ["change", "blur"]}],
content: [{required: true, message: "必填", trigger: ["change", "blur"]}],
subjectTypes: [{required: true, message: "必填", trigger: ["change", "blur"]}],
enable: [{required: true, message: "必填", trigger: ["change", "blur"]}]
},
currentWorksType: {
allowFileTypes: []
},
settingDialogVisible: false,
colorList: ["#081776", "#31cab1", "#e54242", "#ff8939", "#ffb712", "#1dc47b", "#4495f7", "#ff5e8b", "#5d6e7f"],
formData: {},
formLoading: false,
formVisible: true,
copyTemplateMode: false,
copyTemplateName: "",
formOptionsLoaded: false,
formOptionsPromise: null,
unitOptions: [],
clubOptions: []
}
},
computed: {
pageTitle() {
return this.copyTemplateMode ? "从模板创建作品征集活动" : "新建作品征集活动"
}
},
components: {
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue")
},
watch: {
"formData.activityGroupId": {
handler() {
this.getActivityGroup().then((activityGroupList) => {
this.activityGroupList = activityGroupList
})
},
deep: true
}
},
methods: {
/**
* 初始化独立新建页面。
* mode=edit 且携带 id 时表示从指定模板创建新活动;其他情况初始化空白活动。
*/
initPage() {
const params = new URLSearchParams(window.location.search)
const mode = params.get("mode")
const id = params.get("id")
if (mode === "edit" && id) {
this.loadTemplate(id)
return
}
this.formLoading = true
this.loadFormOptions().then(() => {
this.initNewForm()
}).finally(() => {
this.formLoading = false
})
},
initNewForm() {
const worksType = {}
this.initData(worksType)
this.formData = {
nbCount: 0,
activityColor: this.colorList[0],
subjectTypes: [{worksTypes: [worksType]}],
enable: true
}
},
loadTemplate(id) {
this.formLoading = true
Promise.all([
this.$axios.post("/platform/activity/worksCollection/manage/findOne", {id: id}),
this.loadFormOptions()
]).then(([resp]) => {
if (resp.code !== 0 || !resp.data) {
this.$message.warning(resp.msg)
return
}
this.copyTemplateMode = true
this.copyTemplateName = resp.data.name
this.formData = resp.data
this.cleanCopyTemplateData(this.formData)
this.$set(this.formData, "activityTime", [this.formData.startDateTime, this.formData.endDateTime])
if (this.formData.nbStartDateTime && this.formData.nbEndDateTime) {
this.$set(this.formData, "nbTime", [this.formData.nbStartDateTime, this.formData.nbEndDateTime])
}
}).finally(() => {
this.formLoading = false
})
},
getActivityGroup() {
return this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup").then((res) => res.data)
},
getClubsByRole() {
return this.$axios.post("/platform/club/examine/apply/getClubsByRole").then((resp) => resp.data)
},
loadFormOptions() {
if (this.formOptionsLoaded) {
return Promise.resolve()
}
if (this.formOptionsPromise) {
return this.formOptionsPromise
}
this.formOptionsPromise = Promise.all([
this.getActivityGroup(),
this.getClubsByRole(),
this.$businessTool.listUnit()
]).then(([activityGroupList, clubOptions, units]) => {
this.activityGroupList = activityGroupList
this.clubOptions = clubOptions
this.clubOptions.map((item) => {
this.$set(item, "name", item.clubName)
})
this.unitOptions = this.clubOptions.concat(units)
this.formOptionsLoaded = true
}).finally(() => {
this.formOptionsPromise = null
})
return this.formOptionsPromise
},
addType(index) {
const worksType = {}
this.initData(worksType)
this.formData.subjectTypes.splice(index + 1, 0, {worksTypes: [worksType]})
},
openSetting(worksType) {
this.currentWorksType = worksType
this.settingDialogVisible = true
},
addWorksType(type, worksIndex) {
const worksType = {}
this.initData(worksType)
type.worksTypes.splice(worksIndex + 1, 0, worksType)
},
/**
* 为作品类型补齐上传限制默认值,保证新建类型和模板类型使用相同的数据结构。
*/
initData(worksType) {
this.$set(worksType, "maxWordCount", worksType.maxWordCount ? worksType.maxWordCount : 100)
this.$set(worksType, "isUploadPhoto", worksType.isUploadPhoto ? worksType.isUploadPhoto : false)
this.$set(worksType, "photoType", worksType.photoType ? worksType.photoType : ["jpg"])
this.$set(worksType, "photoNum", worksType.photoNum ? worksType.photoNum : 1)
this.$set(worksType, "photoSize", worksType.photoSize ? worksType.photoSize : 2)
this.$set(worksType, "isUploadOther", worksType.isUploadOther ? worksType.isUploadOther : false)
this.$set(worksType, "otherType", worksType.otherType ? worksType.otherType : ["mp4", "docx"])
this.$set(worksType, "otherFileNum", worksType.otherFileNum ? worksType.otherFileNum : 1)
this.$set(worksType, "otherSize", worksType.otherSize ? worksType.otherSize : 2)
this.$set(worksType, "allowFileTypes", worksType.allowFileTypes ? worksType.allowFileTypes : [])
},
/**
* 从模板创建时清理活动、主题和作品类型的主键及审计字段,避免覆盖模板原数据。
*/
cleanCopyTemplateData(data) {
if (!data) {
return
}
this.$set(data, "id", null)
this.$set(data, "isTemplate", false)
this.$set(data, "templateStatus", 0)
this.$set(data, "createdBy", null)
this.$set(data, "createdAt", null)
this.$set(data, "updatedBy", null)
this.$set(data, "updatedAt", null)
this.$set(data, "delFlag", null)
if (Array.isArray(data.subjectTypes)) {
this.$set(data, "subjectTypes", data.subjectTypes.map((subjectType) => {
this.$set(subjectType, "id", null)
this.$set(subjectType, "activityId", null)
this.$set(subjectType, "createdBy", null)
this.$set(subjectType, "createdAt", null)
this.$set(subjectType, "updatedBy", null)
this.$set(subjectType, "updatedAt", null)
this.$set(subjectType, "delFlag", null)
if (Array.isArray(subjectType.worksTypes)) {
this.$set(subjectType, "worksTypes", subjectType.worksTypes.map((worksType) => {
this.$set(worksType, "id", null)
this.$set(worksType, "subjectId", null)
this.$set(worksType, "createdBy", null)
this.$set(worksType, "createdAt", null)
this.$set(worksType, "updatedBy", null)
this.$set(worksType, "updatedAt", null)
this.$set(worksType, "delFlag", null)
return worksType
}))
}
return subjectType
}))
}
},
validateCopyTemplateName() {
if (!this.copyTemplateMode) {
return true
}
const currentName = (this.formData.name || "").trim()
const templateName = (this.copyTemplateName || "").trim()
if (currentName && templateName && currentName === templateName) {
this.$message.warning("请修改活动名称,不能与模板名称一致")
return false
}
return true
},
/**
* 将页面时间区间转换为后端活动实体字段;新建页面只调用 save 或 insert,不执行更新。
*/
buildSubmitData() {
const data = clone(this.formData)
this.$set(data, "startDateTime", data.activityTime[0])
this.$set(data, "endDateTime", data.activityTime[1])
this.$set(data, "nbStartDateTime", data.nbTime ? data.nbTime[0] : null)
this.$set(data, "nbEndDateTime", data.nbTime ? data.nbTime[1] : null)
this.$set(data, "type", "1")
if (this.copyTemplateMode) {
this.cleanCopyTemplateData(data)
}
return data
},
backToManage() {
commonUtil.pjaxPush("/platform/activity/worksCollection/manage")
},
close() {
this.backToManage()
},
doSubmit() {
this.$refs.formRef.validate((valid) => {
if (!valid || !this.validateCopyTemplateName()) {
return
}
const submitData = this.buildSubmitData()
this.formLoading = true
this.$axios.post("/platform/activity/worksCollection/manage/insert", {
data: JSON.stringify(submitData)
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.backToManage()
} else {
this.$message.warning(res.msg)
}
}).finally(() => {
this.formLoading = false
})
})
},
doSave() {
if (!this.validateCopyTemplateName()) {
return
}
const submitData = this.buildSubmitData()
this.formLoading = true
this.$axios.post("/platform/activity/worksCollection/manage/save", {
data: JSON.stringify(submitData)
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.backToManage()
} else {
this.$message.warning(res.msg)
}
}).finally(() => {
this.formLoading = false
})
}
},
created() {
this.initPage()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,196 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<template>
<el-card shadow="never">
<search @search="searchBirthday">
<search-item label="所属校区">
<el-select v-model="pageForm.campusName" clearable filterable placeholder="请选择所属校区"
style="width:100%" @change="searchBirthday">
<el-option v-for="item in campuses" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="姓名/工号">
<el-input v-model="pageForm.searchKeyword" clearable placeholder="请输入姓名或工号"></el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会"
style="width:100%" @change="handleUnionChange" @clear="handleUnionChange">
<el-option v-for="item in unions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="所属单位">
<el-select v-model="pageForm.unitId" clearable filterable placeholder="请选择所属单位" style="width:100%">
<el-option v-for="item in units" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="在职状态">
<dict-select v-model="queryForm.userStates" code="USER_STATE" multiple placeholder="请选择在职状态"></dict-select>
</search-item>
<search-item label="人员类型">
<dict-select v-model="queryForm.personTypes" code="USER_PERSON_TYPE" multiple placeholder="请选择人员类型"></dict-select>
</search-item>
<search-item label="生日日期">
<el-date-picker v-model="birthdayRange" type="daterange" value-format="yyyy-MM-dd"
range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
style="width:100%" @change="handleBirthdayRangeChange"></el-date-picker>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool :label="'生日人员导出(默认展示未来' + birthdayDays + '天,日期查询忽略年份)'"
:columns.sync="tableColumns">
<el-button size="small" type="primary" @click="doExport">导出当前列表</el-button>
<el-button size="small" type="primary" @click="doExportSignature('hz')">导出杭州校区生日签名表</el-button>
<el-button size="small" type="primary" @click="doExportSignature('cx')">导出长兴校区生日签名表</el-button>
</table-tool>
<el-table ref="table" v-loading="tableLoading" :data="tableData" :size="tableSize"
row-key="id" style="width:100%" @sort-change="pageOrder">
<el-table-column :index="indexMethod" label="序号" type="index" width="70" align="center"></el-table-column>
<el-table-column v-for="column in tableColumns" v-if="column.visible !== false"
:key="column.prop" :prop="column.prop"
:label="column.label" :width="column.width" :fixed="column.fixed"
:sortable="column.sortable"
header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
data() {
return {
pageForm: {
searchKeyword: "",
campusName: "",
unionId: "",
unitId: "",
userStates: "[]",
personTypes: "[]",
startDate: "",
endDate: "",
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "",
pageOrderBy: ""
},
queryForm: {userStates:[],personTypes:[]},
birthdayRange: [],
campuses: [
{id:"hz",name:"杭州校区"},
{id:"cx",name:"长兴校区"}
],
unions: [],
units: [],
birthdayDays: 15,
tableColumns: [
{prop:"loginname",label:"工号",sortable:true,width:"120"},
{prop:"username",label:"姓名",sortable:true,width:"110"},
{prop:"sex",label:"性别",width:"70"},
{prop:"mobile",label:"联系方式",width:"130"},
{prop:"birthday",label:"出生日期",sortable:true,width:"120"},
{prop:"daysUntilBirthday",label:"生日倒计时(天)",sortable:true,width:"150"},
{prop:"campusName",label:"所属校区",width:"120"},
{prop:"userState",label:"在职状态",width:"100"},
{prop:"personType",label:"人员类型",width:"130"},
{prop:"unionName",label:"所属工会",width:"180"},
{prop:"unitName",label:"所属单位"}
]
}
},
methods: {
initOrganizationOptions() {
if (this.$auth.hasRoleOr("SYSADMIN, SCHOOL_UNION_ADMIN, SCHOOL_UNION_MEMBER_ADMIN")) {
this.$businessTool.listUnion().then((data) => {
this.$set(this, "unions", data || [])
})
this.$businessTool.listUnit().then((data) => {
this.$set(this, "units", data || [])
})
} else {
const unionId = this.$store.state.user.union.id
this.$businessTool.listUnion(unionId).then((data) => {
this.$set(this, "unions", data || [])
})
this.$businessTool.listUnit(unionId).then((data) => {
this.$set(this, "units", data || [])
})
}
},
handleUnionChange() {
this.$set(this.pageForm, "unitId", "")
this.$set(this, "units", [])
if (this.pageForm.unionId) {
this.$businessTool.listUnit(this.pageForm.unionId).then((data) => {
this.$set(this, "units", data || [])
})
}
},
handleBirthdayRangeChange(value) {
this.$set(this.pageForm, "startDate", value && value.length ? value[0] : "")
this.$set(this.pageForm, "endDate", value && value.length ? value[1] : "")
this.searchBirthday()
},
searchBirthday() {
this.$set(this.pageForm, "userStates", JSON.stringify(this.queryForm.userStates || []))
this.$set(this.pageForm, "personTypes", JSON.stringify(this.queryForm.personTypes || []))
this.doSearch()
},
requestParams() {
return clone(this.pageForm)
},
doExport() {
this.$downLoad(loc() + "/doExport", this.requestParams())
},
doExportSignature(campusName) {
const params = this.requestParams()
this.$set(params, "campusName", campusName)
if (!params.startDate && !params.endDate) {
this.exportSignature(params)
return
}
this.$confirm("检测到已选择生日日期,是否按照搜索栏生日日期导出?", "提示", {
confirmButtonText:"是,按搜索日期导出",
cancelButtonText:"否,默认下月导出",
distinguishCancelAndClose:true,
type:"warning"
}).then(() => {
this.exportSignature(params)
}).catch((action) => {
if (action !== "cancel") return
const defaultParams = clone(params)
this.$set(defaultParams, "startDate", "")
this.$set(defaultParams, "endDate", "")
this.exportSignature(defaultParams)
})
},
exportSignature(params) {
this.$downLoad(loc() + "/doExportSignature", params)
},
loadBirthdayConfig() {
this.$axios.post("/platform/staffManage/birthday/manage/getConfig").then((res) => {
if (res.code !== 0 || !res.data) return
this.$set(this, "birthdayDays", res.data.birthdayDays || 15)
})
}
},
created() {
this.initOrganizationOptions()
this.loadBirthdayConfig()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -6,6 +6,12 @@ layout("/layouts/platform.html"){
<template>
<el-card shadow="never">
<search @search="searchBirthday">
<search-item label="所属校区">
<el-select v-model="pageForm.campusName" clearable filterable placeholder="请选择所属校区"
style="width:100%" @change="searchBirthday">
<el-option v-for="item in campuses" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="姓名/工号">
<el-input v-model="pageForm.searchKeyword" clearable placeholder="请输入姓名或工号"></el-input>
</search-item>
@@ -26,9 +32,6 @@ layout("/layouts/platform.html"){
<search-item label="人员类型">
<dict-select v-model="queryForm.personTypes" code="USER_PERSON_TYPE" multiple placeholder="请选择人员类型"></dict-select>
</search-item>
<search-item label="人员性质">
<dict-select v-model="queryForm.preparedBys" code="PREPARED_BY" multiple placeholder="请选择人员性质"></dict-select>
</search-item>
<search-item label="生日日期">
<el-date-picker v-model="birthdayRange" type="daterange" value-format="yyyy-MM-dd"
range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
@@ -40,7 +43,7 @@ layout("/layouts/platform.html"){
<el-card shadow="never" class="mt10">
<table-tool :label="'生日人员(默认展示未来' + birthdayDays + '天,日期查询忽略年份)'"
:columns.sync="tableColumns">
<el-button size="small" type="primary" @click="doExport">导出Excel</el-button>
<!-- <el-button size="small" type="primary" @click="doExport">导出Excel</el-button>-->
<el-button size="small" type="primary" @click="openSendByQuery">发送通知</el-button>
</table-tool>
<el-table ref="table" v-loading="tableLoading" :data="tableData" :size="tableSize"
@@ -118,11 +121,11 @@ layout("/layouts/platform.html"){
return {
pageForm: {
searchKeyword: "",
campusName: "",
unionId: "",
unitId: "",
userStates: "[]",
personTypes: "[]",
preparedBys: "[]",
startDate: "",
endDate: "",
pageNumber: 1,
@@ -131,8 +134,12 @@ layout("/layouts/platform.html"){
pageOrderName: "",
pageOrderBy: ""
},
queryForm: {userStates:[],personTypes:[],preparedBys:[]},
queryForm: {userStates:[],personTypes:[]},
birthdayRange: [],
campuses: [
{id:"hz",name:"杭州校区"},
{id:"cx",name:"长兴校区"}
],
unions: [],
units: [],
tableColumns: [
@@ -144,7 +151,6 @@ layout("/layouts/platform.html"){
{prop:"daysUntilBirthday",label:"生日倒计时(天)",sortable:true,width:"150"},
{prop:"userState",label:"在职状态",width:"100"},
{prop:"personType",label:"人员类型",width:"130"},
{prop:"preparedBy",label:"人员性质",width:"130"},
{prop:"unionName",label:"所属工会",width:"180"},
{prop:"unitName",label:"所属单位",width:"180"}
],
@@ -195,7 +201,6 @@ layout("/layouts/platform.html"){
searchBirthday() {
this.$set(this.pageForm, "userStates", JSON.stringify(this.queryForm.userStates || []))
this.$set(this.pageForm, "personTypes", JSON.stringify(this.queryForm.personTypes || []))
this.$set(this.pageForm, "preparedBys", JSON.stringify(this.queryForm.preparedBys || []))
this.doSearch()
},
requestParams() {
@@ -4,7 +4,7 @@ layout("/layouts/platform.html"){
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<common-query ref="commonQueryRef" user-attribute-multiple @search="search"></common-query>
<common-query ref="commonQueryRef" @search="search"></common-query>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="申请列表">
@@ -40,12 +40,6 @@
<dict-select v-model="pageForm.aidFundMemberUserType" code="AIDFUND_MEMBER_USER_TYPE"
style="width: 100%"></dict-select>
</search-item>-->
<search-item label="人员属性">
<dict-select v-if="userAttributeMultiple" v-model="pageForm.userAttributes" code="USER_ATTRIBUTE"
multiple collapse-tags clearable style="width: 100%"></dict-select>
<dict-select v-else v-model="pageForm.userAttribute" code="USER_ATTRIBUTE"
clearable style="width: 100%"></dict-select>
</search-item>
</template>
</search>
</script>
@@ -1,12 +1,5 @@
const COMMON_QUERY = {
template: "#member-apply-common-query-template",
props: {
// 默认保持原单选行为,仅由明确传参的页面开启人员属性多选。
userAttributeMultiple: {
type: Boolean,
default: false
}
},
store,
mixins: [initTableMixins],
data() {
@@ -21,8 +14,6 @@ const COMMON_QUERY = {
personTypes: [],
preparedBys: [],
aidFundMemberUserType: '',
userAttribute: '',
userAttributes: [],
}
}
},
@@ -32,7 +23,6 @@ const COMMON_QUERY = {
pageForm.userStates = JSON.stringify(this.pageForm.userStates)
pageForm.personTypes = JSON.stringify(this.pageForm.personTypes)
pageForm.preparedBys = JSON.stringify(this.pageForm.preparedBys)
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes)
this.$emit('search', pageForm)
},
async initData(){
@@ -4,7 +4,7 @@ layout("/layouts/platform.html"){
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<common-query ref="commonQueryRef" user-attribute-multiple @search="search"></common-query>
<common-query ref="commonQueryRef" @search="search"></common-query>
</el-card>
<el-card shadow="never" class="mt10">
@@ -4,7 +4,7 @@ layout("/layouts/platform.html"){
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<common-query ref="commonQueryRef" user-attribute-multiple @search="search"></common-query>
<common-query ref="commonQueryRef" @search="search"></common-query>
</el-card>
<el-card shadow="never" class="mt10">
@@ -5,7 +5,7 @@ layout("/layouts/platform.html"){
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<common-query ref="commonQueryRef" user-attribute-multiple @search="search"></common-query>
<common-query ref="commonQueryRef" @search="search"></common-query>
</el-card>
<el-card shadow="never" class="mt10">
@@ -29,12 +29,5 @@
<dict-select v-model="pageForm.aidFundMemberUserType" placeholder="请选择人员分类" @change="doSearch"
code="AIDFUND_MEMBER_USER_TYPE"></dict-select>
</search-item>-->
<search-item label="人员属性">
<dict-select v-if="userAttributeMultiple" v-model="pageForm.userAttributes"
placeholder="请选择人员属性" @change="doSearch" code="USER_ATTRIBUTE"
multiple collapse-tags clearable></dict-select>
<dict-select v-else v-model="pageForm.userAttribute" placeholder="请选择人员属性"
@change="doSearch" code="USER_ATTRIBUTE"></dict-select>
</search-item>
</search>
</script>
@@ -1,12 +1,5 @@
const COMMON_QUERY = {
template: "#member-change-common-query-template",
props: {
// 未显式开启时保持原单选行为,避免影响未列入本次范围的页面。
userAttributeMultiple: {
type: Boolean,
default: false
}
},
store,
mixins: [initTableMixins],
data() {
@@ -21,8 +14,6 @@ const COMMON_QUERY = {
personTypes: [],
preparedBys: [],
aidFundMemberUserType: '',
userAttribute: '',
userAttributes: [],
}
}
},
@@ -32,8 +23,6 @@ const COMMON_QUERY = {
pageForm.userStates = JSON.stringify(this.pageForm.userStates)
pageForm.personTypes = JSON.stringify(this.pageForm.personTypes)
pageForm.preparedBys = JSON.stringify(this.pageForm.preparedBys)
// 多选人员属性沿用现有数组查询参数的JSON提交方式。
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes)
this.$emit('search', pageForm)
},
async initData(){
@@ -50,10 +50,6 @@ layout("/layouts/platform.html"){
<!-- <dict-select v-model="pageForm.aidFundMemberUserType" placeholder="请选择人员分类" @change="doSearch"-->
<!-- code="AIDFUND_MEMBER_USER_TYPE"></dict-select>-->
<!-- </search-item>-->
<search-item label="人员属性">
<dict-select v-model="pageForm.userAttributes" placeholder="请选择人员属性" @change="doSearch"
code="USER_ATTRIBUTE" multiple collapse-tags clearable></dict-select>
</search-item>
<search-item label="更新日期">
<el-date-picker :picker-options="pickerOptions" @change="changeDateRangeChange"
align="right" end-placeholder="结束日期" format="yyyy-MM-dd"
@@ -523,7 +519,6 @@ layout("/layouts/platform.html"){
isMember: 1,
changeTypes: [],
aidFundMemberUserType: "",
userAttributes: [],
},
unions: [],
units: [],
@@ -569,7 +564,6 @@ layout("/layouts/platform.html"){
{ prop: "personType", label: "人员类型", width: 120, sortable: true },
// 临时屏蔽“人员分类”列表字段,不删除配置,后续需要可恢复。
// { prop: "aidFundMemberUserType", label: "人员分类", width: 120, sortable: true },
{ prop: "userAttribute", 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 },
@@ -597,16 +591,6 @@ layout("/layouts/platform.html"){
"member-all-change": MEMBER_ALL_CHANGE_INFO,
},
methods: {
// 分页和导出共用该参数构建方法,保证人员属性多选值始终按 JSON 数组提交。
buildQueryPageForm(source = this.pageForm) {
const pageForm = clone(source)
pageForm.userAttributes = JSON.stringify(source.userAttributes || [])
return pageForm
},
// 覆盖公共分页方法,使后端按人员属性集合执行查询。
pageData(data = null) {
return initTableMixins.methods.pageData.call(this, this.buildQueryPageForm(data || this.pageForm))
},
dropdownCommand(command) {
const {type, data} = command
if (type === 'view') {
@@ -667,7 +651,7 @@ layout("/layouts/platform.html"){
* 导出会员名单
*/
exportExcel() {
const pageForm = this.buildQueryPageForm()
const pageForm = clone(this.pageForm)
const data = this.tableColumns.filter(item => this.checkedFields.includes(item.prop)).map(item => {
return {prop: item.prop, label: item.label}
})
@@ -4,7 +4,7 @@ layout("/layouts/platform.html"){
<div id="app">
<guava ref="guava">
<el-card shadow="never">
<common-query ref="commonQueryRef" user-attribute-multiple @search="search"></common-query>
<common-query ref="commonQueryRef" @search="search"></common-query>
</el-card>
<el-card shadow="never" class="mt10">
@@ -42,10 +42,6 @@ layout("/layouts/platform.html"){
<dict-select v-model="pageForm.aidFundMemberUserType" placeholder="请选择人员分类" @change="doSearch"
code="AIDFUND_MEMBER_USER_TYPE"></dict-select>
</search-item>-->
<search-item label="人员属性">
<dict-select v-model="pageForm.userAttributes" placeholder="请选择人员属性" @change="doSearch"
code="USER_ATTRIBUTE" multiple collapse-tags clearable></dict-select>
</search-item>
</search>
</el-card>
@@ -150,8 +146,7 @@ layout("/layouts/platform.html"){
userState: "",
personType: "",
changeType: "",
aidFundMemberUserType: "",
userAttributes: []
aidFundMemberUserType: ""
}
}
},
@@ -198,9 +193,6 @@ layout("/layouts/platform.html"){
pageForm.personTypes = JSON.stringify(pageForm.personTypes)
pageForm.changeTypes = JSON.stringify(pageForm.changeTypes)
pageForm.changeOrigins = JSON.stringify(pageForm.changeOrigins)
// 人员属性为多选条件,提交前转换为后端可解析的 JSON 数组。
pageForm.userAttributes = JSON.stringify(pageForm.userAttributes)
this.$axios.post(loc() + '/pageData', pageForm)
.then(res => {
if (res.code === 0) {
@@ -5,7 +5,7 @@ layout("/layouts/platform.html"){
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<common-query ref="commonQueryRef" user-attribute-multiple @search="search"></common-query>
<common-query ref="commonQueryRef" @search="search"></common-query>
</el-card>
<el-card shadow="never" class="mt10">
@@ -43,9 +43,5 @@
<dict-select v-model="pageForm.aidFundMemberUserType" placeholder="请选择人员分类" @change="doSearch"
code="AIDFUND_MEMBER_USER_TYPE"></dict-select>
</search-item>-->
<search-item label="人员属性">
<dict-select v-model="pageForm.userAttributes" placeholder="请选择人员属性" @change="doSearch"
code="USER_ATTRIBUTE" multiple collapse-tags clearable></dict-select>
</search-item>
</search>
</script>
@@ -22,7 +22,6 @@ const COMMON_QUERY = {
personTypes: [],
preparedBys: [],
aidFundMemberUserType: '',
userAttributes: [],
}
}
},
@@ -37,8 +36,6 @@ const COMMON_QUERY = {
pageForm.userStates = JSON.stringify(this.pageForm.userStates)
pageForm.personTypes = JSON.stringify(this.pageForm.personTypes)
pageForm.preparedBys = JSON.stringify(this.pageForm.preparedBys)
// 人员属性多选值按现有数组查询参数口径提交。
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes)
this.$emit('search', pageForm)
},
handleUnionChange() {
@@ -291,8 +291,7 @@ layout("/layouts/platform.html"){
},
search(pageForm) {
if (pageForm) {
// 当前页的在职状态、人员类型使用标签数组筛选,公共查询组件回传的是字符串,
// 这里保留当前页数组,避免选择人员属性后把标签筛选状态覆盖掉。
// 当前页的在职状态、人员类型使用标签数组筛选,需保留当前页已有筛选状态。
const currentUserStates = this.pageForm.userStates
const currentPersonTypes = this.pageForm.personTypes
this.pageForm = {...this.pageForm, ...pageForm}
@@ -458,7 +458,6 @@ layout("/layouts/platform.html"){
{ prop: "unionName", label: "所属工会", sortable: true },
{ prop: "unitName", label: "所属单位",sortable: true },
// { prop: "aidFundMemberUserType", label: "人员分类", width: 120, sortable: true },
// { prop: "userAttribute", 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 },
@@ -49,10 +49,6 @@ layout("/layouts/platform.html"){
<search-item label="人员分类" v-if="false">
<dict-select v-model="pageForm.aidFundMemberUserType" placeholder="请选择人员分类" @change="doSearch" code="AIDFUND_MEMBER_USER_TYPE"></dict-select>
</search-item>
<search-item label="人员属性">
<dict-select v-model="pageForm.userAttributes" placeholder="请选择人员属性" @change="doSearch"
code="USER_ATTRIBUTE" multiple collapse-tags clearable></dict-select>
</search-item>
</search>
</el-card>
@@ -109,8 +105,7 @@ layout("/layouts/platform.html"){
year: new Date().getFullYear() - 1 + "",
memberSearchName: "",
memberSearchKeyWord: "",
aidFundMemberUserType: "",
userAttributes: []
aidFundMemberUserType: ""
},
userId: "",
unions: [],
@@ -133,16 +128,6 @@ layout("/layouts/platform.html"){
"member-info": MEMBER_INFO
},
methods: {
// 分页和导出共用该参数构建方法,保证人员属性多选值始终按 JSON 数组提交。
buildQueryPageForm() {
const pageForm = clone(this.pageForm)
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes || [])
return pageForm
},
// 覆盖公共分页方法,使后端按人员属性集合执行查询。
pageData() {
return initTableMixins.methods.pageData.call(this, this.buildQueryPageForm())
},
openView(row) {
this.$refs.guava.view(() => {
this.$refs.memberInfoRef.onOpen(row.id)
@@ -152,7 +137,7 @@ layout("/layouts/platform.html"){
this.$downLoad("/platform/member/info/history/doExportByUnion", { year: this.pageForm.year })
},
doExport() {
this.$downLoad("/platform/member/info/history/doExport", this.buildQueryPageForm())
this.$downLoad("/platform/member/info/history/doExport", this.pageForm)
},
async initData() {
if (this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])) {
@@ -49,10 +49,6 @@ layout("/layouts/platform.html"){
<search-item label="人员分类" v-if="false">
<dict-select v-model="pageForm.aidFundMemberUserType" placeholder="请选择人员分类" @change="doSearch" code="AIDFUND_MEMBER_USER_TYPE"></dict-select>
</search-item>
<search-item label="人员属性">
<dict-select v-model="pageForm.userAttributes" placeholder="请选择人员属性" @change="doSearch"
code="USER_ATTRIBUTE" multiple collapse-tags clearable></dict-select>
</search-item>
</search>
</el-card>
@@ -113,7 +109,6 @@ layout("/layouts/platform.html"){
data() {
return {
pageForm: {
userAttributes: [],
year: new Date().getFullYear() + "",
memberSearchName: "",
memberSearchKeyWord: ""
@@ -141,12 +136,6 @@ layout("/layouts/platform.html"){
"member-add-form": MEMBER_ADD_FORM
},
methods: {
// 人员属性为多选条件,分页查询前统一转换为后端可接收的 JSON 数组。
pageData(data = null) {
const pageForm = clone(data || this.pageForm)
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes || [])
return initTableMixins.methods.pageData.call(this, pageForm)
},
openAdd() {
this.$refs.guava.edit(() => {
this.$refs.memberAddFormRef.onOpen()
@@ -129,29 +129,6 @@ layout("/layouts/platform.html"){
</span>
</el-col>
</el-row>
<el-row type="flex" align="middle" class="query-row query-row-tag">
<el-col class="query-row-title">人员属性:</el-col>
<el-col class="query-row-content query-row-content-tag">
<el-tag class="query-tag-item"
v-for="item in userAttributeOptions"
:key="item.code"
:effect="pageForm.userAttributes.includes(item.code)?'dark':'plain'"
@click="tagClick('userAttributes',item.code)">
{{ item.name }}
</el-tag>
<span class="query-tag-actions">
<el-link type="danger"
v-if="userAttributeOptions.length&&pageForm.userAttributes.length"
:underline="false"
@click="pageForm.userAttributes=[];doSearch()">清空
</el-link>
<el-link :underline="false"
@click="pageForm.userAttributes=userAttributeOptions.map(p=>p.code);doSearch()"
type="success">全部
</el-link>
</span>
</el-col>
</el-row>
</el-card>
<el-card shadow="never" class="mt10">
@@ -237,12 +214,10 @@ layout("/layouts/platform.html"){
units: [],
personTypeOptions: [],
aidFundMemberUserTypeOptions: [],
userAttributeOptions: [],
pageForm: {
currentYear: moment().format("YYYY"),
queryType: "fgh",
personTypes: [],
userAttributes: [],
aidFundMemberUserTypes: []
},
fullTableData: []
@@ -271,16 +246,12 @@ layout("/layouts/platform.html"){
this.$businessTool.getDictOptions("AIDFUND_MEMBER_USER_TYPE").then((data) => {
this.aidFundMemberUserTypeOptions = data
})
this.$businessTool.getDictOptions("USER_ATTRIBUTE").then((data) => {
this.userAttributeOptions = data
})
},
pageData() {
// 统计接口按 JSON 字符串接收多选条件,这里统一在提交前做转换,保证人员分类和人员属性都能正确过滤
// 统计接口按 JSON 字符串接收多选条件,提交前统一转换人员类型和人员分类
const pageForm = clone(this.pageForm)
pageForm.personTypes = JSON.stringify(this.pageForm.personTypes)
pageForm.aidFundMemberUserTypes = JSON.stringify(this.pageForm.aidFundMemberUserTypes)
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes)
this.$axios.post(loc() + "/pageData", pageForm).then((res) => {
if (res.code === 0) {
this.tableData = res.data
@@ -296,13 +267,6 @@ layout("/layouts/platform.html"){
this.pageForm.personTypes.push(code)
}
}
if (key === "userAttributes") {
if (this.pageForm.userAttributes.includes(code)) {
this.pageForm.userAttributes.splice(this.pageForm.userAttributes.indexOf(code), 1)
} else {
this.pageForm.userAttributes.push(code)
}
}
if (key === "aidFundMemberUserTypes") {
if (this.pageForm.aidFundMemberUserTypes.includes(code)) {
this.pageForm.aidFundMemberUserTypes.splice(this.pageForm.aidFundMemberUserTypes.indexOf(code), 1)
@@ -162,15 +162,13 @@ layout("/layouts/platform.html"){
// { 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: "unitName", label: "所属单位", sortable: true },
// 临时屏蔽“人员分类”列表字段,不删除配置,后续需要可恢复。
// { prop: "aidFundMemberUserType", label: "人员分类", width: 120, sortable: true },
{ prop: "userAttribute", 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 },
@@ -205,7 +203,6 @@ layout("/layouts/platform.html"){
pageForm.memberTypes = JSON.stringify(this.pageForm.memberTypes)
pageForm.personTypes = JSON.stringify(this.pageForm.personTypes)
pageForm.aidFundMemberUserTypes = JSON.stringify(this.pageForm.aidFundMemberUserTypes)
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes)
pageForm.userStates = JSON.stringify(this.pageForm.userStates)
const data = this.tableColumns.filter(item => this.checkedFields.includes(item.prop)).map(item => {
return {prop: item.prop, label: item.label}
@@ -230,7 +227,6 @@ layout("/layouts/platform.html"){
pageForm.memberTypes = JSON.stringify(this.pageForm.memberTypes)
pageForm.personTypes = JSON.stringify(this.pageForm.personTypes)
pageForm.aidFundMemberUserTypes = JSON.stringify(this.pageForm.aidFundMemberUserTypes)
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes)
pageForm.preparedBys = JSON.stringify(this.pageForm.preparedBys)
pageForm.memberStatus = JSON.stringify(this.pageForm.memberStatus)
pageForm.userStates = JSON.stringify(this.pageForm.userStates)
@@ -113,31 +113,6 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
</el-col>
</el-row>
<el-row type="flex" align="middle" class="query-row query-row-tag">
<el-col class="query-row-title">人员属性:</el-col>
<el-col class="query-row-content query-row-content-tag">
<el-tag
style="margin-right: 10px;cursor: pointer"
v-for="item in userAttributeOptions"
:key="item.code"
:type="item.name"
:effect="pageForm.userAttributes.includes(item.code)?'dark':'plain'"
@click="tagClick('userAttributes',item.code)">
{{ item.name }}
</el-tag>
<el-link type="danger"
v-if="userAttributeOptions.length&&pageForm.userAttributes.length"
:underline="false"
@click="pageForm.userAttributes=[];doSearch()">清空
</el-link>
<el-link :underline="false"
@click="pageForm.userAttributes=userAttributeOptions.map(p=>p.code);doSearch()"
type="success">全部
</el-link>
</el-col>
</el-row>
<el-row type="flex" align="middle" class="query-row">
<el-col class="query-row-content">
<el-row>
@@ -235,7 +210,6 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
personTypes: [],
preparedBys: [],
aidFundMemberUserTypes: [],
userAttributes: [],
userStates: [],
memberStatus: [],
year: moment().format("YYYY"),
@@ -253,7 +227,6 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
personTypeOptions: [],
preparedByOptions: [],
aidFundMemberUserTypeOptions: [],
userAttributeOptions: [],
userStateOptions: [],
memberStatusOptions: [],
unions: [],
@@ -346,7 +319,6 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
this.pageForm.personTypes = []
this.pageForm.preparedBys = []
this.pageForm.aidFundMemberUserTypes = []
this.pageForm.userAttributes = []
this.pageForm.memberStatus = []
this.pageForm.userStates = []
this.pageForm.unionId = []
@@ -387,9 +359,6 @@ const MEMBER_STATISTICS_COMPREHENSIVE_QUERY_FORM = {
this.$businessTool.getDictOptions("AIDFUND_MEMBER_USER_TYPE").then((data) => {
this.aidFundMemberUserTypeOptions = data
})
this.$businessTool.getDictOptions("USER_ATTRIBUTE").then((data) => {
this.userAttributeOptions = data
})
this.$businessTool.getDictOptions("USER_STATE").then((data) => {
this.userStateOptions = data
})
@@ -129,29 +129,6 @@ layout("/layouts/platform.html"){
</span>
</el-col>
</el-row>
<el-row type="flex" align="middle" class="query-row query-row-tag">
<el-col class="query-row-title">人员属性:</el-col>
<el-col class="query-row-content query-row-content-tag">
<el-tag class="query-tag-item"
v-for="item in userAttributeOptions"
:key="item.code"
:effect="pageForm.userAttributes.includes(item.code)?'dark':'plain'"
@click="tagClick('userAttributes',item.code)">
{{ item.name }}
</el-tag>
<span class="query-tag-actions">
<el-link type="danger"
v-if="userAttributeOptions.length&&pageForm.userAttributes.length"
:underline="false"
@click="pageForm.userAttributes=[];doSearch()">清空
</el-link>
<el-link :underline="false"
@click="pageForm.userAttributes=userAttributeOptions.map(p=>p.code);doSearch()"
type="success">全部
</el-link>
</span>
</el-col>
</el-row>
</el-card>
<el-card shadow="never" class="mt10">
@@ -198,12 +175,10 @@ layout("/layouts/platform.html"){
unions: [],
units: [],
personTypeOptions: [],
userAttributeOptions: [],
pageForm: {
currentYear: moment().format("YYYY"),
queryType: "fgh",
personTypes: [],
userAttributes: [],
aidFundMemberUserTypes: []
},
aidFundMemberUserTypeOptions: [],
@@ -232,9 +207,6 @@ layout("/layouts/platform.html"){
this.$businessTool.getDictOptions("AIDFUND_MEMBER_USER_TYPE").then((data) => {
this.aidFundMemberUserTypeOptions = data
})
this.$businessTool.getDictOptions("USER_ATTRIBUTE").then((data) => {
this.userAttributeOptions = data
})
},
tagClick(key,code) {
if (key === "personTypes") {
@@ -244,13 +216,6 @@ layout("/layouts/platform.html"){
this.pageForm.personTypes.push(code)
}
}
if (key === "userAttributes") {
if (this.pageForm.userAttributes.includes(code)) {
this.pageForm.userAttributes.splice(this.pageForm.userAttributes.indexOf(code), 1)
} else {
this.pageForm.userAttributes.push(code)
}
}
if (key === "aidFundMemberUserTypes") {
if (this.pageForm.aidFundMemberUserTypes.includes(code)) {
this.pageForm.aidFundMemberUserTypes.splice(this.pageForm.aidFundMemberUserTypes.indexOf(code), 1)
@@ -262,10 +227,9 @@ layout("/layouts/platform.html"){
},
pageData() {
const pageForm = clone(this.pageForm)
// 统计接口按 JSON 字符串接收多选条件,这里把人员分类和人员属性统一成同一提交口径
// 统计接口按 JSON 字符串接收多选条件,提交前转换人员类型和人员分类
pageForm.personTypes = JSON.stringify(this.pageForm.personTypes)
pageForm.aidFundMemberUserTypes = JSON.stringify(this.pageForm.aidFundMemberUserTypes)
pageForm.userAttributes = JSON.stringify(this.pageForm.userAttributes)
this.$axios.post(loc() + "/pageData", pageForm).then((res) => {
if (res.code === 0) {
this.tableData = res.data
@@ -94,14 +94,6 @@ const filterUser = {
v-model="pageForm.personTypes"></dict-select>
</search-item>
<search-item label="人员属性:">
<dict-select clearable code="USER_ATTRIBUTE"
multiple
collapse-tags
placeholder="请选择人员属性"
v-model="pageForm.userAttributes"></dict-select>
</search-item>
<search-item label="在职状态:">
<dict-select clearable code="USER_STATE"
multiple
@@ -169,7 +161,6 @@ const filterUser = {
userStates: [],
personTypes: [],
preparedBys: [],
userAttributes: [],
unitId: null,
threeUnitId: null
},
@@ -212,7 +203,6 @@ const filterUser = {
this.threeUnitOptions = []
this.$set(this.pageForm, "personTypes", [])
this.$set(this.pageForm, "preparedBys", [])
this.$set(this.pageForm, "userAttributes", [])
this.$set(this.pageForm, "userStates", [])
this.$set(this.pageForm, "isMember", null)
},
@@ -65,10 +65,6 @@ layout("/layouts/platform.html"){
<dict-select v-model="pageForm.aidFundMemberUserType" placeholder="请选择人员分类" @change="doSearch"
code="AIDFUND_MEMBER_USER_TYPE"></dict-select>
</search-item>
<search-item label="人员属性">
<dict-select v-model="pageForm.userAttribute" placeholder="请选择人员属性" @change="doSearch"
code="USER_ATTRIBUTE"></dict-select>
</search-item>
</search>
</el-card>
@@ -96,14 +96,6 @@ const addUser = {
v-model="pageForm.personTypes"></dict-select>
</search-item>
<search-item label="人员属性:">
<dict-select clearable code="USER_ATTRIBUTE"
multiple
collapse-tags
placeholder="请选择人员属性"
v-model="pageForm.userAttributes"></dict-select>
</search-item>
<search-item label="在职状态:">
<dict-select clearable code="USER_STATE"
multiple
@@ -173,7 +165,6 @@ const addUser = {
unitId: null,
threeUnitId: null,
personTypes: [],
userAttributes: [],
preparedBys: [],
userStates: [],
isMember: null
@@ -216,7 +207,6 @@ const addUser = {
this.$set(this.pageForm, "threeUnitId", null)
this.$set(this, "threeUnitOptions", [])
this.$set(this.pageForm, "personTypes", [])
this.$set(this.pageForm, "userAttributes", [])
this.$set(this.pageForm, "preparedBys", [])
this.$set(this.pageForm, "userStates", [])
this.$set(this.pageForm, "isMember", null)
@@ -128,11 +128,6 @@ layout("/layouts/platform.html"){
<dict-select v-model="pageForm.aidFundMemberUserType" placeholder="请选择人员分类" @change="doSearch"
code="AIDFUND_MEMBER_USER_TYPE"></dict-select>
</search-item>
<search-item label="人员属性">
<dict-select v-model="pageForm.userAttributes" placeholder="请选择人员属性" @change="doSearch"
code="USER_ATTRIBUTE" clearable multiple collapse-tags></dict-select>
</search-item>
<!-- <search-item label="所选福利:">-->
<!-- <el-select clearable placeholder="请选择所选福利" style="width: 100%" v-model="pageForm.optionId">-->
<!-- <el-option :key="item.id" :label="item.optionName" :value="item.id" v-for="item in pageFormProject"></el-option>-->