Merge branch 'main' of https://dd3skj.picp.vip/JyuHsin/zhgh_cug_v4
This commit is contained in:
@@ -211,6 +211,12 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@DataCenterColumn(name = "教职工类别码", key = "RYLX", dict = "USER_PERSON_TYPE")
|
||||
private String personType;
|
||||
|
||||
@Column
|
||||
@Comment("待确认状态:0不用确认、1待确认、2已确认")
|
||||
@ColDefine(type = ColType.INT, width = 1)
|
||||
@Default("0")
|
||||
private Integer pendingConfirmStatus;
|
||||
|
||||
@Column
|
||||
@Comment("编制类别码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
|
||||
+4
-4
@@ -163,17 +163,17 @@ public class MeetingManageController {
|
||||
cnd.andEX("u.unitId", "=", unitId);
|
||||
cnd.and(Cnd.likeEX("u.sex", sex));
|
||||
if (StrUtil.isNotBlank(roleId)) {
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s')".formatted(roleId)));
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s' and enable='1')".formatted(roleId)));
|
||||
}
|
||||
// 选择届次但未选择角色时,按届次筛选全部相关人员;同时选择角色时限定为同一角色关联记录。
|
||||
if (StrUtil.isBlank(roleId) && StrUtil.isNotBlank(teacherCongressSessionId)) {
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where tcSessionId = '%s')".formatted(teacherCongressSessionId)));
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where enable='1' and tcSessionId = '%s')".formatted(teacherCongressSessionId)));
|
||||
}
|
||||
if (StrUtil.isNotBlank(roleId) && StrUtil.isNotBlank(teacherCongressSessionId)) {
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s' and tcSessionId = '%s')".formatted(roleId, teacherCongressSessionId)));
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s' and enable='1' and tcSessionId = '%s')".formatted(roleId, teacherCongressSessionId)));
|
||||
}
|
||||
if (StrUtil.isNotBlank(roleId) && StrUtil.isNotBlank(workerCongressSessionId)) {
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s' and wcSessionId = '%s')".formatted(roleId, workerCongressSessionId)));
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s' and enable='1' and wcSessionId = '%s')".formatted(roleId, workerCongressSessionId)));
|
||||
}
|
||||
if (Strings.isNotBlank(pageForm.getSearchName()) && Strings.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
cnd.and(pageForm.getSearchName(), "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
|
||||
+4
-2
@@ -115,8 +115,10 @@ public class MeetingOnlineController {
|
||||
@At("/getRealTimeData/?")
|
||||
@Ok("json:full")
|
||||
@SaCheckLogin
|
||||
public Result getRealTimeData(String timePeriodId) {
|
||||
List<MeetingTimePeriodUser> signInUser = onlineService.getSignInUser(timePeriodId);
|
||||
public Result getRealTimeData(String timePeriodId,
|
||||
@Param(value = "signRankOrder") String signRankOrder) {
|
||||
// 签到人员排序统一交由Service处理,控制器只负责接收前端排序方向并返回实时数据。
|
||||
List<MeetingTimePeriodUser> signInUser = onlineService.getSignInUser(timePeriodId, signRankOrder);
|
||||
List<Map<String, Object>> countData = onlineService.getRealTimeData(timePeriodId);
|
||||
return Result.success(Map.of("countData", countData, "userData", signInUser));
|
||||
}
|
||||
|
||||
+10
-1
@@ -18,6 +18,15 @@ import java.util.Map;
|
||||
public interface MeetingOnlineService extends BaseService<MeetingInfo> {
|
||||
|
||||
List<MeetingTimePeriodUser> exportSignature(String timePeriodId);
|
||||
List<MeetingTimePeriodUser> getSignInUser(String timePeriodId);
|
||||
|
||||
/**
|
||||
* 查询指定会议时段的签到人员,并根据签到名次要求排序。
|
||||
*
|
||||
* @param timePeriodId 会议时段ID
|
||||
* @param signRankOrder 签到名次排序方向,ascending为升序,其他值按降序处理
|
||||
* @return 已签到人员列表
|
||||
*/
|
||||
List<MeetingTimePeriodUser> getSignInUser(String timePeriodId, String signRankOrder);
|
||||
|
||||
List<Map<String, Object>> getRealTimeData(String timePeriodId);
|
||||
}
|
||||
|
||||
+12
-8
@@ -70,13 +70,16 @@ public class MeetingOnlineServiceImpl extends BaseServiceImpl<MeetingInfo> imple
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MeetingTimePeriodUser> getSignInUser(String timePeriodId) {
|
||||
return dao().query(
|
||||
MeetingTimePeriodUser.class,
|
||||
Cnd.where(MeetingTimePeriodUser::getTimePeriodId, "=", timePeriodId)
|
||||
.and(MeetingTimePeriodUser::getSignStatus, "=", true)
|
||||
.desc("signTime")
|
||||
);
|
||||
public List<MeetingTimePeriodUser> getSignInUser(String timePeriodId, String signRankOrder) {
|
||||
Cnd cnd = Cnd.where(MeetingTimePeriodUser::getTimePeriodId, "=", timePeriodId)
|
||||
.and(MeetingTimePeriodUser::getSignStatus, "=", true);
|
||||
// 签到名次由签到时间确定,只识别固定方向参数,禁止将前端排序内容直接拼接到SQL中。
|
||||
if ("ascending".equals(signRankOrder)) {
|
||||
cnd.asc("signTime");
|
||||
} else {
|
||||
cnd.desc("signTime");
|
||||
}
|
||||
return dao().query(MeetingTimePeriodUser.class, cnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,7 +136,8 @@ public class MeetingOnlineServiceImpl extends BaseServiceImpl<MeetingInfo> imple
|
||||
Sys_role formalRole = roleService.getByCode(formalRoleCode);
|
||||
Sys_role attendanceRole = roleService.getByCode(attendanceRoleCode);
|
||||
|
||||
List<String> signedInUserIds = getSignInUser(timePeriodId).stream()
|
||||
// 代表签到统计只关心已签到人员集合,使用默认降序查询即可。
|
||||
List<String> signedInUserIds = getSignInUser(timePeriodId, "descending").stream()
|
||||
.map(MeetingTimePeriodUser::getUserId)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
|
||||
+1
-7
@@ -36,7 +36,6 @@ import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberManagePageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberCommonService;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.service.PendingConfirmUserService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -80,8 +79,6 @@ public class MemberChangeManageController {
|
||||
private CommonService commonService;
|
||||
@Inject
|
||||
private MemberCommonService memberCommonService;
|
||||
@Inject
|
||||
private PendingConfirmUserService pendingConfirmUserService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/member/change/manage/index.html")
|
||||
@@ -106,8 +103,7 @@ public class MemberChangeManageController {
|
||||
@SLog(tag = "会员高级管理-会员变更", msg = "分工会/校工会会员管理员提交变更")
|
||||
@SaCheckPermission(value = {
|
||||
"member.change.mange",
|
||||
"staff.member.change.mange",
|
||||
"staff.sourcechange.pending"
|
||||
"staff.member.change.mange"
|
||||
}, mode = SaMode.OR)
|
||||
public Result doSubmitChange(MemberChangeRecord record){
|
||||
// 检验是否有变更
|
||||
@@ -169,8 +165,6 @@ public class MemberChangeManageController {
|
||||
if (schoolRoleBool) {
|
||||
memberCommonService.compareChangeInfoAndUpdateMember(record);
|
||||
}
|
||||
// 待确认人员完成变更提交后转为已确认;普通人员调用时不会产生状态变化。
|
||||
pendingConfirmUserService.markConfirmed(record.getUserId());
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.budwk.app.zhgh.staffmanage.member.controller.statistics;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberPersonTypeStatisticsPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberStatisticsService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
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 org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 人员类型统计控制器。
|
||||
* 统计业务统一由 MemberStatisticsService 处理,控制器仅负责参数接收和结果输出。
|
||||
*/
|
||||
@IocBean
|
||||
@At("/platform/member/statistics/personType")
|
||||
@Ok("json:full")
|
||||
@Api(tags = "人员类型统计")
|
||||
public class MemberPersonTypeStatisticsController {
|
||||
|
||||
@Inject
|
||||
private MemberStatisticsService memberStatisticsService;
|
||||
|
||||
/** 进入人员类型统计页面。 */
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/member/statistics/personType/index.html")
|
||||
@SaCheckPermission("member.statistics.personType")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询按分工会或单位汇总的人员类型数据。
|
||||
*
|
||||
* @param pageForm 分页、统计维度及机构查询条件
|
||||
* @return 人员类型统计结果
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("查询人员类型统计")
|
||||
@SaCheckPermission("member.statistics.personType")
|
||||
public Result pageData(@Param("..") MemberPersonTypeStatisticsPageForm pageForm) {
|
||||
return Result.success(memberStatisticsService.getPersonTypeStatistics(pageForm));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出人员类型统计结果。
|
||||
*
|
||||
* @param pageForm 统计维度及机构查询条件
|
||||
* @param response HTTP 响应
|
||||
*/
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("导出人员类型统计")
|
||||
@SaCheckPermission("member.statistics.personType")
|
||||
public void doExport(@Param("..") MemberPersonTypeStatisticsPageForm pageForm, HttpServletResponse response) {
|
||||
try {
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader("Content-Disposition", "attachment;filename="
|
||||
+ new String("人员类型统计.xlsx".getBytes("UTF-8"), "ISO8859-1"));
|
||||
Workbook workbook = memberStatisticsService.exportPersonTypeStatistics(pageForm);
|
||||
workbook.write(response.getOutputStream());
|
||||
workbook.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("人员类型统计导出失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.budwk.app.zhgh.staffmanage.member.param.pageform;
|
||||
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 人员类型统计分页查询参数。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MemberPersonTypeStatisticsPageForm extends PageForm {
|
||||
|
||||
/** 统计维度:union 按分工会统计,unit 按单位统计。 */
|
||||
private String queryType;
|
||||
|
||||
/** 所属分工会ID。 */
|
||||
private String unionId;
|
||||
|
||||
/** 所属单位ID。 */
|
||||
private String unitId;
|
||||
}
|
||||
+17
@@ -3,6 +3,7 @@ package com.budwk.app.zhgh.staffmanage.member.service;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberStatisticsPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberPersonTypeStatisticsPageForm;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
@@ -96,4 +97,20 @@ public interface MemberStatisticsService extends BaseService<Sys_user> {
|
||||
* @return
|
||||
*/
|
||||
Workbook exportStatisticsExcel(List<NutMap> list, String queryType);
|
||||
|
||||
/**
|
||||
* 按分工会或单位统计各人员类型的会员数量。
|
||||
*
|
||||
* @param pageForm 分页、统计维度及机构查询条件
|
||||
* @return 统计数据、人员类型动态列及实际统计维度
|
||||
*/
|
||||
NutMap getPersonTypeStatistics(MemberPersonTypeStatisticsPageForm pageForm);
|
||||
|
||||
/**
|
||||
* 导出人员类型统计结果。
|
||||
*
|
||||
* @param pageForm 统计维度及机构查询条件
|
||||
* @return XSSF 格式工作簿
|
||||
*/
|
||||
Workbook exportPersonTypeStatistics(MemberPersonTypeStatisticsPageForm pageForm);
|
||||
}
|
||||
|
||||
+227
@@ -8,11 +8,15 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberStatisticsPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberPersonTypeStatisticsPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberStatisticsService;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -22,6 +26,7 @@ import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
@@ -41,6 +46,11 @@ import java.util.stream.Collectors;
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class MemberStatisticsServiceImpl extends BaseServiceImpl<Sys_user> implements MemberStatisticsService {
|
||||
|
||||
private static final String PERSON_TYPE_DICT_CODE = "USER_PERSON_TYPE";
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
public MemberStatisticsServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
@@ -574,4 +584,221 @@ public class MemberStatisticsServiceImpl extends BaseServiceImpl<Sys_user> imple
|
||||
exportParams.setHeight((short) 10);
|
||||
return ExcelExportUtil.exportExcel(exportParams, exportEntities, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 V4 人员视图 vw_user 动态统计 USER_PERSON_TYPE 字典中的各类会员人数。
|
||||
* 非校级管理员固定按当前所属工会下的单位统计,防止通过请求参数越权查询全校数据。
|
||||
*
|
||||
* @param pageForm 分页、统计维度及机构查询条件
|
||||
* @return 统计列表、动态列配置及实际查询维度
|
||||
*/
|
||||
@Override
|
||||
public NutMap getPersonTypeStatistics(MemberPersonTypeStatisticsPageForm pageForm) {
|
||||
boolean schoolAdmin = isPersonTypeStatisticsSchoolAdmin();
|
||||
String actualQueryType = schoolAdmin && "union".equals(pageForm.getQueryType()) ? "union" : "unit";
|
||||
List<Sys_dict> personTypes = sysDictService.getSubListByCode(PERSON_TYPE_DICT_CODE);
|
||||
String personTypeColumns = buildPersonTypeColumns(personTypes.size());
|
||||
|
||||
// 分页查询只返回当前页机构数据,避免机构数量增加后一次加载全部列表。
|
||||
Sql pageSql = createPersonTypeStatisticsSql(actualQueryType, personTypeColumns, schoolAdmin, pageForm);
|
||||
setPersonTypeParams(pageSql, personTypes);
|
||||
Sql countSql = createPersonTypeStatisticsCountSql(actualQueryType, schoolAdmin, pageForm);
|
||||
Pagination pagination = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), pageSql, countSql);
|
||||
List<NutMap> pageList = pagination.getList(NutMap.class);
|
||||
pageList.forEach(row -> row.setv("totalNum", calculatePersonTypeTotal(row, personTypes.size())));
|
||||
|
||||
// 合计重新查询当前权限和筛选范围内的全部机构,不受当前页码及每页条数影响。
|
||||
Sql allSql = createPersonTypeStatisticsSql(actualQueryType, personTypeColumns, schoolAdmin, pageForm);
|
||||
setPersonTypeParams(allSql, personTypes);
|
||||
List<NutMap> allList = listMap(allSql);
|
||||
allList.forEach(row -> row.setv("totalNum", calculatePersonTypeTotal(row, personTypes.size())));
|
||||
|
||||
return NutMap.NEW()
|
||||
.setv("pageData", pagination)
|
||||
.setv("summary", buildPersonTypeSummary(allList, personTypes.size()))
|
||||
.setv("personTypeOptions", buildPersonTypeOptions(personTypes))
|
||||
.setv("queryType", actualQueryType)
|
||||
.setv("schoolAdmin", schoolAdmin);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据人员类型统计结果生成动态列 Excel,表头与 USER_PERSON_TYPE 当前启用字典保持一致。
|
||||
*
|
||||
* @param pageForm 统计维度及机构查询条件
|
||||
* @return XSSF 格式工作簿
|
||||
*/
|
||||
@Override
|
||||
public Workbook exportPersonTypeStatistics(MemberPersonTypeStatisticsPageForm pageForm) {
|
||||
boolean schoolAdmin = isPersonTypeStatisticsSchoolAdmin();
|
||||
String actualQueryType = schoolAdmin && "union".equals(pageForm.getQueryType()) ? "union" : "unit";
|
||||
List<Sys_dict> personTypes = sysDictService.getSubListByCode(PERSON_TYPE_DICT_CODE);
|
||||
Sql sql = createPersonTypeStatisticsSql(
|
||||
actualQueryType, buildPersonTypeColumns(personTypes.size()), schoolAdmin, pageForm);
|
||||
setPersonTypeParams(sql, personTypes);
|
||||
List<NutMap> statisticsList = listMap(sql);
|
||||
statisticsList.forEach(row -> row.setv("totalNum", calculatePersonTypeTotal(row, personTypes.size())));
|
||||
List<NutMap> personTypeOptions = buildPersonTypeOptions(personTypes);
|
||||
|
||||
List<ExcelExportEntity> exportEntities = new ArrayList<>();
|
||||
if ("union".equals(actualQueryType)) {
|
||||
exportEntities.add(new ExcelExportEntity("分工会代码", "unionCode", 20));
|
||||
exportEntities.add(new ExcelExportEntity("分工会名称", "unionName", 25));
|
||||
} else {
|
||||
exportEntities.add(new ExcelExportEntity("单位代码", "unitCode", 20));
|
||||
exportEntities.add(new ExcelExportEntity("单位名称", "unitName", 25));
|
||||
if (schoolAdmin) {
|
||||
exportEntities.add(new ExcelExportEntity("所属分工会", "unionName", 25));
|
||||
}
|
||||
}
|
||||
exportEntities.add(new ExcelExportEntity("小计", "totalNum", 15));
|
||||
personTypeOptions.forEach(option -> exportEntities.add(
|
||||
new ExcelExportEntity(option.getString("name"), option.getString("prop"), 20)));
|
||||
|
||||
NutMap sumRow = buildPersonTypeSummary(statisticsList, personTypes.size());
|
||||
sumRow.setv("union".equals(actualQueryType) ? "unionName" : "unitName", "合计");
|
||||
statisticsList.add(sumRow);
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
exportParams.setHeight((short) 10);
|
||||
return ExcelExportUtil.exportExcel(exportParams, exportEntities, statisticsList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建人员类型统计 SQL。动态部分只包含后端生成的固定别名,字典值全部通过参数绑定传入。
|
||||
*/
|
||||
private Sql createPersonTypeStatisticsSql(String queryType, String personTypeColumns, boolean schoolAdmin,
|
||||
MemberPersonTypeStatisticsPageForm pageForm) {
|
||||
Sql sql;
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if ("union".equals(queryType)) {
|
||||
// 动态列必须先写入完整源 SQL,再由 Sqls.create 解析其中的 @personTypeN 参数。
|
||||
String sqlSource = """
|
||||
SELECT
|
||||
un.id AS unionId,
|
||||
un.unionCode,
|
||||
un.name AS unionName
|
||||
%s
|
||||
FROM sys_union un
|
||||
LEFT JOIN vw_user u ON u.unionId = un.id AND u.member = 1
|
||||
$condition
|
||||
""".formatted(personTypeColumns);
|
||||
sql = Sqls.create(sqlSource);
|
||||
cnd.andEX("un.id", "=", pageForm.getUnionId());
|
||||
cnd.groupBy("un.id", "un.unionCode", "un.name");
|
||||
cnd.asc("un.unionCode");
|
||||
} else {
|
||||
// 单位统计同样在创建 Sql 对象前完成动态列拼装,避免参数被当作 MySQL 会话变量。
|
||||
String sqlSource = """
|
||||
SELECT
|
||||
unit.id AS unitId,
|
||||
unit.unitcode AS unitCode,
|
||||
unit.name AS unitName,
|
||||
un.id AS unionId,
|
||||
un.unionCode,
|
||||
un.name AS unionName
|
||||
%s
|
||||
FROM sys_unit unit
|
||||
LEFT JOIN sys_union un ON un.id = unit.unionId
|
||||
LEFT JOIN vw_user u ON u.unitId = unit.id AND u.member = 1
|
||||
$condition
|
||||
""".formatted(personTypeColumns);
|
||||
sql = Sqls.create(sqlSource);
|
||||
cnd.and("unit.unitLevel", "=", 2);
|
||||
if (!schoolAdmin) {
|
||||
cnd.and("unit.unionId", "=", SecurityUtil.getUnionId());
|
||||
} else {
|
||||
cnd.andEX("unit.unionId", "=", pageForm.getUnionId());
|
||||
}
|
||||
cnd.andEX("unit.id", "=", pageForm.getUnitId());
|
||||
cnd.groupBy("unit.id", "unit.unitcode", "unit.name", "un.id", "un.unionCode", "un.name");
|
||||
cnd.asc("unit.unitcode");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建机构分页总数 SQL。分组统计 SQL 不直接用于计数,避免 GROUP BY 导致分页总数取值不准确。
|
||||
*/
|
||||
private Sql createPersonTypeStatisticsCountSql(String queryType, boolean schoolAdmin,
|
||||
MemberPersonTypeStatisticsPageForm pageForm) {
|
||||
Sql countSql;
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if ("union".equals(queryType)) {
|
||||
countSql = Sqls.create("SELECT COUNT(1) FROM sys_union un $condition");
|
||||
cnd.andEX("un.id", "=", pageForm.getUnionId());
|
||||
} else {
|
||||
countSql = Sqls.create("SELECT COUNT(1) FROM sys_unit unit $condition");
|
||||
cnd.and("unit.unitLevel", "=", 2);
|
||||
if (!schoolAdmin) {
|
||||
cnd.and("unit.unionId", "=", SecurityUtil.getUnionId());
|
||||
} else {
|
||||
cnd.andEX("unit.unionId", "=", pageForm.getUnionId());
|
||||
}
|
||||
cnd.andEX("unit.id", "=", pageForm.getUnitId());
|
||||
}
|
||||
countSql.setCondition(cnd);
|
||||
return countSql;
|
||||
}
|
||||
|
||||
/** 构建人员类型动态统计列,参数名称在 Sqls.create 前写入完整 SQL。 */
|
||||
private String buildPersonTypeColumns(int personTypeCount) {
|
||||
StringBuilder columns = new StringBuilder();
|
||||
for (int i = 0; i < personTypeCount; i++) {
|
||||
columns.append(", SUM(CASE WHEN u.personType = @personType")
|
||||
.append(i)
|
||||
.append(" THEN 1 ELSE 0 END) AS personType")
|
||||
.append(i);
|
||||
}
|
||||
return columns.toString();
|
||||
}
|
||||
|
||||
/** 将 USER_PERSON_TYPE 字典编码填充到已经完成参数解析的 SQL 对象。 */
|
||||
private void setPersonTypeParams(Sql sql, List<Sys_dict> personTypes) {
|
||||
for (int i = 0; i < personTypes.size(); i++) {
|
||||
sql.setParam("personType" + i, personTypes.get(i).getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/** 将人员类型字典转换为前端动态列定义。 */
|
||||
private List<NutMap> buildPersonTypeOptions(List<Sys_dict> personTypes) {
|
||||
List<NutMap> options = new ArrayList<>();
|
||||
for (int i = 0; i < personTypes.size(); i++) {
|
||||
Sys_dict personType = personTypes.get(i);
|
||||
options.add(NutMap.NEW()
|
||||
.setv("code", personType.getCode())
|
||||
.setv("name", personType.getName())
|
||||
.setv("prop", "personType" + i));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/** 计算当前权限及筛选范围内全部机构的人员类型合计。 */
|
||||
private NutMap buildPersonTypeSummary(List<NutMap> statisticsList, int personTypeCount) {
|
||||
NutMap summary = NutMap.NEW();
|
||||
summary.setv("totalNum", statisticsList.stream().mapToInt(row -> row.getInt("totalNum", 0)).sum());
|
||||
for (int i = 0; i < personTypeCount; i++) {
|
||||
String prop = "personType" + i;
|
||||
summary.setv(prop, statisticsList.stream().mapToInt(row -> row.getInt(prop, 0)).sum());
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
/** 计算一行中所有人员类型数量的小计。 */
|
||||
private int calculatePersonTypeTotal(NutMap row, int personTypeCount) {
|
||||
int total = 0;
|
||||
for (int i = 0; i < personTypeCount; i++) {
|
||||
total += row.getInt("personType" + i, 0);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/** 判断当前用户是否具备查看全校人员类型统计的校级角色。 */
|
||||
private boolean isPersonTypeStatisticsSchoolAdmin() {
|
||||
return AuthUtil.hasRoleOr(
|
||||
RoleConstant.SYSADMIN.name(),
|
||||
RoleConstant.SCHOOL_UNION_ADMIN.name(),
|
||||
RoleConstant.SCHOOL_UNION_MEMBER_ADMIN.name());
|
||||
}
|
||||
}
|
||||
|
||||
+38
@@ -2,6 +2,10 @@ package com.budwk.app.zhgh.staffmanage.sourcechange.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.annotation.RepeatSubmit;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserBatchChangeParam;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.service.PendingConfirmUserService;
|
||||
import io.swagger.annotations.Api;
|
||||
@@ -44,4 +48,38 @@ public class PendingConfirmUserController {
|
||||
public Result pageData(PendingConfirmUserPageForm pageForm) {
|
||||
return Result.success(pendingConfirmUserService.pageData(pageForm));
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接提交待确认人员变更,不创建会员变更申请及审批流程。
|
||||
*
|
||||
* @param record 待确认人员变更内容
|
||||
* @return 提交结果
|
||||
*/
|
||||
@At
|
||||
@Ok("json")
|
||||
@RepeatSubmit
|
||||
@SLog(tag = "待确认人员记录", msg = "直接提交人员变更")
|
||||
@ApiOperation("直接提交待确认人员变更")
|
||||
@SaCheckPermission("staff.sourcechange.pending")
|
||||
public Result submitChange(MemberChangeRecord record) {
|
||||
boolean changed = pendingConfirmUserService.submitChange(record);
|
||||
return Result.success(changed ? "变更成功" : "确认成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改勾选人员的人员类型并完成确认。
|
||||
*
|
||||
* @param changeParam 用户ID集合及目标人员类型
|
||||
* @return 批量处理结果
|
||||
*/
|
||||
@At
|
||||
@Ok("json")
|
||||
@RepeatSubmit
|
||||
@SLog(tag = "待确认人员记录", msg = "批量变更人员类型")
|
||||
@ApiOperation("批量变更待确认人员类型")
|
||||
@SaCheckPermission("staff.sourcechange.pending")
|
||||
public Result batchChange(PendingConfirmUserBatchChangeParam changeParam) {
|
||||
int changedCount = pendingConfirmUserService.batchChangePersonType(changeParam);
|
||||
return Result.success("已成功变更" + changedCount + "人");
|
||||
}
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.budwk.app.zhgh.staffmanage.sourcechange.param;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 待确认人员批量变更参数。
|
||||
*/
|
||||
@Data
|
||||
public class PendingConfirmUserBatchChangeParam {
|
||||
|
||||
/**
|
||||
* 前端勾选的用户ID JSON数组。
|
||||
*/
|
||||
private String userIds;
|
||||
|
||||
/**
|
||||
* 批量设置的人员类型。
|
||||
*/
|
||||
private String personType;
|
||||
}
|
||||
+18
@@ -3,6 +3,8 @@ package com.budwk.app.zhgh.staffmanage.sourcechange.service;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserBatchChangeParam;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserPageForm;
|
||||
|
||||
/**
|
||||
@@ -18,6 +20,22 @@ public interface PendingConfirmUserService extends BaseService<Sys_user> {
|
||||
*/
|
||||
Pagination pageData(PendingConfirmUserPageForm pageForm);
|
||||
|
||||
/**
|
||||
* 直接提交待确认人员变更,不生成会员变更申请及流程数据。
|
||||
*
|
||||
* @param record 待确认人员变更内容
|
||||
* @return true 表示人员字段发生变化,false 表示仅完成确认
|
||||
*/
|
||||
boolean submitChange(MemberChangeRecord record);
|
||||
|
||||
/**
|
||||
* 批量修改待确认人员的人员类型,并将处理成功的人员标记为已确认。
|
||||
*
|
||||
* @param changeParam 批量勾选的用户ID及目标人员类型
|
||||
* @return 实际完成确认的人员数量
|
||||
*/
|
||||
int batchChangePersonType(PendingConfirmUserBatchChangeParam changeParam);
|
||||
|
||||
/**
|
||||
* 人员变更提交成功后,将待确认状态更新为已确认。
|
||||
*
|
||||
|
||||
+149
@@ -1,10 +1,21 @@
|
||||
package com.budwk.app.zhgh.staffmanage.sourcechange.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeOrigin;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberCommonService;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserBatchChangeParam;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.service.PendingConfirmUserService;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -13,8 +24,15 @@ import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 待确认人员记录业务实现。
|
||||
@@ -24,6 +42,14 @@ public class PendingConfirmUserServiceImpl extends BaseServiceImpl<Sys_user> imp
|
||||
|
||||
private static final int STATUS_PENDING = 1;
|
||||
private static final int STATUS_CONFIRMED = 2;
|
||||
/** 待确认人员只允许转为以下两个字典名称对应的编码。 */
|
||||
private static final Set<String> ALLOWED_PERSON_TYPE_NAMES = Set.of("离退休人员", "其他");
|
||||
|
||||
@Inject
|
||||
private MemberCommonService memberCommonService;
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
public PendingConfirmUserServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
@@ -81,6 +107,129 @@ public class PendingConfirmUserServiceImpl extends BaseServiceImpl<Sys_user> imp
|
||||
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接更新待确认人员及历史记录,不写会员变更记录,也不创建任何流程实例或任务。
|
||||
*
|
||||
* @param record 待确认人员变更内容
|
||||
* @return true 表示人员字段发生变化,false 表示仅完成确认
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public boolean submitChange(MemberChangeRecord record) {
|
||||
if (record == null || StrUtil.isBlank(record.getUserId())) {
|
||||
throw new BaseException("未获取到待确认人员数据");
|
||||
}
|
||||
if (StrUtil.isBlank(record.getPersonType())) {
|
||||
throw new BaseException("请选择人员类型");
|
||||
}
|
||||
validatePersonType(record.getPersonType());
|
||||
// 单人页面虽然提交完整表单,服务层只采用用户ID和人员类型,防止请求篡改其他只读字段。
|
||||
return changePersonType(record.getUserId(), record.getPersonType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 在同一事务内批量处理选中人员;任意人员状态异常时整体回滚,避免出现部分成功。
|
||||
*
|
||||
* @param changeParam 批量用户ID及目标人员类型
|
||||
* @return 完成确认的人员数量
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public int batchChangePersonType(PendingConfirmUserBatchChangeParam changeParam) {
|
||||
if (changeParam == null || StrUtil.isBlank(changeParam.getUserIds())) {
|
||||
throw new BaseException("请选择需要变更的人员");
|
||||
}
|
||||
if (StrUtil.isBlank(changeParam.getPersonType())) {
|
||||
throw new BaseException("请选择人员类型");
|
||||
}
|
||||
validatePersonType(changeParam.getPersonType());
|
||||
|
||||
List<String> userIds;
|
||||
try {
|
||||
userIds = JSONUtil.parseArray(changeParam.getUserIds()).toList(String.class);
|
||||
} catch (Exception e) {
|
||||
throw new BaseException("批量变更人员参数格式不正确");
|
||||
}
|
||||
// 去除空ID和重复ID,防止同一人员在一次请求内被重复处理。
|
||||
LinkedHashSet<String> normalizedUserIds = new LinkedHashSet<>();
|
||||
for (String userId : userIds) {
|
||||
if (StrUtil.isNotBlank(userId)) {
|
||||
normalizedUserIds.add(userId);
|
||||
}
|
||||
}
|
||||
if (normalizedUserIds.isEmpty()) {
|
||||
throw new BaseException("请选择需要变更的人员");
|
||||
}
|
||||
|
||||
for (String userId : new ArrayList<>(normalizedUserIds)) {
|
||||
changePersonType(userId, changeParam.getPersonType());
|
||||
}
|
||||
return normalizedUserIds.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅修改指定待确认人员的人员类型,并复用会员公共服务生成用户历史记录。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param personType 目标人员类型
|
||||
* @return true 表示人员类型发生变化,false 表示类型未变化但已完成确认
|
||||
*/
|
||||
private boolean changePersonType(String userId, String personType) {
|
||||
Sys_user user = dao().fetch(Sys_user.class, userId);
|
||||
if (user == null || !Integer.valueOf(STATUS_PENDING).equals(user.getPendingConfirmStatus())) {
|
||||
throw new BaseException("当前人员不属于待确认人员记录");
|
||||
}
|
||||
|
||||
// View_user 是数据库视图且未声明 @Name,必须使用条件查询,不能调用主键值重载。
|
||||
View_user userView = dao().fetch(View_user.class, Cnd.where("id", "=", userId));
|
||||
if (userView == null) {
|
||||
throw new BaseException("未获取到人员基础信息");
|
||||
}
|
||||
// 从数据库当前值重建完整变更对象,除人员类型外不信任前端提交的任何人员字段。
|
||||
MemberChangeRecord safeRecord = new MemberChangeRecord();
|
||||
BeanUtil.copyProperties(userView, safeRecord);
|
||||
safeRecord.setUserId(userId);
|
||||
safeRecord.setPersonType(StrUtil.trim(personType));
|
||||
safeRecord.setMember(Integer.valueOf(1).equals(userView.getMember()));
|
||||
safeRecord.setWelfareMember(Integer.valueOf(1).equals(userView.getWelfareMember()));
|
||||
safeRecord.setArrivalAtSchoolDate(userView.getArrivalAtSchoolDate() == null
|
||||
? null : DateUtil.formatDate(userView.getArrivalAtSchoolDate()));
|
||||
safeRecord.setWelfareStopDate(userView.getWelfareStopDate() == null
|
||||
? null : DateUtil.formatDate(userView.getWelfareStopDate()));
|
||||
|
||||
try {
|
||||
// 复用公共校验和历史记录生成口径,确保单人、批量变更结果保持一致。
|
||||
memberCommonService.validateChangeAndSetBasicData(safeRecord, MemberChangeOrigin.SYSTEM);
|
||||
} catch (BaseException e) {
|
||||
// 人员类型与当前值相同时允许直接完成确认;其他业务异常必须继续抛出并触发事务回滚。
|
||||
if ("未获取到异动数据".equals(e.getMessage())) {
|
||||
markConfirmed(userId);
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
memberCommonService.compareChangeInfoAndUpdateMember(safeRecord);
|
||||
markConfirmed(userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验待确认人员允许变更的目标人员类型。
|
||||
*
|
||||
* @param personType 目标人员类型
|
||||
*/
|
||||
private void validatePersonType(String personType) {
|
||||
String targetPersonType = StrUtil.trim(personType);
|
||||
List<Sys_dict> personTypeDicts = sysDictService.getSubListByCode("USER_PERSON_TYPE");
|
||||
boolean allowed = personTypeDicts != null && personTypeDicts.stream().anyMatch(dict ->
|
||||
ALLOWED_PERSON_TYPE_NAMES.contains(dict.getName())
|
||||
&& StrUtil.equals(targetPersonType, dict.getCode()));
|
||||
if (!allowed) {
|
||||
throw new BaseException("人员类型只能选择离退休人员或其他");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 变更提交成功后仅把待确认人员更新为已确认,重复调用不会改变其他状态。
|
||||
*
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 会务管理列表页统一满屏布局:查询区和分页区固定,表格使用剩余高度并在内部滚动。
|
||||
*/
|
||||
.meeting-list-layout {
|
||||
height: calc(100vh - 84px);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.meeting-list-layout > .transition-item {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.meeting-list-layout > .transition-item > .meeting-search-card {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.meeting-list-layout > .transition-item > .meeting-table-card {
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
margin-top: 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.meeting-list-layout > .transition-item > .meeting-table-card > .el-card__body {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.meeting-list-layout .meeting-table-card .ele-table-tool {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.meeting-list-layout .meeting-table-card .meeting-main-table {
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.meeting-list-layout .meeting-table-card .el-pagination-container {
|
||||
flex: 0 0 52px;
|
||||
min-height: 52px;
|
||||
margin-top: 12px !important;
|
||||
margin-bottom: 0 !important;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.meeting-list-layout .meeting-main-table .el-table__body-wrapper {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@media (max-height: 760px) {
|
||||
.meeting-list-layout > .transition-item {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.meeting-list-layout > .transition-item > .meeting-table-card > .el-card__body {
|
||||
padding-top: 14px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.meeting-list-layout .meeting-table-card .el-pagination-container {
|
||||
flex-basis: 48px;
|
||||
min-height: 48px;
|
||||
margin-top: 8px !important;
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -2,10 +2,12 @@
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<guava ref="guava">
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/meeting-list-layout.css">
|
||||
|
||||
<el-card shadow="never">
|
||||
<div id="app">
|
||||
<guava ref="guava" class="meeting-list-layout">
|
||||
|
||||
<el-card shadow="never" class="meeting-search-card">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
@@ -35,14 +37,15 @@ layout("/layouts/platform.html"){
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-card shadow="never" class="meeting-table-card">
|
||||
<table-tool label="申请列表">
|
||||
<el-radio-group v-model="pageForm.approval" size="small" @change="doSearch">
|
||||
<el-radio-button :label="true">已审核</el-radio-button>
|
||||
<el-radio-button :label="false">未审核</el-radio-button>
|
||||
</el-radio-group>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table class="meeting-main-table" height="100%" v-loading="tableLoading"
|
||||
:data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/meeting-list-layout.css">
|
||||
<div id="app">
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<guava ref="guava" class="meeting-list-layout">
|
||||
<el-card shadow="never" class="meeting-search-card">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
@@ -33,9 +35,10 @@ layout("/layouts/platform.html"){
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-card shadow="never" class="meeting-table-card">
|
||||
<table-tool label="申请列表"></table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table class="meeting-main-table" height="100%" v-loading="tableLoading"
|
||||
:data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
|
||||
@@ -246,7 +246,7 @@ const basicForm = {
|
||||
// 民主管理模块才需要按教代会届次筛选人员。
|
||||
isDemocraticModule() {
|
||||
const module = this.moduleOptions.find((item) => item.id === this.pageForm.moduleId)
|
||||
return module && module.name === '民主管理'
|
||||
return module && module.name === '提案工作'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/meeting-list-layout.css">
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<guava ref="guava" class="meeting-list-layout">
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<el-card shadow="never" class="meeting-search-card">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
@@ -30,14 +32,15 @@ layout("/layouts/platform.html"){
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt20">
|
||||
<el-card shadow="never" class="meeting-table-card">
|
||||
<table-tool label="会议列表">
|
||||
<el-button type="primary" size="small" @click="onAdd">
|
||||
<i class="ti-plus"></i>
|
||||
新增会议
|
||||
</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize">
|
||||
<el-table class="meeting-main-table" height="100%" v-loading="tableLoading"
|
||||
:data="tableData" @sort-change="pageOrder" :size="tableSize">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/meeting-list-layout.css">
|
||||
|
||||
<style>
|
||||
.timePeriod_card {
|
||||
border-top: 5px solid #106898;
|
||||
@@ -15,9 +17,9 @@ layout("/layouts/platform.html"){
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<guava ref="guava" class="meeting-list-layout">
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<el-card shadow="never" class="meeting-search-card">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
@@ -47,9 +49,10 @@ layout("/layouts/platform.html"){
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt20">
|
||||
<el-card shadow="never" class="meeting-table-card">
|
||||
<table-tool label="会议列表"></table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize" ref="meetingTable">
|
||||
<el-table class="meeting-main-table" height="100%" v-loading="tableLoading"
|
||||
:data="tableData" @sort-change="pageOrder" :size="tableSize" ref="meetingTable">
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="{ row }">
|
||||
<el-row :gutter="20" justify="start" style="flex-wrap: wrap" type="flex"
|
||||
@@ -62,9 +65,12 @@ layout("/layouts/platform.html"){
|
||||
<el-form-item label="结束时间">{{tp.endTime}}</el-form-item>
|
||||
</el-form>
|
||||
<el-row justify="space-around" style="border-top: 1px solid #ebeef5;padding: 10px 0 0 0" type="flex">
|
||||
<el-button v-if="tp.joinStatus === true" @click="onLeave(tp)" size="medium" type="text">请假</el-button>
|
||||
<el-button v-if="tp.joinStatus === true" :disabled="isMeetingHandled(tp)"
|
||||
@click="onLeave(tp)" size="medium" type="text">请假</el-button>
|
||||
<el-button v-if="tp.joinStatus === false" size="medium" type="text" disabled>您已请假</el-button>
|
||||
<el-button v-if="tp.signStatus === false" :disabled="isSignDisabled(tp.startTime)" @click="onSign(tp)" size="medium" type="text">签到</el-button>
|
||||
<el-button v-if="tp.signStatus === false"
|
||||
:disabled="isMeetingHandled(tp) || isSignDisabled(tp.startTime)"
|
||||
@click="onSign(tp)" size="medium" type="text">签到</el-button>
|
||||
<el-button v-if="tp.signStatus === true" size="medium" type="text" disabled>您已签到</el-button>
|
||||
</el-row>
|
||||
</el-card>
|
||||
@@ -127,11 +133,19 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 已请假或已签到后同时禁用请假、签到按钮,避免继续发起互斥操作。
|
||||
isMeetingHandled(timePeriod) {
|
||||
return timePeriod.joinStatus === false || timePeriod.signStatus === true
|
||||
},
|
||||
// 场次开始前禁用签到,避免用户在会议开始前提交签到操作。
|
||||
isSignDisabled(startTime) {
|
||||
return this.$moment().isBefore(this.$moment(startTime))
|
||||
},
|
||||
onLeave(row) {
|
||||
if (this.isMeetingHandled(row)) {
|
||||
this.$message.warning(row.signStatus === true ? '您已签到,不能请假' : '您已请假,请勿重复操作')
|
||||
return
|
||||
}
|
||||
if(!row.canLeave) {
|
||||
this.$message.warning('该场次不能请假')
|
||||
return
|
||||
@@ -159,6 +173,10 @@ layout("/layouts/platform.html"){
|
||||
}).catch(() => {})
|
||||
},
|
||||
onSign(row) {
|
||||
if (this.isMeetingHandled(row)) {
|
||||
this.$message.warning(row.signStatus === true ? '您已签到,请勿重复操作' : '您已请假,不能签到')
|
||||
return
|
||||
}
|
||||
this.$confirm('您确定要签到吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/meeting-list-layout.css">
|
||||
|
||||
<style>
|
||||
.qrcode {
|
||||
min-width: 240px;
|
||||
@@ -70,9 +72,9 @@ layout("/layouts/platform.html"){
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<guava ref="guava" class="meeting-list-layout">
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<el-card shadow="never" class="meeting-search-card">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
@@ -102,9 +104,10 @@ layout("/layouts/platform.html"){
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt20">
|
||||
<el-card shadow="never" class="meeting-table-card">
|
||||
<table-tool label="会议列表"></table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize" ref="meetingTable">
|
||||
<el-table class="meeting-main-table" height="100%" v-loading="tableLoading"
|
||||
:data="tableData" @sort-change="pageOrder" :size="tableSize" ref="meetingTable">
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="{ row }">
|
||||
<el-row :gutter="20" justify="start" style="flex-wrap: wrap" type="flex"
|
||||
@@ -179,15 +182,20 @@ layout("/layouts/platform.html"){
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table :data="userData" :max-height="signTableHeight" class="mt20" ref="signTable">
|
||||
<el-table :data="userData"
|
||||
:max-height="signTableHeight"
|
||||
:default-sort="{prop: 'signRank', order: 'descending'}"
|
||||
@sort-change="changeSignRankOrder"
|
||||
class="mt20"
|
||||
ref="signTable">
|
||||
<el-table-column label="姓名" prop="userName"></el-table-column>
|
||||
<el-table-column label="工号" prop="loginName"></el-table-column>
|
||||
<el-table-column label="性别" prop="sex"></el-table-column>
|
||||
<el-table-column label="单位" prop="unitName"></el-table-column>
|
||||
<el-table-column label="签到时间" prop="signTime"></el-table-column>
|
||||
<el-table-column label="签到名次">
|
||||
<el-table-column label="签到名次" prop="signRank" sortable="custom">
|
||||
<template slot-scope="scope">
|
||||
{{userData.length - scope.$index}}
|
||||
{{scope.row.signRank}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -224,6 +232,7 @@ layout("/layouts/platform.html"){
|
||||
lastTimeData: [],
|
||||
realTimeData: [],
|
||||
userData: [],
|
||||
signRankOrder: 'descending',
|
||||
signTableHeight: 600
|
||||
}
|
||||
},
|
||||
@@ -261,22 +270,39 @@ layout("/layouts/platform.html"){
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
},
|
||||
async getRealTimeData() {
|
||||
this.lastTimeData = clone(this.realTimeData)
|
||||
const resp = await this.$axios.post('/platform/meeting/online/getRealTimeData/' + this.timePeriodId)
|
||||
if (resp.code === 0) {
|
||||
this.lastTimeData = clone(this.realTimeData)
|
||||
this.$set(this, 'realTimeData', resp.data.countData)
|
||||
let ud = clone(this.userData)
|
||||
ud.unshift(...resp.data.userData)
|
||||
let uMap = new Map()
|
||||
for (let u of ud) {
|
||||
if (!uMap.has(u.loginName)) {
|
||||
uMap.set(u.loginName, u)
|
||||
}
|
||||
}
|
||||
this.$set(this, 'userData', [...uMap.values()])
|
||||
getRealTimeData() {
|
||||
// 表格初始化可能触发默认排序事件,尚未选择会议时段时不调用实时数据接口。
|
||||
if (!this.timePeriodId) {
|
||||
return
|
||||
}
|
||||
this.lastTimeData = clone(this.realTimeData)
|
||||
const requestOrder = this.signRankOrder
|
||||
this.$axios.post('/platform/meeting/online/getRealTimeData/' + this.timePeriodId, {
|
||||
signRankOrder: requestOrder
|
||||
}).then((resp) => {
|
||||
// 排序切换过程中可能存在尚未返回的轮询请求,旧排序结果不再覆盖当前列表。
|
||||
if (requestOrder !== this.signRankOrder) {
|
||||
return
|
||||
}
|
||||
if (resp.code === 0) {
|
||||
this.lastTimeData = clone(this.realTimeData)
|
||||
this.$set(this, 'realTimeData', resp.data.countData)
|
||||
const rankedUsers = resp.data.userData || []
|
||||
// 后台已按签到时间排序,此处只补充对应的固定签到名次用于页面展示。
|
||||
rankedUsers.forEach((user, index) => {
|
||||
user.signRank = requestOrder === 'ascending' ? index + 1 : rankedUsers.length - index
|
||||
})
|
||||
this.$set(this, 'userData', rankedUsers)
|
||||
}
|
||||
})
|
||||
},
|
||||
changeSignRankOrder({prop, order}) {
|
||||
if (prop !== 'signRank') {
|
||||
return
|
||||
}
|
||||
// Element清除排序时恢复默认的名次降序,并立即重新调用后台查询。
|
||||
this.signRankOrder = order || 'descending'
|
||||
this.getRealTimeData()
|
||||
},
|
||||
startPolling(func) {
|
||||
this.lastTimeData = []
|
||||
|
||||
+8
-5
@@ -2,10 +2,12 @@
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<guava ref="guava">
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/meeting-list-layout.css">
|
||||
|
||||
<el-card shadow="never">
|
||||
<div id="app">
|
||||
<guava ref="guava" class="meeting-list-layout">
|
||||
|
||||
<el-card shadow="never" class="meeting-search-card">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
@@ -35,14 +37,15 @@ layout("/layouts/platform.html"){
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-card shadow="never" class="meeting-table-card">
|
||||
<table-tool label="申请列表">
|
||||
<el-radio-group v-model="pageForm.approval" size="small" @change="doSearch">
|
||||
<el-radio-button :label="true">已审核</el-radio-button>
|
||||
<el-radio-button :label="false">未审核</el-radio-button>
|
||||
</el-radio-group>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table class="meeting-main-table" height="100%" v-loading="tableLoading"
|
||||
:data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/meeting-list-layout.css">
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<guava ref="guava" class="meeting-list-layout">
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<el-card shadow="never" class="meeting-search-card">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
@@ -39,9 +41,10 @@ layout("/layouts/platform.html"){
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt20">
|
||||
<el-card shadow="never" class="meeting-table-card">
|
||||
<table-tool label="会议列表"></table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize" ref="meetingTable">
|
||||
<el-table class="meeting-main-table" height="100%" v-loading="tableLoading"
|
||||
:data="tableData" @sort-change="pageOrder" :size="tableSize" ref="meetingTable">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
|
||||
+240
-84
@@ -1,86 +1,108 @@
|
||||
const periodsInfo = {
|
||||
template: /*language=HTML*/ `
|
||||
<div>
|
||||
<el-alert title="可点击表格中的数字查看具体人员名单" type="info" effect="dark" class="mb20" :closable="false" show-icon></el-alert>
|
||||
<el-table ref="periodsTable" :data="periodsTableData" :size="tableSize" @cell-click="handleCellClick">
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:width="column.width"
|
||||
:sortable="column.sortable"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
:class-name="column.className"
|
||||
v-if="!column.condition || column.condition.includes(typeOptions.find(o => o.id === selectRow.typeId)?.designId)"
|
||||
v-for="column in periodsTableColumns"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<el-link v-if="column.className && column.className === 'export-column'"
|
||||
@click="onUser(row, column.prop)" type="primary">
|
||||
{{ row[column.prop] }}
|
||||
</el-link>
|
||||
<span v-else-if="column.prop === 'proportionNum'">
|
||||
{{ proportionNum(row.comeCount, row.totalCount) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ row[column.prop] }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="right" header-align="center" label="操作" width="160">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button @click="exportUser(row)" size="mini" type="primary">导出人员</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="meeting-statistics-detail">
|
||||
<section class="statistics-panel periods-panel">
|
||||
<table-tool label="会议列表"></table-tool>
|
||||
<div class="periods-tip">点击【**人数】可查看对应【人员名单】列表</div>
|
||||
<div class="periods-table-wrap">
|
||||
<el-table ref="periodsTable"
|
||||
:data="periodsTableData"
|
||||
:size="tableSize"
|
||||
height="100%"
|
||||
@cell-click="handleCellClick">
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:width="column.width"
|
||||
:sortable="column.sortable"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
:class-name="column.className"
|
||||
v-if="!column.condition || column.condition.includes(typeOptions.find(o => o.id === selectRow.typeId)?.designId)"
|
||||
v-for="column in periodsTableColumns"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<el-link v-if="column.className && column.className === 'export-column'"
|
||||
@click.stop="selectUserList(row, column.prop, column.label)" type="primary">
|
||||
{{ row[column.prop] }}
|
||||
</el-link>
|
||||
<span v-else-if="column.prop === 'proportionNum'">
|
||||
{{ proportionNum(row.comeCount, row.totalCount) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ row[column.prop] }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="right" header-align="center" label="操作" width="120">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button @click="exportUser(row)" size="mini" type="primary">导出人员</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-alert :title="tooltip + '人员名单'" type="info" effect="dark" class="mt20 mb20" :closable="false" show-icon></el-alert>
|
||||
<div style="padding: 20px; border-radius: 8px; border: 1px solid #ebeef5">
|
||||
<search @search="doSearch">
|
||||
<search-item label="姓名/工号">
|
||||
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" placeholder="请选择所属工会" filterable clearable
|
||||
@change="doSearch" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in unionList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="所属单位">
|
||||
<el-select @change="doSearch"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择单位" style="width: 100%" v-model="pageForm.unitId">
|
||||
<el-option :key="item.id" :label="item.name" :value="item.id" v-for="item in unitList"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
</search>
|
||||
</div>
|
||||
<el-table :data="userTableData" :row-key="(row)=>{row.id}" :size="tableSize" class="mt20">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:width="column.width"
|
||||
:sortable="column.sortable"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
<section class="statistics-panel users-panel">
|
||||
<table-tool :label="tooltip + '人员名单'"></table-tool>
|
||||
<div class="person-search">
|
||||
<search @search="doSearch">
|
||||
<search-item label="姓名/工号">
|
||||
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" placeholder="请选择所属工会" filterable clearable
|
||||
@change="doSearch" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in unionList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="所属单位">
|
||||
<el-select @change="doSearch"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择单位" style="width: 100%" v-model="pageForm.unitId">
|
||||
<el-option :key="item.id" :label="item.name" :value="item.id" v-for="item in unitList"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
</search>
|
||||
</div>
|
||||
<div class="users-table-wrap">
|
||||
<el-table ref="userTable"
|
||||
:data="userTableData"
|
||||
:row-key="(row)=>{row.id}"
|
||||
:size="tableSize"
|
||||
v-loading="userTableLoading"
|
||||
element-loading-text="数据加载中"
|
||||
height="100%">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:key="column.prop"
|
||||
:width="column.width"
|
||||
:sortable="column.sortable"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="users-pagination">
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</div>
|
||||
</section>
|
||||
<slot></slot>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
@@ -124,6 +146,8 @@ const periodsInfo = {
|
||||
{prop: 'proportionNum', label: '人数比例', width: 100},
|
||||
],
|
||||
userTableData: [],
|
||||
userTableLoading: false,
|
||||
userRequestSerial: 0,
|
||||
tooltip: '',
|
||||
|
||||
selectPeriodRow: {},
|
||||
@@ -135,11 +159,20 @@ const periodsInfo = {
|
||||
if (!cell.classList.contains('export-column')) {
|
||||
return; // 不是目标列,直接返回
|
||||
}
|
||||
this.onUser(row, column.property)
|
||||
this.tooltip = '【' + row.periodName + '-' + column.label + '】'
|
||||
this.selectUserList(row, column.property, column.label)
|
||||
},
|
||||
selectUserList(row, type, label) {
|
||||
// 人数链接阻止事件冒泡后统一从此处查询,避免链接点击与单元格点击重复请求人员接口。
|
||||
this.tooltip = '【' + row.periodName + '-' + label + '】'
|
||||
this.onUser(row, type)
|
||||
},
|
||||
onOpen(row) {
|
||||
this.selectRow = row
|
||||
// 打开新的会议统计详情时重置人员查询状态,避免残留上一次查看的数据。
|
||||
this.userRequestSerial++
|
||||
this.userTableLoading = false
|
||||
this.userTableData = []
|
||||
this.pageForm.totalCount = 0
|
||||
this.$axios.post(loc() + '/view', {meetingId: row.id})
|
||||
.then((res) => {
|
||||
this.periodsTableData = res.data
|
||||
@@ -150,6 +183,17 @@ const periodsInfo = {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.onUser()
|
||||
},
|
||||
pageNumberChange(pageNumber) {
|
||||
// 人员名单分页必须继续使用当前会议时段和人数类型查询,不能调用通用会议分页接口。
|
||||
this.pageForm.pageNumber = pageNumber
|
||||
this.onUser()
|
||||
},
|
||||
pageSizeChange(pageSize) {
|
||||
// 切换每页条数后从第一页重新查询,并复用人员表格的加载及防并发覆盖逻辑。
|
||||
this.pageForm.pageSize = pageSize
|
||||
this.pageForm.pageNumber = 1
|
||||
this.onUser()
|
||||
},
|
||||
onUser(row, type) {
|
||||
this.selectPeriodRow = row || this.selectPeriodRow
|
||||
this.selectUserType = type || this.selectUserType
|
||||
@@ -158,11 +202,25 @@ const periodsInfo = {
|
||||
this.$set(this.pageForm, 'meetingId', this.selectPeriodRow.meetingId)
|
||||
this.$set(this.pageForm, 'periodId', this.selectPeriodRow.id)
|
||||
|
||||
// 每次查询分配递增序号,快速切换人数类型时仅允许最后一次请求更新列表。
|
||||
const requestSerial = ++this.userRequestSerial
|
||||
this.userTableLoading = true
|
||||
this.userTableData = []
|
||||
this.pageForm.totalCount = 0
|
||||
this.$axios.post(loc() + "/queryUserTable", this.pageForm).then((res) => {
|
||||
if (requestSerial !== this.userRequestSerial) {
|
||||
return
|
||||
}
|
||||
if (res.code === 0) {
|
||||
this.userTableData = res.data.list
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
}
|
||||
this.userTableLoading = false
|
||||
}, () => {
|
||||
// 接口异常时只关闭当前有效请求的加载状态,旧请求不影响新请求的转圈提示。
|
||||
if (requestSerial === this.userRequestSerial) {
|
||||
this.userTableLoading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
exportUser(row) {
|
||||
@@ -179,8 +237,106 @@ const periodsInfo = {
|
||||
},
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
.el-alert__icon {
|
||||
color: #006DB9;
|
||||
.meeting-statistics-detail {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .statistics-panel {
|
||||
min-height: 0;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .periods-panel {
|
||||
flex: 0 0 260px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .users-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .ele-table-tool {
|
||||
flex: 0 0 38px;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .periods-tip {
|
||||
flex: 0 0 22px;
|
||||
color: #f56c6c;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .periods-table-wrap,
|
||||
.meeting-statistics-detail .users-table-wrap {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .periods-table-wrap {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .person-search {
|
||||
flex: 0 0 auto;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #e4ebf5;
|
||||
border-radius: 6px;
|
||||
background: #f5f9ff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .person-search .search {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .users-table-wrap {
|
||||
flex: 1 1 0;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .users-pagination {
|
||||
flex: 0 0 64px;
|
||||
min-height: 64px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
overflow: visible;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .users-pagination .el-pagination-container {
|
||||
width: 100%;
|
||||
margin-top: 12px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .el-table__body-wrapper {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@media (max-height: 760px) {
|
||||
.meeting-statistics-detail .periods-panel {
|
||||
flex-basis: 230px;
|
||||
}
|
||||
|
||||
.meeting-statistics-detail .statistics-panel {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/meeting-list-layout.css">
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<guava ref="guava" class="meeting-list-layout">
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<el-card shadow="never" class="meeting-search-card">
|
||||
<search @search="doSearch">
|
||||
<search-item label="名称/编码">
|
||||
<el-input placeholder="请输入名称或编码" clearable v-model="pageForm.searchKeyword"
|
||||
@@ -19,14 +21,15 @@ layout("/layouts/platform.html"){
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt20">
|
||||
<el-card shadow="never" class="meeting-table-card">
|
||||
<table-tool label="类型列表">
|
||||
<el-button type="primary" size="small" @click="onAdd">
|
||||
<i class="ti-plus"></i>
|
||||
新增类型
|
||||
</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize">
|
||||
<el-table class="meeting-main-table" height="100%" v-loading="tableLoading"
|
||||
:data="tableData" @sort-change="pageOrder" :size="tableSize">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
|
||||
<el-table-column
|
||||
:label="column.label"
|
||||
|
||||
+1
-3
@@ -1,7 +1,7 @@
|
||||
const MEMBER_CHANGE = {
|
||||
template: /*language=HTML*/ `
|
||||
<el-card shadow="never">
|
||||
<snaker-start v-if="showFlowHeader" slot="header" label="会员变更" define_key="HYBG"></snaker-start>
|
||||
<snaker-start slot="header" label="会员变更" define_key="HYBG"></snaker-start>
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=":"
|
||||
class="flow-task-form">
|
||||
<el-descriptions :column="3" border>
|
||||
@@ -288,8 +288,6 @@ const MEMBER_CHANGE = {
|
||||
props: {
|
||||
id: { type: String, default: '' },
|
||||
showThreeUnit: { type: Boolean, default: false },
|
||||
// 控制公共变更组件的标题及流程图入口,默认保留原会员变更页面样式。
|
||||
showFlowHeader: { type: Boolean, default: true },
|
||||
},
|
||||
mixins: [initTableMixins],
|
||||
store,
|
||||
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
/* 人员类型统计页面固定占满平台内容区,避免页面本身产生纵向滚动条。 */
|
||||
#app .person-type-statistics-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: calc(100vh - 82px);
|
||||
overflow: hidden;
|
||||
}
|
||||
#app .person-type-statistics-query-card {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
/* 列表卡片使用剩余空间,数据超出时仅由 Element 表格内部滚动。 */
|
||||
#app .person-type-statistics-list-card {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
#app .person-type-statistics-list-card > .el-card__body {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#app .person-type-statistics-list-card .ele-table-tool,
|
||||
#app .person-type-statistics-list-card .el-pagination-container {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
/* 固定高度表格使用纵向弹性布局,让数据区占满中间空间,合计行始终贴在列表底部。 */
|
||||
#app .person-type-statistics-list-card .el-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
#app .person-type-statistics-list-card .el-table__header-wrapper,
|
||||
#app .person-type-statistics-list-card .el-table__footer-wrapper {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
#app .person-type-statistics-list-card .el-table__body-wrapper {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<template>
|
||||
<div class="person-type-statistics-layout">
|
||||
<el-card class="person-type-statistics-query-card" shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="所属分工会" v-if="schoolAdmin">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable
|
||||
placeholder="请选择所属分工会" style="width: 100%"
|
||||
@change="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="所属单位" v-if="pageForm.queryType === 'unit'">
|
||||
<el-select v-model="pageForm.unitId" clearable filterable
|
||||
placeholder="请选择所属单位" style="width: 100%"
|
||||
@change="doSearch">
|
||||
<el-option v-for="item in units" :key="item.id"
|
||||
:label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item v-if="schoolAdmin">
|
||||
<el-radio-group v-model="pageForm.queryType" @change="handleQueryTypeChange">
|
||||
<el-radio-button label="union">按分工会统计</el-radio-button>
|
||||
<el-radio-button label="unit">按单位统计</el-radio-button>
|
||||
</el-radio-group>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10 person-type-statistics-list-card" shadow="never">
|
||||
<table-tool label="人员类型统计">
|
||||
<el-button type="primary" icon="el-icon-download" @click="doExport">导出</el-button>
|
||||
</table-tool>
|
||||
<el-table v-loading="tableLoading" :data="tableData" :height="tableHeight"
|
||||
border show-summary :summary-method="getSummaries"
|
||||
ref="statisticsTableRef" style="width: 100%">
|
||||
<el-table-column align="center" header-align="center" type="index" :index="indexMethod"
|
||||
label="序号" width="80"></el-table-column>
|
||||
<el-table-column v-if="pageForm.queryType === 'union'" align="center"
|
||||
header-align="center" label="分工会" min-width="220">
|
||||
<template slot-scope="{row}">
|
||||
{{row.unionCode ? '(' + row.unionCode + ')' : ''}}{{row.unionName}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-else align="center" header-align="center"
|
||||
label="单位" min-width="220">
|
||||
<template slot-scope="{row}">
|
||||
{{row.unitCode ? '(' + row.unitCode + ')' : ''}}{{row.unitName}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="schoolAdmin && pageForm.queryType === 'unit'"
|
||||
align="center" header-align="center" label="所属分工会"
|
||||
prop="unionName" min-width="180"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="小计"
|
||||
prop="totalNum" min-width="100"></el-table-column>
|
||||
<el-table-column v-for="item in personTypeOptions" :key="item.prop"
|
||||
align="center" header-align="center" :label="item.name"
|
||||
:prop="item.prop" min-width="120"></el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
queryType: "union",
|
||||
unionId: "",
|
||||
unitId: ""
|
||||
},
|
||||
personTypeOptions: [],
|
||||
summaryData: {},
|
||||
schoolAdmin: false,
|
||||
unions: [],
|
||||
units: [],
|
||||
tableHeight: 300,
|
||||
tableResizeObserver: null,
|
||||
pageRequestSequence: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 查询后端统计结果,并使用后端返回的实际维度防止非校级用户越权切换。 */
|
||||
pageData() {
|
||||
// 翻页或重新查询时立即清空旧页并显示加载状态,避免用户误认为旧数据属于新页。
|
||||
const requestSequence = ++this.pageRequestSequence
|
||||
const requestData = Object.assign({}, this.pageForm)
|
||||
this.tableData = []
|
||||
this.tableLoading = true
|
||||
this.$axios.post(loc() + "/pageData", requestData).then((resp) => {
|
||||
// 快速切换分页时只接收最后一次请求,防止较慢的旧请求覆盖当前页。
|
||||
if (requestSequence !== this.pageRequestSequence) {
|
||||
return
|
||||
}
|
||||
if (resp.code === 0) {
|
||||
const data = resp.data || {}
|
||||
const pageData = data.pageData || {}
|
||||
this.tableData = pageData.list || []
|
||||
this.pageForm.totalCount = pageData.totalCount || 0
|
||||
this.personTypeOptions = data.personTypeOptions || []
|
||||
this.summaryData = data.summary || {}
|
||||
this.schoolAdmin = data.schoolAdmin === true
|
||||
this.pageForm.queryType = data.queryType || "unit"
|
||||
// 动态列和合计数据返回后重新布局,确保合计行列宽对齐并固定在底部。
|
||||
this.updateTableHeight()
|
||||
} else {
|
||||
this.$message.error(resp.msg)
|
||||
}
|
||||
}).finally(() => {
|
||||
if (requestSequence === this.pageRequestSequence) {
|
||||
this.tableLoading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 按列表卡片剩余空间计算表格高度,固定工具栏和分页并启用表格内部滚动。 */
|
||||
updateTableHeight() {
|
||||
this.$nextTick(() => {
|
||||
const cardBody = this.$el.querySelector(".person-type-statistics-list-card > .el-card__body")
|
||||
if (!cardBody || cardBody.clientHeight <= 0) {
|
||||
return
|
||||
}
|
||||
const getOuterHeight = (element) => {
|
||||
if (!element) {
|
||||
return 0
|
||||
}
|
||||
const style = window.getComputedStyle(element)
|
||||
return element.offsetHeight
|
||||
+ (parseFloat(style.marginTop) || 0)
|
||||
+ (parseFloat(style.marginBottom) || 0)
|
||||
}
|
||||
const bodyStyle = window.getComputedStyle(cardBody)
|
||||
const bodyPadding = (parseFloat(bodyStyle.paddingTop) || 0)
|
||||
+ (parseFloat(bodyStyle.paddingBottom) || 0)
|
||||
const tableTool = cardBody.querySelector(".ele-table-tool")
|
||||
const pagination = cardBody.querySelector(".el-pagination-container")
|
||||
const reservedHeight = bodyPadding
|
||||
+ getOuterHeight(tableTool)
|
||||
+ getOuterHeight(pagination)
|
||||
const nextHeight = Math.max(1, Math.floor(cardBody.clientHeight - reservedHeight))
|
||||
if (this.tableHeight !== nextHeight) {
|
||||
this.tableHeight = nextHeight
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.statisticsTableRef) {
|
||||
this.$refs.statisticsTableRef.doLayout()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
/** 切换分工会时清空单位,并重新加载该分工会下的单位选项。 */
|
||||
handleUnionChange(unionId) {
|
||||
this.pageForm.unitId = ""
|
||||
this.loadUnits(unionId)
|
||||
this.doSearch()
|
||||
},
|
||||
/** 切换统计维度后清空本地筛选并重新查询统计数据。 */
|
||||
handleQueryTypeChange() {
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.unitId = ""
|
||||
this.loadUnits("")
|
||||
this.doSearch()
|
||||
},
|
||||
/** 加载单位下拉选项,非校级用户只能加载当前所属工会的单位。 */
|
||||
loadUnits(unionId) {
|
||||
let targetUnionId = unionId
|
||||
if (!this.schoolAdmin && this.$store.state.user.union) {
|
||||
targetUnionId = this.$store.state.user.union.id
|
||||
}
|
||||
const unitPromise = targetUnionId
|
||||
? this.$businessTool.listUnit(targetUnionId)
|
||||
: this.$businessTool.listUnit()
|
||||
unitPromise.then((data) => {
|
||||
this.units = data || []
|
||||
})
|
||||
},
|
||||
/** 初始化机构筛选数据。 */
|
||||
initOrganizationOptions() {
|
||||
this.$businessTool.listUnion().then((data) => {
|
||||
this.unions = data || []
|
||||
})
|
||||
this.loadUnits("")
|
||||
},
|
||||
/** 使用后台返回的全量统计结果生成合计行,合计不受当前页数据影响。 */
|
||||
getSummaries({columns}) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
return "合计"
|
||||
}
|
||||
if (!column.property) {
|
||||
return ""
|
||||
}
|
||||
const value = this.summaryData[column.property]
|
||||
return value === undefined || value === null ? "" : value
|
||||
})
|
||||
},
|
||||
/** 按当前实际统计维度导出人员类型统计。 */
|
||||
doExport() {
|
||||
const params = $.param({
|
||||
queryType: this.pageForm.queryType,
|
||||
unionId: this.pageForm.unionId,
|
||||
unitId: this.pageForm.unitId
|
||||
})
|
||||
window.open(loc() + "/doExport?" + params)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.schoolAdmin = this.$auth.hasRoleOr([
|
||||
"SYSADMIN",
|
||||
"SCHOOL_UNION_ADMIN",
|
||||
"SCHOOL_UNION_MEMBER_ADMIN"
|
||||
])
|
||||
if (!this.schoolAdmin) {
|
||||
this.pageForm.queryType = "unit"
|
||||
}
|
||||
this.initOrganizationOptions()
|
||||
this.pageData()
|
||||
},
|
||||
mounted() {
|
||||
const listCardBody = this.$el.querySelector(".person-type-statistics-list-card > .el-card__body")
|
||||
if (listCardBody && window.ResizeObserver) {
|
||||
// 查询区换行或窗口尺寸变化时,重新分配表格内部滚动区域高度。
|
||||
this.tableResizeObserver = new ResizeObserver(this.updateTableHeight)
|
||||
this.tableResizeObserver.observe(listCardBody)
|
||||
}
|
||||
this.updateTableHeight()
|
||||
window.addEventListener("resize", this.updateTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.tableResizeObserver) {
|
||||
this.tableResizeObserver.disconnect()
|
||||
this.tableResizeObserver = null
|
||||
}
|
||||
window.removeEventListener("resize", this.updateTableHeight)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+291
-26
@@ -1,9 +1,81 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
/* 待确认人员变更沿用原会员变更表单的字段宽度及单选框布局。 */
|
||||
.pending-member-change-form .el-descriptions-item__label {
|
||||
width: 15% !important;
|
||||
}
|
||||
|
||||
.pending-member-change-form .welfare-member-radio-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pending-member-change-form .welfare-member-radio-group .el-radio {
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 批量按钮位于状态切换左侧,并保留少量操作间距。 */
|
||||
.pending-list-toolbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pending-list-toolbar-actions .pending-batch-change-button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* 批量变更弹窗固定在当前浏览器可视区域正中间。 */
|
||||
.el-dialog.pending-batch-change-dialog {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: 0 !important;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/* 待确认人员列表固定占满平台内容区,页面本身不产生纵向滚动条。 */
|
||||
#app .pending-confirm-list-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: calc(100vh - 82px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app .pending-confirm-query-card {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/* 列表卡片填充剩余高度,超出数据仅在表格内部滚动。 */
|
||||
#app .pending-confirm-table-card {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app .pending-confirm-table-card > .el-card__body {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app .pending-confirm-table-card .ele-table-tool,
|
||||
#app .pending-confirm-table-card .el-pagination-container {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<guava ref="guava" @vchange="handleGuavaChange">
|
||||
<div class="pending-confirm-list-layout">
|
||||
<el-card class="pending-confirm-query-card" shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="姓名/工号">
|
||||
<el-input clearable placeholder="请输入姓名或工号"
|
||||
@@ -35,16 +107,27 @@ layout("/layouts/platform.html"){
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<el-card class="mt10 pending-confirm-table-card" shadow="never">
|
||||
<table-tool label="待确认人员记录">
|
||||
<el-radio-group @change="doSearch" size="small" v-model="pageForm.pendingConfirmStatus">
|
||||
<el-radio-button :label="1">待确认</el-radio-button>
|
||||
<el-radio-button :label="2">已确认</el-radio-button>
|
||||
</el-radio-group>
|
||||
<div class="pending-list-toolbar-actions">
|
||||
<el-button class="pending-batch-change-button" type="primary"
|
||||
icon="el-icon-edit" size="small"
|
||||
v-if="pageForm.pendingConfirmStatus === 1"
|
||||
@click="openBatchChange">批量变更</el-button>
|
||||
<el-radio-group @change="handleConfirmStatusChange" size="small"
|
||||
v-model="pageForm.pendingConfirmStatus">
|
||||
<el-radio-button :label="1">待确认</el-radio-button>
|
||||
<el-radio-button :label="2">已确认</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</table-tool>
|
||||
|
||||
<el-table :data="tableData" :size="tableSize" @sort-change="pageOrder"
|
||||
<el-table :data="tableData" :size="tableSize" :height="pendingTableHeight"
|
||||
@sort-change="pageOrder"
|
||||
@selection-change="handleSelectionChange"
|
||||
class="vi-table" ref="table" row-key="id" style="width: 100%">
|
||||
<el-table-column v-if="pageForm.pendingConfirmStatus === 1" type="selection"
|
||||
align="center" fixed="left" width="55"></el-table-column>
|
||||
<el-table-column :index="indexMethod" align="center" fixed="left"
|
||||
header-align="center" label="序号" type="index" width="80"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="工号" min-width="120"
|
||||
@@ -70,30 +153,54 @@ layout("/layouts/platform.html"){
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="right" header-align="center" label="操作" width="100">
|
||||
<template slot-scope="{row}">
|
||||
<el-button @click="openChange(row.id)" size="mini" type="primary">变更</el-button>
|
||||
<el-button @click="openChange(row.id)" size="mini" type="primary"
|
||||
v-if="row.pendingConfirmStatus === 1"
|
||||
:loading="pendingChangeLoading && pendingChangeLoadingUserId === row.id"
|
||||
:disabled="pendingChangeLoading">变更</el-button>
|
||||
<el-button @click="openView(row.id)" size="mini" type="primary"
|
||||
v-else-if="row.pendingConfirmStatus === 2"
|
||||
:loading="pendingChangeLoading && pendingChangeLoadingUserId === row.id"
|
||||
:disabled="pendingChangeLoading">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<template #public>
|
||||
<member-change ref="memberChangeRef" :show-three-unit="true" :show-flow-header="false"
|
||||
@do-back="closeChange" @do-submit="doSubmit"></member-change>
|
||||
<!--#include("memberChange.html"){}#-->
|
||||
</template>
|
||||
</guava>
|
||||
|
||||
<el-dialog title="批量变更人员类型" :visible.sync="batchDialogVisible"
|
||||
append-to-body width="480px" custom-class="pending-batch-change-dialog"
|
||||
@closed="resetBatchChange">
|
||||
<el-form :model="batchChangeForm" :rules="batchChangeRules"
|
||||
ref="batchChangeFormRef" label-width="100px">
|
||||
<el-form-item label="人员类型" prop="personType">
|
||||
<el-select v-model="batchChangeForm.personType" filterable
|
||||
placeholder="请选择人员类型" style="width: 100%">
|
||||
<el-option v-for="item in pendingPersonTypeOptions" :key="item.code"
|
||||
:label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button @click="batchDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="batchSubmitting"
|
||||
@click="confirmBatchChange">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../member/change/common/memberChange.js"){}#-->
|
||||
<!--#include("memberChange.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
components: {
|
||||
"member-change": MEMBER_CHANGE
|
||||
},
|
||||
mixins: [initTableMixins, PENDING_MEMBER_CHANGE],
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
@@ -105,21 +212,135 @@ layout("/layouts/platform.html"){
|
||||
pendingConfirmStatus: 1
|
||||
},
|
||||
unions: [],
|
||||
units: []
|
||||
units: [],
|
||||
selectedPendingUsers: [],
|
||||
batchDialogVisible: false,
|
||||
batchSubmitting: false,
|
||||
batchChangeForm: {
|
||||
personType: ""
|
||||
},
|
||||
batchChangeRules: {
|
||||
personType: [{required: true, message: "请选择人员类型", trigger: "change"}]
|
||||
},
|
||||
pendingTableHeight: 300,
|
||||
pendingTableResizeObserver: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 分页查询待确认或已确认人员。 */
|
||||
pageData() {
|
||||
this.selectedPendingUsers = []
|
||||
return $.post("/platform/sourcechange/pending/pageData", this.pageForm).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.tableData = resp.data.list
|
||||
this.pageForm.totalCount = resp.data.totalCount
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.table) {
|
||||
this.$refs.table.clearSelection()
|
||||
}
|
||||
this.updatePendingTableHeight()
|
||||
})
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 根据列表卡片可用空间固定表格高度,使大量数据仅在表格内部滚动。 */
|
||||
updatePendingTableHeight() {
|
||||
this.$nextTick(() => {
|
||||
const cardBody = this.$el.querySelector(".pending-confirm-table-card > .el-card__body")
|
||||
if (!cardBody || cardBody.clientHeight <= 0) {
|
||||
return
|
||||
}
|
||||
const getOuterHeight = (element) => {
|
||||
if (!element) {
|
||||
return 0
|
||||
}
|
||||
const style = window.getComputedStyle(element)
|
||||
return element.offsetHeight
|
||||
+ (parseFloat(style.marginTop) || 0)
|
||||
+ (parseFloat(style.marginBottom) || 0)
|
||||
}
|
||||
const bodyStyle = window.getComputedStyle(cardBody)
|
||||
const bodyPadding = (parseFloat(bodyStyle.paddingTop) || 0)
|
||||
+ (parseFloat(bodyStyle.paddingBottom) || 0)
|
||||
const tableTool = cardBody.querySelector(".ele-table-tool")
|
||||
const pagination = cardBody.querySelector(".el-pagination-container")
|
||||
const reservedHeight = bodyPadding
|
||||
+ getOuterHeight(tableTool)
|
||||
+ getOuterHeight(pagination)
|
||||
const nextHeight = Math.max(1, Math.floor(cardBody.clientHeight - reservedHeight))
|
||||
if (this.pendingTableHeight !== nextHeight) {
|
||||
this.pendingTableHeight = nextHeight
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.table) {
|
||||
this.$refs.table.doLayout()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
/** 切换确认状态时清空已勾选人员,并从第一页重新查询。 */
|
||||
handleConfirmStatusChange() {
|
||||
this.selectedPendingUsers = []
|
||||
this.doSearch()
|
||||
},
|
||||
/** 保存当前页勾选的待确认人员,供批量变更使用。 */
|
||||
handleSelectionChange(selection) {
|
||||
this.selectedPendingUsers = (selection || []).filter((item) => item.pendingConfirmStatus === 1)
|
||||
},
|
||||
/** 校验勾选结果后打开批量人员类型弹窗。 */
|
||||
openBatchChange() {
|
||||
if (this.selectedPendingUsers.length === 0) {
|
||||
this.$message.warning("请先勾选需要变更的人员")
|
||||
return
|
||||
}
|
||||
this.batchChangeForm.personType = ""
|
||||
this.batchDialogVisible = true
|
||||
},
|
||||
/** 二次确认批量操作,防止误改多名人员。 */
|
||||
confirmBatchChange() {
|
||||
this.$refs.batchChangeFormRef.validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
const selectedCount = this.selectedPendingUsers.length
|
||||
this.$confirm("是否确认变更已勾选的" + selectedCount + "名人员?", "提示", {
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.submitBatchChange()
|
||||
}).catch(() => {})
|
||||
})
|
||||
},
|
||||
/** 调用批量接口,在一个事务中修改人员类型并完成确认。 */
|
||||
submitBatchChange() {
|
||||
const userIds = this.selectedPendingUsers.map((item) => item.id)
|
||||
this.batchSubmitting = true
|
||||
this.$axios.post("/platform/sourcechange/pending/batchChange", {
|
||||
userIds: JSON.stringify(userIds),
|
||||
personType: this.batchChangeForm.personType
|
||||
}).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.batchDialogVisible = false
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$message.error(resp.msg)
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.error("批量变更失败,请稍后重试")
|
||||
}).finally(() => {
|
||||
this.batchSubmitting = false
|
||||
})
|
||||
},
|
||||
/** 关闭弹窗后清理表单,避免保留上次选择的人员类型。 */
|
||||
resetBatchChange() {
|
||||
this.batchSubmitting = false
|
||||
this.batchChangeForm.personType = ""
|
||||
if (this.$refs.batchChangeFormRef) {
|
||||
this.$refs.batchChangeFormRef.clearValidate()
|
||||
}
|
||||
},
|
||||
/** 切换工会时重新加载所属单位。 */
|
||||
flushUnits() {
|
||||
this.$set(this.pageForm, "unitId", "")
|
||||
@@ -134,21 +355,47 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
}
|
||||
},
|
||||
/** 打开人员变更组件并加载当前人员信息。 */
|
||||
/** 打开人员详情,根据只读参数进入变更或查看模式。 */
|
||||
async openPendingMemberDetail(userId, readonly) {
|
||||
if (this.pendingChangeLoading) {
|
||||
return
|
||||
}
|
||||
this.pendingChangeLoading = true
|
||||
this.pendingChangeLoadingUserId = userId
|
||||
try {
|
||||
await this.initPendingMemberChange(userId, readonly)
|
||||
this.$refs.guava.public()
|
||||
} catch (error) {
|
||||
this.$message.error(error && error.message ? error.message : "人员信息加载失败,请稍后重试")
|
||||
} finally {
|
||||
this.pendingChangeLoading = false
|
||||
this.pendingChangeLoadingUserId = ""
|
||||
}
|
||||
},
|
||||
/** 打开待确认人员可编辑页面。 */
|
||||
openChange(userId) {
|
||||
this.$refs.guava.public(() => {
|
||||
this.$refs.memberChangeRef.init(userId)
|
||||
})
|
||||
return this.openPendingMemberDetail(userId, false)
|
||||
},
|
||||
/** 打开已确认人员只读查看页面。 */
|
||||
openView(userId) {
|
||||
return this.openPendingMemberDetail(userId, true)
|
||||
},
|
||||
/** 关闭变更页面并刷新列表。 */
|
||||
closeChange() {
|
||||
this.$refs.guava.index()
|
||||
this.pageData()
|
||||
},
|
||||
/** 复用人员变更提交接口,成功后刷新待确认状态。 */
|
||||
doSubmit(data) {
|
||||
/** Guava 返回列表时统一清理编辑状态并重新加载列表。 */
|
||||
handleGuavaChange({newValue}) {
|
||||
if (newValue === "index") {
|
||||
this.resetPendingMemberChange()
|
||||
this.pageData()
|
||||
this.updatePendingTableHeight()
|
||||
}
|
||||
},
|
||||
/** 调用待确认人员专用接口,直接更新人员及历史记录。 */
|
||||
doSubmitPendingMemberChange(data) {
|
||||
const loading = createLoading("正在提交中")
|
||||
this.$axios.post("/platform/member/change/manage/doSubmitChange", data).then((resp) => {
|
||||
this.$axios.post("/platform/sourcechange/pending/submitChange", data).then((resp) => {
|
||||
loading.close()
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
@@ -169,7 +416,8 @@ layout("/layouts/platform.html"){
|
||||
}),
|
||||
this.$businessTool.listUnit().then((data) => {
|
||||
this.units = data
|
||||
})
|
||||
}),
|
||||
this.loadPendingPersonTypeOptions()
|
||||
])
|
||||
}
|
||||
},
|
||||
@@ -177,6 +425,23 @@ layout("/layouts/platform.html"){
|
||||
this.initData().then(() => {
|
||||
this.pageData()
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
const listCardBody = this.$el.querySelector(".pending-confirm-table-card > .el-card__body")
|
||||
if (listCardBody && window.ResizeObserver) {
|
||||
// 查询区换行或窗口尺寸变化时同步调整表格内部滚动区域。
|
||||
this.pendingTableResizeObserver = new ResizeObserver(this.updatePendingTableHeight)
|
||||
this.pendingTableResizeObserver.observe(listCardBody)
|
||||
}
|
||||
this.updatePendingTableHeight()
|
||||
window.addEventListener("resize", this.updatePendingTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.pendingTableResizeObserver) {
|
||||
this.pendingTableResizeObserver.disconnect()
|
||||
this.pendingTableResizeObserver = null
|
||||
}
|
||||
window.removeEventListener("resize", this.updatePendingTableHeight)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
<el-card shadow="never">
|
||||
<table-tool :label="pendingChangeReadonly ? '待确认人员查看' : '待确认人员变更'"></table-tool>
|
||||
<el-form :model="pendingChangeForm" ref="pendingChangeFormRef" :rules="pendingChangeRules"
|
||||
:disabled="pendingChangeReadonly"
|
||||
label-width="0" label-suffix=":" class="flow-task-form pending-member-change-form">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="工号">
|
||||
<el-form-item prop="loginname">
|
||||
<el-input v-model="pendingChangeForm.loginname" readonly size="small"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="pendingChangeForm.username" readonly size="small"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<el-form-item prop="sex">
|
||||
<el-radio-group :disabled="pendingAllowFields('sex')" v-model="pendingChangeForm.sex" size="small">
|
||||
<el-radio border label="男">男</el-radio>
|
||||
<el-radio border label="女">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="民族">
|
||||
<el-form-item prop="nation">
|
||||
<dict-select v-model="pendingChangeForm.nation" code="USER_NATION"
|
||||
:disabled="pendingAllowFields('nation')" placeholder="请选择民族"
|
||||
style="width: 100%"></dict-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">
|
||||
<el-form-item prop="birthday">
|
||||
<el-date-picker v-model="pendingChangeForm.birthday" type="date"
|
||||
:disabled="pendingAllowFields('birthday')" placeholder="请选择出生日期"
|
||||
format="yyyy-MM-dd" value-format="yyyy-MM-dd"
|
||||
style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="政治面貌">
|
||||
<el-form-item prop="political">
|
||||
<dict-select v-model="pendingChangeForm.political" code="USER_POLITICAL"
|
||||
:disabled="pendingAllowFields('political')" placeholder="请选择政治面貌"
|
||||
style="width: 100%"></dict-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="学历">
|
||||
<el-form-item prop="education">
|
||||
<dict-select v-model="pendingChangeForm.education" code="USER_EDUCATION"
|
||||
:disabled="pendingAllowFields('education')" placeholder="请选择学历"
|
||||
style="width: 100%"></dict-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="学位">
|
||||
<el-form-item prop="academicDegree">
|
||||
<dict-select v-model="pendingChangeForm.academicDegree" code="USER_ACADEMIC_DEGREE"
|
||||
:disabled="pendingAllowFields('academicDegree')" placeholder="请选择学位"
|
||||
style="width: 100%"></dict-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="工作单位">
|
||||
<el-form-item prop="unitId">
|
||||
<el-input v-model="pendingChangeForm.unitName" disabled placeholder="暂无工作单位"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="所属工会">
|
||||
<el-form-item prop="unionName">
|
||||
<el-input v-model="pendingChangeForm.unionName" disabled placeholder="暂无所属工会"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="三级单位">
|
||||
<el-form-item prop="threeUnitId">
|
||||
<el-select v-model="pendingChangeForm.threeUnitId" clearable filterable
|
||||
:disabled="pendingAllowFields('threeUnitId')"
|
||||
placeholder="请选择三级单位" style="width: 100%">
|
||||
<el-option v-for="item in pendingThreeUnits" :key="item.id" :label="item.name"
|
||||
:value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="身份证号码">
|
||||
<el-form-item prop="idCard">
|
||||
<el-input v-model="pendingChangeForm.idCard" :disabled="pendingAllowFields('idCard')"
|
||||
placeholder="请输入身份证号码" maxlength="18"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">
|
||||
<el-form-item prop="mobile">
|
||||
<el-input v-model="pendingChangeForm.mobile" :disabled="pendingAllowFields('mobile')"
|
||||
placeholder="请输入联系电话" maxlength="15"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="电子邮箱">
|
||||
<el-form-item prop="email">
|
||||
<el-input v-model="pendingChangeForm.email" :disabled="pendingAllowFields('email')"
|
||||
placeholder="请输入电子邮箱" maxlength="50"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="在职状态">
|
||||
<el-form-item prop="userState">
|
||||
<dict-select v-model="pendingChangeForm.userState" code="USER_STATE"
|
||||
:disabled="pendingAllowFields('userState')" style="width: 100%"></dict-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="人员类型">
|
||||
<el-form-item prop="personType">
|
||||
<el-input v-if="pendingChangeReadonly" v-model="pendingChangeForm.personType"
|
||||
readonly></el-input>
|
||||
<el-select v-else v-model="pendingChangeForm.personType" filterable
|
||||
placeholder="请选择人员类型" style="width: 100%">
|
||||
<el-option v-for="item in pendingPersonTypeOptions" :key="item.code"
|
||||
:label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="人员属性">
|
||||
<el-form-item prop="userAttribute">
|
||||
<dict-select v-model="pendingChangeForm.userAttribute" code="USER_ATTRIBUTE"
|
||||
:disabled="pendingAllowFields('userAttribute')"
|
||||
style="width: 100%"></dict-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<template v-if="pendingChangeForm.userState === '退休'">
|
||||
<el-descriptions-item label="退休日期">
|
||||
<el-form-item prop="retireDate">
|
||||
<el-date-picker v-model="pendingChangeForm.retireDate" type="date"
|
||||
:disabled="pendingAllowFields('retireDate')"
|
||||
placeholder="请选择退休日期" format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="福利享受截止日期">
|
||||
<el-form-item prop="welfareStopDate">
|
||||
<el-date-picker v-model="pendingChangeForm.welfareStopDate" type="date"
|
||||
:disabled="pendingAllowFields('welfareStopDate')"
|
||||
placeholder="请选择福利享受截止日期" format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
|
||||
<el-descriptions-item label="会员状态">
|
||||
<el-form-item prop="member">
|
||||
<el-radio-group v-model="pendingChangeForm.member" size="small"
|
||||
:disabled="pendingAllowFields('member')">
|
||||
<el-radio border :label="true">会员</el-radio>
|
||||
<el-radio border :label="false">非会员</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="福利会员状态">
|
||||
<el-form-item prop="welfareMember">
|
||||
<el-radio-group v-model="pendingChangeForm.welfareMember" size="small"
|
||||
:disabled="pendingAllowFields('welfareMember')"
|
||||
class="welfare-member-radio-group">
|
||||
<el-radio border :label="true">福利会员</el-radio>
|
||||
<el-radio border :label="false">非福利会员</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="入校时间">
|
||||
<el-form-item prop="arrivalAtSchoolDate">
|
||||
<el-date-picker v-model="pendingChangeForm.arrivalAtSchoolDate" type="date"
|
||||
:disabled="pendingAllowFields('arrivalAtSchoolDate')"
|
||||
placeholder="请选择入校时间" format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<template v-if="pendingChangeForm.loginname === $store.state.user.loginname">
|
||||
<el-descriptions-item label="家庭主要成员" :span="3">
|
||||
<el-form-item prop="families">
|
||||
<el-table :data="pendingChangeForm.families" border size="small">
|
||||
<el-table-column label="关系" prop="relation">
|
||||
<template slot-scope="{row}">
|
||||
<el-input v-model="row.relation" maxlength="50" :disabled="pendingAllowFields('families')"
|
||||
placeholder="请输入与本人关系"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="姓名" prop="name">
|
||||
<template slot-scope="{row}">
|
||||
<el-input v-model="row.name" maxlength="50" :disabled="pendingAllowFields('families')"
|
||||
placeholder="请输入姓名"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="工作单位" prop="unit">
|
||||
<template slot-scope="{row}">
|
||||
<el-input v-model="row.unit" maxlength="100" :disabled="pendingAllowFields('families')"
|
||||
placeholder="请输入工作单位"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark">
|
||||
<template slot-scope="{row}">
|
||||
<el-input v-model="row.remark" maxlength="100" :disabled="pendingAllowFields('families')"
|
||||
placeholder="请输入备注"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="100">
|
||||
<template slot="header">
|
||||
<el-button type="primary" size="mini"
|
||||
:disabled="pendingChangeReadonly || pendingAllowFields('families')"
|
||||
@click="pendingChangeForm.families.push({})">添加</el-button>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<el-button type="danger" icon="el-icon-delete" size="mini"
|
||||
:disabled="pendingChangeReadonly || pendingChangeForm.families.length === 0 || pendingAllowFields('families')"
|
||||
@click="pendingChangeForm.families.splice(scope.$index, 1)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="个人简历" :span="3">
|
||||
<el-form-item prop="personalData">
|
||||
<text-editor v-if="!pendingAllowFields('personalData')" v-model="pendingChangeForm.personalData"></text-editor>
|
||||
<div v-else v-html="pendingChangeForm.personalData"></div>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
</el-descriptions>
|
||||
</el-form>
|
||||
<el-row type="flex" justify="end" class="mt20">
|
||||
<el-button type="primary" plain @click="closeChange">返回</el-button>
|
||||
<el-button type="primary" v-if="!pendingChangeReadonly"
|
||||
@click="submitPendingMemberChange">提交变更</el-button>
|
||||
</el-row>
|
||||
</el-card>
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 待确认人员变更页面逻辑。
|
||||
* 该对象作为页面 mixin 使用,不注册 Vue 子组件,也不包含审批流程相关处理。
|
||||
*/
|
||||
const PENDING_MEMBER_CHANGE = {
|
||||
data() {
|
||||
return {
|
||||
pendingChangeUser: {},
|
||||
pendingChangeForm: {
|
||||
families: []
|
||||
},
|
||||
pendingChangeRules: {
|
||||
username: [{required: false, message: "必填", trigger: ["change", "blur"]}],
|
||||
sex: [{required: false, message: "必填", trigger: ["change", "blur"]}]
|
||||
},
|
||||
pendingPersonTypeOptions: [],
|
||||
pendingThreeUnits: [],
|
||||
pendingChangeReadonly: false,
|
||||
pendingChangeLoading: false,
|
||||
pendingChangeLoadingUserId: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 编辑模式仅允许修改人员类型;查看模式下所有字段均为只读。 */
|
||||
pendingAllowFields(prop) {
|
||||
return this.pendingChangeReadonly || prop !== "personType"
|
||||
},
|
||||
|
||||
/** 从人员类型字典中仅保留待确认业务允许变更的两种类型。 */
|
||||
loadPendingPersonTypeOptions() {
|
||||
return this.$businessTool.getDictOptions("USER_PERSON_TYPE").then((options) => {
|
||||
const allowedNames = ["离退休人员", "其他"]
|
||||
this.pendingPersonTypeOptions = (options || []).filter((item) => allowedNames.includes(item.name))
|
||||
})
|
||||
},
|
||||
|
||||
/** 初始化人员详情数据;已确认记录进入只读模式,不再提供编辑能力。 */
|
||||
async initPendingMemberChange(userId, readonly) {
|
||||
this.pendingChangeReadonly = readonly === true
|
||||
const resp = await $.post("/platform/member/change/apply/getUserByIdForMemberChange", {userId: userId})
|
||||
if (resp.code !== 0 || !resp.data) {
|
||||
throw new Error(resp.msg || "未获取到人员基础信息")
|
||||
}
|
||||
|
||||
this.pendingChangeUser = resp.data
|
||||
this.fillPendingChangeForm(resp.data)
|
||||
await this.loadPendingThreeUnits()
|
||||
},
|
||||
|
||||
/** 清理待确认人员变更页面状态,避免返回后残留上一次请求数据。 */
|
||||
resetPendingMemberChange() {
|
||||
this.pendingChangeUser = {}
|
||||
this.pendingChangeForm = {families: []}
|
||||
this.pendingThreeUnits = []
|
||||
this.pendingChangeReadonly = false
|
||||
this.pendingChangeLoading = false
|
||||
this.pendingChangeLoadingUserId = ""
|
||||
if (this.$refs.pendingChangeFormRef) {
|
||||
this.$refs.pendingChangeFormRef.clearValidate()
|
||||
}
|
||||
},
|
||||
|
||||
/** 将人员基础信息转换为待确认变更表单数据。 */
|
||||
fillPendingChangeForm(user) {
|
||||
const unit = user.unit || {}
|
||||
const union = user.union || {}
|
||||
const form = {
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
loginname: user.loginname,
|
||||
sex: user.sex,
|
||||
member: user.member === 1 || user.member === true,
|
||||
welfareMember: user.welfareMember === 1 || user.welfareMember === true,
|
||||
nation: user.nation,
|
||||
birthday: user.birthday,
|
||||
political: user.political,
|
||||
education: user.education,
|
||||
academicDegree: user.academicDegree,
|
||||
position: user.position,
|
||||
unitId: unit.id ? String(unit.id) : null,
|
||||
unitName: unit.name || null,
|
||||
unionId: union.id || null,
|
||||
unionName: union.name || null,
|
||||
campus: user.campus,
|
||||
threeUnitId: user.threeUnitId,
|
||||
idCard: user.idCard,
|
||||
mobile: user.mobile,
|
||||
email: user.email,
|
||||
userState: user.userState,
|
||||
personType: user.personType,
|
||||
preparedBy: user.preparedBy,
|
||||
userAttribute: user.userAttribute,
|
||||
aidFundMemberUserType: user.aidFundMemberUserType,
|
||||
retireDate: user.retireDate,
|
||||
welfareStopDate: user.welfareStopDate,
|
||||
arrivalAtSchoolDate: user.arrivalAtSchoolDate,
|
||||
families: user.families || [],
|
||||
personalData: user.personalData
|
||||
}
|
||||
this.pendingChangeForm = form
|
||||
},
|
||||
|
||||
/** 查询人员所属二级单位下的三级单位。 */
|
||||
loadPendingThreeUnits() {
|
||||
this.pendingThreeUnits = []
|
||||
if (!this.pendingChangeForm.unitId) {
|
||||
this.$message.warning("当前人员未配置所属单位,无法查询三级单位")
|
||||
return Promise.resolve()
|
||||
}
|
||||
return this.$axios.post("/platform/sys/unit/child", {pid: this.pendingChangeForm.unitId})
|
||||
.then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.pendingThreeUnits = resp.data || []
|
||||
} else {
|
||||
this.$message.error(resp.msg || "三级单位查询失败")
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error("三级单位查询失败,请稍后重试")
|
||||
})
|
||||
},
|
||||
|
||||
/** 校验表单后调用待确认人员专用接口直接完成变更。 */
|
||||
submitPendingMemberChange() {
|
||||
this.$refs.pendingChangeFormRef.validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
this.$confirm("提交后将直接更新人员信息,确定提交本次变更吗?", "提示", {type: "warning"})
|
||||
.then(() => {
|
||||
const submitData = Object.assign({}, this.pendingChangeForm)
|
||||
if (submitData.families) {
|
||||
submitData.families = JSON.stringify(submitData.families)
|
||||
}
|
||||
this.doSubmitPendingMemberChange(submitData)
|
||||
})
|
||||
.catch(() => {})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,9 +58,13 @@ layout("/layouts/platform_h5.html"){
|
||||
</div>
|
||||
</div>
|
||||
<div class="sign_button">
|
||||
<van-button v-if="item.joinStatus === true" :disabled="leaveLoading" :loading="leaveLoading" @click.stop="onLeave(item)" size="mini" type="info">请假</van-button>
|
||||
<van-button v-if="item.joinStatus === true"
|
||||
:disabled="leaveLoading || isMeetingHandled(item)" :loading="leaveLoading"
|
||||
@click.stop="onLeave(item)" size="mini" type="info">请假</van-button>
|
||||
<van-button v-if="item.joinStatus === false" size="mini" type="info" disabled>您已请假</van-button>
|
||||
<van-button v-if="item.signStatus === false" :disabled="leaveLoading || isSignDisabled(item.startTime)" @click.stop="onSign(item)" size="mini" type="info">签到</van-button>
|
||||
<van-button v-if="item.signStatus === false"
|
||||
:disabled="leaveLoading || isMeetingHandled(item) || isSignDisabled(item.startTime)"
|
||||
@click.stop="onSign(item)" size="mini" type="info">签到</van-button>
|
||||
<van-button v-if="item.signStatus === true" size="mini" type="info" disabled>您已签到</van-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,6 +117,10 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 已请假或已签到后同时禁用请假、签到按钮,保证移动端与PC端状态一致。
|
||||
isMeetingHandled(timePeriod) {
|
||||
return timePeriod.joinStatus === false || timePeriod.signStatus === true
|
||||
},
|
||||
// 场次开始前禁用签到,避免用户在会议开始前提交签到操作。
|
||||
isSignDisabled(startTime) {
|
||||
return this.$moment().isBefore(this.$moment(startTime))
|
||||
@@ -163,6 +171,10 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
},
|
||||
onLeave(row) {
|
||||
if (this.isMeetingHandled(row)) {
|
||||
this.$toast(row.signStatus === true ? '您已签到,不能请假' : '您已请假,请勿重复操作')
|
||||
return
|
||||
}
|
||||
if(!row.canLeave) {
|
||||
this.$toast('该场次不能请假')
|
||||
return
|
||||
@@ -171,6 +183,10 @@ layout("/layouts/platform_h5.html"){
|
||||
this.reasonVisible = true
|
||||
},
|
||||
onSign(row) {
|
||||
if (this.isMeetingHandled(row)) {
|
||||
this.$toast(row.signStatus === true ? '您已签到,请勿重复操作' : '您已请假,不能签到')
|
||||
return
|
||||
}
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要签到吗?"
|
||||
|
||||
Reference in New Issue
Block a user