This commit is contained in:
=
2026-08-05 08:43:28 +08:00
43 changed files with 2082 additions and 426 deletions
@@ -8,6 +8,7 @@ import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.sys.models.Sys_menu;
import com.budwk.app.sys.models.Sys_module;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.sys.models.Sys_user;
@@ -23,6 +24,8 @@ import org.nutz.dao.FieldFilter;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
@@ -120,6 +123,7 @@ public class SysRoleController {
@Ok("json:full")
@SaCheckPermission("sys.manager.role")
public Object pageData(@Param("searchUnit") String searchUnit,
@Param("moduleId") String moduleId,
@Param("searchName") String searchName,
@Param("searchKeyword") String searchKeyword,
@Param("searchName2") String searchName2,
@@ -130,9 +134,10 @@ public class SysRoleController {
try {
Sql sql = Sqls.create("""
SELECT
role.*
role.*, module.name AS moduleName
FROM
sys_role role
LEFT JOIN sys_module module ON module.id = role.moduleId
LEFT JOIN sys_user_role ur ON role.id = ur.roleId
LEFT JOIN sys_user u ON u.id = ur.userId
$condition
@@ -163,6 +168,9 @@ public class SysRoleController {
if (StrUtil.isAllNotBlank(searchName2, searchKeyword2)) {
cnd.and("u." + searchName2, "like", "%" + searchKeyword2 + "%");
}
if (StrUtil.isNotBlank(moduleId)) {
cnd.and("role.moduleId", "=", moduleId);
}
if (Strings.isNotBlank(pageOrderName) && Strings.isNotBlank(pageOrderBy)) {
cnd.orderBy(pageOrderName, PageUtil.getOrder(pageOrderBy));
} else {
@@ -287,8 +295,13 @@ public class SysRoleController {
@Ok("json")
@SaCheckPermission("sys.manager.role.add")
@SLog(tag = "添加角色", msg = "角色名称:${args[1].name}")
@Aop(TransAop.READ_COMMITTED)
public Object addDo(@Param("..") Sys_role role, HttpServletRequest req) {
try {
// 所属模块只允许选择已配置的 PC 端模块,避免角色关联无效或 H5 模块。
if (!isPcModule(role.getModuleId())) {
return Result.error("请选择所属模块");
}
int num = sysRoleService.count(Cnd.where("code", "=", role.getCode().trim()));
if (num > 0) {
return Result.error("角色编码已存在");
@@ -360,8 +373,13 @@ public class SysRoleController {
@Ok("json")
@SaCheckPermission("sys.manager.role.edit")
@SLog(tag = "修改角色", msg = "角色名称:${args[0].name}")
@Aop(TransAop.READ_COMMITTED)
public Object editDo(@Param("..") Sys_role role, HttpServletRequest req) {
try {
// 编辑时同样校验模块,保证历史角色补录后始终关联有效 PC 模块。
if (!isPcModule(role.getModuleId())) {
return Result.error("请选择所属模块");
}
Sys_role oldRole = sysRoleService.fetch(role.getId());
if (oldRole != null && !Strings.sBlank(oldRole.getCode()).equalsIgnoreCase(role.getCode())) {
int num = sysRoleService.count(Cnd.where("code", "=", role.getCode().trim()));
@@ -503,6 +521,35 @@ public class SysRoleController {
cnd.and(Cnd.exps("parentId", "is", null).or("parentId", "=", ""));
return Result.success(Daos.ext(sysRoleService.dao(), fieldFilter).query(Sys_menu.class, cnd));
}
/**
* 查询角色可选择的 PC 端模块。
*
* @return PC 端模块列表,按模块排序编号升序返回,元素包含模块 ID 和模块名称
*/
@At
@Ok("json:full")
@SaCheckPermission("sys.manager.role")
public Result listModule() {
List<Sys_module> modules = sysRoleService.dao().query(Sys_module.class,
Cnd.where(Sys_module::getPlatform, "=", "PC").asc(Sys_module::getSortNum));
return Result.success(modules);
}
/**
* 校验所属模块是否存在且属于 PC 平台。
*
* @param moduleId 模块主键,必须来自 sys_module 的 PC 端模块记录
* @return 模块有效时返回 true,否则返回 false
*/
private boolean isPcModule(String moduleId) {
if (StrUtil.isBlank(moduleId)) {
return false;
}
Sys_module module = sysRoleService.dao().fetch(Sys_module.class, moduleId);
return module != null && "PC".equals(module.getPlatform());
}
@At
@Ok("json:full")
@SaCheckLogin
@@ -60,7 +60,7 @@ public class Sys_log extends BaseModel implements Serializable {
@Column
@Comment("请求结果")
@ColDefine(type = ColType.TEXT)
@ColDefine(customType = "MEDIUMTEXT")
private String param;
@Column
@@ -45,6 +45,11 @@ public class Sys_role extends BaseModel implements Serializable {
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unitid;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("所属模块ID")
private String moduleId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 255)
private String note;
@@ -133,10 +133,20 @@ public class ActivityBasicScopeUserDataController {
/**
* 删除一条记录
* 删除活动分组人员。
* id 有值时删除单个人员;id 为空时按当前页面的姓名/工号、工会、单位、人员类别和在职状态筛选后批量删除。
* groupId 为待删除人员所在的活动分组;existsLoginNameRedisKey 为导入核对名单生成的工号筛选缓存键。
* 返回 Result,code 为 0 表示删除处理成功。
*
* @param id
* @return
* @param id 单个人员 ID;批量删除时传空
* @param groupId 当前活动分组 ID
* @param searchKeyword 姓名或工号关键字
* @param unionId 所属工会 ID
* @param unitId 所属单位 ID
* @param personType 教职工类别
* @param userState 在职状态
* @param existsLoginNameRedisKey 导入核对名单的 Redis 缓存键
* @return 删除结果
*/
@At
@SLog(tag = "活动人员查询", msg = "删除活动人员")
@@ -145,7 +155,6 @@ public class ActivityBasicScopeUserDataController {
public Result doDelete(@Param(value = "id") String id,
@Param(value = "groupId") Integer groupId,
@Param(value = "searchKeyword") String searchKeyword,
@Param(value = "searchName") String searchName,
@Param(value = "unionId") String unionId,
@Param(value = "unitId") String unitId,
@Param(value = "personType") String personType,
@@ -160,7 +169,7 @@ public class ActivityBasicScopeUserDataController {
}
}
List<String> targetUserIds = queryGroupUserIds(groupId, id, searchKeyword, searchName, unionId, unitId, personType, userState, existsLoginNames);
List<String> targetUserIds = queryGroupUserIds(groupId, id, searchKeyword, unionId, unitId, personType, userState, existsLoginNames);
if (Lang.isEmpty(targetUserIds)) {
return Result.success();
}
@@ -282,9 +291,10 @@ public class ActivityBasicScopeUserDataController {
}
/**
* 删除时需要先按页面当前筛选条件圈定目标人员,再根据分组类型删除对应存储表中的快照数据。
* 删除时需要先按页面当前筛选条件圈定目标人员。姓名或工号关键字必须与列表查询使用相同的匹配规则,
* 再根据分组类型删除对应存储表中的快照数据。
*/
private List<String> queryGroupUserIds(Integer groupId, String id, String searchKeyword, String searchName,
private List<String> queryGroupUserIds(Integer groupId, String id, String searchKeyword,
String unionId, String unitId, String personType, String userState,
List<String> existsLoginNames) {
Sql sql = Sqls.queryString("""
@@ -296,8 +306,11 @@ public class ActivityBasicScopeUserDataController {
""");
Cnd cnd = Cnd.NEW();
cnd.and("u.id", "in", activityBasicScopeService.buildGroupUserIdSubSql(groupId));
if (StrUtil.isNotBlank(searchName) && StrUtil.isNotBlank(searchKeyword)) {
cnd.and(Cnd.likeEX(searchName, searchKeyword));
if (StrUtil.isNotBlank(searchKeyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("u.username", searchKeyword);
group.orLike("u.loginname", searchKeyword);
cnd.and(group);
}
cnd.andEX("u.unionid", "=", unionId);
cnd.andEX("u.unitid", "=", unitId);
@@ -49,6 +49,7 @@ public class ArticleQueryController {
info.loginName,
info.unitName,
info.unionName,
info.clubName,
info.submitTime,
info.origin,
ins.id AS instanceId,
@@ -5,6 +5,7 @@ import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
@@ -153,7 +154,7 @@ public class AssetManageController {
@At
@ApiOperation("查询责任人")
@SaCheckPermission("asset.manage")
@SaCheckPermission(value = {"asset.manage", "asset.stocktaking", "h5.asset.stocktaking"}, mode = SaMode.OR)
public Result searchAssetUseUser(String query) {
Cnd cnd = Cnd.NEW();
cnd.where().orLike("username", query);
@@ -4,12 +4,15 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.cadreTraining.model.CadreTrainingAct;
import com.budwk.app.zhgh.dayofficework.cadreTraining.model.CadreTrainingSignUp;
import com.budwk.app.zhgh.dayofficework.cadreTraining.service.CadreTrainingActService;
import com.budwk.app.zhgh.dayofficework.cadreTraining.service.CadreTrainingSignUpService;
import io.swagger.annotations.Api;
@@ -29,6 +32,7 @@ import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import java.util.List;
/**
* @author : hongqiwei
@@ -112,6 +116,33 @@ public class CadreTrainingMineController {
return Result.success(pagination);
}
/**
* 查询“我的报名”查看页的报名人员。
* 管理角色按活动和分工会查看全部报名人员;普通用户仅返回本人记录,避免扩大人员信息可见范围。
*
* @param activityId 培训活动 ID,对应 cadre_training 表主键
* @param unionId 当前查看报名记录所属的分工会 ID
* @return 报名记录列表,元素包含人员姓名、工号、单位、分工会、联系方式及备注
*/
@At
@ApiOperation("查询我的报名活动人员")
@SaCheckPermission(value = {"cadreTraining.mine", "h5.cadreTraining.mine"}, mode = SaMode.OR)
public Result listSignUsers(@Valid String activityId, String unionId) {
Cnd cnd = Cnd.where(CadreTrainingSignUp::getCadreTrainingActId, "=", activityId);
if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name(), RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
// 管理角色必须携带当前报名记录所属的分工会,防止空参数绕过分工会范围。
if (StrUtil.isBlank(unionId)) {
return Result.error("分工会信息不能为空");
}
cnd.and(CadreTrainingSignUp::getUnionId, "=", unionId);
} else {
cnd.and(CadreTrainingSignUp::getUserId, "=", SecurityUtil.getUserId());
}
cnd.asc(CadreTrainingSignUp::getApplyTime);
List<CadreTrainingSignUp> signUsers = dao.query(CadreTrainingSignUp.class, cnd);
return Result.success(signUsers);
}
@At
@ApiOperation("删除")
@Aop(TransAop.READ_COMMITTED)
@@ -141,7 +141,7 @@ public class GhkhKhphController {
List<ExcelExportEntity> exportEntities = new ArrayList<>();
exportEntities.add(new ExcelExportEntity("序号", "index", 10));
exportEntities.add(new ExcelExportEntity("年度", "annual", 10));
exportEntities.add(new ExcelExportEntity("分工会名称", "unionName", 30));
exportEntities.add(new ExcelExportEntity("分工会名称", "unionname", 30));
exportEntities.add(new ExcelExportEntity("线上得分", "xgh_score", 20));
exportEntities.add(new ExcelExportEntity("线上最终得分" + (config.getOnLine() != null ? "" + config.getOnLine() + "%" : ""), "onLineFinalScore", 20));
exportEntities.add(new ExcelExportEntity("线下得分", "offLineScore", 20));
@@ -3,11 +3,13 @@ package com.budwk.app.zhgh.dayofficework.honor.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
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.result.Result;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.sys.services.SysUnionService;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.honor.models.Honor;
import com.budwk.app.zhgh.dayofficework.honor.service.HonorCommonService;
@@ -90,7 +92,10 @@ public class HonorRegistrationController {
seg.orLike("t1.username",keyWord);
cnd.and(seg);
}
cnd.and("t1.unionId","=", SecurityUtil.getUnionId());
// 系统管理员可跨分工会查询人员,其他角色仍限定为当前所属分工会。
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
cnd.and("t1.unionId", "=", SecurityUtil.getUnionId());
}
sql.setCondition(cnd);
Pagination pagination = sysUserService.listPageMap(1, 50, sql);
return Result.success(pagination.getList());
@@ -54,8 +54,8 @@ public class HonorSummaryController {
@SaCheckPermission("honor.honorLevel.Summary")
public Result unionData(HonorPageForm pageForm) {
List<HonorBasicSettings> query = dao.query(HonorBasicSettings.class, Cnd.where("queryTypeCode", "in", List.of(HonorTypeOrigin.HONOR_SINGLE.name(), HonorTypeOrigin.HONOR_LIST.name())));
String listHonorId = query.stream().filter(v->v.getQueryTypeCode().equals(HonorTypeOrigin.HONOR_LIST.name())).findFirst().orElse(new HonorBasicSettings()).getId();
String singleHonorId = query.stream().filter(v->v.getQueryTypeCode().equals(HonorTypeOrigin.HONOR_SINGLE.name())).findFirst().orElse(new HonorBasicSettings()).getId();
String personalHonorId = query.stream().filter(v->v.getQueryTypeCode().equals(HonorTypeOrigin.HONOR_SINGLE.name())).findFirst().orElse(new HonorBasicSettings()).getId();
String collectiveHonorId = query.stream().filter(v->v.getQueryTypeCode().equals(HonorTypeOrigin.HONOR_LIST.name())).findFirst().orElse(new HonorBasicSettings()).getId();
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
SELECT
@@ -65,7 +65,7 @@ public class HonorSummaryController {
( SELECT COUNT( 1 ) FROM honor h WHERE h.honorType = @collectiveHonor AND h.unionId = un.id $cnd) collectiveHonorNum
FROM
sys_union un $condition
""").setParam("personalHonor", listHonorId).setParam("collectiveHonor", singleHonorId);
""").setParam("personalHonor", personalHonorId).setParam("collectiveHonor", collectiveHonorId);
cnd.andEX("id", "=", pageForm.getUnionId());
if (pageForm.getYear() != null) {
sql.setVar("cnd", "and YEAR(grantDate)=" + pageForm.getYear());
@@ -3,11 +3,14 @@ package com.budwk.app.zhgh.dayofficework.meeting.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.RepeatSubmit;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.sys.models.Sys_module;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.zhgh.dayofficework.meeting.model.MeetingInfo;
import com.budwk.app.zhgh.dayofficework.meeting.model.MeetingTimePeriod;
import com.budwk.app.zhgh.dayofficework.meeting.model.MeetingTimePeriodUser;
@@ -101,6 +104,7 @@ public class MeetingManageController {
@At
@ApiOperation("新增/修改会议")
@Aop(TransAop.READ_COMMITTED)
@RepeatSubmit
@SaCheckPermission("meeting.manage")
@SLog(tag = "会议管理系统-会议管理", msg = "新增/修改会议")
public Object submit(@Param("data") MeetingInfo info,
@@ -161,6 +165,10 @@ public class MeetingManageController {
if (StrUtil.isNotBlank(roleId)) {
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s')".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)));
}
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)));
}
@@ -225,4 +233,40 @@ public class MeetingManageController {
List<Worker_congress_session> list = dao.query(Worker_congress_session.class, Cnd.where(Teacher_congress_session::getEnable, "=", true).desc("year"));
return Result.success(list);
}
/**
* 查询会议选择用户时可筛选的 PC 端模块。
*
* @return 模块列表,元素包含模块 ID、名称和平台,按排序编号升序返回
*/
@At
@ApiOperation("查询 PC 端模块")
@SaCheckPermission("meeting.manage")
public Result listModule() {
List<Sys_module> modules = dao.query(Sys_module.class,
Cnd.where(Sys_module::getPlatform, "=", "PC").asc(Sys_module::getSortNum));
return Result.success(modules);
}
/**
* 按模块查询会议选择用户时可筛选的启用角色。
*
* @param moduleId PC 端模块 ID,来源于 sys_module 表
* @return 角色列表,元素包含角色 ID、名称和角色标识,未传模块时返回空列表
*/
@At
@ApiOperation("按模块查询角色")
@SaCheckPermission("meeting.manage")
public Result listRole(String moduleId) {
if (StrUtil.isBlank(moduleId)) {
return Result.success(List.of());
}
Sys_module module = dao.fetch(Sys_module.class, moduleId);
if (module == null || !"PC".equals(module.getPlatform())) {
return Result.success(List.of());
}
List<Sys_role> roles = dao.query(Sys_role.class,
Cnd.where(Sys_role::getModuleId, "=", moduleId).and(Sys_role::isDisabled, "=", false).asc(Sys_role::getSort));
return Result.success(roles);
}
}
@@ -93,6 +93,10 @@ public class MeetingMineController {
List<MeetingTimePeriodUser> periodUsers = dao.query(MeetingTimePeriodUser.class, Cnd.where(MeetingTimePeriodUser::getUserId, "=", SecurityUtil.getUserId()));
List<String> idList = periodUsers.stream().map(MeetingTimePeriodUser::getMeetingId).toList();
// 当前用户未被安排参会时,直接返回空分页,避免生成空 IN 条件导致数据库语法错误。
if (idList.isEmpty()) {
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), 0, List.of()));
}
cnd.and("id", "in", idList);
cnd.andEX("typeId", "=", type);
@@ -217,6 +221,14 @@ public class MeetingMineController {
@Aop(TransAop.READ_COMMITTED)
@SLog(tag = "会务管理-我的会议-签到", msg = "会议签到")
public Result sign(@Valid String periodId) {
MeetingTimePeriod timePeriod = dao.fetch(MeetingTimePeriod.class, periodId);
if (timePeriod == null) {
return Result.error("会议场次不存在!");
}
// 以服务端时间校验场次开始时间,避免客户端时间或页面状态被绕过。
if (timePeriod.getStartTime() == null || DateUtil.date().before(timePeriod.getStartTime())) {
return Result.error("会议尚未开始,暂不能签到!");
}
MeetingTimePeriodUser user = dao.fetch(
MeetingTimePeriodUser.class,
Cnd.where(MeetingTimePeriodUser::getTimePeriodId, "=", periodId)
@@ -19,6 +19,7 @@ import org.nutz.lang.Lang;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -44,11 +45,12 @@ public class MeetingInfoServiceImpl extends BaseServiceImpl<MeetingInfo> impleme
// 插入会议表和会议场次表
info.setCreateTime(DateUtil.now());
insertWith(info, "timePeriods");
List<MeetingTimePeriodUser> uniqueUsers = distinctUsers(users);
// 插入参与人员表
if (Lang.isNotEmpty(info.getTimePeriods()) && Lang.isNotEmpty(users)) {
if (Lang.isNotEmpty(info.getTimePeriods()) && Lang.isNotEmpty(uniqueUsers)) {
List<MeetingTimePeriodUser> relations = info.getTimePeriods()
.stream()
.flatMap(period -> Arrays.stream(users).map(user -> buildRelation(info.getId(), period.getId(), user)))
.flatMap(period -> uniqueUsers.stream().map(user -> buildRelation(info.getId(), period.getId(), user)))
.toList();
dao().insert(relations);
}
@@ -58,6 +60,7 @@ public class MeetingInfoServiceImpl extends BaseServiceImpl<MeetingInfo> impleme
@Aop(TransAop.READ_COMMITTED)
public void edit(MeetingInfo info, MeetingTimePeriodUser[] users) {
String meetingId = info.getId();
List<MeetingTimePeriodUser> uniqueUsers = distinctUsers(users);
// 设置外键并保存场次(含新增和修改)
info.getTimePeriods().forEach(p -> p.setMeetingId(meetingId));
@@ -79,7 +82,7 @@ public class MeetingInfoServiceImpl extends BaseServiceImpl<MeetingInfo> impleme
.collect(Collectors.toSet());
// 页面传过来的新人员
Set<String> newUserIds = Arrays.stream(users).map(MeetingTimePeriodUser::getUserId).collect(Collectors.toSet());
Set<String> newUserIds = uniqueUsers.stream().map(MeetingTimePeriodUser::getUserId).collect(Collectors.toSet());
// 删除废弃场次及其所有关联
Set<String> deletedPeriodIds = Sets.difference(oldPeriodIds, newPeriodIds);
@@ -99,7 +102,7 @@ public class MeetingInfoServiceImpl extends BaseServiceImpl<MeetingInfo> impleme
List<MeetingTimePeriodUser> newRelations = new ArrayList<>();
// 新增的用户 → 所有现存场次
List<MeetingTimePeriodUser> newUsers = Arrays.stream(users).filter(u -> StrUtil.isBlank(u.getId())).toList();
List<MeetingTimePeriodUser> newUsers = uniqueUsers.stream().filter(u -> StrUtil.isBlank(u.getId())).toList();
for (String periodId : newPeriodIds) {
for (MeetingTimePeriodUser user : newUsers) {
newRelations.add(buildRelation(meetingId, periodId, user));
@@ -109,7 +112,7 @@ public class MeetingInfoServiceImpl extends BaseServiceImpl<MeetingInfo> impleme
// 新增的场次 → 所有现存老用户(避免重复插入新用户)
Set<String> addedPeriodIds = Sets.difference(newPeriodIds, oldPeriodIds);
for (String periodId : addedPeriodIds) {
for (MeetingTimePeriodUser user : users) {
for (MeetingTimePeriodUser user : uniqueUsers) {
if (StrUtil.isBlank(user.getId())) continue;
newRelations.add(buildRelation(meetingId, periodId, user));
}
@@ -131,4 +134,21 @@ public class MeetingInfoServiceImpl extends BaseServiceImpl<MeetingInfo> impleme
rel.setJoinStatus(true);
return rel;
}
/**
* 按用户 ID 去重会议参会人员,保留首次选择的人员信息。
* 同一用户在一个会议的每个场次只允许生成一条参会关系,防止重复提交或重复选择产生重复记录。
*
* @param users 前端提交的参会人员数组,每项必须包含 userId
* @return 去重后的参会人员列表,userId 为空的无效项不会参与保存
*/
private List<MeetingTimePeriodUser> distinctUsers(MeetingTimePeriodUser[] users) {
if (users == null || users.length == 0) {
return List.of();
}
return new ArrayList<>(Arrays.stream(users)
.filter(user -> StrUtil.isNotBlank(user.getUserId()))
.collect(Collectors.toMap(MeetingTimePeriodUser::getUserId, user -> user, (first, duplicate) -> first, LinkedHashMap::new))
.values());
}
}
@@ -9,7 +9,7 @@ import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvOption;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
@@ -55,6 +55,8 @@ public class QsvOnlineController {
private QsvUserAnswerRecordService qsvUserAnswerRecordService;
@Inject
private QsvQuizService qsvQuizService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/qsv/online/index.html")
@@ -63,27 +65,41 @@ public class QsvOnlineController {
}
/**
* 查询当前用户可参与的在线问卷、答题和投票活动。
* pageForm 传入页码、每页数量与搜索关键字;year 为活动开始年份;title 为标题关键字;
* isAnswered 用于筛选已答或未答活动。返回 Pagination,列表数据仅包含当前用户命中活动分组的活动。
*
* @param pageForm 分页及搜索参数
* @param year 活动开始年份
* @param title 活动标题关键字
* @param isAnswered 是否只查看已答活动
* @return 当前用户可参与的在线活动分页结果
*/
@At
@SaCheckPermission("qsv.online")
@ApiOperation("在线答题任务分页")
public Result pageData(@Valid PageForm pageForm, Integer year, String title, Boolean isAnswered) {
List<Integer> groupIds = dao.query(ActivityUserScope.class, Cnd.where("userId", "=", SecurityUtil.getUserId()))
.stream().map(ActivityUserScope::getGroupId).collect(Collectors.toList());
if (groupIds.isEmpty()) {
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), 0, new ArrayList<>()));
}
Cnd cnd = Cnd.where("enabled", "=", true);
cnd.and("groupId", "in", groupIds);
cnd.andEX("YEAR(startTime)", "=", year);
cnd.and(Cnd.likeEX("title", title));
cnd.desc("startTime");
int totalCount = dao.count(QsvActivity.class, cnd);
if (totalCount == 0) {
List<QsvActivity> activities = dao.query(QsvActivity.class, cnd);
if (activities.isEmpty()) {
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), 0, new ArrayList<>()));
}
List<QsvActivity> activities = dao.query(QsvActivity.class, cnd);
// SQL条件分组不保存 userId 明细,统一通过分组服务动态判断当前用户是否命中活动分组。
List<Integer> visibleGroupIds = activityBasicScopeService.filterGroupIdsByUser(
activities.stream().map(QsvActivity::getGroupId).collect(Collectors.toList()), SecurityUtil.getUserId());
activities = activities.stream()
.filter(activity -> visibleGroupIds.contains(activity.getGroupId()))
.collect(Collectors.toList());
if (activities.isEmpty()) {
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), 0, new ArrayList<>()));
}
List<String> activityIds = activities.stream().map(QsvActivity::getId).collect(Collectors.toList());
Map<String, List<QsvUserAnswerRecord>> recordGroup = dao.query(QsvUserAnswerRecord.class, Cnd.where("userId", "=", SecurityUtil.getUserId())
.and("activityId", "in", activityIds))
@@ -248,9 +264,7 @@ public class QsvOnlineController {
}
private Result checkUserScope(QsvActivity activity) {
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
.and("userId", "=", SecurityUtil.getUserId()));
if (count != 1) {
if (!activityBasicScopeService.isUserInGroup(activity.getGroupId(), SecurityUtil.getUserId())) {
return Result.error("您无需参加此次调查,感谢您的关注!");
}
return null;
@@ -8,7 +8,7 @@ import cn.hutool.json.JSONObject;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.dayofficework.qsv.dto.QsvCheckAnswerResult;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
@@ -46,6 +46,8 @@ public class H5QsvQuizController {
private QsvUserAnswerRecordService qsvUserAnswerRecordService;
@Inject
private QsvQuizService qsvQuizService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@At("")
@SaCheckLogin
@@ -61,18 +63,24 @@ public class H5QsvQuizController {
}
/**
* 获取移动端答题活动的题目和当前用户答题记录。
* activityId 传入 qsv_activity 表中的活动 ID;返回 Result,data 包含活动信息、题目列表和答题记录 ID。
* 进入前会校验当前登录用户是否命中活动分组,结果分组和 SQL 条件分组均按统一规则判断。
*
* @param activityId 答题活动 ID
* @return 包含答题题目及答题记录信息的结果
*/
@At
@SaCheckLogin
@Aop(TransAop.READ_COMMITTED)
public Result subjects(@Valid String activityId) {
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
if (activity.getGroupId() != null) {
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
.and("userId", "=", SecurityUtil.getUserId()));
if (count != 1) {
return Result.error("您无需参加此次答题,感谢您的关注!");
}
// SQL条件分组不保存 userId 明细,统一通过分组服务动态判断当前用户是否属于活动范围。
if (activity.getGroupId() != null
&& !activityBasicScopeService.isUserInGroup(activity.getGroupId(), SecurityUtil.getUserId())) {
return Result.error("您无需参加此次答题,感谢您的关注!");
}
String mode = activity.getMode();
@@ -4,7 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord;
@@ -39,6 +39,8 @@ public class H5QsvSurveyController {
private Dao dao;
@Inject
private QsvUserAnswerRecordService qsvUserAnswerRecordService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@At("")
@Ok("beetl:/platform/zhghh5/dayofficework/qsv/survey/index.html")
@@ -54,10 +56,10 @@ public class H5QsvSurveyController {
return activityStatusResult;
}
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
.and("userId", "=", SecurityUtil.getUserId()));
if (count != 1) {
return Result.error("您无需参加此次投票,感谢您的关注!");
// SQL 条件分组不保存用户明细,需由分组服务实时判断当前用户是否命中活动范围。
if (activity.getGroupId() != null
&& !activityBasicScopeService.isUserInGroup(activity.getGroupId(), SecurityUtil.getUserId())) {
return Result.error("您无需参加此次调查,感谢您的关注!");
}
String answerRecordId = null;
@@ -4,7 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvOption;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
@@ -41,6 +41,8 @@ public class H5QsvVoteController {
private Dao dao;
@Inject
private QsvUserAnswerRecordService qsvUserAnswerRecordService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@At("")
@Ok("beetl:/platform/zhghh5/dayofficework/qsv/vote/index.html")
@@ -172,9 +174,8 @@ public class H5QsvVoteController {
if (activity.getGroupId() == null) {
return null;
}
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
.and("userId", "=", SecurityUtil.getUserId()));
if (count != 1) {
// SQL条件分组不保存 userId 明细,统一通过分组服务动态判断当前用户是否属于投票范围。
if (!activityBasicScopeService.isUserInGroup(activity.getGroupId(), SecurityUtil.getUserId())) {
return Result.error("\u60a8\u65e0\u9700\u53c2\u52a0\u6b64\u6b21\u6295\u7968\uff0c\u611f\u8c22\u60a8\u7684\u5173\u6ce8\uff01");
}
return null;
@@ -7,6 +7,8 @@ import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.democratic.teachercongress.guildhall.models.GuildHall;
import com.budwk.app.zhgh.democratic.teachercongress.guildhall.models.GuildHallMeeting;
import com.budwk.app.zhgh.democratic.teachercongress.guildhall.models.GuildHallSeat;
@@ -40,6 +42,9 @@ public class GuildHallMeetingController {
@Inject
private GuildHallMeetingService guildHallMeetingService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@At("")
@SaCheckPermission("guildHall.meeting")
@Ok("beetl:/platform/zhgh/democratic/teachercongress/guildhall/meeting/index.html")
@@ -109,6 +114,11 @@ public class GuildHallMeetingController {
if(DateUtil.compare(DateUtil.date(), meeting.getEndTime()) > 0) {
continue;
}
// 配置参与组别后,仅向组内用户返回可选会议;未配置组别时默认全员可见。
if (meeting.getGroupId() != null
&& !activityBasicScopeService.isUserInGroup(meeting.getGroupId(), SecurityUtil.getUserId())) {
continue;
}
NutMap nutMap = Lang.obj2nutmap(meeting);
GuildHall hall = dao.fetch(GuildHall.class, meeting.getHallId());
nutMap.put("address", hall.getAddress());
@@ -3,13 +3,16 @@ package com.budwk.app.zhgh.democratic.teachercongress.guildhall.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.democratic.teachercongress.guildhall.models.GuildHallMeeting;
import com.budwk.app.zhgh.democratic.teachercongress.guildhall.models.GuildHallSeat;
import com.budwk.app.zhgh.democratic.teachercongress.guildhall.models.GuildHallSeatSelect;
import com.budwk.app.zhgh.democratic.teachercongress.guildhall.service.GuildHallService;
import io.swagger.annotations.Api;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
@@ -36,6 +39,8 @@ public class GuildHallSelectSeatController {
private Dao dao;
@Inject
private GuildHallService guildHallService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@At("")
@SaCheckPermission("guildHall.selectSeat")
@@ -52,6 +57,11 @@ public class GuildHallSelectSeatController {
@At
@SaCheckPermission("guildHall.selectSeat")
public Result loadSeats(@Param("meetingId") String meetingId) {
Result permissionResult = checkMeetingPermission(meetingId);
if (permissionResult != null) {
return permissionResult;
}
// 已选择情况
List<GuildHallSeatSelect> selectedSeats = dao.query(GuildHallSeatSelect.class, Cnd.where("meetingId", "=", meetingId));
@@ -79,7 +89,13 @@ public class GuildHallSelectSeatController {
@At
@SaCheckPermission("guildHall.selectSeat")
@Aop(TransAop.READ_COMMITTED)
public Result doSelect(@Param("meetingId") String meetingId, Integer rowNumber, Integer colNumber) {
Result permissionResult = checkMeetingPermission(meetingId);
if (permissionResult != null) {
return permissionResult;
}
GuildHallSeatSelect oldSeat = dao.fetch(GuildHallSeatSelect.class, Cnd.where("meetingId", "=", meetingId).and("userId", "=", SecurityUtil.getUserId()));
if(oldSeat != null){
oldSeat.setUserId(null);
@@ -94,4 +110,22 @@ public class GuildHallSelectSeatController {
return Result.success();
}
/**
* 校验当前用户是否可参与指定会议;未配置参与组别的会议默认允许选座。
*
* @param meetingId 会议主键,用于查询会议的参与组别配置
* @return 校验通过时返回 {@code null};会议不存在或用户不在参与组别时返回错误结果
*/
private Result checkMeetingPermission(String meetingId) {
GuildHallMeeting meeting = dao.fetch(GuildHallMeeting.class, meetingId);
if (meeting == null) {
return Result.error("会议不存在或已删除");
}
if (meeting.getGroupId() != null
&& !activityBasicScopeService.isUserInGroup(meeting.getGroupId(), SecurityUtil.getUserId())) {
return Result.error("您不在本次会议参与组别内");
}
return null;
}
}
@@ -87,6 +87,7 @@ public class ThirtyTeachController {
@ApiOperation("一键设置三十龄教工")
@SaCheckPermission("thirtyTeach.manage")
@SLog(type = "30龄教工-教职工管理", tag = "设置三十龄教工",msg = "设置三十龄教工设置教龄${args[0]}")
@Aop(TransAop.READ_COMMITTED)
public Result setThirtyTeach(Integer setNum, boolean isFlag){
if (isFlag) {
sysUserService.update(Chain.make("isThirtyTeach", 0), Cnd.NEW());
@@ -95,6 +96,8 @@ public class ThirtyTeachController {
cnd.and("arrivalAtSchoolDate", "is not", null);
cnd.and("arrivalAtSchoolDate", "!=", "");
cnd.and(TEACH_NUM_SQL, ">", setNum);
// 一键设置只标记已确认会员,确保设置范围与管理列表保持一致。
cnd.and("member", "=", true);
sysUserService.update(Chain.make("isThirtyTeach", 1), cnd);
return Result.success();
}
@@ -55,6 +55,8 @@ public class ThirtyTeachServiceImpl extends BaseServiceImpl implements ThirtyTea
sql.setVar("teachNumSql", new Static(TEACH_NUM_SQL));
Cnd cnd = Cnd.NEW();
cnd.and("isThirtyTeach", "=", 1);
// 三十年教龄名单仅展示已确认会员,避免非会员进入管理及导出范围。
cnd.and("member", "=", true);
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orLike("loginname", pageForm.getSearchKeyword());
@@ -114,7 +114,7 @@
</div>
<div class="search-query">
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button type="primary" icon="el-icon-search" :loading="tableLoading" @click="doSearch">搜索</el-button>
</div>
</div>
</el-card>
@@ -133,7 +133,8 @@
删除{{ currentGroupName }}
</el-button>
</table-tool>
<el-table ref="userTable" :data="tableData" stripe border :size="tableSize" @sort-change="pageOrder">
<el-table ref="userTable" v-loading="tableLoading" element-loading-text="正在查询,请稍候..."
:data="tableData" stripe border :size="tableSize" @sort-change="pageOrder">
<el-table-column label="序号" type="index" width="80">
<template slot-scope="scope">
<span>{{ scope.$index + (pageForm.pageNumber - 1) * pageForm.pageSize + 1 }}</span>
@@ -334,12 +335,19 @@ module.exports = {
const {data} = await $.post("/platform/activity/basic/scope/getRolesAndUnion")
this.roleData = data
},
async pageData() {
const resp = await $.post("/platform/activity/basic/user/pageData", this.pageForm)
if (resp.code === 0) {
this.tableData = resp.data.list
this.pageForm.totalCount = resp.data.totalCount
}
pageData() {
// 分页查询期间展示加载状态,避免慢查询时用户误以为页面无响应。
this.tableLoading = true
$.post("/platform/activity/basic/user/pageData", this.pageForm)
.then((resp) => {
if (resp.code === 0) {
this.tableData = resp.data.list
this.pageForm.totalCount = resp.data.totalCount
}
})
.always(() => {
this.tableLoading = false
})
}
},
async created() {
@@ -360,13 +360,15 @@
v-for="item in sameTypeGroupList"
:key="item.groupId"
:label="item.groupId"
:disabled="!isSelectedActivityGroup(item)"
border
class="existing-group-radio"
>
{{ formatGroupDisplayName(item) }}
</el-radio>
</el-radio-group>
<div v-if="sameTypeGroupList.length === 0" class="form-tip">当前还没有同类型分组请先创建新分组</div>
<div v-if="!selectedActivityGroup" class="form-tip">请先在筛选条件中选择活动组别</div>
<div v-else class="form-tip">仅允许添加至当前选择的活动组别{{ formatGroupDisplayName(selectedActivityGroup) }}</div>
</el-form-item>
</el-row>
@@ -499,6 +501,9 @@ module.exports = {
unionid() {
return this.roleData.unionid
},
selectedActivityGroup() {
return this.activityGroupList.find((item) => Number(item.groupId) === Number(this.pageForm.activityGroupId))
},
sameTypeGroupList() {
return this.activityGroupList.filter((item) => Number(item.groupType) === Number(this.formData.groupType))
}
@@ -549,7 +554,11 @@ module.exports = {
handleSetGroupModeChange() {
if (this.formData.setGroupType === 1) {
this.formData.setGroupName = ""
if (!this.sameTypeGroupList.some((item) => item.groupId === this.formData.setGroupId)) {
if (this.selectedActivityGroup) {
// 使
this.formData.groupType = Number(this.selectedActivityGroup.groupType)
this.formData.setGroupId = this.selectedActivityGroup.groupId
} else {
this.formData.setGroupId = null
}
return
@@ -557,10 +566,18 @@ module.exports = {
this.formData.setGroupId = null
},
handleGroupTypeChange() {
if (this.formData.setGroupType === 1 && this.selectedActivityGroup) {
this.formData.groupType = Number(this.selectedActivityGroup.groupType)
this.formData.setGroupId = this.selectedActivityGroup.groupId
return
}
if (!this.sameTypeGroupList.some((item) => item.groupId === this.formData.setGroupId)) {
this.formData.setGroupId = null
}
},
isSelectedActivityGroup(group) {
return this.selectedActivityGroup && Number(group.groupId) === Number(this.selectedActivityGroup.groupId)
},
handleActivityGroupChange(val) {
this.pageForm.memberTypes = val ? [] : ["工会会员"]
this.doSearch()
@@ -207,7 +207,8 @@
<el-table-column v-if="!is_view" label="操作" width="100px">
<template v-slot="{row,$index}">
<el-button size="mini" :disabled="nr.bzs.length<=1 || (!row.canDelete && row.id !== null)" type="danger" icon="el-icon-delete"
<!-- 新建行没有 id 时允许删除已保存行仍按 canDelete 控制删除权限 -->
<el-button size="mini" :disabled="nr.bzs.length<=1 || (!row.canDelete && row.id)" type="danger" icon="el-icon-delete"
@click="nr.bzs.splice($index,1)"></el-button>
</template>
</el-table-column>
@@ -329,7 +329,8 @@
},
actions: {}
})
$.get("/platform/sys/user/getLogonUser").then((res) => {
// 暴露当前登录用户的加载状态,业务页面可在用户、角色和所属工会写入 store 后再执行初始化。
window.logonUserReady = $.get("/platform/sys/user/getLogonUser").then((res) => {
if (res.code === 0) {
window.sessionStorage.setItem("user", JSON.stringify(res.data))
store.commit("setUser", res.data)
@@ -14,9 +14,9 @@ layout("/layouts/platform.html"){
<div id="app" v-cloak>
<el-card shadow="never">
<el-row type="flex" style="column-gap: 10px">
<el-select v-model="pageForm.system_name" style="width: 180px" filterable clearable
<el-select v-model="pageForm.moduleId" style="width: 180px" filterable clearable
placeholder="请选择所属模块">
<el-option v-for="item in menuOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-input placeholder="请输入内容" v-model="pageForm.searchKeyword" style="width: 300px"
@keyup.enter.native="doSearch">
@@ -75,15 +75,7 @@ layout("/layouts/platform.html"){
<el-table-column sortable prop="sort" label="排序编号" align="center"></el-table-column>
<el-table-column prop="code" sortable label="角色标识" align="center"
:show-overflow-tooltip="true"></el-table-column>
<!-- <el-table-column prop="menuName" label="所属模块" align="center" :show-overflow-tooltip="true" sortable>-->
<!-- <template slot-scope="scope">
<span v-if="scope.row.unit==null" style="font-weight: bold">
系统角色
</span>
<span v-if="scope.row.unit!=null">
{{scope.row.unit.name}}
</span>
</template>-->
<el-table-column prop="moduleName" label="所属模块" align="center" :show-overflow-tooltip="true">
</el-table-column>
<el-table-column sortable prop="disabled" label="启用状态" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">
@@ -135,11 +127,11 @@ layout("/layouts/platform.html"){
<el-dialog append-to-body title="新建角色" :visible.sync="addDialogVisible" :close-on-click-modal="false"
width="70%">
<el-form :model="formData" ref="addForm" :rules="formRules" size="small" label-width="80px">
<!-- <el-form-item class="is-required" prop="system_name" label="所属系统" label-width="80px">-->
<!-- <el-select v-model="formData.system_name" style="width: 100%" filterable clearable placeholder="请选择">-->
<!-- <el-option v-for="item in menuOptions" :key="item.name" :label="item.name" :value="item.name"></el-option>-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item prop="moduleId" label="所属模块">
<el-select v-model="formData.moduleId" style="width: 100%" filterable placeholder="请选择所属模块">
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="name" label="角色名称">
<el-input maxlength="50" placeholder="角色名称" v-model="formData.name" auto-complete="off" tabindex="2"
type="text"></el-input>
@@ -186,11 +178,11 @@ layout("/layouts/platform.html"){
<el-input maxlength="150" placeholder="角色标识" v-model="formData.code" auto-complete="off"
tabindex="3" type="text"></el-input>
</el-form-item>
<!-- <el-form-item prop="code" label="所属系统">-->
<!-- <el-select v-model="formData.system_name" style="width: 100%" filterable clearable placeholder="请选择">-->
<!-- <el-option v-for="item in menuOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item prop="moduleId" label="所属模块">
<el-select v-model="formData.moduleId" style="width: 100%" filterable placeholder="请选择所属模块">
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="sort" label="排序编号">
<el-input-number placeholder="排序编号" v-model="formData.sort"></el-input-number>
</el-form-item>
@@ -396,7 +388,7 @@ layout("/layouts/platform.html"){
isLeaf: "leaf"
},
tableData: [],
menuOptions: [],
moduleOptions: [],
options: [],
parentUnit: [],
parentUnitSearch: [],
@@ -406,6 +398,7 @@ layout("/layouts/platform.html"){
searchKeyword: "",
searchName2: "username",
searchKeyword2: "",
moduleId: "",
pageNumber: 1,
pageSize: 10,
totalCount: 0,
@@ -415,6 +408,7 @@ layout("/layouts/platform.html"){
formData: {
id: "",
unitid: "",
moduleId: "",
disabled: false,
menuIds: ""
},
@@ -424,7 +418,8 @@ layout("/layouts/platform.html"){
code: [
{required: true, message: "角色标识", trigger: ["blur", "change"]},
{validator: validateName, trigger: ["blur", "change"]}
]
],
moduleId: [{required: true, message: "所属模块必填", trigger: ["blur", "change"]}]
},
editRules: {
// parentUnit: [{ validator: validateUnit, trigger: ["blur", "change"] }],
@@ -580,6 +575,14 @@ layout("/layouts/platform.html"){
this.pageForm.pageNumber = 1
this.pageData()
},
// 加载可选 PC 模块,创建、编辑和列表筛选共用同一数据源。
loadModuleOptions() {
this.$axios.post("/platform/sys/role/listModule").then((res) => {
if (res.code === 0) {
this.$set(this, "moduleOptions", res.data)
}
})
},
addMenuLoad() {
this.$axios.post("/platform/sys/role/menuAll/", {}).then((res) => {
if (res.code === 0) {
@@ -896,7 +899,8 @@ layout("/layouts/platform.html"){
// this.menuOptions = data
// }
},
async created() {
created() {
this.loadModuleOptions()
this.pageData()
// await this.getMenuOptions()
}
@@ -60,7 +60,7 @@ layout("/layouts/platform.html"){
</el-button>
<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">
@@ -31,7 +31,7 @@ let ASSSET_STOCKTAKING_FORM = {
remote
style="width: 100%"
v-model="formData.assetUseUserId">
<el-option :label="item.username+'-'+item.loginname"
<el-option :label="item.username + (item.loginname ? '-' + item.loginname : '')"
:value="item.id"
:key="item.id"
v-for="item in assetUseUserOption"></el-option>
@@ -102,6 +102,12 @@ let ASSSET_STOCKTAKING_FORM = {
this.$set(this.formData, "assetName", row.assetName)
this.$set(this.formData, "assetUsedDate", row.assetUsedDate)
this.$set(this.formData, "assetStorageLocation", row.assetStorageLocation)
// 回显台账责任人,并预置为下拉选项以保证首次打开表单即可显示名称。
this.$set(this.formData, "assetUseUserId", row.assetUseUserId || null)
this.assetUseUserOption = row.assetUseUserId && row.assetUseUserName ? [{
id: row.assetUseUserId,
username: row.assetUseUserName
}] : []
},
doSubmit(){
this.$refs["AddForm"].validate((valid) => {
@@ -32,7 +32,7 @@ const kpiForm = {
</el-button>
</el-row>
<div>
<div class="category-table-wrapper">
<table>
<thead class="bg-gray-50">
<tr>
@@ -44,7 +44,7 @@ const kpiForm = {
<th style="width: 10%">操作</th>
</tr>
</thead>
<tbody ref="categoryList" style="height: 50vh;overflow-y: auto;display: table;width: 100%">
<tbody ref="categoryList">
<template v-for="(category,categoryIndex) in formData.categoryList">
<tr v-for="(item,itemIndex) in category.items"
:class="{'category-split':itemIndex+1===category.items.length}">
@@ -129,7 +129,7 @@ const kpiForm = {
</el-button>
</el-row>
<table class="score-levels-table">
<thead class="bg-gray-50" style="display: table;width: 100%">
<thead class="bg-gray-50">
<tr>
<th style="width: 30%;">最小分数</th>
<th style="width: 30%;">最大分数</th>
@@ -137,7 +137,7 @@ const kpiForm = {
<th>操作</th>
</tr>
</thead>
<tbody ref="scoreLevels" style="display: table;width: 100%">
<tbody ref="scoreLevels">
<tr v-for="(scoreLevel,scoreLevelIndex) in formData.scoreLevels">
<td>
<el-input-number v-model="scoreLevel.min" :min="1"
@@ -401,35 +401,31 @@ const kpiForm = {
}
},
style: /*language=CSS*/ `
.kpi-plus {
/deep/ .kpi-plus {
}
.kpi-plus table {
/deep/ .kpi-plus .category-table-wrapper {
max-height: 50vh;
overflow-y: auto;
}
/deep/ .kpi-plus table {
width: 100%;
border-collapse: collapse;
position: relative;
}
.kpi-plus table thead {
background: #fafafa;
display: table;
table-layout: fixed;
width: calc(100% - 1em);
}
.kpi-plus table tbody {
display: block;
/deep/ .kpi-plus table thead {
background: #fafafa;
}
.kpi-plus thead th,
.kpi-plus tbody td {
width: auto;
min-width: 100px;
/deep/ .kpi-plus thead th,
/deep/ .kpi-plus tbody td {
box-sizing: border-box;
}
.kpi-plus table thead th {
/deep/ .kpi-plus table thead th {
color: rgb(107 114 128);
background: #fafafa;
font-weight: 600;
@@ -437,13 +433,13 @@ const kpiForm = {
line-height: 20px;
}
.kpi-plus table tr:last-child {
/deep/ .kpi-plus table tr:last-child {
position: sticky;
bottom: 0;
background-color: white;
}
.kpi-plus table tbody tr td {
/deep/ .kpi-plus table tbody tr td {
padding: 14px 20px;
color: rgb(17 24 39);
line-height: 20px;
@@ -451,53 +447,53 @@ const kpiForm = {
cursor: pointer;
}
.kpi-plus table tbody tr td:hover {
/deep/ .kpi-plus table tbody tr td:hover {
background: rgb(249 250 251);
}
.kpi-plus table tbody tr td:hover .td-extra {
/deep/ .kpi-plus table tbody tr td:hover .td-extra {
opacity: 1;
display: block;
}
.kpi-plus table tbody td.item-content {
/deep/ .kpi-plus table tbody td.item-content {
border-bottom: 1px solid rgb(229 231 235);
text-align: left;
}
.kpi-plus table tbody .category-split td {
/deep/ .kpi-plus table tbody .category-split td {
border-bottom: 1px solid rgb(33 40 53) !important;
}
.kpi-plus table tbody tr td[rowspan] {
/deep/ .kpi-plus table tbody tr td[rowspan] {
border-bottom: 1px solid rgb(33 40 53);
}
.kpi-plus .score-levels-table .el-input-number{
/deep/ .kpi-plus .score-levels-table .el-input-number{
width: unset!important;
}
.td-extra {
/deep/ .td-extra {
/*display: none;*/
/*opacity: 0;*/
transition: opacity 0.3s ease;
}
.td-extra .edit {
/deep/ .td-extra .edit {
color: #0a84ff;
}
.td-extra .edit:hover {
/deep/ .td-extra .edit:hover {
color: #1375d8;
transform: scale(1.2);
}
.td-extra .delete {
/deep/ .td-extra .delete {
color: #ff3b30;
}
.td-extra .delete:hover {
/deep/ .td-extra .delete:hover {
color: #ec2216;
transform: scale(1.2);
}
@@ -15,7 +15,16 @@ const cadreTrainingInfo = {
<el-descriptions-item label="活动内容" > <div v-html="viewData.notice"></div></el-descriptions-item>
</el-descriptions>
<table-tool label="人员信息"></table-tool>
<el-descriptions :column="2" border class="flow-task-form">
<el-table v-if="signUsersUrl" :data="signUsers" border class="flow-task-form">
<el-table-column label="姓名" prop="userName"></el-table-column>
<el-table-column label="工号" prop="loginName"></el-table-column>
<el-table-column label="分工会" prop="unionName"></el-table-column>
<el-table-column label="单位" prop="unitName"></el-table-column>
<el-table-column label="性别" prop="sex" width="80"></el-table-column>
<el-table-column label="手机号" prop="mobile"></el-table-column>
<el-table-column label="备注" prop="remark"></el-table-column>
</el-table>
<el-descriptions v-else :column="2" border class="flow-task-form">
<el-descriptions-item label="姓名">{{viewData.userName}}</el-descriptions-item>
<el-descriptions-item label="分工会">{{viewData.unionName}}</el-descriptions-item>
<el-descriptions-item label="单位">{{viewData.unitName}}</el-descriptions-item>
@@ -67,11 +76,18 @@ const cadreTrainingInfo = {
`,
store,
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
props: {
signUsersUrl: {
type: String,
default: ""
}
},
data() {
return {
visible: false,
viewData: {},
doneTasks: [],
signUsers: [],
row: null
}
},
@@ -81,6 +97,7 @@ const cadreTrainingInfo = {
this.row = row
this.visible = true
this.getInfo()
this.getSignUsers()
this.getDoneTasks()
},
@@ -94,6 +111,24 @@ const cadreTrainingInfo = {
// })
},
// 查询当前查看范围内的报名人员;未传查询地址时保留公共组件原有的单人展示。
getSignUsers() {
this.$set(this, "signUsers", [])
if (!this.signUsersUrl || !this.row) {
return
}
this.$axios.post(this.signUsersUrl, {
activityId: this.row.cadreTrainingActId,
unionId: this.row.unionId
}).then((res) => {
if (res.code === 0) {
this.$set(this, "signUsers", res.data || [])
} else {
this.$message.error(res.msg)
}
})
},
// 获取已办任务审批记录
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => {
@@ -42,7 +42,9 @@ layout("/layouts/platform.html"){
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #view>
<cadre-training-info ref="cadreTrainingInfoRef"></cadre-training-info>
<cadre-training-info
ref="cadreTrainingInfoRef"
sign-users-url="/platform/cadreTraining/mine/listSignUsers"></cadre-training-info>
</template>
</guava>
</div>
@@ -31,7 +31,7 @@ const signForm = {
<!-- 个人报名信息 -->
<div class="process-title">选择报名人员</div>
<el-select
v-if="$auth.hasRoleOr('SYSADMIN, SCHOOL_UNION_ADMIN, BRANCH_UNION_ADMIN')"
v-if="$auth.hasRoleOr('SYSADMIN, SCHOOL_UNION_ADMIN, BRANCH_UNION_CHAIRMAN')"
v-model="searchTeammateUserId"
filterable
clearable
@@ -47,7 +47,7 @@ const signForm = {
</el-option>
</el-select>
<el-button
v-if="$auth.hasRoleOr('SYSADMIN, SCHOOL_UNION_ADMIN, BRANCH_UNION_ADMIN')"
v-if="$auth.hasRoleOr('SYSADMIN, SCHOOL_UNION_ADMIN, BRANCH_UNION_CHAIRMAN')"
class="ml5" type="primary" icon="el-icon-plus"
@click="addTeamUser">添加
</el-button>
@@ -132,44 +132,54 @@ const basicForm = {
</div>
</el-form>
<el-row class="mt10" justify="end" type="flex">
<el-button @click="$emit('refresh')">取消</el-button>
<el-button v-if="activeName === 1" type="primary" @click="activeName = 0">上一步</el-button>
<el-button v-if="activeName === 2" type="primary" @click="activeName = 1">上一步</el-button>
<el-button v-if="activeName === 0" type="primary" @click="next()">下一步</el-button>
<el-button v-if="activeName === 1" type="primary" @click="activeName = 2">下一步</el-button>
<el-button @click="onSave" type="primary">保存</el-button>
<el-button @click="onSubmit" type="primary">提交</el-button>
<el-button :disabled="formLoading" @click="$emit('refresh')">取消</el-button>
<el-button v-if="activeName === 1" :disabled="formLoading" type="primary" @click="activeName = 0">上一步</el-button>
<el-button v-if="activeName === 2" :disabled="formLoading" type="primary" @click="activeName = 1">上一步</el-button>
<el-button v-if="activeName === 0" :disabled="formLoading" type="primary" @click="next()">下一步</el-button>
<el-button v-if="activeName === 1" :disabled="formLoading" type="primary" @click="activeName = 2">下一步</el-button>
<el-button :disabled="formLoading" :loading="formLoading" @click="onSave" type="primary">保存</el-button>
<el-button :disabled="formLoading" :loading="formLoading" @click="onSubmit" type="primary">提交</el-button>
</el-row>
<el-dialog title="选择用户" :visible.sync="selectDialogVisible" width="70%" append-to-body>
<div class="btn-group tool-button">
<el-input placeholder="请输入内容" v-model="pageForm.searchKeyword" @keyup.enter.native="doSearch" style="width: 260px">
<el-select v-model="pageForm.searchName" slot="prepend" placeholder="查询类型" style="width: 80px">
<el-option label="工号" value="u.loginName"></el-option>
<el-option label="姓名" value="u.userName"></el-option>
<el-option label="手机" value="u.mobile"></el-option>
<el-row>
<el-input placeholder="请输入内容" v-model="pageForm.searchKeyword" @keyup.enter.native="doSearch" style="width: 260px">
<el-select v-model="pageForm.searchName" slot="prepend" placeholder="查询类型" style="width: 80px">
<el-option label="工号" value="u.loginName"></el-option>
<el-option label="姓名" value="u.userName"></el-option>
<el-option label="手机" value="u.mobile"></el-option>
</el-select>
</el-input>
<el-select v-model="pageForm.sex" placeholder="请选择性别" clearable>
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-input>
<el-select v-model="pageForm.sex" placeholder="请选择性别" clearable>
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
<el-select v-model="pageForm.unionId" @change="flushUnits" @clear="flushUnits" placeholder="请选择所属工会" filterable clearable>
<el-option v-for="item in unionList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-select v-model="pageForm.unitId" placeholder="请选择所属单位" filterable clearable>
<el-option v-for="item in unitList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-select v-model="pageForm.roleId" placeholder="请选择角色" filterable clearable>
<el-option v-for="item in roleList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-button icon="el-icon-search" @click="doSearch" type="primary"></el-button>
<el-select v-model="pageForm.unionId" @change="flushUnits" @clear="flushUnits" placeholder="请选择所属工会" filterable clearable>
<el-option v-for="item in unionList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-select v-model="pageForm.unitId" placeholder="请选择所属单位" filterable clearable>
<el-option v-for="item in unitList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-button icon="el-icon-search" @click="doSearch" type="primary"></el-button>
</el-row>
<el-row class="mt10">
<el-select v-model="pageForm.moduleId" @change="handleModuleChange" placeholder="请选择模块" filterable clearable>
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-select v-model="pageForm.roleId" @change="doSearch" :disabled="!pageForm.moduleId" placeholder="请选择角色" filterable clearable>
<el-option v-for="item in roleList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-select v-if="isDemocraticModule" v-model="pageForm.teacherCongressSessionId" @change="doSearch" placeholder="请选择教代会届次" filterable clearable>
<el-option v-for="item in teacherCongressOptions" :key="item.id" :label="item.fullName" :value="item.id"></el-option>
</el-select>
</el-row>
</div>
<div class="left-span-label mt20">人员列表</div>
<el-table
:data="tableData"
@selection-change="handleSelectionChange"
row-key="id"
row-key="userId"
ref="tableRef"
>
<el-table-column type="selection" reserve-selection width="60"></el-table-column>
@@ -204,6 +214,7 @@ const basicForm = {
data() {
return {
activeName: 0,
formLoading: false,
formData: {
timePeriods: [{ canLeave: false }],
},
@@ -219,14 +230,25 @@ const basicForm = {
selectDialogVisible: false,
roleList: [],
moduleOptions: [],
selectUsers: [],
pageForm: {
searchName: 'u.userName',
moduleId: '',
roleId: '',
teacherCongressSessionId: '',
},
teacherCongressOptions: [],
workerCongressOptions: [],
}
},
computed: {
// 民主管理模块才需要按教代会届次筛选人员。
isDemocraticModule() {
const module = this.moduleOptions.find((item) => item.id === this.pageForm.moduleId)
return module && module.name === '民主管理'
}
},
methods: {
next() {
if(!this.formData.typeId) {
@@ -269,9 +291,31 @@ const basicForm = {
}
this.selectDialogVisible = false
},
async getRoleList() {
const resp = await this.$axios.post("/platform/sys/msg/getRoleList")
return resp.data
// 查询选择用户时可用的 PC 端模块。
queryModule() {
this.$axios.post("/platform/meeting/manage/listModule").then((res) => {
if (res.code === 0) {
this.$set(this, "moduleOptions", res.data)
}
})
},
// 模块变更后重置关联条件,并按当前模块刷新角色候选项。
handleModuleChange(moduleId) {
this.$set(this.pageForm, "roleId", "")
this.$set(this.pageForm, "teacherCongressSessionId", "")
this.$set(this, "roleList", [])
if (moduleId) {
this.queryRoleByModule(moduleId)
}
this.doSearch()
},
// 角色仅查询当前选中模块下的启用角色。
queryRoleByModule(moduleId) {
this.$axios.post("/platform/meeting/manage/listRole", {moduleId: moduleId}).then((res) => {
if (res.code === 0) {
this.$set(this, "roleList", res.data)
}
})
},
openSelect() {
this.selectDialogVisible = true
@@ -297,6 +341,7 @@ const basicForm = {
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.formLoading = true
this.$axios.post('/platform/meeting/manage/submit', {
data: JSON.stringify(this.formData),
users: JSON.stringify(this.userTableData)
@@ -307,6 +352,8 @@ const basicForm = {
} else {
this.$message.warning(res.msg)
}
}).finally(() => {
this.formLoading = false
})
})
},
@@ -340,17 +387,21 @@ const basicForm = {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
const resp = await this.$axios.post("/platform/meeting/manage/submit", {
}).then(() => {
this.formLoading = true
this.$axios.post("/platform/meeting/manage/submit", {
data: JSON.stringify(this.formData),
users: JSON.stringify(this.userTableData)
}).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.$emit('refresh')
} else {
this.$message.warning(resp.msg)
}
}).finally(() => {
this.formLoading = false
})
if (resp.code === 0) {
this.$message.success(resp.msg)
this.$emit('refresh')
} else {
this.$message.warning(resp.msg)
}
})
}
})
@@ -382,11 +433,11 @@ const basicForm = {
})
},
},
async created() {
created() {
this.queryMeetingType()
this.queryTeacherCongress()
this.queryWorkerCongress()
this.roleList = await this.getRoleList()
this.queryModule()
},
style: /*language=CSS*/ `
@@ -64,7 +64,7 @@ layout("/layouts/platform.html"){
<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 === false" size="medium" type="text" disabled>您已请假</el-button>
<el-button v-if="tp.signStatus === false" @click="onSign(tp)" size="medium" type="text">签到</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 === true" size="medium" type="text" disabled>您已签到</el-button>
</el-row>
</el-card>
@@ -127,6 +127,10 @@ layout("/layouts/platform.html"){
}
},
methods: {
// 场次开始前禁用签到,避免用户在会议开始前提交签到操作。
isSignDisabled(startTime) {
return this.$moment().isBefore(this.$moment(startTime))
},
onLeave(row) {
if(!row.canLeave) {
this.$message.warning('该场次不能请假')
@@ -180,11 +180,11 @@ layout("/layouts/platform.html"){
</el-row>
<el-table :data="userData" :max-height="signTableHeight" 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="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="signInDateTime"></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="签到名次">
<template slot-scope="scope">
{{userData.length - scope.$index}}
@@ -271,8 +271,8 @@ layout("/layouts/platform.html"){
ud.unshift(...resp.data.userData)
let uMap = new Map()
for (let u of ud) {
if (!uMap.has(u.loginname)) {
uMap.set(u.loginname, u)
if (!uMap.has(u.loginName)) {
uMap.set(u.loginName, u)
}
}
this.$set(this, 'userData', [...uMap.values()])
@@ -1,19 +1,14 @@
const optionImg = {
template: /*language=HTML*/ `
<el-dialog :visible.sync="visible" title="设置图片" append-to-body width="700px">
<div style="background: #f7f8f9;text-align: center;border: solid 1px #d7d8d9;border-radius: 4px;">
<el-upload
action="/platform/sys/file/uploadDynamicReturnUrl"
:show-file-list="false"
:on-success="handleSuccess"
:before-upload="beforeUpload">
<img v-if="img" :src="img" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
<div style="padding: 10px 0">
请上传图片
</div>
<file-upload
:upload_number="1"
:value.sync="img"
accept=".jpg,.jpeg,.png"
upload_mode="image"
upload_result_category="interval"
upload_result_type="url"
></file-upload>
<div slot="footer" class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onConfirm"> </el-button>
@@ -29,21 +24,10 @@ const optionImg = {
},
methods: {
onOpen(img = null, ext) {
this.img = img
this.img = img || ""
this.ext = ext
this.visible = true
},
handleSuccess(response, file, fileList) {
console.log(response)
console.log(file)
console.log(fileList)
if (response.code === 0) {
this.img = response.data
// this.img = "/platform/sys/file/download?id=t38dmq4beuhrupqb54prl52t4u"
this.$message.success("图片上传成功")
}
},
beforeUpload(file) {},
onConfirm() {
this.$emit("confirm", {
img: this.img,
@@ -51,33 +35,5 @@ const optionImg = {
})
this.visible = false
}
},
style: /*language=CSS*/ `
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
`
}
}
@@ -16,7 +16,7 @@ layout("/layouts/platform.html"){
.qsv-online-drawer .el-drawer__body {
padding: 0;
background: #f5f7fa;
background: #eef4ff;
display: flex;
flex-direction: column;
flex: 1;
@@ -53,7 +53,7 @@ layout("/layouts/platform.html"){
min-height: 0;
max-height: none;
overflow-y: auto;
padding: 16px 24px 32px;
padding: 20px 24px 32px;
box-sizing: border-box;
}
@@ -101,50 +101,59 @@ layout("/layouts/platform.html"){
}
.qsv-online-vote {
max-width: 800px;
max-width: 1040px;
margin: 0 auto;
background: #fff7f2;
background: transparent;
color: #303133;
}
.qsv-online-vote-header {
padding: 36px 20px 22px;
background: radial-gradient(circle at right top, #fff 0, #fff7f2 42%, #fffdfb 100%);
text-align: center;
padding: 0;
background: transparent;
}
.qsv-online-vote-title {
margin: 0 0 28px;
color: #ff4d00;
font-size: 22px;
font-weight: 700;
display: none;
}
.qsv-online-vote-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
border: 1px solid #ff7b3c;
border-radius: 10px;
padding: 18px 10px;
background: rgba(255, 255, 255, .55);
min-height: 90px;
border: 1px solid #d7e4ff;
border-radius: 12px;
padding: 0 12px;
background: #ffffff;
box-shadow: 0 3px 8px rgba(46, 107, 214, .08);
}
.qsv-online-vote-stat strong {
display: block;
color: #ff4d00;
font-size: 24px;
color: #2468e8;
font-size: 22px;
line-height: 1.2;
}
.qsv-online-vote-stat span {
display: block;
margin-top: 8px;
color: #909399;
font-size: 13px;
margin-top: 6px;
color: #6f7f9d;
font-size: 12px;
}
.qsv-online-vote-stat {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.qsv-online-vote-stat + .qsv-online-vote-stat {
border-left: 1px solid #e2ebfb;
}
.qsv-online-vote-rank {
color: #ff4d00;
color: #2468e8;
cursor: pointer;
}
@@ -153,113 +162,104 @@ layout("/layouts/platform.html"){
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 22px;
color: #ff4d00;
background: #fff4ee;
font-size: 14px;
margin-top: 12px;
padding: 10px 16px;
border: 1px solid #d7e4ff;
border-radius: 10px;
color: #3f5b88;
background: #ffffff;
font-size: 13px;
font-weight: 600;
box-shadow: 0 3px 8px rgba(46, 107, 214, .05);
}
.qsv-online-vote-countdown b {
display: inline-block;
min-width: 22px;
height: 22px;
border-radius: 2px;
background: #ff5a1f;
min-width: 24px;
height: 20px;
border-radius: 4px;
background: #2f70e8;
color: #fff;
text-align: center;
line-height: 22px;
line-height: 20px;
margin: 0 3px;
}
.qsv-online-vote-search {
display: flex;
border-top: 8px solid #ffd8c5;
border-bottom: 8px solid #ffd8c5;
background: #fff;
gap: 8px;
margin-top: 12px;
background: transparent;
}
.qsv-online-vote-search .el-input__inner {
border: 0;
border-radius: 0;
height: 36px;
border: 1px solid #cfe0ff;
border-radius: 9px;
color: #303133;
}
.qsv-online-vote-search .el-button {
width: 120px;
width: 68px;
height: 36px;
padding: 0;
border: 0;
border-left: 1px solid #ebeef5;
border-radius: 0;
color: #ff4d00;
border-radius: 9px;
color: #ffffff;
background: #2f70e8;
}
.qsv-online-vote-subject {
padding: 18px 20px 24px;
background: #fff;
padding: 14px 0 0;
background: transparent;
}
.qsv-online-vote-subject-title {
margin-bottom: 16px;
font-size: 16px;
line-height: 1.55;
margin-bottom: 10px;
color: #18345e;
font-size: 14px;
font-weight: 700;
line-height: 1.5;
}
.qsv-online-vote-subject-title em {
color: #909399;
color: #6d82a7;
font-style: normal;
}
.qsv-online-vote-option-list {
display: block;
}
.qsv-online-vote-option-list-2,
.qsv-online-vote-option-list-3 {
display: grid;
gap: 16px;
}
.qsv-online-vote-option-list-2 {
gap: 12px;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.qsv-online-vote-option-list-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.qsv-online-vote-option {
margin-bottom: 16px;
border: 1px solid transparent;
position: relative;
min-width: 0;
margin: 0;
border: 1px solid #d2e1fb;
border-radius: 10px;
background: #fff;
cursor: pointer;
transition: border-color .2s, background-color .2s;
}
.qsv-online-vote-option-list-2 .qsv-online-vote-option,
.qsv-online-vote-option-list-3 .qsv-online-vote-option {
min-width: 0;
margin-bottom: 0;
overflow: hidden;
box-shadow: 0 3px 8px rgba(46, 107, 214, .05);
transition: border-color .2s, box-shadow .2s, background-color .2s;
}
.qsv-online-vote-option.active {
border-color: #ff4d00;
background: #fff8f4;
border-color: #2f70e8;
background: #f3f7ff;
box-shadow: 0 4px 12px rgba(47, 112, 232, .14);
}
.qsv-online-vote-img {
position: relative;
height: 260px;
border-radius: 6px;
background: #f4f4f4;
height: 174px;
border-radius: 0;
background: #eaf2ff;
overflow: hidden;
}
.qsv-online-vote-option-list-2 .qsv-online-vote-img {
height: 210px;
}
.qsv-online-vote-option-list-3 .qsv-online-vote-img {
height: 150px;
}
.qsv-online-vote-img img {
width: 100%;
height: 100%;
@@ -272,17 +272,16 @@ layout("/layouts/platform.html"){
display: flex;
align-items: center;
justify-content: center;
color: #ff7b3c;
font-size: 42px;
color: #9fc0f8;
font-size: 48px;
font-weight: 700;
background: linear-gradient(135deg, #fff7f2, #ffe4d7);
background: linear-gradient(135deg, #f6f9ff, #dfebff);
}
.qsv-online-vote-option-body {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px 12px;
display: block;
min-height: 58px;
padding: 10px 12px 12px;
}
.qsv-online-vote-option-text {
@@ -292,19 +291,71 @@ layout("/layouts/platform.html"){
.qsv-online-vote-option-text strong {
display: block;
color: #ff4d00;
margin-bottom: 4px;
color: #4b74bf;
margin-bottom: 5px;
font-size: 13px;
font-weight: 500;
}
.qsv-online-vote-option-text span {
color: #303133;
color: #18345e;
font-size: 15px;
font-weight: 600;
}
.qsv-online-vote-option-actions {
flex-shrink: 0;
margin-top: 20px;
text-align: right;
position: absolute;
right: 10px;
bottom: 7px;
}
.qsv-online-vote-option-actions .el-button {
padding: 4px 0;
color: #2f70e8;
}
.qsv-online-vote-option-check {
position: absolute;
z-index: 2;
top: 10px;
right: 10px;
margin: 0;
}
.qsv-online-vote-option-check .el-checkbox__label {
display: none;
}
.qsv-online-vote-option-check .el-checkbox__inner {
width: 18px;
height: 18px;
border-color: #9ebdf1;
border-radius: 50%;
background: rgba(255, 255, 255, .92);
}
.qsv-online-vote-option-check .el-checkbox__input.is-checked .el-checkbox__inner {
border-color: #2f70e8;
background: #2f70e8;
}
.qsv-online-vote-option-check .el-checkbox__input.is-checked .el-checkbox__inner::after {
top: 3px;
left: 6px;
}
@media screen and (max-width: 720px) {
.qsv-online-preview-body {
padding: 14px 12px 24px;
}
.qsv-online-vote-option-list {
grid-template-columns: minmax(0, 1fr);
}
.qsv-online-vote-img {
height: 190px;
}
}
.qsv-online-footer {
@@ -487,12 +538,18 @@ layout("/layouts/platform.html"){
</em>
<em v-else>【请选择{{currentVoteSubject.type === 'radio' ? '1' : '至少1'}}项】</em>
</div>
<div :class="getVoteOptionListClass(currentVoteSubject)">
<div class="qsv-online-vote-option-list">
<div
v-for="(option, index) in filteredVoteOptions"
:key="option.id"
:class="['qsv-online-vote-option', isOptionSelected(currentVoteSubject, option.id) ? 'active' : '']"
@click="toggleVoteOption(option)">
<el-checkbox
class="qsv-online-vote-option-check"
:value="isOptionSelected(currentVoteSubject, option.id)"
:disabled="answerDialog.readonly || isVoteEnd"
@click.native.stop="toggleVoteOption(option)">
</el-checkbox>
<div class="qsv-online-vote-img" v-if="option.imgUrl">
<img :src="option.imgUrl" alt="">
</div>
@@ -500,11 +557,6 @@ layout("/layouts/platform.html"){
<span>{{index + 1}}</span>
</div>
<div class="qsv-online-vote-option-body">
<el-checkbox
:value="isOptionSelected(currentVoteSubject, option.id)"
:disabled="answerDialog.readonly || isVoteEnd"
@click.native.stop="toggleVoteOption(option)">
</el-checkbox>
<div class="qsv-online-vote-option-text">
<strong>{{getVoteOptionVotes(option.id)}}票({{getVoteOptionPercent(option.id)}}%)</strong>
<span>{{option.text}}</span>
@@ -2,6 +2,29 @@
layout("/layouts/platform.html"){
#-->
<style>
/* 固定会议编辑弹框,避免表单内容撑高整个弹框。 */
.guild-hall-meeting-dialog {
height: calc(100vh - 120px);
max-width: calc(100vw - 32px);
max-height: 700px;
display: flex;
flex-direction: column;
}
.guild-hall-meeting-dialog .el-dialog__header,
.guild-hall-meeting-dialog .el-dialog__footer {
flex-shrink: 0;
}
/* 表单内容超出可用高度时,仅在弹框内容区内滚动。 */
.guild-hall-meeting-dialog .el-dialog__body {
flex: 1;
min-height: 0;
overflow-y: auto;
}
</style>
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
@@ -34,7 +57,8 @@ layout("/layouts/platform.html"){
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="70%">
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="1000px"
custom-class="guild-hall-meeting-dialog">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称"></el-input>
@@ -428,6 +428,7 @@ layout("/layouts/platform.html"){
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
data() {
return {
@@ -644,25 +645,47 @@ layout("/layouts/platform.html"){
},
getTreeData() {
const hasAdmin = this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN", "SCHOOL_UNION_MEMBER_ADMIN"])
const unionId = hasAdmin ? null : this.$store.state.user.union.id
this.$businessTool.listUnion(unionId).then((data) => {
const currentUser = this.$store.state.user || {}
const currentUnion = currentUser.union || {}
const unionId = hasAdmin ? null : currentUnion.id
const setTreeData = (data) => {
const unionList = data || []
// 仅有一个可选分工会时默认收起左栏,仍可通过折叠按钮手动展开。
this.treeCollapsed = unionList.length === 1
this.treeData = [{
id: this.unionRootId,
name: "工会委员会",
type: "root",
children: (data || []).map((item) => ({
children: unionList.map((item) => ({
id: item.id,
name: item.name,
type: "union"
}))
}]
this.$nextTick(() => {
this.currentTreeData = this.treeData[0]
this.setCurrentTreeNode(this.unionRootId)
if (this.tabActive !== "memberList") {
this.tabActive = "memberList"
}
// 唯一分工会作为默认查询节点;多条或无数据时继续使用工会根节点。
const defaultNode = unionList.length === 1 ? this.treeData[0].children[0] : this.treeData[0]
this.treeNodeClick(defaultNode)
this.setCurrentTreeNode(defaultNode.id)
this.updateTableHeight()
if (this.$refs.memberTableRef) {
this.$refs.memberTableRef.doLayout()
}
})
}
// 非管理员只能加载本人所属分工会;用户信息异常时保留根节点,禁止空参数查询全部分工会。
if (!hasAdmin && !unionId) {
setTreeData([])
return
}
this.$businessTool.listUnion(unionId).then((data) => {
setTreeData(data)
}).catch(() => {
// 组织树加载失败时回退到根节点,会员列表仍按后端数据权限正常查询。
setTreeData([])
})
},
setCurrentTreeNode(nodeKey) {
@@ -764,8 +787,17 @@ layout("/layouts/platform.html"){
created() {
this.checkedFields = this.tableColumns.filter(c => c.checked !== 0).map(c => c.prop);
this.initDefaultView()
this.getTreeData()
this.pageData()
const initPage = () => {
this.getTreeData()
}
// 等待公共布局完成当前登录用户回显,避免首次登录或切换账号时读取不到所属分工会。
if (window.logonUserReady && window.logonUserReady.always) {
window.logonUserReady.always(() => {
initPage()
})
} else {
initPage()
}
},
mounted() {
const tabsContent = this.$el.querySelector(".member-group-list-card .el-tabs__content")
@@ -149,6 +149,9 @@ let H5_ASSET_STOCKTAKING_FORM = {
this.$set(this.formData, "assetName", row.assetName)
this.$set(this.formData, "assetUsedDate", row.assetUsedDate)
this.$set(this.formData, "assetStorageLocation", row.assetStorageLocation)
// 回显台账责任人;用户可通过责任人选择面板重新搜索并替换。
this.$set(this.formData, "assetUseUserId", row.assetUseUserId || null)
this.$set(this.formData, "assetUseName", row.assetUseUserName || null)
this.assetUsageStateNameOption = await this.$businessTool.getDictOptions("ASSET_USAGE_STATE")
this.visible = true
},
@@ -58,9 +58,9 @@ layout("/layouts/platform_h5.html"){
</div>
</div>
<div class="sign_button">
<van-button v-if="item.joinStatus === true" @click.stop="onLeave(item)" size="mini" type="info">请假</van-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 === false" size="mini" type="info" disabled>您已请假</van-button>
<van-button v-if="item.signStatus === false" @click.stop="onSign(item)" size="mini" type="info">签到</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 === true" size="mini" type="info" disabled>您已签到</van-button>
</div>
</div>
@@ -109,9 +109,14 @@ layout("/layouts/platform_h5.html"){
reasonVisible: false,
leaveReason: '',
periodRow: {},
leaveLoading: false,
}
},
methods: {
// 场次开始前禁用签到,避免用户在会议开始前提交签到操作。
isSignDisabled(startTime) {
return this.$moment().isBefore(this.$moment(startTime))
},
async onReady() {
const typeList = await this.queryMeetingType()
this.typeOptions = [
@@ -131,18 +136,27 @@ layout("/layouts/platform_h5.html"){
this.$toast('请输入请假事由')
done(false)
} else {
this.leaveLoading = true
this.$axios.post("/platform/meeting/mine/leave", {
periodId: this.periodRow.id,
leaveReason: this.leaveReason,
})
.then((res) => {
done()
this.$toast(res.msg)
if (res.code === 0) {
done()
this.doSearch()
this.reasonVisible = false
this.leaveVisible = false
} else {
done(false)
}
}).catch(() => { done() })
}).catch(() => {
done(false)
}).finally(() => {
// 请假请求结束后恢复按钮状态,接口失败时允许用户修正事由后重新提交。
this.leaveLoading = false
})
}
} else {
done()
@@ -113,6 +113,15 @@ layout("/layouts/platform_h5.html"){
this.$axios.post("/platform/h5/qsv/quiz/subjects", {activityId: this.id}).then((res) => {
if (res.code === 0) {
this.activity = res.data.activity
// 历史链接可能误将投票或问卷指向答题页,按活动实际类型跳转到对应的移动端入口。
if (this.activity.category === "VOTE") {
pjaxReplace("/platform/h5/qsv/vote?id=" + this.id)
return
}
if (this.activity.category === "SURVEY") {
pjaxReplace("/platform/h5/qsv/survey?id=" + this.id)
return
}
this.subjects = res.data.subjects
this.answerRecordId = res.data.answerRecordId
// this.checkGroupPermission()
File diff suppressed because it is too large Load Diff