Compare commits
10
Commits
42e72aad99
...
337bcd55bc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
337bcd55bc | ||
|
|
3e99901d8e | ||
|
|
f7c381acbd | ||
|
|
c55449cd71 | ||
|
|
b05543d624 | ||
|
|
878b0de75f | ||
|
|
075448ab2b | ||
|
|
2af94299dd | ||
|
|
72cdae177e | ||
|
|
ae027378f7 |
@@ -31,6 +31,7 @@ RoleConstant {
|
|||||||
SCHOOL_UNION_ARTICLE_FGH_ADMIN("校工会分工会新闻审批管理员"),
|
SCHOOL_UNION_ARTICLE_FGH_ADMIN("校工会分工会新闻审批管理员"),
|
||||||
SCHOOL_UNION_ARTICLE_ADMIN("校工会新闻审批管理员"),
|
SCHOOL_UNION_ARTICLE_ADMIN("校工会新闻审批管理员"),
|
||||||
SCHOOL_OUTLAY_ADMIN("校工会经费管理员"),
|
SCHOOL_OUTLAY_ADMIN("校工会经费管理员"),
|
||||||
|
TOUR_APPLICANT("疗休养领办人"),
|
||||||
|
|
||||||
BRANCH_UNION_ADMIN("分工会管理员"),
|
BRANCH_UNION_ADMIN("分工会管理员"),
|
||||||
BRANCH_UNION_CHAIRMAN("分工会主席"),
|
BRANCH_UNION_CHAIRMAN("分工会主席"),
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ public class Sys_user extends BaseModel implements Serializable {
|
|||||||
@Column
|
@Column
|
||||||
@Comment("编制类别码")
|
@Comment("编制类别码")
|
||||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||||
@DataCenterColumn(name = "编制类型", key = "BZLB")
|
@DataCenterColumn(name = "编制类型", key = "yrxsmc")
|
||||||
private String preparedBy;
|
private String preparedBy;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
|
|||||||
+15
-10
@@ -163,9 +163,9 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
|||||||
Cnd cnd = Cnd.where(Sys_user_source::getPullTime, "=", updateParam.getPullTime());
|
Cnd cnd = Cnd.where(Sys_user_source::getPullTime, "=", updateParam.getPullTime());
|
||||||
|
|
||||||
// 处理复杂条件
|
// 处理复杂条件
|
||||||
if (updateParam.getConditionGroup() != null) {
|
// if (updateParam.getConditionGroup() != null) {
|
||||||
// cnd = ConditionGroupUtil.applyConditionGroup(cnd, updateParam.getConditionGroup());
|
// cnd = ConditionGroupUtil.applyConditionGroup(cnd, updateParam.getConditionGroup());
|
||||||
}
|
// }
|
||||||
|
|
||||||
List<Sys_user_source> sources = dao.query(Sys_user_source.class, cnd.groupBy("loginname"));
|
List<Sys_user_source> sources = dao.query(Sys_user_source.class, cnd.groupBy("loginname"));
|
||||||
log.info("符合条件的数据源记录数: {}", sources.size());
|
log.info("符合条件的数据源记录数: {}", sources.size());
|
||||||
@@ -181,6 +181,8 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
|||||||
List<String> addMemberUserIds = Collections.synchronizedList(new ArrayList<>());
|
List<String> addMemberUserIds = Collections.synchronizedList(new ArrayList<>());
|
||||||
List<String> removeMemberUserIds = Collections.synchronizedList(new ArrayList<>());
|
List<String> removeMemberUserIds = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
List<String> preparedByList = List.of("人才市场派遣", "人力资源服务中心派遣", "海川派遣", "越仕中心", "柔性引进");
|
||||||
|
|
||||||
// 处理每条数据
|
// 处理每条数据
|
||||||
for (Sys_user_source source : sources) {
|
for (Sys_user_source source : sources) {
|
||||||
Sys_user user = userMap.get(source.getLoginname());
|
Sys_user user = userMap.get(source.getLoginname());
|
||||||
@@ -195,8 +197,9 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
|||||||
u.setSalt(salt);
|
u.setSalt(salt);
|
||||||
u.setPassword(PwdUtil.getPassword(PwdUtil.generate(12), salt));
|
u.setPassword(PwdUtil.getPassword(PwdUtil.generate(12), salt));
|
||||||
|
|
||||||
if (u.getMember()) {
|
if (u.getPersonType().equals("在编") || preparedByList.contains(u.getPreparedBy())) {
|
||||||
//addMemberUserIds.add(u.getId());
|
addMemberUserIds.add(u.getId());
|
||||||
|
u.setMember(true);
|
||||||
}
|
}
|
||||||
needInitUserList.add(u);
|
needInitUserList.add(u);
|
||||||
} else {
|
} else {
|
||||||
@@ -205,12 +208,14 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
|||||||
u.setMember(null);
|
u.setMember(null);
|
||||||
u.setMemberTime(null);
|
u.setMemberTime(null);
|
||||||
// 更新会员状态
|
// 更新会员状态
|
||||||
boolean currentIsMember = user.getMember() != null && user.getMember();
|
if (u.getPersonType().equals("在编") || preparedByList.contains(u.getPreparedBy())) {
|
||||||
|
if (!user.getMember()) {
|
||||||
if (!currentIsMember) {
|
addMemberUserIds.add(u.getId());
|
||||||
//addMemberUserIds.add(user.getId());
|
u.setMember(true);
|
||||||
} else{
|
}
|
||||||
//removeMemberUserIds.add(user.getId());
|
} else {
|
||||||
|
removeMemberUserIds.add(user.getId());
|
||||||
|
u.setMember(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
needDoUpdateList.add(u);
|
needDoUpdateList.add(u);
|
||||||
|
|||||||
@@ -57,6 +57,6 @@ public class SysUserAllRenewJob implements Job {
|
|||||||
param.setUpdateMode(SysDataUpdateMode.ALL.name());
|
param.setUpdateMode(SysDataUpdateMode.ALL.name());
|
||||||
param.setConditionGroup(conditionGroup);
|
param.setConditionGroup(conditionGroup);
|
||||||
|
|
||||||
// sysDataUserUpdateService.update(param);
|
sysDataUserUpdateService.update(param);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-8
@@ -115,7 +115,7 @@ public class ActivityBasicScopeController {
|
|||||||
""");
|
""");
|
||||||
try {
|
try {
|
||||||
Cnd cnd = getCnd(pageForm);
|
Cnd cnd = getCnd(pageForm);
|
||||||
cnd.and("u.member", "=", 1);
|
// cnd.and("u.member", "=", 1);
|
||||||
sql.setCondition(cnd);
|
sql.setCondition(cnd);
|
||||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||||
return Result.success(pagination);
|
return Result.success(pagination);
|
||||||
@@ -199,7 +199,7 @@ public class ActivityBasicScopeController {
|
|||||||
$condition
|
$condition
|
||||||
""");
|
""");
|
||||||
Cnd cnd = Cnd.NEW();
|
Cnd cnd = Cnd.NEW();
|
||||||
if (!StpUtil.hasRole(RoleConstant.SYSADMIN.name()) && !StpUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
if (!canViewAllScopeUsers()) {
|
||||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||||
group.or("creator", "=", SecurityUtil.getUserId());
|
group.or("creator", "=", SecurityUtil.getUserId());
|
||||||
if(AuthUtil.hasRole(RoleConstant.CLUB_PRESIDENT.name())) {
|
if(AuthUtil.hasRole(RoleConstant.CLUB_PRESIDENT.name())) {
|
||||||
@@ -234,16 +234,17 @@ public class ActivityBasicScopeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断用户权限及获取工会id
|
* 获取参加人员范围弹窗所需的当前用户角色和所属工会信息。
|
||||||
|
* 无请求参数;返回 Result,data 中包含校工会管理员、校工会活动管理员、分工会角色、
|
||||||
|
* 系统管理员判断结果及当前用户所属工会 id,供前端控制全校人员筛选条件。
|
||||||
*
|
*
|
||||||
* @return java.lang.Object
|
* @return 当前用户的活动人员范围权限信息
|
||||||
* @author zhf
|
|
||||||
* @description
|
|
||||||
*/
|
*/
|
||||||
@At
|
@At
|
||||||
public Result getRolesAndUnion() {
|
public Result getRolesAndUnion() {
|
||||||
return Result.success(new NutMap() {{
|
return Result.success(new NutMap() {{
|
||||||
addv("is_A06", StpUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name()));
|
addv("is_A06", StpUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name()));
|
||||||
|
addv("is_school_union_activity_admin", StpUtil.hasRole(RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name()));
|
||||||
addv("is_H04", AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_ADMIN.name(), RoleConstant.BRANCH_UNION_WENTI_SPORTS.name(), RoleConstant.BRANCH_UNION_XJSHAN_PROMOTION.name(), RoleConstant.BRANCH_UNION_WENTI_WY.name()));
|
addv("is_H04", AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_ADMIN.name(), RoleConstant.BRANCH_UNION_WENTI_SPORTS.name(), RoleConstant.BRANCH_UNION_XJSHAN_PROMOTION.name(), RoleConstant.BRANCH_UNION_WENTI_WY.name()));
|
||||||
addv("is_sysadmin", StpUtil.hasRole(RoleConstant.SYSADMIN.name()));
|
addv("is_sysadmin", StpUtil.hasRole(RoleConstant.SYSADMIN.name()));
|
||||||
addv("unionid", SecurityUtil.getUnionId());
|
addv("unionid", SecurityUtil.getUnionId());
|
||||||
@@ -377,7 +378,7 @@ public class ActivityBasicScopeController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())){
|
if (!canViewAllScopeUsers()) {
|
||||||
cnd.andEX("u.unionid", "=", SecurityUtil.getUnionId());
|
cnd.andEX("u.unionid", "=", SecurityUtil.getUnionId());
|
||||||
}
|
}
|
||||||
cnd.andEX("u.unionid", EQ_OR_NEQ_OP, activityUserScopePageParam.getUnionId());
|
cnd.andEX("u.unionid", EQ_OR_NEQ_OP, activityUserScopePageParam.getUnionId());
|
||||||
@@ -393,11 +394,25 @@ public class ActivityBasicScopeController {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
cnd.and("u.member", "=", 1);
|
// cnd.and("u.member", "=", 1);
|
||||||
return cnd;
|
return cnd;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前用户是否拥有活动参加人员范围的全校人员查看权限。
|
||||||
|
* 系统管理员、校工会管理员和校工会活动管理员可查看全部人员,其他角色仍按所属工会限制。
|
||||||
|
*
|
||||||
|
* @return true 表示可查看全校人员,false 表示仅可查看所属工会人员
|
||||||
|
*/
|
||||||
|
private boolean canViewAllScopeUsers() {
|
||||||
|
return AuthUtil.hasRoleOr(
|
||||||
|
RoleConstant.SYSADMIN.name(),
|
||||||
|
RoleConstant.SCHOOL_UNION_ADMIN.name(),
|
||||||
|
RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@Ok("void")
|
@Ok("void")
|
||||||
|
|||||||
+12
-2
@@ -71,7 +71,15 @@ public class ActivityBasicScopeUserDataController {
|
|||||||
public void index() {
|
public void index() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询已加入活动分组的人员。
|
||||||
|
* pageForm 传递分页、排序和姓名/工号关键字;unionId、unitId、personType、userState、groupId
|
||||||
|
* 分别传递工会、单位、教职工类别、在职状态和活动分组筛选值;existsLoginNameRedisKey
|
||||||
|
* 用于限制导入核对产生的工号集合。返回 Result,data 为包含 list 和 totalCount 的分页结果。
|
||||||
|
* 校工会活动管理员可查看全部活动分组人员,但删除权限仍沿用原有创建人限制。
|
||||||
|
*
|
||||||
|
* @return 活动分组人员分页结果
|
||||||
|
*/
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("activity.basic.user")
|
@SaCheckPermission("activity.basic.user")
|
||||||
public Result pageData(PageForm pageForm,
|
public Result pageData(PageForm pageForm,
|
||||||
@@ -116,8 +124,10 @@ public class ActivityBasicScopeUserDataController {
|
|||||||
cnd.andEX("u.unitid", "=", unitId);
|
cnd.andEX("u.unitid", "=", unitId);
|
||||||
cnd.andEX("u.personType", "=", personType);
|
cnd.andEX("u.personType", "=", personType);
|
||||||
cnd.andEX("u.userState", "=", userState);
|
cnd.andEX("u.userState", "=", userState);
|
||||||
|
// 校工会活动管理员在设置参加人员范围时,可查看其他管理员已建立的人员分组。
|
||||||
if (!StpUtil.hasRole(RoleConstant.SYSADMIN.name()) &&
|
if (!StpUtil.hasRole(RoleConstant.SYSADMIN.name()) &&
|
||||||
!StpUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
!StpUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name()) &&
|
||||||
|
!StpUtil.hasRole(RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name())) {
|
||||||
cnd.and("aus.creator", "=", SecurityUtil.getUserId());
|
cnd.and("aus.creator", "=", SecurityUtil.getUserId());
|
||||||
}
|
}
|
||||||
if (StrUtil.isNotBlank(existsLoginNameRedisKey)) {
|
if (StrUtil.isNotBlank(existsLoginNameRedisKey)) {
|
||||||
|
|||||||
+22
-10
@@ -17,6 +17,7 @@ import org.nutz.dao.Dao;
|
|||||||
import org.nutz.ioc.aop.Aop;
|
import org.nutz.ioc.aop.Aop;
|
||||||
import org.nutz.ioc.loader.annotation.Inject;
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
import org.nutz.mvc.annotation.Ok;
|
import org.nutz.mvc.annotation.Ok;
|
||||||
import org.nutz.mvc.annotation.Param;
|
import org.nutz.mvc.annotation.Param;
|
||||||
@@ -59,18 +60,17 @@ public class QsvActivityController {
|
|||||||
//保存问卷基础信息
|
//保存问卷基础信息
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("qsv.activity")
|
@SaCheckPermission("qsv.activity")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
@ApiOperation("保存问卷基础信息")
|
@ApiOperation("保存问卷基础信息")
|
||||||
@SLog(type = "qsv.activity", tag = "保存问卷基础信息", msg = "保存问卷基础信息")
|
@SLog(type = "qsv.activity", tag = "保存问卷基础信息", msg = "保存问卷基础信息")
|
||||||
public Result save(QsvActivity qsvActivity) {
|
public Result save(QsvActivity qsvActivity, @Param("forceSave") Boolean forceSave) {
|
||||||
if (qsvActivity.getCategory().equals("QUIZ")) {
|
NutMap confirmResult = qsvActivityService.checkAnswerModeChangeConfirm(qsvActivity);
|
||||||
if (qsvActivity.getMode().equals("SCHEDULED")) {
|
// Mode changes with existing answer records must be returned as a normal response, so the page can show a confirm dialog.
|
||||||
qsvActivity.setRepeatMode("DAILY");
|
if (!Boolean.TRUE.equals(forceSave) && Boolean.TRUE.equals(confirmResult.get("needConfirm"))) {
|
||||||
} else if (qsvActivity.getMode().equals("REGULAR")) {
|
return Result.success(confirmResult.getString("message"), confirmResult);
|
||||||
qsvActivity.setRepeatMode("TOTAL");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
dao.insertOrUpdate(qsvActivity);
|
QsvActivity activity = qsvActivityService.saveActivity(qsvActivity, forceSave);
|
||||||
return Result.success();
|
return Result.success(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@@ -83,13 +83,25 @@ public class QsvActivityController {
|
|||||||
// 删除问卷
|
// 删除问卷
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("qsv.activity")
|
@SaCheckPermission("qsv.activity")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
@ApiOperation("删除问卷")
|
@ApiOperation("删除问卷")
|
||||||
@SLog(type = "qsv.activity", tag = "删除问卷", msg = "删除问卷")
|
@SLog(type = "qsv.activity", tag = "删除问卷", msg = "删除问卷")
|
||||||
public Result delete(@Valid String id) {
|
public Result delete(@Valid String id) {
|
||||||
dao.delete(QsvActivity.class, id);
|
qsvActivityService.deleteActivity(id);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新活动开启状态
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("qsv.activity")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
@ApiOperation("更新活动开启状态")
|
||||||
|
@SLog(type = "qsv.activity", tag = "更新活动开启状态", msg = "更新活动开启状态")
|
||||||
|
public Result updateEnabled(@Valid String id, Boolean enabled) {
|
||||||
|
qsvActivityService.updateEnabled(id, enabled);
|
||||||
|
return Result.success(enabled ? "开启成功" : "关闭成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 保存问卷题目
|
// 保存问卷题目
|
||||||
@At
|
@At
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import com.budwk.app.zhgh.dayofficework.qsv.models.QsvBank;
|
|||||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvBankService;
|
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvBankService;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.ioc.aop.Aop;
|
||||||
import org.nutz.ioc.loader.annotation.Inject;
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
@@ -51,6 +53,7 @@ public class QsvBankController {
|
|||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("qsv.bank")
|
@SaCheckPermission("qsv.bank")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
@ApiOperation("保存题库")
|
@ApiOperation("保存题库")
|
||||||
public Result save(@Param("qsvBank") QsvBank qsvBank) {
|
public Result save(@Param("qsvBank") QsvBank qsvBank) {
|
||||||
dao.insert(qsvBank);
|
dao.insert(qsvBank);
|
||||||
@@ -59,6 +62,7 @@ public class QsvBankController {
|
|||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("qsv.bank")
|
@SaCheckPermission("qsv.bank")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
@ApiOperation("修改题库")
|
@ApiOperation("修改题库")
|
||||||
public Result update(@Param("qsvBank") QsvBank qsvBank) {
|
public Result update(@Param("qsvBank") QsvBank qsvBank) {
|
||||||
dao.updateIgnoreNull(qsvBank);
|
dao.updateIgnoreNull(qsvBank);
|
||||||
@@ -75,6 +79,7 @@ public class QsvBankController {
|
|||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("qsv.bank")
|
@SaCheckPermission("qsv.bank")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
@ApiOperation("删除题库")
|
@ApiOperation("删除题库")
|
||||||
public Result delete(@Valid String id) {
|
public Result delete(@Valid String id) {
|
||||||
dao.delete(QsvBank.class, id);
|
dao.delete(QsvBank.class, id);
|
||||||
@@ -83,6 +88,7 @@ public class QsvBankController {
|
|||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("qsv.bank")
|
@SaCheckPermission("qsv.bank")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
@ApiOperation("保存题库题目")
|
@ApiOperation("保存题库题目")
|
||||||
public Result saveSubjects(@Valid String id, @Param("subjects") QsvSubject[] subjects) {
|
public Result saveSubjects(@Valid String id, @Param("subjects") QsvSubject[] subjects) {
|
||||||
QsvBank qsvBank = dao.fetch(QsvBank.class, id);
|
QsvBank qsvBank = dao.fetch(QsvBank.class, id);
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.budwk.app.zhgh.dayofficework.qsv.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.mvc.annotation.At;
|
||||||
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
|
||||||
|
@IocBean
|
||||||
|
@At("/platform/qsv")
|
||||||
|
@Ok("json:full")
|
||||||
|
@ApiOperation("问卷调查新建")
|
||||||
|
/**
|
||||||
|
* 问卷新建页面控制器,负责承载 PC 端问卷创建向导入口。
|
||||||
|
*/
|
||||||
|
public class QsvNewController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开问卷新建页面,页面内继续完成基础信息、题目与发布配置。
|
||||||
|
*/
|
||||||
|
@At("/new")
|
||||||
|
@Ok("beetl:/platform/zhgh/dayofficework/qsv/activity/new.html")
|
||||||
|
@SaCheckPermission("qsv.new")
|
||||||
|
public void newPage() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+611
@@ -0,0 +1,611 @@
|
|||||||
|
package com.budwk.app.zhgh.dayofficework.qsv.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.lang.Dict;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
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.dayofficework.qsv.models.QsvActivity;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvOption;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.param.QsvAnswerParam;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.dto.QsvCheckAnswerResult;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvQuizService;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvUserAnswerRecordService;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
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.lang.util.NutMap;
|
||||||
|
import org.nutz.mvc.annotation.At;
|
||||||
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
import org.nutz.mvc.annotation.Param;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@IocBean
|
||||||
|
@At("/platform/qsv/online")
|
||||||
|
@Ok("json:full")
|
||||||
|
@ApiOperation("在线答题")
|
||||||
|
/**
|
||||||
|
* PC 端在线答题控制器,负责答题/投票任务列表、题目读取、提交和投票统计。
|
||||||
|
*/
|
||||||
|
public class QsvOnlineController {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Dao dao;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private QsvUserAnswerRecordService qsvUserAnswerRecordService;
|
||||||
|
@Inject
|
||||||
|
private QsvQuizService qsvQuizService;
|
||||||
|
|
||||||
|
@At("")
|
||||||
|
@Ok("beetl:/platform/zhgh/dayofficework/qsv/online/index.html")
|
||||||
|
@SaCheckPermission("qsv.online")
|
||||||
|
/**
|
||||||
|
* 打开 PC 端在线答题页面。
|
||||||
|
*/
|
||||||
|
public void index() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询当前用户可参与的问卷活动,并根据是否已答过滤列表。
|
||||||
|
*
|
||||||
|
* @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) {
|
||||||
|
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), 0, new ArrayList<>()));
|
||||||
|
}
|
||||||
|
List<QsvActivity> activities = dao.query(QsvActivity.class, cnd);
|
||||||
|
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))
|
||||||
|
.stream().collect(Collectors.groupingBy(QsvUserAnswerRecord::getActivityId));
|
||||||
|
List<NutMap> rows = activities.stream()
|
||||||
|
.map((activity) -> buildOnlineRow(activity, recordGroup.getOrDefault(activity.getId(), new ArrayList<>())))
|
||||||
|
.filter((row) -> {
|
||||||
|
if (Boolean.TRUE.equals(isAnswered)) {
|
||||||
|
return row.getBoolean("isAnswered");
|
||||||
|
}
|
||||||
|
return !row.getBoolean("isAnswered") || row.getBoolean("canAnswer");
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
int filteredTotal = rows.size();
|
||||||
|
int fromIndex = Math.min((pageForm.getPageNumber() - 1) * pageForm.getPageSize(), filteredTotal);
|
||||||
|
int toIndex = Math.min(fromIndex + pageForm.getPageSize(), filteredTotal);
|
||||||
|
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), filteredTotal, rows.subList(fromIndex, toIndex)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("qsv.online")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
@ApiOperation("在线答题题目")
|
||||||
|
/**
|
||||||
|
* 查询在线答题题目,并在用户首次进入时生成答题记录。
|
||||||
|
*
|
||||||
|
* @param activityId 活动 ID
|
||||||
|
* @param viewOnly 是否仅查看已完成答题
|
||||||
|
* @return 活动、题目和答题记录信息
|
||||||
|
*/
|
||||||
|
public Result subjects(@Valid String activityId, Boolean viewOnly) {
|
||||||
|
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
|
||||||
|
if (activity == null) {
|
||||||
|
return Result.error("调查不存在");
|
||||||
|
}
|
||||||
|
QsvUserAnswerRecord answerRecord = findCurrentAnswerRecord(activityId, Boolean.TRUE.equals(viewOnly));
|
||||||
|
if (Boolean.TRUE.equals(viewOnly) && answerRecord == null) {
|
||||||
|
return Result.error("未找到已完成的答题记录");
|
||||||
|
}
|
||||||
|
boolean finishedView = Boolean.TRUE.equals(viewOnly) && answerRecord != null && Boolean.TRUE.equals(answerRecord.getIsFinish());
|
||||||
|
if (!finishedView) {
|
||||||
|
Result activityStatusResult = checkActivityStatus(activity);
|
||||||
|
if (activityStatusResult != null) {
|
||||||
|
return activityStatusResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result scopeResult = checkUserScope(activity);
|
||||||
|
if (scopeResult != null) {
|
||||||
|
return scopeResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
answerRecord = prepareAnswerRecord(activity, answerRecord);
|
||||||
|
List<QsvSubject> activitySubjects = queryActivitySubjectsForSync(activity);
|
||||||
|
// 后台调整题目后,同步未完成记录的题目范围,保证用户继续作答时题目完整。
|
||||||
|
syncAnswerRecordSubjects(answerRecord, activity, activitySubjects);
|
||||||
|
|
||||||
|
List<QsvSubject> subjects = querySubjectsByRecordOrder(answerRecord.getSubjectIds());
|
||||||
|
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
|
||||||
|
subjects.forEach((subject) -> {
|
||||||
|
if (subject.getUserSelectOptionIds() == null) {
|
||||||
|
subject.setUserSelectOptionIds(new ArrayList<>());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Result.success(NutMap.NEW()
|
||||||
|
.addv("activity", activity)
|
||||||
|
.addv("subjects", subjects)
|
||||||
|
.addv("answerRecordId", answerRecord.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("qsv.online")
|
||||||
|
@ApiOperation("在线答题记录")
|
||||||
|
/**
|
||||||
|
* 查询单条答题记录,用于恢复用户已填内容。
|
||||||
|
*
|
||||||
|
* @param answerRecordId 答题记录 ID
|
||||||
|
* @return 答题记录
|
||||||
|
*/
|
||||||
|
public Result answerRecord(@Valid String answerRecordId) {
|
||||||
|
QsvUserAnswerRecord record = dao.fetch(QsvUserAnswerRecord.class, answerRecordId);
|
||||||
|
return Result.success(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("qsv.online")
|
||||||
|
@ApiOperation("在线投票统计")
|
||||||
|
/**
|
||||||
|
* 查询投票活动的选项票数、占比和排名。
|
||||||
|
*
|
||||||
|
* @param activityId 投票活动 ID
|
||||||
|
* @return 投票统计数据
|
||||||
|
*/
|
||||||
|
public Result voteStats(@Valid String activityId) {
|
||||||
|
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
|
||||||
|
if (activity == null || !"VOTE".equals(activity.getCategory())) {
|
||||||
|
return Result.error("投票不存在");
|
||||||
|
}
|
||||||
|
Result scopeResult = checkUserScope(activity);
|
||||||
|
if (scopeResult != null) {
|
||||||
|
return scopeResult;
|
||||||
|
}
|
||||||
|
return Result.success(buildVoteStats(activityId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("qsv.online")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
@ApiOperation("提交在线答题")
|
||||||
|
/**
|
||||||
|
* 提交在线答题或投票结果,并写入用户答题记录。
|
||||||
|
*
|
||||||
|
* @param qsvAnswerParam 用户提交的题目答案
|
||||||
|
* @return 提交结果
|
||||||
|
*/
|
||||||
|
public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) {
|
||||||
|
QsvActivity activity = dao.fetch(QsvActivity.class, qsvAnswerParam.getActivityId());
|
||||||
|
Result activityStatusResult = checkActivityStatus(activity);
|
||||||
|
if (activityStatusResult != null) {
|
||||||
|
return activityStatusResult;
|
||||||
|
}
|
||||||
|
Result scopeResult = checkUserScope(activity);
|
||||||
|
if (scopeResult != null) {
|
||||||
|
return scopeResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId());
|
||||||
|
if (answerRecord == null || !SecurityUtil.getUserId().equals(answerRecord.getUserId())) {
|
||||||
|
return Result.error("答题记录不存在");
|
||||||
|
}
|
||||||
|
if (Boolean.TRUE.equals(answerRecord.getIsFinish())) {
|
||||||
|
return Result.error("您已完成该调查");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject extJson = answerRecord.getExtJson();
|
||||||
|
if (extJson == null) {
|
||||||
|
extJson = new JSONObject();
|
||||||
|
}
|
||||||
|
// 提交写入前统一校验多选题最大选择数,避免绕过页面限制提交超量选项。
|
||||||
|
String maxMultiLimitMessage = qsvUserAnswerRecordService.checkMaxMultiLimit(qsvAnswerParam);
|
||||||
|
if (maxMultiLimitMessage != null) {
|
||||||
|
return Result.error(maxMultiLimitMessage);
|
||||||
|
}
|
||||||
|
float totalScore = 0;
|
||||||
|
boolean isQuiz = "QUIZ".equals(activity.getCategory());
|
||||||
|
for (QsvAnswerParam.Subject subject : qsvAnswerParam.getSubjects()) {
|
||||||
|
// 将每道题的选项、填空内容和得分写入 extJson,保持答题记录结构统一。
|
||||||
|
JSONObject entries = extJson.get(subject.getId(), JSONObject.class);
|
||||||
|
if (ObjectUtil.isEmpty(entries)) {
|
||||||
|
entries = new JSONObject();
|
||||||
|
}
|
||||||
|
entries.set("optionIds", subject.getUserSelectOptionIds());
|
||||||
|
entries.set("text", subject.getUserFillContent());
|
||||||
|
entries.set("optionFillContents", subject.getOptionFillContents());
|
||||||
|
if (isQuiz) {
|
||||||
|
QsvCheckAnswerResult answerResult = qsvQuizService.calcScore(subject.getId(), subject.getUserSelectOptionIds());
|
||||||
|
entries.set("score", answerResult.getScore());
|
||||||
|
entries.set("isCorrect", answerResult.isCorrect());
|
||||||
|
totalScore += answerResult.getScore();
|
||||||
|
}
|
||||||
|
extJson.set(subject.getId(), entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
answerRecord.setExtJson(extJson);
|
||||||
|
if (isQuiz) {
|
||||||
|
answerRecord.setTotalScore(totalScore);
|
||||||
|
answerRecord.setAnswerTime(qsvAnswerParam.getAnswerTime());
|
||||||
|
}
|
||||||
|
answerRecord.setAttemptDate(new Date());
|
||||||
|
answerRecord.setSubmitTime(new Date());
|
||||||
|
answerRecord.setIsFinish(true);
|
||||||
|
dao.update(answerRecord);
|
||||||
|
if (isQuiz) {
|
||||||
|
qsvUserAnswerRecordService.calcByScoreMode(qsvAnswerParam.getActivityId(), SecurityUtil.getUserId());
|
||||||
|
}
|
||||||
|
return Result.success("提交成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result checkActivityStatus(QsvActivity activity) {
|
||||||
|
if (activity == null) {
|
||||||
|
return Result.error("调查不存在");
|
||||||
|
}
|
||||||
|
if (Boolean.FALSE.equals(activity.getEnabled())) {
|
||||||
|
return Result.error("活动已关闭,暂不能参与");
|
||||||
|
}
|
||||||
|
Date now = new Date();
|
||||||
|
if (activity.getStartTime() != null && activity.getStartTime().after(now)) {
|
||||||
|
return Result.error("调查尚未开始,请在开始后再参与");
|
||||||
|
}
|
||||||
|
if (activity.getEndTime() != null && activity.getEndTime().before(now)) {
|
||||||
|
return Result.error("调查已结束");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result checkUserScope(QsvActivity activity) {
|
||||||
|
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
|
||||||
|
.and("userId", "=", SecurityUtil.getUserId()));
|
||||||
|
if (count != 1) {
|
||||||
|
return Result.error("您无需参加此次调查,感谢您的关注!");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NutMap buildOnlineRow(QsvActivity activity, List<QsvUserAnswerRecord> records) {
|
||||||
|
boolean isAnswered = records.stream().anyMatch((record) -> Boolean.TRUE.equals(record.getIsFinish()));
|
||||||
|
boolean hasUnfinished = records.stream().anyMatch((record) -> !Boolean.TRUE.equals(record.getIsFinish()));
|
||||||
|
boolean canRepeat = "QUIZ".equals(activity.getCategory())
|
||||||
|
&& Boolean.TRUE.equals(activity.getRepeatable())
|
||||||
|
&& ("SCHEDULED".equals(activity.getMode()) || ("REGULAR".equals(activity.getMode()) && "ALL".equals(activity.getDisplayMode())));
|
||||||
|
boolean randomDisplayMode = "QUIZ".equals(activity.getCategory())
|
||||||
|
&& "REGULAR".equals(activity.getMode())
|
||||||
|
&& "RANDOM".equals(activity.getDisplayMode());
|
||||||
|
int maxAttempts = activity.getMaxAttempts() == null || activity.getMaxAttempts() < 1 ? 1 : activity.getMaxAttempts();
|
||||||
|
int totalRandom = activity.getTotalRandom() == null || activity.getTotalRandom() < 1 ? 1 : activity.getTotalRandom();
|
||||||
|
long attemptCount = records.size();
|
||||||
|
if ("SCHEDULED".equals(activity.getMode())) {
|
||||||
|
attemptCount = records.stream()
|
||||||
|
.filter((record) -> record.getAttemptDate() != null && DateUtil.isSameDay(record.getAttemptDate(), DateUtil.date()))
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
Date now = new Date();
|
||||||
|
boolean notStarted = activity.getStartTime() != null && activity.getStartTime().after(now);
|
||||||
|
boolean ended = activity.getEndTime() != null && activity.getEndTime().before(now);
|
||||||
|
boolean activityAvailable = Boolean.TRUE.equals(activity.getEnabled()) && !notStarted && !ended;
|
||||||
|
boolean canAnswerByAttempt = hasUnfinished || !isAnswered || (canRepeat && attemptCount < maxAttempts) || (randomDisplayMode && attemptCount < totalRandom);
|
||||||
|
boolean canAnswer = activityAvailable && canAnswerByAttempt;
|
||||||
|
boolean isVote = "VOTE".equals(activity.getCategory());
|
||||||
|
String actionText;
|
||||||
|
String actionType;
|
||||||
|
String statusText;
|
||||||
|
String statusType;
|
||||||
|
if (!activityAvailable && !isAnswered) {
|
||||||
|
if (!Boolean.TRUE.equals(activity.getEnabled())) {
|
||||||
|
actionText = "已关闭";
|
||||||
|
statusText = "已关闭";
|
||||||
|
} else if (notStarted) {
|
||||||
|
actionText = isVote ? "未开始投票" : "未开始答题";
|
||||||
|
statusText = "未开始";
|
||||||
|
} else {
|
||||||
|
actionText = isVote ? "投票已结束" : "答题已结束";
|
||||||
|
statusText = "已结束";
|
||||||
|
}
|
||||||
|
actionType = "unavailable";
|
||||||
|
statusType = "info";
|
||||||
|
} else if (hasUnfinished && canAnswer) {
|
||||||
|
actionText = isVote ? "继续投票" : "继续答题";
|
||||||
|
actionType = "answer";
|
||||||
|
statusText = isVote ? "投票中" : "答题中";
|
||||||
|
statusType = "warning";
|
||||||
|
} else if (!isAnswered && canAnswer) {
|
||||||
|
actionText = isVote ? "投票" : "答题";
|
||||||
|
actionType = "answer";
|
||||||
|
statusText = isVote ? "未投票" : "未答题";
|
||||||
|
statusType = "warning";
|
||||||
|
} else if (canAnswer) {
|
||||||
|
actionText = isVote ? "再次投票" : "再次答题";
|
||||||
|
actionType = "answer";
|
||||||
|
statusText = isVote ? "可再次投票" : "可再次答题";
|
||||||
|
statusType = "warning";
|
||||||
|
} else {
|
||||||
|
actionText = isVote ? "查看投票" : "查看答题";
|
||||||
|
actionType = "view";
|
||||||
|
statusText = isVote ? "已投票" : "已答题";
|
||||||
|
statusType = "success";
|
||||||
|
}
|
||||||
|
Optional<QsvUserAnswerRecord> latestFinishedRecord = records.stream()
|
||||||
|
.filter((record) -> Boolean.TRUE.equals(record.getIsFinish()))
|
||||||
|
.max((first, second) -> {
|
||||||
|
long firstTime = first.getSubmitTime() == null ? 0 : first.getSubmitTime().getTime();
|
||||||
|
long secondTime = second.getSubmitTime() == null ? 0 : second.getSubmitTime().getTime();
|
||||||
|
return Long.compare(firstTime, secondTime);
|
||||||
|
});
|
||||||
|
return NutMap.NEW()
|
||||||
|
.addv("id", activity.getId())
|
||||||
|
.addv("title", activity.getTitle())
|
||||||
|
.addv("category", activity.getCategory())
|
||||||
|
.addv("startTime", activity.getStartTime())
|
||||||
|
.addv("endTime", activity.getEndTime())
|
||||||
|
.addv("enabled", activity.getEnabled())
|
||||||
|
.addv("isAnswered", isAnswered)
|
||||||
|
.addv("hasUnfinished", hasUnfinished)
|
||||||
|
.addv("canAnswer", canAnswer)
|
||||||
|
.addv("notStarted", notStarted)
|
||||||
|
.addv("ended", ended)
|
||||||
|
.addv("canRepeat", canRepeat)
|
||||||
|
.addv("answeredCount", records.stream().filter((record) -> Boolean.TRUE.equals(record.getIsFinish())).count())
|
||||||
|
.addv("remainingAttempts", canRepeat ? Math.max(maxAttempts - attemptCount, 0) : (randomDisplayMode ? Math.max(totalRandom - attemptCount, 0) : 0))
|
||||||
|
.addv("actionText", actionText)
|
||||||
|
.addv("actionType", actionType)
|
||||||
|
.addv("statusText", statusText)
|
||||||
|
.addv("statusType", statusType)
|
||||||
|
.addv("totalScore", latestFinishedRecord.map(QsvUserAnswerRecord::getTotalScore).orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private QsvUserAnswerRecord findCurrentAnswerRecord(String activityId, boolean viewOnly) {
|
||||||
|
Cnd cnd = Cnd.where("activityId", "=", activityId)
|
||||||
|
.and("userId", "=", SecurityUtil.getUserId());
|
||||||
|
if (viewOnly) {
|
||||||
|
cnd.and("isFinish", "=", true).desc("submitTime").desc("createdAt");
|
||||||
|
} else {
|
||||||
|
cnd.and(Cnd.exps("isFinish", "=", false).or("isFinish", "is", null)).desc("createdAt");
|
||||||
|
}
|
||||||
|
return dao.fetch(QsvUserAnswerRecord.class, cnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private QsvUserAnswerRecord prepareAnswerRecord(QsvActivity activity, QsvUserAnswerRecord answerRecord) {
|
||||||
|
if (answerRecord != null) {
|
||||||
|
return answerRecord;
|
||||||
|
}
|
||||||
|
if ("QUIZ".equals(activity.getCategory())
|
||||||
|
&& "REGULAR".equals(activity.getMode())
|
||||||
|
&& "RANDOM".equals(activity.getDisplayMode())) {
|
||||||
|
return createRandomAnswerRecord(activity);
|
||||||
|
}
|
||||||
|
List<QsvSubject> activitySubjects = queryActivitySubjectsForSync(activity);
|
||||||
|
return qsvUserAnswerRecordService.insertRecord(activity.getId(), activitySubjects.stream().map(QsvSubject::getId).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private QsvUserAnswerRecord createRandomAnswerRecord(QsvActivity activity) {
|
||||||
|
Integer randomCount = activity.getRandomCount();
|
||||||
|
Integer totalRandom = activity.getTotalRandom();
|
||||||
|
if (randomCount == null || randomCount < 1) {
|
||||||
|
return qsvUserAnswerRecordService.insertRecord(activity.getId(), new ArrayList<>());
|
||||||
|
}
|
||||||
|
if (totalRandom == null || totalRandom < 1) {
|
||||||
|
totalRandom = 1;
|
||||||
|
}
|
||||||
|
List<QsvUserAnswerRecord> answerRecords = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activity.getId())
|
||||||
|
.and("userId", "=", SecurityUtil.getUserId()));
|
||||||
|
if (answerRecords.size() >= totalRandom) {
|
||||||
|
return answerRecords.stream()
|
||||||
|
.max((first, second) -> Integer.compare(first.getRandomNumber() == null ? 0 : first.getRandomNumber(), second.getRandomNumber() == null ? 0 : second.getRandomNumber()))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
List<String> usedSubjectIds = answerRecords.stream()
|
||||||
|
.filter((record) -> record.getSubjectIds() != null)
|
||||||
|
.flatMap((record) -> record.getSubjectIds().stream())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Cnd subjectCnd = Cnd.where("activityId", "=", activity.getId());
|
||||||
|
if (!usedSubjectIds.isEmpty()) {
|
||||||
|
subjectCnd.and("id", "not in", usedSubjectIds);
|
||||||
|
}
|
||||||
|
List<QsvSubject> subjects = dao.query(QsvSubject.class, subjectCnd.asc("sortNum"));
|
||||||
|
if (subjects.size() < randomCount) {
|
||||||
|
throw new RuntimeException("题目数不够,无法生成题目");
|
||||||
|
}
|
||||||
|
Collections.shuffle(subjects);
|
||||||
|
List<String> randomSubjectIds = subjects.subList(0, randomCount).stream().map(QsvSubject::getId).collect(Collectors.toList());
|
||||||
|
return qsvUserAnswerRecordService.insertRecord(activity.getId(), randomSubjectIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<QsvSubject> queryActivitySubjectsForSync(QsvActivity activity) {
|
||||||
|
Cnd cnd = Cnd.where("activityId", "=", activity.getId());
|
||||||
|
if ("QUIZ".equals(activity.getCategory())
|
||||||
|
&& "SCHEDULED".equals(activity.getMode())) {
|
||||||
|
cnd.and("displayDate", "=", DateUtil.today());
|
||||||
|
}
|
||||||
|
return dao.query(QsvSubject.class, cnd.asc("sortNum"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncAnswerRecordSubjects(QsvUserAnswerRecord answerRecord, QsvActivity activity, List<QsvSubject> activitySubjects) {
|
||||||
|
if (answerRecord == null || Boolean.TRUE.equals(answerRecord.getIsFinish())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> currentSubjectIds = activitySubjects.stream().map(QsvSubject::getId).collect(Collectors.toList());
|
||||||
|
List<String> recordSubjectIds = answerRecord.getSubjectIds();
|
||||||
|
if (recordSubjectIds == null) {
|
||||||
|
recordSubjectIds = new ArrayList<>();
|
||||||
|
}
|
||||||
|
JSONObject extJson = answerRecord.getExtJson();
|
||||||
|
if (extJson == null) {
|
||||||
|
extJson = new JSONObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean randomDisplayMode = "QUIZ".equals(activity.getCategory())
|
||||||
|
&& "REGULAR".equals(activity.getMode())
|
||||||
|
&& "RANDOM".equals(activity.getDisplayMode());
|
||||||
|
LinkedHashSet<String> currentSubjectIdSet = new LinkedHashSet<>(currentSubjectIds);
|
||||||
|
List<String> normalizedSubjectIds = recordSubjectIds.stream()
|
||||||
|
.filter(currentSubjectIdSet::contains)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (randomDisplayMode) {
|
||||||
|
currentSubjectIds = normalizedSubjectIds;
|
||||||
|
} else {
|
||||||
|
for (String subjectId : currentSubjectIds) {
|
||||||
|
if (!normalizedSubjectIds.contains(subjectId)) {
|
||||||
|
normalizedSubjectIds.add(subjectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean changed = !recordSubjectIds.equals(normalizedSubjectIds);
|
||||||
|
boolean subjectOrderLocked = Boolean.TRUE.equals(extJson.get("_subjectOrderLocked"));
|
||||||
|
if ("QUIZ".equals(activity.getCategory())
|
||||||
|
&& Boolean.TRUE.equals(activity.getShuffleSubject())
|
||||||
|
&& !subjectOrderLocked
|
||||||
|
&& isAnswerRecordEmpty(extJson, currentSubjectIds)) {
|
||||||
|
Collections.shuffle(normalizedSubjectIds);
|
||||||
|
extJson.set("_subjectOrderLocked", true);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String subjectId : currentSubjectIds) {
|
||||||
|
if (ObjectUtil.isEmpty(extJson.get(subjectId, JSONObject.class))) {
|
||||||
|
extJson.set(subjectId, Dict.create()
|
||||||
|
.set("optionIds", new ArrayList<>())
|
||||||
|
.set("text", null)
|
||||||
|
.set("optionFillContents", new JSONObject()));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
answerRecord.setSubjectIds(normalizedSubjectIds);
|
||||||
|
answerRecord.setExtJson(extJson);
|
||||||
|
dao.update(answerRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAnswerRecordEmpty(JSONObject extJson, List<String> subjectIds) {
|
||||||
|
for (String subjectId : subjectIds) {
|
||||||
|
JSONObject answer = extJson.get(subjectId, JSONObject.class);
|
||||||
|
if (ObjectUtil.isEmpty(answer)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ObjectUtil.isNotEmpty(answer.get("optionIds"))
|
||||||
|
|| ObjectUtil.isNotEmpty(answer.get("text"))
|
||||||
|
|| ObjectUtil.isNotEmpty(answer.get("optionFillContents"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<QsvSubject> querySubjectsByRecordOrder(List<String> subjectIds) {
|
||||||
|
if (subjectIds == null || subjectIds.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("id", "in", subjectIds));
|
||||||
|
Map<String, QsvSubject> subjectMap = subjects.stream()
|
||||||
|
.collect(Collectors.toMap(QsvSubject::getId, Function.identity(), (oldValue, newValue) -> oldValue));
|
||||||
|
return subjectIds.stream()
|
||||||
|
.map(subjectMap::get)
|
||||||
|
.filter(ObjectUtil::isNotEmpty)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private NutMap buildVoteStats(String activityId) {
|
||||||
|
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
|
||||||
|
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
|
||||||
|
|
||||||
|
Map<String, NutMap> optionStats = new LinkedHashMap<>();
|
||||||
|
for (QsvSubject subject : subjects) {
|
||||||
|
if (subject.getOptions() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (QsvOption option : subject.getOptions()) {
|
||||||
|
optionStats.put(option.getId(), NutMap.NEW()
|
||||||
|
.addv("optionId", option.getId())
|
||||||
|
.addv("subjectId", subject.getId())
|
||||||
|
.addv("text", option.getText())
|
||||||
|
.addv("imgUrl", option.getImgUrl())
|
||||||
|
.addv("votes", 0)
|
||||||
|
.addv("percent", 0D));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<QsvUserAnswerRecord> records = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
|
||||||
|
.and("isFinish", "=", true));
|
||||||
|
for (QsvUserAnswerRecord record : records) {
|
||||||
|
JSONObject extJson = record.getExtJson();
|
||||||
|
if (extJson == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (QsvSubject subject : subjects) {
|
||||||
|
JSONObject entry = extJson.get(subject.getId(), JSONObject.class);
|
||||||
|
if (entry == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<String> optionIds = entry.getBeanList("optionIds", String.class);
|
||||||
|
if (optionIds == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (String optionId : optionIds) {
|
||||||
|
NutMap stat = optionStats.get(optionId);
|
||||||
|
if (stat != null) {
|
||||||
|
stat.put("votes", stat.getInt("votes", 0) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalParticipants = records.size();
|
||||||
|
List<NutMap> rankList = new ArrayList<>(optionStats.values());
|
||||||
|
for (NutMap stat : rankList) {
|
||||||
|
int votes = stat.getInt("votes", 0);
|
||||||
|
double percent = totalParticipants == 0 ? 0D : votes * 100D / totalParticipants;
|
||||||
|
stat.put("percent", percent);
|
||||||
|
}
|
||||||
|
rankList.sort((left, right) -> Integer.compare(right.getInt("votes", 0), left.getInt("votes", 0)));
|
||||||
|
|
||||||
|
return NutMap.NEW()
|
||||||
|
.addv("totalParticipants", totalParticipants)
|
||||||
|
.addv("optionCount", optionStats.size())
|
||||||
|
.addv("optionStats", optionStats)
|
||||||
|
.addv("rankList", rankList);
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -4,11 +4,14 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
|
|||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.param.QsvQuizRankPageForm;
|
import com.budwk.app.zhgh.dayofficework.qsv.param.QsvQuizRankPageForm;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvQuizRankService;
|
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvQuizRankService;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.ioc.aop.Aop;
|
||||||
import org.nutz.ioc.loader.annotation.Inject;
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
@@ -64,5 +67,17 @@ public class QsvQuizRankController {
|
|||||||
qsvQuizRankService.exportXlsx(pageForm, response);
|
qsvQuizRankService.exportXlsx(pageForm, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("qsv.quiz.rank.delete")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
@ApiOperation("删除答题记录")
|
||||||
|
public Result deleteAnswerRecord(@Valid String id) {
|
||||||
|
QsvUserAnswerRecord record = dao.fetch(QsvUserAnswerRecord.class, id);
|
||||||
|
if (record == null) {
|
||||||
|
return Result.error("答题记录不存在");
|
||||||
|
}
|
||||||
|
dao.delete(QsvUserAnswerRecord.class, id);
|
||||||
|
return Result.success("删除成功");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+298
-5
@@ -1,18 +1,31 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.qsv.controller;
|
package com.budwk.app.zhgh.dayofficework.qsv.controller;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONArray;
|
||||||
import cn.hutool.json.JSONObject;
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.budwk.app.base.annotation.SLog;
|
import com.budwk.app.base.annotation.SLog;
|
||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.param.PageForm;
|
import com.budwk.app.base.param.PageForm;
|
||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
|
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord;
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvActivityService;
|
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvActivityService;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvSurveyService;
|
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvSurveyService;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.ioc.aop.Aop;
|
||||||
import org.nutz.ioc.loader.annotation.Inject;
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
import org.nutz.lang.util.NutMap;
|
import org.nutz.lang.util.NutMap;
|
||||||
@@ -21,6 +34,10 @@ import org.nutz.mvc.annotation.Ok;
|
|||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@IocBean
|
@IocBean
|
||||||
@@ -43,12 +60,19 @@ public class QsvSurveyController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@At("/reportPage")
|
||||||
|
@Ok("beetl:/platform/zhgh/dayofficework/qsv/survey/reportPage.html")
|
||||||
|
@SaCheckPermission("qsv.survey")
|
||||||
|
public void reportPage() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("qsv.survey")
|
@SaCheckPermission("qsv.survey")
|
||||||
@ApiOperation("分页查询")
|
@ApiOperation("分页查询")
|
||||||
public Result pageData(@Valid PageForm pageForm, Integer year) {
|
public Result pageData(@Valid PageForm pageForm, Integer year) {
|
||||||
Cnd cnd = Cnd.where("category", "=", "SURVEY");
|
Cnd cnd = Cnd.where("category", "in", new String[]{"SURVEY", "VOTE"});
|
||||||
cnd.andEX("YEAR(startTime)", "=", year);
|
cnd.andEX("YEAR(startTime)", "=", year);
|
||||||
cnd.desc("category");
|
cnd.desc("category");
|
||||||
Pagination pagination = qsvActivityService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
|
Pagination pagination = qsvActivityService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
|
||||||
@@ -63,6 +87,43 @@ public class QsvSurveyController {
|
|||||||
return Result.success(report);
|
return Result.success(report);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("qsv.survey")
|
||||||
|
@ApiOperation("查询调查信息")
|
||||||
|
public Result activityInfo(@Valid String activityId) {
|
||||||
|
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
|
||||||
|
NutMap data = NutMap.NEW();
|
||||||
|
if (activity != null) {
|
||||||
|
data.setv("id", activity.getId());
|
||||||
|
data.setv("title", activity.getTitle());
|
||||||
|
}
|
||||||
|
return Result.success(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("qsv.survey")
|
||||||
|
@ApiOperation("生成分类报告")
|
||||||
|
public Result categoryReport(@Valid String activityId, String conditions) {
|
||||||
|
List<NutMap> report = qsvSurveyService.categoryReport(activityId, conditions);
|
||||||
|
return Result.success(report);
|
||||||
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("qsv.survey")
|
||||||
|
@ApiOperation("生成交叉分析报告")
|
||||||
|
public Result crossReport(@Valid String activityId, @Valid String xSubjectIds, @Valid String ySubjectIds, String conditions) {
|
||||||
|
NutMap report = qsvSurveyService.crossReport(activityId, xSubjectIds, ySubjectIds, conditions);
|
||||||
|
return Result.success(report);
|
||||||
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("qsv.survey")
|
||||||
|
@ApiOperation("生成对比分析报告")
|
||||||
|
public Result compareReport(@Valid String activityId, @Valid String subjectId, @Valid String optionId) {
|
||||||
|
NutMap report = qsvSurveyService.compareReport(activityId, subjectId, optionId);
|
||||||
|
return Result.success(report);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@Ok("void")
|
@Ok("void")
|
||||||
@@ -72,13 +133,244 @@ public class QsvSurveyController {
|
|||||||
qsvSurveyService.exportReportXlsx(activityId, response);
|
qsvSurveyService.exportReportXlsx(activityId, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@Ok("void")
|
||||||
|
@SaCheckPermission("qsv.survey")
|
||||||
|
@ApiOperation("下载PDF报告")
|
||||||
|
public void exportReportPdf(String reportData, HttpServletResponse response) {
|
||||||
|
if (StrUtil.isBlank(reportData)) {
|
||||||
|
throw new IllegalArgumentException("报告数据不能为空");
|
||||||
|
}
|
||||||
|
JSONObject report = JSONUtil.parseObj(reportData);
|
||||||
|
String title = StrUtil.blankToDefault(report.getStr("title"), "分析报告");
|
||||||
|
String tabLabel = StrUtil.blankToDefault(report.getStr("tabLabel"), "分析报告");
|
||||||
|
try {
|
||||||
|
CommonDownloadUtil.download(safeFileName(title + "-" + tabLabel) + ".pdf", buildReportPdf(report), response);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("生成PDF报告失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] buildReportPdf(JSONObject report) throws IOException {
|
||||||
|
try (PDDocument document = new PDDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||||
|
PdfReportWriter writer = new PdfReportWriter(document);
|
||||||
|
writer.title(StrUtil.blankToDefault(report.getStr("title"), "分析报告"));
|
||||||
|
writer.text("报告类型:" + StrUtil.blankToDefault(report.getStr("tabLabel"), "分析报告"), 10);
|
||||||
|
writer.text("生成时间:" + DateUtil.now(), 10);
|
||||||
|
writer.gap(8);
|
||||||
|
|
||||||
|
JSONArray sections = report.getJSONArray("sections");
|
||||||
|
if (sections == null || sections.isEmpty()) {
|
||||||
|
writer.text("暂无可下载内容", 12);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < sections.size(); i++) {
|
||||||
|
JSONObject section = JSONUtil.parseObj(sections.get(i));
|
||||||
|
writer.section(StrUtil.blankToDefault(section.getStr("title"), "统计项"));
|
||||||
|
String description = section.getStr("description");
|
||||||
|
if (StrUtil.isNotBlank(description)) {
|
||||||
|
writer.text(description, 10);
|
||||||
|
}
|
||||||
|
String image = section.getStr("image");
|
||||||
|
if (StrUtil.isNotBlank(image)) {
|
||||||
|
writer.image(image);
|
||||||
|
}
|
||||||
|
writer.table(section.getJSONArray("rows"));
|
||||||
|
writer.gap(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.close();
|
||||||
|
document.save(out);
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String safeFileName(String fileName) {
|
||||||
|
return StrUtil.blankToDefault(fileName, "分析报告").replaceAll("[\\\\/:*?\"<>|\\r\\n]", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDType0Font loadChineseFont(PDDocument document, boolean bold) throws IOException {
|
||||||
|
String[] paths = bold
|
||||||
|
? new String[]{"/usr/share/fonts/chinese/simhei.ttf", "/usr/share/fonts/chinese/msyhbd.ttc", "/usr/share/fonts/chinese/simsun.ttc"}
|
||||||
|
: new String[]{"/usr/share/fonts/chinese/simfang.ttf", "/usr/share/fonts/chinese/simsun.ttc", "/usr/share/fonts/chinese/msyh.ttc"};
|
||||||
|
for (String path : paths) {
|
||||||
|
File file = new File(path);
|
||||||
|
if (file.exists()) {
|
||||||
|
return PDType0Font.load(document, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IOException("未找到可用的中文字体");
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PdfReportWriter {
|
||||||
|
private final PDDocument document;
|
||||||
|
private final PDType0Font regularFont;
|
||||||
|
private final PDType0Font boldFont;
|
||||||
|
private final float margin = 42F;
|
||||||
|
private final float pageWidth = PDRectangle.A4.getWidth();
|
||||||
|
private final float pageHeight = PDRectangle.A4.getHeight();
|
||||||
|
private final float contentWidth = pageWidth - margin * 2;
|
||||||
|
private final float sectionTopGap = 14F;
|
||||||
|
private final float sectionMinHeight = 88F;
|
||||||
|
private final float tableBottomGap = 16F;
|
||||||
|
private PDPageContentStream content;
|
||||||
|
private float y;
|
||||||
|
|
||||||
|
PdfReportWriter(PDDocument document) throws IOException {
|
||||||
|
this.document = document;
|
||||||
|
this.regularFont = loadChineseFont(document, false);
|
||||||
|
this.boldFont = loadChineseFont(document, true);
|
||||||
|
newPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void newPage() throws IOException {
|
||||||
|
if (content != null) {
|
||||||
|
content.close();
|
||||||
|
}
|
||||||
|
PDPage page = new PDPage(PDRectangle.A4);
|
||||||
|
document.addPage(page);
|
||||||
|
content = new PDPageContentStream(document, page);
|
||||||
|
y = pageHeight - margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() throws IOException {
|
||||||
|
if (content != null) {
|
||||||
|
content.close();
|
||||||
|
content = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ensure(float height) throws IOException {
|
||||||
|
if (y - height < margin) {
|
||||||
|
newPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gap(float height) throws IOException {
|
||||||
|
ensure(height);
|
||||||
|
y -= height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void title(String text) throws IOException {
|
||||||
|
ensure(36);
|
||||||
|
drawText(text, margin, y, 18, boldFont);
|
||||||
|
y -= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
void section(String text) throws IOException {
|
||||||
|
// Reserve enough space for the subject title and summary line before drawing the next question.
|
||||||
|
ensure(sectionMinHeight);
|
||||||
|
if (y < pageHeight - margin) {
|
||||||
|
y -= sectionTopGap;
|
||||||
|
ensure(sectionMinHeight - sectionTopGap);
|
||||||
|
}
|
||||||
|
drawText(text, margin, y, 13, boldFont);
|
||||||
|
y -= 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
void text(String text, float fontSize) throws IOException {
|
||||||
|
List<String> lines = wrap(StrUtil.blankToDefault(text, ""), fontSize, contentWidth, regularFont);
|
||||||
|
for (String line : lines) {
|
||||||
|
ensure(fontSize + 8);
|
||||||
|
drawText(line, margin, y, fontSize, regularFont);
|
||||||
|
y -= fontSize + 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void image(String dataUrl) throws IOException {
|
||||||
|
int commaIndex = dataUrl.indexOf(",");
|
||||||
|
if (commaIndex < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
byte[] bytes = Base64.getDecoder().decode(dataUrl.substring(commaIndex + 1));
|
||||||
|
PDImageXObject image = PDImageXObject.createFromByteArray(document, bytes, "chart");
|
||||||
|
float width = contentWidth;
|
||||||
|
float height = width * image.getHeight() / image.getWidth();
|
||||||
|
if (height > 260) {
|
||||||
|
height = 260;
|
||||||
|
width = height * image.getWidth() / image.getHeight();
|
||||||
|
}
|
||||||
|
ensure(height + 12);
|
||||||
|
content.drawImage(image, margin, y - height, width, height);
|
||||||
|
y -= height + 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
void table(JSONArray rows) throws IOException {
|
||||||
|
if (rows == null || rows.isEmpty()) {
|
||||||
|
// Empty text-question summaries also need bottom spacing before the next question starts.
|
||||||
|
ensure(42);
|
||||||
|
text("暂无统计数据", 10);
|
||||||
|
gap(tableBottomGap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float[] widths = new float[]{contentWidth * 0.46F, contentWidth * 0.15F, contentWidth * 0.17F, contentWidth * 0.22F};
|
||||||
|
// Keep the table header with at least the first data row when a table starts near the page bottom.
|
||||||
|
ensure(48);
|
||||||
|
drawTableRow(new String[]{"选项/内容", "数量", "占比", "备注"}, widths, true);
|
||||||
|
for (int i = 0; i < rows.size(); i++) {
|
||||||
|
JSONObject row = JSONUtil.parseObj(rows.get(i));
|
||||||
|
drawTableRow(new String[]{
|
||||||
|
StrUtil.blankToDefault(row.getStr("name"), ""),
|
||||||
|
StrUtil.blankToDefault(row.getStr("count"), ""),
|
||||||
|
StrUtil.blankToDefault(row.getStr("percent"), ""),
|
||||||
|
StrUtil.blankToDefault(row.getStr("remark"), "")
|
||||||
|
}, widths, false);
|
||||||
|
}
|
||||||
|
gap(tableBottomGap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawTableRow(String[] values, float[] widths, boolean header) throws IOException {
|
||||||
|
float rowHeight = 24F;
|
||||||
|
ensure(rowHeight);
|
||||||
|
float x = margin;
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
content.addRect(x, y - rowHeight, widths[i], rowHeight);
|
||||||
|
content.stroke();
|
||||||
|
drawText(clip(values[i], header ? 16 : 28), x + 5, y - 16, 9, header ? boldFont : regularFont);
|
||||||
|
x += widths[i];
|
||||||
|
}
|
||||||
|
y -= rowHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawText(String text, float x, float y, float fontSize, PDType0Font font) throws IOException {
|
||||||
|
content.beginText();
|
||||||
|
content.setFont(font, fontSize);
|
||||||
|
content.newLineAtOffset(x, y);
|
||||||
|
content.showText(StrUtil.blankToDefault(text, "").replaceAll("[\\r\\n\\t]", " "));
|
||||||
|
content.endText();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> wrap(String text, float fontSize, float maxWidth, PDType0Font font) throws IOException {
|
||||||
|
List<String> lines = new java.util.ArrayList<>();
|
||||||
|
StringBuilder line = new StringBuilder();
|
||||||
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
char ch = text.charAt(i);
|
||||||
|
String next = line.toString() + ch;
|
||||||
|
if (font.getStringWidth(next) / 1000 * fontSize > maxWidth && line.length() > 0) {
|
||||||
|
lines.add(line.toString());
|
||||||
|
line.setLength(0);
|
||||||
|
}
|
||||||
|
line.append(ch);
|
||||||
|
}
|
||||||
|
if (line.length() > 0 || lines.isEmpty()) {
|
||||||
|
lines.add(line.toString());
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
String clip(String text, int maxLength) {
|
||||||
|
if (text == null || text.length() <= maxLength) {
|
||||||
|
return StrUtil.blankToDefault(text, "");
|
||||||
|
}
|
||||||
|
return text.substring(0, maxLength) + "...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("qsv.survey")
|
@SaCheckPermission("qsv.survey")
|
||||||
@ApiOperation("选项选择详情")
|
@ApiOperation("选项选择详情")
|
||||||
public Result selectOptionUsers(@Valid String activityId, @Valid String subjectId, @Valid String optionId) {
|
public Result selectOptionUsers(@Valid String activityId, @Valid String subjectId, @Valid String optionId, String conditions) {
|
||||||
List<QsvUserAnswerRecord> answerRecords = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId));
|
List<NutMap> selectOptionUsers = qsvSurveyService.selectOptionUsers(activityId, subjectId, optionId, conditions);
|
||||||
List<QsvUserAnswerRecord> selectOptionUsers = answerRecords.stream().filter(ext -> ObjectUtil.isNotNull(ext.getExtJson().get(subjectId, JSONObject.class)) && ext.getExtJson().get(subjectId, JSONObject.class).getJSONArray("optionIds").contains(optionId)).toList();
|
|
||||||
return Result.success(selectOptionUsers);
|
return Result.success(selectOptionUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +387,7 @@ public class QsvSurveyController {
|
|||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("qsv.survey")
|
@SaCheckPermission("qsv.survey")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
@ApiOperation("删除用户答题记录")
|
@ApiOperation("删除用户答题记录")
|
||||||
@SLog(type = "qsv.survey", tag = "删除用户答题记录", msg = "删除用户答题记录")
|
@SLog(type = "qsv.survey", tag = "删除用户答题记录", msg = "删除用户答题记录")
|
||||||
public Result deleteUserAnswer(@Valid String id) {
|
public Result deleteUserAnswer(@Valid String id) {
|
||||||
|
|||||||
+70
-2
@@ -1,19 +1,29 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.qsv.h5controller;
|
package com.budwk.app.zhgh.dayofficework.qsv.h5controller;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.param.PageForm;
|
import com.budwk.app.base.param.PageForm;
|
||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
|
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvActivityService;
|
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvActivityService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
|
import org.nutz.dao.Dao;
|
||||||
import org.nutz.ioc.loader.annotation.Inject;
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
import org.nutz.mvc.annotation.Ok;
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
import org.nutz.mvc.annotation.Param;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@IocBean
|
@IocBean
|
||||||
@At("/platform/h5/qsv")
|
@At("/platform/h5/qsv")
|
||||||
@@ -23,6 +33,8 @@ public class H5QsvController {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private QsvActivityService qsvActivityService;
|
private QsvActivityService qsvActivityService;
|
||||||
|
@Inject
|
||||||
|
private Dao dao;
|
||||||
|
|
||||||
@At("")
|
@At("")
|
||||||
@Ok("beetl:/platform/zhghh5/dayofficework/qsv/index.html")
|
@Ok("beetl:/platform/zhghh5/dayofficework/qsv/index.html")
|
||||||
@@ -33,13 +45,69 @@ public class H5QsvController {
|
|||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("h5.qsv")
|
@SaCheckPermission("h5.qsv")
|
||||||
public Result pageData(@Valid PageForm pageForm, @Valid String category) {
|
public Result pageData(@Valid PageForm pageForm, @Valid String category, @Param("title") String title) {
|
||||||
Cnd cnd = Cnd.NEW();
|
Cnd cnd = Cnd.NEW();
|
||||||
cnd.andEX("category", "=", category);
|
cnd.andEX("category", "=", category);
|
||||||
|
cnd.and(Cnd.likeEX("title", title));
|
||||||
|
cnd.and(Cnd.exps("enabled", "=", true).or("enabled", "is", null));
|
||||||
|
LocalDate currentYearFirstDay = LocalDate.now().withDayOfYear(1);
|
||||||
|
Date currentYearStart = Date.from(currentYearFirstDay.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||||
|
Date nextYearStart = Date.from(currentYearFirstDay.plusYears(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||||
|
cnd.and("startTime", ">=", currentYearStart);
|
||||||
|
cnd.and("startTime", "<", nextYearStart);
|
||||||
cnd.desc("startTime");
|
cnd.desc("startTime");
|
||||||
Pagination pagination = qsvActivityService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
|
Pagination pagination = qsvActivityService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
|
||||||
|
fillAnsweredStatus(pagination);
|
||||||
return Result.success(pagination);
|
return Result.success(pagination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fillAnsweredStatus(Pagination pagination) {
|
||||||
|
List<NutMap> rows = pagination.getList();
|
||||||
|
if (rows == null || rows.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> activityIds = rows.stream()
|
||||||
|
.map(this::getRowActivityId)
|
||||||
|
.filter(id -> id != null && !id.isBlank())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (activityIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<String> answeredActivityIds = dao.query(QsvUserAnswerRecord.class, Cnd.where("userId", "=", SecurityUtil.getUserId())
|
||||||
|
.and("activityId", "in", activityIds)
|
||||||
|
.and("isFinish", "=", true))
|
||||||
|
.stream()
|
||||||
|
.map(QsvUserAnswerRecord::getActivityId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
rows.forEach(row -> {
|
||||||
|
boolean isAnswered = answeredActivityIds.contains(getRowActivityId(row));
|
||||||
|
row.put("isAnswered", isAnswered);
|
||||||
|
row.put("answeredText", isAnswered ? getAnsweredText(row.getString("category")) : "");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRowActivityId(NutMap row) {
|
||||||
|
String id = row.getString("id");
|
||||||
|
if (id == null || id.isBlank()) {
|
||||||
|
id = row.getString("ID");
|
||||||
|
}
|
||||||
|
if (id == null || id.isBlank()) {
|
||||||
|
id = row.getString("activityId");
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAnsweredText(String category) {
|
||||||
|
if ("QUIZ".equals(category)) {
|
||||||
|
return "已答题";
|
||||||
|
}
|
||||||
|
if ("SURVEY".equals(category)) {
|
||||||
|
return "已填写";
|
||||||
|
}
|
||||||
|
if ("VOTE".equals(category)) {
|
||||||
|
return "已投票";
|
||||||
|
}
|
||||||
|
return "已完成";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-1
@@ -124,7 +124,9 @@ public class H5QsvQuizController {
|
|||||||
//首次进来生成答题记录
|
//首次进来生成答题记录
|
||||||
if ("ALL".equals(displayMode)) {
|
if ("ALL".equals(displayMode)) {
|
||||||
resultSubjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
|
resultSubjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
|
||||||
answerRecordId = qsvUserAnswerRecordService.insertRecord(activityId, resultSubjects.stream().map(QsvSubject::getId).collect(Collectors.toList())).getId();
|
QsvUserAnswerRecord answerRecord = qsvUserAnswerRecordService.insertRecord(activityId, resultSubjects.stream().map(QsvSubject::getId).collect(Collectors.toList()));
|
||||||
|
answerRecordId = answerRecord.getId();
|
||||||
|
resultSubjects = querySubjectsByRecordOrder(answerRecord.getSubjectIds());
|
||||||
} else if ("RANDOM".equals(displayMode)) {
|
} else if ("RANDOM".equals(displayMode)) {
|
||||||
//单次随机抽取题目数量
|
//单次随机抽取题目数量
|
||||||
Integer randomCount = activity.getRandomCount();
|
Integer randomCount = activity.getRandomCount();
|
||||||
@@ -333,8 +335,14 @@ public class H5QsvQuizController {
|
|||||||
@At
|
@At
|
||||||
@ApiOperation("提交答题")
|
@ApiOperation("提交答题")
|
||||||
@SLog(type = "qsv.quiz", tag = "提交答题", msg = "问卷调查提交答题")
|
@SLog(type = "qsv.quiz", tag = "提交答题", msg = "问卷调查提交答题")
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) {
|
public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) {
|
||||||
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId());
|
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId());
|
||||||
|
// 提交写入前统一校验多选题最大选择数,避免移动端绕过页面限制提交超量选项。
|
||||||
|
String maxMultiLimitMessage = qsvUserAnswerRecordService.checkMaxMultiLimit(qsvAnswerParam);
|
||||||
|
if (maxMultiLimitMessage != null) {
|
||||||
|
return Result.error(maxMultiLimitMessage);
|
||||||
|
}
|
||||||
JSONObject extJson = answerRecord.getExtJson();
|
JSONObject extJson = answerRecord.getExtJson();
|
||||||
|
|
||||||
float totalScore = 0;
|
float totalScore = 0;
|
||||||
@@ -396,4 +404,17 @@ public class H5QsvQuizController {
|
|||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<QsvSubject> querySubjectsByRecordOrder(List<String> subjectIds) {
|
||||||
|
if (subjectIds == null || subjectIds.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("id", "in", subjectIds));
|
||||||
|
Map<String, QsvSubject> subjectMap = subjects.stream()
|
||||||
|
.collect(Collectors.toMap(QsvSubject::getId, java.util.function.Function.identity(), (oldValue, newValue) -> oldValue));
|
||||||
|
return subjectIds.stream()
|
||||||
|
.map(subjectMap::get)
|
||||||
|
.filter(ObjectUtil::isNotEmpty)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+117
-3
@@ -12,6 +12,8 @@ import com.budwk.app.zhgh.dayofficework.qsv.param.QsvAnswerParam;
|
|||||||
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvUserAnswerRecordService;
|
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvUserAnswerRecordService;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
|
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.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
import org.nutz.lang.util.NutMap;
|
import org.nutz.lang.util.NutMap;
|
||||||
@@ -22,7 +24,11 @@ import org.nutz.mvc.annotation.Param;
|
|||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@IocBean
|
@IocBean
|
||||||
@At("/platform/h5/qsv/survey")
|
@At("/platform/h5/qsv/survey")
|
||||||
@@ -40,8 +46,13 @@ public class H5QsvSurveyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public Result subjects(String activityId) {
|
public Result subjects(String activityId) {
|
||||||
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
|
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
|
||||||
|
Result activityStatusResult = checkActivityStatus(activity);
|
||||||
|
if (activityStatusResult != null) {
|
||||||
|
return activityStatusResult;
|
||||||
|
}
|
||||||
|
|
||||||
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
|
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
|
||||||
.and("userId", "=", SecurityUtil.getUserId()));
|
.and("userId", "=", SecurityUtil.getUserId()));
|
||||||
@@ -51,20 +62,21 @@ public class H5QsvSurveyController {
|
|||||||
|
|
||||||
String answerRecordId = null;
|
String answerRecordId = null;
|
||||||
|
|
||||||
|
List<QsvSubject> activitySubjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
|
||||||
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
|
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
|
||||||
.and("userId", "=", SecurityUtil.getUserId()));
|
.and("userId", "=", SecurityUtil.getUserId()));
|
||||||
if (answerRecord == null) {
|
if (answerRecord == null) {
|
||||||
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
|
qsvUserAnswerRecordService.insertRecord(activityId, activitySubjects.stream().map(QsvSubject::getId).toList());
|
||||||
qsvUserAnswerRecordService.insertRecord(activityId, subjects.stream().map(QsvSubject::getId).toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QsvUserAnswerRecord answerRecord2 = dao.fetch(QsvUserAnswerRecord.class,
|
QsvUserAnswerRecord answerRecord2 = dao.fetch(QsvUserAnswerRecord.class,
|
||||||
Cnd.where("activityId", "=", activityId)
|
Cnd.where("activityId", "=", activityId)
|
||||||
.and("userId", "=", SecurityUtil.getUserId()));
|
.and("userId", "=", SecurityUtil.getUserId()));
|
||||||
|
syncAnswerRecordSubjects(answerRecord2, activitySubjects);
|
||||||
|
|
||||||
answerRecordId = answerRecord2.getId();
|
answerRecordId = answerRecord2.getId();
|
||||||
|
|
||||||
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("id", "in", answerRecord2.getSubjectIds()).asc("sortNum"));
|
List<QsvSubject> subjects = querySubjectsByRecordOrder(answerRecord2.getSubjectIds());
|
||||||
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
|
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
|
||||||
|
|
||||||
for (QsvSubject subject : subjects) {
|
for (QsvSubject subject : subjects) {
|
||||||
@@ -77,6 +89,95 @@ public class H5QsvSurveyController {
|
|||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验调查活动是否处于可答题状态。activity 为空表示活动不存在;
|
||||||
|
* enabled 为 false 表示活动已关闭,当前时间早于 startTime 表示未开始,当前时间晚于 endTime 表示已结束。
|
||||||
|
*
|
||||||
|
* @param activity 当前调查活动,包含开启状态、开始时间和结束时间
|
||||||
|
* @return 状态或时间不允许答题时返回错误结果,允许答题时返回 null
|
||||||
|
*/
|
||||||
|
private Result checkActivityStatus(QsvActivity activity) {
|
||||||
|
if (activity == null) {
|
||||||
|
return Result.error("调查不存在");
|
||||||
|
}
|
||||||
|
if (Boolean.FALSE.equals(activity.getEnabled())) {
|
||||||
|
return Result.error("活动已关闭,暂不能参与");
|
||||||
|
}
|
||||||
|
Date now = new Date();
|
||||||
|
if (activity.getStartTime() != null && activity.getStartTime().after(now)) {
|
||||||
|
return Result.error("调查尚未开始,请在开始后再参与");
|
||||||
|
}
|
||||||
|
if (activity.getEndTime() != null && activity.getEndTime().before(now)) {
|
||||||
|
return Result.error("调查已结束");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步问卷当前题目到未完成答题记录。后台新增题目后,已进入过问卷的用户记录里没有新题ID,
|
||||||
|
* 这里补齐 subjectIds 和 extJson,保证手机端能拿到最新题目并正常提交答案。
|
||||||
|
*
|
||||||
|
* @param answerRecord 用户当前问卷答题记录
|
||||||
|
* @param activitySubjects 当前活动下按排序查询出的最新题目列表
|
||||||
|
*/
|
||||||
|
private void syncAnswerRecordSubjects(QsvUserAnswerRecord answerRecord, List<QsvSubject> activitySubjects) {
|
||||||
|
if (answerRecord == null || Boolean.TRUE.equals(answerRecord.getIsFinish())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> currentSubjectIds = activitySubjects.stream().map(QsvSubject::getId).collect(Collectors.toList());
|
||||||
|
List<String> recordSubjectIds = answerRecord.getSubjectIds();
|
||||||
|
if (recordSubjectIds == null) {
|
||||||
|
recordSubjectIds = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject extJson = answerRecord.getExtJson();
|
||||||
|
if (extJson == null) {
|
||||||
|
extJson = new JSONObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedHashSet<String> currentSubjectIdSet = new LinkedHashSet<>(currentSubjectIds);
|
||||||
|
List<String> normalizedSubjectIds = recordSubjectIds.stream()
|
||||||
|
.filter(currentSubjectIdSet::contains)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
for (String subjectId : currentSubjectIds) {
|
||||||
|
if (!normalizedSubjectIds.contains(subjectId)) {
|
||||||
|
normalizedSubjectIds.add(subjectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean changed = !recordSubjectIds.equals(normalizedSubjectIds);
|
||||||
|
for (String subjectId : currentSubjectIds) {
|
||||||
|
if (ObjectUtil.isEmpty(extJson.get(subjectId, JSONObject.class))) {
|
||||||
|
JSONObject entry = new JSONObject();
|
||||||
|
entry.set("optionIds", new ArrayList<>());
|
||||||
|
entry.set("text", null);
|
||||||
|
entry.set("optionFillContents", new JSONObject());
|
||||||
|
extJson.set(subjectId, entry);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
answerRecord.setSubjectIds(normalizedSubjectIds);
|
||||||
|
answerRecord.setExtJson(extJson);
|
||||||
|
dao.update(answerRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<QsvSubject> querySubjectsByRecordOrder(List<String> subjectIds) {
|
||||||
|
if (subjectIds == null || subjectIds.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("id", "in", subjectIds));
|
||||||
|
Map<String, QsvSubject> subjectMap = subjects.stream()
|
||||||
|
.collect(Collectors.toMap(QsvSubject::getId, Function.identity(), (oldValue, newValue) -> oldValue));
|
||||||
|
return subjectIds.stream()
|
||||||
|
.map(subjectMap::get)
|
||||||
|
.filter(ObjectUtil::isNotEmpty)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
public Result answerRecord(@Valid String answerRecordId) {
|
public Result answerRecord(@Valid String answerRecordId) {
|
||||||
QsvUserAnswerRecord record = dao.fetch(QsvUserAnswerRecord.class, answerRecordId);
|
QsvUserAnswerRecord record = dao.fetch(QsvUserAnswerRecord.class, answerRecordId);
|
||||||
@@ -84,8 +185,20 @@ public class H5QsvSurveyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) {
|
public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) {
|
||||||
|
QsvActivity activity = dao.fetch(QsvActivity.class, qsvAnswerParam.getActivityId());
|
||||||
|
Result activityStatusResult = checkActivityStatus(activity);
|
||||||
|
if (activityStatusResult != null) {
|
||||||
|
return activityStatusResult;
|
||||||
|
}
|
||||||
|
|
||||||
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId());
|
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId());
|
||||||
|
// 提交写入前统一校验多选题最大选择数,避免移动端绕过页面限制提交超量选项。
|
||||||
|
String maxMultiLimitMessage = qsvUserAnswerRecordService.checkMaxMultiLimit(qsvAnswerParam);
|
||||||
|
if (maxMultiLimitMessage != null) {
|
||||||
|
return Result.error(maxMultiLimitMessage);
|
||||||
|
}
|
||||||
JSONObject extJson = answerRecord.getExtJson();
|
JSONObject extJson = answerRecord.getExtJson();
|
||||||
|
|
||||||
for (QsvAnswerParam.Subject subject : qsvAnswerParam.getSubjects()) {
|
for (QsvAnswerParam.Subject subject : qsvAnswerParam.getSubjects()) {
|
||||||
@@ -93,6 +206,7 @@ public class H5QsvSurveyController {
|
|||||||
if (ObjectUtil.isNotEmpty(entries)) {
|
if (ObjectUtil.isNotEmpty(entries)) {
|
||||||
entries.set("optionIds", subject.getUserSelectOptionIds());
|
entries.set("optionIds", subject.getUserSelectOptionIds());
|
||||||
entries.set("text", subject.getUserFillContent());
|
entries.set("text", subject.getUserFillContent());
|
||||||
|
entries.set("optionFillContents", subject.getOptionFillContents());
|
||||||
}
|
}
|
||||||
extJson.set(subject.getId(), entries);
|
extJson.set(subject.getId(), entries);
|
||||||
}
|
}
|
||||||
|
|||||||
+339
@@ -0,0 +1,339 @@
|
|||||||
|
package com.budwk.app.zhgh.dayofficework.qsv.h5controller;
|
||||||
|
|
||||||
|
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.dayofficework.qsv.models.QsvActivity;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvOption;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.param.QsvAnswerParam;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvUserAnswerRecordService;
|
||||||
|
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.lang.util.NutMap;
|
||||||
|
import org.nutz.mvc.annotation.At;
|
||||||
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
import org.nutz.mvc.annotation.Param;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@IocBean
|
||||||
|
@At("/platform/h5/qsv/vote")
|
||||||
|
@Ok("json:full")
|
||||||
|
/**
|
||||||
|
* 移动端投票控制器,负责投票页面、题目读取、投票提交和排行榜统计。
|
||||||
|
*/
|
||||||
|
public class H5QsvVoteController {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Dao dao;
|
||||||
|
@Inject
|
||||||
|
private QsvUserAnswerRecordService qsvUserAnswerRecordService;
|
||||||
|
|
||||||
|
@At("")
|
||||||
|
@Ok("beetl:/platform/zhghh5/dayofficework/qsv/vote/index.html")
|
||||||
|
/**
|
||||||
|
* 打开移动端投票页面。
|
||||||
|
*/
|
||||||
|
public void index() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询投票题目,并为首次进入的用户创建投票记录。
|
||||||
|
*
|
||||||
|
* @param activityId 投票活动 ID
|
||||||
|
* @return 投票活动、题目、答题记录和统计数据
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public Result subjects(String activityId) {
|
||||||
|
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
|
||||||
|
Result activityStatusResult = checkActivityReadable(activity);
|
||||||
|
if (activityStatusResult != null) {
|
||||||
|
return activityStatusResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result scopeResult = checkScope(activity);
|
||||||
|
if (scopeResult != null) {
|
||||||
|
return scopeResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<QsvSubject> activitySubjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
|
||||||
|
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
|
||||||
|
.and("userId", "=", SecurityUtil.getUserId()));
|
||||||
|
if (answerRecord == null) {
|
||||||
|
// 首次进入投票时初始化记录,后续提交时复用同一条记录。
|
||||||
|
List<String> subjectIds = activitySubjects.stream()
|
||||||
|
.map(QsvSubject::getId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
answerRecord = qsvUserAnswerRecordService.insertRecord(activityId, subjectIds);
|
||||||
|
}
|
||||||
|
syncAnswerRecordSubjects(answerRecord, activitySubjects);
|
||||||
|
|
||||||
|
List<QsvSubject> subjects = querySubjectsByRecordOrder(answerRecord.getSubjectIds());
|
||||||
|
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
|
||||||
|
for (QsvSubject subject : subjects) {
|
||||||
|
if (subject.getUserSelectOptionIds() == null) {
|
||||||
|
subject.setUserSelectOptionIds(new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NutMap result = NutMap.NEW()
|
||||||
|
.addv("subjects", subjects)
|
||||||
|
.addv("answerRecordId", answerRecord.getId())
|
||||||
|
.addv("answerRecord", answerRecord)
|
||||||
|
.addv("activity", activity)
|
||||||
|
.addv("voteStats", buildVoteStats(activityId));
|
||||||
|
return Result.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
/**
|
||||||
|
* 查询投票记录,用于恢复用户已选择的投票项。
|
||||||
|
*
|
||||||
|
* @param answerRecordId 投票记录 ID
|
||||||
|
* @return 投票记录
|
||||||
|
*/
|
||||||
|
public Result answerRecord(@Valid String answerRecordId) {
|
||||||
|
QsvUserAnswerRecord record = dao.fetch(QsvUserAnswerRecord.class, answerRecordId);
|
||||||
|
return Result.success(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
/**
|
||||||
|
* 查询投票排行榜和统计数据。
|
||||||
|
*
|
||||||
|
* @param activityId 投票活动 ID
|
||||||
|
* @return 投票统计数据
|
||||||
|
*/
|
||||||
|
public Result rank(@Valid String activityId) {
|
||||||
|
return Result.success(buildVoteStats(activityId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交移动端投票结果,投票成功后标记记录为完成。
|
||||||
|
*
|
||||||
|
* @param qsvAnswerParam 用户提交的投票答案
|
||||||
|
* @return 提交结果
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) {
|
||||||
|
QsvActivity activity = dao.fetch(QsvActivity.class, qsvAnswerParam.getActivityId());
|
||||||
|
Result activityStatusResult = checkActivityWritable(activity);
|
||||||
|
if (activityStatusResult != null) {
|
||||||
|
return activityStatusResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId());
|
||||||
|
if (answerRecord == null || !SecurityUtil.getUserId().equals(answerRecord.getUserId())) {
|
||||||
|
return Result.error("\u6295\u7968\u8bb0\u5f55\u4e0d\u5b58\u5728");
|
||||||
|
}
|
||||||
|
if (Boolean.TRUE.equals(answerRecord.getIsFinish())) {
|
||||||
|
return Result.error("\u60a8\u5df2\u5b8c\u6210\u6295\u7968");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject extJson = answerRecord.getExtJson();
|
||||||
|
if (extJson == null) {
|
||||||
|
extJson = new JSONObject();
|
||||||
|
}
|
||||||
|
// 提交写入前统一校验多选题最大选择数,避免移动端投票绕过页面限制提交超量选项。
|
||||||
|
String maxMultiLimitMessage = qsvUserAnswerRecordService.checkMaxMultiLimit(qsvAnswerParam);
|
||||||
|
if (maxMultiLimitMessage != null) {
|
||||||
|
return Result.error(maxMultiLimitMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (QsvAnswerParam.Subject subject : qsvAnswerParam.getSubjects()) {
|
||||||
|
// 逐题保存用户选择和补充内容,便于统计票数与查看详情。
|
||||||
|
JSONObject entries = extJson.get(subject.getId(), JSONObject.class);
|
||||||
|
if (entries == null) {
|
||||||
|
entries = new JSONObject();
|
||||||
|
}
|
||||||
|
entries.set("optionIds", subject.getUserSelectOptionIds());
|
||||||
|
entries.set("text", subject.getUserFillContent());
|
||||||
|
entries.set("optionFillContents", subject.getOptionFillContents());
|
||||||
|
extJson.set(subject.getId(), entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
answerRecord.setExtJson(extJson);
|
||||||
|
answerRecord.setAttemptDate(new Date());
|
||||||
|
answerRecord.setSubmitTime(new Date());
|
||||||
|
answerRecord.setIsFinish(true);
|
||||||
|
dao.update(answerRecord);
|
||||||
|
|
||||||
|
return Result.success("\u6295\u7968\u6210\u529f");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result checkActivityReadable(QsvActivity activity) {
|
||||||
|
if (activity == null) {
|
||||||
|
return Result.error("\u6295\u7968\u4e0d\u5b58\u5728");
|
||||||
|
}
|
||||||
|
if (Boolean.FALSE.equals(activity.getEnabled())) {
|
||||||
|
return Result.error("\u6d3b\u52a8\u5df2\u5173\u95ed\uff0c\u6682\u4e0d\u80fd\u53c2\u4e0e");
|
||||||
|
}
|
||||||
|
Date now = new Date();
|
||||||
|
if (activity.getStartTime() != null && activity.getStartTime().after(now)) {
|
||||||
|
return Result.error("\u6295\u7968\u5c1a\u672a\u5f00\u59cb\uff0c\u8bf7\u5728\u5f00\u59cb\u540e\u518d\u53c2\u4e0e");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result checkActivityWritable(QsvActivity activity) {
|
||||||
|
Result readableResult = checkActivityReadable(activity);
|
||||||
|
if (readableResult != null) {
|
||||||
|
return readableResult;
|
||||||
|
}
|
||||||
|
Date now = new Date();
|
||||||
|
if (activity.getEndTime() != null && activity.getEndTime().before(now)) {
|
||||||
|
return Result.error("\u6295\u7968\u5df2\u7ed3\u675f");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result checkScope(QsvActivity activity) {
|
||||||
|
if (activity.getGroupId() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
|
||||||
|
.and("userId", "=", SecurityUtil.getUserId()));
|
||||||
|
if (count != 1) {
|
||||||
|
return Result.error("\u60a8\u65e0\u9700\u53c2\u52a0\u6b64\u6b21\u6295\u7968\uff0c\u611f\u8c22\u60a8\u7684\u5173\u6ce8\uff01");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncAnswerRecordSubjects(QsvUserAnswerRecord answerRecord, List<QsvSubject> activitySubjects) {
|
||||||
|
if (answerRecord == null || Boolean.TRUE.equals(answerRecord.getIsFinish())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 后台调整投票题目后,未完成记录需要同步当前有效题目,避免提交旧题目。
|
||||||
|
List<String> currentSubjectIds = activitySubjects.stream()
|
||||||
|
.map(QsvSubject::getId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
List<String> recordSubjectIds = answerRecord.getSubjectIds();
|
||||||
|
if (recordSubjectIds == null) {
|
||||||
|
recordSubjectIds = new ArrayList<>();
|
||||||
|
}
|
||||||
|
JSONObject extJson = answerRecord.getExtJson();
|
||||||
|
if (extJson == null) {
|
||||||
|
extJson = new JSONObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedHashSet<String> currentSubjectIdSet = new LinkedHashSet<>(currentSubjectIds);
|
||||||
|
List<String> normalizedSubjectIds = recordSubjectIds.stream()
|
||||||
|
.filter(currentSubjectIdSet::contains)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
for (String subjectId : currentSubjectIds) {
|
||||||
|
if (!normalizedSubjectIds.contains(subjectId)) {
|
||||||
|
normalizedSubjectIds.add(subjectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean changed = !recordSubjectIds.equals(normalizedSubjectIds);
|
||||||
|
for (String subjectId : currentSubjectIds) {
|
||||||
|
if (ObjectUtil.isEmpty(extJson.get(subjectId, JSONObject.class))) {
|
||||||
|
JSONObject entry = new JSONObject();
|
||||||
|
entry.set("optionIds", new ArrayList<>());
|
||||||
|
entry.set("text", null);
|
||||||
|
entry.set("optionFillContents", new JSONObject());
|
||||||
|
extJson.set(subjectId, entry);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
answerRecord.setSubjectIds(normalizedSubjectIds);
|
||||||
|
answerRecord.setExtJson(extJson);
|
||||||
|
dao.update(answerRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<QsvSubject> querySubjectsByRecordOrder(List<String> subjectIds) {
|
||||||
|
if (subjectIds == null || subjectIds.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("id", "in", subjectIds));
|
||||||
|
Map<String, QsvSubject> subjectMap = subjects.stream()
|
||||||
|
.collect(Collectors.toMap(QsvSubject::getId, Function.identity(), (oldValue, newValue) -> oldValue));
|
||||||
|
return subjectIds.stream()
|
||||||
|
.map(subjectMap::get)
|
||||||
|
.filter(ObjectUtil::isNotEmpty)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private NutMap buildVoteStats(String activityId) {
|
||||||
|
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
|
||||||
|
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
|
||||||
|
|
||||||
|
Map<String, NutMap> optionStats = new LinkedHashMap<>();
|
||||||
|
for (QsvSubject subject : subjects) {
|
||||||
|
if (subject.getOptions() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (QsvOption option : subject.getOptions()) {
|
||||||
|
optionStats.put(option.getId(), NutMap.NEW()
|
||||||
|
.addv("optionId", option.getId())
|
||||||
|
.addv("subjectId", subject.getId())
|
||||||
|
.addv("text", option.getText())
|
||||||
|
.addv("imgUrl", option.getImgUrl())
|
||||||
|
.addv("votes", 0)
|
||||||
|
.addv("percent", 0D));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<QsvUserAnswerRecord> records = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
|
||||||
|
.and("isFinish", "=", true));
|
||||||
|
for (QsvUserAnswerRecord record : records) {
|
||||||
|
JSONObject extJson = record.getExtJson();
|
||||||
|
if (extJson == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (QsvSubject subject : subjects) {
|
||||||
|
JSONObject entry = extJson.get(subject.getId(), JSONObject.class);
|
||||||
|
if (entry == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<String> optionIds = entry.getBeanList("optionIds", String.class);
|
||||||
|
if (optionIds == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (String optionId : optionIds) {
|
||||||
|
NutMap stat = optionStats.get(optionId);
|
||||||
|
if (stat != null) {
|
||||||
|
stat.put("votes", stat.getInt("votes", 0) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalParticipants = records.size();
|
||||||
|
List<NutMap> rankList = new ArrayList<>(optionStats.values());
|
||||||
|
for (NutMap stat : rankList) {
|
||||||
|
int votes = stat.getInt("votes", 0);
|
||||||
|
double percent = totalParticipants == 0 ? 0D : votes * 100D / totalParticipants;
|
||||||
|
stat.put("percent", percent);
|
||||||
|
}
|
||||||
|
rankList.sort((left, right) -> Integer.compare(right.getInt("votes", 0), left.getInt("votes", 0)));
|
||||||
|
|
||||||
|
return NutMap.NEW()
|
||||||
|
.addv("totalParticipants", totalParticipants)
|
||||||
|
.addv("optionCount", optionStats.size())
|
||||||
|
.addv("optionStats", optionStats)
|
||||||
|
.addv("rankList", rankList);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.qsv.models;
|
package com.budwk.app.zhgh.dayofficework.qsv.models;
|
||||||
|
|
||||||
import com.budwk.app.base.model.BaseModel;
|
import com.budwk.app.base.model.BaseModel;
|
||||||
|
import com.budwk.app.sys.models.Sys_home_activity;
|
||||||
|
import com.budwk.app.sys.services.SysHomeConvert;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.nutz.dao.entity.annotation.*;
|
import org.nutz.dao.entity.annotation.*;
|
||||||
@@ -14,7 +16,7 @@ import java.util.Date;
|
|||||||
@Table("qsv_activity")
|
@Table("qsv_activity")
|
||||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||||
@Comment("问卷调查投票活动表")
|
@Comment("问卷调查投票活动表")
|
||||||
public class QsvActivity extends BaseModel implements Serializable {
|
public class QsvActivity extends BaseModel implements Serializable, SysHomeConvert {
|
||||||
@Name
|
@Name
|
||||||
@Comment("ID")
|
@Comment("ID")
|
||||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||||
@@ -46,6 +48,12 @@ public class QsvActivity extends BaseModel implements Serializable {
|
|||||||
@ColDefine(type = ColType.DATETIME)
|
@ColDefine(type = ColType.DATETIME)
|
||||||
private Date endTime;
|
private Date endTime;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("是否开启")
|
||||||
|
@ColDefine(type = ColType.BOOLEAN)
|
||||||
|
@Default("1")
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
@Comment("活动分组ID")
|
@Comment("活动分组ID")
|
||||||
@ColDefine(type = ColType.INT)
|
@ColDefine(type = ColType.INT)
|
||||||
@@ -107,4 +115,35 @@ public class QsvActivity extends BaseModel implements Serializable {
|
|||||||
@Comment("封面图片")
|
@Comment("封面图片")
|
||||||
@ColDefine(type = ColType.VARCHAR, width = 255)
|
@ColDefine(type = ColType.VARCHAR, width = 255)
|
||||||
private String cover;
|
private String cover;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sys_home_activity covertToSysHomeActivity() {
|
||||||
|
Sys_home_activity sysHomeActivity = new Sys_home_activity();
|
||||||
|
sysHomeActivity.setId(this.getId());
|
||||||
|
sysHomeActivity.setName(this.getTitle());
|
||||||
|
sysHomeActivity.setCover(this.getCover());
|
||||||
|
sysHomeActivity.setContent(this.getDescription());
|
||||||
|
sysHomeActivity.setUrl("/platform/qsv/online");
|
||||||
|
sysHomeActivity.setH5Url(getH5Url());
|
||||||
|
sysHomeActivity.setStartDate(this.getStartTime());
|
||||||
|
sysHomeActivity.setEndDate(this.getEndTime());
|
||||||
|
sysHomeActivity.setAllowUserGroupId(this.getGroupId());
|
||||||
|
sysHomeActivity.setEnable(!Boolean.FALSE.equals(this.getEnabled()));
|
||||||
|
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
|
||||||
|
return sysHomeActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据活动类型生成移动端首页跳转地址。
|
||||||
|
* QUIZ 跳转答题页,SURVEY 跳转调查页,VOTE 预留投票页地址,返回值为移动端路由字符串。
|
||||||
|
*/
|
||||||
|
private String getH5Url() {
|
||||||
|
if ("SURVEY".equals(this.getCategory())) {
|
||||||
|
return "/platform/h5/qsv/survey?id=" + this.getId();
|
||||||
|
}
|
||||||
|
if ("VOTE".equals(this.getCategory())) {
|
||||||
|
return "/platform/h5/qsv/vote?id=" + this.getId();
|
||||||
|
}
|
||||||
|
return "/platform/h5/qsv/quiz?id=" + this.getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,16 @@ public class QsvOption extends BaseModel implements Serializable {
|
|||||||
@ColDefine(customType = "longtext")
|
@ColDefine(customType = "longtext")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("选中后是否需要填写补充内容")
|
||||||
|
@ColDefine(type = ColType.BOOLEAN)
|
||||||
|
private Boolean fillRequired;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("补充内容输入提示")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 255)
|
||||||
|
private String fillPlaceholder;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
@Comment("排序")
|
@Comment("排序")
|
||||||
@ColDefine(type = ColType.INT)
|
@ColDefine(type = ColType.INT)
|
||||||
|
|||||||
@@ -34,10 +34,15 @@ public class QsvSubject extends BaseModel implements Serializable {
|
|||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
@Comment("题目类型(single, multi, judge, fill)")
|
@Comment("题目类型(radio单选,checkbox多选,text填空)")
|
||||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("题目组件展示类型(RADIO普通单选,DROPDOWN下拉选择)")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||||
|
private String componentType;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
@Comment("正确答案")
|
@Comment("正确答案")
|
||||||
@ColDefine(type = ColType.MYSQL_JSON)
|
@ColDefine(type = ColType.MYSQL_JSON)
|
||||||
@@ -58,11 +63,41 @@ public class QsvSubject extends BaseModel implements Serializable {
|
|||||||
@ColDefine(type = ColType.INT, width = 1)
|
@ColDefine(type = ColType.INT, width = 1)
|
||||||
private Integer maxMulti;
|
private Integer maxMulti;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("投票选项排列方式")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||||
|
private String optionLayout;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("投票选项横向列数")
|
||||||
|
@ColDefine(type = ColType.INT, width = 1)
|
||||||
|
private Integer optionColumns;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
@Comment("排序")
|
@Comment("排序")
|
||||||
@ColDefine(type = ColType.INT, width = 1)
|
@ColDefine(type = ColType.INT, width = 1)
|
||||||
private Integer sortNum;
|
private Integer sortNum;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("是否启用题目隐显逻辑")
|
||||||
|
@ColDefine(type = ColType.BOOLEAN)
|
||||||
|
private Boolean visibleRuleEnabled;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("隐显逻辑处理方式(show显示,hide隐藏)")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||||
|
private String visibleRuleAction;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("隐显逻辑条件关系(AND并且,OR或者)")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||||
|
private String visibleRuleLogic;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("隐显逻辑条件列表")
|
||||||
|
@ColDefine(type = ColType.MYSQL_JSON)
|
||||||
|
private List<VisibleRuleCondition> visibleRuleConditions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选项
|
* 选项
|
||||||
*/
|
*/
|
||||||
@@ -79,4 +114,52 @@ public class QsvSubject extends BaseModel implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String userFillContent;
|
private String userFillContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题目隐显逻辑条件,一条条件表示选择指定题目的指定选项后参与显示或隐藏判断。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class VisibleRuleCondition implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作为触发条件的题目ID。
|
||||||
|
*/
|
||||||
|
private String subjectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作为触发条件的选项ID。
|
||||||
|
*/
|
||||||
|
private String optionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件节点类型:group 分组,item 条件项。
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分组内条件关系:and 并且,or 或者。
|
||||||
|
*/
|
||||||
|
private String logic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件项引用的题目ID。
|
||||||
|
*/
|
||||||
|
private String field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较符:==、!=、contains、>、<、>=、<=。
|
||||||
|
*/
|
||||||
|
private String operator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较值,选择题为选项ID,填空题可为文本或数字。
|
||||||
|
*/
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子条件节点,支持条件分组嵌套。
|
||||||
|
*/
|
||||||
|
private List<VisibleRuleCondition> children;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.budwk.app.zhgh.dayofficework.qsv.param;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
//答题参数
|
//答题参数
|
||||||
@@ -21,6 +22,7 @@ public class QsvAnswerParam {
|
|||||||
private String id;
|
private String id;
|
||||||
private List<String> userSelectOptionIds;
|
private List<String> userSelectOptionIds;
|
||||||
private String userFillContent;
|
private String userFillContent;
|
||||||
|
private Map<String, String> optionFillContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,26 @@ package com.budwk.app.zhgh.dayofficework.qsv.service;
|
|||||||
|
|
||||||
import com.budwk.app.base.service.BaseService;
|
import com.budwk.app.base.service.BaseService;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
|
|
||||||
public interface QsvActivityService extends BaseService<QsvActivity> {
|
public interface QsvActivityService extends BaseService<QsvActivity> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save activity with normal mode-change protection.
|
||||||
|
*/
|
||||||
|
QsvActivity saveActivity(QsvActivity qsvActivity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save activity and optionally bypass the answer-mode change confirmation after the user confirms.
|
||||||
|
*/
|
||||||
|
QsvActivity saveActivity(QsvActivity qsvActivity, Boolean forceSave);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether changing answer mode needs user confirmation because answer records already exist.
|
||||||
|
*/
|
||||||
|
NutMap checkAnswerModeChangeConfirm(QsvActivity qsvActivity);
|
||||||
|
|
||||||
|
void deleteActivity(String id);
|
||||||
|
|
||||||
|
void updateEnabled(String id, Boolean enabled);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ public interface QsvSurveyService extends BaseService<QsvUserAnswerRecord> {
|
|||||||
|
|
||||||
List<NutMap> report(String activityId);
|
List<NutMap> report(String activityId);
|
||||||
|
|
||||||
|
List<NutMap> categoryReport(String activityId, String conditions);
|
||||||
|
|
||||||
|
NutMap crossReport(String activityId, String xSubjectIds, String ySubjectIds, String conditions);
|
||||||
|
|
||||||
|
NutMap compareReport(String activityId, String subjectId, String optionId);
|
||||||
|
|
||||||
|
List<NutMap> selectOptionUsers(String activityId, String subjectId, String optionId, String conditions);
|
||||||
|
|
||||||
void exportReportXlsx(String activityId, HttpServletResponse response);
|
void exportReportXlsx(String activityId, HttpServletResponse response);
|
||||||
|
|
||||||
void exportUserAnswerXlsx(String activityId, HttpServletResponse response);
|
void exportUserAnswerXlsx(String activityId, HttpServletResponse response);
|
||||||
|
|||||||
+8
@@ -17,6 +17,14 @@ public interface QsvUserAnswerRecordService extends BaseService<QsvUserAnswerRec
|
|||||||
*/
|
*/
|
||||||
float calcScore(QsvAnswerParam qsvAnswerParam);
|
float calcScore(QsvAnswerParam qsvAnswerParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验多选题提交答案是否超过题目配置的最大选择数,返回空表示校验通过。
|
||||||
|
*
|
||||||
|
* @param qsvAnswerParam 用户提交的答题参数
|
||||||
|
* @return 超限提示文案,空表示不超限
|
||||||
|
*/
|
||||||
|
String checkMaxMultiLimit(QsvAnswerParam qsvAnswerParam);
|
||||||
|
|
||||||
void calcByScoreMode(String activityId, String userId);
|
void calcByScoreMode(String activityId, String userId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+117
@@ -1,10 +1,23 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.qsv.service.impl;
|
package com.budwk.app.zhgh.dayofficework.qsv.service.impl;
|
||||||
|
|
||||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||||
|
import com.budwk.app.sys.models.Sys_home_activity;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
|
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;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord;
|
||||||
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvActivityService;
|
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvActivityService;
|
||||||
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.ioc.aop.Aop;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.lang.Strings;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@IocBean(args = {"refer:dao"})
|
@IocBean(args = {"refer:dao"})
|
||||||
public class QsvActivityServiceImpl extends BaseServiceImpl<QsvActivity> implements QsvActivityService {
|
public class QsvActivityServiceImpl extends BaseServiceImpl<QsvActivity> implements QsvActivityService {
|
||||||
@@ -12,4 +25,108 @@ public class QsvActivityServiceImpl extends BaseServiceImpl<QsvActivity> impleme
|
|||||||
public QsvActivityServiceImpl(Dao dao) {
|
public QsvActivityServiceImpl(Dao dao) {
|
||||||
super(dao);
|
super(dao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public QsvActivity saveActivity(QsvActivity qsvActivity) {
|
||||||
|
return saveActivity(qsvActivity, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public QsvActivity saveActivity(QsvActivity qsvActivity, Boolean forceSave) {
|
||||||
|
NutMap confirmResult = checkAnswerModeChangeConfirm(qsvActivity);
|
||||||
|
if (!Boolean.TRUE.equals(forceSave) && Boolean.TRUE.equals(confirmResult.get("needConfirm"))) {
|
||||||
|
throw new IllegalStateException(confirmResult.getString("message"));
|
||||||
|
}
|
||||||
|
fillQuizRepeatMode(qsvActivity);
|
||||||
|
dao().insertOrUpdate(qsvActivity);
|
||||||
|
syncHomeActivity(qsvActivity);
|
||||||
|
return qsvActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NutMap checkAnswerModeChangeConfirm(QsvActivity qsvActivity) {
|
||||||
|
NutMap result = NutMap.NEW();
|
||||||
|
result.put("needConfirm", false);
|
||||||
|
result.put("message", "");
|
||||||
|
if (qsvActivity == null || Strings.isBlank(qsvActivity.getId())) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QsvActivity oldActivity = dao().fetch(QsvActivity.class, qsvActivity.getId());
|
||||||
|
if (oldActivity == null || !isAnswerModeChanged(oldActivity, qsvActivity)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int answerRecordCount = dao().count(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", qsvActivity.getId()));
|
||||||
|
if (answerRecordCount < 1) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answer-mode changes affect unfinished answers, submitted answers and statistics, so the editor must confirm first.
|
||||||
|
result.put("needConfirm", true);
|
||||||
|
result.put("message", "\u5f53\u524d\u95ee\u5377\u5df2\u6709\u7528\u6237\u7b54\u9898\u4e2d\u6216\u5df2\u7b54\u9898\u5b8c\u6210\uff0c\u4fee\u6539\u7b54\u9898\u6a21\u5f0f\u53ef\u80fd\u5f71\u54cd\u73b0\u6709\u7b54\u9898\u8bb0\u5f55\u548c\u7edf\u8ba1\u7ed3\u679c\uff0c\u662f\u5426\u786e\u8ba4\u4fdd\u5b58\uff1f");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public void deleteActivity(String id) {
|
||||||
|
List<QsvSubject> subjects = dao().query(QsvSubject.class, Cnd.where("activityId", "=", id));
|
||||||
|
List<String> subjectIds = subjects.stream().map(QsvSubject::getId).collect(Collectors.toList());
|
||||||
|
if (!subjectIds.isEmpty()) {
|
||||||
|
dao().clear(QsvOption.class, Cnd.where("subjectId", "in", subjectIds));
|
||||||
|
dao().clear(QsvSubject.class, Cnd.where("id", "in", subjectIds));
|
||||||
|
}
|
||||||
|
dao().clear(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", id));
|
||||||
|
dao().delete(QsvActivity.class, id);
|
||||||
|
dao().delete(Sys_home_activity.class, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public void updateEnabled(String id, Boolean enabled) {
|
||||||
|
QsvActivity activity = dao().fetch(QsvActivity.class, id);
|
||||||
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activity.setEnabled(enabled);
|
||||||
|
dao().update(activity);
|
||||||
|
syncHomeActivity(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 答题活动的重复模式由出题模式决定。定时定题按天统计,常规模式按活动总次数统计。
|
||||||
|
*/
|
||||||
|
private void fillQuizRepeatMode(QsvActivity qsvActivity) {
|
||||||
|
if (!"QUIZ".equals(qsvActivity.getCategory())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ("SCHEDULED".equals(qsvActivity.getMode())) {
|
||||||
|
qsvActivity.setRepeatMode("DAILY");
|
||||||
|
} else if ("REGULAR".equals(qsvActivity.getMode())) {
|
||||||
|
qsvActivity.setRepeatMode("TOTAL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断编辑前后的答题模式是否变化;问卷、答题等活动只要 mode 发生变化都需要参与风险确认。
|
||||||
|
*/
|
||||||
|
private boolean isAnswerModeChanged(QsvActivity oldActivity, QsvActivity newActivity) {
|
||||||
|
String oldMode = Strings.sNull(oldActivity.getMode()).trim();
|
||||||
|
String newMode = Strings.sNull(newActivity.getMode()).trim();
|
||||||
|
if (Strings.isBlank(oldMode) && Strings.isBlank(newMode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !Objects.equals(oldMode, newMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步移动端首页活动。问卷活动保存或启停后,都以活动自身 enabled、时间、人员范围生成首页记录。
|
||||||
|
*/
|
||||||
|
private void syncHomeActivity(QsvActivity activity) {
|
||||||
|
Sys_home_activity sysHomeActivity = activity.covertToSysHomeActivity();
|
||||||
|
dao().insertOrUpdate(sysHomeActivity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-11
@@ -90,6 +90,7 @@ public class QsvQuizRankServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord>
|
|||||||
private Sql buildQuerySql(String activityId, String mode, String scoreStatisticsMode, QsvQuizRankPageForm pageForm) {
|
private Sql buildQuerySql(String activityId, String mode, String scoreStatisticsMode, QsvQuizRankPageForm pageForm) {
|
||||||
StringBuilder sqlBuilder = new StringBuilder("""
|
StringBuilder sqlBuilder = new StringBuilder("""
|
||||||
SELECT
|
SELECT
|
||||||
|
t1.id,
|
||||||
t1.userId,
|
t1.userId,
|
||||||
t1.userName,
|
t1.userName,
|
||||||
t1.loginName,
|
t1.loginName,
|
||||||
@@ -119,17 +120,11 @@ public class QsvQuizRankServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord>
|
|||||||
cnd.andEX("t1.unionId","=",pageForm.getUnionId());
|
cnd.andEX("t1.unionId","=",pageForm.getUnionId());
|
||||||
cnd.andEX("t1.unitId","=",pageForm.getUnitId());
|
cnd.andEX("t1.unitId","=",pageForm.getUnitId());
|
||||||
|
|
||||||
if ("SCHEDULED".equals(mode)) {
|
if ("SCHEDULED".equals(mode) || "REGULAR".equals(mode)) {
|
||||||
if ("HIGH".equals(scoreStatisticsMode)) {
|
if ("HIGH".equals(scoreStatisticsMode) || "HIGHEST".equals(scoreStatisticsMode)) {
|
||||||
cnd.and("t1.isHighestScore","=",1);
|
cnd.and("t1.isHighestScore", "=", 1);
|
||||||
} else if ("LAST".equals(scoreStatisticsMode)) {
|
} else {
|
||||||
cnd.and("t1.isLatestScore","=",1);
|
cnd.and("t1.isLatestScore", "=", 1);
|
||||||
}
|
|
||||||
} else if ("REGULAR".equals(mode)) {
|
|
||||||
if ("HIGH".equals(scoreStatisticsMode)) {
|
|
||||||
cnd.and("t1.isHighestScore","=",1);
|
|
||||||
} else if ("LAST".equals(scoreStatisticsMode)) {
|
|
||||||
cnd.and("t1.isLatestScore","=",1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+494
-39
@@ -8,6 +8,7 @@ import cn.afterturn.easypoi.excel.export.ExcelExportService;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.json.JSONArray;
|
import cn.hutool.json.JSONArray;
|
||||||
import cn.hutool.json.JSONObject;
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.param.PageForm;
|
import com.budwk.app.base.param.PageForm;
|
||||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||||
@@ -68,47 +69,47 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
|
|||||||
// 处理每个题目
|
// 处理每个题目
|
||||||
for (NutMap subject : subjects) {
|
for (NutMap subject : subjects) {
|
||||||
// 获取当前题目的选项列表
|
// 获取当前题目的选项列表
|
||||||
List<NutMap> subjectOptions = Lang.collection2list(optionsGroup.get(subject.getString("id")), NutMap.class);
|
List<QsvOption> currentOptions = optionsGroup.get(subject.getString("id"));
|
||||||
|
List<NutMap> subjectOptions = ObjectUtil.isEmpty(currentOptions) ? new ArrayList<>() : Lang.collection2list(currentOptions, NutMap.class);
|
||||||
|
|
||||||
// 获取题目类型
|
// 获取题目类型
|
||||||
String subjectType = subject.getString("type");
|
String subjectType = subject.getString("type");
|
||||||
|
|
||||||
// 处理文本类型题目
|
// 处理文本类型题目
|
||||||
if ("text".equals(subjectType)) {
|
if ("text".equals(subjectType)) {
|
||||||
List<String> texts = answerExtList.stream()
|
List<NutMap> textAnswers = buildTextAnswerDetails(answerRecords, subject.getString("id"));
|
||||||
.filter(ext -> ObjectUtil.isNull(ext.get(subject.getString("id"), JSONObject.class)))
|
List<String> texts = textAnswers.stream()
|
||||||
.map(ext -> ext.get(subject.getString("id"), JSONObject.class).getStr("text"))
|
.map(textAnswer -> textAnswer.getString("text"))
|
||||||
.toList();
|
.toList();
|
||||||
subject.put("texts", texts);
|
subject.put("texts", texts);
|
||||||
|
subject.put("textAnswers", textAnswers);
|
||||||
}
|
}
|
||||||
// 处理单选类型题目 处理多选类型题目
|
// 处理单选类型题目 处理多选类型题目
|
||||||
else if ("radio".equals(subjectType) || "checkbox".equals(subjectType)) {
|
else if ("radio".equals(subjectType) || "checkbox".equals(subjectType)) {
|
||||||
long selectTotal = answerExtList.stream()
|
long selectTotal = answerExtList.stream()
|
||||||
.filter(ext -> ext.containsKey(subject.getString("id")))
|
.filter(ext -> ObjectUtil.isNotEmpty(ext) && ext.containsKey(subject.getString("id")))
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
subjectOptions.forEach(subjectOption -> {
|
subjectOptions.forEach(subjectOption -> {
|
||||||
long selectCount = answerExtList.stream()
|
long selectCount = answerExtList.stream()
|
||||||
.filter(ext ->
|
.filter(ext ->
|
||||||
{
|
{
|
||||||
|
if (ObjectUtil.isEmpty(ext)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
JSONObject jsonObject = ext.get(subject.getString("id"), JSONObject.class);
|
JSONObject jsonObject = ext.get(subject.getString("id"), JSONObject.class);
|
||||||
return jsonObject != null
|
return jsonObject != null
|
||||||
&& jsonObject.getJSONArray("optionIds") != null
|
&& jsonObject.getJSONArray("optionIds") != null
|
||||||
&& jsonObject.getJSONArray("optionIds").contains(subjectOption.getString("id"));
|
&& jsonObject.getJSONArray("optionIds").contains(subjectOption.getString("id"));
|
||||||
})
|
})
|
||||||
.count();
|
.count();
|
||||||
long round = Math.round((double) selectCount / selectTotal * 100);
|
long round = selectTotal == 0 ? 0 : Math.round((double) selectCount / selectTotal * 100);
|
||||||
subjectOption.put("selectPercent", round + "%");
|
subjectOption.put("selectPercent", round + "%");
|
||||||
subjectOption.put("selectCount", selectCount);
|
subjectOption.put("selectCount", selectCount);
|
||||||
});
|
});
|
||||||
|
|
||||||
subjectOptions.sort((o1, o2) -> {
|
subjectOptions.sort((o1, o2) -> {
|
||||||
int count1 = o1.getInt("selectCount");
|
return Integer.compare(o1.getInt("sortNum"), o2.getInt("sortNum"));
|
||||||
int count2 = o2.getInt("selectCount");
|
|
||||||
if (count1 == count2) {
|
|
||||||
return Integer.compare(o1.getInt("sortNum"), o2.getInt("sortNum"));
|
|
||||||
}
|
|
||||||
return Integer.compare(count2, count1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
subject.put("selectTotal", selectTotal);
|
subject.put("selectTotal", selectTotal);
|
||||||
@@ -125,6 +126,228 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<NutMap> categoryReport(String activityId, String conditions) {
|
||||||
|
if (activityId == null || activityId.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("activityId不能为空");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
List<NutMap> conditionList = parseCategoryConditions(conditions);
|
||||||
|
List<QsvUserAnswerRecord> answerRecords = dao().query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
|
||||||
|
.and("isFinish", "=", true));
|
||||||
|
return buildReport(activityId, filterAnswerRecords(answerRecords, conditionList));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("分类报告生成失败", e);
|
||||||
|
throw new RuntimeException("分类报告生成失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NutMap crossReport(String activityId, String xSubjectIds, String ySubjectIds, String conditions) {
|
||||||
|
if (activityId == null || activityId.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("activityId不能为空");
|
||||||
|
}
|
||||||
|
List<String> xSubjectIdList = parseSubjectIds(xSubjectIds);
|
||||||
|
List<String> ySubjectIdList = parseSubjectIds(ySubjectIds);
|
||||||
|
if (ObjectUtil.isEmpty(xSubjectIdList) || ObjectUtil.isEmpty(ySubjectIdList)) {
|
||||||
|
throw new IllegalArgumentException("请选择自变量X和因变量Y");
|
||||||
|
}
|
||||||
|
if (xSubjectIdList.stream().anyMatch(ySubjectIdList::contains)) {
|
||||||
|
throw new IllegalArgumentException("自变量X和因变量Y不能选择相同变量");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<NutMap> subjects = querySubjects(activityId);
|
||||||
|
Map<String, NutMap> subjectMap = subjects.stream()
|
||||||
|
.collect(Collectors.toMap(subject -> subject.getString("id"), subject -> subject));
|
||||||
|
if (!subjectMap.keySet().containsAll(xSubjectIdList) || !subjectMap.keySet().containsAll(ySubjectIdList)) {
|
||||||
|
throw new IllegalArgumentException("题目不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> allSubjectIds = new ArrayList<>();
|
||||||
|
allSubjectIds.addAll(xSubjectIdList);
|
||||||
|
allSubjectIds.addAll(ySubjectIdList);
|
||||||
|
List<QsvOption> options = dao().query(QsvOption.class, Cnd.where("subjectId", "in", allSubjectIds).asc("sortNum"));
|
||||||
|
Map<String, List<QsvOption>> optionGroup = options.stream().collect(Collectors.groupingBy(QsvOption::getSubjectId));
|
||||||
|
List<NutMap> xOptionMaps = buildVariableOptionCombinations(xSubjectIdList, subjectMap, optionGroup);
|
||||||
|
List<NutMap> conditionList = parseCategoryConditions(conditions);
|
||||||
|
List<QsvUserAnswerRecord> answerRecords = dao().query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
|
||||||
|
.and("isFinish", "=", true));
|
||||||
|
List<QsvUserAnswerRecord> filteredRecords = filterAnswerRecords(answerRecords, conditionList);
|
||||||
|
List<NutMap> reports = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String ySubjectId : ySubjectIdList) {
|
||||||
|
NutMap ySubject = subjectMap.get(ySubjectId);
|
||||||
|
List<QsvOption> yOptions = optionGroup.get(ySubjectId);
|
||||||
|
if (ObjectUtil.isEmpty(yOptions)) {
|
||||||
|
yOptions = new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<NutMap> yOptionMaps = ObjectUtil.isEmpty(yOptions) ? new ArrayList<>() : Lang.collection2list(yOptions, NutMap.class);
|
||||||
|
List<NutMap> rows = new ArrayList<>();
|
||||||
|
|
||||||
|
for (NutMap xOption : xOptionMaps) {
|
||||||
|
List<NutMap> xConditions = xOption.getList("conditions", NutMap.class);
|
||||||
|
List<QsvUserAnswerRecord> xMatchedRecords = filteredRecords.stream()
|
||||||
|
.filter(record -> isRecordMatched(record, xConditions))
|
||||||
|
.toList();
|
||||||
|
long rowTotal = xMatchedRecords.size();
|
||||||
|
List<NutMap> cells = new ArrayList<>();
|
||||||
|
for (QsvOption yOption : yOptions) {
|
||||||
|
List<NutMap> yCondition = List.of(NutMap.NEW()
|
||||||
|
.addv("subjectId", ySubjectId)
|
||||||
|
.addv("optionId", yOption.getId()));
|
||||||
|
long count = xMatchedRecords.stream()
|
||||||
|
.filter(record -> isRecordMatched(record, yCondition))
|
||||||
|
.count();
|
||||||
|
double percent = rowTotal == 0 ? 0 : Math.round((double) count / rowTotal * 10000) / 100.0;
|
||||||
|
cells.add(NutMap.NEW()
|
||||||
|
.addv("yOptionId", yOption.getId())
|
||||||
|
.addv("yOptionText", yOption.getText())
|
||||||
|
.addv("count", count)
|
||||||
|
.addv("percent", percent));
|
||||||
|
}
|
||||||
|
rows.add(NutMap.NEW()
|
||||||
|
.addv("xOptionId", xOption.getString("id"))
|
||||||
|
.addv("xOptionText", xOption.getString("text"))
|
||||||
|
.addv("cells", cells)
|
||||||
|
.addv("total", rowTotal));
|
||||||
|
}
|
||||||
|
|
||||||
|
reports.add(NutMap.NEW()
|
||||||
|
.addv("ySubject", ySubject)
|
||||||
|
.addv("yOptions", yOptionMaps)
|
||||||
|
.addv("rows", rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NutMap.NEW()
|
||||||
|
.addv("xSubject", buildVariableSubject(xSubjectIdList, subjectMap))
|
||||||
|
.addv("xOptions", xOptionMaps)
|
||||||
|
.addv("reports", reports)
|
||||||
|
.addv("total", filteredRecords.size());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("交叉分析报告生成失败", e);
|
||||||
|
throw new RuntimeException("交叉分析报告生成失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NutMap compareReport(String activityId, String subjectId, String optionId) {
|
||||||
|
if (activityId == null || activityId.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("activityId不能为空");
|
||||||
|
}
|
||||||
|
if (ObjectUtil.isEmpty(subjectId) || ObjectUtil.isEmpty(optionId)) {
|
||||||
|
throw new IllegalArgumentException("请选择对比变量和选项");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<NutMap> subjects = querySubjects(activityId);
|
||||||
|
Map<String, NutMap> subjectMap = subjects.stream()
|
||||||
|
.collect(Collectors.toMap(subject -> subject.getString("id"), subject -> subject));
|
||||||
|
NutMap compareSubject = subjectMap.get(subjectId);
|
||||||
|
if (compareSubject == null) {
|
||||||
|
throw new IllegalArgumentException("对比变量不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> subjectIds = subjects.stream().map(subject -> subject.getString("id")).toList();
|
||||||
|
List<QsvOption> options = dao().query(QsvOption.class, Cnd.where("subjectId", "in", subjectIds).asc("sortNum"));
|
||||||
|
Map<String, List<QsvOption>> optionGroup = options.stream().collect(Collectors.groupingBy(QsvOption::getSubjectId));
|
||||||
|
QsvOption compareOption = optionGroup.getOrDefault(subjectId, new ArrayList<>()).stream()
|
||||||
|
.filter(option -> optionId.equals(option.getId()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (compareOption == null) {
|
||||||
|
throw new IllegalArgumentException("对比选项不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<NutMap> compareCondition = List.of(NutMap.NEW()
|
||||||
|
.addv("subjectId", subjectId)
|
||||||
|
.addv("optionId", optionId));
|
||||||
|
List<QsvUserAnswerRecord> answerRecords = dao().query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
|
||||||
|
.and("isFinish", "=", true));
|
||||||
|
List<QsvUserAnswerRecord> filteredRecords = filterAnswerRecords(answerRecords, compareCondition);
|
||||||
|
List<NutMap> reports = new ArrayList<>();
|
||||||
|
|
||||||
|
for (NutMap subject : subjects) {
|
||||||
|
String currentSubjectId = subject.getString("id");
|
||||||
|
String subjectType = subject.getString("type");
|
||||||
|
if (subjectId.equals(currentSubjectId) || (!"radio".equals(subjectType) && !"checkbox".equals(subjectType))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<QsvOption> currentOptions = optionGroup.get(currentSubjectId);
|
||||||
|
if (ObjectUtil.isEmpty(currentOptions)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<NutMap> cells = new ArrayList<>();
|
||||||
|
long rowTotal = filteredRecords.size();
|
||||||
|
for (QsvOption option : currentOptions) {
|
||||||
|
List<NutMap> optionCondition = List.of(NutMap.NEW()
|
||||||
|
.addv("subjectId", currentSubjectId)
|
||||||
|
.addv("optionId", option.getId()));
|
||||||
|
long count = filteredRecords.stream()
|
||||||
|
.filter(record -> isRecordMatched(record, optionCondition))
|
||||||
|
.count();
|
||||||
|
double percent = rowTotal == 0 ? 0 : Math.round((double) count / rowTotal * 10000) / 100.0;
|
||||||
|
cells.add(NutMap.NEW()
|
||||||
|
.addv("yOptionId", option.getId())
|
||||||
|
.addv("yOptionText", option.getText())
|
||||||
|
.addv("count", count)
|
||||||
|
.addv("percent", percent));
|
||||||
|
}
|
||||||
|
reports.add(NutMap.NEW()
|
||||||
|
.addv("ySubject", subject)
|
||||||
|
.addv("yOptions", Lang.collection2list(currentOptions, NutMap.class))
|
||||||
|
.addv("rows", List.of(NutMap.NEW()
|
||||||
|
.addv("xOptionId", optionId)
|
||||||
|
.addv("xOptionText", compareOption.getText())
|
||||||
|
.addv("cells", cells)
|
||||||
|
.addv("total", rowTotal))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NutMap.NEW()
|
||||||
|
.addv("compareSubject", compareSubject)
|
||||||
|
.addv("compareOption", Lang.obj2nutmap(compareOption))
|
||||||
|
.addv("reports", reports)
|
||||||
|
.addv("total", filteredRecords.size());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("对比分析报告生成失败", e);
|
||||||
|
throw new RuntimeException("对比分析报告生成失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<NutMap> selectOptionUsers(String activityId, String subjectId, String optionId, String conditions) {
|
||||||
|
List<NutMap> conditionList = parseCategoryConditions(conditions);
|
||||||
|
List<QsvUserAnswerRecord> answerRecords = dao().query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
|
||||||
|
.and("isFinish", "=", true));
|
||||||
|
return filterAnswerRecords(answerRecords, conditionList).stream().map(record -> {
|
||||||
|
JSONObject extJson = record.getExtJson();
|
||||||
|
if (ObjectUtil.isEmpty(extJson)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JSONObject subjectAnswer = extJson.get(subjectId, JSONObject.class);
|
||||||
|
if (ObjectUtil.isEmpty(subjectAnswer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JSONArray optionIds = subjectAnswer.getJSONArray("optionIds");
|
||||||
|
if (ObjectUtil.isEmpty(optionIds) || !optionIds.contains(optionId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JSONObject optionFillContents = subjectAnswer.getJSONObject("optionFillContents");
|
||||||
|
String fillContent = ObjectUtil.isEmpty(optionFillContents) ? "" : optionFillContents.getStr(optionId);
|
||||||
|
return NutMap.NEW()
|
||||||
|
.addv("userName", record.getUserName())
|
||||||
|
.addv("loginName", record.getLoginName())
|
||||||
|
.addv("unionName", record.getUnionName())
|
||||||
|
.addv("unitName", record.getUnitName())
|
||||||
|
.addv("attemptDate", record.getAttemptDate())
|
||||||
|
.addv("submitTime", record.getSubmitTime())
|
||||||
|
.addv("fillContent", fillContent);
|
||||||
|
})
|
||||||
|
.filter(ObjectUtil::isNotNull)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exportReportXlsx(String activityId, HttpServletResponse response) {
|
public void exportReportXlsx(String activityId, HttpServletResponse response) {
|
||||||
List<NutMap> report = report(activityId);
|
List<NutMap> report = report(activityId);
|
||||||
@@ -134,10 +357,8 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
|
|||||||
exportEntities.add(new ExcelExportEntity("选择人数", "selectCount", 20));
|
exportEntities.add(new ExcelExportEntity("选择人数", "selectCount", 20));
|
||||||
exportEntities.add(new ExcelExportEntity("选择比例", "selectPercent", 20));
|
exportEntities.add(new ExcelExportEntity("选择比例", "selectPercent", 20));
|
||||||
|
|
||||||
Map<String, List<NutMap>> listMap = report.stream().collect(Collectors.groupingBy(v -> v.getString("id")));
|
|
||||||
Workbook workbook = new XSSFWorkbook();
|
Workbook workbook = new XSSFWorkbook();
|
||||||
listMap.forEach((k, v) -> {
|
report.forEach(nutMap -> {
|
||||||
NutMap nutMap = v.get(0);
|
|
||||||
ExcelExportService service = new ExcelExportService();
|
ExcelExportService service = new ExcelExportService();
|
||||||
ExportParams exportParams = new ExportParams();
|
ExportParams exportParams = new ExportParams();
|
||||||
exportParams.setTitle(nutMap.getString("title"));
|
exportParams.setTitle(nutMap.getString("title"));
|
||||||
@@ -171,7 +392,7 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
|
|||||||
List<JSONObject> answerExtList = answerRecords.stream().map(QsvUserAnswerRecord::getExtJson).toList();
|
List<JSONObject> answerExtList = answerRecords.stream().map(QsvUserAnswerRecord::getExtJson).toList();
|
||||||
|
|
||||||
// 查询活动题目列表
|
// 查询活动题目列表
|
||||||
List<QsvSubject> subjects = dao().query(QsvSubject.class, Cnd.where("activityId", "=", activityId));
|
List<QsvSubject> subjects = dao().query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
|
||||||
// 将题目列表转换为Map
|
// 将题目列表转换为Map
|
||||||
Map<String, QsvSubject> subjectMap = subjects.stream().collect(Collectors.toMap(QsvSubject::getId, v -> v));
|
Map<String, QsvSubject> subjectMap = subjects.stream().collect(Collectors.toMap(QsvSubject::getId, v -> v));
|
||||||
// 获取题目ID列表
|
// 获取题目ID列表
|
||||||
@@ -185,7 +406,7 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
|
|||||||
// 构建Excel导出实体
|
// 构建Excel导出实体
|
||||||
List<ExcelExportEntity> excelExportEntities = buildExcelExportEntities(subjects);
|
List<ExcelExportEntity> excelExportEntities = buildExcelExportEntities(subjects);
|
||||||
// 构建答题记录列表
|
// 构建答题记录列表
|
||||||
List<NutMap> list = buildAnswerRecords(answerRecords, subjectMap, options);
|
List<NutMap> list = buildAnswerRecords(answerRecords, subjectMap, optionMap);
|
||||||
|
|
||||||
// 设置导出参数
|
// 设置导出参数
|
||||||
ExportParams exportParams = new ExportParams();
|
ExportParams exportParams = new ExportParams();
|
||||||
@@ -223,7 +444,7 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
|
|||||||
// List<JSONObject> answerExtList = answerRecords.stream().map(QsvUserAnswerRecord::getExtJson).toList();
|
// List<JSONObject> answerExtList = answerRecords.stream().map(QsvUserAnswerRecord::getExtJson).toList();
|
||||||
|
|
||||||
// 查询活动题目列表
|
// 查询活动题目列表
|
||||||
List<QsvSubject> subjects = dao().query(QsvSubject.class, Cnd.where("activityId", "=", activityId));
|
List<QsvSubject> subjects = dao().query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
|
||||||
// 将题目列表转换为Map
|
// 将题目列表转换为Map
|
||||||
Map<String, QsvSubject> subjectMap = subjects.stream().collect(Collectors.toMap(QsvSubject::getId, v -> v));
|
Map<String, QsvSubject> subjectMap = subjects.stream().collect(Collectors.toMap(QsvSubject::getId, v -> v));
|
||||||
// 获取题目ID列表
|
// 获取题目ID列表
|
||||||
@@ -245,18 +466,15 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
|
|||||||
.addv("unitName", record.getString("unitName"))
|
.addv("unitName", record.getString("unitName"))
|
||||||
.addv("unionName", record.getString("unionName"));
|
.addv("unionName", record.getString("unionName"));
|
||||||
JSONObject extJson = record.getAs("extJson", JSONObject.class);
|
JSONObject extJson = record.getAs("extJson", JSONObject.class);
|
||||||
|
if (ObjectUtil.isEmpty(extJson)) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
extJson.forEach((k, v) -> {
|
extJson.forEach((k, v) -> {
|
||||||
JSONObject jsonVal = (JSONObject) v;
|
QsvSubject subject = subjectMap.get(k);
|
||||||
|
if (subject == null || !(v instanceof JSONObject jsonVal)) {
|
||||||
String type = subjectMap.get(k).getType();
|
return;
|
||||||
if (type.equals("text")) {
|
|
||||||
map.addv(k, jsonVal.getStr("text"));
|
|
||||||
} else if (type.equals("radio") || type.equals("checkbox")) {
|
|
||||||
JSONArray optionIds = jsonVal.getJSONArray("optionIds");
|
|
||||||
String selectOptionTexts = options.stream().filter(option -> optionIds.contains(option.getId()))
|
|
||||||
.map(QsvOption::getText).collect(Collectors.joining(";"));
|
|
||||||
map.addv(k, selectOptionTexts);
|
|
||||||
}
|
}
|
||||||
|
map.addv(k, buildAnswerText(jsonVal, subject, optionMap));
|
||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
}).toList();
|
}).toList();
|
||||||
@@ -278,12 +496,178 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<NutMap> buildReport(String activityId, List<QsvUserAnswerRecord> answerRecords) {
|
||||||
|
List<JSONObject> answerExtList = answerRecords.stream().map(QsvUserAnswerRecord::getExtJson).toList();
|
||||||
|
List<NutMap> subjects = querySubjects(activityId);
|
||||||
|
List<String> subjectIds = subjects.stream().map(v -> v.getString("id")).toList();
|
||||||
|
List<QsvOption> options = dao().query(QsvOption.class, Cnd.where("subjectId", "in", subjectIds).asc("sortNum"));
|
||||||
|
Map<String, List<QsvOption>> optionsGroup = options.stream().collect(Collectors.groupingBy(QsvOption::getSubjectId));
|
||||||
|
|
||||||
|
for (NutMap subject : subjects) {
|
||||||
|
List<QsvOption> currentOptions = optionsGroup.get(subject.getString("id"));
|
||||||
|
List<NutMap> subjectOptions = ObjectUtil.isEmpty(currentOptions) ? new ArrayList<>() : Lang.collection2list(currentOptions, NutMap.class);
|
||||||
|
String subjectType = subject.getString("type");
|
||||||
|
if ("text".equals(subjectType)) {
|
||||||
|
List<NutMap> textAnswers = buildTextAnswerDetails(answerRecords, subject.getString("id"));
|
||||||
|
List<String> texts = textAnswers.stream()
|
||||||
|
.map(textAnswer -> textAnswer.getString("text"))
|
||||||
|
.toList();
|
||||||
|
subject.put("texts", texts);
|
||||||
|
subject.put("textAnswers", textAnswers);
|
||||||
|
} else if ("radio".equals(subjectType) || "checkbox".equals(subjectType)) {
|
||||||
|
long selectTotal = answerExtList.stream()
|
||||||
|
.filter(ext -> ObjectUtil.isNotEmpty(ext) && ext.containsKey(subject.getString("id")))
|
||||||
|
.count();
|
||||||
|
subjectOptions.forEach(subjectOption -> {
|
||||||
|
long selectCount = answerExtList.stream()
|
||||||
|
.filter(ext -> {
|
||||||
|
if (ObjectUtil.isEmpty(ext)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JSONObject jsonObject = ext.get(subject.getString("id"), JSONObject.class);
|
||||||
|
return jsonObject != null
|
||||||
|
&& jsonObject.getJSONArray("optionIds") != null
|
||||||
|
&& jsonObject.getJSONArray("optionIds").contains(subjectOption.getString("id"));
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
long round = selectTotal == 0 ? 0 : Math.round((double) selectCount / selectTotal * 100);
|
||||||
|
subjectOption.put("selectPercent", round + "%");
|
||||||
|
subjectOption.put("selectCount", selectCount);
|
||||||
|
});
|
||||||
|
subjectOptions.sort((o1, o2) -> Integer.compare(o1.getInt("sortNum"), o2.getInt("sortNum")));
|
||||||
|
subject.put("selectTotal", selectTotal);
|
||||||
|
}
|
||||||
|
subject.addv("options", subjectOptions);
|
||||||
|
}
|
||||||
|
return subjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<NutMap> parseCategoryConditions(String conditions) {
|
||||||
|
if (ObjectUtil.isEmpty(conditions)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return JSONUtil.parseArray(conditions).toList(NutMap.class).stream()
|
||||||
|
.filter(condition -> ObjectUtil.isNotEmpty(condition.getString("subjectId"))
|
||||||
|
&& ObjectUtil.isNotEmpty(condition.getString("optionId")))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> parseSubjectIds(String subjectIds) {
|
||||||
|
if (ObjectUtil.isEmpty(subjectIds)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
String value = subjectIds.trim();
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
if (value.startsWith("[")) {
|
||||||
|
for (Object subjectId : JSONUtil.parseArray(value)) {
|
||||||
|
if (ObjectUtil.isNotEmpty(subjectId)) {
|
||||||
|
result.add(String.valueOf(subjectId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.stream().distinct().toList();
|
||||||
|
}
|
||||||
|
for (String subjectId : value.split(",")) {
|
||||||
|
if (ObjectUtil.isNotEmpty(subjectId)) {
|
||||||
|
result.add(subjectId.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.stream().distinct().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private NutMap buildVariableSubject(List<String> subjectIds, Map<String, NutMap> subjectMap) {
|
||||||
|
String title = subjectIds.stream()
|
||||||
|
.map(subjectId -> subjectMap.get(subjectId).getString("title"))
|
||||||
|
.collect(Collectors.joining(" / "));
|
||||||
|
return NutMap.NEW()
|
||||||
|
.addv("id", String.join("|", subjectIds))
|
||||||
|
.addv("title", title);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<NutMap> buildVariableOptionCombinations(List<String> subjectIds, Map<String, NutMap> subjectMap, Map<String, List<QsvOption>> optionGroup) {
|
||||||
|
List<List<NutMap>> subjectOptionGroups = new ArrayList<>();
|
||||||
|
for (String subjectId : subjectIds) {
|
||||||
|
NutMap subject = subjectMap.get(subjectId);
|
||||||
|
List<QsvOption> options = optionGroup.get(subjectId);
|
||||||
|
if (ObjectUtil.isEmpty(options)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<NutMap> optionMaps = options.stream()
|
||||||
|
.map(option -> NutMap.NEW()
|
||||||
|
.addv("subjectId", subjectId)
|
||||||
|
.addv("subjectTitle", subject.getString("title"))
|
||||||
|
.addv("optionId", option.getId())
|
||||||
|
.addv("optionText", option.getText()))
|
||||||
|
.toList();
|
||||||
|
subjectOptionGroups.add(optionMaps);
|
||||||
|
}
|
||||||
|
if (subjectOptionGroups.size() != subjectIds.size()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<NutMap> result = new ArrayList<>();
|
||||||
|
buildVariableOptionCombinations(subjectOptionGroups, 0, new ArrayList<>(), result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildVariableOptionCombinations(List<List<NutMap>> subjectOptionGroups, int index, List<NutMap> current, List<NutMap> result) {
|
||||||
|
if (index >= subjectOptionGroups.size()) {
|
||||||
|
String id = current.stream()
|
||||||
|
.map(option -> option.getString("subjectId") + ":" + option.getString("optionId"))
|
||||||
|
.collect(Collectors.joining("|"));
|
||||||
|
String text = current.stream()
|
||||||
|
.map(option -> option.getString("optionText"))
|
||||||
|
.collect(Collectors.joining(" / "));
|
||||||
|
List<NutMap> conditions = current.stream()
|
||||||
|
.map(option -> NutMap.NEW()
|
||||||
|
.addv("subjectId", option.getString("subjectId"))
|
||||||
|
.addv("optionId", option.getString("optionId")))
|
||||||
|
.toList();
|
||||||
|
result.add(NutMap.NEW()
|
||||||
|
.addv("id", id)
|
||||||
|
.addv("text", text)
|
||||||
|
.addv("conditions", conditions));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (NutMap option : subjectOptionGroups.get(index)) {
|
||||||
|
current.add(option);
|
||||||
|
buildVariableOptionCombinations(subjectOptionGroups, index + 1, current, result);
|
||||||
|
current.remove(current.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<QsvUserAnswerRecord> filterAnswerRecords(List<QsvUserAnswerRecord> answerRecords, List<NutMap> conditions) {
|
||||||
|
if (ObjectUtil.isEmpty(conditions)) {
|
||||||
|
return answerRecords;
|
||||||
|
}
|
||||||
|
return answerRecords.stream()
|
||||||
|
.filter(record -> isRecordMatched(record, conditions))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRecordMatched(QsvUserAnswerRecord record, List<NutMap> conditions) {
|
||||||
|
JSONObject extJson = record.getExtJson();
|
||||||
|
if (ObjectUtil.isEmpty(extJson)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (NutMap condition : conditions) {
|
||||||
|
JSONObject subjectAnswer = extJson.get(condition.getString("subjectId"), JSONObject.class);
|
||||||
|
if (ObjectUtil.isEmpty(subjectAnswer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JSONArray optionIds = subjectAnswer.getJSONArray("optionIds");
|
||||||
|
if (ObjectUtil.isEmpty(optionIds) || !optionIds.contains(condition.getString("optionId"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private List<NutMap> querySubjects(String activityId) {
|
private List<NutMap> querySubjects(String activityId) {
|
||||||
Sql subjectSql = Sqls.create("""
|
Sql subjectSql = Sqls.create("""
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
|
componentType,
|
||||||
sortNum
|
sortNum
|
||||||
FROM
|
FROM
|
||||||
qsv_subject
|
qsv_subject
|
||||||
@@ -309,7 +693,37 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
|
|||||||
return excelExportEntities;
|
return excelExportEntities;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<NutMap> buildAnswerRecords(List<QsvUserAnswerRecord> answerRecords, Map<String, QsvSubject> subjectMap, List<QsvOption> options) {
|
/**
|
||||||
|
* 构建填空题填写详情。分析弹窗需要同时展示填写内容和答题人信息,
|
||||||
|
* 因此这里从已完成答题记录中提取当前题目的文本答案,并携带姓名、工号、所属工会和单位。
|
||||||
|
*
|
||||||
|
* @param answerRecords 当前活动已完成的答题记录
|
||||||
|
* @param subjectId 当前填空题ID,用于从 extJson 中取出该题填写内容
|
||||||
|
* @return 填空题填写详情列表,每条包含 loginName、userName、unionName、unitName、submitTime、text
|
||||||
|
*/
|
||||||
|
private List<NutMap> buildTextAnswerDetails(List<QsvUserAnswerRecord> answerRecords, String subjectId) {
|
||||||
|
List<NutMap> textAnswers = new ArrayList<>();
|
||||||
|
for (QsvUserAnswerRecord answerRecord : answerRecords) {
|
||||||
|
JSONObject extJson = answerRecord.getExtJson();
|
||||||
|
if (ObjectUtil.isEmpty(extJson)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
JSONObject subjectAnswer = extJson.get(subjectId, JSONObject.class);
|
||||||
|
if (ObjectUtil.isEmpty(subjectAnswer) || ObjectUtil.isEmpty(subjectAnswer.getStr("text"))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
textAnswers.add(NutMap.NEW()
|
||||||
|
.addv("loginName", answerRecord.getLoginName())
|
||||||
|
.addv("userName", answerRecord.getUserName())
|
||||||
|
.addv("unionName", answerRecord.getUnionName())
|
||||||
|
.addv("unitName", answerRecord.getUnitName())
|
||||||
|
.addv("submitTime", answerRecord.getSubmitTime())
|
||||||
|
.addv("text", subjectAnswer.getStr("text")));
|
||||||
|
}
|
||||||
|
return textAnswers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<NutMap> buildAnswerRecords(List<QsvUserAnswerRecord> answerRecords, Map<String, QsvSubject> subjectMap, Map<String, QsvOption> optionMap) {
|
||||||
return answerRecords.stream().map(record -> {
|
return answerRecords.stream().map(record -> {
|
||||||
NutMap map = NutMap.NEW()
|
NutMap map = NutMap.NEW()
|
||||||
.addv("id", record.getId())
|
.addv("id", record.getId())
|
||||||
@@ -318,20 +732,61 @@ public class QsvSurveyServiceImpl extends BaseServiceImpl<QsvUserAnswerRecord> i
|
|||||||
.addv("unitName", record.getUnitName())
|
.addv("unitName", record.getUnitName())
|
||||||
.addv("unionName", record.getUnionName());
|
.addv("unionName", record.getUnionName());
|
||||||
JSONObject extJson = record.getExtJson();
|
JSONObject extJson = record.getExtJson();
|
||||||
|
if (ObjectUtil.isEmpty(extJson)) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
extJson.forEach((k, v) -> {
|
extJson.forEach((k, v) -> {
|
||||||
JSONObject jsonVal = (JSONObject) v;
|
QsvSubject subject = subjectMap.get(k);
|
||||||
|
if (subject == null || !(v instanceof JSONObject jsonVal)) {
|
||||||
String type = subjectMap.get(k).getType();
|
return;
|
||||||
if (type.equals("text")) {
|
|
||||||
map.addv(k, jsonVal.getStr("text"));
|
|
||||||
} else if (type.equals("radio") || type.equals("checkbox")) {
|
|
||||||
JSONArray optionIds = jsonVal.getJSONArray("optionIds");
|
|
||||||
String selectOptionTexts = options.stream().filter(option -> optionIds.contains(option.getId()))
|
|
||||||
.map(QsvOption::getText).collect(Collectors.joining(";"));
|
|
||||||
map.addv(k, selectOptionTexts);
|
|
||||||
}
|
}
|
||||||
|
map.addv(k, buildAnswerText(jsonVal, subject, optionMap));
|
||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组装单题答案展示内容。填空题返回填写文本;选择题返回选项文本,若选项配置了补充填写,
|
||||||
|
* 则追加对应补充内容,保证后台查看答卷和导出能看到用户填写的说明。
|
||||||
|
*
|
||||||
|
* @param jsonVal 答题记录 extJson 中当前题目的答案对象,包含 optionIds、text、optionFillContents
|
||||||
|
* @param subject 当前题目,用于判断题目类型
|
||||||
|
* @param optionMap 当前活动所有选项,key 为选项ID,value 为选项实体
|
||||||
|
* @return 当前题目的答案展示文本
|
||||||
|
*/
|
||||||
|
private String buildAnswerText(JSONObject jsonVal, QsvSubject subject, Map<String, QsvOption> optionMap) {
|
||||||
|
String type = subject.getType();
|
||||||
|
if ("text".equals(type)) {
|
||||||
|
return jsonVal.getStr("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!"radio".equals(type) && !"checkbox".equals(type)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONArray optionIds = jsonVal.getJSONArray("optionIds");
|
||||||
|
if (ObjectUtil.isEmpty(optionIds)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject optionFillContents = jsonVal.getJSONObject("optionFillContents");
|
||||||
|
List<String> answerTexts = new ArrayList<>();
|
||||||
|
for (Object optionIdObj : optionIds) {
|
||||||
|
String optionId = String.valueOf(optionIdObj);
|
||||||
|
QsvOption option = optionMap.get(optionId);
|
||||||
|
if (option == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String answerText = option.getText();
|
||||||
|
if (ObjectUtil.isNotEmpty(optionFillContents)) {
|
||||||
|
String fillContent = optionFillContents.getStr(optionId);
|
||||||
|
if (ObjectUtil.isNotEmpty(fillContent)) {
|
||||||
|
answerText = answerText + ":" + fillContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
answerTexts.add(answerText);
|
||||||
|
}
|
||||||
|
return String.join(";", answerTexts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-2
@@ -26,6 +26,44 @@ public class QsvUserAnswerRecordServiceImpl extends BaseServiceImpl<QsvUserAnswe
|
|||||||
super(dao);
|
super(dao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按题目配置校验多选题最大选择数,防止前端限制被绕过后写入超量选项。
|
||||||
|
*
|
||||||
|
* @param qsvAnswerParam 用户提交的答题参数
|
||||||
|
* @return 超限提示文案,空表示校验通过
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String checkMaxMultiLimit(QsvAnswerParam qsvAnswerParam) {
|
||||||
|
if (qsvAnswerParam == null || qsvAnswerParam.getSubjects() == null || qsvAnswerParam.getSubjects().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<String> subjectIds = qsvAnswerParam.getSubjects().stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(QsvAnswerParam.Subject::getId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
if (subjectIds.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Map<String, QsvSubject> subjectMap = new HashMap<>();
|
||||||
|
dao().query(QsvSubject.class, Cnd.where("id", "in", subjectIds)).forEach(subject -> subjectMap.put(subject.getId(), subject));
|
||||||
|
for (QsvAnswerParam.Subject answerSubject : qsvAnswerParam.getSubjects()) {
|
||||||
|
if (answerSubject == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QsvSubject configSubject = subjectMap.get(answerSubject.getId());
|
||||||
|
if (configSubject == null || !"checkbox".equals(configSubject.getType()) || configSubject.getMaxMulti() == null || configSubject.getMaxMulti() <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<String> selectedOptionIds = answerSubject.getUserSelectOptionIds();
|
||||||
|
int selectedCount = selectedOptionIds == null ? 0 : new LinkedHashSet<>(selectedOptionIds).size();
|
||||||
|
if (selectedCount > configSubject.getMaxMulti()) {
|
||||||
|
return "题目“" + configSubject.getTitle() + "”最多选择" + configSubject.getMaxMulti() + "项";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QsvUserAnswerRecord insertRecord(String activityId, List<String> subjectIds) {
|
public QsvUserAnswerRecord insertRecord(String activityId, List<String> subjectIds) {
|
||||||
QsvActivity activity = dao().fetch(QsvActivity.class, activityId);
|
QsvActivity activity = dao().fetch(QsvActivity.class, activityId);
|
||||||
@@ -121,11 +159,12 @@ public class QsvUserAnswerRecordServiceImpl extends BaseServiceImpl<QsvUserAnswe
|
|||||||
|
|
||||||
String mode = activity.getMode();
|
String mode = activity.getMode();
|
||||||
String scoreMode = activity.getScoreMode();
|
String scoreMode = activity.getScoreMode();
|
||||||
|
boolean highestScoreMode = "HIGH".equals(scoreMode) || "HIGHEST".equals(scoreMode);
|
||||||
|
|
||||||
if (mode.equals("SCHEDULED")) {
|
if (mode.equals("SCHEDULED")) {
|
||||||
processScoreRecords(activityId, SecurityUtil.getUserId(), true, scoreMode.equals("HIGH"));
|
processScoreRecords(activityId, SecurityUtil.getUserId(), true, highestScoreMode);
|
||||||
} else if (mode.equals("REGULAR")) {
|
} else if (mode.equals("REGULAR")) {
|
||||||
processScoreRecords(activityId, SecurityUtil.getUserId(), false, scoreMode.equals("HIGH"));
|
processScoreRecords(activityId, SecurityUtil.getUserId(), false, highestScoreMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+217
-38
@@ -1,6 +1,7 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import cn.dev33.satoken.annotation.SaMode;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.budwk.app.base.constant.RoleConstant;
|
import com.budwk.app.base.constant.RoleConstant;
|
||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
@@ -169,58 +170,184 @@ public class TourGroupController {
|
|||||||
return Result.success(sql.getList(NutMap.class));
|
return Result.success(sql.getList(NutMap.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前事项线路下已经写入疗休养台账的成团信息,供线路成团查看页展示团标签。
|
||||||
|
*/
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("tour.group")
|
@SaCheckPermission("tour.group")
|
||||||
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId) {
|
public Result groupOptions(String matterId) {
|
||||||
|
if (StrUtil.isBlank(matterId)) {
|
||||||
|
return Result.error("参数错误");
|
||||||
|
}
|
||||||
|
Cnd cnd = buildGroupOptionCnd(matterId);
|
||||||
|
return Result.success(tourLedgerService.queryGroupOptions(cnd));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动将当前事项线路下未成团的报名人员按最大成团人数分配成团。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("tour.group")
|
||||||
|
public Result assignGroup(String matterId) {
|
||||||
|
if (StrUtil.isBlank(matterId)) {
|
||||||
|
return Result.error("参数错误");
|
||||||
|
}
|
||||||
|
return tourLedgerService.assignGroup(matterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将勾选的教职工报名台账调整到指定成团中,家属随教职工一起调整。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("tour.group")
|
||||||
|
public Result adjustGroup(String matterId, String groupCode, String ledgerIds) {
|
||||||
|
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode) || StrUtil.isBlank(ledgerIds)) {
|
||||||
|
return Result.error("参数错误");
|
||||||
|
}
|
||||||
|
return tourLedgerService.adjustGroup(matterId, groupCode, ledgerIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission(value = {"tour.group", "tour.signup"}, mode = SaMode.OR)
|
||||||
|
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId, String groupCode) {
|
||||||
if (StrUtil.isBlank(matterId)) {
|
if (StrUtil.isBlank(matterId)) {
|
||||||
return Result.error("参数错误");
|
return Result.error("参数错误");
|
||||||
}
|
}
|
||||||
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||||
pageForm.defaultSortDesc("signupTime");
|
pageForm.defaultSortAsc("groupCode");
|
||||||
}
|
}
|
||||||
Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId);
|
Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId, groupCode);
|
||||||
|
boolean maskOtherUnionPeople = shouldMaskOtherUnionSignupPeople();
|
||||||
|
String currentUnionId = StrUtil.blankToDefault(SecurityUtil.getUnionId(), "");
|
||||||
Sql countSql = Sqls.create("""
|
Sql countSql = Sqls.create("""
|
||||||
SELECT COUNT(1)
|
SELECT COUNT(1)
|
||||||
FROM tour_ledger t
|
FROM (
|
||||||
INNER JOIN tour_matter m ON m.id = t.matterId
|
SELECT t.id AS rowId
|
||||||
INNER JOIN tour_line l ON l.id = m.lineId
|
FROM tour_ledger t
|
||||||
$condition
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
$condition
|
||||||
|
UNION ALL
|
||||||
|
SELECT f.id AS rowId
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
INNER JOIN tour_ledger_family f ON f.ledgerId = t.id AND f.delFlag = 0
|
||||||
|
$condition
|
||||||
|
AND (@maskOtherUnionPeople = 0 OR IFNULL(t.unionId, '') = @currentUnionId)
|
||||||
|
) p
|
||||||
""");
|
""");
|
||||||
countSql.setCondition(cnd);
|
countSql.setCondition(cnd);
|
||||||
|
countSql.setParam("currentUnionId", currentUnionId);
|
||||||
|
countSql.setParam("maskOtherUnionPeople", maskOtherUnionPeople ? 1 : 0);
|
||||||
countSql.setCallback(Sqls.callback.integer());
|
countSql.setCallback(Sqls.callback.integer());
|
||||||
tourLedgerService.dao().execute(countSql);
|
tourLedgerService.dao().execute(countSql);
|
||||||
int count = countSql.getInt();
|
int count = countSql.getInt();
|
||||||
|
|
||||||
Sql listSql = Sqls.create("""
|
Sql listSql = Sqls.create("""
|
||||||
SELECT
|
SELECT *
|
||||||
t.id,
|
FROM (
|
||||||
t.jobNo,
|
SELECT
|
||||||
t.userName,
|
t.id,
|
||||||
'' AS mobile,
|
t.id AS ledgerId,
|
||||||
t.idCard,
|
t.jobNo,
|
||||||
t.unionId,
|
t.userName,
|
||||||
t.unionName,
|
t.gender,
|
||||||
t.signupTime
|
'' AS mobile,
|
||||||
FROM tour_ledger t
|
CASE WHEN @maskOtherUnionPeople = 1 AND IFNULL(t.unionId, '') <> @currentUnionId THEN '' ELSE t.idCard END AS idCard,
|
||||||
INNER JOIN tour_matter m ON m.id = t.matterId
|
t.unionId,
|
||||||
INNER JOIN tour_line l ON l.id = m.lineId
|
t.unionName,
|
||||||
$condition
|
t.signupTime,
|
||||||
ORDER BY $orderColumn $orderBy, t.signupTime DESC, t.createdAt DESC
|
t.groupCode,
|
||||||
|
t.groupName,
|
||||||
|
CASE WHEN @maskOtherUnionPeople = 1 AND IFNULL(t.unionId, '') <> @currentUnionId THEN 0 ELSE IFNULL(fc.familyCount, 0) END AS familyCount,
|
||||||
|
CASE WHEN @maskOtherUnionPeople = 1 AND IFNULL(t.unionId, '') <> @currentUnionId THEN 1 ELSE 1 + IFNULL(fc.familyCount, 0) END AS totalPeople,
|
||||||
|
1 AS staffRow,
|
||||||
|
1 AS selectable,
|
||||||
|
'教职工' AS rowTypeName,
|
||||||
|
'' AS relationship,
|
||||||
|
t.userName AS relatedStaffName,
|
||||||
|
0 AS rowSort,
|
||||||
|
t.createdAt AS ledgerCreatedAt,
|
||||||
|
t.createdAt
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
|
FROM tour_ledger_family
|
||||||
|
WHERE delFlag = 0
|
||||||
|
GROUP BY ledgerId
|
||||||
|
) fc ON fc.ledgerId = t.id
|
||||||
|
$condition
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
f.id,
|
||||||
|
t.id AS ledgerId,
|
||||||
|
'' AS jobNo,
|
||||||
|
f.familyName AS userName,
|
||||||
|
f.gender,
|
||||||
|
'' AS mobile,
|
||||||
|
f.idCard,
|
||||||
|
t.unionId,
|
||||||
|
'' AS unionName,
|
||||||
|
t.signupTime,
|
||||||
|
t.groupCode,
|
||||||
|
t.groupName,
|
||||||
|
0 AS familyCount,
|
||||||
|
'' AS totalPeople,
|
||||||
|
0 AS staffRow,
|
||||||
|
0 AS selectable,
|
||||||
|
'家属' AS rowTypeName,
|
||||||
|
f.relationship,
|
||||||
|
t.userName AS relatedStaffName,
|
||||||
|
1 AS rowSort,
|
||||||
|
t.createdAt AS ledgerCreatedAt,
|
||||||
|
f.createdAt
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
INNER JOIN tour_ledger_family f ON f.ledgerId = t.id AND f.delFlag = 0
|
||||||
|
$condition
|
||||||
|
AND (@maskOtherUnionPeople = 0 OR IFNULL(t.unionId, '') = @currentUnionId)
|
||||||
|
) p
|
||||||
|
ORDER BY $orderColumn $orderBy, p.signupTime ASC, p.ledgerCreatedAt ASC, p.rowSort ASC, p.createdAt ASC
|
||||||
""");
|
""");
|
||||||
listSql.setCondition(cnd);
|
listSql.setCondition(cnd);
|
||||||
listSql.setVar("orderColumn", getSignupOrderColumn(pageForm.getPageOrderName()));
|
listSql.setParam("currentUnionId", currentUnionId);
|
||||||
|
listSql.setParam("maskOtherUnionPeople", maskOtherUnionPeople ? 1 : 0);
|
||||||
|
listSql.setVar("orderColumn", getSignupUnionOrderColumn(pageForm.getPageOrderName()));
|
||||||
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
|
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
|
||||||
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||||
listSql.setCallback(Sqls.callback.maps());
|
listSql.setCallback(Sqls.callback.maps());
|
||||||
tourLedgerService.dao().execute(listSql);
|
tourLedgerService.dao().execute(listSql);
|
||||||
|
|
||||||
List<NutMap> list = listSql.getList(NutMap.class);
|
List<NutMap> list = listSql.getList(NutMap.class);
|
||||||
fillSignupMobile(list);
|
// 人员列表已在 SQL 层按教职工和家属平铺后分页,这里只补齐教职工手机号,避免分页后再次追加家属行。
|
||||||
fillSignupFamilies(list);
|
fillSignupMobile(list, maskOtherUnionPeople);
|
||||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||||
return Result.success(pagination);
|
return Result.success(pagination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造成团标签查询条件:只取当前事项线路已分配成团编码的台账,并保持与列表一致的数据权限。
|
||||||
|
*/
|
||||||
|
private Cnd buildGroupOptionCnd(String matterId) {
|
||||||
|
Cnd cnd = Cnd.NEW();
|
||||||
|
cnd.and("t.delFlag", "=", false);
|
||||||
|
cnd.and("t.matterId", "=", matterId);
|
||||||
|
cnd.and("t.groupCode", "IS NOT", null);
|
||||||
|
cnd.and("t.groupCode", "<>", "");
|
||||||
|
cnd.and("m.delFlag", "=", false);
|
||||||
|
cnd.and("m.enabled", "=", true);
|
||||||
|
cnd.and("m.lineId", "IS NOT", null);
|
||||||
|
cnd.and("m.lineId", "<>", "");
|
||||||
|
cnd.and("l.delFlag", "=", false);
|
||||||
|
cnd.and("l.enabled", "=", true);
|
||||||
|
applyMatterDataScope(cnd);
|
||||||
|
return cnd;
|
||||||
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@Ok("void")
|
@Ok("void")
|
||||||
@SaCheckPermission("tour.group")
|
@SaCheckPermission("tour.group")
|
||||||
@@ -238,7 +365,7 @@ public class TourGroupController {
|
|||||||
CommonDownloadUtil.download(lineName + "参加人员名单.xls", workbook, response);
|
CommonDownloadUtil.download(lineName + "参加人员名单.xls", workbook, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId) {
|
private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId, String groupCode) {
|
||||||
Cnd cnd = Cnd.NEW();
|
Cnd cnd = Cnd.NEW();
|
||||||
cnd.and("t.delFlag", "=", false);
|
cnd.and("t.delFlag", "=", false);
|
||||||
cnd.and("t.matterId", "=", matterId);
|
cnd.and("t.matterId", "=", matterId);
|
||||||
@@ -255,7 +382,8 @@ public class TourGroupController {
|
|||||||
cnd.and(keywordGroup);
|
cnd.and(keywordGroup);
|
||||||
}
|
}
|
||||||
cnd.andEX("t.unionId", "=", unionId);
|
cnd.andEX("t.unionId", "=", unionId);
|
||||||
applyMatterDataScope(cnd);
|
// 团标签联动查询时只筛选当前团;未选择团标签时默认查询全部报名人员。
|
||||||
|
cnd.andEX("t.groupCode", "=", groupCode);
|
||||||
return cnd;
|
return cnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +425,7 @@ public class TourGroupController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<NutMap> queryExportParticipants(String matterId) {
|
private List<NutMap> queryExportParticipants(String matterId) {
|
||||||
Cnd cnd = buildSignupQueryCnd(matterId, null, null);
|
Cnd cnd = buildSignupQueryCnd(matterId, null, null, null);
|
||||||
Sql sql = Sqls.create("""
|
Sql sql = Sqls.create("""
|
||||||
SELECT
|
SELECT
|
||||||
t.id AS ledgerId,
|
t.id AS ledgerId,
|
||||||
@@ -485,12 +613,16 @@ public class TourGroupController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillSignupMobile(List<NutMap> list) {
|
private void fillSignupMobile(List<NutMap> list, boolean maskOtherUnionPeople) {
|
||||||
if (list == null || list.isEmpty()) {
|
if (list == null || list.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<String> jobNos = new ArrayList<>();
|
List<String> jobNos = new ArrayList<>();
|
||||||
|
String currentUnionId = StrUtil.blankToDefault(SecurityUtil.getUnionId(), "");
|
||||||
for (NutMap item : list) {
|
for (NutMap item : list) {
|
||||||
|
if (maskOtherUnionPeople && !currentUnionId.equals(item.getString("unionId", ""))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
String jobNo = item.getString("jobNo", "");
|
String jobNo = item.getString("jobNo", "");
|
||||||
if (StrUtil.isNotBlank(jobNo) && !jobNos.contains(jobNo)) {
|
if (StrUtil.isNotBlank(jobNo) && !jobNos.contains(jobNo)) {
|
||||||
jobNos.add(jobNo);
|
jobNos.add(jobNo);
|
||||||
@@ -510,14 +642,18 @@ public class TourGroupController {
|
|||||||
mobileMap.put(user.getString("loginname", ""), user.getString("mobile", ""));
|
mobileMap.put(user.getString("loginname", ""), user.getString("mobile", ""));
|
||||||
}
|
}
|
||||||
for (NutMap item : list) {
|
for (NutMap item : list) {
|
||||||
|
if (maskOtherUnionPeople && !currentUnionId.equals(item.getString("unionId", ""))) {
|
||||||
|
item.put("mobile", "");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
item.put("mobile", mobileMap.getOrDefault(item.getString("jobNo", ""), ""));
|
item.put("mobile", mobileMap.getOrDefault(item.getString("jobNo", ""), ""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查看成团报名人员时按台账ID带出家属子列表,避免前端逐行请求家属信息。
|
* 查看成团报名人员时将教职工和家属平铺在同一个列表,便于前端统一展示。
|
||||||
*/
|
*/
|
||||||
private void fillSignupFamilies(List<NutMap> list) {
|
private void flattenSignupFamilies(List<NutMap> list) {
|
||||||
if (list == null || list.isEmpty()) {
|
if (list == null || list.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -535,8 +671,9 @@ public class TourGroupController {
|
|||||||
Sql sql = Sqls.create("""
|
Sql sql = Sqls.create("""
|
||||||
SELECT
|
SELECT
|
||||||
ledgerId,
|
ledgerId,
|
||||||
staffJobNo AS jobNo,
|
'' AS jobNo,
|
||||||
familyName AS userName,
|
familyName AS userName,
|
||||||
|
gender,
|
||||||
'' AS mobile,
|
'' AS mobile,
|
||||||
idCard,
|
idCard,
|
||||||
relationship
|
relationship
|
||||||
@@ -551,11 +688,35 @@ public class TourGroupController {
|
|||||||
|
|
||||||
Map<String, List<NutMap>> familyMap = new HashMap<>();
|
Map<String, List<NutMap>> familyMap = new HashMap<>();
|
||||||
for (NutMap family : sql.getList(NutMap.class)) {
|
for (NutMap family : sql.getList(NutMap.class)) {
|
||||||
|
family.put("staffRow", false);
|
||||||
|
family.put("selectable", false);
|
||||||
|
family.put("rowTypeName", "家属");
|
||||||
|
family.put("unionName", "");
|
||||||
|
family.put("groupCode", "");
|
||||||
|
family.put("groupName", "");
|
||||||
|
family.put("totalPeople", "");
|
||||||
familyMap.computeIfAbsent(family.getString("ledgerId", ""), key -> new ArrayList<>()).add(family);
|
familyMap.computeIfAbsent(family.getString("ledgerId", ""), key -> new ArrayList<>()).add(family);
|
||||||
}
|
}
|
||||||
|
List<NutMap> flatList = new ArrayList<>();
|
||||||
for (NutMap item : list) {
|
for (NutMap item : list) {
|
||||||
item.put("families", familyMap.getOrDefault(item.getString("id", ""), List.of()));
|
// 教职工是后续人员调整的最小可选单位,家属随教职工一起分配。
|
||||||
|
item.put("staffRow", true);
|
||||||
|
item.put("selectable", true);
|
||||||
|
item.put("rowTypeName", "教职工");
|
||||||
|
item.put("relationship", "");
|
||||||
|
item.put("relatedStaffName", item.getString("userName", ""));
|
||||||
|
flatList.add(item);
|
||||||
|
List<NutMap> families = familyMap.getOrDefault(item.getString("id", ""), List.of());
|
||||||
|
for (NutMap family : families) {
|
||||||
|
// 家属行通过关联教职工名称标明所属教职工,便于单层列表中识别家庭关系。
|
||||||
|
family.put("relatedStaffName", item.getString("userName", ""));
|
||||||
|
family.put("groupCode", item.getString("groupCode", ""));
|
||||||
|
family.put("groupName", item.getString("groupName", ""));
|
||||||
|
}
|
||||||
|
flatList.addAll(families);
|
||||||
}
|
}
|
||||||
|
list.clear();
|
||||||
|
list.addAll(flatList);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId) {
|
private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId) {
|
||||||
@@ -606,6 +767,15 @@ public class TourGroupController {
|
|||||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name());
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Branch union chairmen can browse all staff rows, but sensitive fields and family rows outside their union are hidden.
|
||||||
|
*/
|
||||||
|
private boolean shouldMaskOtherUnionSignupPeople() {
|
||||||
|
return AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())
|
||||||
|
&& !AuthUtil.hasRole(RoleConstant.SYSADMIN.name())
|
||||||
|
&& !AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasBranchUnionScope() {
|
private boolean hasBranchUnionScope() {
|
||||||
return AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ADMIN.name())
|
return AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ADMIN.name())
|
||||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())
|
||||||
@@ -655,19 +825,28 @@ public class TourGroupController {
|
|||||||
return "l.lineName";
|
return "l.lineName";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSignupOrderColumn(String orderName) {
|
private String getSignupUnionOrderColumn(String orderName) {
|
||||||
if ("jobNo".equals(orderName)) {
|
if ("jobNo".equals(orderName)) {
|
||||||
return "t.jobNo";
|
return "p.jobNo";
|
||||||
}
|
}
|
||||||
if ("userName".equals(orderName)) {
|
if ("userName".equals(orderName)) {
|
||||||
return "t.userName";
|
return "p.userName";
|
||||||
|
}
|
||||||
|
if ("gender".equals(orderName)) {
|
||||||
|
return "p.gender";
|
||||||
}
|
}
|
||||||
if ("idCard".equals(orderName)) {
|
if ("idCard".equals(orderName)) {
|
||||||
return "t.idCard";
|
return "p.idCard";
|
||||||
}
|
}
|
||||||
if ("unionName".equals(orderName)) {
|
if ("unionName".equals(orderName)) {
|
||||||
return "t.unionName";
|
return "p.unionName";
|
||||||
}
|
}
|
||||||
return "t.signupTime";
|
if ("groupCode".equals(orderName) || "groupName".equals(orderName)) {
|
||||||
|
return "p.groupCode";
|
||||||
|
}
|
||||||
|
if ("totalPeople".equals(orderName)) {
|
||||||
|
return "p.totalPeople";
|
||||||
|
}
|
||||||
|
return "p.signupTime";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+71
@@ -26,6 +26,7 @@ import com.budwk.app.zhgh.dayofficework.tour.models.TourTravelAgency;
|
|||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeService;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourParticipantRosterService;
|
||||||
import org.apache.poi.ss.usermodel.Workbook;
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
import org.apache.poi.ss.usermodel.BorderStyle;
|
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||||
@@ -90,6 +91,9 @@ public class TourLedgerController {
|
|||||||
@Inject
|
@Inject
|
||||||
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
|
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private TourParticipantRosterService tourParticipantRosterService;
|
||||||
|
|
||||||
@At("")
|
@At("")
|
||||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/ledger/index.html")
|
@Ok("beetl:/platform/zhgh/dayofficework/Tour/ledger/index.html")
|
||||||
@SaCheckPermission("tour.ledger")
|
@SaCheckPermission("tour.ledger")
|
||||||
@@ -216,6 +220,8 @@ public class TourLedgerController {
|
|||||||
if (ledger == null) {
|
if (ledger == null) {
|
||||||
return Result.error("台账记录不存在");
|
return Result.error("台账记录不存在");
|
||||||
}
|
}
|
||||||
|
// Reset the participant roster before deleting the ledger record from the admin ledger page.
|
||||||
|
tourParticipantRosterService.markNotJoinedByLedger(ledger);
|
||||||
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", id));
|
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", id));
|
||||||
tourLedgerService.delete(id);
|
tourLedgerService.delete(id);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
@@ -313,6 +319,59 @@ public class TourLedgerController {
|
|||||||
CommonDownloadUtil.download("参加人员导入模板.xlsx", workbook, response);
|
CommonDownloadUtil.download("参加人员导入模板.xlsx", workbook, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按台账页面当前查询条件导出全部匹配记录。
|
||||||
|
*
|
||||||
|
* @param startYear 开始年度,传四位年度数字
|
||||||
|
* @param endYear 结束年度,传四位年度数字
|
||||||
|
* @param keyword 姓名或工号关键字,可为空
|
||||||
|
* @param unionId 所属工会ID,可为空
|
||||||
|
* @param lineId 线路ID;历史线路使用legacy:线路名称,可为空
|
||||||
|
* @param travelPeriod 出行时段,格式为开始时间 至 结束时间,可为空
|
||||||
|
* @param lineType 线路类型字典名称,可为空
|
||||||
|
* @param directFamilyOnly 是否仅导出参加直系亲属单位线路人员
|
||||||
|
* @param overCostOnly 是否仅导出申请超出费用由单位承担人员
|
||||||
|
* @param response HTTP响应,返回XSSF格式的Excel文件流
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@Ok("void")
|
||||||
|
@SaCheckPermission("tour.ledger")
|
||||||
|
public void exportData(Integer startYear, Integer endYear, String keyword, String unionId, String lineId,
|
||||||
|
String travelPeriod, String lineType, Boolean directFamilyOnly, Boolean overCostOnly,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
Cnd cnd = buildQueryCnd(startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType,
|
||||||
|
directFamilyOnly, overCostOnly);
|
||||||
|
List<NutMap> rows = tourLedgerService.queryExportRows(cnd);
|
||||||
|
for (int i = 0; i < rows.size(); i++) {
|
||||||
|
NutMap row = rows.get(i);
|
||||||
|
row.put("index", i + 1);
|
||||||
|
row.put("groupInfo", formatGroupInfo(row));
|
||||||
|
row.put("joinedName", row.getBoolean("joined", false) ? "是" : "否");
|
||||||
|
row.put("overCostReimbursedName", row.getBoolean("overCostReimbursed", false) ? "是" : "否");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ExcelExportEntity> entities = new ArrayList<>();
|
||||||
|
entities.add(new ExcelExportEntity("序号", "index", 8));
|
||||||
|
entities.add(new ExcelExportEntity("年度", "year", 12));
|
||||||
|
entities.add(new ExcelExportEntity("工号", "jobNo", 18));
|
||||||
|
entities.add(new ExcelExportEntity("姓名", "userName", 16));
|
||||||
|
entities.add(new ExcelExportEntity("所在单位", "unitName", 28));
|
||||||
|
entities.add(new ExcelExportEntity("报名线路", "lineName", 30));
|
||||||
|
entities.add(new ExcelExportEntity("出行时段", "travelPeriod", 30));
|
||||||
|
entities.add(new ExcelExportEntity("线路类型", "lineType", 16));
|
||||||
|
entities.add(new ExcelExportEntity("成团信息", "groupInfo", 20));
|
||||||
|
entities.add(new ExcelExportEntity("领办工会", "undertakeUnionName", 24));
|
||||||
|
entities.add(new ExcelExportEntity("报名时间", "signupTime", 24));
|
||||||
|
entities.add(new ExcelExportEntity("携带家属人数", "familyCount", 16));
|
||||||
|
entities.add(new ExcelExportEntity("是否参加", "joinedName", 14));
|
||||||
|
entities.add(new ExcelExportEntity("是否申请超出费用由单位承担", "overCostReimbursedName", 28));
|
||||||
|
|
||||||
|
ExportParams exportParams = new ExportParams();
|
||||||
|
exportParams.setType(ExcelType.XSSF);
|
||||||
|
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, rows);
|
||||||
|
CommonDownloadUtil.download("疗休养台账.xlsx", workbook, response);
|
||||||
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("tour.ledger")
|
@SaCheckPermission("tour.ledger")
|
||||||
@Ok("void")
|
@Ok("void")
|
||||||
@@ -414,6 +473,18 @@ public class TourLedgerController {
|
|||||||
return unionRows;
|
return unionRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将成团编码和成团名称合并为列表一致的展示内容,任一字段为空时保留已有信息。
|
||||||
|
*/
|
||||||
|
private String formatGroupInfo(NutMap row) {
|
||||||
|
String groupCode = row.getString("groupCode", "");
|
||||||
|
String groupName = row.getString("groupName", "");
|
||||||
|
if (StrUtil.isBlank(groupCode)) {
|
||||||
|
return groupName;
|
||||||
|
}
|
||||||
|
return StrUtil.isBlank(groupName) ? groupCode : groupCode + " " + groupName;
|
||||||
|
}
|
||||||
|
|
||||||
private Workbook buildUnionSignupWorkbook(String unionName, List<NutMap> rows) {
|
private Workbook buildUnionSignupWorkbook(String unionName, List<NutMap> rows) {
|
||||||
HSSFWorkbook workbook = new HSSFWorkbook();
|
HSSFWorkbook workbook = new HSSFWorkbook();
|
||||||
Sheet sheet = workbook.createSheet("报名汇总表");
|
Sheet sheet = workbook.createSheet("报名汇总表");
|
||||||
|
|||||||
+86
-6
@@ -1,5 +1,9 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||||
|
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.SaCheckPermission;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.budwk.app.base.annotation.SLog;
|
import com.budwk.app.base.annotation.SLog;
|
||||||
@@ -7,6 +11,7 @@ import com.budwk.app.base.constant.RoleConstant;
|
|||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.param.PageForm;
|
import com.budwk.app.base.param.PageForm;
|
||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
|
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||||
import com.budwk.app.sys.models.Sys_dict;
|
import com.budwk.app.sys.models.Sys_dict;
|
||||||
import com.budwk.app.sys.services.SysDictService;
|
import com.budwk.app.sys.services.SysDictService;
|
||||||
import com.budwk.app.sys.views.View_user;
|
import com.budwk.app.sys.views.View_user;
|
||||||
@@ -26,7 +31,10 @@ import org.nutz.ioc.loader.annotation.IocBean;
|
|||||||
import org.nutz.lang.util.NutMap;
|
import org.nutz.lang.util.NutMap;
|
||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
import org.nutz.mvc.annotation.Ok;
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -36,8 +44,6 @@ import java.util.stream.Collectors;
|
|||||||
@At("/platform/tour/route")
|
@At("/platform/tour/route")
|
||||||
public class TourLineController {
|
public class TourLineController {
|
||||||
|
|
||||||
private static final String SORT_CODE_PATTERN = "^\\d+$";
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private TourLineService tourLineService;
|
private TourLineService tourLineService;
|
||||||
|
|
||||||
@@ -52,7 +58,7 @@ public class TourLineController {
|
|||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("tour.route")
|
@SaCheckPermission("tour.route")
|
||||||
public Result pageData(PageForm pageForm, Integer year, String lineName, String lotId) {
|
public Result pageData(PageForm pageForm, Integer year, String lineName, String lotId, String lineType) {
|
||||||
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||||
pageForm.defaultSortAsc("lineCode");
|
pageForm.defaultSortAsc("lineCode");
|
||||||
}
|
}
|
||||||
@@ -62,6 +68,8 @@ public class TourLineController {
|
|||||||
cnd.andEX("l.`year`", "=", year);
|
cnd.andEX("l.`year`", "=", year);
|
||||||
cnd.and(Cnd.likeEX("l.lineName", lineName));
|
cnd.and(Cnd.likeEX("l.lineName", lineName));
|
||||||
cnd.andEX("l.lotId", "=", lotId);
|
cnd.andEX("l.lotId", "=", lotId);
|
||||||
|
// 线路类型查询使用线路表保存的字典名称,和新增/编辑线路时的取值保持一致。
|
||||||
|
cnd.andEX("l.lineType", "=", lineType);
|
||||||
boolean sysAdmin = AuthUtil.hasRole(RoleConstant.SYSADMIN.name());
|
boolean sysAdmin = AuthUtil.hasRole(RoleConstant.SYSADMIN.name());
|
||||||
boolean branchUnionScope = !sysAdmin && AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name());
|
boolean branchUnionScope = !sysAdmin && AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name());
|
||||||
applyDataScope(cnd, sysAdmin, branchUnionScope);
|
applyDataScope(cnd, sysAdmin, branchUnionScope);
|
||||||
@@ -107,6 +115,78 @@ public class TourLineController {
|
|||||||
return Result.success(pagination);
|
return Result.success(pagination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出线路关联事项数据,导出范围复用线路管理列表的筛选条件和数据权限。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@Ok("void")
|
||||||
|
@SaCheckPermission("tour.route")
|
||||||
|
public void exportData(Integer year, String lineName, String lotId, String lineType, HttpServletResponse response) {
|
||||||
|
Cnd cnd = Cnd.NEW();
|
||||||
|
cnd.and("l.delFlag", "=", false);
|
||||||
|
cnd.andEX("l.`year`", "=", year);
|
||||||
|
cnd.and(Cnd.likeEX("l.lineName", lineName));
|
||||||
|
cnd.andEX("l.lotId", "=", lotId);
|
||||||
|
// 导出范围复用列表筛选条件,线路类型也要同步过滤。
|
||||||
|
cnd.andEX("l.lineType", "=", lineType);
|
||||||
|
boolean sysAdmin = AuthUtil.hasRole(RoleConstant.SYSADMIN.name());
|
||||||
|
boolean branchUnionScope = !sysAdmin && AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name());
|
||||||
|
applyDataScope(cnd, sysAdmin, branchUnionScope);
|
||||||
|
String creatorJoin = branchUnionScope ? """
|
||||||
|
LEFT JOIN sys_unit lineUnit ON lineUnit.id = l.unitId
|
||||||
|
LEFT JOIN sys_user creatorUser ON creatorUser.id = COALESCE(NULLIF(l.creatorUserId, ''), l.createdBy)
|
||||||
|
LEFT JOIN sys_unit creatorUnit ON creatorUnit.id = creatorUser.unitId
|
||||||
|
""" : "";
|
||||||
|
|
||||||
|
// 线路导出需要带出事项信息,因此以线路为主表左关联事项,未关联事项的线路仍保留导出。
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
l.`year`,
|
||||||
|
l.lineName,
|
||||||
|
lot.lotName,
|
||||||
|
l.lineType,
|
||||||
|
m.matterName,
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(m.signupStartTime, '') <> '' AND IFNULL(m.signupEndTime, '') <> ''
|
||||||
|
THEN CONCAT(m.signupStartTime, ' 至 ', m.signupEndTime)
|
||||||
|
ELSE ''
|
||||||
|
END AS signupTime,
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||||
|
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||||
|
ELSE ''
|
||||||
|
END AS travelTime,
|
||||||
|
m.contactName,
|
||||||
|
m.contactPhone
|
||||||
|
FROM tour_line l
|
||||||
|
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
|
||||||
|
LEFT JOIN tour_matter m ON l.id = m.lineId AND m.delFlag = 0
|
||||||
|
$creatorJoin
|
||||||
|
$condition
|
||||||
|
ORDER BY CAST(l.lineCode AS UNSIGNED) ASC, l.lineCode ASC, m.matterName ASC, l.lineName ASC
|
||||||
|
""");
|
||||||
|
sql.setVar("creatorJoin", creatorJoin);
|
||||||
|
sql.setCondition(cnd);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
tourLineService.dao().execute(sql);
|
||||||
|
|
||||||
|
List<ExcelExportEntity> entities = new ArrayList<>();
|
||||||
|
entities.add(new ExcelExportEntity("创建年度", "year", 14));
|
||||||
|
entities.add(new ExcelExportEntity("线路名称", "lineName", 30));
|
||||||
|
entities.add(new ExcelExportEntity("时间标段", "lotName", 18));
|
||||||
|
entities.add(new ExcelExportEntity("路线类型", "lineType", 18));
|
||||||
|
entities.add(new ExcelExportEntity("事项名称", "matterName", 30));
|
||||||
|
entities.add(new ExcelExportEntity("报名时间", "signupTime", 34));
|
||||||
|
entities.add(new ExcelExportEntity("出行时间", "travelTime", 24));
|
||||||
|
entities.add(new ExcelExportEntity("联系人", "contactName", 16));
|
||||||
|
entities.add(new ExcelExportEntity("联系方式", "contactPhone", 18));
|
||||||
|
|
||||||
|
ExportParams exportParams = new ExportParams();
|
||||||
|
exportParams.setType(ExcelType.XSSF);
|
||||||
|
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, sql.getList(NutMap.class));
|
||||||
|
CommonDownloadUtil.download("线路事项数据.xlsx", workbook, response);
|
||||||
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("tour.route")
|
@SaCheckPermission("tour.route")
|
||||||
public Result detail(String id) {
|
public Result detail(String id) {
|
||||||
@@ -253,11 +333,11 @@ public class TourLineController {
|
|||||||
if (StrUtil.isBlank(line.getLineName())) {
|
if (StrUtil.isBlank(line.getLineName())) {
|
||||||
return Result.error("线路名称不能为空");
|
return Result.error("线路名称不能为空");
|
||||||
}
|
}
|
||||||
if (StrUtil.isBlank(line.getLineCode())) {
|
if (line.getLineCode() == null) {
|
||||||
return Result.error("排序编号不能为空");
|
return Result.error("排序编号不能为空");
|
||||||
}
|
}
|
||||||
if (!line.getLineCode().matches(SORT_CODE_PATTERN)) {
|
if (line.getLineCode() < 0) {
|
||||||
return Result.error("排序编号必须为整数");
|
return Result.error("排序编号不能小于0");
|
||||||
}
|
}
|
||||||
if (StrUtil.isBlank(line.getLineType())) {
|
if (StrUtil.isBlank(line.getLineType())) {
|
||||||
return Result.error("线路类型不能为空");
|
return Result.error("线路类型不能为空");
|
||||||
|
|||||||
+159
-22
@@ -1,12 +1,17 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||||
|
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.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.budwk.app.base.annotation.SLog;
|
import com.budwk.app.base.annotation.SLog;
|
||||||
import com.budwk.app.base.constant.RoleConstant;
|
import com.budwk.app.base.constant.RoleConstant;
|
||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.param.PageForm;
|
import com.budwk.app.base.param.PageForm;
|
||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
|
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||||
import com.budwk.app.sys.models.Sys_dict;
|
import com.budwk.app.sys.models.Sys_dict;
|
||||||
import com.budwk.app.sys.models.Sys_union;
|
import com.budwk.app.sys.models.Sys_union;
|
||||||
import com.budwk.app.sys.services.SysDictService;
|
import com.budwk.app.sys.services.SysDictService;
|
||||||
@@ -27,7 +32,10 @@ import org.nutz.ioc.loader.annotation.IocBean;
|
|||||||
import org.nutz.lang.util.NutMap;
|
import org.nutz.lang.util.NutMap;
|
||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
import org.nutz.mvc.annotation.Ok;
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -53,18 +61,13 @@ public class TourMatterController {
|
|||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("tour.matter")
|
@SaCheckPermission("tour.matter")
|
||||||
public Result pageData(PageForm pageForm, Integer year, String matterName, String unionId, String organizationType) {
|
public Result pageData(PageForm pageForm, Integer year, String matterName, String unionId, String organizationType, String lineType) {
|
||||||
Cnd cnd = Cnd.NEW();
|
Cnd cnd = buildMatterPageCnd(year, matterName, unionId, organizationType, lineType);
|
||||||
cnd.and("m.delFlag", "=", false);
|
|
||||||
cnd.andEX("m.`year`", "=", year);
|
|
||||||
cnd.and(Cnd.likeEX("m.matterName", matterName));
|
|
||||||
cnd.andEX("m.unionId", "=", unionId);
|
|
||||||
cnd.andEX("m.organizationType", "=", organizationType);
|
|
||||||
applyMatterDataScope(cnd);
|
|
||||||
|
|
||||||
Sql countSql = Sqls.create("""
|
Sql countSql = Sqls.create("""
|
||||||
SELECT COUNT(1)
|
SELECT COUNT(1)
|
||||||
FROM tour_matter m
|
FROM tour_matter m
|
||||||
|
LEFT JOIN tour_line l ON l.id = m.lineId
|
||||||
$condition
|
$condition
|
||||||
""");
|
""");
|
||||||
countSql.setCondition(cnd);
|
countSql.setCondition(cnd);
|
||||||
@@ -91,7 +94,7 @@ public class TourMatterController {
|
|||||||
LEFT JOIN sys_dict d ON d.`code` = m.organizationType
|
LEFT JOIN sys_dict d ON d.`code` = m.organizationType
|
||||||
AND d.parentId = (SELECT id FROM sys_dict WHERE `code` = 'organizationType' LIMIT 1)
|
AND d.parentId = (SELECT id FROM sys_dict WHERE `code` = 'organizationType' LIMIT 1)
|
||||||
$condition
|
$condition
|
||||||
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.createdAt DESC
|
ORDER BY $orderColumn $orderBy, CAST(l.lineCode AS UNSIGNED) ASC, m.`year` DESC, m.createdAt DESC
|
||||||
""");
|
""");
|
||||||
listSql.setCondition(cnd);
|
listSql.setCondition(cnd);
|
||||||
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
|
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
|
||||||
@@ -104,6 +107,109 @@ public class TourMatterController {
|
|||||||
return Result.success(pagination);
|
return Result.success(pagination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出疗休养事项列表,导出范围复用页面查询条件和事项数据权限。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@Ok("void")
|
||||||
|
@SaCheckPermission("tour.matter")
|
||||||
|
public void exportData(Integer year, String matterName, String unionId, String organizationType, String lineType,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
Cnd cnd = buildMatterPageCnd(year, matterName, unionId, organizationType, lineType);
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
m.`year`,
|
||||||
|
m.matterName,
|
||||||
|
l.lineName,
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(m.signupStartTime, '') <> '' AND IFNULL(m.signupEndTime, '') <> ''
|
||||||
|
THEN CONCAT(m.signupStartTime, ' 至 ', m.signupEndTime)
|
||||||
|
ELSE ''
|
||||||
|
END AS signupTime,
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||||
|
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||||
|
ELSE ''
|
||||||
|
END AS travelTime,
|
||||||
|
d.name AS organizationTypeName,
|
||||||
|
m.contactName,
|
||||||
|
m.contactPhone,
|
||||||
|
CASE WHEN m.enabled = 1 THEN '启用' ELSE '禁用' END AS enabledName
|
||||||
|
FROM tour_matter m
|
||||||
|
LEFT JOIN tour_line l ON l.id = m.lineId
|
||||||
|
LEFT JOIN sys_union u ON u.id = m.unionId
|
||||||
|
LEFT JOIN sys_dict d ON d.`code` = m.organizationType
|
||||||
|
AND d.parentId = (SELECT id FROM sys_dict WHERE `code` = 'organizationType' LIMIT 1)
|
||||||
|
$condition
|
||||||
|
ORDER BY CAST(l.lineCode AS UNSIGNED) ASC, m.`year` DESC, m.createdAt DESC
|
||||||
|
""");
|
||||||
|
sql.setCondition(cnd);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
tourMatterService.dao().execute(sql);
|
||||||
|
|
||||||
|
List<NutMap> rows = sql.getList(NutMap.class);
|
||||||
|
for (int i = 0; i < rows.size(); i++) {
|
||||||
|
rows.get(i).put("index", i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ExcelExportEntity> entities = new ArrayList<>();
|
||||||
|
entities.add(new ExcelExportEntity("序号", "index", 8));
|
||||||
|
entities.add(new ExcelExportEntity("年度", "year", 12));
|
||||||
|
entities.add(new ExcelExportEntity("事项名称", "matterName", 30));
|
||||||
|
entities.add(new ExcelExportEntity("线路名称", "lineName", 30));
|
||||||
|
entities.add(new ExcelExportEntity("报名时间", "signupTime", 34));
|
||||||
|
entities.add(new ExcelExportEntity("出行时间", "travelTime", 24));
|
||||||
|
entities.add(new ExcelExportEntity("组织形式", "organizationTypeName", 16));
|
||||||
|
entities.add(new ExcelExportEntity("联系人", "contactName", 16));
|
||||||
|
entities.add(new ExcelExportEntity("联系方式", "contactPhone", 18));
|
||||||
|
entities.add(new ExcelExportEntity("事项状态", "enabledName", 12));
|
||||||
|
|
||||||
|
ExportParams exportParams = new ExportParams();
|
||||||
|
exportParams.setType(ExcelType.XSSF);
|
||||||
|
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, rows);
|
||||||
|
CommonDownloadUtil.download("疗休养事项数据.xlsx", workbook, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按当前事项列表查询条件统一赋值报名时间,更新前端筛选范围内的全部事项。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
@SaCheckPermission("tour.matter")
|
||||||
|
@SLog(type = "tour", tag = "疗休养事项", msg = "统一赋值报名时间")
|
||||||
|
public Result assignSignupTime(Integer year, String matterName, String unionId, String organizationType, String lineType,
|
||||||
|
String signupStartTime, String signupEndTime) {
|
||||||
|
if (StrUtil.isBlank(signupStartTime) || StrUtil.isBlank(signupEndTime)) {
|
||||||
|
return Result.error("报名开始时间和报名结束时间不能为空");
|
||||||
|
}
|
||||||
|
if (signupStartTime.compareTo(signupEndTime) >= 0) {
|
||||||
|
return Result.error("报名开始时间必须小于报名结束时间");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量赋值必须严格复用事项列表查询条件,确保“查询哪些数据就赋值哪些事项”。
|
||||||
|
Cnd cnd = buildMatterPageCnd(year, matterName, unionId, organizationType, lineType);
|
||||||
|
Sql idSql = Sqls.create("""
|
||||||
|
SELECT m.id
|
||||||
|
FROM tour_matter m
|
||||||
|
LEFT JOIN tour_line l ON l.id = m.lineId
|
||||||
|
$condition
|
||||||
|
""");
|
||||||
|
idSql.setCondition(cnd);
|
||||||
|
idSql.setCallback(Sqls.callback.maps());
|
||||||
|
tourMatterService.dao().execute(idSql);
|
||||||
|
List<String> ids = idSql.getList(NutMap.class).stream()
|
||||||
|
.map(item -> item.getString("id"))
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (ids.isEmpty()) {
|
||||||
|
return Result.success("当前查询条件下暂无可赋值事项");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只更新报名时间字段,不影响事项的线路、联系人、成团人数等其他配置。
|
||||||
|
int updateCount = tourMatterService.assignSignupTime(ids, signupStartTime, signupEndTime);
|
||||||
|
return Result.success("赋值成功,共更新" + updateCount + "条事项");
|
||||||
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("tour.matter")
|
@SaCheckPermission("tour.matter")
|
||||||
public Result detail(String id) {
|
public Result detail(String id) {
|
||||||
@@ -142,17 +248,20 @@ public class TourMatterController {
|
|||||||
cnd.and("l.enabled", "=", true);
|
cnd.and("l.enabled", "=", true);
|
||||||
cnd.and("l.openFlag", "=", true);
|
cnd.and("l.openFlag", "=", true);
|
||||||
|
|
||||||
|
// 事项选择线路下拉需要展示线路类型和旅行社,因此这里补充旅行社名称返回给前端拼接显示。
|
||||||
Sql sql = Sqls.create("""
|
Sql sql = Sqls.create("""
|
||||||
SELECT
|
SELECT
|
||||||
l.*,
|
l.*,
|
||||||
|
a.agencyName AS travelAgencyName,
|
||||||
lot.lotName AS lotName,
|
lot.lotName AS lotName,
|
||||||
lot.lotValue AS lotValue,
|
lot.lotValue AS lotValue,
|
||||||
lot.lotValue AS lotDays,
|
lot.lotValue AS lotDays,
|
||||||
lot.activityCost AS activityCost
|
lot.activityCost AS activityCost
|
||||||
FROM tour_line l
|
FROM tour_line l
|
||||||
|
LEFT JOIN tour_travel_agency a ON a.id = l.travelAgencyId
|
||||||
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
|
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
|
||||||
$condition
|
$condition
|
||||||
ORDER BY l.lineCode ASC, l.lineName ASC
|
ORDER BY CAST(l.lineCode AS UNSIGNED) ASC, l.lineName ASC
|
||||||
""");
|
""");
|
||||||
sql.setCondition(cnd);
|
sql.setCondition(cnd);
|
||||||
sql.setCallback(Sqls.callback.maps());
|
sql.setCallback(Sqls.callback.maps());
|
||||||
@@ -199,6 +308,17 @@ public class TourMatterController {
|
|||||||
return Result.success(list.stream().filter(item -> !item.isDisabled()).collect(Collectors.toList()));
|
return Result.success(list.stream().filter(item -> !item.isDisabled()).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("tour.matter")
|
||||||
|
public Result lineTypeOptions() {
|
||||||
|
// 事项列表线路类型查询复用线路管理的 lineType 字典,保证筛选值与线路保存值一致。
|
||||||
|
List<Sys_dict> list = sysDictService.getSubListByCode("lineType");
|
||||||
|
if (list == null) {
|
||||||
|
return Result.success(Collections.emptyList());
|
||||||
|
}
|
||||||
|
return Result.success(list.stream().filter(item -> !item.isDisabled()).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@Aop(TransAop.READ_COMMITTED)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
@SaCheckPermission("tour.matter")
|
@SaCheckPermission("tour.matter")
|
||||||
@@ -328,6 +448,7 @@ public class TourMatterController {
|
|||||||
oldMatter.setContactPhone(matter.getContactPhone());
|
oldMatter.setContactPhone(matter.getContactPhone());
|
||||||
oldMatter.setMinGroupPeople(matter.getMinGroupPeople());
|
oldMatter.setMinGroupPeople(matter.getMinGroupPeople());
|
||||||
oldMatter.setMaxGroupPeople(matter.getMaxGroupPeople());
|
oldMatter.setMaxGroupPeople(matter.getMaxGroupPeople());
|
||||||
|
oldMatter.setMaxSignupPeople(matter.getMaxSignupPeople());
|
||||||
oldMatter.setEstimatedCost(matter.getEstimatedCost());
|
oldMatter.setEstimatedCost(matter.getEstimatedCost());
|
||||||
tourMatterService.update(oldMatter);
|
tourMatterService.update(oldMatter);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
@@ -348,16 +469,14 @@ public class TourMatterController {
|
|||||||
if (setting == null) {
|
if (setting == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (setting.getMinGroupPeople() != null) {
|
// 配置只作为弹窗缺省值,不能覆盖用户在选择线路页面已经提交的成团人数和报名时间。
|
||||||
|
if (matter.getMinGroupPeople() == null && setting.getMinGroupPeople() != null) {
|
||||||
matter.setMinGroupPeople(setting.getMinGroupPeople());
|
matter.setMinGroupPeople(setting.getMinGroupPeople());
|
||||||
}
|
}
|
||||||
if (setting.getMaxGroupPeople() != null) {
|
if (StrUtil.isBlank(matter.getSignupStartTime()) && StrUtil.isNotBlank(setting.getSignupStartTime())) {
|
||||||
matter.setMaxGroupPeople(setting.getMaxGroupPeople());
|
|
||||||
}
|
|
||||||
if (StrUtil.isNotBlank(setting.getSignupStartTime())) {
|
|
||||||
matter.setSignupStartTime(setting.getSignupStartTime());
|
matter.setSignupStartTime(setting.getSignupStartTime());
|
||||||
}
|
}
|
||||||
if (StrUtil.isNotBlank(setting.getSignupEndTime())) {
|
if (StrUtil.isBlank(matter.getSignupEndTime()) && StrUtil.isNotBlank(setting.getSignupEndTime())) {
|
||||||
matter.setSignupEndTime(setting.getSignupEndTime());
|
matter.setSignupEndTime(setting.getSignupEndTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,11 +548,9 @@ public class TourMatterController {
|
|||||||
if (matter.getMinGroupPeople() == null || matter.getMinGroupPeople() <= 0) {
|
if (matter.getMinGroupPeople() == null || matter.getMinGroupPeople() <= 0) {
|
||||||
return Result.error("最少成团人数必须大于0");
|
return Result.error("最少成团人数必须大于0");
|
||||||
}
|
}
|
||||||
if (matter.getMaxGroupPeople() == null || matter.getMaxGroupPeople() <= 0) {
|
// 最多报名人数只控制报名提交容量;为空或0表示不限制,不能影响成团分配人数。
|
||||||
return Result.error("最多成团人数必须大于0");
|
if (matter.getMaxSignupPeople() != null && matter.getMaxSignupPeople() < 0) {
|
||||||
}
|
return Result.error("最多报名人数不能小于0");
|
||||||
if (matter.getMinGroupPeople() > matter.getMaxGroupPeople()) {
|
|
||||||
return Result.error("最少成团人数不能大于最多成团人数");
|
|
||||||
}
|
}
|
||||||
if (matter.getEstimatedCost() == null || matter.getEstimatedCost().signum() < 0) {
|
if (matter.getEstimatedCost() == null || matter.getEstimatedCost().signum() < 0) {
|
||||||
return Result.error("预计费用不能小于0");
|
return Result.error("预计费用不能小于0");
|
||||||
@@ -462,6 +579,22 @@ public class TourMatterController {
|
|||||||
.and(TourLedger::getMatterId, "=", matterId));
|
.and(TourLedger::getMatterId, "=", matterId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建事项列表查询条件,批量赋值报名时间时复用该条件保证更新范围与列表查询一致。
|
||||||
|
*/
|
||||||
|
private Cnd buildMatterPageCnd(Integer year, String matterName, String unionId, String organizationType, String lineType) {
|
||||||
|
Cnd cnd = Cnd.NEW();
|
||||||
|
cnd.and("m.delFlag", "=", false);
|
||||||
|
cnd.andEX("m.`year`", "=", year);
|
||||||
|
cnd.and(Cnd.likeEX("m.matterName", matterName));
|
||||||
|
cnd.andEX("m.unionId", "=", unionId);
|
||||||
|
cnd.andEX("m.organizationType", "=", organizationType);
|
||||||
|
// 事项列表和批量赋值报名时间共用该条件,按线路类型筛选时必须关联线路表字段。
|
||||||
|
cnd.andEX("l.lineType", "=", lineType);
|
||||||
|
applyMatterDataScope(cnd);
|
||||||
|
return cnd;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 事项数据范围:SYSADMIN 查看全部;分工会主席查看本工会事项;其它角色仅查看自己创建的事项。
|
* 事项数据范围:SYSADMIN 查看全部;分工会主席查看本工会事项;其它角色仅查看自己创建的事项。
|
||||||
*/
|
*/
|
||||||
@@ -512,6 +645,10 @@ public class TourMatterController {
|
|||||||
if ("lineName".equals(orderName)) {
|
if ("lineName".equals(orderName)) {
|
||||||
return "l.lineName";
|
return "l.lineName";
|
||||||
}
|
}
|
||||||
|
if ("lineCode".equals(orderName)) {
|
||||||
|
// 线路序号按数字排序,避免 10 排在 2 前面。
|
||||||
|
return "CAST(l.lineCode AS UNSIGNED)";
|
||||||
|
}
|
||||||
if ("settingName".equals(orderName)) {
|
if ("settingName".equals(orderName)) {
|
||||||
return "s.configName";
|
return "s.configName";
|
||||||
}
|
}
|
||||||
@@ -521,6 +658,6 @@ public class TourMatterController {
|
|||||||
if ("enabled".equals(orderName)) {
|
if ("enabled".equals(orderName)) {
|
||||||
return "m.enabled";
|
return "m.enabled";
|
||||||
}
|
}
|
||||||
return "m.`year`";
|
return "CAST(l.lineCode AS UNSIGNED)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+117
-48
@@ -31,6 +31,7 @@ import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeSer
|
|||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourParticipantRosterService;
|
||||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Sqls;
|
import org.nutz.dao.Sqls;
|
||||||
@@ -83,6 +84,9 @@ public class TourMySignupController {
|
|||||||
@Inject
|
@Inject
|
||||||
private TourMatterService tourMatterService;
|
private TourMatterService tourMatterService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private TourParticipantRosterService tourParticipantRosterService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private SysDictService sysDictService;
|
private SysDictService sysDictService;
|
||||||
|
|
||||||
@@ -145,6 +149,8 @@ public class TourMySignupController {
|
|||||||
IFNULL(GROUP_CONCAT(DISTINCT task.displayName), IF(ins.id IS NULL, '', '结束')) AS curTaskName,
|
IFNULL(GROUP_CONCAT(DISTINCT task.displayName), IF(ins.id IS NULL, '', '结束')) AS curTaskName,
|
||||||
IFNULL(f.familyCount, 0) AS familyCount,
|
IFNULL(f.familyCount, 0) AS familyCount,
|
||||||
COALESCE(m.travelEndTime, tp.travelEndTime) AS travelEndTime,
|
COALESCE(m.travelEndTime, tp.travelEndTime) AS travelEndTime,
|
||||||
|
COALESCE(NULLIF(s.signupStartTime, ''), m.signupStartTime) AS signupStartTime,
|
||||||
|
COALESCE(NULLIF(s.signupEndTime, ''), m.signupEndTime) AS signupEndTime,
|
||||||
CASE
|
CASE
|
||||||
WHEN IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') = 'claimed' THEN COALESCE(u.name, '校工会')
|
WHEN IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') = 'claimed' THEN COALESCE(u.name, '校工会')
|
||||||
ELSE ''
|
ELSE ''
|
||||||
@@ -166,6 +172,7 @@ public class TourMySignupController {
|
|||||||
GROUP BY ledgerId
|
GROUP BY ledgerId
|
||||||
) f ON f.ledgerId = t.id
|
) f ON f.ledgerId = t.id
|
||||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||||
|
LEFT JOIN tour_setting s ON s.id = m.settingId AND s.delFlag = 0
|
||||||
LEFT JOIN sys_union u ON u.id = m.unionId
|
LEFT JOIN sys_union u ON u.id = m.unionId
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -290,6 +297,9 @@ public class TourMySignupController {
|
|||||||
m.unionId AS matterUnionId,
|
m.unionId AS matterUnionId,
|
||||||
m.travelStartTime,
|
m.travelStartTime,
|
||||||
m.travelEndTime,
|
m.travelEndTime,
|
||||||
|
COALESCE(NULLIF(s.signupStartTime, ''), m.signupStartTime) AS signupStartTime,
|
||||||
|
COALESCE(NULLIF(s.signupEndTime, ''), m.signupEndTime) AS signupEndTime,
|
||||||
|
m.maxSignupPeople,
|
||||||
CASE
|
CASE
|
||||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||||
@@ -323,6 +333,10 @@ public class TourMySignupController {
|
|||||||
if (isTravelEnded(matter.getString("travelEndTime"))) {
|
if (isTravelEnded(matter.getString("travelEndTime"))) {
|
||||||
return Result.error("线路出行已结束,不能修改");
|
return Result.error("线路出行已结束,不能修改");
|
||||||
}
|
}
|
||||||
|
Result signupEndResult = checkSignupEndTime(ledger);
|
||||||
|
if (signupEndResult != null) {
|
||||||
|
return signupEndResult;
|
||||||
|
}
|
||||||
Cnd familyCnd = Cnd.where(TourLedgerFamily::getDelFlag, "=", false)
|
Cnd familyCnd = Cnd.where(TourLedgerFamily::getDelFlag, "=", false)
|
||||||
.and(TourLedgerFamily::getLedgerId, "=", ledger.getId());
|
.and(TourLedgerFamily::getLedgerId, "=", ledger.getId());
|
||||||
familyCnd.asc(TourLedgerFamily::getCreatedAt);
|
familyCnd.asc(TourLedgerFamily::getCreatedAt);
|
||||||
@@ -440,14 +454,10 @@ public class TourMySignupController {
|
|||||||
return familyResult;
|
return familyResult;
|
||||||
}
|
}
|
||||||
TourMatter matter = tourMatterService.fetch(ledger.getMatterId());
|
TourMatter matter = tourMatterService.fetch(ledger.getMatterId());
|
||||||
Result ruleResult = checkSignupRule(ledger, matter, oldLedger);
|
Result ruleResult = checkSignupRule(ledger, matter, oldLedger, calculateCurrentSignupPeople(familyList));
|
||||||
if (ruleResult != null) {
|
if (ruleResult != null) {
|
||||||
return ruleResult;
|
return ruleResult;
|
||||||
}
|
}
|
||||||
Result maxPeopleResult = checkMaxGroupPeople(matter, oldLedger, familyList);
|
|
||||||
if (maxPeopleResult != null) {
|
|
||||||
return maxPeopleResult;
|
|
||||||
}
|
|
||||||
TourLine line = tourLedgerService.dao().fetch(TourLine.class, matter.getLineId());
|
TourLine line = tourLedgerService.dao().fetch(TourLine.class, matter.getLineId());
|
||||||
boolean directFamilyLine = line != null && Boolean.TRUE.equals(line.getDirectFamilyUnitLine());
|
boolean directFamilyLine = line != null && Boolean.TRUE.equals(line.getDirectFamilyUnitLine());
|
||||||
TourSetting setting = tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId());
|
TourSetting setting = tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId());
|
||||||
@@ -501,6 +511,8 @@ public class TourMySignupController {
|
|||||||
return workflowResult;
|
return workflowResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Signup modification keeps the staff member joined and refreshes the linked ledger id.
|
||||||
|
tourParticipantRosterService.markJoinedByLedger(ledger);
|
||||||
return Result.success().addMsg(approvalRequired ? "修改已提交,等待审核" : "修改成功");
|
return Result.success().addMsg(approvalRequired ? "修改已提交,等待审核" : "修改成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,6 +531,12 @@ public class TourMySignupController {
|
|||||||
if (ledger == null) {
|
if (ledger == null) {
|
||||||
return Result.error("报名记录不存在");
|
return Result.error("报名记录不存在");
|
||||||
}
|
}
|
||||||
|
Result signupEndResult = checkSignupEndTime(ledger);
|
||||||
|
if (signupEndResult != null) {
|
||||||
|
return signupEndResult;
|
||||||
|
}
|
||||||
|
// Reset the roster before deleting the ledger record, because the ledger still carries setting linkage here.
|
||||||
|
tourParticipantRosterService.markNotJoinedByLedger(ledger);
|
||||||
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
|
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
|
||||||
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
||||||
tourLedgerService.clear(Cnd.where(TourLedger::getId, "=", ledger.getId()));
|
tourLedgerService.clear(Cnd.where(TourLedger::getId, "=", ledger.getId()));
|
||||||
@@ -534,6 +552,12 @@ public class TourMySignupController {
|
|||||||
if (ledger == null) {
|
if (ledger == null) {
|
||||||
return Result.error("报名记录不存在");
|
return Result.error("报名记录不存在");
|
||||||
}
|
}
|
||||||
|
Result signupEndResult = checkSignupEndTime(ledger);
|
||||||
|
if (signupEndResult != null) {
|
||||||
|
return signupEndResult;
|
||||||
|
}
|
||||||
|
// Reset the roster before deleting the ledger record, because the ledger still carries setting linkage here.
|
||||||
|
tourParticipantRosterService.markNotJoinedByLedger(ledger);
|
||||||
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
|
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
|
||||||
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
||||||
tourLedgerService.clear(Cnd.where(TourLedger::getId, "=", ledger.getId()));
|
tourLedgerService.clear(Cnd.where(TourLedger::getId, "=", ledger.getId()));
|
||||||
@@ -604,7 +628,30 @@ public class TourMySignupController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result checkSignupRule(TourLedger ledger, TourMatter matter, TourLedger oldLedger) {
|
/**
|
||||||
|
* 修改和取消报名都必须受有效报名结束时间控制,避免前端隐藏按钮后仍可直接调用接口。
|
||||||
|
*/
|
||||||
|
private Result checkSignupEndTime(TourLedger ledger) {
|
||||||
|
if (ledger == null || StrUtil.isBlank(ledger.getMatterId())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TourMatter matter = tourMatterService.fetch(ledger.getMatterId());
|
||||||
|
if (matter == null || Boolean.TRUE.equals(matter.getDelFlag()) || !Boolean.TRUE.equals(matter.getEnabled())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TourSetting setting = tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId());
|
||||||
|
String signupEndTime = defaultIfBlank(setting == null ? "" : setting.getSignupEndTime(), matter.getSignupEndTime());
|
||||||
|
if (StrUtil.isBlank(signupEndTime)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
if (now.compareTo(normalizeDateTime(signupEndTime, true)) > 0) {
|
||||||
|
return Result.error("报名已结束,不能修改或取消报名");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result checkSignupRule(TourLedger ledger, TourMatter matter, TourLedger oldLedger, int currentPeopleCount) {
|
||||||
if (matter == null || Boolean.TRUE.equals(matter.getDelFlag()) || !Boolean.TRUE.equals(matter.getEnabled())) {
|
if (matter == null || Boolean.TRUE.equals(matter.getDelFlag()) || !Boolean.TRUE.equals(matter.getEnabled())) {
|
||||||
return Result.error("报名出行时段不存在或已停用");
|
return Result.error("报名出行时段不存在或已停用");
|
||||||
}
|
}
|
||||||
@@ -622,6 +669,10 @@ public class TourMySignupController {
|
|||||||
if (setting == null || Boolean.TRUE.equals(setting.getDelFlag()) || !Boolean.TRUE.equals(setting.getEnabled())) {
|
if (setting == null || Boolean.TRUE.equals(setting.getDelFlag()) || !Boolean.TRUE.equals(setting.getEnabled())) {
|
||||||
return Result.error("报名事项配置不存在或已停用");
|
return Result.error("报名事项配置不存在或已停用");
|
||||||
}
|
}
|
||||||
|
Result maxSignupResult = checkMaxSignupPeople(matter, oldLedger, currentPeopleCount);
|
||||||
|
if (maxSignupResult != null) {
|
||||||
|
return maxSignupResult;
|
||||||
|
}
|
||||||
Integer activityGroupId = parseInteger(setting.getActivityGroupId());
|
Integer activityGroupId = parseInteger(setting.getActivityGroupId());
|
||||||
if (activityGroupId == null || !activityBasicScopeService.isUserInGroup(activityGroupId, SecurityUtil.getUserId())) {
|
if (activityGroupId == null || !activityBasicScopeService.isUserInGroup(activityGroupId, SecurityUtil.getUserId())) {
|
||||||
return Result.error("您不在本次疗休养报名范围内");
|
return Result.error("您不在本次疗休养报名范围内");
|
||||||
@@ -661,6 +712,8 @@ public class TourMySignupController {
|
|||||||
if (tourLedgerService.count(outProvinceCnd) > 0) {
|
if (tourLedgerService.count(outProvinceCnd) > 0) {
|
||||||
return Result.error("您已参加过省外线路,不能报名省外线路,请选择省内线路");
|
return Result.error("您已参加过省外线路,不能报名省外线路,请选择省内线路");
|
||||||
}
|
}
|
||||||
|
// Out-province quota is shared by year; lock only before the quota count calculation.
|
||||||
|
tourLedgerService.lockTourSignupQuotaYear(matter.getYear());
|
||||||
OutProvinceQuota quota = calculateOutProvinceQuota(matter, setting, oldLedger);
|
OutProvinceQuota quota = calculateOutProvinceQuota(matter, setting, oldLedger);
|
||||||
if (!quota.canApply()) {
|
if (!quota.canApply()) {
|
||||||
return Result.error(buildOutProvinceQuotaNotice(quota));
|
return Result.error(buildOutProvinceQuotaNotice(quota));
|
||||||
@@ -692,6 +745,61 @@ public class TourMySignupController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the matter maximum signup capacity at final submit time.
|
||||||
|
*/
|
||||||
|
private Result checkMaxSignupPeople(TourMatter matter, TourLedger oldLedger, int currentPeopleCount) {
|
||||||
|
int maxSignupPeople = matter == null || matter.getMaxSignupPeople() == null ? 0 : matter.getMaxSignupPeople();
|
||||||
|
if (maxSignupPeople <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Maximum signup capacity is owned by the current matter, so only the same matter is serialized.
|
||||||
|
tourLedgerService.lockTourSignupMatter(matter.getId());
|
||||||
|
String excludeLedgerId = oldLedger == null ? null : oldLedger.getId();
|
||||||
|
int signedPeople = countMatterSignupPeople(matter.getId(), excludeLedgerId);
|
||||||
|
if (signedPeople + currentPeopleCount > maxSignupPeople) {
|
||||||
|
return Result.error("当前线路最多报名人数为" + maxSignupPeople + "人,当前已报名" + signedPeople
|
||||||
|
+ "人,本次报名" + currentPeopleCount + "人,不能超过最多报名人数");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts current submit people as one staff member plus all submitted family members.
|
||||||
|
*/
|
||||||
|
private int calculateCurrentSignupPeople(List<TourLedgerFamily> familyList) {
|
||||||
|
return 1 + (familyList == null ? 0 : familyList.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts valid signed people for a matter, including staff and their family members.
|
||||||
|
*/
|
||||||
|
private int countMatterSignupPeople(String matterId, String excludeLedgerId) {
|
||||||
|
if (StrUtil.isBlank(matterId)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
String excludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND t.id <> @excludeLedgerId";
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT IFNULL(SUM(1 + IFNULL(f.familyCount, 0)), 0) AS peopleCount
|
||||||
|
FROM tour_ledger t
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
|
FROM tour_ledger_family
|
||||||
|
WHERE delFlag = 0
|
||||||
|
GROUP BY ledgerId
|
||||||
|
) f ON f.ledgerId = t.id
|
||||||
|
WHERE t.delFlag = 0
|
||||||
|
AND t.matterId = @matterId
|
||||||
|
""" + excludeSql);
|
||||||
|
sql.setParam("matterId", matterId);
|
||||||
|
if (StrUtil.isNotBlank(excludeLedgerId)) {
|
||||||
|
sql.setParam("excludeLedgerId", excludeLedgerId);
|
||||||
|
}
|
||||||
|
sql.setCallback(Sqls.callback.integer());
|
||||||
|
tourLedgerService.dao().execute(sql);
|
||||||
|
return sql.getInt();
|
||||||
|
}
|
||||||
|
|
||||||
private TourLedger fetchLatestLedger(Cnd cnd) {
|
private TourLedger fetchLatestLedger(Cnd cnd) {
|
||||||
cnd.desc(TourLedger::getSignupTime);
|
cnd.desc(TourLedger::getSignupTime);
|
||||||
cnd.desc(TourLedger::getCreatedAt);
|
cnd.desc(TourLedger::getCreatedAt);
|
||||||
@@ -985,46 +1093,6 @@ public class TourMySignupController {
|
|||||||
&& idCard.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dX]$");
|
&& idCard.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dX]$");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result checkMaxGroupPeople(TourMatter matter, TourLedger oldLedger, List<TourLedgerFamily> familyList) {
|
|
||||||
if (matter == null || matter.getMaxGroupPeople() == null || matter.getMaxGroupPeople() <= 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int currentPeople = 1 + (Lang.isEmpty(familyList) ? 0 : familyList.size());
|
|
||||||
int signedPeople = countSignupPeople(matter.getId(), oldLedger == null ? null : oldLedger.getId());
|
|
||||||
int totalPeople = signedPeople + currentPeople;
|
|
||||||
if (totalPeople > matter.getMaxGroupPeople()) {
|
|
||||||
return Result.error("当前报名人数 " + signedPeople + " 人,本次报名 " + currentPeople
|
|
||||||
+ " 人,最多成团人数 " + matter.getMaxGroupPeople() + " 人,报名后将超过人数上限,不能提交");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int countSignupPeople(String matterId, String excludeLedgerId) {
|
|
||||||
if (StrUtil.isBlank(matterId)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
String excludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND t.id <> @excludeLedgerId";
|
|
||||||
Sql sql = Sqls.create("""
|
|
||||||
SELECT IFNULL(SUM(1 + IFNULL(f.familyCount, 0)), 0) AS signupPeople
|
|
||||||
FROM tour_ledger t
|
|
||||||
LEFT JOIN (
|
|
||||||
SELECT ledgerId, COUNT(1) AS familyCount
|
|
||||||
FROM tour_ledger_family
|
|
||||||
WHERE delFlag = 0
|
|
||||||
GROUP BY ledgerId
|
|
||||||
) f ON f.ledgerId = t.id
|
|
||||||
WHERE t.delFlag = 0
|
|
||||||
AND t.matterId = @matterId
|
|
||||||
""" + excludeSql);
|
|
||||||
sql.setParam("matterId", matterId);
|
|
||||||
if (StrUtil.isNotBlank(excludeLedgerId)) {
|
|
||||||
sql.setParam("excludeLedgerId", excludeLedgerId);
|
|
||||||
}
|
|
||||||
sql.setCallback(Sqls.callback.integer());
|
|
||||||
tourLedgerService.dao().execute(sql);
|
|
||||||
return sql.getInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildSelectedLineMessage(TourLedger ledger) {
|
private String buildSelectedLineMessage(TourLedger ledger) {
|
||||||
String selectedLineName = getLedgerLineName(ledger);
|
String selectedLineName = getLedgerLineName(ledger);
|
||||||
return "您已经选择【" + selectedLineName + "】,取消后再重新选择。";
|
return "您已经选择【" + selectedLineName + "】,取消后再重新选择。";
|
||||||
@@ -1650,14 +1718,15 @@ public class TourMySignupController {
|
|||||||
|
|
||||||
private PDType0Font loadChineseFont(PDDocument document, boolean bold) throws IOException {
|
private PDType0Font loadChineseFont(PDDocument document, boolean bold) throws IOException {
|
||||||
String[] paths = bold
|
String[] paths = bold
|
||||||
? new String[]{"C:/Windows/Fonts/simhei.ttf", "C:/Windows/Fonts/msyhbd.ttf", "C:/Windows/Fonts/simsun.ttf"}
|
? new String[]{"/usr/share/fonts/chinese/simhei.ttf", "/usr/share/fonts/chinese/msyhbd.ttc", "/usr/share/fonts/chinese/simsun.ttc"}
|
||||||
: new String[]{"C:/Windows/Fonts/simfang.ttf", "C:/Windows/Fonts/simsun.ttf", "C:/Windows/Fonts/msyh.ttf"};
|
: new String[]{"/usr/share/fonts/chinese/simfang.ttf", "/usr/share/fonts/chinese/simsun.ttc", "/usr/share/fonts/chinese/msyh.ttc"};
|
||||||
for (String path : paths) {
|
for (String path : paths) {
|
||||||
File file = new File(path);
|
File file = new File(path);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
return PDType0Font.load(document, file);
|
return PDType0Font.load(document, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IOException("未找到中文字体文件");
|
throw new IOException("未找到中文字体文件");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+367
@@ -0,0 +1,367 @@
|
|||||||
|
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
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.zhgh.dayofficework.tour.service.TourParticipantRosterService;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
|
import org.nutz.dao.Sqls;
|
||||||
|
import org.nutz.dao.sql.Sql;
|
||||||
|
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||||
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.json.Json;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
|
import org.nutz.mvc.annotation.At;
|
||||||
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
import org.nutz.mvc.annotation.Param;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 疗休养人员名单统计查询控制器。
|
||||||
|
* 名单表保存可参与人员基础信息,报名线路、成团等信息通过台账补充读取。
|
||||||
|
*/
|
||||||
|
@IocBean
|
||||||
|
@Ok("json:full")
|
||||||
|
@At("/platform/tour/participantRoster")
|
||||||
|
public class TourParticipantRosterController {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private TourParticipantRosterService tourParticipantRosterService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开 PC 端疗休养人员名单查询页面。
|
||||||
|
*/
|
||||||
|
@At("")
|
||||||
|
@Ok("beetl:/platform/zhgh/dayofficework/Tour/participantRoster/index.html")
|
||||||
|
@SaCheckPermission("tour.participantRoster")
|
||||||
|
public void index() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按年度范围和可选报名维度查询人员名单。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("tour.participantRoster")
|
||||||
|
public Result pageData(PageForm pageForm, Integer startYear, Integer endYear, String settingId, String matterId,
|
||||||
|
String keyword, String unitName, String unionId, Boolean joined, String lineType,
|
||||||
|
String groupCode, String undertakeUnionId) {
|
||||||
|
Result yearResult = checkYearRange(startYear, endYear);
|
||||||
|
if (yearResult != null) {
|
||||||
|
return yearResult;
|
||||||
|
}
|
||||||
|
Cnd cnd = buildQueryCnd(startYear, endYear, settingId, matterId, keyword, unitName, unionId, joined,
|
||||||
|
lineType, groupCode, undertakeUnionId);
|
||||||
|
|
||||||
|
Sql countSql = Sqls.create("""
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM tour_participant_roster r
|
||||||
|
INNER JOIN tour_setting s ON s.id = r.settingId AND s.delFlag = 0
|
||||||
|
LEFT JOIN tour_ledger t ON t.id = r.ledgerId AND t.delFlag = 0
|
||||||
|
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||||
|
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||||
|
$condition
|
||||||
|
""");
|
||||||
|
countSql.setCondition(cnd);
|
||||||
|
countSql.setCallback(Sqls.callback.integer());
|
||||||
|
tourParticipantRosterService.dao().execute(countSql);
|
||||||
|
|
||||||
|
Sql listSql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
s.`year`,
|
||||||
|
s.configName,
|
||||||
|
r.jobNo,
|
||||||
|
r.userName,
|
||||||
|
r.unitId,
|
||||||
|
r.unitName,
|
||||||
|
r.unionId,
|
||||||
|
r.unionName,
|
||||||
|
r.joined,
|
||||||
|
r.ledgerId,
|
||||||
|
t.lineType,
|
||||||
|
COALESCE(NULLIF(l.lineName, ''), t.lineName) AS lineName,
|
||||||
|
m.matterName,
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||||
|
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||||
|
ELSE ''
|
||||||
|
END AS travelPeriod,
|
||||||
|
t.groupCode,
|
||||||
|
t.groupName,
|
||||||
|
t.undertakeUnionId,
|
||||||
|
t.undertakeUnionName,
|
||||||
|
t.signupTime
|
||||||
|
FROM tour_participant_roster r
|
||||||
|
INNER JOIN tour_setting s ON s.id = r.settingId AND s.delFlag = 0
|
||||||
|
LEFT JOIN tour_ledger t ON t.id = r.ledgerId AND t.delFlag = 0
|
||||||
|
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||||
|
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||||
|
$condition
|
||||||
|
ORDER BY $orderColumn $orderBy, s.`year` DESC, r.unionName ASC, r.unitName ASC, r.jobNo ASC
|
||||||
|
""");
|
||||||
|
listSql.setCondition(cnd);
|
||||||
|
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
|
||||||
|
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
|
||||||
|
listSql.setPager(tourParticipantRosterService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||||
|
listSql.setCallback(Sqls.callback.maps());
|
||||||
|
tourParticipantRosterService.dao().execute(listSql);
|
||||||
|
|
||||||
|
Pagination<NutMap> pagination = new Pagination<>(
|
||||||
|
pageForm.getPageNumber(),
|
||||||
|
pageForm.getPageSize(),
|
||||||
|
countSql.getInt(),
|
||||||
|
listSql.getList(NutMap.class)
|
||||||
|
);
|
||||||
|
return Result.success(pagination);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据年度范围加载疗休养配置下拉选项。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("tour.participantRoster")
|
||||||
|
public Result settingOptions(Integer startYear, Integer endYear) {
|
||||||
|
Result yearResult = checkYearRange(startYear, endYear);
|
||||||
|
if (yearResult != null) {
|
||||||
|
return yearResult;
|
||||||
|
}
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT id, configName, `year`
|
||||||
|
FROM tour_setting
|
||||||
|
WHERE delFlag = 0
|
||||||
|
AND `year` >= @startYear
|
||||||
|
AND `year` <= @endYear
|
||||||
|
ORDER BY `year` DESC, sortNo ASC, createdAt DESC
|
||||||
|
""");
|
||||||
|
sql.setParam("startYear", startYear);
|
||||||
|
sql.setParam("endYear", endYear);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
tourParticipantRosterService.dao().execute(sql);
|
||||||
|
return Result.success(sql.getList(NutMap.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载线路选择器使用的疗休养事项选项。
|
||||||
|
* 未选择配置时加载年度范围内全部事项,选择配置后只加载当前配置下事项。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("tour.participantRoster")
|
||||||
|
public Result lineOptions(Integer startYear, Integer endYear, String settingId) {
|
||||||
|
Result yearResult = checkYearRange(startYear, endYear);
|
||||||
|
if (yearResult != null) {
|
||||||
|
return yearResult;
|
||||||
|
}
|
||||||
|
Cnd cnd = Cnd.NEW();
|
||||||
|
cnd.and("s.delFlag", "=", false);
|
||||||
|
cnd.and("m.delFlag", "=", false);
|
||||||
|
cnd.and("m.lineId", "IS NOT", null);
|
||||||
|
cnd.and("m.lineId", "<>", "");
|
||||||
|
cnd.and("s.`year`", ">=", startYear);
|
||||||
|
cnd.and("s.`year`", "<=", endYear);
|
||||||
|
cnd.andEX("s.id", "=", settingId);
|
||||||
|
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
m.id AS matterId,
|
||||||
|
m.matterName,
|
||||||
|
s.id AS settingId,
|
||||||
|
s.configName,
|
||||||
|
s.`year`,
|
||||||
|
l.id AS lineId,
|
||||||
|
l.lineName,
|
||||||
|
l.lineType,
|
||||||
|
CONCAT(s.configName, '【', s.`year`, '】 / ', m.matterName) AS label
|
||||||
|
FROM tour_matter m
|
||||||
|
INNER JOIN tour_setting s ON s.id = m.settingId
|
||||||
|
LEFT JOIN tour_line l ON l.id = m.lineId AND l.delFlag = 0
|
||||||
|
$condition
|
||||||
|
ORDER BY s.`year` DESC, s.sortNo ASC, m.travelStartTime ASC, m.createdAt DESC
|
||||||
|
""");
|
||||||
|
sql.setCondition(cnd);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
tourParticipantRosterService.dao().execute(sql);
|
||||||
|
return Result.success(sql.getList(NutMap.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从年度范围内的人员名单加载工会下拉选项。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("tour.participantRoster")
|
||||||
|
public Result unionOptions(Integer startYear, Integer endYear) {
|
||||||
|
Result yearResult = checkYearRange(startYear, endYear);
|
||||||
|
if (yearResult != null) {
|
||||||
|
return yearResult;
|
||||||
|
}
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT DISTINCT r.unionId AS id, r.unionName AS name
|
||||||
|
FROM tour_participant_roster r
|
||||||
|
INNER JOIN tour_setting s ON s.id = r.settingId AND s.delFlag = 0
|
||||||
|
WHERE r.delFlag = 0
|
||||||
|
AND IFNULL(r.unionId, '') <> ''
|
||||||
|
AND s.`year` >= @startYear
|
||||||
|
AND s.`year` <= @endYear
|
||||||
|
ORDER BY r.unionName ASC
|
||||||
|
""");
|
||||||
|
sql.setParam("startYear", startYear);
|
||||||
|
sql.setParam("endYear", endYear);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
tourParticipantRosterService.dao().execute(sql);
|
||||||
|
return Result.success(sql.getList(NutMap.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据参与人员范围和现有报名台账重新生成所选配置的人员名单。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("tour.participantRoster")
|
||||||
|
public Result rebuildRoster(Integer startYear, Integer endYear, @Param("settingIds") String settingIds) {
|
||||||
|
Result yearResult = checkYearRange(startYear, endYear);
|
||||||
|
if (yearResult != null) {
|
||||||
|
return yearResult;
|
||||||
|
}
|
||||||
|
List<String> settingIdList = parseSettingIds(settingIds);
|
||||||
|
if (settingIdList.isEmpty()) {
|
||||||
|
return Result.error("请选择需要重新生成的疗休养配置");
|
||||||
|
}
|
||||||
|
Result settingResult = checkSettingsInYearRange(startYear, endYear, settingIdList);
|
||||||
|
if (settingResult != null) {
|
||||||
|
return settingResult;
|
||||||
|
}
|
||||||
|
List<String> warningMessages = tourParticipantRosterService.rebuildSettingRosters(settingIdList);
|
||||||
|
NutMap data = NutMap.NEW().addv("warnings", warningMessages);
|
||||||
|
return Result.success(data).addMsg("人员名单重新生成成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验年度范围是否完整且顺序合法。
|
||||||
|
*/
|
||||||
|
private Result checkYearRange(Integer startYear, Integer endYear) {
|
||||||
|
if (startYear == null || endYear == null) {
|
||||||
|
return Result.error("年度范围不能为空");
|
||||||
|
}
|
||||||
|
if (startYear > endYear) {
|
||||||
|
return Result.error("开始年度不能大于结束年度");
|
||||||
|
}
|
||||||
|
int nextYear = LocalDate.now().getYear() + 1;
|
||||||
|
if (startYear < 1900 || endYear > nextYear) {
|
||||||
|
return Result.error("年度范围不合法");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析前端多选配置 ID,并过滤空值和重复值。
|
||||||
|
*/
|
||||||
|
private List<String> parseSettingIds(String settingIds) {
|
||||||
|
if (StrUtil.isBlank(settingIds)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<String> rawList = Json.fromJsonAsList(String.class, settingIds);
|
||||||
|
Set<String> idSet = new LinkedHashSet<>();
|
||||||
|
for (String id : rawList) {
|
||||||
|
if (StrUtil.isNotBlank(id)) {
|
||||||
|
idSet.add(id.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>(idSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验所选疗休养配置是否仍属于页面提交的年度范围。
|
||||||
|
*/
|
||||||
|
private Result checkSettingsInYearRange(Integer startYear, Integer endYear, List<String> settingIds) {
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM tour_setting
|
||||||
|
WHERE delFlag = 0
|
||||||
|
AND id IN (@settingIds)
|
||||||
|
AND `year` >= @startYear
|
||||||
|
AND `year` <= @endYear
|
||||||
|
""");
|
||||||
|
sql.setParam("settingIds", settingIds.toArray(new String[0]));
|
||||||
|
sql.setParam("startYear", startYear);
|
||||||
|
sql.setParam("endYear", endYear);
|
||||||
|
sql.setCallback(Sqls.callback.integer());
|
||||||
|
tourParticipantRosterService.dao().execute(sql);
|
||||||
|
if (sql.getInt() != settingIds.size()) {
|
||||||
|
return Result.error("所选疗休养配置与年度范围不匹配,请刷新后重试");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造总数查询和列表查询共用的 SQL 条件。
|
||||||
|
*/
|
||||||
|
private Cnd buildQueryCnd(Integer startYear, Integer endYear, String settingId, String matterId, String keyword,
|
||||||
|
String unitName, String unionId, Boolean joined, String lineType,
|
||||||
|
String groupCode, String undertakeUnionId) {
|
||||||
|
Cnd cnd = Cnd.NEW();
|
||||||
|
cnd.and("r.delFlag", "=", false);
|
||||||
|
cnd.and("s.`year`", ">=", startYear);
|
||||||
|
cnd.and("s.`year`", "<=", endYear);
|
||||||
|
cnd.andEX("r.settingId", "=", settingId);
|
||||||
|
cnd.andEX("t.matterId", "=", matterId);
|
||||||
|
cnd.andEX("r.unionId", "=", unionId);
|
||||||
|
cnd.andEX("r.unitName", "like", StrUtil.isBlank(unitName) ? null : "%" + unitName.trim() + "%");
|
||||||
|
cnd.andEX("t.lineType", "=", lineType);
|
||||||
|
cnd.andEX("t.undertakeUnionId", "=", undertakeUnionId);
|
||||||
|
appendKeywordFilter(cnd, keyword);
|
||||||
|
return cnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加工号和姓名模糊查询条件。
|
||||||
|
*/
|
||||||
|
private void appendKeywordFilter(Cnd cnd, String keyword) {
|
||||||
|
if (StrUtil.isBlank(keyword)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||||
|
group.orLike("r.jobNo", keyword.trim());
|
||||||
|
group.orLike("r.userName", keyword.trim());
|
||||||
|
cnd.and(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将前端排序字段映射为安全的 SQL 排序列。
|
||||||
|
*/
|
||||||
|
private String getOrderColumn(String orderName) {
|
||||||
|
if ("year".equals(orderName)) {
|
||||||
|
return "s.`year`";
|
||||||
|
}
|
||||||
|
if ("jobNo".equals(orderName)) {
|
||||||
|
return "r.jobNo";
|
||||||
|
}
|
||||||
|
if ("userName".equals(orderName)) {
|
||||||
|
return "r.userName";
|
||||||
|
}
|
||||||
|
if ("unitName".equals(orderName)) {
|
||||||
|
return "r.unitName";
|
||||||
|
}
|
||||||
|
if ("unionName".equals(orderName)) {
|
||||||
|
return "r.unionName";
|
||||||
|
}
|
||||||
|
if ("joined".equals(orderName)) {
|
||||||
|
return "r.joined";
|
||||||
|
}
|
||||||
|
if ("lineType".equals(orderName)) {
|
||||||
|
return "t.lineType";
|
||||||
|
}
|
||||||
|
if ("lineName".equals(orderName)) {
|
||||||
|
return "COALESCE(NULLIF(l.lineName, ''), t.lineName)";
|
||||||
|
}
|
||||||
|
if ("signupTime".equals(orderName)) {
|
||||||
|
return "t.signupTime";
|
||||||
|
}
|
||||||
|
return "s.`year`";
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-24
@@ -97,18 +97,7 @@ public class TourSchoolUnionApprovalController {
|
|||||||
task.taskParentId,
|
task.taskParentId,
|
||||||
task.variable AS taskVariable,
|
task.variable AS taskVariable,
|
||||||
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS curTaskName,
|
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS curTaskName,
|
||||||
CASE
|
COALESCE(NULLIF(l.lineName, ''), t.lineName) AS currentLineName,
|
||||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
|
||||||
ELSE CONCAT(
|
|
||||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
|
||||||
'(',
|
|
||||||
CASE
|
|
||||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
|
||||||
ELSE IFNULL(mu.name, '')
|
|
||||||
END,
|
|
||||||
')'
|
|
||||||
)
|
|
||||||
END AS currentLineName,
|
|
||||||
CASE
|
CASE
|
||||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||||
@@ -479,18 +468,7 @@ public class TourSchoolUnionApprovalController {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
Sql sql = Sqls.create("""
|
Sql sql = Sqls.create("""
|
||||||
SELECT CASE
|
SELECT COALESCE(NULLIF(l.lineName, ''), t.lineName) AS lineName
|
||||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
|
||||||
ELSE CONCAT(
|
|
||||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
|
||||||
'(',
|
|
||||||
CASE
|
|
||||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
|
||||||
ELSE IFNULL(mu.name, '')
|
|
||||||
END,
|
|
||||||
')'
|
|
||||||
)
|
|
||||||
END AS lineName
|
|
||||||
FROM tour_ledger t
|
FROM tour_ledger t
|
||||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||||
|
|||||||
+12
-9
@@ -8,6 +8,7 @@ import com.budwk.app.base.param.PageForm;
|
|||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSettingLot;
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourSettingLot;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourParticipantRosterService;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourSettingService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourSettingService;
|
||||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
@@ -32,6 +33,9 @@ public class TourSettingController {
|
|||||||
@Inject
|
@Inject
|
||||||
private TourSettingService tourSettingService;
|
private TourSettingService tourSettingService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private TourParticipantRosterService tourParticipantRosterService;
|
||||||
|
|
||||||
@At("")
|
@At("")
|
||||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/setting/index.html")
|
@Ok("beetl:/platform/zhgh/dayofficework/Tour/setting/index.html")
|
||||||
@SaCheckPermission("tour.setting")
|
@SaCheckPermission("tour.setting")
|
||||||
@@ -153,9 +157,11 @@ public class TourSettingController {
|
|||||||
if (Lang.isNotEmpty(lotDeleteList)) {
|
if (Lang.isNotEmpty(lotDeleteList)) {
|
||||||
tourSettingService.dao().clear(TourSettingLot.class, Cnd.where(TourSettingLot::getId, "in", lotDeleteList));
|
tourSettingService.dao().clear(TourSettingLot.class, Cnd.where(TourSettingLot::getId, "in", lotDeleteList));
|
||||||
}
|
}
|
||||||
tourSettingService.updateIgnoreNull(tourSetting);
|
tourSettingService.updateWithNullableSignupTime(tourSetting);
|
||||||
saveLots(tourSetting);
|
saveLots(tourSetting);
|
||||||
}
|
}
|
||||||
|
// Initialize the participant roster only once; existing roster data is preserved on later setting edits.
|
||||||
|
tourParticipantRosterService.syncSettingRosterIfEmpty(tourSetting.getId());
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +174,8 @@ public class TourSettingController {
|
|||||||
return Result.error("参数错误");
|
return Result.error("参数错误");
|
||||||
}
|
}
|
||||||
tourSettingService.dao().clear(TourSettingLot.class, Cnd.where(TourSettingLot::getSettingId, "=", id));
|
tourSettingService.dao().clear(TourSettingLot.class, Cnd.where(TourSettingLot::getSettingId, "=", id));
|
||||||
|
// Remove roster rows for the deleted setting so later statistics ignore obsolete data.
|
||||||
|
tourParticipantRosterService.clearSettingRoster(id);
|
||||||
tourSettingService.delete(id);
|
tourSettingService.delete(id);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
@@ -205,14 +213,9 @@ public class TourSettingController {
|
|||||||
if (StrUtil.isBlank(tourSetting.getConfigName())) {
|
if (StrUtil.isBlank(tourSetting.getConfigName())) {
|
||||||
return Result.error("配置名称不能为空");
|
return Result.error("配置名称不能为空");
|
||||||
}
|
}
|
||||||
if (tourSetting.getMinGroupPeople() != null && tourSetting.getMaxGroupPeople() != null
|
// 报名时间允许不配置;仅在开始和结束都填写时校验先后顺序。
|
||||||
&& tourSetting.getMinGroupPeople() > tourSetting.getMaxGroupPeople()) {
|
if (StrUtil.isNotBlank(tourSetting.getSignupStartTime()) && StrUtil.isNotBlank(tourSetting.getSignupEndTime())
|
||||||
return Result.error("最少成团人数不能大于最多成团人数");
|
&& tourSetting.getSignupStartTime().compareTo(tourSetting.getSignupEndTime()) >= 0) {
|
||||||
}
|
|
||||||
if (StrUtil.isBlank(tourSetting.getSignupStartTime()) || StrUtil.isBlank(tourSetting.getSignupEndTime())) {
|
|
||||||
return Result.error("报名时间不能为空");
|
|
||||||
}
|
|
||||||
if (tourSetting.getSignupStartTime().compareTo(tourSetting.getSignupEndTime()) >= 0) {
|
|
||||||
return Result.error("开始报名时间必须小于结束报名时间");
|
return Result.error("开始报名时间必须小于结束报名时间");
|
||||||
}
|
}
|
||||||
if (tourSetting.getCycleStartYear() != null && tourSetting.getCycleEndYear() != null
|
if (tourSetting.getCycleStartYear() != null && tourSetting.getCycleEndYear() != null
|
||||||
|
|||||||
+202
-106
@@ -1,5 +1,9 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||||
|
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.SaCheckPermission;
|
||||||
import cn.dev33.satoken.annotation.SaMode;
|
import cn.dev33.satoken.annotation.SaMode;
|
||||||
import cn.hutool.core.lang.Dict;
|
import cn.hutool.core.lang.Dict;
|
||||||
@@ -7,6 +11,7 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.param.PageForm;
|
import com.budwk.app.base.param.PageForm;
|
||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
|
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||||
import com.budwk.app.flow.constant.FlowConst;
|
import com.budwk.app.flow.constant.FlowConst;
|
||||||
import com.budwk.app.flow.engine.FlowEngine;
|
import com.budwk.app.flow.engine.FlowEngine;
|
||||||
import com.budwk.app.flow.entity.ProcessInstance;
|
import com.budwk.app.flow.entity.ProcessInstance;
|
||||||
@@ -30,6 +35,7 @@ import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeSer
|
|||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourParticipantRosterService;
|
||||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Sqls;
|
import org.nutz.dao.Sqls;
|
||||||
@@ -44,10 +50,13 @@ import org.nutz.lang.util.NutMap;
|
|||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
import org.nutz.mvc.annotation.Ok;
|
import org.nutz.mvc.annotation.Ok;
|
||||||
import org.nutz.mvc.annotation.Param;
|
import org.nutz.mvc.annotation.Param;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -71,6 +80,9 @@ public class TourSignupController {
|
|||||||
@Inject
|
@Inject
|
||||||
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
|
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private TourParticipantRosterService tourParticipantRosterService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private SysDictService sysDictService;
|
private SysDictService sysDictService;
|
||||||
|
|
||||||
@@ -136,7 +148,7 @@ public class TourSignupController {
|
|||||||
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
|
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
|
||||||
public Result h5PageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId, Boolean directFamilyUnitLine) {
|
public Result h5PageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId, Boolean directFamilyUnitLine) {
|
||||||
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||||
pageForm.defaultSortAsc("lineName");
|
pageForm.defaultSortAsc("lineCode");
|
||||||
}
|
}
|
||||||
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
|
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
|
||||||
cnd.andEX("l.directFamilyUnitLine", "=", directFamilyUnitLine);
|
cnd.andEX("l.directFamilyUnitLine", "=", directFamilyUnitLine);
|
||||||
@@ -171,6 +183,9 @@ public class TourSignupController {
|
|||||||
lot.lotName,
|
lot.lotName,
|
||||||
m.travelStartTime,
|
m.travelStartTime,
|
||||||
m.travelEndTime,
|
m.travelEndTime,
|
||||||
|
COALESCE(NULLIF(s.signupStartTime, ''), m.signupStartTime) AS signupStartTime,
|
||||||
|
COALESCE(NULLIF(s.signupEndTime, ''), m.signupEndTime) AS signupEndTime,
|
||||||
|
m.maxSignupPeople,
|
||||||
IFNULL(sc.signupCount, 0) AS signupCount,
|
IFNULL(sc.signupCount, 0) AS signupCount,
|
||||||
IFNULL(sc.familyCount, 0) AS familyCount,
|
IFNULL(sc.familyCount, 0) AS familyCount,
|
||||||
my.ledgerId,
|
my.ledgerId,
|
||||||
@@ -183,6 +198,7 @@ public class TourSignupController {
|
|||||||
FROM tour_matter m
|
FROM tour_matter m
|
||||||
INNER JOIN tour_line l ON l.id = m.lineId
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
LEFT JOIN sys_union u ON u.id = m.unionId
|
LEFT JOIN sys_union u ON u.id = m.unionId
|
||||||
|
LEFT JOIN tour_setting s ON s.id = m.settingId
|
||||||
LEFT JOIN tour_travel_agency a ON a.id = l.travelAgencyId
|
LEFT JOIN tour_travel_agency a ON a.id = l.travelAgencyId
|
||||||
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
|
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
@@ -222,7 +238,7 @@ public class TourSignupController {
|
|||||||
GROUP BY t.matterId, t.jobNo
|
GROUP BY t.matterId, t.jobNo
|
||||||
) my ON my.matterId = m.id
|
) my ON my.matterId = m.id
|
||||||
$condition
|
$condition
|
||||||
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC
|
ORDER BY $orderColumn $orderBy, CAST(l.lineCode AS UNSIGNED) ASC, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC
|
||||||
""");
|
""");
|
||||||
listSql.setCondition(cnd);
|
listSql.setCondition(cnd);
|
||||||
listSql.setParam("currentJobNo", currentJobNo);
|
listSql.setParam("currentJobNo", currentJobNo);
|
||||||
@@ -241,7 +257,7 @@ public class TourSignupController {
|
|||||||
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
|
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
|
||||||
public Result pageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId) {
|
public Result pageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId) {
|
||||||
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||||
pageForm.defaultSortAsc("lineName");
|
pageForm.defaultSortAsc("lineCode");
|
||||||
}
|
}
|
||||||
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
|
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
|
||||||
String currentJobNo = currentJobNo();
|
String currentJobNo = currentJobNo();
|
||||||
@@ -265,6 +281,7 @@ public class TourSignupController {
|
|||||||
m.matterName,
|
m.matterName,
|
||||||
m.minGroupPeople,
|
m.minGroupPeople,
|
||||||
m.maxGroupPeople,
|
m.maxGroupPeople,
|
||||||
|
m.maxSignupPeople,
|
||||||
m.unionId,
|
m.unionId,
|
||||||
COALESCE(u.name, '校工会') AS unionName,
|
COALESCE(u.name, '校工会') AS unionName,
|
||||||
m.lineId,
|
m.lineId,
|
||||||
@@ -325,12 +342,12 @@ public class TourSignupController {
|
|||||||
GROUP BY t.matterId, t.jobNo
|
GROUP BY t.matterId, t.jobNo
|
||||||
) my ON my.matterId = m.id
|
) my ON my.matterId = m.id
|
||||||
$condition
|
$condition
|
||||||
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC
|
ORDER BY $orderColumn $orderBy, CAST(l.lineCode AS UNSIGNED) ASC, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC
|
||||||
""");
|
""");
|
||||||
listSql.setCondition(cnd);
|
listSql.setCondition(cnd);
|
||||||
listSql.setParam("currentJobNo", currentJobNo);
|
listSql.setParam("currentJobNo", currentJobNo);
|
||||||
listSql.setParam("abandonState", ProcessInstanceStateEnum.ABANDON.getCode());
|
listSql.setParam("abandonState", ProcessInstanceStateEnum.ABANDON.getCode());
|
||||||
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
|
listSql.setVar("orderColumn", tourLedgerService.getTourSignupOrderColumn(pageForm.getPageOrderName()));
|
||||||
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
|
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
|
||||||
listSql.setPager(tourMatterService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
listSql.setPager(tourMatterService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||||
listSql.setCallback(Sqls.callback.maps());
|
listSql.setCallback(Sqls.callback.maps());
|
||||||
@@ -340,6 +357,28 @@ public class TourSignupController {
|
|||||||
return Result.success(pagination);
|
return Result.success(pagination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出 PC 端疗休养报名列表,导出范围与当前页面查询条件保持一致。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("tour.signup")
|
||||||
|
@Ok("void")
|
||||||
|
public void exportData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||||
|
pageForm.defaultSortAsc("lineCode");
|
||||||
|
}
|
||||||
|
// 导出不分页,按当前查询条件和排序口径查询全量列表数据。
|
||||||
|
List<NutMap> rows = querySignupExportRows(pageForm, year, lineName, lineType, unionId);
|
||||||
|
List<ExcelExportEntity> entities = buildSignupExportEntities();
|
||||||
|
|
||||||
|
// 导出文件统一使用 XSSF,避免大数据量时生成旧版 xls 格式。
|
||||||
|
ExportParams exportParams = new ExportParams();
|
||||||
|
exportParams.setType(ExcelType.XSSF);
|
||||||
|
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, rows);
|
||||||
|
CommonDownloadUtil.download("疗休养报名.xlsx", workbook, response);
|
||||||
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
|
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
|
||||||
public Result unionOptions() {
|
public Result unionOptions() {
|
||||||
@@ -464,6 +503,7 @@ public class TourSignupController {
|
|||||||
m.travelStartTime,
|
m.travelStartTime,
|
||||||
m.travelEndTime,
|
m.travelEndTime,
|
||||||
m.contactName,
|
m.contactName,
|
||||||
|
m.maxSignupPeople,
|
||||||
CASE
|
CASE
|
||||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||||
@@ -529,19 +569,6 @@ public class TourSignupController {
|
|||||||
.and(TourLedger::getJobNo, "=", staff.getString("jobNo"));
|
.and(TourLedger::getJobNo, "=", staff.getString("jobNo"));
|
||||||
ledgerCnd.desc(TourLedger::getCreatedAt);
|
ledgerCnd.desc(TourLedger::getCreatedAt);
|
||||||
TourLedger ledger = tourLedgerService.fetch(ledgerCnd);
|
TourLedger ledger = tourLedgerService.fetch(ledgerCnd);
|
||||||
TourMatter matterEntity = tourMatterService.fetch(matter.getString("matterId"));
|
|
||||||
TourLedger ruleLedger = new TourLedger();
|
|
||||||
ruleLedger.setId(ledger == null ? null : ledger.getId());
|
|
||||||
ruleLedger.setYear(matterEntity == null ? matter.getInt("year") : matterEntity.getYear());
|
|
||||||
ruleLedger.setMatterId(matter.getString("matterId"));
|
|
||||||
ruleLedger.setLineId(matter.getString("lineId"));
|
|
||||||
ruleLedger.setLineName(matter.getString("lineName"));
|
|
||||||
if (!Boolean.TRUE.equals(noticeChecked)) {
|
|
||||||
Result ruleResult = checkSignupRule(ruleLedger, matterEntity, ledger);
|
|
||||||
if (ruleResult != null) {
|
|
||||||
return ruleResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<TourLedgerFamily> families = Collections.emptyList();
|
List<TourLedgerFamily> families = Collections.emptyList();
|
||||||
TourLedgerDirectRelative directRelative = null;
|
TourLedgerDirectRelative directRelative = null;
|
||||||
if (ledger != null) {
|
if (ledger != null) {
|
||||||
@@ -583,47 +610,8 @@ public class TourSignupController {
|
|||||||
if (activityGroupId == null || !activityBasicScopeService.isUserInGroup(activityGroupId, SecurityUtil.getUserId())) {
|
if (activityGroupId == null || !activityBasicScopeService.isUserInGroup(activityGroupId, SecurityUtil.getUserId())) {
|
||||||
return Result.error("您不在本次疗休养报名范围内");
|
return Result.error("您不在本次疗休养报名范围内");
|
||||||
}
|
}
|
||||||
TourLedger oldLedger = fetchCurrentUserMatterLedger(matter.getId());
|
// 我要报名入口不再做周期次数、周期费用和省外名额提示,最终报名规则由 doSignup 统一校验。
|
||||||
CycleAllowedTimes cycleAllowedTimes = calculateCycleAllowedTimes(matter, setting, oldLedger);
|
return Result.success(NutMap.NEW().addv("canApply", true).addv("noticeRequired", false));
|
||||||
if (!cycleAllowedTimes.canApply()) {
|
|
||||||
return Result.success(NutMap.NEW()
|
|
||||||
.addv("canApply", false)
|
|
||||||
.addv("noticeRequired", true)
|
|
||||||
.addv("noticeType", "cycleAllowedTimes")
|
|
||||||
.addv("cycleStartYear", cycleAllowedTimes.startYear())
|
|
||||||
.addv("cycleEndYear", cycleAllowedTimes.endYear())
|
|
||||||
.addv("cycleAllowedTimes", cycleAllowedTimes.allowedTimes())
|
|
||||||
.addv("cycleJoinedTimes", cycleAllowedTimes.joinedTimes())
|
|
||||||
.addv("message", buildCycleAllowedTimesNotice(cycleAllowedTimes)));
|
|
||||||
}
|
|
||||||
CycleTotalCost cycleTotalCost = calculateCycleTotalCost(matter, setting, line, oldLedger);
|
|
||||||
if (!cycleTotalCost.canApply()) {
|
|
||||||
return Result.success(NutMap.NEW()
|
|
||||||
.addv("canApply", false)
|
|
||||||
.addv("noticeRequired", true)
|
|
||||||
.addv("noticeType", "cycleTotalCost")
|
|
||||||
.addv("cycleStartYear", cycleTotalCost.startYear())
|
|
||||||
.addv("cycleEndYear", cycleTotalCost.endYear())
|
|
||||||
.addv("cycleTotalCost", cycleTotalCost.totalCost())
|
|
||||||
.addv("cycleJoinedCost", cycleTotalCost.joinedCost())
|
|
||||||
.addv("currentLineCost", cycleTotalCost.currentCost())
|
|
||||||
.addv("message", buildCycleTotalCostNotice(cycleTotalCost)));
|
|
||||||
}
|
|
||||||
if (!isOutProvinceLine(line.getLineType())) {
|
|
||||||
return Result.success(NutMap.NEW().addv("canApply", true).addv("noticeRequired", false));
|
|
||||||
}
|
|
||||||
OutProvinceQuota quota = calculateOutProvinceQuota(matter, setting, oldLedger);
|
|
||||||
return Result.success(NutMap.NEW()
|
|
||||||
.addv("noticeRequired", true)
|
|
||||||
.addv("noticeType", "outProvinceQuota")
|
|
||||||
.addv("canApply", quota.canApply())
|
|
||||||
.addv("ratioType", quota.ratioType())
|
|
||||||
.addv("basePeople", quota.basePeople())
|
|
||||||
.addv("totalSignupPeople", quota.totalSignupPeople())
|
|
||||||
.addv("outProvinceSignupPeople", quota.outProvinceSignupPeople())
|
|
||||||
.addv("allowPeople", quota.allowPeople())
|
|
||||||
.addv("ratioText", quota.ratioText())
|
|
||||||
.addv("message", buildOutProvinceQuotaNotice(quota)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@@ -740,7 +728,8 @@ public class TourSignupController {
|
|||||||
return Result.error("不能修改他人的报名记录");
|
return Result.error("不能修改他人的报名记录");
|
||||||
}
|
}
|
||||||
TourMatter matter = tourMatterService.fetch(ledger.getMatterId());
|
TourMatter matter = tourMatterService.fetch(ledger.getMatterId());
|
||||||
Result ruleResult = checkSignupRule(ledger, matter, update ? oldLedger : null);
|
TourLedger ruleOldLedger = update ? oldLedger : fetchCurrentUserMatterLedger(ledger.getMatterId());
|
||||||
|
Result ruleResult = checkSignupRule(ledger, matter, update ? oldLedger : null, ruleOldLedger, calculateCurrentSignupPeople(familyList));
|
||||||
if (ruleResult != null) {
|
if (ruleResult != null) {
|
||||||
return ruleResult;
|
return ruleResult;
|
||||||
}
|
}
|
||||||
@@ -751,10 +740,6 @@ public class TourSignupController {
|
|||||||
update = true;
|
update = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Result maxPeopleResult = checkMaxGroupPeople(matter, oldLedger, familyList);
|
|
||||||
if (maxPeopleResult != null) {
|
|
||||||
return maxPeopleResult;
|
|
||||||
}
|
|
||||||
TourLine line = tourLedgerService.dao().fetch(TourLine.class, matter.getLineId());
|
TourLine line = tourLedgerService.dao().fetch(TourLine.class, matter.getLineId());
|
||||||
boolean directFamilyLine = line != null && Boolean.TRUE.equals(line.getDirectFamilyUnitLine());
|
boolean directFamilyLine = line != null && Boolean.TRUE.equals(line.getDirectFamilyUnitLine());
|
||||||
TourSetting setting = tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId());
|
TourSetting setting = tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId());
|
||||||
@@ -817,6 +802,8 @@ public class TourSignupController {
|
|||||||
return workflowResult;
|
return workflowResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Signup and modification both mean the staff member is counted as joined for this setting.
|
||||||
|
tourParticipantRosterService.markJoinedByLedger(ledger);
|
||||||
return Result.success().addMsg(approvalRequired ? (update ? "修改已提交,等待审核" : "申请已提交,等待审核") : (update ? "修改成功" : "报名成功"));
|
return Result.success().addMsg(approvalRequired ? (update ? "修改已提交,等待审核" : "申请已提交,等待审核") : (update ? "修改成功" : "报名成功"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,6 +828,14 @@ public class TourSignupController {
|
|||||||
if (!currentJobNo().equals(ledger.getJobNo())) {
|
if (!currentJobNo().equals(ledger.getJobNo())) {
|
||||||
return Result.error("不能取消他人的报名记录");
|
return Result.error("不能取消他人的报名记录");
|
||||||
}
|
}
|
||||||
|
TourMatter matter = tourMatterService.fetch(ledger.getMatterId());
|
||||||
|
TourSetting setting = matter == null ? null : tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId());
|
||||||
|
Result signupTimeResult = tourLedgerService.checkSignupTimeWindow(matter, setting);
|
||||||
|
if (signupTimeResult != null) {
|
||||||
|
return signupTimeResult;
|
||||||
|
}
|
||||||
|
// Reset the roster before deleting the ledger record, because the ledger still carries setting linkage here.
|
||||||
|
tourParticipantRosterService.markNotJoinedByLedger(ledger);
|
||||||
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
|
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
|
||||||
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
||||||
tourLedgerService.clear(Cnd.where(TourLedger::getId, "=", ledger.getId()));
|
tourLedgerService.clear(Cnd.where(TourLedger::getId, "=", ledger.getId()));
|
||||||
@@ -962,26 +957,12 @@ public class TourSignupController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result checkMaxGroupPeople(TourMatter matter, TourLedger oldLedger, List<TourLedgerFamily> familyList) {
|
|
||||||
if (matter == null || matter.getMaxGroupPeople() == null || matter.getMaxGroupPeople() <= 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int currentPeople = 1 + (Lang.isEmpty(familyList) ? 0 : familyList.size());
|
|
||||||
int signedPeople = countSignupPeople(matter.getId(), oldLedger == null ? null : oldLedger.getId());
|
|
||||||
int totalPeople = signedPeople + currentPeople;
|
|
||||||
if (totalPeople > matter.getMaxGroupPeople()) {
|
|
||||||
return Result.error("当前报名人数 " + signedPeople + " 人,本次报名 " + currentPeople
|
|
||||||
+ " 人,最多成团人数 " + matter.getMaxGroupPeople() + " 人,报名后将超过人数上限,不能提交");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isValidIdCard(String idCard) {
|
private boolean isValidIdCard(String idCard) {
|
||||||
return StrUtil.isNotBlank(idCard)
|
return StrUtil.isNotBlank(idCard)
|
||||||
&& idCard.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dX]$");
|
&& idCard.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dX]$");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result checkSignupRule(TourLedger ledger, TourMatter matter, TourLedger oldLedger) {
|
private Result checkSignupRule(TourLedger ledger, TourMatter matter, TourLedger oldLedger, TourLedger maxGroupOldLedger, int currentPeopleCount) {
|
||||||
if (matter == null || Boolean.TRUE.equals(matter.getDelFlag()) || !Boolean.TRUE.equals(matter.getEnabled())) {
|
if (matter == null || Boolean.TRUE.equals(matter.getDelFlag()) || !Boolean.TRUE.equals(matter.getEnabled())) {
|
||||||
return Result.error("报名出行时段不存在或已停用");
|
return Result.error("报名出行时段不存在或已停用");
|
||||||
}
|
}
|
||||||
@@ -996,6 +977,10 @@ public class TourSignupController {
|
|||||||
if (setting == null || Boolean.TRUE.equals(setting.getDelFlag()) || !Boolean.TRUE.equals(setting.getEnabled())) {
|
if (setting == null || Boolean.TRUE.equals(setting.getDelFlag()) || !Boolean.TRUE.equals(setting.getEnabled())) {
|
||||||
return Result.error("报名事项配置不存在或已停用");
|
return Result.error("报名事项配置不存在或已停用");
|
||||||
}
|
}
|
||||||
|
Result maxSignupResult = checkMaxSignupPeople(matter, maxGroupOldLedger, currentPeopleCount);
|
||||||
|
if (maxSignupResult != null) {
|
||||||
|
return maxSignupResult;
|
||||||
|
}
|
||||||
Integer activityGroupId = parseInteger(setting.getActivityGroupId());
|
Integer activityGroupId = parseInteger(setting.getActivityGroupId());
|
||||||
if (activityGroupId == null || !activityBasicScopeService.isUserInGroup(activityGroupId, SecurityUtil.getUserId())) {
|
if (activityGroupId == null || !activityBasicScopeService.isUserInGroup(activityGroupId, SecurityUtil.getUserId())) {
|
||||||
return Result.error("您不在本次疗休养报名范围内");
|
return Result.error("您不在本次疗休养报名范围内");
|
||||||
@@ -1035,6 +1020,8 @@ public class TourSignupController {
|
|||||||
if (tourLedgerService.count(outProvinceCnd) > 0) {
|
if (tourLedgerService.count(outProvinceCnd) > 0) {
|
||||||
return Result.error("您已参加过省外线路,不能报名省外线路,请选择省内线路");
|
return Result.error("您已参加过省外线路,不能报名省外线路,请选择省内线路");
|
||||||
}
|
}
|
||||||
|
// Out-province quota is shared by year; lock only before the quota count calculation.
|
||||||
|
tourLedgerService.lockTourSignupQuotaYear(matter.getYear());
|
||||||
OutProvinceQuota quota = calculateOutProvinceQuota(matter, setting, oldLedger);
|
OutProvinceQuota quota = calculateOutProvinceQuota(matter, setting, oldLedger);
|
||||||
if (!quota.canApply()) {
|
if (!quota.canApply()) {
|
||||||
return Result.error(buildOutProvinceQuotaNotice(quota));
|
return Result.error(buildOutProvinceQuotaNotice(quota));
|
||||||
@@ -1066,6 +1053,61 @@ public class TourSignupController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the matter maximum signup capacity at final submit time.
|
||||||
|
*/
|
||||||
|
private Result checkMaxSignupPeople(TourMatter matter, TourLedger oldLedger, int currentPeopleCount) {
|
||||||
|
int maxSignupPeople = matter == null || matter.getMaxSignupPeople() == null ? 0 : matter.getMaxSignupPeople();
|
||||||
|
if (maxSignupPeople <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Maximum signup capacity is owned by the current matter, so only the same matter is serialized.
|
||||||
|
tourLedgerService.lockTourSignupMatter(matter.getId());
|
||||||
|
String excludeLedgerId = oldLedger == null ? null : oldLedger.getId();
|
||||||
|
int signedPeople = countMatterSignupPeople(matter.getId(), excludeLedgerId);
|
||||||
|
if (signedPeople + currentPeopleCount > maxSignupPeople) {
|
||||||
|
return Result.error("当前线路最多报名人数为" + maxSignupPeople + "人,当前已报名" + signedPeople
|
||||||
|
+ "人,本次报名" + currentPeopleCount + "人,不能超过最多报名人数");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts current submit people as one staff member plus all submitted family members.
|
||||||
|
*/
|
||||||
|
private int calculateCurrentSignupPeople(List<TourLedgerFamily> familyList) {
|
||||||
|
return 1 + (familyList == null ? 0 : familyList.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts valid signed people for a matter, including staff and their family members.
|
||||||
|
*/
|
||||||
|
private int countMatterSignupPeople(String matterId, String excludeLedgerId) {
|
||||||
|
if (StrUtil.isBlank(matterId)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
String excludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND t.id <> @excludeLedgerId";
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT IFNULL(SUM(1 + IFNULL(f.familyCount, 0)), 0) AS peopleCount
|
||||||
|
FROM tour_ledger t
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
|
FROM tour_ledger_family
|
||||||
|
WHERE delFlag = 0
|
||||||
|
GROUP BY ledgerId
|
||||||
|
) f ON f.ledgerId = t.id
|
||||||
|
WHERE t.delFlag = 0
|
||||||
|
AND t.matterId = @matterId
|
||||||
|
""" + excludeSql);
|
||||||
|
sql.setParam("matterId", matterId);
|
||||||
|
if (StrUtil.isNotBlank(excludeLedgerId)) {
|
||||||
|
sql.setParam("excludeLedgerId", excludeLedgerId);
|
||||||
|
}
|
||||||
|
sql.setCallback(Sqls.callback.integer());
|
||||||
|
tourLedgerService.dao().execute(sql);
|
||||||
|
return sql.getInt();
|
||||||
|
}
|
||||||
|
|
||||||
private TourLedger fetchLatestLedger(Cnd cnd) {
|
private TourLedger fetchLatestLedger(Cnd cnd) {
|
||||||
cnd.desc(TourLedger::getSignupTime);
|
cnd.desc(TourLedger::getSignupTime);
|
||||||
cnd.desc(TourLedger::getCreatedAt);
|
cnd.desc(TourLedger::getCreatedAt);
|
||||||
@@ -1252,32 +1294,6 @@ public class TourSignupController {
|
|||||||
return sql.getInt();
|
return sql.getInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int countSignupPeople(String matterId, String excludeLedgerId) {
|
|
||||||
if (StrUtil.isBlank(matterId)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
String excludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND t.id <> @excludeLedgerId";
|
|
||||||
Sql sql = Sqls.create("""
|
|
||||||
SELECT IFNULL(SUM(1 + IFNULL(f.familyCount, 0)), 0) AS signupPeople
|
|
||||||
FROM tour_ledger t
|
|
||||||
LEFT JOIN (
|
|
||||||
SELECT ledgerId, COUNT(1) AS familyCount
|
|
||||||
FROM tour_ledger_family
|
|
||||||
WHERE delFlag = 0
|
|
||||||
GROUP BY ledgerId
|
|
||||||
) f ON f.ledgerId = t.id
|
|
||||||
WHERE t.delFlag = 0
|
|
||||||
AND t.matterId = @matterId
|
|
||||||
""" + excludeSql);
|
|
||||||
sql.setParam("matterId", matterId);
|
|
||||||
if (StrUtil.isNotBlank(excludeLedgerId)) {
|
|
||||||
sql.setParam("excludeLedgerId", excludeLedgerId);
|
|
||||||
}
|
|
||||||
sql.setCallback(Sqls.callback.integer());
|
|
||||||
tourLedgerService.dao().execute(sql);
|
|
||||||
return sql.getInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildSelectedLineMessage(TourLedger ledger) {
|
private String buildSelectedLineMessage(TourLedger ledger) {
|
||||||
String selectedLineName = getLedgerLineName(ledger);
|
String selectedLineName = getLedgerLineName(ledger);
|
||||||
return "您已经选择【" + selectedLineName + "】,取消后再重新选择。";
|
return "您已经选择【" + selectedLineName + "】,取消后再重新选择。";
|
||||||
@@ -1479,6 +1495,79 @@ public class TourSignupController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按 PC 疗休养报名列表同一批查询条件导出全量数据,导出不分页但保留当前排序口径。
|
||||||
|
*/
|
||||||
|
private List<NutMap> querySignupExportRows(PageForm pageForm, Integer year, String lineName, String lineType, String unionId) {
|
||||||
|
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
l.lineName,
|
||||||
|
l.lineType,
|
||||||
|
m.minGroupPeople,
|
||||||
|
m.maxGroupPeople,
|
||||||
|
m.maxSignupPeople,
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||||
|
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||||
|
ELSE ''
|
||||||
|
END AS travelPeriod,
|
||||||
|
IFNULL(sc.signupCount, 0) AS signupCount
|
||||||
|
FROM tour_matter m
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
LEFT JOIN sys_union u ON u.id = m.unionId
|
||||||
|
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT t.matterId, SUM(1 + IFNULL(f.familyCount, 0)) AS signupCount
|
||||||
|
FROM tour_ledger t
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
|
FROM tour_ledger_family
|
||||||
|
WHERE delFlag = 0
|
||||||
|
GROUP BY ledgerId
|
||||||
|
) f ON f.ledgerId = t.id
|
||||||
|
WHERE t.delFlag = 0
|
||||||
|
AND t.matterId IS NOT NULL
|
||||||
|
AND t.matterId <> ''
|
||||||
|
GROUP BY t.matterId
|
||||||
|
) sc ON sc.matterId = m.id
|
||||||
|
$condition
|
||||||
|
ORDER BY $orderColumn $orderBy, CAST(l.lineCode AS UNSIGNED) ASC, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC
|
||||||
|
""");
|
||||||
|
sql.setCondition(cnd);
|
||||||
|
sql.setVar("orderColumn", tourLedgerService.getTourSignupOrderColumn(pageForm.getPageOrderName()));
|
||||||
|
sql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
tourMatterService.dao().execute(sql);
|
||||||
|
|
||||||
|
List<NutMap> rows = sql.getList(NutMap.class);
|
||||||
|
for (int i = 0; i < rows.size(); i++) {
|
||||||
|
NutMap row = rows.get(i);
|
||||||
|
row.put("indexNo", i + 1);
|
||||||
|
row.put("minGroupPeopleText", row.getInt("minGroupPeople", 0) + "人");
|
||||||
|
row.put("maxGroupPeopleText", row.getInt("maxGroupPeople", 0) + "人");
|
||||||
|
row.put("maxSignupPeopleText", row.getInt("maxSignupPeople", 0) + "人");
|
||||||
|
row.put("signupCountText", row.getInt("signupCount", 0) + "人");
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds export columns consistent with the PC list; signup count is always included.
|
||||||
|
*/
|
||||||
|
private List<ExcelExportEntity> buildSignupExportEntities() {
|
||||||
|
List<ExcelExportEntity> entities = new ArrayList<>();
|
||||||
|
entities.add(new ExcelExportEntity("序号", "indexNo", 10));
|
||||||
|
entities.add(new ExcelExportEntity("线路名称", "lineName", 30));
|
||||||
|
entities.add(new ExcelExportEntity("出行时段", "travelPeriod", 28));
|
||||||
|
entities.add(new ExcelExportEntity("线路类型", "lineType", 16));
|
||||||
|
entities.add(new ExcelExportEntity("最少成团人数", "minGroupPeopleText", 16));
|
||||||
|
entities.add(new ExcelExportEntity("最多成团人数", "maxGroupPeopleText", 16));
|
||||||
|
entities.add(new ExcelExportEntity("最多报名人数", "maxSignupPeopleText", 16));
|
||||||
|
entities.add(new ExcelExportEntity("报名人数", "signupCountText", 16));
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
private Result checkSignupApprovalWorkflowCanSubmit(String ledgerId) {
|
private Result checkSignupApprovalWorkflowCanSubmit(String ledgerId) {
|
||||||
ProcessInstance instance = tourLedgerService.dao().fetch(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "=", ledgerId)
|
ProcessInstance instance = tourLedgerService.dao().fetch(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "=", ledgerId)
|
||||||
.and(ProcessInstance::getState, "<>", ProcessInstanceStateEnum.ABANDON.getCode())
|
.and(ProcessInstance::getState, "<>", ProcessInstanceStateEnum.ABANDON.getCode())
|
||||||
@@ -1537,6 +1626,10 @@ public class TourSignupController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getOrderColumn(String orderName) {
|
private String getOrderColumn(String orderName) {
|
||||||
|
if ("lineCode".equals(orderName)) {
|
||||||
|
// 线路序号按数字排序,避免字符串排序导致 10 排在 2 前面。
|
||||||
|
return "CAST(l.lineCode AS UNSIGNED)";
|
||||||
|
}
|
||||||
if ("lineName".equals(orderName)) {
|
if ("lineName".equals(orderName)) {
|
||||||
return "l.lineName";
|
return "l.lineName";
|
||||||
}
|
}
|
||||||
@@ -1558,9 +1651,12 @@ public class TourSignupController {
|
|||||||
if ("maxGroupPeople".equals(orderName)) {
|
if ("maxGroupPeople".equals(orderName)) {
|
||||||
return "m.maxGroupPeople";
|
return "m.maxGroupPeople";
|
||||||
}
|
}
|
||||||
|
if ("maxSignupPeople".equals(orderName)) {
|
||||||
|
return "m.maxSignupPeople";
|
||||||
|
}
|
||||||
if ("signupCount".equals(orderName)) {
|
if ("signupCount".equals(orderName)) {
|
||||||
return "signupCount";
|
return "signupCount";
|
||||||
}
|
}
|
||||||
return "l.lineName";
|
return "CAST(l.lineCode AS UNSIGNED)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+314
-112
@@ -1,17 +1,22 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||||
|
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.SaCheckPermission;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.budwk.app.base.constant.RoleConstant;
|
import com.budwk.app.base.constant.RoleConstant;
|
||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.param.PageForm;
|
import com.budwk.app.base.param.PageForm;
|
||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
|
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||||
import com.budwk.app.sys.models.Sys_union;
|
import com.budwk.app.sys.models.Sys_union;
|
||||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Sqls;
|
import org.nutz.dao.Sqls;
|
||||||
import org.nutz.dao.sql.Sql;
|
import org.nutz.dao.sql.Sql;
|
||||||
@@ -22,6 +27,7 @@ import org.nutz.lang.util.NutMap;
|
|||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
import org.nutz.mvc.annotation.Ok;
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -61,7 +67,17 @@ public class TourUndertakeController {
|
|||||||
SELECT COUNT(1)
|
SELECT COUNT(1)
|
||||||
FROM tour_matter m
|
FROM tour_matter m
|
||||||
INNER JOIN tour_line l ON l.id = m.lineId
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
LEFT JOIN sys_union u ON u.id = m.unionId
|
INNER JOIN (
|
||||||
|
SELECT
|
||||||
|
t.matterId,
|
||||||
|
t.groupCode,
|
||||||
|
MAX(t.undertakeUnionId) AS undertakeUnionId
|
||||||
|
FROM tour_ledger t
|
||||||
|
WHERE t.delFlag = 0
|
||||||
|
AND t.groupCode IS NOT NULL
|
||||||
|
AND t.groupCode <> ''
|
||||||
|
GROUP BY t.matterId, t.groupCode
|
||||||
|
) gs ON gs.matterId = m.id
|
||||||
$condition
|
$condition
|
||||||
""");
|
""");
|
||||||
countSql.setCondition(cnd);
|
countSql.setCondition(cnd);
|
||||||
@@ -74,14 +90,14 @@ public class TourUndertakeController {
|
|||||||
m.id AS matterId,
|
m.id AS matterId,
|
||||||
m.`year`,
|
m.`year`,
|
||||||
m.matterName,
|
m.matterName,
|
||||||
m.unionId,
|
gs.undertakeUnionId AS unionId,
|
||||||
|
COALESCE(NULLIF(gs.undertakeUnionName, ''), uu.name, '') AS unionName,
|
||||||
CASE
|
CASE
|
||||||
WHEN IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') = 'claimed' THEN COALESCE(u.name, '校工会')
|
WHEN IFNULL(NULLIF(gs.undertakeUnionId, ''), '') <> '' THEN 'claimed'
|
||||||
ELSE ''
|
ELSE 'unclaimed'
|
||||||
END AS unionName,
|
END AS undertakeStatus,
|
||||||
IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') AS undertakeStatus,
|
|
||||||
CASE
|
CASE
|
||||||
WHEN IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') = 'claimed' THEN '已领'
|
WHEN IFNULL(NULLIF(gs.undertakeUnionId, ''), '') <> '' THEN '已领'
|
||||||
ELSE '未领'
|
ELSE '未领'
|
||||||
END AS undertakeStatusName,
|
END AS undertakeStatusName,
|
||||||
m.lineId,
|
m.lineId,
|
||||||
@@ -95,25 +111,20 @@ public class TourUndertakeController {
|
|||||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||||
ELSE ''
|
ELSE ''
|
||||||
END AS travelPeriod,
|
END AS travelPeriod,
|
||||||
m.minGroupPeople,
|
gs.groupCode,
|
||||||
m.maxGroupPeople,
|
gs.groupName,
|
||||||
IFNULL(sc.signupCount, 0) AS signupCount,
|
gs.signupCount,
|
||||||
IFNULL(usc.currentUnionSignupCount, 0) AS currentUnionSignupCount,
|
IFNULL(ugs.currentUnionSignupCount, 0) AS currentUnionSignupCount
|
||||||
CASE
|
|
||||||
WHEN m.maxGroupPeople IS NOT NULL AND m.maxGroupPeople > 0 AND IFNULL(sc.signupCount, 0) > m.maxGroupPeople THEN 'over'
|
|
||||||
WHEN m.minGroupPeople IS NOT NULL AND m.minGroupPeople > 0 AND IFNULL(sc.signupCount, 0) >= m.minGroupPeople THEN 'formed'
|
|
||||||
ELSE 'unformed'
|
|
||||||
END AS groupStatus,
|
|
||||||
CASE
|
|
||||||
WHEN m.maxGroupPeople IS NOT NULL AND m.maxGroupPeople > 0 AND IFNULL(sc.signupCount, 0) > m.maxGroupPeople THEN '超员'
|
|
||||||
WHEN m.minGroupPeople IS NOT NULL AND m.minGroupPeople > 0 AND IFNULL(sc.signupCount, 0) >= m.minGroupPeople THEN '已成团'
|
|
||||||
ELSE '未成团'
|
|
||||||
END AS groupStatusName
|
|
||||||
FROM tour_matter m
|
FROM tour_matter m
|
||||||
INNER JOIN tour_line l ON l.id = m.lineId
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
LEFT JOIN sys_union u ON u.id = m.unionId
|
INNER JOIN (
|
||||||
LEFT JOIN (
|
SELECT
|
||||||
SELECT t.matterId, SUM(1 + IFNULL(f.familyCount, 0)) AS signupCount
|
t.matterId,
|
||||||
|
t.groupCode,
|
||||||
|
MAX(t.groupName) AS groupName,
|
||||||
|
MAX(t.undertakeUnionId) AS undertakeUnionId,
|
||||||
|
MAX(t.undertakeUnionName) AS undertakeUnionName,
|
||||||
|
SUM(1 + IFNULL(f.familyCount, 0)) AS signupCount
|
||||||
FROM tour_ledger t
|
FROM tour_ledger t
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT ledgerId, COUNT(1) AS familyCount
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
@@ -122,12 +133,16 @@ public class TourUndertakeController {
|
|||||||
GROUP BY ledgerId
|
GROUP BY ledgerId
|
||||||
) f ON f.ledgerId = t.id
|
) f ON f.ledgerId = t.id
|
||||||
WHERE t.delFlag = 0
|
WHERE t.delFlag = 0
|
||||||
AND t.matterId IS NOT NULL
|
AND t.groupCode IS NOT NULL
|
||||||
AND t.matterId <> ''
|
AND t.groupCode <> ''
|
||||||
GROUP BY t.matterId
|
GROUP BY t.matterId, t.groupCode
|
||||||
) sc ON sc.matterId = m.id
|
) gs ON gs.matterId = m.id
|
||||||
|
LEFT JOIN sys_union uu ON uu.id = gs.undertakeUnionId
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT t.matterId, SUM(1 + IFNULL(f.familyCount, 0)) AS currentUnionSignupCount
|
SELECT
|
||||||
|
t.matterId,
|
||||||
|
t.groupCode,
|
||||||
|
SUM(1 + IFNULL(f.familyCount, 0)) AS currentUnionSignupCount
|
||||||
FROM tour_ledger t
|
FROM tour_ledger t
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT ledgerId, COUNT(1) AS familyCount
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
@@ -136,13 +151,13 @@ public class TourUndertakeController {
|
|||||||
GROUP BY ledgerId
|
GROUP BY ledgerId
|
||||||
) f ON f.ledgerId = t.id
|
) f ON f.ledgerId = t.id
|
||||||
WHERE t.delFlag = 0
|
WHERE t.delFlag = 0
|
||||||
AND t.matterId IS NOT NULL
|
AND t.groupCode IS NOT NULL
|
||||||
AND t.matterId <> ''
|
AND t.groupCode <> ''
|
||||||
AND t.unionId = @currentUnionId
|
AND t.unionId = @currentUnionId
|
||||||
GROUP BY t.matterId
|
GROUP BY t.matterId, t.groupCode
|
||||||
) usc ON usc.matterId = m.id
|
) ugs ON ugs.matterId = m.id AND ugs.groupCode = gs.groupCode
|
||||||
$condition
|
$condition
|
||||||
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC
|
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, gs.groupCode ASC, m.createdAt DESC
|
||||||
""");
|
""");
|
||||||
listSql.setCondition(cnd);
|
listSql.setCondition(cnd);
|
||||||
listSql.setParam("currentUnionId", currentUnionId);
|
listSql.setParam("currentUnionId", currentUnionId);
|
||||||
@@ -180,49 +195,35 @@ public class TourUndertakeController {
|
|||||||
return Result.success(unionInfo);
|
return Result.success(unionInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 领办当前成团行,接口必须同时接收事项ID和成团编码,避免按事项误领办全部团。
|
||||||
|
*/
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("tour.undertake")
|
@SaCheckPermission("tour.undertake")
|
||||||
public Result undertakeLine(String matterId) {
|
public Result undertakeLine(String matterId, String groupCode) {
|
||||||
if (StrUtil.isBlank(matterId)) {
|
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
|
||||||
return Result.error("参数错误");
|
return Result.error("参数错误");
|
||||||
}
|
}
|
||||||
NutMap unionInfo = currentUnionInfo();
|
NutMap unionInfo = currentUnionInfo();
|
||||||
if (unionInfo.isEmpty()) {
|
if (unionInfo.isEmpty()) {
|
||||||
return Result.error("未获取到当前登录人工会");
|
return Result.error("未获取到当前登录人工会");
|
||||||
}
|
}
|
||||||
TourMatter matter = tourMatterService.fetch(matterId);
|
return tourLedgerService.undertakeGroup(matterId, groupCode, unionInfo.getString("unionId"), unionInfo.getString("unionName"));
|
||||||
if (matter == null || Boolean.TRUE.equals(matter.getDelFlag())) {
|
|
||||||
return Result.error("事项不存在");
|
|
||||||
}
|
|
||||||
if ("claimed".equals(matter.getUndertakeStatus())) {
|
|
||||||
return Result.error("当前线路已领办,不允许修改");
|
|
||||||
}
|
|
||||||
matter.setUnionId(unionInfo.getString("unionId"));
|
|
||||||
matter.setUndertakeStatus("claimed");
|
|
||||||
tourMatterService.update(matter);
|
|
||||||
return Result.success(unionInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解除当前成团行的领办信息,清空范围只限定到当前事项的当前团。
|
||||||
|
*/
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("tour.undertake")
|
@SaCheckPermission("tour.undertake")
|
||||||
public Result releaseUndertakeLine(String matterId) {
|
public Result releaseUndertakeLine(String matterId, String groupCode) {
|
||||||
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
|
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
|
||||||
return Result.error("仅系统管理员可以解除领办");
|
return Result.error("仅系统管理员可以解除领办");
|
||||||
}
|
}
|
||||||
if (StrUtil.isBlank(matterId)) {
|
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
|
||||||
return Result.error("参数错误");
|
return Result.error("参数错误");
|
||||||
}
|
}
|
||||||
TourMatter matter = tourMatterService.fetch(matterId);
|
return tourLedgerService.releaseUndertakeGroup(matterId, groupCode);
|
||||||
if (matter == null || Boolean.TRUE.equals(matter.getDelFlag())) {
|
|
||||||
return Result.error("事项不存在");
|
|
||||||
}
|
|
||||||
if (!"claimed".equals(matter.getUndertakeStatus())) {
|
|
||||||
return Result.error("当前线路未领办,无需解除");
|
|
||||||
}
|
|
||||||
matter.setUnionId("");
|
|
||||||
matter.setUndertakeStatus("unclaimed");
|
|
||||||
tourMatterService.update(matter);
|
|
||||||
return Result.success();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@@ -259,20 +260,30 @@ public class TourUndertakeController {
|
|||||||
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("tour.undertake")
|
@SaCheckPermission("tour.undertake")
|
||||||
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId) {
|
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId, String groupCode) {
|
||||||
if (StrUtil.isBlank(matterId)) {
|
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
|
||||||
return Result.error("参数错误");
|
return Result.error("参数错误");
|
||||||
}
|
}
|
||||||
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||||
pageForm.defaultSortDesc("signupTime");
|
pageForm.defaultSortAsc("signupTime");
|
||||||
}
|
}
|
||||||
Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId);
|
Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId, groupCode);
|
||||||
Sql countSql = Sqls.create("""
|
Sql countSql = Sqls.create("""
|
||||||
SELECT COUNT(1)
|
SELECT COUNT(1)
|
||||||
FROM tour_ledger t
|
FROM (
|
||||||
INNER JOIN tour_matter m ON m.id = t.matterId
|
SELECT t.id AS rowId
|
||||||
INNER JOIN tour_line l ON l.id = m.lineId
|
FROM tour_ledger t
|
||||||
$condition
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
$condition
|
||||||
|
UNION ALL
|
||||||
|
SELECT f.id AS rowId
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
INNER JOIN tour_ledger_family f ON f.ledgerId = t.id AND f.delFlag = 0
|
||||||
|
$condition
|
||||||
|
) p
|
||||||
""");
|
""");
|
||||||
countSql.setCondition(cnd);
|
countSql.setCondition(cnd);
|
||||||
countSql.setCallback(Sqls.callback.integer());
|
countSql.setCallback(Sqls.callback.integer());
|
||||||
@@ -280,23 +291,63 @@ public class TourUndertakeController {
|
|||||||
int count = countSql.getInt();
|
int count = countSql.getInt();
|
||||||
|
|
||||||
Sql listSql = Sqls.create("""
|
Sql listSql = Sqls.create("""
|
||||||
SELECT
|
SELECT *
|
||||||
t.id,
|
FROM (
|
||||||
t.jobNo,
|
SELECT
|
||||||
t.userName,
|
t.id,
|
||||||
'' AS mobile,
|
t.id AS ledgerId,
|
||||||
t.idCard,
|
t.jobNo,
|
||||||
t.unionId,
|
t.userName,
|
||||||
t.unionName,
|
t.gender,
|
||||||
t.signupTime
|
'' AS mobile,
|
||||||
FROM tour_ledger t
|
t.idCard,
|
||||||
INNER JOIN tour_matter m ON m.id = t.matterId
|
t.unionId,
|
||||||
INNER JOIN tour_line l ON l.id = m.lineId
|
t.unionName,
|
||||||
$condition
|
t.signupTime,
|
||||||
ORDER BY $orderColumn $orderBy, t.signupTime DESC, t.createdAt DESC
|
t.groupCode,
|
||||||
|
t.groupName,
|
||||||
|
1 AS staffRow,
|
||||||
|
'教职工' AS rowTypeName,
|
||||||
|
'' AS relationship,
|
||||||
|
t.userName AS relatedStaffName,
|
||||||
|
0 AS rowSort,
|
||||||
|
t.createdAt AS ledgerCreatedAt,
|
||||||
|
t.createdAt
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
$condition
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
f.id,
|
||||||
|
t.id AS ledgerId,
|
||||||
|
'' AS jobNo,
|
||||||
|
f.familyName AS userName,
|
||||||
|
f.gender,
|
||||||
|
'' AS mobile,
|
||||||
|
f.idCard,
|
||||||
|
t.unionId,
|
||||||
|
'' AS unionName,
|
||||||
|
t.signupTime,
|
||||||
|
t.groupCode,
|
||||||
|
t.groupName,
|
||||||
|
0 AS staffRow,
|
||||||
|
'家属' AS rowTypeName,
|
||||||
|
f.relationship,
|
||||||
|
t.userName AS relatedStaffName,
|
||||||
|
1 AS rowSort,
|
||||||
|
t.createdAt AS ledgerCreatedAt,
|
||||||
|
f.createdAt
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
INNER JOIN tour_ledger_family f ON f.ledgerId = t.id AND f.delFlag = 0
|
||||||
|
$condition
|
||||||
|
) p
|
||||||
|
ORDER BY $orderColumn $orderBy, p.signupTime ASC, p.ledgerCreatedAt ASC, p.rowSort ASC, p.createdAt ASC
|
||||||
""");
|
""");
|
||||||
listSql.setCondition(cnd);
|
listSql.setCondition(cnd);
|
||||||
listSql.setVar("orderColumn", getSignupOrderColumn(pageForm.getPageOrderName()));
|
listSql.setVar("orderColumn", getSignupUnionOrderColumn(pageForm.getPageOrderName()));
|
||||||
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
|
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
|
||||||
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||||
listSql.setCallback(Sqls.callback.maps());
|
listSql.setCallback(Sqls.callback.maps());
|
||||||
@@ -304,15 +355,138 @@ public class TourUndertakeController {
|
|||||||
|
|
||||||
List<NutMap> list = listSql.getList(NutMap.class);
|
List<NutMap> list = listSql.getList(NutMap.class);
|
||||||
fillSignupMobile(list);
|
fillSignupMobile(list);
|
||||||
fillSignupFamilies(list);
|
|
||||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||||
return Result.success(pagination);
|
return Result.success(pagination);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId) {
|
/**
|
||||||
|
* 导出领办查看弹窗中的报名人员数据,导出范围复用查看弹窗的事项、成团、关键字和工会条件。
|
||||||
|
*/
|
||||||
|
@At
|
||||||
|
@SaCheckPermission("tour.undertake")
|
||||||
|
@Ok("void")
|
||||||
|
public void exportSignupData(PageForm pageForm, String matterId, String keyword, String unionId, String groupCode,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||||
|
pageForm.defaultSortAsc("signupTime");
|
||||||
|
}
|
||||||
|
// 导出不分页,保持筛选、排序、手机号补充和家属平铺逻辑与查看弹窗一致。
|
||||||
|
List<NutMap> rows = querySignupExportRows(pageForm, matterId, keyword, unionId, groupCode);
|
||||||
|
List<ExcelExportEntity> entities = buildSignupExportEntities();
|
||||||
|
|
||||||
|
ExportParams exportParams = new ExportParams();
|
||||||
|
exportParams.setType(ExcelType.XSSF);
|
||||||
|
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, rows);
|
||||||
|
CommonDownloadUtil.download("领办报名人员.xlsx", workbook, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询领办查看弹窗导出数据,复用查看列表的权限范围和人员组装逻辑,仅去掉分页限制。
|
||||||
|
*/
|
||||||
|
private List<NutMap> querySignupExportRows(PageForm pageForm, String matterId, String keyword, String unionId, String groupCode) {
|
||||||
|
Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId, groupCode);
|
||||||
|
Sql listSql = Sqls.create("""
|
||||||
|
SELECT *
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.id AS ledgerId,
|
||||||
|
t.jobNo,
|
||||||
|
t.userName,
|
||||||
|
t.gender,
|
||||||
|
'' AS mobile,
|
||||||
|
t.idCard,
|
||||||
|
t.unionId,
|
||||||
|
t.unionName,
|
||||||
|
t.signupTime,
|
||||||
|
t.groupCode,
|
||||||
|
t.groupName,
|
||||||
|
1 AS staffRow,
|
||||||
|
'教职工' AS rowTypeName,
|
||||||
|
'' AS relationship,
|
||||||
|
t.userName AS relatedStaffName,
|
||||||
|
0 AS rowSort,
|
||||||
|
t.createdAt AS ledgerCreatedAt,
|
||||||
|
t.createdAt
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
$condition
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
f.id,
|
||||||
|
t.id AS ledgerId,
|
||||||
|
'' AS jobNo,
|
||||||
|
f.familyName AS userName,
|
||||||
|
f.gender,
|
||||||
|
'' AS mobile,
|
||||||
|
f.idCard,
|
||||||
|
t.unionId,
|
||||||
|
'' AS unionName,
|
||||||
|
t.signupTime,
|
||||||
|
t.groupCode,
|
||||||
|
t.groupName,
|
||||||
|
0 AS staffRow,
|
||||||
|
'家属' AS rowTypeName,
|
||||||
|
f.relationship,
|
||||||
|
t.userName AS relatedStaffName,
|
||||||
|
1 AS rowSort,
|
||||||
|
t.createdAt AS ledgerCreatedAt,
|
||||||
|
f.createdAt
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
INNER JOIN tour_ledger_family f ON f.ledgerId = t.id AND f.delFlag = 0
|
||||||
|
$condition
|
||||||
|
) p
|
||||||
|
ORDER BY $orderColumn $orderBy, p.signupTime ASC, p.ledgerCreatedAt ASC, p.rowSort ASC, p.createdAt ASC
|
||||||
|
""");
|
||||||
|
listSql.setCondition(cnd);
|
||||||
|
listSql.setVar("orderColumn", getSignupUnionOrderColumn(pageForm.getPageOrderName()));
|
||||||
|
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
|
||||||
|
listSql.setCallback(Sqls.callback.maps());
|
||||||
|
tourLedgerService.dao().execute(listSql);
|
||||||
|
|
||||||
|
List<NutMap> list = listSql.getList(NutMap.class);
|
||||||
|
fillSignupMobile(list);
|
||||||
|
// 补齐导出专用序号和成团展示字段,确保 Excel 列显示与查看弹窗一致。
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
NutMap item = list.get(i);
|
||||||
|
item.put("indexNo", i + 1);
|
||||||
|
String groupCodeText = item.getString("groupCode", "");
|
||||||
|
String groupNameText = item.getString("groupName", "");
|
||||||
|
item.put("groupInfo", StrUtil.isBlank(groupCodeText) ? "未成团" : groupCodeText + (StrUtil.isBlank(groupNameText) ? "" : " " + groupNameText));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义领办查看弹窗导出列,字段顺序与弹窗表格保持一致。
|
||||||
|
*/
|
||||||
|
private List<ExcelExportEntity> buildSignupExportEntities() {
|
||||||
|
List<ExcelExportEntity> entities = new ArrayList<>();
|
||||||
|
entities.add(new ExcelExportEntity("序号", "indexNo", 10));
|
||||||
|
entities.add(new ExcelExportEntity("工号", "jobNo", 14));
|
||||||
|
entities.add(new ExcelExportEntity("姓名", "userName", 14));
|
||||||
|
entities.add(new ExcelExportEntity("性别", "gender", 10));
|
||||||
|
entities.add(new ExcelExportEntity("手机号码", "mobile", 18));
|
||||||
|
entities.add(new ExcelExportEntity("身份证号", "idCard", 24));
|
||||||
|
entities.add(new ExcelExportEntity("所属工会", "unionName", 24));
|
||||||
|
entities.add(new ExcelExportEntity("人员类型", "rowTypeName", 12));
|
||||||
|
entities.add(new ExcelExportEntity("亲属关系", "relationship", 14));
|
||||||
|
entities.add(new ExcelExportEntity("成团信息", "groupInfo", 24));
|
||||||
|
entities.add(new ExcelExportEntity("关联教职工名称", "relatedStaffName", 18));
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 领办线路查看以团为单位,报名人员查询必须固定到当前点击的成团编码。
|
||||||
|
*/
|
||||||
|
private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId, String groupCode) {
|
||||||
Cnd cnd = Cnd.NEW();
|
Cnd cnd = Cnd.NEW();
|
||||||
cnd.and("t.delFlag", "=", false);
|
cnd.and("t.delFlag", "=", false);
|
||||||
cnd.and("t.matterId", "=", matterId);
|
cnd.and("t.matterId", "=", matterId);
|
||||||
|
cnd.and("t.groupCode", "=", groupCode);
|
||||||
cnd.and("m.delFlag", "=", false);
|
cnd.and("m.delFlag", "=", false);
|
||||||
cnd.and("m.enabled", "=", true);
|
cnd.and("m.enabled", "=", true);
|
||||||
cnd.and("m.lineId", "IS NOT", null);
|
cnd.and("m.lineId", "IS NOT", null);
|
||||||
@@ -360,9 +534,9 @@ public class TourUndertakeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查看成团报名人员时按台账ID带出家属子列表,避免前端逐行请求家属信息。
|
* 查看单个团报名人员时将教职工和家属平铺到同一列表,便于领办线路复用线路成团的展示方式。
|
||||||
*/
|
*/
|
||||||
private void fillSignupFamilies(List<NutMap> list) {
|
private void flattenSignupFamilies(List<NutMap> list) {
|
||||||
if (list == null || list.isEmpty()) {
|
if (list == null || list.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -380,8 +554,9 @@ public class TourUndertakeController {
|
|||||||
Sql sql = Sqls.create("""
|
Sql sql = Sqls.create("""
|
||||||
SELECT
|
SELECT
|
||||||
ledgerId,
|
ledgerId,
|
||||||
staffJobNo AS jobNo,
|
'' AS jobNo,
|
||||||
familyName AS userName,
|
familyName AS userName,
|
||||||
|
gender,
|
||||||
'' AS mobile,
|
'' AS mobile,
|
||||||
idCard,
|
idCard,
|
||||||
relationship
|
relationship
|
||||||
@@ -396,11 +571,29 @@ public class TourUndertakeController {
|
|||||||
|
|
||||||
Map<String, List<NutMap>> familyMap = new HashMap<>();
|
Map<String, List<NutMap>> familyMap = new HashMap<>();
|
||||||
for (NutMap family : sql.getList(NutMap.class)) {
|
for (NutMap family : sql.getList(NutMap.class)) {
|
||||||
|
family.put("staffRow", false);
|
||||||
|
family.put("rowTypeName", "家属");
|
||||||
|
family.put("unionName", "");
|
||||||
familyMap.computeIfAbsent(family.getString("ledgerId", ""), key -> new ArrayList<>()).add(family);
|
familyMap.computeIfAbsent(family.getString("ledgerId", ""), key -> new ArrayList<>()).add(family);
|
||||||
}
|
}
|
||||||
|
List<NutMap> flatList = new ArrayList<>();
|
||||||
for (NutMap item : list) {
|
for (NutMap item : list) {
|
||||||
item.put("families", familyMap.getOrDefault(item.getString("id", ""), List.of()));
|
item.put("staffRow", true);
|
||||||
|
item.put("rowTypeName", "教职工");
|
||||||
|
item.put("relationship", "");
|
||||||
|
item.put("relatedStaffName", item.getString("userName", ""));
|
||||||
|
flatList.add(item);
|
||||||
|
List<NutMap> families = familyMap.getOrDefault(item.getString("id", ""), List.of());
|
||||||
|
for (NutMap family : families) {
|
||||||
|
// 家属行继承教职工成团信息,并标明关联教职工名称。
|
||||||
|
family.put("relatedStaffName", item.getString("userName", ""));
|
||||||
|
family.put("groupCode", item.getString("groupCode", ""));
|
||||||
|
family.put("groupName", item.getString("groupName", ""));
|
||||||
|
}
|
||||||
|
flatList.addAll(families);
|
||||||
}
|
}
|
||||||
|
list.clear();
|
||||||
|
list.addAll(flatList);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId, String undertakeStatus) {
|
private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId, String undertakeStatus) {
|
||||||
@@ -414,14 +607,14 @@ public class TourUndertakeController {
|
|||||||
cnd.andEX("m.`year`", "=", year == null ? LocalDate.now().getYear() : year);
|
cnd.andEX("m.`year`", "=", year == null ? LocalDate.now().getYear() : year);
|
||||||
cnd.and(Cnd.likeEX("l.lineName", lineName));
|
cnd.and(Cnd.likeEX("l.lineName", lineName));
|
||||||
cnd.andEX("l.lineType", "=", lineType);
|
cnd.andEX("l.lineType", "=", lineType);
|
||||||
cnd.andEX("m.unionId", "=", unionId);
|
cnd.andEX("gs.undertakeUnionId", "=", unionId);
|
||||||
if ("claimed".equals(undertakeStatus)) {
|
if ("claimed".equals(undertakeStatus)) {
|
||||||
cnd.and("m.undertakeStatus", "=", "claimed");
|
cnd.and("gs.undertakeUnionId", "IS NOT", null);
|
||||||
|
cnd.and("gs.undertakeUnionId", "<>", "");
|
||||||
} else if ("unclaimed".equals(undertakeStatus)) {
|
} else if ("unclaimed".equals(undertakeStatus)) {
|
||||||
SqlExpressionGroup statusGroup = new SqlExpressionGroup();
|
SqlExpressionGroup statusGroup = new SqlExpressionGroup();
|
||||||
statusGroup.or("m.undertakeStatus", "=", "unclaimed");
|
statusGroup.or("gs.undertakeUnionId", "IS", null);
|
||||||
statusGroup.or("m.undertakeStatus", "IS", null);
|
statusGroup.or("gs.undertakeUnionId", "=", "");
|
||||||
statusGroup.or("m.undertakeStatus", "=", "");
|
|
||||||
cnd.and(statusGroup);
|
cnd.and(statusGroup);
|
||||||
}
|
}
|
||||||
applyMatterDataScope(cnd);
|
applyMatterDataScope(cnd);
|
||||||
@@ -432,7 +625,9 @@ public class TourUndertakeController {
|
|||||||
* 线路成团按事项归属控制数据范围:校工会查看全部;分工会查看本工会事项;其它角色仅查看自己创建的事项。
|
* 线路成团按事项归属控制数据范围:校工会查看全部;分工会查看本工会事项;其它角色仅查看自己创建的事项。
|
||||||
*/
|
*/
|
||||||
private void applyMatterDataScope(Cnd cnd) {
|
private void applyMatterDataScope(Cnd cnd) {
|
||||||
if (hasSchoolUnionScope() || AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
|
if (hasSchoolUnionScope()
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())
|
||||||
|
|| hasTourApplicantScope()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (hasBranchUnionScope()) {
|
if (hasBranchUnionScope()) {
|
||||||
@@ -460,6 +655,13 @@ public class TourUndertakeController {
|
|||||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name());
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 疗休养领办人只扩大领办线路查询范围,不改变领办、解除等操作权限。
|
||||||
|
*/
|
||||||
|
private boolean hasTourApplicantScope() {
|
||||||
|
return AuthUtil.hasRole(RoleConstant.TOUR_APPLICANT.name());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasBranchUnionScope() {
|
private boolean hasBranchUnionScope() {
|
||||||
return AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ADMIN.name())
|
return AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ADMIN.name())
|
||||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())
|
||||||
@@ -489,7 +691,7 @@ public class TourUndertakeController {
|
|||||||
return "m.matterName";
|
return "m.matterName";
|
||||||
}
|
}
|
||||||
if ("unionName".equals(orderName)) {
|
if ("unionName".equals(orderName)) {
|
||||||
return "u.name";
|
return "unionName";
|
||||||
}
|
}
|
||||||
if ("undertakeStatusName".equals(orderName)) {
|
if ("undertakeStatusName".equals(orderName)) {
|
||||||
return "undertakeStatus";
|
return "undertakeStatus";
|
||||||
@@ -498,33 +700,33 @@ public class TourUndertakeController {
|
|||||||
return "l.lineType";
|
return "l.lineType";
|
||||||
}
|
}
|
||||||
if ("signupCount".equals(orderName)) {
|
if ("signupCount".equals(orderName)) {
|
||||||
return "signupCount";
|
return "gs.signupCount";
|
||||||
}
|
}
|
||||||
if ("minGroupPeople".equals(orderName)) {
|
if ("groupName".equals(orderName) || "groupCode".equals(orderName)) {
|
||||||
return "m.minGroupPeople";
|
return "gs.groupCode";
|
||||||
}
|
|
||||||
if ("maxGroupPeople".equals(orderName)) {
|
|
||||||
return "m.maxGroupPeople";
|
|
||||||
}
|
|
||||||
if ("groupStatusName".equals(orderName)) {
|
|
||||||
return "groupStatus";
|
|
||||||
}
|
}
|
||||||
return "l.lineName";
|
return "l.lineName";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSignupOrderColumn(String orderName) {
|
private String getSignupUnionOrderColumn(String orderName) {
|
||||||
if ("jobNo".equals(orderName)) {
|
if ("jobNo".equals(orderName)) {
|
||||||
return "t.jobNo";
|
return "p.jobNo";
|
||||||
}
|
}
|
||||||
if ("userName".equals(orderName)) {
|
if ("userName".equals(orderName)) {
|
||||||
return "t.userName";
|
return "p.userName";
|
||||||
|
}
|
||||||
|
if ("gender".equals(orderName)) {
|
||||||
|
return "p.gender";
|
||||||
}
|
}
|
||||||
if ("idCard".equals(orderName)) {
|
if ("idCard".equals(orderName)) {
|
||||||
return "t.idCard";
|
return "p.idCard";
|
||||||
}
|
}
|
||||||
if ("unionName".equals(orderName)) {
|
if ("unionName".equals(orderName)) {
|
||||||
return "t.unionName";
|
return "p.unionName";
|
||||||
}
|
}
|
||||||
return "t.signupTime";
|
if ("groupCode".equals(orderName) || "groupName".equals(orderName)) {
|
||||||
|
return "p.groupCode";
|
||||||
|
}
|
||||||
|
return "p.signupTime";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-24
@@ -97,18 +97,7 @@ public class TourUnionApprovalController {
|
|||||||
task.taskParentId,
|
task.taskParentId,
|
||||||
task.variable AS taskVariable,
|
task.variable AS taskVariable,
|
||||||
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS curTaskName,
|
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS curTaskName,
|
||||||
CASE
|
COALESCE(NULLIF(l.lineName, ''), t.lineName) AS currentLineName,
|
||||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
|
||||||
ELSE CONCAT(
|
|
||||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
|
||||||
'(',
|
|
||||||
CASE
|
|
||||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
|
||||||
ELSE IFNULL(mu.name, '')
|
|
||||||
END,
|
|
||||||
')'
|
|
||||||
)
|
|
||||||
END AS currentLineName,
|
|
||||||
CASE
|
CASE
|
||||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||||
@@ -479,18 +468,7 @@ public class TourUnionApprovalController {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
Sql sql = Sqls.create("""
|
Sql sql = Sqls.create("""
|
||||||
SELECT CASE
|
SELECT COALESCE(NULLIF(l.lineName, ''), t.lineName) AS lineName
|
||||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
|
||||||
ELSE CONCAT(
|
|
||||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
|
||||||
'(',
|
|
||||||
CASE
|
|
||||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
|
||||||
ELSE IFNULL(mu.name, '')
|
|
||||||
END,
|
|
||||||
')'
|
|
||||||
)
|
|
||||||
END AS lineName
|
|
||||||
FROM tour_ledger t
|
FROM tour_ledger t
|
||||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||||
|
|||||||
+2
-24
@@ -94,18 +94,7 @@ public class TourUnionLedgerController {
|
|||||||
ins.processDefineId AS instanceProcessDefineId,
|
ins.processDefineId AS instanceProcessDefineId,
|
||||||
IFNULL(l.directFamilyUnitLine, 0) AS directFamilyUnitLine,
|
IFNULL(l.directFamilyUnitLine, 0) AS directFamilyUnitLine,
|
||||||
IFNULL(GROUP_CONCAT(DISTINCT task.displayName), IF(ins.id IS NULL, '', '结束')) AS curTaskName,
|
IFNULL(GROUP_CONCAT(DISTINCT task.displayName), IF(ins.id IS NULL, '', '结束')) AS curTaskName,
|
||||||
CASE
|
COALESCE(NULLIF(l.lineName, ''), t.lineName) AS currentLineName,
|
||||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
|
||||||
ELSE CONCAT(
|
|
||||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
|
||||||
'(',
|
|
||||||
CASE
|
|
||||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
|
||||||
ELSE IFNULL(mu.name, '')
|
|
||||||
END,
|
|
||||||
')'
|
|
||||||
)
|
|
||||||
END AS currentLineName,
|
|
||||||
IFNULL(f.familyCount, 0) AS familyCount,
|
IFNULL(f.familyCount, 0) AS familyCount,
|
||||||
CASE
|
CASE
|
||||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||||
@@ -804,18 +793,7 @@ public class TourUnionLedgerController {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
Sql sql = Sqls.create("""
|
Sql sql = Sqls.create("""
|
||||||
SELECT CASE
|
SELECT COALESCE(NULLIF(l.lineName, ''), t.lineName) AS lineName
|
||||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
|
||||||
ELSE CONCAT(
|
|
||||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
|
||||||
'(',
|
|
||||||
CASE
|
|
||||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
|
||||||
ELSE IFNULL(mu.name, '')
|
|
||||||
END,
|
|
||||||
')'
|
|
||||||
)
|
|
||||||
END AS lineName
|
|
||||||
FROM tour_ledger t
|
FROM tour_ledger t
|
||||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||||
|
|||||||
@@ -101,6 +101,32 @@ public class TourLedger extends BaseModel implements Serializable {
|
|||||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||||
private String lineType;
|
private String lineType;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("成团编码")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||||
|
private String groupCode;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("成团名称")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 领办工会编码按团回填到台账,避免领办动作再修改事项归属工会。
|
||||||
|
*/
|
||||||
|
@Column
|
||||||
|
@Comment("领办工会编码")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||||
|
private String undertakeUnionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 领办工会名称与编码一起落台账,列表按团展示时直接读取当前团领办信息。
|
||||||
|
*/
|
||||||
|
@Column
|
||||||
|
@Comment("领办工会")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||||
|
private String undertakeUnionName;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
@Comment("报名酒店")
|
@Comment("报名酒店")
|
||||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ public class TourLine extends BaseModel implements Serializable {
|
|||||||
|
|
||||||
@Column
|
@Column
|
||||||
@Comment("排序编号")
|
@Comment("排序编号")
|
||||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
@ColDefine(type = ColType.INT, width = 11)
|
||||||
private String lineCode;
|
private Integer lineCode;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
@Comment("线路名称")
|
@Comment("线路名称")
|
||||||
|
|||||||
@@ -117,6 +117,11 @@ public class TourMatter extends BaseModel implements Serializable {
|
|||||||
@ColDefine(type = ColType.INT)
|
@ColDefine(type = ColType.INT)
|
||||||
private Integer maxGroupPeople;
|
private Integer maxGroupPeople;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("最多报名人数")
|
||||||
|
@ColDefine(type = ColType.INT)
|
||||||
|
private Integer maxSignupPeople;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
@Comment("预计费用")
|
@Comment("预计费用")
|
||||||
@ColDefine(customType = "decimal(10,2)")
|
@ColDefine(customType = "decimal(10,2)")
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.budwk.app.zhgh.dayofficework.tour.models;
|
||||||
|
|
||||||
|
import com.budwk.app.base.model.BaseModel;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.nutz.dao.entity.annotation.ColDefine;
|
||||||
|
import org.nutz.dao.entity.annotation.ColType;
|
||||||
|
import org.nutz.dao.entity.annotation.Column;
|
||||||
|
import org.nutz.dao.entity.annotation.Comment;
|
||||||
|
import org.nutz.dao.entity.annotation.Default;
|
||||||
|
import org.nutz.dao.entity.annotation.Name;
|
||||||
|
import org.nutz.dao.entity.annotation.Table;
|
||||||
|
import org.nutz.dao.entity.annotation.TableMeta;
|
||||||
|
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tour participant roster stores every eligible staff member for a tour setting.
|
||||||
|
* It is used for joined/not-joined statistics and links to tour_ledger only when the user has a signup record.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Table("tour_participant_roster")
|
||||||
|
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||||
|
@Comment("疗休养人员名单")
|
||||||
|
public class TourParticipantRoster extends BaseModel implements Serializable {
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Name
|
||||||
|
@Comment("ID")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||||
|
@PrevInsert(uu32 = true)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("疗休养配置ID")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||||
|
private String settingId;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("工号")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||||
|
private String jobNo;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("姓名")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("所在单位ID")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||||
|
private String unitId;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("所在单位")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||||
|
private String unitName;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("所在工会ID")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||||
|
private String unionId;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("所在工会")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||||
|
private String unionName;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("是否参加")
|
||||||
|
@ColDefine(type = ColType.BOOLEAN)
|
||||||
|
@Default("0")
|
||||||
|
private Boolean joined;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("疗休养台账ID")
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||||
|
private String ledgerId;
|
||||||
|
}
|
||||||
@@ -1,7 +1,101 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.tour.service;
|
package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||||
|
|
||||||
import com.budwk.app.base.service.BaseService;
|
import com.budwk.app.base.service.BaseService;
|
||||||
|
import com.budwk.app.base.result.Result;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface TourLedgerService extends BaseService<TourLedger> {
|
public interface TourLedgerService extends BaseService<TourLedger> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按查询条件获取疗休养台账中已分配的成团编码和成团名称,返回值已按成团信息去重。
|
||||||
|
*
|
||||||
|
* @param cnd 已包含事项、线路、台账状态和数据权限的查询条件
|
||||||
|
* @return 去重后的成团信息列表
|
||||||
|
*/
|
||||||
|
List<NutMap> queryGroupOptions(Cnd cnd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按疗休养台账页面的完整查询条件获取导出数据,不进行分页。
|
||||||
|
*
|
||||||
|
* @param cnd 台账查询条件,包含年度范围、姓名或工号、所属工会、线路、出行时段、线路类型、
|
||||||
|
* 直系亲属单位线路人员、超出费用由单位承担人员及审批完成状态限制
|
||||||
|
* @return 台账导出数据列表;每项包含年度、人员、线路、成团、领办工会、报名、家属及参加状态信息
|
||||||
|
*/
|
||||||
|
List<NutMap> queryExportRows(Cnd cnd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按事项最大成团人数,将当前事项下未成团的报名教职工及家属整体自动分配到成团中。
|
||||||
|
*
|
||||||
|
* @param matterId 疗休养事项ID
|
||||||
|
* @return 分配结果,包含成功提示或具体业务校验错误
|
||||||
|
*/
|
||||||
|
Result assignGroup(String matterId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将选中的教职工报名台账调整到指定成团中,并校验调整后目标团人数不超过事项最大成团人数。
|
||||||
|
*
|
||||||
|
* @param matterId 当前疗休养事项ID
|
||||||
|
* @param groupCode 目标成团编码
|
||||||
|
* @param ledgerIds 选中的教职工台账ID,多个ID使用逗号分隔
|
||||||
|
* @return 调整结果,包含成功提示或具体业务校验错误
|
||||||
|
*/
|
||||||
|
Result adjustGroup(String matterId, String groupCode, String ledgerIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按事项和成团编码领办当前团,只回填当前团台账的领办工会信息,不修改事项表归属工会。
|
||||||
|
*
|
||||||
|
* @param matterId 当前疗休养事项ID
|
||||||
|
* @param groupCode 当前成团编码
|
||||||
|
* @param unionId 当前登录人所属工会编码
|
||||||
|
* @param unionName 当前登录人所属工会名称
|
||||||
|
* @return 领办结果,包含参数、重复领办和有效团数据校验
|
||||||
|
*/
|
||||||
|
Result undertakeGroup(String matterId, String groupCode, String unionId, String unionName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按事项和成团编码解除当前团领办,只清空当前团台账的领办工会信息。
|
||||||
|
*
|
||||||
|
* @param matterId 当前疗休养事项ID
|
||||||
|
* @param groupCode 当前成团编码
|
||||||
|
* @return 解除结果,包含参数和当前团是否已领办校验
|
||||||
|
*/
|
||||||
|
Result releaseUndertakeGroup(String matterId, String groupCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks the active yearly tour setting row before out-province quota calculation.
|
||||||
|
*
|
||||||
|
* @param year tour signup year that shares one out-province quota pool.
|
||||||
|
*/
|
||||||
|
void lockTourSignupQuotaYear(Integer year);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks the current tour matter row before matter-level signup capacity calculation.
|
||||||
|
*
|
||||||
|
* @param matterId tour matter id that owns the maximum signup capacity limit.
|
||||||
|
*/
|
||||||
|
void lockTourSignupMatter(String matterId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the current time is inside the effective tour signup window.
|
||||||
|
* Setting-level signup time keeps the existing priority and falls back to matter-level time when blank.
|
||||||
|
*
|
||||||
|
* @param matter selected tour matter that carries the fallback signup window.
|
||||||
|
* @param setting tour setting that carries the primary signup window.
|
||||||
|
* @return null when current time is allowed; otherwise a business error result.
|
||||||
|
*/
|
||||||
|
Result checkSignupTimeWindow(TourMatter matter, TourSetting setting);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the tour signup page order column and keeps frontend sorting within known list fields.
|
||||||
|
*
|
||||||
|
* @param orderName requested frontend sort property.
|
||||||
|
* @return safe SQL order column for the tour signup list.
|
||||||
|
*/
|
||||||
|
String getTourSignupOrderColumn(String orderName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,17 @@ package com.budwk.app.zhgh.dayofficework.tour.service;
|
|||||||
import com.budwk.app.base.service.BaseService;
|
import com.budwk.app.base.service.BaseService;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface TourMatterService extends BaseService<TourMatter> {
|
public interface TourMatterService extends BaseService<TourMatter> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按事项ID批量更新报名起止时间,仅处理报名时间字段。
|
||||||
|
*
|
||||||
|
* @param ids 当前查询条件命中的事项ID集合
|
||||||
|
* @param signupStartTime 报名开始时间
|
||||||
|
* @param signupEndTime 报名结束时间
|
||||||
|
* @return 实际更新数量
|
||||||
|
*/
|
||||||
|
int assignSignupTime(List<String> ids, String signupStartTime, String signupEndTime);
|
||||||
}
|
}
|
||||||
|
|||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||||
|
|
||||||
|
import com.budwk.app.base.service.BaseService;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourParticipantRoster;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 疗休养人员名单维护服务。
|
||||||
|
* 用于按配置维护可参与人员基础名单,并同步报名台账中的参加状态。
|
||||||
|
*/
|
||||||
|
public interface TourParticipantRosterService extends BaseService<TourParticipantRoster> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将一个疗休养配置的可参与人员同步到人员名单表。
|
||||||
|
*
|
||||||
|
* @param settingId 疗休养配置 id,配置中的可参与人员组别决定人员范围。
|
||||||
|
*/
|
||||||
|
void syncSettingRoster(String settingId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅当配置下还没有人员名单数据时,同步配置可参与人员。
|
||||||
|
*
|
||||||
|
* @param settingId 需要懒加载人员名单的疗休养配置 id。
|
||||||
|
*/
|
||||||
|
void syncSettingRosterIfEmpty(String settingId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据可参与人员范围和现有疗休养台账,重新生成所选配置的人员名单。
|
||||||
|
*
|
||||||
|
* @param settingIds 所选疗休养配置 id。
|
||||||
|
* @return 已不在可参与人员范围中但存在台账、因此无法剔除的人员提示。
|
||||||
|
*/
|
||||||
|
List<String> rebuildSettingRosters(List<String> settingIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除疗休养配置时清理对应人员名单。
|
||||||
|
*
|
||||||
|
* @param settingId 已删除的疗休养配置 id。
|
||||||
|
*/
|
||||||
|
void clearSettingRoster(String settingId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报名成功或修改报名后,将人员名单标记为已参加并关联最新台账。
|
||||||
|
*
|
||||||
|
* @param ledger 已创建或修改成功的报名台账。
|
||||||
|
*/
|
||||||
|
void markJoinedByLedger(TourLedger ledger);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消报名或删除报名后,将人员名单标记为未参加并清空关联台账。
|
||||||
|
*
|
||||||
|
* @param ledger 正在取消或删除的报名台账。
|
||||||
|
*/
|
||||||
|
void markNotJoinedByLedger(TourLedger ledger);
|
||||||
|
}
|
||||||
@@ -4,4 +4,11 @@ import com.budwk.app.base.service.BaseService;
|
|||||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||||
|
|
||||||
public interface TourSettingService extends BaseService<TourSetting> {
|
public interface TourSettingService extends BaseService<TourSetting> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新疗休养配置,并允许报名开始时间、报名结束时间被清空。
|
||||||
|
*
|
||||||
|
* @param tourSetting 页面提交的疗休养配置
|
||||||
|
*/
|
||||||
|
void updateWithNullableSignupTime(TourSetting tourSetting);
|
||||||
}
|
}
|
||||||
|
|||||||
+787
@@ -1,10 +1,32 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.budwk.app.base.constant.RoleConstant;
|
||||||
|
import com.budwk.app.base.result.Result;
|
||||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||||
|
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||||
|
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
||||||
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
|
import org.nutz.dao.Chain;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.dao.Sqls;
|
||||||
|
import org.nutz.dao.sql.Sql;
|
||||||
|
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||||
|
import org.nutz.ioc.aop.Aop;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@IocBean(args = {"refer:dao"})
|
@IocBean(args = {"refer:dao"})
|
||||||
public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implements TourLedgerService {
|
public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implements TourLedgerService {
|
||||||
@@ -12,4 +34,769 @@ public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implement
|
|||||||
public TourLedgerServiceImpl(Dao dao) {
|
public TourLedgerServiceImpl(Dao dao) {
|
||||||
super(dao);
|
super(dao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据传入条件查询台账中已经存在的成团信息,按成团编码和成团名称去重后返回给页面展示。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<NutMap> queryGroupOptions(Cnd cnd) {
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT DISTINCT
|
||||||
|
t.groupCode,
|
||||||
|
t.groupName
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
$condition
|
||||||
|
ORDER BY t.groupCode ASC, t.groupName ASC
|
||||||
|
""");
|
||||||
|
sql.setCondition(cnd);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
dao().execute(sql);
|
||||||
|
return sql.getList(NutMap.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询符合台账页面筛选条件的全部导出数据,关联线路、事项和家属数据以保持导出结果与列表一致。
|
||||||
|
*
|
||||||
|
* @param cnd 已包含页面全部筛选项和审批完成状态限制的台账查询条件
|
||||||
|
* @return 未分页的台账导出数据列表,每项为一名教职工的一条有效台账记录
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<NutMap> queryExportRows(Cnd cnd) {
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
t.`year`,
|
||||||
|
t.jobNo,
|
||||||
|
t.userName,
|
||||||
|
t.unitName,
|
||||||
|
COALESCE(NULLIF(l.lineName, ''), t.lineName) AS lineName,
|
||||||
|
CASE
|
||||||
|
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||||
|
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||||
|
ELSE tp.travelPeriod
|
||||||
|
END AS travelPeriod,
|
||||||
|
t.lineType,
|
||||||
|
t.groupCode,
|
||||||
|
t.groupName,
|
||||||
|
t.undertakeUnionName,
|
||||||
|
t.signupTime,
|
||||||
|
IFNULL(f.familyCount, 0) AS familyCount,
|
||||||
|
t.joined,
|
||||||
|
t.overCostReimbursed
|
||||||
|
FROM tour_ledger t
|
||||||
|
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
|
FROM tour_ledger_family
|
||||||
|
WHERE delFlag = 0
|
||||||
|
GROUP BY ledgerId
|
||||||
|
) f ON f.ledgerId = t.id
|
||||||
|
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||||
|
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||||
|
LEFT JOIN tour_ledger_direct_relative dr ON dr.ledgerId = t.id AND dr.delFlag = 0
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
`year`,
|
||||||
|
lineId,
|
||||||
|
GROUP_CONCAT(
|
||||||
|
DISTINCT CASE
|
||||||
|
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||||
|
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
ORDER BY travelStartTime ASC
|
||||||
|
SEPARATOR ';'
|
||||||
|
) AS travelPeriod
|
||||||
|
FROM tour_matter
|
||||||
|
WHERE delFlag = 0
|
||||||
|
AND lineId IS NOT NULL
|
||||||
|
AND lineId <> ''
|
||||||
|
GROUP BY `year`, lineId
|
||||||
|
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
|
||||||
|
$condition
|
||||||
|
GROUP BY t.id
|
||||||
|
ORDER BY t.`year` DESC, t.signupTime DESC, t.createdAt DESC
|
||||||
|
""");
|
||||||
|
sql.setCondition(cnd);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
dao().execute(sql);
|
||||||
|
return sql.getList(NutMap.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动分配当前事项下未成团的教职工台账,分配过程中出现容量不合法时整体中止。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public Result assignGroup(String matterId) {
|
||||||
|
if (StrUtil.isBlank(matterId)) {
|
||||||
|
return Result.error("参数错误");
|
||||||
|
}
|
||||||
|
NutMap matter = fetchAssignMatter(matterId);
|
||||||
|
if (matter == null || matter.isEmpty()) {
|
||||||
|
return Result.error("事项不存在或线路信息不完整");
|
||||||
|
}
|
||||||
|
int maxGroupPeople = mapInt(matter, "maxGroupPeople");
|
||||||
|
if (maxGroupPeople <= 0) {
|
||||||
|
return Result.error("未配置最大成团人数,无法分配成团");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GroupBucket> groups = queryGroupBuckets(matterId);
|
||||||
|
List<SignupUnit> signupUnits = queryUnassignedSignupUnits(matterId);
|
||||||
|
if (signupUnits.isEmpty()) {
|
||||||
|
return Result.success("暂无未成团人员需要分配");
|
||||||
|
}
|
||||||
|
for (SignupUnit unit : signupUnits) {
|
||||||
|
if (unit.peopleCount > maxGroupPeople) {
|
||||||
|
return Result.error(unit.userName + "含家属共" + unit.peopleCount + "人,超过最大成团人数" + maxGroupPeople + "人,无法分配");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int nextGroupIndex = nextGroupIndex(groups);
|
||||||
|
int assignCount = 0;
|
||||||
|
for (SignupUnit unit : signupUnits) {
|
||||||
|
GroupBucket targetGroup = findAvailableGroup(groups, unit.peopleCount, maxGroupPeople);
|
||||||
|
if (targetGroup == null) {
|
||||||
|
targetGroup = createGroupBucket(nextGroupIndex++);
|
||||||
|
groups.add(targetGroup);
|
||||||
|
}
|
||||||
|
updateLedgerGroup(unit.ledgerId, targetGroup.groupCode, targetGroup.groupName);
|
||||||
|
targetGroup.peopleCount += unit.peopleCount;
|
||||||
|
assignCount++;
|
||||||
|
}
|
||||||
|
return Result.success("分配成功,共分配" + assignCount + "名教职工");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将选中的教职工台账调整到目标团,家属随教职工一起计入目标团人数。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public Result adjustGroup(String matterId, String groupCode, String ledgerIds) {
|
||||||
|
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
|
||||||
|
return Result.error("参数错误");
|
||||||
|
}
|
||||||
|
List<String> ledgerIdList = parseLedgerIds(ledgerIds);
|
||||||
|
if (ledgerIdList.isEmpty()) {
|
||||||
|
return Result.error("请选择需要调整的教职工");
|
||||||
|
}
|
||||||
|
|
||||||
|
NutMap matter = fetchAssignMatter(matterId);
|
||||||
|
if (matter == null || matter.isEmpty()) {
|
||||||
|
return Result.error("事项不存在或线路信息不完整");
|
||||||
|
}
|
||||||
|
int maxGroupPeople = mapInt(matter, "maxGroupPeople");
|
||||||
|
if (maxGroupPeople <= 0) {
|
||||||
|
return Result.error("未配置最大成团人数,无法调整成团");
|
||||||
|
}
|
||||||
|
|
||||||
|
NutMap targetGroup = fetchTargetGroup(matterId, groupCode);
|
||||||
|
if (targetGroup == null || targetGroup.isEmpty()) {
|
||||||
|
return Result.error("请选择有效的成团信息");
|
||||||
|
}
|
||||||
|
List<SignupUnit> selectedUnits = querySelectedSignupUnits(matterId, ledgerIdList);
|
||||||
|
if (selectedUnits.size() != ledgerIdList.size()) {
|
||||||
|
return Result.error("所选人员信息无效,请刷新后重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
int selectedPeopleCount = selectedUnits.stream().mapToInt(unit -> unit.peopleCount).sum();
|
||||||
|
int targetPeopleCount = countTargetGroupPeople(matterId, groupCode, ledgerIdList);
|
||||||
|
if (targetPeopleCount + selectedPeopleCount > maxGroupPeople) {
|
||||||
|
return Result.error("调整后目标团共" + (targetPeopleCount + selectedPeopleCount)
|
||||||
|
+ "人,超过最大成团人数" + maxGroupPeople + "人,无法调整");
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLedgerGroup(matterId, ledgerIdList, groupCode, targetGroup.getString("groupName", ""));
|
||||||
|
return Result.success("调整成功,共调整" + selectedUnits.size() + "名教职工");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询事项基础信息,最大成团人数是自动分配的核心容量上限。
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 当前领办以团为单位执行,写入范围限定为同一事项、同一成团编码下的有效台账。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public Result undertakeGroup(String matterId, String groupCode, String unionId, String unionName) {
|
||||||
|
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode) || StrUtil.isBlank(unionId)) {
|
||||||
|
return Result.error("参数错误");
|
||||||
|
}
|
||||||
|
NutMap group = fetchUndertakeGroup(matterId, groupCode);
|
||||||
|
if (group == null || group.isEmpty()) {
|
||||||
|
return Result.error("当前团不存在或未成团,无法领办");
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(group.getString("undertakeUnionId", ""))) {
|
||||||
|
return Result.error("当前团已领办,不允许修改");
|
||||||
|
}
|
||||||
|
Cnd cnd = buildUndertakeGroupCnd(matterId, groupCode);
|
||||||
|
Chain chain = Chain.make("undertakeUnionId", unionId)
|
||||||
|
.add("undertakeUnionName", StrUtil.blankToDefault(unionName, ""));
|
||||||
|
dao().update(TourLedger.class, chain, cnd);
|
||||||
|
return Result.success(NutMap.NEW().addv("unionId", unionId).addv("unionName", StrUtil.blankToDefault(unionName, "")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解除领办同样按团处理,仅清空当前团台账领办字段,保留事项表原有归属信息。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public Result releaseUndertakeGroup(String matterId, String groupCode) {
|
||||||
|
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
|
||||||
|
return Result.error("参数错误");
|
||||||
|
}
|
||||||
|
NutMap group = fetchUndertakeGroup(matterId, groupCode);
|
||||||
|
if (group == null || group.isEmpty()) {
|
||||||
|
return Result.error("当前团不存在或未成团,无法解除");
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(group.getString("undertakeUnionId", ""))) {
|
||||||
|
return Result.error("当前团未领办,无需解除");
|
||||||
|
}
|
||||||
|
Cnd cnd = buildUndertakeGroupCnd(matterId, groupCode);
|
||||||
|
dao().update(TourLedger.class, Chain.make("undertakeUnionId", "").add("undertakeUnionName", ""), cnd);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks the yearly setting row so concurrent out-province signup checks read updated quota counts one by one.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void lockTourSignupQuotaYear(Integer year) {
|
||||||
|
if (year == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// MySQL/InnoDB holds this row lock until the current signup transaction commits.
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT id
|
||||||
|
FROM tour_setting
|
||||||
|
WHERE delFlag = 0
|
||||||
|
AND `year` = @year
|
||||||
|
ORDER BY id
|
||||||
|
FOR UPDATE
|
||||||
|
""");
|
||||||
|
sql.setParam("year", year);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
dao().execute(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks one matter row so concurrent submissions for the same travel period check capacity one by one.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void lockTourSignupMatter(String matterId) {
|
||||||
|
if (StrUtil.isBlank(matterId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// MySQL/InnoDB holds this matter row lock until the current signup transaction commits.
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT id
|
||||||
|
FROM tour_matter
|
||||||
|
WHERE delFlag = 0
|
||||||
|
AND id = @matterId
|
||||||
|
FOR UPDATE
|
||||||
|
""");
|
||||||
|
sql.setParam("matterId", matterId);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
dao().execute(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the current time against the effective signup window used by tour signup and cancellation.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Result checkSignupTimeWindow(TourMatter matter, TourSetting setting) {
|
||||||
|
if (matter == null || setting == null) {
|
||||||
|
return Result.error("报名事项配置不存在或已停用");
|
||||||
|
}
|
||||||
|
String signupStartTime = StrUtil.blankToDefault(setting.getSignupStartTime(), matter.getSignupStartTime());
|
||||||
|
String signupEndTime = StrUtil.blankToDefault(setting.getSignupEndTime(), matter.getSignupEndTime());
|
||||||
|
if (StrUtil.isBlank(signupStartTime) || StrUtil.isBlank(signupEndTime)) {
|
||||||
|
return Result.error("报名时间未配置");
|
||||||
|
}
|
||||||
|
String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
if (now.compareTo(normalizeSignupDateTime(signupStartTime, false)) < 0) {
|
||||||
|
return Result.error("报名未开始");
|
||||||
|
}
|
||||||
|
if (now.compareTo(normalizeSignupDateTime(signupEndTime, true)) > 0) {
|
||||||
|
return Result.error("报名已结束");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes date-only signup values so the end date includes the whole day.
|
||||||
|
*/
|
||||||
|
private String normalizeSignupDateTime(String value, boolean endOfDay) {
|
||||||
|
String text = StrUtil.blankToDefault(value, "").trim();
|
||||||
|
if (text.length() == 10) {
|
||||||
|
return text + (endOfDay ? " 23:59:59" : " 00:00:00");
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps controller SQL ordering safe by resolving only known list fields.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getTourSignupOrderColumn(String orderName) {
|
||||||
|
if ("lineCode".equals(orderName)) {
|
||||||
|
return "CAST(l.lineCode AS UNSIGNED)";
|
||||||
|
}
|
||||||
|
if ("lineName".equals(orderName)) {
|
||||||
|
return "l.lineName";
|
||||||
|
}
|
||||||
|
if ("travelPeriod".equals(orderName)) {
|
||||||
|
return "m.travelStartTime";
|
||||||
|
}
|
||||||
|
if ("matterName".equals(orderName)) {
|
||||||
|
return "m.matterName";
|
||||||
|
}
|
||||||
|
if ("unionName".equals(orderName)) {
|
||||||
|
return "u.name";
|
||||||
|
}
|
||||||
|
if ("lineType".equals(orderName)) {
|
||||||
|
return "l.lineType";
|
||||||
|
}
|
||||||
|
if ("minGroupPeople".equals(orderName)) {
|
||||||
|
return "m.minGroupPeople";
|
||||||
|
}
|
||||||
|
if ("maxGroupPeople".equals(orderName)) {
|
||||||
|
return "m.maxGroupPeople";
|
||||||
|
}
|
||||||
|
if ("maxSignupPeople".equals(orderName)) {
|
||||||
|
return "m.maxSignupPeople";
|
||||||
|
}
|
||||||
|
if ("signupCount".equals(orderName)) {
|
||||||
|
return "signupCount";
|
||||||
|
}
|
||||||
|
return "CAST(l.lineCode AS UNSIGNED)";
|
||||||
|
}
|
||||||
|
|
||||||
|
private NutMap fetchAssignMatter(String matterId) {
|
||||||
|
Cnd cnd = buildAssignMatterCnd(matterId);
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
m.id,
|
||||||
|
m.maxGroupPeople
|
||||||
|
FROM tour_matter m
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
$condition
|
||||||
|
""");
|
||||||
|
sql.setCondition(cnd);
|
||||||
|
sql.setCallback(Sqls.callback.map());
|
||||||
|
dao().execute(sql);
|
||||||
|
return sql.getObject(NutMap.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前事项下已存在的目标成团信息,人员调整只能选择当前线路台账中已有的团。
|
||||||
|
*/
|
||||||
|
private NutMap fetchTargetGroup(String matterId, String groupCode) {
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
t.groupCode,
|
||||||
|
MAX(t.groupName) AS groupName
|
||||||
|
FROM tour_ledger t
|
||||||
|
WHERE t.delFlag = 0
|
||||||
|
AND t.matterId = @matterId
|
||||||
|
AND t.groupCode = @groupCode
|
||||||
|
GROUP BY t.groupCode
|
||||||
|
""");
|
||||||
|
sql.setParam("matterId", matterId);
|
||||||
|
sql.setParam("groupCode", groupCode);
|
||||||
|
sql.setCallback(Sqls.callback.map());
|
||||||
|
dao().execute(sql);
|
||||||
|
return sql.getObject(NutMap.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造自动分配事项查询条件,确保自动分配的数据范围与线路成团查看列表保持一致。
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 查询当前事项下指定团的领办字段,供领办和解除领办前置校验使用。
|
||||||
|
*/
|
||||||
|
private NutMap fetchUndertakeGroup(String matterId, String groupCode) {
|
||||||
|
Cnd cnd = buildAssignMatterCnd(matterId);
|
||||||
|
cnd.and("t.delFlag", "=", false);
|
||||||
|
cnd.and("t.groupCode", "=", groupCode);
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
t.groupCode,
|
||||||
|
MAX(t.groupName) AS groupName,
|
||||||
|
MAX(t.undertakeUnionId) AS undertakeUnionId,
|
||||||
|
MAX(t.undertakeUnionName) AS undertakeUnionName
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||||
|
INNER JOIN tour_line l ON l.id = m.lineId
|
||||||
|
$condition
|
||||||
|
GROUP BY t.groupCode
|
||||||
|
""");
|
||||||
|
sql.setCondition(cnd);
|
||||||
|
sql.setCallback(Sqls.callback.map());
|
||||||
|
dao().execute(sql);
|
||||||
|
return sql.getObject(NutMap.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造当前团领办字段更新范围,避免同一事项下其它团被误更新。
|
||||||
|
*/
|
||||||
|
private Cnd buildUndertakeGroupCnd(String matterId, String groupCode) {
|
||||||
|
return Cnd.where("matterId", "=", matterId)
|
||||||
|
.and("groupCode", "=", groupCode)
|
||||||
|
.and("delFlag", "=", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cnd buildAssignMatterCnd(String matterId) {
|
||||||
|
Cnd cnd = Cnd.NEW();
|
||||||
|
cnd.and("m.delFlag", "=", false);
|
||||||
|
cnd.and("m.enabled", "=", true);
|
||||||
|
cnd.and("m.id", "=", matterId);
|
||||||
|
cnd.and("m.lineId", "IS NOT", null);
|
||||||
|
cnd.and("m.lineId", "<>", "");
|
||||||
|
cnd.and("l.delFlag", "=", false);
|
||||||
|
cnd.and("l.enabled", "=", true);
|
||||||
|
applyMatterDataScope(cnd);
|
||||||
|
return cnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询已存在成团及当前占用人数,后续未成团人员优先从最早团开始补足。
|
||||||
|
*/
|
||||||
|
private List<GroupBucket> queryGroupBuckets(String matterId) {
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
t.groupCode,
|
||||||
|
MAX(t.groupName) AS groupName,
|
||||||
|
SUM(1 + IFNULL(f.familyCount, 0)) AS peopleCount
|
||||||
|
FROM tour_ledger t
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
|
FROM tour_ledger_family
|
||||||
|
WHERE delFlag = 0
|
||||||
|
GROUP BY ledgerId
|
||||||
|
) f ON f.ledgerId = t.id
|
||||||
|
WHERE t.delFlag = 0
|
||||||
|
AND t.matterId = @matterId
|
||||||
|
AND t.groupCode IS NOT NULL
|
||||||
|
AND t.groupCode <> ''
|
||||||
|
GROUP BY t.groupCode
|
||||||
|
ORDER BY t.groupCode ASC
|
||||||
|
""");
|
||||||
|
sql.setParam("matterId", matterId);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
dao().execute(sql);
|
||||||
|
|
||||||
|
List<GroupBucket> groups = new ArrayList<>();
|
||||||
|
for (NutMap item : sql.getList(NutMap.class)) {
|
||||||
|
GroupBucket group = new GroupBucket();
|
||||||
|
group.groupCode = item.getString("groupCode", "");
|
||||||
|
group.groupName = item.getString("groupName", "");
|
||||||
|
group.peopleCount = mapInt(item, "peopleCount");
|
||||||
|
groups.add(group);
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询未成团教职工台账,并合并家属人数,保证教职工和家属作为一个整体分配。
|
||||||
|
*/
|
||||||
|
private List<SignupUnit> queryUnassignedSignupUnits(String matterId) {
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
t.id AS ledgerId,
|
||||||
|
t.userName,
|
||||||
|
1 + IFNULL(f.familyCount, 0) AS peopleCount
|
||||||
|
FROM tour_ledger t
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
|
FROM tour_ledger_family
|
||||||
|
WHERE delFlag = 0
|
||||||
|
GROUP BY ledgerId
|
||||||
|
) f ON f.ledgerId = t.id
|
||||||
|
WHERE t.delFlag = 0
|
||||||
|
AND t.matterId = @matterId
|
||||||
|
AND (t.groupCode IS NULL OR t.groupCode = '')
|
||||||
|
ORDER BY t.signupTime ASC, t.createdAt ASC
|
||||||
|
""");
|
||||||
|
sql.setParam("matterId", matterId);
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
dao().execute(sql);
|
||||||
|
|
||||||
|
List<SignupUnit> signupUnits = new ArrayList<>();
|
||||||
|
for (NutMap item : sql.getList(NutMap.class)) {
|
||||||
|
SignupUnit unit = new SignupUnit();
|
||||||
|
unit.ledgerId = item.getString("ledgerId", "");
|
||||||
|
unit.userName = item.getString("userName", "");
|
||||||
|
unit.peopleCount = mapInt(item, "peopleCount");
|
||||||
|
signupUnits.add(unit);
|
||||||
|
}
|
||||||
|
return signupUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询选中教职工台账及其家属人数,确保只调整当前事项下有效的教职工报名记录。
|
||||||
|
*/
|
||||||
|
private List<SignupUnit> querySelectedSignupUnits(String matterId, List<String> ledgerIds) {
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
t.id AS ledgerId,
|
||||||
|
t.userName,
|
||||||
|
1 + IFNULL(f.familyCount, 0) AS peopleCount
|
||||||
|
FROM tour_ledger t
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
|
FROM tour_ledger_family
|
||||||
|
WHERE delFlag = 0
|
||||||
|
GROUP BY ledgerId
|
||||||
|
) f ON f.ledgerId = t.id
|
||||||
|
WHERE t.delFlag = 0
|
||||||
|
AND t.matterId = @matterId
|
||||||
|
AND t.id IN (@ledgerIds)
|
||||||
|
ORDER BY t.signupTime ASC, t.createdAt ASC
|
||||||
|
""");
|
||||||
|
sql.setParam("matterId", matterId);
|
||||||
|
sql.setParam("ledgerIds", ledgerIds.toArray(new String[0]));
|
||||||
|
sql.setCallback(Sqls.callback.maps());
|
||||||
|
dao().execute(sql);
|
||||||
|
|
||||||
|
List<SignupUnit> signupUnits = new ArrayList<>();
|
||||||
|
for (NutMap item : sql.getList(NutMap.class)) {
|
||||||
|
SignupUnit unit = new SignupUnit();
|
||||||
|
unit.ledgerId = item.getString("ledgerId", "");
|
||||||
|
unit.userName = item.getString("userName", "");
|
||||||
|
unit.peopleCount = mapInt(item, "peopleCount");
|
||||||
|
signupUnits.add(unit);
|
||||||
|
}
|
||||||
|
return signupUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计目标团调整前人数,排除本次选中人员,避免原本已在目标团的人员被重复计数。
|
||||||
|
*/
|
||||||
|
private int countTargetGroupPeople(String matterId, String groupCode, List<String> excludeLedgerIds) {
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT IFNULL(SUM(1 + IFNULL(f.familyCount, 0)), 0) AS peopleCount
|
||||||
|
FROM tour_ledger t
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ledgerId, COUNT(1) AS familyCount
|
||||||
|
FROM tour_ledger_family
|
||||||
|
WHERE delFlag = 0
|
||||||
|
GROUP BY ledgerId
|
||||||
|
) f ON f.ledgerId = t.id
|
||||||
|
WHERE t.delFlag = 0
|
||||||
|
AND t.matterId = @matterId
|
||||||
|
AND t.groupCode = @groupCode
|
||||||
|
AND t.id NOT IN (@excludeLedgerIds)
|
||||||
|
""");
|
||||||
|
sql.setParam("matterId", matterId);
|
||||||
|
sql.setParam("groupCode", groupCode);
|
||||||
|
sql.setParam("excludeLedgerIds", excludeLedgerIds.toArray(new String[0]));
|
||||||
|
sql.setCallback(Sqls.callback.map());
|
||||||
|
dao().execute(sql);
|
||||||
|
return mapInt(sql.getObject(NutMap.class), "peopleCount");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从最早的团开始查找可容纳当前报名单位的团,避免前面团未满但后面新建团的情况。
|
||||||
|
*/
|
||||||
|
private GroupBucket findAvailableGroup(List<GroupBucket> groups, int peopleCount, int maxGroupPeople) {
|
||||||
|
for (GroupBucket group : groups) {
|
||||||
|
if (group.peopleCount + peopleCount <= maxGroupPeople) {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据现有最大团序号生成下一个团序号,兼容已有非标准团编码。
|
||||||
|
*/
|
||||||
|
private int nextGroupIndex(List<GroupBucket> groups) {
|
||||||
|
int maxIndex = 0;
|
||||||
|
for (GroupBucket group : groups) {
|
||||||
|
maxIndex = Math.max(maxIndex, parseGroupIndex(group.groupCode));
|
||||||
|
}
|
||||||
|
return maxIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新的成团桶,编码使用T001递增,名称使用中文序号加“团”。
|
||||||
|
*/
|
||||||
|
private GroupBucket createGroupBucket(int index) {
|
||||||
|
GroupBucket group = new GroupBucket();
|
||||||
|
group.groupCode = String.format("T%03d", index);
|
||||||
|
group.groupName = toChineseNumber(index) + "团";
|
||||||
|
group.peopleCount = 0;
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动分配按事项归属控制数据范围:校工会查看全部;分工会查看本工会事项;其它角色仅处理自己创建的事项。
|
||||||
|
*/
|
||||||
|
private void applyMatterDataScope(Cnd cnd) {
|
||||||
|
if (hasSchoolUnionScope()
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())
|
||||||
|
|| hasTourApplicantScope()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasBranchUnionScope()) {
|
||||||
|
cnd.and("m.unionId", "=", SecurityUtil.getUnionId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SqlExpressionGroup creatorGroup = new SqlExpressionGroup();
|
||||||
|
creatorGroup.or("m.creatorUserId", "=", SecurityUtil.getUserId());
|
||||||
|
creatorGroup.or("m.createdBy", "=", SecurityUtil.getUserId());
|
||||||
|
cnd.and(creatorGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前用户是否具备校工会级别的事项处理范围。
|
||||||
|
*/
|
||||||
|
private boolean hasSchoolUnionScope() {
|
||||||
|
return AuthUtil.hasRole(RoleConstant.SYSADMIN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_CHAIRMAN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_VICE_CHAIRMAN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_WELFARE_ADMIN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_MEMBER_ADMIN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_TC_ADMIN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_WC_ADMIN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_DC_ADMIN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_PROPOSAL_ADMIN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 疗休养领办人需要与领办列表保持一致,可在领办前查询到全部成团数据。
|
||||||
|
*/
|
||||||
|
private boolean hasTourApplicantScope() {
|
||||||
|
return AuthUtil.hasRole(RoleConstant.TOUR_APPLICANT.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前用户是否具备分工会级别的事项处理范围。
|
||||||
|
*/
|
||||||
|
private boolean hasBranchUnionScope() {
|
||||||
|
return AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ADMIN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_VICE_CHAIRMAN.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_OPERATOR.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_GROUP_LEADER.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ARTICLE_WRITER.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_WY.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ZUZHI_WY.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_XUANCHUAN_WY.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_NVGONG_WY.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_QINGNIAN_WY.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_WENTI_WY.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_SHENGGHUO_WY.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_TIAOJIE_WY.name())
|
||||||
|
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_WENTI_SPORTS.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新教职工台账成团信息,家属行展示时继承教职工台账的成团信息。
|
||||||
|
*/
|
||||||
|
private void updateLedgerGroup(String ledgerId, String groupCode, String groupName) {
|
||||||
|
dao().update(TourLedger.class, Chain.make("groupCode", groupCode).add("groupName", groupName), Cnd.where("id", "=", ledgerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新选中的教职工台账成团信息,人员调整只写教职工台账,家属展示继续继承教职工。
|
||||||
|
*/
|
||||||
|
private void updateLedgerGroup(String matterId, List<String> ledgerIds, String groupCode, String groupName) {
|
||||||
|
Cnd cnd = Cnd.where("id", "in", ledgerIds)
|
||||||
|
.and("matterId", "=", matterId)
|
||||||
|
.and("delFlag", "=", false);
|
||||||
|
dao().update(TourLedger.class, Chain.make("groupCode", groupCode).add("groupName", groupName), cnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析前端逗号分隔的台账ID,去重并过滤空值,避免重复计数和重复更新。
|
||||||
|
*/
|
||||||
|
private List<String> parseLedgerIds(String ledgerIds) {
|
||||||
|
Set<String> idSet = new LinkedHashSet<>();
|
||||||
|
if (StrUtil.isBlank(ledgerIds)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (String id : ledgerIds.split(",")) {
|
||||||
|
if (StrUtil.isNotBlank(id)) {
|
||||||
|
idSet.add(id.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>(idSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析T001这类标准团编码中的数字序号,非标准编码返回0。
|
||||||
|
*/
|
||||||
|
private int parseGroupIndex(String groupCode) {
|
||||||
|
if (StrUtil.isBlank(groupCode) || !groupCode.startsWith("T")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(groupCode.substring(1));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数字序号转为中文团名序号,支持常见的两位和多位团序号展示。
|
||||||
|
*/
|
||||||
|
private String toChineseNumber(int number) {
|
||||||
|
String[] digits = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
|
||||||
|
if (number <= 0) {
|
||||||
|
return String.valueOf(number);
|
||||||
|
}
|
||||||
|
if (number < 10) {
|
||||||
|
return "第" + digits[number];
|
||||||
|
}
|
||||||
|
if (number < 100) {
|
||||||
|
int ten = number / 10;
|
||||||
|
int one = number % 10;
|
||||||
|
String prefix = ten == 1 ? "十" : digits[ten] + "十";
|
||||||
|
return "第" + prefix + (one == 0 ? "" : digits[one]);
|
||||||
|
}
|
||||||
|
String value = String.valueOf(number);
|
||||||
|
StringBuilder builder = new StringBuilder("第");
|
||||||
|
for (int i = 0; i < value.length(); i++) {
|
||||||
|
builder.append(digits[value.charAt(i) - '0']);
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全读取SQL返回的整数值,避免不同数据库驱动返回Number或字符串时转换失败。
|
||||||
|
*/
|
||||||
|
private int mapInt(NutMap map, String key) {
|
||||||
|
Object value = map == null ? null : map.get(key);
|
||||||
|
if (value instanceof Number number) {
|
||||||
|
return number.intValue();
|
||||||
|
}
|
||||||
|
if (value == null || StrUtil.isBlank(String.valueOf(value))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已有成团容量桶,记录每个团当前人数以及后续分配后的占用人数。
|
||||||
|
*/
|
||||||
|
private static class GroupBucket {
|
||||||
|
private String groupCode;
|
||||||
|
private String groupName;
|
||||||
|
private int peopleCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待分配报名单位,教职工及其家属作为不可拆分的最小分配单位。
|
||||||
|
*/
|
||||||
|
private static class SignupUnit {
|
||||||
|
private String ledgerId;
|
||||||
|
private String userName;
|
||||||
|
private int peopleCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
@@ -3,13 +3,35 @@ package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
|||||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
||||||
|
import org.nutz.dao.Chain;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@IocBean(args = {"refer:dao"})
|
@IocBean(args = {"refer:dao"})
|
||||||
public class TourMatterServiceImpl extends BaseServiceImpl<TourMatter> implements TourMatterService {
|
public class TourMatterServiceImpl extends BaseServiceImpl<TourMatter> implements TourMatterService {
|
||||||
|
|
||||||
public TourMatterServiceImpl(Dao dao) {
|
public TourMatterServiceImpl(Dao dao) {
|
||||||
super(dao);
|
super(dao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按事项ID批量更新报名起止时间,供事项列表“一键赋值报名时间”使用。
|
||||||
|
*
|
||||||
|
* @param ids 当前查询条件命中的事项ID集合
|
||||||
|
* @param signupStartTime 报名开始时间
|
||||||
|
* @param signupEndTime 报名结束时间
|
||||||
|
* @return 实际更新数量
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int assignSignupTime(List<String> ids, String signupStartTime, String signupEndTime) {
|
||||||
|
if (ids == null || ids.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return dao().update(TourMatter.class,
|
||||||
|
Chain.make("signupStartTime", signupStartTime).add("signupEndTime", signupEndTime),
|
||||||
|
Cnd.where("id", "in", ids));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+358
@@ -0,0 +1,358 @@
|
|||||||
|
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||||
|
import com.budwk.app.sys.views.View_user;
|
||||||
|
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourParticipantRoster;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||||
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourParticipantRosterService;
|
||||||
|
import org.nutz.dao.Chain;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.dao.Sqls;
|
||||||
|
import org.nutz.dao.sql.Sql;
|
||||||
|
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 java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 疗休养人员名单维护服务实现。
|
||||||
|
* 负责同步配置可参与人员、回填台账参加状态,并保留已有台账人员的历史统计口径。
|
||||||
|
*/
|
||||||
|
@IocBean(args = {"refer:dao"})
|
||||||
|
public class TourParticipantRosterServiceImpl extends BaseServiceImpl<TourParticipantRoster> implements TourParticipantRosterService {
|
||||||
|
|
||||||
|
private static final int USER_QUERY_BATCH_SIZE = 500;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ActivityBasicScopeService activityBasicScopeService;
|
||||||
|
|
||||||
|
public TourParticipantRosterServiceImpl(Dao dao) {
|
||||||
|
super(dao);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取配置可参与人员组别,并写入或刷新人员名单基础信息。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void syncSettingRoster(String settingId) {
|
||||||
|
if (StrUtil.isBlank(settingId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TourSetting setting = dao().fetch(TourSetting.class, settingId);
|
||||||
|
if (setting == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (View_user user : listEligibleUsers(setting)) {
|
||||||
|
upsertRosterUser(settingId, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置普通保存时只做懒加载:配置下已有名单数据则不重新拉取人员。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void syncSettingRosterIfEmpty(String settingId) {
|
||||||
|
if (StrUtil.isBlank(settingId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int rosterCount = count(Cnd.where("settingId", "=", settingId));
|
||||||
|
if (rosterCount > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
syncSettingRoster(settingId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在一个事务内重建人员名单:删除旧名单、重载当前可参与人员、回放有效台账参加状态。
|
||||||
|
* 若台账人员已不在当前可参与人员中,仍保留在名单中,并返回提示给页面展示。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public List<String> rebuildSettingRosters(List<String> settingIds) {
|
||||||
|
List<String> warningMessages = new ArrayList<>();
|
||||||
|
if (CollUtil.isEmpty(settingIds)) {
|
||||||
|
return warningMessages;
|
||||||
|
}
|
||||||
|
for (String settingId : settingIds) {
|
||||||
|
if (StrUtil.isBlank(settingId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TourSetting setting = dao().fetch(TourSetting.class, settingId);
|
||||||
|
if (setting == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<View_user> eligibleUsers = listEligibleUsers(setting);
|
||||||
|
Set<String> eligibleJobNos = collectEligibleJobNos(eligibleUsers);
|
||||||
|
clear(Cnd.where("settingId", "=", settingId));
|
||||||
|
for (View_user user : eligibleUsers) {
|
||||||
|
upsertRosterUser(settingId, user);
|
||||||
|
}
|
||||||
|
List<String> retainedUserNames = replayLedgerJoinedState(settingId, eligibleJobNos);
|
||||||
|
if (CollUtil.isNotEmpty(retainedUserNames)) {
|
||||||
|
warningMessages.add(buildRetainedLedgerUserMessage(setting, retainedUserNames));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return warningMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除配置下的人员名单,避免已删除配置继续参与统计查询。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearSettingRoster(String settingId) {
|
||||||
|
if (StrUtil.isBlank(settingId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clear(Cnd.where("settingId", "=", settingId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报名成功和修改报名都会将人员名单标记为已参加,并指向当前有效台账。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void markJoinedByLedger(TourLedger ledger) {
|
||||||
|
String settingId = resolveSettingId(ledger);
|
||||||
|
if (StrUtil.isBlank(settingId) || ledger == null || StrUtil.isBlank(ledger.getJobNo())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TourParticipantRoster roster = fetchRoster(settingId, ledger.getJobNo());
|
||||||
|
if (roster == null) {
|
||||||
|
roster = new TourParticipantRoster();
|
||||||
|
roster.setSettingId(settingId);
|
||||||
|
roster.setJobNo(ledger.getJobNo());
|
||||||
|
roster.setJoined(true);
|
||||||
|
fillRosterFromLedger(roster, ledger);
|
||||||
|
insert(roster);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Chain chain = Chain.make("joined", true)
|
||||||
|
.add("ledgerId", StrUtil.blankToDefault(ledger.getId(), ""))
|
||||||
|
.add("userName", StrUtil.blankToDefault(ledger.getUserName(), roster.getUserName()))
|
||||||
|
.add("unitId", StrUtil.blankToDefault(ledger.getUnitId(), roster.getUnitId()))
|
||||||
|
.add("unitName", StrUtil.blankToDefault(ledger.getUnitName(), roster.getUnitName()))
|
||||||
|
.add("unionId", StrUtil.blankToDefault(ledger.getUnionId(), roster.getUnionId()))
|
||||||
|
.add("unionName", StrUtil.blankToDefault(ledger.getUnionName(), roster.getUnionName()));
|
||||||
|
dao().update(TourParticipantRoster.class, chain, Cnd.where("id", "=", roster.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消报名只重置当前人员名单状态,历史台账删除记录仍保留在 tour_ledger 中。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void markNotJoinedByLedger(TourLedger ledger) {
|
||||||
|
String settingId = resolveSettingId(ledger);
|
||||||
|
if (StrUtil.isBlank(settingId) || ledger == null || StrUtil.isBlank(ledger.getJobNo())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TourParticipantRoster roster = fetchRoster(settingId, ledger.getJobNo());
|
||||||
|
if (roster == null) {
|
||||||
|
roster = new TourParticipantRoster();
|
||||||
|
roster.setSettingId(settingId);
|
||||||
|
roster.setJobNo(ledger.getJobNo());
|
||||||
|
roster.setJoined(false);
|
||||||
|
fillRosterFromLedger(roster, ledger);
|
||||||
|
roster.setLedgerId("");
|
||||||
|
insert(roster);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dao().update(TourParticipantRoster.class,
|
||||||
|
Chain.make("joined", false).add("ledgerId", ""),
|
||||||
|
Cnd.where("id", "=", roster.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将配置中以文本保存的可参与人员组别 id 转换为人员范围服务需要的整数。
|
||||||
|
*/
|
||||||
|
private Integer parseGroupId(String activityGroupId) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(activityGroupId);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据配置的可参与人员组别加载人员基础数据。
|
||||||
|
* 这里集中处理空配置和无效组别,方便同步和重建流程复用同一口径。
|
||||||
|
*/
|
||||||
|
private List<View_user> listEligibleUsers(TourSetting setting) {
|
||||||
|
if (setting == null || StrUtil.isBlank(setting.getActivityGroupId())) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
Integer groupId = parseGroupId(setting.getActivityGroupId());
|
||||||
|
if (groupId == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<String> userIds = activityBasicScopeService.listGroupUserIds(groupId);
|
||||||
|
if (CollUtil.isEmpty(userIds)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<View_user> users = new ArrayList<>();
|
||||||
|
for (View_user user : queryUsers(userIds)) {
|
||||||
|
if (user != null && StrUtil.isNotBlank(user.getLoginname())) {
|
||||||
|
users.add(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取当前可参与人员工号集合,用于判断已有台账人员是否已被移出可参与范围。
|
||||||
|
*/
|
||||||
|
private Set<String> collectEligibleJobNos(List<View_user> users) {
|
||||||
|
Set<String> jobNos = new HashSet<>();
|
||||||
|
if (CollUtil.isEmpty(users)) {
|
||||||
|
return jobNos;
|
||||||
|
}
|
||||||
|
for (View_user user : users) {
|
||||||
|
if (user != null && StrUtil.isNotBlank(user.getLoginname())) {
|
||||||
|
jobNos.add(user.getLoginname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jobNos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分批查询用户视图,避免大范围人员组别生成过长的 SQL IN 条件。
|
||||||
|
*/
|
||||||
|
private List<View_user> queryUsers(List<String> userIds) {
|
||||||
|
List<View_user> users = new ArrayList<>();
|
||||||
|
for (int i = 0; i < userIds.size(); i += USER_QUERY_BATCH_SIZE) {
|
||||||
|
int end = Math.min(i + USER_QUERY_BATCH_SIZE, userIds.size());
|
||||||
|
List<String> batch = userIds.subList(i, end);
|
||||||
|
users.addAll(dao().query(View_user.class, Cnd.where("id", "in", batch)));
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增可参与人员,或刷新已存在人员的基础组织信息。
|
||||||
|
*/
|
||||||
|
private void upsertRosterUser(String settingId, View_user user) {
|
||||||
|
TourParticipantRoster roster = fetchRoster(settingId, user.getLoginname());
|
||||||
|
if (roster == null) {
|
||||||
|
roster = new TourParticipantRoster();
|
||||||
|
roster.setSettingId(settingId);
|
||||||
|
roster.setJobNo(user.getLoginname());
|
||||||
|
roster.setJoined(false);
|
||||||
|
fillRosterFromUser(roster, user);
|
||||||
|
insert(roster);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dao().update(TourParticipantRoster.class,
|
||||||
|
Chain.make("userName", StrUtil.blankToDefault(user.getUsername(), ""))
|
||||||
|
.add("unitId", StrUtil.blankToDefault(user.getUnitId(), ""))
|
||||||
|
.add("unitName", StrUtil.blankToDefault(user.getUnitName(), ""))
|
||||||
|
.add("unionId", StrUtil.blankToDefault(user.getUnionId(), ""))
|
||||||
|
.add("unionName", StrUtil.blankToDefault(user.getUnionName(), "")),
|
||||||
|
Cnd.where("id", "=", roster.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回放配置下有效疗休养台账,使重建后的名单保持当前报名状态。
|
||||||
|
* 对已不在可参与人员中但存在台账的人员,继续保留名单记录并返回人员名称用于页面提示。
|
||||||
|
*/
|
||||||
|
private List<String> replayLedgerJoinedState(String settingId, Set<String> eligibleJobNos) {
|
||||||
|
Set<String> retainedUserNames = new LinkedHashSet<>();
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT t.*
|
||||||
|
FROM tour_ledger t
|
||||||
|
INNER JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||||
|
WHERE t.delFlag = 0
|
||||||
|
AND m.settingId = @settingId
|
||||||
|
AND IFNULL(t.jobNo, '') <> ''
|
||||||
|
ORDER BY t.signupTime DESC, t.createdAt DESC
|
||||||
|
""");
|
||||||
|
sql.setParam("settingId", settingId);
|
||||||
|
sql.setEntity(dao().getEntity(TourLedger.class));
|
||||||
|
sql.setCallback(Sqls.callback.entities());
|
||||||
|
dao().execute(sql);
|
||||||
|
|
||||||
|
Set<String> handledJobNos = new HashSet<>();
|
||||||
|
for (TourLedger ledger : sql.getList(TourLedger.class)) {
|
||||||
|
if (ledger == null || StrUtil.isBlank(ledger.getJobNo()) || handledJobNos.contains(ledger.getJobNo())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handledJobNos.add(ledger.getJobNo());
|
||||||
|
if (eligibleJobNos == null || !eligibleJobNos.contains(ledger.getJobNo())) {
|
||||||
|
retainedUserNames.add(displayLedgerUserName(ledger));
|
||||||
|
}
|
||||||
|
markJoinedByLedger(ledger);
|
||||||
|
}
|
||||||
|
return new ArrayList<>(retainedUserNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按配置和工号查找有效人员名单记录,这两个字段是业务逻辑唯一键。
|
||||||
|
*/
|
||||||
|
private TourParticipantRoster fetchRoster(String settingId, String jobNo) {
|
||||||
|
return fetch(Cnd.where("settingId", "=", settingId)
|
||||||
|
.and("jobNo", "=", jobNo)
|
||||||
|
.and("delFlag", "=", false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过台账关联的事项解析配置 id,因为 tour_ledger 本身不直接保存 settingId。
|
||||||
|
*/
|
||||||
|
private String resolveSettingId(TourLedger ledger) {
|
||||||
|
if (ledger == null || StrUtil.isBlank(ledger.getMatterId())) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
TourMatter matter = dao().fetch(TourMatter.class, ledger.getMatterId());
|
||||||
|
return matter == null ? "" : matter.getSettingId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从用户视图复制人员基础信息到名单记录。
|
||||||
|
*/
|
||||||
|
private void fillRosterFromUser(TourParticipantRoster roster, View_user user) {
|
||||||
|
roster.setUserName(StrUtil.blankToDefault(user.getUsername(), ""));
|
||||||
|
roster.setUnitId(StrUtil.blankToDefault(user.getUnitId(), ""));
|
||||||
|
roster.setUnitName(StrUtil.blankToDefault(user.getUnitName(), ""));
|
||||||
|
roster.setUnionId(StrUtil.blankToDefault(user.getUnionId(), ""));
|
||||||
|
roster.setUnionName(StrUtil.blankToDefault(user.getUnionName(), ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当名单同步尚未创建该人员时,从台账复制人员和组织信息补入名单。
|
||||||
|
*/
|
||||||
|
private void fillRosterFromLedger(TourParticipantRoster roster, TourLedger ledger) {
|
||||||
|
roster.setUserName(StrUtil.blankToDefault(ledger.getUserName(), ""));
|
||||||
|
roster.setUnitId(StrUtil.blankToDefault(ledger.getUnitId(), ""));
|
||||||
|
roster.setUnitName(StrUtil.blankToDefault(ledger.getUnitName(), ""));
|
||||||
|
roster.setUnionId(StrUtil.blankToDefault(ledger.getUnionId(), ""));
|
||||||
|
roster.setUnionName(StrUtil.blankToDefault(ledger.getUnionName(), ""));
|
||||||
|
roster.setLedgerId(StrUtil.blankToDefault(ledger.getId(), ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造“有台账但已不在可参与人员范围中”的配置级提示。
|
||||||
|
*/
|
||||||
|
private String buildRetainedLedgerUserMessage(TourSetting setting, List<String> userNames) {
|
||||||
|
return StrUtil.blankToDefault(setting.getConfigName(), "未命名配置")
|
||||||
|
+ "(" + setting.getYear() + "):【" + String.join("、", userNames)
|
||||||
|
+ "】已不在“可参与人员”中,但已生成台账,无法剔除这类人员";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先显示台账姓名,姓名为空时使用工号,保证提示中始终能定位到具体人员。
|
||||||
|
*/
|
||||||
|
private String displayLedgerUserName(TourLedger ledger) {
|
||||||
|
if (StrUtil.isNotBlank(ledger.getUserName())) {
|
||||||
|
return ledger.getUserName();
|
||||||
|
}
|
||||||
|
return StrUtil.blankToDefault(ledger.getJobNo(), "未知人员");
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
@@ -1,8 +1,11 @@
|
|||||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourSettingService;
|
import com.budwk.app.zhgh.dayofficework.tour.service.TourSettingService;
|
||||||
|
import org.nutz.dao.Chain;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
|
||||||
@@ -12,4 +15,19 @@ public class TourSettingServiceImpl extends BaseServiceImpl<TourSetting> impleme
|
|||||||
public TourSettingServiceImpl(Dao dao) {
|
public TourSettingServiceImpl(Dao dao) {
|
||||||
super(dao);
|
super(dao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新疗休养配置,并对报名时间做显式空值覆盖。
|
||||||
|
*
|
||||||
|
* @param tourSetting 页面提交的疗休养配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void updateWithNullableSignupTime(TourSetting tourSetting) {
|
||||||
|
updateIgnoreNull(tourSetting);
|
||||||
|
// updateIgnoreNull 会忽略 null;报名时间允许清空时,需要额外将空值覆盖到数据库。
|
||||||
|
dao().update(TourSetting.class,
|
||||||
|
Chain.make("signupStartTime", StrUtil.blankToDefault(tourSetting.getSignupStartTime(), ""))
|
||||||
|
.add("signupEndTime", StrUtil.blankToDefault(tourSetting.getSignupEndTime(), "")),
|
||||||
|
Cnd.where(TourSetting::getId, "=", tourSetting.getId()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -99,10 +99,13 @@ public class DifficultHelpMineController {
|
|||||||
""");
|
""");
|
||||||
Cnd cnd = Cnd.NEW();
|
Cnd cnd = Cnd.NEW();
|
||||||
pageParam.buildSearch(cnd, "info.");
|
pageParam.buildSearch(cnd, "info.");
|
||||||
if (StrUtil.isAllBlank(pageParam.getPageOrderName(), pageParam.getPageOrderBy())) {
|
// 排序字段必须经过 service 白名单校验,避免前端 pageOrderName 直接进入 ORDER BY。
|
||||||
|
String orderColumn = difficultHelpCommonService.getSafeMineOrderColumn(pageParam.getPageOrderName());
|
||||||
|
String orderBy = PageUtil.getOrder(pageParam.getPageOrderBy());
|
||||||
|
if (StrUtil.isBlank(orderColumn) || StrUtil.isBlank(orderBy)) {
|
||||||
cnd.desc("info.applyTime");
|
cnd.desc("info.applyTime");
|
||||||
} else {
|
} else {
|
||||||
cnd.orderBy("info." + pageParam.getPageOrderName(), PageUtil.getOrder(pageParam.getPageOrderBy()));
|
cnd.orderBy(orderColumn, orderBy);
|
||||||
}
|
}
|
||||||
cnd.groupBy("info.id");
|
cnd.groupBy("info.id");
|
||||||
sql.setCondition(cnd);
|
sql.setCondition(cnd);
|
||||||
|
|||||||
+8
@@ -11,4 +11,12 @@ import com.budwk.app.zhgh.staffbenefit.difficulthelp.models.DifficultHelpInfo;
|
|||||||
* @注释
|
* @注释
|
||||||
*/
|
*/
|
||||||
public interface DifficultHelpCommonService extends BaseService<DifficultHelpInfo> {
|
public interface DifficultHelpCommonService extends BaseService<DifficultHelpInfo> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验困难帮扶“我的申请”列表排序字段,并返回可用于 SQL 排序的安全字段名。
|
||||||
|
*
|
||||||
|
* @param pageOrderName 前端传入的排序字段
|
||||||
|
* @return 白名单内的 SQL 排序字段;不在白名单时返回空字符串
|
||||||
|
*/
|
||||||
|
String getSafeMineOrderColumn(String pageOrderName);
|
||||||
}
|
}
|
||||||
|
|||||||
+25
@@ -1,5 +1,6 @@
|
|||||||
package com.budwk.app.zhgh.staffbenefit.difficulthelp.service.impl;
|
package com.budwk.app.zhgh.staffbenefit.difficulthelp.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||||
import com.budwk.app.zhgh.staffbenefit.difficulthelp.models.DifficultHelpInfo;
|
import com.budwk.app.zhgh.staffbenefit.difficulthelp.models.DifficultHelpInfo;
|
||||||
import com.budwk.app.zhgh.staffbenefit.difficulthelp.service.DifficultHelpCommonService;
|
import com.budwk.app.zhgh.staffbenefit.difficulthelp.service.DifficultHelpCommonService;
|
||||||
@@ -19,4 +20,28 @@ public class DifficultHelpCommonServiceImpl extends BaseServiceImpl<DifficultHel
|
|||||||
public DifficultHelpCommonServiceImpl(Dao dao) {
|
public DifficultHelpCommonServiceImpl(Dao dao) {
|
||||||
super(dao);
|
super(dao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验困难帮扶“我的申请”列表排序字段,避免前端参数直接拼接到 ORDER BY。
|
||||||
|
*
|
||||||
|
* @param pageOrderName 前端传入的排序字段
|
||||||
|
* @return 白名单内的 SQL 排序字段;不在白名单时返回空字符串
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSafeMineOrderColumn(String pageOrderName) {
|
||||||
|
if (StrUtil.isBlank(pageOrderName)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅允许页面表格中存在的可排序字段参与 ORDER BY,未知字段交由 controller 回退默认排序。
|
||||||
|
return switch (pageOrderName) {
|
||||||
|
case "loginName" -> "info.loginName";
|
||||||
|
case "userName" -> "info.userName";
|
||||||
|
case "unionName" -> "info.unionName";
|
||||||
|
case "unitName" -> "info.unitName";
|
||||||
|
case "subsidyStandards" -> "info.subsidyStandards";
|
||||||
|
case "applyTime" -> "info.applyTime";
|
||||||
|
default -> "";
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -108,10 +108,13 @@ public class MemberApplyMineController {
|
|||||||
cnd.and("info.userId", "=", SecurityUtil.getUserId());
|
cnd.and("info.userId", "=", SecurityUtil.getUserId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
// 排序字段必须经过 service 白名单校验,避免前端 pageOrderName 直接进入 ORDER BY。
|
||||||
|
String orderColumn = memberCommonService.getSafeMineOrderColumn(pageForm.getPageOrderName());
|
||||||
|
String orderBy = PageUtil.getOrder(pageForm.getPageOrderBy());
|
||||||
|
if (StrUtil.isBlank(orderColumn) || StrUtil.isBlank(orderBy)) {
|
||||||
cnd.desc("info.applyDateTime");
|
cnd.desc("info.applyDateTime");
|
||||||
} else {
|
} else {
|
||||||
cnd.orderBy("info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
cnd.orderBy(orderColumn, orderBy);
|
||||||
}
|
}
|
||||||
cnd.groupBy("t.id");
|
cnd.groupBy("t.id");
|
||||||
sql.setCondition(cnd);
|
sql.setCondition(cnd);
|
||||||
|
|||||||
+5
-2
@@ -111,10 +111,13 @@ public class MemberChangeMineController {
|
|||||||
cnd.and("info.userId", "=", SecurityUtil.getUserId());
|
cnd.and("info.userId", "=", SecurityUtil.getUserId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
// 排序字段必须经过 service 白名单校验,避免前端 pageOrderName 直接进入 ORDER BY。
|
||||||
|
String orderColumn = memberCommonService.getSafeMineOrderColumn(pageForm.getPageOrderName());
|
||||||
|
String orderBy = PageUtil.getOrder(pageForm.getPageOrderBy());
|
||||||
|
if (StrUtil.isBlank(orderColumn) || StrUtil.isBlank(orderBy)) {
|
||||||
cnd.desc("info.applyDateTime");
|
cnd.desc("info.applyDateTime");
|
||||||
} else {
|
} else {
|
||||||
cnd.orderBy("info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
cnd.orderBy(orderColumn, orderBy);
|
||||||
}
|
}
|
||||||
cnd.groupBy("t.id");
|
cnd.groupBy("t.id");
|
||||||
sql.setCondition(cnd);
|
sql.setCondition(cnd);
|
||||||
|
|||||||
@@ -115,4 +115,12 @@ public interface MemberCommonService extends BaseService<Sys_user> {
|
|||||||
* @param type 类型: apply or change
|
* @param type 类型: apply or change
|
||||||
*/
|
*/
|
||||||
void validateApplyOrChangeIsDoing(String userId, String type);
|
void validateApplyOrChangeIsDoing(String userId, String type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验会员申请、会员变更“我的申请”列表排序字段,并返回可用于 SQL 排序的安全字段名。
|
||||||
|
*
|
||||||
|
* @param pageOrderName 前端传入的排序字段
|
||||||
|
* @return 白名单内的 SQL 排序字段;不在白名单时返回空字符串
|
||||||
|
*/
|
||||||
|
String getSafeMineOrderColumn(String pageOrderName);
|
||||||
}
|
}
|
||||||
|
|||||||
+30
@@ -546,6 +546,36 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验会员申请、会员变更“我的申请”列表排序字段,避免前端参数直接拼接到 ORDER BY。
|
||||||
|
*
|
||||||
|
* @param pageOrderName 前端传入的排序字段
|
||||||
|
* @return 白名单内的 SQL 排序字段;不在白名单时返回空字符串
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSafeMineOrderColumn(String pageOrderName) {
|
||||||
|
if (StrUtil.isBlank(pageOrderName)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅允许页面表格中存在的可排序字段参与 ORDER BY,未知字段交由 controller 回退默认排序。
|
||||||
|
return switch (pageOrderName) {
|
||||||
|
case "loginName" -> "info.loginName";
|
||||||
|
case "userName" -> "info.userName";
|
||||||
|
case "loginname" -> "info.loginname";
|
||||||
|
case "username" -> "info.username";
|
||||||
|
case "sex" -> "info.sex";
|
||||||
|
case "userState" -> "info.userState";
|
||||||
|
case "personType" -> "info.personType";
|
||||||
|
case "preparedBy" -> "info.preparedBy";
|
||||||
|
case "unionName" -> "info.unionName";
|
||||||
|
case "unitName" -> "info.unitName";
|
||||||
|
case "changeTypes" -> "info.changeTypes";
|
||||||
|
case "applyDateTime" -> "info.applyDateTime";
|
||||||
|
default -> "";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private String booleanVerification(Object value) {
|
private String booleanVerification(Object value) {
|
||||||
// 检查是否为null或空字符串
|
// 检查是否为null或空字符串
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
-- 问卷服务菜单初始化脚本。
|
||||||
|
-- 使用 permission 做幂等判断,避免同一环境重复执行时插入重复菜单。
|
||||||
|
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||||
|
SELECT
|
||||||
|
'8f3d9c78a9f04b5b8b7f0f6a00010001',
|
||||||
|
'',
|
||||||
|
LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'),
|
||||||
|
'问卷服务',
|
||||||
|
'Qsv',
|
||||||
|
'menu',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'ti-check-box',
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
'qsv',
|
||||||
|
NULL,
|
||||||
|
992,
|
||||||
|
1,
|
||||||
|
'',
|
||||||
|
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||||
|
'',
|
||||||
|
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||||
|
0,
|
||||||
|
'PC',
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
'w',
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
FROM sys_menu
|
||||||
|
WHERE (parentId = '' OR parentId IS NULL)
|
||||||
|
AND CHAR_LENGTH(path) = 4
|
||||||
|
HAVING NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'qsv') t);
|
||||||
|
|
||||||
|
-- 新建问卷入口,对应 /platform/qsv/new。
|
||||||
|
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||||
|
SELECT '8f3d9c78a9f04b5b8b7f0f6a00010002', p.id, CONCAT(p.path, '0001'), '新建问卷', 'Qsv New', 'menu', '/platform/qsv/new', 'data-pjax', '', 1, 0, 'qsv.new', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
|
||||||
|
FROM sys_menu p
|
||||||
|
WHERE p.permission = 'qsv'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'qsv.new') t);
|
||||||
|
|
||||||
|
-- 问卷活动管理入口,对应 /platform/qsv/activity。
|
||||||
|
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||||
|
SELECT '8f3d9c78a9f04b5b8b7f0f6a00010003', p.id, CONCAT(p.path, '0002'), '问卷管理', 'Qsv Activity', 'menu', '/platform/qsv/activity', 'data-pjax', '', 1, 0, 'qsv.activity', NULL, 2, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'w', 0, 0
|
||||||
|
FROM sys_menu p
|
||||||
|
WHERE p.permission = 'qsv'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'qsv.activity') t);
|
||||||
|
|
||||||
|
-- 在线答题管理入口,对应 /platform/qsv/online。
|
||||||
|
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||||
|
SELECT '8f3d9c78a9f04b5b8b7f0f6a00010004', p.id, CONCAT(p.path, '0003'), '在线答题', 'Qsv Online', 'menu', '/platform/qsv/online', 'data-pjax', '', 1, 0, 'qsv.online', NULL, 3, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'z', 0, 0
|
||||||
|
FROM sys_menu p
|
||||||
|
WHERE p.permission = 'qsv'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'qsv.online') t);
|
||||||
|
|
||||||
|
-- 问卷统计入口,对应 /platform/qsv/survey。
|
||||||
|
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||||
|
SELECT '8f3d9c78a9f04b5b8b7f0f6a00010005', p.id, CONCAT(p.path, '0004'), '问卷统计', 'Qsv Survey', 'menu', '/platform/qsv/survey', 'data-pjax', '', 1, 0, 'qsv.survey', NULL, 4, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'w', 0, 0
|
||||||
|
FROM sys_menu p
|
||||||
|
WHERE p.permission = 'qsv'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'qsv.survey') t);
|
||||||
|
|
||||||
|
-- 答题排名入口,对应 /platform/qsv/quizRank。
|
||||||
|
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||||
|
SELECT '8f3d9c78a9f04b5b8b7f0f6a00010006', p.id, CONCAT(p.path, '0005'), '答题排名', 'Qsv Quiz Rank', 'menu', '/platform/qsv/quizRank', 'data-pjax', '', 1, 0, 'qsv.quiz.rank', NULL, 5, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'd', 0, 0
|
||||||
|
FROM sys_menu p
|
||||||
|
WHERE p.permission = 'qsv'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'qsv.quiz.rank') t);
|
||||||
|
|
||||||
|
-- 题库管理入口,对应 /platform/qsv/bank。
|
||||||
|
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||||
|
SELECT '8f3d9c78a9f04b5b8b7f0f6a00010007', p.id, CONCAT(p.path, '0006'), '题库管理', 'Qsv Bank', 'menu', '/platform/qsv/bank', 'data-pjax', '', 1, 0, 'qsv.bank', NULL, 6, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 't', 0, 0
|
||||||
|
FROM sys_menu p
|
||||||
|
WHERE p.permission = 'qsv'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'qsv.bank') t);
|
||||||
|
|
||||||
|
-- 答题记录删除按钮权限,对应 qsv.quiz.rank.delete。
|
||||||
|
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||||
|
SELECT '8f3d9c78a9f04b5b8b7f0f6a00010008', p.id, CONCAT(p.path, '0001'), '删除答题记录', 'Delete Quiz Record', 'data', '', '', '', 0, 0, 'qsv.quiz.rank.delete', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 's', 0, 0
|
||||||
|
FROM sys_menu p
|
||||||
|
WHERE p.permission = 'qsv.quiz.rank'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'qsv.quiz.rank.delete') t);
|
||||||
|
|
||||||
|
-- 移动端问卷服务入口,对应 /platform/h5/qsv。
|
||||||
|
-- @SaCheckPermission 手机端权限需要使用 h5. 前缀。
|
||||||
|
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||||
|
SELECT
|
||||||
|
'8f3d9c78a9f04b5b8b7f0f6a00011001',
|
||||||
|
'',
|
||||||
|
LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'),
|
||||||
|
'问卷服务',
|
||||||
|
'Qsv H5',
|
||||||
|
'menu',
|
||||||
|
'/platform/h5/qsv',
|
||||||
|
'data-pjax',
|
||||||
|
'records-o',
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
'h5.qsv',
|
||||||
|
NULL,
|
||||||
|
992,
|
||||||
|
0,
|
||||||
|
'',
|
||||||
|
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||||
|
'',
|
||||||
|
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||||
|
0,
|
||||||
|
'H5',
|
||||||
|
NULL,
|
||||||
|
'records-o',
|
||||||
|
'w',
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
FROM sys_menu
|
||||||
|
WHERE (parentId = '' OR parentId IS NULL)
|
||||||
|
AND CHAR_LENGTH(path) = 4
|
||||||
|
HAVING NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.qsv') t);
|
||||||
|
|
||||||
|
-- 默认给系统管理员授权问卷服务所有菜单与按钮权限。
|
||||||
|
INSERT INTO sys_role_menu (roleId, menuId)
|
||||||
|
SELECT r.id, m.id
|
||||||
|
FROM sys_role r
|
||||||
|
JOIN sys_menu m ON m.permission IN (
|
||||||
|
'qsv',
|
||||||
|
'qsv.new',
|
||||||
|
'qsv.activity',
|
||||||
|
'qsv.online',
|
||||||
|
'qsv.survey',
|
||||||
|
'qsv.quiz.rank',
|
||||||
|
'qsv.quiz.rank.delete',
|
||||||
|
'qsv.bank',
|
||||||
|
'h5.qsv'
|
||||||
|
)
|
||||||
|
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
|
||||||
|
WHERE r.code = 'SYSADMIN'
|
||||||
|
AND rm.roleId IS NULL;
|
||||||
@@ -136,7 +136,7 @@ JOIN sys_menu m ON m.permission IN (
|
|||||||
'tour.undertake'
|
'tour.undertake'
|
||||||
)
|
)
|
||||||
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
|
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
|
||||||
WHERE r.code = 'BRANCH_UNION_CHAIRMAN'
|
WHERE r.code IN ('BRANCH_UNION_CHAIRMAN', 'TOUR_APPLICANT')
|
||||||
AND rm.roleId IS NULL;
|
AND rm.roleId IS NULL;
|
||||||
|
|
||||||
-- 普惠疗休养移动端菜单。
|
-- 普惠疗休养移动端菜单。
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `qsv_subject`
|
||||||
|
ADD COLUMN `componentType` VARCHAR(30) NULL COMMENT '题目组件展示类型(RADIO普通单选,DROPDOWN下拉选择)' AFTER `type`;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- 疗休养台账增加成团信息字段,用于线路成团自动分配和人员调整。
|
||||||
|
ALTER TABLE `tour_ledger`
|
||||||
|
ADD COLUMN `groupCode` varchar(20) DEFAULT NULL COMMENT '成团编码' AFTER `lineType`,
|
||||||
|
ADD COLUMN `groupName` varchar(50) DEFAULT NULL COMMENT '成团名称' AFTER `groupCode`;
|
||||||
|
|
||||||
|
ALTER TABLE `tour_ledger`
|
||||||
|
ADD KEY `idx_tour_ledger_matter_line_group` (`matterId`, `lineId`, `groupCode`, `delFlag`);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE tour_ledger
|
||||||
|
ADD COLUMN undertakeUnionId VARCHAR(32) NULL COMMENT '领办工会编码' AFTER groupName,
|
||||||
|
ADD COLUMN undertakeUnionName VARCHAR(100) NULL COMMENT '领办工会' AFTER undertakeUnionId;
|
||||||
|
|
||||||
|
CREATE INDEX idx_tour_ledger_undertake_union ON tour_ledger (matterId, groupCode, undertakeUnionId);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- Convert tour line sort code to numeric type for stable numeric ascending order.
|
||||||
|
UPDATE tour_line
|
||||||
|
SET lineCode = '0'
|
||||||
|
WHERE lineCode IS NULL
|
||||||
|
OR TRIM(lineCode) = ''
|
||||||
|
OR lineCode NOT REGEXP '^[0-9]+$';
|
||||||
|
|
||||||
|
ALTER TABLE tour_line
|
||||||
|
MODIFY COLUMN lineCode INT(11) NOT NULL DEFAULT 0 COMMENT '排序编号';
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- 疗休养事项选择线路新增最多报名人数,用于报名提交容量控制;为空或0表示不限制。
|
||||||
|
ALTER TABLE `tour_matter`
|
||||||
|
ADD COLUMN `maxSignupPeople` int DEFAULT NULL COMMENT '最多报名人数' AFTER `maxGroupPeople`;
|
||||||
@@ -10,6 +10,17 @@ CREATE TABLE IF NOT EXISTS `tour_matter` (
|
|||||||
`undertakeStatus` varchar(20) DEFAULT 'unclaimed' COMMENT '领办状态 claimed已领 unclaimed未领',
|
`undertakeStatus` varchar(20) DEFAULT 'unclaimed' COMMENT '领办状态 claimed已领 unclaimed未领',
|
||||||
`organizationType` varchar(100) DEFAULT NULL COMMENT '组织形式',
|
`organizationType` varchar(100) DEFAULT NULL COMMENT '组织形式',
|
||||||
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
|
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
|
||||||
|
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
|
||||||
|
`signupStartTime` varchar(20) DEFAULT NULL COMMENT '报名开始时间',
|
||||||
|
`signupEndTime` varchar(20) DEFAULT NULL COMMENT '报名结束时间',
|
||||||
|
`travelStartTime` varchar(20) DEFAULT NULL COMMENT '出行开始时间',
|
||||||
|
`travelEndTime` varchar(20) DEFAULT NULL COMMENT '出行结束时间',
|
||||||
|
`contactName` varchar(30) DEFAULT NULL COMMENT '联系人',
|
||||||
|
`contactPhone` varchar(30) DEFAULT NULL COMMENT '联系方式',
|
||||||
|
`minGroupPeople` int DEFAULT NULL COMMENT '最少成团人数',
|
||||||
|
`maxGroupPeople` int DEFAULT NULL COMMENT '最多成团人数',
|
||||||
|
`maxSignupPeople` int DEFAULT NULL COMMENT '最多报名人数',
|
||||||
|
`estimatedCost` decimal(10,2) DEFAULT NULL COMMENT '预计费用',
|
||||||
`enabled` tinyint(1) DEFAULT 1 COMMENT '事项状态',
|
`enabled` tinyint(1) DEFAULT 1 COMMENT '事项状态',
|
||||||
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
|
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
|
||||||
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
|
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
|
||||||
@@ -22,5 +33,6 @@ CREATE TABLE IF NOT EXISTS `tour_matter` (
|
|||||||
KEY `idx_tour_matter_creator` (`creatorUserId`),
|
KEY `idx_tour_matter_creator` (`creatorUserId`),
|
||||||
KEY `idx_tour_matter_setting` (`settingId`),
|
KEY `idx_tour_matter_setting` (`settingId`),
|
||||||
KEY `idx_tour_matter_union` (`unionId`),
|
KEY `idx_tour_matter_union` (`unionId`),
|
||||||
KEY `idx_tour_matter_org_type` (`organizationType`)
|
KEY `idx_tour_matter_org_type` (`organizationType`),
|
||||||
|
KEY `idx_tour_matter_line` (`lineId`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `tour_participant_roster`
|
||||||
|
(
|
||||||
|
`id` varchar(32) NOT NULL COMMENT 'ID',
|
||||||
|
`settingId` varchar(32) NOT NULL COMMENT '疗休养配置ID',
|
||||||
|
`jobNo` varchar(50) NOT NULL COMMENT '工号',
|
||||||
|
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
|
||||||
|
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
|
||||||
|
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
|
||||||
|
`unionId` varchar(32) DEFAULT NULL COMMENT '所在工会ID',
|
||||||
|
`unionName` varchar(100) DEFAULT NULL COMMENT '所在工会',
|
||||||
|
`joined` tinyint(1) DEFAULT 0 COMMENT '是否参加:0否,1是',
|
||||||
|
`ledgerId` varchar(32) DEFAULT NULL COMMENT '疗休养台账ID',
|
||||||
|
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
|
||||||
|
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
|
||||||
|
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_tour_participant_roster_setting_job` (`settingId`, `jobNo`),
|
||||||
|
KEY `idx_tour_participant_roster_setting` (`settingId`),
|
||||||
|
KEY `idx_tour_participant_roster_job` (`jobNo`),
|
||||||
|
KEY `idx_tour_participant_roster_joined` (`joined`),
|
||||||
|
KEY `idx_tour_participant_roster_ledger` (`ledgerId`),
|
||||||
|
KEY `idx_tour_participant_roster_union` (`unionId`)
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4 COMMENT ='疗休养人员名单';
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- 疗休养人员名单查询菜单。
|
||||||
|
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||||
|
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10014', p.id, CONCAT(p.path, '0013'), '疗休养人员名单', 'Tour Participant Roster', 'menu', '/platform/tour/participantRoster', 'data-pjax', '', 1, 0, 'tour.participantRoster', NULL, 13, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
|
||||||
|
FROM sys_menu p
|
||||||
|
WHERE p.permission = 'tour'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.participantRoster') t);
|
||||||
|
|
||||||
|
-- 默认给系统管理员授权,其它角色可以在角色管理中按需分配。
|
||||||
|
INSERT INTO sys_role_menu (roleId, menuId)
|
||||||
|
SELECT r.id, m.id
|
||||||
|
FROM sys_role r
|
||||||
|
JOIN sys_menu m ON m.permission = 'tour.participantRoster'
|
||||||
|
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
|
||||||
|
WHERE r.code = 'SYSADMIN'
|
||||||
|
AND rm.roleId IS NULL;
|
||||||
@@ -121,6 +121,17 @@ CREATE TABLE IF NOT EXISTS `tour_matter` (
|
|||||||
`undertakeStatus` varchar(20) DEFAULT 'unclaimed' COMMENT '领办状态 claimed已领 unclaimed未领',
|
`undertakeStatus` varchar(20) DEFAULT 'unclaimed' COMMENT '领办状态 claimed已领 unclaimed未领',
|
||||||
`organizationType` varchar(100) DEFAULT NULL COMMENT '组织形式',
|
`organizationType` varchar(100) DEFAULT NULL COMMENT '组织形式',
|
||||||
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
|
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
|
||||||
|
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
|
||||||
|
`signupStartTime` varchar(20) DEFAULT NULL COMMENT '报名开始时间',
|
||||||
|
`signupEndTime` varchar(20) DEFAULT NULL COMMENT '报名结束时间',
|
||||||
|
`travelStartTime` varchar(20) DEFAULT NULL COMMENT '出行开始时间',
|
||||||
|
`travelEndTime` varchar(20) DEFAULT NULL COMMENT '出行结束时间',
|
||||||
|
`contactName` varchar(30) DEFAULT NULL COMMENT '联系人',
|
||||||
|
`contactPhone` varchar(30) DEFAULT NULL COMMENT '联系方式',
|
||||||
|
`minGroupPeople` int DEFAULT NULL COMMENT '最少成团人数',
|
||||||
|
`maxGroupPeople` int DEFAULT NULL COMMENT '最多成团人数',
|
||||||
|
`maxSignupPeople` int DEFAULT NULL COMMENT '最多报名人数',
|
||||||
|
`estimatedCost` decimal(10,2) DEFAULT NULL COMMENT '预计费用',
|
||||||
`enabled` tinyint(1) DEFAULT 1 COMMENT '事项状态',
|
`enabled` tinyint(1) DEFAULT 1 COMMENT '事项状态',
|
||||||
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
|
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
|
||||||
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
|
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
|
||||||
@@ -133,7 +144,8 @@ CREATE TABLE IF NOT EXISTS `tour_matter` (
|
|||||||
KEY `idx_tour_matter_creator` (`creatorUserId`),
|
KEY `idx_tour_matter_creator` (`creatorUserId`),
|
||||||
KEY `idx_tour_matter_setting` (`settingId`),
|
KEY `idx_tour_matter_setting` (`settingId`),
|
||||||
KEY `idx_tour_matter_union` (`unionId`),
|
KEY `idx_tour_matter_union` (`unionId`),
|
||||||
KEY `idx_tour_matter_org_type` (`organizationType`)
|
KEY `idx_tour_matter_org_type` (`organizationType`),
|
||||||
|
KEY `idx_tour_matter_line` (`lineId`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
|
||||||
|
|
||||||
-- 疗休养事项批次表。
|
-- 疗休养事项批次表。
|
||||||
|
|||||||
@@ -12,5 +12,5 @@ JOIN sys_menu m ON m.permission IN (
|
|||||||
'tour.undertake'
|
'tour.undertake'
|
||||||
)
|
)
|
||||||
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
|
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
|
||||||
WHERE r.code IN ('SYSADMIN', 'BRANCH_UNION_CHAIRMAN')
|
WHERE r.code IN ('SYSADMIN', 'BRANCH_UNION_CHAIRMAN', 'TOUR_APPLICANT')
|
||||||
AND rm.roleId IS NULL;
|
AND rm.roleId IS NULL;
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
const tourTableHeightMixins = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tourTableHeight: Math.max(120, window.innerHeight - 378),
|
||||||
|
tourTableResizeTimer: null,
|
||||||
|
tourTableHeightReady: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateTourTableHeight() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const table = this.$refs.tourMainTable || this.$refs.tableRef || this.$refs.table
|
||||||
|
const tableEl = table && table.$el ? table.$el : null
|
||||||
|
if (!tableEl) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据当前屏幕、表格位置、分页区域动态计算主列表高度,避免整页被数据行撑出滚动条。
|
||||||
|
const mainContent = document.querySelector(".sub-app-container-main-content")
|
||||||
|
const viewportBottom = mainContent ? mainContent.getBoundingClientRect().bottom : window.innerHeight
|
||||||
|
const tableTop = tableEl.getBoundingClientRect().top
|
||||||
|
const pagination = this.$el.querySelector(".el-pagination-container")
|
||||||
|
const paginationHeight = pagination ? pagination.getBoundingClientRect().height : 46
|
||||||
|
const reserveHeight = Number(this.tourTableReserveHeight || 42)
|
||||||
|
const minHeight = Number(this.tourTableMinHeight || 120)
|
||||||
|
const height = Math.floor(viewportBottom - tableTop - paginationHeight - reserveHeight)
|
||||||
|
this.tourTableHeight = height > minHeight ? height : minHeight
|
||||||
|
this.tourTableHeightReady = true
|
||||||
|
|
||||||
|
// Element 表格高度变化后需要重新布局,否则固定列和表头可能错位。
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (table && table.doLayout) {
|
||||||
|
table.doLayout()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleTourTableResize() {
|
||||||
|
if (this.tourTableResizeTimer) {
|
||||||
|
clearTimeout(this.tourTableResizeTimer)
|
||||||
|
}
|
||||||
|
this.tourTableResizeTimer = setTimeout(() => {
|
||||||
|
this.updateTourTableHeight()
|
||||||
|
this.tourTableResizeTimer = null
|
||||||
|
}, 120)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateTourTableHeight()
|
||||||
|
window.addEventListener("resize", this.handleTourTableResize)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.tourTableResizeTimer) {
|
||||||
|
clearTimeout(this.tourTableResizeTimer)
|
||||||
|
this.tourTableResizeTimer = null
|
||||||
|
}
|
||||||
|
window.removeEventListener("resize", this.handleTourTableResize)
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
tableData() {
|
||||||
|
this.updateTourTableHeight()
|
||||||
|
},
|
||||||
|
"pageForm.pageSize"() {
|
||||||
|
this.updateTourTableHeight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="search-item" v-if="is_sysadmin || is_A06">
|
<div class="search-item" v-if="has_school_scope_view_permission">
|
||||||
<div class="search-item-label">所属工会</div>
|
<div class="search-item-label">所属工会</div>
|
||||||
<div class="search-item-option">
|
<div class="search-item-option">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -196,6 +196,12 @@ module.exports = {
|
|||||||
is_A06() {
|
is_A06() {
|
||||||
return this.roleData.is_A06
|
return this.roleData.is_A06
|
||||||
},
|
},
|
||||||
|
is_school_union_activity_admin() {
|
||||||
|
return this.roleData.is_school_union_activity_admin
|
||||||
|
},
|
||||||
|
has_school_scope_view_permission() {
|
||||||
|
return this.is_sysadmin || this.is_A06 || this.is_school_union_activity_admin
|
||||||
|
},
|
||||||
is_H04() {
|
is_H04() {
|
||||||
return this.roleData.is_H04
|
return this.roleData.is_H04
|
||||||
},
|
},
|
||||||
@@ -292,7 +298,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
async flushUnits() {
|
async flushUnits() {
|
||||||
this.$set(this.pageForm, "unitId", "")
|
this.$set(this.pageForm, "unitId", "")
|
||||||
if (this.is_A06 || this.is_sysadmin) {
|
if (this.has_school_scope_view_permission) {
|
||||||
this.units = await this.$businessTool.listUnit(this.pageForm.unionId)
|
this.units = await this.$businessTool.listUnit(this.pageForm.unionId)
|
||||||
// this.ActivityUnits = await getActivityUnits(this.pageForm.activityUnionId)
|
// this.ActivityUnits = await getActivityUnits(this.pageForm.activityUnionId)
|
||||||
} else {
|
} else {
|
||||||
@@ -328,11 +334,11 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
|
await this.getRolesAndUnion()
|
||||||
this.unions = await this.$businessTool.listUnion()
|
this.unions = await this.$businessTool.listUnion()
|
||||||
// this.ActivityUnions = await getActivityUnions()
|
// this.ActivityUnions = await getActivityUnions()
|
||||||
await this.flushUnits()
|
await this.flushUnits()
|
||||||
await this.getActivityGroup()
|
await this.getActivityGroup()
|
||||||
await this.getRolesAndUnion()
|
|
||||||
this.viewGroupName()
|
this.viewGroupName()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row class="query-row" v-if="is_A06 === true || is_sysadmin === true || is_H04 === true">
|
<el-row class="query-row" v-if="has_school_scope_view_permission === true || is_H04 === true">
|
||||||
<el-col class="query-title hidden-xs-only">会员类型:</el-col>
|
<el-col class="query-title hidden-xs-only">会员类型:</el-col>
|
||||||
<el-col class="query-content">
|
<el-col class="query-content">
|
||||||
<el-tag
|
<el-tag
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row class="query-row" v-if="is_A06 === true || is_sysadmin === true || is_H04 === true">
|
<el-row class="query-row" v-if="has_school_scope_view_permission === true || is_H04 === true">
|
||||||
<el-col class="query-title">教职工类别:</el-col>
|
<el-col class="query-title">教职工类别:</el-col>
|
||||||
<el-col class="query-content">
|
<el-col class="query-content">
|
||||||
<el-tag
|
<el-tag
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row class="query-row" v-if="is_A06 === true || is_sysadmin === true || is_H04 === true">
|
<el-row class="query-row" v-if="has_school_scope_view_permission === true || is_H04 === true">
|
||||||
<el-col class="query-title">在职状态:</el-col>
|
<el-col class="query-title">在职状态:</el-col>
|
||||||
<el-col class="query-content">
|
<el-col class="query-content">
|
||||||
<el-tag
|
<el-tag
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row class="query-row" v-if="is_A06 === true || is_sysadmin === true || is_H04 === true">
|
<el-row class="query-row" v-if="has_school_scope_view_permission === true || is_H04 === true">
|
||||||
<el-col class="query-title">部门查询:</el-col>
|
<el-col class="query-title">部门查询:</el-col>
|
||||||
<el-col class="query-content">
|
<el-col class="query-content">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
flushUnits()
|
flushUnits()
|
||||||
doSearch()
|
doSearch()
|
||||||
"
|
"
|
||||||
:clearable="is_A06 === true || is_sysadmin === true"
|
:clearable="has_school_scope_view_permission === true"
|
||||||
filterable
|
filterable
|
||||||
placeholder="所属工会"
|
placeholder="所属工会"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<template v-if="is_A06 === true || is_sysadmin === true">
|
<template v-if="has_school_scope_view_permission === true">
|
||||||
<!--<el-row class="query-row">
|
<!--<el-row class="query-row">
|
||||||
<el-col class="query-title">活动部门:</el-col>
|
<el-col class="query-title">活动部门:</el-col>
|
||||||
<el-col class="query-content">
|
<el-col class="query-content">
|
||||||
@@ -265,7 +265,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row class="query-row" v-if="is_A06 === true || is_sysadmin === true || is_H04 === true">
|
<el-row class="query-row" v-if="has_school_scope_view_permission === true || is_H04 === true">
|
||||||
<el-col class="query-title">年龄范围:</el-col>
|
<el-col class="query-title">年龄范围:</el-col>
|
||||||
<el-col class="query-content">
|
<el-col class="query-content">
|
||||||
<el-row type="flex" style="align-items: center">
|
<el-row type="flex" style="align-items: center">
|
||||||
@@ -476,6 +476,12 @@ module.exports = {
|
|||||||
is_A06() {
|
is_A06() {
|
||||||
return this.roleData.is_A06
|
return this.roleData.is_A06
|
||||||
},
|
},
|
||||||
|
is_school_union_activity_admin() {
|
||||||
|
return this.roleData.is_school_union_activity_admin
|
||||||
|
},
|
||||||
|
has_school_scope_view_permission() {
|
||||||
|
return this.is_sysadmin || this.is_A06 || this.is_school_union_activity_admin
|
||||||
|
},
|
||||||
is_H04() {
|
is_H04() {
|
||||||
return this.roleData.is_H04
|
return this.roleData.is_H04
|
||||||
},
|
},
|
||||||
@@ -561,7 +567,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
async flushUnits() {
|
async flushUnits() {
|
||||||
this.$set(this.pageForm, "unitId", "")
|
this.$set(this.pageForm, "unitId", "")
|
||||||
if (this.is_sysadmin || this.is_A06) {
|
if (this.has_school_scope_view_permission) {
|
||||||
this.units = await this.$businessTool.listUnit(this.pageForm.unionId)
|
this.units = await this.$businessTool.listUnit(this.pageForm.unionId)
|
||||||
// this.activityUnits = await getActivityUnits(this.pageForm.activityUnionId)
|
// this.activityUnits = await getActivityUnits(this.pageForm.activityUnionId)
|
||||||
} else {
|
} else {
|
||||||
@@ -662,7 +668,7 @@ module.exports = {
|
|||||||
this.listSession()
|
this.listSession()
|
||||||
await this.getRolesAndUnion()
|
await this.getRolesAndUnion()
|
||||||
this.clubOptions = await this.$businessTool.listCLubByRole()
|
this.clubOptions = await this.$businessTool.listCLubByRole()
|
||||||
if ((this.is_sysadmin || this.is_A06 || this.is_H02) === false && this.is_H04 === true) {
|
if ((this.has_school_scope_view_permission || this.is_H02) === false && this.is_H04 === true) {
|
||||||
this.unions = this.$businessTool.listUnion(this.unionid)
|
this.unions = this.$businessTool.listUnion(this.unionid)
|
||||||
this.$set(this.pageForm, "unionId", this.unions[0].id)
|
this.$set(this.pageForm, "unionId", this.unions[0].id)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
<script src="${base!}/assets/platform/js/util/coordinateUtil.js"></script>
|
<script src="${base!}/assets/platform/js/util/coordinateUtil.js"></script>
|
||||||
<script src="${base!}/assets/platform/js/main.js"></script>
|
<script src="${base!}/assets/platform/js/main.js"></script>
|
||||||
<script src="${base!}/assets/platform/js/mixin/initTableMixins.js"></script>
|
<script src="${base!}/assets/platform/js/mixin/initTableMixins.js"></script>
|
||||||
|
<script src="${base!}/assets/platform/js/mixin/tourTableHeightMixins.js?v=2026072103"></script>
|
||||||
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
|
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
|
||||||
<script src="${base!}/assets/platform/js/mixin/flowMixins.js"></script>
|
<script src="${base!}/assets/platform/js/mixin/flowMixins.js"></script>
|
||||||
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
|
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!--#
|
<!--#
|
||||||
layout("/layouts/platform.html"){
|
layout("/layouts/platform.html"){
|
||||||
#-->
|
#-->
|
||||||
|
|
||||||
@@ -51,36 +51,23 @@ layout("/layouts/platform.html"){
|
|||||||
border
|
border
|
||||||
:default-sort="{prop: 'lineName', order: 'ascending'}"
|
:default-sort="{prop: 'lineName', order: 'ascending'}"
|
||||||
:row-class-name="tableRowClassName"
|
:row-class-name="tableRowClassName"
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
ref="tourMainTable"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="出行时段" prop="travelPeriod" width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="出行时段" prop="travelPeriod" width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column v-if="false" label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="线路类型" prop="lineType" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="线路类型" prop="lineType" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="最少成团人数" prop="minGroupPeople" width="140" sortable="custom" align="center" header-align="center">
|
|
||||||
<template slot-scope="{row}">
|
|
||||||
{{ row.minGroupPeople || 0 }}人
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="最多成团人数" prop="maxGroupPeople" width="140" sortable="custom" align="center" header-align="center">
|
|
||||||
<template slot-scope="{row}">
|
|
||||||
{{ row.maxGroupPeople || 0 }}人
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="报名人数" prop="signupCount" width="120" sortable="custom" align="center" header-align="center">
|
<el-table-column label="报名人数" prop="signupCount" width="120" sortable="custom" align="center" header-align="center">
|
||||||
<template slot-scope="{row}">
|
<template slot-scope="{row}">
|
||||||
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">{{ row.signupCount || 0 }}人</el-tag>
|
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">{{ row.signupCount || 0 }}人</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="成团状态" prop="groupStatusName" width="120" sortable="custom" align="center" header-align="center">
|
|
||||||
<template slot-scope="{row}">
|
|
||||||
<el-tag size="mini" :type="groupStatusType(row.groupStatus)">{{ row.groupStatusName || '未成团' }}</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
|
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
|
||||||
<template slot-scope="{row}">
|
<template slot-scope="{row}">
|
||||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
<el-button size="mini" type="primary" @click="openView(row)">组团</el-button>
|
||||||
<el-button size="mini" type="primary" @click="exportLine(row)">导出</el-button>
|
<el-button size="mini" type="primary" @click="exportLine(row)">导出</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -120,33 +107,48 @@ layout("/layouts/platform.html"){
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="mt10" shadow="never">
|
<el-card class="mt10" shadow="never">
|
||||||
<div class="tour-group-view-title">报名人员列表</div>
|
<div class="tour-group-view-header">
|
||||||
|
<div class="tour-group-view-title">报名人员列表</div>
|
||||||
|
<div class="tour-group-options">
|
||||||
|
<el-tag
|
||||||
|
v-for="item in viewGroupOptions"
|
||||||
|
:key="item.groupCode"
|
||||||
|
:class="['tour-group-option', viewForm.groupCode === item.groupCode ? 'is-active' : '']"
|
||||||
|
size="small"
|
||||||
|
:type="viewForm.groupCode === item.groupCode ? 'success' : 'primary'"
|
||||||
|
@click="toggleGroupFilter(item)">
|
||||||
|
{{ formatGroupOption(item) }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="tour-group-actions">
|
||||||
|
<el-button size="mini" type="primary" :loading="assignGroupLoading" @click="confirmAssignGroup">分配成团</el-button>
|
||||||
|
<el-button size="mini" type="primary" @click="openAdjustGroup">人员调整</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
|
ref="viewTable"
|
||||||
v-loading="viewLoading"
|
v-loading="viewLoading"
|
||||||
:data="viewTableData"
|
:data="viewTableData"
|
||||||
:size="tableSize"
|
:size="tableSize"
|
||||||
border
|
border
|
||||||
|
@selection-change="viewSelectionChange"
|
||||||
@sort-change="viewPageOrder">
|
@sort-change="viewPageOrder">
|
||||||
<el-table-column type="expand" width="50">
|
<el-table-column type="selection" width="50" align="center" header-align="center" :selectable="isViewRowSelectable"></el-table-column>
|
||||||
<template slot-scope="{row}">
|
|
||||||
<div v-if="row.families && row.families.length > 0" class="tour-family-list">
|
|
||||||
<el-table :data="row.families" :size="tableSize" border>
|
|
||||||
<el-table-column label="工号" prop="jobNo" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
|
||||||
<el-table-column label="姓名" prop="userName" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
|
||||||
<el-table-column label="亲属关系" prop="relationship" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
|
||||||
<el-table-column label="手机号" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
|
||||||
<el-table-column label="身份证号" prop="idCard" min-width="190" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
<div v-else class="tour-family-empty">暂无亲属信息</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="序号" type="index" :index="viewIndexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="viewIndexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="性别" prop="gender" width="90" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="手机号码" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="手机号码" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="身份证号" prop="idCard" min-width="190" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="身份证号" prop="idCard" min-width="190" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="人员类型" prop="rowTypeName" width="90" align="center" header-align="center"></el-table-column>
|
||||||
|
<el-table-column label="亲属关系" prop="relationship" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="成团信息" prop="groupName" width="160" sortable="custom" align="center" header-align="center" show-overflow-tooltip>
|
||||||
|
<template slot-scope="{row}">
|
||||||
|
<span>{{ formatGroupInfo(row) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="关联教职工名称" prop="relatedStaffName" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-row class="el-pagination-container tour-group-view-pagination">
|
<el-row class="el-pagination-container tour-group-view-pagination">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@@ -163,13 +165,38 @@ layout("/layouts/platform.html"){
|
|||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
title="人员调整"
|
||||||
|
:visible.sync="adjustVisible"
|
||||||
|
width="420px"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@closed="resetAdjustForm">
|
||||||
|
<el-form :model="adjustForm" label-width="90px">
|
||||||
|
<el-form-item label="成团信息" required>
|
||||||
|
<el-select v-model="adjustForm.groupCode" filterable placeholder="请选择成团信息" style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in viewGroupOptions"
|
||||||
|
:key="item.groupCode"
|
||||||
|
:label="formatGroupOption(item)"
|
||||||
|
:value="item.groupCode">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="adjustVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="adjustLoading" @click="submitAdjustGroup">确认</el-button>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
</guava>
|
</guava>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
data() {
|
data() {
|
||||||
const currentYear = moment().format("YYYY")
|
const currentYear = moment().format("YYYY")
|
||||||
return {
|
return {
|
||||||
@@ -181,6 +208,15 @@ layout("/layouts/platform.html"){
|
|||||||
viewTitle: "",
|
viewTitle: "",
|
||||||
viewRow: {},
|
viewRow: {},
|
||||||
viewTableData: [],
|
viewTableData: [],
|
||||||
|
viewGroupOptions: [],
|
||||||
|
viewSelection: [],
|
||||||
|
syncingFamilySelection: false,
|
||||||
|
assignGroupLoading: false,
|
||||||
|
adjustVisible: false,
|
||||||
|
adjustLoading: false,
|
||||||
|
adjustForm: {
|
||||||
|
groupCode: ""
|
||||||
|
},
|
||||||
pageForm: {
|
pageForm: {
|
||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@@ -196,10 +232,11 @@ layout("/layouts/platform.html"){
|
|||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
pageOrderName: "signupTime",
|
pageOrderName: "groupCode",
|
||||||
pageOrderBy: "descending",
|
pageOrderBy: "ascending",
|
||||||
keyword: "",
|
keyword: "",
|
||||||
unionId: ""
|
unionId: "",
|
||||||
|
groupCode: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -226,6 +263,14 @@ layout("/layouts/platform.html"){
|
|||||||
|| row.directFamilyUnitLine === 1
|
|| row.directFamilyUnitLine === 1
|
||||||
|| row.directFamilyUnitLine === "1"
|
|| row.directFamilyUnitLine === "1"
|
||||||
},
|
},
|
||||||
|
formatGroupInfo(row) {
|
||||||
|
if (!row || !row.groupCode) return "未成团"
|
||||||
|
return row.groupName ? row.groupCode + " " + row.groupName : row.groupCode
|
||||||
|
},
|
||||||
|
formatGroupOption(item) {
|
||||||
|
if (!item || !item.groupCode) return ""
|
||||||
|
return item.groupName ? item.groupCode + " " + item.groupName : item.groupCode
|
||||||
|
},
|
||||||
openView(row) {
|
openView(row) {
|
||||||
this.viewRow = row || {}
|
this.viewRow = row || {}
|
||||||
this.viewTitle = this.viewRow.lineName || "报名人员"
|
this.viewTitle = this.viewRow.lineName || "报名人员"
|
||||||
@@ -233,11 +278,17 @@ layout("/layouts/platform.html"){
|
|||||||
this.viewForm.pageNumber = 1
|
this.viewForm.pageNumber = 1
|
||||||
this.viewForm.pageSize = 10
|
this.viewForm.pageSize = 10
|
||||||
this.viewForm.totalCount = 0
|
this.viewForm.totalCount = 0
|
||||||
this.viewForm.pageOrderName = "signupTime"
|
this.viewForm.pageOrderName = "groupCode"
|
||||||
this.viewForm.pageOrderBy = "descending"
|
this.viewForm.pageOrderBy = "ascending"
|
||||||
this.viewForm.keyword = ""
|
this.viewForm.keyword = ""
|
||||||
this.viewForm.unionId = ""
|
this.viewForm.unionId = ""
|
||||||
|
this.viewForm.groupCode = ""
|
||||||
this.viewTableData = []
|
this.viewTableData = []
|
||||||
|
this.viewGroupOptions = []
|
||||||
|
this.viewSelection = []
|
||||||
|
this.assignGroupLoading = false
|
||||||
|
this.resetAdjustForm()
|
||||||
|
this.loadViewGroupOptions()
|
||||||
this.loadViewData()
|
this.loadViewData()
|
||||||
},
|
},
|
||||||
viewSearch() {
|
viewSearch() {
|
||||||
@@ -247,12 +298,118 @@ layout("/layouts/platform.html"){
|
|||||||
viewReset() {
|
viewReset() {
|
||||||
this.viewForm.keyword = ""
|
this.viewForm.keyword = ""
|
||||||
this.viewForm.unionId = ""
|
this.viewForm.unionId = ""
|
||||||
|
this.viewForm.groupCode = ""
|
||||||
|
this.viewForm.pageNumber = 1
|
||||||
|
this.loadViewData()
|
||||||
|
},
|
||||||
|
toggleGroupFilter(item) {
|
||||||
|
const groupCode = item && item.groupCode ? item.groupCode : ""
|
||||||
|
this.viewForm.groupCode = this.viewForm.groupCode === groupCode ? "" : groupCode
|
||||||
this.viewForm.pageNumber = 1
|
this.viewForm.pageNumber = 1
|
||||||
this.loadViewData()
|
this.loadViewData()
|
||||||
},
|
},
|
||||||
viewIndexMethod(index) {
|
viewIndexMethod(index) {
|
||||||
return (this.viewForm.pageNumber - 1) * this.viewForm.pageSize + index + 1
|
return (this.viewForm.pageNumber - 1) * this.viewForm.pageSize + index + 1
|
||||||
},
|
},
|
||||||
|
isViewRowSelectable(row) {
|
||||||
|
return !!(row && row.selectable)
|
||||||
|
},
|
||||||
|
viewSelectionChange(selection) {
|
||||||
|
this.viewSelection = selection || []
|
||||||
|
this.syncFamilySelection(selection || [])
|
||||||
|
},
|
||||||
|
syncFamilySelection(selection) {
|
||||||
|
if (this.syncingFamilySelection || !this.$refs.viewTable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.syncingFamilySelection = true
|
||||||
|
const selectedStaffIds = selection.filter(item => item && item.staffRow).map(item => item.id)
|
||||||
|
this.viewTableData.forEach(row => {
|
||||||
|
if (!row || row.staffRow || !row.ledgerId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$refs.viewTable.toggleRowSelection(row, selectedStaffIds.indexOf(row.ledgerId) !== -1)
|
||||||
|
})
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.syncingFamilySelection = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
confirmAssignGroup() {
|
||||||
|
if (!this.viewRow || !this.viewRow.matterId) {
|
||||||
|
this.$message.warning("线路信息不完整")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$confirm("确认要自动分配未成团人员吗?", "提示", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning"
|
||||||
|
}).then(() => {
|
||||||
|
this.doAssignGroup()
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
doAssignGroup() {
|
||||||
|
this.assignGroupLoading = true
|
||||||
|
this.$axios.post(loc() + "/assignGroup", {
|
||||||
|
matterId: this.viewRow.matterId
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$message.success(res.msg || "分配成功")
|
||||||
|
this.loadViewGroupOptions()
|
||||||
|
this.loadViewData()
|
||||||
|
this.pageData()
|
||||||
|
} else {
|
||||||
|
this.$message.warning(res.msg || "分配失败")
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.assignGroupLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
openAdjustGroup() {
|
||||||
|
if (!this.viewSelection.length) {
|
||||||
|
this.$message.warning("请选择需要调整的教职工")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.viewGroupOptions.length) {
|
||||||
|
this.$message.warning("暂无可调整的成团信息")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.adjustVisible = true
|
||||||
|
},
|
||||||
|
submitAdjustGroup() {
|
||||||
|
if (!this.adjustForm.groupCode) {
|
||||||
|
this.$message.warning("请选择成团信息")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const ledgerIds = this.viewSelection.filter(item => item && item.staffRow).map(item => item.id).filter(id => id).join(",")
|
||||||
|
if (!ledgerIds) {
|
||||||
|
this.$message.warning("请选择需要调整的教职工")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.adjustLoading = true
|
||||||
|
this.$axios.post(loc() + "/adjustGroup", {
|
||||||
|
matterId: this.viewRow.matterId,
|
||||||
|
groupCode: this.adjustForm.groupCode,
|
||||||
|
ledgerIds: ledgerIds
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$message.success(res.msg || "调整成功")
|
||||||
|
this.adjustVisible = false
|
||||||
|
this.loadViewGroupOptions()
|
||||||
|
this.loadViewData()
|
||||||
|
this.pageData()
|
||||||
|
} else {
|
||||||
|
this.$message.warning(res.msg || "调整失败")
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.adjustLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
resetAdjustForm() {
|
||||||
|
this.adjustLoading = false
|
||||||
|
this.adjustForm = {
|
||||||
|
groupCode: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
viewSizeChange(size) {
|
viewSizeChange(size) {
|
||||||
this.viewForm.pageSize = size
|
this.viewForm.pageSize = size
|
||||||
this.viewForm.pageNumber = 1
|
this.viewForm.pageNumber = 1
|
||||||
@@ -271,6 +428,7 @@ layout("/layouts/platform.html"){
|
|||||||
loadViewData() {
|
loadViewData() {
|
||||||
if (!this.viewRow || !this.viewRow.matterId) {
|
if (!this.viewRow || !this.viewRow.matterId) {
|
||||||
this.viewTableData = []
|
this.viewTableData = []
|
||||||
|
this.viewSelection = []
|
||||||
this.viewForm.totalCount = 0
|
this.viewForm.totalCount = 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -280,6 +438,7 @@ layout("/layouts/platform.html"){
|
|||||||
matterId: this.viewRow.matterId,
|
matterId: this.viewRow.matterId,
|
||||||
keyword: this.viewForm.keyword,
|
keyword: this.viewForm.keyword,
|
||||||
unionId: this.viewForm.unionId,
|
unionId: this.viewForm.unionId,
|
||||||
|
groupCode: this.viewForm.groupCode,
|
||||||
pageNumber: this.viewForm.pageNumber,
|
pageNumber: this.viewForm.pageNumber,
|
||||||
pageSize: this.viewForm.pageSize,
|
pageSize: this.viewForm.pageSize,
|
||||||
pageOrderName: this.viewForm.pageOrderName,
|
pageOrderName: this.viewForm.pageOrderName,
|
||||||
@@ -289,6 +448,12 @@ layout("/layouts/platform.html"){
|
|||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const data = res.data || {}
|
const data = res.data || {}
|
||||||
this.viewTableData = data.list || []
|
this.viewTableData = data.list || []
|
||||||
|
this.viewSelection = []
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.viewTable) {
|
||||||
|
this.$refs.viewTable.clearSelection()
|
||||||
|
}
|
||||||
|
})
|
||||||
this.viewForm.totalCount = data.totalCount || 0
|
this.viewForm.totalCount = data.totalCount || 0
|
||||||
} else {
|
} else {
|
||||||
this.$message.warning(res.msg || "查询失败")
|
this.$message.warning(res.msg || "查询失败")
|
||||||
@@ -299,6 +464,21 @@ layout("/layouts/platform.html"){
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
loadViewGroupOptions() {
|
||||||
|
if (!this.viewRow || !this.viewRow.matterId) {
|
||||||
|
this.viewGroupOptions = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$axios.post(loc() + "/groupOptions", {
|
||||||
|
matterId: this.viewRow.matterId
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.viewGroupOptions = res.data || []
|
||||||
|
} else {
|
||||||
|
this.$message.warning(res.msg || "查询成团信息失败")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
exportLine(row) {
|
exportLine(row) {
|
||||||
if (!row || !row.matterId) {
|
if (!row || !row.matterId) {
|
||||||
this.$message.warning("线路信息不完整")
|
this.$message.warning("线路信息不完整")
|
||||||
@@ -347,21 +527,52 @@ layout("/layouts/platform.html"){
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.tour-group-view-header {
|
||||||
|
align-items: flex-start;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
.tour-group-view-title {
|
.tour-group-view-title {
|
||||||
border-left: 4px solid #0079c2;
|
border-left: 4px solid #0079c2;
|
||||||
color: #0079c2;
|
color: #0079c2;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
margin-bottom: 12px;
|
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.tour-family-list {
|
.tour-group-options {
|
||||||
padding: 8px 32px;
|
display: flex;
|
||||||
|
flex: 0 1 820px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
max-width: calc(100% - 120px);
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
.tour-family-empty {
|
.tour-group-actions {
|
||||||
color: #909399;
|
display: flex;
|
||||||
padding: 8px 32px;
|
flex: 0 0 auto;
|
||||||
|
gap: 8px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.tour-group-option {
|
||||||
|
cursor: pointer;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
max-width: 180px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.tour-group-option.is-active {
|
||||||
|
border-color: #67c23a;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.el-table .direct-family-row > td,
|
.el-table .direct-family-row > td,
|
||||||
.el-table .direct-family-row > td .cell {
|
.el-table .direct-family-row > td .cell {
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ layout("/layouts/platform.html"){
|
|||||||
<div class="tour-ledger-actions">
|
<div class="tour-ledger-actions">
|
||||||
<el-button size="medium" type="primary" icon="el-icon-upload2" @click="showImportDialog = true">参加人员导入</el-button>
|
<el-button size="medium" type="primary" icon="el-icon-upload2" @click="showImportDialog = true">参加人员导入</el-button>
|
||||||
<el-button size="medium" type="primary" icon="el-icon-check" @click="setParticipants">设置参加人员</el-button>
|
<el-button size="medium" type="primary" icon="el-icon-check" @click="setParticipants">设置参加人员</el-button>
|
||||||
|
<el-button size="medium" type="primary" icon="el-icon-download" @click="exportData">导出</el-button>
|
||||||
<el-button size="medium" type="primary" icon="el-icon-download" @click="exportUnionSignupZip">导出分工会报名压缩包</el-button>
|
<el-button size="medium" type="primary" icon="el-icon-download" @click="exportUnionSignupZip">导出分工会报名压缩包</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tour-ledger-scope">
|
<div class="tour-ledger-scope">
|
||||||
@@ -99,7 +100,8 @@ layout("/layouts/platform.html"){
|
|||||||
row-key="id"
|
row-key="id"
|
||||||
:row-class-name="tableRowClassName"
|
:row-class-name="tableRowClassName"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column type="selection" width="55" align="center" header-align="center"></el-table-column>
|
<el-table-column type="selection" width="55" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
@@ -108,6 +110,12 @@ layout("/layouts/platform.html"){
|
|||||||
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="出行时段" prop="travelPeriod" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="出行时段" prop="travelPeriod" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="成团信息" prop="groupName" width="160" align="center" header-align="center" show-overflow-tooltip>
|
||||||
|
<template slot-scope="{row}">
|
||||||
|
<span>{{ formatGroupInfo(row) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="领办工会" prop="undertakeUnionName" min-width="160" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
|
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
|
||||||
<template slot-scope="{row}">
|
<template slot-scope="{row}">
|
||||||
@@ -342,7 +350,7 @@ layout("/layouts/platform.html"){
|
|||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
||||||
data() {
|
data() {
|
||||||
const currentYear = moment().format("YYYY")
|
const currentYear = moment().format("YYYY")
|
||||||
@@ -380,6 +388,10 @@ layout("/layouts/platform.html"){
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
formatGroupInfo(row) {
|
||||||
|
if (!row || !row.groupCode) return ""
|
||||||
|
return row.groupName ? row.groupCode + " " + row.groupName : row.groupCode
|
||||||
|
},
|
||||||
toBoolean(value) {
|
toBoolean(value) {
|
||||||
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
|
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
|
||||||
},
|
},
|
||||||
@@ -437,6 +449,23 @@ layout("/layouts/platform.html"){
|
|||||||
}
|
}
|
||||||
window.location.href = loc() + "/exportUnionSignupZip?" + params.toString()
|
window.location.href = loc() + "/exportUnionSignupZip?" + params.toString()
|
||||||
},
|
},
|
||||||
|
// 导出台账时完整传递当前页面筛选项,两个特殊人员范围按钮也作为查询条件参与导出。
|
||||||
|
exportData() {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
;["startYear", "endYear", "keyword", "unionId", "lineId", "travelPeriod", "lineType"].forEach(key => {
|
||||||
|
const value = this.pageForm[key]
|
||||||
|
if (value !== undefined && value !== null && value !== "") {
|
||||||
|
params.append(key, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (this.pageForm.directFamilyOnly) {
|
||||||
|
params.append("directFamilyOnly", "true")
|
||||||
|
}
|
||||||
|
if (this.pageForm.overCostOnly) {
|
||||||
|
params.append("overCostOnly", "true")
|
||||||
|
}
|
||||||
|
window.location.href = loc() + "/exportData?" + params.toString()
|
||||||
|
},
|
||||||
afterImport() {
|
afterImport() {
|
||||||
this.showImportDialog = false
|
this.showImportDialog = false
|
||||||
this.clearTableSelection()
|
this.clearTableSelection()
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ layout("/layouts/platform.html"){
|
|||||||
<el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
<el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</search-item>
|
</search-item>
|
||||||
|
<search-item label="线路类型">
|
||||||
|
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
|
||||||
|
<el-option v-for="item in lineTypeOptions" :key="item.code" :label="item.name" :value="item.name"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</search-item>
|
||||||
<div class="search-query" style="margin-left: auto;">
|
<div class="search-query" style="margin-left: auto;">
|
||||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||||
@@ -43,10 +48,33 @@ layout("/layouts/platform.html"){
|
|||||||
|
|
||||||
<el-card class="mt10" shadow="never">
|
<el-card class="mt10" shadow="never">
|
||||||
<table-tool :app="this" label="事项列表">
|
<table-tool :app="this" label="事项列表">
|
||||||
|
<div class="tour-matter-signup-tool">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="batchSignupTimeForm.signupStartTime"
|
||||||
|
type="datetime"
|
||||||
|
value-format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
placeholder="报名开始时间"
|
||||||
|
size="medium">
|
||||||
|
</el-date-picker>
|
||||||
|
<span>-</span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="batchSignupTimeForm.signupEndTime"
|
||||||
|
type="datetime"
|
||||||
|
value-format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
placeholder="报名结束时间"
|
||||||
|
size="medium">
|
||||||
|
</el-date-picker>
|
||||||
|
<el-button
|
||||||
|
size="medium"
|
||||||
|
type="primary"
|
||||||
|
:loading="batchSignupTimeLoading"
|
||||||
|
@click="assignSignupTime">一键赋值报名时间</el-button>
|
||||||
|
</div>
|
||||||
<el-button @click="openAdd" size="medium" type="primary">
|
<el-button @click="openAdd" size="medium" type="primary">
|
||||||
<i class="el-icon-plus"></i>
|
<i class="el-icon-plus"></i>
|
||||||
新增
|
新增
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button @click="exportData" size="medium" type="primary" icon="el-icon-download">导出</el-button>
|
||||||
</table-tool>
|
</table-tool>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
@@ -54,13 +82,26 @@ layout("/layouts/platform.html"){
|
|||||||
:data="tableData"
|
:data="tableData"
|
||||||
:size="tableSize"
|
:size="tableSize"
|
||||||
border
|
border
|
||||||
|
:default-sort="{prop: 'lineCode', order: 'ascending'}"
|
||||||
:row-class-name="tableRowClassName"
|
:row-class-name="tableRowClassName"
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
ref="tourMainTable"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
|
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="事项名称" prop="matterName" min-width="240" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="事项名称" prop="matterName" min-width="240" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="所属工会" prop="unionName" min-width="160" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column v-if="false" label="所属工会" prop="unionName" min-width="160" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="线路名称" prop="lineName" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="线路名称" prop="lineName" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="报名时间" min-width="300" align="center" header-align="center" show-overflow-tooltip>
|
||||||
|
<template slot-scope="{row}">
|
||||||
|
<span>{{ formatTimeRange(row.signupStartTime, row.signupEndTime) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="出行时间" min-width="220" align="center" header-align="center" show-overflow-tooltip>
|
||||||
|
<template slot-scope="{row}">
|
||||||
|
<span>{{ formatTimeRange(row.travelStartTime, row.travelEndTime) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="组织形式" prop="organizationTypeName" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="组织形式" prop="organizationTypeName" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="联系人" prop="contactName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="联系人" prop="contactName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="联系方式" prop="contactPhone" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="联系方式" prop="contactPhone" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
@@ -201,7 +242,14 @@ layout("/layouts/platform.html"){
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="最多成团人数" prop="maxGroupPeople">
|
<el-form-item label="最多成团人数" prop="maxGroupPeople">
|
||||||
<el-input-number v-model="batchForm.maxGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
|
<el-input-number v-model="batchForm.maxGroupPeople" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="最多报名人数" prop="maxSignupPeople">
|
||||||
|
<el-input-number v-model="batchForm.maxSignupPeople" :min="0" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -218,13 +266,22 @@ layout("/layouts/platform.html"){
|
|||||||
.el-table .direct-family-row > td .cell {
|
.el-table .direct-family-row > td .cell {
|
||||||
color: #f56c6c;
|
color: #f56c6c;
|
||||||
}
|
}
|
||||||
|
.tour-matter-signup-tool {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
.tour-matter-signup-tool .el-date-editor {
|
||||||
|
width: 210px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
store,
|
store,
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
data() {
|
data() {
|
||||||
const currentYear = moment().format("YYYY")
|
const currentYear = moment().format("YYYY")
|
||||||
const validateUnion = (rule, value, callback) => {
|
const validateUnion = (rule, value, callback) => {
|
||||||
@@ -257,13 +314,17 @@ layout("/layouts/platform.html"){
|
|||||||
const validatePeople = (rule, value, callback) => {
|
const validatePeople = (rule, value, callback) => {
|
||||||
if (!value || value <= 0) {
|
if (!value || value <= 0) {
|
||||||
callback(new Error("人数必须大于0"))
|
callback(new Error("人数必须大于0"))
|
||||||
} else if (this.batchForm.minGroupPeople && this.batchForm.maxGroupPeople
|
|
||||||
&& this.batchForm.minGroupPeople > this.batchForm.maxGroupPeople) {
|
|
||||||
callback(new Error("最少人数不能大于最多人数"))
|
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const validateMaxSignupPeople = (rule, value, callback) => {
|
||||||
|
if (value === "" || value === null || value === undefined || value >= 0) {
|
||||||
|
callback()
|
||||||
|
} else {
|
||||||
|
callback(new Error("最多报名人数不能小于0"))
|
||||||
|
}
|
||||||
|
}
|
||||||
const validateBatchTime = (rule, value, callback) => {
|
const validateBatchTime = (rule, value, callback) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
callback(new Error("必填"))
|
callback(new Error("必填"))
|
||||||
@@ -292,9 +353,15 @@ layout("/layouts/platform.html"){
|
|||||||
batchFormVisible: false,
|
batchFormVisible: false,
|
||||||
batchSubDis: false,
|
batchSubDis: false,
|
||||||
batchTitle: "",
|
batchTitle: "",
|
||||||
|
batchSignupTimeLoading: false,
|
||||||
|
batchSignupTimeForm: {
|
||||||
|
signupStartTime: "",
|
||||||
|
signupEndTime: ""
|
||||||
|
},
|
||||||
currentMatter: {},
|
currentMatter: {},
|
||||||
batchForm: {},
|
batchForm: {},
|
||||||
lineOptions: [],
|
lineOptions: [],
|
||||||
|
lineTypeOptions: [],
|
||||||
settingOptions: [],
|
settingOptions: [],
|
||||||
unionOptions: [],
|
unionOptions: [],
|
||||||
organizationTypeOptions: [],
|
organizationTypeOptions: [],
|
||||||
@@ -302,10 +369,13 @@ layout("/layouts/platform.html"){
|
|||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
|
pageOrderName: "lineCode",
|
||||||
|
pageOrderBy: "ascending",
|
||||||
year: currentYear,
|
year: currentYear,
|
||||||
matterName: "",
|
matterName: "",
|
||||||
unionId: "",
|
unionId: "",
|
||||||
organizationType: ""
|
organizationType: "",
|
||||||
|
lineType: ""
|
||||||
},
|
},
|
||||||
formData: {},
|
formData: {},
|
||||||
formRules: {
|
formRules: {
|
||||||
@@ -324,7 +394,7 @@ layout("/layouts/platform.html"){
|
|||||||
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||||
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
|
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
|
||||||
minGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
|
minGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
|
||||||
maxGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
|
maxSignupPeople: [{ validator: validateMaxSignupPeople, trigger: ["blur", "change"] }],
|
||||||
estimatedCost: [{ validator: validateMoney, trigger: ["blur", "change"] }]
|
estimatedCost: [{ validator: validateMoney, trigger: ["blur", "change"] }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,9 +425,65 @@ layout("/layouts/platform.html"){
|
|||||||
this.pageForm.matterName = ""
|
this.pageForm.matterName = ""
|
||||||
this.pageForm.unionId = ""
|
this.pageForm.unionId = ""
|
||||||
this.pageForm.organizationType = ""
|
this.pageForm.organizationType = ""
|
||||||
|
this.pageForm.lineType = ""
|
||||||
this.loadSettings(this.pageForm.year)
|
this.loadSettings(this.pageForm.year)
|
||||||
this.doSearch()
|
this.doSearch()
|
||||||
},
|
},
|
||||||
|
formatTimeRange(startTime, endTime) {
|
||||||
|
if (!startTime && !endTime) return ""
|
||||||
|
if (!startTime) return endTime || ""
|
||||||
|
if (!endTime) return startTime || ""
|
||||||
|
return startTime + " 至 " + endTime
|
||||||
|
},
|
||||||
|
exportData() {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
;["year", "matterName", "unionId", "organizationType", "lineType"].forEach(key => {
|
||||||
|
const value = this.pageForm[key]
|
||||||
|
if (value !== undefined && value !== null && value !== "") {
|
||||||
|
params.append(key, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
window.location.href = loc() + "/exportData?" + params.toString()
|
||||||
|
},
|
||||||
|
// 按当前筛选条件批量更新事项报名时间,提交前必须让用户确认更新范围。
|
||||||
|
assignSignupTime() {
|
||||||
|
const startTime = this.batchSignupTimeForm.signupStartTime
|
||||||
|
const endTime = this.batchSignupTimeForm.signupEndTime
|
||||||
|
if (!startTime || !endTime) {
|
||||||
|
this.$message.warning("请选择报名开始时间和报名结束时间")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (startTime >= endTime) {
|
||||||
|
this.$message.warning("报名开始时间必须小于报名结束时间")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$confirm("确认将当前查询条件下的事项报名时间统一赋值为:" + startTime + " 至 " + endTime + "?", "提示", {
|
||||||
|
confirmButtonText: "确认",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning"
|
||||||
|
}).then(() => {
|
||||||
|
this.batchSignupTimeLoading = true
|
||||||
|
this.$axios.post(loc() + "/assignSignupTime", {
|
||||||
|
year: this.pageForm.year,
|
||||||
|
matterName: this.pageForm.matterName,
|
||||||
|
unionId: this.pageForm.unionId,
|
||||||
|
organizationType: this.pageForm.organizationType,
|
||||||
|
lineType: this.pageForm.lineType,
|
||||||
|
signupStartTime: startTime,
|
||||||
|
signupEndTime: endTime
|
||||||
|
}).then((res) => {
|
||||||
|
this.batchSignupTimeLoading = false
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$message.success(res.msg || "赋值成功")
|
||||||
|
this.pageData()
|
||||||
|
} else {
|
||||||
|
this.$message.warning(res.msg || "赋值失败")
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
this.batchSignupTimeLoading = false
|
||||||
|
})
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
tableRowClassName({ row }) {
|
tableRowClassName({ row }) {
|
||||||
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
|
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
|
||||||
},
|
},
|
||||||
@@ -478,6 +604,13 @@ layout("/layouts/platform.html"){
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
loadLineTypes() {
|
||||||
|
this.$axios.post(loc() + "/lineTypeOptions").then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.lineTypeOptions = res.data || []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
emptyBatchForm() {
|
emptyBatchForm() {
|
||||||
return {
|
return {
|
||||||
id: this.currentMatter.id || "",
|
id: this.currentMatter.id || "",
|
||||||
@@ -490,6 +623,7 @@ layout("/layouts/platform.html"){
|
|||||||
contactPhone: "",
|
contactPhone: "",
|
||||||
minGroupPeople: null,
|
minGroupPeople: null,
|
||||||
maxGroupPeople: null,
|
maxGroupPeople: null,
|
||||||
|
maxSignupPeople: null,
|
||||||
estimatedCost: ""
|
estimatedCost: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -530,7 +664,9 @@ layout("/layouts/platform.html"){
|
|||||||
if (!item) return ""
|
if (!item) return ""
|
||||||
const lineName = this.lineField(item, "lineName") || ""
|
const lineName = this.lineField(item, "lineName") || ""
|
||||||
const lineType = this.lineField(item, "lineType") || ""
|
const lineType = this.lineField(item, "lineType") || ""
|
||||||
return lineType ? lineName + "(" + lineType + ")" : lineName
|
const travelAgencyName = this.lineField(item, "travelAgencyName") || ""
|
||||||
|
const lineDesc = [lineType, travelAgencyName].filter(item => item).join("/")
|
||||||
|
return lineDesc ? lineName + "(" + lineDesc + ")" : lineName
|
||||||
},
|
},
|
||||||
lineChange() {
|
lineChange() {
|
||||||
this.applySelectedLineDefaults(true)
|
this.applySelectedLineDefaults(true)
|
||||||
@@ -589,10 +725,12 @@ layout("/layouts/platform.html"){
|
|||||||
},
|
},
|
||||||
applySettingSignupTime(data) {
|
applySettingSignupTime(data) {
|
||||||
if (!data) return
|
if (!data) return
|
||||||
if (data.signupStartTime) {
|
// 已保存线路配置时保留事项自身报名时间,避免配置默认值覆盖用户修改。
|
||||||
|
const hasSavedLineConfig = !!this.batchForm.lineId
|
||||||
|
if (!hasSavedLineConfig && data.signupStartTime) {
|
||||||
this.$set(this.batchForm, "signupStartTime", data.signupStartTime)
|
this.$set(this.batchForm, "signupStartTime", data.signupStartTime)
|
||||||
}
|
}
|
||||||
if (data.signupEndTime) {
|
if (!hasSavedLineConfig && data.signupEndTime) {
|
||||||
this.$set(this.batchForm, "signupEndTime", data.signupEndTime)
|
this.$set(this.batchForm, "signupEndTime", data.signupEndTime)
|
||||||
}
|
}
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -603,15 +741,17 @@ layout("/layouts/platform.html"){
|
|||||||
},
|
},
|
||||||
applySettingPeople(data) {
|
applySettingPeople(data) {
|
||||||
if (!data) return
|
if (!data) return
|
||||||
if (data.minGroupPeople !== null && data.minGroupPeople !== undefined && data.minGroupPeople !== "") {
|
// 已保存线路配置时保留事项自身成团人数,配置人数只作为首次选择线路的默认值。
|
||||||
|
const hasSavedLineConfig = !!this.batchForm.lineId
|
||||||
|
if (!hasSavedLineConfig && data.minGroupPeople !== null && data.minGroupPeople !== undefined && data.minGroupPeople !== "") {
|
||||||
this.$set(this.batchForm, "minGroupPeople", data.minGroupPeople)
|
this.$set(this.batchForm, "minGroupPeople", data.minGroupPeople)
|
||||||
}
|
}
|
||||||
if (data.maxGroupPeople !== null && data.maxGroupPeople !== undefined && data.maxGroupPeople !== "") {
|
if (!hasSavedLineConfig && data.maxGroupPeople !== null && data.maxGroupPeople !== undefined && data.maxGroupPeople !== "") {
|
||||||
this.$set(this.batchForm, "maxGroupPeople", data.maxGroupPeople)
|
this.$set(this.batchForm, "maxGroupPeople", data.maxGroupPeople)
|
||||||
}
|
}
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.batchFormRef) {
|
if (this.$refs.batchFormRef) {
|
||||||
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople"])
|
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople", "maxSignupPeople"])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -629,7 +769,7 @@ layout("/layouts/platform.html"){
|
|||||||
peopleChange() {
|
peopleChange() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.batchFormRef) {
|
if (this.$refs.batchFormRef) {
|
||||||
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople"])
|
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople", "maxSignupPeople"])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -731,6 +871,7 @@ layout("/layouts/platform.html"){
|
|||||||
this.loadSettings(this.pageForm.year)
|
this.loadSettings(this.pageForm.year)
|
||||||
this.loadUnions()
|
this.loadUnions()
|
||||||
this.loadOrganizationTypes()
|
this.loadOrganizationTypes()
|
||||||
|
this.loadLineTypes()
|
||||||
this.pageData()
|
this.pageData()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ layout("/layouts/platform.html"){
|
|||||||
:data="tableData"
|
:data="tableData"
|
||||||
:size="tableSize"
|
:size="tableSize"
|
||||||
border
|
border
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
ref="tourMainTable"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
@@ -730,7 +732,7 @@ layout("/layouts/platform.html"){
|
|||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
+545
@@ -0,0 +1,545 @@
|
|||||||
|
<!--#
|
||||||
|
layout("/layouts/platform.html"){
|
||||||
|
#-->
|
||||||
|
|
||||||
|
<div id="app" v-cloak>
|
||||||
|
<guava ref="guava">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<search :is-search-button="false">
|
||||||
|
<search-item label="年度">
|
||||||
|
<div class="tour-year-range">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="pageForm.startYear"
|
||||||
|
type="year"
|
||||||
|
value-format="yyyy"
|
||||||
|
placeholder="开始年度"
|
||||||
|
class="tour-year-picker">
|
||||||
|
</el-date-picker>
|
||||||
|
<span class="tour-year-separator">-</span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="pageForm.endYear"
|
||||||
|
type="year"
|
||||||
|
value-format="yyyy"
|
||||||
|
placeholder="结束年度"
|
||||||
|
class="tour-year-picker">
|
||||||
|
</el-date-picker>
|
||||||
|
</div>
|
||||||
|
</search-item>
|
||||||
|
<search-item label="疗休养配置">
|
||||||
|
<el-select
|
||||||
|
v-model="pageForm.settingId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择疗休养配置"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="settingChange">
|
||||||
|
<el-option
|
||||||
|
v-for="item in settingOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="settingOptionLabel(item)"
|
||||||
|
:value="item.id">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</search-item>
|
||||||
|
<search-item label="线路">
|
||||||
|
<el-select
|
||||||
|
v-model="pageForm.matterId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择线路"
|
||||||
|
style="width: 100%"
|
||||||
|
@visible-change="lineVisibleChange">
|
||||||
|
<el-option
|
||||||
|
v-for="item in lineOptions"
|
||||||
|
:key="item.matterId"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.matterId">
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
<span class="tour-line-option">{{ item.lineName }}</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</search-item>
|
||||||
|
<search-item label="姓名/工号">
|
||||||
|
<el-input
|
||||||
|
v-model="pageForm.keyword"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入姓名或工号"
|
||||||
|
style="width: 100%"
|
||||||
|
@keyup.enter.native="doSearch">
|
||||||
|
</el-input>
|
||||||
|
</search-item>
|
||||||
|
<search-item label="所在工会">
|
||||||
|
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所在工会" style="width: 100%">
|
||||||
|
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</search-item>
|
||||||
|
<search-item label="线路类型">
|
||||||
|
<dict-select
|
||||||
|
v-model="pageForm.lineType"
|
||||||
|
code="lineType"
|
||||||
|
option_value="name"
|
||||||
|
placeholder="请选择线路类型">
|
||||||
|
</dict-select>
|
||||||
|
</search-item>
|
||||||
|
<search-item label="领办工会">
|
||||||
|
<el-select v-model="pageForm.undertakeUnionId" clearable filterable placeholder="请选择领办工会" style="width: 100%">
|
||||||
|
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</search-item>
|
||||||
|
<div class="search-query">
|
||||||
|
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||||
|
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||||
|
</div>
|
||||||
|
</search>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="mt10" shadow="never">
|
||||||
|
<div class="tour-roster-toolbar">
|
||||||
|
<table-tool :app="this" label="疗休养人员名单"></table-tool>
|
||||||
|
<el-button size="medium" type="primary" icon="el-icon-refresh" @click="openRebuildDialog">重新生成名单</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
ref="tableRef"
|
||||||
|
v-loading="tableLoading"
|
||||||
|
:data="tableData"
|
||||||
|
:size="tableSize"
|
||||||
|
border
|
||||||
|
row-key="id"
|
||||||
|
@sort-change="pageOrder"
|
||||||
|
:height="tourTableHeight">
|
||||||
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
|
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="所在单位" prop="unitName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="所在工会" prop="unionName" min-width="160" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="是否参加" prop="joined" width="110" sortable="custom" align="center" header-align="center">
|
||||||
|
<template slot-scope="{row}">
|
||||||
|
<el-tag size="mini" :type="toBoolean(row.joined) ? 'success' : 'info'">{{ toBoolean(row.joined) ? '已参加' : '未参加' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="疗休养事项" prop="matterName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="出行时段" prop="travelPeriod" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="成团信息" prop="groupName" width="160" align="center" header-align="center" show-overflow-tooltip>
|
||||||
|
<template slot-scope="{row}">
|
||||||
|
<span>{{ formatGroupInfo(row) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="领办工会" prop="undertakeUnionName" min-width="160" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!--#include("/layouts/pagination.html"){}#-->
|
||||||
|
</el-card>
|
||||||
|
</guava>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
title="重新生成名单"
|
||||||
|
:visible.sync="rebuildDialogVisible"
|
||||||
|
:close-on-click-modal="!rebuildSubmitting"
|
||||||
|
:close-on-press-escape="!rebuildSubmitting"
|
||||||
|
:show-close="!rebuildSubmitting"
|
||||||
|
width="620px">
|
||||||
|
<el-form
|
||||||
|
v-loading="rebuildSubmitting"
|
||||||
|
element-loading-text="正在重新生成名单,请稍候"
|
||||||
|
:model="rebuildForm"
|
||||||
|
label-width="110px"
|
||||||
|
size="medium">
|
||||||
|
<el-form-item label="开始年度" required>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="rebuildForm.startYear"
|
||||||
|
type="year"
|
||||||
|
value-format="yyyy"
|
||||||
|
placeholder="请选择开始年度"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="rebuildYearChange">
|
||||||
|
</el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="结束年度" required>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="rebuildForm.endYear"
|
||||||
|
type="year"
|
||||||
|
value-format="yyyy"
|
||||||
|
placeholder="请选择结束年度"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="rebuildYearChange">
|
||||||
|
</el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="疗休养配置" required>
|
||||||
|
<el-select
|
||||||
|
v-model="rebuildForm.settingIds"
|
||||||
|
multiple
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择疗休养配置"
|
||||||
|
style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in rebuildSettingOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="settingOptionLabel(item)"
|
||||||
|
:value="item.id">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<el-button :disabled="rebuildSubmitting" @click="rebuildDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="rebuildSubmitting" :disabled="rebuildSubmitting" @click="submitRebuildRoster">确认</el-button>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tour-roster-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tour-year-range {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tour-year-picker {
|
||||||
|
width: 112px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tour-year-separator {
|
||||||
|
padding: 0 8px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tour-line-option {
|
||||||
|
float: right;
|
||||||
|
color: #8492a6;
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: 45%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tour-rebuild-warning-message {
|
||||||
|
width: 680px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tour-rebuild-warning-list {
|
||||||
|
max-height: 360px;
|
||||||
|
overflow-y: auto;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tour-rebuild-warning-list p {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script nonce="${cspNonce!}">
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
|
data() {
|
||||||
|
const currentYear = moment().format("YYYY")
|
||||||
|
return {
|
||||||
|
settingOptions: [],
|
||||||
|
rebuildSettingOptions: [],
|
||||||
|
lineOptions: [],
|
||||||
|
unionOptions: [],
|
||||||
|
optionTimer: null,
|
||||||
|
rebuildDialogVisible: false,
|
||||||
|
rebuildSubmitting: false,
|
||||||
|
rebuildForm: {
|
||||||
|
startYear: currentYear,
|
||||||
|
endYear: currentYear,
|
||||||
|
settingIds: []
|
||||||
|
},
|
||||||
|
pageForm: {
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
totalCount: 0,
|
||||||
|
pageOrderName: "year",
|
||||||
|
pageOrderBy: "descending",
|
||||||
|
startYear: currentYear,
|
||||||
|
endYear: currentYear,
|
||||||
|
settingId: "",
|
||||||
|
matterId: "",
|
||||||
|
keyword: "",
|
||||||
|
unitName: "",
|
||||||
|
unionId: "",
|
||||||
|
joined: "",
|
||||||
|
lineType: "",
|
||||||
|
groupCode: "",
|
||||||
|
undertakeUnionId: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toBoolean(value) {
|
||||||
|
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
|
||||||
|
},
|
||||||
|
settingOptionLabel(item) {
|
||||||
|
if (!item) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return (item.configName || "") + "【" + (item.year || "") + "】"
|
||||||
|
},
|
||||||
|
formatGroupInfo(row) {
|
||||||
|
if (!row || !row.groupCode) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return row.groupName ? row.groupCode + " " + row.groupName : row.groupCode
|
||||||
|
},
|
||||||
|
checkYearRange() {
|
||||||
|
if (!this.pageForm.startYear || !this.pageForm.endYear) {
|
||||||
|
this.$message.warning("年度范围不能为空")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (Number(this.pageForm.startYear) > Number(this.pageForm.endYear)) {
|
||||||
|
this.$message.warning("开始年度不能大于结束年度")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
optionParams() {
|
||||||
|
return {
|
||||||
|
startYear: this.pageForm.startYear,
|
||||||
|
endYear: this.pageForm.endYear,
|
||||||
|
settingId: this.pageForm.settingId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadSettingOptions() {
|
||||||
|
if (!this.checkYearRange()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$axios.post(loc() + "/settingOptions", {
|
||||||
|
startYear: this.pageForm.startYear,
|
||||||
|
endYear: this.pageForm.endYear
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.settingOptions = res.data || []
|
||||||
|
if (this.pageForm.settingId && !this.settingOptions.some(item => item.id === this.pageForm.settingId)) {
|
||||||
|
this.pageForm.settingId = ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$message.warning(res.msg || "疗休养配置加载失败")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadLineOptions() {
|
||||||
|
if (!this.checkYearRange()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$axios.post(loc() + "/lineOptions", this.optionParams()).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.lineOptions = res.data || []
|
||||||
|
if (this.pageForm.matterId && !this.lineOptions.some(item => item.matterId === this.pageForm.matterId)) {
|
||||||
|
this.pageForm.matterId = ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$message.warning(res.msg || "线路加载失败")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadUnionOptions() {
|
||||||
|
if (!this.checkYearRange()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$axios.post(loc() + "/unionOptions", {
|
||||||
|
startYear: this.pageForm.startYear,
|
||||||
|
endYear: this.pageForm.endYear
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.unionOptions = res.data || []
|
||||||
|
if (this.pageForm.unionId && !this.unionOptions.some(item => item.id === this.pageForm.unionId)) {
|
||||||
|
this.pageForm.unionId = ""
|
||||||
|
}
|
||||||
|
if (this.pageForm.undertakeUnionId && !this.unionOptions.some(item => item.id === this.pageForm.undertakeUnionId)) {
|
||||||
|
this.pageForm.undertakeUnionId = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadOptions() {
|
||||||
|
this.loadSettingOptions()
|
||||||
|
this.loadLineOptions()
|
||||||
|
this.loadUnionOptions()
|
||||||
|
},
|
||||||
|
loadRebuildSettingOptions() {
|
||||||
|
if (!this.checkRebuildYearRange()) {
|
||||||
|
this.rebuildSettingOptions = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$axios.post(loc() + "/settingOptions", {
|
||||||
|
startYear: this.rebuildForm.startYear,
|
||||||
|
endYear: this.rebuildForm.endYear
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.rebuildSettingOptions = res.data || []
|
||||||
|
} else {
|
||||||
|
this.rebuildSettingOptions = []
|
||||||
|
this.$message.warning(res.msg || "疗休养配置加载失败")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
checkRebuildYearRange() {
|
||||||
|
if (!this.rebuildForm.startYear || !this.rebuildForm.endYear) {
|
||||||
|
this.$message.warning("年度范围不能为空")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (Number(this.rebuildForm.startYear) > Number(this.rebuildForm.endYear)) {
|
||||||
|
this.$message.warning("开始年度不能大于结束年度")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
openRebuildDialog() {
|
||||||
|
const currentYear = moment().format("YYYY")
|
||||||
|
this.rebuildSubmitting = false
|
||||||
|
this.rebuildForm.startYear = currentYear
|
||||||
|
this.rebuildForm.endYear = currentYear
|
||||||
|
this.rebuildForm.settingIds = []
|
||||||
|
this.rebuildDialogVisible = true
|
||||||
|
this.loadRebuildSettingOptions()
|
||||||
|
},
|
||||||
|
rebuildYearChange() {
|
||||||
|
this.rebuildForm.settingIds = []
|
||||||
|
this.loadRebuildSettingOptions()
|
||||||
|
},
|
||||||
|
submitRebuildRoster() {
|
||||||
|
if (this.rebuildSubmitting) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.checkRebuildYearRange()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.rebuildForm.settingIds.length) {
|
||||||
|
this.$message.warning("请选择需要重新生成的疗休养配置")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$confirm("重新生成后将删除所选配置下已有人员名单,并根据可参与人员范围和现有疗休养台账重新生成,是否继续?", "提示", {
|
||||||
|
confirmButtonText: "确认生成",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning"
|
||||||
|
}).then(() => {
|
||||||
|
// 重新生成名单会删除后重建所选配置人员数据,提交期间锁定弹窗避免重复点击。
|
||||||
|
this.rebuildSubmitting = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$axios.post(loc() + "/rebuildRoster", {
|
||||||
|
startYear: this.rebuildForm.startYear,
|
||||||
|
endYear: this.rebuildForm.endYear,
|
||||||
|
settingIds: JSON.stringify(this.rebuildForm.settingIds)
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$message.success(res.msg || "人员名单重新生成成功")
|
||||||
|
this.showRebuildWarnings(res.data && res.data.warnings ? res.data.warnings : [])
|
||||||
|
this.rebuildDialogVisible = false
|
||||||
|
this.pageData()
|
||||||
|
this.loadOptions()
|
||||||
|
} else {
|
||||||
|
this.$message.warning(res.msg || "人员名单重新生成失败")
|
||||||
|
}
|
||||||
|
}, () => {
|
||||||
|
this.$message.error("人员名单重新生成失败")
|
||||||
|
}).then(() => {
|
||||||
|
this.rebuildSubmitting = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
showRebuildWarnings(warnings) {
|
||||||
|
if (!warnings || !warnings.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const content = "<div class=\"tour-rebuild-warning-list\">" + warnings.map(item => {
|
||||||
|
return "<p>" + this.escapeHtml(item) + "</p>"
|
||||||
|
}).join("") + "</div>"
|
||||||
|
this.$alert(content, "无法剔除人员提醒", {
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
confirmButtonText: "我知道了",
|
||||||
|
customClass: "tour-rebuild-warning-message"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
escapeHtml(value) {
|
||||||
|
return String(value || "")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
},
|
||||||
|
scheduleLoadOptions() {
|
||||||
|
if (this.optionTimer) {
|
||||||
|
clearTimeout(this.optionTimer)
|
||||||
|
}
|
||||||
|
this.optionTimer = setTimeout(() => {
|
||||||
|
this.loadOptions()
|
||||||
|
this.optionTimer = null
|
||||||
|
}, 80)
|
||||||
|
},
|
||||||
|
lineVisibleChange(visible) {
|
||||||
|
if (!visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.checkYearRange()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.lineOptions.length) {
|
||||||
|
this.$message.warning(this.pageForm.settingId ? "当前配置下暂无可选线路" : "当前年度范围下暂无可选线路")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settingChange() {
|
||||||
|
this.pageForm.matterId = ""
|
||||||
|
this.loadLineOptions()
|
||||||
|
},
|
||||||
|
doSearch() {
|
||||||
|
if (!this.checkYearRange()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.tableKey = new Date().getTime()
|
||||||
|
this.pageForm.pageNumber = 1
|
||||||
|
this.pageData()
|
||||||
|
this.scheduleLoadOptions()
|
||||||
|
},
|
||||||
|
resetSearch() {
|
||||||
|
const currentYear = moment().format("YYYY")
|
||||||
|
this.pageForm.startYear = currentYear
|
||||||
|
this.pageForm.endYear = currentYear
|
||||||
|
this.pageForm.settingId = ""
|
||||||
|
this.pageForm.matterId = ""
|
||||||
|
this.pageForm.keyword = ""
|
||||||
|
this.pageForm.unitName = ""
|
||||||
|
this.pageForm.unionId = ""
|
||||||
|
this.pageForm.joined = ""
|
||||||
|
this.pageForm.lineType = ""
|
||||||
|
this.pageForm.groupCode = ""
|
||||||
|
this.pageForm.undertakeUnionId = ""
|
||||||
|
this.doSearch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadOptions()
|
||||||
|
this.pageData()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
"pageForm.startYear"() {
|
||||||
|
this.pageForm.settingId = ""
|
||||||
|
this.pageForm.matterId = ""
|
||||||
|
this.scheduleLoadOptions()
|
||||||
|
},
|
||||||
|
"pageForm.endYear"() {
|
||||||
|
this.pageForm.settingId = ""
|
||||||
|
this.pageForm.matterId = ""
|
||||||
|
this.scheduleLoadOptions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--#
|
||||||
|
}
|
||||||
|
#-->
|
||||||
@@ -29,6 +29,11 @@ layout("/layouts/platform.html"){
|
|||||||
<el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option>
|
<el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</search-item>
|
</search-item>
|
||||||
|
<search-item label="线路类型">
|
||||||
|
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
|
||||||
|
<el-option v-for="item in lineTypeOptions" :key="item.code" :label="item.name" :value="item.name"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</search-item>
|
||||||
<div class="search-query" style="margin-left: auto;">
|
<div class="search-query" style="margin-left: auto;">
|
||||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||||
@@ -42,6 +47,7 @@ layout("/layouts/platform.html"){
|
|||||||
<i class="el-icon-plus"></i>
|
<i class="el-icon-plus"></i>
|
||||||
新增
|
新增
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button @click="exportData" size="medium" type="primary" icon="el-icon-download">导出</el-button>
|
||||||
</table-tool>
|
</table-tool>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
@@ -51,7 +57,9 @@ layout("/layouts/platform.html"){
|
|||||||
border
|
border
|
||||||
:default-sort="{prop: 'lineCode', order: 'ascending'}"
|
:default-sort="{prop: 'lineCode', order: 'ascending'}"
|
||||||
:row-class-name="tableRowClassName"
|
:row-class-name="tableRowClassName"
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
ref="tourMainTable"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="创建年度" prop="year" width="120" sortable="custom" align="center" header-align="center"></el-table-column>
|
<el-table-column label="创建年度" prop="year" width="120" sortable="custom" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="线路名称" prop="lineName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="线路名称" prop="lineName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
@@ -323,7 +331,14 @@ layout("/layouts/platform.html"){
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="最多成团人数" prop="maxGroupPeople">
|
<el-form-item label="最多成团人数" prop="maxGroupPeople">
|
||||||
<el-input-number v-model="batchForm.maxGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="matterPeopleChange"></el-input-number>
|
<el-input-number v-model="batchForm.maxGroupPeople" :precision="0" controls-position="right" style="width: 100%" @change="matterPeopleChange"></el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="最多报名人数" prop="maxSignupPeople">
|
||||||
|
<el-input-number v-model="batchForm.maxSignupPeople" :min="0" :precision="0" controls-position="right" style="width: 100%" @change="matterPeopleChange"></el-input-number>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -353,7 +368,7 @@ layout("/layouts/platform.html"){
|
|||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
store,
|
store,
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
data() {
|
data() {
|
||||||
const currentYear = moment().format("YYYY")
|
const currentYear = moment().format("YYYY")
|
||||||
const validateMatterUnion = (rule, value, callback) => {
|
const validateMatterUnion = (rule, value, callback) => {
|
||||||
@@ -386,13 +401,17 @@ layout("/layouts/platform.html"){
|
|||||||
const validateMatterPeople = (rule, value, callback) => {
|
const validateMatterPeople = (rule, value, callback) => {
|
||||||
if (!value || value <= 0) {
|
if (!value || value <= 0) {
|
||||||
callback(new Error("人数必须大于0"))
|
callback(new Error("人数必须大于0"))
|
||||||
} else if (this.batchForm.minGroupPeople && this.batchForm.maxGroupPeople
|
|
||||||
&& this.batchForm.minGroupPeople > this.batchForm.maxGroupPeople) {
|
|
||||||
callback(new Error("最少人数不能大于最多人数"))
|
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const validateMatterMaxSignupPeople = (rule, value, callback) => {
|
||||||
|
if (value === "" || value === null || value === undefined || value >= 0) {
|
||||||
|
callback()
|
||||||
|
} else {
|
||||||
|
callback(new Error("最多报名人数不能小于0"))
|
||||||
|
}
|
||||||
|
}
|
||||||
const validateMatterTime = (rule, value, callback) => {
|
const validateMatterTime = (rule, value, callback) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
callback(new Error("必填"))
|
callback(new Error("必填"))
|
||||||
@@ -438,7 +457,8 @@ layout("/layouts/platform.html"){
|
|||||||
pageOrderBy: "ascending",
|
pageOrderBy: "ascending",
|
||||||
year: currentYear,
|
year: currentYear,
|
||||||
lineName: "",
|
lineName: "",
|
||||||
lotId: ""
|
lotId: "",
|
||||||
|
lineType: ""
|
||||||
},
|
},
|
||||||
formData: {},
|
formData: {},
|
||||||
matterForm: {},
|
matterForm: {},
|
||||||
@@ -468,7 +488,7 @@ layout("/layouts/platform.html"){
|
|||||||
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||||
contactPhone: [{ validator: validateMatterMobile, trigger: ["blur", "change"] }],
|
contactPhone: [{ validator: validateMatterMobile, trigger: ["blur", "change"] }],
|
||||||
minGroupPeople: [{ validator: validateMatterPeople, trigger: ["blur", "change"] }],
|
minGroupPeople: [{ validator: validateMatterPeople, trigger: ["blur", "change"] }],
|
||||||
maxGroupPeople: [{ validator: validateMatterPeople, trigger: ["blur", "change"] }],
|
maxSignupPeople: [{ validator: validateMatterMaxSignupPeople, trigger: ["blur", "change"] }],
|
||||||
estimatedCost: [{ validator: validateMatterMoney, trigger: ["blur", "change"] }]
|
estimatedCost: [{ validator: validateMatterMoney, trigger: ["blur", "change"] }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,9 +498,20 @@ layout("/layouts/platform.html"){
|
|||||||
this.pageForm.year = moment().format("YYYY")
|
this.pageForm.year = moment().format("YYYY")
|
||||||
this.pageForm.lineName = ""
|
this.pageForm.lineName = ""
|
||||||
this.pageForm.lotId = ""
|
this.pageForm.lotId = ""
|
||||||
|
this.pageForm.lineType = ""
|
||||||
this.loadOptions(this.pageForm.year)
|
this.loadOptions(this.pageForm.year)
|
||||||
this.doSearch()
|
this.doSearch()
|
||||||
},
|
},
|
||||||
|
exportData() {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
;["year", "lineName", "lotId", "lineType"].forEach(key => {
|
||||||
|
const value = this.pageForm[key]
|
||||||
|
if (value !== undefined && value !== null && value !== "") {
|
||||||
|
params.append(key, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
window.location.href = loc() + "/exportData?" + params.toString()
|
||||||
|
},
|
||||||
tableRowClassName({ row }) {
|
tableRowClassName({ row }) {
|
||||||
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
|
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
|
||||||
},
|
},
|
||||||
@@ -586,6 +617,7 @@ layout("/layouts/platform.html"){
|
|||||||
contactPhone: "",
|
contactPhone: "",
|
||||||
minGroupPeople: null,
|
minGroupPeople: null,
|
||||||
maxGroupPeople: null,
|
maxGroupPeople: null,
|
||||||
|
maxSignupPeople: null,
|
||||||
estimatedCost: ""
|
estimatedCost: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -767,15 +799,16 @@ layout("/layouts/platform.html"){
|
|||||||
},
|
},
|
||||||
applyMatterSettingPeople(data) {
|
applyMatterSettingPeople(data) {
|
||||||
if (!data) return
|
if (!data) return
|
||||||
|
const hasSavedLineConfig = !!this.batchForm.lineId
|
||||||
if (data.minGroupPeople !== null && data.minGroupPeople !== undefined && data.minGroupPeople !== "") {
|
if (data.minGroupPeople !== null && data.minGroupPeople !== undefined && data.minGroupPeople !== "") {
|
||||||
this.$set(this.batchForm, "minGroupPeople", data.minGroupPeople)
|
this.$set(this.batchForm, "minGroupPeople", data.minGroupPeople)
|
||||||
}
|
}
|
||||||
if (data.maxGroupPeople !== null && data.maxGroupPeople !== undefined && data.maxGroupPeople !== "") {
|
if (!hasSavedLineConfig && data.maxGroupPeople !== null && data.maxGroupPeople !== undefined && data.maxGroupPeople !== "") {
|
||||||
this.$set(this.batchForm, "maxGroupPeople", data.maxGroupPeople)
|
this.$set(this.batchForm, "maxGroupPeople", data.maxGroupPeople)
|
||||||
}
|
}
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.batchFormRef) {
|
if (this.$refs.batchFormRef) {
|
||||||
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople"])
|
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople", "maxSignupPeople"])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -792,7 +825,7 @@ layout("/layouts/platform.html"){
|
|||||||
matterPeopleChange() {
|
matterPeopleChange() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.batchFormRef) {
|
if (this.$refs.batchFormRef) {
|
||||||
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople"])
|
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople", "maxSignupPeople"])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
+115
-23
@@ -72,7 +72,9 @@ layout("/layouts/platform.html"){
|
|||||||
:data="tableData"
|
:data="tableData"
|
||||||
:size="tableSize"
|
:size="tableSize"
|
||||||
border
|
border
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
ref="tourMainTable"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
@@ -113,25 +115,39 @@ layout("/layouts/platform.html"){
|
|||||||
<div class="process-title">
|
<div class="process-title">
|
||||||
{{ formData.taskName }}
|
{{ formData.taskName }}
|
||||||
</div>
|
</div>
|
||||||
<el-form
|
<div class="tour-audit-panel">
|
||||||
:model="formData"
|
<el-form
|
||||||
ref="formRef"
|
:model="formData"
|
||||||
label-width="0"
|
ref="formRef"
|
||||||
label-suffix=":"
|
label-width="100px"
|
||||||
class="flow-task-form">
|
label-suffix=":"
|
||||||
<el-form-item
|
class="tour-audit-form">
|
||||||
label="审批意见"
|
<el-form-item class="tour-audit-result-item" label="审核结果" prop="auditResult" required>
|
||||||
prop="tf_opinion"
|
<el-radio-group class="tour-audit-result-group" v-model="formData.auditResult" size="small" @change="auditResultChange">
|
||||||
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
<el-radio :label="1" border>同意申请</el-radio>
|
||||||
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
|
<el-radio :label="2" border>拒绝申请</el-radio>
|
||||||
</el-form-item>
|
<el-radio :label="6" border>退回发起人</el-radio>
|
||||||
</el-form>
|
</el-radio-group>
|
||||||
<el-row type="flex" justify="end">
|
</el-form-item>
|
||||||
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
|
<el-form-item
|
||||||
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
|
v-if="formData.auditResult !== 1"
|
||||||
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
|
label="审核意见"
|
||||||
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
|
prop="tf_opinion"
|
||||||
</el-row>
|
:rules="[{validator: validateAuditOpinion, trigger:['change','blur']}]">
|
||||||
|
<user-opinion-textarea
|
||||||
|
class="tour-audit-opinion"
|
||||||
|
v-model="formData.tf_opinion"
|
||||||
|
:rows="5"
|
||||||
|
maxlength="500"
|
||||||
|
:placeholder="auditOpinionPlaceholder()">
|
||||||
|
</user-opinion-textarea>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div class="tour-audit-action">
|
||||||
|
<el-button class="tour-audit-cancel-btn" @click="$refs.guava.index()" size="medium">取消</el-button>
|
||||||
|
<el-button class="tour-audit-submit-btn" @click="handleTaskAction" size="medium" type="primary">提交</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</tour-approval-info>
|
</tour-approval-info>
|
||||||
</template>
|
</template>
|
||||||
@@ -169,6 +185,60 @@ layout("/layouts/platform.html"){
|
|||||||
.tour-approval-empty {
|
.tour-approval-empty {
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
.tour-audit-panel {
|
||||||
|
position: relative;
|
||||||
|
min-height: 230px;
|
||||||
|
padding-bottom: 62px;
|
||||||
|
}
|
||||||
|
.tour-audit-form {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.tour-audit-form .el-form-item__label {
|
||||||
|
display: block;
|
||||||
|
color: #606266;
|
||||||
|
font-weight: 400;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.tour-audit-form .el-form-item__content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tour-audit-result-item {
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
.tour-audit-result-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.tour-audit-result-group .el-radio {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.tour-audit-opinion .el-textarea__inner {
|
||||||
|
min-height: 112px !important;
|
||||||
|
}
|
||||||
|
.tour-audit-opinion .opinion-wrap {
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
.tour-audit-opinion .opinion-wrap .el-select {
|
||||||
|
width: 190px;
|
||||||
|
}
|
||||||
|
.tour-audit-action {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.tour-audit-cancel-btn,
|
||||||
|
.tour-audit-submit-btn {
|
||||||
|
min-width: 86px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
@@ -345,7 +415,7 @@ layout("/layouts/platform.html"){
|
|||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
data() {
|
data() {
|
||||||
const currentYear = moment().format("YYYY")
|
const currentYear = moment().format("YYYY")
|
||||||
return {
|
return {
|
||||||
@@ -355,6 +425,7 @@ layout("/layouts/platform.html"){
|
|||||||
lineTypeOptions: [],
|
lineTypeOptions: [],
|
||||||
showApprovalForm: false,
|
showApprovalForm: false,
|
||||||
formData: {
|
formData: {
|
||||||
|
auditResult: 1,
|
||||||
tf_opinion: ""
|
tf_opinion: ""
|
||||||
},
|
},
|
||||||
pageForm: {
|
pageForm: {
|
||||||
@@ -402,6 +473,7 @@ layout("/layouts/platform.html"){
|
|||||||
this.$refs.guava.edit(() => {
|
this.$refs.guava.edit(() => {
|
||||||
this.showApprovalForm = false
|
this.showApprovalForm = false
|
||||||
this.formData = {
|
this.formData = {
|
||||||
|
auditResult: 1,
|
||||||
tf_opinion: ""
|
tf_opinion: ""
|
||||||
}
|
}
|
||||||
this.$refs.infoRef.onOpen(row)
|
this.$refs.infoRef.onOpen(row)
|
||||||
@@ -413,12 +485,32 @@ layout("/layouts/platform.html"){
|
|||||||
this.formData = {
|
this.formData = {
|
||||||
processTaskId: row.taskId,
|
processTaskId: row.taskId,
|
||||||
taskName: row.curTaskName || row.taskName,
|
taskName: row.curTaskName || row.taskName,
|
||||||
|
auditResult: 1,
|
||||||
tf_opinion: ""
|
tf_opinion: ""
|
||||||
}
|
}
|
||||||
this.$refs.infoRef.onOpen(row)
|
this.$refs.infoRef.onOpen(row)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleTaskAction(val) {
|
auditResultChange() {
|
||||||
|
if (this.formData.auditResult === 1) {
|
||||||
|
this.formData.tf_opinion = ""
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.formRef && this.$refs.formRef.clearValidate("tf_opinion")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
auditOpinionPlaceholder() {
|
||||||
|
return this.formData.auditResult === 1 ? "" : "拒绝申请或退回发起人时,请填写审核意见"
|
||||||
|
},
|
||||||
|
validateAuditOpinion(rule, value, callback) {
|
||||||
|
// 同意申请不要求审核意见;拒绝申请和退回发起人必须说明原因,便于申请人查看处理依据。
|
||||||
|
if (this.formData.auditResult === 1 || (value && String(value).trim())) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback(new Error("拒绝申请或退回发起人时,请填写审核意见"))
|
||||||
|
},
|
||||||
|
handleTaskAction() {
|
||||||
this.$refs.formRef.validate((valid) => {
|
this.$refs.formRef.validate((valid) => {
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
this.$confirm("您确定要提交吗?", "提示", {
|
this.$confirm("您确定要提交吗?", "提示", {
|
||||||
@@ -430,7 +522,7 @@ layout("/layouts/platform.html"){
|
|||||||
this.$axios.post("/flow/common/executeTask", {
|
this.$axios.post("/flow/common/executeTask", {
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
...this.formData,
|
...this.formData,
|
||||||
submitType: val
|
submitType: this.formData.auditResult
|
||||||
})
|
})
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ layout("/layouts/platform.html"){
|
|||||||
row-key="id"
|
row-key="id"
|
||||||
v-loading="tableLoading"
|
v-loading="tableLoading"
|
||||||
@sort-change="pageOrder"
|
@sort-change="pageOrder"
|
||||||
>
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="70" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="70" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="年度" prop="year" width="100" align="center" header-align="center"></el-table-column>
|
<el-table-column label="年度" prop="year" width="100" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="疗休养配置名称" prop="configName" min-width="260" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="疗休养配置名称" prop="configName" min-width="260" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
@@ -311,25 +311,12 @@ layout("/layouts/platform.html"){
|
|||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
data() {
|
data() {
|
||||||
// 前端先做成团人数区间校验,后端也会再兜底校验一次。
|
const checkSignupTime = (rule, value, callback) => {
|
||||||
const checkGroupPeople = (rule, value, callback) => {
|
if (this.formData.signupStartTime && this.formData.signupEndTime
|
||||||
if (this.formData.minGroupPeople !== null && this.formData.maxGroupPeople !== null
|
&& this.formData.signupStartTime >= this.formData.signupEndTime) {
|
||||||
&& this.formData.minGroupPeople > this.formData.maxGroupPeople) {
|
callback(new Error("开始报名时间必须小于结束报名时间"))
|
||||||
callback(new Error("最少成团人数不能大于最多成团人数"))
|
|
||||||
} else {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const checkSignupTime = (rule, value, callback) => {
|
|
||||||
if (!value) {
|
|
||||||
callback(new Error("必填"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.formData.signupStartTime && this.formData.signupEndTime
|
|
||||||
&& this.formData.signupStartTime >= this.formData.signupEndTime) {
|
|
||||||
callback(new Error("开始报名时间必须小于结束报名时间"))
|
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
@@ -369,8 +356,6 @@ layout("/layouts/platform.html"){
|
|||||||
formRules: {
|
formRules: {
|
||||||
year: [{required: true, message: "必填", trigger: ["blur", "change"]}],
|
year: [{required: true, message: "必填", trigger: ["blur", "change"]}],
|
||||||
configName: [{required: true, message: "必填", trigger: ["blur", "change"]}],
|
configName: [{required: true, message: "必填", trigger: ["blur", "change"]}],
|
||||||
minGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
|
|
||||||
maxGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
|
|
||||||
signupStartTime: [{validator: checkSignupTime, trigger: ["blur", "change"]}],
|
signupStartTime: [{validator: checkSignupTime, trigger: ["blur", "change"]}],
|
||||||
signupEndTime: [{validator: checkSignupTime, trigger: ["blur", "change"]}],
|
signupEndTime: [{validator: checkSignupTime, trigger: ["blur", "change"]}],
|
||||||
outProvinceRatioType: [{required: true, message: "必填", trigger: ["blur", "change"]}],
|
outProvinceRatioType: [{required: true, message: "必填", trigger: ["blur", "change"]}],
|
||||||
@@ -384,11 +369,12 @@ layout("/layouts/platform.html"){
|
|||||||
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue")
|
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue")
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getActivityGroup() {
|
getActivityGroup() {
|
||||||
const {data} = await this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup")
|
return this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup").then((res) => {
|
||||||
this.activityGroupList = (data || []).map((item) => {
|
this.activityGroupList = ((res && res.data) || []).map((item) => {
|
||||||
return Object.assign({}, item, {
|
return Object.assign({}, item, {
|
||||||
groupId: item.groupId === null || item.groupId === undefined ? "" : String(item.groupId)
|
groupId: item.groupId === null || item.groupId === undefined ? "" : String(item.groupId)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -398,11 +384,11 @@ layout("/layouts/platform.html"){
|
|||||||
this.$refs.drawerUserScope.userScopeDialog = true
|
this.$refs.drawerUserScope.userScopeDialog = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleActivityGroupChange() {
|
handleActivityGroupChange() {
|
||||||
if (this.formData.activityGroupId !== null && this.formData.activityGroupId !== undefined) {
|
if (this.formData.activityGroupId !== null && this.formData.activityGroupId !== undefined) {
|
||||||
this.formData.activityGroupId = String(this.formData.activityGroupId)
|
this.formData.activityGroupId = String(this.formData.activityGroupId)
|
||||||
}
|
}
|
||||||
await this.getActivityGroup()
|
this.getActivityGroup()
|
||||||
},
|
},
|
||||||
resetSearch() {
|
resetSearch() {
|
||||||
this.pageForm.year = ""
|
this.pageForm.year = ""
|
||||||
@@ -548,6 +534,9 @@ layout("/layouts/platform.html"){
|
|||||||
if (submitData.outProvinceRatioType !== "固定人数") {
|
if (submitData.outProvinceRatioType !== "固定人数") {
|
||||||
submitData.outProvinceFixedPeople = 0
|
submitData.outProvinceFixedPeople = 0
|
||||||
}
|
}
|
||||||
|
// 报名时间允许清空,日期组件清空后的 null 统一转为空字符串提交,便于后端覆盖旧值。
|
||||||
|
submitData.signupStartTime = submitData.signupStartTime || ""
|
||||||
|
submitData.signupEndTime = submitData.signupEndTime || ""
|
||||||
submitData.lots = JSON.stringify(this.formData.lots || [])
|
submitData.lots = JSON.stringify(this.formData.lots || [])
|
||||||
submitData.lotDeleteList = JSON.stringify(this.lotDeleteList)
|
submitData.lotDeleteList = JSON.stringify(this.lotDeleteList)
|
||||||
this.$axios.post(loc() + "/doSubmit", submitData).then((res) => {
|
this.$axios.post(loc() + "/doSubmit", submitData).then((res) => {
|
||||||
|
|||||||
@@ -42,16 +42,21 @@ layout("/layouts/platform.html"){
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="mt10" shadow="never">
|
<el-card class="mt10" shadow="never">
|
||||||
<table-tool :app="this" label="线路列表"></table-tool>
|
<div class="tour-signup-list-toolbar">
|
||||||
|
<table-tool :app="this" label="线路列表"></table-tool>
|
||||||
|
<el-button class="tour-signup-export-btn" size="medium" type="primary" icon="el-icon-download" @click="exportData">导出</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
v-loading="tableLoading"
|
v-loading="tableLoading"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
:size="tableSize"
|
:size="tableSize"
|
||||||
border
|
border
|
||||||
:default-sort="{prop: 'lineName', order: 'ascending'}"
|
:default-sort="{prop: 'lineCode', order: 'ascending'}"
|
||||||
:row-class-name="tableRowClassName"
|
:row-class-name="tableRowClassName"
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
ref="tourMainTable"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="出行时段" prop="travelPeriod" width="240" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="出行时段" prop="travelPeriod" width="240" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
@@ -72,6 +77,11 @@ layout("/layouts/platform.html"){
|
|||||||
<span>{{ row.maxGroupPeople || 0 }}人</span>
|
<span>{{ row.maxGroupPeople || 0 }}人</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="最多报名人数" prop="maxSignupPeople" width="140" sortable="custom" align="center" header-align="center">
|
||||||
|
<template slot-scope="{row}">
|
||||||
|
<span>{{ row.maxSignupPeople || 0 }}人</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="报名人数" prop="signupCount" width="130" sortable="custom" align="center" header-align="center">
|
<el-table-column label="报名人数" prop="signupCount" width="130" sortable="custom" align="center" header-align="center">
|
||||||
<template slot-scope="{row}">
|
<template slot-scope="{row}">
|
||||||
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">{{ row.signupCount || 0 }}人</el-tag>
|
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">{{ row.signupCount || 0 }}人</el-tag>
|
||||||
@@ -373,6 +383,7 @@ layout("/layouts/platform.html"){
|
|||||||
<el-descriptions-item label="创建人">{{ lineDetail.creatorName || '' }}</el-descriptions-item>
|
<el-descriptions-item label="创建人">{{ lineDetail.creatorName || '' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="所在单位">{{ lineDetail.unitName || '' }}</el-descriptions-item>
|
<el-descriptions-item label="所在单位">{{ lineDetail.unitName || '' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="是否对外开放">{{ lineDetail.openFlag ? '是' : '否' }}</el-descriptions-item>
|
<el-descriptions-item label="是否对外开放">{{ lineDetail.openFlag ? '是' : '否' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="最多报名人数">{{ lineDetail.maxSignupPeople || 0 }}人</el-descriptions-item>
|
||||||
<el-descriptions-item label="激活状态">{{ lineDetail.enabled ? '启用' : '禁用' }}</el-descriptions-item>
|
<el-descriptions-item label="激活状态">{{ lineDetail.enabled ? '启用' : '禁用' }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
@@ -415,7 +426,9 @@ layout("/layouts/platform.html"){
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="mt10" shadow="never">
|
<el-card class="mt10" shadow="never">
|
||||||
<div class="tour-group-view-title">报名人员列表</div>
|
<div class="tour-group-view-header">
|
||||||
|
<div class="tour-group-view-title">报名人员列表</div>
|
||||||
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
v-loading="peopleViewLoading"
|
v-loading="peopleViewLoading"
|
||||||
:data="peopleViewTableData"
|
:data="peopleViewTableData"
|
||||||
@@ -425,8 +438,19 @@ layout("/layouts/platform.html"){
|
|||||||
<el-table-column label="序号" type="index" :index="peopleViewIndexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="peopleViewIndexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column v-if="peopleViewGroupMode" label="性别" prop="gender" width="90" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column v-if="peopleViewGroupMode" label="手机号码" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column v-if="peopleViewGroupMode" label="身份证号" prop="idCard" min-width="190" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column v-if="!peopleViewGroupMode" label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column v-if="peopleViewGroupMode" label="人员类型" prop="rowTypeName" width="90" align="center" header-align="center"></el-table-column>
|
||||||
|
<el-table-column v-if="peopleViewGroupMode" label="亲属关系" prop="relationship" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column v-if="peopleViewGroupMode" label="成团信息" prop="groupName" width="160" sortable="custom" align="center" header-align="center" show-overflow-tooltip>
|
||||||
|
<template slot-scope="{row}">
|
||||||
|
<span>{{ formatPeopleGroupInfo(row) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="peopleViewGroupMode" label="关联教职工名称" prop="relatedStaffName" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-row class="el-pagination-container tour-group-view-pagination">
|
<el-row class="el-pagination-container tour-group-view-pagination">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@@ -548,6 +572,23 @@ layout("/layouts/platform.html"){
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.tour-signup-list-toolbar {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.tour-signup-export-btn {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.tour-group-view-header {
|
||||||
|
align-items: flex-start;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
.tour-group-view-title {
|
.tour-group-view-title {
|
||||||
border-left: 4px solid #0079c2;
|
border-left: 4px solid #0079c2;
|
||||||
color: #0079c2;
|
color: #0079c2;
|
||||||
@@ -592,7 +633,7 @@ layout("/layouts/platform.html"){
|
|||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
data() {
|
data() {
|
||||||
const currentYear = moment().format("YYYY")
|
const currentYear = moment().format("YYYY")
|
||||||
return {
|
return {
|
||||||
@@ -620,11 +661,12 @@ layout("/layouts/platform.html"){
|
|||||||
peopleViewTitle: "",
|
peopleViewTitle: "",
|
||||||
peopleViewRow: {},
|
peopleViewRow: {},
|
||||||
peopleViewTableData: [],
|
peopleViewTableData: [],
|
||||||
|
peopleViewGroupMode: false,
|
||||||
pageForm: {
|
pageForm: {
|
||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
pageOrderName: "lineName",
|
pageOrderName: "lineCode",
|
||||||
pageOrderBy: "ascending",
|
pageOrderBy: "ascending",
|
||||||
year: currentYear,
|
year: currentYear,
|
||||||
lineName: "",
|
lineName: "",
|
||||||
@@ -635,10 +677,11 @@ layout("/layouts/platform.html"){
|
|||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
pageOrderName: "signupTime",
|
pageOrderName: "groupCode",
|
||||||
pageOrderBy: "descending",
|
pageOrderBy: "ascending",
|
||||||
keyword: "",
|
keyword: "",
|
||||||
unionId: ""
|
unionId: "",
|
||||||
|
groupCode: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -651,6 +694,16 @@ layout("/layouts/platform.html"){
|
|||||||
this.loadLineTypeOptions()
|
this.loadLineTypeOptions()
|
||||||
this.doSearch()
|
this.doSearch()
|
||||||
},
|
},
|
||||||
|
exportData() {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
;["year", "lineName", "lineType", "unionId", "pageOrderName", "pageOrderBy"].forEach(key => {
|
||||||
|
const value = this.pageForm[key]
|
||||||
|
if (value !== undefined && value !== null && value !== "") {
|
||||||
|
params.append(key, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
window.location.href = loc() + "/exportData?" + params.toString()
|
||||||
|
},
|
||||||
signupActionText(row) {
|
signupActionText(row) {
|
||||||
if (row && row.signed) {
|
if (row && row.signed) {
|
||||||
if (this.canRevokeSignup(row)) {
|
if (this.canRevokeSignup(row)) {
|
||||||
@@ -719,17 +772,27 @@ layout("/layouts/platform.html"){
|
|||||||
|| row.directFamilyUnitLine === 1
|
|| row.directFamilyUnitLine === 1
|
||||||
|| row.directFamilyUnitLine === "1"
|
|| row.directFamilyUnitLine === "1"
|
||||||
},
|
},
|
||||||
|
formatPeopleGroupInfo(row) {
|
||||||
|
if (!row || !row.groupCode) return "未成团"
|
||||||
|
return row.groupName ? row.groupCode + " " + row.groupName : row.groupCode
|
||||||
|
},
|
||||||
|
canUseGroupPeopleView() {
|
||||||
|
// Only these roles keep the grouped staff/family popup; other users use the original staff-only popup.
|
||||||
|
return this.$auth && this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN", "BRANCH_UNION_CHAIRMAN"])
|
||||||
|
},
|
||||||
openPeopleView(row) {
|
openPeopleView(row) {
|
||||||
this.peopleViewRow = row || {}
|
this.peopleViewRow = row || {}
|
||||||
|
this.peopleViewGroupMode = this.canUseGroupPeopleView()
|
||||||
this.peopleViewTitle = (this.peopleViewRow.lineName || "报名人员") + " - 报名人员"
|
this.peopleViewTitle = (this.peopleViewRow.lineName || "报名人员") + " - 报名人员"
|
||||||
this.peopleViewVisible = true
|
this.peopleViewVisible = true
|
||||||
this.peopleViewForm.pageNumber = 1
|
this.peopleViewForm.pageNumber = 1
|
||||||
this.peopleViewForm.pageSize = 10
|
this.peopleViewForm.pageSize = 10
|
||||||
this.peopleViewForm.totalCount = 0
|
this.peopleViewForm.totalCount = 0
|
||||||
this.peopleViewForm.pageOrderName = "signupTime"
|
this.peopleViewForm.pageOrderName = this.peopleViewGroupMode ? "groupCode" : "signupTime"
|
||||||
this.peopleViewForm.pageOrderBy = "descending"
|
this.peopleViewForm.pageOrderBy = this.peopleViewGroupMode ? "ascending" : "descending"
|
||||||
this.peopleViewForm.keyword = ""
|
this.peopleViewForm.keyword = ""
|
||||||
this.peopleViewForm.unionId = ""
|
this.peopleViewForm.unionId = ""
|
||||||
|
this.peopleViewForm.groupCode = ""
|
||||||
this.peopleViewTableData = []
|
this.peopleViewTableData = []
|
||||||
this.loadPeopleViewData()
|
this.loadPeopleViewData()
|
||||||
},
|
},
|
||||||
@@ -740,6 +803,7 @@ layout("/layouts/platform.html"){
|
|||||||
peopleViewReset() {
|
peopleViewReset() {
|
||||||
this.peopleViewForm.keyword = ""
|
this.peopleViewForm.keyword = ""
|
||||||
this.peopleViewForm.unionId = ""
|
this.peopleViewForm.unionId = ""
|
||||||
|
this.peopleViewForm.groupCode = ""
|
||||||
this.peopleViewForm.pageNumber = 1
|
this.peopleViewForm.pageNumber = 1
|
||||||
this.loadPeopleViewData()
|
this.loadPeopleViewData()
|
||||||
},
|
},
|
||||||
@@ -756,8 +820,8 @@ layout("/layouts/platform.html"){
|
|||||||
this.loadPeopleViewData()
|
this.loadPeopleViewData()
|
||||||
},
|
},
|
||||||
peopleViewPageOrder({ prop, order }) {
|
peopleViewPageOrder({ prop, order }) {
|
||||||
this.peopleViewForm.pageOrderName = prop || "signupTime"
|
this.peopleViewForm.pageOrderName = prop || (this.peopleViewGroupMode ? "groupCode" : "signupTime")
|
||||||
this.peopleViewForm.pageOrderBy = order || "descending"
|
this.peopleViewForm.pageOrderBy = order || (this.peopleViewGroupMode ? "ascending" : "descending")
|
||||||
this.peopleViewForm.pageNumber = 1
|
this.peopleViewForm.pageNumber = 1
|
||||||
this.loadPeopleViewData()
|
this.loadPeopleViewData()
|
||||||
},
|
},
|
||||||
@@ -769,15 +833,21 @@ layout("/layouts/platform.html"){
|
|||||||
}
|
}
|
||||||
this.peopleViewLoading = true
|
this.peopleViewLoading = true
|
||||||
const requestSeq = ++this.peopleViewRequestSeq
|
const requestSeq = ++this.peopleViewRequestSeq
|
||||||
this.$axios.post(loc() + "/signupPageData", {
|
const requestUrl = this.peopleViewGroupMode ? "/platform/tour/group/signupPageData" : loc() + "/signupPageData"
|
||||||
|
const requestData = {
|
||||||
matterId: this.peopleViewRow.matterId,
|
matterId: this.peopleViewRow.matterId,
|
||||||
|
lineId: this.peopleViewRow.lineId,
|
||||||
keyword: this.peopleViewForm.keyword,
|
keyword: this.peopleViewForm.keyword,
|
||||||
unionId: this.peopleViewForm.unionId,
|
unionId: this.peopleViewForm.unionId,
|
||||||
pageNumber: this.peopleViewForm.pageNumber,
|
pageNumber: this.peopleViewForm.pageNumber,
|
||||||
pageSize: this.peopleViewForm.pageSize,
|
pageSize: this.peopleViewForm.pageSize,
|
||||||
pageOrderName: this.peopleViewForm.pageOrderName,
|
pageOrderName: this.peopleViewForm.pageOrderName,
|
||||||
pageOrderBy: this.peopleViewForm.pageOrderBy
|
pageOrderBy: this.peopleViewForm.pageOrderBy
|
||||||
}).then((res) => {
|
}
|
||||||
|
if (this.peopleViewGroupMode) {
|
||||||
|
requestData.groupCode = this.peopleViewForm.groupCode
|
||||||
|
}
|
||||||
|
this.$axios.post(requestUrl, requestData).then((res) => {
|
||||||
if (requestSeq !== this.peopleViewRequestSeq) return
|
if (requestSeq !== this.peopleViewRequestSeq) return
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const data = res.data || {}
|
const data = res.data || {}
|
||||||
@@ -801,7 +871,10 @@ layout("/layouts/platform.html"){
|
|||||||
openLineView(row) {
|
openLineView(row) {
|
||||||
this.$axios.post(loc() + "/lineDetail", { lineId: row.lineId }).then((res) => {
|
this.$axios.post(loc() + "/lineDetail", { lineId: row.lineId }).then((res) => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
this.lineDetail = res.data || {}
|
// 线路详情接口只返回线路资料,最多报名人数属于事项选线配置,打开抽屉时从当前列表行补入。
|
||||||
|
this.lineDetail = Object.assign({}, res.data || {}, {
|
||||||
|
maxSignupPeople: row.maxSignupPeople
|
||||||
|
})
|
||||||
this.lineContentHtml = ""
|
this.lineContentHtml = ""
|
||||||
this.lineVisible = true
|
this.lineVisible = true
|
||||||
this.buildLineContentHtmlAsync(this.lineDetail.lineContent).then((html) => {
|
this.buildLineContentHtmlAsync(this.lineDetail.lineContent).then((html) => {
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ layout("/layouts/platform.html"){
|
|||||||
:size="tableSize"
|
:size="tableSize"
|
||||||
border
|
border
|
||||||
:default-sort="{prop: 'agencyCode', order: 'ascending'}"
|
:default-sort="{prop: 'agencyCode', order: 'ascending'}"
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
ref="tourMainTable"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
|
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="旅行社名称" prop="agencyName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="旅行社名称" prop="agencyName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
@@ -179,7 +181,7 @@ layout("/layouts/platform.html"){
|
|||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
data() {
|
data() {
|
||||||
const currentYear = moment().format("YYYY")
|
const currentYear = moment().format("YYYY")
|
||||||
const validateMobile = (rule, value, callback) => {
|
const validateMobile = (rule, value, callback) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!--#
|
<!--#
|
||||||
layout("/layouts/platform.html"){
|
layout("/layouts/platform.html"){
|
||||||
#-->
|
#-->
|
||||||
|
|
||||||
@@ -29,8 +29,8 @@ layout("/layouts/platform.html"){
|
|||||||
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
|
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</search-item>
|
</search-item>
|
||||||
<search-item label="所属工会">
|
<search-item label="领办工会">
|
||||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
|
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择领办工会" style="width: 100%">
|
||||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</search-item>
|
</search-item>
|
||||||
@@ -56,40 +56,32 @@ layout("/layouts/platform.html"){
|
|||||||
border
|
border
|
||||||
:default-sort="{prop: 'lineName', order: 'ascending'}"
|
:default-sort="{prop: 'lineName', order: 'ascending'}"
|
||||||
:row-class-name="tableRowClassName"
|
:row-class-name="tableRowClassName"
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
ref="tourMainTable"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="出行时段" prop="travelPeriod" width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="出行时段" prop="travelPeriod" width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column v-if="false" label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column v-if="false" label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="领办工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="领办状态" prop="undertakeStatusName" width="150" sortable="custom" align="center" header-align="center">
|
<el-table-column label="领办状态" prop="undertakeStatusName" width="150" sortable="custom" align="center" header-align="center">
|
||||||
<template slot-scope="{row}">
|
<template slot-scope="{row}">
|
||||||
<el-button size="mini" :type="isClaimed(row) ? 'info' : 'primary'" plain>{{ row.undertakeStatusName || '未领' }}</el-button>
|
<el-button size="mini" :type="isClaimed(row) ? 'info' : 'primary'" plain>{{ row.undertakeStatusName || '未领' }}</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="线路类型" prop="lineType" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="线路类型" prop="lineType" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="最少成团人数" prop="minGroupPeople" width="140" sortable="custom" align="center" header-align="center">
|
<el-table-column label="成团信息" prop="groupName" width="160" align="center" header-align="center" show-overflow-tooltip>
|
||||||
<template slot-scope="{row}">
|
<template slot-scope="{row}">
|
||||||
{{ row.minGroupPeople || 0 }}人
|
<span>{{ formatGroupInfo(row) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="最多成团人数" prop="maxGroupPeople" width="140" sortable="custom" align="center" header-align="center">
|
<el-table-column label="报名人数" prop="signupCount" width="160" align="center" header-align="center">
|
||||||
<template slot-scope="{row}">
|
|
||||||
{{ row.maxGroupPeople || 0 }}人
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="报名人数" prop="signupCount" width="160" sortable="custom" align="center" header-align="center">
|
|
||||||
<template slot-scope="{row}">
|
<template slot-scope="{row}">
|
||||||
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">
|
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">
|
||||||
{{ row.signupCount || 0 }}人<span v-if="row.showCurrentUnionSignupCount">({{ row.currentUnionSignupCount || 0 }}人)</span>
|
{{ row.signupCount || 0 }}人<span v-if="row.showCurrentUnionSignupCount">({{ row.currentUnionSignupCount || 0 }}人)</span>
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="成团状态" prop="groupStatusName" width="120" sortable="custom" align="center" header-align="center">
|
|
||||||
<template slot-scope="{row}">
|
|
||||||
<el-tag size="mini" :type="groupStatusType(row.groupStatus)">{{ row.groupStatusName || '未成团' }}</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="220" align="center" header-align="center" fixed="right">
|
<el-table-column label="操作" width="220" align="center" header-align="center" fixed="right">
|
||||||
<template slot-scope="{row}">
|
<template slot-scope="{row}">
|
||||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
||||||
@@ -134,33 +126,31 @@ layout("/layouts/platform.html"){
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="mt10" shadow="never">
|
<el-card class="mt10" shadow="never">
|
||||||
<div class="tour-group-view-title">报名人员列表</div>
|
<div class="tour-group-view-toolbar">
|
||||||
|
<div class="tour-group-view-title">报名人员列表</div>
|
||||||
|
<el-button size="small" type="primary" icon="el-icon-download" @click="exportViewData">导出</el-button>
|
||||||
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
v-loading="viewLoading"
|
v-loading="viewLoading"
|
||||||
:data="viewTableData"
|
:data="viewTableData"
|
||||||
:size="tableSize"
|
:size="tableSize"
|
||||||
border
|
border
|
||||||
@sort-change="viewPageOrder">
|
@sort-change="viewPageOrder">
|
||||||
<el-table-column type="expand" width="50">
|
|
||||||
<template slot-scope="{row}">
|
|
||||||
<div v-if="row.families && row.families.length > 0" class="tour-family-list">
|
|
||||||
<el-table :data="row.families" :size="tableSize" border>
|
|
||||||
<el-table-column label="工号" prop="jobNo" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
|
||||||
<el-table-column label="姓名" prop="userName" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
|
||||||
<el-table-column label="亲属关系" prop="relationship" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
|
||||||
<el-table-column label="手机号" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
|
||||||
<el-table-column label="身份证号" prop="idCard" min-width="190" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
<div v-else class="tour-family-empty">暂无亲属信息</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="序号" type="index" :index="viewIndexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="viewIndexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="性别" prop="gender" width="90" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="手机号码" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="手机号码" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="身份证号" prop="idCard" min-width="190" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="身份证号" prop="idCard" min-width="190" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="人员类型" prop="rowTypeName" width="90" align="center" header-align="center"></el-table-column>
|
||||||
|
<el-table-column label="亲属关系" prop="relationship" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="成团信息" prop="groupName" width="160" sortable="custom" align="center" header-align="center" show-overflow-tooltip>
|
||||||
|
<template slot-scope="{row}">
|
||||||
|
<span>{{ formatGroupInfo(row) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="关联教职工名称" prop="relatedStaffName" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-row class="el-pagination-container tour-group-view-pagination">
|
<el-row class="el-pagination-container tour-group-view-pagination">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@@ -183,7 +173,7 @@ layout("/layouts/platform.html"){
|
|||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
data() {
|
data() {
|
||||||
const currentYear = moment().format("YYYY")
|
const currentYear = moment().format("YYYY")
|
||||||
return {
|
return {
|
||||||
@@ -194,6 +184,7 @@ layout("/layouts/platform.html"){
|
|||||||
viewRequestSeq: 0,
|
viewRequestSeq: 0,
|
||||||
viewTitle: "",
|
viewTitle: "",
|
||||||
viewRow: {},
|
viewRow: {},
|
||||||
|
viewGroup: {},
|
||||||
viewTableData: [],
|
viewTableData: [],
|
||||||
currentUnionInfo: null,
|
currentUnionInfo: null,
|
||||||
pageForm: {
|
pageForm: {
|
||||||
@@ -213,9 +204,10 @@ layout("/layouts/platform.html"){
|
|||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
pageOrderName: "signupTime",
|
pageOrderName: "signupTime",
|
||||||
pageOrderBy: "descending",
|
pageOrderBy: "ascending",
|
||||||
keyword: "",
|
keyword: "",
|
||||||
unionId: ""
|
unionId: "",
|
||||||
|
groupCode: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -233,11 +225,6 @@ layout("/layouts/platform.html"){
|
|||||||
this.pageForm.pageNumber = 1
|
this.pageForm.pageNumber = 1
|
||||||
this.pageData()
|
this.pageData()
|
||||||
},
|
},
|
||||||
groupStatusType(status) {
|
|
||||||
if (status === "formed") return "success"
|
|
||||||
if (status === "over") return "danger"
|
|
||||||
return "info"
|
|
||||||
},
|
|
||||||
tableRowClassName({ row }) {
|
tableRowClassName({ row }) {
|
||||||
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
|
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
|
||||||
},
|
},
|
||||||
@@ -250,17 +237,30 @@ layout("/layouts/platform.html"){
|
|||||||
isClaimed(row) {
|
isClaimed(row) {
|
||||||
return row && row.undertakeStatus === "claimed"
|
return row && row.undertakeStatus === "claimed"
|
||||||
},
|
},
|
||||||
|
formatGroupInfo(row) {
|
||||||
|
if (!row || !row.groupCode) return "未成团"
|
||||||
|
return row.groupName ? row.groupCode + " " + row.groupName : row.groupCode
|
||||||
|
},
|
||||||
openView(row) {
|
openView(row) {
|
||||||
|
if (!row || !row.groupCode) {
|
||||||
|
this.$message.warning("成团信息不完整")
|
||||||
|
return
|
||||||
|
}
|
||||||
this.viewRow = row || {}
|
this.viewRow = row || {}
|
||||||
this.viewTitle = this.viewRow.lineName || "报名人员"
|
this.viewGroup = {
|
||||||
|
groupCode: row.groupCode,
|
||||||
|
groupName: row.groupName
|
||||||
|
}
|
||||||
|
this.viewTitle = (this.viewRow.lineName || "报名人员") + " - " + (this.viewGroup.groupName || this.viewGroup.groupCode)
|
||||||
this.viewVisible = true
|
this.viewVisible = true
|
||||||
this.viewForm.pageNumber = 1
|
this.viewForm.pageNumber = 1
|
||||||
this.viewForm.pageSize = 10
|
this.viewForm.pageSize = 10
|
||||||
this.viewForm.totalCount = 0
|
this.viewForm.totalCount = 0
|
||||||
this.viewForm.pageOrderName = "signupTime"
|
this.viewForm.pageOrderName = "signupTime"
|
||||||
this.viewForm.pageOrderBy = "descending"
|
this.viewForm.pageOrderBy = "ascending"
|
||||||
this.viewForm.keyword = ""
|
this.viewForm.keyword = ""
|
||||||
this.viewForm.unionId = ""
|
this.viewForm.unionId = ""
|
||||||
|
this.viewForm.groupCode = this.viewGroup.groupCode || ""
|
||||||
this.viewTableData = []
|
this.viewTableData = []
|
||||||
this.loadViewData()
|
this.loadViewData()
|
||||||
},
|
},
|
||||||
@@ -278,24 +278,26 @@ layout("/layouts/platform.html"){
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
undertakeLine(row) {
|
undertakeLine(row) {
|
||||||
if (!row || !row.matterId) {
|
if (!row || !row.matterId || !row.groupCode) {
|
||||||
this.$message.warning("线路信息不完整")
|
this.$message.warning("线路成团信息不完整")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.isClaimed(row)) {
|
if (this.isClaimed(row)) {
|
||||||
this.$message.warning("当前线路已领办,不允许修改")
|
this.$message.warning("当前团已领办,不允许修改")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.loadCurrentUnion().then((union) => {
|
this.loadCurrentUnion().then((union) => {
|
||||||
const lineName = row.lineName || "当前线路"
|
const lineName = row.lineName || "当前线路"
|
||||||
|
const groupName = this.formatGroupInfo(row)
|
||||||
const unionName = (union && union.unionName) || ""
|
const unionName = (union && union.unionName) || ""
|
||||||
return this.$confirm(lineName + "由" + unionName + "领办,领办后,系统不允许修改,请确认。", "提示", {
|
return this.$confirm(lineName + "【" + groupName + "】由" + unionName + "领办,领办后,系统不允许修改,请确认。", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
type: "warning"
|
type: "warning"
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return this.$axios.post(loc() + "/undertakeLine", {
|
return this.$axios.post(loc() + "/undertakeLine", {
|
||||||
matterId: row.matterId
|
matterId: row.matterId,
|
||||||
|
groupCode: row.groupCode
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
@@ -309,22 +311,24 @@ layout("/layouts/platform.html"){
|
|||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
},
|
},
|
||||||
releaseUndertakeLine(row) {
|
releaseUndertakeLine(row) {
|
||||||
if (!row || !row.matterId) {
|
if (!row || !row.matterId || !row.groupCode) {
|
||||||
this.$message.warning("线路信息不完整")
|
this.$message.warning("线路成团信息不完整")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!this.isClaimed(row)) {
|
if (!this.isClaimed(row)) {
|
||||||
this.$message.warning("当前线路未领办,无需解除")
|
this.$message.warning("当前团未领办,无需解除")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const lineName = row.lineName || "当前线路"
|
const lineName = row.lineName || "当前线路"
|
||||||
this.$confirm("确认解除【" + lineName + "】的领办状态吗?解除后该线路将变为未领。", "提示", {
|
const groupName = this.formatGroupInfo(row)
|
||||||
|
this.$confirm("确认解除【" + lineName + "】" + groupName + "的领办状态吗?解除后该团将变为未领。", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
type: "warning"
|
type: "warning"
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return this.$axios.post(loc() + "/releaseUndertakeLine", {
|
return this.$axios.post(loc() + "/releaseUndertakeLine", {
|
||||||
matterId: row.matterId
|
matterId: row.matterId,
|
||||||
|
groupCode: row.groupCode
|
||||||
})
|
})
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (!res) return
|
if (!res) return
|
||||||
@@ -344,6 +348,7 @@ layout("/layouts/platform.html"){
|
|||||||
this.viewForm.keyword = ""
|
this.viewForm.keyword = ""
|
||||||
this.viewForm.unionId = ""
|
this.viewForm.unionId = ""
|
||||||
this.viewForm.pageNumber = 1
|
this.viewForm.pageNumber = 1
|
||||||
|
this.viewForm.groupCode = this.viewGroup.groupCode || ""
|
||||||
this.loadViewData()
|
this.loadViewData()
|
||||||
},
|
},
|
||||||
viewIndexMethod(index) {
|
viewIndexMethod(index) {
|
||||||
@@ -360,12 +365,12 @@ layout("/layouts/platform.html"){
|
|||||||
},
|
},
|
||||||
viewPageOrder({ prop, order }) {
|
viewPageOrder({ prop, order }) {
|
||||||
this.viewForm.pageOrderName = prop || "signupTime"
|
this.viewForm.pageOrderName = prop || "signupTime"
|
||||||
this.viewForm.pageOrderBy = order || "descending"
|
this.viewForm.pageOrderBy = order || "ascending"
|
||||||
this.viewForm.pageNumber = 1
|
this.viewForm.pageNumber = 1
|
||||||
this.loadViewData()
|
this.loadViewData()
|
||||||
},
|
},
|
||||||
loadViewData() {
|
loadViewData() {
|
||||||
if (!this.viewRow || !this.viewRow.matterId) {
|
if (!this.viewRow || !this.viewRow.matterId || !this.viewForm.groupCode) {
|
||||||
this.viewTableData = []
|
this.viewTableData = []
|
||||||
this.viewForm.totalCount = 0
|
this.viewForm.totalCount = 0
|
||||||
return
|
return
|
||||||
@@ -376,6 +381,7 @@ layout("/layouts/platform.html"){
|
|||||||
matterId: this.viewRow.matterId,
|
matterId: this.viewRow.matterId,
|
||||||
keyword: this.viewForm.keyword,
|
keyword: this.viewForm.keyword,
|
||||||
unionId: this.viewForm.unionId,
|
unionId: this.viewForm.unionId,
|
||||||
|
groupCode: this.viewForm.groupCode,
|
||||||
pageNumber: this.viewForm.pageNumber,
|
pageNumber: this.viewForm.pageNumber,
|
||||||
pageSize: this.viewForm.pageSize,
|
pageSize: this.viewForm.pageSize,
|
||||||
pageOrderName: this.viewForm.pageOrderName,
|
pageOrderName: this.viewForm.pageOrderName,
|
||||||
@@ -395,6 +401,20 @@ layout("/layouts/platform.html"){
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
exportViewData() {
|
||||||
|
if (!this.viewRow || !this.viewRow.matterId || !this.viewForm.groupCode) {
|
||||||
|
this.$message.warning("请先选择需要查看的成团线路")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$downLoad(loc() + "/exportSignupData", {
|
||||||
|
matterId: this.viewRow.matterId,
|
||||||
|
keyword: this.viewForm.keyword,
|
||||||
|
unionId: this.viewForm.unionId,
|
||||||
|
groupCode: this.viewForm.groupCode,
|
||||||
|
pageOrderName: this.viewForm.pageOrderName,
|
||||||
|
pageOrderBy: this.viewForm.pageOrderBy
|
||||||
|
})
|
||||||
|
},
|
||||||
loadUnionOptions() {
|
loadUnionOptions() {
|
||||||
this.$axios.post(loc() + "/unionOptions").then((res) => {
|
this.$axios.post(loc() + "/unionOptions").then((res) => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -442,15 +462,13 @@ layout("/layouts/platform.html"){
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
margin-bottom: 12px;
|
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
.tour-family-list {
|
.tour-group-view-toolbar {
|
||||||
padding: 8px 32px;
|
align-items: center;
|
||||||
}
|
display: flex;
|
||||||
.tour-family-empty {
|
justify-content: space-between;
|
||||||
color: #909399;
|
margin-bottom: 12px;
|
||||||
padding: 8px 32px;
|
|
||||||
}
|
}
|
||||||
.tour-undertake-tip {
|
.tour-undertake-tip {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
|||||||
+115
-23
@@ -72,7 +72,9 @@ layout("/layouts/platform.html"){
|
|||||||
:data="tableData"
|
:data="tableData"
|
||||||
:size="tableSize"
|
:size="tableSize"
|
||||||
border
|
border
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
ref="tourMainTable"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
@@ -113,25 +115,39 @@ layout("/layouts/platform.html"){
|
|||||||
<div class="process-title">
|
<div class="process-title">
|
||||||
{{ formData.taskName }}
|
{{ formData.taskName }}
|
||||||
</div>
|
</div>
|
||||||
<el-form
|
<div class="tour-audit-panel">
|
||||||
:model="formData"
|
<el-form
|
||||||
ref="formRef"
|
:model="formData"
|
||||||
label-width="0"
|
ref="formRef"
|
||||||
label-suffix=":"
|
label-width="100px"
|
||||||
class="flow-task-form">
|
label-suffix=":"
|
||||||
<el-form-item
|
class="tour-audit-form">
|
||||||
label="审批意见"
|
<el-form-item class="tour-audit-result-item" label="审核结果" prop="auditResult" required>
|
||||||
prop="tf_opinion"
|
<el-radio-group class="tour-audit-result-group" v-model="formData.auditResult" size="small" @change="auditResultChange">
|
||||||
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
<el-radio :label="1" border>同意申请</el-radio>
|
||||||
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
|
<el-radio :label="2" border>拒绝申请</el-radio>
|
||||||
</el-form-item>
|
<el-radio :label="6" border>退回发起人</el-radio>
|
||||||
</el-form>
|
</el-radio-group>
|
||||||
<el-row type="flex" justify="end">
|
</el-form-item>
|
||||||
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
|
<el-form-item
|
||||||
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
|
v-if="formData.auditResult !== 1"
|
||||||
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
|
label="审核意见"
|
||||||
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
|
prop="tf_opinion"
|
||||||
</el-row>
|
:rules="[{validator: validateAuditOpinion, trigger:['change','blur']}]">
|
||||||
|
<user-opinion-textarea
|
||||||
|
class="tour-audit-opinion"
|
||||||
|
v-model="formData.tf_opinion"
|
||||||
|
:rows="5"
|
||||||
|
maxlength="500"
|
||||||
|
:placeholder="auditOpinionPlaceholder()">
|
||||||
|
</user-opinion-textarea>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div class="tour-audit-action">
|
||||||
|
<el-button class="tour-audit-cancel-btn" @click="$refs.guava.index()" size="medium">取消</el-button>
|
||||||
|
<el-button class="tour-audit-submit-btn" @click="handleTaskAction" size="medium" type="primary">提交</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</tour-approval-info>
|
</tour-approval-info>
|
||||||
</template>
|
</template>
|
||||||
@@ -169,6 +185,60 @@ layout("/layouts/platform.html"){
|
|||||||
.tour-approval-empty {
|
.tour-approval-empty {
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
.tour-audit-panel {
|
||||||
|
position: relative;
|
||||||
|
min-height: 230px;
|
||||||
|
padding-bottom: 62px;
|
||||||
|
}
|
||||||
|
.tour-audit-form {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.tour-audit-form .el-form-item__label {
|
||||||
|
display: block;
|
||||||
|
color: #606266;
|
||||||
|
font-weight: 400;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.tour-audit-form .el-form-item__content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tour-audit-result-item {
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
.tour-audit-result-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.tour-audit-result-group .el-radio {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.tour-audit-opinion .el-textarea__inner {
|
||||||
|
min-height: 112px !important;
|
||||||
|
}
|
||||||
|
.tour-audit-opinion .opinion-wrap {
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
.tour-audit-opinion .opinion-wrap .el-select {
|
||||||
|
width: 190px;
|
||||||
|
}
|
||||||
|
.tour-audit-action {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.tour-audit-cancel-btn,
|
||||||
|
.tour-audit-submit-btn {
|
||||||
|
min-width: 86px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
@@ -345,7 +415,7 @@ layout("/layouts/platform.html"){
|
|||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
data() {
|
data() {
|
||||||
const currentYear = moment().format("YYYY")
|
const currentYear = moment().format("YYYY")
|
||||||
return {
|
return {
|
||||||
@@ -355,6 +425,7 @@ layout("/layouts/platform.html"){
|
|||||||
lineTypeOptions: [],
|
lineTypeOptions: [],
|
||||||
showApprovalForm: false,
|
showApprovalForm: false,
|
||||||
formData: {
|
formData: {
|
||||||
|
auditResult: 1,
|
||||||
tf_opinion: ""
|
tf_opinion: ""
|
||||||
},
|
},
|
||||||
pageForm: {
|
pageForm: {
|
||||||
@@ -402,6 +473,7 @@ layout("/layouts/platform.html"){
|
|||||||
this.$refs.guava.edit(() => {
|
this.$refs.guava.edit(() => {
|
||||||
this.showApprovalForm = false
|
this.showApprovalForm = false
|
||||||
this.formData = {
|
this.formData = {
|
||||||
|
auditResult: 1,
|
||||||
tf_opinion: ""
|
tf_opinion: ""
|
||||||
}
|
}
|
||||||
this.$refs.infoRef.onOpen(row)
|
this.$refs.infoRef.onOpen(row)
|
||||||
@@ -413,12 +485,32 @@ layout("/layouts/platform.html"){
|
|||||||
this.formData = {
|
this.formData = {
|
||||||
processTaskId: row.taskId,
|
processTaskId: row.taskId,
|
||||||
taskName: row.curTaskName || row.taskName,
|
taskName: row.curTaskName || row.taskName,
|
||||||
|
auditResult: 1,
|
||||||
tf_opinion: ""
|
tf_opinion: ""
|
||||||
}
|
}
|
||||||
this.$refs.infoRef.onOpen(row)
|
this.$refs.infoRef.onOpen(row)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleTaskAction(val) {
|
auditResultChange() {
|
||||||
|
if (this.formData.auditResult === 1) {
|
||||||
|
this.formData.tf_opinion = ""
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.formRef && this.$refs.formRef.clearValidate("tf_opinion")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
auditOpinionPlaceholder() {
|
||||||
|
return this.formData.auditResult === 1 ? "" : "拒绝申请或退回发起人时,请填写审核意见"
|
||||||
|
},
|
||||||
|
validateAuditOpinion(rule, value, callback) {
|
||||||
|
// 同意申请不要求审核意见;拒绝申请和退回发起人必须说明原因,便于申请人查看处理依据。
|
||||||
|
if (this.formData.auditResult === 1 || (value && String(value).trim())) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback(new Error("拒绝申请或退回发起人时,请填写审核意见"))
|
||||||
|
},
|
||||||
|
handleTaskAction() {
|
||||||
this.$refs.formRef.validate((valid) => {
|
this.$refs.formRef.validate((valid) => {
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
this.$confirm("您确定要提交吗?", "提示", {
|
this.$confirm("您确定要提交吗?", "提示", {
|
||||||
@@ -430,7 +522,7 @@ layout("/layouts/platform.html"){
|
|||||||
this.$axios.post("/flow/common/executeTask", {
|
this.$axios.post("/flow/common/executeTask", {
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
...this.formData,
|
...this.formData,
|
||||||
submitType: val
|
submitType: this.formData.auditResult
|
||||||
})
|
})
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
|
|||||||
@@ -102,7 +102,9 @@ layout("/layouts/platform.html"){
|
|||||||
:data="tableData"
|
:data="tableData"
|
||||||
:size="tableSize"
|
:size="tableSize"
|
||||||
border
|
border
|
||||||
@sort-change="pageOrder">
|
@sort-change="pageOrder"
|
||||||
|
ref="tourMainTable"
|
||||||
|
:height="tourTableHeight">
|
||||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||||
@@ -310,7 +312,7 @@ layout("/layouts/platform.html"){
|
|||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins, tourTableHeightMixins],
|
||||||
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
||||||
data() {
|
data() {
|
||||||
const currentYear = moment().format("YYYY")
|
const currentYear = moment().format("YYYY")
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
const subjectBank = {
|
const subjectBank = {
|
||||||
template: /*language=HTML*/ `
|
template: /*language=HTML*/ "\n <el-dialog :visible.sync=\"visible\" title=\"题库\" append-to-body width=\"700px\">\n\n </el-dialog>\n ",
|
||||||
<el-dialog :visible.sync="visible" title="题库" append-to-body width="700px">
|
|
||||||
|
|
||||||
</el-dialog>
|
|
||||||
`,
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -26,9 +22,7 @@ const subjectBank = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
style: /*language=CSS*/ `
|
style: /*language=CSS*/ "\n\n "
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2,6 +2,382 @@
|
|||||||
layout("/layouts/platform.html"){
|
layout("/layouts/platform.html"){
|
||||||
#-->
|
#-->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.qsv-logic-drawer .el-drawer__header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 18px 24px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
color: #303133;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-drawer {
|
||||||
|
top: 64px !important;
|
||||||
|
right: 0 !important;
|
||||||
|
bottom: auto !important;
|
||||||
|
width: calc(100% - 250px) !important;
|
||||||
|
height: calc(100vh - 64px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-drawer .el-drawer__body {
|
||||||
|
height: calc(100% - 58px);
|
||||||
|
background: #f0f2f5;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-workspace {
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(520px, 46%) minmax(0, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-section {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-section-header {
|
||||||
|
min-height: 58px;
|
||||||
|
padding: 0 20px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-section-header .el-button--success {
|
||||||
|
min-width: 88px;
|
||||||
|
height: 32px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-question-list,
|
||||||
|
.qsv-logic-preview-list {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-question-card {
|
||||||
|
position: relative;
|
||||||
|
min-height: 66px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 16px 20px 16px 54px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
cursor: grab;
|
||||||
|
transition: all .2s ease-in-out;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-question-card:hover,
|
||||||
|
.qsv-logic-question-card.active {
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 6px 16px rgba(64, 158, 255, .12);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-question-card:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-question-card-main {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-question-index {
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f0f2f5;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
color: #909399;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-question-type {
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
min-width: 70px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-question-type.single {
|
||||||
|
background: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-question-type.multiple {
|
||||||
|
background: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-question-type.text {
|
||||||
|
background: #e6a23c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-question-title {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #303133;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-question-title.has-visible-rule {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-question-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(.9);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: all .2s cubic-bezier(.175, .885, .32, 1.275);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-logic-question-card:hover .qsv-question-actions,
|
||||||
|
.qsv-logic-question-card.active .qsv-question-actions {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-question-actions .el-button-group .el-button {
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-preview-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-preview-question {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 8px 24px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all .2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-preview-question.active {
|
||||||
|
background: #f8fbff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-preview-index {
|
||||||
|
min-width: 22px;
|
||||||
|
line-height: 22px;
|
||||||
|
color: #409eff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: right;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-preview-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-preview-title {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
color: #4a5568;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-preview-options .el-radio,
|
||||||
|
.qsv-preview-options .el-checkbox {
|
||||||
|
margin: 0 32px 12px 0;
|
||||||
|
color: #3f4a5a;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-preview-options .el-radio__label,
|
||||||
|
.qsv-preview-options .el-checkbox__label {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-dialog .el-dialog__body {
|
||||||
|
padding: 18px 20px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-group {
|
||||||
|
background-color: #f9f9f5;
|
||||||
|
border: 1px solid #e8e8e0;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-group.is-root {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-root-title {
|
||||||
|
margin-bottom: -4px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-group-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px dashed #dcdcdc;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-group-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-children {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-item-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
transition: all .2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-item-card:hover {
|
||||||
|
border-color: #dcdfe6;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, .03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-item-index {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f0f2f5;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
color: #909399;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-item-content {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-nested-group-wrapper {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-nested-group-label {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-left: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-empty-tip {
|
||||||
|
padding: 28px;
|
||||||
|
text-align: center;
|
||||||
|
color: #909399;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px dashed #dcdfe6;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-condition-group-footer {
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px dashed #dcdcdc;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-add-group-text {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.qsv-logic-workspace {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<guava ref="guava">
|
<guava ref="guava">
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
@@ -10,32 +386,54 @@ layout("/layouts/platform.html"){
|
|||||||
<el-date-picker placeholder="请选择年度" style="width: 100%" type="year" v-model="pageForm.year"
|
<el-date-picker placeholder="请选择年度" style="width: 100%" type="year" v-model="pageForm.year"
|
||||||
value-format="yyyy"></el-date-picker>
|
value-format="yyyy"></el-date-picker>
|
||||||
</search-item>
|
</search-item>
|
||||||
<search-item label="标题">
|
<search-item label="标题名称">
|
||||||
<el-input placeholder="请输入标题" v-model="pageForm.title" clearable></el-input>
|
<el-input placeholder="请输入标题名称" v-model="pageForm.title" clearable></el-input>
|
||||||
</search-item>
|
</search-item>
|
||||||
</search>
|
</search>
|
||||||
|
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card shadow="never" class="mt10">
|
<el-card shadow="never" class="mt10">
|
||||||
<table-tool label="管理列表">
|
<table-tool label="标题列表">
|
||||||
<el-button @click="$refs.basicFormRef.onOpen()" size="small" type="primary" class="mr5">新增</el-button>
|
|
||||||
</table-tool>
|
</table-tool>
|
||||||
|
|
||||||
<el-table :data="tableData" ref="tableRef" @sort-change="pageOrder">
|
<el-table :data="tableData" ref="tableRef" @sort-change="pageOrder">
|
||||||
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
|
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
|
||||||
<el-table-column label="标题" prop="title" sortable></el-table-column>
|
<el-table-column label="标题名称" prop="title" sortable></el-table-column>
|
||||||
<el-table-column label="类型" prop="category" sortable width="200">
|
<el-table-column label="类型" prop="category" sortable width="200">
|
||||||
<template scope="{row}">
|
<template scope="{row}">
|
||||||
<dict-tag :options="dict.type.ACTIVITY_QSV_CATEGORY" :value="row.category"></dict-tag>
|
<el-tag size="mini" :type="getCategoryTagType(row.category)">
|
||||||
|
{{getCategoryText(row.category)}}
|
||||||
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="开始时间" prop="startTime" sortable></el-table-column>
|
<el-table-column label="开始时间" prop="startTime" sortable></el-table-column>
|
||||||
<el-table-column label="结束时间" prop="endTime" sortable></el-table-column>
|
<el-table-column label="结束时间" prop="endTime" sortable></el-table-column>
|
||||||
<el-table-column label="操作" fixed="right" width="450px">
|
<el-table-column label="状态" width="100">
|
||||||
|
<template scope="{row}">
|
||||||
|
<el-tag v-if="row.enabled !== false" type="success">开启</el-tag>
|
||||||
|
<el-tag v-else type="info">关闭</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" fixed="right" width="580px">
|
||||||
<template scope="{row}">
|
<template scope="{row}">
|
||||||
<el-button size="mini" type="primary" @click="openQrCode(row)">二维码</el-button>
|
<el-button size="mini" type="primary" @click="openQrCode(row)">二维码</el-button>
|
||||||
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
|
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
|
||||||
<el-button size="mini" type="primary" @click="openSubject(row.id)">题目设置</el-button>
|
<el-button size="mini" type="primary" @click="openSubject(row.id)">题目设置</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="row.category === 'SURVEY'"
|
||||||
|
size="mini"
|
||||||
|
type="primary"
|
||||||
|
@click="openLogic(row.id)">逻辑</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="row.enabled === false"
|
||||||
|
size="mini"
|
||||||
|
type="success"
|
||||||
|
@click="updateEnabled(row, true)">开启</el-button>
|
||||||
|
<el-button
|
||||||
|
v-else
|
||||||
|
size="mini"
|
||||||
|
type="warning"
|
||||||
|
@click="updateEnabled(row, false)">关闭</el-button>
|
||||||
<el-button size="mini" type="danger" @click="onDelete(row.id)">删除</el-button>
|
<el-button size="mini" type="danger" @click="onDelete(row.id)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -48,22 +446,22 @@ layout("/layouts/platform.html"){
|
|||||||
</template>
|
</template>
|
||||||
</guava>
|
</guava>
|
||||||
|
|
||||||
<basic-form ref="basicFormRef" @refresh="pageData"></basic-form>
|
<logic-form ref="logicFormRef" @refresh="pageData"></logic-form>
|
||||||
|
|
||||||
<qr-code-plus :url="signatureAddress" ref="qrCodePlusRef"></qr-code-plus>
|
<qr-code-plus :url="signatureAddress" ref="qrCodePlusRef"></qr-code-plus>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script nonce="${cspNonce!}">
|
<script nonce="${cspNonce!}">
|
||||||
<!--#include('basicForm.js'){}#-->
|
|
||||||
<!--#include('subjectForm.js'){}#-->
|
<!--#include('subjectForm.js'){}#-->
|
||||||
|
<!--#include('logicForm.js'){}#-->
|
||||||
new Vue({
|
new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
dicts: ["ACTIVITY_QSV_CATEGORY"],
|
dicts: ["ACTIVITY_QSV_CATEGORY"],
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins],
|
||||||
components: {
|
components: {
|
||||||
"basic-form": basicForm,
|
"subject-form": subjectForm,
|
||||||
"subject-form": subjectForm
|
"logic-form": logicForm
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -71,9 +469,24 @@ layout("/layouts/platform.html"){
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getCategoryText(category) {
|
||||||
|
const options = this.dict && this.dict.type ? (this.dict.type.ACTIVITY_QSV_CATEGORY || []) : []
|
||||||
|
const item = options.find(item => item.code === category)
|
||||||
|
return item ? item.name : category
|
||||||
|
},
|
||||||
|
|
||||||
|
getCategoryTagType(category) {
|
||||||
|
const map = {
|
||||||
|
SURVEY: "success",
|
||||||
|
QUIZ: "primary",
|
||||||
|
VOTE: "danger"
|
||||||
|
}
|
||||||
|
return map[category] || "info"
|
||||||
|
},
|
||||||
|
|
||||||
// 编辑
|
// 编辑
|
||||||
openEdit(row) {
|
openEdit(row) {
|
||||||
this.$refs.basicFormRef.onOpen(row.id)
|
location.href = "/platform/qsv/new?id=" + row.id
|
||||||
},
|
},
|
||||||
|
|
||||||
// 打开二维码
|
// 打开二维码
|
||||||
@@ -94,9 +507,34 @@ layout("/layouts/platform.html"){
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 调查类型独立维护题目之间的显隐逻辑
|
||||||
|
openLogic(id) {
|
||||||
|
this.$refs.logicFormRef.onOpen(id)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 开启或关闭活动,关闭后移动端首页不展示,直达手机端问卷也不能答题
|
||||||
|
updateEnabled(row, enabled) {
|
||||||
|
const actionText = enabled ? "开启" : "关闭"
|
||||||
|
this.$confirm("您确认" + actionText + "该活动吗?", "提示", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning"
|
||||||
|
}).then(() => {
|
||||||
|
$.post("/platform/qsv/activity/updateEnabled", {
|
||||||
|
id: row.id,
|
||||||
|
enabled
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$message.success(res.msg)
|
||||||
|
this.pageData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
onDelete(id) {
|
onDelete(id) {
|
||||||
this.$confirm("您确认删除吗, 是否继续?", "提示", {
|
this.$confirm("删除后将同步删除该任务的题目、选项、用户答题记录、统计分析和成绩数据,且不可恢复。确定删除吗?", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
type: "warning"
|
type: "warning"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,532 @@
|
|||||||
|
<!--#
|
||||||
|
layout("/layouts/platform.html"){
|
||||||
|
#-->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.qsv-new-page {
|
||||||
|
padding-bottom: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-new-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-new-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-new-card {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-new-sub-title {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-new-footer {
|
||||||
|
position: fixed;
|
||||||
|
left: 250px;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 900;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, .04);
|
||||||
|
text-align: right;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-cover-tip {
|
||||||
|
margin-top: 8px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-cover-tip span {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.qsv-new-footer {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-scope-select {
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="app" v-cloak>
|
||||||
|
<template>
|
||||||
|
<custom-card>
|
||||||
|
<h2 slot="header" >{{pageTitle}}</h2>
|
||||||
|
<template>
|
||||||
|
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="150px">
|
||||||
|
<el-form-item label="类型" prop="category">
|
||||||
|
<el-radio-group v-model="formData.category" size="small" @change="onCategoryChange">
|
||||||
|
<el-radio v-for="item in categoryOptions" :label="item.code"
|
||||||
|
:key="item.code"
|
||||||
|
border>
|
||||||
|
{{item.label}}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="标题" prop="title">
|
||||||
|
<el-input v-model="formData.title" placeholder="请输入标题" maxlength="50"
|
||||||
|
show-word-limit></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="说明" prop="description">
|
||||||
|
<text-editor v-model="formData.description" :height="150"></text-editor>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="开始时间" prop="startTime">
|
||||||
|
<el-date-picker v-model="formData.startTime" type="datetime" placeholder="选择日期时间"
|
||||||
|
style="width: 100%"
|
||||||
|
value-format="yyyy-MM-dd HH:mm:ss"></el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="结束时间" prop="endTime">
|
||||||
|
<el-date-picker v-model="formData.endTime" type="datetime" placeholder="选择日期时间"
|
||||||
|
style="width: 100%"
|
||||||
|
value-format="yyyy-MM-dd HH:mm:ss"></el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-form-item label="参加人员范围" prop="groupId">
|
||||||
|
<div style="display: flex;column-gap: 10px">
|
||||||
|
<el-select class="qsv-scope-select"
|
||||||
|
clearable filterable
|
||||||
|
placeholder="参加人员范围"
|
||||||
|
style="width: 640px; max-width: calc(100% - 82px);"
|
||||||
|
v-model="formData.groupId">
|
||||||
|
<el-option :key="item.groupId"
|
||||||
|
:label="item.groupName"
|
||||||
|
:value="item.groupId"
|
||||||
|
v-for="item in activityGroupOptions"></el-option>
|
||||||
|
</el-select>
|
||||||
|
<el-button
|
||||||
|
@click="$refs.drawerUserScope.userScopeDialog = true"
|
||||||
|
type="primary">设置
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<h3 class="qsv-new-sub-title" v-if="formData.category==='QUIZ'">题目信息</h3>
|
||||||
|
|
||||||
|
<template v-if="formData.category==='QUIZ'">
|
||||||
|
<el-form-item label="答题模式" prop="mode">
|
||||||
|
<el-radio-group v-model="formData.mode" size="small" @change="onModeChange">
|
||||||
|
<el-radio v-for="item in modeOptions" :label="item.code"
|
||||||
|
:key="item.code"
|
||||||
|
border>
|
||||||
|
{{item.label}}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="常规题目范围" prop="displayMode"
|
||||||
|
v-if="isRegularMode()">
|
||||||
|
<el-radio-group v-model="formData.displayMode" size="small" @change="normalizeQuizConfig">
|
||||||
|
<el-radio label="ALL" border>显示全部题目</el-radio>
|
||||||
|
<el-radio label="RANDOM" border>随机抽取题目</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="题目展示顺序打乱" prop="shuffleSubject">
|
||||||
|
<el-switch v-model="formData.shuffleSubject" inactive-text="否" active-text="是"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="每次随机题数" prop="randomCount"
|
||||||
|
v-if="isRegularMode() && isRandomDisplayMode()">
|
||||||
|
<el-input-number v-model="formData.randomCount" :min="1" :max="100"></el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="最多随机答题次数" prop="totalRandom"
|
||||||
|
v-if="isRegularMode() && isRandomDisplayMode()">
|
||||||
|
<el-input-number v-model="formData.totalRandom" :min="1" :max="100"></el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="允许重复答题" prop="repeatable"
|
||||||
|
v-if="canRepeatAnswer()">
|
||||||
|
<el-switch v-model="formData.repeatable" inactive-text="否" active-text="是"
|
||||||
|
@change="normalizeQuizConfig"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="最多答题次数" prop="maxAttempts"
|
||||||
|
v-if="canRepeatAnswer() && formData.repeatable">
|
||||||
|
<el-input-number v-model="formData.maxAttempts" :min="1" :max="100"></el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="时间限制(min)" prop="timeLimit">
|
||||||
|
<el-input-number v-model="formData.timeLimit" :min="0" :max="100"
|
||||||
|
:precision="0"
|
||||||
|
placeholder="0表示不限时"></el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="重复答题计分方式" prop="scoreMode"
|
||||||
|
v-if="canRepeatAnswer() && formData.repeatable">
|
||||||
|
<el-radio-group v-model="formData.scoreMode" size="small">
|
||||||
|
<el-radio v-for="item in dict.type.ACTIVITY_QSV_SCORE_MODE" :label="item.code"
|
||||||
|
:key="item.code" border>
|
||||||
|
{{item.label}}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-form-item label="封面" prop="cover">
|
||||||
|
<file-upload
|
||||||
|
:upload_number="1"
|
||||||
|
:upload_size="1024 * 1024"
|
||||||
|
:value.sync="formData.cover"
|
||||||
|
accept=".jpg,.jpeg,.png"
|
||||||
|
upload_mode="image"
|
||||||
|
upload_result_category="interval"
|
||||||
|
upload_result_type="url"
|
||||||
|
></file-upload>
|
||||||
|
<div class="qsv-cover-tip">
|
||||||
|
只能上传<span>1</span>个文件;只能上传<span>.jpg,.jpeg,.png</span>文件;单个文件大小不能超过<span>1.00</span>M;
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template slot="footer">
|
||||||
|
<el-button @click="goBack">取消</el-button>
|
||||||
|
<el-button :loading="saveLoading" @click="saveDraft">保存</el-button>
|
||||||
|
<el-button type="primary" :loading="saveLoading" @click="submitActivity">提交</el-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</custom-card>
|
||||||
|
</template>
|
||||||
|
<drawer-user-scope
|
||||||
|
@group_change="getActivityGroup"
|
||||||
|
ref="drawerUserScope"
|
||||||
|
:group_id.sync="formData.groupId"
|
||||||
|
></drawer-user-scope>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script nonce="${cspNonce!}">
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
dicts: ["ACTIVITY_QSV_CATEGORY", "ACTIVITY_QSV_MODE", "ACTIVITY_QSV_REPEAT_MODE", "ACTIVITY_QSV_SCORE_MODE"],
|
||||||
|
components: {
|
||||||
|
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue")
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
saveLoading: false,
|
||||||
|
activityId: "",
|
||||||
|
formData: {
|
||||||
|
category: "QUIZ",
|
||||||
|
cover: "",
|
||||||
|
enabled: false,
|
||||||
|
mode: "REGULAR",
|
||||||
|
shuffleSubject: false,
|
||||||
|
repeatable: false,
|
||||||
|
displayMode: "ALL",
|
||||||
|
timeLimit: 0
|
||||||
|
},
|
||||||
|
formRules: {
|
||||||
|
category: [{ required: true, message: "请选择类型", trigger: "change" }],
|
||||||
|
title: [{ required: true, message: "请输入标题", trigger: "change" }],
|
||||||
|
startTime: [{ required: true, message: "请选择开始时间", trigger: "change" }],
|
||||||
|
endTime: [{ required: true, message: "请选择结束时间", trigger: "change" }],
|
||||||
|
mode: [{ required: true, message: "请选择答题模式", trigger: "change" }],
|
||||||
|
scoreMode: [{ required: true, message: "请选择重复答题计分方式", trigger: "change" }]
|
||||||
|
},
|
||||||
|
activityGroupOptions: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getQueryString(name) {
|
||||||
|
const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)")
|
||||||
|
const result = window.location.search.substr(1).match(reg)
|
||||||
|
return result ? decodeURIComponent(result[2]) : ""
|
||||||
|
},
|
||||||
|
onCategoryChange(category) {
|
||||||
|
this.formData.category = this.normalizeCategoryValue(category)
|
||||||
|
if (this.formData.category === "QUIZ") {
|
||||||
|
this.normalizeQuizConfig()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onModeChange(mode) {
|
||||||
|
this.$set(this.formData, "mode", this.normalizeModeValue(mode))
|
||||||
|
this.normalizeQuizConfig()
|
||||||
|
},
|
||||||
|
getDefaultScoreMode() {
|
||||||
|
const scoreModes = this.dict && this.dict.type ? (this.dict.type.ACTIVITY_QSV_SCORE_MODE || []) : []
|
||||||
|
const highMode = scoreModes.find(item => item.code === "HIGH")
|
||||||
|
const lastMode = scoreModes.find(item => item.code === "LAST")
|
||||||
|
return (lastMode || highMode || scoreModes[0] || { code: "LAST" }).code
|
||||||
|
},
|
||||||
|
normalizeCategoryValue(category) {
|
||||||
|
const value = (category || "").toString().trim().toUpperCase()
|
||||||
|
if (value === "QUIZ" || value === "VOTE") {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return "SURVEY"
|
||||||
|
},
|
||||||
|
normalizeModeValue(mode) {
|
||||||
|
const value = (mode || "").toString().trim().toUpperCase()
|
||||||
|
if (value.indexOf("SCHEDULE") > -1 || value.indexOf("定时") > -1) {
|
||||||
|
return "SCHEDULED"
|
||||||
|
}
|
||||||
|
return "REGULAR"
|
||||||
|
},
|
||||||
|
normalizeModeOptionValue(item) {
|
||||||
|
const code = item && item.code ? item.code.toString().trim() : ""
|
||||||
|
if (code.toUpperCase().indexOf("SCHEDULE") > -1 || code.indexOf("定时") > -1) {
|
||||||
|
return "SCHEDULED"
|
||||||
|
}
|
||||||
|
if (code.toUpperCase().indexOf("REGULAR") > -1 || code.indexOf("常规") > -1) {
|
||||||
|
return "REGULAR"
|
||||||
|
}
|
||||||
|
return this.normalizeModeValue([item && item.name, item && item.label, item && item.text].join(" "))
|
||||||
|
},
|
||||||
|
getDefaultModeValue() {
|
||||||
|
const regularOption = this.modeOptions.find(item => this.normalizeModeOptionValue(item) === "REGULAR")
|
||||||
|
return regularOption ? regularOption.code : "REGULAR"
|
||||||
|
},
|
||||||
|
normalizeDisplayModeValue(displayMode) {
|
||||||
|
const value = (displayMode || "").toString().trim().toUpperCase()
|
||||||
|
if (value === "RANDOM") {
|
||||||
|
return "RANDOM"
|
||||||
|
}
|
||||||
|
return "ALL"
|
||||||
|
},
|
||||||
|
isRegularMode(data) {
|
||||||
|
const formData = data || this.formData
|
||||||
|
return this.normalizeModeValue(formData.mode) === "REGULAR"
|
||||||
|
},
|
||||||
|
isRandomDisplayMode(data) {
|
||||||
|
const formData = data || this.formData
|
||||||
|
return this.normalizeDisplayModeValue(formData.displayMode) === "RANDOM"
|
||||||
|
},
|
||||||
|
canRepeatAnswer(data) {
|
||||||
|
const formData = data || this.formData
|
||||||
|
const category = this.normalizeCategoryValue(formData.category)
|
||||||
|
const mode = this.normalizeModeValue(formData.mode)
|
||||||
|
const displayMode = this.normalizeDisplayModeValue(formData.displayMode)
|
||||||
|
return category === "QUIZ"
|
||||||
|
&& (mode === "SCHEDULED" || (mode === "REGULAR" && displayMode === "ALL"))
|
||||||
|
},
|
||||||
|
normalizeQuizConfig() {
|
||||||
|
this.formData.category = this.normalizeCategoryValue(this.formData.category)
|
||||||
|
if (this.formData.category !== "QUIZ") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$set(this.formData, "mode", this.formData.mode ? this.normalizeModeValue(this.formData.mode) : this.getDefaultModeValue())
|
||||||
|
this.$set(this.formData, "displayMode", this.normalizeDisplayModeValue(this.formData.displayMode))
|
||||||
|
this.$set(this.formData, "timeLimit", this.formData.timeLimit || 0)
|
||||||
|
if (!this.isRegularMode()) {
|
||||||
|
this.$set(this.formData, "displayMode", "ALL")
|
||||||
|
this.$set(this.formData, "randomCount", null)
|
||||||
|
this.$set(this.formData, "totalRandom", null)
|
||||||
|
} else if (!this.isRandomDisplayMode()) {
|
||||||
|
this.$set(this.formData, "randomCount", null)
|
||||||
|
this.$set(this.formData, "totalRandom", null)
|
||||||
|
} else {
|
||||||
|
this.$set(this.formData, "repeatable", false)
|
||||||
|
this.$set(this.formData, "randomCount", this.formData.randomCount || 1)
|
||||||
|
this.$set(this.formData, "totalRandom", this.formData.totalRandom || 1)
|
||||||
|
}
|
||||||
|
if (!this.canRepeatAnswer()) {
|
||||||
|
this.$set(this.formData, "repeatable", false)
|
||||||
|
this.$set(this.formData, "maxAttempts", 1)
|
||||||
|
this.$set(this.formData, "scoreMode", this.getDefaultScoreMode())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.formData.repeatable) {
|
||||||
|
this.$set(this.formData, "maxAttempts", this.formData.maxAttempts || 1)
|
||||||
|
this.$set(this.formData, "scoreMode", this.formData.scoreMode || this.getDefaultScoreMode())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$set(this.formData, "maxAttempts", 1)
|
||||||
|
this.$set(this.formData, "scoreMode", this.getDefaultScoreMode())
|
||||||
|
},
|
||||||
|
getActivityGroup() {
|
||||||
|
$.post("/platform/activity/basic/scope/getActivityUserScopeGroup").then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.activityGroupOptions = res.data || []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadActivity() {
|
||||||
|
if (!this.activityId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$.post("/platform/qsv/activity/findOne", { id: this.activityId }).then((res) => {
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
this.formData = Object.assign({}, this.formData, res.data)
|
||||||
|
this.normalizeQuizConfig()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
saveDraft() {
|
||||||
|
const enabled = this.activityId ? this.formData.enabled !== false : false
|
||||||
|
this.saveActivity(enabled, "保存")
|
||||||
|
},
|
||||||
|
submitActivity() {
|
||||||
|
this.saveActivity(true, "提交")
|
||||||
|
},
|
||||||
|
buildSubmitData(enabled) {
|
||||||
|
const formData = Object.assign({}, this.formData, { enabled })
|
||||||
|
formData.category = this.normalizeCategoryValue(formData.category)
|
||||||
|
formData.mode = this.normalizeModeValue(formData.mode)
|
||||||
|
if (formData.category !== "QUIZ") {
|
||||||
|
delete formData.repeatable
|
||||||
|
delete formData.repeatMode
|
||||||
|
delete formData.maxAttempts
|
||||||
|
delete formData.displayMode
|
||||||
|
delete formData.randomCount
|
||||||
|
delete formData.totalRandom
|
||||||
|
delete formData.shuffleSubject
|
||||||
|
delete formData.timeLimit
|
||||||
|
delete formData.scoreMode
|
||||||
|
} else {
|
||||||
|
formData.displayMode = this.normalizeDisplayModeValue(formData.displayMode)
|
||||||
|
formData.timeLimit = formData.timeLimit || 0
|
||||||
|
if (formData.mode !== "REGULAR") {
|
||||||
|
formData.displayMode = "ALL"
|
||||||
|
delete formData.randomCount
|
||||||
|
delete formData.totalRandom
|
||||||
|
} else if (formData.displayMode !== "RANDOM") {
|
||||||
|
delete formData.randomCount
|
||||||
|
delete formData.totalRandom
|
||||||
|
} else {
|
||||||
|
formData.repeatable = false
|
||||||
|
formData.maxAttempts = 1
|
||||||
|
formData.scoreMode = this.getDefaultScoreMode()
|
||||||
|
}
|
||||||
|
if (!this.canRepeatAnswer(formData) || !formData.repeatable) {
|
||||||
|
formData.repeatable = false
|
||||||
|
formData.maxAttempts = 1
|
||||||
|
formData.scoreMode = this.getDefaultScoreMode()
|
||||||
|
} else {
|
||||||
|
formData.maxAttempts = formData.maxAttempts || 1
|
||||||
|
formData.scoreMode = formData.scoreMode || this.getDefaultScoreMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return formData
|
||||||
|
},
|
||||||
|
saveActivity(enabled, actionText) {
|
||||||
|
this.$refs.formRef.validate((valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$confirm("确定" + actionText + "吗?", "提示", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning"
|
||||||
|
}).then(() => {
|
||||||
|
this.saveLoading = true
|
||||||
|
const formData = this.buildSubmitData(enabled)
|
||||||
|
$.post("/platform/qsv/activity/save", formData).then((res) => {
|
||||||
|
if (res.code === 0 && res.data && res.data.needConfirm) {
|
||||||
|
this.saveLoading = false
|
||||||
|
this.confirmModeChangeSave(formData, actionText, res.data.message || res.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ((res.code === 2 || res.code === 409) && res.data && res.data.needConfirm) {
|
||||||
|
this.saveLoading = false
|
||||||
|
this.confirmModeChangeSave(formData, actionText, res.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (res.code !== 0) {
|
||||||
|
this.saveLoading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$message.success(actionText + "成功")
|
||||||
|
location.href = "/platform/qsv/activity"
|
||||||
|
}).fail((xhr) => {
|
||||||
|
this.saveLoading = false
|
||||||
|
this.handleModeChangeConfirmResponse(xhr && xhr.responseJSON, formData, actionText)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleModeChangeConfirmResponse(res, formData, actionText) {
|
||||||
|
if (res && res.code === 0 && res.data && res.data.needConfirm) {
|
||||||
|
this.confirmModeChangeSave(formData, actionText, res.data.message || res.msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (res && (res.code === 2 || res.code === 409) && res.data && res.data.needConfirm) {
|
||||||
|
this.confirmModeChangeSave(formData, actionText, res.msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
confirmModeChangeSave(formData, actionText, message) {
|
||||||
|
this.$confirm(message || "当前问卷已有用户答题记录,是否确认保存?", "提示", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning"
|
||||||
|
}).then(() => {
|
||||||
|
this.saveLoading = true
|
||||||
|
const submitData = Object.assign({}, formData, {forceSave: true})
|
||||||
|
$.post("/platform/qsv/activity/save", submitData).then((res) => {
|
||||||
|
if (res.code !== 0) {
|
||||||
|
this.saveLoading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$message.success(actionText + "成功")
|
||||||
|
location.href = "/platform/qsv/activity"
|
||||||
|
}).fail(() => {
|
||||||
|
this.saveLoading = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
goBack() {
|
||||||
|
location.href = "/platform/qsv/activity"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.activityId = this.getQueryString("id")
|
||||||
|
this.getActivityGroup()
|
||||||
|
this.loadActivity()
|
||||||
|
if (!this.activityId) {
|
||||||
|
this.normalizeQuizConfig()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
categoryOptions() {
|
||||||
|
const categoryList = this.dict && this.dict.type ? (this.dict.type.ACTIVITY_QSV_CATEGORY || []) : []
|
||||||
|
return categoryList.map(item => Object.assign({}, item, {
|
||||||
|
code: this.normalizeCategoryValue(item.code)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
modeOptions() {
|
||||||
|
return [
|
||||||
|
{ code: "REGULAR", label: "常规模式" },
|
||||||
|
{ code: "SCHEDULED", label: "定时定题" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
pageTitle() {
|
||||||
|
return this.activityId ? "编辑任务" : "新建任务"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--#
|
||||||
|
}
|
||||||
|
#-->
|
||||||
@@ -1,25 +1,5 @@
|
|||||||
const optionImg = {
|
const optionImg = {
|
||||||
template: /*language=HTML*/ `
|
template: /*language=HTML*/ "\n <el-dialog :visible.sync=\"visible\" title=\"设置图片\" append-to-body width=\"700px\">\n <div style=\"background: #f7f8f9;text-align: center;border: solid 1px #d7d8d9;border-radius: 4px;\">\n <el-upload\n action=\"/platform/sys/file/uploadDynamicReturnUrl\"\n :show-file-list=\"false\"\n :on-success=\"handleSuccess\"\n :before-upload=\"beforeUpload\">\n <img v-if=\"img\" :src=\"img\" class=\"avatar\">\n <i v-else class=\"el-icon-plus avatar-uploader-icon\"></i>\n </el-upload>\n </div>\n <div style=\"padding: 10px 0\">\n 请上传图片\n </div>\n <div slot=\"footer\" class=\"dialog-footer\">\n <el-button @click=\"visible = false\">取 消</el-button>\n <el-button type=\"primary\" @click=\"onConfirm\">确 定</el-button>\n </div>\n </el-dialog>\n ",
|
||||||
<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>
|
|
||||||
<div slot="footer" class="dialog-footer">
|
|
||||||
<el-button @click="visible = false">取 消</el-button>
|
|
||||||
<el-button type="primary" @click="onConfirm">确 定</el-button>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
`,
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -52,32 +32,5 @@ const optionImg = {
|
|||||||
this.visible = false
|
this.visible = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: /*language=CSS*/ `
|
style: /*language=CSS*/ "\n .avatar-uploader .el-upload {\n border: 1px dashed #d9d9d9;\n border-radius: 6px;\n cursor: pointer;\n position: relative;\n overflow: hidden;\n }\n\n .avatar-uploader .el-upload:hover {\n border-color: #409EFF;\n }\n\n .avatar-uploader-icon {\n font-size: 28px;\n color: #8c939d;\n width: 178px;\n height: 178px;\n line-height: 178px;\n text-align: center;\n }\n\n .avatar {\n width: 178px;\n height: 178px;\n display: block;\n }\n "
|
||||||
.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;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,5 @@
|
|||||||
const setting = {
|
const setting = {
|
||||||
template:/*language=HTML*/`
|
template:/*language=HTML*/"\n <el-dialog :title=\"title\" :visible.sync=\"visible\" append-to-body width=\"50%\">\n <el-form :model=\"formData\" label-width=\"80px\">\n <el-form-item label=\"链接\" prop=\"link\">\n <el-input maxlength=\"255\" placeholder=\"\" v-model=\"formData.link\"></el-input>\n </el-form-item>\n <el-form-item label=\"详情\" prop=\"description\">\n <text-editor v-model=\"formData.description\"></text-editor>\n </el-form-item>\n <el-form-item label=\"补充填写\" prop=\"fillRequired\">\n <el-switch\n v-model=\"formData.fillRequired\"\n active-text=\"需要填写\"\n inactive-text=\"无需填写\">\n </el-switch>\n </el-form-item>\n <el-form-item label=\"填写提示\" prop=\"fillPlaceholder\" v-if=\"formData.fillRequired\">\n <el-input\n maxlength=\"255\"\n placeholder=\"例如:请填写原因\"\n v-model=\"formData.fillPlaceholder\">\n </el-input>\n </el-form-item>\n </el-form>\n <div slot=\"footer\" class=\"dialog-footer\">\n <el-button @click=\"visible = false\">取 消</el-button>\n <el-button type=\"primary\" @click=\"onConfirm\">确 定</el-button>\n </div>\n </el-dialog> \n ",
|
||||||
<el-dialog :title="title" :visible.sync="visible" append-to-body width="50%">
|
|
||||||
<el-form :model="formData" label-width="80px">
|
|
||||||
<el-form-item label="链接" prop="link">
|
|
||||||
<el-input maxlength="255" placeholder="" v-model="formData.link"></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="详情" prop="description">
|
|
||||||
<text-editor v-model="formData.description"></text-editor>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<div slot="footer" class="dialog-footer">
|
|
||||||
<el-button @click="visible = false">取 消</el-button>
|
|
||||||
<el-button type="primary" @click="onConfirm">确 定</el-button>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
`,
|
|
||||||
data(){
|
data(){
|
||||||
return{
|
return{
|
||||||
title: '设置',
|
title: '设置',
|
||||||
@@ -26,7 +11,11 @@ const setting = {
|
|||||||
methods:{
|
methods:{
|
||||||
onOpen(option,ext){
|
onOpen(option,ext){
|
||||||
console.log(option)
|
console.log(option)
|
||||||
this.formData = { ...option }
|
this.formData = {
|
||||||
|
...option,
|
||||||
|
fillRequired: !!option.fillRequired,
|
||||||
|
fillPlaceholder: option.fillPlaceholder || ""
|
||||||
|
}
|
||||||
this.ext = ext
|
this.ext = ext
|
||||||
this.visible = true
|
this.visible = true
|
||||||
},
|
},
|
||||||
|
|||||||
+293
-387
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,40 +1,12 @@
|
|||||||
const basicForm = {
|
const basicForm = {
|
||||||
template: /*language=HTML*/ `
|
template: /*language=HTML*/ "\n <el-drawer\n :title=\"formData.id ? '编辑题库' : '新增题库'\"\n :visible.sync=\"dialogVisible\"\n direction=\"rtl\"\n size=\"1024px\"\n custom-class=\"qsv-bank-basic-drawer\"\n append-to-body\n :wrapper-closable=\"false\"\n :close-on-press-escape=\"false\">\n <div class=\"qsv-bank-basic-drawer-page\" v-loading=\"saveLoading\">\n <el-card shadow=\"never\" class=\"qsv-bank-basic-drawer-card\">\n <h3 class=\"qsv-bank-sub-title\">基础信息</h3>\n <el-form :model=\"formData\" ref=\"formRef\" :rules=\"formRules\" label-width=\"96px\">\n <el-form-item label=\"类型\" prop=\"category\">\n <el-radio-group v-model=\"formData.category\" size=\"small\">\n <el-radio v-for=\"item in dict.type.ACTIVITY_QSV_CATEGORY\" :label=\"item.code\"\n :key=\"item.code\"\n border>\n {{item.label || item.name}}\n </el-radio>\n </el-radio-group>\n </el-form-item>\n\n <el-form-item label=\"标题\" prop=\"title\">\n <el-input v-model=\"formData.title\" placeholder=\"请输入标题\" maxlength=\"50\"\n show-word-limit></el-input>\n </el-form-item>\n\n <el-form-item label=\"说明\" prop=\"description\">\n <text-editor v-model=\"formData.description\" :height=\"180\"></text-editor>\n </el-form-item>\n </el-form>\n </el-card>\n </div>\n <div class=\"qsv-bank-basic-drawer-footer\">\n <el-button @click=\"dialogVisible = false\">取消</el-button>\n <el-button type=\"primary\" :loading=\"saveLoading\" @click=\"onSubmit\">保存</el-button>\n </div>\n </el-drawer>\n ",
|
||||||
<el-dialog :visible.sync="dialogVisible" title="基础设置" width="70%">
|
|
||||||
<div style="overflow-y: auto">
|
|
||||||
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="150px">
|
|
||||||
<el-form-item label="类型" prop="category">
|
|
||||||
<el-radio-group v-model="formData.category" size="small">
|
|
||||||
<el-radio v-for="item in dict.type.ACTIVITY_QSV_CATEGORY" :label="item.code"
|
|
||||||
:key="item.code"
|
|
||||||
border>
|
|
||||||
{{item.label}}
|
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="标题" prop="title">
|
|
||||||
<el-input v-model="formData.title" placeholder="请输入标题" maxlength="50"
|
|
||||||
show-word-limit></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="说明" prop="description">
|
|
||||||
<text-editor v-model="formData.description" :height="150"></text-editor>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
<div slot="footer" class="dialog-footer">
|
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
|
||||||
<el-button type="primary" @click="onSubmit">确 定</el-button>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
`,
|
|
||||||
dicts: ["ACTIVITY_QSV_CATEGORY", "ACTIVITY_QSV_MODE", "ACTIVITY_QSV_REPEAT_MODE", "ACTIVITY_QSV_SCORE_MODE"],
|
dicts: ["ACTIVITY_QSV_CATEGORY", "ACTIVITY_QSV_MODE", "ACTIVITY_QSV_REPEAT_MODE", "ACTIVITY_QSV_SCORE_MODE"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
|
saveLoading: false,
|
||||||
formData: {
|
formData: {
|
||||||
category: ''
|
category: 'QUIZ'
|
||||||
},
|
},
|
||||||
formRules: {
|
formRules: {
|
||||||
category: [{required: true, message: "请选择类型", trigger: "change"}],
|
category: [{required: true, message: "请选择类型", trigger: "change"}],
|
||||||
@@ -45,27 +17,42 @@ const basicForm = {
|
|||||||
methods: {
|
methods: {
|
||||||
onOpen(id) {
|
onOpen(id) {
|
||||||
this.dialogVisible = true
|
this.dialogVisible = true
|
||||||
|
this.saveLoading = false
|
||||||
if (id) {
|
if (id) {
|
||||||
$.post("/platform/qsv/bank/findOne", {id}).then((res) => {
|
$.post("/platform/qsv/bank/findOne", {id}).then((res) => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
this.formData = res.data
|
this.formData = res.data || {category: 'QUIZ'}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.formData = {}
|
this.formData = {
|
||||||
|
category: 'QUIZ'
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.formRef) {
|
||||||
|
this.$refs.formRef.clearValidate()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.$refs.formRef.validate((valid) => {
|
this.$refs.formRef.validate((valid) => {
|
||||||
if (valid) {
|
if (!valid) {
|
||||||
$.post("/platform/qsv/bank/" + (this.formData.id ? "update" : "save"), {qsvBank: JSON.stringify(this.formData)}).then((res) => {
|
return
|
||||||
if (res.code === 0) {
|
|
||||||
this.$message.success(res.msg)
|
|
||||||
this.dialogVisible = false
|
|
||||||
this.$emit("refresh", null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
this.saveLoading = true
|
||||||
|
$.post("/platform/qsv/bank/" + (this.formData.id ? "update" : "save"), {
|
||||||
|
qsvBank: JSON.stringify(this.formData)
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$message.success(res.msg || "保存成功")
|
||||||
|
this.dialogVisible = false
|
||||||
|
this.$emit("refresh", null)
|
||||||
|
}
|
||||||
|
this.saveLoading = false
|
||||||
|
}).fail(() => {
|
||||||
|
this.saveLoading = false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,30 +2,152 @@
|
|||||||
layout("/layouts/platform.html"){
|
layout("/layouts/platform.html"){
|
||||||
#-->
|
#-->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.el-drawer.qsv-bank-drawer {
|
||||||
|
top: 64px !important;
|
||||||
|
right: 0 !important;
|
||||||
|
bottom: auto !important;
|
||||||
|
width: calc(100% - 250px) !important;
|
||||||
|
height: calc(100vh - 64px) !important;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-drawer .el-drawer__header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 18px 24px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
color: #303133;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-drawer .el-drawer__body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: #f0f2f5;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-drawer-page {
|
||||||
|
height: 100%;
|
||||||
|
padding: 16px 24px 72px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-drawer-card {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-sub-title {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-drawer-footer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, .04);
|
||||||
|
text-align: right;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-drawer.qsv-bank-basic-drawer {
|
||||||
|
top: 64px !important;
|
||||||
|
right: 0 !important;
|
||||||
|
bottom: auto !important;
|
||||||
|
width: 1024px !important;
|
||||||
|
height: calc(100vh - 64px) !important;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-basic-drawer .el-drawer__header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 18px 24px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
color: #303133;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-basic-drawer .el-drawer__body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: #f0f2f5;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-basic-drawer-page {
|
||||||
|
height: 100%;
|
||||||
|
padding: 16px 16px 72px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-basic-drawer-card {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qsv-bank-basic-drawer-footer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, .04);
|
||||||
|
text-align: right;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.el-drawer.qsv-bank-drawer,
|
||||||
|
.el-drawer.qsv-bank-basic-drawer {
|
||||||
|
top: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<guava ref="guava">
|
<guava ref="guava">
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<search @search="doSearch">
|
<search @search="doSearch">
|
||||||
<search-item label="标题">
|
<search-item label="标题名称">
|
||||||
<el-input placeholder="请输入标题" v-model="pageForm.title" clearable></el-input>
|
<el-input placeholder="请输入标题名称" v-model="pageForm.title" clearable></el-input>
|
||||||
</search-item>
|
</search-item>
|
||||||
</search>
|
</search>
|
||||||
|
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card shadow="never" class="mt10">
|
<el-card shadow="never" class="mt10">
|
||||||
<table-tool label="管理列表">
|
<table-tool label="题库列表">
|
||||||
<el-button @click="$refs.basicFormRef.onOpen()" size="small" type="primary" class="mr5">新增</el-button>
|
<el-button @click="$refs.basicFormRef.onOpen()" size="small" type="primary" class="mr5">新增</el-button>
|
||||||
</table-tool>
|
</table-tool>
|
||||||
|
|
||||||
<el-table :data="tableData" ref="tableRef" @sort-change="pageOrder">
|
<el-table :data="tableData" ref="tableRef" @sort-change="pageOrder">
|
||||||
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
|
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
|
||||||
<el-table-column label="标题" prop="title" sortable></el-table-column>
|
<el-table-column label="标题名称" prop="title" sortable></el-table-column>
|
||||||
<el-table-column label="类型" prop="category" sortable width="200">
|
<el-table-column label="类型" prop="category" sortable width="200">
|
||||||
<template scope="{row}">
|
<template scope="{row}">
|
||||||
<dict-tag :options="dict.type.ACTIVITY_QSV_CATEGORY" :value="row.category"></dict-tag>
|
<el-tag size="mini" :type="getCategoryTagType(row.category)">
|
||||||
|
{{getCategoryText(row.category)}}
|
||||||
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" fixed="right" width="450px">
|
<el-table-column label="操作" width="450px">
|
||||||
<template scope="{row}">
|
<template scope="{row}">
|
||||||
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
|
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
|
||||||
<el-button size="mini" type="primary" @click="openSubject(row.id)">题目设置</el-button>
|
<el-button size="mini" type="primary" @click="openSubject(row.id)">题目设置</el-button>
|
||||||
@@ -61,6 +183,21 @@ layout("/layouts/platform.html"){
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getCategoryText(category) {
|
||||||
|
const options = this.dict && this.dict.type ? (this.dict.type.ACTIVITY_QSV_CATEGORY || []) : []
|
||||||
|
const item = options.find(item => item.code === category)
|
||||||
|
return item ? item.name : category
|
||||||
|
},
|
||||||
|
|
||||||
|
getCategoryTagType(category) {
|
||||||
|
const map = {
|
||||||
|
SURVEY: "success",
|
||||||
|
QUIZ: "primary",
|
||||||
|
VOTE: "danger"
|
||||||
|
}
|
||||||
|
return map[category] || "info"
|
||||||
|
},
|
||||||
|
|
||||||
// 编辑
|
// 编辑
|
||||||
openEdit(row) {
|
openEdit(row) {
|
||||||
this.$refs.basicFormRef.onOpen(row.id)
|
this.$refs.basicFormRef.onOpen(row.id)
|
||||||
@@ -80,7 +217,7 @@ layout("/layouts/platform.html"){
|
|||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
type: "warning"
|
type: "warning"
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
$.post("/platform/qsv/activity/delete", {id}).then((res) => {
|
$.post("/platform/qsv/bank/delete", {id}).then((res) => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
this.$message.success("删除成功")
|
this.$message.success("删除成功")
|
||||||
this.pageData()
|
this.pageData()
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user