Compare commits
31
Commits
29260423d5
...
fix_职工慰问
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cd4f52b30 | ||
|
|
365c812c6c | ||
|
|
12448bee0d | ||
|
|
81a1432702 | ||
|
|
a1fa323420 | ||
|
|
7b7f1f61f9 | ||
|
|
f30dbcaa21 | ||
|
|
42c5f4acd7 | ||
|
|
e70e07bf47 | ||
|
|
790e8e9f50 | ||
|
|
9590ad5f8c | ||
|
|
e60d4d6c68 | ||
|
|
53005c448f | ||
|
|
1a688d80d1 | ||
|
|
81fd5cb773 | ||
|
|
efad953710 | ||
|
|
ceec8eb095 | ||
|
|
e0004e410f | ||
|
|
f8457386fd | ||
|
|
8960d0e723 | ||
|
|
c1ac3db4bf | ||
|
|
6c6caa2558 | ||
|
|
16cae3ed2b | ||
|
|
35877f4b5c | ||
|
|
4f85bb08e2 | ||
|
|
001d456513 | ||
|
|
7dacfd7e5a | ||
|
|
65f340e3b0 | ||
|
|
2a305a3f23 | ||
|
|
fad8a37dfb | ||
|
|
9c17925b60 |
@@ -19,6 +19,7 @@ RoleConstant {
|
||||
SCHOOL_UNION_ADMIN("校工会管理员"),
|
||||
SCHOOL_UNION_CHAIRMAN("校工会主席"),
|
||||
SCHOOL_UNION_VICE_CHAIRMAN("校工会副主席"),
|
||||
SCHOOL_UNION_FINANCE("校工会财务"),
|
||||
SCHOOL_OFFICE_DIRECTOR("校工会办公室主任"),
|
||||
SCHOOL_UNION_WELFARE_ADMIN("校工会福利管理员"),
|
||||
SCHOOL_UNION_MEMBER_ADMIN("校工会会员管理员"),
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package com.budwk.app.base.utils;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import com.aspose.cells.FontConfigs;
|
||||
import com.aspose.cells.PdfSaveOptions;
|
||||
import com.aspose.cells.Workbook;
|
||||
import com.aspose.slides.FontsLoader;
|
||||
import com.aspose.slides.LoadOptions;
|
||||
import com.aspose.slides.Presentation;
|
||||
import com.aspose.words.Document;
|
||||
import com.aspose.words.FontSettings;
|
||||
import com.aspose.words.FontSourceBase;
|
||||
import com.aspose.words.MemoryFontSource;
|
||||
import com.aspose.words.SaveFormat;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -12,6 +18,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 文档转换
|
||||
@@ -19,6 +27,81 @@ import java.io.IOException;
|
||||
@Slf4j
|
||||
public class OfficePlusUtil {
|
||||
|
||||
private static final String DEFAULT_FONT_NAME = "Noto Sans SC";
|
||||
private static final String DEFAULT_FONT_RESOURCE = "/fonts/NotoSansSC-VF.ttf";
|
||||
private static final Object FONT_CONFIG_LOCK = new Object();
|
||||
private static volatile boolean fontConfigured;
|
||||
|
||||
/**
|
||||
* 注册系统内置字体,并将其作为 Office 文档缺失字体或缺失字形时的默认回退字体。
|
||||
* 原文档字体在服务器可用时仍优先使用,只有找不到原字体时才使用内置字体。
|
||||
*/
|
||||
private static void configureDefaultFont() {
|
||||
if (fontConfigured) {
|
||||
return;
|
||||
}
|
||||
synchronized (FONT_CONFIG_LOCK) {
|
||||
if (fontConfigured) {
|
||||
return;
|
||||
}
|
||||
byte[] fontData = loadDefaultFontData();
|
||||
configureWordFont(fontData);
|
||||
configureExcelFont(fontData);
|
||||
FontsLoader.loadExternalFont(fontData);
|
||||
fontConfigured = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 classpath 读取随应用打包的字体,避免依赖线上服务器安装字体。
|
||||
*
|
||||
* @return 默认字体的二进制数据
|
||||
*/
|
||||
private static byte[] loadDefaultFontData() {
|
||||
try (InputStream inputStream = OfficePlusUtil.class.getResourceAsStream(DEFAULT_FONT_RESOURCE)) {
|
||||
if (inputStream == null) {
|
||||
throw new BaseException("系统内置默认字体不存在");
|
||||
}
|
||||
return inputStream.readAllBytes();
|
||||
} catch (IOException e) {
|
||||
log.error("读取系统内置默认字体失败", e);
|
||||
throw new BaseException("读取系统内置默认字体失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 Word 转换保留已有字体源并追加内置字体,同时配置缺失字体和缺失字形回退。
|
||||
*
|
||||
* @param fontData 默认字体的二进制数据
|
||||
*/
|
||||
private static void configureWordFont(byte[] fontData) {
|
||||
FontSettings fontSettings = FontSettings.getDefaultInstance();
|
||||
FontSourceBase[] currentSources = fontSettings.getFontsSources();
|
||||
FontSourceBase[] fontSources = Arrays.copyOf(currentSources, currentSources.length + 1);
|
||||
fontSources[currentSources.length] = new MemoryFontSource(fontData);
|
||||
fontSettings.setFontsSources(fontSources);
|
||||
fontSettings.getSubstitutionSettings().getDefaultFontSubstitution().setDefaultFontName(DEFAULT_FONT_NAME);
|
||||
try {
|
||||
fontSettings.getFallbackSettings().loadNotoFallbackSettings();
|
||||
} catch (Exception e) {
|
||||
log.error("配置 Word 默认字体回退规则失败", e);
|
||||
throw new BaseException("配置 Word 默认字体失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 Excel 转换追加内置字体,并将其设置为找不到原字体时的默认字体。
|
||||
*
|
||||
* @param fontData 默认字体的二进制数据
|
||||
*/
|
||||
private static void configureExcelFont(byte[] fontData) {
|
||||
com.aspose.cells.FontSourceBase[] currentSources = FontConfigs.getFontSources();
|
||||
com.aspose.cells.FontSourceBase[] fontSources = Arrays.copyOf(currentSources, currentSources.length + 1);
|
||||
fontSources[currentSources.length] = new com.aspose.cells.MemoryFontSource(fontData);
|
||||
FontConfigs.setFontSources(fontSources);
|
||||
FontConfigs.setDefaultFontName(DEFAULT_FONT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换
|
||||
*
|
||||
@@ -47,7 +130,11 @@ public class OfficePlusUtil {
|
||||
public static void presentationConvertPdf(String sourcePath, String targetPath) {
|
||||
Presentation presentation = null;
|
||||
try {
|
||||
presentation = new Presentation(sourcePath);
|
||||
configureDefaultFont();
|
||||
LoadOptions loadOptions = new LoadOptions();
|
||||
loadOptions.setDefaultRegularFont(DEFAULT_FONT_NAME);
|
||||
loadOptions.setDefaultAsianFont(DEFAULT_FONT_NAME);
|
||||
presentation = new Presentation(sourcePath, loadOptions);
|
||||
presentation.save(targetPath, com.aspose.slides.SaveFormat.Pdf);
|
||||
} catch (Exception e) {
|
||||
log.info("转换pdf报错", e);
|
||||
@@ -68,10 +155,12 @@ public class OfficePlusUtil {
|
||||
public static void excelConvertPdf(String sourcePath, String targetPath) {
|
||||
FileOutputStream fileOS = null;
|
||||
try {
|
||||
configureDefaultFont();
|
||||
Workbook wb = new Workbook(sourcePath);
|
||||
fileOS = new FileOutputStream(targetPath);
|
||||
PdfSaveOptions pdfSaveOptions = new PdfSaveOptions();
|
||||
pdfSaveOptions.setOnePagePerSheet(true);
|
||||
pdfSaveOptions.setDefaultFont(DEFAULT_FONT_NAME);
|
||||
// for (int i = 0; i < 3; i++) {
|
||||
// wb.getWorksheets().get(i).getHorizontalPageBreaks().clear();
|
||||
// wb.getWorksheets().get(i).getVerticalPageBreaks().clear();
|
||||
@@ -105,6 +194,7 @@ public class OfficePlusUtil {
|
||||
public static void wordConvertPdf(String sourcePath, String targetPath) {
|
||||
FileOutputStream os = null;
|
||||
try {
|
||||
configureDefaultFont();
|
||||
File file = new File(targetPath);
|
||||
os = new FileOutputStream(file);
|
||||
Document doc = new Document(sourcePath);
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.sys.models.Sys_file;
|
||||
import com.budwk.app.sys.models.Sys_office_template;
|
||||
import com.budwk.app.sys.services.SysFileService;
|
||||
import com.budwk.app.sys.utils.SysFileMinIoUtil;
|
||||
import com.deepoove.poi.XWPFTemplate;
|
||||
import com.deepoove.poi.config.Configure;
|
||||
@@ -22,6 +23,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -39,6 +41,9 @@ public class SysOfficeTemplateUtil {
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@Inject
|
||||
private SysFileService sysFileService;
|
||||
|
||||
public String getTemplateFileName(String templateCode) {
|
||||
Sys_office_template officeTemplate = dao.fetch(Sys_office_template.class, Cnd.where(Sys_office_template::getTemplateCode, "=", templateCode));
|
||||
if (ObjectUtil.isNull(officeTemplate)) {
|
||||
@@ -70,7 +75,16 @@ public class SysOfficeTemplateUtil {
|
||||
if (ObjectUtil.isEmpty(sysFile)) {
|
||||
throw new BaseException("模板文件不存在");
|
||||
}
|
||||
byte[] fileBytes = SysFileMinIoUtil.getFileBytes(sysFile.getBucket(), sysFile.getStoragePath());
|
||||
byte[] fileBytes;
|
||||
try {
|
||||
fileBytes = sysFileService.download(id);
|
||||
} catch (IOException e) {
|
||||
log.error("模板文件读取失败,templateCode={},fileId={}", templateCode, id, e);
|
||||
throw new BaseException("模板 {} 的文件读取失败:{}", templateCode, e.getMessage());
|
||||
}
|
||||
if (fileBytes == null || fileBytes.length == 0) {
|
||||
throw new BaseException("模板 {} 的文件为空或读取失败,文件名:{}", templateCode, sysFile.getName());
|
||||
}
|
||||
return new ByteArrayInputStream(fileBytes);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@ public class FlowRecipientUnitSchoolLeaderHandler implements AssignmentHandler {
|
||||
throw new BaseException("未找到申请记录。");
|
||||
}
|
||||
unitId = condolence.getHelpUnitId();
|
||||
} else if ("TSWW".equals(processName)) {
|
||||
com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence info = dao.fetch(
|
||||
com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence.class, businessNo);
|
||||
if (info == null) throw new BaseException("未找到申请记录。");
|
||||
unitId = info.getHelpUnitId();
|
||||
} else {
|
||||
throw new BaseException("当前流程不支持获取受助人所在单位分管校领导。");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.budwk.app.flow.handler;
|
||||
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.flow.engine.AssignmentHandler;
|
||||
import com.budwk.app.flow.engine.core.Execution;
|
||||
import com.budwk.app.flow.engine.core.ServiceContext;
|
||||
import com.budwk.app.flow.engine.model.TaskModel;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 为流程任务分配拥有校工会财务角色的用户。
|
||||
*/
|
||||
public class FlowSchoolUnionFinanceHandler implements AssignmentHandler {
|
||||
|
||||
/**
|
||||
* 按校工会财务角色查询参与者,并去除重复的用户 ID。
|
||||
*
|
||||
* @param model 当前流程任务模型,无需额外传入人员参数
|
||||
* @param execution 当前流程执行上下文,无需额外传入业务参数
|
||||
* @return 用户 ID 列表;未配置角色或角色用户时返回空列表
|
||||
*/
|
||||
@Override
|
||||
public List<String> assign(TaskModel model, Execution execution) {
|
||||
Dao dao = ServiceContext.find(Dao.class);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
userRole.userId
|
||||
FROM
|
||||
`sys_user_role` userRole
|
||||
LEFT JOIN sys_role role ON role.id = userRole.roleId
|
||||
WHERE
|
||||
role.`code` = @roleCode
|
||||
GROUP BY
|
||||
userRole.userId
|
||||
""").setParam("roleCode", RoleConstant.SCHOOL_UNION_FINANCE.name());
|
||||
sql.setCallback(Sqls.callback.strList());
|
||||
dao.execute(sql);
|
||||
return sql.getList(String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回流程设计器中展示的参与者处理类名称。
|
||||
*
|
||||
* @return 流程设计器下拉选项名称
|
||||
*/
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "获取校工会财务";
|
||||
}
|
||||
}
|
||||
@@ -1,80 +1,27 @@
|
||||
package com.budwk.app.flow.listenter;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.flow.constant.FlowConst;
|
||||
import com.budwk.app.flow.engine.event.ProcessEvent;
|
||||
import com.budwk.app.flow.engine.event.ProcessEventListener;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.entity.ProcessTaskActor;
|
||||
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
|
||||
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService;
|
||||
import com.budwk.app.zhgh.staffbenefit.difficulthelp.service.DifficultHelpCommonService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Cnd;
|
||||
import com.budwk.app.flow.service.FlowTaskMessageService;
|
||||
import org.nutz.dao.Dao;
|
||||
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 java.util.List;
|
||||
|
||||
/**
|
||||
* 流程任务开始监听器
|
||||
*/
|
||||
/** 普通节点、首次会签及转办任务统一从开始事件进入待办消息 service。 */
|
||||
@IocBean
|
||||
@Slf4j
|
||||
public class ProcessTaskStartEventListener implements ProcessEventListener {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private GlobalMessageSendService globalMessageSendService;
|
||||
@Inject
|
||||
private DifficultHelpCommonService difficultHelpCommonService;
|
||||
private FlowTaskMessageService flowTaskMessageService;
|
||||
|
||||
@Override
|
||||
public void onEvent(ProcessEvent event) {
|
||||
if (event.getEventType() == ProcessEventTypeEnum.PROCESS_TASK_START) {
|
||||
Long sourceId = event.getSourceId();
|
||||
ProcessTask task = dao.fetch(ProcessTask.class, sourceId);
|
||||
if (task == null) return;
|
||||
sendMessage(task);
|
||||
// 节点及接收人判断集中在业务 service;通知失败不向流程提交接口抛出异常。
|
||||
try {
|
||||
difficultHelpCommonService.notifyOfficeApproval(task);
|
||||
} catch (Exception e) {
|
||||
log.error("困难帮扶企业微信待办通知异常,taskId={}", sourceId, e);
|
||||
}
|
||||
ProcessTask task = dao.fetch(ProcessTask.class, event.getSourceId());
|
||||
flowTaskMessageService.notifyTask(task, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sendMessage(ProcessTask task) {
|
||||
// 说明是流程开始 用户首次完成任务
|
||||
if (task.getTaskParentId() == 0 && task.getTaskName().equals("startTask")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 节点名称
|
||||
String taskDisplayName = task.getDisplayName();
|
||||
// 实例名称
|
||||
Long instanceId = task.getProcessInstanceId();
|
||||
ProcessInstance instance = dao.fetch(ProcessInstance.class, instanceId);
|
||||
String instanceName = Json.fromJson(NutMap.class, instance.getVariable()).getString(FlowConst.PROCESS_INSTANCE_NAME);
|
||||
|
||||
// 任务接收人
|
||||
List<ProcessTaskActor> taskActors = dao.query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", task.getId()));
|
||||
|
||||
for (ProcessTaskActor taskActor : taskActors) {
|
||||
// 模拟发送消息
|
||||
String message = StrUtil.format("您有一条待办任务,实例:{},处理环节:{}", taskActor.getActorName(), instanceName, taskDisplayName);
|
||||
log.info(message);
|
||||
globalMessageSendService.sendMessage(taskDisplayName, message, 2, List.of(taskActor.getActorId()), null);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.budwk.app.flow.service;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.flow.constant.FlowConst;
|
||||
import com.budwk.app.flow.entity.ProcessDefine;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.entity.ProcessTaskActor;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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 java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** WF 待办统一消息入口;站内消息、短信和企业微信沿用全局消息渠道。 */
|
||||
@IocBean
|
||||
@Slf4j
|
||||
public class FlowTaskMessageService {
|
||||
private static final String HOME = "/platform/home";
|
||||
private static final Set<String> TASK_PARAMS = Set.of("taskId", "bizId", "taskKey", "tab");
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private GlobalMessageSendService globalMessageSendService;
|
||||
|
||||
/**
|
||||
* task 为参与人已落库的 WF 任务;includeStartTask 仅驳回重建时传 true,
|
||||
* 使退回申请人的 startTask 也收到通知,首次提交的申请节点不通知本人。
|
||||
* 无返回值:仅通知进行中的任务,异常记录 taskId,不中断业务审核。
|
||||
* config 的 wfTaskId 标识流程通知,wfLoginNames 为办理人的 loginname,
|
||||
* wxWorkLink 为卡片地址;微信策略据此切换到官方企业微信接口。
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void notifyTask(ProcessTask task, boolean includeStartTask) {
|
||||
if (task == null || task.getId() == null || task.getProcessInstanceId() == null
|
||||
|| !ProcessTaskStateEnum.DOING.getCode().equals(task.getTaskState())) return;
|
||||
if (!includeStartTask && "startTask".equals(task.getTaskName())
|
||||
&& (task.getTaskParentId() == null || task.getTaskParentId() == 0L)) return;
|
||||
try {
|
||||
ProcessInstance instance = dao.fetch(ProcessInstance.class, task.getProcessInstanceId());
|
||||
if (instance == null) {
|
||||
log.warn("WF 待办通知跳过:实例不存在,taskId={}", task.getId());
|
||||
return;
|
||||
}
|
||||
ProcessDefine define = instance.getProcessDefineId() == null ? null
|
||||
: dao.fetch(ProcessDefine.class, instance.getProcessDefineId());
|
||||
List<ProcessTaskActor> actors = dao.query(ProcessTaskActor.class,
|
||||
Cnd.where(ProcessTaskActor::getProcessTaskId, "=", task.getId()));
|
||||
List<String> receiverIds = actors.stream().map(ProcessTaskActor::getActorId)
|
||||
.filter(StrUtil::isNotBlank).distinct().toList();
|
||||
if (receiverIds.isEmpty()) {
|
||||
log.warn("WF 待办通知跳过:任务没有实际办理人,taskId={}", task.getId());
|
||||
return;
|
||||
}
|
||||
// 只用实际参与人的账号,不按角色扩大范围,不以内部用户 ID 代替企业微信 userid。
|
||||
List<String> loginNames = actors.stream().filter(actor -> StrUtil.isNotBlank(actor.getActorId()))
|
||||
.map(ProcessTaskActor::getActorAccount).filter(StrUtil::isNotBlank)
|
||||
.map(String::trim).distinct().toList();
|
||||
if (actors.stream().anyMatch(actor -> StrUtil.isBlank(actor.getActorAccount()))) {
|
||||
log.warn("WF 待办存在未绑定 loginname 的办理人,taskId={}", task.getId());
|
||||
}
|
||||
String instanceName = define == null ? "流程审批" : StrUtil.blankToDefault(define.getDisplayName(), "流程审批");
|
||||
if (StrUtil.isNotBlank(instance.getVariable())) {
|
||||
try {
|
||||
instanceName = StrUtil.blankToDefault(JSONUtil.parseObj(instance.getVariable())
|
||||
.getStr(FlowConst.PROCESS_INSTANCE_NAME), instanceName);
|
||||
} catch (Exception e) {
|
||||
log.warn("WF 实例名称解析失败,使用流程定义名称,taskId={}", task.getId());
|
||||
}
|
||||
}
|
||||
String nodeName = StrUtil.blankToDefault(task.getDisplayName(), "待办审核");
|
||||
String content = "您有一条待办任务,实例:" + instanceName + ",处理环节:" + nodeName;
|
||||
JSONObject config = JSONUtil.createObj().set("wfTaskId", task.getId())
|
||||
.set("wfLoginNames", loginNames).set("wxWorkLink", buildLink(task, instance, define));
|
||||
// 系统发送身份兼容定时推进等没有当前登录会话的流程入口。
|
||||
globalMessageSendService.sendSystemMessage(nodeName, content, 2, receiverIds, config);
|
||||
} catch (Exception e) {
|
||||
log.error("WF 待办通知异常,taskId={}", task.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/** 优先节点手机表单;缺失或非法时回首页。保留原查询参数和锚点,替换过期任务参数。 */
|
||||
private String buildLink(ProcessTask task, ProcessInstance instance, ProcessDefine define) {
|
||||
String form = task.getH5FormKey();
|
||||
// KNBF 历史定义中的办公室地址过时,沿用已经验证的实际审核入口。
|
||||
if (define != null && "KNBF".equals(define.getName())
|
||||
&& "36ebafda-44c2-4bd3-bd7f-10839a575a0d".equals(task.getTaskName())) {
|
||||
form = "/platform/difficultHelp/branchUnionApproval/h5";
|
||||
}
|
||||
if (StrUtil.isBlank(form) || "null".equalsIgnoreCase(form.trim())
|
||||
|| StrUtil.isBlank(instance.getBusinessNo()) || StrUtil.isBlank(task.getTaskName())) return HOME;
|
||||
try {
|
||||
form = form.trim();
|
||||
URI uri = URI.create(form);
|
||||
if (uri.getUserInfo() != null || (uri.isAbsolute()
|
||||
? !("https".equalsIgnoreCase(uri.getScheme()) || "http".equalsIgnoreCase(uri.getScheme()))
|
||||
|| uri.getHost() == null
|
||||
: !form.startsWith("/") || uri.getRawAuthority() != null)) return HOME;
|
||||
List<String> params = new ArrayList<>();
|
||||
if (StrUtil.isNotBlank(uri.getRawQuery())) {
|
||||
for (String param : uri.getRawQuery().split("&")) {
|
||||
String key = URLDecoder.decode(param.split("=", 2)[0], StandardCharsets.UTF_8);
|
||||
if (!TASK_PARAMS.contains(key)) params.add(param);
|
||||
}
|
||||
}
|
||||
params.add("taskId=" + task.getId());
|
||||
params.add("bizId=" + URLEncoder.encode(instance.getBusinessNo(), StandardCharsets.UTF_8));
|
||||
params.add("taskKey=" + URLEncoder.encode(task.getTaskName(), StandardCharsets.UTF_8));
|
||||
params.add("tab=todo");
|
||||
String base = form.split("[?#]", 2)[0];
|
||||
return base + "?" + String.join("&", params)
|
||||
+ (uri.getRawFragment() == null ? "" : "#" + uri.getRawFragment());
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("WF 手机表单地址无效,卡片回退首页,taskId={}", task.getId());
|
||||
return HOME;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.entity.ProcessTaskActor;
|
||||
import com.budwk.app.flow.enums.*;
|
||||
import com.budwk.app.flow.service.FlowTaskMessageService;
|
||||
import com.budwk.app.flow.service.ProcessDefineService;
|
||||
import com.budwk.app.flow.service.ProcessInstanceService;
|
||||
import com.budwk.app.flow.service.ProcessTaskService;
|
||||
@@ -60,6 +61,9 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
||||
@Inject
|
||||
private SchoolOaTodoService schoolOaTodoService;
|
||||
|
||||
@Inject
|
||||
private FlowTaskMessageService flowTaskMessageService;
|
||||
|
||||
@Override
|
||||
public boolean save(ProcessTask param) {
|
||||
param.setId(null);
|
||||
@@ -292,6 +296,8 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
||||
addTaskActor(clone.getId(), CollectionUtil.newArrayList(actor));
|
||||
// 并行加签绕过普通节点创建入口,需要为新增的个人任务同步待办。
|
||||
schoolOaTodoService.createTodo(clone);
|
||||
// 并行加签不经过 CreateTaskHandler,只为本次新增任务通知一次。
|
||||
flowTaskMessageService.notifyTask(clone, false);
|
||||
});
|
||||
|
||||
}
|
||||
@@ -362,6 +368,8 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
||||
addTaskActor(task.getId(), CollectionUtil.newArrayList(task.getOperator()));
|
||||
// 驳回创建了新的任务 ID,按新任务推送,避免复用历史待办。
|
||||
schoolOaTodoService.createTodo(task);
|
||||
// 驳回重建不发布开始事件;退回申请节点时也通知申请人重新办理。
|
||||
flowTaskMessageService.notifyTask(task, true);
|
||||
return task;
|
||||
}
|
||||
|
||||
@@ -441,6 +449,12 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
||||
ProcessInstanceService processInstanceService = ServiceContext.find(ProcessInstanceService.class);
|
||||
// 更新会签变量到流程实例参数中
|
||||
processInstanceService.updateCountersignVariable(taskModel, execution, taskActors);
|
||||
// 串行会签后续办理人由 CountersignHandler 直接创建,不经过开始事件;首次会签仍由监听器通知。
|
||||
String counterPrefix = FlowConst.COUNTERSIGN_VARIABLE_PREFIX + taskModel.getName() + "_";
|
||||
if (CountersignTypeEnum.SEQUENTIAL.equals(taskModel.getCountersignType())
|
||||
&& execution.getArgs().get(counterPrefix + FlowConst.LOOP_COUNTER, -1) >= 0) {
|
||||
processTaskList.forEach(task -> flowTaskMessageService.notifyTask(task, false));
|
||||
}
|
||||
return processTaskList;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,10 +84,12 @@ public class SchoolOaTodoServiceImpl implements SchoolOaTodoService {
|
||||
task.getId(), task.getProcessInstanceId(), task.getTaskState());
|
||||
}
|
||||
|
||||
/** 系统参数优先于文件配置,只有明确为 true 才启用;未配置不访问数据库或 OA。 */
|
||||
/**
|
||||
* OA 待办接口联调尚未完成,暂时在代码中固定关闭,避免远端异常阻断内部 WF 流转。
|
||||
* 不受 AppSchoolOa 或 enabled 配置影响;恢复对接时需显式恢复配置开关逻辑。
|
||||
*/
|
||||
private boolean isEnabled() {
|
||||
Object enabled = Globals.MyConfig.get("AppSchoolOa");
|
||||
return Boolean.parseBoolean(enabled == null ? conf.get(PREFIX + "enabled", "false") : enabled.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
private void requireTask(ProcessTask task) {
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.GET;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
import org.nutz.mvc.annotation.POST;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.Valid;
|
||||
@@ -499,6 +500,24 @@ public class SysRoleController {
|
||||
return Result.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为全部 member=1 的用户设置会员角色,无需传入用户 ID、角色 ID 或列表筛选参数。
|
||||
*
|
||||
* @return JSON Result,data.addedCount 为新增关联数,data.enabledCount 为恢复启用关联数
|
||||
*/
|
||||
@At
|
||||
@POST
|
||||
@Ok("json")
|
||||
@SaCheckPermission("sys.manager.role.user")
|
||||
@SLog(tag = "一键设置会员角色", msg = "为全部member=1的人员补齐会员角色")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result doSettingMemberRole() {
|
||||
NutMap counts = sysRoleService.settingMemberRole();
|
||||
sysRoleService.clearCache();
|
||||
sysUserService.clearCache();
|
||||
return Result.success("会员角色设置完成", counts);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("json")
|
||||
@SaCheckPermission("sys.manager.role")
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.budwk.app.sys.models.Sys_role;
|
||||
import com.budwk.app.sys.models.Sys_unit;
|
||||
|
||||
import java.util.List;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
/**
|
||||
* Created by wizzer on 2016/12/22.
|
||||
@@ -128,6 +129,13 @@ public interface SysRoleService extends BaseService<Sys_role> {
|
||||
*/
|
||||
List<String> getRoleIdsByCode(List<RoleConstant> roleConstantList);
|
||||
|
||||
/**
|
||||
* 为用户表中 member=1 的全部人员补齐启用的 MEMBER 角色,不受列表筛选影响。
|
||||
*
|
||||
* @return addedCount 为新增关联数,enabledCount 为恢复启用的关联数;已有启用关联不重复添加
|
||||
*/
|
||||
NutMap settingMemberRole();
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.budwk.app.sys.services.impl;
|
||||
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.sys.services.SysHomeNewsService;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
@@ -35,7 +34,7 @@ public class SysHomeNewsServiceImpl implements SysHomeNewsService {
|
||||
/**
|
||||
* 无需参数,一次抓取首页中的三个栏目,并保留原有一天有效期的缓存写入。
|
||||
*
|
||||
* @return label/value 栏目列表,文章包含 text/time/href;正常空栏目返回空列表。
|
||||
* @return label/value 栏目列表,文章包含 text/time/href;无文章的栏目保留空列表,抓取失败返回空栏目列表。
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@@ -51,7 +50,8 @@ public class SysHomeNewsServiceImpl implements SysHomeNewsService {
|
||||
return news;
|
||||
} catch (IOException e) {
|
||||
log.warn("获取重庆工贸职业技术学院工会首页失败", e);
|
||||
throw new BaseException("学校工会网站暂时无法访问,请稍后重试");
|
||||
// 学校网站连接失败或超时时返回空列表,让首页展示空状态,不向用户报错,也不覆盖已有缓存。
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.plugins.wkcache.annotation.CacheDefaults;
|
||||
import org.nutz.plugins.wkcache.annotation.CacheRemoveAll;
|
||||
import org.nutz.plugins.wkcache.annotation.CacheResult;
|
||||
@@ -284,4 +285,44 @@ public class SysRoleServiceImpl extends BaseServiceImpl<Sys_role> implements Sys
|
||||
public void clearCache() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量补齐会员角色。仅处理 member=1 的用户,保留其他角色及账号禁用状态。
|
||||
*
|
||||
* @return addedCount 为新增关联数,enabledCount 为恢复启用的关联数
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public NutMap settingMemberRole() {
|
||||
// 锁定同一会员角色,使本接口的并发请求依次执行,避免重复插入用户角色关系。
|
||||
Sql roleSql = Sqls.create("SELECT * FROM sys_role WHERE code=@code FOR UPDATE");
|
||||
roleSql.setParam("code", RoleConstant.MEMBER.name());
|
||||
roleSql.setEntity(dao().getEntity(Sys_role.class));
|
||||
roleSql.setCallback(Sqls.callback.entities());
|
||||
dao().execute(roleSql);
|
||||
List<Sys_role> roles = roleSql.getList(Sys_role.class);
|
||||
if (roles.isEmpty()) {
|
||||
throw new BaseException("会员角色(MEMBER)不存在,请先配置会员角色");
|
||||
}
|
||||
Sys_role role = roles.get(0);
|
||||
if (role.isDisabled()) {
|
||||
throw new BaseException("会员角色(MEMBER)已禁用,请先启用会员角色");
|
||||
}
|
||||
|
||||
// 历史关联可能已停用或未填写 enable;恢复关联即可,无需重新插入。
|
||||
Sql enableSql = Sqls.create("UPDATE sys_user_role SET enable=1 WHERE roleId=@roleId "
|
||||
+ "AND (enable IS NULL OR enable<>1) "
|
||||
+ "AND EXISTS (SELECT 1 FROM sys_user u WHERE u.id=sys_user_role.userId AND u.member=1)");
|
||||
enableSql.setParam("roleId", role.getId());
|
||||
dao().execute(enableSql);
|
||||
|
||||
// 按用户、角色判重,只为缺少会员关联的人员添加记录;不删除任何已有角色。
|
||||
Sql insertSql = Sqls.create("INSERT INTO sys_user_role (userId,roleId,enable) "
|
||||
+ "SELECT u.id,@roleId,1 FROM sys_user u WHERE u.member=1 "
|
||||
+ "AND NOT EXISTS (SELECT 1 FROM sys_user_role ur WHERE ur.userId=u.id AND ur.roleId=@roleId)");
|
||||
insertSql.setParam("roleId", role.getId());
|
||||
dao().execute(insertSql);
|
||||
return NutMap.NEW().addv("addedCount", insertSql.getUpdateCount())
|
||||
.addv("enabledCount", enableSql.getUpdateCount());
|
||||
}
|
||||
}
|
||||
|
||||
+94
-21
@@ -4,12 +4,14 @@ package com.budwk.app.zhgh.activity.sports.service.impl;
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.base.utils.DateUtil;
|
||||
import com.budwk.app.sys.models.Sys_home_activity;
|
||||
import com.budwk.app.sys.models.Sys_home_template;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.activity.basic.models.ActivityEvent;
|
||||
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit;
|
||||
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
|
||||
import com.budwk.app.zhgh.activity.culture.models.ActivityTissue;
|
||||
@@ -28,6 +30,7 @@ import org.nutz.dao.entity.Record;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
|
||||
@@ -55,6 +58,7 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public String doAdd(ActivitySchool activitySchool, ActivitySchoolEvent[] events) {
|
||||
events = validateAndApplySingleEventLimits(events);
|
||||
ActivityBasicUnit activityBasicUnit = dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
|
||||
|
||||
if (activitySchool.getActivityLevel() == 40002) {
|
||||
@@ -101,6 +105,7 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void doEdit(ActivitySchool activitySchool, ActivitySchoolEvent[] events) {
|
||||
events = validateAndApplySingleEventLimits(events);
|
||||
ActivitySchool school = dao().fetch(ActivitySchool.class, activitySchool.getId());
|
||||
if (school.getActivityLevel() == 40002) {
|
||||
school.setBelongUnionId(SecurityUtil.getUnionId());
|
||||
@@ -161,6 +166,56 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
|
||||
dao().insertOrUpdate(sysHomeActivity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验单项人数限制,并在具体单项未设置限制时应用“所有单项”的默认值。
|
||||
*
|
||||
* @param events 页面提交的项目配置,允许包含 eventId 为 allItems 的默认配置
|
||||
* @return 已移除 allItems 临时配置、可直接持久化的项目配置数组
|
||||
*/
|
||||
private ActivitySchoolEvent[] validateAndApplySingleEventLimits(ActivitySchoolEvent[] events) {
|
||||
if (events == null || events.length == 0) {
|
||||
return new ActivitySchoolEvent[0];
|
||||
}
|
||||
|
||||
ActivitySchoolEvent allItems = Arrays.stream(events)
|
||||
.filter(event -> event != null && "allItems".equals(event.getEventId()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
List<ActivitySchoolEvent> result = new ArrayList<>();
|
||||
for (ActivitySchoolEvent event : events) {
|
||||
if (event == null || "allItems".equals(event.getEventId())) {
|
||||
continue;
|
||||
}
|
||||
ActivityEvent activityEvent = dao().fetch(ActivityEvent.class, event.getEventId());
|
||||
if (activityEvent != null && "1".equals(activityEvent.getProjectType())) {
|
||||
applySingleEventDefaultLimit(event, allItems);
|
||||
}
|
||||
result.add(event);
|
||||
}
|
||||
return result.toArray(new ActivitySchoolEvent[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单项的两个限制必须同时设置;均未设置或均为0时继承“所有单项”的对应值。
|
||||
*/
|
||||
private void applySingleEventDefaultLimit(ActivitySchoolEvent event, ActivitySchoolEvent allItems) {
|
||||
Integer athletesMaxNum = event.getAthletesMaxNum();
|
||||
Integer restrictRegNumber = event.getRestrictRegNumber();
|
||||
if ((athletesMaxNum == null) != (restrictRegNumber == null)) {
|
||||
throw new BaseException("每队(项)运动员最多人数和每人限报项目必须同时设置");
|
||||
}
|
||||
|
||||
boolean useDefault = athletesMaxNum == null || (athletesMaxNum == 0 && restrictRegNumber == 0);
|
||||
if (!useDefault) {
|
||||
return;
|
||||
}
|
||||
if (allItems == null || allItems.getAthletesMaxNum() == null || allItems.getRestrictRegNumber() == null) {
|
||||
throw new BaseException("请先设置所有单项的运动员最多人数和每人限报项目");
|
||||
}
|
||||
event.setAthletesMaxNum(allItems.getAthletesMaxNum());
|
||||
event.setRestrictRegNumber(allItems.getRestrictRegNumber());
|
||||
}
|
||||
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
private void addTeam(String schoolId, ActivitySchoolEvent[] events) {
|
||||
if (events.length > 0) {
|
||||
@@ -271,33 +326,19 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
|
||||
WHERE
|
||||
app.activityId = @id
|
||||
and app.status = 2
|
||||
$cnd
|
||||
and (@filterUnion = 0 or app.activityUnionId = @unionId)
|
||||
ORDER BY
|
||||
ev.allName DESC,
|
||||
u.unionname DESC,
|
||||
app.`team` asc
|
||||
""").setParam("id", id)
|
||||
.setVar("cnd", "");
|
||||
if (Strings.isNotBlank(unionId)) {
|
||||
sql.setVar("cnd", "and u.unionid = @unionId");
|
||||
sql.setParam("unionId", unionId);
|
||||
}
|
||||
""").setParam("id", id);
|
||||
// 参数必须声明在原始SQL中;setVar追加的字符串不会再次解析其中的@参数。
|
||||
// 未选择分工会时导出整个活动,选择时按报名记录中的活动分工会过滤。
|
||||
sql.setParam("filterUnion", Strings.isNotBlank(unionId) ? 1 : 0);
|
||||
sql.setParam("unionId", Strings.isNotBlank(unionId) ? unionId : "");
|
||||
List<Record> list = list(sql);
|
||||
String activityName;
|
||||
if (Lang.isNotEmpty(list)) {
|
||||
activityName = list.get(0).getString("activityName");
|
||||
} else {
|
||||
activityName = "暂无人员";
|
||||
}
|
||||
|
||||
List<ActivitySchoolApply> excels = new ArrayList<>();
|
||||
|
||||
list.forEach(z -> {
|
||||
ActivitySchoolApply schoolApply = z.toPojo(ActivitySchoolApply.class);
|
||||
String s = schoolApply.getIdentity().stream().map(personType::getName).toList().toString();
|
||||
schoolApply.setSf(s);
|
||||
excels.add(schoolApply);
|
||||
});
|
||||
list.forEach(record -> excels.add(toActivitySchoolApplyExport(record)));
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
@@ -305,10 +346,42 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
|
||||
CommonDownloadUtil.download(getUnionApplyExportFileName(unionId), workbook, response);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new BaseException("导出报名信息失败:" + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 将原生SQL查询结果转换为报名信息导出对象。
|
||||
* identity 是数据库JSON文本,需要显式转换为身份编码集合,避免 Record.toPojo 自动转换失败导致整表无数据。
|
||||
*
|
||||
* @param record 导出SQL返回的一行报名数据
|
||||
* @return EasyPoi 使用的 ActivitySchoolApply 导出对象
|
||||
*/
|
||||
private ActivitySchoolApply toActivitySchoolApplyExport(Record record) {
|
||||
ActivitySchoolApply schoolApply = new ActivitySchoolApply();
|
||||
schoolApply.setUnionname(record.getString("unionname"));
|
||||
schoolApply.setTeam(record.getString("team"));
|
||||
schoolApply.setAllName(record.getString("allName"));
|
||||
schoolApply.setLoginname(record.getString("loginname"));
|
||||
schoolApply.setUsername(record.getString("username"));
|
||||
schoolApply.setMobile(record.getString("mobile"));
|
||||
schoolApply.setIdCard(record.getString("idCard"));
|
||||
schoolApply.setProfessionalLevel(record.getString("professionalLevel"));
|
||||
schoolApply.setSex(record.getString("sex"));
|
||||
|
||||
String identityJson = record.getString("identity");
|
||||
List<String> identities = Strings.isBlank(identityJson)
|
||||
? new ArrayList<>()
|
||||
: Json.fromJsonAsList(String.class, identityJson);
|
||||
String identityName = identities.stream()
|
||||
.map(personType::getName)
|
||||
.filter(Strings::isNotBlank)
|
||||
.collect(Collectors.joining("、"));
|
||||
schoolApply.setSf(identityName);
|
||||
return schoolApply;
|
||||
}
|
||||
|
||||
private String getUnionApplyExportFileName(String unionId) {
|
||||
String unionName = "分工会";
|
||||
if (Strings.isNotBlank(unionId)) {
|
||||
|
||||
+13
-5
@@ -17,7 +17,6 @@ import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
||||
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
|
||||
import com.budwk.app.flow.service.FlowCommonService;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.club.model.ClubUser;
|
||||
import com.budwk.app.zhgh.club.model.SysClub;
|
||||
import com.budwk.app.zhgh.club.model.SysClubExamineRegister;
|
||||
import com.budwk.app.zhgh.club.service.SysClubExamineService;
|
||||
@@ -119,10 +118,7 @@ public class ClubExamineApplyController {
|
||||
if(StrUtil.isBlank(clubId)) {
|
||||
return Result.error("社团信息为空,请核查");
|
||||
}
|
||||
int count = dao.count(ClubUser.class, Cnd.where("clubId", "=", clubId));
|
||||
SysClub club = dao.fetch(SysClub.class, clubId);
|
||||
int money = count * (club.getDue() != null ? Integer.parseInt(club.getDue()) : 0);
|
||||
return Result.success(money);
|
||||
return Result.success(sysClubExamineService.getClubMemberMoney(clubId));
|
||||
}
|
||||
|
||||
@At
|
||||
@@ -175,6 +171,10 @@ public class ClubExamineApplyController {
|
||||
public Result save(@Param("data") SysClubExamineRegister examineRegister,
|
||||
@Param("incomeDetailed") String incomeDetailed,
|
||||
@Param("incomeCensus") String incomeCensus) {
|
||||
String validateMessage = sysClubExamineService.validateActivityRequired(examineRegister);
|
||||
if (validateMessage != null) {
|
||||
return Result.error(validateMessage);
|
||||
}
|
||||
if (StrUtil.isBlank(examineRegister.getId())) {
|
||||
sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus);
|
||||
} else {
|
||||
@@ -190,6 +190,10 @@ public class ClubExamineApplyController {
|
||||
public Result submit(@Param("data") SysClubExamineRegister examineRegister,
|
||||
@Param("incomeDetailed") String incomeDetailed,
|
||||
@Param("incomeCensus") String incomeCensus) {
|
||||
String validateMessage = sysClubExamineService.validateActivityRequired(examineRegister);
|
||||
if (validateMessage != null) {
|
||||
return Result.error(validateMessage);
|
||||
}
|
||||
SysClubExamineRegister reg;
|
||||
if (StrUtil.isBlank(examineRegister.getId())) {
|
||||
reg = sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus);
|
||||
@@ -221,6 +225,10 @@ public class ClubExamineApplyController {
|
||||
@Param("incomeDetailed") String incomeDetailed,
|
||||
@Param("incomeCensus") String incomeCensus,
|
||||
@Param("taskId") Long taskId) {
|
||||
String validateMessage = sysClubExamineService.validateActivityRequired(examineRegister);
|
||||
if (validateMessage != null) {
|
||||
return Result.error(validateMessage);
|
||||
}
|
||||
if (StrUtil.isBlank(examineRegister.getId())) {
|
||||
sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus);
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ public class ClubExamineMineController {
|
||||
//nutMap.setv("schoolName", Globals.MyConfig.getString("AppName"));
|
||||
nutMap.setv("clubName", register.getClubName());
|
||||
nutMap.setv("create_time", register.getFoundTime());
|
||||
nutMap.setv("dues_standard", register.getDue());
|
||||
nutMap.setv("dues_standard", register.getDue() == null ? "--" : register.getDue() + "元/年");
|
||||
// 生成材料中的年度与列表展示的登记年度保持一致。
|
||||
nutMap.setv("year", register.getYear());
|
||||
nutMap.setv("registerDate", register.getRegisterDate());
|
||||
|
||||
@@ -91,8 +91,8 @@ public class SysClub extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Comment("会费标准")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String due;
|
||||
@ColDefine(type = ColType.DOUBLE, width = 10, precision = 2)
|
||||
private Double due;
|
||||
|
||||
@Column
|
||||
@Comment("附件")
|
||||
|
||||
@@ -57,8 +57,8 @@ public class SysClubExamineRegister extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Comment("会费标准")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String due;
|
||||
@ColDefine(type = ColType.DOUBLE, width = 10, precision = 2)
|
||||
private Double due;
|
||||
|
||||
@Column
|
||||
@Comment("年度活动情况")
|
||||
|
||||
@@ -24,6 +24,21 @@ public interface SysClubExamineService extends BaseService<SysClubExamineRegiste
|
||||
|
||||
List<NutMap> getJgUser(@Valid String clubId);
|
||||
|
||||
/**
|
||||
* 计算指定社团的年度会费收入。
|
||||
*
|
||||
* @param clubId 社团ID,用于查询当前会员数量和社团会费标准
|
||||
* @return 会员数量乘以会费标准后的金额;社团或会费标准为空时返回0
|
||||
*/
|
||||
Double getClubMemberMoney(@Valid String clubId);
|
||||
|
||||
/**
|
||||
* 校验考核登记的两组活动明细。
|
||||
* @param examineRegister 页面data参数对应的登记实体,yearActivityList为本年度活动,plans为下一年度计划
|
||||
* @return String类型;通过返回null,否则返回缺失的明细行号及字段提示
|
||||
*/
|
||||
String validateActivityRequired(SysClubExamineRegister examineRegister);
|
||||
|
||||
SysClubExamineRegister doAdd(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus);
|
||||
|
||||
SysClubExamineRegister doEdit(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus);
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.club.model.ClubUser;
|
||||
import com.budwk.app.zhgh.club.model.SysClub;
|
||||
import com.budwk.app.zhgh.club.model.SysClubExamineRegister;
|
||||
import com.budwk.app.zhgh.club.model.SysClubExamineRegisterDetailed;
|
||||
@@ -50,6 +51,15 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getClubMemberMoney(String clubId) {
|
||||
int memberCount = dao().count(ClubUser.class, Cnd.where(ClubUser::getClubId, "=", clubId));
|
||||
SysClub club = dao().fetch(SysClub.class, clubId);
|
||||
// 社团或会费标准为空时按0计算,保证考核登记初始化能够正常完成。
|
||||
Double due = club == null || club.getDue() == null ? 0D : club.getDue();
|
||||
return memberCount * due;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NutMap> getClubUserNum(String clubId, Integer year) {
|
||||
Date yearStart = DateUtil.parse(year + "-01-01 00:00:00");
|
||||
@@ -345,6 +355,40 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
|
||||
return listMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 两组明细均至少填写一条;本年度主办单位和获奖情况保持选填。
|
||||
* @param examineRegister data参数绑定的登记实体,包含yearActivityList和plans两组JSON明细
|
||||
* @return 校验通过返回null,否则返回首个缺失字段的行号和名称
|
||||
*/
|
||||
@Override
|
||||
public String validateActivityRequired(SysClubExamineRegister examineRegister) {
|
||||
if (examineRegister == null) {
|
||||
return "考核登记信息不能为空";
|
||||
}
|
||||
String message = validateActivityRows(examineRegister.getYearActivityList(), "本年度活动开展情况",
|
||||
new String[]{"activityTime", "activityName", "activityAddress", "joinNum"},
|
||||
new String[]{"活动时间", "活动主题/赛事名称", "活动地点", "参加人数"});
|
||||
return message != null ? message : validateActivityRows(examineRegister.getPlans(), "下一年度活动计划",
|
||||
new String[]{"activityName", "activityUnit", "activityAddress"},
|
||||
new String[]{"活动主题/赛事名称", "活动/赛事主办单位", "活动地点"});
|
||||
}
|
||||
|
||||
/** 检查空列表、空行及纯空白字段;数值0按已填写处理,不扩大为人数范围校验。 */
|
||||
private String validateActivityRows(List<JSONObject> rows, String label, String[] fields, String[] names) {
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return label + "至少填写一条";
|
||||
}
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
JSONObject row = rows.get(i);
|
||||
for (int j = 0; j < fields.length; j++) {
|
||||
if (row == null || StrUtil.isBlank(row.getStr(fields[j]))) {
|
||||
return label + "第" + (i + 1) + "条:" + names[j] + "不能为空";
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysClubExamineRegister doAdd(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus) {
|
||||
// JSON转换为实体列表(空值判断避免转换异常)
|
||||
|
||||
+2
-1
@@ -193,7 +193,8 @@ public class GlobalMessageSendService {
|
||||
try {
|
||||
strategy.send(request.getTitle(), request.getContent(), request.getType(),
|
||||
request.getReceiverIds(), request.getConfig());
|
||||
log.info("异步发送消息成功,渠道:{}", strategy.getChannel().getValue());
|
||||
// 策略可能因开关关闭而跳过,实际发送结果由策略日志记录。
|
||||
log.info("异步消息策略执行完成,渠道:{}", strategy.getChannel().getValue());
|
||||
} catch (Exception e) {
|
||||
log.error("异步发送消息失败,渠道:{},错误:{}", strategy.getChannel().getValue(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
+72
@@ -1,16 +1,23 @@
|
||||
package com.budwk.app.zhgh.dayofficework.message.strategy.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.budwk.app.base.sms.SmsService;
|
||||
import com.budwk.app.base.wxwork.WxWorkMessageService;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.zhgh.dayofficework.message.enums.GlobalMessageChannel;
|
||||
import com.budwk.app.zhgh.dayofficework.message.strategy.GlobalMessageSendStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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 java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -24,6 +31,8 @@ public class WechatMessageSendStrategy implements GlobalMessageSendStrategy {
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private SmsService smsService;
|
||||
@Inject
|
||||
private WxWorkMessageService wxWorkMessageService;
|
||||
|
||||
@Override
|
||||
public GlobalMessageChannel getChannel() {
|
||||
@@ -36,7 +45,13 @@ public class WechatMessageSendStrategy implements GlobalMessageSendStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void send(String title, String content, Integer type, List<String> receiverIds, JSONObject config) {
|
||||
// WF 的接收账号已在任务事务内取得,异步线程不再反查尚未提交的任务数据。
|
||||
if (config != null && config.getLong("wfTaskId") != null) {
|
||||
sendFlowCard(title, content, config);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log.info("开始发送微信消息,标题:{},接收人数量:{}", title, receiverIds.size());
|
||||
|
||||
@@ -52,7 +67,12 @@ public class WechatMessageSendStrategy implements GlobalMessageSendStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void sendByLoginName(String title, String content, Integer type, List<String> receiverLoginNames, JSONObject config) {
|
||||
if (config != null && config.getLong("wfTaskId") != null) {
|
||||
sendFlowCard(title, content, config);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log.info("开始发送微信消息(按登录名),标题:{},接收人数量:{}", title, receiverLoginNames.size());
|
||||
|
||||
@@ -67,6 +87,58 @@ public class WechatMessageSendStrategy implements GlobalMessageSendStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WF 专用分支:wfTaskId 为任务ID,wfLoginNames 为实际办理人的 loginname 列表,
|
||||
* wxWorkLink 为非空手机入口。无返回值;关闭开关或无账号时跳过,失败抛给全局异步日志处理,
|
||||
* 不回退旧微信通道,避免双发。非流程消息继续沿用原有通讯平台。
|
||||
*/
|
||||
private void sendFlowCard(String title, String content, JSONObject config) {
|
||||
Long taskId = config.getLong("wfTaskId");
|
||||
List<String> loginNames = config.getJSONArray("wfLoginNames") == null ? List.of()
|
||||
: config.getJSONArray("wfLoginNames").toList(String.class);
|
||||
if (loginNames.isEmpty()) {
|
||||
log.warn("WF 企业微信卡片跳过:办理人 loginname 为空,taskId={}", taskId);
|
||||
return;
|
||||
}
|
||||
String link = StrUtil.blankToDefault(config.getStr("wxWorkLink"), "/platform/home");
|
||||
NutMap result;
|
||||
try {
|
||||
result = wxWorkMessageService.massSend(loginNames, cardText(title, 128, false),
|
||||
cardText(content, 512, true), link);
|
||||
} catch (RuntimeException e) {
|
||||
// 异步线程已脱离任务调用栈,保留任务 ID 便于从错误日志定位具体待办。
|
||||
throw new IllegalStateException("WF 企业微信卡片接口异常,taskId=" + taskId, e);
|
||||
}
|
||||
if (result != null && result.getBoolean("skipped", false)) {
|
||||
log.info("WF 企业微信卡片跳过:消息开关关闭,taskId={}", taskId);
|
||||
} else if (result != null && result.getBoolean("success", false)) {
|
||||
log.info("WF 企业微信卡片发送成功,taskId={},接收账号数={}", taskId, loginNames.size());
|
||||
} else {
|
||||
log.error("WF 企业微信卡片发送失败或部分失败,taskId={},result={}", taskId, result);
|
||||
throw new IllegalStateException("WF 企业微信卡片发送失败或部分失败,taskId=" + taskId);
|
||||
}
|
||||
}
|
||||
|
||||
/** 卡片有 UTF-8 字节上限;按 Unicode 码点缩短摘要,并计入正文 HTML 转义后的长度。 */
|
||||
private String cardText(String value, int limit, boolean description) {
|
||||
String text = HtmlUtil.unescape(HtmlUtil.cleanHtmlTag(StrUtil.blankToDefault(value, "流程待办")))
|
||||
.replace('\r', ' ').replace('\n', ' ');
|
||||
if (text.isBlank()) text = "流程待办";
|
||||
StringBuilder result = new StringBuilder();
|
||||
int bytes = 0;
|
||||
for (int offset = 0; offset < text.length();) {
|
||||
int codePoint = text.codePointAt(offset);
|
||||
String character = new String(Character.toChars(codePoint));
|
||||
int size = (description ? HtmlUtil.escape(character) : character).getBytes(StandardCharsets.UTF_8).length;
|
||||
// 留出省略号的三个 UTF-8 字节,避免长流程名称使整条通知发送失败。
|
||||
if (bytes + size > limit - 3) return result.append("…").toString();
|
||||
result.append(character);
|
||||
bytes += size;
|
||||
offset += Character.charCount(codePoint);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送微信文本消息。
|
||||
* 接收人来自全局消息服务传入的用户ID或登录名,方法内部转换为 Sys_user 后取工号发送;
|
||||
|
||||
+3
@@ -606,6 +606,9 @@ public class TourMySignupController {
|
||||
if (StrUtil.isBlank(ledger.getMobile())) {
|
||||
return Result.error("手机号不能为空");
|
||||
}
|
||||
if (!ledger.getMobile().matches("^1[3-9]\\d{9}$")) {
|
||||
return Result.error("请输入正确的11位手机号");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -841,6 +841,9 @@ public class TourSignupController {
|
||||
if (StrUtil.isBlank(ledger.getMobile())) {
|
||||
return Result.error("手机号不能为空");
|
||||
}
|
||||
if (!ledger.getMobile().matches("^1[3-9]\\d{9}$")) {
|
||||
return Result.error("请输入正确的11位手机号");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -86,7 +86,8 @@ public class GrassrootsCongressMaterialMineController {
|
||||
cnd.and("info.createdBy", "=", SecurityUtil.getUserId());
|
||||
}
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
cnd.and(Cnd.likeEX("info.meetingName", pageForm.getSearchKeyword()));
|
||||
// searchKeyword 为资料名称关键词,按资料表的 materialsName 字段模糊匹配。
|
||||
cnd.and(Cnd.likeEX("info.materialsName", pageForm.getSearchKeyword()));
|
||||
}
|
||||
cnd.andEX("YEAR(info.createdTime)", "=", pageForm.getYear());
|
||||
cnd.desc("info.createdTime");
|
||||
|
||||
+7
-3
@@ -229,14 +229,18 @@ public class ProposalMineController {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除当前用户可删除的提案。
|
||||
*
|
||||
* @param id 提案 ID;仅允许删除本人未发起流程或退回发起节点的提案
|
||||
* @return Result,成功时返回成功结果,不满足删除条件时返回原因
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("proposal.mine")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SLog(tag = "提案管理系统-我的提案", msg = "删除提案")
|
||||
public Result delete(@Valid String id) {
|
||||
dao.delete(ProposalInfo.class, id);
|
||||
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id);
|
||||
return Result.success();
|
||||
return proposalCommonService.deleteMine(id, SecurityUtil.getUserId());
|
||||
}
|
||||
|
||||
@At
|
||||
|
||||
+10
@@ -3,6 +3,7 @@ package com.budwk.app.zhgh.democratic.proposal.service.common;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
|
||||
@@ -19,6 +20,15 @@ import java.util.Set;
|
||||
|
||||
public interface ProposalCommonService extends BaseService<ProposalInfo> {
|
||||
|
||||
/**
|
||||
* 删除当前用户尚未发起流程或已退回发起节点的提案。
|
||||
*
|
||||
* @param id 待删除的提案 ID
|
||||
* @param userId 当前登录用户 ID,用于核对提案创建人
|
||||
* @return Result,删除成功返回 success;提案不存在、非本人或流程不允许删除时返回错误信息
|
||||
*/
|
||||
Result deleteMine(String id, String userId);
|
||||
|
||||
/**
|
||||
* 按页面允许的字段白名单添加排序条件,避免前端字段直接进入SQL。
|
||||
*
|
||||
|
||||
+42
@@ -17,6 +17,7 @@ import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
@@ -52,6 +53,7 @@ import com.deepoove.poi.config.Configure;
|
||||
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ddr.poi.html.HtmlRenderPolicy;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.FieldFilter;
|
||||
@@ -62,6 +64,7 @@ import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
@@ -184,6 +187,45 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
||||
super(dao);
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅允许创建人删除草稿或退回发起节点的提案,并清理其 WF 流程。
|
||||
*
|
||||
* @param id 待删除的提案 ID
|
||||
* @param userId 当前登录用户 ID,用于核对提案创建人
|
||||
* @return Result,删除成功返回 success;提案不存在、非本人或流程不允许删除时返回错误信息
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result deleteMine(String id, String userId) {
|
||||
if (StrUtil.isBlank(id) || StrUtil.isBlank(userId)) {
|
||||
return Result.error("提案不存在或无权删除");
|
||||
}
|
||||
ProposalInfo proposal = dao().fetch(ProposalInfo.class, id);
|
||||
if (proposal == null || !Objects.equals(proposal.getCreatedBy(), userId)) {
|
||||
return Result.error("提案不存在或无权删除");
|
||||
}
|
||||
|
||||
List<ProcessInstance> instances = dao().query(ProcessInstance.class,
|
||||
Cnd.where(ProcessInstance::getBusinessNo, "=", id));
|
||||
// 一个提案出现多个流程实例时无法安全判断当前节点,拒绝删除并保留数据。
|
||||
if (instances.size() > 1) {
|
||||
return Result.error("提案流程数据异常,无法删除");
|
||||
}
|
||||
if (!instances.isEmpty()) {
|
||||
ProcessInstance instance = instances.get(0);
|
||||
List<ProcessTask> tasks = flowEngine.processTaskService().getDoingTaskList(instance.getId(), null);
|
||||
// 存在并行任务时,只有所有活动任务均位于发起节点才允许删除。
|
||||
if (!Objects.equals(instance.getState(), ProcessInstanceStateEnum.DOING.getCode())
|
||||
|| tasks.isEmpty() || tasks.stream().anyMatch(task -> !Objects.equals(task.getTaskName(), "startTask"))) {
|
||||
return Result.error("仅未提交或退回发起节点的提案可以删除");
|
||||
}
|
||||
}
|
||||
|
||||
dao().delete(ProposalInfo.class, id);
|
||||
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询提案综合列表。
|
||||
* 普通查询不关联历史任务表;仅在 origins 包含 answer 且 commonKeyword 非空时,
|
||||
|
||||
+25
-12
@@ -6,14 +6,13 @@ import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
||||
import com.budwk.app.sys.param.SysMsgSummaryPageForm;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
@@ -94,10 +93,12 @@ public class CondolenceReadController {
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
cnd.andEX("info.type", "=", type);
|
||||
cnd.andEX("YEAR(info.createTime)", "=", year);
|
||||
cnd.andEX("info.applyUnionId", "=", unionId);
|
||||
cnd.andEX("info.applyUnitId", "=", unitId);
|
||||
applyReadScope(cnd, "info");
|
||||
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
@@ -106,9 +107,6 @@ public class CondolenceReadController {
|
||||
cnd.and(seg);
|
||||
}
|
||||
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CHAIRMAN.name())) {
|
||||
cnd.and("info.applyUnionId", "=", SecurityUtil.getUnionId());
|
||||
}
|
||||
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
cnd.desc("info.createTime");
|
||||
@@ -142,20 +140,18 @@ public class CondolenceReadController {
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and(ProcessInstance::getState, "=", ProcessTaskStateEnum.FINISHED.getCode());
|
||||
cnd.and("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
cnd.andEX("con.type", "=", type);
|
||||
cnd.andEX("YEAR(info.createTime)", "=", year);
|
||||
cnd.andEX("YEAR(con.createTime)", "=", year);
|
||||
cnd.andEX("con.applyUnionId", "=", unionId);
|
||||
cnd.andEX("con.applyUnitId", "=", unitId);
|
||||
applyReadScope(cnd, "con");
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or("info.helpUserName", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or("info.helpLoginName", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or("con.helpUserName", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or("con.helpLoginName", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CHAIRMAN.name())) {
|
||||
cnd.and("con.applyUnionId", "=", SecurityUtil.getUnionId());
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = condolenceService.listMap(sql);
|
||||
|
||||
@@ -174,4 +170,21 @@ public class CondolenceReadController {
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, list);
|
||||
CommonDownloadUtil.download("申报人汇总.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
private void applyReadScope(Cnd cnd, String alias) {
|
||||
if (AuthUtil.hasPermission("condolence_all_school")) {
|
||||
return;
|
||||
}
|
||||
if (AuthUtil.hasPermission("condolence_branch_union")) {
|
||||
String unionId = SecurityUtil.getUnionId();
|
||||
if (StrUtil.isNotBlank(unionId)) {
|
||||
cnd.and(alias + ".applyUnionId", "=", unionId);
|
||||
return;
|
||||
}
|
||||
} else if (AuthUtil.hasPermission("condolence_self")) {
|
||||
cnd.and(alias + ".createdBy", "=", SecurityUtil.getUserId());
|
||||
return;
|
||||
}
|
||||
cnd.and("1", "=", 0);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-7
@@ -1,10 +1,10 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.difficulthelp.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.flow.constant.FlowConst;
|
||||
@@ -112,7 +112,7 @@ public class DifficultHelpApplyController {
|
||||
@SaCheckPermission("member.apply.submit")
|
||||
@SLog(tag = "困难补助申请", msg = "保存申请,填写人: ${args[0].proxyUserName}")
|
||||
public Result save(@Param("data") DifficultHelpInfo difficultHelpInfo) {
|
||||
difficultHelpCommonService.validateApplyUser(difficultHelpInfo);
|
||||
difficultHelpCommonService.validateApplyUser(difficultHelpInfo, false);
|
||||
difficultHelpInfo.setApplyTime(ObjectUtil.defaultIfNull(difficultHelpInfo.getApplyTime(), new Date()));
|
||||
difficultHelpCommonService.refreshApplyCount(difficultHelpInfo);
|
||||
if (difficultHelpCommonService.hasAppliedInYear(difficultHelpInfo.getUserId(), difficultHelpInfo.getApplyTime(), difficultHelpInfo.getId())) {
|
||||
@@ -177,12 +177,13 @@ public class DifficultHelpApplyController {
|
||||
|
||||
@At
|
||||
@ApiOperation("获取受助人")
|
||||
@SaCheckPermission("difficultHelp.apply")
|
||||
@SaCheckPermission(value = {"difficultHelp.apply", "h5.difficultHelp.apply"}, mode = SaMode.OR)
|
||||
public Result queryRecipients(String key){
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
|
||||
return Result.error("只有分工会主席可以替他人申请");
|
||||
boolean schoolScope = AuthUtil.hasPermission("difficult_assistance_school");
|
||||
if (!schoolScope && !AuthUtil.hasPermission("difficult_assistance_union")) {
|
||||
return Result.error("没有代申请权限");
|
||||
}
|
||||
if (Strings.isBlank(SecurityUtil.getUnionId())) {
|
||||
if (!schoolScope && Strings.isBlank(SecurityUtil.getUnionId())) {
|
||||
return Result.error("当前用户未设置所属分工会");
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
@@ -193,6 +194,10 @@ public class DifficultHelpApplyController {
|
||||
birthday,
|
||||
mobile,
|
||||
sex,
|
||||
idCard,
|
||||
position,
|
||||
professionalTitle AS officialCapacity,
|
||||
(SELECT u.homeAddress FROM sys_user u WHERE u.id = vw_user.id) AS homeAddress,
|
||||
unitName,
|
||||
unionName,
|
||||
unionId,
|
||||
@@ -203,7 +208,9 @@ public class DifficultHelpApplyController {
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("id", "<>", SecurityUtil.getUserId());
|
||||
cnd.and("unionid", "=", SecurityUtil.getUnionId());
|
||||
if (!schoolScope) {
|
||||
cnd.and("unionid", "=", SecurityUtil.getUnionId());
|
||||
}
|
||||
|
||||
if (Strings.isNotBlank(key)) {
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
|
||||
+17
-8
@@ -43,9 +43,10 @@ public class DifficultHelpApprovalController {
|
||||
}
|
||||
ProcessTask task = dao.fetch(ProcessTask.class, args.getLong(FlowConst.PROCESS_TASK_ID_KEY));
|
||||
if (task == null) throw new BaseException("审批任务不存在");
|
||||
boolean amountApproval = "36ebafda-44c2-4bd3-bd7f-10839a575a0d".equals(task.getTaskName())
|
||||
|| "715ece82-4ae3-433c-ad18-ed875f45d1e3".equals(task.getTaskName());
|
||||
if (!amountApproval && !"e405f96c-5fc0-4f63-b323-e69dfe853253".equals(task.getTaskName())) {
|
||||
boolean actualAmountApproval = "e405f96c-5fc0-4f63-b323-e69dfe853253".equals(task.getTaskName());
|
||||
boolean amountApproval = "715ece82-4ae3-433c-ad18-ed875f45d1e3".equals(task.getTaskName());
|
||||
if (!amountApproval && !"36ebafda-44c2-4bd3-bd7f-10839a575a0d".equals(task.getTaskName())
|
||||
&& !"e405f96c-5fc0-4f63-b323-e69dfe853253".equals(task.getTaskName())) {
|
||||
throw new BaseException("当前任务不是困难帮扶审批任务");
|
||||
}
|
||||
ProcessInstance instance = dao.fetch(ProcessInstance.class, task.getProcessInstanceId());
|
||||
@@ -54,20 +55,28 @@ public class DifficultHelpApprovalController {
|
||||
if (StrUtil.isBlank(args.getStr("tf_sign"))) throw new BaseException("请完成审批签字");
|
||||
if (StrUtil.isBlank(args.getStr("tf_opinion"))) throw new BaseException("请填写审批意见");
|
||||
|
||||
String level = args.getStr("level");
|
||||
args.remove("level");
|
||||
args.remove("tf_level");
|
||||
args.remove("tf_actualMoney");
|
||||
Double money = amountApproval ? args.getDouble("money") : null;
|
||||
if (amountApproval) {
|
||||
if (StrUtil.isBlank(level) || money == null || !Double.isFinite(money) || money < 0) {
|
||||
throw new BaseException("请填写完整的级别和帮扶金额");
|
||||
if (money == null || !Double.isFinite(money) || money < 0) {
|
||||
throw new BaseException("请填写有效的帮扶金额");
|
||||
}
|
||||
args.put("tf_level", level);
|
||||
args.put("tf_money", money);
|
||||
} else {
|
||||
args.remove("money");
|
||||
args.remove("tf_money");
|
||||
if (actualAmountApproval && info.getMoney() != null) {
|
||||
args.put("tf_money", info.getMoney());
|
||||
args.put("tf_actualMoney", info.getMoney());
|
||||
}
|
||||
}
|
||||
// 参与者权限由流程引擎校验,业务入口不接受客户端指定管理员代办。
|
||||
args.remove(FlowConst.EXT_ENABLE_OPERATOR);
|
||||
flowCommonService.executeTask(args);
|
||||
if (amountApproval && Integer.valueOf(ProcessSubmitTypeEnum.AGREE.getCode()).equals(args.getInt(FlowConst.SUBMIT_TYPE))) {
|
||||
dao.update(DifficultHelpInfo.class, Chain.make("level", level).add("money", money), Cnd.where("id", "=", info.getId()));
|
||||
dao.update(DifficultHelpInfo.class, Chain.make("money", money), Cnd.where("id", "=", info.getId()));
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
+1
@@ -78,6 +78,7 @@ public class DifficultHelpFirstApprovalController {
|
||||
info.subsidyStandards,
|
||||
LEFT(info.applyTime, 10) AS applyTime,
|
||||
info.applyCount,
|
||||
info.applyMoney,
|
||||
info.mobile,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.difficulthelp.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
@@ -137,7 +138,7 @@ public class DifficultHelpMineController {
|
||||
|
||||
@At
|
||||
@ApiModelProperty("获取困难补助申请")
|
||||
@SaCheckPermission("difficultHelp.mine")
|
||||
@SaCheckPermission(value = {"difficultHelp.mine", "difficultHelp.reading"}, mode = SaMode.OR)
|
||||
public Result findOne(String id) {
|
||||
DifficultHelpInfo helpInfo = dao.fetch(DifficultHelpInfo.class, id);
|
||||
return Result.success().addData(helpInfo);
|
||||
|
||||
+77
-92
@@ -2,7 +2,6 @@ package com.budwk.app.zhgh.staffbenefit.difficulthelp.controller;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.TemplateExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
@@ -10,7 +9,6 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
@@ -154,7 +152,7 @@ public class DifficultHelpReadingController {
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("ins.state", "=", 20);
|
||||
pageParam.buildSearch(cnd, "info.");
|
||||
pageParam.buildReadingSearch(cnd, "info.");
|
||||
cnd.groupBy("info.id");
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pagination = difficultHelpCommonService.listPageMap(pageParam.getPageNumber(), pageParam.getPageSize(), sql);
|
||||
@@ -238,6 +236,7 @@ public class DifficultHelpReadingController {
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("difficultHelp.reading")
|
||||
public void doExportAllApply(DifficultHelpPageParam pageParam, HttpServletResponse response) throws Exception {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -268,7 +267,7 @@ public class DifficultHelpReadingController {
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
pageParam.buildSearch(cnd, "info.");
|
||||
pageParam.buildReadingSearch(cnd, "info.");
|
||||
cnd.groupBy("info.id");
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = difficultHelpCommonService.listMap(sql);
|
||||
@@ -281,42 +280,14 @@ public class DifficultHelpReadingController {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
NutMap record = list.get(i);
|
||||
|
||||
HashMap<String, Object> docData = new HashMap<>();
|
||||
docData.put("unionName", record.getString("unitName"));
|
||||
docData.put("proxyUserName", record.getString("proxyUserName"));
|
||||
docData.put("sex", record.getString("sex"));
|
||||
docData.put("loginName", record.getString("loginName"));
|
||||
docData.put("unitName", record.getString("unitName"));
|
||||
docData.put("homeIncome", record.getDouble("homeIncome"));
|
||||
docData.put("homeNumber", record.getInt("homeNumber"));
|
||||
docData.put("position", record.getString("position"));
|
||||
docData.put("officialCapacity", record.getString("officialCapacity"));
|
||||
docData.put("mobile", record.getString("mobile"));
|
||||
docData.put("userName", record.getString("userName"));
|
||||
docData.put("homeAddress", record.getString("homeAddress"));
|
||||
docData.put("bankOfDeposit", record.getString("bankOfDeposit"));
|
||||
docData.put("bankCardNum", record.getString("bankCardNum"));
|
||||
docData.put("reason", record.getString("reason"));
|
||||
docData.put("applyTime", DateUtil.format(DateUtil.parse(record.getString("applyTime")), "yyyy年MM月dd日"));
|
||||
docData.put("schoolname", "中国地质大学(武汉)");
|
||||
docData.put("year", DateUtil.format(DateUtil.parse(record.getString("applyTime")), "yyyy"));
|
||||
|
||||
String familyListStr = record.getString("familyList");
|
||||
if (StrUtil.isNotBlank(familyListStr)) {
|
||||
docData.put("familyList", JSONUtil.toList(familyListStr, Object.class));
|
||||
} else {
|
||||
docData.put("familyList", new ArrayList<>());
|
||||
DifficultHelpInfo difficultHelp = difficultHelpCommonService.fetch(record.getString("id"));
|
||||
if (difficultHelp == null) {
|
||||
throw new IllegalStateException("待导出的申请记录不存在:" + record.getString("id"));
|
||||
}
|
||||
|
||||
String fileName = (i + 1) + record.getString("userName") + "申请表.docx";
|
||||
byte[] bytes = renderApplyDocument(difficultHelp, false);
|
||||
String fileName = (i + 1) + difficultHelp.getUserName() + "申请表.docx";
|
||||
zipOutputStream.putNextEntry(new ZipEntry(fileName));
|
||||
|
||||
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||
Configure config = Configure.builder().bind("familyList", policy).build();
|
||||
|
||||
try (InputStream input = sysOfficeTemplateUtil.getTemplate("difficulty_apply")) {
|
||||
zipOutputStream.write(sysOfficeTemplateUtil.renderTemplate(input, docData, config, false, "difficulthelp:batch:" + record.getString("id")));
|
||||
}
|
||||
zipOutputStream.write(bytes);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
zipOutputStream.flush();
|
||||
@@ -326,11 +297,41 @@ public class DifficultHelpReadingController {
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("difficultHelp.reading")
|
||||
public void exportReadingApply(String id, DifficultHelpPageParam pageParam, HttpServletResponse response, Boolean print) throws Exception {
|
||||
Cnd cnd = Cnd.where("id", "=", id);
|
||||
pageParam.buildReadingSearch(cnd, "");
|
||||
if (difficultHelpCommonService.count(cnd) == 0) {
|
||||
throw new RuntimeException("该记录不在当前可查看的范围内,无法导出");
|
||||
}
|
||||
doExportApply(id, response, print);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckLogin
|
||||
public void doExportApply(String id, HttpServletResponse response, Boolean print) throws Exception {
|
||||
DifficultHelpInfo difficultHelp = difficultHelpCommonService.fetch(id);
|
||||
if (difficultHelp == null) {
|
||||
throw new RuntimeException("该条记录不存在");
|
||||
}
|
||||
boolean ownApply = AuthUtil.hasPermission("difficultHelp.mine")
|
||||
&& SecurityUtil.getUserId().equals(difficultHelp.getCreatedBy());
|
||||
if (!ownApply) {
|
||||
Cnd cnd = Cnd.where("id", "=", id);
|
||||
new DifficultHelpPageParam().buildReadingSearch(cnd, "");
|
||||
if (!AuthUtil.hasPermission("difficultHelp.reading") || difficultHelpCommonService.count(cnd) == 0) {
|
||||
throw new RuntimeException("该记录不在当前可查看的范围内,无法导出");
|
||||
}
|
||||
}
|
||||
boolean pdf = Boolean.TRUE.equals(print);
|
||||
String fileName = FileUtil.mainName(sysOfficeTemplateUtil.getTemplateFileName("difficulty_apply")) + "_" + difficultHelp.getUserName() + "_" +
|
||||
DateUtil.format(difficultHelp.getApplyTime(), "yyyyMMdd") + (pdf ? ".pdf" : ".docx");
|
||||
byte[] bytes = renderApplyDocument(difficultHelp, pdf);
|
||||
CommonDownloadUtil.download(fileName, bytes, response);
|
||||
}
|
||||
|
||||
private byte[] renderApplyDocument(DifficultHelpInfo difficultHelp, boolean pdf) throws Exception {
|
||||
HashMap<String, Object> docData = new HashMap<>();
|
||||
|
||||
docData.put("unionName", difficultHelp.getUnitName());
|
||||
@@ -363,10 +364,8 @@ public class DifficultHelpReadingController {
|
||||
docData.put("applyMoney", difficultHelp.getApplyMoney());
|
||||
docData.put("money", difficultHelp.getMoney());
|
||||
fillApplyApprovals(docData, difficultHelp.getId());
|
||||
if (print) {
|
||||
exportAsPDF(templateName, docData, config, difficultHelp, response);
|
||||
} else {
|
||||
exportAsWord(templateName, docData, config, difficultHelp,response);
|
||||
try (InputStream input = getApplyTemplate(templateName)) {
|
||||
return sysOfficeTemplateUtil.renderTemplate(input, docData, config, pdf, "difficulthelp:" + difficultHelp.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,32 +442,9 @@ public class DifficultHelpReadingController {
|
||||
return new ByteArrayInputStream(templateBytes);
|
||||
}
|
||||
|
||||
@At
|
||||
private void exportAsPDF(String templateName, HashMap<String, Object> docData,Configure config,
|
||||
DifficultHelpInfo difficultHelp, HttpServletResponse response) throws Exception {
|
||||
String fileName = FileUtil.mainName(sysOfficeTemplateUtil.getTemplateFileName(templateName)) + "_" + difficultHelp.getUserName() + "_" +
|
||||
DateUtil.format(difficultHelp.getApplyTime(), "yyyyMMdd") + ".pdf";
|
||||
|
||||
try (InputStream input = getApplyTemplate(templateName)) {
|
||||
byte[] bytes = sysOfficeTemplateUtil.renderTemplate(input, docData, config, true, "difficulthelp:" + difficultHelp.getId());
|
||||
CommonDownloadUtil.download(fileName, bytes, response);
|
||||
}
|
||||
}
|
||||
|
||||
@At
|
||||
private void exportAsWord(String templateName, HashMap<String, Object> docData,Configure config,
|
||||
DifficultHelpInfo difficultHelp, HttpServletResponse response) throws Exception {
|
||||
String fileName = FileUtil.mainName(sysOfficeTemplateUtil.getTemplateFileName(templateName)) + "_" + difficultHelp.getUserName() + "_" +
|
||||
DateUtil.format(difficultHelp.getApplyTime(), "yyyyMMdd") + ".docx";
|
||||
|
||||
try (InputStream input = getApplyTemplate(templateName)) {
|
||||
byte[] bytes = sysOfficeTemplateUtil.renderTemplate(input, docData, config, false, "difficulthelp:" + difficultHelp.getId());
|
||||
CommonDownloadUtil.download(fileName, bytes, response);
|
||||
}
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("difficultHelp.reading")
|
||||
public void exportSummary(DifficultHelpPageParam pageParam,String flag,
|
||||
HttpServletResponse response) throws Exception {
|
||||
NutMap result = new NutMap();
|
||||
@@ -491,6 +467,7 @@ public class DifficultHelpReadingController {
|
||||
info.subsidyStandards,
|
||||
info.bankOfDeposit,
|
||||
info.bankCardNum,
|
||||
info.money,
|
||||
info.applyCount
|
||||
FROM
|
||||
difficult_help_info info
|
||||
@@ -498,16 +475,44 @@ public class DifficultHelpReadingController {
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
pageParam.buildSearch(cnd, "info.");
|
||||
pageParam.buildReadingSearch(cnd, "info.");
|
||||
cnd.groupBy("info.id");
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = difficultHelpCommonService.listMap(sql);
|
||||
Sys_user user = sysUserService.fetch(SecurityUtil.getUserId());
|
||||
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
list.get(i).put("index", (i + 1));
|
||||
}
|
||||
|
||||
String fileName = "困难慰问汇总表_" + DateUtil.format(new Date(), "yyyyMMdd");
|
||||
if ("xlsx".equals(flag)) {
|
||||
String[][] columns = {
|
||||
{"序号", "index"}, {"姓名", "userName"}, {"工号", "loginName"},
|
||||
{"性别", "sex"}, {"身份证号", "idCard"}, {"年龄", "age"},
|
||||
{"职务", "position"}, {"职称", "officialCapacity"}, {"所属单位", "unitName"},
|
||||
{"手机号码", "mobile"}, {"生活致困原因", "reason"}, {"级别", "level"},
|
||||
{"家庭年总收入", "homeIncome"}, {"家庭人数", "homeNumber"},
|
||||
{"困难类型", "subsidyStandards"}, {"开户行", "bankOfDeposit"},
|
||||
{"银行卡号", "bankCardNum"}, {"实际金额(元)", "money"}, {"申请次数", "applyCount"}
|
||||
};
|
||||
List<ExcelExportEntity> entities = new ArrayList<>();
|
||||
for (String[] column : columns) {
|
||||
entities.add(new ExcelExportEntity(column[0], column[1], 20));
|
||||
}
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
exportParams.setTitle("困难帮扶汇总表");
|
||||
exportParams.setSheetName("汇总数据");
|
||||
try (Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list)) {
|
||||
CommonDownloadUtil.download(fileName + ".xlsx", workbook, response);
|
||||
} catch (Exception e) {
|
||||
log.error("导出Excel汇总表失败", e);
|
||||
throw new RuntimeException("导出Excel汇总表失败:" + e.getMessage(), e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Sys_user user = sysUserService.fetch(SecurityUtil.getUserId());
|
||||
result.put("schoolName", "中国地址大学(武汉)");
|
||||
result.put("year", DateUtil.format(new Date(), "yyyy"));
|
||||
result.put("applyTime", DateUtil.format(new Date(), "yyyy年MM月dd日"));
|
||||
@@ -515,32 +520,12 @@ public class DifficultHelpReadingController {
|
||||
result.put("mobile", user.getMobile());
|
||||
result.put("da", list);
|
||||
|
||||
String resource;
|
||||
if ("xlsx".equals(flag)) {
|
||||
resource = "difficulty_summary_xlsx";
|
||||
if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||
resource = "difficulty_summary_school_xlsx";
|
||||
}
|
||||
} else {
|
||||
resource = "difficulty_summary";
|
||||
if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||
resource = "difficulty_summary_school";
|
||||
}
|
||||
String resource = "difficulty_summary";
|
||||
if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||
resource = "difficulty_summary_school";
|
||||
}
|
||||
|
||||
String fileName = "困难慰问汇总表_" + DateUtil.format(new Date(), "yyyyMMdd");
|
||||
|
||||
if ("xlsx".equals(flag)) {
|
||||
try {
|
||||
InputStream is = sysOfficeTemplateUtil.getTemplate(resource);
|
||||
TemplateExportParams exportParams = new TemplateExportParams(is, null);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, result);
|
||||
CommonDownloadUtil.download(fileName + ".xlsx", workbook, response);
|
||||
} catch (Exception e) {
|
||||
log.error("导出Excel汇总表失败", e);
|
||||
throw new RuntimeException("导出Excel汇总表失败:" + e.getMessage());
|
||||
}
|
||||
} else if ("docx".equals(flag)) {
|
||||
if ("docx".equals(flag)) {
|
||||
try (InputStream input = sysOfficeTemplateUtil.getTemplate(resource)) {
|
||||
Configure config = Configure.builder()
|
||||
.bind("da", new LoopRowTableRenderPolicy())
|
||||
|
||||
+1
@@ -79,6 +79,7 @@ public class DifficultHelpSecondApprovalController {
|
||||
info.subsidyStandards,
|
||||
LEFT(info.applyTime, 10) AS applyTime,
|
||||
info.applyCount,
|
||||
info.applyMoney,
|
||||
info.mobile,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
|
||||
+2
@@ -82,6 +82,8 @@ public class DifficultHelpThirdApprovalController {
|
||||
info.subsidyStandards,
|
||||
LEFT(info.applyTime, 10) AS applyTime,
|
||||
info.applyCount,
|
||||
info.applyMoney,
|
||||
info.money,
|
||||
info.mobile,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
|
||||
+1
-8
@@ -1,7 +1,6 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.difficulthelp.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.zhgh.staffbenefit.difficulthelp.models.DifficultHelpInfo;
|
||||
import com.budwk.app.zhgh.staffbenefit.difficulthelp.vo.DifficultHelpPageParam;
|
||||
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.param.AidFundPageForm;
|
||||
@@ -22,13 +21,7 @@ public interface DifficultHelpCommonService extends BaseService<DifficultHelpInf
|
||||
|
||||
void validateApplyUser(DifficultHelpInfo difficultHelpInfo);
|
||||
|
||||
/**
|
||||
* 困难帮扶进入校工会办公室主任审批时,通知本次任务的实际参与人。
|
||||
* @param task WF 已创建且参与人已落库的任务;仅 KNBF 办公室审批待办触发,
|
||||
* 接收人使用任务参与人的 actorAccount(系统 loginname)
|
||||
* @return 无返回值;开关关闭或非目标节点不发送,发送失败记录日志而不阻断流程
|
||||
*/
|
||||
void notifyOfficeApproval(ProcessTask task);
|
||||
void validateApplyUser(DifficultHelpInfo difficultHelpInfo, boolean requireApplyMoney);
|
||||
|
||||
/**
|
||||
* 导入实际金额数据处理
|
||||
|
||||
+22
-70
@@ -4,14 +4,7 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.wxwork.WxWorkMessageService;
|
||||
import com.budwk.app.flow.entity.ProcessDefine;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.entity.ProcessTaskActor;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.base.utils.ManyAddOrRenewUtil;
|
||||
@@ -37,8 +30,6 @@ import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.random.R;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.log.Log;
|
||||
import org.nutz.log.Logs;
|
||||
import org.nutz.mvc.upload.TempFile;
|
||||
|
||||
import java.util.*;
|
||||
@@ -54,8 +45,6 @@ import java.util.stream.IntStream;
|
||||
*/
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class DifficultHelpCommonServiceImpl extends BaseServiceImpl<DifficultHelpInfo> implements DifficultHelpCommonService {
|
||||
private static final Log log = Logs.get();
|
||||
private static final String OFFICE_APPROVAL_TASK = "36ebafda-44c2-4bd3-bd7f-10839a575a0d";
|
||||
|
||||
public DifficultHelpCommonServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
@@ -63,58 +52,14 @@ public class DifficultHelpCommonServiceImpl extends BaseServiceImpl<DifficultHel
|
||||
|
||||
@Inject
|
||||
private ManyAddOrRenewUtil manyAddOrRenewUtil;
|
||||
@Inject
|
||||
private WxWorkMessageService wxWorkMessageService;
|
||||
|
||||
/**
|
||||
* 只通知 KNBF 办公室待办的实际审核人;重新提交生成的新任务沿用同一入口。
|
||||
* task 为任务开始事件对应的 WF 任务,actorAccount 为学校企业微信 userid。
|
||||
* 无返回值;服务返回 skipped 表示开关关闭,success=false 表示接口失败或部分接收人无效。
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void notifyOfficeApproval(ProcessTask task) {
|
||||
if (task == null || task.getId() == null || task.getProcessInstanceId() == null
|
||||
|| !OFFICE_APPROVAL_TASK.equals(task.getTaskName())
|
||||
|| !ProcessTaskStateEnum.DOING.getCode().equals(task.getTaskState())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ProcessInstance instance = dao().fetch(ProcessInstance.class, task.getProcessInstanceId());
|
||||
if (instance == null || instance.getProcessDefineId() == null) return;
|
||||
ProcessDefine define = dao().fetch(ProcessDefine.class, instance.getProcessDefineId());
|
||||
// 使用实例所属定义核验流程 KEY,避免其他流程复用同一个节点 ID 时误发。
|
||||
if (define == null || !"KNBF".equals(define.getName())) return;
|
||||
DifficultHelpInfo info = StrUtil.isBlank(instance.getBusinessNo()) ? null : fetch(instance.getBusinessNo());
|
||||
if (info == null) {
|
||||
log.warnf("困难帮扶企业微信通知跳过:申请不存在,taskId=%s", task.getId());
|
||||
return;
|
||||
}
|
||||
List<ProcessTaskActor> actors = dao().query(ProcessTaskActor.class,
|
||||
Cnd.where(ProcessTaskActor::getProcessTaskId, "=", task.getId()));
|
||||
List<String> loginNames = actors.stream().map(ProcessTaskActor::getActorAccount)
|
||||
.filter(StrUtil::isNotBlank).map(String::trim).distinct().toList();
|
||||
// 不按角色扩大接收范围,未绑定工号时不以内部用户 ID 代替企业微信 userid。
|
||||
if (loginNames.isEmpty()) {
|
||||
log.warnf("困难帮扶企业微信通知跳过:审核人 loginname 为空,taskId=%s", task.getId());
|
||||
return;
|
||||
}
|
||||
String content = "您有一条困难帮扶申请待审核,受助人:"
|
||||
+ StrUtil.blankToDefault(info.getUserName(), "未填写") + ",请进入校工会办公室审批页面办理。";
|
||||
// 流程定义中仍保留旧的 unionLeaderApproval 链接,本节点实际办理入口为 branchUnionApproval。
|
||||
NutMap result = wxWorkMessageService.massSend(loginNames, "困难帮扶待办提醒", content,
|
||||
"/platform/difficultHelp/branchUnionApproval/h5");
|
||||
if (!result.getBoolean("skipped", false) && !result.getBoolean("success", false)) {
|
||||
log.warnf("困难帮扶企业微信通知发送失败或部分失败,taskId=%s,result=%s", task.getId(), result);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 外部消息不改变审批结果;日志保留任务 ID,便于定位接口、账号或配置问题。
|
||||
log.error("困难帮扶企业微信通知异常,taskId=" + task.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateApplyUser(DifficultHelpInfo difficultHelpInfo) {
|
||||
validateApplyUser(difficultHelpInfo, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateApplyUser(DifficultHelpInfo difficultHelpInfo, boolean requireApplyMoney) {
|
||||
if (difficultHelpInfo == null || StrUtil.isBlank(difficultHelpInfo.getUserId())) {
|
||||
throw new BaseException("请选择受助人");
|
||||
}
|
||||
@@ -124,19 +69,29 @@ public class DifficultHelpCommonServiceImpl extends BaseServiceImpl<DifficultHel
|
||||
throw new BaseException("只能查看他人代为申请的记录,不能修改或提交");
|
||||
}
|
||||
}
|
||||
if (difficultHelpInfo.getApplyMoney() == null || !Double.isFinite(difficultHelpInfo.getApplyMoney())
|
||||
|| difficultHelpInfo.getApplyMoney() <= 0) {
|
||||
if ((requireApplyMoney || difficultHelpInfo.getApplyMoney() != null)
|
||||
&& (difficultHelpInfo.getApplyMoney() == null || !Double.isFinite(difficultHelpInfo.getApplyMoney())
|
||||
|| difficultHelpInfo.getApplyMoney() <= 0)) {
|
||||
throw new BaseException("请填写大于零的申请金额");
|
||||
}
|
||||
if (difficultHelpInfo.getUserId().equals(SecurityUtil.getUserId())) {
|
||||
if (!AuthUtil.hasPermission("difficult_assistance_self")
|
||||
&& !AuthUtil.hasPermission("difficult_assistance_school")
|
||||
&& !AuthUtil.hasPermission("difficult_assistance_union")) {
|
||||
throw new BaseException("没有帮扶申请权限");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
|
||||
throw new BaseException("只有分工会主席可以替他人申请");
|
||||
boolean schoolScope = AuthUtil.hasPermission("difficult_assistance_school");
|
||||
if (!schoolScope && !AuthUtil.hasPermission("difficult_assistance_union")) {
|
||||
throw new BaseException("没有代申请权限");
|
||||
}
|
||||
View_user recipient = dao().fetch(View_user.class, Cnd.where("id", "=", difficultHelpInfo.getUserId()));
|
||||
if (recipient == null || StrUtil.isBlank(SecurityUtil.getUnionId())
|
||||
|| !SecurityUtil.getUnionId().equals(recipient.getUnionId())) {
|
||||
if (recipient == null) {
|
||||
throw new BaseException("受助人不存在");
|
||||
}
|
||||
if (!schoolScope && (StrUtil.isBlank(SecurityUtil.getUnionId())
|
||||
|| !SecurityUtil.getUnionId().equals(recipient.getUnionId()))) {
|
||||
throw new BaseException("只能替本分工会人员申请");
|
||||
}
|
||||
}
|
||||
@@ -182,10 +137,7 @@ public class DifficultHelpCommonServiceImpl extends BaseServiceImpl<DifficultHel
|
||||
|
||||
// 2. 查询符合条件的 DifficultHelpInfo 记录
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
cnd.and(Cnd.exps("loginName", "LIKE", "%" + pageForm.getSearchKeyword() + "%")
|
||||
.or("userName", "LIKE", "%" + pageForm.getSearchKeyword() + "%"));
|
||||
}
|
||||
pageForm.buildReadingSearch(cnd, "");
|
||||
List<DifficultHelpInfo> infoList = dao().query(DifficultHelpInfo.class, cnd);
|
||||
|
||||
// 3. 构建用户缴费索引 (userId -> year -> money)
|
||||
|
||||
+24
@@ -9,6 +9,7 @@ import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.lang.Lang;
|
||||
|
||||
import java.util.List;
|
||||
@@ -32,6 +33,29 @@ public class DifficultHelpPageParam extends PageForm {
|
||||
|
||||
private List<String> dateRange;
|
||||
|
||||
public void buildReadingSearch(Cnd cnd, String prefix) {
|
||||
buildSearch(cnd, prefix, false);
|
||||
applyReadingScope(cnd, prefix);
|
||||
String recordPrefix = StrUtil.isBlank(prefix) ? "difficult_help_info." : prefix;
|
||||
cnd.and(new Static("EXISTS (SELECT 1 FROM wf_process_instance reading_instance WHERE reading_instance.businessNo = "
|
||||
+ recordPrefix + "id AND reading_instance.state = 20)"));
|
||||
}
|
||||
|
||||
public void applyReadingScope(Cnd cnd, String prefix) {
|
||||
if (AuthUtil.hasPermission("difficult_all_school")) {
|
||||
return;
|
||||
}
|
||||
if (AuthUtil.hasPermission("difficult_branch_union") && StrUtil.isNotBlank(SecurityUtil.getUnionId())) {
|
||||
cnd.and(prefix + "unionId", "=", SecurityUtil.getUnionId());
|
||||
return;
|
||||
}
|
||||
if (AuthUtil.hasPermission("difficult_self") && StrUtil.isNotBlank(SecurityUtil.getUserId())) {
|
||||
cnd.and(prefix + "userId", "=", SecurityUtil.getUserId());
|
||||
return;
|
||||
}
|
||||
cnd.and("1", "=", 0);
|
||||
}
|
||||
|
||||
public void buildSearch(Cnd cnd, String prefix){
|
||||
buildSearch(cnd, prefix, true);
|
||||
}
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckRole;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
|
||||
import com.budwk.app.flow.constant.FlowConst;
|
||||
import com.budwk.app.flow.engine.FlowEngine;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
|
||||
import com.budwk.app.flow.service.FlowCommonService;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.democratic.suggestionBox.models.SuggestionBox;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceService;
|
||||
import io.swagger.annotations.Api;
|
||||
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.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.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceApplyController
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/8/20 15:40
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@SaCheckRole("BRANCH_UNION_CHAIRMAN")
|
||||
@IocBean
|
||||
@At("/platform/special_condolence/apply")
|
||||
@Api("特殊慰问申请")
|
||||
@Ok("json:full")
|
||||
public class SpecialCondolenceApplyController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private FlowEngine flowEngine;
|
||||
@Inject
|
||||
private SpecialCondolenceService specialcondolenceService;
|
||||
@Inject
|
||||
private FlowCommonService flowCommonService;
|
||||
@Inject
|
||||
private SysOfficeTemplateUtil sysOfficeTemplateUtil;
|
||||
|
||||
@At("/")
|
||||
@Ok("beetl:/platform/zhgh/staffbenefit/specialcondolence/apply/index.html")
|
||||
@SaCheckPermission("special_condolence.apply")
|
||||
public void index() {}
|
||||
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/staffbenefit/specialcondolence/apply/index.html")
|
||||
@SaCheckPermission("h5.special_condolence.apply")
|
||||
public void h5Index() {}
|
||||
|
||||
@At
|
||||
@ApiOperation("保存申请")
|
||||
@SaCheckPermission(value = {"special_condolence.apply", "h5.special_condolence.apply"}, mode = SaMode.OR)
|
||||
@SLog(tag = "特殊慰问系统-慰问申请", msg = "保存申请")
|
||||
public Result save(@Param("data") SpecialCondolence specialcondolence) {
|
||||
if(StrUtil.isBlank(specialcondolence.getId())) specialcondolence.setCreateTime(DateUtil.now());
|
||||
prepareApplication(specialcondolence);
|
||||
specialcondolenceService.saveDraftApplication(specialcondolence);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("提交申请")
|
||||
@SaCheckPermission(value = {"special_condolence.apply", "h5.special_condolence.apply"}, mode = SaMode.OR)
|
||||
@SLog(tag = "特殊慰问系统-慰问申请", msg = "提交申请")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result submit(@Param("data") SpecialCondolence specialcondolence) {
|
||||
if (StrUtil.isBlank(specialcondolence.getRemark()) || specialcondolence.getRemark().trim().length() > 500) throw new com.budwk.app.base.exception.BaseException("请填写500字以内的申请事由");
|
||||
if (StrUtil.isBlank(specialcondolence.getSignature())) throw new com.budwk.app.base.exception.BaseException("请完成申请人签字");
|
||||
if(StrUtil.isBlank(specialcondolence.getId())) specialcondolence.setCreateTime(DateUtil.now());
|
||||
prepareApplication(specialcondolence);
|
||||
specialcondolenceService.saveApplication(specialcondolence);
|
||||
// 开启流程实例
|
||||
Dict args = Dict.create();
|
||||
args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode());
|
||||
args.set(FlowConst.FORM_DATA, specialcondolence);
|
||||
ProcessInstance instance = flowEngine.startProcessInstanceByKey("TSWW", specialcondolence.getId(), SecurityUtil.getUserId(), args);
|
||||
|
||||
// 自动完成第一个申请任务
|
||||
List<ProcessTask> doingTaskList = flowEngine.processTaskService().getDoingTaskList(instance.getId(), null);
|
||||
for (ProcessTask task : doingTaskList) {
|
||||
flowEngine.executeProcessTask(task.getId(), SecurityUtil.getUserId(), args);
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("重新提交申请")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission(value = {"special_condolence.apply", "h5.special_condolence.apply"}, mode = SaMode.OR)
|
||||
public Result submitAgain(@Param("data") SpecialCondolence specialcondolence, @Param("taskId") Long taskId) {
|
||||
if (StrUtil.isBlank(specialcondolence.getRemark()) || specialcondolence.getRemark().trim().length() > 500) throw new com.budwk.app.base.exception.BaseException("请填写500字以内的申请事由");
|
||||
if (StrUtil.isBlank(specialcondolence.getSignature())) throw new com.budwk.app.base.exception.BaseException("请完成申请人签字");
|
||||
ProcessTask task = taskId == null ? null : dao.fetch(ProcessTask.class, taskId);
|
||||
ProcessInstance instance = task == null ? null : dao.fetch(ProcessInstance.class, task.getProcessInstanceId());
|
||||
if (instance == null || !StrUtil.equals(instance.getBusinessNo(), specialcondolence.getId())
|
||||
|| !"startTask".equals(task.getTaskName()) || !SecurityUtil.getUserId().equals(instance.getOperator())) {
|
||||
throw new com.budwk.app.base.exception.BaseException("申请任务不匹配");
|
||||
}
|
||||
prepareApplication(specialcondolence);
|
||||
specialcondolenceService.saveApplication(specialcondolence);
|
||||
|
||||
Dict dict = Dict.create();
|
||||
dict.set(FlowConst.PROCESS_TASK_ID_KEY, taskId);
|
||||
dict.set(FlowConst.FORM_DATA, specialcondolence);
|
||||
dict.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode());
|
||||
flowCommonService.executeTask(dict);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询用户")
|
||||
@SaCheckPermission(value = {"special_condolence.apply", "h5.special_condolence.apply"}, mode = SaMode.OR)
|
||||
public Object listUser(String keyword) {
|
||||
Sql sql = Sqls.create("""
|
||||
select
|
||||
u.id,
|
||||
u.idCard,
|
||||
u.personType,
|
||||
bank.bankOfDeposit,
|
||||
bank.bankUserName,
|
||||
bank.bankCardNumber,
|
||||
username as userName,
|
||||
loginname as loginName,
|
||||
sex,
|
||||
mobile,
|
||||
technicalTitle,
|
||||
IFNULL(unitname, '暂无') as unitName,
|
||||
unitid as unitId,
|
||||
unionid as unionId,
|
||||
unionname as unionName
|
||||
from
|
||||
vw_user u
|
||||
LEFT JOIN (
|
||||
SELECT idCard, bankOfDeposit, bankUserName, bankCardNumber,
|
||||
ROW_NUMBER() OVER (PARTITION BY idCard ORDER BY createdAt DESC, id DESC) rn
|
||||
FROM special_condolence_recipient WHERE delFlag = false
|
||||
) bank ON bank.idCard = u.idCard AND bank.rn = 1 AND u.idCard <> ''
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or(View_user::getLoginname, "like", "%" + keyword + "%");
|
||||
seg.or(View_user::getUsername, "like", "%" + keyword + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
if (StrUtil.isBlank(SecurityUtil.getUnionId())) throw new com.budwk.app.base.exception.BaseException("申请人未关联分工会");
|
||||
cnd.and("u.unionId", "=", SecurityUtil.getUnionId());
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = specialcondolenceService.listPageMap(1, 50, sql);
|
||||
return Result.success(pagination.getList());
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission(value = {"special_condolence.apply", "h5.special_condolence.apply"}, mode = SaMode.OR)
|
||||
public void importTemplate(javax.servlet.http.HttpServletResponse response) throws Exception {
|
||||
String templateCode = "special_condolence_import";
|
||||
try (java.io.InputStream input = sysOfficeTemplateUtil.getTemplate(templateCode)) {
|
||||
com.budwk.app.base.utils.CommonDownloadUtil.download(
|
||||
sysOfficeTemplateUtil.getTemplateFileName(templateCode), input.readAllBytes(), response);
|
||||
}
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission(value = {"special_condolence.apply", "h5.special_condolence.apply"}, mode = SaMode.OR)
|
||||
@org.nutz.mvc.annotation.AdaptBy(type = org.nutz.mvc.upload.UploadAdaptor.class, args = {"ioc:fileUpload"})
|
||||
public Result importRecipients(@Param("file") org.nutz.mvc.upload.TempFile file) throws Exception {
|
||||
if (file == null) throw new com.budwk.app.base.exception.BaseException("请选择Excel文件");
|
||||
if (!file.getSubmittedFileName().toLowerCase(java.util.Locale.ROOT).matches(".*\\.(xlsx|xls)$")) throw new com.budwk.app.base.exception.BaseException("仅支持xlsx、xls文件");
|
||||
String[] headers = {"慰问对象", "工号", "身份证号码", "开户行", "户名", "收款账户"};
|
||||
String[] fields = {"helpUserName", "helpLoginName", "idCard", "bankOfDeposit", "bankUserName", "bankCardNumber"};
|
||||
java.util.List<cn.hutool.json.JSONObject> result = new java.util.ArrayList<>();
|
||||
try (java.io.InputStream input = new java.io.FileInputStream(file.getFile());
|
||||
org.apache.poi.ss.usermodel.Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(input)) {
|
||||
if (workbook.getNumberOfSheets() == 0) throw new com.budwk.app.base.exception.BaseException("Excel中没有工作表");
|
||||
org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(0);
|
||||
org.apache.poi.ss.usermodel.DataFormatter formatter = new org.apache.poi.ss.usermodel.DataFormatter();
|
||||
org.apache.poi.ss.usermodel.Row header = sheet.getRow(0);
|
||||
for (int c = 0; c < headers.length; c++) {
|
||||
if (header == null || !headers[c].equals(formatter.formatCellValue(header.getCell(c)).trim())) throw new com.budwk.app.base.exception.BaseException("表头不正确,请下载导入模板");
|
||||
}
|
||||
if (sheet.getLastRowNum() > 1000) throw new com.budwk.app.base.exception.BaseException("最多导入1000行");
|
||||
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
|
||||
org.apache.poi.ss.usermodel.Row excelRow = sheet.getRow(r);
|
||||
if (excelRow == null) continue;
|
||||
String prefix = "慰问对象" + (result.size() + 1) + " ";
|
||||
cn.hutool.json.JSONObject row = new cn.hutool.json.JSONObject();
|
||||
boolean empty = true;
|
||||
for (int c = 0; c < fields.length; c++) {
|
||||
org.apache.poi.ss.usermodel.Cell cell = excelRow.getCell(c);
|
||||
String value = formatter.formatCellValue(cell).trim();
|
||||
if (c != 1 && cell != null && cell.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) throw new com.budwk.app.base.exception.BaseException(prefix + "不支持公式,请填写实际值");
|
||||
if ((c == 2 || c == 5) && cell != null && cell.getCellType() == org.apache.poi.ss.usermodel.CellType.NUMERIC) throw new com.budwk.app.base.exception.BaseException(prefix + "工号、身份证和收款账户请使用文本格式,避免数字失真");
|
||||
row.set(fields[c], value);
|
||||
empty = empty && value.isEmpty();
|
||||
}
|
||||
if (empty) continue;
|
||||
if (!com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceRecipients.validIdCard(row.getStr("idCard"))) throw new com.budwk.app.base.exception.BaseException(prefix + "身份证号码格式不正确");
|
||||
if (!row.getStr("bankCardNumber").matches("[0-9]{6,30}")) throw new com.budwk.app.base.exception.BaseException(prefix + "收款账户格式不正确");
|
||||
result.add(row);
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) throw new com.budwk.app.base.exception.BaseException("没有可导入的数据");
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
private void prepareApplication(SpecialCondolence info) {
|
||||
if (StrUtil.isNotBlank(info.getId())) {
|
||||
SpecialCondolence existing = dao.fetch(SpecialCondolence.class, info.getId());
|
||||
if (existing == null || !SecurityUtil.getUserId().equals(existing.getCreatedBy())) {
|
||||
throw new com.budwk.app.base.exception.BaseException("只能修改本人申请");
|
||||
}
|
||||
info.setCreatedBy(existing.getCreatedBy());
|
||||
}
|
||||
info.setApplyUserId(SecurityUtil.getUserId());
|
||||
info.setApplyUserName(SecurityUtil.getUserUsername());
|
||||
info.setApplyLoginName(SecurityUtil.getUserLoginname());
|
||||
info.setApplyUnionId(SecurityUtil.getUnionId());
|
||||
info.setApplyUnitId(SecurityUtil.getUnitId());
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.flow.constant.FlowConst;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.service.FlowCommonService;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
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.json.Json;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
@IocBean
|
||||
@Ok("json")
|
||||
@At("/platform/special_condolence/approval")
|
||||
public class SpecialCondolenceApprovalController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private FlowCommonService flowCommonService;
|
||||
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result executeTask(@Param("data") String data) {
|
||||
Dict args = Json.fromJson(Dict.class, data);
|
||||
if (args == null || args.getLong(FlowConst.PROCESS_TASK_ID_KEY) == null) {
|
||||
throw new BaseException("审批任务不能为空");
|
||||
}
|
||||
ProcessTask task = dao.fetch(ProcessTask.class, args.getLong(FlowConst.PROCESS_TASK_ID_KEY));
|
||||
if (task == null) throw new BaseException("审批任务不存在");
|
||||
if (!"c438972e-2f67-4907-b229-cb7aa17da147".equals(task.getTaskName())
|
||||
&& !"7f560675-aeea-4254-a57e-957d03d6b786".equals(task.getTaskName())
|
||||
&& !"af363a4e-fc56-42a0-9906-8c7a2814006b".equals(task.getTaskName())) {
|
||||
throw new BaseException("当前任务不是特殊慰问审批任务");
|
||||
}
|
||||
ProcessInstance instance = dao.fetch(ProcessInstance.class, task.getProcessInstanceId());
|
||||
com.budwk.app.flow.entity.ProcessDefine define = instance == null ? null : dao.fetch(com.budwk.app.flow.entity.ProcessDefine.class, instance.getProcessDefineId());
|
||||
SpecialCondolence info = define == null || !"TSWW".equals(define.getName()) ? null : dao.fetch(SpecialCondolence.class, instance.getBusinessNo());
|
||||
if (info == null) throw new BaseException("特殊慰问申请不存在");
|
||||
if (StrUtil.isBlank(args.getStr("tf_sign"))) throw new BaseException("请完成审批签字");
|
||||
if (StrUtil.isBlank(args.getStr("tf_opinion"))) throw new BaseException("请填写审批意见");
|
||||
|
||||
// 参与者权限由流程引擎校验,业务入口不接受客户端指定管理员代办。
|
||||
args.remove(FlowConst.EXT_ENABLE_OPERATOR);
|
||||
flowCommonService.executeTask(args);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
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.dao.util.cri.Static;
|
||||
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 java.util.List;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceFourthApprovalController
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/29 16:23
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@IocBean
|
||||
@At("/platform/special_condolence/fourthApproval")
|
||||
@Api("特殊慰问四级审核")
|
||||
@Ok("json:full")
|
||||
public class SpecialCondolenceFourthApprovalController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private SpecialCondolenceService specialcondolenceService;
|
||||
|
||||
@At("/")
|
||||
@Ok("beetl:/platform/zhgh/staffbenefit/specialcondolence/fourthApproval/index.html")
|
||||
@SaCheckPermission("special_condolence.fourthApproval")
|
||||
public void index() {}
|
||||
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/staffbenefit/specialcondolence/fourthApproval/index.html")
|
||||
@SaCheckPermission("h5.special_condolence.fourthApproval")
|
||||
public void h5Index() {}
|
||||
|
||||
@At
|
||||
@ApiOperation("分页查询")
|
||||
@SaCheckPermission(value = {"special_condolence.fourthApproval", "h5.special_condolence.fourthApproval"}, mode = SaMode.OR)
|
||||
public Result pageData(PageForm pageForm,
|
||||
@Param(value = "year") Integer year,
|
||||
@Param(value = "type") String type,
|
||||
@Param(value = "unionId") String unionId,
|
||||
@Param(value = "unitId") String unitId,
|
||||
@Param(value = "approval") Boolean approval) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
COALESCE(recipientTotals.recipientCount, 0) AS recipientCount,
|
||||
type.name as typeName,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state instanceState,
|
||||
ins.variable instanceVariable,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.id taskId,
|
||||
t.taskName AS taskKey,
|
||||
t.displayName taskName,
|
||||
t.taskType,
|
||||
t.performType taskPerformType,
|
||||
t.taskState,
|
||||
t.finishTime,
|
||||
t.taskParentId,
|
||||
t.variable taskVariable,
|
||||
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName,
|
||||
IF(rt.id IS NOT NULL, 1, 0) AS canRevoke
|
||||
FROM
|
||||
wf_process_task t
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
LEFT JOIN special_condolence info ON info.id = ins.businessNo
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
LEFT JOIN (
|
||||
SELECT applyId, COUNT(*) AS recipientCount
|
||||
FROM special_condolence_recipient
|
||||
WHERE delFlag = false
|
||||
GROUP BY applyId
|
||||
) recipientTotals ON recipientTotals.applyId = info.id
|
||||
LEFT JOIN special_condolence_type type ON info.type = type.id
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("info.id", "is not", null);
|
||||
cnd.and(new Static("""
|
||||
NOT EXISTS (
|
||||
SELECT 1 FROM wf_process_task newer
|
||||
WHERE newer.processInstanceId = t.processInstanceId
|
||||
AND newer.taskName = t.taskName
|
||||
AND newer.id > t.id
|
||||
)
|
||||
"""));
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or(SpecialCondolence::getHelpUserName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or(SpecialCondolence::getHelpLoginName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.andEX("info.type", "=", type);
|
||||
cnd.andEX("YEAR(info.createTime)", "=", year);
|
||||
cnd.andEX("info.applyUnionId", "=", unionId);
|
||||
cnd.andEX("info.applyUnitId", "=", unitId);
|
||||
|
||||
cnd.and("t.taskName", "=", "af363a4e-fc56-42a0-9906-8c7a2814006b");
|
||||
if (!"superadmin".equals(SecurityUtil.getUserLoginname()) && !AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name())) {
|
||||
Sql unitSql = Sqls.queryString("""
|
||||
SELECT DISTINCT ur.unitId
|
||||
FROM sys_user_role ur
|
||||
INNER JOIN sys_role r ON r.id = ur.roleId
|
||||
WHERE ur.userId = @userId
|
||||
AND ur.enable = true
|
||||
AND r.disabled = false
|
||||
AND r.code = @roleCode
|
||||
AND ur.unitId IS NOT NULL
|
||||
AND ur.unitId <> ''
|
||||
""");
|
||||
unitSql.setParam("userId", SecurityUtil.getUserId());
|
||||
unitSql.setParam("roleCode", RoleConstant.UNIT_SCHOOL_LEADER.name());
|
||||
dao.execute(unitSql);
|
||||
List<String> managedUnitIds = unitSql.getList(String.class);
|
||||
if (managedUnitIds.isEmpty()) {
|
||||
cnd.and(new Static("1 = 0"));
|
||||
} else {
|
||||
cnd.and("info.applyUnitId", "in", managedUnitIds);
|
||||
}
|
||||
}
|
||||
|
||||
if (approval) {
|
||||
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
cnd.desc("info.createTime");
|
||||
} else {
|
||||
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
}
|
||||
cnd.groupBy("t.id");
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pageVO = specialcondolenceService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pageVO);
|
||||
}
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.sms.SmsService;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
|
||||
import com.budwk.app.flow.engine.FlowEngine;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.flow.vo.ProcessTaskVO;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolenceType;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceService;
|
||||
import com.deepoove.poi.data.PictureRenderData;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
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.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Lang;
|
||||
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.servlet.http.HttpServletResponse;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceApplyController
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/28 11:06
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@At("/platform/special_condolence/mine")
|
||||
@Api("特殊慰问我的")
|
||||
@Ok("json:full")
|
||||
public class SpecialCondolenceMineController {
|
||||
|
||||
@Inject
|
||||
private SpecialCondolenceService specialcondolenceService;
|
||||
@Inject
|
||||
private FlowEngine flowEngine;
|
||||
@Inject
|
||||
private SysOfficeTemplateUtil sysOfficeTemplateUtil;
|
||||
|
||||
@At("/")
|
||||
@Ok("beetl:/platform/zhgh/staffbenefit/specialcondolence/mine/index.html")
|
||||
@SaCheckPermission("special_condolence.mine")
|
||||
public void index() {}
|
||||
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/staffbenefit/specialcondolence/mine/index.html")
|
||||
@SaCheckPermission("h5.special_condolence.mine")
|
||||
public void h5Index() {}
|
||||
|
||||
@At
|
||||
@ApiOperation("分页查询")
|
||||
@SaCheckPermission(value = {"special_condolence.mine", "h5.special_condolence.mine"}, mode = SaMode.OR)
|
||||
public Result pageData(PageForm pageForm,
|
||||
@Param(value = "year") Integer year,
|
||||
@Param(value = "type") String type) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
COALESCE(recipientTotals.recipientCount, 0) AS recipientCount,
|
||||
type.name as typeName,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state instanceState,
|
||||
ins.variable instanceVariable,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.id taskId,
|
||||
t.taskName AS taskKey,
|
||||
t.displayName taskName,
|
||||
t.taskType,
|
||||
t.performType taskPerformType,
|
||||
t.taskState,
|
||||
t.finishTime,
|
||||
t.taskParentId,
|
||||
t.variable taskVariable,
|
||||
IF((SELECT taskName FROM wf_process_task tt WHERE tt.id = t.taskParentId) = 'startTask', 1, 0) canRevoke,
|
||||
(select MAX(id) from wf_process_task where processInstanceId = ins.id and taskName = 'startTask') AS startTaskId
|
||||
FROM
|
||||
special_condolence info
|
||||
LEFT JOIN (
|
||||
SELECT applyId, COUNT(*) AS recipientCount
|
||||
FROM special_condolence_recipient
|
||||
WHERE delFlag = false
|
||||
GROUP BY applyId
|
||||
) recipientTotals ON recipientTotals.applyId = info.id
|
||||
LEFT JOIN special_condolence_type type ON info.type = type.id
|
||||
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
id,
|
||||
processInstanceId,
|
||||
taskName,
|
||||
displayName,
|
||||
taskType,
|
||||
performType,
|
||||
taskState,
|
||||
finishTime,
|
||||
taskParentId,
|
||||
variable,
|
||||
ROW_NUMBER() OVER (PARTITION BY processInstanceId ORDER BY createdAt DESC, id DESC) AS rn
|
||||
FROM wf_process_task
|
||||
WHERE taskState = 10
|
||||
) t ON t.processInstanceId = ins.id AND t.rn = 1
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or(SpecialCondolence::getHelpUserName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or(SpecialCondolence::getHelpLoginName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.andEX("year(info.createTime)", "=", year);
|
||||
cnd.andEX("info.type", "=", type);
|
||||
cnd.and("info.createdBy", "=", SecurityUtil.getUserId());
|
||||
|
||||
if (StrUtil.isNotBlank(pageForm.getPageOrderName()) && StrUtil.isNotBlank(pageForm.getPageOrderBy())) {
|
||||
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
} else {
|
||||
cnd.desc("info.createTime");
|
||||
}
|
||||
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pagination = specialcondolenceService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("导出特殊慰问申请表")
|
||||
@SaCheckPermission("special_condolence.mine")
|
||||
public void doExportApply(String id, HttpServletResponse response, Boolean print) throws Exception {
|
||||
log.info("[特殊慰问导出] 开始,applyId={}, userId={}, format={}", id, SecurityUtil.getUserId(), Boolean.TRUE.equals(print) ? "PDF" : "Word");
|
||||
SpecialCondolence specialcondolence = specialcondolenceService.loadApplication(id);
|
||||
if (specialcondolence == null) throw new BaseException("该条记录不存在");
|
||||
if (!SecurityUtil.getUserId().equals(specialcondolence.getCreatedBy())) {
|
||||
throw new BaseException("只能导出本人提交的申请");
|
||||
}
|
||||
ProcessInstance instance = specialcondolenceService.dao().fetch(ProcessInstance.class,
|
||||
Cnd.where("businessNo", "=", id).desc("id"));
|
||||
if (instance == null || !ProcessInstanceStateEnum.FINISHED.getCode().equals(instance.getState())) {
|
||||
throw new BaseException("只能导出已完成的申请");
|
||||
}
|
||||
log.info("[特殊慰问导出] 流程已完成,applyId={}, instanceId={}, state={}, applicantSignPresent={}", id, instance.getId(), instance.getState(), StrUtil.isNotBlank(specialcondolence.getSignature()));
|
||||
NutMap docData = Lang.obj2nutmap(specialcondolence);
|
||||
docData.put("payUserName", StrUtil.nullToEmpty(specialcondolence.getBankUserName()));
|
||||
SpecialCondolenceType type = specialcondolenceService.dao().fetch(SpecialCondolenceType.class, specialcondolence.getType());
|
||||
docData.put("typeName", type == null ? "" : type.getName());
|
||||
docData.put("signature", null);
|
||||
if (StrUtil.isNotBlank(specialcondolence.getSignature())) {
|
||||
PictureRenderData signature = sysOfficeTemplateUtil.createPictureRenderData(90, 40, specialcondolence.getSignature());
|
||||
if (signature == null) throw new BaseException("申请人签字图片读取失败");
|
||||
docData.put("signature", signature);
|
||||
}
|
||||
fillApplyApprovals(docData, instance);
|
||||
for (String field : List.of("signature", "secondApprovalSign", "thirdApprovalSign", "fourthApprovalSign")) {
|
||||
Object value = docData.get(field);
|
||||
log.info("[特殊慰问导出] 渲染前图片字段,applyId={}, instanceId={}, field={}, valueType={}", id, instance.getId(), field, value == null ? "null" : value.getClass().getName());
|
||||
}
|
||||
String fileName = "重庆工贸职业技术学院特殊慰问申请表_" + StrUtil.nullToEmpty(specialcondolence.getHelpUserName());
|
||||
try (InputStream input = getClass().getResourceAsStream("/templates/condolence/重庆工贸职业技术学院职工慰问申请表.doc")) {
|
||||
if (input == null) throw new BaseException("特殊慰问申请表模板不存在");
|
||||
boolean pdf = Boolean.TRUE.equals(print);
|
||||
com.aspose.words.Document template = new com.aspose.words.Document(input);
|
||||
template.getRange().replace("职工慰问", "特殊慰问", new com.aspose.words.FindReplaceOptions());
|
||||
com.aspose.words.NodeCollection rows = template.getChildNodes(com.aspose.words.NodeType.ROW, true);
|
||||
for (int i = rows.getCount() - 1; i >= 0; i--) {
|
||||
com.aspose.words.Node row = rows.get(i);
|
||||
if (row.getText().contains("firstApproval") || row.getText().contains("分工会主席")) row.remove();
|
||||
}
|
||||
java.io.ByteArrayOutputStream templateOutput = new java.io.ByteArrayOutputStream();
|
||||
template.save(templateOutput, com.aspose.words.SaveFormat.DOCX);
|
||||
byte[] bytes;
|
||||
if (specialcondolence.getRecipients() != null && !specialcondolence.getRecipients().isEmpty()) {
|
||||
com.aspose.words.Document combined = null;
|
||||
for (cn.hutool.json.JSONObject person : specialcondolence.getRecipients()) {
|
||||
NutMap personData = new NutMap();
|
||||
personData.putAll(docData);
|
||||
personData.putAll(person);
|
||||
personData.put("payUserName", person.getStr("bankUserName"));
|
||||
byte[] rendered = sysOfficeTemplateUtil.renderTemplate(new java.io.ByteArrayInputStream(templateOutput.toByteArray()), personData, null, false, "specialcondolence:" + id);
|
||||
com.aspose.words.Document part = new com.aspose.words.Document(new java.io.ByteArrayInputStream(rendered));
|
||||
if (combined == null) combined = part;
|
||||
else combined.appendDocument(part, com.aspose.words.ImportFormatMode.KEEP_SOURCE_FORMATTING);
|
||||
}
|
||||
java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();
|
||||
combined.save(output, pdf ? com.aspose.words.SaveFormat.PDF : com.aspose.words.SaveFormat.DOCX);
|
||||
bytes = output.toByteArray();
|
||||
} else {
|
||||
bytes = sysOfficeTemplateUtil.renderTemplate(new java.io.ByteArrayInputStream(templateOutput.toByteArray()), docData, null, pdf, "specialcondolence:" + id);
|
||||
}
|
||||
log.info("[特殊慰问导出] 渲染完成,applyId={}, format={}, outputBytes={}", id, pdf ? "PDF" : "Word", bytes.length);
|
||||
CommonDownloadUtil.download(fileName + (pdf ? ".pdf" : ".docx"), bytes, response);
|
||||
} catch (Exception e) {
|
||||
log.error("[特殊慰问导出] 模板渲染或文件输出失败,applyId={}, instanceId={}, format={}", id, instance.getId(), Boolean.TRUE.equals(print) ? "PDF" : "Word", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void fillApplyApprovals(NutMap docData, ProcessInstance instance) {
|
||||
String[] taskKeys = {"c438972e-2f67-4907-b229-cb7aa17da147",
|
||||
"7f560675-aeea-4254-a57e-957d03d6b786", "af363a4e-fc56-42a0-9906-8c7a2814006b"};
|
||||
String[] fields = {"secondApproval", "thirdApproval", "fourthApproval"};
|
||||
List<String> suffixes = List.of("Opinion", "Sign", "Year", "Month", "Day");
|
||||
for (String field : fields) {
|
||||
for (String suffix : suffixes) docData.put(field + suffix, "Sign".equals(suffix) ? null : "");
|
||||
}
|
||||
List<ProcessTask> tasks = specialcondolenceService.dao().query(ProcessTask.class,
|
||||
Cnd.where("processInstanceId", "=", instance.getId()).asc("id"));
|
||||
log.info("[特殊慰问导出] 查询审批记录,applyId={}, instanceId={}, taskCount={}, expectedTaskKeys={}", instance.getBusinessNo(), instance.getId(), tasks.size(), List.of(taskKeys));
|
||||
for (ProcessTask task : tasks) {
|
||||
ProcessTaskVO taskVO = flowEngine.processTaskService().findById(task.getId());
|
||||
log.info("[特殊慰问导出] 审批记录,instanceId={}, taskId={}, taskKey={}, displayName={}, state={}, finishTime={}, isFirstTaskNode={}, matchedApproval={}", instance.getId(), task.getId(), task.getTaskName(), task.getDisplayName(), task.getTaskState(), task.getFinishTime(), taskVO.getExt() == null ? null : taskVO.getExt().get("isFirstTaskNode"), List.of(taskKeys).contains(task.getTaskName()));
|
||||
if ("startTask".equals(task.getTaskName()) || (!List.of(taskKeys).contains(task.getTaskName())
|
||||
&& taskVO.getExt() != null && Boolean.TRUE.equals(taskVO.getExt().getBool("isFirstTaskNode")))) {
|
||||
log.info("[特殊慰问导出] 申请节点重置本轮审批字段,instanceId={}, taskId={}, taskKey={}", instance.getId(), task.getId(), task.getTaskName());
|
||||
// 重新申请后,仅导出本轮审批记录。
|
||||
for (String field : fields) {
|
||||
for (String suffix : suffixes) docData.put(field + suffix, "Sign".equals(suffix) ? null : "");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < taskKeys.length; i++) {
|
||||
if (!taskKeys[i].equals(task.getTaskName())) continue;
|
||||
for (String suffix : suffixes) docData.put(fields[i] + suffix, "Sign".equals(suffix) ? null : "");
|
||||
if (!ProcessTaskStateEnum.FINISHED.getCode().equals(task.getTaskState())) {
|
||||
log.info("[特殊慰问导出] 跳过未完成审批,instanceId={}, taskId={}, field={}, state={}", instance.getId(), task.getId(), fields[i], task.getTaskState());
|
||||
continue;
|
||||
}
|
||||
Dict data = taskVO.getExt() == null ? Dict.create() : taskVO.getTaskFormData();
|
||||
log.info("[特殊慰问导出] 读取审批表单,instanceId={}, taskId={}, field={}, formKeys={}, signPresent={}, signReferenceLength={}", instance.getId(), task.getId(), fields[i], data.keySet(), StrUtil.isNotBlank(data.getStr("sign")), StrUtil.nullToEmpty(data.getStr("sign")).length());
|
||||
if (StrUtil.isBlank(data.getStr("sign"))) log.warn("[特殊慰问导出] 已完成审批缺少签名字段,instanceId={}, taskId={}, field={}", instance.getId(), task.getId(), fields[i]);
|
||||
docData.put(fields[i] + "Opinion", StrUtil.nullToEmpty(data.getStr("opinion")));
|
||||
if (StrUtil.isNotBlank(data.getStr("sign"))) {
|
||||
PictureRenderData signature = sysOfficeTemplateUtil.createPictureRenderData(90, 40, data.getStr("sign"));
|
||||
if (signature == null) throw new BaseException("审批签字图片读取失败");
|
||||
docData.put(fields[i] + "Sign", signature);
|
||||
log.info("[特殊慰问导出] 审批签名填充完成,instanceId={}, taskId={}, field={}", instance.getId(), task.getId(), fields[i] + "Sign");
|
||||
}
|
||||
docData.put(fields[i] + "Year", task.getFinishTime() == null ? "" : DateUtil.format(task.getFinishTime(), "yyyy"));
|
||||
docData.put(fields[i] + "Month", task.getFinishTime() == null ? "" : DateUtil.format(task.getFinishTime(), "M"));
|
||||
docData.put(fields[i] + "Day", task.getFinishTime() == null ? "" : DateUtil.format(task.getFinishTime(), "d"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("删除")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission(value = {"special_condolence.mine", "h5.special_condolence.mine"}, mode = SaMode.OR)
|
||||
@SLog(tag = "特殊慰问系统-我的申请", msg = "删除特殊慰问")
|
||||
public Result delete(@Param("id") String id) {
|
||||
specialcondolenceService.deleteApplication(id);
|
||||
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询单个慰问申请信息")
|
||||
@SaCheckPermission(value = {"special_condolence", "h5.special_condolence"}, mode = SaMode.OR)
|
||||
public Result info(String id) {
|
||||
SpecialCondolence specialcondolence = specialcondolenceService.loadApplication(id);
|
||||
SpecialCondolenceType type = specialcondolenceService.dao().fetch(SpecialCondolenceType.class, Cnd.where(SpecialCondolenceType::getId, "=", specialcondolence.getType()));
|
||||
NutMap nutMap = Lang.obj2nutmap(specialcondolence);
|
||||
nutMap.put("typeName", type == null ? "" : type.getName());
|
||||
return Result.success(nutMap);
|
||||
}
|
||||
@Inject
|
||||
private SmsService smsService;
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.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.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.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
||||
import com.budwk.app.sys.param.SysMsgSummaryPageForm;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.dao.Cnd;
|
||||
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.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.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceReadController
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/29 16:25
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@Api(tags = "特殊慰问阅览")
|
||||
@At("/platform/special_condolence/read")
|
||||
public class SpecialCondolenceReadController {
|
||||
|
||||
@Inject
|
||||
private SpecialCondolenceService specialcondolenceService;
|
||||
|
||||
@At("")
|
||||
@SaCheckPermission("special_condolence.read")
|
||||
@Ok("beetl:/platform/zhgh/staffbenefit/specialcondolence/read/index.html")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("分页查询")
|
||||
@SaCheckPermission("special_condolence.read")
|
||||
public Result pageData(PageForm pageForm,
|
||||
@Param(value = "year") Integer year,
|
||||
@Param(value = "type") String type,
|
||||
@Param(value = "unionId") String unionId,
|
||||
@Param(value = "unitId") String unitId) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
(SELECT COUNT(*) FROM special_condolence_recipient recipient WHERE recipient.applyId = info.id AND recipient.delFlag = false) AS recipientCount,
|
||||
ins.id AS instanceId,
|
||||
type.name as typeName,
|
||||
ins.state instanceState,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.displayName taskName
|
||||
FROM
|
||||
special_condolence info
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
processInstanceId,
|
||||
displayName,
|
||||
createdAt,
|
||||
ROW_NUMBER() OVER (PARTITION BY processInstanceId ORDER BY createdAt DESC) AS rn
|
||||
FROM wf_process_task
|
||||
) t ON t.processInstanceId = ins.id AND t.rn = 1
|
||||
LEFT JOIN special_condolence_type type ON info.type = type.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
cnd.andEX("info.type", "=", type);
|
||||
cnd.andEX("YEAR(info.createTime)", "=", year);
|
||||
cnd.andEX("info.applyUnionId", "=", unionId);
|
||||
cnd.andEX("info.applyUnitId", "=", unitId);
|
||||
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or("info.helpUserName", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or("info.helpLoginName", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
|
||||
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
cnd.desc("info.createTime");
|
||||
} else {
|
||||
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pageVO = specialcondolenceService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pageVO);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("special_condolence.read")
|
||||
@ApiOperation("导出特殊慰问汇总表")
|
||||
public void onExport(PageForm pageForm,
|
||||
@Param(value = "year") Integer year,
|
||||
@Param(value = "type") String type,
|
||||
@Param(value = "unionId") String unionId,
|
||||
@Param(value = "unitId") String unitId,
|
||||
HttpServletResponse response) {
|
||||
//查询审核通过的数据
|
||||
Sql sql = Sqls.create("""
|
||||
select
|
||||
con.subject,
|
||||
case when recipient.id is null then con.helpUserName else recipient.helpUserName end as helpUserName,
|
||||
case when recipient.id is null then con.helpLoginName else recipient.helpLoginName end as helpLoginName,
|
||||
con.applyUserName,
|
||||
con.helpUnionName,
|
||||
con.helpUnitName,
|
||||
case when recipient.id is null then type.name else recipient.typeName end as typeName,
|
||||
case when recipient.id is null then con.money else recipient.money end as money,
|
||||
con.remark
|
||||
from
|
||||
special_condolence con
|
||||
left join special_condolence_recipient recipient on recipient.applyId = con.id and recipient.delFlag = false
|
||||
left join wf_process_instance ins on ins.businessNo = con.id
|
||||
left join special_condolence_type type on type.id = con.type
|
||||
$condition
|
||||
order by con.createTime desc, con.id, recipient.sortNum, recipient.id
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
cnd.andEX("con.type", "=", type);
|
||||
cnd.andEX("YEAR(con.createTime)", "=", year);
|
||||
cnd.andEX("con.applyUnionId", "=", unionId);
|
||||
cnd.andEX("con.applyUnitId", "=", unitId);
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or("con.helpUserName", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or("con.helpLoginName", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = specialcondolenceService.listMap(sql);
|
||||
|
||||
List<ExcelExportEntity> exportEntities = new ArrayList<>();
|
||||
exportEntities.add(new ExcelExportEntity("事项", "subject", 30));
|
||||
exportEntities.add(new ExcelExportEntity("慰问对象", "helpUserName", 20));
|
||||
exportEntities.add(new ExcelExportEntity("慰问对象工号", "helpLoginName", 20));
|
||||
exportEntities.add(new ExcelExportEntity("申请人", "applyUserName", 20));
|
||||
exportEntities.add(new ExcelExportEntity("所在工会", "helpUnionName", 20));
|
||||
exportEntities.add(new ExcelExportEntity("所在单位", "helpUnitName", 20));
|
||||
exportEntities.add(new ExcelExportEntity("慰问类型", "typeName", 20));
|
||||
exportEntities.add(new ExcelExportEntity("慰问金额", "money", 20));
|
||||
exportEntities.add(new ExcelExportEntity("申请理由", "remark", 20));
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, list);
|
||||
CommonDownloadUtil.download("申报人汇总.xlsx", workbook, response);
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
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.base.utils.PageUtil;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
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.dao.util.cri.Static;
|
||||
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 java.util.List;
|
||||
|
||||
/**
|
||||
* @author zhf
|
||||
* @date 2025/12/23 14:13
|
||||
* @description 校工会预审
|
||||
*/
|
||||
@IocBean
|
||||
@At("/platform/special_condolence/schoolPrincipalApproval")
|
||||
@Api("特殊慰问二级审核")
|
||||
@Ok("json:full")
|
||||
public class SpecialCondolenceSecondApprovalController {
|
||||
|
||||
@Inject
|
||||
private SpecialCondolenceService specialcondolenceService;
|
||||
|
||||
@At("/")
|
||||
@Ok("beetl:/platform/zhgh/staffbenefit/specialcondolence/schoolPrincipalApproval/index.html")
|
||||
@SaCheckPermission("special_condolence.secondApproval")
|
||||
public void index() {}
|
||||
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/staffbenefit/specialcondolence/schoolPrincipalApproval/index.html")
|
||||
@SaCheckPermission("h5.special_condolence.secondApproval")
|
||||
public void h5Index() {}
|
||||
|
||||
@At
|
||||
@ApiOperation("分页查询")
|
||||
@SaCheckPermission(value = {"special_condolence.secondApproval", "h5.special_condolence.secondApproval"}, mode = SaMode.OR)
|
||||
public Result pageData(PageForm pageForm,
|
||||
@Param(value = "year") Integer year,
|
||||
@Param(value = "type") String type,
|
||||
@Param(value = "unionId") String unionId,
|
||||
@Param(value = "unitId") String unitId,
|
||||
@Param(value = "approval") Boolean approval) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
COALESCE(recipientTotals.recipientCount, 0) AS recipientCount,
|
||||
type.name as typeName,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state instanceState,
|
||||
ins.variable instanceVariable,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.id taskId,
|
||||
t.taskName AS taskKey,
|
||||
t.displayName taskName,
|
||||
t.taskType,
|
||||
t.performType taskPerformType,
|
||||
t.taskState,
|
||||
t.finishTime,
|
||||
t.taskParentId,
|
||||
t.variable taskVariable,
|
||||
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName,
|
||||
IF(rt.id IS NOT NULL, 1, 0) AS canRevoke
|
||||
FROM
|
||||
wf_process_task t
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
LEFT JOIN special_condolence info ON info.id = ins.businessNo
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
LEFT JOIN (
|
||||
SELECT applyId, COUNT(*) AS recipientCount
|
||||
FROM special_condolence_recipient
|
||||
WHERE delFlag = false
|
||||
GROUP BY applyId
|
||||
) recipientTotals ON recipientTotals.applyId = info.id
|
||||
LEFT JOIN special_condolence_type type ON info.type = type.id
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("info.id", "is not", null);
|
||||
cnd.and(new Static("""
|
||||
NOT EXISTS (
|
||||
SELECT 1 FROM wf_process_task newer
|
||||
WHERE newer.processInstanceId = t.processInstanceId
|
||||
AND newer.taskName = t.taskName
|
||||
AND newer.id > t.id
|
||||
)
|
||||
"""));
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or(SpecialCondolence::getHelpUserName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or(SpecialCondolence::getHelpLoginName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.andEX("info.type", "=", type);
|
||||
cnd.andEX("YEAR(info.createTime)", "=", year);
|
||||
cnd.andEX("info.applyUnionId", "=", unionId);
|
||||
cnd.andEX("info.applyUnitId", "=", unitId);
|
||||
|
||||
cnd.and("t.taskName", "=", "c438972e-2f67-4907-b229-cb7aa17da147");
|
||||
// cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
|
||||
|
||||
if (approval) {
|
||||
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
cnd.desc("info.createTime");
|
||||
} else {
|
||||
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
}
|
||||
cnd.groupBy("t.id");
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pageVO = specialcondolenceService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pageVO);
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
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.base.utils.PageUtil;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
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.dao.util.cri.Static;
|
||||
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 java.util.List;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceThirdApprovalController
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/29 16:23
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@IocBean
|
||||
@At("/platform/special_condolence/schoolUnionApproval")
|
||||
@Api("特殊慰问三级审核")
|
||||
@Ok("json:full")
|
||||
public class SpecialCondolenceThirdApprovalController {
|
||||
|
||||
@Inject
|
||||
private SpecialCondolenceService specialcondolenceService;
|
||||
|
||||
@At("/")
|
||||
@Ok("beetl:/platform/zhgh/staffbenefit/specialcondolence/schoolUnionApproval/index.html")
|
||||
@SaCheckPermission("special_condolence.thirdApproval")
|
||||
public void index() {}
|
||||
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/staffbenefit/specialcondolence/schoolUnionApproval/index.html")
|
||||
@SaCheckPermission("h5.special_condolence.thirdApproval")
|
||||
public void h5Index() {}
|
||||
|
||||
@At
|
||||
@ApiOperation("分页查询")
|
||||
@SaCheckPermission(value = {"special_condolence.thirdApproval", "h5.special_condolence.thirdApproval"}, mode = SaMode.OR)
|
||||
public Result pageData(PageForm pageForm,
|
||||
@Param(value = "year") Integer year,
|
||||
@Param(value = "type") String type,
|
||||
@Param(value = "unionId") String unionId,
|
||||
@Param(value = "unitId") String unitId,
|
||||
@Param(value = "approval") Boolean approval) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
COALESCE(recipientTotals.recipientCount, 0) AS recipientCount,
|
||||
type.name as typeName,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state instanceState,
|
||||
ins.variable instanceVariable,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.id taskId,
|
||||
t.taskName AS taskKey,
|
||||
t.displayName taskName,
|
||||
t.taskType,
|
||||
t.performType taskPerformType,
|
||||
t.taskState,
|
||||
t.finishTime,
|
||||
t.taskParentId,
|
||||
t.variable taskVariable,
|
||||
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName,
|
||||
IF(rt.id IS NOT NULL, 1, 0) AS canRevoke
|
||||
FROM
|
||||
wf_process_task t
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
LEFT JOIN special_condolence info ON info.id = ins.businessNo
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
LEFT JOIN (
|
||||
SELECT applyId, COUNT(*) AS recipientCount
|
||||
FROM special_condolence_recipient
|
||||
WHERE delFlag = false
|
||||
GROUP BY applyId
|
||||
) recipientTotals ON recipientTotals.applyId = info.id
|
||||
LEFT JOIN special_condolence_type type ON info.type = type.id
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("info.id", "is not", null);
|
||||
cnd.and(new Static("""
|
||||
NOT EXISTS (
|
||||
SELECT 1 FROM wf_process_task newer
|
||||
WHERE newer.processInstanceId = t.processInstanceId
|
||||
AND newer.taskName = t.taskName
|
||||
AND newer.id > t.id
|
||||
)
|
||||
"""));
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or(SpecialCondolence::getHelpUserName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or(SpecialCondolence::getHelpLoginName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.andEX("info.type", "=", type);
|
||||
cnd.andEX("YEAR(info.createTime)", "=", year);
|
||||
cnd.andEX("info.applyUnionId", "=", unionId);
|
||||
cnd.andEX("info.applyUnitId", "=", unitId);
|
||||
|
||||
cnd.and("t.taskName", "=", "7f560675-aeea-4254-a57e-957d03d6b786");
|
||||
if (!"superadmin".equals(SecurityUtil.getUserLoginname())) {
|
||||
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
|
||||
}
|
||||
|
||||
if (approval) {
|
||||
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
cnd.desc("info.createTime");
|
||||
} else {
|
||||
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
}
|
||||
cnd.groupBy("t.id");
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pageVO = specialcondolenceService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pageVO);
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolenceType;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceTypeService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceTypeController
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/29 10:00
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@Api(tags = "特殊慰问类型")
|
||||
@At("/platform/special_condolence/type")
|
||||
public class SpecialCondolenceTypeController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private SpecialCondolenceTypeService typeService;
|
||||
|
||||
@At("")
|
||||
@SaCheckPermission("special_condolence.type")
|
||||
@Ok("beetl:/platform/zhgh/staffbenefit/specialcondolence/type/index.html")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("分页查询")
|
||||
@SaCheckPermission("special_condolence.type")
|
||||
public Result pageData(PageForm pageForm) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or(SpecialCondolenceType::getName, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or(SpecialCondolenceType::getCode, "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.asc("code");
|
||||
Pagination pagination = typeService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("新增/修改特殊慰问类型")
|
||||
@SaCheckPermission("special_condolence.type")
|
||||
@SLog(tag = "特殊慰问系统-慰问类型", msg = "新增/修改特殊慰问类型")
|
||||
public Object onSubmit(SpecialCondolenceType type) {
|
||||
typeService.insertOrUpdate(type);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("删除特殊慰问类型")
|
||||
@SaCheckPermission("special_condolence.type")
|
||||
@SLog(tag = "特殊慰问系统-慰问类型", msg = "删除特殊慰问类型")
|
||||
public Object onDelete(String id) {
|
||||
typeService.delete(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询特殊慰问类型")
|
||||
@SaCheckLogin
|
||||
public Result querySpecialCondolenceType() {
|
||||
List<SpecialCondolenceType> list = typeService.query(Cnd.where(SpecialCondolenceType::getEnable, "=", true).asc(SpecialCondolenceType::getCode));
|
||||
return Result.success(list);
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.interceptor;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.flow.constant.FlowConst;
|
||||
import com.budwk.app.flow.engine.FlowInterceptor;
|
||||
import com.budwk.app.flow.engine.core.Execution;
|
||||
import com.budwk.app.flow.engine.core.ServiceContext;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.zhgh.dayofficework.article.models.Article;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.json.Json;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceSaveInterceptor
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/28 11:01
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
public class SpecialCondolenceSaveInterceptor implements FlowInterceptor {
|
||||
|
||||
@Override
|
||||
public void intercept(Execution execution) {
|
||||
if (!com.budwk.app.web.commons.auth.utils.AuthUtil.hasRoleOr("BRANCH_UNION_CHAIRMAN")) {
|
||||
throw new com.budwk.app.base.exception.BaseException("仅分工会主席可发起特殊慰问申请");
|
||||
}
|
||||
String formDataStr = execution.getArgs().getStr(FlowConst.FORM_DATA);
|
||||
SpecialCondolence specialcondolence = Json.fromJson(SpecialCondolence.class, formDataStr);
|
||||
|
||||
if(StrUtil.isBlank(specialcondolence.getId())) specialcondolence.setCreateTime(DateUtil.now());
|
||||
|
||||
Dao dao = ServiceContext.find(Dao.class);
|
||||
ServiceContext.find(com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceService.class).saveApplication(specialcondolence);
|
||||
|
||||
// 设置流程变量
|
||||
execution.getArgs().set("origin", specialcondolence.getOrigin());
|
||||
|
||||
int instanceId = execution.getArgs().getInt(FlowConst.PROCESS_INSTANCE_ID_KEY);
|
||||
dao.update(ProcessInstance.class, Chain.make("businessNo", specialcondolence.getId()), Cnd.where(ProcessInstance::getId, "=", instanceId));
|
||||
|
||||
}
|
||||
}
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.model;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import com.budwk.app.sys.models.Sys_file;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolence
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/28 11:03
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("special_condolence")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("特殊慰问")
|
||||
public class SpecialCondolence extends BaseModel {
|
||||
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("事项")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 200)
|
||||
private String subject;
|
||||
|
||||
|
||||
@Column
|
||||
@Comment("申请人ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String applyUserId;
|
||||
|
||||
@Column
|
||||
@Comment("申请人姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String applyUserName;
|
||||
|
||||
@Column
|
||||
@Comment("申请人工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 120)
|
||||
private String applyLoginName;
|
||||
|
||||
@Column
|
||||
@Comment("申请人分工会ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String applyUnionId;
|
||||
|
||||
@Column
|
||||
@Comment("申请人分工会")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String applyUnionName;
|
||||
|
||||
@Column
|
||||
@Comment("申请人单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String applyUnitId;
|
||||
|
||||
@Column
|
||||
@Comment("申请人单位")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String applyUnitName;
|
||||
|
||||
@Column
|
||||
@Comment("补助人ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String helpUserId;
|
||||
|
||||
@Column
|
||||
@Comment("补助人姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String helpUserName;
|
||||
|
||||
@Column
|
||||
@Comment("补助人工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 120)
|
||||
private String helpLoginName;
|
||||
|
||||
@Column
|
||||
@Comment("补助人分工会ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String helpUnionId;
|
||||
|
||||
@Column
|
||||
@Comment("补助人分工会")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String helpUnionName;
|
||||
|
||||
@Column
|
||||
@Comment("补助人单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String helpUnitId;
|
||||
|
||||
@Column
|
||||
@Comment("补助人单位")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String helpUnitName;
|
||||
|
||||
@Column
|
||||
@Comment("类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String origin;
|
||||
|
||||
@Column
|
||||
@Comment("收款人")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String payUserId;
|
||||
|
||||
@Column
|
||||
@Comment("收款人姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String payUserName;
|
||||
|
||||
@Column
|
||||
@Comment("收款人工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String payLoginName;
|
||||
|
||||
@Column
|
||||
@Comment("慰问类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String type;
|
||||
|
||||
@Column
|
||||
@Comment("慰问编码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String typeCode;
|
||||
|
||||
@Column
|
||||
@Comment("慰问方式")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String way;
|
||||
|
||||
@Column
|
||||
@Comment("统一每人慰问金额")
|
||||
@ColDefine(customType = "decimal(10,2)")
|
||||
private java.math.BigDecimal perMoney;
|
||||
|
||||
@Column
|
||||
@Comment("慰问金额")
|
||||
@ColDefine(type = ColType.FLOAT, width = 10, precision = 2)
|
||||
private Double money;
|
||||
|
||||
@Column
|
||||
@Comment("发生时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String occurTime;
|
||||
|
||||
@Column
|
||||
@Comment("生育几孩(孩次)")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 10)
|
||||
private String child;
|
||||
|
||||
@Column
|
||||
@Comment("所在医院")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String hospital;
|
||||
|
||||
@Column
|
||||
@Comment("病房床号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String hospitalHouseNum;
|
||||
|
||||
@Column
|
||||
@Comment("病房床号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String hospitalHouseBedNum;
|
||||
|
||||
@Column
|
||||
@Comment("住院病由")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String hospitalBy;
|
||||
|
||||
@Column
|
||||
@Comment("备注")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String remark;
|
||||
|
||||
@Column
|
||||
@Comment("附件")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<JSONObject> files;
|
||||
|
||||
@Comment("签字")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 255)
|
||||
@Column(hump = true)
|
||||
private String signature;
|
||||
|
||||
@Column
|
||||
@Comment("申请时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String createTime;
|
||||
|
||||
|
||||
|
||||
|
||||
@Column
|
||||
@Comment("户名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String bankUserName;
|
||||
|
||||
@Column
|
||||
@Comment("银行账号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String bankCardNumber;
|
||||
|
||||
@Column
|
||||
@Comment("开户行")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String bankOfDeposit;
|
||||
|
||||
@Column
|
||||
@Comment("证明人ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String certifierUserId;
|
||||
|
||||
@Column
|
||||
@Comment("证明人姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String certifierUserName;
|
||||
|
||||
@Column
|
||||
@Comment("证明人工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 120)
|
||||
private String certifierLoginName;
|
||||
|
||||
@Column
|
||||
@Comment("证明人分工会ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String certifierUnionId;
|
||||
|
||||
@Column
|
||||
@Comment("证明人分工会")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String certifierUnionName;
|
||||
|
||||
@Column
|
||||
@Comment("证明人单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String certifierUnitId;
|
||||
|
||||
@Column
|
||||
@Comment("证明人单位")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String certifierUnitName;
|
||||
|
||||
@Column
|
||||
@Comment("经办人ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String handlerUserId;
|
||||
|
||||
@Column
|
||||
@Comment("经办人姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String handlerUserName;
|
||||
|
||||
@Column
|
||||
@Comment("经办人工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 120)
|
||||
private String handlerLoginName;
|
||||
|
||||
@Column
|
||||
@Comment("经办人分工会ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String handlerUnionId;
|
||||
|
||||
@Column
|
||||
@Comment("经办人分工会")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String handlerUnionName;
|
||||
|
||||
@Column
|
||||
@Comment("经办人单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String handlerUnitId;
|
||||
|
||||
@Column
|
||||
@Comment("经办人单位")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String handlerUnitName;
|
||||
|
||||
@Column
|
||||
@Comment("入院时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String hospitalizationTime;
|
||||
|
||||
@Column
|
||||
@Comment("出院时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String leaveHospitalTime;
|
||||
|
||||
@Column
|
||||
@Comment("当年第几次住院")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer thisYearHospitalizationNum;
|
||||
|
||||
@Comment("慰问对象明细,由明细表加载")
|
||||
private List<JSONObject> recipients;
|
||||
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.model;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("special_condolence_recipient")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@TableIndexes({@Index(name = "idx_special_condolence_recipient_apply", fields = {"applyId"}, unique = false)})
|
||||
@Comment("特殊慰问被申请人明细")
|
||||
public class SpecialCondolenceRecipient extends BaseModel {
|
||||
@Name
|
||||
@PrevInsert(uu32 = true)
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("申请ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String applyId;
|
||||
|
||||
@Column
|
||||
@Comment("慰问对象用户ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String helpUserId;
|
||||
|
||||
@Column
|
||||
@Comment("人员类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String personType;
|
||||
|
||||
@Column
|
||||
@Comment("慰问对象工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 120)
|
||||
private String helpLoginName;
|
||||
|
||||
@Column
|
||||
@Comment("慰问对象")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String helpUserName;
|
||||
|
||||
@Column
|
||||
@Comment("慰问类型ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String type;
|
||||
|
||||
@Column
|
||||
@Comment("慰问类型名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String typeName;
|
||||
|
||||
@Column
|
||||
@Comment("慰问类型编码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String typeCode;
|
||||
|
||||
@Column
|
||||
@Comment("慰问方式")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String way;
|
||||
|
||||
@Column
|
||||
@Comment("身份证号码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 18)
|
||||
private String idCard;
|
||||
|
||||
@Column
|
||||
@Comment("开户行")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String bankOfDeposit;
|
||||
|
||||
@Column
|
||||
@Comment("户名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String bankUserName;
|
||||
|
||||
@Column
|
||||
@Comment("收款账户")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String bankCardNumber;
|
||||
|
||||
@Column
|
||||
@Comment("慰问金额")
|
||||
@ColDefine(customType = "decimal(10,2)")
|
||||
private java.math.BigDecimal money;
|
||||
|
||||
@Column
|
||||
@Comment("慰问时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String occurTime;
|
||||
|
||||
@Column
|
||||
@Comment("行序号")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer sortNum;
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.model;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.DB;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceType
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/29 9:59
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("special_condolence_type")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("特殊慰问类型")
|
||||
public class SpecialCondolenceType extends BaseModel {
|
||||
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("编码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String code;
|
||||
|
||||
@Column
|
||||
@Comment("名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String name;
|
||||
|
||||
@Column
|
||||
@Comment("费用")
|
||||
@ColDefine(type = ColType.FLOAT, width = 10, precision = 2)
|
||||
private Double money;
|
||||
|
||||
@Column
|
||||
@Comment("慰问方式")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String way;
|
||||
|
||||
@Column
|
||||
@Comment("是否启用")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("1")
|
||||
private Boolean enable;
|
||||
|
||||
@Column
|
||||
@Comment("是否上传附件")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean isUploadFile;
|
||||
|
||||
@Column
|
||||
@Comment("上传附件说明")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 50)
|
||||
private String uploadFileDesc;
|
||||
|
||||
@Column
|
||||
@Comment("排序字段")
|
||||
@Prev({
|
||||
@SQL(db = DB.MYSQL, value = "SELECT IFNULL(MAX(location),0)+1 FROM special_condolence_type"),
|
||||
@SQL(db = DB.ORACLE, value = "SELECT COALESCE(MAX(location),0)+1 FROM special_condolence_type")
|
||||
})
|
||||
private Integer location;
|
||||
|
||||
@Column
|
||||
@Comment("排序")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private int sortNum;
|
||||
|
||||
@Column
|
||||
@Comment("上传会议纪要")
|
||||
@Default("0")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean uploadMeetingRecord;
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.service;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolenceType;
|
||||
import org.nutz.dao.Dao;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public final class SpecialCondolenceRecipients {
|
||||
private SpecialCondolenceRecipients() {}
|
||||
|
||||
public static void prepare(SpecialCondolence info, Dao dao) {
|
||||
if (info.getSubject() == null || info.getSubject().trim().isEmpty() || info.getSubject().trim().length() > 200) throw new BaseException("请填写200字以内的事项");
|
||||
info.setSubject(info.getSubject().trim());
|
||||
List<JSONObject> rows = info.getRecipients();
|
||||
if (rows == null || rows.isEmpty()) throw new BaseException("请添加慰问对象");
|
||||
if (rows.size() > 1000) throw new BaseException("每次申请最多1000人");
|
||||
if (info.getFiles() != null && info.getFiles().size() > 5) throw new BaseException("最多上传5个附件");
|
||||
if (info.getRemark() != null && info.getRemark().trim().length() > 500) throw new BaseException("申请事由最多500字");
|
||||
if (info.getPerMoney() == null || info.getPerMoney().signum() <= 0 || info.getPerMoney().compareTo(new BigDecimal("10000")) > 0 || info.getPerMoney().scale() > 2) throw new BaseException("每人慰问金额须大于0且不超过10000元,最多两位小数");
|
||||
try { LocalDate.parse(info.getOccurTime()); }
|
||||
catch (Exception e) { throw new BaseException("请选择有效的统一慰问时间"); }
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
JSONObject row = rows.get(i);
|
||||
String prefix = "第" + (i + 1) + "行:";
|
||||
if (row == null) throw new BaseException(prefix + "明细不能为空");
|
||||
row.set("type", info.getType());
|
||||
row.set("money", info.getPerMoney().toPlainString());
|
||||
row.set("occurTime", info.getOccurTime());
|
||||
if (cn.hutool.core.util.StrUtil.isNotBlank(row.getStr("helpUserId"))) resolveUser(row, dao, prefix);
|
||||
String[] fields = {"helpUserName", "type", "idCard", "bankOfDeposit", "bankUserName", "bankCardNumber", "money", "occurTime"};
|
||||
for (String field : fields) {
|
||||
String value = row.getStr(field);
|
||||
if (value == null || value.trim().isEmpty()) throw new BaseException(prefix + "请填写完整的慰问对象信息");
|
||||
row.set(field, value.trim());
|
||||
}
|
||||
if (!validIdCard(row.getStr("idCard"))) throw new BaseException(prefix + "身份证号码不正确");
|
||||
if (!row.getStr("bankCardNumber").matches("[0-9]{6,30}")) throw new BaseException(prefix + "收款账户须为6至30位数字");
|
||||
if (row.getStr("helpUserName").length() > 100 || row.getStr("bankOfDeposit").length() > 100 || row.getStr("bankUserName").length() > 100) throw new BaseException(prefix + "姓名、开户行、户名不能超过100字");
|
||||
if (!row.getStr("money").matches("[0-9]+(\\.[0-9]{1,2})?")) throw new BaseException(prefix + "金额最多两位小数");
|
||||
BigDecimal money = new BigDecimal(row.getStr("money"));
|
||||
if (money.signum() <= 0 || money.compareTo(new BigDecimal("10000")) > 0) throw new BaseException(prefix + "金额须大于0且不超过10000元");
|
||||
try { LocalDate.parse(row.getStr("occurTime")); }
|
||||
catch (Exception e) { throw new BaseException(prefix + "慰问时间格式须为yyyy-MM-dd"); }
|
||||
SpecialCondolenceType type = dao.fetch(SpecialCondolenceType.class, row.getStr("type"));
|
||||
if (type == null || !Boolean.TRUE.equals(type.getEnable())) throw new BaseException(prefix + "慰问类型不存在或已停用");
|
||||
row.set("typeName", type.getName());
|
||||
row.set("typeCode", type.getCode());
|
||||
row.set("way", type.getWay());
|
||||
if (Boolean.TRUE.equals(type.getIsUploadFile()) && (info.getFiles() == null || info.getFiles().isEmpty())) throw new BaseException(prefix + "该慰问类型需要上传附件");
|
||||
total = total.add(money);
|
||||
}
|
||||
JSONObject first = rows.get(0);
|
||||
String name = first.getStr("helpUserName");
|
||||
info.setHelpUserName((rows.size() > 1 && name.length() > 90 ? name.substring(0, 90) : name) + (rows.size() > 1 ? "等" + rows.size() + "人" : ""));
|
||||
info.setHelpUnitId(info.getApplyUnitId());
|
||||
info.setHelpUnitName(info.getApplyUnitName());
|
||||
info.setHelpUnionId(info.getApplyUnionId());
|
||||
info.setHelpUnionName(info.getApplyUnionName());
|
||||
info.setType(first.getStr("type"));
|
||||
info.setTypeCode(first.getStr("typeCode"));
|
||||
info.setWay(first.getStr("way"));
|
||||
info.setOccurTime(first.getStr("occurTime"));
|
||||
info.setMoney(total.doubleValue());
|
||||
info.setBankOfDeposit(first.getStr("bankOfDeposit"));
|
||||
info.setBankUserName(first.getStr("bankUserName"));
|
||||
info.setBankCardNumber(first.getStr("bankCardNumber"));
|
||||
}
|
||||
|
||||
public static void resolveUser(JSONObject row, Dao dao, String prefix) {
|
||||
String userId = row.getStr("helpUserId");
|
||||
com.budwk.app.sys.views.View_user user;
|
||||
String unionId = com.budwk.app.web.commons.auth.utils.SecurityUtil.getUnionId();
|
||||
if (cn.hutool.core.util.StrUtil.isBlank(unionId)) throw new BaseException(prefix + "申请人未关联分工会");
|
||||
if (cn.hutool.core.util.StrUtil.isNotBlank(userId)) {
|
||||
user = dao.fetch(com.budwk.app.sys.views.View_user.class, org.nutz.dao.Cnd.where("id", "=", userId));
|
||||
} else {
|
||||
java.util.List<com.budwk.app.sys.views.View_user> users = dao.query(com.budwk.app.sys.views.View_user.class,
|
||||
org.nutz.dao.Cnd.where("idCard", "=", row.getStr("idCard"))
|
||||
.and("username", "=", row.getStr("helpUserName")));
|
||||
if (users.isEmpty()) throw new BaseException(prefix + "姓名、身份证与人员档案不匹配");
|
||||
java.util.List<com.budwk.app.sys.views.View_user> members = users.stream()
|
||||
.filter(member -> java.util.Objects.equals(member.getUnionId(), unionId))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
if (members.isEmpty()) throw new BaseException(prefix + "只能选择申请人本工会人员");
|
||||
String loginName = row.getStr("helpLoginName");
|
||||
if (cn.hutool.core.util.StrUtil.isNotBlank(loginName)) {
|
||||
members = members.stream().filter(member -> "劳务派遣人员".equals(member.getPersonType())
|
||||
|| java.util.Objects.equals(member.getLoginname(), loginName))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
if (members.isEmpty()) throw new BaseException(prefix + "工号与慰问对象不一致");
|
||||
if (members.size() > 1) throw new BaseException(prefix + "人员身份重复,请核对档案");
|
||||
user = members.get(0);
|
||||
}
|
||||
if (user == null) throw new BaseException(prefix + "慰问对象不存在");
|
||||
if (!java.util.Objects.equals(user.getUnionId(), unionId)) throw new BaseException(prefix + "只能选择申请人本工会人员");
|
||||
if (!"劳务派遣人员".equals(user.getPersonType()) && cn.hutool.core.util.StrUtil.isBlank(row.getStr("helpLoginName"))) throw new BaseException(prefix + "普通人员必须填写工号");
|
||||
if (!"劳务派遣人员".equals(user.getPersonType()) && (cn.hutool.core.util.StrUtil.isBlank(row.getStr("helpLoginName")) || !java.util.Objects.equals(user.getLoginname(), row.getStr("helpLoginName")))) throw new BaseException(prefix + "工号与慰问对象不一致");
|
||||
if (cn.hutool.core.util.StrUtil.isNotBlank(user.getIdCard()) && !user.getIdCard().equalsIgnoreCase(row.getStr("idCard"))) throw new BaseException(prefix + "身份证号码与所选人员不一致");
|
||||
row.set("helpUserId", user.getId());
|
||||
row.set("helpLoginName", user.getLoginname());
|
||||
row.set("personType", user.getPersonType());
|
||||
row.set("helpUserName", user.getUsername());
|
||||
}
|
||||
|
||||
public static boolean validIdCard(String value) {
|
||||
if (value == null || !value.matches("[1-9][0-9]{16}[0-9Xx]")) return false;
|
||||
try { LocalDate.parse(value.substring(6, 14), java.time.format.DateTimeFormatter.BASIC_ISO_DATE); }
|
||||
catch (Exception e) { return false; }
|
||||
int[] weights = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
|
||||
int sum = 0;
|
||||
for (int i = 0; i < weights.length; i++) sum += (value.charAt(i) - '0') * weights[i];
|
||||
return "10X98765432".charAt(sum % 11) == Character.toUpperCase(value.charAt(17));
|
||||
}
|
||||
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceService
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/29 11:21
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
public interface SpecialCondolenceService extends BaseService<SpecialCondolence> {
|
||||
void saveApplication(SpecialCondolence info);
|
||||
void saveDraftApplication(SpecialCondolence info);
|
||||
SpecialCondolence loadApplication(String id);
|
||||
void deleteApplication(String id);
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolenceType;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceTypeService
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/29 10:03
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
public interface SpecialCondolenceTypeService extends BaseService<SpecialCondolenceType> {
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolenceRecipient;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceRecipients;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolence;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceServiceImpl
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/29 11:22
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class SpecialCondolenceServiceImpl extends BaseServiceImpl<SpecialCondolence> implements SpecialCondolenceService {
|
||||
|
||||
public SpecialCondolenceServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void saveApplication(SpecialCondolence info) {
|
||||
SpecialCondolenceRecipients.prepare(info, dao());
|
||||
persistApplication(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void saveDraftApplication(SpecialCondolence info) {
|
||||
if (info.getRecipients() == null) info.setRecipients(new ArrayList<>());
|
||||
info.getRecipients().removeIf(row -> row == null || java.util.stream.Stream.of(
|
||||
"helpUserId", "helpUserName", "helpLoginName", "idCard", "bankOfDeposit", "bankUserName", "bankCardNumber")
|
||||
.allMatch(field -> StrUtil.isBlank(row.getStr(field))));
|
||||
for (JSONObject row : info.getRecipients()) {
|
||||
row.set("type", info.getType());
|
||||
row.set("typeCode", info.getTypeCode());
|
||||
row.set("way", info.getWay());
|
||||
row.set("money", info.getPerMoney());
|
||||
row.set("occurTime", info.getOccurTime());
|
||||
}
|
||||
info.setMoney(info.getPerMoney() == null ? null : info.getPerMoney().multiply(BigDecimal.valueOf(info.getRecipients().size())).doubleValue());
|
||||
persistApplication(info);
|
||||
}
|
||||
|
||||
private void persistApplication(SpecialCondolence info) {
|
||||
dao().insertOrUpdate(info);
|
||||
dao().clear(SpecialCondolenceRecipient.class,
|
||||
Cnd.where("applyId", "=", info.getId()));
|
||||
int sort = 0;
|
||||
for (JSONObject row : info.getRecipients()) {
|
||||
SpecialCondolenceRecipient person = new SpecialCondolenceRecipient();
|
||||
person.setApplyId(info.getId());
|
||||
person.setHelpUserName(row.getStr("helpUserName", ""));
|
||||
person.setHelpUserId(row.getStr("helpUserId", ""));
|
||||
person.setHelpLoginName(row.getStr("helpLoginName"));
|
||||
person.setPersonType(row.getStr("personType"));
|
||||
person.setType(row.getStr("type", ""));
|
||||
person.setTypeName(row.getStr("typeName"));
|
||||
person.setTypeCode(row.getStr("typeCode"));
|
||||
person.setWay(row.getStr("way"));
|
||||
person.setIdCard(row.getStr("idCard", ""));
|
||||
person.setBankOfDeposit(row.getStr("bankOfDeposit", ""));
|
||||
person.setBankUserName(row.getStr("bankUserName", ""));
|
||||
person.setBankCardNumber(row.getStr("bankCardNumber", ""));
|
||||
person.setMoney(row.getBigDecimal("money", BigDecimal.ZERO));
|
||||
person.setOccurTime(row.getStr("occurTime", ""));
|
||||
person.setSortNum(sort++);
|
||||
dao().insert(person);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpecialCondolence loadApplication(String id) {
|
||||
SpecialCondolence info = fetch(id);
|
||||
if (info != null) {
|
||||
List<JSONObject> rows = new ArrayList<>();
|
||||
for (SpecialCondolenceRecipient person : dao().query(
|
||||
SpecialCondolenceRecipient.class,
|
||||
Cnd.where("applyId", "=", id).asc("sortNum").asc("id"))) {
|
||||
rows.add(JSONUtil.parseObj(person));
|
||||
}
|
||||
info.setRecipients(rows);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void deleteApplication(String id) {
|
||||
dao().clear(SpecialCondolenceRecipient.class,
|
||||
Cnd.where("applyId", "=", id));
|
||||
delete(id);
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.budwk.app.zhgh.staffbenefit.specialcondolence.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.model.SpecialCondolenceType;
|
||||
import com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceTypeService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
/**
|
||||
* @ClassName SpecialCondolenceTypeServiceImpl
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/7/29 10:03
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class SpecialCondolenceTypeServiceImpl extends BaseServiceImpl<SpecialCondolenceType> implements SpecialCondolenceTypeService {
|
||||
|
||||
public SpecialCondolenceTypeServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
+7
-3
@@ -20,6 +20,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.web.commons.base.Globals;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberApplyRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberCommonService;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberApplyRecordService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -54,6 +55,8 @@ public class MemberApplyController {
|
||||
private FlowCommonService flowCommonService;
|
||||
@Inject
|
||||
private MemberCommonService memberCommonService;
|
||||
@Inject
|
||||
private MemberApplyRecordService memberApplyRecordService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/member/apply/submit/index.html")
|
||||
@@ -70,13 +73,14 @@ public class MemberApplyController {
|
||||
|
||||
@At
|
||||
@ApiOperation("保存申请")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("member.apply.submit")
|
||||
@SLog(tag = "会员入会申请", msg = "保存申请,申请人: ${args[0].username}")
|
||||
public Result save(@Param("data") MemberApplyRecord memberApplyRecord) {
|
||||
if (StrUtil.isBlank(memberApplyRecord.getId())) {
|
||||
memberCommonService.validateApplyOrChangeIsDoing(null, "apply");
|
||||
}
|
||||
dao.insertOrUpdate(memberApplyRecord);
|
||||
memberApplyRecordService.saveApplyRecord(memberApplyRecord);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@@ -105,7 +109,7 @@ public class MemberApplyController {
|
||||
.and(new Static("JSON_CONTAINS(unitIds, '\"%s\"')".formatted(SecurityUtil.getUnitId()))));
|
||||
args.set("type", Lang.isEmpty(group) ? "branch" : "group");
|
||||
|
||||
dao.insertOrUpdate(memberApplyRecord);
|
||||
memberApplyRecordService.saveApplyRecord(memberApplyRecord);
|
||||
|
||||
args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode());
|
||||
args.set(FlowConst.FORM_DATA, memberApplyRecord);
|
||||
@@ -143,7 +147,7 @@ public class MemberApplyController {
|
||||
.and(new Static("JSON_CONTAINS(unitIds, '\"%s\"')".formatted(SecurityUtil.getUnitId()))));
|
||||
dict.set("type", Lang.isEmpty(group) ? "branch" : "group");
|
||||
|
||||
dao.insertOrUpdate(memberApplyRecord);
|
||||
memberApplyRecordService.saveApplyRecord(memberApplyRecord);
|
||||
|
||||
dict.set(FlowConst.PROCESS_TASK_ID_KEY, taskId);
|
||||
dict.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode());
|
||||
|
||||
+4
-2
@@ -64,7 +64,7 @@ public class MemberBranchUnionAuditController {
|
||||
info.sex,
|
||||
info.userName,
|
||||
info.loginName,
|
||||
info.unitName,
|
||||
COALESCE(changeUnit.name, info.unitName) AS unitName,
|
||||
info.unionName,
|
||||
info.userState,
|
||||
info.preparedBy,
|
||||
@@ -91,6 +91,7 @@ public class MemberBranchUnionAuditController {
|
||||
wf_process_task t
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
LEFT JOIN member_change_record info ON info.id = ins.businessNo
|
||||
LEFT JOIN sys_unit changeUnit ON changeUnit.id = info.unitId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
|
||||
@@ -111,7 +112,8 @@ public class MemberBranchUnionAuditController {
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
cnd.desc("info.applyDateTime");
|
||||
} else {
|
||||
cnd.orderBy("info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
// 单位列按实际显示名称排序,其他字段保持原有排序规则。
|
||||
cnd.orderBy("unitName".equals(pageForm.getPageOrderName()) ? "unitName" : "info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
}
|
||||
cnd.groupBy("t.id");
|
||||
sql.setCondition(cnd);
|
||||
|
||||
+2
-1
@@ -72,6 +72,7 @@ public class MemberChangeApplyController {
|
||||
|
||||
@At
|
||||
@ApiOperation("保存申请")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("member.apply.submit")
|
||||
@SLog(tag = "会员变更申请", msg = "保存申请,申请人: ${args[0].username}")
|
||||
public Result save(@Param("data") MemberChangeRecord memberChangeRecord) {
|
||||
@@ -147,7 +148,7 @@ public class MemberChangeApplyController {
|
||||
@ApiModelProperty("根据id查询变更记录")
|
||||
@SaCheckPermission("member.change.apply")
|
||||
public Result findChangeById(String id){
|
||||
return Result.success().addData(dao.fetch(MemberChangeRecord.class, id));
|
||||
return Result.success().addData(memberCommonService.findChangeRecordWithUnitName(id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-3
@@ -76,7 +76,7 @@ public class MemberChangeMineController {
|
||||
info.username,
|
||||
info.loginname,
|
||||
info.sex,
|
||||
info.unitName,
|
||||
COALESCE(changeUnit.name, info.unitName) AS unitName,
|
||||
info.unionName,
|
||||
info.userState,
|
||||
info.preparedBy,
|
||||
@@ -103,6 +103,7 @@ public class MemberChangeMineController {
|
||||
member_change_record info
|
||||
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
|
||||
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
|
||||
LEFT JOIN sys_unit changeUnit ON changeUnit.id = info.unitId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
$condition
|
||||
""");
|
||||
@@ -124,7 +125,8 @@ public class MemberChangeMineController {
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
cnd.desc("info.applyDateTime");
|
||||
} else {
|
||||
cnd.orderBy("info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
// 单位列按实际显示名称排序,其他字段保持原有排序规则。
|
||||
cnd.orderBy("unitName".equals(pageForm.getPageOrderName()) ? "unitName" : "info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
}
|
||||
cnd.groupBy("t.id");
|
||||
sql.setCondition(cnd);
|
||||
@@ -166,7 +168,7 @@ public class MemberChangeMineController {
|
||||
@ApiOperation("根据id查询变更记录")
|
||||
@SaCheckPermission(value = {"member.change.mine", "member.change.unionGroup.audit"}, mode = SaMode.OR)
|
||||
public Result findMemberChangeRecord(String id){
|
||||
MemberChangeRecord record = dao.fetch(MemberChangeRecord.class, id);
|
||||
MemberChangeRecord record = memberCommonService.findChangeRecordWithUnitName(id);
|
||||
NutMap nutMap = Lang.obj2nutmap(record);
|
||||
|
||||
// 变更记录只保存三级单位 ID,查看时补充当前单位名称供页面展示。
|
||||
|
||||
+4
-2
@@ -92,7 +92,7 @@ public class MemberSchoolUnionAuditController {
|
||||
info.sex,
|
||||
info.userName,
|
||||
info.loginName,
|
||||
info.unitName,
|
||||
COALESCE(changeUnit.name, info.unitName) AS unitName,
|
||||
info.unionName,
|
||||
info.userState,
|
||||
info.preparedBy,
|
||||
@@ -119,6 +119,7 @@ public class MemberSchoolUnionAuditController {
|
||||
wf_process_task t
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
LEFT JOIN member_change_record info ON info.id = ins.businessNo
|
||||
LEFT JOIN sys_unit changeUnit ON changeUnit.id = info.unitId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
|
||||
@@ -139,7 +140,8 @@ public class MemberSchoolUnionAuditController {
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
cnd.desc("info.applyDateTime");
|
||||
} else {
|
||||
cnd.orderBy("info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
// 单位列按实际显示名称排序,其他字段保持原有排序规则。
|
||||
cnd.orderBy("unitName".equals(pageForm.getPageOrderName()) ? "unitName" : "info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
}
|
||||
cnd.groupBy("t.id");
|
||||
sql.setCondition(cnd);
|
||||
|
||||
+4
-2
@@ -60,7 +60,7 @@ public class MemberUnionGroupAuditController {
|
||||
info.sex,
|
||||
info.userName,
|
||||
info.loginName,
|
||||
info.unitName,
|
||||
COALESCE(changeUnit.name, info.unitName) AS unitName,
|
||||
info.unionName,
|
||||
info.userState,
|
||||
info.preparedBy,
|
||||
@@ -87,6 +87,7 @@ public class MemberUnionGroupAuditController {
|
||||
wf_process_task t
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
LEFT JOIN member_change_record info ON info.id = ins.businessNo
|
||||
LEFT JOIN sys_unit changeUnit ON changeUnit.id = info.unitId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
|
||||
@@ -107,7 +108,8 @@ public class MemberUnionGroupAuditController {
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
cnd.desc("info.applyDateTime");
|
||||
} else {
|
||||
cnd.orderBy("info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
// 单位列按实际显示名称排序,其他字段保持原有排序规则。
|
||||
cnd.orderBy("unitName".equals(pageForm.getPageOrderName()) ? "unitName" : "info." + pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
}
|
||||
cnd.groupBy("t.id");
|
||||
sql.setCondition(cnd);
|
||||
|
||||
+3
-1
@@ -7,6 +7,7 @@ import com.budwk.app.flow.engine.core.ServiceContext;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.web.commons.base.Globals;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberApplyRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberApplyRecordService;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
@@ -35,7 +36,8 @@ public class MemberApplySaveInterceptor implements FlowInterceptor {
|
||||
MemberApplyRecord record = Json.fromJson(MemberApplyRecord.class, formDataStr);
|
||||
|
||||
Dao dao = ServiceContext.find(Dao.class);
|
||||
dao.insertOrUpdate(record);
|
||||
// WF 保存同样经过日期校验,防止绕过普通申请接口写入无效日期。
|
||||
ServiceContext.find(MemberApplyRecordService.class).saveApplyRecord(record);
|
||||
|
||||
execution.getArgs().set(FlowConst.FORM_DATA, Json.toJson(record));
|
||||
|
||||
|
||||
+6
@@ -8,4 +8,10 @@ import com.budwk.app.zhgh.staffmanage.member.models.MemberApplyRecord;
|
||||
*/
|
||||
public interface MemberApplyRecordService extends BaseService<MemberApplyRecord> {
|
||||
|
||||
/**
|
||||
* 校验入职时间并保存入会申请,统一供保存、提交、重新提交及 WF 保存入口调用。
|
||||
* @param record 申请数据,arrivalAtSchoolDate 可为空,非空时必须为有效的 yyyy-MM-dd 日期
|
||||
* @return 无返回值;新增时记录 ID 回填到 record,非空日期非法时抛出业务异常
|
||||
*/
|
||||
void saveApplyRecord(MemberApplyRecord record);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,13 @@ public interface MemberCommonService extends BaseService<Sys_user> {
|
||||
Pagination memberHistoryRecords(MemberManagePageForm pageForm);
|
||||
|
||||
|
||||
/**
|
||||
* 读取变更申请并按申请单位 ID 校准显示名称,不写回历史记录。
|
||||
* @param id 会员变更申请记录 ID
|
||||
* @return 申请记录,unitName 对应本次申请的 unitId;单位不存在时保留原名称
|
||||
*/
|
||||
MemberChangeRecord findChangeRecordWithUnitName(String id);
|
||||
|
||||
/**
|
||||
* 获取可变更字段
|
||||
* @return
|
||||
|
||||
+40
@@ -4,6 +4,13 @@ import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberApplyRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberApplyRecordService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.format.ResolverStyle;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
@@ -12,4 +19,37 @@ public class MemberApplyRecordServiceImpl extends BaseServiceImpl<MemberApplyRec
|
||||
public MemberApplyRecordServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
/**
|
||||
* 入职时间缺失时允许申请并存储为空;有值时拒绝 Invalid date 和不存在的日期。
|
||||
* @param record 入会申请;arrivalAtSchoolDate 可为空或 yyyy-MM-dd,允许首尾空格
|
||||
* @return 无返回值;不通过校验时不执行任何数据库写入
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void saveApplyRecord(MemberApplyRecord record) {
|
||||
if (record == null) {
|
||||
throw new BaseException("未获取到入会申请数据");
|
||||
}
|
||||
String value = record.getArrivalAtSchoolDate();
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
// 数据源未提供入职时间时保留为空,不阻止申请,也不填充虚构日期。
|
||||
record.setArrivalAtSchoolDate(null);
|
||||
} else {
|
||||
value = value.trim();
|
||||
if (!value.matches("[0-9]{4}-[0-9]{2}-[0-9]{2}")) {
|
||||
throw new BaseException("入职时间格式无效,请先核实人员基础信息,日期格式应为yyyy-MM-dd");
|
||||
}
|
||||
try {
|
||||
LocalDate date = LocalDate.parse(value,
|
||||
DateTimeFormatter.ofPattern("uuuu-MM-dd").withResolverStyle(ResolverStyle.STRICT));
|
||||
if (date.getYear() < 1) {
|
||||
throw new BaseException("入职时间年份无效,请核实人员基础信息");
|
||||
}
|
||||
record.setArrivalAtSchoolDate(date.toString());
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new BaseException("入职时间不是有效日期,请先核实人员基础信息");
|
||||
}
|
||||
}
|
||||
dao().insertOrUpdate(record);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -547,7 +547,10 @@ public class MemberInfoServiceImpl extends BaseServiceImpl<Sys_user> implements
|
||||
// 获取系统用户
|
||||
Cnd cnd = Cnd.NEW();
|
||||
List<Sys_user> dataUserList = dao().query(Sys_user.class, cnd);
|
||||
Map<String, Sys_user> userMap = dataUserList.stream().collect(Collectors.toMap(Sys_user::getLoginname, v -> v));
|
||||
// 无工号人员保留原有资料和会员身份,仅不参与本次按工号匹配,避免多个空工号导致 Map 键冲突。
|
||||
Map<String, Sys_user> userMap = dataUserList.stream()
|
||||
.filter(user -> StrUtil.isNotBlank(user.getLoginname()))
|
||||
.collect(Collectors.toMap(Sys_user::getLoginname, v -> v));
|
||||
|
||||
List<Sys_unit> unitList = dao().query(Sys_unit.class, Cnd.where("unitTypeCode", "=", "1").groupBy("name"));
|
||||
|
||||
|
||||
+37
-4
@@ -159,20 +159,21 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
|
||||
his.changeTypes,
|
||||
his.changeOrigin,
|
||||
his.changeReason,
|
||||
his.remark,
|
||||
gh.`name` AS unionName,
|
||||
unit.`name` AS unitName,
|
||||
COALESCE(unit.name, changeRecord.unitName) AS unitName,
|
||||
u.id AS userId
|
||||
FROM
|
||||
`sys_user_history` his
|
||||
LEFT JOIN sys_user u ON u.loginname = his.loginname
|
||||
LEFT JOIN sys_unit unit ON unit.id = u.unitid
|
||||
LEFT JOIN member_change_record changeRecord ON changeRecord.id = his.recordId
|
||||
LEFT JOIN sys_unit unit ON unit.id = COALESCE(NULLIF(changeRecord.unitId, ''), his.unitId)
|
||||
LEFT JOIN sys_union gh ON gh.id = unit.unionId
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("gh.id", "=", pageForm.getUnionId());
|
||||
cnd.andEX("his.unitId", "=", pageForm.getUnitId());
|
||||
// 优先使用本次变更目标单位;无关联申请时使用历史记录自身单位,筛选与显示一致。
|
||||
cnd.andEX("COALESCE(NULLIF(changeRecord.unitId, ''), his.unitId)", "=", pageForm.getUnitId());
|
||||
cnd.andEX("his.personType", "=", pageForm.getPersonType());
|
||||
cnd.andEX("his.userState", "=", pageForm.getUserState());
|
||||
// 变更记录查询自行拼接历史表条件,需要单独补充人员属性多选及旧单值兼容。
|
||||
@@ -251,6 +252,8 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
|
||||
|
||||
@Override
|
||||
public List<NutMap> getChangeInfos(MemberChangeRecord record) {
|
||||
// 普通提交、WF 保存拦截器和审核落库均先比较变更,在此统一校准申请单位名称。
|
||||
normalizeChangeUnitName(record);
|
||||
View_user info = dao().fetch(View_user.class, Cnd.where("id", "=", record.getUserId()));
|
||||
|
||||
NutMap map = getDictAllowChangeFields();
|
||||
@@ -537,6 +540,36 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 按申请 ID 读取详情并校准单位名称;仅修正返回对象,不改写历史记录。
|
||||
* @param id 会员变更申请 ID
|
||||
* @return 含一致 unitId、unitName 的申请记录;记录不存在时提示错误
|
||||
*/
|
||||
@Override
|
||||
public MemberChangeRecord findChangeRecordWithUnitName(String id) {
|
||||
MemberChangeRecord record = dao().fetch(MemberChangeRecord.class, id);
|
||||
if (record == null) {
|
||||
throw new BaseException("未找到会员变更申请记录");
|
||||
}
|
||||
normalizeChangeUnitName(record);
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* 名称以申请目标单位 ID 为准,避免前端残留原单位名称;不改变单位 ID 或所属工会。
|
||||
* 单位已删除或未填写 ID 时保留原名称,避免用人员当前单位覆盖历史申请含义。
|
||||
* @param record 待保存或待展示的申请对象,仅更新内存中的 unitName,不执行数据库更新
|
||||
*/
|
||||
private void normalizeChangeUnitName(MemberChangeRecord record) {
|
||||
if (record == null || StrUtil.isBlank(record.getUnitId())) {
|
||||
return;
|
||||
}
|
||||
Sys_unit unit = dao().fetch(Sys_unit.class, record.getUnitId());
|
||||
if (unit != null && unit.getName() != null) {
|
||||
record.setUnitName(unit.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一规范变更记录中的到校日期,避免 Date 对象复制后以英文字符串形式参与比较或直接落库。
|
||||
*/
|
||||
|
||||
@@ -735,7 +735,6 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
|
||||
entities.add(new ExcelExportEntity("在职状态", "userState", 20));
|
||||
entities.add(new ExcelExportEntity("所属工会", "welfareUnionName", 20));
|
||||
entities.add(new ExcelExportEntity("所属单位", "welfareUnitName", 20));
|
||||
entities.add(new ExcelExportEntity("三级单位", "threeUnitName", 20));
|
||||
entities.add(new ExcelExportEntity("备注", "remark", 20));
|
||||
|
||||
// 设置导出参数
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
-- 全量初始化:完整定义特殊慰问申请主表、被申请人明细表和慰问类型表。
|
||||
-- 表已存在时不执行本文件中的建表语句;本文件不复制数据、不配置角色授权和工作流。
|
||||
CREATE TABLE `special_condolence` (
|
||||
`id` varchar(32) NOT NULL COMMENT 'ID',
|
||||
`subject` varchar(255) DEFAULT NULL COMMENT '事项',
|
||||
`applyUserId` varchar(32) DEFAULT NULL COMMENT '申请人ID',
|
||||
`applyUserName` varchar(100) DEFAULT NULL COMMENT '申请人姓名',
|
||||
`applyLoginName` varchar(120) DEFAULT NULL COMMENT '申请人工号',
|
||||
`applyUnionId` varchar(32) DEFAULT NULL COMMENT '申请人分工会ID',
|
||||
`applyUnionName` varchar(100) DEFAULT NULL COMMENT '申请人分工会',
|
||||
`applyUnitId` varchar(32) DEFAULT NULL COMMENT '申请人单位ID',
|
||||
`applyUnitName` varchar(100) DEFAULT NULL COMMENT '申请人单位',
|
||||
`helpUserId` varchar(32) DEFAULT NULL COMMENT '补助人ID',
|
||||
`helpUserName` varchar(100) DEFAULT NULL COMMENT '补助人姓名',
|
||||
`helpLoginName` varchar(120) DEFAULT NULL COMMENT '补助人工号',
|
||||
`helpUnionId` varchar(32) DEFAULT NULL COMMENT '补助人分工会ID',
|
||||
`helpUnionName` varchar(100) DEFAULT NULL COMMENT '补助人分工会',
|
||||
`helpUnitId` varchar(32) DEFAULT NULL COMMENT '补助人单位ID',
|
||||
`helpUnitName` varchar(100) DEFAULT NULL COMMENT '补助人单位',
|
||||
`origin` varchar(20) DEFAULT NULL COMMENT '类型',
|
||||
`payUserId` varchar(32) DEFAULT NULL COMMENT '收款人',
|
||||
`payUserName` varchar(30) DEFAULT NULL COMMENT '收款人姓名',
|
||||
`payLoginName` varchar(20) DEFAULT NULL COMMENT '收款人工号',
|
||||
`type` varchar(32) DEFAULT NULL COMMENT '慰问类型',
|
||||
`typeCode` varchar(32) DEFAULT NULL COMMENT '慰问编码',
|
||||
`way` varchar(20) DEFAULT NULL COMMENT '慰问方式',
|
||||
`perMoney` decimal(10,2) DEFAULT NULL COMMENT '统一每人慰问金额',
|
||||
`money` float(10,2) DEFAULT NULL COMMENT '慰问金额',
|
||||
`occurTime` varchar(30) DEFAULT NULL COMMENT '发生时间',
|
||||
`child` varchar(10) DEFAULT NULL COMMENT '生育几孩(孩次)',
|
||||
`hospital` varchar(50) DEFAULT NULL COMMENT '所在医院',
|
||||
`hospitalHouseNum` varchar(20) DEFAULT NULL COMMENT '病房床号',
|
||||
`hospitalHouseBedNum` varchar(20) DEFAULT NULL COMMENT '病房床号',
|
||||
`hospitalBy` varchar(100) DEFAULT NULL COMMENT '住院病由',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`files` json DEFAULT NULL COMMENT '附件',
|
||||
`signature` varchar(255) DEFAULT NULL COMMENT '签字',
|
||||
`createTime` varchar(30) DEFAULT NULL COMMENT '申请时间',
|
||||
`bankUserName` varchar(100) DEFAULT NULL COMMENT '户名',
|
||||
`bankCardNumber` varchar(100) DEFAULT NULL COMMENT '银行账号',
|
||||
`bankOfDeposit` varchar(100) DEFAULT NULL COMMENT '开户行',
|
||||
`certifierUserId` varchar(32) DEFAULT NULL COMMENT '证明人ID',
|
||||
`certifierUserName` varchar(100) DEFAULT NULL COMMENT '证明人姓名',
|
||||
`certifierLoginName` varchar(120) DEFAULT NULL COMMENT '证明人工号',
|
||||
`certifierUnionId` varchar(32) DEFAULT NULL COMMENT '证明人分工会ID',
|
||||
`certifierUnionName` varchar(100) DEFAULT NULL COMMENT '证明人分工会',
|
||||
`certifierUnitId` varchar(32) DEFAULT NULL COMMENT '证明人单位ID',
|
||||
`certifierUnitName` varchar(100) DEFAULT NULL COMMENT '证明人单位',
|
||||
`handlerUserId` varchar(32) DEFAULT NULL COMMENT '经办人ID',
|
||||
`handlerUserName` varchar(100) DEFAULT NULL COMMENT '经办人姓名',
|
||||
`handlerLoginName` varchar(120) DEFAULT NULL COMMENT '经办人工号',
|
||||
`handlerUnionId` varchar(32) DEFAULT NULL COMMENT '经办人分工会ID',
|
||||
`handlerUnionName` varchar(100) DEFAULT NULL COMMENT '经办人分工会',
|
||||
`handlerUnitId` varchar(32) DEFAULT NULL COMMENT '经办人单位ID',
|
||||
`handlerUnitName` varchar(100) DEFAULT NULL COMMENT '经办人单位',
|
||||
`hospitalizationTime` varchar(30) DEFAULT NULL COMMENT '入院时间',
|
||||
`leaveHospitalTime` varchar(30) DEFAULT NULL COMMENT '出院时间',
|
||||
`thisYearHospitalizationNum` int DEFAULT NULL COMMENT '当年第几次住院',
|
||||
`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`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='特殊慰问申请';
|
||||
|
||||
CREATE TABLE `special_condolence_type` (
|
||||
`id` varchar(32) NOT NULL COMMENT 'ID',
|
||||
`code` varchar(30) DEFAULT NULL COMMENT '编码',
|
||||
`name` varchar(50) DEFAULT NULL COMMENT '名称',
|
||||
`money` float(10,2) DEFAULT NULL COMMENT '费用',
|
||||
`way` varchar(20) DEFAULT NULL COMMENT '慰问方式',
|
||||
`enable` tinyint(1) DEFAULT 1 COMMENT '是否启用',
|
||||
`isUploadFile` tinyint(1) DEFAULT NULL COMMENT '是否上传附件',
|
||||
`uploadFileDesc` varchar(50) DEFAULT NULL COMMENT '上传附件说明',
|
||||
`location` int DEFAULT NULL COMMENT '排序字段',
|
||||
`sortNum` int NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`uploadMeetingRecord` tinyint(1) DEFAULT 0 COMMENT '上传会议纪要',
|
||||
`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`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='特殊慰问类型';
|
||||
|
||||
-- 一份申请对应一个流程实例;多名被申请人通过 applyId 关联同一份申请。
|
||||
CREATE TABLE `special_condolence_recipient` (
|
||||
`id` varchar(32) NOT NULL COMMENT '明细ID',
|
||||
`applyId` varchar(32) NOT NULL COMMENT '申请主表ID',
|
||||
`helpUserId` varchar(32) NOT NULL COMMENT '慰问对象用户ID',
|
||||
`helpLoginName` varchar(120) DEFAULT NULL COMMENT '慰问对象工号',
|
||||
`personType` varchar(50) DEFAULT NULL COMMENT '人员类型',
|
||||
`helpUserName` varchar(100) NOT NULL COMMENT '慰问对象',
|
||||
`type` varchar(32) NOT NULL COMMENT '慰问类型ID',
|
||||
`typeName` varchar(50) DEFAULT NULL COMMENT '慰问类型名称',
|
||||
`typeCode` varchar(32) DEFAULT NULL COMMENT '慰问类型编码',
|
||||
`way` varchar(20) DEFAULT NULL COMMENT '慰问方式',
|
||||
`idCard` varchar(18) NOT NULL COMMENT '身份证号码',
|
||||
`bankOfDeposit` varchar(100) NOT NULL COMMENT '开户行',
|
||||
`bankUserName` varchar(100) NOT NULL COMMENT '户名',
|
||||
`bankCardNumber` varchar(30) NOT NULL COMMENT '收款账户',
|
||||
`money` decimal(10,2) NOT NULL COMMENT '慰问金额',
|
||||
`occurTime` varchar(30) NOT NULL COMMENT '慰问时间',
|
||||
`sortNum` int NOT NULL DEFAULT 0 COMMENT '行序号',
|
||||
`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`),
|
||||
KEY `idx_special_condolence_recipient_apply` (`applyId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='特殊慰问被申请人明细';
|
||||
|
||||
-- 主表保留原有摘要字段兼容现有列表,money 为整份申请的金额合计。
|
||||
-- 被申请人的完整信息保存在 special_condolence_recipient 中,不在主表使用 JSON 存储。
|
||||
|
||||
-- 新增 PC、H5 菜单,角色授权由管理员另行配置。
|
||||
START TRANSACTION;
|
||||
CREATE TEMPORARY TABLE special_condolence_menu_map AS
|
||||
SELECT id oldId, MD5(CONCAT('special_condolence:', id)) newId
|
||||
FROM sys_menu
|
||||
WHERE id IN ('3b49290f7db44130b9f6394a8212321f', '024811fd168e44948e6ab84911fc26bb')
|
||||
OR (href LIKE '/platform/condolence/%' AND href NOT LIKE '%/branchUnionApproval%');
|
||||
CREATE TEMPORARY TABLE special_condolence_parent_map (
|
||||
oldId varchar(32) NOT NULL COMMENT '原父菜单ID',
|
||||
newId varchar(32) NOT NULL COMMENT '特殊慰问父菜单ID',
|
||||
PRIMARY KEY (oldId)
|
||||
);
|
||||
INSERT INTO special_condolence_parent_map (oldId, newId)
|
||||
SELECT oldId, newId FROM special_condolence_menu_map;
|
||||
|
||||
INSERT INTO sys_menu
|
||||
(id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled,
|
||||
permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt,
|
||||
delFlag, processKey, platform, moduleId, isQuickEntry, picIcon, initialPinyinName,
|
||||
isRecommendApp, isRecommendService)
|
||||
SELECT map.newId, COALESCE(parent.newId, m.parentId),
|
||||
CONCAT('T', SUBSTRING(map.newId, 1, 7)), REPLACE(m.name, '职工慰问', '特殊慰问'),
|
||||
m.aliasName, m.type, REPLACE(m.href, '/condolence/', '/special_condolence/'), m.target,
|
||||
m.icon, m.showit, m.disabled,
|
||||
CASE m.id WHEN '3b49290f7db44130b9f6394a8212321f' THEN 'special_condolence'
|
||||
WHEN '024811fd168e44948e6ab84911fc26bb' THEN 'h5.special_condolence'
|
||||
ELSE REPLACE(m.permission, 'condolence', 'special_condolence') END,
|
||||
REPLACE(m.note, '职工慰问', '特殊慰问'), m.location, m.hasChildren,
|
||||
m.createdBy, UNIX_TIMESTAMP()*1000, m.updatedBy, UNIX_TIMESTAMP()*1000, m.delFlag,
|
||||
REPLACE(m.processKey, 'ZGWW', 'TSWW'), m.platform, m.moduleId, m.isQuickEntry,
|
||||
m.picIcon, IF(m.name = '职工慰问', 't', m.initialPinyinName), m.isRecommendApp, m.isRecommendService
|
||||
FROM sys_menu m JOIN special_condolence_menu_map map ON map.oldId = m.id
|
||||
LEFT JOIN special_condolence_parent_map parent ON parent.oldId = m.parentId
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu existing WHERE existing.id = map.newId);
|
||||
|
||||
-- 父子菜单路径保持树形前缀关系。
|
||||
UPDATE sys_menu child JOIN sys_menu parent ON parent.id = child.parentId
|
||||
JOIN special_condolence_menu_map map ON map.newId = child.id
|
||||
SET child.path = CONCAT(parent.path, RIGHT(child.id, 4));
|
||||
|
||||
DROP TEMPORARY TABLE special_condolence_menu_map;
|
||||
DROP TEMPORARY TABLE special_condolence_parent_map;
|
||||
|
||||
-- 权限部分,不要删除
|
||||
-- SYSADMIN
|
||||
-- 特殊慰问 web
|
||||
INSERT INTO sys_role_menu (roleId, menuId) VALUES ('7f4a820d5a394bb8a0a92d6699afe7c8', '4c46696d75ff0eddf5cb57fac3e5d06d');
|
||||
INSERT INTO sys_role_menu (roleId, menuId)
|
||||
VALUES ('7f4a820d5a394bb8a0a92d6699afe7c8', '3e97a91e7aa9566cdae94850038a6212'),
|
||||
('7f4a820d5a394bb8a0a92d6699afe7c8', '6c5be59ffb8d7c524ec72f2a02d6c6d2'),
|
||||
('7f4a820d5a394bb8a0a92d6699afe7c8', '7f66eb63b6ebd81c5eee9c5c2de2ed98'),
|
||||
('7f4a820d5a394bb8a0a92d6699afe7c8', '8d5ebaa091eee01abb33e89e3dcc92f8'),
|
||||
('7f4a820d5a394bb8a0a92d6699afe7c8', '9c37242f50c5fb4a9c1b2b9b9dd0e2fd'),
|
||||
('7f4a820d5a394bb8a0a92d6699afe7c8', '9f4f850904fe751db1cd13977374f99b'),
|
||||
('7f4a820d5a394bb8a0a92d6699afe7c8', 'b24a5bef9ccbf7241c5bb85e80352f8f');
|
||||
|
||||
-- 特殊慰问 h5
|
||||
INSERT INTO sys_role_menu (roleId, menuId) VALUES ('7f4a820d5a394bb8a0a92d6699afe7c8', '2145812145ee6548552504b4f5a55ff7');
|
||||
INSERT INTO sys_role_menu (roleId, menuId)
|
||||
VALUES ('7f4a820d5a394bb8a0a92d6699afe7c8', '42b4b5f54593cf023961ad1c9ce36f53'),
|
||||
('7f4a820d5a394bb8a0a92d6699afe7c8', '4f3212a13a54fbb2a082ee77651c9a73'),
|
||||
('7f4a820d5a394bb8a0a92d6699afe7c8', '739f64c71fe776dc3520ebb2977c2d68'),
|
||||
('7f4a820d5a394bb8a0a92d6699afe7c8', 'b6c725beaf3bfe9f2b29f46855c45a24'),
|
||||
('7f4a820d5a394bb8a0a92d6699afe7c8', 'eb3d1b27d51b84df1cebf24e1278e26c');
|
||||
|
||||
|
||||
-- 校工会办公室主任
|
||||
-- 特殊慰问 web
|
||||
INSERT INTO sys_role_menu (roleId, menuId)
|
||||
VALUES
|
||||
('494ac71eb8b44d0e99e19b7fde6bd469', '4c46696d75ff0eddf5cb57fac3e5d06d'),
|
||||
('494ac71eb8b44d0e99e19b7fde6bd469', '8d5ebaa091eee01abb33e89e3dcc92f8'),
|
||||
('494ac71eb8b44d0e99e19b7fde6bd469', '7f66eb63b6ebd81c5eee9c5c2de2ed98');
|
||||
|
||||
-- 特殊慰问 h5
|
||||
INSERT INTO sys_role_menu (roleId, menuId)
|
||||
VALUES
|
||||
('494ac71eb8b44d0e99e19b7fde6bd469', '2145812145ee6548552504b4f5a55ff7'),
|
||||
('494ac71eb8b44d0e99e19b7fde6bd469', '42b4b5f54593cf023961ad1c9ce36f53');
|
||||
|
||||
-- 校工会主席
|
||||
-- 特殊慰问 web
|
||||
INSERT INTO sys_role_menu (roleId, menuId)
|
||||
VALUES
|
||||
('6aece46b946e4fb9ac4cb15c0cd8c3e6', '4c46696d75ff0eddf5cb57fac3e5d06d'),
|
||||
('6aece46b946e4fb9ac4cb15c0cd8c3e6', '8d5ebaa091eee01abb33e89e3dcc92f8'),
|
||||
('6aece46b946e4fb9ac4cb15c0cd8c3e6', 'b24a5bef9ccbf7241c5bb85e80352f8f');
|
||||
|
||||
-- 特殊慰问 h5
|
||||
INSERT INTO sys_role_menu (roleId, menuId)
|
||||
VALUES
|
||||
('6aece46b946e4fb9ac4cb15c0cd8c3e6', '2145812145ee6548552504b4f5a55ff7'),
|
||||
('6aece46b946e4fb9ac4cb15c0cd8c3e6', 'b6c725beaf3bfe9f2b29f46855c45a24');
|
||||
|
||||
-- 校工会主席
|
||||
-- 特殊慰问 web
|
||||
INSERT INTO sys_role_menu (roleId, menuId)
|
||||
VALUES
|
||||
('b3854469e6f44901b22bb791cb538431', '4c46696d75ff0eddf5cb57fac3e5d06d'),
|
||||
('b3854469e6f44901b22bb791cb538431', '8d5ebaa091eee01abb33e89e3dcc92f8'),
|
||||
('b3854469e6f44901b22bb791cb538431', '9c37242f50c5fb4a9c1b2b9b9dd0e2fd');
|
||||
|
||||
-- 特殊慰问 h5
|
||||
INSERT INTO sys_role_menu (roleId, menuId)
|
||||
VALUES
|
||||
('b3854469e6f44901b22bb791cb538431', '2145812145ee6548552504b4f5a55ff7'),
|
||||
('b3854469e6f44901b22bb791cb538431', '739f64c71fe776dc3520ebb2977c2d68');
|
||||
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,7 @@
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, processKey, platform, moduleId, isQuickEntry, picIcon, initialPinyinName, isRecommendApp, isRecommendService) VALUES ('184d56fd537041caa2bde800d0d32436', '90bf20d9065e4e11864952ea18544c25', '009000040003', '个人范围', null, 'data', null, null, null, false, false, 'self', null, 1027, false, '17a7f8ad3ee947b4a26175049a9c253d', 1793418331288, '17a7f8ad3ee947b4a26175049a9c253d', 1793418331288, false, null, 'PC', null, false, null, null, false, false);
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, processKey, platform, moduleId, isQuickEntry, picIcon, initialPinyinName, isRecommendApp, isRecommendService) VALUES ('df87d83ed3184019b3872139c15b53ec', '90bf20d9065e4e11864952ea18544c25', '009000040002', '分工会范围', null, 'data', null, null, null, false, false, 'branch_union', null, 1026, false, '17a7f8ad3ee947b4a26175049a9c253d', 1793418331286, '17a7f8ad3ee947b4a26175049a9c253d', 1793418331286, false, null, 'PC', null, false, null, null, false, false);
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, processKey, platform, moduleId, isQuickEntry, picIcon, initialPinyinName, isRecommendApp, isRecommendService) VALUES ('f2a75d2494724978b5325075815808c9', '90bf20d9065e4e11864952ea18544c25', '009000040001', '全校范围', null, 'data', null, null, null, false, false, 'all_school', null, 1025, false, '17a7f8ad3ee947b4a26175049a9c253d', 1793418331280, '17a7f8ad3ee947b4a26175049a9c253d', 1793418331280, false, null, 'PC', null, false, null, null, false, false);
|
||||
INSERT INTO sys_role_menu (roleId, menuId) VALUES ('fd753e8a8c5a404e8e00ef8e0b0c69da', 'df87d83ed3184019b3872139c15b53ec');
|
||||
INSERT INTO sys_role_menu (roleId, menuId) VALUES ('494ac71eb8b44d0e99e19b7fde6bd469', 'f2a75d2494724978b5325075815808c9');
|
||||
INSERT INTO sys_role_menu (roleId, menuId) VALUES ('6aece46b946e4fb9ac4cb15c0cd8c3e6', 'f2a75d2494724978b5325075815808c9');
|
||||
INSERT INTO sys_role_menu (roleId, menuId) VALUES ('b3854469e6f44901b22bb791cb538431', 'f2a75d2494724978b5325075815808c9');
|
||||
@@ -0,0 +1,92 @@
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
@@ -0,0 +1,207 @@
|
||||
/* 首页服务门户布局单独限定作用域,避免影响应用中心和其他业务页。 */
|
||||
#v4-home-app.home-portal {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
color: #20344d;
|
||||
background: #f2f6fb;
|
||||
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
#v4-home-app.home-portal * { box-sizing: border-box; }
|
||||
#v4-home-app .portal-top { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 18px; max-width: 1720px; margin: auto; padding: 18px 24px 0; }
|
||||
/* 使用完整不透明背景铺满横幅,会员信息独立成卡,不再覆盖校园景观。 */
|
||||
#v4-home-app .portal-hero { position: relative; min-width: 0; min-height: 260px; overflow: hidden; border-radius: 12px; background: #e8f4ff; }
|
||||
#v4-home-app .portal-hero::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(90deg, #e8f4ff 0%, #e8f4ff 28%, rgba(232,244,255,.92) 39%, rgba(232,244,255,.35) 54%, rgba(232,244,255,0) 68%); }
|
||||
#v4-home-app .portal-hero-inner { position: relative; z-index: 1; display: flex; align-items: center; min-height: 260px; height: 100%; padding: 32px 40px; }
|
||||
#v4-home-app .portal-hero-copy { max-width: 80%; }
|
||||
#v4-home-app .portal-hero-eyebrow { margin-bottom: 12px; color: #527ca1; font-size: 14px; line-height: 22px; letter-spacing: 2px; }
|
||||
#v4-home-app .portal-hero-copy h1 { margin: 0 0 10px; font-size: 36px; line-height: 1.3; font-weight: 700; color: #083765; }
|
||||
#v4-home-app .portal-hero-copy p { margin: 0; color: #335675; font-size: 17px; line-height: 1.8; }
|
||||
#v4-home-app .portal-values { display: flex; flex-wrap: wrap; gap: 22px 36px; margin-top: 24px; }
|
||||
#v4-home-app .portal-values span { display: inline-flex; align-items: center; gap: 12px; }
|
||||
#v4-home-app .portal-values i { color: #3599f2; font-size: 23px; }
|
||||
/* 首次渲染时分层淡入文字,只改变透明度和位移,不影响卡片布局。 */
|
||||
@keyframes portal-text-enter {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
#v4-home-app .portal-hero-eyebrow,
|
||||
#v4-home-app .portal-hero-copy h1,
|
||||
#v4-home-app .portal-hero-copy p,
|
||||
#v4-home-app .portal-values > span,
|
||||
#v4-home-app .portal-member-date,
|
||||
#v4-home-app .portal-member-heading,
|
||||
#v4-home-app .portal-member .meta-row { animation: portal-text-enter .6s cubic-bezier(.22, 1, .36, 1) backwards; }
|
||||
#v4-home-app .portal-hero-copy h1 { animation-delay: .08s; }
|
||||
#v4-home-app .portal-hero-copy p { animation-delay: .16s; }
|
||||
#v4-home-app .portal-values > span:nth-child(1) { animation-delay: .24s; }
|
||||
#v4-home-app .portal-values > span:nth-child(2) { animation-delay: .30s; }
|
||||
#v4-home-app .portal-values > span:nth-child(3) { animation-delay: .36s; }
|
||||
#v4-home-app .portal-values > span:nth-child(4) { animation-delay: .42s; }
|
||||
#v4-home-app .portal-member-date { animation-delay: .10s; }
|
||||
#v4-home-app .portal-member-heading { animation-delay: .18s; }
|
||||
#v4-home-app .portal-member .meta-row:nth-child(1) { animation-delay: .26s; }
|
||||
#v4-home-app .portal-member .meta-row:nth-child(2) { animation-delay: .34s; }
|
||||
#v4-home-app .portal-member .meta-row:nth-child(3) { animation-delay: .42s; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#v4-home-app .portal-hero-eyebrow,
|
||||
#v4-home-app .portal-hero-copy h1,
|
||||
#v4-home-app .portal-hero-copy p,
|
||||
#v4-home-app .portal-values > span,
|
||||
#v4-home-app .portal-member-date,
|
||||
#v4-home-app .portal-member-heading,
|
||||
#v4-home-app .portal-member .meta-row { animation: none; }
|
||||
}
|
||||
#v4-home-app .portal-content { max-width: 1720px; margin: auto; padding: 18px 24px 28px; }
|
||||
/* 同行共用网格行高,统一横纵间距;待办使用紧凑两行信息,避免撑出大块留白。 */
|
||||
#v4-home-app .portal-columns { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: 18px; margin-top: 18px; align-items: stretch; }
|
||||
#v4-home-app .portal-columns > * { min-width: 0; }
|
||||
#v4-home-app .portal-news-wide { grid-column: 1 / -1; }
|
||||
#v4-home-app .portal-panel,
|
||||
#v4-home-app .entry-wrapper,
|
||||
#v4-home-app .todo-center,
|
||||
#v4-home-app .work-template-wrapper,
|
||||
#v4-home-app .jcdt-container { background: #fff; border: 0; border-radius: 12px; padding: 20px; box-shadow: 0 3px 14px rgba(30, 66, 108, .035); }
|
||||
#v4-home-app .home-section-header,
|
||||
#v4-home-app .portal-todo-heading,
|
||||
#v4-home-app .work-template-header,
|
||||
#v4-home-app .portal-news-header { display: flex; justify-content: space-between; align-items: center; gap: 12px; min-height: 28px; margin: 0 0 16px; }
|
||||
#v4-home-app .home-section-title,
|
||||
#v4-home-app .work-template-title,
|
||||
#v4-home-app .portal-news-title,
|
||||
#v4-home-app .portal-todo-heading h3 { display: inline-flex; align-items: center; gap: 9px; margin: 0; padding: 0; border: 0; font-size: 20px; line-height: 1.4; font-weight: 600; color: #123356; }
|
||||
#v4-home-app .home-section-title i,
|
||||
#v4-home-app .work-template-title i,
|
||||
#v4-home-app .portal-news-title > i:first-child,
|
||||
#v4-home-app .portal-todo-heading i { color: #1687f5; font-size: 19px; }
|
||||
#v4-home-app .portal-more { display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0; border: 0; padding: 4px 0; background: transparent; color: #1687f5; font: inherit; font-size: 13px; cursor: pointer; text-decoration: none; }
|
||||
#v4-home-app .portal-more:hover { color: #0565c6; }
|
||||
#v4-home-app button:focus-visible,
|
||||
#v4-home-app a:focus-visible,
|
||||
#v4-home-app [tabindex]:focus-visible { outline: 2px solid #1687f5; outline-offset: 3px; }
|
||||
#v4-home-app .entry-card { min-width: 0; }
|
||||
#v4-home-app .entry-wrapper { padding-bottom: 0; }
|
||||
#v4-home-app .portal-entry-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; border-bottom: 1px solid #e9eff6; }
|
||||
#v4-home-app .portal-entry-tabs { display: flex; gap: 34px; }
|
||||
#v4-home-app .portal-entry-tabs button { border: 0; border-bottom: 3px solid transparent; padding: 0 0 13px; background: transparent; font: inherit; font-size: 19px; color: #465d78; font-weight: 600; cursor: pointer; }
|
||||
#v4-home-app .portal-entry-tabs button.active { color: #0784fa; border-bottom-color: #0784fa; }
|
||||
#v4-home-app .entry-section + .entry-section { margin-top: 0; }
|
||||
#v4-home-app .app-grid { display: flex; justify-content: flex-start; align-items: center; gap: 12px; min-height: 0; overflow-x: auto; padding: 20px 0; border: 0; border-radius: 0; box-shadow: none; }
|
||||
#v4-home-app .app-item { flex: 0 0 96px; border: 0; font: inherit; padding: 0 4px; max-width: none; }
|
||||
#v4-home-app .app-item:hover { background: #f4f9ff; box-shadow: none; transform: translateY(-2px); }
|
||||
#v4-home-app .app-icon { background: transparent; }
|
||||
#v4-home-app .app-name { display: block; width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 13px; line-height: 20px; min-height: 0; margin-top: 2px; }
|
||||
#v4-home-app .app-empty { padding: 10px 0; }
|
||||
#v4-home-app .act-card { padding: 0; border: 0; border-radius: 0; box-shadow: none; }
|
||||
#v4-home-app .activity-swiper-container { padding: 0 0 4px; }
|
||||
#v4-home-app .activity-swiper .swiper-slide .item { display: flex; flex-direction: column; height: 100%; }
|
||||
/* 保留横向卡片比例,海报等比完整居中显示,比例不一致时以浅色背景补齐空白。 */
|
||||
#v4-home-app .activity-swiper .swiper-slide .item .img-box { flex-shrink: 0; height: auto; aspect-ratio: 1.5; border-radius: 10px; overflow: hidden; clip-path: inset(0 round 10px); background: #f0f5fb; isolation: isolate; }
|
||||
#v4-home-app .img-box .el-image { display: block; width: 100%; height: 100%; position: absolute; inset: 0; border-radius: inherit; overflow: hidden; }
|
||||
#v4-home-app .activity-swiper .swiper-slide .item .img-box img { height: 100% !important; max-height: none !important; object-fit: contain !important; object-position: center; border-radius: 10px; }
|
||||
#v4-home-app .activity-swiper .swiper-slide .item .img-box .status { top: 8px; left: 8px; border-radius: 5px; padding: 4px 7px; background: #ed4859; }
|
||||
#v4-home-app .activity-swiper .swiper-slide .item .img-box .status-ended { background: #8b97a7; }
|
||||
#v4-home-app .activity-swiper .swiper-slide .item .img-box .status-upcoming { background: #1687f5; }
|
||||
#v4-home-app .activity-swiper .swiper-slide .item h4 { flex: 1; margin: 10px 0 8px; height: auto; min-height: 22px; max-height: 44px; font-size: 15px; color: #20344d; line-height: 22px; }
|
||||
#v4-home-app .activity-swiper .swiper-slide .item .time p { white-space: normal; font-size: 12px; color: #7b899d; line-height: 1.6; }
|
||||
#v4-home-app .activity-swiper-container .swiper-button-next,
|
||||
#v4-home-app .activity-swiper-container .swiper-button-prev { width: 44px; height: 44px; margin-top: 0; padding: 0; transform: translateY(-50%); border: 1px solid #dbeaff; background: rgba(255,255,255,.96); color: #1687f5; box-shadow: 0 3px 12px rgba(30, 66, 108, .18); }
|
||||
#v4-home-app .activity-swiper-container .swiper-button-next { right: 8px; }
|
||||
#v4-home-app .activity-swiper-container .swiper-button-prev { left: 8px; }
|
||||
#v4-home-app .activity-swiper-container .swiper-button-next:hover,
|
||||
#v4-home-app .activity-swiper-container .swiper-button-prev:hover { transform: translateY(-50%) scale(1.05); background: #1687f5; color: #fff; }
|
||||
#v4-home-app .activity-swiper-container .swiper-button-next::after,
|
||||
#v4-home-app .activity-swiper-container .swiper-button-prev::after { font-size: 18px; font-weight: 600; }
|
||||
#v4-home-app .portal-cover-empty { height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 9px; color: #94a7bd; background: #f0f5fb; }
|
||||
#v4-home-app .portal-cover-empty i { font-size: 28px; }
|
||||
#v4-home-app .user-wrapper { background: transparent; }
|
||||
#v4-home-app .user-container { display: block; width: 100%; height: 100%; margin: 0; }
|
||||
#v4-home-app .todo-center { display: flex; flex-direction: column; min-width: 0; height: 100%; }
|
||||
#v4-home-app .todo-center .header { display: block; margin: 0; }
|
||||
#v4-home-app .todo-center .el-tabs { margin: 0 0 8px; }
|
||||
#v4-home-app .todo-center .el-tabs__header { margin: 0; }
|
||||
#v4-home-app .todo-center .el-tabs__nav { display: flex; width: 100%; border-color: #e9eff6; border-radius: 6px 6px 0 0; }
|
||||
#v4-home-app .todo-center .el-tabs__item { flex: 1; min-width: 0; padding: 0 7px; font-size: 12px; height: 36px; line-height: 36px; text-align: center; }
|
||||
#v4-home-app .todo-center .el-tabs__item.is-active { border-bottom: 2px solid #1687f5; color: #1687f5; }
|
||||
#v4-home-app .portal-task-list { display: flex; flex-direction: column; justify-content: space-evenly; flex: 1; min-height: 180px; }
|
||||
#v4-home-app .portal-task-row { display: flex; gap: 10px; align-items: center; padding: 8px 6px; border-bottom: 1px solid #eef3f8; }
|
||||
#v4-home-app .portal-task-row:last-child { border-bottom: 0; }
|
||||
#v4-home-app .portal-task-row:hover { background: #f7fbff; }
|
||||
#v4-home-app .portal-task-icon { width: 34px; height: 40px; display: flex; flex: 0 0 34px; align-items: center; justify-content: center; border-radius: 8px; background: #eef7ff; color: #1687f5; font-size: 22px; }
|
||||
#v4-home-app .portal-task-content { min-width: 0; flex: 1; }
|
||||
#v4-home-app .portal-task-title { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: #263548; font-weight: 600; font-size: 14px; line-height: 22px; }
|
||||
#v4-home-app .portal-task-meta { display: flex; align-items: baseline; gap: 10px; font-size: 12px; color: #8390a2; line-height: 21px; }
|
||||
#v4-home-app .portal-task-meta span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
#v4-home-app .portal-task-meta > span:first-child { max-width: 45%; flex-shrink: 0; }
|
||||
#v4-home-app .portal-task-date { flex-shrink: 0; font-size: 12px; color: #7b899d; }
|
||||
#v4-home-app .portal-task-node { flex: 1; min-width: 0; color: #8b98a9; font-size: 11px; line-height: 17px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
#v4-home-app .portal-task-row .el-button { flex-shrink: 0; padding: 8px 14px; }
|
||||
#v4-home-app .portal-task-pagination { text-align: right; margin-top: 8px; }
|
||||
#v4-home-app .portal-empty { min-height: 132px; display: flex; align-items: center; justify-content: center; gap: 10px; color: #8a9aac; }
|
||||
#v4-home-app .portal-empty i { font-size: 26px; color: #83b9ee; }
|
||||
#v4-home-app .work-template-wrapper { width: 100%; margin: 0; }
|
||||
#v4-home-app .work-template-card { min-height: 98px; padding: 0; border: 0; border-radius: 0; box-shadow: none; }
|
||||
#v4-home-app .work-template-grid { display: flex; align-items: flex-start; gap: 14px; overflow-x: auto; padding: 4px 0 10px; scroll-snap-type: x proximity; }
|
||||
#v4-home-app .work-template-item { flex: 0 0 88px; width: 88px; padding: 4px; border: 0; font: inherit; scroll-snap-align: start; }
|
||||
#v4-home-app .work-template-item:hover { background: #f4f9ff; box-shadow: none; }
|
||||
#v4-home-app .work-template-icon,
|
||||
#v4-home-app .work-template-icon .el-image { width: 48px; height: 48px; }
|
||||
#v4-home-app .work-template-icon img { width: 48px !important; height: 48px !important; object-fit: contain; }
|
||||
#v4-home-app .work-template-icon i { font-size: 30px; line-height: 48px; color: #83b9ee; }
|
||||
#v4-home-app .work-template-name { font-size: 13px !important; line-height: 20px; min-height: 40px; max-height: 40px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
||||
#v4-home-app .work-template-wrapper .no-activity-placeholder { padding: 12px; }
|
||||
#v4-home-app .jcdt-container { width: 100%; max-width: none; height: 100%; margin: 0; }
|
||||
#v4-home-app .portal-news-title { background: transparent; cursor: pointer; font-family: inherit; }
|
||||
#v4-home-app .portal-news-title > i:last-child:not(:first-child) { color: #8092a6; font-size: 14px; }
|
||||
#v4-home-app .portal-news-row { display: flex; align-items: center; gap: 12px; width: 100%; padding: 9px 0; border: 0; background: transparent; color: #42536a; font: inherit; text-align: left; cursor: pointer; }
|
||||
#v4-home-app .portal-news-row time { flex-shrink: 0; padding: 3px 7px; background: #eff5fc; border-radius: 4px; color: #45698f; font-size: 13px; }
|
||||
#v4-home-app .portal-news-row span { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
#v4-home-app .portal-news-row:hover span { color: #1687f5; }
|
||||
@media (min-width: 1600px) {
|
||||
#v4-home-app .portal-hero-copy h1 { font-size: 42px; }
|
||||
#v4-home-app .app-item { flex-basis: 104px; }
|
||||
}
|
||||
@media (max-width: 1199px) {
|
||||
#v4-home-app .portal-top { grid-template-columns: minmax(0, 1fr) 300px; padding: 18px 18px 0; }
|
||||
#v4-home-app .portal-hero-inner { padding-left: 32px; }
|
||||
#v4-home-app .portal-content { padding: 18px 18px 24px; }
|
||||
#v4-home-app .portal-columns { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); }
|
||||
#v4-home-app .portal-values { gap: 16px; }
|
||||
#v4-home-app .portal-panel, #v4-home-app .todo-center { padding: 16px; }
|
||||
#v4-home-app .portal-task-row { gap: 8px; padding: 8px 0; }
|
||||
#v4-home-app .portal-task-icon { display: none; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
#v4-home-app .portal-top { grid-template-columns: minmax(0, 1fr); }
|
||||
#v4-home-app .portal-columns { grid-template-columns: minmax(0, 1fr); }
|
||||
#v4-home-app .portal-activities { order: 1; }
|
||||
#v4-home-app .portal-todo { order: 2; }
|
||||
#v4-home-app .portal-templates { order: 3; }
|
||||
#v4-home-app .portal-news { order: 4; }
|
||||
#v4-home-app .portal-hero-copy { max-width: 68%; }
|
||||
#v4-home-app .portal-hero-copy h1 { font-size: 30px; }
|
||||
#v4-home-app .portal-hero-copy p { font-size: 14px; }
|
||||
#v4-home-app .portal-task-icon { display: flex; }
|
||||
#v4-home-app .portal-task-meta { flex-wrap: nowrap; }
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
#v4-home-app .portal-top { padding: 10px 10px 8px; }
|
||||
#v4-home-app .portal-hero-inner { padding: 24px 20px; }
|
||||
#v4-home-app .portal-hero-copy { max-width: 100%; }
|
||||
#v4-home-app .portal-hero-copy h1 { font-size: 28px; }
|
||||
#v4-home-app .portal-values { gap: 12px 18px; font-size: 12px; }
|
||||
#v4-home-app .portal-values i { font-size: 17px; }
|
||||
#v4-home-app .portal-content { padding: 10px; }
|
||||
#v4-home-app .entry-wrapper { padding: 16px 12px 0; }
|
||||
#v4-home-app .portal-entry-tabs { gap: 20px; }
|
||||
#v4-home-app .portal-entry-tabs button { font-size: 16px; }
|
||||
#v4-home-app .app-item { flex-basis: 84px; }
|
||||
#v4-home-app .home-section-title, #v4-home-app .portal-todo-heading h3, #v4-home-app .work-template-title, #v4-home-app .portal-news-title { font-size: 18px; }
|
||||
#v4-home-app .portal-task-row .el-button { padding: 8px 10px; }
|
||||
}
|
||||
|
||||
/* 使用参考项目本地横幅图片,文字遮罩保持首页的可读性。 */
|
||||
#v4-home-app .portal-hero-background { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: right bottom; }
|
||||
/* 已展示全部活动时明确禁用入口,避免出现可点击但没有内容切换的按钮。 */
|
||||
#v4-home-app .portal-more:disabled { color: #8a9aac; cursor: default; }
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
@@ -11,25 +11,25 @@
|
||||
$.ajaxSetup({
|
||||
contentType: "application/x-www-form-urlencoded;charset=utf-8",
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (response.code !== 0) {
|
||||
this.$message.error(response.msg)
|
||||
if (response && typeof response === "object" && response.code !== undefined && response.code !== 0) {
|
||||
Vue.prototype.$message.error(response.msg)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
},
|
||||
error: function (v1) {
|
||||
this.$message.error(v1.status + " " + v1.statusText)
|
||||
Vue.prototype.$message.error(v1.status + " " + v1.statusText)
|
||||
},
|
||||
complete: function (XMLHttpRequest, textStatus) {
|
||||
var sessionstatus = XMLHttpRequest.getResponseHeader("loginStatus") //通过XMLHttpRequest取得响应头,sessionstatus,
|
||||
if (sessionstatus == "accessDenied") {
|
||||
this.$message({
|
||||
Vue.prototype.$message({
|
||||
message: "\u767b\u5f55\u5931\u6548\uff0c\u8bf7\u5237\u65b0\u9875\u9762\u91cd\u65b0\u767b\u5f55",
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
if (sessionstatus == "unauthorized") {
|
||||
this.$message({
|
||||
Vue.prototype.$message({
|
||||
message: "\u6ca1\u6709\u6743\u9650",
|
||||
type: "error"
|
||||
})
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
||||
<div style="padding: 0px 16px" class="clearfix">
|
||||
<el-button icon="el-icon-back" style="font-size: 16px" type="text" @click="userScopeDialog = false">返回</el-button>
|
||||
</div>
|
||||
<el-tabs tab-position="top" v-model="activeName" @tab-click="handleClick" style="margin: 20px">
|
||||
<el-tabs tab-position="top" v-model="activeName" @tab-click="handleClick" :before-leave="beforeTabLeave" style="margin: 20px">
|
||||
<el-tab-pane label="人员设置" name="1">
|
||||
<template>
|
||||
<user-scope ref="userScope" @group_change="group_change" :group_id.sync="groupId"></user-scope>
|
||||
@@ -12,7 +12,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="人员调整" name="2">
|
||||
<user-data-scope @group_change="group_change" ref="userDataScope"></user-data-scope>
|
||||
<user-data-scope v-if="activeName === '2'" @group_change="group_change" ref="userDataScope"></user-data-scope>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-drawer>
|
||||
@@ -32,9 +32,19 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
beforeTabLeave(name) {
|
||||
const permission = name === "2" ? "activity.basic.user" : "activity.basic.scope"
|
||||
if (!this.$auth.hasRole("SYSADMIN") && !this.$auth.hasPermission(permission)) {
|
||||
this.$message.error("无此权限:" + permission)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
handleClick() {
|
||||
if (this.activeName === "2") {
|
||||
this.$refs.userDataScope.getActivityGroup()
|
||||
if (this.$refs.userDataScope) {
|
||||
this.$refs.userDataScope.getActivityGroup()
|
||||
}
|
||||
} else {
|
||||
this.$refs.userScope.getActivityGroup()
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="setDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="doSetActivityUser" v-loading="settingLoading">确定</el-button>
|
||||
<el-button type="primary" :disabled="settingLoading" @click="doSetActivityUser">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
@@ -646,7 +646,7 @@ module.exports = {
|
||||
doSearch(showFullLoading = false) {
|
||||
this.tableKey = new Date().getTime()
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageData(showFullLoading === true)
|
||||
return this.pageData(showFullLoading === true)
|
||||
},
|
||||
async pageData(showFullLoading = false) {
|
||||
this.tableLoading = true
|
||||
@@ -679,43 +679,82 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
},
|
||||
async doSetActivityUser() {
|
||||
const valid = await this.$refs.setForm.validate()
|
||||
if (!valid) {
|
||||
doSetActivityUser() {
|
||||
if (!this.$auth.hasRole("SYSADMIN") && !this.$auth.hasPermission("activity.basic.scope")) {
|
||||
this.$message.error("无此权限:activity.basic.scope")
|
||||
return
|
||||
}
|
||||
// 方法级状态保护与整页遮罩共同阻止慢请求期间的重复确认。
|
||||
if (this.settingLoading) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.formData.groupType !== 2 && this.tableData.length === 0) {
|
||||
this.$message.warning("请先指定筛选条件!")
|
||||
return
|
||||
}
|
||||
|
||||
const confirm = await this.$confirm("是否将符合搜索条件的用户设为活动人员?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
|
||||
if (confirm === "confirm") {
|
||||
this.settingLoading = true
|
||||
const pageForm = this.normalizePageForm(this.pageForm)
|
||||
Object.assign(pageForm, this.formData)
|
||||
const resp = await $.post("/platform/activity/basic/scope/doSetActivityUser", {
|
||||
data: JSON.stringify(pageForm)
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
this.setDialogVisible = false
|
||||
await this.getActivityGroup()
|
||||
this.doSearch()
|
||||
this.$message.success(resp.msg)
|
||||
this.formData = this.getDefaultFormData()
|
||||
} else {
|
||||
this.$message.error(resp.msg)
|
||||
this.$refs.setForm.validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
this.settingLoading = false
|
||||
this.$emit("update:group_id", resp.data)
|
||||
this.$emit("group_change")
|
||||
}
|
||||
|
||||
if (this.formData.groupType !== 2 && this.tableData.length === 0) {
|
||||
this.$message.warning("请先指定筛选条件!")
|
||||
return
|
||||
}
|
||||
|
||||
this.$confirm("是否将符合搜索条件的用户设为活动人员?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
if (this.settingLoading) {
|
||||
return
|
||||
}
|
||||
|
||||
this.settingLoading = true
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: "正在设置活动人员,请稍候...",
|
||||
spinner: "el-icon-loading",
|
||||
background: "rgba(255, 255, 255, 0.65)"
|
||||
})
|
||||
Promise.resolve().then(() => {
|
||||
const pageForm = this.normalizePageForm(this.pageForm)
|
||||
Object.assign(pageForm, this.formData)
|
||||
return $.ajax({
|
||||
url: "/platform/activity/basic/scope/doSetActivityUser",
|
||||
type: "POST",
|
||||
data: { data: JSON.stringify(pageForm) },
|
||||
success: () => {}
|
||||
})
|
||||
}).then((resp) => {
|
||||
if (resp.code !== 0) {
|
||||
// 将后端业务错误转入统一失败分支,直接展示接口返回的真实原因。
|
||||
throw new Error(resp.msg || "设置活动人员失败")
|
||||
}
|
||||
|
||||
this.setDialogVisible = false
|
||||
// 设置成功后等待分组和人员列表刷新完成,再解除整页操作锁定。
|
||||
return this.getActivityGroup()
|
||||
.then(() => this.doSearch())
|
||||
.then(() => {
|
||||
this.$message.success(resp.msg)
|
||||
this.formData = this.getDefaultFormData()
|
||||
this.$emit("update:group_id", resp.data)
|
||||
this.$emit("group_change")
|
||||
})
|
||||
}).then(null, (error) => {
|
||||
// 兼容业务错误、权限异常和网络异常,避免请求失败后页面持续加载。
|
||||
const response = error && error.responseJSON ? error.responseJSON : null
|
||||
const errorMessage = response && response.msg
|
||||
? response.msg
|
||||
: error && error.message
|
||||
? error.message
|
||||
: "设置活动人员失败"
|
||||
this.$message.error(errorMessage)
|
||||
}).finally(() => {
|
||||
this.settingLoading = false
|
||||
loading.close()
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
async getActivityGroup() {
|
||||
const resp = await $.get("/platform/activity/basic/scope/getActivityUserScopeGroup")
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:finished="tableFinished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad">
|
||||
<div class="table-list-container">
|
||||
<div class="table-list-container" @click="showOverflowText">
|
||||
<div v-for="(row, index) in tableData" :key="index" class="table-list-item"
|
||||
:data-row-id="row && row.id ? row.id : ''">
|
||||
<slot name="header" :index="index" :row="row">
|
||||
@@ -36,6 +36,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
<van-popover ref="overflowPopover" :value="overflowVisible" :style="overflowAnchorStyle"
|
||||
:placement="overflowPlacement" get-container="body" @input="onOverflowInput"
|
||||
@opened="positionOverflow">
|
||||
<div class="table-list-overflow-content">{{ overflowText }}</div>
|
||||
<template #reference><span></span></template>
|
||||
</van-popover>
|
||||
</van-pull-refresh>
|
||||
</template>
|
||||
<script>
|
||||
@@ -90,10 +96,138 @@ module.exports = {
|
||||
tableLoading: false,
|
||||
tableFinished: false,
|
||||
tableRefreshing: false,
|
||||
localPageForm: {...this.page_form}
|
||||
localPageForm: {...this.page_form},
|
||||
overflowVisible: false,
|
||||
overflowText: "",
|
||||
overflowTarget: null,
|
||||
overflowPlacement: "top",
|
||||
overflowAnchorStyle: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 只响应真正被截断的纯文字,保留卡片内按钮和业务插槽原有的点击行为。
|
||||
showOverflowText(event) {
|
||||
const target = event.target
|
||||
if (!target || !target.closest) return
|
||||
const card = target.closest(".table-list-item")
|
||||
if (!card || target.closest(".item-actions, a, button, input, textarea, select, [role=button], [tabindex], [onclick]")) return
|
||||
if (target.children.length || !target.textContent.trim() || this.hasOverflowClickHandler(target, card)) return
|
||||
const style = window.getComputedStyle(target)
|
||||
if (style.textOverflow !== "ellipsis" || (target.scrollWidth <= target.clientWidth && target.scrollHeight <= target.clientHeight)) return
|
||||
|
||||
const rect = target.getBoundingClientRect()
|
||||
this.overflowTarget = target
|
||||
this.overflowText = target.textContent.trim()
|
||||
this.overflowPlacement = rect.top < 180 ? "bottom" : "top"
|
||||
this.overflowAnchorStyle = {
|
||||
position: "fixed",
|
||||
display: "block",
|
||||
pointerEvents: "none",
|
||||
left: rect.left + "px",
|
||||
top: rect.top + "px",
|
||||
width: rect.width + "px",
|
||||
height: rect.height + "px"
|
||||
}
|
||||
if (!this.overflowVisible) this.bindOverflowListeners()
|
||||
this.overflowVisible = true
|
||||
this.$nextTick(() => {
|
||||
const instance = this.$refs.overflowPopover
|
||||
if (instance) instance.updateLocation()
|
||||
setTimeout(() => this.positionOverflow(), 50)
|
||||
setTimeout(() => this.positionOverflow(), 350)
|
||||
})
|
||||
},
|
||||
|
||||
// 检查文字及其卡片内的祖先节点,避免干扰已有 Vue 点击事件。
|
||||
hasOverflowClickHandler(target, card) {
|
||||
const ancestors = []
|
||||
let element = target
|
||||
while (element && element !== card.parentElement) {
|
||||
ancestors.push(element)
|
||||
element = element.parentElement
|
||||
}
|
||||
const visit = (node) => {
|
||||
if (!node) return false
|
||||
const events = node.data ? [node.data.on, node.data.nativeOn] : []
|
||||
if (ancestors.includes(node.elm) && events.some((handlers) => handlers &&
|
||||
Object.keys(handlers).some((key) => /^[~!&]*(click|touchstart|pointerdown|mousedown)$/.test(key)))) return true
|
||||
if (node.componentInstance && visit(node.componentInstance._vnode)) return true
|
||||
return Array.isArray(node.children) && node.children.some((child) => visit(child))
|
||||
}
|
||||
return visit(this._vnode)
|
||||
},
|
||||
|
||||
// Vant 的弹层挂在 body;校正窗口边缘的位置,避免完整文字被屏幕裁掉。
|
||||
positionOverflow() {
|
||||
const instance = this.$refs.overflowPopover
|
||||
const popup = instance && instance.$refs.popover && instance.$refs.popover.$el
|
||||
if (!this.overflowVisible || !this.overflowTarget || !popup) return
|
||||
const rect = popup.getBoundingClientRect()
|
||||
const anchor = this.overflowTarget.getBoundingClientRect()
|
||||
if (!rect.width || !rect.height) return
|
||||
const margin = 8
|
||||
const left = Math.max(margin, Math.min(rect.left, window.innerWidth - rect.width - margin))
|
||||
let top = rect.top
|
||||
if (top < margin) top = anchor.bottom + 8
|
||||
if (top + rect.height > window.innerHeight - margin) top = anchor.top - rect.height - 8
|
||||
top = Math.max(margin, Math.min(top, window.innerHeight - rect.height - margin))
|
||||
popup.style.left = (parseFloat(popup.style.left) || 0) + left - rect.left + "px"
|
||||
popup.style.top = (parseFloat(popup.style.top) || 0) + top - rect.top + "px"
|
||||
const arrow = popup.querySelector(".van-popover__arrow")
|
||||
if (arrow) {
|
||||
arrow.style.left = Math.max(12, Math.min(anchor.left + anchor.width / 2 - left, rect.width - 12)) + "px"
|
||||
}
|
||||
},
|
||||
|
||||
onOverflowInput(visible) {
|
||||
this.overflowVisible = visible
|
||||
if (!visible) {
|
||||
this.overflowTarget = null
|
||||
this.unbindOverflowListeners()
|
||||
}
|
||||
},
|
||||
|
||||
// 监听只在气泡打开时存在,PJAX 替换页面前也会关闭气泡并注销。
|
||||
bindOverflowListeners() {
|
||||
document.addEventListener("click", this.closeOverflowOnClick)
|
||||
window.addEventListener("scroll", this.closeOverflowOnMove, true)
|
||||
window.addEventListener("resize", this.closeOverflowOnMove)
|
||||
window.addEventListener("transitionend", this.onOverflowAnimationEnd)
|
||||
window.addEventListener("animationend", this.onOverflowAnimationEnd)
|
||||
$(document).on("pjax:send", this.closeOverflowForNavigation)
|
||||
},
|
||||
|
||||
unbindOverflowListeners() {
|
||||
document.removeEventListener("click", this.closeOverflowOnClick)
|
||||
window.removeEventListener("scroll", this.closeOverflowOnMove, true)
|
||||
window.removeEventListener("resize", this.closeOverflowOnMove)
|
||||
window.removeEventListener("transitionend", this.onOverflowAnimationEnd)
|
||||
window.removeEventListener("animationend", this.onOverflowAnimationEnd)
|
||||
$(document).off("pjax:send", this.closeOverflowForNavigation)
|
||||
},
|
||||
|
||||
closeOverflowForNavigation() {
|
||||
this.onOverflowInput(false)
|
||||
},
|
||||
|
||||
// Vant 会在动画结束时重新计算定位,随后再次限制气泡的屏幕边界。
|
||||
onOverflowAnimationEnd() {
|
||||
if (this.overflowVisible) setTimeout(() => this.positionOverflow(), 0)
|
||||
},
|
||||
|
||||
// 滚动或改变屏幕尺寸时关闭气泡,避免气泡脱离原文字位置。
|
||||
closeOverflowOnMove(event) {
|
||||
if (event && event.target && event.target.closest && event.target.closest(".table-list-overflow-content")) return
|
||||
this.onOverflowInput(false)
|
||||
},
|
||||
|
||||
closeOverflowOnClick(event) {
|
||||
if (!this.overflowVisible) return
|
||||
if (this.overflowTarget && this.overflowTarget.contains(event.target)) return
|
||||
if (event.target.closest && event.target.closest(".table-list-overflow-content")) return
|
||||
this.onOverflowInput(false)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取当前列表快照。
|
||||
* 返回值包含已加载记录、分页参数和加载完成状态,供业务页面在进入详情前暂存列表现场。
|
||||
@@ -186,10 +320,28 @@ module.exports = {
|
||||
},
|
||||
mounted() {
|
||||
this.$emit("ready")
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unbindOverflowListeners()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Vant 将气泡挂到 body,控制宽高并允许长文本在气泡内滚动。 */
|
||||
.table-list-overflow-content {
|
||||
max-width: min(240px, calc(100vw - 48px));
|
||||
max-height: 160px;
|
||||
padding: 10px 12px;
|
||||
overflow-y: auto;
|
||||
color: #253247;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.table-list-container {
|
||||
padding: 0;
|
||||
|
||||
@@ -25,10 +25,7 @@ module.exports = {
|
||||
watch: {
|
||||
value: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.signatureContent = val
|
||||
this.$emit("input", this.signatureContent)
|
||||
}
|
||||
this.signatureContent = val || null
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
@@ -52,6 +49,8 @@ module.exports = {
|
||||
*/
|
||||
init() {
|
||||
this.signatureAddress = origin + "/platform/signature/scanPcCode?id=" + this.id
|
||||
// 打印当前二维码的签字入口,便于从控制台复制地址进行签字测试。
|
||||
console.info("signatureAddress:", this.signatureAddress)
|
||||
return this.$axios.post("/platform/signature/get")
|
||||
.then((res) => {
|
||||
if (this._isBeingDestroyed || this._isDestroyed) {
|
||||
@@ -60,8 +59,11 @@ module.exports = {
|
||||
if (res.code !== 0) {
|
||||
throw new Error("签字信息加载失败")
|
||||
}
|
||||
this.signatureContent = res.data && res.data.signature
|
||||
this.$emit("input", this.signatureContent)
|
||||
// 已保存的业务签字或刚收到的扫码签字优先,默认签名仅补充空值。
|
||||
if (!this.value && !this.signatureContent && res.data && res.data.signature) {
|
||||
this.signatureContent = res.data.signature
|
||||
this.$emit("input", this.signatureContent)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 返回等待完成的 Promise;连接成功、等待30秒或组件销毁时结束等待。
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="h5-user-select">
|
||||
<van-field v-bind="$attrs" :value="displayName" readonly clickable is-link @click="open"></van-field>
|
||||
<van-popup v-model="visible" position="bottom" round get-container="body" class="h5-user-select-popup">
|
||||
<div class="h5-user-select-popup__header">
|
||||
<van-button native-type="button" type="default" size="small" @click="visible = false">取消</van-button>
|
||||
<strong>选择慰问对象</strong>
|
||||
<van-button native-type="button" type="info" size="small" :disabled="!pendingUser" @click="confirm">确认</van-button>
|
||||
</div>
|
||||
<van-search v-model="keyword" placeholder="请输入姓名或工号查询" @input="scheduleSearch" @search="search" @clear="search"></van-search>
|
||||
<div class="h5-user-select-popup__list">
|
||||
<div v-if="loading" class="h5-user-select-popup__status"><van-loading size="24px">加载中</van-loading></div>
|
||||
<div v-else-if="error" class="h5-user-select-popup__status"><span class="h5-user-select-popup__error">{{ error }}</span><van-button native-type="button" size="small" @click="search">重新加载</van-button></div>
|
||||
<van-empty v-else-if="!options.length" description="未找到本工会人员"></van-empty>
|
||||
<template v-else>
|
||||
<van-cell v-for="person in options" :key="person.id" clickable
|
||||
:class="{ 'h5-user-select-popup__selected': pendingUser && pendingUser.id === person.id }"
|
||||
:title="person.userName + (person.loginName ? '(' + person.loginName + ')' : '')"
|
||||
:label="[person.unitName, person.personType].filter(Boolean).join(' · ')"
|
||||
@click="pendingUser = person">
|
||||
<van-icon v-if="pendingUser && pendingUser.id === person.id" name="success" color="#1989fa"></van-icon>
|
||||
</van-cell>
|
||||
</template>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
inheritAttrs: false,
|
||||
model: { prop: 'value', event: 'change' },
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
displayName: { type: String, default: '' },
|
||||
api: { type: String, required: true }
|
||||
},
|
||||
data() {
|
||||
return { visible: false, keyword: '', options: [], pendingUser: null, loading: false, error: '', searchTimer: null, requestId: 0 }
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearTimeout(this.searchTimer)
|
||||
this.requestId++
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.keyword = ''
|
||||
this.pendingUser = null
|
||||
this.visible = true
|
||||
this.search()
|
||||
},
|
||||
scheduleSearch() {
|
||||
clearTimeout(this.searchTimer)
|
||||
this.requestId++
|
||||
this.pendingUser = null
|
||||
this.loading = true
|
||||
this.searchTimer = setTimeout(() => this.search(), 250)
|
||||
},
|
||||
async search() {
|
||||
clearTimeout(this.searchTimer)
|
||||
const requestId = ++this.requestId
|
||||
this.loading = true
|
||||
this.error = ''
|
||||
this.pendingUser = null
|
||||
try {
|
||||
const result = await this.$axios.post(this.api, { keyword: this.keyword })
|
||||
if (requestId !== this.requestId) return
|
||||
if (result.code !== 0) throw new Error(result.msg || '人员加载失败')
|
||||
this.options = result.data || []
|
||||
} catch (error) {
|
||||
if (requestId !== this.requestId) return
|
||||
this.options = []
|
||||
this.error = error.message || '人员加载失败'
|
||||
} finally {
|
||||
if (requestId === this.requestId) this.loading = false
|
||||
}
|
||||
},
|
||||
confirm() {
|
||||
if (!this.pendingUser) return
|
||||
const person = this.pendingUser
|
||||
this.$emit('change', person.id)
|
||||
this.$emit('selected', person, person.id)
|
||||
this.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.h5-user-select-popup { display: flex; flex-direction: column; height: 72vh; max-height: 640px; padding-bottom: env(safe-area-inset-bottom); box-sizing: border-box; }
|
||||
.h5-user-select-popup__header { display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; padding: 14px 16px; border-bottom: 1px solid #ebedf0; }
|
||||
.h5-user-select-popup__header strong { font-size: 16px; color: #323233; }
|
||||
.h5-user-select-popup__header .van-button { min-width: 56px; }
|
||||
.h5-user-select-popup__list { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }
|
||||
.h5-user-select-popup__selected { background: #edf6ff; }
|
||||
.h5-user-select-popup__status { display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 12px; padding: 40px 16px; }
|
||||
.h5-user-select-popup__error { color: #ee0a24; }
|
||||
</style>
|
||||
@@ -106,7 +106,9 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
onChange(val) {
|
||||
const selected = this.options.find(item => String(item[this.option_value]) === String(val)) || null
|
||||
this.$emit("change", val)
|
||||
this.$emit("selected", selected, val)
|
||||
},
|
||||
|
||||
clearOptions() {
|
||||
@@ -114,7 +116,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.clearOptions()
|
||||
this.options = this.option_list || []
|
||||
console.log(this.placeholder)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -706,7 +706,7 @@
|
||||
Vue.component("year-van-dropdown-item", httpVueLoader("/components/plugins/vantMore/yearVanDropDownItem.vue?v=" + h5ComponentVersion))
|
||||
Vue.component("van-text-dialog", httpVueLoader("/components/plugins/vantMore/vantTextDialog.vue?v=" + h5ComponentVersion))
|
||||
Vue.component("enum-tag", httpVueLoader("/components/plugins/sysEnum/EnumTag.vue?v=" + h5ComponentVersion))
|
||||
Vue.component("table-list", httpVueLoader("/components/plugins/h5/TableList.vue?v=" + h5ComponentVersion))
|
||||
Vue.component("table-list", httpVueLoader("/components/plugins/h5/TableList.vue?v=" + h5ComponentVersion + "_overflow1"))
|
||||
Vue.component("table-column", httpVueLoader("/components/plugins/h5/TableColumn.vue?v=" + h5ComponentVersion))
|
||||
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/H5Index.vue?v=" + h5ComponentVersion))
|
||||
</script>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
<!-- 引入 form-create 和 designer -->
|
||||
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
|
||||
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js?v=20261030"></script>
|
||||
|
||||
<!-- swiper -->
|
||||
<script src="${base!}/assets/platform/plugins/swiper/swiper-bundle.js"></script>
|
||||
|
||||
@@ -10,22 +10,23 @@ const act = {
|
||||
<div ref="activitySwiper" class="swiper activity-swiper">
|
||||
<div class="swiper-wrapper">
|
||||
<div class="swiper-slide" v-for="(activity, index) in actList" :key="activity.id">
|
||||
<div class="item" @click="go(activity)">
|
||||
<div class="item" role="button" tabindex="0" @click="go(activity)" @keydown.enter="go(activity)" @keydown.space.prevent="go(activity)">
|
||||
<div class="img-box">
|
||||
<img :src="activity.cover || 'https://www.ncu.edu.cn/__local/8/52/2C/644986B4C9A030F7B65300178A6_8679CB08_18467.jpg'"
|
||||
alt="">
|
||||
<span class="status">
|
||||
<el-image :src="activity.cover" fit="contain" :alt="activity.name">
|
||||
<div slot="error" class="portal-cover-empty"><i class="fa fa-picture-o" aria-hidden="true"></i><span>暂无图片</span></div>
|
||||
</el-image>
|
||||
<span class="status" :class="{'status-ended': getStatusText(activity) === '已结束', 'status-upcoming': getStatusText(activity) === '未开始'}">
|
||||
{{getStatusText(activity)}}
|
||||
</span>
|
||||
</div>
|
||||
<h4 class="title">
|
||||
<h4 class="title" :title="activity.name">
|
||||
{{ activity.name}}
|
||||
</h4>
|
||||
<!--报名时间-->
|
||||
<div class="time">
|
||||
<p>
|
||||
<p :title="[activity.startDate, activity.endDate].filter(Boolean).join(' 至 ')">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
时间:{{ activity.startDate }}至{{ activity.endDate }}
|
||||
{{ formatActivityDate(activity.startDate) }} 至 {{ formatActivityDate(activity.endDate) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,20 +35,21 @@ const act = {
|
||||
</div>
|
||||
|
||||
<!-- 导航按钮 -->
|
||||
<div ref="nextButton" class="swiper-button-next"></div>
|
||||
<div ref="prevButton" class="swiper-button-prev"></div>
|
||||
<button type="button" ref="nextButton" class="swiper-button-next" aria-label="下一组活动"></button>
|
||||
<button type="button" ref="prevButton" class="swiper-button-prev" aria-label="上一组活动"></button>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<div ref="pagination" class="swiper-pagination"></div>
|
||||
</div>
|
||||
|
||||
<div v-else class="no-activity-placeholder">
|
||||
<div class="icon">📅</div>
|
||||
<div class="icon"><i class="fa fa-calendar" aria-hidden="true"></i></div>
|
||||
<p>暂无近期活动</p>
|
||||
<p class="subtext">敬请关注后续通知</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
@@ -57,19 +59,57 @@ const act = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/*查询活动*/
|
||||
// 轮播初始化或尺寸变化后,将左右箭头对齐当前封面的垂直中心。
|
||||
updateNavigationPosition() {
|
||||
this.syncBrowseState()
|
||||
const swiperElement = this.$refs.activitySwiper
|
||||
const cover = swiperElement && swiperElement.querySelector('.img-box')
|
||||
if (!cover) return
|
||||
// 按实际封面高度定位,避免标题换行或响应式列数变化导致箭头偏移。
|
||||
const container = swiperElement.parentElement
|
||||
const top = cover.getBoundingClientRect().top - container.getBoundingClientRect().top + cover.offsetHeight / 2
|
||||
;[this.$refs.prevButton, this.$refs.nextButton].forEach(button => {
|
||||
if (button) button.style.top = top + 'px'
|
||||
})
|
||||
},
|
||||
// value 为接口返回的活动日期;无日期时显示待定,避免展示 Invalid date。
|
||||
formatActivityDate(value) {
|
||||
return value ? this.$moment(value).format('YYYY-MM-DD') : '待定'
|
||||
},
|
||||
// 按轮播实际停靠点判断是否还有内容,尺寸改变后同步首页按钮文案和可用状态。
|
||||
syncBrowseState() {
|
||||
const enabled = !!(this.swiper && !this.swiper.destroyed && !this.swiper.isLocked && this.swiper.snapGrid.length > 1)
|
||||
this.$emit('browse-state', {
|
||||
enabled: enabled,
|
||||
label: enabled ? '浏览更多' : (this.actList.length ? '已展示全部活动' : '暂无活动')
|
||||
})
|
||||
},
|
||||
// 点击前刷新尺寸,避免异步布局导致轮播状态过期;到末组后返回首组。
|
||||
browseNext() {
|
||||
if (!this.swiper || this.swiper.destroyed) this.initSwiper()
|
||||
if (!this.swiper) return
|
||||
this.swiper.update()
|
||||
this.syncBrowseState()
|
||||
if (this.swiper.isLocked || this.swiper.snapGrid.length <= 1) return
|
||||
this.swiper.isEnd ? this.swiper.slideTo(0) : this.swiper.slideNext()
|
||||
},
|
||||
// 无请求参数;data 为当前用户可见且尚未结束的活动数组,保留本校筛选规则。
|
||||
listAct() {
|
||||
this.$axios.post("/platform/home/listHomeActivity").then((res) => {
|
||||
if (res.code === 0) {
|
||||
// for (let i = 0; i < 10; i++) {
|
||||
// this.actList = this.actList.concat(res.data)
|
||||
// }
|
||||
this.actList = res.data
|
||||
this.actList = res.data || []
|
||||
// 数据加载完成后初始化Swiper
|
||||
this.$nextTick(() => {
|
||||
this.initSwiper()
|
||||
})
|
||||
} else {
|
||||
this.$emit('browse-state', {enabled: false, label: '活动加载失败'})
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$emit('browse-state', {enabled: false, label: '活动加载失败'})
|
||||
})
|
||||
},
|
||||
/*初始化Swiper*/
|
||||
@@ -80,6 +120,7 @@ const act = {
|
||||
}
|
||||
const swiperElement = this.$refs.activitySwiper
|
||||
if (!swiperElement) {
|
||||
this.syncBrowseState()
|
||||
return
|
||||
}
|
||||
// 仅操作当前组件的轮播节点,避免页面其他轮播或尺寸观察器影响卡片宽度。
|
||||
@@ -89,13 +130,22 @@ const act = {
|
||||
centeredSlides: false,
|
||||
loop: false,
|
||||
watchOverflow: true,
|
||||
// 双栏中的活动按容器宽度排布,避免用整个窗口宽度挤出过多卡片。
|
||||
breakpointsBase: 'container',
|
||||
observer: false,
|
||||
observeParents: false,
|
||||
resizeObserver: false,
|
||||
// 页面内容加载后可能出现滚动条,需重新计算列宽,避免末张封面及圆角被裁切。
|
||||
resizeObserver: true,
|
||||
roundLengths: true,
|
||||
on: {
|
||||
init: () => this.$nextTick(() => this.updateNavigationPosition()),
|
||||
resize: () => this.$nextTick(() => this.updateNavigationPosition()),
|
||||
slidesUpdated: () => this.$nextTick(() => this.updateNavigationPosition()),
|
||||
},
|
||||
// 鼠标停留在活动区域时可横向浏览,到首尾后释放页面滚动。
|
||||
mousewheel: {
|
||||
enabled: true,
|
||||
forceToAxis: false,
|
||||
forceToAxis: true,
|
||||
releaseOnEdges: true,
|
||||
sensitivity: 0.8,
|
||||
},
|
||||
@@ -108,17 +158,15 @@ const act = {
|
||||
type: 'progressbar',
|
||||
},
|
||||
breakpoints: {
|
||||
768: {
|
||||
480: {
|
||||
slidesPerView: 2,
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 4,
|
||||
},
|
||||
1200: {
|
||||
slidesPerView: 5,
|
||||
740: {
|
||||
slidesPerView: 3,
|
||||
}
|
||||
}
|
||||
})
|
||||
this.syncBrowseState()
|
||||
},
|
||||
|
||||
// 活动状态
|
||||
@@ -139,7 +187,7 @@ const act = {
|
||||
}
|
||||
if (act.url === "/platform/activity/apply"
|
||||
&& !this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN", "BRANCH_UNION_CHAIRMAN", "BRANCH_UNION_WENTI_SPORTS"])) {
|
||||
return this.$message.warning("PC端暂未开放报名,请前往移动端报名!")
|
||||
return this.$message.warning("温馨提示:您没有参加该活动的权限!")
|
||||
|
||||
}
|
||||
window.open(act.url)
|
||||
@@ -308,12 +356,9 @@ const act = {
|
||||
height: 100% !important;
|
||||
max-width: 100% !important;
|
||||
max-height: 128px !important;
|
||||
object-fit: fill !important;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.activity-swiper .swiper-slide .item:hover .img-box img {
|
||||
transform: scale(1.1);
|
||||
/* 活动海报含有文字,等比完整显示且悬停时不放大,避免内容被裁切。 */
|
||||
object-fit: contain !important;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.activity-swiper .swiper-slide .item h4 {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
const entry = {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="entry-wrapper">
|
||||
<div :class="['entry-section', 'entry-section-' + section.key]" v-for="section in entrySections" :key="section.key">
|
||||
<div class="entry-section-header">
|
||||
<div class="entry-section-title">
|
||||
<i :class="section.icon"></i>
|
||||
<span>{{ section.title }}</span>
|
||||
<em>/Applications</em>
|
||||
</div>
|
||||
<div class="portal-entry-header">
|
||||
<div class="portal-entry-tabs" role="tablist" aria-label="推荐入口">
|
||||
<button v-for="section in entrySections" :key="section.key"
|
||||
type="button" role="tab" :aria-selected="activeSection === section.key"
|
||||
:class="{active: activeSection === section.key}" @click="switchSection(section.key)">{{ section.title }}</button>
|
||||
</div>
|
||||
<a class="portal-more" href="/platform/v4/apps">全部应用 <i class="fa fa-arrow-right" aria-hidden="true"></i></a>
|
||||
</div>
|
||||
<div :class="['entry-section', 'entry-section-' + section.key]" v-for="section in entrySections" :key="section.key" v-show="activeSection === section.key" role="tabpanel">
|
||||
|
||||
<div class="apps-container">
|
||||
<div class="app-grid">
|
||||
<div class="app-item"
|
||||
<div class="app-grid" tabindex="0" :aria-label="section.title + ',可横向滚动浏览'">
|
||||
<button type="button" class="app-item"
|
||||
v-for="(item, index) in section.list"
|
||||
:key="item.id || index"
|
||||
@click="openService(item, section.key)">
|
||||
@@ -20,8 +21,8 @@ const entry = {
|
||||
<img v-if="item.picIcon" class="app-icon-img" :src="item.picIcon" alt="" />
|
||||
<i v-else class="fa fa-cube app-icon-fallback"></i>
|
||||
</div>
|
||||
<div class="app-name">{{ item.name }}</div>
|
||||
</div>
|
||||
<div class="app-name" :title="item.name">{{ item.name }}</div>
|
||||
</button>
|
||||
<el-empty class="app-empty"
|
||||
v-if="!section.list || section.list.length === 0"
|
||||
description="暂无数据"
|
||||
@@ -30,10 +31,12 @@ const entry = {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
activeSection: 'serv',
|
||||
hasMore: true,
|
||||
entries: {
|
||||
serv: [],
|
||||
@@ -57,8 +60,12 @@ const entry = {
|
||||
this.getFavorite()
|
||||
},
|
||||
methods: {
|
||||
// 查询推荐服务
|
||||
async getRecommendService() {
|
||||
// sectionKey 为 serv(推荐服务)、app(推荐应用)或 fav(我的收藏);通过组件方法更新栏目,避免内联事件中 this 指向错误。
|
||||
switchSection(sectionKey) {
|
||||
this.$set(this, 'activeSection', sectionKey)
|
||||
},
|
||||
// platform 固定传 PC;Result.data 为当前用户有权限的推荐服务数组。
|
||||
getRecommendService() {
|
||||
this.$axios.post('/platform/home/listRecommendService', {platform: "PC"}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.entries.serv = res.data
|
||||
@@ -66,8 +73,8 @@ const entry = {
|
||||
})
|
||||
},
|
||||
|
||||
// 获取推荐应用
|
||||
async getRecommendApp() {
|
||||
// platform 固定传 PC;Result.data 为当前用户可进入的推荐应用数组。
|
||||
getRecommendApp() {
|
||||
this.$axios.post('/platform/home/listRecommendApp', {platform: "PC"}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.entries.app = res.data
|
||||
@@ -75,8 +82,8 @@ const entry = {
|
||||
})
|
||||
},
|
||||
|
||||
// 获取我的收藏
|
||||
async getFavorite() {
|
||||
// platform 固定传 PC;Result.data 为当前用户收藏且有权限的菜单数组。
|
||||
getFavorite() {
|
||||
this.$axios.post('/platform/home/listFavorite', {platform: "PC"}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.entries.fav = res.data
|
||||
@@ -127,11 +134,6 @@ const entry = {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
/* 临时屏蔽“我的收藏”分组,保留原节点和数据逻辑便于恢复 */
|
||||
.entry-section-fav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.entry-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -116,44 +116,130 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* 最新活动的作用域样式会在组件挂载后注入,首屏先固定布局和图片尺寸,避免刷新时放大闪烁。 */
|
||||
#v4-home-app .act-card .activity-section-wrapper,
|
||||
#v4-home-app .act-card .section-act,
|
||||
#v4-home-app .act-card .activity-swiper {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#v4-home-app .act-card .activity-swiper-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 9px 54px 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#v4-home-app .act-card .activity-swiper .swiper-slide {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-width: 0;
|
||||
flex-shrink: 0;
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
#v4-home-app .act-card .activity-swiper .img-box {
|
||||
width: 100%;
|
||||
height: 128px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#v4-home-app .act-card .activity-swiper .img-box img {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
max-width: 100% !important;
|
||||
max-height: 128px !important;
|
||||
object-fit: fill !important;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
#v4-home-app .act-card .activity-swiper .swiper-slide {
|
||||
width: calc((100% - 18px) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
#v4-home-app .act-card .activity-swiper .swiper-slide {
|
||||
width: calc((100% - 54px) / 4);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
#v4-home-app .act-card .activity-swiper .swiper-slide {
|
||||
width: calc((100% - 72px) / 5);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="v4-container" id="v4-home-app">
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/home-portal.css?v=20261030-1" />
|
||||
|
||||
<!-- 登录资料仅在 HTML 属性中输出并转义;放在 Vue 挂载区域外,避免名称被当作模板表达式解析。 -->
|
||||
<div id="v4-home-profile-data" hidden
|
||||
data-username="${@auth.getPrincipalProperty('username'),escape}"
|
||||
data-loginname="${@auth.getPrincipalProperty('loginname'),escape}"
|
||||
<!--#if(!isEmpty(@auth.getPrincipalProperty('unit'))){#-->
|
||||
data-unit-name="${@auth.getPrincipalProperty('unit').getName(),escape}"
|
||||
<!--#}#-->
|
||||
<!--#if(!isEmpty(@auth.getPrincipalProperty('union'))){#-->
|
||||
data-union-name="${@auth.getPrincipalProperty('union').getName(),escape}"
|
||||
<!--#}#-->
|
||||
></div>
|
||||
|
||||
<div class="v4-container home-portal" id="v4-home-app">
|
||||
<!-- 首页banner -->
|
||||
<div class="section-banner">
|
||||
<div class="banner-img">
|
||||
<img :src="homeBannerList[0] || ''" alt=""/>
|
||||
<stats></stats>
|
||||
<div class="portal-top">
|
||||
<div class="portal-hero">
|
||||
<!-- 使用参考项目的本地背景资源,保留横幅文字及渐变遮罩。 -->
|
||||
<img class="portal-hero-background" src="${base!}/assets/platform/img/home/service-portal-banner-campus-v4.png" alt="" />
|
||||
<div class="portal-hero-inner">
|
||||
<div class="portal-hero-copy">
|
||||
<div class="portal-hero-eyebrow">教职工一站式服务平台</div>
|
||||
<h1>智慧工会系统</h1>
|
||||
<p>情系教职工,服务零距离</p>
|
||||
<div class="portal-values">
|
||||
<span><i class="fa fa-heart" aria-hidden="true"></i>服务职工</span>
|
||||
<span><i class="fa fa-users" aria-hidden="true"></i>凝聚职工</span>
|
||||
<span><i class="fa fa-leaf" aria-hidden="true"></i>关爱职工</span>
|
||||
<span><i class="fa fa-file-text" aria-hidden="true"></i>助力发展</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<user class="portal-member" :profile-only="true" :profile="memberProfile"></user>
|
||||
</div>
|
||||
|
||||
<div class="" style="padding-bottom: 20px">
|
||||
<user></user>
|
||||
|
||||
<div class="home-grid">
|
||||
<div class="portal-content">
|
||||
<div class="entry-card">
|
||||
<entry></entry>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="home-grid">
|
||||
<div class="home-section">
|
||||
<div class="portal-columns">
|
||||
<div class="home-section portal-panel portal-activities">
|
||||
<div class="home-section-header">
|
||||
<div class="home-section-title">
|
||||
<i class="fa fa-calendar"></i>
|
||||
<span>最新活动</span>
|
||||
<em>/Activities</em>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="portal-more" :disabled="!activityBrowse.enabled" @click="$refs.homeActivities.browseNext()">{{ activityBrowse.label }} <i v-if="activityBrowse.enabled" class="fa fa-arrow-right" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="act-card">
|
||||
<act></act>
|
||||
<act ref="homeActivities" @browse-state="updateActivityBrowse"></act>
|
||||
</div>
|
||||
</div>
|
||||
<user ref="homeTodo" class="portal-todo"></user>
|
||||
<work-template class="portal-templates" v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN'])"></work-template>
|
||||
<jcdt class="portal-news" :class="{'portal-news-wide': !$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN'])}" :list="websiteNews"></jcdt>
|
||||
</div>
|
||||
|
||||
<work-template v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN'])"></work-template>
|
||||
|
||||
<jcdt :list="websiteNews"></jcdt>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -170,9 +256,17 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
new Vue({
|
||||
el: '#v4-home-app',
|
||||
data(){
|
||||
// 浏览器读取属性时还原 HTML 转义;未配置单位或工会时使用空字符串,不访问空对象。
|
||||
const profileData = document.getElementById('v4-home-profile-data').dataset
|
||||
return{
|
||||
memberProfile: {
|
||||
username: profileData.username || '',
|
||||
loginname: profileData.loginname || '',
|
||||
unitName: profileData.unitName || '',
|
||||
unionName: profileData.unionName || ''
|
||||
},
|
||||
websiteNews: [],
|
||||
homeBannerList: "${config.AppHomeImg!}".split(",").map(item => item.trim()).filter(item => item)
|
||||
activityBrowse: {enabled: false, label: '活动加载中'}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -187,10 +281,14 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
this.getWebSiteNews()
|
||||
},
|
||||
methods: {
|
||||
// state 由活动组件发布,enabled 表示可以翻页,label 说明加载或全部展示状态。
|
||||
updateActivityBrowse(state) {
|
||||
this.$set(this, 'activityBrowse', state)
|
||||
},
|
||||
getWebSiteNews(){
|
||||
this.$axios.post('/platform/home/getNews').then(res => {
|
||||
if (res.code === 0) {
|
||||
this.websiteNews = res.data
|
||||
this.$set(this, 'websiteNews', res.data || [])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,44 +2,72 @@ const jcdt = {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="jcdt-wrapper">
|
||||
<div class="jcdt-container">
|
||||
<el-carousel height="360px" :interval="10000" indicator-position="none">
|
||||
<el-carousel-item v-for="item,index in list" :key="index">
|
||||
<div class="jcdt-title">
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
<div class="jcdt-content">
|
||||
<div class="jcdt-list">
|
||||
<div class="jcdt-item" v-for="o,i in item.value" :key="i" @click="onLink(o)">
|
||||
<div class="date" v-if="o.time">
|
||||
<i class="el-icon-alarm-clock"></i>
|
||||
<span>{{o.time}}</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div>
|
||||
|
||||
</div>
|
||||
<div class="title">{{o.text}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<div class="portal-news-header">
|
||||
<el-dropdown @command="selectSection" trigger="click">
|
||||
<button type="button" class="portal-news-title"><i class="fa fa-bullhorn" aria-hidden="true"></i> {{ currentNews.label }} <i v-if="list.length > 1" class="fa fa-angle-down" aria-hidden="true"></i></button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item v-for="(section, index) in list" :key="index" :command="index">{{ section.label }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<div v-if="pageCount > 1" class="portal-news-pager" aria-label="动态分页">
|
||||
<span class="portal-news-count">共 {{ currentNews.value.length }} 条</span>
|
||||
<button type="button" :disabled="pageNumber === 1" title="上一页动态" aria-label="上一页动态" @click="changePage(-1)"><i class="fa fa-angle-left" aria-hidden="true"></i></button>
|
||||
<span class="portal-news-page" aria-live="polite">{{ pageNumber }} / {{ pageCount }}</span>
|
||||
<button type="button" :disabled="pageNumber === pageCount" title="下一页动态" aria-label="下一页动态" @click="changePage(1)"><i class="fa fa-angle-right" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="portal-news-list">
|
||||
<button type="button" class="portal-news-row" v-for="(item, index) in visibleNews" :key="index" @click="onLink(item)">
|
||||
<time v-if="item.time" :title="item.time">{{ item.time.slice(5) }}</time>
|
||||
<span :title="item.text">{{ item.text }}</span>
|
||||
</button>
|
||||
<div v-if="!currentNews.value.length" class="portal-empty">暂无动态</div>
|
||||
</div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {}
|
||||
return {activeIndex: 0, pageNumber: 1, pageSize: 3}
|
||||
},
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: []
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentNews() {
|
||||
// 默认遵循接口栏目顺序,不再按栏目名称优先选择。
|
||||
const section = this.list[this.activeIndex] || this.list[0]
|
||||
return section ? {label: section.label, value: section.value || []} : {label: '最新动态', value: []}
|
||||
},
|
||||
visibleNews() {
|
||||
const start = (this.pageNumber - 1) * this.pageSize
|
||||
return this.currentNews.value.slice(start, start + this.pageSize)
|
||||
},
|
||||
pageCount() {
|
||||
return Math.max(1, Math.ceil(this.currentNews.value.length / this.pageSize))
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list() {
|
||||
this.selectSection(0)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// step 为翻页方向(-1 上一页、1 下一页);在组件方法中更新页码,避免事件中的 this 指向错误。
|
||||
changePage(step) {
|
||||
const page = Math.max(1, Math.min(this.pageCount, this.pageNumber + step))
|
||||
this.$set(this, 'pageNumber', page)
|
||||
},
|
||||
// index 为接口栏目数组下标;切换栏目时回到第一页,避免沿用上一栏的末页。
|
||||
selectSection(index) {
|
||||
this.activeIndex = index
|
||||
this.pageNumber = 1
|
||||
},
|
||||
onLink(item) {
|
||||
// 接口返回文章完整地址;相对地址按当前学校工会域名解析。
|
||||
// 完整新闻地址直接打开,相对地址仍以重庆工贸工会网站为基准。
|
||||
const url = this.isHttpOrHttps(item.href) ? item.href : new URL(item.href, "https://gh.cqgmy.edu.cn/").href
|
||||
window.open(url)
|
||||
},
|
||||
@@ -48,6 +76,15 @@ const jcdt = {
|
||||
}
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
/* 每页固定三行,不因展开或末页条数变化撑高相邻的模板卡片。 */
|
||||
#v4-home-app .portal-news-list { min-height: 132px; }
|
||||
#v4-home-app .portal-news-list .portal-news-row { height: 44px; padding: 9px 0; }
|
||||
#v4-home-app .portal-news-pager { display: flex; flex-shrink: 0; align-items: center; gap: 8px; }
|
||||
#v4-home-app .portal-news-count { margin-right: 4px; color: #8a9aac; font-size: 12px; }
|
||||
#v4-home-app .portal-news-page { min-width: 32px; color: #637b96; text-align: center; font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
#v4-home-app .portal-news-pager button { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border: 1px solid #e4edf7; border-radius: 6px; background: #fff; color: #1687f5; font-size: 18px; cursor: pointer; }
|
||||
#v4-home-app .portal-news-pager button:hover:not(:disabled) { border-color: #b9d8fb; background: #f0f7ff; }
|
||||
#v4-home-app .portal-news-pager button:disabled { border-color: #eef2f7; background: #f8fafc; color: #c3cedb; cursor: default; }
|
||||
.jcdt-wrapper {
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
|
||||
@@ -5,21 +5,22 @@ const workTemplate = {
|
||||
<div class="work-template-title">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
<span>工作模板</span>
|
||||
<em>/Templates</em>
|
||||
</div>
|
||||
<button type="button" class="portal-more" @click="browseTemplates">更多模板 <i class="fa fa-arrow-right" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="work-template-card">
|
||||
<div v-if="templateList && templateList.length > 0" class="work-template-grid">
|
||||
<div class="work-template-item"
|
||||
<div ref="templateGrid" v-if="templateList && templateList.length > 0" class="work-template-grid">
|
||||
<button type="button" class="work-template-item"
|
||||
v-for="template in templateList"
|
||||
:key="template.id"
|
||||
@click="go(template)">
|
||||
<div class="work-template-icon">
|
||||
<img :src="template.templateIcon || template.cover || 'https://www.ncu.edu.cn/__local/8/52/2C/644986B4C9A030F7B65300178A6_8679CB08_18467.jpg'"
|
||||
alt="">
|
||||
<el-image :src="template.templateIcon || template.cover" fit="contain">
|
||||
<i slot="error" class="fa fa-clipboard" aria-hidden="true"></i>
|
||||
</el-image>
|
||||
</div>
|
||||
<div class="work-template-name">{{ template.templateName || template.name }}</div>
|
||||
</div>
|
||||
<div class="work-template-name" :title="template.templateName || template.name">{{ template.templateName || template.name }}</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else class="no-activity-placeholder">
|
||||
@@ -28,6 +29,7 @@ const workTemplate = {
|
||||
<p class="subtext">敬请关注后续更新</p>
|
||||
</div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
@@ -36,6 +38,15 @@ const workTemplate = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 每次滚动一屏模板,到末尾后回到开头;无模板时不执行。
|
||||
browseTemplates() {
|
||||
const grid = this.$refs.templateGrid
|
||||
if (!grid) return
|
||||
// 模板仍使用全部真实数据,横向翻页不截断管理员可用的入口。
|
||||
const atEnd = grid.scrollLeft + grid.clientWidth >= grid.scrollWidth - 2
|
||||
grid.scrollTo({left: atEnd ? 0 : grid.scrollLeft + grid.clientWidth, behavior: 'smooth'})
|
||||
},
|
||||
// 无请求参数;Result.data 为当前管理员可用的工作模板数组。
|
||||
listTemplate() {
|
||||
this.$axios.post("/platform/home/listHomeTemplate").then((res) => {
|
||||
if (res.code === 0) {
|
||||
|
||||
@@ -4,40 +4,37 @@ const user = {
|
||||
<!-- 用户容器:左右布局 -->
|
||||
<div class="user-container">
|
||||
<!-- 左侧:用户信息卡片 -->
|
||||
<div class="user-info-card">
|
||||
<div v-if="profileOnly" class="user-info-card">
|
||||
<div class="portal-member-date"><i class="fa fa-calendar-o" aria-hidden="true"></i>{{ $moment().format('YYYY年MM月DD日') }} {{ ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'][$moment().day()] }}</div>
|
||||
<div class="user-details">
|
||||
<div class="user-name">${@auth.getPrincipalProperty('username')}</div>
|
||||
<div class="portal-member-heading">
|
||||
<span class="portal-member-avatar"><i class="fa fa-user" aria-hidden="true"></i></span>
|
||||
<div><div class="portal-member-welcome">欢迎回来</div><div class="user-name">{{ profile.username }}</div></div>
|
||||
</div>
|
||||
|
||||
<div class="user-meta-grid">
|
||||
<div class="meta-row">
|
||||
<span class="meta-icon">🆔</span>
|
||||
<span class="meta-icon"><i class="fa fa-id-card-o" aria-hidden="true"></i></span>
|
||||
<div>
|
||||
<span class="meta-label">工号</span>
|
||||
<span class="meta-value">${@auth.getPrincipalProperty('loginname')}</span>
|
||||
<span class="meta-value">{{ profile.loginname }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="meta-row">
|
||||
<span class="meta-icon">🏢</span>
|
||||
<span class="meta-icon"><i class="fa fa-building-o" aria-hidden="true"></i></span>
|
||||
<div>
|
||||
<span class="meta-label">单位信息</span>
|
||||
<span class="meta-value">
|
||||
<!--#if(!isEmpty(@auth.getPrincipalProperty('unit'))){#-->
|
||||
${@auth.getPrincipalProperty('unit').getName()}
|
||||
<!--#}#-->
|
||||
(
|
||||
<!--#if(!isEmpty(@auth.getPrincipalProperty('union'))){#-->
|
||||
${@auth.getPrincipalProperty('union').getName()}
|
||||
<!--#}#-->
|
||||
)
|
||||
{{ profile.unitName }} ({{ profile.unionName }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-icon">🎭</span>
|
||||
<div class="meta-row meta-row-roles">
|
||||
<span class="meta-icon"><i class="fa fa-shield" aria-hidden="true"></i></span>
|
||||
<div>
|
||||
<span class="meta-label">角色</span>
|
||||
<span class="meta-value">{{ roleNames || '—' }}</span>
|
||||
<div class="portal-member-roles" tabindex="0" role="region" aria-label="角色列表,可滚动查看全部角色"><span v-for="(role, index) in roleTags" :key="index" class="portal-member-role">{{ role }}</span><span v-if="!roleTags.length" class="meta-value">—</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,10 +60,13 @@ const user = {
|
||||
</div>
|
||||
|
||||
<!-- 右侧:待办中心 -->
|
||||
<div class="todo-center">
|
||||
<div v-if="!profileOnly" class="todo-center">
|
||||
<!-- 标题和标签 -->
|
||||
<div class="header">
|
||||
<h3>待办中心</h3>
|
||||
<div class="portal-todo-heading">
|
||||
<h3><i class="fa fa-list-ul" aria-hidden="true"></i> 待办中心</h3>
|
||||
<a class="portal-more" :href="'/flow/todoCenter?status=' + activeTab" target="_blank">全部待办 <i class="fa fa-arrow-right" aria-hidden="true"></i></a>
|
||||
</div>
|
||||
<el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
|
||||
<el-tab-pane :label="tabLabel('待办事宜', taskStats.todoCount)" name="todo"></el-tab-pane>
|
||||
<el-tab-pane :label="tabLabel('已办事宜', taskStats.doneCount)" name="done"></el-tab-pane>
|
||||
@@ -75,55 +75,44 @@ const user = {
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
:data="todoList"
|
||||
style="width: 100%"
|
||||
border
|
||||
stripe
|
||||
v-loading="loading"
|
||||
:cell-style="{ padding: '8px' }"
|
||||
:header-cell-style="{ background: '#f8f9fa', color: '#444', fontWeight: '500' }"
|
||||
>
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="100px"></el-table-column>
|
||||
<el-table-column prop="categoryName" label="流程分类" width="100"></el-table-column>
|
||||
<el-table-column prop="title" label="流程名称" min-width="300">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip effect="dark" :content="scope.row.instanceName" placement="top">
|
||||
<span class="table-title">{{ scope.row.instanceName }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sender" label="申请人" width="120">
|
||||
<template slot-scope="{row}">{{row.variable.initiatorName}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskName" label="当前流程节点" width="140" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="date" label="发起日期" width="120">
|
||||
<template slot-scope="{row}">
|
||||
{{$moment(row.createdAt).format('YYYY-MM-DD')}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100px" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="primary" size="mini" @click="openView(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
<div class="portal-task-list" v-loading="tableLoading">
|
||||
<div class="portal-task-row" v-for="(task, index) in tableData" :key="task.taskId || task.instanceId || index">
|
||||
<i class="fa fa-file-text-o portal-task-icon" aria-hidden="true"></i>
|
||||
<div class="portal-task-content">
|
||||
<div class="portal-task-title" :title="task.instanceName">{{ task.instanceName }}</div>
|
||||
<div class="portal-task-meta">
|
||||
<span :title="task.variable && task.variable.initiatorName">申请人:{{ (task.variable && task.variable.initiatorName) || '—' }}</span>
|
||||
<span class="portal-task-node" :title="[task.categoryName, task.taskName].filter(Boolean).join(' · ')">{{ [task.categoryName, task.taskName].filter(Boolean).join(' · ') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<time class="portal-task-date">{{ task.createdAt ? $moment(task.createdAt).format('YYYY-MM-DD') : '—' }}</time>
|
||||
<el-button type="primary" size="mini" @click="openView(task)">查看</el-button>
|
||||
</div>
|
||||
<div v-if="!tableLoading && !tableData.length" class="portal-empty"><i class="fa fa-check-circle-o" aria-hidden="true"></i><span>暂无{{ activeTab === 'todo' ? '待办事项' : '相关记录' }}</span></div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<el-pagination v-if="pageForm.totalCount > pageForm.pageSize" class="portal-task-pagination"
|
||||
@current-change="pageNumberChange" :current-page="pageForm.pageNumber" :page-size="pageForm.pageSize"
|
||||
layout="total, prev, next" :total="pageForm.totalCount"></el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
mixins: [initTableMixins],
|
||||
// 同一组件分别承载会员卡和待办卡,各自只加载需要的数据。
|
||||
props: {
|
||||
profileOnly: { type: Boolean, default: false },
|
||||
// 首页传入显示资料:username 姓名、loginname 工号、unitName 单位、unionName 工会;缺项显示空文本。
|
||||
profile: {
|
||||
type: Object,
|
||||
default: () => ({username: '', loginname: '', unitName: '', unionName: ''})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'todo',
|
||||
todoList: [],
|
||||
loading: false,
|
||||
pageForm: {
|
||||
pageNum: 1,
|
||||
pageSize: 5,
|
||||
totalCount: 0
|
||||
pageSize: 3
|
||||
},
|
||||
taskStats: {
|
||||
todoCount: 0,
|
||||
@@ -140,6 +129,12 @@ const user = {
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
roleTags() {
|
||||
// 兼容角色接口已有的中英文分隔符,仅改变展示,不改变角色权限。
|
||||
return String(this.roleNames || '').split(/[,,、;;]/).map(role => role.trim()).filter(Boolean)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getWeatherInfo(iconCode, temp) {
|
||||
const info = {
|
||||
@@ -189,91 +184,60 @@ const user = {
|
||||
|
||||
return info;
|
||||
},
|
||||
async getConfigKey(key) {
|
||||
const resp = await this.$axios.post("/open/common/getConfigKey", { key })
|
||||
return resp.data
|
||||
// key 为系统配置名称;返回 Promise,值为 Result.data 中的经度或纬度。
|
||||
getConfigKey(key) {
|
||||
return this.$axios.post("/open/common/getConfigKey", {key}).then(res => res.data)
|
||||
},
|
||||
// 🔆 获取天气
|
||||
async fetchWeather() {
|
||||
try {
|
||||
// 存localStorage 112.13,32.01 广州 113.27,23.14
|
||||
const AppMapCenterPointX = await this.getConfigKey("AppMapCenterPointX")
|
||||
const AppMapCenterPointY = await this.getConfigKey("AppMapCenterPointY")
|
||||
if (!AppMapCenterPointX || !AppMapCenterPointY){
|
||||
return
|
||||
}
|
||||
|
||||
// 1. 先通过城市名获取 locationId
|
||||
this.$axios.get("https://mx5rk62kvj.re.qweatherapi.com/geo/v2/city/lookup?location=" + AppMapCenterPointX + "," + AppMapCenterPointY + "&key=" + this.QWEATHER_KEY).then((geoRes) => {
|
||||
if (geoRes.code !== '200' || !geoRes.location?.[0]) {
|
||||
console.log('城市未找到')
|
||||
}
|
||||
const locationId = geoRes.location[0].id;
|
||||
|
||||
// 2. 获取当前天气
|
||||
this.$axios.get("https://mx5rk62kvj.re.qweatherapi.com/v7/weather/now?location=" + locationId + "&key=" + this.QWEATHER_KEY).then((weatherRes) => {
|
||||
if (weatherRes.code !== '200') {
|
||||
console.warn('天气数据异常:', weatherRes);
|
||||
}
|
||||
const weatherInfo = this.getWeatherInfo(weatherRes.now.icon, weatherRes.now.temp);
|
||||
this.weatherData = {
|
||||
...weatherInfo,
|
||||
temp: weatherRes.now.temp,
|
||||
text: weatherRes.now.text,
|
||||
name: geoRes.location[0].name
|
||||
};
|
||||
this.weatherError = false;
|
||||
})
|
||||
|
||||
|
||||
// 使用本校配置经纬度查询城市及天气,缺少配置时不请求外部天气接口。
|
||||
fetchWeather() {
|
||||
Promise.all([this.getConfigKey("AppMapCenterPointX"), this.getConfigKey("AppMapCenterPointY")])
|
||||
.then(coordinates => {
|
||||
if (!coordinates[0] || !coordinates[1]) return
|
||||
return this.$axios.get("https://mx5rk62kvj.re.qweatherapi.com/geo/v2/city/lookup?location=" + coordinates.join(",") + "&key=" + this.QWEATHER_KEY)
|
||||
.then(geoRes => {
|
||||
if (geoRes.code !== '200' || !geoRes.location || !geoRes.location.length) {
|
||||
throw new Error('城市未找到')
|
||||
}
|
||||
const city = geoRes.location[0]
|
||||
return this.$axios.get("https://mx5rk62kvj.re.qweatherapi.com/v7/weather/now?location=" + city.id + "&key=" + this.QWEATHER_KEY)
|
||||
.then(weatherRes => {
|
||||
if (weatherRes.code !== '200' || !weatherRes.now) throw new Error('天气数据异常')
|
||||
this.weatherData = Object.assign({}, this.getWeatherInfo(weatherRes.now.icon, weatherRes.now.temp), {
|
||||
temp: weatherRes.now.temp, text: weatherRes.now.text, name: city.name
|
||||
})
|
||||
this.weatherError = false
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
this.weatherError = true
|
||||
this.weatherData = null
|
||||
})
|
||||
|
||||
} catch (err) {
|
||||
console.warn('天气加载失败:', err);
|
||||
this.weatherError = true;
|
||||
this.weatherData = null;
|
||||
}
|
||||
},
|
||||
// 获取任务列表
|
||||
tabLabel(title, count) {
|
||||
return title + "(" + (count || 0) + ")"
|
||||
},
|
||||
async getStatistics() {
|
||||
try {
|
||||
const res = await $.post("/flow/todoCenter/statistics")
|
||||
if (res.code === 0) {
|
||||
this.taskStats = Object.assign({}, this.taskStats, res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取待办统计失败:", error)
|
||||
}
|
||||
},
|
||||
async getTasks() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await $.post("/flow/todoCenter/" + this.activeTab, this.pageForm)
|
||||
if (res.code === 0) {
|
||||
this.todoList = res.data.list
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取任务列表失败")
|
||||
console.error("获取任务列表失败:", error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
// 无请求参数;data 的四个 Count 字段分别为当前用户各状态流程数量。
|
||||
getStatistics() {
|
||||
this.$axios.post("/flow/todoCenter/statistics").then(res => {
|
||||
if (res.code === 0) this.taskStats = Object.assign({}, this.taskStats, res.data)
|
||||
}).catch(() => {
|
||||
this.$message.error("获取待办统计失败")
|
||||
})
|
||||
},
|
||||
// WF 状态由 activeTab 决定;pageForm 传分页参数,data.list/totalCount 为记录及总数。
|
||||
// 列表加载和翻页由 initTableMixins 统一处理,首页只补充状态地址和数量统计。
|
||||
pageData() {
|
||||
this.getTasks()
|
||||
this.pageDataUrl = "/flow/todoCenter/" + this.activeTab
|
||||
initTableMixins.methods.pageData.call(this)
|
||||
this.getStatistics()
|
||||
},
|
||||
handleTabClick(tab) {
|
||||
this.pageData()
|
||||
console.log('切换到:', tab.name);
|
||||
handleTabClick() {
|
||||
this.doSearch()
|
||||
},
|
||||
// 处理任务
|
||||
async openView(task) {
|
||||
console.log(task)
|
||||
openView(task) {
|
||||
const {taskId, taskKey, taskState, instanceId, businessNo, formKey} = task
|
||||
if (!formKey) {
|
||||
this.$message.warning("当前流程没有配置地址")
|
||||
@@ -281,8 +245,9 @@ const user = {
|
||||
}
|
||||
window.open(formKey + "?taskId=" + taskId + "&bizId=" + businessNo + "&taskKey=" + taskKey + "&tab=" + this.activeTab)
|
||||
},
|
||||
// 无请求参数;Result.data.roleNames 为当前登录人的角色名称字符串。
|
||||
getRoleNames() {
|
||||
$.post("/platform/sys/role/getRoleNames").then(res => {
|
||||
this.$axios.post("/platform/sys/role/getRoleNames").then(res => {
|
||||
if (res.code === 0) {
|
||||
this.roleNames = res.data.roleNames
|
||||
}
|
||||
@@ -291,13 +256,45 @@ const user = {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.getRoleNames()
|
||||
if (this.profileOnly) {
|
||||
this.getRoleNames()
|
||||
} else {
|
||||
this.pageData()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchWeather()
|
||||
if (this.profileOnly) this.fetchWeather()
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
/* 会员卡样式随组件注入,确保与卡片结构同步加载,避免旧外链样式造成排版缺失。 */
|
||||
#v4-home-app .portal-member { min-width: 0; }
|
||||
#v4-home-app .portal-member .user-info-card { height: 100%; padding: 20px; gap: 14px; border: 1px solid #e3edf8; border-radius: 12px; background: linear-gradient(180deg, #f1f7ff 0, #fff 110px); box-shadow: 0 3px 14px rgba(30,66,108,.035); }
|
||||
#v4-home-app .portal-member-date { display: flex; align-items: center; gap: 8px; color: #71839b; font-size: 12px; line-height: 20px; }
|
||||
#v4-home-app .portal-member-heading { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid #eaf0f7; }
|
||||
#v4-home-app .portal-member-heading > div { min-width: 0; }
|
||||
#v4-home-app .portal-member-avatar { display: flex; align-items: center; justify-content: center; flex: 0 0 46px; height: 46px; border-radius: 50%; background: #e1eeff; color: #1687f5; font-size: 25px; }
|
||||
#v4-home-app .portal-member-welcome { margin-bottom: 3px; color: #8797a9; font-size: 12px; line-height: 18px; }
|
||||
#v4-home-app .portal-member .user-name { margin: 0; color: #163c61; text-align: left; font-size: 18px; line-height: 24px; overflow-wrap: anywhere; }
|
||||
#v4-home-app .portal-member .user-meta-grid { gap: 12px; margin: 0; }
|
||||
#v4-home-app .portal-member .meta-row { gap: 8px; }
|
||||
#v4-home-app .portal-member .meta-icon { min-width: 18px; color: #81a4c9; font-size: 14px; }
|
||||
#v4-home-app .portal-member .meta-row > div { display: grid; grid-template-columns: 54px minmax(0, 1fr); gap: 8px; min-width: 0; }
|
||||
#v4-home-app .portal-member .meta-label { margin: 0; color: #8995a5; font-size: 12px; line-height: 20px; }
|
||||
#v4-home-app .portal-member .meta-value { color: #283c54; font-size: 12px; font-weight: 400; line-height: 20px; overflow-wrap: anywhere; }
|
||||
#v4-home-app .portal-member .meta-row-roles > div { display: block; }
|
||||
/* 角色最多占用三行标签的高度,超出时内部滚动,避免撑高首页横幅。 */
|
||||
#v4-home-app .portal-member-roles { display: flex; flex-wrap: wrap; align-content: flex-start; gap: 5px; margin-top: 5px; max-height: 88px; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; scrollbar-color: #c5d9ee transparent; }
|
||||
#v4-home-app .portal-member-roles::-webkit-scrollbar { width: 4px; }
|
||||
#v4-home-app .portal-member-roles::-webkit-scrollbar-thumb { background: #c5d9ee; border-radius: 4px; }
|
||||
#v4-home-app .portal-member-role { max-width: 100%; display: inline-block; padding: 3px 8px; border: 1px solid #e1ebf7; border-radius: 5px; background: #f3f7fc; color: #46617f; font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
|
||||
#v4-home-app .portal-member .weather-section { margin-top: auto; padding: 8px 10px; border-radius: 8px; background: #f0f7ff; color: #527494; }
|
||||
#v4-home-app .portal-member .weather-header { gap: 6px; }
|
||||
#v4-home-app .portal-member .weather-location,
|
||||
#v4-home-app .portal-member .weather-text { font-size: 12px; }
|
||||
#v4-home-app .portal-member .weather-emoji { font-size: 20px; }
|
||||
#v4-home-app .portal-member .weather-temp { font-size: 18px; }
|
||||
#v4-home-app .portal-member .weather-tip { margin-top: 4px; font-size: 11px; line-height: 18px; }
|
||||
#v4-home-app .portal-member .weather-error { margin-top: auto; padding: 8px; font-size: 12px; }
|
||||
.user-wrapper {
|
||||
width: 100%;
|
||||
background-color: #f5f7fa;
|
||||
|
||||
@@ -155,14 +155,13 @@ layout("/layouts/platform.html"){
|
||||
</el-button>
|
||||
<el-button @click="$refs.sortRef.onOpen()" size="medium" type="primary" icon="el-icon-sort">排序
|
||||
</el-button>
|
||||
<el-button v-if="$auth.hasPermission('sys.manager.role.user')" size="medium" type="primary"
|
||||
icon="el-icon-user" :loading="memberRoleLoading" @click="doSettingMemberRole">
|
||||
一键设置会员角色
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<!-- <div class="pull-right offscreen-right mt5 ml5">-->
|
||||
<!-- <el-button size="medium" @click="doSettingMemberRole" type="primary"><i class="el-icon-s-tools"></i>-->
|
||||
<!-- 一键设会员角色-->
|
||||
<!-- </el-button>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="pull-right offscreen-right mt5 ml5">-->
|
||||
<!-- <el-button size="medium" @click="doPublic" type="primary">一键设置公共角色</el-button>-->
|
||||
<!-- </div>-->
|
||||
@@ -511,6 +510,7 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
}
|
||||
return {
|
||||
memberRoleLoading: false,
|
||||
addDialogVisible: false,
|
||||
editDialogVisible: false,
|
||||
roleDialogVisible: false,
|
||||
@@ -689,18 +689,30 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
},
|
||||
doSettingMemberRole() {
|
||||
this.$confirm("是否确定一键设置会员角色?", "提示", {
|
||||
if (this.memberRoleLoading) {
|
||||
return
|
||||
}
|
||||
// 操作面向全部会员,忽略当前列表筛选;确认期间也锁定按钮,避免重复提交。
|
||||
this.$set(this, "memberRoleLoading", true)
|
||||
this.$confirm("确定为全部已标记为会员的人员设置会员角色吗?已有角色将保留。", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(async () => {
|
||||
const {code, msg} = await this.$axios.post("/platform/sys/role/doSettingMemberRole")
|
||||
if (code === 0) {
|
||||
this.$message.success(msg)
|
||||
}
|
||||
.then(() => {
|
||||
return this.$axios.post("/platform/sys/role/doSettingMemberRole").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("设置完成:新增 " + res.data.addedCount
|
||||
+ " 条,恢复启用 " + res.data.enabledCount + " 条会员角色关联")
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
// 取消确认无需提示;请求异常由全局 axios 拦截器统一提示。
|
||||
})
|
||||
.finally(() => {
|
||||
this.$set(this, "memberRoleLoading", false)
|
||||
})
|
||||
},
|
||||
formatAt(val) {
|
||||
|
||||
@@ -3,8 +3,12 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
<div>
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<snaker-start slot="header" label="文化活动申报" define_key="WHHD"
|
||||
v-if="activity_type===40002||activity_type===40003"></snaker-start>
|
||||
<div slot="header" v-if="activity_type===40002||activity_type===40003"
|
||||
style="display: flex; align-items: center; justify-content: space-between; width: 100%">
|
||||
<h3 style="color: var(--color-primary); margin: 0">文化活动申报</h3>
|
||||
<el-link type="primary" @click="$refs.activityChartRef.onOpen('WHHD')">流程图</el-link>
|
||||
</div>
|
||||
<snaker-chart v-if="activity_type===40002||activity_type===40003" ref="activityChartRef"></snaker-chart>
|
||||
<el-form :model="formData" :rules="formRules" label-suffix=":" label-width="170px"
|
||||
ref="addForm" v-loading="formLoading">
|
||||
<el-tabs tab-position="top" v-model="activeName">
|
||||
@@ -171,7 +175,7 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
@click="$refs.drawerUserScope.userScopeDialog = true"
|
||||
@click="openUserScope"
|
||||
type="primary">设置
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -370,12 +374,14 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!--
|
||||
<el-col :span="24">
|
||||
<el-form-item label="报名表单" prop="formConfig"
|
||||
v-if="formData.isEnrollSystem&&formData.signUpMethod===1">
|
||||
<custom-form-field v-model="formData.formConfig"></custom-form-field>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
-->
|
||||
|
||||
<el-col :span="24" v-if="formData.signUpMethod!=null&&formData.isEnrollSystem">
|
||||
<el-form-item label="封面图" prop="cover">
|
||||
@@ -977,13 +983,20 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
this.$set(this.formData, "time", val)
|
||||
},
|
||||
userChang(id) {
|
||||
const userList = this.$refs.userData.options
|
||||
this.$set(this.formData, "mobile", id ? userList.find((v) => v.id === id).mobile : null)
|
||||
const user = (this.userList || []).find((v) => v.userId === id)
|
||||
this.$set(this.formData, "mobile", id && user ? user.mobile : null)
|
||||
},
|
||||
async getActivityTwoLevelType(code) {
|
||||
const {data} = await this.$axios.post("/platform/activity/basic/settings/getActivityTwoLevelType", {code: code})
|
||||
return data
|
||||
},
|
||||
openUserScope() {
|
||||
if (!this.$auth.hasRole("SYSADMIN") && !this.$auth.hasPermission("activity.basic.scope")) {
|
||||
this.$message.error("无此权限:activity.basic.scope")
|
||||
return
|
||||
}
|
||||
this.$refs.drawerUserScope.userScopeDialog = true
|
||||
},
|
||||
async getActivityGroup() {
|
||||
const {data} = await this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup")
|
||||
this.activityGroupList = data
|
||||
@@ -1048,9 +1061,16 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
|
||||
async findOne(id) {
|
||||
const {data, code, msg} = await this.$axios.post("/platform/activity/culture/infoManage/findOne", {id})
|
||||
if (code === 0) {
|
||||
this.userList = []
|
||||
if (data.userId) {
|
||||
const user = await this.queryUserByIds([data.userId])
|
||||
await this.remoteMethod(user[0].loginname)
|
||||
let loginName = data.loginname
|
||||
if (!loginName) {
|
||||
const users = await this.queryUserByIds([data.userId])
|
||||
loginName = users && users.length > 0 ? users[0].loginname : null
|
||||
}
|
||||
if (loginName) {
|
||||
await this.remoteMethod(loginName)
|
||||
}
|
||||
}
|
||||
if (data.goods) data.goods = JSON.parse(data.goods)
|
||||
if (data.billFiles && data.billFiles.length > 0) data.billFiles = JSON.parse(data.billFiles)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
const ACTIVITY_CULTURE_INFO_MANAGE = {
|
||||
template: /*language=HTML*/`
|
||||
<guava ref="guava">
|
||||
<guava ref="guava" @hook:mounted="initTableResize">
|
||||
<template>
|
||||
<div class="culture-info-manage-layout">
|
||||
<el-card class="culture-info-query-card" shadow="never">
|
||||
@@ -55,45 +55,49 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns">
|
||||
|
||||
<template scope="{row}" v-if="column.prop=='time2'">
|
||||
{{row.applyStartTime}}-{{row.applyEndTime}}
|
||||
</template>
|
||||
|
||||
<template scope="{row}" v-else-if="column.prop=='projectTypeName'">
|
||||
{{row.projectTypeName}}({{row.projectTypeCode}})
|
||||
</template>
|
||||
<template slot-scope="{row}">
|
||||
<template v-if="column.prop=='time2'">
|
||||
{{row.applyStartTime}}-{{row.applyEndTime}}
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.prop=='projectTypeName'">
|
||||
{{row.projectTypeName}}({{row.projectTypeCode}})
|
||||
</template>
|
||||
|
||||
<template scope="{row}" v-else-if="column.prop=='tussueName'">
|
||||
<span v-if="row.activity_type==40002">{{row.unionname?row.unionname:'暂无'}}</span>
|
||||
<span v-if="row.activity_type==40003">{{row.clubName?row.clubName:'暂无'}}</span>
|
||||
<span v-if="row.activity_type==40001">校工会</span>
|
||||
<template v-else-if="column.prop=='tussueName'">
|
||||
<span v-if="row.activity_type==40002">{{row.unionname?row.unionname:'暂无'}}</span>
|
||||
<span v-if="row.activity_type==40003">{{row.clubName?row.clubName:'暂无'}}</span>
|
||||
<span v-if="row.activity_type==40001">校工会</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.prop=='isUnseal'">
|
||||
<el-switch
|
||||
@change="(val)=>{activityStatusChange(row.id,row.isUnseal)}"
|
||||
active-color="#13ce66"
|
||||
:disabled="isUnsealDisabled(row)"
|
||||
inactive-color="#ff4949"
|
||||
v-model="row.isUnseal">
|
||||
</el-switch>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.prop=='taskName'">
|
||||
<span v-if="row.activity_type==40002 || row.activity_type==40003">
|
||||
{{row.taskName}}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.prop=='instanceState'">
|
||||
<span v-if="row.activity_type==40002 || row.activity_type==40003">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template v-else>{{ row[column.prop] }}</template>
|
||||
</template>
|
||||
|
||||
<template scope="{row}" v-else-if="column.prop=='isUnseal'">
|
||||
<el-switch
|
||||
@change="(val)=>{activityStatusChange(row.id,row.isUnseal)}"
|
||||
active-color="#13ce66"
|
||||
:disabled="isUnsealDisabled(row)"
|
||||
inactive-color="#ff4949"
|
||||
v-model="row.isUnseal">
|
||||
</el-switch>
|
||||
</template>
|
||||
|
||||
<template scope="{row}" v-else-if="column.prop=='taskName'">
|
||||
<span v-if="row.activity_type==40002 || row.activity_type==40003">
|
||||
{{row.taskName}}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template scope="{row}" v-else-if="column.prop=='instanceState'">
|
||||
<span v-if="row.activity_type==40002 || row.activity_type==40003">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" header-align="center" label="操作"
|
||||
@@ -110,7 +114,7 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item :command="{type:'edit',row}"
|
||||
>
|
||||
v-if="activity_type !== 40003 || !row.instanceId || row.taskKey === 'startTask'">
|
||||
编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item u
|
||||
@@ -214,6 +218,19 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
|
||||
"activity-culture-info-activity": ACTIVITY_CULTURE_INFO_ACTIVITY
|
||||
},
|
||||
methods: {
|
||||
initTableResize() {
|
||||
if (!this.isTemplateManagedActivity) {
|
||||
return
|
||||
}
|
||||
const listCardBody = this.$refs.guava.$el.querySelector(".culture-info-list-card > .el-card__body")
|
||||
if (listCardBody && window.ResizeObserver) {
|
||||
// 查询区换行、页签权限或窗口变化时同步调整表格内部滚动区域。
|
||||
this.tableResizeObserver = new ResizeObserver(this.updateTableHeight)
|
||||
this.tableResizeObserver.observe(listCardBody)
|
||||
}
|
||||
this.updateTableHeight()
|
||||
window.addEventListener("resize", this.updateTableHeight)
|
||||
},
|
||||
activityStatusChange(id, isUnseal) {
|
||||
this.$axios.post("/platform/activity/culture/infoManage/activityStatusChange", {
|
||||
id: id,
|
||||
@@ -264,7 +281,11 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
|
||||
return
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
const cardBody = this.$el.querySelector(".culture-info-list-card > .el-card__body")
|
||||
const root = this.$refs.guava && this.$refs.guava.$el
|
||||
if (!root || typeof root.querySelector !== "function") {
|
||||
return
|
||||
}
|
||||
const cardBody = root.querySelector(".culture-info-list-card > .el-card__body")
|
||||
if (!cardBody || cardBody.clientHeight <= 0) {
|
||||
return
|
||||
}
|
||||
@@ -408,19 +429,6 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
|
||||
}
|
||||
this.pageData()
|
||||
},
|
||||
mounted() {
|
||||
if (!this.isTemplateManagedActivity) {
|
||||
return
|
||||
}
|
||||
const listCardBody = this.$el.querySelector(".culture-info-list-card > .el-card__body")
|
||||
if (listCardBody && window.ResizeObserver) {
|
||||
// 查询区换行、页签权限或窗口变化时同步调整表格内部滚动区域。
|
||||
this.tableResizeObserver = new ResizeObserver(this.updateTableHeight)
|
||||
this.tableResizeObserver.observe(listCardBody)
|
||||
}
|
||||
this.updateTableHeight()
|
||||
window.addEventListener("resize", this.updateTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.tableResizeObserver) {
|
||||
this.tableResizeObserver.disconnect()
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ layout("/layouts/platform.html"){
|
||||
size="mini" type="danger">删除
|
||||
</el-button>
|
||||
|
||||
<el-button @click="doExportDeclare(row)" size="mini" type="primary">申请表导出</el-button>
|
||||
<el-button v-if="row.instanceState === 20" @click="doExportDeclare(row)" size="mini" type="primary">申请表导出</el-button>
|
||||
<el-button v-if="row.instanceState === 20" @click="doExportReimbursement(row)" size="mini"
|
||||
type="primary">报销凭证导出
|
||||
</el-button>
|
||||
|
||||
@@ -221,8 +221,7 @@ layout("/layouts/platform.html"){
|
||||
placeholder="请输入姓名" filterable clearable
|
||||
style="width: 100%" size="small" v-if="awardsMode==1">
|
||||
<el-option
|
||||
v-for="item in userList"
|
||||
:disabled="item.disabled"
|
||||
v-for="item in getSelectableUserList(row)"
|
||||
:key="item.id"
|
||||
:label="item.username+'('+item.loginname+')'+item.sex"
|
||||
:value="item.id">
|
||||
@@ -721,10 +720,15 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
return score;
|
||||
},
|
||||
getSelectableUserList(row) {
|
||||
// 其他行已经选择的人员不再显示,当前行人员保留用于姓名正常回显。
|
||||
const selectedUserIds = this.userData
|
||||
.map(item => item.userId)
|
||||
.filter(userId => userId)
|
||||
return this.userList.filter(item => item.id === row.userId || !selectedUserIds.includes(item.id))
|
||||
},
|
||||
userDetailsChange(val, row) {
|
||||
const useridArr = this.userData.map(v => v.userId)
|
||||
this.userList.forEach(v => {
|
||||
v.disabled = useridArr.includes(v.id)
|
||||
if (row && row.userId === v.id) {
|
||||
const o = {
|
||||
unionId: v.unionId,
|
||||
|
||||
@@ -601,6 +601,8 @@
|
||||
eventForm: {
|
||||
isLeader: false
|
||||
},
|
||||
// 独立记录当前编辑项目,避免下拉框切换后丢失切换前项目标识。
|
||||
currentEventId: "",
|
||||
eventsIds: [],
|
||||
rightChecked: [],
|
||||
checkedEventInfo: [],
|
||||
@@ -778,8 +780,10 @@
|
||||
this.checkedEventInfo = this.checkedEvents.map((v) => {
|
||||
v.eventId = v.id
|
||||
if (v.projectType === "1") {
|
||||
v.athletesMaxNum = 3
|
||||
v.restrictRegNumber = 2
|
||||
// “所有单项”保存默认值,具体单项以0表示尚未个性化设置。
|
||||
const isAllItems = v.eventId === "allItems"
|
||||
v.athletesMaxNum = isAllItems ? 3 : 0
|
||||
v.restrictRegNumber = isAllItems ? 2 : 0
|
||||
v.leanderNum = 0
|
||||
v.coachNum = 0
|
||||
v.applyUserModel = 1
|
||||
@@ -791,7 +795,23 @@
|
||||
return v
|
||||
})
|
||||
},
|
||||
cacheCurrentEventForm() {
|
||||
if (!this.currentEventId || !this.eventForm) {
|
||||
return
|
||||
}
|
||||
const current = clone(this.eventForm)
|
||||
current.checkedEventId = this.currentEventId
|
||||
current.eventId = this.currentEventId
|
||||
|
||||
const index = this.checkedEventInfo.findIndex((item) => item.eventId === this.currentEventId)
|
||||
if (index >= 0) {
|
||||
this.checkedEventInfo.splice(index, 1, current)
|
||||
} else {
|
||||
this.checkedEventInfo.push(current)
|
||||
}
|
||||
},
|
||||
async eventChange(val) {
|
||||
this.cacheCurrentEventForm()
|
||||
const data = this.checkedEventInfo.find((v) => v.eventId === val)
|
||||
const resp = await $.get("/platform/activity/sports/common/getEventData", { id: val })
|
||||
await this.$nextTick()
|
||||
@@ -804,10 +824,10 @@
|
||||
projectType: resp.data.projectType,
|
||||
leanderNum: 0,
|
||||
coachNum: 0,
|
||||
athletesMaxNum: resp.data.projectType === 1 ? 3 : null,
|
||||
athletesMaxNum: resp.data.projectType === 1 ? 0 : null,
|
||||
restrictTotalTeam: 1,
|
||||
substituteNum: 0,
|
||||
restrictRegNumber: resp.data.projectType === 1 ? 2 : null,
|
||||
restrictRegNumber: resp.data.projectType === 1 ? 0 : null,
|
||||
isLeader: false,
|
||||
applyUserModel: 1
|
||||
}
|
||||
@@ -815,33 +835,27 @@
|
||||
this.projectType = resp.data.projectType
|
||||
}
|
||||
this.$set(this.eventForm, "checkedEventId", val)
|
||||
this.currentEventId = val
|
||||
},
|
||||
async saveEvent() {
|
||||
saveEvent() {
|
||||
const data = clone(this.eventForm)
|
||||
if (data.checkedEventId === "allItems") {
|
||||
this.checkedEventInfo.forEach((v) => {
|
||||
if (v.projectType === "1") {
|
||||
v.applyUserModel = this.eventForm.applyUserModel
|
||||
v.athletesMaxNum = data.athletesMaxNum
|
||||
v.restrictRegNumber = data.restrictRegNumber
|
||||
v.unionLimit = data.unionLimit
|
||||
}
|
||||
})
|
||||
const allItemsIndex = this.checkedEventInfo.findIndex((v) => v.eventId === "allItems")
|
||||
if (allItemsIndex >= 0) {
|
||||
this.checkedEventInfo.splice(allItemsIndex, 1, data)
|
||||
}
|
||||
} else {
|
||||
const info = this.checkedEventInfo.find((v) => v.eventId === data.checkedEventId)
|
||||
if (info != null) {
|
||||
const index = this.checkedEventInfo.findIndex((v) => v.eventId === data.checkedEventId)
|
||||
this.checkedEventInfo.splice(index, 1)
|
||||
if (info.projectType === "1") {
|
||||
this.$set(this.eventForm, "athletesMaxNum", data.athletesMaxNum)
|
||||
this.$set(this.eventForm, "restrictRegNumber", data.restrictRegNumber)
|
||||
}
|
||||
this.$set(this.eventForm, "checkedEventId", info.eventId)
|
||||
}
|
||||
data.eventId = data.checkedEventId
|
||||
this.checkedEventInfo.push(data)
|
||||
}
|
||||
await this.doSave()
|
||||
this.eventForm = clone(data)
|
||||
this.currentEventId = data.checkedEventId
|
||||
return this.doSave()
|
||||
},
|
||||
prev() {
|
||||
this.active--
|
||||
@@ -949,6 +963,7 @@
|
||||
this.copyTemplateMode = false
|
||||
this.copyTemplateName = ""
|
||||
this.pushTodoFlag = false
|
||||
this.currentEventId = ""
|
||||
this.events = await this.getEvents(2)
|
||||
await this.getActivityGroup()
|
||||
this.active = 0
|
||||
@@ -1028,6 +1043,7 @@
|
||||
async openEdit(row, flag, copyTemplateMode = false) {
|
||||
this.copyTemplateMode = !!copyTemplateMode
|
||||
this.pushTodoFlag = false
|
||||
this.currentEventId = ""
|
||||
this.active = flag ? 0 : this.active
|
||||
await this.getActivityGroup()
|
||||
const { id } = row
|
||||
@@ -1184,7 +1200,8 @@
|
||||
}
|
||||
|
||||
if (this.eventForm.checkedEventId == "allItems") {
|
||||
events = events.filter((v) => v.projectType == 1 && v.id != "allItems")
|
||||
// “所有单项”配置随具体单项提交,由后端统一校验并补齐继承值。
|
||||
events = events.filter((v) => v.projectType == 1)
|
||||
} else {
|
||||
this.$set(this.eventForm, "activityId", this.formData.id)
|
||||
this.$set(this.eventForm, "eventId", this.eventForm.checkedEventId)
|
||||
@@ -1192,9 +1209,10 @@
|
||||
this.$set(this.eventForm, "startAgeDate", this.eventForm.ageDate[0])
|
||||
this.$set(this.eventForm, "endAgeDate", this.eventForm.ageDate[1])
|
||||
}
|
||||
events = this.eventForm
|
||||
const allItems = this.checkedEventInfo.find((v) => v.eventId === "allItems")
|
||||
events = allItems ? [clone(allItems), this.eventForm] : [this.eventForm]
|
||||
if (this.copyTemplateMode) {
|
||||
this.cleanCopyTemplateData(null, [events])
|
||||
this.cleanCopyTemplateData(null, events)
|
||||
}
|
||||
}
|
||||
const loading = this.$loading({
|
||||
@@ -1215,6 +1233,7 @@
|
||||
this.copyTemplateMode = false
|
||||
}
|
||||
await this.openEdit(formData, false)
|
||||
this.currentEventId = this.eventForm.checkedEventId || ""
|
||||
this.$message.success(resp.msg)
|
||||
} else {
|
||||
this.$message.error(resp.msg)
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
const REGISTER_INFO_COMPONENT = {
|
||||
// 默认展示换届报告,仅由不需要查看该字段的业务页面显式关闭。
|
||||
props: {
|
||||
hideReplaceReport: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<el-tabs tab-position="top" v-model="activeName">
|
||||
@@ -36,7 +43,7 @@ const REGISTER_INFO_COMPONENT = {
|
||||
{{viewData.createTime}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="会费标准" :span="4">
|
||||
{{viewData.due}}
|
||||
{{ viewData.due === null || viewData.due === undefined ? "--" : viewData.due + " 元/年" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="4">
|
||||
<template slot="label">社团介绍</template>
|
||||
@@ -66,7 +73,7 @@ const REGISTER_INFO_COMPONENT = {
|
||||
<template slot="label">其他附件</template>
|
||||
<file-preview :files="viewData.files" complete_result></file-preview>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="2">
|
||||
<el-descriptions-item :span="2" v-if="!hideReplaceReport">
|
||||
<template slot="label">换届报告</template>
|
||||
<file-preview :files="viewData.replaceReport" complete_result></file-preview>
|
||||
</el-descriptions-item>
|
||||
|
||||
@@ -68,10 +68,12 @@ layout("/layouts/platform.html"){
|
||||
</el-steps>
|
||||
|
||||
<!-- 第一步:基础信息表单 -->
|
||||
<!-- 本表单自行提示首个必填错误,关闭全局校验弹窗增强以免重复提示。 -->
|
||||
<el-form
|
||||
v-show="step === 0"
|
||||
:model="formData"
|
||||
ref="baseForm"
|
||||
no-auto-enhance
|
||||
:rules="baseRules"
|
||||
label-width="150px"
|
||||
class="main-form"
|
||||
@@ -135,7 +137,9 @@ layout("/layouts/platform.html"){
|
||||
v-model="formData.due"
|
||||
placeholder="请输入会费标准"
|
||||
readonly
|
||||
></el-input>
|
||||
>
|
||||
<template slot="append">元/年</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -146,7 +150,7 @@ layout("/layouts/platform.html"){
|
||||
<el-form-item
|
||||
class="is-required"
|
||||
label="本年度活动开展情况"
|
||||
prop="yearActivity"
|
||||
prop="yearActivityList"
|
||||
>
|
||||
<span class="tip-text">说明:非赛事活动无需填写赛事举办单位、获奖情况。</span>
|
||||
<el-table
|
||||
@@ -638,6 +642,7 @@ layout("/layouts/platform.html"){
|
||||
id: GetQueryString("bizId"),
|
||||
taskId: GetQueryString("taskId"),
|
||||
step: 0, // 当前步骤(0-基础信息,1-财务收支,2-成员名单)
|
||||
formLoading: false,
|
||||
isRegistered: false, // 是否已登记
|
||||
currentClubId: "", // 当前选中社团ID
|
||||
|
||||
@@ -656,7 +661,7 @@ layout("/layouts/platform.html"){
|
||||
year: new Date().getFullYear() + "", // 默认当前年度
|
||||
clubId: "",
|
||||
foundTime: "",
|
||||
due: "",
|
||||
due: null,
|
||||
clubName: "",
|
||||
yearActivityList: [{}], // 本年度活动列表
|
||||
plans: [{}], // 下年度计划列表
|
||||
@@ -671,7 +676,10 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
|
||||
// 表单校验规则
|
||||
baseRules: {},
|
||||
baseRules: {
|
||||
yearActivityList: [{validator: this.validateActivityRows, trigger: ['change', 'blur']}],
|
||||
plans: [{validator: this.validateActivityRows, trigger: ['change', 'blur']}]
|
||||
},
|
||||
financeRules: {},
|
||||
memberRules: {}
|
||||
};
|
||||
@@ -724,6 +732,28 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 在列表级逐行校验,避免嵌套表单项未注册、空数组或纯空白文本绕过必填。
|
||||
validateActivityRows(rule, value, callback) {
|
||||
const currentYear = rule.field === 'yearActivityList';
|
||||
const label = currentYear ? '本年度活动开展情况' : '下一年度活动计划';
|
||||
const fields = currentYear
|
||||
? [['activityTime', '活动时间'], ['activityName', '活动主题/赛事名称'], ['activityAddress', '活动地点'], ['joinNum', '参加人数']]
|
||||
: [['activityName', '活动主题/赛事名称'], ['activityUnit', '活动/赛事主办单位'], ['activityAddress', '活动地点']];
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
callback(new Error(label + '至少填写一条'));
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
for (const field of fields) {
|
||||
const content = value[i] && value[i][field[0]];
|
||||
if (content === null || content === undefined || String(content).trim() === '') {
|
||||
callback(new Error(label + '第' + (i + 1) + '条:' + field[1] + '不能为空'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
callback();
|
||||
},
|
||||
/************************ 通用工具方法 ************************/
|
||||
// 格式化数字(保留2位小数,空值显示0)
|
||||
formatNumber(num) {
|
||||
@@ -1110,7 +1140,7 @@ layout("/layouts/platform.html"){
|
||||
if (!val) return;
|
||||
const club = this.clubOptions.find(item => item.id === val);
|
||||
// 更新社团基础信息
|
||||
this.formData.due = club?.due || "";
|
||||
this.formData.due = club && club.due !== null && club.due !== undefined ? club.due : null;
|
||||
this.formData.foundTime = club?.foundTime
|
||||
? this.$moment(club.foundTime).format("YYYY-MM-DD")
|
||||
: this.$moment().format("YYYY-MM-DD");
|
||||
@@ -1161,73 +1191,63 @@ layout("/layouts/platform.html"){
|
||||
// 步骤前进
|
||||
this.step++;
|
||||
},
|
||||
// 保存
|
||||
async handleSave() {
|
||||
try {
|
||||
this.recalculateCensus();
|
||||
const resp = await this.$axios.post("/platform/club/examine/apply/save", {
|
||||
data: JSON.stringify(this.formData),
|
||||
incomeDetailed: JSON.stringify(this.financeData.incomeDetailed),
|
||||
incomeCensus: JSON.stringify(this.financeData.incomeCensus)
|
||||
});
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg);
|
||||
commonUtil.pjaxPush('/platform/club/examine/mine');
|
||||
// 三个操作共用基础信息校验,失败时返回第一步展示行号和必填字段。
|
||||
validateBaseBeforeAction(action) {
|
||||
if (this.formLoading) return;
|
||||
this.$refs.baseForm.validate((valid, fields) => {
|
||||
if (!valid) {
|
||||
this.step = 0;
|
||||
const firstError = Object.values(fields || {}).flat()[0];
|
||||
this.$message.warning('温馨提示:' + (firstError ? firstError.message : '请补全基础信息必填项'));
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
this.handleError(error, "保存失败");
|
||||
}
|
||||
action();
|
||||
});
|
||||
},
|
||||
// 提交(新增)
|
||||
async handleSubmit() {
|
||||
try {
|
||||
this.recalculateCensus();
|
||||
await this.$confirm("确定要提交此申请吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "info"
|
||||
// 仅接收本页三个固定操作的接口后缀;data为登记实体JSON,另两项为财务数组JSON。
|
||||
// 接口返回{code,msg,data},code为0时保存成功,否则展示后端校验提示。
|
||||
requestExamineAction(action) {
|
||||
if (this.formLoading) return;
|
||||
this.recalculateCensus();
|
||||
const params = {
|
||||
data: JSON.stringify(this.formData),
|
||||
incomeDetailed: JSON.stringify(this.financeData.incomeDetailed),
|
||||
incomeCensus: JSON.stringify(this.financeData.incomeCensus)
|
||||
};
|
||||
if (action === 'submitAgain') params.taskId = this.taskId;
|
||||
this.formLoading = true;
|
||||
return this.$axios.post('/platform/club/examine/apply/' + action, params)
|
||||
.then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg);
|
||||
commonUtil.pjaxPush('/platform/club/examine/mine');
|
||||
} else {
|
||||
this.$message.warning(resp.msg);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 后端校验和网络异常已由全局axios拦截器提示,此处仅结束失败请求,避免再次弹窗。
|
||||
})
|
||||
.finally(() => {
|
||||
this.formLoading = false;
|
||||
});
|
||||
const resp = await this.$axios.post("/platform/club/examine/apply/submit", {
|
||||
data: JSON.stringify(this.formData),
|
||||
incomeDetailed: JSON.stringify(this.financeData.incomeDetailed),
|
||||
incomeCensus: JSON.stringify(this.financeData.incomeCensus)
|
||||
});
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg);
|
||||
commonUtil.pjaxPush('/platform/club/examine/mine');
|
||||
} else {
|
||||
this.$message.warning(resp.msg);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
this.handleError(error, "提交失败");
|
||||
}
|
||||
}
|
||||
},
|
||||
// 提交(任务处理)
|
||||
async handleFinishTask() {
|
||||
try {
|
||||
this.recalculateCensus();
|
||||
await this.$confirm("您确定要提交吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
});
|
||||
const resp = await this.$axios.post('/platform/club/examine/apply/submitAgain', {
|
||||
data: JSON.stringify(this.formData),
|
||||
incomeDetailed: JSON.stringify(this.financeData.incomeDetailed),
|
||||
incomeCensus: JSON.stringify(this.financeData.incomeCensus),
|
||||
taskId: this.taskId
|
||||
});
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg);
|
||||
commonUtil.pjaxPush('/platform/club/examine/mine');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
this.handleError(error, "提交失败");
|
||||
}
|
||||
}
|
||||
handleSave() {
|
||||
this.validateBaseBeforeAction(() => this.requestExamineAction('save'));
|
||||
},
|
||||
handleSubmit() {
|
||||
this.validateBaseBeforeAction(() => {
|
||||
this.$confirm('确定要提交此申请吗?', '提示', {
|
||||
confirmButtonText: '确定', cancelButtonText: '取消', type: 'info'
|
||||
}).then(() => this.requestExamineAction('submit')).catch(() => {});
|
||||
});
|
||||
},
|
||||
handleFinishTask() {
|
||||
this.validateBaseBeforeAction(() => {
|
||||
this.$confirm('您确定要提交吗?', '提示', {
|
||||
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
|
||||
}).then(() => this.requestExamineAction('submitAgain')).catch(() => {});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user