人员管理、民主管理、主页优化

This commit is contained in:
2026-10-30 17:49:33 +08:00
parent 7dacfd7e5a
commit 8960d0e723
33 changed files with 296 additions and 111 deletions
@@ -37,6 +37,7 @@ import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.GET;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import org.nutz.mvc.annotation.POST;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
@@ -499,6 +500,24 @@ public class SysRoleController {
return Result.success(list);
}
/**
* 为全部 member=1 的用户设置会员角色,无需传入用户 ID、角色 ID 或列表筛选参数。
*
* @return JSON Result,data.addedCount 为新增关联数,data.enabledCount 为恢复启用关联数
*/
@At
@POST
@Ok("json")
@SaCheckPermission("sys.manager.role.user")
@SLog(tag = "一键设置会员角色", msg = "为全部member=1的人员补齐会员角色")
@Aop(TransAop.READ_COMMITTED)
public Result doSettingMemberRole() {
NutMap counts = sysRoleService.settingMemberRole();
sysRoleService.clearCache();
sysUserService.clearCache();
return Result.success("会员角色设置完成", counts);
}
@At
@Ok("json")
@SaCheckPermission("sys.manager.role")
@@ -8,6 +8,7 @@ import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_unit;
import java.util.List;
import org.nutz.lang.util.NutMap;
/**
* Created by wizzer on 2016/12/22.
@@ -128,6 +129,13 @@ public interface SysRoleService extends BaseService<Sys_role> {
*/
List<String> getRoleIdsByCode(List<RoleConstant> roleConstantList);
/**
* 为用户表中 member=1 的全部人员补齐启用的 MEMBER 角色,不受列表筛选影响。
*
* @return addedCount 为新增关联数,enabledCount 为恢复启用的关联数;已有启用关联不重复添加
*/
NutMap settingMemberRole();
/**
* 清空缓存
*/
@@ -20,6 +20,7 @@ import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.plugins.wkcache.annotation.CacheDefaults;
import org.nutz.plugins.wkcache.annotation.CacheRemoveAll;
import org.nutz.plugins.wkcache.annotation.CacheResult;
@@ -284,4 +285,44 @@ public class SysRoleServiceImpl extends BaseServiceImpl<Sys_role> implements Sys
public void clearCache() {
}
/**
* 批量补齐会员角色。仅处理 member=1 的用户,保留其他角色及账号禁用状态。
*
* @return addedCount 为新增关联数,enabledCount 为恢复启用的关联数
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public NutMap settingMemberRole() {
// 锁定同一会员角色,使本接口的并发请求依次执行,避免重复插入用户角色关系。
Sql roleSql = Sqls.create("SELECT * FROM sys_role WHERE code=@code FOR UPDATE");
roleSql.setParam("code", RoleConstant.MEMBER.name());
roleSql.setEntity(dao().getEntity(Sys_role.class));
roleSql.setCallback(Sqls.callback.entities());
dao().execute(roleSql);
List<Sys_role> roles = roleSql.getList(Sys_role.class);
if (roles.isEmpty()) {
throw new BaseException("会员角色(MEMBER)不存在,请先配置会员角色");
}
Sys_role role = roles.get(0);
if (role.isDisabled()) {
throw new BaseException("会员角色(MEMBER)已禁用,请先启用会员角色");
}
// 历史关联可能已停用或未填写 enable;恢复关联即可,无需重新插入。
Sql enableSql = Sqls.create("UPDATE sys_user_role SET enable=1 WHERE roleId=@roleId "
+ "AND (enable IS NULL OR enable<>1) "
+ "AND EXISTS (SELECT 1 FROM sys_user u WHERE u.id=sys_user_role.userId AND u.member=1)");
enableSql.setParam("roleId", role.getId());
dao().execute(enableSql);
// 按用户、角色判重,只为缺少会员关联的人员添加记录;不删除任何已有角色。
Sql insertSql = Sqls.create("INSERT INTO sys_user_role (userId,roleId,enable) "
+ "SELECT u.id,@roleId,1 FROM sys_user u WHERE u.member=1 "
+ "AND NOT EXISTS (SELECT 1 FROM sys_user_role ur WHERE ur.userId=u.id AND ur.roleId=@roleId)");
insertSql.setParam("roleId", role.getId());
dao().execute(insertSql);
return NutMap.NEW().addv("addedCount", insertSql.getUpdateCount())
.addv("enabledCount", enableSql.getUpdateCount());
}
}
@@ -86,7 +86,8 @@ public class GrassrootsCongressMaterialMineController {
cnd.and("info.createdBy", "=", SecurityUtil.getUserId());
}
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.and(Cnd.likeEX("info.meetingName", pageForm.getSearchKeyword()));
// searchKeyword 为资料名称关键词,按资料表的 materialsName 字段模糊匹配。
cnd.and(Cnd.likeEX("info.materialsName", pageForm.getSearchKeyword()));
}
cnd.andEX("YEAR(info.createdTime)", "=", pageForm.getYear());
cnd.desc("info.createdTime");
@@ -20,6 +20,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.web.commons.base.Globals;
import com.budwk.app.zhgh.staffmanage.member.models.MemberApplyRecord;
import com.budwk.app.zhgh.staffmanage.member.service.MemberCommonService;
import com.budwk.app.zhgh.staffmanage.member.service.MemberApplyRecordService;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
@@ -54,6 +55,8 @@ public class MemberApplyController {
private FlowCommonService flowCommonService;
@Inject
private MemberCommonService memberCommonService;
@Inject
private MemberApplyRecordService memberApplyRecordService;
@At("")
@Ok("beetl:/platform/zhgh/staffmanage/member/apply/submit/index.html")
@@ -70,13 +73,14 @@ public class MemberApplyController {
@At
@ApiOperation("保存申请")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("member.apply.submit")
@SLog(tag = "会员入会申请", msg = "保存申请,申请人: ${args[0].username}")
public Result save(@Param("data") MemberApplyRecord memberApplyRecord) {
if (StrUtil.isBlank(memberApplyRecord.getId())) {
memberCommonService.validateApplyOrChangeIsDoing(null, "apply");
}
dao.insertOrUpdate(memberApplyRecord);
memberApplyRecordService.saveApplyRecord(memberApplyRecord);
return Result.success();
}
@@ -105,7 +109,7 @@ public class MemberApplyController {
.and(new Static("JSON_CONTAINS(unitIds, '\"%s\"')".formatted(SecurityUtil.getUnitId()))));
args.set("type", Lang.isEmpty(group) ? "branch" : "group");
dao.insertOrUpdate(memberApplyRecord);
memberApplyRecordService.saveApplyRecord(memberApplyRecord);
args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode());
args.set(FlowConst.FORM_DATA, memberApplyRecord);
@@ -143,7 +147,7 @@ public class MemberApplyController {
.and(new Static("JSON_CONTAINS(unitIds, '\"%s\"')".formatted(SecurityUtil.getUnitId()))));
dict.set("type", Lang.isEmpty(group) ? "branch" : "group");
dao.insertOrUpdate(memberApplyRecord);
memberApplyRecordService.saveApplyRecord(memberApplyRecord);
dict.set(FlowConst.PROCESS_TASK_ID_KEY, taskId);
dict.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode());
@@ -64,7 +64,7 @@ public class MemberBranchUnionAuditController {
info.sex,
info.userName,
info.loginName,
info.unitName,
COALESCE(changeUnit.name, info.unitName) AS unitName,
info.unionName,
info.userState,
info.preparedBy,
@@ -91,6 +91,7 @@ public class MemberBranchUnionAuditController {
wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN member_change_record info ON info.id = ins.businessNo
LEFT JOIN sys_unit changeUnit ON changeUnit.id = info.unitId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
@@ -111,7 +112,8 @@ public class MemberBranchUnionAuditController {
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("info.applyDateTime");
} else {
cnd.orderBy("info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
// 单位列按实际显示名称排序,其他字段保持原有排序规则。
cnd.orderBy("unitName".equals(pageForm.getPageOrderName()) ? "unitName" : "info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("t.id");
sql.setCondition(cnd);
@@ -72,6 +72,7 @@ public class MemberChangeApplyController {
@At
@ApiOperation("保存申请")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("member.apply.submit")
@SLog(tag = "会员变更申请", msg = "保存申请,申请人: ${args[0].username}")
public Result save(@Param("data") MemberChangeRecord memberChangeRecord) {
@@ -147,7 +148,7 @@ public class MemberChangeApplyController {
@ApiModelProperty("根据id查询变更记录")
@SaCheckPermission("member.change.apply")
public Result findChangeById(String id){
return Result.success().addData(dao.fetch(MemberChangeRecord.class, id));
return Result.success().addData(memberCommonService.findChangeRecordWithUnitName(id));
}
/**
@@ -76,7 +76,7 @@ public class MemberChangeMineController {
info.username,
info.loginname,
info.sex,
info.unitName,
COALESCE(changeUnit.name, info.unitName) AS unitName,
info.unionName,
info.userState,
info.preparedBy,
@@ -103,6 +103,7 @@ public class MemberChangeMineController {
member_change_record info
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN sys_unit changeUnit ON changeUnit.id = info.unitId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
$condition
""");
@@ -124,7 +125,8 @@ public class MemberChangeMineController {
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("info.applyDateTime");
} else {
cnd.orderBy("info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
// 单位列按实际显示名称排序,其他字段保持原有排序规则。
cnd.orderBy("unitName".equals(pageForm.getPageOrderName()) ? "unitName" : "info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("t.id");
sql.setCondition(cnd);
@@ -166,7 +168,7 @@ public class MemberChangeMineController {
@ApiOperation("根据id查询变更记录")
@SaCheckPermission(value = {"member.change.mine", "member.change.unionGroup.audit"}, mode = SaMode.OR)
public Result findMemberChangeRecord(String id){
MemberChangeRecord record = dao.fetch(MemberChangeRecord.class, id);
MemberChangeRecord record = memberCommonService.findChangeRecordWithUnitName(id);
NutMap nutMap = Lang.obj2nutmap(record);
// 变更记录只保存三级单位 ID,查看时补充当前单位名称供页面展示。
@@ -92,7 +92,7 @@ public class MemberSchoolUnionAuditController {
info.sex,
info.userName,
info.loginName,
info.unitName,
COALESCE(changeUnit.name, info.unitName) AS unitName,
info.unionName,
info.userState,
info.preparedBy,
@@ -119,6 +119,7 @@ public class MemberSchoolUnionAuditController {
wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN member_change_record info ON info.id = ins.businessNo
LEFT JOIN sys_unit changeUnit ON changeUnit.id = info.unitId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
@@ -139,7 +140,8 @@ public class MemberSchoolUnionAuditController {
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("info.applyDateTime");
} else {
cnd.orderBy("info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
// 单位列按实际显示名称排序,其他字段保持原有排序规则。
cnd.orderBy("unitName".equals(pageForm.getPageOrderName()) ? "unitName" : "info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("t.id");
sql.setCondition(cnd);
@@ -60,7 +60,7 @@ public class MemberUnionGroupAuditController {
info.sex,
info.userName,
info.loginName,
info.unitName,
COALESCE(changeUnit.name, info.unitName) AS unitName,
info.unionName,
info.userState,
info.preparedBy,
@@ -87,6 +87,7 @@ public class MemberUnionGroupAuditController {
wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN member_change_record info ON info.id = ins.businessNo
LEFT JOIN sys_unit changeUnit ON changeUnit.id = info.unitId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
@@ -107,7 +108,8 @@ public class MemberUnionGroupAuditController {
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("info.applyDateTime");
} else {
cnd.orderBy("info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
// 单位列按实际显示名称排序,其他字段保持原有排序规则。
cnd.orderBy("unitName".equals(pageForm.getPageOrderName()) ? "unitName" : "info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("t.id");
sql.setCondition(cnd);
@@ -7,6 +7,7 @@ import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.web.commons.base.Globals;
import com.budwk.app.zhgh.staffmanage.member.models.MemberApplyRecord;
import com.budwk.app.zhgh.staffmanage.member.service.MemberApplyRecordService;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
@@ -35,7 +36,8 @@ public class MemberApplySaveInterceptor implements FlowInterceptor {
MemberApplyRecord record = Json.fromJson(MemberApplyRecord.class, formDataStr);
Dao dao = ServiceContext.find(Dao.class);
dao.insertOrUpdate(record);
// WF 保存同样经过日期校验,防止绕过普通申请接口写入无效日期。
ServiceContext.find(MemberApplyRecordService.class).saveApplyRecord(record);
execution.getArgs().set(FlowConst.FORM_DATA, Json.toJson(record));
@@ -8,4 +8,10 @@ import com.budwk.app.zhgh.staffmanage.member.models.MemberApplyRecord;
*/
public interface MemberApplyRecordService extends BaseService<MemberApplyRecord> {
/**
* 校验入职时间并保存入会申请,统一供保存、提交、重新提交及 WF 保存入口调用。
* @param record 申请数据,arrivalAtSchoolDate 可为空,非空时必须为有效的 yyyy-MM-dd 日期
* @return 无返回值;新增时记录 ID 回填到 record,非空日期非法时抛出业务异常
*/
void saveApplyRecord(MemberApplyRecord record);
}
@@ -44,6 +44,13 @@ public interface MemberCommonService extends BaseService<Sys_user> {
Pagination memberHistoryRecords(MemberManagePageForm pageForm);
/**
* 读取变更申请并按申请单位 ID 校准显示名称,不写回历史记录。
* @param id 会员变更申请记录 ID
* @return 申请记录,unitName 对应本次申请的 unitId;单位不存在时保留原名称
*/
MemberChangeRecord findChangeRecordWithUnitName(String id);
/**
* 获取可变更字段
* @return
@@ -4,6 +4,13 @@ import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.staffmanage.member.models.MemberApplyRecord;
import com.budwk.app.zhgh.staffmanage.member.service.MemberApplyRecordService;
import org.nutz.dao.Dao;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import com.budwk.app.base.exception.BaseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
@@ -12,4 +19,37 @@ public class MemberApplyRecordServiceImpl extends BaseServiceImpl<MemberApplyRec
public MemberApplyRecordServiceImpl(Dao dao) {
super(dao);
}
/**
* 入职时间缺失时允许申请并存储为空;有值时拒绝 Invalid date 和不存在的日期。
* @param record 入会申请;arrivalAtSchoolDate 可为空或 yyyy-MM-dd,允许首尾空格
* @return 无返回值;不通过校验时不执行任何数据库写入
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void saveApplyRecord(MemberApplyRecord record) {
if (record == null) {
throw new BaseException("未获取到入会申请数据");
}
String value = record.getArrivalAtSchoolDate();
if (value == null || value.trim().isEmpty()) {
// 数据源未提供入职时间时保留为空,不阻止申请,也不填充虚构日期。
record.setArrivalAtSchoolDate(null);
} else {
value = value.trim();
if (!value.matches("[0-9]{4}-[0-9]{2}-[0-9]{2}")) {
throw new BaseException("入职时间格式无效,请先核实人员基础信息,日期格式应为yyyy-MM-dd");
}
try {
LocalDate date = LocalDate.parse(value,
DateTimeFormatter.ofPattern("uuuu-MM-dd").withResolverStyle(ResolverStyle.STRICT));
if (date.getYear() < 1) {
throw new BaseException("入职时间年份无效,请核实人员基础信息");
}
record.setArrivalAtSchoolDate(date.toString());
} catch (DateTimeParseException e) {
throw new BaseException("入职时间不是有效日期,请先核实人员基础信息");
}
}
dao().insertOrUpdate(record);
}
}
@@ -547,7 +547,10 @@ public class MemberInfoServiceImpl extends BaseServiceImpl<Sys_user> implements
// 获取系统用户
Cnd cnd = Cnd.NEW();
List<Sys_user> dataUserList = dao().query(Sys_user.class, cnd);
Map<String, Sys_user> userMap = dataUserList.stream().collect(Collectors.toMap(Sys_user::getLoginname, v -> v));
// 无工号人员保留原有资料和会员身份,仅不参与本次按工号匹配,避免多个空工号导致 Map 键冲突。
Map<String, Sys_user> userMap = dataUserList.stream()
.filter(user -> StrUtil.isNotBlank(user.getLoginname()))
.collect(Collectors.toMap(Sys_user::getLoginname, v -> v));
List<Sys_unit> unitList = dao().query(Sys_unit.class, Cnd.where("unitTypeCode", "=", "1").groupBy("name"));
@@ -159,20 +159,21 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
his.changeTypes,
his.changeOrigin,
his.changeReason,
his.remark,
gh.`name` AS unionName,
unit.`name` AS unitName,
COALESCE(unit.name, changeRecord.unitName) AS unitName,
u.id AS userId
FROM
`sys_user_history` his
LEFT JOIN sys_user u ON u.loginname = his.loginname
LEFT JOIN sys_unit unit ON unit.id = u.unitid
LEFT JOIN member_change_record changeRecord ON changeRecord.id = his.recordId
LEFT JOIN sys_unit unit ON unit.id = COALESCE(NULLIF(changeRecord.unitId, ''), his.unitId)
LEFT JOIN sys_union gh ON gh.id = unit.unionId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("gh.id", "=", pageForm.getUnionId());
cnd.andEX("his.unitId", "=", pageForm.getUnitId());
// 优先使用本次变更目标单位;无关联申请时使用历史记录自身单位,筛选与显示一致。
cnd.andEX("COALESCE(NULLIF(changeRecord.unitId, ''), his.unitId)", "=", pageForm.getUnitId());
cnd.andEX("his.personType", "=", pageForm.getPersonType());
cnd.andEX("his.userState", "=", pageForm.getUserState());
// 变更记录查询自行拼接历史表条件,需要单独补充人员属性多选及旧单值兼容。
@@ -251,6 +252,8 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
@Override
public List<NutMap> getChangeInfos(MemberChangeRecord record) {
// 普通提交、WF 保存拦截器和审核落库均先比较变更,在此统一校准申请单位名称。
normalizeChangeUnitName(record);
View_user info = dao().fetch(View_user.class, Cnd.where("id", "=", record.getUserId()));
NutMap map = getDictAllowChangeFields();
@@ -537,6 +540,36 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
}
/**
* 按申请 ID 读取详情并校准单位名称;仅修正返回对象,不改写历史记录。
* @param id 会员变更申请 ID
* @return 含一致 unitId、unitName 的申请记录;记录不存在时提示错误
*/
@Override
public MemberChangeRecord findChangeRecordWithUnitName(String id) {
MemberChangeRecord record = dao().fetch(MemberChangeRecord.class, id);
if (record == null) {
throw new BaseException("未找到会员变更申请记录");
}
normalizeChangeUnitName(record);
return record;
}
/**
* 名称以申请目标单位 ID 为准,避免前端残留原单位名称;不改变单位 ID 或所属工会。
* 单位已删除或未填写 ID 时保留原名称,避免用人员当前单位覆盖历史申请含义。
* @param record 待保存或待展示的申请对象,仅更新内存中的 unitName,不执行数据库更新
*/
private void normalizeChangeUnitName(MemberChangeRecord record) {
if (record == null || StrUtil.isBlank(record.getUnitId())) {
return;
}
Sys_unit unit = dao().fetch(Sys_unit.class, record.getUnitId());
if (unit != null && unit.getName() != null) {
record.setUnitName(unit.getName());
}
}
/**
* 统一规范变更记录中的到校日期,避免 Date 对象复制后以英文字符串形式参与比较或直接落库。
*/
@@ -96,10 +96,10 @@
#v4-home-app .act-card { padding: 0; border: 0; border-radius: 0; box-shadow: none; }
#v4-home-app .activity-swiper-container { padding: 0 0 4px; }
#v4-home-app .activity-swiper .swiper-slide .item { display: flex; flex-direction: column; height: 100%; }
/* 加高封面并统一裁剪容器,图片、加载态和占位图都保持相同圆角。 */
#v4-home-app .activity-swiper .swiper-slide .item .img-box { flex-shrink: 0; height: auto; aspect-ratio: 1.5; border-radius: 10px; overflow: hidden; clip-path: inset(0 round 10px); background: #1687f5; isolation: isolate; }
/* 保留横向卡片比例,海报等比完整居中显示,比例不一致时以浅色背景补齐空白。 */
#v4-home-app .activity-swiper .swiper-slide .item .img-box { flex-shrink: 0; height: auto; aspect-ratio: 1.5; border-radius: 10px; overflow: hidden; clip-path: inset(0 round 10px); background: #f0f5fb; isolation: isolate; }
#v4-home-app .img-box .el-image { display: block; width: 100%; height: 100%; position: absolute; inset: 0; border-radius: inherit; overflow: hidden; }
#v4-home-app .activity-swiper .swiper-slide .item .img-box img { height: 100% !important; max-height: none !important; object-fit: cover !important; border-radius: 10px; }
#v4-home-app .activity-swiper .swiper-slide .item .img-box img { height: 100% !important; max-height: none !important; object-fit: contain !important; object-position: center; border-radius: 10px; }
#v4-home-app .activity-swiper .swiper-slide .item .img-box .status { top: 8px; left: 8px; border-radius: 5px; padding: 4px 7px; background: #ed4859; }
#v4-home-app .activity-swiper .swiper-slide .item .img-box .status-ended { background: #8b97a7; }
#v4-home-app .activity-swiper .swiper-slide .item .img-box .status-upcoming { background: #1687f5; }
@@ -12,7 +12,7 @@ const act = {
<div class="swiper-slide" v-for="(activity, index) in actList" :key="activity.id">
<div class="item" role="button" tabindex="0" @click="go(activity)" @keydown.enter="go(activity)" @keydown.space.prevent="go(activity)">
<div class="img-box">
<el-image :src="activity.cover" fit="cover" :alt="activity.name">
<el-image :src="activity.cover" fit="contain" :alt="activity.name">
<div slot="error" class="portal-cover-empty"><i class="fa fa-picture-o" aria-hidden="true"></i><span>暂无图片</span></div>
</el-image>
<span class="status" :class="{'status-ended': getStatusText(activity) === '已结束', 'status-upcoming': getStatusText(activity) === '未开始'}">
@@ -356,12 +356,9 @@ const act = {
height: 100% !important;
max-width: 100% !important;
max-height: 128px !important;
object-fit: fill !important;
transition: transform 0.3s ease;
}
.activity-swiper .swiper-slide .item:hover .img-box img {
transform: scale(1.1);
/* 活动海报含有文字,等比完整显示且悬停时不放大,避免内容被裁切。 */
object-fit: contain !important;
object-position: center;
}
.activity-swiper .swiper-slide .item h4 {
@@ -5,7 +5,7 @@ const entry = {
<div class="portal-entry-tabs" role="tablist" aria-label="推荐入口">
<button v-for="section in entrySections" :key="section.key"
type="button" role="tab" :aria-selected="activeSection === section.key"
:class="{active: activeSection === section.key}" @click="$set(this, 'activeSection', section.key)">{{ section.title }}</button>
:class="{active: activeSection === section.key}" @click="switchSection(section.key)">{{ section.title }}</button>
</div>
<a class="portal-more" href="/platform/v4/apps">全部应用 <i class="fa fa-arrow-right" aria-hidden="true"></i></a>
</div>
@@ -60,6 +60,10 @@ const entry = {
this.getFavorite()
},
methods: {
// sectionKey 为 serv(推荐服务)、app(推荐应用)或 fav(我的收藏);通过组件方法更新栏目,避免内联事件中 this 指向错误。
switchSection(sectionKey) {
this.$set(this, 'activeSection', sectionKey)
},
// platform 固定传 PC;Result.data 为当前用户有权限的推荐服务数组。
getRecommendService() {
this.$axios.post('/platform/home/listRecommendService', {platform: "PC"}).then(res => {
@@ -118,7 +118,7 @@ layout("/layouts/v4/baseLayout.html"){
</style>
<link rel="stylesheet" href="${base!}/assets/platform/css/home-portal.css?v=20261028-2" />
<link rel="stylesheet" href="${base!}/assets/platform/css/home-portal.css?v=20261030-1" />
<!-- 登录资料仅在 HTML 属性中输出并转义;放在 Vue 挂载区域外,避免名称被当作模板表达式解析。 -->
<div id="v4-home-profile-data" hidden
@@ -155,14 +155,13 @@ layout("/layouts/platform.html"){
</el-button>
<el-button @click="$refs.sortRef.onOpen()" size="medium" type="primary" icon="el-icon-sort">排序
</el-button>
<el-button v-if="$auth.hasPermission('sys.manager.role.user')" size="medium" type="primary"
icon="el-icon-user" :loading="memberRoleLoading" @click="doSettingMemberRole">
一键设置会员角色
</el-button>
</div>
</el-row>
<!-- <div class="pull-right offscreen-right mt5 ml5">-->
<!-- <el-button size="medium" @click="doSettingMemberRole" type="primary"><i class="el-icon-s-tools"></i>-->
<!-- 一键设会员角色-->
<!-- </el-button>-->
<!-- </div>-->
<!-- <div class="pull-right offscreen-right mt5 ml5">-->
<!-- <el-button size="medium" @click="doPublic" type="primary">一键设置公共角色</el-button>-->
<!-- </div>-->
@@ -511,6 +510,7 @@ layout("/layouts/platform.html"){
}
}
return {
memberRoleLoading: false,
addDialogVisible: false,
editDialogVisible: false,
roleDialogVisible: false,
@@ -689,18 +689,30 @@ layout("/layouts/platform.html"){
})
},
doSettingMemberRole() {
this.$confirm("是否确定一键设置会员角色?", "提示", {
if (this.memberRoleLoading) {
return
}
// 操作面向全部会员,忽略当前列表筛选;确认期间也锁定按钮,避免重复提交。
this.$set(this, "memberRoleLoading", true)
this.$confirm("确定为全部已标记为会员的人员设置会员角色吗?已有角色将保留。", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(async () => {
const {code, msg} = await this.$axios.post("/platform/sys/role/doSettingMemberRole")
if (code === 0) {
this.$message.success(msg)
}
.then(() => {
return this.$axios.post("/platform/sys/role/doSettingMemberRole").then((res) => {
if (res.code === 0) {
this.$message.success("设置完成:新增 " + res.data.addedCount
+ " 条,恢复启用 " + res.data.enabledCount + " 条会员角色关联")
this.pageData()
}
})
})
.catch(() => {
// 取消确认无需提示;请求异常由全局 axios 拦截器统一提示。
})
.finally(() => {
this.$set(this, "memberRoleLoading", false)
})
},
formatAt(val) {
@@ -9,8 +9,8 @@ layout("/layouts/platform.html"){
<search-item label="年度">
<el-date-picker placeholder="请选择年度" style="width: 100%" type="year" v-model="pageForm.year" value-format="yyyy"></el-date-picker>
</search-item>
<search-item label="会议名称">
<el-input @keyup.enter.native="doSearch" clearable placeholder="请输入会议名称" v-model="pageForm.searchKeyword"></el-input>
<search-item label="资料名称">
<el-input @keyup.enter.native="doSearch" clearable placeholder="请输入资料名称" v-model="pageForm.searchKeyword"></el-input>
</search-item>
</search>
</el-card>
@@ -124,7 +124,7 @@ layout("/layouts/platform.html"){
</el-descriptions-item>
<el-descriptions-item label="入职时间">
<el-form-item prop="arrivalAtSchoolDate">
<el-input v-model="formData.arrivalAtSchoolDate" placeholder="请输入入职时间"
<el-input v-model="formData.arrivalAtSchoolDate" placeholder="暂无"
disabled maxlength="50"></el-input>
</el-form-item>
</el-descriptions-item>
@@ -256,7 +256,6 @@ layout("/layouts/platform.html"){
personalData: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
specialty: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
marriage: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
arrivalAtSchoolDate: [{ required: true, message: "必填", trigger: ["change", "blur"] }]
/*email: [
{
validator: (rule, value, callback) => {
@@ -321,6 +320,14 @@ layout("/layouts/platform.html"){
}
},
methods: {
// 缺失或非法日期回填为空,避免生成十二位 Invalid date;不使用当天日期补缺。
formatArrivalAtSchoolDate(value) {
if (value === null || value === undefined || String(value).trim() === '') {
return ''
}
const date = this.$moment(value, this.$moment.ISO_8601, true)
return date.isValid() ? date.format('YYYY-MM-DD') : ''
},
normalizeUnitField() {
if (!this.formData || !this.formData.unitId) {
return
@@ -413,6 +420,7 @@ layout("/layouts/platform.html"){
debugger
if (res.code === 0) {
this.formData = res.data
this.$set(this.formData, "arrivalAtSchoolDate", this.formatArrivalAtSchoolDate(res.data.arrivalAtSchoolDate))
}
} else {
const {
@@ -468,7 +476,7 @@ layout("/layouts/platform.html"){
this.$set(this.formData, "email", email)
this.$set(this.formData, "families", families ? families : [])
this.$set(this.formData, "personalData", personalData)
this.$set(this.formData, "arrivalAtSchoolDate", this.$moment(arrivalAtSchoolDate).format('YYYY-MM-DD'))
this.$set(this.formData, "arrivalAtSchoolDate", this.formatArrivalAtSchoolDate(arrivalAtSchoolDate))
}
}
},
@@ -23,8 +23,9 @@ const INFO = {
<el-descriptions-item label="工作单位">{{ viewData.unitName }}</el-descriptions-item>
<el-descriptions-item label="所属工会">{{ viewData.unionName }}</el-descriptions-item>
<el-descriptions-item v-if="showThreeUnit" label="三级单位">{{ viewData.threeUnitName || '暂无' }}</el-descriptions-item>
<el-descriptions-item v-else label="所属校区">{{ viewData.campus }}</el-descriptions-item>
<!-- 屏蔽三级单位展示,所属校区仍遵循原有显示条件。 -->
<el-descriptions-item v-if="false" label="三级单位">{{ viewData.threeUnitName || '暂无' }}</el-descriptions-item>
<el-descriptions-item v-if="!showThreeUnit" label="所属校区">{{ viewData.campus }}</el-descriptions-item>
<el-descriptions-item label="身份证号码">{{ viewData.idCard }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ viewData.mobile }}</el-descriptions-item>
@@ -74,7 +75,8 @@ const INFO = {
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="变更信息" name="changeInfo" v-if="viewData.changeInfos">
<el-table :data="viewData.changeInfos" :size="tableSize" @sort-change="pageOrder" ref="table"
<!-- 仅过滤展示数据,保留后台三级单位历史变更记录。 -->
<el-table :data="(viewData.changeInfos || []).filter(item => item.field !== 'threeUnitId' && item.field !== 'threeUnitName' && item.fieldName !== '三级单位')" :size="tableSize" @sort-change="pageOrder" ref="table"
row-key="id" style="width: 100%">
<el-table-column :index="indexMethod" align="center" header-align="center"
label="序号" type="index" width="80px"></el-table-column>
@@ -67,7 +67,8 @@ const MEMBER_ALL_CHANGE_INFO = {
:key="index" :timestamp="changeData.timestamp">
<el-card>
<h4>{{ changeData.changeOriginName }}</h4>
<el-table :data="changeData.changeInfos" size="mini" ref="table" row-key="id" style="width: 100%">
<!-- 仅过滤展示数据,保留后台三级单位历史变更记录。 -->
<el-table :data="(changeData.changeInfos || []).filter(item => item.field !== 'threeUnitId' && item.field !== 'threeUnitName' && item.fieldName !== '三级单位')" size="mini" ref="table" row-key="id" style="width: 100%">
<el-table-column align="center" header-align="center"
label="序号" type="index" width="80px"></el-table-column>
<el-table-column prop="fieldName" label="变更字段" header-align="center"></el-table-column>
@@ -86,6 +87,7 @@ const MEMBER_ALL_CHANGE_INFO = {
</el-timeline-item>
</el-timeline>
</el-tab-pane>
<slot></slot>
</el-tabs>
`,
store,
@@ -101,7 +101,8 @@ const MEMBER_CHANGE = {
</el-form-item>
</el-descriptions-item>
</template>
<el-descriptions-item v-if="showThreeUnit" label="三级单位">
<!-- 屏蔽三级单位编辑,保留回填值,避免提交其他变更时清空历史数据。 -->
<el-descriptions-item v-if="false" label="三级单位">
<el-form-item prop="threeUnitId">
<el-select v-model="formData.threeUnitId" clearable filterable
placeholder="请选择三级单位" style="width: 100%">
@@ -110,7 +111,7 @@ const MEMBER_CHANGE = {
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item v-else label="所属校区">
<el-descriptions-item v-if="!showThreeUnit" label="所属校区">
<el-form-item prop="campus">
<dict-select style="width: 100%" placeholder="请选择所属校区" v-model="formData.campus"
:disabled="allowFields('campus')" code="USER_CAMPUS"></dict-select>
@@ -215,8 +216,6 @@ const MEMBER_CHANGE = {
value-format="yyyy-MM-dd"></el-date-picker>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item></el-descriptions-item>
<el-descriptions-item></el-descriptions-item>
<template v-if="formData.loginname == $store.state.user.loginname">
<el-descriptions-item label="家庭主要成员" :span="3">
<el-form-item prop="families">
@@ -393,31 +392,6 @@ const MEMBER_CHANGE = {
allowFields(prop) {
return !this.allowChangeFields.map(v => v.code).includes(prop)
},
// 管理页仅查询当前被编辑人员所属二级单位的直属三级单位,避免越级选择其他单位数据。
initThreeUnits() {
this.threeUnits = []
if (!this.showThreeUnit) {
return Promise.resolve()
}
if (!this.formData.unitId) {
this.$message.warning("当前人员未配置所属单位,无法查询三级单位")
return Promise.resolve()
}
return this.$axios.post("/platform/sys/unit/child", {pid: this.formData.unitId})
.then((resp) => {
if (resp.code === 0) {
this.threeUnits = resp.data || []
if (this.threeUnits.length === 0) {
this.$message.warning("当前所属单位未配置三级单位")
}
} else {
this.$message.error(resp.msg || "三级单位查询失败")
}
})
.catch(() => {
this.$message.error("三级单位查询失败,请稍后重试")
})
},
doSave(){
this.$confirm("保存后可在我的申请里面再次编辑,您确定要保存吗?", "提示", { type: "warning" })
.then(() => {
@@ -461,7 +435,7 @@ const MEMBER_CHANGE = {
this.units = await this.$businessTool.listUnit()
}
this.normalizeUnitField()
this.initThreeUnits()
// 三级单位已屏蔽,不再加载选项或提示配置缺失;原值继续随表单回填。
},
async getUserById(userId){
const resp = await $.post("/platform/member/change/apply/getUserByIdForMemberChange", { userId })
@@ -1,4 +1,4 @@
<!--#
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
@@ -22,7 +22,8 @@ layout("/layouts/platform.html"){
v-for="item in units"></el-option>
</el-select>
</search-item>
<search-item label="三级单位">
<!-- 三级单位已屏蔽,保留字段结构便于维护。 -->
<search-item label="三级单位" v-if="false">
<el-select clearable filterable placeholder="请选择三级单位" style="width: 100%"
v-model="pageForm.threeUnitId">
<el-option :key="item.id" :label="item.name" :value="item.id"
@@ -603,6 +604,8 @@ layout("/layouts/platform.html"){
// 分页和导出共用查询参数,排除已屏蔽的人员属性条件。
buildQueryPageForm(source = this.pageForm) {
const pageForm = clone(source)
// 隐藏的三级单位不参与列表查询和导出筛选。
pageForm.threeUnitId = ""
// 人员属性已屏蔽,查询和导出均不再按该字段过滤。
pageForm.userAttribute = ""
pageForm.userAttributes = "[]"
@@ -700,24 +703,10 @@ layout("/layouts/platform.html"){
})
}
},
// 所属单位变化时只加载其直属三级单位,避免跨单位筛选。
flushThreeUnits(unitId) {
// 三级单位已屏蔽,切换所属单位时仅清空旧筛选条件,不再请求子单位。
flushThreeUnits() {
this.$set(this.pageForm, "threeUnitId", null)
this.threeUnits = []
if (!unitId) {
return
}
this.$axios.post("/platform/sys/unit/child", {pid: unitId})
.then((res) => {
if (res.code === 0) {
this.threeUnits = res.data || []
} else {
this.$message.error(res.msg || "三级单位查询失败")
}
})
.catch(() => {
this.$message.error("三级单位查询失败,请稍后重试")
})
},
checkAll() {
this.checkedFields = this.tableColumns.map(c => c.prop);
@@ -75,7 +75,7 @@ layout("/layouts/platform.html"){
{ prop: "unitName", label: "所属单位", sortable: true },
{ prop: "changeTypes", label: "变更类型", sortable: true, width: 200 },
{ prop: "applyDateTime", label: "申请时间", sortable: true },
{ prop: "curTaskName", label: "当前节点"},
{ prop: "taskName", label: "当前节点"},
{ prop: "instanceState", label: "流程状态"}
],
changeTypeData: [],
@@ -427,8 +427,10 @@
},
getUnionName(val){
const data = this.units.find(v => v.id === val)
this.$set(this.formData, 'unionName', data ? data.unionname : '')
// 切换目标单位时同步名称;ID 类型统一后匹配,避免提交原单位名称。
const data = this.units.find(v => String(v.id) === String(val))
this.$set(this.formData, 'unitName', data ? data.name : '')
this.$set(this.formData, 'unionName', data ? (data.unionName || data.unionname || '') : '')
},
allowFields(prop) {
return !this.allowChangeFields.map(v => v.code).includes(prop)
@@ -432,8 +432,10 @@ layout("/layouts/platform.html"){
// 获取表单工会名称
getUnionName(val){
const data = this.units.find(v => v.id === val)
this.$set(this.formData, 'unionName', data ? data.unionName : '')
// 切换目标单位时同步名称;ID 类型统一后匹配,避免提交原单位名称。
const data = this.units.find(v => String(v.id) === String(val))
this.$set(this.formData, 'unitName', data ? data.name : '')
this.$set(this.formData, 'unionName', data ? (data.unionName || data.unionname || '') : '')
},
// 获取表单可编辑的字段信息
allowFields(prop) {
@@ -14,7 +14,7 @@
<el-descriptions-item label="学位">{{ viewData.academicDegree }}</el-descriptions-item>
<el-descriptions-item label="党政职务">{{ viewData.position }}</el-descriptions-item>
<el-descriptions-item label="工作单位">{{ viewData.unitname }}</el-descriptions-item>
<el-descriptions-item label="工作单位">{{ viewData.unitName }}</el-descriptions-item>
<el-descriptions-item label="所属工会">{{ viewData.unionname }}</el-descriptions-item>
<el-descriptions-item label="所属校区">{{ viewData.campus }}</el-descriptions-item>
@@ -60,7 +60,8 @@
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="变更信息" name="changeInfo" v-if="viewData.changeInfos">
<el-table :data="viewData.changeInfos" :size="tableSize" @sort-change="pageOrder" ref="table" row-key="id" style="width: 100%">
<!-- 仅过滤展示数据,保留后台三级单位历史变更记录。 -->
<el-table :data="(viewData.changeInfos || []).filter(item => item.field !== 'threeUnitId' && item.field !== 'threeUnitName' && item.fieldName !== '三级单位')" :size="tableSize" @sort-change="pageOrder" ref="table" row-key="id" style="width: 100%">
<el-table-column :index="indexMethod" align="center" header-align="center"
label="序号" type="index" width="80px"></el-table-column>
<el-table-column prop="fieldName" label="变更字段" header-align="center"></el-table-column>
@@ -404,6 +404,14 @@ layout("/layouts/platform_h5.html"){
}
},
methods: {
// 缺失或非法日期回填为空,避免生成十二位 Invalid date;不使用当天日期补缺。
formatArrivalAtSchoolDate(value) {
if (value === null || value === undefined || String(value).trim() === '') {
return ''
}
const date = this.$moment(value, this.$moment.ISO_8601, true)
return date.isValid() ? date.format('YYYY-MM-DD') : ''
},
handlePageBack() {
if (window.h5HistoryLayerManager.stack.length) {
window.h5HistoryLayerManager.close()
@@ -592,6 +600,7 @@ layout("/layouts/platform_h5.html"){
.then((res) => {
if (res.code === 0) {
this.$set(this, "formData", res.data)
this.$set(this.formData, "arrivalAtSchoolDate", this.formatArrivalAtSchoolDate(res.data.arrivalAtSchoolDate))
}
})
}
@@ -649,7 +658,7 @@ layout("/layouts/platform_h5.html"){
this.$set(this.formData, "email", email)
this.$set(this.formData, "families", families ? families : [])
this.$set(this.formData, "personalData", personalData)
this.$set(this.formData, "arrivalAtSchoolDate", this.$moment(arrivalAtSchoolDate).format("YYYY-MM-DD"))
this.$set(this.formData, "arrivalAtSchoolDate", this.formatArrivalAtSchoolDate(arrivalAtSchoolDate))
// 处理校区信息
if (campus) {
@@ -133,6 +133,7 @@ const home = {
</div>
</div>
<!-- 暂停展示我的课堂,保留模板供后续恢复。
<div class="classroom-section">
<div class="classroom-section__head">
<div class="classroom-section__title">我的课堂</div>
@@ -192,6 +193,9 @@ const home = {
<div v-else-if="!classroomLoading" class="classroom-empty">暂无课程</div>
</div>
-->
<!-- 暂停展示政策文件,待接入内容后恢复。
<div class="policy-panel">
<div class="entry-panel__heading">
<span class="entry-panel__icon entry-panel__icon--policy"><van-icon name="description"></van-icon></span>
@@ -203,6 +207,8 @@ const home = {
</div>
</div>
-->
<div class="act-list" v-if="showLatestActivity">
<div class="section-title section-title--hidden">最新活动</div>
<template v-if="activityOptions && activityOptions.length > 0">
@@ -276,6 +282,7 @@ const home = {
className: "feature-card--model",
href: "/platform/honor/manage/h5"
},
/* 暂停展示普惠信息,保留入口配置供后续接入。
{
name: "普惠信息",
desc: "普惠服务信息",
@@ -284,6 +291,7 @@ const home = {
className: "feature-card--inclusive",
href: ""
}
*/
],
appLogo: "",
appName: "",
@@ -613,8 +621,9 @@ const home = {
created() {
this.listHomeBrand()
this.listRecommendApp()
this.listClassroomCourses()
this.listClassroomRecommendOptions()
// 我的课堂暂停展示,同步停用课程及推荐标签请求,恢复模块时再启用。
// this.listClassroomCourses()
// this.listClassroomRecommendOptions()
this.listSpreadHomeContent()
if (this.showLatestActivity) {
this.listActivity()
@@ -900,7 +909,8 @@ const home = {
margin: 0 12px 7px;
display: grid;
grid-template-columns: 1.08fr 1fr;
grid-template-rows: 86px 86px 92px;
/* 普惠入口暂停展示,取消其第三行占位,避免留下空白。 */
grid-template-rows: 86px 86px;
gap: 8px;
}